let WINDOWS: { [key: string]: _winConf } = {}; let MOUSE_MOVE_PROCESSING: { [key: string]: { callback: (x: number, y: number) => void, mouseUp: boolean } } = {}; let globalIncrement: number = 1; function escapeHTML(string: string): string { return string.replaceAll("&", "&").replaceAll("<", "<").replaceAll("\"", """); } function incrementZIndex(windowID: string, focus: boolean=false): void { WINDOWS[windowID].element.style.zIndex = String(globalIncrement); WINDOWS[windowID].zIndex = globalIncrement; globalIncrement++; if (focus) { WINDOWS[windowID].element.focus(); } } function createWindow(config: _winInitConf): void { if (document.getElementById(config.id)) { incrementZIndex(config.id); return; } // 1 - border // 10 - padding // 35 - header let _windowPaddingX: number = 1*2 + 10*2; let _windowPaddingY: number = 1*2 + 10*2 + 35; config.width = config.width || 600; config.height = config.height || 400; config.minWidth = config.minWidth || 200; config.minHeight = config.minHeight || 200; let realWidth: number = Math.max(config.minWidth, Math.min(config.width, innerWidth - _windowPaddingX - 20)); let realHeight: number = Math.max(config.minHeight, Math.min(config.height, innerHeight - _windowPaddingY - 20)); let posX: number = config.posX || Math.round((innerWidth / 2) - ((realWidth + _windowPaddingX) / 2)); let posY: number = config.posY || Math.round((innerHeight / 2) - ((realHeight + _windowPaddingY) / 2)); let wO: HTMLDivElement = document.createElement("div"); wO.classList.add("window-outer"); let w: HTMLDivElement = document.createElement("div"); w.classList.add("window"); w.style.width = `${realWidth}px`; w.style.height = `${realHeight}px`; w.innerHTML = config.content; let wH: HTMLDivElement = document.createElement("div"); wH.classList.add("window-header"); wH.innerHTML = ` ${config.title} `; let wC: HTMLDivElement | HTMLLabelElement; let wI: HTMLInputElement = null; if (config.typeable !== false) { function syncInputs(): void { setTimeout(function(): void { let text: string = wI.value; let cursor: number = wI.selectionStart; let el: HTMLElement = wC.querySelector("[data-type-area]"); if (!el) { return; } if (cursor == text.length) { el.innerHTML = `${escapeHTML(text)} `; } else { el.innerHTML = `${escapeHTML(text.slice(0, cursor))}${escapeHTML(text[cursor])}${escapeHTML(text.slice(cursor + 1))}`; } }, 1); } function setCursor(): void { setTimeout((): void => { wI.setSelectionRange(wI.value.length, wI.value.length); syncInputs(); }, 0); } wI = document.createElement("input"); wI.classList.add("window-input"); wI.id = `${config.id}__input`; wI.oninput = (event: KeyboardEvent): void => { syncInputs(); w.scrollTop = w.scrollHeight; }; wI.onkeydown = (event: KeyboardEvent): void => { if (event.key == "Enter") { commandManager(config.id, wI.value.trim()); w.scrollTop = w.scrollHeight; wI.value = ""; } else { syncInputs(); } }; wI.onfocus = setCursor; wI.onclick = setCursor; wC = document.createElement("label"); wC.htmlFor = `${config.id}__input`; } else { wC = document.createElement("div"); } wC.classList.add("window-container"); wC.tabIndex = 0; wC.style.left = `${posX}px`; wC.style.top = `${posY}px`; wC.id = config.id; wC.style.zIndex = String(globalIncrement); wC.style.width = `${realWidth + _windowPaddingX - 2}px`; wO.append(w); wC.append(wH, wO); document.body.append(wC); if (config.typeable !== false) { wC.append(wI); wI.focus(); } else { wC.focus(); } WINDOWS[config.id] = { element: wC, height: realHeight, width: realWidth, posX: posX, posY: posY, fullscreen: false, zIndex: globalIncrement, vars: {} }; function mouseMoveEvent(x: number, y: number): void { WINDOWS[config.id].posX = Math.max(0, Math.min(innerWidth - WINDOWS[config.id].width - _windowPaddingX, x - WINDOWS[config.id].vars.mouseOffsetX)); WINDOWS[config.id].posY = Math.max(0, Math.min(innerHeight - WINDOWS[config.id].height - _windowPaddingY, y - WINDOWS[config.id].vars.mouseOffsetY)); wC.style.left = `${WINDOWS[config.id].posX}px`; wC.style.top = `${WINDOWS[config.id].posY}px`; } // wC.addEventListener("mousedown", function(): void { incrementZIndex(config.id); }); wC.addEventListener("focus", function(): void { incrementZIndex(config.id); }); for (const link of wC.querySelectorAll("a")) { link.addEventListener("focus", function(): void { incrementZIndex(config.id); }); } wH.addEventListener("mousedown", function(e: MouseEvent): void { if ((e.target as HTMLElement).dataset.noMove !== undefined) { return; } WINDOWS[config.id].vars.mouseOffsetX = e.clientX - WINDOWS[config.id].posX; WINDOWS[config.id].vars.mouseOffsetY = e.clientY - WINDOWS[config.id].posY; MOUSE_MOVE_PROCESSING[config.id] = { callback: mouseMoveEvent, mouseUp: true }; }); wH.querySelector(".close").addEventListener("click", function(): void { delete WINDOWS[config.id]; delete windowInformation[config.id]; delete MOUSE_MOVE_PROCESSING[config.id]; wC.remove(); if (typeof config.onDestroy === "function") { config.onDestroy(); } }); wH.querySelector(".fullscreen").addEventListener("click", function(): void { if (WINDOWS[config.id].fullscreen) { WINDOWS[config.id].posX = WINDOWS[config.id].vars.oldPosX; WINDOWS[config.id].posY = WINDOWS[config.id].vars.oldPosY; WINDOWS[config.id].width = WINDOWS[config.id].vars.oldWidth; WINDOWS[config.id].height = WINDOWS[config.id].vars.oldHeight; WINDOWS[config.id].fullscreen = false; delete WINDOWS[config.id].vars.oldPosX; delete WINDOWS[config.id].vars.oldPosY; delete WINDOWS[config.id].vars.oldWidth; delete WINDOWS[config.id].vars.oldHeight; wC.style.left = `${WINDOWS[config.id].posX}px`; wC.style.top = `${WINDOWS[config.id].posY}px`; wC.style.width = `${WINDOWS[config.id].width + _windowPaddingX - 2}px`; w.style.width = `${WINDOWS[config.id].width}px`; w.style.height = `${WINDOWS[config.id].height}px`; } else { WINDOWS[config.id].vars.oldPosX = WINDOWS[config.id].posX; WINDOWS[config.id].vars.oldPosY = WINDOWS[config.id].posY; WINDOWS[config.id].vars.oldWidth = WINDOWS[config.id].width; WINDOWS[config.id].vars.oldHeight = WINDOWS[config.id].height; WINDOWS[config.id].fullscreen = true; WINDOWS[config.id].posX = 0; WINDOWS[config.id].posY = 0; WINDOWS[config.id].width = innerWidth; WINDOWS[config.id].height = innerHeight; wC.style.left = "0px"; wC.style.top = "0px"; wC.style.width = `${WINDOWS[config.id].width}px`; w.style.width = `${WINDOWS[config.id].width - _windowPaddingX}px`; w.style.height = `${WINDOWS[config.id].height - _windowPaddingY}px`; } }); globalIncrement++; } window.addEventListener("mousemove", function(e: MouseEvent): void { for (const key of Object.keys(MOUSE_MOVE_PROCESSING)) { MOUSE_MOVE_PROCESSING[key].callback(e.clientX, e.clientY); } }); window.addEventListener("mouseup", function(): void { for (const key of Object.keys(MOUSE_MOVE_PROCESSING)) { if (MOUSE_MOVE_PROCESSING[key].mouseUp) { delete MOUSE_MOVE_PROCESSING[key]; }; } }); function windowPreset(template: string): void { let el: HTMLElement = document.querySelector(`#window-templates > [data-template-id="${template}"]`); if (!el) { return; } if (WINDOWS[template]) { incrementZIndex(template, true); return; } let config: _winInitConf = { id: template, title: "~ - tSh", content: "