@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.
Files changed (155) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE.md +8 -0
  3. package/README.md +20 -0
  4. package/dist/bases-plugin.d.ts +10 -0
  5. package/dist/bases-plugin.js +118 -0
  6. package/dist/bases-view/app-database-query-source.d.ts +30 -0
  7. package/dist/bases-view/app-database-query-source.js +248 -0
  8. package/dist/bases-view/bases.svelte.d.ts +569 -0
  9. package/dist/bases-view/bases.svelte.js +910 -0
  10. package/dist/bases-view/card-view/components/card-placeholder.svelte +68 -0
  11. package/dist/bases-view/card-view/components/card-placeholder.svelte.d.ts +7 -0
  12. package/dist/bases-view/card-view/components/card.svelte +289 -0
  13. package/dist/bases-view/card-view/components/card.svelte.d.ts +7 -0
  14. package/dist/bases-view/card-view/index.d.ts +11 -0
  15. package/dist/bases-view/card-view/index.js +55 -0
  16. package/dist/bases-view/cell-editor-options.d.ts +2 -0
  17. package/dist/bases-view/cell-editor-options.js +26 -0
  18. package/dist/bases-view/cell-editor.svelte +206 -0
  19. package/dist/bases-view/cell-editor.svelte.d.ts +12 -0
  20. package/dist/bases-view/cell.svelte +132 -0
  21. package/dist/bases-view/cell.svelte.d.ts +16 -0
  22. package/dist/bases-view/column-definition.d.ts +22 -0
  23. package/dist/bases-view/column-definition.js +149 -0
  24. package/dist/bases-view/columns.d.ts +15 -0
  25. package/dist/bases-view/columns.js +103 -0
  26. package/dist/bases-view/components/Tag.svelte +14 -0
  27. package/dist/bases-view/components/Tag.svelte.d.ts +8 -0
  28. package/dist/bases-view/components/autocomplete.svelte +45 -0
  29. package/dist/bases-view/components/autocomplete.svelte.d.ts +17 -0
  30. package/dist/bases-view/components/card-image.svelte +121 -0
  31. package/dist/bases-view/components/card-image.svelte.d.ts +11 -0
  32. package/dist/bases-view/components/column-visibility.svelte +388 -0
  33. package/dist/bases-view/components/column-visibility.svelte.d.ts +13 -0
  34. package/dist/bases-view/components/datetime.svelte +35 -0
  35. package/dist/bases-view/components/datetime.svelte.d.ts +7 -0
  36. package/dist/bases-view/components/file.svelte +31 -0
  37. package/dist/bases-view/components/file.svelte.d.ts +8 -0
  38. package/dist/bases-view/components/filters/filter-editor.svelte +102 -0
  39. package/dist/bases-view/components/filters/filter-editor.svelte.d.ts +10 -0
  40. package/dist/bases-view/components/filters/filter-group.svelte +142 -0
  41. package/dist/bases-view/components/filters/filter-group.svelte.d.ts +11 -0
  42. package/dist/bases-view/components/filters/filter-properties.svelte +77 -0
  43. package/dist/bases-view/components/filters/filter-properties.svelte.d.ts +8 -0
  44. package/dist/bases-view/components/filters/filter-root.svelte +112 -0
  45. package/dist/bases-view/components/filters/filter-root.svelte.d.ts +12 -0
  46. package/dist/bases-view/components/filters/filter-row.svelte +351 -0
  47. package/dist/bases-view/components/filters/filter-row.svelte.d.ts +11 -0
  48. package/dist/bases-view/components/group/group-root.svelte +120 -0
  49. package/dist/bases-view/components/group/group-root.svelte.d.ts +8 -0
  50. package/dist/bases-view/components/limit.svelte +100 -0
  51. package/dist/bases-view/components/limit.svelte.d.ts +12 -0
  52. package/dist/bases-view/components/query-editor.svelte +230 -0
  53. package/dist/bases-view/components/query-editor.svelte.d.ts +15 -0
  54. package/dist/bases-view/components/sort/sort-direction-labels.d.ts +9 -0
  55. package/dist/bases-view/components/sort/sort-direction-labels.js +25 -0
  56. package/dist/bases-view/components/sort/sort-root.svelte +239 -0
  57. package/dist/bases-view/components/sort/sort-root.svelte.d.ts +11 -0
  58. package/dist/bases-view/components/sort/sort-row.svelte +190 -0
  59. package/dist/bases-view/components/sort/sort-row.svelte.d.ts +15 -0
  60. package/dist/bases-view/components/summary-strip.svelte +56 -0
  61. package/dist/bases-view/components/summary-strip.svelte.d.ts +8 -0
  62. package/dist/bases-view/components/tag-chip.svelte +47 -0
  63. package/dist/bases-view/components/tag-chip.svelte.d.ts +12 -0
  64. package/dist/bases-view/components/views.svelte +178 -0
  65. package/dist/bases-view/components/views.svelte.d.ts +12 -0
  66. package/dist/bases-view/data-table-adapter/data-table.svelte.d.ts +45 -0
  67. package/dist/bases-view/data-table-adapter/data-table.svelte.js +112 -0
  68. package/dist/bases-view/data-table-adapter/flex-render.svelte +43 -0
  69. package/dist/bases-view/data-table-adapter/flex-render.svelte.d.ts +30 -0
  70. package/dist/bases-view/data-table-adapter/index.d.ts +3 -0
  71. package/dist/bases-view/data-table-adapter/index.js +3 -0
  72. package/dist/bases-view/data-table-adapter/render-helpers.d.ts +101 -0
  73. package/dist/bases-view/data-table-adapter/render-helpers.js +110 -0
  74. package/dist/bases-view/db.d.ts +31 -0
  75. package/dist/bases-view/db.js +174 -0
  76. package/dist/bases-view/document-core.d.ts +2 -0
  77. package/dist/bases-view/document-core.js +166 -0
  78. package/dist/bases-view/export-core.d.ts +4 -0
  79. package/dist/bases-view/export-core.js +31 -0
  80. package/dist/bases-view/file-fields-core.d.ts +31 -0
  81. package/dist/bases-view/file-fields-core.js +114 -0
  82. package/dist/bases-view/filter-parser.d.ts +27 -0
  83. package/dist/bases-view/filter-parser.js +315 -0
  84. package/dist/bases-view/functions-core.d.ts +66 -0
  85. package/dist/bases-view/functions-core.js +265 -0
  86. package/dist/bases-view/functions.d.ts +5 -0
  87. package/dist/bases-view/functions.js +107 -0
  88. package/dist/bases-view/fuzzy-search.d.ts +6 -0
  89. package/dist/bases-view/fuzzy-search.js +15 -0
  90. package/dist/bases-view/hooks/useResizeObserver.svelte.d.ts +7 -0
  91. package/dist/bases-view/hooks/useResizeObserver.svelte.js +26 -0
  92. package/dist/bases-view/index.d.ts +66 -0
  93. package/dist/bases-view/index.js +231 -0
  94. package/dist/bases-view/list-view/index.d.ts +10 -0
  95. package/dist/bases-view/list-view/index.js +17 -0
  96. package/dist/bases-view/list-view/list.svelte +156 -0
  97. package/dist/bases-view/list-view/list.svelte.d.ts +7 -0
  98. package/dist/bases-view/map-view/index.d.ts +10 -0
  99. package/dist/bases-view/map-view/index.js +17 -0
  100. package/dist/bases-view/map-view/view.svelte +28 -0
  101. package/dist/bases-view/map-view/view.svelte.d.ts +7 -0
  102. package/dist/bases-view/metadata-invalidation-core.d.ts +34 -0
  103. package/dist/bases-view/metadata-invalidation-core.js +162 -0
  104. package/dist/bases-view/metadata-type-info.d.ts +7 -0
  105. package/dist/bases-view/metadata-type-info.js +10 -0
  106. package/dist/bases-view/models.d.ts +90 -0
  107. package/dist/bases-view/models.js +133 -0
  108. package/dist/bases-view/query-result-core.d.ts +8 -0
  109. package/dist/bases-view/query-result-core.js +16 -0
  110. package/dist/bases-view/resource-url.d.ts +2 -0
  111. package/dist/bases-view/resource-url.js +21 -0
  112. package/dist/bases-view/search-core.d.ts +9 -0
  113. package/dist/bases-view/search-core.js +20 -0
  114. package/dist/bases-view/summary-core.d.ts +7 -0
  115. package/dist/bases-view/summary-core.js +17 -0
  116. package/dist/bases-view/table-view/components/div-table.svelte +464 -0
  117. package/dist/bases-view/table-view/components/div-table.svelte.d.ts +32 -0
  118. package/dist/bases-view/table-view/components/dnd-style.d.ts +3 -0
  119. package/dist/bases-view/table-view/components/dnd-style.js +6 -0
  120. package/dist/bases-view/table-view/components/sort-header.svelte +48 -0
  121. package/dist/bases-view/table-view/components/sort-header.svelte.d.ts +11 -0
  122. package/dist/bases-view/table-view/components/table-column-tracks.d.ts +14 -0
  123. package/dist/bases-view/table-view/components/table-column-tracks.js +27 -0
  124. package/dist/bases-view/table-view/components/table-header.svelte +146 -0
  125. package/dist/bases-view/table-view/components/table-header.svelte.d.ts +10 -0
  126. package/dist/bases-view/table-view/components/table-placeholder.svelte +46 -0
  127. package/dist/bases-view/table-view/components/table-placeholder.svelte.d.ts +30 -0
  128. package/dist/bases-view/table-view/components/table-virtualizer-core.d.ts +13 -0
  129. package/dist/bases-view/table-view/components/table-virtualizer-core.js +12 -0
  130. package/dist/bases-view/table-view/components/table.svelte +997 -0
  131. package/dist/bases-view/table-view/components/table.svelte.d.ts +32 -0
  132. package/dist/bases-view/table-view/index.svelte.d.ts +10 -0
  133. package/dist/bases-view/table-view/index.svelte.js +30 -0
  134. package/dist/bases-view/unknown-view/index.d.ts +10 -0
  135. package/dist/bases-view/unknown-view/index.js +19 -0
  136. package/dist/bases-view/unknown-view/view.svelte +11 -0
  137. package/dist/bases-view/unknown-view/view.svelte.d.ts +6 -0
  138. package/dist/bases-view/view-header.svelte +142 -0
  139. package/dist/bases-view/view-header.svelte.d.ts +28 -0
  140. package/dist/bases-view/view-registration-inventory.d.ts +22 -0
  141. package/dist/bases-view/view-registration-inventory.js +7 -0
  142. package/dist/bases-view/view-settings.svelte +350 -0
  143. package/dist/bases-view/view-settings.svelte.d.ts +9 -0
  144. package/dist/bases-view/view.svelte +192 -0
  145. package/dist/bases-view/view.svelte.d.ts +15 -0
  146. package/dist/bases-view-surface.svelte +31 -0
  147. package/dist/bases-view-surface.svelte.d.ts +14 -0
  148. package/dist/index.d.ts +10 -0
  149. package/dist/index.js +8 -0
  150. package/dist/serialized-write-queue.d.ts +10 -0
  151. package/dist/serialized-write-queue.js +36 -0
  152. package/dist/styles.css +2164 -0
  153. package/manifest.json +33 -0
  154. package/package.json +90 -0
  155. 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;
@@ -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,10 @@
1
+ export declare class SerializedWriteQueue<T> {
2
+ private readonly write;
3
+ private latest;
4
+ private running;
5
+ constructor(write: (value: T) => Promise<void>);
6
+ enqueue(value: T): void;
7
+ flush(): Promise<void>;
8
+ private start;
9
+ private drain;
10
+ }
@@ -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
+ }