import { AsyncDirective } from "lit/async-directive.js"; import { directive } from "lit/directive.js"; import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy"; import { dp } from "@supersoniks/concorde/core/utils/PublisherProxy"; import { noChange } from "lit"; import { APIConfiguration } from "@supersoniks/concorde/core/utils/api"; import API from "@supersoniks/concorde/core/utils/api"; import Objects from "../utils/Objects"; import HTML, { SearchableDomElement } from "../utils/HTML"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { detecHTMLLanguageChange } from "../utils/HTML"; type ApiCallKey = { apiConfiguration: APIConfiguration; wordingProvider: string | null; wordingVersionProvider: string | null; }; type ApiCallValue = { api: API; keysToTranslate: Set; translatedKeys: Set; wordingProvider: string | null; callIndex: number; wordingVersionProvider: string | null; apiCallKey: ApiCallKey; }; const apiCalls: Map = new Map(); const getApiCall = (node: SearchableDomElement | null) => { if (!node) return null; const configuration = HTML.getApiConfiguration(node); const wordingProvider = HTML.getAncestorAttributeValue( node, "wordingProvider" ); const wordingVersionProvider = HTML.getAncestorAttributeValue( node, "wordingVersionProvider" ); const searchedKey = { apiConfiguration: configuration, wordingProvider: wordingProvider, wordingVersionProvider: wordingVersionProvider, }; //touver une api avec la même configuration en comparant les avec l'utilitaire Objects deepEqual let apiCall = null; for (const [key, value] of apiCalls) { if (Objects.deepEqual(key, searchedKey)) { apiCall = value; break; } } if (!apiCall) { const api = new API(configuration); apiCall = { api, keysToTranslate: new Set(), translatedKeys: new Set(), wordingProvider, callIndex: 0, wordingVersionProvider: wordingVersionProvider, apiCallKey: searchedKey, }; apiCalls.set(searchedKey, apiCall); } return apiCall; }; export const loadingString = ""; export const isWordingReady = (key: string) => { const value = WordingDirective.publisher["wording_" + key].get(); return value !== loadingString && value != null; }; export default class WordingDirective extends AsyncDirective { static publisher = PublisherManager.get("sonic-wording", { localStorageMode: "enabled", }); unsubscribe(): void { WordingDirective.publisher["wording_" + this.key].offAssign(this.onAssign); } key?: string; node?: SearchableDomElement; useUnsafeHTML = false; /* eslint-disable @typescript-eslint/no-explicit-any*/ constructor(partInfo: any) { super(partInfo); this.node = partInfo.options.host; // options n'est pas déclaré sur PartInfo mais exist en réalité d'où le typage "any" } /* eslint-enable @typescript-eslint/no-explicit-any*/ render(key: string, useUnsafeHTML = false) { this.useUnsafeHTML = useUnsafeHTML; if (this.key !== key) { this.key = key; if (this.isConnected) { this.subscribe(key); } } return noChange; } static firstCall = true; static async callApi( node: SearchableDomElement | null, key: string, relaunch = true, usedApiCall?: ApiCallValue ) { await PublisherManager.getInstance().isLocalStrorageReady; if (WordingDirective.firstCall) { WordingDirective.firstCall = false; detecHTMLLanguageChange(WordingDirective.reloadWordings); //clean keys of the publisher that are equivalent to the loading string const keys = Object.keys(WordingDirective.publisher.get()); for (const key of keys) { if (WordingDirective.publisher.get()[key] === loadingString) { delete WordingDirective.publisher[key]; } } } if (node) { const wordingVersionProvider = HTML.getAncestorAttributeValue( node, "wordingVersionProvider" ); if (wordingVersionProvider) { dp(wordingVersionProvider).onAssign( WordingDirective.handleVersionProvider(node) ); } } let hasWordingForKey = WordingDirective.publisher.get()["wording_" + key] != null; const apiCall = usedApiCall || getApiCall(node); if (!apiCall) return; if (hasWordingForKey && key !== "") { apiCall.translatedKeys.add(key); return; } apiCall.callIndex++; const callIndex = apiCall.callIndex; const wordingProvider = apiCall.wordingProvider ?? ""; // if the wording provider is unfortunately not set, we don't call the api now. // We will try later if (!wordingProvider && relaunch) { window.setTimeout(async () => { WordingDirective.callApi(null, key, false, apiCall); }, 1000); return; } // // const api = apiCall.api; window.queueMicrotask(async () => { hasWordingForKey = WordingDirective.publisher["wording_" + key].get() != null; if (!hasWordingForKey && key !== "") { apiCall.keysToTranslate.add(key); WordingDirective.publisher["wording_" + key].set(loadingString); } if (callIndex !== apiCall.callIndex) return; const wordings = Array.from(apiCall.keysToTranslate); if (!wordings.length) return; const splittedUrl = wordingProvider.split("?"); const locationURL = splittedUrl.shift(); const queryString = (splittedUrl.length > 0 ? splittedUrl.join("?") + "&" : "") + "labels[]=" + wordings.join("&labels[]="); const calledURL = locationURL + "?" + queryString; apiCall.translatedKeys = new Set([ ...apiCall.translatedKeys, ...apiCall.keysToTranslate, ]); apiCall.keysToTranslate.clear(); const result = (await api.get(calledURL)) as Record; for (const key in result) { WordingDirective.publisher["wording_" + key].set(result[key]); } }); } static reloadWordings() { for (const apiCall of apiCalls.values()) { apiCall.keysToTranslate = new Set(apiCall.translatedKeys); if (apiCall.keysToTranslate.size > 0) { WordingDirective.callApi(null, "", false, apiCall); } } } static versionProviderHandlers: Map void> = new Map(); //check if the wording version has changed static handleVersionProvider(node: SearchableDomElement) { const apiCall = getApiCall(node); const defaultReturn = (_v: number) => {}; if (!apiCall) return defaultReturn; if (WordingDirective.versionProviderHandlers.has(apiCall)) { return ( WordingDirective.versionProviderHandlers.get(apiCall) || defaultReturn ); } const versionProviderHandler = function (version: number) { const wordingVersionProvider = apiCall.wordingVersionProvider; if (!wordingVersionProvider) return; // const currentVersions: { serviceURL: string | null; version: number }[] = WordingDirective.publisher.get().__wording_versions__ ?? []; if (version == null) return; const currentVersionData = currentVersions.find( (v) => v.serviceURL === apiCall.api.serviceURL ) || { serviceURL: apiCall.api.serviceURL, version: 0, }; if (!currentVersions.includes(currentVersionData)) currentVersions.push(currentVersionData); if (version === currentVersionData.version) { return; } currentVersionData.version = version; WordingDirective.publisher.set({ __wording_versions__: currentVersions }); WordingDirective.reloadWordings(); }; WordingDirective.versionProviderHandlers.set( apiCall, versionProviderHandler ); return versionProviderHandler; } onAssign = (v: unknown) => { const value = this.useUnsafeHTML ? unsafeHTML(v as string) : v; this.setValue(value); }; // Subscribes to the key, calling the directive's asynchronous // setValue API each time the value changes subscribe(key: string) { this.unsubscribe(); // this.onAssign = (v: unknown) => { // this.setValue(v); // }; WordingDirective.publisher["wording_" + key].onAssign(this.onAssign); WordingDirective.callApi(this.node as SearchableDomElement, key); } // When the directive is disconnected from the DOM, unsubscribe to ensure // the directive instance can be garbage collected disconnected() { this.unsubscribe(); } // If the subtree the directive is in was disconnected and subsequently // re-connected, re-subscribe to make the directive operable again reconnected() { if (!this.key) return; this.subscribe(this.key); } } //autoUpdate directive export const wording = directive(WordingDirective); export const unsafeWording = (key: string) => wording(key, true); export const t = wording; export const w = wording;