@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
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @peers-app/peers-ui
|
|
2
|
+
|
|
3
|
+
React components and hooks for building [Peers](https://peers.app) package UIs.
|
|
4
|
+
|
|
5
|
+
## What is Peers?
|
|
6
|
+
|
|
7
|
+
Peers is a local-first personal computing platform. Your data lives on your devices, syncs peer-to-peer, and is end-to-end encrypted — no servers in the middle. You own your data and your identity. Build apps with the SDK or use AI coding tools to create them; either way, the platform handles sync, encryption, and persistence automatically.
|
|
8
|
+
|
|
9
|
+
## What this package provides
|
|
10
|
+
|
|
11
|
+
### React Hooks
|
|
12
|
+
|
|
13
|
+
Hooks that bridge `@peers-app/peers-sdk` observables to React 18 with concurrent rendering support.
|
|
14
|
+
|
|
15
|
+
| Hook | Purpose |
|
|
16
|
+
|------|---------|
|
|
17
|
+
| `useObservable` | Subscribe to an observable or computed; returns `[value, setter]` |
|
|
18
|
+
| `useObservableState` | Create a component-local observable that persists across renders |
|
|
19
|
+
| `usePromise` | Resolve a promise in a component with an optional initial value |
|
|
20
|
+
| `useSubscription` | Run a side-effect whenever an observable changes |
|
|
21
|
+
| `useOnScreen` | IntersectionObserver hook for visibility detection |
|
|
22
|
+
|
|
23
|
+
### Rich Text
|
|
24
|
+
|
|
25
|
+
- **`MarkdownEditor`** — Lexical-based rich text editor with @mention support, toolbar, and markdown serialization
|
|
26
|
+
- **`MarkdownWithMentions`** — Render markdown with resolved @user and $tool mentions
|
|
27
|
+
|
|
28
|
+
### Layout
|
|
29
|
+
|
|
30
|
+
- **`TabsLayout`** — Multi-tab application shell used by the Peers desktop and PWA clients
|
|
31
|
+
- **`SortableList`** — Drag-and-drop list powered by SortableJS
|
|
32
|
+
- **`InverseLazyList`** — Bottom-anchored lazy-loading list for chat-style UIs
|
|
33
|
+
- **`Tabs`** / **`ScreenTabBody`** — Tab strip and content container
|
|
34
|
+
|
|
35
|
+
### Communication
|
|
36
|
+
|
|
37
|
+
- **`ChatOverlay`** — Floating chat panel for AI assistant conversations
|
|
38
|
+
- **`VoiceIndicator`** — Visual indicator for active voice sessions
|
|
39
|
+
|
|
40
|
+
### Tab Management
|
|
41
|
+
|
|
42
|
+
| Export | Purpose |
|
|
43
|
+
|--------|---------|
|
|
44
|
+
| `goToTabPath` | Navigate to a path within the Peers tab system |
|
|
45
|
+
| `activeTabId` | Observable of the currently active tab ID |
|
|
46
|
+
| `activeTabs` | Observable of all open tabs |
|
|
47
|
+
| `closeCurrentTab` | Close the active tab |
|
|
48
|
+
| `updateActiveTabTitle` | Set the title of the active tab |
|
|
49
|
+
| `TabState` | Tab state management class |
|
|
50
|
+
| `mainContentPath` | Hash-synced route observable |
|
|
51
|
+
|
|
52
|
+
## Peer Dependencies
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"@peers-app/peers-sdk": "^0.18.8",
|
|
57
|
+
"bootstrap": "^5.3.3",
|
|
58
|
+
"react": "^18.0.0",
|
|
59
|
+
"react-dom": "^18.0.0"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install @peers-app/peers-ui
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The fastest way to build a Peers package with a UI is to start from the template:
|
|
70
|
+
|
|
71
|
+
**[peers-package-template](https://github.com/peers-app/peers-package-template)** — scaffold, build, and install a custom package into the Peers runtime.
|
|
72
|
+
|
|
73
|
+
Package UI bundles receive `PeersUI` as a webpack external, so you can import hooks and components without bundling them into your package.
|
|
74
|
+
|
|
75
|
+
## Links
|
|
76
|
+
|
|
77
|
+
- [peers.app](https://peers.app) — try Peers in your browser or download the desktop app
|
|
78
|
+
- [Documentation](https://peers-app.github.io) — architecture, package development, and API reference
|
|
79
|
+
- [GitHub](https://github.com/peers-app) — source repositories and package template
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/dist/index.d.ts
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";
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.updateActiveTabTitle = exports.goToTabPath = exports.closeCurrentTab = exports.activeTabs = exports.activeTabId = exports.mainContentPath = exports.MarkdownEditor = void 0;
|
|
17
|
+
exports.updateActiveTabTitle = exports.goToTabPath = exports.closeCurrentTab = exports.activeTabs = exports.activeTabId = exports.mainContentPath = exports.Typeahead = exports.MarkdownEditor = void 0;
|
|
18
18
|
__exportStar(require("./components/chat-overlay"), exports);
|
|
19
19
|
__exportStar(require("./components/inverse-lazy-list"), exports);
|
|
20
20
|
var editor_1 = require("./components/markdown-editor/editor");
|
|
@@ -23,6 +23,8 @@ __exportStar(require("./components/markdown-editor/editor-inline"), exports);
|
|
|
23
23
|
__exportStar(require("./components/markdown-with-mentions"), exports);
|
|
24
24
|
__exportStar(require("./components/sortable-list"), exports);
|
|
25
25
|
__exportStar(require("./components/tabs"), exports);
|
|
26
|
+
var typeahead_1 = require("./components/typeahead");
|
|
27
|
+
Object.defineProperty(exports, "Typeahead", { enumerable: true, get: function () { return typeahead_1.Typeahead; } });
|
|
26
28
|
__exportStar(require("./components/voice-indicator"), exports);
|
|
27
29
|
var globals_1 = require("./globals");
|
|
28
30
|
Object.defineProperty(exports, "mainContentPath", { enumerable: true, get: function () { return globals_1.mainContentPath; } });
|
|
@@ -111,9 +111,7 @@ function GroupList() {
|
|
|
111
111
|
});
|
|
112
112
|
await (0, peers_sdk_1.Groups)(groupDataContext).signAndSave(group);
|
|
113
113
|
// Auto-install peers-core for the new group
|
|
114
|
-
await peers_sdk_1.rpcServerCalls
|
|
115
|
-
.addOrUpdatePackage(peers_sdk_1.peersCorePackageId, { dataContextId: groupId })
|
|
116
|
-
.catch((err) => {
|
|
114
|
+
await peers_sdk_1.rpcServerCalls.seedBundledPeersCore(groupId).catch((err) => {
|
|
117
115
|
console.error("Error auto-installing peers-core for group:", err);
|
|
118
116
|
});
|
|
119
117
|
(0, globals_1.mainContentPath)(`groups/${group.groupId}`);
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.PackageDetails = void 0;
|
|
7
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
5
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
9
|
-
const react_1 = __importDefault(require("react"));
|
|
10
6
|
const input_1 = require("../../components/input");
|
|
11
7
|
const loading_indicator_1 = require("../../components/loading-indicator");
|
|
12
8
|
const save_button_1 = require("../../components/save-button");
|
|
@@ -15,9 +11,9 @@ const hooks_1 = require("../../hooks");
|
|
|
15
11
|
const tabs_state_1 = require("../../tabs-layout/tabs-state");
|
|
16
12
|
const package_info_1 = require("./package-info");
|
|
17
13
|
const package_versions_1 = require("./package-versions");
|
|
14
|
+
/** Detail screen for a single package with Info, Versions, Components, and Dependencies tabs. */
|
|
18
15
|
const PackageDetails = (props) => {
|
|
19
16
|
const refresh = (0, hooks_1.useObservableState)(Date.now());
|
|
20
|
-
const saveDeviceTagRef = react_1.default.useRef(null);
|
|
21
17
|
const pkg = (0, hooks_1.usePromise)(async () => {
|
|
22
18
|
const pkg = await (0, peers_sdk_1.Packages)().get(props.packageId);
|
|
23
19
|
if (!pkg) {
|
|
@@ -61,11 +57,10 @@ const PackageDetails = (props) => {
|
|
|
61
57
|
}
|
|
62
58
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex", children: [(0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("h4", { children: (0, jsx_runtime_1.jsx)("i", { className: "bi bi-box-fill me-2" }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "flex-grow-1", children: (0, jsx_runtime_1.jsx)("h4", { children: (0, jsx_runtime_1.jsx)(input_1.Input, { className: "border border-0", style: { width: "100%", outline: "none", backgroundColor: "transparent" }, value: pkg.qs.name }, pkg.packageId) }) }), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(save_button_1.SaveButton, { doc: pkg, onClick: async () => {
|
|
63
59
|
await pkg.save();
|
|
64
|
-
await saveDeviceTagRef.current?.();
|
|
65
60
|
}, addActions: [...addActions] }, pkg.packageId) })] }), (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: [
|
|
66
61
|
{
|
|
67
62
|
name: "Info",
|
|
68
|
-
content: ((0, jsx_runtime_1.jsx)(tabs_1.ScreenTabBody, { children: (0, jsx_runtime_1.jsx)(package_info_1.PackageInfo, { pkg: pkg
|
|
63
|
+
content: ((0, jsx_runtime_1.jsx)(tabs_1.ScreenTabBody, { children: (0, jsx_runtime_1.jsx)(package_info_1.PackageInfo, { pkg: pkg }) })),
|
|
69
64
|
},
|
|
70
65
|
{
|
|
71
66
|
name: "Versions",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type IPackageVersion } from "@peers-app/peers-sdk";
|
|
2
|
+
/** Severity of a version update: major > minor > patch. */
|
|
3
|
+
export type UpdateLevel = "major" | "minor" | "patch" | null;
|
|
4
|
+
/** Result of checking a package's version status against available versions. */
|
|
5
|
+
export interface IVersionStatus {
|
|
6
|
+
activePv: IPackageVersion;
|
|
7
|
+
newerLevel: UpdateLevel;
|
|
8
|
+
newestPv: IPackageVersion | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Check a package's version status: what's currently active, whether a newer
|
|
12
|
+
* version exists within the follow policy, and which version is the best
|
|
13
|
+
* upgrade candidate.
|
|
14
|
+
*
|
|
15
|
+
* @param packageId - The package to check.
|
|
16
|
+
* @param activePackageVersionId - The effective active version (device or group).
|
|
17
|
+
* @param followVersionTags - Tag filter from effective prefs.
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkVersionStatus(packageId: string, activePackageVersionId: string, followVersionTags: string | undefined): Promise<IVersionStatus | null>;
|
|
20
|
+
/** Whether the current user is an admin of the active group. */
|
|
21
|
+
export declare function isGroupAdmin(): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Activate a specific package version, handling group-level vs device-local
|
|
24
|
+
* activation. When the device has no follow override and the user is a group
|
|
25
|
+
* admin, the group's `activePackageVersionId` is advanced so all devices
|
|
26
|
+
* stay in sync. Otherwise only the device-local prefs are updated.
|
|
27
|
+
*
|
|
28
|
+
* @param packageId - The package to activate a version for.
|
|
29
|
+
* @param pv - The package version to activate.
|
|
30
|
+
*/
|
|
31
|
+
export declare function activatePackageVersion(packageId: string, pv: IPackageVersion): Promise<void>;
|
|
32
|
+
/** Numeric rank for sorting packages by update urgency. */
|
|
33
|
+
export declare const updateLevelRank: Record<string, number>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.updateLevelRank = void 0;
|
|
4
|
+
exports.checkVersionStatus = checkVersionStatus;
|
|
5
|
+
exports.isGroupAdmin = isGroupAdmin;
|
|
6
|
+
exports.activatePackageVersion = activatePackageVersion;
|
|
7
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
8
|
+
/**
|
|
9
|
+
* Check a package's version status: what's currently active, whether a newer
|
|
10
|
+
* version exists within the follow policy, and which version is the best
|
|
11
|
+
* upgrade candidate.
|
|
12
|
+
*
|
|
13
|
+
* @param packageId - The package to check.
|
|
14
|
+
* @param activePackageVersionId - The effective active version (device or group).
|
|
15
|
+
* @param followVersionTags - Tag filter from effective prefs.
|
|
16
|
+
*/
|
|
17
|
+
async function checkVersionStatus(packageId, activePackageVersionId, followVersionTags) {
|
|
18
|
+
const all = await (0, peers_sdk_1.PackageVersions)().list({ packageId });
|
|
19
|
+
const active = all.find((v) => v.packageVersionId === activePackageVersionId);
|
|
20
|
+
if (!active)
|
|
21
|
+
return null;
|
|
22
|
+
let newerLevel = null;
|
|
23
|
+
let newestPv = null;
|
|
24
|
+
if (active.version) {
|
|
25
|
+
const parse = (v) => v.split(".").map(Number);
|
|
26
|
+
const [aMaj, aMin, aPat] = parse(active.version);
|
|
27
|
+
let bestVersion = active.version;
|
|
28
|
+
for (const v of all) {
|
|
29
|
+
if (!v.version || v.packageVersionId === activePackageVersionId)
|
|
30
|
+
continue;
|
|
31
|
+
if (!(0, peers_sdk_1.doesTagMatch)(active.versionTag, v.versionTag, followVersionTags, undefined))
|
|
32
|
+
continue;
|
|
33
|
+
const [maj, min, pat] = parse(v.version);
|
|
34
|
+
const [bMaj, bMin, bPat] = parse(bestVersion);
|
|
35
|
+
let candidateLevel = null;
|
|
36
|
+
if (maj > aMaj)
|
|
37
|
+
candidateLevel = "major";
|
|
38
|
+
else if (maj === aMaj && min > aMin)
|
|
39
|
+
candidateLevel = "minor";
|
|
40
|
+
else if (maj === aMaj && min === aMin && pat > aPat)
|
|
41
|
+
candidateLevel = "patch";
|
|
42
|
+
else if (maj === aMaj &&
|
|
43
|
+
min === aMin &&
|
|
44
|
+
pat === aPat &&
|
|
45
|
+
(v.createdAt || "") > (active.createdAt || ""))
|
|
46
|
+
candidateLevel = "patch";
|
|
47
|
+
if (!candidateLevel)
|
|
48
|
+
continue;
|
|
49
|
+
const levelRank = { major: 3, minor: 2, patch: 1 };
|
|
50
|
+
if (!newerLevel || levelRank[candidateLevel] > levelRank[newerLevel]) {
|
|
51
|
+
newerLevel = candidateLevel;
|
|
52
|
+
}
|
|
53
|
+
const isHigherThanBest = maj > bMaj ||
|
|
54
|
+
(maj === bMaj && min > bMin) ||
|
|
55
|
+
(maj === bMaj && min === bMin && pat > bPat) ||
|
|
56
|
+
(maj === bMaj &&
|
|
57
|
+
min === bMin &&
|
|
58
|
+
pat === bPat &&
|
|
59
|
+
(v.createdAt || "") > (newestPv?.createdAt || active.createdAt || ""));
|
|
60
|
+
if (!newestPv || isHigherThanBest) {
|
|
61
|
+
newestPv = v;
|
|
62
|
+
bestVersion = v.version;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { activePv: active, newerLevel, newestPv };
|
|
67
|
+
}
|
|
68
|
+
/** Whether the current user is an admin of the active group. */
|
|
69
|
+
async function isGroupAdmin() {
|
|
70
|
+
try {
|
|
71
|
+
const userCtx = await (0, peers_sdk_1.getUserContext)();
|
|
72
|
+
const dc = userCtx.defaultDataContext();
|
|
73
|
+
const contextId = dc?.groupId || dc?.dataContextId;
|
|
74
|
+
if (!contextId)
|
|
75
|
+
return false;
|
|
76
|
+
const role = await (0, peers_sdk_1.getUserRole)(contextId, userCtx.userId);
|
|
77
|
+
return role >= peers_sdk_1.GroupMemberRole.Admin;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Activate a specific package version, handling group-level vs device-local
|
|
85
|
+
* activation. When the device has no follow override and the user is a group
|
|
86
|
+
* admin, the group's `activePackageVersionId` is advanced so all devices
|
|
87
|
+
* stay in sync. Otherwise only the device-local prefs are updated.
|
|
88
|
+
*
|
|
89
|
+
* @param packageId - The package to activate a version for.
|
|
90
|
+
* @param pv - The package version to activate.
|
|
91
|
+
*/
|
|
92
|
+
async function activatePackageVersion(packageId, pv) {
|
|
93
|
+
const prefsVar = (0, peers_sdk_1.packagePrefsVar)(packageId);
|
|
94
|
+
await prefsVar.loadingPromise;
|
|
95
|
+
const devicePrefs = prefsVar();
|
|
96
|
+
const activatesGroup = pv.versionTag !== "dev" && !(0, peers_sdk_1.hasDeviceFollowOverride)(devicePrefs) && (await isGroupAdmin());
|
|
97
|
+
if (activatesGroup) {
|
|
98
|
+
const current = await (0, peers_sdk_1.Packages)().get(packageId);
|
|
99
|
+
if (current) {
|
|
100
|
+
current.activePackageVersionId = pv.packageVersionId;
|
|
101
|
+
await (0, peers_sdk_1.Packages)().signAndSave(current);
|
|
102
|
+
}
|
|
103
|
+
await (0, peers_sdk_1.updatePackagePrefs)(packageId, { activePackageVersionId: undefined });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
await (0, peers_sdk_1.updatePackagePrefs)(packageId, { activePackageVersionId: pv.packageVersionId });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Numeric rank for sorting packages by update urgency. */
|
|
110
|
+
exports.updateLevelRank = { major: 3, minor: 2, patch: 1 };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type IDoc, type IPackage } from "@peers-app/peers-sdk";
|
|
2
|
-
|
|
2
|
+
/** Info tab for the package details screen. */
|
|
3
3
|
export declare const PackageInfo: (props: {
|
|
4
4
|
pkg: IDoc<IPackage>;
|
|
5
|
-
saveDeviceTagRef?: React.MutableRefObject<(() => Promise<void>) | null>;
|
|
6
5
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.PackageInfo = void 0;
|
|
7
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
5
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
9
|
-
const react_1 = __importDefault(require("react"));
|
|
10
6
|
const input_1 = require("../../components/input");
|
|
11
7
|
const markdown_with_mentions_1 = require("../../components/markdown-with-mentions");
|
|
12
8
|
const tooltip_1 = require("../../components/tooltip");
|
|
13
9
|
const hooks_1 = require("../../hooks");
|
|
10
|
+
/** Info tab for the package details screen. */
|
|
14
11
|
const PackageInfo = (props) => {
|
|
15
12
|
const { pkg } = props;
|
|
16
13
|
const [followVersionTags] = (0, hooks_1.useObservable)(pkg.qs.followVersionTags);
|
|
14
|
+
const [versionFollowRange] = (0, hooks_1.useObservable)(pkg.qs.versionFollowRange);
|
|
17
15
|
const localPathVar = (0, peers_sdk_1.groupDeviceVar)(`packageLocalPath_${pkg.packageId}`, {
|
|
18
16
|
defaultValue: `${peers_sdk_1.packagesRootDir}/${pkg.name}`,
|
|
19
17
|
});
|
|
@@ -21,43 +19,13 @@ const PackageInfo = (props) => {
|
|
|
21
19
|
const [devicePrefs] = (0, hooks_1.useObservable)((0, peers_sdk_1.packagePrefsVar)(pkg.packageId));
|
|
22
20
|
const effective = (0, peers_sdk_1.getEffectivePackagePrefs)(pkg.toJS(), devicePrefs);
|
|
23
21
|
const activeVersionId = effective.activePackageVersionId;
|
|
24
|
-
const deviceFollowTags = devicePrefs?.followTags;
|
|
25
22
|
const isPinned = effective.isPinned;
|
|
26
23
|
const followRange = effective.followRange;
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
}, [deviceFollowTags]);
|
|
34
|
-
const deviceTagDirty = deviceTagDraft.trim() !== (deviceFollowTags || "");
|
|
35
|
-
const prevDirtyRef = react_1.default.useRef(false);
|
|
36
|
-
react_1.default.useEffect(() => {
|
|
37
|
-
if (deviceTagDirty && !prevDirtyRef.current) {
|
|
38
|
-
pkg.q((pkg.q() || 0) + 1);
|
|
39
|
-
}
|
|
40
|
-
else if (!deviceTagDirty && prevDirtyRef.current) {
|
|
41
|
-
pkg.q(Math.max(0, (pkg.q() || 0) - 1));
|
|
42
|
-
}
|
|
43
|
-
prevDirtyRef.current = deviceTagDirty;
|
|
44
|
-
}, [deviceTagDirty, pkg.q]);
|
|
45
|
-
if (props.saveDeviceTagRef) {
|
|
46
|
-
props.saveDeviceTagRef.current = async () => {
|
|
47
|
-
const val = deviceTagDraft.trim();
|
|
48
|
-
if (val !== (deviceFollowTags || "")) {
|
|
49
|
-
savingRef.current = true;
|
|
50
|
-
try {
|
|
51
|
-
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, {
|
|
52
|
-
followTags: val || undefined,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
savingRef.current = false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
24
|
+
const hasOverride = (0, peers_sdk_1.hasDeviceFollowOverride)(devicePrefs);
|
|
25
|
+
const groupFollowTags = followVersionTags === "stable,beta" ? "stable,beta" : "stable";
|
|
26
|
+
const groupRange = versionFollowRange || "latest";
|
|
27
|
+
const effectiveFollowTags = effective.followTags || "stable";
|
|
28
|
+
const followTagsValue = effectiveFollowTags === "stable,beta" ? "stable,beta" : "stable";
|
|
61
29
|
const activeVersion = (0, hooks_1.usePromise)(async () => {
|
|
62
30
|
if (!activeVersionId)
|
|
63
31
|
return null;
|
|
@@ -70,7 +38,6 @@ const PackageInfo = (props) => {
|
|
|
70
38
|
const active = all.find((v) => v.packageVersionId === activeVersionId);
|
|
71
39
|
if (!active?.version)
|
|
72
40
|
return "uptodate";
|
|
73
|
-
const effectiveFollowTags = deviceFollowTags || followVersionTags;
|
|
74
41
|
const parse = (v) => v.split(".").map(Number);
|
|
75
42
|
const [aMaj, aMin, aPat] = parse(active.version);
|
|
76
43
|
let highest = null;
|
|
@@ -99,18 +66,56 @@ const PackageInfo = (props) => {
|
|
|
99
66
|
}
|
|
100
67
|
}
|
|
101
68
|
return highest || "uptodate";
|
|
102
|
-
}, undefined, [activeVersionId, pkg.packageId,
|
|
69
|
+
}, undefined, [activeVersionId, pkg.packageId, effectiveFollowTags]);
|
|
103
70
|
const remoteRepoUrl = pkg.remoteRepo;
|
|
104
71
|
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: () => {
|
|
105
72
|
peers_sdk_1.rpcServerCalls.openPackage(localPath || peers_sdk_1.packagesRootDir);
|
|
106
73
|
}, 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: () => {
|
|
107
74
|
peers_sdk_1.rpcServerCalls.openLinkInBrowser(remoteRepoUrl);
|
|
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
|
|
75
|
+
}, 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 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." })] }), (0, jsx_runtime_1.jsxs)("select", { className: "form-select form-select-sm", value: groupRange, onChange: async (e) => {
|
|
76
|
+
const val = e.target.value;
|
|
77
|
+
const current = await (0, peers_sdk_1.Packages)().get(pkg.packageId);
|
|
78
|
+
if (current) {
|
|
79
|
+
current.versionFollowRange = val;
|
|
80
|
+
await (0, peers_sdk_1.Packages)().signAndSave(current);
|
|
81
|
+
await pkg.load();
|
|
82
|
+
}
|
|
83
|
+
}, 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 ${groupRange === "pinned" ? "opacity-50" : ""}`, children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Following:", (0, jsx_runtime_1.jsx)(tooltip_1.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." })] }), (0, jsx_runtime_1.jsxs)("select", { className: "form-select form-select-sm", value: groupFollowTags, disabled: groupRange === "pinned", onChange: async (e) => {
|
|
109
84
|
const val = e.target.value;
|
|
110
|
-
await (0, peers_sdk_1.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
85
|
+
const current = await (0, peers_sdk_1.Packages)().get(pkg.packageId);
|
|
86
|
+
if (current) {
|
|
87
|
+
current.followVersionTags = val === "stable" ? undefined : val;
|
|
88
|
+
await (0, peers_sdk_1.Packages)().signAndSave(current);
|
|
89
|
+
await pkg.load();
|
|
90
|
+
}
|
|
91
|
+
}, children: [(0, jsx_runtime_1.jsx)("option", { value: "stable", children: "Stable" }), (0, jsx_runtime_1.jsx)("option", { value: "stable,beta", children: "Stable + Beta" })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "mb-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "form-check", children: [(0, jsx_runtime_1.jsx)("input", { className: "form-check-input", type: "checkbox", id: `deviceOverride_${pkg.packageId}`, checked: hasOverride, onChange: async (e) => {
|
|
92
|
+
if (e.target.checked) {
|
|
93
|
+
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, {
|
|
94
|
+
followRange: groupRange === "pinned"
|
|
95
|
+
? undefined
|
|
96
|
+
: groupRange,
|
|
97
|
+
followTags: followVersionTags || undefined,
|
|
98
|
+
pinned: groupRange === "pinned" ? true : undefined,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, {
|
|
103
|
+
followRange: undefined,
|
|
104
|
+
followTags: undefined,
|
|
105
|
+
pinned: undefined,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} }), (0, jsx_runtime_1.jsx)("label", { className: "form-check-label", htmlFor: `deviceOverride_${pkg.packageId}`, children: (0, jsx_runtime_1.jsx)("small", { children: "Override on this device" }) }), (0, jsx_runtime_1.jsx)("small", { children: (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { 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.` }) })] }) }), hasOverride && ((0, jsx_runtime_1.jsxs)("div", { className: "ps-3 border-start mb-3", children: [(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 only**. Upgrades here do not change the group's active version." })] }), (0, jsx_runtime_1.jsxs)("select", { className: "form-select form-select-sm", value: isPinned ? "pinned" : followRange, onChange: async (e) => {
|
|
109
|
+
const val = e.target.value;
|
|
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: isPinned ? "opacity-50" : "", children: [(0, jsx_runtime_1.jsxs)("small", { children: ["Following (this device):", (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { markdownContent: "Which release channel to follow on **this device only**, overriding the group setting." })] }), (0, jsx_runtime_1.jsxs)("select", { className: "form-select form-select-sm", value: followTagsValue, disabled: isPinned, onChange: async (e) => {
|
|
115
|
+
const val = e.target.value;
|
|
116
|
+
await (0, peers_sdk_1.updatePackagePrefs)(pkg.packageId, {
|
|
117
|
+
followTags: val === "stable" ? "stable" : val,
|
|
118
|
+
});
|
|
119
|
+
}, children: [(0, jsx_runtime_1.jsx)("option", { value: "stable", children: "Stable" }), (0, jsx_runtime_1.jsx)("option", { value: "stable,beta", children: "Stable + Beta" })] })] })] }))] }), (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 || "" })] })] }));
|
|
115
120
|
};
|
|
116
121
|
exports.PackageInfo = PackageInfo;
|