420 lines
13 KiB
JavaScript
420 lines
13 KiB
JavaScript
import {
|
|
INI_PANEL_ENTRIES,
|
|
getIniPanelEntryByHref,
|
|
getIniPanelEntryManifest,
|
|
} from "./entry-manifest.js";
|
|
import {
|
|
createMachineSessionControlPageController,
|
|
} from "./control-page-controller.js";
|
|
import {
|
|
renderMachineSessionControlView,
|
|
} from "./control-view-renderer.js";
|
|
import {
|
|
controlPageStatusText,
|
|
createControlPageRefreshState,
|
|
refreshMachineSessionControlPage,
|
|
} from "./control-page-refresh-workflow.js";
|
|
import {
|
|
createControlPageSessionLoadState,
|
|
loadMachineSessionForControlPageWorkflow,
|
|
} from "./control-page-session-workflow.js";
|
|
import {
|
|
createMachineSessionReadonlyStatus,
|
|
createMachineSessionReadonlyStatusApiManifest,
|
|
readMachineSessionReadonlyStatus,
|
|
} from "./readonly-status-api.js";
|
|
export {
|
|
controlPageStatusText,
|
|
createControlPageRefreshState,
|
|
refreshMachineSessionControlPage,
|
|
} from "./control-page-refresh-workflow.js";
|
|
export {
|
|
createControlPageSessionLoadState,
|
|
loadMachineSessionForControlPageWorkflow,
|
|
} from "./control-page-session-workflow.js";
|
|
export {
|
|
createMachineSessionReadonlyStatus,
|
|
createMachineSessionReadonlyStatusApiManifest,
|
|
readMachineSessionReadonlyStatus,
|
|
} from "./readonly-status-api.js";
|
|
export {
|
|
INI_PANEL_ENTRIES,
|
|
getIniPanelEntryByHref,
|
|
getIniPanelEntryManifest,
|
|
} from "./entry-manifest.js";
|
|
export {
|
|
createMachineSessionControlPageController,
|
|
} from "./control-page-controller.js";
|
|
export {
|
|
renderMachineSessionControlView,
|
|
} from "./control-view-renderer.js";
|
|
|
|
export function getVisibleIniPanelEntries() {
|
|
return getIniPanelEntryManifest();
|
|
}
|
|
|
|
export function createIniPanelLauncherLinkViewModel(entries = getVisibleIniPanelEntries()) {
|
|
return entries.map(({ id, href, title }) => ({
|
|
entryId: id,
|
|
href,
|
|
label: title,
|
|
}));
|
|
}
|
|
|
|
export function createIniPanelLaunchApiSchema() {
|
|
return {
|
|
apiName: "ini-panel-launch",
|
|
manifestVersion: 1,
|
|
fields: [
|
|
"apiName",
|
|
"manifestVersion",
|
|
"methods",
|
|
"visibleEntries",
|
|
"targetPages",
|
|
"controlPageApiMethods",
|
|
"persistenceCapabilities",
|
|
],
|
|
};
|
|
}
|
|
|
|
export function isSupportedIniPanelLaunchApiManifest(manifest, schema = createIniPanelLaunchApiSchema()) {
|
|
if (!manifest || typeof manifest !== "object") {
|
|
return false;
|
|
}
|
|
if (manifest.apiName !== schema.apiName || manifest.manifestVersion !== schema.manifestVersion) {
|
|
return false;
|
|
}
|
|
return schema.fields.every((fieldName) => Object.hasOwn(manifest, fieldName));
|
|
}
|
|
|
|
export function createControlPageBrowserApiSchema() {
|
|
return {
|
|
apiName: "ini-panel-control-page",
|
|
manifestVersion: 1,
|
|
fields: [
|
|
"apiName",
|
|
"manifestVersion",
|
|
"methods",
|
|
"contract",
|
|
"readonlyStatusApiManifest",
|
|
],
|
|
};
|
|
}
|
|
|
|
export function isSupportedIniPanelControlPageApiManifest(
|
|
manifest,
|
|
schema = createControlPageBrowserApiSchema(),
|
|
) {
|
|
if (!manifest || typeof manifest !== "object") {
|
|
return false;
|
|
}
|
|
if (manifest.apiName !== schema.apiName || manifest.manifestVersion !== schema.manifestVersion) {
|
|
return false;
|
|
}
|
|
return schema.fields.every((fieldName) => Object.hasOwn(manifest, fieldName));
|
|
}
|
|
|
|
export function createIniPanelLaunchApiManifest(entries = getVisibleIniPanelEntries()) {
|
|
const schema = createIniPanelLaunchApiSchema();
|
|
return {
|
|
apiName: schema.apiName,
|
|
manifestVersion: schema.manifestVersion,
|
|
methods: [
|
|
"isSupportedLaunchApiManifest",
|
|
"getLaunchApiSchema",
|
|
"getLaunchApiManifest",
|
|
"getShellIntegrationSchema",
|
|
"getShellIntegrationManifest",
|
|
"getShellIntegrationReadiness",
|
|
"getLauncherLinks",
|
|
"getControlPageApiMethods",
|
|
],
|
|
visibleEntries: entries.map(({ id, href, title }) => ({
|
|
id,
|
|
href,
|
|
title,
|
|
})),
|
|
targetPages: entries.map(({ id, href }) => ({
|
|
entryId: id,
|
|
href,
|
|
})),
|
|
controlPageApiMethods: createControlPageBrowserApiMethods(),
|
|
persistenceCapabilities: [
|
|
{
|
|
id: "machine-files",
|
|
label: "Machine text files",
|
|
scope: "opfs",
|
|
},
|
|
{
|
|
id: "session-snapshot",
|
|
label: "Machine session snapshot",
|
|
scope: "opfs",
|
|
},
|
|
{
|
|
id: "readonly-status",
|
|
label: "Read-only machine session status",
|
|
scope: "control-page",
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
export function createIniPanelShellControlPageContract() {
|
|
const readonlyStatusApiManifest = createMachineSessionReadonlyStatusApiManifest();
|
|
return {
|
|
methodNames: [
|
|
"createController",
|
|
"loadSessionState",
|
|
"readStatus",
|
|
"refresh",
|
|
"renderView",
|
|
],
|
|
readonlyStatusApiManifest,
|
|
readonlyStatusApiMethodNames: Object.keys(readonlyStatusApiManifest.methods),
|
|
readonlyStatusApiStatusFields: readonlyStatusApiManifest.statusFields,
|
|
relatedControlPageMethods: readonlyStatusApiManifest.relatedControlPageMethods,
|
|
};
|
|
}
|
|
|
|
export function createControlPageBrowserApiMethods() {
|
|
return [
|
|
"isSupportedControlPageApiManifest",
|
|
"getControlPageApiSchema",
|
|
"getControlPageApiManifest",
|
|
"getControlPageApiMethods",
|
|
"getControlPageContract",
|
|
"getControlView",
|
|
"getPanelApi",
|
|
"loadControlPageSessionState",
|
|
"readControlPageStatus",
|
|
"refreshControlPage",
|
|
"renderControlPage",
|
|
];
|
|
}
|
|
|
|
export function createControlPageBrowserApiManifest() {
|
|
const schema = createControlPageBrowserApiSchema();
|
|
const contract = createIniPanelShellControlPageContract();
|
|
return {
|
|
apiName: schema.apiName,
|
|
manifestVersion: schema.manifestVersion,
|
|
methods: createControlPageBrowserApiMethods(),
|
|
contract,
|
|
readonlyStatusApiManifest: contract.readonlyStatusApiManifest,
|
|
};
|
|
}
|
|
|
|
export function createIniPanelShellIntegrationSchema() {
|
|
return {
|
|
apiName: "ini-panel-shell-integration",
|
|
manifestVersion: 1,
|
|
fields: [
|
|
"apiName",
|
|
"manifestVersion",
|
|
"pages",
|
|
"launcherLinks",
|
|
"launchApiSchema",
|
|
"launchApiManifest",
|
|
"controlPageApiSchema",
|
|
"controlPageApiManifest",
|
|
"compatibility",
|
|
"persistenceCapabilities",
|
|
],
|
|
};
|
|
}
|
|
|
|
export function isSupportedIniPanelShellIntegrationManifest(
|
|
manifest,
|
|
schema = createIniPanelShellIntegrationSchema(),
|
|
) {
|
|
if (!manifest || typeof manifest !== "object") {
|
|
return false;
|
|
}
|
|
if (manifest.apiName !== schema.apiName || manifest.manifestVersion !== schema.manifestVersion) {
|
|
return false;
|
|
}
|
|
return schema.fields.every((fieldName) => Object.hasOwn(manifest, fieldName));
|
|
}
|
|
|
|
export function createIniPanelShellIntegrationManifest(entries = getVisibleIniPanelEntries()) {
|
|
const schema = createIniPanelShellIntegrationSchema();
|
|
const launchApiSchema = createIniPanelLaunchApiSchema();
|
|
const launchApiManifest = createIniPanelLaunchApiManifest(entries);
|
|
const controlPageApiSchema = createControlPageBrowserApiSchema();
|
|
const controlPageApiManifest = createControlPageBrowserApiManifest();
|
|
return {
|
|
apiName: schema.apiName,
|
|
manifestVersion: schema.manifestVersion,
|
|
pages: entries.map(({ id, href, title }) => ({
|
|
id,
|
|
href,
|
|
title,
|
|
})),
|
|
launcherLinks: createIniPanelLauncherLinkViewModel(entries),
|
|
launchApiSchema,
|
|
launchApiManifest,
|
|
controlPageApiSchema,
|
|
controlPageApiManifest,
|
|
compatibility: {
|
|
launchApiManifest: isSupportedIniPanelLaunchApiManifest(launchApiManifest, launchApiSchema),
|
|
controlPageApiManifest: isSupportedIniPanelControlPageApiManifest(
|
|
controlPageApiManifest,
|
|
controlPageApiSchema,
|
|
),
|
|
},
|
|
persistenceCapabilities: launchApiManifest.persistenceCapabilities,
|
|
};
|
|
}
|
|
|
|
export function createIniPanelShellIntegrationReadiness(
|
|
manifest = createIniPanelShellIntegrationManifest(),
|
|
) {
|
|
const integrationManifestSupported = isSupportedIniPanelShellIntegrationManifest(manifest);
|
|
const launchApiManifestSupported = integrationManifestSupported
|
|
&& isSupportedIniPanelLaunchApiManifest(manifest.launchApiManifest, manifest.launchApiSchema);
|
|
const controlPageApiManifestSupported = integrationManifestSupported
|
|
&& isSupportedIniPanelControlPageApiManifest(
|
|
manifest.controlPageApiManifest,
|
|
manifest.controlPageApiSchema,
|
|
);
|
|
const compatibility = integrationManifestSupported && manifest.compatibility
|
|
? {
|
|
launchApiManifest: Boolean(manifest.compatibility.launchApiManifest),
|
|
controlPageApiManifest: Boolean(manifest.compatibility.controlPageApiManifest),
|
|
}
|
|
: {
|
|
launchApiManifest: false,
|
|
controlPageApiManifest: false,
|
|
};
|
|
const checks = {
|
|
integrationManifestSupported,
|
|
launchApiManifestSupported,
|
|
controlPageApiManifestSupported,
|
|
launchCompatibility: compatibility.launchApiManifest,
|
|
controlPageCompatibility: compatibility.controlPageApiManifest,
|
|
};
|
|
const missing = Object.entries(checks)
|
|
.filter(([, passed]) => !passed)
|
|
.map(([name]) => name);
|
|
const ready = missing.length === 0;
|
|
|
|
return {
|
|
phase: ready ? "ready" : "blocked",
|
|
ready,
|
|
checks,
|
|
missing,
|
|
counts: {
|
|
pages: Array.isArray(manifest?.pages) ? manifest.pages.length : 0,
|
|
launcherLinks: Array.isArray(manifest?.launcherLinks) ? manifest.launcherLinks.length : 0,
|
|
launchMethods: Array.isArray(manifest?.launchApiManifest?.methods)
|
|
? manifest.launchApiManifest.methods.length
|
|
: 0,
|
|
controlPageMethods: Array.isArray(manifest?.controlPageApiManifest?.methods)
|
|
? manifest.controlPageApiManifest.methods.length
|
|
: 0,
|
|
persistenceCapabilities: Array.isArray(manifest?.persistenceCapabilities)
|
|
? manifest.persistenceCapabilities.length
|
|
: 0,
|
|
},
|
|
apiNames: {
|
|
integration: manifest?.apiName ?? null,
|
|
launch: manifest?.launchApiManifest?.apiName ?? null,
|
|
controlPage: manifest?.controlPageApiManifest?.apiName ?? null,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function createIniPanelShellIntegrationWorkflowPlan({
|
|
manifest = createIniPanelShellIntegrationManifest(),
|
|
readiness = createIniPanelShellIntegrationReadiness(manifest),
|
|
controlEntryId = "control-view",
|
|
} = {}) {
|
|
const targetPage = manifest?.launchApiManifest?.targetPages?.find(
|
|
(page) => page.entryId === controlEntryId,
|
|
) ?? null;
|
|
const requiredLaunchApiMethods = [
|
|
"getShellIntegrationManifest",
|
|
"getShellIntegrationReadiness",
|
|
];
|
|
const requiredControlPageApiMethods = [
|
|
"getControlPageApiManifest",
|
|
"loadControlPageSessionState",
|
|
"readControlPageStatus",
|
|
];
|
|
const launchMethods = Array.isArray(manifest?.launchApiManifest?.methods)
|
|
? manifest.launchApiManifest.methods
|
|
: [];
|
|
const controlPageMethods = Array.isArray(manifest?.controlPageApiManifest?.methods)
|
|
? manifest.controlPageApiManifest.methods
|
|
: [];
|
|
const missingLaunchApiMethods = requiredLaunchApiMethods.filter(
|
|
(methodName) => !launchMethods.includes(methodName),
|
|
);
|
|
const missingControlPageApiMethods = requiredControlPageApiMethods.filter(
|
|
(methodName) => !controlPageMethods.includes(methodName),
|
|
);
|
|
const missing = [
|
|
...readiness.missing,
|
|
...(targetPage ? [] : ["controlTargetPage"]),
|
|
...missingLaunchApiMethods.map((methodName) => `launchApi.${methodName}`),
|
|
...missingControlPageApiMethods.map((methodName) => `controlPageApi.${methodName}`),
|
|
];
|
|
const ready = readiness.ready && missing.length === 0;
|
|
|
|
return {
|
|
phase: ready ? "ready" : "blocked",
|
|
ready,
|
|
controlEntryId,
|
|
targetPage,
|
|
readiness,
|
|
requiredLaunchApiMethods,
|
|
requiredControlPageApiMethods,
|
|
missingLaunchApiMethods,
|
|
missingControlPageApiMethods,
|
|
missing,
|
|
readonlyStatusHandoff: {
|
|
loadSessionStateMethod: "loadControlPageSessionState",
|
|
readStatusMethod: "readControlPageStatus",
|
|
expectedStatusFields: manifest?.controlPageApiManifest?.readonlyStatusApiManifest?.statusFields ?? [],
|
|
},
|
|
};
|
|
}
|
|
|
|
export function createIniPanelShellViewModel(entries = getVisibleIniPanelEntries()) {
|
|
const shellIntegrationManifest = createIniPanelShellIntegrationManifest(entries);
|
|
const shellIntegrationReadiness = createIniPanelShellIntegrationReadiness(shellIntegrationManifest);
|
|
return {
|
|
pages: entries.map(({ id, href, title }) => ({
|
|
id,
|
|
href,
|
|
title,
|
|
})),
|
|
launcherLinks: createIniPanelLauncherLinkViewModel(entries),
|
|
shellIntegrationSchema: createIniPanelShellIntegrationSchema(),
|
|
shellIntegrationManifest,
|
|
shellIntegrationReadiness,
|
|
shellIntegrationWorkflowPlan: createIniPanelShellIntegrationWorkflowPlan({
|
|
manifest: shellIntegrationManifest,
|
|
readiness: shellIntegrationReadiness,
|
|
}),
|
|
launchApiSchema: createIniPanelLaunchApiSchema(),
|
|
launchApiManifest: createIniPanelLaunchApiManifest(entries),
|
|
isSupportedShellIntegrationManifest: isSupportedIniPanelShellIntegrationManifest,
|
|
createShellIntegrationReadiness: createIniPanelShellIntegrationReadiness,
|
|
createShellIntegrationWorkflowPlan: createIniPanelShellIntegrationWorkflowPlan,
|
|
controlPage: {
|
|
browserApiSchema: createControlPageBrowserApiSchema(),
|
|
browserApiManifest: createControlPageBrowserApiManifest(),
|
|
browserApiMethods: createControlPageBrowserApiMethods(),
|
|
contract: createIniPanelShellControlPageContract(),
|
|
createController: createMachineSessionControlPageController,
|
|
isSupportedBrowserApiManifest: isSupportedIniPanelControlPageApiManifest,
|
|
loadSessionState: loadMachineSessionForControlPageWorkflow,
|
|
readonlyStatusApiManifest: createMachineSessionReadonlyStatusApiManifest(),
|
|
readStatus: readMachineSessionReadonlyStatus,
|
|
refresh: refreshMachineSessionControlPage,
|
|
renderView: renderMachineSessionControlView,
|
|
},
|
|
};
|
|
}
|