golden-layout
Version:
A multi-screen javascript Layout manager
622 lines (561 loc) • 24.2 kB
text/typescript
import { AssertError, UnreachableCaseError } from '../errors/internal-error';
import { ConfigMinifier } from '../utils/config-minifier';
import { ItemType, JsonValue, ResponsiveMode, Side, SizeUnitEnum } from '../utils/types';
import { deepExtendValue } from '../utils/utils';
/** @public */
export interface ResolvedItemConfig {
// see ItemConfig for comments
readonly type: ItemType;
readonly content: readonly ResolvedItemConfig[];
readonly size: number;
readonly sizeUnit: SizeUnitEnum;
readonly minSize: number | undefined;
readonly minSizeUnit: SizeUnitEnum;
// id no longer specifies whether an Item is maximised. This is now done by HeaderItemConfig.maximised
readonly id: string;
readonly isClosable: boolean;
}
/** @public */
export namespace ResolvedItemConfig {
export const defaults: ResolvedItemConfig = {
type: ItemType.ground, // not really default but need something
content: [],
size: 1,
sizeUnit: SizeUnitEnum.Fractional,
minSize: undefined,
minSizeUnit: SizeUnitEnum.Pixel,
id: '',
isClosable: true,
} as const;
/** Creates a copy of the original ResolvedItemConfig using an alternative content if specified */
export function createCopy(original: ResolvedItemConfig, content?: ResolvedItemConfig[]): ResolvedItemConfig {
switch (original.type) {
case ItemType.ground:
case ItemType.row:
case ItemType.column:
return ResolvedRowOrColumnItemConfig.createCopy(original as ResolvedRowOrColumnItemConfig,
content as ResolvedRowOrColumnItemConfig.ChildItemConfig[]);
case ItemType.stack:
return ResolvedStackItemConfig.createCopy(original as ResolvedStackItemConfig, content as ResolvedComponentItemConfig[]);
case ItemType.component:
return ResolvedComponentItemConfig.createCopy(original as ResolvedComponentItemConfig);
default:
throw new UnreachableCaseError('CICC91354', original.type, 'Invalid Config Item type specified');
}
}
export function createDefault(type: ItemType): ResolvedItemConfig {
switch (type) {
case ItemType.ground:
throw new AssertError('CICCDR91562'); // Get default root from LayoutConfig
case ItemType.row:
case ItemType.column:
return ResolvedRowOrColumnItemConfig.createDefault(type);
case ItemType.stack:
return ResolvedStackItemConfig.createDefault();
case ItemType.component:
return ResolvedComponentItemConfig.createDefault();
default:
throw new UnreachableCaseError('CICCDD91563', type, 'Invalid Config Item type specified');
}
}
export function isComponentItem(itemConfig: ResolvedItemConfig): itemConfig is ResolvedComponentItemConfig {
return itemConfig.type === ItemType.component;
}
export function isStackItem(itemConfig: ResolvedItemConfig): itemConfig is ResolvedStackItemConfig {
return itemConfig.type === ItemType.stack;
}
/** @internal */
export function isGroundItem(itemConfig: ResolvedItemConfig): itemConfig is ResolvedGroundItemConfig {
return itemConfig.type === ItemType.ground;
}
}
// Stack or Component
/** @public */
export interface ResolvedHeaderedItemConfig extends ResolvedItemConfig {
header: ResolvedHeaderedItemConfig.Header | undefined; // undefined means get header settings from LayoutConfig
readonly maximised: boolean;
}
/** @public */
export namespace ResolvedHeaderedItemConfig {
export const defaultMaximised = false;
export interface Header {
// undefined means get property value from LayoutConfig
readonly show: false | Side | undefined;
readonly popout: false | string | undefined;
readonly maximise: false | string | undefined;
readonly close: string | undefined;
readonly minimise: string | undefined;
readonly tabDropdown: false | string | undefined;
}
export namespace Header {
export function createCopy(original: Header | undefined, show?: false | Side): Header | undefined {
if (original === undefined) {
return undefined;
} else {
return {
show: show ?? original.show,
popout: original.popout,
close: original.close,
maximise: original.maximise,
minimise: original.minimise,
tabDropdown: original.tabDropdown,
}
}
}
}
}
/** @public */
export interface ResolvedStackItemConfig extends ResolvedHeaderedItemConfig {
readonly type: 'stack';
readonly content: ResolvedComponentItemConfig[];
/** The index of the active item in the Stack. Only undefined if the Stack is empty. */
readonly activeItemIndex: number | undefined;
}
/** @public */
export namespace ResolvedStackItemConfig {
export const defaultActiveItemIndex = 0;
export function createCopy(original: ResolvedStackItemConfig, content?: ResolvedComponentItemConfig[]): ResolvedStackItemConfig {
const result: ResolvedStackItemConfig = {
type: original.type,
content: content !== undefined ? copyContent(content) : copyContent(original.content),
size: original.size,
sizeUnit: original.sizeUnit,
minSize: original.minSize,
minSizeUnit: original.minSizeUnit,
id: original.id,
maximised: original.maximised,
isClosable: original.isClosable,
activeItemIndex: original.activeItemIndex,
header: ResolvedHeaderedItemConfig.Header.createCopy(original.header),
}
return result;
}
export function copyContent(original: ResolvedComponentItemConfig[]): ResolvedComponentItemConfig[] {
const count = original.length;
const result = new Array<ResolvedComponentItemConfig>(count);
for (let i = 0; i < count; i++) {
result[i] = ResolvedItemConfig.createCopy(original[i]) as ResolvedComponentItemConfig;
}
return result;
}
export function createDefault(): ResolvedStackItemConfig {
const result: ResolvedStackItemConfig = {
type: ItemType.stack,
content: [],
size: ResolvedItemConfig.defaults.size,
sizeUnit: ResolvedItemConfig.defaults.sizeUnit,
minSize: ResolvedItemConfig.defaults.minSize,
minSizeUnit: ResolvedItemConfig.defaults.minSizeUnit,
id: ResolvedItemConfig.defaults.id,
maximised: ResolvedHeaderedItemConfig.defaultMaximised,
isClosable: ResolvedItemConfig.defaults.isClosable,
activeItemIndex: defaultActiveItemIndex,
header: undefined,
}
return result;
}
}
/** @public */
export interface ResolvedComponentItemConfig extends ResolvedHeaderedItemConfig {
// see ComponentItemConfig for comments
readonly type: 'component';
readonly content: [];
readonly title: string;
readonly reorderEnabled: boolean; // Takes precedence over LayoutConfig.reorderEnabled.
/**
* The name of the component as specified in layout.registerComponent. Mandatory if type is 'component'.
*/
readonly componentType: JsonValue;
readonly componentState?: JsonValue;
}
/** @public */
export namespace ResolvedComponentItemConfig {
export const defaultReorderEnabled = true;
export function resolveComponentTypeName(itemConfig: ResolvedComponentItemConfig): string | undefined {
const componentType = itemConfig.componentType;
if (typeof componentType === 'string') {
return componentType;
} else {
return undefined;
}
}
export function createCopy(original: ResolvedComponentItemConfig): ResolvedComponentItemConfig {
const result: ResolvedComponentItemConfig = {
type: original.type,
content: [],
size: original.size,
sizeUnit: original.sizeUnit,
minSize: original.minSize,
minSizeUnit: original.minSizeUnit,
id: original.id,
maximised: original.maximised,
isClosable: original.isClosable,
reorderEnabled: original.reorderEnabled,
title: original.title,
header: ResolvedHeaderedItemConfig.Header.createCopy(original.header),
componentType: original.componentType,
componentState: deepExtendValue(undefined, original.componentState) as JsonValue,
}
return result;
}
export function createDefault(componentType: JsonValue = '', componentState?: JsonValue, title = ''): ResolvedComponentItemConfig {
const result: ResolvedComponentItemConfig = {
type: ItemType.component,
content: [],
size: ResolvedItemConfig.defaults.size,
sizeUnit: ResolvedItemConfig.defaults.sizeUnit,
minSize: ResolvedItemConfig.defaults.minSize,
minSizeUnit: ResolvedItemConfig.defaults.minSizeUnit,
id: ResolvedItemConfig.defaults.id,
maximised: ResolvedHeaderedItemConfig.defaultMaximised,
isClosable: ResolvedItemConfig.defaults.isClosable,
reorderEnabled: ResolvedComponentItemConfig.defaultReorderEnabled,
title,
header: undefined,
componentType,
componentState,
}
return result;
}
export function copyComponentType(componentType: JsonValue): JsonValue {
return deepExtendValue({}, componentType) as JsonValue
}
}
/** Base for Root or RowOrColumn ItemConfigs
* @public
*/
export interface ResolvedRowOrColumnItemConfig extends ResolvedItemConfig {
readonly type: 'row' | 'column';
/** Note that RowOrColumn ResolvedItemConfig contents, can contain ComponentItem itemConfigs. However
* when ContentItems are created, these ComponentItem itemConfigs will create a Stack with a child ComponentItem.
*/
readonly content: readonly (ResolvedRowOrColumnItemConfig | ResolvedStackItemConfig | ResolvedComponentItemConfig)[];
}
/** @public */
export namespace ResolvedRowOrColumnItemConfig {
export type ChildItemConfig = ResolvedRowOrColumnItemConfig | ResolvedStackItemConfig | ResolvedComponentItemConfig;
export function isChildItemConfig(itemConfig: ResolvedItemConfig): itemConfig is ChildItemConfig {
switch (itemConfig.type) {
case ItemType.row:
case ItemType.column:
case ItemType.stack:
case ItemType.component:
return true;
case ItemType.ground:
return false;
default:
throw new UnreachableCaseError('CROCOSPCICIC13687', itemConfig.type);
}
}
export function createCopy(original: ResolvedRowOrColumnItemConfig, content?: ChildItemConfig[]): ResolvedRowOrColumnItemConfig {
const result: ResolvedRowOrColumnItemConfig = {
type: original.type,
content: content !== undefined ? copyContent(content) : copyContent(original.content),
size: original.size,
sizeUnit: original.sizeUnit,
minSize: original.minSize,
minSizeUnit: original.minSizeUnit,
id: original.id,
isClosable: original.isClosable,
}
return result;
}
export function copyContent(original: readonly ChildItemConfig[]): ChildItemConfig[] {
const count = original.length;
const result = new Array<ChildItemConfig>(count);
for (let i = 0; i < count; i++) {
result[i] = ResolvedItemConfig.createCopy(original[i]) as ChildItemConfig;
}
return result;
}
export function createDefault(type: 'row' | 'column'): ResolvedRowOrColumnItemConfig {
const result: ResolvedRowOrColumnItemConfig = {
type,
content: [],
size: ResolvedItemConfig.defaults.size,
sizeUnit: ResolvedItemConfig.defaults.sizeUnit,
minSize: ResolvedItemConfig.defaults.minSize,
minSizeUnit: ResolvedItemConfig.defaults.minSizeUnit,
id: ResolvedItemConfig.defaults.id,
isClosable: ResolvedItemConfig.defaults.isClosable,
}
return result;
}
}
/**
* RootItemConfig is the topmost ResolvedItemConfig specified by the user.
* Note that it does not have a corresponding contentItem. It specifies the one and only child of the Ground ContentItem
* Note that RootItemConfig can be an ComponentItem itemConfig. However when the Ground ContentItem's child is created
* a ComponentItem itemConfig will create a Stack with a child ComponentItem.
* @public
*/
export type ResolvedRootItemConfig = ResolvedRowOrColumnItemConfig | ResolvedStackItemConfig | ResolvedComponentItemConfig;
/** @public */
export namespace ResolvedRootItemConfig {
export function createCopy(config: ResolvedRootItemConfig): ResolvedRootItemConfig {
return ResolvedItemConfig.createCopy(config) as ResolvedRootItemConfig;
}
export function isRootItemConfig(itemConfig: ResolvedItemConfig): itemConfig is ResolvedRootItemConfig {
switch (itemConfig.type) {
case ItemType.row:
case ItemType.column:
case ItemType.stack:
case ItemType.component:
return true;
case ItemType.ground:
return false;
default:
throw new UnreachableCaseError('CROCOSPCICIC13687', itemConfig.type);
}
}
}
/** @internal */
export interface ResolvedGroundItemConfig extends ResolvedItemConfig {
readonly type: 'ground';
readonly size: 100,
readonly sizeUnit: SizeUnitEnum.Percent,
readonly minSize: 0,
readonly minSizeUnit: SizeUnitEnum.Pixel,
readonly id: '',
readonly isClosable: false,
readonly title: '',
readonly reorderEnabled: false,
}
/** @internal */
export namespace ResolvedGroundItemConfig {
export function create(rootItemConfig: ResolvedRootItemConfig | undefined):ResolvedGroundItemConfig {
const content = rootItemConfig === undefined ? [] : [rootItemConfig];
return {
type: ItemType.ground,
content,
size: 100,
sizeUnit: SizeUnitEnum.Percent,
minSize: 0,
minSizeUnit: SizeUnitEnum.Pixel,
id: '',
isClosable: false,
title: '',
reorderEnabled: false,
}
}
}
/** @public */
export interface ResolvedLayoutConfig {
readonly root: ResolvedRootItemConfig | undefined;
readonly openPopouts: ResolvedPopoutLayoutConfig[];
readonly dimensions: ResolvedLayoutConfig.Dimensions;
readonly settings: ResolvedLayoutConfig.Settings;
readonly header: ResolvedLayoutConfig.Header;
readonly resolved: true,
}
/** @public */
export namespace ResolvedLayoutConfig {
export interface Settings {
// see Config.Settings for comments
readonly constrainDragToContainer: boolean;
readonly reorderEnabled: boolean; // also in ResolvedItemConfig which takes precedence
readonly popoutWholeStack: boolean;
readonly blockedPopoutsThrowError: boolean;
/** @deprecated Will be removed in version 3. */
readonly closePopoutsOnUnload: boolean;
readonly responsiveMode: ResponsiveMode;
readonly tabOverlapAllowance: number;
readonly reorderOnTabMenuClick: boolean;
readonly tabControlOffset: number;
readonly popInOnClose: boolean;
}
export namespace Settings {
export const defaults: ResolvedLayoutConfig.Settings = {
constrainDragToContainer: true,
reorderEnabled: true,
popoutWholeStack: false,
blockedPopoutsThrowError: true,
closePopoutsOnUnload: true,
responsiveMode: ResponsiveMode.none, // was onload
tabOverlapAllowance: 0,
reorderOnTabMenuClick: true,
tabControlOffset: 10,
popInOnClose: false,
} as const;
export function createCopy(original: Settings): Settings {
return {
constrainDragToContainer: original.constrainDragToContainer,
reorderEnabled: original.reorderEnabled,
popoutWholeStack: original.popoutWholeStack,
blockedPopoutsThrowError: original.blockedPopoutsThrowError,
closePopoutsOnUnload: original.closePopoutsOnUnload,
responsiveMode: original.responsiveMode,
tabOverlapAllowance: original.tabOverlapAllowance,
reorderOnTabMenuClick: original.reorderOnTabMenuClick,
tabControlOffset: original.tabControlOffset,
popInOnClose: original.popInOnClose,
}
}
}
export interface Dimensions {
// see LayoutConfig.Dimensions for comments
readonly borderWidth: number;
readonly borderGrabWidth: number,
readonly defaultMinItemHeight: number;
readonly defaultMinItemHeightUnit: SizeUnitEnum;
readonly defaultMinItemWidth: number;
readonly defaultMinItemWidthUnit: SizeUnitEnum;
readonly headerHeight: number;
readonly dragProxyWidth: number;
readonly dragProxyHeight: number;
}
export namespace Dimensions {
export function createCopy(original: Dimensions): Dimensions {
return {
borderWidth: original.borderWidth,
borderGrabWidth: original.borderGrabWidth,
defaultMinItemHeight: original.defaultMinItemHeight,
defaultMinItemHeightUnit: original.defaultMinItemHeightUnit,
defaultMinItemWidth: original.defaultMinItemWidth,
defaultMinItemWidthUnit: original.defaultMinItemWidthUnit,
headerHeight: original.headerHeight,
dragProxyWidth: original.dragProxyWidth,
dragProxyHeight: original.dragProxyHeight,
}
}
export const defaults: ResolvedLayoutConfig.Dimensions = {
borderWidth: 5,
borderGrabWidth: 5,
defaultMinItemHeight: 0,
defaultMinItemHeightUnit: SizeUnitEnum.Pixel,
defaultMinItemWidth: 10,
defaultMinItemWidthUnit: SizeUnitEnum.Pixel,
headerHeight: 20,
dragProxyWidth: 300,
dragProxyHeight: 200
} as const;
}
export interface Header {
readonly show: false | Side;
readonly popout: false | string;
readonly dock: string;
readonly maximise: false | string;
readonly minimise: string;
readonly close: false | string;
readonly tabDropdown: false | string;
}
export namespace Header {
export function createCopy(original: Header): Header {
return {
show: original.show,
popout: original.popout,
dock: original.dock,
close: original.close,
maximise: original.maximise,
minimise: original.minimise,
tabDropdown: original.tabDropdown,
}
}
export const defaults: ResolvedLayoutConfig.Header = {
show: Side.top,
popout: 'open in new window',
dock: 'dock',
maximise: 'maximise',
minimise: 'minimise',
close: 'close',
tabDropdown: 'additional tabs'
} as const;
}
export function isPopout(config: ResolvedLayoutConfig): config is ResolvedPopoutLayoutConfig {
return 'parentId' in config;
}
export function createDefault(): ResolvedLayoutConfig {
const result: ResolvedLayoutConfig = {
root: undefined,
openPopouts: [],
dimensions: ResolvedLayoutConfig.Dimensions.defaults,
settings: ResolvedLayoutConfig.Settings.defaults,
header: ResolvedLayoutConfig.Header.defaults,
resolved: true,
}
return result;
}
export function createCopy(config: ResolvedLayoutConfig): ResolvedLayoutConfig {
if (isPopout(config)) {
return ResolvedPopoutLayoutConfig.createCopy(config);
} else {
const result: ResolvedLayoutConfig = {
root: config.root === undefined ? undefined : ResolvedRootItemConfig.createCopy(config.root),
openPopouts: ResolvedLayoutConfig.copyOpenPopouts(config.openPopouts),
settings: ResolvedLayoutConfig.Settings.createCopy(config.settings),
dimensions: ResolvedLayoutConfig.Dimensions.createCopy(config.dimensions),
header: ResolvedLayoutConfig.Header.createCopy(config.header),
resolved: config.resolved,
}
return result;
}
}
export function copyOpenPopouts(original: ResolvedPopoutLayoutConfig[]): ResolvedPopoutLayoutConfig[] {
const count = original.length;
const result = new Array<ResolvedPopoutLayoutConfig>(count);
for (let i = 0; i < count; i++) {
result[i] = ResolvedPopoutLayoutConfig.createCopy(original[i]);
}
return result;
}
/**
* Takes a GoldenLayout configuration object and
* replaces its keys and values recursively with
* one letter counterparts
*/
export function minifyConfig(layoutConfig: ResolvedLayoutConfig): ResolvedLayoutConfig {
return ConfigMinifier.translateObject(layoutConfig, true) as ResolvedLayoutConfig;
}
/**
* Takes a configuration Object that was previously minified
* using minifyConfig and returns its original version
*/
export function unminifyConfig(minifiedConfig: ResolvedLayoutConfig): ResolvedLayoutConfig {
return ConfigMinifier.translateObject(minifiedConfig, false) as ResolvedLayoutConfig;
}
}
/** @public */
export interface ResolvedPopoutLayoutConfig extends ResolvedLayoutConfig {
readonly parentId: string | null;
readonly indexInParent: number | null;
readonly window: ResolvedPopoutLayoutConfig.Window;
}
/** @public */
export namespace ResolvedPopoutLayoutConfig {
export interface Window {
readonly width: number | null,
readonly height: number | null,
readonly left: number | null,
readonly top: number | null,
}
export namespace Window {
export function createCopy(original: Window): Window {
return {
width: original.width,
height: original.height,
left: original.left,
top: original.top,
}
}
export const defaults: ResolvedPopoutLayoutConfig.Window = {
width: null,
height: null,
left: null,
top: null,
} as const;
}
export function createCopy(original: ResolvedPopoutLayoutConfig): ResolvedPopoutLayoutConfig {
const result: ResolvedPopoutLayoutConfig = {
root: original.root === undefined ? undefined : ResolvedRootItemConfig.createCopy(original.root),
openPopouts: ResolvedLayoutConfig.copyOpenPopouts(original.openPopouts),
settings: ResolvedLayoutConfig.Settings.createCopy(original.settings),
dimensions: ResolvedLayoutConfig.Dimensions.createCopy(original.dimensions),
header: ResolvedLayoutConfig.Header.createCopy(original.header),
parentId: original.parentId,
indexInParent: original.indexInParent,
window: ResolvedPopoutLayoutConfig.Window.createCopy(original.window),
resolved: original.resolved,
}
return result;
}
}