Skip to content
This repository was archived by the owner on Feb 6, 2024. It is now read-only.

Commit 1a68966

Browse files
fix: before unload notification and sync button rendering quirk (#1374)
* feat: cannot delete currently edited deck Signed-off-by: peterpeterparker <[email protected]> * feat: no before unload notification upon navigating programmatically from dashboard Signed-off-by: peterpeterparker <[email protected]> * fix: refresh issue and missing ripple effect Signed-off-by: peterpeterparker <[email protected]>
1 parent 27f4f93 commit 1a68966

File tree

11 files changed

+96
-53
lines changed

11 files changed

+96
-53
lines changed

studio/src/app/components/core/app-dashboard-deck-actions/app-dashboard-deck-actions.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {loadingController, popoverController} from '@ionic/core';
55

66
import {Deck} from '@deckdeckgo/editor';
77

8-
import store from '../../../stores/error.store';
8+
import errorStore from '../../../stores/error.store';
99
import i18n from '../../../stores/i18n.store';
1010

1111
import {clone} from '../../../utils/core/dashboard.utils';
@@ -19,7 +19,11 @@ import {AppIcon} from '../../core/app-icon/app-icon';
1919
styleUrl: 'app-dashboard-deck-actions.scss'
2020
})
2121
export class AppDashboardDeckActions {
22-
@Prop() deck: Deck;
22+
@Prop()
23+
deck: Deck;
24+
25+
@Prop()
26+
disableDelete: boolean;
2327

2428
@Event() deckDeleted: EventEmitter<string>;
2529
@Event() deckCloned: EventEmitter<void>;
@@ -71,7 +75,7 @@ export class AppDashboardDeckActions {
7175

7276
this.deckDeleted.emit(this.deck.id);
7377
} catch (err) {
74-
store.state.error = err;
78+
errorStore.state.error = err;
7579
}
7680

7781
await loading.dismiss();
@@ -100,7 +104,7 @@ export class AppDashboardDeckActions {
100104

101105
this.deckCloned.emit();
102106
} catch (err) {
103-
store.state.error = err;
107+
errorStore.state.error = err;
104108
}
105109

106110
await loading.dismiss();
@@ -111,19 +115,14 @@ export class AppDashboardDeckActions {
111115
render() {
112116
return (
113117
<Host>
114-
<button
115-
onClick={($event: UIEvent) => this.cloneDeck($event)}
116-
title={i18n.state.dashboard.copy}
117-
disabled={this.actionInProgress}
118-
class={this.actionInProgress ? 'disabled' : undefined}>
118+
<button onClick={($event: UIEvent) => this.cloneDeck($event)} title={i18n.state.dashboard.copy} disabled={this.actionInProgress}>
119119
<AppIcon name="copy" ariaLabel="" ariaHidden={true}></AppIcon>
120120
</button>
121121

122122
<button
123123
onClick={($event: UIEvent) => this.presentConfirmDelete($event)}
124124
title={i18n.state.dashboard.delete}
125-
disabled={this.actionInProgress}
126-
class={this.actionInProgress ? 'disabled' : undefined}>
125+
disabled={this.actionInProgress || this.disableDelete}>
127126
<AppIcon name="trash" ariaLabel="" ariaHidden={true}></AppIcon>
128127
</button>
129128
</Host>

studio/src/app/components/core/app-navigation-actions/app-navigation-actions.tsx

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,18 @@ export class AppNavigationActions {
140140

141141
return (
142142
<Fragment>
143-
<button class="ion-activatable" onClick={() => this.newDeck()} disabled={disabled} aria-label={i18n.state.tools.new_presentation}>
143+
<button
144+
key="new-deck-action"
145+
class="ion-activatable"
146+
onClick={() => this.newDeck()}
147+
disabled={disabled}
148+
aria-label={i18n.state.tools.new_presentation}>
144149
<ion-ripple-effect></ion-ripple-effect>
145150
<AppIcon name="document" ariaHidden={true} ariaLabel=""></AppIcon>
146151
<ion-label>{i18n.state.tools.new}</ion-label>
147152
</button>
148153

149-
<button class="ion-activatable" onClick={() => this.openFilePicker()} disabled={disabled}>
154+
<button key="open-file-action" class="ion-activatable" onClick={() => this.openFilePicker()} disabled={disabled}>
150155
<ion-ripple-effect></ion-ripple-effect>
151156
<AppIcon name="folder-open" ariaHidden={true} ariaLabel=""></AppIcon>
152157
<ion-label>{i18n.state.tools.open}</ion-label>
@@ -160,7 +165,7 @@ export class AppNavigationActions {
160165
tabindex="-1"
161166
/>
162167

163-
<button class="ion-activatable" onClick={() => this.exportData()}>
168+
<button key="export-action" class="ion-activatable" onClick={() => this.exportData()}>
164169
<ion-ripple-effect></ion-ripple-effect>
165170
<AppIcon name="download" ariaHidden={true} ariaLabel=""></AppIcon>
166171
<ion-label>{i18n.state.editor.export}</ion-label>
@@ -175,7 +180,7 @@ export class AppNavigationActions {
175180
}
176181

177182
return (
178-
<button class="ion-activatable" onClick={() => signIn()}>
183+
<button key="sign-in-action" class="ion-activatable" onClick={() => signIn()}>
179184
<ion-ripple-effect></ion-ripple-effect>
180185
<AppIcon name="log-in" ariaHidden={true} ariaLabel=""></AppIcon>
181186
<ion-label>{i18n.state.nav.sign_in}</ion-label>
@@ -189,7 +194,11 @@ export class AppNavigationActions {
189194
<Fragment>
190195
{this.renderCloudStatus()}
191196

192-
<button class="ion-activatable" onClick={(e: UIEvent) => this.openMenu(e)} aria-label={i18n.state.nav.menu}>
197+
<button
198+
key="user-menu-action"
199+
class="ion-activatable"
200+
onClick={(e: UIEvent) => this.openMenu(e)}
201+
aria-label={i18n.state.nav.menu}>
193202
<ion-ripple-effect></ion-ripple-effect>
194203
<app-avatar src={userStore.state.photoUrl}></app-avatar>
195204
<ion-label>{userStore.state.name ?? i18n.state.tools.user}</ion-label>
@@ -215,15 +224,21 @@ export class AppNavigationActions {
215224
? i18n.state.sync.cloud_pending
216225
: i18n.state.sync.cloud_idle;
217226

227+
const iconName: string =
228+
syncStore.state.sync === 'error'
229+
? 'cloud-offline'
230+
: ['in_progress', 'pending'].includes(syncStore.state.sync)
231+
? 'sync'
232+
: 'cloud-done';
233+
218234
return (
219-
<button class={`cloud ${syncStore.state.sync}`} aria-label={label} onClick={($event: UIEvent) => this.openSyncInfo($event)}>
220-
{syncStore.state.sync === 'error' ? (
221-
<AppIcon name="cloud-offline" ariaHidden={true} ariaLabel=""></AppIcon>
222-
) : ['in_progress', 'pending'].includes(syncStore.state.sync) ? (
223-
<AppIcon name="sync" ariaHidden={true} ariaLabel=""></AppIcon>
224-
) : (
225-
<AppIcon name="cloud-done" ariaHidden={true} ariaLabel=""></AppIcon>
226-
)}
235+
<button
236+
key="cloud-status-action"
237+
class={`cloud ion-activatable ${syncStore.state.sync}`}
238+
aria-label={label}
239+
onClick={($event: UIEvent) => this.openSyncInfo($event)}>
240+
<ion-ripple-effect></ion-ripple-effect>
241+
<AppIcon name={iconName} ariaHidden={true} ariaLabel=""></AppIcon>
227242
<ion-label>{i18n.state.sync.cloud}</ion-label>
228243
</button>
229244
);

studio/src/app/components/core/app-signin/app-signin.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {EnvironmentCloud, EnvironmentDeckDeckGoConfig} from '../../../types/core
1414

1515
import {renderI18n} from '../../../utils/core/i18n.utils';
1616
import {firebase, cloud} from '../../../utils/core/environment.utils';
17+
import {removeSyncBeforeUnload} from '../../../utils/core/sync.window.utils';
1718

1819
@Component({
1920
tag: 'app-signin',
@@ -34,6 +35,9 @@ export class AppSignIn {
3435
await this.loadSignIn();
3536

3637
await this.initSignIn();
38+
39+
// We do not want to present a warning when user sign in
40+
removeSyncBeforeUnload();
3741
}
3842

3943
private onSignInSuccess = (credentials: {uid: string | undefined; githubAccessToken: string | undefined} | undefined) => {

studio/src/app/pages/core/app-decks/app-decks.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ app-decks {
3131
}
3232

3333
article {
34-
cursor: pointer;
35-
3634
visibility: visible;
3735
opacity: 1;
3836

studio/src/app/pages/core/app-decks/app-decks.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {ParseDeckSlotsUtils} from '../../../utils/editor/parse-deck-slots.utils'
1919
import {ParseSlidesUtils} from '../../../utils/editor/parse-slides.utils';
2020
import {TemplateUtils} from '../../../utils/editor/template.utils';
2121
import {loadAndImport} from '../../../utils/core/dashboard.utils';
22+
import {getEdit} from '../../../utils/editor/editor.utils';
23+
import {removeSyncBeforeUnload} from '../../../utils/core/sync.window.utils';
2224

2325
import {decks} from '../../../providers/data/deck/deck.provider';
2426
import {getSlide} from '../../../providers/data/slide/slide.provider';
@@ -50,6 +52,9 @@ export class AppDecks implements ComponentInterface {
5052
@State()
5153
private decksLoading: boolean = true;
5254

55+
@State()
56+
private currentDeckId: string | undefined;
57+
5358
private readonly debounceLoading: () => void;
5459
private readonly debounceDecksLoading: () => void;
5560

@@ -77,6 +82,8 @@ export class AppDecks implements ComponentInterface {
7782
});
7883

7984
await this.initDashboard();
85+
86+
this.currentDeckId = await getEdit();
8087
}
8188

8289
private async initDashboard() {
@@ -223,6 +230,9 @@ export class AppDecks implements ComponentInterface {
223230
}
224231

225232
private navigateReloadEditor() {
233+
// We are aware a sync is going to happen and we are navigating programmatically
234+
removeSyncBeforeUnload();
235+
226236
navStore.state.nav = {
227237
url: '/',
228238
direction: NavDirection.RELOAD
@@ -367,8 +377,10 @@ export class AppDecks implements ComponentInterface {
367377
}
368378

369379
return (
370-
<article key={deck.deck.id} onClick={() => this.navigateDeck(deck)}>
371-
<ion-card class="item ion-no-margin">{this.renderDeck(deck)}</ion-card>
380+
<article key={deck.deck.id}>
381+
<ion-card custom-tappable class="item ion-no-margin" onClick={() => this.navigateDeck(deck)}>
382+
{this.renderDeck(deck)}
383+
</ion-card>
372384

373385
{this.renderAside(deck)}
374386
</article>
@@ -384,6 +396,7 @@ export class AppDecks implements ComponentInterface {
384396

385397
<app-dashboard-deck-actions
386398
deck={deck.deck}
399+
disableDelete={deck.deck.id === this.currentDeckId}
387400
onDeckDeleted={($event: CustomEvent) => this.removeDeletedDeck($event)}
388401
onDeckCloned={() => this.navigateReloadEditor()}></app-dashboard-deck-actions>
389402
</aside>

studio/src/app/pages/editor/app-editor/app-editor.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ import {SlideTemplate, SyncEvent} from '@deckdeckgo/editor';
2020

2121
import {CreateSlidesUtils} from '../../../utils/editor/create-slides.utils';
2222
import {ParseDeckSlotsUtils} from '../../../utils/editor/parse-deck-slots.utils';
23+
import {getEdit} from '../../../utils/editor/editor.utils';
24+
import {signIn as navigateSignIn} from '../../../utils/core/signin.utils';
25+
import {SlideUtils} from '../../../utils/editor/slide.utils';
2326

2427
import {DeckEvents} from '../../../events/editor/deck/deck.events';
2528
import {RemoteEvents} from '../../../events/editor/remote/remote.events';
@@ -32,13 +35,10 @@ import {SlideHelper} from '../../../helpers/editor/slide.helper';
3235

3336
import {SlotType} from '../../../types/editor/slot-type';
3437

35-
import {signIn as navigateSignIn} from '../../../utils/core/signin.utils';
36-
import {SlideUtils} from '../../../utils/editor/slide.utils';
37-
3838
import {EnvironmentConfigService} from '../../../services/environment/environment-config.service';
3939
import {FontsService} from '../../../services/editor/fonts/fonts.service';
4040

41-
import {sync} from '../../../providers/sync/sync.provider';
41+
import {initSyncState, sync} from '../../../providers/sync/sync.provider';
4242

4343
import {EnvironmentGoogleConfig} from '../../../types/core/environment-config';
4444

@@ -158,6 +158,8 @@ export class AppEditor {
158158

159159
await sync(data.data);
160160
};
161+
162+
await initSyncState();
161163
}
162164

163165
@Listen('reloadDeck', {target: 'document'})
@@ -175,7 +177,7 @@ export class AppEditor {
175177
}
176178

177179
private async initOrFetch() {
178-
const deckId: string | undefined = await get<string>('deckdeckgo_deck_id');
180+
const deckId: string | undefined = await getEdit();
179181

180182
if (!deckId) {
181183
await this.initSlide();

studio/src/app/stores/sync.store.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import {createStore} from '@stencil/store';
22

33
import {SyncState} from '@deckdeckgo/editor';
44

5+
import {syncBeforeUnload} from '../utils/core/sync.window.utils';
6+
57
interface SyncStore {
68
sync: SyncState;
79
}
@@ -10,22 +12,6 @@ const {state, onChange} = createStore<SyncStore>({
1012
sync: 'idle'
1113
});
1214

13-
const onBeforeUnload = ($event: BeforeUnloadEvent) => {
14-
if (window.location.pathname === '/signin') {
15-
// We do not want to present a warning when user sign in
16-
return;
17-
}
18-
19-
$event.preventDefault();
20-
return ($event.returnValue = 'Are you sure you want to exit?');
21-
};
22-
23-
onChange('sync', (sync: SyncState) => {
24-
if (['pending', 'in_progress'].includes(sync)) {
25-
window.addEventListener('beforeunload', onBeforeUnload, {capture: true});
26-
} else {
27-
window.removeEventListener('beforeunload', onBeforeUnload, {capture: true});
28-
}
29-
});
15+
onChange('sync', (sync: SyncState) => syncBeforeUnload(sync));
3016

3117
export default {state, onChange};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {SyncState} from '@deckdeckgo/editor';
2+
3+
const onBeforeUnload = ($event: BeforeUnloadEvent) => {
4+
$event.preventDefault();
5+
return ($event.returnValue = 'Are you sure you want to exit?');
6+
};
7+
8+
const addSyncBeforeUnload = () => {
9+
window.addEventListener('beforeunload', onBeforeUnload, {capture: true});
10+
};
11+
12+
export const removeSyncBeforeUnload = () => {
13+
window.removeEventListener('beforeunload', onBeforeUnload, {capture: true});
14+
};
15+
16+
export const syncBeforeUnload = (sync: SyncState) => {
17+
if (['pending', 'in_progress'].includes(sync)) {
18+
addSyncBeforeUnload();
19+
} else {
20+
removeSyncBeforeUnload();
21+
}
22+
};

studio/src/app/utils/editor/editor.utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import {del} from 'idb-keyval';
1+
import {del, get} from 'idb-keyval';
22

33
import {clearSync} from '../../providers/sync/sync.provider';
44

55
import {ImageHistoryService} from '../../services/editor/image-history/image-history.service';
66

7+
export const getEdit = (): Promise<string | undefined> => get('deckdeckgo_deck_id');
8+
79
export const clearEdit = async (clearSyncData: boolean) => {
810
if (clearSyncData) {
911
await clearSync();

studio/src/components.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export namespace Components {
133133
}
134134
interface AppDashboardDeckActions {
135135
"deck": Deck;
136+
"disableDelete": boolean;
136137
}
137138
interface AppDeckFonts {
138139
"deckElement": HTMLElement;
@@ -1409,6 +1410,7 @@ declare namespace LocalJSX {
14091410
}
14101411
interface AppDashboardDeckActions {
14111412
"deck"?: Deck;
1413+
"disableDelete"?: boolean;
14121414
"onDeckCloned"?: (event: CustomEvent<void>) => void;
14131415
"onDeckDeleted"?: (event: CustomEvent<string>) => void;
14141416
}

0 commit comments

Comments
 (0)