From b363b4b54d953c0a87867c80501b6f3012b51d43 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sun, 8 Sep 2024 18:29:27 -0300 Subject: [PATCH 1/2] feat(web-components): only import and create components when it's in the right environment --- src/components/comments/index.ts | 2 +- src/components/who-is-online/index.ts | 2 +- src/web-components/comments/comments.test.ts | 114 ------------ src/web-components/comments/comments.ts | 164 ----------------- .../components/annotation-filter.test.ts | 2 +- .../comments/components/annotation-filter.ts | 4 +- .../components/annotation-item.test.ts | 2 +- .../comments/components/annotation-item.ts | 5 +- .../components/annotation-pin.test.ts | 2 +- .../comments/components/annotation-pin.ts | 5 +- .../components/annotation-resolved.test.ts | 4 +- .../components/annotation-resolved.ts | 4 +- .../comments/components/comment-input.test.ts | 2 +- .../comments/components/comment-input.ts | 5 +- .../comments/components/comment-item.test.ts | 2 +- .../comments/components/comment-item.ts | 5 +- .../comments/components/content.test.ts | 2 +- .../comments/components/content.ts | 5 +- .../components/delete-comment-modal.test.ts | 13 +- .../components/delete-comment-modal.ts | 4 +- .../comments/components/float-button.test.ts | 4 +- .../comments/components/float-button.ts | 5 +- .../comments/components/index.test.ts | 40 ----- .../comments/components/index.ts | 11 -- .../comments/components/mention-list.test.ts | 24 +-- .../comments/components/mention-list.ts | 5 +- .../comments/components/topbar.test.ts | 2 +- .../comments/components/topbar.ts | 4 +- src/web-components/comments/index.test.ts | 138 +++++++++++---- src/web-components/comments/index.ts | 167 +++++++++++++++++- src/web-components/dropdown/index.ts | 5 +- .../decorators/create-element.decorator.ts | 35 ++++ src/web-components/hello-world/index.ts | 4 +- src/web-components/icon/index.ts | 4 +- src/web-components/index.ts | 30 +++- src/web-components/modal/modal-container.ts | 4 +- src/web-components/modal/modal.test.ts | 27 +-- src/web-components/modal/modal.ts | 4 +- src/web-components/tooltip/index.ts | 5 +- .../who-is-online/components/dropdown.ts | 5 +- .../who-is-online/components/messages.ts | 5 +- .../who-is-online/who-is-online.ts | 5 +- 42 files changed, 432 insertions(+), 449 deletions(-) delete mode 100644 src/web-components/comments/comments.test.ts delete mode 100644 src/web-components/comments/comments.ts delete mode 100644 src/web-components/comments/components/index.test.ts delete mode 100644 src/web-components/comments/components/index.ts create mode 100644 src/web-components/global/decorators/create-element.decorator.ts diff --git a/src/components/comments/index.ts b/src/components/comments/index.ts index d0bd4368..d9084c33 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'; diff --git a/src/components/who-is-online/index.ts b/src/components/who-is-online/index.ts index ad66c0ed..c8f36d4d 100644 --- a/src/components/who-is-online/index.ts +++ b/src/components/who-is-online/index.ts @@ -5,7 +5,7 @@ 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/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; From 51b3476ddab7f15833ce1ef360c2562bf7134c79 Mon Sep 17 00:00:00 2001 From: Carlos Date: Sun, 8 Sep 2024 18:52:18 -0300 Subject: [PATCH 2/2] feat(services & components): validate if the window object exists before use it --- src/components/comments/index.test.ts | 2 +- src/components/comments/index.ts | 1 + src/components/presence-mouse/html/index.ts | 2 ++ src/core/launcher/index.ts | 10 +++++++--- src/index.ts | 2 +- src/services/connection-status/index.ts | 12 ++++++++---- src/services/message-bridge/index.ts | 4 +++- src/services/video-conference-manager/index.ts | 15 +++++++++++---- 8 files changed, 34 insertions(+), 14 deletions(-) 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 d9084c33..24ef857d 100644 --- a/src/components/comments/index.ts +++ b/src/components/comments/index.ts @@ -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/presence-mouse/html/index.ts b/src/components/presence-mouse/html/index.ts index 4bffb602..e4d930c8 100644 --- a/src/components/presence-mouse/html/index.ts +++ b/src/components/presence-mouse/html/index.ts @@ -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/core/launcher/index.ts b/src/core/launcher/index.ts index 176d5bee..8087837e 100644 --- a/src/core/launcher/index.ts +++ b/src/core/launcher/index.ts @@ -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 6612ded5..a06d9136 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,7 +58,7 @@ import type { } from './components/comments/types'; import type { Transform } from './components/presence-mouse/types'; -if (window) { +if (typeof window !== 'undefined') { window.SuperVizRoom = { init, CommentEvent, 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/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/video-conference-manager/index.ts b/src/services/video-conference-manager/index.ts index 2eb5daf6..4221af30 100644 --- a/src/services/video-conference-manager/index.ts +++ b/src/services/video-conference-manager/index.ts @@ -161,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 { @@ -372,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, @@ -661,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); + } } /**