diff --git a/CHANGELOG.md b/CHANGELOG.md index 67cb79e2..eae61f01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ IMPORTANT NOTE: This version only works on CACTI 1.x++! * Fix Safari rendering of high-traffic link glows using native SVG filters * Make configuration saves and repairs atomic; coordinate writes, registration and deletion with destination-directory locks and report failed editor saves +* Add separate icon and background image upload and management libraries * 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 diff --git a/css/editor.css b/css/editor.css index 9f85de23..15c23ebd 100644 --- a/css/editor.css +++ b/css/editor.css @@ -346,6 +346,19 @@ body#mainView .ui-dialog textarea.ui-state-default { color: var(--text-color-lighter, #333) !important; } +.wm-image-delete { color: #c62828 !important; background: #fff !important; border: 1px solid #c62828 !important; font-size: 20px; } +#dlgImages .wm-image-library { width: 100%; box-sizing: border-box; display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; max-height: 240px; overflow: auto; align-content: start; padding: 4px; } +#dlgImages .wm-image-card { min-width: 0; padding: 8px; border: 1px solid #999; display: flex; flex-direction: column; gap: 6px; overflow-wrap: anywhere; align-items: center; } +#dlgImages .wm-image-card img { width: 100%; max-width: 130px; height: 70px; object-fit: contain; flex: none; } +#dlgImages .wm-image-uploader { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 10px 0; } +/* Use Cacti's own theme heading colours in every grouped editor dialog. */ +.dlgProperties .wm-editor-section { display: table; width: 100%; box-sizing: border-box; margin: 10px 0 6px; } +.dlgProperties table .wm-editor-section { margin: 0; } +#wm-images-error:not(:empty) { color: inherit; border-left: 3px solid #c62828; padding-left: 8px; font-weight: bold; } + +#dlgImages > .dlgBody { display: block; width: 100%; box-sizing: border-box; } +#dlgImages .wm-editor-section { float: none; } + /* Colour and image libraries. */ #wm-scale-rows input { width: 85px; box-sizing: border-box; } #wm-scale-rows .wm-range-tag { width: 110px; } @@ -362,9 +375,6 @@ body#mainView .ui-dialog textarea.ui-state-default { .wm-traffic-swatch { width: 55px; height: 22px; border: 1px solid #777; } #wm-traffic-advanced > summary { cursor: pointer; margin: 14px 0; } -/* Use Cacti's own theme heading colours in every grouped editor dialog. */ -.dlgProperties .wm-editor-section { display: table; width: 100%; box-sizing: border-box; margin: 10px 0 6px; } -.dlgProperties table .wm-editor-section { margin: 0; } #wm-colours-error:not(:empty) { color: inherit; border-left: 3px solid #c62828; padding-left: 8px; font-weight: bold; } #dlgColours > .dlgBody { display: block; } diff --git a/js/editor.js b/js/editor.js index 8ffed979..8a49f886 100644 --- a/js/editor.js +++ b/js/editor.js @@ -740,6 +740,7 @@ function manage_images() { $('#action').val('set_image'); show_dialog('dlgImages'); + wmOpenImages(); } function prefs() { @@ -1328,7 +1329,7 @@ function show_dialog(dlg) { $('#'+dlg).dialog({ autoOpen: true, - width: 600, + width: dlg === 'dlgImages' ? Math.min(900, $(window).width() - 40) : 600, height: 'auto', modal: false, resizable: false, @@ -1833,6 +1834,69 @@ function wmFriendlyLinkNames() { function wmDecodeEditorUrl(value) { return String(value || '').replace(/&/g, '&'); } +function wmAssetRequest(action, extra, callback, errorId) { + var values = {action: action, mapname: $('#mapname').val()}; + if (action !== 'image_data' && typeof WMConfigRevision !== 'undefined') values.config_revision = WMConfigRevision; + if (typeof csrfMagicToken !== 'undefined') values.__csrf_magic = csrfMagicToken; + $.ajax({url: editor_url, type: action === 'image_data' ? 'GET' : 'POST', dataType: 'json', data: $.extend(values, extra), + success: callback, error: function(xhr) { $('#' + errorId).text(xhr.responseJSON && xhr.responseJSON.error || wmEditorText.imageText3); }}); +} +function wmAssetImages(images) { + $('#wm-images-objects, #wm-images-backgrounds').empty(); + images.forEach(function(item) { + var card = $('
').appendTo('#wm-images-' + item.kind); + $('').attr({src: item.url, alt: item.name, loading: 'lazy'}).appendTo(card); + $('').text(item.name).appendTo(card); + if (item.width && item.height) $('').text(item.width + ' × ' + item.height + ' px').appendTo(card); + $('').text(item.users && item.users.length ? wmEditorText.imageUsed.replace('%s', item.users.join(', ')) : wmEditorText.imageUnused).appendTo(card); + if (item.deletable !== false) $('',{runScripts:'outside-only',pretendToBeVisual:true}); +const w=dom.window,$=jquery(w);w.$=w.jQuery=$;w.editor_url='/editor';w.csrfMagicToken='fixture';w.WMConfigRevision='a'.repeat(64);$.fn.dialog=function(){return this;}; +const inc=fs.readFileSync(path.join(__dirname,'../../lib/editor.inc.php'),'utf8');w.wmEditorText={}; +for(const m of inc.matchAll(/'(imageText[0-9]+|imageUsed|imageUnused)' => __\('((?:\\.|[^'])+)'/g))w.wmEditorText[m[1]]=m[2].replace(/\\'/g,"'"); +w.wmEditorText.imageUnused='Non utilisée'; +const source=fs.readFileSync(path.join(__dirname,'../../js/editor.js'),'utf8');w.eval(source.slice(source.indexOf('function wmAssetRequest('))); +const images=[{kind:'objects',name:'hospital.png',url:'images/objects/hospital.png',width:96,height:96,users:['map.conf'],deletable:false},{kind:'backgrounds',name:'background.jpg',url:'images/backgrounds/background.jpg',width:800,height:600,users:[],deletable:true}]; +$('#node_iconfilename').append('').val('images/icons/external.png'); +w.wmAssetImages(images); +assert.equal($('#node_iconfilename').val(),'images/icons/external.png');assert.equal($('#node_iconfilename option[value="images/icons/external.png"]').length,1); +assert.equal($('#wm-images-objects .wm-image-delete').length,0);assert.match($('#wm-images-objects').text(),/96 × 96 px/);assert.match($('#wm-images-objects').text(),/map.conf/);assert.match($('#wm-images-backgrounds').text(),/Non utilisée/); +assert.equal($('#node_iconfilename option[value="images/objects/hospital.png"]').length,1); +for (const [id, value] of [['node_iconfilename','images/objects/custom folder/site.png'], ['map_bgfile','images/backgrounds/private.jpg']]) { + const select=$('#'+id); + select.append($('