@peers-app/peers-ui 0.12.5 → 0.13.0
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/components/group-switcher.js +3 -3
- package/dist/globals.js +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +7 -1
- package/dist/screens/packages/package-versions.js +76 -25
- package/dist/screens/settings/settings-page.js +18 -1
- package/dist/tabs-layout/tabs-layout.js +9 -7
- package/dist/tabs-layout/tabs-state.js +4 -1
- package/package.json +3 -3
- package/src/components/group-switcher.tsx +9 -9
- package/src/globals.tsx +2 -2
- package/src/index.tsx +3 -1
- package/src/screens/packages/package-versions.tsx +186 -71
- package/src/screens/settings/settings-page.tsx +29 -1
- package/src/tabs-layout/tabs-layout.tsx +10 -10
- package/src/tabs-layout/tabs-state.ts +4 -1
|
@@ -110,17 +110,17 @@ function GroupSwitcher({ colorMode, isMobile = false }) {
|
|
|
110
110
|
maxHeight: '300px',
|
|
111
111
|
overflowY: 'auto'
|
|
112
112
|
} },
|
|
113
|
-
react_1.default.createElement("
|
|
113
|
+
react_1.default.createElement("button", { className: `dropdown-item d-flex align-items-center ${!currentGroup ? 'active' : ''}`, style: { cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left' }, onClick: () => handleGroupSelect(null) },
|
|
114
114
|
react_1.default.createElement("i", { className: "bi-person-fill me-2" }),
|
|
115
115
|
react_1.default.createElement("span", null, "Personal")),
|
|
116
116
|
allGroups.length > 0 && (react_1.default.createElement("div", { className: "dropdown-divider" })),
|
|
117
117
|
allGroups
|
|
118
118
|
.filter((g) => !g.disabled)
|
|
119
|
-
.map((group) => (react_1.default.createElement("
|
|
119
|
+
.map((group) => (react_1.default.createElement("button", { key: group.groupId, className: `dropdown-item d-flex align-items-center ${currentGroup?.groupId === group.groupId ? 'active' : ''}`, style: { cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left' }, onClick: () => handleGroupSelect(group) },
|
|
120
120
|
react_1.default.createElement("i", { className: `${getGroupIcon(group)} me-2` }),
|
|
121
121
|
react_1.default.createElement("span", { className: "text-truncate" }, group.name)))),
|
|
122
122
|
react_1.default.createElement("div", { className: "dropdown-divider" }),
|
|
123
|
-
react_1.default.createElement("
|
|
123
|
+
react_1.default.createElement("button", { className: "dropdown-item d-flex align-items-center", style: { cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left' }, onClick: handleCreateClick },
|
|
124
124
|
react_1.default.createElement("i", { className: "bi-plus-circle me-2" }),
|
|
125
125
|
react_1.default.createElement("span", null, "Create Group")))),
|
|
126
126
|
showDropdown && (react_1.default.createElement("div", { className: "position-fixed w-100 h-100", style: { top: 0, left: 0, zIndex: 999 }, onClick: () => setShowDropdown(false) }))),
|
package/dist/globals.js
CHANGED
|
@@ -124,11 +124,11 @@ async function loadGlobals() {
|
|
|
124
124
|
await peers_sdk_1.rpcServerCalls.addOrUpdatePackage('updateAll');
|
|
125
125
|
}
|
|
126
126
|
await Promise.all([
|
|
127
|
-
(0, peers_sdk_1.Groups)().list().then(exports.groups),
|
|
127
|
+
(0, peers_sdk_1.Groups)().list().then(r => (0, exports.groups)(r)),
|
|
128
128
|
exports._mainContentPath.loadingPromise,
|
|
129
129
|
exports.openThreads.loadingPromise,
|
|
130
130
|
exports.threadViewOpen.loadingPromise,
|
|
131
|
-
(0, peers_sdk_1.sleep)(100),
|
|
131
|
+
(0, peers_sdk_1.sleep)(100),
|
|
132
132
|
]);
|
|
133
133
|
function updateWindowHash() {
|
|
134
134
|
const currentPath = window.location.hash.substring(1);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export * from "./hooks";
|
|
1
2
|
export * from "./screens/events/cron";
|
|
2
3
|
export * from "./tabs-layout/tabs-layout";
|
|
3
|
-
export { activeTabId, activeTabs, TabState } from "./tabs-layout/tabs-state";
|
|
4
|
+
export { activeTabId, activeTabs, TabState, updateActiveTabTitle, closeCurrentTab, goToTabPath } from "./tabs-layout/tabs-state";
|
|
5
|
+
export { mainContentPath } from "./globals";
|
|
4
6
|
export * from "./components/voice-indicator";
|
|
5
7
|
export * from "./components/chat-overlay";
|
|
6
8
|
export * from "./components/sortable-list";
|
package/dist/index.js
CHANGED
|
@@ -14,12 +14,18 @@ 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.activeTabs = exports.activeTabId = void 0;
|
|
17
|
+
exports.mainContentPath = exports.goToTabPath = exports.closeCurrentTab = exports.updateActiveTabTitle = exports.activeTabs = exports.activeTabId = void 0;
|
|
18
|
+
__exportStar(require("./hooks"), exports);
|
|
18
19
|
__exportStar(require("./screens/events/cron"), exports);
|
|
19
20
|
__exportStar(require("./tabs-layout/tabs-layout"), exports);
|
|
20
21
|
var tabs_state_1 = require("./tabs-layout/tabs-state");
|
|
21
22
|
Object.defineProperty(exports, "activeTabId", { enumerable: true, get: function () { return tabs_state_1.activeTabId; } });
|
|
22
23
|
Object.defineProperty(exports, "activeTabs", { enumerable: true, get: function () { return tabs_state_1.activeTabs; } });
|
|
24
|
+
Object.defineProperty(exports, "updateActiveTabTitle", { enumerable: true, get: function () { return tabs_state_1.updateActiveTabTitle; } });
|
|
25
|
+
Object.defineProperty(exports, "closeCurrentTab", { enumerable: true, get: function () { return tabs_state_1.closeCurrentTab; } });
|
|
26
|
+
Object.defineProperty(exports, "goToTabPath", { enumerable: true, get: function () { return tabs_state_1.goToTabPath; } });
|
|
27
|
+
var globals_1 = require("./globals");
|
|
28
|
+
Object.defineProperty(exports, "mainContentPath", { enumerable: true, get: function () { return globals_1.mainContentPath; } });
|
|
23
29
|
__exportStar(require("./components/voice-indicator"), exports);
|
|
24
30
|
__exportStar(require("./components/chat-overlay"), exports);
|
|
25
31
|
__exportStar(require("./components/sortable-list"), exports);
|
|
@@ -136,6 +136,28 @@ const PackageVersionsList = (props) => {
|
|
|
136
136
|
setDeleting(null);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
async function promoteVersion(pv, newTag) {
|
|
140
|
+
try {
|
|
141
|
+
const pvHash = (0, peers_sdk_1.computePackageVersionHash)(pv.version, newTag, pv.packageBundleFileHash, pv.routesBundleFileHash, pv.uiBundleFileHash);
|
|
142
|
+
const updated = { ...pv, versionTag: newTag, packageVersionHash: pvHash };
|
|
143
|
+
await (0, peers_sdk_1.PackageVersions)().signAndSave(updated, { saveAsSnapshot: true });
|
|
144
|
+
refreshKey(refreshKey() + 1);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
alert(`Failed to promote version: ${err}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function updateVersion(pv, newSemver) {
|
|
151
|
+
try {
|
|
152
|
+
const pvHash = (0, peers_sdk_1.computePackageVersionHash)(newSemver, pv.versionTag || 'beta', pv.packageBundleFileHash, pv.routesBundleFileHash, pv.uiBundleFileHash);
|
|
153
|
+
const updated = { ...pv, version: newSemver, packageVersionHash: pvHash };
|
|
154
|
+
await (0, peers_sdk_1.PackageVersions)().signAndSave(updated, { saveAsSnapshot: true });
|
|
155
|
+
refreshKey(refreshKey() + 1);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
alert(`Failed to update version: ${err}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
139
161
|
async function pinVersion() {
|
|
140
162
|
setPinning(true);
|
|
141
163
|
try {
|
|
@@ -168,30 +190,59 @@ const PackageVersionsList = (props) => {
|
|
|
168
190
|
sorted.length,
|
|
169
191
|
" version",
|
|
170
192
|
sorted.length !== 1 ? 's' : ''),
|
|
171
|
-
react_1.default.createElement("div", { className: "list-group mt-2" }, sorted.map(pv => {
|
|
172
|
-
const isActive = pv.packageVersionId === activeVersionId;
|
|
173
|
-
const creatorName = userMap.get(pv.createdBy);
|
|
174
|
-
return (react_1.default.createElement("div", { key: pv.packageVersionId, className: `list-group-item ${isActive ? 'list-group-item-success' : ''}` },
|
|
175
|
-
react_1.default.createElement("div", { className: "d-flex align-items-center justify-content-between" },
|
|
176
|
-
react_1.default.createElement("div", null,
|
|
177
|
-
react_1.default.createElement("strong", { className: "me-2" },
|
|
178
|
-
"v",
|
|
179
|
-
pv.version),
|
|
180
|
-
pv.versionTag && (react_1.default.createElement("span", { className: `badge ${tagBadgeClass(pv.versionTag)} me-2` }, pv.versionTag)),
|
|
181
|
-
react_1.default.createElement("code", { className: "text-muted small me-2" }, pv.packageVersionHash?.substring(0, 8)),
|
|
182
|
-
react_1.default.createElement("small", { className: "text-muted me-2" }, formatDate(pv.createdAt)),
|
|
183
|
-
creatorName && (react_1.default.createElement("small", { className: "text-muted" },
|
|
184
|
-
"by ",
|
|
185
|
-
creatorName))),
|
|
186
|
-
react_1.default.createElement("div", { className: "d-flex gap-1" },
|
|
187
|
-
isActive ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
188
|
-
react_1.default.createElement("button", { className: "btn btn-sm btn-outline-secondary", disabled: isPinned || pinning, onClick: () => pinVersion(), title: isPinned ? 'Already pinned' : 'Pin to this version (disable auto-updates)' },
|
|
189
|
-
pinning ? (react_1.default.createElement("span", { className: "spinner-border spinner-border-sm" })) : (react_1.default.createElement("i", { className: "bi bi-pin-fill" })),
|
|
190
|
-
isPinned ? ' Pinned' : ' Pin'),
|
|
191
|
-
react_1.default.createElement("span", { className: "badge text-bg-success align-self-center" }, "active"))) : (react_1.default.createElement("button", { className: "btn btn-sm btn-outline-primary", disabled: activating === pv.packageVersionId, onClick: () => activateVersion(pv) },
|
|
192
|
-
activating === pv.packageVersionId ? (react_1.default.createElement("span", { className: "spinner-border spinner-border-sm me-1" })) : (react_1.default.createElement("i", { className: "bi bi-check2-circle me-1" })),
|
|
193
|
-
"Activate")),
|
|
194
|
-
!isActive && (react_1.default.createElement("button", { className: "btn btn-sm btn-outline-danger", disabled: deleting === pv.packageVersionId, onClick: () => deleteVersion(pv) }, deleting === pv.packageVersionId ? (react_1.default.createElement("span", { className: "spinner-border spinner-border-sm" })) : (react_1.default.createElement("i", { className: "bi bi-trash" }))))))));
|
|
195
|
-
}))));
|
|
193
|
+
react_1.default.createElement("div", { className: "list-group mt-2" }, sorted.map(pv => (react_1.default.createElement(VersionRow, { key: pv.packageVersionId, pv: pv, isActive: pv.packageVersionId === activeVersionId, isPinned: isPinned, pinning: pinning, activating: activating, deleting: deleting, userMap: userMap, onActivate: activateVersion, onDelete: deleteVersion, onPin: pinVersion, onPromote: promoteVersion, onUpdateVersion: updateVersion }))))));
|
|
196
194
|
};
|
|
197
195
|
exports.PackageVersionsList = PackageVersionsList;
|
|
196
|
+
const TAG_OPTIONS = ['beta', 'stable'];
|
|
197
|
+
function VersionRow(props) {
|
|
198
|
+
const { pv, isActive, isPinned, pinning, activating, deleting, userMap } = props;
|
|
199
|
+
const creatorName = userMap.get(pv.createdBy);
|
|
200
|
+
const [editingVersion, setEditingVersion] = (0, react_1.useState)(false);
|
|
201
|
+
const [versionDraft, setVersionDraft] = (0, react_1.useState)(pv.version);
|
|
202
|
+
function startEditing() {
|
|
203
|
+
setVersionDraft(pv.version);
|
|
204
|
+
setEditingVersion(true);
|
|
205
|
+
}
|
|
206
|
+
function commitVersion() {
|
|
207
|
+
const trimmed = versionDraft.trim();
|
|
208
|
+
if (trimmed && trimmed !== pv.version) {
|
|
209
|
+
props.onUpdateVersion(pv, trimmed);
|
|
210
|
+
}
|
|
211
|
+
setEditingVersion(false);
|
|
212
|
+
}
|
|
213
|
+
const currentTag = pv.versionTag || 'beta';
|
|
214
|
+
const promoteTargets = TAG_OPTIONS.filter(t => t !== currentTag);
|
|
215
|
+
return (react_1.default.createElement("div", { className: `list-group-item ${isActive ? 'list-group-item-success' : ''}` },
|
|
216
|
+
react_1.default.createElement("div", { className: "d-flex align-items-center justify-content-between" },
|
|
217
|
+
react_1.default.createElement("div", { className: "d-flex align-items-center flex-wrap" },
|
|
218
|
+
editingVersion ? (react_1.default.createElement("input", { type: "text", className: "form-control form-control-sm me-2", style: { width: '7em' }, value: versionDraft, onChange: e => setVersionDraft(e.target.value), onBlur: commitVersion, onKeyDown: e => {
|
|
219
|
+
if (e.key === 'Enter')
|
|
220
|
+
commitVersion();
|
|
221
|
+
if (e.key === 'Escape')
|
|
222
|
+
setEditingVersion(false);
|
|
223
|
+
}, autoFocus: true })) : (react_1.default.createElement("strong", { className: "me-2", style: { cursor: 'pointer' }, onClick: startEditing, title: "Click to edit version" },
|
|
224
|
+
"v",
|
|
225
|
+
pv.version)),
|
|
226
|
+
pv.versionTag && (react_1.default.createElement("span", { className: `badge ${tagBadgeClass(pv.versionTag)} me-2` }, pv.versionTag)),
|
|
227
|
+
react_1.default.createElement("code", { className: "text-muted small me-2" }, pv.packageVersionHash?.substring(0, 8)),
|
|
228
|
+
react_1.default.createElement("small", { className: "text-muted me-2" }, formatDate(pv.createdAt)),
|
|
229
|
+
creatorName && (react_1.default.createElement("small", { className: "text-muted" },
|
|
230
|
+
"by ",
|
|
231
|
+
creatorName))),
|
|
232
|
+
react_1.default.createElement("div", { className: "d-flex gap-1 align-items-center" },
|
|
233
|
+
promoteTargets.length > 0 && (react_1.default.createElement("div", { className: "dropdown" },
|
|
234
|
+
react_1.default.createElement("button", { className: "btn btn-sm btn-outline-info dropdown-toggle", "data-bs-toggle": "dropdown", title: "Change version tag" }, "Promote"),
|
|
235
|
+
react_1.default.createElement("ul", { className: "dropdown-menu dropdown-menu-end" }, promoteTargets.map(tag => (react_1.default.createElement("li", { key: tag },
|
|
236
|
+
react_1.default.createElement("button", { className: "dropdown-item", onClick: () => props.onPromote(pv, tag) },
|
|
237
|
+
currentTag,
|
|
238
|
+
" \u2192 ",
|
|
239
|
+
tag))))))),
|
|
240
|
+
isActive ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
241
|
+
react_1.default.createElement("button", { className: "btn btn-sm btn-outline-secondary", disabled: isPinned || pinning, onClick: () => props.onPin(), title: isPinned ? 'Already pinned' : 'Pin to this version (disable auto-updates)' },
|
|
242
|
+
pinning ? (react_1.default.createElement("span", { className: "spinner-border spinner-border-sm" })) : (react_1.default.createElement("i", { className: "bi bi-pin-fill" })),
|
|
243
|
+
isPinned ? ' Pinned' : ' Pin'),
|
|
244
|
+
react_1.default.createElement("span", { className: "badge text-bg-success align-self-center" }, "active"))) : (react_1.default.createElement("button", { className: "btn btn-sm btn-outline-primary", disabled: activating === pv.packageVersionId, onClick: () => props.onActivate(pv) },
|
|
245
|
+
activating === pv.packageVersionId ? (react_1.default.createElement("span", { className: "spinner-border spinner-border-sm me-1" })) : (react_1.default.createElement("i", { className: "bi bi-check2-circle me-1" })),
|
|
246
|
+
"Activate")),
|
|
247
|
+
!isActive && (react_1.default.createElement("button", { className: "btn btn-sm btn-outline-danger", disabled: deleting === pv.packageVersionId, onClick: () => props.onDelete(pv) }, deleting === pv.packageVersionId ? (react_1.default.createElement("span", { className: "spinner-border spinner-border-sm" })) : (react_1.default.createElement("i", { className: "bi bi-trash" }))))))));
|
|
248
|
+
}
|
|
@@ -67,7 +67,9 @@ const SettingsPage = () => {
|
|
|
67
67
|
};
|
|
68
68
|
exports.SettingsPage = SettingsPage;
|
|
69
69
|
const UserSettingsTab = () => {
|
|
70
|
-
return (react_1.default.createElement(
|
|
70
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
71
|
+
react_1.default.createElement(ProfileSection, null),
|
|
72
|
+
react_1.default.createElement(LogoutSection, null)));
|
|
71
73
|
};
|
|
72
74
|
const AppearanceSettingsTab = () => {
|
|
73
75
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
@@ -234,3 +236,18 @@ const DeleteLocalDatabase = () => {
|
|
|
234
236
|
}
|
|
235
237
|
} }, "Delete Local Database")));
|
|
236
238
|
};
|
|
239
|
+
const showLogoutInSettings = typeof window !== 'undefined'
|
|
240
|
+
&& !window.electronAPI
|
|
241
|
+
&& !window.ReactNativeWebView;
|
|
242
|
+
const LogoutSection = () => {
|
|
243
|
+
if (!showLogoutInSettings)
|
|
244
|
+
return null;
|
|
245
|
+
return (react_1.default.createElement("div", { className: "mt-4 pt-3 border-top" },
|
|
246
|
+
react_1.default.createElement("button", { className: "btn btn-outline-danger btn-sm", onClick: async () => {
|
|
247
|
+
const confirmed = confirm('Are you sure you want to logout? This will clear your credentials from this browser. ' +
|
|
248
|
+
'Make sure you have your User ID and Secret Key saved securely, or you will lose access to your account.');
|
|
249
|
+
if (!confirmed)
|
|
250
|
+
return;
|
|
251
|
+
await peers_sdk_1.rpcServerCalls.logout();
|
|
252
|
+
} }, "Logout")));
|
|
253
|
+
};
|
|
@@ -96,14 +96,13 @@ function TabsLayout(props) {
|
|
|
96
96
|
userContextInitialized = true;
|
|
97
97
|
}
|
|
98
98
|
const loaded = (0, hooks_1.usePromise)(async () => {
|
|
99
|
-
await (0, globals_1.loadGlobals)()
|
|
100
|
-
await (0, routes_loader_1.loadAllRoutes)()
|
|
101
|
-
// Wait for tab state and welcome modal state to load from database
|
|
99
|
+
await (0, globals_1.loadGlobals)();
|
|
100
|
+
await (0, routes_loader_1.loadAllRoutes)();
|
|
102
101
|
await Promise.all([
|
|
103
102
|
tabs_state_1.activeTabs.loadingPromise,
|
|
104
103
|
tabs_state_1.activeTabId.loadingPromise,
|
|
105
104
|
tabs_state_1.recentlyUsedApps.loadingPromise,
|
|
106
|
-
peers_sdk_1.hasShownWelcomeModal.loadingPromise
|
|
105
|
+
peers_sdk_1.hasShownWelcomeModal.loadingPromise,
|
|
107
106
|
]);
|
|
108
107
|
return true;
|
|
109
108
|
});
|
|
@@ -422,10 +421,13 @@ function AppSection({ title, iconClassName, apps, onOpenApp, isMobile }) {
|
|
|
422
421
|
function AppCard({ appItem, onOpenApp, isMobile }) {
|
|
423
422
|
const [_colorMode] = (0, hooks_1.useObservable)(color_mode_dropdown_1.colorMode);
|
|
424
423
|
const isDark = _colorMode === 'dark';
|
|
425
|
-
return (react_1.default.createElement("
|
|
424
|
+
return (react_1.default.createElement("button", { className: "d-flex flex-column align-items-center text-center", style: {
|
|
426
425
|
cursor: 'pointer',
|
|
427
426
|
width: isMobile ? '80px' : '90px',
|
|
428
|
-
transition: 'all 0.15s ease'
|
|
427
|
+
transition: 'all 0.15s ease',
|
|
428
|
+
background: 'none',
|
|
429
|
+
border: 'none',
|
|
430
|
+
padding: 0,
|
|
429
431
|
}, title: appItem.name, onMouseEnter: (e) => {
|
|
430
432
|
e.currentTarget.style.transform = 'scale(1.05)';
|
|
431
433
|
}, onMouseLeave: (e) => {
|
|
@@ -442,7 +444,7 @@ function AppCard({ appItem, onOpenApp, isMobile }) {
|
|
|
442
444
|
} },
|
|
443
445
|
react_1.default.createElement("i", { className: appItem.iconClassName, style: {
|
|
444
446
|
fontSize: isMobile ? '28px' : '32px',
|
|
445
|
-
color: isDark ? '#0d6efd' : '#0d6efd'
|
|
447
|
+
color: isDark ? '#0d6efd' : '#0d6efd'
|
|
446
448
|
} })),
|
|
447
449
|
react_1.default.createElement("span", { className: isDark ? 'text-light' : 'text-dark', style: {
|
|
448
450
|
fontSize: isMobile ? '11px' : '12px',
|
|
@@ -164,7 +164,10 @@ function determineAppFromPath(path) {
|
|
|
164
164
|
const pkg = _allPackages.find(p => p.packageId === packageId);
|
|
165
165
|
if (pkg) {
|
|
166
166
|
const subPath = parts.slice(1).join('/');
|
|
167
|
-
const navItem = pkg.appNavs?.find(n =>
|
|
167
|
+
const navItem = pkg.appNavs?.find(n => {
|
|
168
|
+
const navPath = n.navigationPath || n.name.toLowerCase();
|
|
169
|
+
return subPath === navPath || subPath.startsWith(navPath + '/');
|
|
170
|
+
});
|
|
168
171
|
if (navItem) {
|
|
169
172
|
return {
|
|
170
173
|
navItem,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-ui.git"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"test:coverage": "jest --coverage"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@peers-app/peers-sdk": "^0.
|
|
29
|
+
"@peers-app/peers-sdk": "^0.13.0",
|
|
30
30
|
"bootstrap": "^5.3.3",
|
|
31
31
|
"react": "^18.0.0",
|
|
32
32
|
"react-dom": "^18.0.0"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@babel/preset-env": "^7.24.5",
|
|
37
37
|
"@babel/preset-react": "^7.24.1",
|
|
38
38
|
"@babel/preset-typescript": "^7.27.1",
|
|
39
|
-
"@peers-app/peers-sdk": "0.
|
|
39
|
+
"@peers-app/peers-sdk": "0.13.0",
|
|
40
40
|
"@testing-library/dom": "^10.4.0",
|
|
41
41
|
"@testing-library/jest-dom": "^6.6.3",
|
|
42
42
|
"@testing-library/react": "^16.3.0",
|
|
@@ -106,14 +106,14 @@ export function GroupSwitcher({ colorMode, isMobile = false }: GroupSwitcherProp
|
|
|
106
106
|
}}
|
|
107
107
|
>
|
|
108
108
|
{/* Personal Group */}
|
|
109
|
-
<
|
|
109
|
+
<button
|
|
110
110
|
className={`dropdown-item d-flex align-items-center ${!currentGroup ? 'active' : ''}`}
|
|
111
|
-
style={{ cursor: 'pointer' }}
|
|
111
|
+
style={{ cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left' }}
|
|
112
112
|
onClick={() => handleGroupSelect(null)}
|
|
113
113
|
>
|
|
114
114
|
<i className="bi-person-fill me-2" />
|
|
115
115
|
<span>Personal</span>
|
|
116
|
-
</
|
|
116
|
+
</button>
|
|
117
117
|
|
|
118
118
|
{/* Divider */}
|
|
119
119
|
{allGroups.length > 0 && (
|
|
@@ -124,27 +124,27 @@ export function GroupSwitcher({ colorMode, isMobile = false }: GroupSwitcherProp
|
|
|
124
124
|
{allGroups
|
|
125
125
|
.filter((g: IGroup) => !g.disabled)
|
|
126
126
|
.map((group: IGroup) => (
|
|
127
|
-
<
|
|
127
|
+
<button
|
|
128
128
|
key={group.groupId}
|
|
129
129
|
className={`dropdown-item d-flex align-items-center ${currentGroup?.groupId === group.groupId ? 'active' : ''}`}
|
|
130
|
-
style={{ cursor: 'pointer' }}
|
|
130
|
+
style={{ cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left' }}
|
|
131
131
|
onClick={() => handleGroupSelect(group)}
|
|
132
132
|
>
|
|
133
133
|
<i className={`${getGroupIcon(group)} me-2`} />
|
|
134
134
|
<span className="text-truncate">{group.name}</span>
|
|
135
|
-
</
|
|
135
|
+
</button>
|
|
136
136
|
))}
|
|
137
137
|
|
|
138
138
|
{/* Create New Group */}
|
|
139
139
|
<div className="dropdown-divider" />
|
|
140
|
-
<
|
|
140
|
+
<button
|
|
141
141
|
className="dropdown-item d-flex align-items-center"
|
|
142
|
-
style={{ cursor: 'pointer' }}
|
|
142
|
+
style={{ cursor: 'pointer', border: 'none', background: 'none', width: '100%', textAlign: 'left' }}
|
|
143
143
|
onClick={handleCreateClick}
|
|
144
144
|
>
|
|
145
145
|
<i className="bi-plus-circle me-2" />
|
|
146
146
|
<span>Create Group</span>
|
|
147
|
-
</
|
|
147
|
+
</button>
|
|
148
148
|
</div>
|
|
149
149
|
)}
|
|
150
150
|
|
package/src/globals.tsx
CHANGED
|
@@ -134,11 +134,11 @@ export async function loadGlobals() {
|
|
|
134
134
|
await rpcServerCalls.addOrUpdatePackage('updateAll');
|
|
135
135
|
}
|
|
136
136
|
await Promise.all([
|
|
137
|
-
Groups().list().then(groups),
|
|
137
|
+
Groups().list().then(r => groups(r)),
|
|
138
138
|
_mainContentPath.loadingPromise,
|
|
139
139
|
openThreads.loadingPromise,
|
|
140
140
|
threadViewOpen.loadingPromise,
|
|
141
|
-
sleep(100),
|
|
141
|
+
sleep(100),
|
|
142
142
|
]);
|
|
143
143
|
|
|
144
144
|
function updateWindowHash() {
|
package/src/index.tsx
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export * from "./hooks";
|
|
1
2
|
export * from "./screens/events/cron";
|
|
2
3
|
|
|
3
4
|
export * from "./tabs-layout/tabs-layout";
|
|
4
|
-
export { activeTabId, activeTabs, TabState } from "./tabs-layout/tabs-state";
|
|
5
|
+
export { activeTabId, activeTabs, TabState, updateActiveTabTitle, closeCurrentTab, goToTabPath } from "./tabs-layout/tabs-state";
|
|
6
|
+
export { mainContentPath } from "./globals";
|
|
5
7
|
export * from "./components/voice-indicator";
|
|
6
8
|
export * from "./components/chat-overlay";
|
|
7
9
|
export * from "./components/sortable-list";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
|
-
import { IDoc, IPackage, IPackageVersion, Packages, PackageVersions, Users } from "@peers-app/peers-sdk";
|
|
2
|
+
import { computePackageVersionHash, IDoc, IPackage, IPackageVersion, Packages, PackageVersions, Users } from "@peers-app/peers-sdk";
|
|
3
3
|
import { useObservable, useObservableState, usePromise } from "../../hooks";
|
|
4
4
|
|
|
5
5
|
function formatDate(iso: string): string {
|
|
@@ -89,6 +89,28 @@ export const PackageVersionsList = (props: { pkg: IDoc<IPackage> }) => {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
async function promoteVersion(pv: IPackageVersion, newTag: string) {
|
|
93
|
+
try {
|
|
94
|
+
const pvHash = computePackageVersionHash(pv.version, newTag, pv.packageBundleFileHash, pv.routesBundleFileHash, pv.uiBundleFileHash);
|
|
95
|
+
const updated = { ...pv, versionTag: newTag, packageVersionHash: pvHash };
|
|
96
|
+
await PackageVersions().signAndSave(updated, { saveAsSnapshot: true });
|
|
97
|
+
refreshKey(refreshKey() + 1);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
alert(`Failed to promote version: ${err}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function updateVersion(pv: IPackageVersion, newSemver: string) {
|
|
104
|
+
try {
|
|
105
|
+
const pvHash = computePackageVersionHash(newSemver, pv.versionTag || 'beta', pv.packageBundleFileHash, pv.routesBundleFileHash, pv.uiBundleFileHash);
|
|
106
|
+
const updated = { ...pv, version: newSemver, packageVersionHash: pvHash };
|
|
107
|
+
await PackageVersions().signAndSave(updated, { saveAsSnapshot: true });
|
|
108
|
+
refreshKey(refreshKey() + 1);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
alert(`Failed to update version: ${err}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
async function pinVersion() {
|
|
93
115
|
setPinning(true);
|
|
94
116
|
try {
|
|
@@ -124,81 +146,174 @@ export const PackageVersionsList = (props: { pkg: IDoc<IPackage> }) => {
|
|
|
124
146
|
<div>
|
|
125
147
|
<small className="text-muted">{sorted.length} version{sorted.length !== 1 ? 's' : ''}</small>
|
|
126
148
|
<div className="list-group mt-2">
|
|
127
|
-
{sorted.map(pv =>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
{sorted.map(pv => (
|
|
150
|
+
<VersionRow
|
|
151
|
+
key={pv.packageVersionId}
|
|
152
|
+
pv={pv}
|
|
153
|
+
isActive={pv.packageVersionId === activeVersionId}
|
|
154
|
+
isPinned={isPinned}
|
|
155
|
+
pinning={pinning}
|
|
156
|
+
activating={activating}
|
|
157
|
+
deleting={deleting}
|
|
158
|
+
userMap={userMap}
|
|
159
|
+
onActivate={activateVersion}
|
|
160
|
+
onDelete={deleteVersion}
|
|
161
|
+
onPin={pinVersion}
|
|
162
|
+
onPromote={promoteVersion}
|
|
163
|
+
onUpdateVersion={updateVersion}
|
|
164
|
+
/>
|
|
165
|
+
))}
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const TAG_OPTIONS = ['beta', 'stable'];
|
|
172
|
+
|
|
173
|
+
function VersionRow(props: {
|
|
174
|
+
pv: IPackageVersion;
|
|
175
|
+
isActive: boolean;
|
|
176
|
+
isPinned: boolean;
|
|
177
|
+
pinning: boolean;
|
|
178
|
+
activating: string | null;
|
|
179
|
+
deleting: string | null;
|
|
180
|
+
userMap: Map<string, string>;
|
|
181
|
+
onActivate: (pv: IPackageVersion) => void;
|
|
182
|
+
onDelete: (pv: IPackageVersion) => void;
|
|
183
|
+
onPin: () => void;
|
|
184
|
+
onPromote: (pv: IPackageVersion, newTag: string) => void;
|
|
185
|
+
onUpdateVersion: (pv: IPackageVersion, newSemver: string) => void;
|
|
186
|
+
}) {
|
|
187
|
+
const { pv, isActive, isPinned, pinning, activating, deleting, userMap } = props;
|
|
188
|
+
const creatorName = userMap.get(pv.createdBy);
|
|
189
|
+
const [editingVersion, setEditingVersion] = useState(false);
|
|
190
|
+
const [versionDraft, setVersionDraft] = useState(pv.version);
|
|
191
|
+
|
|
192
|
+
function startEditing() {
|
|
193
|
+
setVersionDraft(pv.version);
|
|
194
|
+
setEditingVersion(true);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function commitVersion() {
|
|
198
|
+
const trimmed = versionDraft.trim();
|
|
199
|
+
if (trimmed && trimmed !== pv.version) {
|
|
200
|
+
props.onUpdateVersion(pv, trimmed);
|
|
201
|
+
}
|
|
202
|
+
setEditingVersion(false);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const currentTag = pv.versionTag || 'beta';
|
|
206
|
+
const promoteTargets = TAG_OPTIONS.filter(t => t !== currentTag);
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div className={`list-group-item ${isActive ? 'list-group-item-success' : ''}`}>
|
|
210
|
+
<div className="d-flex align-items-center justify-content-between">
|
|
211
|
+
<div className="d-flex align-items-center flex-wrap">
|
|
212
|
+
{editingVersion ? (
|
|
213
|
+
<input
|
|
214
|
+
type="text"
|
|
215
|
+
className="form-control form-control-sm me-2"
|
|
216
|
+
style={{ width: '7em' }}
|
|
217
|
+
value={versionDraft}
|
|
218
|
+
onChange={e => setVersionDraft(e.target.value)}
|
|
219
|
+
onBlur={commitVersion}
|
|
220
|
+
onKeyDown={e => {
|
|
221
|
+
if (e.key === 'Enter') commitVersion();
|
|
222
|
+
if (e.key === 'Escape') setEditingVersion(false);
|
|
223
|
+
}}
|
|
224
|
+
autoFocus
|
|
225
|
+
/>
|
|
226
|
+
) : (
|
|
227
|
+
<strong
|
|
228
|
+
className="me-2"
|
|
229
|
+
style={{ cursor: 'pointer' }}
|
|
230
|
+
onClick={startEditing}
|
|
231
|
+
title="Click to edit version"
|
|
134
232
|
>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
<i className="bi bi-pin-fill"></i>
|
|
165
|
-
)}
|
|
166
|
-
{isPinned ? ' Pinned' : ' Pin'}
|
|
167
|
-
</button>
|
|
168
|
-
<span className="badge text-bg-success align-self-center">active</span>
|
|
169
|
-
</>) : (
|
|
233
|
+
v{pv.version}
|
|
234
|
+
</strong>
|
|
235
|
+
)}
|
|
236
|
+
{pv.versionTag && (
|
|
237
|
+
<span className={`badge ${tagBadgeClass(pv.versionTag)} me-2`}>
|
|
238
|
+
{pv.versionTag}
|
|
239
|
+
</span>
|
|
240
|
+
)}
|
|
241
|
+
<code className="text-muted small me-2">
|
|
242
|
+
{pv.packageVersionHash?.substring(0, 8)}
|
|
243
|
+
</code>
|
|
244
|
+
<small className="text-muted me-2">{formatDate(pv.createdAt)}</small>
|
|
245
|
+
{creatorName && (
|
|
246
|
+
<small className="text-muted">by {creatorName}</small>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
<div className="d-flex gap-1 align-items-center">
|
|
250
|
+
{promoteTargets.length > 0 && (
|
|
251
|
+
<div className="dropdown">
|
|
252
|
+
<button
|
|
253
|
+
className="btn btn-sm btn-outline-info dropdown-toggle"
|
|
254
|
+
data-bs-toggle="dropdown"
|
|
255
|
+
title="Change version tag"
|
|
256
|
+
>
|
|
257
|
+
Promote
|
|
258
|
+
</button>
|
|
259
|
+
<ul className="dropdown-menu dropdown-menu-end">
|
|
260
|
+
{promoteTargets.map(tag => (
|
|
261
|
+
<li key={tag}>
|
|
170
262
|
<button
|
|
171
|
-
className="
|
|
172
|
-
|
|
173
|
-
onClick={() => activateVersion(pv)}
|
|
263
|
+
className="dropdown-item"
|
|
264
|
+
onClick={() => props.onPromote(pv, tag)}
|
|
174
265
|
>
|
|
175
|
-
{
|
|
176
|
-
<span className="spinner-border spinner-border-sm me-1" />
|
|
177
|
-
) : (
|
|
178
|
-
<i className="bi bi-check2-circle me-1"></i>
|
|
179
|
-
)}
|
|
180
|
-
Activate
|
|
266
|
+
{currentTag} → {tag}
|
|
181
267
|
</button>
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
className="btn btn-sm btn-outline-danger"
|
|
186
|
-
disabled={deleting === pv.packageVersionId}
|
|
187
|
-
onClick={() => deleteVersion(pv)}
|
|
188
|
-
>
|
|
189
|
-
{deleting === pv.packageVersionId ? (
|
|
190
|
-
<span className="spinner-border spinner-border-sm" />
|
|
191
|
-
) : (
|
|
192
|
-
<i className="bi bi-trash"></i>
|
|
193
|
-
)}
|
|
194
|
-
</button>
|
|
195
|
-
)}
|
|
196
|
-
</div>
|
|
197
|
-
</div>
|
|
268
|
+
</li>
|
|
269
|
+
))}
|
|
270
|
+
</ul>
|
|
198
271
|
</div>
|
|
199
|
-
)
|
|
200
|
-
|
|
272
|
+
)}
|
|
273
|
+
{isActive ? (<>
|
|
274
|
+
<button
|
|
275
|
+
className="btn btn-sm btn-outline-secondary"
|
|
276
|
+
disabled={isPinned || pinning}
|
|
277
|
+
onClick={() => props.onPin()}
|
|
278
|
+
title={isPinned ? 'Already pinned' : 'Pin to this version (disable auto-updates)'}
|
|
279
|
+
>
|
|
280
|
+
{pinning ? (
|
|
281
|
+
<span className="spinner-border spinner-border-sm" />
|
|
282
|
+
) : (
|
|
283
|
+
<i className="bi bi-pin-fill"></i>
|
|
284
|
+
)}
|
|
285
|
+
{isPinned ? ' Pinned' : ' Pin'}
|
|
286
|
+
</button>
|
|
287
|
+
<span className="badge text-bg-success align-self-center">active</span>
|
|
288
|
+
</>) : (
|
|
289
|
+
<button
|
|
290
|
+
className="btn btn-sm btn-outline-primary"
|
|
291
|
+
disabled={activating === pv.packageVersionId}
|
|
292
|
+
onClick={() => props.onActivate(pv)}
|
|
293
|
+
>
|
|
294
|
+
{activating === pv.packageVersionId ? (
|
|
295
|
+
<span className="spinner-border spinner-border-sm me-1" />
|
|
296
|
+
) : (
|
|
297
|
+
<i className="bi bi-check2-circle me-1"></i>
|
|
298
|
+
)}
|
|
299
|
+
Activate
|
|
300
|
+
</button>
|
|
301
|
+
)}
|
|
302
|
+
{!isActive && (
|
|
303
|
+
<button
|
|
304
|
+
className="btn btn-sm btn-outline-danger"
|
|
305
|
+
disabled={deleting === pv.packageVersionId}
|
|
306
|
+
onClick={() => props.onDelete(pv)}
|
|
307
|
+
>
|
|
308
|
+
{deleting === pv.packageVersionId ? (
|
|
309
|
+
<span className="spinner-border spinner-border-sm" />
|
|
310
|
+
) : (
|
|
311
|
+
<i className="bi bi-trash"></i>
|
|
312
|
+
)}
|
|
313
|
+
</button>
|
|
314
|
+
)}
|
|
315
|
+
</div>
|
|
201
316
|
</div>
|
|
202
317
|
</div>
|
|
203
318
|
);
|
|
204
|
-
}
|
|
319
|
+
}
|
|
@@ -36,7 +36,10 @@ export const SettingsPage: React.FC = () => {
|
|
|
36
36
|
|
|
37
37
|
const UserSettingsTab: React.FC = () => {
|
|
38
38
|
return (
|
|
39
|
-
|
|
39
|
+
<>
|
|
40
|
+
<ProfileSection />
|
|
41
|
+
<LogoutSection />
|
|
42
|
+
</>
|
|
40
43
|
);
|
|
41
44
|
};
|
|
42
45
|
|
|
@@ -319,4 +322,29 @@ const DeleteLocalDatabase: React.FC = () => {
|
|
|
319
322
|
</button>
|
|
320
323
|
</div>
|
|
321
324
|
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const showLogoutInSettings = typeof window !== 'undefined'
|
|
328
|
+
&& !(window as any).electronAPI
|
|
329
|
+
&& !(window as any).ReactNativeWebView;
|
|
330
|
+
|
|
331
|
+
const LogoutSection: React.FC = () => {
|
|
332
|
+
if (!showLogoutInSettings) return null;
|
|
333
|
+
return (
|
|
334
|
+
<div className="mt-4 pt-3 border-top">
|
|
335
|
+
<button
|
|
336
|
+
className="btn btn-outline-danger btn-sm"
|
|
337
|
+
onClick={async () => {
|
|
338
|
+
const confirmed = confirm(
|
|
339
|
+
'Are you sure you want to logout? This will clear your credentials from this browser. ' +
|
|
340
|
+
'Make sure you have your User ID and Secret Key saved securely, or you will lose access to your account.'
|
|
341
|
+
);
|
|
342
|
+
if (!confirmed) return;
|
|
343
|
+
await rpcServerCalls.logout();
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
Logout
|
|
347
|
+
</button>
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
322
350
|
}
|
|
@@ -87,14 +87,13 @@ export function TabsLayout(props: { userId: string }) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
const loaded = usePromise(async () => {
|
|
90
|
-
await loadGlobals()
|
|
91
|
-
await loadAllRoutes()
|
|
92
|
-
// Wait for tab state and welcome modal state to load from database
|
|
90
|
+
await loadGlobals();
|
|
91
|
+
await loadAllRoutes();
|
|
93
92
|
await Promise.all([
|
|
94
93
|
activeTabs.loadingPromise,
|
|
95
94
|
activeTabId.loadingPromise,
|
|
96
95
|
recentlyUsedApps.loadingPromise,
|
|
97
|
-
hasShownWelcomeModal.loadingPromise
|
|
96
|
+
hasShownWelcomeModal.loadingPromise,
|
|
98
97
|
]);
|
|
99
98
|
return true;
|
|
100
99
|
});
|
|
@@ -730,12 +729,15 @@ function AppCard({ appItem, onOpenApp, isMobile }: AppCardProps) {
|
|
|
730
729
|
const isDark = _colorMode === 'dark';
|
|
731
730
|
|
|
732
731
|
return (
|
|
733
|
-
<
|
|
732
|
+
<button
|
|
734
733
|
className="d-flex flex-column align-items-center text-center"
|
|
735
734
|
style={{
|
|
736
735
|
cursor: 'pointer',
|
|
737
736
|
width: isMobile ? '80px' : '90px',
|
|
738
|
-
transition: 'all 0.15s ease'
|
|
737
|
+
transition: 'all 0.15s ease',
|
|
738
|
+
background: 'none',
|
|
739
|
+
border: 'none',
|
|
740
|
+
padding: 0,
|
|
739
741
|
}}
|
|
740
742
|
title={appItem.name}
|
|
741
743
|
onMouseEnter={(e) => {
|
|
@@ -746,7 +748,6 @@ function AppCard({ appItem, onOpenApp, isMobile }: AppCardProps) {
|
|
|
746
748
|
}}
|
|
747
749
|
onClick={() => onOpenApp(appItem)}
|
|
748
750
|
>
|
|
749
|
-
{/* Icon Container */}
|
|
750
751
|
<div
|
|
751
752
|
className="d-flex align-items-center justify-content-center mb-2"
|
|
752
753
|
style={{
|
|
@@ -763,12 +764,11 @@ function AppCard({ appItem, onOpenApp, isMobile }: AppCardProps) {
|
|
|
763
764
|
className={appItem.iconClassName}
|
|
764
765
|
style={{
|
|
765
766
|
fontSize: isMobile ? '28px' : '32px',
|
|
766
|
-
color: isDark ? '#0d6efd' : '#0d6efd'
|
|
767
|
+
color: isDark ? '#0d6efd' : '#0d6efd'
|
|
767
768
|
}}
|
|
768
769
|
/>
|
|
769
770
|
</div>
|
|
770
771
|
|
|
771
|
-
{/* Title */}
|
|
772
772
|
<span
|
|
773
773
|
className={isDark ? 'text-light' : 'text-dark'}
|
|
774
774
|
style={{
|
|
@@ -783,6 +783,6 @@ function AppCard({ appItem, onOpenApp, isMobile }: AppCardProps) {
|
|
|
783
783
|
>
|
|
784
784
|
{appItem.displayName}
|
|
785
785
|
</span>
|
|
786
|
-
</
|
|
786
|
+
</button>
|
|
787
787
|
);
|
|
788
788
|
}
|
|
@@ -194,7 +194,10 @@ export function determineAppFromPath(path: string): AppInfo | undefined {
|
|
|
194
194
|
const pkg = _allPackages.find(p => p.packageId === packageId);
|
|
195
195
|
if (pkg) {
|
|
196
196
|
const subPath = parts.slice(1).join('/');
|
|
197
|
-
const navItem = pkg.appNavs?.find(n =>
|
|
197
|
+
const navItem = pkg.appNavs?.find(n => {
|
|
198
|
+
const navPath = n.navigationPath || n.name.toLowerCase();
|
|
199
|
+
return subPath === navPath || subPath.startsWith(navPath + '/');
|
|
200
|
+
});
|
|
198
201
|
if (navItem) {
|
|
199
202
|
return {
|
|
200
203
|
navItem,
|