@8btc/xcanvas 0.0.14-beta.12 → 0.0.14-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +97 -2
- package/dist/index.umd.cjs +97 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@ export interface XCanvasAPI {
|
|
|
20
20
|
insertImgs: (url: string[]) => Promise<void>;
|
|
21
21
|
/** 追加多张图片到画布末尾, 如果画布中存在对应图片(url相同),则不做任何操作 */
|
|
22
22
|
appendImgs: (urls: string[]) => Promise<void>;
|
|
23
|
+
// 插入的图片后缀会自动带上时间戳后缀,用于解决maze跨域问题 `${url}?t=${Date.now()}`
|
|
24
|
+
appendImgsForMaze: (urls: string[]) => Promise<void>;
|
|
23
25
|
/** 保存canvas数据 */
|
|
24
26
|
save: () => any;
|
|
25
27
|
/** 从数据中恢复canvas画布 */
|
|
@@ -39,18 +41,22 @@ export interface XCanvasAPI {
|
|
|
39
41
|
reset: () => void;
|
|
40
42
|
/**
|
|
41
43
|
* 导出当前选区为图片(dataUrl), 特殊情况:当前选区为单张外部传入图片,则直接返回外部图片url
|
|
44
|
+
*
|
|
45
|
+
* @param tryToSplit 是否尝试拆分选区内的图片元素(当选区内全是图片时生效)
|
|
46
|
+
* 如果选区内的元素都是图片,则返回选区内图片url数组
|
|
47
|
+
* 如果选区内有非图片元素,则返回选区区域的dataUrl
|
|
42
48
|
* @returns dataUrl | url
|
|
43
49
|
* @throws 如果没有选区,调用这个函数会抛出错误
|
|
44
50
|
*/
|
|
45
|
-
exportSelection: () => Promise<string>;
|
|
51
|
+
exportSelection: (tryToSplit?: boolean) => Promise<string | string[]>;
|
|
46
52
|
/**
|
|
47
|
-
*
|
|
53
|
+
* 在当前旁边插入embeddableElement元素
|
|
48
54
|
*
|
|
49
55
|
* @param data 调用 renderEmbeddable 时, 可以从 element.data 访问到这里传入的数据
|
|
50
56
|
* @param positonCalculator 入参为当前选区左上角x,y 和宽高,返回值为插入元素的左上角x,y,w,h
|
|
51
57
|
* @returns 如果成功插入返回元素id,否则返回undefined
|
|
52
58
|
*/
|
|
53
|
-
|
|
59
|
+
insertCustomElementBesideSelection: (
|
|
54
60
|
data: {
|
|
55
61
|
component: string,
|
|
56
62
|
link?: string,
|
|
@@ -70,5 +76,19 @@ export interface XCanvasAPI {
|
|
|
70
76
|
height: number,
|
|
71
77
|
}
|
|
72
78
|
) => string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* 判断当前是否可以裁切图片
|
|
81
|
+
*/
|
|
82
|
+
predicateCropImage: () => boolean;
|
|
83
|
+
/**
|
|
84
|
+
* 裁切图片
|
|
85
|
+
*/
|
|
86
|
+
cropImage: () => void;
|
|
87
|
+
/**
|
|
88
|
+
* 删除选区里的全部元素
|
|
89
|
+
*/
|
|
90
|
+
deleteSelection: () => void;
|
|
91
|
+
/** 插入多个视频 */
|
|
92
|
+
insertVideos: (urls: string[]) => Promise<void>;
|
|
73
93
|
}
|
|
74
94
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -42,13 +42,13 @@ export declare interface XCanvasAPI {
|
|
|
42
42
|
*/
|
|
43
43
|
exportSelection: (tryToSplit?: boolean) => Promise<string | string[]>;
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
45
|
+
* 在当前旁边插入embeddableElement元素
|
|
46
46
|
*
|
|
47
47
|
* @param data 调用 renderEmbeddable 时, 可以从 element.data 访问到这里传入的数据
|
|
48
48
|
* @param positonCalculator 入参为当前选区左上角x,y 和宽高,返回值为插入元素的左上角x,y,w,h
|
|
49
49
|
* @returns 如果成功插入返回元素id,否则返回undefined
|
|
50
50
|
*/
|
|
51
|
-
|
|
51
|
+
insertCustomElementBesideSelection: (data: {
|
|
52
52
|
component: string;
|
|
53
53
|
link?: string;
|
|
54
54
|
[key: string]: any;
|
|
@@ -75,6 +75,8 @@ export declare interface XCanvasAPI {
|
|
|
75
75
|
* 删除选区里的全部元素
|
|
76
76
|
*/
|
|
77
77
|
deleteSelection: () => void;
|
|
78
|
+
/** 插入一个视频 */
|
|
79
|
+
insertVideo: (urls: string) => Promise<void>;
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
export declare interface XcanvasProps extends ExcalidrawProps {
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
|
|
3
3
|
import { Excalidraw, convertToExcalidrawElements, exportToCanvas } from "@8btc/excalidraw";
|
|
4
4
|
import { useEffect } from "react";
|
|
5
|
-
const version = "0.0.14-beta.
|
|
5
|
+
const version = "0.0.14-beta.14";
|
|
6
6
|
const packageJson = {
|
|
7
7
|
version
|
|
8
8
|
};
|
|
@@ -100,6 +100,26 @@ function findPlaceForNewElements(appState, elements, newElements) {
|
|
|
100
100
|
});
|
|
101
101
|
return ret;
|
|
102
102
|
}
|
|
103
|
+
function getVideoDimensions(videoUrl) {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const video = document.createElement("video");
|
|
106
|
+
video.src = videoUrl;
|
|
107
|
+
video.crossOrigin = "anonymous";
|
|
108
|
+
video.onloadedmetadata = function() {
|
|
109
|
+
const width = video.videoWidth;
|
|
110
|
+
const height = video.videoHeight;
|
|
111
|
+
video.src = "";
|
|
112
|
+
video.remove();
|
|
113
|
+
resolve({ width, height });
|
|
114
|
+
};
|
|
115
|
+
video.onerror = function(err) {
|
|
116
|
+
video.src = "";
|
|
117
|
+
video.remove();
|
|
118
|
+
reject(new Error(`视频加载失败:${err}`));
|
|
119
|
+
};
|
|
120
|
+
video.load();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
103
123
|
function ApiFactory() {
|
|
104
124
|
const excalidrawAPI = useAtomValue(excalidrawApiAtom);
|
|
105
125
|
const setApi = useSetAtom(apiAtom);
|
|
@@ -368,7 +388,7 @@ function ApiFactory() {
|
|
|
368
388
|
const base64 = canvas.toDataURL("image/png", 1);
|
|
369
389
|
return base64;
|
|
370
390
|
},
|
|
371
|
-
|
|
391
|
+
insertCustomElementBesideSelection: (data, positonCalculator) => {
|
|
372
392
|
if (!excalidrawAPI) return;
|
|
373
393
|
const appState = excalidrawAPI.getAppState();
|
|
374
394
|
const selectedElements = getSelectedElements(
|
|
@@ -480,6 +500,81 @@ function ApiFactory() {
|
|
|
480
500
|
selectedElementIds: {}
|
|
481
501
|
}
|
|
482
502
|
});
|
|
503
|
+
},
|
|
504
|
+
insertVideo: async (url) => {
|
|
505
|
+
const currentElements = excalidrawAPI.getSceneElements();
|
|
506
|
+
const dimension = await getVideoDimensions(url);
|
|
507
|
+
const rets = [
|
|
508
|
+
{
|
|
509
|
+
...dimension,
|
|
510
|
+
url
|
|
511
|
+
}
|
|
512
|
+
];
|
|
513
|
+
const existElements = currentElements.filter(
|
|
514
|
+
(el) => el.type === "embeddable" && el.customData?.component === "video" && el.customData?.url === url
|
|
515
|
+
);
|
|
516
|
+
if (existElements.length > 0) {
|
|
517
|
+
excalidrawAPI.scrollToContent(existElements, {
|
|
518
|
+
animate: true
|
|
519
|
+
});
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const places = findPlaceForNewElements(
|
|
523
|
+
excalidrawAPI.getAppState(),
|
|
524
|
+
excalidrawAPI.getSceneElements(),
|
|
525
|
+
rets
|
|
526
|
+
);
|
|
527
|
+
const els = convertToExcalidrawElements(
|
|
528
|
+
rets.map((it, index) => {
|
|
529
|
+
return {
|
|
530
|
+
type: "embeddable",
|
|
531
|
+
width: it.width,
|
|
532
|
+
height: it.height,
|
|
533
|
+
x: places[index].x,
|
|
534
|
+
y: places[index].y,
|
|
535
|
+
// 添加必需的基本样式属性
|
|
536
|
+
strokeColor: "#ffffff00",
|
|
537
|
+
backgroundColor: "transparent",
|
|
538
|
+
fillStyle: "solid",
|
|
539
|
+
strokeWidth: 2,
|
|
540
|
+
strokeStyle: "solid",
|
|
541
|
+
roundness: null,
|
|
542
|
+
roughness: 0,
|
|
543
|
+
opacity: 100,
|
|
544
|
+
id: `video-${Date.now()}`,
|
|
545
|
+
link: it.url ?? "https://www.google.com",
|
|
546
|
+
// 添加必需的变换属性
|
|
547
|
+
angle: 0,
|
|
548
|
+
seed: Math.random(),
|
|
549
|
+
version: 1,
|
|
550
|
+
versionNonce: Math.random(),
|
|
551
|
+
// 添加必需的状态属性
|
|
552
|
+
locked: false,
|
|
553
|
+
isDeleted: false,
|
|
554
|
+
groupIds: [],
|
|
555
|
+
// 添加绑定框属性
|
|
556
|
+
boundElements: [],
|
|
557
|
+
updated: Date.now(),
|
|
558
|
+
// 添加必需的索引和帧ID属性
|
|
559
|
+
frameId: null,
|
|
560
|
+
index: null,
|
|
561
|
+
// 添加缺失的index属性
|
|
562
|
+
customData: {
|
|
563
|
+
custom: true,
|
|
564
|
+
component: "video",
|
|
565
|
+
naturalWidth: it.width,
|
|
566
|
+
naturalHeight: it.height,
|
|
567
|
+
url: it.url
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
})
|
|
571
|
+
);
|
|
572
|
+
excalidrawAPI.updateScene({
|
|
573
|
+
elements: [...currentElements, ...els]
|
|
574
|
+
});
|
|
575
|
+
excalidrawAPI.scrollToContent(els, {
|
|
576
|
+
animate: currentElements.length === 0 ? false : true
|
|
577
|
+
});
|
|
483
578
|
}
|
|
484
579
|
});
|
|
485
580
|
}, [excalidrawAPI, setApi]);
|
package/dist/index.umd.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react/jsx-runtime"), require("jotai"), require("@8btc/excalidraw"), require("react")) : typeof define === "function" && define.amd ? define(["exports", "react/jsx-runtime", "jotai", "@8btc/excalidraw", "react"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.XCanvas = {}, global.jsxRuntime, global.Jotai, global.Excalidraw, global.React));
|
|
3
3
|
})(this, (function(exports2, jsxRuntime, jotai, excalidraw, react) {
|
|
4
4
|
"use strict";
|
|
5
|
-
const version = "0.0.14-beta.
|
|
5
|
+
const version = "0.0.14-beta.14";
|
|
6
6
|
const packageJson = {
|
|
7
7
|
version
|
|
8
8
|
};
|
|
@@ -100,6 +100,26 @@
|
|
|
100
100
|
});
|
|
101
101
|
return ret;
|
|
102
102
|
}
|
|
103
|
+
function getVideoDimensions(videoUrl) {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const video = document.createElement("video");
|
|
106
|
+
video.src = videoUrl;
|
|
107
|
+
video.crossOrigin = "anonymous";
|
|
108
|
+
video.onloadedmetadata = function() {
|
|
109
|
+
const width = video.videoWidth;
|
|
110
|
+
const height = video.videoHeight;
|
|
111
|
+
video.src = "";
|
|
112
|
+
video.remove();
|
|
113
|
+
resolve({ width, height });
|
|
114
|
+
};
|
|
115
|
+
video.onerror = function(err) {
|
|
116
|
+
video.src = "";
|
|
117
|
+
video.remove();
|
|
118
|
+
reject(new Error(`视频加载失败:${err}`));
|
|
119
|
+
};
|
|
120
|
+
video.load();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
103
123
|
function ApiFactory() {
|
|
104
124
|
const excalidrawAPI = jotai.useAtomValue(excalidrawApiAtom);
|
|
105
125
|
const setApi = jotai.useSetAtom(apiAtom);
|
|
@@ -368,7 +388,7 @@
|
|
|
368
388
|
const base64 = canvas.toDataURL("image/png", 1);
|
|
369
389
|
return base64;
|
|
370
390
|
},
|
|
371
|
-
|
|
391
|
+
insertCustomElementBesideSelection: (data, positonCalculator) => {
|
|
372
392
|
if (!excalidrawAPI) return;
|
|
373
393
|
const appState = excalidrawAPI.getAppState();
|
|
374
394
|
const selectedElements = getSelectedElements(
|
|
@@ -480,6 +500,81 @@
|
|
|
480
500
|
selectedElementIds: {}
|
|
481
501
|
}
|
|
482
502
|
});
|
|
503
|
+
},
|
|
504
|
+
insertVideo: async (url) => {
|
|
505
|
+
const currentElements = excalidrawAPI.getSceneElements();
|
|
506
|
+
const dimension = await getVideoDimensions(url);
|
|
507
|
+
const rets = [
|
|
508
|
+
{
|
|
509
|
+
...dimension,
|
|
510
|
+
url
|
|
511
|
+
}
|
|
512
|
+
];
|
|
513
|
+
const existElements = currentElements.filter(
|
|
514
|
+
(el) => el.type === "embeddable" && el.customData?.component === "video" && el.customData?.url === url
|
|
515
|
+
);
|
|
516
|
+
if (existElements.length > 0) {
|
|
517
|
+
excalidrawAPI.scrollToContent(existElements, {
|
|
518
|
+
animate: true
|
|
519
|
+
});
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const places = findPlaceForNewElements(
|
|
523
|
+
excalidrawAPI.getAppState(),
|
|
524
|
+
excalidrawAPI.getSceneElements(),
|
|
525
|
+
rets
|
|
526
|
+
);
|
|
527
|
+
const els = excalidraw.convertToExcalidrawElements(
|
|
528
|
+
rets.map((it, index) => {
|
|
529
|
+
return {
|
|
530
|
+
type: "embeddable",
|
|
531
|
+
width: it.width,
|
|
532
|
+
height: it.height,
|
|
533
|
+
x: places[index].x,
|
|
534
|
+
y: places[index].y,
|
|
535
|
+
// 添加必需的基本样式属性
|
|
536
|
+
strokeColor: "#ffffff00",
|
|
537
|
+
backgroundColor: "transparent",
|
|
538
|
+
fillStyle: "solid",
|
|
539
|
+
strokeWidth: 2,
|
|
540
|
+
strokeStyle: "solid",
|
|
541
|
+
roundness: null,
|
|
542
|
+
roughness: 0,
|
|
543
|
+
opacity: 100,
|
|
544
|
+
id: `video-${Date.now()}`,
|
|
545
|
+
link: it.url ?? "https://www.google.com",
|
|
546
|
+
// 添加必需的变换属性
|
|
547
|
+
angle: 0,
|
|
548
|
+
seed: Math.random(),
|
|
549
|
+
version: 1,
|
|
550
|
+
versionNonce: Math.random(),
|
|
551
|
+
// 添加必需的状态属性
|
|
552
|
+
locked: false,
|
|
553
|
+
isDeleted: false,
|
|
554
|
+
groupIds: [],
|
|
555
|
+
// 添加绑定框属性
|
|
556
|
+
boundElements: [],
|
|
557
|
+
updated: Date.now(),
|
|
558
|
+
// 添加必需的索引和帧ID属性
|
|
559
|
+
frameId: null,
|
|
560
|
+
index: null,
|
|
561
|
+
// 添加缺失的index属性
|
|
562
|
+
customData: {
|
|
563
|
+
custom: true,
|
|
564
|
+
component: "video",
|
|
565
|
+
naturalWidth: it.width,
|
|
566
|
+
naturalHeight: it.height,
|
|
567
|
+
url: it.url
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
})
|
|
571
|
+
);
|
|
572
|
+
excalidrawAPI.updateScene({
|
|
573
|
+
elements: [...currentElements, ...els]
|
|
574
|
+
});
|
|
575
|
+
excalidrawAPI.scrollToContent(els, {
|
|
576
|
+
animate: currentElements.length === 0 ? false : true
|
|
577
|
+
});
|
|
483
578
|
}
|
|
484
579
|
});
|
|
485
580
|
}, [excalidrawAPI, setApi]);
|