refactor(es modules): modulize utils

This commit is contained in:
Azgaar 2022-06-26 19:20:31 +03:00
parent 11df349394
commit 12e1c9f334
45 changed files with 620 additions and 283 deletions

View file

@ -21,8 +21,10 @@ import {
isLand,
shouldRegenerateGrid
} from "./utils/graphUtils";
import {parseError} from "@/utils/errorUtils";
import {rn, minmax, normalize} from "./utils/numberUtils";
import {createTypedArray} from "./utils/arrayUtils";
import {clipPoly} from "@/utils/lineUtils";
import {byId} from "./utils/shorthands";
import "./components";
@ -41,7 +43,7 @@ options = {
winds: [225, 45, 225, 315, 135, 315],
stateLabelsMode: "auto"
};
mapCoordinates = {}; // map coordinates on globe
populationRate = +byId("populationRateInput").value;
distanceScale = +byId("distanceScaleInput").value;
urbanization = +byId("urbanizationInput").value;
@ -368,7 +370,7 @@ async function generate(options) {
OceanLayers();
defineMapSize();
calculateMapCoordinates();
window.mapCoordinates = calculateMapCoordinates();
calculateTemperatures();
generatePrecipitation();
@ -674,7 +676,7 @@ function defineMapSize() {
}
// calculate map position on globe
function calculateMapCoordinates() {
function calculateMapCoordinates(): IMapCoordinates {
const size = +byId("mapSizeOutput").value;
const latShift = +byId("latitudeOutput").value;
@ -683,7 +685,7 @@ function calculateMapCoordinates() {
const latS = rn(latN - latT, 1);
const lon = rn(Math.min(((graphWidth / graphHeight) * latT) / 2, 180));
mapCoordinates = {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon};
return {latT, latN, latS, lonT: lon * 2, lonW: -lon, lonE: lon};
}
// temperature model

View file

@ -1,5 +1,6 @@
import {findCell} from "/src/utils/graphUtils";
import {last} from "/src/utils/arrayUtils";
import {getSegmentId} from "@/utils/lineUtils";
import {rn} from "/src/utils/numberUtils";
export class Rulers {

50
src/scripts/indexedDB.js Normal file
View file

@ -0,0 +1,50 @@
// indexedDB support: ldb object
// @ts-ignore unimplemented historical interfaces
const indexedDBfactory = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
if (!indexedDBfactory) console.error("indexedDB not supported");
let database;
const databaseRequest = indexedDBfactory.open("d2", 1);
databaseRequest.onsuccess = function () {
database = this.result;
};
databaseRequest.onerror = function (e) {
console.error("indexedDB request error", e);
};
databaseRequest.onupgradeneeded = function (event) {
database = null;
const store = databaseRequest.result.createObjectStore("s", {keyPath: "k"});
store.transaction.oncomplete = function (e) {
database = e.target.db;
};
};
function getValue(key, callback) {
if (!database) {
setTimeout(() => getValue(key, callback), 100);
return;
}
database.transaction("s").objectStore("s").get(key).onsuccess = function (e) {
const value = (e.target.result && e.target.result.v) || null;
callback(value);
};
}
function setValue(key) {
if (!database) {
setTimeout(() => setValue(key, value), 100);
return;
}
database
.transaction("s", "readwrite")
.objectStore("s")
.put({[key]: value});
}
export const ldb = {get: getValue, set: setValue};

44
src/scripts/prompt.ts Normal file
View file

@ -0,0 +1,44 @@
import {ERROR} from "@/config/logging";
// prompt replacer (prompt does not work in Electron)
const $prompt: HTMLElement = document.getElementById("prompt")!;
const $form: HTMLFormElement = $prompt.querySelector("#promptForm")!;
const $input: HTMLInputElement = $prompt.querySelector("#promptInput")!;
const $text: HTMLDivElement = $prompt.querySelector("#promptText")!;
const $cancel: HTMLButtonElement = $prompt.querySelector("#promptCancel")!;
const defaultText = "Please provide an input";
const defaultOptions = {default: 1, step: 0.01, min: 0, max: 100, required: true};
export function prompt(promptText = defaultText, options = defaultOptions, callback: (value: number | string) => void) {
if (options.default === undefined)
return ERROR && console.error("Prompt: options object does not have default value defined");
$text.innerHTML = promptText;
$input.type = typeof options.default === "number" ? "number" : "text";
if (options.step !== undefined) $input.step = String(options.step);
if (options.min !== undefined) $input.min = String(options.min);
if (options.max !== undefined) $input.max = String(options.max);
$input.required = options.required === false ? false : true;
$input.placeholder = "type a " + $input.type;
$input.value = String(options.default);
$prompt.style.display = "block";
$form.addEventListener(
"submit",
event => {
event.preventDefault();
$prompt.style.display = "none";
const value = $input.type === "number" ? Number($input.value) : $input.value;
if (callback) callback(value);
},
{once: true}
);
}
$cancel.addEventListener("click", () => {
$prompt.style.display = "none";
});

1
src/types/common.d.ts vendored Normal file
View file

@ -0,0 +1 @@
type UnknownObject = {[key: string]: unknown};

8
src/types/coordinates.d.ts vendored Normal file
View file

@ -0,0 +1,8 @@
interface IMapCoordinates {
latT: number;
latN: number;
latS: number;
lonT: number;
lonW: number;
lonE: number;
}

4
src/types/modules.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare module "lineclip" {
export function polygon(points: number[][], bbox: number[], result?: number[][]): number[][];
export function lineclip(points: number[][], bbox: number[]): number[][];
}

View file

@ -13,6 +13,9 @@ interface Window {
pack: IPack;
grig: IGrid;
d3: typeof d3;
graphHeight: number;
graphWidth: number;
mapCoordinates: IMapCoordinates;
}
interface Node {

3
src/types/point.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
type TPoint = [number, number];
type TPoints = TPoint[];

View file

@ -0,0 +1,25 @@
import {rn} from "./numberUtils";
const {mapCoordinates, graphWidth, graphHeight} = window;
function getLongitude(x: number, decimals = 2) {
return rn(mapCoordinates.lonW + (x / graphWidth) * mapCoordinates.lonT, decimals);
}
function getLatitude(y: number, decimals = 2) {
return rn(mapCoordinates.latN - (y / graphHeight) * mapCoordinates.latT, decimals);
}
export function getCoordinates(x: number, y: number, decimals = 2) {
return [getLongitude(x, decimals), getLatitude(y, decimals)];
}
// convert coordinate to DMS format
export function toDMS(coord: number, type: "lat" | "lon") {
const degrees = Math.floor(Math.abs(coord));
const minutesNotTruncated = (Math.abs(coord) - degrees) * 60;
const minutes = Math.floor(minutesNotTruncated);
const seconds = Math.floor((minutesNotTruncated - minutes) * 60);
const cardinal = type === "lat" ? (coord >= 0 ? "N" : "S") : coord >= 0 ? "E" : "W";
return `${degrees}° ${minutes} ${seconds}${cardinal}`;
}

8
src/utils/errorUtils.ts Normal file
View file

@ -0,0 +1,8 @@
// parse error to get the readable string
export function parseError(error: Error) {
const errorString = error.toString() + " " + error.stack;
const regex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi;
const errorNoURL = errorString.replace(regex, url => "<i>" + url.split("/").at(-1) + "</i>");
const errorParsed = errorNoURL.replace(/at /gi, "<br>&nbsp;&nbsp;at ");
return errorParsed;
}

View file

@ -0,0 +1,3 @@
export function isCtrlClick(event: MouseEvent) {
return event.ctrlKey || event.metaKey;
}

56
src/utils/lineUtils.ts Normal file
View file

@ -0,0 +1,56 @@
import {polygon} from "lineclip";
const {graphWidth, graphHeight, pack} = window;
// clip polygon by graph bbox
export function clipPoly(points: TPoints) {
return polygon(points, [0, 0, graphWidth, graphHeight]);
}
// get segment of any point on polyline
export function getSegmentId(points: TPoints, point: TPoint, step = 10) {
if (points.length === 2) return 1;
const d2 = (p1: TPoint, p2: TPoint) => (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2;
let minSegment = 1;
let minDist = Infinity;
for (let i = 0; i < points.length - 1; i++) {
const p1 = points[i];
const p2 = points[i + 1];
const length = Math.sqrt(d2(p1, p2));
const segments = Math.ceil(length / step);
const dx = (p2[0] - p1[0]) / segments;
const dy = (p2[1] - p1[1]) / segments;
for (let s = 0; s < segments; s++) {
const x = p1[0] + s * dx;
const y = p1[1] + s * dy;
const dist2 = d2(point, [x, y]);
if (dist2 >= minDist) continue;
minDist = dist2;
minSegment = i + 1;
}
}
return minSegment;
}
// return center point of common edge of 2 pack cells
export function getMiddlePoint(cell1: number, cell2: number) {
const {cells, vertices} = pack;
const commonVertices = cells.v[cell1].filter((vertex: number) =>
vertices.c[vertex].some((cellId: number) => cellId === cell2)
);
const [x1, y1] = vertices.p[commonVertices[0]];
const [x2, y2] = vertices.p[commonVertices[1]];
const x = (x1 + x2) / 2;
const y = (y1 + y2) / 2;
return [x, y];
}

14
src/utils/linkUtils.ts Normal file
View file

@ -0,0 +1,14 @@
// open URL in a new tab or window
export function openURL(url: string) {
window.open(url, "_blank");
}
// open project wiki-page
export function wiki(page: string) {
window.open("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/" + page, "_blank");
}
// wrap URL into html anchor element
export function link(url: string, text: string) {
return `<a href="${url}" rel="noopener" target="_blank">${text}</a>`;
}