refactor: hotkeys

This commit is contained in:
Azgaar 2022-07-09 16:01:00 +03:00
parent a15f60150f
commit 0dd7468184
9 changed files with 220 additions and 165 deletions

View file

@ -374,7 +374,6 @@
data-t="tipRegenerate" data-t="tipRegenerate"
data-tip="Click to generate a new map" data-tip="Click to generate a new map"
data-shortcut="F2" data-shortcut="F2"
onclick="regeneratePrompt()"
class="options" class="options"
style="display: none" style="display: none"
> >
@ -5562,19 +5561,14 @@
<div id="exportMapData" style="display: none" class="dialog"> <div id="exportMapData" style="display: none" class="dialog">
<div style="margin-bottom: 0.3em; font-weight: bold">Download image</div> <div style="margin-bottom: 0.3em; font-weight: bold">Download image</div>
<div> <div>
<button <button id="saveSVG" data-tip="Download the map as vector image (open directly in browser or Inkscape)">
onclick="saveSVG()"
data-tip="Download the map as vector image (open directly in browser or Inkscape)"
>
.svg .svg
</button> </button>
<button onclick="savePNG()" data-tip="Download visible part of the map as .png (lossless compressed)"> <button id="savePNG" data-tip="Download visible part of the map as .png (lossless compressed)">.png</button>
.png <button id="saveJPEG" data-tip="Download visible part of the map as .jpeg (lossy compressed) image">
</button>
<button onclick="saveJPEG()" data-tip="Download visible part of the map as .jpeg (lossy compressed) image">
.jpeg .jpeg
</button> </button>
<button onclick="openSaveTiles()" data-tip="Split map into smaller png tiles and download as zip archive"> <button id="openSaveTiles" data-tip="Split map into smaller png tiles and download as zip archive">
tiles tiles
</button> </button>
<span data-tip="Check to not allow system to automatically hide labels"> <span data-tip="Check to not allow system to automatically hide labels">
@ -5608,10 +5602,10 @@
<div style="margin: 1em 0 0.3em; font-weight: bold">Export to GeoJSON</div> <div style="margin: 1em 0 0.3em; font-weight: bold">Export to GeoJSON</div>
<div> <div>
<button onclick="saveGeoJSON_Cells()" data-tip="Download cells data in GeoJSON format">cells</button> <button id="saveGeoJSON_Cells" data-tip="Download cells data in GeoJSON format">cells</button>
<button onclick="saveGeoJSON_Routes()" data-tip="Download routes data in GeoJSON format">routes</button> <button id="saveGeoJSON_Routes" data-tip="Download routes data in GeoJSON format">routes</button>
<button onclick="saveGeoJSON_Rivers()" data-tip="Download rivers data in GeoJSON format">rivers</button> <button id="saveGeoJSON_Rivers" data-tip="Download rivers data in GeoJSON format">rivers</button>
<button onclick="saveGeoJSON_Markers()" data-tip="Download markers data in GeoJSON format">markers</button> <button id="saveGeoJSON_Markers" data-tip="Download markers data in GeoJSON format">markers</button>
</div> </div>
<p> <p>
GeoJSON format is used in GIS tools such as QGIS. Check out GeoJSON format is used in GIS tools such as QGIS. Check out
@ -5622,12 +5616,12 @@
<div style="margin: 1em 0 0.3em; font-weight: bold">Export To JSON</div> <div style="margin: 1em 0 0.3em; font-weight: bold">Export To JSON</div>
<div> <div>
<button onclick="exportToJson('Full')" data-tip="Download full data in JSON">full</button> <button id="exportToJson_Full" data-tip="Download full data in JSON">full</button>
<button onclick="exportToJson('Minimal')" data-tip="Download minimal data in JSON">minimal</button> <button id="exportToJson_Minimal" data-tip="Download minimal data in JSON">minimal</button>
<button onclick="exportToJson('PackCells')" data-tip="Download map metadata and pack cells data in JSON"> <button id="exportToJson_PackCells" data-tip="Download map metadata and pack cells data in JSON">
pack cells pack cells
</button> </button>
<button onclick="exportToJson('GridCells')" data-tip="Download map metadata and grid cells data in JSON"> <button id="exportToJson_GridCells" data-tip="Download map metadata and grid cells data in JSON">
grid cells grid cells
</button> </button>
</div> </div>
@ -5644,14 +5638,12 @@
<div id="saveMapData" style="display: none" class="dialog"> <div id="saveMapData" style="display: none" class="dialog">
<div style="margin-top: 0.3em"> <div style="margin-top: 0.3em">
<strong>Save map to</strong> <strong>Save map to</strong>
<button onclick="dowloadMap()" data-tip="Download .map file to your local disk" data-shortcut="Ctrl + S"> <button id="dowloadMap" data-tip="Download .map file to your local disk" data-shortcut="Ctrl + S">
machine machine
</button> </button>
<button onclick="saveToDropbox()" data-tip="Save .map file to your Dropbox" data-shortcut="Ctrl + C"> <button id="saveToDropbox" data-tip="Save .map file to your Dropbox" data-shortcut="Ctrl + C">dropbox</button>
dropbox
</button>
<button <button
onclick="quickSave()" id="quickSave"
data-tip="Save the project to browser storage. It can be unreliable" data-tip="Save the project to browser storage. It can be unreliable"
data-shortcut="F6" data-shortcut="F6"
> >
@ -5668,29 +5660,22 @@
<div id="loadMapData" style="display: none" class="dialog"> <div id="loadMapData" style="display: none" class="dialog">
<div> <div>
<strong>Load map from</strong> <strong>Load map from</strong>
<button onclick="mapToLoad.click()" data-tip="Load .map file from your local disk">machine</button> <button id="loadFromMachine" data-tip="Load .map file from your local disk">machine</button>
<button onclick="loadURL()" data-tip="Load .map file from URL (server should allow CORS)">URL</button> <button id="loadURL" data-tip="Load .map file from URL (server should allow CORS)">URL</button>
<button onclick="quickLoad()" data-tip="Load map from browser storage (if saved before)">storage</button> <button id="quickLoad" data-tip="Load map from browser storage (if saved before)">storage</button>
</div> </div>
<div id="loadFromDropbox"> <div id="loadFromDropbox">
<p style="margin-bottom: 0.3em"> <p style="margin-bottom: 0.3em">
From your Dropbox account From your Dropbox account
<button <button id="dropboxConnectButton" data-tip="Connect your Dropbox account to be able to load maps from it">
id="dropboxConnectButton"
onclick="connectToDropbox()"
data-tip="Connect your Dropbox account to be able to load maps from it"
>
Connect Connect
</button> </button>
</p> </p>
<select id="loadFromDropboxSelect" style="width: 22em"></select> <select id="loadFromDropboxSelect" style="width: 22em"></select>
<div id="loadFromDropboxButtons" style="margin-bottom: 0.6em"> <div id="loadFromDropboxButtons" style="margin-bottom: 0.6em">
<button onclick="loadFromDropbox()" data-tip="Load .map file from your Dropbox">Load</button> <button id="loadFromDropbox" data-tip="Load .map file from your Dropbox">Load</button>
<button <button id="createSharableDropboxLink" data-tip="Select file and create a link to share with your friends">
onclick="createSharableDropboxLink()"
data-tip="Select file and create a link to share with your friends"
>
Share Share
</button> </button>
</div> </div>
@ -5698,7 +5683,7 @@
<div style="margin-top: 0.3em"> <div style="margin-top: 0.3em">
<div id="sharableLinkContainer" style="display: none"> <div id="sharableLinkContainer" style="display: none">
<a id="sharableLink" target="_blank"></a> <a id="sharableLink" target="_blank"></a>
<i data-tip="Copy link to the clipboard" onclick="copyLinkToClickboard()" class="icon-clone pointer"></i> <i id="copyLinkToClickboard" data-tip="Copy link to the clipboard" class="icon-clone pointer"></i>
</div> </div>
</div> </div>
</div> </div>
@ -7691,7 +7676,6 @@
<script type="module" src="/src/modules/ui/tools.js"></script> <script type="module" src="/src/modules/ui/tools.js"></script>
<script type="module" src="/src/modules/ui/threeD.js"></script> <script type="module" src="/src/modules/ui/threeD.js"></script>
<script type="module" src="/src/modules/ui/submap.js"></script> <script type="module" src="/src/modules/ui/submap.js"></script>
<script type="module" src="/src/modules/ui/hotkeys.js"></script>
<script type="module" src="/src/modules/coa-renderer.js"></script> <script type="module" src="/src/modules/coa-renderer.js"></script>
<script type="module" src="/src/modules/io/save.js"></script> <script type="module" src="/src/modules/io/save.js"></script>
<script type="module" src="/src/modules/io/load.js"></script> <script type="module" src="/src/modules/io/load.js"></script>

View file

@ -8,6 +8,7 @@ import {generateSeed} from "utils/probabilityUtils";
import {getColorScheme} from "utils/colorUtils"; import {getColorScheme} from "utils/colorUtils";
import {aleaPRNG} from "scripts/aleaPRNG"; import {aleaPRNG} from "scripts/aleaPRNG";
import {closeDialogs} from "dialogs/utils"; import {closeDialogs} from "dialogs/utils";
import {regeneratePrompt} from "modules/ui/options";
const initialSeed = generateSeed(); const initialSeed = generateSeed();
let graph = getGraph(grid); let graph = getGraph(grid);

View file

@ -61,7 +61,7 @@ export function addDragToUpload() {
}); });
} }
function quickLoad() { export function quickLoad() {
ldb.get("lastMap", blob => { ldb.get("lastMap", blob => {
if (blob) { if (blob) {
loadMapPrompt(blob); loadMapPrompt(blob);

View file

@ -126,7 +126,7 @@ function getMapData() {
} }
// Download .map file // Download .map file
function dowloadMap() { export function dowloadMap() {
if (customization) if (customization)
return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
closeDialogs("#alert"); closeDialogs("#alert");
@ -142,7 +142,7 @@ function dowloadMap() {
window.URL.revokeObjectURL(URL); window.URL.revokeObjectURL(URL);
} }
async function saveToDropbox() { export async function saveToDropbox() {
if (customization) if (customization)
return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
closeDialogs("#alert"); closeDialogs("#alert");
@ -157,7 +157,7 @@ async function saveToDropbox() {
} }
} }
function quickSave() { export function quickSave() {
if (customization) if (customization)
return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
@ -189,7 +189,7 @@ const saveReminder = function () {
}; };
saveReminder(); saveReminder();
function toggleSaveReminder() { export function toggleSaveReminder() {
if (saveReminder.status) { if (saveReminder.status) {
tip("Save reminder is turned off. Press CTRL+Q again to re-initiate", true, "warn", 2000); tip("Save reminder is turned off. Press CTRL+Q again to re-initiate", true, "warn", 2000);
clearInterval(saveReminder.reminder); clearInterval(saveReminder.reminder);

View file

@ -1,14 +1,26 @@
import {openDialog} from "dialogs"; import {openDialog} from "dialogs";
import {toggleLayer} from "layers"; import {toggleLayer} from "layers";
// @ts-expect-error js module
import {showAboutDialog} from "scripts/options/about"; import {showAboutDialog} from "scripts/options/about";
import {byId} from "utils/shorthands"; import {byId} from "utils/shorthands";
import {closeDialogs} from "dialogs/utils"; import {closeDialogs} from "dialogs/utils";
import {minmax} from "utils/numberUtils";
// @ts-expect-error js module
import {hideOptions} from "modules/ui/options";
// @ts-expect-error js module
import {regeneratePrompt, toggle3dOptions, toggleOptions} from "modules/ui/options";
// @ts-expect-error js module
import {quickSave, saveToDropbox, dowloadMap, toggleSaveReminder} from "modules/io/save";
// @ts-expect-error js module
import {quickLoad} from "modules/io/load";
// Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys // Hotkeys, see github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys
document.on("keydown", handleKeydown); export function addHotkeyListeners() {
document.on("keyup", handleKeyup); document.on("keydown", handleKeydown as EventListener);
document.on("keyup", handleKeyup as EventListener);
}
function handleKeydown(event) { function handleKeydown(event: KeyboardEvent) {
if (!allowHotkeys()) return; // in some cases (e.g. in a textarea) hotkeys are not allowed if (!allowHotkeys()) return; // in some cases (e.g. in a textarea) hotkeys are not allowed
const {code, ctrlKey, altKey} = event; const {code, ctrlKey, altKey} = event;
@ -17,7 +29,7 @@ function handleKeydown(event) {
if (["F1", "F2", "F6", "F9", "Tab"].includes(code)) event.preventDefault(); // disallow default Fn and Tab if (["F1", "F2", "F6", "F9", "Tab"].includes(code)) event.preventDefault(); // disallow default Fn and Tab
} }
function handleKeyup(event) { function handleKeyup(event: KeyboardEvent) {
if (!allowHotkeys()) return; // in some cases (e.g. in a textarea) hotkeys are not allowed if (!allowHotkeys()) return; // in some cases (e.g. in a textarea) hotkeys are not allowed
event.stopPropagation(); event.stopPropagation();
@ -27,6 +39,10 @@ function handleKeyup(event) {
const shift = shiftKey || key === "Shift"; const shift = shiftKey || key === "Shift";
const alt = altKey || key === "Alt"; const alt = altKey || key === "Alt";
const Zoom = window.Zoom;
const $undo = byId("undo");
const $redo = byId("redo");
if (code === "F1") showAboutDialog(); if (code === "F1") showAboutDialog();
else if (code === "F2") regeneratePrompt(); else if (code === "F2") regeneratePrompt();
else if (code === "F6") quickSave(); else if (code === "F6") quickSave();
@ -38,8 +54,8 @@ function handleKeyup(event) {
else if (ctrl && code === "KeyQ") toggleSaveReminder(); else if (ctrl && code === "KeyQ") toggleSaveReminder();
else if (ctrl && code === "KeyS") dowloadMap(); else if (ctrl && code === "KeyS") dowloadMap();
else if (ctrl && code === "KeyC") saveToDropbox(); else if (ctrl && code === "KeyC") saveToDropbox();
else if (ctrl && code === "KeyZ" && undo?.offsetParent) undo.click(); else if (ctrl && code === "KeyZ" && $undo?.offsetParent) $undo.click();
else if (ctrl && code === "KeyY" && redo?.offsetParent) redo.click(); else if (ctrl && code === "KeyY" && $redo?.offsetParent) $redo.click();
else if (shift && code === "KeyH") openDialog("heightmapEditor"); else if (shift && code === "KeyH") openDialog("heightmapEditor");
else if (shift && code === "KeyB") editBiomes(); else if (shift && code === "KeyB") editBiomes();
else if (shift && code === "KeyS") openDialog("statesEditor"); else if (shift && code === "KeyS") openDialog("statesEditor");
@ -95,70 +111,80 @@ function handleKeyup(event) {
else if (code === "KeyK") toggleLayer("toggleMarkers"); else if (code === "KeyK") toggleLayer("toggleMarkers");
else if (code === "Equal") toggleLayer("toggleRulers"); else if (code === "Equal") toggleLayer("toggleRulers");
else if (code === "Slash") toggleLayer("toggleScaleBar"); else if (code === "Slash") toggleLayer("toggleScaleBar");
else if (code === "ArrowLeft") zoom.translateBy(svg, 10, 0); else if (code === "ArrowLeft") Zoom.translateBy(svg, 10, 0);
else if (code === "ArrowRight") zoom.translateBy(svg, -10, 0); else if (code === "ArrowRight") Zoom.translateBy(svg, -10, 0);
else if (code === "ArrowUp") zoom.translateBy(svg, 0, 10); else if (code === "ArrowUp") Zoom.translateBy(svg, 0, 10);
else if (code === "ArrowDown") zoom.translateBy(svg, 0, -10); else if (code === "ArrowDown") Zoom.translateBy(svg, 0, -10);
else if (key === "+" || key === "-") pressNumpadSign(key); else if (key === "+" || key === "-") pressNumpadSign(key);
else if (key === "0") Zoom.reset(1000); else if (key === "0") Zoom.reset(1000);
else if (key === "1") zoom.scaleTo(svg, 1); else if (key === "1") Zoom.scaleTo(svg, 1);
else if (key === "2") zoom.scaleTo(svg, 2); else if (key === "2") Zoom.scaleTo(svg, 2);
else if (key === "3") zoom.scaleTo(svg, 3); else if (key === "3") Zoom.scaleTo(svg, 3);
else if (key === "4") zoom.scaleTo(svg, 4); else if (key === "4") Zoom.scaleTo(svg, 4);
else if (key === "5") zoom.scaleTo(svg, 5); else if (key === "5") Zoom.scaleTo(svg, 5);
else if (key === "6") zoom.scaleTo(svg, 6); else if (key === "6") Zoom.scaleTo(svg, 6);
else if (key === "7") zoom.scaleTo(svg, 7); else if (key === "7") Zoom.scaleTo(svg, 7);
else if (key === "8") zoom.scaleTo(svg, 8); else if (key === "8") Zoom.scaleTo(svg, 8);
else if (key === "9") zoom.scaleTo(svg, 9); else if (key === "9") Zoom.scaleTo(svg, 9);
else if (ctrl) toggleMode(); else if (ctrl) toggleMode();
} }
function allowHotkeys() { function allowHotkeys() {
const {tagName, contentEditable} = document.activeElement; if (document.activeElement) {
const {tagName, contentEditable} = document.activeElement as HTMLElement;
if (["INPUT", "SELECT", "TEXTAREA"].includes(tagName)) return false; if (["INPUT", "SELECT", "TEXTAREA"].includes(tagName)) return false;
if (tagName === "DIV" && contentEditable === "true") return false; if (tagName === "DIV" && contentEditable === "true") return false;
if (document.getSelection().toString()) return false; }
if (document.getSelection()?.toString()) return false;
return true; return true;
} }
function pressNumpadSign(key) { function getActionBrushInput() {
const change = key === "+" ? 1 : -1; if (byId("brushRadius")?.offsetParent) return byId("brushRadius");
let brush = null; if (byId("biomesManuallyBrush")?.offsetParent) return byId("biomesManuallyBrush");
if (byId("statesManuallyBrush")?.offsetParent) return byId("statesManuallyBrush");
if (byId("brushRadius")?.offsetParent) brush = byId("brushRadius"); if (byId("provincesManuallyBrush")?.offsetParent) return byId("provincesManuallyBrush");
else if (byId("biomesManuallyBrush")?.offsetParent) brush = byId("biomesManuallyBrush"); if (byId("culturesManuallyBrush")?.offsetParent) return byId("culturesManuallyBrush");
else if (byId("statesManuallyBrush")?.offsetParent) brush = byId("statesManuallyBrush"); if (byId("zonesBrush")?.offsetParent) return byId("zonesBrush");
else if (byId("provincesManuallyBrush")?.offsetParent) brush = byId("provincesManuallyBrush"); if (byId("religionsManuallyBrush")?.offsetParent) return byId("religionsManuallyBrush");
else if (byId("culturesManuallyBrush")?.offsetParent) brush = byId("culturesManuallyBrush"); return null;
else if (byId("zonesBrush")?.offsetParent) brush = byId("zonesBrush"); }
else if (byId("religionsManuallyBrush")?.offsetParent) brush = byId("religionsManuallyBrush");
function pressNumpadSign(key: "+" | "-") {
const brush = getActionBrushInput() as HTMLInputElement | null;
if (brush) { if (brush) {
const value = minmax(+brush.value + change, +brush.min, +brush.max); const change = key === "+" ? 1 : -1;
brush.value = byId(brush.id + "Number").value = value; const value = String(minmax(+brush.value + change, +brush.min, +brush.max));
return; brush.value = value;
}
const numberInput = byId(brush.id + "Number") as HTMLInputElement | null;
if (numberInput) numberInput.value = value;
} else {
// if no brush inputs visible, Zoom map
const scaleBy = key === "+" ? 1.2 : 0.8; const scaleBy = key === "+" ? 1.2 : 0.8;
zoom.scaleBy(svg, scaleBy); // if no brush elements displayed, zoom map window.Zoom.scaleBy(svg, scaleBy);
}
} }
function toggleMode() { function toggleMode() {
if (zonesRemove?.offsetParent) { const $zonesRemove = byId("zonesRemove");
zonesRemove.classList.contains("pressed") if ($zonesRemove?.offsetParent) {
? zonesRemove.classList.remove("pressed") $zonesRemove.classList.contains("pressed")
: zonesRemove.classList.add("pressed"); ? $zonesRemove.classList.remove("pressed")
: $zonesRemove.classList.add("pressed");
} }
} }
function removeElementOnKey() { function removeElementOnKey() {
const fastDelete = Array.from(document.querySelectorAll("[role='dialog'] .fastDelete")).find( const dialogsWithFastDelete = document.querySelectorAll("[role='dialog'] .fastDelete");
dialog => dialog.style.display !== "none" const $fastDelete = Array.from(dialogsWithFastDelete).find(
); dialog => (dialog as HTMLElement).style.display !== "none"
if (fastDelete) fastDelete.click(); ) as HTMLElement | undefined;
if ($fastDelete) $fastDelete.click();
const visibleDialogs = Array.from(document.querySelectorAll("[role='dialog']")).filter( const visibleDialogs = Array.from(document.querySelectorAll("[role='dialog']")).filter(
dialog => dialog.style.display !== "none" dialog => (dialog as HTMLElement).style.display !== "none"
); );
if (!visibleDialogs.length) return; if (!visibleDialogs.length) return;

View file

@ -13,6 +13,7 @@ import {regenerateMap} from "scripts/generation";
import {fitScaleBar} from "modules/measurers"; import {fitScaleBar} from "modules/measurers";
import {openDialog} from "dialogs"; import {openDialog} from "dialogs";
import {closeDialogs} from "dialogs/utils"; import {closeDialogs} from "dialogs/utils";
import {quickSave, saveToDropbox, dowloadMap} from "modules/io/save.js";
$("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"}); $("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"});
$("#exitCustomization").draggable({handle: "div"}); $("#exitCustomization").draggable({handle: "div"});
@ -33,32 +34,32 @@ function showOptions(event) {
} }
regenerate.style.display = "none"; regenerate.style.display = "none";
document.getElementById("options").style.display = "block"; byId("options").style.display = "block";
optionsTrigger.style.display = "none"; optionsTrigger.style.display = "none";
if (event) event.stopPropagation(); if (event) event.stopPropagation();
} }
// Hide options pane on trigger click // Hide options pane on trigger click
function hideOptions(event) { export function hideOptions(event) {
document.getElementById("options").style.display = "none"; byId("options").style.display = "none";
optionsTrigger.style.display = "block"; optionsTrigger.style.display = "block";
if (event) event.stopPropagation(); if (event) event.stopPropagation();
} }
// To toggle options on hotkey press // To toggle options on hotkey press
function toggleOptions(event) { export function toggleOptions(event) {
if (document.getElementById("options").style.display === "none") showOptions(event); if (byId("options").style.display === "none") showOptions(event);
else hideOptions(event); else hideOptions(event);
} }
// Toggle "New Map!" pane on hover // Toggle "New Map!" pane on hover
optionsTrigger.addEventListener("mouseenter", function () { optionsTrigger.on("mouseenter", function () {
if (optionsTrigger.classList.contains("glow")) return; if (optionsTrigger.classList.contains("glow")) return;
if (document.getElementById("options").style.display === "none") regenerate.style.display = "block"; if (byId("options").style.display === "none") regenerate.style.display = "block";
}); });
collapsible.addEventListener("mouseleave", function () { collapsible.on("mouseleave", function () {
regenerate.style.display = "none"; regenerate.style.display = "none";
}); });
@ -66,14 +67,14 @@ collapsible.addEventListener("mouseleave", function () {
document document
.getElementById("options") .getElementById("options")
.querySelector("div.tab") .querySelector("div.tab")
.addEventListener("click", function (event) { .on("click", function (event) {
if (event.target.tagName !== "BUTTON") return; if (event.target.tagName !== "BUTTON") return;
const id = event.target.id; const id = event.target.id;
const active = document.getElementById("options").querySelector(".tab > button.active"); const active = byId("options").querySelector(".tab > button.active");
if (active && id === active.id) return; // already active tab is clicked if (active && id === active.id) return; // already active tab is clicked
if (active) active.classList.remove("active"); if (active) active.classList.remove("active");
document.getElementById(id).classList.add("active"); byId(id).classList.add("active");
document document
.getElementById("options") .getElementById("options")
.querySelectorAll(".tabcontent") .querySelectorAll(".tabcontent")
@ -101,10 +102,10 @@ async function showSupporters() {
} }
// on any option or dialog change // on any option or dialog change
document.getElementById("options").addEventListener("change", storeValueIfRequired); byId("options").on("change", storeValueIfRequired);
document.getElementById("dialogs").addEventListener("change", storeValueIfRequired); byId("dialogs").on("change", storeValueIfRequired);
document.getElementById("options").addEventListener("input", updateOutputToFollowInput); byId("options").on("input", updateOutputToFollowInput);
document.getElementById("dialogs").addEventListener("input", updateOutputToFollowInput); byId("dialogs").on("input", updateOutputToFollowInput);
function storeValueIfRequired(ev) { function storeValueIfRequired(ev) {
if (ev.target.dataset.stored) lock(ev.target.dataset.stored); if (ev.target.dataset.stored) lock(ev.target.dataset.stored);
@ -119,17 +120,17 @@ function updateOutputToFollowInput(ev) {
// generic case // generic case
if (id.slice(-5) === "Input") { if (id.slice(-5) === "Input") {
const output = document.getElementById(id.slice(0, -5) + "Output"); const output = byId(id.slice(0, -5) + "Output");
if (output) output.value = value; if (output) output.value = value;
} else if (id.slice(-6) === "Output") { } else if (id.slice(-6) === "Output") {
const input = document.getElementById(id.slice(0, -6) + "Input"); const input = byId(id.slice(0, -6) + "Input");
if (input) input.value = value; if (input) input.value = value;
} }
} }
// Option listeners // Option listeners
const optionsContent = document.getElementById("optionsContent"); const optionsContent = byId("optionsContent");
optionsContent.addEventListener("input", function (event) { optionsContent.on("input", function (event) {
const id = event.target.id; const id = event.target.id;
const value = event.target.value; const value = event.target.value;
if (id === "mapWidthInput" || id === "mapHeightInput") mapSizeInputChange(); if (id === "mapWidthInput" || id === "mapHeightInput") mapSizeInputChange();
@ -143,7 +144,7 @@ optionsContent.addEventListener("input", function (event) {
else if (id === "transparencyInput") changeDialogsTheme(themeColorInput.value, value); else if (id === "transparencyInput") changeDialogsTheme(themeColorInput.value, value);
}); });
optionsContent.addEventListener("change", function (event) { optionsContent.on("change", function (event) {
const id = event.target.id; const id = event.target.id;
const value = event.target.value; const value = event.target.value;
@ -156,7 +157,7 @@ optionsContent.addEventListener("change", function (event) {
else if (id === "stateLabelsModeInput") options.stateLabelsMode = value; else if (id === "stateLabelsModeInput") options.stateLabelsMode = value;
}); });
optionsContent.addEventListener("click", function (event) { optionsContent.on("click", function (event) {
const id = event.target.id; const id = event.target.id;
if (id === "toggleFullscreen") toggleFullscreen(); if (id === "toggleFullscreen") toggleFullscreen();
else if (id === "optionsMapHistory") showSeedHistoryDialog(); else if (id === "optionsMapHistory") showSeedHistoryDialog();
@ -252,7 +253,7 @@ const voiceInterval = setInterval(function () {
if (voices.length) clearInterval(voiceInterval); if (voices.length) clearInterval(voiceInterval);
else return; else return;
const select = document.getElementById("speakerVoice"); const select = byId("speakerVoice");
voices.forEach((voice, i) => { voices.forEach((voice, i) => {
select.options.add(new Option(voice.name, i, false)); select.options.add(new Option(voice.name, i, false));
}); });
@ -266,7 +267,7 @@ function testSpeaker() {
const speaker = new SpeechSynthesisUtterance(text); const speaker = new SpeechSynthesisUtterance(text);
const voices = speechSynthesis.getVoices(); const voices = speechSynthesis.getVoices();
if (voices.length) { if (voices.length) {
const voiceId = +document.getElementById("speakerVoice").value; const voiceId = +byId("speakerVoice").value;
speaker.voice = voices[voiceId]; speaker.voice = voices[voiceId];
} }
speechSynthesis.speak(speaker); speechSynthesis.speak(speaker);
@ -366,7 +367,7 @@ function changeCultureSet() {
} }
function changeEmblemShape(emblemShape) { function changeEmblemShape(emblemShape) {
const image = document.getElementById("emblemShapeImage"); const image = byId("emblemShapeImage");
const shapePath = window.COArenderer && COArenderer.shieldPaths[emblemShape]; const shapePath = window.COArenderer && COArenderer.shieldPaths[emblemShape];
shapePath ? image.setAttribute("d", shapePath) : image.removeAttribute("d"); shapePath ? image.setAttribute("d", shapePath) : image.removeAttribute("d");
@ -375,7 +376,7 @@ function changeEmblemShape(emblemShape) {
pack.cultures.filter(c => !c.removed).forEach(c => (c.shield = Cultures.getRandomShield())); pack.cultures.filter(c => !c.removed).forEach(c => (c.shield = Cultures.getRandomShield()));
const rerenderCOA = (id, coa) => { const rerenderCOA = (id, coa) => {
const coaEl = document.getElementById(id); const coaEl = byId(id);
if (!coaEl) return; // not rendered if (!coaEl) return; // not rendered
coaEl.remove(); coaEl.remove();
COArenderer.trigger(id, coa); COArenderer.trigger(id, coa);
@ -421,7 +422,7 @@ function changeUIsize(value) {
uiSizeInput.value = uiSizeOutput.value = value; uiSizeInput.value = uiSizeOutput.value = value;
document.getElementsByTagName("body")[0].style.fontSize = rn(value * 10, 2) + "px"; document.getElementsByTagName("body")[0].style.fontSize = rn(value * 10, 2) + "px";
document.getElementById("options").style.width = value * 300 + "px"; byId("options").style.width = value * 300 + "px";
} }
function getUImaxSize() { function getUImaxSize() {
@ -659,7 +660,7 @@ function restoreDefaultOptions() {
} }
// Sticked menu Options listeners // Sticked menu Options listeners
document.getElementById("sticked").addEventListener("click", function (event) { byId("sticked").on("click", function (event) {
const id = event.target.id; const id = event.target.id;
if (id === "newMapButton") regeneratePrompt(); if (id === "newMapButton") regeneratePrompt();
else if (id === "saveButton") showSavePane(); else if (id === "saveButton") showSavePane();
@ -668,7 +669,9 @@ document.getElementById("sticked").addEventListener("click", function (event) {
else if (id === "zoomReset") Zoom.reset(1000); else if (id === "zoomReset") Zoom.reset(1000);
}); });
function regeneratePrompt(options) { byId("regenerate").on("click", regeneratePrompt);
export function regeneratePrompt(options) {
if (customization) if (customization)
return tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error"); return tip("New map cannot be generated when edit mode is active, please exit the mode and retry", false, "error");
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
@ -692,7 +695,7 @@ function regeneratePrompt(options) {
} }
function showSavePane() { function showSavePane() {
const sharableLinkContainer = document.getElementById("sharableLinkContainer"); const sharableLinkContainer = byId("sharableLinkContainer");
sharableLinkContainer.style.display = "none"; sharableLinkContainer.style.display = "none";
$("#saveMapData").dialog({ $("#saveMapData").dialog({
@ -708,14 +711,18 @@ function showSavePane() {
}); });
} }
byId("dowloadMap").on("click", dowloadMap);
byId("saveToDropbox").on("click", saveToDropbox);
byId("quickSave").on("click", quickSave);
function copyLinkToClickboard() { function copyLinkToClickboard() {
const shrableLink = document.getElementById("sharableLink"); const shrableLink = byId("sharableLink");
const link = shrableLink.getAttribute("href"); const link = shrableLink.getAttribute("href");
navigator.clipboard.writeText(link).then(() => tip("Link is copied to the clipboard", true, "success", 8000)); navigator.clipboard.writeText(link).then(() => tip("Link is copied to the clipboard", true, "success", 8000));
} }
function showExportPane() { function showExportPane() {
document.getElementById("showLabels").checked = !hideLabels.checked; byId("showLabels").checked = !hideLabels.checked;
$("#exportMapData").dialog({ $("#exportMapData").dialog({
title: "Export map data", title: "Export map data",
@ -735,6 +742,21 @@ async function exportToJson(type) {
exportToJson(type); exportToJson(type);
} }
byId("saveSVG").on("click", saveSVG);
byId("savePNG").on("click", savePNG);
byId("saveJPEG").on("click", saveJPEG);
byId("openSaveTiles").on("click", openSaveTiles);
byId("saveGeoJSON_Cells").on("click", saveGeoJSON_Cells);
byId("saveGeoJSON_Routes").on("click", saveGeoJSON_Routes);
byId("saveGeoJSON_Rivers").on("click", saveGeoJSON_Rivers);
byId("saveGeoJSON_Markers").on("click", saveGeoJSON_Markers);
byId("exportToJson_Full").on("click", () => exportToJson("Full"));
byId("exportToJson_Minimal").on("click", () => exportToJson("Minimal"));
byId("exportToJson_PackCells").on("click", () => exportToJson("PackCells"));
byId("exportToJson_GridCells").on("click", () => exportToJson("GridCells"));
async function showLoadPane() { async function showLoadPane() {
$("#loadMapData").dialog({ $("#loadMapData").dialog({
title: "Load map", title: "Load map",
@ -750,10 +772,10 @@ async function showLoadPane() {
// already connected to Dropbox: list saved maps // already connected to Dropbox: list saved maps
if (Cloud.providers.dropbox.api) { if (Cloud.providers.dropbox.api) {
document.getElementById("dropboxConnectButton").style.display = "none"; byId("dropboxConnectButton").style.display = "none";
document.getElementById("loadFromDropboxSelect").style.display = "block"; byId("loadFromDropboxSelect").style.display = "block";
const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons"); const loadFromDropboxButtons = byId("loadFromDropboxButtons");
const fileSelect = document.getElementById("loadFromDropboxSelect"); const fileSelect = byId("loadFromDropboxSelect");
fileSelect.innerHTML = /* html */ `<option value="" disabled selected>Loading...</option>`; fileSelect.innerHTML = /* html */ `<option value="" disabled selected>Loading...</option>`;
const files = await Cloud.providers.dropbox.list(); const files = await Cloud.providers.dropbox.list();
@ -778,11 +800,20 @@ async function showLoadPane() {
} }
// not connected to Dropbox: show connect button // not connected to Dropbox: show connect button
document.getElementById("dropboxConnectButton").style.display = "inline-block"; byId("dropboxConnectButton").style.display = "inline-block";
document.getElementById("loadFromDropboxButtons").style.display = "none"; byId("loadFromDropboxButtons").style.display = "none";
document.getElementById("loadFromDropboxSelect").style.display = "none"; byId("loadFromDropboxSelect").style.display = "none";
} }
byId("loadFromMachine").on("click", () => mapToLoad.click());
byId("loadURL").on("click", loadURL);
byId("quickLoad").on("click", quickLoad);
byId("dropboxConnectButton").on("click", connectToDropbox);
byId("loadFromDropbox").on("click", loadFromDropbox);
byId("createSharableDropboxLink").on("click", createSharableDropboxLink);
byId("copyLinkToClickboard").on("click", copyLinkToClickboard);
async function connectToDropbox() { async function connectToDropbox() {
await Cloud.providers.dropbox.initialize(); await Cloud.providers.dropbox.initialize();
if (Cloud.providers.dropbox.api) showLoadPane(); if (Cloud.providers.dropbox.api) showLoadPane();
@ -816,7 +847,7 @@ function loadURL() {
} }
// load map // load map
document.getElementById("mapToLoad").addEventListener("change", function () { byId("mapToLoad").on("change", function () {
const fileToLoad = this.files[0]; const fileToLoad = this.files[0];
this.value = ""; this.value = "";
closeDialogs(); closeDialogs();
@ -826,12 +857,12 @@ document.getElementById("mapToLoad").addEventListener("change", function () {
function openSaveTiles() { function openSaveTiles() {
closeDialogs(); closeDialogs();
updateTilesOptions(); updateTilesOptions();
const status = document.getElementById("tileStatus"); const status = byId("tileStatus");
status.innerHTML = ""; status.innerHTML = "";
let loading = null; let loading = null;
const inputs = document.getElementById("saveTilesScreen").querySelectorAll("input"); const inputs = byId("saveTilesScreen").querySelectorAll("input");
inputs.forEach(input => input.addEventListener("input", updateTilesOptions)); inputs.forEach(input => input.on("input", updateTilesOptions));
$("#saveTilesScreen").dialog({ $("#saveTilesScreen").dialog({
resizable: false, resizable: false,
@ -867,10 +898,10 @@ function updateTilesOptions() {
if (prev?.tagName === "INPUT") prev.value = this.value; if (prev?.tagName === "INPUT") prev.value = this.value;
} }
const tileSize = document.getElementById("tileSize"); const tileSize = byId("tileSize");
const tilesX = +document.getElementById("tileColsOutput").value; const tilesX = +byId("tileColsOutput").value;
const tilesY = +document.getElementById("tileRowsOutput").value; const tilesY = +byId("tileRowsOutput").value;
const scale = +document.getElementById("tileScaleOutput").value; const scale = +byId("tileScaleOutput").value;
// calculate size // calculate size
const sizeX = graphWidth * scale * tilesX; const sizeX = graphWidth * scale * tilesX;
@ -900,7 +931,7 @@ function updateTilesOptions() {
} }
// View mode // View mode
viewMode.addEventListener("click", changeViewMode); viewMode.on("click", changeViewMode);
export function changeViewMode(event) { export function changeViewMode(event) {
const button = event.target; const button = event.target;
if (button.tagName !== "BUTTON") return; if (button.tagName !== "BUTTON") return;
@ -919,9 +950,9 @@ function enterStandardView() {
heightmap3DView.classList.remove("pressed"); heightmap3DView.classList.remove("pressed");
viewStandard.classList.add("pressed"); viewStandard.classList.add("pressed");
if (!document.getElementById("canvas3d")) return; if (!byId("canvas3d")) return;
ThreeD.stop(); ThreeD.stop();
document.getElementById("canvas3d").remove(); byId("canvas3d").remove();
if (options3dUpdate.offsetParent) $("#options3d").dialog("close"); if (options3dUpdate.offsetParent) $("#options3d").dialog("close");
if (preview3d.offsetParent) $("#preview3d").dialog("close"); if (preview3d.offsetParent) $("#preview3d").dialog("close");
} }
@ -954,7 +985,7 @@ async function enter3dView(type) {
}; };
if (type === "heightmap3DView") { if (type === "heightmap3DView") {
document.getElementById("preview3d").appendChild(canvas); byId("preview3d").appendChild(canvas);
$("#preview3d").dialog({ $("#preview3d").dialog({
title: "3D Preview", title: "3D Preview",
resizable: true, resizable: true,
@ -968,7 +999,7 @@ async function enter3dView(type) {
} }
function resize3d() { function resize3d() {
const canvas = document.getElementById("canvas3d"); const canvas = byId("canvas3d");
canvas.width = parseFloat(preview3d.style.width); canvas.width = parseFloat(preview3d.style.width);
canvas.height = parseFloat(preview3d.style.height) - 2; canvas.height = parseFloat(preview3d.style.height) - 2;
ThreeD.redraw(); ThreeD.redraw();
@ -976,7 +1007,7 @@ function resize3d() {
let isLoaded = false; let isLoaded = false;
function toggle3dOptions() { export function toggle3dOptions() {
if (options3dUpdate.offsetParent) { if (options3dUpdate.offsetParent) {
$("#options3d").dialog("close"); $("#options3d").dialog("close");
return; return;
@ -993,29 +1024,29 @@ function toggle3dOptions() {
if (isLoaded) return; if (isLoaded) return;
isLoaded = true; isLoaded = true;
document.getElementById("options3dUpdate").addEventListener("click", ThreeD.update); byId("options3dUpdate").on("click", ThreeD.update);
document.getElementById("options3dSave").addEventListener("click", ThreeD.saveScreenshot); byId("options3dSave").on("click", ThreeD.saveScreenshot);
document.getElementById("options3dOBJSave").addEventListener("click", ThreeD.saveOBJ); byId("options3dOBJSave").on("click", ThreeD.saveOBJ);
document.getElementById("options3dScaleRange").addEventListener("input", changeHeightScale); byId("options3dScaleRange").on("input", changeHeightScale);
document.getElementById("options3dScaleNumber").addEventListener("change", changeHeightScale); byId("options3dScaleNumber").on("change", changeHeightScale);
document.getElementById("options3dLightnessRange").addEventListener("input", changeLightness); byId("options3dLightnessRange").on("input", changeLightness);
document.getElementById("options3dLightnessNumber").addEventListener("change", changeLightness); byId("options3dLightnessNumber").on("change", changeLightness);
document.getElementById("options3dSunX").addEventListener("change", changeSunPosition); byId("options3dSunX").on("change", changeSunPosition);
document.getElementById("options3dSunY").addEventListener("change", changeSunPosition); byId("options3dSunY").on("change", changeSunPosition);
document.getElementById("options3dSunZ").addEventListener("change", changeSunPosition); byId("options3dSunZ").on("change", changeSunPosition);
document.getElementById("options3dMeshRotationRange").addEventListener("input", changeRotation); byId("options3dMeshRotationRange").on("input", changeRotation);
document.getElementById("options3dMeshRotationNumber").addEventListener("change", changeRotation); byId("options3dMeshRotationNumber").on("change", changeRotation);
document.getElementById("options3dGlobeRotationRange").addEventListener("input", changeRotation); byId("options3dGlobeRotationRange").on("input", changeRotation);
document.getElementById("options3dGlobeRotationNumber").addEventListener("change", changeRotation); byId("options3dGlobeRotationNumber").on("change", changeRotation);
document.getElementById("options3dMeshLabels3d").addEventListener("change", toggleLabels3d); byId("options3dMeshLabels3d").on("change", toggleLabels3d);
document.getElementById("options3dMeshSkyMode").addEventListener("change", toggleSkyMode); byId("options3dMeshSkyMode").on("change", toggleSkyMode);
document.getElementById("options3dMeshSky").addEventListener("input", changeColors); byId("options3dMeshSky").on("input", changeColors);
document.getElementById("options3dMeshWater").addEventListener("input", changeColors); byId("options3dMeshWater").on("input", changeColors);
document.getElementById("options3dGlobeResolution").addEventListener("change", changeResolution); byId("options3dGlobeResolution").on("change", changeResolution);
function updateValues() { function updateValues() {
const globe = document.getElementById("canvas3d").dataset.type === "viewGlobe"; const globe = byId("canvas3d").dataset.type === "viewGlobe";
options3dMesh.style.display = globe ? "none" : "block"; options3dMesh.style.display = globe ? "none" : "block";
options3dGlobe.style.display = globe ? "block" : "none"; options3dGlobe.style.display = globe ? "block" : "none";
options3dScaleRange.value = options3dScaleNumber.value = ThreeD.options.scale; options3dScaleRange.value = options3dScaleNumber.value = ThreeD.options.scale;

View file

@ -65,5 +65,9 @@ window.Zoom = (function () {
zoom.scaleTo(element, scale); zoom.scaleTo(element, scale);
} }
return {setZoomBehavior, invoke, force, to, reset, scaleExtent, translateExtent, scaleTo}; function translateBy(element, x, y) {
zoom.translateBy(element, x, y);
}
return {setZoomBehavior, invoke, force, to, reset, scaleExtent, translateExtent, scaleTo, translateBy};
})(); })();

View file

@ -7,6 +7,7 @@ import {assignSpeakerBehavior} from "./speaker";
import {addResizeListener} from "modules/ui/options"; import {addResizeListener} from "modules/ui/options";
// @ts-ignore // @ts-ignore
import {addDragToUpload} from "modules/io/load"; import {addDragToUpload} from "modules/io/load";
import {addHotkeyListeners} from "modules/ui/hotkeys";
export function addGlobalListeners() { export function addGlobalListeners() {
if (PRODUCTION) { if (PRODUCTION) {
@ -18,6 +19,7 @@ export function addGlobalListeners() {
assignLockBehavior(); assignLockBehavior();
addTooptipListers(); addTooptipListers();
addResizeListener(); addResizeListener();
addHotkeyListeners();
assignSpeakerBehavior(); assignSpeakerBehavior();
addDragToUpload(); addDragToUpload();
} }

7
src/types/pack.d.ts vendored
View file

@ -1,9 +1,12 @@
import {Numeric} from "d3";
interface IPack { interface IPack {
vertices: { vertices: {
p: TPoints; p: TPoints;
v: number[][]; v: number[][];
c: number[][]; c: number[][];
}; };
features: IFeature[];
cells: { cells: {
i: IntArray; i: IntArray;
p: TPoints; p: TPoints;
@ -23,6 +26,10 @@ interface IPack {
religions: IReligion[]; religions: IReligion[];
} }
interface IFeature {
i: Numeric;
}
interface IState { interface IState {
i: number; i: number;
name: string; name: string;