UNPKG

sunshine-layx

Version:

A magical window.

603 lines (496 loc) 23.6 kB
import App from "../core/App"; import UIControl from "../basic/interfaces/UIControl"; import UIComponent from "../basic/models/UIComponent"; import UIParclose from "./UIParclose"; import UIResizeBar from "./UIResizeBar"; import UIToolBar from "./UIToolBar"; import UIActionButton from "./UIActionButton"; import UIContextMenuBar from "./UIContextMenuBar"; import UIActionBar from "./UIActionBar"; import UITopMenuBar from "./UITopMenuBar"; import UISalverBar from "./UISalverBar"; import UIContent from "./UIContent"; import UIStatuBar from "./UIStatuBar"; import * as Types from "../../types"; import * as Enums from "../basic/enums"; import * as ElementHelper from "../utils/ElementHelper"; import * as CastHelper from "../utils/CastHelper"; import * as TypeHelper from "../utils/TypeHelper"; import * as ExceptionHelper from "../utils/ExceptionHelper"; import * as StringHelper from "../utils/StringHelper"; export default class UIWindow extends UIComponent implements UIControl { public readonly elementId: string; public zIndex: number = this.app.zIndex; public enableAnimated: boolean = false; public status: Enums.WindowStatus = Enums.WindowStatus.NORMAL; public lastStatus: Enums.WindowStatus = Enums.WindowStatus.NONE; private flickering: boolean = false; public readonly id: string; public width: number = 800; public height: number = 600; public maxWidth: number = innerWidth; public maxHeight: number = innerHeight; public minWidth: number = 200; public minHeight: number = 200; public left: number; public top: number; public background: string | null = "#ffffff"; public shadow: string | null = "rgba(0, 0, 0, 0.3) 1px 1px 24px"; public parclose: number | false = false; public mode: Enums.WindowMode = Enums.WindowMode.LAYER; public border: string | null = null; public borderRadius: string | null = null; public animate: Enums.WindowAnimate = Enums.WindowAnimate.ZOOM; public resizeBar: Types.ResizeOption | false = {}; public toolBar: Types.ToolBarOption | false = {}; public contextMenu: Array<Types.ContextMenuButtonOption> | false = false; public topMenu: Array<Types.ContextMenuButtonOption> | false = false; public content: Types.ContentOption | false = {}; public statuBar: Types.StatuBarOption | false = false; public storeStatus: boolean = true; private _element: HTMLElement | null = null; get element() { return document.getElementById(this.elementId); } private _flickerShadow: string | null = null; get flickerShadow() { return this.getFlickerShadow(); } private _lastStoreStatus: Types.StoreStatus | null = null; get lastStoreStatus() { const storeStatus = localStorage.getItem(this.app.prefix + this.id); if (!storeStatus) return null; return eval("(" + storeStatus + ")"); } set lastStoreStatus(value: Types.StoreStatus | null) { localStorage.setItem(this.app.prefix + this.id, JSON.stringify(value)); } constructor(app: App, options: Types.WindowOption) { super(app); if (!TypeHelper.isStringWithNotEmpty(options.id)) ExceptionHelper.assertId(); this.id = options.id; this.elementId = this.app.prefix + this.id; this.mode = CastHelper.windowModeCast(options.mode, this.mode); this.width = CastHelper.numberCast(options.width, this.width); this.height = CastHelper.numberCast(options.height, this.height); this.maxWidth = Math.min(CastHelper.numberCast(options.maxWidth, this.maxWidth), this.maxWidth); this.maxHeight = Math.min(CastHelper.numberCast(options.maxHeight, this.maxHeight), this.maxHeight); this.minWidth = Math.max(CastHelper.numberCast(options.minWidth, this.minWidth), this.minWidth); this.minHeight = Math.max(CastHelper.numberCast(options.minHeight, this.minHeight), this.minHeight); this.width = Math.max(this.minWidth, this.width); this.width = Math.min(this.maxWidth, this.width); this.height = Math.max(this.minHeight, this.height); this.height = Math.min(this.maxHeight, this.height); [this.left, this.top] = CastHelper.offsetCast(options.offset, this.width, this.height); this.background = CastHelper.stringOrBooleanStyleCast(options.background, this.background); this.shadow = CastHelper.stringOrBooleanStyleCast(options.shadow, this.shadow); this.parclose = CastHelper.typeOrBooleanCast(options.parclose, this.parclose, 0); [this.border, this.borderRadius] = CastHelper.borderCast(options.border, { width: 1, style: "solid", color: "#3baced", radius: 4 }); this.animate = CastHelper.windowAnimateCast(options.animate, this.animate); this.enableAnimated = this.animate !== Enums.WindowAnimate.NONE; this.resizeBar = CastHelper.jsonOrBooleanCast(options.resizeBar, this.resizeBar); this.toolBar = CastHelper.jsonOrBooleanCast(options.toolBar, this.toolBar); this.contextMenu = CastHelper.contextMenuButtonsCast(options.contextMenu); this.topMenu = CastHelper.contextMenuButtonsCast(options.topMenu); this.content = CastHelper.jsonOrBooleanCast(options.content, this.content); this.statuBar = CastHelper.jsonOrBooleanCast(options.statuBar, this.statuBar); this.storeStatus = CastHelper.booleanCast(options.storeStatus, this.storeStatus); } present(): DocumentFragment { const fragment = ElementHelper.createFragment(); const windowElement = ElementHelper.createElement("div"); windowElement.id = this.elementId; windowElement.setAttribute("data-window-id", this.id); ElementHelper.addClasses(windowElement, this.app.prefix, Enums.ComponentType.WINDOW, `window-${this.mode}`, "flexbox", "flex-column", this.enableAnimated ? "animate" : "", this.enableAnimated ? `animate-${this.animate}-show` : "" ); this.readStoreStatus(); ElementHelper.addStyles(windowElement, <Types.CSSStyleObject>{ zIndex: this.mode === Enums.WindowMode.LAYER ? `${this.zIndex}` : null, maxWidth: `${this.maxWidth}px`, maxHeight: `${this.maxHeight}px`, minWidth: `${this.minWidth}px`, minHeight: `${this.minHeight}px`, width: `${this.width}px`, height: `${this.height}px`, top: this.mode === Enums.WindowMode.LAYER ? `${this.top}px` : null, left: this.mode === Enums.WindowMode.LAYER ? `${this.left}px` : null, background: this.background, border: this.border, borderRadius: this.borderRadius, webkitBorderRadius: this.borderRadius, boxShadow: this.shadow, webkitBoxShadow: this.shadow }); windowElement.addEventListener("mousedown", (ev: MouseEvent) => { this.updateZIndex(); }, true); if (this.toolBar !== false) { const toolBar = new UIToolBar(this.app, this, this.toolBar); const toolBarElement = toolBar.present(); windowElement.appendChild(toolBarElement); this.setComponent(Enums.ComponentType.TOOL_BAR, toolBar); } if (this.topMenu !== false) { const topMenuBar = new UITopMenuBar(this.app, this, this.topMenu); const topMenuBarElement = topMenuBar.present(); windowElement.appendChild(topMenuBarElement); this.setComponent(Enums.ComponentType.TOP_MENU_BAR, topMenuBar); } if (this.content !== false) { const content = new UIContent(this.app, this, this.content); const contentElement = content.present(); windowElement.appendChild(contentElement); this.setComponent(Enums.ComponentType.CONTENT_CONTAINER, content); } if (this.resizeBar !== false) { const resizeBar = new UIResizeBar(this.app, this, this.resizeBar); const resizeElement = resizeBar.present(); windowElement.appendChild(resizeElement); this.setComponent(Enums.ComponentType.RESIZE_BAR, resizeBar); } if (this.statuBar !== false) { const statuBar = new UIStatuBar(this.app, this, this.statuBar); const statuBarElement = statuBar.present(); windowElement.appendChild(statuBarElement); this.setComponent(Enums.ComponentType.STATU_BAR, statuBar); } if (this.parclose !== false) { const parclose = new UIParclose(this.app, this, { opacity: this.parclose }); const parcloseElement = parclose.present(); fragment.appendChild(parcloseElement); this.setComponent(Enums.ComponentType.PARCLOSE, parclose); if (this.app.salver) { this.app.salver.parsecloseCount++; } } if (this.contextMenu !== false) { const contextMenuBar = new UIContextMenuBar(this.app, this, "window", this.contextMenu); const contextMenuBarElement = contextMenuBar.present(); fragment.appendChild(contextMenuBarElement); this.setComponent(Enums.ComponentType.CONTEXT_MENU_BAR, contextMenuBar); } this.bindEvent(windowElement); if (!this.app.salver) { const salverBar = new UISalverBar(this.app); const salverBarElement = salverBar.present(); fragment.appendChild(salverBarElement); this.app.salver = salverBar; } fragment.appendChild(windowElement); return fragment; } private readStoreStatus(): void { const lastStoreStatus = this.lastStoreStatus; if (this.storeStatus) { if (lastStoreStatus) { this.width = lastStoreStatus.width; this.height = lastStoreStatus.height; this.top = lastStoreStatus.top; this.left = lastStoreStatus.left; } else { this.lastStoreStatus = <Types.StoreStatus>{ top: this.top, left: this.left, width: this.width, height: this.height }; } } } private handlerContentByAnimate(isFadeOut: boolean = true) { const content = this.getComponent<UIContent>(Enums.ComponentType.CONTENT_CONTAINER); if (content) { if (isFadeOut) { ElementHelper.addClasses(content.element, this.app.prefix, `${Enums.ComponentType.CONTENT_CONTAINER}-fade-out` ); } else { ElementHelper.removeClasses(content.element, this.app.prefix, `${Enums.ComponentType.CONTENT_CONTAINER}-fade-out` ); } } } private bindEvent(windowElement: HTMLElement): void { if (this.contextMenu !== false) { windowElement.addEventListener("contextmenu", (ev: MouseEvent) => { ev.preventDefault(); ev.returnValue = false; const contextMenuBar = this.getComponent<UIContextMenuBar>(`${Enums.ComponentType.CONTEXT_MENU_BAR}`); contextMenuBar && contextMenuBar.updateOffset(ev, this.zIndex + 1); return false; }); } windowElement.addEventListener("animationstart", (ev: AnimationEvent) => { this.handlerContentByAnimate(); }); windowElement.addEventListener("animationend", (ev: AnimationEvent) => { const element = this.element; ElementHelper.removeClasses(element, this.app.prefix, `animate-${this.animate}-show`, `animate-${this.animate}-drag-to-normal` ); if (ElementHelper.containClass(element, this.app.prefix, `animate-${this.animate}-destroy`)) this.remove(); if (ElementHelper.containClass(element, this.app.prefix, `animate-${this.animate}-to-min`)) this.minimize(); this.handlerContentByAnimate(false); }); windowElement.addEventListener("transitionend", (ev: TransitionEvent) => { ElementHelper.removeClasses(this.element, this.app.prefix, `animate-${this.animate}-to-max`, `animate-${this.animate}-to-normal` ); this.handlerContentByAnimate(false); }); } destroy(): void { if (this.enableAnimated) { ElementHelper.addClasses(this.element, this.app.prefix, `animate-${this.animate}-destroy` ); } else this.remove(); } private remove(): void { if (this.status === Enums.WindowStatus.MAX) { ElementHelper.removeClasses(document.body, `z${this.app.prefix}`, "body-noscroll" ); } const parclose = this.getComponent<UIParclose>(Enums.ComponentType.PARCLOSE); if (parclose) { ElementHelper.removeElement(parclose.element); if (this.app.salver) { this.app.salver.parsecloseCount--; } } if (this.app.salver) this.app.salver.removeItem(); const index = this.app.windows.indexOf(this); this.app.windows.splice(index, 1); this.app.window = null; ElementHelper.removeElement(this.element); } normal(dragToNormal: boolean = false): void { const windowElement = this.element; if (!windowElement || !windowElement.parentElement || this.status === Enums.WindowStatus.NORMAL) return; this.lastStatus = this.status; this.status = Enums.WindowStatus.NORMAL; this.handlerContentByAnimate(); ElementHelper.removeClasses(document.body, `z${this.app.prefix}`, "body-noscroll" ); ElementHelper.addClasses(windowElement, this.app.prefix, this.enableAnimated ? ( dragToNormal === false ? `animate-${this.animate}-to-normal` : `animate-${this.animate}-drag-to-normal` ) : "" ); ElementHelper.addStyles(windowElement, <Types.CSSStyleObject>{ top: `${this.top}px`, left: `${this.left}px`, width: `${this.width}px`, height: `${this.height}px`, borderRadius: this.borderRadius }); const resizeBar = this.getComponent<UIResizeBar>(Enums.ComponentType.RESIZE_BAR); if (resizeBar) { ElementHelper.removeClasses(resizeBar.element, this.app.prefix, "resize-bar-disabled" ); } const actionButtons = this.getComponent<Array<UIActionButton>>(` ${Enums.ComponentType.TOOL_BAR} /${Enums.ComponentType.ACTION_BAR} /${Enums.ComponentType.ACTION_BUTTONS}`); if (!actionButtons || actionButtons.length === 0) return; for (const item of actionButtons) { if (item.id !== "max") continue; const restoreActionButton = new UIActionButton(this.app, this, UIActionButton.restore); const restoreActionButtonElement = restoreActionButton.element; if (!(restoreActionButtonElement && restoreActionButtonElement.parentElement)) return; const maxActionButton = new UIActionButton(this.app, this, item); const maxActionButtonElement = maxActionButton.present(); restoreActionButtonElement.parentElement.replaceChild(maxActionButtonElement, restoreActionButtonElement); break; } this.zoomActionButtons(this.width); } max(): void { const windowElement = this.element; if (!windowElement || !windowElement.parentElement || this.status === Enums.WindowStatus.MAX) return; this.handlerContentByAnimate(); this.lastStatus = this.status; this.status = Enums.WindowStatus.MAX; ElementHelper.addClasses(document.body, `z${this.app.prefix}`, "body-noscroll" ); ElementHelper.addClasses(windowElement, this.app.prefix, this.enableAnimated ? `animate-${this.animate}-to-max` : "" ); ElementHelper.addStyles(windowElement, <Types.CSSStyleObject>{ top: `0`, left: `0`, width: `${innerWidth}px`, height: `${innerHeight}px`, borderRadius: `0` }); const resizeBar = this.getComponent<UIResizeBar>(Enums.ComponentType.RESIZE_BAR); if (resizeBar) { ElementHelper.addClasses(resizeBar.element, this.app.prefix, "resize-bar-disabled" ); } const actionButtons = this.getComponent<Array<UIActionButton>>(` ${Enums.ComponentType.TOOL_BAR} /${Enums.ComponentType.ACTION_BAR} /${Enums.ComponentType.ACTION_BUTTONS}`); if (!actionButtons || actionButtons.length === 0) return; for (const item of actionButtons) { if (item.id !== "max") continue; const maxActionButtonElement = item.element; if (!(maxActionButtonElement && maxActionButtonElement.parentElement)) return; const restoreActionButton = new UIActionButton(this.app, this, UIActionButton.restore); const restoreActionButtonElement = restoreActionButton.present(); maxActionButtonElement.parentElement.replaceChild(restoreActionButtonElement, maxActionButtonElement); break; } this.zoomActionButtons(innerWidth); } min(): void { const windowElement = this.element; if (!windowElement || this.status === Enums.WindowStatus.MIN) return; if (this.parclose !== false) { this.flicker(); } else { if (this.enableAnimated) { ElementHelper.addClasses(this.element, this.app.prefix, `animate-${this.animate}-to-min` ); } else this.minimize(); } } private minimize(): void { const windowElement = this.element; ElementHelper.addClasses(windowElement, this.app.prefix, "window-min" ); if (this.enableAnimated) { ElementHelper.removeClasses(windowElement, this.app.prefix, `animate-${this.animate}-to-min` ); } if (this.app.salver) this.app.salver.addOrUpdateItem(); this.lastStatus = this.status; this.status = Enums.WindowStatus.MIN; } flicker() { if (this.element && this.mode === Enums.WindowMode.LAYER && this.flickering === false) { let flickerTimes: number = 0; const duration: number = 60; const flickerTotals = 12; this.flickering = true; for (let i = 0; i < flickerTotals; i++) { if (i % 2 == 0) { setTimeout(() => { ElementHelper.addStyles(this.element, <Types.CSSStyleObject>{ boxShadow: this.flickerShadow, webkitBoxShadow: this.flickerShadow }); flickerTimes++; }, i * duration); } else { setTimeout(() => { ElementHelper.addStyles(this.element, <Types.CSSStyleObject>{ boxShadow: this.shadow, webkitBoxShadow: this.shadow }); flickerTimes++; }, i * duration); } } const checkFlickering = setInterval(() => { if (flickerTimes >= flickerTotals) { clearInterval(checkFlickering); this.flickering = false; } }, duration); } } private showThis(windowElement?: HTMLElement | null) { if (this.status === Enums.WindowStatus.MIN) { this.handlerContentByAnimate(true); windowElement = windowElement || this.element; ElementHelper.removeClasses(windowElement, this.app.prefix, `window-min` ); ElementHelper.addClasses(windowElement, this.app.prefix, this.enableAnimated ? `animate-${this.animate}-show` : "" ); if (this.enableAnimated) { ElementHelper.addClasses(windowElement, this.app.prefix, `animate-${this.animate}-show` ); } ([this.status, this.lastStatus] = StringHelper.exchangeValue(this.status, this.lastStatus)); } } updateZIndex(): void { if (this === this.app.window) { this.showThis(); return; }; const windowElement = this.element; if (this.mode === Enums.WindowMode.LAYER) { this.showThis(windowElement); this.zIndex = this.app.zIndex; ElementHelper.addStyles(windowElement, <Types.CSSStyleObject>{ zIndex: `${this.zIndex}` }); const parclose = this.getComponent<UIParclose>(Enums.ComponentType.PARCLOSE); parclose && parclose.updateZIndex(this.zIndex - 1); this.app.lastWindow = this.app.window; this.app.window = this; this.app.salver && this.app.salver.addOrUpdateItem(); } } hideMoreActionContextMenu(): void { const moreActionContextMenuBar = this.getComponent<UIContextMenuBar>(Enums.ComponentType.MORE_ACTION_CONTEXT_MENU_BAR); if (!moreActionContextMenuBar) return; ElementHelper.removeClasses(moreActionContextMenuBar.element, this.app.prefix, "context-menu-bar-active" ); } removeMoreActionContextMenu(): void { const moreActionButton = new UIActionButton(this.app, this, UIActionButton.more); const moreActionButtonElement = moreActionButton.element; ElementHelper.removeElement(moreActionButtonElement); } zoomActionButtons(windowWidth: number): void { const actionBar = this.getComponent<UIActionBar>(` ${Enums.ComponentType.TOOL_BAR} /${Enums.ComponentType.ACTION_BAR}`); actionBar && actionBar.zoomActionButtons(windowWidth); } private getFlickerShadow() { if (!this.shadow) return this.shadow; const shadowArray = this.shadow.split(" "); shadowArray[shadowArray.length - 1] = Number(shadowArray[shadowArray.length - 1].replace("px", "")) / 2 + "px"; return shadowArray.join(" "); } }