Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e5b13d3
more attempts to fix geosearch sucking in google js
OSPFNeighbour Feb 16, 2026
8f06fc3
more fixed for this google map js inject
OSPFNeighbour Feb 16, 2026
2df3374
remove identity and urls (#315)
OSPFNeighbour Feb 24, 2026
ed82697
UI fixes (#317)
OSPFNeighbour Feb 25, 2026
e37c921
Bump minimatch from 3.1.2 to 3.1.3 (#316)
dependabot[bot] Feb 25, 2026
766d1d8
Bump webpack from 5.98.0 to 5.105.0 (#299)
dependabot[bot] Feb 25, 2026
eb7f120
Lad config restore defaults button (#318)
OSPFNeighbour Feb 25, 2026
1f0fc94
fix - sidebar resize when no localstorage values
OSPFNeighbour Feb 25, 2026
e46954e
Fixes for massive package sizes (#319)
OSPFNeighbour Feb 25, 2026
3e76f1f
if factory default config, load the HQ passed via the URL (#320)
OSPFNeighbour Feb 27, 2026
dcb6fb7
LAD markerclusters (#322)
OSPFNeighbour Feb 27, 2026
a1c745f
Lots of BOM layers via beacon (#321)
OSPFNeighbour Feb 27, 2026
91f38b1
re-do of all bom layers (#324)
OSPFNeighbour Mar 1, 2026
7ccc086
Monday 2nd March Daily WIP (#325)
OSPFNeighbour Mar 2, 2026
099c6e0
add status to re-order incident UI
OSPFNeighbour Mar 3, 2026
3cc1f91
layer toggle for all incidents
OSPFNeighbour Mar 3, 2026
17964a2
map layer searching
OSPFNeighbour Mar 3, 2026
e2131f9
Fix RainViewer frame timestamp undefined error
OSPFNeighbour Mar 3, 2026
d8151e4
Lad performance fixups (#327)
OSPFNeighbour Mar 3, 2026
e41df94
Added some transparency to the storm tracker polygons
OSPFNeighbour Mar 4, 2026
d69021b
ICEMS unack display code (#329)
OSPFNeighbour Mar 4, 2026
361fcdf
tiny css fix
OSPFNeighbour Mar 4, 2026
aa3d3fc
rain radar legend added
OSPFNeighbour Mar 5, 2026
9bac199
too much debug
OSPFNeighbour Mar 5, 2026
0b21762
fixes to icems code to actually pass a function
OSPFNeighbour Mar 5, 2026
d06b528
two computed performance fixes (#331)
OSPFNeighbour Mar 5, 2026
e7b2803
dark mode theme (#332)
OSPFNeighbour Mar 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 132 additions & 89 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"leaflet-minimap": "^3.6.1",
"leaflet-routing-machine": "^3.2.12",
"leaflet-svg-shape-markers": "^1.3.0",
"leaflet.markercluster": "^1.5.3",
"leaflet.measure": "^1.0.0",
"leaflet.polylinemeasure": "^3.0.0",
"leaflet.vectorgrid": "^1.3.0",
Expand Down Expand Up @@ -90,7 +91,7 @@
"style-loader": "^3.3.1",
"typescript": "^4.5.4",
"underscore": "^1.13.1",
"webpack": "^5.98.0",
"webpack": "^5.105.0",
"webpack-cli": "^4.9.1",
"webpack-extension-manifest-plugin": "^0.8.0",
"webpack-merge": "^5.8.0",
Expand Down
25 changes: 0 additions & 25 deletions src/contentscripts/identity.js

This file was deleted.

21 changes: 16 additions & 5 deletions src/pages/tasking/bindings/sortableArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,21 @@ export function installSortableArrayBindings() {
// allow dragging from handle only if provided
const getHandle = () => (handleSel ? (el.querySelector(handleSel) || el) : el);

// mark draggable
el.setAttribute("draggable", "true");
// Track whether the pointer started on the drag handle.
// In dragstart, e.target is always the draggable element (the <li>),
// not the child the user clicked, so we must track it via pointerdown.
let _pointerOnHandle = !handleSel;

if (handleSel) {
el.setAttribute("draggable", "false");
el.addEventListener("pointerdown", (e) => {
const h = getHandle();
_pointerOnHandle = !!(h && (h === e.target || h.contains(e.target)));
el.setAttribute("draggable", String(_pointerOnHandle));
});
} else {
el.setAttribute("draggable", "true");
}

// KSB-safe: item is from bindingContext.$data
const getIndex = () => {
Expand All @@ -28,9 +41,7 @@ export function installSortableArrayBindings() {
};

el.addEventListener("dragstart", (e) => {
const h = getHandle();
// if handle selector is used, only allow drag when starting on/within handle
if (handleSel && e.target && !h.contains(e.target)) {
if (handleSel && !_pointerOnHandle) {
e.preventDefault();
return;
}
Expand Down
6 changes: 5 additions & 1 deletion src/pages/tasking/components/alerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,11 @@ function buildDefaultRules(vm) {
count: unackedNotifications.length,
onClick: (id) => {
const found = jobs.find(j => jobKey(j) === id);
found?.focusAndExpandInList();
if (found) {
found.focusAndExpandInList();
// Also open the timeline modal to show the notifications
found.attachAndFillTimelineModal?.();
}
}
},
{
Expand Down
74 changes: 74 additions & 0 deletions src/pages/tasking/components/job_icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,80 @@ export function styleForJob(job) {
// tweak strokeWidth if you need stronger outlines
}

/**
* Build an SVG outline that matches the marker shape, used as the pulse-ring
* overlay. `w`/`h` are the pixel dimensions of the ring's L.divIcon box.
*/
export function buildPulseRingSvg(shape, w, h) {
const cx = w / 2, cy = h / 2;
const sw = 2;
const r = Math.min(w, h) / 2 - sw;

let outline;
switch (shape) {
case "circle":
outline = `<circle cx="${cx}" cy="${cy}" r="${r}" />`;
break;
case "square": {
outline = `<rect x="${cx - r}" y="${cy - r}" width="${2 * r}" height="${2 * r}" rx="2" ry="2" />`;
break;
}
case "diamond":
outline = `<polygon points="${cx},${cy - r} ${cx + r},${cy} ${cx},${cy + r} ${cx - r},${cy}" />`;
break;
case "triangle": {
const th = r * Math.sqrt(3);
outline = `<polygon points="${cx},${cy - r} ${cx - th / 2},${cy + r / 2} ${cx + th / 2},${cy + r / 2}" />`;
break;
}
case "hex": {
const pts = [];
for (let i = 0; i < 6; i++) {
const a = (Math.PI / 3) * i - Math.PI / 6;
pts.push(`${cx + r * Math.cos(a)},${cy + r * Math.sin(a)}`);
}
outline = `<polygon points="${pts.join(" ")}" />`;
break;
}
case "star": {
const spikes = 5, step = Math.PI / spikes, pts = [];
for (let i = 0; i < 2 * spikes; i++) {
const len = i % 2 === 0 ? r : r / 2.5;
const a = i * step - Math.PI / 2;
pts.push(`${cx + Math.cos(a) * len},${cy + Math.sin(a) * len}`);
}
outline = `<polygon points="${pts.join(" ")}" />`;
break;
}
case "pentagon": {
const pts = [];
for (let i = 0; i < 5; i++) {
const a = (2 * Math.PI / 5) * i - Math.PI / 2;
pts.push(`${cx + r * Math.cos(a)},${cy + r * Math.sin(a)}`);
}
outline = `<polygon points="${pts.join(" ")}" />`;
break;
}
case "teardrop":
outline = `<path d="M ${cx} ${cy - r} C ${cx + r} ${cy - r / 3}, ${cx + r / 2} ${cy + r}, ${cx} ${cy + r} C ${cx - r / 2} ${cy + r}, ${cx - r} ${cy - r / 3}, ${cx} ${cy - r} Z" />`;
break;
case "cross": {
const s = r * 0.35;
outline = `<path d="M${cx - s},${cy - r} h${2 * s} v${r - s} h${r - s} v${2 * s} h${-(r - s)} v${r - s} h${-(2 * s)} v${-(r - s)} h${-(r - s)} v${-(2 * s)} h${r - s}Z" />`;
break;
}
default:
outline = `<circle cx="${cx}" cy="${cy}" r="${r}" />`;
}

return `<svg class="pulse-ring-shape" xmlns="http://www.w3.org/2000/svg"
width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
<g fill="none" stroke="rgba(247,147,29,0.9)" stroke-width="${sw}">
${outline}
</g>
</svg>`;
}




13 changes: 12 additions & 1 deletion src/pages/tasking/components/job_popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function buildJobPopupKO() {
class="fw-bold text-center"
style="color:white;background: black">
<span class="no-drag" data-bind="text: identifier"></span>
<em class="fa fa-fw fa-share-alt" data-bind="visible: icemsIncidentIdentifier, attr:{ title: icemsIncidentIdentifier }"></em>
<em class="fa fa-fw fa-share-alt" data-bind="visible: icemsIncidentIdentifier, attr:{ title: icemsIncidentIdentifier }, css: { 'text-danger': hasUnacceptedNotifications() }"></em>
</div>
<div id="jobType"
class="fw-bold text-center"
Expand All @@ -20,6 +20,17 @@ export function buildJobPopupKO() {
data-bind="style: { backgroundColor: bannerBGColour}">
<span id="priAndCat" data-bind="text: priorityName +' '+categoriesName"></span>
</div>
<!-- Unacknowledged Notifications Warning -->
<!-- ko if: hasUnacceptedNotifications() -->
<div class="alert alert-danger d-flex align-items-center justify-content-center py-1 px-2 mb-1 mt-1 text-center"
data-bind="click: $root.displayTimelineForJob, clickBubble: false"
role="button"
title="Open timeline"
style="cursor: pointer; font-size: 0.875rem;">
<i class="fa fa-bell me-2"></i>
<span class="fw-semibold">Unacknowledged ICEMS notifications</span>
</div>
<!-- /ko -->
<!-- New line to show tag.Name if actionRequiredTags has length -->
<div id="actionRequiredTags" class="text-center d-flex flex-wrap mt-1" data-bind="visible: actionRequiredTags().length > 0, foreach: actionRequiredTagsDeduplicated">
<span data-bind="class: returnTagClass" style="cursor: default; width: 100%;">
Expand Down
60 changes: 44 additions & 16 deletions src/pages/tasking/components/legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ var L = require('leaflet');

// Legend control (collapsible)
export const LegendControl = L.Control.extend({
options: { position: "bottomleft", collapsed: false, persist: true },
options: { position: "bottomleft", collapsed: true, persist: true },

onAdd() {
const div = L.DomUtil.create("div", "legend-container leaflet-bar");
Expand All @@ -26,7 +26,7 @@ export const LegendControl = L.Control.extend({
</div>

<div class="mb-2">
<div class="fw-semibold small mb-1">Priority → Fill</div>
<div class="fw-semibold small mb-1 mt-2">Priority → Fill</div>
<div class="d-flex flex-wrap gap-2 small">
<div><span class="legend-box" style="background:#FFA500"></span> Priority</div>
<div><span class="legend-box" style="background:#4F92FF"></span> Immediate</div>
Expand All @@ -36,7 +36,7 @@ export const LegendControl = L.Control.extend({
</div>

<div>
<div class="fw-semibold small mb-1">FR: Category → Fill</div>
<div class="fw-semibold small mb-1 mt-2">FR: Category → Fill</div>
<div class="d-flex flex-wrap gap-2 small">
<div><svg width="16" height="16"><polygon points="8,2 14,6 12,14 4,14 2,6" stroke="#000" fill="#7F1D1D" stroke-width="2"/></svg> Cat 1</div>
<div><svg width="16" height="16"><polygon points="8,2 14,6 12,14 4,14 2,6" stroke="#000" fill="#DC2626" stroke-width="2"/></svg> Cat 2</div>
Expand All @@ -48,14 +48,45 @@ export const LegendControl = L.Control.extend({


<div>
<div class="fw-semibold small mb-1">Overlays</div>
<div class="d-flex flex-wrap gap-2 small legend-ring ">
<div><div class="pulse-ring-icon"></div><svg class="pulse-ring" width="16" height="16"><circle cx="8" cy="8" r="6" fill="none" stroke="#000" stroke-width="2"/></svg> Unacknowledged incident</div>
<div class="fw-semibold small mb-1 mt-2">Overlays</div>
<div style="display:grid;grid-template-columns:1fr 1fr;column-gap:12px;row-gap:4px;" class="small legend-ring">
<div style="display:flex;align-items:center;gap:4px;">
<span style="display:inline-block;width:18px;height:18px;border-radius:50%;border:2px solid #f7931d;animation:pulse-ring 1.4s ease-out infinite;"></span>
<span>Unacknowledged Incident</span>
</div>
<div style="display:flex;align-items:center;gap:4px;">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 36 36" style="flex-shrink:0;overflow:visible;">
<polygon points="18,3 33,11.5 33,24.5 18,33 3,24.5 3,11.5" fill="#6b7280" stroke="#888" stroke-width="2"/>
<polygon points="24.1,9 27.5,18 24.1,27 11.9,27 8.5,18 11.9,9" fill="#6b7280"/>
<text x="18" y="18" text-anchor="middle" dominant-baseline="central" fill="#fff" font-size="13" font-weight="700" font-family="system-ui,sans-serif">7</text>
</svg>
<span>Cluster of 7 Incidents</span>
</div>
<div style="display:flex;align-items:center;gap:4px;">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 36 36" style="flex-shrink:0;overflow:visible;">
<!-- Flat-top hexagon, half border green, half red -->
<polygon points="18,4 32,11 32,25 18,32 4,25 4,11" fill="#6b7280"/>
<polyline points="18,4 32,11 32,25 18,32" fill="none" stroke="#28a745" stroke-width="3"/>
<polyline points="18,32 4,25 4,11 18,4" fill="none" stroke="#dc3545" stroke-width="3"/>
<polygon points="18,12 24,18 18,24 12,18" fill="#6b7280"/>
<text x="18" y="18" text-anchor="middle" dominant-baseline="central" fill="#fff" font-size="13" font-weight="700" font-family="system-ui,sans-serif">5</text>
</svg>
<span>Cluster Of Mixed Priorities</span>
</div>
<div style="display:flex;align-items:center;gap:4px;">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 36 36" style="flex-shrink:0;overflow:visible;">
<polygon points="18,3 33,11.5 33,24.5 18,33 3,24.5 3,11.5" fill="#6b7280" stroke="rgba(247,147,29,0.9)" stroke-width="2"/>
<polygon points="24.1,9 27.5,18 24.1,27 11.9,27 8.5,18 11.9,9" fill="#6b7280"/>
<text x="18" y="18" text-anchor="middle" dominant-baseline="central" fill="#fff" font-size="13" font-weight="700" font-family="system-ui,sans-serif">3</text>
<polygon points="18,3 33,11.5 33,24.5 18,33 3,24.5 3,11.5" fill="none" stroke="#f7931d" stroke-width="2" stroke-dasharray="4 2"/>
</svg>
<span>Cluster Contains Unacked Incidents</span>
</div>
</div>
</div>
<div class="legend-section">
<br>
<div class="fw-semibold small mb-1">Assets</div>
</div>


<div class="fw-semibold small mb-1 mt-2">Assets</div>
<div style="display:grid;grid-template-columns:1fr 1fr;column-gap:12px;row-gap:2px;">

<div style="display:flex;align-items:center;margin:2px 0;">
Expand Down Expand Up @@ -146,12 +177,9 @@ export const LegendControl = L.Control.extend({
this._body = div.querySelector(".legend-body");
this._btn = div.querySelector(".toggle-legend");

// initial state
const collapsed =
this.options.persist &&
localStorage.getItem("legendCollapsed") === "1"
? true
: !!this.options.collapsed;
// initial state: use stored preference if available, otherwise fall back to option default
const stored = this.options.persist ? localStorage.getItem("legendCollapsed") : null;
const collapsed = stored !== null ? stored === "1" : !!this.options.collapsed;
this._setCollapsed(collapsed);

// prevent map drag/zoom on click
Expand Down
Loading