mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-25 07:26:00 +00:00
Merge branch 'develop'
This commit is contained in:
16
app/README.md
Normal file
16
app/README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[](https://github.com/prettier/prettier)
|
||||||
|
[](https://paypal.me/JanProchazkaCz/30eur)
|
||||||
|
[](https://www.npmjs.com/package/dbgate)
|
||||||
|
|
||||||
|
# DbGate - database administration tool
|
||||||
|
DbGate is fast and easy to use database administration tool for MySQL, PostgreSQL, SQL Server.
|
||||||
|
|
||||||
|
## Install using npm
|
||||||
|
Please download binary packages from https://dbgate.org . Or run from source code, as described on [github](https://github.com/dbgate/dbgate)
|
||||||
|
|
||||||
|
## Other dbgate packages
|
||||||
|
You can use some functionality of dbgate from your JavaScript code. See [dbgate-api](https://npmjs.com/dbgate-api) package.
|
||||||
|
|
||||||
|
## Screenshot
|
||||||
|
|
||||||
|

|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dbgate",
|
"name": "dbgate",
|
||||||
"version": "3.9.3",
|
"version": "3.9.4-beta.5",
|
||||||
"private": true,
|
|
||||||
"author": "Jan Prochazka <jenasoft.database@gmail.com>",
|
"author": "Jan Prochazka <jenasoft.database@gmail.com>",
|
||||||
"description": "Opensource database administration tool",
|
"description": "Opensource database administration tool",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -11,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/dbshell/dbgate.git"
|
"url": "https://github.com/dbgate/dbgate.git"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "org.dbgate",
|
"appId": "org.dbgate",
|
||||||
@@ -68,7 +67,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"copyfiles": "^2.2.0",
|
"copyfiles": "^2.2.0",
|
||||||
"cross-env": "^6.0.3",
|
"cross-env": "^6.0.3",
|
||||||
"electron": "11.1.1",
|
"electron": "11.2.1",
|
||||||
"electron-builder": "22.9.1"
|
"electron-builder": "22.9.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const { Menu } = require('electron');
|
const { Menu, ipcMain } = require('electron');
|
||||||
const { fork } = require('child_process');
|
const { fork } = require('child_process');
|
||||||
const { autoUpdater } = require('electron-updater');
|
const { autoUpdater } = require('electron-updater');
|
||||||
const Store = require('electron-store');
|
const Store = require('electron-store');
|
||||||
@@ -20,6 +20,7 @@ const store = new Store();
|
|||||||
// be closed automatically when the JavaScript object is garbage collected.
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
let splashWindow;
|
let splashWindow;
|
||||||
|
let mainMenu;
|
||||||
|
|
||||||
log.transports.file.level = 'debug';
|
log.transports.file.level = 'debug';
|
||||||
autoUpdater.logger = log;
|
autoUpdater.logger = log;
|
||||||
@@ -45,18 +46,63 @@ function buildMenu() {
|
|||||||
mainWindow.webContents.executeJavaScript(`dbgate_createNewConnection()`);
|
mainWindow.webContents.executeJavaScript(`dbgate_createNewConnection()`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Open file',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.executeJavaScript(`dbgate_openFile()`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Save',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.executeJavaScript(`dbgate_tabCommand('save')`);
|
||||||
|
},
|
||||||
|
accelerator: 'Ctrl+S',
|
||||||
|
id: 'save',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Save As',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.executeJavaScript(`dbgate_tabCommand('saveAs')`);
|
||||||
|
},
|
||||||
|
accelerator: 'Ctrl+Shift+S',
|
||||||
|
id: 'saveAs',
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'close' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Window',
|
||||||
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'New query',
|
label: 'New query',
|
||||||
click() {
|
click() {
|
||||||
mainWindow.webContents.executeJavaScript(`dbgate_newQuery()`);
|
mainWindow.webContents.executeJavaScript(`dbgate_newQuery()`);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'Close all tabs',
|
||||||
|
click() {
|
||||||
|
mainWindow.webContents.executeJavaScript('dbgate_closeAll()');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ role: 'minimize' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Edit',
|
// {
|
||||||
submenu: [{ role: 'copy' }, { role: 'paste' }],
|
// label: 'Edit',
|
||||||
},
|
// submenu: [
|
||||||
|
// { role: 'undo' },
|
||||||
|
// { role: 'redo' },
|
||||||
|
// { type: 'separator' },
|
||||||
|
// { role: 'cut' },
|
||||||
|
// { role: 'copy' },
|
||||||
|
// { role: 'paste' },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
label: 'View',
|
label: 'View',
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -71,20 +117,6 @@ function buildMenu() {
|
|||||||
{ role: 'togglefullscreen' },
|
{ role: 'togglefullscreen' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
role: 'window',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Close all tabs',
|
|
||||||
click() {
|
|
||||||
mainWindow.webContents.executeJavaScript('dbgate_closeAll()');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ role: 'minimize' },
|
|
||||||
{ role: 'close' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
role: 'help',
|
role: 'help',
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -97,7 +129,7 @@ function buildMenu() {
|
|||||||
{
|
{
|
||||||
label: 'DbGate on GitHub',
|
label: 'DbGate on GitHub',
|
||||||
click() {
|
click() {
|
||||||
require('electron').shell.openExternal('https://github.com/dbshell/dbgate');
|
require('electron').shell.openExternal('https://github.com/dbgate/dbgate');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -106,6 +138,12 @@ function buildMenu() {
|
|||||||
require('electron').shell.openExternal('https://hub.docker.com/r/dbgate/dbgate');
|
require('electron').shell.openExternal('https://hub.docker.com/r/dbgate/dbgate');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Report problem or feature request',
|
||||||
|
click() {
|
||||||
|
require('electron').shell.openExternal('https://github.com/dbgate/dbgate/issues/new');
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'About',
|
label: 'About',
|
||||||
click() {
|
click() {
|
||||||
@@ -119,6 +157,12 @@ function buildMenu() {
|
|||||||
return Menu.buildFromTemplate(template);
|
return Menu.buildFromTemplate(template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcMain.on('update-menu', async (event, arg) => {
|
||||||
|
const commands = await mainWindow.webContents.executeJavaScript(`dbgate_getCurrentTabCommands()`);
|
||||||
|
mainMenu.getMenuItemById('save').enabled = !!commands.save;
|
||||||
|
mainMenu.getMenuItemById('saveAs').enabled = !!commands.saveAs;
|
||||||
|
});
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
const bounds = store.get('winBounds');
|
const bounds = store.get('winBounds');
|
||||||
|
|
||||||
@@ -135,7 +179,8 @@ function createWindow() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.setMenu(buildMenu());
|
mainMenu = buildMenu();
|
||||||
|
mainWindow.setMenu(mainMenu);
|
||||||
|
|
||||||
function loadMainWindow() {
|
function loadMainWindow() {
|
||||||
const startUrl =
|
const startUrl =
|
||||||
|
|||||||
@@ -717,10 +717,10 @@ electron-updater@^4.3.5:
|
|||||||
lodash.isequal "^4.5.0"
|
lodash.isequal "^4.5.0"
|
||||||
semver "^7.3.2"
|
semver "^7.3.2"
|
||||||
|
|
||||||
electron@11.1.1:
|
electron@11.2.1:
|
||||||
version "11.1.1"
|
version "11.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/electron/-/electron-11.1.1.tgz#188f036f8282798398dca9513e9bb3b10213e3aa"
|
resolved "https://registry.yarnpkg.com/electron/-/electron-11.2.1.tgz#8641dd1a62911a1144e0c73c34fd9f37ccc65c2b"
|
||||||
integrity sha512-tlbex3xosJgfileN6BAQRotevPRXB/wQIq48QeQ08tUJJrXwE72c8smsM/hbHx5eDgnbfJ2G3a60PmRjHU2NhA==
|
integrity sha512-Im1y29Bnil+Nzs+FCTq01J1OtLbs+2ZGLLllaqX/9n5GgpdtDmZhS/++JHBsYZ+4+0n7asO+JKQgJD+CqPClzg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@electron/get" "^1.0.1"
|
"@electron/get" "^1.0.1"
|
||||||
"@types/node" "^12.0.12"
|
"@types/node" "^12.0.12"
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveAs_meta: 'post',
|
||||||
|
async saveAs({ filePath, data, format }) {
|
||||||
|
await fs.writeFile(filePath, serialize(format, data));
|
||||||
|
},
|
||||||
|
|
||||||
favorites_meta: 'get',
|
favorites_meta: 'get',
|
||||||
async favorites() {
|
async favorites() {
|
||||||
if (!hasPermission(`files/favorites/read`)) return [];
|
if (!hasPermission(`files/favorites/read`)) return [];
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ const hasPermission = require('../utility/hasPermission');
|
|||||||
|
|
||||||
const preinstallPluginMinimalVersions = {
|
const preinstallPluginMinimalVersions = {
|
||||||
'dbgate-plugin-mssql': '1.0.10',
|
'dbgate-plugin-mssql': '1.0.10',
|
||||||
'dbgate-plugin-mysql': '1.0.3',
|
'dbgate-plugin-mysql': '1.0.4',
|
||||||
'dbgate-plugin-postgres': '1.0.2',
|
'dbgate-plugin-postgres': '1.0.3',
|
||||||
'dbgate-plugin-csv': '1.0.8',
|
'dbgate-plugin-csv': '1.0.8',
|
||||||
'dbgate-plugin-excel': '1.0.6',
|
'dbgate-plugin-excel': '1.0.6',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const lock = new AsyncLock();
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
opened: [],
|
opened: [],
|
||||||
closed: {},
|
closed: {},
|
||||||
|
lastPinged: {},
|
||||||
|
|
||||||
handle_databases(conid, { databases }) {
|
handle_databases(conid, { databases }) {
|
||||||
const existing = this.opened.find(x => x.conid == conid);
|
const existing = this.opened.find(x => x.conid == conid);
|
||||||
@@ -88,7 +89,12 @@ module.exports = {
|
|||||||
ping_meta: 'post',
|
ping_meta: 'post',
|
||||||
async ping({ connections }) {
|
async ping({ connections }) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
connections.map(async conid => {
|
_.uniq(connections).map(async conid => {
|
||||||
|
const last = this.lastPinged[conid];
|
||||||
|
if (last && new Date().getTime() - last < 30 * 1000) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
this.lastPinged[conid] = new Date().getTime();
|
||||||
const opened = await this.ensureOpened(conid);
|
const opened = await this.ensureOpened(conid);
|
||||||
opened.subprocess.send({ msgtype: 'ping' });
|
opened.subprocess.send({ msgtype: 'ping' });
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.7",
|
"version": "1.0.8",
|
||||||
"name": "dbgate-tools",
|
"name": "dbgate-tools",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"typings": "lib/index.d.ts",
|
"typings": "lib/index.d.ts",
|
||||||
|
|||||||
@@ -49,3 +49,15 @@ export function findObjectLike(
|
|||||||
export function findForeignKeyForColumn(table: TableInfo, column: ColumnInfo) {
|
export function findForeignKeyForColumn(table: TableInfo, column: ColumnInfo) {
|
||||||
return (table.foreignKeys || []).find(fk => fk.columns.find(col => col.columnName == column.columnName));
|
return (table.foreignKeys || []).find(fk => fk.columns.find(col => col.columnName == column.columnName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function makeUniqueColumnNames(res: ColumnInfo[]) {
|
||||||
|
const usedNames = new Set();
|
||||||
|
for (let i = 0; i < res.length; i++) {
|
||||||
|
if (usedNames.has(res[i].columnName)) {
|
||||||
|
let suffix = 2;
|
||||||
|
while (usedNames.has(`${res[i].columnName}${suffix}`)) suffix++;
|
||||||
|
res[i].columnName = `${res[i].columnName}${suffix}`;
|
||||||
|
}
|
||||||
|
usedNames.add(res[i].columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { FontIcon } from './icons';
|
import { FontIcon } from './icons';
|
||||||
import useTheme from './theme/useTheme';
|
import useTheme from './theme/useTheme';
|
||||||
|
import getElectron from './utility/getElectron';
|
||||||
import useExtensions from './utility/useExtensions';
|
import useExtensions from './utility/useExtensions';
|
||||||
|
|
||||||
const TargetStyled = styled.div`
|
const TargetStyled = styled.div`
|
||||||
@@ -41,6 +42,9 @@ const TitleWrapper = styled.div`
|
|||||||
export default function DragAndDropFileTarget({ isDragActive, inputProps }) {
|
export default function DragAndDropFileTarget({ isDragActive, inputProps }) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { fileFormats } = useExtensions();
|
const { fileFormats } = useExtensions();
|
||||||
|
const electron = getElectron();
|
||||||
|
const fileTypeNames = fileFormats.filter(x => x.readerFunc).map(x => x.name);
|
||||||
|
if (electron) fileTypeNames.push('SQL');
|
||||||
return (
|
return (
|
||||||
!!isDragActive && (
|
!!isDragActive && (
|
||||||
<TargetStyled theme={theme}>
|
<TargetStyled theme={theme}>
|
||||||
@@ -49,13 +53,7 @@ export default function DragAndDropFileTarget({ isDragActive, inputProps }) {
|
|||||||
<FontIcon icon="icon cloud-upload" />
|
<FontIcon icon="icon cloud-upload" />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
<TitleWrapper>Drop the files to upload to DbGate</TitleWrapper>
|
<TitleWrapper>Drop the files to upload to DbGate</TitleWrapper>
|
||||||
<InfoWrapper>
|
<InfoWrapper>Supported file types: {fileTypeNames.join(', ')}</InfoWrapper>
|
||||||
Supported file types:{' '}
|
|
||||||
{fileFormats
|
|
||||||
.filter(x => x.readerFunc)
|
|
||||||
.map(x => x.name)
|
|
||||||
.join(', ')}
|
|
||||||
</InfoWrapper>
|
|
||||||
</InfoBox>
|
</InfoBox>
|
||||||
<input {...inputProps} />
|
<input {...inputProps} />
|
||||||
</TargetStyled>
|
</TargetStyled>
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export default function Screen() {
|
|||||||
? dimensions.widgetMenu.iconSize + leftPanelWidth + dimensions.splitter.thickness
|
? dimensions.widgetMenu.iconSize + leftPanelWidth + dimensions.splitter.thickness
|
||||||
: dimensions.widgetMenu.iconSize;
|
: dimensions.widgetMenu.iconSize;
|
||||||
const toolbarPortalRef = React.useRef();
|
const toolbarPortalRef = React.useRef();
|
||||||
|
const statusbarPortalRef = React.useRef();
|
||||||
const onSplitDown = useSplitterDrag('clientX', diff => setLeftPanelWidth(v => v + diff));
|
const onSplitDown = useSplitterDrag('clientX', diff => setLeftPanelWidth(v => v + diff));
|
||||||
|
|
||||||
const { getRootProps, getInputProps, isDragActive } = useUploadsZone();
|
const { getRootProps, getInputProps, isDragActive } = useUploadsZone();
|
||||||
@@ -131,10 +132,10 @@ export default function Screen() {
|
|||||||
<TabsPanel></TabsPanel>
|
<TabsPanel></TabsPanel>
|
||||||
</TabsPanelContainer>
|
</TabsPanelContainer>
|
||||||
<BodyDiv contentLeft={contentLeft} theme={theme}>
|
<BodyDiv contentLeft={contentLeft} theme={theme}>
|
||||||
<TabContent toolbarPortalRef={toolbarPortalRef} />
|
<TabContent toolbarPortalRef={toolbarPortalRef} statusbarPortalRef={statusbarPortalRef} />
|
||||||
</BodyDiv>
|
</BodyDiv>
|
||||||
<StausBarContainer theme={theme}>
|
<StausBarContainer theme={theme}>
|
||||||
<StatusBar />
|
<StatusBar statusbarPortalRef={statusbarPortalRef} />
|
||||||
</StausBarContainer>
|
</StausBarContainer>
|
||||||
<ModalLayer />
|
<ModalLayer />
|
||||||
<MenuLayer />
|
<MenuLayer />
|
||||||
|
|||||||
@@ -18,12 +18,18 @@ const TabContainerStyled = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
function TabContainer({ TabComponent, ...props }) {
|
function TabContainer({ TabComponent, ...props }) {
|
||||||
const { tabVisible, tabid, toolbarPortalRef } = props;
|
const { tabVisible, tabid, toolbarPortalRef, statusbarPortalRef } = props;
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<TabContainerStyled tabVisible={tabVisible}>
|
<TabContainerStyled tabVisible={tabVisible}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<TabComponent {...props} tabid={tabid} tabVisible={tabVisible} toolbarPortalRef={toolbarPortalRef} />
|
<TabComponent
|
||||||
|
{...props}
|
||||||
|
tabid={tabid}
|
||||||
|
tabVisible={tabVisible}
|
||||||
|
toolbarPortalRef={toolbarPortalRef}
|
||||||
|
statusbarPortalRef={statusbarPortalRef}
|
||||||
|
/>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</TabContainerStyled>
|
</TabContainerStyled>
|
||||||
);
|
);
|
||||||
@@ -42,7 +48,7 @@ function createTabComponent(selectedTab) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TabContent({ toolbarPortalRef }) {
|
export default function TabContent({ toolbarPortalRef, statusbarPortalRef }) {
|
||||||
const files = useOpenedTabs();
|
const files = useOpenedTabs();
|
||||||
|
|
||||||
const [mountedTabs, setMountedTabs] = React.useState({});
|
const [mountedTabs, setMountedTabs] = React.useState({});
|
||||||
@@ -84,6 +90,7 @@ export default function TabContent({ toolbarPortalRef }) {
|
|||||||
tabid={tabid}
|
tabid={tabid}
|
||||||
tabVisible={tabVisible}
|
tabVisible={tabVisible}
|
||||||
toolbarPortalRef={toolbarPortalRef}
|
toolbarPortalRef={toolbarPortalRef}
|
||||||
|
statusbarPortalRef={statusbarPortalRef}
|
||||||
TabComponent={TabComponent}
|
TabComponent={TabComponent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import useTheme from './theme/useTheme';
|
|||||||
import usePropsCompare from './utility/usePropsCompare';
|
import usePropsCompare from './utility/usePropsCompare';
|
||||||
import { useShowMenu } from './modals/showMenu';
|
import { useShowMenu } from './modals/showMenu';
|
||||||
import { setSelectedTabFunc } from './utility/common';
|
import { setSelectedTabFunc } from './utility/common';
|
||||||
|
import getElectron from './utility/getElectron';
|
||||||
|
|
||||||
// const files = [
|
// const files = [
|
||||||
// { name: 'app.js' },
|
// { name: 'app.js' },
|
||||||
@@ -124,6 +125,15 @@ function getDbIcon(key) {
|
|||||||
return 'icon file';
|
return 'icon file';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildTooltip(tab) {
|
||||||
|
let res = tab.tooltip;
|
||||||
|
if (tab.props && tab.props.savedFilePath) {
|
||||||
|
if (res) res += '\n';
|
||||||
|
res += tab.props.savedFilePath;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
export default function TabsPanel() {
|
export default function TabsPanel() {
|
||||||
// const formatDbKey = (conid, database) => `${database}-${conid}`;
|
// const formatDbKey = (conid, database) => `${database}-${conid}`;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@@ -210,6 +220,16 @@ export default function TabsPanel() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const electron = getElectron();
|
||||||
|
if (electron) {
|
||||||
|
const { ipcRenderer } = electron;
|
||||||
|
const activeTab = tabs.find(x => x.selected);
|
||||||
|
window['dbgate_activeTabId'] = activeTab ? activeTab.tabid : null;
|
||||||
|
ipcRenderer.send('update-menu');
|
||||||
|
}
|
||||||
|
}, [tabs]);
|
||||||
|
|
||||||
// console.log(
|
// console.log(
|
||||||
// 't',
|
// 't',
|
||||||
// tabs.map(x => x.tooltip)
|
// tabs.map(x => x.tooltip)
|
||||||
@@ -252,7 +272,7 @@ export default function TabsPanel() {
|
|||||||
{_.sortBy(tabsByDb[dbKey], ['title', 'tabid']).map(tab => (
|
{_.sortBy(tabsByDb[dbKey], ['title', 'tabid']).map(tab => (
|
||||||
<FileTabItem
|
<FileTabItem
|
||||||
{...tab}
|
{...tab}
|
||||||
title={tab.tooltip}
|
title={buildTooltip(tab)}
|
||||||
key={tab.tabid}
|
key={tab.tabid}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
onClick={e => handleTabClick(e, tab.tabid)}
|
onClick={e => handleTabClick(e, tab.tabid)}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export function AppObjectCore({
|
|||||||
extInfo = undefined,
|
extInfo = undefined,
|
||||||
statusTitle = undefined,
|
statusTitle = undefined,
|
||||||
disableHover = false,
|
disableHover = false,
|
||||||
|
children = null,
|
||||||
Menu = undefined,
|
Menu = undefined,
|
||||||
...other
|
...other
|
||||||
}) {
|
}) {
|
||||||
@@ -63,6 +64,7 @@ export function AppObjectCore({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<AppObjectDiv
|
<AppObjectDiv
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -89,5 +91,7 @@ export function AppObjectCore({
|
|||||||
)}
|
)}
|
||||||
{extInfo && <ExtInfoWrap theme={theme}>{extInfo}</ExtInfoWrap>}
|
{extInfo && <ExtInfoWrap theme={theme}>{extInfo}</ExtInfoWrap>}
|
||||||
</AppObjectDiv>
|
</AppObjectDiv>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ import { DropDownMenuItem } from '../modals/DropDownMenu';
|
|||||||
import { useSetOpenedTabs } from '../utility/globalState';
|
import { useSetOpenedTabs } from '../utility/globalState';
|
||||||
import { AppObjectCore } from './AppObjectCore';
|
import { AppObjectCore } from './AppObjectCore';
|
||||||
import { setSelectedTabFunc } from '../utility/common';
|
import { setSelectedTabFunc } from '../utility/common';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FontIcon } from '../icons';
|
||||||
|
import useTheme from '../theme/useTheme';
|
||||||
|
|
||||||
|
const InfoDiv = styled.div`
|
||||||
|
margin-left: 30px;
|
||||||
|
color: ${props => props.theme.left_font3};
|
||||||
|
`;
|
||||||
|
|
||||||
function Menu({ data }) {
|
function Menu({ data }) {
|
||||||
const setOpenedTabs = useSetOpenedTabs();
|
const setOpenedTabs = useSetOpenedTabs();
|
||||||
@@ -25,18 +33,17 @@ function Menu({ data }) {
|
|||||||
function ClosedTabAppObject({ data, commonProps }) {
|
function ClosedTabAppObject({ data, commonProps }) {
|
||||||
const { tabid, props, selected, icon, title, closedTime, busy } = data;
|
const { tabid, props, selected, icon, title, closedTime, busy } = data;
|
||||||
const setOpenedTabs = useSetOpenedTabs();
|
const setOpenedTabs = useSetOpenedTabs();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
setOpenedTabs(files =>
|
setOpenedTabs(files =>
|
||||||
setSelectedTabFunc(
|
setSelectedTabFunc(
|
||||||
files.map(
|
files.map(x => ({
|
||||||
x => ({
|
|
||||||
...x,
|
...x,
|
||||||
closedTime: x.tabid == tabid ? undefined : x.closedTime,
|
closedTime: x.tabid == tabid ? undefined : x.closedTime,
|
||||||
}),
|
})),
|
||||||
tabid
|
tabid
|
||||||
)
|
)
|
||||||
)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,7 +57,14 @@ function ClosedTabAppObject({ data, commonProps }) {
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isBusy={busy}
|
isBusy={busy}
|
||||||
Menu={Menu}
|
Menu={Menu}
|
||||||
/>
|
>
|
||||||
|
{data.props && data.props.database && (
|
||||||
|
<InfoDiv theme={theme}>
|
||||||
|
<FontIcon icon="icon database" /> {data.props.database}
|
||||||
|
</InfoDiv>
|
||||||
|
)}
|
||||||
|
{data.contentPreview && <InfoDiv theme={theme}>{data.contentPreview}</InfoDiv>}
|
||||||
|
</AppObjectCore>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function Menu({ data }) {
|
|||||||
setOpenedConnections(list => list.filter(x => x != data._id));
|
setOpenedConnections(list => list.filter(x => x != data._id));
|
||||||
};
|
};
|
||||||
const handleConnect = () => {
|
const handleConnect = () => {
|
||||||
setOpenedConnections(list => [...list, data._id]);
|
setOpenedConnections(list => _.uniq([...list, data._id]));
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -72,7 +72,7 @@ function ConnectionAppObject({ data, commonProps }) {
|
|||||||
const extensions = useExtensions();
|
const extensions = useExtensions();
|
||||||
|
|
||||||
const isBold = _.get(currentDatabase, 'connection._id') == _id;
|
const isBold = _.get(currentDatabase, 'connection._id') == _id;
|
||||||
const onClick = () => setOpenedConnections(c => [...c, _id]);
|
const onClick = () => setOpenedConnections(c => _.uniq([...c, _id]));
|
||||||
|
|
||||||
let statusIcon = null;
|
let statusIcon = null;
|
||||||
let statusTitle = null;
|
let statusTitle = null;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function Menu({ data }) {
|
|||||||
|
|
||||||
const handleNewQuery = () => {
|
const handleNewQuery = () => {
|
||||||
openNewTab({
|
openNewTab({
|
||||||
title: 'Query',
|
title: 'Query #',
|
||||||
icon: 'img sql-file',
|
icon: 'img sql-file',
|
||||||
tooltip,
|
tooltip,
|
||||||
tabComponent: 'QueryTab',
|
tabComponent: 'QueryTab',
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export async function openDatabaseObjectDetail(
|
|||||||
|
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: pureName,
|
title: sqlTemplate ? 'Query #' : pureName,
|
||||||
tooltip,
|
tooltip,
|
||||||
icon: sqlTemplate ? 'img sql-file' : icons[objectTypeField],
|
icon: sqlTemplate ? 'img sql-file' : icons[objectTypeField],
|
||||||
tabComponent: sqlTemplate ? 'QueryTab' : tabComponent,
|
tabComponent: sqlTemplate ? 'QueryTab' : tabComponent,
|
||||||
@@ -245,7 +245,7 @@ function Menu({ data }) {
|
|||||||
} else if (menu.isQueryDesigner) {
|
} else if (menu.isQueryDesigner) {
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: data.pureName,
|
title: 'Query #',
|
||||||
icon: 'img query-design',
|
icon: 'img query-design',
|
||||||
tabComponent: 'QueryDesignTab',
|
tabComponent: 'QueryDesignTab',
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export function SavedSqlFileAppObject({ data, commonProps }) {
|
|||||||
|
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: 'Shell',
|
title: 'Shell #',
|
||||||
icon: 'img shell',
|
icon: 'img shell',
|
||||||
tabComponent: 'ShellTab',
|
tabComponent: 'ShellTab',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import useHasPermission from '../utility/useHasPermission';
|
|
||||||
import ToolbarButton from '../widgets/ToolbarButton';
|
import ToolbarButton from '../widgets/ToolbarButton';
|
||||||
|
|
||||||
export default function ChartToolbar({ save, modelState, dispatchModel }) {
|
export default function ChartToolbar({ modelState, dispatchModel }) {
|
||||||
const hasPermission = useHasPermission();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasPermission('files/charts/write') && (
|
|
||||||
<ToolbarButton onClick={save} icon="icon save">
|
|
||||||
Save
|
|
||||||
</ToolbarButton>
|
|
||||||
)}
|
|
||||||
<ToolbarButton disabled={!modelState.canUndo} onClick={() => dispatchModel({ type: 'undo' })} icon="icon undo">
|
<ToolbarButton disabled={!modelState.canUndo} onClick={() => dispatchModel({ type: 'undo' })} icon="icon undo">
|
||||||
Undo
|
Undo
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ export default function DataGridCore(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
[formViewAvailable, display]
|
[formViewAvailable, display, openNewTab]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!columns || columns.length == 0) return <LoadingInfo wrapper message="Waiting for structure" />;
|
if (!columns || columns.length == 0) return <LoadingInfo wrapper message="Waiting for structure" />;
|
||||||
@@ -353,7 +353,7 @@ export default function DataGridCore(props) {
|
|||||||
const handleOpenFreeTable = () => {
|
const handleOpenFreeTable = () => {
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: 'selection',
|
title: 'Data #',
|
||||||
icon: 'img free-table',
|
icon: 'img free-table',
|
||||||
tabComponent: 'FreeTableTab',
|
tabComponent: 'FreeTableTab',
|
||||||
props: {},
|
props: {},
|
||||||
@@ -365,7 +365,7 @@ export default function DataGridCore(props) {
|
|||||||
const handleOpenChart = () => {
|
const handleOpenChart = () => {
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: 'Chart',
|
title: 'Chart #',
|
||||||
icon: 'img chart',
|
icon: 'img chart',
|
||||||
tabComponent: 'ChartTab',
|
tabComponent: 'ChartTab',
|
||||||
props: {},
|
props: {},
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export default function SqlDataGridCore(props) {
|
|||||||
function openActiveChart() {
|
function openActiveChart() {
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: 'Chart',
|
title: 'Chart #',
|
||||||
icon: 'img chart',
|
icon: 'img chart',
|
||||||
tabComponent: 'ChartTab',
|
tabComponent: 'ChartTab',
|
||||||
props: {
|
props: {
|
||||||
@@ -104,7 +104,7 @@ export default function SqlDataGridCore(props) {
|
|||||||
function openQuery() {
|
function openQuery() {
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: 'Query',
|
title: 'Query #',
|
||||||
icon: 'img sql-file',
|
icon: 'img sql-file',
|
||||||
tabComponent: 'QueryTab',
|
tabComponent: 'QueryTab',
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import useHasPermission from '../utility/useHasPermission';
|
|
||||||
import ToolbarButton from '../widgets/ToolbarButton';
|
import ToolbarButton from '../widgets/ToolbarButton';
|
||||||
|
|
||||||
export default function QueryDesignToolbar({
|
export default function QueryDesignToolbar({
|
||||||
execute,
|
execute,
|
||||||
isDatabaseDefined,
|
isDatabaseDefined,
|
||||||
busy,
|
busy,
|
||||||
save,
|
|
||||||
modelState,
|
modelState,
|
||||||
dispatchModel,
|
dispatchModel,
|
||||||
isConnected,
|
isConnected,
|
||||||
kill,
|
kill,
|
||||||
}) {
|
}) {
|
||||||
const hasPermission = useHasPermission();
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToolbarButton disabled={!isDatabaseDefined || busy} onClick={execute} icon="icon run">
|
<ToolbarButton disabled={!isDatabaseDefined || busy} onClick={execute} icon="icon run">
|
||||||
@@ -21,11 +18,6 @@ export default function QueryDesignToolbar({
|
|||||||
<ToolbarButton disabled={!isConnected} onClick={kill} icon="icon close">
|
<ToolbarButton disabled={!isConnected} onClick={kill} icon="icon close">
|
||||||
Kill
|
Kill
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
{hasPermission('files/query/write') && (
|
|
||||||
<ToolbarButton onClick={save} icon="icon save">
|
|
||||||
Save
|
|
||||||
</ToolbarButton>
|
|
||||||
)}
|
|
||||||
<ToolbarButton disabled={!modelState.canUndo} onClick={() => dispatchModel({ type: 'undo' })} icon="icon undo">
|
<ToolbarButton disabled={!modelState.canUndo} onClick={() => dispatchModel({ type: 'undo' })} icon="icon undo">
|
||||||
Undo
|
Undo
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const Container = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #ddeeee;
|
background: ${props => props.theme.gridheader_background_cyan[0]};
|
||||||
height: ${dimensions.toolBar.height}px;
|
height: ${dimensions.toolBar.height}px;
|
||||||
min-height: ${dimensions.toolBar.height}px;
|
min-height: ${dimensions.toolBar.height}px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default function useNewFreeTable() {
|
|||||||
|
|
||||||
return ({ title = undefined, ...props } = {}) =>
|
return ({ title = undefined, ...props } = {}) =>
|
||||||
openNewTab({
|
openNewTab({
|
||||||
title: title || 'Table',
|
title: title || 'Data #',
|
||||||
icon: 'img free-table',
|
icon: 'img free-table',
|
||||||
tabComponent: 'FreeTableTab',
|
tabComponent: 'FreeTableTab',
|
||||||
props,
|
props,
|
||||||
|
|||||||
@@ -411,7 +411,11 @@ function SourceName({ name }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ImportExportConfigurator({ uploadedFile = undefined, onChangePreview = undefined }) {
|
export default function ImportExportConfigurator({
|
||||||
|
uploadedFile = undefined,
|
||||||
|
openedFile = undefined,
|
||||||
|
onChangePreview = undefined,
|
||||||
|
}) {
|
||||||
const { values, setFieldValue, setValues } = useForm();
|
const { values, setFieldValue, setValues } = useForm();
|
||||||
const targetDbinfo = useDatabaseInfo({ conid: values.targetConnectionId, database: values.targetDatabaseName });
|
const targetDbinfo = useDatabaseInfo({ conid: values.targetConnectionId, database: values.targetDatabaseName });
|
||||||
const sourceConnectionInfo = useConnectionInfo({ conid: values.sourceConnectionId });
|
const sourceConnectionInfo = useConnectionInfo({ conid: values.sourceConnectionId });
|
||||||
@@ -453,6 +457,21 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC
|
|||||||
if (uploadedFile) {
|
if (uploadedFile) {
|
||||||
handleUpload(uploadedFile);
|
handleUpload(uploadedFile);
|
||||||
}
|
}
|
||||||
|
if (openedFile) {
|
||||||
|
addFilesToSourceList(
|
||||||
|
extensions,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
fileName: openedFile.filePath,
|
||||||
|
shortName: openedFile.shortName,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
values,
|
||||||
|
setValues,
|
||||||
|
!sourceList || sourceList.length == 0 ? openedFile.storageType : null,
|
||||||
|
setPreviewSource
|
||||||
|
);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const supportsPreview =
|
const supportsPreview =
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import _ from 'lodash';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import '@mdi/font/css/materialdesignicons.css';
|
import '@mdi/font/css/materialdesignicons.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
@@ -22,6 +23,17 @@ import localStorageGarbageCollector from './utility/localStorageGarbageCollector
|
|||||||
// import 'ace-builds/src-noconflict/snippets/mysql';
|
// import 'ace-builds/src-noconflict/snippets/mysql';
|
||||||
|
|
||||||
localStorageGarbageCollector();
|
localStorageGarbageCollector();
|
||||||
|
window['dbgate_tabExports'] = {};
|
||||||
|
window['dbgate_getCurrentTabCommands'] = () => {
|
||||||
|
const tabid = window['dbgate_activeTabId'];
|
||||||
|
return _.mapValues(window['dbgate_tabExports'][tabid] || {}, v => !!v);
|
||||||
|
};
|
||||||
|
window['dbgate_tabCommand'] = cmd => {
|
||||||
|
const tabid = window['dbgate_activeTabId'];
|
||||||
|
const commands = window['dbgate_tabExports'][tabid];
|
||||||
|
const func = (commands || {})[cmd];
|
||||||
|
if (func) func();
|
||||||
|
};
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById('root'));
|
ReactDOM.render(<App />, document.getElementById('root'));
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import useHasPermission from '../utility/useHasPermission';
|
|
||||||
import ToolbarButton from '../widgets/ToolbarButton';
|
import ToolbarButton from '../widgets/ToolbarButton';
|
||||||
|
|
||||||
export default function MarkdownToolbar({ save, showPreview }) {
|
export default function MarkdownToolbar({ showPreview }) {
|
||||||
const hasPermission = useHasPermission();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasPermission('files/markdown/write') && (
|
|
||||||
<ToolbarButton onClick={save} icon="icon save">
|
|
||||||
Save
|
|
||||||
</ToolbarButton>
|
|
||||||
)}
|
|
||||||
<ToolbarButton onClick={showPreview} icon="icon preview">
|
<ToolbarButton onClick={showPreview} icon="icon preview">
|
||||||
Preview
|
Preview
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ function GenerateSctriptButton({ modalState }) {
|
|||||||
const code = await createImpExpScript(extensions, values);
|
const code = await createImpExpScript(extensions, values);
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: 'Shell',
|
title: 'Shell #',
|
||||||
icon: 'img shell',
|
icon: 'img shell',
|
||||||
tabComponent: 'ShellTab',
|
tabComponent: 'ShellTab',
|
||||||
},
|
},
|
||||||
@@ -120,6 +120,7 @@ export default function ImportExportModal({
|
|||||||
modalState,
|
modalState,
|
||||||
initialValues,
|
initialValues,
|
||||||
uploadedFile = undefined,
|
uploadedFile = undefined,
|
||||||
|
openedFile = undefined,
|
||||||
importToArchive = false,
|
importToArchive = false,
|
||||||
}) {
|
}) {
|
||||||
const [executeNumber, setExecuteNumber] = React.useState(0);
|
const [executeNumber, setExecuteNumber] = React.useState(0);
|
||||||
@@ -195,7 +196,11 @@ export default function ImportExportModal({
|
|||||||
<ModalHeader modalState={modalState}>Import/Export {busy && <FontIcon icon="icon loading" />}</ModalHeader>
|
<ModalHeader modalState={modalState}>Import/Export {busy && <FontIcon icon="icon loading" />}</ModalHeader>
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<ContentWrapper theme={theme}>
|
<ContentWrapper theme={theme}>
|
||||||
<ImportExportConfigurator uploadedFile={uploadedFile} onChangePreview={setPreviewReader} />
|
<ImportExportConfigurator
|
||||||
|
uploadedFile={uploadedFile}
|
||||||
|
openedFile={openedFile}
|
||||||
|
onChangePreview={setPreviewReader}
|
||||||
|
/>
|
||||||
</ContentWrapper>
|
</ContentWrapper>
|
||||||
<WidgetColumnWrapper theme={theme}>
|
<WidgetColumnWrapper theme={theme}>
|
||||||
<WidgetColumnBar>
|
<WidgetColumnBar>
|
||||||
|
|||||||
@@ -6,14 +6,51 @@ import ModalHeader from './ModalHeader';
|
|||||||
import ModalContent from './ModalContent';
|
import ModalContent from './ModalContent';
|
||||||
import ModalFooter from './ModalFooter';
|
import ModalFooter from './ModalFooter';
|
||||||
import { FormProvider } from '../utility/FormProvider';
|
import { FormProvider } from '../utility/FormProvider';
|
||||||
|
import FormStyledButton from '../widgets/FormStyledButton';
|
||||||
|
import getElectron from '../utility/getElectron';
|
||||||
|
|
||||||
|
export default function SaveFileModal({
|
||||||
|
data,
|
||||||
|
folder,
|
||||||
|
format,
|
||||||
|
modalState,
|
||||||
|
name,
|
||||||
|
fileExtension,
|
||||||
|
filePath,
|
||||||
|
onSave = undefined,
|
||||||
|
}) {
|
||||||
|
const electron = getElectron();
|
||||||
|
|
||||||
export default function SaveFileModal({ data, folder, format, modalState, name, onSave = undefined }) {
|
|
||||||
const handleSubmit = async values => {
|
const handleSubmit = async values => {
|
||||||
const { name } = values;
|
const { name } = values;
|
||||||
await axios.post('files/save', { folder, file: name, data, format });
|
await axios.post('files/save', { folder, file: name, data, format });
|
||||||
modalState.close();
|
modalState.close();
|
||||||
if (onSave) onSave(name);
|
if (onSave) {
|
||||||
|
onSave(name, {
|
||||||
|
savedFile: name,
|
||||||
|
savedFolder: folder,
|
||||||
|
savedFilePath: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSaveToDisk = async filePath => {
|
||||||
|
const path = window.require('path');
|
||||||
|
const parsed = path.parse(filePath);
|
||||||
|
// if (!parsed.ext) filePath += `.${fileExtension}`;
|
||||||
|
|
||||||
|
await axios.post('files/save-as', { filePath, data, format });
|
||||||
|
modalState.close();
|
||||||
|
|
||||||
|
if (onSave) {
|
||||||
|
onSave(parsed.name, {
|
||||||
|
savedFile: null,
|
||||||
|
savedFolder: null,
|
||||||
|
savedFilePath: filePath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalBase modalState={modalState}>
|
<ModalBase modalState={modalState}>
|
||||||
<ModalHeader modalState={modalState}>Save file</ModalHeader>
|
<ModalHeader modalState={modalState}>Save file</ModalHeader>
|
||||||
@@ -23,6 +60,25 @@ export default function SaveFileModal({ data, folder, format, modalState, name,
|
|||||||
</ModalContent>
|
</ModalContent>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<FormSubmit value="Save" onClick={handleSubmit} />
|
<FormSubmit value="Save" onClick={handleSubmit} />
|
||||||
|
{electron && (
|
||||||
|
<FormStyledButton
|
||||||
|
type="button"
|
||||||
|
value="Save to disk"
|
||||||
|
onClick={() => {
|
||||||
|
const file = electron.remote.dialog.showSaveDialogSync(electron.remote.getCurrentWindow(), {
|
||||||
|
filters: [
|
||||||
|
{ name: `${fileExtension.toUpperCase()} files`, extensions: [fileExtension] },
|
||||||
|
{ name: `All files`, extensions: ['*'] },
|
||||||
|
],
|
||||||
|
defaultPath: filePath || `${name}.${fileExtension}`,
|
||||||
|
properties: ['showOverwriteConfirmation'],
|
||||||
|
});
|
||||||
|
if (file) {
|
||||||
|
handleSaveToDisk(file);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
</ModalBase>
|
</ModalBase>
|
||||||
|
|||||||
@@ -1,53 +1,117 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import axios from '../utility/axios';
|
||||||
import { changeTab } from '../utility/common';
|
import { changeTab } from '../utility/common';
|
||||||
|
import getElectron from '../utility/getElectron';
|
||||||
import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState';
|
import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState';
|
||||||
import keycodes from '../utility/keycodes';
|
import keycodes from '../utility/keycodes';
|
||||||
|
import SaveFileToolbarButton from '../utility/SaveFileToolbarButton';
|
||||||
|
import ToolbarPortal from '../utility/ToolbarPortal';
|
||||||
|
import useHasPermission from '../utility/useHasPermission';
|
||||||
import SaveFileModal from './SaveFileModal';
|
import SaveFileModal from './SaveFileModal';
|
||||||
|
import useModalState from './useModalState';
|
||||||
|
|
||||||
export default function SaveTabModal({ data, folder, format, modalState, tabid, tabVisible }) {
|
export default function SaveTabModal({
|
||||||
|
data,
|
||||||
|
folder,
|
||||||
|
format,
|
||||||
|
tabid,
|
||||||
|
tabVisible,
|
||||||
|
fileExtension,
|
||||||
|
toolbarPortalRef = undefined,
|
||||||
|
}) {
|
||||||
const setOpenedTabs = useSetOpenedTabs();
|
const setOpenedTabs = useSetOpenedTabs();
|
||||||
const openedTabs = useOpenedTabs();
|
const openedTabs = useOpenedTabs();
|
||||||
|
const saveFileModalState = useModalState();
|
||||||
|
const hasPermission = useHasPermission();
|
||||||
|
const canSave = hasPermission(`files/${folder}/write`);
|
||||||
|
|
||||||
const { savedFile } = openedTabs.find(x => x.tabid == tabid).props || {};
|
const { savedFile, savedFilePath } = openedTabs.find(x => x.tabid == tabid).props || {};
|
||||||
const onSave = name =>
|
const onSave = (title, newProps) => {
|
||||||
changeTab(tabid, setOpenedTabs, tab => ({
|
changeTab(tabid, setOpenedTabs, tab => ({
|
||||||
...tab,
|
...tab,
|
||||||
title: name,
|
title,
|
||||||
props: {
|
props: {
|
||||||
...tab.props,
|
...tab.props,
|
||||||
savedFile: name,
|
|
||||||
savedFolder: folder,
|
|
||||||
savedFormat: format,
|
savedFormat: format,
|
||||||
|
...newProps,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (savedFile) {
|
||||||
|
await axios.post('files/save', { folder, file: savedFile, data, format });
|
||||||
|
}
|
||||||
|
if (savedFilePath) {
|
||||||
|
await axios.post('files/save-as', { filePath: savedFilePath, data, format });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleSaveRef = React.useRef(handleSave);
|
||||||
|
handleSaveRef.current = handleSave;
|
||||||
|
|
||||||
const handleKeyboard = React.useCallback(
|
const handleKeyboard = React.useCallback(
|
||||||
e => {
|
e => {
|
||||||
if (e.keyCode == keycodes.s && e.ctrlKey) {
|
if (e.keyCode == keycodes.s && e.ctrlKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
modalState.open();
|
if (e.shiftKey) {
|
||||||
|
saveFileModalState.open();
|
||||||
|
} else {
|
||||||
|
if (savedFile || savedFilePath) handleSaveRef.current();
|
||||||
|
else saveFileModalState.open();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[modalState]
|
[saveFileModalState]
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (tabVisible) {
|
if (tabVisible && canSave) {
|
||||||
document.addEventListener('keydown', handleKeyboard);
|
document.addEventListener('keydown', handleKeyboard);
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', handleKeyboard);
|
document.removeEventListener('keydown', handleKeyboard);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [tabVisible, handleKeyboard]);
|
}, [tabVisible, handleKeyboard, canSave]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const electron = getElectron();
|
||||||
|
if (electron) {
|
||||||
|
const { ipcRenderer } = electron;
|
||||||
|
window['dbgate_tabExports'][tabid] = {
|
||||||
|
save: handleSaveRef.current,
|
||||||
|
saveAs: saveFileModalState.open,
|
||||||
|
};
|
||||||
|
ipcRenderer.send('update-menu');
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
delete window['dbgate_tabExports'][tabid];
|
||||||
|
ipcRenderer.send('update-menu');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<SaveFileModal
|
<SaveFileModal
|
||||||
data={data}
|
data={data}
|
||||||
folder={folder}
|
folder={folder}
|
||||||
format={format}
|
format={format}
|
||||||
modalState={modalState}
|
modalState={saveFileModalState}
|
||||||
name={savedFile || 'newFile'}
|
name={savedFile || 'newFile'}
|
||||||
|
filePath={savedFilePath}
|
||||||
|
fileExtension={fileExtension}
|
||||||
onSave={onSave}
|
onSave={onSave}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{canSave && (
|
||||||
|
<ToolbarPortal tabVisible={tabVisible} toolbarPortalRef={toolbarPortalRef}>
|
||||||
|
<SaveFileToolbarButton
|
||||||
|
saveAs={saveFileModalState.open}
|
||||||
|
save={savedFile || savedFilePath ? handleSave : null}
|
||||||
|
tabid={tabid}
|
||||||
|
/>
|
||||||
|
</ToolbarPortal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import useHasPermission from '../utility/useHasPermission';
|
import useHasPermission from '../utility/useHasPermission';
|
||||||
import ToolbarButton from '../widgets/ToolbarButton';
|
import ToolbarButton from '../widgets/ToolbarButton';
|
||||||
|
|
||||||
export default function QueryToolbar({ execute, isDatabaseDefined, busy, save, format, isConnected, kill }) {
|
export default function QueryToolbar({ execute, isDatabaseDefined, busy, format, isConnected, kill }) {
|
||||||
const hasPermission = useHasPermission();
|
const hasPermission = useHasPermission();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -15,11 +15,11 @@ export default function QueryToolbar({ execute, isDatabaseDefined, busy, save, f
|
|||||||
<ToolbarButton disabled={!isConnected} onClick={kill} icon="icon close">
|
<ToolbarButton disabled={!isConnected} onClick={kill} icon="icon close">
|
||||||
Kill
|
Kill
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
{hasPermission('files/sql/write') && (
|
{/* {hasPermission('files/sql/write') && (
|
||||||
<ToolbarButton onClick={save} icon="icon save">
|
<ToolbarButton onClick={save} icon="icon save">
|
||||||
Save
|
Save
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
)}
|
)} */}
|
||||||
<ToolbarButton onClick={format} icon="icon format-code">
|
<ToolbarButton onClick={format} icon="icon format-code">
|
||||||
Format
|
Format
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import useHasPermission from '../utility/useHasPermission';
|
|
||||||
import ToolbarButton from '../widgets/ToolbarButton';
|
import ToolbarButton from '../widgets/ToolbarButton';
|
||||||
|
|
||||||
export default function ShellToolbar({ execute, cancel, busy, edit, save, editAvailable }) {
|
export default function ShellToolbar({ execute, cancel, busy, edit, editAvailable }) {
|
||||||
const hasPermission = useHasPermission();
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ToolbarButton disabled={busy} onClick={execute} icon="icon run">
|
<ToolbarButton disabled={busy} onClick={execute} icon="icon run">
|
||||||
@@ -15,11 +13,6 @@ export default function ShellToolbar({ execute, cancel, busy, edit, save, editAv
|
|||||||
<ToolbarButton disabled={!editAvailable} onClick={edit} icon="icon show-wizard">
|
<ToolbarButton disabled={!editAvailable} onClick={edit} icon="icon show-wizard">
|
||||||
Show wizard
|
Show wizard
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
{hasPermission('files/shell/write') && (
|
|
||||||
<ToolbarButton onClick={save} icon="icon save">
|
|
||||||
Save
|
|
||||||
</ToolbarButton>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function useNewQuery() {
|
|||||||
return ({ title = undefined, initialData = undefined, ...props } = {}) =>
|
return ({ title = undefined, initialData = undefined, ...props } = {}) =>
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: title || 'Query',
|
title: title || 'Query #',
|
||||||
icon: 'img sql-file',
|
icon: 'img sql-file',
|
||||||
tooltip,
|
tooltip,
|
||||||
tabComponent: 'QueryTab',
|
tabComponent: 'QueryTab',
|
||||||
@@ -40,7 +40,7 @@ export function useNewQueryDesign() {
|
|||||||
return ({ title = undefined, initialData = undefined, ...props } = {}) =>
|
return ({ title = undefined, initialData = undefined, ...props } = {}) =>
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
title: title || 'Query',
|
title: title || 'Query #',
|
||||||
icon: 'img query-design',
|
icon: 'img query-design',
|
||||||
tooltip,
|
tooltip,
|
||||||
tabComponent: 'QueryDesignTab',
|
tabComponent: 'QueryDesignTab',
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import { createFreeTableModel } from 'dbgate-datalib';
|
|||||||
import useUndoReducer from '../utility/useUndoReducer';
|
import useUndoReducer from '../utility/useUndoReducer';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { useUpdateDatabaseForTab } from '../utility/globalState';
|
import { useUpdateDatabaseForTab } from '../utility/globalState';
|
||||||
import useModalState from '../modals/useModalState';
|
|
||||||
import LoadingInfo from '../widgets/LoadingInfo';
|
import LoadingInfo from '../widgets/LoadingInfo';
|
||||||
import ErrorInfo from '../widgets/ErrorInfo';
|
import ErrorInfo from '../widgets/ErrorInfo';
|
||||||
import useEditorData from '../utility/useEditorData';
|
import useEditorData from '../utility/useEditorData';
|
||||||
import SaveTabModal from '../modals/SaveTabModal';
|
import SaveTabModal from '../modals/SaveTabModal';
|
||||||
import ChartEditor from '../charts/ChartEditor';
|
import ChartEditor from '../charts/ChartEditor';
|
||||||
import ChartToolbar from '../charts/ChartToolbar';
|
import ChartToolbar from '../charts/ChartToolbar';
|
||||||
|
import ToolbarPortal from '../utility/ToolbarPortal';
|
||||||
|
|
||||||
export default function ChartTab({ tabVisible, toolbarPortalRef, conid, database, tabid }) {
|
export default function ChartTab({ tabVisible, toolbarPortalRef, conid, database, tabid }) {
|
||||||
const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel());
|
const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel());
|
||||||
const saveFileModalState = useModalState();
|
|
||||||
const { initialData, setEditorData, errorMessage, isLoading } = useEditorData({
|
const { initialData, setEditorData, errorMessage, isLoading } = useEditorData({
|
||||||
tabid,
|
tabid,
|
||||||
});
|
});
|
||||||
@@ -57,20 +56,17 @@ export default function ChartTab({ tabVisible, toolbarPortalRef, conid, database
|
|||||||
database={database}
|
database={database}
|
||||||
/>
|
/>
|
||||||
<SaveTabModal
|
<SaveTabModal
|
||||||
modalState={saveFileModalState}
|
|
||||||
tabVisible={tabVisible}
|
tabVisible={tabVisible}
|
||||||
|
toolbarPortalRef={toolbarPortalRef}
|
||||||
data={modelState.value}
|
data={modelState.value}
|
||||||
format="json"
|
format="json"
|
||||||
folder="charts"
|
folder="charts"
|
||||||
tabid={tabid}
|
tabid={tabid}
|
||||||
|
fileExtension="chart"
|
||||||
/>
|
/>
|
||||||
{toolbarPortalRef &&
|
<ToolbarPortal toolbarPortalRef={toolbarPortalRef} tabVisible={tabVisible}>
|
||||||
toolbarPortalRef.current &&
|
<ChartToolbar modelState={modelState} dispatchModel={dispatchModel} />
|
||||||
tabVisible &&
|
</ToolbarPortal>
|
||||||
ReactDOM.createPortal(
|
|
||||||
<ChartToolbar save={saveFileModalState.open} modelState={modelState} dispatchModel={dispatchModel} />,
|
|
||||||
toolbarPortalRef.current
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import useEditorData from '../utility/useEditorData';
|
|||||||
export default function FreeDataTab({ archiveFolder, archiveFile, tabVisible, toolbarPortalRef, tabid, initialArgs }) {
|
export default function FreeDataTab({ archiveFolder, archiveFile, tabVisible, toolbarPortalRef, tabid, initialArgs }) {
|
||||||
const [config, setConfig] = useGridConfig(tabid);
|
const [config, setConfig] = useGridConfig(tabid);
|
||||||
const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel());
|
const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel());
|
||||||
const saveFileModalState = useModalState();
|
const saveArchiveModalState = useModalState();
|
||||||
const setOpenedTabs = useSetOpenedTabs();
|
const setOpenedTabs = useSetOpenedTabs();
|
||||||
const { initialData, setEditorData, errorMessage, isLoading } = useEditorData({
|
const { initialData, setEditorData, errorMessage, isLoading } = useEditorData({
|
||||||
tabid,
|
tabid,
|
||||||
@@ -59,9 +59,9 @@ export default function FreeDataTab({ archiveFolder, archiveFile, tabVisible, to
|
|||||||
dispatchModel={dispatchModel}
|
dispatchModel={dispatchModel}
|
||||||
tabVisible={tabVisible}
|
tabVisible={tabVisible}
|
||||||
toolbarPortalRef={toolbarPortalRef}
|
toolbarPortalRef={toolbarPortalRef}
|
||||||
onSave={() => saveFileModalState.open()}
|
onSave={() => saveArchiveModalState.open()}
|
||||||
/>
|
/>
|
||||||
<SaveArchiveModal modalState={saveFileModalState} folder={archiveFolder} file={archiveFile} onSave={handleSave} />
|
<SaveArchiveModal modalState={saveArchiveModalState} folder={archiveFolder} file={archiveFile} onSave={handleSave} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import LoadingInfo from '../widgets/LoadingInfo';
|
|||||||
import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState';
|
import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState';
|
||||||
import useOpenNewTab from '../utility/useOpenNewTab';
|
import useOpenNewTab from '../utility/useOpenNewTab';
|
||||||
import { setSelectedTabFunc } from '../utility/common';
|
import { setSelectedTabFunc } from '../utility/common';
|
||||||
|
import ToolbarPortal from '../utility/ToolbarPortal';
|
||||||
|
|
||||||
export default function MarkdownEditorTab({ tabid, tabVisible, toolbarPortalRef, ...other }) {
|
export default function MarkdownEditorTab({ tabid, tabVisible, toolbarPortalRef, ...other }) {
|
||||||
const { editorData, setEditorData, isLoading, saveToStorage } = useEditorData({ tabid });
|
const { editorData, setEditorData, isLoading, saveToStorage } = useEditorData({ tabid });
|
||||||
const saveFileModalState = useModalState();
|
|
||||||
const openedTabs = useOpenedTabs();
|
const openedTabs = useOpenedTabs();
|
||||||
const setOpenedTabs = useSetOpenedTabs();
|
const setOpenedTabs = useSetOpenedTabs();
|
||||||
const openNewTab = useOpenNewTab();
|
const openNewTab = useOpenNewTab();
|
||||||
@@ -61,20 +61,17 @@ export default function MarkdownEditorTab({ tabid, tabVisible, toolbarPortalRef,
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
mode="markdown"
|
mode="markdown"
|
||||||
/>
|
/>
|
||||||
{toolbarPortalRef &&
|
<ToolbarPortal toolbarPortalRef={toolbarPortalRef} tabVisible={tabVisible}>
|
||||||
toolbarPortalRef.current &&
|
<MarkdownToolbar showPreview={showPreview} />
|
||||||
tabVisible &&
|
</ToolbarPortal>
|
||||||
ReactDOM.createPortal(
|
|
||||||
<MarkdownToolbar save={saveFileModalState.open} showPreview={showPreview} />,
|
|
||||||
toolbarPortalRef.current
|
|
||||||
)}
|
|
||||||
<SaveTabModal
|
<SaveTabModal
|
||||||
modalState={saveFileModalState}
|
|
||||||
tabVisible={tabVisible}
|
tabVisible={tabVisible}
|
||||||
|
toolbarPortalRef={toolbarPortalRef}
|
||||||
data={editorData}
|
data={editorData}
|
||||||
format="text"
|
format="text"
|
||||||
folder="markdown"
|
folder="markdown"
|
||||||
tabid={tabid}
|
tabid={tabid}
|
||||||
|
fileExtension="md"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import keycodes from '../utility/keycodes';
|
|||||||
import { changeTab } from '../utility/common';
|
import { changeTab } from '../utility/common';
|
||||||
import useSocket from '../utility/SocketProvider';
|
import useSocket from '../utility/SocketProvider';
|
||||||
import SaveTabModal from '../modals/SaveTabModal';
|
import SaveTabModal from '../modals/SaveTabModal';
|
||||||
import useModalState from '../modals/useModalState';
|
|
||||||
import sqlFormatter from 'sql-formatter';
|
import sqlFormatter from 'sql-formatter';
|
||||||
import useEditorData from '../utility/useEditorData';
|
import useEditorData from '../utility/useEditorData';
|
||||||
import LoadingInfo from '../widgets/LoadingInfo';
|
import LoadingInfo from '../widgets/LoadingInfo';
|
||||||
@@ -25,15 +24,25 @@ import QueryDesignColumns from '../designer/QueryDesignColumns';
|
|||||||
import { findEngineDriver } from 'dbgate-tools';
|
import { findEngineDriver } from 'dbgate-tools';
|
||||||
import { generateDesignedQuery } from '../designer/designerTools';
|
import { generateDesignedQuery } from '../designer/designerTools';
|
||||||
import useUndoReducer from '../utility/useUndoReducer';
|
import useUndoReducer from '../utility/useUndoReducer';
|
||||||
|
import { StatusBarItem } from '../widgets/StatusBar';
|
||||||
|
import useTimerLabel from '../utility/useTimerLabel';
|
||||||
|
import ToolbarPortal from '../utility/ToolbarPortal';
|
||||||
|
|
||||||
export default function QueryDesignTab({ tabid, conid, database, tabVisible, toolbarPortalRef, ...other }) {
|
export default function QueryDesignTab({
|
||||||
|
tabid,
|
||||||
|
conid,
|
||||||
|
database,
|
||||||
|
tabVisible,
|
||||||
|
toolbarPortalRef,
|
||||||
|
statusbarPortalRef,
|
||||||
|
...other
|
||||||
|
}) {
|
||||||
const [sessionId, setSessionId] = React.useState(null);
|
const [sessionId, setSessionId] = React.useState(null);
|
||||||
const [visibleResultTabs, setVisibleResultTabs] = React.useState(false);
|
const [visibleResultTabs, setVisibleResultTabs] = React.useState(false);
|
||||||
const [executeNumber, setExecuteNumber] = React.useState(0);
|
const [executeNumber, setExecuteNumber] = React.useState(0);
|
||||||
const setOpenedTabs = useSetOpenedTabs();
|
const setOpenedTabs = useSetOpenedTabs();
|
||||||
const socket = useSocket();
|
const socket = useSocket();
|
||||||
const [busy, setBusy] = React.useState(false);
|
const [busy, setBusy] = React.useState(false);
|
||||||
const saveFileModalState = useModalState();
|
|
||||||
const extensions = useExtensions();
|
const extensions = useExtensions();
|
||||||
const connection = useConnectionInfo({ conid });
|
const connection = useConnectionInfo({ conid });
|
||||||
const engine = findEngineDriver(connection, extensions);
|
const engine = findEngineDriver(connection, extensions);
|
||||||
@@ -49,6 +58,7 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
},
|
},
|
||||||
{ mergeNearActions: true }
|
{ mergeNearActions: true }
|
||||||
);
|
);
|
||||||
|
const timerLabel = useTimerLabel();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -61,6 +71,7 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
|
|
||||||
const handleSessionDone = React.useCallback(() => {
|
const handleSessionDone = React.useCallback(() => {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
|
timerLabel.stop();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const generatePreview = (value, engine) => {
|
const generatePreview = (value, engine) => {
|
||||||
@@ -114,6 +125,7 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
setSessionId(sesid);
|
setSessionId(sesid);
|
||||||
}
|
}
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
|
timerLabel.start();
|
||||||
await axios.post('sessions/execute-query', {
|
await axios.post('sessions/execute-query', {
|
||||||
sesid,
|
sesid,
|
||||||
sql: sqlPreview,
|
sql: sqlPreview,
|
||||||
@@ -126,6 +138,7 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
});
|
});
|
||||||
setSessionId(null);
|
setSessionId(null);
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
|
timerLabel.stop();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = React.useCallback(
|
const handleKeyDown = React.useCallback(
|
||||||
@@ -182,10 +195,7 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
)}
|
)}
|
||||||
</ResultTabs>
|
</ResultTabs>
|
||||||
</VerticalSplitter>
|
</VerticalSplitter>
|
||||||
{toolbarPortalRef &&
|
<ToolbarPortal toolbarPortalRef={toolbarPortalRef} tabVisible={tabVisible}>
|
||||||
toolbarPortalRef.current &&
|
|
||||||
tabVisible &&
|
|
||||||
ReactDOM.createPortal(
|
|
||||||
<QueryDesignToolbar
|
<QueryDesignToolbar
|
||||||
modelState={modelState}
|
modelState={modelState}
|
||||||
dispatchModel={dispatchModel}
|
dispatchModel={dispatchModel}
|
||||||
@@ -194,19 +204,23 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
busy={busy}
|
busy={busy}
|
||||||
// cancel={handleCancel}
|
// cancel={handleCancel}
|
||||||
// format={handleFormatCode}
|
// format={handleFormatCode}
|
||||||
save={saveFileModalState.open}
|
|
||||||
isConnected={!!sessionId}
|
isConnected={!!sessionId}
|
||||||
kill={handleKill}
|
kill={handleKill}
|
||||||
/>,
|
/>
|
||||||
toolbarPortalRef.current
|
</ToolbarPortal>
|
||||||
)}
|
{statusbarPortalRef &&
|
||||||
|
statusbarPortalRef.current &&
|
||||||
|
tabVisible &&
|
||||||
|
ReactDOM.createPortal(<StatusBarItem>{timerLabel.text}</StatusBarItem>, statusbarPortalRef.current)}
|
||||||
<SaveTabModal
|
<SaveTabModal
|
||||||
modalState={saveFileModalState}
|
// modalState={saveFileModalState}
|
||||||
tabVisible={tabVisible}
|
tabVisible={tabVisible}
|
||||||
|
toolbarPortalRef={toolbarPortalRef}
|
||||||
data={modelState.value}
|
data={modelState.value}
|
||||||
format="json"
|
format="json"
|
||||||
folder="query"
|
folder="query"
|
||||||
tabid={tabid}
|
tabid={tabid}
|
||||||
|
fileExtension="qdesign"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,16 +21,46 @@ import useEditorData from '../utility/useEditorData';
|
|||||||
import applySqlTemplate from '../utility/applySqlTemplate';
|
import applySqlTemplate from '../utility/applySqlTemplate';
|
||||||
import LoadingInfo from '../widgets/LoadingInfo';
|
import LoadingInfo from '../widgets/LoadingInfo';
|
||||||
import useExtensions from '../utility/useExtensions';
|
import useExtensions from '../utility/useExtensions';
|
||||||
|
import useTimerLabel from '../utility/useTimerLabel';
|
||||||
|
import { StatusBarItem } from '../widgets/StatusBar';
|
||||||
|
import ToolbarPortal from '../utility/ToolbarPortal';
|
||||||
|
|
||||||
export default function QueryTab({ tabid, conid, database, initialArgs, tabVisible, toolbarPortalRef, ...other }) {
|
function createSqlPreview(sql) {
|
||||||
|
if (!sql) return undefined;
|
||||||
|
let data = sql.substring(0, 500);
|
||||||
|
data = data.replace(/\[[^\]]+\]\./g, '');
|
||||||
|
data = data.replace(/\[a-zA-Z0-9_]+\./g, '');
|
||||||
|
data = data.replace(/\/\*.*\*\//g, '');
|
||||||
|
data = data.replace(/[\[\]]/g, '');
|
||||||
|
data = data.replace(/--[^\n]*\n/g, '');
|
||||||
|
|
||||||
|
for (let step = 1; step <= 5; step++) {
|
||||||
|
data = data.replace(/\([^\(^\)]+\)/g, '');
|
||||||
|
}
|
||||||
|
data = data.replace(/\s+/g, ' ');
|
||||||
|
data = data.trim();
|
||||||
|
data = data.replace(/^(.{50}[^\s]*).*/, '$1');
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QueryTab({
|
||||||
|
tabid,
|
||||||
|
conid,
|
||||||
|
database,
|
||||||
|
initialArgs,
|
||||||
|
tabVisible,
|
||||||
|
toolbarPortalRef,
|
||||||
|
statusbarPortalRef,
|
||||||
|
...other
|
||||||
|
}) {
|
||||||
const [sessionId, setSessionId] = React.useState(null);
|
const [sessionId, setSessionId] = React.useState(null);
|
||||||
const [visibleResultTabs, setVisibleResultTabs] = React.useState(false);
|
const [visibleResultTabs, setVisibleResultTabs] = React.useState(false);
|
||||||
const [executeNumber, setExecuteNumber] = React.useState(0);
|
const [executeNumber, setExecuteNumber] = React.useState(0);
|
||||||
const setOpenedTabs = useSetOpenedTabs();
|
const setOpenedTabs = useSetOpenedTabs();
|
||||||
const socket = useSocket();
|
const socket = useSocket();
|
||||||
const [busy, setBusy] = React.useState(false);
|
const [busy, setBusy] = React.useState(false);
|
||||||
const saveFileModalState = useModalState();
|
|
||||||
const extensions = useExtensions();
|
const extensions = useExtensions();
|
||||||
|
const timerLabel = useTimerLabel();
|
||||||
const { editorData, setEditorData, isLoading } = useEditorData({
|
const { editorData, setEditorData, isLoading } = useEditorData({
|
||||||
tabid,
|
tabid,
|
||||||
loadFromArgs:
|
loadFromArgs:
|
||||||
@@ -43,6 +73,7 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib
|
|||||||
|
|
||||||
const handleSessionDone = React.useCallback(() => {
|
const handleSessionDone = React.useCallback(() => {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
|
timerLabel.stop();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -61,6 +92,23 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib
|
|||||||
useUpdateDatabaseForTab(tabVisible, conid, database);
|
useUpdateDatabaseForTab(tabVisible, conid, database);
|
||||||
const connection = useConnectionInfo({ conid });
|
const connection = useConnectionInfo({ conid });
|
||||||
|
|
||||||
|
const updateContentPreviewDebounced = React.useRef(
|
||||||
|
_.debounce(
|
||||||
|
// @ts-ignore
|
||||||
|
sql =>
|
||||||
|
changeTab(tabid, setOpenedTabs, tab => ({
|
||||||
|
...tab,
|
||||||
|
contentPreview: createSqlPreview(sql),
|
||||||
|
})),
|
||||||
|
500
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
updateContentPreviewDebounced.current(editorData);
|
||||||
|
}, [editorData]);
|
||||||
|
|
||||||
const handleExecute = async () => {
|
const handleExecute = async () => {
|
||||||
if (busy) return;
|
if (busy) return;
|
||||||
setExecuteNumber(num => num + 1);
|
setExecuteNumber(num => num + 1);
|
||||||
@@ -77,6 +125,7 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib
|
|||||||
setSessionId(sesid);
|
setSessionId(sesid);
|
||||||
}
|
}
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
|
timerLabel.start();
|
||||||
await axios.post('sessions/execute-query', {
|
await axios.post('sessions/execute-query', {
|
||||||
sesid,
|
sesid,
|
||||||
sql: selectedText || editorData,
|
sql: selectedText || editorData,
|
||||||
@@ -95,6 +144,7 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib
|
|||||||
});
|
});
|
||||||
setSessionId(null);
|
setSessionId(null);
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
|
timerLabel.stop();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (data, hash, keyString, keyCode, event) => {
|
const handleKeyDown = (data, hash, keyString, keyCode, event) => {
|
||||||
@@ -151,7 +201,7 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib
|
|||||||
</ResultTabs>
|
</ResultTabs>
|
||||||
)}
|
)}
|
||||||
</VerticalSplitter>
|
</VerticalSplitter>
|
||||||
{toolbarPortalRef &&
|
{/* {toolbarPortalRef &&
|
||||||
toolbarPortalRef.current &&
|
toolbarPortalRef.current &&
|
||||||
tabVisible &&
|
tabVisible &&
|
||||||
ReactDOM.createPortal(
|
ReactDOM.createPortal(
|
||||||
@@ -166,14 +216,31 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib
|
|||||||
kill={handleKill}
|
kill={handleKill}
|
||||||
/>,
|
/>,
|
||||||
toolbarPortalRef.current
|
toolbarPortalRef.current
|
||||||
)}
|
)} */}
|
||||||
|
{statusbarPortalRef &&
|
||||||
|
statusbarPortalRef.current &&
|
||||||
|
tabVisible &&
|
||||||
|
ReactDOM.createPortal(<StatusBarItem>{timerLabel.text}</StatusBarItem>, statusbarPortalRef.current)}
|
||||||
|
<ToolbarPortal toolbarPortalRef={toolbarPortalRef} tabVisible={tabVisible}>
|
||||||
|
<QueryToolbar
|
||||||
|
isDatabaseDefined={conid && database}
|
||||||
|
execute={handleExecute}
|
||||||
|
busy={busy}
|
||||||
|
// cancel={handleCancel}
|
||||||
|
format={handleFormatCode}
|
||||||
|
// save={saveFileModalState.open}
|
||||||
|
isConnected={!!sessionId}
|
||||||
|
kill={handleKill}
|
||||||
|
/>
|
||||||
|
</ToolbarPortal>
|
||||||
<SaveTabModal
|
<SaveTabModal
|
||||||
modalState={saveFileModalState}
|
toolbarPortalRef={toolbarPortalRef}
|
||||||
tabVisible={tabVisible}
|
tabVisible={tabVisible}
|
||||||
data={editorData}
|
data={editorData}
|
||||||
format="text"
|
format="text"
|
||||||
folder="sql"
|
folder="sql"
|
||||||
tabid={tabid}
|
tabid={tabid}
|
||||||
|
fileExtension="sql"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,18 +14,20 @@ import useShowModal from '../modals/showModal';
|
|||||||
import ImportExportModal from '../modals/ImportExportModal';
|
import ImportExportModal from '../modals/ImportExportModal';
|
||||||
import useEditorData from '../utility/useEditorData';
|
import useEditorData from '../utility/useEditorData';
|
||||||
import SaveTabModal from '../modals/SaveTabModal';
|
import SaveTabModal from '../modals/SaveTabModal';
|
||||||
import useModalState from '../modals/useModalState';
|
|
||||||
import LoadingInfo from '../widgets/LoadingInfo';
|
import LoadingInfo from '../widgets/LoadingInfo';
|
||||||
|
import useTimerLabel from '../utility/useTimerLabel';
|
||||||
|
import { StatusBarItem } from '../widgets/StatusBar';
|
||||||
|
import ToolbarPortal from '../utility/ToolbarPortal';
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, ...other }) {
|
export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, statusbarPortalRef, ...other }) {
|
||||||
const [busy, setBusy] = React.useState(false);
|
const [busy, setBusy] = React.useState(false);
|
||||||
const showModal = useShowModal();
|
const showModal = useShowModal();
|
||||||
const { editorData, setEditorData, isLoading } = useEditorData({ tabid });
|
const { editorData, setEditorData, isLoading } = useEditorData({ tabid });
|
||||||
const saveFileModalState = useModalState();
|
const timerLabel = useTimerLabel();
|
||||||
|
|
||||||
const setOpenedTabs = useSetOpenedTabs();
|
const setOpenedTabs = useSetOpenedTabs();
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, ...other
|
|||||||
|
|
||||||
const handleRunnerDone = React.useCallback(() => {
|
const handleRunnerDone = React.useCallback(() => {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
|
timerLabel.stop();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -69,12 +72,14 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, ...other
|
|||||||
runid = resp.data.runid;
|
runid = resp.data.runid;
|
||||||
setRunnerId(runid);
|
setRunnerId(runid);
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
|
timerLabel.start();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
axios.post('runners/cancel', {
|
axios.post('runners/cancel', {
|
||||||
runid: runnerId,
|
runid: runnerId,
|
||||||
});
|
});
|
||||||
|
timerLabel.stop();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (data, hash, keyString, keyCode, event) => {
|
const handleKeyDown = (data, hash, keyString, keyCode, event) => {
|
||||||
@@ -114,27 +119,27 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, ...other
|
|||||||
/>
|
/>
|
||||||
<RunnerOutputPane runnerId={runnerId} executeNumber={executeNumber} />
|
<RunnerOutputPane runnerId={runnerId} executeNumber={executeNumber} />
|
||||||
</VerticalSplitter>
|
</VerticalSplitter>
|
||||||
{toolbarPortalRef &&
|
<ToolbarPortal toolbarPortalRef={toolbarPortalRef} tabVisible={tabVisible}>
|
||||||
toolbarPortalRef.current &&
|
|
||||||
tabVisible &&
|
|
||||||
ReactDOM.createPortal(
|
|
||||||
<ShellToolbar
|
<ShellToolbar
|
||||||
execute={handleExecute}
|
execute={handleExecute}
|
||||||
busy={busy}
|
busy={busy}
|
||||||
cancel={handleCancel}
|
cancel={handleCancel}
|
||||||
edit={handleEdit}
|
edit={handleEdit}
|
||||||
editAvailable={configRegex.test(editorData || '')}
|
editAvailable={configRegex.test(editorData || '')}
|
||||||
save={saveFileModalState.open}
|
/>
|
||||||
/>,
|
</ToolbarPortal>
|
||||||
toolbarPortalRef.current
|
{statusbarPortalRef &&
|
||||||
)}
|
statusbarPortalRef.current &&
|
||||||
|
tabVisible &&
|
||||||
|
ReactDOM.createPortal(<StatusBarItem>{timerLabel.text}</StatusBarItem>, statusbarPortalRef.current)}
|
||||||
<SaveTabModal
|
<SaveTabModal
|
||||||
modalState={saveFileModalState}
|
toolbarPortalRef={toolbarPortalRef}
|
||||||
tabVisible={tabVisible}
|
tabVisible={tabVisible}
|
||||||
data={editorData}
|
data={editorData}
|
||||||
format="text"
|
format="text"
|
||||||
folder="shell"
|
folder="shell"
|
||||||
tabid={tabid}
|
tabid={tabid}
|
||||||
|
fileExtension="js"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ export default function ConnectionsPinger({ children }) {
|
|||||||
const openedConnections = useOpenedConnections();
|
const openedConnections = useOpenedConnections();
|
||||||
const currentDatabase = useCurrentDatabase();
|
const currentDatabase = useCurrentDatabase();
|
||||||
|
|
||||||
const doPing = () => {
|
const doServerPing = () => {
|
||||||
axios.post('server-connections/ping', { connections: openedConnections });
|
axios.post('server-connections/ping', { connections: openedConnections });
|
||||||
|
};
|
||||||
|
|
||||||
|
const doDatabasePing = () => {
|
||||||
const database = _.get(currentDatabase, 'name');
|
const database = _.get(currentDatabase, 'name');
|
||||||
const conid = _.get(currentDatabase, 'connection._id');
|
const conid = _.get(currentDatabase, 'connection._id');
|
||||||
if (conid && database) {
|
if (conid && database) {
|
||||||
@@ -18,9 +20,16 @@ export default function ConnectionsPinger({ children }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
doPing();
|
doServerPing();
|
||||||
const handle = window.setInterval(doPing, 30 * 1000);
|
const handle = window.setInterval(doServerPing, 30 * 1000);
|
||||||
return () => window.clearInterval(handle);
|
return () => window.clearInterval(handle);
|
||||||
}, [openedConnections, currentDatabase]);
|
}, [openedConnections]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
doDatabasePing();
|
||||||
|
const handle = window.setInterval(doDatabasePing, 30 * 1000);
|
||||||
|
return () => window.clearInterval(handle);
|
||||||
|
}, [currentDatabase]);
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|||||||
26
packages/web/src/utility/SaveFileToolbarButton.js
Normal file
26
packages/web/src/utility/SaveFileToolbarButton.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||||
|
import ToolbarButton, { ToolbarDropDownButton } from '../widgets/ToolbarButton';
|
||||||
|
|
||||||
|
export default function SaveFileToolbarButton({ tabid, save, saveAs }) {
|
||||||
|
if (!saveAs) return null;
|
||||||
|
|
||||||
|
if (save) {
|
||||||
|
return (
|
||||||
|
<ToolbarDropDownButton icon="icon save" text="Save">
|
||||||
|
<DropDownMenuItem onClick={save} keyText="Ctrl+S">
|
||||||
|
Save
|
||||||
|
</DropDownMenuItem>
|
||||||
|
<DropDownMenuItem onClick={saveAs} keyText="Ctrl+Shift+S">
|
||||||
|
Save As
|
||||||
|
</DropDownMenuItem>
|
||||||
|
</ToolbarDropDownButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton onClick={saveAs} icon="icon save">
|
||||||
|
Save As
|
||||||
|
</ToolbarButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
packages/web/src/utility/ToolbarPortal.js
Normal file
13
packages/web/src/utility/ToolbarPortal.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
export default function ToolbarPortal({ toolbarPortalRef, tabVisible, children }) {
|
||||||
|
return (
|
||||||
|
(toolbarPortalRef &&
|
||||||
|
toolbarPortalRef.current &&
|
||||||
|
tabVisible &&
|
||||||
|
children &&
|
||||||
|
ReactDOM.createPortal(children, toolbarPortalRef.current)) ||
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,8 +3,10 @@ import { useDropzone } from 'react-dropzone';
|
|||||||
import ImportExportModal from '../modals/ImportExportModal';
|
import ImportExportModal from '../modals/ImportExportModal';
|
||||||
import useShowModal from '../modals/showModal';
|
import useShowModal from '../modals/showModal';
|
||||||
import { findFileFormat } from './fileformats';
|
import { findFileFormat } from './fileformats';
|
||||||
|
import getElectron from './getElectron';
|
||||||
import resolveApi from './resolveApi';
|
import resolveApi from './resolveApi';
|
||||||
import useExtensions from './useExtensions';
|
import useExtensions from './useExtensions';
|
||||||
|
import { useOpenElectronFileCore, canOpenByElectron } from './useOpenElectronFile';
|
||||||
|
|
||||||
const UploadsContext = React.createContext(null);
|
const UploadsContext = React.createContext(null);
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ export function useUploadFiles() {
|
|||||||
const { uploadListener } = useUploadsProvider();
|
const { uploadListener } = useUploadsProvider();
|
||||||
const showModal = useShowModal();
|
const showModal = useShowModal();
|
||||||
const extensions = useExtensions();
|
const extensions = useExtensions();
|
||||||
|
const electron = getElectron();
|
||||||
|
const openElectronFileCore = useOpenElectronFileCore();
|
||||||
|
|
||||||
const handleUploadFiles = React.useCallback(
|
const handleUploadFiles = React.useCallback(
|
||||||
files => {
|
files => {
|
||||||
@@ -31,6 +35,12 @@ export function useUploadFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('FILE', file);
|
console.log('FILE', file);
|
||||||
|
|
||||||
|
if (electron && canOpenByElectron(file.path, extensions)) {
|
||||||
|
openElectronFileCore(file.path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('data', file);
|
formData.append('data', file);
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n
|
|||||||
setValue(init);
|
setValue(init);
|
||||||
valueRef.current = init;
|
valueRef.current = init;
|
||||||
initialDataRef.current = init;
|
initialDataRef.current = init;
|
||||||
|
// mark as not saved
|
||||||
|
changeCounterRef.current += 1;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = (err && err.response && err.response.data && err.response.data.error) || 'Loading failed';
|
const message = (err && err.response && err.response.data && err.response.data.error) || 'Loading failed';
|
||||||
setErrorMessage(message);
|
setErrorMessage(message);
|
||||||
|
|||||||
88
packages/web/src/utility/useOpenElectronFile.js
Normal file
88
packages/web/src/utility/useOpenElectronFile.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import ImportExportModal from '../modals/ImportExportModal';
|
||||||
|
import useShowModal from '../modals/showModal';
|
||||||
|
import useNewQuery from '../query/useNewQuery';
|
||||||
|
import getElectron from './getElectron';
|
||||||
|
import useExtensions from './useExtensions';
|
||||||
|
|
||||||
|
export function canOpenByElectron(file, extensions) {
|
||||||
|
if (!file) return false;
|
||||||
|
const nameLower = file.toLowerCase();
|
||||||
|
if (nameLower.endsWith('.sql')) return true;
|
||||||
|
for (const format of extensions.fileFormats) {
|
||||||
|
if (nameLower.endsWith(`.${format.extension}`)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOpenElectronFileCore() {
|
||||||
|
const newQuery = useNewQuery();
|
||||||
|
const extensions = useExtensions();
|
||||||
|
const showModal = useShowModal();
|
||||||
|
|
||||||
|
return filePath => {
|
||||||
|
const nameLower = filePath.toLowerCase();
|
||||||
|
const path = window.require('path');
|
||||||
|
const fs = window.require('fs');
|
||||||
|
const parsed = path.parse(filePath);
|
||||||
|
|
||||||
|
if (nameLower.endsWith('.sql')) {
|
||||||
|
const data = fs.readFileSync(filePath, { encoding: 'utf-8' });
|
||||||
|
|
||||||
|
newQuery({
|
||||||
|
title: parsed.name,
|
||||||
|
initialData: data,
|
||||||
|
// @ts-ignore
|
||||||
|
savedFilePath: filePath,
|
||||||
|
savedFormat: 'text',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const format of extensions.fileFormats) {
|
||||||
|
if (nameLower.endsWith(`.${format.extension}`)) {
|
||||||
|
showModal(modalState => (
|
||||||
|
<ImportExportModal
|
||||||
|
openedFile={{
|
||||||
|
filePath,
|
||||||
|
storageType: format.storageType,
|
||||||
|
shortName: parsed.name,
|
||||||
|
}}
|
||||||
|
modalState={modalState}
|
||||||
|
importToArchive
|
||||||
|
initialValues={{
|
||||||
|
sourceStorageType: format.storageType,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileFormatFilters(extensions) {
|
||||||
|
return extensions.fileFormats.filter(x => x.readerFunc).map(x => ({ name: x.name, extensions: [x.extension] }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileFormatExtensions(extensions) {
|
||||||
|
return extensions.fileFormats.filter(x => x.readerFunc).map(x => x.extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useOpenElectronFile() {
|
||||||
|
const electron = getElectron();
|
||||||
|
const openElectronFileCore = useOpenElectronFileCore();
|
||||||
|
const extensions = useExtensions();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const filePaths = electron.remote.dialog.showOpenDialogSync(electron.remote.getCurrentWindow(), {
|
||||||
|
filters: [
|
||||||
|
{ name: `All supported files`, extensions: ['sql', ...getFileFormatExtensions(extensions)] },
|
||||||
|
{ name: `SQL files`, extensions: ['sql'] },
|
||||||
|
...getFileFormatFilters(extensions),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const filePath = filePaths && filePaths[0];
|
||||||
|
if (canOpenByElectron(filePath, extensions)) {
|
||||||
|
openElectronFileCore(filePath);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,6 +7,14 @@ import { useOpenedTabs, useSetOpenedTabs } from './globalState';
|
|||||||
import tabs from '../tabs';
|
import tabs from '../tabs';
|
||||||
import { setSelectedTabFunc } from './common';
|
import { setSelectedTabFunc } from './common';
|
||||||
|
|
||||||
|
function findFreeNumber(numbers) {
|
||||||
|
if (numbers.length == 0) return 1;
|
||||||
|
return _.max(numbers) + 1;
|
||||||
|
// let res = 1;
|
||||||
|
// while (numbers.includes(res)) res += 1;
|
||||||
|
// return res;
|
||||||
|
}
|
||||||
|
|
||||||
export default function useOpenNewTab() {
|
export default function useOpenNewTab() {
|
||||||
const setOpenedTabs = useSetOpenedTabs();
|
const setOpenedTabs = useSetOpenedTabs();
|
||||||
const openedTabs = useOpenedTabs();
|
const openedTabs = useOpenedTabs();
|
||||||
@@ -15,11 +23,16 @@ export default function useOpenNewTab() {
|
|||||||
async (newTab, initialData = undefined, options) => {
|
async (newTab, initialData = undefined, options) => {
|
||||||
let existing = null;
|
let existing = null;
|
||||||
|
|
||||||
const { savedFile } = newTab.props || {};
|
const { savedFile, savedFolder, savedFilePath } = newTab.props || {};
|
||||||
if (savedFile) {
|
if (savedFile || savedFilePath) {
|
||||||
existing = openedTabs.find(
|
existing = openedTabs.find(
|
||||||
x =>
|
x =>
|
||||||
x.props && x.tabComponent == newTab.tabComponent && x.closedTime == null && x.props.savedFile == savedFile
|
x.props &&
|
||||||
|
x.tabComponent == newTab.tabComponent &&
|
||||||
|
x.closedTime == null &&
|
||||||
|
x.props.savedFile == savedFile &&
|
||||||
|
x.props.savedFolder == savedFolder &&
|
||||||
|
x.props.savedFilePath == savedFilePath
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +55,15 @@ export default function useOpenNewTab() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// new tab will be created
|
||||||
|
if (newTab.title.endsWith('#')) {
|
||||||
|
const numbers = openedTabs
|
||||||
|
.filter(x => x.closedTime == null && x.title && x.title.startsWith(newTab.title))
|
||||||
|
.map(x => parseInt(x.title.substring(newTab.title.length)));
|
||||||
|
|
||||||
|
newTab.title = `${newTab.title}${findFreeNumber(numbers)}`;
|
||||||
|
}
|
||||||
|
|
||||||
const tabid = uuidv1();
|
const tabid = uuidv1();
|
||||||
if (initialData) {
|
if (initialData) {
|
||||||
for (const key of _.keys(initialData)) {
|
for (const key of _.keys(initialData)) {
|
||||||
@@ -61,7 +83,7 @@ export default function useOpenNewTab() {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
[setOpenedTabs]
|
[setOpenedTabs, openedTabs]
|
||||||
);
|
);
|
||||||
|
|
||||||
return openNewTab;
|
return openNewTab;
|
||||||
|
|||||||
@@ -16,12 +16,16 @@ export default function useStorage(key, storageObject, initialValue) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// use storedValue to ref, so that setValue with function argument works without changeing setValue itself
|
||||||
|
const storedValueRef = React.useRef(storedValue);
|
||||||
|
storedValueRef.current = storedValue;
|
||||||
|
|
||||||
// Return a wrapped version of useState's setter function that ...
|
// Return a wrapped version of useState's setter function that ...
|
||||||
// ... persists the new value to localStorage.
|
// ... persists the new value to localStorage.
|
||||||
const setValue = value => {
|
const setValue = React.useCallback(value => {
|
||||||
try {
|
try {
|
||||||
// Allow value to be a function so we have same API as useState
|
// Allow value to be a function so we have same API as useState
|
||||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
const valueToStore = value instanceof Function ? value(storedValueRef.current) : value;
|
||||||
// Save state
|
// Save state
|
||||||
setStoredValue(valueToStore);
|
setStoredValue(valueToStore);
|
||||||
// Save to local storage
|
// Save to local storage
|
||||||
@@ -31,7 +35,7 @@ export default function useStorage(key, storageObject, initialValue) {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
console.log('Error saving storage value', key, value);
|
console.log('Error saving storage value', key, value);
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
return [storedValue, setValue];
|
return [storedValue, setValue];
|
||||||
}
|
}
|
||||||
|
|||||||
37
packages/web/src/utility/useTimerLabel.js
Normal file
37
packages/web/src/utility/useTimerLabel.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
function formatSeconds(duration) {
|
||||||
|
if (duration == null) return '';
|
||||||
|
const hours = _.padStart(Math.floor(duration / 3600).toString(), 2, '0');
|
||||||
|
const minutes = _.padStart((Math.floor(duration / 60) % 60).toString(), 2, '0');
|
||||||
|
const seconds = _.padStart((duration % 60).toString(), 2, '0');
|
||||||
|
return `${hours}:${minutes}:${seconds}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useTimerLabel() {
|
||||||
|
const [duration, setDuration] = React.useState(null);
|
||||||
|
const [busy, setBusy] = React.useState(false);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (busy) {
|
||||||
|
setDuration(0);
|
||||||
|
const handle = setInterval(() => setDuration(x => x + 1), 1000);
|
||||||
|
return () => window.clearInterval(handle);
|
||||||
|
}
|
||||||
|
}, [busy]);
|
||||||
|
|
||||||
|
const start = React.useCallback(() => {
|
||||||
|
setBusy(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const stop = React.useCallback(() => {
|
||||||
|
setBusy(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
stop,
|
||||||
|
text: formatSeconds(duration),
|
||||||
|
duration,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -44,14 +44,14 @@ export default function FavoritesWidget() {
|
|||||||
const hasPermission = useHasPermission();
|
const hasPermission = useHasPermission();
|
||||||
return (
|
return (
|
||||||
<WidgetColumnBar>
|
<WidgetColumnBar>
|
||||||
<WidgetColumnBarItem title="Recently closed tabs" name="closedTabs" height="20%">
|
|
||||||
<ClosedTabsList />
|
|
||||||
</WidgetColumnBarItem>
|
|
||||||
{hasPermission('files/favorites/read') && (
|
{hasPermission('files/favorites/read') && (
|
||||||
<WidgetColumnBarItem title="Favorites" name="favorites" height="15%">
|
<WidgetColumnBarItem title="Favorites" name="favorites" height="20%">
|
||||||
<FavoritesList />
|
<FavoritesList />
|
||||||
</WidgetColumnBarItem>
|
</WidgetColumnBarItem>
|
||||||
)}
|
)}
|
||||||
|
<WidgetColumnBarItem title="Recently closed tabs" name="closedTabs">
|
||||||
|
<ClosedTabsList />
|
||||||
|
</WidgetColumnBarItem>
|
||||||
</WidgetColumnBar>
|
</WidgetColumnBar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ const Container = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
color: ${props => props.theme.statusbar_font1};
|
color: ${props => props.theme.statusbar_font1};
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
justify-content: space-between;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Item = styled.div`
|
export const StatusBarItem = styled.div`
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
// margin: auto;
|
|
||||||
// flex-grow: 0;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ErrorWrapper = styled.span`
|
const ErrorWrapper = styled.span`
|
||||||
@@ -30,32 +29,37 @@ const InfoWrapper = styled.span`
|
|||||||
props.theme.statusbar_font_green[5]};
|
props.theme.statusbar_font_green[5]};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function StatusBar() {
|
const StatusbarContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function StatusBar({ statusbarPortalRef }) {
|
||||||
const { name, connection } = useCurrentDatabase() || {};
|
const { name, connection } = useCurrentDatabase() || {};
|
||||||
const status = useDatabaseStatus(connection ? { conid: connection._id, database: name } : {});
|
const status = useDatabaseStatus(connection ? { conid: connection._id, database: name } : {});
|
||||||
const { displayName, server, user, engine } = connection || {};
|
const { displayName, server, user, engine } = connection || {};
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<Container theme={theme}>
|
<Container theme={theme}>
|
||||||
|
<StatusbarContainer>
|
||||||
{name && (
|
{name && (
|
||||||
<Item>
|
<StatusBarItem>
|
||||||
<FontIcon icon="icon database" /> {name}
|
<FontIcon icon="icon database" /> {name}
|
||||||
</Item>
|
</StatusBarItem>
|
||||||
)}
|
)}
|
||||||
{(displayName || server) && (
|
{(displayName || server) && (
|
||||||
<Item>
|
<StatusBarItem>
|
||||||
<FontIcon icon="icon server" /> {displayName || server}
|
<FontIcon icon="icon server" /> {displayName || server}
|
||||||
</Item>
|
</StatusBarItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{user && (
|
{user && (
|
||||||
<Item>
|
<StatusBarItem>
|
||||||
<FontIcon icon="icon account" /> {user}
|
<FontIcon icon="icon account" /> {user}
|
||||||
</Item>
|
</StatusBarItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{connection && status && (
|
{connection && status && (
|
||||||
<Item>
|
<StatusBarItem>
|
||||||
{status.name == 'pending' && (
|
{status.name == 'pending' && (
|
||||||
<>
|
<>
|
||||||
<FontIcon icon="icon loading" /> Loading
|
<FontIcon icon="icon loading" /> Loading
|
||||||
@@ -77,15 +81,17 @@ export default function StatusBar() {
|
|||||||
Error
|
Error
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Item>
|
</StatusBarItem>
|
||||||
)}
|
)}
|
||||||
{!connection && (
|
{!connection && (
|
||||||
<Item>
|
<StatusBarItem>
|
||||||
<>
|
<>
|
||||||
<FontIcon icon="icon disconnected" /> Not connected
|
<FontIcon icon="icon disconnected" /> Not connected
|
||||||
</>
|
</>
|
||||||
</Item>
|
</StatusBarItem>
|
||||||
)}
|
)}
|
||||||
|
</StatusbarContainer>
|
||||||
|
<StatusbarContainer ref={statusbarPortalRef}></StatusbarContainer>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import tabs from '../tabs';
|
|||||||
import FavoriteModal from '../modals/FavoriteModal';
|
import FavoriteModal from '../modals/FavoriteModal';
|
||||||
import { useOpenFavorite } from '../appobj/FavoriteFileAppObject';
|
import { useOpenFavorite } from '../appobj/FavoriteFileAppObject';
|
||||||
import ErrorMessageModal from '../modals/ErrorMessageModal';
|
import ErrorMessageModal from '../modals/ErrorMessageModal';
|
||||||
|
import useOpenElectronFile from '../utility/useOpenElectronFile';
|
||||||
|
|
||||||
const ToolbarContainer = styled.div`
|
const ToolbarContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -48,6 +49,7 @@ export default function ToolBar({ toolbarPortalRef }) {
|
|||||||
const electron = getElectron();
|
const electron = getElectron();
|
||||||
const favorites = useFavorites();
|
const favorites = useFavorites();
|
||||||
const openFavorite = useOpenFavorite();
|
const openFavorite = useOpenFavorite();
|
||||||
|
const openElectronFile = useOpenElectronFile();
|
||||||
|
|
||||||
const currentTab = openedTabs.find(x => x.selected);
|
const currentTab = openedTabs.find(x => x.selected);
|
||||||
|
|
||||||
@@ -58,6 +60,7 @@ export default function ToolBar({ toolbarPortalRef }) {
|
|||||||
window['dbgate_newQuery'] = newQuery;
|
window['dbgate_newQuery'] = newQuery;
|
||||||
window['dbgate_closeAll'] = () => setOpenedTabs([]);
|
window['dbgate_closeAll'] = () => setOpenedTabs([]);
|
||||||
window['dbgate_showAbout'] = showAbout;
|
window['dbgate_showAbout'] = showAbout;
|
||||||
|
window['dbgate_openFile'] = openElectronFile;
|
||||||
});
|
});
|
||||||
|
|
||||||
const showAbout = () => {
|
const showAbout = () => {
|
||||||
@@ -91,7 +94,7 @@ export default function ToolBar({ toolbarPortalRef }) {
|
|||||||
|
|
||||||
const newMarkdown = () => {
|
const newMarkdown = () => {
|
||||||
openNewTab({
|
openNewTab({
|
||||||
title: 'Page',
|
title: 'Page #',
|
||||||
tabComponent: 'MarkdownEditorTab',
|
tabComponent: 'MarkdownEditorTab',
|
||||||
icon: 'img markdown',
|
icon: 'img markdown',
|
||||||
});
|
});
|
||||||
@@ -103,7 +106,7 @@ export default function ToolBar({ toolbarPortalRef }) {
|
|||||||
|
|
||||||
const newShell = () => {
|
const newShell = () => {
|
||||||
openNewTab({
|
openNewTab({
|
||||||
title: 'Shell',
|
title: 'Shell #',
|
||||||
icon: 'img shell',
|
icon: 'img shell',
|
||||||
tabComponent: 'ShellTab',
|
tabComponent: 'ShellTab',
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user