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

Commit d8e41e0

Browse files
feat: reorder slides with aside thumbnails (#1295)
* fix: selector for main deck * feat: drag and drop slides * feat: drag and drop slides on top slide * style: align thumbnails and actions
1 parent 4c9f3b4 commit d8e41e0

File tree

7 files changed

+145
-41
lines changed

7 files changed

+145
-41
lines changed

studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.scss

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,33 @@ app-slides-aside {
1313

1414
--preview-width: calc(var(--slides-aside-width) - 32px);
1515

16+
&.drag {
17+
app-slide-thumbnail {
18+
transition: margin 0.25s ease-out, min-height 0.25s ease-in;
19+
}
20+
}
21+
1622
app-slide-thumbnail {
1723
margin-bottom: 16px;
24+
25+
transition: margin 0.15s ease-in;
26+
27+
&.hover {
28+
margin-bottom: calc((var(--slides-aside-width) - 32px) * 9 / 16);
29+
}
30+
31+
&.hover-top {
32+
margin-top: calc((var(--slides-aside-width) - 32px) * 9 / 16);
33+
}
34+
35+
&.drag-start, &.drag-hover {
36+
visibility: hidden;
37+
opacity: 0;
38+
}
39+
40+
&.drag-hover {
41+
min-height: 0;
42+
border: none;
43+
}
1844
}
1945
}

studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import {Component, Listen, h, Host, State, Prop} from '@stencil/core';
1+
import {Component, Listen, h, Host, State, Prop, Event, EventEmitter} from '@stencil/core';
2+
3+
import {ItemReorderEventDetail} from '@ionic/core';
24

35
import {debounce} from '@deckdeckgo/utils';
46

@@ -16,6 +18,12 @@ export class AppSlidesAside {
1618
@Prop()
1719
deckRef!: HTMLDeckgoDeckElement;
1820

21+
@Event()
22+
private reorder: EventEmitter<ItemReorderEventDetail>;
23+
24+
@State()
25+
private reorderDetail: ItemReorderEventDetail | undefined = undefined;
26+
1927
private readonly debounceUpdateAllSlides: () => void;
2028

2129
private readonly debounceUpdateSlide: (updateSlide: HTMLElement) => void;
@@ -67,16 +75,88 @@ export class AppSlidesAside {
6775
.map((slide: HTMLElement) => slide.cloneNode(true) as HTMLElement);
6876
}
6977

78+
private onDragStart(from: number) {
79+
this.reorderDetail = {
80+
from,
81+
to: undefined,
82+
complete: () => {}
83+
};
84+
}
85+
86+
private onDragHover(to: number) {
87+
if (!this.reorderDetail) {
88+
return;
89+
}
90+
91+
this.reorderDetail = {
92+
...this.reorderDetail,
93+
to
94+
};
95+
}
96+
97+
private onDragLeave() {
98+
if (!this.reorderDetail) {
99+
return;
100+
}
101+
102+
if (this.reorderDetail.to !== 0) {
103+
return;
104+
}
105+
106+
this.reorderDetail = {
107+
...this.reorderDetail,
108+
to: -1
109+
};
110+
}
111+
112+
private onDrop() {
113+
if (!this.reorderDetail || this.reorderDetail.to === undefined) {
114+
return;
115+
}
116+
117+
const {from, to, complete} = this.reorderDetail;
118+
const detail = {
119+
from,
120+
to: from > to ? to + 1 : to,
121+
complete
122+
};
123+
124+
this.reorder.emit(detail);
125+
126+
this.slides.splice(detail.to, 0, ...this.slides.splice(detail.from, 1));
127+
this.slides = [...this.slides];
128+
129+
this.reorderDetail = undefined;
130+
}
131+
70132
render() {
71133
return (
72-
<Host>
134+
<Host
135+
onDrop={() => this.onDrop()}
136+
onDragOver={($event: DragEvent) => $event.preventDefault()}
137+
onDragLeave={() => this.onDragLeave()}
138+
class={this.reorderDetail !== undefined ? 'drag' : ''}>
73139
{this.slides.map((slide: HTMLElement, index: number) => (
74140
<app-slide-thumbnail
75141
custom-tappable
76142
onClick={async () => await slideTo(index)}
77143
key={slide.getAttribute('slide_id')}
78144
slide={slide}
79-
deck={this.deckRef}></app-slide-thumbnail>
145+
deck={this.deckRef}
146+
class={
147+
index === this.reorderDetail?.to && this.reorderDetail?.from !== this.reorderDetail?.to
148+
? 'hover'
149+
: index === 0 && this.reorderDetail?.to === -1
150+
? 'hover-top'
151+
: index === this.reorderDetail?.from
152+
? index === this.reorderDetail?.to
153+
? 'drag-start'
154+
: 'drag-hover'
155+
: ''
156+
}
157+
draggable={true}
158+
onDragStart={() => this.onDragStart(index)}
159+
onDragOver={() => this.onDragHover(index)}></app-slide-thumbnail>
80160
))}
81161
</Host>
82162
);

studio/src/app/components/editor/actions/footer/app-actions-editor/app-actions-editor.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ app-actions-editor {
5050
aside {
5151
height: 100%;
5252

53-
padding: 16px 0;
53+
padding: 8px 0;
5454

5555
display: flex;
5656
flex-direction: column;

studio/src/app/handlers/editor/events/remote/remote-events.handler.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {ConnectionState, DeckdeckgoDeckDefinition, DeckdeckgoEventDeckRequest, D
99
import {EnvironmentDeckDeckGoConfig} from '../../../../types/core/environment-config';
1010
import {EnvironmentConfigService} from '../../../../services/environment/environment-config.service';
1111

12+
import {deckSelector} from '../../../../utils/editor/deck.utils';
13+
1214
import {RemoteService} from '../../../../services/editor/remote/remote.service';
1315

1416
export class RemoteEventsHandler {
@@ -74,7 +76,7 @@ export class RemoteEventsHandler {
7476
});
7577
}
7678

77-
const deck: HTMLElement = this.el.querySelector('deckgo-deck');
79+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
7880

7981
if (deck) {
8082
deck.removeEventListener('slidesDidLoad', async ($event: CustomEvent) => {
@@ -171,7 +173,7 @@ export class RemoteEventsHandler {
171173
return;
172174
}
173175

174-
const deck = this.el.querySelector('deckgo-deck');
176+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
175177

176178
if (!deck) {
177179
resolve();
@@ -208,7 +210,7 @@ export class RemoteEventsHandler {
208210
return new Promise<void>(async (resolve) => {
209211
const deckgoRemoteElement = this.el.querySelector('deckgo-remote');
210212

211-
const deck = this.el.querySelector('deckgo-deck');
213+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
212214

213215
if (!deckgoRemoteElement || !deck) {
214216
resolve();
@@ -233,7 +235,7 @@ export class RemoteEventsHandler {
233235

234236
private youtubePlayPause($event) {
235237
return new Promise<void>(async (resolve) => {
236-
const deck = this.el.querySelector('deckgo-deck');
238+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
237239

238240
if (!deck) {
239241
resolve();
@@ -261,7 +263,7 @@ export class RemoteEventsHandler {
261263

262264
private initSlidesDidLoadListener() {
263265
return new Promise<void>(async (resolve) => {
264-
const deck: HTMLElement = this.el.querySelector('deckgo-deck');
266+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
265267

266268
if (!deck) {
267269
resolve();
@@ -294,7 +296,7 @@ export class RemoteEventsHandler {
294296

295297
private initDeckMove() {
296298
return new Promise<void>(async (resolve) => {
297-
const deck: HTMLElement = this.el.querySelector('deckgo-deck');
299+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
298300

299301
if (!deck) {
300302
resolve();
@@ -421,7 +423,7 @@ export class RemoteEventsHandler {
421423

422424
await deckgoRemoteElement.connect();
423425

424-
const deckElement: HTMLDeckgoDeckElement | null = this.el.querySelector('deckgo-deck');
426+
const deckElement: HTMLDeckgoDeckElement | null = document.querySelector(deckSelector);
425427

426428
if (!deckElement) {
427429
return;
@@ -452,7 +454,7 @@ export class RemoteEventsHandler {
452454

453455
private updateRemoteSlidesOnSlidesDidLoad($event: CustomEvent): Promise<void> {
454456
return new Promise<void>(async (resolve) => {
455-
const deck: HTMLElement = this.el.querySelector('deckgo-deck');
457+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
456458

457459
if (!deck || !$event || !$event.detail || !deck.hasChildNodes()) {
458460
resolve();
@@ -475,7 +477,7 @@ export class RemoteEventsHandler {
475477

476478
updateRemoteSlidesOnMutation(): Promise<void> {
477479
return new Promise<void>(async (resolve) => {
478-
const deck: HTMLElement = this.el.querySelector('deckgo-deck');
480+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
479481

480482
if (!deck || !deck.hasChildNodes()) {
481483
resolve();
@@ -496,7 +498,7 @@ export class RemoteEventsHandler {
496498

497499
private updateRemoteDeckWithDefinition(self): Promise<void> {
498500
return new Promise<void>(async (resolve) => {
499-
const deck: HTMLElement = self.el.querySelector('deckgo-deck');
501+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
500502

501503
if (!deck || !deck.hasChildNodes()) {
502504
resolve();
@@ -521,7 +523,7 @@ export class RemoteEventsHandler {
521523

522524
private updateCurrentSlideWithDefinition(self): Promise<void> {
523525
return new Promise<void>(async (resolve) => {
524-
const deck: HTMLElement = self.el.querySelector('deckgo-deck');
526+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
525527

526528
if (!deck || !deck.hasChildNodes()) {
527529
resolve();
@@ -545,7 +547,7 @@ export class RemoteEventsHandler {
545547
return;
546548
}
547549

548-
const deck: HTMLElement = self.el.querySelector('deckgo-deck');
550+
const deck: HTMLDeckgoDeckElement = document.querySelector(deckSelector);
549551

550552
if (!deck || !deck.hasChildNodes()) {
551553
resolve();

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

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ export class AppEditor {
334334
private async concatSlide(extraSlide: JSX.IntrinsicElements) {
335335
this.slides = [...this.slides, extraSlide];
336336

337-
await ParseDeckSlotsUtils.stickLastChildren(this.el);
337+
await ParseDeckSlotsUtils.stickLastChildren(this.mainRef);
338338
}
339339

340340
private async replaceSlide(slide: JSX.IntrinsicElements) {
@@ -683,34 +683,28 @@ export class AppEditor {
683683
await this.reorderSlides($event.detail);
684684
}
685685

686-
private reorderSlides(detail: ItemReorderEventDetail): Promise<void> {
687-
return new Promise<void>(async (resolve) => {
688-
if (!detail) {
689-
resolve();
690-
return;
691-
}
692-
693-
try {
694-
await this.deckEventsHandler.updateDeckSlidesOrder(detail);
686+
private async reorderSlides(detail: ItemReorderEventDetail) {
687+
if (!detail) {
688+
return;
689+
}
695690

696-
if (detail.from < 0 || detail.to < 0 || !this.slides || detail.to >= this.slides.length || detail.from === detail.to) {
697-
resolve();
698-
return;
699-
}
691+
if (detail.from < 0 || detail.to < 0 || !this.slides || detail.to >= this.slides.length || detail.from === detail.to) {
692+
return;
693+
}
700694

701-
await this.remoteEventsHandler.updateRemoteSlidesOnMutation();
695+
try {
696+
await this.deckEventsHandler.updateDeckSlidesOrder(detail);
702697

703-
this.slides.splice(detail.to, 0, ...this.slides.splice(detail.from, 1));
704-
this.slides = [...this.slides];
705-
} catch (err) {
706-
// We ignore the error here
707-
}
698+
await this.remoteEventsHandler.updateRemoteSlidesOnMutation();
708699

709-
// Finish the reorder and position the item in the DOM based on where the gesture ended. This method can also be called directly by the reorder group
710-
detail.complete();
700+
this.slides.splice(detail.to, 0, ...this.slides.splice(detail.from, 1));
701+
this.slides = [...this.slides];
702+
} catch (err) {
703+
// We ignore the error here
704+
}
711705

712-
resolve();
713-
});
706+
// Finish the reorder and position the item in the DOM based on where the gesture ended. This method can also be called directly by the reorder group
707+
detail.complete();
714708
}
715709

716710
private async updatePresenting(presenting: boolean) {

studio/src/app/popovers/editor/app-slide-navigate/app-slide-navigate.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export class AppSlideNavigate {
1818
@State()
1919
private slides: string[];
2020

21-
@Event() private reorder: EventEmitter<ItemReorderEventDetail>;
21+
@Event()
22+
private reorder: EventEmitter<ItemReorderEventDetail>;
2223

2324
async componentDidLoad() {
2425
history.pushState({modal: true}, null);

studio/src/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,6 +1692,7 @@ declare namespace LocalJSX {
16921692
}
16931693
interface AppSlidesAside {
16941694
"deckRef": HTMLDeckgoDeckElement;
1695+
"onReorder"?: (event: CustomEvent<ItemReorderEventDetail>) => void;
16951696
}
16961697
interface AppSlotType {
16971698
"onSelectType"?: (event: CustomEvent<SlotType | null>) => void;

0 commit comments

Comments
 (0)