From 922c6e2431c78252206717c6236433fd2b79de75 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Sun, 26 Jun 2022 00:57:53 +0300 Subject: [PATCH] refactor(es modules): continue migration --- components/fill-box.js | 74 ------- index.html | 111 ++-------- modules/dynamic/editors/cultures-editor.js | 3 +- modules/dynamic/editors/religions-editor.js | 3 +- modules/dynamic/editors/states-editor.js | 5 +- modules/dynamic/hierarchy-tree.js | 5 +- modules/ui/battle-screen.js | 4 +- modules/ui/biomes-editor.js | 7 +- modules/ui/burg-editor.js | 1 + modules/ui/burgs-overview.js | 15 +- modules/ui/diplomacy-editor.js | 5 +- modules/ui/editors.js | 154 +------------- modules/ui/emblems-editor.js | 55 +++-- modules/ui/general.js | 215 -------------------- modules/ui/heightmap-editor.js | 1 + modules/ui/ice-editor.js | 1 + modules/ui/labels-editor.js | 3 +- modules/ui/markers-editor.js | 1 + modules/ui/markers-overview.js | 15 +- modules/ui/military-overview.js | 6 +- modules/ui/options.js | 5 +- modules/ui/provinces-editor.js | 7 +- modules/ui/regiment-editor.js | 1 + modules/ui/regiments-overview.js | 3 +- modules/ui/relief-editor.js | 1 + modules/ui/rivers-creator.js | 1 + modules/ui/rivers-editor.js | 1 + modules/ui/rivers-overview.js | 2 +- modules/ui/routes-editor.js | 5 +- modules/ui/submap.js | 1 + modules/ui/tools.js | 1 + modules/ui/zones-editor.js | 5 +- src/components/fill-box.ts | 66 ++++++ src/components/index.ts | 1 + src/main.ts | 2 + src/modules/legend.ts | 110 ++++++++++ src/scripts/events.ts | 185 +++++++++++++++++ src/scripts/listeners.ts | 2 + src/scripts/tooltips.ts | 57 ++++++ 39 files changed, 551 insertions(+), 589 deletions(-) delete mode 100644 components/fill-box.js create mode 100644 src/components/fill-box.ts create mode 100644 src/components/index.ts create mode 100644 src/modules/legend.ts create mode 100644 src/scripts/tooltips.ts diff --git a/components/fill-box.js b/components/fill-box.js deleted file mode 100644 index b4d075c3..00000000 --- a/components/fill-box.js +++ /dev/null @@ -1,74 +0,0 @@ -{ - const style = /* css */ ` - fill-box:not([disabled]) { - cursor: pointer; - } - - fill-box > svg { - 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 = /* html */ ` - - - - `; - - 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); - this.querySelector("svg")?.setAttribute("height", 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"; - } - } - - // cannot use Shadow DOM here as need an access to svg hatches - customElements.define("fill-box", FillBox); -} diff --git a/index.html b/index.html index bd389c9d..7cd238a1 100644 --- a/index.html +++ b/index.html @@ -3248,7 +3248,11 @@ - + - + @@ -4346,23 +4309,8 @@ - + `; } @@ -473,7 +472,7 @@ export function overviewBurgs() { $("#alert").dialog({ title: "Burgs bubble chart", - width: fitContent(), + width: "fit-content", position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, buttons: {}, close: () => (alertMessage.innerHTML = "") diff --git a/modules/ui/diplomacy-editor.js b/modules/ui/diplomacy-editor.js index 0839ad69..a0d72333 100644 --- a/modules/ui/diplomacy-editor.js +++ b/modules/ui/diplomacy-editor.js @@ -1,5 +1,6 @@ import {restoreDefaultEvents} from "/src/scripts/events"; import {findCell} from "/src/utils/graphUtils"; +import {clearMainTip} from "/src/scripts/tooltips"; export function editDiplomacy() { if (customization) return; @@ -65,7 +66,7 @@ export function editDiplomacy() { $("#diplomacyEditor").dialog({ title: "Diplomacy Editor", resizable: false, - width: fitContent(), + width: "fit-content", close: closeDiplomacyEditor, position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -236,7 +237,7 @@ export function editDiplomacy() { alertMessage.innerHTML = /* html */ `
${header} ${options} ${footer}
`; $("#alert").dialog({ - width: fitContent(), + width: "fit-content", title: `Change relations`, buttons: { Apply: function () { diff --git a/modules/ui/editors.js b/modules/ui/editors.js index cabbe975..dc7dcd57 100644 --- a/modules/ui/editors.js +++ b/modules/ui/editors.js @@ -2,35 +2,8 @@ import {restoreDefaultEvents} from "/src/scripts/events"; import {findCell} from "/src/utils/graphUtils"; import {byId} from "/src/utils/shorthands"; -// on viewbox click event - run function based on target -function clicked() { - const el = d3.event.target; - if (!el || !el.parentElement || !el.parentElement.parentElement) return; - const parent = el.parentElement; - const grand = parent.parentElement; - const great = grand.parentElement; - const p = d3.mouse(this); - const i = findCell(p[0], p[1]); - - if (grand.id === "emblems") editEmblem(); - else if (parent.id === "rivers") editRiver(el.id); - else if (grand.id === "routes") editRoute(); - else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel(); - else if (grand.id === "burgLabels") editBurg(); - else if (grand.id === "burgIcons") editBurg(); - else if (parent.id === "ice") editIce(); - else if (parent.id === "terrain") editReliefIcon(); - else if (grand.id === "markers" || great.id === "markers") editMarker(); - else if (grand.id === "coastline") editCoastline(); - else if (great.id === "armies") editRegiment(); - else if (pack.cells.t[i] === 1) { - const node = byId("island_" + pack.cells.f[i]); - editCoastline(node); - } else if (grand.id === "lakes") editLake(); -} - // clear elSelected variable -function unselect() { +export function unselect() { restoreDefaultEvents(); if (!elSelected) return; elSelected.call(d3.drag().on("drag", null)).attr("class", null); @@ -40,7 +13,7 @@ function unselect() { } // close all dialogs except stated -function closeDialogs(except = "#except") { +export function closeDialogs(except = "#except") { try { $(".dialog:visible") .not(except) @@ -51,7 +24,7 @@ function closeDialogs(except = "#except") { } // move brush radius circle -function moveCircle(x, y, r = 20) { +export function moveCircle(x, y, r = 20) { let circle = byId("brushCircle"); if (!circle) { const html = /* html */ ``; @@ -63,15 +36,10 @@ function moveCircle(x, y, r = 20) { } } -function removeCircle() { +export function removeCircle() { if (byId("brushCircle")) byId("brushCircle").remove(); } -// get browser-defined fit-content -function fitContent() { - return !window.chrome ? "-moz-max-content" : "fit-content"; -} - // apply sorting behaviour for lines on Editor header click document.querySelectorAll(".sortable").forEach(function (event) { event.on("click", function () { @@ -348,118 +316,6 @@ function getMFCGlink(burg) { return url.toString(); } -// draw legend box -function drawLegend(name, data) { - legend.selectAll("*").remove(); // fully redraw every time - legend.attr("data", data.join("|")); // store data - - const itemsInCol = +styleLegendColItems.value; - const fontSize = +legend.attr("font-size"); - const backClr = styleLegendBack.value; - const opacity = +styleLegendOpacity.value; - - const lineHeight = Math.round(fontSize * 1.7); - const colorBoxSize = Math.round(fontSize / 1.7); - const colOffset = fontSize; - const vOffset = fontSize / 2; - - // append items - const boxes = legend.append("g").attr("stroke-width", 0.5).attr("stroke", "#111111").attr("stroke-dasharray", "none"); - const labels = legend.append("g").attr("fill", "#000000").attr("stroke", "none"); - - const columns = Math.ceil(data.length / itemsInCol); - for (let column = 0, i = 0; column < columns; column++) { - const linesInColumn = Math.ceil(data.length / columns); - const offset = column ? colOffset * 2 + legend.node().getBBox().width : colOffset; - - for (let l = 0; l < linesInColumn && data[i]; l++, i++) { - boxes - .append("rect") - .attr("fill", data[i][1]) - .attr("x", offset) - .attr("y", lineHeight + l * lineHeight + vOffset) - .attr("width", colorBoxSize) - .attr("height", colorBoxSize); - - labels - .append("text") - .text(data[i][2]) - .attr("x", offset + colorBoxSize * 1.6) - .attr("y", fontSize / 1.6 + lineHeight + l * lineHeight + vOffset); - } - } - - // append label - const offset = colOffset + legend.node().getBBox().width / 2; - labels - .append("text") - .attr("text-anchor", "middle") - .attr("font-weight", "bold") - .attr("font-size", "1.2em") - .attr("id", "legendLabel") - .text(name) - .attr("x", offset) - .attr("y", fontSize * 1.1 + vOffset / 2); - - // append box - const bbox = legend.node().getBBox(); - const width = bbox.width + colOffset * 2; - const height = bbox.height + colOffset / 2 + vOffset; - - legend - .insert("rect", ":first-child") - .attr("id", "legendBox") - .attr("x", 0) - .attr("y", 0) - .attr("width", width) - .attr("height", height) - .attr("fill", backClr) - .attr("fill-opacity", opacity); - - fitLegendBox(); -} - -// fit Legend box to canvas size -function fitLegendBox() { - if (!legend.selectAll("*").size()) return; - const px = isNaN(+legend.attr("data-x")) ? 99 : legend.attr("data-x") / 100; - const py = isNaN(+legend.attr("data-y")) ? 93 : legend.attr("data-y") / 100; - const bbox = legend.node().getBBox(); - const x = rn(svgWidth * px - bbox.width), - y = rn(svgHeight * py - bbox.height); - legend.attr("transform", `translate(${x},${y})`); -} - -// draw legend with the same data, but using different settings -function redrawLegend() { - if (!legend.select("rect").size()) return; - const name = legend.select("#legendLabel").text(); - const data = legend - .attr("data") - .split("|") - .map(l => l.split(",")); - drawLegend(name, data); -} - -function dragLegendBox() { - const tr = parseTransform(this.getAttribute("transform")); - const x = +tr[0] - d3.event.x, - y = +tr[1] - d3.event.y; - const bbox = legend.node().getBBox(); - - d3.event.on("drag", function () { - const px = rn(((x + d3.event.x + bbox.width) / svgWidth) * 100, 2); - const py = rn(((y + d3.event.y + bbox.height) / svgHeight) * 100, 2); - const transform = `translate(${x + d3.event.x},${y + d3.event.y})`; - legend.attr("transform", transform).attr("data-x", px).attr("data-y", py); - }); -} - -function clearLegend() { - legend.selectAll("*").remove(); - legend.attr("data", null); -} - // draw color (fill) picker function createPicker() { const pos = () => tip("Drag to change the picker position"); @@ -1097,7 +953,7 @@ function selectIcon(initial, callback) { }; $("#iconSelector").dialog({ - width: fitContent(), + width: "fit-content", title: "Select Icon", buttons: { Apply: function () { diff --git a/modules/ui/emblems-editor.js b/modules/ui/emblems-editor.js index 3ab3335a..e8928b16 100644 --- a/modules/ui/emblems-editor.js +++ b/modules/ui/emblems-editor.js @@ -1,5 +1,6 @@ -"use strict"; -function editEmblem(type, id, el) { +import {clearMainTip} from "/src/scripts/tooltips"; + +export function editEmblem(type, id, el) { if (customization) return; if (!id && d3.event) defineEmblemData(d3.event); @@ -44,7 +45,12 @@ function editEmblem(type, id, el) { function defineEmblemData(e) { const parent = e.target.parentNode; - const [g, t] = parent.id === "burgEmblems" ? [pack.burgs, "burg"] : parent.id === "provinceEmblems" ? [pack.provinces, "province"] : [pack.states, "state"]; + const [g, t] = + parent.id === "burgEmblems" + ? [pack.burgs, "burg"] + : parent.id === "provinceEmblems" + ? [pack.provinces, "province"] + : [pack.states, "state"]; const i = +e.target.dataset.i; type = t; id = type + "COA" + i; @@ -88,8 +94,12 @@ function editEmblem(type, id, el) { emblemBurgs.options.length = 0; emblemBurgs.options.add(new Option("", 0, false, !burg)); - const burgList = validBurgs.filter(burg => (province ? pack.cells.province[burg.cell] === province : burg.state === state)); - burgList.forEach(b => emblemBurgs.options.add(new Option(b.capital ? "👑 " + b.name : b.name, b.i, false, b.i === burg))); + const burgList = validBurgs.filter(burg => + province ? pack.cells.province[burg.cell] === province : burg.state === state + ); + burgList.forEach(b => + emblemBurgs.options.add(new Option(b.capital ? "👑 " + b.name : b.name, b.i, false, b.i === burg)) + ); emblemBurgs.options[0].disabled = true; COArenderer.trigger(id, el.coa); @@ -224,12 +234,18 @@ function editEmblem(type, id, el) { } function upload(type) { - const input = type === "image" ? document.getElementById("emblemImageToLoad") : document.getElementById("emblemSVGToLoad"); + const input = + type === "image" ? document.getElementById("emblemImageToLoad") : document.getElementById("emblemSVGToLoad"); const file = input.files[0]; 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); + 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; } @@ -257,7 +273,11 @@ function editEmblem(type, id, el) { 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"); + tip( + "The file should be prepated for load to FMG. Please use Armoria or other relevant tools", + false, + "error" + ); return; } @@ -351,7 +371,9 @@ function editEmblem(type, id, el) { validStates .map(state => { const el = document.getElementById("stateCOA" + state.i); - return `
${state.fullName}
${getSVG(el, 200)}
`; + return `
${ + state.fullName + }
${getSVG(el, 200)}
`; }) .join("") + ``; @@ -362,13 +384,14 @@ function editEmblem(type, id, el) { const figures = stateProvinces .map(province => { const el = document.getElementById("provinceCOA" + province.i); - return `
${province.fullName}
${getSVG( - el, - 200 - )}
`; + return `
${ + province.fullName + }
${getSVG(el, 200)}
`; }) .join(""); - return stateProvinces.length ? `
${back}

${state.fullName} provinces

${figures}
` : ""; + return stateProvinces.length + ? `
${back}

${state.fullName} provinces

${figures}
` + : ""; }) .join(""); @@ -385,7 +408,9 @@ function editEmblem(type, id, el) { return `
${burg.name}
${getSVG(el, 200)}
`; }) .join(""); - return provinceBurgs.length ? `
${back}

${province.fullName} burgs

${provinceBurgFigures}
` : ""; + return provinceBurgs.length + ? `
${back}

${province.fullName} burgs

${provinceBurgFigures}
` + : ""; }) .join(""); diff --git a/modules/ui/general.js b/modules/ui/general.js index c7bc47dd..383b443d 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -13,221 +13,6 @@ if (location.hostname && location.hostname !== "localhost" && location.hostname window.onbeforeunload = () => "Are you sure you want to navigate away?"; } -// Tooltips -const tooltip = document.getElementById("tooltip"); - -// show tip for non-svg elemets with data-tip -document.getElementById("dialogs").addEventListener("mousemove", showDataTip); -document.getElementById("optionsContainer").addEventListener("mousemove", showDataTip); -document.getElementById("exitCustomization").addEventListener("mousemove", showDataTip); - -const tipBackgroundMap = { - info: "linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)", - success: "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)", - warn: "linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)", - error: "linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)" -}; - -function tip(tip = "Tip is undefined", main = false, type = "info", time = 0) { - tooltip.innerHTML = tip; - tooltip.style.background = tipBackgroundMap[type]; - - if (main) { - tooltip.dataset.main = tip; - tooltip.dataset.color = tooltip.style.background; - } - if (time) setTimeout(clearMainTip, time); -} - -function showMainTip() { - tooltip.style.background = tooltip.dataset.color; - tooltip.innerHTML = tooltip.dataset.main; -} - -function clearMainTip() { - tooltip.dataset.color = ""; - tooltip.dataset.main = ""; - tooltip.innerHTML = ""; -} - -// show tip at the bottom of the screen, consider possible translation -function showDataTip(event) { - if (!event.target) return; - - let dataTip = event.target.dataset.tip; - if (!dataTip && event.target.parentNode.dataset.tip) dataTip = event.target.parentNode.dataset.tip; - if (!dataTip) return; - - const shortcut = event.target.dataset.shortcut; - if (shortcut && !MOBILE) dataTip += `. Shortcut: ${shortcut}`; - - //const tooltip = lang === "en" ? dataTip : translate(e.target.dataset.t || e.target.parentNode.dataset.t, dataTip); - tip(dataTip); -} - -function showElementLockTip(event) { - const locked = event?.target?.classList?.contains("icon-lock"); - if (locked) { - tip("Click to unlock the element and allow it to be changed by regeneration tools"); - } else { - tip("Click to lock the element and prevent changes to it by regeneration tools"); - } -} - -const onMouseMove = debounce(handleMouseMove, 100); -function handleMouseMove() { - const point = d3.mouse(this); - const i = findCell(point[0], point[1]); // pack cell id - if (i === undefined) return; - - showNotes(d3.event); - const gridCell = findGridCell(point[0], point[1], grid); - if (tooltip.dataset.main) showMainTip(); - else showMapTooltip(point, d3.event, i, gridCell); - if (cellInfo?.offsetParent) updateCellInfo(point, i, gridCell); -} - -// show note box on hover (if any) -function showNotes(e) { - if (notesEditor?.offsetParent) return; - let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id; - if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id; - else if (e.target.parentNode.parentNode.id === "burgIcons") id = "burg" + e.target.dataset.id; - - const note = notes.find(note => note.id === id); - if (note !== undefined && note.legend !== "") { - document.getElementById("notes").style.display = "block"; - document.getElementById("notesHeader").innerHTML = note.name; - document.getElementById("notesBody").innerHTML = note.legend; - } else if (!options.pinNotes && !markerEditor?.offsetParent) { - document.getElementById("notes").style.display = "none"; - document.getElementById("notesHeader").innerHTML = ""; - document.getElementById("notesBody").innerHTML = ""; - } -} - -// show viewbox tooltip if main tooltip is blank -function showMapTooltip(point, e, i, g) { - tip(""); // clear tip - const path = e.composedPath ? e.composedPath() : getComposedPath(e.target); // apply polyfill - if (!path[path.length - 8]) return; - const group = path[path.length - 7].id; - const subgroup = path[path.length - 8].id; - const land = pack.cells.h[i] >= 20; - - // specific elements - if (group === "armies") return tip(e.target.parentNode.dataset.name + ". Click to edit"); - - if (group === "emblems" && e.target.tagName === "use") { - const parent = e.target.parentNode; - const [g, type] = - parent.id === "burgEmblems" - ? [pack.burgs, "burg"] - : parent.id === "provinceEmblems" - ? [pack.provinces, "province"] - : [pack.states, "state"]; - const i = +e.target.dataset.i; - if (event.shiftKey) highlightEmblemElement(type, g[i]); - - d3.select(e.target).raise(); - d3.select(parent).raise(); - - const name = g[i].fullName || g[i].name; - tip(`${name} ${type} emblem. Click to edit. Hold Shift to show associated area or place`); - return; - } - - if (group === "rivers") { - const river = +e.target.id.slice(5); - const r = pack.rivers.find(r => r.i === river); - const name = r ? r.name + " " + r.type : ""; - tip(name + ". Click to edit"); - if (riversOverview?.offsetParent) highlightEditorLine(riversOverview, river, 5000); - return; - } - - if (group === "routes") return tip("Click to edit the Route"); - - if (group === "terrain") return tip("Click to edit the Relief Icon"); - - if (subgroup === "burgLabels" || subgroup === "burgIcons") { - const burg = +path[path.length - 10].dataset.id; - const b = pack.burgs[burg]; - const population = si(b.population * populationRate * urbanization); - tip(`${b.name}. Population: ${population}. Click to edit`); - if (burgsOverview?.offsetParent) highlightEditorLine(burgsOverview, burg, 5000); - return; - } - if (group === "labels") return tip("Click to edit the Label"); - - if (group === "markers") return tip("Click to edit the Marker and pin the marker note"); - - if (group === "ruler") { - const tag = e.target.tagName; - const className = e.target.getAttribute("class"); - if (tag === "circle" && className === "edge") - return tip("Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point"); - if (tag === "circle" && className === "control") - return tip("Drag to adjust. Hold Shift and drag to keep axial direction. Click to remove the point"); - if (tag === "circle") return tip("Drag to adjust the measurer"); - if (tag === "polyline") return tip("Click on drag to add a control point"); - if (tag === "path") return tip("Drag to move the measurer"); - if (tag === "text") return tip("Drag to move, click to remove the measurer"); - } - - if (subgroup === "burgIcons") return tip("Click to edit the Burg"); - - if (subgroup === "burgLabels") return tip("Click to edit the Burg"); - - if (group === "lakes" && !land) { - const lakeId = +e.target.dataset.f; - const name = pack.features[lakeId]?.name; - const fullName = subgroup === "freshwater" ? name : name + " " + subgroup; - tip(`${fullName} lake. Click to edit`); - return; - } - if (group === "coastline") return tip("Click to edit the coastline"); - - if (group === "zones") { - const zone = path[path.length - 8]; - tip(zone.dataset.description); - if (zonesEditor?.offsetParent) highlightEditorLine(zonesEditor, zone.id, 5000); - return; - } - - if (group === "ice") return tip("Click to edit the Ice"); - - // covering elements - if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: " + getFriendlyPrecipitation(i)); - else if (layerIsOn("togglePopulation")) tip(getPopulationTip(i)); - else if (layerIsOn("toggleTemp")) tip("Temperature: " + convertTemperature(grid.cells.temp[g])); - else if (layerIsOn("toggleBiomes") && pack.cells.biome[i]) { - const biome = pack.cells.biome[i]; - tip("Biome: " + biomesData.name[biome]); - if (biomesEditor?.offsetParent) highlightEditorLine(biomesEditor, biome); - } else if (layerIsOn("toggleReligions") && pack.cells.religion[i]) { - const religion = pack.cells.religion[i]; - const r = pack.religions[religion]; - const type = r.type === "Cult" || r.type == "Heresy" ? r.type : r.type + " religion"; - tip(type + ": " + r.name); - if (religionsEditor?.offsetParent) highlightEditorLine(religionsEditor, religion); - } else if (pack.cells.state[i] && (layerIsOn("toggleProvinces") || layerIsOn("toggleStates"))) { - const state = pack.cells.state[i]; - const stateName = pack.states[state].fullName; - const province = pack.cells.province[i]; - const prov = province ? pack.provinces[province].fullName + ", " : ""; - tip(prov + stateName); - if (document.getElementById("statesEditor")?.offsetParent) highlightEditorLine(statesEditor, state); - if (document.getElementById("diplomacyEditor")?.offsetParent) highlightEditorLine(diplomacyEditor, state); - if (document.getElementById("militaryOverview")?.offsetParent) highlightEditorLine(militaryOverview, state); - if (document.getElementById("provincesEditor")?.offsetParent) highlightEditorLine(provincesEditor, province); - } else if (layerIsOn("toggleCultures") && pack.cells.culture[i]) { - const culture = pack.cells.culture[i]; - tip("Culture: " + pack.cultures[culture].name); - if (document.getElementById("culturesEditor")?.offsetParent) highlightEditorLine(culturesEditor, culture); - } else if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point)); -} - function highlightEditorLine(editor, id, timeout = 10000) { Array.from(editor.getElementsByClassName("states hovered")).forEach(el => el.classList.remove("hovered")); // clear all hovered const hovered = Array.from(editor.querySelectorAll("div")).find(el => el.dataset.id == id); diff --git a/modules/ui/heightmap-editor.js b/modules/ui/heightmap-editor.js index e83f4f6f..5c6c32da 100644 --- a/modules/ui/heightmap-editor.js +++ b/modules/ui/heightmap-editor.js @@ -1,6 +1,7 @@ import {restoreDefaultEvents} from "/src/scripts/events"; import {findGridCell, findGridAll, findCell, getPackPolygon, getGridPolygon} from "/src/utils/graphUtils"; import {last, createTypedArray} from "/src/utils/arrayUtils"; +import {showMainTip, clearMainTip} from "/src/scripts/tooltips"; import {byId} from "/src/utils/shorthands"; export function editHeightmap(options) { diff --git a/modules/ui/ice-editor.js b/modules/ui/ice-editor.js index 6ae9a3eb..3b1175c2 100644 --- a/modules/ui/ice-editor.js +++ b/modules/ui/ice-editor.js @@ -1,4 +1,5 @@ import {findGridCell, getGridPolygon} from "/src/utils/graphUtils"; +import {clearMainTip} from "/src/scripts/tooltips"; export function editIce() { if (customization) return; diff --git a/modules/ui/labels-editor.js b/modules/ui/labels-editor.js index 5143c459..e1fee166 100644 --- a/modules/ui/labels-editor.js +++ b/modules/ui/labels-editor.js @@ -1,4 +1,5 @@ import {findCell} from "/src/utils/graphUtils"; +import {showMainTip} from "/src/scripts/tooltips"; export function editLabel() { if (customization) return; @@ -14,7 +15,7 @@ export function editLabel() { $("#labelEditor").dialog({ title: "Edit Label", resizable: false, - width: fitContent(), + width: "fit-content", position: {my: "center top+10", at: "bottom", of: text, collision: "fit"}, close: closeLabelEditor }); diff --git a/modules/ui/markers-editor.js b/modules/ui/markers-editor.js index 24a1c2ce..dcfe2c89 100644 --- a/modules/ui/markers-editor.js +++ b/modules/ui/markers-editor.js @@ -1,5 +1,6 @@ import {restoreDefaultEvents} from "/src/scripts/events"; import {findCell} from "/src/utils/graphUtils"; +import {clearMainTip} from "/src/scripts/tooltips"; export function editMarker(markerI) { if (customization) return; diff --git a/modules/ui/markers-overview.js b/modules/ui/markers-overview.js index dfe1b638..b799a9d3 100644 --- a/modules/ui/markers-overview.js +++ b/modules/ui/markers-overview.js @@ -1,4 +1,5 @@ import {restoreDefaultEvents} from "/src/scripts/events"; +import {clearMainTip} from "/src/scripts/tooltips"; export function overviewMarkers() { if (customization) return; @@ -21,7 +22,7 @@ export function overviewMarkers() { $("#markersOverview").dialog({ title: "Markers Overview", resizable: false, - width: fitContent(), + width: "fit-content", close: close, position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -51,16 +52,14 @@ export function overviewMarkers() { function addLines() { const lines = pack.markers .map(({i, type, icon, pinned, lock}) => { - return `
+ return /* html */ `
${icon} ${type}
- - + +
`; }) diff --git a/modules/ui/military-overview.js b/modules/ui/military-overview.js index c55e1534..e0b73294 100644 --- a/modules/ui/military-overview.js +++ b/modules/ui/military-overview.js @@ -17,7 +17,7 @@ function overviewMilitary() { $("#militaryOverview").dialog({ title: "Military Overview", resizable: false, - width: fitContent(), + width: "fit-content", position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -244,7 +244,7 @@ function overviewMilitary() { $("#militaryOptions").dialog({ title: "Edit Military Units", resizable: false, - width: fitContent(), + width: "fit-content", position: {my: "center", at: "center", of: "svg"}, buttons: { Apply: applyMilitaryOptions, @@ -382,7 +382,7 @@ function overviewMilitary() { `; $("#alert").dialog({ - width: fitContent(), + width: "fit-content", title: `Limit unit`, buttons: { Invert: function () { diff --git a/modules/ui/options.js b/modules/ui/options.js index 88319c86..c5ce2ce2 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -1,7 +1,8 @@ import {applyOption} from "./general"; import {last} from "/src/utils/arrayUtils"; -import {byId, stored} from "/src/utils/shorthands"; import {lock, locked} from "/src/scripts/options/lock"; +import {clearMainTip} from "/src/scripts/tooltips"; +import {byId, stored} from "/src/utils/shorthands"; $("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"}); $("#exitCustomization").draggable({handle: "div"}); @@ -963,7 +964,7 @@ function toggle3dOptions() { $("#options3d").dialog({ title: "3D mode settings", resizable: false, - width: fitContent(), + width: "fit-content", position: {my: "right top", at: "right-30 top+10", of: "svg", collision: "fit"} }); diff --git a/modules/ui/provinces-editor.js b/modules/ui/provinces-editor.js index 4953cefb..09a05425 100644 --- a/modules/ui/provinces-editor.js +++ b/modules/ui/provinces-editor.js @@ -1,6 +1,7 @@ import {restoreDefaultEvents} from "/src/scripts/events"; import {findAll, findCell, getPackPolygon, isLand} from "/src/utils/graphUtils"; import {unique} from "/src/utils/arrayUtils"; +import {showMainTip, clearMainTip} from "/src/scripts/tooltips"; export function editProvinces() { if (customization) return; @@ -20,7 +21,7 @@ export function editProvinces() { $("#provincesEditor").dialog({ title: "Provinces Editor", resizable: false, - width: fitContent(), + width: "fit-content", close: closeProvincesEditor, position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -197,7 +198,7 @@ export function editProvinces() { togglePercentageMode(); } applySorting(provincesHeader); - $("#provincesEditor").dialog({width: fitContent()}); + $("#provincesEditor").dialog({width: "fit-content"}); } function getCapitalOptions(burgs, capital) { @@ -748,7 +749,7 @@ export function editProvinces() { $("#alert").dialog({ title: "Provinces chart", - width: fitContent(), + width: "fit-content", position: {my: "left bottom", at: "left+10 bottom-10", of: "svg"}, buttons: {}, close: () => { diff --git a/modules/ui/regiment-editor.js b/modules/ui/regiment-editor.js index e78c11ff..86bda2a7 100644 --- a/modules/ui/regiment-editor.js +++ b/modules/ui/regiment-editor.js @@ -1,6 +1,7 @@ import {restoreDefaultEvents} from "/src/scripts/events"; import {findCell} from "/src/utils/graphUtils"; import {last} from "/src/utils/arrayUtils"; +import {clearMainTip} from "/src/scripts/tooltips"; export function editRegiment(selector) { if (customization) return; diff --git a/modules/ui/regiments-overview.js b/modules/ui/regiments-overview.js index a45dee58..1d19d814 100644 --- a/modules/ui/regiments-overview.js +++ b/modules/ui/regiments-overview.js @@ -1,5 +1,6 @@ import {findCell} from "/src/utils/graphUtils"; import {last} from "/src/utils/arrayUtils"; +import {clearMainTip} from "/src/scripts/tooltips"; export function overviewRegiments(state) { if (customization) return; @@ -18,7 +19,7 @@ export function overviewRegiments(state) { $("#regimentsOverview").dialog({ title: "Regiments Overview", resizable: false, - width: fitContent(), + width: "fit-content", position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); diff --git a/modules/ui/relief-editor.js b/modules/ui/relief-editor.js index 258d8114..3987a94a 100644 --- a/modules/ui/relief-editor.js +++ b/modules/ui/relief-editor.js @@ -1,5 +1,6 @@ import {restoreDefaultEvents} from "/src/scripts/events"; import {findCell} from "/src/utils/graphUtils"; +import {showMainTip, clearMainTip} from "/src/scripts/tooltips"; export function editReliefIcon() { if (customization) return; diff --git a/modules/ui/rivers-creator.js b/modules/ui/rivers-creator.js index bd26deed..f6c407c0 100644 --- a/modules/ui/rivers-creator.js +++ b/modules/ui/rivers-creator.js @@ -1,6 +1,7 @@ import {restoreDefaultEvents} from "/src/scripts/events"; import {getPackPolygon, findCell} from "/src/utils/graphUtils"; import {last} from "/src/utils/arrayUtils"; +import {clearMainTip} from "/src/scripts/tooltips"; export function createRiver() { if (customization) return; diff --git a/modules/ui/rivers-editor.js b/modules/ui/rivers-editor.js index de60f291..11443a88 100644 --- a/modules/ui/rivers-editor.js +++ b/modules/ui/rivers-editor.js @@ -1,4 +1,5 @@ import {findCell, getPackPolygon} from "/src/utils/graphUtils"; +import {clearMainTip} from "/src/scripts/tooltips"; export function editRiver(id) { if (customization) return; diff --git a/modules/ui/rivers-overview.js b/modules/ui/rivers-overview.js index 602a8597..88169559 100644 --- a/modules/ui/rivers-overview.js +++ b/modules/ui/rivers-overview.js @@ -14,7 +14,7 @@ function overviewRivers() { $("#riversOverview").dialog({ title: "Rivers Overview", resizable: false, - width: fitContent(), + width: "fit-content", position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); diff --git a/modules/ui/routes-editor.js b/modules/ui/routes-editor.js index 1517ad50..6d2a722b 100644 --- a/modules/ui/routes-editor.js +++ b/modules/ui/routes-editor.js @@ -1,5 +1,6 @@ -"use strict"; -function editRoute(onClick) { +import {showMainTip, clearMainTip} from "/src/scripts/tooltips"; + +export function editRoute(onClick) { if (customization) return; if (!onClick && elSelected && d3.event.target.id === elSelected.attr("id")) return; closeDialogs(".stable"); diff --git a/modules/ui/submap.js b/modules/ui/submap.js index ef88e580..becd666f 100644 --- a/modules/ui/submap.js +++ b/modules/ui/submap.js @@ -1,4 +1,5 @@ import {byId} from "/src/utils/shorthands"; +import {clearMainTip} from "/src/scripts/tooltips"; window.UISubmap = (function () { byId("submapPointsInput").addEventListener("input", function () { diff --git a/modules/ui/tools.js b/modules/ui/tools.js index 3578ee44..534bb94a 100644 --- a/modules/ui/tools.js +++ b/modules/ui/tools.js @@ -1,6 +1,7 @@ import {restoreDefaultEvents} from "/src/scripts/events"; import {findCell} from "/src/utils/graphUtils"; import {last} from "/src/utils/arrayUtils"; +import {clearMainTip} from "/src/scripts/tooltips"; // module to control the Tools options (click to edit, to re-geenerate, tp add) diff --git a/modules/ui/zones-editor.js b/modules/ui/zones-editor.js index fbdcd276..1339c948 100644 --- a/modules/ui/zones-editor.js +++ b/modules/ui/zones-editor.js @@ -1,6 +1,7 @@ import {restoreDefaultEvents} from "/src/scripts/events"; import {findAll, findCell, getPackPolygon} from "/src/utils/graphUtils"; import {unique} from "/src/utils/arrayUtils"; +import {showMainTip, clearMainTip} from "/src/scripts/tooltips"; export function editZones() { closeDialogs(); @@ -16,7 +17,7 @@ export function editZones() { $("#zonesEditor").dialog({ title: "Zones Editor", resizable: false, - width: fitContent(), + width: "fit-content", close: () => exitZonesManualAssignment("close"), position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} }); @@ -136,7 +137,7 @@ export function editZones() { body.dataset.type = "absolute"; togglePercentageMode(); } - $("#zonesEditor").dialog({width: fitContent()}); + $("#zonesEditor").dialog({width: "fit-content"}); } function zoneHighlightOn(event) { diff --git a/src/components/fill-box.ts b/src/components/fill-box.ts new file mode 100644 index 00000000..cef1177b --- /dev/null +++ b/src/components/fill-box.ts @@ -0,0 +1,66 @@ +import {tip} from "../scripts/tooltips"; + +const template = document.createElement("template"); +template.innerHTML = /* html */ ` + + + + +`; + +class FillBox extends HTMLElement { + private tooltip: string; + + constructor() { + super(); + + this.tooltip = this.dataset.tip || "Fill style. Click to change"; + } + + private showTip() { + tip(this.tooltip); + } + + connectedCallback() { + // cannot use Shadow DOM here as need an access to svg hatches + this.appendChild(template.content.cloneNode(true)); + this.querySelector("rect")?.setAttribute("fill", this.fill); + this.querySelector("svg")?.setAttribute("width", this.size); + this.querySelector("svg")?.setAttribute("height", this.size); + + this.addEventListener("mousemove", this.showTip); + } + + disconnectedCallback() { + this.removeEventListener("mousemove", this.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"; + } +} + +customElements.define("fill-box", FillBox); diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 00000000..07247140 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1 @@ +import "./fill-box"; diff --git a/src/main.ts b/src/main.ts index 9f22d34c..9b2b8d23 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,6 +20,8 @@ import {Rulers, Ruler, drawScaleBar} from "./modules/measurers"; import {byId} from "./utils/shorthands"; import {addGlobalListeners} from "./scripts/listeners"; import {restoreDefaultEvents} from "./scripts/events"; +import {clearMainTip} from "./scripts/tooltips"; +import "./components"; addGlobalListeners(); diff --git a/src/modules/legend.ts b/src/modules/legend.ts new file mode 100644 index 00000000..615cb88f --- /dev/null +++ b/src/modules/legend.ts @@ -0,0 +1,110 @@ +export function drawLegend(name: string, data: unknown[]) { + legend.selectAll("*").remove(); // fully redraw every time + legend.attr("data", data.join("|")); // store data + + const itemsInCol = +styleLegendColItems.value; + const fontSize = +legend.attr("font-size"); + const backClr = styleLegendBack.value; + const opacity = +styleLegendOpacity.value; + + const lineHeight = Math.round(fontSize * 1.7); + const colorBoxSize = Math.round(fontSize / 1.7); + const colOffset = fontSize; + const vOffset = fontSize / 2; + + // append items + const boxes = legend.append("g").attr("stroke-width", 0.5).attr("stroke", "#111111").attr("stroke-dasharray", "none"); + const labels = legend.append("g").attr("fill", "#000000").attr("stroke", "none"); + + const columns = Math.ceil(data.length / itemsInCol); + for (let column = 0, i = 0; column < columns; column++) { + const linesInColumn = Math.ceil(data.length / columns); + const offset = column ? colOffset * 2 + legend.node().getBBox().width : colOffset; + + for (let l = 0; l < linesInColumn && data[i]; l++, i++) { + boxes + .append("rect") + .attr("fill", data[i][1]) + .attr("x", offset) + .attr("y", lineHeight + l * lineHeight + vOffset) + .attr("width", colorBoxSize) + .attr("height", colorBoxSize); + + labels + .append("text") + .text(data[i][2]) + .attr("x", offset + colorBoxSize * 1.6) + .attr("y", fontSize / 1.6 + lineHeight + l * lineHeight + vOffset); + } + } + + // append label + const offset = colOffset + legend.node().getBBox().width / 2; + labels + .append("text") + .attr("text-anchor", "middle") + .attr("font-weight", "bold") + .attr("font-size", "1.2em") + .attr("id", "legendLabel") + .text(name) + .attr("x", offset) + .attr("y", fontSize * 1.1 + vOffset / 2); + + // append box + const bbox = legend.node().getBBox(); + const width = bbox.width + colOffset * 2; + const height = bbox.height + colOffset / 2 + vOffset; + + legend + .insert("rect", ":first-child") + .attr("id", "legendBox") + .attr("x", 0) + .attr("y", 0) + .attr("width", width) + .attr("height", height) + .attr("fill", backClr) + .attr("fill-opacity", opacity); + + fitLegendBox(); +} + +// fit Legend box to canvas size +export function fitLegendBox() { + if (!legend.selectAll("*").size()) return; + const px = isNaN(+legend.attr("data-x")) ? 99 : legend.attr("data-x") / 100; + const py = isNaN(+legend.attr("data-y")) ? 93 : legend.attr("data-y") / 100; + const bbox = legend.node().getBBox(); + const x = rn(svgWidth * px - bbox.width), + y = rn(svgHeight * py - bbox.height); + legend.attr("transform", `translate(${x},${y})`); +} + +// draw legend with the same data, but using different settings +export function redrawLegend() { + if (!legend.select("rect").size()) return; + const name = legend.select("#legendLabel").text(); + const data = legend + .attr("data") + .split("|") + .map(l => l.split(",")); + drawLegend(name, data); +} + +export function dragLegendBox() { + const tr = parseTransform(this.getAttribute("transform")); + const x = +tr[0] - d3.event.x; + const y = +tr[1] - d3.event.y; + const bbox = legend.node().getBBox(); + + d3.event.on("drag", function () { + const px = rn(((x + d3.event.x + bbox.width) / svgWidth) * 100, 2); + const py = rn(((y + d3.event.y + bbox.height) / svgHeight) * 100, 2); + const transform = `translate(${x + d3.event.x},${y + d3.event.y})`; + legend.attr("transform", transform).attr("data-x", px).attr("data-y", py); + }); +} + +export function clearLegend() { + legend.selectAll("*").remove(); + legend.attr("data", null); +} diff --git a/src/scripts/events.ts b/src/scripts/events.ts index 02e8842f..a8370e82 100644 --- a/src/scripts/events.ts +++ b/src/scripts/events.ts @@ -1,5 +1,190 @@ +import {dragLegendBox} from "../modules/legend"; +import {findCell, findGridCell} from "../utils/graphUtils"; +import {showMainTip} from "./tooltips"; + export function restoreDefaultEvents() { Zoom.setZoomBehavior(); viewbox.style("cursor", "default").on(".drag", null).on("click", clicked).on("touchmove mousemove", onMouseMove); legend.call(d3.drag().on("start", dragLegendBox)); } + +// on viewbox click event - run function based on target +function clicked() { + const el = d3.event.target; + if (!el || !el.parentElement || !el.parentElement.parentElement) return; + const parent = el.parentElement; + const grand = parent.parentElement; + const great = grand.parentElement; + const p = d3.mouse(this); + const i = findCell(p[0], p[1]); + + if (grand.id === "emblems") editEmblem(); + else if (parent.id === "rivers") editRiver(el.id); + else if (grand.id === "routes") editRoute(); + else if (el.tagName === "tspan" && grand.parentNode.parentNode.id === "labels") editLabel(); + else if (grand.id === "burgLabels") editBurg(); + else if (grand.id === "burgIcons") editBurg(); + else if (parent.id === "ice") editIce(); + else if (parent.id === "terrain") editReliefIcon(); + else if (grand.id === "markers" || great.id === "markers") editMarker(); + else if (grand.id === "coastline") editCoastline(); + else if (great.id === "armies") editRegiment(); + else if (pack.cells.t[i] === 1) { + const node = byId("island_" + pack.cells.f[i]); + editCoastline(node); + } else if (grand.id === "lakes") editLake(); +} + +const onMouseMove = debounce(handleMouseMove, 100); +function handleMouseMove() { + const point = d3.mouse(this); + const i = findCell(point[0], point[1]); // pack cell id + if (i === undefined) return; + + showNotes(d3.event); + const gridCell = findGridCell(point[0], point[1], grid); + if (tooltip.dataset.main) showMainTip(); + else showMapTooltip(point, d3.event, i, gridCell); + if (cellInfo?.offsetParent) updateCellInfo(point, i, gridCell); +} + +// show note box on hover (if any) +function showNotes(event: Event) { + if (notesEditor?.offsetParent) return; + let id = event.target.id || event.target.parentNode.id || event.target.parentNode.parentNode.id; + if (event.target.parentNode.parentNode.id === "burgLabels") id = "burg" + event.target.dataset.id; + else if (event.target.parentNode.parentNode.id === "burgIcons") id = "burg" + event.target.dataset.id; + + const note = notes.find(note => note.id === id); + if (note !== undefined && note.legend !== "") { + document.getElementById("notes").style.display = "block"; + document.getElementById("notesHeader").innerHTML = note.name; + document.getElementById("notesBody").innerHTML = note.legend; + } else if (!options.pinNotes && !markerEditor?.offsetParent) { + document.getElementById("notes").style.display = "none"; + document.getElementById("notesHeader").innerHTML = ""; + document.getElementById("notesBody").innerHTML = ""; + } +} + +// show viewbox tooltip if main tooltip is blank +function showMapTooltip(point: number[], event: Event, packCellId: number, gridCellId: number) { + tip(""); // clear tip + const path = event.composedPath ? event.composedPath() : getComposedPath(event.target); // apply polyfill + if (!path[path.length - 8]) return; + const group = path[path.length - 7].id; + const subgroup = path[path.length - 8].id; + const land = pack.cells.h[packCellId] >= 20; + + // specific elements + if (group === "armies") return tip(event.target.parentNode.dataset.name + ". Click to edit"); + + if (group === "emblems" && event.target.tagName === "use") { + const parent = event.target.parentNode; + const [g, type] = + parent.id === "burgEmblems" + ? [pack.burgs, "burg"] + : parent.id === "provinceEmblems" + ? [pack.provinces, "province"] + : [pack.states, "state"]; + const i = +event.target.dataset.i; + if (event.shiftKey) highlightEmblemElement(type, g[i]); + + d3.select(event.target).raise(); + d3.select(parent).raise(); + + const name = g[i].fullName || g[i].name; + tip(`${name} ${type} emblem. Click to edit. Hold Shift to show associated area or place`); + return; + } + + if (group === "rivers") { + const river = +event.target.id.slice(5); + const r = pack.rivers.find(r => r.i === river); + const name = r ? r.name + " " + r.type : ""; + tip(name + ". Click to edit"); + if (riversOverview?.offsetParent) highlightEditorLine(riversOverview, river, 5000); + return; + } + + if (group === "routes") return tip("Click to edit the Route"); + + if (group === "terrain") return tip("Click to edit the Relief Icon"); + + if (subgroup === "burgLabels" || subgroup === "burgIcons") { + const burg = +path[path.length - 10].dataset.id; + const b = pack.burgs[burg]; + const population = si(b.population * populationRate * urbanization); + tip(`${b.name}. Population: ${population}. Click to edit`); + if (burgsOverview?.offsetParent) highlightEditorLine(burgsOverview, burg, 5000); + return; + } + if (group === "labels") return tip("Click to edit the Label"); + + if (group === "markers") return tip("Click to edit the Marker and pin the marker note"); + + if (group === "ruler") { + const tag = event.target.tagName; + const className = event.target.getAttribute("class"); + if (tag === "circle" && className === "edge") + return tip("Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point"); + if (tag === "circle" && className === "control") + return tip("Drag to adjust. Hold Shift and drag to keep axial direction. Click to remove the point"); + if (tag === "circle") return tip("Drag to adjust the measurer"); + if (tag === "polyline") return tip("Click on drag to add a control point"); + if (tag === "path") return tip("Drag to move the measurer"); + if (tag === "text") return tip("Drag to move, click to remove the measurer"); + } + + if (subgroup === "burgIcons") return tip("Click to edit the Burg"); + + if (subgroup === "burgLabels") return tip("Click to edit the Burg"); + + if (group === "lakes" && !land) { + const lakeId = +event.target.dataset.f; + const name = pack.features[lakeId]?.name; + const fullName = subgroup === "freshwater" ? name : name + " " + subgroup; + tip(`${fullName} lake. Click to edit`); + return; + } + if (group === "coastline") return tip("Click to edit the coastline"); + + if (group === "zones") { + const zone = path[path.length - 8]; + tip(zone.dataset.description); + if (zonesEditor?.offsetParent) highlightEditorLine(zonesEditor, zone.id, 5000); + return; + } + + if (group === "ice") return tip("Click to edit the Ice"); + + // covering elements + if (layerIsOn("togglePrec") && land) tip("Annual Precipitation: " + getFriendlyPrecipitation(packCellId)); + else if (layerIsOn("togglePopulation")) tip(getPopulationTip(packCellId)); + else if (layerIsOn("toggleTemp")) tip("Temperature: " + convertTemperature(grid.cells.temp[gridCellId])); + else if (layerIsOn("toggleBiomes") && pack.cells.biome[packCellId]) { + const biome = pack.cells.biome[packCellId]; + tip("Biome: " + biomesData.name[biome]); + if (biomesEditor?.offsetParent) highlightEditorLine(biomesEditor, biome); + } else if (layerIsOn("toggleReligions") && pack.cells.religion[packCellId]) { + const religion = pack.cells.religion[packCellId]; + const r = pack.religions[religion]; + const type = r.type === "Cult" || r.type == "Heresy" ? r.type : r.type + " religion"; + tip(type + ": " + r.name); + if (religionsEditor?.offsetParent) highlightEditorLine(religionsEditor, religion); + } else if (pack.cells.state[packCellId] && (layerIsOn("toggleProvinces") || layerIsOn("toggleStates"))) { + const state = pack.cells.state[packCellId]; + const stateName = pack.states[state].fullName; + const province = pack.cells.province[packCellId]; + const prov = province ? pack.provinces[province].fullName + ", " : ""; + tip(prov + stateName); + if (document.getElementById("statesEditor")?.offsetParent) highlightEditorLine(statesEditor, state); + if (document.getElementById("diplomacyEditor")?.offsetParent) highlightEditorLine(diplomacyEditor, state); + if (document.getElementById("militaryOverview")?.offsetParent) highlightEditorLine(militaryOverview, state); + if (document.getElementById("provincesEditor")?.offsetParent) highlightEditorLine(provincesEditor, province); + } else if (layerIsOn("toggleCultures") && pack.cells.culture[packCellId]) { + const culture = pack.cells.culture[packCellId]; + tip("Culture: " + pack.cultures[culture].name); + if (document.getElementById("culturesEditor")?.offsetParent) highlightEditorLine(culturesEditor, culture); + } else if (layerIsOn("toggleHeight")) tip("Height: " + getFriendlyHeight(point)); +} diff --git a/src/scripts/listeners.ts b/src/scripts/listeners.ts index c60c5b7a..3e1f18f5 100644 --- a/src/scripts/listeners.ts +++ b/src/scripts/listeners.ts @@ -1,10 +1,12 @@ import {PRODUCTION} from "../constants"; import {assignLockBehavior} from "./options/lock"; +import {addTooptipListers} from "./tooltips"; export function addGlobalListeners() { PRODUCTION && registerServiceWorker(); PRODUCTION && addInstallationPrompt(); assignLockBehavior(); + addTooptipListers(); } function registerServiceWorker() { diff --git a/src/scripts/tooltips.ts b/src/scripts/tooltips.ts new file mode 100644 index 00000000..a2c42a75 --- /dev/null +++ b/src/scripts/tooltips.ts @@ -0,0 +1,57 @@ +import {MOBILE} from "../constants"; +import {byId} from "../utils/shorthands"; + +const $tooltip = byId("tooltip")!; + +const tipBackgroundMap = { + info: "linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)", + success: "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)", + warn: "linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)", + error: "linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)" +} as const; +type TTooltipType = keyof typeof tipBackgroundMap; + +export function tip(text: string, main = false, type: TTooltipType = "info", time = 0) { + $tooltip.textContent = text; + $tooltip.style.background = tipBackgroundMap[type]; + + if (main) { + $tooltip.dataset.main = text; + $tooltip.dataset.color = $tooltip.style.background; + } + if (time) setTimeout(clearMainTip, time); +} + +export function clearMainTip() { + $tooltip.dataset.color = ""; + $tooltip.dataset.main = ""; + $tooltip.textContent = ""; +} + +export function showMainTip() { + $tooltip.style.background = $tooltip.dataset.color || ""; + $tooltip.textContent = $tooltip.dataset.main || ""; +} + +function showDataTip(event: Event) { + if (!event.target) return; + + const target = event.target as HTMLElement; + const {parentNode, dataset} = target; + + const targetTip = dataset.tip; + const parentTip = (parentNode as HTMLElement)?.dataset.tip; + + let tooltip = targetTip || parentTip; + if (!tooltip) return; + + if (dataset.shortcut && !MOBILE) tooltip += `. Shortcut: ${dataset.shortcut}`; + tip(tooltip); +} + +// show tip on mousemove for all non-svg elemets with data-tip +export function addTooptipListers() { + byId("dialogs")?.on("mousemove", showDataTip); + byId("optionsContainer")?.on("mousemove", showDataTip); + byId("exitCustomization")?.on("mousemove", showDataTip); +}