Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f69b971
Add editable map notes with sharp multiline text
alcatron Oct 11, 2026
609418a
Guard note deletion and propagate configuration write failures
alcatron Oct 11, 2026
3f5f48a
Exercise template saves and failing configuration streams
alcatron Oct 11, 2026
d90c70e
Keep sharp notes at logical live-view size and extract palette transl…
alcatron Oct 11, 2026
9ccb9cf
Preserve sharp notes across layers and clarify note deletion
alcatron Oct 11, 2026
22f266f
Preserve note rendering, defaults and failed configuration writes
alcatron Oct 12, 2026
b56c171
Verify atomic saves preserve template and traffic objects
alcatron Oct 12, 2026
2c9f1d6
Keep atomic-save regression checks portable across platforms
alcatron Oct 12, 2026
9a63dea
Fall back safely when a note font is unavailable
alcatron Oct 12, 2026
2978a41
Coordinate configuration writes, deletion and registration by directory
alcatron Oct 12, 2026
20feb14
Return a retry when configuration deletion encounters a busy directory
alcatron Oct 12, 2026
213a88b
Merge updated develop and preserve editor changes
alcatron Oct 12, 2026
eacc6df
Merge develop after gallery resize landed
alcatron Oct 12, 2026
f55a0be
Report stale edits and failed saves; fall back from unreadable fonts
alcatron Oct 12, 2026
1c37cae
Reject stale saves and competing creates; retain text on allocation f…
alcatron Oct 12, 2026
f4b5fda
Let unused note canvases release with the rendering scope
alcatron Oct 12, 2026
9f701fc
Reject stale editor pages and reserve new maps before registration
alcatron Oct 12, 2026
d9c907a
Keep assets and clone reservations safe on failed saves
alcatron Oct 12, 2026
3279a83
Refresh translations for registration failure reporting
alcatron Oct 12, 2026
826118f
Cover legacy background and icon repair failure paths
alcatron Oct 12, 2026
f27d95a
Preserve concurrent-edit status and bind clone registration IDs
alcatron Oct 13, 2026
3cae29d
Keep editor snapshots coherent and fall back missing note fonts
alcatron Oct 13, 2026
99c982e
Document the editor snapshot argument
alcatron Oct 13, 2026
18e4413
End raw edit requests and reject lost map copy sources
alcatron Oct 13, 2026
cfe8468
Preserve literal backslashes in saved note text
alcatron Oct 13, 2026
5080de3
Dispatch node and link Save buttons independently of placement mode
alcatron Oct 13, 2026
727af94
Handle failed creation, unavailable fonts and read-only registration
alcatron Oct 13, 2026
3ffca91
Keep read-only dependency checks and creation retry guidance safe
alcatron Oct 13, 2026
f25c246
Use effective render dimensions for editor and note bounds
alcatron Oct 13, 2026
d6941c1
Limit preview layer cloning to supported node and link objects
alcatron Oct 13, 2026
19c1dd2
Address repair, serialization and dialog submission review findings
alcatron Oct 13, 2026
ed4bfe9
Preserve every dialog submit action during placement and Enter saves
alcatron Oct 13, 2026
207bcda
Merge develop and address save and rendering review findings
alcatron Oct 13, 2026
0583302
Validate existing repair assets and inherited clone images
alcatron Oct 13, 2026
6ddb0a8
Repair sized legacy icons and align creation error branches
alcatron Oct 13, 2026
25de595
Require mutation revisions and keep template notes and clone diagnost…
alcatron Oct 14, 2026
72b3bf6
Preserve CAS across aliases and report incomplete clone cleanup
alcatron Oct 14, 2026
7ade91b
Bind root snapshots and preserve configurations on incomplete cleanup
alcatron Oct 14, 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ IMPORTANT NOTE: This version only works on CACTI 1.x++!

--- develop ---

* Make configuration saves and repairs atomic; coordinate writes, registration and deletion with destination-directory locks and report failed editor saves

* Add editable map notes with alignment and sharp text at logical map dimensions
* Fix editor placement reset, destructive action controls and theme contrast
* Reduce gallery preview cards by 20% while preserving generated thumbnail resolution
* Add a searchable node icon picker and bundled enterprise site icons
Expand Down
143 changes: 122 additions & 21 deletions js/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,9 @@ function wmEditorReturnUrl(target) {
}

function attach_click_events() {
$('#xycapture').off('click.wmnote').on('click.wmnote', function(event) {
if ($('#action').val() === 'place_note' || ($('#action').val() === 'move_node' && typeof WMNotes !== 'undefined' && WMNotes[$('#param').val()])) coord_update(event);
});
$("area[id^='LINK:']").attr('href', '#').off('click').on('click', click_handler);
$("area[id^='NODE:']").attr('href', '#').off('click').on('click', click_handler);
$("area[id^='TIMES']").attr('href', '#').off('click').on('click', position_timestamp);
Expand All @@ -559,6 +562,35 @@ function attach_click_events() {
$('#tb_mapstyle').off('click').on('click', map_style);

$('#tb_addlink').off('click').on('click', add_link);
$('#tb_addnote').off('click').on('click', add_note);
$('.wm-note-colour').off('click').on('click', function() {
$('#note_colour').val($(this).attr('data-colour'));
wmNoteRefreshPalette();
});
$('#note_colour').off('input change').on('input change', wmNoteRefreshPalette);
$('#note_advanced_toggle').off('click').on('click', function() {
var open = $(this).attr('aria-expanded') !== 'true';
$(this).attr('aria-expanded', String(open)).text(open ? wmEditorText.hideAdvanced : wmEditorText.advanced);
$('#note_advanced').toggle(open);
});
$('#note_clone').off('click').on('click', function() {
$('#note_name').val('');
$('#note_x').val(Math.min(parseInt($('#note_x').attr('max'), 10), parseInt($('#note_x').val(), 10) + 20));
$('#note_y').val(Math.min(parseInt($('#note_y').attr('max'), 10), parseInt($('#note_y').val(), 10) + 20));
$('#note_delete,#note_move,#note_clone').hide();
$('#note_text').trigger('focus');
});
$('#note_move').off('click').on('click', function() {
$('#param').val($('#note_name').val());
$('#node_name').val($('#note_name').val());
hide_dialog('dlgNoteProperties');
move_node();
Comment thread
Copilot marked this conversation as resolved.
});
$('#note_delete').off('click').on('click', function() {
$('#param').val($('#note_name').val());
$('#node_name').val($('#note_name').val());
delete_node();
Comment thread
Copilot marked this conversation as resolved.
});
$('#tb_poslegend').off('click').on('click', position_first_legend);
$('#tb_postime').off('click').on('click', position_timestamp);
$('#tb_colours').off('click').on('click', manage_colours);
Expand Down Expand Up @@ -765,19 +797,20 @@ function delete_node() {

var name = $('#node_name').val();
var connected = Object.keys(Links).filter(function(key) { return Links[key].a === name || Links[key].b === name; }).length;
var message = delNodePrompt.replace('%s', function() { return wmNodeDisplayName(name); });
var isNote = typeof WMNotes !== 'undefined' && Object.prototype.hasOwnProperty.call(WMNotes, name);
var message = (isNote ? wmEditorText.noteDelete : delNodePrompt).replace('%s', function() { return wmNodeDisplayName(name); });
if (connected) {
message += ' ' + delNodeConnected.replace('%s', String(connected));
}
$('.dlgConfirm').text(message + ' ' + delNodeKeepDevice);
$('.dlgConfirm').text(message + (isNote ? '' : ' ' + delNodeKeepDevice));

if (['add_link', 'add_link2', 'add_node', 'place_note', 'move_node', 'via_link', 'place_legend', 'place_stamp'].indexOf($('#action').val()) !== -1) $('#action').val('');
mapmode('existing');

$('.dlgConfirm').dialog({
close: function() { mapmode('existing'); },
resizable: false,
title: delNodeTitle,
title: isNote ? wmEditorText.noteDeleteTitle : delNodeTitle,
height: 'auto',
width: 400,
modal: true,
Expand All @@ -790,7 +823,7 @@ function delete_node() {
}
},
{
text: txtDelNode,
text: isNote ? wmEditorText.noteDeleteAction : txtDelNode,
'class': 'wm-delete-action',
click: function() {
$(this).dialog('close');
Expand Down Expand Up @@ -892,6 +925,19 @@ function delete_link() {
});
}

function wmEditorReloadUrl() {
var url = new URL(window.location.href);
url.searchParams.set('mapname', $('#mapname').val());
url.searchParams.set('action', 'nothing');
url.searchParams.delete('config_revision');
url.searchParams.delete('__csrf_magic');
return url.href;
}

function wmReloadEditor() {
window.location.replace(wmEditorReloadUrl());
}

function form_submit(event) {
// Property dialogs can remain open while a toolbar placement action is active.
// Their own Save buttons always dispatch the properties they are showing.
Expand All @@ -916,34 +962,40 @@ function form_submit(event) {
mapmode('existing');
}

if ($('#action').val() === 'place_note') {
show_note('', $('#x').val(), $('#y').val());
return;
}
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]);
var data = $('input, select, textarea').serialize();
if (typeof WMConfigRevision !== 'undefined') data += '&config_revision=' + encodeURIComponent(WMConfigRevision);

$.ajax({
type: 'POST',
url: editor_url,
data: data,
success: function(html) {
error: function(xhr) {
if (xhr && xhr.status === 409) {
window.alert(wmEditorText.staleError);
return;
}
if (savingNote) {
if (noteAction !== 'set_note_properties') show_note(noteTarget);
$('#wm-note-error').text(wmEditorText.noteError).show();
Comment thread
alcatron marked this conversation as resolved.
} else {
window.alert(wmEditorText.saveError);
}
},
success: function() {
hide_all_dialogs();

$.get('?action=load_area_data&mapname=' + encodeURIComponent($('#mapname').val()), function(data) {
$('.mapData').empty().html(data);

$.getScript('?action=load_map_javascript&mapname=' + encodeURIComponent($('#mapname').val()), function(data) {
var date = new Date();

// Reload the images to update page
$('#existingdata').attr('src', $('#existingdata').attr('src') + '&date=' + date.getTime());
$('#xycapture').attr('src', $('#xycapture').attr('src') + '&date=' + date.getTime());

$('#action').val('');

initJS();
});
});
wmReloadEditor();
}
});
}


function map_properties() {
mapmode('existing');

Expand Down Expand Up @@ -1087,6 +1139,10 @@ function prime_node_form(name) {
}

function show_node(name) {
if (typeof WMNotes !== 'undefined' && Object.prototype.hasOwnProperty.call(WMNotes, name)) {
show_note(name);
return;
}
mapmode('existing');

hide_all_dialogs();
Expand Down Expand Up @@ -1298,6 +1354,7 @@ function hide_all_dialogs() {
hide_dialog('dlgLinkProperties');
hide_dialog('dlgTextEdit');
hide_dialog('dlgNodeProperties');
hide_dialog('dlgNoteProperties');
hide_dialog('dlgColours');
hide_dialog('dlgImages');
hide_dialog('dlgEditorSettings');
Expand Down Expand Up @@ -1769,3 +1826,47 @@ function wmFriendlyLinkNames() {
function wmDecodeEditorUrl(value) {
return String(value || '').replace(/&/g, '&');
}
// Notes are ordinary label-only nodes, with a small editor-specific style form.
function add_note() {
hide_all_dialogs();
$('#tb_help').addClass('wm-action-help').text(wmEditorText.addNoteHelp);
$('#action').val('place_note');
mapmode('xy');
Comment thread
alcatron marked this conversation as resolved.
}
function wmNoteRefreshPalette() {
var colour = String($('#note_colour').val()).toLowerCase();
$('.wm-note-colour').each(function() {
var selected = $(this).attr('data-colour') === colour;
$(this).attr('aria-pressed', selected ? 'true' : 'false').css('outline', selected ? '2px solid currentColor' : '');
});
}
function show_note(name, x, y) {
hide_all_dialogs();
mapmode('existing');
var node = name ? Nodes[name] : null;
var style = name ? WMNotes[name] : null;
$('#note_name').val(name);
$('#note_align').val(style && ['left', 'centre', 'right'].indexOf(style.align) !== -1 ? style.align : 'left');
$('#note_text').val(node ? (style && typeof style.text === 'string' ? style.text : node.label.replace(/\\n/g, '\n')) : '');
$('#note_x').val(node ? node.x : x);
$('#note_y').val(node ? node.y : y);
$('#note_colour').val(style ? style.colour : '#000000');
wmNoteRefreshPalette();
$('#note_advanced').hide();
$('#note_advanced_toggle').attr('aria-expanded', 'false').text(wmEditorText.advanced);
if (style) {
if (!$('#note_font option').filter(function() { return this.value === style.font; }).length) {
$('<option>').val(style.font).text(style.fontLabel).appendTo('#note_font');
}
$('#note_font').val(style.font);
} else {
var defaultFont = $('#note_font option').filter(function() { return this.value === 'bundled:Vera.ttf:9' || $(this).attr('data-wm-note-default') === '1'; }).first();
$('#note_font').val(defaultFont.length ? defaultFont.val() : '3');
}
$('#note_delete,#note_move,#note_clone').toggle(!!node);
$('#wm-note-error').hide();
$('#action').val('set_note_properties');
show_dialog('dlgNoteProperties');
$('#note_font,#note_align').each(function() { if ($(this).data('ui-selectmenu')) $(this).selectmenu('refresh'); });
$('#note_text').trigger('focus');
}
Loading
Loading