diff --git a/src/Bones.UI/abstractions/inotifyService.ts b/src/Bones.UI/abstractions/inotifyService.ts index 5cacd7b..75a40f5 100644 --- a/src/Bones.UI/abstractions/inotifyService.ts +++ b/src/Bones.UI/abstractions/inotifyService.ts @@ -15,4 +15,6 @@ export type AllEvent = NotifyEvent | "all"; export type AddOrUpdateCallback = (ev: AddOrUpdateEvent, payload: TDetails) => void; export type DeleteCallback = (ev: DeleteEvent, id: any) => void; export type ResetCallback = (ev: ResetEvent) => void; -export type AllCallback = AddOrUpdateCallback | DeleteCallback | ResetCallback; \ No newline at end of file +export type AllCallback = AddOrUpdateCallback | DeleteCallback | ResetCallback; + +export type SubscribeCall = [AddOrUpdateEvent, AddOrUpdateCallback] | [DeleteEvent, DeleteCallback] | [ResetEvent, ResetCallback] | [AllEvent, AllCallback]; \ No newline at end of file diff --git a/src/Bones.UI/composables/useDevMode.ts b/src/Bones.UI/composables/useDevMode.ts new file mode 100644 index 0000000..f36f5d0 --- /dev/null +++ b/src/Bones.UI/composables/useDevMode.ts @@ -0,0 +1,24 @@ +import { ref, computed } from "vue"; + +import { EventQueue } from "../core/eventQueue"; + +const DEV_MODE_TOPIC = 'devMode'; +const devMode = ref(false); + +EventQueue.instance.subscribe(DEV_MODE_TOPIC, (_topic: string, payload: boolean) => { + devMode.value = payload; + console.log(`Dev mode is now ${devMode.value ? 'enabled' : 'disabled'}`); +}); + +export function useDevMode() { + const toggleDevMode = () => { + EventQueue.instance.publish(DEV_MODE_TOPIC, !devMode.value); + }; + + const isDevMode = computed(() => devMode.value); + + return { + toggleDevMode, + isDevMode + }; +} \ No newline at end of file diff --git a/src/Bones.UI/composables/useTranslations.ts b/src/Bones.UI/composables/useTranslations.ts index c17d411..a7e4d1b 100644 --- a/src/Bones.UI/composables/useTranslations.ts +++ b/src/Bones.UI/composables/useTranslations.ts @@ -1,14 +1,21 @@ import { ref } from 'vue' +import { useDevMode } from './useDevMode'; const _translations = ref<{ code: string, value: string }[]>([]); export function useTranslations() { + const { isDevMode } = useDevMode(); + const $tr = (code: string, defaultValue: string, ...parameters: (string | number)[]): string => { + if (isDevMode.value) { + return code; + } + let translation = _translations.value.find(t => t.code === code)?.value ?? defaultValue; if (translation && parameters.length) { - for (let p of parameters) { - translation = translation.replace(`{${parameters.indexOf(p)}}`, p.toString()); + for (let i = 0; i < parameters.length; i++) { + translation = translation.replace(`{${i}}`, parameters[i].toString()); } } return translation; diff --git a/src/Bones.UI/core/composableFactory.ts b/src/Bones.UI/core/composableFactory.ts index e9419c7..b2d9aa8 100644 --- a/src/Bones.UI/core/composableFactory.ts +++ b/src/Bones.UI/core/composableFactory.ts @@ -25,6 +25,27 @@ export class ComposableFactory { return ComposableFactory.customRemove(service.remove); } + public static subscribe(service: INotifyService) { + return () => { + let subscribersIds: number[] = []; + + onUnmounted(() => { + subscribersIds.forEach(id => service.unsubscribe(id)); + subscribersIds = []; + }); + + const subscribe: INotifyService["subscribe"] = (ev: any, callback: any) => { + const subscriberId = service.subscribe(ev, callback); + subscribersIds.push(subscriberId); + return subscriberId; + } + + return { + subscribe + } + } + } + public static custom(method: (...args: TArgs) => Promise, applyFactory?: () => (entity: Ref) => void) { return () => { const apply = applyFactory ? applyFactory() : () => { }; diff --git a/src/Bones.UI/core/eventQueue.ts b/src/Bones.UI/core/eventQueue.ts index 24ebf2f..fec2709 100644 --- a/src/Bones.UI/core/eventQueue.ts +++ b/src/Bones.UI/core/eventQueue.ts @@ -1,6 +1,8 @@ import _ from "lodash"; import Ajv, { JSONSchemaType, ValidateFunction } from "ajv"; +import { uuidv4 } from "../tools"; + interface WindowsMessage { id: string; topic: string; @@ -55,8 +57,7 @@ export class EventQueue { this.publishInternal(topic, payload); if (window.top && window.top !== window.self) { - this.messageCounter++; - const id = "remote_" + this.messageCounter; + const id = uuidv4(); const message: WindowsMessage = { id, @@ -65,6 +66,7 @@ export class EventQueue { }; this.buffer[this.messageCounter % bufferSize] = message.id; + this.messageCounter++; window.top.postMessage(JSON.stringify(message), "*"); } diff --git a/src/Bones.UI/core/serviceFactory.ts b/src/Bones.UI/core/serviceFactory.ts index 6006535..3c8c680 100644 --- a/src/Bones.UI/core/serviceFactory.ts +++ b/src/Bones.UI/core/serviceFactory.ts @@ -6,6 +6,7 @@ import { INotifyService } from "../abstractions"; export class ServiceFactory { static http: AxiosInstance = axios; + static getAsPost = false; private notifyService: NotifyService; EntityDetails: new (dto: TDetailsDTO) => TDetails; @@ -40,7 +41,17 @@ export class ServiceFactory { const getMany = async (filter?: TFilter) => { const realUrl = typeof url === "string" ? url : url(); - const response = await ServiceFactory.http.get(buildURL(realUrl, filter)); + let response; + + // If the service is configured to use GET as POST to prevent issues with large query strings, + // we send the filter as a POST request with a "_method" parameter to indicate it's a GET request. + if (ServiceFactory.getAsPost && filter) { + response = await ServiceFactory.http.post(buildURL(realUrl, { "_method": "GET" }), filter); + } + else { + response = await ServiceFactory.http.get(buildURL(realUrl, filter)); + } + const dtos: TInfosDTO[] = response.data; return dtos.map(dto => new entity(dto)); @@ -145,8 +156,8 @@ export class ServiceFactory { build(target: T, source1: U, source2: V, source3: W, source4: X): T & U & V & W & X build(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y): T & U & V & W & X & Y build(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y, source6: Z): T & U & V & W & X & Y & Z - build(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y, source6: Z, source7: Z1 ): T & U & V & W & X & Y & Z & Z1 - build(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y, source6: Z, source7: Z1, source8: Z2 ): T & U & V & W & X & Y & Z & Z1 & Z2 + build(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y, source6: Z, source7: Z1): T & U & V & W & X & Y & Z & Z1 + build(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y, source6: Z, source7: Z1, source8: Z2): T & U & V & W & X & Y & Z & Z1 & Z2 build(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y, source6: Z, source7: Z1, source8: Z2, source9: Z3): T & U & V & W & X & Y & Z & Z1 & Z2 & Z3 build(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y, source6: Z, source7: Z1, source8: Z2, source9: Z3, source10: Z4): T & U & V & W & X & Y & Z & Z1 & Z2 & Z3 & Z4 build(target: T, source1: U, source2: V, source3: W, source4: X, source5: Y, source6: Z, source7: Z1, source8: Z2, source9: Z3, source10: Z4, source11: Z5): T & U & V & W & X & Y & Z & Z1 & Z2 & Z3 & Z4 & Z5 diff --git a/tests/Bones.UI.Tests/jest.config.js b/tests/Bones.UI.Tests/jest.config.js index 6c9773c..b860027 100644 --- a/tests/Bones.UI.Tests/jest.config.js +++ b/tests/Bones.UI.Tests/jest.config.js @@ -1,5 +1,5 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { preset: 'ts-jest', - testEnvironment: 'node' + testEnvironment: 'jsdom', }; \ No newline at end of file diff --git a/tests/Bones.UI.Tests/services/testUserService.ts b/tests/Bones.UI.Tests/services/testUserService.ts index 5b4c9e3..2bb3b96 100644 --- a/tests/Bones.UI.Tests/services/testUserService.ts +++ b/tests/Bones.UI.Tests/services/testUserService.ts @@ -23,6 +23,13 @@ const AccountLoginFactory = new ServiceFactory(testUserServiceFactory); export const useTestUserTrack = ComposableFactory.trackRef(testUserServiceFactory); +export const useTestUserSubscribe = ComposableFactory.subscribe(testUserServiceFactory); + +const { subscribe } = useTestUserSubscribe(); + +subscribe("add", (ev, payload) => { + console.log(ev, payload); +}); export const useLogin = ComposableFactory.custom(AccountLoginFactory.login, () => { const { sync } = useTestUsersSync(); diff --git a/tests/Bones.UI.Tests/tests/plugins.test.ts b/tests/Bones.UI.Tests/tests/plugins.test.ts new file mode 100644 index 0000000..ccd0ea4 --- /dev/null +++ b/tests/Bones.UI.Tests/tests/plugins.test.ts @@ -0,0 +1,21 @@ +import { useTranslations } from '@dative-gpi/bones-ui'; + +describe('Translation plugin', () => { + const { $tr } = useTranslations(); + + it('should return the correct default value with formatted parameter', () => { + const result = $tr('code', 'default value : {0}m', "72"); + expect(result).toBe('default value : 72m'); + }); + + it('should return the correct default value with formatted parameters', () => { + const result = $tr('code', 'default value : {0}m, {1}°C', "72", 85); + expect(result).toBe('default value : 72m, 85°C'); + }); + + it('should return the correct default value with equals formatted parameters', () => { + const result = $tr('code', 'default value : {0}m, {1}°C', "72", "72"); + expect(result).toBe('default value : 72m, 72°C'); + }); +}); +