shell tab

This commit is contained in:
Jan Prochazka
2021-03-11 13:40:19 +01:00
parent 7d6bf90a0a
commit 913f89e970
13 changed files with 364 additions and 87 deletions

View File

@@ -5,6 +5,7 @@ import { ThemeDefinition } from 'dbgate-types';
import ConnectionModal from '../modals/ConnectionModal.svelte';
import { showModal } from '../modals/modalTools';
import newQuery from '../query/newQuery';
import saveTabFile, { saveTabEnabledStore } from '../utility/saveTabFile';
function themeCommand(theme: ThemeDefinition) {
return {
@@ -64,3 +65,94 @@ registerCommand({
keyText: 'Ctrl+Q',
onClick: () => newQuery(),
});
export function registerFileCommands({
idPrefix,
category,
editorStore,
editorStatusStore,
folder,
format,
fileExtension,
execute = false,
toggleComment = false,
findReplace = false,
}) {
registerCommand({
id: idPrefix + '.save',
category,
name: 'Save',
keyText: 'Ctrl+S',
icon: 'icon save',
toolbar: true,
enabledStore: saveTabEnabledStore(editorStore),
onClick: () => saveTabFile(editorStore, false, folder, format, fileExtension),
});
registerCommand({
id: idPrefix + '.saveAs',
category,
name: 'Save As',
keyText: 'Ctrl+Shift+S',
enabledStore: saveTabEnabledStore(editorStore),
onClick: () => saveTabFile(editorStore, true, folder, format, fileExtension),
});
if (execute) {
registerCommand({
id: idPrefix + '.execute',
category,
name: 'Execute',
icon: 'icon run',
toolbar: true,
keyText: 'F5 | Ctrl+Enter',
enabledStore: derived(
[editorStore, editorStatusStore],
([editor, status]) => editor != null && !(status as any).busy
),
onClick: () => (get(editorStore) as any).execute(),
});
registerCommand({
id: idPrefix + '.kill',
category,
name: 'Kill',
icon: 'icon close',
toolbar: true,
enabledStore: derived(
[editorStore, editorStatusStore],
([query, status]) => query != null && status && (status as any).isConnected
),
onClick: () => (get(editorStore) as any).kill(),
});
}
if (toggleComment) {
registerCommand({
id: idPrefix + '.toggleComment',
category,
name: 'Toggle comment',
keyText: 'Ctrl+/',
disableHandleKeyText: 'Ctrl+/',
enabledStore: derived(editorStore, query => query != null),
onClick: () => (get(editorStore) as any).toggleComment(),
});
}
if (findReplace) {
registerCommand({
id: idPrefix + '.find',
category,
name: 'Find',
keyText: 'Ctrl+F',
enabledStore: derived(editorStore, query => query != null),
onClick: () => (get(editorStore) as any).find(),
});
registerCommand({
id: idPrefix + '.replace',
category,
keyText: 'Ctrl+H',
name: 'Replace',
enabledStore: derived(editorStore, query => query != null),
onClick: () => (get(editorStore) as any).replace(),
});
}
}

View File

@@ -0,0 +1,86 @@
<script lang="ts">
import _ from 'lodash';
import { compact } from 'lodash';
import { onMount } from 'svelte';
interface TableColumn {
fieldName: string;
header: string;
component?: any;
getProps?: any;
formatter?: any;
}
export let columns: TableColumn[];
export let rows;
export let focusOnCreate = false;
export let selectable = false;
export let selectedIndex = 0;
$: columnList = _.compact(_.flatten(columns));
let domTable;
onMount(() => {
if (focusOnCreate) domTable.focus();
});
</script>
<table bind:this={domTable} class:selectable>
<thead>
<tr>
{#each columnList as col}
<td>{col.header}</td>
{/each}
</tr>
</thead>
<tbody>
{#each rows as row, index}
<tr
class:selected={selectable && selectedIndex == index}
on:click={() => {
if (selectable) {
selectedIndex = index;
domTable.focus();
}
}}
>
{#each columnList as col}
{#if col.component}
<svelte:component this={col.component} {...col.getProps(row)} />
{:else if col.formatter}
{col.formatter(row)}
{:else}
{row[col.fieldName]}
{/if}
{/each}
</tr>
{/each}
</tbody>
</table>
<style>
table {
border-collapse: collapse;
width: 100%;
}
table.selectable {
user-select: none;
}
tbody tr {
background: var(--theme-bg-0);
}
tbody tr.selected {
background: var(--theme-bg-selected);
}
thead td {
border: 1px solid var(--theme-border);
background-color: var(--theme-bg-1);
padding: 5px;
}
tbody td {
border: 1px solid var(--theme-border);
padding: 5px;
}
</style>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import getElectron from '../../utility/getElectron';
const electron = getElectron();
export let row;
</script>
<a
href="#"
on:click={() => {
const file = electron.remote.dialog.showSaveDialogSync(electron.remote.getCurrentWindow(), {});
if (file) {
const fs = window.require('fs');
fs.copyFile(row.path, file, () => {});
}
}}
>
save
</a>

View File

@@ -0,0 +1,10 @@
<script lang="ts">
import resolveApi from '../../utility/resolveApi';
export let runnerId;
export let row;
</script>
<a href={`${resolveApi()}/runners/data/${runnerId}/${row.name}`} target="_blank" rel="noopener noreferrer">
download
</a>

View File

@@ -0,0 +1,81 @@
<script lang="ts">
import ErrorInfo from '../../elements/ErrorInfo.svelte';
import TableControl from '../../elements/TableControl.svelte';
import axiosInstance from '../../utility/axiosInstance';
import formatFileSize from '../../utility/formatFileSize';
import getElectron from '../../utility/getElectron';
import socket from '../../utility/socket';
import useEffect from '../../utility/useEffect';
import CopyLink from './CopyLink.svelte';
import DownloadLink from './DownloadLink.svelte';
import ShowLink from './ShowLink.svelte';
export let runnerId;
export let executeNumber;
let files = [];
$: if (executeNumber >= 0) files = [];
$: effect = useEffect(() => registerRunnerDone(runnerId));
const electron = getElectron();
function registerRunnerDone(rid) {
if (rid) {
socket.on(`runner-done-${rid}`, handleRunnerDone);
return () => {
socket.off(`runner-done-${rid}`, handleRunnerDone);
};
} else {
return () => {};
}
}
$: $effect;
const handleRunnerDone = async () => {
const resp = await axiosInstance.get(`runners/files?runid=${runnerId}`);
files = resp.data;
};
</script>
{#if !files || files.length == 0}
<ErrorInfo message="No output files" icon="img alert" />
{/if}
<TableControl
rows={files}
columns={[
{ fieldName: 'name', header: 'Name' },
{ fieldName: 'size', header: 'Size', formatter: row => formatFileSize(row.size) },
!electron && {
fieldName: 'download',
header: 'Download',
component: DownloadLink,
getProps: row => ({
row,
runnerId,
}),
},
!electron && {
fieldName: 'copy',
header: 'Copy',
component: CopyLink,
getProps: row => ({
row,
runnerId,
}),
},
!electron && {
fieldName: 'show',
header: 'Show',
component: ShowLink,
getProps: row => ({
row,
runnerId,
}),
},
]}
/>

View File

@@ -0,0 +1,15 @@
<script lang="ts">
import getElectron from '../../utility/getElectron';
const electron = getElectron();
export let row;
</script>
<a
href="#"
on:click={() => {
electron.remote.shell.showItemInFolder(row.path);
}}
>
show
</a>

View File

@@ -0,0 +1 @@
export { default } from './RunnerOutputFiles.svelte';

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
import WidgetTitle from '../widgets/WidgetTitle.svelte';
import RunnerOutputFiles from './RunnerOutputFiles';
import SocketMessageView from './SocketMessageView.svelte';
export let runnerId;
export let executeNumber;
</script>
<HorizontalSplitter>
<div class="container">
<WidgetTitle>Messages</WidgetTitle>
<SocketMessageView eventName={runnerId ? `runner-info-${runnerId}` : null} {executeNumber} />
</div>
<div class="container">
<WidgetTitle>Output files</WidgetTitle>
<RunnerOutputFiles {runnerId} {executeNumber} />
</div>
</HorizontalSplitter>
<style>
div {
flex: 1;
display: flex;
flex-direction: column;
}
</style>

View File

@@ -5,40 +5,6 @@
);
const currentQueryStatus = memberStore(currentQuery, query => query?.getStatus() || nullStore);
registerCommand({
id: 'query.execute',
category: 'Query',
name: 'Execute',
icon: 'icon run',
toolbar: true,
keyText: 'F5 | Ctrl+Enter',
enabledStore: derived(
[currentQuery, currentQueryStatus],
([query, status]) => query != null && !(status as any).busy
),
onClick: () => get(currentQuery).execute(),
});
registerCommand({
id: 'query.kill',
category: 'Query',
name: 'Kill',
icon: 'icon close',
toolbar: true,
enabledStore: derived(
[currentQuery, currentQueryStatus],
([query, status]) => query != null && status && (status as any).isConnected
),
onClick: () => get(currentQuery).kill(),
});
registerCommand({
id: 'query.toggleComment',
category: 'Query',
name: 'Toggle comment',
keyText: 'Ctrl+/',
disableHandleKeyText: 'Ctrl+/',
enabledStore: derived(currentQuery, query => query != null),
onClick: () => get(currentQuery).toggleComment(),
});
registerCommand({
id: 'query.formatCode',
category: 'Query',
@@ -46,29 +12,18 @@
enabledStore: derived(currentQuery, query => query != null),
onClick: () => get(currentQuery).formatCode(),
});
registerCommand({
id: 'query.find',
category: 'Query',
name: 'Find',
keyText: 'Ctrl+F',
enabledStore: derived(currentQuery, query => query != null),
onClick: () => get(currentQuery).find(),
});
registerCommand({
id: 'query.replace',
category: 'Query',
keyText: 'Ctrl+H',
name: 'Replace',
enabledStore: derived(currentQuery, query => query != null),
onClick: () => get(currentQuery).replace(),
});
registerSaveCommands({
registerFileCommands({
idPrefix: 'query',
category: 'Query',
editorStore: currentQuery,
editorStatusStore: currentQueryStatus,
folder: 'sql',
format: 'text',
fileExtension: 'sql',
execute: true,
toggleComment:true,
findReplace:true
});
</script>
@@ -93,7 +48,7 @@
import memberStore from '../utility/memberStore';
import useEffect from '../utility/useEffect';
import ResultTabs from '../query/ResultTabs.svelte';
import saveTabFile, { registerSaveCommands, saveTabEnabledStore } from '../utility/saveTabFile';
import { registerFileCommands } from '../commands/stdCommands';
export let tabid;
export let conid;

View File

@@ -0,0 +1,6 @@
export default function formatFileSize(size) {
if (size > 1000000000) return `${Math.round(size / 10000000000) * 10} GB`;
if (size > 1000000) return `${Math.round(size / 10000000) * 10} MB`;
if (size > 1000) return `${Math.round(size / 10000) * 10} KB`;
return `${size} bytes`;
}

View File

@@ -52,24 +52,3 @@ export default function saveTabFile(editorStore, saveAs, folder, format, fileExt
});
}
}
export function registerSaveCommands({ idPrefix, category, editorStore, folder, format, fileExtension }) {
registerCommand({
id: idPrefix + '.save',
category,
name: 'Save',
keyText: 'Ctrl+S',
icon: 'icon save',
toolbar: true,
enabledStore: saveTabEnabledStore(editorStore),
onClick: () => saveTabFile(editorStore, false, folder, format, fileExtension),
});
registerCommand({
id: idPrefix + '.saveAs',
category,
name: 'Save As',
keyText: 'Ctrl+Shift+S',
enabledStore: saveTabEnabledStore(editorStore),
onClick: () => saveTabFile(editorStore, true, folder, format, fileExtension),
});
}

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import WidgetTitle from './WidgetTitle.svelte';
export let title;
export let name;
export let height = null;
@@ -7,21 +9,8 @@
let visible = !collapsed;
</script>
<div class="title" on:click={() => (visible = !visible)}>{title}</div>
<WidgetTitle on:click={() => (visible = !visible)}>{title}</WidgetTitle>
{#if visible}
<slot />
{/if}
<style>
.title {
padding: 5px;
font-weight: bold;
text-transform: uppercase;
background-color: var(--theme-bg-2);
border: 1px solid var(--theme-border);
}
.title:hover {
background-color: var(--theme-bg-3);
}
</style>

View File

@@ -0,0 +1,16 @@
<div on:click>
<slot />
</div>
<style>
div {
padding: 5px;
font-weight: bold;
text-transform: uppercase;
background-color: var(--theme-bg-2);
border: 1px solid var(--theme-border);
}
div:hover {
background-color: var(--theme-bg-3);
}
</style>