sh3-core 0.13.2 → 0.13.4
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/actions/MenuButton.svelte +2 -1
- package/dist/actions/contextMenuModel.d.ts +1 -1
- package/dist/actions/contextMenuModel.js +2 -1
- package/dist/actions/dispatcher.svelte.d.ts +1 -1
- package/dist/actions/dispatcher.svelte.js +2 -1
- package/dist/actions/listActive.d.ts +1 -1
- package/dist/actions/listActive.js +2 -1
- package/dist/actions/listeners.d.ts +1 -1
- package/dist/actions/listeners.js +6 -5
- package/dist/actions/menuBarModel.js +3 -2
- package/dist/actions/paletteModel.js +2 -1
- package/dist/actions/resolveLabel.test.d.ts +1 -0
- package/dist/actions/resolveLabel.test.js +14 -0
- package/dist/actions/types.d.ts +12 -1
- package/dist/actions/types.js +7 -1
- package/dist/app/store/AppUpdateAvailableModal.svelte +87 -0
- package/dist/app/store/AppUpdateAvailableModal.svelte.d.ts +11 -0
- package/dist/app/store/StoreView.svelte +15 -4
- package/dist/app/store/UninstallAppDialog.svelte +86 -0
- package/dist/app/store/UninstallAppDialog.svelte.d.ts +10 -0
- package/dist/app/store/permissionConfirm.d.ts +4 -0
- package/dist/app/store/permissionConfirm.js +27 -0
- package/dist/app/store/storeApp.js +0 -1
- package/dist/app/store/storeShard.svelte.d.ts +8 -1
- package/dist/app/store/storeShard.svelte.js +51 -27
- package/dist/app/store/storeTypes.d.ts +21 -0
- package/dist/app/store/storeTypes.js +33 -0
- package/dist/app/store/storeTypes.test.d.ts +1 -0
- package/dist/app/store/storeTypes.test.js +41 -0
- package/dist/app/store/updatePackage.test.d.ts +1 -0
- package/dist/app/store/updatePackage.test.js +34 -0
- package/dist/app/store/verbs.d.ts +1 -0
- package/dist/app/store/verbs.js +79 -5
- package/dist/app/store/verbs.test.d.ts +1 -0
- package/dist/app/store/verbs.test.js +59 -0
- package/dist/app-appearance/AppAppearanceModal.svelte +174 -0
- package/dist/app-appearance/AppAppearanceModal.svelte.d.ts +8 -0
- package/dist/app-appearance/appearanceShard.svelte.d.ts +2 -0
- package/dist/app-appearance/appearanceShard.svelte.js +61 -0
- package/dist/app-appearance/appearanceState.svelte.d.ts +15 -0
- package/dist/app-appearance/appearanceState.svelte.js +59 -0
- package/dist/app-appearance/appearanceState.test.d.ts +1 -0
- package/dist/app-appearance/appearanceState.test.js +30 -0
- package/dist/app-appearance/index.d.ts +3 -0
- package/dist/app-appearance/index.js +2 -0
- package/dist/app-appearance/types.d.ts +11 -0
- package/dist/app-appearance/types.js +1 -0
- package/dist/apps/types.d.ts +7 -0
- package/dist/assets/iconIds.generated.d.ts +2 -0
- package/dist/assets/iconIds.generated.js +154 -0
- package/dist/host.js +2 -1
- package/dist/overlays/FloatFrame.svelte +18 -1
- package/dist/overlays/float.d.ts +12 -0
- package/dist/overlays/float.js +16 -0
- package/dist/overlays/float.test.js +97 -2
- package/dist/overlays/modal.js +1 -0
- package/dist/overlays/modal.test.js +17 -0
- package/dist/overlays/parentHost.d.ts +1 -0
- package/dist/overlays/parentHost.js +15 -0
- package/dist/overlays/parentHost.test.d.ts +1 -0
- package/dist/overlays/parentHost.test.js +39 -0
- package/dist/overlays/popup.js +1 -0
- package/dist/overlays/popup.test.js +19 -0
- package/dist/primitives/widgets/IconPicker.svelte +115 -0
- package/dist/primitives/widgets/IconPicker.svelte.d.ts +9 -0
- package/dist/primitives/widgets/IconPicker.svelte.test.d.ts +1 -0
- package/dist/primitives/widgets/IconPicker.svelte.test.js +43 -0
- package/dist/projects-shard/ProjectManage.svelte +14 -4
- package/dist/sh3core-shard/ShellHome.svelte +64 -38
- package/dist/sh3core-shard/appActions.d.ts +13 -0
- package/dist/sh3core-shard/appActions.js +181 -0
- package/dist/sh3core-shard/appActions.test.d.ts +1 -0
- package/dist/sh3core-shard/appActions.test.js +25 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +2 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/dist/app/store/InstalledView.svelte +0 -301
- package/dist/app/store/InstalledView.svelte.d.ts +0 -3
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { getLiveDispatcherState } from './state.svelte';
|
|
20
20
|
import type { DispatcherState } from './dispatcher.svelte';
|
|
21
21
|
import { resolveSubmenuItems, type MenuBarItem } from './menuBarModel';
|
|
22
|
+
import { resolveLabel } from './types';
|
|
22
23
|
import type { MenuContainer } from '../apps/types';
|
|
23
24
|
|
|
24
25
|
let { container, items }: {
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
if (!entry || typeof entry.action.run !== 'function') return;
|
|
48
49
|
try {
|
|
49
50
|
void entry.action.run({
|
|
50
|
-
action: { id, label: entry.action
|
|
51
|
+
action: { id, label: resolveLabel(entry.action) },
|
|
51
52
|
appId: state.activeAppId,
|
|
52
53
|
viewId: state.focusedViewId ?? undefined,
|
|
53
54
|
selection: state.selection ?? undefined,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ActionEntry } from './registry';
|
|
2
2
|
import { type DispatcherState, type TierName } from './dispatcher.svelte';
|
|
3
|
-
import type
|
|
3
|
+
import { type AtomicScope } from './types';
|
|
4
4
|
export interface MenuItem {
|
|
5
5
|
id: string;
|
|
6
6
|
label: string;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { TIER_ORDER, isScopeActive, } from './dispatcher.svelte';
|
|
10
10
|
import { effectiveShortcut } from './bindings';
|
|
11
11
|
import { scopeToTier, innermostActiveScope, scopeEquals, normalizeScope, } from './scope-helpers';
|
|
12
|
+
import { resolveLabel } from './types';
|
|
12
13
|
function evalFlag(v) {
|
|
13
14
|
if (v === undefined)
|
|
14
15
|
return false;
|
|
@@ -18,7 +19,7 @@ function toMenuItem(entry, state) {
|
|
|
18
19
|
var _a;
|
|
19
20
|
return {
|
|
20
21
|
id: entry.action.id,
|
|
21
|
-
label: entry.action
|
|
22
|
+
label: resolveLabel(entry.action),
|
|
22
23
|
shortcut: effectiveShortcut(entry.action, state.bindings, state.platform),
|
|
23
24
|
group: (_a = entry.action.group) !== null && _a !== void 0 ? _a : '',
|
|
24
25
|
icon: entry.action.icon,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This module exposes testable state transitions; listeners feed it
|
|
5
5
|
* state snapshots.
|
|
6
6
|
*/
|
|
7
|
+
import { resolveLabel } from './types';
|
|
7
8
|
import { effectiveShortcut } from './bindings';
|
|
8
9
|
import { scopeToTier, normalizeScope } from './scope-helpers';
|
|
9
10
|
export const TIER_ORDER = ['element', 'focus', 'view', 'app', 'home'];
|
|
@@ -91,7 +92,7 @@ export function dispatchKeydown(env) {
|
|
|
91
92
|
return null;
|
|
92
93
|
}
|
|
93
94
|
env.runAction(id, {
|
|
94
|
-
action: { id: entry.action.id, label: entry.action
|
|
95
|
+
action: { id: entry.action.id, label: resolveLabel(entry.action) },
|
|
95
96
|
appId: env.state.activeAppId,
|
|
96
97
|
viewId: (_a = env.state.focusedViewId) !== null && _a !== void 0 ? _a : undefined,
|
|
97
98
|
selection: (_b = env.state.selection) !== null && _b !== void 0 ? _b : undefined,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ActionEntry } from './registry';
|
|
2
2
|
import { type DispatcherState } from './dispatcher.svelte';
|
|
3
|
-
import type
|
|
3
|
+
import { type ActiveActionDescriptor } from './types';
|
|
4
4
|
export declare function listActiveFromEntries(entries: ActionEntry[], state: DispatcherState): ActiveActionDescriptor[];
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { TIER_ORDER, } from './dispatcher.svelte';
|
|
12
12
|
import { effectiveShortcutWithSource } from './bindings';
|
|
13
13
|
import { innermostActiveScope, scopeBadge, scopeToTier } from './scope-helpers';
|
|
14
|
+
import { resolveLabel } from './types';
|
|
14
15
|
export function listActiveFromEntries(entries, state) {
|
|
15
16
|
const byTier = {
|
|
16
17
|
element: [], focus: [], view: [], app: [], home: [],
|
|
@@ -26,7 +27,7 @@ export function listActiveFromEntries(entries, state) {
|
|
|
26
27
|
const { shortcut, source } = effectiveShortcutWithSource(entry.action, state.bindings, state.platform);
|
|
27
28
|
byTier[scopeToTier(winning)].push({
|
|
28
29
|
id: entry.action.id,
|
|
29
|
-
label: entry.action
|
|
30
|
+
label: resolveLabel(entry.action),
|
|
30
31
|
effectiveShortcut: shortcut,
|
|
31
32
|
bindingSource: source,
|
|
32
33
|
scope: winning,
|
|
@@ -8,6 +8,7 @@ import { listActions } from './registry';
|
|
|
8
8
|
import { dispatchKeydown } from './dispatcher.svelte';
|
|
9
9
|
import { getLiveDispatcherState, setFocusedViewId, } from './state.svelte';
|
|
10
10
|
import { eventToShortcut } from './shortcuts';
|
|
11
|
+
import { resolveLabel } from './types';
|
|
11
12
|
import ContextMenu from './ContextMenu.svelte';
|
|
12
13
|
import { buildContextMenuModel, buildContextMenuSubmenu } from './contextMenuModel';
|
|
13
14
|
import ActionPanel from './ActionPanel.svelte';
|
|
@@ -83,7 +84,7 @@ function chainedDispatch(actionId) {
|
|
|
83
84
|
}
|
|
84
85
|
const state = getLiveDispatcherState();
|
|
85
86
|
runAction(actionId, {
|
|
86
|
-
action: { id: entry.action.id, label: entry.action
|
|
87
|
+
action: { id: entry.action.id, label: resolveLabel(entry.action) },
|
|
87
88
|
appId: state.activeAppId,
|
|
88
89
|
viewId: (_a = state.focusedViewId) !== null && _a !== void 0 ? _a : undefined,
|
|
89
90
|
selection: (_b = state.selection) !== null && _b !== void 0 ? _b : undefined,
|
|
@@ -132,7 +133,7 @@ function openContextSubmenu(parentId, state, handle, anchor) {
|
|
|
132
133
|
return;
|
|
133
134
|
try {
|
|
134
135
|
void child.action.run({
|
|
135
|
-
action: { id: cid, label: child.action
|
|
136
|
+
action: { id: cid, label: resolveLabel(child.action) },
|
|
136
137
|
appId: state.activeAppId,
|
|
137
138
|
viewId: (_a = state.focusedViewId) !== null && _a !== void 0 ? _a : undefined,
|
|
138
139
|
selection: (_b = state.selection) !== null && _b !== void 0 ? _b : undefined,
|
|
@@ -174,7 +175,7 @@ function onContextMenu(ev) {
|
|
|
174
175
|
return;
|
|
175
176
|
try {
|
|
176
177
|
void entry.action.run({
|
|
177
|
-
action: { id, label: entry.action
|
|
178
|
+
action: { id, label: resolveLabel(entry.action) },
|
|
178
179
|
appId: state.activeAppId,
|
|
179
180
|
viewId: (_a = state.focusedViewId) !== null && _a !== void 0 ? _a : undefined,
|
|
180
181
|
selection: (_b = state.selection) !== null && _b !== void 0 ? _b : undefined,
|
|
@@ -246,7 +247,7 @@ export function openContextMenu(opts) {
|
|
|
246
247
|
return;
|
|
247
248
|
try {
|
|
248
249
|
void entry.action.run({
|
|
249
|
-
action: { id, label: entry.action
|
|
250
|
+
action: { id, label: resolveLabel(entry.action) },
|
|
250
251
|
appId: state.activeAppId,
|
|
251
252
|
viewId: (_a = state.focusedViewId) !== null && _a !== void 0 ? _a : undefined,
|
|
252
253
|
selection: (_b = state.selection) !== null && _b !== void 0 ? _b : undefined,
|
|
@@ -293,7 +294,7 @@ export function openPalette(opts) {
|
|
|
293
294
|
return;
|
|
294
295
|
try {
|
|
295
296
|
void entry.action.run({
|
|
296
|
-
action: { id, label: entry.action
|
|
297
|
+
action: { id, label: resolveLabel(entry.action) },
|
|
297
298
|
appId: state.activeAppId,
|
|
298
299
|
viewId: (_a = state.focusedViewId) !== null && _a !== void 0 ? _a : undefined,
|
|
299
300
|
selection: (_b = state.selection) !== null && _b !== void 0 ? _b : undefined,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { effectiveShortcut } from './bindings';
|
|
8
8
|
import { innermostActiveScope } from './scope-helpers';
|
|
9
|
+
import { resolveLabel } from './types';
|
|
9
10
|
import { DEFAULT_MENU_CONTAINERS } from './defaultMenuContainers';
|
|
10
11
|
function evalFlag(v) {
|
|
11
12
|
if (v === undefined)
|
|
@@ -64,7 +65,7 @@ export function resolveMenuItems(entries, state, containerId) {
|
|
|
64
65
|
seen.add(entry.action.id);
|
|
65
66
|
out.push({
|
|
66
67
|
id: entry.action.id,
|
|
67
|
-
label: entry.action
|
|
68
|
+
label: resolveLabel(entry.action),
|
|
68
69
|
shortcut: effectiveShortcut(entry.action, state.bindings, state.platform),
|
|
69
70
|
group: (_a = entry.action.group) !== null && _a !== void 0 ? _a : '',
|
|
70
71
|
icon: entry.action.icon,
|
|
@@ -97,7 +98,7 @@ export function resolveSubmenuItems(entries, state, parentId) {
|
|
|
97
98
|
seen.add(entry.action.id);
|
|
98
99
|
out.push({
|
|
99
100
|
id: entry.action.id,
|
|
100
|
-
label: entry.action
|
|
101
|
+
label: resolveLabel(entry.action),
|
|
101
102
|
shortcut: effectiveShortcut(entry.action, state.bindings, state.platform),
|
|
102
103
|
group: (_a = entry.action.group) !== null && _a !== void 0 ? _a : '',
|
|
103
104
|
icon: entry.action.icon,
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { effectiveShortcut } from './bindings';
|
|
16
16
|
import { innermostActiveScope, scopeBadge } from './scope-helpers';
|
|
17
|
+
import { resolveLabel } from './types';
|
|
17
18
|
function evalFlag(v) {
|
|
18
19
|
if (v === undefined)
|
|
19
20
|
return false;
|
|
@@ -39,7 +40,7 @@ export function buildPaletteCandidates(entries, state, opts = {}) {
|
|
|
39
40
|
seen.add(entry.action.id);
|
|
40
41
|
out.push({
|
|
41
42
|
id: entry.action.id,
|
|
42
|
-
label: entry.action
|
|
43
|
+
label: resolveLabel(entry.action),
|
|
43
44
|
shortcut: effectiveShortcut(entry.action, state.bindings, state.platform),
|
|
44
45
|
scopeBadge: scopeBadge(winning),
|
|
45
46
|
submenu: entry.action.submenu === true,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { resolveLabel } from './types';
|
|
3
|
+
describe('resolveLabel', () => {
|
|
4
|
+
it('returns string label as-is', () => {
|
|
5
|
+
const a = { id: 'x', label: 'Hello', scope: 'app' };
|
|
6
|
+
expect(resolveLabel(a)).toBe('Hello');
|
|
7
|
+
});
|
|
8
|
+
it('calls function label and returns the result', () => {
|
|
9
|
+
let n = 0;
|
|
10
|
+
const a = { id: 'x', label: () => `n=${++n}`, scope: 'app' };
|
|
11
|
+
expect(resolveLabel(a)).toBe('n=1');
|
|
12
|
+
expect(resolveLabel(a)).toBe('n=2');
|
|
13
|
+
});
|
|
14
|
+
});
|
package/dist/actions/types.d.ts
CHANGED
|
@@ -4,7 +4,13 @@ export type AtomicScope = 'home' | 'app' | `view:${string}` | `focus:${string}`
|
|
|
4
4
|
export type ActionScope = AtomicScope | AtomicScope[];
|
|
5
5
|
export interface Action {
|
|
6
6
|
id: string;
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Display label. May be a function for live-evaluated labels (re-read on
|
|
9
|
+
* each menu derive — same cadence as `disabled`/`checked`). Function form
|
|
10
|
+
* is appropriate for runtime-suffixed labels (e.g. `· admin only`); static
|
|
11
|
+
* strings are still the common case.
|
|
12
|
+
*/
|
|
13
|
+
label: string | (() => string);
|
|
8
14
|
scope: ActionScope;
|
|
9
15
|
contextItem?: boolean;
|
|
10
16
|
paletteItem?: boolean;
|
|
@@ -143,3 +149,8 @@ export interface ActiveActionDescriptor {
|
|
|
143
149
|
paletteItem: boolean;
|
|
144
150
|
contextItem: boolean;
|
|
145
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Resolve an Action's label to a string. Function labels are called on each
|
|
154
|
+
* read; string labels are returned unchanged.
|
|
155
|
+
*/
|
|
156
|
+
export declare function resolveLabel(action: Pick<Action, 'label'>): string;
|
package/dist/actions/types.js
CHANGED
|
@@ -4,4 +4,10 @@
|
|
|
4
4
|
* context menu, or command palette. See the spec at
|
|
5
5
|
* docs/superpowers/specs/2026-04-22-actions-contexts-design.md.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Resolve an Action's label to a string. Function labels are called on each
|
|
9
|
+
* read; string labels are returned unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveLabel(action) {
|
|
12
|
+
return typeof action.label === 'function' ? action.label() : action.label;
|
|
13
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Confirms an available update for an app launched via the home-card
|
|
4
|
+
* "Check for updates" action. If the user clicks Update, onConfirm runs
|
|
5
|
+
* (which performs the actual storeContext.updatePackage call); the
|
|
6
|
+
* permission-diff prompt — when needed — is opened by updatePackage's
|
|
7
|
+
* own confirmPermissionChange callback. Cancel just closes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
appId: string;
|
|
12
|
+
appLabel: string;
|
|
13
|
+
fromVersion: string;
|
|
14
|
+
toVersion: string;
|
|
15
|
+
onConfirm: () => Promise<void>;
|
|
16
|
+
close: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let { appId, appLabel, fromVersion, toVersion, onConfirm, close }: Props = $props();
|
|
20
|
+
let busy = $state(false);
|
|
21
|
+
let error = $state<string | null>(null);
|
|
22
|
+
|
|
23
|
+
async function confirm() {
|
|
24
|
+
if (busy) return;
|
|
25
|
+
busy = true;
|
|
26
|
+
error = null;
|
|
27
|
+
try {
|
|
28
|
+
await onConfirm();
|
|
29
|
+
close();
|
|
30
|
+
} catch (e) {
|
|
31
|
+
error = (e as Error).message;
|
|
32
|
+
} finally {
|
|
33
|
+
busy = false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<div class="app-update-modal">
|
|
39
|
+
<h2>Update available</h2>
|
|
40
|
+
<p>
|
|
41
|
+
<strong>{appLabel}</strong> can be updated from <code>v{fromVersion}</code>
|
|
42
|
+
to <code>v{toVersion}</code>.
|
|
43
|
+
</p>
|
|
44
|
+
<p class="hint">Package id: <code>{appId}</code></p>
|
|
45
|
+
{#if error}<p class="error">{error}</p>{/if}
|
|
46
|
+
<div class="actions">
|
|
47
|
+
<button type="button" class="primary" onclick={confirm} disabled={busy}>
|
|
48
|
+
{busy ? 'Updating…' : 'Update'}
|
|
49
|
+
</button>
|
|
50
|
+
<button type="button" onclick={close} disabled={busy}>Cancel</button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<style>
|
|
55
|
+
.app-update-modal {
|
|
56
|
+
padding: 16px 20px;
|
|
57
|
+
max-width: 460px;
|
|
58
|
+
color: var(--shell-fg);
|
|
59
|
+
background: var(--shell-bg);
|
|
60
|
+
font: inherit;
|
|
61
|
+
}
|
|
62
|
+
h2 { margin: 0 0 8px; font-size: 16px; }
|
|
63
|
+
p { margin: 4px 0; font-size: 13px; }
|
|
64
|
+
.hint { color: var(--shell-fg-muted); font-size: 12px; }
|
|
65
|
+
.error { color: var(--shell-error, #c33); }
|
|
66
|
+
code {
|
|
67
|
+
font-family: var(--shell-font-mono, monospace);
|
|
68
|
+
background: var(--shell-bg-elevated);
|
|
69
|
+
padding: 0 4px;
|
|
70
|
+
border-radius: var(--shell-radius-sm, 3px);
|
|
71
|
+
}
|
|
72
|
+
.actions { display: flex; gap: 8px; margin-top: 16px; }
|
|
73
|
+
.actions button {
|
|
74
|
+
background: var(--shell-bg-elevated);
|
|
75
|
+
color: var(--shell-fg);
|
|
76
|
+
border: 1px solid var(--shell-border);
|
|
77
|
+
border-radius: var(--shell-radius-sm, 3px);
|
|
78
|
+
padding: 6px 14px; font: inherit; cursor: pointer;
|
|
79
|
+
}
|
|
80
|
+
.actions button.primary {
|
|
81
|
+
background: var(--shell-accent);
|
|
82
|
+
color: #fff;
|
|
83
|
+
border-color: var(--shell-accent);
|
|
84
|
+
}
|
|
85
|
+
.actions button:hover { border-color: var(--shell-accent); }
|
|
86
|
+
.actions button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
87
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
appId: string;
|
|
3
|
+
appLabel: string;
|
|
4
|
+
fromVersion: string;
|
|
5
|
+
toVersion: string;
|
|
6
|
+
onConfirm: () => Promise<void>;
|
|
7
|
+
close: () => void;
|
|
8
|
+
}
|
|
9
|
+
declare const AppUpdateAvailableModal: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type AppUpdateAvailableModal = ReturnType<typeof AppUpdateAvailableModal>;
|
|
11
|
+
export default AppUpdateAvailableModal;
|
|
@@ -17,9 +17,15 @@
|
|
|
17
17
|
import type { InstalledPackage } from '../../registry/types';
|
|
18
18
|
import { FRAMEWORK_SHARD_IDS } from '../../api';
|
|
19
19
|
import PermissionConfirmModal from './PermissionConfirmModal.svelte';
|
|
20
|
+
import {
|
|
21
|
+
displayPackageType,
|
|
22
|
+
displayPackageTypeLabel,
|
|
23
|
+
packageMatchesTypeFilter,
|
|
24
|
+
type PackageTypeFilter,
|
|
25
|
+
} from './storeTypes';
|
|
20
26
|
|
|
21
27
|
let search = $state('');
|
|
22
|
-
let typeFilter = $state<
|
|
28
|
+
let typeFilter = $state<PackageTypeFilter>('all');
|
|
23
29
|
let installingIds = $state<Set<string>>(new Set());
|
|
24
30
|
let updatingIds = $state<Set<string>>(new Set());
|
|
25
31
|
let installError = $state<string | null>(null);
|
|
@@ -70,7 +76,7 @@
|
|
|
70
76
|
const filtered = $derived.by(() => {
|
|
71
77
|
const q = search.toLowerCase().trim();
|
|
72
78
|
return ctx.state.ephemeral.catalog.filter((pkg: ResolvedPackage) => {
|
|
73
|
-
if (
|
|
79
|
+
if (!packageMatchesTypeFilter(pkg.entry.type, typeFilter)) return false;
|
|
74
80
|
if (!q) return true;
|
|
75
81
|
return (
|
|
76
82
|
pkg.entry.id.toLowerCase().includes(q) ||
|
|
@@ -312,6 +318,7 @@
|
|
|
312
318
|
{@const updatable = hasUpdate(pkg.entry.id)}
|
|
313
319
|
{@const updating = updatingIds.has(pkg.entry.id)}
|
|
314
320
|
{@const missing = missingShards(pkg, ctx.state.ephemeral.installed)}
|
|
321
|
+
{@const displayType = displayPackageType(pkg.entry.type)}
|
|
315
322
|
<div class="store-card">
|
|
316
323
|
<div class="store-card-header">
|
|
317
324
|
<div class="store-card-icon">
|
|
@@ -325,8 +332,12 @@
|
|
|
325
332
|
</div>
|
|
326
333
|
<div class="store-card-title">
|
|
327
334
|
<span class="store-card-label">{pkg.entry.label}</span>
|
|
328
|
-
<span
|
|
329
|
-
|
|
335
|
+
<span
|
|
336
|
+
class="store-card-badge"
|
|
337
|
+
class:badge-shard={displayType === 'shard'}
|
|
338
|
+
class:badge-app={displayType === 'app'}
|
|
339
|
+
>
|
|
340
|
+
{displayPackageTypeLabel(pkg.entry.type)}
|
|
330
341
|
</span>
|
|
331
342
|
<span class="store-card-version">{pkg.latest.version}</span>
|
|
332
343
|
</div>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Confirms uninstall of an installed app launched from the home-card
|
|
4
|
+
* Uninstall action. Modeled on DeleteProjectDialog. The actual uninstall
|
|
5
|
+
* (server + local installer) is delegated to the caller via onConfirm.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
appId: string;
|
|
10
|
+
appLabel: string;
|
|
11
|
+
version: string;
|
|
12
|
+
onConfirm: () => Promise<void>;
|
|
13
|
+
close: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { appId, appLabel, version, onConfirm, close }: Props = $props();
|
|
17
|
+
let busy = $state(false);
|
|
18
|
+
let error = $state<string | null>(null);
|
|
19
|
+
|
|
20
|
+
async function confirm() {
|
|
21
|
+
if (busy) return;
|
|
22
|
+
busy = true;
|
|
23
|
+
error = null;
|
|
24
|
+
try {
|
|
25
|
+
await onConfirm();
|
|
26
|
+
close();
|
|
27
|
+
} catch (e) {
|
|
28
|
+
error = (e as Error).message;
|
|
29
|
+
} finally {
|
|
30
|
+
busy = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div class="uninstall-dialog">
|
|
36
|
+
<h2>Uninstall {appLabel}?</h2>
|
|
37
|
+
<p class="hint">
|
|
38
|
+
Package id: <code>{appId}</code> · current version: <code>v{version}</code>
|
|
39
|
+
</p>
|
|
40
|
+
<p>
|
|
41
|
+
The package will be removed from this server. App data stored in your
|
|
42
|
+
browser will remain until you clear it manually.
|
|
43
|
+
</p>
|
|
44
|
+
{#if error}<p class="error">{error}</p>{/if}
|
|
45
|
+
<div class="actions">
|
|
46
|
+
<button type="button" class="danger" onclick={confirm} disabled={busy}>
|
|
47
|
+
{busy ? 'Uninstalling…' : 'Uninstall'}
|
|
48
|
+
</button>
|
|
49
|
+
<button type="button" onclick={close} disabled={busy}>Cancel</button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<style>
|
|
54
|
+
.uninstall-dialog {
|
|
55
|
+
padding: 16px 20px;
|
|
56
|
+
max-width: 460px;
|
|
57
|
+
color: var(--shell-fg);
|
|
58
|
+
background: var(--shell-bg);
|
|
59
|
+
font: inherit;
|
|
60
|
+
}
|
|
61
|
+
h2 { margin: 0 0 8px; font-size: 16px; }
|
|
62
|
+
p { margin: 4px 0; font-size: 13px; }
|
|
63
|
+
.hint { color: var(--shell-fg-muted); font-size: 12px; }
|
|
64
|
+
.error { color: var(--shell-error, #c33); }
|
|
65
|
+
code {
|
|
66
|
+
font-family: var(--shell-font-mono, monospace);
|
|
67
|
+
background: var(--shell-bg-elevated);
|
|
68
|
+
padding: 0 4px;
|
|
69
|
+
border-radius: var(--shell-radius-sm, 3px);
|
|
70
|
+
}
|
|
71
|
+
.actions { display: flex; gap: 8px; margin-top: 16px; }
|
|
72
|
+
.actions button {
|
|
73
|
+
background: var(--shell-bg-elevated);
|
|
74
|
+
color: var(--shell-fg);
|
|
75
|
+
border: 1px solid var(--shell-border);
|
|
76
|
+
border-radius: var(--shell-radius-sm, 3px);
|
|
77
|
+
padding: 6px 14px; font: inherit; cursor: pointer;
|
|
78
|
+
}
|
|
79
|
+
.actions button.danger {
|
|
80
|
+
background: var(--shell-error, #c33);
|
|
81
|
+
color: #fff;
|
|
82
|
+
border-color: var(--shell-error, #c33);
|
|
83
|
+
}
|
|
84
|
+
.actions button:hover { border-color: var(--shell-accent); }
|
|
85
|
+
.actions button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
86
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
appId: string;
|
|
3
|
+
appLabel: string;
|
|
4
|
+
version: string;
|
|
5
|
+
onConfirm: () => Promise<void>;
|
|
6
|
+
close: () => void;
|
|
7
|
+
}
|
|
8
|
+
declare const UninstallAppDialog: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type UninstallAppDialog = ReturnType<typeof UninstallAppDialog>;
|
|
10
|
+
export default UninstallAppDialog;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Shared permission-diff confirmation flow for the `update` path. Opens
|
|
3
|
+
* PermissionConfirmModal via modalManager and resolves to the user's choice.
|
|
4
|
+
* Used by the home-card "Check for updates" context-menu action.
|
|
5
|
+
*/
|
|
6
|
+
import { modalManager } from '../../overlays/modal';
|
|
7
|
+
import PermissionConfirmModal from './PermissionConfirmModal.svelte';
|
|
8
|
+
export function openPermissionConfirmModal(pkg, toVersion, added, removed) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const props = {
|
|
11
|
+
mode: 'update',
|
|
12
|
+
pkg: { label: pkg.label, version: toVersion },
|
|
13
|
+
fromVersion: pkg.version,
|
|
14
|
+
added,
|
|
15
|
+
removed,
|
|
16
|
+
onConfirm: () => {
|
|
17
|
+
handle.close();
|
|
18
|
+
resolve(true);
|
|
19
|
+
},
|
|
20
|
+
onCancel: () => {
|
|
21
|
+
handle.close();
|
|
22
|
+
resolve(false);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const handle = modalManager.open(PermissionConfirmModal, props);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -3,6 +3,12 @@ import type { StateZones } from '../../state/zones.svelte';
|
|
|
3
3
|
import type { ResolvedPackage } from '../../registry/client';
|
|
4
4
|
import type { InstalledPackage } from '../../registry/types';
|
|
5
5
|
import type { EnvState } from '../../env/types';
|
|
6
|
+
/**
|
|
7
|
+
* Pick a version entry from a resolved package. When `requested` is
|
|
8
|
+
* undefined returns `latest`; when set, finds the matching entry by
|
|
9
|
+
* exact version string. Throws if the requested version is absent.
|
|
10
|
+
*/
|
|
11
|
+
export declare function pickVersion(pkg: ResolvedPackage, requested: string | undefined): typeof pkg.latest;
|
|
6
12
|
/**
|
|
7
13
|
* Compute added and removed permissions between two manifest snapshots.
|
|
8
14
|
* Order within each array follows the input order of `newPerms` (for added)
|
|
@@ -34,7 +40,8 @@ export interface StoreContext {
|
|
|
34
40
|
isAdmin: boolean;
|
|
35
41
|
refreshCatalog(): Promise<void>;
|
|
36
42
|
refreshInstalled(): Promise<void>;
|
|
37
|
-
updatePackage(id: string, confirmPermissionChange?: (added: string[], removed: string[]) => Promise<boolean
|
|
43
|
+
updatePackage(id: string, confirmPermissionChange?: (added: string[], removed: string[]) => Promise<boolean>, version?: string): Promise<void>;
|
|
44
|
+
uninstallPackage(id: string): Promise<void>;
|
|
38
45
|
addRegistry(url: string): Promise<void>;
|
|
39
46
|
removeRegistry(url: string): Promise<void>;
|
|
40
47
|
}
|