diff --git a/ea-scripts/Repeat Elements.md b/ea-scripts/Repeat Elements.md index c42fc3a..cba80c2 100644 --- a/ea-scripts/Repeat Elements.md +++ b/ea-scripts/Repeat Elements.md @@ -1,378 +1,378 @@ -/* - -![](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 -*/ - -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 = colorNameToHex(selectedElements[0].backgroundColor); -const rgbBgColor1 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(bgColor1); -const bgColor2 = colorNameToHex(selectedElements[1].backgroundColor); -const rgbBgColor2 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(bgColor2); -let bgHDistance = 0; -let bgSDistance = 0; -let bgLDistance = 0; -if(rgbBgColor1 && rgbBgColor2) { - const bgHsl1 = rgbToHsl([parseInt(rgbBgColor1[1], 16), parseInt(rgbBgColor1[2], 16), parseInt(rgbBgColor1[3], 16)]); - const bgHsl2 = rgbToHsl([parseInt(rgbBgColor2[1], 16), parseInt(rgbBgColor2[2], 16), parseInt(rgbBgColor2[3], 16)]); - - bgHDistance = bgHsl2[0] - bgHsl1[0]; - bgSDistance = bgHsl2[1] - bgHsl1[1]; - bgLDistance = bgHsl2[2] - bgHsl1[2]; -} - -const strokeColor1 = colorNameToHex(selectedElements[0].strokeColor); -const rgbStrokeColor1 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(strokeColor1); -const strokeColor2 = colorNameToHex(selectedElements[1].strokeColor); -const rgbStrokeColor2 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(strokeColor2); -let strokeHDistance = 0; -let strokeSDistance = 0; -let strokeLDistance = 0; -if(rgbStrokeColor1 && rgbStrokeColor2) { - const strokeHsl1 = rgbToHsl([parseInt(rgbStrokeColor1[1], 16), parseInt(rgbStrokeColor1[2], 16), parseInt(rgbStrokeColor1[3], 16)]); - const strokeHsl2 = rgbToHsl([parseInt(rgbStrokeColor2[1], 16), parseInt(rgbStrokeColor2[2], 16), parseInt(rgbStrokeColor2[3], 16)]); - - strokeHDistance = strokeHsl2[0] - strokeHsl1[0]; - strokeSDistance = strokeHsl2[1] - strokeHsl1[1]; - strokeLDistance = strokeHsl2[2] - strokeHsl1[2]; -} - -ea.copyViewElementsToEAforEditing(selectedElements); -for(let i=0; i= 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(rgbBgColor1 && rgbBgColor2) { - const bgHsl2 = rgbToHsl([parseInt(rgbBgColor2[1], 16), parseInt(rgbBgColor2[2], 16), parseInt(rgbBgColor2[3], 16)]); - const newBgH = bgHsl2[0] + bgHDistance * (i + 1); - const newBgS = bgHsl2[1] + bgSDistance * (i + 1); - const newBgL = bgHsl2[2] + bgLDistance * (i + 1); - - if(newBgH >= 0 && newBgH <= 360 && newBgS >= 0 && newBgS <= 100 && newBgL >= 0 && newBgL <= 100) { - const newBgRgb = hslToRgb([newBgH, newBgS, newBgL]); - newEl.backgroundColor = "#" + rgbToHexString(newBgRgb); - } - } - - if(rgbStrokeColor1 && rgbStrokeColor2) { - const strokeHsl2 = rgbToHsl([parseInt(rgbStrokeColor2[1], 16), parseInt(rgbStrokeColor2[2], 16), parseInt(rgbStrokeColor2[3], 16)]); - const newStrokeH = strokeHsl2[0] + strokeHDistance * (i + 1); - const newStrokeS = strokeHsl2[1] + strokeSDistance * (i + 1); - const newStrokeL = strokeHsl2[2] + strokeLDistance * (i + 1); - - if(newStrokeH >= 0 && newStrokeH <= 360 && newStrokeS >= 0 && newStrokeS <= 100 && newStrokeL >= 0 && newStrokeL <= 100) { - const newStrokeRgb = hslToRgb([newStrokeH, newStrokeS, newStrokeL]); - newEl.strokeColor = "#" + rgbToHexString(newStrokeRgb); - } - } -} - -await ea.addElementsToView(false, true, true); - -function rgbToHexString(args) { - const integer = - ((Math.round(args[0]) & 0xff) << 16) + - ((Math.round(args[1]) & 0xff) << 8) + - (Math.round(args[2]) & 0xff); - - const string = integer.toString(16).toUpperCase(); - return "000000".substring(string.length) + string; -} - -function hslToRgb(hsl) { - const h = hsl[0] / 360; - const s = hsl[1] / 100; - const l = hsl[2] / 100; - let t2; - let t3; - let val; - - if (s === 0) { - val = l * 255; - return [val, val, val]; - } - - if (l < 0.5) { - t2 = l * (1 + s); - } else { - t2 = l + s - l * s; - } - - const t1 = 2 * l - t2; - - const rgb = [0, 0, 0]; - for (let i = 0; i < 3; i++) { - t3 = h + (1 / 3) * -(i - 1); - if (t3 < 0) { - t3++; - } - - if (t3 > 1) { - t3--; - } - - if (6 * t3 < 1) { - val = t1 + (t2 - t1) * 6 * t3; - } else if (2 * t3 < 1) { - val = t2; - } else if (3 * t3 < 2) { - val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; - } else { - val = t1; - } - - rgb[i] = val * 255; - } - - return rgb; -} - -function rgbToHsl(rgb) { - const r = rgb[0] / 255; - const g = rgb[1] / 255; - const b = rgb[2] / 255; - const min = Math.min(r, g, b); - const max = Math.max(r, g, b); - const delta = max - min; - let h; - let s; - - if (max === min) { - h = 0; - } else if (r === max) { - h = (g - b) / delta; - } else if (g === max) { - h = 2 + (b - r) / delta; - } else if (b === max) { - h = 4 + (r - g) / delta; - } - - h = Math.min(h * 60, 360); - - if (h < 0) { - h += 360; - } - - const l = (min + max) / 2; - - if (max === min) { - s = 0; - } else if (l <= 0.5) { - s = delta / (max + min); - } else { - s = delta / (2 - max - min); - } - - return [h, s * 100, l * 100]; -} - -function colorNameToHex(color) { - const colors = { - aliceblue: "#f0f8ff", - antiquewhite: "#faebd7", - aqua: "#00ffff", - aquamarine: "#7fffd4", - azure: "#f0ffff", - beige: "#f5f5dc", - bisque: "#ffe4c4", - black: "#000000", - blanchedalmond: "#ffebcd", - blue: "#0000ff", - blueviolet: "#8a2be2", - brown: "#a52a2a", - burlywood: "#deb887", - cadetblue: "#5f9ea0", - chartreuse: "#7fff00", - chocolate: "#d2691e", - coral: "#ff7f50", - cornflowerblue: "#6495ed", - cornsilk: "#fff8dc", - crimson: "#dc143c", - cyan: "#00ffff", - darkblue: "#00008b", - darkcyan: "#008b8b", - darkgoldenrod: "#b8860b", - darkgray: "#a9a9a9", - darkgreen: "#006400", - darkkhaki: "#bdb76b", - darkmagenta: "#8b008b", - darkolivegreen: "#556b2f", - darkorange: "#ff8c00", - darkorchid: "#9932cc", - darkred: "#8b0000", - darksalmon: "#e9967a", - darkseagreen: "#8fbc8f", - darkslateblue: "#483d8b", - darkslategray: "#2f4f4f", - darkturquoise: "#00ced1", - darkviolet: "#9400d3", - deeppink: "#ff1493", - deepskyblue: "#00bfff", - dimgray: "#696969", - dodgerblue: "#1e90ff", - firebrick: "#b22222", - floralwhite: "#fffaf0", - forestgreen: "#228b22", - fuchsia: "#ff00ff", - gainsboro: "#dcdcdc", - ghostwhite: "#f8f8ff", - gold: "#ffd700", - goldenrod: "#daa520", - gray: "#808080", - green: "#008000", - greenyellow: "#adff2f", - honeydew: "#f0fff0", - hotpink: "#ff69b4", - "indianred ": "#cd5c5c", - indigo: "#4b0082", - ivory: "#fffff0", - khaki: "#f0e68c", - lavender: "#e6e6fa", - lavenderblush: "#fff0f5", - lawngreen: "#7cfc00", - lemonchiffon: "#fffacd", - lightblue: "#add8e6", - lightcoral: "#f08080", - lightcyan: "#e0ffff", - lightgoldenrodyellow: "#fafad2", - lightgrey: "#d3d3d3", - lightgreen: "#90ee90", - lightpink: "#ffb6c1", - lightsalmon: "#ffa07a", - lightseagreen: "#20b2aa", - lightskyblue: "#87cefa", - lightslategray: "#778899", - lightsteelblue: "#b0c4de", - lightyellow: "#ffffe0", - lime: "#00ff00", - limegreen: "#32cd32", - linen: "#faf0e6", - magenta: "#ff00ff", - maroon: "#800000", - mediumaquamarine: "#66cdaa", - mediumblue: "#0000cd", - mediumorchid: "#ba55d3", - mediumpurple: "#9370d8", - mediumseagreen: "#3cb371", - mediumslateblue: "#7b68ee", - mediumspringgreen: "#00fa9a", - mediumturquoise: "#48d1cc", - mediumvioletred: "#c71585", - midnightblue: "#191970", - mintcream: "#f5fffa", - mistyrose: "#ffe4e1", - moccasin: "#ffe4b5", - navajowhite: "#ffdead", - navy: "#000080", - oldlace: "#fdf5e6", - olive: "#808000", - olivedrab: "#6b8e23", - orange: "#ffa500", - orangered: "#ff4500", - orchid: "#da70d6", - palegoldenrod: "#eee8aa", - palegreen: "#98fb98", - paleturquoise: "#afeeee", - palevioletred: "#d87093", - papayawhip: "#ffefd5", - peachpuff: "#ffdab9", - peru: "#cd853f", - pink: "#ffc0cb", - plum: "#dda0dd", - powderblue: "#b0e0e6", - purple: "#800080", - rebeccapurple: "#663399", - red: "#ff0000", - rosybrown: "#bc8f8f", - royalblue: "#4169e1", - saddlebrown: "#8b4513", - salmon: "#fa8072", - sandybrown: "#f4a460", - seagreen: "#2e8b57", - seashell: "#fff5ee", - sienna: "#a0522d", - silver: "#c0c0c0", - skyblue: "#87ceeb", - slateblue: "#6a5acd", - slategray: "#708090", - snow: "#fffafa", - springgreen: "#00ff7f", - steelblue: "#4682b4", - tan: "#d2b48c", - teal: "#008080", - thistle: "#d8bfd8", - tomato: "#ff6347", - turquoise: "#40e0d0", - violet: "#ee82ee", - wheat: "#f5deb3", - white: "#ffffff", - whitesmoke: "#f5f5f5", - yellow: "#ffff00", - yellowgreen: "#9acd32", - }; - if (typeof colors[color.toLowerCase()] != "undefined") - return colors[color.toLowerCase()]; - return color; +/* + +![](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 +*/ + +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 = colorNameToHex(selectedElements[0].backgroundColor); +const rgbBgColor1 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(bgColor1); +const bgColor2 = colorNameToHex(selectedElements[1].backgroundColor); +const rgbBgColor2 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(bgColor2); +let bgHDistance = 0; +let bgSDistance = 0; +let bgLDistance = 0; +if(rgbBgColor1 && rgbBgColor2) { + const bgHsl1 = rgbToHsl([parseInt(rgbBgColor1[1], 16), parseInt(rgbBgColor1[2], 16), parseInt(rgbBgColor1[3], 16)]); + const bgHsl2 = rgbToHsl([parseInt(rgbBgColor2[1], 16), parseInt(rgbBgColor2[2], 16), parseInt(rgbBgColor2[3], 16)]); + + bgHDistance = bgHsl2[0] - bgHsl1[0]; + bgSDistance = bgHsl2[1] - bgHsl1[1]; + bgLDistance = bgHsl2[2] - bgHsl1[2]; +} + +const strokeColor1 = colorNameToHex(selectedElements[0].strokeColor); +const rgbStrokeColor1 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(strokeColor1); +const strokeColor2 = colorNameToHex(selectedElements[1].strokeColor); +const rgbStrokeColor2 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(strokeColor2); +let strokeHDistance = 0; +let strokeSDistance = 0; +let strokeLDistance = 0; +if(rgbStrokeColor1 && rgbStrokeColor2) { + const strokeHsl1 = rgbToHsl([parseInt(rgbStrokeColor1[1], 16), parseInt(rgbStrokeColor1[2], 16), parseInt(rgbStrokeColor1[3], 16)]); + const strokeHsl2 = rgbToHsl([parseInt(rgbStrokeColor2[1], 16), parseInt(rgbStrokeColor2[2], 16), parseInt(rgbStrokeColor2[3], 16)]); + + strokeHDistance = strokeHsl2[0] - strokeHsl1[0]; + strokeSDistance = strokeHsl2[1] - strokeHsl1[1]; + strokeLDistance = strokeHsl2[2] - strokeHsl1[2]; +} + +ea.copyViewElementsToEAforEditing(selectedElements); +for(let i=0; i= 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(rgbBgColor1 && rgbBgColor2) { + const bgHsl2 = rgbToHsl([parseInt(rgbBgColor2[1], 16), parseInt(rgbBgColor2[2], 16), parseInt(rgbBgColor2[3], 16)]); + const newBgH = bgHsl2[0] + bgHDistance * (i + 1); + const newBgS = bgHsl2[1] + bgSDistance * (i + 1); + const newBgL = bgHsl2[2] + bgLDistance * (i + 1); + + if(newBgH >= 0 && newBgH <= 360 && newBgS >= 0 && newBgS <= 100 && newBgL >= 0 && newBgL <= 100) { + const newBgRgb = hslToRgb([newBgH, newBgS, newBgL]); + newEl.backgroundColor = "#" + rgbToHexString(newBgRgb); + } + } + + if(rgbStrokeColor1 && rgbStrokeColor2) { + const strokeHsl2 = rgbToHsl([parseInt(rgbStrokeColor2[1], 16), parseInt(rgbStrokeColor2[2], 16), parseInt(rgbStrokeColor2[3], 16)]); + const newStrokeH = strokeHsl2[0] + strokeHDistance * (i + 1); + const newStrokeS = strokeHsl2[1] + strokeSDistance * (i + 1); + const newStrokeL = strokeHsl2[2] + strokeLDistance * (i + 1); + + if(newStrokeH >= 0 && newStrokeH <= 360 && newStrokeS >= 0 && newStrokeS <= 100 && newStrokeL >= 0 && newStrokeL <= 100) { + const newStrokeRgb = hslToRgb([newStrokeH, newStrokeS, newStrokeL]); + newEl.strokeColor = "#" + rgbToHexString(newStrokeRgb); + } + } +} + +await ea.addElementsToView(false, true, true); + +function rgbToHexString(args) { + const integer = + ((Math.round(args[0]) & 0xff) << 16) + + ((Math.round(args[1]) & 0xff) << 8) + + (Math.round(args[2]) & 0xff); + + const string = integer.toString(16).toUpperCase(); + return "000000".substring(string.length) + string; +} + +function hslToRgb(hsl) { + const h = hsl[0] / 360; + const s = hsl[1] / 100; + const l = hsl[2] / 100; + let t2; + let t3; + let val; + + if (s === 0) { + val = l * 255; + return [val, val, val]; + } + + if (l < 0.5) { + t2 = l * (1 + s); + } else { + t2 = l + s - l * s; + } + + const t1 = 2 * l - t2; + + const rgb = [0, 0, 0]; + for (let i = 0; i < 3; i++) { + t3 = h + (1 / 3) * -(i - 1); + if (t3 < 0) { + t3++; + } + + if (t3 > 1) { + t3--; + } + + if (6 * t3 < 1) { + val = t1 + (t2 - t1) * 6 * t3; + } else if (2 * t3 < 1) { + val = t2; + } else if (3 * t3 < 2) { + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + } else { + val = t1; + } + + rgb[i] = val * 255; + } + + return rgb; +} + +function rgbToHsl(rgb) { + const r = rgb[0] / 255; + const g = rgb[1] / 255; + const b = rgb[2] / 255; + const min = Math.min(r, g, b); + const max = Math.max(r, g, b); + const delta = max - min; + let h; + let s; + + if (max === min) { + h = 0; + } else if (r === max) { + h = (g - b) / delta; + } else if (g === max) { + h = 2 + (b - r) / delta; + } else if (b === max) { + h = 4 + (r - g) / delta; + } + + h = Math.min(h * 60, 360); + + if (h < 0) { + h += 360; + } + + const l = (min + max) / 2; + + if (max === min) { + s = 0; + } else if (l <= 0.5) { + s = delta / (max + min); + } else { + s = delta / (2 - max - min); + } + + return [h, s * 100, l * 100]; +} + +function colorNameToHex(color) { + const colors = { + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + green: "#008000", + greenyellow: "#adff2f", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + "indianred ": "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgrey: "#d3d3d3", + lightgreen: "#90ee90", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370d8", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#d87093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + rebeccapurple: "#663399", + red: "#ff0000", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32", + }; + if (typeof colors[color.toLowerCase()] != "undefined") + return colors[color.toLowerCase()]; + return color; } \ No newline at end of file diff --git a/manifest.json b/manifest.json index e7be63b..eb00bbd 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "1.6.11", + "version": "1.6.12", "minAppVersion": "0.12.16", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/package.json b/package.json index 6b8192a..d6f3950 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "author": "", "license": "MIT", "dependencies": { - "@zsviczian/excalidraw": "0.10.0-obsidian-49", + "@zsviczian/excalidraw": "0.11.0-obsidian-1", "monkey-around": "^2.3.0", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/src/ExcalidrawAutomate.ts b/src/ExcalidrawAutomate.ts index 2ac3c82..1518879 100644 --- a/src/ExcalidrawAutomate.ts +++ b/src/ExcalidrawAutomate.ts @@ -1168,14 +1168,9 @@ export async function initExcalidrawAutomate( commitToHistory: false, }); } - if ( - document.fullscreenElement === - (this.targetView as ExcalidrawView).contentEl - ) { - document.exitFullscreen(); - } else { - (this.targetView as ExcalidrawView).contentEl.requestFullscreen(); - } + const view = this.targetView as ExcalidrawView; + if(view.isFullscreen()) view.exitFullscreen(); + else view.gotoFullscreen(); }, connectObjectWithViewSelectedElement( objectA: string, @@ -1445,6 +1440,9 @@ export function measureText( fontSize: number, fontFamily: number, ) { + //following odd error with mindmap on iPad while synchornizing with desktop. + if(!fontSize) fontSize = 20; + if(!fontFamily) fontFamily = 1; const line = document.createElement("div"); const body = document.body; line.style.position = "absolute"; diff --git a/src/ExcalidrawData.ts b/src/ExcalidrawData.ts index 9168bb6..a19e9ba 100644 --- a/src/ExcalidrawData.ts +++ b/src/ExcalidrawData.ts @@ -21,7 +21,7 @@ import { JSON_parse } from "./constants"; import { TextMode } from "./ExcalidrawView"; import { getAttachmentsFolderAndFilePath, - getBakPath, + //getBakPath, getBinaryFileFromDataURL, getLinkParts, isObsidianThemeDark, @@ -265,17 +265,17 @@ export class ExcalidrawData { } return sceneJSONandPOS; }; - try { + //try { sceneJSONandPOS = loadJSON(); - } catch (e) { + /*} catch (e) { if(await this.app.vault.adapter.exists(getBakPath(file))) { data = await this.app.vault.adapter.read(getBakPath(file)) sceneJSONandPOS = loadJSON(); new Notice(t("LOAD_FROM_BACKUP"), 4000); } else { throw e; - } - } + + }*/ if (!this.scene.files) { this.scene.files = {}; //loading legacy scenes that do not yet have the files attribute. @@ -909,7 +909,7 @@ export class ExcalidrawData { let result = false; if (!this.compatibilityMode) { result = await this.syncFiles(); - this.scene.files = {}; + this.scene.files = {}; //files contains the dataURLs of files. Once synced these are all saved to disk } this.updateElementLinksFromScene(); result = diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index f908775..8979148 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -53,7 +53,7 @@ import { download, embedFontsInSVG, errorlog, - getBakPath, + //getBakPath, getIMGFilename, getNewOrAdjacentLeaf, getNewUniqueFilepath, @@ -278,6 +278,7 @@ export default class ExcalidrawView extends TextFileView { } this.preventReload = preventReload; + const allowSave = (this.dirty !== null && this.dirty) || this.autosaving; //dirty == false when view.file == null; this.dirty = null; const scene = this.getScene(); @@ -287,12 +288,11 @@ export default class ExcalidrawView extends TextFileView { (await this.excalidrawData.syncElements(scene)) && !this.autosaving ) { - //debug({where:"ExcalidrawView.save",file:this.file.name,dataTheme:this.excalidrawData.scene.appState.theme,before:"loadDrawing(false)"}) await this.loadDrawing(false); } //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/396 - const bakfilepath = getBakPath(this.file); + /*const bakfilepath = getBakPath(this.file); try { if (await this.app.vault.adapter.exists(bakfilepath)) { await this.app.vault.adapter.remove(bakfilepath); @@ -300,15 +300,17 @@ export default class ExcalidrawView extends TextFileView { await this.app.vault.adapter.copy(this.file.path, bakfilepath); } catch(e) { console.error({where: "ExcalidrawView.save copy backup file", error: e}); + }*/ + + if(allowSave) { + await super.save(); + this.diskIcon.querySelector("svg").removeClass("excalidraw-dirty"); } - - await super.save(); - - try { + /*try { await this.app.vault.adapter.remove(bakfilepath); } catch(e) { console.error({where: "ExcalidrawView.save removing backup file", error: e}); - } + }*/ if (!this.autosaving) { if (this.plugin.settings.autoexportSVG) { @@ -601,12 +603,13 @@ export default class ExcalidrawView extends TextFileView { this.zoomToFit(false); } + diskIcon: HTMLElement; onload() { this.addAction(SCRIPTENGINE_ICON_NAME, t("INSTALL_SCRIPT_BUTTON"), () => { new ScriptInstallPrompt(this.plugin).open(); }); - this.addAction(DISK_ICON_NAME, t("FORCE_SAVE"), async () => { + this.diskIcon = this.addAction(DISK_ICON_NAME, t("FORCE_SAVE"), async () => { await this.save(false); this.plugin.triggerEmbedUpdates(); this.loadSceneFiles(); @@ -733,6 +736,7 @@ export default class ExcalidrawView extends TextFileView { this.preventReload = false; return; } + this.diskIcon.querySelector("svg").removeClass("excalidraw-dirty"); if (this.compatibilityMode) { this.dirty = null; return; @@ -780,6 +784,7 @@ export default class ExcalidrawView extends TextFileView { data = this.data = data.replaceAll("\r\n", "\n").replaceAll("\r", "\n"); this.app.workspace.onLayoutReady(async () => { this.dirty = null; + this.diskIcon.querySelector("svg").removeClass("excalidraw-dirty"); this.compatibilityMode = this.file.extension === "excalidraw"; await this.plugin.loadSettings(); if (this.compatibilityMode) { @@ -865,16 +870,6 @@ export default class ExcalidrawView extends TextFileView { */ private async loadDrawing(justloaded: boolean) { const excalidrawData = this.excalidrawData.scene; - const appState = excalidrawData.appState as AppState; - if (appState.colorPalette?.canvasBackground && appState.colorPalette.canvasBackground.length !== 15) { - new Notice ("appState.colorPalette.canvasBackground must have exactly 15 colors"); - } - if (appState.colorPalette?.elementBackground && appState.colorPalette.elementBackground.length !== 15) { - new Notice ("appState.colorPalette.elementBackground must have exactly 15 colors"); - } - if (appState.colorPalette?.elementStroke && appState.colorPalette.elementStroke.length !== 15) { - new Notice ("appState.colorPalette.elementStroke must have exactly 15 colors"); - } this.justLoaded = justloaded; const om = this.excalidrawData.getOpenMode(); this.preventReload = false; @@ -1102,11 +1097,12 @@ export default class ExcalidrawView extends TextFileView { return data?.library ? data.library : data?.libraryItems ?? []; } + previousSceneVersion = 0; private instantiateExcalidraw(initdata: any) { //console.log("ExcalidrawView.instantiateExcalidraw()"); this.dirty = null; + this.diskIcon.querySelector("svg").removeClass("excalidraw-dirty"); const reactElement = React.createElement(() => { - let previousSceneVersion = 0; let currentPosition = { x: 0, y: 0 }; const excalidrawWrapperRef = React.useRef(null); const [dimensions, setDimensions] = React.useState({ @@ -1293,20 +1289,18 @@ export default class ExcalidrawView extends TextFileView { return { id: imageElement[0].id, fileId: imageElement[0].fileId }; //return image element fileId }; - this.addText = (text: string, fontFamily?: 1 | 2 | 3) => { + this.addText = (text: string, fontFamily?: 1 | 2 | 3 | 4) => { if (!excalidrawRef?.current) { return; } const st: AppState = this.excalidrawAPI.getAppState(); const ea = this.plugin.ea; ea.reset(); - ea.style.strokeColor = st.currentItemStrokeColor; - ea.style.opacity = st.currentItemOpacity; - ea.style.fontFamily = fontFamily - ? fontFamily - : st.currentItemFontFamily; - ea.style.fontSize = st.currentItemFontSize; - ea.style.textAlign = st.currentItemTextAlign; + ea.style.strokeColor = st.currentItemStrokeColor??"black"; + ea.style.opacity = st.currentItemOpacity??1; + ea.style.fontFamily = fontFamily??st.currentItemFontFamily??1; + ea.style.fontSize = st.currentItemFontSize??20; + ea.style.textAlign = st.currentItemTextAlign??"left"; ea.addText(currentPosition.x, currentPosition.y, text); this.addElements(ea.getElements(), false, true); }; @@ -1410,6 +1404,7 @@ export default class ExcalidrawView extends TextFileView { await this.save(false); //preventReload=false will ensure that markdown links are paresed and displayed correctly } else { this.dirty = this.file?.path; + this.diskIcon.querySelector("svg").addClass("excalidraw-dirty"); } return true; }; @@ -1808,20 +1803,21 @@ export default class ExcalidrawView extends TextFileView { if (this.justLoaded) { this.justLoaded = false; this.zoomToFit(false); - previousSceneVersion = getSceneVersion(et); + this.previousSceneVersion = getSceneVersion(et); return; } if ( - st.editingElement == null && - st.resizingElement == null && - st.draggingElement == null && - st.editingGroupId == null && - st.editingLinearElement == null + st.editingElement === null && + st.resizingElement === null && + st.draggingElement === null && + st.editingGroupId === null && + st.editingLinearElement === null ) { const sceneVersion = getSceneVersion(et); - if (sceneVersion != previousSceneVersion) { - previousSceneVersion = sceneVersion; + if (sceneVersion !== this.previousSceneVersion) { + this.previousSceneVersion = sceneVersion; this.dirty = this.file?.path; + this.diskIcon.querySelector("svg").addClass("excalidraw-dirty"); } } }, @@ -1912,7 +1908,7 @@ export default class ExcalidrawView extends TextFileView { currentPosition.y, draggable.file, ); - ea.addElementsToView(false, false); + ea.addElementsToView(false, false, true); })(); return false; } @@ -2027,6 +2023,7 @@ export default class ExcalidrawView extends TextFileView { if (isDeleted) { this.excalidrawData.deleteTextElement(textElement.id); this.dirty = this.file?.path; + this.diskIcon.querySelector("svg").addClass("excalidraw-dirty"); this.setupAutosaveTimer(); return [null, null, null]; } diff --git a/src/InsertImageDialog.ts b/src/InsertImageDialog.ts index 086d2fa..0ff8604 100644 --- a/src/InsertImageDialog.ts +++ b/src/InsertImageDialog.ts @@ -45,7 +45,7 @@ export class InsertImageDialog extends FuzzySuggestModal { ea.canvas.theme = this.view.excalidrawAPI.getAppState().theme; (async () => { await ea.addImage(0, 0, item); - ea.addElementsToView(true, false); + ea.addElementsToView(true, false, true); })(); } diff --git a/src/InsertMDDialog.ts b/src/InsertMDDialog.ts index 827711f..c71e1f2 100644 --- a/src/InsertMDDialog.ts +++ b/src/InsertMDDialog.ts @@ -39,7 +39,7 @@ export class InsertMDDialog extends FuzzySuggestModal { ea.setView(this.view); (async () => { await ea.addImage(0, 0, item); - ea.addElementsToView(true, false); + ea.addElementsToView(true, false, true); })(); } diff --git a/src/Utils.ts b/src/Utils.ts index c08d6bb..315c009 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -125,10 +125,10 @@ export function getIMGPathFromExcalidrawFile( ); } -export function getBakPath(file:TFile):string { +/*export function getBakPath(file:TFile):string { const re = new RegExp(`${file.name}$`,"g"); return file.path.replace(re,`.${file.name}.bak`); -} +}*/ /** * Create new file, if file already exists find first unique filename by adding a number to the end of the filename diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 99a3c5f..3b7f203 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -98,7 +98,7 @@ export default { "The folder may not be the root folder of your Vault. ", AUTOSAVE_NAME: "Enable Autosave", AUTOSAVE_DESC: - "Automatically save the active drawing every 30 seconds, or 1, 2, 3, 4, or 5 minutes. Save normally happens when you close Excalidraw or Obsidian, or move " + + "Automatically save the active drawing, in case there are changes, every 15, 30 seconds, or 1, 2, 3, 4, or 5 minute. Save normally happens when you close Excalidraw or Obsidian, or move " + "focus to another pane. I created this feature with mobile " + "phones and tablets in mind, where 'swiping out Obsidian to close it' led to some data loss.", AUTOSAVE_INTERVAL_NAME: "Interval for autosave", diff --git a/src/main.ts b/src/main.ts index 1947ed6..7e9b36d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -942,7 +942,7 @@ export default class ExcalidrawPlugin extends Plugin { ea.reset(); await ea.addLaTex(0, 0, formula); ea.setView(view); - ea.addElementsToView(true, false); + ea.addElementsToView(true, false, true); }); return true; } @@ -1295,13 +1295,14 @@ export default class ExcalidrawPlugin extends Plugin { self.registerEvent(self.app.vault.on("delete", deleteEventHandler)); //save open drawings when user quits the application - const quitEventHandler = async () => { + //Removing because it is not guaranteed to run, and frequently gets terminated mid flight, causing file consistency issues + /*const quitEventHandler = async () => { const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW); for (let i = 0; i < leaves.length; i++) { await (leaves[i].view as ExcalidrawView).save(true); } }; - self.registerEvent(self.app.workspace.on("quit", quitEventHandler)); + self.registerEvent(self.app.workspace.on("quit", quitEventHandler));*/ //save Excalidraw leaf and update embeds when switching to another leaf const activeLeafChangeEventHandler = async (leaf: WorkspaceLeaf) => { diff --git a/src/settings.ts b/src/settings.ts index 68bb55f..fc749e5 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -80,7 +80,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { templateFilePath: "Excalidraw/Template.excalidraw", scriptFolderPath: "Excalidraw/Scripts", autosave: true, - autosaveInterval: 30000, + autosaveInterval: 15000, drawingFilenamePrefix: "Drawing ", drawingEmbedPrefixWithFilename: true, drawingFilenameDateTime: "YYYY-MM-DD HH.mm.ss", @@ -345,6 +345,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab { .setDesc(fragWithHTML(t("AUTOSAVE_INTERVAL_DESC"))) .addDropdown(async (d: DropdownComponent) => { autosaveDropdown = d; + d.addOption("15000", "15 seconds"); d.addOption("30000", "30 seconds"); d.addOption("60000", "1 minute"); d.addOption("120000", "2 minutes"); diff --git a/styles.css b/styles.css index 168e19f..370c9ba 100644 --- a/styles.css +++ b/styles.css @@ -144,4 +144,8 @@ li[data-testid] { text-align: center; font-weight: bold; margin-bottom: 2em; +} + +.excalidraw-dirty { + color: red; } \ No newline at end of file diff --git a/versions.json b/versions.json index 02e7c66..a8f1d10 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,4 @@ { - "1.6.11": "0.12.16", + "1.6.12": "0.12.16", "1.4.2": "0.11.13" } diff --git a/yarn.lock b/yarn.lock index 273ee59..760cab5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2127,10 +2127,10 @@ "resolved" "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" "version" "4.2.2" -"@zsviczian/excalidraw@0.10.0-obsidian-49": - "integrity" "sha512-32XoMHNyvzWOkkwBMzlS7L6ijabWxCmmA24Pvpn0PUkVQf9dy8OhDDtMHd7NmxD8zglnAXihlAMhTX/l+CLo3A==" - "resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.10.0-obsidian-49.tgz" - "version" "0.10.0-obsidian-49" +"@zsviczian/excalidraw@0.11.0-obsidian-1": + "integrity" "sha512-jW2vCnNvk8/0QDBEhnVZ2yImHXkbUISCTdb+KG3yWhU5rwn8nH6tQPJGxHfnr5dN5D5TPrguh9ScqdezoKOzUw==" + "resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.11.0-obsidian-1.tgz" + "version" "0.11.0-obsidian-1" dependencies: "dotenv" "10.0.0"