import * as plugins from './domtools.plugins.js'; import { type TViewport } from './domtools.css.breakpoints.js'; import { Scroller } from './domtools.classes.scroller.js'; import { WebSetup } from '@push.rocks/websetup'; import { ThemeManager } from './domtools.classes.thememanager.js'; import { Keyboard } from './domtools.classes.keyboard.js'; export interface IDomToolsState { virtualViewport: TViewport; jwt: string; } export interface IDomToolsContructorOptions { ignoreGlobal?: boolean; } export class DomTools { // ====== // STATIC // ====== /** * setups domtools */ public static async setupDomTools(optionsArg: IDomToolsContructorOptions = {}) { let domToolsInstance: DomTools; if (!globalThis.deesDomTools && !optionsArg.ignoreGlobal) { globalThis.deesDomTools = new DomTools(optionsArg); domToolsInstance = globalThis.deesDomTools; // lets make sure the dom is ready const readyStateChangedFunc = () => { if (document.readyState === 'interactive' || document.readyState === 'complete') { domToolsInstance.elements.headElement = document.querySelector('head'); domToolsInstance.elements.bodyElement = document.querySelector('body'); domToolsInstance.domReady.resolve(); } }; document.addEventListener('readystatechange', readyStateChangedFunc); domToolsInstance.domToolsReady.resolve(); } else if (optionsArg.ignoreGlobal) { domToolsInstance = new DomTools(optionsArg); } else { domToolsInstance = globalThis.deesDomTools; } await domToolsInstance.domToolsReady.promise; return domToolsInstance; } /** * if you can, use the static asysnc .setupDomTools() function instead since it is safer to use. */ public static getGlobalDomToolsSync(): DomTools { const globalDomTools: DomTools = globalThis.deesDomTools; if (!globalDomTools) { throw new Error('You tried to access domtools synchronously too early'); } return globalThis.deesDomTools; } // ======== // INSTANCE // ======== // elements public elements: { headElement: HTMLElement; bodyElement: HTMLElement; } = { headElement: null, bodyElement: null, }; public websetup: WebSetup = new WebSetup({ metaObject: { title: 'loading...', }, }); public smartstate = new plugins.smartstate.Smartstate(); public domToolsStatePart = this.smartstate.getStatePart('domtools', { virtualViewport: 'native', jwt: null, }); public router = new plugins.smartrouter.SmartRouter({ debug: false, }); public convenience = { typedrequest: plugins.typedrequest, smartdelay: plugins.smartdelay, smartjson: plugins.smartjson, smarturl: plugins.smarturl, }; public deesComms = new plugins.deesComms.DeesComms(); public scroller = new plugins.SweetScroll({ /* some options */ }); // TODO: switch to scroller class public themeManager = new ThemeManager(this); public keyboard = new Keyboard(document.body); public domToolsReady = plugins.smartpromise.defer(); public domReady = plugins.smartpromise.defer(); public globalStylesReady = plugins.smartpromise.defer(); constructor(optionsArg: IDomToolsContructorOptions) {} private runOnceTrackerStringMap = new plugins.lik.Stringmap(); private runOnceResultMap = new plugins.lik.FastMap(); /** * run a function once and always get the Promise of the first execution * @param identifierArg the indentifier arg identifies functions. functions with the same identifier are considered equal * @param funcArg the actual func arg to run */ public async runOnce(identifierArg: string, funcArg: () => Promise) { const runningId = `${identifierArg}+runningCheck`; if (!this.runOnceTrackerStringMap.checkString(identifierArg)) { this.runOnceTrackerStringMap.addString(identifierArg); this.runOnceTrackerStringMap.addString(runningId); const result = await funcArg(); this.runOnceResultMap.addToMap(identifierArg, result); this.runOnceTrackerStringMap.removeString(runningId); } return await this.runOnceTrackerStringMap.registerUntilTrue( (stringMap) => { return !stringMap.includes(runningId); }, () => { return this.runOnceResultMap.getByKey(identifierArg); } ); } // setStuff /** * allows to set global styles * @param stylesText the css text you want to set */ public async setGlobalStyles(stylesText: string) { await this.domReady.promise; const styleElement = document.createElement('style'); styleElement.type = 'text/css'; styleElement.appendChild(document.createTextNode(stylesText)); this.elements.headElement.appendChild(styleElement); } /** * allows to set global styles * @param stylesText the css text you want to set */ public async setExternalScript(scriptLinkArg: string) { await this.domReady.promise; const done = plugins.smartpromise.defer(); const script = document.createElement('script'); script.src = scriptLinkArg; script.addEventListener('load', function () { done.resolve(); }); const parentNode = document.head || document.body; parentNode.append(script); await done.promise; } /** * allows setting external css files * @param cssLinkArg a url to an external stylesheet */ public async setExternalCss(cssLinkArg: string) { const cssTag = document.createElement('link'); cssTag.rel = 'stylesheet'; cssTag.crossOrigin = 'anonymous'; cssTag.href = cssLinkArg; document.head.append(cssTag); } /** * allows setting of website infos * @param optionsArg the website info */ public async setWebsiteInfo(optionsArg: plugins.websetup.IWebSetupConstructorOptions) { await this.websetup.setup(optionsArg); await this.websetup.readyPromise; } }