From 27c30df99cfadf308258d5dfa66a707bc52baa88 Mon Sep 17 00:00:00 2001 From: Connor Lindsey Date: Thu, 16 May 2024 12:17:50 -0700 Subject: [PATCH] Make playground maps shareable by persisting data to the URL (#18) Co-authored-by: cpojer --- docs/content/examples/map-editor.tsx | 111 ++++++++++++++++++++++++++- docs/package.json | 1 + hera/editor/Types.tsx | 12 +-- pnpm-lock.yaml | 3 + 4 files changed, 120 insertions(+), 7 deletions(-) diff --git a/docs/content/examples/map-editor.tsx b/docs/content/examples/map-editor.tsx index 7d2d8aaf..7eb890b9 100644 --- a/docs/content/examples/map-editor.tsx +++ b/docs/content/examples/map-editor.tsx @@ -1,5 +1,14 @@ +import { encodeEffects } from '@deities/apollo/Effects.tsx'; import { Sniper } from '@deities/athena/info/Unit.tsx'; +import toSlug from '@deities/hephaestus/toSlug.tsx'; import MapEditor from '@deities/hera/editor/MapEditor.tsx'; +import { + MapCreateVariables, + MapObject, + MapUpdateVariables, +} from '@deities/hera/editor/Types.tsx'; +import useLocation from '@deities/ui/hooks/useLocation.tsx'; +import { useCallback, useEffect, useState } from 'react'; const viewer = { access: 'User', @@ -14,17 +23,115 @@ const viewer = { username: 'demo-maxima', } as const; +const decodeMapObject = (data: string | null): MapObject | null => { + if (!data) { + return null; + } + + try { + const maybeMapObject: MapObject | null = JSON.parse(data); + const maybeCreator = maybeMapObject?.creator || null; + const mapObject: MapObject = { + campaigns: { + edges: [], + }, + creator: maybeCreator + ? { + displayName: + typeof maybeCreator.displayName === 'string' + ? maybeCreator.displayName + : viewer.displayName, + id: + typeof maybeCreator.id === 'string' ? maybeCreator.id : viewer.id, + username: + typeof maybeCreator.username === 'string' + ? maybeCreator.username + : viewer.username, + } + : viewer, + effects: + typeof maybeMapObject?.effects === 'string' + ? maybeMapObject.effects + : '', + id: typeof maybeMapObject?.id === 'string' ? maybeMapObject.id : '', + name: typeof maybeMapObject?.name === 'string' ? maybeMapObject.name : '', + slug: typeof maybeMapObject?.slug === 'string' ? maybeMapObject.slug : '', + state: + typeof maybeMapObject?.state === 'string' ? maybeMapObject.state : '', + tags: + Array.isArray(maybeMapObject?.tags) && + maybeMapObject.tags.every((tag) => typeof tag === 'string') + ? maybeMapObject.tags + : [], + }; + return mapObject; + } catch { + return null; + } +}; + export default function MapEditorExample() { + const location = useLocation(); + const params = new URLSearchParams(location.search); + + const [mapObject, setMapObject] = useState(() => + decodeMapObject(params.get('map')), + ); + + const handleMapUpdate = useCallback( + (variables: MapCreateVariables | MapUpdateVariables) => { + setMapObject({ + campaigns: { + edges: [], + }, + creator: { + displayName: viewer.displayName, + id: viewer.id, + username: viewer.username, + }, + effects: JSON.stringify(encodeEffects(variables.effects)), + id: 'id' in variables ? variables.id : '', + name: variables.mapName, + slug: toSlug(variables.mapName), + state: JSON.stringify(variables.map.toJSON()), + tags: variables.tags, + }); + }, + [], + ); + + useEffect(() => { + const params = new URLSearchParams(window.location.search); + params.sort(); + const search = params.toString(); + + const newParams = new URLSearchParams(); + if (mapObject) { + newParams.set('map', JSON.stringify(mapObject)); + } + newParams.sort(); + const newSearch = newParams.toString(); + + if (newSearch !== search) { + window.history.pushState( + {}, + '', + window.location.pathname + `${newSearch ? `?${newSearch}` : ``}`, + ); + } + }, [mapObject]); + return (
{}} + createMap={handleMapUpdate} fogStyle="soft" + mapObject={mapObject} setHasChanges={() => {}} tiltStyle="on" - updateMap={() => {}} + updateMap={handleMapUpdate} user={viewer} />
diff --git a/docs/package.json b/docs/package.json index 0408e9e5..7c0e93bc 100644 --- a/docs/package.json +++ b/docs/package.json @@ -18,6 +18,7 @@ "@deities/apollo": "workspace:*", "@deities/art": "workspace:*", "@deities/athena": "workspace:*", + "@deities/hephaestus": "workspace:*", "@deities/hera": "workspace:*", "@deities/hermes": "workspace:*", "@deities/ui": "workspace:*", diff --git a/hera/editor/Types.tsx b/hera/editor/Types.tsx index 4c0146e0..161b3f55 100644 --- a/hera/editor/Types.tsx +++ b/hera/editor/Types.tsx @@ -68,22 +68,24 @@ export type SetMapFunction = ( ) => void; type MapSaveType = 'New' | 'Update' | 'Disk' | 'Export'; -type MapCreateVariables = Readonly<{ +export type MapCreateVariables = Readonly<{ effects: Effects; map: MapData; mapName: string; tags: ReadonlyArray; }>; +export type MapUpdateVariables = MapCreateVariables & + Readonly<{ + id: string; + }>; + export type MapCreateFunction = ( variables: MapCreateVariables, setSaveState: (state: MapEditorSaveState) => void, ) => void; export type MapUpdateFunction = ( - variables: MapCreateVariables & - Readonly<{ - id: string; - }>, + variables: MapUpdateVariables, type: MapSaveType, setSaveState: (state: MapEditorSaveState) => void, ) => void; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c6e9f2b..a001bea6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -579,6 +579,9 @@ importers: '@deities/athena': specifier: workspace:* version: link:../athena + '@deities/hephaestus': + specifier: workspace:* + version: link:../hephaestus '@deities/hera': specifier: workspace:* version: link:../hera