@yuneta/gobj-ui 7.23.148 → 7.23.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/gobj-ui.cjs.js +249 -52
- package/dist/gobj-ui.es.js +250 -53
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +114 -1
- package/src/c_yui_treedb_graph.js +48 -0
- package/src/treedb_elbow.js +102 -0
- package/src/treedb_elbow.test.js +105 -0
- package/src/yui_icons.css +6 -0
|
@@ -134,6 +134,7 @@ SDATA(data_type_t.DTP_LIST, "hidden_topics", sdata_flag_t.SDF_PERSIST, "[
|
|
|
134
134
|
SDATA(data_type_t.DTP_STRING, "main_topic", sdata_flag_t.SDF_PERSIST, "", "The topic the tree hangs from; empty = deduced by the graph. User preference, per treedb"),
|
|
135
135
|
SDATA(data_type_t.DTP_LIST, "loose_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics whose LOOSE records (no parent) are shown. User preference, per treedb"),
|
|
136
136
|
SDATA(data_type_t.DTP_STRING, "node_mode", sdata_flag_t.SDF_PERSIST, "expanded", "How the records are drawn: `expanded` = the cards with their pills and ports, `compact` = one-line pills with the name, `shape` = coloured figures, the topology alone. User preference"),
|
|
137
|
+
SDATA(data_type_t.DTP_STRING, "edge_shape", sdata_flag_t.SDF_PERSIST, "curved", "How the edges are drawn: `curved` or `elbow` (the bus of an org chart). User preference"),
|
|
137
138
|
SDATA(data_type_t.DTP_BOOLEAN, "node_labels", sdata_flag_t.SDF_PERSIST, true, "A `shape` node says its name under its figure. User preference"),
|
|
138
139
|
SDATA(data_type_t.DTP_DICT, "camera", sdata_flag_t.SDF_PERSIST, "{}", "Where the reader left the viewport: {zoom, x, y}. Handed to the engine on creation and written back when a move settles, so the graph opens where it was left. User preference, per treedb"),
|
|
139
140
|
SDATA(data_type_t.DTP_INTEGER, "fold_level", sdata_flag_t.SDF_PERSIST, 0, "The level of the MAIN tree the toolbar's stepper stands on: 1 = the main topic's roots, N = N-1 levels under them; 0 = never stepped, `expand_depth`. Handed to the engine on creation, so the graph opens at the level it was left. User preference, per treedb"),
|
|
@@ -283,6 +284,7 @@ function mt_create(gobj)
|
|
|
283
284
|
loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
|
|
284
285
|
node_mode: gobj_read_str_attr(gobj, "node_mode") || "expanded",
|
|
285
286
|
node_labels: gobj_read_bool_attr(gobj, "node_labels"),
|
|
287
|
+
edge_shape: gobj_read_str_attr(gobj, "edge_shape") || "curved",
|
|
286
288
|
camera: gobj_read_attr(gobj, "camera") || {},
|
|
287
289
|
},
|
|
288
290
|
gobj
|
|
@@ -625,6 +627,22 @@ function node_shape_items(gobj, wide)
|
|
|
625
627
|
}
|
|
626
628
|
}
|
|
627
629
|
],
|
|
630
|
+
/* How the lines are drawn: curves, or the elbows of an org
|
|
631
|
+
* chart. A toggle, so a pressed pill; disabled on a layout
|
|
632
|
+
* with no rows, where the edges stay curved anyway. */
|
|
633
|
+
['button', {class: 'GRAPH_EDGE_ELBOW button', type: 'button',
|
|
634
|
+
style: {height: wide, width: '2.5em'},
|
|
635
|
+
title: t('elbow edges'), 'data-i18n-title': 'elbow edges',
|
|
636
|
+
'aria-label': t('elbow edges'), 'data-i18n-aria-label': 'elbow edges',
|
|
637
|
+
'aria-pressed': 'false'},
|
|
638
|
+
yui_toolbar_icon('yi-diagram-project'),
|
|
639
|
+
{
|
|
640
|
+
click: (evt) => {
|
|
641
|
+
evt.stopPropagation();
|
|
642
|
+
gobj_send_event(gobj, "EV_TOGGLE_EDGE_SHAPE", {}, gobj);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
],
|
|
628
646
|
];
|
|
629
647
|
}
|
|
630
648
|
|
|
@@ -672,6 +690,17 @@ function refresh_node_shape_buttons(gobj, $root)
|
|
|
672
690
|
$labels.setAttribute('aria-pressed', labels? 'true' : 'false');
|
|
673
691
|
$labels.disabled = (mode !== 'shape');
|
|
674
692
|
}
|
|
693
|
+
|
|
694
|
+
let elbow = (gobj_read_str_attr(gobj, "edge_shape") === "elbow");
|
|
695
|
+
set_pressed_state($root, '.GRAPH_EDGE_ELBOW', elbow);
|
|
696
|
+
let $elbow = $root.querySelector('.GRAPH_EDGE_ELBOW');
|
|
697
|
+
if($elbow) {
|
|
698
|
+
$elbow.setAttribute('aria-pressed', elbow? 'true' : 'false');
|
|
699
|
+
let child = gobj.priv.gobj_nodes_tree;
|
|
700
|
+
let rowless = child? (gobj_read_attr(child, "rowless_layouts") || []) : [];
|
|
701
|
+
let layout = child? gobj_read_str_attr(child, "layout") : "";
|
|
702
|
+
$elbow.disabled = rowless.includes(layout);
|
|
703
|
+
}
|
|
675
704
|
}
|
|
676
705
|
|
|
677
706
|
/************************************************************
|
|
@@ -1319,6 +1348,7 @@ function populate_nodes_tree_options(gobj)
|
|
|
1319
1348
|
$layout_select.value = current_layout;
|
|
1320
1349
|
}
|
|
1321
1350
|
refresh_layout_stepper(gobj);
|
|
1351
|
+
refresh_node_shape_buttons(gobj, $container);
|
|
1322
1352
|
}
|
|
1323
1353
|
|
|
1324
1354
|
// Restore persisted operation_mode selection
|
|
@@ -2614,6 +2644,7 @@ function ac_set_layout(gobj, event, kw, src)
|
|
|
2614
2644
|
gobj
|
|
2615
2645
|
);
|
|
2616
2646
|
refresh_layout_stepper(gobj);
|
|
2647
|
+
refresh_node_shape_buttons(gobj, priv.$container);
|
|
2617
2648
|
|
|
2618
2649
|
return 0;
|
|
2619
2650
|
}
|
|
@@ -3116,6 +3147,7 @@ function ac_layout_autoset(gobj, event, kw, src)
|
|
|
3116
3147
|
}
|
|
3117
3148
|
$select.value = layout;
|
|
3118
3149
|
refresh_layout_stepper(gobj);
|
|
3150
|
+
refresh_node_shape_buttons(gobj, $container);
|
|
3119
3151
|
return 0;
|
|
3120
3152
|
}
|
|
3121
3153
|
|
|
@@ -3213,6 +3245,20 @@ function ac_set_node_mode(gobj, event, kw, src)
|
|
|
3213
3245
|
return 0;
|
|
3214
3246
|
}
|
|
3215
3247
|
|
|
3248
|
+
function ac_toggle_edge_shape(gobj, event, kw, src)
|
|
3249
|
+
{
|
|
3250
|
+
let priv = gobj.priv;
|
|
3251
|
+
let shape = (gobj_read_str_attr(gobj, "edge_shape") === "elbow")? "curved" : "elbow";
|
|
3252
|
+
|
|
3253
|
+
gobj_write_str_attr(gobj, "edge_shape", shape);
|
|
3254
|
+
gobj_save_persistent_attrs(gobj, "edge_shape");
|
|
3255
|
+
refresh_node_shape_buttons(gobj, priv.$container);
|
|
3256
|
+
if(priv.gobj_nodes_tree) {
|
|
3257
|
+
gobj_send_event(priv.gobj_nodes_tree, "EV_SET_EDGE_SHAPE", {edge_shape: shape}, gobj);
|
|
3258
|
+
}
|
|
3259
|
+
return 0;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3216
3262
|
function ac_toggle_node_labels(gobj, event, kw, src)
|
|
3217
3263
|
{
|
|
3218
3264
|
let priv = gobj.priv;
|
|
@@ -3395,6 +3441,7 @@ function create_gclass(gclass_name)
|
|
|
3395
3441
|
["EV_COLLAPSE_LEVEL", ac_fold_level_step, null],
|
|
3396
3442
|
["EV_SET_NODE_MODE", ac_set_node_mode, null],
|
|
3397
3443
|
["EV_TOGGLE_NODE_LABELS", ac_toggle_node_labels, null],
|
|
3444
|
+
["EV_TOGGLE_EDGE_SHAPE", ac_toggle_edge_shape, null],
|
|
3398
3445
|
["EV_LAYOUT_AUTOSET", ac_layout_autoset, null],
|
|
3399
3446
|
["EV_CAMERA_CHANGED", ac_camera_changed, null],
|
|
3400
3447
|
["EV_LEGEND_TOPIC", ac_legend_topic, null],
|
|
@@ -3441,6 +3488,7 @@ function create_gclass(gclass_name)
|
|
|
3441
3488
|
["EV_COLLAPSE_LEVEL", 0],
|
|
3442
3489
|
["EV_SET_NODE_MODE", 0],
|
|
3443
3490
|
["EV_TOGGLE_NODE_LABELS", 0],
|
|
3491
|
+
["EV_TOGGLE_EDGE_SHAPE", 0],
|
|
3444
3492
|
["EV_FIND_RESULT", 0],
|
|
3445
3493
|
["EV_LAYOUT_AUTOSET", 0],
|
|
3446
3494
|
["EV_CAMERA_CHANGED", 0],
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* treedb_elbow.js
|
|
3
|
+
*
|
|
4
|
+
* Where an elbow edge of the treedb graph turns, as plain
|
|
5
|
+
* geometry: the two elbow edge types of c_g6_nodes_tree.js ask
|
|
6
|
+
* this module for their control points on every draw, and it
|
|
7
|
+
* can be tested without G6.
|
|
8
|
+
*
|
|
9
|
+
* An elbow is mxGraph's tree routing: straight out of the
|
|
10
|
+
* parent's port to the channel half way to the child, along it,
|
|
11
|
+
* and straight into the child's port. Two cases are not that:
|
|
12
|
+
*
|
|
13
|
+
* - An edge that does not run FORWARD: the child above its
|
|
14
|
+
* parent, or in the same row. One of the two edges of a
|
|
15
|
+
* reciprocal pair (A -> B and B -> A) is always one. A channel
|
|
16
|
+
* half way between would cross both cards and lie on top of
|
|
17
|
+
* its partner, so it goes ROUND: a short run out of its port,
|
|
18
|
+
* along the outside of both cards, and into the other port
|
|
19
|
+
* from that port's own side.
|
|
20
|
+
*
|
|
21
|
+
* - Several edges joining the SAME two cards: a reciprocal pair,
|
|
22
|
+
* or two hooks of one parent reaching one child. Each gets a
|
|
23
|
+
* LANE: the first stays where it is, the next moves one step to
|
|
24
|
+
* one side, the next one step to the other. The lane follows
|
|
25
|
+
* the order the edges were made, so a new edge never moves the
|
|
26
|
+
* ones already drawn.
|
|
27
|
+
*
|
|
28
|
+
* Written for the top-down reading; left to right is the same
|
|
29
|
+
* geometry with the axes swapped.
|
|
30
|
+
*
|
|
31
|
+
* Copyright (c) 2026, ArtGins.
|
|
32
|
+
* All Rights Reserved.
|
|
33
|
+
***********************************************************************/
|
|
34
|
+
|
|
35
|
+
export const ELBOW_STEP = 10; /* between two lanes */
|
|
36
|
+
export const ELBOW_JETTY = 16; /* straight run out of a port before a detour turns */
|
|
37
|
+
export const ELBOW_CLEAR = 24; /* how far outside the two cards a detour runs */
|
|
38
|
+
|
|
39
|
+
/************************************************************
|
|
40
|
+
* The lane of `id` among `ids`, the edges joining the same two
|
|
41
|
+
* cards: 0, then +1, -1, +2, -2... in the order they were made.
|
|
42
|
+
* Edge ids end in a counter, so a numeric sort is that order.
|
|
43
|
+
************************************************************/
|
|
44
|
+
export function elbow_lane(ids, id)
|
|
45
|
+
{
|
|
46
|
+
let sorted = ids.slice().sort(
|
|
47
|
+
(a, b) => String(a).localeCompare(String(b), undefined, {numeric: true})
|
|
48
|
+
);
|
|
49
|
+
let k = sorted.indexOf(id);
|
|
50
|
+
if(k <= 0) {
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
let side = (k % 2)? 1 : -1;
|
|
54
|
+
return side * Math.ceil(k / 2);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function swap(p)
|
|
58
|
+
{
|
|
59
|
+
return [p[1], p[0]];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function swap_box(b)
|
|
63
|
+
{
|
|
64
|
+
return {x1: b.y1, y1: b.x1, x2: b.y2, y2: b.x2};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/************************************************************
|
|
68
|
+
* The control points of an elbow from the port `s` to the port
|
|
69
|
+
* `t` ([x, y] each), knowing the boxes of the two cards
|
|
70
|
+
* ({x1, y1, x2, y2}) and the edge's lane.
|
|
71
|
+
*
|
|
72
|
+
* vertical the rows are stacked (top to bottom); false
|
|
73
|
+
* when they are side by side (left to right)
|
|
74
|
+
************************************************************/
|
|
75
|
+
export function elbow_points(s, t, box_s, box_t, lane, vertical)
|
|
76
|
+
{
|
|
77
|
+
if(!vertical) {
|
|
78
|
+
return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true)
|
|
79
|
+
.map(swap);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let shift = lane * ELBOW_STEP;
|
|
83
|
+
|
|
84
|
+
/* Forward: the channel half way down, moved by the lane and
|
|
85
|
+
* kept strictly between the two ports. */
|
|
86
|
+
if(t[1] - s[1] > 2 * ELBOW_STEP) {
|
|
87
|
+
let mid = (s[1] + t[1]) / 2 + shift;
|
|
88
|
+
mid = Math.min(Math.max(mid, s[1] + ELBOW_STEP), t[1] - ELBOW_STEP);
|
|
89
|
+
return [[s[0], mid], [t[0], mid]];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Backwards, or across one row: round the outside. A negative
|
|
93
|
+
* lane goes round the left, any other the right, so the two
|
|
94
|
+
* detours of one pair never share a side. */
|
|
95
|
+
let out = ELBOW_JETTY + Math.abs(shift);
|
|
96
|
+
let side = (lane < 0)
|
|
97
|
+
? Math.min(box_s.x1, box_t.x1) - ELBOW_CLEAR - Math.abs(shift)
|
|
98
|
+
: Math.max(box_s.x2, box_t.x2) + ELBOW_CLEAR + Math.abs(shift);
|
|
99
|
+
let y1 = s[1] + out; /* a hook port is on the bottom edge */
|
|
100
|
+
let y2 = t[1] - out; /* an fkey port is on the top edge */
|
|
101
|
+
return [[s[0], y1], [side, y1], [side, y2], [t[0], y2]];
|
|
102
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* treedb_elbow.test.js
|
|
3
|
+
*
|
|
4
|
+
* The elbow edges of the treedb graph, pinned: the lane of an
|
|
5
|
+
* edge among those joining the same two cards, the channel of a
|
|
6
|
+
* forward edge, the detour of one that is not.
|
|
7
|
+
***********************************************************************/
|
|
8
|
+
import { describe, test, expect } from "vitest";
|
|
9
|
+
import { elbow_lane, elbow_points, ELBOW_STEP, ELBOW_CLEAR } from "./treedb_elbow.js";
|
|
10
|
+
|
|
11
|
+
/* Two cards 100x40: A on top, B one row below it. */
|
|
12
|
+
const A = {x1: 0, y1: 0, x2: 100, y2: 40};
|
|
13
|
+
const B = {x1: 0, y1: 130, x2: 100, y2: 170};
|
|
14
|
+
const A_HOOK = [50, 40]; /* A's bottom edge */
|
|
15
|
+
const A_FKEY = [50, 0]; /* A's top edge */
|
|
16
|
+
const B_HOOK = [60, 170];
|
|
17
|
+
const B_FKEY = [50, 130];
|
|
18
|
+
|
|
19
|
+
describe("the lane", () => {
|
|
20
|
+
test("one edge alone keeps the middle", () => {
|
|
21
|
+
expect(elbow_lane(["edge-t-7"], "edge-t-7")).toBe(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("lanes follow the order the edges were made, not the string order", () => {
|
|
25
|
+
let ids = ["edge-t-10", "edge-t-9", "edge-t-11"];
|
|
26
|
+
expect(elbow_lane(ids, "edge-t-9")).toBe(0);
|
|
27
|
+
expect(elbow_lane(ids, "edge-t-10")).toBe(1);
|
|
28
|
+
expect(elbow_lane(ids, "edge-t-11")).toBe(-1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("a new edge does not move the ones already drawn", () => {
|
|
32
|
+
let before = ["edge-t-1", "edge-t-2"];
|
|
33
|
+
let after = before.concat(["edge-t-3"]);
|
|
34
|
+
for(let id of before) {
|
|
35
|
+
expect(elbow_lane(after, id)).toBe(elbow_lane(before, id));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("a forward edge", () => {
|
|
41
|
+
test("turns in the channel half way between the ports", () => {
|
|
42
|
+
let pts = elbow_points(A_HOOK, B_FKEY, A, B, 0, true);
|
|
43
|
+
expect(pts).toEqual([[50, 85], [50, 85]]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("its lane moves the channel, and keeps it between the ports", () => {
|
|
47
|
+
let pts = elbow_points(A_HOOK, B_FKEY, A, B, 1, true);
|
|
48
|
+
expect(pts[0][1]).toBe(85 + ELBOW_STEP);
|
|
49
|
+
let far = elbow_points(A_HOOK, B_FKEY, A, B, 20, true);
|
|
50
|
+
expect(far[0][1]).toBeLessThan(B_FKEY[1]);
|
|
51
|
+
expect(far[0][1]).toBeGreaterThan(A_HOOK[1]);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("an edge that does not run forward", () => {
|
|
56
|
+
test("goes round the outside of both cards", () => {
|
|
57
|
+
/* B -> A: out of B's bottom, back up into A's top. */
|
|
58
|
+
let pts = elbow_points(B_HOOK, A_FKEY, B, A, 0, true);
|
|
59
|
+
expect(pts.length).toBe(4);
|
|
60
|
+
let side = pts[1][0];
|
|
61
|
+
expect(side).toBeGreaterThanOrEqual(100 + ELBOW_CLEAR);
|
|
62
|
+
expect(pts[2][0]).toBe(side);
|
|
63
|
+
expect(pts[0][1]).toBeGreaterThan(B.y2); /* below B */
|
|
64
|
+
expect(pts[3][1]).toBeLessThan(A.y1); /* above A */
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("a reciprocal pair shares no segment", () => {
|
|
68
|
+
let forward = elbow_points(A_HOOK, B_FKEY, A, B, 0, true);
|
|
69
|
+
let back = elbow_points(B_HOOK, A_FKEY, B, A, 1, true);
|
|
70
|
+
let channel = forward[0][1];
|
|
71
|
+
expect(back.every((p) => p[1] !== channel)).toBe(true);
|
|
72
|
+
expect(back[1][0]).toBeGreaterThan(A.x2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("a negative lane goes round the other side", () => {
|
|
76
|
+
let right = elbow_points(B_HOOK, A_FKEY, B, A, 1, true);
|
|
77
|
+
let left = elbow_points(B_HOOK, A_FKEY, B, A, -1, true);
|
|
78
|
+
expect(right[1][0]).toBeGreaterThan(A.x2);
|
|
79
|
+
expect(left[1][0]).toBeLessThan(A.x1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("two cards in one row are not joined through them", () => {
|
|
83
|
+
/* C beside A, same row: A's hook (bottom) to C's fkey (top). */
|
|
84
|
+
let C = {x1: 200, y1: 0, x2: 300, y2: 40};
|
|
85
|
+
let pts = elbow_points(A_HOOK, [250, 0], A, C, 0, true);
|
|
86
|
+
expect(pts.length).toBe(4);
|
|
87
|
+
expect(pts[1][0]).toBeGreaterThan(C.x2);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("read left to right", () => {
|
|
92
|
+
test("is the same geometry with the axes swapped", () => {
|
|
93
|
+
let sw = (p) => [p[1], p[0]];
|
|
94
|
+
let swb = (b) => ({x1: b.y1, y1: b.x1, x2: b.y2, y2: b.x2});
|
|
95
|
+
for(let [s, t, bs, bt, lane] of [
|
|
96
|
+
[A_HOOK, B_FKEY, A, B, 0],
|
|
97
|
+
[A_HOOK, B_FKEY, A, B, 1],
|
|
98
|
+
[B_HOOK, A_FKEY, B, A, -1],
|
|
99
|
+
]) {
|
|
100
|
+
let down = elbow_points(s, t, bs, bt, lane, true);
|
|
101
|
+
let right = elbow_points(sw(s), sw(t), swb(bs), swb(bt), lane, false);
|
|
102
|
+
expect(right).toEqual(down.map(sw));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
package/src/yui_icons.css
CHANGED
|
@@ -444,3 +444,9 @@
|
|
|
444
444
|
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zm11.3-387.3l104 104c4.6 4.6 5.9 11.5 3.5 17.4S366.5 256 360 256l-56 0 0 96c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-96-56 0c-6.5 0-12.3-3.9-14.8-9.9s-1.1-12.9 3.5-17.4l104-104c6.2-6.2 16.4-6.2 22.6 0z'/%3E%3C/svg%3E");
|
|
445
445
|
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zm11.3-387.3l104 104c4.6 4.6 5.9 11.5 3.5 17.4S366.5 256 360 256l-56 0 0 96c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-96-56 0c-6.5 0-12.3-3.9-14.8-9.9s-1.1-12.9 3.5-17.4l104-104c6.2-6.2 16.4-6.2 22.6 0z'/%3E%3C/svg%3E");
|
|
446
446
|
}
|
|
447
|
+
|
|
448
|
+
/* Elbow edges: boxes joined by right-angled lines (FontAwesome Free 7.3.1). */
|
|
449
|
+
.yi-diagram-project::before {
|
|
450
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M0 80C0 53.5 21.5 32 48 32l96 0c26.5 0 48 21.5 48 48l0 16 128 0 0-16c0-26.5 21.5-48 48-48l96 0c26.5 0 48 21.5 48 48l0 96c0 26.5-21.5 48-48 48l-96 0c-26.5 0-48-21.5-48-48l0-16-128 0 0 16c0 7.3-1.7 14.3-4.6 20.5l68.6 91.5 80 0c26.5 0 48 21.5 48 48l0 96c0 26.5-21.5 48-48 48l-96 0c-26.5 0-48-21.5-48-48l0-96c0-7.3 1.7-14.3 4.6-20.5L128 224 48 224c-26.5 0-48-21.5-48-48L0 80z'/%3E%3C/svg%3E");
|
|
451
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M0 80C0 53.5 21.5 32 48 32l96 0c26.5 0 48 21.5 48 48l0 16 128 0 0-16c0-26.5 21.5-48 48-48l96 0c26.5 0 48 21.5 48 48l0 96c0 26.5-21.5 48-48 48l-96 0c-26.5 0-48-21.5-48-48l0-16-128 0 0 16c0 7.3-1.7 14.3-4.6 20.5l68.6 91.5 80 0c26.5 0 48 21.5 48 48l0 96c0 26.5-21.5 48-48 48l-96 0c-26.5 0-48-21.5-48-48l0-96c0-7.3 1.7-14.3 4.6-20.5L128 224 48 224c-26.5 0-48-21.5-48-48L0 80z'/%3E%3C/svg%3E");
|
|
452
|
+
}
|