mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Image mask offset, PDF drift and offset, addAppendUpdateCustomData on EA, some type cleanup
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
This commit is contained in:
@@ -91,6 +91,15 @@ import { EventManager } from "./managers/EventManager";
|
||||
declare const PLUGIN_VERSION:string;
|
||||
declare const INITIAL_TIMESTAMP: number;
|
||||
|
||||
type FileMasterInfo = {
|
||||
isHyperLink: boolean;
|
||||
isLocalLink: boolean;
|
||||
path: string;
|
||||
hasSVGwithBitmap: boolean;
|
||||
blockrefData: string,
|
||||
colorMapJSON?: string
|
||||
}
|
||||
|
||||
export default class ExcalidrawPlugin extends Plugin {
|
||||
private fileManager: PluginFileManager;
|
||||
private observerManager: ObserverManager;
|
||||
@@ -113,7 +122,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
public opencount: number = 0;
|
||||
public ea: ExcalidrawAutomate;
|
||||
//A master list of fileIds to facilitate copy / paste
|
||||
public filesMaster: Map<FileId, { isHyperLink: boolean; isLocalLink: boolean; path: string; hasSVGwithBitmap: boolean; blockrefData: string, colorMapJSON?: string}> =
|
||||
public filesMaster: Map<FileId, FileMasterInfo> =
|
||||
null; //fileId, path
|
||||
public equationsMaster: Map<FileId, string> = null; //fileId, formula
|
||||
public mermaidsMaster: Map<FileId, string> = null; //fileId, mermaidText
|
||||
|
||||
@@ -9,6 +9,30 @@ import { ExportSettings } from "src/view/ExcalidrawView";
|
||||
import { nanoid } from "src/constants/constants";
|
||||
import { svgToBase64 } from "../utils/utils";
|
||||
|
||||
/**
|
||||
* Creates a masked image from an Excalidraw scene.
|
||||
*
|
||||
* The scene must contain:
|
||||
* - One element.type="frame" element that defines the crop area
|
||||
* - One or more element.type="image" elements
|
||||
* - Zero or more non-image shape elements (rectangles, ellipses etc) that define the mask
|
||||
*
|
||||
* The class splits the scene into two parts:
|
||||
* 1. Images (managed in imageEA)
|
||||
* 2. Mask shapes (managed in maskEA)
|
||||
*
|
||||
* A transparent rectangle matching the combined bounding box is added to both
|
||||
* imageEA and maskEA to ensure consistent sizing between image and mask.
|
||||
*
|
||||
* For performance, if there is only one image, it is not rotated, and
|
||||
* its size matches the bounding box,
|
||||
* the image data is used directly from cache rather than regenerating.
|
||||
*
|
||||
* @example
|
||||
* const cropper = new CropImage(elements, files);
|
||||
* const pngBlob = await cropper.getCroppedPNG();
|
||||
* cropper.destroy();
|
||||
*/
|
||||
export class CropImage {
|
||||
private imageEA: ExcalidrawAutomate;
|
||||
private maskEA: ExcalidrawAutomate;
|
||||
@@ -106,10 +130,15 @@ export class CropImage {
|
||||
withTheme: false,
|
||||
isMask: false,
|
||||
}
|
||||
const isRotated = this.imageEA.getElements().some(el=>el.type === "image" && el.angle !== 0);
|
||||
const images = Object.values(this.imageEA.imagesDict);
|
||||
if(!isRotated && (images.length === 1)) {
|
||||
return images[0].dataURL;
|
||||
const images = this.imageEA.getElements().filter(el=>el.type === "image" && el.isDeleted === false);
|
||||
const isRotated = images.some(el=>el.angle !== 0);
|
||||
const imageDataURLs = Object.values(this.imageEA.imagesDict);
|
||||
if(!isRotated && images.length === 1 && imageDataURLs.length === 1) {
|
||||
const { width, height } = this.bbox;
|
||||
if(images[0].width === width && images[0].height === height) {
|
||||
//get image from the cache if mask is not bigger than the image, and if there is a single image element
|
||||
return imageDataURLs[0].dataURL;
|
||||
}
|
||||
}
|
||||
return await this.imageEA.createPNGBase64(null,1,exportSettings,null,null,0);
|
||||
}
|
||||
|
||||
@@ -157,6 +157,15 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Set ea.style.roundness. 0: is the legacy value, 3: is the current default value, null is sharp",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "addAppendUpdateCustomData",
|
||||
code: "addAppendUpdateCustomData(id: string, newData: Partial<Record<string, unknown>>)",
|
||||
desc: "Add, modify keys in element customData and preserve existing keys.\n" +
|
||||
"Creates customData={} if it does not exist.\n" +
|
||||
"Takes the element ID for an element in the elementsDict and the new data to add or modify.\n" +
|
||||
"To delete keys set key value in newData to undefined. so {keyToBeDeleted:undefined} will be deleted.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "addToGroup",
|
||||
code: "addToGroup(objectIds: []): string;",
|
||||
|
||||
@@ -73,7 +73,8 @@ type ImgData = {
|
||||
dataURL: DataURL;
|
||||
created: number;
|
||||
hasSVGwithBitmap: boolean;
|
||||
size: { height: number; width: number };
|
||||
size: Size;
|
||||
pdfPageViewProps?: PDFPageViewProps;
|
||||
};
|
||||
|
||||
export declare type MimeType = ValueOf<typeof IMAGE_MIME_TYPES> | "application/octet-stream";
|
||||
@@ -82,8 +83,16 @@ export type FileData = BinaryFileData & {
|
||||
size: Size;
|
||||
hasSVGwithBitmap: boolean;
|
||||
shouldScale: boolean; //true if image should maintain its area, false if image should display at 100% its size
|
||||
pdfPageViewProps?: PDFPageViewProps;
|
||||
};
|
||||
|
||||
export type PDFPageViewProps = {
|
||||
left: number;
|
||||
bottom: number;
|
||||
right: number;
|
||||
top: number;
|
||||
}
|
||||
|
||||
export type Size = {
|
||||
height: number;
|
||||
width: number;
|
||||
@@ -177,6 +186,7 @@ export class EmbeddedFile {
|
||||
public isLocalLink: boolean = false;
|
||||
public hyperlink:DataURL;
|
||||
public colorMap: ColorMap | null = null;
|
||||
public pdfPageViewProps: PDFPageViewProps;
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string, colorMapJSON?: string) {
|
||||
this.plugin = plugin;
|
||||
@@ -252,12 +262,14 @@ export class EmbeddedFile {
|
||||
return this.mtime !== this.file.stat.mtime;
|
||||
}
|
||||
|
||||
public setImage(
|
||||
imgBase64: string,
|
||||
mimeType: MimeType,
|
||||
size: Size,
|
||||
isDark: boolean,
|
||||
isSVGwithBitmap: boolean,
|
||||
public setImage({ imgBase64, mimeType, size, isDark, isSVGwithBitmap, pdfPageViewProps } : {
|
||||
imgBase64: string;
|
||||
mimeType: MimeType;
|
||||
size: Size;
|
||||
isDark: boolean;
|
||||
isSVGwithBitmap: boolean;
|
||||
pdfPageViewProps?: PDFPageViewProps;
|
||||
}
|
||||
) {
|
||||
if (!this.file && !this.isHyperLink && !this.isLocalLink) {
|
||||
return;
|
||||
@@ -266,6 +278,7 @@ export class EmbeddedFile {
|
||||
this.imgInverted = this.img = "";
|
||||
}
|
||||
this.mtime = this.isHyperLink || this.isLocalLink ? 0 : this.file.stat.mtime;
|
||||
this.pdfPageViewProps = pdfPageViewProps;
|
||||
this.size = size;
|
||||
this.mimeType = mimeType;
|
||||
switch (isDark && isSVGwithBitmap) {
|
||||
@@ -345,6 +358,7 @@ export class EmbeddedFilesLoader {
|
||||
created: number;
|
||||
hasSVGwithBitmap: boolean;
|
||||
size: { height: number; width: number };
|
||||
pdfPageViewProps?: PDFPageViewProps;
|
||||
}> {
|
||||
const result = await this._getObsidianImage(inFile, depth);
|
||||
this.emptyPDFDocsMap();
|
||||
@@ -552,9 +566,9 @@ export class EmbeddedFilesLoader {
|
||||
|
||||
const excalidrawSVG = isExcalidrawFile ? dURL : null;
|
||||
|
||||
const [pdfDataURL, pdfSize] = isPDF
|
||||
const [pdfDataURL, pdfSize, pdfPageViewProps] = isPDF
|
||||
? await this.pdfToDataURL(file,linkParts)
|
||||
: [null, null];
|
||||
: [null, null, null];
|
||||
|
||||
let mimeType: MimeType = isPDF
|
||||
? "image/png"
|
||||
@@ -600,6 +614,7 @@ export class EmbeddedFilesLoader {
|
||||
created: isHyperLink || isLocalLink ? 0 : file.stat.mtime,
|
||||
hasSVGwithBitmap,
|
||||
size,
|
||||
pdfPageViewProps,
|
||||
};
|
||||
} catch(e) {
|
||||
return null;
|
||||
@@ -634,7 +649,7 @@ export class EmbeddedFilesLoader {
|
||||
files.push([]);
|
||||
let batch = 0;
|
||||
|
||||
function* loadIterator():Generator<Promise<void>> {
|
||||
function* loadIterator(this: EmbeddedFilesLoader):Generator<Promise<void>> {
|
||||
while (!(entry = entries.next()).done) {
|
||||
if(fileIDWhiteList && !fileIDWhiteList.has(entry.value[0])) continue;
|
||||
const embeddedFile: EmbeddedFile = entry.value[1];
|
||||
@@ -654,20 +669,22 @@ export class EmbeddedFilesLoader {
|
||||
created: data.created,
|
||||
size: data.size,
|
||||
hasSVGwithBitmap: data.hasSVGwithBitmap,
|
||||
shouldScale: embeddedFile.shouldScale()
|
||||
shouldScale: embeddedFile.shouldScale(),
|
||||
pdfPageViewProps: data.pdfPageViewProps,
|
||||
};
|
||||
files[batch].push(fileData);
|
||||
}
|
||||
} else if (embeddedFile.isSVGwithBitmap && (depth !== 0 || isThemeChange)) {
|
||||
//this will reload the image in light/dark mode when switching themes
|
||||
const fileData = {
|
||||
const fileData: FileData = {
|
||||
mimeType: embeddedFile.mimeType,
|
||||
id: id,
|
||||
dataURL: embeddedFile.getImage(this.isDark) as DataURL,
|
||||
created: embeddedFile.mtime,
|
||||
size: embeddedFile.size,
|
||||
hasSVGwithBitmap: embeddedFile.isSVGwithBitmap,
|
||||
shouldScale: embeddedFile.shouldScale()
|
||||
shouldScale: embeddedFile.shouldScale(),
|
||||
pdfPageViewProps: embeddedFile.pdfPageViewProps,
|
||||
};
|
||||
files[batch].push(fileData);
|
||||
}
|
||||
@@ -803,7 +820,7 @@ export class EmbeddedFilesLoader {
|
||||
private async pdfToDataURL(
|
||||
file: TFile,
|
||||
linkParts: LinkParts,
|
||||
): Promise<[DataURL,{width:number, height:number}]> {
|
||||
): Promise<[DataURL,Size, PDFPageViewProps]> {
|
||||
try {
|
||||
let width = 0, height = 0;
|
||||
const pdfDoc = this.pdfDocsMap.get(file.path) ?? await getPDFDoc(file);
|
||||
@@ -814,6 +831,7 @@ export class EmbeddedFilesLoader {
|
||||
const scale = this.plugin.settings.pdfScale;
|
||||
const cropRect = linkParts.ref.split("rect=")[1]?.split(",").map(x=>parseInt(x));
|
||||
const validRect = cropRect && cropRect.length === 4 && cropRect.every(x=>!isNaN(x));
|
||||
let viewProps: PDFPageViewProps;
|
||||
|
||||
// Render the page
|
||||
const renderPage = async (num:number) => {
|
||||
@@ -824,8 +842,8 @@ export class EmbeddedFilesLoader {
|
||||
const page = await pdfDoc.getPage(num);
|
||||
// Set scale
|
||||
const viewport = page.getViewport({ scale });
|
||||
height = canvas.height = viewport.height;
|
||||
width = canvas.width = viewport.width;
|
||||
height = canvas.height = Math.round(viewport.height);
|
||||
width = canvas.width = Math.round(viewport.width);
|
||||
|
||||
const renderCtx = {
|
||||
canvasContext: ctx,
|
||||
@@ -846,9 +864,10 @@ export class EmbeddedFilesLoader {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const [left, bottom, right, top] = page.view;
|
||||
viewProps = {left, bottom, right, top};
|
||||
|
||||
if(validRect) {
|
||||
const [left, bottom, _, top] = page.view;
|
||||
|
||||
const pageHeight = top - bottom;
|
||||
width = (cropRect[2] - cropRect[0]) * scale;
|
||||
height = (cropRect[3] - cropRect[1]) * scale;
|
||||
@@ -868,19 +887,19 @@ export class EmbeddedFilesLoader {
|
||||
|
||||
const canvas = await renderPage(pageNum);
|
||||
if(canvas) {
|
||||
const result: [DataURL,{width:number, height:number}] = [`data:image/png;base64,${await new Promise((resolve, reject) => {
|
||||
const result: [DataURL,Size, PDFPageViewProps] = [`data:image/png;base64,${await new Promise((resolve, reject) => {
|
||||
canvas.toBlob(async (blob) => {
|
||||
const dataURL = await blobToBase64(blob);
|
||||
resolve(dataURL);
|
||||
});
|
||||
})}` as DataURL, {width, height}];
|
||||
})}` as DataURL, {width, height}, viewProps];
|
||||
canvas.width = 0; //free memory iOS bug
|
||||
canvas.height = 0;
|
||||
return result;
|
||||
}
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
return [null,null];
|
||||
return [null, null, null];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
scaleLoadedImage,
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
addAppendUpdateCustomData,
|
||||
} from "src/utils/utils";
|
||||
import { getAttachmentsFolderAndFilePath, getExcalidrawViews, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/obsidianUtils";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
@@ -97,7 +98,6 @@ import { ExcalidrawLib } from "../types/excalidrawLib";
|
||||
import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
|
||||
import { AddImageOptions, ImageInfo, SVGColorInfo } from "src/types/excalidrawAutomateTypes";
|
||||
import { errorMessage, filterColorMap, getEmbeddedFileForImageElment, isColorStringTransparent, isSVGColorInfo, mergeColorMapIntoSVGColorInfo, svgColorInfoToColorMap, updateOrAddSVGColorInfo } from "src/utils/excalidrawAutomateUtils";
|
||||
import { Color } from "chroma-js";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -141,6 +141,23 @@ export class ExcalidrawAutomate {
|
||||
this.plugin.printStarupBreakdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add, modify keys in element customData and preserve existing keys.
|
||||
* Creates customData={} if it does not exist.
|
||||
* Takes the element ID for an element in the elementsDict and the new data to add or modify.
|
||||
* To delete keys set key value in newData to undefined. so {keyToBeDeleted:undefined} will be deleted.
|
||||
* @param id
|
||||
* @param newData
|
||||
* @returns undefined if element does not exist in elementsDict, returns the modified element otherwise.
|
||||
*/
|
||||
public addAppendUpdateCustomData(id:string, newData: Partial<Record<string, unknown>>) {
|
||||
const el = this.elementsDict[id];
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
return addAppendUpdateCustomData(el,newData);
|
||||
}
|
||||
|
||||
public help(target: Function | string) {
|
||||
if (!target) {
|
||||
log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
@@ -1596,6 +1613,7 @@ export class ExcalidrawAutomate {
|
||||
width: image.size.width,
|
||||
},
|
||||
colorMap,
|
||||
pdfPageViewProps: image.pdfPageViewProps,
|
||||
};
|
||||
if (scale && (Math.max(image.size.width, image.size.height) > MAX_IMAGE_SIZE)) {
|
||||
const scale =
|
||||
@@ -2392,14 +2410,14 @@ export class ExcalidrawAutomate {
|
||||
return false;
|
||||
}
|
||||
const elements = this.getElements();
|
||||
return await this.targetView.addElements(
|
||||
elements,
|
||||
return await this.targetView.addElements({
|
||||
newElements: elements,
|
||||
repositionToCursor,
|
||||
save,
|
||||
this.imagesDict,
|
||||
images: this.imagesDict,
|
||||
newElementsOnTop,
|
||||
shouldRestoreElements,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -649,7 +649,6 @@ export class ExcalidrawData {
|
||||
containers.forEach((container: any) => {
|
||||
if(ellipseAndRhombusContainerWrapping && !container.customData?.legacyTextWrap) {
|
||||
addAppendUpdateCustomData(container, {legacyTextWrap: true});
|
||||
//container.customData = {...container.customData, legacyTextWrap: true};
|
||||
}
|
||||
const filteredBoundElements = container.boundElements.filter(
|
||||
(boundEl: any) => elements.some((el: any) => el.id === boundEl.id),
|
||||
@@ -1569,13 +1568,13 @@ export class ExcalidrawData {
|
||||
filepath,
|
||||
);
|
||||
|
||||
embeddedFile.setImage(
|
||||
dataURL,
|
||||
embeddedFile.setImage({
|
||||
imgBase64: dataURL,
|
||||
mimeType,
|
||||
{ height: 0, width: 0 },
|
||||
scene.appState?.theme === "dark",
|
||||
mimeType === "image/svg+xml", //this treat all SVGs as if they had embedded images REF:addIMAGE
|
||||
);
|
||||
size: { height: 0, width: 0 },
|
||||
isDark: scene.appState?.theme === "dark",
|
||||
isSVGwithBitmap: mimeType === "image/svg+xml", //this treat all SVGs as if they had embedded images REF:addIMAGE
|
||||
});
|
||||
this.setFile(key as FileId, embeddedFile);
|
||||
return file;
|
||||
}
|
||||
@@ -1593,7 +1592,9 @@ export class ExcalidrawData {
|
||||
const pageRef = ef.linkParts.original.split("#")?.[1];
|
||||
if(!pageRef || !pageRef.startsWith("page=") || pageRef.includes("rect")) return;
|
||||
const restOfLink = el.link ? el.link.match(/&rect=\d*,\d*,\d*,\d*(.*)/)?.[1] : "";
|
||||
const link = ef.linkParts.original + getPDFRect(el.crop, pdfScale) + (restOfLink ? restOfLink : "]]");
|
||||
const link = ef.linkParts.original +
|
||||
getPDFRect({elCrop: el.crop, scale: pdfScale, customData: el.customData}) +
|
||||
(restOfLink ? restOfLink : "]]");
|
||||
el.link = `[[${link}`;
|
||||
this.elementLinks.set(el.id, el.link);
|
||||
dirty = true;
|
||||
@@ -1992,7 +1993,7 @@ export class ExcalidrawData {
|
||||
isLocalLink: data.isLocalLink,
|
||||
path: data.hyperlink,
|
||||
blockrefData: null,
|
||||
hasSVGwithBitmap: data.isSVGwithBitmap
|
||||
hasSVGwithBitmap: data.isSVGwithBitmap,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { TFile } from "obsidian";
|
||||
import { FileId } from "src/core";
|
||||
import { ColorMap, MimeType } from "src/shared/EmbeddedFileLoader";
|
||||
import { ColorMap, MimeType, PDFPageViewProps, Size } from "src/shared/EmbeddedFileLoader";
|
||||
|
||||
export type SVGColorInfo = Map<string, {
|
||||
mappedTo: string;
|
||||
@@ -19,8 +19,9 @@ export type ImageInfo = {
|
||||
file?:string | TFile,
|
||||
hasSVGwithBitmap: boolean,
|
||||
latex?: string,
|
||||
size?: {height: number, width: number},
|
||||
size?: Size,
|
||||
colorMap?: ColorMap,
|
||||
pdfPageViewProps?: PDFPageViewProps,
|
||||
}
|
||||
|
||||
export interface AddImageOptions {
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
//for future use, not used currently
|
||||
|
||||
import { ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { PDFPageViewProps } from "src/shared/EmbeddedFileLoader";
|
||||
|
||||
export function getPDFCropRect (props: {
|
||||
scale: number,
|
||||
link: string,
|
||||
naturalHeight: number,
|
||||
naturalWidth: number,
|
||||
pdfPageViewProps: PDFPageViewProps,
|
||||
}) : ImageCrop | null {
|
||||
const rectVal = props.link.match(/&rect=(\d*),(\d*),(\d*),(\d*)/);
|
||||
if (!rectVal || rectVal.length !== 5) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { left, bottom } = props.pdfPageViewProps;
|
||||
const R0 = parseInt(rectVal[1]);
|
||||
const R1 = parseInt(rectVal[2]);
|
||||
const R2 = parseInt(rectVal[3]);
|
||||
const R3 = parseInt(rectVal[4]);
|
||||
|
||||
return {
|
||||
x: R0 * props.scale,
|
||||
y: (props.naturalHeight/props.scale - R3) * props.scale,
|
||||
x: (R0 - left) * props.scale,
|
||||
y: (bottom + props.naturalHeight/props.scale - R3) * props.scale,
|
||||
width: (R2 - R0) * props.scale,
|
||||
height: (R3 - R1) * props.scale,
|
||||
naturalWidth: props.naturalWidth,
|
||||
@@ -28,10 +31,16 @@ export function getPDFCropRect (props: {
|
||||
}
|
||||
}
|
||||
|
||||
export function getPDFRect(elCrop: ImageCrop, scale: number): string {
|
||||
const R0 = elCrop.x / scale;
|
||||
export function getPDFRect({elCrop, scale, customData}:{
|
||||
elCrop: ImageCrop, scale: number, customData: Record<string, unknown>
|
||||
}): string {
|
||||
const { left, bottom } = (customData && customData.pdfPageViewProps)
|
||||
? customData.pdfPageViewProps as PDFPageViewProps
|
||||
: { left: 0, bottom: 0 };
|
||||
|
||||
const R0 = elCrop.x / scale + left;
|
||||
const R2 = elCrop.width / scale + R0;
|
||||
const R3 = (elCrop.naturalHeight - elCrop.y) / scale;
|
||||
const R3 = bottom + (elCrop.naturalHeight - elCrop.y) / scale;
|
||||
const R1 = R3 - elCrop.height / scale;
|
||||
return `&rect=${Math.round(R0)},${Math.round(R1)},${Math.round(R2)},${Math.round(R3)}`;
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
getContainerElement,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../core/main";
|
||||
import { ExcalidrawElement, ExcalidrawTextElement, ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement, ExcalidrawImageElement, ExcalidrawTextElement, ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExportSettings } from "../view/ExcalidrawView";
|
||||
import { getDataURLFromURL, getIMGFilename, getMimeType, getURLImageExtension } from "./fileUtils";
|
||||
import { generateEmbeddableLink } from "./customEmbeddableUtils";
|
||||
@@ -32,6 +32,7 @@ import { runCompressionWorker } from "src/shared/Workers/compression-worker";
|
||||
import Pool from "es6-promise-pool";
|
||||
import { FileData } from "../shared/EmbeddedFileLoader";
|
||||
import { t } from "src/lang/helpers";
|
||||
import ExcalidrawScene from "src/shared/svgToExcalidraw/elements/ExcalidrawScene";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
declare var LZString: any;
|
||||
@@ -415,11 +416,17 @@ export async function getImageSize (
|
||||
});
|
||||
};
|
||||
|
||||
export function addAppendUpdateCustomData (el: Mutable<ExcalidrawElement>, newData: any): ExcalidrawElement {
|
||||
export function addAppendUpdateCustomData (
|
||||
el: Mutable<ExcalidrawElement>,
|
||||
newData: Partial<Record<string, unknown>>
|
||||
): ExcalidrawElement {
|
||||
if(!newData) return el;
|
||||
if(!el.customData) el.customData = {};
|
||||
for (const key in newData) {
|
||||
if(typeof newData[key] === "undefined") continue;
|
||||
if(typeof newData[key] === "undefined") {
|
||||
delete el.customData[key];
|
||||
continue;
|
||||
}
|
||||
el.customData[key] = newData[key];
|
||||
}
|
||||
return el;
|
||||
@@ -447,7 +454,7 @@ export function scaleLoadedImage (
|
||||
|
||||
scene.elements
|
||||
.filter((e: any) => e.type === "image" && e.fileId === img.id)
|
||||
.forEach((el: any) => {
|
||||
.forEach((el: Mutable<ExcalidrawImageElement>) => {
|
||||
const [elWidth, elHeight] = [el.width, el.height];
|
||||
const maintainArea = img.shouldScale; //true if image should maintain its area, false if image should display at 100% its size
|
||||
const elCrop: ImageCrop = el.crop;
|
||||
|
||||
@@ -105,8 +105,9 @@ import {
|
||||
shouldEmbedScene,
|
||||
_getContainerElement,
|
||||
arrayToMap,
|
||||
addAppendUpdateCustomData,
|
||||
} from "../utils/utils";
|
||||
import { cleanBlockRef, cleanSectionHeading, closeLeafView, getAttachmentsFolderAndFilePath, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf, setExcalidrawView } from "../utils/obsidianUtils";
|
||||
import { cleanBlockRef, cleanSectionHeading, closeLeafView, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf, setExcalidrawView } from "../utils/obsidianUtils";
|
||||
import { splitFolderAndFilename } from "../utils/fileUtils";
|
||||
import { ConfirmationPrompt, GenericInputPrompt, NewFileActions, Prompt, linkPrompt } from "../shared/Dialogs/Prompt";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
|
||||
@@ -147,6 +148,7 @@ import { IS_WORKER_SUPPORTED } from "../shared/Workers/compression-worker";
|
||||
import { getPDFCropRect } from "../utils/PDFUtils";
|
||||
import { Position, ViewSemaphores } from "../types/excalidrawViewTypes";
|
||||
import { DropManager } from "./managers/DropManager";
|
||||
import { ImageInfo } from "src/types/excalidrawAutomateTypes";
|
||||
|
||||
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
|
||||
const PREVENT_RELOAD_TIMEOUT = 2000;
|
||||
@@ -223,7 +225,9 @@ export const addFiles = async (
|
||||
.filter((f:FileData) => view.excalidrawData.getFile(f.id)?.file?.extension === "pdf")
|
||||
.forEach((f:FileData) => {
|
||||
s.scene.elements
|
||||
.filter((el:ExcalidrawElement)=>el.type === "image" && el.fileId === f.id && el.crop && el.crop.naturalWidth !== f.size.width)
|
||||
.filter((el:ExcalidrawElement)=>el.type === "image" && el.fileId === f.id && (
|
||||
(el.crop && el.crop.naturalWidth !== f.size.width) || !el.customData?.pdfPageViewProps
|
||||
))
|
||||
.forEach((el:Mutable<ExcalidrawImageElement>) => {
|
||||
s.dirty = true;
|
||||
const scale = f.size.width / el.crop.naturalWidth;
|
||||
@@ -235,6 +239,7 @@ export const addFiles = async (
|
||||
naturalWidth: f.size.width,
|
||||
naturalHeight: f.size.height,
|
||||
};
|
||||
addAppendUpdateCustomData(el, { pdfPageViewProps: f.pdfPageViewProps});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -250,13 +255,14 @@ export const addFiles = async (
|
||||
if (view.excalidrawData.hasFile(f.id)) {
|
||||
const embeddedFile = view.excalidrawData.getFile(f.id);
|
||||
|
||||
embeddedFile.setImage(
|
||||
f.dataURL,
|
||||
f.mimeType,
|
||||
f.size,
|
||||
embeddedFile.setImage({
|
||||
imgBase64: f.dataURL,
|
||||
mimeType: f.mimeType,
|
||||
size: f.size,
|
||||
isDark,
|
||||
f.hasSVGwithBitmap,
|
||||
);
|
||||
isSVGwithBitmap: f.hasSVGwithBitmap,
|
||||
pdfPageViewProps: f.pdfPageViewProps,
|
||||
});
|
||||
}
|
||||
if (view.excalidrawData.hasEquation(f.id)) {
|
||||
const latex = view.excalidrawData.getEquation(f.id).latex;
|
||||
@@ -1087,7 +1093,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
|
||||
isFullscreen(): boolean {
|
||||
//(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.isFullscreen, "ExcalidrawView.isFullscreen");
|
||||
return Boolean(document.body.querySelector(".excalidraw-hidden"));
|
||||
return Boolean(this.ownerDocument.body.querySelector(".excalidraw-hidden"));
|
||||
}
|
||||
|
||||
exitFullscreen() {
|
||||
@@ -3322,19 +3328,31 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
const isPointerOutsideVisibleArea = top.x>this.currentPosition.x || bottom.x<this.currentPosition.x || top.y>this.currentPosition.y || bottom.y<this.currentPosition.y;
|
||||
|
||||
const id = ea.addText(this.currentPosition.x, this.currentPosition.y, text);
|
||||
await this.addElements(ea.getElements(), isPointerOutsideVisibleArea, save, undefined, true);
|
||||
await this.addElements({
|
||||
newElements: ea.getElements(),
|
||||
repositionToCursor: isPointerOutsideVisibleArea,
|
||||
save: save,
|
||||
newElementsOnTop: true
|
||||
});
|
||||
ea.destroy();
|
||||
return id;
|
||||
};
|
||||
|
||||
public async addElements(
|
||||
newElements: ExcalidrawElement[],
|
||||
repositionToCursor: boolean = false,
|
||||
save: boolean = false,
|
||||
images: any,
|
||||
newElementsOnTop: boolean = false,
|
||||
shouldRestoreElements: boolean = false,
|
||||
): Promise<boolean> {
|
||||
public async addElements({
|
||||
newElements,
|
||||
repositionToCursor = false,
|
||||
save = false,
|
||||
images,
|
||||
newElementsOnTop = false,
|
||||
shouldRestoreElements = false,
|
||||
}: {
|
||||
newElements: ExcalidrawElement[];
|
||||
repositionToCursor?: boolean;
|
||||
save?: boolean;
|
||||
images?: {[key: FileId]: ImageInfo};
|
||||
newElementsOnTop?: boolean;
|
||||
shouldRestoreElements?: boolean;
|
||||
}): Promise<boolean> {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.addElements, "ExcalidrawView.addElements", newElements, repositionToCursor, save, images, newElementsOnTop, shouldRestoreElements);
|
||||
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
if (!api) {
|
||||
@@ -3391,40 +3409,38 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
? el.concat(newElements.filter((e) => !removeList.includes(e.id)))
|
||||
: newElements.filter((e) => !removeList.includes(e.id)).concat(el);
|
||||
|
||||
this.updateScene(
|
||||
{
|
||||
elements,
|
||||
storeAction: "capture",
|
||||
},
|
||||
shouldRestoreElements,
|
||||
);
|
||||
|
||||
if (images && Object.keys(images).length >0) {
|
||||
const files: BinaryFileData[] = [];
|
||||
Object.keys(images).forEach((k) => {
|
||||
const files: BinaryFileData[] = [];
|
||||
if (images && Object.keys(images).length >0) {
|
||||
Object.keys(images).forEach((k: FileId) => {
|
||||
files.push({
|
||||
mimeType: images[k].mimeType,
|
||||
id: images[k].id,
|
||||
dataURL: images[k].dataURL,
|
||||
created: images[k].created,
|
||||
});
|
||||
if (images[k].file || images[k].isHyperLink || images[k].isLocalLink) {
|
||||
if (images[k].file || images[k].isHyperLink) { //|| images[k].isLocalLink but isLocalLink was never passed
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
this.file.path,
|
||||
images[k].isHyperLink && !images[k].isLocalLink
|
||||
images[k].isHyperLink //&& !images[k].isLocalLink local link is never passed to addElements
|
||||
? images[k].hyperlink
|
||||
: images[k].file,
|
||||
: (typeof images[k].file === "string" ? images[k].file : images[k].file.path),
|
||||
);
|
||||
const st: AppState = api.getAppState();
|
||||
embeddedFile.setImage(
|
||||
images[k].dataURL,
|
||||
images[k].mimeType,
|
||||
images[k].size,
|
||||
st.theme === "dark",
|
||||
images[k].hasSVGwithBitmap,
|
||||
);
|
||||
embeddedFile.setImage({
|
||||
imgBase64: images[k].dataURL,
|
||||
mimeType: images[k].mimeType,
|
||||
size: images[k].size,
|
||||
isDark: st.theme === "dark",
|
||||
isSVGwithBitmap: images[k].hasSVGwithBitmap,
|
||||
pdfPageViewProps: images[k].pdfPageViewProps,
|
||||
});
|
||||
this.excalidrawData.setFile(images[k].id, embeddedFile);
|
||||
if(images[k].pdfPageViewProps) {
|
||||
elements.filter((e) => e.type === "image" && e.fileId === images[k].id).forEach((e) => {
|
||||
addAppendUpdateCustomData(e, {pdfPageViewProps: images[k].pdfPageViewProps});
|
||||
});
|
||||
}
|
||||
}
|
||||
if (images[k].latex) {
|
||||
this.excalidrawData.setEquation(images[k].id, {
|
||||
@@ -3433,8 +3449,20 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.updateScene(
|
||||
{
|
||||
elements,
|
||||
storeAction: "capture",
|
||||
},
|
||||
shouldRestoreElements,
|
||||
);
|
||||
|
||||
if(files.length > 0) {
|
||||
api.addFiles(files);
|
||||
}
|
||||
|
||||
api.updateContainerSize(api.getSceneElements().filter(el => newIds.includes(el.id)).filter(isContainer));
|
||||
if (save) {
|
||||
await this.save(false); //preventReload=false will ensure that markdown links are paresed and displayed correctly
|
||||
@@ -3993,7 +4021,9 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
link,
|
||||
naturalHeight: fd.size.height,
|
||||
naturalWidth: fd.size.width,
|
||||
pdfPageViewProps: fd.pdfPageViewProps,
|
||||
});
|
||||
addAppendUpdateCustomData(el, {pdfPageViewProps: fd.pdfPageViewProps});
|
||||
if(el.crop) {
|
||||
el.width = el.crop.width/this.plugin.settings.pdfScale;
|
||||
el.height = el.crop.height/this.plugin.settings.pdfScale;
|
||||
|
||||
Reference in New Issue
Block a user