import { nativeConcat, nativeSlice } from "./lodash/basic/_global"; import assign from "./lodash/basic/assign"; import contains from "./lodash/basic/contains"; import isArray from "./lodash/basic/isArray"; import stubFalse from "./lodash/basic/stubFalse"; import uniq from "./lodash/#uniq"; import clone from "./lodash/#clone"; import forEach from "./lodash/#forEach"; import forOwn from "./lodash/#forOwn"; import has from "./lodash/#has"; import isNil from "./lodash/#isNil"; import isNull from "./lodash/#isNull"; import isObject from "./lodash/#isObject"; import map from "./lodash/#map"; import some from "./lodash/#some"; import isPanel from "./isPanel"; import isWindow from "./isWindow"; const root = this; const global = $.global; /** * Tree UI 的集成版本 * * @type {Tree} * @since 0.1.0 * @category Soil * @see https://github.com/RaymondClr/Tree * @example * * ```ts * function foo() { * alert("Yoooooo!!!"); * } * * _.tree.parse({ * style: { margins: 5, alignChildren: ["fill", "fill"] }, * param: ["palette", "", undefined, { resizeable: true }], * group1: { * style: { margins: 0, spacing: 0, orientation: "column", alignChildren: ["fill", "fill"] }, * param: ["mainGroup1", undefined, undefined], * edittext1: { * style: { preferredSize: [180, 230] }, * param: ["console", undefined, "console", { multiline: true, scrolling: true }], * }, * }, * group2: { * style: { orientation: "column", alignChildren: ["fill", "fill"], alignment: ["fill", "bottom"] }, * param: ["paramGroup1", undefined, undefined], * group1: { * style: { orientation: "row", alignment: ["fill", "bottom"] }, * param: ["dropdownlistGroup"], * statictext1: [undefined, [0, 0, 30, 25], "方向"], * dropdownlist1: { * style: { alignment: ["fill", ""], selection: 3 }, //定义下拉列表的默认选项 * param: ["direction", [0, 0, 170, 25], ["+x", "-x", "+y", "-y"]], * }, * }, * group2: { * style: { spacing: 5, orientation: "row", alignChildren: ["fill", "fill"], alignment: ["fill", "bottom"] }, * param: ["settingGroup"], * group1: { * style: { orientation: "column", alignment: ["left", "bottom"] }, * param: ["mainGroup"], * statictext1: ["time", [0, 0, 30, 25], "时间"], * statictext2: ["transition", [0, 0, 30, 25], "过渡"], * statictext3: ["distance", [0, 0, 30, 25], "距离"], * }, * group2: { * style: { orientation: "column", alignChildren: ["fill", "fill"] }, * param: ["mainGroup"], * slider1: ["time", [0, 0, 140, 25], 1, 0, 3], * slider2: ["transition", [0, 0, 140, 25], 1, 0, 3], * slider3: ["distance", [0, 0, 140, 25], 1, 0, 3], * }, * group3: { * style: { orientation: "column", alignment: ["right", "bottom"] }, * param: ["mainGroup"], * edittext1: ["time", [0, 0, 45, 25], "10"], * edittext2: ["transition", [0, 0, 45, 25], "10"], * edittext3: ["distance", [0, 0, 45, 25], "10"], * }, * }, * }, * button1: { * style: { onClick: foo }, //添加事件侦听 * param: ["button", undefined, "添加"], * }, * }); * ``` */ const tree: Tree = { version: "beta 1.0.0", parse: runInContext, windows: [] }; const layoutModeFlags = [0, 1, 2]; const validMainContainerTypeName: MainContainerTypeName[] = ["dialog", "palette", "window"]; const mainContainerDefault: ContainerDefault = { dockable: true, show: true, singleton: false }; const controlParamRef: _Record = { button: 3, checkbox: 3, dropdownlist: 3, edittext: 3, iconbutton: 3, image: 3, listbox: 3, progressbar: 4, radiobutton: 3, scrollbar: 5, slider: 5, statictext: 3, treeview: 3, }; const containerParamRef: _Record = { group: 2, panel: 3, tab: 3, tabbedpanel: 3 }; const elementTypeFlags = { button: "A", checkbox: "B", dialog: "C", dropdownlist: "D", edittext: "E", group: "G", iconbutton: "H", image: "I", item: "J", listbox: "K", node: "L", palette: "M", panel: "N", progressbar: "O", radiobutton: "P", scrollbar: "Q", slider: "R", statictext: "S", tab: "T", tabbedpanel: "U", treeview: "V", window: "W", }; const reCombination = /[CGMNTW][ABDEFGHIKNOPQRSUV]|[DK]J|[VL][LJ]|UT/, reContainer = /[DGKLNTUV]/, reListItemContainer = /[DKLV]/, reSelectableElement = /[DKUV]/, reNativeContainer = /[GNTU]/, reNativeControl = /[ABDEHIKOPQRSV]/; const isContainer = createIsElementFlag(reContainer), isListItemContainer = createIsElementFlag(reListItemContainer), isNativeContainer = createIsElementFlag(reNativeContainer), isNativeControl = createIsElementFlag(reNativeControl), isSelectableElement = createIsElementFlag(reSelectableElement); function addContainer(container: LooseContainer, value: object, flag: string, collector: ElementCollector): LooseContainer { return flag == "node" ? addNodeContainer(container as TreeViewNode, value, flag, collector) : addGeneralContainer(container as NativeContainer, value, flag, collector); } function addControl(container: LooseContainer, value: object, key: string, collector: ElementCollector): void { const element = isListItemContainer(container.type) ? addListItem(container as ListItemContainer, value, key, collector) : addGeneralControl(container as NativeContainer, value, key, collector); assign(element, getElementStyle(value)); } function addGeneralContainer(container: NativeContainer, value: object, flag: string, collector: ElementCollector): any { const style = getElementStyle(value); const newContainer = nativeAddContainer(container, flag, assignElementParam(value, flag)); if (isSelectableElement(flag) && has(style, "selection")) { const value = { container: newContainer as SelectableElement, itemIndex: (style as AnyObject).selection }; collector.selectableElement.push(value); delete (style as AnyObject)["selection"]; return assign(newContainer, style); } assign(newContainer, style); return newContainer; } function addGeneralControl(container: NativeContainer, value: object, flag: string, collector: ElementCollector): NativeControl { return nativeAddControl(container, flag, assignElementParam(value, flag)); } function addGetElementMethods(constructors: [typeof Window, typeof Panel, typeof Group]): void { forEach(constructors, constructor => { const prototype = constructor.prototype as AnyObject; prototype.getElementById = getElementById; prototype.getElementsByName = getElementsByName; prototype.getElementsByType = getElementsByType; freezeProperty(prototype, "getElementById"); freezeProperty(prototype, "getElementsByName"); freezeProperty(prototype, "getElementsByType"); }); } function addListItem(container: ListItemContainer, value: object, flag: string, collector: ElementCollector): ListItem { return nativeAddNodeItem(container, getListItemParam(value)); } function addNodeContainer(container: TreeViewNode, value: object, flag: string, collector: ElementCollector): TreeViewNode { const style = getElementStyle(value) as AnyObject; const node = nativeAddNode(container, getListItemParam(value)); if (style.expanded) { collector.nodeItems.push(node); } delete style["expanded"]; assign(node, style); return node; } function assignContext(global: unknown, dockable: boolean, isSingletonWindow: boolean) { if (isSingletonWindow || !dockable) { return Window; } if (isValidContext(root)) { return root; } return isValidContext(global) ? global : Window; } function assignElementParam(value: object, flag: string): Array { return assignUniqueName(getElementParam(value), flag); } function assignLayoutMode(setAll: number, setAlone: number): number { if (contains(layoutModeFlags, setAlone)) { return setAlone; } if (contains(layoutModeFlags, setAll)) { return setAll; } return 0; } function assignUniqueName(param: Array, flag: string): Array { const name = param[0]; if (isNil(name)) { return param; } const location = getCreationPropertiesLocation(flag); const creationProperties = param[location]; if (isObject(creationProperties)) { if (has(creationProperties, "name")) { return param; } else { param[location].name = name; return param; } } else { param[location] = { name: name }; return param; } } function assignWindowType(param: Array): Array { const type = String(param[0]); param[0] = contains(validMainContainerTypeName, type) ? type : "palette"; return param; } function baseGet(object: AnyObject, key: string): any { return isNil(object) ? undefined : object[key]; } function baseGetConfig(value: object): any { return baseGet(value, "config"); } function baseGetElementId(element: _Control): string { const properties = (element as AnyObject).properties; return properties && properties.name; } function baseGetListItemParam(value: object): any { return baseGet(value, "param"); } function baseGetParam(value: object): Array { const result = baseGet(value, "param"); return isArray(result) ? mapNullToUndefined(result) : []; } function baseGetStyle(value: object): object { const result = baseGet(value, "style"); return isObject(result) ? result : {}; } function buildNativeWindow(resource: AnyObject, context: MainContainer, showWindow: boolean, layoutMode: boolean): MainContainer { const container = bulidElements(resource, context); initLayout(container, layoutMode); if (isWindow(container) && showWindow) { container.show(); } return container; } function buildSingletonWindow(resource: AnyObject, context: MainContainer, showWindow: boolean, layoutMode: boolean): () => MainContainer { let container: MainContainer; return () => { if (isInvisibleContainer(container)) { container = bulidElements(resource, context); } initLayout(container, layoutMode); if (isWindow(container) && showWindow) { container.show(); } return container; }; } function buildWindow(isSingletonWindow: boolean): Window | Panel { const func = isSingletonWindow ? buildSingletonWindow : buildNativeWindow; return func.apply(null, nativeSlice.call(arguments, 1)); } function bulidElements(resource: AnyObject, context: MainContainer): MainContainer { const container = initMainContainer(resource, context); const collector: ElementCollector = { nodeItems: [], selectableElement: [] }; parseElement(resource, container, collector); selectChildItem(collector.selectableElement); expandTreeViewNodes(collector.nodeItems); return container; } function castArray(value: T | Array): Array { return isArray(value) ? value : [value]; } function createIsElementFlag(regex: RegExp) { return (flag: string): flag is T => { if (has(elementTypeFlags, flag)) { return regex.test(elementTypeFlags[flag as ElementFlags]); } return false; }; } function baseEachElement(containers: Array, accumulator: Array<_Control>, breaker: (accumulator: Array<_Control>) => boolean, predicate: (element: _Control) => boolean): boolean { return some(containers, container => { const result: Array = []; const isDone = some(container.children, element => { if (isNativeContainer(element.type)) { result.push(element as Container); } if (predicate(element)) { accumulator.push(element); } return breaker(accumulator); }); return isDone ? true : baseEachElement(result, accumulator, breaker, predicate); }); } function expandTreeViewNodes(nodes: Array): void { forEach(nodes, node => { node.expanded = true; }); } function filterFindElementInput(input: unknown): Array { return uniq(map(nativeConcat.apply([], input), String)); } function freezeProperty(object: T, property: string) { object.watch(property, (name: string, oldValue: T) => oldValue); } function getCreationPropertiesLocation(key: string) { if (isNativeControl(key)) { return controlParamRef[key]; } else if (isNativeContainer(key)) { return containerParamRef[key]; } else { return 0; } } function getElementById(this: Container, targetId: unknown): _Control | null { const id = String(targetId); const result: Array<_Control> = []; const breaker = (accumulator: Array<_Control>): boolean => accumulator.length > 0; baseEachElement([this], result, breaker, element => { const elementId = baseGetElementId(element); if (isNil(elementId)) { return false; } return id === elementId; }); return result.length === 0 ? null : result[0]; } function getElementParam(value: object): Array { return isArray(value) ? mapNullToUndefined(value) : baseGetParam(value); } function getElementStyle(value: object): object { return isArray(value) ? {} : baseGetStyle(value); } function getElementsByName(this: Container, ...args: Array): Array<_Control> | null { const targetNames = filterFindElementInput(arguments); const seen: Array = []; const result: Array<_Control> = []; const breaker = (): boolean => targetNames.length === seen.length; baseEachElement([this], result, breaker, element => { const elementId = baseGetElementId(element); if (isNil(elementId)) { return false; } if (contains(targetNames, elementId) && !contains(seen, elementId)) { seen.push(elementId); return true; } return false; }); return result.length === 0 ? null : result; } function getElementsByType(this: Container, ...args: Array): Array<_Control> | null { const targetTypes = filterFindElementInput(arguments); const result: Array<_Control> = []; baseEachElement([this], result, stubFalse, element => contains(targetTypes, element.type)); return result.length === 0 ? null : result; } function getListItemParam(value: unknown): string { const result = isObject(value) ? baseGetListItemParam(value) : value; return String(result); } function getMainContainer(param: Array, context: MainContainer): _Panel | Window { return isPanel(context) ? context : new Window(param[0], param[1], param[2], param[3]); } function initBuildValues(resource: AnyObject, parserSelf: AnyObject) { const config = assign(clone(mainContainerDefault), baseGetConfig(resource)); const showWindow = Boolean(config.show); const dockable = Boolean(config.dockable); const isSingletonWindow = Boolean(config.singleton); const context = assignContext(parserSelf.context, dockable, isSingletonWindow); const layoutMode = assignLayoutMode(parserSelf.layoutMode, config.layoutMode); return [isSingletonWindow, resource, context, showWindow, layoutMode]; } function initLayout(container: Container, layoutMode: boolean): void { container.layout.layout(layoutMode); container.layout.resize(); } function initMainContainer(resource: AnyObject, context: MainContainer): MainContainer { const mainContainer = getMainContainer(assignWindowType(baseGetParam(resource)), context) as _Window; mainContainer.onResizing = mainContainer.onResize = function () { this.layout.resize(); }; assign(mainContainer, baseGetStyle(resource)); return mainContainer; } function isInvisibleContainer(container: Panel | Window): boolean { return isNil(container) || !container.visible; } function isTabbedpanel(element: _Control): element is TabbedPanel { return element.type == "tabbedpanel"; } function isValidCombination(parentType: ElementFlags, childType: ElementFlags): boolean { const flagCombination = elementTypeFlags[parentType] + elementTypeFlags[childType]; return reCombination.test(flagCombination); } function isValidContext(context: unknown): boolean { return context === global || isPanel(context); } function isValidElement(flag: string): flag is ElementFlags { return has(elementTypeFlags, flag); } function mapNullToUndefined(array: Array): Array { return map(array, value => (isNull(value) ? undefined : value)); } function nativeAddContainer(container: NativeContainer, type: any, param: Array): NativeContainer { return (container.add as any)(type, param[1], param[2], param[3]); } function nativeAddControl(container: NativeContainer, type: any, param: Array): NativeControl { return (container.add as any)(type, param[1], param[2], param[3], param[4], param[5]) as unknown as NativeControl; } function nativeAddNode(container: TreeViewNode, text: string): TreeViewNode { return container.add("node", text); } function nativeAddNodeItem(node: ListItemContainer, text: string): ListItem { return node.add("item", text); } function parseElement(resource: AnyObject, container: LooseContainer, collector: ElementCollector) { forOwn(resource, (value, key) => { const flag = trimNumber(key).toLowerCase(); if (isValidElement(flag) && isValidCombination(container.type as ElementFlags, flag)) { if (isContainer(flag)) { const newContainer = addContainer(container, value, flag, collector); parseElement(value, newContainer, collector); } else { addControl(container, value, flag, collector); } } }); } function runInContext(resource: AnyObject): MainContainer { addGetElementMethods([Window, Panel, Group]); const resource_ = isObject(resource) ? resource : {}; const container = buildWindow.apply(null, initBuildValues(resource_, tree)); tree.windows.push(container); return container; } function selectChildItem(selectableElementValues: Array) { forEach(selectableElementValues, value => { const container = value.container; const itemIndex = value.itemIndex; if (isTabbedpanel(container)) { container.selection = itemIndex; } else { container.selection = map(castArray(itemIndex), value => container.items[value]) as unknown as ListItem; } }); } function trimNumber(string: string): string { return string.replace(/\d/g, ""); } export default tree;