From 57e466b3340eea801922bb7e6ba465a1961ef841 Mon Sep 17 00:00:00 2001 From: Chris Carlon Date: Sun, 15 Dec 2024 00:25:47 +0000 Subject: [PATCH] feat(frontend): added delete guard when deleting projects, added modal styling in workspace area, implenmented rename file when uploading a file [2024-12-15] --- .../[projectName]/components/mapClient.tsx | 73 +++++++++++--- .../components/navBars/mainMapNavigation.tsx | 95 ++++++++++++++----- .../[projectName]/components/navBars/types.ts | 5 + .../workspace/[workspaceId]/projectModal.tsx | 21 +++- .../[workspaceId]/workspaceProjects.tsx | 32 ++++--- gridwalk-ui/src/app/workspace/sidebar.tsx | 2 +- 6 files changed, 175 insertions(+), 53 deletions(-) diff --git a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapClient.tsx b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapClient.tsx index 4f30159..fa99dbc 100644 --- a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapClient.tsx +++ b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/mapClient.tsx @@ -15,6 +15,13 @@ export interface LayerUpload { workspace_id?: string; } +const defaultBaseLayer: BaseEditNav = { + id: "light", + title: "Light Mode", + icon: "light", + description: "Light blue base map style" +}; + const MAP_STYLES = { light: "/OS_VTS_3857_Light.json", dark: "/OS_VTS_3857_Dark.json", @@ -35,15 +42,11 @@ interface MapClientProps { export function MapClient({ apiUrl }: MapClientProps) { // UI States const [selectedItem, setSelectedItem] = useState(null); - const [selectedEditItem, setSelectedEditItem] = useState( - null, - ); - const [selectedBaseItem, setSelectedBaseItem] = useState( - null, - ); + const [selectedEditItem, setSelectedEditItem] = useState(null); + const [selectedBaseItem, setSelectedBaseItem] = useState(defaultBaseLayer); // Initialize with defaultBaseLayer const [isModalOpen, setIsModalOpen] = useState(false); const [currentStyle, setCurrentStyle] = useState(MAP_STYLES.light); - + // Layer Management States const [layers, setLayers] = useState([]); const [uploadProgress, setUploadProgress] = useState(0); @@ -51,6 +54,10 @@ export function MapClient({ apiUrl }: MapClientProps) { const [uploadError, setUploadError] = useState(null); const [uploadSuccess, setUploadSuccess] = useState(false); + // File Upload States + const [selectedFile, setSelectedFile] = useState(null); + const [fileName, setFileName] = useState(""); + // Map Initialisation const { mapContainer, mapError } = useMapInit({ ...INITIAL_MAP_CONFIG, @@ -61,17 +68,36 @@ export function MapClient({ apiUrl }: MapClientProps) { // File Upload Hook Integration const { uploadFile } = useFileUploader(); + // File Selection Handler + const handleFileSelection = useCallback((file: File) => { + setSelectedFile(file); + setUploadError(null); + }, []); + // File Upload Handler const handleUpload = useCallback( - async (file: File) => { + async (fileToUpload: File) => { + if (!fileToUpload || !fileName.trim()) { + setUploadError("Please provide a valid file and name"); + return; + } + setIsUploading(true); setUploadError(null); setUploadSuccess(false); setUploadProgress(0); try { + // Create a new File object with the custom name while preserving the extension + const extension = fileToUpload.name.split('.').pop(); + const renamedFile = new File( + [fileToUpload], + `${fileName}${extension ? `.${extension}` : ''}`, + { type: fileToUpload.type } + ); + await uploadFile( - file, + renamedFile, "", (progress) => { setUploadProgress(progress); @@ -91,10 +117,12 @@ export function MapClient({ apiUrl }: MapClientProps) { setUploadSuccess(true); setIsUploading(false); + setSelectedFile(null); + setFileName(""); setTimeout(() => { setUploadSuccess(false); - }, 1000); + }, 2000); } }, (error) => { @@ -105,15 +133,23 @@ export function MapClient({ apiUrl }: MapClientProps) { setIsUploading(false); } }, - [uploadFile], + [uploadFile, fileName], ); // Abort Upload Handler const handleAbortUpload = useCallback(() => { - // Implement abort logic here if needed setIsUploading(false); setUploadProgress(0); setUploadError("Upload cancelled"); + setSelectedFile(null); + setFileName(""); + }, []); + + // Cancel File Selection + const handleCancelSelection = useCallback(() => { + setSelectedFile(null); + setFileName(""); + setUploadError(null); }, []); // Layer Management @@ -142,8 +178,10 @@ export function MapClient({ apiUrl }: MapClientProps) { const handleModalClose = useCallback(() => { setIsModalOpen(false); setSelectedItem(null); - setUploadSuccess(false); - setUploadError(null); + setUploadSuccess(false); + setUploadError(null); + setSelectedFile(null); + setFileName(""); }, []); return ( @@ -173,6 +211,11 @@ export function MapClient({ apiUrl }: MapClientProps) { uploadProgress={uploadProgress} onAbortUpload={handleAbortUpload} layers={layers} + selectedFile={selectedFile} + fileName={fileName} + onFileSelection={handleFileSelection} + onFileNameChange={setFileName} + onCancelSelection={handleCancelSelection} /> ); -} +} \ No newline at end of file diff --git a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/navBars/mainMapNavigation.tsx b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/navBars/mainMapNavigation.tsx index 7133cf0..6950e0c 100644 --- a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/navBars/mainMapNavigation.tsx +++ b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/navBars/mainMapNavigation.tsx @@ -11,6 +11,7 @@ import { import { ModalProps, MainMapNav } from "./types"; import { useRouter } from "next/navigation"; +// Loading dots for file upload const LoadingDots = () => (
@@ -19,6 +20,7 @@ const LoadingDots = () => (
); +// Modals for uploading files, viewing layers etc const MapModal: React.FC = ({ isOpen, onClose, @@ -28,14 +30,16 @@ const MapModal: React.FC = ({ isUploading, error, uploadSuccess, - uploadProgress + uploadProgress, + selectedFile, + fileName, + onFileSelection, + onFileNameChange, + onCancelSelection }) => { const router = useRouter(); - - // Add a key state that changes when upload is successful const [uploadKey, setUploadKey] = React.useState(0); - // Reset the key when upload is successful React.useEffect(() => { if (uploadSuccess) { setUploadKey(prev => prev + 1); @@ -61,7 +65,13 @@ const MapModal: React.FC = ({ const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { - onLayerUpload(file); + onFileSelection(file); + } + }; + + const handleUploadClick = () => { + if (selectedFile) { + onLayerUpload(selectedFile); } }; @@ -86,24 +96,61 @@ const MapModal: React.FC = ({

Upload

{/* Upload Section */} -
- -
+ {!selectedFile ? ( +
+ +
+ ) : ( +
+
+

+ Selected file: {selectedFile.name} +

+
+ + onFileNameChange(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="Enter layer name" + /> +
+
+ + +
+
+
+ )} {isUploading && (
@@ -211,4 +258,4 @@ const MapModal: React.FC = ({ ); }; -export default MapModal; +export default MapModal; \ No newline at end of file diff --git a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/navBars/types.ts b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/navBars/types.ts index 03fca71..8094362 100644 --- a/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/navBars/types.ts +++ b/gridwalk-ui/src/app/project/[workspaceId]/[projectName]/components/navBars/types.ts @@ -21,6 +21,11 @@ export interface ModalProps { uploadSuccess: boolean; uploadProgress: number; onAbortUpload: () => void; + selectedFile: File | null; + fileName: string; + onFileSelection: (file: File) => void; + onFileNameChange: (name: string) => void; + onCancelSelection: () => void; } /* Map Edit Items */ diff --git a/gridwalk-ui/src/app/workspace/[workspaceId]/projectModal.tsx b/gridwalk-ui/src/app/workspace/[workspaceId]/projectModal.tsx index d4fe863..44af702 100644 --- a/gridwalk-ui/src/app/workspace/[workspaceId]/projectModal.tsx +++ b/gridwalk-ui/src/app/workspace/[workspaceId]/projectModal.tsx @@ -111,15 +111,21 @@ export const DeleteProjectModal: React.FC = ({ }) => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); + const [confirmText, setConfirmText] = useState(""); + + const isDeleteEnabled = confirmText === "DELETE"; const handleDelete = async (e: React.FormEvent) => { e.preventDefault(); + if (!isDeleteEnabled) return; + setIsLoading(true); setError(null); try { await onConfirm(); onClose(); + setConfirmText(""); } catch (err) { setError(err instanceof Error ? err.message : "An error occurred"); } finally { @@ -153,10 +159,21 @@ export const DeleteProjectModal: React.FC = ({ Are you sure you want to delete{" "} {projectName}?

-

+

This action cannot be undone. All project data will be permanently removed.

+

+ Type DELETE to confirm: +

+ setConfirmText(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md text-gray-900 placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-red-500" + placeholder="Enter Text..." + disabled={isLoading} + />
@@ -171,7 +188,7 @@ export const DeleteProjectModal: React.FC = ({ + {/* Modals */} diff --git a/gridwalk-ui/src/app/workspace/sidebar.tsx b/gridwalk-ui/src/app/workspace/sidebar.tsx index a432040..631c046 100644 --- a/gridwalk-ui/src/app/workspace/sidebar.tsx +++ b/gridwalk-ui/src/app/workspace/sidebar.tsx @@ -65,7 +65,7 @@ const WorkspaceAccordion = ({ workspaces }: { workspaces: Workspaces }) => {
-

GridWalk

+

GW

Workspaces