mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-18 02:01:22 +01:00
refactor(es modules): continue migration
This commit is contained in:
parent
4a04a8622d
commit
922c6e2431
39 changed files with 551 additions and 589 deletions
66
src/components/fill-box.ts
Normal file
66
src/components/fill-box.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import {tip} from "../scripts/tooltips";
|
||||
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = /* html */ `
|
||||
<style>
|
||||
fill-box:not([disabled]) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
fill-box > svg {
|
||||
vertical-align: middle;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
fill-box > svg > rect {
|
||||
stroke: #666666;
|
||||
stroke-width: 2;
|
||||
}
|
||||
</style>
|
||||
<svg>
|
||||
<rect x="0" y="0" width="100%" height="100%">
|
||||
</svg>
|
||||
`;
|
||||
|
||||
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);
|
||||
1
src/components/index.ts
Normal file
1
src/components/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
import "./fill-box";
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
110
src/modules/legend.ts
Normal file
110
src/modules/legend.ts
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
57
src/scripts/tooltips.ts
Normal file
57
src/scripts/tooltips.ts
Normal file
|
|
@ -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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue