canvas-select-vite
Version:
一个用于图片标注的 javascript 库,基于 canvas,简单轻量,支持矩形、多边形、点、折线、圆形标注、网格标注。
1,064 lines (1,063 loc) • 45.1 kB
JavaScript
var P = Object.defineProperty;
var N = (v, g, t) => g in v ? P(v, g, { enumerable: !0, configurable: !0, writable: !0, value: t }) : v[g] = t;
var h = (v, g, t) => N(v, typeof g != "symbol" ? g + "" : g, t);
function O() {
const v = [], g = "0123456789abcdef";
for (let e = 0; e < 36; e++) {
const i = Math.floor(Math.random() * 16);
v[e] = g.slice(i, i + 1);
}
v[14] = "4";
const t = v[19] & 3 | 8;
return v[19] = g.slice(t, t + 1), v[8] = v[13] = v[18] = v[23] = "-", v.join("");
}
function X(v, g) {
if (v.type === 1 && g.type === 1) {
const [[t, e], [i, s]] = v.coor, [[a, o], [c, r]] = g.coor;
return t <= a && e <= o && i >= c && s >= r;
} else if (v.type === 1 && g.type === 2) {
const [[t, e], [i, s]] = v.coor, a = g.coor;
for (let o = 0; o < a.length; o++) {
const [c, r] = a[o];
if (c < t || c > i || r < e || r > s)
return !1;
}
return !0;
} else if (v.type === 2 && g.type === 1) {
const t = g.coor;
for (let e = 0; e < t.length; e++) {
const [i, s] = t[e];
if (!w(i, s, v.coor))
return !1;
}
return !0;
} else if (v.type === 2 && g.type === 2) {
const t = v.coor, e = g.coor;
for (let i = 0; i < e.length; i++) {
const [s, a] = e[i];
if (!w(s, a, t))
return !1;
}
return !0;
}
return !1;
}
function w(v, g, t) {
let e = !1;
const i = t.length;
for (let s = 0, a = i - 1; s < i; a = s++) {
const o = t[s][0], c = t[s][1], r = t[a][0], l = t[a][1];
c > g != l > g && v < (r - o) * (g - c) / (l - c) + o && (e = !e);
}
return e;
}
class E {
constructor(g, t) {
/** 标签 */
h(this, "label", "");
/** 是否隐藏标签 */
//@ts-ignore
h(this, "hideLabel");
/** 坐标 */
h(this, "coor", []);
/** 边线颜色 */
//@ts-ignore
h(this, "strokeStyle");
/** 填充颜色 */
//@ts-ignore
h(this, "fillStyle");
/** 边线宽度 */
//@ts-ignore
h(this, "lineWidth");
/** 标签填充颜色 */
//@ts-ignore
h(this, "labelFillStyle");
/** 标签文字颜色 */
//@ts-ignore
h(this, "textFillStyle");
/** 标签文字字体 */
//@ts-ignore
h(this, "labelFont");
/** 1 矩形,2 多边形,3 点,4 折线,5 圆,6 网格 */
//@ts-ignore
h(this, "type");
// 形状
/** 当前是否处于活动状态 */
h(this, "active", !1);
/** 当前是否处于创建状态 */
h(this, "creating", !1);
/** 当前是否处于拖拽状态 */
h(this, "dragging", !1);
/** 索引 */
h(this, "index");
/** 唯一标识 */
h(this, "uuid", O());
/** 向上展示label */
//@ts-ignore
h(this, "labelUp");
/** 隐藏标注 */
//@ts-ignore
h(this, "hide");
this.index = t, Object.assign(this, g);
}
}
class T extends E {
constructor(t, e) {
super(t, e);
h(this, "type", 1);
}
get ctrlsData() {
const [[t, e], [i, s]] = this.coor;
return [
[t, e],
[t + (i - t) / 2, e],
[i, e],
[i, e + (s - e) / 2],
[i, s],
[t + (i - t) / 2, s],
[t, s],
[t, e + (s - e) / 2]
];
}
}
class C extends E {
constructor(t, e) {
super(t, e);
h(this, "type", 2);
}
get ctrlsData() {
return this.coor.length > 2 ? this.coor : [];
}
}
class D extends E {
constructor(t, e) {
super(t, e);
h(this, "type", 3);
}
}
class Y {
constructor() {
h(this, "_eventTree", {});
}
/**
* 注册事件
* @param eventName 事件名称
* @param cb 回调方法
*/
on(g, t) {
const e = this._eventTree[g];
Array.isArray(e) ? e.push(t) : this._eventTree[g] = [t];
}
/**
* 触发事件
* @param eventName 事件名称
* @param payload 传递参数
*/
emit(g, ...t) {
const e = this._eventTree[g];
Array.isArray(e) && e.forEach((i) => i(...t));
}
/**
* 注销事件
* @param eventName 事件名称
* @param cb 传递参数
*/
off(g, t) {
const e = this._eventTree[g], i = e.find((s) => s === t);
Array.isArray(e) && i && e.splice(i, 1);
}
}
class k extends E {
constructor(t, e) {
super(t, e);
h(this, "type", 4);
}
get ctrlsData() {
return this.coor.length > 1 ? this.coor : [];
}
}
class _ extends E {
constructor(t, e) {
super(t, e);
h(this, "type", 5);
h(this, "radius", 0);
this.radius = t.radius || this.radius;
}
get ctrlsData() {
const [t, e] = this.coor;
return [
[t, e - this.radius],
[t + this.radius, e],
[t, e + this.radius],
[t - this.radius, e]
];
}
}
class W extends E {
constructor(t, e) {
super(t, e);
h(this, "type", 6);
h(this, "row", 1);
h(this, "col", 1);
h(this, "selected", []);
h(this, "selectedFillStyle");
this.row = t.row > 0 ? t.row : this.row, this.col = t.col > 0 ? t.col : this.col, this.selected = Array.isArray(t.selected) ? t.selected : [];
}
get ctrlsData() {
const [[t, e], [i, s]] = this.coor;
return [
[t, e],
[t + (i - t) / 2, e],
[i, e],
[i, e + (s - e) / 2],
[i, s],
[t + (i - t) / 2, s],
[t, s],
[t, e + (s - e) / 2]
];
}
get gridRects() {
const [[t, e], [i, s]] = this.coor, { row: a, col: o, strokeStyle: c, fillStyle: r, active: l, creating: I, lineWidth: p } = this, n = (i - t) / this.col, d = (s - e) / this.row, u = [];
for (let f = 0; f < a; f++)
for (let y = 0; y < o; y++) {
const S = [t + y * n, e + f * d], x = f * o + y, m = new T({
coor: [S, [S[0] + n, S[1] + d]],
strokeStyle: c,
fillStyle: r,
active: l,
creating: I,
lineWidth: p
}, x);
u.push(m);
}
return u;
}
}
const F = "canvas-select-vite", z = "0.0.1", U = "module", j = ["dist"], K = "./dist/canvas-select-vite.umd.js", $ = "./dist/canvas-select-vite.es.js", q = "./dist/src/index.d.ts", B = { types: "./dist/src/index.d.ts", import: "./dist/canvas-select-vite.es.js", require: "./dist/canvas-select-vite.umd.js" }, V = { dev: "vite", build: "tsc && vite build" }, Z = { typescript: "~5.7.2", vite: "^6.0.1" }, J = { "vite-plugin-dts": "^4.3.0" }, Q = ["canvas", "marker", "annotation", "rect", "polygon", "dot", "line", "circle", "grid", "标注", "矩形", "多边形", "点", "折线", "圆", "网格"], tt = "heylight", et = "MIT", it = "https://github.com/heylight/canvas-select", st = { type: "git", url: "git+https://github.com/heylight/canvas-select.git" }, ht = {
name: F,
version: z,
type: U,
files: j,
main: K,
module: $,
types: q,
exports: B,
scripts: V,
devDependencies: Z,
dependencies: J,
keywords: Q,
author: tt,
license: et,
homepage: it,
repository: st
};
class nt extends Y {
/**
* @param el Valid CSS selector string, or DOM
* @param src image src
*/
constructor(t, e) {
super();
/** 当前版本 */
h(this, "version", ht.version);
/** 只读模式,画布不允许任何交互 */
h(this, "lock", !1);
/** 只读模式,仅支持查看 */
h(this, "readonly", !1);
/** 最小矩形宽度 */
h(this, "MIN_WIDTH", 10);
/** 最小矩形高度 */
h(this, "MIN_HEIGHT", 10);
/** 最小圆形半径 */
h(this, "MIN_RADIUS", 5);
/** 边线颜色 */
h(this, "strokeStyle", "#0f0");
/** 填充颜色 */
h(this, "fillStyle", "rgba(0, 0, 255, 0.1)");
/** 边线宽度 */
h(this, "lineWidth", 1);
/** 当前选中的标注边线颜色 */
h(this, "activeStrokeStyle", "#f00");
/** 当前选中的标注填充颜色 */
h(this, "activeFillStyle", "rgba(255, 0, 0, 0.1)");
/** 控制点边线颜色 */
h(this, "ctrlStrokeStyle", "#000");
/** 控制点填充颜色 */
h(this, "ctrlFillStyle", "#fff");
/** 控制点半径 */
h(this, "ctrlRadius", 3);
/** 是否隐藏标签 */
h(this, "hideLabel", !1);
/** 标签背景填充颜色 */
h(this, "labelFillStyle", "#fff");
/** 标签字体 */
h(this, "labelFont", "10px sans-serif");
/** 标签文字颜色 */
h(this, "textFillStyle", "#000");
/** 标签字符最大长度,超出使用省略号 */
h(this, "labelMaxLen", 10);
/** 画布宽度 */
h(this, "WIDTH", 0);
/** 画布高度 */
h(this, "HEIGHT", 0);
h(this, "canvas");
h(this, "ctx");
/** 所有标注数据 */
h(this, "dataset", []);
h(this, "offScreen");
h(this, "offScreenCtx");
// 放大镜相关配置 Start
h(this, "magnifierCanvas");
h(this, "magnifierCtx");
// 默认展示放大镜
h(this, "isMagnifierVisible", !0);
// 放大镜位置,默认跟随鼠标
h(this, "magnifierPosition", "auto");
// 放大镜相关配置 End
/** 记录锚点距离 */
h(this, "remmber");
/** 记录鼠标位置 */
h(this, "mouse");
/** 记录背景图鼠标位移 */
h(this, "remmberOrigin", [0, 0]);
/** 0 不创建,1 矩形,2 多边形,3 点,4 折线,5 圆,6 网格 */
h(this, "createType", 0);
//
/** 控制点索引 */
h(this, "ctrlIndex", -1);
/** 背景图片 */
h(this, "image", new Image());
/** 图片原始宽度 */
h(this, "IMAGE_ORIGIN_WIDTH");
/** 图片缩放宽度 */
h(this, "IMAGE_WIDTH", 0);
/** 图片原始高度 */
h(this, "IMAGE_ORIGIN_HEIGHT", 0);
/** 图片缩放高度 */
h(this, "IMAGE_HEIGHT", 0);
/** 原点x */
h(this, "originX", 0);
/** 原点y */
h(this, "originY", 0);
/** 缩放步长 */
h(this, "scaleStep", 0);
/** 滚动缩放 */
h(this, "scrollZoom", !0);
h(this, "timer");
/** 最小touch双击时间 */
h(this, "dblTouch", 300);
/** 记录touch双击开始时间 */
h(this, "dblTouchStore", 0);
//
/** 这个选项可以帮助浏览器进行内部优化 */
h(this, "alpha", !0);
/** 专注模式 */
h(this, "focusMode", !1);
/** 记录当前事件 */
h(this, "evt");
/** 触控缩放时记录上一次两点距离 */
h(this, "scaleTouchStore", 0);
/** 当前是否为双指触控 */
h(this, "isTouch2", !1);
h(this, "isMobile", navigator.userAgent.includes("Mobile"));
/** 向上展示label */
h(this, "labelUp", !1);
h(this, "isCtrlKey", !1);
/** 自定义ctrl快捷键 KeyboardEvent.code */
h(this, "ctrlCode", "ControlLeft");
/** 网格右键菜单 */
h(this, "gridMenuEnable", !0);
/** 网格选中背景填充颜色 */
h(this, "gridSelectedFillStyle", "rgba(255, 255, 0, 0.6)");
this.handleLoad = this.handleLoad.bind(this), this.handleContextmenu = this.handleContextmenu.bind(this), this.handleMousewheel = this.handleMousewheel.bind(this), this.handleMouseDown = this.handleMouseDown.bind(this), this.handleMouseMove = this.handleMouseMove.bind(this), this.handleMouseUp = this.handleMouseUp.bind(this), this.handleDblclick = this.handleDblclick.bind(this), this.handleKeyup = this.handleKeyup.bind(this), this.handleKeydown = this.handleKeydown.bind(this);
const i = typeof t == "string" ? document.querySelector(t) : t;
i instanceof HTMLCanvasElement ? (this.canvas = i, this.offScreen = document.createElement("canvas"), this.initSetting(), this.initEvents(), e && this.setImage(e)) : console.warn("HTMLCanvasElement is required!");
}
/** 当前当前选中的标注 */
get activeShape() {
return this.dataset.find((t) => t.active) || {};
}
/** 当前缩放比例 */
get scale() {
return this.IMAGE_ORIGIN_WIDTH && this.IMAGE_WIDTH ? this.IMAGE_WIDTH / this.IMAGE_ORIGIN_WIDTH : 1;
}
/** 图片最小边尺寸 */
get imageMin() {
return Math.min(this.IMAGE_WIDTH, this.IMAGE_HEIGHT);
}
/** 图片原始最大边尺寸 */
get imageOriginMax() {
return Math.max(this.IMAGE_ORIGIN_WIDTH, this.IMAGE_ORIGIN_HEIGHT);
}
/** 创建放大镜容器 */
createMagnifierCanvas() {
this.isMagnifierVisible && (this.magnifierCanvas = this.magnifierCanvas || document.createElement("canvas"), this.magnifierCtx = this.magnifierCanvas && this.magnifierCanvas.getContext("2d", {
willReadFrequently: !0
}), this.magnifierCanvas.style.position = "fixed", this.magnifierCanvas.style.pointerEvents = "none", this.magnifierCanvas.style.zIndex = "1000", this.magnifierCanvas.style.border = "1px solid black", this.magnifierCanvas.style.borderRadius = "50%", this.magnifierCanvas.style.width = "100px", this.magnifierCanvas.style.height = "100px", document.body.appendChild(this.magnifierCanvas));
}
/** 创建放大镜 */
createMagnifier(t, e) {
this.magnifierCanvas ? this.updateMagnifier(t, e) : this.createMagnifierCanvas();
}
/** 更新放大镜 */
updateMagnifier(t, e) {
if (this.magnifierCanvas && this.magnifierCtx) {
const s = window.devicePixelRatio || 1;
if (this.magnifierCanvas.width = 100, this.magnifierCanvas.height = 100, this.magnifierCtx.clearRect(0, 0, 100, 100), this.magnifierPosition && this.magnifierPosition.length === 2) {
const [r, l] = this.magnifierPosition;
this.magnifierCanvas.style.left = `${r}px`, this.magnifierCanvas.style.top = `${l}px`;
} else
this.magnifierCanvas.style.left = `${t + 10}px`, this.magnifierCanvas.style.top = `${e + 10}px`;
const a = this.getImageDataFromCanvas(this.canvas, [
t * s - 100 / 2,
e * s - 100 / 2,
100,
100
]), o = this.magnifierCanvas.getContext("2d", { willReadFrequently: !0 }).createImageData(this.magnifierCanvas.width, this.magnifierCanvas.height);
let c = 0;
for (let r = 0; r < 100; r += 1)
for (let l = 0; l < 100; l += 1) {
for (let I = r; I < r + 1; I++)
for (let p = l; p < l + 1; p++) {
const n = (I * 100 + p) * 4;
o.data[n] = a.data[c], o.data[n + 1] = a.data[c + 1], o.data[n + 2] = a.data[c + 2], o.data[n + 3] = a.data[c + 3];
}
c += 4;
}
this.magnifierCanvas.getContext("2d", { willReadFrequently: !0 }).putImageData(o, 0, 0), this.magnifierCtx.strokeStyle = "rgba(0, 0, 0, 0.5)", this.magnifierCtx.lineWidth = 2, this.magnifierCtx.strokeRect(0, 0, 100, 100);
}
}
/** 销毁放大镜 */
destroyMagnifier() {
this.magnifierCanvas && (this.magnifierCanvas.remove(), this.magnifierCanvas = null, this.magnifierCtx = null);
}
/* 提取像素信息 */
getImageDataFromCanvas(t, [e, i, s, a]) {
return t.getContext("2d", { willReadFrequently: !0 }).getImageData(e, i, s, a);
}
/** 合成事件 */
mergeEvent(t) {
var o, c;
let e = 0, i = 0, s = 0, a = 0;
if (this.isMobile && t.touches) {
const { clientX: r, clientY: l } = t.touches[0], I = t.target, { left: p, top: n } = I.getBoundingClientRect();
if (e = Math.round(r - p), i = Math.round(l - n), ((o = t.touches) == null ? void 0 : o.length) === 2) {
const { clientX: d = 0, clientY: u = 0 } = t.touches[1] || {};
s = Math.round(Math.abs((d - r) / 2 + r) - p), a = Math.round(Math.abs((u - l) / 2 + l) - n);
} else ((c = t.touches) == null ? void 0 : c.length) === 1 && (s = Math.round(r - p), a = Math.round(l - n));
} else
e = t.offsetX, i = t.offsetY;
return { ...t, mouseX: e, mouseY: i, mouseCX: s, mouseCY: a };
}
handleLoad() {
this.emit("load", this.image.src), this.IMAGE_ORIGIN_WIDTH = this.IMAGE_WIDTH = this.image.width, this.IMAGE_ORIGIN_HEIGHT = this.IMAGE_HEIGHT = this.image.height, this.fitZoom();
}
handleContextmenu(t) {
t.preventDefault(), this.evt = t, this.lock;
}
handleMousewheel(t) {
if (t.stopPropagation(), this.evt = t, this.lock || !this.scrollZoom) return;
const { mouseX: e, mouseY: i } = this.mergeEvent(t);
this.mouse = [e, i], this.setScale(t.deltaY < 0, !0);
}
handleMouseDown(t) {
var r, l, I;
if (t.stopPropagation(), this.evt = t, this.lock) return;
const { mouseX: e, mouseY: i, mouseCX: s, mouseCY: a } = this.mergeEvent(t), o = Math.round(e / this.scale), c = Math.round(i / this.scale);
if (this.mouse = this.isMobile && ((r = t.touches) == null ? void 0 : r.length) === 2 ? [s, a] : [e, i], this.remmberOrigin = [e - this.originX, i - this.originY], !this.isMobile && t.buttons === 1 || this.isMobile && ((l = t.touches) == null ? void 0 : l.length) === 1) {
const p = this.activeShape.ctrlsData || [];
if (this.ctrlIndex = p.findIndex((n) => this.isPointInCircle(this.mouse, n, this.ctrlRadius)), this.ctrlIndex > -1 && !this.readonly) {
const [n, d] = p[this.ctrlIndex];
this.activeShape.type === 2 && this.activeShape.coor.length > 2 && this.ctrlIndex === 0 && this.handleDblclick(t), this.remmber = [[o - n, c - d]];
} else if (this.isInBackground(t)) {
if (this.activeShape.creating && !this.readonly) {
if ([
2,
4
/* Line */
].includes(this.activeShape.type)) {
const [n, d] = this.activeShape.coor[this.activeShape.coor.length - 1];
if (n !== o && d !== c) {
const u = Math.round(o - this.originX / this.scale), f = Math.round(c - this.originY / this.scale);
this.activeShape.coor.push([u, f]);
}
}
} else if (this.createType !== 0 && !this.readonly && !this.isCtrlKey) {
let n;
const d = Math.round(o - this.originX / this.scale), u = Math.round(c - this.originY / this.scale), f = [d, u];
switch (this.createType) {
case 1:
n = new T({ coor: [f, f] }, this.dataset.length), n.creating = !0;
break;
case 2:
n = new C({ coor: [f] }, this.dataset.length), n.creating = !0;
break;
case 3:
n = new D({ coor: f }, this.dataset.length), this.emit("add", n);
break;
case 4:
n = new k({ coor: [f] }, this.dataset.length), n.creating = !0;
break;
case 5:
n = new _({ coor: f }, this.dataset.length), n.creating = !0;
break;
case 6:
n = new W({ coor: [f, f] }, this.dataset.length), n.creating = !0;
break;
}
this.dataset.forEach((y) => {
y.active = !1;
}), n.active = !0, this.dataset.push(n);
} else {
const [n, d] = this.hitOnShape(this.mouse);
if (n > -1) {
if (d.dragging = !0, this.dataset.forEach((u, f) => u.active = f === n), this.dataset.splice(n, 1), this.dataset.push(d), !this.readonly)
if (this.remmber = [], [
3,
5
/* Circle */
].includes(d.type)) {
const [u, f] = d.coor;
this.remmber = [[o - u, c - f]];
} else
d.coor.forEach((u) => {
this.remmber.push([o - u[0], c - u[1]]);
});
this.emit("select", d);
} else
this.activeShape.active = !1, this.dataset.sort((u, f) => u.index - f.index), this.emit("select", null);
}
this.update();
}
} else if (!this.isMobile && t.buttons === 2 || this.isMobile && ((I = t.touches) == null ? void 0 : I.length) === 3 && !this.readonly) {
if ([
6
/* Grid */
].includes(this.activeShape.type) && this.gridMenuEnable) {
const p = prompt("x 行 y 列 x,y", [this.activeShape.row, this.activeShape.col].join(","));
if (typeof p == "string") {
const [n, d] = p.split(",");
/^[1-9]\d*$/.test(n) && /^[1-9]\d*$/.test(d) && (this.activeShape.row = Number(n), this.activeShape.col = Number(d), this.update());
}
}
this.emit("contextmenu", t);
}
}
handleMouseMove(t) {
var r, l, I, p;
if (t.stopPropagation(), this.evt = t, this.lock) return;
const { mouseX: e, mouseY: i, mouseCX: s, mouseCY: a } = this.mergeEvent(t), o = Math.round(e / this.scale), c = Math.round(i / this.scale);
if (this.mouse = this.isMobile && ((r = t.touches) == null ? void 0 : r.length) === 2 ? [s, a] : [e, i], (!this.isMobile && t.buttons === 1 || this.isMobile && ((l = t.touches) == null ? void 0 : l.length) === 1) && this.activeShape.type) {
if (this.ctrlIndex > -1 && this.remmber.length && (this.isInBackground(t) || this.activeShape.type === 5)) {
const [[n, d]] = this.remmber;
if ([
1,
6
/* Grid */
].includes(this.activeShape.type)) {
const [[u, f], [y, S]] = this.activeShape.coor;
let x = [];
switch (this.ctrlIndex) {
case 0:
x = [[o - n, c - d], [y, S]];
break;
case 1:
x = [[u, c - d], [y, S]];
break;
case 2:
x = [[u, c - d], [o - n, S]];
break;
case 3:
x = [[u, f], [o - n, S]];
break;
case 4:
x = [[u, f], [o - n, c - d]];
break;
case 5:
x = [[u, f], [y, c - d]];
break;
case 6:
x = [[o - n, f], [y, c - d]];
break;
case 7:
x = [[o - n, f], [y, S]];
break;
}
let [[m, H], [M, b]] = x;
(m < 0 || M < 0 || H < 0 || b < 0 || M > this.IMAGE_ORIGIN_WIDTH || b > this.IMAGE_ORIGIN_HEIGHT) && (m < 0 && (m = 0), M < 0 && (M = 0), H < 0 && (H = 0), b < 0 && (b = 0), M > this.IMAGE_ORIGIN_WIDTH && (M = this.IMAGE_ORIGIN_WIDTH), b > this.IMAGE_ORIGIN_HEIGHT && (b = this.IMAGE_ORIGIN_HEIGHT)), M - m >= this.MIN_WIDTH && b - H >= this.MIN_HEIGHT ? this.activeShape.coor = [[m, H], [M, b]] : this.emit("warn", `Width cannot be less than ${this.MIN_WIDTH},Height cannot be less than${this.MIN_HEIGHT}。`);
} else if ([
2,
4
/* Line */
].includes(this.activeShape.type)) {
const u = Math.round(o - this.originX / this.scale), f = Math.round(c - this.originY / this.scale), y = [u, f];
this.activeShape.coor.splice(this.ctrlIndex, 1, y);
} else if (this.activeShape.type === 5) {
const f = Math.round(o - this.originX / this.scale) - this.activeShape.coor[0];
f >= this.MIN_RADIUS && (this.activeShape.radius = f);
}
if (this.isMagnifierVisible) {
const [u, f] = this.isMobile ? [s, a] : [e, i];
this.createMagnifier(u, f);
}
} else if (this.activeShape.dragging && !this.readonly) {
if (this.isMagnifierVisible && this.activeShape.type === 3) {
const [y, S] = this.isMobile ? [s, a] : [e, i];
this.createMagnifier(y, S);
}
let n = [], d = !0;
const u = this.IMAGE_ORIGIN_WIDTH || this.WIDTH, f = this.IMAGE_ORIGIN_HEIGHT || this.HEIGHT;
if ([
3,
5
/* Circle */
].includes(this.activeShape.type)) {
const [y, S] = this.remmber[0], x = o - y, m = c - S;
(x < 0 || x > u || m < 0 || m > f) && (d = !1), n = [x, m];
} else
for (let y = 0; y < this.activeShape.coor.length; y++) {
const S = this.remmber[y], x = o - S[0], m = c - S[1];
(x < 0 || x > u || m < 0 || m > f) && (d = !1), n.push([x, m]);
}
d && (this.activeShape.coor = n);
} else if (this.activeShape.creating && this.isInBackground(t)) {
const n = Math.round(o - this.originX / this.scale), d = Math.round(c - this.originY / this.scale);
if ([
1,
6
/* Grid */
].includes(this.activeShape.type))
this.activeShape.coor.splice(1, 1, [n, d]);
else if (this.activeShape.type === 5) {
const [u, f] = this.activeShape.coor, y = Math.sqrt((u - n) ** 2 + (f - d) ** 2);
this.activeShape.radius = y;
}
}
this.update();
} else if ([
2,
4
/* Line */
].includes(this.activeShape.type) && this.activeShape.creating)
this.update();
else if (!this.isMobile && t.buttons === 2 && t.which === 3 || this.isMobile && ((I = t.touches) == null ? void 0 : I.length) === 1 && !this.isTouch2)
this.originX = Math.round(e - this.remmberOrigin[0]), this.originY = Math.round(i - this.remmberOrigin[1]), this.update();
else if (this.isMobile && ((p = t.touches) == null ? void 0 : p.length) === 2) {
this.isTouch2 = !0;
const n = t.touches[0], d = t.touches[1], u = this.scaleTouchStore;
this.scaleTouchStore = Math.abs((d.clientX - n.clientX) * (d.clientY - n.clientY)), this.setScale(this.scaleTouchStore > u, !0);
}
}
handleMouseUp(t) {
var e;
if (t.stopPropagation(), this.evt = t, !this.lock) {
if (this.destroyMagnifier(), this.isMobile) {
if (((e = t.touches) == null ? void 0 : e.length) === 0 && (this.isTouch2 = !1), Date.now() - this.dblTouchStore < this.dblTouch) {
this.handleDblclick(t);
return;
}
this.dblTouchStore = Date.now();
}
if (this.remmber = [], this.activeShape.type !== 0 && !this.isCtrlKey && (this.activeShape.dragging = !1, this.activeShape.creating)) {
if ([
1,
6
/* Grid */
].includes(this.activeShape.type)) {
const [[i, s], [a, o]] = this.activeShape.coor;
Math.abs(i - a) < this.MIN_WIDTH || Math.abs(s - o) < this.MIN_HEIGHT ? (this.dataset.pop(), this.emit("warn", `Width cannot be less than ${this.MIN_WIDTH},Height cannot be less than ${this.MIN_HEIGHT}`)) : (this.activeShape.coor = [[Math.min(i, a), Math.min(s, o)], [Math.max(i, a), Math.max(s, o)]], this.activeShape.creating = !1, this.emit("add", this.activeShape));
} else this.activeShape.type === 5 && (this.activeShape.radius < this.MIN_RADIUS ? (this.dataset.pop(), this.emit("warn", `Radius cannot be less than ${this.MIN_WIDTH}`)) : (this.activeShape.creating = !1, this.emit("add", this.activeShape)));
this.update();
}
}
}
handleDblclick(t) {
if (t.stopPropagation(), this.evt = t, !this.lock)
if ([
2,
4
/* Line */
].includes(this.activeShape.type)) {
const e = this.activeShape.type === 2 && this.activeShape.coor.length > 2, i = this.activeShape.type === 4 && this.activeShape.coor.length > 1;
(e || i) && (this.emit("add", this.activeShape), this.activeShape.creating = !1, this.update());
} else [
6
/* Grid */
].includes(this.activeShape.type) && this.activeShape.active && (this.activeShape.gridRects.forEach((e) => {
if (this.isPointInRect(this.mouse, e.coor)) {
const i = this.activeShape.selected.findIndex((s) => e.index === s);
i > -1 ? this.activeShape.selected.splice(i, 1) : this.activeShape.selected.push(e.index);
}
}), this.update());
}
handleKeydown(t) {
t.code === this.ctrlCode && (this.isCtrlKey = !0);
}
handleKeyup(t) {
t.code === this.ctrlCode && (this.isCtrlKey = !1), this.evt = t, !(this.lock || document.activeElement !== document.body || this.readonly) && this.activeShape.type && ([
2,
4
/* Line */
].includes(this.activeShape.type) && t.key === "Escape" ? (this.activeShape.coor.length > 1 && this.activeShape.creating ? this.activeShape.coor.pop() : this.deleteByIndex(this.activeShape.index), this.update()) : t.key === "Backspace" && this.deleteByIndex(this.activeShape.index));
}
/** 初始化配置 */
initSetting() {
const t = window.devicePixelRatio || 1;
this.image.crossOrigin = "anonymous", this.canvas.style.userSelect = "none", this.ctx = this.ctx || this.canvas.getContext("2d", { alpha: this.alpha }), this.WIDTH = Math.round(this.canvas.clientWidth), this.HEIGHT = Math.round(this.canvas.clientHeight), this.canvas.width = this.WIDTH * t, this.canvas.height = this.HEIGHT * t, this.canvas.style.width = this.WIDTH + "px", this.canvas.style.height = this.HEIGHT + "px", this.offScreen.width = this.WIDTH, this.offScreen.height = this.HEIGHT, this.offScreenCtx = this.offScreenCtx || this.offScreen.getContext("2d", { willReadFrequently: !0 }), this.ctx.scale(t, t);
}
/** 初始化事件 */
initEvents() {
this.image.addEventListener("load", this.handleLoad), this.canvas.addEventListener("touchstart", this.handleMouseDown), this.canvas.addEventListener("touchmove", this.handleMouseMove), this.canvas.addEventListener("touchend", this.handleMouseUp), this.canvas.addEventListener("contextmenu", this.handleContextmenu), this.canvas.addEventListener("mousewheel", this.handleMousewheel), this.canvas.addEventListener("wheel", this.handleMousewheel), this.canvas.addEventListener("mousedown", this.handleMouseDown), this.canvas.addEventListener("mousemove", this.handleMouseMove), this.canvas.addEventListener("mouseup", this.handleMouseUp), this.canvas.addEventListener("dblclick", this.handleDblclick), document.body.addEventListener("keydown", this.handleKeydown, !0), document.body.addEventListener("keyup", this.handleKeyup, !0);
}
/**
* 添加/切换图片
* @param url 图片链接
*/
setImage(t) {
this.image.src = t;
}
/**
* 设置数据
* @param data Array
*/
setData(t) {
setTimeout(() => {
const e = [];
t.forEach((i, s) => {
if (Object.prototype.toString.call(i).includes("Object")) {
let a;
switch (i.type) {
case 1:
a = new T(i, s);
break;
case 2:
a = new C(i, s);
break;
case 3:
a = new D(i, s);
break;
case 4:
a = new k(i, s);
break;
case 5:
a = new _(i, s);
break;
case 6:
a = new W(i, s);
break;
default:
console.warn("Invalid shape", i);
break;
}
[
1,
2,
3,
4,
5,
6
/* Grid */
].includes(i.type) && e.push(a);
} else
console.warn("Shape must be an enumerable Object.", i);
}), this.dataset = e, this.update();
});
}
/**
* 判断是否在标注实例上
* @param mousePoint 点击位置
* @returns
*/
hitOnShape(t) {
let e = -1, i;
for (let s = this.dataset.length - 1; s > -1; s--) {
const a = this.dataset[s];
if (!a.hide && (a.type === 3 && this.isPointInCircle(t, a.coor, this.ctrlRadius) || a.type === 5 && this.isPointInCircle(t, a.coor, a.radius * this.scale) || a.type === 1 && this.isPointInRect(t, a.coor) || a.type === 2 && this.isPointInPolygon(t, a.coor) || a.type === 4 && this.isPointInLine(t, a.coor) || a.type === 6 && this.isPointInRect(t, a.coor))) {
if (this.focusMode && !a.active) continue;
e = s, i = a;
break;
}
}
return [e, i];
}
/**
* 判断鼠标是否在背景图内部
* @param e MouseEvent
* @returns 布尔值
*/
isInBackground(t) {
const { mouseX: e, mouseY: i } = this.mergeEvent(t);
return e >= this.originX && i >= this.originY && e <= this.originX + this.IMAGE_ORIGIN_WIDTH * this.scale && i <= this.originY + this.IMAGE_ORIGIN_HEIGHT * this.scale;
}
/**
* 判断是否在矩形内
* @param point 坐标
* @param coor 区域坐标
* @returns 布尔值
*/
isPointInRect(t, e) {
const [i, s] = t, [[a, o], [c, r]] = e.map((l) => l.map((I) => I * this.scale));
return a + this.originX <= i && i <= c + this.originX && o + this.originY <= s && s <= r + this.originY;
}
/**
* 判断是否在多边形内
* @param point 坐标
* @param coor 区域坐标
* @returns 布尔值
*/
isPointInPolygon(t, e) {
this.offScreenCtx.save(), this.offScreenCtx.clearRect(0, 0, this.WIDTH, this.HEIGHT), this.offScreenCtx.translate(this.originX, this.originY), this.offScreenCtx.beginPath(), e.forEach((a, o) => {
const [c, r] = a.map((l) => Math.round(l * this.scale));
o === 0 ? this.offScreenCtx.moveTo(c, r) : this.offScreenCtx.lineTo(c, r);
}), this.offScreenCtx.closePath(), this.offScreenCtx.fill();
const i = this.offScreenCtx.getImageData(0, 0, this.WIDTH, this.HEIGHT), s = (t[1] - 1) * this.WIDTH * 4 + t[0] * 4;
return this.offScreenCtx.restore(), i.data[s + 3] !== 0;
}
/**
* 判断是否在圆内
* @param point 坐标
* @param center 圆心
* @param r 半径
* @param needScale 是否为圆形点击检测
* @returns 布尔值
*/
isPointInCircle(t, e, i) {
const [s, a] = t, [o, c] = e.map((l) => l * this.scale);
return Math.sqrt((o + this.originX - s) ** 2 + (c + this.originY - a) ** 2) <= i;
}
/**
* 判断是否在折线内
* @param point 坐标
* @param coor 区域坐标
* @returns 布尔值
*/
isPointInLine(t, e) {
this.offScreenCtx.save(), this.offScreenCtx.clearRect(0, 0, this.WIDTH, this.HEIGHT), this.offScreenCtx.translate(this.originX, this.originY), this.offScreenCtx.lineWidth = this.lineWidth > 5 ? this.lineWidth : 5, this.offScreenCtx.beginPath(), e.forEach((a, o) => {
const [c, r] = a.map((l) => Math.round(l * this.scale));
o === 0 ? this.offScreenCtx.moveTo(c, r) : this.offScreenCtx.lineTo(c, r);
}), this.offScreenCtx.stroke();
const i = this.offScreenCtx.getImageData(0, 0, this.WIDTH, this.HEIGHT), s = (t[1] - 1) * this.WIDTH * 4 + t[0] * 4;
return this.offScreenCtx.restore(), i.data[s + 3] !== 0;
}
/**
* 判断是图形是否属于嵌套关系 (目前只支持矩形和多边形)
* @param shape1 标注实例
* @param shape2 标注实例
* @returns 布尔值
*/
isNested(t, e) {
return X(t, e);
}
/**
* 绘制矩形
* @param shape 标注实例
* @returns
*/
drawRect(t, e) {
if (t.coor.length !== 2) return;
const { strokeStyle: i, fillStyle: s, active: a, creating: o, coor: c, lineWidth: r } = t, [[l, I], [p, n]] = c.map((f) => f.map((y) => Math.round(y * this.scale)));
this.ctx.save(), this.ctx.lineWidth = r || this.lineWidth, this.ctx.fillStyle = e != null && e.isSelected ? e == null ? void 0 : e.selectedFillStyle : s || this.fillStyle, this.ctx.strokeStyle = a || o ? this.activeStrokeStyle : i || this.strokeStyle;
const d = p - l, u = n - I;
o || this.ctx.fillRect(l, I, d, u), this.ctx.strokeRect(l, I, d, u), this.ctx.restore(), this.drawLabel(c[0], t);
}
/**
* 绘制多边形
* @param shape 标注实例
*/
drawPolygon(t) {
const { strokeStyle: e, fillStyle: i, active: s, creating: a, coor: o, lineWidth: c } = t;
if (this.ctx.save(), this.ctx.lineJoin = "round", this.ctx.lineWidth = c || this.lineWidth, this.ctx.fillStyle = i || this.fillStyle, this.ctx.strokeStyle = s || a ? this.activeStrokeStyle : e || this.strokeStyle, this.ctx.beginPath(), o.forEach((r, l) => {
const [I, p] = r.map((n) => Math.round(n * this.scale));
l === 0 ? this.ctx.moveTo(I, p) : this.ctx.lineTo(I, p);
}), a) {
const [r, l] = this.mouse || [];
this.ctx.lineTo(r - this.originX, l - this.originY);
} else o.length > 2 && this.ctx.closePath();
this.ctx.fill(), this.ctx.stroke(), this.ctx.restore(), this.drawLabel(o[0], t);
}
/**
* 绘制点
* @param shape 标注实例
*/
drawDot(t) {
const { strokeStyle: e, fillStyle: i, active: s, coor: a, lineWidth: o } = t, [c, r] = a.map((l) => l * this.scale);
this.ctx.save(), this.ctx.lineWidth = o || this.lineWidth, this.ctx.fillStyle = i || this.ctrlFillStyle, this.ctx.strokeStyle = s ? this.activeStrokeStyle : e || this.strokeStyle, this.ctx.beginPath(), this.ctx.arc(c, r, this.ctrlRadius, 0, 2 * Math.PI, !0), this.ctx.fill(), this.ctx.arc(c, r, this.ctrlRadius, 0, 2 * Math.PI, !0), this.ctx.stroke(), this.ctx.restore(), this.drawLabel(a, t);
}
/**
* 绘制圆
* @param shape 标注实例
*/
drawCirle(t) {
const { strokeStyle: e, fillStyle: i, active: s, coor: a, label: o, creating: c, radius: r, ctrlsData: l, lineWidth: I } = t, [p, n] = a.map((d) => d * this.scale);
this.ctx.save(), this.ctx.lineWidth = I || this.lineWidth, this.ctx.fillStyle = i || this.fillStyle, this.ctx.strokeStyle = s || c ? this.activeStrokeStyle : e || this.strokeStyle, this.ctx.beginPath(), this.ctx.arc(p, n, r * this.scale, 0, 2 * Math.PI, !0), this.ctx.fill(), this.ctx.arc(p, n, r * this.scale, 0, 2 * Math.PI, !0), this.ctx.stroke(), this.ctx.restore(), this.drawLabel(l[0], t);
}
/**
* 绘制折线
* @param shape 标注实例
*/
drawLine(t) {
const { strokeStyle: e, active: i, creating: s, coor: a, lineWidth: o } = t;
if (this.ctx.save(), this.ctx.lineJoin = "round", this.ctx.lineWidth = o || this.lineWidth, this.ctx.strokeStyle = i || s ? this.activeStrokeStyle : e || this.strokeStyle, this.ctx.beginPath(), a.forEach((c, r) => {
const [l, I] = c.map((p) => Math.round(p * this.scale));
r === 0 ? this.ctx.moveTo(l, I) : this.ctx.lineTo(l, I);
}), s) {
const [c, r] = this.mouse || [];
this.ctx.lineTo(c - this.originX, r - this.originY);
}
this.ctx.stroke(), this.ctx.restore(), this.drawLabel(a[0], t);
}
/**
* 绘制网格
* @param shape 标注实例
* @returns
*/
drawGrid(t) {
if (t.coor.length !== 2) return;
const { strokeStyle: e, fillStyle: i, active: s, creating: a, coor: o, lineWidth: c } = t, [[r, l], [I, p]] = o.map((u) => u.map((f) => Math.round(f * this.scale)));
this.ctx.save(), this.ctx.lineWidth = c || this.lineWidth, this.ctx.fillStyle = i || this.fillStyle, this.ctx.strokeStyle = s || a ? this.activeStrokeStyle : e || this.strokeStyle, t.gridRects.forEach((u, f) => {
var y;
this.drawRect(u, {
selectedFillStyle: t.selectedFillStyle || this.gridSelectedFillStyle,
isSelected: (y = t.selected) == null ? void 0 : y.includes(f)
});
});
const n = I - r, d = p - l;
a || this.ctx.fillRect(r, l, n, d), this.ctx.strokeRect(r, l, n, d), this.ctx.restore(), this.drawLabel(o[0], t);
}
/**
* 绘制控制点
* @param point 坐标
*/
drawCtrl(t) {
const [e, i] = t.map((s) => s * this.scale);
this.ctx.save(), this.ctx.beginPath(), this.ctx.fillStyle = this.ctrlFillStyle, this.ctx.strokeStyle = this.ctrlStrokeStyle, this.ctx.arc(e, i, this.ctrlRadius, 0, 2 * Math.PI, !0), this.ctx.fill(), this.ctx.arc(e, i, this.ctrlRadius, 0, 2 * Math.PI, !0), this.ctx.stroke(), this.ctx.restore();
}
/**
* 绘制控制点列表
* @param shape 标注实例
*/
drawCtrlList(t) {
t.ctrlsData.forEach((e, i) => {
t.type === 5 ? i === 1 && this.drawCtrl(e) : this.drawCtrl(e);
});
}
/**
* 绘制label
* @param point 位置
* @param label 文本
*/
drawLabel(t, e) {
const { label: i = "", labelFillStyle: s = "", labelFont: a = "", textFillStyle: o = "", hideLabel: c, labelUp: r, lineWidth: l } = e, I = typeof c == "boolean" ? c : this.hideLabel, p = typeof r == "boolean" ? r : this.labelUp, n = l || this.lineWidth;
if (i.length && !I) {
this.ctx.font = a || this.labelFont;
const d = 4, u = 4, f = i.length < this.labelMaxLen + 1 ? i : `${i.slice(0, this.labelMaxLen)}...`, y = this.ctx.measureText(f), S = parseInt(this.ctx.font) - 4, x = y.width + d * 2, m = S + u * 2, [H, M] = t.map((L) => L * this.scale), b = this.IMAGE_ORIGIN_WIDTH - t[0] < x / this.scale, A = this.IMAGE_ORIGIN_HEIGHT - t[1] < m / this.scale, R = t[1] > m / this.scale, G = p ? R : A;
this.ctx.save(), this.ctx.fillStyle = s || this.labelFillStyle, this.ctx.fillRect(b ? H - y.width - d - n / 2 : H + n / 2, G ? M - m - n / 2 : M + n / 2, x, m), this.ctx.fillStyle = o || this.textFillStyle, this.ctx.fillText(f, b ? H - y.width : H + d + n / 2, G ? M - m + S + u : M + S + u + n / 2, 180), this.ctx.restore();
}
}
/**
* 更新画布
*/
update() {
window.cancelAnimationFrame(this.timer), this.timer = window.requestAnimationFrame(() => {
this.ctx.save(), this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT), this.ctx.translate(this.originX, this.originY), this.IMAGE_WIDTH && this.IMAGE_HEIGHT && this.ctx.drawImage(this.image, 0, 0, this.IMAGE_WIDTH, this.IMAGE_HEIGHT);
const t = this.focusMode ? this.activeShape.type ? [this.activeShape] : [] : this.dataset;
for (let e = 0; e < t.length; e++) {
const i = t[e];
if (!i.hide)
switch (i.type) {
case 1:
this.drawRect(i);
break;
case 2:
this.drawPolygon(i);
break;
case 3:
this.drawDot(i);
break;
case 4:
this.drawLine(i);
break;
case 5:
this.drawCirle(i);
break;
case 6:
this.drawGrid(i);
break;
}
}
[
1,
2,
4,
5,
6
/* Grid */
].includes(this.activeShape.type) && !this.activeShape.hide && this.drawCtrlList(this.activeShape), this.ctx.restore(), this.emit("updated", this.dataset);
});
}
/**
* 删除指定矩形
* @param index number
*/
deleteByIndex(t) {
const e = this.dataset.findIndex((i) => i.index === t);
e > -1 && (this.emit("delete", this.dataset[e]), this.dataset.splice(e, 1), this.dataset.forEach((i, s) => {
i.index = s;
}), this.update());
}
/**
* 计算缩放步长
*/
calcStep(t = "") {
this.IMAGE_WIDTH < this.WIDTH && this.IMAGE_HEIGHT < this.HEIGHT && (t === "" || t === "b") && (this.setScale(!0, !1, !0), this.calcStep("b")), (this.IMAGE_WIDTH > this.WIDTH || this.IMAGE_HEIGHT > this.HEIGHT) && (t === "" || t === "s") && (this.setScale(!1, !1, !0), this.calcStep("s"));
}
/**
* 缩放
* @param type true放大5%,false缩小5%
* @param center 缩放中心 center|mouse
* @param pure 不绘制
*/
setScale(t, e = !1, i = !1) {
if (this.lock || !t && this.imageMin < 20 || t && this.IMAGE_WIDTH > this.imageOriginMax * 100) return;
t ? this.scaleStep++ : this.scaleStep--;
let s = 0, a = 0;
const [o, c] = this.mouse || [];
e && (s = (o - this.originX) / this.scale, a = (c - this.originY) / this.scale);
const r = Math.abs(this.scaleStep), l = this.IMAGE_WIDTH;
if (this.IMAGE_WIDTH = Math.round(this.IMAGE_ORIGIN_WIDTH * (this.scaleStep >= 0 ? 1.05 : 0.95) ** r), this.IMAGE_HEIGHT = Math.round(this.IMAGE_ORIGIN_HEIGHT * (this.scaleStep >= 0 ? 1.05 : 0.95) ** r), e)
this.originX = o - s * this.scale, this.originY = c - a * this.scale;
else {
const I = this.IMAGE_WIDTH / l;
this.originX = this.WIDTH / 2 - (this.WIDTH / 2 - this.originX) * I, this.originY = this.HEIGHT / 2 - (this.HEIGHT / 2 - this.originY) * I;
}
i || this.update();
}
/**
* 适配背景图
*/
fitZoom() {
this.calcStep(), this.IMAGE_HEIGHT / this.IMAGE_WIDTH >= this.HEIGHT / this.WIDTH ? (this.IMAGE_WIDTH = this.IMAGE_ORIGIN_WIDTH / (this.IMAGE_ORIGIN_HEIGHT / this.HEIGHT), this.IMAGE_HEIGHT = this.HEIGHT) : (this.IMAGE_WIDTH = this.WIDTH, this.IMAGE_HEIGHT = this.IMAGE_ORIGIN_HEIGHT / (this.IMAGE_ORIGIN_WIDTH / this.WIDTH)), this.originX = (this.WIDTH - this.IMAGE_WIDTH) / 2, this.originY = (this.HEIGHT - this.IMAGE_HEIGHT) / 2, this.update();
}
/**
* 设置专注模式
* @param type {boolean}
*/
setFocusMode(t) {
this.focusMode = t, this.update();
}
/**
* 销毁
*/
destroy() {
this.image.removeEventListener("load", this.handleLoad), this.canvas.removeEventListener("contextmenu", this.handleContextmenu), this.canvas.removeEventListener("mousewheel", this.handleMousewheel), this.canvas.removeEventListener("wheel", this.handleMousewheel), this.canvas.removeEventListener("mousedown", this.handleMouseDown), this.canvas.removeEventListener("touchend", this.handleMouseDown), this.canvas.removeEventListener("mousemove", this.handleMouseMove), this.canvas.removeEventListener("touchmove", this.handleMouseMove), this.canvas.removeEventListener("mouseup", this.handleMouseUp), this.canvas.removeEventListener("touchend", this.handleMouseUp), this.canvas.removeEventListener("dblclick", this.handleDblclick), document.body.removeEventListener("keydown", this.handleKeydown, !0), document.body.removeEventListener("keyup", this.handleKeyup, !0), this.canvas.width = this.WIDTH, this.canvas.height = this.HEIGHT, this.canvas.style.width = null, this.canvas.style.height = null, this.canvas.style.userSelect = null;
}
/**
* 重新设置画布大小
*/
resize() {
this.canvas.width = null, this.canvas.height = null, this.canvas.style.width = null, this.canvas.style.height = null, this.initSetting(), this.update();
}
}
export {
nt as default
};