diff --git a/README.md b/README.md index 3cbe6b6..72a29c0 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,25 @@ If you pull down changes, you'll need to run `docker-compose --file ./docker-com 1. Run `yarn dev` to start the local development server 1. Browse to http://localhost:3000 +### With Vertex-web-sdk + +1. `yarn build` in `vertex-web-sdk` +1. `yarn link` from local `vertex-web-sdk` +1. make these changes in package.json + ``` json + "scripts": { + "dev": "NODE_OPTIONS=--preserve-symlinks next dev --webpack", + ... + }, + "resolutions": { + "@vertexvis/viewer": "portal:../vertex-web-sdk/packages/viewer", + "@vertexvis/viewer-react": "portal:../vertex-web-sdk/packages/viewer-react", + ... + } + ``` +1. `yarn install` to pull local packages + + ### Project organization ```text @@ -36,4 +55,4 @@ A few options for deployment, - [Vercel](https://nextjs.org/docs/deployment) - [Netlify](https://www.netlify.com/blog/2020/11/30/how-to-deploy-next.js-sites-to-netlify/) -- [AWS CDK](https://github.com/serverless-nextjs/serverless-next.js#readme) +- [AWS via OpenNext](https://opennext.js.org) diff --git a/next-env.d.ts b/next-env.d.ts index 254b73c..7996d35 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -/// +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/next.config.js b/next.config.js deleted file mode 100644 index 5f42427..0000000 --- a/next.config.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - webpack: (config) => { - config.output.assetModuleFilename = `static/[hash][ext]`; - config.output.publicPath = `/_next/`; - config.module.rules.push({ - test: /\.worker.js/, - type: `asset/resource`, - }); - return config; - }, -}; diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..7be253c --- /dev/null +++ b/next.config.ts @@ -0,0 +1,51 @@ +import path from "node:path"; + +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + bundlePagesRouterDependencies: true, + // transpilePackages: ["@vertexvis/viewer", "@vertexvis/viewer-react"], + turbopack: { + // Linked workspace packages live outside this app directory. + // Turbopack needs its root raised to resolve those symlink targets. + root: path.join(process.cwd(), ".."), + // Module resolution aliases for monorepo/portal setup + // Ensures consistent react/react-dom resolution across packages + resolveAlias: { + react: "./node_modules/react", + "react-dom": "./node_modules/react-dom", + "react/jsx-runtime": "./node_modules/react/jsx-runtime", + "react/jsx-dev-runtime": "./node_modules/react/jsx-dev-runtime", + }, + // Custom rules for non-standard file types + rules: { + "*.worker.js": { + type: "asset", + }, + }, + }, + + // Webpack config only needed for features not yet supported by Turbopack + // Most webpack customization has been moved to turbopack config above + webpack: (config) => { + config.resolve = config.resolve ?? {}; + config.resolve.alias = { + ...(config.resolve.alias ?? {}), + react: path.join(process.cwd(), "node_modules/react"), + "react-dom": path.join(process.cwd(), "node_modules/react-dom"), + "react/jsx-runtime": path.join(process.cwd(), "node_modules/react/jsx-runtime"), + "react/jsx-dev-runtime": path.join( + process.cwd(), + "node_modules/react/jsx-dev-runtime" + ), + }; + + // Note: Next.js automatically handles: + // - publicPath (set to /_next/ by default) + // - assetModuleFilename (handled by Next.js internally) + + return config; + }, +}; + +export default nextConfig; diff --git a/package.json b/package.json index 49d7fac..dae62f2 100644 --- a/package.json +++ b/package.json @@ -10,32 +10,34 @@ "@mui/icons-material": "^7.3.10", "@mui/material": "^7.3.10", "@vertexvis/api-client-node": "^0.42.0", - "@vertexvis/viewer-react": "^0.24.3", - "next": "^15.5.15", - "react": "^18.3.0", - "react-dom": "^18.3.0", - "react-hotkeys-hook": "^3.4.7" + "@vertexvis/viewer-react": "^1.0.0-testing.8", + "next": "^16.2.4", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "react-hotkeys-hook": "^5.2.4" }, "devDependencies": { "@babel/core": "^7.29.0", - "@types/node": "^22.0.41", - "@types/react": "^18.0.28", - "@types/react-dom": "^18.3.7", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", + "@types/node": "^22.19.17", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@typescript-eslint/eslint-plugin": "^8.59.1", + "@typescript-eslint/parser": "^8.59.1", "babel-plugin-import": "^1.13.8", - "eslint": "^8.57.1", - "eslint-config-next": "^15.5.0", - "eslint-config-prettier": "^8.10.2", - "eslint-plugin-simple-import-sort": "^7.0", + "eslint": "^10.2.1", + "eslint-config-next": "^16.2.4", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-simple-import-sort": "^13.0", "prettier": "^2.8.8", "tslib": "^2.4.0", "typescript": "^5.2.0" }, "scripts": { - "build": "next build", + "build": "next build --webpack", + "build:turbopack": "next build", "clean": "rm -rf .next/ node_modules/", - "dev": "next", + "dev": "next dev --webpack", + "dev:turbopack": "next dev", "format": "prettier --write './**/*.+(js|jsx|ts|tsx|json|yml|yaml|md|mdx|html|css)'", "lint": "next lint", "start": "next start", diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 9ad0c75..137ed89 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,7 +1,7 @@ import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Link from "@mui/material/Link"; -import React from "react"; +import { JSX } from "react"; interface Props { readonly onOpenSceneClick: VoidFunction; diff --git a/src/components/Home.tsx b/src/components/Home.tsx index 2a99469..082db80 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -1,5 +1,5 @@ import { useRouter } from "next/router"; -import React from "react"; +import React, { JSX } from "react"; import { useHotkeys } from "react-hotkeys-hook"; import { @@ -16,6 +16,7 @@ import { Header } from "./Header"; import { Layout, RightDrawerWidth } from "./Layout"; import { encodeCreds, OpenDialog } from "./OpenScene"; import { RightDrawer } from "./RightDrawer"; + import { Viewer } from "./Viewer"; export interface Props { @@ -45,7 +46,12 @@ export function Home({ files, config: { network } }: Props): JSX.Element { // On credentials changes, update URL. React.useEffect(() => { - if (credentials) router.push(encodeCreds(credentials)); + if (!credentials) return; + + const nextUrl = encodeCreds(credentials); + if (router.asPath === nextUrl) return; + + router.replace(nextUrl, undefined, { shallow: true }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [credentials]); diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 0c5db39..b54f05e 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -2,7 +2,7 @@ import MuiAppBar from "@mui/material/AppBar"; import Box from "@mui/material/Box"; import { styled } from "@mui/material/styles"; import Toolbar from "@mui/material/Toolbar"; -import React from "react"; +import React, { JSX } from "react"; import { easeOutEntering, sharpLeaving } from "../lib/transitions"; diff --git a/src/components/MetadataProperties.tsx b/src/components/MetadataProperties.tsx index 9d87e31..2bc054d 100644 --- a/src/components/MetadataProperties.tsx +++ b/src/components/MetadataProperties.tsx @@ -5,10 +5,10 @@ import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Typography from "@mui/material/Typography"; -import React from "react"; import { Metadata } from "../lib/metadata"; import { NoData } from "./NoData"; +import { JSX } from "react"; interface Props { readonly metadata?: Metadata; diff --git a/src/components/NoData.tsx b/src/components/NoData.tsx index 02539b1..4c11e7a 100644 --- a/src/components/NoData.tsx +++ b/src/components/NoData.tsx @@ -1,5 +1,5 @@ import Typography from "@mui/material/Typography"; -import React from "react"; +import { JSX } from "react"; export function NoData(): JSX.Element { return ( diff --git a/src/components/OpenScene.tsx b/src/components/OpenScene.tsx index 6beba33..338ed16 100644 --- a/src/components/OpenScene.tsx +++ b/src/components/OpenScene.tsx @@ -5,7 +5,7 @@ import DialogContent from "@mui/material/DialogContent"; import DialogContentText from "@mui/material/DialogContentText"; import DialogTitle from "@mui/material/DialogTitle"; import TextField from "@mui/material/TextField"; -import React from "react"; +import React, { JSX } from "react"; import { DefaultCredentials, StreamCredentials } from "../lib/config"; diff --git a/src/components/RecentFiles.tsx b/src/components/RecentFiles.tsx index 1b98c60..175452a 100644 --- a/src/components/RecentFiles.tsx +++ b/src/components/RecentFiles.tsx @@ -4,7 +4,7 @@ import TableCell from "@mui/material/TableCell"; import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; -import React from "react"; +import { JSX } from "react"; import { FileData } from "../lib/files"; import { NoData } from "./NoData"; diff --git a/src/components/RightDrawer.tsx b/src/components/RightDrawer.tsx index 05ccc29..7c0a51c 100644 --- a/src/components/RightDrawer.tsx +++ b/src/components/RightDrawer.tsx @@ -4,7 +4,7 @@ import AccordionSummary from "@mui/material/AccordionSummary"; import Drawer, { drawerClasses } from "@mui/material/Drawer"; import { styled } from "@mui/material/styles"; import Typography from "@mui/material/Typography"; -import React from "react"; +import React, { JSX } from "react"; import { FileData } from "../lib/files"; import { Metadata } from "../lib/metadata"; diff --git a/src/components/Viewer.tsx b/src/components/Viewer.tsx index 5069559..bde2a50 100644 --- a/src/components/Viewer.tsx +++ b/src/components/Viewer.tsx @@ -10,14 +10,14 @@ import { VertexViewerToolbar, VertexViewerViewCube, } from '@vertexvis/viewer-react'; -import React from 'react'; +import React, { JSX } from 'react'; import { StreamCredentials } from '../lib/config'; import { ViewerSpeedDial } from './ViewerSpeedDial'; interface ViewerProps extends ViewerJSX.VertexViewer { readonly credentials: StreamCredentials; - readonly viewer: React.MutableRefObject; + readonly viewer: React.RefObject; } export interface ActionProps { diff --git a/src/components/ViewerSpeedDial.tsx b/src/components/ViewerSpeedDial.tsx index 2c32816..f99eab6 100644 --- a/src/components/ViewerSpeedDial.tsx +++ b/src/components/ViewerSpeedDial.tsx @@ -3,6 +3,7 @@ import SpeedDial from "@mui/material/SpeedDial"; import SpeedDialAction from "@mui/material/SpeedDialAction"; import { ActionProps, AnimationDurationMs } from "./Viewer"; +import { JSX } from "react"; interface Props { readonly viewer: React.MutableRefObject; diff --git a/src/lib/scene-items.ts b/src/lib/scene-items.ts index f82a1d1..a868c16 100644 --- a/src/lib/scene-items.ts +++ b/src/lib/scene-items.ts @@ -1,5 +1,5 @@ import { vertexvis } from '@vertexvis/frame-streaming-protos'; -import { Components } from '@vertexvis/viewer'; +import type { Components } from '@vertexvis/viewer'; interface Req { readonly viewer: Components.VertexViewer | null; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 8b8e7c8..2119835 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -7,7 +7,7 @@ import { ThemeProvider } from "@mui/material/styles"; import { AppProps } from "next/app"; import Head from "next/head"; import { useRouter } from "next/router"; -import React from "react"; +import React, { JSX } from "react"; import theme from "../lib/theme"; @@ -16,14 +16,17 @@ cache.compat = true; export default function App({ Component, pageProps }: AppProps): JSX.Element { const { events } = useRouter(); + const analyticsId = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS; React.useEffect(() => { + if (!analyticsId) return; + function handleChange(url: string) { /* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore if (window.gtag) { // @ts-ignore - window.gtag("config", process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS, { + window.gtag("config", analyticsId, { cookie_flags: "SameSite=None;Secure", page_path: url, }); @@ -35,7 +38,7 @@ export default function App({ Component, pageProps }: AppProps): JSX.Element { return () => { events.off("routeChangeComplete", handleChange); }; - }, [events]); + }, [analyticsId, events]); return ( diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 43779e1..5dfdecd 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -2,12 +2,14 @@ import createCache from "@emotion/cache"; import { CacheProvider } from "@emotion/react"; import createEmotionServer from "@emotion/server/create-instance"; import Document, { Head, Html, Main, NextScript } from "next/document"; -import React from "react"; +import React, { JSX } from "react"; import theme from "../lib/theme"; export default class MyDocument extends Document { render(): JSX.Element { + const analyticsId = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS; + return ( @@ -16,23 +18,27 @@ export default class MyDocument extends Document { rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" /> -