diff --git a/app/build/overview/page.tsx b/app/build/overview/page.tsx index 976b6258..ffb53df2 100644 --- a/app/build/overview/page.tsx +++ b/app/build/overview/page.tsx @@ -1,13 +1,15 @@ "use client" import { ArrowDown } from "@carbon/icons-react" import { pipe } from "fp-ts/lib/function" +import dynamic from "next/dynamic" import { Fragment } from "react" import { A } from "~/utils/functions" import { useAnalyseData } from "../../analyse/state/data" import { useSiteCurrency } from "../../design/state/siteCtx" -import HousesView from "./HousesView" import css from "./page.module.css" +const HousesView = dynamic(() => import("./HousesView"), { ssr: false }) + const OverviewIndex = () => { const { formatWithSymbol } = useSiteCurrency() diff --git a/app/design/page.tsx b/app/design/page.tsx index 6f4f5848..dd860380 100644 --- a/app/design/page.tsx +++ b/app/design/page.tsx @@ -1,3 +1,4 @@ +"use client" import DataInit from "~/data/DataInit" import { TrpcProvider } from "../ui/TrpcProvider" import GroupedApp from "./ui-3d/grouped/GroupedApp" diff --git a/app/design/state/houses.ts b/app/design/state/houses.ts index ed5136db..6bf75454 100644 --- a/app/design/state/houses.ts +++ b/app/design/state/houses.ts @@ -1,3 +1,4 @@ +import { Module } from "@/server/data/modules" import { values } from "fp-ts-std/Record" import { pipe } from "fp-ts/lib/function" import { none, some } from "fp-ts/lib/Option" @@ -8,12 +9,11 @@ import { useEffect, useMemo } from "react" import { useKey } from "react-use" import { Vector3 } from "three" import { proxy, subscribe, useSnapshot } from "valtio" -import { BUILDX_LOCAL_STORAGE_HOUSES_KEY } from "./constants" +import { A, R, RA, RR, S } from "~/utils/functions" import { getHousesFromLocalStorage, House, Houses } from "../../data/houses" -import { Module } from "@/server/data/modules" -import { A, R, RA, RNEA, RR, S } from "~/utils/functions" -import { useModules, useSystemModules } from "../../data/modules" import { useHouseTypes } from "../../data/houseTypes" +import { useModules, useSystemModules } from "../../data/modules" +import { BUILDX_LOCAL_STORAGE_HOUSES_KEY } from "./constants" const houses = proxy(getHousesFromLocalStorage()) @@ -30,12 +30,10 @@ export const useLocallyStoredHouses = () => ) export const useHouses = () => { - useLocallyStoredHouses() return useSnapshot(houses) as typeof houses } export const useHouseKeys = () => { - useLocallyStoredHouses() return pipe(useSnapshot(houses) as typeof houses, RR.keys) } diff --git a/app/design/state/sharing/index.ts b/app/design/state/sharing/index.ts new file mode 100644 index 00000000..b2fd1151 --- /dev/null +++ b/app/design/state/sharing/index.ts @@ -0,0 +1,47 @@ +import base64url from "base64url" +import { Remote, wrap } from "comlink" +import { flow } from "fp-ts/lib/function" +import { inflate } from "pako" +import { useEffect, useRef } from "react" +import { snapshot } from "valtio" +import { getMapPolygon } from "../../../locate/state/polygon" +import { useSubscribe } from "../../../utils/hooks" +import houses from "../houses" +import siteCtx from "../siteCtx" +import { SharingWorkerAPI } from "./worker" + +const textDecoder = new TextDecoder() + +export const decodeEncodedStoragePayload = flow( + base64url.toBuffer, + inflate, + (x) => textDecoder.decode(x), + JSON.parse +) + +export const useSharingWorker = () => { + const ref = useRef | null>(null) + + useEffect(() => { + const worker = new Worker(new URL("./worker.ts", import.meta.url)) + ref.current = wrap(worker) + }, []) + + useSubscribe( + houses, + async () => { + if (!ref.current) return + + const polygon = getMapPolygon() + + const encodedStore = await ref.current.compressEncode({ + houses: snapshot(houses), + polygon: polygon ? snapshot(polygon) : undefined, + siteCtx: snapshot(siteCtx), + }) + + console.log(encodedStore) + }, + true + ) +} diff --git a/app/design/state/sharing/worker.ts b/app/design/state/sharing/worker.ts new file mode 100644 index 00000000..3b514a81 --- /dev/null +++ b/app/design/state/sharing/worker.ts @@ -0,0 +1,32 @@ +import { expose } from "comlink" +import * as pako from "pako" +import base64url from "base64url" +import { flow, pipe } from "fp-ts/lib/function" + +// const compressEncode = async (payload: any) => { +// const str = JSON.stringify(payload) +// const uint8Array = new TextEncoder().encode(str) +// const pakoCompressed = pako.deflate(uint8Array) +// const buffer = Buffer.from(pakoCompressed.buffer) +// const base64Encoded = base64url.encode(buffer) + +// return base64Encoded +// } + +const textEncoder = new TextEncoder() + +const compressEncode = flow( + JSON.stringify, + (s) => textEncoder.encode(s), + (x) => Buffer.from(pako.deflate(x)), + base64url.encode +) + +const api = { + compressEncode, + // compressEncode2, +} + +export type SharingWorkerAPI = typeof api + +expose(api) diff --git a/app/design/state/siteCtx.ts b/app/design/state/siteCtx.ts index 14d9eae6..812ce056 100644 --- a/app/design/state/siteCtx.ts +++ b/app/design/state/siteCtx.ts @@ -21,6 +21,15 @@ type SiteCtx = { region: "UK" | "EU" } +export const siteCtxParser = z.object({ + mode: SiteCtxModeEnum, + editMode: EditModeEnum.nullable(), + houseId: z.string().nullable(), + levelIndex: z.number().nullable(), + projectName: z.string().nullable(), + region: z.union([z.literal("UK"), z.literal("EU")]), +}) + const defaults = { houseId: null, levelIndex: null, diff --git a/app/design/ui-3d/grouped/GroupedApp.tsx b/app/design/ui-3d/grouped/GroupedApp.tsx index e5b50af4..845cab26 100644 --- a/app/design/ui-3d/grouped/GroupedApp.tsx +++ b/app/design/ui-3d/grouped/GroupedApp.tsx @@ -6,19 +6,26 @@ import { useRouting } from "~/design/state/routing" import { RA } from "~/utils/functions" import { useExportersWorker } from "../../state/exporters" import { useDragHandler, useGestures } from "../../state/gestures" -import { useHouseKeys } from "../../state/houses" +import { useHouseKeys, useLocallyStoredHouses } from "../../state/houses" +import { useSharingWorker } from "../../state/sharing" +import { useLocallyStoredSiteCtx } from "../../state/siteCtx" import XZPlane from "../XZPlane" import YPlane from "../YPlane" import GroupedHouse from "./GroupedHouse" const GroupedApp = () => { const houseKeys = useHouseKeys() + + useLocallyStoredHouses() + useLocallyStoredSiteCtx() + usePreviews() const bindAll = useGestures() useDragHandler() useRouting() useExportersWorker() + useSharingWorker() return ( diff --git a/app/locate/state/polygon.ts b/app/locate/state/polygon.ts index 693572ab..1c72cf31 100644 --- a/app/locate/state/polygon.ts +++ b/app/locate/state/polygon.ts @@ -1,9 +1,12 @@ "use client" import { Polygon } from "@turf/turf" import { proxy, useSnapshot } from "valtio" +import { isSSR } from "../../utils/next" import { BUILDX_LOCAL_STORAGE_MAP_POLYGON_KEY } from "./constants" function getInitialPolygon() { + if (isSSR()) return null + const rawStoragePayload = localStorage.getItem( BUILDX_LOCAL_STORAGE_MAP_POLYGON_KEY ) @@ -30,6 +33,10 @@ export const useMapPolygon = () => { return polygon } +export const getMapPolygon = () => { + return prox.polygon +} + export const setMapPolygon = (polygon: Polygon) => { prox.polygon = polygon localStorage.setItem( diff --git a/app/page.tsx b/app/page.tsx index 1866362b..49fe0f70 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,13 +1,56 @@ "use client" -import { useRouter } from "next/navigation" +import { useRouter, useSearchParams } from "next/navigation" import { useEffect } from "react" +import { z } from "zod" +import { housesParser, saveHouses } from "./data/houses" +import { BUILDX_LOCAL_STORAGE_CONTEXT_KEY } from "./design/state/constants" +import { decodeEncodedStoragePayload } from "./design/state/sharing" +import { siteCtxParser } from "./design/state/siteCtx" +import { BUILDX_LOCAL_STORAGE_MAP_POLYGON_KEY } from "./locate/state/constants" +import { polygonGeometryParser } from "./locate/state/geojson" +import Loader from "./ui/Loader" const Index = () => { const router = useRouter() + const searchParams = useSearchParams() + const q = searchParams.get("q") + useEffect(() => { - router.push("/locate") - }, [router]) - return
+ if (!q) { + router.push("/locate") + return + } + + try { + const parser = z.object({ + houses: housesParser, + polygon: polygonGeometryParser, + siteCtx: siteCtxParser, + }) + + const decoded = decodeEncodedStoragePayload(q) + + const { houses, polygon, siteCtx } = parser.parse(decoded) + + saveHouses(houses) + + localStorage.setItem( + BUILDX_LOCAL_STORAGE_CONTEXT_KEY, + JSON.stringify(siteCtx) + ) + + localStorage.setItem( + BUILDX_LOCAL_STORAGE_MAP_POLYGON_KEY, + JSON.stringify(polygon) + ) + + router.push("/design") + } catch (e) { + router.push("/locate") + } + }, [q, router]) + + return } export default Index diff --git a/package.json b/package.json index 6f92ac6f..c3920b65 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@turf/turf": "^6.5.0", "@use-gesture/react": "^10.2.20", "airtable": "^0.11.4", + "base64url": "^3.0.1", "camera-controls": "^1.37.2", "clsx": "^1.2.1", "comlink": "^4.4.1", @@ -43,6 +44,7 @@ "leva": "^0.9.34", "mapbox-gl": "^2.11.1", "next": "^13.4.3", + "pako": "^2.1.0", "postprocessing": "^6.31.0", "react": "18.2.0", "react-cool-portal": "^1.2.0", @@ -72,6 +74,7 @@ "@types/mapbox__mapbox-gl-draw": "^1.3.3", "@types/mapbox__mapbox-gl-geocoder": "^4.7.3", "@types/node": "18.7.14", + "@types/pako": "^2.0.0", "@types/react": "18.0.18", "@types/react-dom": "18.0.6", "@types/three": "^0.152.0", diff --git a/yarn.lock b/yarn.lock index 8ea7c65b..0c597e4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2224,6 +2224,11 @@ resolved "https://registry.yarnpkg.com/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz#e4a932069db47bb3eabeb0b305502d01586fa90d" integrity sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg== +"@types/pako@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/pako/-/pako-2.0.0.tgz#12ab4c19107528452e73ac99132c875ccd43bdfb" + integrity sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA== + "@types/prop-types@*": version "15.7.5" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" @@ -2604,6 +2609,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64url@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + bidi-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bidi-js/-/bidi-js-1.0.2.tgz#1a497a762c2ddea377429d2649c9ce0f8a91527f" @@ -4769,6 +4779,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"