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:
Alicia Sykes 2021-10-28 01:33:35 +01:00
parent a19b8b8f88
commit 2facae3dc1
1 changed files with 100 additions and 72 deletions

View File

@ -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);
}
} }
} }