Added types to helpers
This commit is contained in:
parent
5212c94a3d
commit
86f15e9274
@ -1,8 +1,7 @@
|
|||||||
import React from "react";
|
|
||||||
import { ThemeProvider } from "theme-ui";
|
import { ThemeProvider } from "theme-ui";
|
||||||
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
|
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
|
||||||
|
|
||||||
import theme from "./theme.js";
|
import theme from "./theme";
|
||||||
import Home from "./routes/Home";
|
import Home from "./routes/Home";
|
||||||
import Game from "./routes/Game";
|
import Game from "./routes/Game";
|
||||||
import About from "./routes/About";
|
import About from "./routes/About";
|
@ -1,16 +1,16 @@
|
|||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import Dexie, { Version, DexieOptions } from "dexie";
|
import Dexie, { Version, DexieOptions, Transaction } from "dexie";
|
||||||
import "dexie-observable";
|
import "dexie-observable";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
|
|
||||||
import blobToBuffer from "./helpers/blobToBuffer";
|
import blobToBuffer from "./helpers/blobToBuffer";
|
||||||
import { getGridDefaultInset } from "./helpers/grid";
|
import { getGridDefaultInset, Grid } from "./helpers/grid";
|
||||||
import { convertOldActionsToShapes } from "./actions";
|
import { convertOldActionsToShapes } from "./actions";
|
||||||
import { createThumbnail } from "./helpers/image";
|
import { createThumbnail } from "./helpers/image";
|
||||||
|
|
||||||
// Helper to create a thumbnail for a file in a db
|
// Helper to create a thumbnail for a file in a db
|
||||||
async function createDataThumbnail(data) {
|
async function createDataThumbnail(data: any) {
|
||||||
let url;
|
let url: string;
|
||||||
if (data?.resolutions?.low?.file) {
|
if (data?.resolutions?.low?.file) {
|
||||||
url = URL.createObjectURL(new Blob([data.resolutions.low.file]));
|
url = URL.createObjectURL(new Blob([data.resolutions.low.file]));
|
||||||
} else {
|
} else {
|
||||||
@ -20,7 +20,8 @@ async function createDataThumbnail(data) {
|
|||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
let image = new Image();
|
let image = new Image();
|
||||||
image.onload = async () => {
|
image.onload = async () => {
|
||||||
const thumbnail = await createThumbnail(image);
|
// TODO: confirm parameter for type here
|
||||||
|
const thumbnail = await createThumbnail(image, "file");
|
||||||
resolve(thumbnail);
|
resolve(thumbnail);
|
||||||
};
|
};
|
||||||
image.src = url;
|
image.src = url;
|
||||||
@ -34,13 +35,16 @@ async function createDataThumbnail(data) {
|
|||||||
* @param {Version} version
|
* @param {Version} version
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type VersionCallback = (version: Version) => void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of version number to their upgrade function
|
* Mapping of version number to their upgrade function
|
||||||
* @type {Object.<number, VersionCallback>}
|
* @type {Object.<number, VersionCallback>}
|
||||||
*/
|
*/
|
||||||
const versions = {
|
|
||||||
|
const versions: Record<number, VersionCallback> = {
|
||||||
// v1.2.0
|
// v1.2.0
|
||||||
1(v) {
|
1(v: Version) {
|
||||||
v.stores({
|
v.stores({
|
||||||
maps: "id, owner",
|
maps: "id, owner",
|
||||||
states: "mapId",
|
states: "mapId",
|
||||||
@ -49,29 +53,29 @@ const versions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.2.1 - Move from blob files to array buffers
|
// v1.2.1 - Move from blob files to array buffers
|
||||||
2(v) {
|
2(v: Version) {
|
||||||
v.stores({}).upgrade(async (tx) => {
|
v.stores({}).upgrade(async (tx: Transaction) => {
|
||||||
const maps = await Dexie.waitFor(tx.table("maps").toArray());
|
const maps = await Dexie.waitFor(tx.table("maps").toArray());
|
||||||
let mapBuffers = {};
|
let mapBuffers: any = {};
|
||||||
for (let map of maps) {
|
for (let map of maps) {
|
||||||
mapBuffers[map.id] = await Dexie.waitFor(blobToBuffer(map.file));
|
mapBuffers[map.id] = await Dexie.waitFor(blobToBuffer(map.file));
|
||||||
}
|
}
|
||||||
return tx
|
return tx
|
||||||
.table("maps")
|
.table("maps")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((map) => {
|
.modify((map: any) => {
|
||||||
map.file = mapBuffers[map.id];
|
map.file = mapBuffers[map.id];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.3.0 - Added new default tokens
|
// v1.3.0 - Added new default tokens
|
||||||
3(v) {
|
3(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("states")
|
.table("states")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((state) => {
|
.modify((state: any) => {
|
||||||
function mapTokenId(id) {
|
function mapTokenId(id: any) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case "__default-Axes":
|
case "__default-Axes":
|
||||||
return "__default-Barbarian";
|
return "__default-Barbarian";
|
||||||
@ -128,23 +132,23 @@ const versions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.3.1 - Added show grid option
|
// v1.3.1 - Added show grid option
|
||||||
4(v) {
|
4(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("maps")
|
.table("maps")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((map) => {
|
.modify((map: any) => {
|
||||||
map.showGrid = false;
|
map.showGrid = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.4.0 - Added fog subtraction
|
// v1.4.0 - Added fog subtraction
|
||||||
5(v) {
|
5(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("states")
|
.table("states")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((state) => {
|
.modify((state: any) => {
|
||||||
for (let fogAction of state.fogDrawActions) {
|
for (let fogAction of state.fogDrawActions) {
|
||||||
if (fogAction.type === "add" || fogAction.type === "edit") {
|
if (fogAction.type === "add" || fogAction.type === "edit") {
|
||||||
for (let shape of fogAction.shapes) {
|
for (let shape of fogAction.shapes) {
|
||||||
@ -156,24 +160,24 @@ const versions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.4.2 - Added map resolutions
|
// v1.4.2 - Added map resolutions
|
||||||
6(v) {
|
6(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("maps")
|
.table("maps")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((map) => {
|
.modify((map: any) => {
|
||||||
map.resolutions = {};
|
map.resolutions = {};
|
||||||
map.quality = "original";
|
map.quality = "original";
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.5.0 - Fixed default token rogue spelling
|
// v1.5.0 - Fixed default token rogue spelling
|
||||||
7(v) {
|
7(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("states")
|
.table("states")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((state) => {
|
.modify((state: any) => {
|
||||||
for (let id in state.tokens) {
|
for (let id in state.tokens) {
|
||||||
if (state.tokens[id].tokenId === "__default-Rouge") {
|
if (state.tokens[id].tokenId === "__default-Rouge") {
|
||||||
state.tokens[id].tokenId = "__default-Rogue";
|
state.tokens[id].tokenId = "__default-Rogue";
|
||||||
@ -183,23 +187,23 @@ const versions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.5.0 - Added map snap to grid option
|
// v1.5.0 - Added map snap to grid option
|
||||||
8(v) {
|
8(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("maps")
|
.table("maps")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((map) => {
|
.modify((map: any) => {
|
||||||
map.snapToGrid = true;
|
map.snapToGrid = true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.5.1 - Added lock, visibility and modified to tokens
|
// v1.5.1 - Added lock, visibility and modified to tokens
|
||||||
9(v) {
|
9(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("states")
|
.table("states")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((state) => {
|
.modify((state: any) => {
|
||||||
for (let id in state.tokens) {
|
for (let id in state.tokens) {
|
||||||
state.tokens[id].lastModifiedBy = state.tokens[id].lastEditedBy;
|
state.tokens[id].lastModifiedBy = state.tokens[id].lastEditedBy;
|
||||||
delete state.tokens[id].lastEditedBy;
|
delete state.tokens[id].lastEditedBy;
|
||||||
@ -211,51 +215,51 @@ const versions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.5.1 - Added token prop category and remove isVehicle bool
|
// v1.5.1 - Added token prop category and remove isVehicle bool
|
||||||
10(v) {
|
10(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("tokens")
|
.table("tokens")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((token) => {
|
.modify((token: any) => {
|
||||||
token.category = token.isVehicle ? "vehicle" : "character";
|
token.category = token.isVehicle ? "vehicle" : "character";
|
||||||
delete token.isVehicle;
|
delete token.isVehicle;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.5.2 - Added automatic cache invalidation to maps
|
// v1.5.2 - Added automatic cache invalidation to maps
|
||||||
11(v) {
|
11(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("maps")
|
.table("maps")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((map) => {
|
.modify((map: any) => {
|
||||||
map.lastUsed = map.lastModified;
|
map.lastUsed = map.lastModified;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.5.2 - Added automatic cache invalidation to tokens
|
// v1.5.2 - Added automatic cache invalidation to tokens
|
||||||
12(v) {
|
12(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("tokens")
|
.table("tokens")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((token) => {
|
.modify((token: any) => {
|
||||||
token.lastUsed = token.lastModified;
|
token.lastUsed = token.lastModified;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.6.0 - Added map grouping and grid scale and offset
|
// v1.6.0 - Added map grouping and grid scale and offset
|
||||||
13(v) {
|
13(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("maps")
|
.table("maps")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((map) => {
|
.modify((map: any) => {
|
||||||
map.group = "";
|
map.group = "";
|
||||||
map.grid = {
|
map.grid = {
|
||||||
size: { x: map.gridX, y: map.gridY },
|
size: { x: map.gridX, y: map.gridY },
|
||||||
inset: getGridDefaultInset(
|
inset: getGridDefaultInset(
|
||||||
{ size: { x: map.gridX, y: map.gridY }, type: "square" },
|
{ size: { x: map.gridX, y: map.gridY }, type: "square" } as Grid,
|
||||||
map.width,
|
map.width,
|
||||||
map.height
|
map.height
|
||||||
),
|
),
|
||||||
@ -268,21 +272,21 @@ const versions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.6.0 - Added token grouping
|
// v1.6.0 - Added token grouping
|
||||||
14(v) {
|
14(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("tokens")
|
.table("tokens")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((token) => {
|
.modify((token: any) => {
|
||||||
token.group = "";
|
token.group = "";
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.6.1 - Added width and height to tokens
|
// v1.6.1 - Added width and height to tokens
|
||||||
15(v) {
|
15(v: Version) {
|
||||||
v.stores({}).upgrade(async (tx) => {
|
v.stores({}).upgrade(async (tx: Transaction) => {
|
||||||
const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
|
const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
|
||||||
let tokenSizes = {};
|
let tokenSizes: any = {};
|
||||||
for (let token of tokens) {
|
for (let token of tokens) {
|
||||||
const url = URL.createObjectURL(new Blob([token.file]));
|
const url = URL.createObjectURL(new Blob([token.file]));
|
||||||
let image = new Image();
|
let image = new Image();
|
||||||
@ -298,31 +302,31 @@ const versions = {
|
|||||||
return tx
|
return tx
|
||||||
.table("tokens")
|
.table("tokens")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((token) => {
|
.modify((token: any) => {
|
||||||
token.width = tokenSizes[token.id].width;
|
token.width = tokenSizes[token.id].width;
|
||||||
token.height = tokenSizes[token.id].height;
|
token.height = tokenSizes[token.id].height;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// v1.7.0 - Added note tool
|
// v1.7.0 - Added note tool
|
||||||
16(v) {
|
16(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("states")
|
.table("states")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((state) => {
|
.modify((state: any) => {
|
||||||
state.notes = {};
|
state.notes = {};
|
||||||
state.editFlags = [...state.editFlags, "notes"];
|
state.editFlags = [...state.editFlags, "notes"];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 1.7.0 (hotfix) - Optimized fog shape edits to only include needed data
|
// 1.7.0 (hotfix) - Optimized fog shape edits to only include needed data
|
||||||
17(v) {
|
17(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("states")
|
.table("states")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((state) => {
|
.modify((state: any) => {
|
||||||
for (let i = 0; i < state.fogDrawActions.length; i++) {
|
for (let i = 0; i < state.fogDrawActions.length; i++) {
|
||||||
const action = state.fogDrawActions[i];
|
const action = state.fogDrawActions[i];
|
||||||
if (action && action.type === "edit") {
|
if (action && action.type === "edit") {
|
||||||
@ -340,12 +344,12 @@ const versions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 1.8.0 - Added note text only mode, converted draw and fog representations
|
// 1.8.0 - Added note text only mode, converted draw and fog representations
|
||||||
18(v) {
|
18(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("states")
|
.table("states")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((state) => {
|
.modify((state: any) => {
|
||||||
for (let id in state.notes) {
|
for (let id in state.notes) {
|
||||||
state.notes[id].textOnly = false;
|
state.notes[id].textOnly = false;
|
||||||
}
|
}
|
||||||
@ -367,12 +371,12 @@ const versions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 1.8.0 - Add thumbnail to maps and add measurement to grid
|
// 1.8.0 - Add thumbnail to maps and add measurement to grid
|
||||||
19(v) {
|
19(v: Version) {
|
||||||
v.stores({}).upgrade(async (tx) => {
|
v.stores({}).upgrade(async (tx: Transaction) => {
|
||||||
const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
|
const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
|
||||||
.value;
|
.value;
|
||||||
const maps = await Dexie.waitFor(tx.table("maps").toArray());
|
const maps = await Dexie.waitFor(tx.table("maps").toArray());
|
||||||
const thumbnails = {};
|
const thumbnails: any = {};
|
||||||
for (let map of maps) {
|
for (let map of maps) {
|
||||||
try {
|
try {
|
||||||
if (map.owner === userId) {
|
if (map.owner === userId) {
|
||||||
@ -383,19 +387,19 @@ const versions = {
|
|||||||
return tx
|
return tx
|
||||||
.table("maps")
|
.table("maps")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((map) => {
|
.modify((map: any) => {
|
||||||
map.thumbnail = thumbnails[map.id];
|
map.thumbnail = thumbnails[map.id];
|
||||||
map.grid.measurement = { type: "chebyshev", scale: "5ft" };
|
map.grid.measurement = { type: "chebyshev", scale: "5ft" };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 1.8.0 - Add thumbnail to tokens
|
// 1.8.0 - Add thumbnail to tokens
|
||||||
20(v) {
|
20(v: Version) {
|
||||||
v.stores({}).upgrade(async (tx) => {
|
v.stores({}).upgrade(async (tx: Transaction) => {
|
||||||
const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
|
const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
|
||||||
.value;
|
.value;
|
||||||
const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
|
const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
|
||||||
const thumbnails = {};
|
const thumbnails: any = {};
|
||||||
for (let token of tokens) {
|
for (let token of tokens) {
|
||||||
try {
|
try {
|
||||||
if (token.owner === userId) {
|
if (token.owner === userId) {
|
||||||
@ -406,22 +410,22 @@ const versions = {
|
|||||||
return tx
|
return tx
|
||||||
.table("tokens")
|
.table("tokens")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((token) => {
|
.modify((token: any) => {
|
||||||
token.thumbnail = thumbnails[token.id];
|
token.thumbnail = thumbnails[token.id];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 1.8.0 - Upgrade for Dexie.Observable
|
// 1.8.0 - Upgrade for Dexie.Observable
|
||||||
21(v) {
|
21(v: Version) {
|
||||||
v.stores({});
|
v.stores({});
|
||||||
},
|
},
|
||||||
// v1.8.1 - Shorten fog shape ids
|
// v1.8.1 - Shorten fog shape ids
|
||||||
22(v) {
|
22(v: Version) {
|
||||||
v.stores({}).upgrade((tx) => {
|
v.stores({}).upgrade((tx: Transaction) => {
|
||||||
return tx
|
return tx
|
||||||
.table("states")
|
.table("states")
|
||||||
.toCollection()
|
.toCollection()
|
||||||
.modify((state) => {
|
.modify((state: any) => {
|
||||||
for (let id of Object.keys(state.fogShapes)) {
|
for (let id of Object.keys(state.fogShapes)) {
|
||||||
const newId = shortid.generate();
|
const newId = shortid.generate();
|
||||||
state.fogShapes[newId] = state.fogShapes[id];
|
state.fogShapes[newId] = state.fogShapes[id];
|
||||||
@ -440,7 +444,7 @@ const latestVersion = 22;
|
|||||||
* @param {Dexie} db
|
* @param {Dexie} db
|
||||||
* @param {number=} upTo version number to load up to, latest version if undefined
|
* @param {number=} upTo version number to load up to, latest version if undefined
|
||||||
*/
|
*/
|
||||||
export function loadVersions(db, upTo = latestVersion) {
|
export function loadVersions(db: Dexie, upTo = latestVersion) {
|
||||||
for (let versionNumber = 1; versionNumber <= upTo; versionNumber++) {
|
for (let versionNumber = 1; versionNumber <= upTo; versionNumber++) {
|
||||||
versions[versionNumber](db.version(versionNumber));
|
versions[versionNumber](db.version(versionNumber));
|
||||||
}
|
}
|
||||||
@ -454,7 +458,7 @@ export function loadVersions(db, upTo = latestVersion) {
|
|||||||
* @returns {Dexie}
|
* @returns {Dexie}
|
||||||
*/
|
*/
|
||||||
export function getDatabase(
|
export function getDatabase(
|
||||||
options,
|
options: DexieOptions,
|
||||||
name = "OwlbearRodeoDB",
|
name = "OwlbearRodeoDB",
|
||||||
versionNumber = latestVersion
|
versionNumber = latestVersion
|
||||||
) {
|
) {
|
2
src/global.d.ts
vendored
Normal file
2
src/global.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
declare module 'pepjs';
|
||||||
|
declare module 'socket.io-msgpack-parser';
|
@ -2,17 +2,17 @@
|
|||||||
* A faked local or session storage used when the user has disabled storage
|
* A faked local or session storage used when the user has disabled storage
|
||||||
*/
|
*/
|
||||||
class FakeStorage {
|
class FakeStorage {
|
||||||
data = {};
|
data: { [keyName: string ]: any} = {};
|
||||||
key(index) {
|
key(index: number) {
|
||||||
return Object.keys(this.data)[index] || null;
|
return Object.keys(this.data)[index] || null;
|
||||||
}
|
}
|
||||||
getItem(keyName) {
|
getItem(keyName: string ) {
|
||||||
return this.data[keyName] || null;
|
return this.data[keyName] || null;
|
||||||
}
|
}
|
||||||
setItem(keyName, keyValue) {
|
setItem(keyName: string, keyValue: any) {
|
||||||
this.data[keyName] = keyValue;
|
this.data[keyName] = keyValue;
|
||||||
}
|
}
|
||||||
removeItem(keyName) {
|
removeItem(keyName: string) {
|
||||||
delete this.data[keyName];
|
delete this.data[keyName];
|
||||||
}
|
}
|
||||||
clear() {
|
clear() {
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
InteractionEmitterContext,
|
InteractionEmitterContext,
|
||||||
@ -47,7 +47,7 @@ import {
|
|||||||
/**
|
/**
|
||||||
* Provide a bridge for konva that forwards our contexts
|
* Provide a bridge for konva that forwards our contexts
|
||||||
*/
|
*/
|
||||||
function KonvaBridge({ stageRender, children }) {
|
function KonvaBridge({ stageRender, children }: { stageRender: any, children: any}) {
|
||||||
const mapStageRef = useMapStage();
|
const mapStageRef = useMapStage();
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
@ -8,7 +8,7 @@ class Settings {
|
|||||||
currentVersion;
|
currentVersion;
|
||||||
storage;
|
storage;
|
||||||
|
|
||||||
constructor(name) {
|
constructor(name: string) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
// Try and use local storage if it is available, if not mock it with an in memory storage
|
// Try and use local storage if it is available, if not mock it with an in memory storage
|
||||||
try {
|
try {
|
||||||
@ -22,30 +22,30 @@ class Settings {
|
|||||||
this.currentVersion = this.get("__version");
|
this.currentVersion = this.get("__version");
|
||||||
}
|
}
|
||||||
|
|
||||||
version(versionNumber, upgradeFunction) {
|
version(versionNumber: number, upgradeFunction: Function) {
|
||||||
if (versionNumber > this.currentVersion) {
|
if (versionNumber > this.currentVersion) {
|
||||||
this.currentVersion = versionNumber;
|
this.currentVersion = versionNumber;
|
||||||
this.setAll(upgradeFunction(this.getAll()));
|
this.setAll(upgradeFunction(this.getAll()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll() {
|
getAll(): any {
|
||||||
return JSON.parse(this.storage.getItem(this.name));
|
return JSON.parse(this.storage.getItem(this.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key) {
|
get(key: string) {
|
||||||
const settings = this.getAll();
|
const settings = this.getAll();
|
||||||
return settings && settings[key];
|
return settings && settings[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
setAll(newSettings) {
|
setAll(newSettings: any) {
|
||||||
this.storage.setItem(
|
this.storage.setItem(
|
||||||
this.name,
|
this.name,
|
||||||
JSON.stringify({ ...newSettings, __version: this.currentVersion })
|
JSON.stringify({ ...newSettings, __version: this.currentVersion })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key, value) {
|
set(key: string, value: string) {
|
||||||
let settings = this.getAll();
|
let settings = this.getAll();
|
||||||
settings[key] = value;
|
settings[key] = value;
|
||||||
this.setAll(settings);
|
this.setAll(settings);
|
@ -8,9 +8,9 @@ class Size extends Vector2 {
|
|||||||
/**
|
/**
|
||||||
* @param {number} width
|
* @param {number} width
|
||||||
* @param {number} height
|
* @param {number} height
|
||||||
* @param {number=} radius Used to represent hexagon sizes
|
* @param {number} radius Used to represent hexagon sizes
|
||||||
*/
|
*/
|
||||||
constructor(width, height, radius) {
|
constructor(width: number, height: number, radius?: number) {
|
||||||
super(width, height);
|
super(width, height);
|
||||||
this._radius = radius;
|
this._radius = radius;
|
||||||
}
|
}
|
||||||
@ -18,35 +18,35 @@ class Size extends Vector2 {
|
|||||||
/**
|
/**
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
get width() {
|
get width(): number {
|
||||||
return this.x;
|
return this.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} width
|
* @param {number} width
|
||||||
*/
|
*/
|
||||||
set width(width) {
|
set width(width: number) {
|
||||||
this.x = width;
|
this.x = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
get height() {
|
get height(): number {
|
||||||
return this.y;
|
return this.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} height
|
* @param {number} height
|
||||||
*/
|
*/
|
||||||
set height(height) {
|
set height(height: number) {
|
||||||
this.y = height;
|
this.y = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
get radius() {
|
get radius(): number {
|
||||||
if (this._radius) {
|
if (this._radius) {
|
||||||
return this._radius;
|
return this._radius;
|
||||||
} else {
|
} else {
|
||||||
@ -57,7 +57,7 @@ class Size extends Vector2 {
|
|||||||
/**
|
/**
|
||||||
* @param {number} radius
|
* @param {number} radius
|
||||||
*/
|
*/
|
||||||
set radius(radius) {
|
set radius(radius: number) {
|
||||||
this._radius = radius;
|
this._radius = radius;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,6 +5,14 @@ import {
|
|||||||
floorTo as floorToNumber,
|
floorTo as floorToNumber,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
|
export type BoundingBox = {
|
||||||
|
min: Vector2,
|
||||||
|
max: Vector2,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
center: Vector2
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vector class with x, y and static helper methods
|
* Vector class with x, y and static helper methods
|
||||||
*/
|
*/
|
||||||
@ -12,17 +20,17 @@ class Vector2 {
|
|||||||
/**
|
/**
|
||||||
* @type {number} x - X component of the vector
|
* @type {number} x - X component of the vector
|
||||||
*/
|
*/
|
||||||
x;
|
x: number;
|
||||||
/**
|
/**
|
||||||
* @type {number} y - Y component of the vector
|
* @type {number} y - Y component of the vector
|
||||||
*/
|
*/
|
||||||
y;
|
y: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} x
|
* @param {number} x
|
||||||
* @param {number} y
|
* @param {number} y
|
||||||
*/
|
*/
|
||||||
constructor(x, y) {
|
constructor(x: number, y: number) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
@ -31,7 +39,7 @@ class Vector2 {
|
|||||||
* @param {Vector2} p
|
* @param {Vector2} p
|
||||||
* @returns {number} Length squared of `p`
|
* @returns {number} Length squared of `p`
|
||||||
*/
|
*/
|
||||||
static lengthSquared(p) {
|
static lengthSquared(p: Vector2): number {
|
||||||
return p.x * p.x + p.y * p.y;
|
return p.x * p.x + p.y * p.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +47,7 @@ class Vector2 {
|
|||||||
* @param {Vector2} p
|
* @param {Vector2} p
|
||||||
* @returns {number} Length of `p`
|
* @returns {number} Length of `p`
|
||||||
*/
|
*/
|
||||||
static length(p) {
|
static setLength(p: Vector2): number {
|
||||||
return Math.sqrt(this.lengthSquared(p));
|
return Math.sqrt(this.lengthSquared(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,8 +55,8 @@ class Vector2 {
|
|||||||
* @param {Vector2} p
|
* @param {Vector2} p
|
||||||
* @returns {Vector2} `p` normalized, if length of `p` is 0 `{x: 0, y: 0}` is returned
|
* @returns {Vector2} `p` normalized, if length of `p` is 0 `{x: 0, y: 0}` is returned
|
||||||
*/
|
*/
|
||||||
static normalize(p) {
|
static normalize(p: Vector2): Vector2 {
|
||||||
const l = this.length(p);
|
const l = this.setLength(p);
|
||||||
if (l === 0) {
|
if (l === 0) {
|
||||||
return { x: 0, y: 0 };
|
return { x: 0, y: 0 };
|
||||||
}
|
}
|
||||||
@ -60,7 +68,7 @@ class Vector2 {
|
|||||||
* @param {Vector2} b
|
* @param {Vector2} b
|
||||||
* @returns {number} Dot product between `a` and `b`
|
* @returns {number} Dot product between `a` and `b`
|
||||||
*/
|
*/
|
||||||
static dot(a, b) {
|
static dot(a: Vector2, b: Vector2): number {
|
||||||
return a.x * b.x + a.y * b.y;
|
return a.x * b.x + a.y * b.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +77,7 @@ class Vector2 {
|
|||||||
* @param {(Vector2 | number)} b
|
* @param {(Vector2 | number)} b
|
||||||
* @returns {Vector2} a - b
|
* @returns {Vector2} a - b
|
||||||
*/
|
*/
|
||||||
static subtract(a, b) {
|
static subtract(a: Vector2, b: Vector2 | number): Vector2 {
|
||||||
if (typeof b === "number") {
|
if (typeof b === "number") {
|
||||||
return { x: a.x - b, y: a.y - b };
|
return { x: a.x - b, y: a.y - b };
|
||||||
} else {
|
} else {
|
||||||
@ -82,7 +90,7 @@ class Vector2 {
|
|||||||
* @param {(Vector2 | number)} b
|
* @param {(Vector2 | number)} b
|
||||||
* @returns {Vector2} a + b
|
* @returns {Vector2} a + b
|
||||||
*/
|
*/
|
||||||
static add(a, b) {
|
static add(a: Vector2, b: Vector2 | number): Vector2 {
|
||||||
if (typeof b === "number") {
|
if (typeof b === "number") {
|
||||||
return { x: a.x + b, y: a.y + b };
|
return { x: a.x + b, y: a.y + b };
|
||||||
} else {
|
} else {
|
||||||
@ -95,7 +103,7 @@ class Vector2 {
|
|||||||
* @param {(Vector2 | number)} b
|
* @param {(Vector2 | number)} b
|
||||||
* @returns {Vector2} a * b
|
* @returns {Vector2} a * b
|
||||||
*/
|
*/
|
||||||
static multiply(a, b) {
|
static multiply(a: Vector2, b: Vector2 | number): Vector2 {
|
||||||
if (typeof b === "number") {
|
if (typeof b === "number") {
|
||||||
return { x: a.x * b, y: a.y * b };
|
return { x: a.x * b, y: a.y * b };
|
||||||
} else {
|
} else {
|
||||||
@ -108,7 +116,7 @@ class Vector2 {
|
|||||||
* @param {(Vector2 | number)} b
|
* @param {(Vector2 | number)} b
|
||||||
* @returns {Vector2} a / b
|
* @returns {Vector2} a / b
|
||||||
*/
|
*/
|
||||||
static divide(a, b) {
|
static divide(a: Vector2, b: Vector2 | number): Vector2 {
|
||||||
if (typeof b === "number") {
|
if (typeof b === "number") {
|
||||||
return { x: a.x / b, y: a.y / b };
|
return { x: a.x / b, y: a.y / b };
|
||||||
} else {
|
} else {
|
||||||
@ -123,7 +131,7 @@ class Vector2 {
|
|||||||
* @param {number} angle Angle of rotation in degrees
|
* @param {number} angle Angle of rotation in degrees
|
||||||
* @returns {Vector2} Rotated point
|
* @returns {Vector2} Rotated point
|
||||||
*/
|
*/
|
||||||
static rotate(point, origin, angle) {
|
static rotate(point: Vector2, origin: Vector2, angle: number): Vector2 {
|
||||||
const cos = Math.cos(toRadians(angle));
|
const cos = Math.cos(toRadians(angle));
|
||||||
const sin = Math.sin(toRadians(angle));
|
const sin = Math.sin(toRadians(angle));
|
||||||
const dif = this.subtract(point, origin);
|
const dif = this.subtract(point, origin);
|
||||||
@ -139,7 +147,7 @@ class Vector2 {
|
|||||||
* @param {number} angle Angle of rotation in degrees
|
* @param {number} angle Angle of rotation in degrees
|
||||||
* @returns {Vector2} Rotated direction
|
* @returns {Vector2} Rotated direction
|
||||||
*/
|
*/
|
||||||
static rotateDirection(direction, angle) {
|
static rotateDirection(direction: Vector2, angle: number): Vector2 {
|
||||||
return this.rotate(direction, { x: 0, y: 0 }, angle);
|
return this.rotate(direction, { x: 0, y: 0 }, angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +157,7 @@ class Vector2 {
|
|||||||
* @param {(Vector2 | number)} [minimum] Value to compare
|
* @param {(Vector2 | number)} [minimum] Value to compare
|
||||||
* @returns {(Vector2 | number)}
|
* @returns {(Vector2 | number)}
|
||||||
*/
|
*/
|
||||||
static min(a, minimum) {
|
static min(a: Vector2, minimum?: Vector2 | number): Vector2 | number {
|
||||||
if (minimum === undefined) {
|
if (minimum === undefined) {
|
||||||
return a.x < a.y ? a.x : a.y;
|
return a.x < a.y ? a.x : a.y;
|
||||||
} else if (typeof minimum === "number") {
|
} else if (typeof minimum === "number") {
|
||||||
@ -164,7 +172,7 @@ class Vector2 {
|
|||||||
* @param {(Vector2 | number)} [maximum] Value to compare
|
* @param {(Vector2 | number)} [maximum] Value to compare
|
||||||
* @returns {(Vector2 | number)}
|
* @returns {(Vector2 | number)}
|
||||||
*/
|
*/
|
||||||
static max(a, maximum) {
|
static max(a: Vector2, maximum?: Vector2 | number): Vector2 | number {
|
||||||
if (maximum === undefined) {
|
if (maximum === undefined) {
|
||||||
return a.x > a.y ? a.x : a.y;
|
return a.x > a.y ? a.x : a.y;
|
||||||
} else if (typeof maximum === "number") {
|
} else if (typeof maximum === "number") {
|
||||||
@ -180,7 +188,7 @@ class Vector2 {
|
|||||||
* @param {Vector2} to
|
* @param {Vector2} to
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
static roundTo(p, to) {
|
static roundTo(p: Vector2, to: Vector2): Vector2 {
|
||||||
return {
|
return {
|
||||||
x: roundToNumber(p.x, to.x),
|
x: roundToNumber(p.x, to.x),
|
||||||
y: roundToNumber(p.y, to.y),
|
y: roundToNumber(p.y, to.y),
|
||||||
@ -193,7 +201,7 @@ class Vector2 {
|
|||||||
* @param {Vector2} to
|
* @param {Vector2} to
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
static floorTo(p, to) {
|
static floorTo(p: Vector2, to: Vector2): Vector2 {
|
||||||
return {
|
return {
|
||||||
x: floorToNumber(p.x, to.x),
|
x: floorToNumber(p.x, to.x),
|
||||||
y: floorToNumber(p.y, to.y),
|
y: floorToNumber(p.y, to.y),
|
||||||
@ -204,7 +212,7 @@ class Vector2 {
|
|||||||
* @param {Vector2} a
|
* @param {Vector2} a
|
||||||
* @returns {Vector2} The component wise sign of `a`
|
* @returns {Vector2} The component wise sign of `a`
|
||||||
*/
|
*/
|
||||||
static sign(a) {
|
static sign(a: Vector2): Vector2 {
|
||||||
return { x: Math.sign(a.x), y: Math.sign(a.y) };
|
return { x: Math.sign(a.x), y: Math.sign(a.y) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +220,7 @@ class Vector2 {
|
|||||||
* @param {Vector2} a
|
* @param {Vector2} a
|
||||||
* @returns {Vector2} The component wise absolute of `a`
|
* @returns {Vector2} The component wise absolute of `a`
|
||||||
*/
|
*/
|
||||||
static abs(a) {
|
static abs(a: Vector2): Vector2 {
|
||||||
return { x: Math.abs(a.x), y: Math.abs(a.y) };
|
return { x: Math.abs(a.x), y: Math.abs(a.y) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +229,7 @@ class Vector2 {
|
|||||||
* @param {(Vector2 | number)} b
|
* @param {(Vector2 | number)} b
|
||||||
* @returns {Vector2} `a` to the power of `b`
|
* @returns {Vector2} `a` to the power of `b`
|
||||||
*/
|
*/
|
||||||
static pow(a, b) {
|
static pow(a: Vector2, b: Vector2 | number): Vector2 {
|
||||||
if (typeof b === "number") {
|
if (typeof b === "number") {
|
||||||
return { x: Math.pow(a.x, b), y: Math.pow(a.y, b) };
|
return { x: Math.pow(a.x, b), y: Math.pow(a.y, b) };
|
||||||
} else {
|
} else {
|
||||||
@ -233,7 +241,7 @@ class Vector2 {
|
|||||||
* @param {Vector2} a
|
* @param {Vector2} a
|
||||||
* @returns {number} The dot product between `a` and `a`
|
* @returns {number} The dot product between `a` and `a`
|
||||||
*/
|
*/
|
||||||
static dot2(a) {
|
static dot2(a: Vector2): number {
|
||||||
return this.dot(a, a);
|
return this.dot(a, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,7 +252,7 @@ class Vector2 {
|
|||||||
* @param {number} max
|
* @param {number} max
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
static clamp(a, min, max) {
|
static clamp(a: Vector2, min: number, max: number): Vector2 {
|
||||||
return {
|
return {
|
||||||
x: Math.min(Math.max(a.x, min), max),
|
x: Math.min(Math.max(a.x, min), max),
|
||||||
y: Math.min(Math.max(a.y, min), max),
|
y: Math.min(Math.max(a.y, min), max),
|
||||||
@ -259,11 +267,11 @@ class Vector2 {
|
|||||||
* @param {Vector2} b End of the line
|
* @param {Vector2} b End of the line
|
||||||
* @returns {Object} The distance to and the closest point on the line segment
|
* @returns {Object} The distance to and the closest point on the line segment
|
||||||
*/
|
*/
|
||||||
static distanceToLine(p, a, b) {
|
static distanceToLine(p: Vector2, a: Vector2, b: Vector2): Object {
|
||||||
const pa = this.subtract(p, a);
|
const pa = this.subtract(p, a);
|
||||||
const ba = this.subtract(b, a);
|
const ba = this.subtract(b, a);
|
||||||
const h = Math.min(Math.max(this.dot(pa, ba) / this.dot(ba, ba), 0), 1);
|
const h = Math.min(Math.max(this.dot(pa, ba) / this.dot(ba, ba), 0), 1);
|
||||||
const distance = this.length(this.subtract(pa, this.multiply(ba, h)));
|
const distance = this.setLength(this.subtract(pa, this.multiply(ba, h)));
|
||||||
const point = this.add(a, this.multiply(ba, h));
|
const point = this.add(a, this.multiply(ba, h));
|
||||||
return { distance, point };
|
return { distance, point };
|
||||||
}
|
}
|
||||||
@ -278,7 +286,7 @@ class Vector2 {
|
|||||||
* @param {Vector2} C End of the curve
|
* @param {Vector2} C End of the curve
|
||||||
* @returns {Object} The distance to and the closest point on the curve
|
* @returns {Object} The distance to and the closest point on the curve
|
||||||
*/
|
*/
|
||||||
static distanceToQuadraticBezier(pos, A, B, C) {
|
static distanceToQuadraticBezier(pos: Vector2, A: Vector2, B: Vector2, C: Vector2): Object {
|
||||||
let distance = 0;
|
let distance = 0;
|
||||||
let point = { x: pos.x, y: pos.y };
|
let point = { x: pos.x, y: pos.y };
|
||||||
|
|
||||||
@ -358,7 +366,7 @@ class Vector2 {
|
|||||||
* @param {Vector2[]} points
|
* @param {Vector2[]} points
|
||||||
* @returns {BoundingBox}
|
* @returns {BoundingBox}
|
||||||
*/
|
*/
|
||||||
static getBoundingBox(points) {
|
static getBoundingBox(points: Vector2[]): BoundingBox {
|
||||||
let minX = Number.MAX_VALUE;
|
let minX = Number.MAX_VALUE;
|
||||||
let maxX = Number.MIN_VALUE;
|
let maxX = Number.MIN_VALUE;
|
||||||
let minY = Number.MAX_VALUE;
|
let minY = Number.MAX_VALUE;
|
||||||
@ -389,7 +397,7 @@ class Vector2 {
|
|||||||
* @param {Vector2[]} points
|
* @param {Vector2[]} points
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
static pointInPolygon(p, points) {
|
static pointInPolygon(p: Vector2, points: Vector2[]): boolean {
|
||||||
const bounds = this.getBoundingBox(points);
|
const bounds = this.getBoundingBox(points);
|
||||||
if (
|
if (
|
||||||
p.x < bounds.min.x ||
|
p.x < bounds.min.x ||
|
||||||
@ -422,8 +430,9 @@ class Vector2 {
|
|||||||
* @param {Vector2} a
|
* @param {Vector2} a
|
||||||
* @param {Vector2} b
|
* @param {Vector2} b
|
||||||
* @param {number} threshold
|
* @param {number} threshold
|
||||||
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
static compare(a, b, threshold) {
|
static compare(a: Vector2, b: Vector2, threshold: number): boolean {
|
||||||
return this.lengthSquared(this.subtract(a, b)) < threshold * threshold;
|
return this.lengthSquared(this.subtract(a, b)) < threshold * threshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,9 +440,10 @@ class Vector2 {
|
|||||||
* Returns the distance between two vectors
|
* Returns the distance between two vectors
|
||||||
* @param {Vector2} a
|
* @param {Vector2} a
|
||||||
* @param {Vector2} b
|
* @param {Vector2} b
|
||||||
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
static distance(a, b) {
|
static distance(a: Vector2, b: Vector2): number {
|
||||||
return this.length(this.subtract(a, b));
|
return this.setLength(this.subtract(a, b));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -443,15 +453,16 @@ class Vector2 {
|
|||||||
* @param {number} alpha
|
* @param {number} alpha
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
static lerp(a, b, alpha) {
|
static lerp(a: Vector2, b: Vector2, alpha: number): Vector2 {
|
||||||
return { x: lerpNumber(a.x, b.x, alpha), y: lerpNumber(a.y, b.y, alpha) };
|
return { x: lerpNumber(a.x, b.x, alpha), y: lerpNumber(a.y, b.y, alpha) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns total length of a an array of points treated as a path
|
* Returns total length of a an array of points treated as a path
|
||||||
* @param {Vector2[]} points the array of points in the path
|
* @param {Vector2[]} points the array of points in the path
|
||||||
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
static pathLength(points) {
|
static pathLength(points: Vector2[]): number {
|
||||||
let l = 0;
|
let l = 0;
|
||||||
for (let i = 1; i < points.length; i++) {
|
for (let i = 1; i < points.length; i++) {
|
||||||
l += this.distance(points[i - 1], points[i]);
|
l += this.distance(points[i - 1], points[i]);
|
||||||
@ -464,8 +475,9 @@ class Vector2 {
|
|||||||
* based off of http://depts.washington.edu/acelab/proj/dollar/index.html
|
* based off of http://depts.washington.edu/acelab/proj/dollar/index.html
|
||||||
* @param {Vector2[]} points the points to resample
|
* @param {Vector2[]} points the points to resample
|
||||||
* @param {number} n the number of new points
|
* @param {number} n the number of new points
|
||||||
|
* @returns {Vector2[]}
|
||||||
*/
|
*/
|
||||||
static resample(points, n) {
|
static resample(points: Vector2[], n: number): Vector2[] {
|
||||||
if (points.length === 0 || n <= 0) {
|
if (points.length === 0 || n <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -501,7 +513,7 @@ class Vector2 {
|
|||||||
* @param {("counterClockwise"|"clockwise")=} direction Direction to rotate the vector
|
* @param {("counterClockwise"|"clockwise")=} direction Direction to rotate the vector
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
static rotate90(p, direction = "clockwise") {
|
static rotate90(p: Vector2, direction: "counterClockwise" | "clockwise" = "clockwise"): Vector2 {
|
||||||
if (direction === "clockwise") {
|
if (direction === "clockwise") {
|
||||||
return { x: p.y, y: -p.x };
|
return { x: p.y, y: -p.x };
|
||||||
} else {
|
} else {
|
@ -5,22 +5,22 @@ class Vector3 {
|
|||||||
/**
|
/**
|
||||||
* @type {number} x - X component of the vector
|
* @type {number} x - X component of the vector
|
||||||
*/
|
*/
|
||||||
x;
|
x: number;
|
||||||
/**
|
/**
|
||||||
* @type {number} y - Y component of the vector
|
* @type {number} y - Y component of the vector
|
||||||
*/
|
*/
|
||||||
y;
|
y: number;
|
||||||
/**
|
/**
|
||||||
* @type {number} z - Z component of the vector
|
* @type {number} z - Z component of the vector
|
||||||
*/
|
*/
|
||||||
z;
|
z: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} x
|
* @param {number} x
|
||||||
* @param {number} y
|
* @param {number} y
|
||||||
* @param {number} z
|
* @param {number} z
|
||||||
*/
|
*/
|
||||||
constructor(x, y, z) {
|
constructor(x: number, y: number, z: number) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.z = z;
|
this.z = z;
|
||||||
@ -31,7 +31,7 @@ class Vector3 {
|
|||||||
* @param {Vector3} cube
|
* @param {Vector3} cube
|
||||||
* @returns {Vector3}
|
* @returns {Vector3}
|
||||||
*/
|
*/
|
||||||
static cubeRound(cube) {
|
static cubeRound(cube: Vector3): Vector3 {
|
||||||
var rX = Math.round(cube.x);
|
var rX = Math.round(cube.x);
|
||||||
var rY = Math.round(cube.y);
|
var rY = Math.round(cube.y);
|
||||||
var rZ = Math.round(cube.z);
|
var rZ = Math.round(cube.z);
|
@ -1,17 +1,17 @@
|
|||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
|
|
||||||
export function addPolygonDifferenceToShapes(shape, difference, shapes) {
|
export function addPolygonDifferenceToShapes(shape: any, difference: any, shapes: any) {
|
||||||
for (let i = 0; i < difference.length; i++) {
|
for (let i = 0; i < difference.length; i++) {
|
||||||
let newId = shortid.generate();
|
let newId = shortid.generate();
|
||||||
// Holes detected
|
// Holes detected
|
||||||
let holes = [];
|
let holes = [];
|
||||||
if (difference[i].length > 1) {
|
if (difference[i].length > 1) {
|
||||||
for (let j = 1; j < difference[i].length; j++) {
|
for (let j = 1; j < difference[i].length; j++) {
|
||||||
holes.push(difference[i][j].map(([x, y]) => ({ x, y })));
|
holes.push(difference[i][j].map(([x, y]: [ x: number, y: number ]) => ({ x, y })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const points = difference[i][0].map(([x, y]) => ({ x, y }));
|
const points = difference[i][0].map(([x, y]: [ x: number, y: number ]) => ({ x, y }));
|
||||||
|
|
||||||
shapes[newId] = {
|
shapes[newId] = {
|
||||||
...shape,
|
...shape,
|
||||||
@ -24,11 +24,11 @@ export function addPolygonDifferenceToShapes(shape, difference, shapes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addPolygonIntersectionToShapes(shape, intersection, shapes) {
|
export function addPolygonIntersectionToShapes(shape: any, intersection: any, shapes: any) {
|
||||||
for (let i = 0; i < intersection.length; i++) {
|
for (let i = 0; i < intersection.length; i++) {
|
||||||
let newId = shortid.generate();
|
let newId = shortid.generate();
|
||||||
|
|
||||||
const points = intersection[i][0].map(([x, y]) => ({ x, y }));
|
const points = intersection[i][0].map(([x, y]: [ x: number, y: number ]) => ({ x, y }));
|
||||||
|
|
||||||
shapes[newId] = {
|
shapes[newId] = {
|
||||||
...shape,
|
...shape,
|
@ -1,7 +1,7 @@
|
|||||||
import { Texture } from "@babylonjs/core/Materials/Textures/texture";
|
import { Texture } from "@babylonjs/core/Materials/Textures/texture";
|
||||||
|
|
||||||
// Turn texture load into an async function so it can be awaited
|
// Turn texture load into an async function so it can be awaited
|
||||||
export async function importTextureAsync(url) {
|
export async function importTextureAsync(url: string) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let texture = new Texture(
|
let texture = new Texture(
|
||||||
url,
|
url,
|
@ -2,7 +2,7 @@
|
|||||||
* @param {Blob} blob
|
* @param {Blob} blob
|
||||||
* @returns {Promise<Uint8Array>}
|
* @returns {Promise<Uint8Array>}
|
||||||
*/
|
*/
|
||||||
async function blobToBuffer(blob) {
|
async function blobToBuffer(blob: Blob): Promise<Uint8Array> {
|
||||||
if (blob.arrayBuffer) {
|
if (blob.arrayBuffer) {
|
||||||
const arrayBuffer = await blob.arrayBuffer();
|
const arrayBuffer = await blob.arrayBuffer();
|
||||||
return new Uint8Array(arrayBuffer);
|
return new Uint8Array(arrayBuffer);
|
||||||
@ -10,12 +10,12 @@ async function blobToBuffer(blob) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
||||||
function onLoadEnd(event) {
|
function onLoadEnd(event: any) {
|
||||||
reader.removeEventListener("loadend", onLoadEnd, false);
|
reader.removeEventListener("loadend", onLoadEnd, false);
|
||||||
if (event.error) {
|
if (event.error) {
|
||||||
reject(event.error);
|
reject(event.error);
|
||||||
} else {
|
} else {
|
||||||
resolve(Buffer.from(reader.result));
|
resolve(Buffer.from(reader.result as ArrayBuffer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,20 @@
|
|||||||
|
export type Colors = {
|
||||||
|
blue: string;
|
||||||
|
orange: string;
|
||||||
|
red: string;
|
||||||
|
yellow: string;
|
||||||
|
purple: string;
|
||||||
|
green: string;
|
||||||
|
pink: string;
|
||||||
|
teal: string;
|
||||||
|
black: string;
|
||||||
|
darkGray: string;
|
||||||
|
lightGray: string;
|
||||||
|
white: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Colors used for the game for theme general UI colors look at theme.js
|
// Colors used for the game for theme general UI colors look at theme.js
|
||||||
const colors = {
|
const colors: Colors = {
|
||||||
blue: "rgb(26, 106, 255)",
|
blue: "rgb(26, 106, 255)",
|
||||||
orange: "rgb(255, 116, 51)",
|
orange: "rgb(255, 116, 51)",
|
||||||
red: "rgb(255, 77, 77)",
|
red: "rgb(255, 77, 77)",
|
@ -4,7 +4,7 @@ import { Vector3 } from "@babylonjs/core/Maths/math";
|
|||||||
* Find the number facing up on a mesh instance of a dice
|
* Find the number facing up on a mesh instance of a dice
|
||||||
* @param {Object} instance The dice instance
|
* @param {Object} instance The dice instance
|
||||||
*/
|
*/
|
||||||
export function getDiceInstanceRoll(instance) {
|
export function getDiceInstanceRoll(instance: any) {
|
||||||
let highestDot = -1;
|
let highestDot = -1;
|
||||||
let highestLocator;
|
let highestLocator;
|
||||||
for (let locator of instance.getChildTransformNodes()) {
|
for (let locator of instance.getChildTransformNodes()) {
|
||||||
@ -25,7 +25,7 @@ export function getDiceInstanceRoll(instance) {
|
|||||||
* Find the number facing up on a dice object
|
* Find the number facing up on a dice object
|
||||||
* @param {Object} dice The Dice object
|
* @param {Object} dice The Dice object
|
||||||
*/
|
*/
|
||||||
export function getDiceRoll(dice) {
|
export function getDiceRoll(dice: any) {
|
||||||
let number = getDiceInstanceRoll(dice.instance);
|
let number = getDiceInstanceRoll(dice.instance);
|
||||||
// If the dice is a d100 add the d10
|
// If the dice is a d100 add the d10
|
||||||
if (dice.type === "d100") {
|
if (dice.type === "d100") {
|
||||||
@ -42,8 +42,8 @@ export function getDiceRoll(dice) {
|
|||||||
return { type: dice.type, roll: number };
|
return { type: dice.type, roll: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDiceRollTotal(diceRolls) {
|
export function getDiceRollTotal(diceRolls: []) {
|
||||||
return diceRolls.reduce((accumulator, dice) => {
|
return diceRolls.reduce((accumulator: number, dice: any) => {
|
||||||
if (dice.roll === "unknown") {
|
if (dice.roll === "unknown") {
|
||||||
return accumulator;
|
return accumulator;
|
||||||
} else {
|
} else {
|
@ -1,7 +1,7 @@
|
|||||||
import { applyChange, revertChange, diff as deepDiff } from "deep-diff";
|
import { applyChange, Diff, revertChange, diff as deepDiff }from "deep-diff";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
|
|
||||||
export function applyChanges(target, changes) {
|
export function applyChanges<LHS>(target: LHS, changes: Diff<LHS, any>[]) {
|
||||||
for (let change of changes) {
|
for (let change of changes) {
|
||||||
if (change.path && (change.kind === "E" || change.kind === "A")) {
|
if (change.path && (change.kind === "E" || change.kind === "A")) {
|
||||||
// If editing an object or array ensure that the value exists
|
// If editing an object or array ensure that the value exists
|
||||||
@ -15,7 +15,7 @@ export function applyChanges(target, changes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function revertChanges(target, changes) {
|
export function revertChanges<LHS>(target: LHS, changes: Diff<LHS, any>[]) {
|
||||||
for (let change of changes) {
|
for (let change of changes) {
|
||||||
revertChange(target, true, change);
|
revertChange(target, true, change);
|
||||||
}
|
}
|
@ -1,15 +1,20 @@
|
|||||||
import simplify from "simplify-js";
|
import simplify from "simplify-js";
|
||||||
import polygonClipping from "polygon-clipping";
|
import polygonClipping, { Geom, Polygon, Ring } from "polygon-clipping";
|
||||||
|
|
||||||
import Vector2 from "./Vector2";
|
import Vector2, { BoundingBox } from "./Vector2";
|
||||||
|
import Size from "./Size"
|
||||||
import { toDegrees } from "./shared";
|
import { toDegrees } from "./shared";
|
||||||
import { getNearestCellCoordinates, getCellLocation } from "./grid";
|
import { Grid, getNearestCellCoordinates, getCellLocation } from "./grid";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef PointsData
|
* @typedef PointsData
|
||||||
* @property {Vector2[]} points
|
* @property {Vector2[]} points
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type PointsData = {
|
||||||
|
points: Vector2[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef RectData
|
* @typedef RectData
|
||||||
* @property {number} x
|
* @property {number} x
|
||||||
@ -18,30 +23,55 @@ import { getNearestCellCoordinates, getCellLocation } from "./grid";
|
|||||||
* @property {number} height
|
* @property {number} height
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type RectData = {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef CircleData
|
* @typedef CircleData
|
||||||
* @property {number} x
|
* @property {number} x
|
||||||
* @property {number} y
|
* @property {number} y
|
||||||
* @property {number} radius
|
* @property {number} radius
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type CircleData = {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
radius: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef FogData
|
* @typedef FogData
|
||||||
* @property {Vector2[]} points
|
* @property {Vector2[]} points
|
||||||
* @property {Vector2[]} holes
|
* @property {Vector2[][]} holes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type FogData = {
|
||||||
|
points: Vector2[]
|
||||||
|
holes: Vector2[][]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {(PointsData|RectData|CircleData)} ShapeData
|
* @typedef {(PointsData|RectData|CircleData)} ShapeData
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type ShapeData = PointsData | RectData | CircleData
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {("line"|"rectangle"|"circle"|"triangle")} ShapeType
|
* @typedef {("line"|"rectangle"|"circle"|"triangle")} ShapeType
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type ShapeType = "line" | "rectangle" | "circle" | "triangle"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {("fill"|"stroke")} PathType
|
* @typedef {("fill"|"stroke")} PathType
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// type PathType = "fill" | "stroke"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef Path
|
* @typedef Path
|
||||||
* @property {boolean} blend
|
* @property {boolean} blend
|
||||||
@ -53,6 +83,16 @@ import { getNearestCellCoordinates, getCellLocation } from "./grid";
|
|||||||
* @property {"path"} type
|
* @property {"path"} type
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// type Path = {
|
||||||
|
// blend: boolean,
|
||||||
|
// color: string,
|
||||||
|
// data: PointsData,
|
||||||
|
// id: string,
|
||||||
|
// pathType: PathType,
|
||||||
|
// strokeWidth: number,
|
||||||
|
// type: "path"
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef Shape
|
* @typedef Shape
|
||||||
* @property {boolean} blend
|
* @property {boolean} blend
|
||||||
@ -64,6 +104,16 @@ import { getNearestCellCoordinates, getCellLocation } from "./grid";
|
|||||||
* @property {"shape"} type
|
* @property {"shape"} type
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// type Shape = {
|
||||||
|
// blend: boolean,
|
||||||
|
// color: string,
|
||||||
|
// data: ShapeData,
|
||||||
|
// id: string,
|
||||||
|
// shapeType: ShapeType,
|
||||||
|
// strokeWidth: number,
|
||||||
|
// type: "shape"
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef Fog
|
* @typedef Fog
|
||||||
* @property {string} color
|
* @property {string} color
|
||||||
@ -74,29 +124,39 @@ import { getNearestCellCoordinates, getCellLocation } from "./grid";
|
|||||||
* @property {boolean} visible
|
* @property {boolean} visible
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type Fog = {
|
||||||
|
color: string,
|
||||||
|
data: FogData,
|
||||||
|
id: string,
|
||||||
|
strokeWidth: number,
|
||||||
|
type: "fog",
|
||||||
|
visible: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {ShapeType} type
|
* @param {ShapeType} type
|
||||||
* @param {Vector2} brushPosition
|
* @param {Vector2} brushPosition
|
||||||
* @returns {ShapeData}
|
* @returns {ShapeData}
|
||||||
*/
|
*/
|
||||||
export function getDefaultShapeData(type, brushPosition) {
|
export function getDefaultShapeData(type: ShapeType, brushPosition: Vector2): ShapeData | undefined{
|
||||||
|
// TODO: handle undefined if no type found
|
||||||
if (type === "line") {
|
if (type === "line") {
|
||||||
return {
|
return {
|
||||||
points: [
|
points: [
|
||||||
{ x: brushPosition.x, y: brushPosition.y },
|
{ x: brushPosition.x, y: brushPosition.y },
|
||||||
{ x: brushPosition.x, y: brushPosition.y },
|
{ x: brushPosition.x, y: brushPosition.y },
|
||||||
],
|
],
|
||||||
};
|
} as PointsData;
|
||||||
} else if (type === "circle") {
|
} else if (type === "circle") {
|
||||||
return { x: brushPosition.x, y: brushPosition.y, radius: 0 };
|
return { x: brushPosition.x, y: brushPosition.y, radius: 0 } as CircleData;
|
||||||
} else if (type === "rectangle") {
|
} else if (type === "rectangle") {
|
||||||
return {
|
return {
|
||||||
x: brushPosition.x,
|
x: brushPosition.x,
|
||||||
y: brushPosition.y,
|
y: brushPosition.y,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
};
|
} as RectData;
|
||||||
} else if (type === "triangle") {
|
} else if (type === "triangle") {
|
||||||
return {
|
return {
|
||||||
points: [
|
points: [
|
||||||
@ -104,7 +164,7 @@ export function getDefaultShapeData(type, brushPosition) {
|
|||||||
{ x: brushPosition.x, y: brushPosition.y },
|
{ x: brushPosition.x, y: brushPosition.y },
|
||||||
{ x: brushPosition.x, y: brushPosition.y },
|
{ x: brushPosition.x, y: brushPosition.y },
|
||||||
],
|
],
|
||||||
};
|
} as PointsData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +172,7 @@ export function getDefaultShapeData(type, brushPosition) {
|
|||||||
* @param {Vector2} cellSize
|
* @param {Vector2} cellSize
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
export function getGridCellRatio(cellSize) {
|
export function getGridCellRatio(cellSize: Vector2): Vector2 {
|
||||||
if (cellSize.x < cellSize.y) {
|
if (cellSize.x < cellSize.y) {
|
||||||
return { x: cellSize.y / cellSize.x, y: 1 };
|
return { x: cellSize.y / cellSize.x, y: 1 };
|
||||||
} else if (cellSize.y < cellSize.x) {
|
} else if (cellSize.y < cellSize.x) {
|
||||||
@ -131,30 +191,34 @@ export function getGridCellRatio(cellSize) {
|
|||||||
* @returns {ShapeData}
|
* @returns {ShapeData}
|
||||||
*/
|
*/
|
||||||
export function getUpdatedShapeData(
|
export function getUpdatedShapeData(
|
||||||
type,
|
type: ShapeType,
|
||||||
data,
|
data: ShapeData,
|
||||||
brushPosition,
|
brushPosition: Vector2,
|
||||||
gridCellNormalizedSize,
|
gridCellNormalizedSize: Vector2,
|
||||||
mapWidth,
|
mapWidth: number,
|
||||||
mapHeight
|
mapHeight: number
|
||||||
) {
|
): ShapeData | undefined {
|
||||||
|
// TODO: handle undefined type
|
||||||
if (type === "line") {
|
if (type === "line") {
|
||||||
|
data = data as PointsData;
|
||||||
return {
|
return {
|
||||||
points: [data.points[0], { x: brushPosition.x, y: brushPosition.y }],
|
points: [data.points[0], { x: brushPosition.x, y: brushPosition.y }],
|
||||||
};
|
} as PointsData;
|
||||||
} else if (type === "circle") {
|
} else if (type === "circle") {
|
||||||
|
data = data as CircleData;
|
||||||
const gridRatio = getGridCellRatio(gridCellNormalizedSize);
|
const gridRatio = getGridCellRatio(gridCellNormalizedSize);
|
||||||
const dif = Vector2.subtract(brushPosition, {
|
const dif = Vector2.subtract(brushPosition, {
|
||||||
x: data.x,
|
x: data.x,
|
||||||
y: data.y,
|
y: data.y,
|
||||||
});
|
});
|
||||||
const scaled = Vector2.multiply(dif, gridRatio);
|
const scaled = Vector2.multiply(dif, gridRatio);
|
||||||
const distance = Vector2.length(scaled);
|
const distance = Vector2.setLength(scaled);
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
radius: distance,
|
radius: distance,
|
||||||
};
|
};
|
||||||
} else if (type === "rectangle") {
|
} else if (type === "rectangle") {
|
||||||
|
data = data as RectData;
|
||||||
const dif = Vector2.subtract(brushPosition, { x: data.x, y: data.y });
|
const dif = Vector2.subtract(brushPosition, { x: data.x, y: data.y });
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
@ -162,6 +226,7 @@ export function getUpdatedShapeData(
|
|||||||
height: dif.y,
|
height: dif.y,
|
||||||
};
|
};
|
||||||
} else if (type === "triangle") {
|
} else if (type === "triangle") {
|
||||||
|
data = data as PointsData;
|
||||||
// Convert to absolute coordinates
|
// Convert to absolute coordinates
|
||||||
const mapSize = { x: mapWidth, y: mapHeight };
|
const mapSize = { x: mapWidth, y: mapHeight };
|
||||||
const brushPositionPixel = Vector2.multiply(brushPosition, mapSize);
|
const brushPositionPixel = Vector2.multiply(brushPosition, mapSize);
|
||||||
@ -169,7 +234,7 @@ export function getUpdatedShapeData(
|
|||||||
const points = data.points;
|
const points = data.points;
|
||||||
const startPixel = Vector2.multiply(points[0], mapSize);
|
const startPixel = Vector2.multiply(points[0], mapSize);
|
||||||
const dif = Vector2.subtract(brushPositionPixel, startPixel);
|
const dif = Vector2.subtract(brushPositionPixel, startPixel);
|
||||||
const length = Vector2.length(dif);
|
const length = Vector2.setLength(dif);
|
||||||
const direction = Vector2.normalize(dif);
|
const direction = Vector2.normalize(dif);
|
||||||
// Get the angle for a triangle who's width is the same as it's length
|
// Get the angle for a triangle who's width is the same as it's length
|
||||||
const angle = Math.atan(length / 2 / (length === 0 ? 1 : length));
|
const angle = Math.atan(length / 2 / (length === 0 ? 1 : length));
|
||||||
@ -199,10 +264,10 @@ const defaultSimplifySize = 1 / 100;
|
|||||||
* @param {Vector2} gridCellSize
|
* @param {Vector2} gridCellSize
|
||||||
* @param {number} scale
|
* @param {number} scale
|
||||||
*/
|
*/
|
||||||
export function simplifyPoints(points, gridCellSize, scale) {
|
export function simplifyPoints(points: Vector2[], gridCellSize: Vector2, scale: number): any {
|
||||||
return simplify(
|
return simplify(
|
||||||
points,
|
points,
|
||||||
(Vector2.min(gridCellSize) * defaultSimplifySize) / scale
|
(Vector2.min(gridCellSize) as number * defaultSimplifySize) / scale
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,43 +277,50 @@ export function simplifyPoints(points, gridCellSize, scale) {
|
|||||||
* @param {boolean} ignoreHidden
|
* @param {boolean} ignoreHidden
|
||||||
* @returns {Fog[]}
|
* @returns {Fog[]}
|
||||||
*/
|
*/
|
||||||
export function mergeFogShapes(shapes, ignoreHidden = true) {
|
export function mergeFogShapes(shapes: Fog[], ignoreHidden: boolean = true): Fog[] {
|
||||||
if (shapes.length === 0) {
|
if (shapes.length === 0) {
|
||||||
return shapes;
|
return shapes;
|
||||||
}
|
}
|
||||||
let geometries = [];
|
let geometries: Geom[] = [];
|
||||||
for (let shape of shapes) {
|
for (let shape of shapes) {
|
||||||
if (ignoreHidden && !shape.visible) {
|
if (ignoreHidden && !shape.visible) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const shapePoints = shape.data.points.map(({ x, y }) => [x, y]);
|
const shapePoints: Ring = shape.data.points.map(({ x, y }) => [x, y]);
|
||||||
const shapeHoles = shape.data.holes.map((hole) =>
|
const shapeHoles: Polygon = shape.data.holes.map((hole) =>
|
||||||
hole.map(({ x, y }) => [x, y])
|
hole.map(({ x, y }: { x: number, y: number }) => [x, y])
|
||||||
);
|
);
|
||||||
let shapeGeom = [[shapePoints, ...shapeHoles]];
|
let shapeGeom: Geom = [[shapePoints, ...shapeHoles]];
|
||||||
geometries.push(shapeGeom);
|
geometries.push(shapeGeom);
|
||||||
}
|
}
|
||||||
if (geometries.length === 0) {
|
if (geometries.length === 0) {
|
||||||
return geometries;
|
return [];
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let union = polygonClipping.union(...geometries);
|
let union = polygonClipping.union(geometries[0], ...geometries.slice(1));
|
||||||
let merged = [];
|
let merged: Fog[] = [];
|
||||||
for (let i = 0; i < union.length; i++) {
|
for (let i = 0; i < union.length; i++) {
|
||||||
let holes = [];
|
let holes: Vector2[][] = [];
|
||||||
if (union[i].length > 1) {
|
if (union[i].length > 1) {
|
||||||
for (let j = 1; j < union[i].length; j++) {
|
for (let j = 1; j < union[i].length; j++) {
|
||||||
holes.push(union[i][j].map(([x, y]) => ({ x, y })));
|
holes.push(union[i][j].map(([x, y]) => ({ x, y })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// find the first visible shape
|
||||||
|
let visibleShape = shapes.find((shape) => ignoreHidden || shape.visible);
|
||||||
|
if (!visibleShape) {
|
||||||
|
// TODO: handle if visible shape not found
|
||||||
|
throw Error;
|
||||||
|
}
|
||||||
merged.push({
|
merged.push({
|
||||||
// Use the data of the first visible shape as the merge
|
// Use the data of the first visible shape as the merge
|
||||||
...shapes.find((shape) => ignoreHidden || shape.visible),
|
...visibleShape,
|
||||||
id: `merged-${i}`,
|
id: `merged-${i}`,
|
||||||
data: {
|
data: {
|
||||||
points: union[i][0].map(([x, y]) => ({ x, y })),
|
points: union[i][0].map(([x, y]) => ({ x, y })),
|
||||||
holes,
|
holes,
|
||||||
},
|
},
|
||||||
|
type: "fog"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return merged;
|
return merged;
|
||||||
@ -263,7 +335,7 @@ export function mergeFogShapes(shapes, ignoreHidden = true) {
|
|||||||
* @param {boolean} maxPoints Max amount of points per shape to get bounds for
|
* @param {boolean} maxPoints Max amount of points per shape to get bounds for
|
||||||
* @returns {Vector2.BoundingBox[]}
|
* @returns {Vector2.BoundingBox[]}
|
||||||
*/
|
*/
|
||||||
export function getFogShapesBoundingBoxes(shapes, maxPoints = 0) {
|
export function getFogShapesBoundingBoxes(shapes: Fog[], maxPoints = 0): BoundingBox[] {
|
||||||
let boxes = [];
|
let boxes = [];
|
||||||
for (let shape of shapes) {
|
for (let shape of shapes) {
|
||||||
if (maxPoints > 0 && shape.data.points.length > maxPoints) {
|
if (maxPoints > 0 && shape.data.points.length > maxPoints) {
|
||||||
@ -280,14 +352,26 @@ export function getFogShapesBoundingBoxes(shapes, maxPoints = 0) {
|
|||||||
* @property {Vector2} end
|
* @property {Vector2} end
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// type Edge = {
|
||||||
|
// start: Vector2,
|
||||||
|
// end: Vector2
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef Guide
|
* @typedef Guide
|
||||||
* @property {Vector2} start
|
* @property {Vector2} start
|
||||||
* @property {Vector2} end
|
* @property {Vector2} end
|
||||||
* @property {("horizontal"|"vertical")} orientation
|
* @property {("horizontal"|"vertical")} orientation
|
||||||
* @property {number}
|
* @property {number} distance
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type Guide = {
|
||||||
|
start: Vector2,
|
||||||
|
end: Vector2,
|
||||||
|
orientation: "horizontal" | "vertical",
|
||||||
|
distance: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Vector2} brushPosition Brush position in pixels
|
* @param {Vector2} brushPosition Brush position in pixels
|
||||||
* @param {Vector2} grid
|
* @param {Vector2} grid
|
||||||
@ -299,14 +383,14 @@ export function getFogShapesBoundingBoxes(shapes, maxPoints = 0) {
|
|||||||
* @returns {Guide[]}
|
* @returns {Guide[]}
|
||||||
*/
|
*/
|
||||||
export function getGuidesFromGridCell(
|
export function getGuidesFromGridCell(
|
||||||
brushPosition,
|
brushPosition: Vector2,
|
||||||
grid,
|
grid: Grid,
|
||||||
gridCellSize,
|
gridCellSize: Size,
|
||||||
gridOffset,
|
gridOffset: Vector2,
|
||||||
gridCellOffset,
|
gridCellOffset: Vector2,
|
||||||
snappingSensitivity,
|
snappingSensitivity: number,
|
||||||
mapSize
|
mapSize: Vector2
|
||||||
) {
|
): Guide[] {
|
||||||
let boundingBoxes = [];
|
let boundingBoxes = [];
|
||||||
// Add map bounds
|
// Add map bounds
|
||||||
boundingBoxes.push(
|
boundingBoxes.push(
|
||||||
@ -366,11 +450,11 @@ export function getGuidesFromGridCell(
|
|||||||
* @returns {Guide[]}
|
* @returns {Guide[]}
|
||||||
*/
|
*/
|
||||||
export function getGuidesFromBoundingBoxes(
|
export function getGuidesFromBoundingBoxes(
|
||||||
brushPosition,
|
brushPosition: Vector2,
|
||||||
boundingBoxes,
|
boundingBoxes: BoundingBox[],
|
||||||
gridCellSize,
|
gridCellSize: Vector2, // TODO: check if this was meant to be of type Size
|
||||||
snappingSensitivity
|
snappingSensitivity: number
|
||||||
) {
|
): Guide[] {
|
||||||
let horizontalEdges = [];
|
let horizontalEdges = [];
|
||||||
let verticalEdges = [];
|
let verticalEdges = [];
|
||||||
for (let bounds of boundingBoxes) {
|
for (let bounds of boundingBoxes) {
|
||||||
@ -400,7 +484,7 @@ export function getGuidesFromBoundingBoxes(
|
|||||||
end: { x: bounds.max.x, y: bounds.max.y },
|
end: { x: bounds.max.x, y: bounds.max.y },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let guides = [];
|
let guides: Guide[] = [];
|
||||||
for (let edge of verticalEdges) {
|
for (let edge of verticalEdges) {
|
||||||
const distance = Math.abs(brushPosition.x - edge.start.x);
|
const distance = Math.abs(brushPosition.x - edge.start.x);
|
||||||
if (distance / gridCellSize.x < snappingSensitivity) {
|
if (distance / gridCellSize.x < snappingSensitivity) {
|
||||||
@ -421,8 +505,8 @@ export function getGuidesFromBoundingBoxes(
|
|||||||
* @param {Guide[]} guides
|
* @param {Guide[]} guides
|
||||||
* @returns {Guide[]}
|
* @returns {Guide[]}
|
||||||
*/
|
*/
|
||||||
export function findBestGuides(brushPosition, guides) {
|
export function findBestGuides(brushPosition: Vector2, guides: Guide[]): Guide[] {
|
||||||
let bestGuides = [];
|
let bestGuides: Guide[] = [];
|
||||||
let verticalGuide = guides
|
let verticalGuide = guides
|
||||||
.filter((guide) => guide.orientation === "vertical")
|
.filter((guide) => guide.orientation === "vertical")
|
||||||
.sort((a, b) => a.distance - b.distance)[0];
|
.sort((a, b) => a.distance - b.distance)[0];
|
@ -14,12 +14,22 @@ const GRID_TYPE_NOT_IMPLEMENTED = new Error("Grid type not implemented");
|
|||||||
* @property {Vector2} bottomRight Bottom right position of the inset
|
* @property {Vector2} bottomRight Bottom right position of the inset
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type GridInset = {
|
||||||
|
topLeft: Vector2,
|
||||||
|
bottomRight: Vector2
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef GridMeasurement
|
* @typedef GridMeasurement
|
||||||
* @property {("chebyshev"|"alternating"|"euclidean"|"manhattan")} type
|
* @property {("chebyshev"|"alternating"|"euclidean"|"manhattan")} type
|
||||||
* @property {string} scale
|
* @property {string} scale
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type GridMeasurement ={
|
||||||
|
type: ("chebyshev"|"alternating"|"euclidean"|"manhattan")
|
||||||
|
scale: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef Grid
|
* @typedef Grid
|
||||||
* @property {GridInset} inset The inset of the grid from the map
|
* @property {GridInset} inset The inset of the grid from the map
|
||||||
@ -27,6 +37,12 @@ const GRID_TYPE_NOT_IMPLEMENTED = new Error("Grid type not implemented");
|
|||||||
* @property {("square"|"hexVertical"|"hexHorizontal")} type
|
* @property {("square"|"hexVertical"|"hexHorizontal")} type
|
||||||
* @property {GridMeasurement} measurement
|
* @property {GridMeasurement} measurement
|
||||||
*/
|
*/
|
||||||
|
export type Grid = {
|
||||||
|
inset: GridInset,
|
||||||
|
size: Vector2,
|
||||||
|
type: ("square"|"hexVertical"|"hexHorizontal"),
|
||||||
|
measurement: GridMeasurement
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the size of a grid in pixels taking into account the inset
|
* Gets the size of a grid in pixels taking into account the inset
|
||||||
@ -35,7 +51,7 @@ const GRID_TYPE_NOT_IMPLEMENTED = new Error("Grid type not implemented");
|
|||||||
* @param {number} baseHeight Height of the grid in pixels before inset
|
* @param {number} baseHeight Height of the grid in pixels before inset
|
||||||
* @returns {Size}
|
* @returns {Size}
|
||||||
*/
|
*/
|
||||||
export function getGridPixelSize(grid, baseWidth, baseHeight) {
|
export function getGridPixelSize(grid: Grid, baseWidth: number, baseHeight: number): Size {
|
||||||
const width = (grid.inset.bottomRight.x - grid.inset.topLeft.x) * baseWidth;
|
const width = (grid.inset.bottomRight.x - grid.inset.topLeft.x) * baseWidth;
|
||||||
const height = (grid.inset.bottomRight.y - grid.inset.topLeft.y) * baseHeight;
|
const height = (grid.inset.bottomRight.y - grid.inset.topLeft.y) * baseHeight;
|
||||||
return new Size(width, height);
|
return new Size(width, height);
|
||||||
@ -48,7 +64,7 @@ export function getGridPixelSize(grid, baseWidth, baseHeight) {
|
|||||||
* @param {number} gridHeight Height of the grid in pixels after inset
|
* @param {number} gridHeight Height of the grid in pixels after inset
|
||||||
* @returns {Size}
|
* @returns {Size}
|
||||||
*/
|
*/
|
||||||
export function getCellPixelSize(grid, gridWidth, gridHeight) {
|
export function getCellPixelSize(grid: Grid, gridWidth: number, gridHeight: number): Size {
|
||||||
switch (grid.type) {
|
switch (grid.type) {
|
||||||
case "square":
|
case "square":
|
||||||
return new Size(gridWidth / grid.size.x, gridHeight / grid.size.y);
|
return new Size(gridWidth / grid.size.x, gridHeight / grid.size.y);
|
||||||
@ -72,7 +88,7 @@ export function getCellPixelSize(grid, gridWidth, gridHeight) {
|
|||||||
* @param {Size} cellSize Cell size in pixels
|
* @param {Size} cellSize Cell size in pixels
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
export function getCellLocation(grid, col, row, cellSize) {
|
export function getCellLocation(grid: Grid, col: number, row: number, cellSize: Size): Vector2 {
|
||||||
switch (grid.type) {
|
switch (grid.type) {
|
||||||
case "square":
|
case "square":
|
||||||
return {
|
return {
|
||||||
@ -102,7 +118,7 @@ export function getCellLocation(grid, col, row, cellSize) {
|
|||||||
* @param {Size} cellSize Cell size in pixels
|
* @param {Size} cellSize Cell size in pixels
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
export function getNearestCellCoordinates(grid, x, y, cellSize) {
|
export function getNearestCellCoordinates(grid: Grid, x: number, y: number, cellSize: Size): Vector2 {
|
||||||
switch (grid.type) {
|
switch (grid.type) {
|
||||||
case "square":
|
case "square":
|
||||||
return Vector2.divide(Vector2.floorTo({ x, y }, cellSize), cellSize);
|
return Vector2.divide(Vector2.floorTo({ x, y }, cellSize), cellSize);
|
||||||
@ -132,7 +148,7 @@ export function getNearestCellCoordinates(grid, x, y, cellSize) {
|
|||||||
* @param {Size} cellSize Cell size in pixels
|
* @param {Size} cellSize Cell size in pixels
|
||||||
* @returns {Vector2[]}
|
* @returns {Vector2[]}
|
||||||
*/
|
*/
|
||||||
export function getCellCorners(grid, x, y, cellSize) {
|
export function getCellCorners(grid: Grid, x: number, y: number, cellSize: Size): Vector2[] {
|
||||||
const position = new Vector2(x, y);
|
const position = new Vector2(x, y);
|
||||||
switch (grid.type) {
|
switch (grid.type) {
|
||||||
case "square":
|
case "square":
|
||||||
@ -172,8 +188,9 @@ export function getCellCorners(grid, x, y, cellSize) {
|
|||||||
* Get the height of a grid based off of its width
|
* Get the height of a grid based off of its width
|
||||||
* @param {Grid} grid
|
* @param {Grid} grid
|
||||||
* @param {number} gridWidth Width of the grid in pixels after inset
|
* @param {number} gridWidth Width of the grid in pixels after inset
|
||||||
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
function getGridHeightFromWidth(grid, gridWidth) {
|
function getGridHeightFromWidth(grid: Grid, gridWidth: number): number{
|
||||||
switch (grid.type) {
|
switch (grid.type) {
|
||||||
case "square":
|
case "square":
|
||||||
return (grid.size.y * gridWidth) / grid.size.x;
|
return (grid.size.y * gridWidth) / grid.size.x;
|
||||||
@ -195,7 +212,7 @@ function getGridHeightFromWidth(grid, gridWidth) {
|
|||||||
* @param {number} mapHeight Height of the map in pixels before inset
|
* @param {number} mapHeight Height of the map in pixels before inset
|
||||||
* @returns {GridInset}
|
* @returns {GridInset}
|
||||||
*/
|
*/
|
||||||
export function getGridDefaultInset(grid, mapWidth, mapHeight) {
|
export function getGridDefaultInset(grid: Grid, mapWidth: number, mapHeight: number): GridInset {
|
||||||
// Max the width of the inset and figure out the resulting height value
|
// Max the width of the inset and figure out the resulting height value
|
||||||
const insetHeightNorm = getGridHeightFromWidth(grid, mapWidth) / mapHeight;
|
const insetHeightNorm = getGridHeightFromWidth(grid, mapWidth) / mapHeight;
|
||||||
return { topLeft: { x: 0, y: 0 }, bottomRight: { x: 1, y: insetHeightNorm } };
|
return { topLeft: { x: 0, y: 0 }, bottomRight: { x: 1, y: insetHeightNorm } };
|
||||||
@ -208,7 +225,7 @@ export function getGridDefaultInset(grid, mapWidth, mapHeight) {
|
|||||||
* @param {number} mapHeight Height of the map in pixels before inset
|
* @param {number} mapHeight Height of the map in pixels before inset
|
||||||
* @returns {GridInset}
|
* @returns {GridInset}
|
||||||
*/
|
*/
|
||||||
export function getGridUpdatedInset(grid, mapWidth, mapHeight) {
|
export function getGridUpdatedInset(grid: Grid, mapWidth: number, mapHeight: number): GridInset {
|
||||||
let inset = grid.inset;
|
let inset = grid.inset;
|
||||||
// Take current inset width and use it to calculate the new height
|
// Take current inset width and use it to calculate the new height
|
||||||
if (grid.size.x > 0 && grid.size.x > 0) {
|
if (grid.size.x > 0 && grid.size.x > 0) {
|
||||||
@ -226,7 +243,7 @@ export function getGridUpdatedInset(grid, mapWidth, mapHeight) {
|
|||||||
* @param {Grid} grid
|
* @param {Grid} grid
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
export function getGridMaxZoom(grid) {
|
export function getGridMaxZoom(grid: Grid): number {
|
||||||
if (!grid) {
|
if (!grid) {
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
@ -240,7 +257,7 @@ export function getGridMaxZoom(grid) {
|
|||||||
* @param {("hexVertical"|"hexHorizontal")} type
|
* @param {("hexVertical"|"hexHorizontal")} type
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
export function hexCubeToOffset(cube, type) {
|
export function hexCubeToOffset(cube: Vector3, type: ("hexVertical"|"hexHorizontal")) {
|
||||||
if (type === "hexVertical") {
|
if (type === "hexVertical") {
|
||||||
const x = cube.x + (cube.z + (cube.z & 1)) / 2;
|
const x = cube.x + (cube.z + (cube.z & 1)) / 2;
|
||||||
const y = cube.z;
|
const y = cube.z;
|
||||||
@ -257,7 +274,7 @@ export function hexCubeToOffset(cube, type) {
|
|||||||
* @param {("hexVertical"|"hexHorizontal")} type
|
* @param {("hexVertical"|"hexHorizontal")} type
|
||||||
* @returns {Vector3}
|
* @returns {Vector3}
|
||||||
*/
|
*/
|
||||||
export function hexOffsetToCube(offset, type) {
|
export function hexOffsetToCube(offset: Vector2, type: ("hexVertical"|"hexHorizontal")) {
|
||||||
if (type === "hexVertical") {
|
if (type === "hexVertical") {
|
||||||
const x = offset.x - (offset.y + (offset.y & 1)) / 2;
|
const x = offset.x - (offset.y + (offset.y & 1)) / 2;
|
||||||
const z = offset.y;
|
const z = offset.y;
|
||||||
@ -276,8 +293,9 @@ export function hexOffsetToCube(offset, type) {
|
|||||||
* @param {Grid} grid
|
* @param {Grid} grid
|
||||||
* @param {Vector2} a
|
* @param {Vector2} a
|
||||||
* @param {Vector2} b
|
* @param {Vector2} b
|
||||||
|
* @param {Size} cellSize
|
||||||
*/
|
*/
|
||||||
export function gridDistance(grid, a, b, cellSize) {
|
export function gridDistance(grid: Grid, a: Vector2, b: Vector2, cellSize: Size) {
|
||||||
// Get grid coordinates
|
// Get grid coordinates
|
||||||
const aCoord = getNearestCellCoordinates(grid, a.x, a.y, cellSize);
|
const aCoord = getNearestCellCoordinates(grid, a.x, a.y, cellSize);
|
||||||
const bCoord = getNearestCellCoordinates(grid, b.x, b.y, cellSize);
|
const bCoord = getNearestCellCoordinates(grid, b.x, b.y, cellSize);
|
||||||
@ -290,8 +308,8 @@ export function gridDistance(grid, a, b, cellSize) {
|
|||||||
} else if (grid.measurement.type === "alternating") {
|
} else if (grid.measurement.type === "alternating") {
|
||||||
// Alternating diagonal distance like D&D 3.5 and Pathfinder
|
// Alternating diagonal distance like D&D 3.5 and Pathfinder
|
||||||
const delta = Vector2.abs(Vector2.subtract(aCoord, bCoord));
|
const delta = Vector2.abs(Vector2.subtract(aCoord, bCoord));
|
||||||
const max = Vector2.max(delta);
|
const max: any = Vector2.max(delta);
|
||||||
const min = Vector2.min(delta);
|
const min: any = Vector2.min(delta);
|
||||||
return max - min + Math.floor(1.5 * min);
|
return max - min + Math.floor(1.5 * min);
|
||||||
} else if (grid.measurement.type === "euclidean") {
|
} else if (grid.measurement.type === "euclidean") {
|
||||||
return Vector2.distance(aCoord, bCoord);
|
return Vector2.distance(aCoord, bCoord);
|
||||||
@ -322,15 +340,25 @@ export function gridDistance(grid, a, b, cellSize) {
|
|||||||
* @property {number} digits The precision of the scale
|
* @property {number} digits The precision of the scale
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type GridScale = {
|
||||||
|
multiplier: number,
|
||||||
|
unit: string,
|
||||||
|
digits: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a string representation of scale e.g. 5ft into a `GridScale`
|
* Parse a string representation of scale e.g. 5ft into a `GridScale`
|
||||||
* @param {string} scale
|
* @param {string} scale
|
||||||
* @returns {GridScale}
|
* @returns {GridScale}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function parseGridScale(scale) {
|
export function parseGridScale(scale: string): GridScale {
|
||||||
if (typeof scale === "string") {
|
if (typeof scale === "string") {
|
||||||
const match = scale.match(/(\d*)(\.\d*)?([a-zA-Z]*)/);
|
const match = scale.match(/(\d*)(\.\d*)?([a-zA-Z]*)/);
|
||||||
|
// TODO: handle case where match is not found
|
||||||
|
if (!match) {
|
||||||
|
throw Error;
|
||||||
|
}
|
||||||
const integer = parseFloat(match[1]);
|
const integer = parseFloat(match[1]);
|
||||||
const fractional = parseFloat(match[2]);
|
const fractional = parseFloat(match[2]);
|
||||||
const unit = match[3] || "";
|
const unit = match[3] || "";
|
||||||
@ -352,7 +380,7 @@ export function parseGridScale(scale) {
|
|||||||
* @param {number} n
|
* @param {number} n
|
||||||
* @returns {number[]}
|
* @returns {number[]}
|
||||||
*/
|
*/
|
||||||
function factors(n) {
|
function factors(n: number): number[] {
|
||||||
const numbers = Array.from(Array(n + 1), (_, i) => i);
|
const numbers = Array.from(Array(n + 1), (_, i) => i);
|
||||||
return numbers.filter((i) => n % i === 0);
|
return numbers.filter((i) => n % i === 0);
|
||||||
}
|
}
|
||||||
@ -364,7 +392,7 @@ function factors(n) {
|
|||||||
* @param {number} b
|
* @param {number} b
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
function gcd(a, b) {
|
function gcd(a: number, b: number): number {
|
||||||
while (b !== 0) {
|
while (b !== 0) {
|
||||||
const t = b;
|
const t = b;
|
||||||
b = a % b;
|
b = a % b;
|
||||||
@ -379,7 +407,7 @@ function gcd(a, b) {
|
|||||||
* @param {number} b
|
* @param {number} b
|
||||||
* @returns {number[]}
|
* @returns {number[]}
|
||||||
*/
|
*/
|
||||||
function dividers(a, b) {
|
function dividers(a: number, b: number): number[] {
|
||||||
const d = gcd(a, b);
|
const d = gcd(a, b);
|
||||||
return factors(d);
|
return factors(d);
|
||||||
}
|
}
|
||||||
@ -398,7 +426,7 @@ const maxGridSize = 200;
|
|||||||
* @param {number} y
|
* @param {number} y
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function gridSizeVaild(x, y) {
|
export function gridSizeVaild(x: number, y: number): boolean {
|
||||||
return (
|
return (
|
||||||
x > minGridSize && y > minGridSize && x < maxGridSize && y < maxGridSize
|
x > minGridSize && y > minGridSize && x < maxGridSize && y < maxGridSize
|
||||||
);
|
);
|
||||||
@ -408,11 +436,12 @@ export function gridSizeVaild(x, y) {
|
|||||||
* Finds a grid size for an image by finding the closest size to the average grid size
|
* Finds a grid size for an image by finding the closest size to the average grid size
|
||||||
* @param {Image} image
|
* @param {Image} image
|
||||||
* @param {number[]} candidates
|
* @param {number[]} candidates
|
||||||
* @returns {Vector2}
|
* @returns {Vector2 | null}
|
||||||
*/
|
*/
|
||||||
function gridSizeHeuristic(image, candidates) {
|
function gridSizeHeuristic(image: CanvasImageSource, candidates: number[]): Vector2 | null {
|
||||||
const width = image.width;
|
// TODO: check type for Image and CanvasSourceImage
|
||||||
const height = image.height;
|
const width: any = image.width;
|
||||||
|
const height: any = image.height;
|
||||||
// Find the best candidate by comparing the absolute z-scores of each axis
|
// Find the best candidate by comparing the absolute z-scores of each axis
|
||||||
let bestX = 1;
|
let bestX = 1;
|
||||||
let bestY = 1;
|
let bestY = 1;
|
||||||
@ -440,17 +469,23 @@ function gridSizeHeuristic(image, candidates) {
|
|||||||
* Finds the grid size of an image by running the image through a machine learning model
|
* Finds the grid size of an image by running the image through a machine learning model
|
||||||
* @param {Image} image
|
* @param {Image} image
|
||||||
* @param {number[]} candidates
|
* @param {number[]} candidates
|
||||||
* @returns {Vector2}
|
* @returns {Vector2 | null}
|
||||||
*/
|
*/
|
||||||
async function gridSizeML(image, candidates) {
|
async function gridSizeML(image: CanvasImageSource, candidates: number[]): Promise<Vector2 | null> {
|
||||||
const width = image.width;
|
// TODO: check this function because of context and CanvasImageSource -> JSDoc and Typescript do not match
|
||||||
const height = image.height;
|
const width: any = image.width;
|
||||||
|
const height: any = image.height;
|
||||||
const ratio = width / height;
|
const ratio = width / height;
|
||||||
let canvas = document.createElement("canvas");
|
let canvas = document.createElement("canvas");
|
||||||
let context = canvas.getContext("2d");
|
let context = canvas.getContext("2d");
|
||||||
canvas.width = 2048;
|
canvas.width = 2048;
|
||||||
canvas.height = Math.floor(2048 / ratio);
|
canvas.height = Math.floor(2048 / ratio);
|
||||||
|
|
||||||
|
// TODO: handle if context is null
|
||||||
|
if (!context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
let imageData = context.getImageData(
|
let imageData = context.getImageData(
|
||||||
@ -507,8 +542,10 @@ async function gridSizeML(image, candidates) {
|
|||||||
* @param {Image} image
|
* @param {Image} image
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
export async function getGridSizeFromImage(image) {
|
export async function getGridSizeFromImage(image: CanvasImageSource) {
|
||||||
const candidates = dividers(image.width, image.height);
|
const width: any = image.width;
|
||||||
|
const height: any = image.height;
|
||||||
|
const candidates = dividers(width, height);
|
||||||
let prediction;
|
let prediction;
|
||||||
|
|
||||||
// Try and use ML grid detection
|
// Try and use ML grid detection
|
@ -6,13 +6,18 @@ const lightnessDetectionOffset = 0.1;
|
|||||||
* @param {HTMLImageElement} image
|
* @param {HTMLImageElement} image
|
||||||
* @returns {boolean} True is the image is light
|
* @returns {boolean} True is the image is light
|
||||||
*/
|
*/
|
||||||
export function getImageLightness(image) {
|
export function getImageLightness(image: HTMLImageElement) {
|
||||||
const width = image.width;
|
const width = image.width;
|
||||||
const height = image.height;
|
const height = image.height;
|
||||||
let canvas = document.createElement("canvas");
|
let canvas = document.createElement("canvas");
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
let context = canvas.getContext("2d");
|
let context = canvas.getContext("2d");
|
||||||
|
if (!context) {
|
||||||
|
// TODO: handle if context is null
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
context.drawImage(image, 0, 0);
|
context.drawImage(image, 0, 0);
|
||||||
const imageData = context.getImageData(0, 0, width, height);
|
const imageData = context.getImageData(0, 0, width, height);
|
||||||
|
|
||||||
@ -44,13 +49,19 @@ export function getImageLightness(image) {
|
|||||||
* @property {number} height
|
* @property {number} height
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type CanvasImage = {
|
||||||
|
blob: Blob | null,
|
||||||
|
width: number,
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {HTMLCanvasElement} canvas
|
* @param {HTMLCanvasElement} canvas
|
||||||
* @param {string} type
|
* @param {string} type
|
||||||
* @param {number} quality
|
* @param {number} quality
|
||||||
* @returns {Promise<CanvasImage>}
|
* @returns {Promise<CanvasImage>}
|
||||||
*/
|
*/
|
||||||
export async function canvasToImage(canvas, type, quality) {
|
export async function canvasToImage(canvas: HTMLCanvasElement, type: string, quality: number): Promise<CanvasImage> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
canvas.toBlob(
|
canvas.toBlob(
|
||||||
(blob) => {
|
(blob) => {
|
||||||
@ -69,7 +80,7 @@ export async function canvasToImage(canvas, type, quality) {
|
|||||||
* @param {number} quality if image is a jpeg or webp this is the quality setting
|
* @param {number} quality if image is a jpeg or webp this is the quality setting
|
||||||
* @returns {Promise<CanvasImage>}
|
* @returns {Promise<CanvasImage>}
|
||||||
*/
|
*/
|
||||||
export async function resizeImage(image, size, type, quality) {
|
export async function resizeImage(image: HTMLImageElement, size: number, type: string, quality: number): Promise<CanvasImage> {
|
||||||
const width = image.width;
|
const width = image.width;
|
||||||
const height = image.height;
|
const height = image.height;
|
||||||
const ratio = width / height;
|
const ratio = width / height;
|
||||||
@ -82,8 +93,10 @@ export async function resizeImage(image, size, type, quality) {
|
|||||||
canvas.height = size;
|
canvas.height = size;
|
||||||
}
|
}
|
||||||
let context = canvas.getContext("2d");
|
let context = canvas.getContext("2d");
|
||||||
|
// TODO: Add error if context is empty
|
||||||
|
if (context) {
|
||||||
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
return await canvasToImage(canvas, type, quality);
|
return await canvasToImage(canvas, type, quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +109,13 @@ export async function resizeImage(image, size, type, quality) {
|
|||||||
* @property {string} id
|
* @property {string} id
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type ImageFile = {
|
||||||
|
file: Uint8Array | null,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
type: "file",
|
||||||
|
id: string
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Create a image file with resolution `size`x`size` with cover cropping
|
* Create a image file with resolution `size`x`size` with cover cropping
|
||||||
* @param {HTMLImageElement} image the image to resize
|
* @param {HTMLImageElement} image the image to resize
|
||||||
@ -104,7 +124,7 @@ export async function resizeImage(image, size, type, quality) {
|
|||||||
* @param {number} quality if image is a jpeg or webp this is the quality setting
|
* @param {number} quality if image is a jpeg or webp this is the quality setting
|
||||||
* @returns {Promise<ImageFile>}
|
* @returns {Promise<ImageFile>}
|
||||||
*/
|
*/
|
||||||
export async function createThumbnail(image, type, size = 300, quality = 0.5) {
|
export async function createThumbnail(image: HTMLImageElement, type: string, size = 300, quality = 0.5): Promise<ImageFile> {
|
||||||
let canvas = document.createElement("canvas");
|
let canvas = document.createElement("canvas");
|
||||||
canvas.width = size;
|
canvas.width = size;
|
||||||
canvas.height = size;
|
canvas.height = size;
|
||||||
@ -113,6 +133,7 @@ export async function createThumbnail(image, type, size = 300, quality = 0.5) {
|
|||||||
if (ratio > 1) {
|
if (ratio > 1) {
|
||||||
const center = image.width / 2;
|
const center = image.width / 2;
|
||||||
const halfHeight = image.height / 2;
|
const halfHeight = image.height / 2;
|
||||||
|
if (context) {
|
||||||
context.drawImage(
|
context.drawImage(
|
||||||
image,
|
image,
|
||||||
center - halfHeight,
|
center - halfHeight,
|
||||||
@ -124,9 +145,11 @@ export async function createThumbnail(image, type, size = 300, quality = 0.5) {
|
|||||||
canvas.width,
|
canvas.width,
|
||||||
canvas.height
|
canvas.height
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const center = image.height / 2;
|
const center = image.height / 2;
|
||||||
const halfWidth = image.width / 2;
|
const halfWidth = image.width / 2;
|
||||||
|
if (context) {
|
||||||
context.drawImage(
|
context.drawImage(
|
||||||
image,
|
image,
|
||||||
0,
|
0,
|
||||||
@ -139,6 +162,7 @@ export async function createThumbnail(image, type, size = 300, quality = 0.5) {
|
|||||||
canvas.height
|
canvas.height
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const thumbnailImage = await canvasToImage(canvas, type, quality);
|
const thumbnailImage = await canvasToImage(canvas, type, quality);
|
||||||
|
|
@ -6,9 +6,9 @@ import Color from "color";
|
|||||||
import Vector2 from "./Vector2";
|
import Vector2 from "./Vector2";
|
||||||
|
|
||||||
// Holes should be wound in the opposite direction as the containing points array
|
// Holes should be wound in the opposite direction as the containing points array
|
||||||
export function HoleyLine({ holes, ...props }) {
|
export function HoleyLine({ holes, ...props }: { holes: any, props: []}) {
|
||||||
// Converted from https://github.com/rfestag/konva/blob/master/src/shapes/Line.ts
|
// Converted from https://github.com/rfestag/konva/blob/master/src/shapes/Line.ts
|
||||||
function drawLine(points, context, shape) {
|
function drawLine(points: number[], context: any, shape: any) {
|
||||||
const length = points.length;
|
const length = points.length;
|
||||||
const tension = shape.tension();
|
const tension = shape.tension();
|
||||||
const closed = shape.closed();
|
const closed = shape.closed();
|
||||||
@ -76,7 +76,7 @@ export function HoleyLine({ holes, ...props }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw points and holes
|
// Draw points and holes
|
||||||
function sceneFunc(context, shape) {
|
function sceneFunc(context: any, shape: any) {
|
||||||
const points = shape.points();
|
const points = shape.points();
|
||||||
const closed = shape.closed();
|
const closed = shape.closed();
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ export function HoleyLine({ holes, ...props }) {
|
|||||||
return <Line sceneFunc={sceneFunc} {...props} />;
|
return <Line sceneFunc={sceneFunc} {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Tick({ x, y, scale, onClick, cross }) {
|
export function Tick({ x, y, scale, onClick, cross }: { x: any, y: any, scale: any, onClick: any, cross: any}) {
|
||||||
const [fill, setFill] = useState("white");
|
const [fill, setFill] = useState("white");
|
||||||
function handleEnter() {
|
function handleEnter() {
|
||||||
setFill("hsl(260, 100%, 80%)");
|
setFill("hsl(260, 100%, 80%)");
|
||||||
@ -144,13 +144,17 @@ export function Tick({ x, y, scale, onClick, cross }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Trail({ position, size, duration, segments, color }) {
|
interface TrailPoint extends Vector2 {
|
||||||
const trailRef = useRef();
|
lifetime: number
|
||||||
const pointsRef = useRef([]);
|
}
|
||||||
|
|
||||||
|
export function Trail({ position, size, duration, segments, color }: { position: Vector2, size: any, duration: number, segments: any, color: string }) {
|
||||||
|
const trailRef: React.MutableRefObject<Konva.Line | undefined> = useRef();
|
||||||
|
const pointsRef: React.MutableRefObject<TrailPoint[]> = useRef([]);
|
||||||
const prevPositionRef = useRef(position);
|
const prevPositionRef = useRef(position);
|
||||||
const positionRef = useRef(position);
|
const positionRef = useRef(position);
|
||||||
const circleRef = useRef();
|
const circleRef: React.MutableRefObject<Konva.Circle | undefined> = useRef();
|
||||||
// Color of the end of the trial
|
// Color of the end of the trail
|
||||||
const transparentColorRef = useRef(
|
const transparentColorRef = useRef(
|
||||||
Color(color).lighten(0.5).alpha(0).string()
|
Color(color).lighten(0.5).alpha(0).string()
|
||||||
);
|
);
|
||||||
@ -178,7 +182,7 @@ export function Trail({ position, size, duration, segments, color }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let prevTime = performance.now();
|
let prevTime = performance.now();
|
||||||
let request = requestAnimationFrame(animate);
|
let request = requestAnimationFrame(animate);
|
||||||
function animate(time) {
|
function animate(time: any) {
|
||||||
request = requestAnimationFrame(animate);
|
request = requestAnimationFrame(animate);
|
||||||
const deltaTime = time - prevTime;
|
const deltaTime = time - prevTime;
|
||||||
prevTime = time;
|
prevTime = time;
|
||||||
@ -199,13 +203,13 @@ export function Trail({ position, size, duration, segments, color }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the circle position to keep it in sync with the trail
|
// Update the circle position to keep it in sync with the trail
|
||||||
if (circleRef.current) {
|
if (circleRef && circleRef.current) {
|
||||||
circleRef.current.x(positionRef.current.x);
|
circleRef.current.x(positionRef.current.x);
|
||||||
circleRef.current.y(positionRef.current.y);
|
circleRef.current.y(positionRef.current.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trailRef.current) {
|
if (trailRef && trailRef.current) {
|
||||||
trailRef.current.getLayer().draw();
|
trailRef.current.getLayer()?.draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,14 +219,15 @@ export function Trail({ position, size, duration, segments, color }) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Custom scene function for drawing a trail from a line
|
// Custom scene function for drawing a trail from a line
|
||||||
function sceneFunc(context) {
|
function sceneFunc(context: any) {
|
||||||
// Resample points to ensure a smooth trail
|
// Resample points to ensure a smooth trail
|
||||||
const resampledPoints = Vector2.resample(pointsRef.current, segments);
|
const resampledPoints = Vector2.resample(pointsRef.current, segments);
|
||||||
if (resampledPoints.length === 0) {
|
if (resampledPoints.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Draws a line offset in the direction perpendicular to its travel direction
|
// Draws a line offset in the direction perpendicular to its travel direction
|
||||||
const drawOffsetLine = (from, to, alpha) => {
|
// TODO: check alpha type
|
||||||
|
const drawOffsetLine = (from: Vector2, to: Vector2, alpha: number) => {
|
||||||
const forward = Vector2.normalize(Vector2.subtract(from, to));
|
const forward = Vector2.normalize(Vector2.subtract(from, to));
|
||||||
// Rotate the forward vector 90 degrees based off of the direction
|
// Rotate the forward vector 90 degrees based off of the direction
|
||||||
const side = Vector2.rotate90(forward);
|
const side = Vector2.rotate90(forward);
|
||||||
@ -254,7 +259,7 @@ export function Trail({ position, size, duration, segments, color }) {
|
|||||||
// Create a radial gradient from the center of the trail to the tail
|
// Create a radial gradient from the center of the trail to the tail
|
||||||
const gradientCenter = resampledPoints[resampledPoints.length - 1];
|
const gradientCenter = resampledPoints[resampledPoints.length - 1];
|
||||||
const gradientEnd = resampledPoints[0];
|
const gradientEnd = resampledPoints[0];
|
||||||
const gradientRadius = Vector2.length(
|
const gradientRadius = Vector2.setLength(
|
||||||
Vector2.subtract(gradientCenter, gradientEnd)
|
Vector2.subtract(gradientCenter, gradientEnd)
|
||||||
);
|
);
|
||||||
let gradient = context.createRadialGradient(
|
let gradient = context.createRadialGradient(
|
||||||
@ -297,15 +302,24 @@ Trail.defaultProps = {
|
|||||||
* @param {Konva.Node} node
|
* @param {Konva.Node} node
|
||||||
* @returns {Vector2}
|
* @returns {Vector2}
|
||||||
*/
|
*/
|
||||||
export function getRelativePointerPosition(node) {
|
export function getRelativePointerPosition(node: Konva.Node): { x: number, y: number } | undefined {
|
||||||
let transform = node.getAbsoluteTransform().copy();
|
let transform = node.getAbsoluteTransform().copy();
|
||||||
transform.invert();
|
transform.invert();
|
||||||
let position = node.getStage().getPointerPosition();
|
// TODO: handle possible null value
|
||||||
|
let position = node.getStage()?.getPointerPosition();
|
||||||
|
if (!position) {
|
||||||
|
// TODO: handle possible null value
|
||||||
|
return;
|
||||||
|
}
|
||||||
return transform.point(position);
|
return transform.point(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRelativePointerPositionNormalized(node) {
|
export function getRelativePointerPositionNormalized(node: Konva.Node): { x: number, y: number } | undefined {
|
||||||
const relativePosition = getRelativePointerPosition(node);
|
const relativePosition = getRelativePointerPosition(node);
|
||||||
|
if (!relativePosition) {
|
||||||
|
// TODO: handle possible null value
|
||||||
|
return;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
x: relativePosition.x / node.width(),
|
x: relativePosition.x / node.width(),
|
||||||
y: relativePosition.y / node.height(),
|
y: relativePosition.y / node.height(),
|
||||||
@ -317,8 +331,8 @@ export function getRelativePointerPositionNormalized(node) {
|
|||||||
* @param {number[]} points points in an x, y alternating array
|
* @param {number[]} points points in an x, y alternating array
|
||||||
* @returns {Vector2[]} a `Vector2` array
|
* @returns {Vector2[]} a `Vector2` array
|
||||||
*/
|
*/
|
||||||
export function convertPointArray(points) {
|
export function convertPointArray(points: number[]) {
|
||||||
return points.reduce((acc, _, i, arr) => {
|
return points.reduce((acc: any[], _, i, arr) => {
|
||||||
if (i % 2 === 0) {
|
if (i % 2 === 0) {
|
||||||
acc.push({ x: arr[i], y: arr[i + 1] });
|
acc.push({ x: arr[i], y: arr[i + 1] });
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { captureException } from "@sentry/react";
|
import { captureException } from "@sentry/react";
|
||||||
|
|
||||||
export function logError(error) {
|
export function logError(error: any): void {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (process.env.REACT_APP_LOGGING === "true") {
|
if (process.env.REACT_APP_LOGGING === "true") {
|
||||||
captureException(error);
|
captureException(error);
|
@ -1,4 +1,4 @@
|
|||||||
const monsters = [
|
const monsters: string[] = [
|
||||||
"Aboleth",
|
"Aboleth",
|
||||||
"Acolyte",
|
"Acolyte",
|
||||||
"Black Dragon",
|
"Black Dragon",
|
||||||
@ -295,6 +295,6 @@ const monsters = [
|
|||||||
|
|
||||||
export default monsters;
|
export default monsters;
|
||||||
|
|
||||||
export function getRandomMonster() {
|
export function getRandomMonster(): string {
|
||||||
return monsters[Math.floor(Math.random() * monsters.length)];
|
return monsters[Math.floor(Math.random() * monsters.length)];
|
||||||
}
|
}
|
@ -8,10 +8,20 @@ import { groupBy } from "./shared";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Helper for generating search results for items
|
// Helper for generating search results for items
|
||||||
export function useSearch(items, search) {
|
export function useSearch(items: [], search: string) {
|
||||||
const [filteredItems, setFilteredItems] = useState([]);
|
// TODO: add types to search items -> don't like the never type
|
||||||
const [filteredItemScores, setFilteredItemScores] = useState({});
|
const [filteredItems, setFilteredItems]: [
|
||||||
const [fuse, setFuse] = useState();
|
filteredItems: any,
|
||||||
|
setFilteredItems: any
|
||||||
|
] = useState([]);
|
||||||
|
const [filteredItemScores, setFilteredItemScores]: [
|
||||||
|
filteredItemScores: {},
|
||||||
|
setFilteredItemScores: React.Dispatch<React.SetStateAction<{}>>
|
||||||
|
] = useState({});
|
||||||
|
const [fuse, setFuse]: [
|
||||||
|
fuse: Fuse<never> | undefined,
|
||||||
|
setFuse: React.Dispatch<Fuse<never> | undefined>
|
||||||
|
] = useState();
|
||||||
|
|
||||||
// Update search index when items change
|
// Update search index when items change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -21,14 +31,15 @@ export function useSearch(items, search) {
|
|||||||
// Perform search when search changes
|
// Perform search when search changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (search) {
|
if (search) {
|
||||||
const query = fuse.search(search);
|
const query = fuse?.search(search);
|
||||||
setFilteredItems(query.map((result) => result.item));
|
setFilteredItems(query?.map((result: any) => result.item));
|
||||||
setFilteredItemScores(
|
let reduceResult: {} | undefined = query?.reduce(
|
||||||
query.reduce(
|
(acc: {}, value: any) => ({ ...acc, [value.item.id]: value.score }),
|
||||||
(acc, value) => ({ ...acc, [value.item.id]: value.score }),
|
|
||||||
{}
|
{}
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
if (reduceResult) {
|
||||||
|
setFilteredItemScores(reduceResult);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [search, items, fuse]);
|
}, [search, items, fuse]);
|
||||||
|
|
||||||
@ -36,7 +47,12 @@ export function useSearch(items, search) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper for grouping items
|
// Helper for grouping items
|
||||||
export function useGroup(items, filteredItems, useFiltered, filteredScores) {
|
export function useGroup(
|
||||||
|
items: any[],
|
||||||
|
filteredItems: any[],
|
||||||
|
useFiltered: boolean,
|
||||||
|
filteredScores: any[]
|
||||||
|
) {
|
||||||
const itemsByGroup = groupBy(useFiltered ? filteredItems : items, "group");
|
const itemsByGroup = groupBy(useFiltered ? filteredItems : items, "group");
|
||||||
// Get the groups of the items sorting by the average score if we're filtering or the alphabetical order
|
// Get the groups of the items sorting by the average score if we're filtering or the alphabetical order
|
||||||
// with "" at the start and "default" at the end if not
|
// with "" at the start and "default" at the end if not
|
||||||
@ -44,10 +60,10 @@ export function useGroup(items, filteredItems, useFiltered, filteredScores) {
|
|||||||
if (useFiltered) {
|
if (useFiltered) {
|
||||||
itemGroups.sort((a, b) => {
|
itemGroups.sort((a, b) => {
|
||||||
const aScore = itemsByGroup[a].reduce(
|
const aScore = itemsByGroup[a].reduce(
|
||||||
(acc, item) => (acc + filteredScores[item.id]) / 2
|
(acc: any, item: any) => (acc + filteredScores[item.id]) / 2
|
||||||
);
|
);
|
||||||
const bScore = itemsByGroup[b].reduce(
|
const bScore = itemsByGroup[b].reduce(
|
||||||
(acc, item) => (acc + filteredScores[item.id]) / 2
|
(acc: any, item: any) => (acc + filteredScores[item.id]) / 2
|
||||||
);
|
);
|
||||||
return aScore - bScore;
|
return aScore - bScore;
|
||||||
});
|
});
|
||||||
@ -67,12 +83,12 @@ export function useGroup(items, filteredItems, useFiltered, filteredScores) {
|
|||||||
|
|
||||||
// Helper for handling selecting items
|
// Helper for handling selecting items
|
||||||
export function handleItemSelect(
|
export function handleItemSelect(
|
||||||
item,
|
item: any,
|
||||||
selectMode,
|
selectMode: any,
|
||||||
selectedIds,
|
selectedIds: number[],
|
||||||
setSelectedIds,
|
setSelectedIds: any,
|
||||||
itemsByGroup,
|
itemsByGroup: any,
|
||||||
itemGroups
|
itemGroups: any
|
||||||
) {
|
) {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
setSelectedIds([]);
|
setSelectedIds([]);
|
||||||
@ -83,9 +99,9 @@ export function handleItemSelect(
|
|||||||
setSelectedIds([item.id]);
|
setSelectedIds([item.id]);
|
||||||
break;
|
break;
|
||||||
case "multiple":
|
case "multiple":
|
||||||
setSelectedIds((prev) => {
|
setSelectedIds((prev: any[]) => {
|
||||||
if (prev.includes(item.id)) {
|
if (prev.includes(item.id)) {
|
||||||
return prev.filter((id) => id !== item.id);
|
return prev.filter((id: number) => id !== item.id);
|
||||||
} else {
|
} else {
|
||||||
return [...prev, item.id];
|
return [...prev, item.id];
|
||||||
}
|
}
|
||||||
@ -94,32 +110,32 @@ export function handleItemSelect(
|
|||||||
case "range":
|
case "range":
|
||||||
// Create items array
|
// Create items array
|
||||||
let items = itemGroups.reduce(
|
let items = itemGroups.reduce(
|
||||||
(acc, group) => [...acc, ...itemsByGroup[group]],
|
(acc: [], group: any) => [...acc, ...itemsByGroup[group]],
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add all items inbetween the previous selected item and the current selected
|
// Add all items inbetween the previous selected item and the current selected
|
||||||
if (selectedIds.length > 0) {
|
if (selectedIds.length > 0) {
|
||||||
const mapIndex = items.findIndex((m) => m.id === item.id);
|
const mapIndex = items.findIndex((m: any) => m.id === item.id);
|
||||||
const lastIndex = items.findIndex(
|
const lastIndex = items.findIndex(
|
||||||
(m) => m.id === selectedIds[selectedIds.length - 1]
|
(m: any) => m.id === selectedIds[selectedIds.length - 1]
|
||||||
);
|
);
|
||||||
let idsToAdd = [];
|
let idsToAdd: number[] = [];
|
||||||
let idsToRemove = [];
|
let idsToRemove: number[] = [];
|
||||||
const direction = mapIndex > lastIndex ? 1 : -1;
|
const direction = mapIndex > lastIndex ? 1 : -1;
|
||||||
for (
|
for (
|
||||||
let i = lastIndex + direction;
|
let i = lastIndex + direction;
|
||||||
direction < 0 ? i >= mapIndex : i <= mapIndex;
|
direction < 0 ? i >= mapIndex : i <= mapIndex;
|
||||||
i += direction
|
i += direction
|
||||||
) {
|
) {
|
||||||
const itemId = items[i].id;
|
const itemId: number = items[i].id;
|
||||||
if (selectedIds.includes(itemId)) {
|
if (selectedIds.includes(itemId)) {
|
||||||
idsToRemove.push(itemId);
|
idsToRemove.push(itemId);
|
||||||
} else {
|
} else {
|
||||||
idsToAdd.push(itemId);
|
idsToAdd.push(itemId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSelectedIds((prev) => {
|
setSelectedIds((prev: any[]) => {
|
||||||
let ids = [...prev, ...idsToAdd];
|
let ids = [...prev, ...idsToAdd];
|
||||||
return ids.filter((id) => !idsToRemove.includes(id));
|
return ids.filter((id) => !idsToRemove.includes(id));
|
||||||
});
|
});
|
@ -1,5 +1,5 @@
|
|||||||
export function omit(obj, keys) {
|
export function omit(obj:object, keys: string[]) {
|
||||||
let tmp = {};
|
let tmp: { [key: string]: any } = {};
|
||||||
for (let [key, value] of Object.entries(obj)) {
|
for (let [key, value] of Object.entries(obj)) {
|
||||||
if (keys.includes(key)) {
|
if (keys.includes(key)) {
|
||||||
continue;
|
continue;
|
||||||
@ -9,7 +9,7 @@ export function omit(obj, keys) {
|
|||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fromEntries(iterable) {
|
export function fromEntries(iterable: any) {
|
||||||
if (Object.fromEntries) {
|
if (Object.fromEntries) {
|
||||||
return Object.fromEntries(iterable);
|
return Object.fromEntries(iterable);
|
||||||
}
|
}
|
||||||
@ -20,32 +20,32 @@ export function fromEntries(iterable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if all tracks are muted
|
// Check to see if all tracks are muted
|
||||||
export function isStreamStopped(stream) {
|
export function isStreamStopped(stream: any) {
|
||||||
return stream.getTracks().reduce((a, b) => a && b, { mute: true });
|
return stream.getTracks().reduce((a: any, b: any) => a && b, { mute: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function roundTo(x, to) {
|
export function roundTo(x: number, to: number): number {
|
||||||
return Math.round(x / to) * to;
|
return Math.round(x / to) * to;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function floorTo(x, to) {
|
export function floorTo(x: number, to: number): number {
|
||||||
return Math.floor(x / to) * to;
|
return Math.floor(x / to) * to;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toRadians(angle) {
|
export function toRadians(angle: number): number {
|
||||||
return angle * (Math.PI / 180);
|
return angle * (Math.PI / 180);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toDegrees(angle) {
|
export function toDegrees(angle: number): number {
|
||||||
return angle * (180 / Math.PI);
|
return angle * (180 / Math.PI);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function lerp(a, b, alpha) {
|
export function lerp(a: number, b: number, alpha: number): number {
|
||||||
return a * (1 - alpha) + b * alpha;
|
return a * (1 - alpha) + b * alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Console log an image
|
// Console log an image
|
||||||
export function logImage(url, width, height) {
|
export function logImage(url: string, width: number, height: number): void {
|
||||||
const style = [
|
const style = [
|
||||||
"font-size: 1px;",
|
"font-size: 1px;",
|
||||||
`padding: ${height}px ${width}px;`,
|
`padding: ${height}px ${width}px;`,
|
||||||
@ -55,19 +55,19 @@ export function logImage(url, width, height) {
|
|||||||
console.log("%c ", style);
|
console.log("%c ", style);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEmpty(obj) {
|
export function isEmpty(obj: any): boolean {
|
||||||
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function keyBy(array, key) {
|
export function keyBy(array: any, key: any) {
|
||||||
return array.reduce(
|
return array.reduce(
|
||||||
(prev, current) => ({ ...prev, [key ? current[key] : current]: current }),
|
(prev: any, current: any) => ({ ...prev, [key ? current[key] : current]: current }),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function groupBy(array, key) {
|
export function groupBy(array: any, key: string) {
|
||||||
return array.reduce((prev, current) => {
|
return array.reduce((prev: any, current: any) => {
|
||||||
const k = current[key];
|
const k = current[key];
|
||||||
(prev[k] || (prev[k] = [])).push(current);
|
(prev[k] || (prev[k] = [])).push(current);
|
||||||
return prev;
|
return prev;
|
@ -3,10 +3,22 @@ const MILLISECONDS_IN_MINUTE = 60000;
|
|||||||
const MILLISECONDS_IN_SECOND = 1000;
|
const MILLISECONDS_IN_SECOND = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a timers duration in milliseconds
|
* @typedef Time
|
||||||
* @param {Object} t The object with an hour, minute and second property
|
* @property {number} hour
|
||||||
|
* @property {number} minute
|
||||||
|
* @property {number} second
|
||||||
*/
|
*/
|
||||||
export function getHMSDuration(t) {
|
type Time = {
|
||||||
|
hour: number,
|
||||||
|
minute: number,
|
||||||
|
second: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a timers duration in milliseconds
|
||||||
|
* @param {Time} t The object with an hour, minute and second property
|
||||||
|
*/
|
||||||
|
export function getHMSDuration(t: Time) {
|
||||||
if (!t) {
|
if (!t) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -21,7 +33,7 @@ export function getHMSDuration(t) {
|
|||||||
* Returns an object with an hour, minute and second property
|
* Returns an object with an hour, minute and second property
|
||||||
* @param {number} duration The duration in milliseconds
|
* @param {number} duration The duration in milliseconds
|
||||||
*/
|
*/
|
||||||
export function getDurationHMS(duration) {
|
export function getDurationHMS(duration: number) {
|
||||||
let workingDuration = duration;
|
let workingDuration = duration;
|
||||||
const hour = Math.floor(workingDuration / MILLISECONDS_IN_HOUR);
|
const hour = Math.floor(workingDuration / MILLISECONDS_IN_HOUR);
|
||||||
workingDuration -= hour * MILLISECONDS_IN_HOUR;
|
workingDuration -= hour * MILLISECONDS_IN_HOUR;
|
@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import App from "./App";
|
import App from "./App";
|
@ -9,23 +9,26 @@ import blobToBuffer from "../helpers/blobToBuffer";
|
|||||||
const MAX_BUFFER_SIZE = 16000;
|
const MAX_BUFFER_SIZE = 16000;
|
||||||
|
|
||||||
class Connection extends SimplePeer {
|
class Connection extends SimplePeer {
|
||||||
constructor(props) {
|
currentChunks: any;
|
||||||
|
dataChannels: any;
|
||||||
|
|
||||||
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
this.currentChunks = {};
|
this.currentChunks = {} as Blob;
|
||||||
this.dataChannels = {};
|
this.dataChannels = {};
|
||||||
this.on("data", this.handleData);
|
this.on("data", this.handleData);
|
||||||
this.on("datachannel", this.handleDataChannel);
|
this.on("datachannel", this.handleDataChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intercept the data event with decoding and chunking support
|
// Intercept the data event with decoding and chunking support
|
||||||
handleData(packed) {
|
handleData(packed: any) {
|
||||||
const unpacked = decode(packed);
|
const unpacked: any = decode(packed);
|
||||||
// If the special property __chunked is set and true
|
// If the special property __chunked is set and true
|
||||||
// The data is a partial chunk of the a larger file
|
// The data is a partial chunk of the a larger file
|
||||||
// So wait until all chunks are collected and assembled
|
// So wait until all chunks are collected and assembled
|
||||||
// before emitting the dataComplete event
|
// before emitting the dataComplete event
|
||||||
if (unpacked.__chunked) {
|
if (unpacked.__chunked) {
|
||||||
let chunk = this.currentChunks[unpacked.id] || {
|
let chunk: any = this.currentChunks[unpacked.id] || {
|
||||||
data: [],
|
data: [],
|
||||||
count: 0,
|
count: 0,
|
||||||
total: unpacked.total,
|
total: unpacked.total,
|
||||||
@ -57,7 +60,7 @@ class Connection extends SimplePeer {
|
|||||||
|
|
||||||
// Custom send function with encoding, chunking and data channel support
|
// Custom send function with encoding, chunking and data channel support
|
||||||
// Uses `write` to send the data to allow for buffer / backpressure handling
|
// Uses `write` to send the data to allow for buffer / backpressure handling
|
||||||
sendObject(object, channel) {
|
sendObject(object: any, channel: any) {
|
||||||
try {
|
try {
|
||||||
const packedData = encode(object);
|
const packedData = encode(object);
|
||||||
if (packedData.byteLength > MAX_BUFFER_SIZE) {
|
if (packedData.byteLength > MAX_BUFFER_SIZE) {
|
||||||
@ -84,23 +87,25 @@ class Connection extends SimplePeer {
|
|||||||
|
|
||||||
// Override the create data channel function to store our own named reference to it
|
// Override the create data channel function to store our own named reference to it
|
||||||
// and to use our custom data handler
|
// and to use our custom data handler
|
||||||
createDataChannel(channelName, channelConfig, opts) {
|
createDataChannel(channelName: string, channelConfig: any, opts: any) {
|
||||||
|
// TODO: resolve createDataChannel
|
||||||
|
// @ts-ignore
|
||||||
const channel = super.createDataChannel(channelName, channelConfig, opts);
|
const channel = super.createDataChannel(channelName, channelConfig, opts);
|
||||||
this.handleDataChannel(channel);
|
this.handleDataChannel(channel);
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDataChannel(channel) {
|
handleDataChannel(channel: any) {
|
||||||
const channelName = channel.channelName;
|
const channelName = channel.channelName;
|
||||||
this.dataChannels[channelName] = channel;
|
this.dataChannels[channelName] = channel;
|
||||||
channel.on("data", this.handleData.bind(this));
|
channel.on("data", this.handleData.bind(this));
|
||||||
channel.on("error", (error) => {
|
channel.on("error", (error: any) => {
|
||||||
this.emit("error", error);
|
this.emit("error", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converted from https://github.com/peers/peerjs/
|
// Converted from https://github.com/peers/peerjs/
|
||||||
chunk(data) {
|
chunk(data: any) {
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
const size = data.byteLength;
|
const size = data.byteLength;
|
||||||
const total = Math.ceil(size / MAX_BUFFER_SIZE);
|
const total = Math.ceil(size / MAX_BUFFER_SIZE);
|
@ -1,4 +1,4 @@
|
|||||||
import io from "socket.io-client";
|
import io, { Socket } from "socket.io-client";
|
||||||
import msgParser from "socket.io-msgpack-parser";
|
import msgParser from "socket.io-msgpack-parser";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
@ -6,6 +6,7 @@ import Connection from "./Connection";
|
|||||||
|
|
||||||
import { omit } from "../helpers/shared";
|
import { omit } from "../helpers/shared";
|
||||||
import { logError } from "../helpers/logging";
|
import { logError } from "../helpers/logging";
|
||||||
|
import { SimplePeerData } from "simple-peer";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} SessionPeer
|
* @typedef {object} SessionPeer
|
||||||
@ -14,6 +15,12 @@ import { logError } from "../helpers/logging";
|
|||||||
* @property {boolean} initiator - Is this peer the initiator of the connection
|
* @property {boolean} initiator - Is this peer the initiator of the connection
|
||||||
* @property {boolean} ready - Ready for data to be sent
|
* @property {boolean} ready - Ready for data to be sent
|
||||||
*/
|
*/
|
||||||
|
type SessionPeer = {
|
||||||
|
id: string;
|
||||||
|
connection: Connection;
|
||||||
|
initiator: boolean;
|
||||||
|
ready: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @callback peerReply
|
* @callback peerReply
|
||||||
@ -22,6 +29,8 @@ import { logError } from "../helpers/logging";
|
|||||||
* @param {string} channel - The channel to send to
|
* @param {string} channel - The channel to send to
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type peerReply = (id: string, data: SimplePeerData, channel: string) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Session Status Event - Status of the session has changed
|
* Session Status Event - Status of the session has changed
|
||||||
*
|
*
|
||||||
@ -50,24 +59,24 @@ class Session extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @type {io.Socket}
|
* @type {io.Socket}
|
||||||
*/
|
*/
|
||||||
socket;
|
socket: Socket = io();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mapping of socket ids to session peers
|
* A mapping of socket ids to session peers
|
||||||
*
|
*
|
||||||
* @type {Object.<string, SessionPeer>}
|
* @type {Object.<string, SessionPeer>}
|
||||||
*/
|
*/
|
||||||
peers;
|
peers: Record<string, SessionPeer>;
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
return this.socket && this.socket.id;
|
return this.socket && this.socket.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
_iceServers;
|
_iceServers: string[] = [];
|
||||||
|
|
||||||
// Store party id and password for reconnect
|
// Store party id and password for reconnect
|
||||||
_gameId;
|
_gameId: string = "";
|
||||||
_password;
|
_password: string = "";
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -81,6 +90,9 @@ class Session extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
async connect() {
|
async connect() {
|
||||||
try {
|
try {
|
||||||
|
if (!process.env.REACT_APP_ICE_SERVERS_URL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const response = await fetch(process.env.REACT_APP_ICE_SERVERS_URL);
|
const response = await fetch(process.env.REACT_APP_ICE_SERVERS_URL);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw Error("Unable to fetch ICE servers");
|
throw Error("Unable to fetch ICE servers");
|
||||||
@ -88,6 +100,9 @@ class Session extends EventEmitter {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
this._iceServers = data.iceServers;
|
this._iceServers = data.iceServers;
|
||||||
|
|
||||||
|
if (!process.env.REACT_APP_BROKER_URL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.socket = io(process.env.REACT_APP_BROKER_URL, {
|
this.socket = io(process.env.REACT_APP_BROKER_URL, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
parser: msgParser,
|
parser: msgParser,
|
||||||
@ -122,7 +137,7 @@ class Session extends EventEmitter {
|
|||||||
* @param {object} data
|
* @param {object} data
|
||||||
* @param {string} channel
|
* @param {string} channel
|
||||||
*/
|
*/
|
||||||
sendTo(sessionId, eventId, data, channel) {
|
sendTo(sessionId: string, eventId: string, data: SimplePeerData, channel: string) {
|
||||||
if (!(sessionId in this.peers)) {
|
if (!(sessionId in this.peers)) {
|
||||||
if (!this._addPeer(sessionId, true)) {
|
if (!this._addPeer(sessionId, true)) {
|
||||||
return;
|
return;
|
||||||
@ -151,7 +166,11 @@ class Session extends EventEmitter {
|
|||||||
* @param {MediaStreamTrack} track
|
* @param {MediaStreamTrack} track
|
||||||
* @param {MediaStream} stream
|
* @param {MediaStream} stream
|
||||||
*/
|
*/
|
||||||
startStreamTo(sessionId, track, stream) {
|
startStreamTo(
|
||||||
|
sessionId: string,
|
||||||
|
track: MediaStreamTrack,
|
||||||
|
stream: MediaStream
|
||||||
|
) {
|
||||||
if (!(sessionId in this.peers)) {
|
if (!(sessionId in this.peers)) {
|
||||||
if (!this._addPeer(sessionId, true)) {
|
if (!this._addPeer(sessionId, true)) {
|
||||||
return;
|
return;
|
||||||
@ -174,7 +193,7 @@ class Session extends EventEmitter {
|
|||||||
* @param {MediaStreamTrack} track
|
* @param {MediaStreamTrack} track
|
||||||
* @param {MediaStream} stream
|
* @param {MediaStream} stream
|
||||||
*/
|
*/
|
||||||
endStreamTo(sessionId, track, stream) {
|
endStreamTo(sessionId: string, track: MediaStreamTrack, stream: MediaStream) {
|
||||||
if (sessionId in this.peers) {
|
if (sessionId in this.peers) {
|
||||||
this.peers[sessionId].connection.removeTrack(track, stream);
|
this.peers[sessionId].connection.removeTrack(track, stream);
|
||||||
}
|
}
|
||||||
@ -186,7 +205,7 @@ class Session extends EventEmitter {
|
|||||||
* @param {string} gameId - the id of the party to join
|
* @param {string} gameId - the id of the party to join
|
||||||
* @param {string} password - the password of the party
|
* @param {string} password - the password of the party
|
||||||
*/
|
*/
|
||||||
async joinGame(gameId, password) {
|
async joinGame(gameId: string, password: string) {
|
||||||
if (typeof gameId !== "string" || typeof password !== "string") {
|
if (typeof gameId !== "string" || typeof password !== "string") {
|
||||||
console.error(
|
console.error(
|
||||||
"Unable to join game: invalid game ID or password",
|
"Unable to join game: invalid game ID or password",
|
||||||
@ -198,7 +217,12 @@ class Session extends EventEmitter {
|
|||||||
|
|
||||||
this._gameId = gameId;
|
this._gameId = gameId;
|
||||||
this._password = password;
|
this._password = password;
|
||||||
this.socket.emit("join_game", gameId, password, process.env.REACT_APP_VERSION);
|
this.socket.emit(
|
||||||
|
"join_game",
|
||||||
|
gameId,
|
||||||
|
password,
|
||||||
|
process.env.REACT_APP_VERSION
|
||||||
|
);
|
||||||
this.emit("status", "joining");
|
this.emit("status", "joining");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +232,7 @@ class Session extends EventEmitter {
|
|||||||
* @param {boolean} initiator
|
* @param {boolean} initiator
|
||||||
* @returns {boolean} True if peer was added successfully
|
* @returns {boolean} True if peer was added successfully
|
||||||
*/
|
*/
|
||||||
_addPeer(id, initiator) {
|
_addPeer(id: string, initiator: boolean) {
|
||||||
try {
|
try {
|
||||||
const connection = new Connection({
|
const connection = new Connection({
|
||||||
initiator,
|
initiator,
|
||||||
@ -221,15 +245,15 @@ class Session extends EventEmitter {
|
|||||||
|
|
||||||
const peer = { id, connection, initiator, ready: false };
|
const peer = { id, connection, initiator, ready: false };
|
||||||
|
|
||||||
function sendPeer(id, data, channel) {
|
const sendPeer = (id: string, data: SimplePeerData, channel: any) => {
|
||||||
peer.connection.sendObject({ id, data }, channel);
|
peer.connection.sendObject({ id, data }, channel);
|
||||||
}
|
};
|
||||||
|
|
||||||
function handleSignal(signal) {
|
const handleSignal = (signal: any) => {
|
||||||
this.socket.emit("signal", JSON.stringify({ to: peer.id, signal }));
|
this.socket.emit("signal", JSON.stringify({ to: peer.id, signal }));
|
||||||
}
|
};
|
||||||
|
|
||||||
function handleConnect() {
|
const handleConnect = () => {
|
||||||
if (peer.id in this.peers) {
|
if (peer.id in this.peers) {
|
||||||
this.peers[peer.id].ready = true;
|
this.peers[peer.id].ready = true;
|
||||||
}
|
}
|
||||||
@ -241,10 +265,14 @@ class Session extends EventEmitter {
|
|||||||
* @property {SessionPeer} peer
|
* @property {SessionPeer} peer
|
||||||
* @property {peerReply} reply
|
* @property {peerReply} reply
|
||||||
*/
|
*/
|
||||||
this.emit("peerConnect", { peer, reply: sendPeer });
|
const peerConnectEvent: { peer: SessionPeer; reply: peerReply } = {
|
||||||
}
|
peer,
|
||||||
|
reply: sendPeer,
|
||||||
|
};
|
||||||
|
this.emit("peerConnect", peerConnectEvent);
|
||||||
|
};
|
||||||
|
|
||||||
function handleDataComplete(data) {
|
const handleDataComplete = (data: any) => {
|
||||||
/**
|
/**
|
||||||
* Peer Data Event - Data received by a peer
|
* Peer Data Event - Data received by a peer
|
||||||
*
|
*
|
||||||
@ -255,15 +283,30 @@ class Session extends EventEmitter {
|
|||||||
* @property {object} data
|
* @property {object} data
|
||||||
* @property {peerReply} reply
|
* @property {peerReply} reply
|
||||||
*/
|
*/
|
||||||
this.emit("peerData", {
|
let peerDataEvent: {
|
||||||
|
peer: SessionPeer;
|
||||||
|
id: string;
|
||||||
|
data: any;
|
||||||
|
reply: peerReply;
|
||||||
|
} = {
|
||||||
peer,
|
peer,
|
||||||
id: data.id,
|
id: data.id,
|
||||||
data: data.data,
|
data: data.data,
|
||||||
reply: sendPeer,
|
reply: sendPeer,
|
||||||
});
|
};
|
||||||
}
|
console.log(`Data: ${JSON.stringify(data)}`)
|
||||||
|
this.emit("peerData", peerDataEvent);
|
||||||
|
};
|
||||||
|
|
||||||
function handleDataProgress({ id, count, total }) {
|
const handleDataProgress = ({
|
||||||
|
id,
|
||||||
|
count,
|
||||||
|
total,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
count: number;
|
||||||
|
total: number;
|
||||||
|
}) => {
|
||||||
this.emit("peerDataProgress", {
|
this.emit("peerDataProgress", {
|
||||||
peer,
|
peer,
|
||||||
id,
|
id,
|
||||||
@ -271,9 +314,9 @@ class Session extends EventEmitter {
|
|||||||
total,
|
total,
|
||||||
reply: sendPeer,
|
reply: sendPeer,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function handleTrack(track, stream) {
|
const handleTrack = (track: MediaStreamTrack, stream: MediaStream) => {
|
||||||
/**
|
/**
|
||||||
* Peer Track Added Event - A `MediaStreamTrack` was added by a peer
|
* Peer Track Added Event - A `MediaStreamTrack` was added by a peer
|
||||||
*
|
*
|
||||||
@ -283,7 +326,12 @@ class Session extends EventEmitter {
|
|||||||
* @property {MediaStreamTrack} track
|
* @property {MediaStreamTrack} track
|
||||||
* @property {MediaStream} stream
|
* @property {MediaStream} stream
|
||||||
*/
|
*/
|
||||||
this.emit("peerTrackAdded", { peer, track, stream });
|
let peerTrackAddedEvent: {
|
||||||
|
peer: SessionPeer;
|
||||||
|
track: MediaStreamTrack;
|
||||||
|
stream: MediaStream;
|
||||||
|
} = { peer, track, stream };
|
||||||
|
this.emit("peerTrackAdded", peerTrackAddedEvent);
|
||||||
track.addEventListener("mute", () => {
|
track.addEventListener("mute", () => {
|
||||||
/**
|
/**
|
||||||
* Peer Track Removed Event - A `MediaStreamTrack` was removed by a peer
|
* Peer Track Removed Event - A `MediaStreamTrack` was removed by a peer
|
||||||
@ -294,11 +342,16 @@ class Session extends EventEmitter {
|
|||||||
* @property {MediaStreamTrack} track
|
* @property {MediaStreamTrack} track
|
||||||
* @property {MediaStream} stream
|
* @property {MediaStream} stream
|
||||||
*/
|
*/
|
||||||
this.emit("peerTrackRemoved", { peer, track, stream });
|
let peerTrackRemovedEvent: {
|
||||||
|
peer: SessionPeer;
|
||||||
|
track: MediaStreamTrack;
|
||||||
|
stream: MediaStream;
|
||||||
|
} = { peer, track, stream };
|
||||||
|
this.emit("peerTrackRemoved", peerTrackRemovedEvent);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function handleClose() {
|
const handleClose = () => {
|
||||||
/**
|
/**
|
||||||
* Peer Disconnect Event - A peer has disconnected
|
* Peer Disconnect Event - A peer has disconnected
|
||||||
*
|
*
|
||||||
@ -306,14 +359,15 @@ class Session extends EventEmitter {
|
|||||||
* @type {object}
|
* @type {object}
|
||||||
* @property {SessionPeer} peer
|
* @property {SessionPeer} peer
|
||||||
*/
|
*/
|
||||||
this.emit("peerDisconnect", { peer });
|
let peerDisconnectEvent: { peer: SessionPeer } = { peer };
|
||||||
|
this.emit("peerDisconnect", peerDisconnectEvent);
|
||||||
if (peer.id in this.peers) {
|
if (peer.id in this.peers) {
|
||||||
peer.connection.destroy();
|
peer.connection.destroy();
|
||||||
this.peers = omit(this.peers, [peer.id]);
|
this.peers = omit(this.peers, [peer.id]);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function handleError(error) {
|
const handleError = (error: Error) => {
|
||||||
/**
|
/**
|
||||||
* Peer Error Event - An error occured with a peer connection
|
* Peer Error Event - An error occured with a peer connection
|
||||||
*
|
*
|
||||||
@ -322,12 +376,16 @@ class Session extends EventEmitter {
|
|||||||
* @property {SessionPeer} peer
|
* @property {SessionPeer} peer
|
||||||
* @property {Error} error
|
* @property {Error} error
|
||||||
*/
|
*/
|
||||||
this.emit("peerError", { peer, error });
|
let peerErrorEvent: { peer: SessionPeer; error: Error } = {
|
||||||
|
peer,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
this.emit("peerError", peerErrorEvent);
|
||||||
if (peer.id in this.peers) {
|
if (peer.id in this.peers) {
|
||||||
peer.connection.destroy();
|
peer.connection.destroy();
|
||||||
this.peers = omit(this.peers, [peer.id]);
|
this.peers = omit(this.peers, [peer.id]);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
peer.connection.on("signal", handleSignal.bind(this));
|
peer.connection.on("signal", handleSignal.bind(this));
|
||||||
peer.connection.on("connect", handleConnect.bind(this));
|
peer.connection.on("connect", handleConnect.bind(this));
|
||||||
@ -363,7 +421,7 @@ class Session extends EventEmitter {
|
|||||||
this.emit("gameExpired");
|
this.emit("gameExpired");
|
||||||
}
|
}
|
||||||
|
|
||||||
_handlePlayerJoined(id) {
|
_handlePlayerJoined(id: string) {
|
||||||
/**
|
/**
|
||||||
* Player Joined Event - A player has joined the game
|
* Player Joined Event - A player has joined the game
|
||||||
*
|
*
|
||||||
@ -373,7 +431,7 @@ class Session extends EventEmitter {
|
|||||||
this.emit("playerJoined", id);
|
this.emit("playerJoined", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handlePlayerLeft(id) {
|
_handlePlayerLeft(id: string) {
|
||||||
/**
|
/**
|
||||||
* Player Left Event - A player has left the game
|
* Player Left Event - A player has left the game
|
||||||
*
|
*
|
||||||
@ -387,7 +445,7 @@ class Session extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleSignal(data) {
|
_handleSignal(data: any) {
|
||||||
const { from, signal } = data;
|
const { from, signal } = data;
|
||||||
if (!(from in this.peers)) {
|
if (!(from in this.peers)) {
|
||||||
if (!this._addPeer(from, false)) {
|
if (!this._addPeer(from, false)) {
|
1
src/react-app-env.d.ts
vendored
Normal file
1
src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="react-scripts" />
|
@ -20,9 +20,13 @@ const isLocalhost = Boolean(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export function register(config) {
|
export function register(config: any) {
|
||||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
// The URL constructor is available in all browsers that support SW.
|
// The URL constructor is available in all browsers that support SW.
|
||||||
|
if (!process.env.PUBLIC_URL) {
|
||||||
|
// TODO: handle is PUBLIC_URL has not been set
|
||||||
|
return;
|
||||||
|
}
|
||||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
@ -54,9 +58,9 @@ export function register(config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerValidSW(swUrl, config) {
|
function registerValidSW(swUrl: string | URL, config: any) {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register(swUrl)
|
.register(swUrl as string)
|
||||||
.then(registration => {
|
.then(registration => {
|
||||||
registration.onupdatefound = () => {
|
registration.onupdatefound = () => {
|
||||||
const installingWorker = registration.installing;
|
const installingWorker = registration.installing;
|
||||||
@ -98,7 +102,8 @@ function registerValidSW(swUrl, config) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl, config) {
|
// TODO: handle swUrl -> type has to be handled as RequestInfo OR string | URL
|
||||||
|
function checkValidServiceWorker(swUrl: any, config: any) {
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
fetch(swUrl, {
|
fetch(swUrl, {
|
||||||
headers: { 'Service-Worker': 'script' }
|
headers: { 'Service-Worker': 'script' }
|
@ -1,6 +1,6 @@
|
|||||||
import Settings from "./helpers/Settings";
|
import Settings from "./helpers/Settings";
|
||||||
|
|
||||||
function loadVersions(settings) {
|
function loadVersions(settings: Settings) {
|
||||||
settings.version(1, () => ({
|
settings.version(1, () => ({
|
||||||
fog: {
|
fog: {
|
||||||
type: "polygon",
|
type: "polygon",
|
||||||
@ -28,17 +28,17 @@ function loadVersions(settings) {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
// v1.5.2 - Added full screen support for map and label size
|
// v1.5.2 - Added full screen support for map and label size
|
||||||
settings.version(2, (prev) => ({
|
settings.version(2, (prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
map: { fullScreen: false, labelSize: 1 },
|
map: { fullScreen: false, labelSize: 1 },
|
||||||
}));
|
}));
|
||||||
// v1.7.0 - Added game password
|
// v1.7.0 - Added game password
|
||||||
settings.version(3, (prev) => ({
|
settings.version(3, (prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
game: { usePassword: true },
|
game: { usePassword: true },
|
||||||
}));
|
}));
|
||||||
// v1.8.0 - Added pointer color, grid snapping sensitivity and remove measure
|
// v1.8.0 - Added pointer color, grid snapping sensitivity and remove measure
|
||||||
settings.version(4, (prev) => {
|
settings.version(4, (prev: any) => {
|
||||||
let newSettings = {
|
let newSettings = {
|
||||||
...prev,
|
...prev,
|
||||||
pointer: { color: "red" },
|
pointer: { color: "red" },
|
||||||
@ -48,19 +48,19 @@ function loadVersions(settings) {
|
|||||||
return newSettings;
|
return newSettings;
|
||||||
});
|
});
|
||||||
// v1.8.0 - Removed edge snapping for multilayer
|
// v1.8.0 - Removed edge snapping for multilayer
|
||||||
settings.version(5, (prev) => {
|
settings.version(5, (prev: any) => {
|
||||||
let newSettings = { ...prev };
|
let newSettings = { ...prev };
|
||||||
delete newSettings.fog.useEdgeSnapping;
|
delete newSettings.fog.useEdgeSnapping;
|
||||||
newSettings.fog.multilayer = false;
|
newSettings.fog.multilayer = false;
|
||||||
return newSettings;
|
return newSettings;
|
||||||
});
|
});
|
||||||
// v1.8.1 - Add show guides toggle
|
// v1.8.1 - Add show guides toggle
|
||||||
settings.version(6, (prev) => ({
|
settings.version(6, (prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
fog: { ...prev.fog, showGuides: true },
|
fog: { ...prev.fog, showGuides: true },
|
||||||
}));
|
}));
|
||||||
// v1.8.1 - Add fog edit opacity
|
// v1.8.1 - Add fog edit opacity
|
||||||
settings.version(7, (prev) => ({
|
settings.version(7, (prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
fog: { ...prev.fog, editOpacity: 0.5 },
|
fog: { ...prev.fog, editOpacity: 0.5 },
|
||||||
}));
|
}));
|
110
src/shortcuts.js
110
src/shortcuts.js
@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
* @param {KeyboardEvent} event
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function hasModifier(event) {
|
|
||||||
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key press without any modifiers and ignoring capitals
|
|
||||||
* @param {KeyboardEvent} event
|
|
||||||
* @param {string} key
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function singleKey(event, key) {
|
|
||||||
return (
|
|
||||||
!hasModifier(event) &&
|
|
||||||
(event.key === key || event.key === key.toUpperCase())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Keyboard} event
|
|
||||||
*/
|
|
||||||
function undo(event) {
|
|
||||||
const { key, ctrlKey, metaKey, shiftKey } = event;
|
|
||||||
return (key === "z" || key === "Z") && (ctrlKey || metaKey) && !shiftKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Keyboard} event
|
|
||||||
*/
|
|
||||||
function redo(event) {
|
|
||||||
const { key, ctrlKey, metaKey, shiftKey } = event;
|
|
||||||
return (key === "z" || key === "Z") && (ctrlKey || metaKey) && shiftKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Keyboard} event
|
|
||||||
*/
|
|
||||||
function zoomIn(event) {
|
|
||||||
const { key, ctrlKey, metaKey } = event;
|
|
||||||
return (key === "=" || key === "+") && !ctrlKey && !metaKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Keyboard} event
|
|
||||||
*/
|
|
||||||
function zoomOut(event) {
|
|
||||||
const { key, ctrlKey, metaKey } = event;
|
|
||||||
return (key === "-" || key === "_") && !ctrlKey && !metaKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @callback shortcut
|
|
||||||
* @param {KeyboardEvent} event
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {Object.<string, shortcut>}
|
|
||||||
*/
|
|
||||||
const shortcuts = {
|
|
||||||
// Tools
|
|
||||||
move: (event) => singleKey(event, " "),
|
|
||||||
moveTool: (event) => singleKey(event, "w"),
|
|
||||||
drawingTool: (event) => singleKey(event, "d"),
|
|
||||||
fogTool: (event) => singleKey(event, "f"),
|
|
||||||
measureTool: (event) => singleKey(event, "m"),
|
|
||||||
pointerTool: (event) => singleKey(event, "q"),
|
|
||||||
noteTool: (event) => singleKey(event, "n"),
|
|
||||||
// Map editor
|
|
||||||
gridNudgeUp: ({ key }) => key === "ArrowUp",
|
|
||||||
gridNudgeLeft: ({ key }) => key === "ArrowLeft",
|
|
||||||
gridNudgeRight: ({ key }) => key === "ArrowRight",
|
|
||||||
gridNudgeDown: ({ key }) => key === "ArrowDown",
|
|
||||||
// Drawing tool
|
|
||||||
drawBrush: (event) => singleKey(event, "b"),
|
|
||||||
drawPaint: (event) => singleKey(event, "p"),
|
|
||||||
drawLine: (event) => singleKey(event, "l"),
|
|
||||||
drawRect: (event) => singleKey(event, "r"),
|
|
||||||
drawCircle: (event) => singleKey(event, "c"),
|
|
||||||
drawTriangle: (event) => singleKey(event, "t"),
|
|
||||||
drawErase: (event) => singleKey(event, "e"),
|
|
||||||
drawBlend: (event) => singleKey(event, "o"),
|
|
||||||
// Fog tool
|
|
||||||
fogPolygon: (event) => singleKey(event, "p"),
|
|
||||||
fogRectangle: (event) => singleKey(event, "r"),
|
|
||||||
fogBrush: (event) => singleKey(event, "b"),
|
|
||||||
fogToggle: (event) => singleKey(event, "t"),
|
|
||||||
fogErase: (event) => singleKey(event, "e"),
|
|
||||||
fogLayer: (event) => singleKey(event, "l"),
|
|
||||||
fogPreview: (event) => singleKey(event, "f"),
|
|
||||||
fogCut: (event) => singleKey(event, "c"),
|
|
||||||
fogFinishPolygon: ({ key }) => key === "Enter",
|
|
||||||
fogCancelPolygon: ({ key }) => key === "Escape",
|
|
||||||
// Stage interaction
|
|
||||||
stageZoomIn: zoomIn,
|
|
||||||
stageZoomOut: zoomOut,
|
|
||||||
stagePrecisionZoom: ({ key }) => key === "Shift",
|
|
||||||
// Select
|
|
||||||
selectRange: ({ key }) => key === "Shift",
|
|
||||||
selectMultiple: ({ key }) => key === "Control" || key === "Meta",
|
|
||||||
// Common
|
|
||||||
undo,
|
|
||||||
redo,
|
|
||||||
delete: ({ key }) => key === "Backspace" || key === "Delete",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default shortcuts;
|
|
114
src/shortcuts.ts
Normal file
114
src/shortcuts.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* @param {KeyboardEvent} event
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function hasModifier(event: KeyboardEvent): boolean {
|
||||||
|
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key press without any modifiers and ignoring capitals
|
||||||
|
* @param {KeyboardEvent} event
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function singleKey(event: KeyboardEvent, key: string): boolean {
|
||||||
|
return (
|
||||||
|
!hasModifier(event) &&
|
||||||
|
(event.key === key || event.key === key.toUpperCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Keyboard} event
|
||||||
|
* @returns {string | boolean}
|
||||||
|
*/
|
||||||
|
function undo(event: KeyboardEvent): string | boolean {
|
||||||
|
const { key, ctrlKey, metaKey, shiftKey } = event;
|
||||||
|
return (key === "z" || key === "Z") && (ctrlKey || metaKey) && !shiftKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Keyboard} event
|
||||||
|
* @returns {string | boolean}
|
||||||
|
*/
|
||||||
|
function redo(event: KeyboardEvent): string | boolean {
|
||||||
|
const { key, ctrlKey, metaKey, shiftKey } = event;
|
||||||
|
return (key === "z" || key === "Z") && (ctrlKey || metaKey) && shiftKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Keyboard} event
|
||||||
|
* @returns {string | boolean}
|
||||||
|
*/
|
||||||
|
function zoomIn(event: KeyboardEvent): string | boolean {
|
||||||
|
const { key, ctrlKey, metaKey } = event;
|
||||||
|
return (key === "=" || key === "+") && !ctrlKey && !metaKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Keyboard} event
|
||||||
|
* @returns {string | boolean}
|
||||||
|
*/
|
||||||
|
function zoomOut(event: KeyboardEvent): string | boolean {
|
||||||
|
const { key, ctrlKey, metaKey } = event;
|
||||||
|
return (key === "-" || key === "_") && !ctrlKey && !metaKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback shortcut
|
||||||
|
* @param {KeyboardEvent} event
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Object.<string, shortcut>}
|
||||||
|
*/
|
||||||
|
const shortcuts = {
|
||||||
|
// Tools
|
||||||
|
move: (event: KeyboardEvent) => singleKey(event, " "),
|
||||||
|
moveTool: (event: KeyboardEvent) => singleKey(event, "w"),
|
||||||
|
drawingTool: (event: KeyboardEvent) => singleKey(event, "d"),
|
||||||
|
fogTool: (event: KeyboardEvent) => singleKey(event, "f"),
|
||||||
|
measureTool: (event: KeyboardEvent) => singleKey(event, "m"),
|
||||||
|
pointerTool: (event: KeyboardEvent) => singleKey(event, "q"),
|
||||||
|
noteTool: (event: KeyboardEvent) => singleKey(event, "n"),
|
||||||
|
// Map editor
|
||||||
|
gridNudgeUp: ({ key }: { key: string}) => key === "ArrowUp",
|
||||||
|
gridNudgeLeft: ({ key }: { key: string }) => key === "ArrowLeft",
|
||||||
|
gridNudgeRight: ({ key }: { key: string }) => key === "ArrowRight",
|
||||||
|
gridNudgeDown: ({ key }: { key: string }) => key === "ArrowDown",
|
||||||
|
// Drawing tool
|
||||||
|
drawBrush: (event: KeyboardEvent) => singleKey(event, "b"),
|
||||||
|
drawPaint: (event: KeyboardEvent) => singleKey(event, "p"),
|
||||||
|
drawLine: (event: KeyboardEvent) => singleKey(event, "l"),
|
||||||
|
drawRect: (event: KeyboardEvent) => singleKey(event, "r"),
|
||||||
|
drawCircle: (event: KeyboardEvent) => singleKey(event, "c"),
|
||||||
|
drawTriangle: (event: KeyboardEvent) => singleKey(event, "t"),
|
||||||
|
drawErase: (event: KeyboardEvent) => singleKey(event, "e"),
|
||||||
|
drawBlend: (event: KeyboardEvent) => singleKey(event, "o"),
|
||||||
|
// Fog tool
|
||||||
|
fogPolygon: (event: KeyboardEvent) => singleKey(event, "p"),
|
||||||
|
fogRectangle: (event: KeyboardEvent) => singleKey(event, "r"),
|
||||||
|
fogBrush: (event: KeyboardEvent) => singleKey(event, "b"),
|
||||||
|
fogToggle: (event: KeyboardEvent) => singleKey(event, "t"),
|
||||||
|
fogErase: (event: KeyboardEvent) => singleKey(event, "e"),
|
||||||
|
fogLayer: (event: KeyboardEvent) => singleKey(event, "l"),
|
||||||
|
fogPreview: (event: KeyboardEvent) => singleKey(event, "f"),
|
||||||
|
fogCut: (event: KeyboardEvent) => singleKey(event, "c"),
|
||||||
|
fogFinishPolygon: ({ key }: { key: string }) => key === "Enter",
|
||||||
|
fogCancelPolygon: ({ key }: { key: string }) => key === "Escape",
|
||||||
|
// Stage interaction
|
||||||
|
stageZoomIn: zoomIn,
|
||||||
|
stageZoomOut: zoomOut,
|
||||||
|
stagePrecisionZoom: ({ key }: { key: string }) => key === "Shift",
|
||||||
|
// Select
|
||||||
|
selectRange: ({ key }: { key: string }) => key === "Shift",
|
||||||
|
selectMultiple: ({ key }: { key: string }) => key === "Control" || key === "Meta",
|
||||||
|
// Common
|
||||||
|
undo,
|
||||||
|
redo,
|
||||||
|
delete: ({ key }: { key: string }) => key === "Backspace" || key === "Delete",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default shortcuts;
|
Loading…
Reference in New Issue
Block a user