mirror of https://github.com/Lissy93/dashy.git
✨ Adds live preview mode to JSON editor
Also updates the YAML parser, uses reusable radio bttons, reads initial config from state, refactos code, and few other things :)
This commit is contained in:
parent
a19b8b8f88
commit
2facae3dc1
|
@ -1,31 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="json-editor-outer">
|
<div class="json-editor-outer">
|
||||||
<!-- Main JSON editor -->
|
<!-- Main JSON editor -->
|
||||||
<v-jsoneditor
|
<v-jsoneditor v-model="jsonData" :options="options" />
|
||||||
v-model="jsonData"
|
|
||||||
:options="options"
|
|
||||||
/>
|
|
||||||
<!-- Options raido, and save button -->
|
<!-- Options raido, and save button -->
|
||||||
<div class="save-options">
|
<Radio class="save-options"
|
||||||
<span class="save-option-title">{{ $t('config-editor.save-location-label') }}:</span>
|
v-model="saveMode"
|
||||||
<div class="option">
|
:label="$t('config-editor.save-location-label')"
|
||||||
<input type="radio" id="local" value="local"
|
:options="saveOptions"
|
||||||
v-model="saveMode" class="radio-option" :disabled="!allowWriteToDisk" />
|
:initialOption="initialSaveMode"
|
||||||
<label for="local" class="save-option-label">
|
:disabled="!allowWriteToDisk"
|
||||||
{{ $t('config-editor.location-local-label') }}
|
/>
|
||||||
</label>
|
<!-- Save Buttons -->
|
||||||
</div>
|
<div :class="`btn-container ${!isValid ? 'err' : ''}`">
|
||||||
<div class="option">
|
<Button :click="save">
|
||||||
<input type="radio" id="file" value="file" v-model="saveMode" class="radio-option"
|
{{ $t('config-editor.save-button') }}
|
||||||
:disabled="!allowWriteToDisk" />
|
</Button>
|
||||||
<label for="file" class="save-option-label">
|
<Button :click="startPreview">
|
||||||
{{ $t('config-editor.location-disk-label') }}
|
{{ $t('config-editor.preview-button') }}
|
||||||
</label>
|
</Button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<button :class="`save-button ${!isValid ? 'err' : ''}`" @click="save()">
|
|
||||||
{{ $t('config-editor.save-button') }}
|
|
||||||
</button>
|
|
||||||
<!-- List validation warnings -->
|
<!-- List validation warnings -->
|
||||||
<p class="errors">
|
<p class="errors">
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -50,7 +43,6 @@
|
||||||
<p v-if="saveSuccess" class="response-output">
|
<p v-if="saveSuccess" class="response-output">
|
||||||
{{ $t('config-editor.success-note-l1') }}
|
{{ $t('config-editor.success-note-l1') }}
|
||||||
{{ $t('config-editor.success-note-l2') }}
|
{{ $t('config-editor.success-note-l2') }}
|
||||||
{{ $t('config-editor.success-note-l3') }}
|
|
||||||
</p>
|
</p>
|
||||||
<p class="note">{{ $t('config.backup-note') }}</p>
|
<p class="note">{{ $t('config.backup-note') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,25 +53,27 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import ProgressBar from 'rsup-progress';
|
import ProgressBar from 'rsup-progress';
|
||||||
import VJsoneditor from 'v-jsoneditor';
|
import VJsoneditor from 'v-jsoneditor';
|
||||||
|
import jsYaml from 'js-yaml';
|
||||||
import ErrorHandler, { InfoHandler } from '@/utils/ErrorHandler';
|
import ErrorHandler, { InfoHandler } from '@/utils/ErrorHandler';
|
||||||
import configSchema from '@/utils/ConfigSchema.json';
|
import configSchema from '@/utils/ConfigSchema.json';
|
||||||
import JsonToYaml from '@/utils/JsonToYaml';
|
import StoreKeys from '@/utils/StoreMutations';
|
||||||
import { localStorageKeys, serviceEndpoints } from '@/utils/defaults';
|
import { localStorageKeys, serviceEndpoints, modalNames } from '@/utils/defaults';
|
||||||
import { isUserAdmin } from '@/utils/Auth';
|
import { isUserAdmin } from '@/utils/Auth';
|
||||||
|
import Button from '@/components/FormElements/Button';
|
||||||
|
import Radio from '@/components/FormElements/Radio';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'JsonEditor',
|
name: 'JsonEditor',
|
||||||
props: {
|
|
||||||
config: Object,
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
VJsoneditor,
|
VJsoneditor,
|
||||||
|
Button,
|
||||||
|
Radio,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
jsonData: this.config,
|
jsonData: {},
|
||||||
errorMessages: [],
|
errorMessages: [],
|
||||||
saveMode: 'file',
|
saveMode: '',
|
||||||
options: {
|
options: {
|
||||||
schema: configSchema,
|
schema: configSchema,
|
||||||
mode: 'tree',
|
mode: 'tree',
|
||||||
|
@ -87,26 +81,36 @@ export default {
|
||||||
name: 'config',
|
name: 'config',
|
||||||
onValidationError: this.validationErrors,
|
onValidationError: this.validationErrors,
|
||||||
},
|
},
|
||||||
jsonParser: JsonToYaml,
|
|
||||||
responseText: '',
|
responseText: '',
|
||||||
saveSuccess: undefined,
|
saveSuccess: undefined,
|
||||||
allowWriteToDisk: this.shouldAllowWriteToDisk(),
|
|
||||||
progress: new ProgressBar({ color: 'var(--progress-bar)' }),
|
progress: new ProgressBar({ color: 'var(--progress-bar)' }),
|
||||||
|
saveOptions: [
|
||||||
|
{ label: this.$t('config-editor.location-disk-label'), value: 'file' },
|
||||||
|
{ label: this.$t('config-editor.location-local-label'), value: 'local' },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
config() {
|
||||||
|
return this.$store.state.config;
|
||||||
|
},
|
||||||
isValid() {
|
isValid() {
|
||||||
return this.errorMessages.length < 1;
|
return this.errorMessages.length < 1;
|
||||||
},
|
},
|
||||||
},
|
allowWriteToDisk() {
|
||||||
mounted() {
|
|
||||||
if (!this.allowWriteToDisk) this.saveMode = 'local';
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
shouldAllowWriteToDisk() {
|
|
||||||
const { appConfig } = this.config;
|
const { appConfig } = this.config;
|
||||||
return appConfig.allowConfigEdit !== false && isUserAdmin();
|
return appConfig.allowConfigEdit !== false && isUserAdmin();
|
||||||
},
|
},
|
||||||
|
initialSaveMode() {
|
||||||
|
return this.allowWriteToDisk ? 'file' : 'local';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.jsonData = this.config;
|
||||||
|
if (!this.allowWriteToDisk) this.saveMode = 'local';
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/* Calls appropriate save method, based on save-type radio selected */
|
||||||
save() {
|
save() {
|
||||||
if (this.saveMode === 'local' || !this.allowWriteToDisk) {
|
if (this.saveMode === 'local' || !this.allowWriteToDisk) {
|
||||||
this.saveConfigLocally();
|
this.saveConfigLocally();
|
||||||
|
@ -116,9 +120,21 @@ export default {
|
||||||
this.$toasted.show(this.$t('config-editor.error-msg-save-mode'));
|
this.$toasted.show(this.$t('config-editor.error-msg-save-mode'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/* Applies changes to the local state, begins edit mode and closes modal */
|
||||||
|
startPreview() {
|
||||||
|
InfoHandler('Applying changes to local state...', 'Config Update');
|
||||||
|
const data = this.jsonData;
|
||||||
|
this.$store.commit(StoreKeys.SET_APP_CONFIG, data.appConfig);
|
||||||
|
this.$store.commit(StoreKeys.SET_PAGE_INFO, data.pageInfo);
|
||||||
|
this.$store.commit(StoreKeys.SET_SECTIONS, data.sections);
|
||||||
|
this.$store.commit(StoreKeys.SET_MODAL_OPEN, false);
|
||||||
|
this.$store.commit(StoreKeys.SET_EDIT_MODE, true);
|
||||||
|
this.$modal.hide(modalNames.CONF_EDITOR);
|
||||||
|
},
|
||||||
|
/* Converts config to YAML, and writes it to disk */
|
||||||
writeConfigToDisk() {
|
writeConfigToDisk() {
|
||||||
// 1. Convert JSON into YAML
|
// 1. Convert JSON into YAML
|
||||||
const yaml = this.jsonParser(this.jsonData);
|
const yaml = jsYaml.dump(this.config);
|
||||||
// 2. Prepare the request
|
// 2. Prepare the request
|
||||||
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
|
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
|
||||||
const endpoint = `${baseUrl}${serviceEndpoints.save}`;
|
const endpoint = `${baseUrl}${serviceEndpoints.save}`;
|
||||||
|
@ -137,6 +153,7 @@ export default {
|
||||||
this.showToast(this.$t('config-editor.error-msg-cannot-save'), false);
|
this.showToast(this.$t('config-editor.error-msg-cannot-save'), false);
|
||||||
}
|
}
|
||||||
InfoHandler('Config has been written to disk succesfully', 'Config Update');
|
InfoHandler('Config has been written to disk succesfully', 'Config Update');
|
||||||
|
this.$store.commit(StoreKeys.SET_CONFIG, this.jsonData);
|
||||||
this.progress.end();
|
this.progress.end();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -147,6 +164,7 @@ export default {
|
||||||
this.progress.end();
|
this.progress.end();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/* Saves config to local browser storage */
|
||||||
saveConfigLocally() {
|
saveConfigLocally() {
|
||||||
const data = this.jsonData;
|
const data = this.jsonData;
|
||||||
if (data.sections) {
|
if (data.sections) {
|
||||||
|
@ -165,11 +183,13 @@ export default {
|
||||||
InfoHandler('Config has succesfully been saved in browser storage', 'Config Update');
|
InfoHandler('Config has succesfully been saved in browser storage', 'Config Update');
|
||||||
this.showToast(this.$t('config-editor.success-msg-local'), true);
|
this.showToast(this.$t('config-editor.success-msg-local'), true);
|
||||||
},
|
},
|
||||||
|
/* Clears config from browser storage, only removing relevant items */
|
||||||
carefullyClearLocalStorage() {
|
carefullyClearLocalStorage() {
|
||||||
localStorage.removeItem(localStorageKeys.PAGE_INFO);
|
localStorage.removeItem(localStorageKeys.PAGE_INFO);
|
||||||
localStorage.removeItem(localStorageKeys.APP_CONFIG);
|
localStorage.removeItem(localStorageKeys.APP_CONFIG);
|
||||||
localStorage.removeItem(localStorageKeys.CONF_SECTIONS);
|
localStorage.removeItem(localStorageKeys.CONF_SECTIONS);
|
||||||
},
|
},
|
||||||
|
/* Convert error messages into readable format for UI */
|
||||||
validationErrors(errors) {
|
validationErrors(errors) {
|
||||||
const errorMessages = [];
|
const errorMessages = [];
|
||||||
errors.forEach((error) => {
|
errors.forEach((error) => {
|
||||||
|
@ -197,6 +217,7 @@ export default {
|
||||||
});
|
});
|
||||||
this.errorMessages = errorMessages;
|
this.errorMessages = errorMessages;
|
||||||
},
|
},
|
||||||
|
/* Shows toast message */
|
||||||
showToast(message, success) {
|
showToast(message, success) {
|
||||||
this.$toasted.show(message, { className: `toast-${success ? 'success' : 'error'}` });
|
this.$toasted.show(message, { className: `toast-${success ? 'success' : 'error'}` });
|
||||||
},
|
},
|
||||||
|
@ -259,52 +280,59 @@ p.no-permission-note {
|
||||||
color: var(--config-settings-color);
|
color: var(--config-settings-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
button.save-button {
|
.btn-container {
|
||||||
padding: 0.5rem 1rem;
|
display: flex;
|
||||||
margin: 0.25rem auto;
|
align-items: center;
|
||||||
font-size: 1.2rem;
|
justify-content: center;
|
||||||
background: var(--config-settings-color);
|
button {
|
||||||
color: var(--config-settings-background);
|
padding: 0.5rem 1rem;
|
||||||
border: 1px solid var(--config-settings-background);
|
margin: 0.25rem;
|
||||||
border-radius: var(--curve-factor);
|
font-size: 1.2rem;
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
background: var(--config-settings-background);
|
background: var(--config-settings-background);
|
||||||
color: var(--config-settings-color);
|
color: var(--config-settings-color);
|
||||||
border-color: var(--config-settings-color);
|
border: 1px solid var(--config-settings-color);
|
||||||
}
|
border-radius: var(--curve-factor);
|
||||||
&.err {
|
|
||||||
opacity: 0.8;
|
|
||||||
cursor: default;
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--config-settings-color);
|
background: var(--config-settings-color);
|
||||||
color: var(--config-settings-background);
|
color: var(--config-settings-background);
|
||||||
|
border-color: var(--config-settings-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.err button {
|
||||||
|
opacity: 0.8;
|
||||||
|
cursor: default;
|
||||||
|
&:hover {
|
||||||
|
background: var(--config-settings-background);
|
||||||
|
color: var(--config-settings-color);
|
||||||
border-color: var(--danger);
|
border-color: var(--danger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.save-options {
|
div.save-options.radio-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0.5rem;
|
margin: 0;
|
||||||
margin-bottom: 0.5rem;
|
padding: 0;
|
||||||
background: var(--code-editor-background);
|
|
||||||
color: var(--code-editor-color);
|
|
||||||
border-top: 2px solid var(--config-settings-background);
|
border-top: 2px solid var(--config-settings-background);
|
||||||
@include tablet-down { flex-direction: column; }
|
background: var(--code-editor-background);
|
||||||
.option {
|
label.radio-label {
|
||||||
@include tablet-up { margin-left: 2rem; }
|
font-size: 1rem;
|
||||||
|
flex-grow: revert;
|
||||||
|
flex-basis: revert;
|
||||||
|
color: var(--code-editor-color);
|
||||||
|
padding-left: 1rem;
|
||||||
}
|
}
|
||||||
span.save-option-title {
|
.radio-wrapper {
|
||||||
cursor: default;
|
margin: 0;
|
||||||
}
|
font-size: 1rem;
|
||||||
input.radio-option {
|
justify-content: space-around;
|
||||||
cursor: pointer;
|
background: var(--code-editor-background);
|
||||||
}
|
color: var(--code-editor-color);
|
||||||
label.save-option-label {
|
.radio-option:hover:not(.wrap-disabled) {
|
||||||
cursor: pointer;
|
border: 1px solid var(--code-editor-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue