From 595c9424dfcb4f3671b5ae6c7a325e549c0c9d20 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 27 Jan 2022 14:31:46 +0100 Subject: [PATCH 01/18] apps skeleton --- packages/api/src/controllers/apps.js | 100 ++++++++++++++ packages/api/src/controllers/files.js | 11 +- packages/api/src/main.js | 2 + packages/api/src/utility/directories.js | 2 + .../web/src/appobj/AppFileAppObject.svelte | 123 ++++++++++++++++++ .../web/src/appobj/AppFolderAppObject.svelte | 64 +++++++++ .../src/appobj/ArchiveFileAppObject.svelte | 2 +- packages/web/src/commands/stdCommands.ts | 17 +++ packages/web/src/icons/FontIcon.svelte | 4 + packages/web/src/stores.ts | 1 + packages/web/src/utility/metadataLoaders.ts | 26 ++++ packages/web/src/widgets/AppFilesList.svelte | 89 +++++++++++++ packages/web/src/widgets/AppFolderList.svelte | 39 ++++++ packages/web/src/widgets/AppWidget.svelte | 19 +++ .../web/src/widgets/WidgetContainer.svelte | 4 + .../web/src/widgets/WidgetIconPanel.svelte | 5 + 16 files changed, 506 insertions(+), 2 deletions(-) create mode 100644 packages/api/src/controllers/apps.js create mode 100644 packages/web/src/appobj/AppFileAppObject.svelte create mode 100644 packages/web/src/appobj/AppFolderAppObject.svelte create mode 100644 packages/web/src/widgets/AppFilesList.svelte create mode 100644 packages/web/src/widgets/AppFolderList.svelte create mode 100644 packages/web/src/widgets/AppWidget.svelte diff --git a/packages/api/src/controllers/apps.js b/packages/api/src/controllers/apps.js new file mode 100644 index 00000000..655f13b5 --- /dev/null +++ b/packages/api/src/controllers/apps.js @@ -0,0 +1,100 @@ +const fs = require('fs-extra'); +const path = require('path'); +const { appdir } = require('../utility/directories'); +const socket = require('../utility/socket'); + +module.exports = { + folders_meta: true, + async folders() { + const folders = await fs.readdir(appdir()); + return [ + ...folders.map(name => ({ + name, + })), + ]; + }, + + createFolder_meta: true, + async createFolder({ folder }) { + await fs.mkdir(path.join(appdir(), folder)); + socket.emitChanged('app-folders-changed'); + return true; + }, + + files_meta: true, + async files({ folder }) { + const dir = path.join(appdir(), folder); + if (!(await fs.exists(dir))) return []; + const files = await fs.readdir(dir); + + function fileType(ext, type) { + return files + .filter(name => name.endsWith(ext)) + .map(name => ({ + name: name.slice(0, -ext.length), + label: path.parse(name.slice(0, -ext.length)).base, + type, + })); + } + + function refsType() { + return files + .filter(name => name == 'virtual-references.json') + .map(name => ({ + name: 'virtual-references.json', + label: 'virtual-references.json', + type: 'vfk', + })); + } + + return [...refsType(), ...fileType('.command.sql', 'command.sql'), ...fileType('.query.sql', 'query.sql')]; + }, + + refreshFiles_meta: true, + async refreshFiles({ folder }) { + socket.emitChanged(`app-files-changed-${folder}`); + }, + + refreshFolders_meta: true, + async refreshFolders() { + socket.emitChanged(`app-folders-changed`); + }, + + deleteFile_meta: true, + async deleteFile({ folder, file, fileType }) { + await fs.unlink(path.join(appdir(), folder, `${file}.${fileType}`)); + socket.emitChanged(`app-files-changed-${folder}`); + }, + + renameFile_meta: true, + async renameFile({ folder, file, newFile, fileType }) { + await fs.rename( + path.join(path.join(appdir(), folder), `${file}.${fileType}`), + path.join(path.join(appdir(), folder), `${newFile}.${fileType}`) + ); + socket.emitChanged(`app-files-changed-${folder}`); + }, + + renameFolder_meta: true, + async renameFolder({ folder, newFolder }) { + const uniqueName = await this.getNewAppFolder({ name: newFolder }); + await fs.rename(path.join(appdir(), folder), path.join(appdir(), uniqueName)); + socket.emitChanged(`app-folders-changed`); + }, + + deleteFolder_meta: true, + async deleteFolder({ folder }) { + if (!folder) throw new Error('Missing folder parameter'); + await fs.rmdir(path.join(appdir(), folder), { recursive: true }); + socket.emitChanged(`app-folders-changed`); + }, + + async getNewAppFolder({ name }) { + if (!(await fs.exists(path.join(appdir(), name)))) return name; + let index = 2; + while (await fs.exists(path.join(appdir(), `${name}${index}`))) { + index += 1; + } + return `${name}${index}`; + }, +}; diff --git a/packages/api/src/controllers/files.js b/packages/api/src/controllers/files.js index 78a29778..8c8319a7 100644 --- a/packages/api/src/controllers/files.js +++ b/packages/api/src/controllers/files.js @@ -1,7 +1,7 @@ const uuidv1 = require('uuid/v1'); const fs = require('fs-extra'); const path = require('path'); -const { filesdir, archivedir, resolveArchiveFolder, uploadsdir } = require('../utility/directories'); +const { filesdir, archivedir, resolveArchiveFolder, uploadsdir, appdir } = require('../utility/directories'); const getChartExport = require('../utility/getChartExport'); const hasPermission = require('../utility/hasPermission'); const socket = require('../utility/socket'); @@ -74,6 +74,11 @@ module.exports = { encoding: 'utf-8', }); return deserialize(format, text); + } else if (folder.startsWith('app:')) { + const text = await fs.readFile(path.join(appdir(), folder.substring('app:'.length), file), { + encoding: 'utf-8', + }); + return deserialize(format, text); } else { if (!hasPermission(`files/${folder}/read`)) return null; const text = await fs.readFile(path.join(filesdir(), folder, file), { encoding: 'utf-8' }); @@ -88,6 +93,10 @@ module.exports = { await fs.writeFile(path.join(dir, file), serialize(format, data)); socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`); return true; + } else if (folder.startsWith('app:')) { + await fs.writeFile(path.join(appdir(), folder.substring('app:'.length), file), serialize(format, data)); + socket.emitChanged(`app-files-changed-${folder.substring('app:'.length)}`); + return true; } else { if (!hasPermission(`files/${folder}/write`)) return false; const dir = path.join(filesdir(), folder); diff --git a/packages/api/src/main.js b/packages/api/src/main.js index 5cb5ec46..2ad5588d 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -19,6 +19,7 @@ const runners = require('./controllers/runners'); const jsldata = require('./controllers/jsldata'); const config = require('./controllers/config'); const archive = require('./controllers/archive'); +const apps = require('./controllers/apps'); const uploads = require('./controllers/uploads'); const plugins = require('./controllers/plugins'); const files = require('./controllers/files'); @@ -157,6 +158,7 @@ function useAllControllers(app, electron) { useController(app, electron, '/files', files); useController(app, electron, '/scheduler', scheduler); useController(app, electron, '/query-history', queryHistory); + useController(app, electron, '/apps', apps); } function initializeElectronSender(electronSender) { diff --git a/packages/api/src/utility/directories.js b/packages/api/src/utility/directories.js index 9af898e1..35b5c887 100644 --- a/packages/api/src/utility/directories.js +++ b/packages/api/src/utility/directories.js @@ -38,6 +38,7 @@ const rundir = dirFunc('run', true); const uploadsdir = dirFunc('uploads', true); const pluginsdir = dirFunc('plugins'); const archivedir = dirFunc('archive'); +const appdir = dirFunc('apps'); const filesdir = dirFunc('files'); function packagedPluginsDir() { @@ -103,6 +104,7 @@ module.exports = { rundir, uploadsdir, archivedir, + appdir, ensureDirectory, pluginsdir, filesdir, diff --git a/packages/web/src/appobj/AppFileAppObject.svelte b/packages/web/src/appobj/AppFileAppObject.svelte new file mode 100644 index 00000000..e1525103 --- /dev/null +++ b/packages/web/src/appobj/AppFileAppObject.svelte @@ -0,0 +1,123 @@ + + + + + diff --git a/packages/web/src/appobj/AppFolderAppObject.svelte b/packages/web/src/appobj/AppFolderAppObject.svelte new file mode 100644 index 00000000..64e0bc8a --- /dev/null +++ b/packages/web/src/appobj/AppFolderAppObject.svelte @@ -0,0 +1,64 @@ + + + + + ($currentApplication = data.name)} + menu={createMenu} +/> diff --git a/packages/web/src/appobj/ArchiveFileAppObject.svelte b/packages/web/src/appobj/ArchiveFileAppObject.svelte index 83e24c4a..bc2b3cbf 100644 --- a/packages/web/src/appobj/ArchiveFileAppObject.svelte +++ b/packages/web/src/appobj/ArchiveFileAppObject.svelte @@ -81,7 +81,7 @@ markArchiveFileAsDataSheet, markArchiveFileAsReadonly, } from '../utility/archiveTools'; -import { apiCall } from '../utility/api'; + import { apiCall } from '../utility/api'; export let data; diff --git a/packages/web/src/commands/stdCommands.ts b/packages/web/src/commands/stdCommands.ts index b5b051e0..85b79c5c 100644 --- a/packages/web/src/commands/stdCommands.ts +++ b/packages/web/src/commands/stdCommands.ts @@ -128,6 +128,23 @@ registerCommand({ }, }); +registerCommand({ + id: 'new.application', + category: 'New', + icon: 'img app', + name: 'Application', + onClick: () => { + showModal(InputTextModal, { + value: '', + label: 'New application name', + header: 'Create application', + onConfirm: async folder => { + apiCall('apps/create-folder', { folder }); + }, + }); + }, +}); + registerCommand({ id: 'new.table', category: 'New', diff --git a/packages/web/src/icons/FontIcon.svelte b/packages/web/src/icons/FontIcon.svelte index 2c79d71f..90aebcfe 100644 --- a/packages/web/src/icons/FontIcon.svelte +++ b/packages/web/src/icons/FontIcon.svelte @@ -26,6 +26,7 @@ 'icon version': 'mdi mdi-ticket-confirmation', 'icon pin': 'mdi mdi-pin', 'icon arrange': 'mdi mdi-arrange-send-to-back', + 'icon app': 'mdi mdi-layers-triple', 'icon columns': 'mdi mdi-view-column', 'icon columns-outline': 'mdi mdi-view-column-outline', @@ -126,6 +127,9 @@ 'img diagram': 'mdi mdi-graph color-icon-blue', 'img yaml': 'mdi mdi-code-brackets color-icon-red', 'img compare': 'mdi mdi-compare color-icon-red', + 'img app': 'mdi mdi-layers-triple color-icon-magenta', + 'img app-command': 'mdi mdi-flash color-icon-green', + 'img app-query': 'mdi mdi-view-comfy color-icon-magenta', 'img add': 'mdi mdi-plus-circle color-icon-green', 'img minus': 'mdi mdi-minus-circle color-icon-red', diff --git a/packages/web/src/stores.ts b/packages/web/src/stores.ts index 068ba507..0266b9b8 100644 --- a/packages/web/src/stores.ts +++ b/packages/web/src/stores.ts @@ -64,6 +64,7 @@ export const openedModals = writable([]); export const openedSnackbars = writable([]); export const nullStore = readable(null, () => {}); export const currentArchive = writableWithStorage('default', 'currentArchive'); +export const currentApplication = writableWithStorage(null, 'currentApplication'); export const isFileDragActive = writable(false); export const selectedCellsCallback = writable(null); export const loadingPluginStore = writable({ diff --git a/packages/web/src/utility/metadataLoaders.ts b/packages/web/src/utility/metadataLoaders.ts index 3fcf6fa6..d688b929 100644 --- a/packages/web/src/utility/metadataLoaders.ts +++ b/packages/web/src/utility/metadataLoaders.ts @@ -103,6 +103,18 @@ const archiveFilesLoader = ({ folder }) => ({ reloadTrigger: `archive-files-changed-${folder}`, }); +const appFoldersLoader = () => ({ + url: 'apps/folders', + params: {}, + reloadTrigger: `app-folders-changed`, +}); + +const appFilesLoader = ({ folder }) => ({ + url: 'apps/files', + params: { folder }, + reloadTrigger: `app-files-changed-${folder}`, +}); + const serverStatusLoader = () => ({ url: 'server-connections/server-status', params: {}, @@ -401,6 +413,20 @@ export function useArchiveFolders(args = {}) { return useCore(archiveFoldersLoader, args); } +export function getAppFiles(args) { + return getCore(appFilesLoader, args); +} +export function useAppFiles(args) { + return useCore(appFilesLoader, args); +} + +export function getAppFolders(args = {}) { + return getCore(appFoldersLoader, args); +} +export function useAppFolders(args = {}) { + return useCore(appFoldersLoader, args); +} + export function getInstalledPlugins(args = {}) { return getCore(installedPluginsLoader, args) || []; } diff --git a/packages/web/src/widgets/AppFilesList.svelte b/packages/web/src/widgets/AppFilesList.svelte new file mode 100644 index 00000000..3206586a --- /dev/null +++ b/packages/web/src/widgets/AppFilesList.svelte @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + ({ + fileName: file.name, + folderName: folder, + fileType: file.type, + fileLabel: file.label, + }))} + groupFunc={data => APP_LABELS[data.fileType] || 'App config'} + module={appFileAppObject} + {filter} + /> + diff --git a/packages/web/src/widgets/AppFolderList.svelte b/packages/web/src/widgets/AppFolderList.svelte new file mode 100644 index 00000000..84aa0871 --- /dev/null +++ b/packages/web/src/widgets/AppFolderList.svelte @@ -0,0 +1,39 @@ + + + + + + runCommand('new.application')} title="Create new application"> + + + + + + + + + diff --git a/packages/web/src/widgets/AppWidget.svelte b/packages/web/src/widgets/AppWidget.svelte new file mode 100644 index 00000000..13074843 --- /dev/null +++ b/packages/web/src/widgets/AppWidget.svelte @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/packages/web/src/widgets/WidgetContainer.svelte b/packages/web/src/widgets/WidgetContainer.svelte index 14802429..c53900b5 100644 --- a/packages/web/src/widgets/WidgetContainer.svelte +++ b/packages/web/src/widgets/WidgetContainer.svelte @@ -6,6 +6,7 @@ import PluginsWidget from './PluginsWidget.svelte'; import CellDataWidget from './CellDataWidget.svelte'; import HistoryWidget from './HistoryWidget.svelte'; + import AppWidget from './AppWidget.svelte';