@peers-app/peers-ui 0.18.5 → 0.18.6
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/screens/packages/package-info.js +23 -21
- package/dist/screens/packages/package-versions.js +20 -16
- package/dist/ui-router/routes-loader.js +75 -9
- package/dist/ui-router/ui-loader.js +30 -2
- package/package.json +3 -3
- package/src/screens/packages/package-info.tsx +34 -39
- package/src/screens/packages/package-versions.tsx +25 -16
- package/src/ui-router/routes-loader.ts +76 -9
- package/src/ui-router/ui-loader.tsx +32 -2
|
@@ -11,25 +11,27 @@ const input_1 = require("../../components/input");
|
|
|
11
11
|
const markdown_with_mentions_1 = require("../../components/markdown-with-mentions");
|
|
12
12
|
const tooltip_1 = require("../../components/tooltip");
|
|
13
13
|
const hooks_1 = require("../../hooks");
|
|
14
|
-
const deviceVersionTagVar = (0, peers_sdk_1.groupDeviceVar)("deviceVersionTag");
|
|
15
14
|
const PackageInfo = (props) => {
|
|
16
15
|
const { pkg } = props;
|
|
17
|
-
const [activeVersionId] = (0, hooks_1.useObservable)(pkg.qs.activePackageVersionId);
|
|
18
|
-
const [versionFollowRange] = (0, hooks_1.useObservable)(pkg.qs.versionFollowRange);
|
|
19
16
|
const [followVersionTags] = (0, hooks_1.useObservable)(pkg.qs.followVersionTags);
|
|
20
|
-
const [deviceTag] = (0, hooks_1.useObservable)(deviceVersionTagVar);
|
|
21
17
|
const localPathVar = (0, peers_sdk_1.groupDeviceVar)(`packageLocalPath_${pkg.packageId}`, {
|
|
22
18
|
defaultValue: `${peers_sdk_1.packagesRootDir}/${pkg.name}`,
|
|
23
19
|
});
|
|
24
20
|
const [localPath, setLocalPath] = (0, hooks_1.useObservable)(localPathVar);
|
|
25
|
-
const [
|
|
21
|
+
const [devicePrefs] = (0, hooks_1.useObservable)((0, peers_sdk_1.packagePrefsVar)(pkg.packageId));
|
|
22
|
+
const effective = (0, peers_sdk_1.getEffectivePackagePrefs)(pkg.toJS(), devicePrefs);
|
|
23
|
+
const activeVersionId = effective.activePackageVersionId;
|
|
24
|
+
const deviceFollowTags = devicePrefs?.followTags;
|
|
25
|
+
const isPinned = effective.isPinned;
|
|
26
|
+
const followRange = effective.followRange;
|
|
27
|
+
const [deviceTagDraft, setDeviceTagDraft] = react_1.default.useState(deviceFollowTags || "");
|
|
26
28
|
const savingRef = react_1.default.useRef(false);
|
|
27
29
|
react_1.default.useEffect(() => {
|
|
28
30
|
if (!savingRef.current) {
|
|
29
|
-
setDeviceTagDraft(
|
|
31
|
+
setDeviceTagDraft(deviceFollowTags || "");
|
|
30
32
|
}
|
|
31
|
-
}, [
|
|
32
|
-
const deviceTagDirty = deviceTagDraft.trim() !== (
|
|
33
|
+
}, [deviceFollowTags]);
|
|
34
|
+
const deviceTagDirty = deviceTagDraft.trim() !== (deviceFollowTags || "");
|
|
33
35
|
const prevDirtyRef = react_1.default.useRef(false);
|
|
34
36
|
react_1.default.useEffect(() => {
|
|
35
37
|
if (deviceTagDirty && !prevDirtyRef.current) {
|
|
@@ -43,15 +45,12 @@ const PackageInfo = (props) => {
|
|
|
43
45
|
if (props.saveDeviceTagRef) {
|
|
44
46
|
props.saveDeviceTagRef.current = async () => {
|
|
45
47
|
const val = deviceTagDraft.trim();
|
|
46
|
-
if (val !== (
|
|
48
|
+
if (val !== (deviceFollowTags || "")) {
|
|
47
49
|
savingRef.current = true;
|
|
48
50
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
await deviceVersionTagVar.delete();
|
|
54
|
-
}
|
|
51
|
+
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, {
|
|
52
|
+
followTags: val || undefined,
|
|
53
|
+
});
|
|
55
54
|
}
|
|
56
55
|
finally {
|
|
57
56
|
savingRef.current = false;
|
|
@@ -71,13 +70,14 @@ const PackageInfo = (props) => {
|
|
|
71
70
|
const active = all.find((v) => v.packageVersionId === activeVersionId);
|
|
72
71
|
if (!active?.version)
|
|
73
72
|
return "uptodate";
|
|
73
|
+
const effectiveFollowTags = deviceFollowTags || followVersionTags;
|
|
74
74
|
const parse = (v) => v.split(".").map(Number);
|
|
75
75
|
const [aMaj, aMin, aPat] = parse(active.version);
|
|
76
76
|
let highest = null;
|
|
77
77
|
for (const v of all) {
|
|
78
78
|
if (!v.version || v.packageVersionId === activeVersionId)
|
|
79
79
|
continue;
|
|
80
|
-
if (!(0, peers_sdk_1.doesTagMatch)(active.versionTag, v.versionTag,
|
|
80
|
+
if (!(0, peers_sdk_1.doesTagMatch)(active.versionTag, v.versionTag, effectiveFollowTags, undefined))
|
|
81
81
|
continue;
|
|
82
82
|
const [maj, min, pat] = parse(v.version);
|
|
83
83
|
if (maj > aMaj)
|
|
@@ -99,16 +99,18 @@ const PackageInfo = (props) => {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
return highest || "uptodate";
|
|
102
|
-
}, undefined, [activeVersionId, pkg.packageId, followVersionTags,
|
|
103
|
-
const isPinned = versionFollowRange === "pinned";
|
|
102
|
+
}, undefined, [activeVersionId, pkg.packageId, followVersionTags, deviceFollowTags]);
|
|
104
103
|
const remoteRepoUrl = pkg.remoteRepo;
|
|
105
104
|
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("small", { children: "Name:" }), (0, jsx_runtime_1.jsx)(input_1.Input, { value: pkg.qs.name, className: "form-control mb-3 p-0 ps-2", placeholder: "Package name", title: "Package name", disabled: true }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Local Path:", (0, jsx_runtime_1.jsxs)("small", { children: [(0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: `The local path to the directory containing the package. This will open in VS Code if it's installed otherwise this will open in your native file system` }), (0, jsx_runtime_1.jsx)("button", { className: "btn btn-sm btn-link", onClick: () => {
|
|
106
105
|
peers_sdk_1.rpcServerCalls.openPackage(localPath || peers_sdk_1.packagesRootDir);
|
|
107
106
|
}, children: "Open Local" })] })] }), (0, jsx_runtime_1.jsx)("input", { type: "text", className: "form-control mb-3 p-0 ps-2", placeholder: "~/peers/packages/my-package", value: localPath || "", onChange: (e) => setLocalPath(e.target.value) })] }), remoteRepoUrl ? ((0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Source URL:", (0, jsx_runtime_1.jsxs)("small", { children: [(0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: `The remote source of this package. Often a github repository URL or a link to the Peers page for the package.` }), (0, jsx_runtime_1.jsx)("button", { className: "btn btn-sm btn-link", onClick: () => {
|
|
108
107
|
peers_sdk_1.rpcServerCalls.openLinkInBrowser(remoteRepoUrl);
|
|
109
|
-
}, children: "Open Remote" })] })] }), (0, jsx_runtime_1.jsx)(input_1.Input, { value: pkg.qs.remoteRepo, className: "form-control mb-3 p-0 ps-2", disabled: true })] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsx)("hr", {}), (0, jsx_runtime_1.jsxs)("small", { children: ["Version:", (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: "The currently active version of this package. Manage versions in the Versions tab." })] }), activeVersion ? ((0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center mt-1 mb-3", children: [(0, jsx_runtime_1.jsxs)("strong", { className: "me-2", children: ["v", activeVersion.version] }), activeVersion.versionTag && ((0, jsx_runtime_1.jsx)("span", { className: `badge ${activeVersion.versionTag === "dev" ? "text-bg-danger" : activeVersion.versionTag.startsWith("beta") ? "text-bg-warning" : "text-bg-success"} me-2`, children: activeVersion.versionTag })), (0, jsx_runtime_1.jsx)("code", { className: "text-muted small me-2", children: activeVersion.packageVersionHash?.substring(0, 8) }), newerLevel === "uptodate" ? ((0, jsx_runtime_1.jsx)("span", { className: "badge text-bg-success", children: "Up to date" })) : newerLevel ? ((0, jsx_runtime_1.jsxs)("span", { className: `badge text-bg-${newerLevel === "major" ? "danger" : newerLevel === "minor" ? "warning" : "info"}`, children: ["Newer ", newerLevel, " version available"] })) : null] })) : ((0, jsx_runtime_1.jsx)("div", { className: "text-muted small mt-1 mb-3", children: "No active version" })), (0, jsx_runtime_1.jsxs)("div", { className: "mb-3", children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Auto-Update Range:", (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: "Controls which new versions are auto-activated. **Pinned** = never auto-update. **Patch** = same major.minor (e.g. 1.2.x). **Minor** = same major (e.g. 1.x.x). **Latest** = always auto-update to newest." })] }), (0, jsx_runtime_1.jsxs)("select", { className: "form-select form-select-sm", value:
|
|
108
|
+
}, children: "Open Remote" })] })] }), (0, jsx_runtime_1.jsx)(input_1.Input, { value: pkg.qs.remoteRepo, className: "form-control mb-3 p-0 ps-2", disabled: true })] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsx)("hr", {}), (0, jsx_runtime_1.jsxs)("small", { children: ["Version:", (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: "The currently active version of this package. Manage versions in the Versions tab." })] }), activeVersion ? ((0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center mt-1 mb-3", children: [(0, jsx_runtime_1.jsxs)("strong", { className: "me-2", children: ["v", activeVersion.version] }), activeVersion.versionTag && ((0, jsx_runtime_1.jsx)("span", { className: `badge ${activeVersion.versionTag === "dev" ? "text-bg-danger" : activeVersion.versionTag.startsWith("beta") ? "text-bg-warning" : "text-bg-success"} me-2`, children: activeVersion.versionTag })), (0, jsx_runtime_1.jsx)("code", { className: "text-muted small me-2", children: activeVersion.packageVersionHash?.substring(0, 8) }), newerLevel === "uptodate" ? ((0, jsx_runtime_1.jsx)("span", { className: "badge text-bg-success", children: "Up to date" })) : newerLevel ? ((0, jsx_runtime_1.jsxs)("span", { className: `badge text-bg-${newerLevel === "major" ? "danger" : newerLevel === "minor" ? "warning" : "info"}`, children: ["Newer ", newerLevel, " version available"] })) : null] })) : ((0, jsx_runtime_1.jsx)("div", { className: "text-muted small mt-1 mb-3", children: "No active version" })), (0, jsx_runtime_1.jsxs)("div", { className: "mb-3", children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Auto-Update Range (this device):", (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: "Controls which new versions are auto-activated on this device. **Pinned** = never auto-update. **Patch** = same major.minor (e.g. 1.2.x). **Minor** = same major (e.g. 1.x.x). **Latest** = always auto-update to newest." })] }), (0, jsx_runtime_1.jsxs)("select", { className: "form-select form-select-sm", value: isPinned ? "pinned" : followRange, onChange: async (e) => {
|
|
110
109
|
const val = e.target.value;
|
|
111
|
-
pkg.
|
|
112
|
-
|
|
110
|
+
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, {
|
|
111
|
+
pinned: val === "pinned",
|
|
112
|
+
followRange: val === "pinned" ? undefined : val,
|
|
113
|
+
});
|
|
114
|
+
}, children: [(0, jsx_runtime_1.jsx)("option", { value: "latest", children: "Latest (auto-update to newest)" }), (0, jsx_runtime_1.jsx)("option", { value: "minor", children: "Minor (same major version)" }), (0, jsx_runtime_1.jsx)("option", { value: "patch", children: "Patch (same major.minor version)" }), (0, jsx_runtime_1.jsx)("option", { value: "pinned", children: "Pinned (no auto-updates)" })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: `mb-3 ${isPinned ? "opacity-50" : ""}`, children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Follow Version Tags (this device):", (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: "Which version tags to auto-activate on this device. Leave empty to follow the group's tag policy. Use `*` for any tag, or a comma-separated list like `stable,beta`." })] }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center gap-2", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", className: "form-control form-control-sm p-0 ps-2", placeholder: `Following "${activeVersion?.versionTag || "stable"}"`, value: deviceTagDraft, onChange: (e) => setDeviceTagDraft(e.target.value), disabled: isPinned }), deviceTagDraft && ((0, jsx_runtime_1.jsx)("button", { className: "btn btn-sm btn-outline-secondary", title: "Clear device tag override", onClick: () => setDeviceTagDraft(""), children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-x-lg" }) }))] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-2", children: [(0, jsx_runtime_1.jsx)("hr", {}), (0, jsx_runtime_1.jsxs)("small", { children: ["Description:", (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: `This should be edited in the package's README.md. It will automatically update when you restart Peers or reload the package.` })] }), (0, jsx_runtime_1.jsx)(markdown_with_mentions_1.MarkdownWithMentions, { content: pkg.description || "" })] })] }));
|
|
113
115
|
};
|
|
114
116
|
exports.PackageInfo = PackageInfo;
|
|
@@ -40,13 +40,14 @@ function tagBadgeClass(tag) {
|
|
|
40
40
|
}
|
|
41
41
|
const PackageVersionsList = (props) => {
|
|
42
42
|
const { pkg } = props;
|
|
43
|
-
const [activeVersionId] = (0, hooks_1.useObservable)(pkg.qs.activePackageVersionId);
|
|
44
|
-
const [versionFollowRange] = (0, hooks_1.useObservable)(pkg.qs.versionFollowRange);
|
|
45
43
|
const refreshKey = (0, hooks_1.useObservableState)(0);
|
|
46
44
|
const [activating, setActivating] = (0, react_1.useState)(null);
|
|
47
45
|
const [deleting, setDeleting] = (0, react_1.useState)(null);
|
|
48
46
|
const [pinning, setPinning] = (0, react_1.useState)(false);
|
|
49
|
-
const
|
|
47
|
+
const [devicePrefs] = (0, hooks_1.useObservable)((0, peers_sdk_1.packagePrefsVar)(pkg.packageId));
|
|
48
|
+
const effective = (0, peers_sdk_1.getEffectivePackagePrefs)(pkg.toJS(), devicePrefs);
|
|
49
|
+
const effectiveActiveVersionId = effective.activePackageVersionId;
|
|
50
|
+
const isPinned = effective.isPinned;
|
|
50
51
|
const versions = (0, hooks_1.usePromise)(async () => {
|
|
51
52
|
const all = await (0, peers_sdk_1.PackageVersions)().list({ packageId: pkg.packageId });
|
|
52
53
|
const parse = (v) => v.split(".").map(Number);
|
|
@@ -76,13 +77,20 @@ const PackageVersionsList = (props) => {
|
|
|
76
77
|
async function activateVersion(pv) {
|
|
77
78
|
setActivating(pv.packageVersionId);
|
|
78
79
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, {
|
|
81
|
+
activePackageVersionId: pv.packageVersionId,
|
|
82
|
+
});
|
|
83
|
+
// For non-dev versions, also update the group default so other devices
|
|
84
|
+
// can discover this as the recommended version.
|
|
85
|
+
if (pv.versionTag !== "dev") {
|
|
86
|
+
const current = await (0, peers_sdk_1.Packages)().get(pkg.packageId);
|
|
87
|
+
if (current) {
|
|
88
|
+
current.activePackageVersionId = pv.packageVersionId;
|
|
89
|
+
await (0, peers_sdk_1.Packages)().signAndSave(current);
|
|
90
|
+
}
|
|
85
91
|
}
|
|
92
|
+
await pkg.load();
|
|
93
|
+
refreshKey(refreshKey() + 1);
|
|
86
94
|
}
|
|
87
95
|
catch (err) {
|
|
88
96
|
alert(`Failed to activate version: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -139,12 +147,8 @@ const PackageVersionsList = (props) => {
|
|
|
139
147
|
async function pinVersion() {
|
|
140
148
|
setPinning(true);
|
|
141
149
|
try {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
current.versionFollowRange = "pinned";
|
|
145
|
-
await (0, peers_sdk_1.Packages)().signAndSave(current);
|
|
146
|
-
await pkg.load();
|
|
147
|
-
}
|
|
150
|
+
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, { pinned: true });
|
|
151
|
+
await pkg.load();
|
|
148
152
|
}
|
|
149
153
|
catch (err) {
|
|
150
154
|
alert(`Failed to pin version: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -160,7 +164,7 @@ const PackageVersionsList = (props) => {
|
|
|
160
164
|
if (sorted.length === 0) {
|
|
161
165
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "text-muted text-center p-4", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-tag me-1" }), "No versions found for this package."] }));
|
|
162
166
|
}
|
|
163
|
-
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("small", { className: "text-muted", children: [sorted.length, " version", sorted.length !== 1 ? "s" : ""] }), (0, jsx_runtime_1.jsx)("div", { className: "list-group mt-2", children: sorted.map((pv) => ((0, jsx_runtime_1.jsx)(VersionRow, { pv: pv, isActive: pv.packageVersionId ===
|
|
167
|
+
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("small", { className: "text-muted", children: [sorted.length, " version", sorted.length !== 1 ? "s" : ""] }), (0, jsx_runtime_1.jsx)("div", { className: "list-group mt-2", children: sorted.map((pv) => ((0, jsx_runtime_1.jsx)(VersionRow, { pv: pv, isActive: pv.packageVersionId === effectiveActiveVersionId, isPinned: isPinned, pinning: pinning, activating: activating, deleting: deleting, userMap: userMap, onActivate: activateVersion, onDelete: deleteVersion, onPin: pinVersion, onPromote: promoteVersion, onUpdateVersion: updateVersion }, pv.packageVersionId))) })] }));
|
|
164
168
|
};
|
|
165
169
|
exports.PackageVersionsList = PackageVersionsList;
|
|
166
170
|
/**
|
|
@@ -5,32 +5,89 @@ const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
|
5
5
|
require("../ui-defaults");
|
|
6
6
|
exports.allPackages = (0, peers_sdk_1.observable)([]);
|
|
7
7
|
let allRoutesLoaded = false;
|
|
8
|
+
const prefsSubscribed = new Set();
|
|
9
|
+
function subscribeToPrefs(packageId) {
|
|
10
|
+
if (prefsSubscribed.has(packageId))
|
|
11
|
+
return;
|
|
12
|
+
prefsSubscribed.add(packageId);
|
|
13
|
+
const pvar = (0, peers_sdk_1.packagePrefsVar)(packageId);
|
|
14
|
+
let lastPvId = pvar()?.activePackageVersionId;
|
|
15
|
+
pvar.subscribe(async (prefs) => {
|
|
16
|
+
const newPvId = prefs?.activePackageVersionId;
|
|
17
|
+
if (newPvId && newPvId !== lastPvId) {
|
|
18
|
+
lastPvId = newPvId;
|
|
19
|
+
const pkg = await (0, peers_sdk_1.Packages)().get(packageId);
|
|
20
|
+
if (pkg) {
|
|
21
|
+
const enrichedPkg = await enrichWithPvAppNavs(pkg);
|
|
22
|
+
const _allPackages = (0, exports.allPackages)();
|
|
23
|
+
const idx = _allPackages.findIndex((p) => p.packageId === packageId);
|
|
24
|
+
if (idx >= 0) {
|
|
25
|
+
_allPackages[idx] = enrichedPkg;
|
|
26
|
+
(0, exports.allPackages)([..._allPackages]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
reloadPackage(packageId);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Enrich an IPackage with appNavs from the device's active PV record.
|
|
35
|
+
* Falls back to `pkg.appNavs` for backward compat with older data.
|
|
36
|
+
*/
|
|
37
|
+
async function enrichWithPvAppNavs(pkg) {
|
|
38
|
+
let pvId = pkg.activePackageVersionId;
|
|
39
|
+
try {
|
|
40
|
+
const pvar = (0, peers_sdk_1.packagePrefsVar)(pkg.packageId);
|
|
41
|
+
await pvar.loadingPromise;
|
|
42
|
+
pvId = pvar()?.activePackageVersionId ?? pvId;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* use group default */
|
|
46
|
+
}
|
|
47
|
+
if (!pvId)
|
|
48
|
+
return pkg;
|
|
49
|
+
try {
|
|
50
|
+
const pv = await (0, peers_sdk_1.PackageVersions)().get(pvId);
|
|
51
|
+
if (pv?.appNavs) {
|
|
52
|
+
return { ...pkg, appNavs: pv.appNavs };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
/* fall through to pkg.appNavs */
|
|
57
|
+
}
|
|
58
|
+
return pkg;
|
|
59
|
+
}
|
|
8
60
|
const loadAllRoutes = async () => {
|
|
9
61
|
if (allRoutesLoaded)
|
|
10
62
|
return;
|
|
11
63
|
allRoutesLoaded = true;
|
|
12
|
-
// Filter packages that have an active version
|
|
13
64
|
const packagesWithUI = await (0, peers_sdk_1.Packages)().list({
|
|
14
65
|
disabled: { $ne: true },
|
|
15
66
|
activePackageVersionId: { $exists: true },
|
|
16
67
|
});
|
|
17
|
-
|
|
18
|
-
|
|
68
|
+
const enriched = await Promise.all(packagesWithUI.map(enrichWithPvAppNavs));
|
|
69
|
+
(0, exports.allPackages)(enriched);
|
|
70
|
+
await Promise.all(enriched.map((pkg) => loadRoutesBundle(pkg)));
|
|
71
|
+
for (const pkg of enriched) {
|
|
72
|
+
subscribeToPrefs(pkg.packageId);
|
|
73
|
+
}
|
|
19
74
|
(0, peers_sdk_1.Packages)().dataChanged.subscribe(async (evt) => {
|
|
75
|
+
const enrichedPkg = await enrichWithPvAppNavs(evt.dataObject);
|
|
20
76
|
const _allPackages = (0, exports.allPackages)();
|
|
21
77
|
if (evt.op === "insert") {
|
|
22
|
-
_allPackages.push(
|
|
78
|
+
_allPackages.push(enrichedPkg);
|
|
79
|
+
subscribeToPrefs(evt.dataObject.packageId);
|
|
23
80
|
}
|
|
24
81
|
else if (evt.op === "delete") {
|
|
25
|
-
const idx =
|
|
82
|
+
const idx = _allPackages.findIndex((p) => p.packageId === evt.dataObject.packageId);
|
|
26
83
|
if (idx >= 0) {
|
|
27
84
|
_allPackages.splice(idx, 1);
|
|
28
85
|
}
|
|
29
86
|
}
|
|
30
87
|
else {
|
|
31
|
-
const idx =
|
|
88
|
+
const idx = _allPackages.findIndex((p) => p.packageId === evt.dataObject.packageId);
|
|
32
89
|
if (idx >= 0) {
|
|
33
|
-
_allPackages[idx] =
|
|
90
|
+
_allPackages[idx] = enrichedPkg;
|
|
34
91
|
}
|
|
35
92
|
}
|
|
36
93
|
(0, exports.allPackages)([..._allPackages]);
|
|
@@ -59,10 +116,19 @@ function loadRoutesBundle(pkg, forceRefresh) {
|
|
|
59
116
|
console.log(`waiting for registerPeersUIRoute to be defined on the window object`);
|
|
60
117
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
61
118
|
}
|
|
119
|
+
let pvId = pkg.activePackageVersionId;
|
|
120
|
+
try {
|
|
121
|
+
const pvar = (0, peers_sdk_1.packagePrefsVar)(pkg.packageId);
|
|
122
|
+
await pvar.loadingPromise;
|
|
123
|
+
pvId = pvar()?.activePackageVersionId ?? pvId;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* use group default */
|
|
127
|
+
}
|
|
62
128
|
let routesBundleFileId;
|
|
63
|
-
if (
|
|
129
|
+
if (pvId) {
|
|
64
130
|
try {
|
|
65
|
-
const pv = await (0, peers_sdk_1.PackageVersions)().get(
|
|
131
|
+
const pv = await (0, peers_sdk_1.PackageVersions)().get(pvId);
|
|
66
132
|
routesBundleFileId = pv?.routesBundleFileId;
|
|
67
133
|
}
|
|
68
134
|
catch {
|
|
@@ -190,10 +190,19 @@ const uiLoadingPromises = {};
|
|
|
190
190
|
// Check if we're running in a React Native WebView (has injectUIBundle available)
|
|
191
191
|
const isReactNativeWebView = typeof window.ReactNativeWebView !== "undefined";
|
|
192
192
|
async function resolveUiBundleFileId(pkg) {
|
|
193
|
-
|
|
193
|
+
let pvId = pkg.activePackageVersionId;
|
|
194
|
+
try {
|
|
195
|
+
const pvar = (0, peers_sdk_1.packagePrefsVar)(pkg.packageId);
|
|
196
|
+
await pvar.loadingPromise;
|
|
197
|
+
pvId = pvar()?.activePackageVersionId ?? pvId;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
/* use group default */
|
|
201
|
+
}
|
|
202
|
+
if (!pvId)
|
|
194
203
|
return undefined;
|
|
195
204
|
try {
|
|
196
|
-
const pv = await (0, peers_sdk_1.PackageVersions)().get(
|
|
205
|
+
const pv = await (0, peers_sdk_1.PackageVersions)().get(pvId);
|
|
197
206
|
return pv?.uiBundleFileId;
|
|
198
207
|
}
|
|
199
208
|
catch {
|
|
@@ -415,12 +424,31 @@ const reloadPackage = (0, peers_sdk_1.debounceByArgs)(async (packageId) => {
|
|
|
415
424
|
(0, globals_1.packageReloaded)(Date.now());
|
|
416
425
|
}
|
|
417
426
|
}, 200);
|
|
427
|
+
const uiPrefsSubscribed = new Set();
|
|
428
|
+
function subscribeToPrefs(packageId) {
|
|
429
|
+
if (uiPrefsSubscribed.has(packageId))
|
|
430
|
+
return;
|
|
431
|
+
uiPrefsSubscribed.add(packageId);
|
|
432
|
+
const pvar = (0, peers_sdk_1.packagePrefsVar)(packageId);
|
|
433
|
+
let lastPvId = pvar()?.activePackageVersionId;
|
|
434
|
+
pvar.subscribe((prefs) => {
|
|
435
|
+
const newPvId = prefs?.activePackageVersionId;
|
|
436
|
+
if (newPvId && newPvId !== lastPvId) {
|
|
437
|
+
lastPvId = newPvId;
|
|
438
|
+
reloadPackage(packageId);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
}
|
|
418
442
|
(0, peers_sdk_1.getUserContext)().then((_userContext) => {
|
|
443
|
+
for (const packageId of Object.keys(uiLoadingPromises)) {
|
|
444
|
+
subscribeToPrefs(packageId);
|
|
445
|
+
}
|
|
419
446
|
(0, peers_sdk_1.Packages)().dataChanged.subscribe(async (evt) => {
|
|
420
447
|
const pkg = evt.dataObject;
|
|
421
448
|
const loadingPromise = uiLoadingPromises[pkg.packageId];
|
|
422
449
|
if (!loadingPromise)
|
|
423
450
|
return;
|
|
451
|
+
subscribeToPrefs(pkg.packageId);
|
|
424
452
|
reloadPackage(pkg.packageId);
|
|
425
453
|
});
|
|
426
454
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.6",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-ui.git"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"lint:fix": "biome check --write ."
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@peers-app/peers-sdk": "^0.18.
|
|
31
|
+
"@peers-app/peers-sdk": "^0.18.6",
|
|
32
32
|
"bootstrap": "^5.3.3",
|
|
33
33
|
"react": "^18.0.0",
|
|
34
34
|
"react-dom": "^18.0.0"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@babel/preset-env": "^7.24.5",
|
|
40
40
|
"@babel/preset-react": "^7.24.1",
|
|
41
41
|
"@babel/preset-typescript": "^7.27.1",
|
|
42
|
-
"@peers-app/peers-sdk": "0.18.
|
|
42
|
+
"@peers-app/peers-sdk": "0.18.6",
|
|
43
43
|
"@testing-library/dom": "^10.4.0",
|
|
44
44
|
"@testing-library/jest-dom": "^6.6.3",
|
|
45
45
|
"@testing-library/react": "^16.3.0",
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
doesTagMatch,
|
|
3
|
+
getEffectivePackagePrefs,
|
|
3
4
|
groupDeviceVar,
|
|
4
5
|
type IDoc,
|
|
5
6
|
type IPackage,
|
|
6
7
|
type IPackageVersion,
|
|
7
8
|
PackageVersions,
|
|
9
|
+
packagePrefsVar,
|
|
8
10
|
packagesRootDir,
|
|
9
11
|
rpcServerCalls,
|
|
12
|
+
updatePackagePrefs,
|
|
10
13
|
} from "@peers-app/peers-sdk";
|
|
11
14
|
import React from "react";
|
|
12
15
|
import { Input } from "../../components/input";
|
|
@@ -14,33 +17,35 @@ import { MarkdownWithMentions } from "../../components/markdown-with-mentions";
|
|
|
14
17
|
import { Tooltip } from "../../components/tooltip";
|
|
15
18
|
import { useObservable, usePromise } from "../../hooks";
|
|
16
19
|
|
|
17
|
-
const deviceVersionTagVar = groupDeviceVar<string | undefined>("deviceVersionTag");
|
|
18
|
-
|
|
19
20
|
export const PackageInfo = (props: {
|
|
20
21
|
pkg: IDoc<IPackage>;
|
|
21
22
|
saveDeviceTagRef?: React.MutableRefObject<(() => Promise<void>) | null>;
|
|
22
23
|
}) => {
|
|
23
24
|
const { pkg } = props;
|
|
24
|
-
const [activeVersionId] = useObservable(pkg.qs.activePackageVersionId);
|
|
25
|
-
const [versionFollowRange] = useObservable(pkg.qs.versionFollowRange);
|
|
26
25
|
const [followVersionTags] = useObservable(pkg.qs.followVersionTags);
|
|
27
|
-
const [deviceTag] = useObservable(deviceVersionTagVar);
|
|
28
26
|
|
|
29
27
|
const localPathVar = groupDeviceVar<string>(`packageLocalPath_${pkg.packageId}`, {
|
|
30
28
|
defaultValue: `${packagesRootDir}/${pkg.name}`,
|
|
31
29
|
});
|
|
32
30
|
const [localPath, setLocalPath] = useObservable(localPathVar);
|
|
33
31
|
|
|
34
|
-
const [
|
|
32
|
+
const [devicePrefs] = useObservable(packagePrefsVar(pkg.packageId));
|
|
33
|
+
const effective = getEffectivePackagePrefs(pkg.toJS(), devicePrefs);
|
|
34
|
+
const activeVersionId = effective.activePackageVersionId;
|
|
35
|
+
const deviceFollowTags = devicePrefs?.followTags;
|
|
36
|
+
const isPinned = effective.isPinned;
|
|
37
|
+
const followRange = effective.followRange;
|
|
38
|
+
|
|
39
|
+
const [deviceTagDraft, setDeviceTagDraft] = React.useState(deviceFollowTags || "");
|
|
35
40
|
const savingRef = React.useRef(false);
|
|
36
41
|
|
|
37
42
|
React.useEffect(() => {
|
|
38
43
|
if (!savingRef.current) {
|
|
39
|
-
setDeviceTagDraft(
|
|
44
|
+
setDeviceTagDraft(deviceFollowTags || "");
|
|
40
45
|
}
|
|
41
|
-
}, [
|
|
46
|
+
}, [deviceFollowTags]);
|
|
42
47
|
|
|
43
|
-
const deviceTagDirty = deviceTagDraft.trim() !== (
|
|
48
|
+
const deviceTagDirty = deviceTagDraft.trim() !== (deviceFollowTags || "");
|
|
44
49
|
const prevDirtyRef = React.useRef(false);
|
|
45
50
|
React.useEffect(() => {
|
|
46
51
|
if (deviceTagDirty && !prevDirtyRef.current) {
|
|
@@ -54,14 +59,12 @@ export const PackageInfo = (props: {
|
|
|
54
59
|
if (props.saveDeviceTagRef) {
|
|
55
60
|
props.saveDeviceTagRef.current = async () => {
|
|
56
61
|
const val = deviceTagDraft.trim();
|
|
57
|
-
if (val !== (
|
|
62
|
+
if (val !== (deviceFollowTags || "")) {
|
|
58
63
|
savingRef.current = true;
|
|
59
64
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
await deviceVersionTagVar.delete();
|
|
64
|
-
}
|
|
65
|
+
await updatePackagePrefs(pkg.packageId, {
|
|
66
|
+
followTags: val || undefined,
|
|
67
|
+
});
|
|
65
68
|
} finally {
|
|
66
69
|
savingRef.current = false;
|
|
67
70
|
}
|
|
@@ -84,12 +87,14 @@ export const PackageInfo = (props: {
|
|
|
84
87
|
const all = await PackageVersions().list({ packageId: pkg.packageId });
|
|
85
88
|
const active = all.find((v) => v.packageVersionId === activeVersionId);
|
|
86
89
|
if (!active?.version) return "uptodate";
|
|
90
|
+
const effectiveFollowTags = deviceFollowTags || followVersionTags;
|
|
87
91
|
const parse = (v: string) => v.split(".").map(Number);
|
|
88
92
|
const [aMaj, aMin, aPat] = parse(active.version);
|
|
89
93
|
let highest: "major" | "minor" | "patch" | null = null;
|
|
90
94
|
for (const v of all) {
|
|
91
95
|
if (!v.version || v.packageVersionId === activeVersionId) continue;
|
|
92
|
-
if (!doesTagMatch(active.versionTag, v.versionTag,
|
|
96
|
+
if (!doesTagMatch(active.versionTag, v.versionTag, effectiveFollowTags, undefined))
|
|
97
|
+
continue;
|
|
93
98
|
const [maj, min, pat] = parse(v.version);
|
|
94
99
|
if (maj > aMaj) return "major";
|
|
95
100
|
if (maj === aMaj && min > aMin) {
|
|
@@ -113,10 +118,9 @@ export const PackageInfo = (props: {
|
|
|
113
118
|
return highest || "uptodate";
|
|
114
119
|
},
|
|
115
120
|
undefined,
|
|
116
|
-
[activeVersionId, pkg.packageId, followVersionTags,
|
|
121
|
+
[activeVersionId, pkg.packageId, followVersionTags, deviceFollowTags],
|
|
117
122
|
);
|
|
118
123
|
|
|
119
|
-
const isPinned = versionFollowRange === "pinned";
|
|
120
124
|
const remoteRepoUrl = pkg.remoteRepo;
|
|
121
125
|
|
|
122
126
|
return (
|
|
@@ -213,15 +217,18 @@ export const PackageInfo = (props: {
|
|
|
213
217
|
|
|
214
218
|
<div className="mb-3">
|
|
215
219
|
<small>
|
|
216
|
-
Auto-Update Range:
|
|
217
|
-
<Tooltip markdownContent="Controls which new versions are auto-activated. **Pinned** = never auto-update. **Patch** = same major.minor (e.g. 1.2.x). **Minor** = same major (e.g. 1.x.x). **Latest** = always auto-update to newest." />
|
|
220
|
+
Auto-Update Range (this device):
|
|
221
|
+
<Tooltip markdownContent="Controls which new versions are auto-activated on this device. **Pinned** = never auto-update. **Patch** = same major.minor (e.g. 1.2.x). **Minor** = same major (e.g. 1.x.x). **Latest** = always auto-update to newest." />
|
|
218
222
|
</small>
|
|
219
223
|
<select
|
|
220
224
|
className="form-select form-select-sm"
|
|
221
|
-
value={
|
|
222
|
-
onChange={(e) => {
|
|
225
|
+
value={isPinned ? "pinned" : followRange}
|
|
226
|
+
onChange={async (e) => {
|
|
223
227
|
const val = e.target.value as "pinned" | "patch" | "minor" | "latest";
|
|
224
|
-
pkg.
|
|
228
|
+
await updatePackagePrefs(pkg.packageId, {
|
|
229
|
+
pinned: val === "pinned",
|
|
230
|
+
followRange: val === "pinned" ? undefined : val,
|
|
231
|
+
});
|
|
225
232
|
}}
|
|
226
233
|
>
|
|
227
234
|
<option value="latest">Latest (auto-update to newest)</option>
|
|
@@ -233,29 +240,17 @@ export const PackageInfo = (props: {
|
|
|
233
240
|
|
|
234
241
|
<div className={`mb-3 ${isPinned ? "opacity-50" : ""}`}>
|
|
235
242
|
<small>
|
|
236
|
-
Follow Version Tags:
|
|
237
|
-
<Tooltip markdownContent="Which version tags to auto-activate. Leave empty
|
|
238
|
-
</small>
|
|
239
|
-
<Input
|
|
240
|
-
value={pkg.qs.followVersionTags}
|
|
241
|
-
className="form-control form-control-sm p-0 ps-2"
|
|
242
|
-
placeholder={`Following "${activeVersion?.versionTag || "stable"}"`}
|
|
243
|
-
disabled={isPinned}
|
|
244
|
-
/>
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
<div className="mb-2">
|
|
248
|
-
<small>
|
|
249
|
-
Device-Specific Version Tag:
|
|
250
|
-
<Tooltip markdownContent="Override the tag policy for this device only. For example, set to `beta` to test pre-release versions on this device without affecting other devices or group members. This is not synced." />
|
|
243
|
+
Follow Version Tags (this device):
|
|
244
|
+
<Tooltip markdownContent="Which version tags to auto-activate on this device. Leave empty to follow the group's tag policy. Use `*` for any tag, or a comma-separated list like `stable,beta`." />
|
|
251
245
|
</small>
|
|
252
246
|
<div className="d-flex align-items-center gap-2">
|
|
253
247
|
<input
|
|
254
248
|
type="text"
|
|
255
249
|
className="form-control form-control-sm p-0 ps-2"
|
|
256
|
-
placeholder="
|
|
250
|
+
placeholder={`Following "${activeVersion?.versionTag || "stable"}"`}
|
|
257
251
|
value={deviceTagDraft}
|
|
258
252
|
onChange={(e) => setDeviceTagDraft(e.target.value)}
|
|
253
|
+
disabled={isPinned}
|
|
259
254
|
/>
|
|
260
255
|
{deviceTagDraft && (
|
|
261
256
|
<button
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
computePackageVersionHash,
|
|
3
|
+
getEffectivePackagePrefs,
|
|
3
4
|
getMe,
|
|
4
5
|
type IDoc,
|
|
5
6
|
type IPackage,
|
|
6
7
|
type IPackageVersion,
|
|
7
8
|
Packages,
|
|
8
9
|
PackageVersions,
|
|
10
|
+
packagePrefsVar,
|
|
9
11
|
Users,
|
|
12
|
+
updatePackagePrefs,
|
|
10
13
|
} from "@peers-app/peers-sdk";
|
|
11
14
|
import { useState } from "react";
|
|
12
15
|
import { useObservable, useObservableState, usePromise } from "../../hooks";
|
|
@@ -39,13 +42,15 @@ function tagBadgeClass(tag?: string): string {
|
|
|
39
42
|
|
|
40
43
|
export const PackageVersionsList = (props: { pkg: IDoc<IPackage> }) => {
|
|
41
44
|
const { pkg } = props;
|
|
42
|
-
const [activeVersionId] = useObservable(pkg.qs.activePackageVersionId);
|
|
43
|
-
const [versionFollowRange] = useObservable(pkg.qs.versionFollowRange);
|
|
44
45
|
const refreshKey = useObservableState(0);
|
|
45
46
|
const [activating, setActivating] = useState<string | null>(null);
|
|
46
47
|
const [deleting, setDeleting] = useState<string | null>(null);
|
|
47
48
|
const [pinning, setPinning] = useState(false);
|
|
48
|
-
|
|
49
|
+
|
|
50
|
+
const [devicePrefs] = useObservable(packagePrefsVar(pkg.packageId));
|
|
51
|
+
const effective = getEffectivePackagePrefs(pkg.toJS(), devicePrefs);
|
|
52
|
+
const effectiveActiveVersionId = effective.activePackageVersionId;
|
|
53
|
+
const isPinned = effective.isPinned;
|
|
49
54
|
|
|
50
55
|
const versions = usePromise(
|
|
51
56
|
async () => {
|
|
@@ -78,13 +83,21 @@ export const PackageVersionsList = (props: { pkg: IDoc<IPackage> }) => {
|
|
|
78
83
|
async function activateVersion(pv: IPackageVersion) {
|
|
79
84
|
setActivating(pv.packageVersionId);
|
|
80
85
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
await updatePackagePrefs(pkg.packageId, {
|
|
87
|
+
activePackageVersionId: pv.packageVersionId,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// For non-dev versions, also update the group default so other devices
|
|
91
|
+
// can discover this as the recommended version.
|
|
92
|
+
if (pv.versionTag !== "dev") {
|
|
93
|
+
const current = await Packages().get(pkg.packageId);
|
|
94
|
+
if (current) {
|
|
95
|
+
current.activePackageVersionId = pv.packageVersionId;
|
|
96
|
+
await Packages().signAndSave(current);
|
|
97
|
+
}
|
|
87
98
|
}
|
|
99
|
+
await pkg.load();
|
|
100
|
+
refreshKey(refreshKey() + 1);
|
|
88
101
|
} catch (err: unknown) {
|
|
89
102
|
alert(`Failed to activate version: ${err instanceof Error ? err.message : String(err)}`);
|
|
90
103
|
} finally {
|
|
@@ -151,12 +164,8 @@ export const PackageVersionsList = (props: { pkg: IDoc<IPackage> }) => {
|
|
|
151
164
|
async function pinVersion() {
|
|
152
165
|
setPinning(true);
|
|
153
166
|
try {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
current.versionFollowRange = "pinned";
|
|
157
|
-
await Packages().signAndSave(current);
|
|
158
|
-
await pkg.load();
|
|
159
|
-
}
|
|
167
|
+
await updatePackagePrefs(pkg.packageId, { pinned: true });
|
|
168
|
+
await pkg.load();
|
|
160
169
|
} catch (err: unknown) {
|
|
161
170
|
alert(`Failed to pin version: ${err instanceof Error ? err.message : String(err)}`);
|
|
162
171
|
} finally {
|
|
@@ -193,7 +202,7 @@ export const PackageVersionsList = (props: { pkg: IDoc<IPackage> }) => {
|
|
|
193
202
|
<VersionRow
|
|
194
203
|
key={pv.packageVersionId}
|
|
195
204
|
pv={pv}
|
|
196
|
-
isActive={pv.packageVersionId ===
|
|
205
|
+
isActive={pv.packageVersionId === effectiveActiveVersionId}
|
|
197
206
|
isPinned={isPinned}
|
|
198
207
|
pinning={pinning}
|
|
199
208
|
activating={activating}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
observable,
|
|
7
7
|
Packages,
|
|
8
8
|
PackageVersions,
|
|
9
|
+
packagePrefsVar,
|
|
9
10
|
rpcServerCalls,
|
|
10
11
|
} from "@peers-app/peers-sdk";
|
|
11
12
|
import "../ui-defaults";
|
|
@@ -13,30 +14,87 @@ import "../ui-defaults";
|
|
|
13
14
|
export const allPackages = observable<IPackage[]>([]);
|
|
14
15
|
let allRoutesLoaded = false;
|
|
15
16
|
|
|
17
|
+
const prefsSubscribed = new Set<string>();
|
|
18
|
+
|
|
19
|
+
function subscribeToPrefs(packageId: string) {
|
|
20
|
+
if (prefsSubscribed.has(packageId)) return;
|
|
21
|
+
prefsSubscribed.add(packageId);
|
|
22
|
+
const pvar = packagePrefsVar(packageId);
|
|
23
|
+
let lastPvId = pvar()?.activePackageVersionId;
|
|
24
|
+
pvar.subscribe(async (prefs) => {
|
|
25
|
+
const newPvId = prefs?.activePackageVersionId;
|
|
26
|
+
if (newPvId && newPvId !== lastPvId) {
|
|
27
|
+
lastPvId = newPvId;
|
|
28
|
+
const pkg = await Packages().get(packageId);
|
|
29
|
+
if (pkg) {
|
|
30
|
+
const enrichedPkg = await enrichWithPvAppNavs(pkg);
|
|
31
|
+
const _allPackages = allPackages();
|
|
32
|
+
const idx = _allPackages.findIndex((p) => p.packageId === packageId);
|
|
33
|
+
if (idx >= 0) {
|
|
34
|
+
_allPackages[idx] = enrichedPkg;
|
|
35
|
+
allPackages([..._allPackages]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
reloadPackage(packageId);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Enrich an IPackage with appNavs from the device's active PV record.
|
|
45
|
+
* Falls back to `pkg.appNavs` for backward compat with older data.
|
|
46
|
+
*/
|
|
47
|
+
async function enrichWithPvAppNavs(pkg: IPackage): Promise<IPackage> {
|
|
48
|
+
let pvId = pkg.activePackageVersionId;
|
|
49
|
+
try {
|
|
50
|
+
const pvar = packagePrefsVar(pkg.packageId);
|
|
51
|
+
await pvar.loadingPromise;
|
|
52
|
+
pvId = pvar()?.activePackageVersionId ?? pvId;
|
|
53
|
+
} catch {
|
|
54
|
+
/* use group default */
|
|
55
|
+
}
|
|
56
|
+
if (!pvId) return pkg;
|
|
57
|
+
try {
|
|
58
|
+
const pv = await PackageVersions().get(pvId);
|
|
59
|
+
if (pv?.appNavs) {
|
|
60
|
+
return { ...pkg, appNavs: pv.appNavs };
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
/* fall through to pkg.appNavs */
|
|
64
|
+
}
|
|
65
|
+
return pkg;
|
|
66
|
+
}
|
|
67
|
+
|
|
16
68
|
export const loadAllRoutes = async () => {
|
|
17
69
|
if (allRoutesLoaded) return;
|
|
18
70
|
allRoutesLoaded = true;
|
|
19
|
-
// Filter packages that have an active version
|
|
20
71
|
const packagesWithUI = await Packages().list({
|
|
21
72
|
disabled: { $ne: true },
|
|
22
73
|
activePackageVersionId: { $exists: true },
|
|
23
74
|
});
|
|
24
|
-
|
|
25
|
-
|
|
75
|
+
const enriched = await Promise.all(packagesWithUI.map(enrichWithPvAppNavs));
|
|
76
|
+
allPackages(enriched);
|
|
77
|
+
await Promise.all(enriched.map((pkg) => loadRoutesBundle(pkg)));
|
|
78
|
+
|
|
79
|
+
for (const pkg of enriched) {
|
|
80
|
+
subscribeToPrefs(pkg.packageId);
|
|
81
|
+
}
|
|
26
82
|
|
|
27
83
|
Packages().dataChanged.subscribe(async (evt) => {
|
|
84
|
+
const enrichedPkg = await enrichWithPvAppNavs(evt.dataObject);
|
|
28
85
|
const _allPackages = allPackages();
|
|
29
86
|
if (evt.op === "insert") {
|
|
30
|
-
_allPackages.push(
|
|
87
|
+
_allPackages.push(enrichedPkg);
|
|
88
|
+
subscribeToPrefs(evt.dataObject.packageId);
|
|
31
89
|
} else if (evt.op === "delete") {
|
|
32
|
-
const idx =
|
|
90
|
+
const idx = _allPackages.findIndex((p) => p.packageId === evt.dataObject.packageId);
|
|
33
91
|
if (idx >= 0) {
|
|
34
92
|
_allPackages.splice(idx, 1);
|
|
35
93
|
}
|
|
36
94
|
} else {
|
|
37
|
-
const idx =
|
|
95
|
+
const idx = _allPackages.findIndex((p) => p.packageId === evt.dataObject.packageId);
|
|
38
96
|
if (idx >= 0) {
|
|
39
|
-
_allPackages[idx] =
|
|
97
|
+
_allPackages[idx] = enrichedPkg;
|
|
40
98
|
}
|
|
41
99
|
}
|
|
42
100
|
allPackages([..._allPackages]);
|
|
@@ -72,10 +130,19 @@ function loadRoutesBundle(pkg: IPackage, forceRefresh?: boolean): Promise<unknow
|
|
|
72
130
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
73
131
|
}
|
|
74
132
|
|
|
133
|
+
let pvId = pkg.activePackageVersionId;
|
|
134
|
+
try {
|
|
135
|
+
const pvar = packagePrefsVar(pkg.packageId);
|
|
136
|
+
await pvar.loadingPromise;
|
|
137
|
+
pvId = pvar()?.activePackageVersionId ?? pvId;
|
|
138
|
+
} catch {
|
|
139
|
+
/* use group default */
|
|
140
|
+
}
|
|
141
|
+
|
|
75
142
|
let routesBundleFileId: string | undefined;
|
|
76
|
-
if (
|
|
143
|
+
if (pvId) {
|
|
77
144
|
try {
|
|
78
|
-
const pv = await PackageVersions().get(
|
|
145
|
+
const pv = await PackageVersions().get(pvId);
|
|
79
146
|
routesBundleFileId = pv?.routesBundleFileId;
|
|
80
147
|
} catch {
|
|
81
148
|
/* no version record */
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
newid,
|
|
9
9
|
Packages,
|
|
10
10
|
PackageVersions,
|
|
11
|
+
packagePrefsVar,
|
|
11
12
|
rpcServerCalls,
|
|
12
13
|
toJSON,
|
|
13
14
|
type UIContext,
|
|
@@ -283,9 +284,17 @@ const isReactNativeWebView =
|
|
|
283
284
|
typeof (window as Window & { ReactNativeWebView?: unknown }).ReactNativeWebView !== "undefined";
|
|
284
285
|
|
|
285
286
|
async function resolveUiBundleFileId(pkg: IPackage): Promise<string | undefined> {
|
|
286
|
-
|
|
287
|
+
let pvId = pkg.activePackageVersionId;
|
|
287
288
|
try {
|
|
288
|
-
const
|
|
289
|
+
const pvar = packagePrefsVar(pkg.packageId);
|
|
290
|
+
await pvar.loadingPromise;
|
|
291
|
+
pvId = pvar()?.activePackageVersionId ?? pvId;
|
|
292
|
+
} catch {
|
|
293
|
+
/* use group default */
|
|
294
|
+
}
|
|
295
|
+
if (!pvId) return undefined;
|
|
296
|
+
try {
|
|
297
|
+
const pv = await PackageVersions().get(pvId);
|
|
289
298
|
return pv?.uiBundleFileId;
|
|
290
299
|
} catch {
|
|
291
300
|
return undefined;
|
|
@@ -628,11 +637,32 @@ const reloadPackage = debounceByArgs(async (packageId: string) => {
|
|
|
628
637
|
}
|
|
629
638
|
}, 200);
|
|
630
639
|
|
|
640
|
+
const uiPrefsSubscribed = new Set<string>();
|
|
641
|
+
|
|
642
|
+
function subscribeToPrefs(packageId: string) {
|
|
643
|
+
if (uiPrefsSubscribed.has(packageId)) return;
|
|
644
|
+
uiPrefsSubscribed.add(packageId);
|
|
645
|
+
const pvar = packagePrefsVar(packageId);
|
|
646
|
+
let lastPvId = pvar()?.activePackageVersionId;
|
|
647
|
+
pvar.subscribe((prefs) => {
|
|
648
|
+
const newPvId = prefs?.activePackageVersionId;
|
|
649
|
+
if (newPvId && newPvId !== lastPvId) {
|
|
650
|
+
lastPvId = newPvId;
|
|
651
|
+
reloadPackage(packageId);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
631
656
|
getUserContext().then((_userContext) => {
|
|
657
|
+
for (const packageId of Object.keys(uiLoadingPromises)) {
|
|
658
|
+
subscribeToPrefs(packageId);
|
|
659
|
+
}
|
|
660
|
+
|
|
632
661
|
Packages().dataChanged.subscribe(async (evt) => {
|
|
633
662
|
const pkg = evt.dataObject;
|
|
634
663
|
const loadingPromise = uiLoadingPromises[pkg.packageId];
|
|
635
664
|
if (!loadingPromise) return;
|
|
665
|
+
subscribeToPrefs(pkg.packageId);
|
|
636
666
|
reloadPackage(pkg.packageId);
|
|
637
667
|
});
|
|
638
668
|
});
|