@protobase/ui 0.19.2 → 0.19.4

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.
Files changed (35) hide show
  1. package/dist/app/displays/map-display.js +3 -1
  2. package/dist/app/gallery/detail-fields.js +12 -0
  3. package/dist/app/gallery/gallery-layout.js +3 -1
  4. package/dist/app/gallery/gallery-section.js +7 -4
  5. package/dist/app/gallery/lightbox.js +38 -44
  6. package/dist/app/gallery/photo-details.js +21 -28
  7. package/dist/app/map/map-layer-choice.js +11 -0
  8. package/dist/app/map/map-layer-switch.js +55 -0
  9. package/dist/app/map/map-section.js +3 -1
  10. package/dist/app/map/position-areas.js +4 -2
  11. package/dist/app/map/record-map.js +16 -11
  12. package/dist/app/map/records-map.js +7 -2
  13. package/dist/app/pages/record-actions.js +32 -6
  14. package/dist/app/record-mode/record-mode.js +12 -7
  15. package/dist/app/record-page.js +27 -38
  16. package/dist/data/meta-data.d.ts +3 -2
  17. package/dist/format/date.js +2 -15
  18. package/dist/package.js +1 -1
  19. package/dist/record-view/action-rail.js +22 -11
  20. package/dist/record-view/record-header.js +5 -5
  21. package/dist/shell/assets/app-BYFiTXK6.js +66 -0
  22. package/dist/shell/assets/{index-Crd6_WsY.js → index-CEvfMgFJ.js} +1 -1
  23. package/dist/shell/assets/index-CiGP6kle.css +2 -0
  24. package/dist/shell/assets/maplibre-D6zfLfTV.js +814 -0
  25. package/dist/shell/assets/record-map-B6_rbCcG.js +1 -0
  26. package/dist/shell/assets/records-map-bQbuIc13.js +1 -0
  27. package/dist/shell/assets/{shared-protobase-ui-DvvdY2OO.js → shared-protobase-ui-8pJgwjfc.js} +1 -1
  28. package/dist/shell/index.html +4 -4
  29. package/dist/shell/protobase.shell.json +1 -1
  30. package/package.json +4 -4
  31. package/dist/shell/assets/app-Rbouc_gR.js +0 -66
  32. package/dist/shell/assets/index-DaFzUWmg.css +0 -2
  33. package/dist/shell/assets/maplibre-Gu5hHDDK.js +0 -814
  34. package/dist/shell/assets/record-map-DQ0oyZff.js +0 -1
  35. package/dist/shell/assets/records-map-Dp97bL3p.js +0 -1
@@ -1,7 +1,6 @@
1
1
  import { cn } from "../lib/cn.js";
2
2
  import { inApp } from "../app-shell/breadcrumb.js";
3
3
  import { useAuth } from "../auth/auth-provider.js";
4
- import { formatRelativeTime } from "../format/date.js";
5
4
  import { Button, buttonClass } from "../primitives/button.js";
6
5
  import { keys } from "../data/query-keys.js";
7
6
  import { useClient } from "../data/api-provider.js";
@@ -42,7 +41,7 @@ import { useRecordEditor } from "./use-record-editor.js";
42
41
  import { useRelatedRecords } from "./use-related-records.js";
43
42
  import { RelatedSection } from "./related-section.js";
44
43
  import { Fragment, useMemo } from "react";
45
- import { Check, Pencil } from "lucide-react";
44
+ import { Check } from "lucide-react";
46
45
  import { isApiError } from "@protobase/client";
47
46
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
48
47
  import { useQueryClient } from "@tanstack/react-query";
@@ -50,9 +49,8 @@ import { enumLabel } from "@protobase/schema";
50
49
  //#region src/app/record-page.tsx
51
50
  const hidden = (model, name) => name === model.tenant || model.primaryKey.includes(name);
52
51
  /**
53
- * The view's layout, or one section with the editable fields when the view has none. A user who may only read the
54
- * resource has every field read-only, so they get every field, locked, rather than an empty section; so does the read
55
- * view, which shows the record rather than what can be changed.
52
+ * The view's layout, or one section with the editable fields when the view has none. The read view, which is all a
53
+ * user who may only read the resource gets, shows every field: the record rather than what can be changed.
56
54
  */
57
55
  const layoutFor = (model, view, read) => {
58
56
  if (view && view.layout.length > 0) return view.layout;
@@ -148,9 +146,10 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
148
146
  const { fields: fieldTypes } = useProjectUi();
149
147
  const layout = layoutFor(model, view, shown === "read");
150
148
  const sidebarFields = layout.flatMap((item) => item.kind === "sidebar" ? item.fields : []);
151
- const status = model.fields.status?.type === "enum" ? String(stored.record.status) : void 0;
149
+ const hasStatus = model.fields.status?.type === "enum";
150
+ const statusValue = stored.record.status;
151
+ const status = hasStatus && statusValue !== null && statusValue !== void 0 && statusValue !== "" ? String(statusValue) : void 0;
152
152
  const updatedAt = auditField(model, "updated");
153
- const updated = updatedAt && stored.record[updatedAt] ? Math.max(0, Math.round((Date.now() - Date.parse(String(stored.record[updatedAt]))) / 1e3)) : void 0;
154
153
  const hint = (name) => view?.fields[name];
155
154
  const visible = (name) => fieldAccess(stored.permissions, name) !== "hidden";
156
155
  const fieldView = (name) => {
@@ -251,30 +250,18 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
251
250
  const save = async () => {
252
251
  if (await editor.save() && leavesForm) go(readAddress, { replace: true });
253
252
  };
254
- const deleteButton = canDelete && /* @__PURE__ */ jsx(Button, {
255
- variant: "danger",
256
- className: "max-md:min-h-11",
257
- onClick: () => void remove.start({
258
- key: recordKey,
259
- title,
260
- etag: stored.etag,
261
- snapshot: stored.record
262
- }),
263
- children: "Delete"
264
- });
265
- const saveControls = offersEdit ? /* @__PURE__ */ jsxs(Fragment$1, { children: [deleteButton, /* @__PURE__ */ jsxs(AppLink, {
266
- to: editAddress,
267
- className: cn(buttonClass({ variant: "primary" }), "max-md:min-h-11 max-md:flex-1"),
268
- children: [/* @__PURE__ */ jsx(Pencil, {
269
- className: "size-3.5",
270
- "aria-hidden": true
271
- }), "Edit"]
272
- })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
253
+ const startDelete = canDelete ? () => void remove.start({
254
+ key: recordKey,
255
+ title,
256
+ etag: stored.etag,
257
+ snapshot: stored.record
258
+ }) : void 0;
259
+ const formControls = shown === "form" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
273
260
  editor.dirty && /* @__PURE__ */ jsx("span", {
274
261
  className: "text-xs text-muted-foreground",
275
262
  children: "Unsaved changes"
276
263
  }),
277
- editor.dirty && canUpdate && !leavesForm && /* @__PURE__ */ jsx(Button, {
264
+ editor.dirty && !leavesForm && /* @__PURE__ */ jsx(Button, {
278
265
  className: "max-md:min-h-11",
279
266
  disabled: editor.saving,
280
267
  onClick: editor.discard,
@@ -286,8 +273,7 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
286
273
  onClick: (event) => inApp(event) && editor.discard(),
287
274
  children: "Cancel"
288
275
  }),
289
- deleteButton,
290
- canUpdate && /* @__PURE__ */ jsxs(Button, {
276
+ /* @__PURE__ */ jsxs(Button, {
291
277
  variant: "primary",
292
278
  className: "max-md:min-h-11 max-md:flex-1",
293
279
  loading: editor.saving,
@@ -306,16 +292,17 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
306
292
  className: "mx-auto max-w-[1100px] px-4 pb-8 pt-6 md:px-10 md:py-8",
307
293
  children: [
308
294
  /* @__PURE__ */ jsx(RecordHeader, {
309
- collection: view?.names?.plural ?? humanize(model.name),
310
295
  title,
311
- status: {
312
- label: status ? enumLabel(status, hint("status")?.valueLabels) : singular,
313
- tone: status ? toneFor(status) : "neutral"
296
+ status: status ? {
297
+ label: enumLabel(status, hint("status")?.valueLabels),
298
+ tone: toneFor(status)
299
+ } : hasStatus ? void 0 : {
300
+ label: singular,
301
+ tone: "neutral"
314
302
  },
315
- lastEditedBy: { ago: updated === void 0 ? "recently" : formatRelativeTime(updated) },
316
303
  actions: /* @__PURE__ */ jsx("div", {
317
304
  className: "hidden items-center gap-4 md:flex",
318
- children: saveControls
305
+ children: formControls
319
306
  })
320
307
  }),
321
308
  editor.message && /* @__PURE__ */ jsx("div", {
@@ -411,7 +398,9 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
411
398
  /* @__PURE__ */ jsx(RecordActions, {
412
399
  model,
413
400
  stored,
414
- recordKey
401
+ recordKey,
402
+ edit: offersEdit ? editAddress : void 0,
403
+ onDelete: startDelete
415
404
  }),
416
405
  firstSidebar === -1 && summary,
417
406
  layout.map((item, index) => {
@@ -448,9 +437,9 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
448
437
  ]
449
438
  })
450
439
  }),
451
- (editor.dirty || canDelete || canUpdate) && /* @__PURE__ */ jsx("div", {
440
+ formControls && /* @__PURE__ */ jsx("div", {
452
441
  className: "sticky bottom-[var(--pb-sticky-bottom,0px)] z-20 flex shrink-0 items-center gap-2 border-t bg-background p-3 md:hidden",
453
- children: saveControls
442
+ children: formControls
454
443
  })
455
444
  ]
456
445
  });
@@ -1,4 +1,4 @@
1
- import { PluginRef, ResourcePermissions, SourceRef } from "@protobase/client";
1
+ import { MapLayerModel, PluginRef, ResourcePermissions, SourceRef } from "@protobase/client";
2
2
  import { BottomBarModel, ResourceModel, SignInViewModel, UserMenuModel, ViewModel } from "@protobase/schema";
3
3
  import { PageModel } from "@protobase/layout";
4
4
  //#region src/data/meta-data.d.ts
@@ -27,9 +27,10 @@ export type MetaData = {
27
27
  dsn: string;
28
28
  environment?: string;
29
29
  };
30
- /** The map style the record page's maps are drawn with, when the app has one (see `MapSection`). */
30
+ /** The map style the app's maps are drawn with, and the layers they may switch to, when the app has one (see `MapSection`). */
31
31
  maps?: {
32
32
  style: string;
33
+ layers?: MapLayerModel[];
33
34
  };
34
35
  /** The hosts besides the app's own whose images are drawn with the page's referrer (see `FileImage`). */
35
36
  files?: {
@@ -19,19 +19,6 @@ const time = new Intl.DateTimeFormat("en-GB", {
19
19
  const formatDate = (ms) => day.format(ms);
20
20
  const formatDayShort = (ms) => dayShort.format(ms);
21
21
  const formatDateTime = (ms) => `${day.format(ms)}, ${time.format(ms)}`;
22
- const relative = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
23
- const units = [
24
- ["year", 31536e3],
25
- ["month", 2592e3],
26
- ["day", 86400],
27
- ["hour", 3600],
28
- ["minute", 60]
29
- ];
30
- /** "5 minutes ago", "yesterday", "3 years ago"; `seconds` is how long ago it was. */
31
- const formatRelativeTime = (seconds) => {
32
- const [unit, size] = units.find(([, size]) => seconds >= size) ?? ["second", 1];
33
- if (unit === "second" && seconds < 45) return "just now";
34
- return relative.format(-Math.floor(seconds / size), unit);
35
- };
22
+ new Intl.RelativeTimeFormat("en", { numeric: "auto" });
36
23
  //#endregion
37
- export { formatDate, formatDateTime, formatDayShort, formatRelativeTime };
24
+ export { formatDate, formatDateTime, formatDayShort };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.19.2";
2
+ var version = "0.19.4";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -1,5 +1,7 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { cn } from "../lib/cn.js";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
3
  //#region src/record-view/action-rail.tsx
4
+ const itemClass = "flex min-h-11 items-center gap-2 rounded-md border border-border-strong px-3 text-left hover:bg-surface disabled:cursor-default disabled:opacity-60 md:min-h-0 md:w-full md:items-start md:gap-3 md:rounded-none md:border-0 md:px-4 md:py-3";
3
5
  /** Named actions with one-line descriptions; on phones a row of buttons, from `md` a list. */
4
6
  const ActionRail = ({ actions }) => /* @__PURE__ */ jsxs("div", {
5
7
  className: "overflow-hidden rounded-lg border border-border bg-background",
@@ -8,20 +10,29 @@ const ActionRail = ({ actions }) => /* @__PURE__ */ jsxs("div", {
8
10
  children: "Actions"
9
11
  }), /* @__PURE__ */ jsx("ul", {
10
12
  className: "flex flex-wrap gap-2 p-2 md:block md:divide-y md:divide-border md:p-0",
11
- children: actions.map(({ name, description, icon: Icon, onRun, blocked, running }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
12
- type: "button",
13
- onClick: onRun,
14
- disabled: Boolean(blocked) || running,
15
- title: blocked,
16
- className: "flex min-h-11 items-center gap-2 rounded-md border border-border-strong px-3 text-left hover:bg-surface disabled:cursor-default disabled:opacity-60 md:min-h-0 md:w-full md:items-start md:gap-3 md:rounded-none md:border-0 md:px-4 md:py-3",
17
- children: [/* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0 text-muted-foreground md:mt-0.5" }), /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
18
- className: "block text-[13px] font-medium text-foreground",
13
+ children: actions.map(({ name, description, icon: Icon, onRun, blocked, running, tone, render }) => {
14
+ const children = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, {
15
+ className: cn("size-4 shrink-0 md:mt-0.5", tone === "danger" ? "text-danger-text" : "text-muted-foreground"),
16
+ "aria-hidden": true
17
+ }), /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("span", {
18
+ className: cn("block text-[13px] font-medium", tone === "danger" ? "text-danger-text" : "text-foreground"),
19
19
  children: name
20
20
  }), description && /* @__PURE__ */ jsx("span", {
21
21
  className: "mt-0.5 hidden text-xs leading-snug text-muted-foreground md:block",
22
22
  children: description
23
- })] })]
24
- }) }, name))
23
+ })] })] });
24
+ return /* @__PURE__ */ jsx("li", { children: render ? render({
25
+ className: itemClass,
26
+ children
27
+ }) : /* @__PURE__ */ jsx("button", {
28
+ type: "button",
29
+ onClick: onRun,
30
+ disabled: Boolean(blocked) || running,
31
+ title: blocked,
32
+ className: itemClass,
33
+ children
34
+ }) }, name);
35
+ })
25
36
  })]
26
37
  });
27
38
  //#endregion
@@ -5,21 +5,21 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  const RecordHeader = ({ collection, title, status, lastEditedBy, presence = [], actions }) => /* @__PURE__ */ jsxs("header", {
6
6
  className: "flex flex-col gap-4 border-b border-border pb-6 md:flex-row md:items-end md:justify-between md:gap-6",
7
7
  children: [/* @__PURE__ */ jsxs("div", { children: [
8
- /* @__PURE__ */ jsx("p", {
9
- className: "text-xs text-muted-foreground",
8
+ collection && /* @__PURE__ */ jsx("p", {
9
+ className: "mb-1 text-xs text-muted-foreground",
10
10
  children: collection
11
11
  }),
12
12
  /* @__PURE__ */ jsxs("div", {
13
- className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1",
13
+ className: "flex flex-wrap items-center gap-x-3 gap-y-1",
14
14
  children: [/* @__PURE__ */ jsx("h1", {
15
15
  className: "text-xl font-semibold tracking-tight text-foreground md:text-2xl",
16
16
  children: title
17
- }), /* @__PURE__ */ jsx(Badge, {
17
+ }), status && /* @__PURE__ */ jsx(Badge, {
18
18
  tone: status.tone,
19
19
  children: status.label
20
20
  })]
21
21
  }),
22
- /* @__PURE__ */ jsx("p", {
22
+ lastEditedBy && /* @__PURE__ */ jsx("p", {
23
23
  className: "mt-1.5 text-xs text-muted-foreground",
24
24
  children: lastEditedBy.name ? /* @__PURE__ */ jsxs(Fragment, { children: [
25
25
  "Last edited by ",