@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,68 @@
1
+ <script lang="ts">
2
+ import { Skeleton } from "@lapismd/design-core/shadcn/skeleton";
3
+ import type { BasesView } from "../../bases.svelte";
4
+ import { toNumber } from "..";
5
+
6
+ let {
7
+ view,
8
+ }: {
9
+ view: BasesView;
10
+ } = $props();
11
+ let queryResults = $derived(view.data);
12
+ let cardSize: number = $derived(toNumber(view.config.get("cardSize")) || 0);
13
+ let imageAspectRatio: number = $derived(
14
+ toNumber(view.config.get("imageAspectRatio")) || 1,
15
+ );
16
+ let imageFit = $derived(view.config.get("imageFit") || "contain");
17
+ let imageSize: number = $derived.by(() => {
18
+ if (!view.config.get("image")) {
19
+ return 0;
20
+ }
21
+ return cardSize * imageAspectRatio;
22
+ });
23
+
24
+ let cardHeight = $derived.by(() => {
25
+ return view.config.getOrder().length * 56 + 17 + imageSize + 28;
26
+ });
27
+ </script>
28
+
29
+ <div
30
+ class="bases-card-grid bases-style-h-full-668b21 bases-style-w-full-6da6a3 bases-style-flex-grow-95a3df bases-style-overflow-auto-73fc3f bases-style-pb-16-db15dd bases-style-pl-5-151215"
31
+ style={`scrollbar-gutter: stable; transform: translate3d(0,0,0);`}
32
+ >
33
+ <div class="bases-style-flex-60fbb7 bases-style-w-full-6da6a3 bases-style-items-center-3960ff bases-style-justify-center-86843c">
34
+ <div class="loader"></div>
35
+ </div>
36
+ <div class="bases-style-flex-60fbb7 bases-style-flex-wrap-1eb5c6 bases-style-gap-2-77a2a2">
37
+ {#each queryResults.data as record}
38
+ <div
39
+ style={`width: ${cardSize}}px; height: ${cardHeight}px;`}
40
+ class="bases-card-placeholder bases-style-w-full-6da6a3 bases-style-overflow-hidden-2cd02d bases-style-rounded-lg-5f22e6 border bases-style-text-sm-fc7473 bases-style-whitespace-nowrap-e82ae8"
41
+ >
42
+ <div class="bases-style-h-full-668b21 bases-style-w-full-6da6a3">
43
+ {#if imageSize}
44
+ <div
45
+ class="bases-card__image bases-style-rounded-t-lg-bc0d7f"
46
+ style={`height: ${imageSize}px; background-size: ${imageFit}`}
47
+ ></div>
48
+ {/if}
49
+
50
+ {#each view.config.getOrder() as o}
51
+ <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-2-f46b61">
52
+ <div
53
+ class="bases-card-placeholder__label bases-style-overflow-hidden-2cd02d bases-style-text-ellipsis-6e2e11 bases-style-whitespace-nowrap-e82ae8"
54
+ >
55
+ <Skeleton class="bases-style-h-20px-fa9795 bases-style-w-full-6da6a3" />
56
+ </div>
57
+ <div
58
+ class="bases-style-h-full-668b21 bases-style-w-full-6da6a3 bases-style-overflow-hidden-2cd02d bases-style-text-ellipsis-6e2e11 bases-style-whitespace-nowrap-e82ae8"
59
+ >
60
+ <Skeleton class="bases-style-h-28px-d8acda bases-style-w-full-6da6a3" />
61
+ </div>
62
+ </div>
63
+ {/each}
64
+ </div>
65
+ </div>
66
+ {/each}
67
+ </div>
68
+ </div>
@@ -0,0 +1,7 @@
1
+ import type { BasesView } from "../../bases.svelte";
2
+ type $$ComponentProps = {
3
+ view: BasesView;
4
+ };
5
+ declare const CardPlaceholder: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type CardPlaceholder = ReturnType<typeof CardPlaceholder>;
7
+ export default CardPlaceholder;
@@ -0,0 +1,289 @@
1
+ <script lang="ts">
2
+ import type { BasesEntry, BasesView } from "../../bases.svelte";
3
+ import ChevronDown from "@lucide/svelte/icons/chevron-down";
4
+ import ChevronRight from "@lucide/svelte/icons/chevron-right";
5
+ import { useResizeObserver } from "../../hooks/useResizeObserver.svelte";
6
+ import throttle from "lodash-es/throttle";
7
+ import { createVirtualizer, type MetadataType } from "@lapis-notes/api";
8
+ import { toNumber } from "..";
9
+ import { ScrollArea } from "@lapismd/design-core/shadcn/scroll-area";
10
+ import Cell from "../../cell.svelte";
11
+ import SummaryStrip from "../../components/summary-strip.svelte";
12
+ import { getMetadataTypeInfo } from "../../metadata-type-info";
13
+ import { onMount } from "svelte";
14
+ import CardPlaceholder from "./card-placeholder.svelte";
15
+ import CardImage from "../../components/card-image.svelte";
16
+
17
+ let {
18
+ view,
19
+ }: {
20
+ view: BasesView;
21
+ } = $props();
22
+ let queryResults = $derived(view.data);
23
+ let groups = $derived(queryResults.groupedData);
24
+ let groupBy = $derived(queryResults.groupBy);
25
+ let groupLabel = $derived.by(() => {
26
+ if (!groupBy) return "";
27
+ return view.config.getDisplayName(groupBy) || groupBy;
28
+ });
29
+ let cardSize: number = $derived(toNumber(view.config.get("cardSize")) || 0);
30
+ let imageAspectRatio: number = $derived(
31
+ toNumber(view.config.get("imageAspectRatio")) || 1,
32
+ );
33
+
34
+ let imageSize: number = $derived.by(() => {
35
+ if (!view.config.get("image")) {
36
+ return 0;
37
+ }
38
+ return cardSize * imageAspectRatio;
39
+ });
40
+ let types = $derived.by(() => {
41
+ return queryResults.properties
42
+ .filter((it) => !(it.startsWith("file.") || it.startsWith("formula.")))
43
+ .reduce<Record<string, { type: MetadataType; icon: string }>>(
44
+ (acc, id) => {
45
+ const key = id.startsWith("note.") ? id.substring(5) : id;
46
+ const { type, icon } = getMetadataTypeInfo(
47
+ key,
48
+ "text",
49
+ view.controller.app,
50
+ );
51
+
52
+ acc[id] = { type, icon };
53
+ acc[key] = { type, icon };
54
+ return acc;
55
+ },
56
+ {},
57
+ );
58
+ });
59
+
60
+ let containerSize = useResizeObserver();
61
+ let containerEl: HTMLDivElement = $state()!;
62
+ let cardHeight = $derived.by(() => {
63
+ return view.config.getOrder().length * 56 + 17 + imageSize + 28;
64
+ });
65
+ let imageFit = $derived(String(view.config.get("imageFit") || "contain"));
66
+ let imageColumn = $derived(String(view.config.get("image") || ""));
67
+
68
+ let gap = $state({ x: 10, y: 10 });
69
+ let columns = $state(3);
70
+ let rows = $derived(Math.ceil(queryResults.data.length / columns));
71
+ let collapsedGroups = $state<Record<string, boolean>>({});
72
+
73
+ function groupId(
74
+ group: { key?: { toString(): string } | null },
75
+ index: number,
76
+ ) {
77
+ return `${groupBy || "ungrouped"}:${group.key?.toString() ?? `ungrouped_${index}`}`;
78
+ }
79
+
80
+ function isCollapsed(
81
+ group: { key?: { toString(): string } | null },
82
+ index: number,
83
+ ) {
84
+ return !!collapsedGroups[groupId(group, index)];
85
+ }
86
+
87
+ function toggleGroup(
88
+ group: { key?: { toString(): string } | null },
89
+ index: number,
90
+ ) {
91
+ const key = groupId(group, index);
92
+ collapsedGroups = {
93
+ ...collapsedGroups,
94
+ [key]: !collapsedGroups[key],
95
+ };
96
+ }
97
+
98
+ const handleResize = throttle(() => {
99
+ const size = Math.floor(containerSize.size.width / (cardSize + gap.x));
100
+ columns = size;
101
+ }, 200);
102
+
103
+ $effect(() => {
104
+ if (!containerSize.size.width) return;
105
+ if (!cardSize) return;
106
+ handleResize();
107
+ });
108
+
109
+ let rowVirtualizer = createVirtualizer({
110
+ get count() {
111
+ return rows;
112
+ },
113
+ getScrollElement: () => containerEl,
114
+ estimateSize: () => cardHeight + gap.y,
115
+ measureElement:
116
+ typeof window !== "undefined" &&
117
+ navigator.userAgent.indexOf("Firefox") === -1
118
+ ? (element) => element?.getBoundingClientRect().height
119
+ : undefined,
120
+ overscan: 2,
121
+ });
122
+ $effect(() => {
123
+ rowVirtualizer.setOptions({ count: rows });
124
+ });
125
+
126
+ let columnVirtualizer = createVirtualizer({
127
+ horizontal: true,
128
+ get count() {
129
+ return columns;
130
+ },
131
+ getScrollElement: () => containerEl,
132
+ estimateSize: () => cardSize + gap.x,
133
+ measureElement:
134
+ typeof window !== "undefined" &&
135
+ navigator.userAgent.indexOf("Firefox") === -1
136
+ ? (element) => element?.getBoundingClientRect().height
137
+ : undefined,
138
+ overscan: 2,
139
+ });
140
+ $effect(() => {
141
+ columnVirtualizer.setOptions({ count: columns });
142
+ });
143
+
144
+ $effect(() => {
145
+ const _ = [cardHeight, columns];
146
+ rowVirtualizer.measure();
147
+ columnVirtualizer.measure();
148
+ });
149
+
150
+ let show = $state(false);
151
+ onMount(() => {
152
+ setTimeout(() => {
153
+ show = true;
154
+ });
155
+ });
156
+ </script>
157
+
158
+ {#snippet cardBody(row: BasesEntry)}
159
+ <ScrollArea class="bases-style-h-full-668b21 bases-style-w-full-6da6a3">
160
+ {#if imageSize}
161
+ <CardImage
162
+ app={view.controller.app}
163
+ value={imageColumn ? row.getValue(imageColumn) : null}
164
+ sourceFile={row.file}
165
+ height={imageSize}
166
+ fit={imageFit || "contain"}
167
+ />
168
+ {/if}
169
+
170
+ {#each view.config.getOrder() as id}
171
+ <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-2-f46b61">
172
+ <div
173
+ class="bases-card__label bases-style-overflow-hidden-2cd02d bases-style-text-ellipsis-6e2e11 bases-style-whitespace-nowrap-e82ae8"
174
+ >
175
+ {view.config.getDisplayName(id)}
176
+ </div>
177
+ <div
178
+ class="bases-style-h-full-668b21 bases-style-w-full-6da6a3 bases-style-overflow-hidden-2cd02d bases-style-text-ellipsis-6e2e11 bases-style-whitespace-nowrap-e82ae8"
179
+ >
180
+ <Cell
181
+ app={view.controller.app}
182
+ name={id}
183
+ file={row.file}
184
+ readOnly={view.controller.readOnly}
185
+ type={types[id]?.type}
186
+ value={row.getValue(id)}
187
+ class="bases-style-z-1-0bcb04 bases-style-h-full-668b21 bases-style-w-full-6da6a3 bases-style-min-w-full-a1e7a8 bases-style-overflow-hidden-2cd02d bases-style-text-nowrap-621d3b bases-style-overflow-ellipsis-5b2ef5"
188
+ />
189
+ </div>
190
+ </div>
191
+ {/each}
192
+ </ScrollArea>
193
+ {/snippet}
194
+
195
+ {#if show}
196
+ <div
197
+ class="bases-card-grid bases-style-h-full-668b21 bases-style-w-full-6da6a3 bases-style-flex-grow-95a3df bases-style-overflow-auto-73fc3f bases-style-pb-16-db15dd bases-style-pl-5-151215"
198
+ data-layout="cards"
199
+ data-ui-component="bases-card-view"
200
+ data-ui-part="viewport"
201
+ bind:this={
202
+ () => containerEl,
203
+ (value) => {
204
+ containerEl = value;
205
+ containerSize.ref = value;
206
+ }
207
+ }
208
+ style={`scrollbar-gutter: stable; transform: translate3d(0,0,0);`}
209
+ >
210
+ <SummaryStrip
211
+ {view}
212
+ class="bases-card-grid__summary sticky bases-style-top-0-216740 bases-style-left-0-c78fac bases-style-z-90-3d4b39 bases-style-mb-4-da0198 bases-style-py-3-1b2d54 bases-style-pr-5-752a63 backdrop-blur"
213
+ />
214
+ {#if groupBy}
215
+ <div class="bases-style-space-y-6-b3542e bases-style-pr-5-752a63">
216
+ {#each groups as group, index (`${index}_${group.key?.toString() ?? "ungrouped"}`)}
217
+ <section class="bases-style-space-y-3-6ed543">
218
+ <div
219
+ class="bases-card-grid__group-header sticky bases-style-top-0-216740 bases-style-z-10-236812 bases-style-border-b-65fdba bases-style-px-1-d8e0e3 bases-style-py-3-1b2d54 bases-style-text-xs-359090 bases-style-font-semibold-e83a70 bases-style-tracking-0-18em-a82f6f uppercase backdrop-blur"
220
+ >
221
+ <button
222
+ type="button"
223
+ class="bases-style-flex-60fbb7 bases-style-w-full-6da6a3 bases-style-items-center-3960ff bases-style-gap-2-77a2a2 bases-style-text-left-2eba0d"
224
+ aria-expanded={!isCollapsed(group, index)}
225
+ onclick={() => toggleGroup(group, index)}
226
+ >
227
+ {#if isCollapsed(group, index)}
228
+ <ChevronRight class="bases-style-size-4-f7b5fa bases-style-shrink-0-012fbd" />
229
+ {:else}
230
+ <ChevronDown class="bases-style-size-4-f7b5fa bases-style-shrink-0-012fbd" />
231
+ {/if}
232
+ <span>{groupLabel}</span>
233
+ <span class="bases-card-grid__group-value">
234
+ {#if group.hasKey()}
235
+ {group.key?.toString()}
236
+ {:else}
237
+ Ungrouped
238
+ {/if}
239
+ </span>
240
+ </button>
241
+ </div>
242
+ {#if !isCollapsed(group, index)}
243
+ <div
244
+ class="bases-style-grid-f3c543 bases-style-justify-start-4b5cc1"
245
+ style={`grid-template-columns: repeat(auto-fill, minmax(${cardSize}px, ${cardSize}px)); gap: ${gap.y}px ${gap.x}px;`}
246
+ >
247
+ {#each group.entries as row (row.id)}
248
+ <div
249
+ class="bases-card bases-style-overflow-hidden-2cd02d bases-style-rounded-lg-5f22e6 border bases-style-text-sm-fc7473 bases-style-whitespace-nowrap-e82ae8"
250
+ data-ui-part="card"
251
+ style={`width: ${cardSize}px; height: ${cardHeight}px;`}
252
+ >
253
+ {@render cardBody(row)}
254
+ </div>
255
+ {/each}
256
+ </div>
257
+ {/if}
258
+ </section>
259
+ {/each}
260
+ </div>
261
+ {:else}
262
+ <div
263
+ class="relative"
264
+ style={`height: ${rowVirtualizer.getTotalSize() + cardHeight}px; width: ${columnVirtualizer.getTotalSize()}px;`}
265
+ >
266
+ {#each rowVirtualizer.getVirtualItems() as virtualRow (virtualRow.key)}
267
+ {#each columnVirtualizer.getVirtualItems() as virtualColumn (virtualColumn.key)}
268
+ {@const row =
269
+ queryResults.data[
270
+ virtualRow.index * columns + virtualColumn.index
271
+ ]}
272
+ {#if row}
273
+ <div
274
+ data-coord={`${virtualRow.index},${virtualColumn.index}`}
275
+ data-ui-part="card"
276
+ style={`width: ${cardSize}px; height: ${cardHeight}px;transform: translateX(${virtualColumn.start}px) translateY(${virtualRow.start}px)`}
277
+ class="bases-card absolute bases-style-top-0-216740 bases-style-left-0-c78fac bases-style-w-full-6da6a3 bases-style-overflow-hidden-2cd02d bases-style-rounded-lg-5f22e6 border bases-style-text-sm-fc7473 bases-style-whitespace-nowrap-e82ae8"
278
+ >
279
+ {@render cardBody(row)}
280
+ </div>
281
+ {/if}
282
+ {/each}
283
+ {/each}
284
+ </div>
285
+ {/if}
286
+ </div>
287
+ {:else}
288
+ <CardPlaceholder {view} />
289
+ {/if}
@@ -0,0 +1,7 @@
1
+ import type { BasesView } from "../../bases.svelte";
2
+ type $$ComponentProps = {
3
+ view: BasesView;
4
+ };
5
+ declare const Card: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Card = ReturnType<typeof Card>;
7
+ export default Card;
@@ -0,0 +1,11 @@
1
+ import type { BasesOptions } from "@lapis-notes/api";
2
+ import { BasesView, QueryController } from "../bases.svelte";
3
+ export declare function toNumber(value: unknown): number;
4
+ export declare class CardView extends BasesView {
5
+ readonly controller: QueryController;
6
+ readonly scrollEl: HTMLElement;
7
+ type: string;
8
+ constructor(controller: QueryController, scrollEl: HTMLElement);
9
+ onDataUpdated(): void;
10
+ static getViewOptions(): BasesOptions[];
11
+ }
@@ -0,0 +1,55 @@
1
+ import { BasesView, QueryController } from "../bases.svelte";
2
+ import CardViewComponent from "./components/card.svelte";
3
+ export function toNumber(value) {
4
+ if (typeof value === "number")
5
+ return value;
6
+ const n = Number(value);
7
+ return Number.isFinite(n) ? n : 0;
8
+ }
9
+ export class CardView extends BasesView {
10
+ controller;
11
+ scrollEl;
12
+ type = "cards";
13
+ constructor(controller, scrollEl) {
14
+ super(controller);
15
+ this.controller = controller;
16
+ this.scrollEl = scrollEl;
17
+ this.mountViewComponent(CardViewComponent, { view: this }, scrollEl);
18
+ }
19
+ onDataUpdated() { }
20
+ static getViewOptions() {
21
+ return [
22
+ {
23
+ key: "cardSize",
24
+ type: "slider",
25
+ displayName: "Card size",
26
+ min: 50,
27
+ max: 800,
28
+ step: 1,
29
+ },
30
+ {
31
+ key: "image",
32
+ type: "property",
33
+ displayName: "Image",
34
+ },
35
+ {
36
+ key: "imageFit",
37
+ displayName: "Image fit",
38
+ type: "dropdown",
39
+ default: "contain",
40
+ options: {
41
+ contain: "Contain",
42
+ cover: "Cover",
43
+ },
44
+ },
45
+ {
46
+ key: "imageAspectRatio",
47
+ type: "slider",
48
+ displayName: "image aspect ration",
49
+ min: 0.25,
50
+ max: 2.5,
51
+ step: 0.05,
52
+ },
53
+ ];
54
+ }
55
+ }
@@ -0,0 +1,2 @@
1
+ export declare function metadataPropertyKey(propertyId: string): string;
2
+ export declare function collectMetadataSuggestions(values: readonly unknown[], splitDelimited?: boolean): string[];
@@ -0,0 +1,26 @@
1
+ export function metadataPropertyKey(propertyId) {
2
+ return propertyId.startsWith("note.")
3
+ ? propertyId.slice("note.".length)
4
+ : propertyId;
5
+ }
6
+ export function collectMetadataSuggestions(values, splitDelimited = false) {
7
+ const suggestions = new Set();
8
+ function add(value) {
9
+ if (Array.isArray(value)) {
10
+ value.forEach(add);
11
+ return;
12
+ }
13
+ if (value === null || value === undefined || value === "")
14
+ return;
15
+ const candidates = splitDelimited && typeof value === "string"
16
+ ? value.split(/[,;]+/u)
17
+ : [String(value)];
18
+ for (const candidate of candidates) {
19
+ const normalized = candidate.trim();
20
+ if (normalized)
21
+ suggestions.add(normalized);
22
+ }
23
+ }
24
+ values.forEach(add);
25
+ return [...suggestions];
26
+ }
@@ -0,0 +1,206 @@
1
+ <script lang="ts">
2
+ import { cn, type App } from "@lapis-notes/api";
3
+ import Autocomplete from "./components/autocomplete.svelte";
4
+ import ChipAutocomplete from "@lapismd/design-core/forms/ChipAutocomplete.svelte";
5
+ import { Input } from "@lapismd/design-core/shadcn/input";
6
+ import { DateTime } from "luxon";
7
+ import {
8
+ collectMetadataSuggestions,
9
+ metadataPropertyKey,
10
+ } from "./cell-editor-options";
11
+
12
+ let {
13
+ app,
14
+ type,
15
+ name,
16
+ value = $bindable(),
17
+ class: className,
18
+ onValueChange,
19
+ }: {
20
+ app: App;
21
+ type: string;
22
+ name: string;
23
+ value: any;
24
+ class?: string;
25
+ onValueChange?: (name: string, value: any) => void;
26
+ } = $props();
27
+
28
+ function fileOptions() {
29
+ return app.vault.getFiles().map((f) => {
30
+ return { label: f.path, value: f.path };
31
+ });
32
+ }
33
+
34
+ function folderOptions() {
35
+ return app.vault.getAllFolders().map((f) => {
36
+ return { label: f.path || "/", value: f.path || "/" };
37
+ });
38
+ }
39
+
40
+ function onChange(event: FocusEvent) {
41
+ const target = event.target as HTMLInputElement;
42
+ if (target.type === "checkbox") {
43
+ value = target.checked;
44
+ } else if (target.type === "number") {
45
+ const trimmed = target.value.trim();
46
+ value = trimmed === "" ? null : +trimmed;
47
+ } else {
48
+ value = target.value;
49
+ }
50
+ onValueChange?.(name, value);
51
+ }
52
+
53
+ function onDateChange(event: Event) {
54
+ value = (event.target as HTMLInputElement).value;
55
+ if (value) {
56
+ onValueChange?.(name, value);
57
+ } else {
58
+ onValueChange?.(name, null);
59
+ }
60
+ }
61
+
62
+ function metadataSuggestions(key: string, splitDelimited = false) {
63
+ return collectMetadataSuggestions(
64
+ app.metadataTypeManager.getValues(metadataPropertyKey(key)),
65
+ splitDelimited,
66
+ );
67
+ }
68
+
69
+ function metadataOptions(key: string) {
70
+ return metadataSuggestions(key).map((suggestion) => ({
71
+ label: suggestion,
72
+ value: suggestion,
73
+ }));
74
+ }
75
+
76
+ function currentTagValues() {
77
+ return collectMetadataSuggestions([value], true);
78
+ }
79
+
80
+ function onTagsChange(values: string[]) {
81
+ value = values;
82
+ onValueChange?.(name, values);
83
+ }
84
+
85
+ function toDateTime(value: unknown) {
86
+ if (value instanceof DateTime) {
87
+ return value.toISO()?.substring(0, 16);
88
+ } else if (value instanceof Date) {
89
+ return value.toISOString().substring(0, 16);
90
+ } else if (typeof value === "number") {
91
+ return DateTime.fromMillis(value)?.toISO()?.substring(0, 16);
92
+ }
93
+ return value;
94
+ }
95
+
96
+ function toDate(value: unknown) {
97
+ if (value instanceof DateTime) {
98
+ return value.toISODate();
99
+ } else if (value instanceof Date) {
100
+ return DateTime.fromJSDate(value).toISODate();
101
+ } else if (typeof value === "number") {
102
+ return DateTime.fromMillis(value).toISODate();
103
+ }
104
+ return value;
105
+ }
106
+
107
+ const editorLabel = $derived(
108
+ name.startsWith("note.") ? name.slice("note.".length) : name,
109
+ );
110
+ </script>
111
+
112
+ <div
113
+ class="bases-cell-editor"
114
+ data-ui-component="bases-cell-editor"
115
+ data-ui-part="root"
116
+ data-type={type}
117
+ >
118
+ {#if type === "file"}
119
+ <Autocomplete
120
+ placeholder="—"
121
+ aria-label={editorLabel}
122
+ bind:value
123
+ options={fileOptions()}
124
+ class={className}
125
+ onSelect={(selectedValue) => onValueChange?.(name, selectedValue)}
126
+ />
127
+ {:else if type === "folder"}
128
+ <Autocomplete
129
+ placeholder="—"
130
+ aria-label={editorLabel}
131
+ bind:value
132
+ options={folderOptions()}
133
+ class={className}
134
+ onSelect={(selectedValue) => onValueChange?.(name, selectedValue)}
135
+ />
136
+ {:else if type === "none"}
137
+ <span class="bases-cell-editor__empty"></span>
138
+ {:else if type === "datetime"}
139
+ <Input
140
+ type="datetime-local"
141
+ placeholder="—"
142
+ aria-label={editorLabel}
143
+ value={toDateTime(value)}
144
+ onblur={(evt) => onDateChange(evt)}
145
+ onchange={(evt) => onDateChange(evt)}
146
+ class={cn("bases-cell-editor__control", className)}
147
+ data-ui-part="control"
148
+ />
149
+ {:else if type === "date"}
150
+ <Input
151
+ type="date"
152
+ placeholder="—"
153
+ aria-label={editorLabel}
154
+ value={toDate(value)}
155
+ onblur={(evt) => onDateChange(evt)}
156
+ onchange={(evt) => onDateChange(evt)}
157
+ class={cn("bases-cell-editor__control", className)}
158
+ data-ui-part="control"
159
+ />
160
+ {:else if type === "number"}
161
+ <Input
162
+ type="number"
163
+ placeholder="—"
164
+ aria-label={editorLabel}
165
+ {value}
166
+ onblur={(evt) => onChange(evt)}
167
+ class={cn("bases-cell-editor__control", className)}
168
+ data-ui-part="control"
169
+ />
170
+ {:else if type === "checkbox"}
171
+ <span class="bases-cell-editor__checkbox-wrap">
172
+ <input
173
+ type="checkbox"
174
+ placeholder="—"
175
+ aria-label={editorLabel}
176
+ data-indeterminate={value === null ||
177
+ value === undefined ||
178
+ value === ""}
179
+ bind:checked={value}
180
+ onblur={(evt) => onChange(evt)}
181
+ class={cn("metadata-input-checkbox bases-cell-editor__checkbox", className)}
182
+ data-ui-part="checkbox"
183
+ />
184
+ </span>
185
+ {:else if type === "multitext" || type === "tags"}
186
+ <ChipAutocomplete
187
+ placeholder="—"
188
+ label={editorLabel}
189
+ showLabel={false}
190
+ embedded
191
+ uppercase={false}
192
+ value={currentTagValues()}
193
+ suggestions={metadataSuggestions(name, true)}
194
+ onChange={onTagsChange}
195
+ />
196
+ {:else}
197
+ <Autocomplete
198
+ onSelect={(value) => onValueChange?.(name, value)}
199
+ placeholder=""
200
+ aria-label={editorLabel}
201
+ class={cn("bases-cell-editor__control", className)}
202
+ bind:value
203
+ options={metadataOptions(name)}
204
+ />
205
+ {/if}
206
+ </div>
@@ -0,0 +1,12 @@
1
+ import { type App } from "@lapis-notes/api";
2
+ type $$ComponentProps = {
3
+ app: App;
4
+ type: string;
5
+ name: string;
6
+ value: any;
7
+ class?: string;
8
+ onValueChange?: (name: string, value: any) => void;
9
+ };
10
+ declare const CellEditor: import("svelte").Component<$$ComponentProps, {}, "value">;
11
+ type CellEditor = ReturnType<typeof CellEditor>;
12
+ export default CellEditor;