import type { CanvasApplication } from "../canvases"; import type { MediaLibraryApplication } from "../installations"; import type { Interface, ResolvedInterfacesOf } from "./applications"; import { BrettApplication, brandApplicationId, type ApplicationId, } from "./brett-applications"; import type { CoreApp, CoreApplication } from "./core-apps"; import type { Studio, StudioApplication, StudioWorkspace } from "./studios"; /** * @public */ export type Application = | CoreApplication | StudioApplication | StudioWorkspace | CanvasApplication | MediaLibraryApplication; /** * @public */ export class ApplicationList { readonly applications: TApplication[]; /** * @param applications - The applications to initialize the list with. */ constructor(applications: TApplication[]) { this.applications = [...applications]; } toJSON(): (Studio | CoreApp)[] { return this.applications.flatMap((application) => application instanceof BrettApplication ? [application.toJSON()] : [], ); } /** Enables `for...of`, spread and `Array.from` over the list. */ [Symbol.iterator](): ArrayIterator { return this.applications[Symbol.iterator](); } findApplicationsByType( type: T, ): Extract[]; findApplicationsByType( types: T, ): Extract[]; findApplicationsByType( _type: TApplication["type"] | TApplication["type"][], ): TApplication[] { const types = Array.isArray(_type) ? _type : [_type]; return this.applications.filter((r) => types.includes(r.type)); } findApplication( type: "media-library", ): Extract | undefined; findApplication( type: "canvas", ): Extract | undefined; findApplication( type: "coreApp", id: ApplicationId, ): Extract | undefined; findApplication( type: "studio", id: ApplicationId, ): Extract | undefined; findApplication( type: "workspace", id: string, ): Extract | undefined; findApplication( type: TApplication["type"], id?: ApplicationId | string, ): TApplication | undefined { switch (type) { case "media-library": case "canvas": return this.applications.find((r) => r.type === type); case "coreApp": case "studio": case "workspace": { return this.applications.find((r) => r.type === type && r.id === id); } } } /** * The one application a location puts in the foreground — media library and * canvas by route (one per org), applications by the id in the URL. * `undefined` when nothing matches or the match has no app view (not * navigable, e.g. an SDK app that only docks panels). */ findByPath(pathname: string): TApplication | undefined { const [root, id] = pathname.split("/").filter(Boolean); const application = (() => { switch (root) { case "media": return this.findApplication("media-library"); case "canvas": return this.findApplication("canvas"); case "studio": return id ? this.findApplication("studio", brandApplicationId(id)) : undefined; case "application": return id ? this.findApplication("coreApp", brandApplicationId(id)) : undefined; case "local": // A local app can share its id with its deployed twin; the /local // route always renders the local instance. return this.applications.find( (candidate) => candidate.isLocal && (candidate.type === "studio" || candidate.type === "coreApp") && candidate.id === id, ); default: return undefined; } })(); return application?.hasAppView ? application : undefined; } /** * Every brokered interface across the whole list, flattened into loadable * resolved refs — narrowed to the given `type`, or all of them. Delegates to * each app's {@link AbstractApplication.resolvedInterfaces}, so a consumer * holding the list (e.g. off the `applications.list` bus topic) can discover * every `tile` (with its footprint metadata) in the workbench and load each * into its own federation instance in one call. */ resolvedInterfaces( type?: T, ): ResolvedInterfacesOf { return this.applications.flatMap((application) => application.resolvedInterfaces(type), ) as unknown as ResolvedInterfacesOf; } /** * Absence counts as a value, so callers diffing the foregrounded application * can compare across the states where nothing is. */ static areApplicationsEqual( application1?: Application | null, application2?: Application | null, ): boolean { if (application1 == null || application2 == null) { return application1 === application2; } return application1.id === application2.id; } }