UNPKG

lingo3d

Version:

Lingo3D is a React/Vue 3d game development framework that ships with a complete visual editor

1,571 lines 265 kB
import { handleStopPropagation } from "../../engine/hotkeys"; import { emitEditorEdit } from "../../events/onEditorEdit"; import { tweakpaneChangePtr } from "../../pointers/tweakpaneChangePtr"; import { tweakpaneDownPtr } from "../../pointers/tweanpaneDownPtr"; const handleDown = (ev) => { handleStopPropagation(ev); tweakpaneDownPtr[0] = true; queueMicrotask(() => { const [key, value] = tweakpaneChangePtr[0]; emitEditorEdit({ phase: "start", key, value }); }); }; const handleUp = (ev) => { handleStopPropagation(ev); queueMicrotask(() => { tweakpaneDownPtr[0] = false; const [key, value] = tweakpaneChangePtr[0]; emitEditorEdit({ phase: "end", key, value }); }); }; /*** * A simple semantic versioning perser. */ class Semver { /** * @hidden */ constructor(text) { const [core, prerelease] = text.split("-"); const coreComps = core.split("."); this.major = parseInt(coreComps[0], 10); this.minor = parseInt(coreComps[1], 10); this.patch = parseInt(coreComps[2], 10); this.prerelease = prerelease !== null && prerelease !== void 0 ? prerelease : null; } toString() { const core = [this.major, this.minor, this.patch].join("."); return this.prerelease !== null ? [core, this.prerelease].join("-") : core; } } class BladeApi { constructor(controller) { this.controller_ = controller; } get element() { return this.controller_.view.element; } get disabled() { return this.controller_.viewProps.get("disabled"); } set disabled(disabled) { this.controller_.viewProps.set("disabled", disabled); } get hidden() { return this.controller_.viewProps.get("hidden"); } set hidden(hidden) { this.controller_.viewProps.set("hidden", hidden); } dispose() { this.controller_.viewProps.set("disposed", true); } } class TpEvent { constructor(target) { this.target = target; } } class TpChangeEvent extends TpEvent { constructor(target, value, presetKey, last) { super(target); this.value = value; this.presetKey = presetKey; this.last = last !== null && last !== void 0 ? last : true; } } class TpUpdateEvent extends TpEvent { constructor(target, value, presetKey) { super(target); this.value = value; this.presetKey = presetKey; } } class TpFoldEvent extends TpEvent { constructor(target, expanded) { super(target); this.expanded = expanded; } } class TpTabSelectEvent extends TpEvent { constructor(target, index) { super(target); this.index = index; } } function forceCast(v) { return v; } function isEmpty(value) { return value === null || value === undefined; } function deepEqualsArray(a1, a2) { if (a1.length !== a2.length) { return false; } for (let i = 0; i < a1.length; i++) { if (a1[i] !== a2[i]) { return false; } } return true; } const CREATE_MESSAGE_MAP = { alreadydisposed: () => "View has been already disposed", invalidparams: (context) => `Invalid parameters for '${context.name}'`, nomatchingcontroller: (context) => `No matching controller for '${context.key}'`, nomatchingview: (context) => `No matching view for '${JSON.stringify(context.params)}'`, notbindable: () => `Value is not bindable`, propertynotfound: (context) => `Property '${context.name}' not found`, shouldneverhappen: () => "This error should never happen" }; class TpError { constructor(config) { var _a; this.message = (_a = CREATE_MESSAGE_MAP[config.type](forceCast(config.context))) !== null && _a !== void 0 ? _a : "Unexpected error"; this.name = this.constructor.name; this.stack = new Error(this.message).stack; this.type = config.type; } static alreadyDisposed() { return new TpError({ type: "alreadydisposed" }); } static notBindable() { return new TpError({ type: "notbindable" }); } static propertyNotFound(name) { return new TpError({ type: "propertynotfound", context: { name: name } }); } static shouldNeverHappen() { return new TpError({ type: "shouldneverhappen" }); } } class BindingTarget { constructor(obj, key, opt_id) { this.obj_ = obj; this.key_ = key; this.presetKey_ = opt_id !== null && opt_id !== void 0 ? opt_id : key; } static isBindable(obj) { if (obj === null) { return false; } if (typeof obj !== "object") { return false; } return true; } get key() { return this.key_; } get presetKey() { return this.presetKey_; } read() { return this.obj_[this.key_]; } write(value) { this.obj_[this.key_] = value; } writeProperty(name, value) { const valueObj = this.read(); if (!BindingTarget.isBindable(valueObj)) { throw TpError.notBindable(); } if (!(name in valueObj)) { throw TpError.propertyNotFound(name); } valueObj[name] = value; } } class ButtonApi extends BladeApi { get label() { return this.controller_.props.get("label"); } set label(label) { this.controller_.props.set("label", label); } get title() { var _a; return (_a = this.controller_.valueController.props.get("title")) !== null && _a !== void 0 ? _a : ""; } set title(title) { this.controller_.valueController.props.set("title", title); } on(eventName, handler) { const bh = handler.bind(this); const emitter = this.controller_.valueController.emitter; emitter.on(eventName, () => { bh(new TpEvent(this)); }); return this; } } class Emitter { constructor() { this.observers_ = {}; } on(eventName, handler) { let observers = this.observers_[eventName]; if (!observers) { observers = this.observers_[eventName] = []; } observers.push({ handler: handler }); return this; } off(eventName, handler) { const observers = this.observers_[eventName]; if (observers) { this.observers_[eventName] = observers.filter((observer) => { return observer.handler !== handler; }); } return this; } emit(eventName, event) { const observers = this.observers_[eventName]; if (!observers) { return; } observers.forEach((observer) => { observer.handler(event); }); } } const PREFIX = "tp"; function ClassName(viewName) { const fn = (opt_elementName, opt_modifier) => { return [ PREFIX, "-", viewName, "v", opt_elementName ? `_${opt_elementName}` : "", opt_modifier ? `-${opt_modifier}` : "" ].join(""); }; return fn; } function compose(h1, h2) { return (input) => h2(h1(input)); } function extractValue(ev) { return ev.rawValue; } function bindValue(value, applyValue) { value.emitter.on("change", compose(extractValue, applyValue)); applyValue(value.rawValue); } function bindValueMap(valueMap, key, applyValue) { bindValue(valueMap.value(key), applyValue); } function applyClass(elem, className, active) { if (active) { elem.classList.add(className); } else { elem.classList.remove(className); } } function valueToClassName(elem, className) { return (value) => { applyClass(elem, className, value); }; } function bindValueToTextContent(value, elem) { bindValue(value, (text) => { elem.textContent = text !== null && text !== void 0 ? text : ""; }); } const className$q = ClassName("btn"); class ButtonView { constructor(doc, config) { this.element = doc.createElement("div"); this.element.classList.add(className$q()); config.viewProps.bindClassModifiers(this.element); const buttonElem = doc.createElement("button"); buttonElem.classList.add(className$q("b")); config.viewProps.bindDisabled(buttonElem); this.element.appendChild(buttonElem); this.buttonElement = buttonElem; const titleElem = doc.createElement("div"); titleElem.classList.add(className$q("t")); bindValueToTextContent(config.props.value("title"), titleElem); this.buttonElement.appendChild(titleElem); } } class ButtonController { constructor(doc, config) { this.emitter = new Emitter(); this.onClick_ = this.onClick_.bind(this); this.props = config.props; this.viewProps = config.viewProps; this.view = new ButtonView(doc, { props: this.props, viewProps: this.viewProps }); this.view.buttonElement.addEventListener("click", this.onClick_); } onClick_() { this.emitter.emit("click", { sender: this }); } } class BoundValue { constructor(initialValue, config) { var _a; this.constraint_ = config === null || config === void 0 ? void 0 : config.constraint; this.equals_ = (_a = config === null || config === void 0 ? void 0 : config.equals) !== null && _a !== void 0 ? _a : (v1, v2) => v1 === v2; this.emitter = new Emitter(); this.rawValue_ = initialValue; } get constraint() { return this.constraint_; } get rawValue() { return this.rawValue_; } set rawValue(rawValue) { this.setRawValue(rawValue, { forceEmit: false, last: true }); } setRawValue(rawValue, options) { const opts = options !== null && options !== void 0 ? options : { forceEmit: false, last: true }; const constrainedValue = this.constraint_ ? this.constraint_.constrain(rawValue) : rawValue; const changed = !this.equals_(this.rawValue_, constrainedValue); if (!changed && !opts.forceEmit) { return; } this.emitter.emit("beforechange", { sender: this }); this.rawValue_ = constrainedValue; this.emitter.emit("change", { options: opts, rawValue: constrainedValue, sender: this }); } } class PrimitiveValue { constructor(initialValue) { this.emitter = new Emitter(); this.value_ = initialValue; } get rawValue() { return this.value_; } set rawValue(value) { this.setRawValue(value, { forceEmit: false, last: true }); } setRawValue(value, options) { const opts = options !== null && options !== void 0 ? options : { forceEmit: false, last: true }; if (this.value_ === value && !opts.forceEmit) { return; } this.emitter.emit("beforechange", { sender: this }); this.value_ = value; this.emitter.emit("change", { options: opts, rawValue: this.value_, sender: this }); } } function createValue(initialValue, config) { const constraint = config === null || config === void 0 ? void 0 : config.constraint; const equals = config === null || config === void 0 ? void 0 : config.equals; if (!constraint && !equals) { return new PrimitiveValue(initialValue); } return new BoundValue(initialValue, config); } class ValueMap { constructor(valueMap) { this.emitter = new Emitter(); this.valMap_ = valueMap; for (const key in this.valMap_) { const v = this.valMap_[key]; v.emitter.on("change", () => { this.emitter.emit("change", { key: key, sender: this }); }); } } static createCore(initialValue) { const keys = Object.keys(initialValue); return keys.reduce((o, key) => { return Object.assign(o, { [key]: createValue(initialValue[key]) }); }, {}); } static fromObject(initialValue) { const core = this.createCore(initialValue); return new ValueMap(core); } get(key) { return this.valMap_[key].rawValue; } set(key, value) { this.valMap_[key].rawValue = value; } value(key) { return this.valMap_[key]; } } function parseObject(value, keyToParserMap) { const keys = Object.keys(keyToParserMap); const result = keys.reduce((tmp, key) => { if (tmp === undefined) { return undefined; } const parser = keyToParserMap[key]; const result = parser(value[key]); return result.succeeded ? Object.assign(Object.assign({}, tmp), { [key]: result.value }) : undefined; }, {}); return forceCast(result); } function parseArray(value, parseItem) { return value.reduce((tmp, item) => { if (tmp === undefined) { return undefined; } const result = parseItem(item); if (!result.succeeded || result.value === undefined) { return undefined; } return [...tmp, result.value]; }, []); } function isObject(value) { if (value === null) { return false; } return typeof value === "object"; } function createParamsParserBuilder(parse) { return (optional) => (v) => { if (!optional && v === undefined) { return { succeeded: false, value: undefined }; } if (optional && v === undefined) { return { succeeded: true, value: undefined }; } const result = parse(v); return result !== undefined ? { succeeded: true, value: result } : { succeeded: false, value: undefined }; }; } function createParamsParserBuilders(optional) { return { custom: (parse) => createParamsParserBuilder(parse)(optional), boolean: createParamsParserBuilder((v) => typeof v === "boolean" ? v : undefined)(optional), number: createParamsParserBuilder((v) => typeof v === "number" ? v : undefined)(optional), string: createParamsParserBuilder((v) => typeof v === "string" ? v : undefined)(optional), function: createParamsParserBuilder((v) => typeof v === "function" ? v : undefined)(optional), constant: (value) => createParamsParserBuilder((v) => (v === value ? value : undefined))(optional), raw: createParamsParserBuilder((v) => v)(optional), object: (keyToParserMap) => createParamsParserBuilder((v) => { if (!isObject(v)) { return undefined; } return parseObject(v, keyToParserMap); })(optional), array: (itemParser) => createParamsParserBuilder((v) => { if (!Array.isArray(v)) { return undefined; } return parseArray(v, itemParser); })(optional) }; } const ParamsParsers = { optional: createParamsParserBuilders(true), required: createParamsParserBuilders(false) }; function parseParams(value, keyToParserMap) { const result = ParamsParsers.required.object(keyToParserMap)(value); return result.succeeded ? result.value : undefined; } function disposeElement(elem) { if (elem && elem.parentElement) { elem.parentElement.removeChild(elem); } return null; } function getAllBladePositions() { return ["veryfirst", "first", "last", "verylast"]; } const className$p = ClassName(""); const POS_TO_CLASS_NAME_MAP = { veryfirst: "vfst", first: "fst", last: "lst", verylast: "vlst" }; class BladeController { constructor(config) { this.parent_ = null; this.blade = config.blade; this.view = config.view; this.viewProps = config.viewProps; const elem = this.view.element; this.blade.value("positions").emitter.on("change", () => { getAllBladePositions().forEach((pos) => { elem.classList.remove(className$p(undefined, POS_TO_CLASS_NAME_MAP[pos])); }); this.blade.get("positions").forEach((pos) => { elem.classList.add(className$p(undefined, POS_TO_CLASS_NAME_MAP[pos])); }); }); this.viewProps.handleDispose(() => { disposeElement(elem); }); } get parent() { return this.parent_; } } const SVG_NS = "http://www.w3.org/2000/svg"; function forceReflow(element) { element.offsetHeight; } function disableTransitionTemporarily(element, callback) { const t = element.style.transition; element.style.transition = "none"; callback(); element.style.transition = t; } function supportsTouch(doc) { return doc.ontouchstart !== undefined; } function getGlobalObject() { return new Function("return this")(); } function getWindowDocument() { const globalObj = forceCast(getGlobalObject()); return globalObj.document; } function getCanvasContext(canvasElement) { const win = canvasElement.ownerDocument.defaultView; if (!win) { return null; } const isBrowser = "document" in win; return isBrowser ? canvasElement.getContext("2d") : null; } const ICON_ID_TO_INNER_HTML_MAP = { check: '<path d="M2 8l4 4l8 -8"/>', dropdown: '<path d="M5 7h6l-3 3 z"/>', p2dpad: '<path d="M8 4v8"/><path d="M4 8h8"/><circle cx="12" cy="12" r="1.2"/>' }; function createSvgIconElement(document, iconId) { const elem = document.createElementNS(SVG_NS, "svg"); elem.innerHTML = ICON_ID_TO_INNER_HTML_MAP[iconId]; return elem; } function insertElementAt(parentElement, element, index) { parentElement.insertBefore(element, parentElement.children[index]); } function removeElement(element) { if (element.parentElement) { element.parentElement.removeChild(element); } } function removeChildElements(element) { while (element.children.length > 0) { element.removeChild(element.children[0]); } } function removeChildNodes(element) { while (element.childNodes.length > 0) { element.removeChild(element.childNodes[0]); } } function findNextTarget(ev) { if (ev.relatedTarget) { return forceCast(ev.relatedTarget); } if ("explicitOriginalTarget" in ev) { return ev.explicitOriginalTarget; } return null; } const className$o = ClassName("lbl"); function createLabelNode(doc, label) { const frag = doc.createDocumentFragment(); const lineNodes = label.split("\n").map((line) => { return doc.createTextNode(line); }); lineNodes.forEach((lineNode, index) => { if (index > 0) { frag.appendChild(doc.createElement("br")); } frag.appendChild(lineNode); }); return frag; } class LabelView { constructor(doc, config) { this.element = doc.createElement("div"); this.element.classList.add(className$o()); config.viewProps.bindClassModifiers(this.element); const labelElem = doc.createElement("div"); labelElem.classList.add(className$o("l")); bindValueMap(config.props, "label", (value) => { if (isEmpty(value)) { this.element.classList.add(className$o(undefined, "nol")); } else { this.element.classList.remove(className$o(undefined, "nol")); removeChildNodes(labelElem); labelElem.appendChild(createLabelNode(doc, value)); } }); this.element.appendChild(labelElem); this.labelElement = labelElem; const valueElem = doc.createElement("div"); valueElem.classList.add(className$o("v")); this.element.appendChild(valueElem); this.valueElement = valueElem; } } class LabelController extends BladeController { constructor(doc, config) { const viewProps = config.valueController.viewProps; super(Object.assign(Object.assign({}, config), { view: new LabelView(doc, { props: config.props, viewProps: viewProps }), viewProps: viewProps })); this.props = config.props; this.valueController = config.valueController; this.view.valueElement.appendChild(this.valueController.view.element); } } const ButtonBladePlugin = { id: "button", type: "blade", accept(params) { const p = ParamsParsers; const result = parseParams(params, { title: p.required.string, view: p.required.constant("button"), label: p.optional.string }); return result ? { params: result } : null; }, controller(args) { return new LabelController(args.document, { blade: args.blade, props: ValueMap.fromObject({ label: args.params.label }), valueController: new ButtonController(args.document, { props: ValueMap.fromObject({ title: args.params.title }), viewProps: args.viewProps }) }); }, api(args) { if (!(args.controller instanceof LabelController)) { return null; } if (!(args.controller.valueController instanceof ButtonController)) { return null; } return new ButtonApi(args.controller); } }; class ValueBladeController extends BladeController { constructor(config) { super(config); this.value = config.value; } } function createBlade() { return new ValueMap({ positions: createValue([], { equals: deepEqualsArray }) }); } class Foldable extends ValueMap { constructor(valueMap) { super(valueMap); } static create(expanded) { const coreObj = { completed: true, expanded: expanded, expandedHeight: null, shouldFixHeight: false, temporaryExpanded: null }; const core = ValueMap.createCore(coreObj); return new Foldable(core); } get styleExpanded() { var _a; return (_a = this.get("temporaryExpanded")) !== null && _a !== void 0 ? _a : this.get("expanded"); } get styleHeight() { if (!this.styleExpanded) { return "0"; } const exHeight = this.get("expandedHeight"); if (this.get("shouldFixHeight") && !isEmpty(exHeight)) { return `${exHeight}px`; } return "auto"; } bindExpandedClass(elem, expandedClassName) { const onExpand = () => { const expanded = this.styleExpanded; if (expanded) { elem.classList.add(expandedClassName); } else { elem.classList.remove(expandedClassName); } }; bindValueMap(this, "expanded", onExpand); bindValueMap(this, "temporaryExpanded", onExpand); } cleanUpTransition() { this.set("shouldFixHeight", false); this.set("expandedHeight", null); this.set("completed", true); } } function computeExpandedFolderHeight(folder, containerElement) { let height = 0; disableTransitionTemporarily(containerElement, () => { folder.set("expandedHeight", null); folder.set("temporaryExpanded", true); forceReflow(containerElement); height = containerElement.clientHeight; folder.set("temporaryExpanded", null); forceReflow(containerElement); }); return height; } function applyHeight(foldable, elem) { elem.style.height = foldable.styleHeight; } function bindFoldable(foldable, elem) { foldable.value("expanded").emitter.on("beforechange", () => { foldable.set("completed", false); if (isEmpty(foldable.get("expandedHeight"))) { foldable.set("expandedHeight", computeExpandedFolderHeight(foldable, elem)); } foldable.set("shouldFixHeight", true); forceReflow(elem); }); foldable.emitter.on("change", () => { applyHeight(foldable, elem); }); applyHeight(foldable, elem); elem.addEventListener("transitionend", (ev) => { if (ev.propertyName !== "height") { return; } foldable.cleanUpTransition(); }); } class RackLikeApi extends BladeApi { constructor(controller, rackApi) { super(controller); this.rackApi_ = rackApi; } } function addButtonAsBlade(api, params) { return api.addBlade(Object.assign(Object.assign({}, params), { view: "button" })); } function addFolderAsBlade(api, params) { return api.addBlade(Object.assign(Object.assign({}, params), { view: "folder" })); } function addSeparatorAsBlade(api, opt_params) { const params = opt_params !== null && opt_params !== void 0 ? opt_params : {}; return api.addBlade(Object.assign(Object.assign({}, params), { view: "separator" })); } function addTabAsBlade(api, params) { return api.addBlade(Object.assign(Object.assign({}, params), { view: "tab" })); } class NestedOrderedSet { constructor(extract) { this.emitter = new Emitter(); this.items_ = []; this.cache_ = new Set(); this.onSubListAdd_ = this.onSubListAdd_.bind(this); this.onSubListRemove_ = this.onSubListRemove_.bind(this); this.extract_ = extract; } get items() { return this.items_; } allItems() { return Array.from(this.cache_); } find(callback) { for (const item of this.allItems()) { if (callback(item)) { return item; } } return null; } includes(item) { return this.cache_.has(item); } add(item, opt_index) { if (this.includes(item)) { throw TpError.shouldNeverHappen(); } const index = opt_index !== undefined ? opt_index : this.items_.length; this.items_.splice(index, 0, item); this.cache_.add(item); const subList = this.extract_(item); if (subList) { subList.emitter.on("add", this.onSubListAdd_); subList.emitter.on("remove", this.onSubListRemove_); subList.allItems().forEach((item) => { this.cache_.add(item); }); } this.emitter.emit("add", { index: index, item: item, root: this, target: this }); } remove(item) { const index = this.items_.indexOf(item); if (index < 0) { return; } this.items_.splice(index, 1); this.cache_.delete(item); const subList = this.extract_(item); if (subList) { subList.emitter.off("add", this.onSubListAdd_); subList.emitter.off("remove", this.onSubListRemove_); } this.emitter.emit("remove", { index: index, item: item, root: this, target: this }); } onSubListAdd_(ev) { this.cache_.add(ev.item); this.emitter.emit("add", { index: ev.index, item: ev.item, root: this, target: ev.target }); } onSubListRemove_(ev) { this.cache_.delete(ev.item); this.emitter.emit("remove", { index: ev.index, item: ev.item, root: this, target: ev.target }); } } class InputBindingApi extends BladeApi { constructor(controller) { super(controller); this.onBindingChange_ = this.onBindingChange_.bind(this); this.emitter_ = new Emitter(); this.controller_.binding.emitter.on("change", this.onBindingChange_); } get label() { return this.controller_.props.get("label"); } set label(label) { this.controller_.props.set("label", label); } on(eventName, handler) { const bh = handler.bind(this); this.emitter_.on(eventName, (ev) => { bh(ev.event); }); return this; } refresh() { this.controller_.binding.read(); } onBindingChange_(ev) { const value = ev.sender.target.read(); this.emitter_.emit("change", { event: new TpChangeEvent(this, forceCast(value), this.controller_.binding.target.presetKey, ev.options.last) }); } } class InputBindingController extends LabelController { constructor(doc, config) { super(doc, config); this.binding = config.binding; } } class MonitorBindingApi extends BladeApi { constructor(controller) { super(controller); this.onBindingUpdate_ = this.onBindingUpdate_.bind(this); this.emitter_ = new Emitter(); this.controller_.binding.emitter.on("update", this.onBindingUpdate_); } get label() { return this.controller_.props.get("label"); } set label(label) { this.controller_.props.set("label", label); } on(eventName, handler) { const bh = handler.bind(this); this.emitter_.on(eventName, (ev) => { bh(ev.event); }); return this; } refresh() { this.controller_.binding.read(); } onBindingUpdate_(ev) { const value = ev.sender.target.read(); this.emitter_.emit("update", { event: new TpUpdateEvent(this, forceCast(value), this.controller_.binding.target.presetKey) }); } } class MonitorBindingController extends LabelController { constructor(doc, config) { super(doc, config); this.binding = config.binding; this.viewProps.bindDisabled(this.binding.ticker); this.viewProps.handleDispose(() => { this.binding.dispose(); }); } } function findSubBladeApiSet(api) { if (api instanceof RackApi) { return api["apiSet_"]; } if (api instanceof RackLikeApi) { return api["rackApi_"]["apiSet_"]; } return null; } function getApiByController(apiSet, controller) { const api = apiSet.find((api) => api.controller_ === controller); if (!api) { throw TpError.shouldNeverHappen(); } return api; } function createBindingTarget(obj, key, opt_id) { if (!BindingTarget.isBindable(obj)) { throw TpError.notBindable(); } return new BindingTarget(obj, key, opt_id); } class RackApi extends BladeApi { constructor(controller, pool) { super(controller); this.onRackAdd_ = this.onRackAdd_.bind(this); this.onRackRemove_ = this.onRackRemove_.bind(this); this.onRackInputChange_ = this.onRackInputChange_.bind(this); this.onRackMonitorUpdate_ = this.onRackMonitorUpdate_.bind(this); this.emitter_ = new Emitter(); this.apiSet_ = new NestedOrderedSet(findSubBladeApiSet); this.pool_ = pool; const rack = this.controller_.rack; rack.emitter.on("add", this.onRackAdd_); rack.emitter.on("remove", this.onRackRemove_); rack.emitter.on("inputchange", this.onRackInputChange_); rack.emitter.on("monitorupdate", this.onRackMonitorUpdate_); rack.children.forEach((bc) => { this.setUpApi_(bc); }); } get children() { return this.controller_.rack.children.map((bc) => getApiByController(this.apiSet_, bc)); } addInput(object, key, opt_params) { const params = opt_params !== null && opt_params !== void 0 ? opt_params : {}; const doc = this.controller_.view.element.ownerDocument; const bc = this.pool_.createInput(doc, createBindingTarget(object, key, params.presetKey), params); const api = new InputBindingApi(bc); return this.add(api, params.index); } addMonitor(object, key, opt_params) { const params = opt_params !== null && opt_params !== void 0 ? opt_params : {}; const doc = this.controller_.view.element.ownerDocument; const bc = this.pool_.createMonitor(doc, createBindingTarget(object, key), params); const api = new MonitorBindingApi(bc); return forceCast(this.add(api, params.index)); } addFolder(params) { return addFolderAsBlade(this, params); } addButton(params) { return addButtonAsBlade(this, params); } addSeparator(opt_params) { return addSeparatorAsBlade(this, opt_params); } addTab(params) { return addTabAsBlade(this, params); } add(api, opt_index) { this.controller_.rack.add(api.controller_, opt_index); const gapi = this.apiSet_.find((a) => a.controller_ === api.controller_); if (gapi) { this.apiSet_.remove(gapi); } this.apiSet_.add(api); return api; } remove(api) { this.controller_.rack.remove(api.controller_); } addBlade(params) { const doc = this.controller_.view.element.ownerDocument; const bc = this.pool_.createBlade(doc, params); const api = this.pool_.createBladeApi(bc); return this.add(api, params.index); } on(eventName, handler) { const bh = handler.bind(this); this.emitter_.on(eventName, (ev) => { bh(ev.event); }); return this; } setUpApi_(bc) { const api = this.apiSet_.find((api) => api.controller_ === bc); if (!api) { this.apiSet_.add(this.pool_.createBladeApi(bc)); } } onRackAdd_(ev) { this.setUpApi_(ev.bladeController); } onRackRemove_(ev) { if (ev.isRoot) { const api = getApiByController(this.apiSet_, ev.bladeController); this.apiSet_.remove(api); } } onRackInputChange_(ev) { const bc = ev.bladeController; if (bc instanceof InputBindingController) { const api = getApiByController(this.apiSet_, bc); const binding = bc.binding; this.emitter_.emit("change", { event: new TpChangeEvent(api, forceCast(binding.target.read()), binding.target.presetKey, ev.options.last) }); } else if (bc instanceof ValueBladeController) { const api = getApiByController(this.apiSet_, bc); this.emitter_.emit("change", { event: new TpChangeEvent(api, bc.value.rawValue, undefined, ev.options.last) }); } } onRackMonitorUpdate_(ev) { if (!(ev.bladeController instanceof MonitorBindingController)) { throw TpError.shouldNeverHappen(); } const api = getApiByController(this.apiSet_, ev.bladeController); const binding = ev.bladeController.binding; this.emitter_.emit("update", { event: new TpUpdateEvent(api, forceCast(binding.target.read()), binding.target.presetKey) }); } } class FolderApi extends RackLikeApi { constructor(controller, pool) { super(controller, new RackApi(controller.rackController, pool)); this.emitter_ = new Emitter(); this.controller_.foldable .value("expanded") .emitter.on("change", (ev) => { this.emitter_.emit("fold", { event: new TpFoldEvent(this, ev.sender.rawValue) }); }); this.rackApi_.on("change", (ev) => { this.emitter_.emit("change", { event: ev }); }); this.rackApi_.on("update", (ev) => { this.emitter_.emit("update", { event: ev }); }); } get expanded() { return this.controller_.foldable.get("expanded"); } set expanded(expanded) { this.controller_.foldable.set("expanded", expanded); } get title() { return this.controller_.props.get("title"); } set title(title) { this.controller_.props.set("title", title); } get children() { return this.rackApi_.children; } addInput(object, key, opt_params) { return this.rackApi_.addInput(object, key, opt_params); } addMonitor(object, key, opt_params) { return this.rackApi_.addMonitor(object, key, opt_params); } addFolder(params) { return this.rackApi_.addFolder(params); } addButton(params) { return this.rackApi_.addButton(params); } addSeparator(opt_params) { return this.rackApi_.addSeparator(opt_params); } addTab(params) { return this.rackApi_.addTab(params); } add(api, opt_index) { return this.rackApi_.add(api, opt_index); } remove(api) { this.rackApi_.remove(api); } addBlade(params) { return this.rackApi_.addBlade(params); } on(eventName, handler) { const bh = handler.bind(this); this.emitter_.on(eventName, (ev) => { bh(ev.event); }); return this; } } class RackLikeController extends BladeController { constructor(config) { super({ blade: config.blade, view: config.view, viewProps: config.rackController.viewProps }); this.rackController = config.rackController; } } class PlainView { constructor(doc, config) { const className = ClassName(config.viewName); this.element = doc.createElement("div"); this.element.classList.add(className()); config.viewProps.bindClassModifiers(this.element); } } function findInputBindingController(bcs, b) { for (let i = 0; i < bcs.length; i++) { const bc = bcs[i]; if (bc instanceof InputBindingController && bc.binding === b) { return bc; } } return null; } function findMonitorBindingController(bcs, b) { for (let i = 0; i < bcs.length; i++) { const bc = bcs[i]; if (bc instanceof MonitorBindingController && bc.binding === b) { return bc; } } return null; } function findValueBladeController(bcs, v) { for (let i = 0; i < bcs.length; i++) { const bc = bcs[i]; if (bc instanceof ValueBladeController && bc.value === v) { return bc; } } return null; } function findSubRack(bc) { if (bc instanceof RackController) { return bc.rack; } if (bc instanceof RackLikeController) { return bc.rackController.rack; } return null; } function findSubBladeControllerSet(bc) { const rack = findSubRack(bc); return rack ? rack["bcSet_"] : null; } class BladeRack { constructor(blade) { var _a; this.onBladePositionsChange_ = this.onBladePositionsChange_.bind(this); this.onSetAdd_ = this.onSetAdd_.bind(this); this.onSetRemove_ = this.onSetRemove_.bind(this); this.onChildDispose_ = this.onChildDispose_.bind(this); this.onChildPositionsChange_ = this.onChildPositionsChange_.bind(this); this.onChildInputChange_ = this.onChildInputChange_.bind(this); this.onChildMonitorUpdate_ = this.onChildMonitorUpdate_.bind(this); this.onChildValueChange_ = this.onChildValueChange_.bind(this); this.onChildViewPropsChange_ = this.onChildViewPropsChange_.bind(this); this.onDescendantLayout_ = this.onDescendantLayout_.bind(this); this.onDescendantInputChange_ = this.onDescendantInputChange_.bind(this); this.onDescendantMonitorUpdate_ = this.onDescendantMonitorUpdate_.bind(this); this.emitter = new Emitter(); this.blade_ = blade !== null && blade !== void 0 ? blade : null; (_a = this.blade_) === null || _a === void 0 ? void 0 : _a .value("positions") .emitter.on("change", this.onBladePositionsChange_); this.bcSet_ = new NestedOrderedSet(findSubBladeControllerSet); this.bcSet_.emitter.on("add", this.onSetAdd_); this.bcSet_.emitter.on("remove", this.onSetRemove_); } get children() { return this.bcSet_.items; } add(bc, opt_index) { if (bc.parent) { bc.parent.remove(bc); } bc["parent_"] = this; this.bcSet_.add(bc, opt_index); } remove(bc) { bc["parent_"] = null; this.bcSet_.remove(bc); } find(controllerClass) { return forceCast(this.bcSet_.allItems().filter((bc) => { return bc instanceof controllerClass; })); } onSetAdd_(ev) { this.updatePositions_(); const isRoot = ev.target === ev.root; this.emitter.emit("add", { bladeController: ev.item, index: ev.index, isRoot: isRoot, sender: this }); if (!isRoot) { return; } const bc = ev.item; bc.viewProps.emitter.on("change", this.onChildViewPropsChange_); bc.blade .value("positions") .emitter.on("change", this.onChildPositionsChange_); bc.viewProps.handleDispose(this.onChildDispose_); if (bc instanceof InputBindingController) { bc.binding.emitter.on("change", this.onChildInputChange_); } else if (bc instanceof MonitorBindingController) { bc.binding.emitter.on("update", this.onChildMonitorUpdate_); } else if (bc instanceof ValueBladeController) { bc.value.emitter.on("change", this.onChildValueChange_); } else { const rack = findSubRack(bc); if (rack) { const emitter = rack.emitter; emitter.on("layout", this.onDescendantLayout_); emitter.on("inputchange", this.onDescendantInputChange_); emitter.on("monitorupdate", this.onDescendantMonitorUpdate_); } } } onSetRemove_(ev) { this.updatePositions_(); const isRoot = ev.target === ev.root; this.emitter.emit("remove", { bladeController: ev.item, isRoot: isRoot, sender: this }); if (!isRoot) { return; } const bc = ev.item; if (bc instanceof InputBindingController) { bc.binding.emitter.off("change", this.onChildInputChange_); } else if (bc instanceof MonitorBindingController) { bc.binding.emitter.off("update", this.onChildMonitorUpdate_); } else if (bc instanceof ValueBladeController) { bc.value.emitter.off("change", this.onChildValueChange_); } else { const rack = findSubRack(bc); if (rack) { const emitter = rack.emitter; emitter.off("layout", this.onDescendantLayout_); emitter.off("inputchange", this.onDescendantInputChange_); emitter.off("monitorupdate", this.onDescendantMonitorUpdate_); } } } updatePositions_() { const visibleItems = this.bcSet_.items.filter((bc) => !bc.viewProps.get("hidden")); const firstVisibleItem = visibleItems[0]; const lastVisibleItem = visibleItems[visibleItems.length - 1]; this.bcSet_.items.forEach((bc) => { const ps = []; if (bc === firstVisibleItem) { ps.push("first"); if (!this.blade_ || this.blade_.get("positions").includes("veryfirst")) { ps.push("veryfirst"); } } if (bc === lastVisibleItem) { ps.push("last"); if (!this.blade_ || this.blade_.get("positions").includes("verylast")) { ps.push("verylast"); } } bc.blade.set("positions", ps); }); } onChildPositionsChange_() { this.updatePositions_(); this.emitter.emit("layout", { sender: this }); } onChildViewPropsChange_(_ev) { this.updatePositions_(); this.emitter.emit("layout", { sender: this }); } onChildDispose_() { const disposedUcs = this.bcSet_.items.filter((bc) => { return bc.viewProps.get("disposed"); }); disposedUcs.forEach((bc) => { this.bcSet_.remove(bc); }); } onChildInputChange_(ev) { const bc = findInputBindingController(this.find(InputBindingController), ev.sender); if (!bc) { throw TpError.shouldNeverHappen(); } this.emitter.emit("inputchange", { bladeController: bc, options: ev.options, sender: this }); } onChildMonitorUpdate_(ev) { const bc = findMonitorBindingController(this.find(MonitorBindingController), ev.sender); if (!bc) { throw TpError.shouldNeverHappen(); } this.emitter.emit("monitorupdate", { bladeController: bc, sender: this }); } onChildValueChange_(ev) { const bc = findValueBladeController(this.find(ValueBladeController), ev.sender); if (!bc) { throw TpError.shouldNeverHappen(); } this.emitter.emit("inputchange", { bladeController: bc, options: ev.options, sender: this }); } onDescendantLayout_(_) { this.updatePositions_(); this.emitter.emit("layout", { sender: this }); } onDescendantInputChange_(ev) { this.emitter.emit("inputchange", { bladeController: ev.bladeController, options: ev.options, sender: this }); } onDescendantMonitorUpdate_(ev) { this.emitter.emit("monitorupdate", { bladeController: ev.bladeController, sender: this }); } onBladePositionsChange_() { this.updatePositions_(); } } class RackController extends BladeController { constructor(doc, config) { super(Object.assign(Object.assign({}, config), { view: new PlainView(doc, { viewName: "brk", viewProps: config.viewProps }) })); this.onRackAdd_ = this.onRackAdd_.bind(this); this.onRackRemove_ = this.onRackRemove_.bind(this); const rack = new BladeRack(config.root ? undefined : config.blade); rack.emitter.on("add", this.onRackAdd_); rack.emitter.on("remove", this.onRackRemove_); this.rack = rack; this.viewProps.handleDispose(() => { for (let i = this.rack.children.length - 1; i >= 0; i--) { const bc = this.rack.children[i]; bc.viewProps.set("disposed", true); } }); } onRackAdd_(ev) { if (!ev.isRoot) { return; } insertElementAt(this.view.element, ev.bladeController.view.element, ev.index); } onRackRemove_(ev) { if (!ev.isRoot) { return; } removeElement(ev.bladeController.view.element); } } const bladeContainerClassName = ClassName("cnt"); class FolderView { constructor(doc, config) { var _a; this.className_ = ClassName((_a = config.viewName) !== null && _a !== void 0 ? _a : "fld"); this.element = doc.createElement("div"); this.element.classList.add(this.className_(), bladeContainerClassName()); config.viewProps.bindClassModifiers(this.element); this.foldable_ = config.foldable; this.foldable_.bindExpandedClass(this.element, this.className_(undefined, "expanded")); bindValueMap(this.foldable_, "completed", valueToClassName(this.element, this.className_(undefined, "cpl"))); const buttonElem = doc.createElement("button"); buttonElem.classList.add(this.className_("b")); bindValueMap(config.props, "title", (title) => { if (isEmpty(title)) { this.element.classList.add(this.className_(undefined, "not")); } else { this.element.classList.remove(this.className_(undefined, "not")); } }); config.viewProps.bindDisabled(buttonElem); this.element.appendChild(buttonElem); this.buttonElement = buttonElem; const titleElem = doc.createElement("div"); titleElem.classList.add(this.className_("t")); bindValueToTextContent(config.props.value("title"), titleElem); this.buttonElement.appendChild(titleElem); this.titleElement = titleElem; const markElem = doc.createElement("div"); markElem.classList.add(this.className_("m")); this.buttonElement.appendChild(markElem); const containerElem = config.conta