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

Commit 471fac0

Browse files
feat: new popover to edit images and delete action for background images
1 parent b85535d commit 471fac0

File tree

8 files changed

+249
-7
lines changed

8 files changed

+249
-7
lines changed

studio/src/app/components/editor/app-editor-toolbar/app-editor-toolbar.tsx

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {SlotType} from '../../../utils/editor/create-slides.utils';
88
import {ToggleSlotUtils} from '../../../utils/editor/toggle-slot.utils';
99
import {PhotoHelper} from '../../../helpers/editor/photo.helper';
1010

11+
import {ImageAction} from '../../../popovers/editor/app-image/image-action';
12+
1113
import {BusyService} from '../../../services/editor/busy/busy.service';
1214

1315
@Component({
@@ -343,7 +345,7 @@ export class AppEditorToolbar {
343345
const windowWidth: number = window.innerWidth | screen.width;
344346

345347
const leftStandardPosition: number = left + offsetWidth - extraLeft;
346-
const leftPosition: number = leftStandardPosition + width > windowWidth ? windowWidth - width : leftStandardPosition;
348+
const leftPosition: number = leftStandardPosition + width > windowWidth ? windowWidth - width : leftStandardPosition;
347349

348350
// Set left position
349351
applyTo.style.left = '' + leftPosition + 'px';
@@ -637,7 +639,34 @@ export class AppEditorToolbar {
637639
await popover.present();
638640
}
639641

640-
private openPhotos = async () => {
642+
private async openBackground() {
643+
const popover: HTMLIonPopoverElement = await this.popoverController.create({
644+
component: 'app-image',
645+
componentProps: {
646+
deckOrSlide: this.deckOrSlide
647+
},
648+
mode: 'md',
649+
cssClass: 'popover-menu'
650+
});
651+
652+
popover.onWillDismiss().then(async (detail: OverlayEventDetail) => {
653+
if (detail.data) {
654+
if (detail.data.hasOwnProperty('applyToAllDeck')) {
655+
this.applyToAllDeck = detail.data.applyToAllDeck;
656+
}
657+
658+
if (detail.data.action === ImageAction.OPEN_PHOTOS) {
659+
await this.openPhotos();
660+
} else if (detail.data.action === ImageAction.DELETE_PHOTO) {
661+
await this.deleteBackgroundPhoto();
662+
}
663+
}
664+
});
665+
666+
await popover.present();
667+
}
668+
669+
private async openPhotos() {
641670
const modal: HTMLIonModalElement = await this.modalController.create({
642671
component: 'app-photo'
643672
});
@@ -650,7 +679,19 @@ export class AppEditorToolbar {
650679
});
651680

652681
await modal.present();
653-
};
682+
}
683+
684+
private deleteBackgroundPhoto(): Promise<void> {
685+
return new Promise<void>(async (resolve) => {
686+
if (!this.deckOrSlide) {
687+
resolve();
688+
return;
689+
}
690+
691+
const helper: PhotoHelper = new PhotoHelper(this.slideDidChange, this.deckDidChange);
692+
await helper.deleteBackground(this.selectedElement, this.applyToAllDeck);
693+
});
694+
}
654695

655696
render() {
656697
return [
@@ -683,7 +724,8 @@ export class AppEditorToolbar {
683724
};
684725

685726
return [
686-
<a onClick={(e: UIEvent) => this.openForDeckOrSlide(e, this.openBackgroundPicker)} title="Background">
727+
<a onClick={(e: UIEvent) => this.openForDeckOrSlide(e, this.openBackgroundPicker)}
728+
title="Background">
687729
<ion-label style={styleBackground}>Bg</ion-label>
688730
</a>,
689731
<a onClick={(e: UIEvent) => this.openForDeckOrSlide(e, this.openColorPicker)} title="Color">
@@ -713,8 +755,7 @@ export class AppEditorToolbar {
713755
}
714756

715757
private renderPhotos() {
716-
// TODO undefined if not div
717-
return <a onClick={(e: UIEvent) => this.openForDeckOrSlide(e, this.openPhotos)} title="Add a stock photo">
758+
return <a onClick={() => this.openBackground()} title="Background">
718759
<ion-icon name="images"></ion-icon>
719760
</a>
720761
}

studio/src/app/helpers/editor/photo.helper.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,49 @@ export class PhotoHelper {
3030
});
3131
}
3232

33+
deleteBackground(selectedElement: HTMLElement, applyToAllDeck: boolean): Promise<void> {
34+
return new Promise<void>(async (resolve) => {
35+
if (!selectedElement || !document) {
36+
resolve();
37+
return;
38+
}
39+
40+
const element: HTMLElement = applyToAllDeck ? selectedElement.parentElement : selectedElement;
41+
42+
if (!element) {
43+
resolve();
44+
return;
45+
}
46+
47+
const currentSlotElement: HTMLElement = element.querySelector(':scope > [slot=\'background\']');
48+
49+
if (currentSlotElement) {
50+
this.busyService.deckBusy(true);
51+
52+
if (applyToAllDeck) {
53+
element.removeChild(currentSlotElement);
54+
55+
this.deckDidChange.emit(selectedElement.parentElement);
56+
57+
await (element as any).loadBackground();
58+
} else if (selectedElement.hasAttribute('custom-background')) {
59+
element.removeChild(currentSlotElement);
60+
61+
selectedElement.removeAttribute('custom-background');
62+
63+
this.slideDidChange.emit(selectedElement);
64+
65+
// Refresh background, deck might have one defined which need to be replicated on the slide which
66+
if (element.parentElement) {
67+
await (element.parentElement as any).loadBackground();
68+
}
69+
}
70+
}
71+
72+
resolve();
73+
});
74+
}
75+
3376
private createImgElement(photo: UnsplashPhoto): HTMLElement {
3477
const img: HTMLElement = document.createElement('deckgo-lazy-img');
3578
(img as any).imgSrc = photo.urls.regular;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
app-image {
2+
h2 {
3+
color: var(--ion-color-primary);
4+
}
5+
6+
ion-list.list-ios {
7+
margin-bottom: 0;
8+
padding-top: 8px;
9+
padding-bottom: 8px;
10+
}
11+
12+
ion-item {
13+
--border-width: 0;
14+
--inner-border-width: 0;
15+
16+
&.action-button {
17+
--min-height: 42px;
18+
}
19+
}
20+
21+
ion-item-divider {
22+
--color: var(--ion-color-primary);
23+
--background: inherit;
24+
--ion-item-border-color: transparent;
25+
--ion-padding: 0;
26+
27+
ion-label {
28+
margin: 16px 0 4px;
29+
}
30+
}
31+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {Component, Element, Prop} from '@stencil/core';
2+
3+
import {ImageAction} from './image-action';
4+
5+
@Component({
6+
tag: 'app-image',
7+
styleUrl: 'app-image.scss'
8+
})
9+
export class AppImage {
10+
11+
@Element() el: HTMLElement;
12+
13+
@Prop()
14+
deckOrSlide: boolean = false;
15+
16+
private applyToAllDeck: boolean = false;
17+
18+
private async closePopover(action: ImageAction) {
19+
const data = {
20+
action: action
21+
};
22+
23+
if (this.deckOrSlide) {
24+
data['applyToAllDeck'] = this.applyToAllDeck;
25+
}
26+
27+
await (this.el.closest('ion-popover') as HTMLIonModalElement).dismiss(data);
28+
}
29+
30+
render() {
31+
return [<div class="ion-padding"><h2>{this.deckOrSlide ? 'Background' : 'Image'}</h2></div>,
32+
<ion-list>
33+
{this.renderDeckOrSlide()}
34+
35+
<ion-item class="ion-margin-top action-button">
36+
<ion-button shape="round" onClick={() => this.closePopover(ImageAction.OPEN_PHOTOS)} color="primary">
37+
<ion-label class="ion-text-uppercase">Add a stock photo</ion-label>
38+
</ion-button>
39+
</ion-item>
40+
41+
{this.renderDeleteAction()}
42+
</ion-list>
43+
];
44+
}
45+
46+
private selectApplyToAllDeck($event: CustomEvent) {
47+
if ($event && $event.detail) {
48+
this.applyToAllDeck = $event.detail.value;
49+
}
50+
}
51+
52+
private renderDeckOrSlide() {
53+
if (!this.deckOrSlide) {
54+
return undefined;
55+
} else{
56+
return [
57+
<ion-item-divider class="ion-padding-top"><ion-label>Apply change to</ion-label></ion-item-divider>,
58+
<ion-radio-group onIonChange={($event) => this.selectApplyToAllDeck($event)}>
59+
<ion-item>
60+
<ion-label>Just this slide</ion-label>
61+
<ion-radio slot="start" value={false} checked></ion-radio>
62+
</ion-item>
63+
<ion-item>
64+
<ion-label>The all deck</ion-label>
65+
<ion-radio slot="start" value={true}></ion-radio>
66+
</ion-item>
67+
</ion-radio-group>
68+
]
69+
}
70+
}
71+
72+
private renderDeleteAction() {
73+
if (!this.deckOrSlide) {
74+
return undefined;
75+
} else {
76+
return <ion-item class="action-button">
77+
<ion-button shape="round" onClick={() => this.closePopover(ImageAction.DELETE_PHOTO)} color="medium" fill="outline">
78+
<ion-label class="ion-text-uppercase">Delete background</ion-label>
79+
</ion-button>
80+
</ion-item>;
81+
}
82+
}
83+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum ImageAction {
2+
OPEN_PHOTOS,
3+
DELETE_PHOTO
4+
}

studio/src/components.d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@ export namespace Components {
204204
interface AppEditorActions {}
205205
interface AppEditorActionsAttributes extends StencilHTMLAttributes {}
206206

207+
interface AppImage {
208+
'deckOrSlide': boolean;
209+
}
210+
interface AppImageAttributes extends StencilHTMLAttributes {
211+
'deckOrSlide'?: boolean;
212+
}
213+
207214
interface AppSlideType {}
208215
interface AppSlideTypeAttributes extends StencilHTMLAttributes {}
209216

@@ -250,6 +257,7 @@ declare global {
250257
'AppCode': Components.AppCode;
251258
'AppDeckOrSlide': Components.AppDeckOrSlide;
252259
'AppEditorActions': Components.AppEditorActions;
260+
'AppImage': Components.AppImage;
253261
'AppSlideType': Components.AppSlideType;
254262
'AppSlotType': Components.AppSlotType;
255263
}
@@ -288,6 +296,7 @@ declare global {
288296
'app-code': Components.AppCodeAttributes;
289297
'app-deck-or-slide': Components.AppDeckOrSlideAttributes;
290298
'app-editor-actions': Components.AppEditorActionsAttributes;
299+
'app-image': Components.AppImageAttributes;
291300
'app-slide-type': Components.AppSlideTypeAttributes;
292301
'app-slot-type': Components.AppSlotTypeAttributes;
293302
}
@@ -491,6 +500,12 @@ declare global {
491500
new (): HTMLAppEditorActionsElement;
492501
};
493502

503+
interface HTMLAppImageElement extends Components.AppImage, HTMLStencilElement {}
504+
var HTMLAppImageElement: {
505+
prototype: HTMLAppImageElement;
506+
new (): HTMLAppImageElement;
507+
};
508+
494509
interface HTMLAppSlideTypeElement extends Components.AppSlideType, HTMLStencilElement {}
495510
var HTMLAppSlideTypeElement: {
496511
prototype: HTMLAppSlideTypeElement;
@@ -537,6 +552,7 @@ declare global {
537552
'app-code': HTMLAppCodeElement
538553
'app-deck-or-slide': HTMLAppDeckOrSlideElement
539554
'app-editor-actions': HTMLAppEditorActionsElement
555+
'app-image': HTMLAppImageElement
540556
'app-slide-type': HTMLAppSlideTypeElement
541557
'app-slot-type': HTMLAppSlotTypeElement
542558
}
@@ -575,6 +591,7 @@ declare global {
575591
'app-code': HTMLAppCodeElement;
576592
'app-deck-or-slide': HTMLAppDeckOrSlideElement;
577593
'app-editor-actions': HTMLAppEditorActionsElement;
594+
'app-image': HTMLAppImageElement;
578595
'app-slide-type': HTMLAppSlideTypeElement;
579596
'app-slot-type': HTMLAppSlotTypeElement;
580597
}

webcomponents/core/src/components/deck/deckdeckgo-deck/deckdeckgo-deck.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ export class DeckdeckgoDeck {
464464
return;
465465
}
466466

467-
// TODO remove previous background slots -> removeChildren on slides
467+
await DeckdeckgoDeckBackgroundUtils.removeSlots(filteredSlides, 'background');
468468

469469
await DeckdeckgoDeckBackgroundUtils.cloneAndLoadBackground(this.el, filteredSlides, this.cloneBackground);
470470

webcomponents/core/src/components/utils/deckdeckgo-deck-background-utils.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,27 @@ export class DeckdeckgoDeckBackgroundUtils {
110110
});
111111
}
112112

113+
static removeSlots(slides: HTMLElement[], slotName: string): Promise<void> {
114+
return new Promise<void>((resolve) => {
115+
if (!slides || slides.length <= 0) {
116+
resolve();
117+
return;
118+
}
119+
120+
slides.forEach((slide: Element) => {
121+
const custom: boolean = slide.hasAttribute('custom-' + slotName);
122+
123+
if (!custom) {
124+
const currentSlotElement: HTMLElement = slide.querySelector(':scope > [slot=\'' + slotName + '\']');
125+
126+
if (currentSlotElement) {
127+
slide.removeChild(currentSlotElement);
128+
}
129+
}
130+
});
131+
132+
resolve();
133+
});
134+
}
135+
113136
}

0 commit comments

Comments
 (0)