@protobase/ui 0.15.2 → 0.15.3
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/named-icon.js +3 -2
- package/dist/app/pages/action-href.js +39 -1
- package/dist/app/pages/use-run-action.js +6 -5
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-CDvqhtbz.js +66 -0
- package/dist/shell/assets/{index-C3BxeQpA.js → index-DQSkJbGu.js} +1 -1
- package/dist/shell/assets/{maplibre-gl-vL3SLP-z.js → maplibre-gl-Dlx-44Hm.js} +1 -1
- package/dist/shell/assets/{record-map-CMEE5LNh.js → record-map-CKvAK_H-.js} +2 -2
- package/dist/shell/assets/{records-map-2GklcX3V.js → records-map-BKJA963O.js} +1 -1
- package/dist/shell/assets/{shared-protobase-ui-TrJA21Ip.js → shared-protobase-ui-DhHLDXiE.js} +1 -1
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-BBxRH0zo.js +0 -66
package/dist/app/named-icon.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Bed, Bell, Bookmark, FileText, Heart, RulerDimensionLine, Send, Wallet } from "lucide-react";
|
|
1
|
+
import { Bed, Bell, Bookmark, ExternalLink, FileText, Heart, RulerDimensionLine, Send, Wallet } from "lucide-react";
|
|
2
2
|
//#region src/app/named-icon.ts
|
|
3
3
|
/** The icons an action's, a field's or a sign-in prompt's `icon` may name. */
|
|
4
4
|
const byName = {
|
|
@@ -9,7 +9,8 @@ const byName = {
|
|
|
9
9
|
bed: Bed,
|
|
10
10
|
heart: Heart,
|
|
11
11
|
bell: Bell,
|
|
12
|
-
bookmark: Bookmark
|
|
12
|
+
bookmark: Bookmark,
|
|
13
|
+
"external-link": ExternalLink
|
|
13
14
|
};
|
|
14
15
|
/** The icon a name stands for, or nothing for a name the app does not know. */
|
|
15
16
|
const namedIcon = (name) => name !== void 0 && Object.hasOwn(byName, name) ? byName[name] : void 0;
|
|
@@ -1,5 +1,43 @@
|
|
|
1
|
+
import { hrefField } from "@protobase/schema/internal";
|
|
1
2
|
//#region src/app/pages/action-href.ts
|
|
2
3
|
/** `href` with every `{field}` replaced by the record's value, percent-encoded; a missing value becomes empty. */
|
|
3
4
|
const fillHref = (href, record = {}) => href.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => encodeURIComponent(record[name] === null || record[name] === void 0 ? "" : String(record[name])));
|
|
5
|
+
const webUrl = (value) => {
|
|
6
|
+
if (typeof value !== "string" || !URL.canParse(value)) return void 0;
|
|
7
|
+
const url = new URL(value);
|
|
8
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url.href : void 0;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Where a link action's `href` goes from `record`: an href that is only `{field}` is the record's value as it is, when
|
|
12
|
+
* that is an `https:` or `http:` URL, and nothing otherwise; any other href is filled by `fillHref`.
|
|
13
|
+
*/
|
|
14
|
+
const linkTarget = (href, record = {}) => {
|
|
15
|
+
const field = hrefField(href);
|
|
16
|
+
return field === void 0 ? fillHref(href, record) : webUrl(record[field]);
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Opens a link's target: an `https:` or `http:` page in a new tab that gets neither the app's window nor its address,
|
|
20
|
+
* anything else (`mailto:`) in place. A link without a target does nothing.
|
|
21
|
+
*/
|
|
22
|
+
const openLink = (target) => {
|
|
23
|
+
if (target === void 0) return;
|
|
24
|
+
if (/^https?:/i.test(target)) window.open(target, "_blank", "noopener,noreferrer");
|
|
25
|
+
else window.location.assign(target);
|
|
26
|
+
};
|
|
27
|
+
const hostOf = (target) => URL.canParse(target) ? new URL(target).hostname.replace(/^www\./, "") : "";
|
|
28
|
+
/**
|
|
29
|
+
* `action` as it shows for `record`: a link whose `href` has no target there is left out, and `{host}` in a link's label
|
|
30
|
+
* becomes the host its target opens, without `www.`.
|
|
31
|
+
*/
|
|
32
|
+
const actionFor = (action, record) => {
|
|
33
|
+
const run = action.run;
|
|
34
|
+
if (run?.kind !== "link" || !("href" in run)) return action;
|
|
35
|
+
const target = linkTarget(run.href, record);
|
|
36
|
+
if (target === void 0) return void 0;
|
|
37
|
+
return action.label.includes("{host}") ? {
|
|
38
|
+
...action,
|
|
39
|
+
label: action.label.replaceAll("{host}", hostOf(target))
|
|
40
|
+
} : action;
|
|
41
|
+
};
|
|
4
42
|
//#endregion
|
|
5
|
-
export { fillHref };
|
|
43
|
+
export { actionFor, fillHref, linkTarget, openLink };
|
|
@@ -5,15 +5,16 @@ import { useAdminMeta, usePermissions } from "../meta-gate.js";
|
|
|
5
5
|
import { useAppAddress } from "../use-app-address.js";
|
|
6
6
|
import { can } from "../permissions.js";
|
|
7
7
|
import { useRefreshResource } from "./use-layout-data.js";
|
|
8
|
-
import {
|
|
8
|
+
import { actionFor, linkTarget, openLink } from "./action-href.js";
|
|
9
9
|
import { useState } from "react";
|
|
10
10
|
import { fillAddress } from "@protobase/schema/internal";
|
|
11
11
|
//#region src/app/pages/use-run-action.ts
|
|
12
12
|
const message = (error) => error instanceof Error ? error.message : "Something went wrong";
|
|
13
13
|
/**
|
|
14
14
|
* The named actions of `resource`'s view, ready to run on `scope` (the record they act on): built-in behaviour first
|
|
15
|
-
* (`update`, `delete`, `link`), else the handler the project registered under the action's name.
|
|
16
|
-
* cannot run; `afterRun` hears about every action
|
|
15
|
+
* (`update`, `delete`, `link`), else the handler the project registered under the action's name. A link to a URL the
|
|
16
|
+
* record holds is left out while it holds none. `blocked` says why one cannot run; `afterRun` hears about every action
|
|
17
|
+
* that succeeded.
|
|
17
18
|
*/
|
|
18
19
|
const useActionRunner = (resource, scope, afterRun) => {
|
|
19
20
|
const { views } = useAdminMeta();
|
|
@@ -25,7 +26,7 @@ const useActionRunner = (resource, scope, afterRun) => {
|
|
|
25
26
|
const refresh = useRefreshResource();
|
|
26
27
|
const { go } = useAppAddress();
|
|
27
28
|
const [running, setRunning] = useState();
|
|
28
|
-
const actions = views[resource]?.actions ?? [];
|
|
29
|
+
const actions = (views[resource]?.actions ?? []).flatMap((action) => actionFor(action, scope?.record) ?? []);
|
|
29
30
|
const blocked = (action) => {
|
|
30
31
|
const kind = action.run?.kind;
|
|
31
32
|
if ((kind === "update" || kind === "delete") && !scope) return "Needs a record";
|
|
@@ -37,7 +38,7 @@ const useActionRunner = (resource, scope, afterRun) => {
|
|
|
37
38
|
const { run } = action;
|
|
38
39
|
if (run?.kind === "link") {
|
|
39
40
|
if ("to" in run) go(fillAddress(run.to, scope?.record));
|
|
40
|
-
else
|
|
41
|
+
else openLink(linkTarget(run.href, scope?.record));
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
44
|
if (run?.kind === "update") await client.update(resource, scope.key, run.values, scope.etag ?? "*");
|
package/dist/package.js
CHANGED