@peers-app/peers-ui 0.18.8 → 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/README.md +83 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/screens/groups/group-list.js +1 -3
- 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 +30 -14
- package/dist/system-apps/index.d.ts +3 -2
- package/dist/system-apps/index.js +1 -0
- package/dist/tabs-layout/tabs-state.d.ts +3 -2
- package/dist/ui-router/routes-loader.d.ts +6 -18
- package/dist/ui-router/routes-loader.js +2 -3
- package/package.json +3 -3
- package/src/index.tsx +1 -0
- package/src/screens/groups/group-list.tsx +3 -6
- 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 +34 -15
- package/src/system-apps/index.ts +4 -2
- package/src/tabs-layout/tabs-state.ts +3 -4
- package/src/ui-router/routes-loader.ts +9 -6
|
@@ -2,27 +2,27 @@ import {
|
|
|
2
2
|
doesTagMatch,
|
|
3
3
|
getEffectivePackagePrefs,
|
|
4
4
|
groupDeviceVar,
|
|
5
|
+
hasDeviceFollowOverride,
|
|
5
6
|
type IDoc,
|
|
6
7
|
type IPackage,
|
|
7
8
|
type IPackageVersion,
|
|
9
|
+
Packages,
|
|
8
10
|
PackageVersions,
|
|
9
11
|
packagePrefsVar,
|
|
10
12
|
packagesRootDir,
|
|
11
13
|
rpcServerCalls,
|
|
12
14
|
updatePackagePrefs,
|
|
13
15
|
} from "@peers-app/peers-sdk";
|
|
14
|
-
import React from "react";
|
|
15
16
|
import { Input } from "../../components/input";
|
|
16
17
|
import { MarkdownWithMentions } from "../../components/markdown-with-mentions";
|
|
17
18
|
import { Tooltip } from "../../components/tooltip";
|
|
18
19
|
import { useObservable, usePromise } from "../../hooks";
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
saveDeviceTagRef?: React.MutableRefObject<(() => Promise<void>) | null>;
|
|
23
|
-
}) => {
|
|
21
|
+
/** Info tab for the package details screen. */
|
|
22
|
+
export const PackageInfo = (props: { pkg: IDoc<IPackage> }) => {
|
|
24
23
|
const { pkg } = props;
|
|
25
24
|
const [followVersionTags] = useObservable(pkg.qs.followVersionTags);
|
|
25
|
+
const [versionFollowRange] = useObservable(pkg.qs.versionFollowRange);
|
|
26
26
|
|
|
27
27
|
const localPathVar = groupDeviceVar<string>(`packageLocalPath_${pkg.packageId}`, {
|
|
28
28
|
defaultValue: `${packagesRootDir}/${pkg.name}`,
|
|
@@ -32,45 +32,16 @@ export const PackageInfo = (props: {
|
|
|
32
32
|
const [devicePrefs] = useObservable(packagePrefsVar(pkg.packageId));
|
|
33
33
|
const effective = getEffectivePackagePrefs(pkg.toJS(), devicePrefs);
|
|
34
34
|
const activeVersionId = effective.activePackageVersionId;
|
|
35
|
-
const deviceFollowTags = devicePrefs?.followTags;
|
|
36
35
|
const isPinned = effective.isPinned;
|
|
37
36
|
const followRange = effective.followRange;
|
|
38
37
|
|
|
39
|
-
const
|
|
40
|
-
const savingRef = React.useRef(false);
|
|
38
|
+
const hasOverride = hasDeviceFollowOverride(devicePrefs);
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
setDeviceTagDraft(deviceFollowTags || "");
|
|
45
|
-
}
|
|
46
|
-
}, [deviceFollowTags]);
|
|
40
|
+
const groupFollowTags = followVersionTags === "stable,beta" ? "stable,beta" : "stable";
|
|
41
|
+
const groupRange = versionFollowRange || "latest";
|
|
47
42
|
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
React.useEffect(() => {
|
|
51
|
-
if (deviceTagDirty && !prevDirtyRef.current) {
|
|
52
|
-
pkg.q((pkg.q() || 0) + 1);
|
|
53
|
-
} else if (!deviceTagDirty && prevDirtyRef.current) {
|
|
54
|
-
pkg.q(Math.max(0, (pkg.q() || 0) - 1));
|
|
55
|
-
}
|
|
56
|
-
prevDirtyRef.current = deviceTagDirty;
|
|
57
|
-
}, [deviceTagDirty, pkg.q]);
|
|
58
|
-
|
|
59
|
-
if (props.saveDeviceTagRef) {
|
|
60
|
-
props.saveDeviceTagRef.current = async () => {
|
|
61
|
-
const val = deviceTagDraft.trim();
|
|
62
|
-
if (val !== (deviceFollowTags || "")) {
|
|
63
|
-
savingRef.current = true;
|
|
64
|
-
try {
|
|
65
|
-
await updatePackagePrefs(pkg.packageId, {
|
|
66
|
-
followTags: val || undefined,
|
|
67
|
-
});
|
|
68
|
-
} finally {
|
|
69
|
-
savingRef.current = false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
}
|
|
43
|
+
const effectiveFollowTags = effective.followTags || "stable";
|
|
44
|
+
const followTagsValue = effectiveFollowTags === "stable,beta" ? "stable,beta" : "stable";
|
|
74
45
|
|
|
75
46
|
const activeVersion = usePromise(
|
|
76
47
|
async () => {
|
|
@@ -87,7 +58,6 @@ export const PackageInfo = (props: {
|
|
|
87
58
|
const all = await PackageVersions().list({ packageId: pkg.packageId });
|
|
88
59
|
const active = all.find((v) => v.packageVersionId === activeVersionId);
|
|
89
60
|
if (!active?.version) return "uptodate";
|
|
90
|
-
const effectiveFollowTags = deviceFollowTags || followVersionTags;
|
|
91
61
|
const parse = (v: string) => v.split(".").map(Number);
|
|
92
62
|
const [aMaj, aMin, aPat] = parse(active.version);
|
|
93
63
|
let highest: "major" | "minor" | "patch" | null = null;
|
|
@@ -118,7 +88,7 @@ export const PackageInfo = (props: {
|
|
|
118
88
|
return highest || "uptodate";
|
|
119
89
|
},
|
|
120
90
|
undefined,
|
|
121
|
-
[activeVersionId, pkg.packageId,
|
|
91
|
+
[activeVersionId, pkg.packageId, effectiveFollowTags],
|
|
122
92
|
);
|
|
123
93
|
|
|
124
94
|
const remoteRepoUrl = pkg.remoteRepo;
|
|
@@ -217,18 +187,20 @@ export const PackageInfo = (props: {
|
|
|
217
187
|
|
|
218
188
|
<div className="mb-3">
|
|
219
189
|
<small>
|
|
220
|
-
Auto-Update Range
|
|
221
|
-
<Tooltip markdownContent="Controls which new versions are auto-activated
|
|
190
|
+
Auto-Update Range:
|
|
191
|
+
<Tooltip markdownContent="Controls which new versions are auto-activated for the group. When an admin device auto-upgrades, the group's active version advances for everyone. **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." />
|
|
222
192
|
</small>
|
|
223
193
|
<select
|
|
224
194
|
className="form-select form-select-sm"
|
|
225
|
-
value={
|
|
195
|
+
value={groupRange}
|
|
226
196
|
onChange={async (e) => {
|
|
227
197
|
const val = e.target.value as "pinned" | "patch" | "minor" | "latest";
|
|
228
|
-
await
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
198
|
+
const current = await Packages().get(pkg.packageId);
|
|
199
|
+
if (current) {
|
|
200
|
+
current.versionFollowRange = val;
|
|
201
|
+
await Packages().signAndSave(current);
|
|
202
|
+
await pkg.load();
|
|
203
|
+
}
|
|
232
204
|
}}
|
|
233
205
|
>
|
|
234
206
|
<option value="latest">Latest (auto-update to newest)</option>
|
|
@@ -238,31 +210,114 @@ export const PackageInfo = (props: {
|
|
|
238
210
|
</select>
|
|
239
211
|
</div>
|
|
240
212
|
|
|
241
|
-
<div className={`mb-3 ${
|
|
213
|
+
<div className={`mb-3 ${groupRange === "pinned" ? "opacity-50" : ""}`}>
|
|
242
214
|
<small>
|
|
243
|
-
|
|
244
|
-
<Tooltip markdownContent="Which
|
|
215
|
+
Following:
|
|
216
|
+
<Tooltip markdownContent="Which release channel the group follows. Devices using group settings will auto-upgrade within this channel. **Stable** = only fully promoted releases. **Stable + Beta** = also includes beta pre-releases for early testing." />
|
|
245
217
|
</small>
|
|
246
|
-
<
|
|
218
|
+
<select
|
|
219
|
+
className="form-select form-select-sm"
|
|
220
|
+
value={groupFollowTags}
|
|
221
|
+
disabled={groupRange === "pinned"}
|
|
222
|
+
onChange={async (e) => {
|
|
223
|
+
const val = e.target.value;
|
|
224
|
+
const current = await Packages().get(pkg.packageId);
|
|
225
|
+
if (current) {
|
|
226
|
+
current.followVersionTags = val === "stable" ? undefined : val;
|
|
227
|
+
await Packages().signAndSave(current);
|
|
228
|
+
await pkg.load();
|
|
229
|
+
}
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
<option value="stable">Stable</option>
|
|
233
|
+
<option value="stable,beta">Stable + Beta</option>
|
|
234
|
+
</select>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div className="mb-3">
|
|
238
|
+
<div className="form-check">
|
|
247
239
|
<input
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
onChange={(e) =>
|
|
253
|
-
|
|
240
|
+
className="form-check-input"
|
|
241
|
+
type="checkbox"
|
|
242
|
+
id={`deviceOverride_${pkg.packageId}`}
|
|
243
|
+
checked={hasOverride}
|
|
244
|
+
onChange={async (e) => {
|
|
245
|
+
if (e.target.checked) {
|
|
246
|
+
await updatePackagePrefs(pkg.packageId, {
|
|
247
|
+
followRange:
|
|
248
|
+
groupRange === "pinned"
|
|
249
|
+
? undefined
|
|
250
|
+
: (groupRange as "patch" | "minor" | "latest"),
|
|
251
|
+
followTags: followVersionTags || undefined,
|
|
252
|
+
pinned: groupRange === "pinned" ? true : undefined,
|
|
253
|
+
});
|
|
254
|
+
} else {
|
|
255
|
+
await updatePackagePrefs(pkg.packageId, {
|
|
256
|
+
followRange: undefined,
|
|
257
|
+
followTags: undefined,
|
|
258
|
+
pinned: undefined,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}}
|
|
254
262
|
/>
|
|
255
|
-
|
|
256
|
-
<
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
)}
|
|
263
|
+
<label className="form-check-label" htmlFor={`deviceOverride_${pkg.packageId}`}>
|
|
264
|
+
<small>Override on this device</small>
|
|
265
|
+
</label>
|
|
266
|
+
<small>
|
|
267
|
+
<Tooltip
|
|
268
|
+
markdownContent={`**Off:** This device follows the group's auto-update and channel settings. When an admin device upgrades, the group's active version advances for all devices.\n\n**On:** This device uses its own settings below. Upgrades only affect this device — the group's active version is not changed.`}
|
|
269
|
+
/>
|
|
270
|
+
</small>
|
|
264
271
|
</div>
|
|
265
272
|
</div>
|
|
273
|
+
|
|
274
|
+
{hasOverride && (
|
|
275
|
+
<div className="ps-3 border-start mb-3">
|
|
276
|
+
<div className="mb-3">
|
|
277
|
+
<small>
|
|
278
|
+
Auto-Update Range (this device):
|
|
279
|
+
<Tooltip markdownContent="Controls which new versions are auto-activated on **this device only**. Upgrades here do not change the group's active version." />
|
|
280
|
+
</small>
|
|
281
|
+
<select
|
|
282
|
+
className="form-select form-select-sm"
|
|
283
|
+
value={isPinned ? "pinned" : followRange}
|
|
284
|
+
onChange={async (e) => {
|
|
285
|
+
const val = e.target.value as "pinned" | "patch" | "minor" | "latest";
|
|
286
|
+
await updatePackagePrefs(pkg.packageId, {
|
|
287
|
+
pinned: val === "pinned",
|
|
288
|
+
followRange: val === "pinned" ? undefined : val,
|
|
289
|
+
});
|
|
290
|
+
}}
|
|
291
|
+
>
|
|
292
|
+
<option value="latest">Latest (auto-update to newest)</option>
|
|
293
|
+
<option value="minor">Minor (same major version)</option>
|
|
294
|
+
<option value="patch">Patch (same major.minor version)</option>
|
|
295
|
+
<option value="pinned">Pinned (no auto-updates)</option>
|
|
296
|
+
</select>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
<div className={isPinned ? "opacity-50" : ""}>
|
|
300
|
+
<small>
|
|
301
|
+
Following (this device):
|
|
302
|
+
<Tooltip markdownContent="Which release channel to follow on **this device only**, overriding the group setting." />
|
|
303
|
+
</small>
|
|
304
|
+
<select
|
|
305
|
+
className="form-select form-select-sm"
|
|
306
|
+
value={followTagsValue}
|
|
307
|
+
disabled={isPinned}
|
|
308
|
+
onChange={async (e) => {
|
|
309
|
+
const val = e.target.value;
|
|
310
|
+
await updatePackagePrefs(pkg.packageId, {
|
|
311
|
+
followTags: val === "stable" ? "stable" : val,
|
|
312
|
+
});
|
|
313
|
+
}}
|
|
314
|
+
>
|
|
315
|
+
<option value="stable">Stable</option>
|
|
316
|
+
<option value="stable,beta">Stable + Beta</option>
|
|
317
|
+
</select>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
)}
|
|
266
321
|
</div>
|
|
267
322
|
|
|
268
323
|
<div className="mt-2">
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
getEffectivePackagePrefs,
|
|
3
3
|
type ICursorIterable,
|
|
4
4
|
type IPackage,
|
|
5
|
+
type IPackageVersion,
|
|
5
6
|
observable,
|
|
6
7
|
Packages,
|
|
7
8
|
PackageVersions,
|
|
9
|
+
packagePrefsVar,
|
|
8
10
|
packagesRootDir,
|
|
9
11
|
rpcServerCalls,
|
|
12
|
+
updatePackagePrefs,
|
|
10
13
|
} from "@peers-app/peers-sdk";
|
|
11
14
|
import type React from "react";
|
|
12
15
|
import { useCallback, useEffect, useState } from "react";
|
|
@@ -17,88 +20,217 @@ import { Tooltip } from "../../components/tooltip";
|
|
|
17
20
|
import { isDesktop, mainContentPath } from "../../globals";
|
|
18
21
|
import { useObservable, useObservableState, usePromise } from "../../hooks";
|
|
19
22
|
import { registerInternalPeersUI } from "../../ui-router/ui-loader";
|
|
23
|
+
import {
|
|
24
|
+
activatePackageVersion,
|
|
25
|
+
checkVersionStatus,
|
|
26
|
+
type IVersionStatus,
|
|
27
|
+
type UpdateLevel,
|
|
28
|
+
updateLevelRank,
|
|
29
|
+
} from "./package-helpers";
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
packageId: string;
|
|
28
|
-
followVersionTags?: string;
|
|
29
|
-
}) => {
|
|
30
|
-
const data = usePromise(
|
|
31
|
-
async () => {
|
|
32
|
-
if (!activePackageVersionId) return null;
|
|
33
|
-
const all = await PackageVersions().list({ packageId });
|
|
34
|
-
const active = all.find((v) => v.packageVersionId === activePackageVersionId);
|
|
35
|
-
if (!active) return null;
|
|
31
|
+
interface IPackageRowStatus extends IVersionStatus {
|
|
32
|
+
/** When on a dev version, the group's non-dev active PV (if any). */
|
|
33
|
+
groupReleasePv: IPackageVersion | null;
|
|
34
|
+
/** When on a non-dev version, the latest dev PV (if any). */
|
|
35
|
+
latestDevPv: IPackageVersion | null;
|
|
36
|
+
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
38
|
+
function tagBadgeClass(tag: string): string {
|
|
39
|
+
if (tag === "dev") return "text-bg-danger";
|
|
40
|
+
if (tag.startsWith("beta")) return "text-bg-warning";
|
|
41
|
+
return "text-bg-success";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function updateBadgeClass(level: UpdateLevel): string {
|
|
45
|
+
if (level === "major") return "text-bg-danger";
|
|
46
|
+
if (level === "minor") return "text-bg-warning";
|
|
47
|
+
return "text-bg-info";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const PackageRow = ({ pkg, onUpdated }: { pkg: IPackage; onUpdated: () => void }) => {
|
|
51
|
+
const [updating, setUpdating] = useState(false);
|
|
52
|
+
const [devicePrefs] = useObservable(packagePrefsVar(pkg.packageId));
|
|
53
|
+
const effective = getEffectivePackagePrefs(pkg, devicePrefs);
|
|
54
|
+
|
|
55
|
+
const status = usePromise(
|
|
56
|
+
async (): Promise<IPackageRowStatus | null> => {
|
|
57
|
+
if (!effective.activePackageVersionId) return null;
|
|
58
|
+
const s = await checkVersionStatus(
|
|
59
|
+
pkg.packageId,
|
|
60
|
+
effective.activePackageVersionId,
|
|
61
|
+
effective.followTags,
|
|
62
|
+
);
|
|
63
|
+
if (!s) return null;
|
|
64
|
+
|
|
65
|
+
let groupReleasePv: IPackageVersion | null = null;
|
|
66
|
+
if (
|
|
67
|
+
s.activePv.versionTag === "dev" &&
|
|
68
|
+
pkg.activePackageVersionId &&
|
|
69
|
+
pkg.activePackageVersionId !== effective.activePackageVersionId
|
|
70
|
+
) {
|
|
71
|
+
try {
|
|
72
|
+
const gpv = await PackageVersions().get(pkg.activePackageVersionId);
|
|
73
|
+
if (gpv && gpv.versionTag !== "dev") groupReleasePv = gpv;
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
76
|
+
let latestDevPv: IPackageVersion | null = null;
|
|
77
|
+
if (s.activePv.versionTag !== "dev") {
|
|
78
|
+
const allVersions = await PackageVersions().list({ packageId: pkg.packageId });
|
|
79
|
+
for (const v of allVersions) {
|
|
80
|
+
if (v.versionTag !== "dev") continue;
|
|
81
|
+
if (!latestDevPv || (v.createdAt || "") > (latestDevPv.createdAt || "")) {
|
|
82
|
+
latestDevPv = v;
|
|
66
83
|
}
|
|
67
84
|
}
|
|
68
85
|
}
|
|
69
|
-
|
|
86
|
+
|
|
87
|
+
return { ...s, groupReleasePv, latestDevPv };
|
|
70
88
|
},
|
|
71
89
|
undefined,
|
|
72
|
-
[
|
|
90
|
+
[
|
|
91
|
+
effective.activePackageVersionId,
|
|
92
|
+
pkg.packageId,
|
|
93
|
+
effective.followTags,
|
|
94
|
+
pkg.activePackageVersionId,
|
|
95
|
+
],
|
|
73
96
|
);
|
|
74
97
|
|
|
75
|
-
|
|
76
|
-
const
|
|
98
|
+
const isOnDev = status?.activePv.versionTag === "dev";
|
|
99
|
+
const hasGroupRelease = !!status?.groupReleasePv;
|
|
100
|
+
const hasUpdate = !isOnDev && !!status?.newerLevel && !!status?.newestPv;
|
|
101
|
+
const hasDevVersion = !isOnDev && !!status?.latestDevPv;
|
|
102
|
+
const isDeviceOverridden =
|
|
103
|
+
!!effective.activePackageVersionId &&
|
|
104
|
+
!!pkg.activePackageVersionId &&
|
|
105
|
+
effective.activePackageVersionId !== pkg.activePackageVersionId;
|
|
106
|
+
|
|
107
|
+
async function handleUpdate() {
|
|
108
|
+
if (!status?.newestPv) return;
|
|
109
|
+
setUpdating(true);
|
|
110
|
+
try {
|
|
111
|
+
await activatePackageVersion(pkg.packageId, status.newestPv);
|
|
112
|
+
onUpdated();
|
|
113
|
+
} catch (err: unknown) {
|
|
114
|
+
alert(`Failed to update: ${err instanceof Error ? err.message : String(err)}`);
|
|
115
|
+
} finally {
|
|
116
|
+
setUpdating(false);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function handleUseRelease() {
|
|
121
|
+
setUpdating(true);
|
|
122
|
+
try {
|
|
123
|
+
await updatePackagePrefs(pkg.packageId, { activePackageVersionId: undefined });
|
|
124
|
+
onUpdated();
|
|
125
|
+
} catch (err: unknown) {
|
|
126
|
+
alert(`Failed to switch: ${err instanceof Error ? err.message : String(err)}`);
|
|
127
|
+
} finally {
|
|
128
|
+
setUpdating(false);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function handleUseDev() {
|
|
133
|
+
if (!status?.latestDevPv) return;
|
|
134
|
+
setUpdating(true);
|
|
135
|
+
try {
|
|
136
|
+
await updatePackagePrefs(pkg.packageId, {
|
|
137
|
+
activePackageVersionId: status.latestDevPv.packageVersionId,
|
|
138
|
+
});
|
|
139
|
+
onUpdated();
|
|
140
|
+
} catch (err: unknown) {
|
|
141
|
+
alert(`Failed to switch: ${err instanceof Error ? err.message : String(err)}`);
|
|
142
|
+
} finally {
|
|
143
|
+
setUpdating(false);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
77
146
|
|
|
78
147
|
return (
|
|
79
|
-
<
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
148
|
+
<div className="container-fluid pb-4 d-flex align-items-center">
|
|
149
|
+
<div className="flex-grow-1" style={{ minWidth: 0 }}>
|
|
150
|
+
<i className="bi bi-box-fill"></i>
|
|
151
|
+
<a href={`#packages/${pkg.packageId}`}>{pkg.name}</a>
|
|
152
|
+
{isDeviceOverridden && (
|
|
153
|
+
<span
|
|
154
|
+
className="text-warning ms-1"
|
|
155
|
+
title="This device is using a different version than the group"
|
|
156
|
+
>
|
|
157
|
+
<i className="bi bi-pc-display" />
|
|
158
|
+
</span>
|
|
159
|
+
)}
|
|
160
|
+
{status && (
|
|
161
|
+
<span className="ms-2">
|
|
162
|
+
<small className="text-muted">v{status.activePv.version}</small>
|
|
163
|
+
{status.activePv.versionTag && (
|
|
164
|
+
<span
|
|
165
|
+
className={`badge ms-1 ${tagBadgeClass(status.activePv.versionTag)}`}
|
|
166
|
+
style={{ fontSize: "0.65em" }}
|
|
167
|
+
>
|
|
168
|
+
{status.activePv.versionTag}
|
|
169
|
+
</span>
|
|
170
|
+
)}
|
|
171
|
+
{hasUpdate ? (
|
|
172
|
+
<span
|
|
173
|
+
className={`badge ms-1 ${updateBadgeClass(status.newerLevel)}`}
|
|
174
|
+
style={{ fontSize: "0.65em" }}
|
|
175
|
+
>
|
|
176
|
+
v{status.newestPv?.version} available
|
|
177
|
+
</span>
|
|
178
|
+
) : isOnDev && hasGroupRelease ? (
|
|
179
|
+
<span className="badge ms-1 text-bg-secondary" style={{ fontSize: "0.65em" }}>
|
|
180
|
+
release: v{status.groupReleasePv?.version}
|
|
181
|
+
</span>
|
|
182
|
+
) : (
|
|
183
|
+
<span className="badge ms-1 text-bg-success" style={{ fontSize: "0.65em" }}>
|
|
184
|
+
up to date
|
|
185
|
+
</span>
|
|
186
|
+
)}
|
|
187
|
+
</span>
|
|
188
|
+
)}
|
|
189
|
+
<Tooltip markdownContent={pkg.description} positions={["bottom", "top", "right", "left"]} />
|
|
190
|
+
</div>
|
|
191
|
+
{hasUpdate && (
|
|
192
|
+
<button
|
|
193
|
+
className="btn btn-sm btn-outline-primary ms-2 text-nowrap"
|
|
194
|
+
disabled={updating}
|
|
195
|
+
onClick={handleUpdate}
|
|
85
196
|
>
|
|
86
|
-
{
|
|
87
|
-
|
|
197
|
+
{updating ? (
|
|
198
|
+
<span className="spinner-border spinner-border-sm me-1" />
|
|
199
|
+
) : (
|
|
200
|
+
<i className="bi bi-arrow-up-circle me-1" />
|
|
201
|
+
)}
|
|
202
|
+
Update
|
|
203
|
+
</button>
|
|
88
204
|
)}
|
|
89
|
-
{
|
|
90
|
-
<
|
|
91
|
-
className=
|
|
92
|
-
|
|
205
|
+
{isOnDev && hasGroupRelease && (
|
|
206
|
+
<button
|
|
207
|
+
className="btn btn-sm btn-outline-secondary ms-2 text-nowrap"
|
|
208
|
+
disabled={updating}
|
|
209
|
+
onClick={handleUseRelease}
|
|
210
|
+
>
|
|
211
|
+
{updating ? (
|
|
212
|
+
<span className="spinner-border spinner-border-sm me-1" />
|
|
213
|
+
) : (
|
|
214
|
+
<i className="bi bi-box-arrow-in-right me-1" />
|
|
215
|
+
)}
|
|
216
|
+
Use Release
|
|
217
|
+
</button>
|
|
218
|
+
)}
|
|
219
|
+
{hasDevVersion && (
|
|
220
|
+
<button
|
|
221
|
+
className="btn btn-sm btn-outline-danger ms-2 text-nowrap"
|
|
222
|
+
disabled={updating}
|
|
223
|
+
onClick={handleUseDev}
|
|
93
224
|
>
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
225
|
+
{updating ? (
|
|
226
|
+
<span className="spinner-border spinner-border-sm me-1" />
|
|
227
|
+
) : (
|
|
228
|
+
<i className="bi bi-tools me-1" />
|
|
229
|
+
)}
|
|
230
|
+
Use Dev
|
|
231
|
+
</button>
|
|
100
232
|
)}
|
|
101
|
-
</
|
|
233
|
+
</div>
|
|
102
234
|
);
|
|
103
235
|
};
|
|
104
236
|
|
|
@@ -106,6 +238,7 @@ export const PackageList = () => {
|
|
|
106
238
|
const [searchTextObs] = useState(() => observable(""));
|
|
107
239
|
const [searchText] = useObservable(searchTextObs);
|
|
108
240
|
const addingPackage = useObservableState(false);
|
|
241
|
+
const refreshKey = useObservableState(0);
|
|
109
242
|
|
|
110
243
|
const [cursorObs] = useState(() => observable<ICursorIterable<IPackage> | undefined>());
|
|
111
244
|
|
|
@@ -140,6 +273,44 @@ export const PackageList = () => {
|
|
|
140
273
|
if (moreMatches.length === 0) {
|
|
141
274
|
cursorObs(undefined);
|
|
142
275
|
}
|
|
276
|
+
|
|
277
|
+
if (!searchText.length && existing.length === 0 && moreMatches.length > 0) {
|
|
278
|
+
const DEV_WITH_RELEASE_RANK = 0.5;
|
|
279
|
+
const rankMap = new Map<string, number>();
|
|
280
|
+
await Promise.all(
|
|
281
|
+
moreMatches.map(async (pkg) => {
|
|
282
|
+
try {
|
|
283
|
+
const pVar = packagePrefsVar(pkg.packageId);
|
|
284
|
+
await pVar.loadingPromise;
|
|
285
|
+
const eff = getEffectivePackagePrefs(pkg, pVar());
|
|
286
|
+
if (!eff.activePackageVersionId) return;
|
|
287
|
+
const s = await checkVersionStatus(
|
|
288
|
+
pkg.packageId,
|
|
289
|
+
eff.activePackageVersionId,
|
|
290
|
+
eff.followTags,
|
|
291
|
+
);
|
|
292
|
+
if (!s) return;
|
|
293
|
+
if (s.newerLevel) {
|
|
294
|
+
rankMap.set(pkg.packageId, updateLevelRank[s.newerLevel]);
|
|
295
|
+
} else if (s.activePv.versionTag === "dev" && pkg.activePackageVersionId) {
|
|
296
|
+
try {
|
|
297
|
+
const gpv = await PackageVersions().get(pkg.activePackageVersionId);
|
|
298
|
+
if (gpv && gpv.versionTag !== "dev") {
|
|
299
|
+
rankMap.set(pkg.packageId, DEV_WITH_RELEASE_RANK);
|
|
300
|
+
}
|
|
301
|
+
} catch {}
|
|
302
|
+
}
|
|
303
|
+
} catch {}
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
306
|
+
moreMatches.sort((a, b) => {
|
|
307
|
+
const aRank = rankMap.get(a.packageId) ?? 0;
|
|
308
|
+
const bRank = rankMap.get(b.packageId) ?? 0;
|
|
309
|
+
if (bRank !== aRank) return bRank - aRank;
|
|
310
|
+
return (a.name || "").localeCompare(b.name || "");
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
143
314
|
return moreMatches;
|
|
144
315
|
}
|
|
145
316
|
|
|
@@ -148,7 +319,6 @@ export const PackageList = () => {
|
|
|
148
319
|
const name = searchText.trim();
|
|
149
320
|
if (!name) return;
|
|
150
321
|
|
|
151
|
-
// check if name is a remote repo url
|
|
152
322
|
if (name.startsWith("http") || name.endsWith(".git")) {
|
|
153
323
|
if (!confirm(`Add remote package: ${name}`)) return;
|
|
154
324
|
try {
|
|
@@ -160,7 +330,6 @@ export const PackageList = () => {
|
|
|
160
330
|
}
|
|
161
331
|
} catch (err: unknown) {
|
|
162
332
|
let errMessage = err instanceof Error ? err.message : String(err);
|
|
163
|
-
// replace all whitespace with a single space for confirm dialog
|
|
164
333
|
errMessage = errMessage.replace(/\s+/g, " ");
|
|
165
334
|
confirm(`Error adding remote package: ${errMessage}`);
|
|
166
335
|
} finally {
|
|
@@ -261,27 +430,17 @@ export const PackageList = () => {
|
|
|
261
430
|
|
|
262
431
|
<div className="peers-list-container">
|
|
263
432
|
<LazyList
|
|
264
|
-
resetTrigger={searchText}
|
|
433
|
+
resetTrigger={`${searchText}_${refreshKey()}`}
|
|
265
434
|
loadMore={loadMore}
|
|
266
435
|
scrollThreshold={0.6}
|
|
267
436
|
renderItems={(packages) => {
|
|
268
|
-
return packages.map((pkg) =>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
packageId={pkg.packageId}
|
|
276
|
-
followVersionTags={pkg.followVersionTags}
|
|
277
|
-
/>
|
|
278
|
-
<Tooltip
|
|
279
|
-
markdownContent={pkg.description}
|
|
280
|
-
positions={["bottom", "top", "right", "left"]}
|
|
281
|
-
/>
|
|
282
|
-
</div>
|
|
283
|
-
);
|
|
284
|
-
});
|
|
437
|
+
return packages.map((pkg) => (
|
|
438
|
+
<PackageRow
|
|
439
|
+
key={pkg.packageId}
|
|
440
|
+
pkg={pkg}
|
|
441
|
+
onUpdated={() => refreshKey(refreshKey() + 1)}
|
|
442
|
+
/>
|
|
443
|
+
));
|
|
285
444
|
}}
|
|
286
445
|
loadingIndicator={
|
|
287
446
|
<div className="d-flex justify-content-center" style={{ height: 200 }}>
|