refactor(tabs): use camelCase

store active tab in sqlite
This commit is contained in:
Yann Amsellem
2025-01-14 17:30:28 +01:00
parent 8b1620fd3b
commit 91accda8d0
4 changed files with 67 additions and 51 deletions

View File

@@ -36,7 +36,7 @@
value = update.state.doc.toString();
}
}),
placeholder('Enter a query...')
placeholder('Type your query...')
]
});

View File

@@ -3,5 +3,6 @@ CREATE TABLE IF NOT EXISTS tabs (
name TEXT NOT NULL,
contents TEXT NOT NULL,
query_id INTEGER,
tab_index INTEGER NOT NULL
tab_index INTEGER NOT NULL,
active BOOL NOT NULL DEFAULT FALSE
);

View File

@@ -8,27 +8,37 @@ export interface Tab {
}
export interface TabRepository {
get(): Promise<Tab[]>;
set(tabs: Tab[]): Promise<void>;
get(): Promise<[tabs: Tab[], activeIndex: number]>;
save(tabs: Tab[], activeIndex: number): Promise<void>;
}
class SQLiteTabRepository implements TabRepository {
constructor(private db: Database) {}
async get(): Promise<Tab[]> {
async get(): Promise<[tabs: Tab[], activeIndex: number]> {
const rows = await this.db.exec('select * from tabs order by tab_index');
return rows.map(row_to_tab);
let index = rows.findIndex((r) => r.active);
return [rows.map(row_to_tab), Math.max(0, index)];
}
async set(tabs: Tab[]): Promise<void> {
async save(tabs: Tab[], activeIndex: number): Promise<void> {
const rows = tabs.map((tab, tab_index) => ({ ...tab, tab_index }));
await this.db.exec(
`DELETE FROM tabs;
INSERT INTO tabs (id, name, contents, query_id, tab_index)
VALUES ${Array.from({ length: rows.length }).fill('(?,?,?,?,?)').join(',\n')}
INSERT INTO tabs (id, name, contents, query_id, tab_index, active)
VALUES ${Array.from({ length: rows.length }).fill('(?,?,?,?,?, ?)').join(',\n')}
`,
rows.map((r) => [r.id, r.name, r.contents, r.query_id ?? null, r.tab_index]).flat()
rows
.map((r) => [
r.id,
r.name,
r.contents,
r.query_id ?? null,
r.tab_index,
r.tab_index === activeIndex
])
.flat()
);
}
}

View File

@@ -26,7 +26,7 @@
let loading = $state(false);
async function handleExec() {
const query = current_tab.contents;
const query = currentTab.contents;
if (loading || !query) {
return;
}
@@ -65,10 +65,10 @@
}
function handleHistoryClick(entry: HistoryEntry) {
if (current_tab.contents) {
selected_tab_index =
if (currentTab.contents) {
selectedTabIndex =
tabs.push({ id: crypto.randomUUID(), contents: entry.content, name: 'Untitled' }) - 1;
} else tabs[selected_tab_index] = { ...current_tab, contents: entry.content };
} else tabs[selectedTabIndex] = { ...currentTab, contents: entry.content };
if (is_mobile) open_drawer = false;
}
@@ -90,9 +90,9 @@
async function handleCreateQuery({
name
}: Parameters<NonNullable<ComponentProps<typeof SaveQueryModal>['onCreate']>>['0']) {
const q = await query_repository.create(name, current_tab.contents);
const q = await query_repository.create(name, currentTab.contents);
queries = queries.concat(q);
tabs[selected_tab_index] = { ...current_tab, name, query_id: q.id };
tabs[selectedTabIndex] = { ...currentTab, name, query_id: q.id };
}
async function handleDeleteQuery(query: Query) {
@@ -103,8 +103,8 @@
function handleQueryOpen(query: Query) {
const index = tabs.findIndex((t) => t.query_id === query.id);
if (index === -1) {
if (current_tab.contents) {
selected_tab_index =
if (currentTab.contents) {
selectedTabIndex =
tabs.push({
id: crypto.randomUUID(),
contents: query.sql,
@@ -112,13 +112,13 @@
query_id: query.id
}) - 1;
} else
tabs[selected_tab_index] = {
...current_tab,
tabs[selectedTabIndex] = {
...currentTab,
contents: query.sql,
name: query.name,
query_id: query.id
};
} else selected_tab_index = index;
} else selectedTabIndex = index;
if (is_mobile) open_drawer = false;
}
@@ -135,7 +135,7 @@
}
async function handleSaveQuery() {
const { contents, query_id } = current_tab;
const { contents, query_id: query_id } = currentTab;
if (contents && !query_id) {
return save_query_modal?.show();
}
@@ -158,43 +158,42 @@
if (!is_mobile) open_drawer = false;
});
let tabs = $state<Tab[]>([{ id: crypto.randomUUID(), contents: '', name: 'Untitled' }]);
let tabs = $state<Tab[]>([]);
$effect(
() =>
void tab_repository.get().then((t) => {
if (t.length) tabs = t;
void tab_repository.get().then(([t, active]) => {
if (t.length) (tabs = t), (selectedTabIndex = active);
else tabs.push({ id: crypto.randomUUID(), contents: '', name: 'Untitled' });
})
);
const set_tabs = debounce((tabs: Tab[]) => tab_repository.set(tabs), 300);
const saveTabs = debounce(
(tabs: Tab[], activeIndex: number) => tab_repository.save(tabs, activeIndex),
2_000
);
let selected_tab_index = $state(0);
const current_tab = $derived(tabs[selected_tab_index]);
const can_save = $derived.by(() => {
if (current_tab.query_id) {
const query = queries.find((q) => q.id === current_tab.query_id);
if (!query) return true;
return query.sql !== current_tab.contents;
let selectedTabIndex = $state(0);
const currentTab = $derived(tabs[selectedTabIndex]);
const canSave = $derived.by(() => {
if (!tabs.length) return false;
if (currentTab.query_id) {
const query = queries.find((q) => q.id === currentTab.query_id);
return query?.sql !== currentTab.contents;
}
return !!current_tab.contents;
return !!currentTab.contents;
});
function addNewTab() {
const next_index = tabs.length;
tabs.push({ id: crypto.randomUUID(), name: 'Untitled', contents: '' });
selected_tab_index = next_index;
selectedTabIndex = tabs.push({ id: crypto.randomUUID(), name: 'Untitled', contents: '' }) - 1;
}
function closeTab(index: number) {
tabs.splice(index, 1);
selected_tab_index = Math.max(0, selected_tab_index - 1);
selectedTabIndex = Math.max(0, selectedTabIndex - 1);
}
$effect(() => {
$state.snapshot(tabs);
set_tabs(tabs).catch(console.error);
});
$effect(() => void saveTabs($state.snapshot(tabs), selectedTabIndex).catch(console.error));
</script>
<svelte:window onkeydown={handleKeyDown} bind:innerWidth={screen_width} />
@@ -245,10 +244,10 @@
{#each tabs as tab, i}
<TabComponent
close-hidden={tabs.length === 1}
active={i === selected_tab_index}
active={i === selectedTabIndex}
label={tab.name}
onClose={() => closeTab(i)}
onSelect={() => (selected_tab_index = i)}
onSelect={() => (selectedTabIndex = i)}
/>
{/each}
<button
@@ -261,7 +260,7 @@
</button>
</div>
<div class="workspace-actions">
<button class="action" onclick={handleSaveQuery} disabled={!can_save}>
<button class="action" onclick={handleSaveQuery} disabled={!canSave}>
<Save size="12" />
</button>
<button class="action" onclick={handleExec} disabled={loading}>
@@ -270,7 +269,7 @@
</div>
</nav>
{#each tabs as tab, i (tab.id)}
<div style:display={selected_tab_index == i ? 'block' : 'none'}>
<div style:display={selectedTabIndex == i ? 'block' : 'none'}>
<Editor bind:value={tab.contents} {tables} />
</div>
{/each}
@@ -318,8 +317,14 @@
}
& button.action {
height: 100%;
aspect-ratio: 1;
background-color: transparent;
border-radius: 0;
&:is(:hover, :focus-within):not(:disabled) {
background: hsl(0deg 0% 10%);
}
}
& ~ div {
@@ -330,9 +335,13 @@
.add-new {
height: calc(100% - 8px);
aspect-ratio: 1;
padding: 4px;
display: flex;
place-items: center;
justify-content: center;
padding: 0;
margin-left: 4px;
border-radius: 4px;
background-color: transparent;
&:hover {
cursor: pointer;
@@ -350,13 +359,9 @@
border: none;
font-size: 10px;
font-weight: 500;
background-color: hsl(0deg 0% 9%);
padding: 4px 2;
border-radius: 3px;
&:is(:hover, :focus-within):not(:disabled) {
cursor: pointer;
background: hsl(0deg 0% 10%);
}
}