diff --git a/CHANGELOG.md b/CHANGELOG.md index f4de5c6..c43a7b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/js/editor.js b/js/editor.js index 4ee8f47..17080cb 100644 --- a/js/editor.js +++ b/js/editor.js @@ -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); @@ -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(); + }); + $('#note_delete').off('click').on('click', function() { + $('#param').val($('#note_name').val()); + $('#node_name').val($('#note_name').val()); + delete_node(); + }); $('#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); @@ -765,11 +797,12 @@ 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'); @@ -777,7 +810,7 @@ function delete_node() { $('.dlgConfirm').dialog({ close: function() { mapmode('existing'); }, resizable: false, - title: delNodeTitle, + title: isNote ? wmEditorText.noteDeleteTitle : delNodeTitle, height: 'auto', width: 400, modal: true, @@ -790,7 +823,7 @@ function delete_node() { } }, { - text: txtDelNode, + text: isNote ? wmEditorText.noteDeleteAction : txtDelNode, 'class': 'wm-delete-action', click: function() { $(this).dialog('close'); @@ -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. @@ -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(); + } 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'); @@ -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(); @@ -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'); @@ -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'); +} +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) { + $('