refactor(ext/vault): extract vault-router.ts; trim vault.ts to entry point (Plan C Phase 4)
Moves the routing core — parseHash/setHash, the renderPane pane-dispatch + teardownPaneComponents, loadManifest, and selectItem — out of vault.ts into vault-router.ts (carrying the popup-component imports with it). vault.ts is now just the entry point: state singleton, the VaultController assembly, the StateHost registration, and the DOMContentLoaded bootstrap (1037 -> 203 LOC). No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
205
extension/src/vault/vault-router.ts
Normal file
205
extension/src/vault/vault-router.ts
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
// Vault-tab routing core: hash parsing/serialization, pane dispatch (delegating
|
||||||
|
// to the shared popup components), and data loading. Receives the
|
||||||
|
// VaultController (`ctx`) and reaches sibling concerns through it. Imports only
|
||||||
|
// from shared/, the popup components, vault-context, vault-drawer, and
|
||||||
|
// vault-form-wrapper — never from vault.ts or the shell/sidebar/list modules.
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ItemId, ItemType, ManifestEntry, Item, VaultSettings,
|
||||||
|
} from '../shared/types';
|
||||||
|
import { renderItemDetail } from '../popup/components/item-detail';
|
||||||
|
import { renderItemForm } from '../popup/components/item-form';
|
||||||
|
import { renderTrash, teardown as teardownTrash } from '../popup/components/trash';
|
||||||
|
import { renderDevices, teardown as teardownDevices } from '../popup/components/devices';
|
||||||
|
import { renderSettings, teardownSettings } from '../popup/components/settings';
|
||||||
|
import { renderVaultSettings as renderVaultSettingsView } from '../popup/components/settings-vault';
|
||||||
|
import { renderFieldHistory, teardown as teardownFieldHistory } from '../popup/components/field-history';
|
||||||
|
import { renderItemHistoryIndex, teardown as teardownHistoryIndex } from '../popup/components/item-history-index';
|
||||||
|
import { renderBackupPanel, teardown as teardownBackup } from './components/backup-panel';
|
||||||
|
import { renderImportPanel, teardown as teardownImport } from './components/import-panel';
|
||||||
|
import {
|
||||||
|
type VaultController, type VaultView, type HashRoute,
|
||||||
|
} from './vault-context';
|
||||||
|
import { ensureDrawerClosedForRoute } from './vault-drawer';
|
||||||
|
import { renderFormWrapped } from './vault-form-wrapper';
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Hash routing
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function parseHash(): HashRoute {
|
||||||
|
let raw = window.location.hash.replace(/^#\/?/, '');
|
||||||
|
if (!raw) return { view: 'list' };
|
||||||
|
|
||||||
|
// Normalize legacy bookmarks: #field-history/<id> → #history/<id>
|
||||||
|
if (raw.startsWith('field-history/')) {
|
||||||
|
raw = 'history/' + raw.slice('field-history/'.length);
|
||||||
|
window.location.hash = raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = raw.split('/');
|
||||||
|
const view = parts[0] as VaultView;
|
||||||
|
|
||||||
|
switch (view) {
|
||||||
|
case 'detail':
|
||||||
|
case 'edit':
|
||||||
|
return { view, id: parts[1] };
|
||||||
|
case 'add':
|
||||||
|
return { view, type: parts[1] };
|
||||||
|
case 'history':
|
||||||
|
return parts[1]
|
||||||
|
? { view: 'field-history', id: parts[1] }
|
||||||
|
: { view: 'history' };
|
||||||
|
case 'trash':
|
||||||
|
case 'devices':
|
||||||
|
case 'settings':
|
||||||
|
case 'settings-vault':
|
||||||
|
case 'field-history':
|
||||||
|
case 'backup':
|
||||||
|
case 'import':
|
||||||
|
return { view };
|
||||||
|
default:
|
||||||
|
return { view: 'list' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setHash(view: VaultView, param?: string): void {
|
||||||
|
const fragment = param ? `${view}/${param}` : view;
|
||||||
|
window.location.hash = fragment === 'list' ? '' : fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Pane rendering — delegates to shared popup components
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function teardownPaneComponents(): void {
|
||||||
|
teardownTrash();
|
||||||
|
teardownDevices();
|
||||||
|
teardownSettings();
|
||||||
|
teardownFieldHistory();
|
||||||
|
teardownHistoryIndex();
|
||||||
|
teardownBackup();
|
||||||
|
teardownImport();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderPane(ctx: VaultController): void {
|
||||||
|
const pane = document.getElementById('vault-pane');
|
||||||
|
if (!pane) return;
|
||||||
|
|
||||||
|
teardownPaneComponents();
|
||||||
|
|
||||||
|
const route = parseHash();
|
||||||
|
ensureDrawerClosedForRoute(ctx.state, route);
|
||||||
|
// Keep state.view in sync with hash for components that read it
|
||||||
|
ctx.state.view = route.view;
|
||||||
|
ctx.applyShellViewClass();
|
||||||
|
|
||||||
|
pane.className = 'vault-pane';
|
||||||
|
|
||||||
|
switch (route.view) {
|
||||||
|
case 'detail':
|
||||||
|
if (ctx.state.selectedItem) {
|
||||||
|
renderItemDetail(pane);
|
||||||
|
} else {
|
||||||
|
pane.className = 'vault-pane vault-pane--empty';
|
||||||
|
pane.innerHTML = 'select an item';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
// Prefer hash type for deep-links; otherwise keep the in-memory value
|
||||||
|
// set by the type-selection click handler (which calls setState →
|
||||||
|
// renderPane before the URL hash has been updated to include the type).
|
||||||
|
ctx.state.newType = (route.type as ItemType) ?? ctx.state.newType ?? null;
|
||||||
|
// Use the form wrapper (sticky bar + header) when a type is already chosen.
|
||||||
|
// Without a type the type-selection screen renders — no sticky bar needed.
|
||||||
|
if (ctx.state.newType) {
|
||||||
|
renderFormWrapped(ctx, pane, 'add');
|
||||||
|
} else {
|
||||||
|
renderItemForm(pane, 'add');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'edit':
|
||||||
|
renderFormWrapped(ctx, pane, 'edit');
|
||||||
|
break;
|
||||||
|
case 'trash':
|
||||||
|
renderTrash(pane);
|
||||||
|
break;
|
||||||
|
case 'devices':
|
||||||
|
renderDevices(pane);
|
||||||
|
break;
|
||||||
|
case 'settings':
|
||||||
|
void renderSettings(pane);
|
||||||
|
break;
|
||||||
|
case 'settings-vault':
|
||||||
|
renderVaultSettingsView(pane);
|
||||||
|
break;
|
||||||
|
case 'field-history':
|
||||||
|
renderFieldHistory(pane);
|
||||||
|
break;
|
||||||
|
case 'history':
|
||||||
|
renderItemHistoryIndex(pane);
|
||||||
|
break;
|
||||||
|
case 'backup':
|
||||||
|
renderBackupPanel(pane);
|
||||||
|
break;
|
||||||
|
case 'import':
|
||||||
|
renderImportPanel(pane);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pane.className = 'vault-pane vault-pane--empty';
|
||||||
|
pane.innerHTML = 'select an item';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Data loading
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export async function loadManifest(ctx: VaultController): Promise<void> {
|
||||||
|
const listResp = await ctx.sendMessage({ type: 'list_items' });
|
||||||
|
if (listResp.ok) {
|
||||||
|
const data = listResp.data as { items: Array<[ItemId, ManifestEntry]> };
|
||||||
|
ctx.state.entries = data.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vsResp = await ctx.sendMessage({ type: 'get_vault_settings' });
|
||||||
|
if (vsResp.ok) {
|
||||||
|
const data = vsResp.data as { settings: VaultSettings };
|
||||||
|
ctx.state.vaultSettings = data.settings;
|
||||||
|
ctx.state.generatorDefaults = data.settings.generator_defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle deep link from hash
|
||||||
|
const route = parseHash();
|
||||||
|
if (route.view === 'detail' && route.id) {
|
||||||
|
const itemResp = await ctx.sendMessage({ type: 'get_item', id: route.id });
|
||||||
|
if (itemResp.ok) {
|
||||||
|
const data = itemResp.data as { item: Item };
|
||||||
|
ctx.state.selectedId = route.id;
|
||||||
|
ctx.state.selectedItem = data.item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Legacy selectItem — used by hash-change deep linking
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export async function selectItem(ctx: VaultController, id: ItemId): Promise<void> {
|
||||||
|
ctx.state.loading = true;
|
||||||
|
const resp = await ctx.sendMessage({ type: 'get_item', id });
|
||||||
|
if (resp.ok) {
|
||||||
|
const data = resp.data as { item: Item };
|
||||||
|
ctx.state.selectedId = id;
|
||||||
|
ctx.state.selectedItem = data.item;
|
||||||
|
ctx.state.loading = false;
|
||||||
|
setHash('detail', id);
|
||||||
|
ctx.renderSidebarCategories();
|
||||||
|
ctx.renderListPane();
|
||||||
|
renderPane(ctx);
|
||||||
|
} else {
|
||||||
|
ctx.state.loading = false;
|
||||||
|
ctx.state.error = (resp as { error: string }).error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,23 +5,10 @@
|
|||||||
/// vault tab's pane area.
|
/// vault tab's pane area.
|
||||||
|
|
||||||
import type { Request, Response } from '../shared/messages';
|
import type { Request, Response } from '../shared/messages';
|
||||||
import type {
|
|
||||||
ItemId, ItemType, ManifestEntry, Item, VaultSettings, GeneratorRequest,
|
|
||||||
} from '../shared/types';
|
|
||||||
import { registerHost } from '../shared/state';
|
import { registerHost } from '../shared/state';
|
||||||
import { type ErrorCta } from '../shared/error-copy';
|
import { type ErrorCta } from '../shared/error-copy';
|
||||||
import { renderItemDetail } from '../popup/components/item-detail';
|
|
||||||
import { renderItemForm } from '../popup/components/item-form';
|
|
||||||
import { renderTrash, teardown as teardownTrash } from '../popup/components/trash';
|
|
||||||
import { renderDevices, teardown as teardownDevices } from '../popup/components/devices';
|
|
||||||
import { renderSettings, teardownSettings } from '../popup/components/settings';
|
|
||||||
import { renderVaultSettings as renderVaultSettingsView } from '../popup/components/settings-vault';
|
|
||||||
import { renderFieldHistory, teardown as teardownFieldHistory } from '../popup/components/field-history';
|
|
||||||
import { renderItemHistoryIndex, teardown as teardownHistoryIndex } from '../popup/components/item-history-index';
|
|
||||||
import { renderBackupPanel, teardown as teardownBackup } from './components/backup-panel';
|
|
||||||
import { renderImportPanel, teardown as teardownImport } from './components/import-panel';
|
|
||||||
import {
|
import {
|
||||||
type VaultController, type VaultState, type VaultView, type HashRoute,
|
type VaultController, type VaultState, type VaultView,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
} from './vault-context';
|
} from './vault-context';
|
||||||
import {
|
import {
|
||||||
@@ -33,9 +20,8 @@ import { wireSidebar, renderSidebarCategories } from './vault-sidebar';
|
|||||||
import { renderListPane } from './vault-list';
|
import { renderListPane } from './vault-list';
|
||||||
import {
|
import {
|
||||||
openDrawer, closeDrawer, renderDrawer, selectItemForDrawer,
|
openDrawer, closeDrawer, renderDrawer, selectItemForDrawer,
|
||||||
ensureDrawerClosedForRoute,
|
|
||||||
} from './vault-drawer';
|
} from './vault-drawer';
|
||||||
import { renderFormWrapped } from './vault-form-wrapper';
|
import { parseHash, setHash, renderPane, loadManifest, selectItem } from './vault-router';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Helpers
|
// Helpers
|
||||||
@@ -70,51 +56,6 @@ function sendMessage(request: Request): Promise<Response> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Hash routing
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function parseHash(): HashRoute {
|
|
||||||
let raw = window.location.hash.replace(/^#\/?/, '');
|
|
||||||
if (!raw) return { view: 'list' };
|
|
||||||
|
|
||||||
// Normalize legacy bookmarks: #field-history/<id> → #history/<id>
|
|
||||||
if (raw.startsWith('field-history/')) {
|
|
||||||
raw = 'history/' + raw.slice('field-history/'.length);
|
|
||||||
window.location.hash = raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = raw.split('/');
|
|
||||||
const view = parts[0] as VaultView;
|
|
||||||
|
|
||||||
switch (view) {
|
|
||||||
case 'detail':
|
|
||||||
case 'edit':
|
|
||||||
return { view, id: parts[1] };
|
|
||||||
case 'add':
|
|
||||||
return { view, type: parts[1] };
|
|
||||||
case 'history':
|
|
||||||
return parts[1]
|
|
||||||
? { view: 'field-history', id: parts[1] }
|
|
||||||
: { view: 'history' };
|
|
||||||
case 'trash':
|
|
||||||
case 'devices':
|
|
||||||
case 'settings':
|
|
||||||
case 'settings-vault':
|
|
||||||
case 'field-history':
|
|
||||||
case 'backup':
|
|
||||||
case 'import':
|
|
||||||
return { view };
|
|
||||||
default:
|
|
||||||
return { view: 'list' };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setHash(view: VaultView, param?: string): void {
|
|
||||||
const fragment = param ? `${view}/${param}` : view;
|
|
||||||
window.location.hash = fragment === 'list' ? '' : fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// State
|
// State
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -148,7 +89,7 @@ const ctx: VaultController = {
|
|||||||
state,
|
state,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
render: () => render(ctx),
|
render: () => render(ctx),
|
||||||
renderPane: () => renderPane(),
|
renderPane: () => renderPane(ctx),
|
||||||
renderListPane: () => renderListPane(ctx),
|
renderListPane: () => renderListPane(ctx),
|
||||||
renderSidebarCategories: () => renderSidebarCategories(ctx),
|
renderSidebarCategories: () => renderSidebarCategories(ctx),
|
||||||
renderDrawer: (item) => renderDrawer(ctx, item),
|
renderDrawer: (item) => renderDrawer(ctx, item),
|
||||||
@@ -160,7 +101,7 @@ const ctx: VaultController = {
|
|||||||
openTypePanel: () => openTypePanel(ctx),
|
openTypePanel: () => openTypePanel(ctx),
|
||||||
closeTypePanel: () => closeTypePanel(ctx),
|
closeTypePanel: () => closeTypePanel(ctx),
|
||||||
wireSidebar: () => wireSidebar(ctx),
|
wireSidebar: () => wireSidebar(ctx),
|
||||||
loadManifest: () => loadManifest(),
|
loadManifest: () => loadManifest(ctx),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -171,7 +112,7 @@ registerHost({
|
|||||||
getState: () => state,
|
getState: () => state,
|
||||||
setState: (partial) => {
|
setState: (partial) => {
|
||||||
Object.assign(state, partial);
|
Object.assign(state, partial);
|
||||||
renderPane();
|
renderPane(ctx);
|
||||||
},
|
},
|
||||||
navigate: (view, extras) => {
|
navigate: (view, extras) => {
|
||||||
Object.assign(state, { view, error: null, loading: false, ...extras });
|
Object.assign(state, { view, error: null, loading: false, ...extras });
|
||||||
@@ -179,7 +120,7 @@ registerHost({
|
|||||||
applyShellViewClass(ctx);
|
applyShellViewClass(ctx);
|
||||||
renderSidebarCategories(ctx);
|
renderSidebarCategories(ctx);
|
||||||
if (state.view === 'list') renderListPane(ctx);
|
if (state.view === 'list') renderListPane(ctx);
|
||||||
renderPane();
|
renderPane(ctx);
|
||||||
},
|
},
|
||||||
sendMessage,
|
sendMessage,
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
@@ -188,120 +129,6 @@ registerHost({
|
|||||||
openVaultTab: () => {},
|
openVaultTab: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Pane rendering — delegates to shared popup components
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function teardownPaneComponents(): void {
|
|
||||||
teardownTrash();
|
|
||||||
teardownDevices();
|
|
||||||
teardownSettings();
|
|
||||||
teardownFieldHistory();
|
|
||||||
teardownHistoryIndex();
|
|
||||||
teardownBackup();
|
|
||||||
teardownImport();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPane(): void {
|
|
||||||
const pane = document.getElementById('vault-pane');
|
|
||||||
if (!pane) return;
|
|
||||||
|
|
||||||
teardownPaneComponents();
|
|
||||||
|
|
||||||
const route = parseHash();
|
|
||||||
ensureDrawerClosedForRoute(state, route);
|
|
||||||
// Keep state.view in sync with hash for components that read it
|
|
||||||
state.view = route.view;
|
|
||||||
applyShellViewClass(ctx);
|
|
||||||
|
|
||||||
pane.className = 'vault-pane';
|
|
||||||
|
|
||||||
switch (route.view) {
|
|
||||||
case 'detail':
|
|
||||||
if (state.selectedItem) {
|
|
||||||
renderItemDetail(pane);
|
|
||||||
} else {
|
|
||||||
pane.className = 'vault-pane vault-pane--empty';
|
|
||||||
pane.innerHTML = 'select an item';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'add':
|
|
||||||
// Prefer hash type for deep-links; otherwise keep the in-memory value
|
|
||||||
// set by the type-selection click handler (which calls setState →
|
|
||||||
// renderPane before the URL hash has been updated to include the type).
|
|
||||||
state.newType = (route.type as ItemType) ?? state.newType ?? null;
|
|
||||||
// Use the form wrapper (sticky bar + header) when a type is already chosen.
|
|
||||||
// Without a type the type-selection screen renders — no sticky bar needed.
|
|
||||||
if (state.newType) {
|
|
||||||
renderFormWrapped(ctx, pane, 'add');
|
|
||||||
} else {
|
|
||||||
renderItemForm(pane, 'add');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'edit':
|
|
||||||
renderFormWrapped(ctx, pane, 'edit');
|
|
||||||
break;
|
|
||||||
case 'trash':
|
|
||||||
renderTrash(pane);
|
|
||||||
break;
|
|
||||||
case 'devices':
|
|
||||||
renderDevices(pane);
|
|
||||||
break;
|
|
||||||
case 'settings':
|
|
||||||
void renderSettings(pane);
|
|
||||||
break;
|
|
||||||
case 'settings-vault':
|
|
||||||
renderVaultSettingsView(pane);
|
|
||||||
break;
|
|
||||||
case 'field-history':
|
|
||||||
renderFieldHistory(pane);
|
|
||||||
break;
|
|
||||||
case 'history':
|
|
||||||
renderItemHistoryIndex(pane);
|
|
||||||
break;
|
|
||||||
case 'backup':
|
|
||||||
renderBackupPanel(pane);
|
|
||||||
break;
|
|
||||||
case 'import':
|
|
||||||
renderImportPanel(pane);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
pane.className = 'vault-pane vault-pane--empty';
|
|
||||||
pane.innerHTML = 'select an item';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Data loading
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
async function loadManifest(): Promise<void> {
|
|
||||||
const listResp = await sendMessage({ type: 'list_items' });
|
|
||||||
if (listResp.ok) {
|
|
||||||
const data = listResp.data as { items: Array<[ItemId, ManifestEntry]> };
|
|
||||||
state.entries = data.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vsResp = await sendMessage({ type: 'get_vault_settings' });
|
|
||||||
if (vsResp.ok) {
|
|
||||||
const data = vsResp.data as { settings: VaultSettings };
|
|
||||||
state.vaultSettings = data.settings;
|
|
||||||
state.generatorDefaults = data.settings.generator_defaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle deep link from hash
|
|
||||||
const route = parseHash();
|
|
||||||
if (route.view === 'detail' && route.id) {
|
|
||||||
const itemResp = await sendMessage({ type: 'get_item', id: route.id });
|
|
||||||
if (itemResp.ok) {
|
|
||||||
const data = itemResp.data as { item: Item };
|
|
||||||
state.selectedId = route.id;
|
|
||||||
state.selectedItem = data.item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Init
|
// Init
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -337,7 +164,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
const data = resp.data as { unlocked: boolean };
|
const data = resp.data as { unlocked: boolean };
|
||||||
if (data.unlocked) {
|
if (data.unlocked) {
|
||||||
state.unlocked = true;
|
state.unlocked = true;
|
||||||
await loadManifest();
|
await loadManifest(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,13 +183,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
// If navigating to a detail/edit view for an item we already have loaded
|
// If navigating to a detail/edit view for an item we already have loaded
|
||||||
if ((route.view === 'detail' || route.view === 'edit') && route.id) {
|
if ((route.view === 'detail' || route.view === 'edit') && route.id) {
|
||||||
if (state.selectedId === route.id && state.selectedItem) {
|
if (state.selectedId === route.id && state.selectedItem) {
|
||||||
renderPane();
|
renderPane(ctx);
|
||||||
renderSidebarCategories(ctx);
|
renderSidebarCategories(ctx);
|
||||||
if (state.view === 'list') renderListPane(ctx);
|
if (state.view === 'list') renderListPane(ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Need to fetch the item
|
// Need to fetch the item
|
||||||
selectItem(route.id);
|
selectItem(ctx, route.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,28 +198,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
state.selectedItem = null;
|
state.selectedItem = null;
|
||||||
renderSidebarCategories(ctx);
|
renderSidebarCategories(ctx);
|
||||||
if (state.view === 'list') renderListPane(ctx);
|
if (state.view === 'list') renderListPane(ctx);
|
||||||
renderPane();
|
renderPane(ctx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Legacy selectItem — used by hash-change deep linking
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
async function selectItem(id: ItemId): Promise<void> {
|
|
||||||
state.loading = true;
|
|
||||||
const resp = await sendMessage({ type: 'get_item', id });
|
|
||||||
if (resp.ok) {
|
|
||||||
const data = resp.data as { item: Item };
|
|
||||||
state.selectedId = id;
|
|
||||||
state.selectedItem = data.item;
|
|
||||||
state.loading = false;
|
|
||||||
setHash('detail', id);
|
|
||||||
renderSidebarCategories(ctx);
|
|
||||||
renderListPane(ctx);
|
|
||||||
renderPane();
|
|
||||||
} else {
|
|
||||||
state.loading = false;
|
|
||||||
state.error = (resp as { error: string }).error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user