Skip to content

Commit 050382d

Browse files
committed
feat(ui): queries can now have a title
1 parent 1cc71de commit 050382d

File tree

5 files changed

+160
-66
lines changed

5 files changed

+160
-66
lines changed

src/lib/components/explorer/EntityTree.svelte

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
5050
let dragHoverClass = $derived($mode === 'light' ? 'draghover' : 'draghover-dark');
5151
52-
function toggleExpanded({ id }: { id: string }) {
52+
function toggleExpanded(id: string) {
5353
if (!expandMap.has(id)) {
5454
let expanded = $state(true);
5555
expandMap.set(id, {
@@ -194,7 +194,7 @@
194194
builders={[builder]}
195195
variant="ghost"
196196
class="flex w-full justify-start gap-1"
197-
on:click={() => toggleExpanded({ id })}
197+
on:click={() => toggleExpanded(id)}
198198
ondrop={(e: DropEvent) => handleDropEvent(e, dropProperties)}
199199
ondragenter={handleDragEnter}
200200
ondragleave={handleDragLeave}

src/lib/components/explorer/QueryHistory.svelte

+5-4
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,18 @@
2525
</script>
2626

2727
<div class="flex flex-col gap-2">
28-
{#each queriesStore.streams as stream}
29-
{@const streamInfo = queriesStore.getStreamInfo(stream)}
28+
{#each queriesStore.entries as entry}
29+
{@const streamInfo = queriesStore.getStreamInfo(entry.stream)}
3030
<div class="flex flex-row">
3131
<Label class="flex flex-col">
3232
<span>{streamInfo.id}</span>
33+
<span class="text-xs font-bold">{entry.title}</span>
3334
<span class="text-pretty text-xs text-muted-foreground">{streamInfo.query}</span>
34-
{#if stream.kind === 'full'}
35+
{#if entry.stream.kind === 'full'}
3536
<div class="flex gap-1">
3637
<Icon icon="carbon:save" width={16} height={16} />
3738
<span class="text-pretty text-xs text-muted-foreground"
38-
>{stream.stream.rows.length} rows</span
39+
>{entry.stream.stream.rows.length} rows</span
3940
>
4041
</div>
4142
{/if}

src/lib/components/query/QueryPane.svelte

+55-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import ExportDialog from '$lib/components/dialog/ExportDialog.svelte';
1212
import QueryResultsTable from './QueryResultsTable.svelte';
1313
import { queryPaneGroup, type SplitDirection } from './QueryPaneGroup.svelte';
14+
import { DEFAULT_QUERY_TITLE } from './QueryPane.svelte';
1415
1516
import Icon from '@iconify/svelte';
1617
import { mount } from 'svelte';
@@ -28,6 +29,33 @@
2829
2930
let { direction, paneId, closable = false }: Props = $props();
3031
32+
class QueryTitle {
33+
mode = $state<'show' | 'edit'>('show');
34+
value = $state(queryPaneGroup.panes[paneId].title);
35+
36+
toggleEdit() {
37+
if (this.mode === 'show') this.mode = 'edit';
38+
else {
39+
queryPaneGroup.setTitle(paneId, this.value);
40+
this.mode = 'show';
41+
}
42+
}
43+
44+
handleKeyDown(ev: KeyboardEvent) {
45+
if (ev.code === 'Enter') this.toggleEdit();
46+
}
47+
48+
focus(input: HTMLInputElement) {
49+
input.focus();
50+
}
51+
52+
reset() {
53+
this.mode = 'show';
54+
this.value = DEFAULT_QUERY_TITLE;
55+
}
56+
}
57+
58+
const queryTitle = new QueryTitle();
3159
let queryError: any | undefined = $state(undefined);
3260
3361
let editorLanguage = $state('mysql');
@@ -58,7 +86,7 @@
5886
} else {
5987
try {
6088
queryError = undefined;
61-
await queryPaneGroup.run(paneId);
89+
await queryPaneGroup.run(paneId, queryTitle.value);
6290
} catch (e) {
6391
handleError(e);
6492
}
@@ -84,6 +112,11 @@
84112
toast.error(`Failed to export data: ${e}`);
85113
}
86114
}
115+
116+
function clear() {
117+
queryTitle.reset();
118+
queryPaneGroup.clear(paneId);
119+
}
87120
</script>
88121

89122
<div class="relative flex h-full max-h-screen w-full flex-col gap-1 overflow-auto">
@@ -181,7 +214,7 @@
181214
icon: 'carbon:erase',
182215
tooltip: 'Clear',
183216
disabled: queryStream === undefined,
184-
action: () => queryPaneGroup.clear(paneId)
217+
action: clear
185218
})}
186219

187220
{@render topBarItem({
@@ -192,6 +225,26 @@
192225
})}
193226
</div>
194227

228+
<Separator orientation="vertical" />
229+
<div class="ml-2 flex items-center gap-1">
230+
{#if queryTitle.mode === 'show'}
231+
<Label class="ml-2">{queryTitle.value}</Label>
232+
{:else if queryTitle.mode === 'edit'}
233+
<input
234+
bind:value={queryTitle.value}
235+
class="border-input bg-background text-sm ring-offset-background"
236+
onkeydown={(e) => queryTitle.handleKeyDown(e)}
237+
use:queryTitle.focus
238+
/>
239+
{/if}
240+
{@render topBarItem({
241+
icon: queryTitle.mode === 'show' ? 'carbon:pen' : 'carbon:checkmark',
242+
tooltip: 'Rename query',
243+
disabled: false,
244+
action: () => queryTitle.toggleEdit()
245+
})}
246+
</div>
247+
195248
<div class="ml-auto mr-2 flex gap-1">
196249
{#if closable}
197250
<Button

src/lib/components/query/QueryPaneGroup.svelte.ts

+57-37
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,52 @@
11
import type { StreamId } from "$lib/lens/types";
2-
import { queriesStore } from "$lib/stores/queries.svelte";
2+
import { queriesStore, type QueryStoreEntry } from "$lib/stores/queries.svelte";
33
import type { QueryStream } from "$lib/stores/QueryStream.svelte";
44

55
export type SplitDirection = 'vertical' | 'horizontal';
6+
export const DEFAULT_QUERY_TITLE: string = 'Unnamed';
67

7-
type PaneInfo = {
8-
get query(): string,
9-
set query(val: string),
8+
class QueryPane {
9+
query = $state('');
10+
title = $state('');
11+
stream = $state<QueryStream | undefined>(undefined);
1012

11-
stream?: QueryStream,
12-
};
13+
streamId?: StreamId;
1314

14-
function usePane(queryString?: string): PaneInfo {
15-
let query = $state(queryString ?? '');
15+
constructor(queryString?: string, title: string = DEFAULT_QUERY_TITLE) {
16+
this.query = queryString ?? '';
17+
this.title = title;
18+
}
19+
20+
clear() {
21+
this.query = '';
22+
this.title = DEFAULT_QUERY_TITLE;
23+
24+
this.streamId = undefined;
25+
this.stream = undefined;
26+
}
27+
28+
renew(entry: QueryStoreEntry) {
29+
const { title, stream } = entry;
1630

17-
return {
18-
get query(): string { return query },
19-
set query(val: string) { query = val },
31+
this.title = title;
2032

21-
stream: undefined,
22-
};
33+
if (stream.kind === 'partial') {
34+
this.query = stream.query;
35+
this.streamId = stream.id;
36+
this.stream = undefined;
37+
} else if (stream.kind === 'full') {
38+
const { query, streamId } = stream.stream;
39+
40+
this.query = query;
41+
this.streamId = streamId;
42+
this.stream = stream.stream;
43+
}
44+
}
2345
}
2446

2547
export class QueryPaneGroup {
2648
direction = $state<SplitDirection | undefined>(undefined);
27-
panes = $state<PaneInfo[]>([usePane(undefined)]);
49+
panes = $state<QueryPane[]>([new QueryPane('')]);
2850
overlayVisible = $state(false);
2951

3052
constructor() {
@@ -34,7 +56,7 @@ export class QueryPaneGroup {
3456
this.direction = direction;
3557

3658
if (this.panes.length == 1) {
37-
this.panes.push(usePane(undefined))
59+
this.panes.push(new QueryPane(''))
3860
}
3961
}
4062

@@ -49,21 +71,11 @@ export class QueryPaneGroup {
4971
if (paneId >= this.panes.length)
5072
throw new Error(`invalid pane ${paneId}`);
5173

52-
const stream = queriesStore.get(streamId);
53-
if (!stream)
54-
throw new Error(`Could not find stream for id ${streamId}`);
74+
const entry = queriesStore.get(streamId);
75+
if (!entry)
76+
throw new Error(`Could not find query for stream ${streamId}`);
5577

56-
if (stream.kind === 'partial') {
57-
this.panes[paneId] = {
58-
query: stream.query,
59-
stream: undefined
60-
}
61-
} else if (stream.kind === 'full') {
62-
this.panes[paneId] = {
63-
query: stream.stream.query,
64-
stream: stream.stream
65-
}
66-
}
78+
this.panes[paneId].renew(entry);
6779
}
6880

6981
save(paneId: number) {
@@ -75,25 +87,33 @@ export class QueryPaneGroup {
7587
queriesStore.save(stream);
7688
}
7789

90+
setTitle(paneId: number, title: string) {
91+
if (paneId >= this.panes.length)
92+
throw new Error(`invalid pane ${paneId}`);
93+
94+
const streamId = this.panes[paneId]?.streamId;
95+
if (streamId)
96+
queriesStore.setTitle(streamId, title);
97+
98+
}
99+
78100
clear(paneId: number) {
79101
if (paneId >= this.panes.length)
80102
throw new Error(`invalid pane ${paneId}`);
81103

82-
this.panes[paneId].stream = undefined;
83-
this.panes[paneId].query = '';
104+
this.panes[paneId].clear();
84105
}
85106

86-
async run(paneId: number): Promise<QueryStream> {
107+
async run(paneId: number, title: string): Promise<QueryStream> {
87108
if (paneId >= this.panes.length)
88109
throw new Error(`invalid pane ${paneId}`);
89110

90111
const query = this.panes[paneId].query;
91-
const stream = await queriesStore.run(query);
112+
const stream = await queriesStore.run(query, title);
113+
114+
this.panes[paneId].streamId = stream.streamId;
115+
this.panes[paneId].stream = stream;
92116

93-
this.panes[paneId] = {
94-
query,
95-
stream
96-
}
97117
return stream;
98118
}
99119

src/lib/stores/queries.svelte.ts

+41-21
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,46 @@ type StreamInfo = {
66
query: string;
77
}
88

9-
type Stream = {
9+
export type QueryStoreStream = {
1010
kind: 'partial',
1111
id: StreamId;
1212
query: string;
1313
} |
1414
{ kind: 'full', stream: QueryStream };
1515

16+
export class QueryStoreEntry {
17+
title = $state('');
18+
query: string;
19+
stream: QueryStoreStream;
20+
21+
constructor(stream: QueryStream, title: string) {
22+
const { streamId: id, query } = stream;
23+
24+
this.title = title;
25+
this.query = query;
26+
27+
this.stream = {
28+
kind: 'partial',
29+
id,
30+
query,
31+
}
32+
}
33+
}
1634

1735
class QueriesStore {
1836
private maxHistory?: number;
19-
streams = $state<Stream[]>([]);
37+
entries = $state<QueryStoreEntry[]>([]);
2038

2139
constructor(maxHistory?: number) {
2240
this.maxHistory = maxHistory;
2341
}
2442

25-
async run(query: string): Promise<QueryStream> {
43+
async run(query: string, title: string): Promise<QueryStream> {
2644
const addToHistory = ({ stream }: { stream: QueryStream }) => {
27-
const { streamId: id, query } = stream;
28-
29-
this.streams.push({
30-
kind: 'partial',
31-
query,
32-
id
33-
});
45+
this.entries.push(new QueryStoreEntry(stream, title));
3446

35-
if (this.maxHistory && this.streams.length >= this.maxHistory) {
36-
this.streams.splice(0, 1);
47+
if (this.maxHistory && this.entries.length >= this.maxHistory) {
48+
this.entries.splice(0, 1);
3749
}
3850
};
3951

@@ -42,12 +54,20 @@ class QueriesStore {
4254
return stream;
4355
}
4456

57+
setTitle(streamId: StreamId, title: string) {
58+
let index = this.entries.findIndex(e => streamId === this.getStreamId(e.stream));
59+
if (index === undefined)
60+
return;
61+
62+
this.entries[index].title = title;
63+
}
64+
4565
save(stream: QueryStream): boolean {
46-
let index = this.streams.findIndex(s => stream.streamId === this.getStreamId(s));
66+
let index = this.entries.findIndex(e => stream.streamId === this.getStreamId(e.stream));
4767
if (index === undefined)
4868
return false;
4969

50-
this.streams[index] = {
70+
this.entries[index].stream = {
5171
kind: 'full',
5272
stream
5373
};
@@ -56,16 +76,16 @@ class QueriesStore {
5676
}
5777

5878
delete(id: StreamId): boolean {
59-
const oldLen = this.streams.length;
60-
this.streams = this.streams.filter(s => id !== this.getStreamId(s));
61-
return oldLen > this.streams.length;
79+
const oldLen = this.entries.length;
80+
this.entries = this.entries.filter(e => id !== this.getStreamId(e.stream));
81+
return oldLen > this.entries.length;
6282
}
6383

64-
get(id: StreamId): Stream | undefined {
65-
return this.streams.find(s => id === this.getStreamId(s));
84+
get(id: StreamId): QueryStoreEntry | undefined {
85+
return this.entries.find(e => id === this.getStreamId(e.stream));
6686
}
6787

68-
getStreamInfo(stream: Stream): StreamInfo {
88+
getStreamInfo(stream: QueryStoreStream): StreamInfo {
6989
if (stream.kind === 'partial') {
7090
const { id, query } = stream;
7191
return { id, query };
@@ -79,7 +99,7 @@ class QueriesStore {
7999
throw new Error("Can't determine information for stream");
80100
}
81101

82-
getStreamId(stream: Stream): StreamId {
102+
getStreamId(stream: QueryStoreStream): StreamId {
83103
return this.getStreamInfo(stream).id;
84104
}
85105
}

0 commit comments

Comments
 (0)