diff --git a/.esbuild/build.js b/.esbuild/build.js index 7a7e82d5..68fde21e 100644 --- a/.esbuild/build.js +++ b/.esbuild/build.js @@ -6,12 +6,12 @@ const esbuild = require('esbuild'); await Promise.all([ esbuild.build({ ...cjsConfig, - outfile: 'lib/index.cjs.js', + outfile: 'dist/index.cjs.js', }), esbuild.build({ ...esmConfig, - outdir: 'lib', + outdir: 'dist', }), ]); } catch (error) { diff --git a/__mocks__/config.mock.ts b/__mocks__/config.mock.ts index 064b1edf..f0670b13 100644 --- a/__mocks__/config.mock.ts +++ b/__mocks__/config.mock.ts @@ -6,7 +6,6 @@ import { LIMITS_MOCK } from './limits.mock'; import { WATERMARK_MOCK } from './watermark.mock'; export const MOCK_CONFIG: Configuration = { - ablyKey: 'unit-test-ably-key', apiKey: 'unit-test-api-key', apiUrl: 'http://unit-test-api-url', conferenceLayerUrl: 'https://unit-test-conference-layer-url', diff --git a/__mocks__/io.mock.ts b/__mocks__/io.mock.ts index 78795ee2..e38ffb8a 100644 --- a/__mocks__/io.mock.ts +++ b/__mocks__/io.mock.ts @@ -1,5 +1,5 @@ import { jest } from '@jest/globals'; -import { Room } from '../src/lib/socket'; +import { Room } from '@superviz/socket-client'; export const MOCK_IO = { ClientState: { diff --git a/jest.setup.js b/jest.setup.js index e631e1b8..ff4045e5 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -36,4 +36,4 @@ global.DOMPoint = class { } }; -jest.mock('./src/lib/socket', () => MOCK_IO); +jest.mock('@superviz/socket-client', () => MOCK_IO); diff --git a/package.json b/package.json index e8ceb3bd..22a0f058 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,14 @@ "name": "@superviz/sdk", "version": "0.0.0-development", "description": "SuperViz SDK", - "main": "./lib/index.js", - "types": "./lib/index.d.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { - "import": "./lib/index.js", - "require": "./lib/index.cjs.js" + "import": "./dist/index.js", + "require": "./dist/index.cjs.js" }, "files": [ - "lib" + "dist" ], "scripts": { "prepare": "husky install", @@ -92,7 +92,7 @@ "luxon": "^3.4.4", "rxjs": "^7.8.1", "semantic-release-version-file": "^1.0.2", - "socket.io-client": "^4.7.5", + "@superviz/socket-client": "^1.10.0", "zod": "^3.23.8" }, "config": { diff --git a/src/common/styles/global.css b/src/common/styles/global.css index 46ce494f..08ce6b88 100644 --- a/src/common/styles/global.css +++ b/src/common/styles/global.css @@ -5,7 +5,8 @@ --sv-primary: 98, 16, 204; } -html, body { +html, +body { width: 100%; height: 100%; overflow: hidden; @@ -46,14 +47,14 @@ html, body { display: none; } - /* Presence Mouse */ .pointer-mouse { display: flex; - height: 17px; - width: 17px; + height: 15px; + width: 16px; background-image: url(https://production.cdn.superviz.com/static/pointers/0.svg); + background-repeat: no-repeat; } .mouse-user-name { @@ -76,4 +77,4 @@ html, body { display: block; z-index: 2; transition: all 150ms linear, opacity 100s ease-in; -} \ No newline at end of file +} diff --git a/src/common/types/cdn.types.ts b/src/common/types/cdn.types.ts index 5742c5a7..5fbf41ae 100644 --- a/src/common/types/cdn.types.ts +++ b/src/common/types/cdn.types.ts @@ -1,4 +1,4 @@ -import { +import type { CanvasPin, HTMLPin, Comments, @@ -8,15 +8,18 @@ import { WhoIsOnline, FormElements, } from '../../components'; -import { RealtimeComponentEvent, RealtimeComponentState } from '../../components/realtime/types'; -import { LauncherFacade } from '../../core/launcher/types'; -import { +import type { + RealtimeComponentEvent, + RealtimeComponentState, +} from '../../components/realtime/types'; +import type { LauncherFacade } from '../../core/launcher/types'; +import type { CamerasPosition, LayoutMode, LayoutPosition, } from '../../services/video-conference-manager/types'; -import { +import type { DeviceEvent, MeetingEvent, RealtimeEvent, @@ -31,6 +34,10 @@ import { } from './events.types'; import { ParticipantType } from './participant.types'; import { SuperVizSdkOptions } from './sdk-options.types'; +import { StoreType } from '../types/stores.types'; +import { PresenceEvents } from '@superviz/socket-client'; +import { FieldEvents } from '../../components/form-elements/types'; +import { PinMode } from '../../web-components/comments/components/types'; export interface SuperVizCdn { init: (apiKey: string, options: SuperVizSdkOptions) => Promise; @@ -59,4 +66,9 @@ export interface SuperVizCdn { FormElements: typeof FormElements; RealtimeComponentState: typeof RealtimeComponentState; RealtimeComponentEvent: typeof RealtimeComponentEvent; + StoreType: typeof StoreType; + PresenceEvents: typeof PresenceEvents; + FieldEvents: typeof FieldEvents; + PinMode: typeof PinMode; + Comment: typeof Comment; } diff --git a/src/common/types/sdk-options.types.ts b/src/common/types/sdk-options.types.ts index 80ec0b85..33b577f9 100644 --- a/src/common/types/sdk-options.types.ts +++ b/src/common/types/sdk-options.types.ts @@ -11,8 +11,9 @@ export interface SuperVizSdkOptions { roomId: string; participant: { id: string; - name: string; + name?: string; avatar?: Avatar; + email?: string; }; group: Group; customColors?: ColorsVariables; diff --git a/src/components/base/index.ts b/src/components/base/index.ts index cbcae7de..6d72e47e 100644 --- a/src/components/base/index.ts +++ b/src/components/base/index.ts @@ -1,4 +1,4 @@ -import * as Socket from '../../lib/socket'; +import * as Socket from '@superviz/socket-client'; import { ComponentLifeCycleEvent } from '../../common/types/events.types'; import { Group } from '../../common/types/participant.types'; diff --git a/src/components/comments/index.test.ts b/src/components/comments/index.test.ts index 3b065496..66231b66 100644 --- a/src/components/comments/index.test.ts +++ b/src/components/comments/index.test.ts @@ -11,7 +11,7 @@ import ApiService from '../../services/api'; import { IOC } from '../../services/io'; import { Presence3DManager } from '../../services/presence-3d-manager'; import { useGlobalStore } from '../../services/stores'; -import { CommentsFloatButton } from '../../web-components'; +import type { CommentsFloatButton } from '../../web-components/comments/components/float-button'; import { ComponentNames } from '../types'; import { PinAdapter, CommentsSide, Annotation, PinCoordinates } from './types'; diff --git a/src/components/comments/index.ts b/src/components/comments/index.ts index d0bd4368..24ef857d 100644 --- a/src/components/comments/index.ts +++ b/src/components/comments/index.ts @@ -5,7 +5,7 @@ import { Logger } from '../../common/utils'; import ApiService from '../../services/api'; import config from '../../services/config'; import subject from '../../services/stores/subject'; -import type { Comments as CommentElement } from '../../web-components'; +import type { Comments as CommentElement } from '../../web-components/comments'; import { CommentsFloatButton } from '../../web-components/comments/components/float-button'; import { BaseComponent } from '../base'; import { ComponentNames } from '../types'; @@ -152,6 +152,7 @@ export class Comments extends BaseComponent { * @returns {void} */ protected start(): void { + if (typeof window === 'undefined') return; this.clientUrl = window.location.href; this.positionComments(); diff --git a/src/components/form-elements/index.ts b/src/components/form-elements/index.ts index be3e5b44..8b14a522 100644 --- a/src/components/form-elements/index.ts +++ b/src/components/form-elements/index.ts @@ -1,4 +1,4 @@ -import type { SocketEvent } from '../../lib/socket'; +import type { SocketEvent } from '@superviz/socket-client'; import { Participant } from '../../common/types/participant.types'; import { StoreType } from '../../common/types/stores.types'; diff --git a/src/components/form-elements/types.ts b/src/components/form-elements/types.ts index eacd4527..5d07277f 100644 --- a/src/components/form-elements/types.ts +++ b/src/components/form-elements/types.ts @@ -1,4 +1,4 @@ -import { SocketEvent } from '../../lib/socket'; +import { SocketEvent } from '@superviz/socket-client'; export type FormElementsProps = { fields?: string[] | string; diff --git a/src/components/presence-mouse/canvas/index.ts b/src/components/presence-mouse/canvas/index.ts index 3d04b265..d8f328c5 100644 --- a/src/components/presence-mouse/canvas/index.ts +++ b/src/components/presence-mouse/canvas/index.ts @@ -1,4 +1,4 @@ -import * as Socket from '../../../lib/socket'; +import * as Socket from '@superviz/socket-client'; import { throttle } from 'lodash'; import { RealtimeEvent } from '../../../common/types/events.types'; diff --git a/src/components/presence-mouse/html/index.ts b/src/components/presence-mouse/html/index.ts index 0b47e02e..e4d930c8 100644 --- a/src/components/presence-mouse/html/index.ts +++ b/src/components/presence-mouse/html/index.ts @@ -1,4 +1,4 @@ -import * as Socket from '../../../lib/socket'; +import * as Socket from '@superviz/socket-client'; import { isEqual } from 'lodash'; import { Subscription, fromEvent, throttleTime } from 'rxjs'; @@ -205,6 +205,8 @@ export class PointersHTML extends BaseComponent { * @returns {void} */ private onMyParticipantMouseLeave = (event: MouseEvent): void => { + if (typeof window === 'undefined') return; + const { left, top, right, bottom } = this.container.getBoundingClientRect(); const isInsideContainer = event.x > left && event.y > top && event.x < right && event.y < bottom; diff --git a/src/components/realtime/channel.test.ts b/src/components/realtime/channel.test.ts index d49471a5..ecdd9cff 100644 --- a/src/components/realtime/channel.test.ts +++ b/src/components/realtime/channel.test.ts @@ -34,14 +34,14 @@ describe('Realtime Channel', () => { ChannelInstance['state'] = RealtimeChannelState.DISCONNECTED; const spy = jest.spyOn(ChannelInstance['logger'], 'log' as any); - ChannelInstance.publish('test'); + ChannelInstance.publish('test', {}); expect(spy).toHaveBeenCalled(); }); test('should publish an event', () => { const spy = jest.spyOn(ChannelInstance['channel'], 'emit' as any); - ChannelInstance.publish('test'); + ChannelInstance.publish('test', {}); expect(spy).toHaveBeenCalled(); }); diff --git a/src/components/realtime/channel.ts b/src/components/realtime/channel.ts index 6775244f..9a30e783 100644 --- a/src/components/realtime/channel.ts +++ b/src/components/realtime/channel.ts @@ -1,12 +1,20 @@ -import * as Socket from '../../lib/socket'; +import * as Socket from '@superviz/socket-client'; import throttle from 'lodash/throttle'; -import { ComponentLifeCycleEvent } from '../../common/types/events.types'; import { Participant } from '../../common/types/participant.types'; import { Logger, Observable, Observer } from '../../common/utils'; import { IOC } from '../../services/io'; -import { RealtimeChannelEvent, RealtimeChannelState, RealtimeData, RealtimeMessage } from './types'; +import { + RealtimeChannelEvent, + RealtimeChannelState, + RealtimeData, + RealtimeMessage, + RealtimePublish, + RealtimeChannelSubscribe, + Callback, +} from './types'; +import { RealtimePresence } from './presence'; export class Channel extends Observable { private name: string; @@ -19,6 +27,7 @@ export class Channel extends Observable { event: string; callback: (data: unknown) => void; }> = []; + public participant: RealtimePresence; constructor( name: string, @@ -36,6 +45,7 @@ export class Channel extends Observable { this.subscribeToRealtimeEvents(); this.logger.log('started'); + this.participant = new RealtimePresence(this.channel); } public async disconnect(): Promise { @@ -51,16 +61,11 @@ export class Channel extends Observable { /** * @function publish - * @description Publishes an event with optional data to the channel. + * @description Publishes an event with data to the channel. * @param event - The name of the event to publish. - * @param data - Optional data to be sent along with the event. + * @param data - Data to be sent along with the event. */ - public publish = throttle((event: string, data?: unknown): void => { - if (Object.values(ComponentLifeCycleEvent).includes(event as ComponentLifeCycleEvent)) { - this.publishEventToClient(event, data); - return; - } - + public publish: RealtimePublish = throttle((event: string, data): void => { if (this.state !== RealtimeChannelState.CONNECTED) { const message = `Realtime channel ${this.name} is not started yet. You can't publish event ${event} before start`; this.logger.log(message); @@ -78,7 +83,10 @@ export class Channel extends Observable { * @param event - The name of the event to subscribe to. * @param callback - The callback function to handle the received data. It takes a parameter of type `RealtimeMessage` or `string`. */ - public subscribe = (event: string, callback: (data: RealtimeMessage | string) => void): void => { + public subscribe: RealtimeChannelSubscribe = ( + event: string, + callback: Callback, + ): void => { if (this.state !== RealtimeChannelState.CONNECTED) { this.callbacksToSubscribeWhenJoined.push({ event, callback }); return; @@ -97,13 +105,11 @@ export class Channel extends Observable { * @param event - The event to unsubscribe from. * @param callback - An optional callback function to be called when the event is unsubscribed. */ - public unsubscribe = ( + public unsubscribe: RealtimeChannelSubscribe = ( event: string, - callback?: (data: RealtimeMessage | string) => void, + callback?: Callback, ): void => { - if (!this.observers[event]) return; - - this.observers[event].unsubscribe(callback); + this.observers[event]?.unsubscribe(callback); }; /** @@ -116,7 +122,10 @@ export class Channel extends Observable { this.logger.log('realtime component @ changeState - state changed', state); this.state = state; - this.publishEventToClient(RealtimeChannelEvent.REALTIME_CHANNEL_STATE_CHANGED, this.state); + this.publishEventToClient( + RealtimeChannelEvent.REALTIME_CHANNEL_STATE_CHANGED, + this.state, + ); } private subscribeToRealtimeEvents(): void { @@ -136,13 +145,13 @@ export class Channel extends Observable { this.channel.on(`message:${this.name}`, (event) => { this.logger.log('message received', event); - this.publishEventToClient(event.data.name, { + this.publishEventToClient(event.data.name, { data: event.data.payload, - participantId: event?.presence?.id || null, + participantId: event?.presence?.id ?? null, name: event.data.name, timestamp: event.timestamp, connectionId: event.connectionId, - } as RealtimeMessage); + }); }); } @@ -214,11 +223,9 @@ export class Channel extends Observable { * @param data - data to publish * @returns {void} */ - private publishEventToClient = (event: string, data?: unknown): void => { + private publishEventToClient = (event: string, data?: T): void => { this.logger.log('realtime channel @ publishEventToClient', { event, data }); - if (!this.observers[event]) return; - - this.observers[event].publish(data); + this.observers[event]?.publish(data); }; } diff --git a/src/components/realtime/index.test.ts b/src/components/realtime/index.test.ts index 97d7d209..6449e1a8 100644 --- a/src/components/realtime/index.test.ts +++ b/src/components/realtime/index.test.ts @@ -9,6 +9,7 @@ import { RealtimeComponentState } from './types'; import { Realtime } from '.'; import { LIMITS_MOCK } from '../../../__mocks__/limits.mock'; +import { StoreType } from '../../common/types/stores.types'; jest.mock('lodash/throttle', () => jest.fn((fn) => fn)); jest.useFakeTimers(); @@ -22,6 +23,9 @@ describe('realtime component', () => { console.error = jest.fn(); console.debug = jest.fn(); + const { hasJoinedRoom } = useStore(StoreType.GLOBAL); + hasJoinedRoom.publish(true); + RealtimeComponentInstance = new Realtime(); RealtimeComponentInstance.attach({ ioc: new IOC(MOCK_LOCAL_PARTICIPANT), @@ -55,13 +59,13 @@ describe('realtime component', () => { }); describe('connect', () => { - test('should log an error when trying to create a channel before start', () => { + test('should return a promise when trying to create a channel before start', () => { + RealtimeComponentInstance['start'](); RealtimeComponentInstance['state'] = RealtimeComponentState.STOPPED; - const spy = jest.spyOn(RealtimeComponentInstance['logger'], 'log'); - RealtimeComponentInstance.connect('test'); + const channel = RealtimeComponentInstance.connect('test'); - expect(spy).toHaveBeenCalled(); + expect(channel instanceof Promise).toBe(true); }); test('should create a new channel', () => { diff --git a/src/components/realtime/index.ts b/src/components/realtime/index.ts index 9fcc433c..d93339e8 100644 --- a/src/components/realtime/index.ts +++ b/src/components/realtime/index.ts @@ -1,16 +1,21 @@ +import { ComponentLifeCycleEvent } from '../../common/types/events.types'; import { Participant } from '../../common/types/participant.types'; import { StoreType } from '../../common/types/stores.types'; import { Logger } from '../../common/utils'; +import { useGlobalStore } from '../../services/stores'; import { BaseComponent } from '../base'; import { ComponentNames } from '../types'; import { Channel } from './channel'; +import { RealtimePresence } from './presence'; + import { + Callback, RealtimeChannelEvent, RealtimeChannelState, RealtimeComponentEvent, RealtimeComponentState, - RealtimeMessage, + RealtimeComponentSubscribe, } from './types'; export class Realtime extends BaseComponent { @@ -25,6 +30,7 @@ export class Realtime extends BaseComponent { event: string; callback: (data: unknown) => void; }> = []; + public participant: RealtimePresence; constructor() { super(); @@ -41,25 +47,36 @@ export class Realtime extends BaseComponent { * @param name - channel name * @returns {Channel} */ - public connect(name: string): Channel { - if (this.state !== RealtimeComponentState.STARTED) { - const message = - "[SuperViz] Realtime component is not started yet. You can't connect to a channel before start"; + public connect(name: string): Promise { + if (!this.channel) { + return new Promise((resolve) => { + const { localParticipant } = useGlobalStore(); - this.logger.log(message); - console.warn(message); - return; + localParticipant.subscribe('connect-after-init', (participant) => { + if (!participant.activeComponents.includes(ComponentNames.REALTIME)) return; + + localParticipant.unsubscribe('connect-after-init'); + resolve(this.connect(name)); + }); + }); } let channel: Channel = this.channels.get(name); - - if (channel) return channel; + if (channel) return channel as unknown as Promise; channel = new Channel(name, this.ioc, this.localParticipant, this.connectionLimit); - this.channels.set(name, channel); - return channel; + if (this.state === RealtimeComponentState.STARTED) { + return channel as unknown as Promise; + } + + return new Promise((resolve) => { + this.subscribe(RealtimeComponentEvent.REALTIME_STATE_CHANGED, (state) => { + if (state !== RealtimeComponentState.STARTED) return; + resolve(channel); + }); + }); } /** @@ -69,22 +86,30 @@ export class Realtime extends BaseComponent { * @param event - The name of the event to subscribe to. * @param callback - The callback function to handle the received data. It takes a parameter of type `RealtimeMessage` or `string`. */ - public subscribe = (event: string, callback: (data: RealtimeMessage | string) => void): void => { + public subscribe: RealtimeComponentSubscribe = ( + event: string, + callback: Callback, + ): void => { if (!this.channel) { this.callbacksToSubscribeWhenJoined.push({ event, callback }); return; } - this.channel?.subscribe(event, callback); + this.channel.subscribe(event, callback); }; /** * @function publish * @description Publishes an event with optional data to the channel. * @param event - The name of the event to publish. - * @param data - Optional data to be sent along with the event. + * @param data - Data to be sent along with the event. */ - public publish = (event: string, data?: unknown): void => { + public publish = (event: string, data: T): void => { + if (ComponentLifeCycleEvent[event.toUpperCase() as keyof typeof ComponentLifeCycleEvent]) { + this.channel['publishEventToClient'](event, data); + return; + } + this.channel?.publish(event, data); }; @@ -94,7 +119,10 @@ export class Realtime extends BaseComponent { * @param event - The event to unsubscribe from. * @param callback - An optional callback function to be called when the event is unsubscribed. */ - public unsubscribe = (event: string, callback?: (data: RealtimeMessage) => void): void => { + public unsubscribe: RealtimeComponentSubscribe = ( + event: string, + callback?: Callback, + ): void => { this.channel?.unsubscribe(event, callback); }; @@ -113,6 +141,7 @@ export class Realtime extends BaseComponent { this.callbacksToSubscribeWhenJoined = []; this.channel.unsubscribe(RealtimeChannelEvent.REALTIME_CHANNEL_STATE_CHANGED); + this.participant = this.channel.participant; this.channels.set('default', this.channel); }); } diff --git a/src/components/realtime/presence.test.ts b/src/components/realtime/presence.test.ts new file mode 100644 index 00000000..e1baa777 --- /dev/null +++ b/src/components/realtime/presence.test.ts @@ -0,0 +1,72 @@ +import { PresenceEvent, PresenceEvents, Room } from '@superviz/socket-client'; + +import { RealtimePresence } from './presence'; +import { MOCK_IO } from '../../../__mocks__/io.mock'; + +describe('realtime component', () => { + let RealtimePresenceInstance: RealtimePresence; + + beforeEach(() => { + jest.clearAllMocks(); + + const room = new MOCK_IO.Realtime('', '', ''); + RealtimePresenceInstance = new RealtimePresence(room.connect() as unknown as Room); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Realtime Participant Presence', () => { + test('should update presence', () => { + const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'update' as any); + const data = { + id: '123', + name: 'John Doe', + }; + + RealtimePresenceInstance['update'](data); + + expect(spy).toHaveBeenCalledWith(data); + }); + + test('should subscribe to presence events', () => { + const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'on' as any); + const event = MOCK_IO.PresenceEvents.UPDATE; + const callback = jest.fn(); + + RealtimePresenceInstance['subscribe'](event as PresenceEvents, callback); + + expect(spy).toHaveBeenCalledWith(event, callback); + }); + + test('should unsubscribe from presence events', () => { + const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'off' as any); + const event = MOCK_IO.PresenceEvents.UPDATE; + + RealtimePresenceInstance['unsubscribe'](event as PresenceEvents); + + expect(spy).toHaveBeenCalledWith(event); + }); + + test('should get all presences', () => { + const spy = jest.spyOn(RealtimePresenceInstance['room'].presence as any, 'get' as any); + RealtimePresenceInstance['getAll'](); + + expect(spy).toHaveBeenCalled(); + }); + + test('should get all presences and resolve', async () => { + RealtimePresenceInstance['room'].presence.get = jest.fn((callback) => { + callback([{ id: '123', name: 'John Doe' }] as PresenceEvent[]); + }); + const presences = RealtimePresenceInstance['getAll'](); + + expect(presences instanceof Promise).toBe(true); + + const data = await presences; + + expect(data).toEqual([{ id: '123', name: 'John Doe' }]); + }); + }); +}); diff --git a/src/components/realtime/presence.ts b/src/components/realtime/presence.ts new file mode 100644 index 00000000..f3fe65c7 --- /dev/null +++ b/src/components/realtime/presence.ts @@ -0,0 +1,43 @@ +import { Logger } from '../../common/utils'; +import * as Socket from '@superviz/socket-client'; + +export class RealtimePresence { + private logger: Logger; + + constructor(private room: Socket.Room) { + this.logger = new Logger('@superviz/sdk/realtime-presence'); + } + + public update(data: T) { + this.logger.log('Realtime Presence @ update presence', data); + this.room.presence.update(data); + } + + public subscribe( + event: Socket.PresenceEventsArg, + callback: Socket.PresenceCallback, + ) { + this.logger.log('Realtime Presence @ subscribe', event); + this.room.presence.on(event, callback); + } + + public unsubscribe(event: Socket.PresenceEventsArg) { + this.logger.log('Realtime Presence @ unsubscribe', event); + this.room.presence.off(event); + } + + public async getAll() { + this.logger.log('Realtime Presence @ get all'); + return new Promise((resolve, reject) => { + this.room.presence.get( + (data) => resolve(data), + (error) => { + const message = `[SuperViz] ${error.name} - ${error.message}`; + this.logger.log(error); + console.error(message); + reject(error); + }, + ); + }); + } +} diff --git a/src/components/realtime/types.ts b/src/components/realtime/types.ts index e755ff6f..d04d3182 100644 --- a/src/components/realtime/types.ts +++ b/src/components/realtime/types.ts @@ -22,10 +22,32 @@ export type RealtimeData = { payload: any; }; -export type RealtimeMessage = { +export type RealtimeMessage = { name: string; connectionId: string; participantId: string | null; - data: unknown; + data: T; timestamp: number; }; + +type ChannelEvents = RealtimeChannelEvent | `${RealtimeChannelEvent}`; +type ComponentEvents = RealtimeComponentEvent | `${RealtimeComponentEvent}`; + +type ChannelState = RealtimeChannelState | `${RealtimeChannelState}`; +type ComponentState = RealtimeComponentState | `${RealtimeComponentState}`; + +type States = ChannelState | ComponentState; + +export type Callback = (data: T extends States ? T : RealtimeMessage) => void; + +export type RealtimeChannelSubscribe = { + (event: ChannelEvents, callback?: Callback): void; + (event: string, callback?: Callback): void; +}; + +export type RealtimeComponentSubscribe = { + (event: ComponentEvents, callback?: Callback): void; + (event: string, callback?: Callback): void; +}; + +export type RealtimePublish = (event: string, data: T) => void; diff --git a/src/components/video/index.test.ts b/src/components/video/index.test.ts index 345d3986..1a8b362f 100644 --- a/src/components/video/index.test.ts +++ b/src/components/video/index.test.ts @@ -1,6 +1,6 @@ import { TextEncoder, TextDecoder } from 'util'; -import { PresenceEvent } from '../../lib/socket'; +import { PresenceEvent } from '@superviz/socket-client'; import { MOCK_CONFIG } from '../../../__mocks__/config.mock'; import { EVENT_BUS_MOCK } from '../../../__mocks__/event-bus.mock'; diff --git a/src/components/video/index.ts b/src/components/video/index.ts index 515d3517..e4847a3d 100644 --- a/src/components/video/index.ts +++ b/src/components/video/index.ts @@ -1,4 +1,4 @@ -import { PresenceEvent, PresenceEvents, Room } from '../../lib/socket'; +import { PresenceEvent, PresenceEvents, Room } from '@superviz/socket-client'; import { ColorsVariables } from '../../common/types/colors.types'; import { @@ -614,7 +614,6 @@ export class VideoConference extends BaseComponent { }); this.connectionService.removeListeners(); - this.publish(MeetingEvent.DESTROY); this.publish(MeetingEvent.MY_PARTICIPANT_LEFT, this.localParticipant); this.unsubscribeFromVideoEvents(); diff --git a/src/components/who-is-online/index.ts b/src/components/who-is-online/index.ts index 97b7bafb..c8f36d4d 100644 --- a/src/components/who-is-online/index.ts +++ b/src/components/who-is-online/index.ts @@ -1,11 +1,11 @@ -import { PresenceEvent, PresenceEvents } from '../../lib/socket'; +import { PresenceEvent, PresenceEvents } from '@superviz/socket-client'; import { RealtimeEvent, WhoIsOnlineEvent } from '../../common/types/events.types'; import { Participant, Avatar } from '../../common/types/participant.types'; import { StoreType } from '../../common/types/stores.types'; import { Logger } from '../../common/utils'; import { Following } from '../../services/stores/who-is-online/types'; -import { WhoIsOnline as WhoIsOnlineElement } from '../../web-components'; +import type { WhoIsOnline as WhoIsOnlineElement } from '../../web-components/who-is-online'; import { DropdownOption } from '../../web-components/dropdown/types'; import { BaseComponent } from '../base'; import { ComponentNames } from '../types'; diff --git a/src/core/index.test.ts b/src/core/index.test.ts index f3042fd1..17ba8235 100644 --- a/src/core/index.test.ts +++ b/src/core/index.test.ts @@ -88,7 +88,7 @@ describe('initialization errors', () => { ...SIMPLE_INITIALIZATION_MOCK, participant: undefined as unknown as SuperVizSdkOptions['participant'], }), - ).rejects.toThrow('Participant name and id is required'); + ).rejects.toThrow('Participant id is required'); }); test('should throw an error if no participant id is provided', async () => { @@ -97,16 +97,7 @@ describe('initialization errors', () => { ...SIMPLE_INITIALIZATION_MOCK, participant: { name: 'unit-test-participant-name' } as SuperVizSdkOptions['participant'], }), - ).rejects.toThrow('Participant name and id is required'); - }); - - test('should throw an error if participant name is not provided', async () => { - await expect( - sdk(UNIT_TEST_API_KEY, { - ...SIMPLE_INITIALIZATION_MOCK, - participant: { id: 'unit-test-participant-id' } as SuperVizSdkOptions['participant'], - }), - ).rejects.toThrow('Participant name and id is required'); + ).rejects.toThrow('Participant id is required'); }); test('should throw an error if no group name is provided', async () => { @@ -118,14 +109,6 @@ describe('initialization errors', () => { ).rejects.toThrow('Group fields is required'); }); - test('should throw an error if envoriment is invalid', async () => { - ApiService.fetchConfig = jest.fn().mockResolvedValue(undefined); - - expect(sdk(UNIT_TEST_API_KEY, SIMPLE_INITIALIZATION_MOCK)).rejects.toThrow( - 'Failed to load configuration from server', - ); - }); - test('should throw an error if custom colors variables names are invalid', async () => { const colorKey = 'invalid-color'; @@ -189,4 +172,26 @@ describe('initialization errors', () => { '[SuperViz] Participant id is invalid, it should be between 2 and 64 characters and only accept letters, numbers and special characters: -_&@+=,(){}[]/«».:|\'"', ); }); + + test('should throw an error if participant email is invalid', async () => { + await expect( + sdk(UNIT_TEST_API_KEY, { + ...SIMPLE_INITIALIZATION_MOCK, + participant: { ...SIMPLE_INITIALIZATION_MOCK.participant, email: 'invalid-email' }, + }), + ).rejects.toThrow('[SuperViz] Participant email is invalid'); + }); + + test('should throw an error if participant does not exist and name is not defined', async () => { + ApiService.fetchParticipant = jest.fn().mockRejectedValue({}); + + await expect( + sdk(UNIT_TEST_API_KEY, { + ...SIMPLE_INITIALIZATION_MOCK, + participant: { ...SIMPLE_INITIALIZATION_MOCK.participant, name: undefined }, + }), + ).rejects.toThrow( + '[SuperViz] - Participant does not exist, create the user in the API or add the name in the initialization to initialize the SuperViz room.', + ); + }); }); diff --git a/src/core/index.ts b/src/core/index.ts index dfa89e42..1f27f67c 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -31,6 +31,11 @@ function validateId(id: string): boolean { return true; } +function validateEmail(email: string): boolean { + const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + return emailPattern.test(email); +} + /** * @function validateOptions * @description Validate the options passed to the SDK @@ -51,8 +56,8 @@ const validateOptions = ({ throw new Error('[SuperViz] Group fields is required'); } - if (!participant || !participant.id || !participant.name) { - throw new Error('[SuperViz] Participant name and id is required'); + if (!participant || !participant.id) { + throw new Error('[SuperViz] Participant id is required'); } if (!roomId) { @@ -70,6 +75,10 @@ const validateOptions = ({ '[SuperViz] Participant id is invalid, it should be between 2 and 64 characters and only accept letters, numbers and special characters: -_&@+=,(){}[]/«».:|\'"', ); } + + if (participant.email && !validateEmail(participant.email)) { + throw new Error('[SuperViz] Participant email is invalid'); + } }; /** @@ -81,13 +90,13 @@ const validateColorsVariablesNames = (colors: ColorsVariables) => { Object.entries(colors).forEach(([key, value]) => { if (!Object.values(ColorsVariablesNames).includes(key as ColorsVariablesNames)) { throw new Error( - `Color ${key} is not a valid color variable name. Please check the documentation for more information.`, + `[SuperViz] Color ${key} is not a valid color variable name. Please check the documentation for more information.`, ); } if (!/^(\d{1,3}\s){2}\d{1,3}$/.test(value)) { throw new Error( - `Color ${key} is not a valid color variable value. Please check the documentation for more information.`, + `[SuperViz] Color ${key} is not a valid color variable value. Please check the documentation for more information.`, ); } }); @@ -140,24 +149,17 @@ const init = async (apiKey: string, options: SuperVizSdkOptions): Promise { - throw new Error('Failed to load configuration from server'); + throw new Error('[SuperViz] Failed to load configuration from server'); }); - if (!environment || !environment.ablyKey) { - throw new Error('Failed to load configuration from server'); - } - - const { ablyKey } = environment; - const { participant, roomId, customColors: colors } = options; + const { participant, roomId, customColors } = options; config.setConfig({ apiUrl, - ablyKey, apiKey, conferenceLayerUrl, environment: (options.environment as EnvironmentTypes) ?? EnvironmentTypes.PROD, @@ -165,19 +167,38 @@ const init = async (apiKey: string, options: SuperVizSdkOptions): Promise null); - return LauncherFacade(options); + if (!apiParticipant && !participant.name) { + throw new Error( + '[SuperViz] - Participant does not exist, create the user in the API or add the name in the initialization to initialize the SuperViz room.', + ); + } + + if (!apiParticipant) { + await ApiService.createParticipant({ + participantId: participant.id, + name: participant?.name, + avatar: participant.avatar?.imageUrl, + email: participant?.email, + }); + } + + return LauncherFacade({ + ...options, + participant: { + id: participant.id, + name: participant.name ?? apiParticipant?.name, + avatar: participant.avatar ?? apiParticipant?.avatar, + email: participant.email ?? apiParticipant?.email, + }, + }); }; export default init; diff --git a/src/core/launcher/index.ts b/src/core/launcher/index.ts index 486e24cd..8087837e 100644 --- a/src/core/launcher/index.ts +++ b/src/core/launcher/index.ts @@ -1,4 +1,4 @@ -import * as Socket from '../../lib/socket'; +import * as Socket from '@superviz/socket-client'; import { isEqual } from 'lodash'; import { ParticipantEvent } from '../../common/types/events.types'; @@ -207,7 +207,9 @@ export class Launcher extends Observable implements DefaultLauncher { this.isDestroyed = true; // clean window object - window.SUPERVIZ = undefined; + if (typeof window !== 'undefined') { + window.SUPERVIZ = undefined; + } }; /** @@ -455,7 +457,7 @@ export class Launcher extends Observable implements DefaultLauncher { * @returns {LauncherFacade} */ export default (options: LauncherOptions): LauncherFacade => { - if (window.SUPERVIZ) { + if (typeof window !== 'undefined' && window.SUPERVIZ) { console.warn('[SUPERVIZ] Room already initialized'); return { @@ -469,7 +471,9 @@ export default (options: LauncherOptions): LauncherFacade => { const launcher = new Launcher(options); - window.SUPERVIZ = launcher; + if (typeof window !== 'undefined') { + window.SUPERVIZ = launcher; + } return { destroy: launcher.destroy, diff --git a/src/index.ts b/src/index.ts index de46d797..a06d9136 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,8 @@ +import init from './core'; +import './web-components'; +import './common/styles/global.css'; + +// #region enums import { MeetingEvent, RealtimeEvent, @@ -11,7 +16,19 @@ import { ComponentLifeCycleEvent, WhoIsOnlineEvent, } from './common/types/events.types'; +import { + CamerasPosition, + LayoutMode, + LayoutPosition, +} from './services/video-conference-manager/types'; import { ParticipantType } from './common/types/participant.types'; +import { RealtimeComponentEvent, RealtimeComponentState } from './components/realtime/types'; +import { StoreType } from './common/types/stores.types'; +import { PresenceEvents } from '@superviz/socket-client'; +import { FieldEvents } from './components/form-elements/types'; +import { PinMode } from './web-components/comments/components/types'; + +// #region Classes import { VideoConference, MousePointers, @@ -22,35 +39,16 @@ import { WhoIsOnline, FormElements, } from './components'; -import { Transform } from './components/presence-mouse/types'; -import { - RealtimeComponentEvent, - RealtimeComponentState, - RealtimeMessage, -} from './components/realtime/types'; -import init from './core'; -import './web-components'; -import './common/styles/global.css'; -import { - CamerasPosition, - LayoutMode, - LayoutPosition, -} from './services/video-conference-manager/types'; - -export { StoreType } from './common/types/stores.types'; - -export { Presence3DManager } from './services/presence-3d-manager'; +import type { Channel } from './components/realtime/channel'; +import type { Presence3DManager } from './services/presence-3d-manager'; -export { FieldEvents } from './components/form-elements/types'; -export { PinMode } from './web-components/comments/components/types'; - -export { Participant, Group, Avatar } from './common/types/participant.types'; -export { SuperVizSdkOptions, DevicesOptions } from './common/types/sdk-options.types'; -export { BrowserService } from './services/browser'; -export { BrowserStats } from './services/browser/types'; -export { LauncherFacade } from './core/launcher/types'; -export { Observer } from './common/utils/observer'; -export { +// #region Types and Interfaces +import type { RealtimeMessage } from './components/realtime/types'; +import type { Participant, Group, Avatar } from './common/types/participant.types'; +import type { SuperVizSdkOptions, DevicesOptions } from './common/types/sdk-options.types'; +import type { BrowserStats } from './services/browser/types'; +import type { LauncherFacade } from './core/launcher/types'; +import type { Annotation, Comment, PinAdapter, @@ -58,8 +56,9 @@ export { AnnotationPositionInfo, Offset, } from './components/comments/types'; +import type { Transform } from './components/presence-mouse/types'; -if (window) { +if (typeof window !== 'undefined') { window.SuperVizRoom = { init, CommentEvent, @@ -87,6 +86,11 @@ if (window) { RealtimeComponentEvent, ComponentLifeCycleEvent, WhoIsOnlineEvent, + StoreType, + PresenceEvents, + FieldEvents, + PinMode, + Comment, }; } @@ -118,6 +122,25 @@ export { VideoConference, Realtime, RealtimeMessage, + Channel, + StoreType, + PresenceEvents, + Presence3DManager, + FieldEvents, + PinMode, + Participant, + SuperVizSdkOptions, + BrowserStats, + LauncherFacade, + Annotation, + Comment, + PinAdapter, + PinCoordinates, + AnnotationPositionInfo, + Offset, + DevicesOptions, + Group, + Avatar, }; export default init; diff --git a/src/lib/socket/common/types/callbacks.types.ts b/src/lib/socket/common/types/callbacks.types.ts deleted file mode 100644 index a97e7e2d..00000000 --- a/src/lib/socket/common/types/callbacks.types.ts +++ /dev/null @@ -1 +0,0 @@ -export type ErrorCallback = (error: Error) => void; diff --git a/src/lib/socket/common/types/event.types.ts b/src/lib/socket/common/types/event.types.ts deleted file mode 100644 index 96dbc206..00000000 --- a/src/lib/socket/common/types/event.types.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @enum RoomEvents - * @description events that the server listens to in the room module - * @property JOIN_ROOM - event to join a room - * @property LEAVE_ROOM - event to leave a room - * @property UPDATE - event to update a room - * @property JOINED_ROOM - event to indicate a user has joined a room - * @property ERROR - event to indicate an error in the room module - */ -export enum RoomEvents { - JOIN_ROOM = 'room.join', - JOINED_ROOM = 'room.joined', - LEAVE_ROOM = 'room.leave', - UPDATE = 'room.update', - ERROR = 'room.error', -} - -export enum InternalRoomEvents { - GET = 'room.get', -} - -/** - * @enum PresenceEvents - * @description events that the server listens to in the presence module - * @property JOINED_ROOM - event to indicate a user has joined a room - * @property LEAVE - event to indicate a user has left a room - * @property UPDATE - event to indicate a user has updated their presence - * @property ERROR - event to indicate an error in the presence module - */ -export enum PresenceEvents { - JOINED_ROOM = 'presence.joined-room', - LEAVE = 'presence.leave', - ERROR = 'presence.error', - UPDATE = 'presence.update', -} - -/** - * @enum InternalPresenceEvents - * @description events that the server listens to in the presence module - * @property GET - event to get the presence list - */ -export enum InternalPresenceEvents { - GET = 'presence.get', -} diff --git a/src/lib/socket/common/types/presence.types.ts b/src/lib/socket/common/types/presence.types.ts deleted file mode 100644 index 2b600164..00000000 --- a/src/lib/socket/common/types/presence.types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { z } from 'zod'; - -export const PresenceSchema = z.object({ - id: z.string(), - name: z.string(), -}); - -export type Presence = z.infer; diff --git a/src/lib/socket/connection/index.ts b/src/lib/socket/connection/index.ts deleted file mode 100644 index 9affaa34..00000000 --- a/src/lib/socket/connection/index.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Subject } from 'rxjs'; -import type { Socket } from 'socket.io-client'; - -import { ErrorCallback } from '../common/types/callbacks.types'; - -import { ClientState, ConnectionState, SocketErrorEvent, SocketEvent } from './types'; -import { Logger } from '../../../common/utils'; - -export class ClientConnection { - private logger: Logger; - private stateObserver: Subject; - - public state: ClientState; - - constructor(private socket: Socket) { - this.logger = new Logger('@superviz/sdk/socket-client/connection'); - this.subscribeToManagerEvents(); - this.stateObserver = new Subject(); - } - - public on(next: (state: ConnectionState) => void, error?: ErrorCallback) { - if (this.stateObserver.closed) { - this.stateObserver = new Subject(); - } - - this.stateObserver.subscribe({ - next, - error, - }); - } - - public off() { - if (this.stateObserver.closed) return; - - this.stateObserver.unsubscribe(); - } - - /** - * @function subscribeToManagerEvents - * @description Subscribe to the manager events - * @returns {void} - */ - private subscribeToManagerEvents(): void { - this.socket.on('connect', this.onConnect); - this.socket.on('disconnect', this.onDisconnect); - this.socket.on('connect_error', this.onConnectError); - this.socket.io.on('error', this.onConnectionError); - this.socket.io.on('reconnect', this.onReconnect); - this.socket.io.on('reconnect_attempt', this.onReconnecAttempt); - this.socket.io.on('reconnect_error', this.onReconnectError); - this.socket.io.on('reconnect_failed', this.onReconnectFailed); - - // custom validations listener - this.socket.on(SocketEvent.ERROR, this.onCustomError); - } - - /** - * @function changeState - * @description Change the state of the connection - * @returns {void} - */ - private changeState(state: ClientState, reason?: string): void { - this.state = state; - - if (this.stateObserver.closed) return; - - this.stateObserver.next({ - state, - reason, - }); - } - - /** Manager events handlers */ - - private onConnect = () => { - this.logger.log('connection @ on connect', 'Connected to the socket'); - this.changeState(ClientState.CONNECTED); - }; - - private onDisconnect = (reason: Socket.DisconnectReason) => { - this.logger.log('connection @ on disconnect', 'Disconnected from the socket'); - this.changeState(ClientState.DISCONNECTED, reason); - }; - - private onConnectError = (error: Error) => { - this.logger.log('connection @ on connect error', 'Connection error', error); - this.changeState(ClientState.CONNECTION_ERROR, error.message); - }; - - private onConnectionError = (error: Error) => { - this.logger.log('connection @ on connection error', 'Connection error', error); - this.changeState(ClientState.CONNECTION_ERROR, error.message); - }; - - private onReconnect = () => { - this.logger.log('connection @ on reconnect', 'Reconnected to the socket'); - this.changeState(ClientState.CONNECTED); - }; - - private onReconnectError = (error: Error) => { - this.logger.log('connection @ on reconnect error', 'Reconnect error', error); - this.changeState(ClientState.RECONNECT_ERROR, error.message); - }; - - private onReconnectFailed = () => { - this.logger.log('connection @ on reconnect failed', 'Failed to reconnect to the socket'); - this.changeState(ClientState.RECONNECT_ERROR); - }; - - private onReconnecAttempt = (attempt: number) => { - this.logger.log('connection @ on reconnect attempt', `Reconnect attempt #${attempt}`); - this.changeState(ClientState.RECONNECTING, `Reconnect attempt #${attempt}`); - }; - - private onCustomError = (error: SocketErrorEvent) => { - if (error.needsToDisconnect) { - this.socket.disconnect(); - this.changeState(ClientState.DISCONNECTED, error.errorType); - } - - const logMessage = `[SuperViz] - - Error: ${error.errorType} - - Message: ${error.message} - `; - - if (error.level === 'error') { - console.error(logMessage); - return; - } - - console.warn(logMessage); - }; -} diff --git a/src/lib/socket/connection/types.ts b/src/lib/socket/connection/types.ts deleted file mode 100644 index 1fdde25e..00000000 --- a/src/lib/socket/connection/types.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @enum ClientState - * @description the state of the client - * @property CONNECTED - the client is connected - * @property CONNECTING - the client is connecting - * @property DISCONNECTED - the client is disconnected - * @property CONNECTION_ERROR - the client has a connection error - * @property RECONNECTING - the client is reconnecting - * @property RECONNECT_ERROR - the client has a reconnect error - */ -export enum ClientState { - CONNECTED = 'CONNECTED', - CONNECTING = 'CONNECTING', - DISCONNECTED = 'DISCONNECTED', - CONNECTION_ERROR = 'CONNECTION_ERROR', - RECONNECTING = 'RECONNECTING', - RECONNECT_ERROR = 'RECONNECT_ERROR', -} - -/** - * @interface ConnectionState - * @description the state of the connection - * @property state - the state of the connection - * @property reason - the reason for the state change - */ -export interface ConnectionState { - state: ClientState; - reason?: string; -} - -export type SocketErrorEvent = { - errorType: - | 'message-size-limit' - | 'rate-limit' - | 'room-connections-limit' - | 'user-already-in-room'; - message: string; - connectionId: string; - needsToDisconnect: boolean; - level: 'error' | 'warn'; -}; - -export enum SocketEvent { - ERROR = 'socket-event.error', -} diff --git a/src/lib/socket/index.ts b/src/lib/socket/index.ts deleted file mode 100644 index 6a37879e..00000000 --- a/src/lib/socket/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { PresenceEvents, RoomEvents } from './common/types/event.types'; -import { ClientState, ConnectionState } from './connection/types'; -import { PresenceRoom } from './presence'; -import { PresenceCallback, PresenceEvent } from './presence/types'; -import { Realtime } from './realtime'; -import { Room } from './room'; -import { Callback, SocketEvent, JoinRoomPayload, RoomHistory } from './room/types'; - -export { - Realtime, - PresenceEvents, - RoomEvents, - ClientState, - ConnectionState, - Callback, - SocketEvent, - JoinRoomPayload, - RoomHistory, - PresenceCallback, - PresenceEvent, - Room, - PresenceRoom, -}; diff --git a/src/lib/socket/presence/index.ts b/src/lib/socket/presence/index.ts deleted file mode 100644 index d633afcc..00000000 --- a/src/lib/socket/presence/index.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { Subject } from 'rxjs'; -import type { Socket } from 'socket.io-client'; - -import { ErrorCallback } from '../common/types/callbacks.types'; -import { InternalPresenceEvents, PresenceEvents } from '../common/types/event.types'; -import type { Presence } from '../common/types/presence.types'; - -import { PresenceCallback, PresenceEvent, PresenceEventFromServer } from './types'; -import { Logger } from '../../../common/utils'; - -export class PresenceRoom { - private logger: Logger; - private presences: Set = new Set(); - private observers: Map> = new Map(); - - constructor(private io: Socket, private presence: Presence, private roomId: string) { - this.logger = new Logger('@superviz/sdk/socket-client/presence'); - - this.registerSubsjects(); - this.subscribeToPresenceEvents(); - } - - public static register(io: Socket, presence: Presence, roomId: string) { - return new PresenceRoom(io, presence, roomId); - } - - /** - * @function get - * @description Get the presences in the room - * @returns {void} - */ - public get(next: (data: PresenceEvent[]) => void, error?: ErrorCallback): void { - const subject = new Subject(); - - subject.subscribe({ - next, - error, - }); - - const callback = (event: { - presences: PresenceEventFromServer[]; - connectionId: string; - timestamp: number; - roomId: string; - }) => { - const presences = event.presences.map((presence) => ({ - connectionId: presence.connectionId, - data: presence.data, - id: presence.id, - name: presence.name, - timestamp: presence.timestamp, - })); - - this.logger.log('presence room @ get', event); - this.io.off(InternalPresenceEvents.GET, callback); - subject.next(presences); - subject.complete(); - }; - - this.io.on(InternalPresenceEvents.GET, callback); - this.io.emit(InternalPresenceEvents.GET, this.roomId); - } - - /** - * @function update - * @description update the presence data in the room - * @param payload - The data to update - * @returns {void} - */ - public update(payload: T): void { - const body: PresenceEvent = { - connectionId: this.io.id, - data: payload, - id: this.presence.id, - name: this.presence.name, - timestamp: Date.now(), - }; - - this.io.emit(PresenceEvents.UPDATE, this.roomId, body); - this.logger.log('presence room @ update', this.roomId, body); - } - - public destroy(): void { - this.io.off(PresenceEvents.JOINED_ROOM, this.onPresenceJoin); - this.io.off(PresenceEvents.LEAVE, this.onPresenceLeave); - this.io.off(PresenceEvents.UPDATE, this.onPresenceUpdate); - - this.observers.forEach((observer) => observer.unsubscribe()); - this.observers.clear(); - } - - /** - * @function registerSubsjects - * @description Register the subjects for the presence events - * @returns {void} - */ - private registerSubsjects(): void { - this.observers.set(PresenceEvents.JOINED_ROOM, new Subject()); - this.observers.set(PresenceEvents.LEAVE, new Subject()); - this.observers.set(PresenceEvents.UPDATE, new Subject()); - } - - /** - * @function on - * @description Listen to an event - * @param event - The event to listen to - * @param callback - The callback to execute when the event is emitted - * @returns {void} - */ - public on( - event: PresenceEvents, - callback: PresenceCallback, - error?: ErrorCallback, - ): void { - this.observers.get(event).subscribe({ - error, - next: callback, - }); - } - - /** - * @function off - * @description Stop listening to an event - * @param event - The event to stop listening to - * @param callback - The callback to remove from the event - * @returns {void} - */ - public off(event: PresenceEvents): void { - this.observers.get(event).unsubscribe(); - } - - /** - * @function subscribeToPresenceEvents - * @description Subscribe to the presence events - * @returns {void} - */ - private subscribeToPresenceEvents(): void { - this.io.on(PresenceEvents.JOINED_ROOM, this.onPresenceJoin); - this.io.on(PresenceEvents.LEAVE, this.onPresenceLeave); - this.io.on(PresenceEvents.UPDATE, this.onPresenceUpdate); - } - - /** - * @function onPresenceJoin - * @description Handle the presence join event - * @param event - The presence event - * @returns {void} - */ - private onPresenceJoin = (event: PresenceEventFromServer): void => { - if (event?.roomId !== this.roomId) return; - - this.logger.log('presence room @ presence join', event); - this.presences.add(event); - this.observers.get(PresenceEvents.JOINED_ROOM).next({ - connectionId: event.connectionId, - data: event.data, - id: event.id, - name: event.name, - timestamp: event.timestamp, - }); - }; - - /** - * @function onPresenceLeave - * @description Handle the presence leave event - * @param event - The presence event - * @returns {void} - */ - private onPresenceLeave = (event: PresenceEventFromServer): void => { - if (event?.roomId !== this.roomId) return; - - this.logger.log('presence room @ presence leave', event); - this.presences.delete(event); - this.observers.get(PresenceEvents.LEAVE).next(event); - }; - - /** - * @function onPresenceUpdate - * @description Handle the presence update event - * @param event - The presence event - * @returns {void} - */ - private onPresenceUpdate = (event: PresenceEventFromServer): void => { - if (event?.roomId !== this.roomId) return; - - this.logger.log('presence room @ presence update', event); - this.observers.get(PresenceEvents.UPDATE).next({ - connectionId: event.connectionId, - data: event.data, - id: event.id, - name: event.name, - roomId: event?.roomId, - timestamp: event.timestamp, - } as PresenceEvent); - }; -} diff --git a/src/lib/socket/presence/types.ts b/src/lib/socket/presence/types.ts deleted file mode 100644 index 89b986e3..00000000 --- a/src/lib/socket/presence/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type PresenceEvent = { - id: string; - name: string; - connectionId: string; - data: T; - timestamp: number; -}; - -export interface PresenceEventFromServer extends PresenceEvent { - roomKey: string; - roomId: string; -} - -export type PresenceCallback = (event: PresenceEvent) => void; diff --git a/src/lib/socket/realtime/index.ts b/src/lib/socket/realtime/index.ts deleted file mode 100644 index f45d54de..00000000 --- a/src/lib/socket/realtime/index.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { type Socket, Manager } from 'socket.io-client'; - -import { Presence } from '../common/types/presence.types'; -import { ClientConnection } from '../connection'; -import { ClientState } from '../connection/types'; -import { Room } from '../room'; - -export class Realtime { - private socket: Socket; - private manager: Manager; - public connection: ClientConnection; - - constructor( - private apiKey: string, - private environment: 'dev' | 'prod', - private presence: Presence, - ) { - this.manager = new Manager('https://io.superviz.com', { - addTrailingSlash: false, - secure: true, - withCredentials: true, - reconnection: true, - reconnectionDelay: 1000, - reconnectionDelayMax: 5000, - reconnectionAttempts: 5, - extraHeaders: { - 'sv-api-key': this.apiKey, - }, - }); - - // use the default namespace to handle the connections - const { origin } = window.location; - this.socket = this.manager.socket(`/${environment}`, { - auth: { - apiKey: this.apiKey, - origin, - envirioment: this.environment, - }, - }); - - this.connection = new ClientConnection(this.socket); - } - - public get state(): ClientState { - return this.connection.state; - } - - /** - * @function connect - * @param room - The room name - * @param maxConnections - The maximum number of connections allowed in the room - * @returns {Room} - The room instance - */ - public connect(room: string, maxConnections?: number | 'unlimited'): Room { - return Room.register(this.socket, this.presence, room, this.apiKey, maxConnections); - } - - public destroy() { - this.socket.disconnect(); - this.connection.off(); - } -} diff --git a/src/lib/socket/room/index.ts b/src/lib/socket/room/index.ts deleted file mode 100644 index 7c205a02..00000000 --- a/src/lib/socket/room/index.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { Subject, Subscription } from 'rxjs'; -import type { Socket } from 'socket.io-client'; - -import { ErrorCallback } from '../common/types/callbacks.types'; -import { InternalRoomEvents, RoomEvents } from '../common/types/event.types'; -import { Presence } from '../common/types/presence.types'; -import { PresenceRoom } from '../presence'; - -import { Callback, SocketEvent, JoinRoomPayload, RoomHistory } from './types'; -import { Logger } from '../../../common/utils'; - -export class Room { - private logger: Logger; - private isJoined: boolean = false; - private subscriptions: Map, Subscription> = new Map(); - private observers: Map> = new Map(); - - public presence: PresenceRoom; - - constructor( - private io: Socket, - private user: Presence, - private roomId: string, - private apiKey: string, - private maxConnections: number | 'unlimited' = 100, - ) { - this.logger = new Logger('@superviz/sdk/socket-client/room'); - - const payload: JoinRoomPayload = { - name: roomId, - user, - maxConnections, - }; - - this.presence = PresenceRoom.register(io, user, roomId); - this.io.emit(RoomEvents.JOIN_ROOM, payload); - this.subscribeToRoomEvents(); - } - - public static register( - io: Socket, - presence: Presence, - roomId: string, - apiKey: string, - maxConnections: number | 'unlimited', - ): Room { - return new Room(io, presence, roomId, apiKey, maxConnections); - } - - /** - * @function on - * @description Listen to an event - * @param event - The event to listen to - * @param callback - The callback to execute when the event is emitted - * @returns {void} - */ - public on(event: string, callback: Callback): void { - this.logger.log('room @ on', event); - - let subject = this.observers.get(event); - - if (!subject) { - subject = new Subject(); - this.observers.set(event, subject); - - this.io.on(event, (data: SocketEvent) => { - this.publishEventToClient(event, data); - }); - } - - this.subscriptions.set(callback, subject.subscribe(callback)); - } - - /** - * @function off - * @description Stop listening to an event - * @param event - The event to stop listening to - * @param callback - The callback to remove from the event - * @returns {void} - */ - public off(event: string, callback?: Callback): void { - this.logger.log('room @ off', event); - - if (!callback) { - this.observers.delete(event); - this.io.off(event); - return; - } - - this.subscriptions.get(callback)?.unsubscribe(); - } - - /** - * @function emit - * @description Emit an event - * @param event - The event to emit - * @param payload - The payload to send with the event - * @returns {void} - */ - public emit(event: string, payload: T): void { - if (!this.isJoined) { - this.logger.log('Cannot emit event. Not joined to room'); - return; - } - - const body: SocketEvent = { - name: event, - roomId: this.roomId, - presence: this.user, - connectionId: this.io.id, - data: payload, - timestamp: Date.now(), - }; - - this.io.emit(RoomEvents.UPDATE, this.roomId, body); - this.logger.log('room @ emit', event, payload); - } - - /** - * @function get - * @description Get the presences in the room - * @returns {void} - */ - public history(next: (data: RoomHistory) => void, error?: ErrorCallback): void { - const subject = new Subject(); - - subject.subscribe({ - next, - error, - }); - - const callback = (event: RoomHistory) => { - this.logger.log('room @ history', event); - this.io.off(InternalRoomEvents.GET, callback); - subject.next(event); - subject.complete(); - subject.unsubscribe(); - }; - - this.io.on(InternalRoomEvents.GET, callback); - this.io.emit(InternalRoomEvents.GET, this.roomId); - } - - /** - * @function disconnect - * @description Disconnect from the room - * @returns {void} - */ - public disconnect(): void { - this.logger.log('room @ disconnect', 'Leaving room...', this.roomId); - this.io.emit(RoomEvents.LEAVE_ROOM, this.roomId); - - // unsubscribe from all events - this.subscriptions.forEach((subscription) => subscription.unsubscribe()); - this.subscriptions.clear(); - this.observers.forEach((subject) => subject.unsubscribe()); - this.observers.clear(); - - this.presence.destroy(); - } - - /** - * @function publishEventToClient - * @description Publish an event to the client - * @param event - The event to publish - * @param data - The data to publish - * @returns {void} - */ - private publishEventToClient(event: string, data: SocketEvent): void { - const subject = this.observers.get(event); - - if (!subject || data.roomId !== this.roomId) return; - - subject.next(data); - } - - /* - * @function subscribeToRoomEvents - * @description Subscribe to room events - * @returns {void} - */ - private subscribeToRoomEvents(): void { - this.io.on(RoomEvents.JOINED_ROOM, this.onJoinedRoom); - this.io.on(RoomEvents.ERROR, (event: SocketEvent) => { - this.logger.log('Error:', event.data); - }); - - this.io.on(`http:${this.roomId}:${this.apiKey}`, this.onHttpEvent); - } - - /** - * @function onJoinedRoom - * @description handles the event when a user joins a room. - * @param event The socket event containing presence data. - * @returns {void} - */ - private onJoinedRoom = (event: SocketEvent<{ name: string }>): void => { - if (this.roomId !== event?.data?.name) return; - - this.isJoined = true; - this.io.emit(RoomEvents.JOINED_ROOM, this.roomId, event.data); - this.logger.log('room @ joined', event); - }; - - private onHttpEvent = (event: SocketEvent) => { - this.publishEventToClient(event.name, event); - }; -} diff --git a/src/lib/socket/room/types.ts b/src/lib/socket/room/types.ts deleted file mode 100644 index e40f3c0b..00000000 --- a/src/lib/socket/room/types.ts +++ /dev/null @@ -1,39 +0,0 @@ -import z from 'zod'; - -import { PresenceSchema } from '../common/types/presence.types'; - -export type SocketEvent = { - name: string; - roomId: string; - connectionId: string; - presence?: z.infer; - data: T; - timestamp: number; -}; - -export type RoomHistory = { - roomId: string; - room: { - id: string; - name: string; - userId: string; - apiKey: string; - createdAt: Date; - }; - events: SocketEvent[]; - connectionId: string; - timestamp: Date; -}; - -export type Callback = (event: SocketEvent) => void; - -export const JoinRoomSchema = z.object({ - name: z.string(), - user: z.object({ - id: z.string(), - name: z.string(), - }), - maxConnections: z.union([z.number(), z.literal('unlimited'), z.undefined()]), -}); - -export type JoinRoomPayload = z.infer; diff --git a/src/services/api/index.test.ts b/src/services/api/index.test.ts index 7e222bb9..a081fbdc 100644 --- a/src/services/api/index.test.ts +++ b/src/services/api/index.test.ts @@ -11,6 +11,14 @@ const CHECK_LIMITS_MOCK = { limits: LIMITS_MOCK, }; +const FETCH_PARTICIPANT_MOCK = { + id: 'any_user_id', + name: 'any_user_name', + email: null, + avatar: null, + createdAt: '2024-08-13T09:13:09.438Z', +}; + const FETCH_PARTICIPANTS_BY_GROUP_MOCK = [ { id: 'any_user_id', @@ -33,14 +41,6 @@ jest.mock('../../common/utils', () => { return Promise.resolve({ status: 404 }); } - if (url.includes('/immersive-config')) { - return Promise.resolve( - JSON.stringify({ - ablyKey: MOCK_ABLY_KEY, - }), - ); - } - if (url.includes('/user/watermark')) { return Promise.resolve({ message: true, @@ -81,6 +81,10 @@ jest.mock('../../common/utils', () => { return Promise.resolve(FETCH_PARTICIPANTS_BY_GROUP_MOCK); } + if (url.includes('/participants/any_user_id') && method === 'GET') { + return Promise.resolve(FETCH_PARTICIPANT_MOCK); + } + if (url.includes('/mentions') && method === 'POST') { return Promise.resolve({}); } @@ -109,15 +113,6 @@ describe('ApiService', () => { }); }); - describe('fetchConfig', () => { - test('should return the config', async () => { - const baseUrl = 'https://dev.nodeapi.superviz.com'; - const response = await ApiService.fetchConfig(baseUrl, VALID_API_KEY); - - expect(response).toBe(JSON.stringify({ ablyKey: MOCK_ABLY_KEY })); - }); - }); - describe('fetchWatermark', () => { test('should return the watermark', async () => { const baseUrl = 'https://dev.nodeapi.superviz.com'; @@ -233,4 +228,12 @@ describe('ApiService', () => { expect(response).toEqual({}); }); }); + + describe('fetchParticipant', () => { + test('should return the participant', async () => { + const response = await ApiService.fetchParticipant('any_user_id'); + + expect(response).toEqual(FETCH_PARTICIPANT_MOCK); + }); + }); }); diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 83a84f55..0c7c83f7 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -7,7 +7,7 @@ import { ComponentLimits } from '../limits/types'; import { AnnotationParams, CommentParams, - CreateOrUpdateParticipantParams, + CreateParticipantParams, FetchAnnotationsParams, MentionParams, } from './types'; @@ -105,8 +105,8 @@ export default class ApiService { return doRequest(url, 'DELETE', {}, { apikey: apiKey }); } - static async createOrUpdateParticipant( - participant: CreateOrUpdateParticipantParams, + static async createParticipant( + participant: CreateParticipantParams, ): Promise { const baseUrl = config.get('apiUrl'); const path = '/participants'; @@ -114,6 +114,13 @@ export default class ApiService { return doRequest(url, 'POST', { ...participant }, { apikey: config.get('apiKey') }); } + static async fetchParticipant(id: string) { + const baseUrl = config.get('apiUrl'); + const path = `/participants/${id}`; + const url = this.createUrl(baseUrl, path); + return doRequest(url, 'GET', undefined, { apikey: config.get('apiKey') }); + } + static async sendActivity(userId: string, groupId: string, groupName: string, product: string) { const path = '/activity'; const baseUrl = config.get('apiUrl'); diff --git a/src/services/api/types.ts b/src/services/api/types.ts index a4a2311a..624b6cee 100644 --- a/src/services/api/types.ts +++ b/src/services/api/types.ts @@ -26,8 +26,9 @@ export type MentionParticipantParams = { readed: number } -export type CreateOrUpdateParticipantParams = { - name: string; +export type CreateParticipantParams = { + name?: string; participantId: string; - avatar: string | null; + avatar?: string | null; + email?: string }; diff --git a/src/services/config/index.test.ts b/src/services/config/index.test.ts index 8a4b78e2..6872d83a 100644 --- a/src/services/config/index.test.ts +++ b/src/services/config/index.test.ts @@ -13,7 +13,6 @@ describe('ConfigurationService', () => { describe('setConfig', () => { test('configuration should be set correctly', () => { - expect(configService.get('ablyKey')).toEqual(MOCK_CONFIG.ablyKey); expect(configService.get('apiKey')).toEqual(MOCK_CONFIG.apiKey); expect(configService.get('apiUrl')).toEqual(MOCK_CONFIG.apiUrl); expect(configService.get('conferenceLayerUrl')).toEqual(MOCK_CONFIG.conferenceLayerUrl); @@ -24,8 +23,8 @@ describe('ConfigurationService', () => { describe('get', () => { test('should get a value from configuration', () => { - const result = configService.get('ablyKey', 'defaultValue'); - expect(result).toBe(MOCK_CONFIG.ablyKey); + const result = configService.get('apiKey', 'defaultValue'); + expect(result).toBe(MOCK_CONFIG.apiKey); }); test('should provide a default value if key is not found', () => { @@ -35,7 +34,7 @@ describe('ConfigurationService', () => { test('should return the default value if the config is not available', () => { configService.setConfig(null as unknown as Configuration); - expect(configService.get('ablyKey')).toBeUndefined(); + expect(configService.get('apiKey')).toBeUndefined(); }); }); }); diff --git a/src/services/config/types.ts b/src/services/config/types.ts index d1c9ec17..4be7f0c2 100644 --- a/src/services/config/types.ts +++ b/src/services/config/types.ts @@ -7,7 +7,6 @@ export interface Configuration { roomId: string; environment: EnvironmentTypes; apiKey: string; - ablyKey: string; apiUrl: string; conferenceLayerUrl: string; debug: boolean; diff --git a/src/services/connection-status/index.ts b/src/services/connection-status/index.ts index fbd2244a..84bf9148 100644 --- a/src/services/connection-status/index.ts +++ b/src/services/connection-status/index.ts @@ -23,8 +23,10 @@ export class ConnectionService implements DefaultConnectionService { * @returns {void} */ public addListeners(): void { - window.addEventListener('online', this.onUpdateBrowserOnlineStatus); - window.addEventListener('offline', this.onUpdateBrowserOnlineStatus); + if (typeof window !== 'undefined') { + window.addEventListener('online', this.onUpdateBrowserOnlineStatus); + window.addEventListener('offline', this.onUpdateBrowserOnlineStatus); + } } /** @@ -33,8 +35,10 @@ export class ConnectionService implements DefaultConnectionService { * @returns {void} */ public removeListeners(): void { - window.removeEventListener('online', this.onUpdateBrowserOnlineStatus); - window.removeEventListener('offline', this.onUpdateBrowserOnlineStatus); + if (typeof window !== 'undefined') { + window.removeEventListener('online', this.onUpdateBrowserOnlineStatus); + window.removeEventListener('offline', this.onUpdateBrowserOnlineStatus); + } } /** diff --git a/src/services/io/index.ts b/src/services/io/index.ts index d85989d4..bbbc4697 100644 --- a/src/services/io/index.ts +++ b/src/services/io/index.ts @@ -1,4 +1,4 @@ -import * as Socket from '../../lib/socket'; +import * as Socket from '@superviz/socket-client'; import { Subject } from 'rxjs'; import { Participant } from '../../common/types/participant.types'; diff --git a/src/services/message-bridge/index.ts b/src/services/message-bridge/index.ts index 946fc8df..9fa292ee 100644 --- a/src/services/message-bridge/index.ts +++ b/src/services/message-bridge/index.ts @@ -62,7 +62,9 @@ export class MessageBridge { delete this.observers[type]; }); - window.removeEventListener('message', this.onReceiveMessage); + if (typeof window !== 'undefined') { + window.removeEventListener('message', this.onReceiveMessage); + } delete this.logger; delete this.allowedOrigins; diff --git a/src/services/presence-3d-manager/index.test.ts b/src/services/presence-3d-manager/index.test.ts index 58edff58..e3168e03 100644 --- a/src/services/presence-3d-manager/index.test.ts +++ b/src/services/presence-3d-manager/index.test.ts @@ -1,4 +1,4 @@ -import { PresenceEvents } from '../../lib/socket'; +import { PresenceEvents } from '@superviz/socket-client'; import { MOCK_IO } from '../../../__mocks__/io.mock'; import { MOCK_LOCAL_PARTICIPANT } from '../../../__mocks__/participants.mock'; @@ -248,7 +248,7 @@ describe('Presence3DManager', () => { ...MOCK_LOCAL_PARTICIPANT, name: 'new name', id: undefined, - }; + } as unknown as Participant; presence3DManager['unthrottledUpdatePresence3D'](modifiedLocalParticipant); diff --git a/src/services/presence-3d-manager/index.ts b/src/services/presence-3d-manager/index.ts index 4b81612b..7a214aa2 100644 --- a/src/services/presence-3d-manager/index.ts +++ b/src/services/presence-3d-manager/index.ts @@ -1,4 +1,4 @@ -import { PresenceEvent, PresenceEvents, Room, SocketEvent } from '../../lib/socket'; +import { PresenceEvent, PresenceEvents, Room, SocketEvent } from '@superviz/socket-client'; import { throttle } from 'lodash'; import { Participant } from '../../common/types/participant.types'; diff --git a/src/services/room-state/index.test.ts b/src/services/room-state/index.test.ts index a3c064a6..f8492d08 100644 --- a/src/services/room-state/index.test.ts +++ b/src/services/room-state/index.test.ts @@ -1,6 +1,6 @@ import { TextEncoder, TextDecoder } from 'util'; -import { PresenceEvent, PresenceEvents } from '../../lib/socket'; +import { PresenceEvent, PresenceEvents } from '@superviz/socket-client'; import { MOCK_LOCAL_PARTICIPANT } from '../../../__mocks__/participants.mock'; import { TranscriptState } from '../../common/types/events.types'; diff --git a/src/services/room-state/index.ts b/src/services/room-state/index.ts index 5eb81a8b..a1c5cde7 100644 --- a/src/services/room-state/index.ts +++ b/src/services/room-state/index.ts @@ -1,4 +1,4 @@ -import { PresenceEvent, PresenceEvents, Room, SocketEvent } from '../../lib/socket'; +import { PresenceEvent, PresenceEvents, Room, SocketEvent } from '@superviz/socket-client'; import { TranscriptState } from '../../common/types/events.types'; import { ParticipantType, VideoParticipant } from '../../common/types/participant.types'; diff --git a/src/services/room-state/type.ts b/src/services/room-state/type.ts index 71d70aed..8dbc4ddf 100644 --- a/src/services/room-state/type.ts +++ b/src/services/room-state/type.ts @@ -1,4 +1,4 @@ -import { PresenceEvent } from '../../lib/socket'; +import { PresenceEvent } from '@superviz/socket-client'; import { TranscriptState } from '../../common/types/events.types'; import { Participant } from '../../common/types/participant.types'; diff --git a/src/services/slot/index.ts b/src/services/slot/index.ts index 7f592a0c..a178b4a6 100644 --- a/src/services/slot/index.ts +++ b/src/services/slot/index.ts @@ -1,4 +1,4 @@ -import * as Socket from '../../lib/socket'; +import * as Socket from '@superviz/socket-client'; import { NAME_IS_WHITE_TEXT, diff --git a/src/services/video-conference-manager/index.ts b/src/services/video-conference-manager/index.ts index b1d8216c..4221af30 100644 --- a/src/services/video-conference-manager/index.ts +++ b/src/services/video-conference-manager/index.ts @@ -104,7 +104,6 @@ export default class VideoConfereceManager { this.frameConfig = { apiKey: config.get('apiKey'), apiUrl: config.get('apiUrl'), - ablyKey: config.get('ablyKey'), debug: config.get('debug'), roomId: config.get('roomId'), limits: config.get('limits'), @@ -162,8 +161,11 @@ export default class VideoConfereceManager { locales, }; this.meetingAvatars = avatars; - window.addEventListener('resize', this.onWindowResize); - window.addEventListener('orientationchange', this.onWindowResize); + + if (typeof window !== 'undefined') { + window.addEventListener('resize', this.onWindowResize); + window.addEventListener('orientationchange', this.onWindowResize); + } } get isWaterMarkEnabled(): boolean { @@ -373,6 +375,8 @@ export default class VideoConfereceManager { * @returns {void} */ private onFrameDimensionsUpdate = ({ width, height }: Dimensions): void => { + if (typeof window === 'undefined') return; + const frame = document.getElementById(FRAME_ID); const { bottom: offsetBottom, @@ -662,8 +666,10 @@ export default class VideoConfereceManager { this.bricklayer = null; this.frameState = null; - window.removeEventListener('resize', this.onWindowResize); - window.removeEventListener('orientationchange', this.onWindowResize); + if (typeof window !== 'undefined') { + window.removeEventListener('resize', this.onWindowResize); + window.removeEventListener('orientationchange', this.onWindowResize); + } } /** diff --git a/src/services/video-conference-manager/types.ts b/src/services/video-conference-manager/types.ts index 6e3e97a3..ce5b66a8 100644 --- a/src/services/video-conference-manager/types.ts +++ b/src/services/video-conference-manager/types.ts @@ -64,7 +64,6 @@ export interface FrameLocale { export interface FrameConfig { apiKey: string; apiUrl: string; - ablyKey: string; roomId: string; debug: boolean; limits: ComponentLimits; diff --git a/src/web-components/comments/comments.test.ts b/src/web-components/comments/comments.test.ts deleted file mode 100644 index 487beeed..00000000 --- a/src/web-components/comments/comments.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import '.'; -import { ParticipantByGroupApi } from '../../common/types/participant.types'; -import sleep from '../../common/utils/sleep'; - -let element: HTMLElement; -const MOCK_PARTICIPANTS: ParticipantByGroupApi[] = [ - { - name: 'John Zero', - avatar: 'avatar1.png', - id: '1', - email: 'john.zero@mail.com', - }, - { - name: 'John Uno', - avatar: 'avatar2.png', - id: '2', - email: 'john.uno@mail.com', - }, - { - name: 'John Doe', - avatar: 'avatar3.png', - id: '3', - email: 'john.doe@mail.com', - }, -]; -describe('comments', () => { - beforeEach(async () => { - element = document.createElement('superviz-comments'); - element.setAttribute('comments', JSON.stringify([])); - document.body.appendChild(element); - await sleep(); - }); - - afterEach(() => { - document.body.removeChild(element); - }); - - test('renders the component', async () => { - const renderedElement = document.getElementsByTagName('superviz-comments')[0]; - expect(renderedElement).toBeTruthy(); - }); - - test('should close superviz comments', async () => { - const renderedElement = document.getElementsByTagName('superviz-comments')[0]; - const app = renderedElement.shadowRoot!.querySelector('.superviz-comments'); - - renderedElement.setAttributeNode(document.createAttribute('open')); - renderedElement.removeAttribute('open'); - - expect(renderedElement.hasAttribute('open')).toBeFalsy(); - expect(app?.classList.contains('hide-at-left')).toBeTruthy(); - }); - - test('should open superviz comments', async () => { - const renderedElement = document.getElementsByTagName('superviz-comments')[0]; - const app = renderedElement.shadowRoot!.getElementById('superviz-comments'); - - renderedElement.setAttributeNode(document.createAttribute('open')); - - await sleep(); - - expect(renderedElement.hasAttribute('open')).toBeTruthy(); - expect(app?.classList.contains('superviz-comments')).toBe(true); - }); - - test('should update annotations', async () => { - const annotations = [{ id: '1', x: 0, y: 0, width: 0, height: 0, text: 'test' }]; - - element['updateAnnotations'](annotations); - - await sleep(); - - expect(element['annotations']).toEqual(annotations); - }); - - test('should set filter', async () => { - const label = 'test'; - const detail = { filter: { label } }; - - element['setFilter']({ detail }); - - await sleep(); - - expect(element['annotationFilter']).toEqual(label); - }); - - test('should show water mark', async () => { - const waterMark = true; - element['waterMarkStatus'](waterMark); - - await sleep(); - - expect(element['waterMarkState']).toEqual(waterMark); - }); - - test('should not show water mark', async () => { - const waterMark = false; - element['waterMarkStatus'](waterMark); - - await sleep(); - - expect(element['waterMarkState']).toEqual(waterMark); - }); - - test('should receive participants List', async () => { - const participantsList = JSON.stringify([MOCK_PARTICIPANTS]); - - element['participantsListed'](participantsList); - - await sleep(); - - expect(element['participantsList']).toEqual(participantsList); - }); -}); diff --git a/src/web-components/comments/comments.ts b/src/web-components/comments/comments.ts deleted file mode 100644 index d5835194..00000000 --- a/src/web-components/comments/comments.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; -import { classMap } from 'lit/directives/class-map.js'; - -import { ParticipantByGroupApi } from '../../common/types/participant.types'; -import { Annotation, CommentsSide, Offset } from '../../components/comments/types'; -import { WebComponentsBase } from '../base'; -import importStyle from '../base/utils/importStyle'; - -import { AnnotationFilter } from './components/types'; -import { commentsStyle, poweredByStyle } from './css/index'; -import { waterMarkElementObserver } from './utils/watermark'; - -const WebComponentsBaseElement = WebComponentsBase(LitElement); -const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, commentsStyle, poweredByStyle]; - -@customElement('superviz-comments') -export class Comments extends WebComponentsBaseElement { - static styles = styles; - - declare open: boolean; - declare annotations: Annotation[]; - declare annotationFilter: AnnotationFilter; - declare waterMarkState: boolean; - declare side: CommentsSide; - declare participantsList: ParticipantByGroupApi[]; - declare offset: Offset; - - static properties = { - open: { type: Boolean }, - annotations: { type: Object }, - annotationFilter: { type: String }, - waterMarkState: { type: Boolean }, - side: { type: String }, - participantsList: { type: Object }, - offset: { type: Object }, - }; - - constructor() { - super(); - this.annotations = []; - this.annotationFilter = AnnotationFilter.ALL; - this.waterMarkState = false; - this.participantsList = []; - this.side = CommentsSide.LEFT; - } - - protected firstUpdated( - _changedProperties: PropertyValueMap | Map, - ): void { - super.firstUpdated(_changedProperties); - this.updateComplete.then(() => { - importStyle.call(this, ['comments']); - }); - } - - protected updated(changedProperties: Map) { - super.updated(changedProperties); - this.updateComplete.then(() => { - if (this.waterMarkState) { - waterMarkElementObserver(this.shadowRoot); - } - - if (changedProperties.has('offset')) { - this.applyOffset(); - } - }); - } - - public participantsListed(participants: ParticipantByGroupApi[]) { - this.participantsList = participants; - } - - public updateAnnotations(data: Annotation[]) { - this.annotations = data; - } - - private close() { - this.emitEvent('close-threads', {}); - } - - waterMarkStatus(waterMark: boolean) { - this.waterMarkState = waterMark; - } - - private setFilter({ detail }) { - const { - filter: { label }, - } = detail; - this.annotationFilter = label; - } - - private getOffset(offset: number) { - if (offset === null || offset === undefined || offset < 0) { - return '10px'; - } - - return `${offset}px`; - } - - private applyOffset() { - const supervizCommentsDiv: HTMLDivElement = this.shadowRoot.querySelector('.superviz-comments'); - if (!supervizCommentsDiv) return; - - const { left, right, top, bottom } = this.offset; - - supervizCommentsDiv.style.setProperty('--offset-top', this.getOffset(top)); - supervizCommentsDiv.style.setProperty('--offset-bottom', this.getOffset(bottom)); - supervizCommentsDiv.style.setProperty('--offset-right', this.getOffset(right)); - supervizCommentsDiv.style.setProperty('--offset-left', this.getOffset(left)); - } - - private get poweredBy() { - if (!this.waterMarkState) return html``; - - return html` `; - } - - protected render() { - const classes = { - 'superviz-comments': true, - 'threads-on-left-side': this.side === CommentsSide.LEFT, - 'threads-on-right-side': this.side === CommentsSide.RIGHT, - 'hide-at-right': this.side === CommentsSide.RIGHT && !this.open, - 'hide-at-left': this.side === CommentsSide.LEFT && !this.open, - }; - - return html` -
-
- -
- - - - ${this.poweredBy} -
- `; - } -} diff --git a/src/web-components/comments/components/annotation-filter.test.ts b/src/web-components/comments/components/annotation-filter.test.ts index e5792c76..8cebc102 100644 --- a/src/web-components/comments/components/annotation-filter.test.ts +++ b/src/web-components/comments/components/annotation-filter.test.ts @@ -1,7 +1,7 @@ import { MOCK_ANNOTATION } from '../../../../__mocks__/comments.mock'; import sleep from '../../../common/utils/sleep'; -import '.'; +import './annotation-filter'; import { AnnotationFilter } from './types'; let element: HTMLElement; diff --git a/src/web-components/comments/components/annotation-filter.ts b/src/web-components/comments/components/annotation-filter.ts index 861afafc..7787d40d 100644 --- a/src/web-components/comments/components/annotation-filter.ts +++ b/src/web-components/comments/components/annotation-filter.ts @@ -1,5 +1,4 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { WebComponentsBase } from '../../base'; @@ -8,6 +7,7 @@ import { DropdownOption } from '../../dropdown/types'; import { annotationFilterStyle } from '../css'; import { AnnotationFilter } from './types'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, annotationFilterStyle]; @@ -21,7 +21,7 @@ const options: DropdownOption[] = [ }, ]; -@customElement('superviz-comments-annotation-filter') +@CreateElement('superviz-comments-annotation-filter') export class CommentsAnnotationFilter extends WebComponentsBaseElement { constructor() { super(); diff --git a/src/web-components/comments/components/annotation-item.test.ts b/src/web-components/comments/components/annotation-item.test.ts index 065b00d8..60efdcc9 100644 --- a/src/web-components/comments/components/annotation-item.test.ts +++ b/src/web-components/comments/components/annotation-item.test.ts @@ -1,7 +1,7 @@ import { MOCK_ANNOTATION } from '../../../../__mocks__/comments.mock'; import sleep from '../../../common/utils/sleep'; -import '.'; +import './annotation-item'; import { AnnotationFilter } from './types'; let element: HTMLElement; diff --git a/src/web-components/comments/components/annotation-item.ts b/src/web-components/comments/components/annotation-item.ts index d1541d07..113f3c0b 100644 --- a/src/web-components/comments/components/annotation-item.ts +++ b/src/web-components/comments/components/annotation-item.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { classMap } from 'lit/directives/class-map.js'; import { ParticipantByGroupApi } from '../../../common/types/participant.types'; @@ -9,11 +9,12 @@ import importStyle from '../../base/utils/importStyle'; import { annotationItemStyle } from '../css'; import { AnnotationFilter } from './types'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, annotationItemStyle]; -@customElement('superviz-comments-annotation-item') +@CreateElement('superviz-comments-annotation-item') export class CommentsAnnotationItem extends WebComponentsBaseElement { static styles = styles; diff --git a/src/web-components/comments/components/annotation-pin.test.ts b/src/web-components/comments/components/annotation-pin.test.ts index 858f654d..0911dc13 100644 --- a/src/web-components/comments/components/annotation-pin.test.ts +++ b/src/web-components/comments/components/annotation-pin.test.ts @@ -6,7 +6,7 @@ import { useGlobalStore } from '../../../services/stores'; import type { CommentsAnnotationPin } from './annotation-pin'; import { PinMode } from './types'; -import '.'; +import './annotation-pin'; import '../../icon'; interface CreateAnnotationPinOptions { diff --git a/src/web-components/comments/components/annotation-pin.ts b/src/web-components/comments/components/annotation-pin.ts index c11ce7a3..fa5aa4df 100644 --- a/src/web-components/comments/components/annotation-pin.ts +++ b/src/web-components/comments/components/annotation-pin.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { classMap } from 'lit/directives/class-map.js'; import { Participant, ParticipantByGroupApi } from '../../../common/types/participant.types'; @@ -10,11 +10,12 @@ import importStyle from '../../base/utils/importStyle'; import { annotationPinStyles } from '../css'; import { PinMode, HorizontalSide, Sides } from './types'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, annotationPinStyles]; -@customElement('superviz-comments-annotation-pin') +@CreateElement('superviz-comments-annotation-pin') export class CommentsAnnotationPin extends WebComponentsBaseElement { declare type: PinMode; declare active: boolean; diff --git a/src/web-components/comments/components/annotation-resolved.test.ts b/src/web-components/comments/components/annotation-resolved.test.ts index 258c87b7..10d62803 100644 --- a/src/web-components/comments/components/annotation-resolved.test.ts +++ b/src/web-components/comments/components/annotation-resolved.test.ts @@ -1,8 +1,8 @@ import sleep from '../../../common/utils/sleep'; -import '.'; - let element: HTMLElement; +import './annotation-resolved'; + const createElement = async (timeToHide = 1000) => { const element = document.createElement('superviz-comments-annotation-resolved'); diff --git a/src/web-components/comments/components/annotation-resolved.ts b/src/web-components/comments/components/annotation-resolved.ts index 79e45e33..c50ccd02 100644 --- a/src/web-components/comments/components/annotation-resolved.ts +++ b/src/web-components/comments/components/annotation-resolved.ts @@ -1,16 +1,16 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; import { WebComponentsBase } from '../../base'; import importStyle from '../../base/utils/importStyle'; import { annotationResolvedStyle } from '../css'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, annotationResolvedStyle]; const DEFAULT_SECONDS_TO_HIDE = 10 * 1000; -@customElement('superviz-comments-annotation-resolved') +@CreateElement('superviz-comments-annotation-resolved') export class CommentsAnnotationResolved extends WebComponentsBaseElement { constructor() { super(); diff --git a/src/web-components/comments/components/comment-input.test.ts b/src/web-components/comments/components/comment-input.test.ts index 1ff66cee..80597392 100644 --- a/src/web-components/comments/components/comment-input.test.ts +++ b/src/web-components/comments/components/comment-input.test.ts @@ -2,7 +2,7 @@ import { MOCK_PARTICIPANT_LIST } from '../../../../__mocks__/participants.mock'; import sleep from '../../../common/utils/sleep'; import { AutoCompleteHandler } from '../utils/autocomplete-handler'; import mentionHandler from '../utils/mention-handler'; -import '.'; +import './comment-input'; import { CommentsCommentInput } from './comment-input'; diff --git a/src/web-components/comments/components/comment-input.ts b/src/web-components/comments/components/comment-input.ts index 4ae6c029..6b948fbe 100644 --- a/src/web-components/comments/components/comment-input.ts +++ b/src/web-components/comments/components/comment-input.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { classMap } from 'lit/directives/class-map.js'; import { ParticipantByGroupApi } from '../../../common/types/participant.types'; @@ -11,11 +11,12 @@ import { AutoCompleteHandler } from '../utils/autocomplete-handler'; import mentionHandler from '../utils/mention-handler'; import { CommentMode } from './types'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, commentInputStyle]; -@customElement('superviz-comments-comment-input') +@CreateElement('superviz-comments-comment-input') export class CommentsCommentInput extends WebComponentsBaseElement { declare eventType: string; declare text: string; diff --git a/src/web-components/comments/components/comment-item.test.ts b/src/web-components/comments/components/comment-item.test.ts index ed5eef27..5e1dce5c 100644 --- a/src/web-components/comments/components/comment-item.test.ts +++ b/src/web-components/comments/components/comment-item.test.ts @@ -2,7 +2,7 @@ import { DateTime } from 'luxon'; import sleep from '../../../common/utils/sleep'; -import '.'; +import './comment-item'; import { CommentDropdownOptions, CommentMode } from './types'; const DEFAULT_ELEMENT_OPTIONS = { diff --git a/src/web-components/comments/components/comment-item.ts b/src/web-components/comments/components/comment-item.ts index 5721146f..cd674918 100644 --- a/src/web-components/comments/components/comment-item.ts +++ b/src/web-components/comments/components/comment-item.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { classMap } from 'lit/directives/class-map.js'; import { DateTime } from 'luxon'; @@ -9,11 +9,12 @@ import importStyle from '../../base/utils/importStyle'; import { commentItemStyle } from '../css'; import { CommentMode, CommentDropdownOptions, AnnotationFilter } from './types'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, commentItemStyle]; -@customElement('superviz-comments-comment-item') +@CreateElement('superviz-comments-comment-item') export class CommentsCommentItem extends WebComponentsBaseElement { constructor() { super(); diff --git a/src/web-components/comments/components/content.test.ts b/src/web-components/comments/components/content.test.ts index 18afb04c..aecc819a 100644 --- a/src/web-components/comments/components/content.test.ts +++ b/src/web-components/comments/components/content.test.ts @@ -1,6 +1,6 @@ import { MOCK_ANNOTATION } from '../../../../__mocks__/comments.mock'; import sleep from '../../../common/utils/sleep'; -import '.'; +import './content'; import { AnnotationFilter } from './types'; diff --git a/src/web-components/comments/components/content.ts b/src/web-components/comments/components/content.ts index 94dab69e..0d37b441 100644 --- a/src/web-components/comments/components/content.ts +++ b/src/web-components/comments/components/content.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, LitElement, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { repeat } from 'lit/directives/repeat.js'; import { ParticipantByGroupApi } from '../../../common/types/participant.types'; @@ -8,11 +8,12 @@ import { WebComponentsBase } from '../../base'; import { contentStyle } from '../css'; import { AnnotationFilter, PinMode } from './types'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, contentStyle]; -@customElement('superviz-comments-content') +@CreateElement('superviz-comments-content') export class CommentsContent extends WebComponentsBaseElement { static styles = styles; diff --git a/src/web-components/comments/components/delete-comment-modal.test.ts b/src/web-components/comments/components/delete-comment-modal.test.ts index 64457f1c..f236191f 100644 --- a/src/web-components/comments/components/delete-comment-modal.test.ts +++ b/src/web-components/comments/components/delete-comment-modal.test.ts @@ -1,6 +1,6 @@ import sleep from '../../../common/utils/sleep'; -import '.'; +import './delete-comment-modal'; const createEl = async (open = false) => { const element = document.createElement('superviz-comments-delete-comments-modal'); @@ -20,8 +20,7 @@ const modal = () => element().shadowRoot!.querySelector('superviz-modal') as HTM describe('delete-comment-modal', () => { afterEach(() => { - document.body.querySelector('superviz-comments-delete-comments-modal') - ?.remove(); + document.body.querySelector('superviz-comments-delete-comments-modal')?.remove(); }); test('should render', async () => { @@ -65,7 +64,9 @@ describe('delete-comment-modal', () => { const spy = jest.fn(); element.addEventListener('close', spy); - window.document.body.dispatchEvent(new CustomEvent('superviz-modal--close', { composed: true, bubbles: true })); + window.document.body.dispatchEvent( + new CustomEvent('superviz-modal--close', { composed: true, bubbles: true }), + ); await element['updateComplete']; @@ -77,7 +78,9 @@ describe('delete-comment-modal', () => { const spy = jest.fn(); element.addEventListener('confirm', spy); - window.document.body.dispatchEvent(new CustomEvent('superviz-modal--confirm', { composed: true, bubbles: true })); + window.document.body.dispatchEvent( + new CustomEvent('superviz-modal--confirm', { composed: true, bubbles: true }), + ); await element['updateComplete']; diff --git a/src/web-components/comments/components/delete-comment-modal.ts b/src/web-components/comments/components/delete-comment-modal.ts index 9c3a78f6..8b2f9a3a 100644 --- a/src/web-components/comments/components/delete-comment-modal.ts +++ b/src/web-components/comments/components/delete-comment-modal.ts @@ -1,12 +1,12 @@ import { CSSResultGroup, LitElement, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; import { WebComponentsBase } from '../../base'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles]; -@customElement('superviz-comments-delete-comments-modal') +@CreateElement('superviz-comments-delete-comments-modal') export class DeleteCommentModal extends WebComponentsBaseElement { static styles = styles; diff --git a/src/web-components/comments/components/float-button.test.ts b/src/web-components/comments/components/float-button.test.ts index ba27c9d1..ab208ba6 100644 --- a/src/web-components/comments/components/float-button.test.ts +++ b/src/web-components/comments/components/float-button.test.ts @@ -1,7 +1,7 @@ -import '.'; import sleep from '../../../common/utils/sleep'; -import type { CommentsFloatButton } from '.'; +import './float-button'; +import type { CommentsFloatButton } from './float-button'; let element: CommentsFloatButton; diff --git a/src/web-components/comments/components/float-button.ts b/src/web-components/comments/components/float-button.ts index 7618f814..a95f2d0f 100644 --- a/src/web-components/comments/components/float-button.ts +++ b/src/web-components/comments/components/float-button.ts @@ -1,15 +1,16 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { classMap } from 'lit/directives/class-map.js'; import { WebComponentsBase } from '../../base'; import importStyle from '../../base/utils/importStyle'; import { floatButtonStyle } from '../css'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, floatButtonStyle]; -@customElement('superviz-comments-button') +@CreateElement('superviz-comments-button') export class CommentsFloatButton extends WebComponentsBaseElement { static styles = styles; declare isHidden: boolean; diff --git a/src/web-components/comments/components/index.test.ts b/src/web-components/comments/components/index.test.ts deleted file mode 100644 index 0226ca03..00000000 --- a/src/web-components/comments/components/index.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { CommentsAnnotationItem } from './annotation-item'; -import { CommentsAnnotationPin } from './annotation-pin'; -import { CommentsCommentInput } from './comment-input'; -import { CommentsCommentItem } from './comment-item'; -import { CommentsContent } from './content'; -import { CommentsTopbar } from './topbar'; - -import * as Components from '.'; - -describe('Components', () => { - test('should be export CommentsTopbar', () => { - expect(Components.CommentsTopbar).toBeDefined(); - expect(Components.CommentsTopbar).toBe(CommentsTopbar); - }); - - test('should be export CommentsContent', () => { - expect(Components.CommentsContent).toBeDefined(); - expect(Components.CommentsContent).toBe(CommentsContent); - }); - - test('should be export CommentsCommentItem', () => { - expect(Components.CommentsCommentItem).toBeDefined(); - expect(Components.CommentsCommentItem).toBe(CommentsCommentItem); - }); - - test('should be export CommentsCommentInput', () => { - expect(Components.CommentsCommentInput).toBeDefined(); - expect(Components.CommentsCommentInput).toBe(CommentsCommentInput); - }); - - test('should be export CommentsAnnotationPin', () => { - expect(Components.CommentsAnnotationPin).toBeDefined(); - expect(Components.CommentsAnnotationPin).toBe(CommentsAnnotationPin); - }); - - test('should be export CommentsAnnotationItem', () => { - expect(Components.CommentsAnnotationItem).toBeDefined(); - expect(Components.CommentsAnnotationItem).toBe(CommentsAnnotationItem); - }); -}); diff --git a/src/web-components/comments/components/index.ts b/src/web-components/comments/components/index.ts deleted file mode 100644 index f6158b42..00000000 --- a/src/web-components/comments/components/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { CommentsTopbar } from './topbar'; -export { CommentsContent } from './content'; -export { CommentsCommentItem } from './comment-item'; -export { CommentsCommentInput } from './comment-input'; -export { CommentsAnnotationPin } from './annotation-pin'; -export { CommentsAnnotationItem } from './annotation-item'; -export { DeleteCommentModal } from './delete-comment-modal'; -export { CommentsAnnotationResolved } from './annotation-resolved'; -export { CommentsAnnotationFilter } from './annotation-filter'; -export { CommentsFloatButton } from './float-button'; -export { CommentsMentionList } from './mention-list'; diff --git a/src/web-components/comments/components/mention-list.test.ts b/src/web-components/comments/components/mention-list.test.ts index e02b1114..77918681 100644 --- a/src/web-components/comments/components/mention-list.test.ts +++ b/src/web-components/comments/components/mention-list.test.ts @@ -1,6 +1,7 @@ import { MOCK_PARTICIPANT_LIST } from '../../../../__mocks__/participants.mock'; import sleep from '../../../common/utils/sleep'; -import '.'; + +import './mention-list'; describe('CommentsMentionList', () => { let element; @@ -23,15 +24,15 @@ describe('CommentsMentionList', () => { expect(element.shadowRoot.getElementById('mention-list').style.display).toEqual('block'); }); - test('selects a participant and emits event', async () => { + test('selects a participant and emits event', async () => { element.participants = MOCK_PARTICIPANT_LIST; await element.updateComplete; - + const mentionItem = element.shadowRoot.querySelector('.mention-item'); mentionItem.click(); - + element['selectParticipant'](MOCK_PARTICIPANT_LIST[0]); - expect(element.shadowRoot.getElementById('mention-list').style.display).toEqual('none'); + expect(element.shadowRoot.getElementById('mention-list').style.display).toEqual('none'); }); test('renders without participants', async () => { @@ -46,7 +47,7 @@ describe('CommentsMentionList', () => { expect(element.shadowRoot.getElementById('mention-list').style.display).toEqual('block'); }); - test('selects a participant and emits event', async () => { + test('selects a participant and emits event', async () => { element.participants = MOCK_PARTICIPANT_LIST; await element.updateComplete; @@ -54,7 +55,7 @@ describe('CommentsMentionList', () => { mentionItem.click(); element['selectParticipant'](MOCK_PARTICIPANT_LIST[0]); - expect(element.shadowRoot.getElementById('mention-list').style.display).toEqual('none'); + expect(element.shadowRoot.getElementById('mention-list').style.display).toEqual('none'); }); test('should display avatar', async () => { @@ -69,8 +70,8 @@ describe('CommentsMentionList', () => { element.participants = [ { ...MOCK_PARTICIPANT_LIST[0], - avatar: null - } + avatar: null, + }, ]; await element.updateComplete; @@ -87,7 +88,6 @@ describe('CommentsMentionList', () => { expect(mentionListElement?.style.display).toEqual('block'); - const mockHandleZoom = jest.fn(); element.stopHandleZoom = mockHandleZoom; @@ -142,8 +142,8 @@ describe('CommentsMentionList', () => { element.participants = [ { ...MOCK_PARTICIPANT_LIST[0], - avatar: null - } + avatar: null, + }, ]; await element.updateComplete; diff --git a/src/web-components/comments/components/mention-list.ts b/src/web-components/comments/components/mention-list.ts index 87ca8cb5..d8df809b 100644 --- a/src/web-components/comments/components/mention-list.ts +++ b/src/web-components/comments/components/mention-list.ts @@ -1,15 +1,16 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { repeat } from 'lit/directives/repeat.js'; import { ParticipantByGroupApi } from '../../../common/types/participant.types'; import { WebComponentsBase } from '../../base'; import { mentionListStyle } from '../css'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, mentionListStyle]; -@customElement('superviz-comments-mention-list') +@CreateElement('superviz-comments-mention-list') export class CommentsMentionList extends WebComponentsBaseElement { constructor() { super(); diff --git a/src/web-components/comments/components/topbar.test.ts b/src/web-components/comments/components/topbar.test.ts index 287c6eb0..7334be8b 100644 --- a/src/web-components/comments/components/topbar.test.ts +++ b/src/web-components/comments/components/topbar.test.ts @@ -1,4 +1,4 @@ -import '.'; +import './topbar'; const element = document.createElement('superviz-comments-topbar'); document.body.appendChild(element); diff --git a/src/web-components/comments/components/topbar.ts b/src/web-components/comments/components/topbar.ts index 8da61774..8cdbdb8c 100644 --- a/src/web-components/comments/components/topbar.ts +++ b/src/web-components/comments/components/topbar.ts @@ -1,14 +1,14 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; import { WebComponentsBase } from '../../base'; import importStyle from '../../base/utils/importStyle'; import { topbarStyle } from '../css'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, topbarStyle]; -@customElement('superviz-comments-topbar') +@CreateElement('superviz-comments-topbar') export class CommentsTopbar extends WebComponentsBase(LitElement) { static styles = styles; declare side: string; diff --git a/src/web-components/comments/index.test.ts b/src/web-components/comments/index.test.ts index 0666d87f..623640b4 100644 --- a/src/web-components/comments/index.test.ts +++ b/src/web-components/comments/index.test.ts @@ -1,47 +1,125 @@ -import { Comments } from './comments'; +import { ParticipantByGroupApi } from '../../common/types/participant.types'; +import sleep from '../../common/utils/sleep'; +import '.'; +import './components/topbar'; +import './components/content'; +import './components/comment-item'; +import './components/comment-input'; +import './components/annotation-pin'; +import './components/annotation-item'; +import './components/delete-comment-modal'; +import './components/annotation-resolved'; +import './components/annotation-filter'; +import './components/float-button'; +import './components/mention-list'; -import * as Components from '.'; +let element: HTMLElement; +const MOCK_PARTICIPANTS: ParticipantByGroupApi[] = [ + { + name: 'John Zero', + avatar: 'avatar1.png', + id: '1', + email: 'john.zero@mail.com', + }, + { + name: 'John Uno', + avatar: 'avatar2.png', + id: '2', + email: 'john.uno@mail.com', + }, + { + name: 'John Doe', + avatar: 'avatar3.png', + id: '3', + email: 'john.doe@mail.com', + }, +]; +describe('comments', () => { + beforeEach(async () => { + element = document.createElement('superviz-comments'); + element.setAttribute('comments', JSON.stringify([])); + document.body.appendChild(element); + await sleep(); + }); -export { CommentsTopbar } from './components/topbar'; -export { CommentsContent } from './components/content'; -export { CommentsCommentItem } from './components/comment-item'; -export { CommentsCommentInput } from './components/comment-input'; -export { CommentsAnnotationPin } from './components/annotation-pin'; -export { CommentsAnnotationItem } from './components/annotation-item'; + afterEach(() => { + document.body.removeChild(element); + }); -describe('Components', () => { - test('should be export Comments', () => { - expect(Comments).toBeDefined(); - expect(Components.Comments).toBe(Comments); + test('renders the component', async () => { + const renderedElement = document.getElementsByTagName('superviz-comments')[0]; + expect(renderedElement).toBeTruthy(); }); - test('should be export CommentsTopbar', () => { - expect(Components.CommentsTopbar).toBeDefined(); - expect(Components.CommentsTopbar).toBe(Components.CommentsTopbar); + test('should close superviz comments', async () => { + const renderedElement = document.getElementsByTagName('superviz-comments')[0]; + const app = renderedElement.shadowRoot!.querySelector('.superviz-comments'); + + renderedElement.setAttributeNode(document.createAttribute('open')); + renderedElement.removeAttribute('open'); + + expect(renderedElement.hasAttribute('open')).toBeFalsy(); + expect(app?.classList.contains('hide-at-left')).toBeTruthy(); }); - test('should be export CommentsContent', () => { - expect(Components.CommentsContent).toBeDefined(); - expect(Components.CommentsContent).toBe(Components.CommentsContent); + test('should open superviz comments', async () => { + const renderedElement = document.getElementsByTagName('superviz-comments')[0]; + const app = renderedElement.shadowRoot!.getElementById('superviz-comments'); + + renderedElement.setAttributeNode(document.createAttribute('open')); + + await sleep(); + + expect(renderedElement.hasAttribute('open')).toBeTruthy(); + expect(app?.classList.contains('superviz-comments')).toBe(true); + }); + + test('should update annotations', async () => { + const annotations = [{ id: '1', x: 0, y: 0, width: 0, height: 0, text: 'test' }]; + + element['updateAnnotations'](annotations); + + await sleep(); + + expect(element['annotations']).toEqual(annotations); }); - test('should be export CommentsCommentItem', () => { - expect(Components.CommentsCommentItem).toBeDefined(); - expect(Components.CommentsCommentItem).toBe(Components.CommentsCommentItem); + test('should set filter', async () => { + const label = 'test'; + const detail = { filter: { label } }; + + element['setFilter']({ detail }); + + await sleep(); + + expect(element['annotationFilter']).toEqual(label); }); - test('should be export CommentsCommentInput', () => { - expect(Components.CommentsCommentInput).toBeDefined(); - expect(Components.CommentsCommentInput).toBe(Components.CommentsCommentInput); + test('should show water mark', async () => { + const waterMark = true; + element['waterMarkStatus'](waterMark); + + await sleep(); + + expect(element['waterMarkState']).toEqual(waterMark); }); - test('should be export CommentsAnnotationPin', () => { - expect(Components.CommentsAnnotationPin).toBeDefined(); - expect(Components.CommentsAnnotationPin).toBe(Components.CommentsAnnotationPin); + test('should not show water mark', async () => { + const waterMark = false; + element['waterMarkStatus'](waterMark); + + await sleep(); + + expect(element['waterMarkState']).toEqual(waterMark); }); - test('should be export CommentsAnnotationItem', () => { - expect(Components.CommentsAnnotationItem).toBeDefined(); - expect(Components.CommentsAnnotationItem).toBe(Components.CommentsAnnotationItem); + test('should receive participants List', async () => { + const participantsList = JSON.stringify([MOCK_PARTICIPANTS]); + + element['participantsListed'](participantsList); + + await sleep(); + + expect(element['participantsList']).toEqual(participantsList); }); }); diff --git a/src/web-components/comments/index.ts b/src/web-components/comments/index.ts index c3eb71fa..f8096c04 100644 --- a/src/web-components/comments/index.ts +++ b/src/web-components/comments/index.ts @@ -1,2 +1,165 @@ -export { Comments } from './comments'; -export * from './components'; +import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; + +import { classMap } from 'lit/directives/class-map.js'; + +import { ParticipantByGroupApi } from '../../common/types/participant.types'; +import { Annotation, CommentsSide, Offset } from '../../components/comments/types'; +import { WebComponentsBase } from '../base'; +import importStyle from '../base/utils/importStyle'; + +import { AnnotationFilter } from './components/types'; +import { commentsStyle, poweredByStyle } from './css/index'; +import { waterMarkElementObserver } from './utils/watermark'; +import { CreateElement } from '../global/decorators/create-element.decorator'; + +const WebComponentsBaseElement = WebComponentsBase(LitElement); +const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, commentsStyle, poweredByStyle]; + +@CreateElement('superviz-comments') +export class Comments extends WebComponentsBaseElement { + static styles = styles; + + declare open: boolean; + declare annotations: Annotation[]; + declare annotationFilter: AnnotationFilter; + declare waterMarkState: boolean; + declare side: CommentsSide; + declare participantsList: ParticipantByGroupApi[]; + declare offset: Offset; + + static properties = { + open: { type: Boolean }, + annotations: { type: Object }, + annotationFilter: { type: String }, + waterMarkState: { type: Boolean }, + side: { type: String }, + participantsList: { type: Object }, + offset: { type: Object }, + }; + + constructor() { + super(); + this.annotations = []; + this.annotationFilter = AnnotationFilter.ALL; + this.waterMarkState = false; + this.participantsList = []; + this.side = CommentsSide.LEFT; + } + + protected firstUpdated( + _changedProperties: PropertyValueMap | Map, + ): void { + super.firstUpdated(_changedProperties); + this.updateComplete.then(() => { + importStyle.call(this, ['comments']); + }); + } + + protected updated(changedProperties: Map) { + super.updated(changedProperties); + this.updateComplete.then(() => { + if (this.waterMarkState) { + waterMarkElementObserver(this.shadowRoot); + } + + if (changedProperties.has('offset')) { + this.applyOffset(); + } + }); + } + + public participantsListed(participants: ParticipantByGroupApi[]) { + this.participantsList = participants; + } + + public updateAnnotations(data: Annotation[]) { + this.annotations = data; + } + + private close() { + this.emitEvent('close-threads', {}); + } + + waterMarkStatus(waterMark: boolean) { + this.waterMarkState = waterMark; + } + + private setFilter({ detail }) { + const { + filter: { label }, + } = detail; + this.annotationFilter = label; + } + + private getOffset(offset: number) { + if (offset === null || offset === undefined || offset < 0) { + return '10px'; + } + + return `${offset}px`; + } + + private applyOffset() { + const supervizCommentsDiv: HTMLDivElement = this.shadowRoot.querySelector('.superviz-comments'); + if (!supervizCommentsDiv) return; + + const { left, right, top, bottom } = this.offset; + + supervizCommentsDiv.style.setProperty('--offset-top', this.getOffset(top)); + supervizCommentsDiv.style.setProperty('--offset-bottom', this.getOffset(bottom)); + supervizCommentsDiv.style.setProperty('--offset-right', this.getOffset(right)); + supervizCommentsDiv.style.setProperty('--offset-left', this.getOffset(left)); + } + + private get poweredBy() { + if (!this.waterMarkState) return html``; + + return html` `; + } + + protected render() { + const classes = { + 'superviz-comments': true, + 'threads-on-left-side': this.side === CommentsSide.LEFT, + 'threads-on-right-side': this.side === CommentsSide.RIGHT, + 'hide-at-right': this.side === CommentsSide.RIGHT && !this.open, + 'hide-at-left': this.side === CommentsSide.LEFT && !this.open, + }; + + return html` +
+
+ +
+ + + + ${this.poweredBy} +
+ `; + } +} diff --git a/src/web-components/dropdown/index.ts b/src/web-components/dropdown/index.ts index 4189b1d1..10b4ad66 100644 --- a/src/web-components/dropdown/index.ts +++ b/src/web-components/dropdown/index.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { classMap } from 'lit/directives/class-map.js'; import { WebComponentsBase } from '../base'; @@ -7,11 +7,12 @@ import importStyle from '../base/utils/importStyle'; import { dropdownStyle } from './index.style'; import { DropdownOption } from './types'; +import { CreateElement } from '../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, dropdownStyle]; -@customElement('superviz-dropdown') +@CreateElement('superviz-dropdown') export class Dropdown extends WebComponentsBaseElement { static styles = styles; diff --git a/src/web-components/global/decorators/create-element.decorator.ts b/src/web-components/global/decorators/create-element.decorator.ts new file mode 100644 index 00000000..e27b648b --- /dev/null +++ b/src/web-components/global/decorators/create-element.decorator.ts @@ -0,0 +1,35 @@ +import { customElement } from 'lit/decorators.js'; + +/** + * A decorator function that creates a custom element. + * + * @param {string} elementName - The name of the custom element to be created. + * @returns {Function} A decorator function that registers the custom element. + * + * @description + * This decorator function wraps the Lit `customElement` decorator and adds some additional checks: + * - It checks if the element has already been declared to avoid duplicate declarations. + * - It checks if the current environment supports custom elements (i.e., if `window` and `HTMLElement` are defined). + * + * If the element has already been declared or if the environment doesn't support custom elements, + * the decorator will not register the element and will simply return. + * + * @example + * ``` + * @CreateElement('my-custom-element') + * class MyCustomElement extends LitElement { + * // ... + * } + * ``` + */ +export function CreateElement(elementName: string): Function { + return function (constructor: Function) { + const alreadyDeclared = !!customElements.get(elementName); + const isWithWrongEnvirionment = + typeof window === 'undefined' || typeof HTMLElement === 'undefined'; + + if (isWithWrongEnvirionment || alreadyDeclared) return; + + customElement(elementName)(constructor); + }; +} diff --git a/src/web-components/hello-world/index.ts b/src/web-components/hello-world/index.ts index 6481edf4..b22eb8ee 100644 --- a/src/web-components/hello-world/index.ts +++ b/src/web-components/hello-world/index.ts @@ -1,7 +1,7 @@ import { LitElement, css, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; +import { CreateElement } from '../global/decorators/create-element.decorator'; -@customElement('superviz-hello-world') +@CreateElement('superviz-hello-world') export class HelloWorld extends LitElement { declare name: string; diff --git a/src/web-components/icon/index.ts b/src/web-components/icon/index.ts index d3a7ef96..91e03f22 100644 --- a/src/web-components/icon/index.ts +++ b/src/web-components/icon/index.ts @@ -1,14 +1,14 @@ import { CSSResultGroup, LitElement, css, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; import { WebComponentsBase } from '../base'; import { IconSizes } from './types'; +import { CreateElement } from '../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles]; -@customElement('superviz-icon') +@CreateElement('superviz-icon') export class Icon extends WebComponentsBaseElement { declare name: string; declare size: string; diff --git a/src/web-components/index.ts b/src/web-components/index.ts index f7318221..13a2c4ff 100644 --- a/src/web-components/index.ts +++ b/src/web-components/index.ts @@ -1,7 +1,23 @@ -export { HelloWorld } from './hello-world'; -export { Icon } from './icon'; -export { Dropdown } from './dropdown'; -export { Tooltip } from './tooltip'; -export * from './modal'; -export * from './comments'; -export * from './who-is-online'; +if (typeof window !== 'undefined' && typeof HTMLElement !== 'undefined') { + // Generic components + import('./icon'); + import('./dropdown'); + import('./tooltip'); + import('./modal'); + // Comments + import('./comments'); + import('./comments/components/topbar'); + import('./comments/components/content'); + import('./comments/components/comment-item'); + import('./comments/components/comment-input'); + import('./comments/components/annotation-pin'); + import('./comments/components/annotation-item'); + import('./comments/components/delete-comment-modal'); + import('./comments/components/annotation-resolved'); + import('./comments/components/annotation-filter'); + import('./comments/components/float-button'); + import('./comments/components/mention-list'); + // Who is Online + import('./who-is-online'); + import('./hello-world'); +} diff --git a/src/web-components/modal/modal-container.ts b/src/web-components/modal/modal-container.ts index 49bcbf80..4c08f2d7 100644 --- a/src/web-components/modal/modal-container.ts +++ b/src/web-components/modal/modal-container.ts @@ -1,15 +1,15 @@ import { CSSResultGroup, LitElement, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; import { WebComponentsBase } from '../base'; import { modalStyle } from './styles/index.style'; import { ModalOptions } from './types'; +import { CreateElement } from '../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, modalStyle]; -@customElement('superviz-modal-container') +@CreateElement('superviz-modal-container') export class ModalContainer extends WebComponentsBaseElement { static styles = styles; diff --git a/src/web-components/modal/modal.test.ts b/src/web-components/modal/modal.test.ts index 9f51b11a..36dcc984 100644 --- a/src/web-components/modal/modal.test.ts +++ b/src/web-components/modal/modal.test.ts @@ -10,17 +10,18 @@ const createEl = (): HTMLElement => { describe('modal', () => { afterEach(() => { - document.body.querySelector('superviz-modal') - ?.remove(); + document.body.querySelector('superviz-modal')?.remove(); }); test('should render modal container when open is true', async () => { createEl(); await sleep(100); - document.body.dispatchEvent(new CustomEvent('superviz-modal--open', { - detail: {}, - })); + document.body.dispatchEvent( + new CustomEvent('superviz-modal--open', { + detail: {}, + }), + ); await sleep(100); @@ -34,9 +35,11 @@ describe('modal', () => { await sleep(100); - document.body.dispatchEvent(new CustomEvent('superviz-modal--open', { - detail: {}, - })); + document.body.dispatchEvent( + new CustomEvent('superviz-modal--open', { + detail: {}, + }), + ); await sleep(100); @@ -51,9 +54,11 @@ describe('modal', () => { const element = createEl(); await sleep(100); - document.body.dispatchEvent(new CustomEvent('superviz-modal--open', { - detail: {}, - })); + document.body.dispatchEvent( + new CustomEvent('superviz-modal--open', { + detail: {}, + }), + ); await sleep(100); diff --git a/src/web-components/modal/modal.ts b/src/web-components/modal/modal.ts index 41f6dc9b..20a9df34 100644 --- a/src/web-components/modal/modal.ts +++ b/src/web-components/modal/modal.ts @@ -1,16 +1,16 @@ import { CSSResultGroup, LitElement } from 'lit'; -import { customElement } from 'lit/decorators.js'; import { WebComponentsBase } from '../base'; import { ModalContainer } from './modal-container'; import { modalStyle } from './styles/index.style'; import { ModalOptions } from './types'; +import { CreateElement } from '../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, modalStyle]; -@customElement('superviz-modal') +@CreateElement('superviz-modal') export class Modal extends WebComponentsBaseElement { static styles = styles; diff --git a/src/web-components/tooltip/index.ts b/src/web-components/tooltip/index.ts index a4b9b896..8d4814ec 100644 --- a/src/web-components/tooltip/index.ts +++ b/src/web-components/tooltip/index.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { classMap } from 'lit/directives/class-map.js'; import { WebComponentsBase } from '../base'; @@ -7,11 +7,12 @@ import importStyle from '../base/utils/importStyle'; import { dropdownStyle } from './index.style'; import { Positions, PositionsEnum } from './types'; +import { CreateElement } from '../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, dropdownStyle]; -@customElement('superviz-tooltip') +@CreateElement('superviz-tooltip') export class Tooltip extends WebComponentsBaseElement { static styles = styles; diff --git a/src/web-components/who-is-online/components/dropdown.ts b/src/web-components/who-is-online/components/dropdown.ts index 41c31251..8cd063df 100644 --- a/src/web-components/who-is-online/components/dropdown.ts +++ b/src/web-components/who-is-online/components/dropdown.ts @@ -1,6 +1,6 @@ // @ts-nocheck import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { classMap } from 'lit/directives/class-map.js'; import { repeat } from 'lit/directives/repeat.js'; @@ -11,11 +11,12 @@ import importStyle from '../../base/utils/importStyle'; import { dropdownStyle } from '../css'; import { Following, VerticalSide, HorizontalSide } from './types'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, dropdownStyle]; -@customElement('superviz-who-is-online-dropdown') +@CreateElement('superviz-who-is-online-dropdown') export class WhoIsOnlineDropdown extends WebComponentsBaseElement { static styles = styles; diff --git a/src/web-components/who-is-online/components/messages.ts b/src/web-components/who-is-online/components/messages.ts index 5c3d9f71..cb9ef9cf 100644 --- a/src/web-components/who-is-online/components/messages.ts +++ b/src/web-components/who-is-online/components/messages.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, LitElement, PropertyDeclaration, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { classMap } from 'lit/directives/class-map.js'; import { Participant } from '../../../common/types/participant.types'; @@ -11,11 +11,12 @@ import { messagesStyle } from '../css'; import { HorizontalSide, VerticalSide } from './types'; import { MEETING_COLORS } from '../../../common/types/meeting-colors.types'; +import { CreateElement } from '../../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, messagesStyle]; -@customElement('superviz-who-is-online-messages') +@CreateElement('superviz-who-is-online-messages') export class WhoIsOnlineMessages extends WebComponentsBaseElement { static styles = styles; diff --git a/src/web-components/who-is-online/who-is-online.ts b/src/web-components/who-is-online/who-is-online.ts index 9026b7fe..001e87a9 100644 --- a/src/web-components/who-is-online/who-is-online.ts +++ b/src/web-components/who-is-online/who-is-online.ts @@ -1,5 +1,5 @@ import { CSSResultGroup, LitElement, PropertyValueMap, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; + import { classMap } from 'lit/directives/class-map.js'; import { repeat } from 'lit/directives/repeat.js'; @@ -16,11 +16,12 @@ import importStyle from '../base/utils/importStyle'; import type { LocalParticipantData } from './components/types'; import { whoIsOnlineStyle } from './css/index'; +import { CreateElement } from '../global/decorators/create-element.decorator'; const WebComponentsBaseElement = WebComponentsBase(LitElement); const styles: CSSResultGroup[] = [WebComponentsBaseElement.styles, whoIsOnlineStyle]; -@customElement('superviz-who-is-online') +@CreateElement('superviz-who-is-online') export class WhoIsOnline extends WebComponentsBaseElement { static styles = styles; declare position: string; diff --git a/tsconfig.json b/tsconfig.json index c2f8412e..219a0eb9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "rootDirs": ["./src", "."], "target": "ES2020", "module": "ES2020", - "outDir": "./lib", + "outDir": "./dist", "lib": ["ES2020", "DOM"], "preserveWatchOutput": true, "emitDeclarationOnly": true, @@ -12,13 +12,8 @@ "moduleResolution": "Node", "experimentalDecorators": true, "skipLibCheck": true, - "allowJs": true, + "allowJs": true }, - "include": [ - "./src" - ], - "exclude": [ - "./src/**/*.test.ts", - "node_modules" - ] -} \ No newline at end of file + "include": ["./src"], + "exclude": ["./src/**/*.test.ts", "node_modules"] +} diff --git a/yarn.lock b/yarn.lock index bcdb9811..affa3bec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2472,6 +2472,13 @@ unbzip2-stream "1.4.3" yargs "17.7.1" +"@reactivex/rxjs@^6.6.7": + version "6.6.7" + resolved "https://registry.yarnpkg.com/@reactivex/rxjs/-/rxjs-6.6.7.tgz#52ab48f989aba9cda2b995acc904a43e6e1b3b40" + integrity sha512-xZIV2JgHhWoVPm3uVcFbZDRVJfx2hgqmuTX7J4MuKaZ+j5jN29agniCPBwrlCmpA15/zLKcPi7/bogt0ZwOFyA== + dependencies: + tslib "^1.9.0" + "@rollup/plugin-node-resolve@^15.0.1": version "15.1.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.1.0.tgz#9ffcd8e8c457080dba89bb9fcb583a6778dc757e" @@ -2659,6 +2666,19 @@ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== +"@superviz/socket-client@^1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@superviz/socket-client/-/socket-client-1.10.0.tgz#98773e2a30ecdea6971cbca4f89ba3cf8a14ec18" + integrity sha512-7cbeFynVdILFiEV4UE7XyPrVHgRNnlNwtc+13YPPpn1JMufO7D9AwUHU6cUd9FOZAy3nCQG4BW9GFpGSMmDtWA== + dependencies: + "@reactivex/rxjs" "^6.6.7" + debug "^4.3.5" + lodash "^4.17.21" + rxjs "^7.8.1" + semantic-release-version-file "^1.0.2" + socket.io-client "^4.7.5" + zod "^3.23.8" + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" @@ -4763,6 +4783,13 @@ debug@^4.3.1, debug@~4.3.1, debug@~4.3.2: dependencies: ms "2.1.2" +debug@^4.3.5: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + dependencies: + ms "2.1.2" + decimal.js@^10.4.2: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" @@ -10578,6 +10605,11 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + tslib@^2.0.1, tslib@^2.4.0: version "2.6.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410"