diff --git a/index.css b/index.css index ddd272a5..e208afb4 100644 --- a/index.css +++ b/index.css @@ -1527,13 +1527,13 @@ div.states > .resourceIcon > * { pointer-events: none; } -div.states > .icon { +div.states > .resourceIcon { margin: 0; cursor: pointer; vertical-align: middle; } -div.states > .icon > * { +div.states > .resourceIcon > * { pointer-events: none; } diff --git a/modules/ui/emblems-editor.js b/modules/ui/emblems-editor.js index 3ab3335a..ddb43b62 100644 --- a/modules/ui/emblems-editor.js +++ b/modules/ui/emblems-editor.js @@ -1,14 +1,14 @@ -"use strict"; +'use strict'; function editEmblem(type, id, el) { if (customization) return; if (!id && d3.event) defineEmblemData(d3.event); - emblems.selectAll("use").call(d3.drag().on("drag", dragEmblem)).classed("draggable", true); + emblems.selectAll('use').call(d3.drag().on('drag', dragEmblem)).classed('draggable', true); - const emblemStates = document.getElementById("emblemStates"); - const emblemProvinces = document.getElementById("emblemProvinces"); - const emblemBurgs = document.getElementById("emblemBurgs"); - const emblemShapeSelector = document.getElementById("emblemShapeSelector"); + const emblemStates = document.getElementById('emblemStates'); + const emblemProvinces = document.getElementById('emblemProvinces'); + const emblemBurgs = document.getElementById('emblemBurgs'); + const emblemShapeSelector = document.getElementById('emblemShapeSelector'); updateElementSelectors(type, id, el); @@ -26,21 +26,21 @@ function editEmblem(type, id, el) { emblemProvinces.oninput = selectProvince; emblemBurgs.oninput = selectBurg; emblemShapeSelector.oninput = changeShape; - document.getElementById("emblemSizeSlider").oninput = changeSize; - document.getElementById("emblemSizeNumber").oninput = changeSize; - document.getElementById("emblemsRegenerate").onclick = regenerate; - document.getElementById("emblemsArmoria").onclick = openInArmoria; - document.getElementById("emblemsUpload").onclick = toggleUpload; - document.getElementById("emblemsUploadImage").onclick = () => emblemImageToLoad.click(); - document.getElementById("emblemsUploadSVG").onclick = () => emblemSVGToLoad.click(); - document.getElementById("emblemImageToLoad").onchange = () => upload("image"); - document.getElementById("emblemSVGToLoad").onchange = () => upload("svg"); - document.getElementById("emblemsDownload").onclick = toggleDownload; - document.getElementById("emblemsDownloadSVG").onclick = () => download("svg"); - document.getElementById("emblemsDownloadPNG").onclick = () => download("png"); - document.getElementById("emblemsDownloadJPG").onclick = () => download("jpeg"); - document.getElementById("emblemsGallery").onclick = downloadGallery; - document.getElementById("emblemsFocus").onclick = showArea; + document.getElementById('emblemSizeSlider').oninput = changeSize; + document.getElementById('emblemSizeNumber').oninput = changeSize; + document.getElementById('emblemsRegenerate').onclick = regenerate; + document.getElementById('emblemsArmoria').onclick = openInArmoria; + document.getElementById('emblemsUpload').onclick = toggleUpload; + document.getElementById('emblemsUploadImage').onclick = () => imageToLoad.click(); + document.getElementById('emblemsUploadSVG').onclick = () => svgToLoad.click(); + document.getElementById('imageToLoad').onchange = () => uploadImage('image'); + document.getElementById('svgToLoad').onchange = () => uploadImage('svg'); + document.getElementById('emblemsDownload').onclick = toggleDownload; + document.getElementById('emblemsDownloadSVG').onclick = () => download('svg'); + document.getElementById('emblemsDownloadPNG').onclick = () => download('png'); + document.getElementById('emblemsDownloadJPG').onclick = () => download('jpeg'); + document.getElementById('emblemsGallery').onclick = downloadGallery; + document.getElementById('emblemsFocus').onclick = showArea; function defineEmblemData(e) { const parent = e.target.parentNode; @@ -57,9 +57,9 @@ function editEmblem(type, id, el) { burg = 0; // set active type - emblemStates.parentElement.className = type === "state" ? "active" : ""; - emblemProvinces.parentElement.className = type === "province" ? "active" : ""; - emblemBurgs.parentElement.className = type === "burg" ? "active" : ""; + emblemStates.parentElement.className = type === 'state' ? 'active' : ''; + emblemProvinces.parentElement.className = type === 'province' ? 'active' : ''; + emblemBurgs.parentElement.className = type === 'burg' ? 'active' : ''; // define selected values if (type === "state") state = el.i; @@ -72,19 +72,19 @@ function editEmblem(type, id, el) { state = el.state; } - const validBurgs = pack.burgs.filter(burg => burg.i && !burg.removed && burg.coa); + const validBurgs = pack.burgs.filter((burg) => burg.i && !burg.removed && burg.coa); // update option list and select actual values emblemStates.options.length = 0; - const neutralBurgs = validBurgs.filter(burg => !burg.state); + const neutralBurgs = validBurgs.filter((burg) => !burg.state); if (neutralBurgs.length) emblemStates.options.add(new Option(pack.states[0].name, 0, false, !state)); - const stateList = pack.states.filter(state => state.i && !state.removed); - stateList.forEach(s => emblemStates.options.add(new Option(s.name, s.i, false, s.i === state))); + const stateList = pack.states.filter((state) => state.i && !state.removed); + stateList.forEach((s) => emblemStates.options.add(new Option(s.name, s.i, false, s.i === state))); emblemProvinces.options.length = 0; - emblemProvinces.options.add(new Option("", 0, false, !province)); - const provinceList = pack.provinces.filter(province => !province.removed && province.state === state); - provinceList.forEach(p => emblemProvinces.options.add(new Option(p.name, p.i, false, p.i === province))); + emblemProvinces.options.add(new Option('', 0, false, !province)); + const provinceList = pack.provinces.filter((province) => !province.removed && province.state === state); + provinceList.forEach((p) => emblemProvinces.options.add(new Option(p.name, p.i, false, p.i === province))); emblemBurgs.options.length = 0; emblemBurgs.options.add(new Option("", 0, false, !burg)); @@ -98,33 +98,33 @@ function editEmblem(type, id, el) { function updateEmblemData(type, id, el) { if (!el.coa) return; - document.getElementById("emblemImage").setAttribute("href", "#" + id); + document.getElementById('emblemImage').setAttribute('href', '#' + id); let name = el.fullName || el.name; - if (type === "burg") name = "Burg of " + name; - document.getElementById("emblemArmiger").innerText = name; + if (type === 'burg') name = 'Burg of ' + name; + document.getElementById('emblemArmiger').innerText = name; - if (el.coa === "custom") emblemShapeSelector.disabled = true; + if (el.coa === 'custom') emblemShapeSelector.disabled = true; else { emblemShapeSelector.disabled = false; emblemShapeSelector.value = el.coa.shield; } const size = el.coaSize || 1; - document.getElementById("emblemSizeSlider").value = size; - document.getElementById("emblemSizeNumber").value = size; + document.getElementById('emblemSizeSlider').value = size; + document.getElementById('emblemSizeNumber').value = size; } function selectState() { const state = +this.value; if (state) { - type = "state"; + type = 'state'; el = pack.states[state]; id = "stateCOA" + state; } else { // select neutral burg if state is changed to Neutrals - const neutralBurgs = pack.burgs.filter(burg => burg.i && !burg.removed && !burg.state); + const neutralBurgs = pack.burgs.filter((burg) => burg.i && !burg.removed && !burg.state); if (!neutralBurgs.length) return; - type = "burg"; + type = 'burg'; el = neutralBurgs[0]; id = "burgCOA" + neutralBurgs[0].i; } @@ -135,13 +135,13 @@ function editEmblem(type, id, el) { const province = +this.value; if (province) { - type = "province"; + type = 'province'; el = pack.provinces[province]; id = "provinceCOA" + province; } else { // select state if province is changed to null value const state = +emblemStates.value; - type = "state"; + type = 'state'; el = pack.states[state]; id = "stateCOA" + state; } @@ -151,7 +151,7 @@ function editEmblem(type, id, el) { function selectBurg() { const burg = +this.value; - type = "burg"; + type = 'burg'; el = pack.burgs[burg]; id = "burgCOA" + burg; updateElementSelectors(type, id, el); @@ -193,8 +193,8 @@ function editEmblem(type, id, el) { function regenerate() { let parent = null; - if (type === "province") parent = pack.states[el.state]; - else if (type === "burg") { + if (type === 'province') parent = pack.states[el.state]; + else if (type === 'burg') { const province = pack.cells.province[el.cell]; parent = province ? pack.provinces[province] : pack.states[el.state]; } @@ -211,64 +211,57 @@ function editEmblem(type, id, el) { } function openInArmoria() { - const coa = el.coa && el.coa !== "custom" ? el.coa : {t1: "sable"}; - const json = JSON.stringify(coa).replaceAll("#", "%23"); + const coa = el.coa && el.coa !== 'custom' ? el.coa : {t1: 'sable'}; + const json = JSON.stringify(coa).replaceAll('#', '%23'); const url = `https://azgaar.github.io/Armoria/?coa=${json}&from=FMG`; openURL(url); } function toggleUpload() { - document.getElementById("emblemDownloadControl").classList.add("hidden"); - const buttons = document.getElementById("emblemUploadControl"); - buttons.classList.toggle("hidden"); + document.getElementById('emblemDownloadControl').classList.add('hidden'); + const buttons = document.getElementById('emblemUploadControl'); + buttons.classList.toggle('hidden'); } - function upload(type) { - const input = type === "image" ? document.getElementById("emblemImageToLoad") : document.getElementById("emblemSVGToLoad"); + function uploadImage(type) { + const input = type === 'image' ? document.getElementById('imageToLoad') : document.getElementById('svgToLoad'); const file = input.files[0]; - input.value = ""; + input.value = ''; - if (file.size > 500000) { - tip(`File is too big, please optimize file size up to 500kB and re-upload. Recommended size is 200x200 px and up to 100kB`, true, "error", 5000); - return; - } + if (file.size > 500000) return tip(`File is too big, please optimize file size up to 500kB and re-upload. Recommended size is 200x200 px and up to 100kB`, true, 'error', 5000); const reader = new FileReader(); - reader.onload = function (readerEvent) { const result = readerEvent.target.result; - const defs = document.getElementById("defs-emblems"); + const defs = document.getElementById('defs-emblems'); const coa = document.getElementById(id); // old emblem - if (type === "image") { + if (type === 'image') { const svg = ``; - defs.insertAdjacentHTML("beforeend", svg); + defs.insertAdjacentHTML('beforeend', svg); } else { - const el = document.createElement("html"); + const el = document.createElement('html'); el.innerHTML = result; // remove sodipodi and inkscape attributes - el.querySelectorAll("*").forEach(el => { + el.querySelectorAll('*').forEach((el) => { const attributes = el.getAttributeNames(); - attributes.forEach(attr => { - if (attr.includes("inkscape") || attr.includes("sodipodi")) el.removeAttribute(attr); + attributes.forEach((attr) => { + if (attr.includes('inkscape') || attr.includes('sodipodi')) el.removeAttribute(attr); }); }); - const svg = el.querySelector("svg"); - if (!svg) { - tip("The file should be prepated for load to FMG. Please use Armoria or other relevant tools", false, "error"); - return; - } + const svg = el.querySelector('svg'); + if (!svg) return tip('The file should be prepated for load to FMG. Please use Armoria or other relevant tools', false, 'error'); const newEmblem = defs.appendChild(svg); newEmblem.id = id; - newEmblem.setAttribute("width", 200); - newEmblem.setAttribute("height", 200); + newEmblem.setAttribute('width', 200); + newEmblem.setAttribute('height', 200); } if (coa) coa.remove(); // remove old emblem - el.coa = "custom"; + el.coa = 'custom'; emblemShapeSelector.disabled = true; }; @@ -277,17 +270,17 @@ function editEmblem(type, id, el) { } function toggleDownload() { - document.getElementById("emblemUploadControl").classList.add("hidden"); - const buttons = document.getElementById("emblemDownloadControl"); - buttons.classList.toggle("hidden"); + document.getElementById('emblemUploadControl').classList.add('hidden'); + const buttons = document.getElementById('emblemDownloadControl'); + buttons.classList.toggle('hidden'); } async function download(format) { const coa = document.getElementById(id); const size = +emblemsDownloadSize.value; const url = await getURL(coa, size); - const link = document.createElement("a"); - link.download = getFileName(`Emblem ${el.fullName || el.name}`) + "." + format; + const link = document.createElement('a'); + link.download = getFileName(`Emblem ${el.fullName || el.name}`) + '.' + format; if (format === "svg") downloadSVG(url, link); else downloadRaster(format, url, link, size); @@ -300,8 +293,8 @@ function editEmblem(type, id, el) { } function downloadRaster(format, url, link, size) { - const canvas = document.createElement("canvas"); - const ctx = canvas.getContext("2d"); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); canvas.width = size; canvas.height = size; @@ -336,10 +329,10 @@ function editEmblem(type, id, el) { } async function downloadGallery() { - const name = getFileName("Emblems Gallery"); - const validStates = pack.states.filter(s => s.i && !s.removed && s.coa); - const validProvinces = pack.provinces.filter(p => p.i && !p.removed && p.coa); - const validBurgs = pack.burgs.filter(b => b.i && !b.removed && b.coa); + const name = getFileName('Emblems Gallery'); + const validStates = pack.states.filter((s) => s.i && !s.removed && s.coa); + const validProvinces = pack.provinces.filter((p) => p.i && !p.removed && p.coa); + const validBurgs = pack.burgs.filter((b) => b.i && !b.removed && b.coa); await renderAllEmblems(validStates, validProvinces, validBurgs); runDownload(); @@ -480,14 +473,14 @@ function editEmblem(type, id, el) { } async function renderAllEmblems(states, provinces, burgs) { - tip("Preparing for download...", true, "warn"); + tip('Preparing for download...', true, 'warn'); const statePromises = states.map(state => COArenderer.trigger("stateCOA" + state.i, state.coa)); const provincePromises = provinces.map(province => COArenderer.trigger("provinceCOA" + province.i, province.coa)); const burgPromises = burgs.map(burg => COArenderer.trigger("burgCOA" + burg.i, burg.coa)); const promises = [...statePromises, ...provincePromises, ...burgPromises]; - return Promise.allSettled(promises).then(res => clearMainTip()); + return Promise.allSettled(promises).then((res) => clearMainTip()); } function dragEmblem() { @@ -502,6 +495,6 @@ function editEmblem(type, id, el) { } function closeEmblemEditor() { - emblems.selectAll("use").call(d3.drag().on("drag", null)).attr("class", null); + emblems.selectAll('use').call(d3.drag().on('drag', null)).attr('class', null); } } diff --git a/modules/ui/resources-editor.js b/modules/ui/resources-editor.js index 997ea9de..d84d3f5e 100644 --- a/modules/ui/resources-editor.js +++ b/modules/ui/resources-editor.js @@ -34,6 +34,7 @@ function editResources() { cl = el.classList, line = el.parentNode; const resource = Resources.get(+line.dataset.id); + if (cl.contains('resourceIcon')) return changeIcon(resource, line, el); if (cl.contains('resourceCategory')) return changeCategory(resource, line, el); if (cl.contains('resourceModel')) return changeModel(resource, line, el); if (cl.contains('resourceBonus')) return changeBonus(resource, line, el); @@ -82,7 +83,7 @@ function editResources() { data-id=${r.i} data-name="${r.name}" data-color="${r.color}" data-category="${r.category}" data-chance="${r.chance}" data-bonus="${bonusString}" data-value="${r.value}" data-model="${r.model}" data-cells="${r.cells}"> - + @@ -105,10 +106,7 @@ function editResources() { document.getElementById('resourcesNumber').innerHTML = pack.resources.length; // add listeners - // body.querySelectorAll("div.resources").forEach(el => el.addEventListener("mouseenter", ev => resourceHighlightOn(ev))); - // body.querySelectorAll("div.resources").forEach(el => el.addEventListener("mouseleave", ev => resourceHighlightOff(ev))); body.querySelectorAll('div.states').forEach((el) => el.addEventListener('click', selectResourceOnLineClick)); - body.querySelectorAll('svg.icon').forEach((el) => el.addEventListener('click', resourceChangeColor)); if (body.dataset.type === 'percentage') { body.dataset.type = 'absolute'; @@ -307,17 +305,110 @@ function editResources() { resource.chance = line.dataset.chance = +chance; } - function resourceChangeColor() { - const circle = this.querySelector('circle'); - const resource = Resources.get(+this.parentNode.dataset.id); + function changeIcon(resource, line, el) { + const standardIcons = Array.from(document.getElementById('resource-icons').querySelectorAll('symbol')).map((el) => el.id); + const standardIconsOptions = standardIcons.map((icon) => ``); - const callback = function (fill) { + const customIconsEl = document.getElementById('defs-icons'); + const customIcons = customIconsEl ? Array.from(document.getElementById('defs-icons').querySelectorAll('svg')).map((el) => el.id) : []; + const customIconsOptions = customIcons.map((icon) => ``); + + const select = document.getElementById('resourceSelectIcon'); + select.innerHTML = standardIconsOptions + customIconsOptions; + + const preview = document.getElementById('resourceIconPreview'); + preview.setAttribute('href', '#' + resource.icon); + + $('#resourceIconEditor').dialog({ + resizable: false, + title: 'Change Icon', + buttons: { + Cancel: function () { + $(this).dialog('close'); + }, + 'Change color': () => changeColor(resource, line, el), + Apply: function () { + $(this).dialog('close'); + resource.icon = select.value; + line.querySelector('svg.resourceIcon > use').setAttribute('href', '#' + select.value); + drawResources(); + } + }, + position: {my: 'center bottom', at: 'center', of: 'svg'} + }); + + const uploadTo = document.getElementById('defs-icons'); + const onUpload = (id) => { + preview.setAttribute('href', '#' + id); + select.innerHTML += ``; + select.value = id; + }; + + // add listeners + select.onchange = () => preview.setAttribute('href', '#' + select.value); + document.getElementById('resourceUploadIconRaster').onclick = () => imageToLoad.click(); + document.getElementById('resourceUploadIconVector').onclick = () => svgToLoad.click(); + document.getElementById('imageToLoad').onchange = () => uploadImage('image', uploadTo, onUpload); + document.getElementById('svgToLoad').onchange = () => uploadImage('svg', uploadTo, onUpload); + } + + function uploadImage(type, uploadTo, callback) { + const input = type === 'image' ? document.getElementById('imageToLoad') : document.getElementById('svgToLoad'); + const file = input.files[0]; + input.value = ''; + + if (file.size > 200000) return tip(`File is too big, please optimize file size up to 200kB and re-upload. Recommended size is 48x48 px and up to 10kB`, true, 'error', 5000); + + const reader = new FileReader(); + reader.onload = function (readerEvent) { + const result = readerEvent.target.result; + const id = 'resource-custom-' + Math.random().toString(36).slice(-6); + + if (type === 'image') { + const svg = ``; + uploadTo.insertAdjacentHTML('beforeend', svg); + } else { + const el = document.createElement('html'); + el.innerHTML = result; + + // remove sodipodi and inkscape attributes + el.querySelectorAll('*').forEach((el) => { + const attributes = el.getAttributeNames(); + attributes.forEach((attr) => { + if (attr.includes('inkscape') || attr.includes('sodipodi')) el.removeAttribute(attr); + }); + }); + + // remove all text if source is Noun project (to make it usable) + if (result.includes('from the Noun Project')) el.querySelectorAll('text').forEach((textEl) => textEl.remove()); + + const svg = el.querySelector('svg'); + if (!svg) return tip("The file should be prepated for load to FMG. If you don't know why it's happening, try to upload the raster image", false, 'error'); + + const icon = uploadTo.appendChild(svg); + icon.id = id; + icon.setAttribute('width', 200); + icon.setAttribute('height', 200); + } + + callback(id); + }; + + if (type === 'image') reader.readAsDataURL(file); + else reader.readAsText(file); + } + + function changeColor(resource, line, el) { + const circle = el.querySelector('circle'); + + const callback = (fill) => { const stroke = Resources.getStroke(fill); circle.setAttribute('fill', fill); circle.setAttribute('stroke', stroke); resource.color = fill; resource.stroke = stroke; goods.selectAll(`circle[data-i='${resource.i}']`).attr('fill', fill).attr('stroke', stroke); + line.dataset.color = fill; }; openPicker(resource.color, callback, {allowHatching: false});