diff --git a/ea-scripts/Slideshow.md b/ea-scripts/Slideshow.md new file mode 100644 index 0000000..b9409a7 --- /dev/null +++ b/ea-scripts/Slideshow.md @@ -0,0 +1,282 @@ +/* +![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-slideshow-1.jpg) +![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-slideshow-2.jpg) +The script will convert your drawing into a slideshow presentation. + +```javascript +*/ + +if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.2")) { + new Notice("This script requires a newer version of Excalidraw. Please install the latest version."); + return; +} + +//constants +const STEPCOUNT = 100; +const FRAME_SLEEP = 1; //milliseconds + +//utility & convenience functions +const doc = ea.targetView.ownerDocument; +const win = ea.targetView.ownerWindow; +const api = ea.getExcalidrawAPI(); +const contentEl = ea.targetView.contentEl; +const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +//clean up potential clutter from previous run +window.removePresentationEventHandlers?.(); + +//check if line or arrow is selected, if not inform the user and terminate presentation +const lineEl = ea.getViewSelectedElement(); +if(!lineEl || !["line","arrow"].contains(lineEl.type)) { + new Notice("Please select the line or arrow for the presentation path"); + return; +} + +//goto fullscreen +if(app.isMobile) { + ea.viewToggleFullScreen(true); +} else { + await contentEl.requestFullscreen(); + await sleep(50); + ea.setViewModeEnabled(true); +} +const deltaWidth = () => contentEl.clientWidth-api.getAppState().width; +let watchdog = 0; +while (deltaWidth()>50 && watchdog++<20) await sleep(100); //wait for Excalidraw to resize to fullscreen +contentEl.querySelector(".layer-ui__wrapper").addClass("excalidraw-hidden"); + +//hide the arrow and save the arrow color before doing so +const originalColor = { + strokeColor: lineEl.strokeColor, + backgroundColor: lineEl.backgroundColor +} +ea.copyViewElementsToEAforEditing([lineEl]); +ea.getElement(lineEl.id).strokeColor = "transparent"; +ea.getElement(lineEl.id).backgroundColor = "transparent"; +await ea.addElementsToView(); + +//---------------------------- +//scroll-to-location functions +//---------------------------- +let slide = -1; +const slideCount = Math.floor(lineEl.points.length/2)-1; + +const getNextSlide = (forward) => { + slide = forward + ? slide < slideCount ? slide + 1 : 0 + : slide <= 0 ? slideCount : slide - 1; + return {pointA:lineEl.points[slide*2], pointB:lineEl.points[slide*2+1]} +} + +const getSlideRect = ({pointA, pointB}) => { + const {width, height} = api.getAppState(); + const x1 = lineEl.x+pointA[0]; + const y1 = lineEl.y+pointA[1]; + const x2 = lineEl.x+pointB[0]; + const y2 = lineEl.y+pointB[1]; + const ratioX = width/Math.abs(x1-x2); + const ratioY = height/Math.abs(y1-y2); + let ratio = ratioX 10) ratio = 10; + const deltaX = (ratio===ratioY)?(width/ratio - Math.abs(x1-x2))/2:0; + const deltaY = (ratio===ratioX)?(height/ratio - Math.abs(y1-y2))/2:0; + return { + left: (x1 { + let watchdog = 0; + while(busy && watchdog++<15) await(100); + if(busy && watchdog >= 15) return; + busy = true; + const {scrollX, scrollY, zoom} = api.getAppState(); + const zoomStep = (zoom.value-nextZoom)/STEPCOUNT; + const xStep = (left+scrollX)/STEPCOUNT; + const yStep = (top+scrollY)/STEPCOUNT; + for(i=1;i<=STEPCOUNT;i++) { + api.updateScene({ + appState: { + scrollX:scrollX-(xStep*i), + scrollY:scrollY-(yStep*i), + zoom:{value:zoom.value-zoomStep*i}, + shouldCacheIgnoreZoom:true, + } + }); + await sleep(FRAME_SLEEP); + } + api.updateScene({appState:{shouldCacheIgnoreZoom:false}}); + busy = false; +} + +const navigate = async (dir) => { + const forward = dir === "fwd"; + const prevSlide = slide; + const nextSlide = getNextSlide(forward); + + //exit if user navigates from last slide forward or first slide backward + const shouldExit = forward + ? slide<=prevSlide + : slide>=prevSlide; + if(shouldExit) { + if(!app.isMobile) await doc.exitFullscreen(); + exitPresentation(); + return; + } + if(slideNumberEl) slideNumberEl.innerText = (slide+1).toString(); + const nextRect = getSlideRect(nextSlide); + await scrollToNextRect(nextRect); +} + +//-------------------------------------- +//Slideshow control +//-------------------------------------- +//create slideshow controlpanel container +const top = contentEl.innerHeight; +const left = contentEl.innerWidth; +const containerEl = contentEl.createDiv({ + cls: ["excalidraw","excalidraw-presentation-panel"], + attr: { + style: ` + width: calc(var(--default-button-size)*3); + z-index:5; + position: absolute; + top:calc(${top}px - var(--default-button-size)*2); + left:calc(${left}px - var(--default-button-size)*3.5);` + } +}); +const panelColumn = containerEl.createDiv({ + cls: "panelColumn", +}); +let slideNumberEl; +panelColumn.createDiv({ + cls: ["Island", "buttonList"], + attr: { + style: ` + height: calc(var(--default-button-size)*1.5); + width: 100%; + background: var(--island-bg-color);`, + } +}, el=>{ + el.createEl("button",{ + text: "<", + attr: { + style: ` + margin-top: calc(var(--default-button-size)*0.25); + margin-left: calc(var(--default-button-size)*0.25);` + } + }, button => button .onclick = () => navigate("bkwd")); + el.createEl("button",{ + text: ">", + attr: { + style: ` + margin-top: calc(var(--default-button-size)*0.25); + margin-right: calc(var(--default-button-size)*0.25);` + } + }, button => button.onclick = () => navigate("fwd")); + slideNumberEl = el.createEl("span",{ + text: "1", + cls: ["ToolIcon__keybinding"], + }) +}); + +//keyboard navigation +const keydownListener = (e) => { + e.preventDefault(); + switch(e.key) { + case "escape": + if(app.isMobile) exitPresentation(); + break; + case "ArrowRight": + case "ArrowDown": + navigate("fwd"); + break; + case "ArrowLeft": + case "ArrowUp": + navigate("bkwd"); + break; + } +} +doc.addEventListener('keydown',keydownListener); + +//slideshow panel drag +let pos1 = pos2 = pos3 = pos4 = 0; + +const updatePosition = (deltaY = 0, deltaX = 0) => { + const { + offsetTop, + offsetLeft, + clientWidth: width, + clientHeight: height, + } = containerEl; + containerEl.style.top = (offsetTop - deltaY) + 'px'; + containerEl.style.left = (offsetLeft - deltaX) + 'px'; +} + +const pointerUp = () => { + win.removeEventListener('pointermove', onDrag, true); +} + +const pointerDown = (e) => { + pos3 = e.clientX; + pos4 = e.clientY; + win.addEventListener('pointermove', onDrag, true); +} + +const onDrag = (e) => { + e.preventDefault(); + pos1 = pos3 - e.clientX; + pos2 = pos4 - e.clientY; + pos3 = e.clientX; + pos4 = e.clientY; + updatePosition(pos2, pos1); +} + +containerEl.addEventListener('pointerdown', pointerDown, false); +win.addEventListener('pointerup', pointerUp, false); + +//event listners for terminating the presentation +window.removePresentationEventHandlers = () => { + ea.onLinkClickHook = null; + containerEl.parentElement?.removeChild(containerEl); + if(!app.isMobile) win.removeEventListener('fullscreenchange', fullscreenListener); + doc.removeEventListener('keydown',keydownListener); + win.removeEventListener('pointerup',pointerUp); + contentEl.querySelector(".layer-ui__wrapper").removeClass("excalidraw-hidden"); + delete window.removePresentationEventHandlers; +} + +const exitPresentation = () => { + window.removePresentationEventHandlers?.(); + if(app.isMobile) ea.viewToggleFullScreen(true); + else ea.setViewModeEnabled(false); + ea.clear(); + ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === lineEl.id)); + ea.getElement(lineEl.id).strokeColor = originalColor.strokeColor; + ea.getElement(lineEl.id).backgroundColor = originalColor.backgroundColor; + ea.addElementsToView(); + ea.selectElementsInView(ea.getElements()); +} + +ea.onLinkClickHook = () => { + exitPresentation(); + return true; +}; + +const fullscreenListener = (e) => { + e.preventDefault(); + exitPresentation(); +} + +if(!app.isMobile) { + win.addEventListener('fullscreenchange', fullscreenListener); +} + +//navigate to the first slide on start +setTimeout(()=>navigate("fwd")); diff --git a/ea-scripts/Slideshow.svg b/ea-scripts/Slideshow.svg new file mode 100644 index 0000000..e83964b --- /dev/null +++ b/ea-scripts/Slideshow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ea-scripts/index-new.md b/ea-scripts/index-new.md index ee0110f..16f4ad4 100644 --- a/ea-scripts/index-new.md +++ b/ea-scripts/index-new.md @@ -72,6 +72,7 @@ I would love to include your contribution in the script library. If you have a s |
|[[#Set Link Alias]]| |
|[[#Set Stroke Width of Selected Elements]]| |
|[[#Set Text Alignment]]| +|
|[[#Slideshow]]| |
|[[#Split text by lines]]| |
|[[#Text Arch]]| |
|[[#Transfer TextElements to Excalidraw markdown metadata]]| @@ -347,6 +348,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea ```
Author@zsviczian
SourceFile on GitHub
DescriptionSets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.
+## Slideshow +```excalidraw-script-install +https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Slideshow.md +``` +
Author@zsviczian
SourceFile on GitHub
DescriptionThe script will convert your drawing into a slideshow presentation.

+ ## Split text by lines ```excalidraw-script-install https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20text%20by%20lines.md