@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,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,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,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;
|