ExcalidrawAutomate blob,line,scale

This commit is contained in:
Zsolt Viczian
2021-09-14 22:38:03 +02:00
parent ec3e9fd5b7
commit 3e2d85dd09
2 changed files with 151 additions and 60 deletions

View File

@@ -59,7 +59,8 @@ export interface ExcalidrawAutomate extends Window {
addRect(topX:number, topY:number, width:number, height:number):string;
addDiamond(topX:number, topY:number, width:number, height:number):string;
addEllipse(topX:number, topY:number, width:number, height:number):string;
addText(topX:number, topY:number, text:string, formatting?:{width?:number, height?:number,textAlign?: string, verticalAlign?:string, box?: boolean, boxPadding?: number},id?:string):string;
addBlob(topX:number, topY:number, width:number, height:number):string;
addText(topX:number, topY:number, text:string, formatting?:{wrapAt?:number, width?:number, height?:number,textAlign?: string, verticalAlign?:string, box?: boolean, boxPadding?: number},id?:string):string;
addLine(points: [[x:number,y:number]]):string;
addArrow(points: [[x:number,y:number]],formatting?:{startArrowHead?:string,endArrowHead?:string,startObjectId?:string,endObjectId?:string}):string ;
connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints?: number,startArrowHead?:string,endArrowHead?:string, padding?: number}):void;
@@ -83,7 +84,6 @@ declare let window: ExcalidrawAutomate;
export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
window.ExcalidrawAutomate = {
plugin: plugin,
//elementIds: [],
elementsDict: {},
style: {
strokeColor: "#000000",
@@ -274,28 +274,99 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
},
addRect(topX:number, topY:number, width:number, height:number):string {
const id = nanoid();
//this.elementIds.push(id);
this.elementsDict[id] = boxedElement(id,"rectangle",topX,topY,width,height);
return id;
},
addDiamond(topX:number, topY:number, width:number, height:number):string {
const id = nanoid();
//this.elementIds.push(id);
this.elementsDict[id] = boxedElement(id,"diamond",topX,topY,width,height);
return id;
},
addEllipse(topX:number, topY:number, width:number, height:number):string {
const id = nanoid();
//this.elementIds.push(id);
this.elementsDict[id] = boxedElement(id,"ellipse",topX,topY,width,height);
return id;
},
addText(topX:number, topY:number, text:string, formatting?:{width?:number, height?:number,textAlign?: string, verticalAlign?:string, box?: boolean, boxPadding?: number},id?:string):string {
if(!id) id = nanoid();
},
addBlob(topX:number, topY:number, width:number, height:number):string {
const b = height*0.5; //minor axis of the ellipsis
const a = width*0.5; //major axis of the ellipsis
const sx = a/9;
const sy = b*0.8;
const step = 6;
let p:any = [];
const pushPoint = (i:number,dir:number) => {
const x = i + Math.random()*sx-sx/2;
p.push([x+Math.random()*sx-sx/2+(i%2)*sx/6+topX,dir*Math.sqrt(b*b*(1-(x*x)/(a*a)))+Math.random()*sy-sy/2+(i%2)*sy/6+topY]);
}
let i:number;
for (i=-a+sx/2;i<=a-sx/2;i+=a/step) {
pushPoint(i,1);
}
for(i=a-sx/2;i>=-a+sx/2;i-=a/step) {
pushPoint(i,-1);
}
p.push(p[0]);
const id=this.addLine(p);
const scale = (element:ExcalidrawElement):ExcalidrawElement => {
if(!(element.type=="line" || element.type=="arrow")) return null;
const scaleX = width/element.width;
const scaleY = height/element.height;
let i;
for(i=0;i<element.points.length;i++) {
let [x,y] = element.points[i];
x = (x-element.x)*scaleX+element.x;
y = (y-element.y)*scaleY+element.y;
//@ts-ignore
element.points[i]=[x,y]
}
//@ts-ignore
element.width=width;
//@ts-ignore
element.heigth=height;
return element;
}
this.elementsDict[id]=repositionElementsToCursor([scale(this.getElement(id))],{x:topX,y:topY},false)[0];
return id;
},
addText(
topX:number,
topY:number,
text:string,
formatting?:{
wrapAt?:number,
width?:number,
height?:number,
textAlign?:string,
verticalAlign?:string,
box?: boolean|"box"|"blob"|"ellipse"|"diamond",
boxPadding?:number
},
id?:string
):string {
id = id ?? nanoid();
text = (formatting?.wrapAt) ? this.wrapText(text,formatting.wrapAt):text;
const {w, h, baseline} = measureText(text, this.style.fontSize,this.style.fontFamily);
const width = formatting?.width ? formatting.width : w;
const height = formatting?.height ? formatting.height : h;
//this.elementIds.push(id);
let boxId:string = null;
const boxPadding = formatting?.boxPadding ?? 10;
if(formatting?.box) {
switch(formatting?.box) {
case true || "box":
boxId = this.addRect(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding);
break;
case "ellipse":
boxId = this.addEllipse(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding);
break;
case "diamond":
boxId = this.addDiamond(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding);
break;
case "blob":
boxId = this.addBlob(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding);
break;
}
}
this.elementsDict[id] = {
text: text,
fontSize: window.ExcalidrawAutomate.style.fontSize,
@@ -305,20 +376,15 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
baseline: baseline,
... boxedElement(id,"text",topX,topY,width,height)
};
if(formatting?.box) {
const boxPadding = formatting?.boxPadding!=null ? formatting.boxPadding : 10;
const boxId = this.addRect(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding);
this.addToGroup([id,boxId])
return boxId;
}
return id;
if (boxId) this.addToGroup([id,boxId])
return boxId ?? id;
},
addLine(points: [[x:number,y:number]]):string {
const box = getLineBox(points);
const id = nanoid();
//this.elementIds.push(id);
this.elementsDict[id] = {
points: normalizeLinePoints(points),
points: normalizeLinePoints(points,box),
lastCommittedPoint: null,
startBinding: null,
endBinding: null,
@@ -333,7 +399,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
const id = nanoid();
//this.elementIds.push(id);
this.elementsDict[id] = {
points: normalizeLinePoints(points),
points: normalizeLinePoints(points,box),
lastCommittedPoint: null,
startBinding: {elementId:formatting?.startObjectId,focus:0.1,gap:4},
endBinding: {elementId:formatting?.endObjectId,focus:0.1,gap:4},
@@ -383,7 +449,6 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
});
},
clear() {
//this.elementIds = [];
this.elementsDict = {};
},
reset() {
@@ -500,10 +565,10 @@ export function destroyExcalidrawAutomate() {
delete window.ExcalidrawAutomate;
}
function normalizeLinePoints(points:[[x:number,y:number]]) {
function normalizeLinePoints(points:[[x:number,y:number]],box:{x:number,y:number,w:number,h:number}) {
let p = [];
for(let i=0;i<points.length;i++) {
p.push([points[i][0]-points[0][0], points[i][1]-points[0][1]]);
p.push([points[i][0]-box.x, points[i][1]-box.y]);
}
return p;
}
@@ -535,11 +600,12 @@ function boxedElement(id:string,eltype:any,x:number,y:number,w:number,h:number)
}
function getLineBox(points: [[x:number,y:number]]) {
const [x1,y1,x2,y2] = estimateLineBound(points);
return {
x: points[0][0],
y: points[0][1],
w: Math.abs(points[points.length-1][0]-points[0][0]),
h: Math.abs(points[points.length-1][1]-points[0][1])
x: x1,
y: y1,
w: x2-x1, //Math.abs(points[points.length-1][0]-points[0][0]),
h: y2-y1 //Math.abs(points[points.length-1][1]-points[0][1])
}
}
@@ -604,6 +670,62 @@ async function getTemplate(fileWithPath: string):Promise<{elements: any,appState
}
}
function estimateLineBound(points:any):[number,number,number,number] {
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
points.forEach((p:any) => {
const [x,y] = p;
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
});
return[minX,minY,maxX,maxY];
}
export function estimateElementBounds (element:ExcalidrawElement):[number,number,number,number] {
if(element.type=="line" || element.type=="arrow") {
const [minX,minY,maxX,maxY] = estimateLineBound(element.points);
return [minX+element.x,minY+element.y,maxX+element.x,maxY+element.y];
}
return[element.x,element.y,element.x+element.width,element.y+element.height];
}
export function estimateBounds (elements:ExcalidrawElement[]):[number,number,number,number] {
if(!elements.length) return [0,0,0,0];
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
elements.forEach((element)=>{
const [x1,y1,x2,y2] = estimateElementBounds(element);
minX = Math.min(minX, x1);
minY = Math.min(minY, y1);
maxX = Math.max(maxX, x2);
maxY = Math.max(maxY, y2);
});
return [minX,minY,maxX,maxY];
}
export function repositionElementsToCursor (elements:ExcalidrawElement[],newPosition:{x:number, y:number},center:boolean=false):ExcalidrawElement[] {
const [x1,y1,x2,y2] = estimateBounds(elements);
let [offsetX,offsetY] = [0,0];
if (center) {
[offsetX,offsetY] = [newPosition.x-(x1+x2)/2,newPosition.y-(y1+y2)/2];
} else {
[offsetX,offsetY] = [newPosition.x-x1,newPosition.y-y1];
}
elements.forEach((element:any)=>{ //using any so I can write read-only propery x & y
element.x=element.x+offsetX;
element.y=element.y+offsetY;
});
return elements;
}
function errorMessage(message: string, source: string) {
switch(message) {
case "targetView not set":

View File

@@ -31,7 +31,7 @@ import {
JSON_parse
} from './constants';
import ExcalidrawPlugin from './main';
import {ExcalidrawAutomate} from './ExcalidrawAutomate';
import {estimateBounds, ExcalidrawAutomate, repositionElementsToCursor} from './ExcalidrawAutomate';
import { t } from "./lang/helpers";
import { ExcalidrawData, REG_LINKINDEX_HYPERLINK, REGEX_LINK } from "./ExcalidrawData";
import { checkAndCreateFolder, download, getNewUniqueFilepath, splitFolderAndFilename, viewportCoordsToSceneCoords } from "./Utils";
@@ -604,39 +604,8 @@ export default class ExcalidrawView extends TextFileView {
}
this.addElements = async (newElements:ExcalidrawElement[],repositionToCursor:boolean = false, save:boolean=false):Promise<boolean> => {
if(!excalidrawRef?.current) return false;
const estimateElementBounds = (element:ExcalidrawElement):[number,number,number,number] => {
return[element.x,element.y,element.x+element.width,element.y+element.height];
}
const estimateBounds = (elements:ExcalidrawElement[]):[number,number,number,number] => {
if(!elements.length) return [0,0,0,0];
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
elements.forEach((element)=>{
const [x1,y1,x2,y2] = estimateElementBounds(element);
minX = Math.min(minX, x1);
minY = Math.min(minY, y1);
maxX = Math.max(maxX, x2);
maxY = Math.max(maxY, y2);
});
return [minX,minY,maxX,maxY];
}
const repositionElementsToCursor = (elements:ExcalidrawElement[]):ExcalidrawElement[] => {
const [x1,y1,x2,y2] = estimateBounds(elements);
const [offsetX,offsetY] = [currentPosition.x-(x1+x2)/2,currentPosition.y-(y1+y2)/2]
elements.forEach((element:any)=>{ //using any so I can write read-only propery x & y
element.x=element.x+offsetX;
element.y=element.y+offsetY;
});
return elements;
}
if(!excalidrawRef?.current) return false;
const textElements = newElements.filter((el)=>el.type=="text");
for(let i=0;i<textElements.length;i++) {
//@ts-ignore
@@ -648,7 +617,7 @@ export default class ExcalidrawView extends TextFileView {
const el: ExcalidrawElement[] = excalidrawRef.current.getSceneElements();
const st: AppState = excalidrawRef.current.getAppState();
if(repositionToCursor) newElements = repositionElementsToCursor(newElements);
if(repositionToCursor) newElements = repositionElementsToCursor(newElements,currentPosition,true);
this.excalidrawRef.current.updateScene({
elements: el.concat(newElements),
appState: st,