COMMENTSTYLE edge
diff --git a/lib/WeatherMap.class.php b/lib/WeatherMap.class.php
index b7e5c672..b280a9ae 100644
--- a/lib/WeatherMap.class.php
+++ b/lib/WeatherMap.class.php
@@ -2763,6 +2763,7 @@ function ReadConfig($input, $is_include = false) {
['LINK', '/^\s*OUTCOMMENT\s+(.*)\s*$/i', ['comments[OUT]' => 1]],
['LINK', '/^\s*BWFONT\s+(\d+)\s*$/i', ['bwfont' => 1]],
['LINK', '/^\s*COMMENTFONT\s+(\d+)\s*$/i', ['commentfont' => 1]],
+ ['LINK', '/^\s*COMMENTCURVE\s+([01])\s*$/i', ['commentcurve' => 1]],
['LINK', '/^\s*COMMENTSTYLE\s+(edge|center)\s*$/i', ['commentstyle' => 1]],
['LINK', '/^\s*DUPLEX\s+(full|half)\s*$/i', ['duplex' => 1]],
['LINK', '/^\s*BWSTYLE\s+(classic|angled)\s*$/i', ['labelboxstyle' => 1]],
diff --git a/lib/WeatherMap.keywords.inc.php b/lib/WeatherMap.keywords.inc.php
index 24d77dbe..fe0dc303 100644
--- a/lib/WeatherMap.keywords.inc.php
+++ b/lib/WeatherMap.keywords.inc.php
@@ -757,6 +757,9 @@
['linkstyle' => 1]
],
],
+ 'COMMENTCURVE' => [
+ ['LINK', '/^COMMENTCURVE\s+([01])\s*$/i', ['commentcurve' => 1]],
+ ],
'COMMENTSTYLE' => [
[
'LINK',
diff --git a/lib/WeatherMapLink.class.php b/lib/WeatherMapLink.class.php
index 4dcf2796..8d89c48c 100644
--- a/lib/WeatherMapLink.class.php
+++ b/lib/WeatherMapLink.class.php
@@ -166,6 +166,7 @@ class WeatherMapLink extends WeatherMapItem {
var $commentfontcolour;
/** @var mixed */
var $commentstyle;
+ var $commentcurve;
/** @var mixed */
var $bwfontcolour;
@@ -204,6 +205,7 @@ function __construct() {
'commentoffset_out' => 5,
'commentoffset_in' => 95,
'commentstyle' => 'edge',
+ 'commentcurve' => 1,
'arrowstyle' => 'classic',
'viastyle' => 'curved',
'usescale' => 'DEFAULT',
@@ -307,6 +309,116 @@ function CopyFrom(&$source) {
}
}
+ /**
+ * Draw a comment along the sampled link spine. Return false before drawing
+ * anything when the font or geometry requires the ordinary straight label.
+ */
+ function DrawCurvedComment($image, $comment, $colour, $width, $percent) {
+ $font = $this->owner->fonts[$this->commentfont] ?? null;
+ if (!$font || $font->type !== 'truetype' || strpbrk($comment, "\r\n") !== false) {
+ return false;
+ }
+ $glyphs = [];
+ if (!preg_match_all('/\X/u', $comment, $matches) || count($matches[0]) > 512) {
+ return false;
+ }
+ // Rebuild distances, discarding repeated VIA points to avoid zero tangents.
+ $points = [];
+ $total = 0.0;
+ foreach ($this->curvepoints as $point) {
+ if ($points) {
+ $last = $points[count($points) - 1];
+ $step = hypot($point[0] - $last[0], $point[1] - $last[1]);
+ if ($step < 0.001) {
+ continue;
+ }
+ $total += $step;
+ }
+ $points[] = [$point[0], $point[1], $total];
+ }
+ if (count($points) < 2 || $total < 1) {
+ return false;
+ }
+ $at = static function ($distance) use ($points, $total) {
+ $distance = max(0, min($total, $distance));
+ for ($i = 1; $i < count($points); $i++) {
+ if ($distance <= $points[$i][2]) {
+ $a = $points[$i - 1];
+ $b = $points[$i];
+ $t = ($distance - $a[2]) / ($b[2] - $a[2]);
+ return [$a[0] + $t * ($b[0] - $a[0]), $a[1] + $t * ($b[1] - $a[1])];
+ }
+ }
+ return array_slice($points[count($points) - 1], 0, 2);
+ };
+ $file = wm_font_file($font->file);
+ // A trailing marker includes space advances, which a glyph's ink box omits.
+ $marker = imagettfbbox($font->size, 0, $file, '|');
+ if ($marker === false) {
+ return false;
+ }
+ $prefix = '';
+ $length = 0.0;
+ foreach ($matches[0] as $glyph) {
+ $prefix .= $glyph;
+ $box = imagettfbbox($font->size, 0, $file, $prefix . '|');
+ if ($box === false) {
+ return false;
+ }
+ $next = $box[2] - $marker[2];
+ $advance = $next - $length;
+ if ($advance <= 0) {
+ return false;
+ }
+ $glyphs[] = [$glyph, $length, $advance];
+ $length = $next;
+ }
+ if ($length > $total || $length < 1) {
+ return false;
+ }
+ $anchor = max(0, min($total, $total * $percent / 100));
+ $a = $at($anchor - 1);
+ $b = $at($anchor + 1);
+ $reverse = $b[0] < $a[0];
+ $sign = $reverse ? -1 : 1;
+ $anchor += $sign * (int)$this->get_hint('comment_nudgealong');
+ $anchor = $reverse ? max($length, min($total, $anchor)) : max(0, min($total - $length, $anchor));
+ [, $height] = $this->owner->myimagestringsize($this->commentfont, $comment);
+ $offset = (int)$this->get_hint('comment_nudgeout');
+ $offset += $this->commentstyle === 'center' ? -$height / 2 : $width + 4;
+ $layout = [];
+ $previous = null;
+ foreach ($glyphs as [$glyph, $start, $advance]) {
+ $distance = $anchor + $sign * ($start + $advance / 2);
+ $p = $at($distance);
+ $a = $at($distance - max(1, $advance / 2));
+ $b = $at($distance + max(1, $advance / 2));
+ $dx = ($b[0] - $a[0]) * $sign;
+ $dy = ($b[1] - $a[1]) * $sign;
+ $norm = hypot($dx, $dy);
+ if ($norm < 0.001) {
+ return false;
+ }
+ $dx /= $norm;
+ $dy /= $norm;
+ // Keep the whole word upright; reject abrupt bends between letters.
+ if ($dx < -0.05 || ($previous && $dx * $previous[0] + $dy * $previous[1] < cos(deg2rad(35)))) {
+ return false;
+ }
+ $x = $p[0] + $dy * $offset;
+ $y = $p[1] - $dx * $offset;
+ if ($previous && hypot($x - $previous[2], $y - $previous[3]) < 0.65 * ($advance + $previous[4]) / 2) {
+ return false;
+ }
+ $layout[] = [$x - $dx * $advance / 2, $y - $dy * $advance / 2, $glyph, rad2deg(atan2(-$dy, $dx))];
+ $previous = [$dx, $dy, $x, $y, $advance];
+ }
+ foreach ($layout as [$x, $y, $glyph, $angle]) {
+ $this->owner->myimagestring($image, $this->commentfont, $x, $y, $glyph, $colour, $angle);
+ }
+ return true;
+ }
+
/**
* image = GD image references
* col = array of Colour objects
@@ -351,6 +463,10 @@ function DrawComments($image, $col, $widths) {
if ($comment != '') {
// print "\n\n----------------------------------------------------------------\nComment $dir for ".$this->name."\n";
+ if ($this->commentcurve && $this->DrawCurvedComment($image, $comment, $col[$dir], $widths[$dir], $commentpos[$dir])) {
+ continue;
+ }
+
[$textlength, $textheight] = $this->owner->myimagestringsize($this->commentfont, $comment);
$extra_percent = $commentpos[$dir];
@@ -714,6 +830,7 @@ function WriteConfig() {
['splitpos', 'SPLITPOS', CONFIG_TYPE_LITERAL],
['duplex', 'DUPLEX', CONFIG_TYPE_LITERAL],
['commentstyle', 'COMMENTSTYLE', CONFIG_TYPE_LITERAL],
+ ['commentcurve', 'COMMENTCURVE', CONFIG_TYPE_LITERAL],
['labelboxstyle', 'BWSTYLE', CONFIG_TYPE_LITERAL],
// ['usescale', 'USESCALE', CONFIG_TYPE_LITERAL],
diff --git a/lib/editor.actions.php b/lib/editor.actions.php
index a315941a..6cccd520 100644
--- a/lib/editor.actions.php
+++ b/lib/editor.actions.php
@@ -613,7 +613,7 @@ function setMapStyle($mapfile) {
handle_inheritance($map, $inheritables);
wm_comment_update_default($map, 'bwfont', wm_comment_resolve_font($map, get_nfilter_request_var('mapstyle_linkfont')));
- wm_comment_apply_style($map, get_nfilter_request_var('mapstyle_commentfont'), get_nfilter_request_var('mapstyle_commentcolour'));
+ wm_comment_apply_style($map, get_nfilter_request_var('mapstyle_commentfont'), get_nfilter_request_var('mapstyle_commentcolour'), isset($_POST['mapstyle_commentcurve']) ? get_nfilter_request_var('mapstyle_commentcurve') : '0');
if (!wm_editor_write_config($map, $mapfile)) return;
}
diff --git a/lib/editor.comment-style.php b/lib/editor.comment-style.php
index c93be069..c67e4da6 100644
--- a/lib/editor.comment-style.php
+++ b/lib/editor.comment-style.php
@@ -162,6 +162,8 @@ function wm_comment_style_fields($map) {
}
$out .= '';
+ $out .= '
' . html_escape(__('Link Comments', 'weathermap')) . ' links['DEFAULT']->commentcurve) ? ' checked' : '') . '> ' . html_escape(__('Follow link curve', 'weathermap')) . ' ' . html_escape(__('Uses a TrueType font. Tight bends or text longer than the link use straight text.', 'weathermap')) . ' ';
+
return $out;
}
/**
@@ -270,7 +272,10 @@ function wm_comment_update_default($map, $field, $new) {
*
* @return void
*/
-function wm_comment_apply_style($map, $font, $colour) {
+function wm_comment_apply_style($map, $font, $colour, $curve = null) {
+ if ($curve === '0' || $curve === '1') {
+ wm_comment_update_default($map, 'commentcurve', (int)$curve);
+ }
wm_comment_update_default($map, 'commentfont', wm_comment_resolve_font($map, $font));
wm_comment_update_default($map, 'commentfontcolour', wm_comment_resolve_colour($colour));
}
diff --git a/locales/po/cacti.pot b/locales/po/cacti.pot
index 18edb16a..b33cca6b 100644
--- a/locales/po/cacti.pot
+++ b/locales/po/cacti.pot
@@ -1,17 +1,17 @@
# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR The Cacti Group
-# This file is distributed under the same license as the Cacti package.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR
, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: Cacti 1.7\n"
-"Report-Msgid-Bugs-To: developers@cacti.net\n"
-"POT-Creation-Date: 2026-10-15 10:19+1030\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2026-10-16 23:17+1030\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Cacti Developers \n"
-"Language-Team: Cacti Developers \n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -2684,3 +2684,15 @@ msgstr ""
#: weathermap-cacti-plugin.php
msgid "Paused"
msgstr ""
+
+#: lib/editor.comment-style.php
+msgid "Link Comments"
+msgstr ""
+
+#: lib/editor.comment-style.php
+msgid "Follow link curve"
+msgstr ""
+
+#: lib/editor.comment-style.php
+msgid "Uses a TrueType font. Tight bends or text longer than the link use straight text."
+msgstr ""
diff --git a/tests/Unit/CurvedLinkCommentTest.php b/tests/Unit/CurvedLinkCommentTest.php
new file mode 100644
index 00000000..5088992d
--- /dev/null
+++ b/tests/Unit/CurvedLinkCommentTest.php
@@ -0,0 +1,79 @@
+drawn[] = [$x, $y, $string, $angle];
+ }
+ };
+ self::assertEquals(1, $map->links['DEFAULT']->commentcurve);
+ self::assertStringContainsString('value="1" checked', wm_comment_style_fields($map));
+ wm_comment_apply_style($map, 'bundled:Vera.ttf:13', '0 0 255', '1');
+ $link = new WeatherMapLink();
+ $link->Reset($map);
+ $link->curvepoints = [];
+ for ($i = 0; $i <= 120; $i++) {
+ $t = $i / 120 * M_PI / 2;
+ $link->curvepoints[] = [100 + 220 * sin($t), 320 - 220 * cos($t), 220 * $t];
+ }
+ self::assertTrue($link->DrawCurvedComment(null, '5G (Backup)', 0, 7, 20));
+ self::assertSame('5G (Backup)', implode('', array_column($map->drawn, 2)));
+ $angles = array_column($map->drawn, 3);
+ self::assertGreaterThan(20, max($angles) - min($angles));
+ foreach ($angles as $angle) {
+ self::assertLessThanOrEqual(93, abs($angle));
+ }
+ // Reversing the spine must still produce an upright, complete label.
+ $link->curvepoints = array_reverse($link->curvepoints);
+ $map->drawn = [];
+ self::assertTrue($link->DrawCurvedComment(null, '5G (Backup)', 0, 7, 60));
+ self::assertSame('5G (Backup)', implode('', array_column($map->drawn, 2)));
+ self::assertLessThanOrEqual(93, max(array_map('abs', array_column($map->drawn, 3))));
+ // Unsupported fonts, multiline and short geometry fall back atomically.
+ $map->drawn = [];
+ self::assertFalse($link->DrawCurvedComment(null, "two\nlines", 0, 7, 30));
+ $link->commentfont = 2;
+ self::assertFalse($link->DrawCurvedComment(null, '5G', 0, 7, 30));
+ $link->commentfont = $map->links['DEFAULT']->commentfont;
+ $link->curvepoints = [[0, 0, 0], [0, 0, 0], [1, 0, 1]];
+ self::assertFalse($link->DrawCurvedComment(null, 'too long', 0, 7, 0));
+ self::assertSame([], $map->drawn);
+ $link->curvepoints = [[0, 0, 0], [50, 0, 50], [50, 100, 150]];
+ self::assertFalse($link->DrawCurvedComment(null, '5G (Backup)', 0, 7, 0));
+ self::assertSame([], $map->drawn);
+ wm_comment_apply_style($map, null, null, 'invalid');
+ self::assertSame(1, $map->links['DEFAULT']->commentcurve);
+ $file = sys_get_temp_dir() . '/wm-curve-' . bin2hex(random_bytes(8)) . '.conf';
+ try {
+ $map->WriteConfig($file);
+ self::assertStringNotContainsString('COMMENTCURVE', file_get_contents($file));
+ $loaded = new WeatherMap();
+ $loaded->ReadConfig($file);
+ self::assertEquals(1, $loaded->links['DEFAULT']->commentcurve);
+ self::assertStringContainsString('value="1" checked', wm_comment_style_fields($loaded));
+ wm_comment_apply_style($loaded, null, null, '0');
+ self::assertSame(0, $loaded->links['DEFAULT']->commentcurve);
+ $loaded->WriteConfig($file);
+ self::assertStringContainsString('COMMENTCURVE 0', file_get_contents($file));
+ $disabled = new WeatherMap();
+ $disabled->ReadConfig($file);
+ self::assertEquals(0, $disabled->links['DEFAULT']->commentcurve);
+ self::assertStringNotContainsString('value="1" checked', wm_comment_style_fields($disabled));
+ } finally {
+ @unlink($file);
+ }
+ }
+}
From c5a530cd7e97e22e36e37f617756fa4f53164d63 Mon Sep 17 00:00:00 2001
From: alcatron <27321443+alcatron@users.noreply.github.com>
Date: Fri, 16 Oct 2026 23:28:01 +1030
Subject: [PATCH 02/46] Use 1000M as the inherited link bandwidth default
---
CHANGELOG.md | 2 ++
lib/WeatherMapLink.class.php | 8 ++++----
locales/po/cacti.pot | 14 +++++++-------
tests/Unit/DefaultLinkBandwidthTest.php | 14 ++++++++++++++
4 files changed, 27 insertions(+), 11 deletions(-)
create mode 100644 tests/Unit/DefaultLinkBandwidthTest.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eae61f01..1df424f4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,8 @@ IMPORTANT NOTE: This version only works on CACTI 1.x++!
--- develop ---
+* feature: Default implicit link capacities to 1000M in both directions, while preserving explicitly configured capacities
+
* Add traffic colour presets and optional high-load link pulsing
* Fix Safari rendering of high-traffic link glows using native SVG filters
diff --git a/lib/WeatherMapLink.class.php b/lib/WeatherMapLink.class.php
index 4dcf2796..daef31c5 100644
--- a/lib/WeatherMapLink.class.php
+++ b/lib/WeatherMapLink.class.php
@@ -239,10 +239,10 @@ function __construct() {
// 'outcomment' => '',
'zorder' => 300,
'overlibcaption' => ['', ''],
- 'max_bandwidth_in' => 100000000,
- 'max_bandwidth_out' => 100000000,
- 'max_bandwidth_in_cfg' => '100M',
- 'max_bandwidth_out_cfg' => '100M'
+ 'max_bandwidth_in' => 1000000000,
+ 'max_bandwidth_out' => 1000000000,
+ 'max_bandwidth_in_cfg' => '1000M',
+ 'max_bandwidth_out_cfg' => '1000M'
];
// $this->a_offset = 'C';
diff --git a/locales/po/cacti.pot b/locales/po/cacti.pot
index 18edb16a..a89d7aa5 100644
--- a/locales/po/cacti.pot
+++ b/locales/po/cacti.pot
@@ -1,17 +1,17 @@
# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR The Cacti Group
-# This file is distributed under the same license as the Cacti package.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR , YEAR.
#
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: Cacti 1.7\n"
-"Report-Msgid-Bugs-To: developers@cacti.net\n"
-"POT-Creation-Date: 2026-10-15 10:19+1030\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2026-10-16 23:17+1030\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: Cacti Developers \n"
-"Language-Team: Cacti Developers \n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
diff --git a/tests/Unit/DefaultLinkBandwidthTest.php b/tests/Unit/DefaultLinkBandwidthTest.php
new file mode 100644
index 00000000..21518392
--- /dev/null
+++ b/tests/Unit/DefaultLinkBandwidthTest.php
@@ -0,0 +1,14 @@
+Reset($map);
+ foreach([$map->links['DEFAULT'],$link] as $item){
+ self::assertSame(1000000000,$item->max_bandwidth_in);self::assertSame(1000000000,$item->max_bandwidth_out);
+ self::assertSame('1000M',$item->max_bandwidth_in_cfg);self::assertSame('1000M',$item->max_bandwidth_out_cfg);
+ }
+ }
+}
From 306dabcd9f210f0d3798d1e7616ab16723998365 Mon Sep 17 00:00:00 2001
From: alcatron <27321443+alcatron@users.noreply.github.com>
Date: Fri, 16 Oct 2026 23:28:01 +1030
Subject: [PATCH 03/46] Clarify link workflows and add node display-name
positioning
---
CHANGELOG.md | 2 +
css/editor.css | 160 ++++++
js/editor.js | 462 ++++++++++++++----
lib/WeatherMapNode.class.php | 6 +
lib/editor.actions.php | 9 +-
lib/editor.colours.php | 5 +
lib/editor.comment-style.php | 15 +-
lib/editor.inc.php | 62 ++-
locales/po/cacti.pot | 246 +++++++++-
tests/Browser/EditorContextMenuTest.cjs | 3 +-
tests/Browser/EditorWorkflowTest.cjs | 230 ++++++++-
tests/Browser/LinkSourcePickerTest.cjs | 33 ++
tests/Browser/NodeLabelPositionTest.cjs | 9 +
tests/Browser/PickerPaginationTest.cjs | 48 +-
.../CommentColourLocationRegression.php | 20 +
tests/Support/HoverGraphFilesRegression.php | 17 +
tests/Support/NodeLabelPositionRegression.php | 23 +
tests/Unit/MapStyleDefaultsTest.php | 6 +-
.../Unit/PropertiesWorkflowRegressionTest.php | 29 ++
weathermap-cacti-plugin-editor.php | 42 +-
20 files changed, 1280 insertions(+), 147 deletions(-)
create mode 100644 tests/Browser/LinkSourcePickerTest.cjs
create mode 100644 tests/Browser/NodeLabelPositionTest.cjs
create mode 100644 tests/Support/CommentColourLocationRegression.php
create mode 100644 tests/Support/HoverGraphFilesRegression.php
create mode 100644 tests/Support/NodeLabelPositionRegression.php
create mode 100644 tests/Unit/PropertiesWorkflowRegressionTest.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eae61f01..98ca09d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,8 @@ IMPORTANT NOTE: This version only works on CACTI 1.x++!
--- develop ---
+* feature: Simplify link interface/source/hover workflows, limit the form to two previews, add node display-name positioning, move comment colours to Manage Colours and clarify raw-edit controls
+
* Add traffic colour presets and optional high-load link pulsing
* Fix Safari rendering of high-traffic link glows using native SVG filters
diff --git a/css/editor.css b/css/editor.css
index 15c23ebd..e5b0ebf0 100644
--- a/css/editor.css
+++ b/css/editor.css
@@ -418,3 +418,163 @@ body#mainView .wm-icon-choice[aria-pressed="true"] {
background: var(--background-lowlight, #eee);
border-color: var(--text-color-highlight-a, #2874aa);
}
+
+/* Link properties has one scrolling body and a persistent action row. */
+#dlgLinkProperties {
+ overflow: hidden !important;
+}
+#dlgLinkProperties > .cactiTable {
+ display: flex;
+ flex-direction: column;
+ max-height: calc(85vh - 65px);
+ min-height: 0;
+}
+#dlgLinkProperties .dlgBody {
+ display: block;
+ overflow-y: auto;
+ min-height: 0;
+ padding: 12px;
+}
+#dlgLinkProperties .dlgButtons {
+ display: block;
+ flex: 0 0 auto;
+ border-top: 1px solid var(--border-color, #888);
+}
+#dlgLinkProperties .dlgSubButtons {
+ margin: 10px;
+ white-space: normal;
+}
+#dlgLinkProperties .wm-link-group {
+ margin: 8px 0;
+ border: 1px solid var(--border-color, #888);
+ border-radius: 3px;
+}
+#dlgLinkProperties .wm-link-group > summary {
+ display: list-item;
+ list-style-position: inside;
+ height: auto !important;
+ min-height: 0;
+ line-height: 1.4;
+ margin: 0;
+ cursor: pointer;
+ padding: 8px;
+}
+#dlgLinkProperties .wm-link-group > table {
+ width: 100%;
+ table-layout: fixed;
+}
+#dlgLinkProperties .wm-link-group td:first-child {
+ width: 30%;
+}
+#dlgLinkProperties textarea {
+ box-sizing: border-box;
+ width: 100%;
+ min-width: 0;
+}
+#dlgLinkProperties .wm-link-group-hint,
+#dlgLinkProperties .wm-link-source-actions {
+ margin: 10px;
+}
+#dlgLinkProperties .dlgHelp {
+ display: block;
+ height: auto;
+ line-height: 1.4;
+ border: 0;
+}
+#dlgLinkProperties #wm-link-purpose {
+ color: inherit !important;
+ line-height: 1.4;
+}
+
+#dlgLinkProperties .wm-link-field-hint {
+ display: block;
+ margin-top: 3px;
+ font-size: 0.9em;
+ line-height: 1.3;
+ color: inherit;
+}
+#dlgLinkProperties #wm-link-help-button {
+ min-width: 2em;
+ font-weight: bold;
+}
+#dlgLinkProperties #wm-link-full-help {
+ margin: 10px 0 0;
+ padding: 8px;
+ border-top: 1px solid var(--border-color, #888);
+}
+#dlgLinkProperties #wm-link-rrd {
+ display: block;
+ overflow-wrap: anywhere;
+ color: inherit !important;
+}
+#dlgLinkProperties #wm-link-graph { display: block; margin: 8px 10px; }
+
+#dlgLinkProperties .wm-link-source-actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 6px;
+}
+#dlgLinkProperties #wm-link-source-picker {
+ flex: 1 1 200px;
+ min-width: 0;
+ max-width: 100%;
+ padding: 6px;
+ font: inherit;
+ box-sizing: border-box;
+}
+
+#dlgLinkProperties .wm-link-extra-item { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
+#dlgLinkProperties .wm-link-extra-details { flex: 1 1 180px; min-width: 0; overflow-wrap: anywhere; }
+#dlgLinkProperties .wm-link-extra-name { font: inherit; }
+#dlgLinkProperties .wm-link-extra-rrds { display: block; margin-top: 3px; font-size: .85em; }
+#dlgLinkProperties #wm-link-hover-status { margin: 6px 0; }
+#dlgLinkProperties #wm-link-hover-status:empty { display: none; }
+#dlgLinkProperties #wm-link-hovers .wm-link-group-hint { font-size: .9em; line-height: 1.4; }
+
+#dlgLinkProperties .wm-link-source-actions { margin: 0; }
+#dlgLinkProperties #wm-link-source-status:empty { display: none; }
+
+#dlgLinkProperties .wm-link-section-buttons { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
+#dlgLinkProperties .wm-link-group[hidden] { display: none !important; }
+
+#dlgLinkProperties .wm-link-source-item { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
+#dlgLinkProperties .wm-link-source-item code { flex: 1 1 200px; min-width: 0; overflow-wrap: anywhere; }
+
+#dlgLinkProperties #wm-link-source-status { margin: 6px 0; padding: 6px 8px; border-left: 3px solid currentColor; font-weight: bold; }
+
+/* Keep node and link action controls the same size while retaining theme colours. */
+#dlgNodeProperties a.ui-button,
+#dlgNodeProperties button.ui-button:not(.wm-icon-choice),
+#dlgNodeProperties input[type="button"].ui-button,
+#dlgLinkProperties a.ui-button,
+#dlgLinkProperties button.ui-button,
+#dlgLinkProperties input[type="button"].ui-button {
+ font-family: inherit;
+ font-size: 11px;
+ line-height: 1.2;
+ padding: .4em 1em;
+ box-sizing: border-box;
+}
+#dlgLinkProperties .wm-link-section-buttons { align-items: center; }
+#dlgLinkProperties .wm-link-section-buttons .ui-button { margin: 0; }
+
+/* Keep the graph chooser and its actions in a predictable two-line layout. */
+#dlgLinkProperties #wm-link-hovers td { vertical-align: top; padding-top: 6px; padding-bottom: 6px; }
+#dlgLinkProperties #wm-link-hovers td:first-child { padding-top: 12px; }
+#dlgLinkProperties .wm-link-hover-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
+#dlgLinkProperties .wm-link-hover-actions .ui-button { margin: 0; }
+#dlgLinkProperties #wm-link-extra-list .wm-link-extra-item { margin: 0; align-items: flex-start; }
+#dlgLinkProperties #wm-link-extra-list .wm-link-extra-details { padding-top: 6px; }
+
+#dlgNodeProperties .dlgSubButtons,
+#dlgLinkProperties .dlgSubButtons {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ gap: 4px;
+ margin: 10px;
+}
+#dlgNodeProperties .dlgSubButtons .ui-button,
+#dlgLinkProperties .dlgSubButtons .ui-button { margin-left: 0; }
diff --git a/js/editor.js b/js/editor.js
index 8a49f886..4bd105c9 100644
--- a/js/editor.js
+++ b/js/editor.js
@@ -174,53 +174,28 @@ function graphPicker() {
dialogForm += "";
dialogForm += " ";
dialogForm += " ";
- dialogForm += " ";
+ if (id !== 'link_target_picker') dialogForm += " ";
$(this).after(dialogForm);
$(this).hide();
+ $('#' + id + '_add').val($(this).attr('data-add-label') || 'Add');
+ $('#' + id + '_rep').val($(this).attr('data-replace-label') || 'Replace');
$('#' + id + '_add').off('click').on('click', function() {
var hover = 'graph_image.php?local_graph_id=';;
var infourl = infoUrlTarget;
if (id == 'link_target_picker') {
- var target = $('#' + id).val();
- var existing = $('#link_target').val();
-
- $('#link_target').val(existing + (existing != '' ? ' ':'') + target);
-
- // Add the graph hovers if possible
- var ehover = $('#link_hover').val();
- var einfo = $('#link_infourl').val();
-
- if (local_graph_id > 0) {
- if (ehover == '') {
- $('#link_hover').val(hover + local_graph_id);
- }
-
- if (einfo == '' || infoUrlStyle == 1) {
- $('#link_infourl').val(infourl + local_graph_id);
- }
- }
+ wmApplyLinkTrafficSource();
} else {
var hover = 'graph_image.php?local_graph_id=';;
var infourl = infoUrlTarget;
if (id == 'link_picker') {
- var target = $('#' + id).val();
- var ehover = $('#link_hover').val();
- var einfo = $('#link_infourl').val();
-
- if (einfo == '') {
- einfo = infourl;
- }
-
- $('#link_hover').val(ehover + (ehover != '' ? ' ':'') + hover + target);
- if (infoUrlStyle == 0) {
- $('#link_infourl').val(einfo + (einfo != '' ? ',':'') + target);
- } else {
- $('#link_infourl').val(infourl + target);
- }
+ var target = String($('#' + id).val() || '');
+ if (!/^[1-9][0-9]*$/.test(target)) return;
+ var graphUrl = new URL(hover + target, new URL('../../', window.location.href));
+ wmSetLinkExtraHover(wmLinkCompactHoverUrl(graphUrl.pathname + graphUrl.search), false);
} else if (id == 'node_picker') {
var target = $('#' + id).val();
var ehover = $('#node_hover').val();
@@ -243,13 +218,13 @@ function graphPicker() {
$('#' + id + '_rep').off('click').on('click', function() {
if (id == 'link_picker') {
- $('#link_hover').val('graph_image.php?local_graph_id=' + $('#' + id).val());
- $('#link_infourl').val(infoUrlTarget + $('#' + id).val());
+ var target = String($('#' + id).val() || '');
+ if (!/^[1-9][0-9]*$/.test(target)) return;
+ var graphUrl = new URL('graph_image.php?local_graph_id=' + target, new URL('../../', window.location.href));
+ wmSetLinkExtraHover(wmLinkCompactHoverUrl(graphUrl.pathname + graphUrl.search), true);
} else if (id == 'node_picker') {
$('#node_hover').val('graph_image.php?local_graph_id=' + $('#' + id).val());
$('#node_infourl').val(infoUrlTarget + $('#' + id).val());
- } else if (id == 'link_target_picker') {
- $('#link_target').val($('#' + id).val());
}
});
@@ -274,7 +249,7 @@ function graphPicker() {
'&action=' + action +
'&term=' + encodeURIComponent(request.term) +
'&target=' + id +
- '&graph_template_id='+template + '&paged=1&offset=' + pickerOffset;
+ '&graph_template_id='+template + '&paged=1&include_rrds=' + (id === 'link_picker' ? '1' : '0') + '&offset=' + pickerOffset;
$.getJSON(url, function(data) {
var items = data.items;
@@ -299,6 +274,7 @@ function graphPicker() {
return false;
}
$('#' + id + '_input').val(ui.item.label);
+ if (id === 'link_picker') $(this).data('wm-graph-choice', ui.item);
if (ui.item.id) {
$('#' + id).val(ui.item.id);
@@ -975,6 +951,13 @@ function form_submit(event) {
var noteAction = $('#action').val();
var noteTarget = noteAction === 'set_note_properties' ? $('#note_name').val() : noteAction === 'move_node' ? $('#node_name').val() : $('#param').val();
var savingNote = noteAction === 'set_note_properties' || (['move_node', 'delete_node'].indexOf(noteAction) !== -1 && typeof WMNotes !== 'undefined' && WMNotes[noteTarget]);
+ if ($('#action').val() === 'set_link_properties') {
+ wmSyncLinkHoverParts();
+ if (wmLinkHoverEntries($('#wm-link-extra-hover').val()).length > 1 || wmLinkHoverEntries($('#link_hover').val()).length > 2) {
+ window.alert(wmEditorText.hoverLimit);
+ return;
+ }
+ }
var data = $('input, select, textarea').serialize();
if (typeof WMConfigRevision !== 'undefined') data += '&config_revision=' + encodeURIComponent(WMConfigRevision);
@@ -1106,6 +1089,16 @@ function show_itemtext(itemtype,name) {
});
}
+// Use existing relative LABELOFFSET syntax so positions scale with the icon.
+function wmPrimeNodeLabelPosition(node) {
+ var offset = String(node.labeloffset || '').toUpperCase();
+ var position = (!offset || offset === 'C') && !Number(node.labeloffsetx || 0) && !Number(node.labeloffsety || 0) ? 'C' :
+ (['N60', 'S60'].includes(offset) && !Number(node.labeloffsetx || 0) && !Number(node.labeloffsety || 0) ? offset : 'keep');
+ $('#node_labelposition option[value="keep"]').prop('hidden', position !== 'keep');
+ $('#node_labelposition').val(position).trigger('change.select2');
+ if ($('#node_labelposition').data('ui-selectmenu')) $('#node_labelposition').selectmenu('refresh');
+}
+
function prime_node_form(name) {
var mynode = Nodes[name];
@@ -1119,6 +1112,7 @@ function prime_node_form(name) {
$('#node_name').val(mynode.name);
$('#node_new_name').val(mynode.name);
$('#node_label').val(mynode.label);
+ wmPrimeNodeLabelPosition(mynode);
$('#node_infourl').val(wmDecodeEditorUrl(mynode.infourl));
$('#node_hover').val(wmDecodeEditorUrl(mynode.overliburl));
@@ -1426,28 +1420,318 @@ function attach_help_events() {
}
-// Compact link workflow; existing fields remain available under Advanced.
+// Compact link workflow; custom configuration fields remain submitted unchanged.
+function wmLinkHoverEntries(value) {
+ return String(value || '').trim().split(/\s+/).filter(Boolean);
+}
+function wmLinkCompactHoverUrl(value) {
+ if (!wmLocalGraphIds(value, ['graph_image.php']).length) return value;
+ var root = new URL('../../', window.location.href);
+ var url = new URL(value, /^graph_image\.php/.test(value) ? root : window.location.href);
+ [['rra_id', '0'], ['graph_nolegend', 'true'], ['graph_height', '100'], ['graph_width', '300']].forEach(function(pair) {
+ if (!url.searchParams.has(pair[0])) url.searchParams.set(pair[0], pair[1]);
+ });
+ return url.pathname + url.search + url.hash;
+}
+function wmSyncLinkHoverParts() {
+ if (!$('#wm-link-extra-hover').length) return;
+ var main = $('#wm-link-primary-hover').val();
+ var entries = wmLinkHoverEntries(main).concat(wmLinkHoverEntries($('#wm-link-extra-hover').val()));
+ $('#link_hover').val(Array.from(new Set(entries)).join(' '));
+ wmRenderLinkExtraGraphs();
+}
+function wmRenderLinkExtraGraphs() {
+ var list = $('#wm-link-extra-list');
+ if (!list.length) return;
+ list.empty();
+ var count = wmLinkHoverEntries($('#wm-link-extra-hover').val()).length;
+ $('#link_picker_add').prop('disabled', count >= 1);
+ $('#wm-link-hover-status').text(count >= 1 ? wmEditorText.hoverLimit : '');
+ var table = $('').appendTo(list);
+ wmLinkHoverEntries($('#wm-link-extra-hover').val()).forEach(function(url) {
+ var row = $(' ').appendTo(table);
+ $(' ').text(wmEditorText.additionalGraph).appendTo(row);
+ var item = $('').appendTo($(' ').appendTo(row));
+ var details = $('').appendTo(item);
+ var ids = wmLocalGraphIds(url, ['graph_image.php']);
+ var label = $('').appendTo(details);
+ var files = $('').appendTo(details);
+ item.attr('data-hover-url', url);
+ if (ids.length) {
+ var selected = String($('#link_picker').val()) === ids[0] ? $('#link_picker_input').val() : '';
+ label.text(selected || wmEditorText.currentGraph.replace('%s', ids[0]));
+ var choice = $('#link_picker_input').data('wm-graph-choice');
+ if (choice && String(choice.id) === ids[0]) files.text((choice.rrds || []).join(', ') || wmEditorText.noGraphRrd);
+ } else label.text(wmEditorText.customHoverGraph);
+ $(' ').text(wmEditorText.removeHoverGraph)
+ .appendTo(item).on('click', function() {
+ $('#wm-link-extra-hover').val(wmLinkHoverEntries($('#wm-link-extra-hover').val()).filter(function(entry) { return entry !== url; }).join('\n'));
+ wmSyncLinkHoverParts();
+ });
+ });
+}
+function wmInitLinkHoverParts() {
+ if (!$('#wm-link-extra-hover').length) return;
+ var entries = wmLinkHoverEntries($('#link_hover').val()).map(wmLinkCompactHoverUrl);
+ $('#wm-link-primary-hover').val(entries.shift() || '');
+ $('#wm-link-extra-hover').val(entries.join('\n')).off('.wmHovers').on('input.wmHovers change.wmHovers', wmSyncLinkHoverParts);
+ wmSyncLinkHoverParts();
+}
+function wmSetLinkPrimaryHover(value) {
+ if (!$('#wm-link-extra-hover').length) {
+ $('#link_hover').val(value);
+ return;
+ }
+ $('#wm-link-primary-hover').val(value);
+ var ids = wmLocalGraphIds(value, ['graph_image.php']);
+ var extras = wmLinkHoverEntries($('#wm-link-extra-hover').val()).filter(function(entry) {
+ return !ids.some(function(id) { return wmLocalGraphIds(entry, ['graph_image.php']).includes(id); });
+ });
+ $('#wm-link-extra-hover').val(extras.join('\n'));
+ wmSyncLinkHoverParts();
+}
+function wmPromoteLinkTrafficHover(graphId) {
+ if (!$('#wm-link-extra-hover').length || !/^[1-9][0-9]*$/.test(String(graphId))) return;
+ var main = $('#wm-link-primary-hover').val();
+ var extras = wmLinkHoverEntries($('#wm-link-extra-hover').val());
+ if (!main && !extras.length) return;
+ if (wmLocalGraphIds(main, ['graph_image.php']).includes(String(graphId))) return;
+ var traffic = extras.find(function(entry) {
+ return wmLocalGraphIds(entry, ['graph_image.php']).includes(String(graphId));
+ });
+ if (main) extras.unshift(main);
+ $('#wm-link-extra-hover').val(extras.join('\n'));
+ wmSetLinkPrimaryHover(traffic || wmLinkCompactHoverUrl(new URL('../../', window.location.href).pathname + 'graph_image.php?local_graph_id=' + graphId));
+}
+function wmSetLinkExtraHover(value, replace) {
+ var field = $('#wm-link-extra-hover');
+ if (!field.length) field = $('#link_hover');
+ var ids = wmLocalGraphIds(value, ['graph_image.php']);
+ var primary = wmLocalGraphIds($('#wm-link-primary-hover').val() || '', ['graph_image.php']);
+ if (ids.some(function(id) { return primary.includes(id); })) return;
+ var entries = replace ? [] : wmLinkHoverEntries(field.val());
+ if (!entries.some(function(entry) {
+ return entry === value || ids.some(function(id) { return wmLocalGraphIds(entry, ['graph_image.php']).includes(id); });
+ })) {
+ if (entries.length >= 1) {
+ $('#wm-link-hover-status').text(wmEditorText.hoverLimit);
+ return;
+ }
+ entries.push(value);
+ }
+ field.val(entries.join('\n'));
+ wmSyncLinkHoverParts();
+}
+
+function wmRenderLinkTrafficSources() {
+ var list = $('#wm-link-measured-sources');
+ if (!list.length) return;
+ var entries = wmLinkHoverEntries($('#link_target').val());
+ list.empty();
+ if (!$('#wm-link-source-count').length) $(' ').insertAfter('#wm-link-current');
+ $('#wm-link-source-count').text(entries.length > 1 ? wmEditorText.combinedSources.replace('%s', String(entries.length)) : '');
+ $('#wm-link-rrd').text((entries[0] || '').split('/').pop());
+ if (!entries.length) $(' ').text(wmEditorText.noMeasuredSources).appendTo(list);
+ entries.forEach(function(source, index) {
+ var row = $('
').appendTo(list);
+ $('').text(source).appendTo(row);
+ if (index === 0) {
+ $(' ').text(wmEditorText.primarySource).appendTo(row);
+ return;
+ }
+ $(' ')
+ .text(wmEditorText.removeSource).attr('aria-label', wmEditorText.removeSource + ': ' + source)
+ .appendTo(row).on('click', function() {
+ var current = wmLinkHoverEntries($('#link_target').val());
+ if (current.join(' ') !== entries.join(' ')) { wmRenderLinkTrafficSources(); return; }
+ current.splice(index, 1);
+ $('#link_target').val(current.join(' '));
+ wmRenderLinkTrafficSources();
+ $('#wm-link-source-status').text(wmEditorText.sourceRemoved);
+ var next = list.find('button').eq(Math.min(index - 1, current.length - 2));
+ if (next.length) next.trigger('focus');
+ else $('#link_target').trigger('focus');
+ });
+ });
+}
+
+function wmApplyLinkTrafficSource() {
+ var entries = wmLinkHoverEntries($('#link_target').val());
+ if (!entries.length) {
+ $('#wm-link-source-status').text(wmEditorText.choosePrimaryFirst);
+ return;
+ }
+ var item = $('#wm-link-source-picker').data('wm-choice');
+ if (!item || !item.id || !(item.local_graph_id > 0)) {
+ $('#wm-link-source-status').text(wmEditorText.chooseSourceFirst);
+ return;
+ }
+ var duplicate = entries.includes(String(item.id));
+ if (!duplicate) entries.push(String(item.id));
+ $('#link_target').val(entries.join(' '));
+ wmRenderLinkTrafficSources();
+ $('#wm-link-source-status').text(duplicate ? wmEditorText.sourceAlreadyAdded : wmEditorText.sourceAdded);
+}
+function wmInitLinkSourcePicker(container) {
+ var picker = $(' ')
+ .attr({'aria-label': wmEditorText.chooseSource, placeholder: wmEditorText.searchInterface}).prependTo(container);
+ var offset = 0, term = '', generation = 0;
+ var buttons = $('#link_target_picker_add');
+ picker.autocomplete({
+ minLength: 0, autoFocus: false,
+ source: function(request, response) {
+ if (request.term !== term) offset = 0;
+ term = request.term;
+ var ticket = ++generation, link = $('#link_name').val(), map = $('#mapname').val();
+ $.getJSON(window.location.pathname, {action:'datasources', target:'link_target_picker', mapname:map, graph_template_id:-1, paged:1, offset:offset, term:term})
+ .done(function(data) {
+ if (ticket !== generation || $('#link_name').val() !== link || $('#mapname').val() !== map) return response([]);
+ var items = data.items.slice();
+ if (data.offset > 0) items.unshift({label:data.previous_label, value:term, wmOffset:Math.max(0,data.offset-100)});
+ if (data.next_offset != null) items.push({label:data.next_label, value:term, wmOffset:data.next_offset});
+ response(items);
+ }).fail(function() { response([]); });
+ },
+ focus: function(event, ui) { if (ui.item.wmOffset !== undefined) return false; },
+ select: function(event, ui) {
+ if (ui.item.wmOffset !== undefined) {
+ offset = ui.item.wmOffset;
+ setTimeout(function() { picker.autocomplete('search', term); }, 0);
+ return false;
+ }
+ var valid = !!(ui.item.id && ui.item.local_graph_id > 0);
+ picker.val(valid ? ui.item.label : '').data('wm-choice', valid ? ui.item : null);
+ buttons.prop('disabled', !valid);
+ $('#wm-link-source-status').text(valid ? wmEditorText.sourceReady : wmEditorText.chooseSourceFirst);
+ return false;
+ }
+ });
+ picker.on('input.wmSource', function() {
+ picker.removeData('wm-choice'); buttons.prop('disabled', true); $('#wm-link-source-status').text(wmEditorText.chooseSourceFirst);
+ }).on('focus.wmSource', function() { picker.autocomplete('search', picker.data('wm-choice') ? '' : picker.val()); });
+}
+
+function wmRestoreLinkGraphChoice() {
+ var picker = $('#link_picker_input');
+ if (!picker.length) return;
+ var extras = String($('#wm-link-extra-hover').val() || '');
+ var link = $('#link_name').val();
+ $('#link_picker').val('');
+ picker.val('').attr('placeholder', wmEditorText.searchGraph);
+ var entries = wmLinkHoverEntries(extras);
+ var ids = entries.length === 1 ? wmLocalGraphIds(entries[0], ['graph_image.php']) : [];
+ if (ids.length !== 1) return;
+ wmGetGraphSummaries(ids).done(function(items) {
+ if ($('#link_name').val() !== link || String($('#wm-link-extra-hover').val() || '') !== extras || picker.val()) return;
+ var item = items.find(function(item) { return String(item.id) === ids[0]; });
+ if (!item) return;
+ $('#link_picker').val(String(item.id));
+ picker.val(item.label).data('wm-graph-choice', item);
+ var row = $('#wm-link-extra-list .wm-link-extra-item').filter(function() { return $(this).attr('data-hover-url') === entries[0]; });
+ row.find('.wm-link-extra-name').text(item.label);
+ row.find('.wm-link-extra-rrds').text((item.rrds || []).join(', ') || wmEditorText.noGraphRrd);
+ });
+}
+
+// Keep advanced link tasks separate without duplicating submitted fields or pickers.
+function wmBuildLinkSections() {
+ var dlg = $('#dlgLinkProperties');
+ if ($('#wm-link-sections').length) return;
+ var body = dlg.find('.dlgBody');
+ if (!body.length) body = dlg;
+ var sections = $('
').appendTo(body);
+ var controls = $('
').appendTo(sections);
+ function group(id, title, fields, hint) {
+ var panel = $('
')
+ .attr({id:id, 'aria-labelledby':id+'-button'}).appendTo(sections);
+ $(' ')
+ .attr({id:id+'-button', 'aria-controls':id}).text(title).appendTo(controls)
+ .on('click', function() {
+ var open = $(this).attr('aria-expanded') !== 'true';
+ controls.find('.wm-link-section-toggle').attr('aria-expanded', 'false').removeClass('ui-state-active');
+ sections.find('.wm-link-group').prop('hidden', true);
+ $(this).attr('aria-expanded', String(open)).toggleClass('ui-state-active', open);
+ panel.prop('hidden', !open);
+ if (dlg.dialog('instance')) dlg.dialog('option', 'position', {my:'center', at:'center', of:window});
+ });
+ if (hint) $('
').text(hint).appendTo(panel);
+ var table = $('').appendTo(panel);
+ dlg.find(fields).closest('tr').appendTo(table);
+ return panel;
+ }
+ var hovers = group('wm-link-hovers', wmEditorText.additionalGraphs, '#link_template,#link_picker');
+ dlg.find('.wm-link-section p').addClass('wm-link-group-hint').prependTo(hovers);
+ dlg.find('.wm-link-section').remove();
+ $('
').append($('#link_picker_add,#link_picker_rep')).appendTo($('#link_picker').closest('td'));
+ $('
').appendTo($('#link_picker').closest('td'));
+ $('').appendTo(hovers);
+ wmRenderLinkExtraGraphs();
+ $('#link_target_picker_rep').remove();
+ var advanced = group('wm-link-advanced', wmEditorText.advanced,
+ '#link_target,#link_infourl,#wm-link-primary-hover,#wm-link-extra-hover', wmEditorText.linkAdvancedHint);
+ var sourceRow = $(' ').insertBefore($('#link_target').closest('tr'));
+ $(' ').text(wmEditorText.chooseSource).appendTo(sourceRow);
+ var sourceActions = $('
').appendTo($(' ').appendTo(sourceRow))
+ .append($('#link_target_picker_add'));
+ wmInitLinkSourcePicker(sourceActions);
+ $(' ').text(wmEditorText.trafficSourcesHint).appendTo(sourceActions.parent());
+ $('
').insertBefore('#link_target');
+ $('
').attr('aria-label', wmEditorText.trafficSources).insertBefore('#link_target');
+ $('#link_target').off('.wmExpert').on('input.wmExpert', function() {
+ ++wmInterfaceLookupGeneration;
+ $('#wm-link-current').text(wmEditorText.customInterface);
+ $('#wm-link-graph').text(wmEditorText.customConfig);
+ $('#link_target_picker').val('').removeData('wm-choice');
+ $('#link_target_picker_input').val('');
+ $('#wm-link-use').prop('disabled', true);
+ wmRenderLinkTrafficSources();
+ $('#wm-link-source-status').text(wmEditorText.customSourcesChanged);
+ });
+ $('#link_infourl,#wm-link-primary-hover,#wm-link-extra-hover').off('.wmExpert').on('input.wmExpert change.wmExpert', function() {
+ ++wmInterfaceLookupGeneration;
+ wmSyncLinkHoverParts();
+ });
+ dlg.find('#viastyle,#link_width,#link_commentin,#link_commentout').closest('tr')
+ .insertAfter($('#link_target_picker').closest('tr'));
+ $('#link_edit').appendTo(controls);
+ var help = dlg.find('.dlgHelp').attr('id', 'wm-link-full-help').insertAfter(sections).hide();
+ $('? ')
+ .attr({'aria-label': wmEditorText.linkHelp, title: wmEditorText.linkHelp}).appendTo(controls)
+ .on('click', function() {
+ var open = $(this).attr('aria-expanded') !== 'true';
+ $(this).attr('aria-expanded', String(open));
+ help.toggle(open);
+ if (dlg.dialog('instance')) dlg.dialog('option', 'position', {my:'center', at:'center', of:window});
+ });
+ function hint(id, field, text) {
+ $(' ').attr('id', id).text(text).appendTo(dlg.find(field).closest('td'));
+ var input = dlg.find(field), described = String(input.attr('aria-describedby') || '').split(/\s+/).filter(Boolean);
+ if (!described.includes(id)) described.push(id);
+ input.attr('aria-describedby', described.join(' '));
+ }
+ hint('wm-link-bandwidth-hint', '#link_bandwidth_in', wmEditorText.bandwidthHint);
+ hint('wm-link-comments-hint', '#link_commentout', wmEditorText.commentsHint);
+ hint('wm-link-destination-hint', '#link_infourl', wmEditorText.clickDestinationHint);
+ hint('wm-link-extra-url-hint', '#wm-link-extra-hover', wmEditorText.extraUrlsHint);
+}
+
function compactLinkEditorBase() {
+ wmInitLinkHoverParts();
var dlg = $('#dlgLinkProperties'),
- table = $('#link_target').closest('table');
- var fields = '#viastyle,#link_target,#link_infourl,#link_hover,#link_template,#link_picker';
- var rows = dlg.find(fields).closest('tr').addClass('wm-link-advanced');
+ table = $('#link_bandwidth_in').closest('table');
+ wmBuildLinkSections();
+ wmRenderLinkTrafficSources();
+ wmRestoreLinkGraphChoice();
+ $('#wm-link-source-status').text(wmEditorText.chooseSourceFirst);
+ $('#wm-link-source-picker').val('').removeData('wm-choice');
+ $('#link_target_picker_add').prop('disabled', true);
+ $('#wm-link-full-help').hide();
+ $('#wm-link-help-button').attr('aria-expanded', 'false');
+ $('#wm-link-primary-hover').prop('readonly', false).next('small').text(wmEditorText.trafficHoverHint);
if (!$('#wm-link-summary').length) {
$(
'
'
).prependTo(table).find('td:first').text(wmEditorText.currentInterface);
- $(' ')
- .text(wmEditorText.advanced)
- .insertBefore(dlg.find('.dlgButtons'))
- .on('click', function () {
- var expanded = $(this).attr('aria-expanded') !== 'true';
- $(this)
- .attr('aria-expanded', String(expanded))
- .text(expanded ? wmEditorText.hideAdvanced : wmEditorText.advanced);
- rows.toggle(expanded);
- dlg.find('.dlgHelp,#link_edit').toggle(expanded);
- $('#link_target_picker_add,#link_target_picker_rep').toggle(expanded);
- });
$(
' '
)
@@ -1457,10 +1741,12 @@ function compactLinkEditorBase() {
var item = $('#link_target_picker').data('wm-choice');
if (!item || !item.id || !(item.local_graph_id > 0)) return;
++wmInterfaceLookupGeneration;
- $('#link_target').val(item.id);
+ var extras = wmLinkHoverEntries($('#link_target').val()).slice(1).filter(function(source) { return source !== String(item.id); });
+ $('#link_target').val([String(item.id)].concat(extras).join(' '));
+ wmRenderLinkTrafficSources();
var destination = new URL(infoUrlTarget + item.local_graph_id, new URL('../../', window.location.href));
$('#link_infourl').val(destination.pathname + destination.search + destination.hash);
- $('#link_hover').val(
+ wmSetLinkPrimaryHover(
new URL('../../', window.location.href).pathname +
'graph_image.php?local_graph_id=' +
item.local_graph_id +
@@ -1473,15 +1759,17 @@ function compactLinkEditorBase() {
$(this).prop('disabled', true);
});
}
- rows.hide();
- dlg.find('.dlgHelp,#link_edit').hide();
- $('#wm-link-toggle').attr('aria-expanded', 'false').text(wmEditorText.advanced);
- $('#link_target_picker_add,#link_target_picker_rep').hide();
+ $('#wm-link-graph').prependTo('#wm-link-advanced');
+ wmRenderLinkTrafficSources();
+ $('#wm-link-sections').show().find('.wm-link-group').prop('hidden', true);
+ $('#wm-link-sections .wm-link-section-toggle').attr('aria-expanded', 'false').removeClass('ui-state-active');
+ $('#link_edit').show();
+ $('#link_target_picker_add').show();
$('#link_target_picker').closest('tr').find('td:first').text(wmEditorText.changeInterface);
$('#link_target_picker_input').val('').attr('placeholder', wmEditorText.searchInterface);
$('#link_target_picker').val('').removeData('wm-choice');
local_graph_id = 0;
- $('#link_target_picker_add,#link_target_picker_rep,#wm-link-use').prop('disabled', true);
+ $('#wm-link-use').prop('disabled', true);
var picker = $('#link_target_picker_input');
if (!picker.data('wm-compact-hook')) {
var original = picker.autocomplete('option', 'select');
@@ -1491,12 +1779,12 @@ function compactLinkEditorBase() {
var valid = !!(ui.item.id && ui.item.local_graph_id > 0);
$('#link_target_picker').val(valid ? ui.item.id : '').data('wm-choice', valid ? ui.item : null);
local_graph_id = valid ? ui.item.local_graph_id : 0;
- $('#link_target_picker_add,#link_target_picker_rep,#wm-link-use').prop('disabled', !valid);
+ $('#wm-link-use').prop('disabled', !valid);
})
.on('input.wmCompact', function () {
$('#link_target_picker').val('').removeData('wm-choice');
local_graph_id = 0;
- $('#link_target_picker_add,#link_target_picker_rep,#wm-link-use').prop('disabled', true);
+ $('#wm-link-use').prop('disabled', true);
});
picker.data('wm-compact-hook', true);
}
@@ -1563,15 +1851,11 @@ function compactLinkEditor() {
var generation = ++wmInterfaceLookupGeneration;
compactLinkEditorBase();
var dlg = $('#dlgLinkProperties');
- dlg.find('#link_commentin,#link_commentout').closest('tr').removeClass('wm-link-advanced').show();
if (!$('#wm-link-rrd').length)
$(' ').insertAfter(
'#wm-link-current'
);
- if (!$('#wm-link-purpose').length)
- $(
- '
'
- ).text(wmEditorText.linkPurpose).insertAfter('#wm-link-toggle');
+
var buttons = dlg.find('.dlgSubButtons');
$('#tb_link_submit').prependTo(buttons).css({ 'font-weight': 'bold' });
$('#tb_link_cancel').insertAfter('#tb_link_submit');
@@ -1610,8 +1894,9 @@ function compactLinkEditor() {
if (describedBy.indexOf('wm-link-apply-hint') === -1) describedBy.push('wm-link-apply-hint');
picker.attr('aria-describedby', describedBy.join(' '));
var target = String($('#link_target').val() || '').trim();
- var file = target.split('/').pop();
- $('#wm-link-rrd').text(target);
+ var primary = wmLinkHoverEntries(target)[0] || '';
+ var file = primary.split('/').pop();
+ $('#wm-link-rrd').text(primary.split('/').pop());
$('#wm-link-current').text(target ? wmEditorText.lookingUp : wmEditorText.noInterface);
var graphIds = wmLocalGraphIds(String($('#link_infourl').val() || ''), ['graph.php', 'graph_view.php']);
@@ -1626,9 +1911,9 @@ function compactLinkEditor() {
wmGetInterfaceCandidates(graphIds)
.done(function (data) {
var items = data.items;
- if (generation !== wmInterfaceLookupGeneration || String($('#link_target').val() || '').trim() !== target) return;
+ if (generation !== wmInterfaceLookupGeneration || (wmLinkHoverEntries($('#link_target').val())[0] || '') !== primary) return;
var matches = items.filter(function (item) {
- return item.id === target;
+ return item.id === primary;
});
if (!matches.length) {
matches = items.filter(function(item) { return item.id.split('/').pop() === file; });
@@ -1641,17 +1926,20 @@ function compactLinkEditor() {
? wmEditorText.customInterface
: wmEditorText.noInterface
);
- if (matches.length && !picker.val() && !$('#link_target_picker').data('wm-choice'))
- picker.val(matches[0].label);
+ if (matches.length) wmPromoteLinkTrafficHover(matches[0].local_graph_id);
+ if (matches.length && !$('#link_target_picker').data('wm-choice')) {
+ if (!picker.val()) picker.val(matches[0].label);
+ $('#link_target_picker').val(matches[0].id).data('wm-choice', matches[0]);
+ }
})
.fail(function () {
- if (generation !== wmInterfaceLookupGeneration || String($('#link_target').val() || '').trim() !== target) return;
+ if (generation !== wmInterfaceLookupGeneration || (wmLinkHoverEntries($('#link_target').val())[0] || '') !== primary) return;
$('#wm-link-current').text(wmEditorText.unavailableInterface);
});
if (!$('#wm-link-use').data('wm-detail-hook')) {
$('#wm-link-use')
.on('click.wmDetails', function () {
- $('#wm-link-rrd').text($('#link_target').val());
+ $('#wm-link-rrd').text((wmLinkHoverEntries($('#link_target').val())[0] || '').split('/').pop());
})
.data('wm-detail-hook', true);
}
@@ -1666,7 +1954,8 @@ function compactNodeEditor() {
$('#node_template').closest('tr').find('td:first').text(wmEditorText.graphType);
$('#node_picker').closest('tr').find('td:first').text(wmEditorText.hoverGraphs);
$('#node_label').closest('tr').prependTo(table);
- $('#node_iconfilename').closest('tr').insertAfter($('#node_label').closest('tr'));
+ $('#node_labelposition').closest('tr').insertAfter($('#node_label').closest('tr'));
+ $('#node_iconfilename').closest('tr').insertAfter($('#node_labelposition').length ? $('#node_labelposition').closest('tr') : $('#node_label').closest('tr'));
if (!$('#wm-node-delete-bar').length)
$('
').insertBefore(table);
$('#node_delete').text(wmEditorText.deleteNode).appendTo('#wm-node-delete-bar').css('color', '#9b2525');
@@ -1788,7 +2077,7 @@ function wmGetGraphSummaries(ids) {
}
$.getJSON(window.location.pathname, {
action: 'graphs', term: '', mapname: $('#mapname').val(),
- target: 'node_picker', graph_template_id: -1, paged: 1,
+ target: 'node_picker', graph_template_id: -1, paged: 1, include_rrds: '1',
graph_ids: unique.slice(offset, offset + 100).join(',')
}).done(function(data) {
labels = labels.concat(data.items);
@@ -1818,16 +2107,11 @@ function wmFriendlyLinkNames() {
.text(wmEditorText.bandwidthOut.replace('%s', function() { return wmNodeDisplayName(link.a); }));
if (!$('#wm-link-internal-id').length)
$('
').insertBefore(
- $('#link_target').closest('table')
+ $('#wm-link-advanced').find('table')
);
$('#wm-link-internal-id')
- .text(wmEditorText.internalLink.replace('%s', function() { return link.name; }))
- .hide();
- $('#wm-link-toggle')
- .off('click.wmNames')
- .on('click.wmNames', function () {
- $('#wm-link-internal-id').toggle($(this).attr('aria-expanded') === 'true');
- });
+ .text(wmEditorText.internalLink.replace('%s', function() { return link.name; }));
+
}
// Map JavaScript HTML-escapes URL values; undo that when populating text fields.
@@ -1995,6 +2279,10 @@ function wmOpenColours() {
wmColourRequest('colour_data', {}, function(data) {
$('#wm-traffic-pulse').prop('checked', data.colours.pulse === true);
$('#wm-outline-colour').empty().append(wmAssetColour(data.colours.outline, false, true, false, wmEditorText.borderColour));
+ var comment = $('