mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 06:37:07 +00:00
751 lines
23 KiB
Vue
751 lines
23 KiB
Vue
<template>
|
|
<v-container fluid>
|
|
|
|
<v-overlay :value="loading && !configuration" absolute>
|
|
<v-progress-circular
|
|
indeterminate
|
|
size="64"
|
|
></v-progress-circular>
|
|
</v-overlay>
|
|
|
|
<v-overlay :value="!configuration && !loading" absolute opacity="0.8">
|
|
<v-row justify="center">
|
|
<v-alert
|
|
type="error"
|
|
>
|
|
The configuration could not be loaded.
|
|
</v-alert>
|
|
</v-row>
|
|
<v-row justify="center">
|
|
<v-btn color="primary" @click="getConfiguration">Retry</v-btn>
|
|
</v-row>
|
|
</v-overlay>
|
|
|
|
<v-window v-model="viewMode">
|
|
<v-window-item>
|
|
<v-row>
|
|
<v-col cols="4" max-height="100%">
|
|
<v-toolbar
|
|
dense
|
|
flat
|
|
@contextmenu="showContextMenu"
|
|
>
|
|
<v-toolbar-title>
|
|
Survey configuration
|
|
</v-toolbar-title>
|
|
<v-spacer/>
|
|
<template v-if="dirty">
|
|
<v-icon left color="primary" @click="saveToFile" title="Save changes to file">mdi-content-save-outline</v-icon>
|
|
<v-icon color="primary" @click="upload" title="Upload changes to server">mdi-cloud-upload</v-icon>
|
|
</template>
|
|
</v-toolbar>
|
|
|
|
<v-switch
|
|
dense
|
|
label="Active"
|
|
title="Inactive surveys do not take new data, but changes to the logs can still be made."
|
|
:disabled="!configuration"
|
|
v-model="surveyState"
|
|
></v-switch>
|
|
|
|
<v-treeview
|
|
dense
|
|
activatable
|
|
hoverable
|
|
:multiple-active="false"
|
|
:active.sync="active"
|
|
:open.sync="open"
|
|
:items="treeview"
|
|
style="cursor:pointer;"
|
|
>
|
|
</v-treeview>
|
|
|
|
</v-col>
|
|
|
|
<v-col cols="8" v-if="activeComponent">
|
|
<component
|
|
:is="activeComponent"
|
|
v-bind="activeValues"
|
|
v-model.sync="configuration"
|
|
@update="activeUpdateHandler"
|
|
@close="deselect"
|
|
></component>
|
|
</v-col>
|
|
<v-col cols="8" v-else>
|
|
<v-card>
|
|
<v-card-text>
|
|
<p>Select a configuration section to change its settings. When clicking <v-btn small color="primary">Save</v-btn> the changes are immediately saved to the server and start to take effect.</p>
|
|
|
|
<v-divider class="my-5"></v-divider>
|
|
|
|
<v-alert border="left" type="warning">
|
|
Be careful when changing configuration settings! It is rather easy to break things.
|
|
</v-alert>
|
|
|
|
<v-alert border="left" type="info">
|
|
On the first save, the survey will be switched to <em>inactive</em> if not already so. This means that no new data will be read. Remember to switch back to <strong>active</strong> when satisfied with your changes.
|
|
</v-alert>
|
|
|
|
<v-divider class="my-5"></v-divider>
|
|
|
|
<p>It is recommended that you download a backup of your configuration before making any changes.</p>
|
|
|
|
<v-divider class="my-5"></v-divider>
|
|
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-btn
|
|
outlined
|
|
color="primary"
|
|
title="Fetch the configuration from the server anew. Any unsaved changes you might have will be lost."
|
|
@click="getConfiguration"
|
|
>
|
|
<v-icon small left>mdi-cloud-refresh</v-icon>
|
|
Refresh
|
|
</v-btn>
|
|
|
|
<v-spacer></v-spacer>
|
|
|
|
<v-btn
|
|
outlined
|
|
class="ml-2"
|
|
color="primary"
|
|
:disabled="!configuration"
|
|
@click="saveToFile"
|
|
title="Save the current configuration to a file, including any changes you might have made but not yet sent to the server."
|
|
>
|
|
<v-icon small left>mdi-content-save-outline</v-icon>
|
|
Save
|
|
</v-btn>
|
|
|
|
<v-dialog
|
|
max-width="400px"
|
|
v-model.sync="fileLoadDialog"
|
|
>
|
|
<template v-slot:activator="{ on, attrs }">
|
|
<v-btn
|
|
outlined
|
|
class="ml-2"
|
|
color="primary lighten-2"
|
|
title="Load the configuration from a file. It will overwrite any changes that you might have made so far but it won't upload the configuration to the server. You can review the configuration and make changes before using the upload button."
|
|
v-bind="attrs"
|
|
v-on="on"
|
|
>
|
|
<v-icon small left>mdi-folder-open-outline</v-icon>
|
|
Load
|
|
</v-btn>
|
|
</template>
|
|
<v-card flat>
|
|
<v-card-text class="pt-5">
|
|
<v-file-input
|
|
v-model="files"
|
|
class="mt-4"
|
|
show-size
|
|
accept="application/json,application/yaml,.json,.yaml"
|
|
label="Select configuration file"
|
|
append-outer-icon="mdi-folder-open-outline"
|
|
:error-messages="fileInputErrors"
|
|
@click:append-outer="loadFromFile"
|
|
></v-file-input>
|
|
</v-card-text>
|
|
</v-card>
|
|
</v-dialog>
|
|
|
|
<v-spacer></v-spacer>
|
|
|
|
<v-btn
|
|
class="ml-2"
|
|
color="warning"
|
|
:disabled="!configuration"
|
|
title="Save the configuration on the server. This will replace the existing configuration. The project will be set to INACTIVE. Change its state back to active once you're satisfied with your changes."
|
|
@click="upload"
|
|
>
|
|
<v-icon small left>mdi-cloud-upload</v-icon>
|
|
Upload
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-col>
|
|
</v-row>
|
|
</v-window-item>
|
|
|
|
<v-window-item>
|
|
<v-row>
|
|
<v-col cols="12">
|
|
<v-toolbar
|
|
dense
|
|
flat
|
|
@contextmenu="showContextMenu"
|
|
>
|
|
<v-toolbar-title>
|
|
Advanced survey configuration
|
|
</v-toolbar-title>
|
|
<v-spacer/>
|
|
<v-btn small outlined @click="viewMode=0">Go to normal configuration</v-btn>
|
|
</v-toolbar>
|
|
</v-col>
|
|
</v-row>
|
|
<v-row>
|
|
<v-col cols="12">
|
|
<dougal-json-builder
|
|
name="Dougal configuration"
|
|
v-model="configuration"
|
|
></dougal-json-builder>
|
|
</v-col>
|
|
</v-row>
|
|
</v-window-item>
|
|
|
|
</v-window>
|
|
|
|
<v-menu
|
|
v-model="contextMenu"
|
|
:position-x="contextMenuX"
|
|
:position-y="contextMenuY"
|
|
absolute
|
|
offset-y
|
|
>
|
|
<v-list dense>
|
|
<v-list-item>
|
|
<v-btn
|
|
small
|
|
outlined
|
|
color="red"
|
|
title="Not a good idea"
|
|
@click="viewMode=1"
|
|
>Advanced configuration…</v-btn>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
</v-container>
|
|
</template>
|
|
|
|
|
|
<script>
|
|
import YAML from 'yaml';
|
|
import { mapActions, mapGetters } from 'vuex';
|
|
import { deepSet } from '@/lib/utils';
|
|
|
|
import DougalJsonBuilder from '@/components/json-builder/json-builder';
|
|
|
|
import DougalProjectSettingsNameId from '@/components/project-settings/name-id';
|
|
import DougalProjectSettingsGroups from '@/components/project-settings/groups';
|
|
import DougalProjectSettingsGeodetics from '@/components/project-settings/geodetics';
|
|
import DougalProjectSettingsBinning from '@/components/project-settings/binning';
|
|
import DougalProjectSettingsFilePath from '@/components/project-settings/file-path';
|
|
import DougalProjectSettingsPreplots from '@/components/project-settings/preplots';
|
|
import DougalProjectSettingsRawP111 from '@/components/project-settings/input-raw-p111';
|
|
import DougalProjectSettingsFinalP111 from '@/components/project-settings/input-final-p111';
|
|
import DougalProjectSettingsRawNTBP from '@/components/project-settings/input-raw-ntbp';
|
|
import DougalProjectSettingsFinalPending from '@/components/project-settings/input-final-pending';
|
|
import DougalProjectSettingsSmartsourceHeader from '@/components/project-settings/input-smartsource-header';
|
|
import DougalProjectSettingsSmartsourceSegy from '@/components/project-settings/input-smartsource-segy';
|
|
import DougalProjectSettingsPlanner from '@/components/project-settings/planner';
|
|
import DougalProjectSettingsOnlineLineNameFormat from '@/components/project-settings/online-line-name-format';
|
|
import DougalProjectSettingsASAQC from '@/components/project-settings/asaqc';
|
|
import DougalProjectSettingsProduction from '@/components/project-settings/production';
|
|
// Temporary placeholder component
|
|
import DougalProjectSettingsNotImplemented from '@/components/project-settings/not-implemented';
|
|
|
|
const components = {
|
|
name_id: DougalProjectSettingsNameId,
|
|
groups: DougalProjectSettingsGroups,
|
|
geodetics: DougalProjectSettingsGeodetics,
|
|
binning: DougalProjectSettingsBinning,
|
|
input_files: DougalProjectSettingsFilePath,
|
|
preplots: DougalProjectSettingsPreplots,
|
|
//raw_data: DougalProjectSettingsNotImplemented,
|
|
raw_data_p111: DougalProjectSettingsRawP111,
|
|
raw_data_smsrc_header: DougalProjectSettingsSmartsourceHeader,
|
|
raw_data_smsrc_segy: DougalProjectSettingsSmartsourceSegy,
|
|
raw_data_ntbp: DougalProjectSettingsRawNTBP,
|
|
//final_data: DougalProjectSettingsNotImplemented,
|
|
final_data_p111: DougalProjectSettingsFinalP111,
|
|
final_data_pending: DougalProjectSettingsFinalPending,
|
|
line_name_format: DougalProjectSettingsOnlineLineNameFormat,
|
|
planner_settings: DougalProjectSettingsPlanner,
|
|
logging: DougalProjectSettingsNotImplemented,
|
|
logging_preset_comments: DougalProjectSettingsNotImplemented,
|
|
logging_labels: DougalProjectSettingsNotImplemented,
|
|
asaqc: DougalProjectSettingsASAQC,
|
|
production: DougalProjectSettingsProduction
|
|
}
|
|
|
|
export default {
|
|
name: "DougalProjectSettings",
|
|
|
|
components: {
|
|
DougalJsonBuilder
|
|
},
|
|
|
|
data () {
|
|
return {
|
|
configuration: null,
|
|
settings: null,
|
|
active: [],
|
|
open: [],
|
|
files: [],
|
|
treeview: [
|
|
/*
|
|
{
|
|
id: 0,
|
|
name: "Archive",
|
|
values: (cfg) => cfg.archived,
|
|
save: (data, cfg) => {
|
|
cfg.archived = data.archived;
|
|
return cfg;
|
|
}
|
|
},
|
|
*/
|
|
{
|
|
id: "name_id",
|
|
name: "Name and ID",
|
|
values: (obj) => ({
|
|
id: obj?.id,
|
|
name: obj?.name
|
|
})
|
|
},
|
|
{
|
|
id: "groups",
|
|
name: "Groups",
|
|
values: (obj) => ({
|
|
groups: obj?.groups
|
|
})
|
|
},
|
|
{
|
|
id: "geodetics",
|
|
name: "Geodetics",
|
|
values: (obj) => ({
|
|
epsg: obj?.epsg
|
|
})
|
|
},
|
|
{
|
|
id: "binning",
|
|
name: "Binning",
|
|
values: (obj) => ({
|
|
...obj.binning
|
|
})
|
|
},
|
|
{
|
|
id: "input_files",
|
|
name: "Input files",
|
|
values: obj => ({ rootPath: obj.rootPath}),
|
|
children: [
|
|
{
|
|
id: "preplots",
|
|
name: "Preplots",
|
|
values: (obj) => ({
|
|
preplots: structuredClone(obj.preplots),
|
|
rootPath: obj.rootPath
|
|
})
|
|
},
|
|
{
|
|
id: "raw_data",
|
|
name: "Raw data",
|
|
children: [
|
|
{
|
|
id: "raw_data_p111",
|
|
name: "P1/11",
|
|
values: (obj) => ({
|
|
rootPath: obj.rootPath,
|
|
globs: obj.raw.p111.globs,
|
|
paths: obj.raw.p111.paths,
|
|
pattern: obj.raw?.p111?.pattern,
|
|
lineNameInfo: obj.raw?.p111?.lineNameInfo
|
|
})
|
|
},
|
|
{
|
|
id: "raw_data_smsrc",
|
|
name: "Smartsource",
|
|
children: [
|
|
{
|
|
id: "raw_data_smsrc_header",
|
|
name: "Headers",
|
|
values: (obj) => ({
|
|
rootPath: obj.rootPath,
|
|
globs: obj?.raw?.source?.smsrc?.header?.globs,
|
|
paths: obj?.raw?.source?.smsrc?.header?.paths,
|
|
pattern: obj?.raw?.source?.smsrc?.header?.pattern,
|
|
lineNameInfo: obj?.raw?.source?.smsrc?.header?.lineNameInfo
|
|
})
|
|
},
|
|
{
|
|
id: "raw_data_smsrc_segy",
|
|
name: "Hydrophone data",
|
|
values: (obj) => ({
|
|
rootPath: obj.rootPath,
|
|
globs: obj?.raw?.source?.smsrc?.segy?.globs,
|
|
paths: obj?.raw?.source?.smsrc?.segy?.paths,
|
|
pattern: obj?.raw?.source?.smsrc?.segy?.pattern,
|
|
lineNameInfo: obj?.raw?.source?.smsrc?.segy?.lineNameInfo
|
|
})
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: "raw_data_ntbp",
|
|
name: "NTBP detection",
|
|
values: (obj) => ({
|
|
regex: obj.raw.ntbp?.pattern?.regex,
|
|
flags: obj.raw.ntbp?.pattern?.flags
|
|
})
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: "final_data",
|
|
name: "Final data",
|
|
children: [
|
|
{
|
|
id: "final_data_p111",
|
|
name: "P1/11",
|
|
values: (obj) => ({
|
|
rootPath: obj.rootPath,
|
|
globs: obj.final.p111.globs,
|
|
paths: obj.final.p111.paths,
|
|
pattern: obj.final.p111.pattern,
|
|
lineNameInfo: obj.final?.p111?.lineNameInfo
|
|
})
|
|
},
|
|
{
|
|
id: "final_data_pending",
|
|
name: "Pending line detection",
|
|
values: (obj) => ({
|
|
regex: obj.final.pending?.pattern?.regex,
|
|
flags: obj.final.pending?.pattern?.flags
|
|
})
|
|
}
|
|
]
|
|
},
|
|
]
|
|
},
|
|
{
|
|
id: "line_name_format",
|
|
name: "Line name format",
|
|
values: (obj) => ({
|
|
fields: this.makeSection(obj, "online.line.lineNameBuilder.fields", []),
|
|
values: this.makeSection(obj, "online.line.lineNameBuilder.values",
|
|
Object.fromEntries(
|
|
Object.keys(this.settings.lineNameBuilder.properties ?? {}).map( k => [k, undefined] ))),
|
|
properties: this.settings.lineNameBuilder.properties ?? {}
|
|
}),
|
|
update: (obj) => {
|
|
const configuration = structuredClone(this.configuration);
|
|
deepSet(configuration, ["online", "line", "lineNameBuilder"], obj);
|
|
this.configuration = configuration;
|
|
}
|
|
},
|
|
{
|
|
id: "planner_settings",
|
|
name: "Planner settings",
|
|
values: (obj) => ({planner: obj?.planner})
|
|
},
|
|
{
|
|
id: "production",
|
|
name: "Production settings",
|
|
values: (obj) => ({production: obj?.production})
|
|
},
|
|
{
|
|
id: "cloud_apis",
|
|
name: "Cloud APIs",
|
|
children: [
|
|
{
|
|
id: "asaqc",
|
|
name: "ASAQC",
|
|
values: (obj) => ({value: obj?.cloud?.asaqc}),
|
|
}
|
|
]
|
|
}
|
|
],
|
|
|
|
dirty: false,
|
|
|
|
fileLoadDialog: false,
|
|
|
|
viewMode: 0,
|
|
dialog: false,
|
|
contextMenu: false,
|
|
contextMenuX: null,
|
|
contextMenuY: null
|
|
};
|
|
},
|
|
|
|
watch: {
|
|
|
|
configuration: {
|
|
handler (cur, prev) {
|
|
if (cur && prev) {
|
|
this.dirty = true;
|
|
} else {
|
|
this.dirty = false;
|
|
}
|
|
},
|
|
deep: true
|
|
},
|
|
|
|
active (cur, prev) {
|
|
if (cur == prev) {
|
|
return;
|
|
}
|
|
|
|
if (!this.activeComponent && this.activeItem?.children?.length) {
|
|
// Automatically expand this item
|
|
if (!this.open.includes(cur)) {
|
|
this.open.push(cur);
|
|
}
|
|
this.$nextTick( () => {
|
|
const idx = this.active.findIndex(i => i == cur);
|
|
if (idx != -1) {
|
|
this.active.push(this.activeItem.children[0].id);
|
|
this.active.splice(cur, 1);
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
|
|
activeComponent () {
|
|
return components[this.active[0]];
|
|
},
|
|
|
|
activeItem () {
|
|
function walk (tree) {
|
|
const list = [];
|
|
for (const leaf of tree) {
|
|
if (leaf.children) {
|
|
list.push(...walk(leaf.children));
|
|
}
|
|
list.push(leaf);
|
|
}
|
|
return list;
|
|
}
|
|
return walk(this.treeview).find(i => i.id === this.active[0]);
|
|
},
|
|
|
|
activeValues () {
|
|
return this.activeItem?.values &&
|
|
this.activeItem.values(this.configuration);
|
|
},
|
|
|
|
activeUpdateHandler () {
|
|
return this.activeItem?.update ?? ((obj) => {
|
|
console.warn("Unhandled update event on", this.activeItem?.id, obj);
|
|
})
|
|
},
|
|
|
|
surveyState: {
|
|
get () {
|
|
return !this.configuration?.archived;
|
|
},
|
|
|
|
async set (value) {
|
|
if (this.configuration) {
|
|
await this.patch({archived: !value});
|
|
// this.configuration.archived = !value;
|
|
}
|
|
}
|
|
},
|
|
|
|
fileInputErrors () {
|
|
const messages = [];
|
|
|
|
const validTypes = [
|
|
"application/json",
|
|
"application/yaml",
|
|
"application/x-yaml"
|
|
];
|
|
|
|
if (this.files instanceof File) {
|
|
if (!validTypes.includes(this.files.type)) {
|
|
messages.push(`Invalid file type: ${this.files.type}`);
|
|
messages.push("Please select a JSON or YAML file");
|
|
} else if (this.files.size < 32) { // 32 is an arbitrary small value
|
|
messages.push("File too small to be a valid Dougal configuration");
|
|
}
|
|
} else if (this.files && this.files.length) {
|
|
messages.push("Invalid file path");
|
|
}
|
|
|
|
return messages;
|
|
},
|
|
|
|
...mapGetters(['user', 'writeaccess', 'loading', 'serverEvent'])
|
|
},
|
|
|
|
methods: {
|
|
|
|
makeSection (obj, path, defaultVaue) {
|
|
|
|
function reduced (obj = {}, path = []) {
|
|
return path.reduce( (acc, cur) =>
|
|
{
|
|
if (!(cur in acc)) {
|
|
acc[cur] = {} ;
|
|
};
|
|
return acc[cur]
|
|
}, obj);
|
|
}
|
|
|
|
if (!obj) {
|
|
obj = {};
|
|
}
|
|
|
|
if (typeof path == "string") {
|
|
path = path.split(".");
|
|
}
|
|
|
|
let value = reduced(obj, path);
|
|
|
|
const key = path.pop();
|
|
if (!Object.keys(value ?? {}).length && defaultVaue !== undefined) {
|
|
reduced(obj, path)[key] = defaultVaue;
|
|
}
|
|
return reduced(obj, path)[key];
|
|
},
|
|
|
|
async getConfiguration () {
|
|
this.configuration = null;
|
|
const url = `/project/${this.$route.params.project}/configuration`;
|
|
const init = {
|
|
headers: {
|
|
"If-None-Match": "" // Ensure we get a fresh response
|
|
}
|
|
};
|
|
this.configuration = await this.api([url, init]);
|
|
this.dirty = false;
|
|
},
|
|
|
|
async getSettings () {
|
|
this.settings = null;
|
|
let url = `/project/${this.$route.params.project}/linename/properties`;
|
|
const init = {
|
|
headers: {
|
|
"If-None-Match": "" // Ensure we get a fresh response
|
|
}
|
|
};
|
|
this.settings = {
|
|
lineNameBuilder: {
|
|
properties: await this.api([url, init])
|
|
}
|
|
};
|
|
},
|
|
|
|
makeTree (obj, id=0) {
|
|
const isObject = typeof obj === "object" && !Array.isArray(obj) && obj !== null;
|
|
return isObject
|
|
? Object.keys(obj).map(k => {
|
|
const children = this.makeTree(obj[k], id); //.filter(i => i !== null);
|
|
return {
|
|
id: id++,
|
|
name: k,
|
|
children
|
|
}
|
|
})
|
|
: null;
|
|
},
|
|
|
|
deselect () {
|
|
this.active.pop();
|
|
},
|
|
|
|
merge ([path, value]) {
|
|
deepSet(this.configuration, path, value);
|
|
},
|
|
|
|
// Use to change the project's archival status
|
|
async patch (data) {
|
|
const url = `/project/${this.$route.params.project}/configuration`;
|
|
const init = {
|
|
method: "PATCH",
|
|
body: data
|
|
};
|
|
const callback = async (err, res) => {
|
|
if (!err && res.ok) {
|
|
this.showSnack(["Configuration saved", "success"]);
|
|
}
|
|
};
|
|
|
|
const refreshedConfiguration = await this.api([url, init, callback]);
|
|
if (refreshedConfiguration) {
|
|
this.configuration = refreshedConfiguration;
|
|
}
|
|
},
|
|
|
|
async upload () {
|
|
const url = `/project/${this.$route.params.project}/configuration`;
|
|
const init = {
|
|
method: "PUT",
|
|
headers: {
|
|
//"If-Match": "" // Ensure we're not overwriting someone else's changes
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: {...this.configuration, archived: true}
|
|
};
|
|
const res = await this.api([url, init]);
|
|
if (res && res.id == this.configuration.id) {
|
|
// In case the server decided to apply any changes
|
|
this.showSnack(["Configuration uploaded to server", "success"]);
|
|
this.$nextTick( () => {
|
|
this.configuration = res;
|
|
this.$nextTick( () => {
|
|
this.dirty = false;
|
|
});
|
|
});
|
|
}
|
|
},
|
|
|
|
async loadFromFile () {
|
|
if (!this.fileInputErrors.length) {
|
|
if (this.files.type == "application/json") {
|
|
this.configuration = JSON.parse(await this.files.text());
|
|
this.showSnack(["Configuration loaded from file", "primary"]);
|
|
} else if (this.files.type == "application/yaml" || this.files.type == "application/x-yaml") {
|
|
this.configuration = YAML.parse(await this.files.text());
|
|
this.showSnack(["Configuration loaded from file", "primary"]);
|
|
} else {
|
|
console.error("Unknown file format (shouldn't happen)", this.files.type);
|
|
}
|
|
this.fileLoadDialog = false;
|
|
}
|
|
},
|
|
|
|
async saveToFile () {
|
|
const payload = YAML.stringify(this.configuration);
|
|
const blob = new Blob([payload], {type: "application/yaml"});
|
|
const url = URL.createObjectURL(blob);
|
|
const filename = `${this.$route.params.project}-configuration.yaml`;
|
|
|
|
const element = document.createElement('a');
|
|
element.download = filename;
|
|
element.href = url;
|
|
element.click();
|
|
URL.revokeObjectURL(url);
|
|
},
|
|
|
|
closeDialog () {
|
|
},
|
|
|
|
showContextMenu (e) {
|
|
e.preventDefault();
|
|
this.contextMenu = false
|
|
this.contextMenuX = e.clientX
|
|
this.contextMenuY = e.clientY
|
|
this.$nextTick(() => {
|
|
this.contextMenu = true
|
|
})
|
|
},
|
|
|
|
...mapActions(["api", "showSnack"])
|
|
|
|
},
|
|
|
|
async mounted () {
|
|
this.getSettings();
|
|
this.getConfiguration();
|
|
},
|
|
|
|
}
|
|
</script>
|