diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8863eefd1..0490666cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+
+
+# [1.5.0](https://github.com/deckgo/deckdeckgo/compare/v1.4.0...v1.5.0) (In progress)
+
+### Web Components
+
+- remote: v1.2.0 ([CHANGELOG](https://github.com/deckgo/deckdeckgo/blob/master/webcomponents/remote/CHANGELOG.md))
+
# [1.4.0](https://github.com/deckgo/deckdeckgo/compare/v1.3.0...v1.4.0) (2020-06-25)
diff --git a/docs/package-lock.json b/docs/package-lock.json
index 3d00cbcdb..f0a8647fb 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -1535,14 +1535,6 @@
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
- "rxjs": {
- "version": "6.5.5",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
- "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
- "requires": {
- "tslib": "^1.9.0"
- }
- },
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
diff --git a/docs/package.json b/docs/package.json
index 9a2770e96..84441d370 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -43,8 +43,7 @@
"@deckdeckgo/social": "^1.0.1",
"@deckdeckgo/youtube": "^1.1.2",
"@ionic/core": "^5.2.2",
- "idb-keyval": "^3.2.0",
- "rxjs": "^6.5.5"
+ "idb-keyval": "^3.2.0"
},
"devDependencies": {
"@deckdeckgo/types": "^1.1.0",
diff --git a/remote/package-lock.json b/remote/package-lock.json
index feb98526c..5c1d55cb7 100644
--- a/remote/package-lock.json
+++ b/remote/package-lock.json
@@ -1356,6 +1356,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
+ },
"@surma/rollup-plugin-off-main-thread": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.1.tgz",
@@ -3920,14 +3926,6 @@
}
}
},
- "rxjs": {
- "version": "6.5.5",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
- "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
- "requires": {
- "tslib": "^1.9.0"
- }
- },
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
diff --git a/remote/package.json b/remote/package.json
index 38ec0c93f..0282824f3 100644
--- a/remote/package.json
+++ b/remote/package.json
@@ -47,7 +47,6 @@
"date-fns": "^2.14.0",
"idb-keyval": "^3.2.0",
"remarkable": "^2.0.1",
- "rxjs": "^6.5.5",
"socket.io-client": "^2.3.0",
"uuid": "^8.2.0"
},
@@ -56,6 +55,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/w3c-generic-sensor": "^1.0.2",
"@types/webrtc": "0.0.26",
diff --git a/remote/src/app/app-root.tsx b/remote/src/app/app-root.tsx
index defcc73b5..983817208 100644
--- a/remote/src/app/app-root.tsx
+++ b/remote/src/app/app-root.tsx
@@ -1,7 +1,5 @@
import {Build, Component, h, State} from '@stencil/core';
-import {Subscription} from 'rxjs';
-
import {TimerService} from './services/timer/timer.service';
import {AccelerometerService} from './services/accelerometer/accelerometer.service';
import {ThemeService} from './services/theme/theme.service';
@@ -14,11 +12,8 @@ export class AppRoot {
private timerService: TimerService;
private accelerometerService: AccelerometerService;
- private themeSubscription: Subscription;
private themeService: ThemeService;
- private domBodyClassList: DOMTokenList = document.body.classList;
-
@State()
private loading: boolean = true;
@@ -29,10 +24,6 @@ export class AppRoot {
}
async componentWillLoad() {
- this.themeSubscription = this.themeService.watch().subscribe((dark: boolean) => {
- this.updateDarkModePreferences(dark);
- });
-
await this.themeService.initDarkModePreference();
}
@@ -46,16 +37,8 @@ export class AppRoot {
}
}
- componentDidUnload() {
- this.timerService.destroy();
-
- if (this.themeSubscription) {
- this.themeSubscription.unsubscribe();
- }
- }
-
- private updateDarkModePreferences(dark: boolean) {
- dark ? this.domBodyClassList.add('dark') : this.domBodyClassList.remove('dark');
+ async componentDidUnload() {
+ await this.timerService.destroy();
}
render() {
diff --git a/remote/src/app/components/app-header/app-header.tsx b/remote/src/app/components/app-header/app-header.tsx
index 3dc8b0e55..ff0d55427 100644
--- a/remote/src/app/components/app-header/app-header.tsx
+++ b/remote/src/app/components/app-header/app-header.tsx
@@ -1,40 +1,16 @@
-import {Component, h, State} from '@stencil/core';
+import {Component, h} from '@stencil/core';
-import {Subscription} from 'rxjs';
-
-import {ThemeService} from '../../services/theme/theme.service';
+import themeStore from '../../stores/theme.store';
@Component({
tag: 'app-header',
- styleUrl: 'app-header.scss'
+ styleUrl: 'app-header.scss',
})
export class AppHeader {
- private themeSubscription: Subscription;
- private themeService: ThemeService;
-
- @State()
- private darkTheme: boolean;
-
- constructor() {
- this.themeService = ThemeService.getInstance();
- }
-
- async componentWillLoad() {
- this.themeSubscription = this.themeService.watch().subscribe((dark: boolean) => {
- this.darkTheme = dark;
- });
- }
-
- componentWillUnload() {
- if (this.themeSubscription) {
- this.themeSubscription.unsubscribe();
- }
- }
-
render() {
return (
-
+
diff --git a/remote/src/app/components/app-notes/app-notes.tsx b/remote/src/app/components/app-notes/app-notes.tsx
index cd1323f34..765e28d8d 100644
--- a/remote/src/app/components/app-notes/app-notes.tsx
+++ b/remote/src/app/components/app-notes/app-notes.tsx
@@ -1,19 +1,15 @@
import {Component, h, Listen, State} from '@stencil/core';
-import {Subscription} from 'rxjs';
+import notesStores from '../../stores/notes.store';
import {Remarkable} from 'remarkable';
-import {NotesService} from '../../services/notes/notes.service';
-
@Component({
tag: 'app-notes',
- styleUrl: 'app-notes.scss'
+ styleUrl: 'app-notes.scss',
})
export class AppNotes {
- private notesService: NotesService;
-
- private subscription: Subscription;
+ private destroyListener;
@State()
private portrait: boolean = true;
@@ -21,12 +17,8 @@ export class AppNotes {
@State()
private notes: string;
- constructor() {
- this.notesService = NotesService.getInstance();
- }
-
componentWillLoad() {
- this.subscription = this.notesService.watch().subscribe((element: HTMLElement) => {
+ notesStores.onChange('currentSlide', (element: HTMLElement) => {
let notes: string = undefined;
if (document && element) {
@@ -36,7 +28,7 @@ export class AppNotes {
const md: Remarkable = new Remarkable({
html: true,
xhtmlOut: true,
- breaks: true
+ breaks: true,
});
const codeRule = (inline: boolean) => (tokens, idx, _options, _env) => {
@@ -63,8 +55,8 @@ export class AppNotes {
}
componentDidUnload() {
- if (this.subscription) {
- this.subscription.unsubscribe();
+ if (this.destroyListener) {
+ this.destroyListener();
}
}
diff --git a/remote/src/app/components/app-stopwatch-time/app-stopwatch-time.tsx b/remote/src/app/components/app-stopwatch-time/app-stopwatch-time.tsx
index 4db1376c7..7cb16dae7 100644
--- a/remote/src/app/components/app-stopwatch-time/app-stopwatch-time.tsx
+++ b/remote/src/app/components/app-stopwatch-time/app-stopwatch-time.tsx
@@ -1,84 +1,24 @@
-import {Component, State, h} from '@stencil/core';
+import {Component, h} from '@stencil/core';
-import {BehaviorSubject, Subscription} from 'rxjs';
-
-import {addMilliseconds, formatDistanceStrict} from 'date-fns';
-
-import {TimerInterval, TimerService} from '../../services/timer/timer.service';
+import timerStore from '../../stores/timer.store';
@Component({
tag: 'app-stopwatch-time',
- styleUrl: 'app-stopwatch-time.scss'
+ styleUrl: 'app-stopwatch-time.scss',
})
export class AppStopwatchTime {
- private watcherSubscription: Subscription;
- private timerSubscription: Subscription;
-
- @State()
- private remainingText: string;
-
- @State()
- private remainingTime: number = 0;
-
- private timerService: TimerService;
-
- constructor() {
- this.timerService = TimerService.getInstance();
- }
-
- async componentWillLoad() {
- this.watcherSubscription = this.timerService.watch().subscribe((timer: BehaviorSubject) => {
- if (timer) {
- this.timerSubscription = timer.subscribe(
- (interval: TimerInterval) => {
- if (interval) {
- this.remainingTime = interval.timerRemaining;
-
- const end: Date = addMilliseconds(new Date(), this.remainingTime);
- this.remainingText = formatDistanceStrict(end, new Date());
- } else {
- this.remainingTime = 0;
- this.remainingText = formatDistanceStrict(new Date(), new Date());
- }
- },
- (_err) => {
- // Do nothing
- },
- async () => {
- this.remainingTime = 0;
- this.remainingText = formatDistanceStrict(new Date(), new Date());
-
- if (this.timerSubscription) {
- this.timerSubscription.unsubscribe();
- }
- }
- );
- }
- });
- }
-
- async componentDidUnload() {
- if (this.timerSubscription) {
- this.timerSubscription.unsubscribe();
- }
-
- if (this.watcherSubscription) {
- this.watcherSubscription.unsubscribe();
- }
- }
-
render() {
let color: string = 'primary';
- if (this.remainingTime >= 30000 && this.remainingTime < 60000) {
+ if (timerStore.state.remainingTime >= 30000 && timerStore.state.remainingTime < 60000) {
color = 'secondary';
- } else if (this.remainingTime < 30000 && this.remainingTime >= 0) {
+ } else if (timerStore.state.remainingTime < 30000 && timerStore.state.remainingTime >= 0) {
color = 'tertiary';
}
- if (this.remainingText) {
+ if (timerStore.state.remainingText) {
return (
- {this.remainingText}
+ {timerStore.state.remainingText}
);
} else {
diff --git a/remote/src/app/components/settings/app-experimental-settings/app-experimental-settings.tsx b/remote/src/app/components/settings/app-experimental-settings/app-experimental-settings.tsx
index 873b7ab76..157d964a4 100644
--- a/remote/src/app/components/settings/app-experimental-settings/app-experimental-settings.tsx
+++ b/remote/src/app/components/settings/app-experimental-settings/app-experimental-settings.tsx
@@ -1,20 +1,17 @@
import {Component, Element, State, h} from '@stencil/core';
import {RangeChangeEventDetail} from '@ionic/core';
-import {take} from 'rxjs/operators';
+import accStore from '../../../stores/accelerometer.store';
// Services
import {AccelerometerService} from '../../../services/accelerometer/accelerometer.service';
@Component({
- tag: 'app-experimental-settings'
+ tag: 'app-experimental-settings',
})
export class AppExperimentalSettings {
@Element() el: HTMLElement;
- @State()
- private accelerometerEnabled: boolean = false;
-
@State()
private accelerometerFrequency: number = 0;
@@ -34,13 +31,6 @@ export class AppExperimentalSettings {
}
componentWillLoad() {
- this.accelerometerService
- .watchEnabled()
- .pipe(take(1))
- .subscribe((enabled: boolean) => {
- this.accelerometerEnabled = enabled;
- });
-
this.accelerometerFrequency = this.accelerometerService.frequency;
this.accelerometerSensibility = this.accelerometerService.sensibility;
this.accelerometerTakeUntil = this.accelerometerService.takeUntil;
@@ -48,14 +38,22 @@ export class AppExperimentalSettings {
}
private async toggleAccelerometerSupport() {
- try {
- this.accelerometerEnabled = await this.accelerometerService.toggle();
+ let destroyListener;
- if (this.accelerometerEnabled) {
+ try {
+ destroyListener = accStore.onChange('enable', async () => {
await this.accelerometerService.start();
- }
+
+ destroyListener();
+ });
+
+ await this.accelerometerService.toggle();
} catch (err) {
- this.accelerometerEnabled = false;
+ accStore.state.enable = false;
+
+ if (destroyListener) {
+ destroyListener();
+ }
}
}
@@ -98,10 +96,10 @@ export class AppExperimentalSettings {
Swipe like a Jedi
- this.toggleAccelerometerSupport()}>
+ this.toggleAccelerometerSupport()}>
-
+
Accelerometer's frequency ({this.accelerometerFrequency} measures/seconds)
@@ -111,12 +109,12 @@ export class AppExperimentalSettings {
max={300}
value={this.accelerometerFrequency}
mode="md"
- disabled={!this.accelerometerEnabled}
+ disabled={!accStore.state.enable}
color="switcher"
onIonChange={(e: CustomEvent) => this.updateAccelerometerFrequency(e)}>
-
+
Debounce (trigger swipe after {(this.accelerometerTakeUntil / this.accelerometerFrequency).toFixed(2)} seconds)
@@ -126,12 +124,12 @@ export class AppExperimentalSettings {
max={30}
value={this.accelerometerTakeUntil}
mode="md"
- disabled={!this.accelerometerEnabled}
+ disabled={!accStore.state.enable}
color="switcher"
onIonChange={(e: CustomEvent) => this.updateAccelerometerTakeUntil(e)}>
-
+
Sensibility (detect acceleration above velocity {this.accelerometerSensibility})
@@ -141,12 +139,12 @@ export class AppExperimentalSettings {
max={30}
value={this.accelerometerSensibility}
mode="md"
- disabled={!this.accelerometerEnabled}
+ disabled={!accStore.state.enable}
color="switcher"
onIonChange={(e: CustomEvent) => this.updateAccelerometerSensibility(e)}>
-
+
Delay (after swipe, detect again after {this.accelerometerDelay}ms)
@@ -156,11 +154,11 @@ export class AppExperimentalSettings {
max={2500}
value={this.accelerometerDelay}
mode="md"
- disabled={!this.accelerometerEnabled}
+ disabled={!accStore.state.enable}
color="switcher"
onIonChange={(e: CustomEvent) => this.updateAccelerometerDelay(e)}>
-
+ ,
];
}
}
diff --git a/remote/src/app/components/settings/app-general-settings/app-general-settings.tsx b/remote/src/app/components/settings/app-general-settings/app-general-settings.tsx
index e9b65db39..0c9bd0abd 100644
--- a/remote/src/app/components/settings/app-general-settings/app-general-settings.tsx
+++ b/remote/src/app/components/settings/app-general-settings/app-general-settings.tsx
@@ -1,11 +1,11 @@
-import {Component, h, State} from '@stencil/core';
+import {Component, h} from '@stencil/core';
-import {take} from 'rxjs/operators';
+import themeStore from '../../../stores/theme.store';
import {ThemeService} from '../../../services/theme/theme.service';
@Component({
- tag: 'app-general-settings'
+ tag: 'app-general-settings',
})
export class AppGeneralSettings {
private themeService: ThemeService;
@@ -14,34 +14,21 @@ export class AppGeneralSettings {
this.themeService = ThemeService.getInstance();
}
- componentWillLoad() {
- this.themeService
- .watch()
- .pipe(take(1))
- .subscribe((dark: boolean) => {
- this.darkTheme = dark;
- });
- }
-
async toggleTheme() {
- this.darkTheme = !this.darkTheme;
- await this.themeService.switch(this.darkTheme);
+ await this.themeService.switch(!themeStore.state.darkTheme);
}
- @State()
- private darkTheme: boolean;
-
render() {
return [
Settings ,
- {this.darkTheme ? 'Dark' : 'Light'} theme {this.darkTheme ? '🌑' : '☀️'}
+ {themeStore.state.darkTheme ? 'Dark' : 'Light'} theme {themeStore.state.darkTheme ? '🌑' : '☀️'}
- this.toggleTheme()}>
+ this.toggleTheme()}>
-
+ ,
];
}
}
diff --git a/remote/src/app/modals/app-remote-connect/app-remote-connect.tsx b/remote/src/app/modals/app-remote-connect/app-remote-connect.tsx
index bb13d769e..cb020a8a6 100644
--- a/remote/src/app/modals/app-remote-connect/app-remote-connect.tsx
+++ b/remote/src/app/modals/app-remote-connect/app-remote-connect.tsx
@@ -1,34 +1,25 @@
import {Component, Element, Listen, State, h} from '@stencil/core';
-import {Subscription} from 'rxjs';
+import remoteStore, {ActiveRoom} from '../../stores/remote.store';
// Services
-import {ActiveRoom, CommunicationService} from '../../services/communication/communication.service';
+import {CommunicationService} from '../../services/communication/communication.service';
@Component({
tag: 'app-remote-connect',
- styleUrl: 'app-remote-connect.scss'
+ styleUrl: 'app-remote-connect.scss',
})
export class AppRemoteConnect {
@Element() el: HTMLElement;
- @State()
- private rooms: ActiveRoom[] = [];
-
@State()
private readonly clientId: string;
- private readonly subscription: Subscription;
-
private communicationService: CommunicationService;
constructor() {
this.communicationService = CommunicationService.getInstance();
- this.subscription = this.communicationService.watchRooms().subscribe(async (activeRooms: ActiveRoom[]) => {
- this.rooms = activeRooms;
- });
-
this.clientId = this.communicationService.clientId;
}
@@ -38,12 +29,6 @@ export class AppRemoteConnect {
history.pushState({modal: true}, null);
}
- async componentDidUnload() {
- if (this.subscription) {
- this.subscription.unsubscribe();
- }
- }
-
@Listen('popstate', {target: 'window'})
async handleHardwareBackbutton(_e: PopStateEvent) {
await this.closeModal(false);
@@ -69,12 +54,12 @@ export class AppRemoteConnect {
,
- {this.renderContent()}
+ {this.renderContent()} ,
];
}
private renderContent() {
- if (this.rooms && this.rooms.length > 0) {
+ if (remoteStore.state.rooms && remoteStore.state.rooms.length > 0) {
return (
@@ -92,7 +77,7 @@ export class AppRemoteConnect {
}
private renderRooms() {
- return this.rooms.map((activeRoom: ActiveRoom) => {
+ return remoteStore.state.rooms.map((activeRoom: ActiveRoom) => {
return (
this.updateAndCloseModal(activeRoom.room)} disabled={activeRoom.connected}>
{activeRoom.room}
diff --git a/remote/src/app/pages/app-remote/app-remote.tsx b/remote/src/app/pages/app-remote/app-remote.tsx
index 1df208637..07514084a 100644
--- a/remote/src/app/pages/app-remote/app-remote.tsx
+++ b/remote/src/app/pages/app-remote/app-remote.tsx
@@ -1,10 +1,12 @@
import {Component, Element, Prop, State, h, JSX} from '@stencil/core';
import {alertController, modalController, OverlayEventDetail} from '@ionic/core';
-import {Subscription} from 'rxjs';
-
import {isMobile} from '@deckdeckgo/utils';
+import notesStores from '../../stores/notes.store';
+import remoteStore from '../../stores/remote.store';
+import accStore from '../../stores/accelerometer.store';
+
// Types
import {
DeckdeckgoEvent,
@@ -22,12 +24,11 @@ import {
// Utils
import {ParseSlidesUtils} from '../../utils/parse-slides.utils';
+import {ParseAttributesUtils} from '../../utils/parse-attributes.utils';
// Services
import {CommunicationService} from '../../services/communication/communication.service';
import {AccelerometerService} from '../../services/accelerometer/accelerometer.service';
-import {NotesService} from '../../services/notes/notes.service';
-import {ParseAttributesUtils} from '../../utils/parse-attributes.utils';
@Component({
tag: 'app-remote',
@@ -39,8 +40,8 @@ export class AppRemote {
@Prop()
room: string;
- private subscriptionState: Subscription;
- private subscriptionEvent: Subscription;
+ private stateDestroyListener;
+ private eventDestroyListener;
@State() private connectionState: ConnectionState = ConnectionState.DISCONNECTED;
@@ -64,17 +65,15 @@ export class AppRemote {
@State()
private clientId: string;
- private acceleratorSubscription: Subscription;
- private acceleratorInitSubscription: Subscription;
+ private destroyAcceleratorListener;
+ private destroyAcceleratorInitListener;
private communicationService: CommunicationService;
private accelerometerService: AccelerometerService;
- private notesService: NotesService;
constructor() {
this.communicationService = CommunicationService.getInstance();
this.accelerometerService = AccelerometerService.getInstance();
- this.notesService = NotesService.getInstance();
}
componentWillLoad() {
@@ -82,7 +81,7 @@ export class AppRemote {
}
async componentDidLoad() {
- this.subscriptionState = this.communicationService.watchState().subscribe(async (state: ConnectionState) => {
+ this.stateDestroyListener = remoteStore.onChange('state', async (state: ConnectionState) => {
this.connectionState = state;
if (state === ConnectionState.CONNECTED) {
@@ -93,7 +92,7 @@ export class AppRemote {
}
});
- this.subscriptionEvent = this.communicationService.watchEvent().subscribe(async ($event: DeckdeckgoEvent) => {
+ this.eventDestroyListener = remoteStore.onChange('$event', async ($event: DeckdeckgoEvent) => {
if ($event.emitter === DeckdeckgoEventEmitter.DECK) {
if ($event.type === DeckdeckgoEventType.SLIDES_ANSWER) {
await this.initDeckAndSlides($event as DeckdeckgoEventDeck);
@@ -125,7 +124,7 @@ export class AppRemote {
}
});
- this.acceleratorSubscription = this.accelerometerService.watch().subscribe(async (prev: boolean) => {
+ this.destroyAcceleratorListener = accStore.onChange('trigger', async (prev: boolean) => {
await this.prevNextSlide(prev, false);
setTimeout(async () => {
@@ -133,7 +132,7 @@ export class AppRemote {
}, this.accelerometerService.delay);
});
- this.acceleratorInitSubscription = this.accelerometerService.watchInitialized().subscribe(async (initialized: boolean) => {
+ this.destroyAcceleratorInitListener = accStore.onChange('initialized', async (initialized: boolean) => {
if (initialized) {
const deck: HTMLElement = this.el.querySelector('deckgo-deck');
@@ -186,20 +185,20 @@ export class AppRemote {
async componentDidUnload() {
await this.disconnect();
- if (this.subscriptionState) {
- this.subscriptionState.unsubscribe();
+ if (this.stateDestroyListener) {
+ this.stateDestroyListener();
}
- if (this.subscriptionEvent) {
- this.subscriptionEvent.unsubscribe();
+ if (this.eventDestroyListener) {
+ this.eventDestroyListener();
}
- if (this.acceleratorSubscription) {
- this.acceleratorSubscription.unsubscribe();
+ if (this.destroyAcceleratorListener) {
+ this.destroyAcceleratorListener();
}
- if (this.acceleratorInitSubscription) {
- this.acceleratorInitSubscription.unsubscribe();
+ if (this.destroyAcceleratorInitListener) {
+ this.destroyAcceleratorInitListener();
}
}
@@ -297,7 +296,7 @@ export class AppRemote {
next = this.el.querySelector('.deckgo-slide-container:nth-child(' + (this.slideIndex + 1) + ')');
}
- this.notesService.next(next);
+ notesStores.state.currentSlide = next;
}
private async lazyLoadPollContent($slideEvent: DeckdeckgoEventSlide) {
diff --git a/remote/src/app/pages/app-timer/app-timer.tsx b/remote/src/app/pages/app-timer/app-timer.tsx
index e2417c1c8..3d87ae4a5 100644
--- a/remote/src/app/pages/app-timer/app-timer.tsx
+++ b/remote/src/app/pages/app-timer/app-timer.tsx
@@ -1,31 +1,22 @@
-import {Component, Element, State, h} from '@stencil/core';
+import {Component, Element, State, h, Listen} from '@stencil/core';
import {DatetimeChangeEventDetail} from '@ionic/core';
import {differenceInMilliseconds, isAfter, startOfDay} from 'date-fns';
-import {BehaviorSubject, Subscription} from 'rxjs';
+import timerStore from '../../stores/timer.store';
import {Comparator} from '../../services/utils/utils';
-import {TimerInterval, TimerService} from '../../services/timer/timer.service';
import {NotificationService} from '../../services/notification/notification.service';
+import {TimerService} from '../../services/timer/timer.service';
@Component({
tag: 'app-timer',
- styleUrl: 'app-timer.scss'
+ styleUrl: 'app-timer.scss',
})
export class AppTimer {
@Element() el: HTMLElement;
- private watcherSubscription: Subscription;
- private timerSubscription: Subscription;
-
- @State()
- private timerRemaining: number;
-
- @State()
- private timerLength: number;
-
@State()
private timerRunning: boolean;
@@ -35,6 +26,8 @@ export class AppTimer {
private timerService: TimerService;
private notificationService: NotificationService;
+ private destroyListener;
+
constructor() {
this.timerService = TimerService.getInstance();
this.notificationService = NotificationService.getInstance();
@@ -43,8 +36,6 @@ export class AppTimer {
async componentDidLoad() {
this.notificationService.init();
- this.watchTimer();
-
this.timerRunning = await this.timerService.isTimerStarted();
if (this.timerRunning) {
@@ -53,36 +44,11 @@ export class AppTimer {
}
async componentDidUnload() {
- if (this.timerSubscription) {
- this.timerSubscription.unsubscribe();
- }
-
- if (this.watcherSubscription) {
- this.watcherSubscription.unsubscribe();
+ if (this.destroyListener) {
+ this.destroyListener();
}
}
- private watchTimer() {
- this.watcherSubscription = this.timerService.watch().subscribe((timer: BehaviorSubject) => {
- if (timer) {
- this.timerSubscription = timer.subscribe(
- (interval: TimerInterval) => {
- if (interval) {
- this.timerRemaining = interval.timerRemaining;
- this.timerLength = interval.timerLength;
- }
- },
- (_err) => {
- // Do nothing
- },
- async () => {
- await this.clearStopwatch();
- }
- );
- }
- });
- }
-
async startTimer(length: number) {
await this.timerService.start(length);
this.timerRunning = true;
@@ -99,18 +65,10 @@ export class AppTimer {
this.timerPause = pause;
}
- private async clearStopwatch() {
- if (this.timerSubscription) {
- this.timerSubscription.unsubscribe();
- }
-
- // Reset timerRemaining
- this.timerRemaining = 0;
- this.timerLength = 0;
+ @Listen('stopTimer', {target: 'document'})
+ async clearStopwatch() {
this.timerRunning = false;
- await this.timerService.clearEndAt();
-
await this.resetDatetime();
}
@@ -245,15 +203,15 @@ export class AppTimer {
value={startOfDay(new Date()).toDateString()}
onIonCancel={() => this.toggleFabActivated()}
onIonChange={(e: CustomEvent) => this.initTimerLengthAndStartTimer(e)}>
-
+ ,
];
}
private renderContent() {
- if (this.timerRemaining >= 0) {
+ if (timerStore.state.remainingTime >= 0 && timerStore.state.timer) {
return (
);
} else {
@@ -274,11 +232,11 @@ export class AppTimer {
}
private renderActions() {
- if (this.timerRunning === null || (this.timerRunning && this.timerRemaining === null)) {
+ if (this.timerRunning === null || (this.timerRunning && timerStore.state.remainingTime === null)) {
return undefined;
}
- const style = {visibility: `${this.timerRemaining === null || this.timerRemaining === undefined ? 'hidden' : 'inherit'}`};
+ const style = {visibility: `${timerStore.state.remainingTime === null || timerStore.state.remainingTime === undefined ? 'hidden' : 'inherit'}`};
return (
e.stopPropagation()} style={style}>
diff --git a/remote/src/app/services/accelerometer/accelerometer.service.tsx b/remote/src/app/services/accelerometer/accelerometer.service.tsx
index 704622f79..be332021b 100644
--- a/remote/src/app/services/accelerometer/accelerometer.service.tsx
+++ b/remote/src/app/services/accelerometer/accelerometer.service.tsx
@@ -1,4 +1,4 @@
-import {BehaviorSubject, Observable, Subject} from 'rxjs';
+import store from '../../stores/accelerometer.store';
import {get, set} from 'idb-keyval';
@@ -13,7 +13,6 @@ interface AccelerometerValues {
export class AccelerometerService {
private static instance: AccelerometerService;
- private enableSubject: BehaviorSubject = new BehaviorSubject(false);
private enable: boolean = false;
private permissionGranted: boolean = false;
@@ -32,10 +31,6 @@ export class AccelerometerService {
private sumAccelerationNext: number = 0;
private sumAccelerationPrev: number = 0;
- private triggerSubject: Subject = new Subject();
-
- private initializedSubject: BehaviorSubject = new BehaviorSubject(false);
-
private constructor() {
// Private constructor, singleton
}
@@ -98,7 +93,7 @@ export class AccelerometerService {
if (this.sensor.x > this.sensibility || this.sensor.x < this.sensibility * -1) {
// We are emitting the direction after a bit of time
if (this.takeValues >= this.takeUntil) {
- this.triggerSubject.next(this.sumAccelerationNext > this.sumAccelerationPrev);
+ store.state.trigger = this.sumAccelerationNext > this.sumAccelerationPrev;
// We are stopping to listen and will start again once the slide transition is done
this.stop();
@@ -123,28 +118,23 @@ export class AccelerometerService {
}
}
- toggle(): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- if (this.enable) {
- this.stop();
- } else {
- await this.askPermission();
- }
-
- this.toggleEnabled(!this.enable);
-
- resolve(this.enable);
- } catch (err) {
- this.toggleEnabled(false);
- reject(err);
+ async toggle() {
+ try {
+ if (this.enable) {
+ this.stop();
+ } else {
+ await this.askPermission();
}
- });
+
+ this.toggleEnabled(!this.enable);
+ } catch (err) {
+ this.toggleEnabled(false);
+ }
}
private toggleEnabled(enabled: boolean) {
this.enable = enabled;
- this.enableSubject.next(this.enable);
+ store.state.enable = this.enable;
}
private askPermission(): Promise {
@@ -172,17 +162,13 @@ export class AccelerometerService {
);
}
- watch(): Observable {
- return this.triggerSubject.asObservable();
- }
-
async save() {
await set('deckdeckgo_accelerometer', {
enable: this.enable,
frequency: this.frequency,
sensibility: this.sensibility,
takeUntil: this.takeUntil,
- delay: this.delay
+ delay: this.delay,
});
}
@@ -199,17 +185,9 @@ export class AccelerometerService {
await this.askPermission();
}
- this.enableSubject.next(this.enable);
+ store.state.enable = this.enable;
}
- this.initializedSubject.next(true);
- }
-
- watchInitialized(): Observable {
- return this.initializedSubject.asObservable();
- }
-
- watchEnabled(): Observable {
- return this.enableSubject.asObservable();
+ store.state.initialized = true;
}
}
diff --git a/remote/src/app/services/communication/communication.service.tsx b/remote/src/app/services/communication/communication.service.tsx
index 4bb9106d7..376122ac6 100644
--- a/remote/src/app/services/communication/communication.service.tsx
+++ b/remote/src/app/services/communication/communication.service.tsx
@@ -1,6 +1,6 @@
import * as io from 'socket.io-client';
-import {BehaviorSubject, Observable, Subject} from 'rxjs';
+import remoteStore from '../../stores/remote.store';
// Types
import {
@@ -9,7 +9,7 @@ import {
DeckdeckgoEventNextPrevSlide,
DeckdeckgoEventSlideAction,
DeckdeckgoEventSlideTo,
- ConnectionState
+ ConnectionState,
} from '@deckdeckgo/types';
// Services
@@ -20,17 +20,11 @@ const configuration: RTCConfiguration = {
{
urls: 'turn:api.deckdeckgo.com:3478',
username: 'user',
- credential: 'deckdeckgo'
- }
- ]
+ credential: 'deckdeckgo',
+ },
+ ],
};
-export interface ActiveRoom {
- room: string;
- clients: number;
- connected: boolean;
-}
-
// @ts-ignore
const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.msRTCPeerConnection;
@@ -51,10 +45,6 @@ export class CommunicationService {
.toString()
.replace(/\B(?=(\d{2})+(?!\d))/g, ' ');
- private state: BehaviorSubject = new BehaviorSubject(ConnectionState.DISCONNECTED);
- private event: Subject = new Subject();
- private rooms: BehaviorSubject = new BehaviorSubject([]);
-
private constructor() {
// Private constructor, singleton
}
@@ -73,14 +63,14 @@ export class CommunicationService {
return;
}
- this.state.next(ConnectionState.CONNECTING);
+ remoteStore.state.state = ConnectionState.CONNECTING;
const url: string = EnvironmentConfigService.getInstance().get('signalingServerUrl');
this.socket = io.connect(url, {
reconnectionAttempts: 5,
transports: ['websocket', 'xhr-polling'],
- query: 'type=app'
+ query: 'type=app',
});
this.socket.on('connect', async () => {
@@ -88,7 +78,7 @@ export class CommunicationService {
});
this.socket.on('active_rooms', async (data) => {
- this.rooms.next(data.rooms);
+ remoteStore.state.rooms = [...data.rooms];
});
this.socket.on('joined', async () => {
@@ -96,7 +86,7 @@ export class CommunicationService {
this.sendApp();
- this.state.next(ConnectionState.CONNECTED_WITH_SIGNALING_SERVER);
+ remoteStore.state.state = ConnectionState.CONNECTED_WITH_SIGNALING_SERVER;
});
this.socket.on('deck_joined', async () => {
@@ -124,7 +114,7 @@ export class CommunicationService {
}
},
(_err) => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ remoteStore.state.state = ConnectionState.NOT_CONNECTED;
}
);
} else {
@@ -134,23 +124,23 @@ export class CommunicationService {
});
this.socket.on('connect_error', () => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ remoteStore.state.state = ConnectionState.NOT_CONNECTED;
});
this.socket.on('connect_timeout', () => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ remoteStore.state.state = ConnectionState.NOT_CONNECTED;
});
this.socket.on('error', () => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ remoteStore.state.state = ConnectionState.NOT_CONNECTED;
});
this.socket.on('reconnect_failed', () => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ remoteStore.state.state = ConnectionState.NOT_CONNECTED;
});
this.socket.on('reconnect_error', () => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ remoteStore.state.state = ConnectionState.NOT_CONNECTED;
});
resolve();
@@ -163,7 +153,7 @@ export class CommunicationService {
}
this.socket.emit('join', {
- room: this.room
+ room: this.room,
});
}
@@ -182,13 +172,13 @@ export class CommunicationService {
if (this.socket) {
this.socket.emit('leave', {
- room: this.room
+ room: this.room,
});
this.socket.removeAllListeners();
this.socket.disconnect();
}
- this.state.next(ConnectionState.DISCONNECTED);
+ remoteStore.state.state = ConnectionState.DISCONNECTED;
resolve();
});
@@ -200,18 +190,6 @@ export class CommunicationService {
}
}
- watchState(): Observable {
- return this.state.asObservable();
- }
-
- watchEvent(): Observable {
- return this.event.asObservable();
- }
-
- watchRooms(): Observable {
- return this.rooms.asObservable();
- }
-
private startSignaling() {
this.rtcPeerConn = new PeerConnection(configuration);
@@ -223,7 +201,7 @@ export class CommunicationService {
this.socket.emit('signal', {
type: 'ice_candidate',
message: JSON.stringify({candidate: evt.candidate}),
- room: this.room
+ room: this.room,
});
}
};
@@ -235,7 +213,7 @@ export class CommunicationService {
this.socket.emit('signal', {
type: 'app_here',
room: this.room,
- message: this.clientId
+ message: this.clientId,
});
}
@@ -245,11 +223,11 @@ export class CommunicationService {
this.socket.emit('signal', {
type: 'sending_local_description',
message: JSON.stringify({sdp: this.rtcPeerConn.localDescription}),
- room: this.room
+ room: this.room,
});
},
(_err) => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ remoteStore.state.state = ConnectionState.NOT_CONNECTED;
}
);
}
@@ -261,7 +239,7 @@ export class CommunicationService {
};
private connectedWhenOpened = () => {
- this.state.next(ConnectionState.CONNECTED);
+ remoteStore.state.state = ConnectionState.CONNECTED;
};
private receiveDataChannelMessage = (event) => {
@@ -270,6 +248,6 @@ export class CommunicationService {
}
const data: DeckdeckgoEvent = JSON.parse(event.data);
- this.event.next(data);
+ remoteStore.state.$event = {...data};
};
}
diff --git a/remote/src/app/services/notes/notes.service.tsx b/remote/src/app/services/notes/notes.service.tsx
deleted file mode 100644
index 530bedd33..000000000
--- a/remote/src/app/services/notes/notes.service.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import {BehaviorSubject, Observable} from 'rxjs';
-
-export class NotesService {
- private static instance: NotesService;
-
- private currentSlide: BehaviorSubject = new BehaviorSubject(null);
-
- private constructor() {
- // Private constructor, singleton
- }
-
- static getInstance() {
- if (!NotesService.instance) {
- NotesService.instance = new NotesService();
- }
- return NotesService.instance;
- }
-
- watch(): Observable {
- return this.currentSlide.asObservable();
- }
-
- next(slide: HTMLElement) {
- this.currentSlide.next(slide);
- }
-}
diff --git a/remote/src/app/services/theme/theme.service.tsx b/remote/src/app/services/theme/theme.service.tsx
index ffaa52fd9..9e8e12205 100644
--- a/remote/src/app/services/theme/theme.service.tsx
+++ b/remote/src/app/services/theme/theme.service.tsx
@@ -1,12 +1,10 @@
-import {Observable, ReplaySubject} from 'rxjs';
+import themeStore 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);
+ themeStore.state.darkTheme = dark;
try {
await set('deckdeckgo_dark_mode', dark);
@@ -38,17 +32,17 @@ export class ThemeService {
// If user already specified once a preference, we use that as default
if (savedDarkModePreference !== undefined) {
- this.switch(savedDarkModePreference);
+ await this.switch(savedDarkModePreference);
return;
}
} catch (err) {
- this.switch(false);
+ await this.switch(false);
return;
}
// Otherwise we check the prefers-color-scheme of the OS
const darkModePreferenceFromMedia: MediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
- this.switch(darkModePreferenceFromMedia.matches);
+ await this.switch(darkModePreferenceFromMedia.matches);
}
}
diff --git a/remote/src/app/services/timer/timer.service.tsx b/remote/src/app/services/timer/timer.service.tsx
index 85014a38b..d1dbae75f 100644
--- a/remote/src/app/services/timer/timer.service.tsx
+++ b/remote/src/app/services/timer/timer.service.tsx
@@ -1,40 +1,30 @@
-import {Observable, interval, Subject, BehaviorSubject, Subscription} from 'rxjs';
-import {filter, takeUntil, withLatestFrom} from 'rxjs/operators';
+import {Build} from '@stencil/core';
import {differenceInMilliseconds, addMilliseconds, isAfter} from 'date-fns';
import {get, set, del} from 'idb-keyval';
+import timerStore from '../../stores/timer.store';
+
import {Comparator, Converter} from '../utils/utils';
-import {NotificationService} from '../notification/notification.service';
-import {Build} from '@stencil/core';
-export interface TimerInterval {
- timerProgression: number;
- timerRemaining: number;
- timerLength: number;
-}
+import {NotificationService} from '../notification/notification.service';
export class TimerService {
private static instance: TimerService;
- private timerSubject: BehaviorSubject> = new BehaviorSubject(null);
-
- private intervalSubject: BehaviorSubject;
-
- private stopTimer: Subject = new Subject();
- private pauseTimer: Subject = new Subject();
-
private timerPausedAt: Date;
- private stopwatchSubscription: Subscription;
-
private timerEnd: Date;
private timerLength: number;
private timerProgression: number;
private notificationService: NotificationService = NotificationService.getInstance();
+ private timerInterval: NodeJS.Timeout;
+
+ private pauseTimer: boolean = false;
+
private constructor() {
// Private constructor, singleton
}
@@ -46,12 +36,20 @@ export class TimerService {
return TimerService.instance;
}
- destroy() {
- this.stopTimer.next();
+ async destroy() {
+ await this.clear();
+ }
+
+ private async clear() {
+ timerStore.reset();
- if (this.stopwatchSubscription) {
- this.stopwatchSubscription.unsubscribe();
+ if (this.timerInterval) {
+ clearInterval(this.timerInterval);
}
+
+ await del('deckdeckgo_timer_end');
+ await del('deckdeckgo_timer_length');
+ await del('deckdeckgo_timer_progression');
}
start(length: number): Promise {
@@ -65,9 +63,9 @@ export class TimerService {
await set('deckdeckgo_timer_length', this.timerLength);
await set('deckdeckgo_timer_progression', this.timerProgression);
- this.initTimer();
+ this.pauseTimer = false;
- this.pauseTimer.next(false);
+ this.initTimer();
resolve();
} catch (e) {
@@ -86,9 +84,9 @@ export class TimerService {
this.timerLength = parseInt(await get('deckdeckgo_timer_length'), 0);
this.timerProgression = parseInt(await get('deckdeckgo_timer_progression'), 0);
- this.initTimer();
+ this.pauseTimer = false;
- this.pauseTimer.next(false);
+ this.initTimer();
}
resolve();
@@ -99,66 +97,45 @@ export class TimerService {
}
private initTimer() {
- this.intervalSubject = new BehaviorSubject(null);
-
- this.stopwatchSubscription = interval(1000)
- .pipe(
- takeUntil(this.stopTimer),
- withLatestFrom(this.pauseTimer.asObservable()),
- filter(([_v, paused]) => !paused)
- )
- .subscribe(async ([_intervalValue, _paused]: [number, boolean]) => {
- if (this.timerEnd) {
- const now: Date = new Date();
-
- if (isAfter(this.timerEnd, now)) {
- this.timerProgression = this.timerProgression + 1000;
- const timerRemaining: number = this.timerLength - this.timerProgression;
-
- await set('deckdeckgo_timer_progression', this.timerProgression);
-
- this.intervalSubject.next({
- timerProgression: this.timerProgression,
- timerRemaining: timerRemaining,
- timerLength: this.timerLength
- });
-
- await this.showWarnNotification(timerRemaining);
- } else {
- await this.stop(true);
- }
- }
- });
+ this.timerInterval = setInterval(async () => {
+ if (this.timerEnd && !this.pauseTimer) {
+ const now: Date = new Date();
- this.timerSubject.next(this.intervalSubject);
- }
+ if (isAfter(this.timerEnd, now)) {
+ this.timerProgression = this.timerProgression + 1000;
+ const timerRemaining: number = this.timerLength - this.timerProgression;
- watch(): Observable> {
- return this.timerSubject.asObservable();
- }
+ await set('deckdeckgo_timer_progression', this.timerProgression);
+
+ timerStore.state.timer = {
+ timerProgression: this.timerProgression,
+ timerRemaining: timerRemaining,
+ timerLength: this.timerLength,
+ };
- pause(state: boolean): Promise {
- return new Promise((resolve) => {
- if (state) {
- this.timerPausedAt = new Date();
- } else {
- const pausedFor: number = differenceInMilliseconds(new Date(), this.timerPausedAt);
- this.timerEnd = addMilliseconds(this.timerEnd, pausedFor);
+ await this.showWarnNotification(timerRemaining);
+ } else {
+ await this.stop(true);
+ }
}
+ }, 1000);
+ }
- this.pauseTimer.next(state);
+ async pause(state: boolean) {
+ this.pauseTimer = state;
- resolve();
- });
+ if (state) {
+ this.timerPausedAt = new Date();
+ } else {
+ const pausedFor: number = differenceInMilliseconds(new Date(), this.timerPausedAt);
+ this.timerEnd = addMilliseconds(this.timerEnd, pausedFor);
+ }
}
async stop(notification: boolean) {
- if (!this.intervalSubject) {
- return;
- }
+ await this.clear();
- this.intervalSubject.complete();
- this.stopTimer.next();
+ this.emitStepEvent();
if (notification) {
// Star Wars shamelessly taken from the awesome Peter Beverloo as in the Google example
@@ -180,11 +157,19 @@ export class TimerService {
110,
170,
40,
- 500
+ 500,
]);
}
}
+ private emitStepEvent() {
+ const stopTimer: CustomEvent = new CustomEvent('stopTimer', {
+ bubbles: true,
+ });
+
+ document.dispatchEvent(stopTimer);
+ }
+
private showWarnNotification(timerRemaining: number): Promise {
return new Promise(async (resolve) => {
if (timerRemaining === 60000) {
@@ -224,8 +209,4 @@ export class TimerService {
}
});
}
-
- clearEndAt(): Promise {
- return del('deckdeckgo_timer_end');
- }
}
diff --git a/remote/src/app/stores/accelerometer.store.ts b/remote/src/app/stores/accelerometer.store.ts
new file mode 100644
index 000000000..e89dd72b1
--- /dev/null
+++ b/remote/src/app/stores/accelerometer.store.ts
@@ -0,0 +1,15 @@
+import {createStore} from '@stencil/store';
+
+interface AccelerometerStore {
+ enable: boolean;
+ initialized: boolean;
+ trigger: boolean | undefined;
+}
+
+const {state, onChange} = createStore({
+ enable: false,
+ initialized: false,
+ trigger: undefined,
+} as AccelerometerStore);
+
+export default {state, onChange};
diff --git a/remote/src/app/stores/notes.store.ts b/remote/src/app/stores/notes.store.ts
new file mode 100644
index 000000000..17c84b5ea
--- /dev/null
+++ b/remote/src/app/stores/notes.store.ts
@@ -0,0 +1,11 @@
+import {createStore} from '@stencil/store';
+
+interface NotesStore {
+ currentSlide: HTMLElement | undefined;
+}
+
+const {state, onChange} = createStore({
+ currentSlide: undefined,
+} as NotesStore);
+
+export default {state, onChange};
diff --git a/remote/src/app/stores/remote.store.ts b/remote/src/app/stores/remote.store.ts
new file mode 100644
index 000000000..d018e70bf
--- /dev/null
+++ b/remote/src/app/stores/remote.store.ts
@@ -0,0 +1,23 @@
+import {createStore} from '@stencil/store';
+
+import {ConnectionState, DeckdeckgoEvent} from '@deckdeckgo/types';
+
+export interface ActiveRoom {
+ room: string;
+ clients: number;
+ connected: boolean;
+}
+
+interface RemoteStore {
+ state: ConnectionState;
+ $event: DeckdeckgoEvent | undefined;
+ rooms: ActiveRoom[];
+}
+
+const {state, onChange} = createStore({
+ state: ConnectionState.DISCONNECTED,
+ $event: undefined,
+ rooms: [],
+} as RemoteStore);
+
+export default {state, onChange};
diff --git a/remote/src/app/stores/theme.store.ts b/remote/src/app/stores/theme.store.ts
new file mode 100644
index 000000000..0114b74cf
--- /dev/null
+++ b/remote/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};
diff --git a/remote/src/app/stores/timer.store.ts b/remote/src/app/stores/timer.store.ts
new file mode 100644
index 000000000..07dbdd2cd
--- /dev/null
+++ b/remote/src/app/stores/timer.store.ts
@@ -0,0 +1,35 @@
+import {createStore} from '@stencil/store';
+
+import {addMilliseconds, formatDistanceStrict} from 'date-fns';
+
+export interface TimerInterval {
+ timerProgression: number;
+ timerRemaining: number;
+ timerLength: number;
+}
+
+interface TimerStore {
+ timer: TimerInterval | undefined;
+ remainingTime: number;
+ remainingText: string | undefined;
+}
+
+const {state, onChange, reset} = createStore({
+ timer: undefined,
+ remainingTime: 0,
+ remainingText: undefined,
+} as TimerStore);
+
+onChange('timer', (interval: TimerInterval | undefined) => {
+ if (interval) {
+ state.remainingTime = interval.timerRemaining;
+
+ const end: Date = addMilliseconds(new Date(), interval.timerRemaining);
+ state.remainingText = formatDistanceStrict(end, new Date());
+ } else {
+ state.remainingTime = 0;
+ state.remainingText = formatDistanceStrict(new Date(), new Date());
+ }
+});
+
+export default {state, onChange, reset};
diff --git a/studio/package-lock.json b/studio/package-lock.json
index ad3e45575..269a1c3fc 100644
--- a/studio/package-lock.json
+++ b/studio/package-lock.json
@@ -133,13 +133,12 @@
"integrity": "sha512-Y2Mn70b1R/opDV3LgkUrma5+hsOcION6v6xg0Bu378gKaQt9A1pHiyenXzFQ0H7CRpmb4CNjpYcIuIWVy63OHQ=="
},
"@deckdeckgo/remote": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@deckdeckgo/remote/-/remote-1.1.1.tgz",
- "integrity": "sha512-nMktWQnUszn9T0rcq0LGx3plTkqkbZkxSefO47UG0RizFDiJvoEH8upjdFFwjMcrbYOwlraY4U28RD4AxA150A==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@deckdeckgo/remote/-/remote-1.2.0.tgz",
+ "integrity": "sha512-w18pFE8ASDFCzQYMMKkGhk5lCOZp4d18bqaWsJZHkZ33tS4MtCkKwNyKg0fB6YS/Fj7kUi+MV//Uq7lwHvbn5A==",
"requires": {
"@deckdeckgo/remote-utils": "^1.0.0",
"@deckdeckgo/utils": "^1.1.0",
- "rxjs": "^6.5.5",
"socket.io-client": "^2.3.0"
}
},
@@ -682,6 +681,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",
@@ -2000,14 +2005,6 @@
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
- "rxjs": {
- "version": "6.5.5",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
- "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
- "requires": {
- "tslib": "^1.9.0"
- }
- },
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
diff --git a/studio/package.json b/studio/package.json
index 294008ffb..91d100891 100644
--- a/studio/package.json
+++ b/studio/package.json
@@ -27,7 +27,7 @@
"@deckdeckgo/lazy-img": "^1.0.1",
"@deckdeckgo/math": "^1.0.1",
"@deckdeckgo/qrcode": "^1.0.1",
- "@deckdeckgo/remote": "^1.1.1",
+ "@deckdeckgo/remote": "^1.2.0",
"@deckdeckgo/slide-aspect-ratio": "^1.0.1",
"@deckdeckgo/slide-author": "^1.0.1",
"@deckdeckgo/slide-chart": "^1.0.1",
@@ -46,7 +46,6 @@
"@ionic/core": "^5.2.2",
"firebase": "^7.15.4",
"idb-keyval": "^3.2.0",
- "rxjs": "^6.5.5",
"socket.io-client": "^2.3.0",
"uuid": "^8.2.0",
"web-social-share": "^6.4.0"
@@ -57,6 +56,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..2c3b93614 100644
--- a/studio/src/app/app-root.tsx
+++ b/studio/src/app/app-root.tsx
@@ -2,15 +2,14 @@ import {Build, Component, Element, h, Listen, State} from '@stencil/core';
import {toastController} from '@ionic/core';
-import {Subscription} from 'rxjs';
+import errorStore from './stores/error.store';
+import navStore from './stores/nav.store';
-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';
-
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',
@@ -19,28 +18,20 @@ 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;
- private navService: NavService;
-
- private themeSubscription: Subscription;
private themeService: ThemeService;
private offlineService: OfflineService;
- private domBodyClassList: DOMTokenList = document.body.classList;
-
@State()
private loading: boolean = true;
+ private destroyErrorListener;
+ private destroyNavListener;
+
constructor() {
- this.errorService = ErrorService.getInstance();
this.authService = AuthService.getInstance();
- this.navService = NavService.getInstance();
this.themeService = ThemeService.getInstance();
this.offlineService = OfflineService.getInstance();
}
@@ -56,30 +47,24 @@ export class AppRoot {
async componentDidLoad() {
this.loading = false;
- this.errorSubscription = this.errorService.watch().subscribe(async (error: string) => {
- await this.toastError(error);
+ this.destroyErrorListener = errorStore.onChange('error', (error: string | undefined) => {
+ if (error) {
+ this.toastError(error);
+ }
});
- this.navSubscription = this.navService.watch().subscribe(async (params: NavParams) => {
+ this.destroyNavListener = navStore.onChange('nav', async (params: NavParams | undefined) => {
await this.navigate(params);
});
-
- this.themeSubscription = this.themeService.watch().subscribe((dark: boolean) => {
- this.updateDarkModePreferences(dark);
- });
}
- async componentDidUnload() {
- if (this.errorSubscription) {
- this.errorSubscription.unsubscribe();
- }
-
- if (this.navSubscription) {
- this.navSubscription.unsubscribe();
+ componentDidUnload() {
+ if (this.destroyErrorListener) {
+ this.destroyErrorListener();
}
- if (this.themeSubscription) {
- this.themeSubscription.unsubscribe();
+ if (this.destroyNavListener) {
+ this.destroyNavListener();
}
}
@@ -100,10 +85,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-menu/app-menu.tsx b/studio/src/app/components/core/app-menu/app-menu.tsx
index e58aea550..7bdbfcd60 100644
--- a/studio/src/app/components/core/app-menu/app-menu.tsx
+++ b/studio/src/app/components/core/app-menu/app-menu.tsx
@@ -1,61 +1,39 @@
-import {Component, Element, State, h} from '@stencil/core';
+import {Component, Element, h} from '@stencil/core';
-import {Subscription} from 'rxjs';
-
-import {AuthUser} from '../../../models/auth/auth.user';
-
-import {Utils} from '../../../utils/core/utils';
+import navStore from '../../../stores/nav.store';
+import authStore from '../../../stores/auth.store';
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;
private authService: AuthService;
- private authSubscription: Subscription;
-
- private navService: NavService;
-
- @State()
- private authUser: AuthUser;
constructor() {
this.authService = AuthService.getInstance();
- this.navService = NavService.getInstance();
- }
-
- componentWillLoad() {
- this.authSubscription = this.authService.watch().subscribe(async (authUser: AuthUser) => {
- this.authUser = authUser;
- });
- }
-
- componentDidUnload() {
- if (this.authSubscription) {
- this.authSubscription.unsubscribe();
- }
}
private async signIn() {
- this.navService.navigate({
+ navStore.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({
+ navStore.state.nav = {
url: '/',
- direction: NavDirection.ROOT
- });
+ direction: NavDirection.ROOT,
+ };
}
render() {
@@ -73,7 +51,7 @@ export class AppMenu {
}
private renderUser() {
- if (Utils.isLoggedIn(this.authUser)) {
+ if (authStore.state.loggedIn) {
return (
@@ -85,7 +63,7 @@ export class AppMenu {
}
private renderDashboard() {
- if (Utils.isLoggedIn(this.authUser)) {
+ if (authStore.state.loggedIn) {
return (
@@ -98,7 +76,7 @@ export class AppMenu {
}
private renderSignInOut() {
- if (Utils.isLoggedIn(this.authUser)) {
+ if (authStore.state.loggedIn) {
return (
this.signOut()}>
@@ -125,7 +103,7 @@ export class AppMenu {
}
private renderDiscover() {
- if (Utils.isLoggedIn(this.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 3dfe02e8d..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
@@ -1,105 +1,39 @@
-import {Component, Event, EventEmitter, Prop, State, h} from '@stencil/core';
+import {Component, Event, EventEmitter, Prop, h} from '@stencil/core';
import {popoverController} from '@ionic/core';
-import {Subscription} from 'rxjs';
-
-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 {NavDirection, NavService} from '../../../services/core/nav/nav.service';
-import {UserService} from '../../../services/data/user/user.service';
-import {ThemeService} from '../../../services/theme/theme.service';
+import themeStore from '../../../stores/theme.store';
+import navStore, {NavDirection} from '../../../stores/nav.store';
+import authStore from '../../../stores/auth.store';
+import userStore from '../../../stores/user.store';
@Component({
tag: 'app-navigation-actions',
styleUrl: 'app-navigation-actions.scss',
- shadow: false
+ shadow: false,
})
export class AppNavigationActions {
@Prop() signIn: boolean = true;
@Prop() presentation: boolean = false;
@Prop() publish: boolean = false;
- private authService: AuthService;
- private subscription: Subscription;
-
- private navService: NavService;
-
- private userService: UserService;
- private userSubscription: Subscription;
-
- private themeService: ThemeService;
- private themeSubscription: Subscription;
-
- @State()
- private authUser: AuthUser;
-
- @State()
- private photoUrl: string;
-
- @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() {
- 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;
- });
-
- this.themeSubscription = this.themeService.watch().subscribe((dark: boolean) => {
- this.darkMode = dark;
- });
- }
-
- componentDidUnload() {
- if (this.subscription) {
- this.subscription.unsubscribe();
- }
-
- 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();
}
private async navigateSignIn() {
- this.navService.navigate({
+ navStore.state.nav = {
url: '/signin' + (window && window.location ? window.location.pathname : ''),
- direction: NavDirection.FORWARD
- });
+ direction: NavDirection.FORWARD,
+ };
}
render() {
@@ -115,7 +49,7 @@ export class AppNavigationActions {
}
private renderFeed() {
- if (Utils.isLoggedIn(this.authUser) || !this.signIn) {
+ if (authStore.state.loggedIn || !this.signIn) {
return undefined;
} else if (this.presentation || this.publish) {
return (
@@ -127,7 +61,7 @@ export class AppNavigationActions {
}
private renderSignIn() {
- if (Utils.isLoggedIn(this.authUser) || !this.signIn) {
+ if (authStore.state.loggedIn || !this.signIn) {
return undefined;
} else if (this.presentation || this.publish) {
return (
@@ -139,10 +73,10 @@ export class AppNavigationActions {
}
private renderLoggedIn() {
- if (Utils.isLoggedIn(this.authUser) && this.photoUrlLoaded) {
+ if (authStore.state.loggedIn && userStore.state.loaded) {
return (
this.openMenu(e)} aria-label="Open menu" tabindex={0}>
-
+
);
} else {
@@ -153,7 +87,13 @@ export class AppNavigationActions {
private renderPresentationButton() {
if (this.presentation) {
return (
-
+
Write a presentation
);
@@ -165,7 +105,12 @@ 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={themeStore.state.darkTheme ? 'light' : 'dark'}>
Ready to share?
);
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..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,16 +1,13 @@
-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 {Deck} from '../../../models/data/deck';
-
-import {DeckEditorService} from '../../../services/editor/deck/deck-editor.service';
-import {OfflineService} from '../../../services/editor/offline/offline.service';
+import store from '../../../stores/deck.store';
@Component({
tag: 'app-navigation',
styleUrl: 'app-navigation.scss',
- shadow: false
+ shadow: false,
})
export class AppNavigation {
@Prop() menuToggle: boolean = true;
@@ -20,46 +17,9 @@ 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();
- }
- }
-
render() {
return (
-
+
{this.renderTitleOnline()}
{this.renderTitleOffline()}
@@ -72,11 +32,11 @@ export class AppNavigation {
}
private renderTitleOnline() {
- if (this.offline !== undefined) {
+ if (offlineStore.state.offline !== undefined) {
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,23 +44,23 @@ export class AppNavigation {
{this.renderLogo()}
- {this.deckName}
+ {store.state.name}
);
}
private renderTitleOffline() {
- if (this.offline === undefined) {
+ if (offlineStore.state.offline === undefined) {
return undefined;
}
return (
-
+
{this.renderLogo()}
- {this.offline.name}
+ {offlineStore.state.offline.name}
@@ -136,7 +96,7 @@ export class AppNavigation {
}
private renderMenuToggle() {
- if (this.offline !== undefined) {
+ if (offlineStore.state.offline !== undefined) {
return undefined;
}
@@ -156,7 +116,7 @@ export class AppNavigation {
}
private renderUser() {
- if (this.offline !== undefined) {
+ if (offlineStore.state.offline !== undefined) {
return undefined;
}
@@ -172,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/core/app-user-info/app-user-info.tsx b/studio/src/app/components/core/app-user-info/app-user-info.tsx
index 38c3598a4..9b6148bdc 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
@@ -1,92 +1,32 @@
-import {Component, Prop, State, h} from '@stencil/core';
+import {Component, Prop, h} from '@stencil/core';
-import {Subscription} from 'rxjs';
-
-import {AuthUser} from '../../../models/auth/auth.user';
-import {User} from '../../../models/data/user';
-
-import {ApiUser} from '../../../models/api/api.user';
-
-import {AuthService} from '../../../services/auth/auth.service';
-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';
+import authStore from '../../../stores/auth.store';
+import userStore from '../../../stores/user.store';
+import apiUserStore from '../../../stores/api.user.store';
@Component({
tag: 'app-user-info',
- styleUrl: 'app-user-info.scss'
+ styleUrl: 'app-user-info.scss',
})
export class AppUserInfo {
@Prop()
avatarColSize: number = 4;
- private authService: AuthService;
- private authSubscription: Subscription;
-
- private apiUserService: ApiUserService;
- private apiUserSubscription: Subscription;
-
- private userService: UserService;
- private userSubscription: Subscription;
-
- @State()
- private authUser: AuthUser;
-
- @State()
- private apiUser: ApiUser;
-
- @State()
- private name: string;
-
- @State()
- private photoUrl: string;
-
- constructor() {
- this.authService = AuthService.getInstance();
- this.apiUserService = ApiUserFactoryService.getInstance();
- this.userService = UserService.getInstance();
- }
-
- componentWillLoad() {
- this.authSubscription = this.authService.watch().subscribe((authUser: AuthUser) => {
- this.authUser = authUser;
- });
-
- 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.authSubscription) {
- this.authSubscription.unsubscribe();
- }
-
- if (this.apiUserSubscription) {
- this.apiUserSubscription.unsubscribe();
- }
-
- if (this.userSubscription) {
- this.userSubscription.unsubscribe();
- }
- }
-
render() {
- if (this.authUser) {
+ if (authStore.state.authUser) {
return (
-
+
- {this.name}
- {!this.authUser.anonymous && this.apiUser && this.apiUser.username ? '@' + this.apiUser.username : undefined}
+ {userStore.state.name}
+
+ {!authStore.state.authUser.anonymous && apiUserStore.state.apiUser && apiUserStore.state.apiUser.username
+ ? '@' + apiUserStore.state.apiUser.username
+ : undefined}
+
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/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 (
- this.selectStep(step)} disabled={this.busy}>
+ this.selectStep(step)} disabled={store.state.deckBusy}>
{step}
);
}
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/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/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..56dd515b8 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,8 +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';
@@ -17,8 +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 {OfflineService} from '../../../../../services/editor/offline/offline.service';
-import {RemoteService} from '../../../../../services/editor/remote/remote.service';
import {PlaygroundAction} from '../../../../../utils/editor/playground-action';
@Component({
@@ -67,45 +65,22 @@ 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) => {
+ const destroyListener = remoteStore.onChange('pendingRequests', async (requests: DeckdeckgoEventDeckRequest[] | undefined) => {
if (requests && requests.length > 0) {
await this.clickToOpenRemote();
}
- });
- }
-
- async componentDidUnload() {
- if (this.offlineSubscription) {
- this.offlineSubscription.unsubscribe();
- }
- if (this.remoteSubscription) {
- this.remoteSubscription.unsubscribe();
- }
+ destroyListener();
+ });
}
async onActionOpenSlideAdd($event: CustomEvent) {
@@ -392,7 +367,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 +448,7 @@ export class AppActionsDeck {
const modal: HTMLIonModalElement = await modalController.create({
component: 'app-offline',
componentProps: {
- offline: this.offline,
+ offline: offlineStore.state.offline,
},
cssClass: 'fullscreen',
});
@@ -482,47 +457,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() {
@@ -576,8 +543,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/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..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,9 @@
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 {debounce, isFullscreen, isIOS, isMobile} from '@deckdeckgo/utils';
-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 +20,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,16 +67,9 @@ 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;
- private moveToolbarSubject: Subject = new Subject();
+ private readonly debounceResizeSlideContent: () => void;
@Event() signIn: EventEmitter;
@@ -89,18 +79,12 @@ export class AppActionsElement {
@Event() private resetted: EventEmitter;
constructor() {
- this.busyService = BusyService.getInstance();
+ this.debounceResizeSlideContent = debounce(async () => {
+ await this.resizeSlideContent();
+ }, 250);
}
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();
- });
-
this.imageHelper = new ImageHelper(this.slideDidChange, this.blockSlide, this.signIn);
this.shapeHelper = new ShapeHelper(this.slideDidChange, this.signIn);
}
@@ -110,26 +94,18 @@ export class AppActionsElement {
}
async componentDidUnload() {
- if (this.subscription) {
- this.subscription.unsubscribe();
- }
-
- 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);
}
}
@@ -401,12 +377,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 +415,7 @@ export class AppActionsElement {
return;
}
- if (this.deckBusy || !this.shape) {
+ if (store.state.deckBusy || !this.shape) {
resolve();
return;
}
@@ -457,12 +433,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);
@@ -800,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();
@@ -815,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();
@@ -1051,7 +1027,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 +1039,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 +1050,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 +1148,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/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/offline/app-go-offline/app-go-offline.tsx b/studio/src/app/components/editor/offline/app-go-offline/app-go-offline.tsx
index f17e2fbf3..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,12 +1,12 @@
import {Component, h, State, Event, EventEmitter} from '@stencil/core';
-import {Subscription} from 'rxjs';
+import errorStore from '../../../../stores/error.store';
+import offlineStore from '../../../../stores/offline.store';
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()
@@ -18,29 +18,10 @@ export class AppGoOffline {
@Event()
private inProgress: EventEmitter;
- @State()
- private progress: number = 0;
-
private offlineService: OfflineService;
- private errorService: ErrorService;
-
- private progressSubscription: Subscription;
constructor() {
this.offlineService = OfflineService.getInstance();
- this.errorService = ErrorService.getInstance();
- }
-
- componentWillLoad() {
- this.progressSubscription = this.offlineService.watchProgress().subscribe((progress: number) => {
- this.progress = progress;
- });
- }
-
- componentDidUnload() {
- if (this.progressSubscription) {
- this.progressSubscription.unsubscribe();
- }
}
private async goOffline() {
@@ -54,7 +35,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.');
+ errorStore.state.error = 'Apologies, something went wrong and app was unable to go offline.';
}
}
@@ -84,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 2c0787906..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,12 +1,12 @@
import {Component, h, State, Event, EventEmitter} from '@stencil/core';
-import {Subscription} from 'rxjs';
+import errorStore from '../../../../stores/error.store';
+import offlineStore from '../../../../stores/offline.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()
@@ -21,29 +21,10 @@ export class AppGoOnline {
@Event()
private inProgress: EventEmitter;
- @State()
- private progress: number = 0;
-
private offlineService: OfflineService;
- private errorService: ErrorService;
-
- private progressSubscription: Subscription;
constructor() {
this.offlineService = OfflineService.getInstance();
- this.errorService = ErrorService.getInstance();
- }
-
- componentWillLoad() {
- this.progressSubscription = this.offlineService.watchProgress().subscribe((progress: number) => {
- this.progress = progress;
- });
- }
-
- componentDidUnload() {
- if (this.progressSubscription) {
- this.progressSubscription.unsubscribe();
- }
}
private async goOnline() {
@@ -60,7 +41,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!');
+ errorStore.state.error = 'Something went wrong. Double check your internet connection and try again. If it still does not work, contact us!';
}
}
@@ -95,7 +76,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.
,
];
}
@@ -109,7 +90,7 @@ export class AppGoOnline {
} else {
return (
-
+
Hang on still, we are uploading the content.
);
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..ddfd7c294 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,20 +1,19 @@
import {Component, Event, EventEmitter, h, State} from '@stencil/core';
-import {Subject, Subscription} from 'rxjs';
-import {debounceTime, 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';
+import publishStore from '../../../../stores/publish.store';
+import apiUserStore from '../../../../stores/api.user.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';
-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 {
data: string | null;
@@ -22,7 +21,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,98 +45,43 @@ export class AppPublishEdit {
@State()
private tags: string[] = [];
- private deckEditorService: DeckEditorService;
private deckService: DeckService;
- private errorService: ErrorService;
-
- private updateDeckSubscription: Subscription;
- private updateDeckSubject: Subject = new Subject();
+ private readonly debounceUpdateDeck: () => void;
@Event() private published: EventEmitter;
- private apiUser: ApiUser;
- private apiUserService: ApiUserService;
-
private publishService: PublishService;
- private feedService: FeedService;
-
- @State()
- private progress: number = 0;
-
- private progressSubscription: Subscription;
-
constructor() {
- this.deckEditorService = DeckEditorService.getInstance();
this.deckService = DeckService.getInstance();
- this.errorService = ErrorService.getInstance();
-
- this.apiUserService = ApiUserFactoryService.getInstance();
-
this.publishService = PublishService.getInstance();
- this.feedService = FeedService.getInstance();
+ this.debounceUpdateDeck = debounce(async () => {
+ await this.updateDeck();
+ }, 500);
}
async componentWillLoad() {
- this.deckEditorService
- .watch()
- .pipe(take(1))
- .subscribe(async (deck: Deck) => {
- await this.init(deck);
- });
-
- this.progressSubscription = this.publishService.watchProgress().subscribe((progress: number) => {
- this.progress = progress;
- });
-
- this.apiUserService
- .watch()
- .pipe(
- filter((apiUser: ApiUser) => apiUser !== null && apiUser !== undefined && !apiUser.anonymous),
- take(1)
- )
- .subscribe(async (apiUser: ApiUser) => {
- this.apiUser = apiUser;
- });
-
- this.updateDeckSubscription = this.updateDeckSubject
- .asObservable()
- .pipe(debounceTime(500))
- .subscribe(async () => {
- await this.updateDeck();
- });
+ await this.init();
}
componentDidLoad() {
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[]) : [];
-
- resolve();
- });
- }
-
- componentDidUnload() {
- if (this.updateDeckSubscription) {
- this.updateDeckSubscription.unsubscribe();
+ private async init() {
+ if (!deckStore.state.deck || !deckStore.state.deck.data) {
+ return;
}
- if (this.progressSubscription) {
- this.progressSubscription.unsubscribe();
- }
+ this.caption = deckStore.state.deck.data.name;
+ this.description =
+ deckStore.state.deck.data.meta && deckStore.state.deck.data.meta.description
+ ? (deckStore.state.deck.data.meta.description as string)
+ : await this.getFirstSlideContent();
+ this.tags = deckStore.state.deck.data.meta && deckStore.state.deck.data.meta.tags ? (deckStore.state.deck.data.meta.tags as string[]) : [];
}
private getFirstSlideContent(): Promise {
@@ -173,26 +117,21 @@ 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 (!deckStore.state.deck || !deckStore.state.deck.data || !deckStore.state.deck.id) {
+ this.disablePublish = false;
+ resolve();
+ return;
+ }
+
+ deckStore.state.deck.data.name = this.caption;
+
+ 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();
@@ -217,12 +156,12 @@ 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) {
this.publishing = false;
- this.errorService.error(err);
+ errorStore.state.error = err;
resolve();
}
});
@@ -239,7 +178,7 @@ export class AppPublishEdit {
this.caption = title;
- this.updateDeckSubject.next();
+ this.debounceUpdateDeck();
resolve();
});
@@ -451,14 +390,14 @@ export class AppPublishEdit {
private renderPublish() {
if (!this.publishing) {
return (
-
+
Publish now
);
} else {
return (
-
+
Hang on, we are publishing your presentation
);
diff --git a/studio/src/app/components/editor/styles/deck/app-deck-transition/app-deck-transition.tsx b/studio/src/app/components/editor/styles/deck/app-deck-transition/app-deck-transition.tsx
index fea1a0f48..9b30f327f 100644
--- a/studio/src/app/components/editor/styles/deck/app-deck-transition/app-deck-transition.tsx
+++ b/studio/src/app/components/editor/styles/deck/app-deck-transition/app-deck-transition.tsx
@@ -1,7 +1,5 @@
import {Component, Element, Event, EventEmitter, Prop, h, State} from '@stencil/core';
-import {interval, Subscription} from 'rxjs';
-
@Component({
tag: 'app-deck-transition',
styleUrl: 'app-deck-transition.scss',
@@ -17,7 +15,8 @@ export class AppDeckTransition {
@State()
private selectedTransition: 'slide' | 'fade' | 'none';
- private timerSubscription: Subscription;
+ private timerInterval: NodeJS.Timeout;
+ private timerCounter: number = 0;
async componentWillLoad() {
await this.initSelectedTransition();
@@ -28,8 +27,8 @@ export class AppDeckTransition {
}
componentDidUnload() {
- if (this.timerSubscription) {
- this.timerSubscription.unsubscribe();
+ if (this.timerInterval) {
+ clearInterval(this.timerInterval);
}
}
@@ -48,19 +47,21 @@ export class AppDeckTransition {
}
private async animateDecks() {
- this.timerSubscription = interval(2000).subscribe(async (val: number) => {
+ this.timerInterval = setInterval(async () => {
const elements: NodeListOf = this.el.querySelectorAll('deckgo-deck');
if (elements) {
for (const element of Array.from(elements)) {
- if (val % 2 === 0) {
+ if (this.timerCounter % 2 === 0) {
await (element as any).slideNext();
} else {
await (element as any).slidePrev();
}
}
}
- });
+
+ this.timerCounter++;
+ }, 2000);
}
private applyTransition(transition: 'slide' | 'fade' | 'none'): Promise {
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..dcbc4a3f8 100644
--- a/studio/src/app/components/feed/app-feed/app-feed.tsx
+++ b/studio/src/app/components/feed/app-feed/app-feed.tsx
@@ -1,33 +1,24 @@
import {Component, h, State, Host} from '@stencil/core';
-import {Subscription} from 'rxjs';
-
import {isMobile} from '@deckdeckgo/utils';
+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',
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,51 +27,28 @@ export class AppFeed {
private presentationUrl: string = EnvironmentConfigService.getInstance().get('deckdeckgo').presentationUrl;
- private subscription: Subscription;
- private lastPageSubscription: Subscription;
-
- private offlineSubscription: Subscription;
- private offlineService: OfflineService;
-
constructor() {
this.feedService = FeedService.getInstance();
- this.offlineService = OfflineService.getInstance();
}
async componentWillLoad() {
- this.subscription = this.feedService.watchDecks().subscribe((decks: Deck[]) => {
- this.decks = decks;
- this.decksFetched = true;
- });
+ this.initOffline(offlineStore.state.offline);
- this.lastPageSubscription = this.feedService.watchLastPageReached().subscribe((lastPageReached: boolean) => {
- this.lastPageReached = lastPageReached;
- this.decksFetched = lastPageReached;
- });
+ const destroyListner = offlineStore.onChange('offline', (offline: OfflineDeck | undefined) => {
+ this.initOffline(offline);
- this.offlineSubscription = this.offlineService.watchOffline().subscribe((offline: OfflineDeck | undefined) => {
- this.offline = navigator && !navigator.onLine && offline !== undefined ? offline : undefined;
+ destroyListner();
});
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.subscription) {
- this.subscription.unsubscribe();
- }
-
- if (this.lastPageSubscription) {
- this.lastPageSubscription.unsubscribe();
- }
-
- if (this.offlineSubscription) {
- this.offlineSubscription.unsubscribe();
- }
+ async componentDidLoad() {
+ await this.feedService.find();
}
private async findNextDecks($event) {
@@ -125,12 +93,12 @@ export class AppFeed {
{this.renderDecks()}
- this.findNextDecks($event)} disabled={this.lastPageReached}>
+ this.findNextDecks($event)} disabled={feedStore.state.lastPageReached}>
,
-
+ ,
];
}
@@ -148,8 +116,8 @@ export class AppFeed {
}
private renderDecks() {
- if (this.decks && this.decks.length > 0) {
- return this.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}>
@@ -162,7 +130,7 @@ export class AppFeed {
}
private renderLoading() {
- if (this.decksFetched) {
+ if (feedStore.state.decks !== undefined) {
return undefined;
} else {
return (
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());
}
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..9abc1f0a6 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,14 +1,17 @@
import {ItemReorderEventDetail} from '@ionic/core';
-import {Subject, Subscription} from 'rxjs';
-import {debounceTime, filter, take} from 'rxjs/operators';
+import {debounce} from '@deckdeckgo/utils';
+
+import deckStore from '../../../../stores/deck.store';
+import errorStore from '../../../../stores/error.store';
+import busyStore from '../../../../stores/busy.store';
+import authStore from '../../../../stores/auth.store';
import {cleanContent} from '@deckdeckgo/deck-utils';
import * as firebase from 'firebase/app';
import 'firebase/firestore';
-import {AuthUser} from '../../../../models/auth/auth.user';
import {Deck, DeckAttributes, DeckData} from '../../../../models/data/deck';
import {Slide, SlideAttributes, SlideAttributesYAxisDomain, SlideChartType, SlideData, SlideSplitType, SlideTemplate} from '../../../../models/data/slide';
@@ -17,42 +20,31 @@ 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 {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';
export class DeckEventsHandler {
private el: HTMLElement;
- private errorService: ErrorService;
- private busyService: BusyService;
-
- private authService: AuthService;
-
- private updateSlideSubscription: Subscription;
- private updateSlideSubject: Subject = new Subject();
-
- private deckEditorService: DeckEditorService;
-
- 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.errorService = ErrorService.getInstance();
- this.busyService = BusyService.getInstance();
+ this.deckService = DeckService.getInstance();
+ this.slideService = SlideService.getInstance();
- this.authService = AuthService.getInstance();
+ this.debounceUpdateSlide = debounce(async (element: HTMLElement) => {
+ await this.updateSlide(element);
- this.deckEditorService = DeckEditorService.getInstance();
+ await this.emitSlideDidUpdate(element);
+ }, 500);
- this.deckService = DeckService.getInstance();
- this.slideService = SlideService.getInstance();
+ this.debounceUpdateDeckTitle = debounce(async (title: string) => {
+ await this.updateDeckTitle(title);
+ }, 500);
}
init(el: HTMLElement): Promise {
@@ -75,16 +67,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();
});
}
@@ -105,14 +87,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) => {
@@ -141,7 +115,7 @@ export class DeckEventsHandler {
return;
}
- this.updateSlideSubject.next($event.detail);
+ this.debounceUpdateSlide($event.detail);
};
private onCustomEventChange = async ($event: CustomEvent) => {
@@ -157,7 +131,7 @@ export class DeckEventsHandler {
return;
}
- this.updateSlideSubject.next(parent);
+ this.debounceUpdateSlide(parent);
};
private onInputChange = async ($event: Event) => {
@@ -177,11 +151,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);
}
};
@@ -203,35 +177,31 @@ export class DeckEventsHandler {
if (slide.getAttribute('slide_id')) {
// !isNew
- this.busyService.slideEditable(slide);
+ busyStore.state.slideEditable = slide;
resolve();
return;
}
- this.busyService.deckBusy(true);
+ busyStore.state.deckBusy = true;
- this.deckEditorService
- .watch()
- .pipe(take(1))
- .subscribe(async (deck: Deck) => {
- if (!deck) {
- deck = await this.createDeck();
- }
+ if (!deckStore.state.deck) {
+ const persistedDeck: Deck = await this.createDeck();
+ deckStore.state.deck = {...persistedDeck};
+ }
- const persistedSlide: Slide = await this.postSlide(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(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(deckStore.state.deck, persistedSlide);
- this.busyService.deckBusy(false);
+ busyStore.state.deckBusy = false;
- resolve();
- });
+ resolve();
} catch (err) {
- this.errorService.error(err);
- this.busyService.deckBusy(false);
+ errorStore.state.error = err;
+ busyStore.state.deckBusy = false;
resolve();
}
});
@@ -259,7 +229,7 @@ export class DeckEventsHandler {
if (persistedSlide && persistedSlide.id) {
slide.setAttribute('slide_id', persistedSlide.id);
- this.busyService.slideEditable(slide);
+ busyStore.state.slideEditable = slide;
}
resolve(persistedSlide);
@@ -269,32 +239,29 @@ export class DeckEventsHandler {
private createDeck(): Promise {
return new Promise(async (resolve, reject) => {
try {
- this.authService
- .watch()
- .pipe(
- filter((user: AuthUser) => user !== null && user !== undefined),
- take(1)
- )
- .subscribe(async (authUser: AuthUser) => {
- let deck: DeckData = {
- name: `Presentation ${await Utils.getNow()}`,
- owner_id: authUser.uid,
- };
-
- // Retrieve text and background color style randomly generated in the editor
- const deckElement: HTMLElement = this.el.querySelector('deckgo-deck');
- if (deckElement) {
- const attributes: DeckAttributes = await this.getDeckAttributes(deckElement, false);
- deck.attributes = attributes;
- }
+ if (!authStore.state.authUser) {
+ reject('User not authenticated');
+ return;
+ }
- const persistedDeck: Deck = await this.deckService.create(deck);
- this.deckEditorService.next(persistedDeck);
+ let deck: DeckData = {
+ name: `Presentation ${await Utils.getNow()}`,
+ owner_id: authStore.state.authUser.uid,
+ };
- await this.updateNavigation(persistedDeck);
+ // Retrieve text and background color style randomly generated in the editor
+ const deckElement: HTMLElement = this.el.querySelector('deckgo-deck');
+ if (deckElement) {
+ const attributes: DeckAttributes = await this.getDeckAttributes(deckElement, false);
+ deck.attributes = attributes;
+ }
- resolve(persistedDeck);
- });
+ const persistedDeck: Deck = await this.deckService.create(deck);
+ deckStore.state.deck = {...persistedDeck};
+
+ await this.updateNavigation(persistedDeck);
+
+ resolve(persistedDeck);
} catch (err) {
reject(err);
}
@@ -321,7 +288,7 @@ export class DeckEventsHandler {
deck.data.slides.push(slide.id);
const updatedDeck: Deck = await this.deckService.update(deck);
- this.deckEditorService.next(updatedDeck);
+ deckStore.state.deck = {...updatedDeck};
resolve();
} catch (err) {
@@ -351,36 +318,33 @@ export class DeckEventsHandler {
return;
}
- this.busyService.deckBusy(true);
+ busyStore.state.deckBusy = true;
- this.deckEditorService
- .watch()
- .pipe(take(1))
- .subscribe(async (currentDeck: Deck) => {
- if (!currentDeck || !currentDeck.data) {
- resolve();
- return;
- }
+ const currentDeck: Deck | null = deckStore.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);
+ deckStore.state.deck = {...updatedDeck};
- this.busyService.deckBusy(false);
+ busyStore.state.deckBusy = false;
- resolve();
- });
+ resolve();
} catch (err) {
- this.errorService.error(err);
- this.busyService.deckBusy(false);
+ errorStore.state.error = err;
+ busyStore.state.deckBusy = false;
resolve();
}
});
@@ -394,36 +358,32 @@ export class DeckEventsHandler {
return;
}
- this.busyService.deckBusy(true);
+ busyStore.state.deckBusy = true;
- this.deckEditorService
- .watch()
- .pipe(take(1))
- .subscribe(async (currentDeck: Deck) => {
- if (!currentDeck || !currentDeck.data) {
- resolve();
- return;
- }
+ const currentDeck: Deck | null = deckStore.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);
+ deckStore.state.deck = {...updatedDeck};
- this.busyService.deckBusy(false);
+ busyStore.state.deckBusy = false;
- resolve();
- });
+ resolve();
} catch (err) {
- this.errorService.error(err);
- this.busyService.deckBusy(false);
+ errorStore.state.error = err;
+ busyStore.state.deckBusy = false;
resolve();
}
});
@@ -438,7 +398,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;
}
@@ -464,21 +424,16 @@ 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 (deckStore.state.deck) {
+ await this.slideService.update(deckStore.state.deck.id, slideUpdate);
+ }
- this.busyService.deckBusy(false);
+ busyStore.state.deckBusy = false;
- resolve();
- });
+ resolve();
} catch (err) {
- this.errorService.error(err);
- this.busyService.deckBusy(false);
+ errorStore.state.error = err;
+ busyStore.state.deckBusy = false;
resolve();
}
});
@@ -493,46 +448,43 @@ 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');
- this.deckEditorService
- .watch()
- .pipe(take(1))
- .subscribe(async (deck: Deck) => {
- if (deck && deck.data) {
- const slide: Slide = await this.slideService.get(deck.id, slideId);
+ const currentDeck: Deck | null = deckStore.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.
+ 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);
+ // 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 (deck.data.slides && deck.data.slides.indexOf(slideId) > -1) {
- deck.data.slides.splice(deck.data.slides.indexOf(slideId), 1);
+ // 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(deck);
- this.deckEditorService.next(updatedDeck);
- }
- }
+ const updatedDeck: Deck = await this.deckService.update(currentDeck);
+ deckStore.state.deck = {...updatedDeck};
}
+ }
+ }
- await this.deleteSlideElement();
+ await this.deleteSlideElement();
- this.busyService.deckBusy(false);
+ busyStore.state.deckBusy = false;
- resolve();
- });
+ resolve();
} catch (err) {
- this.errorService.error(err);
- this.busyService.deckBusy(false);
+ errorStore.state.error = err;
+ busyStore.state.deckBusy = false;
resolve();
}
});
@@ -909,19 +861,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 = deckStore.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);
+ deckStore.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..74b1f7480 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,6 @@
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';
@@ -17,44 +16,40 @@ export class RemoteEventsHandler {
private remoteService: RemoteService;
- private connectSubscription: Subscription;
- private acceptRequestSubscription: Subscription;
+ private destroyConnectListener;
+ private destroyAcceptRequestListener;
constructor() {
this.remoteService = RemoteService.getInstance();
}
- init(el: HTMLElement): Promise {
- return new Promise(async (resolve) => {
- this.el = el;
+ async init(el: HTMLElement) {
+ this.el = el;
- await this.initRemote();
+ await this.initRemote();
- this.connectSubscription = this.remoteService.watch().subscribe(async (enable: boolean) => {
- if (enable) {
- await this.connect();
- } else {
- await this.disconnect();
- }
- });
-
- this.acceptRequestSubscription = this.remoteService.watchAcceptedRequest().subscribe(async (request: DeckdeckgoEventDeckRequest) => {
- await this.startAcceptedRemoteRequest(request);
- });
+ this.destroyConnectListener = remoteStore.onChange('remote', async (enable: boolean) => {
+ if (enable) {
+ await this.connect();
+ } else {
+ await this.disconnect();
+ }
+ });
- resolve();
+ this.destroyAcceptRequestListener = remoteStore.onChange('acceptedRequest', async (request: DeckdeckgoEventDeckRequest) => {
+ await this.startAcceptedRemoteRequest(request);
});
}
async destroy() {
await this.disconnect();
- if (this.connectSubscription) {
- this.connectSubscription.unsubscribe();
+ if (this.destroyConnectListener) {
+ this.destroyConnectListener();
}
- if (this.acceptRequestSubscription) {
- this.acceptRequestSubscription.unsubscribe();
+ if (this.destroyAcceptRequestListener) {
+ this.destroyAcceptRequestListener();
}
const deckgoRemoteElement: HTMLElement = this.el.querySelector('deckgo-remote');
@@ -148,8 +143,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 +262,8 @@ export class RemoteEventsHandler {
}
deck.addEventListener('slidesDidLoad', async ($event: CustomEvent) => {
+ await this.remoteService.init();
+
await this.initRemoteSize();
await this.initRemoteSlides($event);
@@ -418,6 +413,11 @@ export class RemoteEventsHandler {
const room: string = await this.remoteService.getRoom();
+ if (!room) {
+ resolve();
+ return;
+ }
+
deckgoRemoteElement.room = room;
await deckgoRemoteElement.connect();
@@ -488,7 +488,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();
});
@@ -597,7 +597,7 @@ export class RemoteEventsHandler {
return;
}
- this.executeIfConnected(this.deleteRemoteSlide);
+ await this.executeIfConnected(this.deleteRemoteSlide);
};
private slideDidUpdate = async ($event: CustomEvent) => {
@@ -605,7 +605,7 @@ export class RemoteEventsHandler {
return;
}
- this.executeIfConnected(this.updateCurrentSlideWithDefinition);
+ await this.executeIfConnected(this.updateCurrentSlideWithDefinition);
};
private pollUpdated = async ($event: CustomEvent) => {
@@ -613,7 +613,7 @@ export class RemoteEventsHandler {
return;
}
- this.executeIfConnected(this.updatePollSlideWithDefinition, $event.target);
+ await this.executeIfConnected(this.updatePollSlideWithDefinition, $event.target);
};
private deckDidChange = async ($event: CustomEvent) => {
@@ -621,18 +621,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/helpers/editor/editor.helper.tsx b/studio/src/app/helpers/editor/editor.helper.tsx
index f0ac4ece3..15a93601e 100644
--- a/studio/src/app/helpers/editor/editor.helper.tsx
+++ b/studio/src/app/helpers/editor/editor.helper.tsx
@@ -1,57 +1,47 @@
import {JSX} from '@stencil/core';
-import {take} from 'rxjs/operators';
+
+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 {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';
export class EditorHelper {
- private errorService: ErrorService;
- private busyService: BusyService;
-
- private deckEditorService: DeckEditorService;
-
private slideService: SlideService;
private deckService: DeckService;
constructor() {
this.slideService = SlideService.getInstance();
- this.errorService = ErrorService.getInstance();
- this.busyService = BusyService.getInstance();
-
- this.deckEditorService = DeckEditorService.getInstance();
-
this.deckService = DeckService.getInstance();
}
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;
}
- this.busyService.deckBusy(true);
+ busyStore.state.deckBusy = true;
try {
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;
}
- this.deckEditorService.next(deck);
+ store.state.deck = {...deck};
if (!deck.data.slides || deck.data.slides.length <= 0) {
resolve([]);
@@ -73,12 +63,12 @@ export class EditorHelper {
return;
}
- this.busyService.deckBusy(false);
+ busyStore.state.deckBusy = false;
resolve(parsedSlides);
} catch (err) {
- this.errorService.error(err);
- this.busyService.deckBusy(false);
+ errorStore.state.error = err;
+ busyStore.state.deckBusy = false;
resolve(null);
}
});
@@ -92,7 +82,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);
}
});
@@ -107,31 +97,26 @@ 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;
}
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);
+ busyStore.state.deckBusy = false;
- resolve(element);
- });
+ resolve(element);
} catch (err) {
- this.errorService.error(err);
- this.busyService.deckBusy(false);
+ errorStore.state.error = err;
+ 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..970254d6f 100644
--- a/studio/src/app/helpers/editor/image.helper.tsx
+++ b/studio/src/app/helpers/editor/image.helper.tsx
@@ -1,23 +1,17 @@
import {EventEmitter} from '@stencil/core';
import {modalController, OverlayEventDetail} from '@ionic/core';
+import busyStore from '../../stores/busy.store';
+import authStore from '../../stores/auth.store';
+
import {ImageAction} from '../../utils/editor/image-action';
import {EditAction} from '../../utils/editor/edit-action';
import {SlotUtils} from '../../utils/editor/slot.utils';
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();
- }
+ constructor(private didChange: EventEmitter, private blockSlide: EventEmitter, private signIn: EventEmitter) {}
async imageAction(selectedElement: HTMLElement, slide: boolean, deck: boolean, imageAction: ImageAction) {
if (imageAction.action === EditAction.OPEN_PHOTOS) {
@@ -35,7 +29,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) => {
@@ -58,9 +52,7 @@ export class ImageHelper {
}
async openCustomModalRestricted(selectedElement: HTMLElement, slide: boolean, deck: boolean, componentTag: string, action: EditAction) {
- const isAnonymous: boolean = await this.anonymousService.isAnonymous();
-
- if (isAnonymous) {
+ if (authStore.state.anonymous) {
this.signIn.emit();
return;
}
@@ -75,7 +67,7 @@ export class ImageHelper {
return;
}
- this.busyService.deckBusy(true);
+ busyStore.state.deckBusy = true;
if (slide || deck) {
await this.appendBackgroundImg(selectedElement, image, deck);
@@ -102,7 +94,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 +212,7 @@ export class ImageHelper {
return;
}
- this.busyService.deckBusy(true);
+ busyStore.state.deckBusy = true;
selectedElement.removeAttribute('img-src');
@@ -237,7 +229,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..5c0ce27da 100644
--- a/studio/src/app/helpers/editor/shape.helper.tsx
+++ b/studio/src/app/helpers/editor/shape.helper.tsx
@@ -2,23 +2,17 @@ import {EventEmitter} from '@stencil/core';
import {modalController, OverlayEventDetail} from '@ionic/core';
+import busyStore from '../../stores/busy.store';
+import authStore from '../../stores/auth.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();
- }
+ constructor(private didChange: EventEmitter, private signIn: EventEmitter) {}
async appendShape(slideElement: HTMLElement, shapeAction: ShapeAction) {
if (shapeAction.svg) {
@@ -29,7 +23,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 +41,7 @@ export class ShapeHelper {
}
async cloneShape(shapeElement: HTMLElement) {
- this.busyService.deckBusy(true);
+ busyStore.state.deckBusy = true;
await this.cloneShapeElement(shapeElement);
}
@@ -56,16 +50,14 @@ 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');
}
}
private async openModalRestricted(slideElement: HTMLElement) {
- const isAnonymous: boolean = await this.anonymousService.isAnonymous();
-
- if (isAnonymous) {
+ if (authStore.state.anonymous) {
this.signIn.emit();
return;
}
@@ -75,7 +67,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/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..e9c656ed7 100644
--- a/studio/src/app/pages/core/app-dashboard/app-dashboard.tsx
+++ b/studio/src/app/pages/core/app-dashboard/app-dashboard.tsx
@@ -1,24 +1,23 @@
import {Component, h, JSX, State} from '@stencil/core';
-import {filter, take} from 'rxjs/operators';
-
import {convertStyle} from '@deckdeckgo/deck-utils';
-import {AuthUser} from '../../../models/auth/auth.user';
+import authStore from '../../../stores/auth.store';
+
import {Deck} from '../../../models/data/deck';
import {Slide} from '../../../models/data/slide';
+import {AuthUser} from '../../../models/auth/auth.user';
import {ParseBackgroundUtils} from '../../../utils/editor/parse-background.utils';
-import {ParseSlidesUtils} from '../../../utils/editor/parse-slides.utils';
-import {AuthService} from '../../../services/auth/auth.service';
+import {ParseSlidesUtils} from '../../../utils/editor/parse-slides.utils';
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 {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;
@@ -32,18 +31,11 @@ interface DeckAndFirstSlide {
styleUrl: 'app-dashboard.scss',
})
export class AppDashboard {
- @State()
- private authUser: AuthUser;
-
@State()
private filteredDecks: DeckAndFirstSlide[] = null;
private decks: DeckAndFirstSlide[] = null;
- private authService: AuthService;
-
- private navService: NavService;
-
private deckService: DeckService;
private slideService: SlideService;
@@ -53,8 +45,6 @@ export class AppDashboard {
private chartEventsHandler: ChartEventsHandler = new ChartEventsHandler();
constructor() {
- this.authService = AuthService.getInstance();
- this.navService = NavService.getInstance();
this.deckService = DeckService.getInstance();
this.slideService = SlideService.getInstance();
this.deckDashboardService = DeckDashboardService.getInstance();
@@ -64,22 +54,26 @@ export class AppDashboard {
await this.imageEventsHandler.init();
await this.chartEventsHandler.init();
- this.authService
- .watch()
- .pipe(
- filter((authUser: AuthUser) => authUser !== null && authUser !== undefined && !authUser.anonymous),
- take(1)
- )
- .subscribe(async (authUser: AuthUser) => {
- this.authUser = authUser;
-
- const userDecks: Deck[] = await this.deckService.getUserDecks(authUser.uid);
- this.decks = await this.fetchFirstSlides(userDecks);
- await this.filterDecks(null);
-
- // If some decks are currently cloned, we watch them to update GUI when clone has finished processing
- await this.initWatchForClonedDecks();
- });
+ const destroyListener = authStore.onChange('authUser', async (_authUser: AuthUser | null) => {
+ await this.initDashboard(destroyListener);
+ });
+
+ await this.initDashboard(destroyListener);
+ }
+
+ private async initDashboard(destroyListener) {
+ if (!authStore.state.authUser) {
+ return;
+ }
+
+ destroyListener();
+
+ const userDecks: Deck[] = await this.deckService.getUserDecks(authStore.state.authUser.uid);
+ this.decks = await this.fetchFirstSlides(userDecks);
+ await this.filterDecks(null);
+
+ // If some decks are currently cloned, we watch them to update GUI when clone has finished processing
+ await this.initWatchForClonedDecks();
}
componentDidUnload() {
@@ -223,10 +217,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 +252,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 {
@@ -407,7 +401,7 @@ export class AppDashboard {
}
private renderGuardedContent() {
- if (!this.authUser) {
+ if (!authStore.state.authUser) {
return this.renderNotLoggedInContent();
} else {
return this.renderContent();
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..ceedecbe0 100644
--- a/studio/src/app/pages/core/app-poll/app-poll.tsx
+++ b/studio/src/app/pages/core/app-poll/app-poll.tsx
@@ -1,25 +1,22 @@
import {Component, h, Prop, State} from '@stencil/core';
-import {Subscription} from 'rxjs';
+import errorStore from '../../../stores/error.store';
+import pollStore from '../../../stores/poll.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})
pollKey: string;
- @State()
- private poll: DeckdeckgoPoll;
-
@State()
private choice: string;
@@ -35,21 +32,17 @@ export class AppPoll {
private keywords: string[] = ['You did it', 'Applause', 'Thumbs up', 'Congratulations'];
private pollService: PollService;
- private errorService: ErrorService;
- private subscription: Subscription;
+ private destroyPollListener;
constructor() {
this.pollService = PollService.getInstance();
- this.errorService = ErrorService.getInstance();
}
async componentWillLoad() {
- this.subscription = this.pollService.watch().subscribe((poll: DeckdeckgoPoll) => {
- this.poll = poll;
-
+ this.destroyPollListener = pollStore.onChange('poll', (poll: DeckdeckgoPoll | undefined) => {
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.');
+ errorStore.state.error = 'Oopsie the poll was not found. Double check that the code is correct and try again.';
}
this.connecting = false;
@@ -63,8 +56,8 @@ export class AppPoll {
async componentDidUnload() {
await this.pollService.disconnect();
- if (this.subscription) {
- this.subscription.unsubscribe();
+ if (this.destroyPollListener) {
+ this.destroyPollListener();
}
}
@@ -75,7 +68,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;
}
@@ -84,13 +77,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) {
- this.errorService.error(err);
+ errorStore.state.error = err;
}
}
@@ -131,7 +124,7 @@ export class AppPoll {
private cancel() {
this.pollKey = undefined;
this.hasVoted = false;
- this.poll = undefined;
+ pollStore.reset();
}
render() {
@@ -143,7 +136,7 @@ export class AppPoll {
{this.renderJoinPoll()}
{this.renderHasVoted()}
-
+ ,
];
}
@@ -152,28 +145,28 @@ 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} ,
+ ,
];
}
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}
@@ -188,7 +181,7 @@ export class AppPoll {
return undefined;
}
- if (this.poll && this.poll.poll) {
+ if (pollStore.state.poll && pollStore.state.poll.poll) {
return undefined;
}
@@ -198,7 +191,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 9ef9366c8..50c321eb5 100644
--- a/studio/src/app/pages/core/app-settings/app-settings.tsx
+++ b/studio/src/app/pages/core/app-settings/app-settings.tsx
@@ -1,21 +1,22 @@
import {Component, Listen, State, h, Element} from '@stencil/core';
import {loadingController, modalController, OverlayEventDetail} from '@ionic/core';
-import {filter, take} from 'rxjs/operators';
-
import firebase from '@firebase/app';
import '@firebase/auth';
+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 apiUserStore from '../../../stores/api.user.store';
+
import {ApiUser} from '../../../models/api/api.user';
-import {AuthUser} from '../../../models/auth/auth.user';
import {User} from '../../../models/data/user';
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';
@@ -24,14 +25,11 @@ 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;
- @State()
- private authUser: AuthUser;
-
@State()
private user: User;
@@ -51,14 +49,9 @@ export class AppHome {
@State()
private saving: boolean = false;
- private authService: AuthService;
private userService: UserService;
private apiUserService: ApiUserService;
- private navService: NavService;
-
- private errorService: ErrorService;
-
private imageHistoryService: ImageHistoryService;
private profilePicture: File;
@@ -85,84 +78,86 @@ export class AppHome {
@State()
private custom: string = undefined;
- @State()
- private darkTheme: boolean;
+ private destroyUserListener;
+ private destroyApiUserListener;
constructor() {
- 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();
this.themeService = ThemeService.getInstance();
}
- componentWillLoad() {
- this.authService
- .watch()
- .pipe(
- filter((authUser: AuthUser) => authUser !== null && authUser !== undefined && !authUser.anonymous),
- take(1)
- )
- .subscribe(async (authUser: AuthUser) => {
- this.authUser = authUser;
- });
+ async componentDidLoad() {
+ const promises: Promise[] = [this.initUser(), this.initApiUser()];
+ await Promise.all(promises);
+ }
- this.apiUserService
- .watch()
- .pipe(
- filter((apiUser: ApiUser) => apiUser !== null && apiUser !== undefined && !apiUser.anonymous),
- take(1)
- )
- .subscribe(async (apiUser: ApiUser) => {
- this.apiUser = apiUser;
+ private async initUser() {
+ if (userStore.state.user) {
+ await this.initSocial();
+ } else {
+ this.destroyUserListener = userStore.onChange('user', async () => {
+ await this.initSocial();
+ });
+ }
+ }
- this.apiUsername = this.apiUser.username;
+ private async initApiUser() {
+ if (apiUserStore.state.apiUser) {
+ await this.initUsername();
+ } else {
+ this.destroyApiUserListener = apiUserStore.onChange('apiUser', async () => {
+ await this.initUsername();
});
+ }
+ }
- 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;
+ componentDidUnload() {
+ if (this.destroyUserListener) {
+ this.destroyUserListener();
+ }
- await this.initSocial();
- });
+ if (this.destroyApiUserListener) {
+ this.destroyApiUserListener();
+ }
+ }
- this.themeService
- .watch()
- .pipe(take(1))
- .subscribe((dark: boolean) => {
- this.darkTheme = dark;
- });
+ private initUsername() {
+ if (!apiUserStore.state.apiUser || apiUserStore.state.apiUser === undefined || apiUserStore.state.apiUser.anonymous) {
+ return;
+ }
+
+ this.apiUser = apiUserStore.state.apiUser;
+
+ this.apiUsername = this.apiUser.username;
}
- private initSocial(): Promise {
- return new Promise((resolve) => {
- if (!this.user || !this.user.data || !this.user.data.social) {
- resolve();
- return;
- }
+ private async initSocial() {
+ if (!userStore.state.user || userStore.state.user === undefined || !userStore.state.user.data || userStore.state.user.data.anonymous) {
+ 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;
- });
+ 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() {
- this.navService.navigate({
+ navStore.state.nav = {
url: '/signin' + (window && window.location ? window.location.pathname : ''),
- direction: NavDirection.FORWARD
- });
+ direction: NavDirection.FORWARD,
+ };
}
@Listen('keydown')
@@ -253,7 +248,7 @@ export class AppHome {
this.saving = false;
} catch (err) {
- this.errorService.error(err);
+ errorStore.state.error = err;
this.saving = false;
}
@@ -293,7 +288,7 @@ export class AppHome {
this.apiUser.username = this.apiUsername;
try {
- await this.apiUserService.put(this.apiUser, this.authUser.token, this.apiUser.id);
+ await this.apiUserService.put(this.apiUser, authStore.state.authUser.token, this.apiUser.id);
resolve();
} catch (err) {
@@ -332,8 +327,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) => {
@@ -356,10 +351,10 @@ export class AppHome {
if (firebaseUser) {
// We need the user token to access the API, therefore delete it here first
- await this.apiUserService.delete(this.apiUser.id, this.authUser.token);
+ await this.apiUserService.delete(this.apiUser.id, authStore.state.authUser.token);
// Then delete the user
- await this.userService.delete(this.authUser.uid);
+ await this.userService.delete(authStore.state.authUser.uid);
// Decks and slides are delete with a cloud function triggered on auth.delete
@@ -368,16 +363,16 @@ export class AppHome {
await this.imageHistoryService.clear();
- this.navService.navigate({
+ navStore.state.nav = {
url: '/',
- direction: NavDirection.ROOT
- });
+ direction: NavDirection.ROOT,
+ };
await loading.dismiss();
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";
}
});
}
@@ -407,12 +402,12 @@ export class AppHome {
{this.renderDarkLightToggle()}
{this.renderGuardedContent()}
-
+ ,
];
}
private renderGuardedContent() {
- if (!this.authUser) {
+ if (!authStore.state.authUser || authStore.state.anonymous) {
return this.renderNotLoggedInContent();
} else {
return [this.renderUserContent(), this.renderDangerZone()];
@@ -426,7 +421,7 @@ export class AppHome {
Sign in
to access your profile and settings.
-
+ ,
];
}
@@ -448,7 +443,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 +463,7 @@ export class AppHome {
disabled={this.saving}
onIonInput={($event: CustomEvent) => this.handleNameInput($event)}
onIonChange={() => this.validateNameInput()}>
-
+ ,
];
}
@@ -497,13 +492,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(!themeStore.state.darkTheme);
}
private renderDarkLightToggle() {
@@ -512,11 +506,11 @@ export class AppHome {
- {this.darkTheme ? 'Dark' : 'Light'} theme {this.darkTheme ? '🌑' : '☀️'}
+ {themeStore.state.darkTheme ? 'Dark' : 'Light'} theme {themeStore.state.darkTheme ? '🌑' : '☀️'}
- this.toggleTheme()}>
+ this.toggleTheme()}>
-
+ ,
];
}
@@ -536,7 +530,7 @@ export class AppHome {
input-mode="text"
onIonInput={($event: CustomEvent) => this.handleUsernameInput($event)}
onIonChange={() => this.validateUsernameInput()}>
-
+ ,
];
}
@@ -557,9 +551,9 @@ export class AppHome {
shape="round"
fill="outline"
onClick={() => this.presentConfirmDelete()}
- disabled={this.saving || !this.apiUser || !this.authUser}>
+ disabled={this.saving || !this.apiUser || !authStore.state.authUser}>
Delete my user
-
+ ,
];
}
@@ -639,7 +633,7 @@ export class AppHome {
input-mode="text"
disabled={this.saving}
onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'twitter')}>
-
+ ,
];
}
@@ -661,7 +655,7 @@ export class AppHome {
input-mode="text"
disabled={this.saving}
onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'linkedin')}>
-
+ ,
];
}
@@ -683,7 +677,7 @@ export class AppHome {
input-mode="text"
disabled={this.saving}
onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'dev')}>
-
+ ,
];
}
@@ -705,7 +699,7 @@ export class AppHome {
input-mode="text"
disabled={this.saving}
onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'medium')}>
-
+ ,
];
}
@@ -727,7 +721,7 @@ export class AppHome {
input-mode="text"
disabled={this.saving}
onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'github')}>
-
+ ,
];
}
@@ -749,7 +743,7 @@ export class AppHome {
input-mode="text"
disabled={this.saving}
onIonInput={($event: CustomEvent) => this.handleSocialInput($event, 'custom')}>
-
+ ,
];
}
}
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..2f3940073 100644
--- a/studio/src/app/pages/core/app-signin/app-signin.tsx
+++ b/studio/src/app/pages/core/app-signin/app-signin.tsx
@@ -3,27 +3,24 @@ 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 {del, get, set} from 'idb-keyval';
+import deckStore from '../../../stores/deck.store';
+import navStore, {NavDirection} from '../../../stores/nav.store';
+import authStore from '../../../stores/auth.store';
+
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';
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;
@@ -37,22 +34,14 @@ export class AppSignIn {
@State()
private signInInProgress: boolean = false;
- private navService: NavService;
-
private userService: UserService;
- private authService: AuthService;
private deckService: DeckService;
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 +105,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
@@ -161,34 +150,41 @@ export class AppSignIn {
return;
}
- this.authService
- .watch()
- .pipe(
- filter((authUser: AuthUser) => authUser !== null && authUser !== undefined && authUser.uid && authUser.uid !== mergeInfo.userId),
- take(1)
- )
- .subscribe(async (authUser: AuthUser) => {
- // Merge deck to new user
- await this.deckService.mergeDeck(mergeInfo.deckId, authUser.uid);
+ const destroyListener = authStore.onChange('authUser', async (_authUser: AuthUser | null) => {
+ await this.mergeDeck(mergeInfo, destroyListener);
- // Delete previous anonymous user from the database
- await this.userService.delete(mergeInfo.userId);
+ resolve();
+ });
- // Delete previous anonymous user from Firebase
- if (this.firebaseUser) {
- await this.firebaseUser.delete();
- }
+ await firebase.auth().signInWithCredential(cred);
+ });
+ };
- await this.navigateRedirect();
+ private async mergeDeck(mergeInfo: MergeInformation, destroyListener) {
+ if (
+ authStore.state.authUser === null ||
+ authStore.state.authUser === undefined ||
+ !authStore.state.authUser.uid ||
+ authStore.state.authUser.uid === mergeInfo.userId
+ ) {
+ return;
+ }
- resolve();
- });
+ destroyListener();
- await firebase.auth().signInWithCredential(cred);
+ // Merge deck to new user
+ await this.deckService.mergeDeck(mergeInfo.deckId, authStore.state.authUser.uid);
- resolve();
- });
- };
+ // Delete previous anonymous user from the database
+ await this.userService.delete(mergeInfo.userId);
+
+ // Delete previous anonymous user from Firebase
+ if (this.firebaseUser) {
+ await this.firebaseUser.delete();
+ }
+
+ await this.navigateRedirect();
+ }
private saveRedirect(): Promise {
return new Promise(async (resolve) => {
@@ -201,20 +197,14 @@ 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)));
-
- 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();
+ await set('deckdeckgo_redirect_info', {
+ deckId: deckStore.state.deck ? deckStore.state.deck.id : null,
+ userId: authStore.state.authUser ? authStore.state.authUser.uid : null,
+ userToken: authStore.state.authUser ? authStore.state.authUser.token : null,
+ anonymous: authStore.state.authUser ? authStore.state.authUser.anonymous : true,
});
+
+ resolve();
});
}
@@ -234,16 +224,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
- });
+ direction: NavDirection.ROOT,
+ };
}
async navigateBack() {
- this.navService.navigate({
- direction: NavDirection.BACK
- });
+ navStore.state.nav = {
+ direction: NavDirection.BACK,
+ };
}
render() {
@@ -261,7 +251,7 @@ export class AppSignIn {
DeckDeckGo is free and open source 🖖
-
+ ,
];
}
@@ -272,12 +262,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..85ca7be78 100644
--- a/studio/src/app/pages/editor/app-editor/app-editor.tsx
+++ b/studio/src/app/pages/editor/app-editor/app-editor.tsx
@@ -2,8 +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 deckStore from '../../../stores/deck.store';
+import busyStore from '../../../stores/busy.store';
+import navStore, {NavDirection} from '../../../stores/nav.store';
+import authStore from '../../../stores/auth.store';
import {debounce, isFullscreen, isIOS, isMobile} from '@deckdeckgo/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';
@@ -33,9 +34,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';
import {EnvironmentConfigService} from '../../../services/core/environment/environment-config.service';
@@ -80,12 +78,6 @@ export class AppEditor {
private authService: AuthService;
private anonymousService: AnonymousService;
- private navService: NavService;
-
- private deckEditorService: DeckEditorService;
-
- private busySubscription: Subscription;
- private busyService: BusyService;
private offlineService: OfflineService;
@@ -106,12 +98,11 @@ export class AppEditor {
@State()
private fullscreen: boolean = false;
+ private destroyBusyListener;
+
constructor() {
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();
}
@@ -136,34 +127,9 @@ export class AppEditor {
await this.initOffline();
- // If no user create an anonymous one
- this.authService
- .watch()
- .pipe(take(1))
- .subscribe(async (authUser: AuthUser) => {
- if (!authUser) {
- await this.authService.signInAnonymous();
- }
- });
-
- // As soon as we have got a user, an anonymous where the creation started above or an already used anonymous or a logged one, we init
- this.authService
- .watch()
- .pipe(
- filter((authUser: AuthUser) => authUser !== null && authUser !== undefined),
- take(1)
- )
- .subscribe(async (_authUser: AuthUser) => {
- if (!this.deckId) {
- await this.initSlide();
- } else {
- await this.fetchSlides();
- }
-
- this.slidesFetched = true;
- });
+ await this.initWithAuth();
- this.busySubscription = this.busyService.watchSlideEditable().subscribe(async (slide: HTMLElement) => {
+ this.destroyBusyListener = busyStore.onChange('slideEditable', async (slide: HTMLElement | undefined) => {
this.slidesEditable = true;
await this.contentEditable(slide);
@@ -172,6 +138,35 @@ export class AppEditor {
this.fullscreen = isFullscreen() && !isIOS();
}
+ private async initWithAuth() {
+ if (!authStore.state.authUser) {
+ // As soon as the anonymous is created, we proceed
+ const destroyListener = authStore.onChange('authUser', async (authUser: AuthUser | null) => {
+ if (authUser) {
+ await this.initOrFetch();
+ }
+
+ destroyListener();
+ });
+
+ // If no user create an anonymous one
+ await this.authService.signInAnonymous();
+ } else {
+ // We have got a user, regardless if anonymous or not, we init
+ await this.initOrFetch();
+ }
+ }
+
+ private async initOrFetch() {
+ if (!this.deckId) {
+ await this.initSlide();
+ } else {
+ await this.fetchSlides();
+ }
+
+ this.slidesFetched = true;
+ }
+
async initOffline() {
// if we are offline we can't create a new deck or edit another one that the one we have marked as currently being edited offline
const offline: OfflineDeck = await this.offlineService.status();
@@ -189,11 +184,7 @@ export class AppEditor {
await this.remoteEventsHandler.destroy();
- if (this.busySubscription) {
- this.busySubscription.unsubscribe();
- }
-
- this.deckEditorService.next(null);
+ deckStore.reset();
}
async componentDidLoad() {
@@ -211,6 +202,10 @@ export class AppEditor {
await this.remoteEventsHandler.destroy();
this.removeWindowResize();
+
+ if (this.destroyBusyListener) {
+ this.destroyBusyListener();
+ }
}
private updateInlineEditorListener(): Promise {
@@ -276,30 +271,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 (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;
+ }
- this.background = await ParseBackgroundUtils.convertBackground(deck.data.background, true);
+ 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;
+ }
- const google: EnvironmentGoogleConfig = EnvironmentConfigService.getInstance().get('google');
- await this.fontsService.loadGoogleFont(google.fontsUrl, this.style);
+ this.background = await ParseBackgroundUtils.convertBackground(deckStore.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 {
@@ -590,10 +576,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/popovers/editor/app-create-slide/app-create-slide.tsx b/studio/src/app/popovers/editor/app-create-slide/app-create-slide.tsx
index 335a031e2..ee6dad6a4 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,7 +1,8 @@
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';
@@ -11,9 +12,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 {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';
@@ -34,9 +32,6 @@ enum ComposeTemplate {
export class AppCreateSlide {
@Element() el: HTMLElement;
- @State()
- private photoUrl: string;
-
@State()
private assets: Assets | undefined = undefined;
@@ -51,32 +46,14 @@ export class AppCreateSlide {
private user: User;
- private userService: UserService;
- private anonymousService: AnonymousService;
- private deckEditorService: DeckEditorService;
-
@Event() signIn: EventEmitter;
- private timerSubscription: Subscription;
+ private timerInterval: NodeJS.Timeout;
+ private timerCounter: number = 0;
private config: EnvironmentDeckDeckGoConfig = EnvironmentConfigService.getInstance().get('deckdeckgo');
- constructor() {
- this.userService = UserService.getInstance();
- this.anonymousService = AnonymousService.getInstance();
- this.deckEditorService = DeckEditorService.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();
}
@@ -95,8 +72,8 @@ export class AppCreateSlide {
}
private unsubscribeTimer() {
- if (this.timerSubscription) {
- this.timerSubscription.unsubscribe();
+ if (this.timerInterval) {
+ clearInterval(this.timerInterval);
}
}
@@ -168,20 +145,13 @@ 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, deckStore.state.deck);
}
// We need the data in the user account (like twitter, profile image etc.) to generate the author slide
private async addRestrictedSlide(template: SlideTemplate) {
- const isAnonymous: boolean = await this.anonymousService.isAnonymous();
-
- if (isAnonymous) {
+ if (authStore.state.anonymous) {
this.signIn.emit();
await this.closePopover(null);
return;
@@ -193,9 +163,7 @@ export class AppCreateSlide {
// User will need an account to upload her/his data
private async closePopoverRestricted(template: SlideTemplate, attributes: SlideAttributes) {
- const isAnonymous: boolean = await this.anonymousService.isAnonymous();
-
- if (isAnonymous) {
+ if (authStore.state.anonymous) {
this.signIn.emit();
await this.closePopover(null);
return;
@@ -236,15 +204,17 @@ export class AppCreateSlide {
this.unsubscribeTimer();
- this.timerSubscription = interval(2000).subscribe(async (val: number) => {
+ this.timerInterval = setInterval(async () => {
const elements: NodeListOf = this.el.querySelectorAll('deckgo-slide-chart[animation]');
if (elements) {
for (const element of Array.from(elements)) {
- await (element as any).beforeSwipe(val % 2 === 0, true);
+ await (element as any).beforeSwipe(this.timerCounter % 2 === 0, true);
}
}
- });
+
+ this.timerCounter++;
+ }, 2000);
}
private async selectElement(slotType: SlotType) {
@@ -780,7 +750,14 @@ export class AppCreateSlide {
return (
this.addRestrictedSlide(SlideTemplate.AUTHOR)}>
-
+
Author
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/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/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/api/presentation/api.presentation.prod.service.tsx b/studio/src/app/services/api/presentation/api.presentation.prod.service.tsx
index 3851fc10a..a50cfa697 100644
--- a/studio/src/app/services/api/presentation/api.presentation.prod.service.tsx
+++ b/studio/src/app/services/api/presentation/api.presentation.prod.service.tsx
@@ -1,3 +1,5 @@
+import authStore from '../../../stores/auth.store';
+
import {ApiPresentationService} from './api.presentation.service';
import {ApiDeck} from '../../../models/api/api.deck';
import {ApiPresentation} from '../../../models/api/api.presentation';
@@ -12,7 +14,7 @@ export class ApiPresentationProdService extends ApiPresentationService {
const config: EnvironmentDeckDeckGoConfig = EnvironmentConfigService.getInstance().get('deckdeckgo');
if (!bearer) {
- bearer = await this.authService.getBearer();
+ bearer = authStore.state.bearer;
}
const rawResponse: Response = await fetch(config.apiUrl + context, {
@@ -20,9 +22,9 @@ export class ApiPresentationProdService extends ApiPresentationService {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
- Authorization: bearer
+ Authorization: bearer,
},
- body: JSON.stringify(deck)
+ body: JSON.stringify(deck),
});
if (!rawResponse || !rawResponse.ok) {
diff --git a/studio/src/app/services/api/user/api.user.mock.service.tsx b/studio/src/app/services/api/user/api.user.mock.service.tsx
index e46f72181..2683fa8d0 100644
--- a/studio/src/app/services/api/user/api.user.mock.service.tsx
+++ b/studio/src/app/services/api/user/api.user.mock.service.tsx
@@ -1,3 +1,5 @@
+import apiUserStore from '../../../stores/api.user.store';
+
import {ApiUser, ApiUserInfo} from '../../../models/api/api.user';
import {ApiUserService} from './api.user.service';
@@ -12,7 +14,7 @@ export class ApiUserMockService extends ApiUserService {
return new Promise(async (resolve) => {
const testUser: ApiUser = await this.createTestUserInfo(apiUserInfo);
- this.apiUserSubject.next(testUser);
+ apiUserStore.state.apiUser = {...testUser};
resolve(testUser);
});
@@ -38,7 +40,7 @@ export class ApiUserMockService extends ApiUserService {
id: apiUserInfo.firebase_uid,
anonymous: false,
firebase_uid: apiUserInfo.firebase_uid,
- username: 'Peter Parker'
+ username: 'Peter Parker',
};
resolve(testUser);
diff --git a/studio/src/app/services/api/user/api.user.prod.service.tsx b/studio/src/app/services/api/user/api.user.prod.service.tsx
index 52f028fe0..c45b9e398 100644
--- a/studio/src/app/services/api/user/api.user.prod.service.tsx
+++ b/studio/src/app/services/api/user/api.user.prod.service.tsx
@@ -1,3 +1,5 @@
+import apiUserStore from '../../../stores/api.user.store';
+
import {ApiUser, ApiUserInfo} from '../../../models/api/api.user';
import {EnvironmentConfigService} from '../../core/environment/environment-config.service';
@@ -17,9 +19,9 @@ export class ApiUserProdService extends ApiUserService {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
- Authorization: `Bearer ${token}`
+ Authorization: `Bearer ${token}`,
},
- body: JSON.stringify(apiUserInfo)
+ body: JSON.stringify(apiUserInfo),
});
if (!rawResponse || (!rawResponse.ok && rawResponse.status !== 409)) {
@@ -29,7 +31,7 @@ export class ApiUserProdService extends ApiUserService {
const persistedUser: ApiUser = await rawResponse.json();
- this.apiUserSubject.next(persistedUser);
+ apiUserStore.state.apiUser = {...persistedUser};
resolve(persistedUser);
} catch (err) {
@@ -49,8 +51,8 @@ export class ApiUserProdService extends ApiUserService {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
- Authorization: `Bearer ${token}`
- }
+ Authorization: `Bearer ${token}`,
+ },
});
if (!rawResponse || !rawResponse.ok) {
@@ -75,8 +77,8 @@ export class ApiUserProdService extends ApiUserService {
method: 'GET',
headers: {
Accept: 'application/json',
- 'Content-Type': 'application/json'
- }
+ 'Content-Type': 'application/json',
+ },
});
if (!rawResponse || !rawResponse.ok) {
@@ -87,7 +89,7 @@ export class ApiUserProdService extends ApiUserService {
const persistedUser: ApiUser = await rawResponse.json();
- this.apiUserSubject.next(persistedUser);
+ apiUserStore.state.apiUser = {...persistedUser};
resolve(persistedUser);
} catch (err) {
diff --git a/studio/src/app/services/api/user/api.user.service.tsx b/studio/src/app/services/api/user/api.user.service.tsx
index 991ab31af..fdfcc3adc 100644
--- a/studio/src/app/services/api/user/api.user.service.tsx
+++ b/studio/src/app/services/api/user/api.user.service.tsx
@@ -1,11 +1,9 @@
-import {Observable, ReplaySubject} from 'rxjs';
+import apiUserStore from '../../../stores/api.user.store';
import {ApiUser, ApiUserInfo} from '../../../models/api/api.user';
import {AuthUser} from '../../../models/auth/auth.user';
export abstract class ApiUserService {
- protected apiUserSubject: ReplaySubject = new ReplaySubject(1);
-
abstract query(apiUserInfo: ApiUserInfo | ApiUser, token: string, context: string, method: string): Promise;
abstract delete(userId: string, token: string): Promise;
@@ -41,12 +39,8 @@ export abstract class ApiUserService {
});
}
- signOut(): Promise {
- return new Promise(async (resolve) => {
- this.apiUserSubject.next(null);
-
- resolve();
- });
+ async signOut() {
+ apiUserStore.reset();
}
post(apiUser: ApiUserInfo, token: string): Promise {
@@ -57,10 +51,6 @@ export abstract class ApiUserService {
return this.query(apiUser, token, `/users/${userId}`, 'PUT');
}
- watch(): Observable {
- return this.apiUserSubject.asObservable();
- }
-
private createUserInfo(authUser: AuthUser): Promise {
return new Promise((resolve) => {
if (!authUser) {
@@ -71,7 +61,7 @@ export abstract class ApiUserService {
const apiUserInfo: ApiUserInfo = {
anonymous: authUser.anonymous,
firebase_uid: authUser.uid,
- email: authUser.anonymous ? null : authUser.email
+ email: authUser.anonymous ? null : authUser.email,
};
resolve(apiUserInfo);
diff --git a/studio/src/app/services/auth/auth.service.tsx b/studio/src/app/services/auth/auth.service.tsx
index 97701d046..be63f638c 100644
--- a/studio/src/app/services/auth/auth.service.tsx
+++ b/studio/src/app/services/auth/auth.service.tsx
@@ -2,8 +2,8 @@ import firebase from '@firebase/app';
import '@firebase/auth';
import {User as FirebaseUser} from 'firebase';
-import {Observable, ReplaySubject} from 'rxjs';
-import {take} from 'rxjs/operators';
+import errorStore from '../../stores/error.store';
+import authStore from '../../stores/auth.store';
import {get, set, del} from 'idb-keyval';
@@ -11,17 +11,11 @@ import {EnvironmentConfigService} from '../core/environment/environment-config.s
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';
export class AuthService {
- private authUserSubject: ReplaySubject = new ReplaySubject(1);
-
- private errorService: ErrorService;
-
private apiUserService: ApiUserService;
private firestoreUserService: UserService;
@@ -30,7 +24,6 @@ export class AuthService {
private constructor() {
// Private constructor, singleton
- this.errorService = ErrorService.getInstance();
this.apiUserService = ApiUserFactoryService.getInstance();
this.firestoreUserService = UserService.getInstance();
}
@@ -47,13 +40,13 @@ export class AuthService {
// We also save the user in the local storage to avoid a flickering in the GUI till Firebase as correctly fetched the user
// And we also need it in case the user go offline, so we could also check offline if user is anonymous or not
const localUser: AuthUser = await this.getLocalAuthUser();
- this.authUserSubject.next(localUser);
+ authStore.state.authUser = localUser ? {...localUser} : null;
firebase.initializeApp(EnvironmentConfigService.getInstance().get('firebase'));
firebase.auth().onAuthStateChanged(async (firebaseUser: FirebaseUser) => {
if (!firebaseUser) {
- this.authUserSubject.next(null);
+ authStore.reset();
await del('deckdeckgo_auth_user');
await this.apiUserService.signOut();
@@ -67,7 +60,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
@@ -84,7 +77,7 @@ export class AuthService {
await set('deckdeckgo_auth_user', authUser);
- this.authUserSubject.next(authUser);
+ authStore.state.authUser = {...authUser};
await this.apiUserService.signIn(authUser);
}
@@ -110,26 +103,12 @@ export class AuthService {
resolve();
} catch (err) {
- this.errorService.error(err.message);
+ errorStore.state.error = err.message;
resolve();
}
});
}
- watch(): Observable {
- return this.authUserSubject.asObservable();
- }
-
- getBearer(): Promise {
- return new Promise((resolve) => {
- this.watch()
- .pipe(take(1))
- .subscribe((authUser: AuthUser) => {
- resolve(`Bearer ${authUser ? authUser.token : ''}`);
- });
- });
- }
-
async getLocalAuthUser(): Promise {
return get('deckdeckgo_auth_user');
}
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/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/services/data/feed/feed.service.tsx b/studio/src/app/services/data/feed/feed.service.tsx
index 563731ba4..082036d4a 100644
--- a/studio/src/app/services/data/feed/feed.service.tsx
+++ b/studio/src/app/services/data/feed/feed.service.tsx
@@ -1,32 +1,18 @@
import * as firebase from 'firebase/app';
import 'firebase/firestore';
-import {BehaviorSubject, Observable, ReplaySubject} from 'rxjs';
-import {take} from 'rxjs/operators';
+import feedStore from '../../../stores/feed.store';
+import errorStore from '../../../stores/error.store';
import {Deck, DeckData} from '../../../models/data/deck';
-import {ErrorService} from '../../core/error/error.service';
-
export class FeedService {
private static instance: 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();
@@ -34,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();
@@ -65,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 {
@@ -88,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;
@@ -107,7 +65,7 @@ export class FeedService {
resolve();
} catch (err) {
- this.errorService.error("Something weird happened, we couldn't fetch the decks.");
+ errorStore.state.error = "Something weird happened, we couldn't fetch the decks.";
resolve();
}
});
@@ -132,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;
@@ -140,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/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/anonymous/anonymous.service.tsx b/studio/src/app/services/editor/anonymous/anonymous.service.tsx
index deabf6c8a..7ab8e87e2 100644
--- a/studio/src/app/services/editor/anonymous/anonymous.service.tsx
+++ b/studio/src/app/services/editor/anonymous/anonymous.service.tsx
@@ -1,19 +1,8 @@
-import {take} from 'rxjs/operators';
-
-import {AuthUser} from '../../../models/auth/auth.user';
-
-import {AuthService} from '../../auth/auth.service';
+import authStore from '../../../stores/auth.store';
export class AnonymousService {
private static instance: AnonymousService;
- private authService: AuthService;
-
- private constructor() {
- // Private constructor, singleton
- this.authService = AuthService.getInstance();
- }
-
static getInstance() {
if (!AnonymousService.instance) {
AnonymousService.instance = new AnonymousService();
@@ -32,22 +21,17 @@ export class AnonymousService {
return;
}
- this.authService
- .watch()
- .pipe(take(1))
- .subscribe((authUser: AuthUser) => {
- if (!authUser) {
- resolve(false);
- return;
- }
+ if (!authStore.state.authUser) {
+ resolve(false);
+ return;
+ }
- if (!authUser.anonymous) {
- resolve(true);
- return;
- }
+ if (!authStore.state.authUser.anonymous) {
+ resolve(true);
+ return;
+ }
- resolve(slides.length < 3);
- });
+ resolve(slides.length < 3);
});
}
@@ -58,33 +42,12 @@ export class AnonymousService {
return;
}
- this.authService
- .watch()
- .pipe(take(1))
- .subscribe((authUser: AuthUser) => {
- if (!authUser) {
- resolve(false);
- return;
- }
-
- resolve(!authUser.anonymous);
- });
- });
- }
-
- isAnonymous(): Promise {
- return new Promise((resolve) => {
- this.authService
- .watch()
- .pipe(take(1))
- .subscribe((authUser: AuthUser) => {
- if (!authUser) {
- resolve(true);
- return;
- }
+ if (!authStore.state.authUser) {
+ resolve(false);
+ return;
+ }
- resolve(authUser.anonymous);
- });
+ resolve(!authStore.state.anonymous);
});
}
}
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/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..aae748cce 100644
--- a/studio/src/app/services/editor/offline/offline.service.tsx
+++ b/studio/src/app/services/editor/offline/offline.service.tsx
@@ -2,8 +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 deckStore from '../../../stores/deck.store';
+import offlineStore from '../../../stores/offline.store';
import {Deck} from '../../../models/data/deck';
import {Slide} from '../../../models/data/slide';
@@ -13,7 +13,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 +25,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;
@@ -35,13 +32,7 @@ export class OfflineService {
private assetsService: AssetsService;
private fontsService: FontsService;
- private offlineSubject: BehaviorSubject = new BehaviorSubject(undefined);
-
- private progressSubject: BehaviorSubject = new BehaviorSubject(0);
-
private constructor() {
- this.deckEditorService = DeckEditorService.getInstance();
-
this.deckOnlineService = DeckOnlineService.getInstance();
this.slideOnlineService = SlideOnlineService.getInstance();
this.storageOnlineService = StorageOnlineService.getInstance();
@@ -58,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 {
@@ -134,7 +107,7 @@ export class OfflineService {
this.progressComplete();
- this.offlineSubject.next(undefined);
+ offlineStore.reset();
resolve();
} catch (err) {
@@ -144,34 +117,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 (!deckStore.state.deck || !deckStore.state.deck.id || !deckStore.state.deck.data) {
+ reject('No deck found');
+ return;
+ }
+
+ const offline: OfflineDeck = {
+ id: deckStore.state.deck.id,
+ name: deckStore.state.deck.data.name,
+ };
+
+ await set('deckdeckgo_offline', offline);
+
+ offlineStore.state.offline = {...offline};
+
+ this.progressComplete();
+
+ resolve();
+ } catch (err) {
+ reject(err);
+ }
} catch (err) {
reject(err);
}
@@ -375,33 +343,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 (!deckStore.state.deck || !deckStore.state.deck.id || !deckStore.state.deck.data) {
+ reject('No deck found');
+ return;
+ }
+
+ await this.saveSlides(deckStore.state.deck);
+
+ if (deckStore.state.deck.data.background && OfflineUtils.shouldAttributeBeCleaned(deckStore.state.deck.data.background)) {
+ deckStore.state.deck.data.background = null;
+ }
+
+ await set(`/decks/${deckStore.state.deck.id}`, deckStore.state.deck);
+
+ this.progress(0.5);
+
+ resolve();
} catch (err) {
reject(err);
}
@@ -456,31 +415,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 (!deckStore.state.deck || !deckStore.state.deck.id || !deckStore.state.deck.data) {
+ reject('No deck found');
+ return;
+ }
- await this.uploadSlides(deck);
+ await this.uploadSlides(deckStore.state.deck);
- await this.deleteSlides(deck);
+ await this.deleteSlides(deckStore.state.deck);
- const persistedDeck: Deck = await this.uploadDeck(deck);
+ const persistedDeck: Deck = await this.uploadDeck(deckStore.state.deck);
- this.deckEditorService.next(persistedDeck);
+ deckStore.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..4275fd867 100644
--- a/studio/src/app/services/editor/publish/publish.service.tsx
+++ b/studio/src/app/services/editor/publish/publish.service.tsx
@@ -1,21 +1,19 @@
import * as firebase from 'firebase/app';
import 'firebase/firestore';
-import {Observable, Subject} from 'rxjs';
-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 {DeckEditorService} from '../deck/deck-editor.service';
import {ApiPresentationService} from '../../api/presentation/api.presentation.service';
import {ApiPresentationFactoryService} from '../../api/presentation/api.presentation.factory.service';
@@ -27,30 +25,19 @@ import {FontsService} from '../fonts/fonts.service';
export class PublishService {
private static instance: PublishService;
- private deckEditorService: DeckEditorService;
-
private apiPresentationService: ApiPresentationService;
private deckService: DeckService;
private slideService: SlideService;
- private userService: UserService;
-
private fontsService: FontsService;
- private progressSubject: Subject = new Subject();
-
private constructor() {
- // Private constructor, singleton
- this.deckEditorService = DeckEditorService.getInstance();
-
this.apiPresentationService = ApiPresentationFactoryService.getInstance();
this.deckService = DeckService.getInstance();
this.slideService = SlideService.getInstance();
- this.userService = UserService.getInstance();
-
this.fontsService = FontsService.getInstance();
}
@@ -61,69 +48,61 @@ 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?
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 (!deckStore.state.deck || !deckStore.state.deck.id || !deckStore.state.deck.data) {
+ this.progressComplete();
+ reject('No deck found');
+ return;
+ }
- const apiDeck: ApiDeck = await this.convertDeck(deck, description);
+ const apiDeck: ApiDeck = await this.convertDeck(deckStore.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(deckStore.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 = deckStore.state.deck.data.api_id !== apiDeckPublish.id;
+ if (newApiId) {
+ deckStore.state.deck.data.api_id = apiDeckPublish.id;
- deck = await this.deckService.update(deck);
- }
+ const updatedDeck: Deck = await this.deckService.update(deckStore.state.deck);
+ deckStore.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(deckStore.state.deck, publishedUrl, description, tags, newApiId);
- resolve(publishedUrl);
- } catch (err) {
- this.progressComplete();
- reject(err);
- }
- });
+ resolve(publishedUrl);
+ } catch (err) {
+ this.progressComplete();
+ reject(err);
+ }
});
}
@@ -376,7 +355,7 @@ export class PublishService {
return new Promise(async (resolve, reject) => {
try {
const freshDeck: Deck = await this.deckService.get(deckId);
- this.deckEditorService.next(freshDeck);
+ deckStore.state.deck = {...freshDeck};
resolve();
} catch (err) {
@@ -393,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/services/editor/remote/remote.service.tsx b/studio/src/app/services/editor/remote/remote.service.tsx
index 2a0f87282..7758a35d5 100644
--- a/studio/src/app/services/editor/remote/remote.service.tsx
+++ b/studio/src/app/services/editor/remote/remote.service.tsx
@@ -1,7 +1,7 @@
import {Build} from '@stencil/core';
-import {BehaviorSubject, Observable, Subject} from 'rxjs';
-import {filter, take} from 'rxjs/operators';
+import deckStore from '../../../stores/deck.store';
+import remoteStore from '../../../stores/remote.store';
import {get, set} from 'idb-keyval';
@@ -9,26 +9,9 @@ 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);
-
- private remotePendingRequestsSubject: BehaviorSubject = new BehaviorSubject(undefined);
-
- private remoteStateSubject: BehaviorSubject = new BehaviorSubject(ConnectionState.DISCONNECTED);
-
- private remoteAcceptedRequestSubject: Subject = new Subject();
-
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();
@@ -45,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();
});
@@ -59,72 +38,43 @@ export class RemoteService {
async switch(enable: boolean) {
await set('deckdeckgo_remote', enable);
- this.remoteSubject.next(enable);
+ remoteStore.state.remote = enable;
}
- watch(): Observable {
- return this.remoteSubject.asObservable();
- }
+ async getRoom(): Promise {
+ const deck: Deck | null = deckStore.state.deck;
- 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);
- });
- });
+ 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) {
- 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();
-
- this.remotePendingRequestsSubject.next(requests);
- }
+ async shiftPendingRequests() {
+ if (remoteStore.state.pendingRequests && remoteStore.state.pendingRequests.length > 0) {
+ remoteStore.state.pendingRequests.shift();
- 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/services/editor/share/share.service.tsx b/studio/src/app/services/editor/share/share.service.tsx
index b2abd1fee..17b527464 100644
--- a/studio/src/app/services/editor/share/share.service.tsx
+++ b/studio/src/app/services/editor/share/share.service.tsx
@@ -1,25 +1,12 @@
-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 {AuthUser} from '../../../models/auth/auth.user';
-
-import {DeckEditorService} from '../deck/deck-editor.service';
-import {AuthService} from '../../auth/auth.service';
+import deckStore from '../../../stores/deck.store';
+import authStore from '../../../stores/auth.store';
export class ShareService {
private static instance: ShareService;
- private deckEditorService: DeckEditorService;
- private authService: AuthService;
-
- private constructor() {
- this.deckEditorService = DeckEditorService.getInstance();
- this.authService = AuthService.getInstance();
- }
-
static getInstance() {
if (!ShareService.instance) {
ShareService.instance = new ShareService();
@@ -29,43 +16,34 @@ 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 (
+ deckStore.state.deck &&
+ deckStore.state.deck.data &&
+ deckStore.state.deck.data.meta &&
+ deckStore.state.deck.data.meta.pathname &&
+ deckStore.state.deck.data.meta.pathname !== ''
+ ) {
+ const config: EnvironmentDeckDeckGoConfig = EnvironmentConfigService.getInstance().get('deckdeckgo');
+ resolve(config.presentationUrl + deckStore.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 (deckStore.state.deck && deckStore.state.deck.data && deckStore.state.deck.data.name && deckStore.state.deck.data.name !== '') {
+ if (!authStore.state.anonymous && authStore.state.authUser && authStore.state.authUser.name && authStore.state.authUser.name !== '') {
+ resolve(`"${deckStore.state.deck.data.name}" by ${authStore.state.authUser.name} created with DeckDeckGo`);
+ } else {
+ resolve(`"${deckStore.state.deck.data.name}" created with DeckDeckGo`);
+ }
+ } else {
+ resolve('A presentation created with DeckDeckGo');
+ }
});
}
}
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/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..b6b446663 100644
--- a/studio/src/app/services/storage/storage.online.service.tsx
+++ b/studio/src/app/services/storage/storage.online.service.tsx
@@ -3,26 +3,14 @@ import '@firebase/storage';
import {Reference, ListResult, ListOptions} from '@firebase/storage-types';
-import {take} from 'rxjs/operators';
-
-import {AuthUser} from '../../models/auth/auth.user';
+import errorStore from '../../stores/error.store';
+import authStore from '../../stores/auth.store';
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();
- }
-
static getInstance() {
if (!StorageOnlineService.instance) {
StorageOnlineService.instance = new StorageOnlineService();
@@ -31,74 +19,64 @@ export class StorageOnlineService {
}
uploadFile(data: File, folder: string, maxSize: number): Promise {
- return new Promise((resolve) => {
+ return new Promise(async (resolve) => {
try {
- this.authService
- .watch()
- .pipe(take(1))
- .subscribe(async (authUser: AuthUser) => {
- if (!authUser || !authUser.uid || authUser.uid === '' || authUser.uid === undefined) {
- this.errorService.error('Not logged in.');
- resolve();
- return;
- }
-
- if (!data || !data.name) {
- this.errorService.error('File not valid.');
- resolve();
- return;
- }
-
- if (data.size > maxSize) {
- this.errorService.error(`File is too big (max. ${maxSize / 1048576} Mb)`);
- resolve();
- return;
- }
-
- const ref: Reference = firebase.storage().ref(`${authUser.uid}/assets/${folder}/${data.name}`);
-
- await ref.put(data);
-
- resolve({
- downloadUrl: await ref.getDownloadURL(),
- fullPath: ref.fullPath,
- name: ref.name,
- });
- });
+ if (!authStore.state.authUser || !authStore.state.authUser.uid || authStore.state.authUser.uid === '' || authStore.state.authUser.uid === undefined) {
+ errorStore.state.error = 'Not logged in.';
+ resolve();
+ return;
+ }
+
+ if (!data || !data.name) {
+ errorStore.state.error = 'File not valid.';
+ resolve();
+ return;
+ }
+
+ if (data.size > maxSize) {
+ errorStore.state.error = `File is too big (max. ${maxSize / 1048576} Mb)`;
+ resolve();
+ return;
+ }
+
+ const ref: Reference = firebase.storage().ref(`${authStore.state.authUser.uid}/assets/${folder}/${data.name}`);
+
+ await ref.put(data);
+
+ resolve({
+ downloadUrl: await ref.getDownloadURL(),
+ fullPath: ref.fullPath,
+ name: ref.name,
+ });
} catch (err) {
- this.errorService.error(err.message);
+ errorStore.state.error = err.message;
resolve();
}
});
}
getFiles(next: string | null, folder: string): Promise {
- return new Promise((resolve) => {
+ return new Promise(async (resolve) => {
try {
- this.authService
- .watch()
- .pipe(take(1))
- .subscribe(async (authUser: AuthUser) => {
- if (!authUser || !authUser.uid || authUser.uid === '' || authUser.uid === undefined) {
- this.errorService.error('Not logged in.');
- resolve(null);
- return;
- }
-
- const ref = firebase.storage().ref(`${authUser.uid}/assets/${folder}/`);
-
- let options: ListOptions = {
- maxResults: Resources.Constants.STORAGE.MAX_QUERY_RESULTS,
- };
-
- if (next) {
- options.pageToken = next;
- }
-
- const results: ListResult = await ref.list(options);
-
- resolve(this.toStorageFileList(results));
- });
+ if (!authStore.state.authUser || !authStore.state.authUser.uid || authStore.state.authUser.uid === '' || authStore.state.authUser.uid === undefined) {
+ errorStore.state.error = 'Not logged in.';
+ resolve(null);
+ return;
+ }
+
+ const ref = firebase.storage().ref(`${authStore.state.authUser.uid}/assets/${folder}/`);
+
+ let options: ListOptions = {
+ maxResults: Resources.Constants.STORAGE.MAX_QUERY_RESULTS,
+ };
+
+ if (next) {
+ options.pageToken = next;
+ }
+
+ const results: ListResult = await ref.list(options);
+
+ resolve(this.toStorageFileList(results));
} catch (err) {
resolve(null);
}
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/services/theme/theme.service.tsx b/studio/src/app/services/theme/theme.service.tsx
index 6130d2966..9e8e12205 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 themeStore 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);
+ themeStore.state.darkTheme = dark;
try {
await set('deckdeckgo_dark_mode', dark);
diff --git a/studio/src/app/stores/api.user.store.ts b/studio/src/app/stores/api.user.store.ts
new file mode 100644
index 000000000..6e8105c4d
--- /dev/null
+++ b/studio/src/app/stores/api.user.store.ts
@@ -0,0 +1,13 @@
+import {createStore} from '@stencil/store';
+
+import {ApiUser} from '../models/api/api.user';
+
+interface ApitUserStore {
+ apiUser: ApiUser | undefined;
+}
+
+const {state, onChange, reset} = createStore({
+ apiUser: undefined,
+} as ApitUserStore);
+
+export default {state, onChange, reset};
diff --git a/studio/src/app/stores/auth.store.ts b/studio/src/app/stores/auth.store.ts
new file mode 100644
index 000000000..622b5bc82
--- /dev/null
+++ b/studio/src/app/stores/auth.store.ts
@@ -0,0 +1,25 @@
+import {createStore} from '@stencil/store';
+
+import {AuthUser} from '../models/auth/auth.user';
+
+interface AuthStore {
+ authUser: AuthUser | null;
+ anonymous: boolean;
+ bearer: string;
+ loggedIn: boolean;
+}
+
+const {state, onChange, reset} = createStore({
+ authUser: null,
+ anonymous: true,
+ bearer: 'Bearer ',
+ loggedIn: false,
+} as AuthStore);
+
+onChange('authUser', (authUser: AuthUser) => {
+ state.anonymous = authUser ? authUser.anonymous : true;
+ state.bearer = `Bearer ${authUser ? authUser.token : ''}`;
+ state.loggedIn = authUser && !authUser.anonymous;
+});
+
+export default {state, onChange, reset};
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};
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};
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};
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};
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};
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};
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};
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};
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};
diff --git a/studio/src/app/stores/theme.store.ts b/studio/src/app/stores/theme.store.ts
new file mode 100644
index 000000000..0114b74cf
--- /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};
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};
diff --git a/studio/src/app/utils/core/utils.tsx b/studio/src/app/utils/core/utils.tsx
index 6f7c01af9..6c89152ef 100644
--- a/studio/src/app/utils/core/utils.tsx
+++ b/studio/src/app/utils/core/utils.tsx
@@ -1,7 +1,5 @@
import DateTimeFormatOptions = Intl.DateTimeFormatOptions;
-import {AuthUser} from '../../models/auth/auth.user';
-
export class Utils {
static injectJS(id: string, src: string): Promise {
return new Promise((resolve, reject) => {
@@ -55,10 +53,6 @@ export class Utils {
});
}
- static isLoggedIn(authUser: AuthUser): boolean {
- return authUser && !authUser.anonymous;
- }
-
static getNow(): Promise {
return new Promise((resolve) => {
const options: DateTimeFormatOptions = {
@@ -68,7 +62,7 @@ export class Utils {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
- hour12: false
+ hour12: false,
};
const now: string = new Intl.DateTimeFormat('en-US', options).format(new Date());
diff --git a/webcomponents/remote/CHANGELOG.md b/webcomponents/remote/CHANGELOG.md
index 2fb7c38bb..089c25bbf 100644
--- a/webcomponents/remote/CHANGELOG.md
+++ b/webcomponents/remote/CHANGELOG.md
@@ -1,6 +1,12 @@
+# 1.2.0 (2020-06-30)
+
+### Features
+
+- migrate `rxjs` to `@stencil/store` ([#773](https://github.com/deckgo/deckdeckgo/issues/773))
+
# 1.1.1 (2020-05-11)
-### Feat
+### Features
- update Stencil for Gatsby build
diff --git a/webcomponents/remote/package-lock.json b/webcomponents/remote/package-lock.json
index ebe7ce106..f9a87a03f 100644
--- a/webcomponents/remote/package-lock.json
+++ b/webcomponents/remote/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "@deckdeckgo/remote",
- "version": "1.1.1",
+ "version": "1.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -56,12 +56,12 @@
"integrity": "sha512-/bomB2gBlEuXmLSXCHujABB1EYDTrcK/IQaH9NFh6ZUB+dEfb3L3uCof8RyUvQcEzqhVJJa63ZBnDnitJSOWYg=="
},
"@stencil/core": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@stencil/core/-/core-1.13.0.tgz",
- "integrity": "sha512-++kIXaEgmwm/vq+9QAVHPuLLddCKVdJyI8OfHxknkpu5udxZMYA/vaN/K9i+2NIiTLbGpvHNk9E+RyYzKxS0XQ==",
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/@stencil/core/-/core-1.15.0.tgz",
+ "integrity": "sha512-aWTagMDQtKTyNFCpAC98mYRDGDEp6ZfIuEO11RUxha2lqwwVjsXsn9HChr8k3XVPszb9azbmLoWrjT7b+2OKog==",
"dev": true,
"requires": {
- "typescript": "3.8.3"
+ "typescript": "3.9.5"
}
},
"@stencil/postcss": {
@@ -74,9 +74,15 @@
}
},
"@stencil/sass": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-1.3.1.tgz",
- "integrity": "sha512-5qsEyhLGTywpG4zlWv6eBhhj/z2Z37nbUGa87Ak0KqfsEiclJCYRA/AMM9FiN1jHfBvr968G4zE8rNlYmiPLsQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-1.3.2.tgz",
+ "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
},
"@stencil/utils": {
@@ -104,9 +110,9 @@
"dev": true
},
"@types/socket.io-client": {
- "version": "1.4.32",
- "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.32.tgz",
- "integrity": "sha512-Vs55Kq8F+OWvy1RLA31rT+cAyemzgm0EWNeax6BWF8H7QiiOYMJIdcwSDdm5LVgfEkoepsWkS+40+WNb7BUMbg==",
+ "version": "1.4.33",
+ "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.33.tgz",
+ "integrity": "sha512-m4LnxkljsI9fMsjwpW5QhRpMixo2BeeLpFmg0AE+sS4H1pzAd/cs/ftTiL60FLZgfFa8PFRPx5KsHu8O0bADKQ==",
"dev": true
},
"@types/webrtc": {
@@ -186,18 +192,31 @@
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"autoprefixer": {
- "version": "9.7.6",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.6.tgz",
- "integrity": "sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ==",
+ "version": "9.8.4",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.4.tgz",
+ "integrity": "sha512-84aYfXlpUe45lvmS+HoAWKCkirI/sw4JK0/bTeeqgHYco3dcsOn0NqdejISjptsYwNji/21dnkDri9PsYKk89A==",
"dev": true,
"requires": {
- "browserslist": "^4.11.1",
- "caniuse-lite": "^1.0.30001039",
- "chalk": "^2.4.2",
+ "browserslist": "^4.12.0",
+ "caniuse-lite": "^1.0.30001087",
+ "colorette": "^1.2.0",
"normalize-range": "^0.1.2",
"num2fraction": "^1.2.2",
- "postcss": "^7.0.27",
- "postcss-value-parser": "^4.0.3"
+ "postcss": "^7.0.32",
+ "postcss-value-parser": "^4.1.0"
+ },
+ "dependencies": {
+ "postcss": {
+ "version": "7.0.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz",
+ "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.2",
+ "source-map": "^0.6.1",
+ "supports-color": "^6.1.0"
+ }
+ }
}
},
"backo2": {
@@ -360,15 +379,15 @@
}
},
"browserslist": {
- "version": "4.11.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.11.1.tgz",
- "integrity": "sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==",
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.2.tgz",
+ "integrity": "sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw==",
"dev": true,
"requires": {
- "caniuse-lite": "^1.0.30001038",
- "electron-to-chromium": "^1.3.390",
- "node-releases": "^1.1.53",
- "pkg-up": "^2.0.0"
+ "caniuse-lite": "^1.0.30001088",
+ "electron-to-chromium": "^1.3.483",
+ "escalade": "^3.0.1",
+ "node-releases": "^1.1.58"
}
},
"buffer-es6": {
@@ -401,9 +420,9 @@
"dev": true
},
"caniuse-lite": {
- "version": "1.0.30001040",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz",
- "integrity": "sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==",
+ "version": "1.0.30001090",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001090.tgz",
+ "integrity": "sha512-QzPRKDCyp7RhjczTPZaqK3CjPA5Ht2UnXhZhCI4f7QiB5JK6KEuZBxIzyWnB3wO4hgAj4GMRxAhuiacfw0Psjg==",
"dev": true
},
"chalk": {
@@ -465,6 +484,12 @@
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
+ "colorette": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.0.tgz",
+ "integrity": "sha512-soRSroY+OF/8OdA3PTQXwaDJeMc7TfknKKrxeSCencL2a4+Tx5zhxmmv7hdpCjhKBjehzp8+bwe/T68K0hpIjw==",
+ "dev": true
+ },
"compare-versions": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
@@ -629,9 +654,9 @@
}
},
"electron-to-chromium": {
- "version": "1.3.402",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.402.tgz",
- "integrity": "sha512-gaCDfX7IUH0s3JmBiHCDPrvVcdnTTP1r4WLJc2dHkYYbLmXZ2XHiJCcGQ9Balf91aKTvuCKCyu2JjJYRykoI1w==",
+ "version": "1.3.483",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.483.tgz",
+ "integrity": "sha512-+05RF8S9rk8S0G8eBCqBRBaRq7+UN3lDs2DAvnG8SBSgQO3hjy0+qt4CmRk5eiuGbTcaicgXfPmBi31a+BD3lg==",
"dev": true
},
"elliptic": {
@@ -706,6 +731,12 @@
"is-arrayish": "^0.2.1"
}
},
+ "escalade": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.1.tgz",
+ "integrity": "sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA==",
+ "dev": true
+ },
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@@ -739,15 +770,6 @@
"strip-final-newline": "^2.0.0"
}
},
- "find-up": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
- "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
- "dev": true,
- "requires": {
- "locate-path": "^2.0.0"
- }
- },
"find-versions": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz",
@@ -1214,16 +1236,6 @@
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
"dev": true
},
- "locate-path": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
- "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
- "dev": true,
- "requires": {
- "p-locate": "^2.0.0",
- "path-exists": "^3.0.0"
- }
- },
"ltgt": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz",
@@ -1309,9 +1321,9 @@
}
},
"node-releases": {
- "version": "1.1.53",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz",
- "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==",
+ "version": "1.1.58",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.58.tgz",
+ "integrity": "sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg==",
"dev": true
},
"normalize-range": {
@@ -1387,30 +1399,6 @@
"integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==",
"dev": true
},
- "p-limit": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
- "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
- "dev": true,
- "requires": {
- "p-try": "^1.0.0"
- }
- },
- "p-locate": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
- "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
- "dev": true,
- "requires": {
- "p-limit": "^1.1.0"
- }
- },
- "p-try": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
- "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
- "dev": true
- },
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -1462,12 +1450,6 @@
"better-assert": "~1.0.0"
}
},
- "path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
- "dev": true
- },
"path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -1553,15 +1535,6 @@
}
}
},
- "pkg-up": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz",
- "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=",
- "dev": true,
- "requires": {
- "find-up": "^2.1.0"
- }
- },
"please-upgrade-node": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
@@ -1583,9 +1556,9 @@
}
},
"postcss-value-parser": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz",
- "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
+ "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
"dev": true
},
"prettier": {
@@ -1777,14 +1750,6 @@
"process-es6": "^0.11.2"
}
},
- "rxjs": {
- "version": "6.5.5",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
- "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
- "requires": {
- "tslib": "^1.9.0"
- }
- },
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
@@ -1933,11 +1898,6 @@
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
},
- "tslib": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
- "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
- },
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
@@ -1951,9 +1911,9 @@
"dev": true
},
"typescript": {
- "version": "3.8.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
- "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
+ "version": "3.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz",
+ "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==",
"dev": true
},
"util-deprecate": {
diff --git a/webcomponents/remote/package.json b/webcomponents/remote/package.json
index cb60c7132..965537d7d 100644
--- a/webcomponents/remote/package.json
+++ b/webcomponents/remote/package.json
@@ -1,6 +1,6 @@
{
"name": "@deckdeckgo/remote",
- "version": "1.1.1",
+ "version": "1.2.0",
"description": "Present and interact with your DeckDeckGo's PWA presentation",
"main": "dist/index.js",
"module": "dist/index.mjs",
@@ -24,18 +24,18 @@
"dependencies": {
"@deckdeckgo/remote-utils": "^1.0.0",
"@deckdeckgo/utils": "^1.1.0",
- "rxjs": "^6.5.5",
"socket.io-client": "^2.3.0"
},
"devDependencies": {
"@deckdeckgo/types": "^1.1.0",
- "@stencil/core": "^1.13.0",
+ "@stencil/core": "^1.15.0",
"@stencil/postcss": "^1.0.1",
- "@stencil/sass": "^1.3.1",
+ "@stencil/sass": "^1.3.2",
+ "@stencil/store": "^1.3.0",
"@stencil/utils": "0.0.5",
- "@types/socket.io-client": "^1.4.32",
+ "@types/socket.io-client": "^1.4.33",
"@types/webrtc": "0.0.26",
- "autoprefixer": "^9.7.6",
+ "autoprefixer": "^9.8.4",
"husky": "^4.2.5",
"prettier": "2.0.5",
"pretty-quick": "^2.0.1",
diff --git a/webcomponents/remote/src/components.d.ts b/webcomponents/remote/src/components.d.ts
index 8d28e337a..d55864f82 100644
--- a/webcomponents/remote/src/components.d.ts
+++ b/webcomponents/remote/src/components.d.ts
@@ -5,7 +5,7 @@
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
-import { ConnectionState, DeckdeckgoDeckDefinition, DeckdeckgoEvent, DeckdeckgoSlideDefinition, } from "@deckdeckgo/types";
+import { ConnectionState, DeckdeckgoDeckDefinition, DeckdeckgoEvent, DeckdeckgoSlideDefinition } from "@deckdeckgo/types";
export namespace Components {
interface DeckgoRemote {
"autoConnect": boolean;
@@ -21,7 +21,7 @@ export namespace Components {
"play": () => Promise;
"prevSlide": (slideAnimation?: boolean) => Promise;
"room": string;
- "slideTo": (index: number, speed?: number) => Promise;
+ "slideTo": (index: number, speed?: number | undefined) => Promise;
"socketUrl": string;
"start": (appSocketId: string) => Promise;
"updateReveal": (reveal: boolean) => Promise;
diff --git a/webcomponents/remote/src/components/deckdeckgo-remote/deckdeckgo-remote.tsx b/webcomponents/remote/src/components/deckdeckgo-remote/deckdeckgo-remote.tsx
index 25bf483d4..358f561ba 100644
--- a/webcomponents/remote/src/components/deckdeckgo-remote/deckdeckgo-remote.tsx
+++ b/webcomponents/remote/src/components/deckdeckgo-remote/deckdeckgo-remote.tsx
@@ -1,7 +1,5 @@
import {Component, Element, Event, EventEmitter, h, Method, Prop, State, Watch} from '@stencil/core';
-import {Subscription} from 'rxjs';
-
// Types
import {
DeckdeckgoDrawAction,
@@ -12,20 +10,22 @@ import {
DeckdeckgoSlideAction,
DeckdeckgoDeckDefinition,
DeckdeckgoSlideDefinition,
- ConnectionState
+ ConnectionState,
} from '@deckdeckgo/types';
import {isMobile} from '@deckdeckgo/utils';
import {Arrow, Circle, Drawable, Pencil} from '@deckdeckgo/remote-utils';
+import store from '../../stores/remote.store';
+
// Services
import {CommunicationService} from '../../services/communication/communication.service';
@Component({
tag: 'deckgo-remote',
styleUrl: 'deckdeckgo-remote.scss',
- shadow: true
+ shadow: true,
})
export class DeckdeckgoRemote {
@Element() el: HTMLElement;
@@ -46,8 +46,8 @@ export class DeckdeckgoRemote {
@Event() state: EventEmitter;
@Event() event: EventEmitter;
- private subscriptionState: Subscription;
- private subscriptionEvent: Subscription;
+ private destroyStateListener;
+ private destroyEventListener;
private ctx: CanvasRenderingContext2D;
private drawables: Drawable[] = [];
@@ -64,26 +64,26 @@ export class DeckdeckgoRemote {
}
async componentDidLoad() {
- this.subscriptionState = this.communicationService.watchState().subscribe((state: ConnectionState) => {
+ this.destroyStateListener = store.onChange('state', (state: ConnectionState) => {
this.state.emit(state);
});
- this.subscriptionEvent = this.communicationService.watchEvent().subscribe(async (event: DeckdeckgoEvent) => {
- if (event.emitter === DeckdeckgoEventEmitter.APP) {
- if (event.type === DeckdeckgoEventType.SLIDES_REQUEST) {
+ this.destroyEventListener = store.onChange('$event', async ($event: DeckdeckgoEvent) => {
+ if ($event.emitter === DeckdeckgoEventEmitter.APP) {
+ if ($event.type === DeckdeckgoEventType.SLIDES_REQUEST) {
// If app is asking for the deck length, how many slides, we answer directly
await this.sendSlidesToApp(DeckdeckgoEventType.SLIDES_ANSWER);
- } else if (event.type === DeckdeckgoEventType.CLEAR_SLIDE) {
+ } else if ($event.type === DeckdeckgoEventType.CLEAR_SLIDE) {
await this.clear();
- } else if (event.type === DeckdeckgoEventType.START_DRAWING) {
- await this.startDrawing(event as DeckdeckgoEventDraw);
- } else if (event.type === DeckdeckgoEventType.END_DRAWING) {
- await this.endDrawing(event as DeckdeckgoEventDraw);
- } else if (event.type === DeckdeckgoEventType.DRAW) {
- await this.draw(event as DeckdeckgoEventDraw);
+ } else if ($event.type === DeckdeckgoEventType.START_DRAWING) {
+ await this.startDrawing($event as DeckdeckgoEventDraw);
+ } else if ($event.type === DeckdeckgoEventType.END_DRAWING) {
+ await this.endDrawing($event as DeckdeckgoEventDraw);
+ } else if ($event.type === DeckdeckgoEventType.DRAW) {
+ await this.draw($event as DeckdeckgoEventDraw);
} else {
// Else it's a command to apply on the deck, we propagate
- this.event.emit(event);
+ this.event.emit($event);
}
}
});
@@ -106,12 +106,12 @@ export class DeckdeckgoRemote {
async componentDidUnload() {
await this.communicationService.disconnect();
- if (this.subscriptionState) {
- this.subscriptionState.unsubscribe();
+ if (this.destroyStateListener) {
+ this.destroyStateListener();
}
- if (this.subscriptionEvent) {
- this.subscriptionEvent.unsubscribe();
+ if (this.destroyEventListener) {
+ this.destroyEventListener();
}
}
@@ -210,7 +210,7 @@ export class DeckdeckgoRemote {
{x: this.startX, y: this.startY},
{
x: this.startX,
- y: this.startY
+ y: this.startY,
},
event.color
)
@@ -245,7 +245,7 @@ export class DeckdeckgoRemote {
{x: this.startX, y: this.startY},
{
x: toX,
- y: toY
+ y: toY,
},
event.color
);
@@ -277,7 +277,7 @@ export class DeckdeckgoRemote {
{x: this.startX, y: this.startY},
{
x: toX,
- y: toY
+ y: toY,
},
event.color
);
@@ -308,7 +308,7 @@ export class DeckdeckgoRemote {
emitter: DeckdeckgoEventEmitter.DECK,
length: this.length,
deck: this.deck,
- mobile: isMobile()
+ mobile: isMobile(),
});
resolve();
@@ -339,7 +339,7 @@ export class DeckdeckgoRemote {
type: DeckdeckgoEventType.SLIDE_UPDATE,
emitter: DeckdeckgoEventEmitter.DECK,
index: index,
- slide: slide
+ slide: slide,
});
resolve();
@@ -351,7 +351,7 @@ export class DeckdeckgoRemote {
return new Promise((resolve) => {
this.communicationService.emit({
type: DeckdeckgoEventType.DELETE_SLIDE,
- emitter: DeckdeckgoEventEmitter.DECK
+ emitter: DeckdeckgoEventEmitter.DECK,
});
resolve();
@@ -364,7 +364,7 @@ export class DeckdeckgoRemote {
this.communicationService.emit({
type: DeckdeckgoEventType.DECK_REVEAL_UPDATE,
emitter: DeckdeckgoEventEmitter.DECK,
- reveal: reveal
+ reveal: reveal,
});
resolve();
@@ -406,7 +406,7 @@ export class DeckdeckgoRemote {
type: DeckdeckgoEventType.SLIDE_TO,
emitter: DeckdeckgoEventEmitter.DECK,
index: index,
- speed: speed
+ speed: speed,
});
}
@@ -415,7 +415,7 @@ export class DeckdeckgoRemote {
this.communicationService.emit({
type: DeckdeckgoEventType.SLIDE_ACTION,
emitter: DeckdeckgoEventEmitter.DECK,
- action: DeckdeckgoSlideAction.PLAY
+ action: DeckdeckgoSlideAction.PLAY,
});
}
@@ -424,7 +424,7 @@ export class DeckdeckgoRemote {
this.communicationService.emit({
type: DeckdeckgoEventType.SLIDE_ACTION,
emitter: DeckdeckgoEventEmitter.DECK,
- action: DeckdeckgoSlideAction.PAUSE
+ action: DeckdeckgoSlideAction.PAUSE,
});
}
diff --git a/webcomponents/remote/src/services/communication/communication.service.tsx b/webcomponents/remote/src/services/communication/communication.service.tsx
index a92965b52..4ac274a0e 100644
--- a/webcomponents/remote/src/services/communication/communication.service.tsx
+++ b/webcomponents/remote/src/services/communication/communication.service.tsx
@@ -1,6 +1,6 @@
import * as io from 'socket.io-client';
-import {BehaviorSubject, Observable, Subject} from 'rxjs';
+import store from '../../stores/remote.store';
// Types
import {
@@ -14,7 +14,7 @@ import {
DeckdeckgoEventEmitter,
DeckdeckgoEventDeckRequest,
DeckdeckgoEventType,
- ConnectionState
+ ConnectionState,
} from '@deckdeckgo/types';
const configuration: RTCConfiguration = {
@@ -22,14 +22,14 @@ const configuration: RTCConfiguration = {
{
urls: 'turn:api.deckdeckgo.com:3478',
username: 'user',
- credential: 'deckdeckgo'
- }
- ]
+ credential: 'deckdeckgo',
+ },
+ ],
};
const dataChannelOptions = {
ordered: false, //no guaranteed delivery, unreliable but faster
- maxPacketLifeTime: 1000 //milliseconds
+ maxPacketLifeTime: 1000, //milliseconds
};
const DEFAULT_SOCKET_URL: string = 'https://api.deckdeckgo.com';
@@ -52,9 +52,6 @@ export class CommunicationService {
room: string;
socketUrl: string;
- private state: BehaviorSubject = new BehaviorSubject(ConnectionState.DISCONNECTED);
- private event: Subject = new Subject();
-
private constructor() {
// Private constructor, singleton
}
@@ -75,24 +72,24 @@ export class CommunicationService {
const url: string = this.socketUrl ? this.socketUrl : DEFAULT_SOCKET_URL;
- this.state.next(ConnectionState.CONNECTING);
+ store.state.state = ConnectionState.CONNECTING;
this.socket = io.connect(url, {
reconnectionAttempts: 5,
transports: ['websocket', 'xhr-polling'],
- query: 'type=app'
+ query: 'type=app',
});
this.socket.on('connect', async () => {
this.socket.emit('join', {
room: this.room,
- deck: true
+ deck: true,
});
});
this.socket.on('joined', async () => {
// Do nothing on the deck side
- this.state.next(ConnectionState.CONNECTED_WITH_SIGNALING_SERVER);
+ store.state.state = ConnectionState.CONNECTED_WITH_SIGNALING_SERVER;
});
this.socket.on('signaling_message', async (data) => {
@@ -102,12 +99,12 @@ export class CommunicationService {
}
if (data.type === 'app_here') {
- this.event.next({
+ store.state.$event = {
type: DeckdeckgoEventType.DECK_REQUEST,
emitter: DeckdeckgoEventEmitter.APP,
message: data.message,
- fromSocketId: data.fromSocketId
- } as DeckdeckgoEventDeckRequest);
+ fromSocketId: data.fromSocketId,
+ } as DeckdeckgoEventDeckRequest;
return;
}
@@ -119,7 +116,7 @@ export class CommunicationService {
// App create answer
},
(_err) => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ store.state.state = ConnectionState.NOT_CONNECTED;
}
);
} else {
@@ -128,23 +125,23 @@ export class CommunicationService {
});
this.socket.on('connect_error', () => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ store.state.state = ConnectionState.NOT_CONNECTED;
});
this.socket.on('connect_timeout', () => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ store.state.state = ConnectionState.NOT_CONNECTED;
});
this.socket.on('error', () => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ store.state.state = ConnectionState.NOT_CONNECTED;
});
this.socket.on('reconnect_failed', () => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ store.state.state = ConnectionState.NOT_CONNECTED;
});
this.socket.on('reconnect_error', () => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ store.state.state = ConnectionState.NOT_CONNECTED;
});
resolve();
@@ -166,13 +163,13 @@ export class CommunicationService {
if (this.socket) {
this.socket.emit('leave', {
- room: this.room
+ room: this.room,
});
this.socket.removeAllListeners();
this.socket.disconnect();
}
- this.state.next(ConnectionState.DISCONNECTED);
+ store.state.state = ConnectionState.DISCONNECTED;
resolve();
});
@@ -187,7 +184,7 @@ export class CommunicationService {
this.sendLocalDesc(desc, appSocketId);
},
(_err) => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ store.state.state = ConnectionState.NOT_CONNECTED;
}
);
}
@@ -195,14 +192,6 @@ export class CommunicationService {
return;
}
- watchState(): Observable {
- return this.state.asObservable();
- }
-
- watchEvent(): Observable {
- return this.event.asObservable();
- }
-
private startSignaling() {
this.rtcPeerConn = new PeerConnection(configuration);
@@ -215,7 +204,7 @@ export class CommunicationService {
this.socket.emit('signal', {
type: 'ice_candidate',
message: JSON.stringify({candidate: evt.candidate}),
- room: this.room
+ room: this.room,
});
}
};
@@ -228,11 +217,11 @@ export class CommunicationService {
type: 'sending_local_description',
message: JSON.stringify({sdp: this.rtcPeerConn.localDescription}),
room: this.room,
- toSocketId: appSocketId
+ toSocketId: appSocketId,
});
},
(_err) => {
- this.state.next(ConnectionState.NOT_CONNECTED);
+ store.state.state = ConnectionState.NOT_CONNECTED;
}
);
}
@@ -240,11 +229,11 @@ export class CommunicationService {
private dataChannelStateChanged = () => {
if (this.dataChannelOut.readyState === 'open') {
this.dataChannelOut.onmessage = this.receiveDataChannelMessage;
- this.state.next(ConnectionState.CONNECTED);
+ store.state.state = ConnectionState.CONNECTED;
if (this.socket) {
this.socket.emit('connected', {
- room: this.room
+ room: this.room,
});
}
}
@@ -256,7 +245,7 @@ export class CommunicationService {
}
const data: DeckdeckgoEvent = JSON.parse($event.data);
- this.event.next(data);
+ store.state.$event = {...data};
};
emit(
diff --git a/webcomponents/remote/src/stores/remote.store.ts b/webcomponents/remote/src/stores/remote.store.ts
new file mode 100644
index 000000000..17829c90f
--- /dev/null
+++ b/webcomponents/remote/src/stores/remote.store.ts
@@ -0,0 +1,15 @@
+import {createStore} from '@stencil/store';
+
+import {ConnectionState, DeckdeckgoEvent} from '@deckdeckgo/types';
+
+interface RemoteStore {
+ state: ConnectionState;
+ $event: DeckdeckgoEvent | undefined;
+}
+
+const {state, onChange} = createStore({
+ state: ConnectionState.DISCONNECTED,
+ $event: undefined,
+} as RemoteStore);
+
+export default {state, onChange};