import type { Project } from "../../projects"; import { joinUrlPaths, normalizePath } from "../../shared/urls"; import { AbstractApplication } from "../applications"; import type { IconDescriptor } from "../applications"; import type { ApplicationId } from "../brett-applications"; import type { Workspace } from "./schemas"; import type { StudioApplication } from "./studios"; /** * @public */ export class StudioWorkspace extends AbstractApplication<"workspace"> { /** * Workspaces always belong to a studio application. * They do not exist on their own & therefore can access * information about the studio they're in via this property. */ private readonly studioApplication: StudioApplication; readonly workspace: Workspace; readonly project: Project; private readonly isDefaultWorkspace: boolean; constructor( studioApplication: StudioApplication, workspace: Workspace, project: Project, isDefaultWorkspace: boolean, ) { super("workspace"); this.studioApplication = studioApplication; this.workspace = workspace; this.project = project; this.isDefaultWorkspace = isDefaultWorkspace; } /** * The studio application that this workspace belongs to. */ get studio() { return this.studioApplication; } get id() { return StudioWorkspace.makeId(this.studio.id, this.workspace.name); } get href() { return joinUrlPaths(this.studio.href, this.workspace.name); } get title() { /** * If there's no manifest we will have created a single workspace for the application. * In this circumstance we won't have a meaningful title, so instead we use the project's display name. */ if (this.isDefaultWorkspace) { return this.project.displayName; } return this.workspace.title; } get subtitle() { if (this.isDefaultWorkspace) { return this.studio.url.hostname; } return this.get("subtitle"); } get>( attr: TKey, ): Workspace[TKey] { return this.workspace[attr]; } get isFederated() { return this.studio.isFederated; } /** * @returns the URL to the workspace of a studio application. */ get url(): URL { const studioUrl = new URL(this.studio.url); const normalizedUrlPath = normalizePath(studioUrl.pathname); let finalBasePath = normalizePath(this.get("basePath")); // the studio URL may already contain the basepath for externally hosted // studios or embedded studios, so we deduplicate the segments if (finalBasePath.startsWith(normalizedUrlPath)) { finalBasePath = finalBasePath.slice(normalizedUrlPath.length); } studioUrl.pathname = joinUrlPaths(normalizedUrlPath, finalBasePath); return studioUrl; } get icon(): IconDescriptor { const svg = this.workspace.icon; if (svg) { return { variant: "image", svg }; } return { variant: "avatar", initials: this.initials, color: this.avatarColor, }; } static makeId(applicationId: ApplicationId, workspaceName: string) { return `${applicationId}-${workspaceName}`; } static splitId(id: string): [string, string] { return id.split(new RegExp(/-(.*)/)).slice(0, 2) as [string, string]; } }