@peers-app/peers-ui 0.19.0 → 0.19.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/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/screens/packages/package-details.d.ts +1 -0
- package/dist/screens/packages/package-details.js +2 -7
- package/dist/screens/packages/package-helpers.d.ts +33 -0
- package/dist/screens/packages/package-helpers.js +110 -0
- package/dist/screens/packages/package-info.d.ts +1 -2
- package/dist/screens/packages/package-info.js +52 -47
- package/dist/screens/packages/package-list.js +140 -45
- package/dist/screens/packages/package-versions.js +13 -13
- package/dist/system-apps/index.d.ts +3 -2
- package/dist/tabs-layout/tabs-state.d.ts +3 -2
- package/dist/ui-router/routes-loader.d.ts +6 -20
- package/dist/ui-router/routes-loader.js +2 -3
- package/package.json +3 -3
- package/src/index.tsx +1 -0
- package/src/screens/packages/package-details.tsx +2 -4
- package/src/screens/packages/package-helpers.ts +140 -0
- package/src/screens/packages/package-info.tsx +122 -67
- package/src/screens/packages/package-list.tsx +247 -88
- package/src/screens/packages/package-versions.tsx +14 -14
- package/src/system-apps/index.ts +3 -2
- package/src/tabs-layout/tabs-state.ts +3 -4
- package/src/ui-router/routes-loader.ts +9 -6
|
@@ -11,56 +11,119 @@ const tooltip_1 = require("../../components/tooltip");
|
|
|
11
11
|
const globals_1 = require("../../globals");
|
|
12
12
|
const hooks_1 = require("../../hooks");
|
|
13
13
|
const ui_loader_1 = require("../../ui-router/ui-loader");
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const package_helpers_1 = require("./package-helpers");
|
|
15
|
+
function tagBadgeClass(tag) {
|
|
16
|
+
if (tag === "dev")
|
|
17
|
+
return "text-bg-danger";
|
|
18
|
+
if (tag.startsWith("beta"))
|
|
19
|
+
return "text-bg-warning";
|
|
20
|
+
return "text-bg-success";
|
|
21
|
+
}
|
|
22
|
+
function updateBadgeClass(level) {
|
|
23
|
+
if (level === "major")
|
|
24
|
+
return "text-bg-danger";
|
|
25
|
+
if (level === "minor")
|
|
26
|
+
return "text-bg-warning";
|
|
27
|
+
return "text-bg-info";
|
|
28
|
+
}
|
|
29
|
+
const PackageRow = ({ pkg, onUpdated }) => {
|
|
30
|
+
const [updating, setUpdating] = (0, react_1.useState)(false);
|
|
31
|
+
const [devicePrefs] = (0, hooks_1.useObservable)((0, peers_sdk_1.packagePrefsVar)(pkg.packageId));
|
|
32
|
+
const effective = (0, peers_sdk_1.getEffectivePackagePrefs)(pkg, devicePrefs);
|
|
33
|
+
const status = (0, hooks_1.usePromise)(async () => {
|
|
34
|
+
if (!effective.activePackageVersionId)
|
|
17
35
|
return null;
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
if (!active)
|
|
36
|
+
const s = await (0, package_helpers_1.checkVersionStatus)(pkg.packageId, effective.activePackageVersionId, effective.followTags);
|
|
37
|
+
if (!s)
|
|
21
38
|
return null;
|
|
22
|
-
let
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
if (maj === aMaj && min === aMin && pat > aPat && !newerLevel) {
|
|
41
|
-
newerLevel = "patch";
|
|
39
|
+
let groupReleasePv = null;
|
|
40
|
+
if (s.activePv.versionTag === "dev" &&
|
|
41
|
+
pkg.activePackageVersionId &&
|
|
42
|
+
pkg.activePackageVersionId !== effective.activePackageVersionId) {
|
|
43
|
+
try {
|
|
44
|
+
const gpv = await (0, peers_sdk_1.PackageVersions)().get(pkg.activePackageVersionId);
|
|
45
|
+
if (gpv && gpv.versionTag !== "dev")
|
|
46
|
+
groupReleasePv = gpv;
|
|
47
|
+
}
|
|
48
|
+
catch { }
|
|
49
|
+
}
|
|
50
|
+
let latestDevPv = null;
|
|
51
|
+
if (s.activePv.versionTag !== "dev") {
|
|
52
|
+
const allVersions = await (0, peers_sdk_1.PackageVersions)().list({ packageId: pkg.packageId });
|
|
53
|
+
for (const v of allVersions) {
|
|
54
|
+
if (v.versionTag !== "dev")
|
|
42
55
|
continue;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
min === aMin &&
|
|
46
|
-
pat === aPat &&
|
|
47
|
-
(v.createdAt || "") > (active.createdAt || "") &&
|
|
48
|
-
!newerLevel) {
|
|
49
|
-
newerLevel = "patch";
|
|
56
|
+
if (!latestDevPv || (v.createdAt || "") > (latestDevPv.createdAt || "")) {
|
|
57
|
+
latestDevPv = v;
|
|
50
58
|
}
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
|
-
return {
|
|
54
|
-
}, undefined, [
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
return { ...s, groupReleasePv, latestDevPv };
|
|
62
|
+
}, undefined, [
|
|
63
|
+
effective.activePackageVersionId,
|
|
64
|
+
pkg.packageId,
|
|
65
|
+
effective.followTags,
|
|
66
|
+
pkg.activePackageVersionId,
|
|
67
|
+
]);
|
|
68
|
+
const isOnDev = status?.activePv.versionTag === "dev";
|
|
69
|
+
const hasGroupRelease = !!status?.groupReleasePv;
|
|
70
|
+
const hasUpdate = !isOnDev && !!status?.newerLevel && !!status?.newestPv;
|
|
71
|
+
const hasDevVersion = !isOnDev && !!status?.latestDevPv;
|
|
72
|
+
const isDeviceOverridden = !!effective.activePackageVersionId &&
|
|
73
|
+
!!pkg.activePackageVersionId &&
|
|
74
|
+
effective.activePackageVersionId !== pkg.activePackageVersionId;
|
|
75
|
+
async function handleUpdate() {
|
|
76
|
+
if (!status?.newestPv)
|
|
77
|
+
return;
|
|
78
|
+
setUpdating(true);
|
|
79
|
+
try {
|
|
80
|
+
await (0, package_helpers_1.activatePackageVersion)(pkg.packageId, status.newestPv);
|
|
81
|
+
onUpdated();
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
alert(`Failed to update: ${err instanceof Error ? err.message : String(err)}`);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
setUpdating(false);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function handleUseRelease() {
|
|
91
|
+
setUpdating(true);
|
|
92
|
+
try {
|
|
93
|
+
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, { activePackageVersionId: undefined });
|
|
94
|
+
onUpdated();
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
alert(`Failed to switch: ${err instanceof Error ? err.message : String(err)}`);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
setUpdating(false);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function handleUseDev() {
|
|
104
|
+
if (!status?.latestDevPv)
|
|
105
|
+
return;
|
|
106
|
+
setUpdating(true);
|
|
107
|
+
try {
|
|
108
|
+
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, {
|
|
109
|
+
activePackageVersionId: status.latestDevPv.packageVersionId,
|
|
110
|
+
});
|
|
111
|
+
onUpdated();
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
alert(`Failed to switch: ${err instanceof Error ? err.message : String(err)}`);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
setUpdating(false);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid pb-4 d-flex align-items-center", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex-grow-1", style: { minWidth: 0 }, children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-fill" }), "\u00A0\u00A0", (0, jsx_runtime_1.jsx)("a", { href: `#packages/${pkg.packageId}`, children: pkg.name }), isDeviceOverridden && ((0, jsx_runtime_1.jsx)("span", { className: "text-warning ms-1", title: "This device is using a different version than the group", children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-pc-display" }) })), status && ((0, jsx_runtime_1.jsxs)("span", { className: "ms-2", children: [(0, jsx_runtime_1.jsxs)("small", { className: "text-muted", children: ["v", status.activePv.version] }), status.activePv.versionTag && ((0, jsx_runtime_1.jsx)("span", { className: `badge ms-1 ${tagBadgeClass(status.activePv.versionTag)}`, style: { fontSize: "0.65em" }, children: status.activePv.versionTag })), hasUpdate ? ((0, jsx_runtime_1.jsxs)("span", { className: `badge ms-1 ${updateBadgeClass(status.newerLevel)}`, style: { fontSize: "0.65em" }, children: ["v", status.newestPv?.version, " available"] })) : isOnDev && hasGroupRelease ? ((0, jsx_runtime_1.jsxs)("span", { className: "badge ms-1 text-bg-secondary", style: { fontSize: "0.65em" }, children: ["release: v", status.groupReleasePv?.version] })) : ((0, jsx_runtime_1.jsx)("span", { className: "badge ms-1 text-bg-success", style: { fontSize: "0.65em" }, children: "up to date" }))] })), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: pkg.description, positions: ["bottom", "top", "right", "left"] })] }), hasUpdate && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-primary ms-2 text-nowrap", disabled: updating, onClick: handleUpdate, children: [updating ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-arrow-up-circle me-1" })), "Update"] })), isOnDev && hasGroupRelease && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-secondary ms-2 text-nowrap", disabled: updating, onClick: handleUseRelease, children: [updating ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-arrow-in-right me-1" })), "Use Release"] })), hasDevVersion && ((0, jsx_runtime_1.jsxs)("button", { className: "btn btn-sm btn-outline-danger ms-2 text-nowrap", disabled: updating, onClick: handleUseDev, children: [updating ? ((0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1" })) : ((0, jsx_runtime_1.jsx)("i", { className: "bi bi-tools me-1" })), "Use Dev"] }))] }));
|
|
59
121
|
};
|
|
60
122
|
const PackageList = () => {
|
|
61
123
|
const [searchTextObs] = (0, react_1.useState)(() => (0, peers_sdk_1.observable)(""));
|
|
62
124
|
const [searchText] = (0, hooks_1.useObservable)(searchTextObs);
|
|
63
125
|
const addingPackage = (0, hooks_1.useObservableState)(false);
|
|
126
|
+
const refreshKey = (0, hooks_1.useObservableState)(0);
|
|
64
127
|
const [cursorObs] = (0, react_1.useState)(() => (0, peers_sdk_1.observable)());
|
|
65
128
|
const newCursor = (0, react_1.useCallback)(async () => {
|
|
66
129
|
const cursor = await (0, peers_sdk_1.Packages)().cursor({
|
|
@@ -91,6 +154,42 @@ const PackageList = () => {
|
|
|
91
154
|
if (moreMatches.length === 0) {
|
|
92
155
|
cursorObs(undefined);
|
|
93
156
|
}
|
|
157
|
+
if (!searchText.length && existing.length === 0 && moreMatches.length > 0) {
|
|
158
|
+
const DEV_WITH_RELEASE_RANK = 0.5;
|
|
159
|
+
const rankMap = new Map();
|
|
160
|
+
await Promise.all(moreMatches.map(async (pkg) => {
|
|
161
|
+
try {
|
|
162
|
+
const pVar = (0, peers_sdk_1.packagePrefsVar)(pkg.packageId);
|
|
163
|
+
await pVar.loadingPromise;
|
|
164
|
+
const eff = (0, peers_sdk_1.getEffectivePackagePrefs)(pkg, pVar());
|
|
165
|
+
if (!eff.activePackageVersionId)
|
|
166
|
+
return;
|
|
167
|
+
const s = await (0, package_helpers_1.checkVersionStatus)(pkg.packageId, eff.activePackageVersionId, eff.followTags);
|
|
168
|
+
if (!s)
|
|
169
|
+
return;
|
|
170
|
+
if (s.newerLevel) {
|
|
171
|
+
rankMap.set(pkg.packageId, package_helpers_1.updateLevelRank[s.newerLevel]);
|
|
172
|
+
}
|
|
173
|
+
else if (s.activePv.versionTag === "dev" && pkg.activePackageVersionId) {
|
|
174
|
+
try {
|
|
175
|
+
const gpv = await (0, peers_sdk_1.PackageVersions)().get(pkg.activePackageVersionId);
|
|
176
|
+
if (gpv && gpv.versionTag !== "dev") {
|
|
177
|
+
rankMap.set(pkg.packageId, DEV_WITH_RELEASE_RANK);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch { }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch { }
|
|
184
|
+
}));
|
|
185
|
+
moreMatches.sort((a, b) => {
|
|
186
|
+
const aRank = rankMap.get(a.packageId) ?? 0;
|
|
187
|
+
const bRank = rankMap.get(b.packageId) ?? 0;
|
|
188
|
+
if (bRank !== aRank)
|
|
189
|
+
return bRank - aRank;
|
|
190
|
+
return (a.name || "").localeCompare(b.name || "");
|
|
191
|
+
});
|
|
192
|
+
}
|
|
94
193
|
return moreMatches;
|
|
95
194
|
}
|
|
96
195
|
async function searchSubmit(evt) {
|
|
@@ -99,7 +198,6 @@ const PackageList = () => {
|
|
|
99
198
|
const name = searchText.trim();
|
|
100
199
|
if (!name)
|
|
101
200
|
return;
|
|
102
|
-
// check if name is a remote repo url
|
|
103
201
|
if (name.startsWith("http") || name.endsWith(".git")) {
|
|
104
202
|
if (!confirm(`Add remote package: ${name}`))
|
|
105
203
|
return;
|
|
@@ -113,7 +211,6 @@ const PackageList = () => {
|
|
|
113
211
|
}
|
|
114
212
|
catch (err) {
|
|
115
213
|
let errMessage = err instanceof Error ? err.message : String(err);
|
|
116
|
-
// replace all whitespace with a single space for confirm dialog
|
|
117
214
|
errMessage = errMessage.replace(/\s+/g, " ");
|
|
118
215
|
confirm(`Error adding remote package: ${errMessage}`);
|
|
119
216
|
}
|
|
@@ -142,10 +239,8 @@ const PackageList = () => {
|
|
|
142
239
|
if (addingPackage()) {
|
|
143
240
|
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "container-fluid d-flex justify-content-center mt-5", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-seam" }), "\u00A0 Adding Package"] }), (0, jsx_runtime_1.jsx)("div", { className: "container-fluid d-flex justify-content-center mt-2", children: (0, jsx_runtime_1.jsx)("div", { className: "progress", role: "progressbar", "aria-label": "Animated striped example", "aria-valuenow": 100, "aria-valuemin": 0, "aria-valuemax": 100, children: (0, jsx_runtime_1.jsx)("div", { className: "progress-bar progress-bar-striped progress-bar-animated", style: { width: 150 } }) }) })] }));
|
|
144
241
|
}
|
|
145
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid", children: [(0, jsx_runtime_1.jsxs)("div", { className: "input-group mt-3 mb-3", children: [(0, jsx_runtime_1.jsx)(input_1.Input, { value: searchTextObs, className: "form-control", placeholder: "Search, add, or create package", autoFocus: !!(0, globals_1.isDesktop)(), onKeyUp: (evt) => searchSubmit(evt) }), (0, jsx_runtime_1.jsx)("button", { className: "btn btn-outline-secondary dropdown-toggle sm", type: "button", "data-bs-toggle": "dropdown", "aria-expanded": "false", children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-plus-lg" }) }), (0, jsx_runtime_1.jsxs)("ul", { className: "dropdown-menu dropdown-menu-end", children: [(0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item", href: "#packages/newlocal", children: "New Local Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item disabled", href: "#", children: "Add Local Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item disabled", href: "#", children: "Add Remote Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("hr", { className: "dropdown-divider" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item", onClick: () => peers_sdk_1.rpcServerCalls.openPath(peers_sdk_1.packagesRootDir), style: { cursor: "pointer" }, children: "Open Packages Directory" }) })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "peers-list-container", children: (0, jsx_runtime_1.jsx)(lazy_list_1.LazyList, { resetTrigger: searchText
|
|
146
|
-
return packages.map((pkg) => {
|
|
147
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid pb-4", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-fill" }), "\u00A0\u00A0", (0, jsx_runtime_1.jsx)("a", { href: `#packages/${pkg.packageId}`, children: pkg.name }), (0, jsx_runtime_1.jsx)(PackageVersionBadge, { activePackageVersionId: pkg.activePackageVersionId, packageId: pkg.packageId, followVersionTags: pkg.followVersionTags }), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: pkg.description, positions: ["bottom", "top", "right", "left"] })] }, pkg.packageId));
|
|
148
|
-
});
|
|
242
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid", children: [(0, jsx_runtime_1.jsxs)("div", { className: "input-group mt-3 mb-3", children: [(0, jsx_runtime_1.jsx)(input_1.Input, { value: searchTextObs, className: "form-control", placeholder: "Search, add, or create package", autoFocus: !!(0, globals_1.isDesktop)(), onKeyUp: (evt) => searchSubmit(evt) }), (0, jsx_runtime_1.jsx)("button", { className: "btn btn-outline-secondary dropdown-toggle sm", type: "button", "data-bs-toggle": "dropdown", "aria-expanded": "false", children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-plus-lg" }) }), (0, jsx_runtime_1.jsxs)("ul", { className: "dropdown-menu dropdown-menu-end", children: [(0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item", href: "#packages/newlocal", children: "New Local Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item disabled", href: "#", children: "Add Local Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item disabled", href: "#", children: "Add Remote Package" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("hr", { className: "dropdown-divider" }) }), (0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("a", { className: "dropdown-item", onClick: () => peers_sdk_1.rpcServerCalls.openPath(peers_sdk_1.packagesRootDir), style: { cursor: "pointer" }, children: "Open Packages Directory" }) })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "peers-list-container", children: (0, jsx_runtime_1.jsx)(lazy_list_1.LazyList, { resetTrigger: `${searchText}_${refreshKey()}`, loadMore: loadMore, scrollThreshold: 0.6, renderItems: (packages) => {
|
|
243
|
+
return packages.map((pkg) => ((0, jsx_runtime_1.jsx)(PackageRow, { pkg: pkg, onUpdated: () => refreshKey(refreshKey() + 1) }, pkg.packageId)));
|
|
149
244
|
}, loadingIndicator: (0, jsx_runtime_1.jsx)("div", { className: "d-flex justify-content-center", style: { height: 200 }, children: (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {}) }), endOfList: (0, jsx_runtime_1.jsx)("div", { className: "d-flex justify-content-center", style: { height: 200 } }) }) })] }));
|
|
150
245
|
};
|
|
151
246
|
exports.PackageList = PackageList;
|
|
@@ -5,6 +5,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
6
6
|
const react_1 = require("react");
|
|
7
7
|
const hooks_1 = require("../../hooks");
|
|
8
|
+
const package_helpers_1 = require("./package-helpers");
|
|
8
9
|
function formatDate(iso) {
|
|
9
10
|
try {
|
|
10
11
|
const d = new Date(iso);
|
|
@@ -77,18 +78,7 @@ const PackageVersionsList = (props) => {
|
|
|
77
78
|
async function activateVersion(pv) {
|
|
78
79
|
setActivating(pv.packageVersionId);
|
|
79
80
|
try {
|
|
80
|
-
await (0,
|
|
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
|
-
}
|
|
91
|
-
}
|
|
81
|
+
await (0, package_helpers_1.activatePackageVersion)(pkg.packageId, pv);
|
|
92
82
|
await pkg.load();
|
|
93
83
|
refreshKey(refreshKey() + 1);
|
|
94
84
|
}
|
|
@@ -163,7 +153,17 @@ const PackageVersionsList = (props) => {
|
|
|
163
153
|
async function pinVersion() {
|
|
164
154
|
setPinning(true);
|
|
165
155
|
try {
|
|
166
|
-
|
|
156
|
+
const pinsGroup = !(0, peers_sdk_1.hasDeviceFollowOverride)(devicePrefs) && (await (0, package_helpers_1.isGroupAdmin)());
|
|
157
|
+
if (pinsGroup) {
|
|
158
|
+
const current = await (0, peers_sdk_1.Packages)().get(pkg.packageId);
|
|
159
|
+
if (current) {
|
|
160
|
+
current.versionFollowRange = "pinned";
|
|
161
|
+
await (0, peers_sdk_1.Packages)().signAndSave(current);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, { pinned: true });
|
|
166
|
+
}
|
|
167
167
|
await pkg.load();
|
|
168
168
|
}
|
|
169
169
|
catch (err) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { IAppNav
|
|
1
|
+
import type { IAppNav } from "@peers-app/peers-sdk";
|
|
2
|
+
import type { IPackageWithNavs } from "../ui-router/routes-loader";
|
|
2
3
|
export { accountApp } from "./account.app";
|
|
3
4
|
export { assistantsApp } from "./assistants.app";
|
|
4
5
|
export { consoleLogsApp } from "./console-logs.app";
|
|
@@ -17,4 +18,4 @@ export { typesApp } from "./types.app";
|
|
|
17
18
|
export { variablesApp } from "./variables.app";
|
|
18
19
|
export { workflowsApp } from "./workflows.app";
|
|
19
20
|
export declare const systemApps: IAppNav[];
|
|
20
|
-
export declare const systemPackage:
|
|
21
|
+
export declare const systemPackage: IPackageWithNavs;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type IAppNav, type
|
|
1
|
+
import { type IAppNav, type Observable } from "@peers-app/peers-sdk";
|
|
2
|
+
import { type IPackageWithNavs } from "../ui-router/routes-loader";
|
|
2
3
|
export interface TabState {
|
|
3
4
|
tabId: string;
|
|
4
5
|
packageId?: string;
|
|
@@ -21,7 +22,7 @@ export declare function goToTabPath(path: string): void;
|
|
|
21
22
|
export declare const handleMainPathChanged: (oldPath: string, newPath: string, setNewMainPath: (path: string) => void) => void;
|
|
22
23
|
type AppInfo = {
|
|
23
24
|
navItem: IAppNav;
|
|
24
|
-
package:
|
|
25
|
+
package: IPackageWithNavs;
|
|
25
26
|
};
|
|
26
27
|
export declare function determineAppFromPath(path: string): AppInfo | undefined;
|
|
27
28
|
export declare const updateActiveTabTitle: (newTitle: string) => void;
|
|
@@ -1,22 +1,8 @@
|
|
|
1
|
+
import { type IAppNav, type IPackage } from "@peers-app/peers-sdk";
|
|
1
2
|
import "../ui-defaults";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
createdBy: string;
|
|
8
|
-
publishPublicKey: string;
|
|
9
|
-
disabled?: boolean | undefined;
|
|
10
|
-
appNavs?: {
|
|
11
|
-
name: string;
|
|
12
|
-
iconClassName: string;
|
|
13
|
-
navigationPath: string;
|
|
14
|
-
displayName?: string | undefined;
|
|
15
|
-
}[] | undefined;
|
|
16
|
-
remoteRepo?: string | undefined;
|
|
17
|
-
activePackageVersionId?: string | undefined;
|
|
18
|
-
versionFollowRange?: "pinned" | "patch" | "minor" | "latest" | undefined;
|
|
19
|
-
followVersionTags?: string | undefined;
|
|
20
|
-
updateUrl?: string | undefined;
|
|
21
|
-
}[]>;
|
|
3
|
+
/** IPackage enriched with appNavs from the active PackageVersion. */
|
|
4
|
+
export type IPackageWithNavs = IPackage & {
|
|
5
|
+
appNavs?: IAppNav[];
|
|
6
|
+
};
|
|
7
|
+
export declare const allPackages: import("@peers-app/peers-sdk").Observable<IPackageWithNavs[]>;
|
|
22
8
|
export declare const loadAllRoutes: () => Promise<true | undefined>;
|
|
@@ -31,8 +31,7 @@ function subscribeToPrefs(packageId) {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Enrich an IPackage with appNavs from the device's active
|
|
35
|
-
* Falls back to `pkg.appNavs` for backward compat with older data.
|
|
34
|
+
* Enrich an IPackage with appNavs from the device's active PackageVersion record.
|
|
36
35
|
*/
|
|
37
36
|
async function enrichWithPvAppNavs(pkg) {
|
|
38
37
|
let pvId = pkg.activePackageVersionId;
|
|
@@ -53,7 +52,7 @@ async function enrichWithPvAppNavs(pkg) {
|
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
catch {
|
|
56
|
-
/*
|
|
55
|
+
/* PV not found */
|
|
57
56
|
}
|
|
58
57
|
return pkg;
|
|
59
58
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.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.19.
|
|
31
|
+
"@peers-app/peers-sdk": "^0.19.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.19.
|
|
42
|
+
"@peers-app/peers-sdk": "0.19.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",
|
package/src/index.tsx
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./components/markdown-editor/editor-inline";
|
|
|
6
6
|
export * from "./components/markdown-with-mentions";
|
|
7
7
|
export * from "./components/sortable-list";
|
|
8
8
|
export * from "./components/tabs";
|
|
9
|
+
export { Typeahead, type TypeaheadItem } from "./components/typeahead";
|
|
9
10
|
export * from "./components/voice-indicator";
|
|
10
11
|
export { mainContentPath } from "./globals";
|
|
11
12
|
export * from "./hooks";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Packages, rpcServerCalls } from "@peers-app/peers-sdk";
|
|
2
|
-
import React from "react";
|
|
3
2
|
import { Input } from "../../components/input";
|
|
4
3
|
import { LoadingIndicator } from "../../components/loading-indicator";
|
|
5
4
|
import { type ISaveButtonProps, SaveButton } from "../../components/save-button";
|
|
@@ -13,9 +12,9 @@ interface IProps {
|
|
|
13
12
|
packageId: string;
|
|
14
13
|
}
|
|
15
14
|
|
|
15
|
+
/** Detail screen for a single package with Info, Versions, Components, and Dependencies tabs. */
|
|
16
16
|
export const PackageDetails = (props: IProps) => {
|
|
17
17
|
const refresh = useObservableState(Date.now());
|
|
18
|
-
const saveDeviceTagRef = React.useRef<(() => Promise<void>) | null>(null);
|
|
19
18
|
|
|
20
19
|
const pkg = usePromise(
|
|
21
20
|
async () => {
|
|
@@ -88,7 +87,6 @@ export const PackageDetails = (props: IProps) => {
|
|
|
88
87
|
doc={pkg}
|
|
89
88
|
onClick={async () => {
|
|
90
89
|
await pkg.save();
|
|
91
|
-
await saveDeviceTagRef.current?.();
|
|
92
90
|
}}
|
|
93
91
|
addActions={[...addActions]}
|
|
94
92
|
/>
|
|
@@ -102,7 +100,7 @@ export const PackageDetails = (props: IProps) => {
|
|
|
102
100
|
name: "Info",
|
|
103
101
|
content: (
|
|
104
102
|
<ScreenTabBody>
|
|
105
|
-
<PackageInfo pkg={pkg}
|
|
103
|
+
<PackageInfo pkg={pkg} />
|
|
106
104
|
</ScreenTabBody>
|
|
107
105
|
),
|
|
108
106
|
},
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
doesTagMatch,
|
|
3
|
+
GroupMemberRole,
|
|
4
|
+
getUserContext,
|
|
5
|
+
getUserRole,
|
|
6
|
+
hasDeviceFollowOverride,
|
|
7
|
+
type IPackageVersion,
|
|
8
|
+
Packages,
|
|
9
|
+
PackageVersions,
|
|
10
|
+
packagePrefsVar,
|
|
11
|
+
updatePackagePrefs,
|
|
12
|
+
} from "@peers-app/peers-sdk";
|
|
13
|
+
|
|
14
|
+
/** Severity of a version update: major > minor > patch. */
|
|
15
|
+
export type UpdateLevel = "major" | "minor" | "patch" | null;
|
|
16
|
+
|
|
17
|
+
/** Result of checking a package's version status against available versions. */
|
|
18
|
+
export interface IVersionStatus {
|
|
19
|
+
activePv: IPackageVersion;
|
|
20
|
+
newerLevel: UpdateLevel;
|
|
21
|
+
newestPv: IPackageVersion | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check a package's version status: what's currently active, whether a newer
|
|
26
|
+
* version exists within the follow policy, and which version is the best
|
|
27
|
+
* upgrade candidate.
|
|
28
|
+
*
|
|
29
|
+
* @param packageId - The package to check.
|
|
30
|
+
* @param activePackageVersionId - The effective active version (device or group).
|
|
31
|
+
* @param followVersionTags - Tag filter from effective prefs.
|
|
32
|
+
*/
|
|
33
|
+
export async function checkVersionStatus(
|
|
34
|
+
packageId: string,
|
|
35
|
+
activePackageVersionId: string,
|
|
36
|
+
followVersionTags: string | undefined,
|
|
37
|
+
): Promise<IVersionStatus | null> {
|
|
38
|
+
const all = await PackageVersions().list({ packageId });
|
|
39
|
+
const active = all.find((v) => v.packageVersionId === activePackageVersionId);
|
|
40
|
+
if (!active) return null;
|
|
41
|
+
|
|
42
|
+
let newerLevel: UpdateLevel = null;
|
|
43
|
+
let newestPv: IPackageVersion | null = null;
|
|
44
|
+
|
|
45
|
+
if (active.version) {
|
|
46
|
+
const parse = (v: string) => v.split(".").map(Number);
|
|
47
|
+
const [aMaj, aMin, aPat] = parse(active.version);
|
|
48
|
+
let bestVersion = active.version;
|
|
49
|
+
|
|
50
|
+
for (const v of all) {
|
|
51
|
+
if (!v.version || v.packageVersionId === activePackageVersionId) continue;
|
|
52
|
+
if (!doesTagMatch(active.versionTag, v.versionTag, followVersionTags, undefined)) continue;
|
|
53
|
+
const [maj, min, pat] = parse(v.version);
|
|
54
|
+
const [bMaj, bMin, bPat] = parse(bestVersion);
|
|
55
|
+
|
|
56
|
+
let candidateLevel: UpdateLevel = null;
|
|
57
|
+
if (maj > aMaj) candidateLevel = "major";
|
|
58
|
+
else if (maj === aMaj && min > aMin) candidateLevel = "minor";
|
|
59
|
+
else if (maj === aMaj && min === aMin && pat > aPat) candidateLevel = "patch";
|
|
60
|
+
else if (
|
|
61
|
+
maj === aMaj &&
|
|
62
|
+
min === aMin &&
|
|
63
|
+
pat === aPat &&
|
|
64
|
+
(v.createdAt || "") > (active.createdAt || "")
|
|
65
|
+
)
|
|
66
|
+
candidateLevel = "patch";
|
|
67
|
+
|
|
68
|
+
if (!candidateLevel) continue;
|
|
69
|
+
|
|
70
|
+
const levelRank = { major: 3, minor: 2, patch: 1 };
|
|
71
|
+
if (!newerLevel || levelRank[candidateLevel] > levelRank[newerLevel]) {
|
|
72
|
+
newerLevel = candidateLevel;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const isHigherThanBest =
|
|
76
|
+
maj > bMaj ||
|
|
77
|
+
(maj === bMaj && min > bMin) ||
|
|
78
|
+
(maj === bMaj && min === bMin && pat > bPat) ||
|
|
79
|
+
(maj === bMaj &&
|
|
80
|
+
min === bMin &&
|
|
81
|
+
pat === bPat &&
|
|
82
|
+
(v.createdAt || "") > (newestPv?.createdAt || active.createdAt || ""));
|
|
83
|
+
|
|
84
|
+
if (!newestPv || isHigherThanBest) {
|
|
85
|
+
newestPv = v;
|
|
86
|
+
bestVersion = v.version;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { activePv: active, newerLevel, newestPv };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Whether the current user is an admin of the active group. */
|
|
94
|
+
export async function isGroupAdmin(): Promise<boolean> {
|
|
95
|
+
try {
|
|
96
|
+
const userCtx = await getUserContext();
|
|
97
|
+
const dc = userCtx.defaultDataContext();
|
|
98
|
+
const contextId = dc?.groupId || dc?.dataContextId;
|
|
99
|
+
if (!contextId) return false;
|
|
100
|
+
const role = await getUserRole(contextId, userCtx.userId);
|
|
101
|
+
return role >= GroupMemberRole.Admin;
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Activate a specific package version, handling group-level vs device-local
|
|
109
|
+
* activation. When the device has no follow override and the user is a group
|
|
110
|
+
* admin, the group's `activePackageVersionId` is advanced so all devices
|
|
111
|
+
* stay in sync. Otherwise only the device-local prefs are updated.
|
|
112
|
+
*
|
|
113
|
+
* @param packageId - The package to activate a version for.
|
|
114
|
+
* @param pv - The package version to activate.
|
|
115
|
+
*/
|
|
116
|
+
export async function activatePackageVersion(
|
|
117
|
+
packageId: string,
|
|
118
|
+
pv: IPackageVersion,
|
|
119
|
+
): Promise<void> {
|
|
120
|
+
const prefsVar = packagePrefsVar(packageId);
|
|
121
|
+
await prefsVar.loadingPromise;
|
|
122
|
+
const devicePrefs = prefsVar();
|
|
123
|
+
|
|
124
|
+
const activatesGroup =
|
|
125
|
+
pv.versionTag !== "dev" && !hasDeviceFollowOverride(devicePrefs) && (await isGroupAdmin());
|
|
126
|
+
|
|
127
|
+
if (activatesGroup) {
|
|
128
|
+
const current = await Packages().get(packageId);
|
|
129
|
+
if (current) {
|
|
130
|
+
current.activePackageVersionId = pv.packageVersionId;
|
|
131
|
+
await Packages().signAndSave(current);
|
|
132
|
+
}
|
|
133
|
+
await updatePackagePrefs(packageId, { activePackageVersionId: undefined });
|
|
134
|
+
} else {
|
|
135
|
+
await updatePackagePrefs(packageId, { activePackageVersionId: pv.packageVersionId });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Numeric rank for sorting packages by update urgency. */
|
|
140
|
+
export const updateLevelRank: Record<string, number> = { major: 3, minor: 2, patch: 1 };
|