@protobase/ui 0.21.10 → 0.21.11
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/dist/app/create/create-values.js +2 -1
- package/dist/app/pages/blocks/show.js +5 -34
- package/dist/app/pages/use-conditions.js +49 -0
- package/dist/app/record-mode/record-mode.js +4 -1
- package/dist/app/record-mode/shown-layout.js +18 -0
- package/dist/app/record-mode/use-shown-layout.js +14 -0
- package/dist/app/record-page.js +9 -8
- package/dist/app/related-section.js +9 -0
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-CiQapuHq.js +69 -0
- package/dist/shell/assets/{index-C_o0i6I2.js → index-Dimu63Ym.js} +1 -1
- package/dist/shell/assets/{maplibre-DLYsHwYU.js → maplibre-ERswanhA.js} +1 -1
- package/dist/shell/assets/{markdown-content-B410ow-t.js → markdown-content-D-haiVrI.js} +1 -1
- package/dist/shell/assets/{record-map-DvgWFyLn.js → record-map-C3i03kwc.js} +1 -1
- package/dist/shell/assets/{records-map-CLj0uHGq.js → records-map-C1JyIuz3.js} +1 -1
- package/dist/shell/assets/{shared-protobase-ui-Bf1h2ktk.js → shared-protobase-ui-Bt4hh6GB.js} +1 -1
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-9imuOKuW.js +0 -69
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isEditable, toPatchValue } from "../../live/field-values.js";
|
|
2
2
|
import { whenShown } from "../record-mode/record-mode.js";
|
|
3
|
+
import { isRecordCondition } from "@protobase/schema";
|
|
3
4
|
//#region src/app/create/create-values.ts
|
|
4
5
|
const isChosen = (value) => typeof value === "object" && value !== null && "key" in value;
|
|
5
6
|
/** Fields the user can set on a new record: not read-only, not the tenant, and not structured data. */
|
|
@@ -13,7 +14,7 @@ const createSections = (model, view) => {
|
|
|
13
14
|
const creatable = Object.values(model.fields).filter((field) => isCreatable(model, field)).map((field) => field.name);
|
|
14
15
|
const placed = /* @__PURE__ */ new Set();
|
|
15
16
|
const sections = (view?.layout ?? []).flatMap((item) => {
|
|
16
|
-
if (item.kind !== "section" || !whenShown(item.when, true)) return [];
|
|
17
|
+
if (item.kind !== "section" || isRecordCondition(item.when) || !whenShown(item.when, true)) return [];
|
|
17
18
|
const fields = item.fields.filter((name) => creatable.includes(name) && !placed.has(name) && whenShown(view?.fields[name]?.when, true));
|
|
18
19
|
fields.forEach((name) => placed.add(name));
|
|
19
20
|
return fields.length > 0 ? [{
|
|
@@ -1,43 +1,14 @@
|
|
|
1
|
-
import { useClient } from "../../../data/api-provider.js";
|
|
2
|
-
import { useAdminMeta } from "../../meta-gate.js";
|
|
3
1
|
import { useLayoutRecord } from "../record-scope.js";
|
|
4
|
-
import { countQuery } from "../use-layout-data.js";
|
|
5
2
|
import { propsOf } from "./block-props.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import { useQueries } from "@tanstack/react-query";
|
|
10
|
-
import { countedResource, evaluateCondition, parseCondition } from "@protobase/layout";
|
|
3
|
+
import { useConditions } from "../use-conditions.js";
|
|
4
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
5
|
+
import "@protobase/layout";
|
|
11
6
|
//#region src/app/pages/blocks/show.tsx
|
|
12
7
|
/** Its children while `when` holds: fields of the record around it, and `<resource>.count` for any resource. */
|
|
13
8
|
const ShowBlock = ({ node, children }) => {
|
|
14
9
|
const { when } = propsOf(node);
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const scope = useLayoutRecord();
|
|
18
|
-
const condition = useMemo(() => parseCondition(when), [when]);
|
|
19
|
-
const paths = condition.ok ? condition.paths : [];
|
|
20
|
-
const counted = paths.map((path) => countedResource(path, (name) => Boolean(scope && Object.hasOwn(scope.model.fields, name))));
|
|
21
|
-
const fieldPaths = paths.filter((_, index) => counted[index] === void 0);
|
|
22
|
-
const fields = usePathValues(scope, fieldPaths);
|
|
23
|
-
const counts = useQueries({ queries: counted.flatMap((resource) => {
|
|
24
|
-
const model = resource === void 0 ? void 0 : resources[resource];
|
|
25
|
-
if (!model) return [];
|
|
26
|
-
return [countQuery(client, model)];
|
|
27
|
-
}) });
|
|
28
|
-
if (!condition.ok) return null;
|
|
29
|
-
if (fields.some((value) => value.state === "loading") || counts.some((count) => count.isPending)) return null;
|
|
30
|
-
const values = {};
|
|
31
|
-
fieldPaths.forEach((path, index) => {
|
|
32
|
-
const value = fields[index];
|
|
33
|
-
if (value.state === "ready") values[path] = value.value;
|
|
34
|
-
});
|
|
35
|
-
let next = 0;
|
|
36
|
-
paths.forEach((path, index) => {
|
|
37
|
-
if (counted[index] === void 0) return;
|
|
38
|
-
values[path] = resources[counted[index]] ? counts[next++]?.data ?? 0 : 0;
|
|
39
|
-
});
|
|
40
|
-
return evaluateCondition(condition, values) ? /* @__PURE__ */ jsx(Fragment$1, { children }) : null;
|
|
10
|
+
const [shown] = useConditions([when], useLayoutRecord());
|
|
11
|
+
return shown ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
|
|
41
12
|
};
|
|
42
13
|
//#endregion
|
|
43
14
|
export { ShowBlock };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useClient } from "../../data/api-provider.js";
|
|
2
|
+
import { useAdminMeta } from "../meta-gate.js";
|
|
3
|
+
import { countQuery } from "./use-layout-data.js";
|
|
4
|
+
import { usePathValues } from "./use-path-value.js";
|
|
5
|
+
import { useMemo } from "react";
|
|
6
|
+
import { useQueries } from "@tanstack/react-query";
|
|
7
|
+
import { countedResource, evaluateCondition, parseCondition } from "@protobase/layout";
|
|
8
|
+
//#region src/app/pages/use-conditions.ts
|
|
9
|
+
/**
|
|
10
|
+
* Whether each condition holds for the record around it (`scope`): `undefined` while a value it reads loads, `false`
|
|
11
|
+
* when it does not parse. Fields come from the stored record, followed through relations; `<resource>.count` counts
|
|
12
|
+
* records. All conditions share one set of queries, so the hook count never changes with the conditions.
|
|
13
|
+
*/
|
|
14
|
+
const useConditions = (whens, scope) => {
|
|
15
|
+
const { resources } = useAdminMeta();
|
|
16
|
+
const client = useClient();
|
|
17
|
+
const key = JSON.stringify(whens);
|
|
18
|
+
const parsed = useMemo(() => JSON.parse(key).map(parseCondition), [key]);
|
|
19
|
+
const isField = (name) => Boolean(scope && Object.hasOwn(scope.model.fields, name));
|
|
20
|
+
const paths = [...new Set(parsed.flatMap((condition) => condition.ok ? condition.paths : []))];
|
|
21
|
+
const counted = new Map(paths.flatMap((path) => {
|
|
22
|
+
const resource = countedResource(path, isField);
|
|
23
|
+
return resource === void 0 ? [] : [[path, resource]];
|
|
24
|
+
}));
|
|
25
|
+
const fieldPaths = paths.filter((path) => !counted.has(path));
|
|
26
|
+
const countedModels = [...new Set(counted.values())].flatMap((resource) => resources[resource] ? [resources[resource]] : []);
|
|
27
|
+
const fields = usePathValues(scope, fieldPaths);
|
|
28
|
+
const counts = useQueries({ queries: countedModels.map((model) => countQuery(client, model)) });
|
|
29
|
+
return parsed.map((condition) => {
|
|
30
|
+
if (!condition.ok) return false;
|
|
31
|
+
const values = {};
|
|
32
|
+
for (const path of condition.paths) {
|
|
33
|
+
const resource = counted.get(path);
|
|
34
|
+
if (resource === void 0) {
|
|
35
|
+
const value = fields[fieldPaths.indexOf(path)];
|
|
36
|
+
if (value.state === "loading") return void 0;
|
|
37
|
+
if (value.state === "ready") values[path] = value.value;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const index = countedModels.findIndex((model) => model.name === resource);
|
|
41
|
+
if (index === -1) values[path] = 0;
|
|
42
|
+
else if (counts[index].isPending) return void 0;
|
|
43
|
+
else values[path] = counts[index].data ?? 0;
|
|
44
|
+
}
|
|
45
|
+
return evaluateCondition(condition, values);
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { useConditions };
|
|
@@ -32,7 +32,10 @@ const recordShown = (mode, canUpdate, edit) => {
|
|
|
32
32
|
const helpShown = (own, view, editing) => (own ?? view?.helpWhen ?? "always") === "always" || editing;
|
|
33
33
|
/** A field's help line on the record page, when it shows. */
|
|
34
34
|
const fieldHelp = (hints, view, editing) => hints?.help !== void 0 && helpShown(hints.helpWhen, view, editing) ? hints.help : void 0;
|
|
35
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Whether a field or layout item shows: always, unless it says only while editing or only while reading and the page is not.
|
|
37
|
+
* A record condition is no mode and counts as always here; the record page evaluates it separately.
|
|
38
|
+
*/
|
|
36
39
|
const whenShown = (when, editing) => {
|
|
37
40
|
if (when === "editing") return editing;
|
|
38
41
|
if (when === "reading") return !editing;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { whenShown } from "./record-mode.js";
|
|
2
|
+
import { isRecordCondition } from "@protobase/schema";
|
|
3
|
+
//#region src/app/record-mode/shown-layout.ts
|
|
4
|
+
/** The items with a record condition in `when`, whose conditions the page evaluates. */
|
|
5
|
+
const conditionalItems = (items) => items.filter((item) => isRecordCondition(item.when));
|
|
6
|
+
/**
|
|
7
|
+
* The items that show: those with a record condition only when it holds (`held`, one answer per conditional item in
|
|
8
|
+
* order, `undefined` while loading) and the page is no preview; the others by the form or read view.
|
|
9
|
+
*/
|
|
10
|
+
const shownLayout = (items, held, editing, preview) => {
|
|
11
|
+
let next = 0;
|
|
12
|
+
return items.filter((item) => {
|
|
13
|
+
if (!isRecordCondition(item.when)) return whenShown(item.when, editing);
|
|
14
|
+
return !preview && held[next++] === true;
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { conditionalItems, shownLayout };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useConditions } from "../pages/use-conditions.js";
|
|
2
|
+
import { conditionalItems, shownLayout } from "./shown-layout.js";
|
|
3
|
+
//#region src/app/record-mode/use-shown-layout.ts
|
|
4
|
+
/**
|
|
5
|
+
* The layout items to draw: those meant for the form or the read view as the page is, and those with a record
|
|
6
|
+
* condition while it holds for the saved record. Conditional items stay out while their values load and on a preview.
|
|
7
|
+
*/
|
|
8
|
+
const useShownLayout = (items, scope, editing, preview) => {
|
|
9
|
+
const conditions = preview ? [] : conditionalItems(items).map((item) => item.when);
|
|
10
|
+
const held = useConditions(conditions, scope);
|
|
11
|
+
return shownLayout(items, held, editing, preview);
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { useShownLayout };
|
package/dist/app/record-page.js
CHANGED
|
@@ -52,6 +52,7 @@ import { useLinesDraft } from "./use-lines-draft.js";
|
|
|
52
52
|
import { GallerySection } from "./gallery/gallery-section.js";
|
|
53
53
|
import { HistorySection } from "./history/history-section.js";
|
|
54
54
|
import { CommentsSection } from "./comments/comments-section.js";
|
|
55
|
+
import { useShownLayout } from "./record-mode/use-shown-layout.js";
|
|
55
56
|
import { useRecordEditor } from "./use-record-editor.js";
|
|
56
57
|
import { useRelatedRecords } from "./use-related-records.js";
|
|
57
58
|
import { IncomingCard } from "./incoming-card.js";
|
|
@@ -190,7 +191,14 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
190
191
|
editor.change(name, keyValue(target, next.key));
|
|
191
192
|
};
|
|
192
193
|
const { fields: fieldTypes } = useProjectUi();
|
|
193
|
-
const
|
|
194
|
+
const scope = {
|
|
195
|
+
model,
|
|
196
|
+
record: stored.record,
|
|
197
|
+
key: recordKey,
|
|
198
|
+
etag: stored.etag,
|
|
199
|
+
permissions: stored.permissions
|
|
200
|
+
};
|
|
201
|
+
const layout = useShownLayout(layoutFor(model, view, shown === "read"), scope, editing, preview);
|
|
194
202
|
const sidebarFields = layout.flatMap((item) => item.kind === "sidebar" ? item.fields : []);
|
|
195
203
|
const hasStatus = model.fields.status?.type === "enum";
|
|
196
204
|
const statusValue = stored.record.status;
|
|
@@ -307,13 +315,6 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
307
315
|
}];
|
|
308
316
|
})
|
|
309
317
|
});
|
|
310
|
-
const scope = {
|
|
311
|
-
model,
|
|
312
|
-
record: stored.record,
|
|
313
|
-
key: recordKey,
|
|
314
|
-
etag: stored.etag,
|
|
315
|
-
permissions: stored.permissions
|
|
316
|
-
};
|
|
317
318
|
const save = async () => {
|
|
318
319
|
if (await editor.save() && leavesForm) go(readAddress, { replace: true });
|
|
319
320
|
};
|
|
@@ -5,6 +5,7 @@ import { Section } from "../record-view/section.js";
|
|
|
5
5
|
import { ErrorBanner } from "./error-banner.js";
|
|
6
6
|
import { listColumnNames } from "../live/columns-from-model.js";
|
|
7
7
|
import { useAdminMeta, usePermissions } from "./meta-gate.js";
|
|
8
|
+
import { countQuery } from "./pages/use-layout-data.js";
|
|
8
9
|
import { searchAll } from "../live/search-all.js";
|
|
9
10
|
import { relatedOpens } from "./related-opens.js";
|
|
10
11
|
import { EmbeddedDisplaySwitch, EmbeddedList } from "./embedded-list/embedded-list.js";
|
|
@@ -82,7 +83,15 @@ const RelatedSection = ({ item, recordKey, owner }) => {
|
|
|
82
83
|
const [display, pick] = useEmbeddedDisplay(view, `related:${owner}:${item.title}`, item.display);
|
|
83
84
|
const permissions = usePermissions(item.resource);
|
|
84
85
|
const [sorting, setSorting] = useState(false);
|
|
86
|
+
const client = useClient();
|
|
87
|
+
const counting = Boolean(item.hideEmpty && model && match.filter);
|
|
88
|
+
const count = useQuery({
|
|
89
|
+
queryKey: keys.count(item.resource, match.filter ?? ""),
|
|
90
|
+
enabled: counting,
|
|
91
|
+
queryFn: () => countQuery(client, model, match.filter ?? "").queryFn()
|
|
92
|
+
});
|
|
85
93
|
if (!model) return null;
|
|
94
|
+
if (item.hideEmpty && (match.isPending || match.filter === null || counting && (count.isPending || count.data === 0))) return null;
|
|
86
95
|
const opens = relatedOpens(item, model, resources);
|
|
87
96
|
const columns = item.columns ?? listColumnNames(model, view).filter((name) => name !== item.field);
|
|
88
97
|
const handOrder = relatedHandOrder(item, model, permissions);
|
package/dist/package.js
CHANGED