@myrmidon/gve-snapshot-rendition 2.0.7 → 2.0.10
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/dist/hint-designer/gve-hint-designer.d.ts +17 -0
- package/dist/index.cjs.min.js +4 -4
- package/dist/index.cjs.min.js.map +1 -1
- package/dist/index.js +642 -433
- package/dist/index.js.map +1 -1
- package/dist/rendering/svg-utils.d.ts +18 -1
- package/dist/settings/settings.d.ts +4 -0
- package/dist/utils/text-utils.d.ts +5 -1
- package/package.json +74 -82
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const DEFAULT_SETTINGS = {
|
|
|
12
12
|
underline: undefined,
|
|
13
13
|
overline: undefined,
|
|
14
14
|
strike: undefined,
|
|
15
|
+
textLineStyle: undefined,
|
|
16
|
+
textLineColor: undefined,
|
|
15
17
|
// Spacing settings
|
|
16
18
|
spaceWidthFraction: 0.33,
|
|
17
19
|
charSpacing: 2,
|
|
@@ -695,9 +697,13 @@ function applyTextStyle(element, style) {
|
|
|
695
697
|
attrs["font-style"] = "italic";
|
|
696
698
|
if (style.bold)
|
|
697
699
|
attrs["font-weight"] = "bold";
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
//
|
|
700
|
+
setAttributes(element, attrs);
|
|
701
|
+
// Text decorations. Applied via inline CSS longhands (not the SVG
|
|
702
|
+
// text-decoration attribute) so that thickness, line style and color are
|
|
703
|
+
// honored by the text layout engine and follow the glyphs when the
|
|
704
|
+
// character is rotated. NOTE: per-character rotation is NOT handled here,
|
|
705
|
+
// because a plain rotate() would spin the glyph around the SVG origin;
|
|
706
|
+
// renderers apply rotate(deg, x, y) around each character's own anchor.
|
|
701
707
|
const decorations = [];
|
|
702
708
|
if (style.underline)
|
|
703
709
|
decorations.push("underline");
|
|
@@ -706,9 +712,20 @@ function applyTextStyle(element, style) {
|
|
|
706
712
|
if (style.strike)
|
|
707
713
|
decorations.push("line-through");
|
|
708
714
|
if (decorations.length > 0) {
|
|
709
|
-
|
|
715
|
+
element.style.textDecorationLine = decorations.join(" ");
|
|
716
|
+
// CSS has a single thickness for all decoration lines of an element:
|
|
717
|
+
// use the first non-zero one (they are rarely combined).
|
|
718
|
+
const thickness = style.underline || style.overline || style.strike;
|
|
719
|
+
if (thickness && thickness > 0) {
|
|
720
|
+
element.style.textDecorationThickness = `${thickness}px`;
|
|
721
|
+
}
|
|
722
|
+
if (style.textLineStyle) {
|
|
723
|
+
element.style.textDecorationStyle = style.textLineStyle;
|
|
724
|
+
}
|
|
725
|
+
if (style.textLineColor) {
|
|
726
|
+
element.style.textDecorationColor = style.textLineColor;
|
|
727
|
+
}
|
|
710
728
|
}
|
|
711
|
-
setAttributes(element, attrs);
|
|
712
729
|
}
|
|
713
730
|
/**
|
|
714
731
|
* Parse SVG from string and return the root element.
|
|
@@ -803,6 +820,32 @@ function getTransformedBBox(element) {
|
|
|
803
820
|
return new DOMRect(0, 0, 0, 0);
|
|
804
821
|
}
|
|
805
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* Compute the axis-aligned bounding box of a rectangle rotated around a
|
|
825
|
+
* point, using SVG rotation direction (positive = clockwise, y-axis down).
|
|
826
|
+
*
|
|
827
|
+
* Used to cache correct bounds for per-character rotated text: getBBox()
|
|
828
|
+
* returns LOCAL coordinates which ignore the rotate(deg, x, y) transform
|
|
829
|
+
* applied to the element, while getTransformedBBox() would also bake in
|
|
830
|
+
* ancestor transforms (e.g. the pan/zoom viewport), which must stay out
|
|
831
|
+
* of the bounds cache.
|
|
832
|
+
*/
|
|
833
|
+
function rotateRectAroundPoint(rect, degrees, cx, cy) {
|
|
834
|
+
const rad = (degrees * Math.PI) / 180;
|
|
835
|
+
const cos = Math.cos(rad);
|
|
836
|
+
const sin = Math.sin(rad);
|
|
837
|
+
const corners = [
|
|
838
|
+
[rect.x, rect.y],
|
|
839
|
+
[rect.x + rect.width, rect.y],
|
|
840
|
+
[rect.x, rect.y + rect.height],
|
|
841
|
+
[rect.x + rect.width, rect.y + rect.height],
|
|
842
|
+
];
|
|
843
|
+
const xs = corners.map(([x, y]) => cx + (x - cx) * cos - (y - cy) * sin);
|
|
844
|
+
const ys = corners.map(([x, y]) => cy + (x - cx) * sin + (y - cy) * cos);
|
|
845
|
+
const minX = Math.min(...xs);
|
|
846
|
+
const minY = Math.min(...ys);
|
|
847
|
+
return new DOMRect(minX, minY, Math.max(...xs) - minX, Math.max(...ys) - minY);
|
|
848
|
+
}
|
|
806
849
|
/**
|
|
807
850
|
* Get computed text width for a given text and style.
|
|
808
851
|
* Creates a temporary SVG element to measure.
|
|
@@ -921,16 +964,30 @@ function parseOffset(value, textHeight, textWidth) {
|
|
|
921
964
|
}
|
|
922
965
|
/**
|
|
923
966
|
* Replace placeholders in SVG content.
|
|
924
|
-
* Placeholders are in the format {{featureName}}
|
|
967
|
+
* Placeholders are in the format {{featureName}}, or a fallback chain
|
|
968
|
+
* {{name ?? other ?? ...}}: names are tried left to right and the first one
|
|
969
|
+
* having a variable wins. There is no limit to the number of fallbacks.
|
|
970
|
+
* For instance {{r_fore-color ?? r_color}} resolves to the value of
|
|
971
|
+
* r_fore-color when defined, otherwise to the value of r_color.
|
|
925
972
|
*
|
|
926
973
|
* @param svgContent - The SVG content with placeholders
|
|
927
974
|
* @param variables - Map of variable names to values
|
|
928
975
|
* @returns SVG content with placeholders replaced
|
|
929
976
|
*/
|
|
930
977
|
function resolvePlaceholders(svgContent, variables) {
|
|
931
|
-
// Match {{
|
|
932
|
-
|
|
933
|
-
|
|
978
|
+
// Match {{name}} or {{name ?? name ?? ...}} where each name can contain
|
|
979
|
+
// letters, numbers, hyphens, and underscores
|
|
980
|
+
return svgContent.replace(/\{\{\s*([\w\-]+(?:\s*\?\?\s*[\w\-]+)*)\s*\}\}/g, (match, chain) => {
|
|
981
|
+
const names = chain.split("??").map((n) => n.trim());
|
|
982
|
+
for (const name of names) {
|
|
983
|
+
const value = variables.get(name);
|
|
984
|
+
// An empty string is a defined value; only a missing variable
|
|
985
|
+
// falls through to the next name in the chain
|
|
986
|
+
if (value !== undefined) {
|
|
987
|
+
return value;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return match; // Keep placeholder if no name in the chain resolves
|
|
934
991
|
});
|
|
935
992
|
}
|
|
936
993
|
|
|
@@ -1028,12 +1085,17 @@ function parseDisplacedSpan(value) {
|
|
|
1028
1085
|
*/
|
|
1029
1086
|
function parseHintVars(value) {
|
|
1030
1087
|
const result = new Map();
|
|
1031
|
-
// Match pattern name=value
|
|
1088
|
+
// Match pattern name=value, splitting at the first "=" only so that
|
|
1089
|
+
// values containing "=" (e.g. URLs with query strings) survive intact
|
|
1032
1090
|
const pairs = value.trim().split(/\s+/);
|
|
1033
1091
|
for (const pair of pairs) {
|
|
1034
|
-
const
|
|
1092
|
+
const sepIndex = pair.indexOf("=");
|
|
1093
|
+
if (sepIndex <= 0)
|
|
1094
|
+
continue;
|
|
1095
|
+
const name = pair.substring(0, sepIndex).trim();
|
|
1096
|
+
const val = pair.substring(sepIndex + 1).trim();
|
|
1035
1097
|
if (name && val) {
|
|
1036
|
-
result.set(name
|
|
1098
|
+
result.set(name, val);
|
|
1037
1099
|
}
|
|
1038
1100
|
}
|
|
1039
1101
|
return result;
|
|
@@ -1300,9 +1362,11 @@ class TextRenderer {
|
|
|
1300
1362
|
const node = nodes[i];
|
|
1301
1363
|
const pos = positions[i];
|
|
1302
1364
|
// Cache bounds for spaces even though they're not rendered
|
|
1303
|
-
// This is needed for RBR calculations that include spaces
|
|
1365
|
+
// This is needed for RBR calculations that include spaces.
|
|
1366
|
+
// Use the layout's space width (reference char width × fraction) so the
|
|
1367
|
+
// cached bounds match the horizontal advance actually applied.
|
|
1304
1368
|
if (isSpace(node)) {
|
|
1305
|
-
const spaceWidth = this.
|
|
1369
|
+
const spaceWidth = this._textLayout.getSpaceWidth();
|
|
1306
1370
|
const spaceY = pos.y - this._settings.fontSize; // Approximate top of character
|
|
1307
1371
|
// Create a DOMRect-like object for the space bounds
|
|
1308
1372
|
const spaceBounds = {
|
|
@@ -1352,6 +1416,11 @@ class TextRenderer {
|
|
|
1352
1416
|
backColor: config.backColor,
|
|
1353
1417
|
italic: config.italic,
|
|
1354
1418
|
bold: config.bold,
|
|
1419
|
+
underline: config.underline,
|
|
1420
|
+
overline: config.overline,
|
|
1421
|
+
strike: config.strike,
|
|
1422
|
+
textLineStyle: config.textLineStyle,
|
|
1423
|
+
textLineColor: config.textLineColor,
|
|
1355
1424
|
});
|
|
1356
1425
|
}
|
|
1357
1426
|
else {
|
|
@@ -1362,12 +1431,24 @@ class TextRenderer {
|
|
|
1362
1431
|
backColor: this._settings.backColor,
|
|
1363
1432
|
italic: this._settings.italic,
|
|
1364
1433
|
bold: this._settings.bold,
|
|
1434
|
+
underline: this._settings.underline,
|
|
1435
|
+
overline: this._settings.overline,
|
|
1436
|
+
strike: this._settings.strike,
|
|
1437
|
+
textLineStyle: this._settings.textLineStyle,
|
|
1438
|
+
textLineColor: this._settings.textLineColor,
|
|
1365
1439
|
});
|
|
1366
1440
|
}
|
|
1441
|
+
// Per-character rotation (r_rotate): rotate each glyph around its own
|
|
1442
|
+
// anchor point (the character's baseline position), not the SVG origin.
|
|
1443
|
+
const rotate = config?.rotate;
|
|
1444
|
+
if (rotate) {
|
|
1445
|
+
textEl.setAttribute("transform", `rotate(${rotate}, ${position.x}, ${position.y})`);
|
|
1446
|
+
}
|
|
1367
1447
|
rootSvg.appendChild(textEl);
|
|
1368
|
-
// Cache bounds
|
|
1448
|
+
// Cache bounds. getBBox() ignores the element's own transform, so for
|
|
1449
|
+
// rotated characters the cached rect is recomputed around the anchor.
|
|
1369
1450
|
const bbox = getSafeBBox(textEl);
|
|
1370
|
-
this._boundsCache.set(`n_${node.id}`, bbox);
|
|
1451
|
+
this._boundsCache.set(`n_${node.id}`, rotate ? rotateRectAroundPoint(bbox, rotate, position.x, position.y) : bbox);
|
|
1371
1452
|
// Animate if function provided
|
|
1372
1453
|
if (animationFn) {
|
|
1373
1454
|
// Set initial opacity to 0 for animation via CSS style (GSAP animates CSS)
|
|
@@ -1394,6 +1475,7 @@ class TextRenderer {
|
|
|
1394
1475
|
this._logger.info(`Rendering ${nodes.length} additional text characters for ${versionTag}`);
|
|
1395
1476
|
if (nodes.length === 0) {
|
|
1396
1477
|
this._logger.warn("No nodes to render");
|
|
1478
|
+
this._logger.timeEnd(`renderAdditionalText-${versionTag}`);
|
|
1397
1479
|
return;
|
|
1398
1480
|
}
|
|
1399
1481
|
// Apply r_t-value override if specified
|
|
@@ -1410,8 +1492,8 @@ class TextRenderer {
|
|
|
1410
1492
|
}
|
|
1411
1493
|
// If r_t-value has fewer characters, we need to only render those nodes
|
|
1412
1494
|
if (overrideChars.length < nodes.length) {
|
|
1413
|
-
nodes = nodes.slice(0, overrideChars.length);
|
|
1414
1495
|
this._logger.debug("TextRenderer", `r_t-value has fewer characters (${overrideChars.length}) than nodes (${nodes.length}), only rendering first ${overrideChars.length} characters`);
|
|
1496
|
+
nodes = nodes.slice(0, overrideChars.length);
|
|
1415
1497
|
}
|
|
1416
1498
|
}
|
|
1417
1499
|
// 1. Calculate RBR (or use displaced span if specified)
|
|
@@ -1425,6 +1507,7 @@ class TextRenderer {
|
|
|
1425
1507
|
}
|
|
1426
1508
|
if (rbrs.length === 0) {
|
|
1427
1509
|
this._logger.warn("No RBRs calculated for additional text, skipping");
|
|
1510
|
+
this._logger.timeEnd(`renderAdditionalText-${versionTag}`);
|
|
1428
1511
|
return;
|
|
1429
1512
|
}
|
|
1430
1513
|
// Use first RBR for positioning (additional text doesn't repeat per RBR)
|
|
@@ -1465,7 +1548,17 @@ class TextRenderer {
|
|
|
1465
1548
|
backColor: config.backColor,
|
|
1466
1549
|
italic: config.italic,
|
|
1467
1550
|
bold: config.bold,
|
|
1551
|
+
underline: config.underline,
|
|
1552
|
+
overline: config.overline,
|
|
1553
|
+
strike: config.strike,
|
|
1554
|
+
textLineStyle: config.textLineStyle,
|
|
1555
|
+
textLineColor: config.textLineColor,
|
|
1468
1556
|
});
|
|
1557
|
+
// Per-character rotation (r_rotate) is a size transform: apply it
|
|
1558
|
+
// before measuring so the EBR accounts for the rotated glyphs.
|
|
1559
|
+
if (config.rotate) {
|
|
1560
|
+
textEl.setAttribute("transform", `rotate(${config.rotate}, ${pos.x}, ${pos.y})`);
|
|
1561
|
+
}
|
|
1469
1562
|
tempGroup.appendChild(textEl);
|
|
1470
1563
|
measuredElements.push({ el: textEl, node, initX: pos.x, initY: pos.y });
|
|
1471
1564
|
}
|
|
@@ -1522,11 +1615,22 @@ class TextRenderer {
|
|
|
1522
1615
|
: undefined;
|
|
1523
1616
|
// 9. Move each element to its final position and add to the SVG
|
|
1524
1617
|
for (const { el, node, initX, initY } of measuredElements) {
|
|
1525
|
-
|
|
1526
|
-
|
|
1618
|
+
const finalX = initX + dx;
|
|
1619
|
+
const finalY = initY + dy;
|
|
1620
|
+
el.setAttribute("x", String(finalX));
|
|
1621
|
+
el.setAttribute("y", String(finalY));
|
|
1622
|
+
// Re-anchor the per-character rotation to the moved position, or the
|
|
1623
|
+
// glyph would rotate around the pre-translation measuring anchor.
|
|
1624
|
+
if (config.rotate) {
|
|
1625
|
+
el.setAttribute("transform", `rotate(${config.rotate}, ${finalX}, ${finalY})`);
|
|
1626
|
+
}
|
|
1527
1627
|
rootSvg.appendChild(el);
|
|
1628
|
+
// getBBox() ignores the element's own transform, so for rotated
|
|
1629
|
+
// characters the cached rect is recomputed around the anchor.
|
|
1528
1630
|
const bbox = getSafeBBox(el);
|
|
1529
|
-
this._boundsCache.set(`n_${node.id}`,
|
|
1631
|
+
this._boundsCache.set(`n_${node.id}`, config.rotate
|
|
1632
|
+
? rotateRectAroundPoint(bbox, config.rotate, finalX, finalY)
|
|
1633
|
+
: bbox);
|
|
1530
1634
|
if (animationFn) {
|
|
1531
1635
|
el.style.opacity = "0";
|
|
1532
1636
|
await this._animationEngine.animate(el, animationFn, rootSvg);
|
|
@@ -1543,23 +1647,34 @@ class TextRenderer {
|
|
|
1543
1647
|
calculateAdditionalTextPositions(nodes, baseX, baseY, config) {
|
|
1544
1648
|
const positions = [];
|
|
1545
1649
|
let currentX = baseX;
|
|
1546
|
-
|
|
1650
|
+
let currentY = baseY;
|
|
1651
|
+
let lineNumber = 0;
|
|
1547
1652
|
const fontSize = config?.fontSize ?? this._settings.fontSize;
|
|
1548
1653
|
const fontFamily = config?.fontFamily ?? this._settings.fontFamily;
|
|
1549
1654
|
const bold = config?.bold ?? this._settings.bold;
|
|
1550
1655
|
const italic = config?.italic ?? this._settings.italic;
|
|
1656
|
+
// Space width follows the same rule as base text: a fraction
|
|
1657
|
+
// (spaceWidthFraction) of the reference character's width in the
|
|
1658
|
+
// effective font, not of the font size.
|
|
1659
|
+
const spaceWidth = getAverageCharWidth(fontFamily, fontSize, bold, italic, this._measurementRoot) *
|
|
1660
|
+
this._settings.spaceWidthFraction;
|
|
1551
1661
|
for (let i = 0; i < nodes.length; i++) {
|
|
1552
1662
|
const node = nodes[i];
|
|
1553
1663
|
if (isLineBreak(node)) {
|
|
1554
|
-
|
|
1664
|
+
// Line break: start a new line, mirroring base text layout. Uses the
|
|
1665
|
+
// effective font size so smaller/larger added text wraps accordingly.
|
|
1666
|
+
currentX = baseX;
|
|
1667
|
+
currentY +=
|
|
1668
|
+
fontSize * this._settings.lineHeight + this._settings.lineSpacing;
|
|
1669
|
+
lineNumber++;
|
|
1670
|
+
positions.push({ x: currentX, y: currentY, nodeId: node.id, lineNumber });
|
|
1555
1671
|
}
|
|
1556
1672
|
else if (isSpace(node)) {
|
|
1557
|
-
|
|
1558
|
-
positions.push({ x: currentX, y: currentY, nodeId: node.id, lineNumber: 0 });
|
|
1673
|
+
positions.push({ x: currentX, y: currentY, nodeId: node.id, lineNumber });
|
|
1559
1674
|
currentX += spaceWidth + this._settings.charSpacing;
|
|
1560
1675
|
}
|
|
1561
1676
|
else {
|
|
1562
|
-
positions.push({ x: currentX, y: currentY, nodeId: node.id, lineNumber
|
|
1677
|
+
positions.push({ x: currentX, y: currentY, nodeId: node.id, lineNumber });
|
|
1563
1678
|
const charWidth = getTextWidth(node.data, fontFamily, fontSize, bold, italic, this._measurementRoot);
|
|
1564
1679
|
currentX += charWidth + this._settings.charSpacing;
|
|
1565
1680
|
}
|
|
@@ -1625,8 +1740,15 @@ class TextRenderer {
|
|
|
1625
1740
|
if (nodes.length === 0)
|
|
1626
1741
|
return [];
|
|
1627
1742
|
const rbrs = [];
|
|
1628
|
-
const nodeElementIds = nodes.map((n) => `n_${n.id}`);
|
|
1629
1743
|
// Group nodes by line by checking their Y coordinates.
|
|
1744
|
+
// IMPORTANT: spaces and line breaks are skipped, exactly as in
|
|
1745
|
+
// HintRenderer.calculateRBRs. They have no SVG element, so the baseline
|
|
1746
|
+
// read below would fall back to their cached synthetic bounds top
|
|
1747
|
+
// (baseline - fontSize), which differs from neighboring characters'
|
|
1748
|
+
// baseline by ~fontSize — far beyond the 5px tolerance — falsely
|
|
1749
|
+
// splitting a single line into one group per word. Interior spaces
|
|
1750
|
+
// don't affect the union bounds anyway, since the flanking characters
|
|
1751
|
+
// cover their x-range.
|
|
1630
1752
|
// IMPORTANT: this compares the character's laid-out baseline Y (the `y`
|
|
1631
1753
|
// attribute TextLayout assigned when positioning it), NOT its cached
|
|
1632
1754
|
// getBBox().y. The tight ink bbox top varies per glyph shape (ascenders,
|
|
@@ -1636,7 +1758,11 @@ class TextRenderer {
|
|
|
1636
1758
|
const lineGroups = [];
|
|
1637
1759
|
let currentLineGroup = [];
|
|
1638
1760
|
let lastBaselineY = null;
|
|
1639
|
-
for (const
|
|
1761
|
+
for (const node of nodes) {
|
|
1762
|
+
if (isSpace(node) || isLineBreak(node)) {
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
const elementId = `n_${node.id}`;
|
|
1640
1766
|
const bounds = this._boundsCache.get(elementId);
|
|
1641
1767
|
if (!bounds) {
|
|
1642
1768
|
this._logger.warn(`Bounds not found for node element ${elementId}`);
|
|
@@ -1659,6 +1785,17 @@ class TextRenderer {
|
|
|
1659
1785
|
if (currentLineGroup.length > 0) {
|
|
1660
1786
|
lineGroups.push(currentLineGroup);
|
|
1661
1787
|
}
|
|
1788
|
+
// Fallback: if every reference node is invisible (e.g. the operation's
|
|
1789
|
+
// reference span is a lone space), use the cached synthetic bounds of
|
|
1790
|
+
// all nodes as a single group so the added text still gets an RBR.
|
|
1791
|
+
if (lineGroups.length === 0) {
|
|
1792
|
+
const cachedIds = nodes
|
|
1793
|
+
.map((n) => `n_${n.id}`)
|
|
1794
|
+
.filter((id) => this._boundsCache.get(id) !== undefined);
|
|
1795
|
+
if (cachedIds.length > 0) {
|
|
1796
|
+
lineGroups.push(cachedIds);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1662
1799
|
// Calculate RBR for each line group
|
|
1663
1800
|
for (const group of lineGroups) {
|
|
1664
1801
|
const rbr = this._boundsCache.getUnionBounds(group);
|
|
@@ -2241,6 +2378,8 @@ class FeatureResolver {
|
|
|
2241
2378
|
underline: baseSettings.underline,
|
|
2242
2379
|
overline: baseSettings.overline,
|
|
2243
2380
|
strike: baseSettings.strike,
|
|
2381
|
+
textLineStyle: baseSettings.textLineStyle,
|
|
2382
|
+
textLineColor: baseSettings.textLineColor,
|
|
2244
2383
|
textOffsetX: 0,
|
|
2245
2384
|
textOffsetY: 0,
|
|
2246
2385
|
};
|
|
@@ -2423,9 +2562,13 @@ class FeatureResolver {
|
|
|
2423
2562
|
return;
|
|
2424
2563
|
}
|
|
2425
2564
|
}
|
|
2426
|
-
//
|
|
2427
|
-
//
|
|
2428
|
-
|
|
2565
|
+
// Untargeted overrides (no "@" prefix) are always stored under the "*"
|
|
2566
|
+
// wildcard key. Storing them under the hint keys from config.hints would
|
|
2567
|
+
// (a) depend on whether r_hints was parsed before this feature, and
|
|
2568
|
+
// (b) give them key-level priority, letting them overwrite an explicit
|
|
2569
|
+
// "@key:" override for the same property — the documented precedence is
|
|
2570
|
+
// ordinal > key > wildcard (see applyHintOverrides in HintRenderer).
|
|
2571
|
+
const hints = targetHints ?? ["*"];
|
|
2429
2572
|
for (const hintKey of hints) {
|
|
2430
2573
|
// Get or create override object for this hint key / ordinal
|
|
2431
2574
|
let override = config.hintOverrides.get(hintKey);
|
|
@@ -25588,375 +25731,375 @@ class GoldenLayout extends VirtualLayout {
|
|
|
25588
25731
|
* Source: golden-layout@2.6.0 dist/css/goldenlayout-base.css + adapted light theme.
|
|
25589
25732
|
* Update this file when upgrading golden-layout.
|
|
25590
25733
|
*/
|
|
25591
|
-
const GOLDEN_LAYOUT_CSS = `
|
|
25592
|
-
/* ── Golden Layout base ──────────────────────────────────────────────── */
|
|
25593
|
-
.lm_root {
|
|
25594
|
-
position: relative;
|
|
25595
|
-
}
|
|
25596
|
-
.lm_row > .lm_item {
|
|
25597
|
-
float: left;
|
|
25598
|
-
}
|
|
25599
|
-
.lm_content {
|
|
25600
|
-
overflow: hidden;
|
|
25601
|
-
position: relative;
|
|
25602
|
-
}
|
|
25603
|
-
.lm_dragging,
|
|
25604
|
-
.lm_dragging * {
|
|
25605
|
-
cursor: move !important;
|
|
25606
|
-
user-select: none;
|
|
25607
|
-
}
|
|
25608
|
-
.lm_maximised {
|
|
25609
|
-
position: absolute;
|
|
25610
|
-
top: 0;
|
|
25611
|
-
left: 0;
|
|
25612
|
-
z-index: 40;
|
|
25613
|
-
}
|
|
25614
|
-
.lm_maximise_placeholder {
|
|
25615
|
-
display: none;
|
|
25616
|
-
}
|
|
25617
|
-
.lm_splitter {
|
|
25618
|
-
position: relative;
|
|
25619
|
-
z-index: 2;
|
|
25620
|
-
touch-action: none;
|
|
25621
|
-
}
|
|
25622
|
-
.lm_splitter.lm_vertical .lm_drag_handle {
|
|
25623
|
-
width: 100%;
|
|
25624
|
-
position: absolute;
|
|
25625
|
-
cursor: ns-resize;
|
|
25626
|
-
touch-action: none;
|
|
25627
|
-
user-select: none;
|
|
25628
|
-
}
|
|
25629
|
-
.lm_splitter.lm_horizontal {
|
|
25630
|
-
float: left;
|
|
25631
|
-
height: 100%;
|
|
25632
|
-
}
|
|
25633
|
-
.lm_splitter.lm_horizontal .lm_drag_handle {
|
|
25634
|
-
height: 100%;
|
|
25635
|
-
position: absolute;
|
|
25636
|
-
cursor: ew-resize;
|
|
25637
|
-
touch-action: none;
|
|
25638
|
-
user-select: none;
|
|
25639
|
-
}
|
|
25640
|
-
.lm_header {
|
|
25641
|
-
overflow: visible;
|
|
25642
|
-
position: relative;
|
|
25643
|
-
z-index: 1;
|
|
25644
|
-
user-select: none;
|
|
25645
|
-
}
|
|
25646
|
-
.lm_header [class^=lm_] {
|
|
25647
|
-
box-sizing: content-box !important;
|
|
25648
|
-
}
|
|
25649
|
-
.lm_header .lm_controls {
|
|
25650
|
-
position: absolute;
|
|
25651
|
-
right: 3px;
|
|
25652
|
-
display: flex;
|
|
25653
|
-
}
|
|
25654
|
-
.lm_header .lm_controls > * {
|
|
25655
|
-
cursor: pointer;
|
|
25656
|
-
float: left;
|
|
25657
|
-
width: 18px;
|
|
25658
|
-
height: 18px;
|
|
25659
|
-
text-align: center;
|
|
25660
|
-
}
|
|
25661
|
-
.lm_header .lm_tabs {
|
|
25662
|
-
position: absolute;
|
|
25663
|
-
display: flex;
|
|
25664
|
-
}
|
|
25665
|
-
.lm_header .lm_tab {
|
|
25666
|
-
cursor: pointer;
|
|
25667
|
-
float: left;
|
|
25668
|
-
height: 14px;
|
|
25669
|
-
margin-top: 1px;
|
|
25670
|
-
padding: 0px 10px 5px;
|
|
25671
|
-
padding-right: 25px;
|
|
25672
|
-
position: relative;
|
|
25673
|
-
touch-action: none;
|
|
25674
|
-
}
|
|
25675
|
-
.lm_header .lm_tab i {
|
|
25676
|
-
width: 2px;
|
|
25677
|
-
height: 19px;
|
|
25678
|
-
position: absolute;
|
|
25679
|
-
}
|
|
25680
|
-
.lm_header .lm_tab i.lm_left {
|
|
25681
|
-
top: 0;
|
|
25682
|
-
left: -2px;
|
|
25683
|
-
}
|
|
25684
|
-
.lm_header .lm_tab i.lm_right {
|
|
25685
|
-
top: 0;
|
|
25686
|
-
right: -2px;
|
|
25687
|
-
}
|
|
25688
|
-
.lm_header .lm_tab .lm_title {
|
|
25689
|
-
display: inline-block;
|
|
25690
|
-
overflow: hidden;
|
|
25691
|
-
text-overflow: ellipsis;
|
|
25692
|
-
}
|
|
25693
|
-
.lm_header .lm_tab .lm_close_tab {
|
|
25694
|
-
width: 14px;
|
|
25695
|
-
height: 14px;
|
|
25696
|
-
position: absolute;
|
|
25697
|
-
top: 0;
|
|
25698
|
-
right: 0;
|
|
25699
|
-
text-align: center;
|
|
25700
|
-
}
|
|
25701
|
-
.lm_stack {
|
|
25702
|
-
position: relative;
|
|
25703
|
-
}
|
|
25704
|
-
.lm_stack > .lm_items {
|
|
25705
|
-
overflow: hidden;
|
|
25706
|
-
}
|
|
25707
|
-
.lm_stack.lm_left > .lm_items {
|
|
25708
|
-
position: absolute;
|
|
25709
|
-
left: 20px;
|
|
25710
|
-
top: 0;
|
|
25711
|
-
}
|
|
25712
|
-
.lm_stack.lm_right > .lm_items {
|
|
25713
|
-
position: absolute;
|
|
25714
|
-
right: 20px;
|
|
25715
|
-
top: 0;
|
|
25716
|
-
}
|
|
25717
|
-
.lm_stack.lm_right > .lm_header {
|
|
25718
|
-
position: absolute;
|
|
25719
|
-
right: 0;
|
|
25720
|
-
top: 0;
|
|
25721
|
-
}
|
|
25722
|
-
.lm_stack.lm_bottom > .lm_items {
|
|
25723
|
-
position: absolute;
|
|
25724
|
-
bottom: 20px;
|
|
25725
|
-
}
|
|
25726
|
-
.lm_stack.lm_bottom > .lm_header {
|
|
25727
|
-
position: absolute;
|
|
25728
|
-
bottom: 0;
|
|
25729
|
-
}
|
|
25730
|
-
.lm_left.lm_stack .lm_header,
|
|
25731
|
-
.lm_right.lm_stack .lm_header {
|
|
25732
|
-
height: 100%;
|
|
25733
|
-
}
|
|
25734
|
-
.lm_left.lm_dragProxy .lm_header,
|
|
25735
|
-
.lm_right.lm_dragProxy .lm_header,
|
|
25736
|
-
.lm_left.lm_dragProxy .lm_items,
|
|
25737
|
-
.lm_right.lm_dragProxy .lm_items {
|
|
25738
|
-
float: left;
|
|
25739
|
-
}
|
|
25740
|
-
.lm_left.lm_dragProxy .lm_header,
|
|
25741
|
-
.lm_right.lm_dragProxy .lm_header,
|
|
25742
|
-
.lm_left.lm_stack .lm_header,
|
|
25743
|
-
.lm_right.lm_stack .lm_header {
|
|
25744
|
-
width: 20px;
|
|
25745
|
-
vertical-align: top;
|
|
25746
|
-
}
|
|
25747
|
-
.lm_left.lm_dragProxy .lm_header .lm_tabs,
|
|
25748
|
-
.lm_right.lm_dragProxy .lm_header .lm_tabs,
|
|
25749
|
-
.lm_left.lm_stack .lm_header .lm_tabs,
|
|
25750
|
-
.lm_right.lm_stack .lm_header .lm_tabs {
|
|
25751
|
-
transform-origin: left top;
|
|
25752
|
-
top: 0;
|
|
25753
|
-
width: 1000px;
|
|
25754
|
-
}
|
|
25755
|
-
.lm_left.lm_dragProxy .lm_header .lm_controls,
|
|
25756
|
-
.lm_right.lm_dragProxy .lm_header .lm_controls,
|
|
25757
|
-
.lm_left.lm_stack .lm_header .lm_controls,
|
|
25758
|
-
.lm_right.lm_stack .lm_header .lm_controls {
|
|
25759
|
-
bottom: 0;
|
|
25760
|
-
flex-flow: column;
|
|
25761
|
-
}
|
|
25762
|
-
.lm_dragProxy.lm_left .lm_header .lm_tabs,
|
|
25763
|
-
.lm_stack.lm_left .lm_header .lm_tabs {
|
|
25764
|
-
transform: rotate(-90deg) scaleX(-1);
|
|
25765
|
-
left: 0;
|
|
25766
|
-
}
|
|
25767
|
-
.lm_dragProxy.lm_left .lm_header .lm_tabs .lm_tab,
|
|
25768
|
-
.lm_stack.lm_left .lm_header .lm_tabs .lm_tab {
|
|
25769
|
-
transform: scaleX(-1);
|
|
25770
|
-
margin-top: 1px;
|
|
25771
|
-
}
|
|
25772
|
-
.lm_dragProxy.lm_right .lm_content {
|
|
25773
|
-
float: left;
|
|
25774
|
-
}
|
|
25775
|
-
.lm_dragProxy.lm_right .lm_header .lm_tabs,
|
|
25776
|
-
.lm_stack.lm_right .lm_header .lm_tabs {
|
|
25777
|
-
transform: rotate(90deg) scaleX(1);
|
|
25778
|
-
left: 100%;
|
|
25779
|
-
margin-left: 0;
|
|
25780
|
-
}
|
|
25781
|
-
.lm_dragProxy.lm_right .lm_header .lm_controls,
|
|
25782
|
-
.lm_stack.lm_right .lm_header .lm_controls {
|
|
25783
|
-
left: 3px;
|
|
25784
|
-
}
|
|
25785
|
-
.lm_dragProxy.lm_bottom .lm_header,
|
|
25786
|
-
.lm_stack.lm_bottom .lm_header {
|
|
25787
|
-
width: 100%;
|
|
25788
|
-
}
|
|
25789
|
-
.lm_dragProxy.lm_bottom .lm_header .lm_tab,
|
|
25790
|
-
.lm_stack.lm_bottom .lm_header .lm_tab {
|
|
25791
|
-
margin-top: 0;
|
|
25792
|
-
border-top: none;
|
|
25793
|
-
}
|
|
25794
|
-
.lm_dragProxy.lm_bottom .lm_header .lm_controls,
|
|
25795
|
-
.lm_stack.lm_bottom .lm_header .lm_controls {
|
|
25796
|
-
top: 3px;
|
|
25797
|
-
}
|
|
25798
|
-
.lm_drop_tab_placeholder {
|
|
25799
|
-
float: left;
|
|
25800
|
-
width: 100px;
|
|
25801
|
-
visibility: hidden;
|
|
25802
|
-
}
|
|
25803
|
-
.lm_header .lm_controls .lm_tabdropdown:before {
|
|
25804
|
-
content: '';
|
|
25805
|
-
width: 0;
|
|
25806
|
-
height: 0;
|
|
25807
|
-
vertical-align: middle;
|
|
25808
|
-
display: inline-block;
|
|
25809
|
-
border-top: 5px dashed;
|
|
25810
|
-
border-right: 5px solid transparent;
|
|
25811
|
-
border-left: 5px solid transparent;
|
|
25812
|
-
color: white;
|
|
25813
|
-
}
|
|
25814
|
-
.lm_header .lm_tabdropdown_list {
|
|
25815
|
-
position: absolute;
|
|
25816
|
-
top: 20px;
|
|
25817
|
-
right: 0;
|
|
25818
|
-
z-index: 5;
|
|
25819
|
-
overflow: hidden;
|
|
25820
|
-
}
|
|
25821
|
-
.lm_header .lm_tabdropdown_list .lm_tab {
|
|
25822
|
-
clear: both;
|
|
25823
|
-
padding-right: 10px;
|
|
25824
|
-
margin: 0;
|
|
25825
|
-
}
|
|
25826
|
-
.lm_header .lm_tabdropdown_list .lm_tab .lm_title {
|
|
25827
|
-
width: 100px;
|
|
25828
|
-
}
|
|
25829
|
-
.lm_header .lm_tabdropdown_list .lm_close_tab {
|
|
25830
|
-
display: none !important;
|
|
25831
|
-
}
|
|
25832
|
-
.lm_dragProxy {
|
|
25833
|
-
position: absolute;
|
|
25834
|
-
top: 0;
|
|
25835
|
-
left: 0;
|
|
25836
|
-
z-index: 30;
|
|
25837
|
-
}
|
|
25838
|
-
.lm_dragProxy .lm_header {
|
|
25839
|
-
background: transparent;
|
|
25840
|
-
}
|
|
25841
|
-
.lm_dragProxy .lm_content {
|
|
25842
|
-
border-top: none;
|
|
25843
|
-
overflow: hidden;
|
|
25844
|
-
}
|
|
25845
|
-
.lm_dropTargetIndicator {
|
|
25846
|
-
display: none;
|
|
25847
|
-
position: absolute;
|
|
25848
|
-
z-index: 35;
|
|
25849
|
-
transition: all 200ms ease;
|
|
25850
|
-
}
|
|
25851
|
-
.lm_dropTargetIndicator .lm_inner {
|
|
25852
|
-
width: 100%;
|
|
25853
|
-
height: 100%;
|
|
25854
|
-
position: relative;
|
|
25855
|
-
top: 0;
|
|
25856
|
-
left: 0;
|
|
25857
|
-
}
|
|
25858
|
-
.lm_transition_indicator {
|
|
25859
|
-
display: none;
|
|
25860
|
-
width: 20px;
|
|
25861
|
-
height: 20px;
|
|
25862
|
-
position: absolute;
|
|
25863
|
-
top: 0;
|
|
25864
|
-
left: 0;
|
|
25865
|
-
z-index: 20;
|
|
25866
|
-
}
|
|
25867
|
-
.lm_popin {
|
|
25868
|
-
width: 20px;
|
|
25869
|
-
height: 20px;
|
|
25870
|
-
position: absolute;
|
|
25871
|
-
bottom: 0;
|
|
25872
|
-
right: 0;
|
|
25873
|
-
z-index: 9999;
|
|
25874
|
-
}
|
|
25875
|
-
.lm_popin > * {
|
|
25876
|
-
width: 100%;
|
|
25877
|
-
height: 100%;
|
|
25878
|
-
position: absolute;
|
|
25879
|
-
top: 0;
|
|
25880
|
-
left: 0;
|
|
25881
|
-
}
|
|
25882
|
-
.lm_popin > .lm_bg {
|
|
25883
|
-
z-index: 10;
|
|
25884
|
-
}
|
|
25885
|
-
.lm_popin > .lm_icon {
|
|
25886
|
-
z-index: 20;
|
|
25887
|
-
}
|
|
25888
|
-
|
|
25889
|
-
/* ── Adapted theme (using --gve- CSS variables) ──────────────────────── */
|
|
25890
|
-
.lm_goldenlayout {
|
|
25891
|
-
background: transparent;
|
|
25892
|
-
}
|
|
25893
|
-
.lm_content {
|
|
25894
|
-
background: var(--gve-bg-color, #fafafa);
|
|
25895
|
-
border: none;
|
|
25896
|
-
}
|
|
25897
|
-
.lm_dragProxy .lm_content {
|
|
25898
|
-
box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
|
25899
|
-
}
|
|
25900
|
-
.lm_dropTargetIndicator {
|
|
25901
|
-
box-shadow: inset 0 0 30px rgba(0,0,0,0.3);
|
|
25902
|
-
outline: 1px dashed var(--gve-border-color, #ccc);
|
|
25903
|
-
}
|
|
25904
|
-
.lm_dropTargetIndicator .lm_inner {
|
|
25905
|
-
background: #000;
|
|
25906
|
-
opacity: 0.08;
|
|
25907
|
-
}
|
|
25908
|
-
.lm_splitter {
|
|
25909
|
-
background: var(--gve-border-color, #ccc);
|
|
25910
|
-
opacity: 0.4;
|
|
25911
|
-
transition: opacity 200ms ease;
|
|
25912
|
-
}
|
|
25913
|
-
.lm_splitter:hover,
|
|
25914
|
-
.lm_splitter.lm_dragging {
|
|
25915
|
-
background: var(--gve-border-color, #aaa);
|
|
25916
|
-
opacity: 1;
|
|
25917
|
-
}
|
|
25918
|
-
.lm_header {
|
|
25919
|
-
height: 26px;
|
|
25920
|
-
background: var(--gve-toolbar-bg, #f5f5f5);
|
|
25921
|
-
border-bottom: 1px solid var(--gve-border-color, #ddd);
|
|
25922
|
-
}
|
|
25923
|
-
.lm_header .lm_tab {
|
|
25924
|
-
font-family: Arial, sans-serif;
|
|
25925
|
-
font-size: 12px;
|
|
25926
|
-
font-weight: 600;
|
|
25927
|
-
color: var(--gve-text-secondary, #777);
|
|
25928
|
-
background: transparent;
|
|
25929
|
-
margin-right: 2px;
|
|
25930
|
-
padding: 5px 10px 4px;
|
|
25931
|
-
padding-right: 14px;
|
|
25932
|
-
border: none;
|
|
25933
|
-
}
|
|
25934
|
-
.lm_header .lm_tab .lm_title {
|
|
25935
|
-
padding-top: 1px;
|
|
25936
|
-
}
|
|
25937
|
-
.lm_header .lm_tab.lm_active {
|
|
25938
|
-
background: var(--gve-bg-color, #fff);
|
|
25939
|
-
color: var(--gve-text-color, #333);
|
|
25940
|
-
border-bottom: 2px solid var(--gve-primary-color, #007bff);
|
|
25941
|
-
padding-bottom: 3px;
|
|
25942
|
-
}
|
|
25943
|
-
.lm_header .lm_tab:hover {
|
|
25944
|
-
color: var(--gve-text-color, #333);
|
|
25945
|
-
}
|
|
25946
|
-
.lm_header .lm_controls .lm_tabdropdown:before {
|
|
25947
|
-
color: var(--gve-text-color, #333);
|
|
25948
|
-
}
|
|
25949
|
-
.lm_transition_indicator {
|
|
25950
|
-
background: #000;
|
|
25951
|
-
border: 1px dashed #555;
|
|
25952
|
-
}
|
|
25953
|
-
.lm_popin {
|
|
25954
|
-
cursor: pointer;
|
|
25955
|
-
}
|
|
25956
|
-
.lm_popin .lm_bg {
|
|
25957
|
-
background: #000;
|
|
25958
|
-
opacity: 0.7;
|
|
25959
|
-
}
|
|
25734
|
+
const GOLDEN_LAYOUT_CSS = `
|
|
25735
|
+
/* ── Golden Layout base ──────────────────────────────────────────────── */
|
|
25736
|
+
.lm_root {
|
|
25737
|
+
position: relative;
|
|
25738
|
+
}
|
|
25739
|
+
.lm_row > .lm_item {
|
|
25740
|
+
float: left;
|
|
25741
|
+
}
|
|
25742
|
+
.lm_content {
|
|
25743
|
+
overflow: hidden;
|
|
25744
|
+
position: relative;
|
|
25745
|
+
}
|
|
25746
|
+
.lm_dragging,
|
|
25747
|
+
.lm_dragging * {
|
|
25748
|
+
cursor: move !important;
|
|
25749
|
+
user-select: none;
|
|
25750
|
+
}
|
|
25751
|
+
.lm_maximised {
|
|
25752
|
+
position: absolute;
|
|
25753
|
+
top: 0;
|
|
25754
|
+
left: 0;
|
|
25755
|
+
z-index: 40;
|
|
25756
|
+
}
|
|
25757
|
+
.lm_maximise_placeholder {
|
|
25758
|
+
display: none;
|
|
25759
|
+
}
|
|
25760
|
+
.lm_splitter {
|
|
25761
|
+
position: relative;
|
|
25762
|
+
z-index: 2;
|
|
25763
|
+
touch-action: none;
|
|
25764
|
+
}
|
|
25765
|
+
.lm_splitter.lm_vertical .lm_drag_handle {
|
|
25766
|
+
width: 100%;
|
|
25767
|
+
position: absolute;
|
|
25768
|
+
cursor: ns-resize;
|
|
25769
|
+
touch-action: none;
|
|
25770
|
+
user-select: none;
|
|
25771
|
+
}
|
|
25772
|
+
.lm_splitter.lm_horizontal {
|
|
25773
|
+
float: left;
|
|
25774
|
+
height: 100%;
|
|
25775
|
+
}
|
|
25776
|
+
.lm_splitter.lm_horizontal .lm_drag_handle {
|
|
25777
|
+
height: 100%;
|
|
25778
|
+
position: absolute;
|
|
25779
|
+
cursor: ew-resize;
|
|
25780
|
+
touch-action: none;
|
|
25781
|
+
user-select: none;
|
|
25782
|
+
}
|
|
25783
|
+
.lm_header {
|
|
25784
|
+
overflow: visible;
|
|
25785
|
+
position: relative;
|
|
25786
|
+
z-index: 1;
|
|
25787
|
+
user-select: none;
|
|
25788
|
+
}
|
|
25789
|
+
.lm_header [class^=lm_] {
|
|
25790
|
+
box-sizing: content-box !important;
|
|
25791
|
+
}
|
|
25792
|
+
.lm_header .lm_controls {
|
|
25793
|
+
position: absolute;
|
|
25794
|
+
right: 3px;
|
|
25795
|
+
display: flex;
|
|
25796
|
+
}
|
|
25797
|
+
.lm_header .lm_controls > * {
|
|
25798
|
+
cursor: pointer;
|
|
25799
|
+
float: left;
|
|
25800
|
+
width: 18px;
|
|
25801
|
+
height: 18px;
|
|
25802
|
+
text-align: center;
|
|
25803
|
+
}
|
|
25804
|
+
.lm_header .lm_tabs {
|
|
25805
|
+
position: absolute;
|
|
25806
|
+
display: flex;
|
|
25807
|
+
}
|
|
25808
|
+
.lm_header .lm_tab {
|
|
25809
|
+
cursor: pointer;
|
|
25810
|
+
float: left;
|
|
25811
|
+
height: 14px;
|
|
25812
|
+
margin-top: 1px;
|
|
25813
|
+
padding: 0px 10px 5px;
|
|
25814
|
+
padding-right: 25px;
|
|
25815
|
+
position: relative;
|
|
25816
|
+
touch-action: none;
|
|
25817
|
+
}
|
|
25818
|
+
.lm_header .lm_tab i {
|
|
25819
|
+
width: 2px;
|
|
25820
|
+
height: 19px;
|
|
25821
|
+
position: absolute;
|
|
25822
|
+
}
|
|
25823
|
+
.lm_header .lm_tab i.lm_left {
|
|
25824
|
+
top: 0;
|
|
25825
|
+
left: -2px;
|
|
25826
|
+
}
|
|
25827
|
+
.lm_header .lm_tab i.lm_right {
|
|
25828
|
+
top: 0;
|
|
25829
|
+
right: -2px;
|
|
25830
|
+
}
|
|
25831
|
+
.lm_header .lm_tab .lm_title {
|
|
25832
|
+
display: inline-block;
|
|
25833
|
+
overflow: hidden;
|
|
25834
|
+
text-overflow: ellipsis;
|
|
25835
|
+
}
|
|
25836
|
+
.lm_header .lm_tab .lm_close_tab {
|
|
25837
|
+
width: 14px;
|
|
25838
|
+
height: 14px;
|
|
25839
|
+
position: absolute;
|
|
25840
|
+
top: 0;
|
|
25841
|
+
right: 0;
|
|
25842
|
+
text-align: center;
|
|
25843
|
+
}
|
|
25844
|
+
.lm_stack {
|
|
25845
|
+
position: relative;
|
|
25846
|
+
}
|
|
25847
|
+
.lm_stack > .lm_items {
|
|
25848
|
+
overflow: hidden;
|
|
25849
|
+
}
|
|
25850
|
+
.lm_stack.lm_left > .lm_items {
|
|
25851
|
+
position: absolute;
|
|
25852
|
+
left: 20px;
|
|
25853
|
+
top: 0;
|
|
25854
|
+
}
|
|
25855
|
+
.lm_stack.lm_right > .lm_items {
|
|
25856
|
+
position: absolute;
|
|
25857
|
+
right: 20px;
|
|
25858
|
+
top: 0;
|
|
25859
|
+
}
|
|
25860
|
+
.lm_stack.lm_right > .lm_header {
|
|
25861
|
+
position: absolute;
|
|
25862
|
+
right: 0;
|
|
25863
|
+
top: 0;
|
|
25864
|
+
}
|
|
25865
|
+
.lm_stack.lm_bottom > .lm_items {
|
|
25866
|
+
position: absolute;
|
|
25867
|
+
bottom: 20px;
|
|
25868
|
+
}
|
|
25869
|
+
.lm_stack.lm_bottom > .lm_header {
|
|
25870
|
+
position: absolute;
|
|
25871
|
+
bottom: 0;
|
|
25872
|
+
}
|
|
25873
|
+
.lm_left.lm_stack .lm_header,
|
|
25874
|
+
.lm_right.lm_stack .lm_header {
|
|
25875
|
+
height: 100%;
|
|
25876
|
+
}
|
|
25877
|
+
.lm_left.lm_dragProxy .lm_header,
|
|
25878
|
+
.lm_right.lm_dragProxy .lm_header,
|
|
25879
|
+
.lm_left.lm_dragProxy .lm_items,
|
|
25880
|
+
.lm_right.lm_dragProxy .lm_items {
|
|
25881
|
+
float: left;
|
|
25882
|
+
}
|
|
25883
|
+
.lm_left.lm_dragProxy .lm_header,
|
|
25884
|
+
.lm_right.lm_dragProxy .lm_header,
|
|
25885
|
+
.lm_left.lm_stack .lm_header,
|
|
25886
|
+
.lm_right.lm_stack .lm_header {
|
|
25887
|
+
width: 20px;
|
|
25888
|
+
vertical-align: top;
|
|
25889
|
+
}
|
|
25890
|
+
.lm_left.lm_dragProxy .lm_header .lm_tabs,
|
|
25891
|
+
.lm_right.lm_dragProxy .lm_header .lm_tabs,
|
|
25892
|
+
.lm_left.lm_stack .lm_header .lm_tabs,
|
|
25893
|
+
.lm_right.lm_stack .lm_header .lm_tabs {
|
|
25894
|
+
transform-origin: left top;
|
|
25895
|
+
top: 0;
|
|
25896
|
+
width: 1000px;
|
|
25897
|
+
}
|
|
25898
|
+
.lm_left.lm_dragProxy .lm_header .lm_controls,
|
|
25899
|
+
.lm_right.lm_dragProxy .lm_header .lm_controls,
|
|
25900
|
+
.lm_left.lm_stack .lm_header .lm_controls,
|
|
25901
|
+
.lm_right.lm_stack .lm_header .lm_controls {
|
|
25902
|
+
bottom: 0;
|
|
25903
|
+
flex-flow: column;
|
|
25904
|
+
}
|
|
25905
|
+
.lm_dragProxy.lm_left .lm_header .lm_tabs,
|
|
25906
|
+
.lm_stack.lm_left .lm_header .lm_tabs {
|
|
25907
|
+
transform: rotate(-90deg) scaleX(-1);
|
|
25908
|
+
left: 0;
|
|
25909
|
+
}
|
|
25910
|
+
.lm_dragProxy.lm_left .lm_header .lm_tabs .lm_tab,
|
|
25911
|
+
.lm_stack.lm_left .lm_header .lm_tabs .lm_tab {
|
|
25912
|
+
transform: scaleX(-1);
|
|
25913
|
+
margin-top: 1px;
|
|
25914
|
+
}
|
|
25915
|
+
.lm_dragProxy.lm_right .lm_content {
|
|
25916
|
+
float: left;
|
|
25917
|
+
}
|
|
25918
|
+
.lm_dragProxy.lm_right .lm_header .lm_tabs,
|
|
25919
|
+
.lm_stack.lm_right .lm_header .lm_tabs {
|
|
25920
|
+
transform: rotate(90deg) scaleX(1);
|
|
25921
|
+
left: 100%;
|
|
25922
|
+
margin-left: 0;
|
|
25923
|
+
}
|
|
25924
|
+
.lm_dragProxy.lm_right .lm_header .lm_controls,
|
|
25925
|
+
.lm_stack.lm_right .lm_header .lm_controls {
|
|
25926
|
+
left: 3px;
|
|
25927
|
+
}
|
|
25928
|
+
.lm_dragProxy.lm_bottom .lm_header,
|
|
25929
|
+
.lm_stack.lm_bottom .lm_header {
|
|
25930
|
+
width: 100%;
|
|
25931
|
+
}
|
|
25932
|
+
.lm_dragProxy.lm_bottom .lm_header .lm_tab,
|
|
25933
|
+
.lm_stack.lm_bottom .lm_header .lm_tab {
|
|
25934
|
+
margin-top: 0;
|
|
25935
|
+
border-top: none;
|
|
25936
|
+
}
|
|
25937
|
+
.lm_dragProxy.lm_bottom .lm_header .lm_controls,
|
|
25938
|
+
.lm_stack.lm_bottom .lm_header .lm_controls {
|
|
25939
|
+
top: 3px;
|
|
25940
|
+
}
|
|
25941
|
+
.lm_drop_tab_placeholder {
|
|
25942
|
+
float: left;
|
|
25943
|
+
width: 100px;
|
|
25944
|
+
visibility: hidden;
|
|
25945
|
+
}
|
|
25946
|
+
.lm_header .lm_controls .lm_tabdropdown:before {
|
|
25947
|
+
content: '';
|
|
25948
|
+
width: 0;
|
|
25949
|
+
height: 0;
|
|
25950
|
+
vertical-align: middle;
|
|
25951
|
+
display: inline-block;
|
|
25952
|
+
border-top: 5px dashed;
|
|
25953
|
+
border-right: 5px solid transparent;
|
|
25954
|
+
border-left: 5px solid transparent;
|
|
25955
|
+
color: white;
|
|
25956
|
+
}
|
|
25957
|
+
.lm_header .lm_tabdropdown_list {
|
|
25958
|
+
position: absolute;
|
|
25959
|
+
top: 20px;
|
|
25960
|
+
right: 0;
|
|
25961
|
+
z-index: 5;
|
|
25962
|
+
overflow: hidden;
|
|
25963
|
+
}
|
|
25964
|
+
.lm_header .lm_tabdropdown_list .lm_tab {
|
|
25965
|
+
clear: both;
|
|
25966
|
+
padding-right: 10px;
|
|
25967
|
+
margin: 0;
|
|
25968
|
+
}
|
|
25969
|
+
.lm_header .lm_tabdropdown_list .lm_tab .lm_title {
|
|
25970
|
+
width: 100px;
|
|
25971
|
+
}
|
|
25972
|
+
.lm_header .lm_tabdropdown_list .lm_close_tab {
|
|
25973
|
+
display: none !important;
|
|
25974
|
+
}
|
|
25975
|
+
.lm_dragProxy {
|
|
25976
|
+
position: absolute;
|
|
25977
|
+
top: 0;
|
|
25978
|
+
left: 0;
|
|
25979
|
+
z-index: 30;
|
|
25980
|
+
}
|
|
25981
|
+
.lm_dragProxy .lm_header {
|
|
25982
|
+
background: transparent;
|
|
25983
|
+
}
|
|
25984
|
+
.lm_dragProxy .lm_content {
|
|
25985
|
+
border-top: none;
|
|
25986
|
+
overflow: hidden;
|
|
25987
|
+
}
|
|
25988
|
+
.lm_dropTargetIndicator {
|
|
25989
|
+
display: none;
|
|
25990
|
+
position: absolute;
|
|
25991
|
+
z-index: 35;
|
|
25992
|
+
transition: all 200ms ease;
|
|
25993
|
+
}
|
|
25994
|
+
.lm_dropTargetIndicator .lm_inner {
|
|
25995
|
+
width: 100%;
|
|
25996
|
+
height: 100%;
|
|
25997
|
+
position: relative;
|
|
25998
|
+
top: 0;
|
|
25999
|
+
left: 0;
|
|
26000
|
+
}
|
|
26001
|
+
.lm_transition_indicator {
|
|
26002
|
+
display: none;
|
|
26003
|
+
width: 20px;
|
|
26004
|
+
height: 20px;
|
|
26005
|
+
position: absolute;
|
|
26006
|
+
top: 0;
|
|
26007
|
+
left: 0;
|
|
26008
|
+
z-index: 20;
|
|
26009
|
+
}
|
|
26010
|
+
.lm_popin {
|
|
26011
|
+
width: 20px;
|
|
26012
|
+
height: 20px;
|
|
26013
|
+
position: absolute;
|
|
26014
|
+
bottom: 0;
|
|
26015
|
+
right: 0;
|
|
26016
|
+
z-index: 9999;
|
|
26017
|
+
}
|
|
26018
|
+
.lm_popin > * {
|
|
26019
|
+
width: 100%;
|
|
26020
|
+
height: 100%;
|
|
26021
|
+
position: absolute;
|
|
26022
|
+
top: 0;
|
|
26023
|
+
left: 0;
|
|
26024
|
+
}
|
|
26025
|
+
.lm_popin > .lm_bg {
|
|
26026
|
+
z-index: 10;
|
|
26027
|
+
}
|
|
26028
|
+
.lm_popin > .lm_icon {
|
|
26029
|
+
z-index: 20;
|
|
26030
|
+
}
|
|
26031
|
+
|
|
26032
|
+
/* ── Adapted theme (using --gve- CSS variables) ──────────────────────── */
|
|
26033
|
+
.lm_goldenlayout {
|
|
26034
|
+
background: transparent;
|
|
26035
|
+
}
|
|
26036
|
+
.lm_content {
|
|
26037
|
+
background: var(--gve-bg-color, #fafafa);
|
|
26038
|
+
border: none;
|
|
26039
|
+
}
|
|
26040
|
+
.lm_dragProxy .lm_content {
|
|
26041
|
+
box-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
|
26042
|
+
}
|
|
26043
|
+
.lm_dropTargetIndicator {
|
|
26044
|
+
box-shadow: inset 0 0 30px rgba(0,0,0,0.3);
|
|
26045
|
+
outline: 1px dashed var(--gve-border-color, #ccc);
|
|
26046
|
+
}
|
|
26047
|
+
.lm_dropTargetIndicator .lm_inner {
|
|
26048
|
+
background: #000;
|
|
26049
|
+
opacity: 0.08;
|
|
26050
|
+
}
|
|
26051
|
+
.lm_splitter {
|
|
26052
|
+
background: var(--gve-border-color, #ccc);
|
|
26053
|
+
opacity: 0.4;
|
|
26054
|
+
transition: opacity 200ms ease;
|
|
26055
|
+
}
|
|
26056
|
+
.lm_splitter:hover,
|
|
26057
|
+
.lm_splitter.lm_dragging {
|
|
26058
|
+
background: var(--gve-border-color, #aaa);
|
|
26059
|
+
opacity: 1;
|
|
26060
|
+
}
|
|
26061
|
+
.lm_header {
|
|
26062
|
+
height: 26px;
|
|
26063
|
+
background: var(--gve-toolbar-bg, #f5f5f5);
|
|
26064
|
+
border-bottom: 1px solid var(--gve-border-color, #ddd);
|
|
26065
|
+
}
|
|
26066
|
+
.lm_header .lm_tab {
|
|
26067
|
+
font-family: Arial, sans-serif;
|
|
26068
|
+
font-size: 12px;
|
|
26069
|
+
font-weight: 600;
|
|
26070
|
+
color: var(--gve-text-secondary, #777);
|
|
26071
|
+
background: transparent;
|
|
26072
|
+
margin-right: 2px;
|
|
26073
|
+
padding: 5px 10px 4px;
|
|
26074
|
+
padding-right: 14px;
|
|
26075
|
+
border: none;
|
|
26076
|
+
}
|
|
26077
|
+
.lm_header .lm_tab .lm_title {
|
|
26078
|
+
padding-top: 1px;
|
|
26079
|
+
}
|
|
26080
|
+
.lm_header .lm_tab.lm_active {
|
|
26081
|
+
background: var(--gve-bg-color, #fff);
|
|
26082
|
+
color: var(--gve-text-color, #333);
|
|
26083
|
+
border-bottom: 2px solid var(--gve-primary-color, #007bff);
|
|
26084
|
+
padding-bottom: 3px;
|
|
26085
|
+
}
|
|
26086
|
+
.lm_header .lm_tab:hover {
|
|
26087
|
+
color: var(--gve-text-color, #333);
|
|
26088
|
+
}
|
|
26089
|
+
.lm_header .lm_controls .lm_tabdropdown:before {
|
|
26090
|
+
color: var(--gve-text-color, #333);
|
|
26091
|
+
}
|
|
26092
|
+
.lm_transition_indicator {
|
|
26093
|
+
background: #000;
|
|
26094
|
+
border: 1px dashed #555;
|
|
26095
|
+
}
|
|
26096
|
+
.lm_popin {
|
|
26097
|
+
cursor: pointer;
|
|
26098
|
+
}
|
|
26099
|
+
.lm_popin .lm_bg {
|
|
26100
|
+
background: #000;
|
|
26101
|
+
opacity: 0.7;
|
|
26102
|
+
}
|
|
25960
26103
|
`;
|
|
25961
26104
|
|
|
25962
26105
|
/**
|
|
@@ -25971,7 +26114,7 @@ class GveSnapshotRendition extends HTMLElement {
|
|
|
25971
26114
|
* of the web component is loaded.
|
|
25972
26115
|
*/
|
|
25973
26116
|
static get version() {
|
|
25974
|
-
return "2.0.
|
|
26117
|
+
return "2.0.10";
|
|
25975
26118
|
}
|
|
25976
26119
|
constructor() {
|
|
25977
26120
|
super();
|
|
@@ -26297,6 +26440,20 @@ class GveSnapshotRendition extends HTMLElement {
|
|
|
26297
26440
|
* The SVG rendition panel has no header and acts as the fixed background.
|
|
26298
26441
|
*/
|
|
26299
26442
|
initializeGoldenLayout() {
|
|
26443
|
+
// Guard against stale rAF callbacks: render() can run more than once
|
|
26444
|
+
// before the first scheduled callback fires (e.g. a consumer sets
|
|
26445
|
+
// settings/data before the element is connected, then connectedCallback
|
|
26446
|
+
// renders again). Without these checks two GoldenLayout instances would
|
|
26447
|
+
// be created on the same container, duplicating panels and leaking the
|
|
26448
|
+
// first instance's ResizeObserver.
|
|
26449
|
+
if (!this.isConnected) {
|
|
26450
|
+
this._logger.debug("GL", "Skipping GL init - component not connected");
|
|
26451
|
+
return;
|
|
26452
|
+
}
|
|
26453
|
+
if (this._goldenLayout) {
|
|
26454
|
+
this._goldenLayout.destroy();
|
|
26455
|
+
this._goldenLayout = undefined;
|
|
26456
|
+
}
|
|
26300
26457
|
const glContainer = this._shadow.getElementById("gl-container");
|
|
26301
26458
|
if (!glContainer) {
|
|
26302
26459
|
this._logger.error("GL", "gl-container not found in Shadow DOM");
|
|
@@ -26368,6 +26525,18 @@ class GveSnapshotRendition extends HTMLElement {
|
|
|
26368
26525
|
showPopoutIcon: false,
|
|
26369
26526
|
showMaximiseIcon: false,
|
|
26370
26527
|
showCloseIcon: false,
|
|
26528
|
+
// Tab dragging/docking is disabled: the four panels are fixed
|
|
26529
|
+
// functional areas, and users only need the splitters to
|
|
26530
|
+
// redistribute space. This prevents dragging a panel into another
|
|
26531
|
+
// stack (or into limbo) and losing the intended layout. The
|
|
26532
|
+
// "reset layout" toolbar button remains the escape hatch.
|
|
26533
|
+
reorderEnabled: false,
|
|
26534
|
+
},
|
|
26535
|
+
dimensions: {
|
|
26536
|
+
// Prevent panels from being collapsed to zero size via the
|
|
26537
|
+
// splitters, which would make them hard to grab back.
|
|
26538
|
+
defaultMinItemWidth: "80px",
|
|
26539
|
+
defaultMinItemHeight: "60px",
|
|
26371
26540
|
},
|
|
26372
26541
|
root: {
|
|
26373
26542
|
type: "column",
|
|
@@ -26569,12 +26738,13 @@ class GveSnapshotRendition extends HTMLElement {
|
|
|
26569
26738
|
if (refNodes.length === 0) {
|
|
26570
26739
|
this._logger.warn(`No reference nodes found for operation ${operation.id}`);
|
|
26571
26740
|
// Still fire event even if no reference nodes
|
|
26741
|
+
const previousVersion = this._currentVersion;
|
|
26572
26742
|
this._currentVersion = versionTag;
|
|
26573
26743
|
// Extract version index from tag (e.g., "v2" → 2)
|
|
26574
26744
|
const versionMatch = versionTag.match(/v(\d+)/);
|
|
26575
26745
|
this._currentVersionIndex = versionMatch ? parseInt(versionMatch[1]) : 0;
|
|
26576
26746
|
this.dispatchEvent(new CustomEvent("versionTagChange", {
|
|
26577
|
-
detail: { from:
|
|
26747
|
+
detail: { from: previousVersion, to: versionTag },
|
|
26578
26748
|
}));
|
|
26579
26749
|
return;
|
|
26580
26750
|
}
|
|
@@ -40952,7 +41122,7 @@ function requireD () {
|
|
|
40952
41122
|
+ 'pragma private protected public pure ref return scope shared static struct '
|
|
40953
41123
|
+ 'super switch synchronized template this throw try typedef typeid typeof union '
|
|
40954
41124
|
+ 'unittest version void volatile while with __FILE__ __LINE__ __gshared|10 '
|
|
40955
|
-
+ '__thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ 2.0.
|
|
41125
|
+
+ '__thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ 2.0.10',
|
|
40956
41126
|
built_in:
|
|
40957
41127
|
'bool cdouble cent cfloat char creal dchar delegate double dstring float function '
|
|
40958
41128
|
+ 'idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar '
|
|
@@ -90566,23 +90736,36 @@ class GveHintDesigner extends HTMLElement {
|
|
|
90566
90736
|
this._isPlaying = false;
|
|
90567
90737
|
// Create logger
|
|
90568
90738
|
this._logger = new Logger("GVE-HintDesigner", this._settings.debug);
|
|
90569
|
-
|
|
90739
|
+
this.registerGsapPlugins();
|
|
90740
|
+
// Create shadow DOM
|
|
90741
|
+
this._shadow = this.attachShadow({ mode: "open" });
|
|
90742
|
+
this._logger.info("GveHintDesigner component created");
|
|
90743
|
+
}
|
|
90744
|
+
/**
|
|
90745
|
+
* Register GSAP plugins when GSAP is available. Retried in
|
|
90746
|
+
* connectedCallback because GSAP may be loaded after the component
|
|
90747
|
+
* class is constructed.
|
|
90748
|
+
*/
|
|
90749
|
+
registerGsapPlugins() {
|
|
90570
90750
|
const gsap = window.gsap;
|
|
90571
|
-
if (gsap) {
|
|
90751
|
+
if (gsap && !gsap.plugins?.drawSVG) {
|
|
90572
90752
|
gsap.registerPlugin(DrawSVGPlugin);
|
|
90573
90753
|
this._logger.debug("Component", "DrawSVG plugin registered:", gsap.plugins.drawSVG !== undefined);
|
|
90574
90754
|
}
|
|
90575
|
-
// Create shadow DOM
|
|
90576
|
-
this._shadow = this.attachShadow({ mode: "open" });
|
|
90577
|
-
this._logger.info("GveHintDesigner component created");
|
|
90578
90755
|
}
|
|
90579
90756
|
/**
|
|
90580
90757
|
* Called when component is added to the DOM.
|
|
90581
90758
|
*/
|
|
90582
90759
|
connectedCallback() {
|
|
90583
90760
|
this._logger.info("Component connected to DOM");
|
|
90761
|
+
this.registerGsapPlugins();
|
|
90584
90762
|
this.render();
|
|
90585
90763
|
this.initializeEventListeners();
|
|
90764
|
+
// Reflect any state assigned before the element was connected (a common
|
|
90765
|
+
// pattern in vanilla consumers: set data/settings/hintId first, append
|
|
90766
|
+
// later). render() builds empty dropdowns/tables, so populate them now.
|
|
90767
|
+
this.refreshUI();
|
|
90768
|
+
this.refreshVariablesTable();
|
|
90586
90769
|
}
|
|
90587
90770
|
/**
|
|
90588
90771
|
* Called when component is removed from the DOM.
|
|
@@ -90644,9 +90827,10 @@ class GveHintDesigner extends HTMLElement {
|
|
|
90644
90827
|
this._hintVariables = value || [];
|
|
90645
90828
|
}
|
|
90646
90829
|
this.refreshVariablesTable();
|
|
90647
|
-
|
|
90648
|
-
|
|
90649
|
-
|
|
90830
|
+
// NOTE: no hintVariablesChange event here. Change events represent USER
|
|
90831
|
+
// edits (add/edit/delete/populate, which fire it themselves); firing from
|
|
90832
|
+
// the setter would echo programmatic assignments back to the consumer,
|
|
90833
|
+
// which can loop forever with two-way bindings that clone the array.
|
|
90650
90834
|
}
|
|
90651
90835
|
// ==================== RENDERING ====================
|
|
90652
90836
|
/**
|
|
@@ -91261,7 +91445,11 @@ class GveHintDesigner extends HTMLElement {
|
|
|
91261
91445
|
this._lastPanY = mouseEvent.clientY;
|
|
91262
91446
|
panel.style.cursor = "grabbing";
|
|
91263
91447
|
});
|
|
91264
|
-
|
|
91448
|
+
// Remove any handlers from a previous connect cycle before adding new
|
|
91449
|
+
// ones: connectedCallback re-runs initializeEventListeners each time the
|
|
91450
|
+
// element is (re)attached, and document-level listeners would accumulate.
|
|
91451
|
+
this.removeDocumentPanListeners();
|
|
91452
|
+
this._panMoveHandler = (e) => {
|
|
91265
91453
|
if (!this._isPanning)
|
|
91266
91454
|
return;
|
|
91267
91455
|
const deltaX = e.clientX - this._lastPanX;
|
|
@@ -91271,17 +91459,34 @@ class GveHintDesigner extends HTMLElement {
|
|
|
91271
91459
|
this._lastPanX = e.clientX;
|
|
91272
91460
|
this._lastPanY = e.clientY;
|
|
91273
91461
|
this.updateSvgTransform();
|
|
91274
|
-
}
|
|
91275
|
-
document.addEventListener("
|
|
91462
|
+
};
|
|
91463
|
+
document.addEventListener("mousemove", this._panMoveHandler);
|
|
91464
|
+
this._panUpHandler = () => {
|
|
91276
91465
|
if (this._isPanning) {
|
|
91277
91466
|
this._isPanning = false;
|
|
91278
|
-
const panelEl = panel;
|
|
91279
|
-
panelEl
|
|
91467
|
+
const panelEl = this._shadow.querySelector(".svg-display-panel");
|
|
91468
|
+
if (panelEl) {
|
|
91469
|
+
panelEl.style.cursor = "grab";
|
|
91470
|
+
}
|
|
91280
91471
|
}
|
|
91281
|
-
}
|
|
91472
|
+
};
|
|
91473
|
+
document.addEventListener("mouseup", this._panUpHandler);
|
|
91282
91474
|
// Set initial cursor
|
|
91283
91475
|
panel.style.cursor = "grab";
|
|
91284
91476
|
}
|
|
91477
|
+
/**
|
|
91478
|
+
* Remove the document-level pan listeners, if any.
|
|
91479
|
+
*/
|
|
91480
|
+
removeDocumentPanListeners() {
|
|
91481
|
+
if (this._panMoveHandler) {
|
|
91482
|
+
document.removeEventListener("mousemove", this._panMoveHandler);
|
|
91483
|
+
this._panMoveHandler = undefined;
|
|
91484
|
+
}
|
|
91485
|
+
if (this._panUpHandler) {
|
|
91486
|
+
document.removeEventListener("mouseup", this._panUpHandler);
|
|
91487
|
+
this._panUpHandler = undefined;
|
|
91488
|
+
}
|
|
91489
|
+
}
|
|
91285
91490
|
/**
|
|
91286
91491
|
* Initialize zooming with mouse wheel.
|
|
91287
91492
|
*/
|
|
@@ -91325,6 +91530,8 @@ class GveHintDesigner extends HTMLElement {
|
|
|
91325
91530
|
clearInterval(this._progressUpdateInterval);
|
|
91326
91531
|
this._progressUpdateInterval = undefined;
|
|
91327
91532
|
}
|
|
91533
|
+
this.removeDocumentPanListeners();
|
|
91534
|
+
this._isPanning = false;
|
|
91328
91535
|
}
|
|
91329
91536
|
// ==================== UI ACTIONS ====================
|
|
91330
91537
|
/**
|
|
@@ -91693,13 +91900,14 @@ class GveHintDesigner extends HTMLElement {
|
|
|
91693
91900
|
const confirm = window.confirm(`Delete hint "${this._hintId}"?`);
|
|
91694
91901
|
if (!confirm)
|
|
91695
91902
|
return;
|
|
91696
|
-
|
|
91903
|
+
const deletedId = this._hintId;
|
|
91904
|
+
delete this._data.hints[deletedId];
|
|
91697
91905
|
this.hintId = undefined;
|
|
91698
91906
|
this.refreshHintsDropdown();
|
|
91699
91907
|
this.clearHintForm();
|
|
91700
91908
|
this.showMessage("Hint deleted", "success");
|
|
91701
91909
|
this.fireEvent("dataChange", { data: this._data });
|
|
91702
|
-
this._logger.info("Deleted hint",
|
|
91910
|
+
this._logger.info("Deleted hint", deletedId);
|
|
91703
91911
|
}
|
|
91704
91912
|
/**
|
|
91705
91913
|
* Parse offset value (number or string with suffix).
|
|
@@ -91795,26 +92003,29 @@ class GveHintDesigner extends HTMLElement {
|
|
|
91795
92003
|
}
|
|
91796
92004
|
/**
|
|
91797
92005
|
* Resolve variables in SVG code.
|
|
92006
|
+
* Delegates to the same resolver used by the renderer (shared semantics:
|
|
92007
|
+
* literal names — no regex injection — and "??" fallback chains like
|
|
92008
|
+
* {{r_fore-color ?? r_color}}).
|
|
91798
92009
|
*/
|
|
91799
92010
|
resolveVariables(svgCode) {
|
|
91800
|
-
|
|
91801
|
-
|
|
91802
|
-
const placeholder = `{{${variable.name}}}`;
|
|
91803
|
-
resolved = resolved.replace(new RegExp(placeholder, "g"), variable.value);
|
|
91804
|
-
});
|
|
91805
|
-
return resolved;
|
|
92011
|
+
const variables = new Map(this._hintVariables.map((v) => [v.name, v.value]));
|
|
92012
|
+
return resolvePlaceholders(svgCode, variables);
|
|
91806
92013
|
}
|
|
91807
92014
|
/**
|
|
91808
92015
|
* Extract variable names from SVG code (variables in {{...}} placeholders).
|
|
92016
|
+
* Placeholders may contain "??" fallback chains: each name in a chain is
|
|
92017
|
+
* reported as a separate variable.
|
|
91809
92018
|
*/
|
|
91810
92019
|
extractVariablesFromSvg(svgCode) {
|
|
91811
92020
|
const variablePattern = /\{\{([^}]+)\}\}/g;
|
|
91812
92021
|
const variables = [];
|
|
91813
92022
|
let match;
|
|
91814
92023
|
while ((match = variablePattern.exec(svgCode)) !== null) {
|
|
91815
|
-
const
|
|
91816
|
-
|
|
91817
|
-
variables.
|
|
92024
|
+
for (const name of match[1].split("??")) {
|
|
92025
|
+
const varName = name.trim();
|
|
92026
|
+
if (varName && !variables.includes(varName)) {
|
|
92027
|
+
variables.push(varName);
|
|
92028
|
+
}
|
|
91818
92029
|
}
|
|
91819
92030
|
}
|
|
91820
92031
|
return variables;
|
|
@@ -92828,9 +93039,8 @@ class GveHintDesigner extends HTMLElement {
|
|
|
92828
93039
|
syncSvgScroll() {
|
|
92829
93040
|
if (!this._svgTextarea || !this._svgHighlightContainer)
|
|
92830
93041
|
return;
|
|
92831
|
-
|
|
92832
|
-
|
|
92833
|
-
this._svgHighlightContainer.style.transform = `translate(-${scrollLeft}px, -${scrollTop}px)`;
|
|
93042
|
+
this._svgHighlightContainer.scrollTop = this._svgTextarea.scrollTop;
|
|
93043
|
+
this._svgHighlightContainer.scrollLeft = this._svgTextarea.scrollLeft;
|
|
92834
93044
|
}
|
|
92835
93045
|
/**
|
|
92836
93046
|
* Sync scroll position for JS textarea and highlight container.
|
|
@@ -92838,9 +93048,8 @@ class GveHintDesigner extends HTMLElement {
|
|
|
92838
93048
|
syncJsScroll() {
|
|
92839
93049
|
if (!this._jsTextarea || !this._jsHighlightContainer)
|
|
92840
93050
|
return;
|
|
92841
|
-
|
|
92842
|
-
|
|
92843
|
-
this._jsHighlightContainer.style.transform = `translate(-${scrollLeft}px, -${scrollTop}px)`;
|
|
93051
|
+
this._jsHighlightContainer.scrollTop = this._jsTextarea.scrollTop;
|
|
93052
|
+
this._jsHighlightContainer.scrollLeft = this._jsTextarea.scrollLeft;
|
|
92844
93053
|
}
|
|
92845
93054
|
/**
|
|
92846
93055
|
* Fire a custom event.
|