shade master and new duplicate image command

This commit is contained in:
zsviczian
2024-12-26 23:38:29 +01:00
parent a64586c3e6
commit d3f4437478
2 changed files with 254 additions and 187 deletions

View File

@@ -1,18 +1,58 @@
/*
This script modifies the color lightness/hue/saturation/transparency of selected Excalidraw elements.
Select elements in the scene, then run the script.
This is an experimental script. If you find bugs, please consider debugging yourself then submitting a PR on github with the fix, instead of raising an issue. Thank you!
This script modifies the color lightness/hue/saturation/transparency of selected Excalidraw elements and SVG and nested Excalidraw drawings. Select eligible elements in the scene, then run the script.
- The color of Excalidraw elements (lines, ellipses, rectangles, etc.) will be changed by the script.
- The color of SVG elements and nested Excalidraw drawings will only be mapped. When mapping colors, the original image remains unchanged, only a mapping table is created and the image is recolored during rendering of your Excalidraw screen. In case you want to make manual changes you can also edit the mapping in Markdown View Mode under `## Embedded Files`
If you select only a single SVG or nested Excalidraw element, then the script offers an additional feature. You can map colors one by one in the image.
```js*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.7.19")) {
const HELP_TEXT = `
- Select SVG images, nested Excalidraw drawings and/or regular Excalidraw elements
- For a single selected image, you can map colors individually in the color mapping section
- For Excalidraw elements: stroke and background colors are modified permanently
- For SVG/nested drawings: original files stay unchanged, color mapping is stored under \`## Embedded Files\`
- Using color maps helps maintain links between drawings while allowing different color themes
- Sliders work on relative scale - the amount of change is applied to current values
- Unlike Excalidraw's opacity setting which affects the whole element:
- Shade Master can set different opacity for stroke vs background
- Note: SVG/nested drawing colors are mapped at color name level, thus "black" is different from "#000000"
- Additionally if the same color is used as fill and stroke the color can only be changed once
- This is an experimental script - contributions welcome on GitHub via PRs
`;
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.7.3")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
/*
SVGColorInfo is returned by ea.getSVGColorInfoForImgElement. Color info will all the color strings in the SVG file plus "fill" which represents the default fill color for SVG icons set at the SVG root element level. Fill if not set defaults to black:
type SVGColorInfo = Map<string, {
mappedTo: string;
fill: boolean;
stroke: boolean;
}>;
In the Excalidraw file under `## Embedded Files` the color map is included after the file. That color map implements ColorMap. ea.updateViewSVGImageColorMap takes a ColorMap as input.
interface ColorMap {
[color: string]: string;
};
*/
// Main script execution
const allElements = ea.getViewSelectedElements();
const svgImageElements = allElements.filter(el =>
el.type === "image" && ea.getViewFileForImageElement(el)?.extension === "svg"
);
const svgImageElements = allElements.filter(el => {
const file = ea.getViewFileForImageElement(el);
if(!file) return false;
return el.type === "image" && (
file.extension === "svg" ||
ea.isExcalidrawFile(file)
);
});
if(allElements.length === 0) {
new Notice("Select at least one rectangle, ellipse, diamond, line, arrow, freedraw, text or SVG image elment");
@@ -21,6 +61,7 @@ if(allElements.length === 0) {
const originalColors = new Map();
const colorInputs = new Map();
const sliderResetters = [];
let terminate = false;
const FORMAT = "Color Format";
const STROKE = "Modify Stroke Color";
@@ -41,6 +82,15 @@ if(!settings[STROKE]) {
ea.setScriptSettings(settings);
}
function getRegularElements() {
ea.clear();
//loading view elements again as element objects change when colors are updated
const allElements = ea.getViewSelectedElements();
return allElements.filter(el =>
["rectangle", "ellipse", "diamond", "line", "arrow", "freedraw", "text"].includes(el.type)
);
}
const updatedImageElementColorMaps = new Map();
let isWaitingForSVGUpdate = false;
function updateViewImageColors() {
@@ -52,24 +102,22 @@ function updateViewImageColors() {
colorMapArray = Array.from(updatedImageElementColorMaps.values());
updatedImageElementColorMaps.clear();
ea.updateViewSVGImageColorMap(elementArray, colorMapArray).then(()=>{
console.log("Updated colors", elementArray, colorMapArray);
isWaitingForSVGUpdate = false;
updateViewImageColors();
});
}
async function storeOriginalColors() {
// Store colors for regular elements
const regularElements = allElements.filter(el =>
["rectangle", "ellipse", "diamond", "line", "arrow", "freedraw", "text"].includes(el.type)
);
for (const el of regularElements) {
// Store colors for regular elements
for (const el of getRegularElements()) {
const key = el.id;
originalColors.set(key, {
const colorData = {
type: "regular",
strokeColor: el.strokeColor,
backgroundColor: el.backgroundColor
});
};
originalColors.set(key, colorData);
}
// Store colors for SVG elements
@@ -79,22 +127,56 @@ async function storeOriginalColors() {
for (const [color, info] of colorInfo.entries()) {
svgColors.set(color, {...info});
}
originalColors.set(el.id, {
const svgColorData = {
type: "svg",
colors: svgColors
});
};
originalColors.set(el.id, svgColorData);
}
}
function clearSVGMapping() {
for (const resetter of sliderResetters) {
resetter();
}
// Reset SVG elements
if (svgImageElements.length === 1) {
const el = svgImageElements[0];
const original = originalColors.get(el.id);
if (original && original.type === "svg") {
for (const color of original.colors.keys()) {
// Update UI components
const inputs = colorInputs.get(color);
if (inputs) {
if(color === "fill") {
//stroke is a special value in case the SVG has no fill color defined (i.e black)
inputs.textInput.setValue("black");
inputs.colorPicker.setValue("#000000");
} else {
const cm = ea.getCM(color);
inputs.textInput.setValue(color);
inputs.colorPicker.setValue(cm.stringHEX({alpha: false}).toLowerCase());
}
}
}
updatedImageElementColorMaps.set(el, {});
}
} else {
for (const el of svgImageElements) {
updatedImageElementColorMaps.set(el, {});
}
}
updateViewImageColors();
}
// Function to reset colors
async function resetColors() {
ea.clear();
const allElements = ea.getViewSelectedElements();
// Reset regular elements
const regularElements = allElements.filter(el =>
["rectangle", "ellipse", "diamond", "line", "arrow", "freedraw", "text"].includes(el.type)
);
for (const resetter of sliderResetters) {
resetter();
}
const regularElements = getRegularElements();
if (regularElements.length > 0) {
ea.copyViewElementsToEAforEditing(regularElements);
@@ -114,55 +196,29 @@ async function resetColors() {
const original = originalColors.get(el.id);
if (original && original.type === "svg") {
const newColorMap = {};
let hasChanges = false;
const currentColors = await ea.getSVGColorInfoForImgElement(el);
for (const [color, info] of currentColors.entries()) {
const originalInfo = original.colors.get(color);
if (originalInfo) {
newColorMap[color] = originalInfo.mappedTo;
hasChanges = true;
// Update UI components
const inputs = colorInputs.get(color);
if (inputs) {
const cm = ea.getCM(originalInfo.mappedTo);
inputs.textInput.setValue(originalInfo.mappedTo);
inputs.colorPicker.setValue(cm.stringHEX({alpha: false}));
}
for (const [color, info] of original.colors.entries()) {
newColorMap[color] = info.mappedTo;
// Update UI components
const inputs = colorInputs.get(color);
if (inputs) {
const cm = ea.getCM(info.mappedTo);
inputs.textInput.setValue(info.mappedTo);
inputs.colorPicker.setValue(cm.stringHEX({alpha: false}).toLowerCase());
}
}
if (hasChanges) {
updatedImageElementColorMaps.set(el, newColorMap);
} else {
updatedImageElementColorMaps.delete(el);
}
updatedImageElementColorMaps.set(el, newColorMap);
}
} else {
for (const el of allElements.filter(el =>
el.type === "image" && ea.getViewFileForImageElement(el)?.extension === "svg"
)) {
for (const el of svgImageElements) {
const original = originalColors.get(el.id);
if (original && original.type === "svg") {
const newColorMap = {};
let hasChanges = false;
const currentColors = await ea.getSVGColorInfoForImgElement(el);
for (const [color, info] of currentColors.entries()) {
const originalInfo = original.colors.get(color);
if (originalInfo) {
newColorMap[color] = originalInfo.mappedTo;
hasChanges = true;
}
}
if (hasChanges) {
//using el.id as key as elements may change
updatedImageElementColorMaps.set(el, newColorMap);
} else {
updatedImageElementColorMaps.delete(el);
}
for (const [color, info] of original.colors.entries()) {
newColorMap[color] = info.mappedTo;
}
updatedImageElementColorMaps.set(el, newColorMap);
}
}
}
@@ -196,32 +252,44 @@ function modifyColor(color, isDecrease, step, action) {
const format = settings[FORMAT].value;
switch(format) {
case "RGB": return modified.stringRGB(opts);
case "HEX": return modified.stringHEX(opts);
default: return modified.stringHSL(opts);
case "RGB": return modified.stringRGB(opts).toLowerCase();
case "HEX": return modified.stringHEX(opts).toLowerCase();
default: return modified.stringHSL(opts).toLowerCase();
}
}
function slider(contentEl, action, min, max, step, invert) {
let prevValue = (max-min)/2;
let debounce = false;
let sliderControl;
new ea.obsidian.Setting(contentEl)
.setName(action)
.addSlider(slider => slider
.setLimits(min, max, step)
.setValue(prevValue)
.onChange(async (value) => {
const isDecrease = invert ? value > prevValue : value < prevValue;
const step = Math.abs(value-prevValue);
prevValue = value;
if(step>0) {
await run(isDecrease, step, action);
}
}),
.addSlider(slider => {
sliderControl = slider;
slider
.setLimits(min, max, step)
.setValue(prevValue)
.onChange(async (value) => {
if (debounce) return;
const isDecrease = invert ? value > prevValue : value < prevValue;
const step = Math.abs(value-prevValue);
prevValue = value;
if(step>0) {
run(isDecrease, step, action);
}
});
}
);
return () => {
debounce = true;
prevValue = (max-min)/2;
sliderControl.setValue(prevValue);
debounce = false;
}
}
function showModal() {
let isColorPickerChange = false;
let debounceColorPicker = true;
const modal = new ea.obsidian.Modal(app);
let dirty = false;
@@ -230,6 +298,14 @@ function showModal() {
modal.bgOpacity = 0;
contentEl.createEl('h2', { text: 'Shade Master' });
const helpDiv = contentEl.createEl("details", {
attr: { style: "margin-bottom: 1em;background: var(--background-secondary); padding: 1em; border-radius: 4px;" }});
helpDiv.createEl("summary", { text: "Help & Usage Guide", attr: { style: "cursor: pointer; color: var(--text-accent);" } });
helpDiv.createEl("div", {
text: HELP_TEXT,
attr: { style: "margin-top: 0.5em; white-space: pre-wrap;" }
});
new ea.obsidian.Setting(contentEl)
.setName(FORMAT)
.setDesc("Output color format")
@@ -266,10 +342,10 @@ function showModal() {
})
);
slider(contentEl, "Hue", 0, 400, 1, false);
slider(contentEl, "Saturation", 0, 200, 1, false);
slider(contentEl, "Lightness", 0, 50, 1, false);
slider(contentEl, "Transparency", 0, 1, 0.05, true);
sliderResetters.push(slider(contentEl, "Hue", 0, 400, 1, false));
sliderResetters.push(slider(contentEl, "Saturation", 0, 200, 1, false));
sliderResetters.push(slider(contentEl, "Lightness", 0, 100, 1, false));
sliderResetters.push(slider(contentEl, "Transparency", 0, 2, 0.05, true));
// Add color pickers if a single SVG image is selected
if (svgImageElements.length === 1) {
@@ -281,7 +357,7 @@ function showModal() {
for (const [color, info] of colorInfo.entries()) {
const row = new ea.obsidian.Setting(colorSection)
.setName(color)
.setName(color === "fill" ? "SVG default" : color)
.setDesc(`${info.fill ? "Fill" : ""}${info.fill && info.stroke ? " & " : ""}${info.stroke ? "Stroke" : ""}`);
// Create color preview div
@@ -289,50 +365,58 @@ function showModal() {
previewDiv.style.width = "30px";
previewDiv.style.height = "20px";
previewDiv.style.border = "1px solid var(--background-modifier-border)";
//previewDiv.style.marginRight = "10px";
previewDiv.style.backgroundColor = ea.getCM(color).stringHEX({alpha: false});
// Add reset button
if (color === "transparent") {
previewDiv.style.backgroundImage = "linear-gradient(45deg, #808080 25%, transparent 25%), linear-gradient(-45deg, #808080 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #808080 75%), linear-gradient(-45deg, transparent 75%, #808080 75%)";
previewDiv.style.backgroundSize = "10px 10px";
previewDiv.style.backgroundPosition = "0 0, 0 5px, 5px -5px, -5px 0px";
} else {
previewDiv.style.backgroundColor = ea.getCM(color).stringHEX({alpha: false}).toLowerCase();
}
const resetButton = new ea.obsidian.Setting(row.controlEl)
.addButton(button => button
.setButtonText(">>>")
.setClass("reset-color-button") // Optional: for styling
.setClass("reset-color-button")
.onClick(async () => {
const original = originalColors.get(svgElement.id);
if (original?.type === "svg") {
const originalInfo = original.colors.get(color);
if (originalInfo) {
const newColorMap = ea.getColorMapForImageElement(svgElement);
const newColorMap = await ea.getColorMapForImageElement(svgElement);
delete newColorMap[color];
updatedImageElementColorMaps.set(svgElement, newColorMap);
updateViewImageColors();
// Update UI components
isColorPickerChange = false;
textInput.setValue(color);
colorPicker.setValue(ea.getCM(color).stringHEX({alpha: false}));
debounceColorPicker = true;
textInput.setValue(color === "fill" ? "black" : color);
colorPicker.setValue(color === "fill"
? "#000000"
: ea.getCM(color).stringHEX({alpha: false}).toLowerCase()
);
updateViewImageColors();
}
}
}))
resetButton.settingEl.style.padding = "0";
resetButton.settingEl.style.border = "0";
//resetButton.settingEl.style.marginRight = "5px";
// Add text input for color value
const textInput = new ea.obsidian.TextComponent(row.controlEl)
.setValue(info.mappedTo)
.setPlaceholder("Color value");
textInput.inputEl.style.width = "120px";
//textInput.inputEl.style.marginRight = "5px";
textInput.onChange(value => {
const lower = value.toLowerCase();
if (lower === color) return;
textInput.setValue(lower);
})
const applyButtonComponent = new ea.obsidian.Setting(row.controlEl)
.addButton(button => button
.setIcon("check")
.setTooltip("Apply")
.onClick(async () => {
debugger;
const value = textInput.getValue();
try {
if(!CSS.supports("color",value)) {
@@ -341,17 +425,15 @@ function showModal() {
}
const cm = ea.getCM(value);
if (cm) {
// Preserve alpha from original color if new color is valid
const originalAlpha = ea.getCM(info.mappedTo).alpha;
const format = settings[FORMAT].value;
const alpha = cm.alpha < 1 ? true : false;
const newColor = format === "RGB"
? cm.stringRGB({alpha , precision: [1,2,2,3]})
? cm.stringRGB({alpha , precision: [1,2,2,3]}).toLowerCase()
: format === "HEX"
? cm.stringHEX({alpha})
: cm.stringHSL({alpha, precision: [1,2,2,3]});
? cm.stringHEX({alpha}).toLowerCase()
: cm.stringHSL({alpha, precision: [1,2,2,3]}).toLowerCase();
const newColorMap = ea.getColorMapForImageElement(svgElement);
const newColorMap = await ea.getColorMapForImageElement(svgElement);
if(color === newColor) {
delete newColorMap[color];
} else {
@@ -359,8 +441,8 @@ function showModal() {
}
updatedImageElementColorMaps.set(svgElement, newColorMap);
updateViewImageColors();
isColorPickerChange = false;
colorPicker.setValue(cm.stringHEX({alpha: false}));
debounceColorPicker = true;
colorPicker.setValue(cm.stringHEX({alpha: false}).toLowerCase());
}
} catch (e) {
console.error("Invalid color value:", e);
@@ -371,7 +453,7 @@ function showModal() {
// Add color picker
const colorPicker = new ea.obsidian.ColorComponent(row.controlEl)
.setValue(ea.getCM(info.mappedTo).stringHEX({alpha: false}));
.setValue(ea.getCM(info.mappedTo).stringHEX({alpha: false}).toLowerCase());
// Store references to the components
colorInputs.set(color, {
@@ -382,12 +464,12 @@ function showModal() {
});
colorPicker.colorPickerEl.addEventListener('click', () => {
isColorPickerChange = true;
debounceColorPicker = false;
});
colorPicker.onChange(async (value) => {
try {
if(isColorPickerChange) {
if(!debounceColorPicker) {
// Preserve alpha from original color
const originalAlpha = ea.getCM(info.mappedTo).alpha;
const cm = ea.getCM(value);
@@ -395,16 +477,16 @@ function showModal() {
const alpha = originalAlpha < 1 ? true : false;
const format = settings[FORMAT].value;
const newColor = format === "RGB"
? cm.stringRGB({alpha, precision: [1,2,2,3]})
? cm.stringRGB({alpha, precision: [1,2,2,3]}).toLowerCase()
: format === "HEX"
? cm.stringHEX({alpha})
: cm.stringHSL({alpha, precision: [1,2,2,3]});
? cm.stringHEX({alpha}).toLowerCase()
: cm.stringHSL({alpha, precision: [1,2,2,3]}).toLowerCase();
// Update text input
textInput.setValue(newColor);
// Update SVG
const newColorMap = ea.getColorMapForImageElement(svgElement);
const newColorMap = await ea.getColorMapForImageElement(svgElement);
if(color === newColor) {
delete newColorMap[color];
} else {
@@ -416,26 +498,36 @@ function showModal() {
} catch (e) {
console.error("Invalid color value:", e);
} finally {
isColorPickerChange = false;
debounceColorPicker = true;
}
});
}
}
new ea.obsidian.Setting(contentEl)
const buttons = new ea.obsidian.Setting(contentEl);
if(svgImageElements.length > 0) {
buttons.addButton(button => button
.setButtonText("Initialize SVG Colors")
.onClick(() => {
debounceColorPicker = true;
clearSVGMapping();
})
);
}
buttons
.addButton(button => button
.setButtonText("Reset Colors")
.onClick(async () => {
isColorPickerChange = false;
await resetColors();
.setButtonText("Reset")
.onClick(() => {
debounceColorPicker = true;
resetColors();
}))
.addButton(button => button
.setButtonText("Close")
.setCta(true)
.onClick(() => modal.close()));
makeModalDraggable(modal.modalEl); // Add draggable functionality
makeModalDraggable(modal.modalEl);
};
modal.onClose = () => {
@@ -462,7 +554,7 @@ function makeModalDraggable(modalEl) {
const header = modalEl.querySelector('.modal-titlebar') || modalEl; // Default to modalEl if no titlebar
header.style.cursor = 'move';
const onMouseDown = (e) => {
const onPointerDown = (e) => {
// Ensure the event target isn't an interactive element like slider, button, or input
if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
@@ -474,12 +566,12 @@ function makeModalDraggable(modalEl) {
initialY = rect.top;
modalEl.style.position = 'absolute';
modalEl.style.margin = '0'; // Reset margin to avoid issues
modalEl.style.margin = '0';
modalEl.style.left = `${initialX}px`;
modalEl.style.top = `${initialY}px`;
};
const onMouseMove = (e) => {
const onPointerMove = (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
@@ -489,57 +581,28 @@ function makeModalDraggable(modalEl) {
modalEl.style.top = `${initialY + dy}px`;
};
const onMouseUp = () => {
const onPointerUp = () => {
isDragging = false;
};
header.addEventListener('mousedown', onMouseDown);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
header.addEventListener('pointerdown', onPointerDown);
document.addEventListener('pointermove', onPointerMove);
document.addEventListener('pointerup', onPointerUp);
// Clean up event listeners on modal close
modalEl.addEventListener('remove', () => {
header.removeEventListener('mousedown', onMouseDown);
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
header.removeEventListener('pointerdown', onPointerDown);
document.removeEventListener('pointermove', onPointerMove);
document.removeEventListener('pointerup', onPointerUp);
});
}
isRunning = false;
const queue = [];
function processQueue() {
if (!isRunning && queue.length > 0) {
const [isDecrease, step, action] = queue.shift();
executeChange(isDecrease, step, action).then(() => {
updateViewImageColors()
if (!terminate && queue.length > 0) processQueue();
});
}
}
const MAX_QUEUE_SIZE = 100;
function run(isDecrease, step, action) {
if (queue.length >= MAX_QUEUE_SIZE) {
new Notice ("Queue overflow. Dropping task.");
return;
}
queue.push([isDecrease, step, action]);
if (!isRunning) processQueue();
}
async function executeChange(isDecrease, step, action) {
try {
isRunning = true;
ea.clear();
const modifyStroke = settings[STROKE].value;
const modifyBackground = settings[BACKGROUND].value;
//must reselect after each run since elements change in the scene
const allElements = ea.getViewSelectedElements();
const regularElements = allElements.filter(el =>
["rectangle", "ellipse", "diamond", "line", "arrow", "freedraw", "text"].includes(el.type)
);
const regularElements = getRegularElements();
// Process regular elements
if (regularElements.length > 0) {
@@ -561,7 +624,6 @@ async function executeChange(isDecrease, step, action) {
const el = svgImageElements[0];
const colorInfo = await ea.getSVGColorInfoForImgElement(el);
const newColorMap = {};
let hasChanges = false;
// Process each color in the SVG
for (const [color, info] of colorInfo.entries()) {
@@ -571,32 +633,22 @@ async function executeChange(isDecrease, step, action) {
const modifiedColor = modifyColor(info.mappedTo, isDecrease, step, action);
if (modifiedColor !== color) {
newColorMap[color] = modifiedColor;
hasChanges = true;
// Update UI components if they exist
const inputs = colorInputs.get(color);
if (inputs) {
const cm = ea.getCM(modifiedColor);
inputs.textInput.setValue(modifiedColor);
inputs.colorPicker.setValue(cm.stringHEX({alpha: false}));
}
}
// Update UI components if they exist
const inputs = colorInputs.get(color);
if (inputs) {
const cm = ea.getCM(modifiedColor);
inputs.textInput.setValue(modifiedColor);
inputs.colorPicker.setValue(cm.stringHEX({alpha: false}).toLowerCase());
}
}
}
// Update the SVG if any colors were modified
if (hasChanges) {
updatedImageElementColorMaps.set(el, newColorMap);
} else {
updatedImageElementColorMaps.delete(el);
}
updatedImageElementColorMaps.set(el, newColorMap);
} else {
if (svgImageElements.length > 0) {
for (const el of svgImageElements) {
// Get current color mapping
const colorInfo = await ea.getSVGColorInfoForImgElement(el);
const newColorMap = {};
let hasChanges = false;
// Process each color in the SVG
for (const [color, info] of colorInfo.entries()) {
@@ -606,18 +658,10 @@ async function executeChange(isDecrease, step, action) {
const modifiedColor = modifyColor(info.mappedTo, isDecrease, step, action);
if (modifiedColor !== color) {
newColorMap[color] = modifiedColor;
hasChanges = true;
}
}
}
// Update the SVG if any colors were modified
if (hasChanges) {
//using el.id as key as elements may change
updatedImageElementColorMaps.set(el, newColorMap);
} else {
updatedImageElementColorMaps.delete(el);
}
updatedImageElementColorMaps.set(el, newColorMap);
}
}
}
@@ -629,6 +673,28 @@ async function executeChange(isDecrease, step, action) {
}
}
isRunning = false;
const queue = [];
function processQueue() {
if (!terminate && !isRunning && queue.length > 0) {
const [isDecrease, step, action] = queue.shift();
executeChange(isDecrease, step, action).then(() => {
updateViewImageColors()
if (queue.length > 0) processQueue();
});
}
}
const MAX_QUEUE_SIZE = 100;
function run(isDecrease, step, action) {
if (queue.length >= MAX_QUEUE_SIZE) {
new Notice ("Queue overflow. Dropping task.");
return;
}
queue.push([isDecrease, step, action]);
if (!isRunning) processQueue();
}
await storeOriginalColors();
showModal();
processQueue();

View File

@@ -30,6 +30,7 @@ export default {
"Script is up to date - Click to reinstall",
OPEN_AS_EXCALIDRAW: "Open as Excalidraw Drawing",
TOGGLE_MODE: "Toggle between Excalidraw and Markdown mode",
DUPLICATE_IMAGE: "Duplicate selected image with a different image ID",
CONVERT_NOTE_TO_EXCALIDRAW: "Convert markdown note to Excalidraw Drawing",
CONVERT_EXCALIDRAW: "Convert *.excalidraw to *.md files",
CREATE_NEW: "Create new drawing",