from github to this gitea
This commit is contained in:
488
Excalidraw/Scripts/Downloaded/Slideshow.md
Normal file
488
Excalidraw/Scripts/Downloaded/Slideshow.md
Normal file
@@ -0,0 +1,488 @@
|
||||
/*
|
||||

|
||||

|
||||
The script will convert your drawing into a slideshow presentation.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.8.17")) {
|
||||
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
|
||||
const EDIT_ZOOMOUT = 0.7; //70% of original slide zoom, set to a value between 1 and 0
|
||||
|
||||
//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
|
||||
let lineEl = ea.getViewElements().filter(el=>["line","arrow"].contains(el.type) && el.customData?.slideshow)[0];
|
||||
const selectedEl = ea.getViewSelectedElement();
|
||||
let preventHideAction = false;
|
||||
if(lineEl && selectedEl && ["line","arrow"].contains(selectedEl.type)) {
|
||||
api.setToast({
|
||||
message:"Using selected line instead of hidden line. Note that there is a hidden presentation path for this drawing. Run the slideshow script without selecting any elements to access the hidden presentation path",
|
||||
duration: 5000,
|
||||
closable: true
|
||||
})
|
||||
preventHideAction = true;
|
||||
lineEl = selectedEl;
|
||||
}
|
||||
if(!lineEl) lineEl = selectedEl;
|
||||
if(!lineEl || !["line","arrow"].contains(lineEl.type)) {
|
||||
api.setToast({
|
||||
message:"Please select the line or arrow for the presentation path",
|
||||
duration: 3000,
|
||||
closable: true
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
//goto fullscreen
|
||||
const gotoFullscreen = async () => {
|
||||
if(app.isMobile) {
|
||||
ea.viewToggleFullScreen(true);
|
||||
} else {
|
||||
await contentEl.webkitRequestFullscreen();
|
||||
await sleep(500);
|
||||
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 originalProps = lineEl.customData?.slideshow?.hidden
|
||||
? lineEl.customData.slideshow.originalProps
|
||||
: {
|
||||
strokeColor: lineEl.strokeColor,
|
||||
backgroundColor: lineEl.backgroundColor,
|
||||
locked: lineEl.locked,
|
||||
};
|
||||
let hidden = lineEl.customData?.slideshow?.hidden ?? false;
|
||||
|
||||
const hideArrow = async (setToHidden) => {
|
||||
ea.clear();
|
||||
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === lineEl.id));
|
||||
const el = ea.getElement(lineEl.id);
|
||||
el.strokeColor = "transparent";
|
||||
el.backgroundColor = "transparent";
|
||||
const customData = el.customData;
|
||||
if(setToHidden && !preventHideAction) {
|
||||
el.locked = true;
|
||||
el.customData = {
|
||||
...customData,
|
||||
slideshow: {
|
||||
originalProps,
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
hidden = true;
|
||||
} else {
|
||||
if(customData) delete el.customData.slideshow;
|
||||
hidden = false;
|
||||
}
|
||||
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<ratioY?ratioX:ratioY;
|
||||
if (ratio < 0.1) ratio = 0.1;
|
||||
if (ratio > 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<x2?x1:x2)-deltaX,
|
||||
top: (y1<y2?y1:y2)-deltaY,
|
||||
right: (x1<x2?x2:x1)+deltaX,
|
||||
bottom: (y1<y2?y2:y1)+deltaY,
|
||||
nextZoom: ratio
|
||||
};
|
||||
}
|
||||
|
||||
let busy = false;
|
||||
const scrollToNextRect = async ({left,top,right,bottom,nextZoom},steps = STEPCOUNT) => {
|
||||
let watchdog = 0;
|
||||
while(busy && watchdog++<15) await(100);
|
||||
if(busy && watchdog >= 15) return;
|
||||
busy = true;
|
||||
api.updateScene({appState:{shouldCacheIgnoreZoom:true}});
|
||||
const {scrollX, scrollY, zoom} = api.getAppState();
|
||||
const zoomStep = (zoom.value-nextZoom)/steps;
|
||||
const xStep = (left+scrollX)/steps;
|
||||
const yStep = (top+scrollY)/steps;
|
||||
for(i=1;i<=steps;i++) {
|
||||
api.updateScene({
|
||||
appState: {
|
||||
scrollX:scrollX-(xStep*i),
|
||||
scrollY:scrollY-(yStep*i),
|
||||
zoom:{value:zoom.value-zoomStep*i},
|
||||
}
|
||||
});
|
||||
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) {
|
||||
exitPresentation();
|
||||
return;
|
||||
}
|
||||
if(slideNumberEl) slideNumberEl.innerText = `${slide+1}/${slideCount+1}`;
|
||||
const nextRect = getSlideRect(nextSlide);
|
||||
await scrollToNextRect(nextRect);
|
||||
if(settingsModal) {
|
||||
slideNumberDropdown.setValue(`${slide}`.padStart(3,"0"));
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
// Settings Modal
|
||||
//--------------------------
|
||||
let settingsModal;
|
||||
let slideNumberDropdown;
|
||||
const presentationSettings = () => {
|
||||
let dirty = false;
|
||||
settingsModal = new ea.obsidian.Modal(app);
|
||||
|
||||
const getSlideNumberLabel = (i) => {
|
||||
switch(i) {
|
||||
case 0: return "1 - Start";
|
||||
case slideCount: return `${i+1} - End`;
|
||||
default: return `${i+1}`;
|
||||
}
|
||||
}
|
||||
|
||||
const getSlidesList = () => {
|
||||
const options = {};
|
||||
for(i=0;i<=slideCount;i++) {
|
||||
options[`${i}`.padStart(3,"0")] = getSlideNumberLabel(i);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
settingsModal.onOpen = () => {
|
||||
settingsModal.contentEl.createEl("h1",{text: "Slideshow Actions"});
|
||||
settingsModal.contentEl.createEl("p",{text: "To open this window double click presentation script icon or press ENTER during presentation."});
|
||||
new ea.obsidian.Setting(settingsModal.contentEl)
|
||||
.setName("Jump to slide")
|
||||
.addDropdown(dropdown => {
|
||||
slideNumberDropdown = dropdown;
|
||||
dropdown
|
||||
.addOptions(getSlidesList())
|
||||
.setValue(`${slide}`.padStart(3,"0"))
|
||||
.onChange(value => {
|
||||
slide = parseInt(value)-1;
|
||||
navigate("fwd");
|
||||
})
|
||||
})
|
||||
|
||||
if(!preventHideAction) {
|
||||
new ea.obsidian.Setting(settingsModal.contentEl)
|
||||
.setName("Hide navigation arrow after slideshow")
|
||||
.setDesc("Toggle on: arrow hidden, toggle off: arrow visible")
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(hidden)
|
||||
.onChange(value => hideArrow(value))
|
||||
)
|
||||
}
|
||||
|
||||
new ea.obsidian.Setting(settingsModal.contentEl)
|
||||
.setName("Edit current slide")
|
||||
.setDesc("Pressing 'e' during the presentation will open the current slide for editing.")
|
||||
.addButton(button => button
|
||||
.setButtonText("Edit")
|
||||
.onClick(async ()=>{
|
||||
await hideArrow(false);
|
||||
exitPresentation(true);
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
settingsModal.onClose = () => {
|
||||
setTimeout(()=>delete settingsModal);
|
||||
}
|
||||
|
||||
settingsModal.open();
|
||||
contentEl.appendChild(settingsModal.containerEl);
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
//Slideshow control
|
||||
//--------------------------------------
|
||||
let controlPanelEl;
|
||||
let slideNumberEl;
|
||||
const createNavigationPanel = () => {
|
||||
//create slideshow controlpanel container
|
||||
const top = contentEl.innerHeight;
|
||||
const left = contentEl.innerWidth;
|
||||
controlPanelEl = 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 = controlPanelEl.createDiv({
|
||||
cls: "panelColumn",
|
||||
});
|
||||
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;
|
||||
case "Enter":
|
||||
presentationSettings();
|
||||
break;
|
||||
case "End":
|
||||
slide = slideCount - 1;
|
||||
navigate("fwd");
|
||||
break;
|
||||
case "Home":
|
||||
slide = -1;
|
||||
navigate("fwd");
|
||||
break;
|
||||
case "e":
|
||||
(async ()=>{
|
||||
await hideArrow(false);
|
||||
exitPresentation(true);
|
||||
})()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//slideshow panel drag
|
||||
let pos1 = pos2 = pos3 = pos4 = 0;
|
||||
|
||||
const updatePosition = (deltaY = 0, deltaX = 0) => {
|
||||
const {
|
||||
offsetTop,
|
||||
offsetLeft,
|
||||
clientWidth: width,
|
||||
clientHeight: height,
|
||||
} = controlPanelEl;
|
||||
controlPanelEl.style.top = (offsetTop - deltaY) + 'px';
|
||||
controlPanelEl.style.left = (offsetLeft - deltaX) + 'px';
|
||||
}
|
||||
|
||||
const pointerUp = () => {
|
||||
win.removeEventListener('pointermove', onDrag, true);
|
||||
}
|
||||
|
||||
let dblClickTimer = 0;
|
||||
const pointerDown = (e) => {
|
||||
const now = Date.now();
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
win.addEventListener('pointermove', onDrag, true);
|
||||
if(now-dblClickTimer < 400) {
|
||||
presentationSettings();
|
||||
}
|
||||
dblClickTimer = now;
|
||||
}
|
||||
|
||||
const onDrag = (e) => {
|
||||
e.preventDefault();
|
||||
pos1 = pos3 - e.clientX;
|
||||
pos2 = pos4 - e.clientY;
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
updatePosition(pos2, pos1);
|
||||
}
|
||||
|
||||
const initializeEventListners = () => {
|
||||
doc.addEventListener('keydown',keydownListener);
|
||||
controlPanelEl.addEventListener('pointerdown', pointerDown, false);
|
||||
win.addEventListener('pointerup', pointerUp, false);
|
||||
|
||||
//event listners for terminating the presentation
|
||||
window.removePresentationEventHandlers = () => {
|
||||
ea.onLinkClickHook = null;
|
||||
controlPanelEl.parentElement?.removeChild(controlPanelEl);
|
||||
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;
|
||||
}
|
||||
|
||||
ea.onLinkClickHook = () => {
|
||||
exitPresentation();
|
||||
return true;
|
||||
};
|
||||
|
||||
if(!app.isMobile) {
|
||||
win.addEventListener('fullscreenchange', fullscreenListener);
|
||||
}
|
||||
}
|
||||
|
||||
const exitPresentation = async (openForEdit = false) => {
|
||||
if(openForEdit) ea.targetView.preventAutozoom();
|
||||
if(!app.isMobile) await doc.exitFullscreen();
|
||||
if(app.isMobile) {
|
||||
ea.viewToggleFullScreen(true);
|
||||
} else {
|
||||
ea.setViewModeEnabled(false);
|
||||
}
|
||||
if(settingsModal) settingsModal.close();
|
||||
ea.clear();
|
||||
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el=>el.id === lineEl.id));
|
||||
const el = ea.getElement(lineEl.id);
|
||||
if(!hidden) {
|
||||
el.strokeColor = originalProps.strokeColor;
|
||||
el.backgroundProps = originalProps.backgroundColor;
|
||||
el.locked = openForEdit ? false : originalProps.locked;
|
||||
}
|
||||
await ea.addElementsToView();
|
||||
ea.selectElementsInView([el]);
|
||||
if(openForEdit) {
|
||||
const nextSlide = getNextSlide(--slide);
|
||||
let nextRect = getSlideRect(nextSlide);
|
||||
const offsetW = (nextRect.right-nextRect.left)*(1-EDIT_ZOOMOUT)/2;
|
||||
const offsetH = (nextRect.bottom-nextRect.top)*(1-EDIT_ZOOMOUT)/2
|
||||
nextRect = {
|
||||
left: nextRect.left-offsetW,
|
||||
right: nextRect.right+offsetW,
|
||||
top: nextRect.top-offsetH,
|
||||
bottom: nextRect.bottom+offsetH,
|
||||
nextZoom: nextRect.nextZoom*EDIT_ZOOMOUT > 0.1 ? nextRect.nextZoom*EDIT_ZOOMOUT : 0.1 //0.1 is the minimu zoom value
|
||||
};
|
||||
await scrollToNextRect(nextRect,1);
|
||||
api.startLineEditor(
|
||||
ea.getViewSelectedElement(),
|
||||
[slide*2,slide*2+1]
|
||||
);
|
||||
}
|
||||
window.removePresentationEventHandlers?.();
|
||||
setTimeout(()=>{
|
||||
//Resets pointer offsets. Ugly solution.
|
||||
//During testing offsets were wrong after presentation, but don't know why.
|
||||
//This should solve it even if they are wrong.
|
||||
ea.targetView.refresh();
|
||||
})
|
||||
}
|
||||
|
||||
const fullscreenListener = (e) => {
|
||||
e.preventDefault();
|
||||
exitPresentation();
|
||||
}
|
||||
|
||||
|
||||
//--------------------------
|
||||
// Start presentation or open presentation settings on double click
|
||||
//--------------------------
|
||||
const start = async () => {
|
||||
await gotoFullscreen();
|
||||
await hideArrow(hidden);
|
||||
createNavigationPanel();
|
||||
initializeEventListners();
|
||||
//navigate to the first slide on start
|
||||
setTimeout(()=>navigate("fwd"));
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (timestamp - window.ExcalidrawSlideshow.timestamp <400) ) {
|
||||
if(window.ExcalidrawSlideshowStartTimer) {
|
||||
clearTimeout(window.ExcalidrawSlideshowStartTimer);
|
||||
delete window.ExcalidrawSlideshowStartTimer;
|
||||
}
|
||||
await start();
|
||||
presentationSettings();
|
||||
} else {
|
||||
window.ExcalidrawSlideshow = {
|
||||
script: utils.scriptFile.path,
|
||||
timestamp
|
||||
};
|
||||
window.ExcalidrawSlideshowStartTimer = setTimeout(start,500);
|
||||
}
|
Reference in New Issue
Block a user