Skip to content
This repository has been archived by the owner on Oct 18, 2024. It is now read-only.

Commit

Permalink
feat: abstract logic of stores inside base classes
Browse files Browse the repository at this point in the history
  • Loading branch information
Raspincel committed Mar 6, 2024
1 parent f9ac938 commit b4ecd39
Show file tree
Hide file tree
Showing 44 changed files with 495 additions and 181 deletions.
22 changes: 22 additions & 0 deletions src/common/types/stores.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useGlobalStore } from '../../services/stores';
import { PublicSubject } from '../../services/stores/common/types';

export enum StoreType {
GLOBAL = 'global-store',
COMMENTS = 'comments-store',
WHO_IS_ONLINE = 'who-is-online-store',
}

type StoreApi<T extends (...args: any[]) => any> = {
[K in keyof ReturnType<T>]: {
subscribe(callback?: (value: keyof T) => void): void;
subject: PublicSubject<keyof T>;
publish<T>(value: T): void;
};
};

// When creating new Stores, expand the ternary with the new Store. For example:
// ...T extends StoreType.GLOBAL ? StoreApi<typeof useGlobalStore> : T extends StoreType.WHO_IS_ONLINE ? StoreApi<typeof useWhoIsOnlineStore> : never;
// Yes, it will be a little bit verbose, but it's not like we'll be creating more and more Stores just for. Rarely will someone need to come here
export type Store<T> = T extends StoreType.GLOBAL ? StoreApi<typeof useGlobalStore> : never;
export type StoresTypes = typeof StoreType;
2 changes: 1 addition & 1 deletion src/common/utils/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class Observer {
'superviz-sdk:observer-helper:publish:error',
`
Failed to execute callback on publish value.
Callback: ${callback}
Callback: ${callback.name}
Event: ${JSON.stringify(event)}
Error: ${error}
`,
Expand Down
13 changes: 13 additions & 0 deletions src/common/utils/use-store.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { StoreType } from '../types/stores.types';

import { useStore } from './use-store';

describe('useStore', () => {
test('should return an api to use a store', () => {
const result = useStore.call(this, StoreType.GLOBAL);

expect(result).toHaveProperty('subscribe');
expect(result).toHaveProperty('subject');
expect(result).toHaveProperty('publish');
});
});
56 changes: 56 additions & 0 deletions src/common/utils/use-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { PublicSubject } from '../../services/stores/common/types';
import { useGlobalStore } from '../../services/stores/global';
import { Store, StoreType, StoresTypes } from '../types/stores.types';

const stores = {
[StoreType.GLOBAL]: useGlobalStore,
};

/**
* @function subscribe
* @description Subscribes to a subject and either update the value of the property each time there is a change, or call the callback that provides a custom behavior to the subscription
* @param name The name of the property to be updated in case there isn't a callback
* @param subject The subject to be subscribed
* @param callback The callback to be called each time there is a change
*/
function subscribeTo<T>(
name: string,
subject: PublicSubject<T>,
callback?: (value: T) => void,
): void {
subject.subscribe(this, () => {
this[name] = subject.value;

if (callback) {
callback(subject.value);
}
});

this.unsubscribeFrom.push(subject.unsubscribe);
}

/**
* @function useGlobalStore
* @description Returns a proxy of the global store data and a subscribe function to be used in the components
*/
export function useStore<T extends StoreType>(name: T): Store<T> {
// @TODO - Improve types to get better sugestions when writing code
const storeData = stores[name as StoreType]();
const bindedSubscribeTo = subscribeTo.bind(this);

const proxy = new Proxy(storeData, {
get(store: Store<T>, valueName: string) {
return {
subscribe<K extends Store<T>>(callback?: (value: K) => void) {
bindedSubscribeTo(valueName, store[valueName], callback);
},
subject: store[valueName] as typeof storeData,
publish(newValue: keyof Store<T>) {
this.subject.value = newValue;
},
};
},
});

return proxy;
}
22 changes: 7 additions & 15 deletions src/components/base/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { EVENT_BUS_MOCK } from '../../../__mocks__/event-bus.mock';
import { MOCK_OBSERVER_HELPER } from '../../../__mocks__/observer-helper.mock';
import { MOCK_GROUP, MOCK_LOCAL_PARTICIPANT } from '../../../__mocks__/participants.mock';
import { ABLY_REALTIME_MOCK } from '../../../__mocks__/realtime.mock';
import { Group, Participant } from '../../common/types/participant.types';
import { Group } from '../../common/types/participant.types';
import { Logger } from '../../common/utils';
import { Configuration } from '../../services/config/types';
import { EventBus } from '../../services/event-bus';
import { AblyRealtimeService } from '../../services/realtime';
import { useGlobalStore } from '../../services/stores';
import { ComponentNames } from '../types';

import { BaseComponent } from '.';
Expand Down Expand Up @@ -48,6 +49,10 @@ describe('BaseComponent', () => {
console.error = jest.fn();

jest.clearAllMocks();
const { localParticipant, group } = useGlobalStore();
localParticipant.value = MOCK_LOCAL_PARTICIPANT;
group.value = MOCK_GROUP;

DummyComponentInstance = new DummyComponent();
});

Expand All @@ -59,8 +64,6 @@ describe('BaseComponent', () => {
test('should not call start if realtime is not joined room', () => {
DummyComponentInstance.attach({
realtime: ABLY_REALTIME_MOCK,
localParticipant: MOCK_LOCAL_PARTICIPANT,
group: MOCK_GROUP,
config: MOCK_CONFIG,
eventBus: EVENT_BUS_MOCK,
});
Expand All @@ -83,8 +86,6 @@ describe('BaseComponent', () => {

DummyComponentInstance.attach({
realtime: ablyMock as AblyRealtimeService,
localParticipant: MOCK_LOCAL_PARTICIPANT,
group: MOCK_GROUP,
config: MOCK_CONFIG,
eventBus: EVENT_BUS_MOCK,
});
Expand All @@ -101,27 +102,22 @@ describe('BaseComponent', () => {
expect(DummyComponentInstance.attach).toBeDefined();

DummyComponentInstance.attach({
localParticipant: MOCK_LOCAL_PARTICIPANT,
realtime: REALTIME_MOCK,
group: MOCK_GROUP,
config: MOCK_CONFIG,
eventBus: EVENT_BUS_MOCK,
});

expect(DummyComponentInstance['localParticipant']).toEqual(MOCK_LOCAL_PARTICIPANT);
expect(DummyComponentInstance['realtime']).toEqual(REALTIME_MOCK);
expect(DummyComponentInstance['isAttached']).toBeTruthy();
expect(DummyComponentInstance['start']).toBeCalled();
});

test('should throw error if realtime or localParticipant are not provided', () => {
test('should throw error if realtime is not provided', () => {
expect(DummyComponentInstance.attach).toBeDefined();

expect(() => {
DummyComponentInstance.attach({
localParticipant: null as unknown as Participant,
realtime: null as unknown as AblyRealtimeService,
group: null as unknown as Group,
config: null as unknown as Configuration,
eventBus: null as unknown as EventBus,
});
Expand All @@ -135,9 +131,7 @@ describe('BaseComponent', () => {
expect(DummyComponentInstance.detach).toBeDefined();

DummyComponentInstance.attach({
localParticipant: MOCK_LOCAL_PARTICIPANT,
realtime: REALTIME_MOCK,
group: MOCK_GROUP,
config: MOCK_CONFIG,
eventBus: EVENT_BUS_MOCK,
});
Expand All @@ -156,9 +150,7 @@ describe('BaseComponent', () => {
DummyComponentInstance['destroy'] = jest.fn();

DummyComponentInstance.attach({
localParticipant: MOCK_LOCAL_PARTICIPANT,
realtime: REALTIME_MOCK,
group: MOCK_GROUP,
config: MOCK_CONFIG,
eventBus: EVENT_BUS_MOCK,
});
Expand Down
13 changes: 6 additions & 7 deletions src/components/base/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ComponentLifeCycleEvent } from '../../common/types/events.types';
import { Group, Participant } from '../../common/types/participant.types';
import { Group } from '../../common/types/participant.types';
import { StoreType } from '../../common/types/stores.types';
import { Logger, Observable } from '../../common/utils';
import { useStore } from '../../common/utils/use-store';
import config from '../../services/config';
import { EventBus } from '../../services/event-bus';
import { AblyRealtimeService } from '../../services/realtime';
Expand All @@ -11,12 +13,12 @@ import { DefaultAttachComponentOptions } from './types';
export abstract class BaseComponent extends Observable {
public abstract name: ComponentNames;
protected abstract logger: Logger;
protected localParticipant: Participant;
protected group: Group;
protected realtime: AblyRealtimeService;
protected eventBus: EventBus;

protected isAttached = false;
protected unsubscribeFrom: Array<(id: unknown) => void> = [];
protected useStore = useStore.bind(this) as typeof useStore;

/**
* @function attach
Expand All @@ -32,7 +34,7 @@ export abstract class BaseComponent extends Observable {
throw new Error(message);
}

const { realtime, localParticipant, group, config: globalConfig, eventBus } = params;
const { realtime, config: globalConfig, eventBus } = params;

if (!realtime.isDomainWhitelisted) {
const message = `Component ${this.name} can't be used because this website's domain is not whitelisted. Please add your domain in https://dashboard.superviz.com/developer`;
Expand All @@ -43,8 +45,6 @@ export abstract class BaseComponent extends Observable {

config.setConfig(globalConfig);
this.realtime = realtime;
this.localParticipant = localParticipant;
this.group = group;
this.eventBus = eventBus;
this.isAttached = true;

Expand Down Expand Up @@ -87,7 +87,6 @@ export abstract class BaseComponent extends Observable {

this.observers = undefined;
this.realtime = undefined;
this.localParticipant = undefined;
this.isAttached = false;
};

Expand Down
9 changes: 7 additions & 2 deletions src/components/base/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import { Group, Participant } from '../../common/types/participant.types';
import { Configuration } from '../../services/config/types';
import { EventBus } from '../../services/event-bus';
import { AblyRealtimeService } from '../../services/realtime';
import { useGlobalStore } from '../../services/stores';

export interface DefaultAttachComponentOptions {
realtime: AblyRealtimeService;
localParticipant: Participant;
group: Group;
config: Configuration;
eventBus: EventBus;
}

export type GlobalStore = {
[K in keyof ReturnType<typeof useGlobalStore>]: {
subscribe(callback?: (value: unknown) => void): void;
};
};
9 changes: 2 additions & 7 deletions src/components/comments/canvas-pin-adapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Logger, Observer } from '../../../common/utils';
import { PinMode } from '../../../web-components/comments/components/types';
import { Annotation, PinAdapter, PinCoordinates } from '../types';

import { CanvasSides, SimpleParticipant } from './types';
import { CanvasSides } from './types';

export class CanvasPin implements PinAdapter {
private logger: Logger;
Expand All @@ -22,7 +22,6 @@ export class CanvasPin implements PinAdapter {
private temporaryPinCoordinates: { x: number; y: number } | null = null;
private commentsSide: 'left' | 'right' = 'left';
private movedTemporaryPin: boolean;
private localParticipant: SimpleParticipant = {};
private originalCanvasCursor: string;
declare participants: ParticipantByGroupApi[];

Expand Down Expand Up @@ -164,9 +163,7 @@ export class CanvasPin implements PinAdapter {
temporaryPin.setAttribute('commentsSide', this.commentsSide);
temporaryPin.setAttribute('position', JSON.stringify(this.temporaryPinCoordinates));
temporaryPin.setAttribute('annotation', JSON.stringify({}));
temporaryPin.setAttribute('localAvatar', this.localParticipant.avatar ?? '');
temporaryPin.setAttribute('participantsList', JSON.stringify(this.participants));
temporaryPin.setAttribute('localName', this.localParticipant.name ?? '');
temporaryPin.setAttributeNode(document.createAttribute('active'));
this.divWrapper.appendChild(temporaryPin);
}
Expand Down Expand Up @@ -201,10 +198,8 @@ export class CanvasPin implements PinAdapter {
document.body.addEventListener('click', this.hideTemporaryPin);
}

public setCommentsMetadata = (side: 'left' | 'right', avatar: string, name: string): void => {
public setCommentsMetadata = (side: 'left' | 'right'): void => {
this.commentsSide = side;
this.localParticipant.avatar = avatar;
this.localParticipant.name = name;
};

/**
Expand Down
5 changes: 0 additions & 5 deletions src/components/comments/canvas-pin-adapter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,3 @@ export interface CanvasSides {
right: number;
bottom: number;
}

export interface SimpleParticipant {
name?: string;
avatar?: string;
}
12 changes: 0 additions & 12 deletions src/components/comments/html-pin-adapter/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,18 +487,6 @@ describe('HTMLPinAdapter', () => {
});
});

describe('setCommentsMetadata', () => {
test('should store updated data about comments and local participant', () => {
instance.setCommentsMetadata('right', 'user-avatar', 'user name');

expect(instance['commentsSide']).toEqual('right');
expect(instance['localParticipant']).toEqual({
avatar: 'user-avatar',
name: 'user name',
});
});
});

describe('resetPins', () => {
test('should remove active on Escape key', () => {
instance.updateAnnotations([MOCK_ANNOTATION_HTML]);
Expand Down
15 changes: 2 additions & 13 deletions src/components/comments/html-pin-adapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ import { Logger, Observer } from '../../../common/utils';
import { PinMode } from '../../../web-components/comments/components/types';
import { Annotation, PinAdapter, PinCoordinates } from '../types';

import {
HorizontalSide,
Simple2DPoint,
SimpleParticipant,
TemporaryPinData,
HTMLPinOptions,
} from './types';
import { HorizontalSide, Simple2DPoint, TemporaryPinData, HTMLPinOptions } from './types';

export class HTMLPin implements PinAdapter {
// Public properties
Expand All @@ -21,7 +15,6 @@ export class HTMLPin implements PinAdapter {
// Private properties
// Comments data
private annotations: Annotation[];
private localParticipant: SimpleParticipant = {};

declare participants: ParticipantByGroupApi[];

Expand Down Expand Up @@ -339,10 +332,8 @@ export class HTMLPin implements PinAdapter {
* @param {string} name the name of the local participant
* @returns {void}
*/
public setCommentsMetadata = (side: HorizontalSide, avatar: string, name: string): void => {
public setCommentsMetadata = (side: HorizontalSide): void => {
this.commentsSide = side;
this.localParticipant.avatar = avatar;
this.localParticipant.name = name;
};

/**
Expand Down Expand Up @@ -415,8 +406,6 @@ export class HTMLPin implements PinAdapter {
temporaryPin.setAttribute('commentsSide', this.commentsSide);
temporaryPin.setAttribute('position', JSON.stringify(this.temporaryPinCoordinates));
temporaryPin.setAttribute('annotation', JSON.stringify({}));
temporaryPin.setAttribute('localAvatar', this.localParticipant.avatar ?? '');
temporaryPin.setAttribute('localName', this.localParticipant.name ?? '');
temporaryPin.setAttribute('participantsList', JSON.stringify(this.participants));

temporaryPin.setAttribute('keepPositionRatio', '');
Expand Down
5 changes: 0 additions & 5 deletions src/components/comments/html-pin-adapter/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
export interface SimpleParticipant {
name?: string;
avatar?: string;
}

export interface Simple2DPoint {
x: number;
y: number;
Expand Down
Loading

0 comments on commit b4ecd39

Please sign in to comment.