2.11.0-beta-1, 0.18.0-12 : added screenshot feature
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled

This commit is contained in:
zsviczian
2025-04-27 16:50:22 +02:00
parent 875bd4cb35
commit bd9721f308
9 changed files with 458 additions and 62 deletions

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.10.2-beta-1",
"version": "2.11.0-beta-1",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -23,7 +23,7 @@
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.8",
"@zsviczian/excalidraw": "0.18.0-9",
"@zsviczian/excalidraw": "0.18.0-12",
"chroma-js": "^2.4.2",
"clsx": "^2.0.0",
"@zsviczian/colormaster": "^1.2.2",

View File

@@ -1100,6 +1100,15 @@ FILENAME_HEAD: "Filename",
EXPORTDIALOG_PDF_PROGRESS_DONE: "Export complete",
EXPORTDIALOG_PDF_PROGRESS_ERROR: "Error exporting PDF, check developer console for details",
// Screenshot tab
EXPORTDIALOG_TAB_SCREENSHOT: "Screenshot",
EXPORTDIALOG_SCREENSHOT_DESC: "Screenshots will include embeddables such as markdown pages, YouTube, websites, etc. They are only available on desktop, cannot be automatically exported, and only support PNG format.",
SCREENSHOT_DESKTOP_ONLY: "Screenshot feature is only available on desktop",
SCREENSHOT_FILE_SUCCESS: "Screenshot saved to vault",
SCREENSHOT_CLIPBOARD_SUCCESS: "Screenshot copied to clipboard",
SCREENSHOT_CLIPBOARD_ERROR: "Failed to copy screenshot to clipboard: ",
SCREENSHOT_ERROR: "Error capturing screenshot - see console log",
//exportUtils.ts
PDF_EXPORT_DESKTOP_ONLY: "PDF export is only available on desktop",
};

View File

@@ -6,9 +6,11 @@ import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
import ExcalidrawView from "src/view/ExcalidrawView";
import ExcalidrawPlugin from "src/core/main";
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground, shouldEmbedScene } from "src/utils/utils";
import { PageOrientation, PageSize, PDFPageAlignment, PDFPageMarginString, exportSVGToClipboard } from "src/utils/exportUtils";
import { PageOrientation, PageSize, PDFPageAlignment, PDFPageMarginString, exportSVGToClipboard, exportPNG, exportPNGToClipboard } from "src/utils/exportUtils";
import { t } from "src/lang/helpers";
import { PDFExportSettings, PDFExportSettingsComponent } from "./PDFExportSettingsComponent";
import { captureScreenshot } from "src/utils/screenshot";
import { createOrOverwriteFile, getIMGFilename } from "src/utils/fileUtils";
@@ -34,7 +36,7 @@ export class ExportDialog extends Modal {
public saveToVault: boolean;
public pageSize: PageSize = "A4";
public pageOrientation: PageOrientation = "portrait";
private activeTab: "image" | "pdf" = "image";
private activeTab: "image" | "pdf" | "screenshot" = "image";
private contentContainer: HTMLDivElement;
private buttonContainerRow1: HTMLDivElement;
private buttonContainerRow2: HTMLDivElement;
@@ -126,11 +128,17 @@ export class ExportDialog extends Modal {
cls: `nav-button ${this.activeTab === "pdf" ? "is-active" : ""}`
});
const screenshotTab = tabContainer.createEl("button", {
text: t("EXPORTDIALOG_TAB_SCREENSHOT"),
cls: `nav-button ${this.activeTab === "screenshot" ? "is-active" : ""}`
});
// Tab click handlers
imageTab.onclick = () => {
this.activeTab = "image";
imageTab.addClass("is-active");
pdfTab.removeClass("is-active");
screenshotTab.removeClass("is-active");
this.renderContent();
};
@@ -138,8 +146,17 @@ export class ExportDialog extends Modal {
this.activeTab = "pdf";
pdfTab.addClass("is-active");
imageTab.removeClass("is-active");
screenshotTab.removeClass("is-active");
this.renderContent();
};
screenshotTab.onclick = () => {
this.activeTab = "screenshot";
screenshotTab.addClass("is-active");
imageTab.removeClass("is-active");
pdfTab.removeClass("is-active");
this.renderContent();
}
}
// Create content container
@@ -170,14 +187,23 @@ export class ExportDialog extends Modal {
this.buttonContainerRow1.empty();
this.buttonContainerRow2.empty();
if (this.activeTab === "image") {
this.createImageSettings();
this.createExportSettings();
this.createImageButtons();
} else {
this.createImageSettings();
this.createPDFSettings();
this.createPDFButton();
this.createHeader();
switch (this.activeTab) {
case "pdf":
this.createImageSettings();
this.createPDFSettings();
this.createPDFButton();
break;
case "screenshot":
this.createImageSettings(true);
this.createImageButtons(true);
break;
case "image":
default:
this.createImageSettings(false);
this.createExportSettings();
this.createImageButtons();
break;
}
}
@@ -186,12 +212,28 @@ export class ExportDialog extends Modal {
const height = Math.round(this.scale*this.boundingBox.height + this.padding*2);
return fragWithHTML(`${t("EXPORTDIALOG_SIZE_DESC")}<br>${t("EXPORTDIALOG_SCALE_VALUE")} <b>${this.scale}</b><br>${t("EXPORTDIALOG_IMAGE_SIZE")} <b>${width}x${height}</b>`);
}
private createImageSettings() {
let paddingSetting: Setting;
this.contentContainer.createEl("h1",{text: t("EXPORTDIALOG_IMAGE_SETTINGS")});
this.contentContainer.createEl("p",{text: t("EXPORTDIALOG_IMAGE_DESC")})
private createHeader() {
switch (this.activeTab) {
case "pdf":
this.contentContainer.createEl("h1",{text: t("EXPORTDIALOG_PDF_SETTINGS")});
//this.contentContainer.createEl("p",{text: t("EXPORTDIALOG_PDF_DESC")});
break;
case "screenshot":
this.contentContainer.createEl("h1",{text: t("EXPORTDIALOG_TAB_SCREENSHOT")});
this.contentContainer.createEl("p",{text: t("EXPORTDIALOG_SCREENSHOT_DESC")})
break;
case "image":
default:
this.contentContainer.createEl("h1",{text: t("EXPORTDIALOG_IMAGE_SETTINGS")});
this.contentContainer.createEl("p",{text: t("EXPORTDIALOG_IMAGE_DESC")})
break;
}
}
private createImageSettings(isScreenshot: boolean = false) {
let paddingSetting: Setting;
this.createSaveSettingsDropdown();
@@ -238,17 +280,19 @@ export class ExportDialog extends Modal {
})
)
new Setting(this.contentContainer)
.setName(t("EXPORTDIALOG_BACKGROUND"))
.addDropdown(dropdown =>
dropdown
.addOption("transparent", t("EXPORTDIALOG_BACKGROUND_TRANSPARENT"))
.addOption("with-color", t("EXPORTDIALOG_BACKGROUND_USE_COLOR"))
.setValue(this.transparent?"transparent":"with-color")
.onChange(value => {
this.transparent = value === "transparent";
})
)
if(!isScreenshot) {
new Setting(this.contentContainer)
.setName(t("EXPORTDIALOG_BACKGROUND"))
.addDropdown(dropdown =>
dropdown
.addOption("transparent", t("EXPORTDIALOG_BACKGROUND_TRANSPARENT"))
.addOption("with-color", t("EXPORTDIALOG_BACKGROUND_USE_COLOR"))
.setValue(this.transparent?"transparent":"with-color")
.onChange(value => {
this.transparent = value === "transparent";
})
)
}
this.selectedOnlySetting = new Setting(this.contentContainer)
.setName(t("EXPORTDIALOG_SELECTED_ELEMENTS"))
@@ -262,6 +306,8 @@ export class ExportDialog extends Modal {
this.updateBoundingBox();
})
);
//@ts-ignore
this.selectedOnlySetting.setVisibility(this.hasSelectedElements);
}
private createExportSettings() {
@@ -308,14 +354,29 @@ export class ExportDialog extends Modal {
).render();
}
private createImageButtons() {
private createImageButtons(isScreenshot: boolean = false) {
if(DEVICE.isDesktop) {
const bPNG = this.buttonContainerRow1.createEl("button", {
text: t("EXPORTDIALOG_PNGTOFILE"),
cls: "excalidraw-export-button"
});
bPNG.onclick = () => {
this.view.exportPNG(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
if(isScreenshot) {
//allow dialot to close before taking screenshot
setTimeout(async () => {
const png = await captureScreenshot(this.view, {
zoom: this.scale,
margin: this.padding,
selectedOnly: this.exportSelectedOnly,
theme: this.theme
});
if(png) {
exportPNG(png, this.view.file.basename);
}
});
} else {
this.view.exportPNG(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
}
this.close();
};
}
@@ -325,7 +386,22 @@ export class ExportDialog extends Modal {
cls: "excalidraw-export-button"
});
bPNGVault.onclick = () => {
this.view.savePNG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly));
if(isScreenshot) {
//allow dialot to close before taking screenshot
setTimeout(async () => {
const png = await captureScreenshot(this.view, {
zoom: this.scale,
margin: this.padding,
selectedOnly: this.exportSelectedOnly,
theme: this.theme
});
if(png) {
createOrOverwriteFile(this.app, getIMGFilename(this.view.file.path,"png"), png);
}
});
} else {
this.view.savePNG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly));
}
this.close();
};
@@ -334,10 +410,27 @@ export class ExportDialog extends Modal {
cls: "excalidraw-export-button"
});
bPNGClipboard.onclick = async () => {
this.view.exportPNGToClipboard(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
if(isScreenshot) {
//allow dialot to close before taking screenshot
setTimeout(async () => {
const png = await captureScreenshot(this.view, {
zoom: this.scale,
margin: this.padding,
selectedOnly: this.exportSelectedOnly,
theme: this.theme
});
if(png) {
exportPNGToClipboard(png);
}
});
} else {
this.view.exportPNGToClipboard(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
}
this.close();
};
if(isScreenshot) return;
if(DEVICE.isDesktop) {
const bExcalidraw = this.buttonContainerRow2.createEl("button", {
text: t("EXPORTDIALOG_EXCALIDRAW"),

View File

@@ -17,11 +17,9 @@ I build this plugin in my free time, as a labor of love. Curious about the philo
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div>
`,
"2.10.2": `
## Fixed by Excalidraw.com
- Alt-duplicate now preserves the original element. Previously, using Alt to duplicate would swap the original with the new element, leading to unexpected behavior and several downstream issues. [#9403](https://github.com/excalidraw/excalidraw/pull/9403)
"2.11.0": `
## New
- New "Screenshot" option in the Export Image dialog. This allows you to take a screenshot of the current view, including embedded web pages, youtube videos, and markdown documents. Screenshot is only possible in PNG.
- Expose parameter in plugin settings to disable AI functionality [#2325](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2325)
- Enable double-click text editing option in Excalidraw appearance and behavior (based on request on Discord)
- Added two new PDF export sizes: "Match image", "HD Screen".
@@ -29,8 +27,13 @@ I build this plugin in my free time, as a labor of love. Curious about the philo
## Fixed in the plugin
- Scaling multiple embeddables at once did not work. [#2276](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2276)
- When creating multiple back-of-the-note the second card is not created correctly if autosave has not yet happened.
- Drawing reloads while editing the back-of-the-note card in certain cases causing editing to be interrupted.
- Moved Excalidraw filetype indicator ✏️ to after filename where other filetype tags are displayed. You can turn filetype indicator on/off in plugin settings under Miscellaneous.
- Drawing reloads while editing the back-of-the-note card in certain cases causes editing to be interrupted.
- Moved Excalidraw filetype indicator ✏️ to after filename where other filetype tags are displayed. You can turn the filetype indicator on/off in plugin settings under Miscellaneous.
## Fixed by Excalidraw.com
- Alt-duplicate now preserves the original element. Previously, using Alt to duplicate would swap the original with the new element, leading to unexpected behavior and several downstream issues. [#9403](https://github.com/excalidraw/excalidraw/pull/9403)
- When dragging the arrow endpoint, update the binding only on the dragged side [#9367](https://github.com/excalidraw/excalidraw/pull/9367)
- Laser pointer trail disappearing on pointerup [#9413](https://github.com/excalidraw/excalidraw/pull/9413) [#9427](https://github.com/excalidraw/excalidraw/pull/9427)
`,
"2.10.1": `

View File

@@ -1,6 +1,8 @@
import { Notice } from 'obsidian';
import { DEVICE } from 'src/constants/constants';
import { t } from 'src/lang/helpers';
import { download } from './fileUtils';
import { svgToBase64 } from './utils';
const DPI = 96;
@@ -512,4 +514,30 @@ export async function exportSVGToClipboard(svg: SVGSVGElement) {
} catch (error) {
console.error("Failed to copy SVG to clipboard: ", error);
}
}
}
export async function exportPNGToClipboard(png: Blob) {
await navigator.clipboard.write([
new window.ClipboardItem({
"image/png": png,
}),
]);
}
export function exportPNG(png: Blob, filename: string) {
const reader = new FileReader();
reader.readAsDataURL(png);
reader.onloadend = () => {
const base64data = reader.result;
download(null, base64data, `${filename}.png`);
};
}
export function exportSVG(svg: SVGSVGElement, filename: string) {
download(
null,
svgToBase64(svg.outerHTML),
`${filename}.svg`,
);
}

View File

@@ -404,8 +404,8 @@ export const getAliasWithSize = (alias: string, size: string): string => {
}
export const getCropFileNameAndFolder = async (plugin: ExcalidrawPlugin, hostPath: string, baseNewFileName: string):Promise<{folderpath: string, filename: string}> => {
let prefix = plugin.settings.cropPrefix;
if(!prefix || prefix.trim() === "") prefix = CROPPED_PREFIX;
let prefix = plugin.settings.cropPrefix || "";
if(prefix.trim() === "") prefix = CROPPED_PREFIX;
const filename = prefix + baseNewFileName + ".md";
if(!plugin.settings.cropFolder || plugin.settings.cropFolder.trim() === "") {
const folderpath = (await getAttachmentsFolderAndFilePath(plugin.app, hostPath, filename)).folder;
@@ -417,8 +417,8 @@ export const getCropFileNameAndFolder = async (plugin: ExcalidrawPlugin, hostPat
}
export const getAnnotationFileNameAndFolder = async (plugin: ExcalidrawPlugin, hostPath: string, baseNewFileName: string):Promise<{folderpath: string, filename: string}> => {
let prefix = plugin.settings.annotatePrefix;
if(!prefix || prefix.trim() === "") prefix = ANNOTATED_PREFIX;
let prefix = plugin.settings.annotatePrefix || "";
if(prefix.trim() === "") prefix = ANNOTATED_PREFIX;
const filename = prefix + baseNewFileName + ".md";
if(!plugin.settings.annotateFolder || plugin.settings.annotateFolder.trim() === "") {
const folderpath = (await getAttachmentsFolderAndFilePath(plugin.app, hostPath, filename)).folder;
@@ -494,8 +494,11 @@ export const hasExcalidrawEmbeddedImagesTreeChanged = (sourceFile: TFile, mtime:
return fileList.some(f=>f.stat.mtime > mtime);
}
export async function createOrOverwriteFile(app: App, path: string, content: string | ArrayBuffer): Promise<TFile> {
export async function createOrOverwriteFile(app: App, path: string, content: string | ArrayBuffer | Blob): Promise<TFile> {
const file = app.vault.getAbstractFileByPath(normalizePath(path));
if (content instanceof Blob) {
content = await content.arrayBuffer();
}
if(content instanceof ArrayBuffer) {
if(file && file instanceof TFile) {
await app.vault.modifyBinary(file, content);

273
src/utils/screenshot.ts Normal file
View File

@@ -0,0 +1,273 @@
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
import { request } from "http";
import { Notice } from "obsidian";
import { DEVICE } from "src/constants/constants";
import { getEA } from "src/core";
import { t } from "src/lang/helpers";
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
import ExcalidrawView from "src/view/ExcalidrawView";
export interface ScreenshotOptions {
zoom: number;
margin: number;
selectedOnly: boolean;
theme: string;
}
export async function captureScreenshot(view: ExcalidrawView, options: ScreenshotOptions): Promise<Blob | null> {
if (!DEVICE.isDesktop) {
new Notice(t("SCREENSHOT_DESKTOP_ONLY"));
return null;
}
(view.excalidrawAPI as ExcalidrawImperativeAPI).setForceRenderAllEmbeddables(true);
const remote = window.require("electron").remote;
const scene = view.getScene();
const viewSelectedElements = view.getViewSelectedElements();
const selectedIDs = new Set(options.selectedOnly ? viewSelectedElements.map(el => el.id) : []);
const savedOpacity: { id: string; opacity: number }[] = [];
const ea = getEA(view) as ExcalidrawAutomate;
// Save the current browser zoom level
const webContents = remote.getCurrentWebContents();
const originalZoomFactor = webContents.getZoomFactor();
// Set browser zoom to 100%
webContents.setZoomFactor(1.0);
await sleep(100); // Give the browser time to apply zoom
const devicePixelRatio = window.devicePixelRatio || 1;
if (options.selectedOnly) {
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el => !selectedIDs.has(el.id)));
ea.getElements().forEach(el => {
savedOpacity.push({
id: el.id,
opacity: el.opacity
});
el.opacity = 0;
});
if (savedOpacity.length > 0) {
await ea.addElementsToView(false, false, false, false);
}
}
let boundingBox = ea.getBoundingBox(options.selectedOnly ? viewSelectedElements : scene.elements);
boundingBox = {
topX: Math.ceil(boundingBox.topX),
topY: Math.ceil(boundingBox.topY),
width: Math.ceil(boundingBox.width),
height: Math.ceil(boundingBox.height)
}
const margin = options.margin;
const availableWidth = Math.floor(view.excalidrawAPI.getAppState().width);
const availableHeight = Math.floor(view.excalidrawAPI.getAppState().height);
// Apply zoom to the total dimensions
const totalWidth = Math.ceil(boundingBox.width * options.zoom + margin * 2);
const totalHeight = Math.ceil(boundingBox.height * options.zoom + margin * 2);
// Calculate number of tiles
const cols = Math.ceil(totalWidth / availableWidth);
const rows = Math.ceil(totalHeight / availableHeight);
// Use exact tile sizes to avoid rounding issues
const tileWidth = Math.ceil(totalWidth / cols);
const tileHeight = Math.ceil(totalHeight / rows);
// Adjust totalWidth and totalHeight to be multiples of tileWidth and tileHeight
const adjustedTotalWidth = tileWidth * cols;
const adjustedTotalHeight = tileHeight * rows;
// Save and set state
const saveState = () => {
const {
scrollX,
scrollY,
zoom,
viewModeEnabled,
linkOpacity,
theme,
} = view.excalidrawAPI.getAppState();
return {
scrollX,
scrollY,
zoom,
viewModeEnabled,
linkOpacity,
theme,
};
}
const restoreState = (st: any) => {
view.updateScene({
appState: {
...st
}
});
}
const savedState = saveState();
// Switch to view mode for layerUIWrapper to be rendered so it can be hidden
view.updateScene({
appState: {
viewModeEnabled: true,
linkOpacity: 0,
theme: options.theme,
},
});
await sleep(50);
// Hide UI elements (must be after changing to view mode)
const container = view.excalidrawWrapperRef.current;
const layerUIWrapper = container.querySelector(".layer-ui__wrapper");
const layerUIWrapperOriginalDisplay = layerUIWrapper.style.display;
layerUIWrapper.style.display = "none";
const originalStyle = {
width: container.style.width,
height: container.style.height,
left: container.style.left,
top: container.style.top,
position: container.style.position,
overflow: container.style.overflow,
};
try {
container.style.width = tileWidth + "px";
container.style.height = tileHeight + "px";
container.style.overflow = "visible";
// Set canvas size and zoom value for capture
view.updateScene({
appState: {
zoom: {
value: options.zoom
},
width: tileWidth,
height: tileHeight
},
});
await sleep(50); // wait for frame to render
// Prepare to collect tile images as data URLs
const rect = container.getBoundingClientRect();
const tiles = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
// Calculate scroll position for this tile (adjusted for zoom)
const scrollX = boundingBox.topX - margin / options.zoom + (col * tileWidth) / options.zoom;
const scrollY = boundingBox.topY - margin / options.zoom + (row * tileHeight) / options.zoom;
view.updateScene({
appState: {
scrollX: -scrollX,
scrollY: -scrollY,
zoom: {
value: options.zoom
},
width: tileWidth,
height: tileHeight
},
});
await sleep(250);
// Calculate the exact width/height for this tile
const captureWidth = col === cols - 1 ? adjustedTotalWidth - tileWidth * (cols - 1) : tileWidth;
const captureHeight = row === rows - 1 ? adjustedTotalHeight - tileHeight * (rows - 1) : tileHeight;
const image = await remote.getCurrentWebContents().capturePage({
x: rect.left * devicePixelRatio,
y: rect.top * devicePixelRatio,
width: captureWidth * devicePixelRatio,
height: captureHeight * devicePixelRatio,
});
tiles.push({
url: "data:image/png;base64," + image.toPNG().toString("base64"),
width: captureWidth,
height: captureHeight,
col: col,
row: row
});
}
}
// Restore original styles
Object.assign(container.style, originalStyle);
// Stitch tiles together using a browser canvas
const canvas = document.createElement("canvas");
canvas.width = adjustedTotalWidth * devicePixelRatio;
canvas.height = adjustedTotalHeight * devicePixelRatio;
canvas.style.width = `${adjustedTotalWidth}px`;
canvas.style.height = `${adjustedTotalHeight}px`;
const ctx = canvas.getContext("2d");
ctx.scale(1, 1);
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
let y = 0;
for (let row = 0; row < rows; row++) {
let x = 0;
for (let col = 0; col < cols; col++) {
const tile = tiles[row * cols + col];
const img = new window.Image();
img.src = tile.url;
await new Promise(res => {
img.onload = res;
});
ctx.drawImage(img, x, y);
x += tile.width * devicePixelRatio;
}
y += tiles[row * cols].height * devicePixelRatio; // Use the height of the first tile in the row
}
// Return the blob for the caller to handle
return new Promise<Blob>((resolve) => {
canvas.toBlob((blob) => {
resolve(blob);
}, "image/png");
});
} catch (e) {
console.error(e);
new Notice(t("SCREENSHOT_ERROR"));
return null;
} finally {
// Restore browser zoom to its original value
webContents.setZoomFactor(originalZoomFactor);
// Restore UI elements
try {
const container = view.excalidrawWrapperRef.current;
const layerUIWrapper = container.querySelector(".layer-ui__wrapper");
if (layerUIWrapper) {
layerUIWrapper.style.display = layerUIWrapperOriginalDisplay;
}
// Restore original state
restoreState(savedState);
// Restore opacity for selected elements if necessary
if (options.selectedOnly && savedOpacity.length > 0) {
ea.clear();
ea.copyViewElementsToEAforEditing(ea.getViewElements().filter(el => !selectedIDs.has(el.id)));
savedOpacity.forEach(x => {
ea.getElement(x.id).opacity = x.opacity;
});
await ea.addElementsToView(false, false, false, false);
}
} catch (e) {
console.error("Error in cleanup:", e);
}
}
}

View File

@@ -151,7 +151,7 @@ import { getPDFCropRect } from "../utils/PDFUtils";
import { Position, ViewSemaphores } from "../types/excalidrawViewTypes";
import { DropManager } from "./managers/DropManager";
import { ImageInfo } from "src/types/excalidrawAutomateTypes";
import { exportToPDF, getMarginValue, getPageDimensions, PageOrientation, PageSize } from "src/utils/exportUtils";
import { exportPNG, exportPNGToClipboard, exportSVG, exportToPDF, getMarginValue, getPageDimensions, PageOrientation, PageSize } from "src/utils/exportUtils";
import { FrameRenderingOptions } from "src/types/utilTypes";
import { CaptureUpdateAction } from "src/constants/constants";
@@ -581,11 +581,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
if (!svg) {
return;
}
download(
null,
svgToBase64(svg.outerHTML),
`${this.file.basename}.svg`,
);
exportSVG(svg, this.file.basename);
}
public async getSVG(embedScene?: boolean, selectedOnly?: boolean):Promise<SVGSVGElement> {
@@ -685,7 +681,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
if (!png) {
return;
}
await createOrOverwriteFile(this.app, filepath, await png.arrayBuffer());
await createOrOverwriteFile(this.app, filepath, png);
}
if(this.plugin.settings.autoExportLightAndDark) {
@@ -714,11 +710,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
//
// not await so that we can detect whether the thrown error likely relates
// to a lack of support for the Promise ClipboardItem constructor
await navigator.clipboard.write([
new window.ClipboardItem({
"image/png": png,
}),
]);
await exportPNGToClipboard(png);
}
public async exportPNG(embedScene?:boolean, selectedOnly?: boolean):Promise<void> {
@@ -731,12 +723,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
if (!png) {
return;
}
const reader = new FileReader();
reader.readAsDataURL(png);
reader.onloadend = () => {
const base64data = reader.result;
download(null, base64data, `${this.file.basename}.png`);
};
exportPNG(png, this.file.basename);
}
public setPreventReload() {