mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-30 08:03: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 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,
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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[];
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
fileFormats: [fileFormat],
|
||||
quickExports: [
|
||||
{
|
||||
label: 'MS Excel',
|
||||
extension: 'xlsx',
|
||||
createWriter: (fileName, dataName) => ({
|
||||
functionName: 'writer@dbgate-plugin-excel',
|
||||
props: {
|
||||
fileName,
|
||||
sheetName: dataName,
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
initialize,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user