@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
package/dist/app/field-errors.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
import { formatInt } from "../format/number.js";
|
|
2
|
+
import { text } from "./field-messages.js";
|
|
2
3
|
import { isApiError } from "@protobase/client";
|
|
3
4
|
//#region src/app/field-errors.ts
|
|
4
5
|
const numberIn = (params, key) => typeof params?.[key] === "number" ? params[key] : void 0;
|
|
5
|
-
const characters = (count) => `${formatInt(count)} character${count === 1 ? "" : "s"}`;
|
|
6
6
|
const textMessage = (label, params) => {
|
|
7
7
|
const min = numberIn(params, "min");
|
|
8
8
|
const max = numberIn(params, "max");
|
|
9
|
-
if (min !== void 0) return
|
|
10
|
-
if (max !== void 0) return
|
|
9
|
+
if (min !== void 0) return text("atLeastCharacters", label, formatInt(min), min);
|
|
10
|
+
if (max !== void 0) return text("atMostCharacters", label, formatInt(max), max);
|
|
11
11
|
};
|
|
12
12
|
const wholeNumber = (params) => {
|
|
13
13
|
const min = numberIn(params, "min");
|
|
14
14
|
const max = numberIn(params, "max");
|
|
15
|
-
if (min !== void 0) return
|
|
16
|
-
if (max !== void 0) return
|
|
17
|
-
return "
|
|
15
|
+
if (min !== void 0) return text("wholeNumberAtLeast", formatInt(min));
|
|
16
|
+
if (max !== void 0) return text("wholeNumberAtMost", formatInt(max));
|
|
17
|
+
return text("wholeNumber");
|
|
18
18
|
};
|
|
19
19
|
const decimalMessage = (params) => {
|
|
20
20
|
const precision = numberIn(params, "precision");
|
|
21
21
|
const scale = numberIn(params, "scale");
|
|
22
|
-
if (precision === void 0 || scale === void 0) return "
|
|
22
|
+
if (precision === void 0 || scale === void 0) return text("number");
|
|
23
23
|
const below = formatInt(10 ** (precision - scale));
|
|
24
|
-
return scale === 0 ?
|
|
24
|
+
return scale === 0 ? text("wholeNumberBelow", below) : text("decimalsBelow", scale, below);
|
|
25
25
|
};
|
|
26
26
|
/** What to say about a value of each field type the server turned down; without an entry, the server's message shows. */
|
|
27
27
|
const invalidValue = {
|
|
28
28
|
text: textMessage,
|
|
29
29
|
integer: (_, params) => wholeNumber(params),
|
|
30
|
-
bigint: () => "
|
|
30
|
+
bigint: () => text("wholeNumber"),
|
|
31
31
|
decimal: (_, params) => decimalMessage(params),
|
|
32
|
-
date: () => "
|
|
33
|
-
timestamp: () => "
|
|
34
|
-
country: () => "
|
|
35
|
-
currency: () => "
|
|
36
|
-
uuid: () => "
|
|
37
|
-
enum: () => "
|
|
38
|
-
relation: () => "
|
|
39
|
-
json: () => "
|
|
32
|
+
date: () => text("date"),
|
|
33
|
+
timestamp: () => text("timestamp"),
|
|
34
|
+
country: () => text("country"),
|
|
35
|
+
currency: () => text("currency"),
|
|
36
|
+
uuid: () => text("uuid"),
|
|
37
|
+
enum: () => text("enum"),
|
|
38
|
+
relation: () => text("relation"),
|
|
39
|
+
json: () => text("json")
|
|
40
40
|
};
|
|
41
41
|
/**
|
|
42
42
|
* The message a form shows under a field the server turned down, in the words of the field's label: "Country is
|
|
@@ -44,7 +44,7 @@ const invalidValue = {
|
|
|
44
44
|
* server words them.
|
|
45
45
|
*/
|
|
46
46
|
const fieldMessage = (field, label, error) => {
|
|
47
|
-
if (error.code === "required") return
|
|
47
|
+
if (error.code === "required") return text("required", label);
|
|
48
48
|
if (error.code !== "invalid-value" || !field) return error.message;
|
|
49
49
|
return invalidValue[field.type]?.(label, error.params) ?? error.message;
|
|
50
50
|
};
|
|
@@ -64,7 +64,7 @@ const saveFailureDescription = (error) => {
|
|
|
64
64
|
const fields = new Set(error.errors.flatMap((entry) => entry.field && entry.field !== "_record" ? [entry.field] : []));
|
|
65
65
|
const record = error.errors.flatMap((entry) => entry.field === "_record" ? [entry.message] : []);
|
|
66
66
|
if (fields.size === 0 && record.length === 0) return error.message;
|
|
67
|
-
return [...fields.size === 0 ? [] : [
|
|
67
|
+
return [...fields.size === 0 ? [] : [fields.size === 1 ? text("checkField") : text("checkFields", fields.size)], ...record].join(" ");
|
|
68
68
|
};
|
|
69
69
|
//#endregion
|
|
70
70
|
export { fieldErrors, fieldMessage, saveFailureDescription };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { defineMessages } from "../locale/messages.js";
|
|
2
|
+
//#region src/app/field-messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
atLeastCharacters: (label, count, n) => `${label} needs at least ${count} character${n === 1 ? "" : "s"}`,
|
|
6
|
+
atMostCharacters: (label, count, n) => `${label} can have at most ${count} character${n === 1 ? "" : "s"}`,
|
|
7
|
+
wholeNumberAtLeast: (min) => `Enter a whole number of at least ${min}`,
|
|
8
|
+
wholeNumberAtMost: (max) => `Enter a whole number of at most ${max}`,
|
|
9
|
+
wholeNumber: "Enter a whole number",
|
|
10
|
+
number: "Enter a number",
|
|
11
|
+
wholeNumberBelow: (below) => `Enter a whole number below ${below}`,
|
|
12
|
+
decimalsBelow: (scale, below) => `Enter a number with at most ${scale} decimal${scale === 1 ? "" : "s"}, below ${below}`,
|
|
13
|
+
date: "Enter a date",
|
|
14
|
+
timestamp: "Enter a date and time",
|
|
15
|
+
country: "Enter a two-letter country code, such as NL",
|
|
16
|
+
currency: "Enter a three-letter currency code, such as EUR",
|
|
17
|
+
uuid: "Enter a valid ID",
|
|
18
|
+
enum: "Choose one of the options",
|
|
19
|
+
relation: "Choose a record",
|
|
20
|
+
json: "Enter valid JSON",
|
|
21
|
+
required: (label) => `${label} is required`,
|
|
22
|
+
checkField: "Check the marked field.",
|
|
23
|
+
checkFields: (count) => `Check ${count} fields.`
|
|
24
|
+
},
|
|
25
|
+
nl: {
|
|
26
|
+
atLeastCharacters: (label, count, n) => `${label} moet minstens ${count} ${n === 1 ? "teken" : "tekens"} hebben`,
|
|
27
|
+
atMostCharacters: (label, count, n) => `${label} mag hoogstens ${count} ${n === 1 ? "teken" : "tekens"} hebben`,
|
|
28
|
+
wholeNumberAtLeast: (min) => `Vul een geheel getal in van minstens ${min}`,
|
|
29
|
+
wholeNumberAtMost: (max) => `Vul een geheel getal in van hoogstens ${max}`,
|
|
30
|
+
wholeNumber: "Vul een geheel getal in",
|
|
31
|
+
number: "Vul een getal in",
|
|
32
|
+
wholeNumberBelow: (below) => `Vul een geheel getal in onder ${below}`,
|
|
33
|
+
decimalsBelow: (scale, below) => `Vul een getal in met hoogstens ${scale} ${scale === 1 ? "decimaal" : "decimalen"}, onder ${below}`,
|
|
34
|
+
date: "Vul een datum in",
|
|
35
|
+
timestamp: "Vul een datum en tijd in",
|
|
36
|
+
country: "Vul een landcode van twee letters in, zoals NL",
|
|
37
|
+
currency: "Vul een valutacode van drie letters in, zoals EUR",
|
|
38
|
+
uuid: "Vul een geldig ID in",
|
|
39
|
+
enum: "Kies een van de opties",
|
|
40
|
+
relation: "Kies een record",
|
|
41
|
+
json: "Vul geldige JSON in",
|
|
42
|
+
required: (label) => `${label} is verplicht`,
|
|
43
|
+
checkField: "Controleer het gemarkeerde veld.",
|
|
44
|
+
checkFields: (count) => `Controleer ${count} velden.`
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
//#endregion
|
|
48
|
+
export { text };
|
|
@@ -9,9 +9,10 @@ import { ErrorBanner } from "../error-banner.js";
|
|
|
9
9
|
import { useProjectUi } from "../pages/project-ui.js";
|
|
10
10
|
import { DataGrid } from "../../data-grid/data-grid.js";
|
|
11
11
|
import { embeddedColumns } from "../embedded-list/embedded-columns.js";
|
|
12
|
+
import { text } from "./messages.js";
|
|
12
13
|
import { useHandOrder } from "./use-hand-order.js";
|
|
13
14
|
import { useMemo } from "react";
|
|
14
|
-
import { jsx
|
|
15
|
+
import { jsx } from "react/jsx-runtime";
|
|
15
16
|
//#region src/app/hand-order/hand-sorted-list.tsx
|
|
16
17
|
/**
|
|
17
18
|
* The records in their order by hand, in the list's table with a drag handle in each row's checkbox place: dragging a
|
|
@@ -59,22 +60,16 @@ const HandSortedList = ({ model, view, field, filter, columns }) => {
|
|
|
59
60
|
children: Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }, index))
|
|
60
61
|
});
|
|
61
62
|
if (order.error) return /* @__PURE__ */ jsx(ErrorBanner, {
|
|
62
|
-
title: "
|
|
63
|
+
title: text("couldNotLoad"),
|
|
63
64
|
error: order.error
|
|
64
65
|
});
|
|
65
|
-
if (order.more) return /* @__PURE__ */
|
|
66
|
+
if (order.more) return /* @__PURE__ */ jsx("p", {
|
|
66
67
|
className: "text-muted-foreground",
|
|
67
|
-
children:
|
|
68
|
-
"There are more than ",
|
|
69
|
-
500 .toLocaleString(formatTag()),
|
|
70
|
-
" ",
|
|
71
|
-
plural,
|
|
72
|
-
", too many to sort by hand."
|
|
73
|
-
]
|
|
68
|
+
children: text("tooMany", 500 .toLocaleString(formatTag()), plural)
|
|
74
69
|
});
|
|
75
70
|
if (!source || source.total === 0) return /* @__PURE__ */ jsx("p", {
|
|
76
71
|
className: "text-muted-foreground",
|
|
77
|
-
children: "
|
|
72
|
+
children: text("nothingYet")
|
|
78
73
|
});
|
|
79
74
|
const openRecord = (row) => go({
|
|
80
75
|
kind: "record",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineMessages } from "../../locale/messages.js";
|
|
2
|
+
//#region src/app/hand-order/messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
done: "Done",
|
|
6
|
+
sort: "Sort",
|
|
7
|
+
couldNotLoad: "Could not load",
|
|
8
|
+
tooMany: (limit, plural) => `There are more than ${limit} ${plural}, too many to sort by hand.`,
|
|
9
|
+
nothingYet: "Nothing here yet.",
|
|
10
|
+
couldNotChange: "Could not change the order"
|
|
11
|
+
},
|
|
12
|
+
nl: {
|
|
13
|
+
done: "Klaar",
|
|
14
|
+
sort: "Sorteren",
|
|
15
|
+
couldNotLoad: "Kon niet laden",
|
|
16
|
+
tooMany: (limit, plural) => `Er zijn meer dan ${limit} ${plural}, te veel om met de hand te sorteren.`,
|
|
17
|
+
nothingYet: "Hier staat nog niets.",
|
|
18
|
+
couldNotChange: "Kon de volgorde niet wijzigen"
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
//#endregion
|
|
22
|
+
export { text };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Button } from "../../primitives/button.js";
|
|
2
|
+
import { text } from "./messages.js";
|
|
2
3
|
import { ArrowUpDown, Check } from "lucide-react";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
//#region src/app/hand-order/sort-button.tsx
|
|
5
6
|
/** The button that turns sorting by hand on, as **Sort**, and off again, as **Done**; with `iconOnly` its icon alone. */
|
|
6
7
|
const SortButton = ({ sorting, onChange, size, iconOnly = false }) => {
|
|
7
|
-
const label = sorting ? "
|
|
8
|
+
const label = sorting ? text("done") : text("sort");
|
|
8
9
|
return /* @__PURE__ */ jsxs(Button, {
|
|
9
10
|
size,
|
|
10
11
|
variant: sorting ? "primary" : "secondary",
|
|
@@ -6,6 +6,7 @@ import { refusalReason } from "../displays/board-columns.js";
|
|
|
6
6
|
import { useAdminMeta } from "../meta-gate.js";
|
|
7
7
|
import { useRefreshResource } from "../pages/use-layout-data.js";
|
|
8
8
|
import { listLabels, withLabels } from "../../live/relation-labels.js";
|
|
9
|
+
import { text } from "./messages.js";
|
|
9
10
|
import { useRef, useState } from "react";
|
|
10
11
|
import { encodeKey } from "@protobase/schema/internal";
|
|
11
12
|
import { useQuery } from "@tanstack/react-query";
|
|
@@ -73,7 +74,7 @@ const useHandOrder = ({ model, field, filter, fields }) => {
|
|
|
73
74
|
setMoved(void 0);
|
|
74
75
|
toast.show({
|
|
75
76
|
state: "error",
|
|
76
|
-
title: "
|
|
77
|
+
title: text("couldNotChange"),
|
|
77
78
|
description: refusalReason(error)
|
|
78
79
|
});
|
|
79
80
|
};
|
|
@@ -3,6 +3,7 @@ import { formatInt } from "../format/number.js";
|
|
|
3
3
|
import { SidebarCard } from "../record-view/record-sidebar.js";
|
|
4
4
|
import { useAdminMeta } from "./meta-gate.js";
|
|
5
5
|
import { AppLink } from "./use-app-address.js";
|
|
6
|
+
import { text } from "./record-messages.js";
|
|
6
7
|
import { incomingAddress, incomingRelations } from "./incoming-relations.js";
|
|
7
8
|
import { useIncomingCounts } from "./use-incoming-counts.js";
|
|
8
9
|
import { ChevronRight } from "lucide-react";
|
|
@@ -14,7 +15,7 @@ const IncomingCard = ({ model, recordKey, layout, lines }) => {
|
|
|
14
15
|
const counts = useIncomingCounts(incomingRelations(model, resources, views, layout, lines), resources, recordKey);
|
|
15
16
|
if (counts.length === 0) return null;
|
|
16
17
|
return /* @__PURE__ */ jsx(SidebarCard, {
|
|
17
|
-
title: "
|
|
18
|
+
title: text("relatedRecords"),
|
|
18
19
|
children: /* @__PURE__ */ jsx("ul", {
|
|
19
20
|
className: "-mx-2",
|
|
20
21
|
children: counts.map(({ relation, count }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(AppLink, {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineMessages } from "../../locale/messages.js";
|
|
2
|
+
//#region src/app/list-edit/messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
changedBySomeoneElse: "Changed by someone else",
|
|
6
|
+
couldNotSave: "Could not save"
|
|
7
|
+
},
|
|
8
|
+
nl: {
|
|
9
|
+
changedBySomeoneElse: "Gewijzigd door iemand anders",
|
|
10
|
+
couldNotSave: "Kon niet bewaren"
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
//#endregion
|
|
14
|
+
export { text };
|
|
@@ -2,6 +2,7 @@ import { keys } from "../../data/query-keys.js";
|
|
|
2
2
|
import { useClient } from "../../data/api-provider.js";
|
|
3
3
|
import { useToast } from "../../toasts/toast-provider.js";
|
|
4
4
|
import { recordKey } from "../../live/model-helpers.js";
|
|
5
|
+
import { text } from "./messages.js";
|
|
5
6
|
import { useRef, useState } from "react";
|
|
6
7
|
import { isApiError } from "@protobase/client";
|
|
7
8
|
import { encodeKey } from "@protobase/schema/internal";
|
|
@@ -54,7 +55,7 @@ const useListEdits = (model) => {
|
|
|
54
55
|
const reason = error.errors.find((entry) => entry.field === name)?.message ?? error.message;
|
|
55
56
|
toast.show({
|
|
56
57
|
state: "error",
|
|
57
|
-
title: changed ? "
|
|
58
|
+
title: changed ? text("changedBySomeoneElse") : text("couldNotSave"),
|
|
58
59
|
description: reason
|
|
59
60
|
});
|
|
60
61
|
}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
+
import { text } from "./list-messages.js";
|
|
1
2
|
//#region src/app/list-empty-state.ts
|
|
2
3
|
/** Why a list has no rows, and the buttons that help: clear the search, clear the filters, or create the first record. */
|
|
3
4
|
const listEmptyState = ({ plural, search, filtered, scoped, canCreate }) => {
|
|
4
|
-
const noun = plural.toLowerCase();
|
|
5
5
|
if (search) return {
|
|
6
|
-
message:
|
|
6
|
+
message: text("noMatchSearch", plural, search),
|
|
7
7
|
clearSearch: true,
|
|
8
8
|
clearFilters: filtered,
|
|
9
9
|
create: false
|
|
10
10
|
};
|
|
11
11
|
if (filtered) return {
|
|
12
|
-
message:
|
|
12
|
+
message: text("noMatchFilters", plural),
|
|
13
13
|
clearSearch: false,
|
|
14
14
|
clearFilters: true,
|
|
15
15
|
create: false
|
|
16
16
|
};
|
|
17
17
|
if (scoped) return {
|
|
18
|
-
message:
|
|
18
|
+
message: text("noMatch", plural),
|
|
19
19
|
clearSearch: false,
|
|
20
20
|
clearFilters: false,
|
|
21
21
|
create: false
|
|
22
22
|
};
|
|
23
23
|
return {
|
|
24
|
-
message:
|
|
24
|
+
message: text("noneYet", plural),
|
|
25
25
|
clearSearch: false,
|
|
26
26
|
clearFilters: false,
|
|
27
27
|
create: canCreate
|
package/dist/app/list-empty.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Button } from "../primitives/button.js";
|
|
2
|
+
import { text } from "./list-messages.js";
|
|
2
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
4
|
//#region src/app/list-empty.tsx
|
|
4
5
|
/** A list without rows: why (see `listEmptyState`), with buttons to clear its search or filters, or its New button. */
|
|
@@ -9,11 +10,11 @@ const ListEmpty = ({ state, onClearSearch, onClearFilters, create }) => /* @__PU
|
|
|
9
10
|
children: [
|
|
10
11
|
state.clearSearch && /* @__PURE__ */ jsx(Button, {
|
|
11
12
|
onClick: onClearSearch,
|
|
12
|
-
children: "
|
|
13
|
+
children: text("clearSearch")
|
|
13
14
|
}),
|
|
14
15
|
state.clearFilters && /* @__PURE__ */ jsx(Button, {
|
|
15
16
|
onClick: onClearFilters,
|
|
16
|
-
children: "
|
|
17
|
+
children: text("clearFilters")
|
|
17
18
|
}),
|
|
18
19
|
state.create && create
|
|
19
20
|
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { defineMessages } from "../locale/messages.js";
|
|
2
|
+
//#region src/app/list-messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
noList: (name) => `There is no list called ${name}.`,
|
|
6
|
+
searchPlural: (plural) => `Search ${plural.toLowerCase()}`,
|
|
7
|
+
search: "Search",
|
|
8
|
+
sort: "Sort",
|
|
9
|
+
filters: "Filters",
|
|
10
|
+
filtersCount: (count) => `Filters (${count})`,
|
|
11
|
+
newRecord: (singular) => `New ${singular.toLowerCase()}`,
|
|
12
|
+
export: "Export",
|
|
13
|
+
openRecord: (singular) => `Open ${singular.toLowerCase()}`,
|
|
14
|
+
delete: "Delete",
|
|
15
|
+
exportCount: (count) => `Export ${count}`,
|
|
16
|
+
deleteCount: (count) => `Delete ${count}`,
|
|
17
|
+
dragToSort: "Drag a row by its handle to move it. Every move is saved as you drop it.",
|
|
18
|
+
unreadableFilter: "The filter in the address could not be read",
|
|
19
|
+
couldNotLoadRows: "Could not load rows",
|
|
20
|
+
created: (plural) => `${plural} created`,
|
|
21
|
+
noMatchSearch: (plural, search) => `No ${plural.toLowerCase()} match “${search}”.`,
|
|
22
|
+
noMatchFilters: (plural) => `No ${plural.toLowerCase()} match these filters.`,
|
|
23
|
+
noMatch: (plural) => `No ${plural.toLowerCase()} match.`,
|
|
24
|
+
noneYet: (plural) => `No ${plural.toLowerCase()} yet.`,
|
|
25
|
+
clearSearch: "Clear search",
|
|
26
|
+
clearFilters: "Clear filters"
|
|
27
|
+
},
|
|
28
|
+
nl: {
|
|
29
|
+
noList: (name) => `Er is geen lijst met de naam ${name}.`,
|
|
30
|
+
searchPlural: (plural) => `Zoeken in ${plural.toLowerCase()}`,
|
|
31
|
+
search: "Zoeken",
|
|
32
|
+
sort: "Sorteren",
|
|
33
|
+
filters: "Filters",
|
|
34
|
+
filtersCount: (count) => `Filters (${count})`,
|
|
35
|
+
newRecord: (singular) => `${singular} toevoegen`,
|
|
36
|
+
export: "Exporteren",
|
|
37
|
+
openRecord: (singular) => `${singular} openen`,
|
|
38
|
+
delete: "Verwijderen",
|
|
39
|
+
exportCount: (count) => `${count} exporteren`,
|
|
40
|
+
deleteCount: (count) => `${count} verwijderen`,
|
|
41
|
+
dragToSort: "Sleep een rij aan de greep om hem te verplaatsen. Elke verplaatsing wordt bewaard zodra je loslaat.",
|
|
42
|
+
unreadableFilter: "Het filter in het adres kon niet worden gelezen",
|
|
43
|
+
couldNotLoadRows: "Kon de rijen niet laden",
|
|
44
|
+
created: (plural) => `${plural} aangemaakt`,
|
|
45
|
+
noMatchSearch: (plural, search) => `Geen ${plural.toLowerCase()} gevonden voor “${search}”.`,
|
|
46
|
+
noMatchFilters: (plural) => `Geen ${plural.toLowerCase()} gevonden met deze filters.`,
|
|
47
|
+
noMatch: (plural) => `Geen ${plural.toLowerCase()} gevonden.`,
|
|
48
|
+
noneYet: (plural) => `Nog geen ${plural.toLowerCase()}.`,
|
|
49
|
+
clearSearch: "Zoekopdracht wissen",
|
|
50
|
+
clearFilters: "Filters wissen"
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
//#endregion
|
|
54
|
+
export { text };
|
package/dist/app/list-page.js
CHANGED
|
@@ -12,6 +12,7 @@ import { parseSort, sortToOrderBy } from "./list-order.js";
|
|
|
12
12
|
import { listedResource } from "./current-address.js";
|
|
13
13
|
import { Notice, useAdminMeta, usePermissions } from "./meta-gate.js";
|
|
14
14
|
import { AppLink, useAppAddress } from "./use-app-address.js";
|
|
15
|
+
import { text } from "./messages.js";
|
|
15
16
|
import { ErrorBanner } from "./error-banner.js";
|
|
16
17
|
import { useProjectUi } from "./pages/project-ui.js";
|
|
17
18
|
import { Sheet } from "../primitives/sheet.js";
|
|
@@ -46,6 +47,7 @@ import { createOffered } from "./create/create-offered.js";
|
|
|
46
47
|
import { ImportButton } from "./import/import-button.js";
|
|
47
48
|
import { exportOffered, exportsSelection } from "./export/export-options.js";
|
|
48
49
|
import { useExportFlow } from "./export/use-export-flow.js";
|
|
50
|
+
import { text as text$1 } from "./list-messages.js";
|
|
49
51
|
import { ListEmpty } from "./list-empty.js";
|
|
50
52
|
import { listEmptyState } from "./list-empty-state.js";
|
|
51
53
|
import { ListCards } from "./displays/list-cards.js";
|
|
@@ -81,22 +83,14 @@ const ListPage = ({ address }) => {
|
|
|
81
83
|
if (!model) return prompt ? /* @__PURE__ */ jsx(SignInPrompt, {
|
|
82
84
|
as: "page",
|
|
83
85
|
prompt: prompt.signIn
|
|
84
|
-
}) : /* @__PURE__ */
|
|
85
|
-
title: "
|
|
86
|
-
children:
|
|
87
|
-
"There is no resource called ",
|
|
88
|
-
address.resource,
|
|
89
|
-
"."
|
|
90
|
-
]
|
|
86
|
+
}) : /* @__PURE__ */ jsx(Notice, {
|
|
87
|
+
title: text("notFound"),
|
|
88
|
+
children: text("noResource", address.resource)
|
|
91
89
|
});
|
|
92
90
|
const view = viewOfList(views[address.resource], address.list);
|
|
93
|
-
if (address.list !== void 0 && !view) return /* @__PURE__ */
|
|
94
|
-
title: "
|
|
95
|
-
children:
|
|
96
|
-
"There is no list called ",
|
|
97
|
-
address.list,
|
|
98
|
-
"."
|
|
99
|
-
]
|
|
91
|
+
if (address.list !== void 0 && !view) return /* @__PURE__ */ jsx(Notice, {
|
|
92
|
+
title: text("notFound"),
|
|
93
|
+
children: text$1("noList", address.list)
|
|
100
94
|
});
|
|
101
95
|
if (view?.searchesOf) return /* @__PURE__ */ jsx(SavedSearches, {
|
|
102
96
|
model,
|
|
@@ -225,7 +219,7 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
225
219
|
const searchBox = model.search?.length ? /* @__PURE__ */ jsx(ListSearch, {
|
|
226
220
|
value: search?.kind === "search" ? search.text : "",
|
|
227
221
|
onCommit: commitSearch,
|
|
228
|
-
placeholder:
|
|
222
|
+
placeholder: text$1("searchPlural", plural),
|
|
229
223
|
className: desktop ? void 0 : "min-h-11"
|
|
230
224
|
}) : null;
|
|
231
225
|
const panel = /* @__PURE__ */ jsx(FilterPanel, {
|
|
@@ -248,7 +242,7 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
248
242
|
onClear: () => setFilter(emptyFilterState)
|
|
249
243
|
});
|
|
250
244
|
const phoneSort = sorted && /* @__PURE__ */ jsx(FilterSection, {
|
|
251
|
-
title: "
|
|
245
|
+
title: text$1("sort"),
|
|
252
246
|
children: /* @__PURE__ */ jsx(SortMenu, {
|
|
253
247
|
model,
|
|
254
248
|
view,
|
|
@@ -267,11 +261,7 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
267
261
|
className: "min-h-11",
|
|
268
262
|
onClick: () => setSheetOpen(true),
|
|
269
263
|
"aria-haspopup": "dialog",
|
|
270
|
-
children: [
|
|
271
|
-
/* @__PURE__ */ jsx(SlidersHorizontal, { className: "size-3.5" }),
|
|
272
|
-
"Filters",
|
|
273
|
-
chips.length > 0 && ` (${chips.length})`
|
|
274
|
-
]
|
|
264
|
+
children: [/* @__PURE__ */ jsx(SlidersHorizontal, { className: "size-3.5" }), chips.length > 0 ? text$1("filtersCount", chips.length) : text$1("filters")]
|
|
275
265
|
}), searchBox ?? /* @__PURE__ */ jsx("div", {
|
|
276
266
|
className: "min-w-0 flex-1",
|
|
277
267
|
children: chipsRow
|
|
@@ -279,7 +269,7 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
279
269
|
}),
|
|
280
270
|
searchBox && chipsRow,
|
|
281
271
|
/* @__PURE__ */ jsx(Sheet, {
|
|
282
|
-
title: "
|
|
272
|
+
title: text$1("filters"),
|
|
283
273
|
open: sheetOpen,
|
|
284
274
|
onClose: () => setSheetOpen(false),
|
|
285
275
|
children: /* @__PURE__ */ jsx(FilterPanel, {
|
|
@@ -299,11 +289,7 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
299
289
|
resource
|
|
300
290
|
},
|
|
301
291
|
className: buttonClass({ variant: "primary" }),
|
|
302
|
-
children: [
|
|
303
|
-
/* @__PURE__ */ jsx(Plus, { className: "size-3.5" }),
|
|
304
|
-
"New ",
|
|
305
|
-
singular.toLowerCase()
|
|
306
|
-
]
|
|
292
|
+
children: [/* @__PURE__ */ jsx(Plus, { className: "size-3.5" }), text$1("newRecord", singular)]
|
|
307
293
|
});
|
|
308
294
|
const saveSearch = view?.saveSearch && !scope && parsed.ok && filterText && /* @__PURE__ */ jsx(SaveSearchButton, {
|
|
309
295
|
model,
|
|
@@ -325,10 +311,10 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
325
311
|
const exportButton = exportOffered(view) && /* @__PURE__ */ jsxs(Button, {
|
|
326
312
|
onClick: () => exportFlow.open(),
|
|
327
313
|
...!desktop && {
|
|
328
|
-
"aria-label": "
|
|
329
|
-
title: "
|
|
314
|
+
"aria-label": text$1("export"),
|
|
315
|
+
title: text$1("export")
|
|
330
316
|
},
|
|
331
|
-
children: [/* @__PURE__ */ jsx(Download, { className: "size-3.5" }), desktop && "
|
|
317
|
+
children: [/* @__PURE__ */ jsx(Download, { className: "size-3.5" }), desktop && text$1("export")]
|
|
332
318
|
});
|
|
333
319
|
const sortButton = handOrder && /* @__PURE__ */ jsx(SortButton, {
|
|
334
320
|
sorting: handSorting,
|
|
@@ -371,19 +357,19 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
371
357
|
initialScrollIndex: scroll.initialIndex,
|
|
372
358
|
onScrollIndex: scroll.remember,
|
|
373
359
|
rowActions: [{
|
|
374
|
-
label:
|
|
360
|
+
label: text$1("openRecord", singular),
|
|
375
361
|
onSelect: openRecord
|
|
376
362
|
}, ...can(permissions, "delete") ? [{
|
|
377
|
-
label: "
|
|
363
|
+
label: text$1("delete"),
|
|
378
364
|
destructive: true,
|
|
379
365
|
available: (row) => rowAllows(row, "delete"),
|
|
380
366
|
onSelect: (row) => void remove.start(target(row))
|
|
381
367
|
}] : []],
|
|
382
368
|
bulkActions: [...exportOffered(view) && exportsSelection(model) ? [{
|
|
383
|
-
label: (count) =>
|
|
369
|
+
label: (count) => text$1("exportCount", count),
|
|
384
370
|
onRun: (rows) => exportFlow.open(rows)
|
|
385
371
|
}] : [], ...can(permissions, "delete") ? [{
|
|
386
|
-
label: (count) =>
|
|
372
|
+
label: (count) => text$1("deleteCount", count),
|
|
387
373
|
destructive: true,
|
|
388
374
|
onRun: (rows) => remove.startBulk(rows.map(target))
|
|
389
375
|
}] : []],
|
|
@@ -465,7 +451,7 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
465
451
|
className: "flex flex-col gap-3",
|
|
466
452
|
children: [/* @__PURE__ */ jsx("p", {
|
|
467
453
|
className: "text-muted-foreground",
|
|
468
|
-
children: "
|
|
454
|
+
children: text$1("dragToSort")
|
|
469
455
|
}), /* @__PURE__ */ jsx(HandSortedList, {
|
|
470
456
|
model,
|
|
471
457
|
view,
|
|
@@ -499,11 +485,11 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
499
485
|
] })
|
|
500
486
|
}),
|
|
501
487
|
!parsed.ok && /* @__PURE__ */ jsx(ErrorBanner, {
|
|
502
|
-
title: "
|
|
488
|
+
title: text$1("unreadableFilter"),
|
|
503
489
|
error: new Error(parsed.errors.map((error) => error.message).join("; "))
|
|
504
490
|
}),
|
|
505
491
|
rows.error && !display && !handSorting && /* @__PURE__ */ jsx(ErrorBanner, {
|
|
506
|
-
title: "
|
|
492
|
+
title: text$1("couldNotLoadRows"),
|
|
507
493
|
error: rows.error
|
|
508
494
|
}),
|
|
509
495
|
view?.chart && !handSorting && (isGroupChart(view.chart) ? /* @__PURE__ */ jsx(ListGroupChart, {
|
|
@@ -515,7 +501,7 @@ const LoadedList = ({ model, address, scope, heading }) => {
|
|
|
515
501
|
}) : /* @__PURE__ */ jsx(ListChart, {
|
|
516
502
|
resource,
|
|
517
503
|
chart: view.chart,
|
|
518
|
-
title:
|
|
504
|
+
title: text$1("created", plural),
|
|
519
505
|
noun: plural.toLowerCase(),
|
|
520
506
|
filter: loadFilter
|
|
521
507
|
})),
|
package/dist/app/list-search.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { Input } from "../primitives/input.js";
|
|
3
|
+
import { text } from "./list-messages.js";
|
|
3
4
|
import { useEffect, useState } from "react";
|
|
4
5
|
import { Search } from "lucide-react";
|
|
5
6
|
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
//#region src/app/list-search.tsx
|
|
7
8
|
/** A search box that commits after a short pause or on Enter; `className` goes on its frame. */
|
|
8
9
|
const ListSearch = ({ value, onCommit, placeholder, className }) => {
|
|
9
|
-
const [text, setText] = useState(value);
|
|
10
|
+
const [text$1, setText] = useState(value);
|
|
10
11
|
useEffect(() => setText(value), [value]);
|
|
11
12
|
useEffect(() => {
|
|
12
|
-
if (text === value) return;
|
|
13
|
-
const timer = setTimeout(() => onCommit(text), 350);
|
|
13
|
+
if (text$1 === value) return;
|
|
14
|
+
const timer = setTimeout(() => onCommit(text$1), 350);
|
|
14
15
|
return () => clearTimeout(timer);
|
|
15
16
|
}, [
|
|
16
|
-
text,
|
|
17
|
+
text$1,
|
|
17
18
|
value,
|
|
18
19
|
onCommit
|
|
19
20
|
]);
|
|
@@ -21,10 +22,10 @@ const ListSearch = ({ value, onCommit, placeholder, className }) => {
|
|
|
21
22
|
wrapperClassName: cn("min-w-0 flex-1 sm:w-72 sm:flex-initial", className),
|
|
22
23
|
leading: /* @__PURE__ */ jsx(Search, { className: "size-4" }),
|
|
23
24
|
placeholder,
|
|
24
|
-
"aria-label": "
|
|
25
|
-
value: text,
|
|
25
|
+
"aria-label": text("search"),
|
|
26
|
+
value: text$1,
|
|
26
27
|
onChange: (event) => setText(event.target.value),
|
|
27
|
-
onKeyDown: (event) => event.key === "Enter" && onCommit(text)
|
|
28
|
+
onKeyDown: (event) => event.key === "Enter" && onCommit(text$1)
|
|
28
29
|
});
|
|
29
30
|
};
|
|
30
31
|
//#endregion
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineMessages } from "../locale/messages.js";
|
|
2
|
+
//#region src/app/messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
notFound: "Not found",
|
|
6
|
+
noResource: (name) => `There is no resource called ${name}.`,
|
|
7
|
+
somethingWentWrong: "Something went wrong"
|
|
8
|
+
},
|
|
9
|
+
nl: {
|
|
10
|
+
notFound: "Niet gevonden",
|
|
11
|
+
noResource: (name) => `Er is geen onderdeel met de naam ${name}.`,
|
|
12
|
+
somethingWentWrong: "Er ging iets mis"
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
export { text };
|
|
@@ -5,6 +5,7 @@ import { AppLink, useAppAddress } from "../use-app-address.js";
|
|
|
5
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
|
+
import { text } from "../record-messages.js";
|
|
8
9
|
import { actionIcon } from "../action-icon.js";
|
|
9
10
|
import { useState } from "react";
|
|
10
11
|
import { Pencil, Trash2 } from "lucide-react";
|
|
@@ -43,7 +44,7 @@ const RecordActions = ({ model, stored, recordKey, edit, onDelete }) => {
|
|
|
43
44
|
const deletes = runner.actions.some((action) => action.run?.kind === "delete");
|
|
44
45
|
const actions = [
|
|
45
46
|
...edit ? [{
|
|
46
|
-
name: "
|
|
47
|
+
name: text("edit"),
|
|
47
48
|
icon: Pencil,
|
|
48
49
|
render: ({ className, children, role }) => /* @__PURE__ */ jsx(AppLink, {
|
|
49
50
|
to: edit,
|
|
@@ -54,7 +55,7 @@ const RecordActions = ({ model, stored, recordKey, edit, onDelete }) => {
|
|
|
54
55
|
}] : [],
|
|
55
56
|
...named,
|
|
56
57
|
...onDelete && !deletes ? [{
|
|
57
|
-
name: "
|
|
58
|
+
name: text("delete"),
|
|
58
59
|
icon: Trash2,
|
|
59
60
|
tone: "danger",
|
|
60
61
|
onRun: onDelete
|
|
@@ -68,7 +69,7 @@ const RecordActions = ({ model, stored, recordKey, edit, onDelete }) => {
|
|
|
68
69
|
}),
|
|
69
70
|
slot && createPortal(/* @__PURE__ */ jsx(ActionMenu, {
|
|
70
71
|
actions,
|
|
71
|
-
label: "
|
|
72
|
+
label: text("recordActions"),
|
|
72
73
|
className: "inline-flex size-11 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted md:hidden"
|
|
73
74
|
}), slot),
|
|
74
75
|
asking && /* @__PURE__ */ jsx(ActionPrompt, {
|