From 1f8f5985f37fda97f9d4e6beb82ea08a0793e2e7 Mon Sep 17 00:00:00 2001 From: didierfranc Date: Mon, 13 Jan 2025 15:32:03 +0100 Subject: [PATCH 1/9] feat: base tabs ui --- src/routes/+page.svelte | 44 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 245541d..4129c7a 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -122,6 +122,13 @@ $effect(() => { if (!is_mobile) open_drawer = false; }); + + let tabs = [ + { id: '', content: 'select 1', name: 'new' }, + { id: '', content: 'select 2', name: 'query1' }, + { id: '', content: 'select 3', name: 'query2' } + ]; + let selected_tab = $state(0); @@ -169,7 +176,18 @@ {/if} + + {#each tabs as tab, i} +
(selected_tab = i)} + > + {tab.name} +
+ {/each} +
+
-
- -
+ {#each tabs as tab, i} +
+ +
+ {/each} {/snippet} {#snippet b()} @@ -200,6 +220,8 @@ & > .left { flex: 1; + align-items: center; + height: 100%; } & > .right { @@ -208,7 +230,6 @@ } & button { - height: 100%; background-color: transparent; border-radius: 0; } @@ -218,6 +239,21 @@ } } + .tab { + font-size: 11px; + border-right: 1px solid hsl(0deg 0% 20%); + padding: 0 10px 0 10px; + display: inline-flex; + align-items: center; + height: 100%; + color: hsl(0deg 0% 70%); + } + + .active { + background-color: hsl(0deg 0% 12%); + color: white; + } + button { appearance: none; outline: none; From 4eb0cdb2598ed147bfd37d9fc4fa6ddf596af9f6 Mon Sep 17 00:00:00 2001 From: Yann Amsellem Date: Mon, 13 Jan 2025 17:21:19 +0100 Subject: [PATCH 2/9] feat(tabs): add new tab and close tab --- src/lib/components/Editor/Editor.svelte | 16 +- src/lib/icons/XMark.svelte | 18 +++ src/routes/+page.svelte | 188 ++++++++++++++++++------ 3 files changed, 160 insertions(+), 62 deletions(-) create mode 100644 src/lib/icons/XMark.svelte diff --git a/src/lib/components/Editor/Editor.svelte b/src/lib/components/Editor/Editor.svelte index d7f7134..7083955 100644 --- a/src/lib/components/Editor/Editor.svelte +++ b/src/lib/components/Editor/Editor.svelte @@ -2,7 +2,7 @@ import type { Table } from '$lib/olap-engine'; import { sql } from '@codemirror/lang-sql'; import { Compartment, EditorState } from '@codemirror/state'; - import { EditorView, keymap, placeholder } from '@codemirror/view'; + import { EditorView, placeholder } from '@codemirror/view'; import { untrack } from 'svelte'; import './codemirror.css'; import { default_extensions, default_keymaps } from './extensions'; @@ -11,11 +11,10 @@ type Props = { value: string; - onExec?: () => unknown; tables?: Table[]; }; - let { value = $bindable(''), onExec, tables = [] }: Props = $props(); + let { value = $bindable(''), tables = [] }: Props = $props(); let container: HTMLDivElement; let editor_view: EditorView; @@ -37,16 +36,7 @@ value = update.state.doc.toString(); } }), - placeholder('Enter a query...'), - keymap.of([ - { - key: 'Mod-Enter', - run: () => { - onExec?.(); - return true; - } - } - ]) + placeholder('Enter a query...') ] }); diff --git a/src/lib/icons/XMark.svelte b/src/lib/icons/XMark.svelte new file mode 100644 index 0000000..18c0cab --- /dev/null +++ b/src/lib/icons/XMark.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 4129c7a..a7e213f 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -9,7 +9,9 @@ import { set_app_context } from '$lib/context'; import Bars3 from '$lib/icons/Bars3.svelte'; import Play from '$lib/icons/Play.svelte'; + import Plus from '$lib/icons/Plus.svelte'; import Save from '$lib/icons/Save.svelte'; + import XMark from '$lib/icons/XMark.svelte'; import type { Table } from '$lib/olap-engine'; import { engine, type OLAPResponse } from '$lib/olap-engine'; import { history_repository, type HistoryEntry } from '$lib/repositories/history'; @@ -23,19 +25,17 @@ let loading = $state(false); async function handleExec() { + const query = current_tab.contents; if (loading || !query) { return; } loading = true; - const query_to_execute = query; - response = await engine.exec(query_to_execute).finally(() => (loading = false)); + response = await engine.exec(query).finally(() => (loading = false)); const last = await history_repository.getLast(); - if (response && last?.content !== query_to_execute) { - await addHistoryEntry(query_to_execute); - } + if (response && last?.content !== query) await addHistoryEntry(query); } let tables = $state.raw([]); @@ -81,6 +81,8 @@ save_query_modal?.show(); } } + + if (event.key === 'Enter' && event.metaKey) handleExec(); } async function handleCreateQuery({ @@ -92,8 +94,7 @@ async function handleDeleteQuery(query: Query) { await query_repository.delete(query.id); - const index = queries.indexOf(query); - queries = queries.slice(0, index).concat(queries.slice(index + 1)); + queries = queries.toSpliced(queries.indexOf(query), 1); } function handleQueryOpen(_query: Query) { @@ -104,12 +105,7 @@ async function handleQueryRename(query: Query) { const updated = await query_repository.update(query); const index = queries.findIndex((query) => query.id === updated.id); - if (index !== -1) { - queries = queries - .slice(0, index) - .concat(updated) - .concat(queries.slice(index + 1)); - } + if (index !== -1) queries = queries.with(index, updated); } const context_menu = new ContextMenuState(); @@ -123,12 +119,27 @@ if (!is_mobile) open_drawer = false; }); - let tabs = [ - { id: '', content: 'select 1', name: 'new' }, - { id: '', content: 'select 2', name: 'query1' }, - { id: '', content: 'select 3', name: 'query2' } - ]; - let selected_tab = $state(0); + interface Tab { + id: string; + contents: string; + name: string; + query_id?: Query['id']; + } + + let tabs = $state([{ id: crypto.randomUUID(), contents: '', name: 'Untitled' }]); + let selected_tab_index = $state(0); + const current_tab = $derived(tabs[selected_tab_index]); + + function addNewTab() { + const next_index = tabs.length; + tabs.push({ id: crypto.randomUUID(), name: 'Untitled', contents: '' }); + selected_tab_index = next_index; + } + + function closeTab(index: number) { + tabs.splice(index, 1); + selected_tab_index = Math.max(0, index - 1); + } @@ -169,35 +180,57 @@ {#snippet a()}
-
- {#each tabs as tab, i} + {#each tabs as tab, i (tab.id)}
@@ -343,6 +343,8 @@ height: 100%; color: hsl(0deg 0% 70%); position: relative; + user-select: none; + -webkit-user-select: none; &:hover { cursor: pointer; From b7e65c73ec69a57b675cbe6339ec9f51c4d19779 Mon Sep 17 00:00:00 2001 From: Yann Amsellem Date: Tue, 14 Jan 2025 13:40:15 +0100 Subject: [PATCH 6/9] chore(ui): create tab component --- src/lib/components/Tab.svelte | 78 ++++++++++++++++++++++++++++++++++ src/routes/+page.svelte | 79 ++++------------------------------- 2 files changed, 86 insertions(+), 71 deletions(-) create mode 100644 src/lib/components/Tab.svelte diff --git a/src/lib/components/Tab.svelte b/src/lib/components/Tab.svelte new file mode 100644 index 0000000..6fa8d02 --- /dev/null +++ b/src/lib/components/Tab.svelte @@ -0,0 +1,78 @@ + + +
{}}> + {label} + + {#if !hide_close} + + {/if} +
+ + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a8c557c..46f9ea6 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -6,12 +6,12 @@ import { SaveQueryModal } from '$lib/components/Queries'; import Result from '$lib/components/Result.svelte'; import SideBar from '$lib/components/SideBar.svelte'; + import TabComponent from '$lib/components/Tab.svelte'; import { set_app_context } from '$lib/context'; import Bars3 from '$lib/icons/Bars3.svelte'; import Play from '$lib/icons/Play.svelte'; import Plus from '$lib/icons/Plus.svelte'; import Save from '$lib/icons/Save.svelte'; - import XMark from '$lib/icons/XMark.svelte'; import type { Table } from '$lib/olap-engine'; import { engine, type OLAPResponse } from '$lib/olap-engine'; import { history_repository, type HistoryEntry } from '$lib/repositories/history'; @@ -234,28 +234,13 @@ {/if} {#each tabs as tab, i} -
(selected_tab_index = i)} - tabindex="0" - onkeyup={() => {}} - > - {tab.name} - - -
+ closeTab(i)} + onSelect={() => (selected_tab_index = i)} + /> {/each}