@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,910 @@
|
|
|
1
|
+
import { BasesViewConfig as SharedBasesViewConfig, Component, TFile, } from "@lapis-notes/api";
|
|
2
|
+
import { DateTime, Duration } from "luxon";
|
|
3
|
+
import { Context, Decimal, parseDateTime, parseDuration, } from "peaql";
|
|
4
|
+
import {} from "./models";
|
|
5
|
+
import { BasesTable, VaultFile } from "./db";
|
|
6
|
+
import { columnsFor, frontMatterTypesForColumns, } from "./columns";
|
|
7
|
+
import { generateQuery } from "./filter-parser";
|
|
8
|
+
import { isEqual } from "lodash-es";
|
|
9
|
+
import { groupEntries } from "./query-result-core";
|
|
10
|
+
import { filterEntriesBySearch } from "./search-core";
|
|
11
|
+
import { collectMetadataDependencies, } from "./metadata-invalidation-core";
|
|
12
|
+
import { appDatabaseRowsToVaultRecords, buildBasesAppDatabaseQuery, queryBasesAppDatabaseRows, } from "./app-database-query-source";
|
|
13
|
+
import { mount, unmount } from "svelte";
|
|
14
|
+
export { BasesViewConfig } from "@lapis-notes/api";
|
|
15
|
+
/**
|
|
16
|
+
* Represent a single "row" or file in a base.
|
|
17
|
+
*
|
|
18
|
+
* @since 1.10.0
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export class BasesEntry {
|
|
22
|
+
id;
|
|
23
|
+
file;
|
|
24
|
+
context;
|
|
25
|
+
constructor(id, file, context = {}) {
|
|
26
|
+
this.id = id;
|
|
27
|
+
this.file = file;
|
|
28
|
+
this.context = context;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the value of the property.
|
|
32
|
+
*
|
|
33
|
+
* @since 1.10.0
|
|
34
|
+
* @throws Error if the property is a formula and cannot be evaluated.
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
getValue(propertyId) {
|
|
38
|
+
if (propertyId.startsWith("file.") ||
|
|
39
|
+
propertyId.startsWith("note.") ||
|
|
40
|
+
propertyId.startsWith("formula.")) {
|
|
41
|
+
return this.context[propertyId] ?? null;
|
|
42
|
+
}
|
|
43
|
+
return (this.context[`note.${propertyId}`] ?? this.context[propertyId] ?? null);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Container type for data which can expose functions for retrieving, comparing,
|
|
48
|
+
* and rendering the data. Most commonly used in conjunction with formulas for
|
|
49
|
+
* Bases. Values can be used as formula parameters, intermediate values, and the
|
|
50
|
+
* result of evaluation.
|
|
51
|
+
*
|
|
52
|
+
* @since 1.10.0
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export class Value {
|
|
56
|
+
/**
|
|
57
|
+
* @since 1.10.0
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
static equals(a, b) {
|
|
61
|
+
if (!a || !b)
|
|
62
|
+
return a === b;
|
|
63
|
+
return a.equals(b);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* @since 1.10.0
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
static looseEquals(a, b) {
|
|
70
|
+
if (!a || !b)
|
|
71
|
+
return a == b;
|
|
72
|
+
return a.looseEquals(b);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* @since 1.10.0
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
equals(other) {
|
|
79
|
+
if (!(other instanceof Value))
|
|
80
|
+
return false;
|
|
81
|
+
return this === other;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* @since 1.10.0
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
looseEquals(other) {
|
|
88
|
+
if (!(other instanceof Value))
|
|
89
|
+
return false;
|
|
90
|
+
return this == other;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Render this value into the provided HTMLElement.
|
|
94
|
+
*
|
|
95
|
+
* @since 1.10.0
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
renderTo(el, ctx) { }
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Base type for all non-null {@link Values}.
|
|
102
|
+
*
|
|
103
|
+
* @since 1.10.0
|
|
104
|
+
* @public
|
|
105
|
+
*/
|
|
106
|
+
export class NotNullValue extends Value {
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* {@link Value} which represents null. NullValue is a singleton and
|
|
110
|
+
* `NullValue.value` should be used instead of calling the constructor.
|
|
111
|
+
*
|
|
112
|
+
* @since 1.10.0
|
|
113
|
+
* @public
|
|
114
|
+
*/
|
|
115
|
+
export class NullValue extends Value {
|
|
116
|
+
/**
|
|
117
|
+
* @since 1.10.0
|
|
118
|
+
* @public
|
|
119
|
+
*/
|
|
120
|
+
static value = new NullValue();
|
|
121
|
+
get value() {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* @since 1.10.0
|
|
126
|
+
* @public
|
|
127
|
+
*/
|
|
128
|
+
toString() {
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
isTruthy() {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
equals(other) {
|
|
135
|
+
return other === null || other === undefined;
|
|
136
|
+
}
|
|
137
|
+
looseEquals(other) {
|
|
138
|
+
return other == null || other == undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Base type for {@link Values} which wrap a single primitive.
|
|
143
|
+
*
|
|
144
|
+
* @since 1.10.0
|
|
145
|
+
* @public
|
|
146
|
+
*/
|
|
147
|
+
export class PrimitiveValue extends NotNullValue {
|
|
148
|
+
value;
|
|
149
|
+
/**
|
|
150
|
+
* @since 1.10.0
|
|
151
|
+
* @public
|
|
152
|
+
*/
|
|
153
|
+
constructor(value) {
|
|
154
|
+
super();
|
|
155
|
+
this.value = value;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* @since 1.10.0
|
|
159
|
+
* @public
|
|
160
|
+
*/
|
|
161
|
+
toString() {
|
|
162
|
+
return this.value?.toString();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* @since 1.10.0
|
|
166
|
+
* @public
|
|
167
|
+
*/
|
|
168
|
+
isTruthy() {
|
|
169
|
+
return this.value ? true : false;
|
|
170
|
+
}
|
|
171
|
+
equals(other) {
|
|
172
|
+
if (!(other instanceof PrimitiveValue))
|
|
173
|
+
return false;
|
|
174
|
+
return this.value === other.value;
|
|
175
|
+
}
|
|
176
|
+
looseEquals(other) {
|
|
177
|
+
if (!(other instanceof PrimitiveValue))
|
|
178
|
+
return false;
|
|
179
|
+
return this.value == other.value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* {@link Value} wrapping a boolean.
|
|
184
|
+
*
|
|
185
|
+
* @since 1.10.0
|
|
186
|
+
* @public
|
|
187
|
+
*/
|
|
188
|
+
export class BooleanValue extends PrimitiveValue {
|
|
189
|
+
/**
|
|
190
|
+
* @since 1.10.0
|
|
191
|
+
* @public
|
|
192
|
+
*/
|
|
193
|
+
static type = "boolean";
|
|
194
|
+
looseEquals(other) {
|
|
195
|
+
if (other instanceof BooleanValue) {
|
|
196
|
+
return this.value === other.value;
|
|
197
|
+
}
|
|
198
|
+
else if (other instanceof PrimitiveValue) {
|
|
199
|
+
if (typeof other.value === "number") {
|
|
200
|
+
return other.value > 0;
|
|
201
|
+
}
|
|
202
|
+
else if (typeof other.value === "string") {
|
|
203
|
+
return other.value.toLowerCase() === this.toString();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* {@link Value} wrapping a number.
|
|
211
|
+
*
|
|
212
|
+
* @since 1.10.0
|
|
213
|
+
* @public
|
|
214
|
+
*/
|
|
215
|
+
export class NumberValue extends PrimitiveValue {
|
|
216
|
+
/**
|
|
217
|
+
* @since 1.10.0
|
|
218
|
+
* @public
|
|
219
|
+
*/
|
|
220
|
+
static type = "number";
|
|
221
|
+
equals(other) {
|
|
222
|
+
return this.number === other.number;
|
|
223
|
+
}
|
|
224
|
+
looseEquals(other) {
|
|
225
|
+
if (other instanceof NumberValue) {
|
|
226
|
+
return this.number === other.number;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
toString() {
|
|
231
|
+
return this.number.toString();
|
|
232
|
+
}
|
|
233
|
+
get number() {
|
|
234
|
+
if (typeof this.value === "number") {
|
|
235
|
+
return this.value;
|
|
236
|
+
}
|
|
237
|
+
return this.value.number;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* {@link Value} wrapping a string.
|
|
242
|
+
*
|
|
243
|
+
* @since 1.10.0
|
|
244
|
+
* @public
|
|
245
|
+
*/
|
|
246
|
+
export class StringValue extends PrimitiveValue {
|
|
247
|
+
/**
|
|
248
|
+
* @since 1.10.0
|
|
249
|
+
* @public
|
|
250
|
+
*/
|
|
251
|
+
static type = "string";
|
|
252
|
+
}
|
|
253
|
+
export class DateValue extends NotNullValue {
|
|
254
|
+
value;
|
|
255
|
+
constructor(value) {
|
|
256
|
+
super();
|
|
257
|
+
if (value instanceof DateTime) {
|
|
258
|
+
this.value = value;
|
|
259
|
+
}
|
|
260
|
+
else if (value instanceof Date) {
|
|
261
|
+
this.value = DateTime.fromJSDate(value);
|
|
262
|
+
}
|
|
263
|
+
else if (typeof value === "number") {
|
|
264
|
+
this.value = DateTime.fromMillis(value);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
this.value = parseDateTime(value);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* @since 1.10.0
|
|
272
|
+
* @public
|
|
273
|
+
*/
|
|
274
|
+
toString() {
|
|
275
|
+
return this.value.toISO()?.substring(0, 16) ?? "";
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* @since 1.10.0
|
|
279
|
+
* @returns A new DateValue with any time portion in this DateValue removed.
|
|
280
|
+
* @public
|
|
281
|
+
*/
|
|
282
|
+
dateOnly() {
|
|
283
|
+
return new DateValue(this.value.startOf("day"));
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* @since 1.10.0
|
|
287
|
+
* @returns A new {@link RelativeDateValue} based on this DateValue.
|
|
288
|
+
* @public
|
|
289
|
+
*/
|
|
290
|
+
relative() {
|
|
291
|
+
return this.value.toRelative() ?? "";
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* @since 1.10.0
|
|
295
|
+
* @public
|
|
296
|
+
*/
|
|
297
|
+
isTruthy() {
|
|
298
|
+
return this.value.isValid;
|
|
299
|
+
}
|
|
300
|
+
equals(other) {
|
|
301
|
+
if (!(other instanceof DateValue))
|
|
302
|
+
return false;
|
|
303
|
+
return this.value.toMillis() === other.value.toMillis();
|
|
304
|
+
}
|
|
305
|
+
looseEquals(other) {
|
|
306
|
+
if (other instanceof DateValue) {
|
|
307
|
+
return this.value.toMillis() === other.value.toMillis();
|
|
308
|
+
}
|
|
309
|
+
else if (other instanceof NumberValue) {
|
|
310
|
+
return this.value.toMillis() === other.number;
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Create new DateValue from an input string.
|
|
316
|
+
*
|
|
317
|
+
* @since 1.10.0
|
|
318
|
+
* @example
|
|
319
|
+
* parseFromString("2025-12-31");
|
|
320
|
+
* parseFromString("2025-12-31T23:59");
|
|
321
|
+
* parseFromString("2025-12-31T23:59:59");
|
|
322
|
+
* parseFromString("2025-12-31T23:59:59Z-07");
|
|
323
|
+
*
|
|
324
|
+
* @param input - An ISO 8601 date or datetime string.
|
|
325
|
+
* @public
|
|
326
|
+
*/
|
|
327
|
+
static parseFromString(input) {
|
|
328
|
+
try {
|
|
329
|
+
return new DateValue(parseDateTime(input));
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
export class DurationValue extends NotNullValue {
|
|
337
|
+
value;
|
|
338
|
+
constructor(value) {
|
|
339
|
+
super();
|
|
340
|
+
this.value = value;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* @since 1.10.0
|
|
344
|
+
* @public
|
|
345
|
+
*/
|
|
346
|
+
toString() {
|
|
347
|
+
return this.value.toString();
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* @since 1.10.0
|
|
351
|
+
* @public
|
|
352
|
+
*/
|
|
353
|
+
isTruthy() {
|
|
354
|
+
return this.value.isValid;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Modifies the provided {@DateValue} by this duration.
|
|
358
|
+
*
|
|
359
|
+
* @since 1.10.0
|
|
360
|
+
* @public
|
|
361
|
+
*/
|
|
362
|
+
addToDate(value, subtract) {
|
|
363
|
+
return new DateValue(subtract ? value.value.minus(this.value) : value.value.plus(this.value));
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Convert this duration into milliseconds.
|
|
367
|
+
*
|
|
368
|
+
* @since 1.10.0
|
|
369
|
+
* @public
|
|
370
|
+
*/
|
|
371
|
+
getMilliseconds() {
|
|
372
|
+
return this.value.toMillis();
|
|
373
|
+
}
|
|
374
|
+
equals(other) {
|
|
375
|
+
if (!(other instanceof DurationValue))
|
|
376
|
+
return false;
|
|
377
|
+
return this.getMilliseconds() === other.getMilliseconds();
|
|
378
|
+
}
|
|
379
|
+
looseEquals(other) {
|
|
380
|
+
if (other instanceof DurationValue) {
|
|
381
|
+
return this.getMilliseconds() === other.getMilliseconds();
|
|
382
|
+
}
|
|
383
|
+
else if (other instanceof NumberValue) {
|
|
384
|
+
return this.getMilliseconds() === other.number;
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Create a new DurationValue using an ISO 8601 duration. See
|
|
390
|
+
* {@link https://en.wikipedia.org/wiki/ISO_8601#Durations} for duration format
|
|
391
|
+
* details.
|
|
392
|
+
*
|
|
393
|
+
* @since 1.10.0
|
|
394
|
+
* @public
|
|
395
|
+
*/
|
|
396
|
+
static parseFromString(input) {
|
|
397
|
+
try {
|
|
398
|
+
return new DurationValue(parseDuration(input));
|
|
399
|
+
}
|
|
400
|
+
catch (_) { }
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Create a new DurationValue from milliseconds.
|
|
405
|
+
*
|
|
406
|
+
* @since 1.10.0
|
|
407
|
+
* @public
|
|
408
|
+
*/
|
|
409
|
+
static fromMilliseconds(milliseconds) {
|
|
410
|
+
return new DurationValue(Duration.fromMillis(milliseconds));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
export class FileValue extends NotNullValue {
|
|
414
|
+
file;
|
|
415
|
+
constructor(file) {
|
|
416
|
+
super();
|
|
417
|
+
this.file = file;
|
|
418
|
+
}
|
|
419
|
+
get value() {
|
|
420
|
+
return this.file;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* @since 1.10.0
|
|
424
|
+
* @public
|
|
425
|
+
*/
|
|
426
|
+
toString() {
|
|
427
|
+
return this.file.path;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* @since 1.10.0
|
|
431
|
+
* @public
|
|
432
|
+
*/
|
|
433
|
+
isTruthy() {
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
equals(other) {
|
|
437
|
+
if (!(other instanceof FileValue))
|
|
438
|
+
return false;
|
|
439
|
+
return this.file.path === other.file.path;
|
|
440
|
+
}
|
|
441
|
+
looseEquals(other) {
|
|
442
|
+
if (other instanceof FileValue) {
|
|
443
|
+
return other.file.path === this.file.path;
|
|
444
|
+
}
|
|
445
|
+
else if (other instanceof PrimitiveValue) {
|
|
446
|
+
return this.file.path === other.value;
|
|
447
|
+
}
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
export class ObjectValue extends NotNullValue {
|
|
452
|
+
value;
|
|
453
|
+
constructor(value) {
|
|
454
|
+
super();
|
|
455
|
+
this.value = value;
|
|
456
|
+
}
|
|
457
|
+
equals(other) {
|
|
458
|
+
if (!(other instanceof ObjectValue))
|
|
459
|
+
return false;
|
|
460
|
+
return isEqual(this, other);
|
|
461
|
+
}
|
|
462
|
+
looseEquals(other) {
|
|
463
|
+
return isEqual(this, other);
|
|
464
|
+
}
|
|
465
|
+
toString() {
|
|
466
|
+
return this.value.toString();
|
|
467
|
+
}
|
|
468
|
+
isTruthy() {
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function unknownView(type) {
|
|
473
|
+
return {
|
|
474
|
+
name: "Unknown",
|
|
475
|
+
type: type ?? "unknown",
|
|
476
|
+
filter: { and: [] },
|
|
477
|
+
order: [],
|
|
478
|
+
sort: [],
|
|
479
|
+
limit: 0,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function toEntry(value, type) {
|
|
483
|
+
if (value instanceof Value) {
|
|
484
|
+
return value;
|
|
485
|
+
}
|
|
486
|
+
if (value === null || value === undefined) {
|
|
487
|
+
return NullValue.value;
|
|
488
|
+
}
|
|
489
|
+
else if (type === String) {
|
|
490
|
+
return new StringValue(value);
|
|
491
|
+
}
|
|
492
|
+
else if (type === Number || type == Decimal || typeof value === "number") {
|
|
493
|
+
return new NumberValue(value);
|
|
494
|
+
}
|
|
495
|
+
else if (type === DateTime || type === Date) {
|
|
496
|
+
return new DateValue(value);
|
|
497
|
+
}
|
|
498
|
+
else if (type === Boolean) {
|
|
499
|
+
return new BooleanValue(value);
|
|
500
|
+
}
|
|
501
|
+
else if (type === Duration) {
|
|
502
|
+
return new DurationValue(value);
|
|
503
|
+
}
|
|
504
|
+
else if (type === TFile || type == VaultFile) {
|
|
505
|
+
return new FileValue(value);
|
|
506
|
+
}
|
|
507
|
+
return new ObjectValue(value);
|
|
508
|
+
}
|
|
509
|
+
function convertEntries(dataset) {
|
|
510
|
+
const entries = [];
|
|
511
|
+
const [rows, types] = dataset;
|
|
512
|
+
for (const row of rows) {
|
|
513
|
+
const record = row["$rowId"];
|
|
514
|
+
if (!record)
|
|
515
|
+
continue;
|
|
516
|
+
const context = {};
|
|
517
|
+
for (const key of Object.keys(row)) {
|
|
518
|
+
if (key === "$rowId")
|
|
519
|
+
continue;
|
|
520
|
+
const id = key;
|
|
521
|
+
context[id] = toEntry(row[id], types[id]);
|
|
522
|
+
}
|
|
523
|
+
entries.push(new BasesEntry(record.id, record.file, context));
|
|
524
|
+
}
|
|
525
|
+
return entries;
|
|
526
|
+
}
|
|
527
|
+
export class QueryController extends Component {
|
|
528
|
+
views;
|
|
529
|
+
readOnly;
|
|
530
|
+
app;
|
|
531
|
+
data = $state([]);
|
|
532
|
+
loadError = $state(null);
|
|
533
|
+
doc = $state();
|
|
534
|
+
properties = $state({});
|
|
535
|
+
formulas = $state({});
|
|
536
|
+
searchQuery = $state("");
|
|
537
|
+
searchPanelOpen = $state(false);
|
|
538
|
+
columns = $derived(columnsFor(this.data, this.properties, this.formulas, this.app));
|
|
539
|
+
frontMatterTypes = $derived(frontMatterTypesForColumns(this.columns));
|
|
540
|
+
db = $derived(Context.create(new BasesTable("bases")
|
|
541
|
+
.withApp(this.app)
|
|
542
|
+
.withPropertyColumns(this.frontMatterTypes)
|
|
543
|
+
.data(() => this.data)).withDefaultTable("bases"));
|
|
544
|
+
selectedView = $state();
|
|
545
|
+
#view = $state();
|
|
546
|
+
prevDoc = "";
|
|
547
|
+
lastViewResults = new WeakMap();
|
|
548
|
+
query = $derived(generateQuery(this.selectedView, this.formulas, this.doc.filters));
|
|
549
|
+
metadataDependencies = $derived.by(() => {
|
|
550
|
+
return collectMetadataDependencies({
|
|
551
|
+
documentFilter: this.doc.filters,
|
|
552
|
+
viewFilter: this.selectedView?.filter,
|
|
553
|
+
order: (this.selectedView?.order ?? []),
|
|
554
|
+
sort: this.selectedView?.sort ?? [],
|
|
555
|
+
groupByProperty: this.selectedView?.groupBy?.property ?? null,
|
|
556
|
+
imageProperty: typeof this.selectedView?.image === "string"
|
|
557
|
+
? this.selectedView.image
|
|
558
|
+
: null,
|
|
559
|
+
formulas: this.formulas,
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
reloadSignature = $derived.by(() => {
|
|
563
|
+
return JSON.stringify({
|
|
564
|
+
documentFilter: $state.snapshot(this.doc.filters),
|
|
565
|
+
viewFilter: $state.snapshot(this.selectedView?.filter),
|
|
566
|
+
sort: $state.snapshot(this.selectedView?.sort ?? []),
|
|
567
|
+
limit: this.selectedView?.limit ?? 0,
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
dataset = $state([
|
|
571
|
+
[],
|
|
572
|
+
{},
|
|
573
|
+
]);
|
|
574
|
+
queryResult = $derived.by(() => {
|
|
575
|
+
const visibleProperties = (this.selectedView?.order ??
|
|
576
|
+
[]);
|
|
577
|
+
const entries = filterEntriesBySearch(convertEntries(this.dataset), visibleProperties, this.searchQuery);
|
|
578
|
+
return new BasesQueryResult(entries, this.getAllColumns().map((it) => it.id), this.selectedView?.groupBy?.property);
|
|
579
|
+
});
|
|
580
|
+
constructor(app, document, views, onChange, readOnly = false) {
|
|
581
|
+
super();
|
|
582
|
+
this.views = views;
|
|
583
|
+
this.readOnly = readOnly;
|
|
584
|
+
this.app = app;
|
|
585
|
+
this.doc = document;
|
|
586
|
+
this.properties = this.doc.properties || {};
|
|
587
|
+
this.formulas = this.doc.formulas || {};
|
|
588
|
+
this.selectedView =
|
|
589
|
+
this.doc.views.find((it) => it.name === this.doc.activeView) ||
|
|
590
|
+
this.doc.views[0] ||
|
|
591
|
+
unknownView();
|
|
592
|
+
this.prevDoc = JSON.stringify(document);
|
|
593
|
+
$effect(() => {
|
|
594
|
+
if (!this.selectedView)
|
|
595
|
+
return;
|
|
596
|
+
this.doc.activeView = this.selectedView.name;
|
|
597
|
+
});
|
|
598
|
+
$effect(() => {
|
|
599
|
+
const props = {
|
|
600
|
+
...$state.snapshot(this.doc),
|
|
601
|
+
properties: $state.snapshot(this.properties),
|
|
602
|
+
formulas: $state.snapshot(this.formulas),
|
|
603
|
+
};
|
|
604
|
+
const content = JSON.stringify(props);
|
|
605
|
+
if (content !== this.prevDoc) {
|
|
606
|
+
onChange(props);
|
|
607
|
+
this.prevDoc = content;
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
$effect(() => {
|
|
611
|
+
if (!this.#view)
|
|
612
|
+
return;
|
|
613
|
+
this.syncViewData(this.#view, this.queryResult);
|
|
614
|
+
});
|
|
615
|
+
$effect(() => {
|
|
616
|
+
this.reloadSignature;
|
|
617
|
+
void this.reload();
|
|
618
|
+
});
|
|
619
|
+
$effect(() => {
|
|
620
|
+
this.dataset = this.getRecords(this.query);
|
|
621
|
+
});
|
|
622
|
+
this.registerEvent(this.app.metadataCache.on("index-changed", (change) => {
|
|
623
|
+
if (change.reset || change.domains.includes("metadata"))
|
|
624
|
+
void this.reload();
|
|
625
|
+
}));
|
|
626
|
+
this.registerEvent(this.app.metadataCache.on("loaded", () => {
|
|
627
|
+
void this.reload();
|
|
628
|
+
}));
|
|
629
|
+
}
|
|
630
|
+
get view() {
|
|
631
|
+
return this.#view;
|
|
632
|
+
}
|
|
633
|
+
set view(view) {
|
|
634
|
+
this.#view = view;
|
|
635
|
+
}
|
|
636
|
+
syncViewData(view, result) {
|
|
637
|
+
if (this.lastViewResults.get(view) === result) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
this.lastViewResults.set(view, result);
|
|
641
|
+
view.data = result;
|
|
642
|
+
view.onDataUpdated();
|
|
643
|
+
}
|
|
644
|
+
get viewConfig() {
|
|
645
|
+
return this.views.get(this.selectedView.type) ?? this.views.get("unknown");
|
|
646
|
+
}
|
|
647
|
+
getRecords(qs) {
|
|
648
|
+
const types = {};
|
|
649
|
+
try {
|
|
650
|
+
const [columns, data] = this.db.execute(qs);
|
|
651
|
+
columns.forEach((col) => {
|
|
652
|
+
types[col.name.description] = col.type;
|
|
653
|
+
});
|
|
654
|
+
const dataset = data.map((row) => {
|
|
655
|
+
return columns.reduce((acc, value, i) => {
|
|
656
|
+
acc[value.name.description] = row[i];
|
|
657
|
+
return acc;
|
|
658
|
+
}, {});
|
|
659
|
+
});
|
|
660
|
+
return [dataset, types];
|
|
661
|
+
}
|
|
662
|
+
catch (e) {
|
|
663
|
+
console.error("Error executing query", e);
|
|
664
|
+
return [[], types];
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
getAllColumns() {
|
|
668
|
+
return Object.values(this.columns);
|
|
669
|
+
}
|
|
670
|
+
isVisible(id) {
|
|
671
|
+
return this.selectedView && this.selectedView.order.includes(id);
|
|
672
|
+
}
|
|
673
|
+
toggleVisibility(id) {
|
|
674
|
+
if (this.selectedView) {
|
|
675
|
+
const index = this.selectedView.order.findIndex((it) => it === id);
|
|
676
|
+
if (index === -1) {
|
|
677
|
+
this.selectedView.order.push(id);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
this.selectedView.order.splice(index, 1);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
getColumn(id) {
|
|
684
|
+
return this.columns[id];
|
|
685
|
+
}
|
|
686
|
+
get count() {
|
|
687
|
+
return this.dataset[0].length;
|
|
688
|
+
}
|
|
689
|
+
get searchCount() {
|
|
690
|
+
return this.queryResult.data.length;
|
|
691
|
+
}
|
|
692
|
+
toggleSearchPanel() {
|
|
693
|
+
this.searchPanelOpen = !this.searchPanelOpen;
|
|
694
|
+
}
|
|
695
|
+
async reload() {
|
|
696
|
+
const generation = (this
|
|
697
|
+
._reloadGeneration ?? 0) + 1;
|
|
698
|
+
this._reloadGeneration = generation;
|
|
699
|
+
try {
|
|
700
|
+
const rows = await queryBasesAppDatabaseRows(this.app.appDatabase, buildBasesAppDatabaseQuery({
|
|
701
|
+
documentFilter: this.doc.filters,
|
|
702
|
+
viewFilter: this.selectedView?.filter,
|
|
703
|
+
sort: this.selectedView?.sort ?? [],
|
|
704
|
+
limit: this.selectedView?.limit ?? 0,
|
|
705
|
+
}));
|
|
706
|
+
if (this
|
|
707
|
+
._reloadGeneration !== generation) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const records = appDatabaseRowsToVaultRecords(this.app, rows);
|
|
711
|
+
if (this.metadataDependencies.backlinks && records.length) {
|
|
712
|
+
const backlinks = new Map();
|
|
713
|
+
for (let offset = 0; offset < records.length; offset += 400) {
|
|
714
|
+
const links = await this.app.metadataCache.queryLinks({
|
|
715
|
+
direction: "incoming",
|
|
716
|
+
paths: records.slice(offset, offset + 400).map((record) => record.id),
|
|
717
|
+
resolution: "resolved",
|
|
718
|
+
limit: 100_000,
|
|
719
|
+
});
|
|
720
|
+
for (const link of links) {
|
|
721
|
+
if (!link.resolvedTargetPath)
|
|
722
|
+
continue;
|
|
723
|
+
const sources = backlinks.get(link.resolvedTargetPath) ?? new Set();
|
|
724
|
+
sources.add(link.sourcePath);
|
|
725
|
+
backlinks.set(link.resolvedTargetPath, sources);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
for (const record of records) {
|
|
729
|
+
record.backlinks = [...(backlinks.get(record.id) ?? [])];
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
this.loadError = null;
|
|
733
|
+
this.data = records;
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
console.error("Error loading Bases rows from AppDatabase", error);
|
|
737
|
+
if (this
|
|
738
|
+
._reloadGeneration !== generation) {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
this.loadError = error instanceof Error ? error.message : String(error);
|
|
742
|
+
this.data = [];
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
export class BasesView extends Component {
|
|
747
|
+
controller;
|
|
748
|
+
mountedView = null;
|
|
749
|
+
mountedViewContainer = null;
|
|
750
|
+
/**
|
|
751
|
+
* @since 1.10.0
|
|
752
|
+
* @public
|
|
753
|
+
*/
|
|
754
|
+
app;
|
|
755
|
+
/**
|
|
756
|
+
* The config object for this view.
|
|
757
|
+
*
|
|
758
|
+
* @since 1.10.0
|
|
759
|
+
* @public
|
|
760
|
+
*/
|
|
761
|
+
config = $state(new SharedBasesViewConfig({
|
|
762
|
+
type: "unknown",
|
|
763
|
+
sort: [],
|
|
764
|
+
order: [],
|
|
765
|
+
limit: 0,
|
|
766
|
+
name: "Unknown",
|
|
767
|
+
filter: { and: [] },
|
|
768
|
+
}, {}));
|
|
769
|
+
/**
|
|
770
|
+
* The most recent output from executing the bases query, applying filters,
|
|
771
|
+
* and evaluating formulas. This object will be replaced with a new result set
|
|
772
|
+
* when changes to the vault or Bases config occur, so views should not keep a
|
|
773
|
+
* reference to it. Also note the contained BasesEntry objects will be
|
|
774
|
+
* recreated.
|
|
775
|
+
*
|
|
776
|
+
* @since 1.10.0
|
|
777
|
+
* @public
|
|
778
|
+
*/
|
|
779
|
+
data = $state(new BasesQueryResult([], []));
|
|
780
|
+
constructor(controller) {
|
|
781
|
+
super();
|
|
782
|
+
this.controller = controller;
|
|
783
|
+
}
|
|
784
|
+
mountViewComponent(component, props, containerEl) {
|
|
785
|
+
this.unmountViewComponent();
|
|
786
|
+
containerEl.replaceChildren();
|
|
787
|
+
const target = containerEl.createDiv("bases-view__renderer");
|
|
788
|
+
this.mountedViewContainer = target;
|
|
789
|
+
this.mountedView = mount(component, {
|
|
790
|
+
props,
|
|
791
|
+
target,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
unmountViewComponent() {
|
|
795
|
+
if (this.mountedView) {
|
|
796
|
+
unmount(this.mountedView);
|
|
797
|
+
this.mountedView = null;
|
|
798
|
+
}
|
|
799
|
+
this.mountedViewContainer?.remove();
|
|
800
|
+
this.mountedViewContainer = null;
|
|
801
|
+
}
|
|
802
|
+
onunload() {
|
|
803
|
+
this.unmountViewComponent();
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* All available properties from the dataset.
|
|
807
|
+
*
|
|
808
|
+
* @since 1.10.0
|
|
809
|
+
* @public
|
|
810
|
+
*/
|
|
811
|
+
get allProperties() {
|
|
812
|
+
if (!this.data)
|
|
813
|
+
return [];
|
|
814
|
+
return this.data.properties;
|
|
815
|
+
}
|
|
816
|
+
async createFileForView(baseFileName, frontmatterProcessor) {
|
|
817
|
+
const fileName = (baseFileName?.trim() || "Untitled").replace(/\.md$/i, "");
|
|
818
|
+
const path = this.app.fileManager.getAvailablePathForAttachment(`${fileName}.md`, "");
|
|
819
|
+
const file = await this.app.vault.create(path, "");
|
|
820
|
+
if (frontmatterProcessor) {
|
|
821
|
+
await this.app.fileManager.processFrontMatter(file, frontmatterProcessor);
|
|
822
|
+
}
|
|
823
|
+
await this.app.openFile(file);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* A group of BasesEntry objects for a given value of the groupBy key. If there
|
|
828
|
+
* are entries in the results which do not have a value for the groupBy key, the
|
|
829
|
+
* key will be the {@link NullValue}.
|
|
830
|
+
*
|
|
831
|
+
* @since 1.10.0
|
|
832
|
+
* @public
|
|
833
|
+
*/
|
|
834
|
+
export class BasesEntryGroup {
|
|
835
|
+
entries;
|
|
836
|
+
key;
|
|
837
|
+
constructor(entries, key) {
|
|
838
|
+
this.entries = entries;
|
|
839
|
+
this.key = key;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* @since 1.10.0
|
|
843
|
+
* @returns True iff this entry group has a non-null key.
|
|
844
|
+
* @public
|
|
845
|
+
*/
|
|
846
|
+
hasKey() {
|
|
847
|
+
if (this.key === null || this.key === undefined) {
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
return this.key.isTruthy();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* The BasesQueryResult contains all of the available information from executing
|
|
855
|
+
* the bases query, applying filters, and evaluating formulas. The `data` or
|
|
856
|
+
* `groupedData` should be displayed by your view.
|
|
857
|
+
*
|
|
858
|
+
* @since 1.10.0
|
|
859
|
+
* @public
|
|
860
|
+
*/
|
|
861
|
+
export class BasesQueryResult {
|
|
862
|
+
data;
|
|
863
|
+
properties;
|
|
864
|
+
groupBy;
|
|
865
|
+
constructor(data, properties, groupBy) {
|
|
866
|
+
this.data = data;
|
|
867
|
+
this.properties = properties;
|
|
868
|
+
this.groupBy = groupBy;
|
|
869
|
+
}
|
|
870
|
+
get groupedData() {
|
|
871
|
+
return groupEntries(this.data, this.groupBy, (left, right) => Value.looseEquals(left, right)).map((group) => new BasesEntryGroup(group.entries, group.key ?? undefined));
|
|
872
|
+
}
|
|
873
|
+
getSummaryValue(queryController, entries, prop, summaryKey) {
|
|
874
|
+
const values = entries
|
|
875
|
+
.map((entry) => entry.getValue(prop))
|
|
876
|
+
.filter((value) => !!value && !(value instanceof NullValue));
|
|
877
|
+
const numbers = values
|
|
878
|
+
.filter((value) => value instanceof NumberValue)
|
|
879
|
+
.map((value) => value.number);
|
|
880
|
+
switch (summaryKey.toLowerCase()) {
|
|
881
|
+
case "count":
|
|
882
|
+
return new NumberValue(entries.length);
|
|
883
|
+
case "count-empty":
|
|
884
|
+
return new NumberValue(entries.length - values.length);
|
|
885
|
+
case "count-non-empty":
|
|
886
|
+
case "count-filled":
|
|
887
|
+
return new NumberValue(values.length);
|
|
888
|
+
case "sum":
|
|
889
|
+
return numbers.length
|
|
890
|
+
? new NumberValue(numbers.reduce((total, value) => total + value, 0))
|
|
891
|
+
: NullValue.value;
|
|
892
|
+
case "average":
|
|
893
|
+
case "avg":
|
|
894
|
+
return numbers.length
|
|
895
|
+
? new NumberValue(numbers.reduce((total, value) => total + value, 0) /
|
|
896
|
+
numbers.length)
|
|
897
|
+
: NullValue.value;
|
|
898
|
+
case "min":
|
|
899
|
+
return numbers.length
|
|
900
|
+
? new NumberValue(Math.min(...numbers))
|
|
901
|
+
: NullValue.value;
|
|
902
|
+
case "max":
|
|
903
|
+
return numbers.length
|
|
904
|
+
? new NumberValue(Math.max(...numbers))
|
|
905
|
+
: NullValue.value;
|
|
906
|
+
default:
|
|
907
|
+
return NullValue.value;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|