From 8ca7973f778d824f8661660310e7f4d3a9d42709 Mon Sep 17 00:00:00 2001 From: Danny Coulombe Date: Tue, 16 Dec 2025 08:47:28 -0500 Subject: [PATCH 1/5] remove page workflow --- .github/workflows/deploy-pages.yml | 63 ------------------------------ 1 file changed, 63 deletions(-) delete mode 100644 .github/workflows/deploy-pages.yml diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml deleted file mode 100644 index 70ffe2b..0000000 --- a/.github/workflows/deploy-pages.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Deploy static content to Pages - -on: - push: - branches: [$default-branch] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '>=18' - - - name: Defining environment variables - run: | - echo "VITE_SERVER_URL=https://server.json.ms" > .env - echo "VITE_DEMO_PREVIEW_URL=https://demo.json.ms" >> .env - - - name: Install dependencies - run: yarn - - - name: Build project - run: yarn build-only - - - name: Copy index.html to 404.html - run: cp ./dist/index.html ./dist/404.html - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./dist - - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 From fa8b4e704665c549d7b22f57e1adf5502ac0fb0e Mon Sep 17 00:00:00 2001 From: Danny Coulombe Date: Tue, 16 Dec 2025 09:30:40 -0500 Subject: [PATCH 2/5] typings/syncing refactor --- src/components/ActionBar.vue | 2 +- src/components/FileManager.vue | 4 + src/components/JSONms.vue | 13 +- src/components/StructureEditor.vue | 14 +- src/components/UserSettingsDialog.vue | 2 +- src/composables/structure.ts | 2 +- src/composables/syncing.ts | 459 ++++++++++++++++++++++++++ src/composables/typings.ts | 420 +---------------------- src/composables/user-data.ts | 11 +- 9 files changed, 496 insertions(+), 431 deletions(-) create mode 100644 src/composables/syncing.ts diff --git a/src/components/ActionBar.vue b/src/components/ActionBar.vue index f287ab8..2257377 100644 --- a/src/components/ActionBar.vue +++ b/src/components/ActionBar.vue @@ -45,7 +45,7 @@ const onSetAsDefaultValues = () => { } const onSyncWithLocalOnly = () => { - useTypings().syncToFolder(modelStore.structure, 'typescript', ['data']); + useTypings().syncToFolder(modelStore.structure, ['data']); userDataSaved.value = true; setTimeout(() => userDataSaved.value = false, 1000); } diff --git a/src/components/FileManager.vue b/src/components/FileManager.vue index e3ab295..9fbedb5 100644 --- a/src/components/FileManager.vue +++ b/src/components/FileManager.vue @@ -7,8 +7,10 @@ import ImgTag from '@/components/ImgTag.vue'; import VideoPlayer from '@/components/VideoPlayer.vue'; import {downloadFilesAsZip, getFileIcon, phpStringSizeToBytes} from '@/utils'; import ModalDialog from '@/components/ModalDialog.vue'; +import {useSyncing} from "@/composables/syncing"; const globalStore = useGlobalStore(); +const syncing = useSyncing(); const structure = defineModel({ required: true }); const { selected = [], serverSettings, canUpload = false, canDelete = false, canSelect = false, canDownload = false } = defineProps<{ selected?: IFile[], @@ -254,6 +256,8 @@ watch(() => globalStore.fileManager.visible, () => { if (globalStore.fileManager.visible) { selectedFiles.value = []; load(); + + syncing.getFiles(structure.value); } }) diff --git a/src/components/JSONms.vue b/src/components/JSONms.vue index f78117a..92874ae 100644 --- a/src/components/JSONms.vue +++ b/src/components/JSONms.vue @@ -26,6 +26,7 @@ import Docs from '@/components/Docs.vue'; import FileManager from '@/components/FileManager.vue'; import {useModelStore} from '@/stores/model'; import structureMd from "../../docs/structure.md"; +import {useSyncing} from "@/composables/syncing"; // Model & Props const structure = defineModel({ required: true }); @@ -47,7 +48,7 @@ const dataEditor = ref | null>(); const { serverSettings, structureParsedData, structureStates, structureHasSettingsError, getAvailableSection, deleteStructure, getAvailableLocale, structureHasSection, structureHasLocale, saveStructure, canSaveStructure, canDeleteStructure, resetStructure } = useStructure(); const { fetchUserData, canSave, saveUserData, downloading, userDataLoaded, userDataLoading, setUserData } = useUserData(); const { sendMessageToIframe } = useIframe(); -const { syncFromFolder, autoAskToSyncFolder, syncToFolder, unSyncFolder, stopWatchSnapshotDirectory, watchSnapshotDirectory, isFolderSynced } = useTypings(); +const { syncFromFolder, autoAskToSyncFolder, syncToFolder, unSyncFolder, stopWatchSnapshotDirectory, watchSnapshotDirectory } = useSyncing(); globalStore.initUserSettings(); @@ -156,7 +157,7 @@ const onSaveStructure = () => { modelStore.structure.content = modelStore.temporaryContent || modelStore.structure.content; saveStructure().then(() => { bottomSheetData.value = { text: 'Structure saved!', color: 'success', icon: 'mdi-check' }; - syncToFolder(modelStore.structure, 'typescript', ['structure', 'default', 'typings', 'settings', 'index']); + syncToFolder(modelStore.structure, ['structure', 'default', 'typings', 'settings', 'index']); const newModel = modelStore.structure; globalStore.addStructure(newModel); updateRoute(newModel.hash, getAvailableSection(), getAvailableLocale()).then(() => { @@ -219,7 +220,7 @@ const onApplyJsonContent = (json: any) => { let oldModel: IStructure | null = null; const onStructureChange = (model: IStructure) => { if (oldModel) { - unSyncFolder(oldModel, 'typescript'); + unSyncFolder(oldModel); } oldModel = model; resetStructure(); @@ -232,7 +233,7 @@ const onStructureChange = (model: IStructure) => { dataEditor.value?.resetValidation() } if (globalStore.session.loggedIn) { - autoAskToSyncFolder(model, 'typescript'); + autoAskToSyncFolder(model); } } @@ -259,7 +260,7 @@ watch(() => [ ], autoSyncCallback, { deep: true }); autoSyncCallback(); window.addEventListener('fs-change', () => { - syncFromFolder(structure.value, 'typescript'); + syncFromFolder(structure.value); }) const refreshUserData = async (): Promise => { @@ -311,7 +312,7 @@ if (globalStore.userSettings.data.userDataAutoFetch) { refreshUserData(); } if (globalStore.session.loggedIn) { - autoAskToSyncFolder(modelStore.structure, 'typescript'); + autoAskToSyncFolder(modelStore.structure); } diff --git a/src/components/StructureEditor.vue b/src/components/StructureEditor.vue index 0b6cf93..6422625 100644 --- a/src/components/StructureEditor.vue +++ b/src/components/StructureEditor.vue @@ -11,6 +11,7 @@ import '@/plugins/aceeditor'; import {useGlobalStore} from '@/stores/global'; import {useTypings} from "@/composables/typings"; import {useModelStore} from "@/stores/model"; +import {useSyncing} from "@/composables/syncing"; // import yaml from 'js-yaml'; const emit = defineEmits(['save', 'create', 'change', 'focus', 'blur']); @@ -22,7 +23,8 @@ const { columns = false, userData } = defineProps<{ }>(); const { canSaveStructure, yamlException, structureStates } = useStructure(); const modelStore = useModelStore(); -const { getTypescriptTypings, getTypescriptDefaultObj, isFolderSynced, lastStateTimestamp, askToSyncFolder, unSyncFolder } = useTypings(); +const { getTypescriptTypings, getTypescriptDefaultObj, lastStateTimestamp } = useTypings(); +const { isFolderSynced, askToSyncFolder, unSyncFolder } = useSyncing(); const showParsingDelay = ref(false); const progressBarValue = ref(0); const progressBarCompleted = ref(false); @@ -32,7 +34,7 @@ const blueprintEditorTypings: Ref = ref(null); const blueprintEditorDefault: Ref = ref(null); const blueprintTypings = ref('') const blueprintDefault = ref('') -const blueprintLanguage = ref<'typescript' | 'php'>('typescript') +const blueprintLanguage = ref<'typescript'>('typescript') const sectionMenu = ref(false); @@ -210,10 +212,10 @@ watch(tab, () => { updateBlueprintContent(); const onUnSync = () => { - unSyncFolder(modelStore.structure, 'typescript', true); + unSyncFolder(modelStore.structure, true); } const onSync = () => { - askToSyncFolder(modelStore.structure, 'typescript'); + askToSyncFolder(modelStore.structure); } let lastPosition: { row: number, column: number } | null; @@ -546,7 +548,7 @@ watch(() => globalStore.userSettings.data, () => { > diff --git a/src/components/FileManager.vue b/src/components/FileManager.vue index 9fbedb5..8b05516 100644 --- a/src/components/FileManager.vue +++ b/src/components/FileManager.vue @@ -7,14 +7,15 @@ import ImgTag from '@/components/ImgTag.vue'; import VideoPlayer from '@/components/VideoPlayer.vue'; import {downloadFilesAsZip, getFileIcon, phpStringSizeToBytes} from '@/utils'; import ModalDialog from '@/components/ModalDialog.vue'; -import {useSyncing} from "@/composables/syncing"; +import {isFolderSynced, useSyncing, blobFileList} from "@/composables/syncing"; const globalStore = useGlobalStore(); const syncing = useSyncing(); const structure = defineModel({ required: true }); -const { selected = [], serverSettings, canUpload = false, canDelete = false, canSelect = false, canDownload = false } = defineProps<{ +const { selected = [], serverSettings, canUpload = false, canAddToLocal = false, canDelete = false, canSelect = false, canDownload = false } = defineProps<{ selected?: IFile[], canUpload?: boolean, + canAddToLocal?: boolean, canDelete?: boolean, canSelect?: boolean, canDownload?: boolean, @@ -107,68 +108,116 @@ const onFileClick = (file: IFile) => { } const load = () => { - if (structure.value.endpoint) { - loading.value = true; - return Services.get(structure.value.server_url + '/file/list/' + structure.value.hash, { + loading.value = true; + Promise.all([ + isFolderSynced(structure.value) ? syncing.getFiles(structure.value) : [], + structure.value.endpoint ? Services.get(structure.value.server_url + '/file/list/' + structure.value.hash, { 'Content-Type': 'application/json', 'X-Jms-Api-Key': structure.value.server_secret, - }) - .then(response => files.value = response) - .then(() => { - selectedFiles.value = []; - selected.forEach(selectedFile => { - const file = files.value.find(file => file.path === selectedFile.path); - if (file) { - selectedFiles.value.push(file); + }) : [] + ]).then(([syncResponse, endpointResponse]) => { + files.value = []; + syncResponse.forEach(item => { + if (!['data.json', 'structure.json', 'structure.yml', 'default.ts', 'index.ts', 'typings.ts', 'settings.json'].includes(item.path)) { + files.value.push({ + path: item.path, + meta: { + type: item.file.type, + width: item.width, + height: item.height, + size: item.file.size, + timestamp: item.file.lastModified / 1000, + originalFileName: item.file.name, } - }) - }) - .catch(globalStore.catchError) - .finally(() => loading.value = false); - } + }); + } + }); + + endpointResponse.forEach((item: IFile) => { + files.value.push(item); + }); + + selectedFiles.value = []; + selected.forEach(selectedFile => { + const file = files.value.find(file => file.path === selectedFile.path); + if (file) { + selectedFiles.value.push(file); + } + }) + }) + .catch(globalStore.catchError) + .finally(() => loading.value = false); } -const promptUpload = () => { +const promptUpload = (type: 'remote' | 'local' | null = null) => { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.multiple = true; if (globalStore.fileManager.accept) { fileInput.accept = globalStore.fileManager.accept; } + if (type === null && canAddToLocal) { + type = 'local'; + } + if (type === null && canUpload) { + type = 'remote'; + } fileInput.addEventListener('change', function() { - if (fileInput.files && fileInput.files.length > 0) { - upload(fileInput.files); + if (fileInput.files && fileInput.files.length > 0 && type) { + upload(fileInput.files, type); } }); fileInput.click(); } -const upload = async (fileList: FileList) => { +const upload = async (fileList: FileList, type: 'remote' | 'local') => { uploading.value = true; uploadProgress.value = 0; const promises = []; - for (let i = 0; i < fileList.length; i++) { - const file = fileList[i]; - if (file.size > phpStringSizeToBytes(serverSettings.uploadMaxSize)) { - globalStore.catchError(new Error( - 'This file is exceeding the maximum size of ' + serverSettings.uploadMaxSize + ' defined by the server.' - )); + if (type === 'remote') { + for (let i = 0; i < fileList.length; i++) { + const file = fileList[i]; + if (file.size > phpStringSizeToBytes(serverSettings.uploadMaxSize)) { + globalStore.catchError(new Error( + 'This file is exceeding the maximum size of ' + serverSettings.uploadMaxSize + ' defined by the server.' + )); + } } } for (let i = 0; i < fileList.length; i++) { const file = fileList[i]; - promises.push( - Services.upload(structure.value.server_url + '/file/upload/' + structure.value.hash, file, progress => uploadProgress.value = progress, { - 'X-Jms-Api-Key': structure.value.server_secret, - }) - .then(response => { - if (!files.value.find(item => item.path === response.internalPath)) { - files.value.push({ - 'path': response.internalPath, - 'meta': response.meta, - }) - } + if (type === 'remote') { + promises.push( + Services.upload(structure.value.server_url + '/file/upload/' + structure.value.hash, file, progress => uploadProgress.value = progress, { + 'X-Jms-Api-Key': structure.value.server_secret, + }) + .then(response => { + if (!files.value.find(item => item.path === response.internalPath)) { + files.value.push({ + 'path': response.internalPath, + 'meta': response.meta, + }) + } + })) + } else if (type === 'local') { + const folder = 'files'; + promises.push(syncing.addFile(structure.value, file, folder).then(async () => { + const path = folder + '/' + file.name; + const metadata = await syncing.getFileMetadata(file, path); + syncing.loadBlob(file, path) + files.value.push({ + path, + meta: { + type: file.type, + width: metadata.width, + height: metadata.height, + size: file.size, + timestamp: file.lastModified / 1000, + originalFileName: file.name, + } + }); })) + } } return Promise.all(promises) .catch(globalStore.catchError) @@ -189,23 +238,33 @@ const remove = () => { const promises = []; for (let i = 0; i < selectedFiles.value.length; i++) { const file = selectedFiles.value[i]; - promises.push( - Services.delete(structure.value.server_url + '/file/delete/' + structure.value.hash + '/' + file.path, { - 'X-Jms-Api-Key': structure.value.server_secret, - }) - .then(() => { - selectedFiles.value = selectedFiles.value.filter(item => item.path !== file.path); - files.value = files.value.filter(item => item.path !== file.path); + const removeCallback = () => { + selectedFiles.value = selectedFiles.value.filter(item => item.path !== file.path); + files.value = files.value.filter(item => item.path !== file.path); + } + if (file.path && blobFileList[file.path]) { + promises.push( + syncing.removeFile(structure.value, file.path) + .then(removeCallback) + ); + } else { + promises.push( + Services.delete(structure.value.server_url + '/file/delete/' + structure.value.hash + '/' + file.path, { + 'X-Jms-Api-Key': structure.value.server_secret, }) - ); + .then(removeCallback) + ); + } } return Promise.all(promises) .catch(err => { reject(); globalStore.catchError(err); }) - .finally(resolve) - .finally(() => deleting.value = false); + .finally(() => { + resolve(); + deleting.value = false; + }) }) }); } @@ -222,7 +281,7 @@ const select = () => { const download = () => { downloading.value = true; nextTick(() => { - downloadFilesAsZip(selectedFiles.value.map(item => serverSettings.publicUrl + item.path), false, 'jsonms-file-download.zip', globalStore.userSettings.data.editorTabSize); + downloadFilesAsZip(selectedFiles.value.map(item => (item.path && blobFileList[item.path]) || (serverSettings.publicUrl + item.path)), false, 'jsonms-file-download.zip', globalStore.userSettings.data.editorTabSize); downloading.value = false; }) } @@ -256,8 +315,6 @@ watch(() => globalStore.fileManager.visible, () => { if (globalStore.fileManager.visible) { selectedFiles.value = []; load(); - - syncing.getFiles(structure.value); } }) @@ -338,7 +395,7 @@ watch(() => globalStore.fileManager.visible, () => { @dragover.prevent.stop="onDragEnter" @dragleave.prevent.stop="onDragLeave" > -
+
globalStore.fileManager.visible, () => { flat > + globalStore.fileManager.visible, () => {
@@ -444,6 +507,17 @@ watch(() => globalStore.fileManager.visible, () => {
+ globalStore.fileManager.visible, () => { text="Upload" variant="outlined" class="px-3" - @click="promptUpload" + @click="() => promptUpload('remote')" />