mirror of
				https://github.com/Lissy93/dashy.git
				synced 2025-10-31 03:14:00 +01:00 
			
		
		
		
	✨ 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user