From 6281e898098d6c7643be94f31a5451493aaa4c4d Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Wed, 18 Aug 2021 12:08:58 +0200 Subject: [PATCH 1/6] feat: slides thumbnails aside --- .../app-slides-aside/app-slides-aside.scss | 19 ++++ .../app-slides-aside/app-slides-aside.tsx | 73 +++++++++++++ .../app-slide-preview/app-slide-preview.scss | 24 ----- .../app-slide-preview/app-slide-preview.tsx | 41 ++----- .../app-slide-thumbnail.scss | 24 +++++ .../app-slide-thumbnail.tsx | 93 ++++++++++++++++ .../pages/editor/app-editor/app-editor.scss | 10 ++ .../pages/editor/app-editor/app-editor.tsx | 102 +++++++++--------- studio/src/components.d.ts | 32 ++++++ 9 files changed, 311 insertions(+), 107 deletions(-) create mode 100644 studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.scss create mode 100644 studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx create mode 100644 studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.scss create mode 100644 studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.tsx diff --git a/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.scss b/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.scss new file mode 100644 index 000000000..bbcc0a207 --- /dev/null +++ b/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.scss @@ -0,0 +1,19 @@ +app-slides-aside { + display: flex; + flex-direction: column; + + min-height: 100%; + height: 100%; + + width: var(--slides-aside-width); + + padding: 16px; + overflow: scroll; + border-right: 1px solid #dedede; + + --preview-width: calc(var(--slides-aside-width) - 32px); + + app-slide-thumbnail { + margin-bottom: 16px; + } +} diff --git a/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx b/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx new file mode 100644 index 000000000..c3dc86be6 --- /dev/null +++ b/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx @@ -0,0 +1,73 @@ +import {Component, Listen, h, Host, State, Prop} from '@stencil/core'; + +import {isSlide} from '../../../../../../../utils/deck/src'; +import {debounce} from '@deckdeckgo/utils'; + +@Component({ + tag: 'app-slides-aside', + styleUrl: 'app-slides-aside.scss' +}) +export class AppSlidesAside { + @State() + private slides: HTMLElement[] = []; + + @Prop() + deckRef!: HTMLDeckgoDeckElement; + + private readonly debounceUpdateAllSlides: () => void; + + private readonly debounceUpdateSlide: (updateSlide: HTMLElement) => void; + + constructor() { + this.debounceUpdateAllSlides = debounce(async () => { + await this.updateAllSlides(); + }, 750); + + this.debounceUpdateSlide = debounce(async (updateSlide: HTMLElement) => { + await this.updateSlide(updateSlide); + }, 750); + } + + @Listen('deckDidLoad', {target: 'document'}) + onDeckDidLoad() { + this.debounceUpdateAllSlides(); + } + + @Listen('deckDidChange', {target: 'document'}) + onDeckDidChange() { + this.debounceUpdateAllSlides(); + } + + @Listen('slideDidUpdate', {target: 'document'}) + onSlideDidUpdate({detail: updatedSlide}: CustomEvent) { + this.debounceUpdateSlide(updatedSlide); + } + + private async updateSlide(updatedSlide: HTMLElement) { + const slideIndex: number = Array.from(updatedSlide.parentNode.children).indexOf(updatedSlide); + + this.slides = [...this.slides.map((slide: HTMLElement, index: number) => (slideIndex === index ? (updatedSlide.cloneNode(true) as HTMLElement) : slide))]; + } + + private async updateAllSlides() { + const slides: NodeListOf = document.querySelectorAll('app-editor > ion-content div.deck > main > deckgo-deck > *'); + + if (!slides) { + return; + } + + this.slides = Array.from(slides) + .filter((slide: HTMLElement) => isSlide(slide)) + .map((slide: HTMLElement) => slide.cloneNode(true) as HTMLElement); + } + + render() { + return ( + + {this.slides.map((slide: HTMLElement) => ( + + ))} + + ); + } +} diff --git a/studio/src/app/components/editor/slide/app-slide-preview/app-slide-preview.scss b/studio/src/app/components/editor/slide/app-slide-preview/app-slide-preview.scss index 77ac915b1..81ce97334 100644 --- a/studio/src/app/components/editor/slide/app-slide-preview/app-slide-preview.scss +++ b/studio/src/app/components/editor/slide/app-slide-preview/app-slide-preview.scss @@ -1,6 +1,3 @@ -@use "../../../../../global/theme/mixins/editor"; -@use "../../../../../global/theme/mixins/deck"; - app-slide-preview { position: absolute; top: var(--ios-top, 8px); @@ -19,25 +16,4 @@ app-slide-preview { visibility: hidden; opacity: 0; } - - article { - position: relative; - - --preview-width: 200px; - - width: var(--preview-width); - height: calc(var(--preview-width) * 9 / 16); - - @media screen and (max-width: 768px) { - --preview-width: 128px; - } - - overflow: hidden; - - @include editor.panel; - - deckgo-deck { - @include deck.padding; - } - } } diff --git a/studio/src/app/components/editor/slide/app-slide-preview/app-slide-preview.tsx b/studio/src/app/components/editor/slide/app-slide-preview/app-slide-preview.tsx index 3248b3edd..246894bbc 100644 --- a/studio/src/app/components/editor/slide/app-slide-preview/app-slide-preview.tsx +++ b/studio/src/app/components/editor/slide/app-slide-preview/app-slide-preview.tsx @@ -1,6 +1,6 @@ import {Component, h, Host, Listen, State, Event, EventEmitter, Element, Prop} from '@stencil/core'; -import {cleanContent, isSlide} from '@deckdeckgo/deck-utils'; +import {isSlide} from '@deckdeckgo/deck-utils'; import {debounce, isIOS, isLandscape} from '@deckdeckgo/utils'; import {SlotUtils} from '../../../../utils/editor/slot.utils'; @@ -21,7 +21,8 @@ export class AppSlidePreview { @State() private iosPositionTop: string | undefined = undefined; - private deckPreviewRef!: HTMLDeckgoDeckElement; + @State() + private slideElement: HTMLElement | undefined = undefined; @Event({bubbles: false}) private previewAttached: EventEmitter; @@ -67,8 +68,6 @@ export class AppSlidePreview { this.preview = isSlide(selectedElement?.parentElement) && SlotUtils.isNodeEditable(selectedElement) && !SlotUtils.isNodeWordCloud(selectedElement); if (this.preview) { - await this.initDeckPreview(); - this.el.addEventListener('previewAttached', async () => await this.updateSlide(selectedElement.parentElement), {once: true}); this.deckRef.addEventListener('keypress', () => this.debounceUpdatePreview(), {passive: true}); @@ -85,28 +84,8 @@ export class AppSlidePreview { } } - async initDeckPreview() { - if (!this.deckRef) { - return; - } - - this.deckPreviewRef?.setAttribute('style', this.deckRef.style.cssText); - - await this.deckPreviewRef?.initSlideSize(); - } - - async updateSlide(slide: HTMLElement | undefined) { - if (!slide || !this.deckPreviewRef) { - return; - } - - const content: string = await cleanContent(slide.outerHTML); - - this.deckPreviewRef.innerHTML = content; - } - - private async blockSlide() { - await this.deckPreviewRef?.blockSlide(true); + private async updateSlide(slide: HTMLElement | undefined) { + this.slideElement = slide?.cloneNode(true) as HTMLElement | undefined; } private async updatePreview() { @@ -130,7 +109,7 @@ export class AppSlidePreview { class={{ preview: this.preview }}> -
{this.renderPreview()}
+ {this.renderPreview()} ); } @@ -140,12 +119,6 @@ export class AppSlidePreview { return undefined; } - return ( - (this.deckPreviewRef = el as HTMLDeckgoDeckElement)} - onSlidesDidLoad={() => this.blockSlide()}> - ); + return ; } } diff --git a/studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.scss b/studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.scss new file mode 100644 index 000000000..7aeecb32b --- /dev/null +++ b/studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.scss @@ -0,0 +1,24 @@ +@use "../../../../../global/theme/mixins/editor"; +@use "../../../../../global/theme/mixins/deck"; + +app-slide-thumbnail { + display: block; + + position: relative; + + width: var(--preview-width, 200px); + height: calc(var(--preview-width, 200px) * 9 / 16); + min-height: calc(var(--preview-width, 200px) * 9 / 16); + + @media screen and (max-width: 768px) { + --preview-width: 128px; + } + + overflow: hidden; + + @include editor.panel; + + deckgo-deck { + @include deck.padding; + } +} diff --git a/studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.tsx b/studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.tsx new file mode 100644 index 000000000..ed63d26b3 --- /dev/null +++ b/studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.tsx @@ -0,0 +1,93 @@ +import {Component, h, Host, Element, Prop} from '@stencil/core'; + +import {cleanContent} from '@deckdeckgo/deck-utils'; +import {debounce} from '@deckdeckgo/utils'; + +@Component({ + tag: 'app-slide-thumbnail', + styleUrl: 'app-slide-thumbnail.scss' +}) +export class AppSlideThumbnail { + @Element() el: HTMLElement; + + @Prop() + slide: HTMLElement; + + @Prop() + deck: HTMLDeckgoDeckElement; + + private deckPreviewRef!: HTMLDeckgoDeckElement; + + private readonly debounceUpdateThumbnail: () => void; + + constructor() { + this.debounceUpdateThumbnail = debounce(async () => { + this.updateDeckStyle(); + + await this.updateThumbnail(); + }, 150); + } + + componentDidUpdate() { + this.debounceUpdateThumbnail(); + } + + async componentDidLoad() { + await this.initDeckThumbnail(); + + this.debounceUpdateThumbnail(); + } + + private async initDeckThumbnail() { + if (!this.slide || !this.deck) { + return; + } + + this.updateDeckStyle(); + + await this.deckPreviewRef?.initSlideSize(); + } + + private updateDeckStyle() { + if (!this.deck) { + return; + } + + this.deckPreviewRef?.setAttribute('style', this.deck.style.cssText); + } + + private async updateThumbnail() { + if (!this.slide || !this.deckPreviewRef) { + return; + } + + const content: string = await cleanContent(this.slide.outerHTML); + + this.deckPreviewRef.innerHTML = content; + } + + private async blockSlide($event: CustomEvent) { + $event.stopPropagation(); + + await this.deckPreviewRef?.blockSlide(true); + } + + render() { + return {this.renderMiniature()}; + } + + private renderMiniature() { + if (!this.slide) { + return undefined; + } + + return ( + (this.deckPreviewRef = el as HTMLDeckgoDeckElement)} + onSlidesDidLoad={($event: CustomEvent) => this.blockSlide($event)} + onDeckDidLoad={($event: CustomEvent) => $event.stopPropagation()}> + ); + } +} diff --git a/studio/src/app/pages/editor/app-editor/app-editor.scss b/studio/src/app/pages/editor/app-editor/app-editor.scss index 99ae9ba52..09067a0ae 100644 --- a/studio/src/app/pages/editor/app-editor/app-editor.scss +++ b/studio/src/app/pages/editor/app-editor/app-editor.scss @@ -1,8 +1,18 @@ @use "../../../../global/theme/editor/editor-fullscreen"; ion-nav app-editor ion-content { + div.editor { + height: 100%; + width: 100%; + + display: flex; + + --slides-aside-width: 190px; + } + div.grid { height: 100%; + width: calc(100% - var(--slides-aside-width)); display: flex; flex-direction: column; 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 8319ad6e1..0a2d85333 100644 --- a/studio/src/app/pages/editor/app-editor/app-editor.tsx +++ b/studio/src/app/pages/editor/app-editor/app-editor.tsx @@ -749,56 +749,60 @@ export class AppEditor { return [ , this.selectDeck($event)}> -
-
(this.contentRef = el as HTMLElement)}> -
(this.mainRef = el as HTMLElement)} - class={busyStore.state.slideReady ? (this.presenting ? 'ready idle' : 'ready') : undefined} - style={{'--main-size-width': this.mainSize?.width, '--main-size-height': this.mainSize?.height}}> - {this.renderLoading()} - (this.deckRef = el as HTMLDeckgoDeckElement)} - embedded={true} - style={this.style} - reveal={this.fullscreen && this.presenting} - direction={this.direction} - directionMobile={this.directionMobile} - animation={this.animation} - autoSlide={this.fullscreen && this.presenting && autoSlide ? 'true' : 'false'} - onMouseDown={(e: MouseEvent) => this.deckTouched(e)} - onTouchStart={(e: TouchEvent) => this.deckTouched(e)} - onSlideNextDidChange={() => this.onSlideChange()} - onSlidePrevDidChange={() => this.onSlideChange()} - onSlideToChange={() => this.onSlideChange()}> - {this.slides} - {this.background} - {this.header} - {this.footer} - - - -
+
+ + +
+
(this.contentRef = el as HTMLElement)}> +
(this.mainRef = el as HTMLElement)} + class={busyStore.state.slideReady ? (this.presenting ? 'ready idle' : 'ready') : undefined} + style={{'--main-size-width': this.mainSize?.width, '--main-size-height': this.mainSize?.height}}> + {this.renderLoading()} + (this.deckRef = el as HTMLDeckgoDeckElement)} + embedded={true} + style={this.style} + reveal={this.fullscreen && this.presenting} + direction={this.direction} + directionMobile={this.directionMobile} + animation={this.animation} + autoSlide={this.fullscreen && this.presenting && autoSlide ? 'true' : 'false'} + onMouseDown={(e: MouseEvent) => this.deckTouched(e)} + onTouchStart={(e: TouchEvent) => this.deckTouched(e)} + onSlideNextDidChange={() => this.onSlideChange()} + onSlidePrevDidChange={() => this.onSlideChange()} + onSlideToChange={() => this.onSlideChange()}> + {this.slides} + {this.background} + {this.header} + {this.footer} + + + +
+
+ + (this.breadCrumbsRef = el as HTMLAppBreadcrumbsElement)} + slideNumber={this.activeIndex} + onStepTo={($event: CustomEvent) => this.selectStep($event)}> + + (this.actionsEditorRef = el as HTMLAppActionsEditorElement)} + hideActions={this.hideActions} + fullscreen={this.fullscreen} + slides={this.slides} + slideNumber={this.activeIndex} + onSignIn={() => this.signIn()} + onAddSlide={($event: CustomEvent) => this.addSlide($event)} + onAnimatePrevNextSlide={($event: CustomEvent) => this.animatePrevNextSlide($event)} + onSlideTo={($event: CustomEvent) => this.slideTo($event)} + onSlideCopy={($event: CustomEvent) => this.copySlide($event)} + onSlideTransform={($event: CustomEvent) => this.transformSlide($event)} + onElementFocus={($event: CustomEvent) => this.onElementFocus($event)} + onPresenting={($event: CustomEvent) => this.updatePresenting($event?.detail)}>
- - (this.breadCrumbsRef = el as HTMLAppBreadcrumbsElement)} - slideNumber={this.activeIndex} - onStepTo={($event: CustomEvent) => this.selectStep($event)}> - - (this.actionsEditorRef = el as HTMLAppActionsEditorElement)} - hideActions={this.hideActions} - fullscreen={this.fullscreen} - slides={this.slides} - slideNumber={this.activeIndex} - onSignIn={() => this.signIn()} - onAddSlide={($event: CustomEvent) => this.addSlide($event)} - onAnimatePrevNextSlide={($event: CustomEvent) => this.animatePrevNextSlide($event)} - onSlideTo={($event: CustomEvent) => this.slideTo($event)} - onSlideCopy={($event: CustomEvent) => this.copySlide($event)} - onSlideTransform={($event: CustomEvent) => this.transformSlide($event)} - onElementFocus={($event: CustomEvent) => this.onElementFocus($event)} - onPresenting={($event: CustomEvent) => this.updatePresenting($event?.detail)}>
{this.renderLaserPointer()} diff --git a/studio/src/components.d.ts b/studio/src/components.d.ts index b8994bb00..0eda4d1b1 100644 --- a/studio/src/components.d.ts +++ b/studio/src/components.d.ts @@ -352,12 +352,19 @@ export namespace Components { interface AppSlidePreview { "deckRef": HTMLDeckgoDeckElement; } + interface AppSlideThumbnail { + "deck": HTMLDeckgoDeckElement; + "slide": HTMLElement; + } interface AppSlideWarning { } interface AppSlideWarningInfo { "lowContrast": boolean; "overflow": boolean; } + interface AppSlidesAside { + "deckRef": HTMLDeckgoDeckElement; + } interface AppSlotType { "selectedElement": HTMLElement; "skip": boolean; @@ -1005,6 +1012,12 @@ declare global { prototype: HTMLAppSlidePreviewElement; new (): HTMLAppSlidePreviewElement; }; + interface HTMLAppSlideThumbnailElement extends Components.AppSlideThumbnail, HTMLStencilElement { + } + var HTMLAppSlideThumbnailElement: { + prototype: HTMLAppSlideThumbnailElement; + new (): HTMLAppSlideThumbnailElement; + }; interface HTMLAppSlideWarningElement extends Components.AppSlideWarning, HTMLStencilElement { } var HTMLAppSlideWarningElement: { @@ -1017,6 +1030,12 @@ declare global { prototype: HTMLAppSlideWarningInfoElement; new (): HTMLAppSlideWarningInfoElement; }; + interface HTMLAppSlidesAsideElement extends Components.AppSlidesAside, HTMLStencilElement { + } + var HTMLAppSlidesAsideElement: { + prototype: HTMLAppSlidesAsideElement; + new (): HTMLAppSlidesAsideElement; + }; interface HTMLAppSlotTypeElement extends Components.AppSlotType, HTMLStencilElement { } var HTMLAppSlotTypeElement: { @@ -1246,8 +1265,10 @@ declare global { "app-signin-page": HTMLAppSigninPageElement; "app-slide-navigate": HTMLAppSlideNavigateElement; "app-slide-preview": HTMLAppSlidePreviewElement; + "app-slide-thumbnail": HTMLAppSlideThumbnailElement; "app-slide-warning": HTMLAppSlideWarningElement; "app-slide-warning-info": HTMLAppSlideWarningInfoElement; + "app-slides-aside": HTMLAppSlidesAsideElement; "app-slot-type": HTMLAppSlotTypeElement; "app-spinner": HTMLAppSpinnerElement; "app-start-deck": HTMLAppStartDeckElement; @@ -1662,12 +1683,19 @@ declare namespace LocalJSX { "deckRef": HTMLDeckgoDeckElement; "onPreviewAttached"?: (event: CustomEvent) => void; } + interface AppSlideThumbnail { + "deck"?: HTMLDeckgoDeckElement; + "slide"?: HTMLElement; + } interface AppSlideWarning { } interface AppSlideWarningInfo { "lowContrast"?: boolean; "overflow"?: boolean; } + interface AppSlidesAside { + "deckRef": HTMLDeckgoDeckElement; + } interface AppSlotType { "onSelectType"?: (event: CustomEvent) => void; "selectedElement"?: HTMLElement; @@ -1846,8 +1874,10 @@ declare namespace LocalJSX { "app-signin-page": AppSigninPage; "app-slide-navigate": AppSlideNavigate; "app-slide-preview": AppSlidePreview; + "app-slide-thumbnail": AppSlideThumbnail; "app-slide-warning": AppSlideWarning; "app-slide-warning-info": AppSlideWarningInfo; + "app-slides-aside": AppSlidesAside; "app-slot-type": AppSlotType; "app-spinner": AppSpinner; "app-start-deck": AppStartDeck; @@ -1972,8 +2002,10 @@ declare module "@stencil/core" { "app-signin-page": LocalJSX.AppSigninPage & JSXBase.HTMLAttributes; "app-slide-navigate": LocalJSX.AppSlideNavigate & JSXBase.HTMLAttributes; "app-slide-preview": LocalJSX.AppSlidePreview & JSXBase.HTMLAttributes; + "app-slide-thumbnail": LocalJSX.AppSlideThumbnail & JSXBase.HTMLAttributes; "app-slide-warning": LocalJSX.AppSlideWarning & JSXBase.HTMLAttributes; "app-slide-warning-info": LocalJSX.AppSlideWarningInfo & JSXBase.HTMLAttributes; + "app-slides-aside": LocalJSX.AppSlidesAside & JSXBase.HTMLAttributes; "app-slot-type": LocalJSX.AppSlotType & JSXBase.HTMLAttributes; "app-spinner": LocalJSX.AppSpinner & JSXBase.HTMLAttributes; "app-start-deck": LocalJSX.AppStartDeck & JSXBase.HTMLAttributes; From feb66ab42fba6ce78256f6ed7fef1aa8300e6092 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Wed, 18 Aug 2021 13:08:24 +0200 Subject: [PATCH 2/6] feat: display thumbnails as of screen wider than 1200px --- .../app-slides-aside/app-slides-aside.tsx | 4 ++++ .../pages/editor/app-editor/app-editor.scss | 10 ++++++-- .../pages/editor/app-editor/app-editor.tsx | 23 +++++++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx b/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx index c3dc86be6..5082dfb79 100644 --- a/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx +++ b/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx @@ -28,6 +28,10 @@ export class AppSlidesAside { }, 750); } + componentDidLoad() { + this.debounceUpdateAllSlides(); + } + @Listen('deckDidLoad', {target: 'document'}) onDeckDidLoad() { this.debounceUpdateAllSlides(); diff --git a/studio/src/app/pages/editor/app-editor/app-editor.scss b/studio/src/app/pages/editor/app-editor/app-editor.scss index 09067a0ae..e0e1c8ab8 100644 --- a/studio/src/app/pages/editor/app-editor/app-editor.scss +++ b/studio/src/app/pages/editor/app-editor/app-editor.scss @@ -7,12 +7,14 @@ ion-nav app-editor ion-content { display: flex; - --slides-aside-width: 190px; + @media screen and (min-width: 1200px) { + --slides-aside-width: 190px; + } } div.grid { height: 100%; - width: calc(100% - var(--slides-aside-width)); + width: calc(100% - var(--slides-aside-width, 0px)); display: flex; flex-direction: column; @@ -28,6 +30,10 @@ ion-nav app-editor ion-content { left: 50%; transform: translate(-50%, -50%); + @media screen and (min-width: 1200px) { + left: calc(50% - 32px); + } + width: var(--main-size-width); height: var(--main-size-height); 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 0a2d85333..2d27e40a4 100644 --- a/studio/src/app/pages/editor/app-editor/app-editor.tsx +++ b/studio/src/app/pages/editor/app-editor/app-editor.tsx @@ -108,6 +108,9 @@ export class AppEditor { @State() private fullscreen: boolean = false; + @State() + private thumbnails: boolean = false; + @State() private mainSize: {width: string; height: string}; @@ -598,12 +601,14 @@ export class AppEditor { setTimeout(async () => { this.initMainSize(); await this.initSlideSize(); + this.initThumbnails(); }, 100); } private initMainSizeObserver() { this.mainResizeObserver = new ResizeObserver((_entries) => { this.initMainSize(); + this.initThumbnails(); }); if (this.contentRef) { @@ -615,6 +620,12 @@ export class AppEditor { } } + private initThumbnails() { + const wideScreen: MediaQueryList = window.matchMedia('(min-width: 1200px)'); + + this.thumbnails = !isFullscreen() && wideScreen.matches; + } + private initMainSize() { if (!this.contentRef || isFullscreen() || (isMobile() && !isIPad() && !isAndroidTablet())) { this.mainSize = { @@ -628,7 +639,7 @@ export class AppEditor { const wideScreen: MediaQueryList = window.matchMedia('(min-width: 1200px)'); - const width: number = this.contentRef.offsetWidth - (wideScreen.matches ? 192 : 32); + const width: number = this.contentRef.offsetWidth - (wideScreen.matches ? 164 : 32); const height: number = (width * 9) / 16; this.mainSize = @@ -750,7 +761,7 @@ export class AppEditor { , this.selectDeck($event)}>
- + {this.renderSlidesThumbnails()}
(this.contentRef = el as HTMLElement)}> @@ -840,4 +851,12 @@ export class AppEditor { return ; } } + + private renderSlidesThumbnails() { + if (!this.thumbnails) { + return undefined; + } + + return ; + } } From ddb587e08ae07b8c7c3a2224e5640922bdb3cf29 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Wed, 18 Aug 2021 13:27:28 +0200 Subject: [PATCH 3/6] feat: deck selector and fix list of slides in popover --- CHANGELOG.md | 4 ++++ studio/package-lock.json | 6 +++--- studio/package.json | 2 +- .../app-slides-aside/app-slides-aside.tsx | 6 ++++-- .../app-slide-navigate/app-slide-navigate.tsx | 6 +++--- .../editor/offline/offline.service.ts | 9 +++++---- studio/src/app/utils/editor/deck.utils.ts | 1 + utils/deck/CHANGELOG.md | 6 ++++++ utils/deck/package-lock.json | 2 +- utils/deck/package.json | 2 +- utils/deck/src/utils/slides.utils.ts | 20 ++++++++----------- 11 files changed, 37 insertions(+), 27 deletions(-) create mode 100644 studio/src/app/utils/editor/deck.utils.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 382d7b67c..194fb90a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - highlight-code: v3.3.1 ([CHANGELOG](https://github.com/deckgo/deckdeckgo/blob/master/webcomponents/highlight-code/CHANGELOG.md)) +### Others + +- - deck-utils: v4.1.0 ([CHANGELOG](https://github.com/deckgo/deckdeckgo/blob/master/utils/deck/CHANGELOG.md)) + # [4.8.0](https://github.com/deckgo/deckdeckgo/compare/v4.7.0...v4.8.0) (2021-06-13) diff --git a/studio/package-lock.json b/studio/package-lock.json index f5e1190a4..3bccbd79d 100644 --- a/studio/package-lock.json +++ b/studio/package-lock.json @@ -1158,9 +1158,9 @@ } }, "@deckdeckgo/deck-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@deckdeckgo/deck-utils/-/deck-utils-4.0.2.tgz", - "integrity": "sha512-ltw9LHMZxT+F4hrzxSZ5jUcro+36Fw/083sBDpzjv+yWAuPmXAmo0zPB9m9mzzn7tQrWwGQvfinmu2hF+qk8uQ==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@deckdeckgo/deck-utils/-/deck-utils-4.1.0.tgz", + "integrity": "sha512-NCJXOZPKy4mEvUBayq0KaCgGQSGwj1l3ZSmeqkPP3QGKTTQbDiYvkq0HU/grR2wv+xufz89+UdT07i6H0VfUGw==" }, "@deckdeckgo/demo": { "version": "2.1.0", diff --git a/studio/package.json b/studio/package.json index cabe7bcc6..40ef8f3ec 100644 --- a/studio/package.json +++ b/studio/package.json @@ -23,7 +23,7 @@ "@deckdeckgo/charts": "^2.1.0", "@deckdeckgo/color": "^4.1.0", "@deckdeckgo/core": "^8.2.1", - "@deckdeckgo/deck-utils": "^4.0.2", + "@deckdeckgo/deck-utils": "^4.1.0", "@deckdeckgo/demo": "^2.1.0", "@deckdeckgo/drag-resize-rotate": "^2.2.0", "@deckdeckgo/highlight-code": "^3.3.1", diff --git a/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx b/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx index 5082dfb79..9f177b097 100644 --- a/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx +++ b/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx @@ -1,8 +1,10 @@ import {Component, Listen, h, Host, State, Prop} from '@stencil/core'; -import {isSlide} from '../../../../../../../utils/deck/src'; import {debounce} from '@deckdeckgo/utils'; +import {isSlide} from '../../../../../../../utils/deck/src'; +import {deckSelector} from '../../../../utils/editor/deck.utils'; + @Component({ tag: 'app-slides-aside', styleUrl: 'app-slides-aside.scss' @@ -54,7 +56,7 @@ export class AppSlidesAside { } private async updateAllSlides() { - const slides: NodeListOf = document.querySelectorAll('app-editor > ion-content div.deck > main > deckgo-deck > *'); + const slides: NodeListOf = document.querySelectorAll(`${deckSelector} > *`); if (!slides) { return; diff --git a/studio/src/app/popovers/editor/app-slide-navigate/app-slide-navigate.tsx b/studio/src/app/popovers/editor/app-slide-navigate/app-slide-navigate.tsx index 48ac3aa2e..6d759a00b 100644 --- a/studio/src/app/popovers/editor/app-slide-navigate/app-slide-navigate.tsx +++ b/studio/src/app/popovers/editor/app-slide-navigate/app-slide-navigate.tsx @@ -6,6 +6,8 @@ import {findSlidesTitle} from '@deckdeckgo/deck-utils'; import i18n from '../../../stores/i18n.store'; +import {deckSelector} from '../../../utils/editor/deck.utils'; + @Component({ tag: 'app-slide-navigate', styleUrl: 'app-slide-navigate.scss' @@ -21,7 +23,7 @@ export class AppSlideNavigate { async componentDidLoad() { history.pushState({modal: true}, null); - this.slides = await findSlidesTitle(); + this.slides = await findSlidesTitle(deckSelector); } async jumpToSlide(index: number) { @@ -35,8 +37,6 @@ export class AppSlideNavigate { render() { return ( -

{i18n.state.editor.jump_or_change}

- ) => this.onReorder($event)} disabled={!this.slides || this.slides.length <= 1}> diff --git a/studio/src/app/services/editor/offline/offline.service.ts b/studio/src/app/services/editor/offline/offline.service.ts index fd98e9832..e385584b2 100644 --- a/studio/src/app/services/editor/offline/offline.service.ts +++ b/studio/src/app/services/editor/offline/offline.service.ts @@ -13,6 +13,7 @@ import {SlotType} from '../../../types/editor/slot-type'; import {FirestoreUtils} from '../../../utils/editor/firestore.utils'; import {ServiceWorkerUtils} from '../../../utils/core/service-worker.utils'; +import {deckSelector} from '../../../utils/editor/deck.utils'; import {SlideOnlineService} from '../../data/slide/slide.online.service'; import {DeckOnlineService} from '../../data/deck/deck.online.service'; @@ -146,7 +147,7 @@ export class OfflineService { private cacheServiceWorker(): Promise { return new Promise(async (resolve, reject) => { try { - const deckElement: HTMLElement = document.querySelector('app-editor > ion-content div.deck > main > deckgo-deck'); + const deckElement: HTMLElement = document.querySelector(deckSelector); if (!deckElement) { reject('No deck found'); @@ -301,7 +302,7 @@ export class OfflineService { private lazyLoadAllContent(): Promise { return new Promise(async (resolve, reject) => { try { - const deck = document.querySelector('app-editor > ion-content div.deck > main > deckgo-deck'); + const deck = document.querySelector(deckSelector); if (!deck) { reject('Deck not found'); @@ -461,7 +462,7 @@ export class OfflineService { private uploadSlideLocalUserAssets(deck: Deck, slideId: string): Promise { return new Promise(async (resolve, reject) => { - const slideElement: HTMLElement = document.querySelector(`app-editor > ion-content div.deck > main > deckgo-deck > *[slide_id="${slideId}"]`); + const slideElement: HTMLElement = document.querySelector(`${deckSelector} > *[slide_id="${slideId}"]`); if (!slideElement) { reject('No slide found'); @@ -747,7 +748,7 @@ export class OfflineService { private uploadDeckBackgroundAssets(deck: Deck): Promise { return new Promise(async (resolve, reject) => { - const backgroundElement: HTMLElement = document.querySelector(`app-editor > ion-content div.deck > main > deckgo-deck > *[slot="background"]`); + const backgroundElement: HTMLElement = document.querySelector(`${deckSelector} > *[slot="background"]`); if (!backgroundElement) { resolve(); diff --git a/studio/src/app/utils/editor/deck.utils.ts b/studio/src/app/utils/editor/deck.utils.ts new file mode 100644 index 000000000..6a18148f8 --- /dev/null +++ b/studio/src/app/utils/editor/deck.utils.ts @@ -0,0 +1 @@ +export const deckSelector: string = 'app-editor > ion-content div.deck > main > deckgo-deck'; diff --git a/utils/deck/CHANGELOG.md b/utils/deck/CHANGELOG.md index 8e894571b..5ae9d3ec5 100644 --- a/utils/deck/CHANGELOG.md +++ b/utils/deck/CHANGELOG.md @@ -1,3 +1,9 @@ +# 4.1.0 (2021-08-18) + +### Features + +- `findSlidesTitle` deck query selector can be specified + # 4.0.2 (2021-05-24) ### Fix diff --git a/utils/deck/package-lock.json b/utils/deck/package-lock.json index 52431abda..f5d3a0c73 100644 --- a/utils/deck/package-lock.json +++ b/utils/deck/package-lock.json @@ -1,6 +1,6 @@ { "name": "@deckdeckgo/deck-utils", - "version": "4.0.2", + "version": "4.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/utils/deck/package.json b/utils/deck/package.json index f7bbf9d49..2c5a6f090 100644 --- a/utils/deck/package.json +++ b/utils/deck/package.json @@ -1,6 +1,6 @@ { "name": "@deckdeckgo/deck-utils", - "version": "4.0.2", + "version": "4.1.0", "author": "David Dal Busco", "description": "Utils and styles for the DeckDeckGo applications", "license": "MIT", diff --git a/utils/deck/src/utils/slides.utils.ts b/utils/deck/src/utils/slides.utils.ts index f3bd1943f..2b1de8b01 100644 --- a/utils/deck/src/utils/slides.utils.ts +++ b/utils/deck/src/utils/slides.utils.ts @@ -1,39 +1,35 @@ import {DeckdeckgoSlideDefinition, DeckdeckgoAttributeDefinition} from '@deckdeckgo/types'; -export const findSlidesTitle = async (): Promise => { - const slides: NodeListOf = document.querySelectorAll('deckgo-deck > *'); +export const findSlidesTitle = async (deckSelector: string = 'deckgo-deck'): Promise => { + const slides: NodeListOf = document.querySelectorAll(`${deckSelector} > *`); if (!slides) { return []; } - const results: string[] = []; - - Array.from(slides) + return Array.from(slides) .filter((slide: HTMLElement) => isSlide(slide)) - .forEach((slide: HTMLElement, index: number) => { + .map((slide: HTMLElement, index: number) => { const title: HTMLElement | null = slide.querySelector('[slot="title"],[slot="question"]'); if (title && title.textContent && title.textContent !== '') { - results.push(title.textContent); + return title.textContent; } else { const start: HTMLElement | null = slide.querySelector('[slot="start"],[slot="header"]'); if (start && start.textContent && start.textContent !== '') { - results.push(start.textContent); + return start.textContent; } else { const end: HTMLElement | null = slide.querySelector('[slot="end"],[slot="footer"]'); if (end && end.textContent && end.textContent !== '') { - results.push(end.textContent); + return end.textContent; } else { - results.push(`Slide #${index}`); + return `Slide #${index}`; } } } }); - - return results; }; export function getSlideDefinition(slide: HTMLElement): Promise { From 3270f5ea9d0c159a80bb5300eebcaaf1a6a6e838 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Wed, 18 Aug 2021 13:47:20 +0200 Subject: [PATCH 4/6] fix: spacing in fullscreen without thumbnails --- studio/src/global/theme/editor/editor-fullscreen.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/studio/src/global/theme/editor/editor-fullscreen.scss b/studio/src/global/theme/editor/editor-fullscreen.scss index cbcd7744e..cd222fc84 100644 --- a/studio/src/global/theme/editor/editor-fullscreen.scss +++ b/studio/src/global/theme/editor/editor-fullscreen.scss @@ -91,9 +91,15 @@ --editor-toolbar-padding: 32px; } + ion-nav app-editor ion-content div.grid div.deck main { + left: 50% + } + ion-content div.grid { display: block; + --slides-aside-width: 0px; + > app-breadcrumbs { display: none; } From 80a714e31711948f1a274770b499880f88e7d085 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Wed, 18 Aug 2021 13:56:01 +0200 Subject: [PATCH 5/6] feat: navigate to slide from aside --- .../actions/app-slides-aside/app-slides-aside.tsx | 11 ++++++++--- .../deck/app-actions-deck/app-actions-deck.tsx | 11 +---------- .../app-actions-editor/app-actions-editor.tsx | 3 --- .../app-slide-thumbnail/app-slide-thumbnail.scss | 2 ++ .../src/app/pages/editor/app-editor/app-editor.tsx | 13 ------------- .../app-slide-navigate/app-slide-navigate.tsx | 6 ++++-- studio/src/app/utils/editor/deck.utils.ts | 5 +++++ studio/src/components.d.ts | 3 --- 8 files changed, 20 insertions(+), 34 deletions(-) diff --git a/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx b/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx index 9f177b097..e5907ad5b 100644 --- a/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx +++ b/studio/src/app/components/editor/actions/app-slides-aside/app-slides-aside.tsx @@ -3,7 +3,7 @@ import {Component, Listen, h, Host, State, Prop} from '@stencil/core'; import {debounce} from '@deckdeckgo/utils'; import {isSlide} from '../../../../../../../utils/deck/src'; -import {deckSelector} from '../../../../utils/editor/deck.utils'; +import {deckSelector, slideTo} from '../../../../utils/editor/deck.utils'; @Component({ tag: 'app-slides-aside', @@ -70,8 +70,13 @@ export class AppSlidesAside { render() { return ( - {this.slides.map((slide: HTMLElement) => ( - + {this.slides.map((slide: HTMLElement, index: number) => ( + await slideTo(index)} + key={slide.getAttribute('slide_id')} + slide={slide} + deck={this.deckRef}> ))} ); 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 fb64fb14e..27ef6a462 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 @@ -15,7 +15,7 @@ import {MoreAction} from '../../../../../types/editor/more-action'; import {BackupOfflineService} from '../../../../../services/editor/backup/backup.offline.service'; -import { AppIcon } from '../../../../core/app-icon/app-icon'; +import {AppIcon} from '../../../../core/app-icon/app-icon'; @Component({ tag: 'app-actions-deck', @@ -42,9 +42,6 @@ export class AppActionsDeck { @Prop() animatePrevNextSlide: EventEmitter; - @Prop() - slideTo: EventEmitter; - @Prop() toggleFullScreen: EventEmitter; @@ -83,12 +80,6 @@ export class AppActionsDeck { cssClass: 'popover-menu popover-menu-wide' }); - popover.onDidDismiss().then(async (detail: OverlayEventDetail) => { - if (detail.data >= 0) { - this.slideTo.emit(detail.data); - } - }); - await popover.present(); } diff --git a/studio/src/app/components/editor/actions/footer/app-actions-editor/app-actions-editor.tsx b/studio/src/app/components/editor/actions/footer/app-actions-editor/app-actions-editor.tsx index be974de21..b1c1ac146 100644 --- a/studio/src/app/components/editor/actions/footer/app-actions-editor/app-actions-editor.tsx +++ b/studio/src/app/components/editor/actions/footer/app-actions-editor/app-actions-editor.tsx @@ -35,8 +35,6 @@ export class AppActionsEditor { @Event() private animatePrevNextSlide: EventEmitter; - @Event() private slideTo: EventEmitter; - @Event() private toggleFullScreen: EventEmitter; @Event() private actionPublish: EventEmitter; @@ -214,7 +212,6 @@ export class AppActionsEditor { signIn={this.signIn} addSlide={this.addSlide} animatePrevNextSlide={this.animatePrevNextSlide} - slideTo={this.slideTo} toggleFullScreen={this.toggleFullScreen} actionPublish={this.actionPublish} deckDidChange={this.deckDidChange} diff --git a/studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.scss b/studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.scss index 7aeecb32b..cd1ba61d1 100644 --- a/studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.scss +++ b/studio/src/app/components/editor/slide/app-slide-thumbnail/app-slide-thumbnail.scss @@ -19,6 +19,8 @@ app-slide-thumbnail { @include editor.panel; deckgo-deck { + pointer-events: none; + @include deck.padding; } } 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 2d27e40a4..065cc3f5d 100644 --- a/studio/src/app/pages/editor/app-editor/app-editor.tsx +++ b/studio/src/app/pages/editor/app-editor/app-editor.tsx @@ -357,18 +357,6 @@ export class AppEditor { } } - private async slideTo($event: CustomEvent) { - if (!$event) { - return; - } - - if (!this.deckRef) { - return; - } - - await this.deckRef.slideTo($event.detail); - } - private async copySlide($event: CustomEvent) { if (!$event || !$event.detail) { return; @@ -808,7 +796,6 @@ export class AppEditor { onSignIn={() => this.signIn()} onAddSlide={($event: CustomEvent) => this.addSlide($event)} onAnimatePrevNextSlide={($event: CustomEvent) => this.animatePrevNextSlide($event)} - onSlideTo={($event: CustomEvent) => this.slideTo($event)} onSlideCopy={($event: CustomEvent) => this.copySlide($event)} onSlideTransform={($event: CustomEvent) => this.transformSlide($event)} onElementFocus={($event: CustomEvent) => this.onElementFocus($event)} diff --git a/studio/src/app/popovers/editor/app-slide-navigate/app-slide-navigate.tsx b/studio/src/app/popovers/editor/app-slide-navigate/app-slide-navigate.tsx index 6d759a00b..61f6538c3 100644 --- a/studio/src/app/popovers/editor/app-slide-navigate/app-slide-navigate.tsx +++ b/studio/src/app/popovers/editor/app-slide-navigate/app-slide-navigate.tsx @@ -6,7 +6,7 @@ import {findSlidesTitle} from '@deckdeckgo/deck-utils'; import i18n from '../../../stores/i18n.store'; -import {deckSelector} from '../../../utils/editor/deck.utils'; +import {deckSelector, slideTo} from '../../../utils/editor/deck.utils'; @Component({ tag: 'app-slide-navigate', @@ -27,7 +27,9 @@ export class AppSlideNavigate { } async jumpToSlide(index: number) { - await (this.el.closest('ion-popover') as HTMLIonPopoverElement).dismiss(index); + await slideTo(index); + + await (this.el.closest('ion-popover') as HTMLIonPopoverElement).dismiss(); } private onReorder($event: CustomEvent) { diff --git a/studio/src/app/utils/editor/deck.utils.ts b/studio/src/app/utils/editor/deck.utils.ts index 6a18148f8..648c9cd40 100644 --- a/studio/src/app/utils/editor/deck.utils.ts +++ b/studio/src/app/utils/editor/deck.utils.ts @@ -1 +1,6 @@ export const deckSelector: string = 'app-editor > ion-content div.deck > main > deckgo-deck'; + +export const slideTo = async (index: number) => { + const deck: HTMLDeckgoDeckElement | undefined = document.querySelector(deckSelector); + await deck.slideTo(index); +}; diff --git a/studio/src/components.d.ts b/studio/src/components.d.ts index 0eda4d1b1..4abb8762a 100644 --- a/studio/src/components.d.ts +++ b/studio/src/components.d.ts @@ -47,7 +47,6 @@ export namespace Components { "deckDidChange": EventEmitter; "fullscreen": boolean; "signIn": EventEmitter; - "slideTo": EventEmitter; "slides": JSX.IntrinsicElements[]; "toggleFullScreen": EventEmitter; } @@ -1321,7 +1320,6 @@ declare namespace LocalJSX { "fullscreen"?: boolean; "onSelectDeck"?: (event: CustomEvent) => void; "signIn"?: EventEmitter; - "slideTo"?: EventEmitter; "slides"?: JSX.IntrinsicElements[]; "toggleFullScreen"?: EventEmitter; } @@ -1337,7 +1335,6 @@ declare namespace LocalJSX { "onPresenting"?: (event: CustomEvent) => void; "onSignIn"?: (event: CustomEvent) => void; "onSlideCopy"?: (event: CustomEvent) => void; - "onSlideTo"?: (event: CustomEvent) => void; "onSlideTransform"?: (event: CustomEvent) => void; "onToggleFullScreen"?: (event: CustomEvent) => void; "slideNumber"?: number; From 15c7bbcf11fa861d785e069602682be962c92e80 Mon Sep 17 00:00:00 2001 From: peterpeterparker Date: Wed, 18 Aug 2021 13:58:54 +0200 Subject: [PATCH 6/6] feat: no slide preview if thumbnails are displayed --- studio/src/app/pages/editor/app-editor/app-editor.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 065cc3f5d..ed8dec39d 100644 --- a/studio/src/app/pages/editor/app-editor/app-editor.tsx +++ b/studio/src/app/pages/editor/app-editor/app-editor.tsx @@ -802,7 +802,7 @@ export class AppEditor { onPresenting={($event: CustomEvent) => this.updatePresenting($event?.detail)}>
- + {this.renderSlidePreview()} {this.renderLaserPointer()} , this.renderInlineEditor() @@ -846,4 +846,12 @@ export class AppEditor { return ; } + + private renderSlidePreview() { + if (this.thumbnails) { + return undefined; + } + + return ; + } }