@protobase/ui 0.21.11 → 0.21.12
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/app.js +1 -1
- package/dist/app/comments/comment-composer.js +1 -1
- package/dist/app/create/create-page.js +1 -1
- package/dist/app/create/create-values.js +2 -1
- package/dist/app/current-address.d.ts +1 -0
- package/dist/app/current-address.js +2 -1
- package/dist/app/displays/board-column.js +1 -1
- package/dist/app/displays/calendar-display.js +1 -1
- package/dist/app/displays/list-cards.js +1 -1
- package/dist/app/displays/map-display.js +1 -1
- package/dist/app/embedded-list/embedded-list.js +1 -1
- package/dist/app/field-editor.js +3 -3
- package/dist/app/gallery/detail-fields.js +2 -1
- package/dist/app/gallery/gallery-section.js +1 -1
- package/dist/app/gallery/photo-details.js +1 -1
- package/dist/app/group-open.js +7 -0
- package/dist/app/hand-order/hand-sorted-list.js +1 -1
- package/dist/app/incoming-relations.js +2 -1
- package/dist/app/list-page.js +1 -1
- package/dist/app/pages/action-inputs/action-inputs-dialog.js +1 -1
- package/dist/app/pages/blocks/card-row.js +1 -1
- package/dist/app/pages/blocks/modal-form.js +1 -1
- package/dist/app/pages/blocks/record-card.js +1 -1
- package/dist/app/pages/record-actions.js +1 -1
- package/dist/app/record-mode/shown-layout.js +13 -6
- package/dist/app/record-page.js +81 -64
- package/dist/app/record-row/record-row-body.js +1 -1
- package/dist/app/record-row/saved-record-row.js +1 -1
- package/dist/app/related-section.js +1 -1
- package/dist/app/searches/saved-search-rows.js +1 -1
- package/dist/app/searches/saved-searches.js +1 -1
- package/dist/app/sensitive-field.js +1 -1
- package/dist/app/use-app-address.d.ts +1 -0
- package/dist/app/use-record-editor.js +1 -1
- package/dist/forms/public-form-page.js +1 -1
- package/dist/index.js +1 -1
- package/dist/live/field-values.js +1 -1
- package/dist/package.js +1 -1
- package/dist/record-view/group-section.js +35 -0
- package/dist/shell/assets/app-IuP56Dep.js +69 -0
- package/dist/shell/assets/{index-CzqIlZPP.css → index-DhW8t8wq.css} +1 -1
- package/dist/shell/assets/{index-Dimu63Ym.js → index-GB5YRjlo.js} +1 -1
- package/dist/shell/assets/{maplibre-ERswanhA.js → maplibre-BLm41BrA.js} +1 -1
- package/dist/shell/assets/{markdown-content-D-haiVrI.js → markdown-content-Cia4jbSB.js} +1 -1
- package/dist/shell/assets/{record-map-C3i03kwc.js → record-map-C-Svn7nc.js} +1 -1
- package/dist/shell/assets/{records-map-C1JyIuz3.js → records-map-D5LZhC54.js} +1 -1
- package/dist/shell/assets/{shared-protobase-ui-Bt4hh6GB.js → shared-protobase-ui-D16YbnPr.js} +1 -1
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-CiQapuHq.js +0 -69
package/dist/app/app.js
CHANGED
|
@@ -15,11 +15,11 @@ import { isAdmin, userToShell } from "../auth/user-to-shell.js";
|
|
|
15
15
|
import { useOpenedRecord } from "../data/use-record.js";
|
|
16
16
|
import { humanize } from "../live/naming.js";
|
|
17
17
|
import { ToastProvider } from "../toasts/toast-provider.js";
|
|
18
|
-
import { documentTitle, titleParts, useDocumentTitle } from "./document-title.js";
|
|
19
18
|
import { recordTitle } from "../live/model-helpers.js";
|
|
20
19
|
import { sourceStatusOf } from "../data/with-sources.js";
|
|
21
20
|
import { MetaGate, Notice, useAdminMeta } from "./meta-gate.js";
|
|
22
21
|
import { useAppAddress } from "./use-app-address.js";
|
|
22
|
+
import { documentTitle, titleParts, useDocumentTitle } from "./document-title.js";
|
|
23
23
|
import { ProjectUiContext, useProjectUi } from "./pages/project-ui.js";
|
|
24
24
|
import { PublicFormPage } from "../forms/public-form-page.js";
|
|
25
25
|
import { useFormAddress } from "../forms/use-form-address.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn.js";
|
|
2
2
|
import { Button } from "../../primitives/button.js";
|
|
3
3
|
import { useToast } from "../../toasts/toast-provider.js";
|
|
4
|
-
import { Textarea } from "../../primitives/textarea.js";
|
|
5
4
|
import { refusalReason } from "../displays/board-columns.js";
|
|
5
|
+
import { Textarea } from "../../primitives/textarea.js";
|
|
6
6
|
import { menuKeyTarget } from "../../primitives/menu-keys.js";
|
|
7
7
|
import { activeMention, insertMention, mentionedIds } from "./mention-text.js";
|
|
8
8
|
import { useMentionable } from "./use-mentionable.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useClient } from "../../data/api-provider.js";
|
|
2
2
|
import { humanize } from "../../live/naming.js";
|
|
3
|
-
import { createBody, createSections, hasDatabaseDefault, initialDraft, isChosen, isRequired } from "./create-values.js";
|
|
4
3
|
import { recordKey } from "../../live/model-helpers.js";
|
|
5
4
|
import { Notice, useAdminMeta, usePermissions } from "../meta-gate.js";
|
|
6
5
|
import { useAppAddress } from "../use-app-address.js";
|
|
6
|
+
import { createBody, createSections, hasDatabaseDefault, initialDraft, isChosen, isRequired } from "./create-values.js";
|
|
7
7
|
import { FieldEditor } from "../field-editor.js";
|
|
8
8
|
import { fieldErrors, fieldMessage } from "../field-errors.js";
|
|
9
9
|
import { useFocusFirstInvalid } from "../use-focus-first-invalid.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isEditable, toPatchValue } from "../../live/field-values.js";
|
|
2
2
|
import { whenShown } from "../record-mode/record-mode.js";
|
|
3
3
|
import { isRecordCondition } from "@protobase/schema";
|
|
4
|
+
import { layoutItems } from "@protobase/schema/internal";
|
|
4
5
|
//#region src/app/create/create-values.ts
|
|
5
6
|
const isChosen = (value) => typeof value === "object" && value !== null && "key" in value;
|
|
6
7
|
/** Fields the user can set on a new record: not read-only, not the tenant, and not structured data. */
|
|
@@ -13,7 +14,7 @@ const isCreatable = (model, field) => field.name !== model.tenant && !field.read
|
|
|
13
14
|
const createSections = (model, view) => {
|
|
14
15
|
const creatable = Object.values(model.fields).filter((field) => isCreatable(model, field)).map((field) => field.name);
|
|
15
16
|
const placed = /* @__PURE__ */ new Set();
|
|
16
|
-
const sections = (view?.layout ?? []).flatMap((item) => {
|
|
17
|
+
const sections = layoutItems(view?.layout ?? []).flatMap((item) => {
|
|
17
18
|
if (item.kind !== "section" || isRecordCondition(item.when) || !whenShown(item.when, true)) return [];
|
|
18
19
|
const fields = item.fields.filter((name) => creatable.includes(name) && !placed.has(name) && whenShown(view?.fields[name]?.when, true));
|
|
19
20
|
fields.forEach((name) => placed.add(name));
|
|
@@ -32,7 +32,8 @@ const currentAddress = (read, defaults) => {
|
|
|
32
32
|
kind: "record",
|
|
33
33
|
resource: read.resource,
|
|
34
34
|
key: encodeKey(read.key),
|
|
35
|
-
...read.edit && { edit: true }
|
|
35
|
+
...read.edit && { edit: true },
|
|
36
|
+
...read.open && { open: read.open }
|
|
36
37
|
};
|
|
37
38
|
if (read?.kind !== "list") return read;
|
|
38
39
|
return {
|
|
@@ -2,8 +2,8 @@ import { cn } from "../../lib/cn.js";
|
|
|
2
2
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
3
3
|
import { Button } from "../../primitives/button.js";
|
|
4
4
|
import { humanize } from "../../live/naming.js";
|
|
5
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
6
5
|
import { boardCard } from "./board-columns.js";
|
|
6
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
7
7
|
import { rowAllows } from "../permissions.js";
|
|
8
8
|
import { ValueBadge } from "../value-badge.js";
|
|
9
9
|
import { RecordCard } from "./record-card.js";
|
|
@@ -2,9 +2,9 @@ import { cn } from "../../lib/cn.js";
|
|
|
2
2
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
3
3
|
import { dayIn, viewerZone } from "../../format/zone.js";
|
|
4
4
|
import { formatDate, formatWeekday } from "../../format/date.js";
|
|
5
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
6
5
|
import { usePermissions } from "../meta-gate.js";
|
|
7
6
|
import { AppLink } from "../use-app-address.js";
|
|
7
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
8
8
|
import { can, rowAllows } from "../permissions.js";
|
|
9
9
|
import { collision } from "./board-dnd.js";
|
|
10
10
|
import { byDay, calendarView, gridDays, rangeOf } from "./calendar-range.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
2
2
|
import { humanize } from "../../live/naming.js";
|
|
3
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
4
3
|
import { displayRow } from "./list-displays.js";
|
|
4
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
5
5
|
import { RecordCard } from "./record-card.js";
|
|
6
6
|
import { VirtualCards } from "./virtual-cards.js";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -2,9 +2,9 @@ import { cn } from "../../lib/cn.js";
|
|
|
2
2
|
import { Button } from "../../primitives/button.js";
|
|
3
3
|
import { Problem } from "../pages/problem.js";
|
|
4
4
|
import { humanize } from "../../live/naming.js";
|
|
5
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
6
5
|
import { mapCards } from "./list-displays.js";
|
|
7
6
|
import { useAdminMeta } from "../meta-gate.js";
|
|
7
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
8
8
|
import { useDebounced } from "../../lib/use-debounced.js";
|
|
9
9
|
import { RecordCard } from "./record-card.js";
|
|
10
10
|
import { boundsAround, worldArea } from "../map/map-area.js";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useIsDesktop, useIsXs } from "../../lib/use-media-query.js";
|
|
2
2
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
3
3
|
import { humanize } from "../../live/naming.js";
|
|
4
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
5
4
|
import { listColumnNames } from "../../live/columns-from-model.js";
|
|
6
5
|
import { displayRow, loadedFields, shownDisplay } from "../displays/list-displays.js";
|
|
7
6
|
import { defaultOrderBy, parseSort, sortToOrderBy } from "../list-order.js";
|
|
8
7
|
import { useAdminMeta, usePermissions } from "../meta-gate.js";
|
|
9
8
|
import { AppLink, useAppAddress } from "../use-app-address.js";
|
|
9
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
10
10
|
import { useProjectUi } from "../pages/project-ui.js";
|
|
11
11
|
import { can } from "../permissions.js";
|
|
12
12
|
import { DataGrid } from "../../data-grid/data-grid.js";
|
package/dist/app/field-editor.js
CHANGED
|
@@ -3,13 +3,13 @@ import { useFieldControl } from "../primitives/field-control.js";
|
|
|
3
3
|
import { Input } from "../primitives/input.js";
|
|
4
4
|
import { buttonClass } from "../primitives/button.js";
|
|
5
5
|
import { humanize } from "../live/naming.js";
|
|
6
|
-
import { fromDraft, isEditable, toFieldDraft } from "../live/field-values.js";
|
|
7
|
-
import { Switch } from "../primitives/switch.js";
|
|
8
|
-
import { Textarea } from "../primitives/textarea.js";
|
|
9
6
|
import { isMarkdown } from "../markdown/markdown-hint.js";
|
|
10
7
|
import { contactFormat, contactInputProps } from "../live/contact-links.js";
|
|
11
8
|
import { displayValue, valueTitle } from "../live/display-value.js";
|
|
12
9
|
import { AppLink } from "./use-app-address.js";
|
|
10
|
+
import { fromDraft, isEditable, toFieldDraft } from "../live/field-values.js";
|
|
11
|
+
import { Switch } from "../primitives/switch.js";
|
|
12
|
+
import { Textarea } from "../primitives/textarea.js";
|
|
13
13
|
import { RelationPicker } from "./relation-picker.js";
|
|
14
14
|
import { SensitiveField } from "./sensitive-field.js";
|
|
15
15
|
import { FileField } from "./file-field.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { whenShown } from "../record-mode/record-mode.js";
|
|
2
|
+
import { layoutItems } from "@protobase/schema/internal";
|
|
2
3
|
//#region src/app/gallery/detail-fields.ts
|
|
3
4
|
/**
|
|
4
5
|
* The fields of a photo's details: those its record page shows in its sections, or without a layout every field but its
|
|
@@ -6,7 +7,7 @@ import { whenShown } from "../record-mode/record-mode.js";
|
|
|
6
7
|
* field pointing at the gallery's record.
|
|
7
8
|
*/
|
|
8
9
|
const detailFields = (model, view, omit) => {
|
|
9
|
-
const sections = (view?.layout ?? []).flatMap((item) => item.kind === "section" && whenShown(item.when, false) ? item.fields : []);
|
|
10
|
+
const sections = layoutItems(view?.layout ?? []).flatMap((item) => item.kind === "section" && whenShown(item.when, false) ? item.fields : []);
|
|
10
11
|
return (sections.length > 0 ? [...new Set(sections)] : Object.keys(model.fields).filter((name) => name !== model.tenant && !model.primaryKey.includes(name))).filter((name) => model.fields[name] && !model.fields[name].sensitive && !omit.includes(name) && whenShown(view?.fields[name]?.when, false));
|
|
11
12
|
};
|
|
12
13
|
//#endregion
|
|
@@ -3,8 +3,8 @@ import { Skeleton } from "../../primitives/skeleton.js";
|
|
|
3
3
|
import { humanize } from "../../live/naming.js";
|
|
4
4
|
import { Section } from "../../record-view/section.js";
|
|
5
5
|
import { shownImage } from "../../live/file-values.js";
|
|
6
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
7
6
|
import { useAdminMeta, usePermissions } from "../meta-gate.js";
|
|
7
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
8
8
|
import { FileImage } from "../../live/file-image.js";
|
|
9
9
|
import { galleryTiles } from "./gallery-layout.js";
|
|
10
10
|
import { Lightbox } from "./lightbox.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
2
2
|
import { useRecord } from "../../data/use-record.js";
|
|
3
3
|
import { humanize } from "../../live/naming.js";
|
|
4
|
+
import { AppLink } from "../use-app-address.js";
|
|
4
5
|
import { SpecTable } from "../../record-view/spec-table.js";
|
|
5
6
|
import { ErrorBanner } from "../error-banner.js";
|
|
6
|
-
import { AppLink } from "../use-app-address.js";
|
|
7
7
|
import { fieldAccess } from "../permissions.js";
|
|
8
8
|
import { FieldValue } from "../pages/field-value.js";
|
|
9
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/app/group-open.ts
|
|
2
|
+
/** The open groups after the person opens or closes `title`. */
|
|
3
|
+
const toggledOpen = (open = [], title) => open.includes(title) ? open.filter((name) => name !== title) : [...open, title];
|
|
4
|
+
/** Whether a group shows its items: always unless `collapsed`, else when the address names it or one of its sections holds a field that needs the person's attention. */
|
|
5
|
+
const groupOpen = (group, open = [], needsAttention) => !group.collapsed || open.includes(group.title) || group.items.some((item) => item.kind === "section" && item.fields.some(needsAttention));
|
|
6
|
+
//#endregion
|
|
7
|
+
export { groupOpen, toggledOpen };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
2
2
|
import { humanize } from "../../live/naming.js";
|
|
3
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
4
3
|
import { recordKey, recordTitle } from "../../live/model-helpers.js";
|
|
5
4
|
import { loadedFields } from "../displays/list-displays.js";
|
|
6
5
|
import { useAdminMeta } from "../meta-gate.js";
|
|
7
6
|
import { useAppAddress } from "../use-app-address.js";
|
|
7
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
8
8
|
import { useProjectUi } from "../pages/project-ui.js";
|
|
9
9
|
import { DataGrid } from "../../data-grid/data-grid.js";
|
|
10
10
|
import { embeddedColumns } from "../embedded-list/embedded-columns.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { humanize } from "../live/naming.js";
|
|
2
2
|
import { createWhere, printFilter } from "@protobase/schema";
|
|
3
|
+
import { layoutItems } from "@protobase/schema/internal";
|
|
3
4
|
//#region src/app/incoming-relations.ts
|
|
4
5
|
const where$1 = createWhere();
|
|
5
6
|
/**
|
|
@@ -10,7 +11,7 @@ const where$1 = createWhere();
|
|
|
10
11
|
*/
|
|
11
12
|
const incomingRelations = (model, resources, views, layout, lines) => {
|
|
12
13
|
if (model.primaryKey.length !== 1) return [];
|
|
13
|
-
const listed = new Set(layout.flatMap((item) => item.kind === "related" && !item.through ? [`${item.resource}.${item.field}`] : []));
|
|
14
|
+
const listed = new Set(layoutItems(layout).flatMap((item) => item.kind === "related" && !item.through ? [`${item.resource}.${item.field}`] : []));
|
|
14
15
|
const found = Object.values(resources).flatMap((source) => Object.values(source.fields).filter((field) => field.type === "relation" && field.relation?.resource === model.name && field.relation.columns.length === 1 && field.filterable).filter((field) => field.name !== source.tenant && source.name !== lines?.name && !listed.has(`${source.name}.${field.name}`)).map((field) => ({
|
|
15
16
|
source,
|
|
16
17
|
field: field.name
|
package/dist/app/list-page.js
CHANGED
|
@@ -4,7 +4,6 @@ import { PageHeader } from "../app-shell/page-header.js";
|
|
|
4
4
|
import { useAuth } from "../auth/auth-provider.js";
|
|
5
5
|
import { Button, buttonClass } from "../primitives/button.js";
|
|
6
6
|
import { humanize } from "../live/naming.js";
|
|
7
|
-
import { ErrorBanner } from "./error-banner.js";
|
|
8
7
|
import { recordKey, recordTitleField } from "../live/model-helpers.js";
|
|
9
8
|
import { columnsFromModel, listColumnNames } from "../live/columns-from-model.js";
|
|
10
9
|
import { displayRow, listDisplayChoices, loadedFields, shownListDisplay } from "./displays/list-displays.js";
|
|
@@ -12,6 +11,7 @@ import { parseSort, sortToOrderBy } from "./list-order.js";
|
|
|
12
11
|
import { listedResource } from "./current-address.js";
|
|
13
12
|
import { Notice, useAdminMeta, usePermissions } from "./meta-gate.js";
|
|
14
13
|
import { AppLink, useAppAddress } from "./use-app-address.js";
|
|
14
|
+
import { ErrorBanner } from "./error-banner.js";
|
|
15
15
|
import { useProjectUi } from "./pages/project-ui.js";
|
|
16
16
|
import { Sheet } from "../primitives/sheet.js";
|
|
17
17
|
import { can, rowAllows } from "./permissions.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Button } from "../../../primitives/button.js";
|
|
2
2
|
import { Field } from "../../../record-view/field.js";
|
|
3
|
+
import { useAdminMeta } from "../../meta-gate.js";
|
|
3
4
|
import { isChosen } from "../../create/create-values.js";
|
|
4
5
|
import { ErrorBanner } from "../../error-banner.js";
|
|
5
|
-
import { useAdminMeta } from "../../meta-gate.js";
|
|
6
6
|
import { useProjectUi } from "../project-ui.js";
|
|
7
7
|
import { FieldEditor } from "../../field-editor.js";
|
|
8
8
|
import { ResponsiveDialog } from "../../../primitives/responsive-dialog.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Skeleton } from "../../../primitives/skeleton.js";
|
|
2
|
-
import { ErrorBanner } from "../../error-banner.js";
|
|
3
2
|
import { useAdminMeta } from "../../meta-gate.js";
|
|
3
|
+
import { ErrorBanner } from "../../error-banner.js";
|
|
4
4
|
import { RecordScope } from "../record-scope.js";
|
|
5
5
|
import { scopeOf, useListPage } from "../use-layout-data.js";
|
|
6
6
|
import { propsOf } from "./block-props.js";
|
|
@@ -4,10 +4,10 @@ import { useClient } from "../../../data/api-provider.js";
|
|
|
4
4
|
import { humanize } from "../../../live/naming.js";
|
|
5
5
|
import { useToast } from "../../../toasts/toast-provider.js";
|
|
6
6
|
import { Field } from "../../../record-view/field.js";
|
|
7
|
+
import { useAdminMeta, usePermissions } from "../../meta-gate.js";
|
|
7
8
|
import { differs, toDraft, toPatchValue } from "../../../live/field-values.js";
|
|
8
9
|
import { createBody, isChosen } from "../../create/create-values.js";
|
|
9
10
|
import { ErrorBanner } from "../../error-banner.js";
|
|
10
|
-
import { useAdminMeta, usePermissions } from "../../meta-gate.js";
|
|
11
11
|
import { FieldEditor } from "../../field-editor.js";
|
|
12
12
|
import { fieldErrors, fieldMessage } from "../../field-errors.js";
|
|
13
13
|
import { useFocusFirstInvalid } from "../../use-focus-first-invalid.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Skeleton } from "../../../primitives/skeleton.js";
|
|
2
|
-
import { ErrorBanner } from "../../error-banner.js";
|
|
3
2
|
import { useAdminMeta } from "../../meta-gate.js";
|
|
3
|
+
import { ErrorBanner } from "../../error-banner.js";
|
|
4
4
|
import { RecordScope } from "../record-scope.js";
|
|
5
5
|
import { useFirstRecord } from "../use-layout-data.js";
|
|
6
6
|
import { propsOf } from "./block-props.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useTopBarSlot } from "../../app-shell/top-bar-slot.js";
|
|
2
2
|
import "../../app-shell/top-bar.js";
|
|
3
3
|
import { ActionRail } from "../../record-view/action-rail.js";
|
|
4
|
-
import { ActionMenu } from "../../record-view/action-menu.js";
|
|
5
4
|
import { AppLink, useAppAddress } from "../use-app-address.js";
|
|
5
|
+
import { ActionMenu } from "../../record-view/action-menu.js";
|
|
6
6
|
import { ActionPrompt, asksFirst } from "./action-prompt.js";
|
|
7
7
|
import { useActionRunner } from "./use-run-action.js";
|
|
8
8
|
import { actionIcon } from "../action-icon.js";
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { whenShown } from "./record-mode.js";
|
|
2
2
|
import { isRecordCondition } from "@protobase/schema";
|
|
3
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));
|
|
4
|
+
/** The items with a record condition in `when`, those inside groups too, whose conditions the page evaluates: a group before its items. */
|
|
5
|
+
const conditionalItems = (items) => items.flatMap((item) => item.kind === "group" ? [item, ...item.items] : [item]).filter((item) => isRecordCondition(item.when));
|
|
6
6
|
/**
|
|
7
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
|
|
8
|
+
* the order `conditionalItems` lists them, `undefined` while loading) and the page is no preview; the others by the
|
|
9
|
+
* form or read view. A group keeps the items inside it that show, and goes when it or all of them do not.
|
|
9
10
|
*/
|
|
10
11
|
const shownLayout = (items, held, editing, preview) => {
|
|
11
12
|
let next = 0;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return
|
|
13
|
+
const shown = (item) => isRecordCondition(item.when) ? !preview && held[next++] === true : whenShown(item.when, editing);
|
|
14
|
+
return items.flatMap((item) => {
|
|
15
|
+
if (item.kind !== "group") return shown(item) ? [item] : [];
|
|
16
|
+
const groupShown = shown(item);
|
|
17
|
+
const inner = item.items.filter(shown);
|
|
18
|
+
return groupShown && inner.length > 0 ? [{
|
|
19
|
+
...item,
|
|
20
|
+
items: inner
|
|
21
|
+
}] : [];
|
|
15
22
|
});
|
|
16
23
|
};
|
|
17
24
|
//#endregion
|
package/dist/app/record-page.js
CHANGED
|
@@ -13,12 +13,6 @@ import { Field } from "../record-view/field.js";
|
|
|
13
13
|
import { Section } from "../record-view/section.js";
|
|
14
14
|
import { MetadataCard } from "../record-view/record-sidebar.js";
|
|
15
15
|
import { RecordHeader } from "../record-view/record-header.js";
|
|
16
|
-
import { SpecTable } from "../record-view/spec-table.js";
|
|
17
|
-
import { KeyFacts } from "../record-view/key-facts.js";
|
|
18
|
-
import { fromDraft, isRequiredOnEdit } from "../live/field-values.js";
|
|
19
|
-
import { fieldHelp, helpShown, recordShown, whenShown } from "./record-mode/record-mode.js";
|
|
20
|
-
import { isChosen, keyValue } from "./create/create-values.js";
|
|
21
|
-
import { ErrorBanner } from "./error-banner.js";
|
|
22
16
|
import { isMarkdown } from "../markdown/markdown-hint.js";
|
|
23
17
|
import { recordTitle } from "../live/model-helpers.js";
|
|
24
18
|
import { contactFormat } from "../live/contact-links.js";
|
|
@@ -27,6 +21,13 @@ import { displayValue, valueTitle } from "../live/display-value.js";
|
|
|
27
21
|
import { filledFields } from "./filled-fields.js";
|
|
28
22
|
import { Notice, useAdminMeta, usePermissions } from "./meta-gate.js";
|
|
29
23
|
import { AppLink, useAppAddress } from "./use-app-address.js";
|
|
24
|
+
import { GroupSection } from "../record-view/group-section.js";
|
|
25
|
+
import { SpecTable } from "../record-view/spec-table.js";
|
|
26
|
+
import { KeyFacts } from "../record-view/key-facts.js";
|
|
27
|
+
import { fromDraft, isRequiredOnEdit } from "../live/field-values.js";
|
|
28
|
+
import { fieldHelp, helpShown, recordShown, whenShown } from "./record-mode/record-mode.js";
|
|
29
|
+
import { isChosen, keyValue } from "./create/create-values.js";
|
|
30
|
+
import { ErrorBanner } from "./error-banner.js";
|
|
30
31
|
import { useProjectUi } from "./pages/project-ui.js";
|
|
31
32
|
import { shownWidget } from "./field-types/shown-widget.js";
|
|
32
33
|
import { WidgetPart } from "./field-types/widget-part.js";
|
|
@@ -45,6 +46,7 @@ import { auditField } from "../live/audit-fields.js";
|
|
|
45
46
|
import { CustomSection } from "./custom-section.js";
|
|
46
47
|
import { MapSection } from "./map/map-section.js";
|
|
47
48
|
import { RecordActions } from "./pages/record-actions.js";
|
|
49
|
+
import { groupOpen, toggledOpen } from "./group-open.js";
|
|
48
50
|
import { RecordLines, findLinesResource } from "./record-lines.js";
|
|
49
51
|
import { draftStorageKey } from "./draft-storage.js";
|
|
50
52
|
import { useLines } from "./use-lines.js";
|
|
@@ -150,15 +152,17 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
150
152
|
const { shown, offersEdit, editing } = recordShown(view?.recordMode, canUpdate, address?.kind === "record" && address.edit === true);
|
|
151
153
|
useNavigationGuard(editor.dirty && shown === "form");
|
|
152
154
|
usePauseFocusRefresh(editor.dirty);
|
|
153
|
-
const
|
|
155
|
+
const openGroups = address?.kind === "record" ? address.open : void 0;
|
|
156
|
+
const atEdit = address?.kind === "record" && address.edit === true;
|
|
157
|
+
const recordAt = (open = [], edit = false) => ({
|
|
154
158
|
kind: "record",
|
|
155
159
|
resource: model.name,
|
|
156
|
-
key: recordKey
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
key: recordKey,
|
|
161
|
+
...edit && { edit },
|
|
162
|
+
...open.length > 0 && { open }
|
|
163
|
+
});
|
|
164
|
+
const readAddress = recordAt(openGroups);
|
|
165
|
+
const editAddress = recordAt(openGroups, true);
|
|
162
166
|
const leavesForm = shown === "form" && view?.recordMode !== void 0 && view.recordMode !== "edit";
|
|
163
167
|
const canDelete = can(permissions, "delete") && stored.permissions?.delete !== false;
|
|
164
168
|
const remove = useDeleteFlow({
|
|
@@ -344,6 +348,69 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
344
348
|
children: "Unsaved changes"
|
|
345
349
|
})
|
|
346
350
|
] });
|
|
351
|
+
const mainItem = (item, index) => {
|
|
352
|
+
if (item.kind === "group") {
|
|
353
|
+
const children = item.items.flatMap(mainItem);
|
|
354
|
+
if (children.length === 0) return [];
|
|
355
|
+
const open = groupOpen(item, openGroups, (name) => editing && (editor.errors[name] !== void 0 || editor.conflicts[name] !== void 0));
|
|
356
|
+
const toggle = item.collapsed ? recordAt(toggledOpen(openGroups, item.title), atEdit) : void 0;
|
|
357
|
+
return [/* @__PURE__ */ jsx(GroupSection, {
|
|
358
|
+
title: item.title,
|
|
359
|
+
open,
|
|
360
|
+
toggle,
|
|
361
|
+
children
|
|
362
|
+
}, `group:${item.title}`)];
|
|
363
|
+
}
|
|
364
|
+
if (item.kind === "related") return [/* @__PURE__ */ jsx(RelatedSection, {
|
|
365
|
+
item,
|
|
366
|
+
recordKey,
|
|
367
|
+
owner: model.name
|
|
368
|
+
}, `related:${item.title}`)];
|
|
369
|
+
if (preview && (item.kind === "custom" || item.kind === "map" || item.kind === "gallery")) return [];
|
|
370
|
+
if (item.kind === "custom") return item.sidebar ? [] : [/* @__PURE__ */ jsx(CustomSection, {
|
|
371
|
+
item,
|
|
372
|
+
scope
|
|
373
|
+
}, `custom:${index}`)];
|
|
374
|
+
if (item.kind === "map") return item.sidebar ? [] : [/* @__PURE__ */ jsx(MapSection, {
|
|
375
|
+
item,
|
|
376
|
+
record: stored.record
|
|
377
|
+
}, `map:${index}`)];
|
|
378
|
+
if (item.kind === "gallery") return [/* @__PURE__ */ jsx(GallerySection, {
|
|
379
|
+
item,
|
|
380
|
+
record: stored.record,
|
|
381
|
+
recordKey
|
|
382
|
+
}, `gallery:${index}`)];
|
|
383
|
+
if (item.kind === "history") return [/* @__PURE__ */ jsx(HistorySection, {
|
|
384
|
+
item,
|
|
385
|
+
model,
|
|
386
|
+
view,
|
|
387
|
+
recordKey,
|
|
388
|
+
etag: stored.etag
|
|
389
|
+
}, `history:${index}`)];
|
|
390
|
+
if (item.kind === "comments") return [/* @__PURE__ */ jsx(CommentsSection, {
|
|
391
|
+
item,
|
|
392
|
+
resource: model.name,
|
|
393
|
+
recordKey,
|
|
394
|
+
canWrite: auth.kind !== "visitor"
|
|
395
|
+
}, `comments:${index}`)];
|
|
396
|
+
if (item.kind !== "section") return [];
|
|
397
|
+
const visibleFields = item.fields.filter((name) => model.fields[name] && visible(name));
|
|
398
|
+
const facts = shown === "read" && item.presentation === "facts";
|
|
399
|
+
const fields = facts ? filledFields(stored.record, visibleFields) : visibleFields;
|
|
400
|
+
if (fields.length === 0) return [];
|
|
401
|
+
return [/* @__PURE__ */ jsx(Section, {
|
|
402
|
+
title: item.title,
|
|
403
|
+
help: helpShown(item.helpWhen, view, editing) ? item.help : void 0,
|
|
404
|
+
children: facts ? /* @__PURE__ */ jsx(KeyFacts, { facts: fields.map((name) => ({
|
|
405
|
+
label: readRow(name).label,
|
|
406
|
+
value: readRow(name).value,
|
|
407
|
+
icon: namedIcon(hint(name)?.icon)
|
|
408
|
+
})) }) : shown === "read" ? /* @__PURE__ */ jsx(SpecTable, { rows: fields.map(readRow) }) : /* @__PURE__ */ jsx("div", {
|
|
409
|
+
className: "grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2",
|
|
410
|
+
children: fields.map(fieldView)
|
|
411
|
+
})
|
|
412
|
+
}, item.title)];
|
|
413
|
+
};
|
|
347
414
|
return /* @__PURE__ */ jsxs("div", {
|
|
348
415
|
ref: fieldsRef,
|
|
349
416
|
className: "flex flex-1 flex-col",
|
|
@@ -412,57 +479,7 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
412
479
|
className: "grid grid-cols-1 gap-8 md:grid-cols-[minmax(0,1fr)_300px] md:gap-12",
|
|
413
480
|
children: [/* @__PURE__ */ jsxs("main", {
|
|
414
481
|
className: "divide-y divide-border",
|
|
415
|
-
children: [layout.flatMap(
|
|
416
|
-
if (item.kind === "related") return [/* @__PURE__ */ jsx(RelatedSection, {
|
|
417
|
-
item,
|
|
418
|
-
recordKey,
|
|
419
|
-
owner: model.name
|
|
420
|
-
}, `related:${item.title}`)];
|
|
421
|
-
if (preview && (item.kind === "custom" || item.kind === "map" || item.kind === "gallery")) return [];
|
|
422
|
-
if (item.kind === "custom") return item.sidebar ? [] : [/* @__PURE__ */ jsx(CustomSection, {
|
|
423
|
-
item,
|
|
424
|
-
scope
|
|
425
|
-
}, `custom:${index}`)];
|
|
426
|
-
if (item.kind === "map") return item.sidebar ? [] : [/* @__PURE__ */ jsx(MapSection, {
|
|
427
|
-
item,
|
|
428
|
-
record: stored.record
|
|
429
|
-
}, `map:${index}`)];
|
|
430
|
-
if (item.kind === "gallery") return [/* @__PURE__ */ jsx(GallerySection, {
|
|
431
|
-
item,
|
|
432
|
-
record: stored.record,
|
|
433
|
-
recordKey
|
|
434
|
-
}, `gallery:${index}`)];
|
|
435
|
-
if (item.kind === "history") return [/* @__PURE__ */ jsx(HistorySection, {
|
|
436
|
-
item,
|
|
437
|
-
model,
|
|
438
|
-
view,
|
|
439
|
-
recordKey,
|
|
440
|
-
etag: stored.etag
|
|
441
|
-
}, `history:${index}`)];
|
|
442
|
-
if (item.kind === "comments") return [/* @__PURE__ */ jsx(CommentsSection, {
|
|
443
|
-
item,
|
|
444
|
-
resource: model.name,
|
|
445
|
-
recordKey,
|
|
446
|
-
canWrite: auth.kind !== "visitor"
|
|
447
|
-
}, `comments:${index}`)];
|
|
448
|
-
if (item.kind !== "section") return [];
|
|
449
|
-
const visibleFields = item.fields.filter((name) => model.fields[name] && visible(name));
|
|
450
|
-
const facts = shown === "read" && item.presentation === "facts";
|
|
451
|
-
const fields = facts ? filledFields(stored.record, visibleFields) : visibleFields;
|
|
452
|
-
if (fields.length === 0) return [];
|
|
453
|
-
return [/* @__PURE__ */ jsx(Section, {
|
|
454
|
-
title: item.title,
|
|
455
|
-
help: helpShown(item.helpWhen, view, editing) ? item.help : void 0,
|
|
456
|
-
children: facts ? /* @__PURE__ */ jsx(KeyFacts, { facts: fields.map((name) => ({
|
|
457
|
-
label: readRow(name).label,
|
|
458
|
-
value: readRow(name).value,
|
|
459
|
-
icon: namedIcon(hint(name)?.icon)
|
|
460
|
-
})) }) : shown === "read" ? /* @__PURE__ */ jsx(SpecTable, { rows: fields.map(readRow) }) : /* @__PURE__ */ jsx("div", {
|
|
461
|
-
className: "grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2",
|
|
462
|
-
children: fields.map(fieldView)
|
|
463
|
-
})
|
|
464
|
-
}, item.title)];
|
|
465
|
-
}), linesModel && !preview && /* @__PURE__ */ jsx(RecordLines, {
|
|
482
|
+
children: [layout.flatMap(mainItem), linesModel && !preview && /* @__PURE__ */ jsx(RecordLines, {
|
|
466
483
|
lines: linesModel,
|
|
467
484
|
draft: linesDraft,
|
|
468
485
|
vatRate: Number(stored.record.vatRate ?? 21) / 100,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useIsXs } from "../../lib/use-media-query.js";
|
|
2
2
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
3
3
|
import { humanize } from "../../live/naming.js";
|
|
4
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
5
4
|
import { displaysOf, loadedFields } from "../displays/list-displays.js";
|
|
6
5
|
import { defaultOrderBy } from "../list-order.js";
|
|
7
6
|
import { useAdminMeta } from "../meta-gate.js";
|
|
8
7
|
import { AppLink } from "../use-app-address.js";
|
|
8
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
9
9
|
import { useLiveRows } from "../../live/use-live-rows.js";
|
|
10
10
|
import { viewAllAddress } from "../embedded-list/embedded-display.js";
|
|
11
11
|
import { FilterChipList } from "../searches/filter-chip-list.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
2
2
|
import { humanize } from "../../live/naming.js";
|
|
3
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
4
3
|
import { listedResource } from "../current-address.js";
|
|
5
4
|
import { useAdminMeta } from "../meta-gate.js";
|
|
5
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
6
6
|
import { savedScope } from "../saved/saved-records.js";
|
|
7
7
|
import { useSavedRecords } from "../saved/use-saved-records.js";
|
|
8
8
|
import { RecordRowBody } from "./record-row-body.js";
|
|
@@ -2,9 +2,9 @@ import { Skeleton } from "../primitives/skeleton.js";
|
|
|
2
2
|
import { keys } from "../data/query-keys.js";
|
|
3
3
|
import { useClient } from "../data/api-provider.js";
|
|
4
4
|
import { Section } from "../record-view/section.js";
|
|
5
|
-
import { ErrorBanner } from "./error-banner.js";
|
|
6
5
|
import { listColumnNames } from "../live/columns-from-model.js";
|
|
7
6
|
import { useAdminMeta, usePermissions } from "./meta-gate.js";
|
|
7
|
+
import { ErrorBanner } from "./error-banner.js";
|
|
8
8
|
import { countQuery } from "./pages/use-layout-data.js";
|
|
9
9
|
import { searchAll } from "../live/search-all.js";
|
|
10
10
|
import { relatedOpens } from "./related-opens.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
2
2
|
import { humanize } from "../../live/naming.js";
|
|
3
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
4
3
|
import { recordKey } from "../../live/model-helpers.js";
|
|
5
4
|
import { useAdminMeta } from "../meta-gate.js";
|
|
6
5
|
import { AppLink } from "../use-app-address.js";
|
|
6
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
7
7
|
import { InlineFilterChips } from "./filter-chip-list.js";
|
|
8
8
|
import { RecordRowBody } from "../record-row/record-row-body.js";
|
|
9
9
|
import { useSavedSearches } from "./use-saved-searches.js";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Skeleton } from "../../primitives/skeleton.js";
|
|
2
2
|
import { PageHeader } from "../../app-shell/page-header.js";
|
|
3
3
|
import { humanize } from "../../live/naming.js";
|
|
4
|
-
import { ErrorBanner } from "../error-banner.js";
|
|
5
4
|
import { recordKey } from "../../live/model-helpers.js";
|
|
6
5
|
import { Notice, useAdminMeta, usePermissions } from "../meta-gate.js";
|
|
7
6
|
import { AppLink } from "../use-app-address.js";
|
|
7
|
+
import { ErrorBanner } from "../error-banner.js";
|
|
8
8
|
import { can } from "../permissions.js";
|
|
9
9
|
import { useSavedSearches } from "./use-saved-searches.js";
|
|
10
10
|
import { useDeleteFlow } from "../delete/use-delete-flow.js";
|
|
@@ -2,9 +2,9 @@ import { cn } from "../lib/cn.js";
|
|
|
2
2
|
import { Input } from "../primitives/input.js";
|
|
3
3
|
import { Button } from "../primitives/button.js";
|
|
4
4
|
import { humanize } from "../live/naming.js";
|
|
5
|
+
import { displayValue } from "../live/display-value.js";
|
|
5
6
|
import { toDraft } from "../live/field-values.js";
|
|
6
7
|
import { ErrorBanner } from "./error-banner.js";
|
|
7
|
-
import { displayValue } from "../live/display-value.js";
|
|
8
8
|
import { useState } from "react";
|
|
9
9
|
import { Eye, EyeOff } from "lucide-react";
|
|
10
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -21,6 +21,7 @@ export declare const useAppAddress: () => {
|
|
|
21
21
|
/** A link to an address in the app: an ordinary anchor that the router follows without reloading the page. */
|
|
22
22
|
export declare const AppLink: ({ to, ...rest }: AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
23
23
|
to: AppAddress;
|
|
24
|
+
replace?: boolean;
|
|
24
25
|
}) => import("react").JSX.Element;
|
|
25
26
|
//#endregion
|
|
26
27
|
export type { CurrentAddress, ListAddress, ListChange };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { keys } from "../data/query-keys.js";
|
|
2
2
|
import { useClient } from "../data/api-provider.js";
|
|
3
|
-
import { differs, toDraft, toPatchValue } from "../live/field-values.js";
|
|
4
3
|
import { displayValue } from "../live/display-value.js";
|
|
4
|
+
import { differs, toDraft, toPatchValue } from "../live/field-values.js";
|
|
5
5
|
import { fieldErrors } from "./field-errors.js";
|
|
6
6
|
import { useLiveSave } from "./use-live-save.js";
|
|
7
7
|
import { readDraft, writeDraft } from "./draft-storage.js";
|