import { UserInterface , Components , useState , State , Controller } from "thoriumjs"; import RouteRecognizer , { Results , Result } from 'route-recognizer'; import { doesNotMatch } from "assert"; const {ElementUI} = UserInterface; const [defaultNavigation,setDefaltNavigation] = useState<'navigate'|'action'>('navigate'); export const URILocation = new class URLocation{ /** liste des routeurs */ #_routers:Map, (value: RouterViewController) => RouterViewController]> = new Map(); /** rtourne la liste des routeurs */ get routers(){return this.#_routers}; #_URIRouter:Map = new Map(); get uriRouters(){return this.#_URIRouter}; setRouterState = (options:RouterViewOptionsInit):[State , (router:RouterViewController) => void] => { this.#_routers.set(options.id , useState(null)); const [router] = this.#_routers.get(options.id); // alert('setRouterState') this.#_URIRouter.set(options.id , new RouteRecognizer()); this.#_URIRouter.get(options.id).add(Array.from( Object.keys(options.paths) , (path) => { // console.warn('setRouterPath' , `/${options.id}${path}`) return { path : path , handler : () => { // alert(`path : ${router} handler`); return router.value; } } } )) return this.#_routers.get(options.id); }; getRouterState = (routerId:string) => { // console.log('getRouterState') return ( this.#_routers.has(routerId) ? this.#_routers.get(routerId) : null ) }; deleteRouterState = (mutations:MutationRecord[]) => { const routerId = (mutations[0].removedNodes[0] as HTMLElement).id; console.log('deleteRouterState') console.log(mutations); // this.#_routers.delete(routerId); // this.#_unWatchRouter(routerId) } getCorrespondance(routerId):[string,() => void]{ const split_hash = this.hash.split('#').filter((x) => x); const find = Array.from( split_hash , (hash,iterator) => { // console.warn('recognize :',`/${routerId}${hash}`); const results = this.#_URIRouter.get(routerId).recognize(hash); // console.log('results',results); if(results && results.length > 0)return [split_hash[iterator],results] else return null; } ).filter((x) => x)[0] as [string,Results]; // alert('find'); console.log('find',find); if(find){ let [path,results] = find; console.log(results); return (results && results.length > 0 ? [ path , results[0].handler as () => void] : null); } else return null; } get hash():string {return location.hash;} set hash(value:string){ setDefaltNavigation('action'); window.location.hash = value; setDefaltNavigation('navigate'); } get host():string {return location.host;} get hostname():string {return location.hostname;} get href():string {return location.href;} get origin():string {return location.origin;} get pathname():string {return location.pathname;} get port():string {return location.port;} get protocol():string {return location.protocol;} onPathChange = (e?:Event) => { const hash = Array.from( document.querySelectorAll('router') , (key) => { // let state = this.#_routers.get(key); // if(state){ // let [router] = state; // console.log(router , router.value && !router.value.ownerDocument.contains(router.value as HTMLElement)) // if(router.value && !router.value.ownerDocument.contains(router.value as HTMLElement)){ // // this.#_routers.delete(key); // // this.#_URIRouter.delete(key) // }else {return router.value.getAttribute('path');} // } return key.getAttribute('path') } ).filter((x) => x).join('#'); // alert(`${this.hash} - ${hash}`); // console.log(this.#_routers) this.hash = hash; } constructor(){ // window.addEventListener('popstate', (e) => { // if(defaultNavigation.value == "navigate")Array.from( [...this.#_routers.keys()] , (key) => { // alert(`popstate`); // const [router] = this.routers.get(key); // const correspondance = this.getCorrespondance(key); // console.log(correspondance) // if(correspondance){ // let [p] = correspondance; // if(router.value.getAttribute('path') != p )router.value.setAttribute('path',p); // } // } ) // }); } }() // console.log(URILocation); export class RouterButtonLink extends Components.Button{ constructor(options:RouterLinkOptionsInit){ super({ prop : { name : 'buttonLink', ...(options.text ? {text : options.text} : {}) }, proto : { onMouseDown(){ // alert() if(options.id)document.querySelectorAll(`router[id="${options.id}"]`)[0].setAttribute('path',options.link) else this.context('router').setAttribute('path',options.link); } } }) } } export interface RouterLinkOptionsInit{ link:string; text?:string; id?:string; } export class RouterLink extends Components.Link{ constructor(options:RouterLinkOptionsInit){ super({ prop : { name : 'link', ...(options.text ? {text : options.text} : {}) }, proto : { onMouseDown(){ // alert() if(options.id)document.querySelectorAll(`router[id="${options.id}"]`)[0].setAttribute('path',options.link) else this.context('router').setAttribute('path',options.link); } } }) } } export interface RouterViewController extends Controller{ // onPathChange:(even:Event)=>void; } export interface RouterViewOptionsInit{ id?:string; paths? : Record<'/' | string, typeof ElementUI> >; defaultPath?:string; onPathError?:typeof ElementUI>; onPathChange?:(e:MutationRecord)=>void; } /** * Router allows a path to be assigned a component, when the 'path' attribute of the router element is modified, the router will try to render the element linked to the router path. */ export class RouterView extends UserInterface.ElementUI{ constructor(options:RouterViewOptionsInit){ if(!options.defaultPath)options.defaultPath = '/'; const [thisRouter,setRouter] = URILocation.setRouterState(options); /** * ### captureCorrespondance * @param path * @returns (string | true)[] * - Description : Determines whether the current path matches an existing key and returns the match in the case of a key with a dynamic format. */ const captureCorrespondance = (path) => { const isDynamicPath = (path:string) => { return Array.from(path.split('/') , (str) => { if(str[0] == ':')return true; else return false; }).includes(true); }; const dynamicPaths = (() => { return Array.from(Object.keys(options.paths), (path) => { if(isDynamicPath(path))return path; }).filter((x) => x); })() return Array.from(dynamicPaths , (dyPath) => { let pathSplit = path.split('/').filter((x) => x); let dyPathSplit = dyPath.split('/').filter((x) => x); if(pathSplit.length == dyPathSplit.length){ const corresponance = !Array.from({length : pathSplit.length} , (x:unknown , i:number) => { let pathStr = pathSplit[i]; let dyPathStr = dyPathSplit[i]; if(pathStr == dyPathStr)return true; else if(dyPathStr[0] == ':')return true; else return false; }).includes(false); if(corresponance)return [dyPath , corresponance]; } else return null; }).filter((x) => x)[0]; } /** * ### extractData * @param path * @param dyPath * @returns Record * - Description : Allows you to extract key values from a key with a dynamic format */ const extractData = (path,dyPath) => { let pathSplit = path.split('/').filter((x) => x); let dyPathSplit = dyPath.split('/').filter((x) => x); return Object.fromEntries(new Map(Array.from({length : pathSplit.length} , (x:unknown , i:number) => { let pathStr = pathSplit[i]; let dyPathStr = dyPathSplit[i]; if(dyPathStr[0] == ':')return [dyPathStr.slice(1,dyPathStr.length) , pathStr]; }).filter((x) => x) as [string , string][])); } /** * ### ListRouters * @param router * @param routers * @returns RouterViewController[] */ const ListRouters = (router:Controller , routers:Controller[] = []) => { let routerParent = null; try {routerParent = thisRouter.value.context('router');} catch (error) {} routers.push(router); if(routerParent && routerParent.id != router.id){ return ListRouters(routerParent,routers); }else return routers.reverse(); } /** * ### setPath * @returns * - Description : */ const setPath = () => { const path = (options.defaultPath ? options.defaultPath : '/') let x = URILocation.getCorrespondance(options.id); // alert(`set path`) if(x){ let [p] = x; if(thisRouter.value.getAttribute('path') != p)thisRouter.value.setAttribute('path',p); }else thisRouter.value.setAttribute('path',path); } // alert(); super({ type : 'router', prop : { id:options.id, name : 'router', class : 'context', ':path' : '' }, proto : { AfterInitialise(){ // alert(); setRouter(this as RouterViewController); setPath(); }, onMutation(mutation){ // alert(this.id) const {attributeName:attributeName} = mutation; const value = this.getAttribute(attributeName); // Change in URL bar const paths = window.location.href.split('#').filter((x) => x); const pathsList = paths.slice(1,paths.length); // alert(`on mutation ${value}`); // if(!options.paths[value]){ if(this.children[0])this.children[0].remove(); const correspondance = captureCorrespondance(value); console.log('ici' , correspondance) if(correspondance){ const data = extractData(value , correspondance[0]); new UserInterface.NodeUI([ new options.paths[correspondance[0] as string](data) ]) .BuildIn(this) .then((nodes) => { nodes.Initialise(); if(options.onPathChange)options.onPathChange(mutation); // URILocation.hash = value; URILocation.onPathChange(); // URILocation.hash = getComposedPath(value); }) } else if('onPathError' in options){ new UserInterface.NodeUI([ new options.onPathError() ]) .BuildIn(this) .then((nodes) => { nodes.Initialise(); // URILocation.hash = value; URILocation.onPathChange(); // URILocation.hash = getComposedPath(value); }) } } else if(attributeName == 'path' && options.paths[value]){ if(this.children[0])this.children[0].remove(); new UserInterface.NodeUI([ new options.paths[value]() ]) .BuildIn(this) .then((nodes) => { nodes.Initialise(); if(options.onPathChange)options.onPathChange(mutation); // URILocation.hash = value; URILocation.onPathChange(); // URILocation.hash = getComposedPath(value); }) } // else URILocation.hash = getComposedPath(value); } } }) } } export function Router(){ return new router; } export class router{ id = ('randomUUID' in crypto ? crypto.randomUUID() : (() => { return (([1e7] as any)+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ); })()); get RouterView(){ const router = this; return class extends RouterView{ constructor(options:RouterViewOptionsInit){ super({ id:router.id, ...options }) } }; } get Link(){ const router = this; return class extends RouterLink{ constructor(options:RouterLinkOptionsInit){ super({ id:router.id, ...options }) } } } get Button(){ const router = this; return class extends RouterButtonLink{ constructor(options:RouterLinkOptionsInit){ super({ id:router.id, ...options }) } } } }