@scion/workbench
Version:
SCION Workbench enables the creation of Angular web applications that require a flexible layout to display content side-by-side or stacked, all personalizable by the user via drag & drop. This type of layout is ideal for applications with non-linear workf
1,190 lines (1,179 loc) • 255 kB
TypeScript
import * as _angular_cdk_portal from '@angular/cdk/portal';
import { ComponentType } from '@angular/cdk/portal';
import * as i0 from '@angular/core';
import { InjectionToken, Signal, Injector, Type, EnvironmentProviders, TemplateRef, WritableSignal, ComponentRef, Provider, ViewContainerRef, OnDestroy, OnInit, DestroyableInjector, ElementRef, PipeTransform } from '@angular/core';
import { MicrofrontendPlatformConfig, Qualifier, Capability } from '@scion/microfrontend-platform';
import { CanMatchFn, UrlSegment, NavigationExtras, ActivatedRoute } from '@angular/router';
import { Observable, BehaviorSubject } from 'rxjs';
import * as _scion_workbench from '@scion/workbench';
/**
* Set of log levels to control logging output.
*/
declare enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3
}
/**
* Provides information about a message to be logged.
*/
interface LogEvent {
/**
* Severity of the message.
*/
level: LogLevel;
/**
* Name of the logger used to log the message.
*/
logger: string;
/**
* Supplies the message.
*/
messageSupplier: () => string;
/**
* Arguments as passed to the logger.
*/
args: any[];
}
/**
* Delivers log events to a destination, e.g., writing logs to the console.
*/
declare abstract class LogAppender {
/**
* Each message to be logged is passed to this method in the form of a {@link LogEvent} object.
*/
abstract onLogMessage(event: LogEvent): void;
static ɵfac: i0.ɵɵFactoryDeclaration<LogAppender, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<LogAppender>;
}
/**
* Logger name to be used to log a message.
*
* Pass an instance of this class as the first argument to the logger when logging a message.
*/
declare class LoggerName {
private readonly _value;
constructor(_value: string);
toString(): string;
}
/**
* Logger used by the workbench to log messages.
*
* Log messages are passed to registered log appenders in the form of log events for delivery to a destination.
*
* When configuring the workbench, you can register one or more log appenders and specify the minimum severity
* level a message must have in order to be logged. At runtime, you can change the minimum required log level
* by setting the `loglevel` query parameter. For example, to log messages with debug severity or higher, set
* the query parameters as follows: `loglevel=debug`.
*
* By default, if not registering a log appender, the workbench registers a console log appender. The default
* minimal required log level is set to {@link LogLevel#INFO}.
*
* @see {@link LogAppender}
*/
declare abstract class Logger {
/**
* Log level of the workbench logger.
*/
abstract readonly logLevel: LogLevel;
/**
* Logs a message with debug severity.
*
* For messages that are expensive to compose, you can provide a message supplier function.
* The logger will call this function only if at least `DEBUG` log level is enabled.
*
* Optionally, you can pass the logger name as the first argument in the `args` list. The log message
* will then be prefixed with that logger name. Logger names must be instances of {@link LoggerName}.
*/
abstract debug(message: string | (() => string), ...args: any[]): void;
abstract debug(message: string | (() => string), loggerName: LoggerName, ...args: any[]): void;
/**
* Logs a message with info severity.
*
* For messages that are expensive to compose, you can provide a message supplier function.
* The logger will call this function only if at least `INFO` log level is enabled.
*
* Optionally, you can pass the logger name as the first argument in the `args` list. The log message
* will then be prefixed with that logger name. Logger names must be instances of {@link LoggerName}.
*/
abstract info(message: string | (() => string), ...args: any[]): void;
abstract info(message: string | (() => string), loggerName: LoggerName, ...args: any[]): void;
/**
* Logs a message with warn severity.
*
* For messages that are expensive to compose, you can provide a message supplier function.
* The logger will call this function only if at least `WARN` log level is enabled.
*
* Optionally, you can pass the logger name as the first argument in the `args` list. The log message
* will then be prefixed with that logger name. Logger names must be instances of {@link LoggerName}.
*/
abstract warn(message: string | (() => string), ...args: any[]): void;
abstract warn(message: string | (() => string), loggerName: LoggerName, ...args: any[]): void;
/**
* Logs a message with error severity.
*
* For messages that are expensive to compose, you can provide a message supplier function.
* The logger will call this function only if at least `ERROR` log level is enabled.
*
* Optionally, you can pass the logger name as the first argument in the `args` list. The log message
* will then be prefixed with that logger name. Logger names must be instances of {@link LoggerName}.
*/
abstract error(message: string | (() => string), ...args: any[]): void;
abstract error(message: string | (() => string), loggerName: LoggerName, ...args: any[]): void;
static ɵfac: i0.ɵɵFactoryDeclaration<Logger, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<Logger>;
}
/**
* Allows loading the configuration for the SCION Microfrontend Platform asynchronously, e.g., over the network or from a JSON file.
*/
declare abstract class MicrofrontendPlatformConfigLoader {
/**
* Loads the platform configuration asynchronously.
*/
abstract load(): Promise<MicrofrontendPlatformConfig>;
static ɵfac: i0.ɵɵFactoryDeclaration<MicrofrontendPlatformConfigLoader, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<MicrofrontendPlatformConfigLoader>;
}
/**
* Configures a route to only match workbench parts navigated to the specified part capability.
*
* Use this guard to differentiate microfrontend routes, which must all have an empty path.
*
* @example - Route matching a part capability with qualifier {part: 'navigator'}
* ```ts
* import {Routes} from '@angular/router';
* import {canMatchWorkbenchPartCapability} from '@scion/workbench';
*
* const routes: Routes = [
* {path: '', canMatch: [canMatchWorkbenchPartCapability({part: 'navigator'})], component: NavigatorComponent},
* ];
* ```
*
* The above route matches the following part capability:
*
* ```json
* {
* "type": "part",
* "qualifier": {
* "part": "navigator"
* },
* "properties": {
* "path": ""
* }
* }
* ```
*
* @param qualifier - Identifies the part capability.
* @return guard matching the specified part capability.
*/
declare function canMatchWorkbenchPartCapability(qualifier: Qualifier): CanMatchFn;
/**
* Configures a route to only match workbench views navigated to the specified view capability.
*
* Use this guard to differentiate microfrontend routes, which must all have an empty path.
*
* @example - Route matching a view capability with qualifier {view: 'search'}
* ```ts
* import {Routes} from '@angular/router';
* import {canMatchWorkbenchViewCapability} from '@scion/workbench';
*
* const routes: Routes = [
* {path: '', canMatch: [canMatchWorkbenchViewCapability({view: 'search'})], component: SearchComponent},
* ];
* ```
*
* The above route matches the following view capability:
*
* ```json
* {
* "type": "view",
* "qualifier": {
* "view": "search"
* },
* "properties": {
* "path": ""
* }
* }
* ```
*
* @param qualifier - Identifies the view capability.
* @return guard matching the specified view capability.
*/
declare function canMatchWorkbenchViewCapability(qualifier: Qualifier): CanMatchFn;
/**
* Configures a route to only match workbench dialogs navigated to the specified dialog capability.
*
* Use this guard to differentiate microfrontend routes, which must all have an empty path.
*
* @example - Route matching a dialog capability with qualifier {dialog: 'about'}
* ```ts
* import {Routes} from '@angular/router';
* import {canMatchWorkbenchDialogCapability} from '@scion/workbench';
*
* const routes: Routes = [
* {path: '', canMatch: [canMatchWorkbenchDialogCapability({dialog: 'about'})], component: AboutComponent},
* ];
* ```
*
* The above route matches the following dialog capability:
*
* ```json
* {
* "type": "dialog",
* "qualifier": {
* "dialog": "about"
* },
* "properties": {
* "path": ""
* }
* }
* ```
*
* @param qualifier - Identifies the dialog capability.
* @return guard matching the specified dialog capability.
*/
declare function canMatchWorkbenchDialogCapability(qualifier: Qualifier): CanMatchFn;
/**
* Configures a route to only match workbench messageboxes navigated to the specified messagebox capability.
*
* Use this guard to differentiate microfrontend routes, which must all have an empty path.
*
* @example - Route matching a messagebox capability with qualifier {messagebox: 'alert'}
* ```ts
* import {Routes} from '@angular/router';
* import {canMatchWorkbenchMessageBoxCapability} from '@scion/workbench';
*
* const routes: Routes = [
* {path: '', canMatch: [canMatchWorkbenchMessageBoxCapability({messagebox: 'alert'})], component: AlertComponent},
* ];
* ```
*
* The above route matches the following messagebox capability:
*
* ```json
* {
* "type": "messagebox",
* "qualifier": {
* "messagebox": "alert"
* },
* "properties": {
* "path": ""
* }
* }
* ```
*
* @param qualifier - Identifies the messagebox capability.
* @return guard matching the specified messagebox capability.
*/
declare function canMatchWorkbenchMessageBoxCapability(qualifier: Qualifier): CanMatchFn;
/**
* Configures a route to only match workbench popups navigated to the specified popup capability.
*
* Use this guard to differentiate microfrontend routes, which must all have an empty path.
*
* @example - Route matching a popup capability with qualifier {popup: 'info'}
* ```ts
* import {Routes} from '@angular/router';
* import {canMatchWorkbenchPopupCapability} from '@scion/workbench';
*
* const routes: Routes = [
* {path: '', canMatch: [canMatchWorkbenchPopupCapability({popup: 'info'})], component: InfoComponent},
* ];
* ```
*
* The above route matches the following popup capability:
*
* ```json
* {
* "type": "popup",
* "qualifier": {
* "popup": "info"
* },
* "properties": {
* "path": ""
* }
* }
* ```
*
* @param qualifier - Identifies the popup capability.
* @return guard matching the specified popup capability.
*/
declare function canMatchWorkbenchPopupCapability(qualifier: Qualifier): CanMatchFn;
/**
* Configures a route to only match workbench notifications navigated to the specified notification capability.
*
* Use this guard to differentiate microfrontend routes, which must all have an empty path.
*
* @example - Route matching a notification capability with qualifier {notification: 'info'}
* ```ts
* import {Routes} from '@angular/router';
* import {canMatchWorkbenchNotificationCapability} from '@scion/workbench';
*
* const routes: Routes = [
* {path: '', canMatch: [canMatchWorkbenchNotificationCapability({notification: 'info'})], component: InfoComponent},
* ];
* ```
*
* The above route matches the following notification capability:
*
* ```json
* {
* "type": "notification",
* "qualifier": {
* "notification": "info"
* },
* "properties": {
* "path": ""
* }
* }
* ```
*
* @param qualifier - Identifies the notification capability.
* @return guard matching the specified notification capability.
*/
declare function canMatchWorkbenchNotificationCapability(qualifier: Qualifier): CanMatchFn;
/**
* Format of a microfrontend capability id.
*/
type MicrofrontendCapabilityId = string;
/**
* Format of a microfrontend host outlet name.
*
* Microfrontend host outlets are used to render microfrontends provided by the host application.
*
* Format: `workbench.microfrontend.host.<capabilityId>.<workbenchElementType>.<workbenchElementId>`
* Example: `workbench.microfrontend.host.39768ab.part.5485357a`
*/
type MicrofrontendHostOutlet = `workbench.microfrontend.host.${MicrofrontendCapabilityId}.${PartId | ViewId | DialogId | PopupId | NotificationId}`;
/**
* DI token to get a unique id of the workbench.
*
* The id is different each time the app is reloaded. Different workbench windows have different ids.
*/
declare const WORKBENCH_ID: InjectionToken<string>;
/**
* Format of a part identifier.
*
* Each part is assigned a unique identifier (e.g., `part.9fdf7ab4`, `part.c6485225`, etc.).
* A part can also have an alternative id, a meaningful but not necessarily unique name. A part can
* be identified either by its unique or alternative id.
*/
type PartId = `${typeof PART_ID_PREFIX}${string}`;
/**
* Format of a view identifier.
*
* Each view is assigned a unique identifier (e.g., `view.d4de99fb`, `view.cad347dd`, etc.).
* A view can also have an alternative id, a meaningful but not necessarily unique name. A view can
* be identified either by its unique or alternative id.
*/
type ViewId = `${typeof VIEW_ID_PREFIX}${string}`;
/**
* Format of a dialog identifier.
*/
type DialogId = `${typeof DIALOG_ID_PREFIX}${string}`;
/**
* Format of a popup identifier.
*/
type PopupId = `${typeof POPUP_ID_PREFIX}${string}`;
/**
* Format of a notification identifier.
*/
type NotificationId = `${typeof NOTIFICATION_ID_PREFIX}${string}`;
/**
* Format of an activity identifier.
*
* Each activity is assigned a unique identifier (e.g., `activity.9fdf7ab4`, `activity.c6485225`, etc.).
*
* @docs-private Not public API. For internal use only.
*/
type ActivityId = `${typeof ACTIVITY_ID_PREFIX}${string}`;
/**
* Format of a view outlet name.
*/
type ViewOutlet = ViewId;
/**
* Format of a part outlet name.
*/
type PartOutlet = PartId;
/**
* Union of workbench outlets.
*/
type WorkbenchOutlet = PartOutlet | ViewOutlet | MicrofrontendHostOutlet;
/**
* Represents the id prefix of views.
*
* @see ViewId
*/
declare const VIEW_ID_PREFIX = "view.";
/**
* Represents the id prefix of parts.
*
* @see PartId
*/
declare const PART_ID_PREFIX = "part.";
/**
* Represents the id prefix of activities.
*
* @see ActivityId
*/
declare const ACTIVITY_ID_PREFIX = "activity.";
/**
* Represents the id prefix of dialogs.
*
* @see DialogId
*/
declare const DIALOG_ID_PREFIX = "dialog.";
/**
* Represents the id prefix of notifications.
*
* @see NotificationId
*/
declare const NOTIFICATION_ID_PREFIX = "notification.";
/**
* Represents the id prefix of popups.
*
* @see PopupId
*/
declare const POPUP_ID_PREFIX = "popup.";
/**
* Options to control the navigation.
*/
interface WorkbenchNavigationExtras extends NavigationExtras {
/**
* Controls where to open the view. Defaults to `auto`.
*
* One of:
* - `auto`: Navigates existing views that match the path, or opens a new view otherwise. Matrix params do not affect view resolution.
* - `blank`: Opens a new view and navigates it.
* - `<viewId>`: Navigates the specified view. If already opened, replaces it, or opens a new view otherwise.
*/
target?: ViewId | string | 'blank' | 'auto';
/**
* Controls in which part to navigate views.
*
* If {@link target} is `blank`, opens the view in the specified part.
* If {@link target} is `auto`, navigates matching views in the specified part, or opens a new view in that part otherwise.
*
* If the specified part is not in the layout, opens the view in the active part, with the active part of the main area, if any, taking precedence.
*/
partId?: PartId | string;
/**
* Allows differentiation between routes with identical paths.
*
* Multiple views can navigate to the same path but still resolve to different routes, e.g., the empty-path route to maintain a clean URL.
* Like the path, a hint affects view resolution. If set, the router will only navigate views with an equivalent hint, or if not set, views without a hint.
*
* Example route config matching routes based on the hint passed to the navigation:
* ```ts
* import {canMatchWorkbenchView} from '@scion/workbench';
*
* const routes = [
* {
* path: '',
* component: View1Component,
* canMatch: [canMatchWorkbenchView('hint-1')], // matches navigations with hint 'hint-1'
* },
* {
* path: '',
* component: View2Component,
* canMatch: [canMatchWorkbenchView('hint-2')], // matches navigations with hint 'hint-2'
* },
* ];
* ```
* @see canMatchWorkbenchView
*/
hint?: string;
/**
* Instructs the router to activate the view. Defaults to `true`.
*/
activate?: boolean;
/**
* Specifies where to insert the view into the tab bar. Has no effect if navigating an existing view. Defaults to after the active view.
*/
position?: number | 'start' | 'end' | 'before-active-view' | 'after-active-view';
/**
* Associates data with the navigation.
*
* Unlike matrix parameters, navigation data is stored in the layout and not added to the URL, allowing data to be passed to an empty-path navigation.
*
* Data must be JSON serializable. Data can be read from {@link WorkbenchView.navigation.data}.
*/
data?: NavigationData;
/**
* Passes state to the navigation.
*
* State is not persistent, unlike {@link data}; however, state is added to the browser's session history to support back/forward browser navigation.
* State can be read from {@link WorkbenchView.navigation.state} or from the browser's session history via `history.state`.
*/
state?: NavigationState;
/**
* Closes views that match the specified path and navigation hint. Matrix parameters do not affect view resolution.
* The path supports the asterisk wildcard segment (`*`) to match views with any value in a segment.
* To close a specific view, set a view target instead of a path.
*/
close?: boolean;
/**
* Specifies CSS class(es) to add to the view, e.g., to locate the view in tests.
*/
cssClass?: string | string[];
}
/**
* Represents an ordered list of path segments instructing the router which route to navigate to.
*
* A command can be a string or an object literal. A string represents a path segment, an object literal associates matrix parameters with the preceding segment.
* Multiple segments can be combined into a single command, separated by a forward slash.
*
* The first path segment supports the usage of navigational symbols such as `/`, `./`, or `../`.
*
* Examples:
* - Navigates to the path 'path/to/view', passing two parameters:
* ['path', 'to', 'view', {param1: 'value1', param2: 'value2'}]
* - Alternative syntax using a combined segment:
* ['path/to/view', {param1: 'value1', param2: 'value2'}]
*/
type Commands = readonly unknown[];
/**
* URL segments of workbench elements contained in the workbench layout.
*/
interface Outlets {
[outlet: WorkbenchOutlet]: UrlSegment[];
}
/**
* State associated with a navigation.
*/
interface NavigationStates {
[outlet: WorkbenchOutlet]: NavigationState;
}
/**
* State passed to a navigation.
*
* State is not persistent, unlike {@link data}; however, state is added to the browser's session history to support back/forward browser navigation.
*/
interface NavigationState {
[key: string]: unknown;
}
/**
* Data associated with a navigation. Data must be JSON serializable.
*/
interface NavigationData {
[key: string]: unknown;
}
/**
* Signature of a function to modify the workbench layout.
*
* The router will invoke this function with the current workbench layout. The layout has methods for modifying it.
* The layout is immutable; each modification creates a new instance.
*
* The function can call `inject` to get any required dependencies.
*
* ## Workbench Layout
* The workbench layout is an arrangement of parts and views. Parts can be docked to the side or positioned relative to each other.
* Views are stacked in parts and can be dragged to other parts. Content can be displayed in both parts and views.
*
* ## Example
* The following example adds a part to the left of the main area, inserts a view and navigates it.
*
* ```ts
* inject(WorkbenchRouter).navigate(layout => layout
* .addPart('left', {relativeTo: MAIN_AREA, align: 'left'})
* .addView('navigator', {partId: 'left'})
* .navigateView('navigator', ['path/to/view'])
* .activateView('navigator')
* );
* ```
*
* @param layout - Reference to the current workbench layout for modification.
* @return Modified layout, or `null` to cancel the navigation.
*/
type NavigateFn = (layout: WorkbenchLayout) => Promise<WorkbenchLayout | null> | WorkbenchLayout | null;
/**
* Signature of a function to provide texts to the SCION Workbench.
*
* Texts starting with the percent symbol (`%`) are passed to the text provider for translation, with the percent symbol omitted.
*
* A text provider can be registered via configuration passed to the {@link provideWorkbench} function.
*
* The function:
* - Can call `inject` to get any required dependencies.
* - Can call `toSignal` to convert an Observable to a Signal.
*
* @param key - Translation key of the text.
* @param params - Parameters used for text interpolation.
* @return Text associated with the key, or `undefined` if not found.
* Localized applications should return the text in the current language, and update the signal with the translated text each time when the language changes.
*/
type WorkbenchTextProviderFn = (key: string, params: {
[name: string]: string;
}) => Signal<string> | string | undefined;
/**
* Represents either a text or a key for translation.
*
* A translation key starts with the percent symbol (`%`) and may include parameters in matrix notation for text interpolation.
* Key and parameters are passed to {@link WorkbenchConfig.textProvider} for translation.
*
* Semicolons in interpolation parameters must be escaped with two backslashes (`\\;`).
*
* Examples:
* - `%key`: translation key
* - `%key;param=value`: translation key with a single interpolation parameter
* - `%key;param1=value1;param2=value2`: translation key with multiple interpolation parameters
* - `text`: no translation key, text is returned as is
*/
type Translatable = string | `%${string}`;
/**
* Factory for creating a {@link WorkbenchLayout}.
*/
declare abstract class WorkbenchLayoutFactory {
/**
* Creates a layout with given part.
*
* @param id - The id of the part. Use {@link MAIN_AREA} to add the main area.
* @param extras - Controls how to add the part to the layout.
* @param extras.activate - Controls whether to activate the part. If not set, defaults to `false`.
* @return layout with the part added.
*/
abstract addPart(id: string | MAIN_AREA, extras?: PartExtras): WorkbenchLayout;
static ɵfac: i0.ɵɵFactoryDeclaration<WorkbenchLayoutFactory, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<WorkbenchLayoutFactory>;
}
/**
* The workbench layout is an arrangement of parts and views. Parts can be docked to the side or positioned relative to each other.
* Views are stacked in parts and can be dragged to other parts. Content can be displayed in both parts and views.
*
* A typical workbench application has a main area part and parts docked to the side, providing navigation and context-sensitive assistance to
* support the user's workflow.
*
* Multiple layouts, called perspectives, can be created. Users can switch between perspectives. Perspectives share the same main area, if any.
*
* The layout has methods for modifying it. The layout is immutable; each modification creates a new instance.
*/
interface WorkbenchLayout {
/**
* Adds a part with the given id to this layout. Position and size are expressed relative to a reference part.
*
* A part can also be aligned relative to a docked part, enabling inline layouts within docked parts, such as splitting the docked parts into multiple sections.
*
* @param id - The id of the part to add. Use {@link MAIN_AREA} to add the main area.
* @param relativeTo - Specifies the reference part to lay out the part.
* @param extras - Controls how to add the part to the layout.
* @return a copy of this layout with the part added.
*/
addPart(id: string | MAIN_AREA, relativeTo: ReferencePart, extras?: PartExtras): WorkbenchLayout;
/**
* Adds a part with the given id to this layout, docking it to the specified docking area.
*
* Docked parts can be minimized to create more space for the main content. Users cannot drag views into or out of docked parts.
*
* A part can be docked to the left, right, or bottom side of the workbench.
* Each side has two docking areas: `left-top` and `left-bottom`, `right-top` and `right-bottom`, and `bottom-left` and `bottom-right`.
* Parts added to the same area are stacked, with only one part active per stack. If there is an active part in both stacks of a side,
* the two parts are split vertically or horizontally, depending on the side.
*
* A docked part may be navigated to display content, have views, or define a layout with multiple parts aligned relative to each other.
*
* @param id - The id of the part to add.
* @param dockTo - Controls to which area to dock the part.
* @param extras - Controls how to add the part to the layout.
* @return a copy of this layout with the part added.
*/
addPart(id: string, dockTo: DockingArea, extras: DockedPartExtras): WorkbenchLayout;
/**
* Navigates the specified part based on the provided array of commands and extras.
*
* Navigating a part displays content when its view stack is empty. A navigated part can still have views but won't display
* navigated content unless its view stack is empty. Views cannot be dragged into parts displaying navigated content, except
* for the main area part.
*
* A command can be a string or an object literal. A string represents a path segment, an object literal associates matrix parameters with the preceding segment.
* Multiple segments can be combined into a single command, separated by a forward slash.
*
* By default, navigation is absolute. Set `relativeTo` in extras for relative navigation.
*
* Usage:
* ```
* layout.navigatePart(partId, ['path', 'to', 'part', {param 'value'}]);
* layout.navigatePart(partId, ['path/to/part', {param: 'value'}]);
* ```
*
* @param id - Identifies the part for navigation.
* @param commands - Instructs the router which route to navigate to.
* @param extras - Controls navigation:
* @param extras.hint - Allows differentiation between routes with identical paths.
* Multiple parts can navigate to the same path but still resolve to different routes, e.g., the empty-path route to maintain a clean URL.
*
* Example:
* ```ts
* import {canMatchWorkbenchPart} from '@scion/workbench';
*
* const routes = [
* {
* path: '',
* component: Part1Component,
* canMatch: [canMatchWorkbenchPart('hint-1')], // matches navigations with hint 'hint-1'
* },
* {
* path: '',
* component: Part2Component,
* canMatch: [canMatchWorkbenchPart('hint-2')], // matches navigations with hint 'hint-1'
* },
* ];
* ```
* @param extras.relativeTo - Specifies the route for relative navigation, supporting navigational symbols such as '/', './', or '../' in the commands.
* @param extras.data - Associates data with the navigation.
* Unlike matrix parameters, navigation data is stored in the layout and not added to the URL, allowing data to be passed to an empty-path navigation.
* Data must be JSON serializable. Data can be read from {@link WorkbenchPart.navigation.data}.
* @param extras.state - Passes state to the navigation.
* State is not persistent, unlike {@link data}; however, state is added to the browser's session history to support back/forward browser navigation.
* State can be read from {@link WorkbenchPart.navigation.state} or from the browser's session history via `history.state`.
* @param extras.cssClass - Specifies CSS class(es) to add to the part, e.g., to locate the part in tests.
* @return a copy of this layout with the part navigated.
*/
navigatePart(id: string, commands: Commands, extras?: {
hint?: string;
relativeTo?: ActivatedRoute;
data?: NavigationData;
state?: NavigationState;
cssClass?: string | string[];
}): WorkbenchLayout;
/**
* Adds a view to the specified part.
*
* @param id - The id of the view to add.
* @param extras - Controls how to add the view to the layout.
* @param extras.partId - References the part to which to add the view.
* @param extras.position - Specifies the position where to insert the view. The position is zero-based. Defaults to `end`.
* @param extras.activateView - Controls whether to activate the view. Defaults to `false`.
* @param extras.activatePart - Controls whether to activate the part that contains the view. Defaults to `false`.
* @param extras.cssClass - Specifies CSS class(es) to add to the view, e.g., to locate the view in tests.
* @return a copy of this layout with the view added.
*/
addView(id: string, extras: {
partId: string;
position?: number | 'start' | 'end' | 'before-active-view' | 'after-active-view';
activateView?: boolean;
activatePart?: boolean;
cssClass?: string | string[];
}): WorkbenchLayout;
/**
* Navigates the specified view based on the provided array of commands and extras.
*
* A command can be a string or an object literal. A string represents a path segment, an object literal associates matrix parameters with the preceding segment.
* Multiple segments can be combined into a single command, separated by a forward slash.
*
* By default, navigation is absolute. Set `relativeTo` in extras for relative navigation.
*
* Usage:
* ```
* layout.navigateView(viewId, ['path', 'to', 'view', {param 'value'}]);
* layout.navigateView(viewId, ['path/to/view', {param: 'value'}]);
* ```
*
* @param id - Identifies the view for navigation.
* @param commands - Instructs the router which route to navigate to.
* @param extras - Controls navigation:
* @param extras.hint - Allows differentiation between routes with identical paths.
* Multiple views can navigate to the same path but still resolve to different routes, e.g., the empty-path route to maintain a clean URL.
* Like the path, a hint affects view resolution. If set, the router will only navigate views with an equivalent hint, or if not set, views without a hint.
*
* Example route config matching routes based on the hint passed to the navigation:
* ```ts
* import {canMatchWorkbenchView} from '@scion/workbench';
*
* const routes = [
* {
* path: '',
* component: View1Component,
* canMatch: [canMatchWorkbenchView('hint-1')], // matches navigations with hint 'hint-1'
* },
* {
* path: '',
* component: View2Component,
* canMatch: [canMatchWorkbenchView('hint-2')], // matches navigations with hint 'hint-2'
* },
* ];
* ```
* @param extras.relativeTo - Specifies the route for relative navigation, supporting navigational symbols such as '/', './', or '../' in the commands.
* @param extras.data - Associates data with the navigation.
* Unlike matrix parameters, navigation data is stored in the layout and not added to the URL, allowing data to be passed to an empty-path navigation.
* Data must be JSON serializable. Data can be read from {@link WorkbenchView.navigation.data}.
* @param extras.state - Passes state to the navigation.
* State is not persistent, unlike {@link data}; however, state is added to the browser's session history to support back/forward browser navigation.
* State can be read from {@link WorkbenchView.navigation.state} or from the browser's session history via `history.state`.
* @param extras.cssClass - Specifies CSS class(es) to add to the view, e.g., to locate the view in tests.
* @return a copy of this layout with the view navigated.
*/
navigateView(id: string, commands: Commands, extras?: {
hint?: string;
relativeTo?: ActivatedRoute;
data?: NavigationData;
state?: NavigationState;
cssClass?: string | string[];
}): WorkbenchLayout;
/**
* Removes given view from the layout.
*
* - If the view is active, the last used view is activated.
* - If the view is the last view in the part, the part is removed unless it is the last part in its grid or a structural part.
*
* @param id - Specifies the id of the view to remove.
* @return a copy of this layout with the view removed.
*/
removeView(id: string): WorkbenchLayout;
/**
* Removes given part from the layout.
*
* If the part is active, the last used part is activated.
*
* @param id - The id of the part to remove.
* @return a copy of this layout with the part removed.
*/
removePart(id: string): WorkbenchLayout;
/**
* Moves a view to a different part or moves it within a part.
*
* @param id - The id of the view to be moved.
* @param targetPartId - The id of the part to which to move the view.
* @param options - Controls moving of the view.
* @param options.position - Specifies the position where to move the view in the target part. The position is zero-based. Defaults to `end` when moving the view to a different part.
* @param options.activateView - Controls if to activate the view. Defaults to `false`.
* @param options.activatePart - Controls if to activate the target part. Defaults to `false`.
* @return a copy of this layout with the view moved.
*/
moveView(id: string, targetPartId: string, options?: {
position?: number | 'start' | 'end' | 'before-active-view' | 'after-active-view';
activateView?: boolean;
activatePart?: boolean;
}): WorkbenchLayout;
/**
* Activates the given view.
*
* @param id - The id of the view which to activate.
* @param options - Controls view activation.
* @param options.activatePart - Controls whether to activate the part that contains the view. Defaults to `false`.
* @return a copy of this layout with the view activated.
*/
activateView(id: string, options?: {
activatePart?: boolean;
}): WorkbenchLayout;
/**
* Activates the given part.
*
* @param id - The id of the part which to activate.
* @return a copy of this layout with the part activated.
*/
activatePart(id: string): WorkbenchLayout;
/**
* Tests if given part is contained in the layout.
*/
hasPart(id: string): boolean;
/**
* Tests if given view is contained in the layout.
*/
hasView(id: string): boolean;
/**
* Applies a modification function to this layout, enabling conditional changes while maintaining method chaining.
*
* @param modifyFn - A function that takes the current layout and returns a modified layout.
* @return The modified layout returned by the modify function.
*/
modify(modifyFn: (layout: WorkbenchLayout) => WorkbenchLayout): WorkbenchLayout;
}
/**
* Describes how to lay out a part relative to another part.
*/
interface ReferencePart {
/**
* Specifies the part which to use as the reference part to lay out the part.
* If not set, the part will be aligned relative to the root of the workbench layout.
*/
relativeTo?: string;
/**
* Specifies the side of the reference part where to add the part.
*/
align: 'left' | 'right' | 'top' | 'bottom';
/**
* Specifies the proportional size of the part relative to the reference part.
* The ratio is the closed interval [0,1]. If not set, defaults to `0.5`.
*/
ratio?: number;
}
/**
* Controls the appearance and behavior of a part.
*/
interface PartExtras {
/**
* Title displayed in the part bar.
*
* Can be text or a translation key. A translation key starts with the percent symbol (`%`) and may include parameters in matrix notation for text interpolation.
*/
title?: Translatable;
/**
* Specifies CSS class(es) to add to the part, e.g., to locate the part in tests.
*/
cssClass?: string | string[];
/**
* Controls whether to activate the part. Defaults to `false`.
*/
activate?: boolean;
}
/**
* Controls the appearance of a docked part and its toggle button.
*
* A docked part is a part that is docked to the left, right, or bottom side of the workbench.
*
* Docked parts can be minimized to create more space for the main content. Users cannot drag
* views into or out of docked parts.
*/
interface DockedPartExtras {
/**
* Icon (key) displayed in the toggle button.
*
* The actual icon is resolved through an {@link WorkbenchIconProviderFn} registered in {@link WorkbenchConfig.iconProvider}.
*
* If no icon provider is configured, the icon defaults to a Material Icon font ligature. The default icon provider requires
* the application to include the Material icon font, for example in `styles.scss`, as follows:
*
* ```scss
* @import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded');
* ```
*
* The application can then reference icons from the Material Icons Font: https://fonts.google.com/icons
*/
icon: string;
/**
* Label displayed in the toggle button.
*
* Can be text or a translation key. A translation key starts with the percent symbol (`%`) and may include parameters in matrix notation for text interpolation.
*/
label: Translatable;
/**
* Tooltip displayed when hovering over the toggle button.
*
* Can be text or a translation key. A translation key starts with the percent symbol (`%`) and may include parameters in matrix notation for text interpolation.
*/
tooltip?: Translatable;
/**
* Title displayed in the part bar.
*
* If not provided, defaults to {@link DockedPartExtras.label}. Set to `false` to not display a title.
*
* Can be text or a translation key. A translation key starts with the percent symbol (`%`) and may include parameters in matrix notation for text interpolation.
*/
title?: Translatable | false;
/**
* CSS class(es) to add to the docked part and its toggle button, e.g., to locate the part in tests.
*/
cssClass?: string | string[];
/**
* Controls whether to activate the docked part. Defaults to `false`.
*/
activate?: boolean;
/**
* Internal identifier for the docked part.
*
* @docs-private Not public API. For internal use only.
*/
ɵactivityId?: ActivityId;
}
/**
* A part can be docked to the left, right, or bottom side of the workbench.
*
* Each side has two docking areas: `left-top` and `left-bottom`, `right-top` and `right-bottom`, and `bottom-left` and `bottom-right`.
* Parts added to the same area are stacked, with only one part active per stack. If there is an active part in both stacks of a side,
* the two parts are split vertically or horizontally, depending on the side.
*/
interface DockingArea {
/**
* Controls where to dock a part.
* - `left-top`: Dock to the top on the left side.
* - `left-bottom`: Dock to the bottom on the left side.
* - `right-top`: Dock to the top on the right side.
* - `right-bottom`: Dock to the bottom on the right side.
* - `bottom-left`: Dock to the left on the bottom side.
* - `bottom-right`: Dock to the right on the bottom side.
*/
dockTo: 'left-top' | 'left-bottom' | 'right-top' | 'right-bottom' | 'bottom-left' | 'bottom-right';
}
/**
* Identifies the main area part in the workbench layout.
*
* Refer to this part to align parts relative to the main area.
*
* The main area is a special part that can be added to the layout. The main area is where the workbench opens views by default.
* It is shared between perspectives and its layout is not reset when resetting perspectives.
*/
declare const MAIN_AREA: MAIN_AREA;
/**
* Represents the type of the {@link MAIN_AREA} constant.
*/
type MAIN_AREA = 'part.main-area';
/**
* Signature of a function to provide a workbench layout.
*
* The workbench will invoke this function with a factory object to create the layout. The layout is immutable; each modification creates a new instance.
*
* The function can call `inject` to get any required dependencies.
*
* ## Workbench Layout
* The workbench layout is an arrangement of parts and views. Parts can be docked to the side or positioned relative to each other.
* Views are stacked in parts and can be dragged to other parts. Content can be displayed in both parts and views.
*
* The layout typically has a main area part and parts docked to the side, providing navigation and context-sensitive assistance to support
* the user's workflow. Initially empty or displaying a welcome page, the main area is where the workbench opens views by default.
* Unlike any other part, the main area is shared between perspectives, and its layout is not reset when resetting perspectives.
* ## Steps to create the layout
* Start by adding parts to the layout. Parts can be docked to a specific area (`left-top`, `left-bottom`, `right-top`, `right-bottom`, `bottom-left`, `bottom-right`)
* or aligned relative to each other. Views can be added to any part except the main area part. To display content, navigate parts and views to registered routes.
*
* To maintain a clean URL, navigate parts and views in the layout to empty-path routes and use a navigation hint for differentiation.
* - Use the `canMatchWorkbenchPart` guard to match a route only for parts navigated with a particular hint.
* - Use the `canMatchWorkbenchView` guard to match a route only for views navigated with a particular hint.
*
* ## Example
* The following example defines a layout with a main area and four parts docked to the side:
*
* ```plain
* +------+-----------+------+
* | | | |
* | | | |
* | left | main area | right|
* | | | |
* | | | |
* +------+-----------+------+
* | bottom |
* +-------------------------+
* ```
*
* ```ts
* import {bootstrapApplication} from '@angular/platform-browser';
* import {MAIN_AREA, provideWorkbench, WorkbenchLayoutFactory} from '@scion/workbench';
*
* bootstrapApplication(AppComponent, {
* providers: [
* provideWorkbench({
* layout: (factory: WorkbenchLayoutFactory) => factory
* // Add parts to dock areas on the side.
* .addPart(MAIN_AREA)
* .addPart('navigator', {dockTo: 'left-top'}, {label: 'Navigator', icon: 'folder'})
* .addPart('find', {dockTo: 'bottom-left'}, {label: 'Find', icon: 'search'})
* .addPart('inventory', {dockTo: 'right-top'}, {label: 'Inventory', icon: 'inventory'})
* .addPart('notifications', {dockTo: 'right-bottom'}, {label: 'Notifications', icon: 'notifications'})
*
* // Add views to parts.
* .addView('find1', {partId: 'find'})
* .addView('find2', {partId: 'find'})
* .addView('find3', {partId: 'find'})
*
* // Navigate the main area to display a welcome page.
* .navigatePart(MAIN_AREA, [], {hint: 'welcome'})
*
* // Navigate parts to display content.
* .navigatePart('navigator', [], {hint: 'navigator'})
* .navigatePart('inventory', ['path/to/inventory'])
* .navigatePart('notifications', [], {hint: 'notifications'})
*
* // Navigate views to display content.
* .navigateView('find1', ['path/to/find'])
* .navigateView('find2', ['path/to/find'])
* .navigateView('find3', ['path/to/find'])
*
* // Activate parts to open docked parts.
* .activatePart('navigator')
* .activatePart('find')
* .activatePart('inventory'),
* }),
* ],
* });
* ```
*
* The layout requires the following routes.
*
* ```ts
* import {bootstrapApplication} from '@angular/platform-browser';
* import {provideRouter} from '@angular/router';
* import {canMatchWorkbenchView, canMatchWorkbenchPart} from '@scion/workbench';
*
* bootstrapApplication(AppComponent, {
* providers: [
* provideRouter([
* // Route for the "MAIN_AREA Part"
* {path: '', canMatch: [canMatchWorkbenchPart('welcome')], loadComponent: () => import('./welcome/welcome.component')},
* // Route for the "Navigator Part"
* {path: '', canMatch: [canMatchWorkbenchPart('navigator')], loadComponent: () => import('./navigator/navigator.component')},
* // Route for the "Find View"
* {path: 'path/to/find', loadComponent: () => import('./find/find.component')},
* // Route for the "Inventory Part"
* {path: 'path/to/inventory', loadComponent: () => import('./inventory/inventory.component')},
* // Route for the "Notifications Part"
* {path: '', canMatch: [canMatchWorkbenchPart('notifications')], loadComponent: () => import('./notifications/notifications.component')},
* ]),
* ],
* });
* ```
*/
type WorkbenchLayoutFn = (factory: WorkbenchLayoutFactory) => Promise<WorkbenchLayout> | WorkbenchLayout;
/**
* Represents a workbench perspective.
*
* A perspective is a named layout. Multiple perspectives can be created. Users can switch between perspectives.
* Perspectives share the same main area, if any.
*/
interface WorkbenchPerspective {
/**
* Unique identifier of this perspective.
*/
readonly id: string;
/**
* Arbitrary data associated with this perspective.
*/
readonly data: {
[key: string]: unknown;
};
/**
* Indicates whether this perspective is active.
*/
readonly active: Signal<boolean>;
/**
* Indicates whether this perspective is transient, with its layout only memoized, not persisted.
*/
readonly transient: boolean;
}
/**
* Defines the workbench layouts of the application
*
* A perspective is a named layout. Multiple perspectives can be created. Users can switch between perspectives.
* Perspectives share the same main area, if any.
*/
interface WorkbenchPerspectives {
/**
* Specifies perspectives to register in the workbench.
*/
perspectives?: WorkbenchPerspectiveDefinition[];
/**
* Specifies which perspective to activate. Defaults to the first registered perspective.
*
* The workbench remembers the active perspective and only evaluates this property when starting the workbench
* for the first time, after clearing storage, or if the active perspective no longer exists.
*
* Can be either the identity of a perspective or a function to resolve the perspective.
*/
initialPerspective?: string | WorkbenchPerspectiveSelectionFn;
}
/**
* Describes a perspective to register in the workbench.
*/
interface WorkbenchPerspectiveDefinition {
/**
* Specifies the unique identity of the perspective.
*/
id: string;
/**
* Function to create the layout of the perspective. The function can call `inject` to get any required dependencies.
*
* A perspective defines an arrangement of parts and views. Parts can be docked to the side or positioned relative to each other.
* Views are stacked in parts and can be dragged to other parts. Content can be displayed in both parts and views.
*
* A perspective typically has a main area part and parts docked to the side, providing navigation and context-sensitive assistance to support
* the user's workflow. Initially empty or displaying a welcome page, the main area is where the workbench opens views by default.
* Unlike any other part, the main area is shared between perspectives, and its layo