From d2c7860a86b4195a5ae71fbb256f5e62f6e75c58 Mon Sep 17 00:00:00 2001 From: zerj9 <96551236+zerj9@users.noreply.github.com> Date: Mon, 18 Nov 2024 22:08:36 +0000 Subject: [PATCH] feat: project page component is now a server component --- .../[projectName]/components/mapClient.tsx | 175 +++++++++++++++++ .../{mapInit/mapInit.tsx => mapInit.ts} | 30 +-- .../[workspaceId]/[projectName]/page.tsx | 179 +----------------- 3 files changed, 197 insertions(+), 187 deletions(-) create mode 100644 gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapClient.tsx rename gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/{mapInit/mapInit.tsx => mapInit.ts} (87%) diff --git a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapClient.tsx b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapClient.tsx new file mode 100644 index 0000000..ec37cb4 --- /dev/null +++ b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapClient.tsx @@ -0,0 +1,175 @@ +'use client' +import React, { useState, useCallback } from "react" +import { useMapInit } from "./mapInit" +import MainMapNavigation from "./navBars/mainMapNavigation" +import MapEditNavigation from "./navBars/mapEditNavigation" +import BaseLayerNavigation from "./navBars/baseLayerNavigation" +import { useFileUploader } from "./hooks/useFileUploader" +import { + MainMapNav, + MapEditNav, + BaseEditNav, +} from "./navBars/types" + +export interface LayerUpload { + id: string + name: string + type: string + visible: boolean + workspace_id?: string +} + +const MAP_STYLES = { + light: "/OS_VTS_3857_Light.json", + dark: "/OS_VTS_3857_Dark.json", + car: "/OS_VTS_3857_Road.json", +} as const + +type MapStyleKey = keyof typeof MAP_STYLES + +const INITIAL_MAP_CONFIG = { + center: [-0.1278, 51.5074] as [number, number], + zoom: 11, +} as const + +interface MapClientProps { + apiUrl: string +} + +export function MapClient({ apiUrl }: MapClientProps) { + // UI States + const [selectedItem, setSelectedItem] = useState(null) + const [selectedEditItem, setSelectedEditItem] = useState(null) + const [selectedBaseItem, setSelectedBaseItem] = useState(null) + const [isModalOpen, setIsModalOpen] = useState(false) + const [currentStyle, setCurrentStyle] = useState(MAP_STYLES.light) + + // Layer Management States + const [layers, setLayers] = useState([]) + const [uploadProgress, setUploadProgress] = useState(0) + const [isUploading, setIsUploading] = useState(false) + const [uploadError, setUploadError] = useState(null) + const [uploadSuccess, setUploadSuccess] = useState(false) + + // Map Initialization + const { mapContainer, mapError } = useMapInit({ + ...INITIAL_MAP_CONFIG, + styleUrl: currentStyle, + apiUrl, + }) + + // File Upload Hook Integration + const { uploadFile } = useFileUploader(); + + // File Upload Handler + const handleUpload = useCallback( + async (file: File) => { + setIsUploading(true); + setUploadError(null); + setUploadSuccess(false); + setUploadProgress(0); + + try { + await uploadFile( + file, + undefined, + (progress) => { + setUploadProgress(progress); + }, + (response) => { + if (response.success && response.data) { + setLayers((prev) => [ + ...prev, + { + id: response.data!.id, + name: response.data!.name, + type: "vector", + visible: true, + workspace_id: response.data!.workspace_id, + }, + ]); + setUploadSuccess(true); + } + }, + (error) => { + setUploadError(error); + }, + ); + } finally { + setIsUploading(false); + } + }, + [uploadFile], + ); + + // Abort Upload Handler + const handleAbortUpload = useCallback(() => { + // Implement abort logic here if needed + setIsUploading(false); + setUploadProgress(0); + setUploadError("Upload cancelled"); + }, []); + + // Layer Management + const handleLayerDelete = useCallback((layerId: string) => { + setLayers((prev) => prev.filter((layer) => layer.id !== layerId)); + }, []); + + // Navigation Handlers + const handleNavItemClick = useCallback((item: MainMapNav) => { + setSelectedItem(item); + setIsModalOpen(true); + }, []); + + const handleEditItemClick = useCallback((item: MapEditNav) => { + setSelectedEditItem((prev) => (prev?.id === item.id ? null : item)); + }, []); + + const handleBaseItemClick = useCallback((item: BaseEditNav) => { + setSelectedBaseItem(item); + const styleKey = item.id as MapStyleKey; + if (styleKey in MAP_STYLES) { + setCurrentStyle(MAP_STYLES[styleKey]); + } + }, []); + + const handleModalClose = useCallback(() => { + setIsModalOpen(false); + setSelectedItem(null); + }, []); + + return ( +
+ {mapError && ( +
+ {mapError} +
+ )} +
+
+
+ + + +
+ ) +} diff --git a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapInit/mapInit.tsx b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapInit.ts similarity index 87% rename from gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapInit/mapInit.tsx rename to gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapInit.ts index fa6a81f..9068c28 100644 --- a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapInit/mapInit.tsx +++ b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapInit.ts @@ -1,6 +1,7 @@ -import { useEffect, useRef, useState } from "react"; -import maplibregl from "maplibre-gl"; -import "maplibre-gl/dist/maplibre-gl.css"; +'use client' +import { useEffect, useRef, useState } from "react" +import maplibregl from "maplibre-gl" +import "maplibre-gl/dist/maplibre-gl.css" // Constants const REFRESH_THRESHOLD = 30; @@ -20,6 +21,7 @@ export interface MapConfig { center?: [number, number]; zoom?: number; styleUrl?: string; + apiUrl: string } export interface UseMapInitResult { @@ -28,18 +30,16 @@ export interface UseMapInitResult { mapError: string | null; } -// Helper functions -// Get token -const getToken = (): TokenData => { - const xhr = new XMLHttpRequest(); - xhr.open("GET", "http://localhost:3001/os-token", false); - xhr.setRequestHeader("Content-Type", "application/json"); - xhr.send(); +const getToken = (apiUrl: string): TokenData => { + const xhr = new XMLHttpRequest() + xhr.open("GET", `${apiUrl}/os-token`, false) + xhr.setRequestHeader("Content-Type", "application/json") + xhr.send() if (xhr.status !== 200) { - throw new Error(`HTTP error! status: ${xhr.status}`); + throw new Error(`HTTP error! status: ${xhr.status}`) } - return JSON.parse(xhr.responseText); -}; + return JSON.parse(xhr.responseText) +} const isTokenValid = (tokenData: TokenData | null): boolean => { if (!tokenData) return false; @@ -50,7 +50,7 @@ const isTokenValid = (tokenData: TokenData | null): boolean => { return currentTime < expirationTime; }; -export const useMapInit = (config?: MapConfig): UseMapInitResult => { +export const useMapInit = (config: MapConfig): UseMapInitResult => { const mapContainer = useRef(null); const map = useRef(null); const [mapError, setMapError] = useState(null); @@ -86,7 +86,7 @@ export const useMapInit = (config?: MapConfig): UseMapInitResult => { if (url.startsWith("https://api.os.uk")) { if (!isTokenValid(tokenRef.current)) { try { - tokenRef.current = getToken(); + tokenRef.current = getToken(config.apiUrl); } catch (error) { console.error("Failed to fetch token:", error); return { diff --git a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/page.tsx b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/page.tsx index d324dd8..49ce798 100644 --- a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/page.tsx +++ b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/page.tsx @@ -1,176 +1,11 @@ -"use client"; -import React, { useState, useCallback } from "react"; -import { useMapInit } from "./components/mapInit/mapInit"; -import MainMapNavigation from "./components/navBars/mainMapNavigation"; -import MapEditNavigation from "./components/navBars/mapEditNavigation"; -import BaseLayerNavigation from "./components/navBars/baseLayerNavigation"; -import { useFileUploader } from "./components/hooks/useFileUploader"; -import { - MainMapNav, - MapEditNav, - BaseEditNav, -} from "./components/navBars/types"; +import { MapClient } from './components/mapClient' -export interface LayerUpload { - id: string; - name: string; - type: string; - visible: boolean; - workspace_id?: string; -} - -const MAP_STYLES = { - light: "/OS_VTS_3857_Light.json", - dark: "/OS_VTS_3857_Dark.json", - car: "/OS_VTS_3857_Road.json", -} as const; - -type MapStyleKey = keyof typeof MAP_STYLES; - -const INITIAL_MAP_CONFIG = { - center: [-0.1278, 51.5074] as [number, number], - zoom: 11, -} as const; - -export default function Project() { - // UI States - const [selectedItem, setSelectedItem] = useState(null); - const [selectedEditItem, setSelectedEditItem] = useState( - null, - ); - const [selectedBaseItem, setSelectedBaseItem] = useState( - null, - ); - const [isModalOpen, setIsModalOpen] = useState(false); - const [currentStyle, setCurrentStyle] = useState(MAP_STYLES.light); - - // Layer Management States - const [layers, setLayers] = useState([]); - const [uploadProgress, setUploadProgress] = useState(0); - const [isUploading, setIsUploading] = useState(false); - const [uploadError, setUploadError] = useState(null); - const [uploadSuccess, setUploadSuccess] = useState(false); - - // Map Initialization - const { mapContainer, mapError } = useMapInit({ - ...INITIAL_MAP_CONFIG, - styleUrl: currentStyle, - }); - - // File Upload Hook Integration - const { uploadFile } = useFileUploader(); - - // File Upload Handler - const handleUpload = useCallback( - async (file: File) => { - setIsUploading(true); - setUploadError(null); - setUploadSuccess(false); - setUploadProgress(0); - - try { - await uploadFile( - file, - undefined, - (progress) => { - setUploadProgress(progress); - }, - (response) => { - if (response.success && response.data) { - setLayers((prev) => [ - ...prev, - { - id: response.data!.id, - name: response.data!.name, - type: "vector", - visible: true, - workspace_id: response.data!.workspace_id, - }, - ]); - setUploadSuccess(true); - } - }, - (error) => { - setUploadError(error); - }, - ); - } finally { - setIsUploading(false); - } - }, - [uploadFile], - ); - - // Abort Upload Handler - const handleAbortUpload = useCallback(() => { - // Implement abort logic here if needed - setIsUploading(false); - setUploadProgress(0); - setUploadError("Upload cancelled"); - }, []); - - // Layer Management - const handleLayerDelete = useCallback((layerId: string) => { - setLayers((prev) => prev.filter((layer) => layer.id !== layerId)); - }, []); - - // Navigation Handlers - const handleNavItemClick = useCallback((item: MainMapNav) => { - setSelectedItem(item); - setIsModalOpen(true); - }, []); - - const handleEditItemClick = useCallback((item: MapEditNav) => { - setSelectedEditItem((prev) => (prev?.id === item.id ? null : item)); - }, []); - - const handleBaseItemClick = useCallback((item: BaseEditNav) => { - setSelectedBaseItem(item); - const styleKey = item.id as MapStyleKey; - if (styleKey in MAP_STYLES) { - setCurrentStyle(MAP_STYLES[styleKey]); - } - }, []); - - const handleModalClose = useCallback(() => { - setIsModalOpen(false); - setSelectedItem(null); - }, []); +export default async function Project() { + const apiUrl = process.env.GRIDWALK_API - const ErrorDisplay = mapError ? ( -
- {mapError} -
- ) : null; + if (!apiUrl) { + throw new Error('GRIDWALK_API environment variable is not set') + } - return ( -
- {ErrorDisplay} -
-
-
- - - -
- ); + return }