@protobase/ui 0.12.17 → 0.12.19

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.
@@ -0,0 +1,38 @@
1
+ import { useAdminMeta } from "../meta-gate.js";
2
+ import { Section } from "../../record-view/section.js";
3
+ import { SidebarCard } from "../../record-view/record-sidebar.js";
4
+ import { recordPosition } from "./record-position.js";
5
+ import { Suspense, lazy } from "react";
6
+ import { jsx } from "react/jsx-runtime";
7
+ //#region src/app/map/map-section.tsx
8
+ const RecordMap = lazy(() => import("./record-map.js").then((module) => ({ default: module.RecordMap })));
9
+ const defaultZoom = 15;
10
+ /**
11
+ * A map of the record's position (`l.map`), in a section or a sidebar card under its title, drawn with the map style
12
+ * `/meta` names. Without a style, or for a record without a position, there is nothing to draw and no section.
13
+ */
14
+ const MapSection = ({ item, record }) => {
15
+ const style = useAdminMeta().maps?.style;
16
+ const position = recordPosition(record, item);
17
+ if (!style || !position) return null;
18
+ const map = /* @__PURE__ */ jsx("div", {
19
+ className: `overflow-hidden rounded-lg border border-border bg-muted ${item.sidebar ? "h-48" : "h-64 md:h-80"}`,
20
+ children: /* @__PURE__ */ jsx(Suspense, {
21
+ fallback: null,
22
+ children: /* @__PURE__ */ jsx(RecordMap, {
23
+ style,
24
+ position,
25
+ zoom: item.zoom ?? defaultZoom
26
+ }, `${position.latitude},${position.longitude}`)
27
+ })
28
+ });
29
+ return item.sidebar ? /* @__PURE__ */ jsx(SidebarCard, {
30
+ title: item.title,
31
+ children: map
32
+ }) : /* @__PURE__ */ jsx(Section, {
33
+ title: item.title,
34
+ children: map
35
+ });
36
+ };
37
+ //#endregion
38
+ export { MapSection };
@@ -0,0 +1,39 @@
1
+ import { MapPin } from "lucide-react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as maplibre from "maplibre-gl";
4
+ import workerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url";
5
+ import "maplibre-gl/dist/maplibre-gl.css";
6
+ import { Protocol } from "pmtiles";
7
+ import { Map, Marker, NavigationControl } from "react-map-gl/maplibre";
8
+ //#region src/app/map/record-map.tsx
9
+ maplibre.setWorkerUrl(workerUrl);
10
+ maplibre.addProtocol("pmtiles", new Protocol().tile);
11
+ /**
12
+ * A map of `style` centred on `position` at `zoom`, with a pin on it. It pans and zooms with two fingers on a phone and
13
+ * with Ctrl or Cmd and the wheel, so scrolling the page past it does not move it.
14
+ */
15
+ const RecordMap = ({ style, position, zoom }) => /* @__PURE__ */ jsxs(Map, {
16
+ mapLib: maplibre,
17
+ mapStyle: style,
18
+ initialViewState: {
19
+ ...position,
20
+ zoom
21
+ },
22
+ cooperativeGestures: true,
23
+ style: {
24
+ width: "100%",
25
+ height: "100%"
26
+ },
27
+ children: [/* @__PURE__ */ jsx(NavigationControl, { showCompass: false }), /* @__PURE__ */ jsx(Marker, {
28
+ latitude: position.latitude,
29
+ longitude: position.longitude,
30
+ anchor: "bottom",
31
+ children: /* @__PURE__ */ jsx(MapPin, {
32
+ "aria-hidden": true,
33
+ className: "size-8 fill-primary text-primary-foreground drop-shadow",
34
+ strokeWidth: 1.5
35
+ })
36
+ })]
37
+ });
38
+ //#endregion
39
+ export { RecordMap };
@@ -0,0 +1,19 @@
1
+ //#region src/app/map/record-position.ts
2
+ const degrees = (value) => {
3
+ if (typeof value === "number") return value;
4
+ if (typeof value !== "string" || value.trim() === "") return void 0;
5
+ return Number(value);
6
+ };
7
+ /** The record's position from its two fields, when both hold degrees on the globe; none otherwise, so no map is drawn. */
8
+ const recordPosition = (record, fields) => {
9
+ const latitude = degrees(record[fields.latitude]);
10
+ const longitude = degrees(record[fields.longitude]);
11
+ if (latitude === void 0 || longitude === void 0) return void 0;
12
+ if (!(Math.abs(latitude) <= 90 && Math.abs(longitude) <= 180)) return void 0;
13
+ return {
14
+ latitude,
15
+ longitude
16
+ };
17
+ };
18
+ //#endregion
19
+ export { recordPosition };
@@ -26,6 +26,7 @@ import { useRestore } from "./delete/use-restore.js";
26
26
  import { ValueBadge } from "./value-badge.js";
27
27
  import { auditField } from "../live/audit-fields.js";
28
28
  import { CustomSection } from "./custom-section.js";
29
+ import { MapSection } from "./map/map-section.js";
29
30
  import { RecordActions } from "./pages/record-actions.js";
30
31
  import { RecordLines, findLinesResource } from "./record-lines.js";
31
32
  import { draftStorageKey } from "./draft-storage.js";
@@ -297,6 +298,10 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
297
298
  item,
298
299
  scope
299
300
  }, `custom:${index}`)];
301
+ if (item.kind === "map") return item.sidebar ? [] : [/* @__PURE__ */ jsx(MapSection, {
302
+ item,
303
+ record: stored.record
304
+ }, `map:${index}`)];
300
305
  if (item.kind !== "section") return [];
301
306
  const fields = item.fields.filter((name) => model.fields[name] && visible(name));
302
307
  if (fields.length === 0) return [];
@@ -323,10 +328,17 @@ const LoadedRecord = ({ model, recordKey, stored }) => {
323
328
  recordKey
324
329
  }),
325
330
  firstSidebar === -1 && summary,
326
- layout.map((item, index) => item.kind === "custom" && item.sidebar ? /* @__PURE__ */ jsx(CustomSection, {
327
- item,
328
- scope
329
- }, `custom:${index}`) : index === firstSidebar ? /* @__PURE__ */ jsx(Fragment, { children: summary }, "summary") : null),
331
+ layout.map((item, index) => {
332
+ if (item.kind === "custom" && item.sidebar) return /* @__PURE__ */ jsx(CustomSection, {
333
+ item,
334
+ scope
335
+ }, `custom:${index}`);
336
+ if (item.kind === "map" && item.sidebar) return /* @__PURE__ */ jsx(MapSection, {
337
+ item,
338
+ record: stored.record
339
+ }, `map:${index}`);
340
+ return index === firstSidebar ? /* @__PURE__ */ jsx(Fragment, { children: summary }, "summary") : null;
341
+ }),
330
342
  /* @__PURE__ */ jsx(MetadataCard, {
331
343
  title: "Details",
332
344
  rows: [auditField(model, "created"), updatedAt].flatMap((name) => {
@@ -25,6 +25,10 @@ export type MetaData = {
25
25
  dsn: string;
26
26
  environment?: string;
27
27
  };
28
+ /** The map style the record page's maps are drawn with, when the app has one (see `MapSection`). */
29
+ maps?: {
30
+ style: string;
31
+ };
28
32
  /** The plugins the app loads for the user (see `PluginGate`). */
29
33
  plugins?: PluginRef[];
30
34
  /** The other APIs the app draws resources from for the user, and how far their `/meta` has loaded (see `withSources`). */
@@ -35,6 +35,7 @@ const useMeta = () => {
35
35
  ...result.meta.assistant && { assistant: result.meta.assistant },
36
36
  ...result.meta.runtime && { runtime: result.meta.runtime },
37
37
  ...result.meta.errorReporting && { errorReporting: result.meta.errorReporting },
38
+ ...result.meta.maps && { maps: result.meta.maps },
38
39
  ...result.meta.plugins && { plugins: result.meta.plugins },
39
40
  ...result.meta.sources && { sources: result.meta.sources.map((source) => ({
40
41
  source,
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.12.17";
2
+ var version = "0.12.19";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };