diff --git a/package.json b/package.json index e2e223a..786aefd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@json.ms/www", "private": true, "type": "module", - "version": "1.3.0", + "version": "1.3.1", "scripts": { "dev": "vite --host", "build": "run-p type-check \"build-only {@}\" --", diff --git a/src/assets/logo-dark.png b/src/assets/logo-dark.png new file mode 100644 index 0000000..c8a2d3b Binary files /dev/null and b/src/assets/logo-dark.png differ diff --git a/src/components/ActionBar.vue b/src/components/ActionBar.vue index d8b38f1..4da182f 100644 --- a/src/components/ActionBar.vue +++ b/src/components/ActionBar.vue @@ -19,7 +19,7 @@ const globalStore = useGlobalStore(); const modelStore = useModelStore(); const { structureIsPristine, setDefaultValues } = useStructure(); const { windowWidth, layoutSize } = useLayout(); -const { saveUserData, resetUserData, userDataHasChanged, canInteractWithServer, canInteractWithSyncedFolder, userDataSaving, userDataSaved, canSave } = useUserData(); +const { saveUserData, resetUserData, cleanUserData, userDataHasChanged, canInteractWithServer, canInteractWithSyncedFolder, userDataSaving, userDataSaved, canSave } = useUserData(); const saveMaxWidth = computed((): string => { const long = canInteractWithServer.value && canInteractWithSyncedFolder.value; @@ -38,7 +38,8 @@ const onSetAsDefaultValues = () => { btnIcon: 'mdi-text-box-check-outline', btnColor: 'warning', callback: () => new Promise(resolve => { - setDefaultValues(modelStore.structure, modelStore.userData); + const cleanData = cleanUserData(); + setDefaultValues(modelStore.structure, cleanData); resolve(); }) }) diff --git a/src/components/FieldItem.vue b/src/components/FieldItem.vue index cb3f062..753a91d 100644 --- a/src/components/FieldItem.vue +++ b/src/components/FieldItem.vue @@ -922,4 +922,16 @@ watch(() => field.collapsed, () => { .handle { cursor: grabbing; } +.v-theme--dark { + .CodeMirror { + background-color: rgb(var(--v-theme-sheet)); + color: inherit; + } + .CodeMirror-cursor { + border-left: white solid 1px; + } + .editor-toolbar button:hover { + color: black; + } +} diff --git a/src/components/JSONms.vue b/src/components/JSONms.vue index 0d5961d..181ac5f 100644 --- a/src/components/JSONms.vue +++ b/src/components/JSONms.vue @@ -26,6 +26,7 @@ import FileManager from '@/components/FileManager.vue'; import {useModelStore} from '@/stores/model'; import structureMd from "../../docs/structure.md"; import {useSyncing} from "@/composables/syncing"; +import { useTheme } from 'vuetify' // Model & Props const structure = defineModel({ required: true }); @@ -208,14 +209,18 @@ const onEditJson = () => { editJsonContent.value = JSON.stringify(deepToRaw(modelStore.userData), null, globalStore.userSettings.data.editorTabSize); } -const onMigrateData = () => { - showMigrationDialog.value = true; +const onCleanJson = (data: any) => { + editJsonContent.value = JSON.stringify(data || {}, null, globalStore.userSettings.data.editorTabSize); } const onApplyJsonContent = (json: any) => { setUserData(json); } +const onMigrateData = () => { + showMigrationDialog.value = true; +} + let oldModel: IStructure | null = null; const onStructureChange = (model: IStructure) => { if (oldModel) { @@ -262,6 +267,15 @@ window.addEventListener('fs-change', () => { syncFromFolder(structure.value); }) +const theme = useTheme(); +const toggleDarkMode = () => { + theme.change(globalStore.userSettings.data.appearanceDarkMode ? 'dark' : 'light'); +} +watch(() => [ + globalStore.userSettings.data.appearanceDarkMode, +], toggleDarkMode); +toggleDarkMode(); + const refreshUserData = async (): Promise => { return fetchUserData().then((response: any) => { setUserData(response.data, true); @@ -486,7 +500,7 @@ if (globalStore.session.loggedIn) { v-model="editJsonContent" v-model:visible="showEditJson" @apply="onApplyJsonContent" - @clean="onEditJson" + @clean="onCleanJson" /> diff --git a/src/components/JsonEditModal.vue b/src/components/JsonEditModal.vue index 8fc9bca..9958e32 100644 --- a/src/components/JsonEditModal.vue +++ b/src/components/JsonEditModal.vue @@ -47,8 +47,8 @@ const onCleanUserData = () => { btnIcon: 'mdi-vacuum-outline', btnColor: 'warning', callback: () => new Promise(resolve => { - cleanUserData() - emit('clean'); + const data = cleanUserData() + emit('clean', data); resolve(); }) }) diff --git a/src/components/Logo.vue b/src/components/Logo.vue index f783f88..72b51ca 100644 --- a/src/components/Logo.vue +++ b/src/components/Logo.vue @@ -1,5 +1,14 @@ diff --git a/src/components/SitePreview.vue b/src/components/SitePreview.vue index 765c414..b4f1abc 100644 --- a/src/components/SitePreview.vue +++ b/src/components/SitePreview.vue @@ -166,10 +166,10 @@ defineExpose({ > = ref([]); const globalStore = useGlobalStore(); const structureEditor: Ref = ref(null); const blueprintEditorTypings: Ref = ref(null); @@ -111,7 +118,10 @@ const value = computed({ set(value: string) { modelStore.setTemporaryContent(value); if (globalStore.userSettings.data.editorLiveUpdate) { - emit('change', value); + printAnnotations(value); + if (annotations.value.length === 0) { + emit('change', value); + } } else { clearInterval(parseInterval); clearInterval(showParseInterval); @@ -124,7 +134,10 @@ const value = computed({ parseValueInterval = setTimeout(() => progressBarValue.value = 100, 100); parseInterval = setTimeout(() => { - emit('change', value); + printAnnotations(value); + if (annotations.value.length === 0) { + emit('change', value); + } progressBarCompleted.value = true; setTimeout(() => showParsingDelay.value = true); setTimeout(() => { @@ -177,21 +190,60 @@ const onBlur = () => { emit('blur'); } +const printAnnotations = (content: string) => { + const instance = structureEditor.value?.getAceInstance(); + if (instance) { + animateAnnotationWarning.value = false; + annotations.value = []; + const lineCounter = new LineCounter(); + const doc = parseDocument(content, {lineCounter}); + doc.errors.forEach(error => { + const line = error.linePos?.[0].line; + if (line) { + annotations.value.push({ + row: line - 1, + column: 0, + text: error.message, + type: "error" + }); + } + }) + doc.warnings.forEach(error => { + const line = error.linePos?.[0].line; + if (line) { + annotations.value.push({ + row: line - 1, + column: 0, + text: error.message, + type: "warning" + }); + } + }) + instance.session.setAnnotations(annotations.value); + + if (annotations.value.length > 0) { + animateAnnotationWarning.value = true; + setTimeout(() => { + animateAnnotationWarning.value = false; + }, 200); + } + } +} + +const goToNextAnnotation = () => { + const instance = structureEditor.value?.getAceInstance(); + if (instance && annotations.value.length > 0) { + instance.renderer.scrollToLine(annotations.value[0].row, false, true); + } +} + const scrollToSection = (section: string) => { const instance = structureEditor.value?.getAceInstance(); if (instance) { - const annotations = []; const line = findNeedleInString(structure.value.content, ' ' + section + ':'); if (line) { instance.renderer.scrollToLine(line - 1, false, true); - annotations.push({ - row: line - 1, - column: 0, - text: "Current section", - type: "info" - }); } - instance.session.setAnnotations(annotations); } } @@ -257,6 +309,7 @@ const tryToAddCommands = () => { onMounted(() => { tryToAddCommands(); onChange(); + printAnnotations(structure.value.content); }) onBeforeUnmount(() => { // editor.value?.destroy(); @@ -356,7 +409,7 @@ defineExpose({ function onChange() { if (structureEditor.value) { -// updateAnnotations(editor.value?.getAceInstance()); + } } @@ -435,6 +488,9 @@ const structureOptions = computed(() => { return { ...options.value, tabSize: 2 }; }); +watch(() => structure.value.hash, () => { + printAnnotations(structure.value.content); +}); watch(() => globalStore.userSettings.data, () => { options.value.fontSize = globalStore.userSettings.data.editorFontSize; options.value.tabSize = globalStore.userSettings.data.editorTabSize; @@ -487,9 +543,33 @@ watch(() => globalStore.userSettings.data, () => { /> + +
+ + + + +
globalStore.userSettings.data, () => { @@ -684,4 +764,20 @@ watch(() => globalStore.userSettings.data, () => { .v-window ::v-deep .v-window__container { height: 100% !important; } +.annotation-warning { + &.animate { + animation: pulse-once 200ms ease-out normal; + } +} +@keyframes pulse-once { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.5); + } + 100% { + transform: scale(1); + } +} diff --git a/src/components/UserSettingsDialog.vue b/src/components/UserSettingsDialog.vue index ae4f9e9..f112964 100644 --- a/src/components/UserSettingsDialog.vue +++ b/src/components/UserSettingsDialog.vue @@ -162,6 +162,19 @@ watch(() => visible.value, () => { + + + + + + { + const cleanUserData = (): any => { const parsedStructure = getParsedStructureData(modelStore.structure); - const data = getParsedUserData(parsedStructure, modelStore.userData, true); - setUserData(data, true); + return getParsedUserData(parsedStructure, modelStore.userData, true); } const setUserData = (data: any, setOriginal = false) => { diff --git a/src/directives/if-ref.ts b/src/directives/if-ref.ts index 73dbe9e..6997ab4 100644 --- a/src/directives/if-ref.ts +++ b/src/directives/if-ref.ts @@ -1,29 +1,33 @@ +import type { Directive, DirectiveBinding } from 'vue'; + +type CleanupEl = HTMLElement & { + _cleanup?: () => void; +}; + const placeholder = document.createComment('v-if-ref'); -const checkCondition = (el, binding) => { +const checkCondition = (el: CleanupEl, binding: DirectiveBinding): void => { if (el.parentNode && !binding.value) { el.parentNode.insertBefore(placeholder, el); el.remove(); } else if (!el.parentNode && binding.value) { - placeholder.parentNode.insertBefore(el, placeholder); + placeholder.parentNode?.insertBefore(el, placeholder); placeholder.remove(); } }; -const vIfRef = { - mounted(el, binding) { +const vIfRef: Directive = { + mounted(el: CleanupEl, binding: DirectiveBinding) { checkCondition(el, binding); const handleResize = () => checkCondition(el, binding); window.addEventListener('resize', handleResize); el._cleanup = () => window.removeEventListener('resize', handleResize); }, - updated(el, binding) { + updated(el: CleanupEl, binding: DirectiveBinding) { console.log(binding); checkCondition(el, binding); }, - beforeUnmount(el) { - if (el._cleanup) { - el._cleanup(); - } + beforeUnmount(el: CleanupEl) { + el._cleanup?.(); } }; diff --git a/src/interfaces.ts b/src/interfaces.ts index de48218..e1f477c 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -67,6 +67,7 @@ export interface IFile { } export interface IUserSettings { + appearanceDarkMode: boolean, editorFontSize: number editorShowPrintMargin: boolean editorTabSize: number diff --git a/src/plugins/vuetify.ts b/src/plugins/vuetify.ts index 351975e..17be75f 100644 --- a/src/plugins/vuetify.ts +++ b/src/plugins/vuetify.ts @@ -23,12 +23,14 @@ export default createVuetify({ themes: { light: { colors: { + footer: '#f9f9f9', background: '#eee', } }, dark: { colors: { - background: '#eee', + footer: '#030303', + background: '#111', } }, }, diff --git a/src/stores/global.ts b/src/stores/global.ts index ff0b5aa..3c5e146 100644 --- a/src/stores/global.ts +++ b/src/stores/global.ts @@ -73,6 +73,7 @@ export const useGlobalStore = defineStore('global', { userSettings: { visible: false, data: { + appearanceDarkMode: false, editorFontSize: 16, editorLiveUpdate: true, editorUpdateTimeout: 1000,