grungnet/src/database.ts

469 lines
14 KiB
TypeScript
Raw Normal View History

// eslint-disable-next-line no-unused-vars
2021-05-25 03:35:26 -04:00
import Dexie, { Version, DexieOptions, Transaction } from "dexie";
import "dexie-observable";
2021-03-11 19:19:12 -05:00
import shortid from "shortid";
import blobToBuffer from "./helpers/blobToBuffer";
2021-05-25 03:35:26 -04:00
import { getGridDefaultInset, Grid } from "./helpers/grid";
import { convertOldActionsToShapes } from "./actions";
import { createThumbnail } from "./helpers/image";
// Helper to create a thumbnail for a file in a db
2021-05-25 03:35:26 -04:00
async function createDataThumbnail(data: any) {
let url: string;
2021-02-24 16:55:43 -05:00
if (data?.resolutions?.low?.file) {
url = URL.createObjectURL(new Blob([data.resolutions.low.file]));
} else {
url = URL.createObjectURL(new Blob([data.file]));
}
return await Dexie.waitFor(
new Promise((resolve) => {
let image = new Image();
image.onload = async () => {
2021-05-25 03:35:26 -04:00
// TODO: confirm parameter for type here
const thumbnail = await createThumbnail(image, "file");
resolve(thumbnail);
};
image.src = url;
2021-02-23 05:03:05 -05:00
}),
60000 * 10 // 10 minute timeout
);
}
/**
* @callback VersionCallback
* @param {Version} version
*/
2021-05-25 03:35:26 -04:00
type VersionCallback = (version: Version) => void
/**
* Mapping of version number to their upgrade function
* @type {Object.<number, VersionCallback>}
*/
2021-05-25 03:35:26 -04:00
const versions: Record<number, VersionCallback> = {
2020-05-03 08:12:39 -04:00
// v1.2.0
2021-05-25 03:35:26 -04:00
1(v: Version) {
v.stores({
maps: "id, owner",
states: "mapId",
tokens: "id, owner",
user: "key",
});
},
2020-05-03 08:12:39 -04:00
// v1.2.1 - Move from blob files to array buffers
2021-05-25 03:35:26 -04:00
2(v: Version) {
v.stores({}).upgrade(async (tx: Transaction) => {
2020-05-03 08:12:39 -04:00
const maps = await Dexie.waitFor(tx.table("maps").toArray());
2021-05-25 03:35:26 -04:00
let mapBuffers: any = {};
2020-05-03 08:12:39 -04:00
for (let map of maps) {
mapBuffers[map.id] = await Dexie.waitFor(blobToBuffer(map.file));
}
return tx
.table("maps")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((map: any) => {
2020-05-03 08:12:39 -04:00
map.file = mapBuffers[map.id];
});
});
},
2020-05-18 02:31:32 -04:00
// v1.3.0 - Added new default tokens
2021-05-25 03:35:26 -04:00
3(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
2020-05-18 02:31:32 -04:00
return tx
.table("states")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((state: any) => {
function mapTokenId(id: any) {
2020-05-18 02:31:32 -04:00
switch (id) {
case "__default-Axes":
return "__default-Barbarian";
2020-05-18 02:31:32 -04:00
case "__default-Bird":
return "__default-Druid";
2020-05-18 02:31:32 -04:00
case "__default-Book":
return "__default-Wizard";
2020-05-18 02:31:32 -04:00
case "__default-Crown":
return "__default-Humanoid";
2020-05-18 02:31:32 -04:00
case "__default-Dragon":
return "__default-Dragon";
2020-05-18 02:31:32 -04:00
case "__default-Eye":
return "__default-Warlock";
2020-05-18 02:31:32 -04:00
case "__default-Fist":
return "__default-Monk";
2020-05-18 02:31:32 -04:00
case "__default-Horse":
return "__default-Fey";
2020-05-18 02:31:32 -04:00
case "__default-Leaf":
return "__default-Druid";
2020-05-18 02:31:32 -04:00
case "__default-Lion":
return "__default-Monstrosity";
2020-05-18 02:31:32 -04:00
case "__default-Money":
return "__default-Humanoid";
2020-05-18 02:31:32 -04:00
case "__default-Moon":
return "__default-Cleric";
2020-05-18 02:31:32 -04:00
case "__default-Potion":
return "__default-Sorcerer";
2020-05-18 02:31:32 -04:00
case "__default-Shield":
return "__default-Paladin";
2020-05-18 02:31:32 -04:00
case "__default-Skull":
return "__default-Undead";
2020-05-18 02:31:32 -04:00
case "__default-Snake":
return "__default-Beast";
2020-05-18 02:31:32 -04:00
case "__default-Sun":
return "__default-Cleric";
2020-05-18 02:31:32 -04:00
case "__default-Swords":
return "__default-Fighter";
2020-05-18 02:31:32 -04:00
case "__default-Tree":
return "__default-Plant";
2020-05-18 02:31:32 -04:00
case "__default-Triangle":
return "__default-Sorcerer";
2020-05-18 02:31:32 -04:00
default:
return "__default-Fighter";
2020-05-18 02:31:32 -04:00
}
}
for (let stateId in state.tokens) {
state.tokens[stateId].tokenId = mapTokenId(
state.tokens[stateId].tokenId
);
state.tokens[stateId].lastEditedBy = "";
state.tokens[stateId].rotation = 0;
2020-05-18 02:31:32 -04:00
}
});
});
},
2020-05-31 02:25:05 -04:00
// v1.3.1 - Added show grid option
2021-05-25 03:35:26 -04:00
4(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
2020-05-31 02:25:05 -04:00
return tx
.table("maps")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((map: any) => {
2020-05-31 02:25:05 -04:00
map.showGrid = false;
});
});
},
// v1.4.0 - Added fog subtraction
2021-05-25 03:35:26 -04:00
5(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
return tx
.table("states")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((state: any) => {
for (let fogAction of state.fogDrawActions) {
if (fogAction.type === "add" || fogAction.type === "edit") {
for (let shape of fogAction.shapes) {
shape.data.holes = [];
}
}
}
});
});
},
// v1.4.2 - Added map resolutions
2021-05-25 03:35:26 -04:00
6(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
return tx
.table("maps")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((map: any) => {
map.resolutions = {};
map.quality = "original";
});
});
},
2020-08-05 20:07:10 -04:00
// v1.5.0 - Fixed default token rogue spelling
2021-05-25 03:35:26 -04:00
7(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
2020-08-05 20:07:10 -04:00
return tx
.table("states")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((state: any) => {
2020-08-05 20:07:10 -04:00
for (let id in state.tokens) {
if (state.tokens[id].tokenId === "__default-Rouge") {
state.tokens[id].tokenId = "__default-Rogue";
}
}
});
});
},
// v1.5.0 - Added map snap to grid option
2021-05-25 03:35:26 -04:00
8(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
return tx
.table("maps")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((map: any) => {
map.snapToGrid = true;
});
});
},
// v1.5.1 - Added lock, visibility and modified to tokens
2021-05-25 03:35:26 -04:00
9(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
return tx
.table("states")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((state: any) => {
for (let id in state.tokens) {
state.tokens[id].lastModifiedBy = state.tokens[id].lastEditedBy;
delete state.tokens[id].lastEditedBy;
state.tokens[id].lastModified = Date.now();
state.tokens[id].locked = false;
state.tokens[id].visible = true;
}
});
});
},
2020-08-27 05:09:16 -04:00
// v1.5.1 - Added token prop category and remove isVehicle bool
2021-05-25 03:35:26 -04:00
10(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
2020-08-27 05:09:16 -04:00
return tx
.table("tokens")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((token: any) => {
2020-08-27 05:09:16 -04:00
token.category = token.isVehicle ? "vehicle" : "character";
delete token.isVehicle;
});
});
},
// v1.5.2 - Added automatic cache invalidation to maps
2021-05-25 03:35:26 -04:00
11(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
return tx
.table("maps")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((map: any) => {
map.lastUsed = map.lastModified;
});
});
},
// v1.5.2 - Added automatic cache invalidation to tokens
2021-05-25 03:35:26 -04:00
12(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
return tx
.table("tokens")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((token: any) => {
token.lastUsed = token.lastModified;
});
});
},
// v1.6.0 - Added map grouping and grid scale and offset
2021-05-25 03:35:26 -04:00
13(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
2020-10-01 01:05:30 -04:00
return tx
.table("maps")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((map: any) => {
2020-10-01 01:05:30 -04:00
map.group = "";
map.grid = {
size: { x: map.gridX, y: map.gridY },
inset: getGridDefaultInset(
2021-05-25 03:35:26 -04:00
{ size: { x: map.gridX, y: map.gridY }, type: "square" } as Grid,
map.width,
map.height
),
type: "square",
};
delete map.gridX;
delete map.gridY;
delete map.gridType;
2020-10-01 01:05:30 -04:00
});
});
},
// v1.6.0 - Added token grouping
2021-05-25 03:35:26 -04:00
14(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
return tx
.table("tokens")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((token: any) => {
token.group = "";
});
});
},
// v1.6.1 - Added width and height to tokens
2021-05-25 03:35:26 -04:00
15(v: Version) {
v.stores({}).upgrade(async (tx: Transaction) => {
const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
2021-05-25 03:35:26 -04:00
let tokenSizes: any = {};
for (let token of tokens) {
const url = URL.createObjectURL(new Blob([token.file]));
let image = new Image();
tokenSizes[token.id] = await Dexie.waitFor(
new Promise((resolve) => {
image.onload = () => {
resolve({ width: image.width, height: image.height });
};
image.src = url;
})
);
}
return tx
.table("tokens")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((token: any) => {
token.width = tokenSizes[token.id].width;
token.height = tokenSizes[token.id].height;
});
});
},
2020-11-03 01:15:39 -05:00
// v1.7.0 - Added note tool
2021-05-25 03:35:26 -04:00
16(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
2020-11-03 01:15:39 -05:00
return tx
.table("states")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((state: any) => {
2020-11-03 01:15:39 -05:00
state.notes = {};
2020-11-05 00:21:52 -05:00
state.editFlags = [...state.editFlags, "notes"];
2020-11-03 01:15:39 -05:00
});
});
},
// 1.7.0 (hotfix) - Optimized fog shape edits to only include needed data
2021-05-25 03:35:26 -04:00
17(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
return tx
.table("states")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((state: any) => {
for (let i = 0; i < state.fogDrawActions.length; i++) {
const action = state.fogDrawActions[i];
if (action && action.type === "edit") {
for (let j = 0; j < action.shapes.length; j++) {
const shape = action.shapes[j];
const temp = { ...shape };
state.fogDrawActions[i].shapes[j] = {
id: temp.id,
visible: temp.visible,
};
}
}
}
});
});
},
// 1.8.0 - Added note text only mode, converted draw and fog representations
2021-05-25 03:35:26 -04:00
18(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
2021-01-24 18:03:20 -05:00
return tx
.table("states")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((state: any) => {
2021-01-24 18:03:20 -05:00
for (let id in state.notes) {
state.notes[id].textOnly = false;
}
state.drawShapes = convertOldActionsToShapes(
state.mapDrawActions,
state.mapDrawActionIndex
);
state.fogShapes = convertOldActionsToShapes(
state.fogDrawActions,
state.fogDrawActionIndex
);
delete state.mapDrawActions;
delete state.mapDrawActionIndex;
delete state.fogDrawActions;
delete state.fogDrawActionIndex;
2021-01-24 18:03:20 -05:00
});
});
},
// 1.8.0 - Add thumbnail to maps and add measurement to grid
2021-05-25 03:35:26 -04:00
19(v: Version) {
v.stores({}).upgrade(async (tx: Transaction) => {
2021-02-24 16:55:43 -05:00
const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
.value;
const maps = await Dexie.waitFor(tx.table("maps").toArray());
2021-05-25 03:35:26 -04:00
const thumbnails: any = {};
for (let map of maps) {
2021-02-23 04:53:29 -05:00
try {
2021-02-24 16:55:43 -05:00
if (map.owner === userId) {
thumbnails[map.id] = await createDataThumbnail(map);
}
2021-02-23 04:53:29 -05:00
} catch {}
}
return tx
.table("maps")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((map: any) => {
map.thumbnail = thumbnails[map.id];
map.grid.measurement = { type: "chebyshev", scale: "5ft" };
});
});
},
2021-02-08 01:08:29 -05:00
// 1.8.0 - Add thumbnail to tokens
2021-05-25 03:35:26 -04:00
20(v: Version) {
v.stores({}).upgrade(async (tx: Transaction) => {
2021-02-24 16:55:43 -05:00
const userId = (await Dexie.waitFor(tx.table("user").get("userId")))
.value;
const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
2021-05-25 03:35:26 -04:00
const thumbnails: any = {};
for (let token of tokens) {
2021-02-23 04:53:29 -05:00
try {
2021-02-24 16:55:43 -05:00
if (token.owner === userId) {
thumbnails[token.id] = await createDataThumbnail(token);
}
2021-02-23 04:53:29 -05:00
} catch {}
}
return tx
.table("tokens")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((token: any) => {
token.thumbnail = thumbnails[token.id];
});
});
},
// 1.8.0 - Upgrade for Dexie.Observable
2021-05-25 03:35:26 -04:00
21(v: Version) {
v.stores({});
},
2021-03-11 19:19:12 -05:00
// v1.8.1 - Shorten fog shape ids
2021-05-25 03:35:26 -04:00
22(v: Version) {
v.stores({}).upgrade((tx: Transaction) => {
2021-03-11 19:19:12 -05:00
return tx
.table("states")
.toCollection()
2021-05-25 03:35:26 -04:00
.modify((state: any) => {
2021-03-11 19:19:12 -05:00
for (let id of Object.keys(state.fogShapes)) {
const newId = shortid.generate();
state.fogShapes[newId] = state.fogShapes[id];
state.fogShapes[newId].id = newId;
delete state.fogShapes[id];
}
});
});
},
};
2021-03-11 19:19:12 -05:00
const latestVersion = 22;
/**
* Load versions onto a database up to a specific version number
* @param {Dexie} db
* @param {number=} upTo version number to load up to, latest version if undefined
*/
2021-05-25 03:35:26 -04:00
export function loadVersions(db: Dexie, upTo = latestVersion) {
for (let versionNumber = 1; versionNumber <= upTo; versionNumber++) {
versions[versionNumber](db.version(versionNumber));
}
}
/**
* Get a Dexie database with a name and versions applied
* @param {DexieOptions} options
* @param {string=} name
* @param {number=} versionNumber
* @returns {Dexie}
*/
export function getDatabase(
2021-05-25 03:35:26 -04:00
options: DexieOptions,
name = "OwlbearRodeoDB",
versionNumber = latestVersion
) {
let db = new Dexie(name, options);
loadVersions(db, versionNumber);
return db;
}