@linkurious/ogma-annotations
Version:
Headless annotation plugin for Ogma
1,587 lines • 230 kB
JavaScript
var Fn = Object.defineProperty;
var Pn = (i, e, t) => e in i ? Fn(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
var y = (i, e, t) => Pn(i, typeof e != "symbol" ? e + "" : e, t);
import { Node as Dn, geometry as zt } from "@linkurious/ogma";
function Ln(i) {
return i && i.__esModule && Object.prototype.hasOwnProperty.call(i, "default") ? i.default : i;
}
var jt = { exports: {} }, me;
function Hn() {
return me || (me = 1, (function(i) {
var e = Object.prototype.hasOwnProperty, t = "~";
function n() {
}
Object.create && (n.prototype = /* @__PURE__ */ Object.create(null), new n().__proto__ || (t = !1));
function s(l, h, c) {
this.fn = l, this.context = h, this.once = c || !1;
}
function r(l, h, c, d, u) {
if (typeof c != "function")
throw new TypeError("The listener must be a function");
var g = new s(c, d || l, u), f = t ? t + h : h;
return l._events[f] ? l._events[f].fn ? l._events[f] = [l._events[f], g] : l._events[f].push(g) : (l._events[f] = g, l._eventsCount++), l;
}
function o(l, h) {
--l._eventsCount === 0 ? l._events = new n() : delete l._events[h];
}
function a() {
this._events = new n(), this._eventsCount = 0;
}
a.prototype.eventNames = function() {
var h = [], c, d;
if (this._eventsCount === 0) return h;
for (d in c = this._events)
e.call(c, d) && h.push(t ? d.slice(1) : d);
return Object.getOwnPropertySymbols ? h.concat(Object.getOwnPropertySymbols(c)) : h;
}, a.prototype.listeners = function(h) {
var c = t ? t + h : h, d = this._events[c];
if (!d) return [];
if (d.fn) return [d.fn];
for (var u = 0, g = d.length, f = new Array(g); u < g; u++)
f[u] = d[u].fn;
return f;
}, a.prototype.listenerCount = function(h) {
var c = t ? t + h : h, d = this._events[c];
return d ? d.fn ? 1 : d.length : 0;
}, a.prototype.emit = function(h, c, d, u, g, f) {
var p = t ? t + h : h;
if (!this._events[p]) return !1;
var m = this._events[p], w = arguments.length, b, v;
if (m.fn) {
switch (m.once && this.removeListener(h, m.fn, void 0, !0), w) {
case 1:
return m.fn.call(m.context), !0;
case 2:
return m.fn.call(m.context, c), !0;
case 3:
return m.fn.call(m.context, c, d), !0;
case 4:
return m.fn.call(m.context, c, d, u), !0;
case 5:
return m.fn.call(m.context, c, d, u, g), !0;
case 6:
return m.fn.call(m.context, c, d, u, g, f), !0;
}
for (v = 1, b = new Array(w - 1); v < w; v++)
b[v - 1] = arguments[v];
m.fn.apply(m.context, b);
} else {
var S = m.length, x;
for (v = 0; v < S; v++)
switch (m[v].once && this.removeListener(h, m[v].fn, void 0, !0), w) {
case 1:
m[v].fn.call(m[v].context);
break;
case 2:
m[v].fn.call(m[v].context, c);
break;
case 3:
m[v].fn.call(m[v].context, c, d);
break;
case 4:
m[v].fn.call(m[v].context, c, d, u);
break;
default:
if (!b) for (x = 1, b = new Array(w - 1); x < w; x++)
b[x - 1] = arguments[x];
m[v].fn.apply(m[v].context, b);
}
}
return !0;
}, a.prototype.on = function(h, c, d) {
return r(this, h, c, d, !1);
}, a.prototype.once = function(h, c, d) {
return r(this, h, c, d, !0);
}, a.prototype.removeListener = function(h, c, d, u) {
var g = t ? t + h : h;
if (!this._events[g]) return this;
if (!c)
return o(this, g), this;
var f = this._events[g];
if (f.fn)
f.fn === c && (!u || f.once) && (!d || f.context === d) && o(this, g);
else {
for (var p = 0, m = [], w = f.length; p < w; p++)
(f[p].fn !== c || u && !f[p].once || d && f[p].context !== d) && m.push(f[p]);
m.length ? this._events[g] = m.length === 1 ? m[0] : m : o(this, g);
}
return this;
}, a.prototype.removeAllListeners = function(h) {
var c;
return h ? (c = t ? t + h : h, this._events[c] && o(this, c)) : (this._events = new n(), this._eventsCount = 0), this;
}, a.prototype.off = a.prototype.removeListener, a.prototype.addListener = a.prototype.on, a.prefixed = t, a.EventEmitter = a, i.exports = a;
})(jt)), jt.exports;
}
var _n = Hn();
const In = /* @__PURE__ */ Ln(_n), vr = -1, Ut = "dragging", Ct = "dragstart", Bt = "dragend", oe = "click", zn = "select", Bn = "unselect", Sr = "hover", Ar = "unhover", Rn = "remove", ae = "add", $n = "cancelDrawing", Ze = "completeDrawing", Nn = "update", Yn = "link", On = "history", Er = "data-annotation", Ke = 5, Xn = 3, ue = {
SHAPES: 1,
EDITOR: 2,
HANDLES: 3
}, X = "start", it = "end", I = {
default: "default",
move: "move",
pointer: "pointer",
crosshair: "crosshair",
grab: "grab",
grabbing: "grabbing",
ewResize: "ew-resize",
nsResize: "ns-resize",
neswResize: "nesw-resize",
nwseResize: "nwse-resize",
nwResize: "nw-resize",
neResize: "ne-resize",
swResize: "sw-resize",
seResize: "se-resize",
nResize: "n-resize",
eResize: "e-resize",
sResize: "s-resize",
wResize: "w-resize"
}, at = "collapsed", Rt = "expanded", $t = 1.2, Je = 0.2, T = {
TEXT: "text",
NODE: "node",
BOX: "box",
COMMENT: "comment",
POLYGON: "polygon",
ANNOTATION: "annotation",
EDGE: "edge"
}, Qe = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22 2L11 13M22 2L15 22L11 13M22 2L2 9L11 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`, tn = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 6.00015H7.33333C6.97971 6.00015 6.64057 6.14063 6.39052 6.39068C6.14048 6.64072 6 6.97986 6 7.33348V16.6668C6 17.0204 6.14048 17.3596 6.39052 17.6096C6.64057 17.8597 6.97971 18.0002 7.33333 18.0002H16.6667C17.0203 18.0002 17.3594 17.8597 17.6095 17.6096C17.8595 17.3596 18 17.0204 18 16.6668V12.0002M16.25 5.75015C16.5152 5.48493 16.8749 5.33594 17.25 5.33594C17.6251 5.33594 17.9848 5.48493 18.25 5.75015C18.5152 6.01537 18.6642 6.37508 18.6642 6.75015C18.6642 7.12522 18.5152 7.48493 18.25 7.75015L12.2413 13.7595C12.083 13.9176 11.8875 14.0334 11.6727 14.0962L9.75733 14.6562C9.69997 14.6729 9.63916 14.6739 9.58127 14.6591C9.52339 14.6442 9.47055 14.6141 9.4283 14.5719C9.38604 14.5296 9.35593 14.4768 9.3411 14.4189C9.32627 14.361 9.32727 14.3002 9.344 14.2428L9.904 12.3275C9.96702 12.1129 10.083 11.9175 10.2413 11.7595L16.25 5.75015Z" stroke="#1A70E5" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
`;
let ut = (i = 21) => crypto.getRandomValues(new Uint8Array(i)).reduce((e, t) => (t &= 63, t < 36 ? e += t.toString(36) : t < 62 ? e += (t - 26).toString(36).toUpperCase() : t > 62 ? e += "-" : e += "_", e), "");
function Un(i, e) {
const t = i[0] - e[0], n = i[1] - e[1];
return t * t + n * n;
}
function qn(i, e, t) {
let n = e[0], s = e[1], r = t[0] - n, o = t[1] - s;
if (r !== 0 || o !== 0) {
const a = ((i[0] - n) * r + (i[1] - s) * o) / (r * r + o * o);
a > 1 ? (n = t[0], s = t[1]) : a > 0 && (n += r * a, s += o * a);
}
return r = i[0] - n, o = i[1] - s, r * r + o * o;
}
function Wn(i, e) {
let t = i[0];
const n = [t];
let s;
for (let r = 1, o = i.length; r < o; r++)
s = i[r], Un(s, t) > e && (n.push(s), t = s);
return t !== s && n.push(s), n;
}
function ce(i, e, t, n, s) {
let r = n, o = 0;
for (let a = e + 1; a < t; a++) {
const l = qn(i[a], i[e], i[t]);
l > r && (o = a, r = l);
}
r > n && (o - e > 1 && ce(i, e, o, n, s), s.push(i[o]), t - o > 1 && ce(i, o, t, n, s));
}
function jn(i, e) {
const t = i.length - 1, n = [i[0]];
return ce(i, 0, t, e, n), n.push(i[t]), n;
}
function ye(i, e, t) {
if (i.length <= 2) return i;
const n = e !== void 0 ? e * e : 1;
return i = t ? i : Wn(i, n), i = jn(i, n), i;
}
function we(i) {
if (i.geometry.bbox) return i.geometry.bbox;
const e = i.geometry.coordinates[0];
let t = 1 / 0, n = 1 / 0, s = -1 / 0, r = -1 / 0;
for (const [o, a] of e)
o < t && (t = o), o > s && (s = o), a < n && (n = a), a > r && (r = a);
return [t, n, s, r];
}
function en(i) {
const e = i.geometry.coordinates[0];
let t = 0, n = 0;
const s = e.length - 1;
for (let r = 0; r < s; r++)
t += e[r][0], n += e[r][1];
return {
x: t / s,
y: n / s
};
}
function Cr(i, e, t) {
const n = i.geometry.coordinates.map(
(o) => o.map(([a, l]) => [a + e, l + t])
), s = i.geometry.bbox, r = s ? [s[0] + e, s[1] + t, s[2] + e, s[3] + t] : void 0;
return {
...i,
geometry: {
...i.geometry,
coordinates: n,
bbox: r
}
};
}
function nn(i) {
const e = i.geometry.coordinates[0];
let t = 1 / 0, n = 1 / 0, s = -1 / 0, r = -1 / 0;
for (const [o, a] of e)
o < t && (t = o), o > s && (s = o), a < n && (n = a), a > r && (r = a);
i.geometry.bbox = [t, n, s, r];
}
function kr(i, e, t, n) {
const s = i.geometry.coordinates.map(
(o) => o.map(([a, l]) => {
const h = a - t, c = l - n;
return [t + h * e, n + c * e];
})
), r = {
...i,
geometry: {
...i.geometry,
coordinates: s
}
};
return nn(r), r;
}
const Gn = "http://www.w3.org/2000/svg";
function M(i) {
return document.createElementNS(Gn, i);
}
function rt(i) {
return i.geometry.bbox || kt(i), i.geometry.bbox;
}
function q(i) {
if ("width" in i.properties && "height" in i.properties)
return {
width: i.properties.width,
height: i.properties.height
};
const e = rt(i);
return {
width: e[2] - e[0],
height: e[3] - e[1]
};
}
function St(i, e = !1, t = 1) {
if (i.geometry.type === "Point" && "width" in i.properties && "height" in i.properties) {
const [s, r] = i.geometry.coordinates;
let o = i.properties.width, a = i.properties.height;
return e && (o /= t, a /= t), {
x: s - o / 2,
y: r - a / 2
};
}
const n = rt(i);
return { x: n[0], y: n[1] };
}
function Q(i) {
if (i.geometry.type === "Point")
return { x: i.geometry.coordinates[0], y: i.geometry.coordinates[1] };
const e = rt(i);
return { x: (e[0] + e[2]) / 2, y: (e[1] + e[3]) / 2 };
}
function kt(i) {
if (tt(i)) {
const [e, t] = i.geometry.coordinates[0], [n, s] = i.geometry.coordinates[1];
i.geometry.bbox = [
Math.min(e, n),
Math.min(t, s),
Math.max(e, n),
Math.max(t, s)
];
} else if (i.geometry.type === "Point" && "width" in i.properties && "height" in i.properties) {
const [e, t] = i.geometry.coordinates, n = i.properties.width / 2, s = i.properties.height / 2;
i.geometry.bbox = [e - n, t - s, e + n, t + s];
} else if (i.geometry.type === "Polygon") {
const e = i.geometry.coordinates[0];
let t = Number.POSITIVE_INFINITY, n = Number.POSITIVE_INFINITY, s = Number.NEGATIVE_INFINITY, r = Number.NEGATIVE_INFINITY;
for (const o of e) {
const a = o[0], l = o[1];
a < t && (t = a), l < n && (n = l), a > s && (s = a), l > r && (r = l);
}
i.geometry.bbox = [t, n, s, r];
}
}
function Mr(i, e, t, n, s) {
i.geometry.coordinates = [e + n / 2, t + s / 2], i.geometry.bbox = [e, t, e + n, t + s], i.properties.width = n, i.properties.height = s;
}
function sn(i) {
const [e, t] = i.geometry.coordinates[0];
return { x: e, y: t };
}
function Ht(i, e) {
const [t, n] = i.geometry.coordinates[e === X ? 0 : 1];
return { x: t, y: n };
}
function rn(i) {
const [e, t] = i.geometry.coordinates[1];
return { x: e, y: t };
}
function Vn(i, e, t) {
i.geometry.coordinates[0] = [e, t];
}
function Zn(i, e, t) {
i.geometry.coordinates[1] = [e, t];
}
function ge(i) {
return { start: sn(i), end: rn(i) };
}
function Kn(i, e, t, n) {
e === X ? Vn(i, t, n) : Zn(i, t, n);
}
const Tr = (i) => parseInt(i.getAttribute("data-handle-id") || "-1");
function Fr(i) {
return _t(i).reduce(
(e, t) => (e[0] = Math.min(t[0], e[0]), e[1] = Math.min(t[1], e[1]), e[2] = Math.max(t[0], e[2]), e[3] = Math.max(t[1], e[3]), e),
[
Number.POSITIVE_INFINITY,
Number.POSITIVE_INFINITY,
Number.NEGATIVE_INFINITY,
Number.NEGATIVE_INFINITY
]
);
}
function Jn(i) {
return Array.isArray(i) && i.length === 2 && i.every(isFinite);
}
function Pr(i, e, t, n) {
for (let s = 0; s < i.coordinates.length; s++) {
const r = i.coordinates[s];
if (Jn(r))
r[0] = t + (r[0] - t) * e, r[1] = n + (r[1] - n) * e;
else
for (let o = 0; o < r.length; o++) {
const a = r[o];
a[0] = t + (a[0] - t) * e, a[1] = n + (a[1] - n) * e;
}
}
return i;
}
function _t(i) {
let e = [];
if (i.type == "Point")
i.bbox ? e = [
[i.bbox[0], i.bbox[1]],
[i.bbox[2], i.bbox[3]]
] : e = [i.coordinates];
else if (i.type == "LineString" || i.type == "MultiPoint")
e = i.coordinates;
else if (i.type == "Polygon" || i.type == "MultiLineString")
e = i.coordinates.reduce(function(t, n) {
return t.concat(n);
}, []);
else if (i.type == "MultiPolygon")
e = i.coordinates.reduce(
(t, n) => t.concat(n.reduce((s, r) => s.concat(r), [])),
[]
);
else if (i.type == "Feature")
if (_(i) || W(i)) {
const t = rt(i);
e = [
[t[0], t[1]],
[t[2], t[3]]
];
} else e = _t(i.geometry);
else i.type == "GeometryCollection" ? e = i.geometries.reduce(
(t, n) => t.concat(_t(n)),
[]
) : i.type == "FeatureCollection" && (e = i.features.reduce(
(t, n) => t.concat(_t(n)),
[]
));
return e;
}
function Dr(i, e, t) {
const n = Math.atan2(i.y - e.y, i.x - e.x);
return {
x: e.x + t * Math.cos(n),
y: e.y + t * Math.sin(n)
};
}
function It(i, e) {
if (!e) return { x: i.clientX, y: i.clientY };
const t = e.getBoundingClientRect();
return {
x: i.clientX - t.left - e.clientLeft,
y: i.clientY - t.top - e.clientTop
};
}
function Lr(i, e) {
return i.startsWith("#") ? Qn(i, e) : i.startsWith("rgb") ? ts(i, e) : i;
}
function Hr(i) {
if (i.startsWith("#")) {
const e = fe(i), t = parseInt(e.slice(1, 3), 16), n = parseInt(e.slice(3, 5), 16), s = parseInt(e.slice(5, 7), 16);
return { r: t, g: n, b: s, a: 1 };
} else {
const e = i.match(
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)/
);
if (!e) throw new Error(`Invalid RGB color: ${i}`);
return {
r: parseInt(e[1]),
g: parseInt(e[2]),
b: parseInt(e[3]),
a: e[4] !== void 0 ? parseFloat(e[4]) : 1
};
}
}
function fe(i) {
return i.length === 4 ? i.split("").map((e) => e + e).join("") : i;
}
function Qn(i, e) {
const [t, n, s] = fe(i).match(/\w\w/g).map((r) => parseInt(r, 16));
return `rgba(${t}, ${n}, ${s}, ${e})`;
}
function ts(i, e) {
const [t, n, s] = i.match(/\d+/g).map((r) => parseInt(r, 10));
return `rgba(${t}, ${n}, ${s}, ${e})`;
}
function xe() {
return typeof window < "u" ? window : void 0;
}
const on = (i, e = 16, t = !1) => {
let n = !1, s = null;
const r = Math.max(1, Math.round(e * 60 / 1e3)), o = (a) => {
requestAnimationFrame(() => {
if (a > 1)
o(a - 1);
else if (n = !1, t && s !== null) {
const l = s;
s = null, i(...l), n = !0, o(r);
}
});
};
return (...a) => {
if (n) {
t && (s = a);
return;
}
i(...a), n = !0, o(r);
};
}, _r = (i, e) => {
let t;
return (...s) => {
clearTimeout(t), t = setTimeout(() => i(...s), e);
};
};
function Ir(i, e) {
let t;
return function(...n) {
clearTimeout(t), t = setTimeout(() => {
i.apply(this, n);
}, e);
};
}
function es(i) {
if ((W(i) || _(i)) && i.geometry.type === "Polygon") {
const e = i.geometry.coordinates[0], t = e[0][0], n = e[0][1], s = e[2][0], r = e[2][1], o = s - t, a = r - n, l = t + o / 2, h = n + a / 2;
return {
...i,
geometry: {
type: "Point",
coordinates: [l, h],
bbox: [t, n, s, r]
},
properties: {
...i.properties,
width: o,
height: a
}
};
}
return i;
}
function an(i, e) {
let t, n, s, r = 1;
const o = i.trim();
if (o.startsWith("#")) {
const d = (o.length === 4 ? fe(o) : o).match(/#?(\w{2})(\w{2})(\w{2})/);
if (!d) return i;
t = parseInt(d[1], 16), n = parseInt(d[2], 16), s = parseInt(d[3], 16);
} else {
const c = o.match(
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)/
);
if (!c) return i;
t = parseInt(c[1]), n = parseInt(c[2]), s = parseInt(c[3]), r = c[4] !== void 0 ? parseFloat(c[4]) : 1;
}
const a = e * 100, h = 0.299 * t + 0.587 * n + 0.114 * s > 186 ? -a / 2 : a;
return t = Math.max(0, Math.min(255, t + Math.round(2.55 * h))), n = Math.max(0, Math.min(255, n + Math.round(2.55 * h))), s = Math.max(0, Math.min(255, s + Math.round(2.55 * h))), `rgba(${t},${n},${s},${r})`;
}
const Ft = (i) => an(i, Je), zr = (i) => an(i, -Je), Y = (i, e) => ({
x: i.x - e.x,
y: i.y - e.y
}), J = (i) => Math.sqrt(i.x * i.x + i.y * i.y), ns = (i) => ({
x: -i.x,
y: -i.y
}), Nt = (i) => {
const e = J(i);
return e === 0 ? { x: 0, y: 0 } : {
x: i.x / e,
y: i.y / e
};
}, dt = (i, e) => ({
x: i.x + e.x,
y: i.y + e.y
}), ot = (i, e) => ({
x: i.x * e,
y: i.y * e
}), be = (i, e) => {
const t = Math.sin(e), n = Math.cos(e);
return {
x: i.x * n - i.y * t,
y: i.x * t + i.y * n
};
}, mt = (i, e) => i.x * e.x + i.y * e.y, ss = (i, e) => i.x * e.y - i.y * e.x, tt = (i) => i.properties.type === "arrow";
function cn(i, e, t) {
const { start: n, end: s } = ge(i), r = Y(e, n), o = i.properties.style.strokeWidth, a = Y(s, n), l = J(a), h = mt(r, Nt(a));
return h > 0 && h < l && Math.abs(ss(r, Nt(a))) < o / 2 + t;
}
const yt = {
strokeType: "plain",
strokeColor: "#202020",
strokeWidth: 1,
head: "none",
tail: "none"
}, Br = {
id: void 0,
// will be set by the editor
type: "Feature",
properties: {
type: "arrow",
style: {
...yt
}
},
geometry: {
type: "LineString",
coordinates: [
[0, 0],
[100, 100]
]
}
// type: 'arrow',
// stroke: {
// type: 'plain',
// color: 'black',
// width: 1
// },
// head: 'none',
// tail: 'arrow-plain',
// start: { x: 0, y: 0 },
// end: { x: 100, y: 100 }
}, Yt = (i = 0, e = 0, t = 0, n = 0, s = { ...yt }) => ({
id: ut(),
type: "Feature",
properties: {
type: "arrow",
style: {
...yt,
...s
}
},
geometry: {
type: "LineString",
coordinates: [
[i, e],
[t, n]
]
}
}), W = (i) => i.properties.type === "box";
function is(i, e, t = 0, n = 1, s = 0) {
const r = St(i), { width: o, height: a } = q(i), l = e.x - r.x, h = e.y - r.y, c = l * n - h * t, d = l * t + h * n;
return c > -s && c < o + s && d > -s && d < a + s;
}
const Mt = {
background: "#f5f5f5",
strokeWidth: 0,
borderRadius: 8,
boxShadow: "",
padding: 16,
strokeType: "plain"
}, Rr = {
id: void 0,
// will be set by the editor
type: "Feature",
properties: {
type: "box",
width: 100,
height: 50,
style: { ...Mt }
},
geometry: {
type: "Point",
coordinates: [50, 25],
// center of 100x50 box
bbox: [0, 0, 100, 50]
}
}, ve = (i = 0, e = 0, t = 100, n = 50, s = { ...Mt }) => ({
id: ut(),
type: "Feature",
properties: {
type: "box",
width: t,
height: n,
style: { ...Mt, ...s }
},
geometry: {
type: "Point",
coordinates: [i + t / 2, e + n / 2]
// center
}
}), _ = (i) => i.properties.type === "text", G = {
font: "sans-serif",
fontSize: 18,
color: "#505050",
background: "#f5f5f5",
strokeWidth: 0,
borderRadius: 8,
padding: 16,
strokeType: "plain",
fixedSize: !1
}, $r = {
id: ut(),
type: "Feature",
properties: {
type: "text",
content: "",
width: 100,
height: 50,
style: { ...G }
},
geometry: {
type: "Point",
coordinates: [50, 25],
// center of 100x50 box
bbox: [0, 0, 100, 50]
}
}, Se = (i = 0, e = 0, t = 100, n = 50, s = "", r = { ...G }) => ({
id: ut(),
type: "Feature",
properties: {
type: "text",
content: s,
width: t,
height: n,
style: { ...G, ...r }
},
geometry: {
type: "Point",
coordinates: [i + t / 2, e + n / 2],
// center
bbox: [i, e, i + t, e + n]
}
});
function ln(i, e, t = 0, n = 0, s = 1, r = 1) {
var m;
const [o, a] = i.geometry.coordinates;
let { width: l, height: h } = q(i);
(m = i.properties.style) != null && m.fixedSize && (l /= r, h /= r);
const c = l / 2, d = h / 2, u = e.x - o, g = e.y - a, f = u * s - g * n, p = u * n + g * s;
return f > -c - t && f < c + t && p > -d - t && p < d + t;
}
const P = (i) => i.properties.type === "comment", V = {
// Box styling
background: "#FFFACD",
// Light yellow (sticky note color)
padding: 8,
borderRadius: 4,
strokeColor: "#DDD",
strokeWidth: 1,
strokeType: "plain",
// Icon styling (collapsed mode)
iconColor: "#FFFACD",
// Gold
iconSymbol: "💬",
iconBorderColor: "#aaa",
iconBorderWidth: 1,
// Size properties
minHeight: 60,
maxHeight: 480,
iconSize: 32,
// collapseZoomThreshold is undefined by default, so it auto-calculates from dimensions
// Text styling
color: "#333",
font: "Arial, sans-serif",
fontSize: 12,
// Editing UI
showSendButton: !0,
autoGrow: !0,
// Visual effects
shadow: !0,
expandOnSelect: !1,
// Fixed size (always screen-aligned)
fixedSize: !0
}, Gt = {
mode: "expanded",
width: 200,
height: 60,
// Initial height, will auto-grow
style: V
};
function hn(i, e, t, n) {
const s = {
type: "comment",
content: t,
mode: (n == null ? void 0 : n.mode) ?? Gt.mode,
width: (n == null ? void 0 : n.width) ?? Gt.width,
height: (n == null ? void 0 : n.height) ?? Gt.height,
author: n == null ? void 0 : n.author,
timestamp: n == null ? void 0 : n.timestamp,
style: {
...V,
...n == null ? void 0 : n.style
}
};
return {
id: ut(),
type: "Feature",
properties: s,
geometry: {
type: "Point",
coordinates: [i, e]
}
};
}
function Nr(i) {
return {
...i,
properties: {
...i.properties,
mode: i.properties.mode === at ? Rt : at
}
};
}
function rs(i, e, t = 0, n, s, r = 1) {
if (i.properties.mode !== at)
return ln(
i,
e,
t,
n,
s,
r
);
const o = i.properties, a = { ...V, ...o.style };
let l = a.iconSize, h = a.iconSize;
a.fixedSize && (l /= r, h /= r);
const c = l / 2, d = h / 2, u = e.x - i.geometry.coordinates[0], g = e.y - i.geometry.coordinates[1];
return u >= -c - t && u <= c + t && g >= -d - t && g <= d + t;
}
function Yr(i) {
const [e, t] = i.geometry.coordinates;
return { x: e, y: t };
}
function Or(i) {
const e = i.properties, t = { ...V, ...e.style };
return e.mode === at ? { width: t.iconSize, height: t.iconSize } : { width: e.width, height: e.height };
}
function os(i, e = 80) {
const t = e / i.properties.width;
return Math.max(0.1, Math.min(1, t));
}
function Xr(i) {
const e = { ...V, ...i.properties.style };
return e.collapseZoomThreshold !== void 0 ? e.collapseZoomThreshold : os(i);
}
function Ur(i, e, t, n, s = "", r) {
const o = hn(
t,
n,
s,
r == null ? void 0 : r.commentStyle
), a = {
...yt,
head: "arrow",
// Default to arrow head
...r == null ? void 0 : r.arrowStyle
}, l = Yt(t, n, i, e, a);
return l.properties.link = {
[X]: {
id: o.id,
side: X,
type: "comment",
magnet: { x: 0, y: 0 }
}
}, { comment: o, arrow: l };
}
function as(i, e, t, n, s, r, o, a, l) {
const h = i - o, c = e - a, d = o + h * r - c * s, u = a + h * s + c * r, g = o + (h + t) * r - c * s, f = a + (h + t) * s + c * r, p = o + (h + t) * r - (c + n) * s, m = a + (h + t) * s + (c + n) * r, w = o + h * r - (c + n) * s, b = a + h * s + (c + n) * r;
return l[0] = Math.min(d, g, p, w), l[1] = Math.min(u, f, m, b), l[2] = Math.max(d, g, p, w), l[3] = Math.max(u, f, m, b), l;
}
function cs(i, e, t) {
const n = t.x - e.x, s = t.y - e.y, r = n * n + s * s;
if (r === 0) {
const d = i.x - e.x, u = i.y - e.y;
return Math.sqrt(d * d + u * u);
}
let o = ((i.x - e.x) * n + (i.y - e.y) * s) / r;
o = Math.max(0, Math.min(1, o));
const a = e.x + o * n, l = e.y + o * s, h = i.x - a, c = i.y - l;
return Math.sqrt(h * h + c * c);
}
const st = (i) => i.properties.type === "polygon";
function dn(i, e, t = 0) {
const n = i.geometry.coordinates[0], s = i.geometry.bbox;
if (s) {
const [o, a, l, h] = s;
if (e.x < o - t || e.x > l + t || e.y < a - t || e.y > h + t)
return !1;
}
let r = !1;
for (let o = 0, a = n.length - 1; o < n.length; a = o++) {
const l = n[o][0], h = n[o][1], c = n[a][0], d = n[a][1];
h > e.y != d > e.y && e.x < (c - l) * (e.y - h) / (d - h) + l && (r = !r);
}
if (r) return !0;
if (t > 0)
for (let o = 0; o < n.length - 1; o++) {
const [a, l] = n[o], [h, c] = n[o + 1];
if (cs(e, { x: a, y: l }, { x: h, y: c }) <= t) return !0;
}
return !1;
}
function ls(i, e) {
const t = i[0];
t.length > 0 && (t[0][0] !== t[t.length - 1][0] || t[0][1] !== t[t.length - 1][1]) && t.push([t[0][0], t[0][1]]);
let n = 1 / 0, s = 1 / 0, r = -1 / 0, o = -1 / 0;
for (const [d, u] of t)
d < n && (n = d), d > r && (r = d), u < s && (s = u), u > o && (o = u);
const a = [n, s, r, o], { id: l, style: h, ...c } = e || {};
return {
type: "Feature",
id: l || ut(),
geometry: {
type: "Polygon",
coordinates: i,
bbox: a
},
properties: {
type: "polygon",
style: {
...lt,
...h
},
...c
}
};
}
const lt = {
...Mt,
strokeColor: "#000000",
strokeWidth: 2,
background: "transparent"
}, qr = {
type: "polygon",
style: {
...lt
}
}, Ae = (i) => i.type === "FeatureCollection";
function un(i) {
return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(i.trim());
}
function gn(i) {
return /^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/.test(i.trim());
}
function fn(i) {
return /^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[0-9.]+\s*\)$/.test(
i.trim()
);
}
function hs(i) {
return un(i) || gn(i) || fn(i);
}
function Wr(i) {
if (hs(i)) return i;
throw new Error(`Invalid color format: ${i}`);
}
function jr(i) {
if (un(i)) return i;
throw new Error(`Invalid hex color format: ${i}`);
}
function Gr(i) {
if (gn(i)) return i;
throw new Error(`Invalid RGB color format: ${i}`);
}
function Vr(i) {
if (fn(i)) return i;
throw new Error(`Invalid RGBA color format: ${i}`);
}
class ds {
constructor(e) {
y(this, "previousZoom", -1);
this.store = e;
}
/**
* Toggle a comment between collapsed and expanded mode
* @param id The id of the comment to toggle
*/
toggleComment(e) {
const t = this.store.getState().getFeature(e);
if (!t || !P(t)) return;
const n = t;
this.store.getState().applyLiveUpdate(e, {
properties: {
...n.properties,
mode: n.properties.mode === "collapsed" ? "expanded" : "collapsed"
}
});
}
/**
* Update comment modes based on current zoom level
* Uses live updates to avoid creating undo/redo history entries
* @param zoom Current zoom level
*/
updateCommentModesForZoom(e) {
if (Math.abs(this.previousZoom - e) < 5e-4) return;
this.previousZoom = e;
const t = this.store.getState(), n = t.features, s = {};
Object.values(n).forEach((r) => {
if (P(r)) {
const o = r, a = this.getCommentZoomThreshold(o), l = e < a ? at : Rt;
if (o.properties.mode === l) return;
s[o.id] = {
properties: {
...o.properties,
mode: l
}
};
}
}), t.applyLiveUpdates(s);
}
/**
* Get the effective zoom threshold for a comment
* Uses explicit threshold if set, otherwise calculates from dimensions
* @param comment Comment to get threshold for
* @returns Zoom threshold
*/
getCommentZoomThreshold(e) {
const t = { ...e.properties.style };
if (t.collapseZoomThreshold !== void 0)
return t.collapseZoomThreshold;
const s = 80 / e.properties.width;
return Math.max(0.1, Math.min(1, s));
}
}
class qt extends EventTarget {
constructor(t, n) {
super();
y(this, "annotation", null);
y(this, "ogma");
y(this, "dragging", !1);
y(this, "dragStartPoint");
y(this, "hoveredHandle");
y(this, "ogmaPanningOption", !1);
y(this, "store");
y(this, "draggingWasEnabled", !0);
y(this, "handleMouseMove", (t) => {
if (t.target instanceof HTMLTextAreaElement || !this.isActive()) return;
const n = this.store.getState();
if (!this.dragging && n.mousePressed && n.mousePressPoint && (this.detectHandle(t, this.ogma.view.getZoom()), this.hoveredHandle)) {
this.dragStartPoint = n.mousePressPoint, this.onDragStart(t), this.dispatchEvent(new CustomEvent(Ct, {
detail: {
id: this.annotation,
position: {
x: t.clientX,
y: t.clientY
}
}
})), this.disablePanning();
return;
}
this.dragging ? this.dragStartPoint && this.onDrag(t) : this.detectHandle(t, this.ogma.view.getZoom());
});
y(this, "handleMouseDown", (t) => {
t.target instanceof HTMLTextAreaElement || !this.isActive() || this.dragging || this.hoveredHandle || this.detectHandle(t, this.ogma.view.getZoom());
});
y(this, "disablePanning", () => {
var t, n;
this.ogmaPanningOption = !!((n = (t = this.ogma.getOptions().interactions) == null ? void 0 : t.pan) != null && n.enabled), this.ogma.setOptions({
interactions: { pan: { enabled: !1 }, drag: { enabled: !1 } }
});
});
y(this, "restorePanning", () => {
this.ogma.setOptions({
interactions: { pan: { enabled: !0 }, drag: { enabled: !0 } }
});
});
y(this, "handleMouseUp", (t) => {
this.isActive() && this.dragging && (this.restorePanning(), this.onDragEnd(t), this.dispatchEvent(new CustomEvent(Bt, {
detail: {
id: this.annotation,
position: {
x: t.clientX,
y: t.clientY
}
}
})));
});
y(this, "onClick", (t) => {
});
this.store = n, this.ogma = t, this.store.subscribe(
(s) => s.features,
(s) => {
this.isActive() && !s[this.annotation] && this.stopEditing();
}
);
}
cancelEdit() {
!this.isActive() || this.annotation === null || this.clearDragState();
}
clearDragState() {
this.dragging = !1, this.dragStartPoint = void 0, this.hoveredHandle = void 0, this.restorePanning(), this.setCursor(I.default);
}
commitChange() {
this.getAnnotation() && this.store.getState().commitLiveUpdates();
}
/**
* Handles the dragging of the selected handle.
* @param evt Mouse event
*/
onDrag(t) {
this.dispatchEvent(new Event(Ut));
}
onDragStart(t) {
return this.isActive() ? (this.dragging = !0, this.dragStartPoint = this.clientToCanvas(t), this.disablePanning(), !0) : !1;
}
onDragEnd(t) {
return this.isActive() ? (this.restorePanning(), this.dragging = !1, !0) : !1;
}
clientToCanvas(t) {
const n = this.ogma, s = It(t, n.getContainer());
return n.view.screenToGraphCoordinates(s);
}
setAnnotation(t) {
if (this.annotation = t ? t.id : null, this.annotation !== null) {
const n = this.ogma.getContainer();
if (n) {
const s = xe() || n;
s.addEventListener("mousemove", this.handleMouseMove), s.addEventListener("mouseup", this.handleMouseUp, !1), n.addEventListener("mousedown", this.handleMouseDown, !0), s.addEventListener("click", this.onClick, !0);
}
} else {
const n = this.ogma.getContainer();
if (n) {
const s = xe() || n;
s.removeEventListener("mousemove", this.handleMouseMove), s.removeEventListener("mouseup", this.handleMouseUp), n.removeEventListener("mousedown", this.handleMouseDown), s.removeEventListener("click", this.onClick);
}
this.clearDragState(), this.setCursor(I.default);
}
}
getAnnotation(t) {
const n = this.store.getState(), s = n.getFeature(this.annotation);
if (!t)
return s;
const r = n.liveUpdates[this.annotation];
return s && r ? { ...s, ...r } : s;
}
setCursor(t) {
var s;
const n = (s = this.ogma.getContainer()) == null ? void 0 : s.firstChild;
n && (n.style.cursor = t);
}
stopEditing() {
this.isActive() && this.setAnnotation(null);
}
cancelDrawing() {
if (!this.isActive()) return;
const t = this.store.getState();
t.drawingFeature === this.annotation && t.removeFeature(this.annotation), this.stopEditing();
}
isActive() {
return this.annotation !== null;
}
}
class us extends qt {
constructor(t, n, s, r, o, a, l) {
super(t, n);
y(this, "links");
y(this, "snapping");
y(this, "arrowHandler");
y(this, "arrowStyle");
y(this, "offsetX");
y(this, "offsetY");
y(this, "comment");
y(this, "startX", 0);
y(this, "startY", 0);
y(this, "onArrowComplete", () => {
var v;
this.arrowHandler.removeEventListener("dragend", this.onArrowComplete);
const t = this.store.getState(), n = Array.from(t.selectedFeatures);
if (n.length === 0) return;
const s = t.getFeature(n[0]);
if (!s || !tt(s)) return;
const r = s.geometry.coordinates[0], o = s.geometry.coordinates[1], a = o[0] - this.startX, l = o[1] - this.startY, h = Math.sqrt(a * a + l * l);
let c, d;
h < 5 ? (c = this.startX + this.offsetX, d = this.startY + this.offsetY) : (c = o[0], d = o[1]);
const u = this.comment;
u.geometry.coordinates = [c, d], t.addFeature(u), this.store.setState({ drawingFeature: u.id });
const g = u.properties.height, f = t.zoom, p = g / f, m = c, w = d + p * 0.5;
this.snapArrowStart(s, r[0], r[1]);
const b = (v = s.properties.link) == null ? void 0 : v.start;
t.updateFeature(s.id, {
...s,
geometry: {
...s.geometry,
coordinates: [
[m, w],
// Start: comment bottom edge
r
// End: original mousedown point (with any snapping from ArrowHandler)
]
},
properties: {
...s.properties,
link: {
start: {
side: X,
id: u.id,
type: T.COMMENT,
magnet: { x: 0, y: 0.5 }
},
// If ArrowHandler snapped to something at the original start point,
// that becomes the end point now
end: b ? {
side: it,
id: b.id,
type: b.type,
magnet: b.magnet
} : void 0
}
}
}), this.links.add(s, X, u.id, T.COMMENT, {
x: 0,
y: 0.5
}), b && b.magnet && this.links.add(
s,
it,
b.id,
b.type,
b.magnet
), t.setSelectedFeatures([u.id]), this.store.setState({ drawingFeature: null });
});
this.links = s, this.arrowHandler = o, this.snapping = r, this.offsetX = (l == null ? void 0 : l.offsetX) ?? 100, this.offsetY = (l == null ? void 0 : l.offsetY) ?? -50, this.arrowStyle = l == null ? void 0 : l.arrowStyle, this.comment = a;
}
detectHandle(t, n) {
}
startDrawing(t, n, s) {
var o;
this.startX = n, this.startY = s;
const r = Yt(n, s, n, s, {
...yt,
head: "arrow",
...(o = this.arrowStyle) == null ? void 0 : o.style
});
this.store.getState().addFeature(r), this.store.setState({ drawingFeature: r.id }), this.arrowHandler.addEventListener("dragend", this.onArrowComplete), this.store.getState().setSelectedFeatures([r.id]), this.arrowHandler.startDrawing(r.id, n, s);
}
snapArrowStart(t, n, s) {
const r = this.snapping.snap({ x: n, y: s });
r && (t.geometry.coordinates[0] = [r.point.x, r.point.y], t.properties.link = {
start: {
side: X,
id: r.id,
type: r.type,
magnet: r.magnet
}
}, this.links.add(t, X, r.id, r.type, r.magnet));
}
}
function gs(i, e, t, n, s) {
const { width: r, height: o } = n.view.getSize(), a = 8, l = 16, h = a * l, c = { width: 150, height: 50 }, d = c.width, u = c.height, g = Math.max(r, o), f = 200, p = {
minX: 0,
minY: 0,
maxX: 0,
maxY: 0
}, m = n.view.graphToScreenCoordinates({ x: i, y: e });
for (let w = 0; w < h; w++) {
const b = w % a, v = Math.floor(w / a), S = 2 * Math.PI * (b / a), x = f + v / l * (g - f), A = Math.cos(S) * x, C = Math.sin(S) * x, E = m.x + A, k = m.y + C;
if (p.minX = E, p.minY = k - u / 2, p.maxX = E + d, p.maxY = k + u / 2, A < 0 && (p.minX -= d, p.maxX -= d), p.minX < 0 || p.maxX > r || p.minY < 0 || p.maxY > o)
continue;
const F = n.view.screenToGraphCoordinates({
x: p.minX,
y: p.minY
}), O = n.view.screenToGraphCoordinates({
x: p.maxX,
y: p.maxY
});
if (p.minX = F.x, p.minY = F.y, p.maxX = O.x, p.maxY = O.y, !t.query(p).filter((B) => B.properties.type !== "arrow").length)
return n.view.screenToGraphCoordinates({ x: E, y: k });
}
return n.view.screenToGraphCoordinates({ x: r / 2, y: o / 2 });
}
class fs {
constructor(e, t, n, s, r, o, a) {
y(this, "ogma");
y(this, "store");
y(this, "editor");
y(this, "interactions");
y(this, "links");
y(this, "control");
y(this, "index");
// Track pending drawing listener to clean up on cancel
y(this, "pendingDrawingListener", null);
// Track placement mode listeners for cleanup
y(this, "placementCleanup", null);
this.ogma = e, this.store = t, this.editor = n, this.interactions = s, this.links = r, this.control = o, this.index = a;
}
/**
* Cancel the current drawing operation
* @internal
*/
cancelPendingDrawing() {
this.pendingDrawingListener && (this.ogma.events.off(this.pendingDrawingListener), this.pendingDrawingListener = null), this.placementCleanup && (this.placementCleanup(), this.placementCleanup = null);
}
isDrawing() {
return this.store.getState().drawingFeature !== null;
}
/**
* Helper method to enable drawing mode with proper cleanup
* @private
*/
enableDrawingMode(e) {
this.control.unselect().cancelDrawing();
const t = (n) => {
this.ogma.events.off(t), this.pendingDrawingListener = null;
const { x: s, y: r } = this.ogma.view.screenToGraphCoordinates(n);
e(s, r);
};
return this.pendingDrawingListener = t, this.ogma.events.once("mousedown", t), this.control;
}
enableArrowDrawing(e) {
return this.enableDrawingMode((t, n) => {
const s = Yt(t, n, t, n, e);
this.startArrow(t, n, s);
});
}
enableTextDrawing(e) {
return this.enableDrawingMode((t, n) => {
const s = Se(t, n, 0, 0, void 0, e);
this.startText(t, n, s);
});
}
/**
* @param style Box style options
* @returns Control instance for chaining
* @see startBox for low-level programmatic control
*/
enableBoxDrawing(e) {
return this.enableDrawingMode((t, n) => {
const s = ve(t, n, 0, 0, e);
this.startBox(t, n, s);
});
}
/**
* @param style Polygon style options
* @returns Control instance for chaining
* @see startPolygon for low-level programmatic control
*/
enablePolygonDrawing(e) {
return this.enableDrawingMode((t, n) => {
const s = ls([[[t, n]]], { style: e });
this.startPolygon(t, n, s);
});
}
/**
* @param options Drawing options including offsets and styles
* @param options.offsetX Manual X offset for comment placement (overrides smart positioning)
* @param options.offsetY Manual Y offset for comment placement (overrides smart positioning)
* @param options.commentStyle Style options for the comment box
* @param options.arrowStyle Style options for the arrow
* @returns Control instance for chaining
* @see startComment for low-level programmatic control
*/
enableCommentDrawing(e = {}) {
return this.enableDrawingMode((t, n) => {
let s = e.offsetX, r = e.offsetY;
if (s === void 0 && r === void 0) {
const a = gs(t, n, this.index, this.ogma);
s = a.x, r = a.y;
}
const o = hn(t, n, "", e == null ? void 0 : e.commentStyle);
this.startComment(t, n, o, {
...e,
offsetX: s,
offsetY: r
});
});
}
/**
* Place a pre-created annotation by moving it with the cursor.
* The annotation follows the mouse until the user clicks to place it.
* Press Escape to cancel.
*/
enablePlacement(e) {
this.control.unselect().cancelDrawing(), this.store.setState({ drawingFeature: e.id }), this.control.add(e);
const t = this.ogma.getContainer();
if (!t) return this.control;
const n = (a) => {
const { x: l, y: h } = this.ogma.view.screenToGraphCoordinates(a), c = e.properties.width, d = e.properties.height;
this.store.getState().applyLiveUpdate(e.id, {
geometry: {
...e.geometry,
coordinates: [l, h],
bbox: [l - c / 2, h - d / 2, l + c / 2, h + d / 2]
}
});
}, s = (a) => {
o();
const { x: l, y: h } = this.ogma.view.screenToGraphCoordinates(a), c = e.properties.width, d = e.properties.height;
this.store.getState().updateFeature(e.id, {
geometry: {
...e.geometry,
coordinates: [l, h],
bbox: [l - c / 2, h - d / 2, l + c / 2, h + d / 2]
}
}), this.store.setState({ drawingFeature: null }), this.interactions.suppressClicksTemporarily(200), this.control.select(e.id);
}, r = (a) => {
a.key === "Escape" && (o(), this.control.remove(e), this.store.setState({ drawingFeature: null }));
}, o = () => {
t.removeEventListener("mousemove", n), this.ogma.events.off(s), document.removeEventListener("keydown", r), this.placementCleanup = null;
};
return t.addEventListener("mousemove", n, { passive: !0 }), this.ogma.events.once("mousedown", s), document.addEventListener("keydown", r), this.placementCleanup = o, this.control;
}
startComment(e, t, n, s) {
this.editor.getActiveHandler() && this.editor.getActiveHandler().stopEditing(), this.control.cancelDrawing(), this.store.setState({ drawingFeature: n.id }), this.interactions.suppressClicksTemporarily(200);
const r = new us(
this.ogma,
this.store,
this.links,
this.editor.getSnapping(),
this.editor.getArrowHandler(),
n,
s
), o = (a) => {
var l;
a.id === n.id && (this.control.select(a.id), this.control.off(ae, o), (l = this.editor.getActiveHandler()) == null || l.startEditingText());
};
return this.control.on(ae, o), r.startDrawing(n.id, e, t), this.control;
}
startBox(e, t, n) {
return n || (n = ve(e, t)), this.store.setState({ drawingFeature: n.id }), this.control.add(n), this.interactions.suppressClicksTemporarily(200), this.control.select(n.id), this.editor.getActiveHandler().startDrawing(n.id, e, t), this.control;
}
startArrow(e, t, n) {
return n || (n = Yt(e, t)), this.editor.getActiveHandler() && this.editor.getActiveHandler().stopEditing(), this.control.cancelDrawing(), this.store.setState({ drawingFeature: n.id }), this.control.add(n), this.interactions.suppressClicksTemporarily(200), this.control.select(n.id), this.editor.getActiveHandler().startDrawing(n.id, e, t), this.control;
}
startText(e, t, n) {
return n || (n = Se(e, t)), this.store.setState({ drawingFeature: n.id }), this.control.add(n), this.interactions.suppressClicksTemporarily(200), this.control.select(n.id), this.editor.getActiveHandler().startDrawing(n.id, e, t), this.control;
}
startPolygon(e, t, n) {
return this.store.setState({ drawingFeature: n.id }), this.control.add(n), this.interactions.suppressClicksTemporarily(200), this.control.select(n.id), this.editor.getActiveHandler().startDrawing(n.id, e, t), this.control;
}
}
class ps {
constructor(e, t) {
this.store = e, this.links = t;
}
/**
* Undo the last change
* @returns true if undo was successful, false if no changes to undo
*/
undo() {
return this.canUndo() ? (this.store.temporal.getState().undo(), this.links.refresh(), !0) : !1;
}
/**
* Redo the last undone change
* @returns true if redo was successful, false if no changes to redo
*/
redo() {
return this.canRedo() ? (this.store.temporal.getState().redo(), this.links.refresh(), !0) : !1;
}
/**
* Check if there are changes to undo
* @returns true if undo is possible
*/
canUndo() {
return this.store.temporal.getState().pastStates.length > 0;
}
/**
* Check if there are changes to redo
* @returns true if redo is possible
*/
canRedo() {
return this.store.temporal.getState().futureStates.length > 0;
}
/**
* Clear the undo/redo history
*/
clearHistory() {
this.store.temporal.getState().clear();
}
}
class ms {
constructor(e) {
this.store = e;
}
/**
* Select one or more annotations by id
* @param annotations The id(s) of the annotation(s) to select
*/
select(e) {
const t = Array.isArray(e) ? e : [e];
this.store.getState().setSelectedFeatures(t);
}
/**
* Unselect one or more annotations, or all if no ids provided
* @param annotations The id(s) of the annotation(s) to unselect, or undefined to unselect all
*/
unselect(e) {
const t = Array.isArray(e) ? e : [e];
if (e === void 0)
this.store.getState().setSelectedFeatures([]);
else {
const n = new Set(t), s = Array.from(
this.store.getState().selectedFeatures
).filter((r) => !n.has(r));
this.store.getState().setSelectedFeatures(s);
}
}
/**
* Get the currently selected annotations as a collection
* @returns A FeatureCollection of selected annotations
*/
getSelectedAnnotations() {
const e = this.store.getState();
return {
type: "FeatureCollection",
features: Array.from(e.selectedFeatures).map(
(t) => e.features[t]
)
};
}
/**
* Get the first selected annotation (for backwards compatibility)
* @returns The currently selected annotation, or null if none selected
*/
getSelected() {
const e = this.store.getState(), t = Array.from(e.selectedFeatures)[0];
return t ? e.features[t] : null;
}
}
class ys {
constructor(e) {
this.store = e;
}
/**
* Add an annotation to the controller
* @param annotation The annotation to add
*/
add(e) {
if (Ae(e))
for (const t of e.features) this.add(t);
else {
const t = es(e);
this.store.getState().addFeature(t);
}
}
/**
* Remove an annotation or an array of annotations from the controller
* @param annotation The annotation(s) to remove
*/
remove(e) {
if (Ae(e))
for (const t of e.features) this.remove(t);
else
this.store.getState().removeFeature(e.id);
}
/**
* Get all annotations in the controller
* @returns A FeatureCollection containing all annotations
*/
getAnnotations() {
const e = this.store.getState().features;
return {
type: "FeatureCollection",
features: Object.values(e)
};
}
/**
* Get a specific annotation by id
* @param id The id of the annotation to retrieve
* @returns The annotation with the given id, or undefined if not found
*/
getAnnotation(e) {
return this.store.getState().getFeature(e);
}
/**
* Update the style of the annotation with the given id
* @param id The id of the annotation to update
* @param style The new style
*/
updateStyle(e, t) {
const n = this.store.getState().getFeature(e);
n && this.store.getState().updateFeature(e, {
id: e,
properties: {
...n.properties,
style: {
...n.properties.style,
...t
}
}
});
}
/**
* Update an annotation with partial updates
*
* This method allows you to update any properties of an annotation, including
* geometry, properties, and style. Updates are merged with existing data.
*
* @param annotation Partial annotation object with id and properties to update
*
* @example
* ```ts
* // Update arrow geometry
* controller.update({
* id: arrowId,
* geometry: {
* type: 'LineString',
* coordinates: [[0, 0], [200, 200]]
* }
* });
*
* // Update text content and position
* controller.update({
* id: textId,
* geometry: {
* type: 'Point',
* coordinates: [100, 100]
* },
* properties: {
* content: 'Updated text'
* }
* });
*
* // Update style only (prefer updateStyle for style-only updates)
* controller.update({
* id: boxId,
* properties: {
* style: {
* background: '#ff0000'
* }
* }
* });
* ```
*/
update(e) {
var s;
const t = this.store.getState(), n = t.getFeature(e.id);
n && t.updateFeature(e.id, {
...n,
...e,
properties: {
...n.properties,
...e.properties,
style: {
...n.properties.style,
...(s = e.properties) == null ? void 0 : s.style
}
},
geometry: {
...n.geometry,
...e.geometry
}
});
}
/**
* Scale an annotation by a given factor around an origin point
* @param id The id of the annotat