@lapis-notes/bases 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/LICENSE.md +8 -0
- package/README.md +20 -0
- package/dist/bases-plugin.d.ts +10 -0
- package/dist/bases-plugin.js +118 -0
- package/dist/bases-view/app-database-query-source.d.ts +30 -0
- package/dist/bases-view/app-database-query-source.js +248 -0
- package/dist/bases-view/bases.svelte.d.ts +569 -0
- package/dist/bases-view/bases.svelte.js +910 -0
- package/dist/bases-view/card-view/components/card-placeholder.svelte +68 -0
- package/dist/bases-view/card-view/components/card-placeholder.svelte.d.ts +7 -0
- package/dist/bases-view/card-view/components/card.svelte +289 -0
- package/dist/bases-view/card-view/components/card.svelte.d.ts +7 -0
- package/dist/bases-view/card-view/index.d.ts +11 -0
- package/dist/bases-view/card-view/index.js +55 -0
- package/dist/bases-view/cell-editor-options.d.ts +2 -0
- package/dist/bases-view/cell-editor-options.js +26 -0
- package/dist/bases-view/cell-editor.svelte +206 -0
- package/dist/bases-view/cell-editor.svelte.d.ts +12 -0
- package/dist/bases-view/cell.svelte +132 -0
- package/dist/bases-view/cell.svelte.d.ts +16 -0
- package/dist/bases-view/column-definition.d.ts +22 -0
- package/dist/bases-view/column-definition.js +149 -0
- package/dist/bases-view/columns.d.ts +15 -0
- package/dist/bases-view/columns.js +103 -0
- package/dist/bases-view/components/Tag.svelte +14 -0
- package/dist/bases-view/components/Tag.svelte.d.ts +8 -0
- package/dist/bases-view/components/autocomplete.svelte +45 -0
- package/dist/bases-view/components/autocomplete.svelte.d.ts +17 -0
- package/dist/bases-view/components/card-image.svelte +121 -0
- package/dist/bases-view/components/card-image.svelte.d.ts +11 -0
- package/dist/bases-view/components/column-visibility.svelte +388 -0
- package/dist/bases-view/components/column-visibility.svelte.d.ts +13 -0
- package/dist/bases-view/components/datetime.svelte +35 -0
- package/dist/bases-view/components/datetime.svelte.d.ts +7 -0
- package/dist/bases-view/components/file.svelte +31 -0
- package/dist/bases-view/components/file.svelte.d.ts +8 -0
- package/dist/bases-view/components/filters/filter-editor.svelte +102 -0
- package/dist/bases-view/components/filters/filter-editor.svelte.d.ts +10 -0
- package/dist/bases-view/components/filters/filter-group.svelte +142 -0
- package/dist/bases-view/components/filters/filter-group.svelte.d.ts +11 -0
- package/dist/bases-view/components/filters/filter-properties.svelte +77 -0
- package/dist/bases-view/components/filters/filter-properties.svelte.d.ts +8 -0
- package/dist/bases-view/components/filters/filter-root.svelte +112 -0
- package/dist/bases-view/components/filters/filter-root.svelte.d.ts +12 -0
- package/dist/bases-view/components/filters/filter-row.svelte +351 -0
- package/dist/bases-view/components/filters/filter-row.svelte.d.ts +11 -0
- package/dist/bases-view/components/group/group-root.svelte +120 -0
- package/dist/bases-view/components/group/group-root.svelte.d.ts +8 -0
- package/dist/bases-view/components/limit.svelte +100 -0
- package/dist/bases-view/components/limit.svelte.d.ts +12 -0
- package/dist/bases-view/components/query-editor.svelte +230 -0
- package/dist/bases-view/components/query-editor.svelte.d.ts +15 -0
- package/dist/bases-view/components/sort/sort-direction-labels.d.ts +9 -0
- package/dist/bases-view/components/sort/sort-direction-labels.js +25 -0
- package/dist/bases-view/components/sort/sort-root.svelte +239 -0
- package/dist/bases-view/components/sort/sort-root.svelte.d.ts +11 -0
- package/dist/bases-view/components/sort/sort-row.svelte +190 -0
- package/dist/bases-view/components/sort/sort-row.svelte.d.ts +15 -0
- package/dist/bases-view/components/summary-strip.svelte +56 -0
- package/dist/bases-view/components/summary-strip.svelte.d.ts +8 -0
- package/dist/bases-view/components/tag-chip.svelte +47 -0
- package/dist/bases-view/components/tag-chip.svelte.d.ts +12 -0
- package/dist/bases-view/components/views.svelte +178 -0
- package/dist/bases-view/components/views.svelte.d.ts +12 -0
- package/dist/bases-view/data-table-adapter/data-table.svelte.d.ts +45 -0
- package/dist/bases-view/data-table-adapter/data-table.svelte.js +112 -0
- package/dist/bases-view/data-table-adapter/flex-render.svelte +43 -0
- package/dist/bases-view/data-table-adapter/flex-render.svelte.d.ts +30 -0
- package/dist/bases-view/data-table-adapter/index.d.ts +3 -0
- package/dist/bases-view/data-table-adapter/index.js +3 -0
- package/dist/bases-view/data-table-adapter/render-helpers.d.ts +101 -0
- package/dist/bases-view/data-table-adapter/render-helpers.js +110 -0
- package/dist/bases-view/db.d.ts +31 -0
- package/dist/bases-view/db.js +174 -0
- package/dist/bases-view/document-core.d.ts +2 -0
- package/dist/bases-view/document-core.js +166 -0
- package/dist/bases-view/export-core.d.ts +4 -0
- package/dist/bases-view/export-core.js +31 -0
- package/dist/bases-view/file-fields-core.d.ts +31 -0
- package/dist/bases-view/file-fields-core.js +114 -0
- package/dist/bases-view/filter-parser.d.ts +27 -0
- package/dist/bases-view/filter-parser.js +315 -0
- package/dist/bases-view/functions-core.d.ts +66 -0
- package/dist/bases-view/functions-core.js +265 -0
- package/dist/bases-view/functions.d.ts +5 -0
- package/dist/bases-view/functions.js +107 -0
- package/dist/bases-view/fuzzy-search.d.ts +6 -0
- package/dist/bases-view/fuzzy-search.js +15 -0
- package/dist/bases-view/hooks/useResizeObserver.svelte.d.ts +7 -0
- package/dist/bases-view/hooks/useResizeObserver.svelte.js +26 -0
- package/dist/bases-view/index.d.ts +66 -0
- package/dist/bases-view/index.js +231 -0
- package/dist/bases-view/list-view/index.d.ts +10 -0
- package/dist/bases-view/list-view/index.js +17 -0
- package/dist/bases-view/list-view/list.svelte +156 -0
- package/dist/bases-view/list-view/list.svelte.d.ts +7 -0
- package/dist/bases-view/map-view/index.d.ts +10 -0
- package/dist/bases-view/map-view/index.js +17 -0
- package/dist/bases-view/map-view/view.svelte +28 -0
- package/dist/bases-view/map-view/view.svelte.d.ts +7 -0
- package/dist/bases-view/metadata-invalidation-core.d.ts +34 -0
- package/dist/bases-view/metadata-invalidation-core.js +162 -0
- package/dist/bases-view/metadata-type-info.d.ts +7 -0
- package/dist/bases-view/metadata-type-info.js +10 -0
- package/dist/bases-view/models.d.ts +90 -0
- package/dist/bases-view/models.js +133 -0
- package/dist/bases-view/query-result-core.d.ts +8 -0
- package/dist/bases-view/query-result-core.js +16 -0
- package/dist/bases-view/resource-url.d.ts +2 -0
- package/dist/bases-view/resource-url.js +21 -0
- package/dist/bases-view/search-core.d.ts +9 -0
- package/dist/bases-view/search-core.js +20 -0
- package/dist/bases-view/summary-core.d.ts +7 -0
- package/dist/bases-view/summary-core.js +17 -0
- package/dist/bases-view/table-view/components/div-table.svelte +464 -0
- package/dist/bases-view/table-view/components/div-table.svelte.d.ts +32 -0
- package/dist/bases-view/table-view/components/dnd-style.d.ts +3 -0
- package/dist/bases-view/table-view/components/dnd-style.js +6 -0
- package/dist/bases-view/table-view/components/sort-header.svelte +48 -0
- package/dist/bases-view/table-view/components/sort-header.svelte.d.ts +11 -0
- package/dist/bases-view/table-view/components/table-column-tracks.d.ts +14 -0
- package/dist/bases-view/table-view/components/table-column-tracks.js +27 -0
- package/dist/bases-view/table-view/components/table-header.svelte +146 -0
- package/dist/bases-view/table-view/components/table-header.svelte.d.ts +10 -0
- package/dist/bases-view/table-view/components/table-placeholder.svelte +46 -0
- package/dist/bases-view/table-view/components/table-placeholder.svelte.d.ts +30 -0
- package/dist/bases-view/table-view/components/table-virtualizer-core.d.ts +13 -0
- package/dist/bases-view/table-view/components/table-virtualizer-core.js +12 -0
- package/dist/bases-view/table-view/components/table.svelte +997 -0
- package/dist/bases-view/table-view/components/table.svelte.d.ts +32 -0
- package/dist/bases-view/table-view/index.svelte.d.ts +10 -0
- package/dist/bases-view/table-view/index.svelte.js +30 -0
- package/dist/bases-view/unknown-view/index.d.ts +10 -0
- package/dist/bases-view/unknown-view/index.js +19 -0
- package/dist/bases-view/unknown-view/view.svelte +11 -0
- package/dist/bases-view/unknown-view/view.svelte.d.ts +6 -0
- package/dist/bases-view/view-header.svelte +142 -0
- package/dist/bases-view/view-header.svelte.d.ts +28 -0
- package/dist/bases-view/view-registration-inventory.d.ts +22 -0
- package/dist/bases-view/view-registration-inventory.js +7 -0
- package/dist/bases-view/view-settings.svelte +350 -0
- package/dist/bases-view/view-settings.svelte.d.ts +9 -0
- package/dist/bases-view/view.svelte +192 -0
- package/dist/bases-view/view.svelte.d.ts +15 -0
- package/dist/bases-view-surface.svelte +31 -0
- package/dist/bases-view-surface.svelte.d.ts +14 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/serialized-write-queue.d.ts +10 -0
- package/dist/serialized-write-queue.js +36 -0
- package/dist/styles.css +2164 -0
- package/manifest.json +33 -0
- package/package.json +90 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { keywordCompletionSource, sql } from "@codemirror/lang-sql";
|
|
3
|
+
import { syntaxHighlighting } from "@codemirror/language";
|
|
4
|
+
import {
|
|
5
|
+
CompletionContext,
|
|
6
|
+
type Completion,
|
|
7
|
+
type CompletionResult,
|
|
8
|
+
} from "@codemirror/autocomplete";
|
|
9
|
+
import { Compartment, EditorState } from "@codemirror/state";
|
|
10
|
+
import {
|
|
11
|
+
EditorView,
|
|
12
|
+
ViewUpdate,
|
|
13
|
+
keymap,
|
|
14
|
+
placeholder as placeHolder,
|
|
15
|
+
tooltips,
|
|
16
|
+
} from "@codemirror/view";
|
|
17
|
+
import {
|
|
18
|
+
cn,
|
|
19
|
+
lapisCodeMirrorAutocomplete,
|
|
20
|
+
setIcon,
|
|
21
|
+
type BasesPropertyId,
|
|
22
|
+
} from "@lapis-notes/api";
|
|
23
|
+
import { classHighlighter } from "@lapis-notes/api/editor/extensions/class-highlighter";
|
|
24
|
+
import { debounce } from "lodash-es";
|
|
25
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
26
|
+
import { basesDialect } from "../db";
|
|
27
|
+
import { filterLabels } from "../models";
|
|
28
|
+
import { isOp } from "../filter-parser";
|
|
29
|
+
import type { QueryController } from "../bases.svelte";
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
content = $bindable(""),
|
|
33
|
+
onDocChange,
|
|
34
|
+
onBlur,
|
|
35
|
+
class: className,
|
|
36
|
+
placeholder,
|
|
37
|
+
controller,
|
|
38
|
+
invalid = false,
|
|
39
|
+
describedBy,
|
|
40
|
+
ariaLabel = "Filter formula",
|
|
41
|
+
...rest
|
|
42
|
+
}: HTMLAttributes<HTMLDivElement> & {
|
|
43
|
+
content: string;
|
|
44
|
+
onBlur?: (value: string) => void;
|
|
45
|
+
onDocChange?: (value: string) => void;
|
|
46
|
+
placeholder?: string;
|
|
47
|
+
controller: QueryController;
|
|
48
|
+
invalid?: boolean;
|
|
49
|
+
describedBy?: string;
|
|
50
|
+
ariaLabel?: string;
|
|
51
|
+
} = $props();
|
|
52
|
+
|
|
53
|
+
let editorView!: EditorView;
|
|
54
|
+
|
|
55
|
+
let reportChanges = debounce((value: string) => {
|
|
56
|
+
if (onDocChange) {
|
|
57
|
+
onDocChange(value);
|
|
58
|
+
}
|
|
59
|
+
}, 500);
|
|
60
|
+
|
|
61
|
+
let dialect = $derived(basesDialect(controller));
|
|
62
|
+
let completionIcons = $derived.by(() => {
|
|
63
|
+
return controller
|
|
64
|
+
.getAllColumns()
|
|
65
|
+
.reduce<Record<string, string>>((acc, value) => {
|
|
66
|
+
acc[value.id] = value.icon ?? "lucide-info";
|
|
67
|
+
if (value.id.startsWith("note.")) {
|
|
68
|
+
acc[value.id] = acc[value.id];
|
|
69
|
+
}
|
|
70
|
+
return acc;
|
|
71
|
+
}, {});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
function completeFunctions(
|
|
75
|
+
context: CompletionContext,
|
|
76
|
+
): CompletionResult | null {
|
|
77
|
+
let word = context.matchBefore(/[\w$]+(\.[\w$]+(\([^)]*\))?)*\.?/);
|
|
78
|
+
if (!word || (word.from == word.to && !context.explicit)) return null;
|
|
79
|
+
const parts = word.text.split(".");
|
|
80
|
+
if (parts.length >= 2) {
|
|
81
|
+
const base = parts
|
|
82
|
+
.slice(0, parts.length - 1)
|
|
83
|
+
.join(".") as BasesPropertyId;
|
|
84
|
+
const after = parts.length > 2 ? parts[parts.length - 1] : "";
|
|
85
|
+
const type = completionIcons[base]
|
|
86
|
+
? (controller.getColumn(base)?.type ?? "unknown")
|
|
87
|
+
: "unknown";
|
|
88
|
+
if (filterLabels[type]) {
|
|
89
|
+
const completions: Completion[] = filterLabels[type]
|
|
90
|
+
.filter((it) => !it.value.startsWith("!") && !isOp(it.value))
|
|
91
|
+
.map((value) => {
|
|
92
|
+
return {
|
|
93
|
+
label: `${value.value}()`,
|
|
94
|
+
type: "function",
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
from: word.to - after.length,
|
|
99
|
+
options: [...completions, { label: `toString()`, type: "function" }],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type EditorInput = {
|
|
107
|
+
content: string;
|
|
108
|
+
invalid: boolean;
|
|
109
|
+
describedBy?: string;
|
|
110
|
+
ariaLabel: string;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function editorContentAttributes(input: EditorInput) {
|
|
114
|
+
return {
|
|
115
|
+
"aria-label": input.ariaLabel,
|
|
116
|
+
"aria-invalid": String(input.invalid),
|
|
117
|
+
...(input.describedBy
|
|
118
|
+
? { "aria-describedby": input.describedBy }
|
|
119
|
+
: {}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function codeMirror(el: HTMLElement, input: EditorInput) {
|
|
124
|
+
const contentAttributes = new Compartment();
|
|
125
|
+
let previousContent = input.content;
|
|
126
|
+
editorView = new EditorView({
|
|
127
|
+
state: EditorState.create({
|
|
128
|
+
doc: input.content,
|
|
129
|
+
extensions: [
|
|
130
|
+
sql({
|
|
131
|
+
dialect: dialect,
|
|
132
|
+
}),
|
|
133
|
+
...lapisCodeMirrorAutocomplete({
|
|
134
|
+
override: [keywordCompletionSource(dialect), completeFunctions],
|
|
135
|
+
addToOptions: [
|
|
136
|
+
{
|
|
137
|
+
render(completion) {
|
|
138
|
+
let icon = completionIcons[completion.label];
|
|
139
|
+
if (completion.type === "function") {
|
|
140
|
+
icon = "lucide-square-function";
|
|
141
|
+
}
|
|
142
|
+
if (icon) {
|
|
143
|
+
const span = createSpan(
|
|
144
|
+
"suggestion-flair [&_svg:not([class*='text-'])]:text-muted-foreground text-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
145
|
+
);
|
|
146
|
+
setIcon(span, icon);
|
|
147
|
+
return span;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
},
|
|
151
|
+
position: 20,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
}),
|
|
155
|
+
syntaxHighlighting(classHighlighter),
|
|
156
|
+
tooltips({
|
|
157
|
+
parent: document.body,
|
|
158
|
+
}),
|
|
159
|
+
EditorView.domEventHandlers({
|
|
160
|
+
blur: () => {
|
|
161
|
+
onBlur?.(editorView.state.doc.toString());
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
keymap.of([
|
|
165
|
+
{
|
|
166
|
+
key: "Enter",
|
|
167
|
+
run: (view) => {
|
|
168
|
+
// Insert newline and prevent event from bubbling to parent
|
|
169
|
+
view.dispatch({
|
|
170
|
+
changes: {
|
|
171
|
+
from: view.state.selection.main.head,
|
|
172
|
+
insert: "\n",
|
|
173
|
+
},
|
|
174
|
+
selection: { anchor: view.state.selection.main.head + 1 },
|
|
175
|
+
});
|
|
176
|
+
return true; // This prevents the event from propagating
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
]),
|
|
180
|
+
placeholder ? placeHolder(placeholder) : [],
|
|
181
|
+
EditorView.editorAttributes.of({ class: "mod-inline" }),
|
|
182
|
+
contentAttributes.of(
|
|
183
|
+
EditorView.contentAttributes.of(editorContentAttributes(input)),
|
|
184
|
+
),
|
|
185
|
+
EditorView.updateListener.of((v: ViewUpdate) => {
|
|
186
|
+
if (v.docChanged) {
|
|
187
|
+
const next = v.state.doc.toString();
|
|
188
|
+
reportChanges(next);
|
|
189
|
+
}
|
|
190
|
+
}),
|
|
191
|
+
],
|
|
192
|
+
}),
|
|
193
|
+
parent: el,
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
update: (next: EditorInput) => {
|
|
197
|
+
const current = editorView.state.doc.toString();
|
|
198
|
+
if (next.content !== previousContent && current !== next.content) {
|
|
199
|
+
editorView.dispatch({
|
|
200
|
+
changes: [
|
|
201
|
+
{
|
|
202
|
+
from: 0,
|
|
203
|
+
to: editorView.state.doc.length,
|
|
204
|
+
insert: next.content,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
previousContent = next.content;
|
|
210
|
+
editorView.dispatch({
|
|
211
|
+
effects: contentAttributes.reconfigure(
|
|
212
|
+
EditorView.contentAttributes.of(editorContentAttributes(next)),
|
|
213
|
+
),
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
destroy: () => {
|
|
217
|
+
reportChanges.cancel();
|
|
218
|
+
editorView.destroy();
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<div
|
|
225
|
+
{...rest}
|
|
226
|
+
use:codeMirror={{ content, invalid, describedBy, ariaLabel }}
|
|
227
|
+
data-ui-component="bases-query-editor"
|
|
228
|
+
data-invalid={invalid}
|
|
229
|
+
class={cn("bases-style-h-full-668b21", className)}
|
|
230
|
+
></div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
import type { QueryController } from "../bases.svelte";
|
|
3
|
+
type $$ComponentProps = HTMLAttributes<HTMLDivElement> & {
|
|
4
|
+
content: string;
|
|
5
|
+
onBlur?: (value: string) => void;
|
|
6
|
+
onDocChange?: (value: string) => void;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
controller: QueryController;
|
|
9
|
+
invalid?: boolean;
|
|
10
|
+
describedBy?: string;
|
|
11
|
+
ariaLabel?: string;
|
|
12
|
+
};
|
|
13
|
+
declare const QueryEditor: import("svelte").Component<$$ComponentProps, {}, "content">;
|
|
14
|
+
type QueryEditor = ReturnType<typeof QueryEditor>;
|
|
15
|
+
export default QueryEditor;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MetadataType } from "@lapis-notes/api";
|
|
2
|
+
export declare const sortDirectionLabels: Partial<Record<MetadataType, Array<{
|
|
3
|
+
label: string;
|
|
4
|
+
value: "ASC" | "DESC";
|
|
5
|
+
}>>>;
|
|
6
|
+
export declare function getSortDirectionLabels(type: MetadataType | undefined): {
|
|
7
|
+
label: string;
|
|
8
|
+
value: "ASC" | "DESC";
|
|
9
|
+
}[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const sortDirectionLabels = {
|
|
2
|
+
unknown: [
|
|
3
|
+
{ label: "A → Z", value: "ASC" },
|
|
4
|
+
{ label: "Z → A", value: "DESC" },
|
|
5
|
+
],
|
|
6
|
+
text: [
|
|
7
|
+
{ label: "A → Z", value: "ASC" },
|
|
8
|
+
{ label: "Z → A", value: "DESC" },
|
|
9
|
+
],
|
|
10
|
+
date: [
|
|
11
|
+
{ label: "Old to new", value: "ASC" },
|
|
12
|
+
{ label: "New to old", value: "DESC" },
|
|
13
|
+
],
|
|
14
|
+
datetime: [
|
|
15
|
+
{ label: "Old to new", value: "ASC" },
|
|
16
|
+
{ label: "New to old", value: "DESC" },
|
|
17
|
+
],
|
|
18
|
+
number: [
|
|
19
|
+
{ label: "0 → 1", value: "ASC" },
|
|
20
|
+
{ label: "1 → 0", value: "DESC" },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
export function getSortDirectionLabels(type) {
|
|
24
|
+
return (sortDirectionLabels[type ?? "unknown"] ?? sortDirectionLabels.unknown ?? []);
|
|
25
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { DragDropProvider } from "@dnd-kit/svelte";
|
|
3
|
+
import type { DragEndEvent } from "@dnd-kit/dom";
|
|
4
|
+
import { arrayMove } from "@dnd-kit/helpers";
|
|
5
|
+
import * as Popover from "@lapismd/design-core/shadcn/popover";
|
|
6
|
+
import * as Command from "@lapismd/design-core/shadcn/command";
|
|
7
|
+
import * as Select from "@lapismd/design-core/shadcn/select";
|
|
8
|
+
import SortIcon from "@lucide/svelte/icons/arrow-up-down";
|
|
9
|
+
import { ScrollArea } from "@lapismd/design-core/shadcn/scroll-area";
|
|
10
|
+
import type { Table } from "@tanstack/table-core";
|
|
11
|
+
import { Button } from "@lapismd/design-core/shadcn/button";
|
|
12
|
+
import type { SortColumn } from "../..";
|
|
13
|
+
import SortRow from "./sort-row.svelte";
|
|
14
|
+
import PlusIcon from "@lucide/svelte/icons/plus";
|
|
15
|
+
import { cn } from "@lapis-notes/api";
|
|
16
|
+
import type { QueryController } from "../../bases.svelte";
|
|
17
|
+
import CheckIcon from "@lucide/svelte/icons/check";
|
|
18
|
+
import Trash from "@lucide/svelte/icons/trash-2";
|
|
19
|
+
import { Icon } from "@lapis-notes/api/icon";
|
|
20
|
+
import { getSortDirectionLabels } from "./sort-direction-labels";
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
class: className,
|
|
24
|
+
orderBy = $bindable(),
|
|
25
|
+
table,
|
|
26
|
+
controller = table as QueryController,
|
|
27
|
+
}: {
|
|
28
|
+
orderBy: Array<SortColumn>;
|
|
29
|
+
class?: string;
|
|
30
|
+
controller?: QueryController;
|
|
31
|
+
table?: any;
|
|
32
|
+
} = $props();
|
|
33
|
+
|
|
34
|
+
let empty: SortColumn = $state({ property: "", direction: "ASC" });
|
|
35
|
+
let groupBy = $derived(controller.selectedView.groupBy);
|
|
36
|
+
let selectedGroupColumn = $derived.by(() => {
|
|
37
|
+
return groupBy?.property
|
|
38
|
+
? controller.getColumn(groupBy.property)
|
|
39
|
+
: undefined;
|
|
40
|
+
});
|
|
41
|
+
let groupDirections = $derived.by(() => {
|
|
42
|
+
return getSortDirectionLabels(selectedGroupColumn?.type);
|
|
43
|
+
});
|
|
44
|
+
let groupDirectionLabel = $derived(
|
|
45
|
+
groupDirections.find((direction) => direction.value === getGroupDirection())
|
|
46
|
+
?.label ?? "",
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
$effect(() => {
|
|
50
|
+
if (empty.property && !orderBy.length) {
|
|
51
|
+
orderBy.push($state.snapshot(empty));
|
|
52
|
+
empty = { property: "", direction: "ASC" };
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function handleDragEnd(event: DragEndEvent) {
|
|
57
|
+
const source = event.operation.source?.data;
|
|
58
|
+
const target = event.operation.target?.data;
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
source?.type !== "bases-sort-row" ||
|
|
62
|
+
target?.type !== "bases-sort-row" ||
|
|
63
|
+
source.index === undefined ||
|
|
64
|
+
target.index === undefined ||
|
|
65
|
+
source.index === target.index
|
|
66
|
+
) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
orderBy = arrayMove(orderBy, source.index, target.index);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function deleteSort(index: number) {
|
|
74
|
+
orderBy.splice(index, 1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function addSort() {
|
|
78
|
+
orderBy.push({ property: "", direction: "ASC" });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function setGroupBy(property: string) {
|
|
82
|
+
controller.selectedView.groupBy = {
|
|
83
|
+
property,
|
|
84
|
+
direction: controller.selectedView.groupBy?.direction ?? "ASC",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getGroupDirection() {
|
|
89
|
+
return controller.selectedView.groupBy?.direction ?? "ASC";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function setGroupDirection(direction: string) {
|
|
93
|
+
if (!controller.selectedView.groupBy) return;
|
|
94
|
+
controller.selectedView.groupBy = {
|
|
95
|
+
...controller.selectedView.groupBy,
|
|
96
|
+
direction: direction === "DESC" ? "DESC" : "ASC",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<Popover.Root>
|
|
102
|
+
<Popover.Trigger>
|
|
103
|
+
{#snippet child({ props }: { props: Record<string, any> })}
|
|
104
|
+
<Button
|
|
105
|
+
{...props}
|
|
106
|
+
variant="outline"
|
|
107
|
+
size="sm"
|
|
108
|
+
class={cn("bases-style-h-8-ed8a5d bases-style-border-dashed-a29b7a", className)}
|
|
109
|
+
>
|
|
110
|
+
<SortIcon /> Sort
|
|
111
|
+
</Button>
|
|
112
|
+
{/snippet}
|
|
113
|
+
</Popover.Trigger>
|
|
114
|
+
<Popover.Content
|
|
115
|
+
class="bases-query-popover bases-sort-popover bases-style-bg-sidebar-d3aed6 bases-style-mr-2-d2347e bases-style-max-h-70svh-d6926c bases-style-overflow-y-auto-92bf82 bases-style-p-2-7660b4"
|
|
116
|
+
data-bases-popover="sort"
|
|
117
|
+
align="start"
|
|
118
|
+
>
|
|
119
|
+
<div class="bases-style-mb-2-a77ed4 bases-style-grid-f3c543 bases-style-gap-2-77a2a2">
|
|
120
|
+
<div
|
|
121
|
+
class="bases-style-text-muted-foreground-bfa603 bases-style-px-1-d8e0e3 bases-style-text-xs-359090 bases-style-font-semibold-e83a70 bases-style-tracking-0-18em-a82f6f uppercase"
|
|
122
|
+
>
|
|
123
|
+
Group by
|
|
124
|
+
</div>
|
|
125
|
+
<div class="bases-style-flex-60fbb7 bases-style-h-9-e7a768 bases-style-items-center-3960ff bases-style-rounded-md-421ac2 bases-style-border-2-65935d">
|
|
126
|
+
<Popover.Root>
|
|
127
|
+
<Popover.Trigger>
|
|
128
|
+
{#snippet child({ props }: { props: Record<string, any> })}
|
|
129
|
+
<Button
|
|
130
|
+
{...props}
|
|
131
|
+
variant="outline"
|
|
132
|
+
size="sm"
|
|
133
|
+
class="bases-style-h-8-ed8a5d grow bases-style-rounded-none-0c5e91 bases-style-rounded-l-md-9b2e91 bases-style-border-none-4a5f0e bases-style-shadow-none-ad47d1 bases-style-outline-none-df37b1"
|
|
134
|
+
>
|
|
135
|
+
{#if selectedGroupColumn}
|
|
136
|
+
<Icon name={[selectedGroupColumn.icon ?? "lucide-file"]} />
|
|
137
|
+
{selectedGroupColumn.displayName}
|
|
138
|
+
{:else}
|
|
139
|
+
<span class="bases-style-opacity-50-0b8c50">Property</span>
|
|
140
|
+
{/if}
|
|
141
|
+
<SortIcon />
|
|
142
|
+
</Button>
|
|
143
|
+
{/snippet}
|
|
144
|
+
</Popover.Trigger>
|
|
145
|
+
<Popover.Content class="bases-style-w-max-2f935d bases-style-p-0-8a539c" align="start">
|
|
146
|
+
<Command.Root>
|
|
147
|
+
<Command.Input placeholder="Group by property" />
|
|
148
|
+
<Command.List class="bases-style-p-1-eb6a3c">
|
|
149
|
+
<Command.Empty>No results found.</Command.Empty>
|
|
150
|
+
{#each controller.getAllColumns() as column (column.id)}
|
|
151
|
+
<Command.Item onSelect={() => setGroupBy(column.id)}>
|
|
152
|
+
<div
|
|
153
|
+
data-ui-part="bases-option-indicator"
|
|
154
|
+
data-selected={groupBy?.property === column.id}
|
|
155
|
+
class={cn(
|
|
156
|
+
"bases-style-mr-2-d2347e bases-style-flex-60fbb7 bases-style-size-4-f7b5fa bases-style-items-center-3960ff bases-style-justify-center-86843c bases-style-rounded-sm-36d446 border bases-style-border-var-interactive-accent-f46c93",
|
|
157
|
+
groupBy?.property === column.id
|
|
158
|
+
? "bases-style-ring-ring-3e1868 bases-style-border-none-4a5f0e bases-style-bg-var-interactive-accent-c58cc0 bases-style-text-var-text-on-accent-22b2b1 hover:ring"
|
|
159
|
+
: "bases-style-opacity-50-0b8c50 [&_svg]:invisible",
|
|
160
|
+
)}
|
|
161
|
+
>
|
|
162
|
+
<CheckIcon class="bases-style-size-4-f7b5fa" />
|
|
163
|
+
</div>
|
|
164
|
+
<Icon name={[column.icon ?? "lucide-info"]} />
|
|
165
|
+
<span class="bases-style-overflow-hidden-2cd02d bases-style-text-ellipsis-6e2e11"
|
|
166
|
+
>{column.displayName}</span
|
|
167
|
+
>
|
|
168
|
+
{#if !column.id.startsWith("note.")}
|
|
169
|
+
<Command.Shortcut class="bases-style-font-mono-0e6570 bases-style-text-0-75em-c425cc"
|
|
170
|
+
>{column.id}</Command.Shortcut
|
|
171
|
+
>
|
|
172
|
+
{/if}
|
|
173
|
+
</Command.Item>
|
|
174
|
+
{/each}
|
|
175
|
+
</Command.List>
|
|
176
|
+
</Command.Root>
|
|
177
|
+
</Popover.Content>
|
|
178
|
+
</Popover.Root>
|
|
179
|
+
<Select.Root
|
|
180
|
+
type="single"
|
|
181
|
+
bind:value={getGroupDirection, setGroupDirection}
|
|
182
|
+
>
|
|
183
|
+
<Select.Trigger
|
|
184
|
+
size="sm"
|
|
185
|
+
class="bases-style-bg-background-e6f9e3 bases-style-h-8-ed8a5d bases-style-rounded-none-0c5e91 bases-style-border-none-4a5f0e bases-style-shadow-none-ad47d1 bases-style-outline-none-df37b1"
|
|
186
|
+
>
|
|
187
|
+
{groupDirectionLabel}
|
|
188
|
+
<SortIcon />
|
|
189
|
+
</Select.Trigger>
|
|
190
|
+
<Select.Content>
|
|
191
|
+
{#each groupDirections as direction (direction.value)}
|
|
192
|
+
<Select.Item value={direction.value} label={direction.label}
|
|
193
|
+
>{direction.label}</Select.Item
|
|
194
|
+
>
|
|
195
|
+
{/each}
|
|
196
|
+
</Select.Content>
|
|
197
|
+
</Select.Root>
|
|
198
|
+
<Button
|
|
199
|
+
variant="ghost"
|
|
200
|
+
size="sm"
|
|
201
|
+
class="bases-style-bg-background-e6f9e3 bases-style-h-8-ed8a5d bases-style-rounded-none-0c5e91 bases-style-rounded-r-md-d4eb07 bases-style-border-none-4a5f0e bases-style-shadow-none-ad47d1 bases-style-outline-none-df37b1"
|
|
202
|
+
disabled={!selectedGroupColumn}
|
|
203
|
+
onclick={() => (controller.selectedView.groupBy = undefined)}
|
|
204
|
+
>
|
|
205
|
+
<Trash />
|
|
206
|
+
</Button>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
<div class="bases-style-mb-2-a77ed4 bases-style-border-t-b950dd"></div>
|
|
210
|
+
<div
|
|
211
|
+
class="bases-style-text-muted-foreground-bfa603 bases-style-mb-2-a77ed4 bases-style-px-1-d8e0e3 bases-style-text-xs-359090 bases-style-font-semibold-e83a70 bases-style-tracking-0-18em-a82f6f uppercase"
|
|
212
|
+
>
|
|
213
|
+
Sort by
|
|
214
|
+
</div>
|
|
215
|
+
<DragDropProvider onDragEnd={handleDragEnd}>
|
|
216
|
+
<ScrollArea
|
|
217
|
+
orientation="both"
|
|
218
|
+
class="bases-style-h-full-668b21 bases-style-w-full-6da6a3 bases-style-data-scroll-area-viewport-max-h-55svh-15dd46"
|
|
219
|
+
>
|
|
220
|
+
{#if !orderBy.length}
|
|
221
|
+
<SortRow {controller} bind:sort={empty} deleteSort={() => {}} />
|
|
222
|
+
{/if}
|
|
223
|
+
{#each orderBy as order, i}
|
|
224
|
+
<SortRow
|
|
225
|
+
{controller}
|
|
226
|
+
index={i}
|
|
227
|
+
bind:sort={orderBy[i]}
|
|
228
|
+
deleteSort={() => deleteSort(i)}
|
|
229
|
+
/>
|
|
230
|
+
{/each}
|
|
231
|
+
</ScrollArea>
|
|
232
|
+
</DragDropProvider>
|
|
233
|
+
<div class="filter-group-actions bases-style-mt-2-50d0d2">
|
|
234
|
+
<Button size="sm" variant="ghost" onclick={addSort}>
|
|
235
|
+
<PlusIcon /> Add sort
|
|
236
|
+
</Button>
|
|
237
|
+
</div>
|
|
238
|
+
</Popover.Content>
|
|
239
|
+
</Popover.Root>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SortColumn } from "../..";
|
|
2
|
+
import type { QueryController } from "../../bases.svelte";
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
orderBy: Array<SortColumn>;
|
|
5
|
+
class?: string;
|
|
6
|
+
controller?: QueryController;
|
|
7
|
+
table?: any;
|
|
8
|
+
};
|
|
9
|
+
declare const SortRoot: import("svelte").Component<$$ComponentProps, {}, "orderBy">;
|
|
10
|
+
type SortRoot = ReturnType<typeof SortRoot>;
|
|
11
|
+
export default SortRoot;
|