@protobase/ui 0.23.10 → 0.23.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-page.js +3 -2
- package/dist/app/create/create-record-form.js +4 -3
- package/dist/app/create/create-route.js +5 -8
- package/dist/app/create/create-values.js +2 -1
- package/dist/app/create/form.js +3 -2
- package/dist/app/create/messages.js +32 -0
- package/dist/app/delete/blocked-delete.js +6 -5
- package/dist/app/delete/dialogs.js +21 -20
- package/dist/app/delete/messages.js +66 -0
- package/dist/app/delete/use-delete-flow.js +7 -6
- package/dist/app/displays/display-controls.js +5 -4
- package/dist/app/displays/list-displays.js +2 -1
- package/dist/app/displays/messages.js +20 -0
- package/dist/app/embedded-list/cut-off-note.js +2 -1
- package/dist/app/embedded-list/embedded-list.js +11 -6
- package/dist/app/embedded-list/embedded-row-targets.js +4 -3
- package/dist/app/embedded-list/messages.js +24 -0
- package/dist/app/error-banner.js +2 -1
- package/dist/app/field-errors.js +19 -19
- package/dist/app/field-messages.js +48 -0
- package/dist/app/hand-order/hand-sorted-list.js +6 -11
- package/dist/app/hand-order/messages.js +22 -0
- package/dist/app/hand-order/sort-button.js +2 -1
- package/dist/app/hand-order/use-hand-order.js +2 -1
- package/dist/app/incoming-card.js +2 -1
- package/dist/app/list-edit/messages.js +14 -0
- package/dist/app/list-edit/use-list-edits.js +2 -1
- package/dist/app/list-empty-state.js +5 -5
- package/dist/app/list-empty.js +3 -2
- package/dist/app/list-messages.js +54 -0
- package/dist/app/list-page.js +24 -38
- package/dist/app/list-search.js +8 -7
- package/dist/app/messages.js +16 -0
- package/dist/app/pages/record-actions.js +4 -3
- package/dist/app/record-lines.js +3 -2
- package/dist/app/record-messages.js +78 -0
- package/dist/app/record-page.js +31 -32
- package/dist/app/related-section.js +5 -4
- package/dist/app/use-live-save.js +4 -3
- package/dist/data-grid/cell-text.js +2 -1
- package/dist/data-grid/cells.js +15 -14
- package/dist/data-grid/grid-footer.js +3 -2
- package/dist/data-grid/grid-header.js +4 -3
- package/dist/data-grid/grid-row.js +2 -1
- package/dist/data-grid/header-menu.js +5 -4
- package/dist/data-grid/messages.js +50 -0
- package/dist/data-grid/row-menu.js +2 -1
- package/dist/data-grid/row-reorder.js +8 -8
- package/dist/filter-panel/active-filter-chips.js +3 -2
- package/dist/filter-panel/date-presets.js +5 -4
- package/dist/filter-panel/facet-combobox.js +3 -2
- package/dist/filter-panel/facet-group.js +3 -2
- package/dist/filter-panel/filter-config.js +15 -12
- package/dist/filter-panel/filter-panel.js +4 -3
- package/dist/filter-panel/filter-state.js +2 -2
- package/dist/filter-panel/messages.js +52 -0
- package/dist/filter-panel/more-filters-menu.js +3 -2
- package/dist/filter-panel/range-filter.js +2 -1
- package/dist/filter-panel/secondary-filters.js +2 -1
- package/dist/forms/messages.js +24 -0
- package/dist/forms/prove-human.js +2 -1
- package/dist/forms/public-form-page.js +14 -8
- package/dist/live/display-value.js +3 -2
- package/dist/live/file-values.js +2 -1
- package/dist/live/messages.js +18 -0
- package/dist/package.js +1 -1
- package/dist/record-view/action-menu.js +2 -1
- package/dist/record-view/action-rail.js +2 -1
- package/dist/record-view/field.js +12 -7
- package/dist/record-view/messages.js +38 -0
- package/dist/record-view/record-header.js +8 -10
- package/dist/shell/assets/app-Bc3JCvDX.js +69 -0
- package/dist/shell/assets/export-worker-Bz57Pf5g.js +2 -0
- package/dist/shell/assets/{index-CPnp4jVD.js → index-DMPV9_11.js} +1 -1
- package/dist/shell/assets/{maplibre-CJlfYYmB.js → maplibre-CqykHSB6.js} +1 -1
- package/dist/shell/assets/{markdown-content-BDgnIKSJ.js → markdown-content-BmKfQOZ6.js} +1 -1
- package/dist/shell/assets/{record-map-BCWeSTdZ.js → record-map-D2ccAykf.js} +1 -1
- package/dist/shell/assets/{records-map-BhNGzm42.js → records-map-DKQG53I0.js} +1 -1
- package/dist/shell/assets/{shared-protobase-ui-C6_QptrK.js → shared-protobase-ui-BCsFQ2PD.js} +1 -1
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/shell/protobase.sw.js +2 -2
- package/package.json +4 -4
- package/dist/shell/assets/app-BPh2QhzJ.js +0 -69
- package/dist/shell/assets/export-worker-O1HF9R4j.js +0 -2
|
@@ -3,6 +3,7 @@ import { humanize } from "../../live/naming.js";
|
|
|
3
3
|
import { recordKey } from "../../live/model-helpers.js";
|
|
4
4
|
import { Notice, useAdminMeta, usePermissions } from "../meta-gate.js";
|
|
5
5
|
import { useAppAddress } from "../use-app-address.js";
|
|
6
|
+
import { text } from "./messages.js";
|
|
6
7
|
import { createBody, createSections, hasDatabaseDefault, initialDraft, isChosen, isRequired } from "./create-values.js";
|
|
7
8
|
import { FieldEditor } from "../field-editor.js";
|
|
8
9
|
import { fieldErrors, fieldMessage } from "../field-errors.js";
|
|
@@ -50,7 +51,7 @@ const LoadedCreate = ({ resource, values }) => {
|
|
|
50
51
|
const queryClient = useQueryClient();
|
|
51
52
|
const { go } = useAppAddress();
|
|
52
53
|
const noun = view?.names?.singular ?? humanize(resource);
|
|
53
|
-
const feedback = useLiveSave(view?.saveFeedback ?? "toast",
|
|
54
|
+
const feedback = useLiveSave(view?.saveFeedback ?? "toast", text("subject", noun));
|
|
54
55
|
const sections = createSections(model, view);
|
|
55
56
|
const [draft, setDraft] = useState(() => initialDraft(model, sections, values));
|
|
56
57
|
const [errors, setErrors] = useState({});
|
|
@@ -131,7 +132,7 @@ const LoadedCreate = ({ resource, values }) => {
|
|
|
131
132
|
value: isChosen(value) ? value.key : value ?? (field.type === "boolean" ? false : ""),
|
|
132
133
|
stored: void 0,
|
|
133
134
|
relationLabel: isChosen(value) ? value.label : void 0,
|
|
134
|
-
placeholder: hasDatabaseDefault(field) ? "
|
|
135
|
+
placeholder: hasDatabaseDefault(field) ? text("setByDatabase") : void 0,
|
|
135
136
|
relationPicker: target ? {
|
|
136
137
|
target,
|
|
137
138
|
view: meta.views[target.name]
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { Field } from "../../record-view/field.js";
|
|
2
2
|
import { Section } from "../../record-view/section.js";
|
|
3
|
+
import { text } from "./messages.js";
|
|
3
4
|
import { Form } from "./form.js";
|
|
4
5
|
import { jsx } from "react/jsx-runtime";
|
|
5
6
|
//#region src/app/create/create-record-form.tsx
|
|
6
7
|
/** The presentation of "New <thing>": the view's sections, each field with its editor, and Create or Cancel. */
|
|
7
8
|
const CreateRecordForm = ({ noun, collection, sections, saving, message, onSubmit, onCancel, ref }) => /* @__PURE__ */ jsx(Form, {
|
|
8
9
|
ref,
|
|
9
|
-
title:
|
|
10
|
+
title: text("title", noun),
|
|
10
11
|
eyebrow: collection,
|
|
11
|
-
submit:
|
|
12
|
+
submit: text("submit", noun),
|
|
12
13
|
saving,
|
|
13
14
|
error: message ? new Error(message) : void 0,
|
|
14
|
-
errorTitle: "
|
|
15
|
+
errorTitle: text("notCreated"),
|
|
15
16
|
onSubmit,
|
|
16
17
|
onCancel,
|
|
17
18
|
children: sections.map((section) => /* @__PURE__ */ jsx(Section, {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { humanize } from "../../live/naming.js";
|
|
2
2
|
import { Notice, useAdminMeta } from "../meta-gate.js";
|
|
3
|
+
import { text } from "./messages.js";
|
|
3
4
|
import { ComposedPage } from "../pages/composed-page.js";
|
|
4
5
|
import { CreatePage } from "./create-page.js";
|
|
5
|
-
import { jsx
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
//#region src/app/create/create-route.tsx
|
|
7
8
|
/**
|
|
8
9
|
* `/:resource/new`: the view's create page when it names one, which reads the address's `values` itself and may change
|
|
@@ -15,13 +16,9 @@ const CreateRoute = ({ resource, values }) => {
|
|
|
15
16
|
resource,
|
|
16
17
|
values
|
|
17
18
|
}, new URLSearchParams(values).toString());
|
|
18
|
-
if (!meta.pages[view.create.page]) return /* @__PURE__ */
|
|
19
|
-
title: "
|
|
20
|
-
children:
|
|
21
|
-
"You cannot create ",
|
|
22
|
-
view.names?.plural.toLowerCase() ?? humanize(resource),
|
|
23
|
-
"."
|
|
24
|
-
]
|
|
19
|
+
if (!meta.pages[view.create.page]) return /* @__PURE__ */ jsx(Notice, {
|
|
20
|
+
title: text("notAllowed"),
|
|
21
|
+
children: text("cannotCreate", view.names?.plural.toLowerCase() ?? humanize(resource))
|
|
25
22
|
});
|
|
26
23
|
return /* @__PURE__ */ jsx(ComposedPage, { name: view.create.page });
|
|
27
24
|
};
|
|
@@ -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 { text } from "./messages.js";
|
|
3
4
|
import { layoutItems } from "@protobase/schema/internal";
|
|
4
5
|
import { isRecordCondition } from "@protobase/schema";
|
|
5
6
|
//#region src/app/create/create-values.ts
|
|
@@ -27,7 +28,7 @@ const createSections = (model, view) => {
|
|
|
27
28
|
const rest = creatable.filter((name) => !placed.has(name));
|
|
28
29
|
if (rest.length === 0) return sections;
|
|
29
30
|
return [...sections, {
|
|
30
|
-
title: sections.length === 0 ? "
|
|
31
|
+
title: sections.length === 0 ? text("details") : text("otherFields"),
|
|
31
32
|
fields: rest
|
|
32
33
|
}];
|
|
33
34
|
};
|
package/dist/app/create/form.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { Button } from "../../primitives/button.js";
|
|
2
|
+
import { text } from "./messages.js";
|
|
2
3
|
import { ErrorBanner } from "../error-banner.js";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
//#region src/app/create/form.tsx
|
|
5
6
|
/** The frame of the app's forms: Cancel and the submit button, a failure that belongs to no field, and the sections. */
|
|
6
|
-
const Form = ({ title, eyebrow, submit, saving, disabled, error, errorTitle = "
|
|
7
|
+
const Form = ({ title, eyebrow, submit, saving, disabled, error, errorTitle = text("notSaved"), onSubmit, onCancel, children, ref }) => {
|
|
7
8
|
const buttons = /* @__PURE__ */ jsxs("div", {
|
|
8
9
|
className: "flex items-center gap-2",
|
|
9
10
|
children: [onCancel && /* @__PURE__ */ jsx(Button, {
|
|
10
11
|
className: "max-sm:min-h-11",
|
|
11
12
|
onClick: onCancel,
|
|
12
|
-
children: "
|
|
13
|
+
children: text("cancel")
|
|
13
14
|
}), /* @__PURE__ */ jsx(Button, {
|
|
14
15
|
variant: "primary",
|
|
15
16
|
type: "submit",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineMessages } from "../../locale/messages.js";
|
|
2
|
+
//#region src/app/create/messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
title: (noun) => `New ${noun.toLowerCase()}`,
|
|
6
|
+
subject: (noun) => `New ${noun.toLowerCase()}`,
|
|
7
|
+
submit: (noun) => `Create ${noun.toLowerCase()}`,
|
|
8
|
+
notCreated: "Not created",
|
|
9
|
+
notSaved: "Not saved",
|
|
10
|
+
cancel: "Cancel",
|
|
11
|
+
setByDatabase: "Set by the database",
|
|
12
|
+
details: "Details",
|
|
13
|
+
otherFields: "Other fields",
|
|
14
|
+
notAllowed: "Not allowed",
|
|
15
|
+
cannotCreate: (plural) => `You cannot create ${plural}.`
|
|
16
|
+
},
|
|
17
|
+
nl: {
|
|
18
|
+
title: (noun) => `${noun} toevoegen`,
|
|
19
|
+
subject: (noun) => noun,
|
|
20
|
+
submit: (noun) => `${noun} aanmaken`,
|
|
21
|
+
notCreated: "Niet aangemaakt",
|
|
22
|
+
notSaved: "Niet bewaard",
|
|
23
|
+
cancel: "Annuleren",
|
|
24
|
+
setByDatabase: "Ingevuld door de database",
|
|
25
|
+
details: "Details",
|
|
26
|
+
otherFields: "Overige velden",
|
|
27
|
+
notAllowed: "Niet toegestaan",
|
|
28
|
+
cannotCreate: (plural) => `Je mag geen ${plural} aanmaken.`
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
//#endregion
|
|
32
|
+
export { text };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { humanize } from "../../live/naming.js";
|
|
2
|
+
import { text } from "./messages.js";
|
|
2
3
|
import { createWhere, printFilter } from "@protobase/schema";
|
|
3
4
|
//#region src/app/delete/blocked-delete.ts
|
|
4
5
|
const where$1 = createWhere();
|
|
5
6
|
/** What a delete other records still point at says, such as "Orders still point at this company." */
|
|
6
|
-
const blockedDeleteText = ({ blockingPlural, noun }) =>
|
|
7
|
+
const blockedDeleteText = ({ blockingPlural, noun }) => text("stillPointAt", blockingPlural, noun);
|
|
7
8
|
/**
|
|
8
9
|
* Why the delete was refused, naming the records that point at it by their view's plural, and, when the server names
|
|
9
10
|
* the field pointing at it, a link to their list filtered to those records. `undefined` for a resource the app does not show.
|
|
@@ -12,20 +13,20 @@ const blockedDelete = ({ blockedBy, resources, views, noun, key }) => {
|
|
|
12
13
|
const { resource, field } = blockedBy;
|
|
13
14
|
if (!resources[resource]) return void 0;
|
|
14
15
|
const plural = views[resource]?.names?.plural ?? humanize(resource);
|
|
15
|
-
const text = blockedDeleteText({
|
|
16
|
+
const text$1 = blockedDeleteText({
|
|
16
17
|
blockingPlural: plural,
|
|
17
18
|
noun
|
|
18
19
|
});
|
|
19
|
-
if (!field || key === void 0) return { text };
|
|
20
|
+
if (!field || key === void 0) return { text: text$1 };
|
|
20
21
|
const list = {
|
|
21
22
|
kind: "list",
|
|
22
23
|
resource,
|
|
23
24
|
filter: printFilter(where$1.eq(field, key))
|
|
24
25
|
};
|
|
25
26
|
return {
|
|
26
|
-
text,
|
|
27
|
+
text: text$1,
|
|
27
28
|
link: {
|
|
28
|
-
label:
|
|
29
|
+
label: text("show", plural),
|
|
29
30
|
to: list
|
|
30
31
|
}
|
|
31
32
|
};
|
|
@@ -1,39 +1,40 @@
|
|
|
1
1
|
import { Button } from "../../primitives/button.js";
|
|
2
2
|
import { Dialog } from "../../primitives/dialog.js";
|
|
3
|
+
import { text } from "./messages.js";
|
|
3
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
//#region src/app/delete/dialogs.tsx
|
|
5
6
|
const ConfirmDeleteDialog = ({ title, noun, busy, onConfirm, onCancel }) => /* @__PURE__ */ jsx(Dialog, {
|
|
6
|
-
title:
|
|
7
|
-
description:
|
|
7
|
+
title: text("confirmTitle", title),
|
|
8
|
+
description: text("confirmDescription", noun, title),
|
|
8
9
|
onClose: onCancel,
|
|
9
10
|
actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
10
11
|
onClick: onCancel,
|
|
11
|
-
children: "
|
|
12
|
+
children: text("cancel")
|
|
12
13
|
}), /* @__PURE__ */ jsx(Button, {
|
|
13
14
|
variant: "primary",
|
|
14
15
|
className: "bg-danger hover:bg-danger/90",
|
|
15
16
|
loading: busy,
|
|
16
17
|
onClick: onConfirm,
|
|
17
|
-
children: "
|
|
18
|
+
children: text("delete")
|
|
18
19
|
})] })
|
|
19
20
|
});
|
|
20
21
|
const ConflictDialog = ({ title, changes, busy, onReview, onDeleteAnyway, onCancel }) => /* @__PURE__ */ jsx(Dialog, {
|
|
21
|
-
title:
|
|
22
|
-
description: "
|
|
22
|
+
title: text("changedTitle", title),
|
|
23
|
+
description: text("changedDescription"),
|
|
23
24
|
onClose: onCancel,
|
|
24
25
|
actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
25
26
|
onClick: onReview,
|
|
26
|
-
children: "
|
|
27
|
+
children: text("reviewChanges")
|
|
27
28
|
}), /* @__PURE__ */ jsx(Button, {
|
|
28
29
|
variant: "primary",
|
|
29
30
|
className: "bg-danger hover:bg-danger/90",
|
|
30
31
|
loading: busy,
|
|
31
32
|
onClick: onDeleteAnyway,
|
|
32
|
-
children: "
|
|
33
|
+
children: text("deleteAnyway")
|
|
33
34
|
})] }),
|
|
34
35
|
children: changes.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
35
36
|
className: "text-muted-foreground",
|
|
36
|
-
children: "
|
|
37
|
+
children: text("noVisibleChanges")
|
|
37
38
|
}) : /* @__PURE__ */ jsx("ul", {
|
|
38
39
|
className: "divide-y rounded-md border",
|
|
39
40
|
children: changes.map((change) => /* @__PURE__ */ jsxs("li", {
|
|
@@ -57,38 +58,38 @@ const ConflictDialog = ({ title, changes, busy, onReview, onDeleteAnyway, onCanc
|
|
|
57
58
|
});
|
|
58
59
|
const BulkDeleteDialog = (props) => {
|
|
59
60
|
if (props.stage === "confirm") return /* @__PURE__ */ jsx(Dialog, {
|
|
60
|
-
title:
|
|
61
|
-
description: "
|
|
61
|
+
title: text("bulkConfirmTitle", props.count, props.plural),
|
|
62
|
+
description: text("bulkConfirmDescription"),
|
|
62
63
|
onClose: props.onCancel,
|
|
63
64
|
actions: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
64
65
|
onClick: props.onCancel,
|
|
65
|
-
children: "
|
|
66
|
-
}), /* @__PURE__ */
|
|
66
|
+
children: text("cancel")
|
|
67
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
67
68
|
variant: "primary",
|
|
68
69
|
className: "bg-danger hover:bg-danger/90",
|
|
69
70
|
onClick: props.onConfirm,
|
|
70
|
-
children:
|
|
71
|
+
children: text("deleteCount", props.count)
|
|
71
72
|
})] })
|
|
72
73
|
});
|
|
73
74
|
if (props.stage === "running") return /* @__PURE__ */ jsx(Dialog, {
|
|
74
|
-
title: "
|
|
75
|
-
description:
|
|
75
|
+
title: text("deleting"),
|
|
76
|
+
description: text("progress", props.done, props.total),
|
|
76
77
|
onClose: () => void 0,
|
|
77
78
|
actions: /* @__PURE__ */ jsx(Button, {
|
|
78
79
|
loading: true,
|
|
79
80
|
disabled: true,
|
|
80
|
-
children: "
|
|
81
|
+
children: text("working")
|
|
81
82
|
})
|
|
82
83
|
});
|
|
83
84
|
const deleted = props.total - props.failures.length;
|
|
84
85
|
return /* @__PURE__ */ jsx(Dialog, {
|
|
85
|
-
title: props.failures.length === 0 ?
|
|
86
|
-
description: props.failures.length === 0 ? void 0 :
|
|
86
|
+
title: props.failures.length === 0 ? text("deleted", deleted) : text("deletedOf", deleted, props.total),
|
|
87
|
+
description: props.failures.length === 0 ? void 0 : text("kept", props.failures.length),
|
|
87
88
|
onClose: props.onClose,
|
|
88
89
|
actions: /* @__PURE__ */ jsx(Button, {
|
|
89
90
|
variant: "primary",
|
|
90
91
|
onClick: props.onClose,
|
|
91
|
-
children: "
|
|
92
|
+
children: text("done")
|
|
92
93
|
}),
|
|
93
94
|
children: props.failures.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
94
95
|
className: "max-h-48 divide-y overflow-y-auto rounded-md border",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { defineMessages } from "../../locale/messages.js";
|
|
2
|
+
//#region src/app/delete/messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
confirmTitle: (title) => `Delete ${title}?`,
|
|
6
|
+
confirmDescription: (noun, _title) => `This ${noun.toLowerCase()} will be removed. This cannot be undone.`,
|
|
7
|
+
cancel: "Cancel",
|
|
8
|
+
delete: "Delete",
|
|
9
|
+
changedTitle: (title) => `${title} was changed since you opened it`,
|
|
10
|
+
changedDescription: "Someone saved this record in the meantime. Review what changed before you delete it.",
|
|
11
|
+
reviewChanges: "Review changes",
|
|
12
|
+
deleteAnyway: "Delete anyway",
|
|
13
|
+
noVisibleChanges: "No visible fields differ; the record was saved again with the same values.",
|
|
14
|
+
bulkConfirmTitle: (count, plural) => `Delete ${count} ${count === 1 ? plural.replace(/s$/, "") : plural.toLowerCase()}?`,
|
|
15
|
+
bulkConfirmDescription: "This cannot be undone. Each record is deleted only if it has not changed since the list loaded; anything that changed is reported and kept.",
|
|
16
|
+
deleteCount: (count) => `Delete ${count}`,
|
|
17
|
+
deleting: "Deleting",
|
|
18
|
+
progress: (done, total) => `${done} of ${total} done`,
|
|
19
|
+
working: "Working",
|
|
20
|
+
deleted: (count) => `Deleted ${count}`,
|
|
21
|
+
deletedOf: (count, total) => `Deleted ${count} of ${total}`,
|
|
22
|
+
kept: (count) => `${count} could not be deleted and were kept.`,
|
|
23
|
+
done: "Done",
|
|
24
|
+
deletedToast: (title) => `${title} deleted`,
|
|
25
|
+
undo: "Undo",
|
|
26
|
+
restored: (title) => `${title} restored`,
|
|
27
|
+
alreadyBack: (title) => `${title} is already back`,
|
|
28
|
+
couldNotRestore: (title) => `Could not restore ${title}`,
|
|
29
|
+
couldNotDelete: (title) => `Could not delete ${title}`,
|
|
30
|
+
changedSinceLoaded: "Changed since the list loaded, so it was kept.",
|
|
31
|
+
stillPointAt: (plural, noun) => `${plural} still point at this ${noun.toLowerCase()}.`,
|
|
32
|
+
show: (plural) => `Show ${plural.toLowerCase()}`
|
|
33
|
+
},
|
|
34
|
+
nl: {
|
|
35
|
+
confirmTitle: (title) => `${title} verwijderen?`,
|
|
36
|
+
confirmDescription: (_noun, title) => `${title} wordt verwijderd. Dit kan niet ongedaan worden gemaakt.`,
|
|
37
|
+
cancel: "Annuleren",
|
|
38
|
+
delete: "Verwijderen",
|
|
39
|
+
changedTitle: (title) => `${title} is gewijzigd sinds je het opende`,
|
|
40
|
+
changedDescription: "Iemand heeft dit record intussen bewaard. Bekijk wat er veranderde voordat je het verwijdert.",
|
|
41
|
+
reviewChanges: "Wijzigingen bekijken",
|
|
42
|
+
deleteAnyway: "Toch verwijderen",
|
|
43
|
+
noVisibleChanges: "Geen zichtbaar veld verschilt; het record is opnieuw bewaard met dezelfde waarden.",
|
|
44
|
+
bulkConfirmTitle: (count, plural) => count === 1 ? "Eén record verwijderen?" : `${count} ${plural.toLowerCase()} verwijderen?`,
|
|
45
|
+
bulkConfirmDescription: "Dit kan niet ongedaan worden gemaakt. Elk record wordt alleen verwijderd als het niet is gewijzigd sinds de lijst laadde; wat wel is gewijzigd, wordt gemeld en behouden.",
|
|
46
|
+
deleteCount: (count) => `${count} verwijderen`,
|
|
47
|
+
deleting: "Verwijderen",
|
|
48
|
+
progress: (done, total) => `${done} van ${total} klaar`,
|
|
49
|
+
working: "Bezig",
|
|
50
|
+
deleted: (count) => `${count} verwijderd`,
|
|
51
|
+
deletedOf: (count, total) => `${count} van ${total} verwijderd`,
|
|
52
|
+
kept: (count) => `${count} kon${count === 1 ? "" : "den"} niet worden verwijderd en ${count === 1 ? "is" : "zijn"} behouden.`,
|
|
53
|
+
done: "Klaar",
|
|
54
|
+
deletedToast: (title) => `${title} verwijderd`,
|
|
55
|
+
undo: "Ongedaan maken",
|
|
56
|
+
restored: (title) => `${title} teruggezet`,
|
|
57
|
+
alreadyBack: (title) => `${title} is al terug`,
|
|
58
|
+
couldNotRestore: (title) => `Kon ${title} niet terugzetten`,
|
|
59
|
+
couldNotDelete: (title) => `Kon ${title} niet verwijderen`,
|
|
60
|
+
changedSinceLoaded: "Gewijzigd sinds de lijst laadde, dus behouden.",
|
|
61
|
+
stillPointAt: (plural, _noun) => `${plural} verwijzen hier nog naar.`,
|
|
62
|
+
show: (plural) => `${plural} tonen`
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
//#endregion
|
|
66
|
+
export { text };
|
|
@@ -3,6 +3,7 @@ import { humanize } from "../../live/naming.js";
|
|
|
3
3
|
import { useToast } from "../../toasts/toast-provider.js";
|
|
4
4
|
import { useAdminMeta } from "../meta-gate.js";
|
|
5
5
|
import { useAppAddress } from "../use-app-address.js";
|
|
6
|
+
import { text } from "./messages.js";
|
|
6
7
|
import { blockedDelete } from "./blocked-delete.js";
|
|
7
8
|
import { BulkDeleteDialog, ConfirmDeleteDialog, ConflictDialog } from "./dialogs.js";
|
|
8
9
|
import { diffRecords } from "./record-diff.js";
|
|
@@ -62,22 +63,22 @@ const useDeleteFlow = ({ model, view, onDeleted, onReview, restore }) => {
|
|
|
62
63
|
onDeleted?.(target);
|
|
63
64
|
toast.show({
|
|
64
65
|
state: "success",
|
|
65
|
-
title:
|
|
66
|
+
title: text("deletedToast", target.title),
|
|
66
67
|
...model.softDelete && restore && { action: {
|
|
67
|
-
label: "
|
|
68
|
+
label: text("undo"),
|
|
68
69
|
onClick: () => {
|
|
69
70
|
restore(target).then(() => {
|
|
70
71
|
refresh();
|
|
71
72
|
toast.show({
|
|
72
73
|
state: "success",
|
|
73
|
-
title:
|
|
74
|
+
title: text("restored", target.title)
|
|
74
75
|
});
|
|
75
76
|
}, (error) => {
|
|
76
77
|
if (!isApiError(error)) throw error;
|
|
77
78
|
refresh();
|
|
78
79
|
toast.show({
|
|
79
80
|
state: error.status === 409 ? "success" : "error",
|
|
80
|
-
title: error.status === 409 ?
|
|
81
|
+
title: error.status === 409 ? text("alreadyBack", target.title) : text("couldNotRestore", target.title),
|
|
81
82
|
description: error.status === 409 ? void 0 : error.message
|
|
82
83
|
});
|
|
83
84
|
});
|
|
@@ -102,7 +103,7 @@ const useDeleteFlow = ({ model, view, onDeleted, onReview, restore }) => {
|
|
|
102
103
|
const why = blocked(error, target);
|
|
103
104
|
toast.show({
|
|
104
105
|
state: "error",
|
|
105
|
-
title:
|
|
106
|
+
title: text("couldNotDelete", target.title),
|
|
106
107
|
description: why?.text ?? error.message,
|
|
107
108
|
...why?.link && { action: {
|
|
108
109
|
label: why.link.label,
|
|
@@ -118,7 +119,7 @@ const useDeleteFlow = ({ model, view, onDeleted, onReview, restore }) => {
|
|
|
118
119
|
try {
|
|
119
120
|
return await remove(target, target.etag) ? {
|
|
120
121
|
title: target.title,
|
|
121
|
-
message: "
|
|
122
|
+
message: text("changedSinceLoaded")
|
|
122
123
|
} : void 0;
|
|
123
124
|
} catch (error) {
|
|
124
125
|
if (!isApiError(error)) throw error;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn.js";
|
|
2
2
|
import { Segmented } from "../../primitives/segmented.js";
|
|
3
3
|
import { humanize } from "../../live/naming.js";
|
|
4
|
+
import { text } from "./messages.js";
|
|
4
5
|
import { displayChoices, sortFields } from "./list-displays.js";
|
|
5
6
|
import { parseSort } from "../list-order.js";
|
|
6
7
|
import { MenuSelect } from "../../primitives/menu-select.js";
|
|
@@ -30,13 +31,13 @@ const DisplaySwitch = ({ view, value, onChange, menu = false, choices = displayC
|
|
|
30
31
|
icon: choice.icon === void 0 ? void 0 : icons[choice.icon]
|
|
31
32
|
}));
|
|
32
33
|
if (menu) return /* @__PURE__ */ jsx(MenuSelect, {
|
|
33
|
-
label: "
|
|
34
|
+
label: text("display"),
|
|
34
35
|
value,
|
|
35
36
|
onChange,
|
|
36
37
|
options
|
|
37
38
|
});
|
|
38
39
|
return /* @__PURE__ */ jsx(Segmented, {
|
|
39
|
-
label: "
|
|
40
|
+
label: text("display"),
|
|
40
41
|
size: "md",
|
|
41
42
|
value,
|
|
42
43
|
onChange,
|
|
@@ -51,7 +52,7 @@ const SortMenu = ({ model, view, display, orderBy, onChange, className }) => {
|
|
|
51
52
|
const names = fields.includes(current.columnId) ? fields : [current.columnId, ...fields];
|
|
52
53
|
const label = (name) => view?.fields[name]?.label ?? humanize(name);
|
|
53
54
|
return /* @__PURE__ */ jsx("select", {
|
|
54
|
-
"aria-label": "
|
|
55
|
+
"aria-label": text("sort"),
|
|
55
56
|
value: `${current.columnId} ${current.direction}`,
|
|
56
57
|
onChange: (event) => onChange(event.target.value),
|
|
57
58
|
className: cn("h-8 rounded-md border border-border-strong bg-background px-2.5 shadow-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20", className),
|
|
@@ -60,7 +61,7 @@ const SortMenu = ({ model, view, display, orderBy, onChange, className }) => {
|
|
|
60
61
|
children: [
|
|
61
62
|
label(name),
|
|
62
63
|
", ",
|
|
63
|
-
word
|
|
64
|
+
text(word)
|
|
64
65
|
]
|
|
65
66
|
}, `${name} ${direction}`)))
|
|
66
67
|
});
|
|
@@ -5,6 +5,7 @@ import { displayValue } from "../../live/display-value.js";
|
|
|
5
5
|
import { photosKey } from "../../live/row-photos.js";
|
|
6
6
|
import { filledFields } from "../filled-fields.js";
|
|
7
7
|
import { boardCard } from "./board-columns.js";
|
|
8
|
+
import { text } from "./messages.js";
|
|
8
9
|
import { encodeKey, tableDisplay } from "@protobase/schema/internal";
|
|
9
10
|
//#region src/app/displays/list-displays.ts
|
|
10
11
|
const displaysOf = (view) => view?.list?.displays ?? [];
|
|
@@ -68,7 +69,7 @@ const defaultIcons = {
|
|
|
68
69
|
};
|
|
69
70
|
const displayChoices = (view) => [{
|
|
70
71
|
name: tableDisplay,
|
|
71
|
-
label: "
|
|
72
|
+
label: text("table"),
|
|
72
73
|
icon: "table"
|
|
73
74
|
}, ...displaysOf(view).map((display) => ({
|
|
74
75
|
name: display.name,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineMessages } from "../../locale/messages.js";
|
|
2
|
+
//#region src/app/displays/messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
table: "Table",
|
|
6
|
+
display: "Display",
|
|
7
|
+
sort: "Sort",
|
|
8
|
+
ascending: "ascending",
|
|
9
|
+
descending: "descending"
|
|
10
|
+
},
|
|
11
|
+
nl: {
|
|
12
|
+
table: "Tabel",
|
|
13
|
+
display: "Weergave",
|
|
14
|
+
sort: "Sorteren",
|
|
15
|
+
ascending: "oplopend",
|
|
16
|
+
descending: "aflopend"
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
//#endregion
|
|
20
|
+
export { text };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatTag } from "../../locale/active-locale.js";
|
|
2
|
+
import { text } from "./messages.js";
|
|
2
3
|
//#region src/app/embedded-list/cut-off-note.ts
|
|
3
4
|
/**
|
|
4
5
|
* What a list that shows only its first `shown` of `total` records says about the rest, as "The first 10 of 37.";
|
|
@@ -6,7 +7,7 @@ import { formatTag } from "../../locale/active-locale.js";
|
|
|
6
7
|
*/
|
|
7
8
|
const cutOffNote = (shown, total, estimated) => {
|
|
8
9
|
if (shown >= total) return void 0;
|
|
9
|
-
return
|
|
10
|
+
return estimated ? text("firstOfAbout", shown, total.toLocaleString(formatTag())) : text("firstOf", shown, total.toLocaleString(formatTag()));
|
|
10
11
|
};
|
|
11
12
|
//#endregion
|
|
12
13
|
export { cutOffNote };
|
|
@@ -19,6 +19,7 @@ import { CustomDisplay } from "../displays/custom-display.js";
|
|
|
19
19
|
import { MapDisplay } from "../displays/map-display.js";
|
|
20
20
|
import { DisplaySwitch } from "../displays/display-controls.js";
|
|
21
21
|
import { embeddedColumns } from "./embedded-columns.js";
|
|
22
|
+
import { text } from "./messages.js";
|
|
22
23
|
import { cutOffNote } from "./cut-off-note.js";
|
|
23
24
|
import { embeddedRowTargets } from "./embedded-row-targets.js";
|
|
24
25
|
import { useEmbeddedRows } from "./use-embedded-rows.js";
|
|
@@ -94,23 +95,27 @@ const EmbeddedList = ({ model, view, filter, sort, columns, limit, display, empt
|
|
|
94
95
|
}),
|
|
95
96
|
onClick: viewAll?.onOpen,
|
|
96
97
|
className: "flex items-center gap-1 self-end font-medium text-primary-text hover:underline",
|
|
97
|
-
children: [viewAll ? viewAll.label : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
children: [viewAll ? viewAll.label : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
99
|
+
text("viewAll"),
|
|
100
|
+
" ",
|
|
101
|
+
/* @__PURE__ */ jsx("span", {
|
|
102
|
+
className: "sr-only",
|
|
103
|
+
children: plural.toLowerCase()
|
|
104
|
+
})
|
|
105
|
+
] }), /* @__PURE__ */ jsx(ArrowRight, {
|
|
101
106
|
className: "size-3.5",
|
|
102
107
|
"aria-hidden": true
|
|
103
108
|
})]
|
|
104
109
|
});
|
|
105
110
|
if (rows.isPending) return /* @__PURE__ */ jsx(Loading, {});
|
|
106
111
|
if (rows.error) return /* @__PURE__ */ jsx(ErrorBanner, {
|
|
107
|
-
title: "
|
|
112
|
+
title: text("couldNotLoad"),
|
|
108
113
|
error: rows.error
|
|
109
114
|
});
|
|
110
115
|
if (!source || source.total === 0) {
|
|
111
116
|
const nothing = /* @__PURE__ */ jsx("p", {
|
|
112
117
|
className: "text-muted-foreground",
|
|
113
|
-
children: empty ?? "
|
|
118
|
+
children: empty ?? text("nothingYet")
|
|
114
119
|
});
|
|
115
120
|
return viewAll && link ? /* @__PURE__ */ jsxs("div", {
|
|
116
121
|
className: "flex flex-col gap-3",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { humanize } from "../../live/naming.js";
|
|
2
2
|
import { recordKey } from "../../live/model-helpers.js";
|
|
3
3
|
import { rowAllows } from "../permissions.js";
|
|
4
|
+
import { text } from "./messages.js";
|
|
4
5
|
import { relationTarget } from "../related-opens.js";
|
|
5
6
|
//#region src/app/embedded-list/embedded-row-targets.ts
|
|
6
7
|
const singularOf = (model, view) => (view?.names?.singular ?? humanize(model.name)).toLowerCase();
|
|
@@ -29,19 +30,19 @@ const embeddedRowTargets = ({ model, view, opens, resources, views, canUpdate })
|
|
|
29
30
|
const singular = singularOf(model, view);
|
|
30
31
|
const actions = (linkTo) => {
|
|
31
32
|
const openOwn = {
|
|
32
|
-
label:
|
|
33
|
+
label: text("open", singular),
|
|
33
34
|
link: (row) => linkTo(own(row))
|
|
34
35
|
};
|
|
35
36
|
if (!far) return [openOwn];
|
|
36
37
|
const editable = (row) => canUpdate && rowAllows(row, "update");
|
|
37
38
|
return [
|
|
38
39
|
{
|
|
39
|
-
label:
|
|
40
|
+
label: text("open", singularOf(far, views[far.name])),
|
|
40
41
|
available: (row) => other(row) !== void 0,
|
|
41
42
|
link: (row) => linkTo(other(row))
|
|
42
43
|
},
|
|
43
44
|
{
|
|
44
|
-
label:
|
|
45
|
+
label: text("edit", singular),
|
|
45
46
|
available: editable,
|
|
46
47
|
link: (row) => linkTo({
|
|
47
48
|
kind: "record",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineMessages } from "../../locale/messages.js";
|
|
2
|
+
//#region src/app/embedded-list/messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
firstOf: (shown, total) => `The first ${shown} of ${total}.`,
|
|
6
|
+
firstOfAbout: (shown, total) => `The first ${shown} of about ${total}.`,
|
|
7
|
+
open: (singular) => `Open ${singular}`,
|
|
8
|
+
edit: (singular) => `Edit ${singular}`,
|
|
9
|
+
viewAll: "View all",
|
|
10
|
+
couldNotLoad: "Could not load",
|
|
11
|
+
nothingYet: "Nothing here yet."
|
|
12
|
+
},
|
|
13
|
+
nl: {
|
|
14
|
+
firstOf: (shown, total) => `De eerste ${shown} van ${total}.`,
|
|
15
|
+
firstOfAbout: (shown, total) => `De eerste ${shown} van ongeveer ${total}.`,
|
|
16
|
+
open: (singular) => `${singular} openen`,
|
|
17
|
+
edit: (singular) => `${singular} bewerken`,
|
|
18
|
+
viewAll: "Alles bekijken",
|
|
19
|
+
couldNotLoad: "Kon niet laden",
|
|
20
|
+
nothingYet: "Hier staat nog niets."
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
//#endregion
|
|
24
|
+
export { text };
|
package/dist/app/error-banner.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { text } from "./messages.js";
|
|
1
2
|
import { TriangleAlert } from "lucide-react";
|
|
2
3
|
import { isApiError } from "@protobase/client";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
//#region src/app/error-banner.tsx
|
|
5
6
|
const describe = (error) => {
|
|
6
|
-
if (!isApiError(error)) return [{ message: error instanceof Error ? error.message : "
|
|
7
|
+
if (!isApiError(error)) return [{ message: error instanceof Error ? error.message : text("somethingWentWrong") }];
|
|
7
8
|
const detailed = error.errors.map((entry) => ({
|
|
8
9
|
message: entry.message,
|
|
9
10
|
hint: entry.hint
|