@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,350 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Icon } from "@lapis-notes/api/icon";
|
|
3
|
+
import { Label } from "@lapismd/design-core/shadcn/label";
|
|
4
|
+
import { Input } from "@lapismd/design-core/shadcn/input";
|
|
5
|
+
import { Textarea } from "@lapismd/design-core/shadcn/textarea";
|
|
6
|
+
import * as Select from "@lapismd/design-core/shadcn/select";
|
|
7
|
+
import { Slider } from "@lapismd/design-core/shadcn/slider";
|
|
8
|
+
import { Switch } from "@lapismd/design-core/shadcn/switch";
|
|
9
|
+
import * as Popover from "@lapismd/design-core/shadcn/popover";
|
|
10
|
+
import * as Command from "@lapismd/design-core/shadcn/command";
|
|
11
|
+
import { Button } from "@lapismd/design-core/shadcn/button";
|
|
12
|
+
import ArrowUpDown from "@lucide/svelte/icons/chevrons-up-down";
|
|
13
|
+
import X from "@lucide/svelte/icons/x";
|
|
14
|
+
import CheckIcon from "@lucide/svelte/icons/check";
|
|
15
|
+
import { cn } from "@lapis-notes/api";
|
|
16
|
+
import type {
|
|
17
|
+
BasesAllOptions,
|
|
18
|
+
BasesOptions,
|
|
19
|
+
BasesPropertyId,
|
|
20
|
+
} from "@lapis-notes/api";
|
|
21
|
+
import type { QueryController } from "./bases.svelte";
|
|
22
|
+
import type { BasesViewBase } from "./models";
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
controller,
|
|
26
|
+
view = $bindable(),
|
|
27
|
+
}: { controller: QueryController; view: BasesViewBase } = $props();
|
|
28
|
+
let layoutOptions = $derived.by(() => {
|
|
29
|
+
return [...controller.views.entries()].map(([key, view]) => {
|
|
30
|
+
return { value: key, label: view.name, icon: view.icon };
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
let options = $derived(
|
|
34
|
+
controller.views.get(view.type)?.options?.(controller.view.config) || [],
|
|
35
|
+
);
|
|
36
|
+
type OptionEntry =
|
|
37
|
+
| { kind: "group"; key: string; displayName: string }
|
|
38
|
+
| { kind: "option"; key: string; option: BasesOptions };
|
|
39
|
+
function flattenOptions(
|
|
40
|
+
items: BasesAllOptions[],
|
|
41
|
+
path = "options",
|
|
42
|
+
): OptionEntry[] {
|
|
43
|
+
return items.flatMap((item, index) => {
|
|
44
|
+
const key = `${path}-${index}`;
|
|
45
|
+
if (item.type !== "group") {
|
|
46
|
+
return [{ kind: "option", key, option: item } satisfies OptionEntry];
|
|
47
|
+
}
|
|
48
|
+
if (item.shouldHide?.()) return [];
|
|
49
|
+
return [
|
|
50
|
+
{ kind: "group", key, displayName: item.displayName } satisfies OptionEntry,
|
|
51
|
+
...flattenOptions(item.items, key),
|
|
52
|
+
];
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
let optionEntries = $derived.by(() => flattenOptions(options));
|
|
56
|
+
const selectedLayout = $derived(
|
|
57
|
+
layoutOptions.find((f) => f.value === view.type) ?? layoutOptions[0],
|
|
58
|
+
);
|
|
59
|
+
let openPropertyKey = $state<string | null>(null);
|
|
60
|
+
|
|
61
|
+
function dropDownOptions(data: Record<string, string>) {
|
|
62
|
+
return Object.entries(data).map(([value, label]) => {
|
|
63
|
+
return { label, value };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<div class="bases-style-pb-6-fe985c">
|
|
69
|
+
<div class="bases-style-grid-f3c543 bases-style-grid-cols-1-d7c833 bases-style-items-center-3960ff bases-style-gap-2-77a2a2 bases-style-px-2-d5eab2 bases-style-pt-1-6b7d6e">
|
|
70
|
+
<Label for="layout" class="bases-style-text-sm-fc7473 bases-style-opacity-50-0b8c50">Layout</Label>
|
|
71
|
+
<Select.Root
|
|
72
|
+
type="single"
|
|
73
|
+
name="layout"
|
|
74
|
+
bind:value={
|
|
75
|
+
() => view.type,
|
|
76
|
+
(value) => {
|
|
77
|
+
view.type = value;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
>
|
|
81
|
+
<Select.Trigger class="bases-style-w-full-6da6a3">
|
|
82
|
+
<Icon name={selectedLayout.icon} />
|
|
83
|
+
{selectedLayout.label}
|
|
84
|
+
</Select.Trigger>
|
|
85
|
+
<Select.Content>
|
|
86
|
+
<Select.Group>
|
|
87
|
+
<Select.Label>Layouts</Select.Label>
|
|
88
|
+
{#each layoutOptions as option (option.value)}
|
|
89
|
+
<Select.Item value={option.value} label={option.label}>
|
|
90
|
+
<Icon name={option.icon} />
|
|
91
|
+
{option.label}
|
|
92
|
+
</Select.Item>
|
|
93
|
+
{/each}
|
|
94
|
+
</Select.Group>
|
|
95
|
+
</Select.Content>
|
|
96
|
+
</Select.Root>
|
|
97
|
+
</div>
|
|
98
|
+
{#each optionEntries as entry (entry.key)}
|
|
99
|
+
{#if entry.kind === "group"}
|
|
100
|
+
<h3 class="bases-view-settings__group" data-ui-part="option-group">
|
|
101
|
+
{entry.displayName}
|
|
102
|
+
</h3>
|
|
103
|
+
{:else}
|
|
104
|
+
{@const option = entry.option}
|
|
105
|
+
{#if option.type === "dropdown"}
|
|
106
|
+
{@const selectOptions = dropDownOptions(option.options)}
|
|
107
|
+
{@const selectedOption =
|
|
108
|
+
selectOptions.find(
|
|
109
|
+
(it) =>
|
|
110
|
+
it.value ===
|
|
111
|
+
(controller.view.config.get(option.key) ?? option.default),
|
|
112
|
+
) ?? selectOptions[0]}
|
|
113
|
+
<div class="bases-style-grid-f3c543 bases-style-grid-cols-1-d7c833 bases-style-items-center-3960ff bases-style-gap-2-77a2a2 bases-style-px-2-d5eab2 bases-style-pt-1-6b7d6e" data-ui-part="view-option" data-option-type="dropdown">
|
|
114
|
+
<Label for={option.key} class="bases-style-text-sm-fc7473 bases-style-opacity-50-0b8c50"
|
|
115
|
+
>{option.displayName}</Label
|
|
116
|
+
>
|
|
117
|
+
<Select.Root
|
|
118
|
+
type="single"
|
|
119
|
+
name={option.key}
|
|
120
|
+
bind:value={
|
|
121
|
+
() => {
|
|
122
|
+
return (controller.view.config.get(option.key) ||
|
|
123
|
+
option.default) as string;
|
|
124
|
+
},
|
|
125
|
+
(value) => {
|
|
126
|
+
controller.view.config.set(option.key, value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
>
|
|
130
|
+
<Select.Trigger class="bases-style-w-full-6da6a3">
|
|
131
|
+
{selectedOption.label}
|
|
132
|
+
</Select.Trigger>
|
|
133
|
+
<Select.Content>
|
|
134
|
+
<Select.Group>
|
|
135
|
+
<Select.Label>{option.displayName}</Select.Label>
|
|
136
|
+
{#each selectOptions as opt (opt.value)}
|
|
137
|
+
<Select.Item value={opt.value} label={opt.label}>
|
|
138
|
+
{opt.label}
|
|
139
|
+
</Select.Item>
|
|
140
|
+
{/each}
|
|
141
|
+
</Select.Group>
|
|
142
|
+
</Select.Content>
|
|
143
|
+
</Select.Root>
|
|
144
|
+
</div>
|
|
145
|
+
{:else if option.type === "slider"}
|
|
146
|
+
<div class="bases-style-grid-f3c543 bases-style-grid-cols-1-d7c833 bases-style-items-center-3960ff bases-style-gap-2-77a2a2 bases-style-px-2-d5eab2 bases-style-py-2-03b4dd" data-ui-part="view-option" data-option-type="slider">
|
|
147
|
+
<Label for={option.key} class="bases-style-text-sm-fc7473 bases-style-opacity-50-0b8c50"
|
|
148
|
+
>{option.displayName}</Label
|
|
149
|
+
>
|
|
150
|
+
<Slider
|
|
151
|
+
data-tooltip={controller.view.config.get(option.key)}
|
|
152
|
+
type="single"
|
|
153
|
+
bind:value={
|
|
154
|
+
() => {
|
|
155
|
+
return (controller.view.config.get(option.key) ||
|
|
156
|
+
option.default) as number;
|
|
157
|
+
},
|
|
158
|
+
(value) => {
|
|
159
|
+
controller.view.config.set(option.key, value);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
min={option.min}
|
|
163
|
+
max={option.max}
|
|
164
|
+
step={option.step}
|
|
165
|
+
class="bases-style-w-full-6da6a3"
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
{:else if option.type === "text"}
|
|
169
|
+
<div class="bases-style-grid-f3c543 bases-style-grid-cols-1-d7c833 bases-style-items-center-3960ff bases-style-gap-2-77a2a2 bases-style-px-2-d5eab2 bases-style-pt-1-6b7d6e" data-ui-part="view-option" data-option-type="text">
|
|
170
|
+
<Label for={option.key} class="bases-style-text-sm-fc7473 bases-style-opacity-50-0b8c50"
|
|
171
|
+
>{option.displayName}</Label
|
|
172
|
+
>
|
|
173
|
+
<Input
|
|
174
|
+
bind:value={
|
|
175
|
+
() => {
|
|
176
|
+
return (controller.view.config.get(option.key) ??
|
|
177
|
+
option.default ??
|
|
178
|
+
"") as string;
|
|
179
|
+
},
|
|
180
|
+
(value) => {
|
|
181
|
+
controller.view.config.set(option.key, value);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
id={option.key}
|
|
185
|
+
placeholder={option.placeholder}
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
{:else if option.type === "multitext"}
|
|
189
|
+
<div class="bases-style-grid-f3c543 bases-style-grid-cols-1-d7c833 bases-style-items-center-3960ff bases-style-gap-2-77a2a2 bases-style-px-2-d5eab2 bases-style-pt-1-6b7d6e" data-ui-part="view-option" data-option-type="multitext">
|
|
190
|
+
<Label for={option.key} class="bases-style-text-sm-fc7473 bases-style-opacity-50-0b8c50">{option.displayName}</Label>
|
|
191
|
+
<Input
|
|
192
|
+
id={option.key}
|
|
193
|
+
value={((controller.view.config.get(option.key) ?? option.default ?? []) as string[]).join(", ")}
|
|
194
|
+
onblur={(event) => {
|
|
195
|
+
controller.view.config.set(
|
|
196
|
+
option.key,
|
|
197
|
+
event.currentTarget.value
|
|
198
|
+
.split(",")
|
|
199
|
+
.map((value) => value.trim())
|
|
200
|
+
.filter(Boolean),
|
|
201
|
+
);
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
</div>
|
|
205
|
+
{:else if option.type === "toggle"}
|
|
206
|
+
<div class="bases-style-grid-f3c543 bases-style-grid-cols-1-d7c833 bases-style-items-center-3960ff bases-style-gap-2-77a2a2 bases-style-px-2-d5eab2 bases-style-pt-1-6b7d6e" data-ui-part="view-option" data-option-type="toggle">
|
|
207
|
+
<Label for={option.key} class="bases-style-text-sm-fc7473 bases-style-opacity-50-0b8c50"
|
|
208
|
+
>{option.displayName}</Label
|
|
209
|
+
>
|
|
210
|
+
<Switch
|
|
211
|
+
bind:checked={
|
|
212
|
+
() => {
|
|
213
|
+
return Boolean(
|
|
214
|
+
controller.view.config.get(option.key) ?? option.default,
|
|
215
|
+
);
|
|
216
|
+
},
|
|
217
|
+
(value) => {
|
|
218
|
+
controller.view.config.set(option.key, value);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
id={option.key}
|
|
222
|
+
/>
|
|
223
|
+
</div>
|
|
224
|
+
{:else if option.type === "file" || option.type === "folder"}
|
|
225
|
+
<div class="bases-style-grid-f3c543 bases-style-grid-cols-1-d7c833 bases-style-items-center-3960ff bases-style-gap-2-77a2a2 bases-style-px-2-d5eab2 bases-style-pt-1-6b7d6e" data-ui-part="view-option" data-option-type={option.type}>
|
|
226
|
+
<Label for={option.key} class="bases-style-text-sm-fc7473 bases-style-opacity-50-0b8c50">{option.displayName}</Label>
|
|
227
|
+
<Input
|
|
228
|
+
id={option.key}
|
|
229
|
+
placeholder={option.placeholder}
|
|
230
|
+
bind:value={
|
|
231
|
+
() => (controller.view.config.get(option.key) ?? option.default ?? "") as string,
|
|
232
|
+
(value) => controller.view.config.set(option.key, value)
|
|
233
|
+
}
|
|
234
|
+
/>
|
|
235
|
+
</div>
|
|
236
|
+
{:else if option.type === "formula"}
|
|
237
|
+
<div class="bases-style-grid-f3c543 bases-style-grid-cols-1-d7c833 bases-style-items-center-3960ff bases-style-gap-2-77a2a2 bases-style-px-2-d5eab2 bases-style-pt-1-6b7d6e" data-ui-part="view-option" data-option-type="formula">
|
|
238
|
+
<Label for={option.key} class="bases-style-text-sm-fc7473 bases-style-opacity-50-0b8c50">{option.displayName}</Label>
|
|
239
|
+
<Textarea
|
|
240
|
+
id={option.key}
|
|
241
|
+
placeholder={option.placeholder}
|
|
242
|
+
bind:value={
|
|
243
|
+
() => (controller.view.config.get(option.key) ?? option.default ?? "") as string,
|
|
244
|
+
(value) => controller.view.config.set(option.key, value)
|
|
245
|
+
}
|
|
246
|
+
/>
|
|
247
|
+
</div>
|
|
248
|
+
{:else if option.type === "property"}
|
|
249
|
+
<div class="bases-style-grid-f3c543 bases-style-grid-cols-1-d7c833 bases-style-items-center-3960ff bases-style-gap-2-77a2a2 bases-style-px-2-d5eab2 bases-style-pt-1-6b7d6e" data-ui-part="view-option" data-option-type="property">
|
|
250
|
+
<Label for={option.key} class="bases-style-text-sm-fc7473 bases-style-opacity-50-0b8c50"
|
|
251
|
+
>{option.displayName}</Label
|
|
252
|
+
>
|
|
253
|
+
<Popover.Root
|
|
254
|
+
bind:open={
|
|
255
|
+
() => openPropertyKey === option.key,
|
|
256
|
+
(value) => {
|
|
257
|
+
openPropertyKey = value ? option.key : null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
>
|
|
261
|
+
<Popover.Trigger>
|
|
262
|
+
{#snippet child({ props }: { props: Record<string, any> })}
|
|
263
|
+
{@const selectedProperty = (controller.view.config.get(
|
|
264
|
+
option.key,
|
|
265
|
+
) ?? option.default) as BasesPropertyId | undefined}
|
|
266
|
+
{@const column = selectedProperty
|
|
267
|
+
? controller.getColumn(selectedProperty)
|
|
268
|
+
: undefined}
|
|
269
|
+
<div class="relative bases-style-flex-60fbb7">
|
|
270
|
+
<Button
|
|
271
|
+
{...props}
|
|
272
|
+
id={option.key}
|
|
273
|
+
variant="outline"
|
|
274
|
+
size="sm"
|
|
275
|
+
class="bases-style-h-8-ed8a5d grow bases-style-justify-between-8ef226 bases-style-rounded-none-0c5e91 bases-style-border-none-4a5f0e bases-style-shadow-none-ad47d1 bases-style-outline-none-df37b1"
|
|
276
|
+
>
|
|
277
|
+
<Icon name={[column?.icon ?? "lucide-file"]} />
|
|
278
|
+
{#if column?.displayName}
|
|
279
|
+
{column.displayName}
|
|
280
|
+
{:else}
|
|
281
|
+
<span class="bases-style-text-muted-foreground-bfa603"
|
|
282
|
+
>{option.placeholder ?? "Property"}</span
|
|
283
|
+
>{/if}
|
|
284
|
+
<ArrowUpDown />
|
|
285
|
+
</Button>
|
|
286
|
+
<X
|
|
287
|
+
onclick={(event) => {
|
|
288
|
+
event.preventDefault();
|
|
289
|
+
event.stopPropagation();
|
|
290
|
+
controller.view.config.set(option.key, "");
|
|
291
|
+
openPropertyKey = null;
|
|
292
|
+
}}
|
|
293
|
+
class="absolute bases-style-top-2-9a2db8 bases-style-right-6-82d7e5 bases-style-size-4-f7b5fa"
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
{/snippet}
|
|
297
|
+
</Popover.Trigger>
|
|
298
|
+
<Popover.Content class="bases-style-w-310px-8357f2 bases-style-p-0-8a539c" align="start">
|
|
299
|
+
<Command.Root>
|
|
300
|
+
<Command.Input placeholder="Find or create" />
|
|
301
|
+
<Command.List class="bases-style-p-1-eb6a3c">
|
|
302
|
+
<Command.Empty>No results found.</Command.Empty>
|
|
303
|
+
{#each controller
|
|
304
|
+
.getAllColumns()
|
|
305
|
+
.filter((p) => !option.filter || option.filter(p.id)) as column (column.id)}
|
|
306
|
+
<Command.Item
|
|
307
|
+
onSelect={() => {
|
|
308
|
+
controller.view.config.set(option.key, column.id);
|
|
309
|
+
openPropertyKey = null;
|
|
310
|
+
}}
|
|
311
|
+
>
|
|
312
|
+
<div
|
|
313
|
+
class={cn(
|
|
314
|
+
"bases-style-mr-2-d2347e bases-style-flex-60fbb7 bases-style-size-4-f7b5fa bases-style-items-center-3960ff bases-style-justify-center-86843c",
|
|
315
|
+
(controller.view.config.get(option.key) ??
|
|
316
|
+
option.default) === column.id
|
|
317
|
+
? ""
|
|
318
|
+
: "bases-style-opacity-50-0b8c50 [&_svg]:invisible",
|
|
319
|
+
)}
|
|
320
|
+
>
|
|
321
|
+
<CheckIcon class="bases-style-size-4-f7b5fa" />
|
|
322
|
+
</div>
|
|
323
|
+
<Icon name={[column?.icon ?? "lucide-info"]} />
|
|
324
|
+
<span>{column?.displayName ?? column.id}</span>
|
|
325
|
+
<Command.Shortcut class="bases-style-font-mono-0e6570 bases-style-text-0-75em-c425cc"
|
|
326
|
+
>{column.id}</Command.Shortcut
|
|
327
|
+
>
|
|
328
|
+
</Command.Item>
|
|
329
|
+
{/each}
|
|
330
|
+
</Command.List>
|
|
331
|
+
</Command.Root>
|
|
332
|
+
</Popover.Content>
|
|
333
|
+
</Popover.Root>
|
|
334
|
+
</div>
|
|
335
|
+
{/if}
|
|
336
|
+
{/if}
|
|
337
|
+
{/each}
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
<style>
|
|
341
|
+
.bases-view-settings__group {
|
|
342
|
+
margin: 0.75rem 0 0.25rem;
|
|
343
|
+
padding: 0 0.5rem;
|
|
344
|
+
color: var(--ui-workspace-muted-foreground);
|
|
345
|
+
font-size: 0.75rem;
|
|
346
|
+
font-weight: 600;
|
|
347
|
+
letter-spacing: 0.08em;
|
|
348
|
+
text-transform: uppercase;
|
|
349
|
+
}
|
|
350
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { QueryController } from "./bases.svelte";
|
|
2
|
+
import type { BasesViewBase } from "./models";
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
controller: QueryController;
|
|
5
|
+
view: BasesViewBase;
|
|
6
|
+
};
|
|
7
|
+
declare const ViewSettings: import("svelte").Component<$$ComponentProps, {}, "view">;
|
|
8
|
+
type ViewSettings = ReturnType<typeof ViewSettings>;
|
|
9
|
+
export default ViewSettings;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { type App, type Component } from "@lapis-notes/api";
|
|
3
|
+
import type { BasesDocument } from "./models";
|
|
4
|
+
import { Input } from "@lapismd/design-core/shadcn/input";
|
|
5
|
+
import { onMount, untrack } from "svelte";
|
|
6
|
+
import {
|
|
7
|
+
BasesViewConfig,
|
|
8
|
+
type BasesViewRegistration,
|
|
9
|
+
QueryController,
|
|
10
|
+
} from "./bases.svelte";
|
|
11
|
+
import { createBasesViewRegistrations } from ".";
|
|
12
|
+
import ViewHeader from "./view-header.svelte";
|
|
13
|
+
import Search from "@lucide/svelte/icons/search";
|
|
14
|
+
import X from "@lucide/svelte/icons/x";
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
registrations,
|
|
18
|
+
app,
|
|
19
|
+
document,
|
|
20
|
+
onChange,
|
|
21
|
+
host = null,
|
|
22
|
+
readOnly = false,
|
|
23
|
+
showHeader = true,
|
|
24
|
+
}: {
|
|
25
|
+
app: App;
|
|
26
|
+
registrations?: ReadonlyMap<string, BasesViewRegistration>;
|
|
27
|
+
document: BasesDocument;
|
|
28
|
+
onChange?: (doc: BasesDocument) => void;
|
|
29
|
+
host?: Component | null;
|
|
30
|
+
readOnly?: boolean;
|
|
31
|
+
showHeader?: boolean;
|
|
32
|
+
} = $props();
|
|
33
|
+
|
|
34
|
+
let contentEl: HTMLElement = $state()!;
|
|
35
|
+
let searchInput: HTMLInputElement | null = $state(null);
|
|
36
|
+
|
|
37
|
+
let queryController: QueryController = $state(
|
|
38
|
+
untrack(
|
|
39
|
+
() =>
|
|
40
|
+
new QueryController(
|
|
41
|
+
app,
|
|
42
|
+
document,
|
|
43
|
+
createBasesViewRegistrations(app, registrations),
|
|
44
|
+
onChange ?? (() => {}),
|
|
45
|
+
readOnly,
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
let controllerLoaded = $state(false);
|
|
50
|
+
let componentHost = $derived(host ?? queryController);
|
|
51
|
+
let basesView: BasesViewRegistration | null | undefined = $derived(
|
|
52
|
+
queryController.viewConfig,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
$effect(() => {
|
|
56
|
+
if (
|
|
57
|
+
!controllerLoaded ||
|
|
58
|
+
!basesView ||
|
|
59
|
+
!contentEl ||
|
|
60
|
+
!queryController.selectedView
|
|
61
|
+
) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const renderer = basesView.factory(queryController, contentEl);
|
|
66
|
+
queryController.view = renderer;
|
|
67
|
+
componentHost.addChild(renderer);
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
componentHost.removeChild(renderer);
|
|
71
|
+
renderer.unload();
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
$effect(() => {
|
|
76
|
+
if (!queryController.view || !queryController.selectedView) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
queryController.view.config = new BasesViewConfig(
|
|
81
|
+
queryController.selectedView,
|
|
82
|
+
queryController.columns,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
let show = $state(false);
|
|
87
|
+
onMount(() => {
|
|
88
|
+
queryController.load();
|
|
89
|
+
controllerLoaded = true;
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
show = true;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
controllerLoaded = false;
|
|
96
|
+
queryController.unload();
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
$effect(() => {
|
|
101
|
+
if (!queryController.searchPanelOpen || !searchInput) return;
|
|
102
|
+
searchInput.focus();
|
|
103
|
+
searchInput.select();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
let activeSearchQuery = $derived(queryController.searchQuery.trim());
|
|
107
|
+
|
|
108
|
+
function clearSearch() {
|
|
109
|
+
queryController.searchQuery = "";
|
|
110
|
+
searchInput?.focus();
|
|
111
|
+
}
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<div
|
|
115
|
+
class="bases-view bases-style-flex-60fbb7 bases-style-h-full-668b21 bases-style-min-h-0-fb7302 bases-style-w-full-6da6a3 bases-style-flex-col-8dddea"
|
|
116
|
+
data-ui-component="bases-view"
|
|
117
|
+
data-ui-part="root"
|
|
118
|
+
data-slot="bases-view"
|
|
119
|
+
data-type={queryController.selectedView?.type}
|
|
120
|
+
data-read-only={readOnly ? "true" : "false"}
|
|
121
|
+
>
|
|
122
|
+
{#if showHeader}
|
|
123
|
+
<ViewHeader bind:controller={queryController} />
|
|
124
|
+
{/if}
|
|
125
|
+
{#if queryController.searchPanelOpen}
|
|
126
|
+
<div class="bases-view__search-panel bases-style-border-b-65fdba bases-style-px-2-d5eab2 bases-style-pb-2-f4cc51">
|
|
127
|
+
<div class="relative">
|
|
128
|
+
<Search
|
|
129
|
+
class="bases-view__search-icon absolute bases-style-top-1-2-d694ba bases-style-left-3-22e59b bases-style-size-4-f7b5fa -translate-y-1/2"
|
|
130
|
+
/>
|
|
131
|
+
<Input
|
|
132
|
+
bind:ref={searchInput}
|
|
133
|
+
bind:value={queryController.searchQuery}
|
|
134
|
+
class="bases-style-h-10-426b8b bases-style-w-full-6da6a3 bases-style-pr-36-180815 bases-style-pl-9-9e83b2"
|
|
135
|
+
placeholder="Find..."
|
|
136
|
+
/>
|
|
137
|
+
{#if activeSearchQuery}
|
|
138
|
+
<div
|
|
139
|
+
class="bases-view__search-summary absolute bases-style-top-1-2-d694ba bases-style-right-2-7b2d63 bases-style-flex-60fbb7 -translate-y-1/2 bases-style-items-center-3960ff bases-style-gap-3-1004c0 bases-style-rounded-full-ac204c border bases-style-px-3-0e17f2 bases-style-py-1-660d2e bases-style-text-sm-fc7473"
|
|
140
|
+
>
|
|
141
|
+
<span>Showing {queryController.searchCount}</span>
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
class="bases-view__search-clear bases-style-inline-flex-52083e bases-style-size-5-add63b bases-style-items-center-3960ff bases-style-justify-center-86843c bases-style-rounded-full-ac204c bases-style-transition-colors-ceb69a"
|
|
145
|
+
aria-label="Clear search"
|
|
146
|
+
onclick={clearSearch}
|
|
147
|
+
>
|
|
148
|
+
<X class="bases-style-size-4-f7b5fa" />
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
{/if}
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
{/if}
|
|
155
|
+
{#if queryController.loadError}
|
|
156
|
+
<div class="bases-view__query-error" role="alert">
|
|
157
|
+
<p>Unable to load indexed metadata: {queryController.loadError}</p>
|
|
158
|
+
<button type="button" onclick={() => void queryController.reload()}>
|
|
159
|
+
Retry
|
|
160
|
+
</button>
|
|
161
|
+
</div>
|
|
162
|
+
{:else if show}
|
|
163
|
+
<div
|
|
164
|
+
class="bases-view__content bases-style-min-h-0-fb7302 bases-style-w-full-6da6a3 bases-style-flex-1-36e579"
|
|
165
|
+
bind:this={contentEl}
|
|
166
|
+
></div>
|
|
167
|
+
{:else}
|
|
168
|
+
<div class="bases-view__loading bases-style-flex-60fbb7 bases-style-flex-1-36e579 bases-style-items-center-3960ff bases-style-justify-center-86843c">
|
|
169
|
+
<div class="loader"></div>
|
|
170
|
+
</div>
|
|
171
|
+
{/if}
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<style>
|
|
175
|
+
.bases-view__query-error {
|
|
176
|
+
align-items: center;
|
|
177
|
+
color: var(--destructive, currentColor);
|
|
178
|
+
display: flex;
|
|
179
|
+
flex: 1;
|
|
180
|
+
flex-direction: column;
|
|
181
|
+
gap: 0.75rem;
|
|
182
|
+
justify-content: center;
|
|
183
|
+
padding: 1rem;
|
|
184
|
+
text-align: center;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.bases-view__query-error button {
|
|
188
|
+
border: 1px solid currentColor;
|
|
189
|
+
border-radius: 0.375rem;
|
|
190
|
+
padding: 0.375rem 0.75rem;
|
|
191
|
+
}
|
|
192
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type App, type Component } from "@lapis-notes/api";
|
|
2
|
+
import type { BasesDocument } from "./models";
|
|
3
|
+
import { type BasesViewRegistration } from "./bases.svelte";
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
app: App;
|
|
6
|
+
registrations?: ReadonlyMap<string, BasesViewRegistration>;
|
|
7
|
+
document: BasesDocument;
|
|
8
|
+
onChange?: (doc: BasesDocument) => void;
|
|
9
|
+
host?: Component | null;
|
|
10
|
+
readOnly?: boolean;
|
|
11
|
+
showHeader?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare const View: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
14
|
+
type View = ReturnType<typeof View>;
|
|
15
|
+
export default View;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { App } from "@lapis-notes/api";
|
|
3
|
+
import InternalBasesView from "./bases-view/view.svelte";
|
|
4
|
+
import type { BasesViewRegistration } from "./bases-view/bases.svelte";
|
|
5
|
+
import type { BasesDocument } from "./bases-view/models";
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
app,
|
|
9
|
+
document,
|
|
10
|
+
onChange,
|
|
11
|
+
readOnly = false,
|
|
12
|
+
showHeader = true,
|
|
13
|
+
registrations,
|
|
14
|
+
}: {
|
|
15
|
+
app: App;
|
|
16
|
+
document: BasesDocument;
|
|
17
|
+
onChange?: (document: BasesDocument) => void;
|
|
18
|
+
readOnly?: boolean;
|
|
19
|
+
showHeader?: boolean;
|
|
20
|
+
registrations?: ReadonlyMap<string, BasesViewRegistration>;
|
|
21
|
+
} = $props();
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<InternalBasesView
|
|
25
|
+
{app}
|
|
26
|
+
{document}
|
|
27
|
+
{onChange}
|
|
28
|
+
{readOnly}
|
|
29
|
+
{showHeader}
|
|
30
|
+
{registrations}
|
|
31
|
+
/>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { App } from "@lapis-notes/api";
|
|
2
|
+
import type { BasesViewRegistration } from "./bases-view/bases.svelte";
|
|
3
|
+
import type { BasesDocument } from "./bases-view/models";
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
app: App;
|
|
6
|
+
document: BasesDocument;
|
|
7
|
+
onChange?: (document: BasesDocument) => void;
|
|
8
|
+
readOnly?: boolean;
|
|
9
|
+
showHeader?: boolean;
|
|
10
|
+
registrations?: ReadonlyMap<string, BasesViewRegistration>;
|
|
11
|
+
};
|
|
12
|
+
declare const BasesViewSurface: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
13
|
+
type BasesViewSurface = ReturnType<typeof BasesViewSurface>;
|
|
14
|
+
export default BasesViewSurface;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import "./bases-view/functions";
|
|
2
|
+
import { BasesPlugin } from "./bases-plugin";
|
|
3
|
+
import "./styles.css";
|
|
4
|
+
export { default as manifest } from "../../manifest.json";
|
|
5
|
+
export { BasesPlugin };
|
|
6
|
+
export { default as BasesViewSurface } from "./bases-view-surface.svelte";
|
|
7
|
+
export { BasesViewType, parseBasesDocument, serializeBasesDocument, } from "./bases-view";
|
|
8
|
+
export type { AnyBasesView, BasesDocument, BasesViewBase, CardsView, CustomBasesView, FilterLine, Filters, ListView, MapView, SortColumn, TableView, } from "./bases-view/models";
|
|
9
|
+
export type { BasesViewRegistration } from "./bases-view/bases.svelte";
|
|
10
|
+
export default BasesPlugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./bases-view/functions";
|
|
2
|
+
import { BasesPlugin } from "./bases-plugin";
|
|
3
|
+
import "./styles.css";
|
|
4
|
+
export { default as manifest } from "../../manifest.json";
|
|
5
|
+
export { BasesPlugin };
|
|
6
|
+
export { default as BasesViewSurface } from "./bases-view-surface.svelte";
|
|
7
|
+
export { BasesViewType, parseBasesDocument, serializeBasesDocument, } from "./bases-view";
|
|
8
|
+
export default BasesPlugin;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const EMPTY = Symbol("empty");
|
|
2
|
+
export class SerializedWriteQueue {
|
|
3
|
+
write;
|
|
4
|
+
latest = EMPTY;
|
|
5
|
+
running = null;
|
|
6
|
+
constructor(write) {
|
|
7
|
+
this.write = write;
|
|
8
|
+
}
|
|
9
|
+
enqueue(value) {
|
|
10
|
+
this.latest = value;
|
|
11
|
+
void this.start().catch(() => { });
|
|
12
|
+
}
|
|
13
|
+
async flush() {
|
|
14
|
+
while (this.latest !== EMPTY || this.running) {
|
|
15
|
+
await this.start();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
start() {
|
|
19
|
+
if (!this.running) {
|
|
20
|
+
this.running = this.drain().finally(() => {
|
|
21
|
+
this.running = null;
|
|
22
|
+
if (this.latest !== EMPTY) {
|
|
23
|
+
void this.start().catch(() => { });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return this.running;
|
|
28
|
+
}
|
|
29
|
+
async drain() {
|
|
30
|
+
while (this.latest !== EMPTY) {
|
|
31
|
+
const value = this.latest;
|
|
32
|
+
this.latest = EMPTY;
|
|
33
|
+
await this.write(value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|