from github to this gitea

This commit is contained in:
oscar.plaisant@icloud.com
2023-10-23 23:09:51 +02:00
commit a2ee0fa5ca
2898 changed files with 307871 additions and 0 deletions

View File

@@ -0,0 +1,399 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-download-raw.jpg)
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-auto-layout.png)
This script performs automatic layout for the selected top-level grouping objects. It is powered by [elkjs](https://github.com/kieler/elkjs) and needs to be connected to the Internet.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if (
!ea.verifyMinimumPluginVersion ||
!ea.verifyMinimumPluginVersion("1.5.21")
) {
new Notice(
"This script requires a newer version of Excalidraw. Please install the latest version."
);
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if (!settings["Layout Options JSON"]) {
settings = {
"Layout Options JSON": {
height: "450px",
value: `{\n "org.eclipse.elk.layered.crossingMinimization.semiInteractive": "true",\n "org.eclipse.elk.layered.considerModelOrder.components": "FORCE_MODEL_ORDER"\n}`,
description: `You can use layout options to configure the layout algorithm. A list of all options and further details of their exact effects is available in <a href="http://www.eclipse.org/elk/reference.html" rel="nofollow">ELK's documentation</a>.`,
},
};
ea.setScriptSettings(settings);
}
if (typeof ELK === "undefined") {
loadELK(doAutoLayout);
} else {
doAutoLayout();
}
async function doAutoLayout() {
const selectedElements = ea.getViewSelectedElements();
const groups = ea
.getMaximumGroups(selectedElements)
.map((g) => g.filter((el) => el.containerId == null)) // ignore text in stickynote
.filter((els) => els.length > 0);
const stickynotesMap = selectedElements
.filter((el) => el.containerId != null)
.reduce((result, el) => {
result.set(el.containerId, el);
return result;
}, new Map());
const elk = new ELK();
const knownLayoutAlgorithms = await elk.knownLayoutAlgorithms();
const layoutAlgorithms = knownLayoutAlgorithms
.map((knownLayoutAlgorithm) => ({
id: knownLayoutAlgorithm.id,
displayText:
knownLayoutAlgorithm.id === "org.eclipse.elk.layered" ||
knownLayoutAlgorithm.id === "org.eclipse.elk.radial" ||
knownLayoutAlgorithm.id === "org.eclipse.elk.mrtree"
? "* " +
knownLayoutAlgorithm.name +
": " +
knownLayoutAlgorithm.description
: knownLayoutAlgorithm.name + ": " + knownLayoutAlgorithm.description,
}))
.sort((lha, rha) => lha.displayText.localeCompare(rha.displayText));
const layoutAlgorithmsSimple = knownLayoutAlgorithms
.map((knownLayoutAlgorithm) => ({
id: knownLayoutAlgorithm.id,
displayText:
knownLayoutAlgorithm.id === "org.eclipse.elk.layered" ||
knownLayoutAlgorithm.id === "org.eclipse.elk.radial" ||
knownLayoutAlgorithm.id === "org.eclipse.elk.mrtree"
? "* " + knownLayoutAlgorithm.name
: knownLayoutAlgorithm.name,
}))
.sort((lha, rha) => lha.displayText.localeCompare(rha.displayText));
// const knownOptions = knownLayoutAlgorithms
// .reduce(
// (result, knownLayoutAlgorithm) => [
// ...result,
// ...knownLayoutAlgorithm.knownOptions,
// ],
// []
// )
// .filter((value, index, self) => self.indexOf(value) === index) // remove duplicates
// .sort((lha, rha) => lha.localeCompare(rha));
// console.log("knownOptions", knownOptions);
const selectedAlgorithm = await utils.suggester(
layoutAlgorithms.map((algorithmInfo) => algorithmInfo.displayText),
layoutAlgorithms.map((algorithmInfo) => algorithmInfo.id),
"Layout algorithm"
);
const knownNodePlacementStrategy = [
"SIMPLE",
"INTERACTIVE",
"LINEAR_SEGMENTS",
"BRANDES_KOEPF",
"NETWORK_SIMPLEX",
];
const knownDirections = [
"UNDEFINED",
"RIGHT",
"LEFT",
"DOWN",
"UP"
];
let nodePlacementStrategy = "BRANDES_KOEPF";
let componentComponentSpacing = "10";
let nodeNodeSpacing = "100";
let nodeNodeBetweenLayersSpacing = "100";
let discoComponentLayoutAlgorithm = "org.eclipse.elk.layered";
let direction = "UNDEFINED";
if (selectedAlgorithm === "org.eclipse.elk.layered") {
nodePlacementStrategy = await utils.suggester(
knownNodePlacementStrategy,
knownNodePlacementStrategy,
"Node placement strategy"
);
selectedDirection = await utils.suggester(
knownDirections,
knownDirections,
"Direction"
);
direction = selectedDirection??"UNDEFINED";
} else if (selectedAlgorithm === "org.eclipse.elk.disco") {
const componentLayoutAlgorithms = layoutAlgorithmsSimple.filter(al => al.id !== "org.eclipse.elk.disco");
const selectedDiscoComponentLayoutAlgorithm = await utils.suggester(
componentLayoutAlgorithms.map((algorithmInfo) => algorithmInfo.displayText),
componentLayoutAlgorithms.map((algorithmInfo) => algorithmInfo.id),
"Disco Connected Components Layout Algorithm"
);
discoComponentLayoutAlgorithm = selectedDiscoComponentLayoutAlgorithm??"org.eclipse.elk.layered";
}
if (
selectedAlgorithm === "org.eclipse.elk.box" ||
selectedAlgorithm === "org.eclipse.elk.rectpacking"
) {
nodeNodeSpacing = await utils.inputPrompt("Node Spacing", "number", "10");
} else {
let userSpacingStr = await utils.inputPrompt(
"Components Spacing, Node Spacing, Node Node Between Layers Spacing",
"number, number, number",
"10, 100, 100"
);
let userSpacingArr = (userSpacingStr??"").split(",");
componentComponentSpacing = userSpacingArr[0] ?? "10";
nodeNodeSpacing = userSpacingArr[1] ?? "100";
nodeNodeBetweenLayersSpacing = userSpacingArr[2] ?? "100";
}
let layoutOptionsJson = {};
try {
layoutOptionsJson = JSON.parse(settings["Layout Options JSON"].value);
} catch (e) {
new Notice(
"Error reading Layout Options JSON, see developer console for more information",
4000
);
console.log(e);
}
layoutOptionsJson["elk.algorithm"] = selectedAlgorithm;
layoutOptionsJson["org.eclipse.elk.spacing.componentComponent"] =
componentComponentSpacing;
layoutOptionsJson["org.eclipse.elk.spacing.nodeNode"] = nodeNodeSpacing;
layoutOptionsJson["org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers"] =
nodeNodeBetweenLayersSpacing;
layoutOptionsJson["org.eclipse.elk.layered.nodePlacement.strategy"] =
nodePlacementStrategy;
layoutOptionsJson["org.eclipse.elk.disco.componentCompaction.componentLayoutAlgorithm"] =
discoComponentLayoutAlgorithm;
layoutOptionsJson["org.eclipse.elk.direction"] = direction;
const graph = {
id: "root",
layoutOptions: layoutOptionsJson,
children: [],
edges: [],
};
let groupMap = new Map();
let targetElkMap = new Map();
let arrowEls = [];
for (let i = 0; i < groups.length; i++) {
const elements = groups[i];
if (
elements.length === 1 &&
(elements[0].type === "arrow" || elements[0].type === "line")
) {
if (
elements[0].type === "arrow" &&
elements[0].startBinding &&
elements[0].endBinding
) {
arrowEls.push(elements[0]);
}
} else {
let elkId = "g" + i;
elements.reduce((result, el) => {
result.set(el.id, elkId);
return result;
}, targetElkMap);
const box = ea.getBoundingBox(elements);
groupMap.set(elkId, {
elements: elements,
boundingBox: box,
});
graph.children.push({
id: elkId,
width: box.width,
height: box.height,
x: box.topX,
y: box.topY,
});
}
}
for (let i = 0; i < arrowEls.length; i++) {
const arrowEl = arrowEls[i];
const startElkId = targetElkMap.get(arrowEl.startBinding.elementId);
const endElkId = targetElkMap.get(arrowEl.endBinding.elementId);
graph.edges.push({
id: "e" + i,
sources: [startElkId],
targets: [endElkId],
});
}
const initTopX =
Math.min(...Array.from(groupMap.values()).map((v) => v.boundingBox.topX)) -
12;
const initTopY =
Math.min(...Array.from(groupMap.values()).map((v) => v.boundingBox.topY)) -
12;
elk
.layout(graph)
.then((resultGraph) => {
for (const elkEl of resultGraph.children) {
const group = groupMap.get(elkEl.id);
for (const groupEl of group.elements) {
const originalDistancX = groupEl.x - group.boundingBox.topX;
const originalDistancY = groupEl.y - group.boundingBox.topY;
const groupElDistanceX =
elkEl.x + initTopX + originalDistancX - groupEl.x;
const groupElDistanceY =
elkEl.y + initTopY + originalDistancY - groupEl.y;
groupEl.x = groupEl.x + groupElDistanceX;
groupEl.y = groupEl.y + groupElDistanceY;
if (stickynotesMap.has(groupEl.id)) {
const stickynote = stickynotesMap.get(groupEl.id);
stickynote.x = stickynote.x + groupElDistanceX;
stickynote.y = stickynote.y + groupElDistanceY;
}
}
}
ea.copyViewElementsToEAforEditing(selectedElements);
ea.addElementsToView(false, false);
normalizeSelectedArrows();
})
.catch(console.error);
}
function loadELK(doAfterLoaded) {
let script = document.createElement("script");
script.onload = function () {
if (typeof ELK !== "undefined") {
doAfterLoaded();
}
};
script.src =
"https://cdn.jsdelivr.net/npm/elkjs@0.8.2/lib/elk.bundled.min.js";
document.head.appendChild(script);
}
/*
* Normalize Selected Arrows
*/
function normalizeSelectedArrows() {
let gapValue = 2;
const selectedIndividualArrows = ea.getMaximumGroups(ea.getViewSelectedElements())
.reduce((result, g) => [...result, ...g.filter(el => el.type === 'arrow')], []);
const allElements = ea.getViewElements();
for (const arrow of selectedIndividualArrows) {
const startBindingEl = allElements.filter(
(el) => el.id === (arrow.startBinding || {}).elementId
)[0];
const endBindingEl = allElements.filter(
(el) => el.id === (arrow.endBinding || {}).elementId
)[0];
if (startBindingEl) {
recalculateStartPointOfLine(
arrow,
startBindingEl,
endBindingEl,
gapValue
);
}
if (endBindingEl) {
recalculateEndPointOfLine(arrow, endBindingEl, startBindingEl, gapValue);
}
}
ea.copyViewElementsToEAforEditing(selectedIndividualArrows);
ea.addElementsToView(false, false);
}
function recalculateStartPointOfLine(line, el, elB, gapValue) {
const aX = el.x + el.width / 2;
const bX =
line.points.length <= 2 && elB
? elB.x + elB.width / 2
: line.x + line.points[1][0];
const aY = el.y + el.height / 2;
const bY =
line.points.length <= 2 && elB
? elB.y + elB.height / 2
: line.y + line.points[1][1];
line.startBinding.gap = gapValue;
line.startBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.startBinding.gap
);
if (intersectA.length > 0) {
line.points[0] = [0, 0];
for (let i = 1; i < line.points.length; i++) {
line.points[i][0] -= intersectA[0][0] - line.x;
line.points[i][1] -= intersectA[0][1] - line.y;
}
line.x = intersectA[0][0];
line.y = intersectA[0][1];
}
}
function recalculateEndPointOfLine(line, el, elB, gapValue) {
const aX = el.x + el.width / 2;
const bX =
line.points.length <= 2 && elB
? elB.x + elB.width / 2
: line.x + line.points[line.points.length - 2][0];
const aY = el.y + el.height / 2;
const bY =
line.points.length <= 2 && elB
? elB.y + elB.height / 2
: line.y + line.points[line.points.length - 2][1];
line.endBinding.gap = gapValue;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if (intersectA.length > 0) {
line.points[line.points.length - 1] = [
intersectA[0][0] - line.x,
intersectA[0][1] - line.y,
];
}
}

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1670131481615" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3504" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M947.2 0H76.8C33.6 0 0 33.6 0 76.8v870.4C0 990.4 33.6 1024 76.8 1024h870.4c38.4 0 72-30.4 76.8-68.8V76.8C1024 33.6 990.4 0 947.2 0zM84.8 84.8h852.8V256H84.8V84.8z m256 256h596.8v256H340.8v-256z m-256 598.4V340.8H256v596.8H84.8z m256 0v-256h596.8v256H340.8z" p-id="3505"></path></svg>

After

Width:  |  Height:  |  Size: 616 B

View File

@@ -0,0 +1,55 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-box-elements.jpg)
This script will add an encapsulating box around the currently selected elements in Excalidraw.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Default padding"]) {
settings = {
"Prompt for padding?": true,
"Default padding" : {
value: 10,
description: "Padding between the bounding box of the selected elements, and the box the script creates"
}
};
ea.setScriptSettings(settings);
}
let padding = settings["Default padding"].value;
if(settings["Prompt for padding?"]) {
padding = parseInt (await utils.inputPrompt("padding?","number",padding.toString()));
}
if(isNaN(padding)) {
new Notice("The padding value provided is not a number");
return;
}
elements = ea.getViewSelectedElements();
const box = ea.getBoundingBox(elements);
color = ea
.getExcalidrawAPI()
.getAppState()
.currentItemStrokeColor;
//uncomment for random color:
//color = '#'+(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,"0");
ea.style.strokeColor = color;
id = ea.addRect(
box.topX - padding,
box.topY - padding,
box.width + 2*padding,
box.height + 2*padding
);
ea.copyViewElementsToEAforEditing(elements);
ea.addToGroup([id].concat(elements.map((el)=>el.id)));
ea.addElementsToView(false,false);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="100 80 500 400"><path d="M164.27 110.96h158.32v-28H136.27v170.87h28zM377.42 110.96h158.32v142.87h28V82.96H377.42zM535.73 449.04H377.41v28h186.32V306.17h-28zM164.27 306.16h-28v170.87h186.32v-28H164.27z"/><path d="M197.86 220.36v119.3c0 42.34 34.445 76.789 76.793 76.789h150.68c42.348 0 76.793-34.445 76.793-76.789l.004-119.3c0-42.34-34.445-76.789-76.793-76.789h-150.69c-42.348-.004-76.793 34.445-76.793 76.789zm276.27 0v119.3c0 26.902-21.887 48.789-48.793 48.789l-150.69-.004c-26.906 0-48.793-21.887-48.793-48.789v-119.29c0-26.902 21.887-48.789 48.793-48.789h150.68c26.91-.004 48.797 21.88 48.797 48.79z"/></svg>

After

Width:  |  Height:  |  Size: 660 B

View File

@@ -0,0 +1,81 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-connect-elements.jpg)
This script will connect two objects with an arrow. If either of the objects are a set of grouped elements (e.g. a text element grouped with an encapsulating rectangle), the script will identify these groups, and connect the arrow to the largest object in the group (assuming you want to connect the arrow to the box around the text element).
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Starting arrowhead"]) {
settings = {
"Starting arrowhead" : {
value: "none",
valueset: ["none","arrow","triangle","bar","dot"]
},
"Ending arrowhead" : {
value: "triangle",
valueset: ["none","arrow","triangle","bar","dot"]
},
"Line points" : {
value: 1,
description: "Number of line points between start and end"
}
};
ea.setScriptSettings(settings);
}
const arrowStart = settings["Starting arrowhead"].value === "none" ? null : settings["Starting arrowhead"].value;
const arrowEnd = settings["Ending arrowhead"].value === "none" ? null : settings["Ending arrowhead"].value;
const linePoints = Math.floor(settings["Line points"].value);
const elements = ea.getViewSelectedElements();
ea.copyViewElementsToEAforEditing(elements);
groups = ea.getMaximumGroups(elements);
if(groups.length !== 2) {
//unfortunately getMaxGroups returns duplicated resultset for sticky notes
//needs additional filtering
cleanGroups=[];
idList = [];
for (group of groups) {
keep = true;
for(item of group) if(idList.contains(item.id)) keep = false;
if(keep) {
cleanGroups.push(group);
idList = idList.concat(group.map(el=>el.id))
}
}
if(cleanGroups.length !== 2) return;
groups = cleanGroups;
}
els = [
ea.getLargestElement(groups[0]),
ea.getLargestElement(groups[1])
];
ea.style.strokeColor = els[0].strokeColor;
ea.style.strokeWidth = els[0].strokeWidth;
ea.style.strokeStyle = els[0].strokeStyle;
ea.style.strokeSharpness = els[0].strokeSharpness;
ea.connectObjects(
els[0].id,
null,
els[1].id,
null,
{
endArrowHead: arrowEnd,
startArrowHead: arrowStart,
numberOfPoints: linePoints
}
);
ea.addElementsToView(false,false,true);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="5 5 170 70" stroke="#000"><path fill="none" stroke-linecap="round" stroke-width="4" d="M10 10h60m-60 0h60m0 0v60m0-60v60m0 0H10m60 0H10m0 0V10m0 60V10"/><g stroke-linecap="round"><path fill="none" stroke-width="4" d="M70 40h40m-40 0h40"/><path fill-rule="evenodd" stroke-width="0" d="m110 40-13.59 6.34V33.66L110 40"/><path fill="none" stroke-width="4" d="M110 40c-4.72 2.2-9.43 4.4-13.59 6.34M110 40c-4.7 2.19-9.4 4.38-13.59 6.34m0 0V33.66m0 12.68V33.66m0 0c5.07 2.37 10.14 4.73 13.59 6.34m-13.59-6.34c3.25 1.52 6.51 3.04 13.59 6.34m0 0s0 0 0 0m0 0s0 0 0 0"/></g><path fill="none" stroke-linecap="round" stroke-width="4" d="M110 10h60m-60 0h60m0 0v60m0-60v60m0 0h-60m60 0h-60m0 0V10m0 60V10"/></svg>

After

Width:  |  Height:  |  Size: 753 B

View File

@@ -0,0 +1,70 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-textelement-to-transparent-stickynote.png)
Converts selected plain text elements to sticky notes with transparent background and transparent stroke color. Essentially converts text element into a wrappable format.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
let settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Border color"]) {
settings = {
"Border color" : {
value: "#000000",
description: "Any legal HTML color (#000000, rgb, color-name, etc.). Set to 'transparent' for transparent color."
},
"Background color" : {
value: "transparent",
description: "Background color of the sticky note. Set to 'transparent' for transparent color."
},
"Background fill style" : {
value: "solid",
description: "Fill style of the sticky note",
valueset: ["hachure","cross-hatch","solid"]
}
};
await ea.setScriptSettings(settings);
}
if(!settings["Max sticky note width"]) {
settings["Max sticky note width"] = {
value: "600",
description: "Maximum width of new sticky note. If text is longer, it will be wrapped",
valueset: ["400","600","800","1000","1200","1400","2000"]
}
await ea.setScriptSettings(settings);
}
const maxWidth = parseInt(settings["Max sticky note width"].value);
const strokeColor = settings["Border color"].value;
const backgroundColor = settings["Background color"].value;
const fillStyle = settings["Background fill style"].value;
const elements = ea
.getViewSelectedElements()
.filter((el)=>(el.type==="text")&&(el.containerId===null));
if(elements.length===0) {
new Notice("Please select a text element");
return;
}
ea.style.strokeColor = strokeColor;
ea.style.backgroundColor = backgroundColor;
ea.style.fillStyle = fillStyle;
const padding = 6;
const boxes = [];
ea.copyViewElementsToEAforEditing(elements);
ea.getElements().forEach((el)=>{
const width = el.width+2*padding;
const widthOK = width<=maxWidth;
const id = ea.addRect(el.x-padding,el.y-padding,widthOK?width:maxWidth,el.height+2*padding);
boxes.push(id);
ea.getElement(id).boundElements=[{type:"text",id:el.id}];
el.containerId = id;
});
await ea.addElementsToView(false,true);
const containers = ea.getViewElements().filter(el=>boxes.includes(el.id));
ea.getExcalidrawAPI().updateContainerSize(containers);
ea.selectElementsInView(containers);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 170 90" stroke="#000"><path fill="none" stroke-linecap="round" stroke-width="4" d="M100 10h40m-40 0h40m0 0 20 20m-20-20 20 20m0 0v50m0-50v50m0 0h-60m60 0h-60m0 0V10m0 70V10M140 10v20m0-20v20m0 0h20m-20 0h20M110 40c11.87.13 23.73.26 31 .33M110 40c6.44.07 12.89.14 31 .33M110 50c10.03.11 20.05.22 31 .33M110 50l31 .33M110 60c9.93.11 19.86.21 31 .33M110 60c11.38.12 22.75.24 31 .33M10 20c8.88.1 17.77.19 31 .33M10 20c6.49.07 12.98.14 31 .33M10 40c11.73.13 23.47.25 31 .33M10 40c10.52.11 21.04.23 31 .33M10 60c11.67.13 23.34.25 31 .33M10 60c6.23.07 12.46.13 31 .33"/><g fill-rule="evenodd" stroke-linecap="round"><path stroke-width="0" d="m80 20 9.33 19.67L80 60V50H60V30h20V20"/><path fill="none" stroke-width="4" d="M80 20c2.84 5.99 5.68 11.98 9.33 19.67M80 20c2.52 5.31 5.04 10.63 9.33 19.67m0 0C87.01 44.73 84.69 49.79 80 60m9.33-20.33C87.4 43.88 85.47 48.09 80 60m0 0V50m0 10V50m0 0H60m20 0H60m0 0V30m0 20V30m0 0h20m-20 0h20m0 0V20m0 10V20m0 0s0 0 0 0m0 0s0 0 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,93 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-download-raw.jpg)
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/elbow-connectors.png)
This script converts the selected connectors to elbows.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
const selectedCenterConnectPoints = await utils.suggester(
['Yes', 'No'],
[true, false],
"Center connect points?"
);
const centerConnectPoints = selectedCenterConnectPoints??false;
const allElements = ea.getViewElements();
const elements = ea.getViewSelectedElements();
const lines = elements.filter((el)=>el.type==="arrow" || el.type==="line");
for (const line of lines) {
if (line.points.length >= 3) {
if(centerConnectPoints) {
const startBindingEl = allElements.filter(el => el.id === (line.startBinding||{}).elementId)[0];
const endBindingEl = allElements.filter(el => el.id === (line.endBinding||{}).elementId)[0];
if(startBindingEl) {
const startPointX = line.x +line.points[0][0];
if(startPointX >= startBindingEl.x && startPointX <= startBindingEl.x + startBindingEl.width) {
line.points[0][0] = startBindingEl.x + startBindingEl.width / 2 - line.x;
}
const startPointY = line.y +line.points[0][1];
if(startPointY >= startBindingEl.y && startPointY <= startBindingEl.y + startBindingEl.height) {
line.points[0][1] = startBindingEl.y + startBindingEl.height / 2 - line.y;
}
}
if(endBindingEl) {
const startPointX = line.x +line.points[line.points.length-1][0];
if(startPointX >= endBindingEl.x && startPointX <= endBindingEl.x + endBindingEl.width) {
line.points[line.points.length-1][0] = endBindingEl.x + endBindingEl.width / 2 - line.x;
}
const startPointY = line.y +line.points[line.points.length-1][1];
if(startPointY >= endBindingEl.y && startPointY <= endBindingEl.y + endBindingEl.height) {
line.points[line.points.length-1][1] = endBindingEl.y + endBindingEl.height / 2 - line.y;
}
}
}
for (var i = 0; i < line.points.length - 2; i++) {
var p1;
var p3;
if (line.points[i][0] < line.points[i + 2][0]) {
p1 = line.points[i];
p3 = line.points[i+2];
} else {
p1 = line.points[i + 2];
p3 = line.points[i];
}
const p2 = line.points[i + 1];
if (p1[0] === p3[0]) {
continue;
}
const k = (p3[1] - p1[1]) / (p3[0] - p1[0]);
const b = p1[1] - k * p1[0];
y0 = k * p2[0] + b;
const up = p2[1] < y0;
if ((k > 0 && !up) || (k < 0 && up)) {
p2[0] = p1[0];
p2[1] = p3[1];
} else {
p2[0] = p3[0];
p2[1] = p1[1];
}
}
}
}
ea.copyViewElementsToEAforEditing(lines);
await ea.addElementsToView(false,false);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 120" stroke="#000"><path fill="none" stroke-linecap="round" stroke-width="8" d="M10 10v80m0-80v80m0 0h80m-80 0h80M70 70l20 20M70 70l20 20m0 0-20 20m20-20-20 20"/></svg>

After

Width:  |  Height:  |  Size: 226 B

View File

@@ -0,0 +1,69 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-download-raw.jpg)
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-fix-space-demo.png)
The script arranges the selected elements horizontally with a fixed spacing.
When we create an architecture diagram or mind map, we often need to arrange a large number of elements in a fixed spacing. `Fixed spacing` and `Fixed vertical Distance` scripts can save us a lot of time.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Default spacing"]) {
settings = {
"Prompt for spacing?": true,
"Default spacing" : {
value: 10,
description: "Fixed horizontal spacing between elements"
},
"Remember last spacing?": false
};
ea.setScriptSettings(settings);
}
let spacingStr = settings["Default spacing"].value.toString();
const rememberLastSpacing = settings["Remember last spacing?"];
if(settings["Prompt for spacing?"]) {
spacingStr = await utils.inputPrompt("spacing?","number",spacingStr);
}
const spacing = parseInt(spacingStr);
if(isNaN(spacing)) {
return;
}
if(rememberLastSpacing) {
settings["Default spacing"].value = spacing;
ea.setScriptSettings(settings);
}
const elements=ea.getViewSelectedElements();
const topGroups = ea.getMaximumGroups(elements)
.filter(els => !(els.length === 1 && els[0].type ==="arrow")) // ignore individual arrows
.filter(els => !(els.length === 1 && (els[0].containerId))); // ignore text in stickynote
const groups = topGroups.sort((lha,rha) => lha[0].x - rha[0].x);
for(var i=0; i<groups.length; i++) {
if(i > 0) {
const preGroup = groups[i-1];
const curGroup = groups[i];
const preRight = Math.max(...preGroup.map(el => el.x + el.width));
const curLeft = Math.min(...curGroup.map(el => el.x));
const distance = curLeft - preRight - spacing;
for(const curEl of curGroup) {
curEl.x = curEl.x - distance;
}
}
}
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false, false);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 465 290" stroke="#000"><path fill="none" stroke-linecap="round" stroke-width="4" d="M10 10h60m-60 0h60m0 0v60m0-60v60m0 0H10m60 0H10m0 0V10m0 60V10M50 150h60m-60 0h60m0 0v60m0-60v60m0 0H50m60 0H50m0 0v-60m0 60v-60M40 80h50m-50 0h50m0 0v60m0-60v60m0 0H40m50 0H40m0 0V80m0 60V80M70 220h30m-30 0h30m0 0v60m0-60v60m0 0H70m30 0H70m0 0v-60m0 60v-60M240 10h60m-60 0h60m0 0v60m0-60v60m0 0h-60m60 0h-60m0 0V10m0 60V10M360 150h60m-60 0h60m0 0v60m0-60v60m0 0h-60m60 0h-60m0 0v-60m0 60v-60M305 80h50m-50 0h50m0 0v60m0-60v60m0 0h-50m50 0h-50m0 0V80m0 60V80M425 220h30m-30 0h30m0 0v60m0-60v60m0 0h-30m30 0h-30m0 0v-60m0 60v-60"/><g fill-rule="evenodd" stroke-linecap="round"><path stroke-width="0" d="m130 10-.58 269.33 81.66-130L130 10"/><path fill="none" stroke-width="4" d="M130 10c-.2 94.2-.41 188.39-.58 269.33M130 10c-.18 81.45-.35 162.91-.58 269.33m0 0c28.12-44.76 56.24-89.53 81.66-130m-81.66 130c19.55-31.13 39.11-62.26 81.66-130m0 0C189.8 112.77 168.53 76.2 130 10m81.08 139.33C180.58 96.91 150.07 44.49 130 10m0 0s0 0 0 0m0 0s0 0 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,53 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-invert-colors.jpg)
The script inverts the colors on the canvas including the color palette in Element Properties.
```javascript
*/
const defaultColorPalette = { // https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.8
elementStroke:["#000000","#343a40","#495057","#c92a2a","#a61e4d","#862e9c","#5f3dc4","#364fc7","#1864ab","#0b7285","#087f5b","#2b8a3e","#5c940d","#e67700","#d9480f"],
elementBackground:["transparent","#ced4da","#868e96","#fa5252","#e64980","#be4bdb","#7950f2","#4c6ef5","#228be6","#15aabf","#12b886","#40c057","#82c91e","#fab005","#fd7e14"],
canvasBackground:["#ffffff","#f8f9fa","#f1f3f5","#fff5f5","#fff0f6","#f8f0fc","#f3f0ff","#edf2ff","#e7f5ff","#e3fafc","#e6fcf5","#ebfbee","#f4fce3","#fff9db","#fff4e6"]
};
const api = ea.getExcalidrawAPI();
const st = api.getAppState();
let colorPalette = st.colorPalette ?? defaultColorPalette;
if (Object.entries(colorPalette).length === 0) colorPalette = defaultColorPalette;
if(!colorPalette.elementStroke || Object.entries(colorPalette.elementStroke).length === 0) colorPalette.elementStroke = defaultColorPalette.elementStroke;
if(!colorPalette.elementBackground || Object.entries(colorPalette.elementBackground).length === 0) colorPalette.elementBackground = defaultColorPalette.elementBackground;
if(!colorPalette.canvasBackground || Object.entries(colorPalette.canvasBackground).length === 0) colorPalette.canvasBackground = defaultColorPalette.canvasBackground;
const invertColor = (color) => {
if(color.toLowerCase()==="transparent") return color;
const cm = ea.getCM(color);
const lightness = cm.lightness;
cm.lightnessTo(Math.abs(lightness-100));
switch (cm.format) {
case "hsl": return cm.stringHSL();
case "rgb": return cm.stringRGB();
case "hsv": return cm.stringHSV();
default: return cm.stringHEX({alpha: false});
}
}
const invertPaletteColors = (palette) => Object.keys(palette).forEach(key => palette[key] = invertColor(palette[key]));
Object.keys(colorPalette).forEach(key => invertPaletteColors(colorPalette[key]));
ea.copyViewElementsToEAforEditing(ea.getViewElements());
ea.getElements().forEach(el=>{
el.strokeColor = invertColor(el.strokeColor);
el.backgroundColor = invertColor(el.backgroundColor);
});
ea.viewUpdateScene({
appState:{
colorPalette,
viewBackgroundColor: invertColor(st.viewBackgroundColor),
currentItemStrokeColor: invertColor(st.currentItemStrokeColor),
currentItemBackgroundColor: invertColor(st.currentItemBackgroundColor)
},
elements: ea.getElements()
});

View File

@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke-width="2" fill="none" d="M12 16a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"></path>
<path stroke-width="2" fill="none" d="M12 8a2.828 2.828 0 1 0 4 4"></path>
<path stroke-width="2" fill="none" d="M12 2v2"></path>
<path stroke-width="2" fill="none" d="M12 20v2"></path>
<path stroke-width="2" fill="none" d="m4.93 4.93 1.41 1.41"></path>
<path stroke-width="2" fill="none" d="m17.66 17.66 1.41 1.41"></path>
<path stroke-width="2" fill="none" d="M2 12h2"></path>
<path stroke-width="2" fill="none" d="M20 12h2"></path>
<path stroke-width="2" fill="none" d="m6.34 17.66-1.41 1.41"></path>
<path stroke-width="2" fill="none" d="m19.07 4.93-1.41 1.41"></path>
</svg>

After

Width:  |  Height:  |  Size: 865 B

View File

@@ -0,0 +1,370 @@
/*
format **the left to right** mind map
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-mindmap-format-1.png)
# tree
Mind map is actually a tree, so you must have a **root node**. The script will determine **the leftmost element** of the selected element as the root element (node is excalidraw element, e.g. rectangle, diamond, ellipse, text, image, but it can't be arrow, line, freedraw, **group**)
The element connecting node and node must be an **arrow** and have the correct direction, e.g. **parent node -> children node**
# sort
The order of nodes in the Y axis or vertical direction is determined by **the creation time** of the arrow connecting it
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-mindmap-format-2.png)
So if you want to readjust the order, you can **delete arrows and reconnect them**
# setting
Script provides options to adjust the style of mind map, The option is at the bottom of the option of the exalidraw plugin(e.g. Settings -> Community plugins -> Excalidraw -> drag to bottom)
# problem
1. since the start bingding and end bingding of the arrow are easily disconnected from the node, so if there are unformatted parts, please **check the connection** and use the script to **reformat**
```javascript
*/
let settings = ea.getScriptSettings();
//set default values on first run
if (!settings["MindMap Format"]) {
settings = {
"MindMap Format": {
value: "Excalidraw/MindMap Format",
description:
"This is prepared for the namespace of MindMap Format and does not need to be modified",
},
"default gap": {
value: 10,
description: "Interval size of element",
},
"curve length": {
value: 40,
description: "The length of the curve part in the mind map line",
},
"length between element and line": {
value: 50,
description:
"The distance between the tail of the connection and the connecting elements of the mind map",
},
};
ea.setScriptSettings(settings);
}
const sceneElements = ea.getExcalidrawAPI().getSceneElements();
// default X coordinate of the middle point of the arc
const defaultDotX = Number(settings["curve length"].value);
// The default length from the middle point of the arc on the X axis
const defaultLengthWithCenterDot = Number(
settings["length between element and line"].value
);
// Initial trimming distance of the end point on the Y axis
const initAdjLength = 4;
// default gap
const defaultGap = Number(settings["default gap"].value);
const setCenter = (parent, line) => {
// Focus and gap need the api calculation of excalidraw
// e.g. determineFocusDistance, but they are not available now
// so they are uniformly set to 0/1
line.startBinding.focus = 0;
line.startBinding.gap = 1;
line.endBinding.focus = 0;
line.endBinding.gap = 1;
line.x = parent.x + parent.width;
line.y = parent.y + parent.height / 2;
};
/**
* set the middle point of curve
* @param {any} lineEl the line element of excalidraw
* @param {number} height height of dot on Y axis
* @param {number} [ratio=1] coefficient of the initial trimming distance of the end point on the Y axis, default is 1
*/
const setTopCurveDotOnLine = (lineEl, height, ratio = 1) => {
if (lineEl.points.length < 3) {
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1] - height]);
} else if (lineEl.points.length === 3) {
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] - height];
} else {
lineEl.points.splice(2, lineEl.points.length - 3);
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] - height];
}
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
// adjust the curvature of the second line segment
lineEl.points[2][1] = lineEl.points[1][1] - initAdjLength * ratio * 0.8;
};
const setMidCurveDotOnLine = (lineEl) => {
if (lineEl.points.length < 3) {
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1]]);
} else if (lineEl.points.length === 3) {
lineEl.points[1] = [defaultDotX, lineEl.points[0][1]];
} else {
lineEl.points.splice(2, lineEl.points.length - 3);
lineEl.points[1] = [defaultDotX, lineEl.points[0][1]];
}
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
lineEl.points[2][1] = lineEl.points[1][1];
};
/**
* set the middle point of curve
* @param {any} lineEl the line element of excalidraw
* @param {number} height height of dot on Y axis
* @param {number} [ratio=1] coefficient of the initial trimming distance of the end point on the Y axis, default is 1
*/
const setBottomCurveDotOnLine = (lineEl, height, ratio = 1) => {
if (lineEl.points.length < 3) {
lineEl.points.splice(1, 0, [defaultDotX, lineEl.points[0][1] + height]);
} else if (lineEl.points.length === 3) {
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] + height];
} else {
lineEl.points.splice(2, lineEl.points.length - 3);
lineEl.points[1] = [defaultDotX, lineEl.points[0][1] + height];
}
lineEl.points[2][0] = lineEl.points[1][0] + defaultLengthWithCenterDot;
// adjust the curvature of the second line segment
lineEl.points[2][1] = lineEl.points[1][1] + initAdjLength * ratio * 0.8;
};
const setTextXY = (rect, text) => {
text.x = rect.x + (rect.width - text.width) / 2;
text.y = rect.y + (rect.height - text.height) / 2;
};
const setChildrenXY = (parent, children, line, elementsMap) => {
x = parent.x + parent.width + line.points[2][0];
y = parent.y + parent.height / 2 + line.points[2][1] - children.height / 2;
distX = children.x - x;
distY = children.y - y;
ea.getElementsInTheSameGroupWithElement(children, sceneElements).forEach((el) => {
el.x = el.x - distX;
el.y = el.y - distY;
});
if (
["rectangle", "diamond", "ellipse"].includes(children.type) &&
![null, undefined].includes(children.boundElements)
) {
const textDesc = children.boundElements.filter(
(el) => el.type === "text"
)[0];
if (textDesc !== undefined) {
const textEl = elementsMap.get(textDesc.id);
setTextXY(children, textEl);
}
}
};
/**
* returns the height of the upper part of all child nodes
* and the height of the lower part of all child nodes
* @param {Number[]} childrenTotalHeightArr
* @returns {Number[]} [topHeight, bottomHeight]
*/
const getNodeCurrentHeight = (childrenTotalHeightArr) => {
if (childrenTotalHeightArr.length <= 0) return [0, 0];
else if (childrenTotalHeightArr.length === 1)
return [childrenTotalHeightArr[0] / 2, childrenTotalHeightArr[0] / 2];
const heightArr = childrenTotalHeightArr;
let topHeight = 0,
bottomHeight = 0;
const isEven = heightArr.length % 2 === 0;
const mid = Math.floor(heightArr.length / 2);
const topI = mid - 1;
const bottomI = isEven ? mid : mid + 1;
topHeight = isEven ? 0 : heightArr[mid] / 2;
for (let i = topI; i >= 0; i--) {
topHeight += heightArr[i];
}
bottomHeight = isEven ? 0 : heightArr[mid] / 2;
for (let i = bottomI; i < heightArr.length; i++) {
bottomHeight += heightArr[i];
}
return [topHeight, bottomHeight];
};
/**
* handle the height of each point in the single-level tree
* @param {Array} lines
* @param {Map} elementsMap
* @param {Boolean} isEven
* @param {Number} mid 'lines' array midpoint index
* @returns {Array} height array corresponding to 'lines'
*/
const handleDotYValue = (lines, elementsMap, isEven, mid) => {
const getTotalHeight = (line, elementsMap) => {
return elementsMap.get(line.endBinding.elementId).totalHeight;
};
const getTopHeight = (line, elementsMap) => {
return elementsMap.get(line.endBinding.elementId).topHeight;
};
const getBottomHeight = (line, elementsMap) => {
return elementsMap.get(line.endBinding.elementId).bottomHeight;
};
const heightArr = new Array(lines.length).fill(0);
const upI = mid === 0 ? 0 : mid - 1;
const bottomI = isEven ? mid : mid + 1;
let initHeight = isEven ? 0 : getTopHeight(lines[mid], elementsMap);
for (let i = upI; i >= 0; i--) {
heightArr[i] = initHeight + getBottomHeight(lines[i], elementsMap);
initHeight += getTotalHeight(lines[i], elementsMap);
}
initHeight = isEven ? 0 : getBottomHeight(lines[mid], elementsMap);
for (let i = bottomI; i < lines.length; i++) {
heightArr[i] = initHeight + getTopHeight(lines[i], elementsMap);
initHeight += getTotalHeight(lines[i], elementsMap);
}
return heightArr;
};
/**
* format single-level tree
* @param {any} parent
* @param {Array} lines
* @param {Map} childrenDescMap
* @param {Map} elementsMap
*/
const formatTree = (parent, lines, childrenDescMap, elementsMap) => {
lines.forEach((item) => setCenter(parent, item));
const isEven = lines.length % 2 === 0;
const mid = Math.floor(lines.length / 2);
const heightArr = handleDotYValue(lines, childrenDescMap, isEven, mid);
lines.forEach((item, index) => {
if (isEven) {
if (index < mid) setTopCurveDotOnLine(item, heightArr[index], index + 1);
else setBottomCurveDotOnLine(item, heightArr[index], index - mid + 1);
} else {
if (index < mid) setTopCurveDotOnLine(item, heightArr[index], index + 1);
else if (index === mid) setMidCurveDotOnLine(item);
else setBottomCurveDotOnLine(item, heightArr[index], index - mid);
}
});
lines.forEach((item) => {
if (item.endBinding !== null) {
setChildrenXY(
parent,
elementsMap.get(item.endBinding.elementId),
item,
elementsMap
);
}
});
};
const generateTree = (elements) => {
const elIdMap = new Map([[elements[0].id, elements[0]]]);
let minXEl = elements[0];
for (let i = 1; i < elements.length; i++) {
elIdMap.set(elements[i].id, elements[i]);
if (
!(elements[i].type === "arrow" || elements[i].type === "line") &&
elements[i].x < minXEl.x
) {
minXEl = elements[i];
}
}
const root = {
el: minXEl,
totalHeight: minXEl.height,
topHeight: 0,
bottomHeight: 0,
linkChildrensLines: [],
isLeafNode: false,
children: [],
};
const preIdSet = new Set(); // The id_set of Elements that is already in the tree, avoid a dead cycle
const dfsForTreeData = (root) => {
if (preIdSet.has(root.el.id)) {
return 0;
}
preIdSet.add(root.el.id);
let lines = root.el.boundElements.filter(
(el) =>
el.type === "arrow" &&
!preIdSet.has(el.id) &&
elIdMap.get(el.id)?.startBinding?.elementId === root.el.id
);
if (lines.length === 0) {
root.isLeafNode = true;
root.totalHeight = root.el.height + 2 * defaultGap;
[root.topHeight, root.bottomHeight] = [
root.totalHeight / 2,
root.totalHeight / 2,
];
return root.totalHeight;
} else {
lines = lines.map((elementDesc) => {
preIdSet.add(elementDesc.id);
return elIdMap.get(elementDesc.id);
});
}
const linkChildrensLines = [];
lines.forEach((el) => {
const line = el;
if (
line &&
line.endBinding !== null &&
line.endBinding !== undefined &&
!preIdSet.has(elIdMap.get(line.endBinding.elementId).id)
) {
const children = elIdMap.get(line.endBinding.elementId);
linkChildrensLines.push(line);
root.children.push({
el: children,
totalHeight: 0,
topHeight: 0,
bottomHeight: 0,
linkChildrensLines: [],
isLeafNode: false,
children: [],
});
}
});
let totalHeight = 0;
root.children.forEach((el) => (totalHeight += dfsForTreeData(el)));
root.linkChildrensLines = linkChildrensLines;
if (root.children.length === 0) {
root.isLeafNode = true;
root.totalHeight = root.el.height + 2 * defaultGap;
[root.topHeight, root.bottomHeight] = [
root.totalHeight / 2,
root.totalHeight / 2,
];
} else if (root.children.length > 0) {
root.totalHeight = Math.max(root.el.height + 2 * defaultGap, totalHeight);
[root.topHeight, root.bottomHeight] = getNodeCurrentHeight(
root.children.map((item) => item.totalHeight)
);
}
return totalHeight;
};
dfsForTreeData(root);
const dfsForFormat = (root) => {
if (root.isLeafNode) return;
const childrenDescMap = new Map(
root.children.map((item) => [item.el.id, item])
);
formatTree(root.el, root.linkChildrensLines, childrenDescMap, elIdMap);
root.children.forEach((el) => dfsForFormat(el));
};
dfsForFormat(root);
};
const elements = ea.getViewSelectedElements();
generateTree(elements);
ea.copyViewElementsToEAforEditing(elements);
await ea.addElementsToView(false, false);

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1673428425027" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1642" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24"><path d="M388.7 542.88c-16.57 0-30-13.43-30-30s13.43-30 30-30c52.3 0 94.85-42.55 94.85-94.85v-67.81c0-40.96 15.84-79.58 44.6-108.74 28.76-29.16 67.16-45.53 108.12-46.1l3.43-0.05c16.57-0.22 30.18 13.02 30.41 29.58 0.23 16.57-13.02 30.18-29.58 30.41l-3.43 0.05c-51.58 0.71-93.55 43.25-93.55 94.84v67.81c0 85.4-69.47 154.86-154.85 154.86z" fill="#000000" p-id="1643"></path><path d="M640.12 860.42h-0.42l-3.43-0.05c-40.96-0.56-79.36-16.93-108.12-46.09s-44.6-67.78-44.6-108.74v-67.8c0-52.3-42.55-94.85-94.85-94.85-16.57 0-30-13.43-30-30s13.43-30 30-30c85.38 0 154.85 69.47 154.85 154.85v67.8c0 51.59 41.96 94.13 93.55 94.84l3.43 0.05c16.57 0.23 29.81 13.84 29.59 30.41-0.24 16.42-13.62 29.58-30 29.58z" fill="#000000" p-id="1644"></path><path d="M640.11 542.88H388.7c-16.57 0-30-13.43-30-30s13.43-30 30-30h251.42c16.57 0 30 13.43 30 30-0.01 16.57-13.44 30-30.01 30z" fill="#000000" p-id="1645"></path><path d="M343.89 638.95H137.78c-38.6 0-70-31.4-70-70V456.81c0-38.6 31.4-70 70-70h206.11c38.6 0 70 31.4 70 70v112.13c0 38.6-31.4 70.01-70 70.01zM137.78 446.81c-5.51 0-10 4.49-10 10v112.13c0 5.51 4.49 10 10 10h206.11c5.51 0 10-4.49 10-10V456.81c0-5.51-4.49-10-10-10H137.78zM830.16 316.96h-93.98c-69.51 0-126.07-56.55-126.07-126.07S666.66 64.83 736.18 64.83h93.98c69.51 0 126.07 56.55 126.07 126.07-0.01 69.5-56.56 126.06-126.07 126.06z m-93.98-192.13c-36.43 0-66.07 29.64-66.07 66.07s29.64 66.07 66.07 66.07h93.98c36.43 0 66.07-29.64 66.07-66.07s-29.64-66.07-66.07-66.07h-93.98zM830.16 638.95h-93.98c-69.51 0-126.07-56.55-126.07-126.07 0-69.51 56.55-126.07 126.07-126.07h93.98c69.51 0 126.07 56.55 126.07 126.07-0.01 69.51-56.56 126.07-126.07 126.07z m-93.98-192.14c-36.43 0-66.07 29.64-66.07 66.07 0 36.43 29.64 66.07 66.07 66.07h93.98c36.43 0 66.07-29.64 66.07-66.07 0-36.43-29.64-66.07-66.07-66.07h-93.98z" fill="#000000" p-id="1646"></path><path d="M830.16 959.17h-93.98c-69.51 0-126.07-56.55-126.07-126.07s56.55-126.07 126.07-126.07h93.98c69.51 0 126.07 56.55 126.07 126.07s-56.56 126.07-126.07 126.07z m-93.98-192.13c-36.43 0-66.07 29.64-66.07 66.07s29.64 66.07 66.07 66.07h93.98c36.43 0 66.07-29.64 66.07-66.07s-29.64-66.07-66.07-66.07h-93.98z" fill="#000000" p-id="1647"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,91 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-normalize-selected-arrows.png)
This script will reset the start and end positions of the selected arrows. The arrow will point to the center of the connected box and will have a gap of 8px from the box.
Tips: If you are drawing a flowchart, you can use `Normalize Selected Arrows` script to correct the position of the start and end points of the arrows, then use `Elbow connectors` script, and you will get the perfect connecting line!
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.21")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
settings = ea.getScriptSettings();
//set default values on first run
if(!settings["Gap"]) {
settings = {
"Gap" : {
value: 8,
description: "The value of the gap between the connection line and the element, which must be greater than 0. If you want the connector to be next to the element, set it to 1."
}
};
ea.setScriptSettings(settings);
}
let gapValue = settings["Gap"].value;
const selectedIndividualArrows = ea.getMaximumGroups(ea.getViewSelectedElements())
.reduce((result, g) => [...result, ...g.filter(el => el.type === 'arrow')], []);
const allElements = ea.getViewElements();
for(const arrow of selectedIndividualArrows) {
const startBindingEl = allElements.filter(el => el.id === (arrow.startBinding||{}).elementId)[0];
const endBindingEl = allElements.filter(el => el.id === (arrow.endBinding||{}).elementId)[0];
if(startBindingEl) {
recalculateStartPointOfLine(arrow, startBindingEl, endBindingEl, gapValue);
}
if(endBindingEl) {
recalculateEndPointOfLine(arrow, endBindingEl, startBindingEl, gapValue);
}
}
ea.copyViewElementsToEAforEditing(selectedIndividualArrows);
await ea.addElementsToView(false,false);
function recalculateStartPointOfLine(line, el, elB, gapValue) {
const aX = el.x + el.width/2;
const bX = (line.points.length <=2 && elB) ? elB.x + elB.width/2 : line.x + line.points[1][0];
const aY = el.y + el.height/2;
const bY = (line.points.length <=2 && elB) ? elB.y + elB.height/2 : line.y + line.points[1][1];
line.startBinding.gap = gapValue;
line.startBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.startBinding.gap
);
if(intersectA.length > 0) {
line.points[0] = [0, 0];
for(var i = 1; i<line.points.length; i++) {
line.points[i][0] -= intersectA[0][0] - line.x;
line.points[i][1] -= intersectA[0][1] - line.y;
}
line.x = intersectA[0][0];
line.y = intersectA[0][1];
}
}
function recalculateEndPointOfLine(line, el, elB, gapValue) {
const aX = el.x + el.width/2;
const bX = (line.points.length <=2 && elB) ? elB.x + elB.width/2 : line.x + line.points[line.points.length-2][0];
const aY = el.y + el.height/2;
const bY = (line.points.length <=2 && elB) ? elB.y + elB.height/2 : line.y + line.points[line.points.length-2][1];
line.endBinding.gap = gapValue;
line.endBinding.focus = 0;
const intersectA = ea.intersectElementWithLine(
el,
[bX, bY],
[aX, aY],
line.endBinding.gap
);
if(intersectA.length > 0) {
line.points[line.points.length - 1] = [intersectA[0][0] - line.x, intersectA[0][1] - line.y];
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" stroke="#000"><path fill="none" stroke-linecap="round" stroke-width="4" d="M10 10h80m-80 0h80m0 0v40m0-40v40m0 0H10m80 0H10m0 0V10m0 40V10"/><g stroke-linecap="round"><path fill="none" stroke-width="4" d="M10 90c4.5-6.67 22.5-33.33 27-40M10 90c4.5-6.67 22.5-33.33 27-40"/><path fill-rule="evenodd" stroke-width="0" d="m37 50-2.35 14.81-10.51-7.09L37 50"/><path fill="none" stroke-width="4" d="M37 50c-.55 3.44-1.09 6.89-2.35 14.81M37 50c-.9 5.69-1.81 11.39-2.35 14.81m0 0c-4.05-2.73-8.1-5.46-10.51-7.09m10.51 7.09c-2.73-1.83-5.45-3.67-10.51-7.09m0 0C26.78 56.13 29.43 54.55 37 50m-12.86 7.72C29.26 54.65 34.39 51.57 37 50m0 0s0 0 0 0m0 0s0 0 0 0"/></g><g stroke-linecap="round"><path fill="none" stroke-width="4" d="M90 90 63 51.67M90 90 63 51.67"/><path fill-rule="evenodd" stroke-width="0" d="m63 51.67 13.01 7.46-10.36 7.3L63 51.67"/><path fill="none" stroke-width="4" d="M63 51.67c3.02 1.73 6.03 3.46 13.01 7.46M63 51.67c3.43 1.97 6.87 3.94 13.01 7.46m0 0c-3.33 2.34-6.65 4.69-10.36 7.3m10.36-7.3c-3.43 2.42-6.86 4.84-10.36 7.3m0 0c-.96-5.35-1.92-10.7-2.65-14.76m2.65 14.76c-.9-4.99-1.79-9.98-2.65-14.76m0 0s0 0 0 0m0 0s0 0 0 0"/></g><g stroke-linecap="round"><path fill="none" stroke-width="4" d="M50 90V50m0 40V50"/><path fill-rule="evenodd" stroke-width="0" d="m50 50 6.34 13.59H43.66L50 50"/><path fill="none" stroke-width="4" d="M50 50c2.27 4.86 4.53 9.72 6.34 13.59M50 50c1.62 3.47 3.24 6.95 6.34 13.59m0 0H43.66m12.68 0H43.66m0 0C46.18 58.2 48.69 52.81 50 50m-6.34 13.59C45.75 59.12 47.84 54.64 50 50m0 0s0 0 0 0m0 0s0 0 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,116 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-download-raw.jpg)
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-repeat-elements.png)
This script will detect the difference between 2 selected elements, including position, size, angle, stroke and background color, and create several elements that repeat these differences based on the number of repetitions entered by the user.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.7.19")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
let repeatNum = parseInt(await utils.inputPrompt("repeat times?","number","5"));
if(!repeatNum) {
new Notice("Please enter a number.");
return;
}
const selectedElements = ea.getViewSelectedElements().sort((lha,rha) =>
lha.x === rha.x? (lha.y === rha.y?
(lha.width === rha.width?
(lha.height - rha.height) : lha.width - rha.width)
: lha.y - rha.y) : lha.x - rha.x);
if(selectedElements.length !== 2) {
new Notice("Please select 2 elements.");
return;
}
if(selectedElements[0].type !== selectedElements[1].type) {
new Notice("The selected elements must be of the same type.");
return;
}
const xDistance = selectedElements[1].x - selectedElements[0].x;
const yDistance = selectedElements[1].y - selectedElements[0].y;
const widthDistance = selectedElements[1].width - selectedElements[0].width;
const heightDistance = selectedElements[1].height - selectedElements[0].height;
const angleDistance = selectedElements[1].angle - selectedElements[0].angle;
const bgColor1 = ea.colorNameToHex(selectedElements[0].backgroundColor);
const cmBgColor1 = ea.getCM(bgColor1);
const bgColor2 = ea.colorNameToHex(selectedElements[1].backgroundColor);
let cmBgColor2 = ea.getCM(bgColor2);
const isBgTransparent = cmBgColor1.alpha === 0 || cmBgColor2.alpha === 0;
const bgHDistance = cmBgColor2.hue - cmBgColor1.hue;
const bgSDistance = cmBgColor2.saturation - cmBgColor1.saturation;
const bgLDistance = cmBgColor2.lightness - cmBgColor1.lightness;
const bgADistance = cmBgColor2.alpha - cmBgColor1.alpha;
const strokeColor1 = ea.colorNameToHex(selectedElements[0].strokeColor);
const cmStrokeColor1 = ea.getCM(strokeColor1);
const strokeColor2 = ea.colorNameToHex(selectedElements[1].strokeColor);
let cmStrokeColor2 = ea.getCM(strokeColor2);
const isStrokeTransparent = cmStrokeColor1.alpha === 0 || cmStrokeColor2.alpha ===0;
const strokeHDistance = cmStrokeColor2.hue - cmStrokeColor1.hue;
const strokeSDistance = cmStrokeColor2.saturation - cmStrokeColor1.saturation;
const strokeLDistance = cmStrokeColor2.lightness - cmStrokeColor1.lightness;
const strokeADistance = cmStrokeColor2.alpha - cmStrokeColor1.alpha;
ea.copyViewElementsToEAforEditing(selectedElements);
for(let i=0; i<repeatNum; i++) {
const newEl = ea.cloneElement(selectedElements[1]);
ea.elementsDict[newEl.id] = newEl;
newEl.x += xDistance * (i + 1);
newEl.y += yDistance * (i + 1);
newEl.angle += angleDistance * (i + 1);
const originWidth = newEl.width;
const originHeight = newEl.height;
const newWidth = newEl.width + widthDistance * (i + 1);
const newHeight = newEl.height + heightDistance * (i + 1);
if(newWidth >= 0 && newHeight >= 0) {
if(newEl.type === 'arrow' || newEl.type === 'line' || newEl.type === 'freedraw') {
const minX = Math.min(...newEl.points.map(pt => pt[0]));
const minY = Math.min(...newEl.points.map(pt => pt[1]));
for(let j = 0; j < newEl.points.length; j++) {
if(newEl.points[j][0] > minX) {
newEl.points[j][0] = newEl.points[j][0] + ((newEl.points[j][0] - minX) / originWidth) * (newWidth - originWidth);
}
if(newEl.points[j][1] > minY) {
newEl.points[j][1] = newEl.points[j][1] + ((newEl.points[j][1] - minY) / originHeight) * (newHeight - originHeight);
}
}
}
else {
newEl.width = newWidth;
newEl.height = newHeight;
}
}
if(!isBgTransparent) {
cmBgColor2 = cmBgColor2.hueBy(bgHDistance).saturateBy(bgSDistance).lighterBy(bgLDistance).alphaBy(bgADistance);
newEl.backgroundColor = cmBgColor2.stringHEX();
} else {
newEl.backgroundColor = "transparent";
}
if(!isStrokeTransparent) {
cmStrokeColor2 = cmStrokeColor2.hueBy(strokeHDistance).saturateBy(strokeSDistance).lighterBy(strokeLDistance).alphaBy(strokeADistance);
newEl.strokeColor = cmStrokeColor2.stringHEX();
} else {
newEl.strokeColor = "transparent";
}
}
await ea.addElementsToView(false, false, true);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 120" stroke="#000"><defs/><path fill="none" stroke-linecap="round" stroke-width="4" d="M10 110h200m-200 0h200M30 90h160M30 90h160M50 70h120M50 70h120M70 50h80m-80 0h80M90 30h40m-40 0h40M110 10s0 0 0 0m0 0s0 0 0 0"/></svg>

After

Width:  |  Height:  |  Size: 279 B

View File

@@ -0,0 +1,23 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-download-raw.jpg)
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-reverse-arrow.jpg)
Reverse the direction of **arrows** within the scope of selected elements.
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
elements = ea.getViewSelectedElements().filter((el)=>el.type==="arrow");
if(!elements || elements.length===0) return;
elements.forEach((el)=>{
const start = el.startArrowhead;
el.startArrowhead = el.endArrowhead;
el.endArrowhead = start;
});
ea.copyViewElementsToEAforEditing(elements);
ea.addElementsToView(false,false);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M32 176h370.8l-57.38 57.38c-12.5 12.5-12.5 32.75 0 45.25C351.6 284.9 359.8 288 368 288s16.38-3.125 22.62-9.375l112-112c12.5-12.5 12.5-32.75 0-45.25l-112-112c-12.5-12.5-32.75-12.5-45.25 0s-12.5 32.75 0 45.25L402.8 112H32c-17.69 0-32 14.31-32 32S14.31 176 32 176zM480 336H109.3l57.38-57.38c12.5-12.5 12.5-32.75 0-45.25s-32.75-12.5-45.25 0l-112 112c-12.5 12.5-12.5 32.75 0 45.25l112 112C127.6 508.9 135.8 512 144 512s16.38-3.125 22.62-9.375c12.5-12.5 12.5-32.75 0-45.25L109.3 400H480c17.69 0 32-14.31 32-32S497.7 336 480 336z"/></svg>

After

Width:  |  Height:  |  Size: 602 B

View File

@@ -0,0 +1,488 @@
/*
![](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.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);
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -0,0 +1,27 @@
/*
## requires Excalidraw 1.5.1 or higher
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-download-raw.jpg)
Download this file and save to your Obsidian Vault including the first line, or open it in "Raw" and copy the entire contents to Obsidian.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-split-lines.jpg)
Split lines of text into separate text elements for easier reorganization
See documentation for more details:
https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.html
```javascript
*/
elements = ea.getViewSelectedElements().filter((el)=>el.type==="text");
elements.forEach((el)=>{
ea.style.strokeColor = el.strokeColor;
ea.style.fontFamily = el.fontFamily;
ea.style.fontSize = el.fontSize;
const text = el.text.split("\n");
for(i=0;i<text.length;i++) {
ea.addText(el.x,el.y+i*el.height/text.length,text[i]);
}
});
ea.addElementsToView(false,false);
ea.deleteViewElements(elements);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="100 80 500 400"><path d="M309.12 184.8c0 21.957-17.801 39.758-39.762 39.758-21.957 0-39.758-17.801-39.758-39.758 0-21.961 17.801-39.762 39.758-39.762 21.961 0 39.762 17.801 39.762 39.762M341.6 159.04h179.2v51.52H341.6zM309.12 282.24c0 21.961-17.801 39.762-39.762 39.762-21.957 0-39.758-17.801-39.758-39.762 0-21.957 17.801-39.758 39.758-39.758 21.961 0 39.762 17.801 39.762 39.758M341.6 256.48h179.2V308H341.6zM309.12 379.12c0 21.957-17.801 39.758-39.762 39.758-21.957 0-39.758-17.801-39.758-39.758 0-21.961 17.801-39.762 39.758-39.762 21.961 0 39.762 17.801 39.762 39.762M341.6 353.36h179.2v51.52H341.6z"/></svg>

After

Width:  |  Height:  |  Size: 662 B

View File

@@ -0,0 +1,11 @@
{
"created_time": "2022-09-27T07:23:33Z",
"files": [
],
"folders": [
],
"id": "469",
"modified_time": "2022-09-27T07:23:33Z",
"signature": "1773952147817407733",
"version": 3
}