Merge branch 'quick-export'

This commit is contained in:
Jan Prochazka
2021-06-06 18:40:44 +02:00
24 changed files with 758 additions and 119 deletions

View File

@@ -6,6 +6,7 @@ const copyStream = require('./copyStream');
const fakeObjectReader = require('./fakeObjectReader');
const consoleObjectWriter = require('./consoleObjectWriter');
const jsonLinesWriter = require('./jsonLinesWriter');
const jsonArrayWriter = require('./jsonArrayWriter');
const jsonLinesReader = require('./jsonLinesReader');
const jslDataReader = require('./jslDataReader');
const archiveWriter = require('./archiveWriter');
@@ -26,6 +27,7 @@ const dbgateApi = {
tableReader,
copyStream,
jsonLinesWriter,
jsonArrayWriter,
jsonLinesReader,
fakeObjectReader,
consoleObjectWriter,

View File

@@ -0,0 +1,52 @@
const fs = require('fs');
const stream = require('stream');
class StringifyStream extends stream.Transform {
constructor() {
super({ objectMode: true });
this.wasHeader = false;
this.wasRecord = false;
}
_transform(chunk, encoding, done) {
let skip = false;
if (!this.wasHeader) {
skip =
chunk.__isStreamHeader ||
// TODO remove isArray test
Array.isArray(chunk.columns);
this.wasHeader = true;
}
if (!skip) {
if (!this.wasRecord) {
this.push('[\n');
} else {
this.push(',\n');
}
this.wasRecord = true;
this.push(JSON.stringify(chunk));
}
done();
}
_flush(done) {
if (!this.wasRecord) {
this.push('[]\n');
} else {
this.push('\n]\n');
}
done();
}
}
async function jsonArrayWriter({ fileName, encoding = 'utf-8' }) {
console.log(`Writing file ${fileName}`);
const stringify = new StringifyStream();
const fileStream = fs.createWriteStream(fileName, encoding);
stringify.pipe(fileStream);
stringify['finisher'] = fileStream;
return stringify;
}
module.exports = jsonArrayWriter;

View File

@@ -33,9 +33,16 @@ export interface PluginDefinition {
content: any;
}
export interface QuickExportDefinition {
label: string;
createWriter: (fileName: string) => { functionName: string; props: any };
extension: string;
}
export interface ExtensionsDirectory {
plugins: PluginDefinition[];
fileFormats: FileFormatDefinition[];
quickExports: QuickExportDefinition[];
drivers: EngineDriver[];
themes: ThemeDefinition[];
}

View File

@@ -6,6 +6,7 @@
currentThemeDefinition,
isFileDragActive,
leftPanelWidth,
openedSnackbars,
selectedWidget,
visibleCommandPalette,
visibleToolbar,
@@ -17,11 +18,13 @@
import splitterDrag from './utility/splitterDrag';
import CurrentDropDownMenu from './modals/CurrentDropDownMenu.svelte';
import StatusBar from './widgets/StatusBar.svelte';
import Snackbar from './widgets/Snackbar.svelte';
import ModalLayer from './modals/ModalLayer.svelte';
import DragAndDropFileTarget from './DragAndDropFileTarget.svelte';
import dragDropFileTarget from './utility/dragDropFileTarget';
$: currentThemeType = $currentThemeDefinition?.themeType == 'dark' ? 'theme-type-dark' : 'theme-type-light';
</script>
<div class={`${$currentTheme} ${currentThemeType} root`} use:dragDropFileTarget>
@@ -64,6 +67,11 @@
{#if $isFileDragActive}
<DragAndDropFileTarget />
{/if}
<div class="snackbar-container">
{#each $openedSnackbars as snackbar(snackbar.id)}
<Snackbar {...snackbar} />
{/each}
</div>
</div>
<style>
@@ -139,4 +147,11 @@
bottom: var(--dim-statusbar-height);
left: calc(var(--dim-widget-icon-size) + var(--dim-left-panel-width));
}
.snackbar-container {
position: fixed;
right: 0;
bottom: var(--dim-statusbar-height);
}
</style>

View File

@@ -14,14 +14,19 @@
export const extractKey = data => data.fileName;
export const createMatcher = ({ fileName }) => filter => filterName(filter, fileName);
</script>
<script lang="ts">
import { filterName } from 'dbgate-tools';
import ImportExportModal from '../modals/ImportExportModal.svelte';
import { showModal } from '../modals/modalTools';
import { currentArchive } from '../stores';
import { currentArchive, extensions } from '../stores';
import axiosInstance from '../utility/axiosInstance';
import createQuickExportMenu from '../utility/createQuickExportMenu';
import { exportElectronFile } from '../utility/exportElectronFile';
import openNewTab from '../utility/openNewTab';
import AppObjectCore from './AppObjectCore.svelte';
@@ -60,8 +65,34 @@
{ text: 'Open (readonly)', onClick: handleOpenRead },
{ text: 'Open in free table editor', onClick: handleOpenWrite },
{ text: 'Delete', onClick: handleDelete },
createQuickExportMenu($extensions, fmt => async () => {
exportElectronFile(
data.fileName,
{
functionName: 'archiveReader',
props: {
fileName: data.fileName,
folderName: data.folderName,
},
},
fmt
);
}),
{
text: 'Export',
onClick: () => {
showModal(ImportExportModal, {
initialValues: {
sourceStorageType: 'archive',
sourceArchiveFolder: data.folderName,
sourceList: [data.fileName],
},
});
},
},
];
}
</script>
<AppObjectCore

View File

@@ -1,6 +1,7 @@
<script lang="ts" context="module">
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
export const createMatcher = ({ pureName }) => filter => filterName(filter, pureName);
const electron = getElectron();
const icons = {
tables: 'img table',
@@ -46,6 +47,10 @@
{
divider: true,
},
{
isQuickExport: true,
functionName: 'tableReader',
},
{
label: 'Export',
isExport: true,
@@ -108,6 +113,10 @@
{
divider: true,
},
{
isQuickExport: true,
functionName: 'tableReader',
},
{
label: 'Export',
isExport: true,
@@ -165,6 +174,10 @@
{
divider: true,
},
{
isQuickExport: true,
functionName: 'tableReader',
},
{
label: 'Export',
isExport: true,
@@ -261,6 +274,10 @@
},
},
},
{
isQuickExport: true,
functionName: 'tableReader',
},
{
label: 'Export',
isExport: true,
@@ -311,6 +328,7 @@
{ forceNewTab }
);
}
</script>
<script lang="ts">
@@ -326,7 +344,10 @@
import { findEngineDriver } from 'dbgate-tools';
import uuidv1 from 'uuid/v1';
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
import getConnectionLabel from '../utility/getConnectionLabel';
import getConnectionLabel from '../utility/getConnectionLabel';
import getElectron from '../utility/getElectron';
import { exportElectronFile } from '../utility/exportElectronFile';
import createQuickExportMenu from '../utility/createQuickExportMenu';
export let data;
@@ -370,104 +391,129 @@ import getConnectionLabel from '../utility/getConnectionLabel';
function createMenu() {
const { objectTypeField } = data;
return menus[objectTypeField].map(menu => {
if (menu.divider) return menu;
return {
text: menu.label,
onClick: async () => {
if (menu.isExport) {
showModal(ImportExportModal, {
initialValues: {
sourceStorageType: 'database',
sourceConnectionId: data.conid,
sourceDatabaseName: data.database,
sourceSchemaName: data.schemaName,
sourceList: [data.pureName],
},
});
} else if (menu.isOpenFreeTable) {
return menus[objectTypeField]
.filter(x => x)
.map(menu => {
if (menu.divider) return menu;
if (menu.isQuickExport) {
return createQuickExportMenu($extensions, fmt => async () => {
const coninfo = await getConnectionInfo(data);
openNewTab({
title: data.pureName,
icon: 'img free-table',
tabComponent: 'FreeTableTab',
props: {
initialArgs: {
functionName: 'tableReader',
props: {
connection: {
...coninfo,
database: data.database,
exportElectronFile(
data.pureName,
{
functionName: menu.functionName,
props: {
connection: {
..._.omit(coninfo, ['_id', 'displayName']),
..._.pick(data, ['database']),
},
..._.pick(data, ['pureName', 'schemaName']),
},
},
fmt
);
});
}
return {
text: menu.label,
onClick: async () => {
if (menu.isExport) {
showModal(ImportExportModal, {
initialValues: {
sourceStorageType: 'database',
sourceConnectionId: data.conid,
sourceDatabaseName: data.database,
sourceSchemaName: data.schemaName,
sourceList: [data.pureName],
},
});
} else if (menu.isOpenFreeTable) {
const coninfo = await getConnectionInfo(data);
openNewTab({
title: data.pureName,
icon: 'img free-table',
tabComponent: 'FreeTableTab',
props: {
initialArgs: {
functionName: 'tableReader',
props: {
connection: {
...coninfo,
database: data.database,
},
schemaName: data.schemaName,
pureName: data.pureName,
},
schemaName: data.schemaName,
pureName: data.pureName,
},
},
},
});
} else if (menu.isActiveChart) {
const driver = await getDriver();
const dmp = driver.createDumper();
dmp.put('^select * from %f', data);
openNewTab(
{
title: data.pureName,
icon: 'img chart',
tabComponent: 'ChartTab',
props: {
conid: data.conid,
database: data.database,
});
} else if (menu.isActiveChart) {
const driver = await getDriver();
const dmp = driver.createDumper();
dmp.put('^select * from %f', data);
openNewTab(
{
title: data.pureName,
icon: 'img chart',
tabComponent: 'ChartTab',
props: {
conid: data.conid,
database: data.database,
},
},
},
{
editor: {
config: { chartType: 'bar' },
sql: dmp.s,
{
editor: {
config: { chartType: 'bar' },
sql: dmp.s,
},
}
);
} else if (menu.isQueryDesigner) {
openNewTab(
{
title: 'Query #',
icon: 'img query-design',
tabComponent: 'QueryDesignTab',
props: {
conid: data.conid,
database: data.database,
},
},
}
);
} else if (menu.isQueryDesigner) {
openNewTab(
{
title: 'Query #',
icon: 'img query-design',
tabComponent: 'QueryDesignTab',
props: {
conid: data.conid,
database: data.database,
},
},
{
editor: {
tables: [
{
...data,
designerId: uuidv1(),
left: 50,
top: 50,
},
],
},
}
);
} else if (menu.sqlGeneratorProps) {
showModal(SqlGeneratorModal, {
initialObjects: [data],
initialConfig: menu.sqlGeneratorProps,
conid: data.conid,
database: data.database,
});
} else {
openDatabaseObjectDetail(menu.tab, menu.scriptTemplate, data, menu.forceNewTab, menu.initialData);
}
},
};
});
{
editor: {
tables: [
{
...data,
designerId: uuidv1(),
left: 50,
top: 50,
},
],
},
}
);
} else if (menu.sqlGeneratorProps) {
showModal(SqlGeneratorModal, {
initialObjects: [data],
initialConfig: menu.sqlGeneratorProps,
conid: data.conid,
database: data.database,
});
} else {
openDatabaseObjectDetail(menu.tab, menu.scriptTemplate, data, menu.forceNewTab, menu.initialData);
}
},
};
});
}
</script>
<AppObjectCore
{...$$restProps}
module={$$props.module}
{data}
title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
icon={icons[data.objectTypeField]}

View File

@@ -112,6 +112,7 @@
return response.data.count;
}
</script>
<script lang="ts">
@@ -125,10 +126,14 @@
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
import ImportExportModal from '../modals/ImportExportModal.svelte';
import { showModal } from '../modals/modalTools';
import { extensions } from '../stores';
import axiosInstance from '../utility/axiosInstance';
import { registerMenu } from '../utility/contextMenu';
import createActivator, { getActiveComponent } from '../utility/createActivator';
import createQuickExportMenu from '../utility/createQuickExportMenu';
import { exportElectronFile } from '../utility/exportElectronFile';
import { getConnectionInfo } from '../utility/metadataLoaders';
import openNewTab from '../utility/openNewTab';
import ChangeSetGrider from './ChangeSetGrider';
@@ -201,8 +206,30 @@
registerMenu(
{ command: 'collectionDataGrid.openQuery', tag: 'export' },
{
...createQuickExportMenu($extensions, fmt => async () => {
const coninfo = await getConnectionInfo({ conid });
exportElectronFile(
pureName || 'Data',
{
functionName: 'queryReader',
props: {
connection: {
..._.omit(coninfo, ['_id', 'displayName']),
database,
},
sql: getExportQuery(),
},
},
fmt
);
}),
tag: 'export',
},
{ command: 'collectionDataGrid.export', tag: 'export' }
);
</script>
<LoadingDataGridCore

View File

@@ -39,6 +39,7 @@
});
return response.data.rowCount;
}
</script>
<script lang="ts">
@@ -46,10 +47,13 @@
import registerCommand from '../commands/registerCommand';
import ImportExportModal from '../modals/ImportExportModal.svelte';
import { showModal } from '../modals/modalTools';
import { extensions } from '../stores';
import axiosInstance from '../utility/axiosInstance';
import { registerMenu } from '../utility/contextMenu';
import createActivator, { getActiveComponent } from '../utility/createActivator';
import createQuickExportMenu from '../utility/createQuickExportMenu';
import { exportElectronFile } from '../utility/exportElectronFile';
import socket from '../utility/socket';
import useEffect from '../utility/useEffect';
@@ -103,7 +107,40 @@
showModal(ImportExportModal, { initialValues });
}
registerMenu({ command: 'jslTableGrid.export', tag: 'export' });
registerMenu(
{
...createQuickExportMenu($extensions, fmt => async () => {
const archiveMatch = jslid.match(/^archive:\/\/([^/]+)\/(.*)$/);
if (archiveMatch) {
exportElectronFile(
archiveMatch[2],
{
functionName: 'archiveReader',
props: {
folderName: archiveMatch[1],
fileName: archiveMatch[2],
},
},
fmt
);
} else {
exportElectronFile(
'Query',
{
functionName: 'jslDataReader',
props: {
jslid,
},
},
fmt
);
}
}),
tag: 'export',
},
{ command: 'jslTableGrid.export', tag: 'export' }
);
</script>
<LoadingDataGridCore

View File

@@ -68,20 +68,23 @@
return parseInt(response.data.rows[0].count);
}
</script>
<script lang="ts">
import { changeSetToSql, createChangeSet } from 'dbgate-datalib';
import { scriptToSql } from 'dbgate-sqltree';
import _ from 'lodash';
import registerCommand from '../commands/registerCommand';
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
import ImportExportModal from '../modals/ImportExportModal.svelte';
import { showModal } from '../modals/modalTools';
import { extensions } from '../stores';
import axiosInstance from '../utility/axiosInstance';
import { registerMenu } from '../utility/contextMenu';
import createActivator, { getActiveComponent } from '../utility/createActivator';
import createQuickExportMenu from '../utility/createQuickExportMenu';
import { exportElectronFile } from '../utility/exportElectronFile';
import { getConnectionInfo } from '../utility/metadataLoaders';
import openNewTab from '../utility/openNewTab';
import ChangeSetGrider from './ChangeSetGrider';
@@ -173,8 +176,29 @@
registerMenu(
{ command: 'sqlDataGrid.openActiveChart', tag: 'chart' },
{ command: 'sqlDataGrid.openQuery', tag: 'export' },
{
...createQuickExportMenu($extensions, fmt => async () => {
const coninfo = await getConnectionInfo({ conid });
exportElectronFile(
pureName || 'Data',
{
functionName: 'queryReader',
props: {
connection: {
..._.omit(coninfo, ['_id', 'displayName']),
database,
},
sql: display.getExportQuery(),
},
},
fmt
);
}),
tag: 'export',
},
{ command: 'sqlDataGrid.export', tag: 'export' }
);
</script>
<LoadingDataGridCore

View File

@@ -20,14 +20,6 @@
</script>
<script lang="ts">
import { changeSetToSql, createChangeSet } from 'dbgate-datalib';
import { scriptToSql } from 'dbgate-sqltree';
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
import { showModal } from '../modals/modalTools';
import axiosInstance from '../utility/axiosInstance';
import ChangeSetFormer from './ChangeSetFormer';
import FormView from './FormView.svelte';

View File

@@ -67,6 +67,7 @@
'icon chevron-left': 'mdi mdi-chevron-left',
'icon chevron-right': 'mdi mdi-chevron-right',
'icon chevron-up': 'mdi mdi-chevron-up',
'icon menu-right': 'mdi mdi-menu-right',
'icon plugin': 'mdi mdi-toy-brick',
'icon menu': 'mdi mdi-menu',
@@ -76,6 +77,7 @@
'img error': 'mdi mdi-close-circle color-icon-red',
'img error-inv': 'mdi mdi-close-circle color-icon-inv-red',
'img warn': 'mdi mdi-alert color-icon-gold',
'img info': 'mdi mdi-information color-icon-blue',
// 'img statusbar-ok': 'mdi mdi-check-circle color-on-statusbar-green',
'img archive': 'mdi mdi-table color-icon-gold',

View File

@@ -1,9 +1,10 @@
<script context="module">
function getElementOffset(element) {
function getElementOffset(element, side = null) {
var de = document.documentElement;
var box = element.getBoundingClientRect();
var top = box.top + window.pageYOffset - de.clientTop;
var left = box.left + window.pageXOffset - de.clientLeft;
if (side == 'right') return { top: top, left: left + box.width };
return { top: top, left: left };
}
@@ -52,6 +53,7 @@
}
return item;
}
</script>
<script>
@@ -61,18 +63,35 @@
import { onMount } from 'svelte';
import { commandsCustomized, visibleCommandPalette } from '../stores';
import { extractMenuItems } from '../utility/contextMenu';
import FontIcon from '../icons/FontIcon.svelte';
export let items;
export let top;
export let left;
export let onCloseParent;
let element;
let hoverItem;
let hoverOffset;
let submenuItem;
let submenuOffset;
const dispatch = createEventDispatcher();
function handleClick(item) {
function handleClick(e, item) {
if (item.disabled) return;
if (item.submenu) {
hoverItem = item;
hoverOffset = getElementOffset(e.target, 'right');
submenuItem = item;
submenuOffset = hoverOffset;
return;
}
dispatch('close');
if (onCloseParent) onCloseParent();
if (item.onClick) item.onClick();
}
@@ -80,32 +99,68 @@
fixPopupPlacement(element);
});
const changeActiveSubmenu = _.throttle(() => {
submenuItem = hoverItem;
submenuOffset = hoverOffset;
}, 500);
$: extracted = extractMenuItems(items);
$: compacted = _.compact(extracted.map(x => mapItem(x, $commandsCustomized)));
$: filtered = compacted.filter(x => !x.disabled || !x.hideDisabled);
const handleClickOutside = event => {
// if (element && !element.contains(event.target) && !event.defaultPrevented) {
if (event.target.closest('ul.dropDownMenuMarker')) return;
dispatch('close');
};
onMount(() => {
document.addEventListener('mousedown', handleClickOutside, true);
return () => {
document.removeEventListener('mousedown', handleClickOutside, true);
};
});
</script>
<ul
style={`left: ${left}px; top: ${top}px`}
use:clickOutside
on:clickOutside={() => dispatch('close')}
bind:this={element}
>
<ul class="dropDownMenuMarker" style={`left: ${left}px; top: ${top}px`} bind:this={element}>
{#each filtered as item}
{#if item.divider}
<li class="divider" />
{:else}
<li>
<a on:click={() => handleClick(item)} class:disabled={item.disabled}>
<li
on:mouseenter={e => {
hoverOffset = getElementOffset(e.target, 'right');
hoverItem = item;
changeActiveSubmenu();
}}
>
<a on:click={e => handleClick(e, item)} class:disabled={item.disabled}>
{item.text}
{#if item.keyText}
<span class="keyText">{item.keyText}</span>
{/if}
{#if item.submenu}
<div class="menu-right">
<FontIcon icon="icon menu-right" />
</div>
{/if}
</a>
</li>
{/if}
{/each}
</ul>
{#if submenuItem?.submenu}
<svelte:self
items={submenuItem?.submenu}
{...submenuOffset}
onCloseParent={() => {
if (onCloseParent) onCloseParent();
dispatch('close');
}}
/>
{/if}
<style>
ul {
@@ -136,9 +191,10 @@
a {
padding: 3px 20px;
line-height: 1.42;
display: block;
white-space: nop-wrap;
color: #262626;
display: flex;
justify-content: space-between;
}
a.disabled {
@@ -156,4 +212,10 @@
border-top: 1px solid #f2f2f2;
border-bottom: 1px solid #fff;
}
.menu-right {
position: relative;
left: 15px;
}
</style>

View File

@@ -40,7 +40,6 @@
function buildDrivers(plugins) {
const res = [];
for (const { content } of plugins) {
// if (content.driver) res.push(content.driver);
if (content.drivers) res.push(...content.drivers);
}
return res;
@@ -52,6 +51,7 @@
fileFormats: buildFileFormats(plugins),
themes: buildThemes(plugins),
drivers: buildDrivers(plugins),
quickExports: buildQuickExports(plugins),
};
return extensions;
}
@@ -63,7 +63,7 @@
import { extensions, loadingPluginStore } from '../stores';
import axiosInstance from '../utility/axiosInstance';
import { useInstalledPlugins } from '../utility/metadataLoaders';
import { buildFileFormats } from './fileformats';
import { buildFileFormats, buildQuickExports } from './fileformats';
import { buildThemes } from './themes';
import dbgateTools from 'dbgate-tools';

View File

@@ -1,4 +1,4 @@
import { FileFormatDefinition } from 'dbgate-types';
import { FileFormatDefinition, QuickExportDefinition } from 'dbgate-types';
const jsonlFormat = {
storageType: 'jsonl',
@@ -8,8 +8,37 @@ const jsonlFormat = {
writerFunc: 'jsonLinesWriter',
};
const jsonFormat = {
storageType: 'json',
extension: 'json',
name: 'JSON',
writerFunc: 'jsonArrayWriter',
};
const jsonlQuickExport = {
label: 'JSON lines',
extension: 'jsonl',
createWriter: fileName => ({
functionName: 'jsonLinesWriter',
props: {
fileName,
},
}),
};
const jsonQuickExport = {
label: 'JSON',
extension: 'json',
createWriter: fileName => ({
functionName: 'jsonArrayWriter',
props: {
fileName,
},
}),
};
export function buildFileFormats(plugins): FileFormatDefinition[] {
const res = [jsonlFormat];
const res = [jsonlFormat, jsonFormat];
for (const { content } of plugins) {
const { fileFormats } = content;
if (fileFormats) res.push(...fileFormats);
@@ -17,6 +46,14 @@ export function buildFileFormats(plugins): FileFormatDefinition[] {
return res;
}
export function buildQuickExports(plugins): QuickExportDefinition[] {
const res = [jsonQuickExport, jsonlQuickExport];
for (const { content } of plugins) {
if (content.quickExports) res.push(...content.quickExports);
}
return res;
}
export function findFileFormat(extensions, storageType) {
return extensions.fileFormats.find(x => x.storageType == storageType);
}

View File

@@ -55,6 +55,7 @@ export const visibleToolbar = writableWithStorage(true, 'visibleToolbar');
export const leftPanelWidth = writable(300);
export const currentDropDownMenu = writable(null);
export const openedModals = writable([]);
export const openedSnackbars = writable([]);
export const nullStore = readable(null, () => {});
export const currentArchive = writable('default');
export const isFileDragActive = writable(false);

View File

@@ -14,24 +14,37 @@
findReplace: true,
});
// registerCommand({
// id: 'shell.openWizard',
// category: 'Shell',
// name: 'Open wizard',
// // testEnabled: () => getCurrentEditor()?.openWizardEnabled(),
// onClick: () => getCurrentEditor().openWizard(),
// });
const configRegex = /\s*\/\/\s*@ImportExportConfigurator\s*\n\s*\/\/\s*(\{[^\n]+\})\n/;
const requireRegex = /\s*(\/\/\s*@require\s+[^\n]+)\n/g;
const initRegex = /([^\n]+\/\/\s*@init)/g;
</script>
<script lang="ts">
import { getContext } from 'svelte';
import { getContext } from 'svelte';
import invalidateCommands from '../commands/invalidateCommands';
import registerCommand from '../commands/registerCommand';
import { registerFileCommands } from '../commands/stdCommands';
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
import ImportExportModal from '../modals/ImportExportModal.svelte';
import { showModal } from '../modals/modalTools';
import AceEditor from '../query/AceEditor.svelte';
import RunnerOutputPane from '../query/RunnerOutputPane.svelte';
import useEditorData from '../query/useEditorData';
import axiosInstance from '../utility/axiosInstance';
import { changeTab } from '../utility/common';
import createActivator, { getActiveComponent } from '../utility/createActivator';
import { showSnackbarError } from '../utility/snackbar';
import socket from '../utility/socket';
import useEffect from '../utility/useEffect';
import useTimerLabel from '../utility/useTimerLabel';
@@ -118,6 +131,19 @@
return busy;
}
// export function openWizardEnabled() {
// return ($editorValue || '').match(configRegex);
// }
export function openWizard() {
const jsonTextMatch = ($editorValue || '').match(configRegex);
if (jsonTextMatch) {
showModal(ImportExportModal, { initialValues: JSON.parse(jsonTextMatch[1]) });
} else {
showSnackbarError('No wizard info found');
}
}
export async function execute() {
if (busy) return;
executeNumber += 1;
@@ -138,6 +164,10 @@
timerLabel.start();
}
export function canKill() {
return busy;
}
export function kill() {
axiosInstance.post('runners/cancel', {
runid: runnerId,
@@ -151,6 +181,7 @@
return [
{ command: 'shell.execute' },
{ command: 'shell.kill' },
{ command: 'shell.openWizard' },
{ divider: true },
{ command: 'shell.toggleComment' },
{ divider: true },
@@ -161,6 +192,7 @@
{ command: 'shell.replace' },
];
}
</script>
<VerticalSplitter>

View File

@@ -16,6 +16,7 @@
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName'];
export const allowAddToFavorites = props => true;
</script>
<script lang="ts">
@@ -46,6 +47,7 @@
import createActivator, { getActiveComponent } from '../utility/createActivator';
import registerCommand from '../commands/registerCommand';
import { registerMenu } from '../utility/contextMenu';
import { showSnackbarSuccess } from '../utility/snackbar';
export let tabid;
export let conid;
@@ -78,6 +80,7 @@
} else {
dispatchChangeSet({ type: 'reset', value: createChangeSet() });
cache.update(reloadDataCacheFunc);
showSnackbarSuccess('Saved to database');
}
}
@@ -102,6 +105,7 @@
}
registerMenu({ command: 'tableData.save', tag: 'save' });
</script>
<TableDataGrid

View File

@@ -38,7 +38,7 @@ function doExtractMenuItems(menu, res) {
for (const item of menu) {
doExtractMenuItems(item, res);
}
} else if (_.isPlainObject(menu)) {
} else if (_.isPlainObject(menu) && !menu._skip) {
res.push(menu);
}
}

View File

@@ -0,0 +1,19 @@
import { ExtensionsDirectory, QuickExportDefinition } from 'dbgate-types';
import getElectron from './getElectron';
export default function createQuickExportMenu(
extensions: ExtensionsDirectory,
handler: (fmt: QuickExportDefinition) => Function
) {
const electron = getElectron();
if (!electron) {
return { _skip: true };
}
return {
text: 'Quick export',
submenu: extensions.quickExports.map(fmt => ({
text: fmt.label,
onClick: handler(fmt),
})),
};
}

View File

@@ -0,0 +1,56 @@
import ScriptWriter from '../impexp/ScriptWriter';
import getElectron from './getElectron';
import axiosInstance from '../utility/axiosInstance';
import socket from '../utility/socket';
import { showSnackbar, showSnackbarInfo, showSnackbarError, closeSnackbar } from '../utility/snackbar';
export async function exportElectronFile(dataName, reader, format) {
const electron = getElectron();
const filters = [{ name: format.label, extensions: [format.extension] }];
const filePath = electron.remote.dialog.showSaveDialogSync(electron.remote.getCurrentWindow(), {
filters,
defaultPath: `${dataName}.${format.extension}`,
properties: ['showOverwriteConfirmation'],
});
if (!filePath) return;
const script = new ScriptWriter();
const sourceVar = script.allocVariable();
script.assign(sourceVar, reader.functionName, reader.props);
const targetVar = script.allocVariable();
const writer = format.createWriter(filePath, dataName);
script.assign(targetVar, writer.functionName, writer.props);
script.copyStream(sourceVar, targetVar);
script.put();
const resp = await axiosInstance.post('runners/start', { script: script.getScript() });
const runid = resp.data.runid;
let isCanceled = false;
const snackId = showSnackbar({
message: `Exporting ${dataName}`,
icon: 'icon loading',
buttons: [
{
label: 'Cancel',
onClick: () => {
isCanceled = true;
axiosInstance.post('runners/cancel', { runid });
},
},
],
});
function handleRunnerDone() {
closeSnackbar(snackId);
socket.off(`runner-done-${runid}`, handleRunnerDone);
if (isCanceled) showSnackbarError(`Export ${dataName} canceled`);
else showSnackbarInfo(`Export ${dataName} finished`);
}
socket.on(`runner-done-${runid}`, handleRunnerDone);
}

View File

@@ -0,0 +1,75 @@
import { openedSnackbars } from '../stores';
export interface SnackbarButton {
label: string;
onClick: Function;
}
export interface SnackbarInfo {
message: string;
icon?: string;
autoClose?: boolean;
allowClose?: boolean;
buttons?: SnackbarButton[];
}
let lastSnackbarId = 0;
export function showSnackbar(snackbar: SnackbarInfo): string {
lastSnackbarId += 1;
const id = lastSnackbarId.toString();
openedSnackbars.update(x => [
...x,
{
...snackbar,
id,
},
]);
return id;
}
export function showSnackbarSuccess(message: string) {
showSnackbar({
message,
icon: 'img ok',
allowClose: true,
autoClose: true,
});
}
export function showSnackbarInfo(message: string) {
showSnackbar({
message,
icon: 'img info',
allowClose: true,
autoClose: true,
});
}
export function showSnackbarError(message: string) {
showSnackbar({
message,
icon: 'img error',
allowClose: true,
autoClose: true,
});
}
export function closeSnackbar(snackId: string) {
openedSnackbars.update(x => x.filter(x => x.id != snackId));
}
// showSnackbar({
// icon: 'img ok',
// message: 'Test snackbar',
// allowClose: true,
// });
showSnackbar({
icon: 'img ok',
message: 'Auto close',
autoClose: true,
});
// showSnackbar({
// icon: 'img warn',
// message: 'Buttons',
// buttons: [{ label: 'OK', onClick: () => console.log('OK') }],
// });

View File

@@ -0,0 +1,80 @@
<script lang="ts">
import FontIcon from '../icons/FontIcon.svelte';
import { onMount } from 'svelte';
import FormStyledButton from '../elements/FormStyledButton.svelte';
import { openedSnackbars } from '../stores';
export let message;
export let id;
export let icon = null;
export let autoClose = false;
export let allowClose = false;
export let buttons = [];
function handleClose() {
openedSnackbars.update(x => x.filter(x => x.id != id));
}
onMount(() => {
if (autoClose) setTimeout(handleClose, 3000);
});
</script>
<div class="wrapper">
<div class="message">
<FontIcon {icon} />
{message}
</div>
{#if allowClose}
<div class="close" on:click={handleClose}>
<FontIcon icon="icon close" />
</div>
{/if}
{#if buttons?.length > 0}
<div class="buttons">
{#each buttons as button}
<div class="button">
<FormStyledButton value={button.label} on:click={button.onClick} />
</div>
{/each}
</div>
{/if}
</div>
<style>
.wrapper {
width: 400px;
border: 1px solid var(--theme-border);
background-color: var(--theme-bg-2);
margin: 10px;
position: relative;
}
.message {
margin: 10px;
}
.close {
position: absolute;
right: 5px;
top: 5px;
cursor: pointer;
}
.close:hover {
color: var(--theme-font-hover);
}
.buttons {
display: flex;
justify-content: flex-end;
}
.button {
margin: 5px;
}
</style>

View File

@@ -43,4 +43,29 @@ const fileFormat = {
export default {
fileFormats: [fileFormat],
quickExports: [
{
label: 'CSV file',
extension: 'csv',
createWriter: (fileName) => ({
functionName: 'writer@dbgate-plugin-csv',
props: {
fileName,
delimiter: ',',
},
}),
},
{
label: 'CSV file (semicolor separated)',
extension: 'csv',
createWriter: (fileName) => ({
functionName: 'writer@dbgate-plugin-csv',
props: {
fileName,
delimiter: ';',
},
}),
},
],
};

View File

@@ -64,5 +64,18 @@ const fileFormat = {
export default {
fileFormats: [fileFormat],
quickExports: [
{
label: 'MS Excel',
extension: 'xlsx',
createWriter: (fileName, dataName) => ({
functionName: 'writer@dbgate-plugin-excel',
props: {
fileName,
sheetName: dataName,
},
}),
},
],
initialize,
};