mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-03 17:33:58 +00:00
Merge branch 'quick-export'
This commit is contained in:
@@ -6,6 +6,7 @@ const copyStream = require('./copyStream');
|
|||||||
const fakeObjectReader = require('./fakeObjectReader');
|
const fakeObjectReader = require('./fakeObjectReader');
|
||||||
const consoleObjectWriter = require('./consoleObjectWriter');
|
const consoleObjectWriter = require('./consoleObjectWriter');
|
||||||
const jsonLinesWriter = require('./jsonLinesWriter');
|
const jsonLinesWriter = require('./jsonLinesWriter');
|
||||||
|
const jsonArrayWriter = require('./jsonArrayWriter');
|
||||||
const jsonLinesReader = require('./jsonLinesReader');
|
const jsonLinesReader = require('./jsonLinesReader');
|
||||||
const jslDataReader = require('./jslDataReader');
|
const jslDataReader = require('./jslDataReader');
|
||||||
const archiveWriter = require('./archiveWriter');
|
const archiveWriter = require('./archiveWriter');
|
||||||
@@ -26,6 +27,7 @@ const dbgateApi = {
|
|||||||
tableReader,
|
tableReader,
|
||||||
copyStream,
|
copyStream,
|
||||||
jsonLinesWriter,
|
jsonLinesWriter,
|
||||||
|
jsonArrayWriter,
|
||||||
jsonLinesReader,
|
jsonLinesReader,
|
||||||
fakeObjectReader,
|
fakeObjectReader,
|
||||||
consoleObjectWriter,
|
consoleObjectWriter,
|
||||||
|
|||||||
52
packages/api/src/shell/jsonArrayWriter.js
Normal file
52
packages/api/src/shell/jsonArrayWriter.js
Normal 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;
|
||||||
7
packages/types/extensions.d.ts
vendored
7
packages/types/extensions.d.ts
vendored
@@ -33,9 +33,16 @@ export interface PluginDefinition {
|
|||||||
content: any;
|
content: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface QuickExportDefinition {
|
||||||
|
label: string;
|
||||||
|
createWriter: (fileName: string) => { functionName: string; props: any };
|
||||||
|
extension: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ExtensionsDirectory {
|
export interface ExtensionsDirectory {
|
||||||
plugins: PluginDefinition[];
|
plugins: PluginDefinition[];
|
||||||
fileFormats: FileFormatDefinition[];
|
fileFormats: FileFormatDefinition[];
|
||||||
|
quickExports: QuickExportDefinition[];
|
||||||
drivers: EngineDriver[];
|
drivers: EngineDriver[];
|
||||||
themes: ThemeDefinition[];
|
themes: ThemeDefinition[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
currentThemeDefinition,
|
currentThemeDefinition,
|
||||||
isFileDragActive,
|
isFileDragActive,
|
||||||
leftPanelWidth,
|
leftPanelWidth,
|
||||||
|
openedSnackbars,
|
||||||
selectedWidget,
|
selectedWidget,
|
||||||
visibleCommandPalette,
|
visibleCommandPalette,
|
||||||
visibleToolbar,
|
visibleToolbar,
|
||||||
@@ -17,11 +18,13 @@
|
|||||||
import splitterDrag from './utility/splitterDrag';
|
import splitterDrag from './utility/splitterDrag';
|
||||||
import CurrentDropDownMenu from './modals/CurrentDropDownMenu.svelte';
|
import CurrentDropDownMenu from './modals/CurrentDropDownMenu.svelte';
|
||||||
import StatusBar from './widgets/StatusBar.svelte';
|
import StatusBar from './widgets/StatusBar.svelte';
|
||||||
|
import Snackbar from './widgets/Snackbar.svelte';
|
||||||
import ModalLayer from './modals/ModalLayer.svelte';
|
import ModalLayer from './modals/ModalLayer.svelte';
|
||||||
import DragAndDropFileTarget from './DragAndDropFileTarget.svelte';
|
import DragAndDropFileTarget from './DragAndDropFileTarget.svelte';
|
||||||
import dragDropFileTarget from './utility/dragDropFileTarget';
|
import dragDropFileTarget from './utility/dragDropFileTarget';
|
||||||
|
|
||||||
$: currentThemeType = $currentThemeDefinition?.themeType == 'dark' ? 'theme-type-dark' : 'theme-type-light';
|
$: currentThemeType = $currentThemeDefinition?.themeType == 'dark' ? 'theme-type-dark' : 'theme-type-light';
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={`${$currentTheme} ${currentThemeType} root`} use:dragDropFileTarget>
|
<div class={`${$currentTheme} ${currentThemeType} root`} use:dragDropFileTarget>
|
||||||
@@ -64,6 +67,11 @@
|
|||||||
{#if $isFileDragActive}
|
{#if $isFileDragActive}
|
||||||
<DragAndDropFileTarget />
|
<DragAndDropFileTarget />
|
||||||
{/if}
|
{/if}
|
||||||
|
<div class="snackbar-container">
|
||||||
|
{#each $openedSnackbars as snackbar(snackbar.id)}
|
||||||
|
<Snackbar {...snackbar} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -139,4 +147,11 @@
|
|||||||
bottom: var(--dim-statusbar-height);
|
bottom: var(--dim-statusbar-height);
|
||||||
left: calc(var(--dim-widget-icon-size) + var(--dim-left-panel-width));
|
left: calc(var(--dim-widget-icon-size) + var(--dim-left-panel-width));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.snackbar-container {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
bottom: var(--dim-statusbar-height);
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,14 +14,19 @@
|
|||||||
|
|
||||||
export const extractKey = data => data.fileName;
|
export const extractKey = data => data.fileName;
|
||||||
export const createMatcher = ({ fileName }) => filter => filterName(filter, fileName);
|
export const createMatcher = ({ fileName }) => filter => filterName(filter, fileName);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { filterName } from 'dbgate-tools';
|
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 axiosInstance from '../utility/axiosInstance';
|
||||||
|
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
||||||
|
import { exportElectronFile } from '../utility/exportElectronFile';
|
||||||
import openNewTab from '../utility/openNewTab';
|
import openNewTab from '../utility/openNewTab';
|
||||||
import AppObjectCore from './AppObjectCore.svelte';
|
import AppObjectCore from './AppObjectCore.svelte';
|
||||||
|
|
||||||
@@ -60,8 +65,34 @@
|
|||||||
{ text: 'Open (readonly)', onClick: handleOpenRead },
|
{ text: 'Open (readonly)', onClick: handleOpenRead },
|
||||||
{ text: 'Open in free table editor', onClick: handleOpenWrite },
|
{ text: 'Open in free table editor', onClick: handleOpenWrite },
|
||||||
{ text: 'Delete', onClick: handleDelete },
|
{ 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>
|
</script>
|
||||||
|
|
||||||
<AppObjectCore
|
<AppObjectCore
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
|
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
|
||||||
export const createMatcher = ({ pureName }) => filter => filterName(filter, pureName);
|
export const createMatcher = ({ pureName }) => filter => filterName(filter, pureName);
|
||||||
|
const electron = getElectron();
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
tables: 'img table',
|
tables: 'img table',
|
||||||
@@ -46,6 +47,10 @@
|
|||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
isQuickExport: true,
|
||||||
|
functionName: 'tableReader',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Export',
|
label: 'Export',
|
||||||
isExport: true,
|
isExport: true,
|
||||||
@@ -108,6 +113,10 @@
|
|||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
isQuickExport: true,
|
||||||
|
functionName: 'tableReader',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Export',
|
label: 'Export',
|
||||||
isExport: true,
|
isExport: true,
|
||||||
@@ -165,6 +174,10 @@
|
|||||||
{
|
{
|
||||||
divider: true,
|
divider: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
isQuickExport: true,
|
||||||
|
functionName: 'tableReader',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Export',
|
label: 'Export',
|
||||||
isExport: true,
|
isExport: true,
|
||||||
@@ -261,6 +274,10 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
isQuickExport: true,
|
||||||
|
functionName: 'tableReader',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Export',
|
label: 'Export',
|
||||||
isExport: true,
|
isExport: true,
|
||||||
@@ -311,6 +328,7 @@
|
|||||||
{ forceNewTab }
|
{ forceNewTab }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -326,7 +344,10 @@
|
|||||||
import { findEngineDriver } from 'dbgate-tools';
|
import { findEngineDriver } from 'dbgate-tools';
|
||||||
import uuidv1 from 'uuid/v1';
|
import uuidv1 from 'uuid/v1';
|
||||||
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
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;
|
export let data;
|
||||||
|
|
||||||
@@ -370,104 +391,129 @@ import getConnectionLabel from '../utility/getConnectionLabel';
|
|||||||
|
|
||||||
function createMenu() {
|
function createMenu() {
|
||||||
const { objectTypeField } = data;
|
const { objectTypeField } = data;
|
||||||
return menus[objectTypeField].map(menu => {
|
return menus[objectTypeField]
|
||||||
if (menu.divider) return menu;
|
.filter(x => x)
|
||||||
return {
|
.map(menu => {
|
||||||
text: menu.label,
|
if (menu.divider) return menu;
|
||||||
onClick: async () => {
|
|
||||||
if (menu.isExport) {
|
if (menu.isQuickExport) {
|
||||||
showModal(ImportExportModal, {
|
return createQuickExportMenu($extensions, fmt => async () => {
|
||||||
initialValues: {
|
|
||||||
sourceStorageType: 'database',
|
|
||||||
sourceConnectionId: data.conid,
|
|
||||||
sourceDatabaseName: data.database,
|
|
||||||
sourceSchemaName: data.schemaName,
|
|
||||||
sourceList: [data.pureName],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (menu.isOpenFreeTable) {
|
|
||||||
const coninfo = await getConnectionInfo(data);
|
const coninfo = await getConnectionInfo(data);
|
||||||
openNewTab({
|
exportElectronFile(
|
||||||
title: data.pureName,
|
data.pureName,
|
||||||
icon: 'img free-table',
|
{
|
||||||
tabComponent: 'FreeTableTab',
|
functionName: menu.functionName,
|
||||||
props: {
|
props: {
|
||||||
initialArgs: {
|
connection: {
|
||||||
functionName: 'tableReader',
|
..._.omit(coninfo, ['_id', 'displayName']),
|
||||||
props: {
|
..._.pick(data, ['database']),
|
||||||
connection: {
|
},
|
||||||
...coninfo,
|
..._.pick(data, ['pureName', 'schemaName']),
|
||||||
database: data.database,
|
},
|
||||||
|
},
|
||||||
|
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) {
|
||||||
} else if (menu.isActiveChart) {
|
const driver = await getDriver();
|
||||||
const driver = await getDriver();
|
const dmp = driver.createDumper();
|
||||||
const dmp = driver.createDumper();
|
dmp.put('^select * from %f', data);
|
||||||
dmp.put('^select * from %f', data);
|
openNewTab(
|
||||||
openNewTab(
|
{
|
||||||
{
|
title: data.pureName,
|
||||||
title: data.pureName,
|
icon: 'img chart',
|
||||||
icon: 'img chart',
|
tabComponent: 'ChartTab',
|
||||||
tabComponent: 'ChartTab',
|
props: {
|
||||||
props: {
|
conid: data.conid,
|
||||||
conid: data.conid,
|
database: data.database,
|
||||||
database: data.database,
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
editor: {
|
||||||
editor: {
|
config: { chartType: 'bar' },
|
||||||
config: { chartType: 'bar' },
|
sql: dmp.s,
|
||||||
sql: dmp.s,
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (menu.isQueryDesigner) {
|
||||||
|
openNewTab(
|
||||||
|
{
|
||||||
|
title: 'Query #',
|
||||||
|
icon: 'img query-design',
|
||||||
|
tabComponent: 'QueryDesignTab',
|
||||||
|
props: {
|
||||||
|
conid: data.conid,
|
||||||
|
database: data.database,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
{
|
||||||
);
|
editor: {
|
||||||
} else if (menu.isQueryDesigner) {
|
tables: [
|
||||||
openNewTab(
|
{
|
||||||
{
|
...data,
|
||||||
title: 'Query #',
|
designerId: uuidv1(),
|
||||||
icon: 'img query-design',
|
left: 50,
|
||||||
tabComponent: 'QueryDesignTab',
|
top: 50,
|
||||||
props: {
|
},
|
||||||
conid: data.conid,
|
],
|
||||||
database: data.database,
|
},
|
||||||
},
|
}
|
||||||
},
|
);
|
||||||
{
|
} else if (menu.sqlGeneratorProps) {
|
||||||
editor: {
|
showModal(SqlGeneratorModal, {
|
||||||
tables: [
|
initialObjects: [data],
|
||||||
{
|
initialConfig: menu.sqlGeneratorProps,
|
||||||
...data,
|
conid: data.conid,
|
||||||
designerId: uuidv1(),
|
database: data.database,
|
||||||
left: 50,
|
});
|
||||||
top: 50,
|
} else {
|
||||||
},
|
openDatabaseObjectDetail(menu.tab, menu.scriptTemplate, data, menu.forceNewTab, menu.initialData);
|
||||||
],
|
}
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
);
|
});
|
||||||
} 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>
|
</script>
|
||||||
|
|
||||||
<AppObjectCore
|
<AppObjectCore
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
|
module={$$props.module}
|
||||||
{data}
|
{data}
|
||||||
title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
|
title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
|
||||||
icon={icons[data.objectTypeField]}
|
icon={icons[data.objectTypeField]}
|
||||||
|
|||||||
@@ -112,6 +112,7 @@
|
|||||||
|
|
||||||
return response.data.count;
|
return response.data.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -125,10 +126,14 @@
|
|||||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||||
import { showModal } from '../modals/modalTools';
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import { extensions } from '../stores';
|
||||||
|
|
||||||
import axiosInstance from '../utility/axiosInstance';
|
import axiosInstance from '../utility/axiosInstance';
|
||||||
import { registerMenu } from '../utility/contextMenu';
|
import { registerMenu } from '../utility/contextMenu';
|
||||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
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 openNewTab from '../utility/openNewTab';
|
||||||
import ChangeSetGrider from './ChangeSetGrider';
|
import ChangeSetGrider from './ChangeSetGrider';
|
||||||
|
|
||||||
@@ -201,8 +206,30 @@
|
|||||||
|
|
||||||
registerMenu(
|
registerMenu(
|
||||||
{ command: 'collectionDataGrid.openQuery', tag: 'export' },
|
{ 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' }
|
{ command: 'collectionDataGrid.export', tag: 'export' }
|
||||||
);
|
);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LoadingDataGridCore
|
<LoadingDataGridCore
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
});
|
});
|
||||||
return response.data.rowCount;
|
return response.data.rowCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -46,10 +47,13 @@
|
|||||||
import registerCommand from '../commands/registerCommand';
|
import registerCommand from '../commands/registerCommand';
|
||||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||||
import { showModal } from '../modals/modalTools';
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import { extensions } from '../stores';
|
||||||
|
|
||||||
import axiosInstance from '../utility/axiosInstance';
|
import axiosInstance from '../utility/axiosInstance';
|
||||||
import { registerMenu } from '../utility/contextMenu';
|
import { registerMenu } from '../utility/contextMenu';
|
||||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||||
|
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
||||||
|
import { exportElectronFile } from '../utility/exportElectronFile';
|
||||||
import socket from '../utility/socket';
|
import socket from '../utility/socket';
|
||||||
import useEffect from '../utility/useEffect';
|
import useEffect from '../utility/useEffect';
|
||||||
|
|
||||||
@@ -103,7 +107,40 @@
|
|||||||
showModal(ImportExportModal, { initialValues });
|
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>
|
</script>
|
||||||
|
|
||||||
<LoadingDataGridCore
|
<LoadingDataGridCore
|
||||||
|
|||||||
@@ -68,20 +68,23 @@
|
|||||||
|
|
||||||
return parseInt(response.data.rows[0].count);
|
return parseInt(response.data.rows[0].count);
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { changeSetToSql, createChangeSet } from 'dbgate-datalib';
|
import _ from 'lodash';
|
||||||
import { scriptToSql } from 'dbgate-sqltree';
|
|
||||||
import registerCommand from '../commands/registerCommand';
|
import registerCommand from '../commands/registerCommand';
|
||||||
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
|
|
||||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
|
||||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||||
import { showModal } from '../modals/modalTools';
|
import { showModal } from '../modals/modalTools';
|
||||||
|
import { extensions } from '../stores';
|
||||||
|
|
||||||
import axiosInstance from '../utility/axiosInstance';
|
import axiosInstance from '../utility/axiosInstance';
|
||||||
import { registerMenu } from '../utility/contextMenu';
|
import { registerMenu } from '../utility/contextMenu';
|
||||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
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 openNewTab from '../utility/openNewTab';
|
||||||
import ChangeSetGrider from './ChangeSetGrider';
|
import ChangeSetGrider from './ChangeSetGrider';
|
||||||
|
|
||||||
@@ -173,8 +176,29 @@
|
|||||||
registerMenu(
|
registerMenu(
|
||||||
{ command: 'sqlDataGrid.openActiveChart', tag: 'chart' },
|
{ command: 'sqlDataGrid.openActiveChart', tag: 'chart' },
|
||||||
{ command: 'sqlDataGrid.openQuery', tag: 'export' },
|
{ 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' }
|
{ command: 'sqlDataGrid.export', tag: 'export' }
|
||||||
);
|
);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LoadingDataGridCore
|
<LoadingDataGridCore
|
||||||
|
|||||||
@@ -20,14 +20,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 axiosInstance from '../utility/axiosInstance';
|
||||||
import ChangeSetFormer from './ChangeSetFormer';
|
import ChangeSetFormer from './ChangeSetFormer';
|
||||||
import FormView from './FormView.svelte';
|
import FormView from './FormView.svelte';
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
'icon chevron-left': 'mdi mdi-chevron-left',
|
'icon chevron-left': 'mdi mdi-chevron-left',
|
||||||
'icon chevron-right': 'mdi mdi-chevron-right',
|
'icon chevron-right': 'mdi mdi-chevron-right',
|
||||||
'icon chevron-up': 'mdi mdi-chevron-up',
|
'icon chevron-up': 'mdi mdi-chevron-up',
|
||||||
|
'icon menu-right': 'mdi mdi-menu-right',
|
||||||
'icon plugin': 'mdi mdi-toy-brick',
|
'icon plugin': 'mdi mdi-toy-brick',
|
||||||
'icon menu': 'mdi mdi-menu',
|
'icon menu': 'mdi mdi-menu',
|
||||||
|
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
'img error': 'mdi mdi-close-circle color-icon-red',
|
'img error': 'mdi mdi-close-circle color-icon-red',
|
||||||
'img error-inv': 'mdi mdi-close-circle color-icon-inv-red',
|
'img error-inv': 'mdi mdi-close-circle color-icon-inv-red',
|
||||||
'img warn': 'mdi mdi-alert color-icon-gold',
|
'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 statusbar-ok': 'mdi mdi-check-circle color-on-statusbar-green',
|
||||||
|
|
||||||
'img archive': 'mdi mdi-table color-icon-gold',
|
'img archive': 'mdi mdi-table color-icon-gold',
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<script context="module">
|
<script context="module">
|
||||||
function getElementOffset(element) {
|
function getElementOffset(element, side = null) {
|
||||||
var de = document.documentElement;
|
var de = document.documentElement;
|
||||||
var box = element.getBoundingClientRect();
|
var box = element.getBoundingClientRect();
|
||||||
var top = box.top + window.pageYOffset - de.clientTop;
|
var top = box.top + window.pageYOffset - de.clientTop;
|
||||||
var left = box.left + window.pageXOffset - de.clientLeft;
|
var left = box.left + window.pageXOffset - de.clientLeft;
|
||||||
|
if (side == 'right') return { top: top, left: left + box.width };
|
||||||
return { top: top, left: left };
|
return { top: top, left: left };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -61,18 +63,35 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { commandsCustomized, visibleCommandPalette } from '../stores';
|
import { commandsCustomized, visibleCommandPalette } from '../stores';
|
||||||
import { extractMenuItems } from '../utility/contextMenu';
|
import { extractMenuItems } from '../utility/contextMenu';
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
|
||||||
export let items;
|
export let items;
|
||||||
export let top;
|
export let top;
|
||||||
export let left;
|
export let left;
|
||||||
|
export let onCloseParent;
|
||||||
|
|
||||||
let element;
|
let element;
|
||||||
|
|
||||||
|
let hoverItem;
|
||||||
|
let hoverOffset;
|
||||||
|
|
||||||
|
let submenuItem;
|
||||||
|
let submenuOffset;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
function handleClick(item) {
|
function handleClick(e, item) {
|
||||||
if (item.disabled) return;
|
if (item.disabled) return;
|
||||||
|
if (item.submenu) {
|
||||||
|
hoverItem = item;
|
||||||
|
hoverOffset = getElementOffset(e.target, 'right');
|
||||||
|
|
||||||
|
submenuItem = item;
|
||||||
|
submenuOffset = hoverOffset;
|
||||||
|
return;
|
||||||
|
}
|
||||||
dispatch('close');
|
dispatch('close');
|
||||||
|
if (onCloseParent) onCloseParent();
|
||||||
if (item.onClick) item.onClick();
|
if (item.onClick) item.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,32 +99,68 @@
|
|||||||
fixPopupPlacement(element);
|
fixPopupPlacement(element);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const changeActiveSubmenu = _.throttle(() => {
|
||||||
|
submenuItem = hoverItem;
|
||||||
|
submenuOffset = hoverOffset;
|
||||||
|
}, 500);
|
||||||
|
|
||||||
$: extracted = extractMenuItems(items);
|
$: extracted = extractMenuItems(items);
|
||||||
$: compacted = _.compact(extracted.map(x => mapItem(x, $commandsCustomized)));
|
$: compacted = _.compact(extracted.map(x => mapItem(x, $commandsCustomized)));
|
||||||
$: filtered = compacted.filter(x => !x.disabled || !x.hideDisabled);
|
$: 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>
|
</script>
|
||||||
|
|
||||||
<ul
|
<ul class="dropDownMenuMarker" style={`left: ${left}px; top: ${top}px`} bind:this={element}>
|
||||||
style={`left: ${left}px; top: ${top}px`}
|
|
||||||
use:clickOutside
|
|
||||||
on:clickOutside={() => dispatch('close')}
|
|
||||||
bind:this={element}
|
|
||||||
>
|
|
||||||
{#each filtered as item}
|
{#each filtered as item}
|
||||||
{#if item.divider}
|
{#if item.divider}
|
||||||
<li class="divider" />
|
<li class="divider" />
|
||||||
{:else}
|
{:else}
|
||||||
<li>
|
<li
|
||||||
<a on:click={() => handleClick(item)} class:disabled={item.disabled}>
|
on:mouseenter={e => {
|
||||||
|
hoverOffset = getElementOffset(e.target, 'right');
|
||||||
|
hoverItem = item;
|
||||||
|
changeActiveSubmenu();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a on:click={e => handleClick(e, item)} class:disabled={item.disabled}>
|
||||||
{item.text}
|
{item.text}
|
||||||
{#if item.keyText}
|
{#if item.keyText}
|
||||||
<span class="keyText">{item.keyText}</span>
|
<span class="keyText">{item.keyText}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if item.submenu}
|
||||||
|
<div class="menu-right">
|
||||||
|
<FontIcon icon="icon menu-right" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
{#if submenuItem?.submenu}
|
||||||
|
<svelte:self
|
||||||
|
items={submenuItem?.submenu}
|
||||||
|
{...submenuOffset}
|
||||||
|
onCloseParent={() => {
|
||||||
|
if (onCloseParent) onCloseParent();
|
||||||
|
dispatch('close');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
ul {
|
ul {
|
||||||
@@ -136,9 +191,10 @@
|
|||||||
a {
|
a {
|
||||||
padding: 3px 20px;
|
padding: 3px 20px;
|
||||||
line-height: 1.42;
|
line-height: 1.42;
|
||||||
display: block;
|
|
||||||
white-space: nop-wrap;
|
white-space: nop-wrap;
|
||||||
color: #262626;
|
color: #262626;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.disabled {
|
a.disabled {
|
||||||
@@ -156,4 +212,10 @@
|
|||||||
border-top: 1px solid #f2f2f2;
|
border-top: 1px solid #f2f2f2;
|
||||||
border-bottom: 1px solid #fff;
|
border-bottom: 1px solid #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-right {
|
||||||
|
position: relative;
|
||||||
|
left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -40,7 +40,6 @@
|
|||||||
function buildDrivers(plugins) {
|
function buildDrivers(plugins) {
|
||||||
const res = [];
|
const res = [];
|
||||||
for (const { content } of plugins) {
|
for (const { content } of plugins) {
|
||||||
// if (content.driver) res.push(content.driver);
|
|
||||||
if (content.drivers) res.push(...content.drivers);
|
if (content.drivers) res.push(...content.drivers);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
@@ -52,6 +51,7 @@
|
|||||||
fileFormats: buildFileFormats(plugins),
|
fileFormats: buildFileFormats(plugins),
|
||||||
themes: buildThemes(plugins),
|
themes: buildThemes(plugins),
|
||||||
drivers: buildDrivers(plugins),
|
drivers: buildDrivers(plugins),
|
||||||
|
quickExports: buildQuickExports(plugins),
|
||||||
};
|
};
|
||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
import { extensions, loadingPluginStore } from '../stores';
|
import { extensions, loadingPluginStore } from '../stores';
|
||||||
import axiosInstance from '../utility/axiosInstance';
|
import axiosInstance from '../utility/axiosInstance';
|
||||||
import { useInstalledPlugins } from '../utility/metadataLoaders';
|
import { useInstalledPlugins } from '../utility/metadataLoaders';
|
||||||
import { buildFileFormats } from './fileformats';
|
import { buildFileFormats, buildQuickExports } from './fileformats';
|
||||||
import { buildThemes } from './themes';
|
import { buildThemes } from './themes';
|
||||||
import dbgateTools from 'dbgate-tools';
|
import dbgateTools from 'dbgate-tools';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FileFormatDefinition } from 'dbgate-types';
|
import { FileFormatDefinition, QuickExportDefinition } from 'dbgate-types';
|
||||||
|
|
||||||
const jsonlFormat = {
|
const jsonlFormat = {
|
||||||
storageType: 'jsonl',
|
storageType: 'jsonl',
|
||||||
@@ -8,8 +8,37 @@ const jsonlFormat = {
|
|||||||
writerFunc: 'jsonLinesWriter',
|
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[] {
|
export function buildFileFormats(plugins): FileFormatDefinition[] {
|
||||||
const res = [jsonlFormat];
|
const res = [jsonlFormat, jsonFormat];
|
||||||
for (const { content } of plugins) {
|
for (const { content } of plugins) {
|
||||||
const { fileFormats } = content;
|
const { fileFormats } = content;
|
||||||
if (fileFormats) res.push(...fileFormats);
|
if (fileFormats) res.push(...fileFormats);
|
||||||
@@ -17,6 +46,14 @@ export function buildFileFormats(plugins): FileFormatDefinition[] {
|
|||||||
return res;
|
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) {
|
export function findFileFormat(extensions, storageType) {
|
||||||
return extensions.fileFormats.find(x => x.storageType == storageType);
|
return extensions.fileFormats.find(x => x.storageType == storageType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export const visibleToolbar = writableWithStorage(true, 'visibleToolbar');
|
|||||||
export const leftPanelWidth = writable(300);
|
export const leftPanelWidth = writable(300);
|
||||||
export const currentDropDownMenu = writable(null);
|
export const currentDropDownMenu = writable(null);
|
||||||
export const openedModals = writable([]);
|
export const openedModals = writable([]);
|
||||||
|
export const openedSnackbars = writable([]);
|
||||||
export const nullStore = readable(null, () => {});
|
export const nullStore = readable(null, () => {});
|
||||||
export const currentArchive = writable('default');
|
export const currentArchive = writable('default');
|
||||||
export const isFileDragActive = writable(false);
|
export const isFileDragActive = writable(false);
|
||||||
|
|||||||
@@ -14,24 +14,37 @@
|
|||||||
findReplace: true,
|
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 configRegex = /\s*\/\/\s*@ImportExportConfigurator\s*\n\s*\/\/\s*(\{[^\n]+\})\n/;
|
||||||
const requireRegex = /\s*(\/\/\s*@require\s+[^\n]+)\n/g;
|
const requireRegex = /\s*(\/\/\s*@require\s+[^\n]+)\n/g;
|
||||||
const initRegex = /([^\n]+\/\/\s*@init)/g;
|
const initRegex = /([^\n]+\/\/\s*@init)/g;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
|
|
||||||
import invalidateCommands from '../commands/invalidateCommands';
|
import invalidateCommands from '../commands/invalidateCommands';
|
||||||
|
import registerCommand from '../commands/registerCommand';
|
||||||
import { registerFileCommands } from '../commands/stdCommands';
|
import { registerFileCommands } from '../commands/stdCommands';
|
||||||
|
|
||||||
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
||||||
|
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||||
|
import { showModal } from '../modals/modalTools';
|
||||||
import AceEditor from '../query/AceEditor.svelte';
|
import AceEditor from '../query/AceEditor.svelte';
|
||||||
import RunnerOutputPane from '../query/RunnerOutputPane.svelte';
|
import RunnerOutputPane from '../query/RunnerOutputPane.svelte';
|
||||||
import useEditorData from '../query/useEditorData';
|
import useEditorData from '../query/useEditorData';
|
||||||
import axiosInstance from '../utility/axiosInstance';
|
import axiosInstance from '../utility/axiosInstance';
|
||||||
import { changeTab } from '../utility/common';
|
import { changeTab } from '../utility/common';
|
||||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||||
|
import { showSnackbarError } from '../utility/snackbar';
|
||||||
import socket from '../utility/socket';
|
import socket from '../utility/socket';
|
||||||
import useEffect from '../utility/useEffect';
|
import useEffect from '../utility/useEffect';
|
||||||
import useTimerLabel from '../utility/useTimerLabel';
|
import useTimerLabel from '../utility/useTimerLabel';
|
||||||
@@ -118,6 +131,19 @@
|
|||||||
return busy;
|
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() {
|
export async function execute() {
|
||||||
if (busy) return;
|
if (busy) return;
|
||||||
executeNumber += 1;
|
executeNumber += 1;
|
||||||
@@ -138,6 +164,10 @@
|
|||||||
timerLabel.start();
|
timerLabel.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function canKill() {
|
||||||
|
return busy;
|
||||||
|
}
|
||||||
|
|
||||||
export function kill() {
|
export function kill() {
|
||||||
axiosInstance.post('runners/cancel', {
|
axiosInstance.post('runners/cancel', {
|
||||||
runid: runnerId,
|
runid: runnerId,
|
||||||
@@ -151,6 +181,7 @@
|
|||||||
return [
|
return [
|
||||||
{ command: 'shell.execute' },
|
{ command: 'shell.execute' },
|
||||||
{ command: 'shell.kill' },
|
{ command: 'shell.kill' },
|
||||||
|
{ command: 'shell.openWizard' },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ command: 'shell.toggleComment' },
|
{ command: 'shell.toggleComment' },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
@@ -161,6 +192,7 @@
|
|||||||
{ command: 'shell.replace' },
|
{ command: 'shell.replace' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<VerticalSplitter>
|
<VerticalSplitter>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName'];
|
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName'];
|
||||||
export const allowAddToFavorites = props => true;
|
export const allowAddToFavorites = props => true;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||||
import registerCommand from '../commands/registerCommand';
|
import registerCommand from '../commands/registerCommand';
|
||||||
import { registerMenu } from '../utility/contextMenu';
|
import { registerMenu } from '../utility/contextMenu';
|
||||||
|
import { showSnackbarSuccess } from '../utility/snackbar';
|
||||||
|
|
||||||
export let tabid;
|
export let tabid;
|
||||||
export let conid;
|
export let conid;
|
||||||
@@ -78,6 +80,7 @@
|
|||||||
} else {
|
} else {
|
||||||
dispatchChangeSet({ type: 'reset', value: createChangeSet() });
|
dispatchChangeSet({ type: 'reset', value: createChangeSet() });
|
||||||
cache.update(reloadDataCacheFunc);
|
cache.update(reloadDataCacheFunc);
|
||||||
|
showSnackbarSuccess('Saved to database');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +105,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerMenu({ command: 'tableData.save', tag: 'save' });
|
registerMenu({ command: 'tableData.save', tag: 'save' });
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TableDataGrid
|
<TableDataGrid
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function doExtractMenuItems(menu, res) {
|
|||||||
for (const item of menu) {
|
for (const item of menu) {
|
||||||
doExtractMenuItems(item, res);
|
doExtractMenuItems(item, res);
|
||||||
}
|
}
|
||||||
} else if (_.isPlainObject(menu)) {
|
} else if (_.isPlainObject(menu) && !menu._skip) {
|
||||||
res.push(menu);
|
res.push(menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
packages/web/src/utility/createQuickExportMenu.ts
Normal file
19
packages/web/src/utility/createQuickExportMenu.ts
Normal 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),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
56
packages/web/src/utility/exportElectronFile.ts
Normal file
56
packages/web/src/utility/exportElectronFile.ts
Normal 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);
|
||||||
|
}
|
||||||
75
packages/web/src/utility/snackbar.ts
Normal file
75
packages/web/src/utility/snackbar.ts
Normal 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') }],
|
||||||
|
// });
|
||||||
80
packages/web/src/widgets/Snackbar.svelte
Normal file
80
packages/web/src/widgets/Snackbar.svelte
Normal 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>
|
||||||
@@ -43,4 +43,29 @@ const fileFormat = {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
fileFormats: [fileFormat],
|
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: ';',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -64,5 +64,18 @@ const fileFormat = {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
fileFormats: [fileFormat],
|
fileFormats: [fileFormat],
|
||||||
|
quickExports: [
|
||||||
|
{
|
||||||
|
label: 'MS Excel',
|
||||||
|
extension: 'xlsx',
|
||||||
|
createWriter: (fileName, dataName) => ({
|
||||||
|
functionName: 'writer@dbgate-plugin-excel',
|
||||||
|
props: {
|
||||||
|
fileName,
|
||||||
|
sheetName: dataName,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
initialize,
|
initialize,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user