mirror of
https://github.com/dbgate/dbgate
synced 2024-11-07 20:26:23 +00:00
apps skeleton
This commit is contained in:
parent
8e3d614fb8
commit
595c9424df
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 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);
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
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,
|
||||
markArchiveFileAsReadonly,
|
||||
} from '../utility/archiveTools';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
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({
|
||||
id: 'new.table',
|
||||
category: 'New',
|
||||
|
@ -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',
|
||||
|
@ -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({
|
||||
|
@ -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) || [];
|
||||
}
|
||||
|
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 CellDataWidget from './CellDataWidget.svelte';
|
||||
import HistoryWidget from './HistoryWidget.svelte';
|
||||
import AppWidget from './AppWidget.svelte';
|
||||
</script>
|
||||
|
||||
<DatabaseWidget hidden={$selectedWidget != 'database'} />
|
||||
@ -25,3 +26,6 @@
|
||||
{#if $selectedWidget == 'cell-data'}
|
||||
<CellDataWidget />
|
||||
{/if}
|
||||
{#if $selectedWidget == 'app'}
|
||||
<AppWidget />
|
||||
{/if}
|
||||
|
@ -40,6 +40,11 @@
|
||||
name: 'cell-data',
|
||||
title: 'Selected cell data detail view',
|
||||
},
|
||||
{
|
||||
icon: 'icon app',
|
||||
name: 'app',
|
||||
title: 'Application layers',
|
||||
},
|
||||
// {
|
||||
// icon: 'icon settings',
|
||||
// name: 'settings',
|
||||
|
Loading…
Reference in New Issue
Block a user