mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-28 16:26:00 +00:00
apps skeleton
This commit is contained in:
100
packages/api/src/controllers/apps.js
Normal file
100
packages/api/src/controllers/apps.js
Normal file
@@ -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}`;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
const uuidv1 = require('uuid/v1');
|
const uuidv1 = require('uuid/v1');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const path = require('path');
|
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 getChartExport = require('../utility/getChartExport');
|
||||||
const hasPermission = require('../utility/hasPermission');
|
const hasPermission = require('../utility/hasPermission');
|
||||||
const socket = require('../utility/socket');
|
const socket = require('../utility/socket');
|
||||||
@@ -74,6 +74,11 @@ module.exports = {
|
|||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
});
|
});
|
||||||
return deserialize(format, text);
|
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 {
|
} else {
|
||||||
if (!hasPermission(`files/${folder}/read`)) return null;
|
if (!hasPermission(`files/${folder}/read`)) return null;
|
||||||
const text = await fs.readFile(path.join(filesdir(), folder, file), { encoding: 'utf-8' });
|
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));
|
await fs.writeFile(path.join(dir, file), serialize(format, data));
|
||||||
socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`);
|
socket.emitChanged(`archive-files-changed-${folder.substring('archive:'.length)}`);
|
||||||
return true;
|
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 {
|
} else {
|
||||||
if (!hasPermission(`files/${folder}/write`)) return false;
|
if (!hasPermission(`files/${folder}/write`)) return false;
|
||||||
const dir = path.join(filesdir(), folder);
|
const dir = path.join(filesdir(), folder);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const runners = require('./controllers/runners');
|
|||||||
const jsldata = require('./controllers/jsldata');
|
const jsldata = require('./controllers/jsldata');
|
||||||
const config = require('./controllers/config');
|
const config = require('./controllers/config');
|
||||||
const archive = require('./controllers/archive');
|
const archive = require('./controllers/archive');
|
||||||
|
const apps = require('./controllers/apps');
|
||||||
const uploads = require('./controllers/uploads');
|
const uploads = require('./controllers/uploads');
|
||||||
const plugins = require('./controllers/plugins');
|
const plugins = require('./controllers/plugins');
|
||||||
const files = require('./controllers/files');
|
const files = require('./controllers/files');
|
||||||
@@ -157,6 +158,7 @@ function useAllControllers(app, electron) {
|
|||||||
useController(app, electron, '/files', files);
|
useController(app, electron, '/files', files);
|
||||||
useController(app, electron, '/scheduler', scheduler);
|
useController(app, electron, '/scheduler', scheduler);
|
||||||
useController(app, electron, '/query-history', queryHistory);
|
useController(app, electron, '/query-history', queryHistory);
|
||||||
|
useController(app, electron, '/apps', apps);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeElectronSender(electronSender) {
|
function initializeElectronSender(electronSender) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const rundir = dirFunc('run', true);
|
|||||||
const uploadsdir = dirFunc('uploads', true);
|
const uploadsdir = dirFunc('uploads', true);
|
||||||
const pluginsdir = dirFunc('plugins');
|
const pluginsdir = dirFunc('plugins');
|
||||||
const archivedir = dirFunc('archive');
|
const archivedir = dirFunc('archive');
|
||||||
|
const appdir = dirFunc('apps');
|
||||||
const filesdir = dirFunc('files');
|
const filesdir = dirFunc('files');
|
||||||
|
|
||||||
function packagedPluginsDir() {
|
function packagedPluginsDir() {
|
||||||
@@ -103,6 +104,7 @@ module.exports = {
|
|||||||
rundir,
|
rundir,
|
||||||
uploadsdir,
|
uploadsdir,
|
||||||
archivedir,
|
archivedir,
|
||||||
|
appdir,
|
||||||
ensureDirectory,
|
ensureDirectory,
|
||||||
pluginsdir,
|
pluginsdir,
|
||||||
filesdir,
|
filesdir,
|
||||||
|
|||||||
123
packages/web/src/appobj/AppFileAppObject.svelte
Normal file
123
packages/web/src/appobj/AppFileAppObject.svelte
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
async function openTextFile(fileName, fileType, folderName, tabComponent, icon) {
|
||||||
|
const connProps: any = {};
|
||||||
|
let tooltip = undefined;
|
||||||
|
|
||||||
|
const resp = await apiCall('files/load', {
|
||||||
|
folder: 'app:' + folderName,
|
||||||
|
file: fileName + '.' + fileType,
|
||||||
|
format: 'text',
|
||||||
|
});
|
||||||
|
|
||||||
|
openNewTab(
|
||||||
|
{
|
||||||
|
title: fileName,
|
||||||
|
icon,
|
||||||
|
tabComponent,
|
||||||
|
tooltip,
|
||||||
|
props: {
|
||||||
|
savedFile: fileName + '.' + fileType,
|
||||||
|
savedFolder: 'app:' + folderName,
|
||||||
|
savedFormat: 'text',
|
||||||
|
appFolder: folderName,
|
||||||
|
...connProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ editor: resp }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extractKey = data => data.fileName;
|
||||||
|
export const createMatcher = ({ fileName }) => filter => filterName(filter, fileName);
|
||||||
|
const APP_ICONS = {
|
||||||
|
'command.sql': 'img app-command',
|
||||||
|
'query.sql': 'img app-query',
|
||||||
|
};
|
||||||
|
|
||||||
|
function getAppIcon( data) {
|
||||||
|
return APP_ICONS[data.fileType];
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { filterName } from 'dbgate-tools';
|
||||||
|
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||||
|
import { showModal } from '../modals/modalTools';
|
||||||
|
|
||||||
|
import { archiveFilesAsDataSheets, currentArchive, extensions, getCurrentDatabase } from '../stores';
|
||||||
|
|
||||||
|
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
||||||
|
import { exportElectronFile } from '../utility/exportElectronFile';
|
||||||
|
import openNewTab from '../utility/openNewTab';
|
||||||
|
import AppObjectCore from './AppObjectCore.svelte';
|
||||||
|
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||||
|
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||||
|
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||||
|
import {
|
||||||
|
isArchiveFileMarkedAsDataSheet,
|
||||||
|
markArchiveFileAsDataSheet,
|
||||||
|
markArchiveFileAsReadonly,
|
||||||
|
} from '../utility/archiveTools';
|
||||||
|
import { apiCall } from '../utility/api';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
const handleRename = () => {
|
||||||
|
showModal(InputTextModal, {
|
||||||
|
value: data.fileName,
|
||||||
|
label: 'New file name',
|
||||||
|
header: 'Rename file',
|
||||||
|
onConfirm: newFile => {
|
||||||
|
apiCall('apps/rename-file', {
|
||||||
|
file: data.fileName,
|
||||||
|
folder: data.folderName,
|
||||||
|
fileType: data.fileType,
|
||||||
|
newFile,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
showModal(ConfirmModal, {
|
||||||
|
message: `Really delete file ${data.fileName}?`,
|
||||||
|
onConfirm: () => {
|
||||||
|
apiCall('apps/delete-file', {
|
||||||
|
file: data.fileName,
|
||||||
|
folder: data.folderName,
|
||||||
|
fileType: data.fileType,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleClick = () => {
|
||||||
|
if (data.fileType.endsWith('.sql')) {
|
||||||
|
handleOpenSqlFile();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleOpenSqlFile = () => {
|
||||||
|
openTextFile(data.fileName, data.fileType, data.folderName, 'QueryTab', 'img sql-file');
|
||||||
|
};
|
||||||
|
const handleOpenYamlFile = () => {
|
||||||
|
openTextFile(data.fileName, data.fileType, data.folderName, 'YamlEditorTab', 'img yaml');
|
||||||
|
};
|
||||||
|
|
||||||
|
function createMenu() {
|
||||||
|
return [
|
||||||
|
{ text: 'Delete', onClick: handleDelete },
|
||||||
|
{ text: 'Rename', onClick: handleRename },
|
||||||
|
data.fileType.endsWith('.sql') && { text: 'Open SQL', onClick: handleOpenSqlFile },
|
||||||
|
// data.fileType.endsWith('.yaml') && { text: 'Open YAML', onClick: handleOpenYamlFile },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AppObjectCore
|
||||||
|
{...$$restProps}
|
||||||
|
{data}
|
||||||
|
title={data.fileLabel}
|
||||||
|
icon={getAppIcon( data)}
|
||||||
|
menu={createMenu}
|
||||||
|
on:click={handleClick}
|
||||||
|
/>
|
||||||
64
packages/web/src/appobj/AppFolderAppObject.svelte
Normal file
64
packages/web/src/appobj/AppFolderAppObject.svelte
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export const extractKey = data => data.name;
|
||||||
|
export const createMatcher = data => filter => filterName(filter, data.name);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { filterName } from 'dbgate-tools';
|
||||||
|
|
||||||
|
import { currentApplication } from '../stores';
|
||||||
|
|
||||||
|
import AppObjectCore from './AppObjectCore.svelte';
|
||||||
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||||
|
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||||
|
import { apiCall } from '../utility/api';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
showModal(ConfirmModal, {
|
||||||
|
message: `Really delete application ${data.name}?`,
|
||||||
|
onConfirm: () => {
|
||||||
|
apiCall('apps/delete-folder', { folder: data.name });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRename = () => {
|
||||||
|
const { name } = data;
|
||||||
|
|
||||||
|
showModal(InputTextModal, {
|
||||||
|
value: name,
|
||||||
|
label: 'New application name',
|
||||||
|
header: 'Rename application',
|
||||||
|
onConfirm: async newFolder => {
|
||||||
|
await apiCall('apps/rename-folder', {
|
||||||
|
folder: data.name,
|
||||||
|
newFolder: newFolder,
|
||||||
|
});
|
||||||
|
if ($currentApplication == data.name) {
|
||||||
|
$currentApplication = newFolder;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function createMenu() {
|
||||||
|
return [
|
||||||
|
{ text: 'Delete', onClick: handleDelete },
|
||||||
|
{ text: 'Rename', onClick: handleRename },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AppObjectCore
|
||||||
|
{...$$restProps}
|
||||||
|
{data}
|
||||||
|
title={data.name}
|
||||||
|
icon={'img app'}
|
||||||
|
isBold={data.name == $currentApplication}
|
||||||
|
on:click={() => ($currentApplication = data.name)}
|
||||||
|
menu={createMenu}
|
||||||
|
/>
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
markArchiveFileAsDataSheet,
|
markArchiveFileAsDataSheet,
|
||||||
markArchiveFileAsReadonly,
|
markArchiveFileAsReadonly,
|
||||||
} from '../utility/archiveTools';
|
} from '../utility/archiveTools';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
|
|
||||||
|
|||||||
@@ -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({
|
registerCommand({
|
||||||
id: 'new.table',
|
id: 'new.table',
|
||||||
category: 'New',
|
category: 'New',
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
'icon version': 'mdi mdi-ticket-confirmation',
|
'icon version': 'mdi mdi-ticket-confirmation',
|
||||||
'icon pin': 'mdi mdi-pin',
|
'icon pin': 'mdi mdi-pin',
|
||||||
'icon arrange': 'mdi mdi-arrange-send-to-back',
|
'icon arrange': 'mdi mdi-arrange-send-to-back',
|
||||||
|
'icon app': 'mdi mdi-layers-triple',
|
||||||
|
|
||||||
'icon columns': 'mdi mdi-view-column',
|
'icon columns': 'mdi mdi-view-column',
|
||||||
'icon columns-outline': 'mdi mdi-view-column-outline',
|
'icon columns-outline': 'mdi mdi-view-column-outline',
|
||||||
@@ -126,6 +127,9 @@
|
|||||||
'img diagram': 'mdi mdi-graph color-icon-blue',
|
'img diagram': 'mdi mdi-graph color-icon-blue',
|
||||||
'img yaml': 'mdi mdi-code-brackets color-icon-red',
|
'img yaml': 'mdi mdi-code-brackets color-icon-red',
|
||||||
'img compare': 'mdi mdi-compare 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 add': 'mdi mdi-plus-circle color-icon-green',
|
||||||
'img minus': 'mdi mdi-minus-circle color-icon-red',
|
'img minus': 'mdi mdi-minus-circle color-icon-red',
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export const openedModals = writable([]);
|
|||||||
export const openedSnackbars = writable([]);
|
export const openedSnackbars = writable([]);
|
||||||
export const nullStore = readable(null, () => {});
|
export const nullStore = readable(null, () => {});
|
||||||
export const currentArchive = writableWithStorage('default', 'currentArchive');
|
export const currentArchive = writableWithStorage('default', 'currentArchive');
|
||||||
|
export const currentApplication = writableWithStorage(null, 'currentApplication');
|
||||||
export const isFileDragActive = writable(false);
|
export const isFileDragActive = writable(false);
|
||||||
export const selectedCellsCallback = writable(null);
|
export const selectedCellsCallback = writable(null);
|
||||||
export const loadingPluginStore = writable({
|
export const loadingPluginStore = writable({
|
||||||
|
|||||||
@@ -103,6 +103,18 @@ const archiveFilesLoader = ({ folder }) => ({
|
|||||||
reloadTrigger: `archive-files-changed-${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 = () => ({
|
const serverStatusLoader = () => ({
|
||||||
url: 'server-connections/server-status',
|
url: 'server-connections/server-status',
|
||||||
params: {},
|
params: {},
|
||||||
@@ -401,6 +413,20 @@ export function useArchiveFolders(args = {}) {
|
|||||||
return useCore(archiveFoldersLoader, 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 = {}) {
|
export function getInstalledPlugins(args = {}) {
|
||||||
return getCore(installedPluginsLoader, args) || [];
|
return getCore(installedPluginsLoader, args) || [];
|
||||||
}
|
}
|
||||||
|
|||||||
89
packages/web/src/widgets/AppFilesList.svelte
Normal file
89
packages/web/src/widgets/AppFilesList.svelte
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
const APP_LABELS = {
|
||||||
|
'command.sql': 'SQL commands',
|
||||||
|
'query.sql': 'SQL queries',
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { createFreeTableModel } from 'dbgate-datalib';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import AppObjectList from '../appobj/AppObjectList.svelte';
|
||||||
|
import * as appFileAppObject from '../appobj/AppFileAppObject.svelte';
|
||||||
|
import CloseSearchButton from '../elements/CloseSearchButton.svelte';
|
||||||
|
import DropDownButton from '../elements/DropDownButton.svelte';
|
||||||
|
|
||||||
|
import InlineButton from '../elements/InlineButton.svelte';
|
||||||
|
|
||||||
|
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
||||||
|
import SearchInput from '../elements/SearchInput.svelte';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||||
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import newQuery from '../query/newQuery';
|
||||||
|
import { currentApplication } from '../stores';
|
||||||
|
import { apiCall } from '../utility/api';
|
||||||
|
import { markArchiveFileAsDataSheet } from '../utility/archiveTools';
|
||||||
|
import { useAppFiles, useArchiveFolders } from '../utility/metadataLoaders';
|
||||||
|
import openNewTab from '../utility/openNewTab';
|
||||||
|
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||||
|
|
||||||
|
let filter = '';
|
||||||
|
|
||||||
|
$: folder = $currentApplication;
|
||||||
|
$: files = useAppFiles({ folder });
|
||||||
|
|
||||||
|
const handleRefreshFiles = () => {
|
||||||
|
apiCall('apps/refresh-files', { folder });
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleNewSqlFile(fileType, header) {
|
||||||
|
showModal(InputTextModal, {
|
||||||
|
value: '',
|
||||||
|
label: 'New file name',
|
||||||
|
header,
|
||||||
|
onConfirm: async file => {
|
||||||
|
newQuery({
|
||||||
|
title: file,
|
||||||
|
// @ts-ignore
|
||||||
|
savedFile: file + '.' + fileType,
|
||||||
|
savedFolder: 'app:' + $currentApplication,
|
||||||
|
savedFormat: 'text',
|
||||||
|
appFolder: $currentApplication,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAddMenu() {
|
||||||
|
return [
|
||||||
|
{ text: 'New SQL command', onClick: () => handleNewSqlFile('command.sql', 'Create new SQL command') },
|
||||||
|
{ text: 'New query view', onClick: () => handleNewSqlFile('query.sql', 'Create new SQL query') },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SearchBoxWrapper>
|
||||||
|
<SearchInput placeholder="Search application files" bind:value={filter} />
|
||||||
|
|
||||||
|
<CloseSearchButton bind:filter />
|
||||||
|
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
|
||||||
|
<InlineButton on:click={handleRefreshFiles} title="Refresh files of selected application">
|
||||||
|
<FontIcon icon="icon refresh" />
|
||||||
|
</InlineButton>
|
||||||
|
</SearchBoxWrapper>
|
||||||
|
<WidgetsInnerContainer>
|
||||||
|
<AppObjectList
|
||||||
|
list={($files || []).map(file => ({
|
||||||
|
fileName: file.name,
|
||||||
|
folderName: folder,
|
||||||
|
fileType: file.type,
|
||||||
|
fileLabel: file.label,
|
||||||
|
}))}
|
||||||
|
groupFunc={data => APP_LABELS[data.fileType] || 'App config'}
|
||||||
|
module={appFileAppObject}
|
||||||
|
{filter}
|
||||||
|
/>
|
||||||
|
</WidgetsInnerContainer>
|
||||||
39
packages/web/src/widgets/AppFolderList.svelte
Normal file
39
packages/web/src/widgets/AppFolderList.svelte
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import AppObjectList from '../appobj/AppObjectList.svelte';
|
||||||
|
import * as appFolderAppObject from '../appobj/AppFolderAppObject.svelte';
|
||||||
|
import runCommand from '../commands/runCommand';
|
||||||
|
import CloseSearchButton from '../elements/CloseSearchButton.svelte';
|
||||||
|
|
||||||
|
import InlineButton from '../elements/InlineButton.svelte';
|
||||||
|
|
||||||
|
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
||||||
|
import SearchInput from '../elements/SearchInput.svelte';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
import { apiCall } from '../utility/api';
|
||||||
|
import { useAppFolders } from '../utility/metadataLoaders';
|
||||||
|
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||||
|
|
||||||
|
let filter = '';
|
||||||
|
|
||||||
|
$: folders = useAppFolders();
|
||||||
|
|
||||||
|
const handleRefreshFolders = () => {
|
||||||
|
apiCall('apps/refresh-folders');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SearchBoxWrapper>
|
||||||
|
<SearchInput placeholder="Search applications" bind:value={filter} />
|
||||||
|
<CloseSearchButton bind:filter />
|
||||||
|
<InlineButton on:click={() => runCommand('new.application')} title="Create new application">
|
||||||
|
<FontIcon icon="icon plus-thick" />
|
||||||
|
</InlineButton>
|
||||||
|
<InlineButton on:click={handleRefreshFolders} title="Refresh application list">
|
||||||
|
<FontIcon icon="icon refresh" />
|
||||||
|
</InlineButton>
|
||||||
|
</SearchBoxWrapper>
|
||||||
|
<WidgetsInnerContainer>
|
||||||
|
<AppObjectList list={_.sortBy($folders, 'name')} module={appFolderAppObject} {filter} />
|
||||||
|
</WidgetsInnerContainer>
|
||||||
19
packages/web/src/widgets/AppWidget.svelte
Normal file
19
packages/web/src/widgets/AppWidget.svelte
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import AppFilesList from './AppFilesList.svelte';
|
||||||
|
|
||||||
|
import WidgetColumnBar from './WidgetColumnBar.svelte';
|
||||||
|
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
|
||||||
|
|
||||||
|
import { useFavorites } from '../utility/metadataLoaders';
|
||||||
|
import AppFolderList from './AppFolderList.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<WidgetColumnBar>
|
||||||
|
<WidgetColumnBarItem title="Applications" name="apps" height="30%" storageName="appsWidget">
|
||||||
|
<AppFolderList />
|
||||||
|
</WidgetColumnBarItem>
|
||||||
|
|
||||||
|
<WidgetColumnBarItem title="Application files" name="files" storageName="appFilesWidget">
|
||||||
|
<AppFilesList />
|
||||||
|
</WidgetColumnBarItem>
|
||||||
|
</WidgetColumnBar>
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
import PluginsWidget from './PluginsWidget.svelte';
|
import PluginsWidget from './PluginsWidget.svelte';
|
||||||
import CellDataWidget from './CellDataWidget.svelte';
|
import CellDataWidget from './CellDataWidget.svelte';
|
||||||
import HistoryWidget from './HistoryWidget.svelte';
|
import HistoryWidget from './HistoryWidget.svelte';
|
||||||
|
import AppWidget from './AppWidget.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DatabaseWidget hidden={$selectedWidget != 'database'} />
|
<DatabaseWidget hidden={$selectedWidget != 'database'} />
|
||||||
@@ -25,3 +26,6 @@
|
|||||||
{#if $selectedWidget == 'cell-data'}
|
{#if $selectedWidget == 'cell-data'}
|
||||||
<CellDataWidget />
|
<CellDataWidget />
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $selectedWidget == 'app'}
|
||||||
|
<AppWidget />
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -40,6 +40,11 @@
|
|||||||
name: 'cell-data',
|
name: 'cell-data',
|
||||||
title: 'Selected cell data detail view',
|
title: 'Selected cell data detail view',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'icon app',
|
||||||
|
name: 'app',
|
||||||
|
title: 'Application layers',
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// icon: 'icon settings',
|
// icon: 'icon settings',
|
||||||
// name: 'settings',
|
// name: 'settings',
|
||||||
|
|||||||
Reference in New Issue
Block a user