diff --git a/package.json b/package.json index d942b02..e9af296 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "dependencies": { "@babylonjs/core": "^4.2.0", "@babylonjs/loaders": "^4.2.0", - "@dnd-kit/core": "^3.0.2", + "@dnd-kit/core": "3.0.2", "@dnd-kit/sortable": "^3.0.1", "@mitchemmc/dexie-export-import": "^1.0.1", "@msgpack/msgpack": "^2.4.1", diff --git a/src/components/Tile.js b/src/components/Tile.js index 1ea117b..04c0bae 100644 --- a/src/components/Tile.js +++ b/src/components/Tile.js @@ -1,10 +1,9 @@ import React from "react"; -import { Flex, Image as UIImage, IconButton, Box, Text, Badge } from "theme-ui"; +import { Flex, IconButton, Box, Text, Badge, Grid } from "theme-ui"; import EditTileIcon from "../icons/EditTileIcon"; function Tile({ - src, title, isSelected, onSelect, @@ -13,6 +12,8 @@ function Tile({ canEdit, badges, editTitle, + columns, + children, }) { return ( - {src && ( - - )} + + {children} + {}, @@ -126,6 +126,7 @@ Tile.defaultProps = { canEdit: false, badges: [], editTitle: "Edit", + columns: "1fr", }; export default Tile; diff --git a/src/components/dice/DiceTile.js b/src/components/dice/DiceTile.js index 230b3ad..e482908 100644 --- a/src/components/dice/DiceTile.js +++ b/src/components/dice/DiceTile.js @@ -1,16 +1,18 @@ import React from "react"; +import { Image } from "theme-ui"; import Tile from "../Tile"; function DiceTile({ dice, isSelected, onDiceSelect, onDone }) { return ( onDiceSelect(dice)} onDoubleClick={() => onDone(dice)} - /> + > + + ); } diff --git a/src/components/drag/SortableTiles.js b/src/components/drag/SortableTiles.js index e8f794e..4527ad8 100644 --- a/src/components/drag/SortableTiles.js +++ b/src/components/drag/SortableTiles.js @@ -30,8 +30,8 @@ function SortableTiles({ groups, onGroupChange, renderTile, children }) { function handleDragEnd({ active, over }) { setDragId(); if (active && over && active.id !== over.id) { - const oldIndex = groups.indexOf(active.id); - const newIndex = groups.indexOf(over.id); + const oldIndex = groups.findIndex((group) => group.id === active.id); + const newIndex = groups.findIndex((group) => group.id === over.id); onGroupChange(arrayMove(groups, oldIndex, newIndex)); } } @@ -53,7 +53,7 @@ function SortableTiles({ groups, onGroupChange, renderTile, children }) { {dragId && ( - {renderTile(dragId)} + {renderTile(groups.find((group) => group.id === dragId))} )} , diff --git a/src/components/map/MapTile.js b/src/components/map/MapTile.js index ed6ebcf..7b01c35 100644 --- a/src/components/map/MapTile.js +++ b/src/components/map/MapTile.js @@ -1,9 +1,7 @@ import React from "react"; import Tile from "../Tile"; - -import { useDataURL } from "../../contexts/AssetsContext"; -import { mapSources as defaultMapSources } from "../../maps"; +import MapTileImage from "./MapTileImage"; function MapTile({ map, @@ -14,16 +12,8 @@ function MapTile({ canEdit, badges, }) { - const mapURL = useDataURL( - map, - defaultMapSources, - undefined, - map.type === "file" - ); - return ( onMapSelect(map)} @@ -32,7 +22,9 @@ function MapTile({ canEdit={canEdit} badges={badges} editTitle="Edit Map" - /> + > + + ); } diff --git a/src/components/map/MapTileGroup.js b/src/components/map/MapTileGroup.js new file mode 100644 index 0000000..730484a --- /dev/null +++ b/src/components/map/MapTileGroup.js @@ -0,0 +1,33 @@ +import React from "react"; + +import Tile from "../Tile"; +import MapTileImage from "./MapTileImage"; + +function MapTileGroup({ + group, + maps, + isSelected, + onGroupSelect, + onOpen, + canOpen, +}) { + return ( + onGroupSelect(group)} + // onDoubleClick={() => canOpen && onOpen()} + columns="1fr 1fr" + > + {maps.slice(0, 4).map((map) => ( + + ))} + + ); +} + +export default MapTileGroup; diff --git a/src/components/map/MapTileImage.js b/src/components/map/MapTileImage.js new file mode 100644 index 0000000..037baba --- /dev/null +++ b/src/components/map/MapTileImage.js @@ -0,0 +1,18 @@ +import React from "react"; +import { Image } from "theme-ui"; + +import { useDataURL } from "../../contexts/AssetsContext"; +import { mapSources as defaultMapSources } from "../../maps"; + +function MapTileImage({ map, sx }) { + const mapURL = useDataURL( + map, + defaultMapSources, + undefined, + map.type === "file" + ); + + return ; +} + +export default MapTileImage; diff --git a/src/components/map/MapTiles.js b/src/components/map/MapTiles.js index 14f08d0..4967f2e 100644 --- a/src/components/map/MapTiles.js +++ b/src/components/map/MapTiles.js @@ -6,6 +6,7 @@ import RemoveMapIcon from "../../icons/RemoveMapIcon"; import ResetMapIcon from "../../icons/ResetMapIcon"; import MapTile from "./MapTile"; +import MapTileGroup from "./MapTileGroup"; import Link from "../Link"; import FilterBar from "../FilterBar"; @@ -53,24 +54,35 @@ function MapTiles({ (map) => map.type === "default" ); - function mapToTile(mapId) { - const map = maps.find((map) => map.id === mapId); - const isSelected = selectedMaps.includes(map); - return ( - - ); + function groupToMapTile(group) { + if (group.type === "item") { + const map = maps.find((map) => map.id === group.id); + const isSelected = selectedMaps.includes(map); + return ( + + ); + } else { + return ( + + maps.find((map) => map.id === item.id) + )} + /> + ); + } } const multipleSelected = selectedMaps.length > 1; @@ -79,7 +91,7 @@ function MapTiles({ onMapSelect()} > - {groups.map((mapId) => ( - - {mapToTile(mapId)} + {groups.map((group) => ( + + {groupToMapTile(group)} ))} diff --git a/src/components/token/TokenBar.js b/src/components/token/TokenBar.js index 25a0fb5..2c11909 100644 --- a/src/components/token/TokenBar.js +++ b/src/components/token/TokenBar.js @@ -29,6 +29,7 @@ function TokenBar({ onMapTokenStateCreate }) { function handleDragEnd({ active }) { setDragId(null); const token = tokensById[active.id]; + console.log("Drag", active); if (token) { // TODO: Get drag position const tokenState = createTokenState(token, { x: 0, y: 0 }, userId); @@ -36,8 +37,9 @@ function TokenBar({ onMapTokenStateCreate }) { } } const tokens = tokenGroups - .map((tokenId) => tokensById[tokenId]) - .filter((token) => !token.hideInSidebar) + .map((group) => tokensById[group.id]) + // TODO: Add group support + .filter((token) => token && !token.hideInSidebar) .map((token) => ( diff --git a/src/components/token/TokenTile.js b/src/components/token/TokenTile.js index b0bc0f7..1a98bae 100644 --- a/src/components/token/TokenTile.js +++ b/src/components/token/TokenTile.js @@ -1,10 +1,7 @@ import React from "react"; import Tile from "../Tile"; - -import { useDataURL } from "../../contexts/AssetsContext"; - -import { tokenSources as defaultTokenSources } from "../../tokens"; +import TokenTileImage from "./TokenTileImage"; function TokenTile({ token, @@ -14,16 +11,8 @@ function TokenTile({ canEdit, badges, }) { - const tokenURL = useDataURL( - token, - defaultTokenSources, - undefined, - token.type === "file" - ); - return ( onTokenSelect(token)} @@ -31,7 +20,9 @@ function TokenTile({ canEdit={canEdit} badges={badges} editTitle="Edit Token" - /> + > + + ); } diff --git a/src/components/token/TokenTileGroup.js b/src/components/token/TokenTileGroup.js new file mode 100644 index 0000000..d65cf0c --- /dev/null +++ b/src/components/token/TokenTileGroup.js @@ -0,0 +1,33 @@ +import React from "react"; + +import Tile from "../Tile"; +import TokenTileImage from "./TokenTileImage"; + +function TokenTileGroup({ + group, + tokens, + isSelected, + onGroupSelect, + onOpen, + canOpen, +}) { + return ( + onGroupSelect(group)} + // onDoubleClick={() => canOpen && onOpen()} + columns="1fr 1fr" + > + {tokens.slice(0, 4).map((token) => ( + + ))} + + ); +} + +export default TokenTileGroup; diff --git a/src/components/token/TokenTileImage.js b/src/components/token/TokenTileImage.js new file mode 100644 index 0000000..d4b5a15 --- /dev/null +++ b/src/components/token/TokenTileImage.js @@ -0,0 +1,19 @@ +import React from "react"; +import { Image } from "theme-ui"; + +import { useDataURL } from "../../contexts/AssetsContext"; + +import { tokenSources as defaultTokenSources } from "../../tokens"; + +function TokenTileImage({ token, sx }) { + const tokenURL = useDataURL( + token, + defaultTokenSources, + undefined, + token.type === "file" + ); + + return ; +} + +export default TokenTileImage; diff --git a/src/components/token/TokenTiles.js b/src/components/token/TokenTiles.js index 16f09b0..47332a6 100644 --- a/src/components/token/TokenTiles.js +++ b/src/components/token/TokenTiles.js @@ -7,6 +7,7 @@ import TokenHideIcon from "../../icons/TokenHideIcon"; import TokenShowIcon from "../../icons/TokenShowIcon"; import TokenTile from "./TokenTile"; +import TokenTileGroup from "./TokenTileGroup"; import Link from "../Link"; import FilterBar from "../FilterBar"; @@ -40,25 +41,37 @@ function TokenTiles({ ); let allTokensVisible = selectedTokens.every((token) => !token.hideInSidebar); - function tokenToTile(tokenId) { - const token = tokens.find((token) => token.id === tokenId); - const isSelected = selectedTokens.includes(token); - return ( - - ); + function groupToTokenTile(group) { + if (group.type === "item") { + const token = tokens.find((token) => token.id === group.id); + const isSelected = selectedTokens.includes(token); + return ( + + ); + } else { + return ( + + tokens.find((token) => token.id === item.id) + )} + /> + ); + } } const multipleSelected = selectedTokens.length > 1; @@ -82,7 +95,7 @@ function TokenTiles({ onTokenSelect()} > - {groups.map((tokenId) => ( - - {tokenToTile(tokenId)} + {groups.map((group) => ( + + {groupToTokenTile(group)} ))} diff --git a/src/contexts/MapDataContext.js b/src/contexts/MapDataContext.js index 2577d29..e56ce7e 100644 --- a/src/contexts/MapDataContext.js +++ b/src/contexts/MapDataContext.js @@ -47,7 +47,7 @@ export function MapDataProvider({ children }) { const storedStates = await database.table("states").toArray(); setMapStates(storedStates); const group = await database.table("groups").get("maps"); - const storedGroups = group.data; + const storedGroups = group.items; setMapGroups(storedGroups); setMapsLoading(false); } @@ -82,9 +82,9 @@ export function MapDataProvider({ children }) { await database.table("maps").add(map); await database.table("states").add(state); const group = await database.table("groups").get("maps"); - await database - .table("groups") - .update("maps", { data: [map.id, ...group.data] }); + await database.table("groups").update("maps", { + items: [{ id: map.id, type: "item" }, ...group.items], + }); }, [database] ); @@ -146,7 +146,7 @@ export function MapDataProvider({ children }) { async (groups) => { // Update group state immediately to avoid animation delay setMapGroups(groups); - await database.table("groups").update("maps", { data: groups }); + await database.table("groups").update("maps", { items: groups }); }, [database] ); @@ -207,7 +207,7 @@ export function MapDataProvider({ children }) { } if (change.table === "groups") { if (change.type === 2 && change.key === "maps") { - setMapGroups(change.obj.data); + setMapGroups(change.obj.items); } } } diff --git a/src/contexts/TokenDataContext.js b/src/contexts/TokenDataContext.js index d3a40c0..0b79d47 100644 --- a/src/contexts/TokenDataContext.js +++ b/src/contexts/TokenDataContext.js @@ -33,7 +33,7 @@ export function TokenDataProvider({ children }) { } setTokens(storedTokens); const group = await database.table("groups").get("tokens"); - const storedGroups = group.data; + const storedGroups = group.items; setTokenGroups(storedGroups); setTokensLoading(false); } @@ -54,9 +54,9 @@ export function TokenDataProvider({ children }) { async (token) => { await database.table("tokens").add(token); const group = await database.table("groups").get("tokens"); - await database - .table("groups") - .update("tokens", { data: [token.id, ...group.data] }); + await database.table("groups").update("tokens", { + items: [{ id: token.id, type: "item" }, ...group.items], + }); }, [database] ); @@ -99,7 +99,7 @@ export function TokenDataProvider({ children }) { async (groups) => { // Update group state immediately to avoid animation delay setTokenGroups(groups); - await database.table("groups").update("tokens", { data: groups }); + await database.table("groups").update("tokens", { items: groups }); }, [database] ); @@ -139,7 +139,7 @@ export function TokenDataProvider({ children }) { } if (change.table === "groups") { if (change.type === 2 && change.key === "tokens") { - setTokenGroups(change.obj.data); + setTokenGroups(change.obj.items); } } } diff --git a/src/database.js b/src/database.js index 35ca524..fc70dfb 100644 --- a/src/database.js +++ b/src/database.js @@ -21,8 +21,11 @@ function populate(db) { const tokens = getDefaultTokens(userId); db.table("tokens").bulkAdd(tokens); db.table("groups").bulkAdd([ - { id: "maps", data: maps.map((map) => map.id) }, - { id: "tokens", data: tokens.map((token) => token.id) }, + { id: "maps", items: maps.map((map) => ({ id: map.id, type: "item" })) }, + { + id: "tokens", + items: tokens.map((token) => ({ id: token.id, type: "item" })), + }, ]); }); } diff --git a/src/upgrade.js b/src/upgrade.js index 74760a1..941e9e7 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -639,23 +639,59 @@ export const versions = { tx.table("tokens").bulkAdd(tokens); }); }, - // v1.9.0 - + // v1.9.0 - Add new group table 33(v) { v.stores({ groups: "id" }).upgrade(async (tx) => { + function groupItems(items) { + let groups = []; + let subGroups = {}; + for (let item of items) { + if (!item.group) { + groups.push({ id: item.id, type: "item" }); + } else if (item.group in subGroups) { + subGroups[item.group].items.push({ id: item.id, type: "item" }); + } else { + subGroups[item.group] = { + id: uuid(), + type: "group", + name: item.group, + items: [{ id: item.id, type: "item" }], + }; + } + } + groups.push(...Object.values(subGroups)); + return groups; + } + let maps = await Dexie.waitFor(tx.table("maps").toArray()); maps = maps.sort((a, b) => b.created - a.created); - const mapIds = maps.map((map) => map.id); - tx.table("groups").add({ id: "maps", data: mapIds }); + const mapGroupItems = groupItems(maps); + tx.table("groups").add({ id: "maps", items: mapGroupItems }); let tokens = await Dexie.waitFor(tx.table("tokens").toArray()); tokens = tokens.sort((a, b) => b.created - a.created); - const tokenIds = tokens.map((token) => token.id); - tx.table("groups").add({ id: "tokens", data: tokenIds }); + const tokenGroupItems = groupItems(tokens); + tx.table("groups").add({ id: "tokens", items: tokenGroupItems }); + }); + }, + // v1.9.0 - Remove map and token group in respective tables + 34(v) { + v.stores({}).upgrade((tx) => { + tx.table("maps") + .toCollection() + .modify((map) => { + delete map.group; + }); + tx.table("tokens") + .toCollection() + .modify((token) => { + delete token.group; + }); }); }, }; -export const latestVersion = 33; +export const latestVersion = 34; /** * Load versions onto a database up to a specific version number diff --git a/yarn.lock b/yarn.lock index 95b1b1e..2959dd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1803,7 +1803,7 @@ dependencies: tslib "^2.0.0" -"@dnd-kit/core@^3.0.0", "@dnd-kit/core@^3.0.2": +"@dnd-kit/core@3.0.2", "@dnd-kit/core@^3.0.0": version "3.0.2" resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-3.0.2.tgz#e46ae11ef667aa5c31fddab21cf36ffd80d3ce5b" integrity sha512-L+rGnDYBb4BfYKDylzIBeODRIlJ+YVvo2iL9pVXsh317Nq7c9irCvi3XK8JnWD5QBw/3WZ5FmbPmTE91EKwKeA== @@ -11894,6 +11894,7 @@ simple-peer@feross/simple-peer#694/head: resolved "https://codeload.github.com/feross/simple-peer/tar.gz/0d08d07b83ff3b8c60401688d80642d24dfeffe2" dependencies: debug "^4.0.1" + err-code "^2.0.3" get-browser-rtc "^1.0.0" queue-microtask "^1.1.0" randombytes "^2.0.3"