diff --git a/components/fill-box.js b/components/fill-box.js new file mode 100644 index 00000000..eecfda92 --- /dev/null +++ b/components/fill-box.js @@ -0,0 +1,74 @@ +// fill-box cannot use Shadow DOM as it needs access to svg hatches +// append stylesheet +{ + const style = ` + fill-box:not([disabled]) { + cursor: pointer; + } + + fill-box > svg { + aspect-ratio: 1; + vertical-align: middle; + pointer-events: none; + } + + fill-box > svg > rect { + stroke: #666666; + stroke-width: 2; + }`; + + const styleElement = document.createElement("style"); + styleElement.setAttribute("type", "text/css"); + styleElement.innerHTML = style; + document.head.appendChild(styleElement); +} + +{ + const template = document.createElement("template"); + template.innerHTML = ` + + + + `; + + class FillBox extends HTMLElement { + constructor() { + super(); + + this.appendChild(template.content.cloneNode(true)); + this.querySelector("rect")?.setAttribute("fill", this.fill); + this.querySelector("svg")?.setAttribute("width", this.size); + } + + static showTip() { + tip(this.tip); + } + + connectedCallback() { + this.addEventListener("mousemove", this.constructor.showTip); + } + + disconnectedCallback() { + this.removeEventListener("mousemove", this.constructor.showTip); + } + + get fill() { + return this.getAttribute("fill") || "#333"; + } + + set fill(newFill) { + this.setAttribute("fill", newFill); + this.querySelector("rect")?.setAttribute("fill", newFill); + } + + get size() { + return this.getAttribute("size") || "1em"; + } + + get tip() { + return this.dataset.tip || "Fill style. Click to change"; + } + } + + customElements.define("fill-box", FillBox); +} diff --git a/index.css b/index.css index c2297f96..9946d1b2 100644 --- a/index.css +++ b/index.css @@ -1531,6 +1531,11 @@ div.states > .riverType { cursor: pointer; } +.changeRelations > * { + pointer-events: none; + cursor: pointer; +} + #diplomacySelect { width: 5em; margin: 0.1em 0 0 -0.3em; @@ -1668,11 +1673,6 @@ div.states > div.biomeArea { width: 5em; } -rect.fillRect { - stroke: #666666; - stroke-width: 2; -} - #militaryHeader > div, #regimentsHeader > div { width: 5.2em; diff --git a/index.html b/index.html index 9a23d661..ebb2b775 100644 --- a/index.html +++ b/index.html @@ -4443,5 +4443,8 @@ + + + diff --git a/modules/ui/battle-screen.js b/modules/ui/battle-screen.js index 37a1bd51..2936549c 100644 --- a/modules/ui/battle-screen.js +++ b/modules/ui/battle-screen.js @@ -141,8 +141,8 @@ class Battle { const state = pack.states[regiment.state]; const distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScaleInput.value) | 0; // distance between regiment and its base const color = state.color[0] === "#" ? state.color : "#999"; - const icon = ` - + const icon = ` + ${regiment.icon}`; const body = ``; @@ -183,7 +183,7 @@ class Battle { dist = added ? "0 " + distanceUnitInput.value : distance(r); return `
- +
${s.name.slice(0, 11)}
${r.icon}
${r.name.slice(0, 24)}
diff --git a/modules/ui/biomes-editor.js b/modules/ui/biomes-editor.js index ca5bb41d..527e0b08 100644 --- a/modules/ui/biomes-editor.js +++ b/modules/ui/biomes-editor.js @@ -37,9 +37,9 @@ function editBiomes() { document.getElementById("biomesExport").addEventListener("click", downloadBiomesData); body.addEventListener("click", function (ev) { - const el = ev.target, - cl = el.classList; - if (cl.contains("fillRect")) biomeChangeColor(el); + const el = ev.target; + const cl = el.classList; + if (el.tagName === "FILL-BOX") biomeChangeColor(el); else if (cl.contains("icon-info-circled")) openWiki(el); else if (cl.contains("icon-trash-empty")) removeCustomBiome(el); if (customization === 6) selectBiomeOnLineClick(el); @@ -94,9 +94,7 @@ function editBiomes() { lines += `
- + % { + el.fill = newFill; + biomesData.color[biome] = newFill; biomes .select("#biome" + biome) - .attr("fill", fill) - .attr("stroke", fill); + .attr("fill", newFill) + .attr("stroke", newFill); }; openPicker(currentFill, callback); @@ -270,7 +268,7 @@ function editBiomes() { const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const line = `
- + % diff --git a/modules/ui/cultures-editor.js b/modules/ui/cultures-editor.js index 74c73535..50b5bc63 100644 --- a/modules/ui/cultures-editor.js +++ b/modules/ui/cultures-editor.js @@ -108,9 +108,7 @@ function editCultures() { lines += `
- - - + @@ -148,7 +146,7 @@ function editCultures() { body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseenter", ev => cultureHighlightOn(ev))); body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseleave", ev => cultureHighlightOff(ev))); body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectCultureOnLineClick)); - body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", cultureChangeColor)); + body.querySelectorAll("fill-box").forEach(el => el.addEventListener("click", cultureChangeColor)); body.querySelectorAll("div > input.cultureName").forEach(el => el.addEventListener("input", cultureChangeName)); body.querySelectorAll("div > span.icon-cw").forEach(el => el.addEventListener("click", cultureRegenerateName)); body.querySelectorAll("div > input.statePower").forEach(el => el.addEventListener("input", cultureChangeExpansionism)); @@ -248,16 +246,16 @@ function editCultures() { function cultureChangeColor() { const el = this; const currentFill = el.getAttribute("fill"); - const culture = +el.parentNode.parentNode.dataset.id; + const culture = +el.parentNode.dataset.id; - const callback = function (fill) { - el.setAttribute("fill", fill); - pack.cultures[culture].color = fill; + const callback = newFill => { + el.fill = newFill; + pack.cultures[culture].color = newFill; cults .select("#culture" + culture) - .attr("fill", fill) - .attr("stroke", fill); - debug.select("#cultureCenter" + culture).attr("fill", fill); + .attr("fill", newFill) + .attr("stroke", newFill); + debug.select("#cultureCenter" + culture).attr("fill", newFill); }; openPicker(currentFill, callback); diff --git a/modules/ui/diplomacy-editor.js b/modules/ui/diplomacy-editor.js index 657f6b73..bed60a3b 100644 --- a/modules/ui/diplomacy-editor.js +++ b/modules/ui/diplomacy-editor.js @@ -101,10 +101,8 @@ function editDiplomacy() { lines += `
${name}
-
- - - +
+ ${relation}
`; @@ -195,9 +193,7 @@ function editDiplomacy() { ([relation, {color, inText, tip}]) => `
` ) diff --git a/modules/ui/military-overview.js b/modules/ui/military-overview.js index 38b25a41..0d244314 100644 --- a/modules/ui/military-overview.js +++ b/modules/ui/military-overview.js @@ -75,12 +75,9 @@ function overviewMilitary() { const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" "); const lineData = options.military.map(u => `
${getForces(u)}
`).join(" "); - lines += `
- + lines += `
+ ${lineData}
${si(total)}
diff --git a/modules/ui/provinces-editor.js b/modules/ui/provinces-editor.js index 7983d430..045505ee 100644 --- a/modules/ui/provinces-editor.js +++ b/modules/ui/provinces-editor.js @@ -44,7 +44,8 @@ function editProvinces() { cl = el.classList, line = el.parentNode, p = +line.dataset.id; - if (cl.contains("fillRect")) changeFill(el); + + if (el.tagName === "FILL-BOX") changeFill(el); else if (cl.contains("name")) editProvinceName(p); else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]); else if (cl.contains("icon-star-empty")) capitalZoomIn(p); @@ -133,9 +134,7 @@ function editProvinces() { lines += `
- + @@ -215,14 +214,14 @@ function editProvinces() { function changeFill(el) { const currentFill = el.getAttribute("fill"); - const p = +el.parentNode.parentNode.dataset.id; + const p = +el.parentNode.dataset.id; - const callback = function (fill) { - el.setAttribute("fill", fill); - pack.provinces[p].color = fill; + const callback = newFill => { + el.fill = newFill; + pack.provinces[p].color = newFill; const g = provs.select("#provincesBody"); - g.select("#province" + p).attr("fill", fill); - g.select("#province-gap" + p).attr("stroke", fill); + g.select("#province" + p).attr("fill", newFill); + g.select("#province-gap" + p).attr("stroke", newFill); }; openPicker(currentFill, callback); diff --git a/modules/ui/regiments-overview.js b/modules/ui/regiments-overview.js index 67099d35..42a9f4e0 100644 --- a/modules/ui/regiments-overview.js +++ b/modules/ui/regiments-overview.js @@ -14,7 +14,9 @@ function overviewRegiments(state) { updateHeaders(); $("#regimentsOverview").dialog({ - title: "Regiments Overview", resizable: false, width: fitContent(), + title: "Regiments Overview", + resizable: false, + width: fitContent(), position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -31,11 +33,13 @@ function overviewRegiments(state) { header.querySelectorAll(".removable").forEach(el => el.remove()); const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html); for (const u of options.military) { - const label = capitalize(u.name.replace(/_/g, ' ')); + const label = capitalize(u.name.replace(/_/g, " ")); insert(`
${label} 
`); } - header.querySelectorAll(".removable").forEach(function(e) { - e.addEventListener("click", function() {sortLines(this);}); + header.querySelectorAll(".removable").forEach(function (e) { + e.addEventListener("click", function () { + sortLines(this); + }); }); } @@ -51,11 +55,13 @@ function overviewRegiments(state) { if (state !== -1 && s.i !== state) continue; // specific state is selected for (const r of s.military) { - const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name]||0}`).join(" "); - const lineData = options.military.map(u => `
${r.u[u.name]||0}
`).join(" "); + const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name] || 0}`).join(" "); + const lineData = options.military + .map(u => `
${r.u[u.name] || 0}
`) + .join(" "); lines += `
- + ${r.icon} @@ -70,12 +76,15 @@ function overviewRegiments(state) { lines += `
Regiments: ${regiments.length}
- ${options.military.map(u => `
${si(d3.sum(regiments.map(r => r.u[u.name]||0)))}
`).join(" ")} + ${options.military.map(u => `
${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}
`).join(" ")}
${si(d3.sum(regiments.map(r => r.a)))}
`; body.insertAdjacentHTML("beforeend", lines); - if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();} + if (body.dataset.type === "percentage") { + body.dataset.type = "absolute"; + togglePercentageMode(); + } applySorting(regimentsHeader); // add listeners @@ -87,7 +96,7 @@ function overviewRegiments(state) { const filter = document.getElementById("regimentsFilter"); filter.options.length = 0; // remove all options filter.options.add(new Option(`all`, -1, false, state === -1)); - const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name) ? 1 : -1); + const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1)); statesSorted.forEach(s => filter.options.add(new Option(s.name, s.i, false, s.i == state))); } @@ -108,19 +117,20 @@ function overviewRegiments(state) { if (body.dataset.type === "absolute") { body.dataset.type = "percentage"; const lines = body.querySelectorAll(":scope > div:not(.totalLine)"); - const array = Array.from(lines), cache = []; + const array = Array.from(lines), + cache = []; - const total = function(type) { + const total = function (type) { if (cache[type]) cache[type]; cache[type] = d3.sum(array.map(el => +el.dataset[type])); return cache[type]; - } + }; - lines.forEach(function(el) { - el.querySelectorAll("div").forEach(function(div) { + lines.forEach(function (el) { + el.querySelectorAll("div").forEach(function (div) { const type = div.dataset.type; if (type === "rate") return; - div.textContent = total(type) ? rn(+el.dataset[type] / total(type) * 100) + "%" : "0%"; + div.textContent = total(type) ? rn((+el.dataset[type] / total(type)) * 100) + "%" : "0%"; }); }); } else { @@ -145,15 +155,19 @@ function overviewRegiments(state) { function addRegimentOnClick() { const state = +regimentsFilter.value; - if (state === -1) {tip("Please select state from the list", false, "error"); return;} + if (state === -1) { + tip("Please select state from the list", false, "error"); + return; + } const point = d3.mouse(this); const cell = findCell(point[0], point[1]); - const x = pack.cells.p[cell][0], y = pack.cells.p[cell][1]; + const x = pack.cells.p[cell][0], + y = pack.cells.p[cell][1]; const military = pack.states[state].military; const i = military.length ? last(military).i + 1 : 0; const n = +(pack.cells.h[cell] < 20); // naval or land - const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, state, icon:"🛡️"}; + const reg = {a: 0, cell, i, n, u: {}, x, y, bx: x, by: y, state, icon: "🛡️"}; reg.name = Military.getName(reg, military); military.push(reg); Military.generateNote(reg, pack.states[state]); // add legend @@ -163,9 +177,9 @@ function overviewRegiments(state) { function downloadRegimentsData() { const units = options.military.map(u => u.name); - let data = "State,Id,Name,"+units.map(u => capitalize(u)).join(",")+",Total\n"; // headers + let data = "State,Id,Name," + units.map(u => capitalize(u)).join(",") + ",Total\n"; // headers - body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function(el) { + body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function (el) { data += el.dataset.state + ","; data += el.dataset.id + ","; data += el.dataset.name + ","; @@ -176,5 +190,4 @@ function overviewRegiments(state) { const name = getFileName("Regiments") + ".csv"; downloadFile(data, name); } - -} \ No newline at end of file +} diff --git a/modules/ui/religions-editor.js b/modules/ui/religions-editor.js index f635dd40..98a4d40d 100644 --- a/modules/ui/religions-editor.js +++ b/modules/ui/religions-editor.js @@ -79,7 +79,7 @@ function editReligions() { if (r.i) { lines += `
- + @@ -93,7 +93,9 @@ function editReligions() {
`; } else { // No religion (neutral) line - lines += `
+ lines += `
@@ -124,7 +126,7 @@ function editReligions() { body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseenter", ev => religionHighlightOn(ev))); body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseleave", ev => religionHighlightOff(ev))); body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectReligionOnLineClick)); - body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", religionChangeColor)); + body.querySelectorAll("fill-box").forEach(el => el.addEventListener("click", religionChangeColor)); body.querySelectorAll("div > input.religionName").forEach(el => el.addEventListener("input", religionChangeName)); body.querySelectorAll("div > select.religionType").forEach(el => el.addEventListener("change", religionChangeType)); body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm)); @@ -215,13 +217,13 @@ function editReligions() { function religionChangeColor() { const el = this; const currentFill = el.getAttribute("fill"); - const religion = +el.parentNode.parentNode.dataset.id; + const religion = +el.parentNode.dataset.id; - const callback = function (fill) { - el.setAttribute("fill", fill); - pack.religions[religion].color = fill; - relig.select("#religion" + religion).attr("fill", fill); - debug.select("#religionsCenter" + religion).attr("fill", fill); + const callback = newFill => { + el.fill = newFill; + pack.religions[religion].color = newFill; + relig.select("#religion" + religion).attr("fill", newFill); + debug.select("#religionsCenter" + religion).attr("fill", newFill); }; openPicker(currentFill, callback); @@ -459,7 +461,13 @@ function editReligions() { // prepare svg alertMessage.innerHTML = "
"; - const svg = d3.select("#alertMessage").insert("svg", "#religionInfo").attr("id", "hierarchy").attr("width", width).attr("height", height).style("text-anchor", "middle"); + const svg = d3 + .select("#alertMessage") + .insert("svg", "#religionInfo") + .attr("id", "hierarchy") + .attr("width", width) + .attr("height", height) + .style("text-anchor", "middle"); const graph = svg.append("g").attr("transform", `translate(10, -45)`); const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa"); const nodes = graph.append("g"); @@ -473,7 +481,24 @@ function editReligions() { .enter() .append("path") .attr("d", d => { - return "M" + d.source.x + "," + d.source.y + "C" + d.source.x + "," + (d.source.y * 3 + d.target.y) / 4 + " " + d.target.x + "," + (d.source.y * 2 + d.target.y) / 3 + " " + d.target.x + "," + d.target.y; + return ( + "M" + + d.source.x + + "," + + d.source.y + + "C" + + d.source.x + + "," + + (d.source.y * 3 + d.target.y) / 4 + + " " + + d.target.x + + "," + + (d.source.y * 2 + d.target.y) / 3 + + " " + + d.target.x + + "," + + d.target.y + ); }); const node = nodes @@ -578,7 +603,11 @@ function editReligions() { $("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); tip("Click on religion to select, drag the circle to change religion", true); - viewbox.style("cursor", "crosshair").on("click", selectReligionOnMapClick).call(d3.drag().on("start", dragReligionBrush)).on("touchmove mousemove", moveReligionBrush); + viewbox + .style("cursor", "crosshair") + .on("click", selectReligionOnMapClick) + .call(d3.drag().on("start", dragReligionBrush)) + .on("touchmove mousemove", moveReligionBrush); body.querySelector("div").classList.add("selected"); } diff --git a/modules/ui/states-editor.js b/modules/ui/states-editor.js index c1d8e978..d7909614 100644 --- a/modules/ui/states-editor.js +++ b/modules/ui/states-editor.js @@ -45,7 +45,7 @@ function editStates() { cl = el.classList, line = el.parentNode, state = +line.dataset.id; - if (cl.contains("fillRect")) stateChangeFill(el); + if (el.tagName === "FILL-BOX") stateChangeFill(el); else if (cl.contains("name")) editStateName(state); else if (cl.contains("coaIcon")) editEmblem("state", "stateCOA" + state, pack.states[state]); else if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state); @@ -102,7 +102,7 @@ function editStates() { // Neutral line lines += `
- + @@ -126,15 +126,10 @@ function editStates() { const capital = pack.burgs[s.capital].name; COArenderer.trigger("stateCOA" + s.i, s.coa); - lines += `
- + lines += `
+ @@ -149,9 +144,8 @@ function editStates() {
${si(population)}
- +
${s.cells}
@@ -237,18 +231,18 @@ function editStates() { function stateChangeFill(el) { const currentFill = el.getAttribute("fill"); - const state = +el.parentNode.parentNode.dataset.id; + const state = +el.parentNode.dataset.id; - const callback = function (fill) { - el.setAttribute("fill", fill); - pack.states[state].color = fill; - statesBody.select("#state" + state).attr("fill", fill); - statesBody.select("#state-gap" + state).attr("stroke", fill); - const halo = d3.color(fill) ? d3.color(fill).darker().hex() : "#666666"; + const callback = function (newFill) { + el.fill = newFill; + pack.states[state].color = newFill; + statesBody.select("#state" + state).attr("fill", newFill); + statesBody.select("#state-gap" + state).attr("stroke", newFill); + const halo = d3.color(newFill) ? d3.color(newFill).darker().hex() : "#666666"; statesHalo.select("#state-border" + state).attr("stroke", halo); // recolor regiments - const solidColor = fill[0] === "#" ? fill : "#999"; + const solidColor = newFill[0] === "#" ? newFill : "#999"; const darkerColor = d3.color(solidColor).darker().hex(); armies.select("#army" + state).attr("fill", solidColor); armies diff --git a/modules/ui/zones-editor.js b/modules/ui/zones-editor.js index 00ea9196..f011f47b 100644 --- a/modules/ui/zones-editor.js +++ b/modules/ui/zones-editor.js @@ -33,26 +33,12 @@ function editZones() { const el = ev.target, cl = el.classList, zone = el.parentNode.dataset.id; - if (cl.contains("culturePopulation")) { - changePopulation(zone); - return; - } - if (cl.contains("icon-trash-empty")) { - zoneRemove(zone); - return; - } - if (cl.contains("icon-eye")) { - toggleVisibility(el); - return; - } - if (cl.contains("icon-pin")) { - toggleFog(zone, cl); - return; - } - if (cl.contains("fillRect")) { - changeFill(el); - return; - } + if (el.tagName === "FILL-BOX") changeFill(el); + else if (cl.contains("culturePopulation")) changePopulation(zone); + else if (cl.contains("icon-trash-empty")) zoneRemove(zone); + else if (cl.contains("icon-eye")) toggleVisibility(el); + else if (cl.contains("icon-pin")) toggleFog(zone, cl); + if (customization) selectZone(el); }); @@ -79,8 +65,9 @@ function editZones() { const inactive = this.style.display === "none"; const focused = defs.select("#fog #focus" + this.id).size(); - lines += `
- + lines += `
+
${c.length}
@@ -275,9 +262,9 @@ function editZones() { function changeFill(el) { const fill = el.getAttribute("fill"); - const callback = function (fill) { - el.setAttribute("fill", fill); - document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute("fill", fill); + const callback = newFill => { + el.fill = newFill; + document.getElementById(el.parentNode.dataset.id).setAttribute("fill", newFill); }; openPicker(fill, callback); @@ -349,7 +336,7 @@ function editZones() { const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const line = `
- +
0