import { LanguagePool, LanguagePoolString } from "./language.js" var counter = 0; var globalStyle = document.createElement("style"); document.head.appendChild(globalStyle); String.prototype.map = Array.prototype.map; function toCssAttr(txt) { return txt.map(d => (d.toUpperCase() === d && d !== d.toLowerCase()) ? "-" + d.toLowerCase() : d).join(""); }; class callPromise extends Promise { #opts = {}; constructor(cb) { super(cb); }; set opts(opts) { this.#opts = opts; } get opts() { return this.#opts; } } const pools = { }; const stears = []; /** Stear class */ export class Stear { elem; #childs = {}; #frames = []; /** * Creating a Stear class. It can be used to render "stear" in an Element. * * @param {HTMLElement} elem */ constructor(elem) { this.elem = elem; elem.style.position = "relative"; stears.push(this); } destroy() { let i = stears.indexOf(this); if (i == -1) return; stears.splice(i, 1); delete this; } /** * Change the Style of the render Element. * * @param {JSON} css */ style(css) { Object.entries(css).forEach(([k, d]) => { this.elem.style[k] = d; }); } /** * An Store for Frames * * @param {string} id * @param {SFrame} elem */ addElement(id, elem) { if (!(elem instanceof SFrame)) throw new TypeError("Cannot add Element not extending from SFrame"); this.#childs[id] = elem; } /** * Access an Element from the Frame Store * * @param {string} id * @returns {SFrame} */ getElement(id) { return this.g(id); } g(id) { return this.#childs[id]; } /** * Render an Element gloabal. * * @param {SFrame} elem * @param {*} args * @param {number} layer * @returns {callPromise} */ async call(elem, args, layer = 1) { return await elem.call(this, args, this.#frames, layer); } /** * Render an Element in annother Element. * * @param {SFrame} elem * @param {*} args * @param {HTMLElement} renderParent * @param {number} layer * @returns {callPromise} */ include(elem, args, renderParent, layer = 1) { return elem.call(this, args, this.#frames, layer, renderParent); } /** * Rernder everthing. * * @param {*} arg * @returns {Promise} */ rerenderGlobal(arg) { return new Promise((res, rej) => { if (this.#frames.length == 0) return void res(); let running = this.#frames.length; for (let i = 0; i < this.#frames.length; i++) { const element = this.#frames[i]; element.globalRenderRequest(arg) .then(() => { running--; if (running == 0) res(); }); } }); } /** * Add raw css Data. * * @param {string} text */ static addGlobalStyleText(text) { globalStyle.innerHTML += "\n" + text; } /** * Add a css Animation * * @param {object} steps - css like json Syntax * @param {string} name * @returns {string} name */ static addAnimation(steps, name = "stearAnimation_" + counter++) { Stear.addGlobalStyleText(`@keyframes ${name} { ${Object.entries(steps).map(([k, d]) => ` ${k} { ${Object.entries(d).map(d => " " + toCssAttr(d[0]) + ": " + d[1] + ";").join("\n")} }`).join("\n") } }`); return name; } /** * * @param {object} json - css like json Syntax * @param {string} name - e.g. .classname * @returns {string} name */ static addGlobalStyleJSON(json, name = ".stearClass_" + counter++) { Stear.addGlobalStyleText(` ${name} { ${Object.entries(json).map(d => " " + toCssAttr(d[0]) + ": " + d[1] + ";").join("\n")} } `) return name; } /** * Add A Language Pool. E.g. for a subpage. * * @param {string} name * @returns {LanguagePool} */ static addLanguagePool(name) { if (typeof pools[name] == "undefined") { pools[name] = new LanguagePool(); } return pools[name]; } /** * Add a file containing translations. * * @param {JSON} data * @param {string} lang */ static addLanguageFile(data, lang) { Object.entries(data).forEach(([k, d]) => { if (typeof pools[k] == "undefined") pools[k] = new LanguagePool(); pools[k].addFile(d, lang); }); } /** * Used Language */ static set lang(lang) { Object.entries(pools).forEach(([k, d]) => { d.lang = lang; }); stears.forEach(s => s.rerenderGlobal()); } /** * Generate Language File with defaults */ static get getLanguageFile() { var out = {}; Object.entries(pools).forEach(([k, d]) => { out[k] = d.getFile; }); return out; } } //////////////////////////////////////////////////////////////////////// //utils /** * Can be called to close a frame and return a response. * @async * @callback utilsResolveCallback * @param {*} returnValue * @param {boolean} close */ /** * Remove this Frame from Screen * @async * @callback utilsCloseCallback */ /** * Rerender the local Content * @async * @callback utilsRenderCallback * @param {...*} args * @return {objectt} find */ /** * Open and run another frame with the ability to return data. * @async * @callback utilsCallCallback * @param {SFrame} elem * @param {*} args * @return {callPromise} */ /** * Embet another Frame * @async * @callback utilsIncludeCallback * @param {SFrame} elem * @param {*} args * @return {includeFrame} */ ///////////////////////////////////////////////////////////////////////// //eventCallbacks /** * Called when Page is fully loaded. * @callback eventOnLoadedCallback */ /** * Will be called and awaited bevor resolving. * @async * @callback eventOnResolveCallback */ /** * Will be called and awaited bevor closing. * @async * @callback eventOnCloseCallback */ /** * Will be called and awaited bevor rerendering. * @async * @callback eventOnBeforRerenderCallback * @param {...*} args - any given param */ /** * Will be called and awaited after rerendering. * @async * @callback eventOnAfterRerenderCallback * @param {...*} args - any given param */ /** * Will be called when included in another Frame and that Frame rerenders. * @callback eventOnParentRenderCallback */ /** * Will be called when included in another Frame and that Frame rerenders. * @callback eventOnGlobalRenderCallback */ /** * @typedef callUtils * @type {Object} * @property {Object} find - find an element in local tree * @property {utilsResolveCallback} resolve - Return the Data * @property {utilsCloseCallback} close - Close the Frame * @property {utilsRenderCallback} render - Rerender the Frame * @property {utilsCallCallback} call - Use another Frame * @property {utilsIncludeCallback} include - Include another Frame * @property {Object} event - local Events * @property {eventOnLoadedCallback} event.onloaded * @property {eventOnResolveCallback} event.onresolve * @property {eventOnCloseCallback} event.close * @property {eventOnBeforRerenderCallback} event.onBeforRerender * @property {eventOnAfterRerenderCallback} event.onAfterRerender * @property {eventOnParentRenderCallback} event.onParentRender * @property {eventOnGlobalRenderCallback} event.onGlobalRenderRequest */ /** * This callback will be called, when a frame should be opened. * @callback callCallback * @param {Stear} stear * @param {callUtils} utils - lots of utils for Frame manipulation * @param {*} args */ /** * SFrame class */ export class SFrame { #preRender; #call; /** * * @param {Object} args * @param {callCallback} args.call * @param {boolean} [args.preRender] */ constructor({ call, preRender = true }) { this.#preRender = preRender; this.#call = call; } call(stear, args, frames, layer = 1, renderParent = stear.elem) { let lastRender; let renderElem; let event = { onloaded: () => { }, onclose: () => { }, onresolve: () => { }, onBeforRerender: () => { }, onAfterRerender: () => { }, onParentRender: () => { }, onGlobalRenderRequest: () => true }; let find = {}; let resolved = false; async function render(args) { if (!renderElem) return; if (lastRender) if ([...(renderParent.children)].includes(lastRender._)) renderParent.removeChild(lastRender._); let now = renderElem; if (typeof now == "function") now = await now(); if (!(now instanceof class_)) throw new Error("The Element to render is not an instance of class_"); await now.build(args); renderParent.appendChild(now.render); lastRender = now; now._.style.zIndex = layer; for (var member in find) delete find[member]; Object.assign(find, now.find); return lastRender; } const ppp = new callPromise(async (res, rej) => { const globalRegister = { globalRenderRequest: async (...args) => { const arg = await event.onGlobalRenderRequest(...args); if (arg === false) return; await options.render(arg); } } frames.push(globalRegister); const options = { find, render: async (...args) => { await event.onBeforRerender(...args); if (renderElem) await render(args); firstRender = true; await event.onAfterRerender(...args); return find; }, resolve: async (r, close = true) => { if (resolved) return; resolved = true; await event.onresolve(); if (close && lastRender && [...(renderParent.children)].includes(lastRender._)) renderParent.removeChild(lastRender._); let i = frames.indexOf(globalRegister); if (i > -1) frames.splice(i, 1); res(r); }, call: (elem, args = {}) => { return stear.call(elem, args, layer + 1); }, close: async () => { await event.onclose(); if (lastRender && [...(renderParent.children)].includes(lastRender._)) renderParent.removeChild(lastRender._); }, include: (frame, args = {}) => { let iFrame = new includeFrame(stear, frame, layer + 1, args); return iFrame; }, event, }; window.queueMicrotask(() => ppp.opts = options); let firstRender = false; renderElem = await this.#call(stear, options, args); if (!renderElem) { } else { if (this.#preRender | firstRender) await render([]); event.onloaded(); } }); return ppp; } } /** Create a SWindow, a Fullscreen Frame. */ export class SWindow extends SFrame { #Frame; /** * * @param {Object} args * @param {callCallback} args.call * @param {boolean} [args.preRender] */ constructor({ call, preRender = true, backgroundColor = "transparent" }) { /*var Frame = _({ style: { top: "0px", left: "0px", position: "absolute", height: "100%", width: "100%", display: "block", backgroundColor, //overflow:"scroll" }, find:"main" }, []);*/ super({ call: async (...args) => _({ style: { top: "0px", left: "0px", position: "absolute", height: "100%", width: "100%", display: "block", backgroundColor, //overflow:"scroll" }, find: "main" }, await call(...args)), preRender }); //this.#Frame = Frame; } } /** * @typedef renderNodeSettings * @type {Object} * @property {string}[type] * @property {string}[find] * @property {Object}[event] * @property {Object}[attr] */ /** Create a Render node, */ export class class_ { #elem; #childs; #build; #find; #doBuild; #dynamicState = 0; /** * Generate a new Stear render Node. * * @param {renderNodeSettings} settings * @param {Array} childs * @param {boolean} doBuild */ constructor(settings, childs, doBuild = true) { this.#elem = document.createElement(settings.type ?? "div"); this.#childs = childs; var keys = Object.keys(settings); for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (key == "type") { } else if (key == "style") { Object.entries(settings[key]).forEach(([k, d]) => { this.#elem.style[k] = d; }); } else if (key == "find") { this.#find = settings[key]; } else if (key == "event") { Object.entries(settings[key]).forEach(([k, d]) => { this.#elem.addEventListener(k, d); }); } else if (key == "attr") { Object.entries(settings[key]).forEach(([k, d]) => { this.#elem.setAttribute(k, d); }); } else { this.#elem.setAttribute(key, settings[key]); } } this.#doBuild = doBuild; } /** * @param {Array} childs */ set childs(childs) { if (!this.#doBuild) return; this.#childs = Array.isArray(childs) ? childs : [childs]; this.#dynamicState = 0; } /** * @param {renderNodeSettings} settings */ set settings(settings) { var keys = Object.keys(settings); for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (key == "type") { } else if (key == "style") { Object.entries(settings[key]).forEach(([k, d]) => { this.#elem.style[k] = d; }); } else if (key == "find") { this.#find = settings[key]; } else { this.#elem.setAttribute(key, settings[key]); } } } async #syncBuild(args) { for (let i = 0; i < this.#childs.length; i++) { let elem = this.#childs[i]; if (Array.isArray(elem)) { for (let j = 0; j < elem.length; j++) { if (elem[j] instanceof class_) await elem[j].build(args); } } else { if (elem instanceof class_) await elem.build(args); } } } /** * Build Stear Structure * * @param {*} args */ async build(args) { if (!this.#doBuild) return; if (this.#dynamicState < 0) return void await this.#syncBuild(args); this.#build = []; for (let i = 0; i < this.#childs.length; i++) { let elem = this.#childs[i]; if (typeof elem == "function") { elem = (await elem(...args)) ?? []; this.#dynamicState = 2; } if (Array.isArray(elem)) { for (let j = 0; j < elem.length; j++) { if (elem[j] instanceof class_) await elem[j].build(args); } this.#build.push(...elem); } else { if (elem instanceof class_) await elem.build(args); this.#build.push(elem); } } if (this.#dynamicState <= 0) this.#dynamicState = -1; } #syncRender() { let out = []; for (let i = 0; i < this.#build.length; i++) { const elem = this.#build[i]; if (typeof elem != "string" && !(elem instanceof LanguagePoolString)) { out[i] = elem.render; } } } /** * Genereate HTML Structure * * @return {HTMLElement} */ get render() { if (!this.#doBuild) return this.#elem; if (this.#dynamicState < -1) { this.#syncRender(); return this.#elem; } let out = []; for (let i = 0; i < this.#build.length; i++) { const elem = this.#build[i]; if (typeof elem == "string" || elem instanceof LanguagePoolString) { out[i] = document.createTextNode(String(elem)); } else { out[i] = elem.render; } } this.#elem.replaceChildren(...out); if (this.#dynamicState < 0) this.#dynamicState = -2; return this.#elem; } /** * @return {HTMLElement} */ get _() { return this.#elem; } async rerender(args = []) { await this.build(args); this.render; } /** * Returns find Object * * @return {Object} find */ get find() { var out = {}; if (this.#doBuild) { this.#build.forEach(d => { Object.assign(out, d.find); }); } if (this.#find) out[this.#find] = this; return out; } } /** * * @param {renderNodeSettings} [settings] * @param {Array} [childs] * @returns {class_} */ export const _ = (settings = {}, childs = []) => new class_(settings, Array.isArray(childs) ? childs : [childs]); export const s = (type, settings, ...childs) => { settings = settings ?? {}; settings.type = type; return new class_(settings, childs); } /** includeFrame */ class includeFrame { #elem; #call; /** * Call another Frame and generate an includeFrame. * * @param {Stear} stear * @param {SFrame} frame * @param {number} layer * @param {*} args */ constructor(stear, frame, layer, args = {}) { this.#elem = new class_({}, [], false); this.#call = stear.include(frame, args, this.#elem._, layer); this.#call.then(() => { }) .catch(() => { }); } /** * @return {callUtils} utils from included Frame */ get opts() { return this.#call.opts; } /** * Close the included Frame. */ close() { if (this.#call) { this.#call.opts.close(); } } /** * Trigger onParentRender event * * @param {*} settings * @returns {HTMLElement} */ render(settings = {}) { this.#elem.settings = settings; this.#call.opts.event.onParentRender(); return this.#elem; } };