diff --git a/packages/web/public/bulma.css b/packages/web/public/bulma.css
new file mode 100644
index 00000000..6492cd0c
--- /dev/null
+++ b/packages/web/public/bulma.css
@@ -0,0 +1,419 @@
+.m-0 {
+ margin: 0 !important;
+}
+
+.mt-0 {
+ margin-top: 0 !important;
+}
+
+.mr-0 {
+ margin-right: 0 !important;
+}
+
+.mb-0 {
+ margin-bottom: 0 !important;
+}
+
+.ml-0 {
+ margin-left: 0 !important;
+}
+
+.mx-0 {
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+}
+
+.my-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+}
+
+.m-1 {
+ margin: 0.25rem !important;
+}
+
+.mt-1 {
+ margin-top: 0.25rem !important;
+}
+
+.mr-1 {
+ margin-right: 0.25rem !important;
+}
+
+.mb-1 {
+ margin-bottom: 0.25rem !important;
+}
+
+.ml-1 {
+ margin-left: 0.25rem !important;
+}
+
+.mx-1 {
+ margin-left: 0.25rem !important;
+ margin-right: 0.25rem !important;
+}
+
+.my-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+}
+
+.m-2 {
+ margin: 0.5rem !important;
+}
+
+.mt-2 {
+ margin-top: 0.5rem !important;
+}
+
+.mr-2 {
+ margin-right: 0.5rem !important;
+}
+
+.mb-2 {
+ margin-bottom: 0.5rem !important;
+}
+
+.ml-2 {
+ margin-left: 0.5rem !important;
+}
+
+.mx-2 {
+ margin-left: 0.5rem !important;
+ margin-right: 0.5rem !important;
+}
+
+.my-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+}
+
+.m-3 {
+ margin: 0.75rem !important;
+}
+
+.mt-3 {
+ margin-top: 0.75rem !important;
+}
+
+.mr-3 {
+ margin-right: 0.75rem !important;
+}
+
+.mb-3 {
+ margin-bottom: 0.75rem !important;
+}
+
+.ml-3 {
+ margin-left: 0.75rem !important;
+}
+
+.mx-3 {
+ margin-left: 0.75rem !important;
+ margin-right: 0.75rem !important;
+}
+
+.my-3 {
+ margin-top: 0.75rem !important;
+ margin-bottom: 0.75rem !important;
+}
+
+.m-4 {
+ margin: 1rem !important;
+}
+
+.mt-4 {
+ margin-top: 1rem !important;
+}
+
+.mr-4 {
+ margin-right: 1rem !important;
+}
+
+.mb-4 {
+ margin-bottom: 1rem !important;
+}
+
+.ml-4 {
+ margin-left: 1rem !important;
+}
+
+.mx-4 {
+ margin-left: 1rem !important;
+ margin-right: 1rem !important;
+}
+
+.my-4 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+}
+
+.m-5 {
+ margin: 1.5rem !important;
+}
+
+.mt-5 {
+ margin-top: 1.5rem !important;
+}
+
+.mr-5 {
+ margin-right: 1.5rem !important;
+}
+
+.mb-5 {
+ margin-bottom: 1.5rem !important;
+}
+
+.ml-5 {
+ margin-left: 1.5rem !important;
+}
+
+.mx-5 {
+ margin-left: 1.5rem !important;
+ margin-right: 1.5rem !important;
+}
+
+.my-5 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+}
+
+.m-6 {
+ margin: 3rem !important;
+}
+
+.mt-6 {
+ margin-top: 3rem !important;
+}
+
+.mr-6 {
+ margin-right: 3rem !important;
+}
+
+.mb-6 {
+ margin-bottom: 3rem !important;
+}
+
+.ml-6 {
+ margin-left: 3rem !important;
+}
+
+.mx-6 {
+ margin-left: 3rem !important;
+ margin-right: 3rem !important;
+}
+
+.my-6 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+}
+
+.p-0 {
+ padding: 0 !important;
+}
+
+.pt-0 {
+ padding-top: 0 !important;
+}
+
+.pr-0 {
+ padding-right: 0 !important;
+}
+
+.pb-0 {
+ padding-bottom: 0 !important;
+}
+
+.pl-0 {
+ padding-left: 0 !important;
+}
+
+.px-0 {
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+.py-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+}
+
+.p-1 {
+ padding: 0.25rem !important;
+}
+
+.pt-1 {
+ padding-top: 0.25rem !important;
+}
+
+.pr-1 {
+ padding-right: 0.25rem !important;
+}
+
+.pb-1 {
+ padding-bottom: 0.25rem !important;
+}
+
+.pl-1 {
+ padding-left: 0.25rem !important;
+}
+
+.px-1 {
+ padding-left: 0.25rem !important;
+ padding-right: 0.25rem !important;
+}
+
+.py-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+}
+
+.p-2 {
+ padding: 0.5rem !important;
+}
+
+.pt-2 {
+ padding-top: 0.5rem !important;
+}
+
+.pr-2 {
+ padding-right: 0.5rem !important;
+}
+
+.pb-2 {
+ padding-bottom: 0.5rem !important;
+}
+
+.pl-2 {
+ padding-left: 0.5rem !important;
+}
+
+.px-2 {
+ padding-left: 0.5rem !important;
+ padding-right: 0.5rem !important;
+}
+
+.py-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+}
+
+.p-3 {
+ padding: 0.75rem !important;
+}
+
+.pt-3 {
+ padding-top: 0.75rem !important;
+}
+
+.pr-3 {
+ padding-right: 0.75rem !important;
+}
+
+.pb-3 {
+ padding-bottom: 0.75rem !important;
+}
+
+.pl-3 {
+ padding-left: 0.75rem !important;
+}
+
+.px-3 {
+ padding-left: 0.75rem !important;
+ padding-right: 0.75rem !important;
+}
+
+.py-3 {
+ padding-top: 0.75rem !important;
+ padding-bottom: 0.75rem !important;
+}
+
+.p-4 {
+ padding: 1rem !important;
+}
+
+.pt-4 {
+ padding-top: 1rem !important;
+}
+
+.pr-4 {
+ padding-right: 1rem !important;
+}
+
+.pb-4 {
+ padding-bottom: 1rem !important;
+}
+
+.pl-4 {
+ padding-left: 1rem !important;
+}
+
+.px-4 {
+ padding-left: 1rem !important;
+ padding-right: 1rem !important;
+}
+
+.py-4 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+}
+
+.p-5 {
+ padding: 1.5rem !important;
+}
+
+.pt-5 {
+ padding-top: 1.5rem !important;
+}
+
+.pr-5 {
+ padding-right: 1.5rem !important;
+}
+
+.pb-5 {
+ padding-bottom: 1.5rem !important;
+}
+
+.pl-5 {
+ padding-left: 1.5rem !important;
+}
+
+.px-5 {
+ padding-left: 1.5rem !important;
+ padding-right: 1.5rem !important;
+}
+
+.py-5 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+}
+
+.p-6 {
+ padding: 3rem !important;
+}
+
+.pt-6 {
+ padding-top: 3rem !important;
+}
+
+.pr-6 {
+ padding-right: 3rem !important;
+}
+
+.pb-6 {
+ padding-bottom: 3rem !important;
+}
+
+.pl-6 {
+ padding-left: 3rem !important;
+}
+
+.px-6 {
+ padding-left: 3rem !important;
+ padding-right: 3rem !important;
+}
+
+.py-6 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+}
diff --git a/packages/web/public/index.html b/packages/web/public/index.html
index a2cb44ac..8dd8402c 100644
--- a/packages/web/public/index.html
+++ b/packages/web/public/index.html
@@ -11,6 +11,7 @@
+
diff --git a/packages/web/src/Screen.svelte b/packages/web/src/Screen.svelte
index db77a107..21a3ff5c 100644
--- a/packages/web/src/Screen.svelte
+++ b/packages/web/src/Screen.svelte
@@ -1,12 +1,13 @@
-
+
@@ -22,6 +23,11 @@ import TabContent from './TabContent.svelte';
+ {#if $visibleCommandPalette}
+
+
+
+ {/if}
diff --git a/packages/web/src/commands/CommandPalette.svelte b/packages/web/src/commands/CommandPalette.svelte
new file mode 100644
index 00000000..a5c94634
--- /dev/null
+++ b/packages/web/src/commands/CommandPalette.svelte
@@ -0,0 +1,84 @@
+
+
+
+
+
($visibleCommandPalette = false)}>
+
+
+
+ {#each filteredItems as command, index}
+
handleCommand(command)}>
+ {command.text}
+
+ {/each}
+
+
+
diff --git a/packages/web/src/commands/registerCommand.ts b/packages/web/src/commands/registerCommand.ts
new file mode 100644
index 00000000..90cca00f
--- /dev/null
+++ b/packages/web/src/commands/registerCommand.ts
@@ -0,0 +1,39 @@
+import { commands } from '../stores';
+
+export interface SubCommand {
+ text: string;
+ onClick: Function;
+}
+
+export interface GlobalCommand {
+ id: string;
+ text: string;
+ getSubCommands?: () => SubCommand[];
+ onClick?: Function;
+ enabledStore?: any;
+ icon?: string;
+ toolbar?: boolean;
+ enabled?: boolean;
+}
+
+export default function registerCommand(command: GlobalCommand) {
+ const { enabledStore } = command;
+ commands.update(x => ({
+ ...x,
+ [command.id]: {
+ ...command,
+ enabled: !enabledStore,
+ },
+ }));
+ if (enabledStore) {
+ enabledStore.subscribe(value => {
+ commands.update(x => ({
+ ...x,
+ [command.id]: {
+ ...x[command.id],
+ enabled: value,
+ },
+ }));
+ });
+ }
+}
diff --git a/packages/web/src/commands/runCommand.ts b/packages/web/src/commands/runCommand.ts
new file mode 100644
index 00000000..26251c21
--- /dev/null
+++ b/packages/web/src/commands/runCommand.ts
@@ -0,0 +1,9 @@
+import { get } from 'svelte/store';
+import { commands } from '../stores';
+import { GlobalCommand } from './registerCommand';
+
+export default function runCommand(commandId: string) {
+ const commandsValue = get(commands);
+ const command: GlobalCommand = commandsValue[commandId];
+ if (command.enabled) command.onClick();
+}
diff --git a/packages/web/src/commands/stdCommands.ts b/packages/web/src/commands/stdCommands.ts
new file mode 100644
index 00000000..a145d8f9
--- /dev/null
+++ b/packages/web/src/commands/stdCommands.ts
@@ -0,0 +1,17 @@
+import { currentTheme } from '../stores';
+import registerCommand from './registerCommand';
+
+registerCommand({
+ id: 'theme.changeTheme',
+ text: 'Theme: Change',
+ getSubCommands: () => [
+ {
+ text: 'Light',
+ onClick: () => currentTheme.set('theme-light'),
+ },
+ {
+ text: 'Dark',
+ onClick: () => currentTheme.set('theme-dark'),
+ },
+ ],
+});
diff --git a/packages/web/src/main.ts b/packages/web/src/main.ts
index 4d5a7685..9f4a3a23 100644
--- a/packages/web/src/main.ts
+++ b/packages/web/src/main.ts
@@ -1,5 +1,6 @@
import App from './App.svelte';
import './utility/connectionsPinger';
+import './commands/stdCommands';
const app = new App({
target: document.body,
diff --git a/packages/web/src/stores.ts b/packages/web/src/stores.ts
index 15f9e542..895f5081 100644
--- a/packages/web/src/stores.ts
+++ b/packages/web/src/stores.ts
@@ -25,5 +25,8 @@ export const openedConnections = writable([]);
export const currentDatabase = writable(null);
export const openedTabs = writableWithStorage
([], 'openedTabs');
export const extensions = writable(null);
+export const visibleCommandPalette = writable(false);
+export const commands = writable({});
+export const currentTheme = writableWithStorage('theme-light', 'currentTheme');
// export const leftPanelWidth = writable(300);
diff --git a/packages/web/src/utility/clickOutside.ts b/packages/web/src/utility/clickOutside.ts
new file mode 100644
index 00000000..0e694f06
--- /dev/null
+++ b/packages/web/src/utility/clickOutside.ts
@@ -0,0 +1,15 @@
+export function clickOutside(node) {
+ const handleClick = event => {
+ if (node && !node.contains(event.target) && !event.defaultPrevented) {
+ node.dispatchEvent(new CustomEvent('clickOutside', node));
+ }
+ };
+
+ document.addEventListener('click', handleClick, true);
+
+ return {
+ destroy() {
+ document.removeEventListener('click', handleClick, true);
+ },
+ };
+}
diff --git a/packages/web/src/utility/keycodes.js b/packages/web/src/utility/keycodes.js
new file mode 100644
index 00000000..4c02d1af
--- /dev/null
+++ b/packages/web/src/utility/keycodes.js
@@ -0,0 +1,99 @@
+export default {
+ backspace: 8,
+ tab: 9,
+ enter: 13,
+ shift: 16,
+ ctrl: 17,
+ alt: 18,
+ pauseBreak: 19,
+ capsLock: 20,
+ escape: 27,
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ leftArrow: 37,
+ upArrow: 38,
+ rightArrow: 39,
+ downArrow: 40,
+ insert: 45,
+ delete: 46,
+ n0: 48,
+ n1: 49,
+ n2: 50,
+ n3: 51,
+ n4: 52,
+ n5: 53,
+ n6: 54,
+ n7: 55,
+ n8: 56,
+ n9: 57,
+ a: 65,
+ b: 66,
+ c: 67,
+ d: 68,
+ e: 69,
+ f: 70,
+ g: 71,
+ h: 72,
+ i: 73,
+ j: 74,
+ k: 75,
+ l: 76,
+ m: 77,
+ n: 78,
+ o: 79,
+ p: 80,
+ q: 81,
+ r: 82,
+ s: 83,
+ t: 84,
+ u: 85,
+ v: 86,
+ w: 87,
+ x: 88,
+ y: 89,
+ z: 90,
+ leftWindowKey: 91,
+ rightWindowKey: 92,
+ selectKey: 93,
+ numPad0: 96,
+ numPad1: 97,
+ numPad2: 98,
+ numPad3: 99,
+ numPad4: 100,
+ numPad5: 101,
+ numPad6: 102,
+ numPad7: 103,
+ numPad8: 104,
+ numPad9: 105,
+ multiply: 106,
+ add: 107,
+ subtract: 109,
+ decimalPoint: 110,
+ divide: 111,
+ f1: 112,
+ f2: 113,
+ f3: 114,
+ f4: 115,
+ f5: 116,
+ f6: 117,
+ f7: 118,
+ f8: 119,
+ f9: 120,
+ f10: 121,
+ f12: 123,
+ numLock: 144,
+ scrollLock: 145,
+ semiColon: 186,
+ equalSign: 187,
+ comma: 188,
+ dash: 189,
+ period: 190,
+ forwardSlash: 191,
+ graveAccent: 192,
+ openBracket: 219,
+ backSlash: 220,
+ closeBracket: 221,
+ singleQuote: 222,
+};
diff --git a/packages/web/src/widgets/WidgetIconPanel.svelte b/packages/web/src/widgets/WidgetIconPanel.svelte
index 19415f4a..0a6be95a 100644
--- a/packages/web/src/widgets/WidgetIconPanel.svelte
+++ b/packages/web/src/widgets/WidgetIconPanel.svelte
@@ -1,6 +1,6 @@
+ ($visibleCommandPalette = true)}>
+
+
{#each widgets as item}
handleChangeWidget(item.name)}>
diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json
index 265a2590..e7506e66 100644
--- a/packages/web/tsconfig.json
+++ b/packages/web/tsconfig.json
@@ -5,6 +5,7 @@
"exclude": ["node_modules/*", "public/*"],
"compilerOptions": {
+ "moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,