From 26faceb29e7f24b9f500629df772f4fbc66f3f1d Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sun, 28 Jun 2020 17:06:39 +0200 Subject: [PATCH 01/32] feat(#773): use stencil/store to handle dark mode switcher --- studio/package-lock.json | 6 ++ studio/package.json | 1 + studio/src/app/app-root.tsx | 15 ----- .../app-navigation-actions.tsx | 34 +++++------ .../pages/core/app-settings/app-settings.tsx | 57 ++++++++----------- .../src/app/services/theme/theme.service.tsx | 10 +--- studio/src/app/stores/theme.store.ts | 16 ++++++ 7 files changed, 62 insertions(+), 77 deletions(-) create mode 100644 studio/src/app/stores/theme.store.ts diff --git a/studio/package-lock.json b/studio/package-lock.json index ad3e45575..5744d87c5 100644 --- a/studio/package-lock.json +++ b/studio/package-lock.json @@ -682,6 +682,12 @@ "integrity": "sha512-w6rkOsRIPY1rBa/13Wf+rMZrOzc6z86/Mkp3inzaYGsxBmLkf4PeP1rfaUB4SFDVRfMduP7FTd4ZJi/+FVrsMw==", "dev": true }, + "@stencil/store": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@stencil/store/-/store-1.3.0.tgz", + "integrity": "sha512-e1/Ru/q8P5BkqUYMF+kW54rFWyH9XRABLcxFLruUlbw+ZIGN5OwKe6Rf1vw1wQSa/6vMy0EQq5IrkRYNhENpOA==", + "dev": true + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", diff --git a/studio/package.json b/studio/package.json index 294008ffb..cb0eeff1c 100644 --- a/studio/package.json +++ b/studio/package.json @@ -57,6 +57,7 @@ "@stencil/core": "^1.15.0", "@stencil/postcss": "^1.0.1", "@stencil/sass": "^1.3.2", + "@stencil/store": "^1.3.0", "@types/socket.io-client": "^1.4.33", "@types/uuid": "^8.0.0", "autoprefixer": "^9.8.4", diff --git a/studio/src/app/app-root.tsx b/studio/src/app/app-root.tsx index d1817d371..88a0023ab 100644 --- a/studio/src/app/app-root.tsx +++ b/studio/src/app/app-root.tsx @@ -27,13 +27,10 @@ export class AppRoot { private navSubscription: Subscription; private navService: NavService; - private themeSubscription: Subscription; private themeService: ThemeService; private offlineService: OfflineService; - private domBodyClassList: DOMTokenList = document.body.classList; - @State() private loading: boolean = true; @@ -63,10 +60,6 @@ export class AppRoot { this.navSubscription = this.navService.watch().subscribe(async (params: NavParams) => { await this.navigate(params); }); - - this.themeSubscription = this.themeService.watch().subscribe((dark: boolean) => { - this.updateDarkModePreferences(dark); - }); } async componentDidUnload() { @@ -77,10 +70,6 @@ export class AppRoot { if (this.navSubscription) { this.navSubscription.unsubscribe(); } - - if (this.themeSubscription) { - this.themeSubscription.unsubscribe(); - } } private async toastError(error: string) { @@ -100,10 +89,6 @@ export class AppRoot { await popover.present(); } - private updateDarkModePreferences(dark: boolean) { - dark ? this.domBodyClassList.add('dark') : this.domBodyClassList.remove('dark'); - } - private async navigate(params: NavParams) { if (!params) { return; diff --git a/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx b/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx index 3dfe02e8d..7d494c4e9 100644 --- a/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx +++ b/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx @@ -4,6 +4,8 @@ import {popoverController} from '@ionic/core'; import {Subscription} from 'rxjs'; +import state from '../../../stores/theme.store'; + import {AuthUser} from '../../../models/auth/auth.user'; import {User} from '../../../models/data/user'; @@ -12,12 +14,11 @@ import {Utils} from '../../../utils/core/utils'; import {AuthService} from '../../../services/auth/auth.service'; import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; import {UserService} from '../../../services/data/user/user.service'; -import {ThemeService} from '../../../services/theme/theme.service'; @Component({ tag: 'app-navigation-actions', styleUrl: 'app-navigation-actions.scss', - shadow: false + shadow: false, }) export class AppNavigationActions { @Prop() signIn: boolean = true; @@ -32,9 +33,6 @@ export class AppNavigationActions { private userService: UserService; private userSubscription: Subscription; - private themeService: ThemeService; - private themeSubscription: Subscription; - @State() private authUser: AuthUser; @@ -44,16 +42,12 @@ export class AppNavigationActions { @State() private photoUrlLoaded: boolean = false; - @State() - private darkMode: boolean; - @Event() private actionPublish: EventEmitter; constructor() { this.authService = AuthService.getInstance(); this.navService = NavService.getInstance(); this.userService = UserService.getInstance(); - this.themeService = ThemeService.getInstance(); } componentWillLoad() { @@ -65,10 +59,6 @@ export class AppNavigationActions { this.photoUrl = user && user.data ? user.data.photo_url : undefined; this.photoUrlLoaded = true; }); - - this.themeSubscription = this.themeService.watch().subscribe((dark: boolean) => { - this.darkMode = dark; - }); } componentDidUnload() { @@ -79,17 +69,13 @@ export class AppNavigationActions { if (this.userSubscription) { this.userSubscription.unsubscribe(); } - - if (this.themeSubscription) { - this.themeSubscription.unsubscribe(); - } } private async openMenu($event: UIEvent) { const popover: HTMLIonPopoverElement = await popoverController.create({ component: 'app-user-menu', event: $event, - mode: 'ios' + mode: 'ios', }); await popover.present(); @@ -98,7 +84,7 @@ export class AppNavigationActions { private async navigateSignIn() { this.navService.navigate({ url: '/signin' + (window && window.location ? window.location.pathname : ''), - direction: NavDirection.FORWARD + direction: NavDirection.FORWARD, }); } @@ -153,7 +139,13 @@ export class AppNavigationActions { private renderPresentationButton() { if (this.presentation) { return ( - + Write a presentation ); @@ -165,7 +157,7 @@ export class AppNavigationActions { private renderPublishButton() { if (this.publish) { return ( - this.actionPublish.emit()} mode="md" color={this.darkMode ? 'light' : 'dark'}> + this.actionPublish.emit()} mode="md" color={state.darkTheme ? 'light' : 'dark'}> Ready to share? ); diff --git a/studio/src/app/pages/core/app-settings/app-settings.tsx b/studio/src/app/pages/core/app-settings/app-settings.tsx index 9ef9366c8..2bead7fed 100644 --- a/studio/src/app/pages/core/app-settings/app-settings.tsx +++ b/studio/src/app/pages/core/app-settings/app-settings.tsx @@ -6,6 +6,8 @@ import {filter, take} from 'rxjs/operators'; import firebase from '@firebase/app'; import '@firebase/auth'; +import state from '../../../stores/theme.store'; + import {ApiUser} from '../../../models/api/api.user'; import {AuthUser} from '../../../models/auth/auth.user'; import {User} from '../../../models/data/user'; @@ -24,7 +26,7 @@ import {ThemeService} from '../../../services/theme/theme.service'; @Component({ tag: 'app-settings', - styleUrl: 'app-settings.scss' + styleUrl: 'app-settings.scss', }) export class AppHome { @Element() el: HTMLElement; @@ -85,9 +87,6 @@ export class AppHome { @State() private custom: string = undefined; - @State() - private darkTheme: boolean; - constructor() { this.authService = AuthService.getInstance(); this.apiUserService = ApiUserFactoryService.getInstance(); @@ -133,13 +132,6 @@ export class AppHome { await this.initSocial(); }); - - this.themeService - .watch() - .pipe(take(1)) - .subscribe((dark: boolean) => { - this.darkTheme = dark; - }); } private initSocial(): Promise { @@ -161,7 +153,7 @@ export class AppHome { private async signIn() { this.navService.navigate({ url: '/signin' + (window && window.location ? window.location.pathname : ''), - direction: NavDirection.FORWARD + direction: NavDirection.FORWARD, }); } @@ -332,8 +324,8 @@ export class AppHome { const modal: HTMLIonModalElement = await modalController.create({ component: 'app-user-delete', componentProps: { - username: this.apiUser.username - } + username: this.apiUser.username, + }, }); modal.onDidDismiss().then(async (detail: OverlayEventDetail) => { @@ -370,7 +362,7 @@ export class AppHome { this.navService.navigate({ url: '/', - direction: NavDirection.ROOT + direction: NavDirection.ROOT, }); await loading.dismiss(); @@ -407,7 +399,7 @@ export class AppHome { {this.renderDarkLightToggle()} {this.renderGuardedContent()} - + , ]; } @@ -426,7 +418,7 @@ export class AppHome { Sign in to access your profile and settings. -

+

, ]; } @@ -448,7 +440,7 @@ export class AppHome { {this.renderSubmitForm()} , -

Note that your update has no effect on the presentations you would have already published.

+

Note that your update has no effect on the presentations you would have already published.

, ]; } @@ -468,7 +460,7 @@ export class AppHome { disabled={this.saving} onIonInput={($event: CustomEvent) => this.handleNameInput($event)} onIonChange={() => this.validateNameInput()}> - + , ]; } @@ -497,13 +489,12 @@ export class AppHome { checked={this.user && this.user.data ? this.user.data.newsletter : false} disabled={this.saving} onIonChange={($event: CustomEvent) => this.toggleNewsletter($event)}> - + , ]; } async toggleTheme() { - this.darkTheme = !this.darkTheme; - await this.themeService.switch(this.darkTheme); + await this.themeService.switch(!state.darkTheme); } private renderDarkLightToggle() { @@ -512,11 +503,11 @@ export class AppHome { - {this.darkTheme ? 'Dark' : 'Light'} theme {this.darkTheme ? '🌑' : '☀️'} + {state.darkTheme ? 'Dark' : 'Light'} theme {state.darkTheme ? '🌑' : '☀️'} - this.toggleTheme()}> + this.toggleTheme()}> - + , ]; } @@ -536,7 +527,7 @@ export class AppHome { input-mode="text" onIonInput={($event: CustomEvent) => this.handleUsernameInput($event)} onIonChange={() => this.validateUsernameInput()}> - + , ]; } @@ -559,7 +550,7 @@ export class AppHome { onClick={() => this.presentConfirmDelete()} disabled={this.saving || !this.apiUser || !this.authUser}> Delete my user -
+
, ]; } @@ -639,7 +630,7 @@ export class AppHome { input-mode="text" disabled={this.saving} onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'twitter')}> - + , ]; } @@ -661,7 +652,7 @@ export class AppHome { input-mode="text" disabled={this.saving} onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'linkedin')}> - + , ]; } @@ -683,7 +674,7 @@ export class AppHome { input-mode="text" disabled={this.saving} onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'dev')}> - + , ]; } @@ -705,7 +696,7 @@ export class AppHome { input-mode="text" disabled={this.saving} onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'medium')}> - + , ]; } @@ -727,7 +718,7 @@ export class AppHome { input-mode="text" disabled={this.saving} onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'github')}> - + , ]; } @@ -749,7 +740,7 @@ export class AppHome { input-mode="text" disabled={this.saving} onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'custom')}> - + , ]; } } diff --git a/studio/src/app/services/theme/theme.service.tsx b/studio/src/app/services/theme/theme.service.tsx index 6130d2966..5ce465a59 100644 --- a/studio/src/app/services/theme/theme.service.tsx +++ b/studio/src/app/services/theme/theme.service.tsx @@ -1,12 +1,10 @@ -import {Observable, ReplaySubject} from 'rxjs'; +import state from '../../stores/theme.store'; import {get, set} from 'idb-keyval'; export class ThemeService { private static instance: ThemeService; - private darkTheme: ReplaySubject = new ReplaySubject(1); - private constructor() { // Private constructor, singleton } @@ -18,12 +16,8 @@ export class ThemeService { return ThemeService.instance; } - watch(): Observable { - return this.darkTheme.asObservable(); - } - async switch(dark: boolean) { - this.darkTheme.next(dark); + state.darkTheme = dark; try { await set('deckdeckgo_dark_mode', dark); diff --git a/studio/src/app/stores/theme.store.ts b/studio/src/app/stores/theme.store.ts new file mode 100644 index 000000000..35999a30f --- /dev/null +++ b/studio/src/app/stores/theme.store.ts @@ -0,0 +1,16 @@ +import {createStore} from '@stencil/store'; + +interface DarkThemeStore { + darkTheme: boolean | undefined; +} + +const {state, onChange} = createStore({ + darkTheme: undefined, +} as DarkThemeStore); + +onChange('darkTheme', (dark: boolean | undefined) => { + const domBodyClassList: DOMTokenList = document.body.classList; + dark ? domBodyClassList.add('dark') : domBodyClassList.remove('dark'); +}); + +export default state; From a80bbe12bc50182011f4cf4bdc1fe986a22ddd2a Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sun, 28 Jun 2020 20:15:10 +0200 Subject: [PATCH 02/32] feat(#773): use stencil/store to handle dark mode switcher --- .../core/app-navigation/app-navigation.tsx | 24 +-- .../app-action-share/app-action-share.tsx | 39 +--- .../app-share-options/app-share-options.tsx | 38 +--- .../app-publish-edit/app-publish-edit.tsx | 65 +++--- .../events/deck/deck-events.handler.tsx | 185 ++++++++---------- .../events/remote/remote-events.handler.tsx | 5 + .../src/app/helpers/editor/editor.helper.tsx | 29 +-- .../app/pages/core/app-signin/app-signin.tsx | 47 ++--- .../pages/editor/app-editor/app-editor.tsx | 42 ++-- .../app-create-slide/app-create-slide.tsx | 14 +- .../app-edit-slide-qrcode.tsx | 31 +-- .../editor/deck/deck-editor.service.tsx | 28 --- .../editor/offline/offline.service.tsx | 128 +++++------- .../editor/publish/publish.service.tsx | 76 ++++--- .../services/editor/remote/remote.service.tsx | 35 ++-- .../services/editor/share/share.service.tsx | 66 +++---- studio/src/app/stores/deck.store.ts | 22 +++ 17 files changed, 336 insertions(+), 538 deletions(-) delete mode 100644 studio/src/app/services/editor/deck/deck-editor.service.tsx create mode 100644 studio/src/app/stores/deck.store.ts diff --git a/studio/src/app/components/core/app-navigation/app-navigation.tsx b/studio/src/app/components/core/app-navigation/app-navigation.tsx index 2e0723afc..e7b58311b 100644 --- a/studio/src/app/components/core/app-navigation/app-navigation.tsx +++ b/studio/src/app/components/core/app-navigation/app-navigation.tsx @@ -2,15 +2,14 @@ import {Component, Prop, h, State} from '@stencil/core'; import {Subscription} from 'rxjs'; -import {Deck} from '../../../models/data/deck'; +import store from '../../../stores/deck.store'; -import {DeckEditorService} from '../../../services/editor/deck/deck-editor.service'; import {OfflineService} from '../../../services/editor/offline/offline.service'; @Component({ tag: 'app-navigation', styleUrl: 'app-navigation.scss', - shadow: false + shadow: false, }) export class AppNavigation { @Prop() menuToggle: boolean = true; @@ -20,38 +19,23 @@ export class AppNavigation { @Prop() presentation: boolean = false; @Prop() publish: boolean = false; - private subscription: Subscription; - private deckEditorService: DeckEditorService; - private offlineSubscription: Subscription; private offlineService: OfflineService; - @State() - private deckName: string = null; - @State() private offline: OfflineDeck = undefined; constructor() { - this.deckEditorService = DeckEditorService.getInstance(); this.offlineService = OfflineService.getInstance(); } componentWillLoad() { - this.subscription = this.deckEditorService.watch().subscribe((deck: Deck) => { - this.deckName = deck && deck.data && deck.data.name && deck.data.name !== '' ? deck.data.name : null; - }); - this.offlineSubscription = this.offlineService.watchOffline().subscribe((offline: OfflineDeck | undefined) => { this.offline = offline; }); } componentDidUnload() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - if (this.offlineSubscription) { this.offlineSubscription.unsubscribe(); } @@ -76,7 +60,7 @@ export class AppNavigation { return undefined; } - const titleClass = this.deckName && this.deckName !== '' ? 'title deck-name-visible' : 'title'; + const titleClass = store.state.name && store.state.name !== '' ? 'title deck-name-visible' : 'title'; return (
@@ -84,7 +68,7 @@ export class AppNavigation { {this.renderLogo()} - {this.deckName} + {store.state.name}
); } diff --git a/studio/src/app/components/editor/actions/deck/app-action-share/app-action-share.tsx b/studio/src/app/components/editor/actions/deck/app-action-share/app-action-share.tsx index 5a73ee1cd..024ac1381 100644 --- a/studio/src/app/components/editor/actions/deck/app-action-share/app-action-share.tsx +++ b/studio/src/app/components/editor/actions/deck/app-action-share/app-action-share.tsx @@ -1,49 +1,28 @@ import {Component, Element, Event, EventEmitter, h} from '@stencil/core'; import {OverlayEventDetail, popoverController} from '@ionic/core'; -import {take} from 'rxjs/operators'; - -import {Deck} from '../../../../../models/data/deck'; +import store from '../../../../../stores/deck.store'; import {MoreAction} from '../../../../../utils/editor/more-action'; -import {DeckEditorService} from '../../../../../services/editor/deck/deck-editor.service'; - @Component({ - tag: 'app-action-share' + tag: 'app-action-share', }) export class AppActionShare { @Element() el: HTMLElement; - private deckEditorService: DeckEditorService; - @Event() private actionPublish: EventEmitter; @Event() private openShare: EventEmitter; @Event() private openEmbed: EventEmitter; - constructor() { - this.deckEditorService = DeckEditorService.getInstance(); - } - - private share($event: UIEvent): Promise { - return new Promise((resolve) => { - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - const deckPublished: boolean = deck && deck.data && deck.data.meta && deck.data.meta.published; - - if (deckPublished) { - await this.openShareOptions($event); - } else { - this.actionPublish.emit(); - } - - resolve(); - }); - }); + private async share($event: UIEvent) { + if (store.state.published) { + await this.openShareOptions($event); + } else { + this.actionPublish.emit(); + } } async openShareOptions($event: UIEvent) { @@ -54,7 +33,7 @@ export class AppActionShare { const popover: HTMLIonPopoverElement = await popoverController.create({ component: 'app-more-share-options', event: $event, - mode: 'ios' + mode: 'ios', }); popover.onDidDismiss().then(async (detail: OverlayEventDetail) => { diff --git a/studio/src/app/components/editor/app-share-options/app-share-options.tsx b/studio/src/app/components/editor/app-share-options/app-share-options.tsx index e771c5150..3c5eaf6d3 100644 --- a/studio/src/app/components/editor/app-share-options/app-share-options.tsx +++ b/studio/src/app/components/editor/app-share-options/app-share-options.tsx @@ -1,43 +1,17 @@ -import {Component, Event, EventEmitter, h, Host, State} from '@stencil/core'; +import {Component, Event, EventEmitter, h, Host} from '@stencil/core'; -import {Subscription} from 'rxjs'; - -import {Deck} from '../../../models/data/deck'; +import store from '../../../stores/deck.store'; import {MoreAction} from '../../../utils/editor/more-action'; -import {DeckEditorService} from '../../../services/editor/deck/deck-editor.service'; - @Component({ tag: 'app-share-options', styleUrl: 'app-share-options.scss', - shadow: true + shadow: true, }) export class AppMoreShareOptions { @Event() selectedOption: EventEmitter; - @State() - private published: boolean = false; - - private deckEditorService: DeckEditorService; - private subscription: Subscription; - - constructor() { - this.deckEditorService = DeckEditorService.getInstance(); - } - - componentWillLoad() { - this.subscription = this.deckEditorService.watch().subscribe(async (deck: Deck) => { - this.published = deck && deck.data && deck.data.meta && deck.data.meta.published; - }); - } - - componentDidUnload() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - } - render() { return ( @@ -49,7 +23,7 @@ export class AppMoreShareOptions { } private renderUpdate() { - if (this.published) { + if (store.state.published) { return ( this.selectedOption.emit(MoreAction.PUBLISH)}>

Publish to update share

@@ -61,7 +35,7 @@ export class AppMoreShareOptions { } private renderEmbed() { - if (this.published) { + if (store.state.published) { return (
this.selectedOption.emit(MoreAction.EMBED)}>

Embed

@@ -73,7 +47,7 @@ export class AppMoreShareOptions { } private renderShareLink() { - if (this.published) { + if (store.state.published) { return (
this.selectedOption.emit(MoreAction.SHARE)}>

Share link

diff --git a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx index b65a4cee1..fb4747f8d 100644 --- a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx +++ b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx @@ -3,11 +3,12 @@ import {Component, Event, EventEmitter, h, State} from '@stencil/core'; import {Subject, Subscription} from 'rxjs'; import {debounceTime, filter, take} from 'rxjs/operators'; +import store from '../../../../stores/deck.store'; + import {Deck} from '../../../../models/data/deck'; import {Resources} from '../../../../utils/core/resources'; -import {DeckEditorService} from '../../../../services/editor/deck/deck-editor.service'; import {ErrorService} from '../../../../services/core/error/error.service'; import {DeckService} from '../../../../services/data/deck/deck.service'; import {ApiUser} from '../../../../models/api/api.user'; @@ -22,7 +23,7 @@ interface CustomInputEvent extends KeyboardEvent { @Component({ tag: 'app-publish-edit', - styleUrl: 'app-publish-edit.scss' + styleUrl: 'app-publish-edit.scss', }) export class AppPublishEdit { @State() @@ -46,7 +47,6 @@ export class AppPublishEdit { @State() private tags: string[] = []; - private deckEditorService: DeckEditorService; private deckService: DeckService; private errorService: ErrorService; @@ -69,7 +69,6 @@ export class AppPublishEdit { private progressSubscription: Subscription; constructor() { - this.deckEditorService = DeckEditorService.getInstance(); this.deckService = DeckService.getInstance(); this.errorService = ErrorService.getInstance(); @@ -82,12 +81,7 @@ export class AppPublishEdit { } async componentWillLoad() { - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - await this.init(deck); - }); + await this.init(); this.progressSubscription = this.publishService.watchProgress().subscribe((progress: number) => { this.progress = progress; @@ -115,19 +109,17 @@ export class AppPublishEdit { this.validateCaptionInput(); } - private init(deck: Deck): Promise { - return new Promise(async (resolve) => { - if (!deck || !deck.data) { - resolve(); - return; - } - - this.caption = deck.data.name; - this.description = deck.data.meta && deck.data.meta.description ? (deck.data.meta.description as string) : await this.getFirstSlideContent(); - this.tags = deck.data.meta && deck.data.meta.tags ? (deck.data.meta.tags as string[]) : []; + private async init() { + if (!store.state.deck || !store.state.deck.data) { + return; + } - resolve(); - }); + this.caption = store.state.deck.data.name; + this.description = + store.state.deck.data.meta && store.state.deck.data.meta.description + ? (store.state.deck.data.meta.description as string) + : await this.getFirstSlideContent(); + this.tags = store.state.deck.data.meta && store.state.deck.data.meta.tags ? (store.state.deck.data.meta.tags as string[]) : []; } componentDidUnload() { @@ -173,23 +165,18 @@ export class AppPublishEdit { this.disablePublish = true; try { - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - if (!deck || !deck.data || !deck.id) { - this.disablePublish = false; - resolve(); - return; - } - - deck.data.name = this.caption; - - const updatedDeck: Deck = await this.deckService.update(deck); - this.deckEditorService.next(updatedDeck); - - this.disablePublish = false; - }); + if (!store.state.deck || !store.state.deck.data || !store.state.deck.id) { + this.disablePublish = false; + resolve(); + return; + } + + store.state.deck.data.name = this.caption; + + const updatedDeck: Deck = await this.deckService.update(store.state.deck); + store.state.deck = {...updatedDeck}; + + this.disablePublish = false; } catch (err) { this.disablePublish = false; this.errorService.error(err); diff --git a/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx b/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx index 3322716de..70d8ed2ce 100644 --- a/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx +++ b/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx @@ -3,6 +3,8 @@ import {ItemReorderEventDetail} from '@ionic/core'; import {Subject, Subscription} from 'rxjs'; import {debounceTime, filter, take} from 'rxjs/operators'; +import store from '../../../../stores/deck.store'; + import {cleanContent} from '@deckdeckgo/deck-utils'; import * as firebase from 'firebase/app'; @@ -19,7 +21,6 @@ import {SlotUtils} from '../../../../utils/editor/slot.utils'; import {ErrorService} from '../../../../services/core/error/error.service'; import {BusyService} from '../../../../services/editor/busy/busy.service'; -import {DeckEditorService} from '../../../../services/editor/deck/deck-editor.service'; import {AuthService} from '../../../../services/auth/auth.service'; import {DeckService} from '../../../../services/data/deck/deck.service'; import {SlideService} from '../../../../services/data/slide/slide.service'; @@ -35,8 +36,6 @@ export class DeckEventsHandler { private updateSlideSubscription: Subscription; private updateSlideSubject: Subject = new Subject(); - private deckEditorService: DeckEditorService; - private updateDeckTitleSubscription: Subscription; private updateDeckTitleSubject: Subject = new Subject(); @@ -49,8 +48,6 @@ export class DeckEventsHandler { this.authService = AuthService.getInstance(); - this.deckEditorService = DeckEditorService.getInstance(); - this.deckService = DeckService.getInstance(); this.slideService = SlideService.getInstance(); } @@ -211,24 +208,20 @@ export class DeckEventsHandler { this.busyService.deckBusy(true); - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - if (!deck) { - deck = await this.createDeck(); - } + if (!store.state.deck) { + const persistedDeck: Deck = await this.createDeck(); + store.state.deck = {...persistedDeck}; + } - const persistedSlide: Slide = await this.postSlide(deck, slide); + const persistedSlide: Slide = await this.postSlide(store.state.deck, slide); - // Because of the offline mode, is kind of handy to handle the list on the client side too. - // But maybe in the future it is something which could be moved to the cloud. - await this.updateDeckSlideList(deck, persistedSlide); + // Because of the offline mode, is kind of handy to handle the list on the client side too. + // But maybe in the future it is something which could be moved to the cloud. + await this.updateDeckSlideList(store.state.deck, persistedSlide); - this.busyService.deckBusy(false); + this.busyService.deckBusy(false); - resolve(); - }); + resolve(); } catch (err) { this.errorService.error(err); this.busyService.deckBusy(false); @@ -289,7 +282,7 @@ export class DeckEventsHandler { } const persistedDeck: Deck = await this.deckService.create(deck); - this.deckEditorService.next(persistedDeck); + store.state.deck = {...persistedDeck}; await this.updateNavigation(persistedDeck); @@ -321,7 +314,7 @@ export class DeckEventsHandler { deck.data.slides.push(slide.id); const updatedDeck: Deck = await this.deckService.update(deck); - this.deckEditorService.next(updatedDeck); + store.state.deck = {...updatedDeck}; resolve(); } catch (err) { @@ -353,31 +346,28 @@ export class DeckEventsHandler { this.busyService.deckBusy(true); - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (currentDeck: Deck) => { - if (!currentDeck || !currentDeck.data) { - resolve(); - return; - } + const currentDeck: Deck | null = store.state.deck; + + if (!currentDeck || !currentDeck.data) { + resolve(); + return; + } - const attributes: DeckAttributes = await this.getDeckAttributes(deck, true); - // @ts-ignore - currentDeck.data.attributes = attributes && Object.keys(attributes).length > 0 ? attributes : firebase.firestore.FieldValue.delete(); + const attributes: DeckAttributes = await this.getDeckAttributes(deck, true); + // @ts-ignore + currentDeck.data.attributes = attributes && Object.keys(attributes).length > 0 ? attributes : firebase.firestore.FieldValue.delete(); - const background: string = await this.getDeckBackground(deck); - // @ts-ignore - currentDeck.data.background = background && background !== undefined && background !== '' ? background : firebase.firestore.FieldValue.delete(); + const background: string = await this.getDeckBackground(deck); + // @ts-ignore + currentDeck.data.background = background && background !== undefined && background !== '' ? background : firebase.firestore.FieldValue.delete(); - const updatedDeck: Deck = await this.deckService.update(currentDeck); + const updatedDeck: Deck = await this.deckService.update(currentDeck); - this.deckEditorService.next(updatedDeck); + store.state.deck = {...updatedDeck}; - this.busyService.deckBusy(false); + this.busyService.deckBusy(false); - resolve(); - }); + resolve(); } catch (err) { this.errorService.error(err); this.busyService.deckBusy(false); @@ -396,31 +386,27 @@ export class DeckEventsHandler { this.busyService.deckBusy(true); - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (currentDeck: Deck) => { - if (!currentDeck || !currentDeck.data) { - resolve(); - return; - } + const currentDeck: Deck | null = store.state.deck; - // TODO: Add a check, we should not update the title from the slide in case it would have been set in the publication + if (!currentDeck || !currentDeck.data) { + resolve(); + return; + } - if (title.length >= Resources.Constants.DECK.TITLE_MAX_LENGTH) { - title = title.substr(0, Resources.Constants.DECK.TITLE_MAX_LENGTH); - } + // TODO: Add a check, we should not update the title from the slide in case it would have been set in the publication - currentDeck.data.name = title; + if (title.length >= Resources.Constants.DECK.TITLE_MAX_LENGTH) { + title = title.substr(0, Resources.Constants.DECK.TITLE_MAX_LENGTH); + } - const updatedDeck: Deck = await this.deckService.update(currentDeck); + currentDeck.data.name = title; - this.deckEditorService.next(updatedDeck); + const updatedDeck: Deck = await this.deckService.update(currentDeck); + store.state.deck = {...updatedDeck}; - this.busyService.deckBusy(false); + this.busyService.deckBusy(false); - resolve(); - }); + resolve(); } catch (err) { this.errorService.error(err); this.busyService.deckBusy(false); @@ -464,18 +450,13 @@ export class DeckEventsHandler { slideUpdate.data.attributes = attributes; } - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - if (deck) { - await this.slideService.update(deck.id, slideUpdate); - } + if (store.state.deck) { + await this.slideService.update(store.state.deck.id, slideUpdate); + } - this.busyService.deckBusy(false); + this.busyService.deckBusy(false); - resolve(); - }); + resolve(); } catch (err) { this.errorService.error(err); this.busyService.deckBusy(false); @@ -500,36 +481,33 @@ export class DeckEventsHandler { const slideId: string = slide.getAttribute('slide_id'); - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - if (deck && deck.data) { - const slide: Slide = await this.slideService.get(deck.id, slideId); - - if (slide && slide.data) { - // Because there is an offline mode, it is handy currently to proceed the following on the client side. - // But at some point, it might be interesting to move the logic to a cloud function. - - // 1. Delete the slide in Firestore or locally - await this.slideService.delete(deck.id, slideId); - - // 2. Update list of slide in the deck (in Firestore) - if (deck.data.slides && deck.data.slides.indexOf(slideId) > -1) { - deck.data.slides.splice(deck.data.slides.indexOf(slideId), 1); - - const updatedDeck: Deck = await this.deckService.update(deck); - this.deckEditorService.next(updatedDeck); - } - } + const currentDeck: Deck | null = store.state.deck; + + if (currentDeck && currentDeck.data) { + const slide: Slide = await this.slideService.get(currentDeck.id, slideId); + + if (slide && slide.data) { + // Because there is an offline mode, it is handy currently to proceed the following on the client side. + // But at some point, it might be interesting to move the logic to a cloud function. + + // 1. Delete the slide in Firestore or locally + await this.slideService.delete(currentDeck.id, slideId); + + // 2. Update list of slide in the deck (in Firestore) + if (currentDeck.data.slides && currentDeck.data.slides.indexOf(slideId) > -1) { + currentDeck.data.slides.splice(currentDeck.data.slides.indexOf(slideId), 1); + + const updatedDeck: Deck = await this.deckService.update(currentDeck); + store.state.deck = {...updatedDeck}; } + } + } - await this.deleteSlideElement(); + await this.deleteSlideElement(); - this.busyService.deckBusy(false); + this.busyService.deckBusy(false); - resolve(); - }); + resolve(); } catch (err) { this.errorService.error(err); this.busyService.deckBusy(false); @@ -909,19 +887,16 @@ export class DeckEventsHandler { return; } - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - if (deck && deck.data && deck.data.slides && detail.to < deck.data.slides.length) { - deck.data.slides.splice(detail.to, 0, ...deck.data.slides.splice(detail.from, 1)); + const currentDeck: Deck | null = store.state.deck; - const updatedDeck: Deck = await this.deckService.update(deck); - this.deckEditorService.next(updatedDeck); - } + if (currentDeck && currentDeck.data && currentDeck.data.slides && detail.to < currentDeck.data.slides.length) { + currentDeck.data.slides.splice(detail.to, 0, ...currentDeck.data.slides.splice(detail.from, 1)); - resolve(); - }); + const updatedDeck: Deck = await this.deckService.update(currentDeck); + store.state.deck = {...updatedDeck}; + } + + resolve(); } catch (err) { reject(err); } diff --git a/studio/src/app/handlers/editor/events/remote/remote-events.handler.tsx b/studio/src/app/handlers/editor/events/remote/remote-events.handler.tsx index 0464704ef..c5bfb10ba 100644 --- a/studio/src/app/handlers/editor/events/remote/remote-events.handler.tsx +++ b/studio/src/app/handlers/editor/events/remote/remote-events.handler.tsx @@ -418,6 +418,11 @@ export class RemoteEventsHandler { const room: string = await this.remoteService.getRoom(); + if (!room) { + resolve(); + return; + } + deckgoRemoteElement.room = room; await deckgoRemoteElement.connect(); diff --git a/studio/src/app/helpers/editor/editor.helper.tsx b/studio/src/app/helpers/editor/editor.helper.tsx index f0ac4ece3..65e06c352 100644 --- a/studio/src/app/helpers/editor/editor.helper.tsx +++ b/studio/src/app/helpers/editor/editor.helper.tsx @@ -1,5 +1,6 @@ import {JSX} from '@stencil/core'; -import {take} from 'rxjs/operators'; + +import store from '../../stores/deck.store'; import {Slide} from '../../models/data/slide'; import {Deck} from '../../models/data/deck'; @@ -8,7 +9,6 @@ import {ParseSlidesUtils} from '../../utils/editor/parse-slides.utils'; import {ErrorService} from '../../services/core/error/error.service'; import {BusyService} from '../../services/editor/busy/busy.service'; -import {DeckEditorService} from '../../services/editor/deck/deck-editor.service'; import {DeckService} from '../../services/data/deck/deck.service'; import {SlideService} from '../../services/data/slide/slide.service'; @@ -16,8 +16,6 @@ export class EditorHelper { private errorService: ErrorService; private busyService: BusyService; - private deckEditorService: DeckEditorService; - private slideService: SlideService; private deckService: DeckService; @@ -27,8 +25,6 @@ export class EditorHelper { this.errorService = ErrorService.getInstance(); this.busyService = BusyService.getInstance(); - this.deckEditorService = DeckEditorService.getInstance(); - this.deckService = DeckService.getInstance(); } @@ -51,7 +47,7 @@ export class EditorHelper { return; } - this.deckEditorService.next(deck); + store.state.deck = {...deck}; if (!deck.data.slides || deck.data.slides.length <= 0) { resolve([]); @@ -114,21 +110,16 @@ export class EditorHelper { const slideId: string = slide.getAttribute('slide_id'); - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - let element: JSX.IntrinsicElements = null; + let element: JSX.IntrinsicElements = null; - if (deck && deck.data) { - const slide: Slide = await this.slideService.get(deck.id, slideId); - element = await ParseSlidesUtils.parseSlide(deck, slide, true, true); - } + if (store.state.deck && store.state.deck.data) { + const slide: Slide = await this.slideService.get(store.state.deck.id, slideId); + element = await ParseSlidesUtils.parseSlide(store.state.deck, slide, true, true); + } - this.busyService.deckBusy(false); + this.busyService.deckBusy(false); - resolve(element); - }); + resolve(element); } catch (err) { this.errorService.error(err); this.busyService.deckBusy(false); diff --git a/studio/src/app/pages/core/app-signin/app-signin.tsx b/studio/src/app/pages/core/app-signin/app-signin.tsx index 53d1e79a5..ffe380357 100644 --- a/studio/src/app/pages/core/app-signin/app-signin.tsx +++ b/studio/src/app/pages/core/app-signin/app-signin.tsx @@ -3,13 +3,13 @@ import {Component, Element, Prop, State, Watch, h} from '@stencil/core'; import firebase from '@firebase/app'; import '@firebase/auth'; -import {forkJoin} from 'rxjs'; import {filter, take} from 'rxjs/operators'; +import store from '../../../stores/deck.store'; + import {del, get, set} from 'idb-keyval'; import {AuthUser} from '../../../models/auth/auth.user'; -import {Deck} from '../../../models/data/deck'; import {Utils} from '../../../utils/core/utils'; import {EnvironmentDeckDeckGoConfig} from '../../../services/core/environment/environment-config'; @@ -17,13 +17,12 @@ import {EnvironmentDeckDeckGoConfig} from '../../../services/core/environment/en import {EnvironmentConfigService} from '../../../services/core/environment/environment-config.service'; import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; import {AuthService} from '../../../services/auth/auth.service'; -import {DeckEditorService} from '../../../services/editor/deck/deck-editor.service'; import {UserService} from '../../../services/data/user/user.service'; import {DeckService} from '../../../services/data/deck/deck.service'; @Component({ tag: 'app-signin', - styleUrl: 'app-signin.scss' + styleUrl: 'app-signin.scss', }) export class AppSignIn { @Element() el: HTMLElement; @@ -45,14 +44,11 @@ export class AppSignIn { private firebaseUser: firebase.User; - private deckEditorService: DeckEditorService; - constructor() { this.navService = NavService.getInstance(); this.deckService = DeckService.getInstance(); this.userService = UserService.getInstance(); this.authService = AuthService.getInstance(); - this.deckEditorService = DeckEditorService.getInstance(); } async componentDidLoad() { @@ -116,8 +112,8 @@ export class AppSignIn { }, // signInFailure callback must be provided to handle merge conflicts which // occur when an existing credential is linked to an anonymous user. - signInFailure: this.onSignInFailure - } + signInFailure: this.onSignInFailure, + }, }; // @ts-ignore @@ -201,20 +197,19 @@ export class AppSignIn { await set('deckdeckgo_redirect', this.redirect ? this.redirect : '/'); - const observables = []; - observables.push(this.authService.watch().pipe(take(1))); - observables.push(this.deckEditorService.watch().pipe(take(1))); + this.authService + .watch() + .pipe(take(1)) + .subscribe(async (authUser: AuthUser) => { + await set('deckdeckgo_redirect_info', { + deckId: store.state.deck ? store.state.deck.id : null, + userId: authUser ? authUser.uid : null, + userToken: authUser ? authUser.token : null, + anonymous: authUser ? authUser.anonymous : true, + }); - forkJoin(observables).subscribe(async ([authUser, deck]: [AuthUser, Deck]) => { - await set('deckdeckgo_redirect_info', { - deckId: deck ? deck.id : null, - userId: authUser ? authUser.uid : null, - userToken: authUser ? authUser.token : null, - anonymous: authUser ? authUser.anonymous : true + resolve(); }); - - resolve(); - }); }); } @@ -236,13 +231,13 @@ export class AppSignIn { // Do not push a new page but reload as we might later face a DOM with contains two firebaseui which would not work this.navService.navigate({ url: url, - direction: NavDirection.ROOT + direction: NavDirection.ROOT, }); } async navigateBack() { this.navService.navigate({ - direction: NavDirection.BACK + direction: NavDirection.BACK, }); } @@ -261,7 +256,7 @@ export class AppSignIn { DeckDeckGo is free and open source 🖖

- + , ]; } @@ -272,12 +267,12 @@ export class AppSignIn {

Sign in to unleash all features of the editor like adding more slides, uploading and using your own images, using the author template or being able to share your presentation as an app. -

+

, ]; } else { return [

Oh, hi! Welcome back.

, -

Sign in to unleash all features of the editor and to be able to share your presentation as an app.

+

Sign in to unleash all features of the editor and to be able to share your presentation as an app.

, ]; } } diff --git a/studio/src/app/pages/editor/app-editor/app-editor.tsx b/studio/src/app/pages/editor/app-editor/app-editor.tsx index 2f240379e..eb5ae0c10 100644 --- a/studio/src/app/pages/editor/app-editor/app-editor.tsx +++ b/studio/src/app/pages/editor/app-editor/app-editor.tsx @@ -5,6 +5,8 @@ import {ItemReorderEventDetail, modalController, OverlayEventDetail} from '@ioni import {Subscription} from 'rxjs'; import {filter, take} from 'rxjs/operators'; +import store from '../../../stores/deck.store'; + import {debounce, isFullscreen, isIOS, isMobile} from '@deckdeckgo/utils'; import {convertStyle} from '@deckdeckgo/deck-utils'; @@ -13,7 +15,6 @@ import {generateRandomStyleColors} from '../../../utils/editor/random-palette'; import {AuthUser} from '../../../models/auth/auth.user'; import {SlideTemplate} from '../../../models/data/slide'; -import {Deck} from '../../../models/data/deck'; import {CreateSlidesUtils} from '../../../utils/editor/create-slides.utils'; import {ParseBackgroundUtils} from '../../../utils/editor/parse-background.utils'; @@ -34,7 +35,6 @@ import {SlotUtils} from '../../../utils/editor/slot.utils'; import {AuthService} from '../../../services/auth/auth.service'; import {AnonymousService} from '../../../services/editor/anonymous/anonymous.service'; import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; -import {DeckEditorService} from '../../../services/editor/deck/deck-editor.service'; import {BusyService} from '../../../services/editor/busy/busy.service'; import {EnvironmentGoogleConfig} from '../../../services/core/environment/environment-config'; @@ -82,8 +82,6 @@ export class AppEditor { private anonymousService: AnonymousService; private navService: NavService; - private deckEditorService: DeckEditorService; - private busySubscription: Subscription; private busyService: BusyService; @@ -110,7 +108,6 @@ export class AppEditor { this.authService = AuthService.getInstance(); this.anonymousService = AnonymousService.getInstance(); this.navService = NavService.getInstance(); - this.deckEditorService = DeckEditorService.getInstance(); this.busyService = BusyService.getInstance(); this.offlineService = OfflineService.getInstance(); this.fontsService = FontsService.getInstance(); @@ -193,7 +190,7 @@ export class AppEditor { this.busySubscription.unsubscribe(); } - this.deckEditorService.next(null); + store.reset(); } async componentDidLoad() { @@ -276,30 +273,21 @@ export class AppEditor { this.style = await generateRandomStyleColors(); } - private initDeckStyle(): Promise { - return new Promise((resolve) => { - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - if (deck && deck.data && deck.data.attributes && deck.data.attributes.style) { - this.style = await convertStyle(deck.data.attributes.style); - } else { - this.style = undefined; - } - - if (deck && deck.data && deck.data.attributes && deck.data.attributes.transition) { - this.transition = deck.data.attributes.transition; - } + private async initDeckStyle() { + if (store.state.deck && store.state.deck.data && store.state.deck.data.attributes && store.state.deck.data.attributes.style) { + this.style = await convertStyle(store.state.deck.data.attributes.style); + } else { + this.style = undefined; + } - this.background = await ParseBackgroundUtils.convertBackground(deck.data.background, true); + if (store.state.deck && store.state.deck.data && store.state.deck.data.attributes && store.state.deck.data.attributes.transition) { + this.transition = store.state.deck.data.attributes.transition; + } - const google: EnvironmentGoogleConfig = EnvironmentConfigService.getInstance().get('google'); - await this.fontsService.loadGoogleFont(google.fontsUrl, this.style); + this.background = await ParseBackgroundUtils.convertBackground(store.state.deck.data.background, true); - resolve(); - }); - }); + const google: EnvironmentGoogleConfig = EnvironmentConfigService.getInstance().get('google'); + await this.fontsService.loadGoogleFont(google.fontsUrl, this.style); } private concatSlide(extraSlide: JSX.IntrinsicElements): Promise { diff --git a/studio/src/app/popovers/editor/app-create-slide/app-create-slide.tsx b/studio/src/app/popovers/editor/app-create-slide/app-create-slide.tsx index 335a031e2..8ee67268a 100644 --- a/studio/src/app/popovers/editor/app-create-slide/app-create-slide.tsx +++ b/studio/src/app/popovers/editor/app-create-slide/app-create-slide.tsx @@ -3,6 +3,8 @@ import {Component, Element, Event, EventEmitter, h, JSX, State} from '@stencil/c import {interval, Subscription} from 'rxjs'; import {take} from 'rxjs/operators'; +import store from '../../../stores/deck.store'; + import {SlideAttributes, SlideChartType, SlideSplitType, SlideTemplate} from '../../../models/data/slide'; import {User} from '../../../models/data/user'; @@ -13,7 +15,6 @@ import {SlotType} from '../../../utils/editor/slot-type'; import {UserService} from '../../../services/data/user/user.service'; import {AnonymousService} from '../../../services/editor/anonymous/anonymous.service'; -import {DeckEditorService} from '../../../services/editor/deck/deck-editor.service'; import {AssetsService} from '../../../services/core/assets/assets.service'; import {EnvironmentConfigService} from '../../../services/core/environment/environment-config.service'; @@ -53,7 +54,6 @@ export class AppCreateSlide { private userService: UserService; private anonymousService: AnonymousService; - private deckEditorService: DeckEditorService; @Event() signIn: EventEmitter; @@ -64,7 +64,6 @@ export class AppCreateSlide { constructor() { this.userService = UserService.getInstance(); this.anonymousService = AnonymousService.getInstance(); - this.deckEditorService = DeckEditorService.getInstance(); } async componentWillLoad() { @@ -168,13 +167,8 @@ export class AppCreateSlide { await this.closePopover(template, slide); } - private addSlideQRCode() { - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - await this.addSlide(SlideTemplate.QRCODE, deck); - }); + private async addSlideQRCode() { + await this.addSlide(SlideTemplate.QRCODE, store.state.deck); } // We need the data in the user account (like twitter, profile image etc.) to generate the author slide diff --git a/studio/src/app/popovers/editor/slide/app-edit-slide-qrcode/app-edit-slide-qrcode.tsx b/studio/src/app/popovers/editor/slide/app-edit-slide-qrcode/app-edit-slide-qrcode.tsx index 4b4f2647d..81802fdda 100644 --- a/studio/src/app/popovers/editor/slide/app-edit-slide-qrcode/app-edit-slide-qrcode.tsx +++ b/studio/src/app/popovers/editor/slide/app-edit-slide-qrcode/app-edit-slide-qrcode.tsx @@ -2,17 +2,13 @@ import {Component, Element, EventEmitter, h, Prop, State, Event} from '@stencil/ import {alertController} from '@ionic/core'; -import {take} from 'rxjs/operators'; - -import {Deck} from '../../../../models/data/deck'; +import store from '../../../../stores/deck.store'; import {QRCodeUtils} from '../../../../utils/editor/qrcode.utils'; import {EditAction} from '../../../../utils/editor/edit-action'; -import {DeckEditorService} from '../../../../services/editor/deck/deck-editor.service'; - @Component({ - tag: 'app-edit-slide-qrcode' + tag: 'app-edit-slide-qrcode', }) export class AppEditSlideQRCode { @Element() el: HTMLElement; @@ -32,12 +28,6 @@ export class AppEditSlideQRCode { @Event() private action: EventEmitter; - private deckEditorService: DeckEditorService; - - constructor() { - this.deckEditorService = DeckEditorService.getInstance(); - } - async componentWillLoad() { this.customQRCode = this.selectedElement && this.selectedElement.hasAttribute('custom-qrcode'); this.customContent = this.customQRCode && this.selectedElement ? this.selectedElement.getAttribute('content') : undefined; @@ -118,19 +108,14 @@ export class AppEditSlideQRCode { return; } - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - this.selectedElement.setAttribute('content', QRCodeUtils.getPresentationUrl(deck)); - this.selectedElement.removeAttribute('custom-qrcode'); + this.selectedElement.setAttribute('content', QRCodeUtils.getPresentationUrl(store.state.deck)); + this.selectedElement.removeAttribute('custom-qrcode'); - this.customContent = undefined; + this.customContent = undefined; - this.slideDidChange.emit(this.selectedElement); + this.slideDidChange.emit(this.selectedElement); - resolve(); - }); + resolve(); }); } @@ -184,7 +169,7 @@ export class AppEditSlideQRCode { this.action.emit(EditAction.DELETE_LOGO)} fill="outline" class="delete"> Delete logo - + , ]; } } diff --git a/studio/src/app/services/editor/deck/deck-editor.service.tsx b/studio/src/app/services/editor/deck/deck-editor.service.tsx deleted file mode 100644 index e18d52eb1..000000000 --- a/studio/src/app/services/editor/deck/deck-editor.service.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import {BehaviorSubject, Observable} from 'rxjs'; - -import {Deck} from '../../../models/data/deck'; - -export class DeckEditorService { - private deckSubject: BehaviorSubject = new BehaviorSubject(null); - - private static instance: DeckEditorService; - - private constructor() { - // Private constructor, singleton - } - - static getInstance() { - if (!DeckEditorService.instance) { - DeckEditorService.instance = new DeckEditorService(); - } - return DeckEditorService.instance; - } - - watch(): Observable { - return this.deckSubject.asObservable(); - } - - next(deck: Deck) { - this.deckSubject.next(deck); - } -} diff --git a/studio/src/app/services/editor/offline/offline.service.tsx b/studio/src/app/services/editor/offline/offline.service.tsx index 32580244b..bc3366e0a 100644 --- a/studio/src/app/services/editor/offline/offline.service.tsx +++ b/studio/src/app/services/editor/offline/offline.service.tsx @@ -5,6 +5,8 @@ import {del, get, set} from 'idb-keyval'; import {BehaviorSubject, Observable} from 'rxjs'; import {take} from 'rxjs/operators'; +import store from '../../../stores/deck.store'; + import {Deck} from '../../../models/data/deck'; import {Slide} from '../../../models/data/slide'; @@ -13,7 +15,6 @@ import {SlotType} from '../../../utils/editor/slot-type'; import {OfflineUtils} from '../../../utils/editor/offline.utils'; import {ServiceWorkerUtils} from '../../../utils/core/service-worker-utils'; -import {DeckEditorService} from '../deck/deck-editor.service'; import {SlideOnlineService} from '../../data/slide/slide.online.service'; import {DeckOnlineService} from '../../data/deck/deck.online.service'; import {AssetsService} from '../../core/assets/assets.service'; @@ -26,8 +27,6 @@ import {FontsService} from '../fonts/fonts.service'; export class OfflineService { private static instance: OfflineService; - private deckEditorService: DeckEditorService; - private slideOnlineService: SlideOnlineService; private deckOnlineService: DeckOnlineService; private storageOnlineService: StorageOnlineService; @@ -40,8 +39,6 @@ export class OfflineService { private progressSubject: BehaviorSubject = new BehaviorSubject(0); private constructor() { - this.deckEditorService = DeckEditorService.getInstance(); - this.deckOnlineService = DeckOnlineService.getInstance(); this.slideOnlineService = SlideOnlineService.getInstance(); this.storageOnlineService = StorageOnlineService.getInstance(); @@ -144,34 +141,29 @@ export class OfflineService { } private toggleOffline(): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { try { - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - try { - if (!deck || !deck.id || !deck.data) { - reject('No deck found'); - return; - } - - const offline: OfflineDeck = { - id: deck.id, - name: deck.data.name, - }; - - await set('deckdeckgo_offline', offline); - - this.offlineSubject.next(offline); - - this.progressComplete(); - - resolve(); - } catch (err) { - reject(err); - } - }); + try { + if (!store.state.deck || !store.state.deck.id || !store.state.deck.data) { + reject('No deck found'); + return; + } + + const offline: OfflineDeck = { + id: store.state.deck.id, + name: store.state.deck.data.name, + }; + + await set('deckdeckgo_offline', offline); + + this.offlineSubject.next(offline); + + this.progressComplete(); + + resolve(); + } catch (err) { + reject(err); + } } catch (err) { reject(err); } @@ -375,33 +367,24 @@ export class OfflineService { } private saveDeck(): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { try { - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - try { - if (!deck || !deck.id || !deck.data) { - reject('No deck found'); - return; - } - - await this.saveSlides(deck); - - if (deck.data.background && OfflineUtils.shouldAttributeBeCleaned(deck.data.background)) { - deck.data.background = null; - } - - await set(`/decks/${deck.id}`, deck); - - this.progress(0.5); - - resolve(); - } catch (err) { - reject(err); - } - }); + if (!store.state.deck || !store.state.deck.id || !store.state.deck.data) { + reject('No deck found'); + return; + } + + await this.saveSlides(store.state.deck); + + if (store.state.deck.data.background && OfflineUtils.shouldAttributeBeCleaned(store.state.deck.data.background)) { + store.state.deck.data.background = null; + } + + await set(`/decks/${store.state.deck.id}`, store.state.deck); + + this.progress(0.5); + + resolve(); } catch (err) { reject(err); } @@ -456,31 +439,22 @@ export class OfflineService { } private uploadData(): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { try { - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - try { - if (!deck || !deck.id || !deck.data) { - reject('No deck found'); - return; - } + if (!store.state.deck || !store.state.deck.id || !store.state.deck.data) { + reject('No deck found'); + return; + } - await this.uploadSlides(deck); + await this.uploadSlides(store.state.deck); - await this.deleteSlides(deck); + await this.deleteSlides(store.state.deck); - const persistedDeck: Deck = await this.uploadDeck(deck); + const persistedDeck: Deck = await this.uploadDeck(store.state.deck); - this.deckEditorService.next(persistedDeck); + store.state.deck = {...persistedDeck}; - resolve(); - } catch (err) { - reject(err); - } - }); + resolve(); } catch (err) { reject(err); } diff --git a/studio/src/app/services/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index dd64a616f..0e1a2d2d0 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -4,6 +4,8 @@ import 'firebase/firestore'; import {Observable, Subject} from 'rxjs'; import {take} from 'rxjs/operators'; +import store from '../../../stores/deck.store'; + import {Deck, DeckMetaAuthor} from '../../../models/data/deck'; import {ApiDeck} from '../../../models/api/api.deck'; import {Slide, SlideAttributes, SlideTemplate} from '../../../models/data/slide'; @@ -15,7 +17,6 @@ import {ApiSlide} from '../../../models/api/api.slide'; import {DeckService} from '../../data/deck/deck.service'; import {SlideService} from '../../data/slide/slide.service'; import {UserService} from '../../data/user/user.service'; -import {DeckEditorService} from '../deck/deck-editor.service'; import {ApiPresentationService} from '../../api/presentation/api.presentation.service'; import {ApiPresentationFactoryService} from '../../api/presentation/api.presentation.factory.service'; @@ -27,8 +28,6 @@ import {FontsService} from '../fonts/fonts.service'; export class PublishService { private static instance: PublishService; - private deckEditorService: DeckEditorService; - private apiPresentationService: ApiPresentationService; private deckService: DeckService; @@ -41,9 +40,6 @@ export class PublishService { private progressSubject: Subject = new Subject(); private constructor() { - // Private constructor, singleton - this.deckEditorService = DeckEditorService.getInstance(); - this.apiPresentationService = ApiPresentationFactoryService.getInstance(); this.deckService = DeckService.getInstance(); @@ -75,55 +71,51 @@ export class PublishService { // TODO: Move in a cloud functions? publish(description: string, tags: string[]): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { this.progress(0); - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - try { - if (!deck || !deck.id || !deck.data) { - this.progressComplete(); - reject('No deck found'); - return; - } + try { + if (!store.state.deck || !store.state.deck.id || !store.state.deck.data) { + this.progressComplete(); + reject('No deck found'); + return; + } - const apiDeck: ApiDeck = await this.convertDeck(deck, description); + const apiDeck: ApiDeck = await this.convertDeck(store.state.deck, description); - this.progress(0.25); + this.progress(0.25); - const apiDeckPublish: ApiPresentation = await this.publishDeck(deck, apiDeck); + const apiDeckPublish: ApiPresentation = await this.publishDeck(store.state.deck, apiDeck); - this.progress(0.5); + this.progress(0.5); - if (!apiDeckPublish || !apiDeckPublish.id || !apiDeckPublish.url) { - this.progressComplete(); - reject('Publish failed'); - return; - } + if (!apiDeckPublish || !apiDeckPublish.id || !apiDeckPublish.url) { + this.progressComplete(); + reject('Publish failed'); + return; + } - this.progress(0.75); + this.progress(0.75); - const newApiId: boolean = deck.data.api_id !== apiDeckPublish.id; - if (newApiId) { - deck.data.api_id = apiDeckPublish.id; + const newApiId: boolean = store.state.deck.data.api_id !== apiDeckPublish.id; + if (newApiId) { + store.state.deck.data.api_id = apiDeckPublish.id; - deck = await this.deckService.update(deck); - } + const updatedDeck: Deck = await this.deckService.update(store.state.deck); + store.state.deck = {...updatedDeck}; + } - this.progress(0.8); + this.progress(0.8); - const publishedUrl: string = apiDeckPublish.url; + const publishedUrl: string = apiDeckPublish.url; - await this.delayUpdateMeta(deck, publishedUrl, description, tags, newApiId); + await this.delayUpdateMeta(store.state.deck, publishedUrl, description, tags, newApiId); - resolve(publishedUrl); - } catch (err) { - this.progressComplete(); - reject(err); - } - }); + resolve(publishedUrl); + } catch (err) { + this.progressComplete(); + reject(err); + } }); } @@ -376,7 +368,7 @@ export class PublishService { return new Promise(async (resolve, reject) => { try { const freshDeck: Deck = await this.deckService.get(deckId); - this.deckEditorService.next(freshDeck); + store.state.deck = {...freshDeck}; resolve(); } catch (err) { diff --git a/studio/src/app/services/editor/remote/remote.service.tsx b/studio/src/app/services/editor/remote/remote.service.tsx index 2a0f87282..7682970b3 100644 --- a/studio/src/app/services/editor/remote/remote.service.tsx +++ b/studio/src/app/services/editor/remote/remote.service.tsx @@ -1,7 +1,9 @@ import {Build} from '@stencil/core'; import {BehaviorSubject, Observable, Subject} from 'rxjs'; -import {filter, take} from 'rxjs/operators'; +import {take} from 'rxjs/operators'; + +import store from '../../../stores/deck.store'; import {get, set} from 'idb-keyval'; @@ -9,8 +11,6 @@ import {DeckdeckgoEventDeckRequest, ConnectionState} from '@deckdeckgo/types'; import {Deck} from '../../../models/data/deck'; -import {DeckEditorService} from '../deck/deck-editor.service'; - export class RemoteService { private remoteSubject: BehaviorSubject = new BehaviorSubject(false); @@ -22,13 +22,6 @@ export class RemoteService { private static instance: RemoteService; - private deckEditorService: DeckEditorService; - - private constructor() { - // Private constructor, singleton - this.deckEditorService = DeckEditorService.getInstance(); - } - static getInstance() { if (!RemoteService.instance) { RemoteService.instance = new RemoteService(); @@ -66,20 +59,14 @@ export class RemoteService { return this.remoteSubject.asObservable(); } - getRoom(): Promise { - return new Promise((resolve) => { - this.deckEditorService - .watch() - .pipe( - filter((deck: Deck) => deck && deck.data && deck.data.name && deck.data.name !== undefined && deck.data.name !== ''), - take(1) - ) - .subscribe(async (deck: Deck) => { - const roomName: string = deck.data.name.replace(/\.|#/g, '_'); - - resolve(roomName); - }); - }); + async getRoom(): Promise { + const deck: Deck | null = store.state.deck; + + if (deck && deck.data && deck.data.name && deck.data.name !== undefined && deck.data.name !== '') { + return deck.data.name.replace(/\.|#/g, '_'); + } + + return null; } async addPendingRequests(request: DeckdeckgoEventDeckRequest) { diff --git a/studio/src/app/services/editor/share/share.service.tsx b/studio/src/app/services/editor/share/share.service.tsx index b2abd1fee..75885e923 100644 --- a/studio/src/app/services/editor/share/share.service.tsx +++ b/studio/src/app/services/editor/share/share.service.tsx @@ -3,20 +3,18 @@ import {take} from 'rxjs/operators'; import {EnvironmentDeckDeckGoConfig} from '../../core/environment/environment-config'; import {EnvironmentConfigService} from '../../core/environment/environment-config.service'; -import {Deck} from '../../../models/data/deck'; +import store from '../../../stores/deck.store'; + import {AuthUser} from '../../../models/auth/auth.user'; -import {DeckEditorService} from '../deck/deck-editor.service'; import {AuthService} from '../../auth/auth.service'; export class ShareService { private static instance: ShareService; - private deckEditorService: DeckEditorService; private authService: AuthService; private constructor() { - this.deckEditorService = DeckEditorService.getInstance(); this.authService = AuthService.getInstance(); } @@ -29,43 +27,39 @@ export class ShareService { getPublishedUrl(): Promise { return new Promise((resolve) => { - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - if (deck && deck.data && deck.data.meta && deck.data.meta.pathname && deck.data.meta.pathname !== '') { - const config: EnvironmentDeckDeckGoConfig = EnvironmentConfigService.getInstance().get('deckdeckgo'); - resolve(config.presentationUrl + deck.data.meta.pathname); - } else { - // Should not happens - const deckDeckGoConfig: EnvironmentDeckDeckGoConfig = EnvironmentConfigService.getInstance().get('deckdeckgo'); - resolve(deckDeckGoConfig.appUrl); - } - }); + if ( + store.state.deck && + store.state.deck.data && + store.state.deck.data.meta && + store.state.deck.data.meta.pathname && + store.state.deck.data.meta.pathname !== '' + ) { + const config: EnvironmentDeckDeckGoConfig = EnvironmentConfigService.getInstance().get('deckdeckgo'); + resolve(config.presentationUrl + store.state.deck.data.meta.pathname); + } else { + // Should not happens + const deckDeckGoConfig: EnvironmentDeckDeckGoConfig = EnvironmentConfigService.getInstance().get('deckdeckgo'); + resolve(deckDeckGoConfig.appUrl); + } }); } getShareText(): Promise { return new Promise((resolve) => { - this.deckEditorService - .watch() - .pipe(take(1)) - .subscribe(async (deck: Deck) => { - if (deck && deck.data && deck.data.name && deck.data.name !== '') { - this.authService - .watch() - .pipe(take(1)) - .subscribe(async (authUser: AuthUser) => { - if (authUser && !authUser.anonymous && authUser.name && authUser.name !== '') { - resolve(`"${deck.data.name}" by ${authUser.name} created with DeckDeckGo`); - } else { - resolve(`"${deck.data.name}" created with DeckDeckGo`); - } - }); - } else { - resolve('A presentation created with DeckDeckGo'); - } - }); + if (store.state.deck && store.state.deck.data && store.state.deck.data.name && store.state.deck.data.name !== '') { + this.authService + .watch() + .pipe(take(1)) + .subscribe(async (authUser: AuthUser) => { + if (authUser && !authUser.anonymous && authUser.name && authUser.name !== '') { + resolve(`"${store.state.deck.data.name}" by ${authUser.name} created with DeckDeckGo`); + } else { + resolve(`"${store.state.deck.data.name}" created with DeckDeckGo`); + } + }); + } else { + resolve('A presentation created with DeckDeckGo'); + } }); } } diff --git a/studio/src/app/stores/deck.store.ts b/studio/src/app/stores/deck.store.ts new file mode 100644 index 000000000..d306a0a5e --- /dev/null +++ b/studio/src/app/stores/deck.store.ts @@ -0,0 +1,22 @@ +import {createStore} from '@stencil/store'; + +import {Deck} from '../models/data/deck'; + +interface DeckStore { + deck: Deck | null; + name: string | null; + published: boolean; +} + +const {state, onChange, reset} = createStore({ + deck: null, + name: null, + published: false, +} as DeckStore); + +onChange('deck', (deck: Deck | null) => { + state.name = deck && deck.data && deck.data.name && deck.data.name !== '' ? deck.data.name : null; + state.published = deck && deck.data && deck.data.meta && deck.data.meta.published ? deck.data.meta.published : false; +}); + +export default {state, reset}; From 2182c1e2adac6bfd310b3a093df3ef525beeedc8 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sun, 28 Jun 2020 20:47:50 +0200 Subject: [PATCH 03/32] feat(#773): use stencil/store to handle error --- studio/src/app/app-root.tsx | 17 +++--- .../app-dashboard-deck-actions.tsx | 17 +++--- .../offline/app-go-offline/app-go-offline.tsx | 9 ++-- .../offline/app-go-online/app-go-online.tsx | 11 ++-- .../app-publish-edit/app-publish-edit.tsx | 30 +++++------ .../events/deck/deck-events.handler.tsx | 52 +++++++++---------- .../src/app/helpers/editor/editor.helper.tsx | 16 +++--- .../src/app/pages/core/app-poll/app-poll.tsx | 17 +++--- .../pages/core/app-settings/app-settings.tsx | 9 ++-- .../api/photo/api.photo.prod.service.tsx | 6 ++- .../services/api/photo/api.photo.service.tsx | 9 ---- studio/src/app/services/auth/auth.service.tsx | 11 ++-- .../app/services/core/error/error.service.tsx | 26 ---------- .../app/services/data/feed/feed.service.tsx | 13 ++--- .../storage/storage.offline.service.tsx | 20 +++---- .../storage/storage.online.service.tsx | 15 +++--- .../app/services/tenor/gif/gif.service.tsx | 20 +++---- studio/src/app/stores/error.store.ts | 11 ++++ 18 files changed, 121 insertions(+), 188 deletions(-) delete mode 100644 studio/src/app/services/core/error/error.service.tsx create mode 100644 studio/src/app/stores/error.store.ts diff --git a/studio/src/app/app-root.tsx b/studio/src/app/app-root.tsx index 88a0023ab..144b3c51b 100644 --- a/studio/src/app/app-root.tsx +++ b/studio/src/app/app-root.tsx @@ -2,9 +2,10 @@ import {Build, Component, Element, h, Listen, State} from '@stencil/core'; import {toastController} from '@ionic/core'; +import store from './stores/error.store'; + import {Subscription} from 'rxjs'; -import {ErrorService} from './services/core/error/error.service'; import {AuthService} from './services/auth/auth.service'; import {NavDirection, NavParams, NavService} from './services/core/nav/nav.service'; @@ -19,9 +20,6 @@ import {OfflineService} from './services/editor/offline/offline.service'; export class AppRoot { @Element() el: HTMLElement; - private errorSubscription: Subscription; - private errorService: ErrorService; - private authService: AuthService; private navSubscription: Subscription; @@ -35,7 +33,6 @@ export class AppRoot { private loading: boolean = true; constructor() { - this.errorService = ErrorService.getInstance(); this.authService = AuthService.getInstance(); this.navService = NavService.getInstance(); this.themeService = ThemeService.getInstance(); @@ -53,8 +50,10 @@ export class AppRoot { async componentDidLoad() { this.loading = false; - this.errorSubscription = this.errorService.watch().subscribe(async (error: string) => { - await this.toastError(error); + store.onChange('error', (error: string | undefined) => { + if (error) { + this.toastError(error); + } }); this.navSubscription = this.navService.watch().subscribe(async (params: NavParams) => { @@ -63,10 +62,6 @@ export class AppRoot { } async componentDidUnload() { - if (this.errorSubscription) { - this.errorSubscription.unsubscribe(); - } - if (this.navSubscription) { this.navSubscription.unsubscribe(); } diff --git a/studio/src/app/components/dashboard/app-dashboard-deck-actions/app-dashboard-deck-actions.tsx b/studio/src/app/components/dashboard/app-dashboard-deck-actions/app-dashboard-deck-actions.tsx index 6e787672c..98d169302 100644 --- a/studio/src/app/components/dashboard/app-dashboard-deck-actions/app-dashboard-deck-actions.tsx +++ b/studio/src/app/components/dashboard/app-dashboard-deck-actions/app-dashboard-deck-actions.tsx @@ -1,16 +1,17 @@ import {Component, Event, EventEmitter, h, Prop, Host, State} from '@stencil/core'; import {loadingController, modalController, OverlayEventDetail} from '@ionic/core'; +import store from '../../../stores/error.store'; + import {Deck} from '../../../models/data/deck'; import {DeckService} from '../../../services/data/deck/deck.service'; import {DeckDashboardCloneResult, DeckDashboardService} from '../../../services/dashboard/deck/deck-dashboard.service'; -import {ErrorService} from '../../../services/core/error/error.service'; @Component({ tag: 'app-dashboard-deck-actions', styleUrl: 'app-dashboard-deck-actions.scss', - shadow: true + shadow: true, }) export class AppDashboardDeckActions { @Prop() deck: Deck; @@ -18,8 +19,6 @@ export class AppDashboardDeckActions { private deckService: DeckService; private deckDashboardService: DeckDashboardService; - private errorService: ErrorService; - @Event() deckDeleted: EventEmitter; @Event() deckCloned: EventEmitter; @@ -29,8 +28,6 @@ export class AppDashboardDeckActions { constructor() { this.deckService = DeckService.getInstance(); this.deckDashboardService = DeckDashboardService.getInstance(); - - this.errorService = ErrorService.getInstance(); } private async presentConfirmDelete($event: UIEvent) { @@ -54,8 +51,8 @@ export class AppDashboardDeckActions { component: 'app-deck-delete', componentProps: { deckName: this.deck.data.name, - published: this.deck.data.meta && this.deck.data.meta.published - } + published: this.deck.data.meta && this.deck.data.meta.published, + }, }); modal.onDidDismiss().then(async (detail: OverlayEventDetail) => { @@ -85,7 +82,7 @@ export class AppDashboardDeckActions { this.deckDeleted.emit(this.deck.id); } catch (err) { - this.errorService.error(err); + store.state.error = err; } await loading.dismiss(); @@ -133,7 +130,7 @@ export class AppDashboardDeckActions { this.deckCloned.emit(clone); } catch (err) { - this.errorService.error(err); + store.state.error = err; } await loading.dismiss(); diff --git a/studio/src/app/components/editor/offline/app-go-offline/app-go-offline.tsx b/studio/src/app/components/editor/offline/app-go-offline/app-go-offline.tsx index f17e2fbf3..ddd20b0c6 100644 --- a/studio/src/app/components/editor/offline/app-go-offline/app-go-offline.tsx +++ b/studio/src/app/components/editor/offline/app-go-offline/app-go-offline.tsx @@ -1,12 +1,13 @@ import {Component, h, State, Event, EventEmitter} from '@stencil/core'; +import store from '../../../../stores/error.store'; + import {Subscription} from 'rxjs'; import {OfflineService} from '../../../../services/editor/offline/offline.service'; -import {ErrorService} from '../../../../services/core/error/error.service'; @Component({ - tag: 'app-go-offline' + tag: 'app-go-offline', }) export class AppGoOffline { @State() @@ -22,13 +23,11 @@ export class AppGoOffline { private progress: number = 0; private offlineService: OfflineService; - private errorService: ErrorService; private progressSubscription: Subscription; constructor() { this.offlineService = OfflineService.getInstance(); - this.errorService = ErrorService.getInstance(); } componentWillLoad() { @@ -54,7 +53,7 @@ export class AppGoOffline { } catch (err) { this.goingOffline = false; this.inProgress.emit(false); - this.errorService.error('Apologies, something went wrong and app was unable to go offline.'); + store.state.error = 'Apologies, something went wrong and app was unable to go offline.'; } } diff --git a/studio/src/app/components/editor/offline/app-go-online/app-go-online.tsx b/studio/src/app/components/editor/offline/app-go-online/app-go-online.tsx index 2c0787906..0a514f410 100644 --- a/studio/src/app/components/editor/offline/app-go-online/app-go-online.tsx +++ b/studio/src/app/components/editor/offline/app-go-online/app-go-online.tsx @@ -2,11 +2,12 @@ import {Component, h, State, Event, EventEmitter} from '@stencil/core'; import {Subscription} from 'rxjs'; +import store from '../../../../stores/error.store'; + import {OfflineService} from '../../../../services/editor/offline/offline.service'; -import {ErrorService} from '../../../../services/core/error/error.service'; @Component({ - tag: 'app-go-online' + tag: 'app-go-online', }) export class AppGoOnline { @State() @@ -25,13 +26,11 @@ export class AppGoOnline { private progress: number = 0; private offlineService: OfflineService; - private errorService: ErrorService; private progressSubscription: Subscription; constructor() { this.offlineService = OfflineService.getInstance(); - this.errorService = ErrorService.getInstance(); } componentWillLoad() { @@ -60,7 +59,7 @@ export class AppGoOnline { } catch (err) { this.goingOnline = false; this.inProgress.emit(false); - this.errorService.error('Something went wrong. Double check your internet connection and try again. If it still does not work, contact us!'); + store.state.error = 'Something went wrong. Double check your internet connection and try again. If it still does not work, contact us!'; } } @@ -95,7 +94,7 @@ export class AppGoOnline {

Please note that the upload of this deck will replace its previous online version.

, -

Long story short, your local presentation is going to be uploaded and saved in the database as the good one.

+

Long story short, your local presentation is going to be uploaded and saved in the database as the good one.

, ]; } diff --git a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx index fb4747f8d..59036700a 100644 --- a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx +++ b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx @@ -3,13 +3,13 @@ import {Component, Event, EventEmitter, h, State} from '@stencil/core'; import {Subject, Subscription} from 'rxjs'; import {debounceTime, filter, take} from 'rxjs/operators'; -import store from '../../../../stores/deck.store'; +import deckStore from '../../../../stores/deck.store'; +import errorStore from '../../../../stores/error.store'; import {Deck} from '../../../../models/data/deck'; import {Resources} from '../../../../utils/core/resources'; -import {ErrorService} from '../../../../services/core/error/error.service'; import {DeckService} from '../../../../services/data/deck/deck.service'; import {ApiUser} from '../../../../models/api/api.user'; import {ApiUserService} from '../../../../services/api/user/api.user.service'; @@ -49,8 +49,6 @@ export class AppPublishEdit { private deckService: DeckService; - private errorService: ErrorService; - private updateDeckSubscription: Subscription; private updateDeckSubject: Subject = new Subject(); @@ -71,8 +69,6 @@ export class AppPublishEdit { constructor() { this.deckService = DeckService.getInstance(); - this.errorService = ErrorService.getInstance(); - this.apiUserService = ApiUserFactoryService.getInstance(); this.publishService = PublishService.getInstance(); @@ -110,16 +106,16 @@ export class AppPublishEdit { } private async init() { - if (!store.state.deck || !store.state.deck.data) { + if (!deckStore.state.deck || !deckStore.state.deck.data) { return; } - this.caption = store.state.deck.data.name; + this.caption = deckStore.state.deck.data.name; this.description = - store.state.deck.data.meta && store.state.deck.data.meta.description - ? (store.state.deck.data.meta.description as string) + deckStore.state.deck.data.meta && deckStore.state.deck.data.meta.description + ? (deckStore.state.deck.data.meta.description as string) : await this.getFirstSlideContent(); - this.tags = store.state.deck.data.meta && store.state.deck.data.meta.tags ? (store.state.deck.data.meta.tags as string[]) : []; + this.tags = deckStore.state.deck.data.meta && deckStore.state.deck.data.meta.tags ? (deckStore.state.deck.data.meta.tags as string[]) : []; } componentDidUnload() { @@ -165,21 +161,21 @@ export class AppPublishEdit { this.disablePublish = true; try { - if (!store.state.deck || !store.state.deck.data || !store.state.deck.id) { + if (!deckStore.state.deck || !deckStore.state.deck.data || !deckStore.state.deck.id) { this.disablePublish = false; resolve(); return; } - store.state.deck.data.name = this.caption; + deckStore.state.deck.data.name = this.caption; - const updatedDeck: Deck = await this.deckService.update(store.state.deck); - store.state.deck = {...updatedDeck}; + const updatedDeck: Deck = await this.deckService.update(deckStore.state.deck); + deckStore.state.deck = {...updatedDeck}; this.disablePublish = false; } catch (err) { this.disablePublish = false; - this.errorService.error(err); + errorStore.state.error = err; } resolve(); @@ -209,7 +205,7 @@ export class AppPublishEdit { resolve(); } catch (err) { this.publishing = false; - this.errorService.error(err); + errorStore.state.error = err; resolve(); } }); diff --git a/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx b/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx index 70d8ed2ce..a950f54ce 100644 --- a/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx +++ b/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx @@ -3,7 +3,8 @@ import {ItemReorderEventDetail} from '@ionic/core'; import {Subject, Subscription} from 'rxjs'; import {debounceTime, filter, take} from 'rxjs/operators'; -import store from '../../../../stores/deck.store'; +import deckStore from '../../../../stores/deck.store'; +import errorStore from '../../../../stores/error.store'; import {cleanContent} from '@deckdeckgo/deck-utils'; @@ -19,7 +20,6 @@ import {Resources} from '../../../../utils/core/resources'; import {SlotUtils} from '../../../../utils/editor/slot.utils'; -import {ErrorService} from '../../../../services/core/error/error.service'; import {BusyService} from '../../../../services/editor/busy/busy.service'; import {AuthService} from '../../../../services/auth/auth.service'; import {DeckService} from '../../../../services/data/deck/deck.service'; @@ -28,7 +28,6 @@ import {SlideService} from '../../../../services/data/slide/slide.service'; export class DeckEventsHandler { private el: HTMLElement; - private errorService: ErrorService; private busyService: BusyService; private authService: AuthService; @@ -43,7 +42,6 @@ export class DeckEventsHandler { private slideService: SlideService; constructor() { - this.errorService = ErrorService.getInstance(); this.busyService = BusyService.getInstance(); this.authService = AuthService.getInstance(); @@ -208,22 +206,22 @@ export class DeckEventsHandler { this.busyService.deckBusy(true); - if (!store.state.deck) { + if (!deckStore.state.deck) { const persistedDeck: Deck = await this.createDeck(); - store.state.deck = {...persistedDeck}; + deckStore.state.deck = {...persistedDeck}; } - const persistedSlide: Slide = await this.postSlide(store.state.deck, slide); + const persistedSlide: Slide = await this.postSlide(deckStore.state.deck, slide); // Because of the offline mode, is kind of handy to handle the list on the client side too. // But maybe in the future it is something which could be moved to the cloud. - await this.updateDeckSlideList(store.state.deck, persistedSlide); + await this.updateDeckSlideList(deckStore.state.deck, persistedSlide); this.busyService.deckBusy(false); resolve(); } catch (err) { - this.errorService.error(err); + errorStore.state.error = err; this.busyService.deckBusy(false); resolve(); } @@ -282,7 +280,7 @@ export class DeckEventsHandler { } const persistedDeck: Deck = await this.deckService.create(deck); - store.state.deck = {...persistedDeck}; + deckStore.state.deck = {...persistedDeck}; await this.updateNavigation(persistedDeck); @@ -314,7 +312,7 @@ export class DeckEventsHandler { deck.data.slides.push(slide.id); const updatedDeck: Deck = await this.deckService.update(deck); - store.state.deck = {...updatedDeck}; + deckStore.state.deck = {...updatedDeck}; resolve(); } catch (err) { @@ -346,7 +344,7 @@ export class DeckEventsHandler { this.busyService.deckBusy(true); - const currentDeck: Deck | null = store.state.deck; + const currentDeck: Deck | null = deckStore.state.deck; if (!currentDeck || !currentDeck.data) { resolve(); @@ -363,13 +361,13 @@ export class DeckEventsHandler { const updatedDeck: Deck = await this.deckService.update(currentDeck); - store.state.deck = {...updatedDeck}; + deckStore.state.deck = {...updatedDeck}; this.busyService.deckBusy(false); resolve(); } catch (err) { - this.errorService.error(err); + errorStore.state.error = err; this.busyService.deckBusy(false); resolve(); } @@ -386,7 +384,7 @@ export class DeckEventsHandler { this.busyService.deckBusy(true); - const currentDeck: Deck | null = store.state.deck; + const currentDeck: Deck | null = deckStore.state.deck; if (!currentDeck || !currentDeck.data) { resolve(); @@ -402,13 +400,13 @@ export class DeckEventsHandler { currentDeck.data.name = title; const updatedDeck: Deck = await this.deckService.update(currentDeck); - store.state.deck = {...updatedDeck}; + deckStore.state.deck = {...updatedDeck}; this.busyService.deckBusy(false); resolve(); } catch (err) { - this.errorService.error(err); + errorStore.state.error = err; this.busyService.deckBusy(false); resolve(); } @@ -424,7 +422,7 @@ export class DeckEventsHandler { } if (!slide.getAttribute('slide_id')) { - this.errorService.error('Slide is not defined'); + errorStore.state.error = 'Slide is not defined'; resolve(); return; } @@ -450,15 +448,15 @@ export class DeckEventsHandler { slideUpdate.data.attributes = attributes; } - if (store.state.deck) { - await this.slideService.update(store.state.deck.id, slideUpdate); + if (deckStore.state.deck) { + await this.slideService.update(deckStore.state.deck.id, slideUpdate); } this.busyService.deckBusy(false); resolve(); } catch (err) { - this.errorService.error(err); + errorStore.state.error = err; this.busyService.deckBusy(false); resolve(); } @@ -474,14 +472,14 @@ export class DeckEventsHandler { } if (!slide.getAttribute('slide_id')) { - this.errorService.error('Slide is not defined'); + errorStore.state.error = 'Slide is not defined'; resolve(); return; } const slideId: string = slide.getAttribute('slide_id'); - const currentDeck: Deck | null = store.state.deck; + const currentDeck: Deck | null = deckStore.state.deck; if (currentDeck && currentDeck.data) { const slide: Slide = await this.slideService.get(currentDeck.id, slideId); @@ -498,7 +496,7 @@ export class DeckEventsHandler { currentDeck.data.slides.splice(currentDeck.data.slides.indexOf(slideId), 1); const updatedDeck: Deck = await this.deckService.update(currentDeck); - store.state.deck = {...updatedDeck}; + deckStore.state.deck = {...updatedDeck}; } } } @@ -509,7 +507,7 @@ export class DeckEventsHandler { resolve(); } catch (err) { - this.errorService.error(err); + errorStore.state.error = err; this.busyService.deckBusy(false); resolve(); } @@ -887,13 +885,13 @@ export class DeckEventsHandler { return; } - const currentDeck: Deck | null = store.state.deck; + const currentDeck: Deck | null = deckStore.state.deck; if (currentDeck && currentDeck.data && currentDeck.data.slides && detail.to < currentDeck.data.slides.length) { currentDeck.data.slides.splice(detail.to, 0, ...currentDeck.data.slides.splice(detail.from, 1)); const updatedDeck: Deck = await this.deckService.update(currentDeck); - store.state.deck = {...updatedDeck}; + deckStore.state.deck = {...updatedDeck}; } resolve(); diff --git a/studio/src/app/helpers/editor/editor.helper.tsx b/studio/src/app/helpers/editor/editor.helper.tsx index 65e06c352..37961ca02 100644 --- a/studio/src/app/helpers/editor/editor.helper.tsx +++ b/studio/src/app/helpers/editor/editor.helper.tsx @@ -1,19 +1,18 @@ import {JSX} from '@stencil/core'; import store from '../../stores/deck.store'; +import errorStore from '../../stores/error.store'; import {Slide} from '../../models/data/slide'; import {Deck} from '../../models/data/deck'; import {ParseSlidesUtils} from '../../utils/editor/parse-slides.utils'; -import {ErrorService} from '../../services/core/error/error.service'; import {BusyService} from '../../services/editor/busy/busy.service'; import {DeckService} from '../../services/data/deck/deck.service'; import {SlideService} from '../../services/data/slide/slide.service'; export class EditorHelper { - private errorService: ErrorService; private busyService: BusyService; private slideService: SlideService; @@ -22,7 +21,6 @@ export class EditorHelper { constructor() { this.slideService = SlideService.getInstance(); - this.errorService = ErrorService.getInstance(); this.busyService = BusyService.getInstance(); this.deckService = DeckService.getInstance(); @@ -31,7 +29,7 @@ export class EditorHelper { loadDeckAndRetrieveSlides(deckId: string): Promise { return new Promise(async (resolve) => { if (!deckId) { - this.errorService.error('Deck is not defined'); + errorStore.state.error = 'Deck is not defined'; resolve(null); return; } @@ -42,7 +40,7 @@ export class EditorHelper { const deck: Deck = await this.deckService.get(deckId); if (!deck || !deck.data) { - this.errorService.error('No deck could be fetched'); + errorStore.state.error = 'No deck could be fetched'; resolve(null); return; } @@ -73,7 +71,7 @@ export class EditorHelper { resolve(parsedSlides); } catch (err) { - this.errorService.error(err); + errorStore.state.error = err; this.busyService.deckBusy(false); resolve(null); } @@ -88,7 +86,7 @@ export class EditorHelper { resolve(element); } catch (err) { - this.errorService.error('Something went wrong while loading and parsing a slide'); + errorStore.state.error = 'Something went wrong while loading and parsing a slide'; resolve(null); } }); @@ -103,7 +101,7 @@ export class EditorHelper { } if (!slide.getAttribute('slide_id')) { - this.errorService.error('Slide is not defined'); + errorStore.state.error = 'Slide is not defined'; resolve(null); return; } @@ -121,7 +119,7 @@ export class EditorHelper { resolve(element); } catch (err) { - this.errorService.error(err); + errorStore.state.error = err; this.busyService.deckBusy(false); resolve(null); } diff --git a/studio/src/app/pages/core/app-poll/app-poll.tsx b/studio/src/app/pages/core/app-poll/app-poll.tsx index 178f9f691..724abca04 100644 --- a/studio/src/app/pages/core/app-poll/app-poll.tsx +++ b/studio/src/app/pages/core/app-poll/app-poll.tsx @@ -2,16 +2,17 @@ import {Component, h, Prop, State} from '@stencil/core'; import {Subscription} from 'rxjs'; +import store from '../../../stores/error.store'; + import {get, set} from 'idb-keyval'; import {DeckdeckgoPoll, DeckdeckgoPollAnswer} from '@deckdeckgo/types'; import {PollService} from '../../../services/poll/poll.service'; -import {ErrorService} from '../../../services/core/error/error.service'; @Component({ tag: 'app-poll', - styleUrl: 'app-poll.scss' + styleUrl: 'app-poll.scss', }) export class AppPoll { @Prop({mutable: true}) @@ -35,13 +36,11 @@ export class AppPoll { private keywords: string[] = ['You did it', 'Applause', 'Thumbs up', 'Congratulations']; private pollService: PollService; - private errorService: ErrorService; private subscription: Subscription; constructor() { this.pollService = PollService.getInstance(); - this.errorService = ErrorService.getInstance(); } async componentWillLoad() { @@ -49,7 +48,7 @@ export class AppPoll { this.poll = poll; if (this.pollKey && (!poll || poll === undefined)) { - this.errorService.error('Oopsie the poll was not found. Double check that the code is correct and try again.'); + store.state.error = 'Oopsie the poll was not found. Double check that the code is correct and try again.'; } this.connecting = false; @@ -90,7 +89,7 @@ export class AppPoll { await set(`deckdeckgo_poll_${this.poll.key}`, new Date().getTime()); } catch (err) { - this.errorService.error(err); + store.state.error = err; } } @@ -143,7 +142,7 @@ export class AppPoll { {this.renderJoinPoll()} {this.renderHasVoted()} - + , ]; } @@ -164,7 +163,7 @@ export class AppPoll { {this.renderSubmitForm()} - + , ]; } @@ -198,7 +197,7 @@ export class AppPoll { this.renderJoinPollForm(),

Live interactive audience participation

,

Engage your audience or class in real time.

, -

Involve them to contribute to your presentations with their smartphones and show the results live.

+

Involve them to contribute to your presentations with their smartphones and show the results live.

, ]; } diff --git a/studio/src/app/pages/core/app-settings/app-settings.tsx b/studio/src/app/pages/core/app-settings/app-settings.tsx index 2bead7fed..847ec3bbd 100644 --- a/studio/src/app/pages/core/app-settings/app-settings.tsx +++ b/studio/src/app/pages/core/app-settings/app-settings.tsx @@ -7,6 +7,7 @@ import firebase from '@firebase/app'; import '@firebase/auth'; import state from '../../../stores/theme.store'; +import errorStore from '../../../stores/error.store'; import {ApiUser} from '../../../models/api/api.user'; import {AuthUser} from '../../../models/auth/auth.user'; @@ -17,7 +18,6 @@ import {UserUtils} from '../../../utils/core/user-utils'; import {ApiUserService} from '../../../services/api/user/api.user.service'; import {AuthService} from '../../../services/auth/auth.service'; import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; -import {ErrorService} from '../../../services/core/error/error.service'; import {ImageHistoryService} from '../../../services/editor/image-history/image-history.service'; import {UserService} from '../../../services/data/user/user.service'; import {StorageService} from '../../../services/storage/storage.service'; @@ -59,8 +59,6 @@ export class AppHome { private navService: NavService; - private errorService: ErrorService; - private imageHistoryService: ImageHistoryService; private profilePicture: File; @@ -91,7 +89,6 @@ export class AppHome { this.authService = AuthService.getInstance(); this.apiUserService = ApiUserFactoryService.getInstance(); this.navService = NavService.getInstance(); - this.errorService = ErrorService.getInstance(); this.imageHistoryService = ImageHistoryService.getInstance(); this.userService = UserService.getInstance(); this.storageService = StorageService.getInstance(); @@ -245,7 +242,7 @@ export class AppHome { this.saving = false; } catch (err) { - this.errorService.error(err); + errorStore.state.error = err; this.saving = false; } @@ -369,7 +366,7 @@ export class AppHome { resolve(); } catch (err) { - this.errorService.error("Your user couldn't be deleted, contact us per email"); + errorStore.state.error = "Your user couldn't be deleted, contact us per email"; } }); } diff --git a/studio/src/app/services/api/photo/api.photo.prod.service.tsx b/studio/src/app/services/api/photo/api.photo.prod.service.tsx index 702f82de3..c473b913c 100644 --- a/studio/src/app/services/api/photo/api.photo.prod.service.tsx +++ b/studio/src/app/services/api/photo/api.photo.prod.service.tsx @@ -3,6 +3,8 @@ import {ApiPhotoService} from './api.photo.service'; import {EnvironmentUnsplashConfig} from '../../core/environment/environment-config'; import {EnvironmentConfigService} from '../../core/environment/environment-config.service'; +import store from '../../../stores/error.store'; + export class ApiPhotoProdService extends ApiPhotoService { // @Override getPhotos(searchTerm: string, next: string | number): Promise { @@ -17,14 +19,14 @@ export class ApiPhotoProdService extends ApiPhotoService { const response: UnsplashSearchResponse = JSON.parse(await rawResponse.text()); if (!response) { - this.errorService.error('Unsplash photos could not be fetched'); + store.state.error = 'Unsplash photos could not be fetched'; resolve(); return; } resolve(response); } catch (err) { - this.errorService.error(err.message); + store.state.error = err.message; resolve(); } }); diff --git a/studio/src/app/services/api/photo/api.photo.service.tsx b/studio/src/app/services/api/photo/api.photo.service.tsx index 9f9179b86..f92430174 100644 --- a/studio/src/app/services/api/photo/api.photo.service.tsx +++ b/studio/src/app/services/api/photo/api.photo.service.tsx @@ -1,13 +1,4 @@ -import {ErrorService} from '../../core/error/error.service'; - export abstract class ApiPhotoService { - protected errorService: ErrorService; - - public constructor() { - // Private constructor, singleton - this.errorService = ErrorService.getInstance(); - } - abstract getPhotos(searchTerm: string, next: string | number): Promise; abstract registerDownload(photoId: string): Promise; diff --git a/studio/src/app/services/auth/auth.service.tsx b/studio/src/app/services/auth/auth.service.tsx index 97701d046..11aa84205 100644 --- a/studio/src/app/services/auth/auth.service.tsx +++ b/studio/src/app/services/auth/auth.service.tsx @@ -5,14 +5,14 @@ import {User as FirebaseUser} from 'firebase'; import {Observable, ReplaySubject} from 'rxjs'; import {take} from 'rxjs/operators'; +import errorStore from '../../stores/error.store'; + import {get, set, del} from 'idb-keyval'; import {EnvironmentConfigService} from '../core/environment/environment-config.service'; import {AuthUser} from '../../models/auth/auth.user'; -import {ErrorService} from '../core/error/error.service'; - import {ApiUserService} from '../api/user/api.user.service'; import {UserService} from '../data/user/user.service'; import {ApiUserFactoryService} from '../api/user/api.user.factory.service'; @@ -20,8 +20,6 @@ import {ApiUserFactoryService} from '../api/user/api.user.factory.service'; export class AuthService { private authUserSubject: ReplaySubject = new ReplaySubject(1); - private errorService: ErrorService; - private apiUserService: ApiUserService; private firestoreUserService: UserService; @@ -30,7 +28,6 @@ export class AuthService { private constructor() { // Private constructor, singleton - this.errorService = ErrorService.getInstance(); this.apiUserService = ApiUserFactoryService.getInstance(); this.firestoreUserService = UserService.getInstance(); } @@ -67,7 +64,7 @@ export class AuthService { name: firebaseUser.displayName, email: firebaseUser.email, email_verified: firebaseUser.emailVerified, - photo_url: firebaseUser.photoURL + photo_url: firebaseUser.photoURL, }; // Update anonymous user @@ -110,7 +107,7 @@ export class AuthService { resolve(); } catch (err) { - this.errorService.error(err.message); + errorStore.state.error = err.message; resolve(); } }); diff --git a/studio/src/app/services/core/error/error.service.tsx b/studio/src/app/services/core/error/error.service.tsx deleted file mode 100644 index 51de96cf0..000000000 --- a/studio/src/app/services/core/error/error.service.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import {Observable, Subject} from 'rxjs'; - -export class ErrorService { - private errorSubject: Subject = new Subject(); - - private static instance: ErrorService; - - private constructor() { - // Private constructor, singleton - } - - static getInstance() { - if (!ErrorService.instance) { - ErrorService.instance = new ErrorService(); - } - return ErrorService.instance; - } - - watch(): Observable { - return this.errorSubject.asObservable(); - } - - error(error: string) { - this.errorSubject.next(error); - } -} diff --git a/studio/src/app/services/data/feed/feed.service.tsx b/studio/src/app/services/data/feed/feed.service.tsx index 563731ba4..9b9a7f82a 100644 --- a/studio/src/app/services/data/feed/feed.service.tsx +++ b/studio/src/app/services/data/feed/feed.service.tsx @@ -4,9 +4,9 @@ import 'firebase/firestore'; import {BehaviorSubject, Observable, ReplaySubject} from 'rxjs'; import {take} from 'rxjs/operators'; -import {Deck, DeckData} from '../../../models/data/deck'; +import store from '../../../stores/error.store'; -import {ErrorService} from '../../core/error/error.service'; +import {Deck, DeckData} from '../../../models/data/deck'; export class FeedService { private static instance: FeedService; @@ -14,19 +14,12 @@ export class FeedService { private decksSubject: ReplaySubject = new ReplaySubject(1); private lastPageReached: BehaviorSubject = new BehaviorSubject(false); - private errorService: ErrorService; - private nextQueryAfter: firebase.firestore.DocumentSnapshot; private queryLimit: number = 20; private decks: Deck[] = []; - private constructor() { - // Private constructor, singleton - this.errorService = ErrorService.getInstance(); - } - static getInstance() { if (!FeedService.instance) { FeedService.instance = new FeedService(); @@ -107,7 +100,7 @@ export class FeedService { resolve(); } catch (err) { - this.errorService.error("Something weird happened, we couldn't fetch the decks."); + store.state.error = "Something weird happened, we couldn't fetch the decks."; resolve(); } }); diff --git a/studio/src/app/services/storage/storage.offline.service.tsx b/studio/src/app/services/storage/storage.offline.service.tsx index 4a289c9e9..e1c97740e 100644 --- a/studio/src/app/services/storage/storage.offline.service.tsx +++ b/studio/src/app/services/storage/storage.offline.service.tsx @@ -1,16 +1,10 @@ import {keys, set} from 'idb-keyval'; -import {ErrorService} from '../core/error/error.service'; +import store from '../../stores/error.store'; export class StorageOfflineService { private static instance: StorageOfflineService; - private errorService: ErrorService; - - private constructor() { - this.errorService = ErrorService.getInstance(); - } - static getInstance() { if (!StorageOfflineService.instance) { StorageOfflineService.instance = new StorageOfflineService(); @@ -22,13 +16,13 @@ export class StorageOfflineService { return new Promise(async (resolve) => { try { if (!data || !data.name) { - this.errorService.error('File not valid.'); + store.state.error = 'File not valid.'; resolve(); return; } if (data.size > maxSize) { - this.errorService.error(`File is too big (max. ${maxSize / 1048576} Mb)`); + store.state.error = `File is too big (max. ${maxSize / 1048576} Mb)`; resolve(); return; } @@ -40,10 +34,10 @@ export class StorageOfflineService { resolve({ downloadUrl: key, fullPath: key, - name: data.name + name: data.name, }); } catch (err) { - this.errorService.error('File could not be saved.'); + store.state.error = 'File could not be saved.'; resolve(); } }); @@ -71,13 +65,13 @@ export class StorageOfflineService { return { downloadUrl: key, fullPath: key, - name: key + name: key, } as StorageFile; }); resolve({ items, - nextPageToken: undefined + nextPageToken: undefined, }); }); } diff --git a/studio/src/app/services/storage/storage.online.service.tsx b/studio/src/app/services/storage/storage.online.service.tsx index 6b3b96c52..7be57eb0c 100644 --- a/studio/src/app/services/storage/storage.online.service.tsx +++ b/studio/src/app/services/storage/storage.online.service.tsx @@ -3,6 +3,8 @@ import '@firebase/storage'; import {Reference, ListResult, ListOptions} from '@firebase/storage-types'; +import store from '../../stores/error.store'; + import {take} from 'rxjs/operators'; import {AuthUser} from '../../models/auth/auth.user'; @@ -10,16 +12,13 @@ import {AuthUser} from '../../models/auth/auth.user'; import {Resources} from '../../utils/core/resources'; import {AuthService} from '../auth/auth.service'; -import {ErrorService} from '../core/error/error.service'; export class StorageOnlineService { private static instance: StorageOnlineService; private authService: AuthService; - private errorService: ErrorService; private constructor() { - this.errorService = ErrorService.getInstance(); this.authService = AuthService.getInstance(); } @@ -38,19 +37,19 @@ export class StorageOnlineService { .pipe(take(1)) .subscribe(async (authUser: AuthUser) => { if (!authUser || !authUser.uid || authUser.uid === '' || authUser.uid === undefined) { - this.errorService.error('Not logged in.'); + store.state.error = 'Not logged in.'; resolve(); return; } if (!data || !data.name) { - this.errorService.error('File not valid.'); + store.state.error = 'File not valid.'; resolve(); return; } if (data.size > maxSize) { - this.errorService.error(`File is too big (max. ${maxSize / 1048576} Mb)`); + store.state.error = `File is too big (max. ${maxSize / 1048576} Mb)`; resolve(); return; } @@ -66,7 +65,7 @@ export class StorageOnlineService { }); }); } catch (err) { - this.errorService.error(err.message); + store.state.error = err.message; resolve(); } }); @@ -80,7 +79,7 @@ export class StorageOnlineService { .pipe(take(1)) .subscribe(async (authUser: AuthUser) => { if (!authUser || !authUser.uid || authUser.uid === '' || authUser.uid === undefined) { - this.errorService.error('Not logged in.'); + store.state.error = 'Not logged in.'; resolve(null); return; } diff --git a/studio/src/app/services/tenor/gif/gif.service.tsx b/studio/src/app/services/tenor/gif/gif.service.tsx index ddba8759a..52814d417 100644 --- a/studio/src/app/services/tenor/gif/gif.service.tsx +++ b/studio/src/app/services/tenor/gif/gif.service.tsx @@ -2,19 +2,13 @@ import {EnvironmentConfigService} from '../../core/environment/environment-confi import {get, set} from 'idb-keyval'; -import {ErrorService} from '../../core/error/error.service'; +import store from '../../../stores/error.store'; + import {EnvironmentTenorConfig} from '../../core/environment/environment-config'; export class GifService { private static instance: GifService; - private errorService: ErrorService; - - private constructor() { - // Private constructor, singleton - this.errorService = ErrorService.getInstance(); - } - static getInstance() { if (!GifService.instance) { GifService.instance = new GifService(); @@ -36,13 +30,13 @@ export class GifService { const response: TenorCategoryResponse = JSON.parse(await rawResponse.text()); if (!response) { - this.errorService.error('Tenor trending could not be fetched'); + store.state.error = 'Tenor trending could not be fetched'; return; } resolve(response.tags); } catch (err) { - this.errorService.error(err.message); + store.state.error = err.message; resolve(); } }); @@ -73,14 +67,14 @@ export class GifService { const response: TenorSearchResponse = JSON.parse(await rawResponse.text()); if (!response) { - this.errorService.error('Tenor trending could not be fetched'); + store.state.error = 'Tenor trending could not be fetched'; resolve(); return; } resolve(response); } catch (err) { - this.errorService.error(err.message); + store.state.error = err.message; resolve(); } }); @@ -101,7 +95,7 @@ export class GifService { const response: TenorSearchResponse = JSON.parse(await rawResponse.text()); if (!response) { - this.errorService.error('Tenor trending could not be fetched'); + store.state.error = 'Tenor trending could not be fetched'; resolve(); return; } diff --git a/studio/src/app/stores/error.store.ts b/studio/src/app/stores/error.store.ts new file mode 100644 index 000000000..06f0550d6 --- /dev/null +++ b/studio/src/app/stores/error.store.ts @@ -0,0 +1,11 @@ +import {createStore} from '@stencil/store'; + +interface ErrorStore { + error: string | undefined; +} + +const {state, onChange} = createStore({ + error: undefined, +} as ErrorStore); + +export default {state, onChange}; From bd1f7742d540731f2c62aa177c1741281c7bccd8 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sun, 28 Jun 2020 21:26:30 +0200 Subject: [PATCH 04/32] feat(#773): handle none well formatted date --- .../feed/card/app-feed-card/app-feed-card.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/studio/src/app/components/feed/card/app-feed-card/app-feed-card.tsx b/studio/src/app/components/feed/card/app-feed-card/app-feed-card.tsx index 9b6c348db..2c9700c47 100644 --- a/studio/src/app/components/feed/card/app-feed-card/app-feed-card.tsx +++ b/studio/src/app/components/feed/card/app-feed-card/app-feed-card.tsx @@ -109,7 +109,12 @@ export class AppFeedCard { } const options: DateTimeFormatOptions = {year: 'numeric', month: 'short', day: 'numeric'}; - this.formattedPublishedAt = new Intl.DateTimeFormat('en-US', options).format(this.getDateObj(this.deck.data.meta.published_at)); + + try { + this.formattedPublishedAt = new Intl.DateTimeFormat('en-US', options).format(this.getDateObj(this.deck.data.meta.published_at)); + } catch (err) { + this.formattedPublishedAt = undefined; + } resolve(); }); @@ -141,7 +146,7 @@ export class AppFeedCard { } private getDateObj(myDate: any): Date { - if (myDate == null) { + if (!myDate) { return null; } @@ -150,7 +155,12 @@ export class AppFeedCard { } // A Firebase Timestamp format - if (myDate && (myDate.seconds >= 0 || myDate.seconds < 0) && (myDate.nanoseconds >= 0 || myDate.nanoseconds < 0)) { + if ( + myDate && + (myDate.seconds >= 0 || myDate.seconds < 0) && + (myDate.nanoseconds >= 0 || myDate.nanoseconds < 0) && + typeof (myDate as any).toDate === 'function' + ) { return new Date(myDate.toDate()); } From 0d964c5cd4cc7577e7ce4ec85f5ba21efb7185c8 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Sun, 28 Jun 2020 21:27:07 +0200 Subject: [PATCH 05/32] feat(#773): use stencil/store for the feed --- .../app-publish-edit/app-publish-edit.tsx | 8 +-- .../app/components/feed/app-feed/app-feed.tsx | 44 +++---------- .../app/services/data/feed/feed.service.tsx | 63 +++++-------------- studio/src/app/stores/feed.store.ts | 15 +++++ 4 files changed, 39 insertions(+), 91 deletions(-) create mode 100644 studio/src/app/stores/feed.store.ts diff --git a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx index 59036700a..a919266cf 100644 --- a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx +++ b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx @@ -5,6 +5,7 @@ import {debounceTime, filter, take} from 'rxjs/operators'; import deckStore from '../../../../stores/deck.store'; import errorStore from '../../../../stores/error.store'; +import feedStore from '../../../../stores/feed.store'; import {Deck} from '../../../../models/data/deck'; @@ -14,7 +15,6 @@ import {DeckService} from '../../../../services/data/deck/deck.service'; import {ApiUser} from '../../../../models/api/api.user'; import {ApiUserService} from '../../../../services/api/user/api.user.service'; import {PublishService} from '../../../../services/editor/publish/publish.service'; -import {FeedService} from '../../../../services/data/feed/feed.service'; import {ApiUserFactoryService} from '../../../../services/api/user/api.user.factory.service'; interface CustomInputEvent extends KeyboardEvent { @@ -59,8 +59,6 @@ export class AppPublishEdit { private publishService: PublishService; - private feedService: FeedService; - @State() private progress: number = 0; @@ -72,8 +70,6 @@ export class AppPublishEdit { this.apiUserService = ApiUserFactoryService.getInstance(); this.publishService = PublishService.getInstance(); - - this.feedService = FeedService.getInstance(); } async componentWillLoad() { @@ -200,7 +196,7 @@ export class AppPublishEdit { this.publishing = false; // In case the user would have browse the feed before, reset it to fetch is updated or new presentation - await this.feedService.reset(); + feedStore.reset(); resolve(); } catch (err) { diff --git a/studio/src/app/components/feed/app-feed/app-feed.tsx b/studio/src/app/components/feed/app-feed/app-feed.tsx index aeab50e8f..1d17c3524 100644 --- a/studio/src/app/components/feed/app-feed/app-feed.tsx +++ b/studio/src/app/components/feed/app-feed/app-feed.tsx @@ -4,6 +4,8 @@ import {Subscription} from 'rxjs'; import {isMobile} from '@deckdeckgo/utils'; +import store from '../../../stores/feed.store'; + import {Deck} from '../../../models/data/deck'; import {EnvironmentConfigService} from '../../../services/core/environment/environment-config.service'; @@ -14,20 +16,11 @@ import {OfflineService} from '../../../services/editor/offline/offline.service'; @Component({ tag: 'app-feed', styleUrl: 'app-feed.scss', - shadow: false + shadow: false, }) export class AppFeed { private feedService: FeedService; - @State() - private decks: Deck[] = []; - - @State() - private lastPageReached: boolean = false; - - @State() - private decksFetched: boolean = false; - @State() private mobile: boolean = false; @@ -36,9 +29,6 @@ export class AppFeed { private presentationUrl: string = EnvironmentConfigService.getInstance().get('deckdeckgo').presentationUrl; - private subscription: Subscription; - private lastPageSubscription: Subscription; - private offlineSubscription: Subscription; private offlineService: OfflineService; @@ -48,16 +38,6 @@ export class AppFeed { } async componentWillLoad() { - this.subscription = this.feedService.watchDecks().subscribe((decks: Deck[]) => { - this.decks = decks; - this.decksFetched = true; - }); - - this.lastPageSubscription = this.feedService.watchLastPageReached().subscribe((lastPageReached: boolean) => { - this.lastPageReached = lastPageReached; - this.decksFetched = lastPageReached; - }); - this.offlineSubscription = this.offlineService.watchOffline().subscribe((offline: OfflineDeck | undefined) => { this.offline = navigator && !navigator.onLine && offline !== undefined ? offline : undefined; }); @@ -70,14 +50,6 @@ export class AppFeed { } async componentDidUnload() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - - if (this.lastPageSubscription) { - this.lastPageSubscription.unsubscribe(); - } - if (this.offlineSubscription) { this.offlineSubscription.unsubscribe(); } @@ -125,12 +97,12 @@ export class AppFeed {
{this.renderDecks()} - this.findNextDecks($event)} disabled={this.lastPageReached}> + this.findNextDecks($event)} disabled={store.state.lastPageReached}>
, - + , ]; } @@ -148,8 +120,8 @@ export class AppFeed { } private renderDecks() { - if (this.decks && this.decks.length > 0) { - return this.decks.map((deck: Deck, i: number) => { + if (store.state.decks && store.state.decks.length > 0) { + return store.state.decks.map((deck: Deck, i: number) => { return (
0} deck={deck}> @@ -162,7 +134,7 @@ export class AppFeed { } private renderLoading() { - if (this.decksFetched) { + if (store.state.decks !== undefined) { return undefined; } else { return ( diff --git a/studio/src/app/services/data/feed/feed.service.tsx b/studio/src/app/services/data/feed/feed.service.tsx index 9b9a7f82a..082036d4a 100644 --- a/studio/src/app/services/data/feed/feed.service.tsx +++ b/studio/src/app/services/data/feed/feed.service.tsx @@ -1,25 +1,18 @@ import * as firebase from 'firebase/app'; import 'firebase/firestore'; -import {BehaviorSubject, Observable, ReplaySubject} from 'rxjs'; -import {take} from 'rxjs/operators'; - -import store from '../../../stores/error.store'; +import feedStore from '../../../stores/feed.store'; +import errorStore from '../../../stores/error.store'; import {Deck, DeckData} from '../../../models/data/deck'; export class FeedService { private static instance: FeedService; - private decksSubject: ReplaySubject = new ReplaySubject(1); - private lastPageReached: BehaviorSubject = new BehaviorSubject(false); - private nextQueryAfter: firebase.firestore.DocumentSnapshot; private queryLimit: number = 20; - private decks: Deck[] = []; - static getInstance() { if (!FeedService.instance) { FeedService.instance = new FeedService(); @@ -27,30 +20,11 @@ export class FeedService { return FeedService.instance; } - watchDecks(): Observable { - return this.decksSubject.asObservable(); - } - - watchLastPageReached(): Observable { - return this.lastPageReached.asObservable(); - } - - reset(): Promise { - return new Promise((resolve) => { - this.nextQueryAfter = null; - this.decks = []; - - this.lastPageReached.next(false); - this.decksSubject.next([]); - - resolve(); - }); - } - refresh(): Promise { return new Promise(async (resolve) => { this.nextQueryAfter = null; - this.decks = []; + + feedStore.reset(); await this.find(); @@ -58,21 +32,12 @@ export class FeedService { }); } - find(): Promise { - return new Promise(async (resolve) => { - this.watchLastPageReached() - .pipe(take(1)) - .subscribe(async (reached: boolean) => { - if (reached) { - resolve(); - return; - } - - await this.findNext(); + async find() { + if (feedStore.state.lastPageReached) { + return; + } - resolve(); - }); - }); + await this.findNext(); } private findNext(): Promise { @@ -81,7 +46,7 @@ export class FeedService { const snapshot: firebase.firestore.QuerySnapshot = await this.query(); if (!snapshot || !snapshot.docs || snapshot.docs.length <= 0) { - this.lastPageReached.next(true); + feedStore.state.lastPageReached = true; resolve(); return; @@ -100,7 +65,7 @@ export class FeedService { resolve(); } catch (err) { - store.state.error = "Something weird happened, we couldn't fetch the decks."; + errorStore.state.error = "Something weird happened, we couldn't fetch the decks."; resolve(); } }); @@ -125,7 +90,7 @@ export class FeedService { private addDecks(decks: Deck[]): Promise { return new Promise(async (resolve) => { if (!decks || decks.length <= 0) { - this.lastPageReached.next(true); + feedStore.state.lastPageReached = true; resolve(); return; @@ -133,9 +98,9 @@ export class FeedService { // It costs around half a second to randomize 10 cards with Chrome but makes the feed more dynamic const randomlySortedDecks: Deck[] = await this.shuffle(decks); - this.decks = this.decks.concat(randomlySortedDecks); + const updatedDecks: Deck[] = feedStore.state.decks ? feedStore.state.decks.concat(randomlySortedDecks) : randomlySortedDecks; - this.decksSubject.next(this.decks); + feedStore.state.decks = [...updatedDecks]; resolve(); }); diff --git a/studio/src/app/stores/feed.store.ts b/studio/src/app/stores/feed.store.ts new file mode 100644 index 000000000..3fa139573 --- /dev/null +++ b/studio/src/app/stores/feed.store.ts @@ -0,0 +1,15 @@ +import {createStore} from '@stencil/store'; + +import {Deck} from '../models/data/deck'; + +interface FeedStore { + decks: Deck[] | undefined; + lastPageReached: boolean; +} + +const {state, reset} = createStore({ + decks: undefined, + lastPageReached: false, +} as FeedStore); + +export default {state, reset}; From e83927bd70335f9c85d252ab1ed256d73ec8a6b6 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 16:03:25 +0200 Subject: [PATCH 06/32] feat(#773): use stencil/store for the busy service --- .../app-breadcrumbs/app-breadcrumbs.tsx | 34 +++------------- .../deck/app-action-busy/app-action-busy.tsx | 32 ++------------- .../app-actions-element.tsx | 40 +++++-------------- .../events/deck/deck-events.handler.tsx | 39 +++++++++--------- .../src/app/helpers/editor/editor.helper.tsx | 16 +++----- .../src/app/helpers/editor/image.helper.tsx | 15 ++++--- .../src/app/helpers/editor/shape.helper.tsx | 13 +++--- .../pages/editor/app-editor/app-editor.tsx | 15 ++----- .../app/services/editor/busy/busy.service.ts | 35 ---------------- studio/src/app/stores/busy.store.ts | 13 ++++++ 10 files changed, 75 insertions(+), 177 deletions(-) delete mode 100644 studio/src/app/services/editor/busy/busy.service.ts create mode 100644 studio/src/app/stores/busy.store.ts diff --git a/studio/src/app/components/editor/actions/app-breadcrumbs/app-breadcrumbs.tsx b/studio/src/app/components/editor/actions/app-breadcrumbs/app-breadcrumbs.tsx index 46bec271a..3c6e43921 100644 --- a/studio/src/app/components/editor/actions/app-breadcrumbs/app-breadcrumbs.tsx +++ b/studio/src/app/components/editor/actions/app-breadcrumbs/app-breadcrumbs.tsx @@ -1,15 +1,13 @@ -import {Component, Prop, h, Host, EventEmitter, Event, State} from '@stencil/core'; +import {Component, Prop, h, Host, EventEmitter, Event} from '@stencil/core'; -import {Subscription} from 'rxjs'; +import store from '../../../../stores/busy.store'; import {BreadcrumbsStep} from '../../../../utils/editor/breadcrumbs-type'; -import {BusyService} from '../../../../services/editor/busy/busy.service'; - @Component({ tag: 'app-breadcrumbs', styleUrl: 'app-breadcrumbs.scss', - shadow: false + shadow: false, }) export class AppBreadcrumbs { @Prop() @@ -18,28 +16,6 @@ export class AppBreadcrumbs { @Event() private stepTo: EventEmitter; - private busySubscription: Subscription; - private busyService: BusyService; - - @State() - private busy: boolean = false; - - constructor() { - this.busyService = BusyService.getInstance(); - } - - componentWillLoad() { - this.busySubscription = this.busyService.watchDeckBusy().subscribe((busy: boolean) => { - this.busy = busy; - }); - } - - componentDidUnload() { - if (this.busySubscription) { - this.busySubscription.unsubscribe(); - } - } - private async selectStep(step: BreadcrumbsStep) { if (!document) { return; @@ -88,13 +64,13 @@ export class AppBreadcrumbs { private renderStep(step: BreadcrumbsStep) { return ( - ); } private renderSeparator() { - return >; + return >; } } diff --git a/studio/src/app/components/editor/actions/deck/app-action-busy/app-action-busy.tsx b/studio/src/app/components/editor/actions/deck/app-action-busy/app-action-busy.tsx index 1e65537c8..9f171c7c8 100644 --- a/studio/src/app/components/editor/actions/deck/app-action-busy/app-action-busy.tsx +++ b/studio/src/app/components/editor/actions/deck/app-action-busy/app-action-busy.tsx @@ -1,49 +1,25 @@ -import {Component, Event, EventEmitter, State, h, Prop} from '@stencil/core'; +import {Component, Event, EventEmitter, h, Prop} from '@stencil/core'; -import {Subscription} from 'rxjs'; - -import {BusyService} from '../../../../../services/editor/busy/busy.service'; +import store from '../../../../../stores/busy.store'; @Component({ tag: 'app-action-busy', styleUrl: 'app-action-busy.scss', - shadow: false + shadow: false, }) export class AppActionBusy { @Event() private actionReady: EventEmitter; - private subscription: Subscription; - private busyService: BusyService; - - @State() - private deckBusy: boolean = false; - @Prop() iconSrc: string; - constructor() { - this.busyService = BusyService.getInstance(); - } - private action($event: UIEvent) { this.actionReady.emit($event); } - async componentWillLoad() { - this.subscription = this.busyService.watchDeckBusy().subscribe((busy: boolean) => { - this.deckBusy = busy; - }); - } - - async componentDidUnload() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - } - render() { return ( - this.action(e)} color="primary" disabled={this.deckBusy} mode="md"> + this.action(e)} color="primary" disabled={store.state.deckBusy} mode="md"> diff --git a/studio/src/app/components/editor/actions/element/app-actions-element/app-actions-element.tsx b/studio/src/app/components/editor/actions/element/app-actions-element/app-actions-element.tsx index 31cf63d72..141e4a939 100644 --- a/studio/src/app/components/editor/actions/element/app-actions-element/app-actions-element.tsx +++ b/studio/src/app/components/editor/actions/element/app-actions-element/app-actions-element.tsx @@ -6,6 +6,8 @@ import {debounceTime} from 'rxjs/operators'; import {isFullscreen, isIOS, isMobile} from '@deckdeckgo/utils'; +import store from '../../../../../stores/busy.store'; + import {ImageHelper} from '../../../../../helpers/editor/image.helper'; import {ShapeHelper} from '../../../../../helpers/editor/shape.helper'; @@ -21,8 +23,6 @@ import {MoreAction} from '../../../../../utils/editor/more-action'; import {DemoAction} from '../../../../../utils/editor/demo-action'; import {PlaygroundAction} from '../../../../../utils/editor/playground-action'; -import {BusyService} from '../../../../../services/editor/busy/busy.service'; - @Component({ tag: 'app-actions-element', styleUrl: 'app-actions-element.scss', @@ -70,12 +70,6 @@ export class AppActionsElement { @Event() private imgDidChange: EventEmitter; @Event() private notesDidChange: EventEmitter; - private subscription: Subscription; - private busyService: BusyService; - - @State() - private deckBusy: boolean = false; - private elementResizeObserver: ResizeObserverConstructor; private moveToolbarSubscription: Subscription; @@ -88,15 +82,7 @@ export class AppActionsElement { @Event() private resetted: EventEmitter; - constructor() { - this.busyService = BusyService.getInstance(); - } - async componentWillLoad() { - this.subscription = this.busyService.watchDeckBusy().subscribe((busy: boolean) => { - this.deckBusy = busy; - }); - this.moveToolbarSubscription = this.moveToolbarSubject.pipe(debounceTime(250)).subscribe(async () => { await this.resizeSlideContent(); }); @@ -110,10 +96,6 @@ export class AppActionsElement { } async componentDidUnload() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - if (this.moveToolbarSubscription) { this.moveToolbarSubscription.unsubscribe(); } @@ -401,12 +383,12 @@ export class AppActionsElement { return; } - if (this.deckBusy && this.slide) { + if (store.state.deckBusy && this.slide) { resolve(); return; } - this.busyService.deckBusy(true); + store.state.deckBusy = true; if (this.selectedElement.nodeName && this.selectedElement.nodeName.toLowerCase().indexOf('deckgo-slide') > -1) { this.slideDelete.emit(this.selectedElement); @@ -439,7 +421,7 @@ export class AppActionsElement { return; } - if (this.deckBusy || !this.shape) { + if (store.state.deckBusy || !this.shape) { resolve(); return; } @@ -457,12 +439,12 @@ export class AppActionsElement { return; } - if (this.deckBusy || !this.slide) { + if (store.state.deckBusy || !this.slide) { resolve(); return; } - this.busyService.deckBusy(true); + store.state.deckBusy = true; this.slideCopy.emit(this.selectedElement); @@ -1051,7 +1033,7 @@ export class AppActionsElement { aria-label="Delete" color="primary" mode="md" - disabled={this.deckBusy && this.slide} + disabled={store.state.deckBusy && this.slide} class="wider-devices"> Delete @@ -1063,7 +1045,7 @@ export class AppActionsElement { const classElement: string | undefined = `wider-devices ${this.slide ? '' : 'hidden'}`; return ( - this.openNotes()} aria-label="Notes" color="primary" mode="md" disabled={this.deckBusy} class={classElement}> + this.openNotes()} aria-label="Notes" color="primary" mode="md" disabled={store.state.deckBusy} class={classElement}> Notes @@ -1074,7 +1056,7 @@ export class AppActionsElement { const classSlide: string | undefined = `wider-devices ${this.slide || this.shape ? '' : 'hidden'}`; return ( - this.clone()} aria-label="Copy" color="primary" mode="md" disabled={this.deckBusy} class={classSlide}> + this.clone()} aria-label="Copy" color="primary" mode="md" disabled={store.state.deckBusy} class={classSlide}> Copy @@ -1172,7 +1154,7 @@ export class AppActionsElement { private renderMore() { return ( - this.openMoreActions(e)} disabled={this.deckBusy} color="primary" class="small-devices" mode="md"> + this.openMoreActions(e)} disabled={store.state.deckBusy} color="primary" class="small-devices" mode="md"> More diff --git a/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx b/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx index a950f54ce..318bc0ccb 100644 --- a/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx +++ b/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx @@ -5,6 +5,7 @@ import {debounceTime, filter, take} from 'rxjs/operators'; import deckStore from '../../../../stores/deck.store'; import errorStore from '../../../../stores/error.store'; +import busyStore from '../../../../stores/busy.store'; import {cleanContent} from '@deckdeckgo/deck-utils'; @@ -20,7 +21,6 @@ import {Resources} from '../../../../utils/core/resources'; import {SlotUtils} from '../../../../utils/editor/slot.utils'; -import {BusyService} from '../../../../services/editor/busy/busy.service'; import {AuthService} from '../../../../services/auth/auth.service'; import {DeckService} from '../../../../services/data/deck/deck.service'; import {SlideService} from '../../../../services/data/slide/slide.service'; @@ -28,8 +28,6 @@ import {SlideService} from '../../../../services/data/slide/slide.service'; export class DeckEventsHandler { private el: HTMLElement; - private busyService: BusyService; - private authService: AuthService; private updateSlideSubscription: Subscription; @@ -42,8 +40,6 @@ export class DeckEventsHandler { private slideService: SlideService; constructor() { - this.busyService = BusyService.getInstance(); - this.authService = AuthService.getInstance(); this.deckService = DeckService.getInstance(); @@ -198,13 +194,16 @@ export class DeckEventsHandler { if (slide.getAttribute('slide_id')) { // !isNew - this.busyService.slideEditable(slide); + + console.log('yolo', slide, {...slide}); + + busyStore.state.slideEditable = slide; resolve(); return; } - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; if (!deckStore.state.deck) { const persistedDeck: Deck = await this.createDeck(); @@ -217,12 +216,12 @@ export class DeckEventsHandler { // But maybe in the future it is something which could be moved to the cloud. await this.updateDeckSlideList(deckStore.state.deck, persistedSlide); - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(); } catch (err) { errorStore.state.error = err; - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(); } }); @@ -250,7 +249,7 @@ export class DeckEventsHandler { if (persistedSlide && persistedSlide.id) { slide.setAttribute('slide_id', persistedSlide.id); - this.busyService.slideEditable(slide); + busyStore.state.slideEditable = slide; } resolve(persistedSlide); @@ -342,7 +341,7 @@ export class DeckEventsHandler { return; } - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; const currentDeck: Deck | null = deckStore.state.deck; @@ -363,12 +362,12 @@ export class DeckEventsHandler { deckStore.state.deck = {...updatedDeck}; - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(); } catch (err) { errorStore.state.error = err; - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(); } }); @@ -382,7 +381,7 @@ export class DeckEventsHandler { return; } - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; const currentDeck: Deck | null = deckStore.state.deck; @@ -402,12 +401,12 @@ export class DeckEventsHandler { const updatedDeck: Deck = await this.deckService.update(currentDeck); deckStore.state.deck = {...updatedDeck}; - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(); } catch (err) { errorStore.state.error = err; - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(); } }); @@ -452,12 +451,12 @@ export class DeckEventsHandler { await this.slideService.update(deckStore.state.deck.id, slideUpdate); } - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(); } catch (err) { errorStore.state.error = err; - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(); } }); @@ -503,12 +502,12 @@ export class DeckEventsHandler { await this.deleteSlideElement(); - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(); } catch (err) { errorStore.state.error = err; - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(); } }); diff --git a/studio/src/app/helpers/editor/editor.helper.tsx b/studio/src/app/helpers/editor/editor.helper.tsx index 37961ca02..15a93601e 100644 --- a/studio/src/app/helpers/editor/editor.helper.tsx +++ b/studio/src/app/helpers/editor/editor.helper.tsx @@ -2,27 +2,23 @@ import {JSX} from '@stencil/core'; import store from '../../stores/deck.store'; import errorStore from '../../stores/error.store'; +import busyStore from '../../stores/busy.store'; import {Slide} from '../../models/data/slide'; import {Deck} from '../../models/data/deck'; import {ParseSlidesUtils} from '../../utils/editor/parse-slides.utils'; -import {BusyService} from '../../services/editor/busy/busy.service'; import {DeckService} from '../../services/data/deck/deck.service'; import {SlideService} from '../../services/data/slide/slide.service'; export class EditorHelper { - private busyService: BusyService; - private slideService: SlideService; private deckService: DeckService; constructor() { this.slideService = SlideService.getInstance(); - this.busyService = BusyService.getInstance(); - this.deckService = DeckService.getInstance(); } @@ -34,7 +30,7 @@ export class EditorHelper { return; } - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; try { const deck: Deck = await this.deckService.get(deckId); @@ -67,12 +63,12 @@ export class EditorHelper { return; } - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(parsedSlides); } catch (err) { errorStore.state.error = err; - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(null); } }); @@ -115,12 +111,12 @@ export class EditorHelper { element = await ParseSlidesUtils.parseSlide(store.state.deck, slide, true, true); } - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(element); } catch (err) { errorStore.state.error = err; - this.busyService.deckBusy(false); + busyStore.state.deckBusy = false; resolve(null); } }); diff --git a/studio/src/app/helpers/editor/image.helper.tsx b/studio/src/app/helpers/editor/image.helper.tsx index 2033fa1e7..2b92a43e3 100644 --- a/studio/src/app/helpers/editor/image.helper.tsx +++ b/studio/src/app/helpers/editor/image.helper.tsx @@ -1,6 +1,8 @@ import {EventEmitter} from '@stencil/core'; import {modalController, OverlayEventDetail} from '@ionic/core'; +import busyStore from '../../stores/busy.store'; + import {ImageAction} from '../../utils/editor/image-action'; import {EditAction} from '../../utils/editor/edit-action'; import {SlotUtils} from '../../utils/editor/slot.utils'; @@ -8,15 +10,12 @@ import {SlotType} from '../../utils/editor/slot-type'; import {DeckgoImgAction, ImageActionUtils} from '../../utils/editor/image-action.utils'; import {AnonymousService} from '../../services/editor/anonymous/anonymous.service'; -import {BusyService} from '../../services/editor/busy/busy.service'; export class ImageHelper { private anonymousService: AnonymousService; - private busyService: BusyService; constructor(private didChange: EventEmitter, private blockSlide: EventEmitter, private signIn: EventEmitter) { this.anonymousService = AnonymousService.getInstance(); - this.busyService = BusyService.getInstance(); } async imageAction(selectedElement: HTMLElement, slide: boolean, deck: boolean, imageAction: ImageAction) { @@ -35,7 +34,7 @@ export class ImageHelper { private async openModal(selectedElement: HTMLElement, slide: boolean, deck: boolean, componentTag: string, action?: EditAction) { const modal: HTMLIonModalElement = await modalController.create({ - component: componentTag + component: componentTag, }); modal.onDidDismiss().then(async (detail: OverlayEventDetail) => { @@ -75,7 +74,7 @@ export class ImageHelper { return; } - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; if (slide || deck) { await this.appendBackgroundImg(selectedElement, image, deck); @@ -102,7 +101,7 @@ export class ImageHelper { const currentSlotElement: HTMLElement = selectedElement.querySelector(":scope > [slot='background']"); if (currentSlotElement) { - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; if (deck) { selectedElement.removeChild(currentSlotElement); @@ -220,7 +219,7 @@ export class ImageHelper { return; } - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; selectedElement.removeAttribute('img-src'); @@ -237,7 +236,7 @@ export class ImageHelper { return; } - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; selectedElement.setAttribute(attribute, image.downloadUrl); diff --git a/studio/src/app/helpers/editor/shape.helper.tsx b/studio/src/app/helpers/editor/shape.helper.tsx index ffb89b174..f464525bf 100644 --- a/studio/src/app/helpers/editor/shape.helper.tsx +++ b/studio/src/app/helpers/editor/shape.helper.tsx @@ -2,21 +2,20 @@ import {EventEmitter} from '@stencil/core'; import {modalController, OverlayEventDetail} from '@ionic/core'; +import busyStore from '../../stores/busy.store'; + import {ShapeAction, ShapeActionSVG} from '../../utils/editor/shape-action'; import {ImageAction} from '../../utils/editor/image-action'; import {SlotType} from '../../utils/editor/slot-type'; import {DeckgoImgAction, ImageActionUtils} from '../../utils/editor/image-action.utils'; import {EditAction} from '../../utils/editor/edit-action'; -import {BusyService} from '../../services/editor/busy/busy.service'; import {AnonymousService} from '../../services/editor/anonymous/anonymous.service'; export class ShapeHelper { - private busyService: BusyService; private anonymousService: AnonymousService; constructor(private didChange: EventEmitter, private signIn: EventEmitter) { - this.busyService = BusyService.getInstance(); this.anonymousService = AnonymousService.getInstance(); } @@ -29,7 +28,7 @@ export class ShapeHelper { } private async appendShapeSVG(slideElement: HTMLElement, shapeAction: ShapeActionSVG) { - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; await this.appendContentShape(slideElement, shapeAction.ratio, shapeAction.src, shapeAction.label, 'svg'); } @@ -47,7 +46,7 @@ export class ShapeHelper { } async cloneShape(shapeElement: HTMLElement) { - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; await this.cloneShapeElement(shapeElement); } @@ -56,7 +55,7 @@ export class ShapeHelper { const deckgImg: DeckgoImgAction | undefined = ImageActionUtils.extractAttributes(image); if (deckgImg !== undefined) { - this.busyService.deckBusy(true); + busyStore.state.deckBusy = true; await this.appendContentShape(slideElement, 1, deckgImg.src, deckgImg.label, 'img'); } @@ -75,7 +74,7 @@ export class ShapeHelper { private async openModal(slideElement: HTMLElement, componentTag: string) { const modal: HTMLIonModalElement = await modalController.create({ - component: componentTag + component: componentTag, }); modal.onDidDismiss().then(async (detail: OverlayEventDetail) => { diff --git a/studio/src/app/pages/editor/app-editor/app-editor.tsx b/studio/src/app/pages/editor/app-editor/app-editor.tsx index eb5ae0c10..e7d677cda 100644 --- a/studio/src/app/pages/editor/app-editor/app-editor.tsx +++ b/studio/src/app/pages/editor/app-editor/app-editor.tsx @@ -2,10 +2,10 @@ import {Component, Element, h, JSX, Listen, Prop, State} from '@stencil/core'; import {ItemReorderEventDetail, modalController, OverlayEventDetail} from '@ionic/core'; -import {Subscription} from 'rxjs'; import {filter, take} from 'rxjs/operators'; import store from '../../../stores/deck.store'; +import busyStore from '../../../stores/busy.store'; import {debounce, isFullscreen, isIOS, isMobile} from '@deckdeckgo/utils'; @@ -35,7 +35,6 @@ import {SlotUtils} from '../../../utils/editor/slot.utils'; import {AuthService} from '../../../services/auth/auth.service'; import {AnonymousService} from '../../../services/editor/anonymous/anonymous.service'; import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; -import {BusyService} from '../../../services/editor/busy/busy.service'; import {EnvironmentGoogleConfig} from '../../../services/core/environment/environment-config'; import {EnvironmentConfigService} from '../../../services/core/environment/environment-config.service'; @@ -82,9 +81,6 @@ export class AppEditor { private anonymousService: AnonymousService; private navService: NavService; - private busySubscription: Subscription; - private busyService: BusyService; - private offlineService: OfflineService; private fontsService: FontsService; @@ -108,7 +104,6 @@ export class AppEditor { this.authService = AuthService.getInstance(); this.anonymousService = AnonymousService.getInstance(); this.navService = NavService.getInstance(); - this.busyService = BusyService.getInstance(); this.offlineService = OfflineService.getInstance(); this.fontsService = FontsService.getInstance(); } @@ -160,7 +155,9 @@ export class AppEditor { this.slidesFetched = true; }); - this.busySubscription = this.busyService.watchSlideEditable().subscribe(async (slide: HTMLElement) => { + busyStore.onChange('slideEditable', async (slide: HTMLElement | undefined) => { + console.log('yo', slide, slide !== undefined); + this.slidesEditable = true; await this.contentEditable(slide); @@ -186,10 +183,6 @@ export class AppEditor { await this.remoteEventsHandler.destroy(); - if (this.busySubscription) { - this.busySubscription.unsubscribe(); - } - store.reset(); } diff --git a/studio/src/app/services/editor/busy/busy.service.ts b/studio/src/app/services/editor/busy/busy.service.ts deleted file mode 100644 index 66508a4f9..000000000 --- a/studio/src/app/services/editor/busy/busy.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {Observable, Subject} from 'rxjs'; - -export class BusyService { - private deckBusySubject: Subject = new Subject(); - private slideEditableSubject: Subject = new Subject(); - - private static instance: BusyService; - - private constructor() { - // Private constructor, singleton - } - - static getInstance() { - if (!BusyService.instance) { - BusyService.instance = new BusyService(); - } - return BusyService.instance; - } - - watchDeckBusy(): Observable { - return this.deckBusySubject.asObservable(); - } - - deckBusy(busy: boolean) { - this.deckBusySubject.next(busy); - } - - watchSlideEditable(): Observable { - return this.slideEditableSubject.asObservable(); - } - - slideEditable(slide: HTMLElement) { - this.slideEditableSubject.next(slide); - } -} diff --git a/studio/src/app/stores/busy.store.ts b/studio/src/app/stores/busy.store.ts new file mode 100644 index 000000000..1cac53b27 --- /dev/null +++ b/studio/src/app/stores/busy.store.ts @@ -0,0 +1,13 @@ +import {createStore} from '@stencil/store'; + +interface BusyStore { + deckBusy: boolean | undefined; + slideEditable: HTMLElement | undefined; +} + +const {state, onChange} = createStore({ + deckBusy: undefined, + slideEditable: undefined, +} as BusyStore); + +export default {state, onChange}; From 9e181e190889f74c5ec6bacbaaa67b18b87fd6c7 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 16:31:08 +0200 Subject: [PATCH 07/32] feat(#773): use stencil/store for the nav --- studio/src/app/app-root.tsx | 22 +++-------- .../app/components/core/app-menu/app-menu.tsx | 21 +++++------ .../app-navigation-actions.tsx | 9 ++--- .../events/deck/deck-events.handler.tsx | 3 -- .../core/app-deck-delete/app-deck-delete.tsx | 16 +++----- .../core/app-user-delete/app-user-delete.tsx | 18 ++++----- .../core/app-dashboard/app-dashboard.tsx | 17 ++++----- .../pages/core/app-settings/app-settings.tsx | 13 +++---- .../app/pages/core/app-signin/app-signin.tsx | 19 ++++------ .../pages/editor/app-editor/app-editor.tsx | 24 +++++------- .../core/app-user-menu/app-user-menu.tsx | 19 +++++----- .../src/app/services/core/nav/nav.service.tsx | 37 ------------------- studio/src/app/stores/nav.store.ts | 22 +++++++++++ 13 files changed, 93 insertions(+), 147 deletions(-) delete mode 100644 studio/src/app/services/core/nav/nav.service.tsx create mode 100644 studio/src/app/stores/nav.store.ts diff --git a/studio/src/app/app-root.tsx b/studio/src/app/app-root.tsx index 144b3c51b..d3f8ebdda 100644 --- a/studio/src/app/app-root.tsx +++ b/studio/src/app/app-root.tsx @@ -2,16 +2,14 @@ import {Build, Component, Element, h, Listen, State} from '@stencil/core'; import {toastController} from '@ionic/core'; -import store from './stores/error.store'; - -import {Subscription} from 'rxjs'; +import errorStore from './stores/error.store'; +import navStore from './stores/nav.store'; import {AuthService} from './services/auth/auth.service'; -import {NavDirection, NavParams, NavService} from './services/core/nav/nav.service'; - import {ThemeService} from './services/theme/theme.service'; import {OfflineService} from './services/editor/offline/offline.service'; +import {NavDirection, NavParams} from './stores/nav.store'; @Component({ tag: 'app-root', @@ -22,9 +20,6 @@ export class AppRoot { private authService: AuthService; - private navSubscription: Subscription; - private navService: NavService; - private themeService: ThemeService; private offlineService: OfflineService; @@ -34,7 +29,6 @@ export class AppRoot { constructor() { this.authService = AuthService.getInstance(); - this.navService = NavService.getInstance(); this.themeService = ThemeService.getInstance(); this.offlineService = OfflineService.getInstance(); } @@ -50,23 +44,17 @@ export class AppRoot { async componentDidLoad() { this.loading = false; - store.onChange('error', (error: string | undefined) => { + errorStore.onChange('error', (error: string | undefined) => { if (error) { this.toastError(error); } }); - this.navSubscription = this.navService.watch().subscribe(async (params: NavParams) => { + navStore.onChange('nav', async (params: NavParams | undefined) => { await this.navigate(params); }); } - async componentDidUnload() { - if (this.navSubscription) { - this.navSubscription.unsubscribe(); - } - } - private async toastError(error: string) { const popover: HTMLIonToastElement = await toastController.create({ message: error, diff --git a/studio/src/app/components/core/app-menu/app-menu.tsx b/studio/src/app/components/core/app-menu/app-menu.tsx index e58aea550..1a075784b 100644 --- a/studio/src/app/components/core/app-menu/app-menu.tsx +++ b/studio/src/app/components/core/app-menu/app-menu.tsx @@ -2,17 +2,19 @@ import {Component, Element, State, h} from '@stencil/core'; import {Subscription} from 'rxjs'; +import store from '../../../stores/nav.store'; + import {AuthUser} from '../../../models/auth/auth.user'; import {Utils} from '../../../utils/core/utils'; import {AuthService} from '../../../services/auth/auth.service'; -import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; +import {NavDirection} from '../../../stores/nav.store'; @Component({ tag: 'app-menu', styleUrl: 'app-menu.scss', - shadow: false + shadow: false, }) export class AppMenu { @Element() el: HTMLElement; @@ -20,14 +22,11 @@ export class AppMenu { private authService: AuthService; private authSubscription: Subscription; - private navService: NavService; - @State() private authUser: AuthUser; constructor() { this.authService = AuthService.getInstance(); - this.navService = NavService.getInstance(); } componentWillLoad() { @@ -43,19 +42,19 @@ export class AppMenu { } private async signIn() { - this.navService.navigate({ + store.state.nav = { url: '/signin' + (window && window.location ? window.location.pathname : ''), - direction: NavDirection.FORWARD - }); + direction: NavDirection.FORWARD, + }; } private async signOut() { await this.authService.signOut(); - this.navService.navigate({ + store.state.nav = { url: '/', - direction: NavDirection.ROOT - }); + direction: NavDirection.ROOT, + }; } render() { diff --git a/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx b/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx index 7d494c4e9..71893bd13 100644 --- a/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx +++ b/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx @@ -5,6 +5,7 @@ import {popoverController} from '@ionic/core'; import {Subscription} from 'rxjs'; import state from '../../../stores/theme.store'; +import navStore, {NavDirection} from '../../../stores/nav.store'; import {AuthUser} from '../../../models/auth/auth.user'; import {User} from '../../../models/data/user'; @@ -12,7 +13,6 @@ import {User} from '../../../models/data/user'; import {Utils} from '../../../utils/core/utils'; import {AuthService} from '../../../services/auth/auth.service'; -import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; import {UserService} from '../../../services/data/user/user.service'; @Component({ @@ -28,8 +28,6 @@ export class AppNavigationActions { private authService: AuthService; private subscription: Subscription; - private navService: NavService; - private userService: UserService; private userSubscription: Subscription; @@ -46,7 +44,6 @@ export class AppNavigationActions { constructor() { this.authService = AuthService.getInstance(); - this.navService = NavService.getInstance(); this.userService = UserService.getInstance(); } @@ -82,10 +79,10 @@ export class AppNavigationActions { } private async navigateSignIn() { - this.navService.navigate({ + navStore.state.nav = { url: '/signin' + (window && window.location ? window.location.pathname : ''), direction: NavDirection.FORWARD, - }); + }; } render() { diff --git a/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx b/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx index 318bc0ccb..3b5590349 100644 --- a/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx +++ b/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx @@ -194,9 +194,6 @@ export class DeckEventsHandler { if (slide.getAttribute('slide_id')) { // !isNew - - console.log('yolo', slide, {...slide}); - busyStore.state.slideEditable = slide; resolve(); diff --git a/studio/src/app/modals/core/app-deck-delete/app-deck-delete.tsx b/studio/src/app/modals/core/app-deck-delete/app-deck-delete.tsx index 25ee80703..c73601475 100644 --- a/studio/src/app/modals/core/app-deck-delete/app-deck-delete.tsx +++ b/studio/src/app/modals/core/app-deck-delete/app-deck-delete.tsx @@ -1,10 +1,10 @@ import {Component, Element, Listen, Prop, h} from '@stencil/core'; -import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; +import navStore, {NavDirection} from '../../../stores/nav.store'; @Component({ tag: 'app-deck-delete', - styleUrl: 'app-deck-delete.scss' + styleUrl: 'app-deck-delete.scss', }) export class AppDeckDelete { @Element() el: HTMLElement; @@ -15,12 +15,8 @@ export class AppDeckDelete { @Prop() published: string; - private navService: NavService; - async componentDidLoad() { history.pushState({modal: true}, null); - - this.navService = NavService.getInstance(); } @Listen('popstate', {target: 'window'}) @@ -41,10 +37,10 @@ export class AppDeckDelete { private async navigateContact() { await this.closeModal(); - this.navService.navigate({ + navStore.state.nav = { url: '/contact', - direction: NavDirection.FORWARD - }); + direction: NavDirection.FORWARD, + }; } render() { @@ -71,7 +67,7 @@ export class AppDeckDelete { {this.renderNotePublished()} - + , ]; } diff --git a/studio/src/app/modals/core/app-user-delete/app-user-delete.tsx b/studio/src/app/modals/core/app-user-delete/app-user-delete.tsx index dccfdbe8a..259881660 100644 --- a/studio/src/app/modals/core/app-user-delete/app-user-delete.tsx +++ b/studio/src/app/modals/core/app-user-delete/app-user-delete.tsx @@ -1,10 +1,12 @@ import {Component, Element, Listen, Prop, State, h} from '@stencil/core'; + +import navStore, {NavDirection} from '../../../stores/nav.store'; + import {UserUtils} from '../../../utils/core/user-utils'; -import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; @Component({ tag: 'app-user-delete', - styleUrl: 'app-user-delete.scss' + styleUrl: 'app-user-delete.scss', }) export class AppUserDelete { @Element() el: HTMLElement; @@ -17,12 +19,8 @@ export class AppUserDelete { private inputUsername: string; - private navService: NavService; - async componentDidLoad() { history.pushState({modal: true}, null); - - this.navService = NavService.getInstance(); } @Listen('popstate', {target: 'window'}) @@ -55,10 +53,10 @@ export class AppUserDelete { private async navigateContact() { await this.closeModal(); - this.navService.navigate({ + navStore.state.nav = { url: '/contact', - direction: NavDirection.FORWARD - }); + direction: NavDirection.FORWARD, + }; } render() { @@ -99,7 +97,7 @@ export class AppUserDelete { Please note that currently, your presentations are not automatically removed from internet. If you wish to unpublish them, drop us a message on one of our this.navigateContact()}>contact channels.

- + , ]; } } diff --git a/studio/src/app/pages/core/app-dashboard/app-dashboard.tsx b/studio/src/app/pages/core/app-dashboard/app-dashboard.tsx index de54f951b..5998905f4 100644 --- a/studio/src/app/pages/core/app-dashboard/app-dashboard.tsx +++ b/studio/src/app/pages/core/app-dashboard/app-dashboard.tsx @@ -13,12 +13,12 @@ import {ParseSlidesUtils} from '../../../utils/editor/parse-slides.utils'; import {AuthService} from '../../../services/auth/auth.service'; import {DeckService} from '../../../services/data/deck/deck.service'; -import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; import {SlideService} from '../../../services/data/slide/slide.service'; import {DeckDashboardCloneResult, DeckDashboardService} from '../../../services/dashboard/deck/deck-dashboard.service'; import {ImageEventsHandler} from '../../../handlers/core/events/image/image-events.handler'; import {ChartEventsHandler} from '../../../handlers/core/events/chart/chart-events.handler'; +import navStore, {NavDirection} from '../../../stores/nav.store'; interface DeckAndFirstSlide { deck: Deck; @@ -42,8 +42,6 @@ export class AppDashboard { private authService: AuthService; - private navService: NavService; - private deckService: DeckService; private slideService: SlideService; @@ -54,7 +52,6 @@ export class AppDashboard { constructor() { this.authService = AuthService.getInstance(); - this.navService = NavService.getInstance(); this.deckService = DeckService.getInstance(); this.slideService = SlideService.getInstance(); this.deckDashboardService = DeckDashboardService.getInstance(); @@ -223,10 +220,10 @@ export class AppDashboard { } private async signIn() { - this.navService.navigate({ + navStore.state.nav = { url: '/signin' + (window && window.location ? window.location.pathname : ''), direction: NavDirection.FORWARD, - }); + }; } private async filterDecksOnChange(e: CustomEvent) { @@ -258,17 +255,17 @@ export class AppDashboard { const url: string = `/editor/${deck.deck.id}`; - this.navService.navigate({ + navStore.state.nav = { url: url, direction: NavDirection.ROOT, - }); + }; } private async navigateEditor() { - this.navService.navigate({ + navStore.state.nav = { url: '/editor', direction: NavDirection.ROOT, - }); + }; } private removeDeletedDeck($event: CustomEvent): Promise { diff --git a/studio/src/app/pages/core/app-settings/app-settings.tsx b/studio/src/app/pages/core/app-settings/app-settings.tsx index 847ec3bbd..49a755a3e 100644 --- a/studio/src/app/pages/core/app-settings/app-settings.tsx +++ b/studio/src/app/pages/core/app-settings/app-settings.tsx @@ -8,6 +8,7 @@ import '@firebase/auth'; import state from '../../../stores/theme.store'; import errorStore from '../../../stores/error.store'; +import navStore, {NavDirection} from '../../../stores/nav.store'; import {ApiUser} from '../../../models/api/api.user'; import {AuthUser} from '../../../models/auth/auth.user'; @@ -17,7 +18,6 @@ import {UserUtils} from '../../../utils/core/user-utils'; import {ApiUserService} from '../../../services/api/user/api.user.service'; import {AuthService} from '../../../services/auth/auth.service'; -import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; import {ImageHistoryService} from '../../../services/editor/image-history/image-history.service'; import {UserService} from '../../../services/data/user/user.service'; import {StorageService} from '../../../services/storage/storage.service'; @@ -57,8 +57,6 @@ export class AppHome { private userService: UserService; private apiUserService: ApiUserService; - private navService: NavService; - private imageHistoryService: ImageHistoryService; private profilePicture: File; @@ -88,7 +86,6 @@ export class AppHome { constructor() { this.authService = AuthService.getInstance(); this.apiUserService = ApiUserFactoryService.getInstance(); - this.navService = NavService.getInstance(); this.imageHistoryService = ImageHistoryService.getInstance(); this.userService = UserService.getInstance(); this.storageService = StorageService.getInstance(); @@ -148,10 +145,10 @@ export class AppHome { } private async signIn() { - this.navService.navigate({ + navStore.state.nav = { url: '/signin' + (window && window.location ? window.location.pathname : ''), direction: NavDirection.FORWARD, - }); + }; } @Listen('keydown') @@ -357,10 +354,10 @@ export class AppHome { await this.imageHistoryService.clear(); - this.navService.navigate({ + navStore.state.nav = { url: '/', direction: NavDirection.ROOT, - }); + }; await loading.dismiss(); diff --git a/studio/src/app/pages/core/app-signin/app-signin.tsx b/studio/src/app/pages/core/app-signin/app-signin.tsx index ffe380357..8d43b9fd9 100644 --- a/studio/src/app/pages/core/app-signin/app-signin.tsx +++ b/studio/src/app/pages/core/app-signin/app-signin.tsx @@ -5,17 +5,17 @@ import '@firebase/auth'; import {filter, take} from 'rxjs/operators'; -import store from '../../../stores/deck.store'; - import {del, get, set} from 'idb-keyval'; +import deckStore from '../../../stores/deck.store'; +import navStore, {NavDirection} from '../../../stores/nav.store'; + import {AuthUser} from '../../../models/auth/auth.user'; import {Utils} from '../../../utils/core/utils'; import {EnvironmentDeckDeckGoConfig} from '../../../services/core/environment/environment-config'; import {EnvironmentConfigService} from '../../../services/core/environment/environment-config.service'; -import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; import {AuthService} from '../../../services/auth/auth.service'; import {UserService} from '../../../services/data/user/user.service'; import {DeckService} from '../../../services/data/deck/deck.service'; @@ -36,8 +36,6 @@ export class AppSignIn { @State() private signInInProgress: boolean = false; - private navService: NavService; - private userService: UserService; private authService: AuthService; private deckService: DeckService; @@ -45,7 +43,6 @@ export class AppSignIn { private firebaseUser: firebase.User; constructor() { - this.navService = NavService.getInstance(); this.deckService = DeckService.getInstance(); this.userService = UserService.getInstance(); this.authService = AuthService.getInstance(); @@ -202,7 +199,7 @@ export class AppSignIn { .pipe(take(1)) .subscribe(async (authUser: AuthUser) => { await set('deckdeckgo_redirect_info', { - deckId: store.state.deck ? store.state.deck.id : null, + deckId: deckStore.state.deck ? deckStore.state.deck.id : null, userId: authUser ? authUser.uid : null, userToken: authUser ? authUser.token : null, anonymous: authUser ? authUser.anonymous : true, @@ -229,16 +226,16 @@ export class AppSignIn { const url: string = !redirectUrl || redirectUrl.trim() === '' || redirectUrl.trim() === '/' ? '/' : '/' + redirectUrl + (!mergeInfo || !mergeInfo.deckId || mergeInfo.deckId.trim() === '' || mergeInfo.deckId.trim() === '/' ? '' : '/' + mergeInfo.deckId); // Do not push a new page but reload as we might later face a DOM with contains two firebaseui which would not work - this.navService.navigate({ + navStore.state.nav = { url: url, direction: NavDirection.ROOT, - }); + }; } async navigateBack() { - this.navService.navigate({ + navStore.state.nav = { direction: NavDirection.BACK, - }); + }; } render() { diff --git a/studio/src/app/pages/editor/app-editor/app-editor.tsx b/studio/src/app/pages/editor/app-editor/app-editor.tsx index e7d677cda..92073da81 100644 --- a/studio/src/app/pages/editor/app-editor/app-editor.tsx +++ b/studio/src/app/pages/editor/app-editor/app-editor.tsx @@ -4,8 +4,9 @@ import {ItemReorderEventDetail, modalController, OverlayEventDetail} from '@ioni import {filter, take} from 'rxjs/operators'; -import store from '../../../stores/deck.store'; +import deckStore from '../../../stores/deck.store'; import busyStore from '../../../stores/busy.store'; +import navStore, {NavDirection} from '../../../stores/nav.store'; import {debounce, isFullscreen, isIOS, isMobile} from '@deckdeckgo/utils'; @@ -34,7 +35,6 @@ import {SlotUtils} from '../../../utils/editor/slot.utils'; import {AuthService} from '../../../services/auth/auth.service'; import {AnonymousService} from '../../../services/editor/anonymous/anonymous.service'; -import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; import {EnvironmentGoogleConfig} from '../../../services/core/environment/environment-config'; import {EnvironmentConfigService} from '../../../services/core/environment/environment-config.service'; @@ -79,7 +79,6 @@ export class AppEditor { private authService: AuthService; private anonymousService: AnonymousService; - private navService: NavService; private offlineService: OfflineService; @@ -103,7 +102,6 @@ export class AppEditor { constructor() { this.authService = AuthService.getInstance(); this.anonymousService = AnonymousService.getInstance(); - this.navService = NavService.getInstance(); this.offlineService = OfflineService.getInstance(); this.fontsService = FontsService.getInstance(); } @@ -156,8 +154,6 @@ export class AppEditor { }); busyStore.onChange('slideEditable', async (slide: HTMLElement | undefined) => { - console.log('yo', slide, slide !== undefined); - this.slidesEditable = true; await this.contentEditable(slide); @@ -183,7 +179,7 @@ export class AppEditor { await this.remoteEventsHandler.destroy(); - store.reset(); + deckStore.reset(); } async componentDidLoad() { @@ -267,17 +263,17 @@ export class AppEditor { } private async initDeckStyle() { - if (store.state.deck && store.state.deck.data && store.state.deck.data.attributes && store.state.deck.data.attributes.style) { - this.style = await convertStyle(store.state.deck.data.attributes.style); + if (deckStore.state.deck && deckStore.state.deck.data && deckStore.state.deck.data.attributes && deckStore.state.deck.data.attributes.style) { + this.style = await convertStyle(deckStore.state.deck.data.attributes.style); } else { this.style = undefined; } - if (store.state.deck && store.state.deck.data && store.state.deck.data.attributes && store.state.deck.data.attributes.transition) { - this.transition = store.state.deck.data.attributes.transition; + if (deckStore.state.deck && deckStore.state.deck.data && deckStore.state.deck.data.attributes && deckStore.state.deck.data.attributes.transition) { + this.transition = deckStore.state.deck.data.attributes.transition; } - this.background = await ParseBackgroundUtils.convertBackground(store.state.deck.data.background, true); + this.background = await ParseBackgroundUtils.convertBackground(deckStore.state.deck.data.background, true); const google: EnvironmentGoogleConfig = EnvironmentConfigService.getInstance().get('google'); await this.fontsService.loadGoogleFont(google.fontsUrl, this.style); @@ -571,10 +567,10 @@ export class AppEditor { @Listen('signIn', {target: 'document'}) async signIn() { - this.navService.navigate({ + navStore.state.nav = { url: '/signin' + (window && window.location ? window.location.pathname : ''), direction: NavDirection.FORWARD, - }); + }; } private contentEditable(slide: HTMLElement): Promise { diff --git a/studio/src/app/popovers/core/app-user-menu/app-user-menu.tsx b/studio/src/app/popovers/core/app-user-menu/app-user-menu.tsx index 38d09a416..b6e4e1f80 100644 --- a/studio/src/app/popovers/core/app-user-menu/app-user-menu.tsx +++ b/studio/src/app/popovers/core/app-user-menu/app-user-menu.tsx @@ -1,24 +1,23 @@ import {Component, Element, h} from '@stencil/core'; +import navStore, {NavDirection} from '../../../stores/nav.store'; + import {AuthService} from '../../../services/auth/auth.service'; -import {NavDirection, NavService} from '../../../services/core/nav/nav.service'; import {ImageHistoryService} from '../../../services/editor/image-history/image-history.service'; @Component({ tag: 'app-user-menu', - styleUrl: 'app-user-menu.scss' + styleUrl: 'app-user-menu.scss', }) export class AppUserMenu { @Element() el: HTMLElement; private authService: AuthService; - private navService: NavService; private imageHistoryService: ImageHistoryService; constructor() { this.authService = AuthService.getInstance(); - this.navService = NavService.getInstance(); this.imageHistoryService = ImageHistoryService.getInstance(); } @@ -28,10 +27,10 @@ export class AppUserMenu { await this.closePopover(); - this.navService.navigate({ + navStore.state.nav = { url: '/', - direction: NavDirection.ROOT - }); + direction: NavDirection.ROOT, + }; } async closePopover() { @@ -39,10 +38,10 @@ export class AppUserMenu { } private async navigateEditor() { - this.navService.navigate({ + navStore.state.nav = { url: '/editor', - direction: NavDirection.ROOT - }); + direction: NavDirection.ROOT, + }; await this.closePopover(); } diff --git a/studio/src/app/services/core/nav/nav.service.tsx b/studio/src/app/services/core/nav/nav.service.tsx deleted file mode 100644 index 6cc54c310..000000000 --- a/studio/src/app/services/core/nav/nav.service.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import {Observable, Subject} from 'rxjs'; - -export enum NavDirection { - FORWARD, - ROOT, - BACK -} - -export interface NavParams { - url?: string; - direction: NavDirection; -} - -export class NavService { - private navSubject: Subject = new Subject(); - - private static instance: NavService; - - private constructor() { - // Private constructor, singleton - } - - static getInstance() { - if (!NavService.instance) { - NavService.instance = new NavService(); - } - return NavService.instance; - } - - watch(): Observable { - return this.navSubject.asObservable(); - } - - navigate(params: NavParams) { - this.navSubject.next(params); - } -} diff --git a/studio/src/app/stores/nav.store.ts b/studio/src/app/stores/nav.store.ts new file mode 100644 index 000000000..adfc29ca9 --- /dev/null +++ b/studio/src/app/stores/nav.store.ts @@ -0,0 +1,22 @@ +import {createStore} from '@stencil/store'; + +export enum NavDirection { + FORWARD, + ROOT, + BACK, +} + +export interface NavParams { + url?: string; + direction: NavDirection; +} + +interface NavStore { + nav: NavParams | undefined; +} + +const {state, onChange} = createStore({ + nav: undefined, +} as NavStore); + +export default {state, onChange}; From 6d8a06da714d41f2bf08d46f0d3f19aee29917cb Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 16:38:38 +0200 Subject: [PATCH 08/32] feat(#773): use stencil/store for the poll --- .../src/app/pages/core/app-poll/app-poll.tsx | 40 +++++++------------ studio/src/app/services/poll/poll.service.tsx | 35 ++++++---------- studio/src/app/stores/poll.store.ts | 13 ++++++ 3 files changed, 39 insertions(+), 49 deletions(-) create mode 100644 studio/src/app/stores/poll.store.ts diff --git a/studio/src/app/pages/core/app-poll/app-poll.tsx b/studio/src/app/pages/core/app-poll/app-poll.tsx index 724abca04..abe4a264e 100644 --- a/studio/src/app/pages/core/app-poll/app-poll.tsx +++ b/studio/src/app/pages/core/app-poll/app-poll.tsx @@ -1,8 +1,7 @@ import {Component, h, Prop, State} from '@stencil/core'; -import {Subscription} from 'rxjs'; - -import store from '../../../stores/error.store'; +import errorStore from '../../../stores/error.store'; +import pollStore from '../../../stores/poll.store'; import {get, set} from 'idb-keyval'; @@ -18,9 +17,6 @@ export class AppPoll { @Prop({mutable: true}) pollKey: string; - @State() - private poll: DeckdeckgoPoll; - @State() private choice: string; @@ -37,18 +33,14 @@ export class AppPoll { private pollService: PollService; - private subscription: Subscription; - constructor() { this.pollService = PollService.getInstance(); } async componentWillLoad() { - this.subscription = this.pollService.watch().subscribe((poll: DeckdeckgoPoll) => { - this.poll = poll; - + pollStore.onChange('poll', (poll: DeckdeckgoPoll | undefined) => { if (this.pollKey && (!poll || poll === undefined)) { - store.state.error = 'Oopsie the poll was not found. Double check that the code is correct and try again.'; + errorStore.state.error = 'Oopsie the poll was not found. Double check that the code is correct and try again.'; } this.connecting = false; @@ -61,10 +53,6 @@ export class AppPoll { async componentDidUnload() { await this.pollService.disconnect(); - - if (this.subscription) { - this.subscription.unsubscribe(); - } } private async onChoiceChange($event: CustomEvent) { @@ -74,7 +62,7 @@ export class AppPoll { private async handleSubmit($event: Event) { $event.preventDefault(); - if (!this.poll || !this.poll.key) { + if (!pollStore.state.poll || !pollStore.state.poll.key) { return; } @@ -83,13 +71,13 @@ export class AppPoll { } try { - await this.pollService.vote(this.poll.key, this.choice); + await this.pollService.vote(pollStore.state.poll.key, this.choice); this.hasVoted = true; - await set(`deckdeckgo_poll_${this.poll.key}`, new Date().getTime()); + await set(`deckdeckgo_poll_${pollStore.state.poll.key}`, new Date().getTime()); } catch (err) { - store.state.error = err; + errorStore.state.error = err; } } @@ -130,7 +118,7 @@ export class AppPoll { private cancel() { this.pollKey = undefined; this.hasVoted = false; - this.poll = undefined; + pollStore.reset(); } render() { @@ -151,12 +139,12 @@ export class AppPoll { return undefined; } - if (!this.poll || !this.poll.poll) { + if (!pollStore.state.poll || !pollStore.state.poll.poll) { return undefined; } return [ -

{this.poll.poll.label}

, +

{pollStore.state.poll.poll.label}

,
this.handleSubmit(e)}> this.onChoiceChange($event)}>{this.renderPollChoices()} @@ -168,11 +156,11 @@ export class AppPoll { } private renderPollChoices() { - if (!this.poll.poll.values || this.poll.poll.values.length <= 0) { + if (!pollStore.state.poll.poll.values || pollStore.state.poll.poll.values.length <= 0) { return undefined; } - return this.poll.poll.values.map((choice: DeckdeckgoPollAnswer) => { + return pollStore.state.poll.poll.values.map((choice: DeckdeckgoPollAnswer) => { return ( {choice.label} @@ -187,7 +175,7 @@ export class AppPoll { return undefined; } - if (this.poll && this.poll.poll) { + if (pollStore.state.poll && pollStore.state.poll.poll) { return undefined; } diff --git a/studio/src/app/services/poll/poll.service.tsx b/studio/src/app/services/poll/poll.service.tsx index dc879c184..13c1e8bd3 100644 --- a/studio/src/app/services/poll/poll.service.tsx +++ b/studio/src/app/services/poll/poll.service.tsx @@ -1,7 +1,6 @@ import * as io from 'socket.io-client'; -import {BehaviorSubject, Observable} from 'rxjs'; -import {take} from 'rxjs/operators'; +import store from '../../stores/poll.store'; import {DeckdeckgoPoll} from '@deckdeckgo/types'; @@ -11,8 +10,6 @@ import {EnvironmentConfigService} from '../core/environment/environment-config.s export class PollService { private socket: SocketIOClient.Socket; - private poll: BehaviorSubject = new BehaviorSubject(undefined); - private static instance: PollService; private constructor() { @@ -40,7 +37,7 @@ export class PollService { reconnectionAttempts: 5, transports: ['websocket', 'xhr-polling'], query: 'type=app', - path: '/poll' + path: '/poll', }); this.socket.on('connect', async () => { @@ -48,7 +45,7 @@ export class PollService { }); this.socket.on('poll_desc', async (data: DeckdeckgoPoll) => { - this.poll.next(data); + store.state.poll = {...data}; }); resolve(); @@ -64,7 +61,7 @@ export class PollService { this.socket.emit('vote', { key: key, - answer: answer + answer: answer, }); resolve(); @@ -78,24 +75,16 @@ export class PollService { return; } - this.watch() - .pipe(take(1)) - .subscribe((poll: DeckdeckgoPoll) => { - if (poll) { - this.socket.emit('leave', { - key: poll.key - }); - } + if (store.state.poll) { + this.socket.emit('leave', { + key: store.state.poll.key, + }); + } - this.socket.removeAllListeners(); - this.socket.disconnect(); + this.socket.removeAllListeners(); + this.socket.disconnect(); - resolve(); - }); + resolve(); }); } - - watch(): Observable { - return this.poll.asObservable(); - } } diff --git a/studio/src/app/stores/poll.store.ts b/studio/src/app/stores/poll.store.ts new file mode 100644 index 000000000..5b40c64b7 --- /dev/null +++ b/studio/src/app/stores/poll.store.ts @@ -0,0 +1,13 @@ +import {createStore} from '@stencil/store'; + +import {DeckdeckgoPoll} from '@deckdeckgo/types'; + +interface PollStore { + poll: DeckdeckgoPoll | undefined; +} + +const {state, onChange, reset} = createStore({ + poll: undefined, +} as PollStore); + +export default {state, onChange, reset}; From 722a4021954a350f54b59eba4a7b3bf28c895566 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 16:42:13 +0200 Subject: [PATCH 09/32] refactor(#773): use export store for theme too --- .../app-navigation-actions/app-navigation-actions.tsx | 11 ++++++++--- .../src/app/pages/core/app-settings/app-settings.tsx | 8 ++++---- studio/src/app/services/theme/theme.service.tsx | 4 ++-- studio/src/app/stores/theme.store.ts | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx b/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx index 71893bd13..78d4a0e7b 100644 --- a/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx +++ b/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx @@ -4,7 +4,7 @@ import {popoverController} from '@ionic/core'; import {Subscription} from 'rxjs'; -import state from '../../../stores/theme.store'; +import themeStore from '../../../stores/theme.store'; import navStore, {NavDirection} from '../../../stores/nav.store'; import {AuthUser} from '../../../models/auth/auth.user'; @@ -142,7 +142,7 @@ export class AppNavigationActions { href="/editor" routerDirection="root" mode="md" - color={state.darkTheme ? 'light' : 'dark'}> + color={themeStore.state.darkTheme ? 'light' : 'dark'}> Write a presentation ); @@ -154,7 +154,12 @@ export class AppNavigationActions { private renderPublishButton() { if (this.publish) { return ( - this.actionPublish.emit()} mode="md" color={state.darkTheme ? 'light' : 'dark'}> + this.actionPublish.emit()} + mode="md" + color={themeStore.state.darkTheme ? 'light' : 'dark'}> Ready to share? ); diff --git a/studio/src/app/pages/core/app-settings/app-settings.tsx b/studio/src/app/pages/core/app-settings/app-settings.tsx index 49a755a3e..c797a145e 100644 --- a/studio/src/app/pages/core/app-settings/app-settings.tsx +++ b/studio/src/app/pages/core/app-settings/app-settings.tsx @@ -6,7 +6,7 @@ import {filter, take} from 'rxjs/operators'; import firebase from '@firebase/app'; import '@firebase/auth'; -import state from '../../../stores/theme.store'; +import themeStore from '../../../stores/theme.store'; import errorStore from '../../../stores/error.store'; import navStore, {NavDirection} from '../../../stores/nav.store'; @@ -488,7 +488,7 @@ export class AppHome { } async toggleTheme() { - await this.themeService.switch(!state.darkTheme); + await this.themeService.switch(!themeStore.state.darkTheme); } private renderDarkLightToggle() { @@ -497,9 +497,9 @@ export class AppHome { - {state.darkTheme ? 'Dark' : 'Light'} theme {state.darkTheme ? '🌑' : '☀️'} + {themeStore.state.darkTheme ? 'Dark' : 'Light'} theme {themeStore.state.darkTheme ? '🌑' : '☀️'} - this.toggleTheme()}> + this.toggleTheme()}> , ]; diff --git a/studio/src/app/services/theme/theme.service.tsx b/studio/src/app/services/theme/theme.service.tsx index 5ce465a59..9e8e12205 100644 --- a/studio/src/app/services/theme/theme.service.tsx +++ b/studio/src/app/services/theme/theme.service.tsx @@ -1,4 +1,4 @@ -import state from '../../stores/theme.store'; +import themeStore from '../../stores/theme.store'; import {get, set} from 'idb-keyval'; @@ -17,7 +17,7 @@ export class ThemeService { } async switch(dark: boolean) { - state.darkTheme = dark; + themeStore.state.darkTheme = dark; try { await set('deckdeckgo_dark_mode', dark); diff --git a/studio/src/app/stores/theme.store.ts b/studio/src/app/stores/theme.store.ts index 35999a30f..0114b74cf 100644 --- a/studio/src/app/stores/theme.store.ts +++ b/studio/src/app/stores/theme.store.ts @@ -13,4 +13,4 @@ onChange('darkTheme', (dark: boolean | undefined) => { dark ? domBodyClassList.add('dark') : domBodyClassList.remove('dark'); }); -export default state; +export default {state}; From 5799be9074b5241e9afdbacefeee2591b50990ec Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 16:51:00 +0200 Subject: [PATCH 10/32] feat(#773): use vanilla debounce --- .../events/deck/deck-events.handler.tsx | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx b/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx index 3b5590349..1daa0b292 100644 --- a/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx +++ b/studio/src/app/handlers/editor/events/deck/deck-events.handler.tsx @@ -1,7 +1,8 @@ import {ItemReorderEventDetail} from '@ionic/core'; -import {Subject, Subscription} from 'rxjs'; -import {debounceTime, filter, take} from 'rxjs/operators'; +import {filter, take} from 'rxjs/operators'; + +import {debounce} from '@deckdeckgo/utils'; import deckStore from '../../../../stores/deck.store'; import errorStore from '../../../../stores/error.store'; @@ -30,20 +31,27 @@ export class DeckEventsHandler { private authService: AuthService; - private updateSlideSubscription: Subscription; - private updateSlideSubject: Subject = new Subject(); - - private updateDeckTitleSubscription: Subscription; - private updateDeckTitleSubject: Subject = new Subject(); - private deckService: DeckService; private slideService: SlideService; + private readonly debounceUpdateSlide: (slide: HTMLElement) => void; + private readonly debounceUpdateDeckTitle: (title: string) => void; + constructor() { this.authService = AuthService.getInstance(); this.deckService = DeckService.getInstance(); this.slideService = SlideService.getInstance(); + + this.debounceUpdateSlide = debounce(async (element: HTMLElement) => { + await this.updateSlide(element); + + await this.emitSlideDidUpdate(element); + }, 500); + + this.debounceUpdateDeckTitle = debounce(async (title: string) => { + await this.updateDeckTitle(title); + }, 500); } init(el: HTMLElement): Promise { @@ -66,16 +74,6 @@ export class DeckEventsHandler { document.addEventListener('deckDidChange', this.onDeckChange, false); } - this.updateSlideSubscription = this.updateSlideSubject.pipe(debounceTime(500)).subscribe(async (element: HTMLElement) => { - await this.updateSlide(element); - - await this.emitSlideDidUpdate(element); - }); - - this.updateDeckTitleSubscription = this.updateDeckTitleSubject.pipe(debounceTime(500)).subscribe(async (title: string) => { - await this.updateDeckTitle(title); - }); - resolve(); }); } @@ -96,14 +94,6 @@ export class DeckEventsHandler { if (document) { document.removeEventListener('deckDidChange', this.onDeckChange, true); } - - if (this.updateSlideSubscription) { - this.updateSlideSubscription.unsubscribe(); - } - - if (this.updateDeckTitleSubscription) { - this.updateDeckTitleSubscription.unsubscribe(); - } } private onSlideDidLoad = async ($event: CustomEvent) => { @@ -132,7 +122,7 @@ export class DeckEventsHandler { return; } - this.updateSlideSubject.next($event.detail); + this.debounceUpdateSlide($event.detail); }; private onCustomEventChange = async ($event: CustomEvent) => { @@ -148,7 +138,7 @@ export class DeckEventsHandler { return; } - this.updateSlideSubject.next(parent); + this.debounceUpdateSlide(parent); }; private onInputChange = async ($event: Event) => { @@ -168,11 +158,11 @@ export class DeckEventsHandler { return; } - this.updateSlideSubject.next(parent); + this.debounceUpdateSlide(parent); // The first content editable element on the first slide is the title of the presentation if (parent && !parent.previousElementSibling && !element.previousElementSibling) { - this.updateDeckTitleSubject.next(element.textContent); + this.debounceUpdateDeckTitle(element.textContent); } }; From d0ad6ee8f213fcafa4ee0a2b3a49147b48da72c0 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 16:55:40 +0200 Subject: [PATCH 11/32] feat(#773): use stencil/store for publish progression --- .../app-publish-edit/app-publish-edit.tsx | 16 ++-------- .../editor/publish/publish.service.tsx | 32 ++++++++----------- studio/src/app/stores/publish.store.ts | 11 +++++++ 3 files changed, 26 insertions(+), 33 deletions(-) create mode 100644 studio/src/app/stores/publish.store.ts diff --git a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx index a919266cf..cb875912b 100644 --- a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx +++ b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx @@ -6,6 +6,7 @@ import {debounceTime, filter, take} from 'rxjs/operators'; import deckStore from '../../../../stores/deck.store'; import errorStore from '../../../../stores/error.store'; import feedStore from '../../../../stores/feed.store'; +import publishStore from '../../../../stores/publish.store'; import {Deck} from '../../../../models/data/deck'; @@ -59,11 +60,6 @@ export class AppPublishEdit { private publishService: PublishService; - @State() - private progress: number = 0; - - private progressSubscription: Subscription; - constructor() { this.deckService = DeckService.getInstance(); @@ -75,10 +71,6 @@ export class AppPublishEdit { async componentWillLoad() { await this.init(); - this.progressSubscription = this.publishService.watchProgress().subscribe((progress: number) => { - this.progress = progress; - }); - this.apiUserService .watch() .pipe( @@ -118,10 +110,6 @@ export class AppPublishEdit { if (this.updateDeckSubscription) { this.updateDeckSubscription.unsubscribe(); } - - if (this.progressSubscription) { - this.progressSubscription.unsubscribe(); - } } private getFirstSlideContent(): Promise { @@ -437,7 +425,7 @@ export class AppPublishEdit { } else { return (
- + Hang on, we are publishing your presentation
); diff --git a/studio/src/app/services/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index 0e1a2d2d0..666f00c55 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -1,10 +1,10 @@ import * as firebase from 'firebase/app'; import 'firebase/firestore'; -import {Observable, Subject} from 'rxjs'; import {take} from 'rxjs/operators'; -import store from '../../../stores/deck.store'; +import deckStore from '../../../stores/deck.store'; +import publishStore from '../../../stores/publish.store'; import {Deck, DeckMetaAuthor} from '../../../models/data/deck'; import {ApiDeck} from '../../../models/api/api.deck'; @@ -37,8 +37,6 @@ export class PublishService { private fontsService: FontsService; - private progressSubject: Subject = new Subject(); - private constructor() { this.apiPresentationService = ApiPresentationFactoryService.getInstance(); @@ -57,16 +55,12 @@ export class PublishService { return PublishService.instance; } - watchProgress(): Observable { - return this.progressSubject.asObservable(); - } - private progress(progress: number) { - this.progressSubject.next(progress); + publishStore.state.progress = progress; } private progressComplete() { - this.progressSubject.next(1); + publishStore.state.progress = 1; } // TODO: Move in a cloud functions? @@ -75,17 +69,17 @@ export class PublishService { this.progress(0); try { - if (!store.state.deck || !store.state.deck.id || !store.state.deck.data) { + if (!deckStore.state.deck || !deckStore.state.deck.id || !deckStore.state.deck.data) { this.progressComplete(); reject('No deck found'); return; } - const apiDeck: ApiDeck = await this.convertDeck(store.state.deck, description); + const apiDeck: ApiDeck = await this.convertDeck(deckStore.state.deck, description); this.progress(0.25); - const apiDeckPublish: ApiPresentation = await this.publishDeck(store.state.deck, apiDeck); + const apiDeckPublish: ApiPresentation = await this.publishDeck(deckStore.state.deck, apiDeck); this.progress(0.5); @@ -97,19 +91,19 @@ export class PublishService { this.progress(0.75); - const newApiId: boolean = store.state.deck.data.api_id !== apiDeckPublish.id; + const newApiId: boolean = deckStore.state.deck.data.api_id !== apiDeckPublish.id; if (newApiId) { - store.state.deck.data.api_id = apiDeckPublish.id; + deckStore.state.deck.data.api_id = apiDeckPublish.id; - const updatedDeck: Deck = await this.deckService.update(store.state.deck); - store.state.deck = {...updatedDeck}; + const updatedDeck: Deck = await this.deckService.update(deckStore.state.deck); + deckStore.state.deck = {...updatedDeck}; } this.progress(0.8); const publishedUrl: string = apiDeckPublish.url; - await this.delayUpdateMeta(store.state.deck, publishedUrl, description, tags, newApiId); + await this.delayUpdateMeta(deckStore.state.deck, publishedUrl, description, tags, newApiId); resolve(publishedUrl); } catch (err) { @@ -368,7 +362,7 @@ export class PublishService { return new Promise(async (resolve, reject) => { try { const freshDeck: Deck = await this.deckService.get(deckId); - store.state.deck = {...freshDeck}; + deckStore.state.deck = {...freshDeck}; resolve(); } catch (err) { diff --git a/studio/src/app/stores/publish.store.ts b/studio/src/app/stores/publish.store.ts new file mode 100644 index 000000000..47e5fcea2 --- /dev/null +++ b/studio/src/app/stores/publish.store.ts @@ -0,0 +1,11 @@ +import {createStore} from '@stencil/store'; + +interface PublishStore { + progress: number | undefined; +} + +const {state} = createStore({ + progress: undefined, +} as PublishStore); + +export default {state}; From 2d0778e88f42c06eb4f0798c03a7b4fa2fa5c21f Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 16:58:28 +0200 Subject: [PATCH 12/32] feat(#773): use vanilla debounce --- .../app-publish-edit/app-publish-edit.tsx | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx index cb875912b..aff48edca 100644 --- a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx +++ b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx @@ -1,7 +1,6 @@ import {Component, Event, EventEmitter, h, State} from '@stencil/core'; -import {Subject, Subscription} from 'rxjs'; -import {debounceTime, filter, take} from 'rxjs/operators'; +import {filter, take} from 'rxjs/operators'; import deckStore from '../../../../stores/deck.store'; import errorStore from '../../../../stores/error.store'; @@ -17,6 +16,7 @@ import {ApiUser} from '../../../../models/api/api.user'; import {ApiUserService} from '../../../../services/api/user/api.user.service'; import {PublishService} from '../../../../services/editor/publish/publish.service'; import {ApiUserFactoryService} from '../../../../services/api/user/api.user.factory.service'; +import {debounce} from '@deckdeckgo/utils'; interface CustomInputEvent extends KeyboardEvent { data: string | null; @@ -50,8 +50,7 @@ export class AppPublishEdit { private deckService: DeckService; - private updateDeckSubscription: Subscription; - private updateDeckSubject: Subject = new Subject(); + private readonly debounceUpdateDeck: () => void; @Event() private published: EventEmitter; @@ -66,6 +65,10 @@ export class AppPublishEdit { this.apiUserService = ApiUserFactoryService.getInstance(); this.publishService = PublishService.getInstance(); + + this.debounceUpdateDeck = debounce(async () => { + await this.updateDeck(); + }, 500); } async componentWillLoad() { @@ -80,13 +83,6 @@ export class AppPublishEdit { .subscribe(async (apiUser: ApiUser) => { this.apiUser = apiUser; }); - - this.updateDeckSubscription = this.updateDeckSubject - .asObservable() - .pipe(debounceTime(500)) - .subscribe(async () => { - await this.updateDeck(); - }); } componentDidLoad() { @@ -106,12 +102,6 @@ export class AppPublishEdit { this.tags = deckStore.state.deck.data.meta && deckStore.state.deck.data.meta.tags ? (deckStore.state.deck.data.meta.tags as string[]) : []; } - componentDidUnload() { - if (this.updateDeckSubscription) { - this.updateDeckSubscription.unsubscribe(); - } - } - private getFirstSlideContent(): Promise { return new Promise(async (resolve) => { let content: string = ''; @@ -206,7 +196,7 @@ export class AppPublishEdit { this.caption = title; - this.updateDeckSubject.next(); + this.debounceUpdateDeck(); resolve(); }); From 23ce65c4b5929f760c3a78ab11e2f6acbdfdd6f4 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 17:06:43 +0200 Subject: [PATCH 13/32] feat(#773): use vanilla debounce --- .../app-actions-element.tsx | 28 ++++++++----------- .../app-publish-edit/app-publish-edit.tsx | 3 +- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/studio/src/app/components/editor/actions/element/app-actions-element/app-actions-element.tsx b/studio/src/app/components/editor/actions/element/app-actions-element/app-actions-element.tsx index 141e4a939..e3e01878b 100644 --- a/studio/src/app/components/editor/actions/element/app-actions-element/app-actions-element.tsx +++ b/studio/src/app/components/editor/actions/element/app-actions-element/app-actions-element.tsx @@ -1,10 +1,7 @@ import {Component, Element, Event, EventEmitter, h, Listen, Method, Prop, State} from '@stencil/core'; import {modalController, OverlayEventDetail, popoverController} from '@ionic/core'; -import {Subject, Subscription} from 'rxjs'; -import {debounceTime} from 'rxjs/operators'; - -import {isFullscreen, isIOS, isMobile} from '@deckdeckgo/utils'; +import {debounce, isFullscreen, isIOS, isMobile} from '@deckdeckgo/utils'; import store from '../../../../../stores/busy.store'; @@ -72,8 +69,7 @@ export class AppActionsElement { private elementResizeObserver: ResizeObserverConstructor; - private moveToolbarSubscription: Subscription; - private moveToolbarSubject: Subject = new Subject(); + private readonly debounceResizeSlideContent: () => void; @Event() signIn: EventEmitter; @@ -82,11 +78,13 @@ export class AppActionsElement { @Event() private resetted: EventEmitter; - async componentWillLoad() { - this.moveToolbarSubscription = this.moveToolbarSubject.pipe(debounceTime(250)).subscribe(async () => { + constructor() { + this.debounceResizeSlideContent = debounce(async () => { await this.resizeSlideContent(); - }); + }, 250); + } + async componentWillLoad() { this.imageHelper = new ImageHelper(this.slideDidChange, this.blockSlide, this.signIn); this.shapeHelper = new ShapeHelper(this.slideDidChange, this.signIn); } @@ -96,22 +94,18 @@ export class AppActionsElement { } async componentDidUnload() { - if (this.moveToolbarSubscription) { - this.moveToolbarSubscription.unsubscribe(); - } - this.removeWindowResize(); } private initWindowResize() { if (window) { - window.addEventListener('resize', () => this.moveToolbarSubject.next(), {passive: true}); + window.addEventListener('resize', () => this.debounceResizeSlideContent(), {passive: true}); } } private removeWindowResize() { if (window) { - window.removeEventListener('resize', () => this.moveToolbarSubject.next(), true); + window.removeEventListener('resize', () => this.debounceResizeSlideContent(), true); } } @@ -782,7 +776,7 @@ export class AppActionsElement { this.elementResizeObserver.observe(this.selectedElement); } else { // Fallback, better than nothing. It won't place the toolbar if the size on enter or delete but at least if the content change like if list is toggled - this.selectedElement.addEventListener('focusout', () => this.moveToolbarSubject.next(), {passive: true}); + this.selectedElement.addEventListener('focusout', () => this.debounceResizeSlideContent(), {passive: true}); } resolve(); @@ -797,7 +791,7 @@ export class AppActionsElement { this.elementResizeObserver.disconnect; } } else { - this.selectedElement.removeEventListener('focusout', () => this.moveToolbarSubject.next(), true); + this.selectedElement.removeEventListener('focusout', () => this.debounceResizeSlideContent(), true); } resolve(); diff --git a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx index aff48edca..119c61448 100644 --- a/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx +++ b/studio/src/app/components/editor/publish/app-publish-edit/app-publish-edit.tsx @@ -2,6 +2,8 @@ import {Component, Event, EventEmitter, h, State} from '@stencil/core'; import {filter, take} from 'rxjs/operators'; +import {debounce} from '@deckdeckgo/utils'; + import deckStore from '../../../../stores/deck.store'; import errorStore from '../../../../stores/error.store'; import feedStore from '../../../../stores/feed.store'; @@ -16,7 +18,6 @@ import {ApiUser} from '../../../../models/api/api.user'; import {ApiUserService} from '../../../../services/api/user/api.user.service'; import {PublishService} from '../../../../services/editor/publish/publish.service'; import {ApiUserFactoryService} from '../../../../services/api/user/api.user.factory.service'; -import {debounce} from '@deckdeckgo/utils'; interface CustomInputEvent extends KeyboardEvent { data: string | null; From b3939164fb7b378eb0289747552a9af546c0c7c5 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 17:34:53 +0200 Subject: [PATCH 14/32] feat(#773): use stencil/store for offline state --- .../core/app-navigation/app-navigation.tsx | 46 +++-------- .../app-actions-deck/app-actions-deck.tsx | 26 ++----- .../offline/app-go-offline/app-go-offline.tsx | 26 +------ .../offline/app-go-online/app-go-online.tsx | 26 +------ .../app/components/feed/app-feed/app-feed.tsx | 34 ++++----- .../editor/offline/offline.service.tsx | 76 +++++++------------ studio/src/app/stores/offline.store.ts | 13 ++++ 7 files changed, 78 insertions(+), 169 deletions(-) create mode 100644 studio/src/app/stores/offline.store.ts diff --git a/studio/src/app/components/core/app-navigation/app-navigation.tsx b/studio/src/app/components/core/app-navigation/app-navigation.tsx index e7b58311b..7eb9d3751 100644 --- a/studio/src/app/components/core/app-navigation/app-navigation.tsx +++ b/studio/src/app/components/core/app-navigation/app-navigation.tsx @@ -1,11 +1,9 @@ -import {Component, Prop, h, State} from '@stencil/core'; +import {Component, Prop, h} from '@stencil/core'; -import {Subscription} from 'rxjs'; +import offlineStore from '../../../stores/offline.store'; import store from '../../../stores/deck.store'; -import {OfflineService} from '../../../services/editor/offline/offline.service'; - @Component({ tag: 'app-navigation', styleUrl: 'app-navigation.scss', @@ -19,31 +17,9 @@ export class AppNavigation { @Prop() presentation: boolean = false; @Prop() publish: boolean = false; - private offlineSubscription: Subscription; - private offlineService: OfflineService; - - @State() - private offline: OfflineDeck = undefined; - - constructor() { - this.offlineService = OfflineService.getInstance(); - } - - componentWillLoad() { - this.offlineSubscription = this.offlineService.watchOffline().subscribe((offline: OfflineDeck | undefined) => { - this.offline = offline; - }); - } - - componentDidUnload() { - if (this.offlineSubscription) { - this.offlineSubscription.unsubscribe(); - } - } - render() { return ( - + {this.renderTitleOnline()} {this.renderTitleOffline()} @@ -56,7 +32,7 @@ export class AppNavigation { } private renderTitleOnline() { - if (this.offline !== undefined) { + if (offlineStore.state.offline !== undefined) { return undefined; } @@ -74,17 +50,17 @@ export class AppNavigation { } private renderTitleOffline() { - if (this.offline === undefined) { + if (offlineStore.state.offline === undefined) { return undefined; } return (
- +
{this.renderLogo()} - {this.offline.name} + {offlineStore.state.offline.name}
@@ -120,7 +96,7 @@ export class AppNavigation { } private renderMenuToggle() { - if (this.offline !== undefined) { + if (offlineStore.state.offline !== undefined) { return undefined; } @@ -140,7 +116,7 @@ export class AppNavigation { } private renderUser() { - if (this.offline !== undefined) { + if (offlineStore.state.offline !== undefined) { return undefined; } @@ -156,12 +132,12 @@ export class AppNavigation { } private renderInfoOffline() { - if (this.offline === undefined) { + if (offlineStore.state.offline === undefined) { return undefined; } return ( - + You are editing offline. ); diff --git a/studio/src/app/components/editor/actions/deck/app-actions-deck/app-actions-deck.tsx b/studio/src/app/components/editor/actions/deck/app-actions-deck/app-actions-deck.tsx index 2442c444d..2edc7d6d2 100644 --- a/studio/src/app/components/editor/actions/deck/app-actions-deck/app-actions-deck.tsx +++ b/studio/src/app/components/editor/actions/deck/app-actions-deck/app-actions-deck.tsx @@ -9,6 +9,8 @@ import {get, set} from 'idb-keyval'; import {forkJoin, Subscription} from 'rxjs'; import {take} from 'rxjs/operators'; +import offlineStore from '../../../../../stores/offline.store'; + import {SlideAttributes, SlideSplitType, SlideTemplate} from '../../../../../models/data/slide'; import {MoreAction} from '../../../../../utils/editor/more-action'; @@ -17,7 +19,6 @@ import {CreateSlidesUtils} from '../../../../../utils/editor/create-slides.utils import {DemoAction} from '../../../../../utils/editor/demo-action'; import {AnonymousService} from '../../../../../services/editor/anonymous/anonymous.service'; -import {OfflineService} from '../../../../../services/editor/offline/offline.service'; import {RemoteService} from '../../../../../services/editor/remote/remote.service'; import {PlaygroundAction} from '../../../../../utils/editor/playground-action'; @@ -67,30 +68,19 @@ export class AppActionsDeck { @State() private fullscreenEnable: boolean = true; - @State() - private offline: boolean = false; - private anonymousService: AnonymousService; - private offlineService: OfflineService; - private offlineSubscription: Subscription; - private remoteService: RemoteService; private remoteSubscription: Subscription; constructor() { this.anonymousService = AnonymousService.getInstance(); - this.offlineService = OfflineService.getInstance(); this.remoteService = RemoteService.getInstance(); } async componentWillLoad() { this.fullscreenEnable = !isIPad(); - this.offlineSubscription = this.offlineService.watchOffline().subscribe((status: OfflineDeck | undefined) => { - this.offline = status !== undefined; - }); - this.remoteSubscription = this.remoteService.watchRequests().subscribe(async (requests: DeckdeckgoEventDeckRequest[] | undefined) => { if (requests && requests.length > 0) { await this.clickToOpenRemote(); @@ -99,10 +89,6 @@ export class AppActionsDeck { } async componentDidUnload() { - if (this.offlineSubscription) { - this.offlineSubscription.unsubscribe(); - } - if (this.remoteSubscription) { this.remoteSubscription.unsubscribe(); } @@ -392,7 +378,7 @@ export class AppActionsDeck { const popover: HTMLIonPopoverElement = await popoverController.create({ component: 'app-more-deck-actions', componentProps: { - offline: this.offline, + offline: offlineStore.state.offline, }, event: $event, mode: 'ios', @@ -473,7 +459,7 @@ export class AppActionsDeck { const modal: HTMLIonModalElement = await modalController.create({ component: 'app-offline', componentProps: { - offline: this.offline, + offline: offlineStore.state.offline, }, cssClass: 'fullscreen', }); @@ -576,8 +562,8 @@ export class AppActionsDeck { this.openEmbed()}> this.goOnlineOffline()} color="primary" class="wider-devices" mode="md"> - - {this.offline ? Go online : Go offline} + + {offlineStore.state.offline ? Go online : Go offline} diff --git a/studio/src/app/components/editor/offline/app-go-offline/app-go-offline.tsx b/studio/src/app/components/editor/offline/app-go-offline/app-go-offline.tsx index ddd20b0c6..6917fbf40 100644 --- a/studio/src/app/components/editor/offline/app-go-offline/app-go-offline.tsx +++ b/studio/src/app/components/editor/offline/app-go-offline/app-go-offline.tsx @@ -1,8 +1,7 @@ import {Component, h, State, Event, EventEmitter} from '@stencil/core'; -import store from '../../../../stores/error.store'; - -import {Subscription} from 'rxjs'; +import errorStore from '../../../../stores/error.store'; +import offlineStore from '../../../../stores/offline.store'; import {OfflineService} from '../../../../services/editor/offline/offline.service'; @@ -19,29 +18,12 @@ export class AppGoOffline { @Event() private inProgress: EventEmitter; - @State() - private progress: number = 0; - private offlineService: OfflineService; - private progressSubscription: Subscription; - constructor() { this.offlineService = OfflineService.getInstance(); } - componentWillLoad() { - this.progressSubscription = this.offlineService.watchProgress().subscribe((progress: number) => { - this.progress = progress; - }); - } - - componentDidUnload() { - if (this.progressSubscription) { - this.progressSubscription.unsubscribe(); - } - } - private async goOffline() { this.goingOffline = true; this.inProgress.emit(true); @@ -53,7 +35,7 @@ export class AppGoOffline { } catch (err) { this.goingOffline = false; this.inProgress.emit(false); - store.state.error = 'Apologies, something went wrong and app was unable to go offline.'; + errorStore.state.error = 'Apologies, something went wrong and app was unable to go offline.'; } } @@ -83,7 +65,7 @@ export class AppGoOffline { } else { return (
- + Hang on, we are gathering the content.
); diff --git a/studio/src/app/components/editor/offline/app-go-online/app-go-online.tsx b/studio/src/app/components/editor/offline/app-go-online/app-go-online.tsx index 0a514f410..acb2cce12 100644 --- a/studio/src/app/components/editor/offline/app-go-online/app-go-online.tsx +++ b/studio/src/app/components/editor/offline/app-go-online/app-go-online.tsx @@ -1,8 +1,7 @@ import {Component, h, State, Event, EventEmitter} from '@stencil/core'; -import {Subscription} from 'rxjs'; - -import store from '../../../../stores/error.store'; +import errorStore from '../../../../stores/error.store'; +import offlineStore from '../../../../stores/offline.store'; import {OfflineService} from '../../../../services/editor/offline/offline.service'; @@ -22,29 +21,12 @@ export class AppGoOnline { @Event() private inProgress: EventEmitter; - @State() - private progress: number = 0; - private offlineService: OfflineService; - private progressSubscription: Subscription; - constructor() { this.offlineService = OfflineService.getInstance(); } - componentWillLoad() { - this.progressSubscription = this.offlineService.watchProgress().subscribe((progress: number) => { - this.progress = progress; - }); - } - - componentDidUnload() { - if (this.progressSubscription) { - this.progressSubscription.unsubscribe(); - } - } - private async goOnline() { this.goingOnline = true; this.inProgress.emit(true); @@ -59,7 +41,7 @@ export class AppGoOnline { } catch (err) { this.goingOnline = false; this.inProgress.emit(false); - store.state.error = 'Something went wrong. Double check your internet connection and try again. If it still does not work, contact us!'; + errorStore.state.error = 'Something went wrong. Double check your internet connection and try again. If it still does not work, contact us!'; } } @@ -108,7 +90,7 @@ export class AppGoOnline { } else { return (
- + Hang on still, we are uploading the content.
); diff --git a/studio/src/app/components/feed/app-feed/app-feed.tsx b/studio/src/app/components/feed/app-feed/app-feed.tsx index 1d17c3524..2717edf74 100644 --- a/studio/src/app/components/feed/app-feed/app-feed.tsx +++ b/studio/src/app/components/feed/app-feed/app-feed.tsx @@ -1,17 +1,15 @@ import {Component, h, State, Host} from '@stencil/core'; -import {Subscription} from 'rxjs'; - import {isMobile} from '@deckdeckgo/utils'; -import store from '../../../stores/feed.store'; +import feedStore from '../../../stores/feed.store'; +import offlineStore from '../../../stores/offline.store'; import {Deck} from '../../../models/data/deck'; import {EnvironmentConfigService} from '../../../services/core/environment/environment-config.service'; import {FeedService} from '../../../services/data/feed/feed.service'; -import {OfflineService} from '../../../services/editor/offline/offline.service'; @Component({ tag: 'app-feed', @@ -29,30 +27,26 @@ export class AppFeed { private presentationUrl: string = EnvironmentConfigService.getInstance().get('deckdeckgo').presentationUrl; - private offlineSubscription: Subscription; - private offlineService: OfflineService; - constructor() { this.feedService = FeedService.getInstance(); - this.offlineService = OfflineService.getInstance(); } async componentWillLoad() { - this.offlineSubscription = this.offlineService.watchOffline().subscribe((offline: OfflineDeck | undefined) => { - this.offline = navigator && !navigator.onLine && offline !== undefined ? offline : undefined; + this.initOffline(offlineStore.state.offline); + + offlineStore.onChange('offline', (offline: OfflineDeck | undefined) => { + this.initOffline(offline); }); this.mobile = isMobile(); } - async componentDidLoad() { - await this.feedService.find(); + private initOffline(offline: OfflineDeck | undefined) { + this.offline = navigator && !navigator.onLine && offline !== undefined ? offline : undefined; } - async componentDidUnload() { - if (this.offlineSubscription) { - this.offlineSubscription.unsubscribe(); - } + async componentDidLoad() { + await this.feedService.find(); } private async findNextDecks($event) { @@ -97,7 +91,7 @@ export class AppFeed {
{this.renderDecks()} - this.findNextDecks($event)} disabled={store.state.lastPageReached}> + this.findNextDecks($event)} disabled={feedStore.state.lastPageReached}>
, @@ -120,8 +114,8 @@ export class AppFeed { } private renderDecks() { - if (store.state.decks && store.state.decks.length > 0) { - return store.state.decks.map((deck: Deck, i: number) => { + if (feedStore.state.decks && feedStore.state.decks.length > 0) { + return feedStore.state.decks.map((deck: Deck, i: number) => { return ( 0} deck={deck}> @@ -134,7 +128,7 @@ export class AppFeed { } private renderLoading() { - if (store.state.decks !== undefined) { + if (feedStore.state.decks !== undefined) { return undefined; } else { return ( diff --git a/studio/src/app/services/editor/offline/offline.service.tsx b/studio/src/app/services/editor/offline/offline.service.tsx index bc3366e0a..aae748cce 100644 --- a/studio/src/app/services/editor/offline/offline.service.tsx +++ b/studio/src/app/services/editor/offline/offline.service.tsx @@ -2,10 +2,8 @@ import * as firebase from 'firebase/app'; import {del, get, set} from 'idb-keyval'; -import {BehaviorSubject, Observable} from 'rxjs'; -import {take} from 'rxjs/operators'; - -import store from '../../../stores/deck.store'; +import deckStore from '../../../stores/deck.store'; +import offlineStore from '../../../stores/offline.store'; import {Deck} from '../../../models/data/deck'; import {Slide} from '../../../models/data/slide'; @@ -34,10 +32,6 @@ export class OfflineService { private assetsService: AssetsService; private fontsService: FontsService; - private offlineSubject: BehaviorSubject = new BehaviorSubject(undefined); - - private progressSubject: BehaviorSubject = new BehaviorSubject(0); - private constructor() { this.deckOnlineService = DeckOnlineService.getInstance(); this.slideOnlineService = SlideOnlineService.getInstance(); @@ -55,49 +49,31 @@ export class OfflineService { } async status(): Promise { - return new Promise((resolve) => { - this.offlineSubject.pipe(take(1)).subscribe(async (offline: OfflineDeck | undefined) => { - if (offline === undefined) { - const saved: OfflineDeck = await get('deckdeckgo_offline'); + if (offlineStore.state.offline === undefined) { + const saved: OfflineDeck = await get('deckdeckgo_offline'); - this.offlineSubject.next(saved); + offlineStore.state.offline = saved ? {...saved} : undefined; - resolve(saved); - return; - } + return saved; + } - resolve(offline); - }); - }); + return offlineStore.state.offline; } async init() { await this.status(); } - watchOffline(): Observable { - return this.offlineSubject.asObservable(); - } - - watchProgress(): Observable { - return this.progressSubject.asObservable(); - } - private progress(progress: number) { - this.progressSubject - .asObservable() - .pipe(take(1)) - .subscribe((current: number) => { - this.progressSubject.next(current + progress); - }); + offlineStore.state.progress += progress; } private progressStart() { - this.progressSubject.next(0); + offlineStore.state.progress = 0; } private progressComplete() { - this.progressSubject.next(1); + offlineStore.state.progress = 1; } save(): Promise { @@ -131,7 +107,7 @@ export class OfflineService { this.progressComplete(); - this.offlineSubject.next(undefined); + offlineStore.reset(); resolve(); } catch (err) { @@ -144,19 +120,19 @@ export class OfflineService { return new Promise(async (resolve, reject) => { try { try { - if (!store.state.deck || !store.state.deck.id || !store.state.deck.data) { + if (!deckStore.state.deck || !deckStore.state.deck.id || !deckStore.state.deck.data) { reject('No deck found'); return; } const offline: OfflineDeck = { - id: store.state.deck.id, - name: store.state.deck.data.name, + id: deckStore.state.deck.id, + name: deckStore.state.deck.data.name, }; await set('deckdeckgo_offline', offline); - this.offlineSubject.next(offline); + offlineStore.state.offline = {...offline}; this.progressComplete(); @@ -369,18 +345,18 @@ export class OfflineService { private saveDeck(): Promise { return new Promise(async (resolve, reject) => { try { - if (!store.state.deck || !store.state.deck.id || !store.state.deck.data) { + if (!deckStore.state.deck || !deckStore.state.deck.id || !deckStore.state.deck.data) { reject('No deck found'); return; } - await this.saveSlides(store.state.deck); + await this.saveSlides(deckStore.state.deck); - if (store.state.deck.data.background && OfflineUtils.shouldAttributeBeCleaned(store.state.deck.data.background)) { - store.state.deck.data.background = null; + if (deckStore.state.deck.data.background && OfflineUtils.shouldAttributeBeCleaned(deckStore.state.deck.data.background)) { + deckStore.state.deck.data.background = null; } - await set(`/decks/${store.state.deck.id}`, store.state.deck); + await set(`/decks/${deckStore.state.deck.id}`, deckStore.state.deck); this.progress(0.5); @@ -441,18 +417,18 @@ export class OfflineService { private uploadData(): Promise { return new Promise(async (resolve, reject) => { try { - if (!store.state.deck || !store.state.deck.id || !store.state.deck.data) { + if (!deckStore.state.deck || !deckStore.state.deck.id || !deckStore.state.deck.data) { reject('No deck found'); return; } - await this.uploadSlides(store.state.deck); + await this.uploadSlides(deckStore.state.deck); - await this.deleteSlides(store.state.deck); + await this.deleteSlides(deckStore.state.deck); - const persistedDeck: Deck = await this.uploadDeck(store.state.deck); + const persistedDeck: Deck = await this.uploadDeck(deckStore.state.deck); - store.state.deck = {...persistedDeck}; + deckStore.state.deck = {...persistedDeck}; resolve(); } catch (err) { diff --git a/studio/src/app/stores/offline.store.ts b/studio/src/app/stores/offline.store.ts new file mode 100644 index 000000000..877070e05 --- /dev/null +++ b/studio/src/app/stores/offline.store.ts @@ -0,0 +1,13 @@ +import {createStore} from '@stencil/store'; + +interface OfflineStore { + offline: OfflineDeck | undefined; + progress: number | undefined; +} + +const {state, onChange, reset} = createStore({ + offline: undefined, + progress: undefined, +} as OfflineStore); + +export default {state, onChange, reset}; From c45d9155510f151799aada0f16b52284109035b5 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 20:03:01 +0200 Subject: [PATCH 15/32] feat(#773): use stencil/store for remote --- .../app-actions-deck/app-actions-deck.tsx | 83 +++++++------------ .../events/remote/remote-events.handler.tsx | 58 ++++++------- .../app-remote-connect/app-remote-connect.tsx | 21 ++--- .../app-remote-request/app-remote-request.tsx | 13 +-- .../services/editor/remote/remote.service.tsx | 77 +++++------------ studio/src/app/stores/remote.store.ts | 19 +++++ 6 files changed, 104 insertions(+), 167 deletions(-) create mode 100644 studio/src/app/stores/remote.store.ts diff --git a/studio/src/app/components/editor/actions/deck/app-actions-deck/app-actions-deck.tsx b/studio/src/app/components/editor/actions/deck/app-actions-deck/app-actions-deck.tsx index 2edc7d6d2..486c5c7f6 100644 --- a/studio/src/app/components/editor/actions/deck/app-actions-deck/app-actions-deck.tsx +++ b/studio/src/app/components/editor/actions/deck/app-actions-deck/app-actions-deck.tsx @@ -6,10 +6,8 @@ import {ConnectionState, DeckdeckgoEventDeckRequest} from '@deckdeckgo/types'; import {get, set} from 'idb-keyval'; -import {forkJoin, Subscription} from 'rxjs'; -import {take} from 'rxjs/operators'; - import offlineStore from '../../../../../stores/offline.store'; +import remoteStore from '../../../../../stores/remote.store'; import {SlideAttributes, SlideSplitType, SlideTemplate} from '../../../../../models/data/slide'; @@ -19,7 +17,6 @@ import {CreateSlidesUtils} from '../../../../../utils/editor/create-slides.utils import {DemoAction} from '../../../../../utils/editor/demo-action'; import {AnonymousService} from '../../../../../services/editor/anonymous/anonymous.service'; -import {RemoteService} from '../../../../../services/editor/remote/remote.service'; import {PlaygroundAction} from '../../../../../utils/editor/playground-action'; @Component({ @@ -70,30 +67,20 @@ export class AppActionsDeck { private anonymousService: AnonymousService; - private remoteService: RemoteService; - private remoteSubscription: Subscription; - constructor() { this.anonymousService = AnonymousService.getInstance(); - this.remoteService = RemoteService.getInstance(); } async componentWillLoad() { this.fullscreenEnable = !isIPad(); - this.remoteSubscription = this.remoteService.watchRequests().subscribe(async (requests: DeckdeckgoEventDeckRequest[] | undefined) => { + remoteStore.onChange('pendingRequests', async (requests: DeckdeckgoEventDeckRequest[] | undefined) => { if (requests && requests.length > 0) { await this.clickToOpenRemote(); } }); } - async componentDidUnload() { - if (this.remoteSubscription) { - this.remoteSubscription.unsubscribe(); - } - } - async onActionOpenSlideAdd($event: CustomEvent) { if (!$event || !$event.detail) { return; @@ -468,47 +455,39 @@ export class AppActionsDeck { } private async clickToOpenRemote() { - this.remoteService - .watchState() - .pipe(take(1)) - .subscribe((state: ConnectionState) => { - if (state !== ConnectionState.CONNECTED) { - let button: HTMLElement = this.el.querySelector('ion-tab-button.open-remote'); - - if (!button) { - return; - } - - const style: CSSStyleDeclaration = window.getComputedStyle(button); - - // Actions are grouped in a popover on small devices? - if (style.display === 'none') { - button = this.el.querySelector('ion-tab-button.open-remote-small-devices'); - - if (!button) { - return; - } - } - - // We click to button as we want to pass $event to the popover to stick it next to the button - button.click(); + if (remoteStore.state.connectionState !== ConnectionState.CONNECTED) { + let button: HTMLElement = this.el.querySelector('ion-tab-button.open-remote'); + + if (!button) { + return; + } + + const style: CSSStyleDeclaration = window.getComputedStyle(button); + + // Actions are grouped in a popover on small devices? + if (style.display === 'none') { + button = this.el.querySelector('ion-tab-button.open-remote-small-devices'); + + if (!button) { + return; } - }); + } + + // We click to button as we want to pass $event to the popover to stick it next to the button + button.click(); + } } private async openRemote($event: UIEvent) { - forkJoin([this.remoteService.watchRequests().pipe(take(1)), this.remoteService.watchState().pipe(take(1))]) - .pipe(take(1)) - .subscribe(async ([requests, state]: [DeckdeckgoEventDeckRequest[] | undefined, ConnectionState]) => { - const connected: boolean = state !== ConnectionState.DISCONNECTED && state !== ConnectionState.NOT_CONNECTED; - - if (connected && requests && requests.length > 0) { - await this.closeRemote(); - await this.openRemoteControl($event, 'app-remote-request'); - } else { - await this.openRemoteControl($event, 'app-remote-connect'); - } - }); + const connected: boolean = + remoteStore.state.connectionState !== ConnectionState.DISCONNECTED && remoteStore.state.connectionState !== ConnectionState.NOT_CONNECTED; + + if (connected && remoteStore.state.pendingRequests && remoteStore.state.pendingRequests.length > 0) { + await this.closeRemote(); + await this.openRemoteControl($event, 'app-remote-request'); + } else { + await this.openRemoteControl($event, 'app-remote-connect'); + } } private async closeRemote() { diff --git a/studio/src/app/handlers/editor/events/remote/remote-events.handler.tsx b/studio/src/app/handlers/editor/events/remote/remote-events.handler.tsx index c5bfb10ba..d3ce42bed 100644 --- a/studio/src/app/handlers/editor/events/remote/remote-events.handler.tsx +++ b/studio/src/app/handlers/editor/events/remote/remote-events.handler.tsx @@ -1,7 +1,8 @@ import {Build} from '@stencil/core'; import {Subscription} from 'rxjs'; -import {take} from 'rxjs/operators'; + +import remoteStore from '../../../../stores/remote.store'; import {debounce} from '@deckdeckgo/utils'; import {getSlideDefinition} from '@deckdeckgo/deck-utils'; @@ -24,25 +25,21 @@ export class RemoteEventsHandler { this.remoteService = RemoteService.getInstance(); } - init(el: HTMLElement): Promise { - return new Promise(async (resolve) => { - this.el = el; - - await this.initRemote(); + async init(el: HTMLElement) { + this.el = el; - this.connectSubscription = this.remoteService.watch().subscribe(async (enable: boolean) => { - if (enable) { - await this.connect(); - } else { - await this.disconnect(); - } - }); + await this.initRemote(); - this.acceptRequestSubscription = this.remoteService.watchAcceptedRequest().subscribe(async (request: DeckdeckgoEventDeckRequest) => { - await this.startAcceptedRemoteRequest(request); - }); + remoteStore.onChange('remote', async (enable: boolean) => { + if (enable) { + await this.connect(); + } else { + await this.disconnect(); + } + }); - resolve(); + remoteStore.onChange('acceptedRequest', async (request: DeckdeckgoEventDeckRequest) => { + await this.startAcceptedRemoteRequest(request); }); } @@ -148,8 +145,6 @@ export class RemoteEventsHandler { await this.initDeckMove(); - await this.remoteService.init(); - this.el.addEventListener('slideDelete', this.onSlideDelete, false); this.el.addEventListener('slideDidUpdate', this.slideDidUpdate, false); this.el.addEventListener('pollUpdated', this.pollUpdated, false); @@ -269,6 +264,8 @@ export class RemoteEventsHandler { } deck.addEventListener('slidesDidLoad', async ($event: CustomEvent) => { + await this.remoteService.init(); + await this.initRemoteSize(); await this.initRemoteSlides($event); @@ -493,7 +490,7 @@ export class RemoteEventsHandler { } const observer: MutationObserver = new MutationObserver(async (_mutations: MutationRecord[], _observer: MutationObserver) => { - this.executeIfConnected(this.updateRemoteDeckWithDefinition); + await this.executeIfConnected(this.updateRemoteDeckWithDefinition); observer.disconnect(); }); @@ -602,7 +599,7 @@ export class RemoteEventsHandler { return; } - this.executeIfConnected(this.deleteRemoteSlide); + await this.executeIfConnected(this.deleteRemoteSlide); }; private slideDidUpdate = async ($event: CustomEvent) => { @@ -610,7 +607,7 @@ export class RemoteEventsHandler { return; } - this.executeIfConnected(this.updateCurrentSlideWithDefinition); + await this.executeIfConnected(this.updateCurrentSlideWithDefinition); }; private pollUpdated = async ($event: CustomEvent) => { @@ -618,7 +615,7 @@ export class RemoteEventsHandler { return; } - this.executeIfConnected(this.updatePollSlideWithDefinition, $event.target); + await this.executeIfConnected(this.updatePollSlideWithDefinition, $event.target); }; private deckDidChange = async ($event: CustomEvent) => { @@ -626,18 +623,13 @@ export class RemoteEventsHandler { return; } - this.executeIfConnected(this.updateRemoteDeckWithDefinition); + await this.executeIfConnected(this.updateRemoteDeckWithDefinition); }; - private executeIfConnected(func: (self, options?) => Promise, options?) { - this.remoteService - .watch() - .pipe(take(1)) - .subscribe(async (enable: boolean) => { - if (enable) { - await func(this, options); - } - }); + private async executeIfConnected(func: (self, options?) => Promise, options?) { + if (remoteStore.state.remote) { + await func(this, options); + } } private deleteRemoteSlide(self): Promise { diff --git a/studio/src/app/popovers/editor/remote/app-remote-connect/app-remote-connect.tsx b/studio/src/app/popovers/editor/remote/app-remote-connect/app-remote-connect.tsx index 0b6d3f829..f93c2eb8a 100644 --- a/studio/src/app/popovers/editor/remote/app-remote-connect/app-remote-connect.tsx +++ b/studio/src/app/popovers/editor/remote/app-remote-connect/app-remote-connect.tsx @@ -1,19 +1,16 @@ import {Component, Element, State, h} from '@stencil/core'; -import {take} from 'rxjs/operators'; +import remoteStore from '../../../../stores/remote.store'; import {RemoteService} from '../../../../services/editor/remote/remote.service'; @Component({ tag: 'app-remote-connect', - styleUrl: 'app-remote-connect.scss' + styleUrl: 'app-remote-connect.scss', }) export class AppRemoteConnect { @Element() el: HTMLElement; - @State() - private remoteEnabled: boolean = false; - private remoteService: RemoteService; @State() @@ -24,13 +21,6 @@ export class AppRemoteConnect { } async componentWillLoad() { - this.remoteService - .watch() - .pipe(take(1)) - .subscribe((enable: boolean) => { - this.remoteEnabled = enable; - }); - await this.initQRCodeURI(); await this.initCloseOnConnected(); @@ -86,8 +76,7 @@ export class AppRemoteConnect { } private async toggleRemoteEnabled() { - this.remoteEnabled = !this.remoteEnabled; - await this.remoteService.switch(this.remoteEnabled); + await this.remoteService.switch(!remoteStore.state.remote); } private initQRCodeURI(): Promise { @@ -122,7 +111,7 @@ export class AppRemoteConnect { {this.renderLabel()} - this.toggleRemoteEnabled()}> + this.toggleRemoteEnabled()}> @@ -134,7 +123,7 @@ export class AppRemoteConnect { } private renderLabel() { - if (this.remoteEnabled) { + if (remoteStore.state.remote) { return ( Remote is enabled diff --git a/studio/src/app/popovers/editor/remote/app-remote-request/app-remote-request.tsx b/studio/src/app/popovers/editor/remote/app-remote-request/app-remote-request.tsx index cbc3f186f..b8c31487d 100644 --- a/studio/src/app/popovers/editor/remote/app-remote-request/app-remote-request.tsx +++ b/studio/src/app/popovers/editor/remote/app-remote-request/app-remote-request.tsx @@ -1,6 +1,6 @@ import {Component, Element, h, State} from '@stencil/core'; -import {take} from 'rxjs/operators'; +import remoteStore from '../../../../stores/remote.store'; import {DeckdeckgoEventDeckRequest} from '@deckdeckgo/types'; @@ -8,7 +8,7 @@ import {RemoteService} from '../../../../services/editor/remote/remote.service'; @Component({ tag: 'app-remote-request', - styleUrl: 'app-remote-request.scss' + styleUrl: 'app-remote-request.scss', }) export class AppRemoteRequest { @Element() el: HTMLElement; @@ -23,12 +23,7 @@ export class AppRemoteRequest { } async componentDidLoad() { - this.remoteService - .watchRequests() - .pipe(take(1)) - .subscribe((requests: DeckdeckgoEventDeckRequest[] | undefined) => { - this.request = requests && requests.length >= 1 ? requests[0] : undefined; - }); + this.request = remoteStore.state.pendingRequests && remoteStore.state.pendingRequests.length >= 1 ? remoteStore.state.pendingRequests[0] : undefined; } private async shiftRequestsAndClose() { @@ -67,7 +62,7 @@ export class AppRemoteRequest { - + , ]; } } diff --git a/studio/src/app/services/editor/remote/remote.service.tsx b/studio/src/app/services/editor/remote/remote.service.tsx index 7682970b3..7758a35d5 100644 --- a/studio/src/app/services/editor/remote/remote.service.tsx +++ b/studio/src/app/services/editor/remote/remote.service.tsx @@ -1,9 +1,7 @@ import {Build} from '@stencil/core'; -import {BehaviorSubject, Observable, Subject} from 'rxjs'; -import {take} from 'rxjs/operators'; - -import store from '../../../stores/deck.store'; +import deckStore from '../../../stores/deck.store'; +import remoteStore from '../../../stores/remote.store'; import {get, set} from 'idb-keyval'; @@ -12,14 +10,6 @@ import {DeckdeckgoEventDeckRequest, ConnectionState} from '@deckdeckgo/types'; import {Deck} from '../../../models/data/deck'; export class RemoteService { - private remoteSubject: BehaviorSubject = new BehaviorSubject(false); - - private remotePendingRequestsSubject: BehaviorSubject = new BehaviorSubject(undefined); - - private remoteStateSubject: BehaviorSubject = new BehaviorSubject(ConnectionState.DISCONNECTED); - - private remoteAcceptedRequestSubject: Subject = new Subject(); - private static instance: RemoteService; static getInstance() { @@ -38,13 +28,9 @@ export class RemoteService { const remote: boolean = await get('deckdeckgo_remote'); - this.watch() - .pipe(take(1)) - .subscribe((current: boolean) => { - if (current !== remote) { - this.remoteSubject.next(remote); - } - }); + if (remoteStore.state.remote !== remote) { + remoteStore.state.remote = remote; + } resolve(); }); @@ -52,15 +38,11 @@ export class RemoteService { async switch(enable: boolean) { await set('deckdeckgo_remote', enable); - this.remoteSubject.next(enable); - } - - watch(): Observable { - return this.remoteSubject.asObservable(); + remoteStore.state.remote = enable; } async getRoom(): Promise { - const deck: Deck | null = store.state.deck; + const deck: Deck | null = deckStore.state.deck; if (deck && deck.data && deck.data.name && deck.data.name !== undefined && deck.data.name !== '') { return deck.data.name.replace(/\.|#/g, '_'); @@ -70,48 +52,29 @@ export class RemoteService { } async addPendingRequests(request: DeckdeckgoEventDeckRequest) { - this.remotePendingRequestsSubject.pipe(take(1)).subscribe((requests: DeckdeckgoEventDeckRequest[] | undefined) => { - if (!requests) { - requests = []; - } + let requests: DeckdeckgoEventDeckRequest[] = remoteStore.state.pendingRequests; + if (!requests) { + requests = []; + } - requests.push(request); + requests.push(request); - this.remotePendingRequestsSubject.next(requests); - }); + remoteStore.state.pendingRequests = [...requests]; } - shiftPendingRequests(): Promise { - return new Promise((resolve) => { - this.remotePendingRequestsSubject.pipe(take(1)).subscribe((requests: DeckdeckgoEventDeckRequest[] | undefined) => { - if (requests && requests.length > 0) { - requests.shift(); + async shiftPendingRequests() { + if (remoteStore.state.pendingRequests && remoteStore.state.pendingRequests.length > 0) { + remoteStore.state.pendingRequests.shift(); - this.remotePendingRequestsSubject.next(requests); - } - - resolve(); - }); - }); + remoteStore.state.pendingRequests = [...remoteStore.state.pendingRequests]; + } } acceptRequest(request: DeckdeckgoEventDeckRequest) { - this.remoteAcceptedRequestSubject.next(request); - } - - watchRequests(): Observable { - return this.remotePendingRequestsSubject.asObservable(); - } - - watchState(): Observable { - return this.remoteStateSubject.asObservable(); - } - - watchAcceptedRequest(): Observable { - return this.remoteAcceptedRequestSubject.asObservable(); + remoteStore.state.acceptedRequest = {...request}; } nextState(state: ConnectionState) { - this.remoteStateSubject.next(state); + remoteStore.state.connectionState = state; } } diff --git a/studio/src/app/stores/remote.store.ts b/studio/src/app/stores/remote.store.ts new file mode 100644 index 000000000..bca0f21ef --- /dev/null +++ b/studio/src/app/stores/remote.store.ts @@ -0,0 +1,19 @@ +import {createStore} from '@stencil/store'; + +import {ConnectionState, DeckdeckgoEventDeckRequest} from '@deckdeckgo/types'; + +interface RemoteStore { + remote: boolean; + pendingRequests: DeckdeckgoEventDeckRequest[] | undefined; + connectionState: ConnectionState; + acceptedRequest: DeckdeckgoEventDeckRequest | undefined; +} + +const {state, onChange} = createStore({ + remote: false, + pendingRequests: undefined, + connectionState: ConnectionState.DISCONNECTED, + acceptedRequest: undefined, +} as RemoteStore); + +export default {state, onChange}; From 1b90f4d135017bd7615d54ee931de7b01e33c416 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Mon, 29 Jun 2020 22:25:30 +0200 Subject: [PATCH 16/32] feat(#773): use stencil/store for authentication --- .../app/components/core/app-menu/app-menu.tsx | 37 ++---- .../app-navigation-actions.tsx | 24 +--- .../core/app-user-info/app-user-info.tsx | 25 +--- .../events/deck/deck-events.handler.tsx | 52 +++----- .../src/app/helpers/editor/image.helper.tsx | 13 +- .../src/app/helpers/editor/shape.helper.tsx | 13 +- .../core/app-dashboard/app-dashboard.tsx | 55 ++++---- .../pages/core/app-settings/app-settings.tsx | 30 +---- .../app/pages/core/app-signin/app-signin.tsx | 78 ++++++----- .../pages/editor/app-editor/app-editor.tsx | 59 +++++---- .../app-create-slide/app-create-slide.tsx | 16 +-- .../api.presentation.prod.service.tsx | 8 +- studio/src/app/services/auth/auth.service.tsx | 26 +--- .../editor/anonymous/anonymous.service.tsx | 67 +++------- .../services/editor/share/share.service.tsx | 44 ++----- .../storage/storage.online.service.tsx | 123 ++++++++---------- studio/src/app/stores/auth.store.ts | 22 ++++ 17 files changed, 263 insertions(+), 429 deletions(-) create mode 100644 studio/src/app/stores/auth.store.ts diff --git a/studio/src/app/components/core/app-menu/app-menu.tsx b/studio/src/app/components/core/app-menu/app-menu.tsx index 1a075784b..8223719d3 100644 --- a/studio/src/app/components/core/app-menu/app-menu.tsx +++ b/studio/src/app/components/core/app-menu/app-menu.tsx @@ -1,10 +1,7 @@ -import {Component, Element, State, h} from '@stencil/core'; +import {Component, Element, h} from '@stencil/core'; -import {Subscription} from 'rxjs'; - -import store from '../../../stores/nav.store'; - -import {AuthUser} from '../../../models/auth/auth.user'; +import navStore from '../../../stores/nav.store'; +import authStore from '../../../stores/auth.store'; import {Utils} from '../../../utils/core/utils'; @@ -20,29 +17,13 @@ export class AppMenu { @Element() el: HTMLElement; private authService: AuthService; - private authSubscription: Subscription; - - @State() - private authUser: AuthUser; constructor() { this.authService = AuthService.getInstance(); } - componentWillLoad() { - this.authSubscription = this.authService.watch().subscribe(async (authUser: AuthUser) => { - this.authUser = authUser; - }); - } - - componentDidUnload() { - if (this.authSubscription) { - this.authSubscription.unsubscribe(); - } - } - private async signIn() { - store.state.nav = { + navStore.state.nav = { url: '/signin' + (window && window.location ? window.location.pathname : ''), direction: NavDirection.FORWARD, }; @@ -51,7 +32,7 @@ export class AppMenu { private async signOut() { await this.authService.signOut(); - store.state.nav = { + navStore.state.nav = { url: '/', direction: NavDirection.ROOT, }; @@ -72,7 +53,7 @@ export class AppMenu { } private renderUser() { - if (Utils.isLoggedIn(this.authUser)) { + if (Utils.isLoggedIn(authStore.state.authUser)) { return ( @@ -84,7 +65,7 @@ export class AppMenu { } private renderDashboard() { - if (Utils.isLoggedIn(this.authUser)) { + if (Utils.isLoggedIn(authStore.state.authUser)) { return ( @@ -97,7 +78,7 @@ export class AppMenu { } private renderSignInOut() { - if (Utils.isLoggedIn(this.authUser)) { + if (Utils.isLoggedIn(authStore.state.authUser)) { return ( this.signOut()}> @@ -124,7 +105,7 @@ export class AppMenu { } private renderDiscover() { - if (Utils.isLoggedIn(this.authUser)) { + if (Utils.isLoggedIn(authStore.state.authUser)) { return undefined; } diff --git a/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx b/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx index 78d4a0e7b..1a1ee2f1c 100644 --- a/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx +++ b/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx @@ -6,13 +6,12 @@ import {Subscription} from 'rxjs'; import themeStore from '../../../stores/theme.store'; import navStore, {NavDirection} from '../../../stores/nav.store'; +import authStore from '../../../stores/auth.store'; -import {AuthUser} from '../../../models/auth/auth.user'; import {User} from '../../../models/data/user'; import {Utils} from '../../../utils/core/utils'; -import {AuthService} from '../../../services/auth/auth.service'; import {UserService} from '../../../services/data/user/user.service'; @Component({ @@ -25,15 +24,9 @@ export class AppNavigationActions { @Prop() presentation: boolean = false; @Prop() publish: boolean = false; - private authService: AuthService; - private subscription: Subscription; - private userService: UserService; private userSubscription: Subscription; - @State() - private authUser: AuthUser; - @State() private photoUrl: string; @@ -43,15 +36,10 @@ export class AppNavigationActions { @Event() private actionPublish: EventEmitter; constructor() { - this.authService = AuthService.getInstance(); this.userService = UserService.getInstance(); } componentWillLoad() { - this.subscription = this.authService.watch().subscribe((authUser: AuthUser) => { - this.authUser = authUser; - }); - this.userSubscription = this.userService.watch().subscribe((user: User) => { this.photoUrl = user && user.data ? user.data.photo_url : undefined; this.photoUrlLoaded = true; @@ -59,10 +47,6 @@ export class AppNavigationActions { } componentDidUnload() { - if (this.subscription) { - this.subscription.unsubscribe(); - } - if (this.userSubscription) { this.userSubscription.unsubscribe(); } @@ -98,7 +82,7 @@ export class AppNavigationActions { } private renderFeed() { - if (Utils.isLoggedIn(this.authUser) || !this.signIn) { + if (Utils.isLoggedIn(authStore.state.authUser) || !this.signIn) { return undefined; } else if (this.presentation || this.publish) { return ( @@ -110,7 +94,7 @@ export class AppNavigationActions { } private renderSignIn() { - if (Utils.isLoggedIn(this.authUser) || !this.signIn) { + if (Utils.isLoggedIn(authStore.state.authUser) || !this.signIn) { return undefined; } else if (this.presentation || this.publish) { return ( @@ -122,7 +106,7 @@ export class AppNavigationActions { } private renderLoggedIn() { - if (Utils.isLoggedIn(this.authUser) && this.photoUrlLoaded) { + if (Utils.isLoggedIn(authStore.state.authUser) && this.photoUrlLoaded) { return ( ); } else { diff --git a/studio/src/app/components/core/app-user-info/app-user-info.tsx b/studio/src/app/components/core/app-user-info/app-user-info.tsx index 751504561..7a8d8937d 100644 --- a/studio/src/app/components/core/app-user-info/app-user-info.tsx +++ b/studio/src/app/components/core/app-user-info/app-user-info.tsx @@ -3,12 +3,10 @@ import {Component, Prop, State, h} from '@stencil/core'; import {Subscription} from 'rxjs'; import authStore from '../../../stores/auth.store'; - -import {User} from '../../../models/data/user'; +import userStore from '../../../stores/user.store'; import {ApiUser} from '../../../models/api/api.user'; -import {UserService} from '../../../services/data/user/user.service'; import {ApiUserService} from '../../../services/api/user/api.user.service'; import {ApiUserFactoryService} from '../../../services/api/user/api.user.factory.service'; @@ -23,42 +21,23 @@ export class AppUserInfo { private apiUserService: ApiUserService; private apiUserSubscription: Subscription; - private userService: UserService; - private userSubscription: Subscription; - @State() private apiUser: ApiUser; - @State() - private name: string; - - @State() - private photoUrl: string; - constructor() { this.apiUserService = ApiUserFactoryService.getInstance(); - this.userService = UserService.getInstance(); } componentWillLoad() { this.apiUserSubscription = this.apiUserService.watch().subscribe((user: ApiUser) => { this.apiUser = user; }); - - this.userSubscription = this.userService.watch().subscribe((user: User) => { - this.name = user && user.data ? user.data.name : undefined; - this.photoUrl = user && user.data ? user.data.photo_url : undefined; - }); } componentDidUnload() { if (this.apiUserSubscription) { this.apiUserSubscription.unsubscribe(); } - - if (this.userSubscription) { - this.userSubscription.unsubscribe(); - } } render() { @@ -67,10 +46,10 @@ export class AppUserInfo { - + diff --git a/studio/src/app/pages/core/app-settings/app-settings.tsx b/studio/src/app/pages/core/app-settings/app-settings.tsx index 2742a8bed..8082fc9cf 100644 --- a/studio/src/app/pages/core/app-settings/app-settings.tsx +++ b/studio/src/app/pages/core/app-settings/app-settings.tsx @@ -10,6 +10,7 @@ import themeStore from '../../../stores/theme.store'; import errorStore from '../../../stores/error.store'; import navStore, {NavDirection} from '../../../stores/nav.store'; import authStore from '../../../stores/auth.store'; +import userStore from '../../../stores/user.store'; import {ApiUser} from '../../../models/api/api.user'; import {User} from '../../../models/data/user'; @@ -78,6 +79,8 @@ export class AppHome { @State() private custom: string = undefined; + private destroyListener; + constructor() { this.apiUserService = ApiUserFactoryService.getInstance(); this.imageHistoryService = ImageHistoryService.getInstance(); @@ -98,34 +101,41 @@ export class AppHome { this.apiUsername = this.apiUser.username; }); + } - this.userService - .watch() - .pipe( - filter((user: User) => user !== null && user !== undefined && user.data && !user.data.anonymous), - take(1) - ) - .subscribe(async (user: User) => { - this.user = user; - + async componentDidLoad() { + if (userStore.state.user) { + await this.initSocial(); + } else { + this.destroyListener = userStore.onChange('user', async () => { await this.initSocial(); }); + } } - private initSocial(): Promise { - return new Promise((resolve) => { - if (!this.user || !this.user.data || !this.user.data.social) { - resolve(); - return; - } + componentDidUnload() { + if (this.destroyListener) { + this.destroyListener(); + } + } - this.twitter = this.user.data.social.twitter; - this.linkedin = this.user.data.social.linkedin; - this.dev = this.user.data.social.dev; - this.medium = this.user.data.social.medium; - this.github = this.user.data.social.github; - this.custom = this.user.data.social.custom; - }); + private async initSocial() { + if (!userStore.state.user || !userStore.state.user.data || userStore.state.user.data.anonymous) { + return; + } + + this.user = {...userStore.state.user}; + + if (!userStore.state.user.data.social) { + return; + } + + this.twitter = this.user.data.social.twitter; + this.linkedin = this.user.data.social.linkedin; + this.dev = this.user.data.social.dev; + this.medium = this.user.data.social.medium; + this.github = this.user.data.social.github; + this.custom = this.user.data.social.custom; } private async signIn() { diff --git a/studio/src/app/popovers/editor/app-create-slide/app-create-slide.tsx b/studio/src/app/popovers/editor/app-create-slide/app-create-slide.tsx index af09e5305..2b9d4a5d0 100644 --- a/studio/src/app/popovers/editor/app-create-slide/app-create-slide.tsx +++ b/studio/src/app/popovers/editor/app-create-slide/app-create-slide.tsx @@ -1,10 +1,10 @@ import {Component, Element, Event, EventEmitter, h, JSX, State} from '@stencil/core'; import {interval, Subscription} from 'rxjs'; -import {take} from 'rxjs/operators'; import deckStore from '../../../stores/deck.store'; import authStore from '../../../stores/auth.store'; +import userStore from '../../../stores/user.store'; import {SlideAttributes, SlideChartType, SlideSplitType, SlideTemplate} from '../../../models/data/slide'; @@ -14,7 +14,6 @@ import {Deck} from '../../../models/data/deck'; import {CreateSlidesUtils} from '../../../utils/editor/create-slides.utils'; import {SlotType} from '../../../utils/editor/slot-type'; -import {UserService} from '../../../services/data/user/user.service'; import {AssetsService} from '../../../services/core/assets/assets.service'; import {EnvironmentConfigService} from '../../../services/core/environment/environment-config.service'; @@ -35,9 +34,6 @@ enum ComposeTemplate { export class AppCreateSlide { @Element() el: HTMLElement; - @State() - private photoUrl: string; - @State() private assets: Assets | undefined = undefined; @@ -52,28 +48,13 @@ export class AppCreateSlide { private user: User; - private userService: UserService; - @Event() signIn: EventEmitter; private timerSubscription: Subscription; private config: EnvironmentDeckDeckGoConfig = EnvironmentConfigService.getInstance().get('deckdeckgo'); - constructor() { - this.userService = UserService.getInstance(); - } - async componentWillLoad() { - this.userService - .watch() - .pipe(take(1)) - .subscribe(async (user: User) => { - this.user = user; - this.photoUrl = - user && user.data && user.data.photo_url ? user.data.photo_url : 'https://pbs.twimg.com/profile_images/941274539979366400/bTKGkd-O_400x400.jpg'; - }); - this.assets = await AssetsService.getInstance().assets(); } @@ -768,7 +749,14 @@ export class AppCreateSlide { return (
this.addRestrictedSlide(SlideTemplate.AUTHOR)}> - +

Author

diff --git a/studio/src/app/services/data/user/user.service.tsx b/studio/src/app/services/data/user/user.service.tsx index c09dbed61..d49898240 100644 --- a/studio/src/app/services/data/user/user.service.tsx +++ b/studio/src/app/services/data/user/user.service.tsx @@ -1,14 +1,12 @@ import * as firebase from 'firebase/app'; import 'firebase/firestore'; -import {Observable, ReplaySubject} from 'rxjs'; +import store from '../../../stores/user.store'; import {AuthUser} from '../../../models/auth/auth.user'; import {User, UserData} from '../../../models/data/user'; export class UserService { - private userSubject: ReplaySubject = new ReplaySubject(1); - private static instance: UserService; private constructor() { @@ -37,16 +35,16 @@ export class UserService { if (!snapshot.exists) { const user: User = await this.createUser(authUser); - this.userSubject.next(user); + store.state.user = {...user}; } else { const user: UserData = snapshot.data() as UserData; const updatedUser: UserData = await this.updateUserWithAuthData(authUser, user); - this.userSubject.next({ + store.state.user = { id: authUser.uid, data: updatedUser, - }); + }; } resolve(); @@ -143,10 +141,6 @@ export class UserService { } } - watch(): Observable { - return this.userSubject.asObservable(); - } - update(user: User): Promise { return new Promise(async (resolve, reject) => { const firestore: firebase.firestore.Firestore = firebase.firestore(); @@ -157,7 +151,7 @@ export class UserService { try { await firestore.collection('users').doc(user.id).set(user.data, {merge: true}); - this.userSubject.next(user); + store.state.user = {...user}; resolve(user); } catch (err) { @@ -173,7 +167,7 @@ export class UserService { await firestore.collection('users').doc(userId).delete(); - this.userSubject.next(null); + store.reset(); resolve(); } catch (err) { diff --git a/studio/src/app/services/editor/publish/publish.service.tsx b/studio/src/app/services/editor/publish/publish.service.tsx index 666f00c55..4275fd867 100644 --- a/studio/src/app/services/editor/publish/publish.service.tsx +++ b/studio/src/app/services/editor/publish/publish.service.tsx @@ -1,22 +1,19 @@ import * as firebase from 'firebase/app'; import 'firebase/firestore'; -import {take} from 'rxjs/operators'; - import deckStore from '../../../stores/deck.store'; import publishStore from '../../../stores/publish.store'; +import userStore from '../../../stores/user.store'; import {Deck, DeckMetaAuthor} from '../../../models/data/deck'; import {ApiDeck} from '../../../models/api/api.deck'; import {Slide, SlideAttributes, SlideTemplate} from '../../../models/data/slide'; -import {User} from '../../../models/data/user'; import {ApiPresentation} from '../../../models/api/api.presentation'; import {ApiSlide} from '../../../models/api/api.slide'; import {DeckService} from '../../data/deck/deck.service'; import {SlideService} from '../../data/slide/slide.service'; -import {UserService} from '../../data/user/user.service'; import {ApiPresentationService} from '../../api/presentation/api.presentation.service'; import {ApiPresentationFactoryService} from '../../api/presentation/api.presentation.factory.service'; @@ -33,8 +30,6 @@ export class PublishService { private deckService: DeckService; private slideService: SlideService; - private userService: UserService; - private fontsService: FontsService; private constructor() { @@ -43,8 +38,6 @@ export class PublishService { this.deckService = DeckService.getInstance(); this.slideService = SlideService.getInstance(); - this.userService = UserService.getInstance(); - this.fontsService = FontsService.getInstance(); } @@ -379,62 +372,62 @@ export class PublishService { return; } - this.userService - .watch() - .pipe(take(1)) - .subscribe(async (user: User) => { - const url: URL = new URL(publishedUrl); - const now: firebase.firestore.Timestamp = firebase.firestore.Timestamp.now(); - - if (!deck.data.meta) { - deck.data.meta = { - title: deck.data.name, - pathname: url.pathname, - published: true, - published_at: now, - feed: true, - updated_at: now, - }; - } else { - deck.data.meta.title = deck.data.name; - deck.data.meta.pathname = url.pathname; - deck.data.meta.updated_at = now; - } - - if (description && description !== undefined && description !== '') { - deck.data.meta.description = description; - } else { - deck.data.meta.description = firebase.firestore.FieldValue.delete(); - } - - if (!tags || tags.length <= 0) { - deck.data.meta.tags = firebase.firestore.FieldValue.delete(); - } else { - deck.data.meta.tags = tags; - } - - if (user && user.data && user.data.name) { - if (!deck.data.meta.author) { - deck.data.meta.author = { - name: user.data.name, - }; - } else { - (deck.data.meta.author as DeckMetaAuthor).name = user.data.name; - } - - if (user.data.photo_url) { - (deck.data.meta.author as DeckMetaAuthor).photo_url = user.data.photo_url; - } - } else { - if (deck.data.meta.author) { - deck.data.meta.author = firebase.firestore.FieldValue.delete(); - } - } - - await this.deckService.update(deck); - - resolve(); - }); + if (!userStore.state.user || !userStore.state.user.data) { + reject('No user'); + return; + } + + const url: URL = new URL(publishedUrl); + const now: firebase.firestore.Timestamp = firebase.firestore.Timestamp.now(); + + if (!deck.data.meta) { + deck.data.meta = { + title: deck.data.name, + pathname: url.pathname, + published: true, + published_at: now, + feed: true, + updated_at: now, + }; + } else { + deck.data.meta.title = deck.data.name; + deck.data.meta.pathname = url.pathname; + deck.data.meta.updated_at = now; + } + + if (description && description !== undefined && description !== '') { + deck.data.meta.description = description; + } else { + deck.data.meta.description = firebase.firestore.FieldValue.delete(); + } + + if (!tags || tags.length <= 0) { + deck.data.meta.tags = firebase.firestore.FieldValue.delete(); + } else { + deck.data.meta.tags = tags; + } + + if (userStore.state.user && userStore.state.user.data && userStore.state.user.data.name) { + if (!deck.data.meta.author) { + deck.data.meta.author = { + name: userStore.state.user.data.name, + }; + } else { + (deck.data.meta.author as DeckMetaAuthor).name = userStore.state.user.data.name; + } + + if (userStore.state.user.data.photo_url) { + (deck.data.meta.author as DeckMetaAuthor).photo_url = userStore.state.user.data.photo_url; + } + } else { + if (deck.data.meta.author) { + deck.data.meta.author = firebase.firestore.FieldValue.delete(); + } + } + + await this.deckService.update(deck); + + resolve(); } catch (err) { reject(err); } diff --git a/studio/src/app/stores/user.store.ts b/studio/src/app/stores/user.store.ts new file mode 100644 index 000000000..b00a98160 --- /dev/null +++ b/studio/src/app/stores/user.store.ts @@ -0,0 +1,25 @@ +import {createStore} from '@stencil/store'; + +import {User} from '../models/data/user'; + +interface UserStore { + user: User | undefined; + photoUrl: string | undefined; + loaded: boolean; + name: string | undefined; +} + +const {state, onChange, reset} = createStore({ + user: undefined, + photoUrl: undefined, + loaded: false, + name: undefined, +} as UserStore); + +onChange('user', (user: User | undefined) => { + state.photoUrl = user && user.data ? user.data.photo_url : undefined; + state.name = user && user.data ? user.data.name : undefined; + state.loaded = true; +}); + +export default {state, onChange, reset}; From 92a584fef65cd92ac91c2207a8bdf080ba369f97 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Tue, 30 Jun 2020 18:53:13 +0200 Subject: [PATCH 19/32] feat(#773): add loggedIn information to store --- studio/src/app/components/core/app-menu/app-menu.tsx | 10 ++++------ .../app-navigation-actions/app-navigation-actions.tsx | 8 +++----- studio/src/app/stores/auth.store.ts | 3 +++ studio/src/app/utils/core/utils.tsx | 8 +------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/studio/src/app/components/core/app-menu/app-menu.tsx b/studio/src/app/components/core/app-menu/app-menu.tsx index 8223719d3..7bdbfcd60 100644 --- a/studio/src/app/components/core/app-menu/app-menu.tsx +++ b/studio/src/app/components/core/app-menu/app-menu.tsx @@ -3,8 +3,6 @@ import {Component, Element, h} from '@stencil/core'; import navStore from '../../../stores/nav.store'; import authStore from '../../../stores/auth.store'; -import {Utils} from '../../../utils/core/utils'; - import {AuthService} from '../../../services/auth/auth.service'; import {NavDirection} from '../../../stores/nav.store'; @@ -53,7 +51,7 @@ export class AppMenu { } private renderUser() { - if (Utils.isLoggedIn(authStore.state.authUser)) { + if (authStore.state.loggedIn) { return ( @@ -65,7 +63,7 @@ export class AppMenu { } private renderDashboard() { - if (Utils.isLoggedIn(authStore.state.authUser)) { + if (authStore.state.loggedIn) { return ( @@ -78,7 +76,7 @@ export class AppMenu { } private renderSignInOut() { - if (Utils.isLoggedIn(authStore.state.authUser)) { + if (authStore.state.loggedIn) { return ( this.signOut()}> @@ -105,7 +103,7 @@ export class AppMenu { } private renderDiscover() { - if (Utils.isLoggedIn(authStore.state.authUser)) { + if (authStore.state.loggedIn) { return undefined; } diff --git a/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx b/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx index fd7bc7f02..53e0c75f0 100644 --- a/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx +++ b/studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx @@ -7,8 +7,6 @@ import navStore, {NavDirection} from '../../../stores/nav.store'; import authStore from '../../../stores/auth.store'; import userStore from '../../../stores/user.store'; -import {Utils} from '../../../utils/core/utils'; - @Component({ tag: 'app-navigation-actions', styleUrl: 'app-navigation-actions.scss', @@ -51,7 +49,7 @@ export class AppNavigationActions { } private renderFeed() { - if (Utils.isLoggedIn(authStore.state.authUser) || !this.signIn) { + if (authStore.state.loggedIn || !this.signIn) { return undefined; } else if (this.presentation || this.publish) { return ( @@ -63,7 +61,7 @@ export class AppNavigationActions { } private renderSignIn() { - if (Utils.isLoggedIn(authStore.state.authUser) || !this.signIn) { + if (authStore.state.loggedIn || !this.signIn) { return undefined; } else if (this.presentation || this.publish) { return ( @@ -75,7 +73,7 @@ export class AppNavigationActions { } private renderLoggedIn() { - if (Utils.isLoggedIn(authStore.state.authUser) && userStore.state.loaded) { + if (authStore.state.loggedIn && userStore.state.loaded) { return (