/* eslint-disable @typescript-eslint/no-explicit-any*/ /** * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * Merci de laisser ce fichier sans la moindre dépendance en dehors de types du ceur. * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * **/ import type { PublisherInterface, CoreJSType, PublisherContentType, } from "../_types/types"; import HTML from "./HTML"; import Objects from "./Objects"; import { sonicClassPrefix } from "./Utils"; export type Observable = string; type DynamicFillingListener = any; type TemplateFillingListener = { propertyMap: Record; } & DynamicFillingListener; type PublisherProxyOptions = { localStorageMode?: string; expirationDelayMs?: number; invalidateOnPageShow?: boolean; }; function isLeaf(value: any) { return Object.prototype.hasOwnProperty.call(value, "__value"); } function isComplex(value: any) { return typeof value === "object" && value !== null; } declare const __BUILD_DATE__: string; declare const __SONIC_PREFIX__: string; let SONIC_PREFIX = "sonic"; if (typeof __BUILD_DATE__ === "undefined") { (window as any).__BUILD_DATE__ = "No build date"; } if (typeof __SONIC_PREFIX__ === "undefined") { SONIC_PREFIX = "sonic"; } export const prefix = SONIC_PREFIX; const localStorageDataKey = SONIC_PREFIX == "sonic" ? "publisher-proxies-data" : SONIC_PREFIX + "-publisher-proxies-data"; /** * Custom Proxy contient les méthodes des publishers retournés par PublisherManager.get(publisherId) qui seront utilisées couramment */ export class PublisherProxy { static instances = new Map(); static instancesCounter = 0; _proxies_: Map = new Map(); _value_: T; _is_savable_ = false; _expiration_delay_ = 1000 * 60 * 60 * 12; _invalidate_on_page_show_ = false; _invalidateListeners_ = new Set(); _formInvalidateListeners_ = new Set(); _assignListeners_ = new Set<(value: T) => void>(); _mutationListeners_ = new Set(); _fillListeners_ = new Set>(); _templateFillListeners_ = new Set(); _lockInternalMutationPublishing_ = false; _instanceCounter_ = 0; parent: PublisherProxy | null; _parentKey_?: string; root: PublisherProxy; constructor( target: T, parentProxPub: PublisherProxy | null, parentKey?: string, ) { this._value_ = target; this.parent = parentProxPub || null; this._parentKey_ = parentKey; this.root = this; this._instanceCounter_ = 0; while (this.root.parent) { this.root = this.root.parent; } } /** * Supprime le proxy et ses sous proxy * Supprime les écouteurs associés */ delete() { for (const key in this._proxies_.keys()) { if ((key as string) == "_parent_") continue; this._proxies_.get(key)?.delete(); } this._invalidateListeners_.clear(); this._formInvalidateListeners_.clear(); this._assignListeners_.clear(); this._mutationListeners_.clear(); this._fillListeners_.clear(); this._templateFillListeners_.clear(); this._proxies_.clear(); PublisherProxy.instances.delete(this._instanceCounter_); } /** * Utile pour savoir si quelque chose est en écoute d'une modification sur le proxy via une des methodes associées */ hasListener() { return ( this._templateFillListeners_.size > 0 || this._assignListeners_.size > 0 || this._invalidateListeners_.size > 0 || this._formInvalidateListeners_.size > 0 || this._mutationListeners_.size > 0 || this._fillListeners_.size > 0 ); } _assignmentId_ = 0; _publishInternalMutation_(lockInternalMutationsTransmission = false) { this._mutationListeners_.forEach((handler: VoidFunction): void => handler(), ); if (this._is_savable_ && !PublisherManager.changed) { PublisherManager.changed = true; PublisherManager.saveId++; const saveId = PublisherManager.saveId; setTimeout( () => PublisherManager.getInstance().saveToLocalStorage(saveId), 1000, ); } if (lockInternalMutationsTransmission) return; if (this.parent) { this.parent._publishInternalMutation_(); } } async _publishAssignement_(lockInternalMutationsTransmission = false) { this._assignmentId_++; const currentId = this._assignmentId_; if (currentId !== this._assignmentId_) return; const newValue = this.get(); this._assignListeners_.forEach((handler: (value: T) => void) => { handler(newValue); }); this._publishInternalMutation_(lockInternalMutationsTransmission); } _publishInvalidation_() { this._invalidateListeners_.forEach((handler: VoidFunction) => handler()); } _publishFormInvalidation_() { this._formInvalidateListeners_.forEach((handler: VoidFunction) => handler(), ); } _publishDynamicFilling_(key: string, value: CoreJSType) { this._fillListeners_.forEach((handler) => { if (handler[key] !== value) handler[key] = value; }); this._publishTemplateFilling_(key, value); } _publishTemplateFilling_(key: string, value: CoreJSType) { this._templateFillListeners_.forEach((handler) => { const desc = Object.getOwnPropertyDescriptor(handler, key); if (desc && !desc.set && !desc.writable) return; if (handler.propertyMap && handler.propertyMap[key]) { key = handler.propertyMap[key]; } if (typeof handler[key] != "undefined" && handler[key] !== value) { handler[key] = value; } }); } /** * Appel la fonction "handler" (passée en paramettre) lorsque la valeur gérée par le proxy change par assignation * hanlder reçois alors la nouvelle valeur interne du proxy en paramètre */ onAssign(handler: (value: T) => void, directHandlerCall = true) { if (typeof handler != "function") return; if (this._assignListeners_.has(handler)) return; this._assignListeners_.add(handler); if (directHandlerCall) handler(this.get()); } /** * Stop les appels de la fonction "handler" (passée en paramettre) lorsque la valeur gérée par le proxy change par assignation */ offAssign(handler: (value: T) => void) { this._assignListeners_.delete(handler); } /** * Appel la fonction "handler" (passée en paramettre) lorsque la donnée est flaggée comme invalide */ onInvalidate(handler: VoidFunction | undefined) { if (typeof handler != "function") return; this._invalidateListeners_.add(handler); } /** * Stop les appels de la fonction "handler" (passée en paramettre) lorsque la donnée est flaggée comme invalide */ offInvalidate(handler: VoidFunction | undefined) { if (typeof handler != "function") return; this._invalidateListeners_.delete(handler); } /** * Flag les données comme étant invalides */ invalidate() { this._publishInvalidation_(); } /** * Appel la fonction "handler" (passée en paramettre) lorsque la donnée est flaggée comme invalide */ onFormInvalidate(handler: VoidFunction | undefined) { if (typeof handler != "function") return; this._formInvalidateListeners_.add(handler); } /** * Stop les appels de la fonction "handler" (passée en paramettre) lorsque la donnée est flaggée comme invalide */ offFormInvalidate(handler: VoidFunction | undefined) { if (typeof handler != "function") return; this._formInvalidateListeners_.delete(handler); } invalidateForm() { this._publishFormInvalidation_(); } /** * Appel la fonction "handler" (passée en paramettre) lorsque quelque chose change la valeur gérée par le proxy quelque soit la profondeur de la donnée * */ onInternalMutation(handler: VoidFunction | undefined) { if (typeof handler != "function") return; this._mutationListeners_.add(handler); handler(); } /** * Stop les Appels de la fonction "handler" (passée en paramettre) lorsque quelque chose change la valeur gérée par le proxy quelque soit la profondeur de la donnée */ offInternalMutation(handler: VoidFunction | undefined) { if (typeof handler != "function") return; this._mutationListeners_.delete(handler); } /** * Maintient le remplissage de l'objet / tableau "handler" passé en paramètre avec les valeurs du proxy * Remplit uniquement les valeurs déjà présentes dans l'objet / tableau passé en paramètre */ startTemplateFilling(handler: TemplateFillingListener) { this._templateFillListeners_.add(handler); if (typeof this._value_ != "object") return; for (const z in this._value_) { let valueKey = z as string; const value = this._value_[z]; if (handler.propertyMap && handler.propertyMap[z]) { valueKey = handler.propertyMap[z]; } if (typeof handler[z] != "undefined" && handler[z] !== value) { handler[valueKey] = value; } } } /** * Arrête de maintenir le remplissage de l'objet / tableau "handler" passé en paramètre avec les valeurs du proxy */ stopTemplateFilling(handler: TemplateFillingListener) { this._templateFillListeners_.delete(handler); } /** * Maintient le remplissage de l'objet / tableau "handler" passé en paramètre avec les valeurs du proxy * Remplit toute valeur qu'elle soit présente ou pas initialement dans l'objet */ startDynamicFilling(handler: DynamicFillingListener) { this._fillListeners_.add(handler); for (const z in this._value_) { const value = this._value_[z]; if (handler[z] !== value) handler[z] = value; } } /* * Arrête de maintenir le remplissage de l'objet / tableau "handler" passé en paramètre avec les valeurs du proxy */ stopDynamicFilling(handler: DynamicFillingListener) { this._fillListeners_.delete(handler); } /** * Assigne une nouvelle valeur au proxy ce qui déclenche la transmission de la donnée en fonction des "écouteurs" associés */ set(newValue: T, lockInternalMutationsTransmission = false) { /** * On retounre tout de suite si la valeur n'a pas changé */ if (this._value_ === newValue) return true; /** * On retounre tout de suite si la valeur n'a pas changé * et qu'elle est primitive */ if ( (isComplex(this._value_) && isComplex(newValue) && newValue && isLeaf(this._value_) && isLeaf(newValue) && (this._value_ as any).__value === (newValue as any).__value) || (!isComplex(newValue) && newValue === (this._value_ as any).__value) ) { return true; } /** * On assigne la nouvelle valeur */ /** Si la valeur est complexe on assigne newValue directe sinon on le met dans une sous valeur __value */ this._value_ = isComplex(newValue) ? newValue : ({ __value: newValue } as any); this._cachedGet_ = undefined; /** * Si il s'agit d'une valeur primitive (un entier, une chaine ) la valeure en renseignée par un objet contenant la vaeur {__value} * On publie juste et on sen va. */ const isLeafNewValue = isLeaf(this._value_); if (this._parentKey_ && this.parent) { //si le parent n'a pas de valeur, on la crée en renseignant this.value et on retourne. const valueTosetInParent = isLeaf(this._value_) ? (this._value_ as any).__value : this._value_; if (this.parent?.get() == null && this.parent?.get() == undefined) { //if _parentKey_ is a number, we assume it's an array if (!isNaN(Number(this._parentKey_))) { const parent = []; parent[Number(this._parentKey_)] = valueTosetInParent; this.parent.set(parent); } else { this.parent.set({ [this._parentKey_]: valueTosetInParent }); } // return; } else { //on mets à jour la valeur de la clef dans l'objet parnet pour refléter le changement au ca sou elle n'existait pas this.parent._value_[this._parentKey_] = valueTosetInParent; } } if (isLeafNewValue) { // Si la valeur précédente était composite, on reset tous les sous-éléments à null this._proxies_.forEach((_subProxy, key) => { /** * * On reùmplace les undefined par des nuls pour eviter la suppression des clefs sauf pour les clef tableaux **/ if (key != "_parent_") { _subProxy.set(null); this._publishDynamicFilling_(key, null); } }); //await //await here gives better performance but can cause issues with some listeners this._publishAssignement_(lockInternalMutationsTransmission); if (this.parent && this._parentKey_) { this.parent._publishDynamicFilling_( this._parentKey_, (this._value_ as any).__value, ); } return true; } /** * On fait la diff sur l'existant. * On maintient les proxys à supprimer et ceux à ajouter. * On met à jour leur valeur interne * On publie les maj au fur et a mesure de modifications */ for (const key in this._value_) { const currentValue = this._value_[key] as any; if (currentValue === undefined) delete this._value_[key]; } this._proxies_.forEach((_subProxy, key) => { /** * * On reùmplace les undefined par des nuls pour eviter la suppression des clefs sauf pour les clef tableaux **/ const currentValue: any = (this._value_ as any)[key]; if (key != "_parent_") { if ( currentValue === undefined && currentValue !== null && isNaN(Number(key)) ) { _subProxy.set(null); this._publishDynamicFilling_(key, null); } } }); /** * On prévient les écouteurs que la valeur a changé */ //await //await here gives better performance but can cause issues with some listeners this._publishAssignement_(); if (this.parent && this._parentKey_) this.parent._publishDynamicFilling_( this._parentKey_, this._value_ as any, ); /** * Si la donnée est complexe (objet, tableau) * on crée les proxys pour les sous-éléments qui n'en on pas * On renseigne les nouvelles valeurs internes des proxies */ if (isComplex(this._value_)) { for (const key in this._value_) { const v = newValue[key] as any; const isVComplex = isComplex(v); const valueV = isVComplex ? v : { __value: v }; if (!this._proxies_.has(key)) { // A surveiller ancienne version // const newPublisher = new DataProvider({}, this); // this._proxies_.set(key, newPublisher); // newPublisher._proxies_.set("_parent_", this); this._publishDynamicFilling_(key, v); continue; } //await //await here gives better performance but can cause issues with some listeners this._proxies_.get(key)?.set(valueV, true); this._publishDynamicFilling_(key, v); } } return true; } /** * Extraire la valeur actuelle du proxy */ _cachedGet_?: T; get(): T { if (PublisherManager.modifiedCollectore.length > 0) PublisherManager.modifiedCollectore[0].add(this); if (this._cachedGet_ !== undefined) return this._cachedGet_; if (Object.prototype.hasOwnProperty.call(this._value_, "__value")) { const v = (this._value_ as any).__value; return (this._cachedGet_ = (v != undefined ? v : null) as T); } return (this._cachedGet_ = ( this._value_ != undefined ? this._value_ : null ) as T); } /** * retourner le webcomponent auquel le proxy est associé */ get $tag(): string { if (!this._instanceCounter_) { PublisherProxy.instancesCounter++; this._instanceCounter_ = PublisherProxy.instancesCounter; } PublisherProxy.instances.set(this._instanceCounter_, this); const str = "<" + SONIC_PREFIX + '-publisher-proxy publisher="' + this._instanceCounter_ + '">"; return str; } } /** * Utilitaires de gestion des DataProvider * Obtenir, replacer ou supprimer un DataProvider * */ export class PublisherManager { static buildDate = __BUILD_DATE__; static changed = false; static saving = false; static saveId = 0; static instance: PublisherManager | null = null; static instances: Map = new Map(); enabledLocaStorageProxies: string[] = []; publishers = new Map(); localStorageData: Record< string, { lastModifiationMS: number; data: PublisherContentType; expirationDelayMs?: number; } > = {}; isLocalStrorageReady: Promise | null = null; constructor() { if (PublisherManager.instance != null) throw "Singleton / use getInstance"; PublisherManager.instance = this; this.isLocalStrorageReady = this.cleanStorageData() as Promise; } invalidateAll() { this.publishers.forEach((publisher) => { if (!publisher._invalidate_on_page_show_) { return; } publisher.invalidate(); }); } async cleanStorageData() { return new Promise((resolve) => { const doiIt = async () => { try { let compressedData = localStorage.getItem(localStorageDataKey); let localStorageJSON = null; if (compressedData) localStorageJSON = await this.decompress(compressedData, "gzip"); if (localStorageJSON) { try { this.localStorageData = JSON.parse(localStorageJSON); } catch (e) { this.localStorageData = {}; } } else { compressedData = await this.compress("{}", "gzip"); localStorage.setItem(localStorageDataKey, compressedData); this.localStorageData = {}; } const expirationDelay = 1000 * 60 * 60 * 12; for (const key in this.localStorageData) { const item = this.localStorageData[key]; const expires = new Date().getTime() - (item.expirationDelayMs || expirationDelay); if (item.lastModifiationMS < expires) { delete this.localStorageData[key]; } } resolve(true); } catch (e) { window.requestAnimationFrame(() => { resolve(false); }); console.warn("no publisher cache in this browser"); } }; doiIt(); }); } /** * PublisherManager est un singleton */ static getInstance(id?: string): PublisherManager { if (id) { const pm = PublisherManager.instances.get(id); if (pm) { return pm; } else { console.warn( "No PublisherManager instance registered with id:", id, "creating new one", ); return new PublisherManager(); } } if (PublisherManager.instance == null) return new PublisherManager(); return PublisherManager.instance; } static registerInstance(id: string, instance: PublisherManager) { if (PublisherManager.instances.has(id)) { console.warn( "PublisherManager instance already registered with id: ", id, ); } PublisherManager.instances.set(id, instance); } /** * shortcut static pour obtenir un publisher vias sont id/adresse sans taper getInstance. * Si le publisher n'existe pas, il est créé. */ public static get( id: string, options?: { localStorageMode?: string; expirationDelayMs?: number }, ) { return PublisherManager.getInstance().get(id, options); } public static modifiedCollectore: Set[] = []; public static collectModifiedPublisher() { PublisherManager.modifiedCollectore.unshift(new Set()); } public static getModifiedPublishers() { return PublisherManager.modifiedCollectore.shift() as Set; } /** * shortcut static pour supprimer un publisher de la liste et appel également delete sur le publisher ce qui le supprime, de même que ses sous publishers */ static delete(id: string | null) { if (!id) return false; return PublisherManager.getInstance().delete(id); } /** * Obtenir un publisher vias sont id/adresse * Si le publisher n'existe pas, il est créé. */ async setLocalData(publisher: PublisherInterface, id: string) { await this.isLocalStrorageReady; publisher.set( this.localStorageData[id + "¤lang_" + HTML.getLanguage()]?.data || publisher.get(), ); } initialisedData: string[] = []; get(id: string, options?: PublisherProxyOptions): DataProvider { const hasLocalStorage = options?.localStorageMode === "enabled"; const invalidateOnPageShow = options?.invalidateOnPageShow === true; if (!this.publishers.has(id)) { const data = {}; const publisher = createPublisher(data as T) as DataProvider; this.set(id, publisher); } const publisher = this.publishers.get(id) as DataProvider; if (hasLocalStorage && this.initialisedData.indexOf(id) === -1) { if (options?.expirationDelayMs) { (publisher as PublisherProxy)._expiration_delay_ = options.expirationDelayMs; } (publisher as PublisherProxy)._is_savable_ = true; this.initialisedData.push(id); this.setLocalData(publisher, id); } if (invalidateOnPageShow) { (publisher as PublisherProxy)._invalidate_on_page_show_ = invalidateOnPageShow; } return this.publishers.get(id) as DataProvider; } /** * Remplace un publisher pour l'id fourni par un autre. * L'autre publisher n'est pas supprimé. */ set(id: string, publisher: DataProvider) { this.publishers.set(id, publisher); } /** * @warning * !!!!! ATTENTION !!!!! * Il est fort à aprier que vous ne voulez pas utiliser cette methode * Il s'agit d'une supression complete * * du publisher de la liste * * des bindings * * de même que ses sous publishers * * UTILISEZ PLUTÔT la méthode publisher.set({}) pour réinitialiser un publisher sans perdre les ecouteurs */ delete(id: string) { if (!this.publishers.has(id)) return false; this.publishers.delete(id); return true; } async saveToLocalStorage(saveId = 0) { /** * si l'id a changé et que ce n'est pas un multiple de 10, on ne sauve pas * on sauvegarde quand même tous les 10 au cas ou on aurrait des changements en continue, par exemple à chaque frame */ if (saveId !== PublisherManager.saveId && saveId % 10 != 0) return; try { if (!PublisherManager.changed || PublisherManager.saving) return; PublisherManager.saving = true; PublisherManager.changed = false; const keys = Array.from(this.publishers.keys()); let hasChanged = false; for (const key of keys) { const publisher = this.publishers.get(key); if (!publisher?._is_savable_) continue; const data = publisher?.get(); if (!data) continue; this.localStorageData[key + "¤lang_" + HTML.getLanguage()] = { lastModifiationMS: new Date().getTime(), expirationDelayMs: publisher._expiration_delay_, data: data, }; hasChanged = true; } // on enregistre les données if (hasChanged) { const compressedData = await this.compress( JSON.stringify(this.localStorageData), "gzip", ); localStorage.setItem(localStorageDataKey, compressedData); } PublisherManager.saving = false; if (PublisherManager.changed) { PublisherManager.saveId++; const saveId = PublisherManager.saveId; setTimeout(() => this.saveToLocalStorage(saveId), 1000); } } catch (e) { PublisherManager.saving = false; } } async compress(string: string, encoding: string) { const byteArray = new TextEncoder().encode(string); const win = window as any; const cs = new win.CompressionStream(encoding); const writer = cs.writable.getWriter(); writer.write(byteArray); writer.close(); const result = await new Response(cs.readable).arrayBuffer(); const arrayBufferView = new Uint8Array(result); let str = ""; for (let i = 0; i < arrayBufferView.length; i++) { str += String.fromCharCode(arrayBufferView[i]); } return btoa(str); } async decompress(str: string, encoding: string) { const decodedString = atob(str); const arrayBufferViewFromLocalStorage = Uint8Array.from( decodedString, (c) => c.charCodeAt(0), ); const byteArray = arrayBufferViewFromLocalStorage.buffer; const win = window as any; const cs = new win.DecompressionStream(encoding); const writer = cs.writable.getWriter(); writer.write(byteArray); writer.close(); const result = await new Response(cs.readable).arrayBuffer(); return new TextDecoder().decode(result); } } if (typeof window !== "undefined") { const win = window as any; win[sonicClassPrefix + "PublisherManager"] = win[sonicClassPrefix + "PublisherManager"] || PublisherManager; } const internalProps: Set = new Set([ "invalidate", "onInvalidate", "offInvalidate", "invalidateForm", "onFormInvalidate", "offFormInvalidate", "onAssign", "offAssign", "startDynamicFilling", "stopDynamicFilling", "startTemplateFilling", "stopTemplateFilling", "onInternalMutation", "offInternalMutation", "set", "get", "$tag", "_cachedGet_", "_templateFillListeners_", "_fillListeners_", "_assignListeners_", "_invalidateListeners_", "_formInvalidateListeners_", "_publishInternalMutation_", "hasListener", "delete", "_mutationListeners_", "_publishDynamicFilling_", "_publishInvalidation_", "_publishFormInvalidation_", "_publishTemplateFilling_", "_publishAssignement_", "_proxies_", "parent", "_parentKey_", "_value_", "_is_savable_", "_expiration_delay_", "_lockInternalMutationPublishing_", "_instanceCounter_", "_assignmentId_", "_invalidate_on_page_show_", ]); // type UnderscoreWrappedKeys = { // [K in keyof T]-?: K extends `_${string}_` ? K : never // }[keyof T]; /** * Le Proxy Javascript */ type WithoutUnderscoreKeys = { [K in keyof T as K extends `_${string}_` ? never : K]: T[K]; }; export type DataProvider = WithoutUnderscoreKeys> & { [K in keyof T]: DataProvider; }; function createPublisherHandler( publisherInstance: PublisherProxy, getProxy: () => DataProvider, ): ProxyHandler> { return { /** * Lorsque l'on écrit monConteneur = publisher.maClef ou monConteneur = publisher["maClef"] monConteneur contient : * Les methodes de PublisherProxy (onAssign... : voir liste dans kle tableaus si dessous), si la clef est une méthode de PublisherProxy,, * Sinon un autre proxy qui a comme valeur interne la valeur corespondante à la clef dans l'objet. */ get: function (_publisherInstance, sKey: any) { if (typeof sKey === "string" && internalProps.has(sKey)) return (publisherInstance as any)[sKey]; if (sKey == Symbol.toPrimitive) { return () => getProxy().get() as T; } // Support de la notation à points (rigolo mais pas intuitif) // if (sKey.includes(".")) { // const keys = sKey.split("."); // return thisProxy[keys.shift()][keys.join(".")]; // } if (!publisherInstance._proxies_.has(sKey)) { const vValue = (publisherInstance._value_ as any)[sKey]; const newPublisher = createPublisher( isComplex(vValue) ? vValue : ({ __value: vValue } as any), publisherInstance, sKey, ); newPublisher._proxies_.set("_parent_", getProxy()); publisherInstance._proxies_.set(sKey, newPublisher); } return publisherInstance._proxies_.get(sKey); }, /** * Lorsque l'on écrit publisher.maClef = value ou publisher["maClef"] = value, on assigne la valeur à la clef dans l'objet interne. * Les gestionnairs associés sopnt déclenchés en conséquence de manière profonde et remontante si nécessaire. */ set: function ( _publisherInstance, sKey: string, vValue: T | PublisherProxy[keyof PublisherProxy], ) { //Fonctionnement pour la donnée interne pas de dispatch; if (sKey == "_value_") { publisherInstance._value_ = vValue; return true; } if (sKey == "_cachedGet_") { publisherInstance._cachedGet_ = vValue; return true; } if (sKey == "_assignmentId_") { publisherInstance._assignmentId_ = vValue; return true; } if (sKey == "_is_savable_") { publisherInstance._is_savable_ = vValue; return true; } if (sKey == "_expiration_delay_") { publisherInstance._expiration_delay_ = vValue; return true; } if (sKey == "_invalidate_on_page_show_") { publisherInstance._invalidate_on_page_show_ = vValue; return true; } if (sKey == "_instanceCounter_") { publisherInstance._instanceCounter_ = vValue; return true; } // Support de la notation à points (rigolo mais pas intuitif) // if (sKey.includes(".")) { // const keys = sKey.split("."); // thisProxy[keys.shift() || ""][keys.join(".")].set(vValue); // return true; // } //Création du publisher si il n'existe pas if (!publisherInstance._proxies_.has(sKey)) { const newPublisher = createPublisher( {} as any, publisherInstance, sKey, ); newPublisher._proxies_.set("_parent_", getProxy()); publisherInstance._proxies_.set(sKey, newPublisher); } //mis à jour et publication de la donnée si elle a changé const prevValue = (publisherInstance._value_ as any)[sKey]; if (prevValue !== vValue) { (publisherInstance._value_ as any)[sKey] = vValue; publisherInstance._publishDynamicFilling_(sKey, vValue); publisherInstance._proxies_ .get(sKey) ?.set(isComplex(vValue) ? vValue : { __value: vValue }); } //on retourne le proxy pour pouvoir chainer les assignements // return publisherInstance._proxies_.get(sKey); return true; }, /** * Autres propriétés classiques d'un objet implémentées par le proxy */ deleteProperty: function (_publisherInstance, sKey: string) { publisherInstance._publishDynamicFilling_(sKey, null); // if (!publisherInstance._proxies_.get(sKey)?.hasListener()) { // publisherInstance._proxies_.delete(sKey); // } else { publisherInstance._proxies_.get(sKey)?.set(null); // } return delete (publisherInstance._value_ as any)[sKey]; }, // enumerate: function (publisherInstance, sKey): CoreJSType { // return publisherInstance._value_.keys(); // }, has: function (_publisherInstance, sKey) { return ( sKey in (publisherInstance._value_ as any) && sKey != "_lockInternalMutationPublishing_" ); }, defineProperty: function (_publisherInstance, sKey, oDesc) { if (oDesc && "value" in oDesc) { (publisherInstance._value_ as any)[sKey] = oDesc.value; } return true; // return publisherInstance._value_; }, getOwnPropertyDescriptor: function (_publisherInstance, sKey) { sKey; return { enumerable: true, configurable: true, }; }, ownKeys: function (_publisherInstance) { if ((publisherInstance._value_ as any).__value) return Object.keys((publisherInstance._value_ as any).__value); return Object.keys((publisherInstance as any)._value_); }, }; } export function createPublisher( target: T, parentProxPub: PublisherProxy | null = null, parentKey?: string, ) { const instance = new PublisherProxy(target, parentProxPub, parentKey); let proxy = null as unknown as DataProvider & T; const handler = createPublisherHandler(instance, () => proxy); proxy = new Proxy>( instance, handler, ) as unknown as DataProvider & T; return proxy; } /** * @deprecated @see {@link DataProvider} instead */ export default DataProvider; export type Publisher = DataProvider; // if (typeof module != "undefined") module.exports = {DataProvider: DataProvider, PublisherManager: PublisherManager}; // /** // * A custom webcomponent wich will be linked to a publisher via its attribute "publisher" // * the publisher will be found via PublisherManager.get(publisherId) and will be used to fill the component using the onAssign method // */ class PublisherWebComponent extends HTMLElement { publisherId = ""; publisher: any; constructor() { super(); } connectedCallback() { this.publisherId = this.getAttribute("publisher") || ""; this.publisher = PublisherProxy.instances.get(parseInt(this.publisherId)); this.publisher?.onAssign(this.onAssign); } disconnectedCallback() { this.publisher?.offAssign(this.onAssign); } onAssign = (value: any) => { this.innerHTML = value.toString(); }; } try { customElements.define( SONIC_PREFIX + "-publisher-proxy", PublisherWebComponent, ); } catch (e) {} export const getObservables = ( observable: string, ): Set> => { if (typeof observable === "function") { const func = observable as () => any; PublisherManager.collectModifiedPublisher(); func(); return PublisherManager.getModifiedPublishers() as Set>; } if (typeof observable === "string") { const split = observable.split("."); const dataProvider: string = split.shift() || ""; let publisher = PublisherManager.get(dataProvider); publisher = Objects.traverse(publisher, split); const set = new Set>(); set.add(publisher as DataProvider); return set; } return new Set>([observable as DataProvider]); }; // get value export const get = (id: string) => { return getObservables(id).values().next().value?.get() as T; }; const deepee = (id: string) => { const value = getObservables(id).values().next().value as DataProvider; // if (defaultValue !== undefined && value) { // const innerValue = value.get(); // if (Objects.isEmpty(innerValue as Record)) { // value.set(defaultValue); // } // } return value; }; export const dataProvider: ( id: string, defaultValue?: T, ) => DataProvider = deepee; export const dp = deepee; export const set: (id: string, value: T) => void = ( id: string, value: T, ) => { getObservables(id).values().next().value?.set(value); }; /** * next back handling data invalidation */ window.addEventListener("pageshow", (e) => { if (e.persisted) PublisherManager.getInstance().invalidateAll(); });