From 727aaf0a9334292f2e3ad149c2232bae1744d41a Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 26 Jul 2025 16:07:54 +0800 Subject: [PATCH 01/43] Added data studio page --- src/main.jsx | 3 +++ src/pages/DataStudio.jsx | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 src/pages/DataStudio.jsx diff --git a/src/main.jsx b/src/main.jsx index 96f4afa..9bacd86 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -21,6 +21,7 @@ import ProtectedLayout from './ProtectedLayout.jsx'; import AnimateIn from './AnimateIn.jsx'; import PublicGallery from './pages/PublicGallery.jsx'; import PublicProfile from './pages/PublicProfile.jsx'; +import DataStudio from './pages/DataStudio.jsx'; const store = configureStore({ reducer: { @@ -45,6 +46,8 @@ createRoot(document.getElementById('root')).render( } /> + + } /> {/* Protected Pages */} diff --git a/src/pages/DataStudio.jsx b/src/pages/DataStudio.jsx new file mode 100644 index 0000000..bf0ef23 --- /dev/null +++ b/src/pages/DataStudio.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +function DataStudio() { + return ( +
DataStudio
+ ) +} + +export default DataStudio \ No newline at end of file From de2b08af88dd2f820f54e1c358308d7ecfe28c62 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 26 Jul 2025 18:56:03 +0800 Subject: [PATCH 02/43] Added left item grid for data studio --- src/components/DataStudio/ItemGrid.jsx | 30 ++++++++++++++++++++++++ src/components/DataStudio/ItemGroups.jsx | 11 +++++++++ src/pages/DataStudio.jsx | 30 ++++++++++++++++++++---- 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 src/components/DataStudio/ItemGrid.jsx create mode 100644 src/components/DataStudio/ItemGroups.jsx diff --git a/src/components/DataStudio/ItemGrid.jsx b/src/components/DataStudio/ItemGrid.jsx new file mode 100644 index 0000000..12a24be --- /dev/null +++ b/src/components/DataStudio/ItemGrid.jsx @@ -0,0 +1,30 @@ +import { Box, Grid } from "@chakra-ui/react"; +import CardItem from "../Catalogue/cardItem.jsx"; +import placeholderImage from "../../assets/placeholderImage.png"; + +const dummyData = Array.from({ length: 24 }, (_, i) => ({ + imageSrc: placeholderImage, + title: `Item ${i + 1}`, + description: `This is item ${i + 1}`, +})); + +function ItemGrid({ selectedTitle }) { + return ( + + + {dummyData.map((item, idx) => ( + + + + ))} + + + ); +} + +export default ItemGrid; diff --git a/src/components/DataStudio/ItemGroups.jsx b/src/components/DataStudio/ItemGroups.jsx new file mode 100644 index 0000000..2ff8752 --- /dev/null +++ b/src/components/DataStudio/ItemGroups.jsx @@ -0,0 +1,11 @@ +import { Box, Flex, Text } from "@chakra-ui/react"; + +function ItemGroups() { + return ( + + + + ); +} + +export default ItemGroups; diff --git a/src/pages/DataStudio.jsx b/src/pages/DataStudio.jsx index bf0ef23..9c2fa99 100644 --- a/src/pages/DataStudio.jsx +++ b/src/pages/DataStudio.jsx @@ -1,9 +1,29 @@ -import React from 'react' +import { Box, Flex, Text } from "@chakra-ui/react"; +import ItemGrid from "../components/DataStudio/ItemGrid.jsx"; +import ItemGroups from "../components/DataStudio/ItemGroups.jsx"; function DataStudio() { - return ( -
DataStudio
- ) + return ( + + {/* Header */} + + Data Studio + + + Batch + + + {/* 60/40 Split using percentage width */} + + + + + + + + + + ); } -export default DataStudio \ No newline at end of file +export default DataStudio; From 6c5fb14ecc640c24cd429b4cd86e683089452d1e Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 26 Jul 2025 20:30:08 +0800 Subject: [PATCH 03/43] Added groupToggle for books and category segments --- src/components/DataStudio/ItemGrid.jsx | 2 +- src/components/DataStudio/ItemGroups.jsx | 7 +++-- src/components/DataStudio/groupCard.jsx | 9 ++++++ src/components/DataStudio/groupToggle.jsx | 34 +++++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 src/components/DataStudio/groupCard.jsx create mode 100644 src/components/DataStudio/groupToggle.jsx diff --git a/src/components/DataStudio/ItemGrid.jsx b/src/components/DataStudio/ItemGrid.jsx index 12a24be..a7fc382 100644 --- a/src/components/DataStudio/ItemGrid.jsx +++ b/src/components/DataStudio/ItemGrid.jsx @@ -10,7 +10,7 @@ const dummyData = Array.from({ length: 24 }, (_, i) => ({ function ItemGrid({ selectedTitle }) { return ( - + {dummyData.map((item, idx) => ( diff --git a/src/components/DataStudio/ItemGroups.jsx b/src/components/DataStudio/ItemGroups.jsx index 2ff8752..1696ea8 100644 --- a/src/components/DataStudio/ItemGroups.jsx +++ b/src/components/DataStudio/ItemGroups.jsx @@ -1,10 +1,11 @@ import { Box, Flex, Text } from "@chakra-ui/react"; +import GroupToggle from "./groupToggle"; function ItemGroups() { return ( - - - + + + ); } diff --git a/src/components/DataStudio/groupCard.jsx b/src/components/DataStudio/groupCard.jsx new file mode 100644 index 0000000..ac3c207 --- /dev/null +++ b/src/components/DataStudio/groupCard.jsx @@ -0,0 +1,9 @@ +import React from 'react' + +function groupCard() { + return ( +
groupCard
+ ) +} + +export default groupCard \ No newline at end of file diff --git a/src/components/DataStudio/groupToggle.jsx b/src/components/DataStudio/groupToggle.jsx new file mode 100644 index 0000000..c90fb0e --- /dev/null +++ b/src/components/DataStudio/groupToggle.jsx @@ -0,0 +1,34 @@ +import { HStack, SegmentGroup } from "@chakra-ui/react"; +import { FaBookOpen, FaImages } from "react-icons/fa"; + +const GroupToggle = ({ value, onChange }) => { + return ( + + + + + Books + + ), + }, + { + value: "categories", + label: ( + + + Categories + + ), + }, + ]} + /> + + ); +}; + +export default GroupToggle; From 7107586217b36edcf07b63940dce78272dc88c87 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 26 Jul 2025 21:09:11 +0800 Subject: [PATCH 04/43] Added group card --- src/components/DataStudio/ItemGroups.jsx | 8 ++++++-- src/components/DataStudio/groupCard.jsx | 25 ++++++++++++++++++------ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/components/DataStudio/ItemGroups.jsx b/src/components/DataStudio/ItemGroups.jsx index 1696ea8..5d6a8a4 100644 --- a/src/components/DataStudio/ItemGroups.jsx +++ b/src/components/DataStudio/ItemGroups.jsx @@ -1,10 +1,14 @@ import { Box, Flex, Text } from "@chakra-ui/react"; import GroupToggle from "./groupToggle"; +import GroupCard from "./groupCard"; function ItemGroups() { return ( - - + + + + + ); } diff --git a/src/components/DataStudio/groupCard.jsx b/src/components/DataStudio/groupCard.jsx index ac3c207..9fb8985 100644 --- a/src/components/DataStudio/groupCard.jsx +++ b/src/components/DataStudio/groupCard.jsx @@ -1,9 +1,22 @@ -import React from 'react' +import { Button, Card } from "@chakra-ui/react" -function groupCard() { - return ( -
groupCard
- ) +function GroupCard() { + return ( + + + Nue Camp + + This is the card body. Lorem ipsum dolor sit amet, + consectetur adipiscing elit. Curabitur nec odio vel dui + euismod fermentum. Curabitur nec odio vel dui euismod + fermentum. + + + + + + + ); } -export default groupCard \ No newline at end of file +export default GroupCard; From 6675581a22b51e1d26f1b9e2d58a10b60491b14f Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 26 Jul 2025 22:18:41 +0800 Subject: [PATCH 05/43] Added group item section to show items in the group --- src/components/DataStudio/ItemGrid.jsx | 14 +- src/components/DataStudio/ItemGroups.jsx | 13 +- src/components/DataStudio/groupCard.jsx | 16 +-- .../DataStudio/groupItemsSection.jsx | 124 ++++++++++++++++++ src/pages/DataStudio.jsx | 31 +++-- 5 files changed, 175 insertions(+), 23 deletions(-) create mode 100644 src/components/DataStudio/groupItemsSection.jsx diff --git a/src/components/DataStudio/ItemGrid.jsx b/src/components/DataStudio/ItemGrid.jsx index a7fc382..ee986b5 100644 --- a/src/components/DataStudio/ItemGrid.jsx +++ b/src/components/DataStudio/ItemGrid.jsx @@ -1,3 +1,4 @@ +// src/components/DataStudio/ItemGrid.jsx import { Box, Grid } from "@chakra-ui/react"; import CardItem from "../Catalogue/cardItem.jsx"; import placeholderImage from "../../assets/placeholderImage.png"; @@ -10,8 +11,15 @@ const dummyData = Array.from({ length: 24 }, (_, i) => ({ function ItemGrid({ selectedTitle }) { return ( - - + + {dummyData.map((item, idx) => ( - + + @@ -13,4 +18,4 @@ function ItemGroups() { ); } -export default ItemGroups; +export default ItemGroups; \ No newline at end of file diff --git a/src/components/DataStudio/groupCard.jsx b/src/components/DataStudio/groupCard.jsx index 9fb8985..6a92ef5 100644 --- a/src/components/DataStudio/groupCard.jsx +++ b/src/components/DataStudio/groupCard.jsx @@ -1,22 +1,22 @@ -import { Button, Card } from "@chakra-ui/react" +import { Card } from "@chakra-ui/react"; +import GroupItemsSection from "./groupItemsSection"; function GroupCard() { return ( - + - Nue Camp - + Nue Camp + This is the card body. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur nec odio vel dui - euismod fermentum. Curabitur nec odio vel dui euismod - fermentum. + euismod fermentum. - + ); } -export default GroupCard; +export default GroupCard; \ No newline at end of file diff --git a/src/components/DataStudio/groupItemsSection.jsx b/src/components/DataStudio/groupItemsSection.jsx new file mode 100644 index 0000000..f2577ba --- /dev/null +++ b/src/components/DataStudio/groupItemsSection.jsx @@ -0,0 +1,124 @@ +import { Box, Image, Flex } from "@chakra-ui/react"; +import { useRef, useState, useEffect } from "react"; +import ArrowOverlay from "../Catalogue/arrowOverlay"; +import placeholderImage from "../../assets/placeholderImage.png"; + +function GroupItemsSection() { + const scrollRef = useRef(null); + const [atStart, setAtStart] = useState(true); + const [atEnd, setAtEnd] = useState(false); + const [isOverflowing, setIsOverflowing] = useState(false); + + const items = Array.from({ length: 12 }, (_, i) => ({ + id: i + 1, + imageSrc: placeholderImage, + name: `Item ${i + 1}`, + })); + + const checkScrollEdges = () => { + const node = scrollRef.current; + if (!node) return; + + setAtStart(node.scrollLeft === 0); + setAtEnd( + Math.ceil(node.scrollLeft + node.clientWidth) >= node.scrollWidth + ); + + setIsOverflowing(node.scrollWidth > node.clientWidth); + }; + + const scroll = (direction) => { + if (!scrollRef.current) return; + const scrollAmount = 300; + scrollRef.current.scrollBy({ + left: direction === "left" ? -scrollAmount : scrollAmount, + behavior: "smooth", + }); + }; + + useEffect(() => { + const node = scrollRef.current; + if (!node) return; + + checkScrollEdges(); + node.addEventListener("scroll", checkScrollEdges); + window.addEventListener("resize", checkScrollEdges); + + return () => { + node.removeEventListener("scroll", checkScrollEdges); + window.removeEventListener("resize", checkScrollEdges); + }; + }, [items]); + + return ( + + {/* Arrow overlays with size constraints */} + + scroll("left")} + isOverflowing={isOverflowing && !atStart} + /> + + + + scroll("right")} + isOverflowing={isOverflowing && !atEnd} + /> + + + {/* Scrollable items container */} + + {items.map((item) => ( + + {item.name} + + ))} + + + ); +} + +export default GroupItemsSection; diff --git a/src/pages/DataStudio.jsx b/src/pages/DataStudio.jsx index 9c2fa99..733cbde 100644 --- a/src/pages/DataStudio.jsx +++ b/src/pages/DataStudio.jsx @@ -4,21 +4,36 @@ import ItemGroups from "../components/DataStudio/ItemGroups.jsx"; function DataStudio() { return ( - + {/* Header */} - + Data Studio - + Batch - {/* 60/40 Split using percentage width */} - - + {/* Responsive layout */} + + - + @@ -26,4 +41,4 @@ function DataStudio() { ); } -export default DataStudio; +export default DataStudio; \ No newline at end of file From 324e3f3fbde13b5b87666e6a753976d60a5bbc69 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 26 Jul 2025 23:32:16 +0800 Subject: [PATCH 06/43] Added arrow scrolling for group item section --- src/components/Catalogue/arrowOverlay.jsx | 82 +++++++++++-------- src/components/DataStudio/ItemGroups.jsx | 13 ++- src/components/DataStudio/groupCard.jsx | 13 +-- .../DataStudio/groupItemsSection.jsx | 55 ++++--------- 4 files changed, 82 insertions(+), 81 deletions(-) diff --git a/src/components/Catalogue/arrowOverlay.jsx b/src/components/Catalogue/arrowOverlay.jsx index 5afc9b7..662f549 100644 --- a/src/components/Catalogue/arrowOverlay.jsx +++ b/src/components/Catalogue/arrowOverlay.jsx @@ -4,46 +4,58 @@ import { motion, AnimatePresence } from "framer-motion"; const MotionBox = motion.create(Box); -const ArrowOverlay = ({ direction, isDisabled, onClick, isOverflowing }) => { +const ArrowOverlay = ({ direction, isDisabled, onClick, isOverflowing, smaller = false }) => { const isLeft = direction === "left"; + const commonProps = { + initial: { opacity: 0, x: isLeft ? -20 : 20 }, + animate: { opacity: 1, x: 0 }, + exit: { opacity: 0, x: isLeft ? -20 : 20 }, + transition: { duration: 0.3 }, + position: "absolute", + top: 0, + bottom: 0, + left: isLeft ? 0 : "auto", + right: isLeft ? "auto" : 0, + display: "flex", + alignItems: "center", + justifyContent: isLeft ? "flex-start" : "flex-end", + zIndex: 10, + cursor: isDisabled ? "not-allowed" : "pointer", + pointerEvents: isDisabled ? "none" : "auto", + opacity: isDisabled ? 0.3 : 1, + onClick, + bgGradient: isLeft ? "to-r" : "to-l", + gradientFrom: "rgb(255, 255, 255)", + gradientTo: "rgba(255, 255, 255, 0)", + }; + return ( {isOverflowing && ( - - - + smaller ? ( + + + + ) : ( + + + + ) )} ); diff --git a/src/components/DataStudio/ItemGroups.jsx b/src/components/DataStudio/ItemGroups.jsx index 2904f70..5cd9f82 100644 --- a/src/components/DataStudio/ItemGroups.jsx +++ b/src/components/DataStudio/ItemGroups.jsx @@ -3,17 +3,22 @@ import GroupToggle from "./groupToggle"; import GroupCard from "./groupCard"; function ItemGroups() { + const title = "Book of LIFE"; + const description = "This book contains the life of Mr Joon (AKA B.A.S), a legend forged in caffeine, code, and sheer audacity. From questionable life choices to accidental genius, his story blurs the line between myth and mayhem. Read on, but beware — once you know B.A.S, reality might never feel normal again.."; + return ( - - + + {/* + */} ); } diff --git a/src/components/DataStudio/groupCard.jsx b/src/components/DataStudio/groupCard.jsx index 6a92ef5..7529c83 100644 --- a/src/components/DataStudio/groupCard.jsx +++ b/src/components/DataStudio/groupCard.jsx @@ -1,15 +1,18 @@ import { Card } from "@chakra-ui/react"; import GroupItemsSection from "./groupItemsSection"; -function GroupCard() { +function GroupCard({ title, description }) { + const truncatedDesc = + description.length > 50 ? description.slice(0, 50) + "..." : description; + return ( - Nue Camp + + {title} + - This is the card body. Lorem ipsum dolor sit amet, - consectetur adipiscing elit. Curabitur nec odio vel dui - euismod fermentum. + {truncatedDesc} diff --git a/src/components/DataStudio/groupItemsSection.jsx b/src/components/DataStudio/groupItemsSection.jsx index f2577ba..6643368 100644 --- a/src/components/DataStudio/groupItemsSection.jsx +++ b/src/components/DataStudio/groupItemsSection.jsx @@ -52,50 +52,31 @@ function GroupItemsSection() { return ( - {/* Arrow overlays with size constraints */} - - scroll("left")} - isOverflowing={isOverflowing && !atStart} - /> - - - - scroll("right")} - isOverflowing={isOverflowing && !atEnd} - /> - + {/* Arrow overlays with small size */} + scroll("left")} + isOverflowing={isOverflowing && !atStart} + smaller={true} + /> + scroll("right")} + isOverflowing={isOverflowing && !atEnd} + smaller={true} + /> {/* Scrollable items container */} {items.map((item) => ( From 524cfa41d36b8ba196f35026d8487d174d4c4f04 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 26 Jul 2025 23:50:05 +0800 Subject: [PATCH 07/43] Added overflow scrolling for item groups --- src/components/DataStudio/ItemGroups.jsx | 36 ++++++++++++++++++------ src/components/DataStudio/groupCard.jsx | 4 +-- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/components/DataStudio/ItemGroups.jsx b/src/components/DataStudio/ItemGroups.jsx index 5cd9f82..5265f32 100644 --- a/src/components/DataStudio/ItemGroups.jsx +++ b/src/components/DataStudio/ItemGroups.jsx @@ -4,23 +4,41 @@ import GroupCard from "./groupCard"; function ItemGroups() { const title = "Book of LIFE"; - const description = "This book contains the life of Mr Joon (AKA B.A.S), a legend forged in caffeine, code, and sheer audacity. From questionable life choices to accidental genius, his story blurs the line between myth and mayhem. Read on, but beware — once you know B.A.S, reality might never feel normal again.."; + const description = + "This book contains the life of Mr Joon (AKA B.A.S), a legend forged in caffeine, code, and sheer audacity. From questionable life choices to accidental genius, his story blurs the line between myth and mayhem. Read on, but beware — once you know B.A.S, reality might never feel normal again.."; return ( - + {/* Sticky Toggle */} + - - {/* - */} + + {/* Scrollable Group Cards */} + + {Array.from({ length: 10 }).map((_, i) => ( + + ))} + ); } -export default ItemGroups; \ No newline at end of file +export default ItemGroups; diff --git a/src/components/DataStudio/groupCard.jsx b/src/components/DataStudio/groupCard.jsx index 7529c83..76cae9f 100644 --- a/src/components/DataStudio/groupCard.jsx +++ b/src/components/DataStudio/groupCard.jsx @@ -6,8 +6,8 @@ function GroupCard({ title, description }) { description.length > 50 ? description.slice(0, 50) + "..." : description; return ( - - + + {title} From 43b1e204efcc00a7cfb138713d18314b57070d74 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sun, 27 Jul 2025 02:42:42 +0800 Subject: [PATCH 08/43] Refined group toggle --- src/components/DataStudio/ItemGroups.jsx | 11 ++-- src/components/DataStudio/groupCard.jsx | 3 +- src/components/DataStudio/groupToggle.jsx | 71 +++++++++++++++-------- src/pages/DataStudio.jsx | 56 +++++++++--------- 4 files changed, 83 insertions(+), 58 deletions(-) diff --git a/src/components/DataStudio/ItemGroups.jsx b/src/components/DataStudio/ItemGroups.jsx index 5265f32..242ea40 100644 --- a/src/components/DataStudio/ItemGroups.jsx +++ b/src/components/DataStudio/ItemGroups.jsx @@ -1,8 +1,11 @@ +import { useState } from "react"; import { Box, Flex } from "@chakra-ui/react"; import GroupToggle from "./groupToggle"; import GroupCard from "./groupCard"; function ItemGroups() { + const [selectedGroup, setSelectedGroup] = useState("books"); + const title = "Book of LIFE"; const description = "This book contains the life of Mr Joon (AKA B.A.S), a legend forged in caffeine, code, and sheer audacity. From questionable life choices to accidental genius, his story blurs the line between myth and mayhem. Read on, but beware — once you know B.A.S, reality might never feel normal again.."; @@ -10,21 +13,21 @@ function ItemGroups() { return ( - {/* Sticky Toggle */} - + {/* Scrollable Group Cards */} diff --git a/src/components/DataStudio/groupCard.jsx b/src/components/DataStudio/groupCard.jsx index 76cae9f..51f5b0e 100644 --- a/src/components/DataStudio/groupCard.jsx +++ b/src/components/DataStudio/groupCard.jsx @@ -2,8 +2,7 @@ import { Card } from "@chakra-ui/react"; import GroupItemsSection from "./groupItemsSection"; function GroupCard({ title, description }) { - const truncatedDesc = - description.length > 50 ? description.slice(0, 50) + "..." : description; + const truncatedDesc = description.length > 50 ? description.slice(0, 50) + "..." : description; return ( diff --git a/src/components/DataStudio/groupToggle.jsx b/src/components/DataStudio/groupToggle.jsx index c90fb0e..40fb6b0 100644 --- a/src/components/DataStudio/groupToggle.jsx +++ b/src/components/DataStudio/groupToggle.jsx @@ -1,32 +1,55 @@ import { HStack, SegmentGroup } from "@chakra-ui/react"; import { FaBookOpen, FaImages } from "react-icons/fa"; +const items = [ + { + value: "books", + label: ( + + + Books + + ), + }, + { + value: "categories", + label: ( + + + Categories + + ), + }, +]; + const GroupToggle = ({ value, onChange }) => { return ( - - - - - Books - - ), - }, - { - value: "categories", - label: ( - - - Categories - - ), - }, - ]} - /> + onChange(value)} + size="lg" + bg="gray.50" + p="1" + rounded="l" + boxShadow="md" + gap="1" + > + + {items.map(({ value: val, label }) => ( + + {label} + + + ))} ); }; diff --git a/src/pages/DataStudio.jsx b/src/pages/DataStudio.jsx index 733cbde..b4003f1 100644 --- a/src/pages/DataStudio.jsx +++ b/src/pages/DataStudio.jsx @@ -4,40 +4,40 @@ import ItemGroups from "../components/DataStudio/ItemGroups.jsx"; function DataStudio() { return ( - - {/* Header */} - - Data Studio - - - Batch - - - {/* Responsive layout */} + + {/* Left Panel (Headers + Grid) */} - - + + Data Studio + + Batch - - + + {/* Scrollable Grid */} + + - + + {/* Right Panel (Groups, full height) */} + + + + ); } From e9b0a20978d0327add30208199f2063f53bfe3f4 Mon Sep 17 00:00:00 2001 From: JunHam Date: Mon, 28 Jul 2025 11:17:12 +0800 Subject: [PATCH 09/43] Changed groupToggle colour, added ArtefactEditor page --- src/components/DataStudio/groupToggle.jsx | 66 ++++++++++------------- src/main.jsx | 2 + src/pages/ArtefactEditor.jsx | 12 +++++ src/pages/Catalogue.jsx | 12 +++++ 4 files changed, 54 insertions(+), 38 deletions(-) create mode 100644 src/pages/ArtefactEditor.jsx diff --git a/src/components/DataStudio/groupToggle.jsx b/src/components/DataStudio/groupToggle.jsx index 40fb6b0..b05361a 100644 --- a/src/components/DataStudio/groupToggle.jsx +++ b/src/components/DataStudio/groupToggle.jsx @@ -1,28 +1,32 @@ -import { HStack, SegmentGroup } from "@chakra-ui/react"; +import { HStack, SegmentGroup, Text } from "@chakra-ui/react"; import { FaBookOpen, FaImages } from "react-icons/fa"; -const items = [ - { - value: "books", - label: ( - - - Books - - ), - }, - { - value: "categories", - label: ( - - - Categories - - ), - }, -]; - const GroupToggle = ({ value, onChange }) => { + const items = [ + { + value: "books", + label: ( + + + + Books + + + ), + }, + { + value: "categories", + label: ( + + + + Categories + + + ), + }, + ]; + return ( { boxShadow="md" gap="1" > - - {items.map(({ value: val, label }) => ( - - {label} - - - ))} + + ); }; diff --git a/src/main.jsx b/src/main.jsx index 9bacd86..16ee102 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -22,6 +22,7 @@ import AnimateIn from './AnimateIn.jsx'; import PublicGallery from './pages/PublicGallery.jsx'; import PublicProfile from './pages/PublicProfile.jsx'; import DataStudio from './pages/DataStudio.jsx'; +import ArtefactEditor from './pages/ArtefactEditor.jsx'; const store = configureStore({ reducer: { @@ -48,6 +49,7 @@ createRoot(document.getElementById('root')).render(
} /> + } /> {/* Protected Pages */} diff --git a/src/pages/ArtefactEditor.jsx b/src/pages/ArtefactEditor.jsx new file mode 100644 index 0000000..a3ef9c5 --- /dev/null +++ b/src/pages/ArtefactEditor.jsx @@ -0,0 +1,12 @@ +import { Box, Flex } from "@chakra-ui/react"; +import { useParams } from 'react-router-dom'; + +function ArtefactEditor() { + const { id } = useParams(); + console.log(id) + return ( + ArtefactEditor for {id} + ); +} + +export default ArtefactEditor; diff --git a/src/pages/Catalogue.jsx b/src/pages/Catalogue.jsx index 3a64487..59cc4e9 100644 --- a/src/pages/Catalogue.jsx +++ b/src/pages/Catalogue.jsx @@ -73,6 +73,18 @@ function Catalogue() { return ( <> + {/* Fixed Headers */} + + + Catalogue Browser + + + {hasBooks && ( handleItemClick(title, "MM")} From f179e40d17b93597f9f949cc143b9007d1f7adc0 Mon Sep 17 00:00:00 2001 From: JunHam Date: Mon, 28 Jul 2025 12:21:38 +0800 Subject: [PATCH 10/43] Added description and disabled hover effect for data studio card item --- src/components/Catalogue/cardItem.jsx | 12 ++++++------ src/components/DataStudio/ItemGrid.jsx | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/Catalogue/cardItem.jsx b/src/components/Catalogue/cardItem.jsx index 8c61dc2..e61d334 100644 --- a/src/components/Catalogue/cardItem.jsx +++ b/src/components/Catalogue/cardItem.jsx @@ -5,7 +5,7 @@ import CardComponent from "./cardComponent"; const MotionBox = motion.create(Box); -const CardItem = ({ itemTitle, itemDescription, selectedTitle, imageSrc }) => { +const CardItem = ({ itemTitle, itemDescription, selectedTitle, imageSrc, expandable = true, descriptionDisplay = false }) => { const isMobile = useBreakpointValue({ base: true, md: false }); const [isHovered, setIsHovered] = useState(false); @@ -44,7 +44,7 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, imageSrc }) => { }; const handleMouseEnter = () => { - if (isMobile) return; + if (isMobile || !expandable) return; hoverTimeoutRef.current = setTimeout(() => { updateHoverPosition(); setIsHovered(true); @@ -104,7 +104,7 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, imageSrc }) => { bg={isSelected ? "gray.100" : "white"} borderRadius="md" boxShadow={isSelected ? "lg" : "md"} - zIndex={isHovered ? 30 : "auto"} // Conditional z-index + zIndex={isHovered ? 30 : "auto"} > { itemDescription={itemDescription} isLoading={isLoading} isSelected={isSelected} - expanded={false} + expanded={descriptionDisplay} /> - {!isMobile && ( + {!isMobile && expandable && ( {isHovered && ( { position: "fixed", top: hoverPos.top, left: hoverPos.left + 15, - zIndex: 30, // Always high z-index for expanded card + zIndex: 30, pointerEvents: "auto", width: "280px", }} diff --git a/src/components/DataStudio/ItemGrid.jsx b/src/components/DataStudio/ItemGrid.jsx index ee986b5..40e1d4b 100644 --- a/src/components/DataStudio/ItemGrid.jsx +++ b/src/components/DataStudio/ItemGrid.jsx @@ -1,4 +1,3 @@ -// src/components/DataStudio/ItemGrid.jsx import { Box, Grid } from "@chakra-ui/react"; import CardItem from "../Catalogue/cardItem.jsx"; import placeholderImage from "../../assets/placeholderImage.png"; @@ -11,7 +10,7 @@ const dummyData = Array.from({ length: 24 }, (_, i) => ({ function ItemGrid({ selectedTitle }) { return ( - + ))} From 0de53f13fc854ead375636575e204916fce33f5d Mon Sep 17 00:00:00 2001 From: JunHam Date: Mon, 28 Jul 2025 15:19:56 +0800 Subject: [PATCH 11/43] Added rough drag and drop for data studio --- package.json | 1 + src/components/DataStudio/ItemGrid.jsx | 53 +++++++++---- src/components/DataStudio/ItemGroups.jsx | 24 +++--- src/pages/DataStudio.jsx | 96 ++++++++++++++++-------- 4 files changed, 119 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index 07ac251..e257f40 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@chakra-ui/react": "^3.20.0", + "@dnd-kit/core": "^6.3.1", "@emotion/react": "^11.14.0", "@reduxjs/toolkit": "^2.8.2", "axios": "^1.9.0", diff --git a/src/components/DataStudio/ItemGrid.jsx b/src/components/DataStudio/ItemGrid.jsx index 40e1d4b..965a50e 100644 --- a/src/components/DataStudio/ItemGrid.jsx +++ b/src/components/DataStudio/ItemGrid.jsx @@ -1,39 +1,62 @@ import { Box, Grid } from "@chakra-ui/react"; import CardItem from "../Catalogue/cardItem.jsx"; import placeholderImage from "../../assets/placeholderImage.png"; +import { useDraggable } from "@dnd-kit/core"; const dummyData = Array.from({ length: 24 }, (_, i) => ({ + id: `item-${i}`, imageSrc: placeholderImage, title: `Item ${i + 1}`, description: `This is item ${i + 1}`, })); +function DraggableCard({ item, selectedTitle }) { + const { attributes, listeners, setNodeRef } = useDraggable({ + id: item.id, + data: { + id: item.id, + title: item.title, + description: item.description, + imageSrc: item.imageSrc, + }, + }); + + return ( + + + + ); +} + + function ItemGrid({ selectedTitle }) { return ( - - + {dummyData.map((item, idx) => ( - - - + ))} ); } -export default ItemGrid; \ No newline at end of file +export default ItemGrid; diff --git a/src/components/DataStudio/ItemGroups.jsx b/src/components/DataStudio/ItemGroups.jsx index 242ea40..9fda549 100644 --- a/src/components/DataStudio/ItemGroups.jsx +++ b/src/components/DataStudio/ItemGroups.jsx @@ -3,12 +3,12 @@ import { Box, Flex } from "@chakra-ui/react"; import GroupToggle from "./groupToggle"; import GroupCard from "./groupCard"; -function ItemGroups() { +function ItemGroups({ droppedItems }) { const [selectedGroup, setSelectedGroup] = useState("books"); const title = "Book of LIFE"; const description = - "This book contains the life of Mr Joon (AKA B.A.S), a legend forged in caffeine, code, and sheer audacity. From questionable life choices to accidental genius, his story blurs the line between myth and mayhem. Read on, but beware — once you know B.A.S, reality might never feel normal again.."; + "This book contains the life of Mr Joon (AKA B.A.S), a legend forged in caffeine, code, and sheer audacity. From questionable life choices to accidental genius, his story blurs the line between myth and mayhem."; return ( - {/* Scrollable Group Cards */} - {Array.from({ length: 10 }).map((_, i) => ( - - ))} + {Array.from({ length: 3 }).map((_, i) => { + const groupId = `group-${i}`; + return ( + + ); + })} ); diff --git a/src/pages/DataStudio.jsx b/src/pages/DataStudio.jsx index b4003f1..c1165d4 100644 --- a/src/pages/DataStudio.jsx +++ b/src/pages/DataStudio.jsx @@ -1,44 +1,80 @@ +import { DndContext, DragOverlay } from "@dnd-kit/core"; +import { useState } from "react"; import { Box, Flex, Text } from "@chakra-ui/react"; import ItemGrid from "../components/DataStudio/ItemGrid.jsx"; import ItemGroups from "../components/DataStudio/ItemGroups.jsx"; +import CardItem from "../components/Catalogue/cardItem.jsx"; function DataStudio() { + const [droppedItems, setDroppedItems] = useState({}); + const [activeItem, setActiveItem] = useState(null); // Track currently dragged item + + const handleDragStart = (event) => { + setActiveItem(event.active.data.current); + }; + + const handleDragEnd = (event) => { + const { active, over } = event; + setActiveItem(null); // Clear on drop + + if (over && active) { + const groupId = over.id; + const itemData = active.data.current; + + setDroppedItems((prev) => { + const existing = prev[groupId] || []; + const newList = [...existing, itemData]; + return { + ...prev, + [groupId]: newList, + }; + }); + } + }; + return ( - - {/* Left Panel (Headers + Grid) */} - - {/* Fixed Headers */} - + + {/* Left Panel */} + - - Data Studio - - Batch - + + Data Studio + Batch + + + + + + - {/* Scrollable Grid */} - - + {/* Right Panel */} + + - {/* Right Panel (Groups, full height) */} - - - - + {/* Drag preview (not clipped by layout) */} + + {activeItem ? ( + + + + ) : null} + + ); } -export default DataStudio; \ No newline at end of file +export default DataStudio; From 50147381b805b244f7fc5e71836dd56b2c97b252 Mon Sep 17 00:00:00 2001 From: JunHam Date: Mon, 28 Jul 2025 16:35:19 +0800 Subject: [PATCH 12/43] Refined drag and drop for data studio --- src/components/DataStudio/ItemGrid.jsx | 34 +++++++++++------------- src/components/DataStudio/ItemGroups.jsx | 31 +++++++++++++++++---- src/pages/DataStudio.jsx | 25 +++++++++-------- 3 files changed, 55 insertions(+), 35 deletions(-) diff --git a/src/components/DataStudio/ItemGrid.jsx b/src/components/DataStudio/ItemGrid.jsx index 965a50e..8fcae2b 100644 --- a/src/components/DataStudio/ItemGrid.jsx +++ b/src/components/DataStudio/ItemGrid.jsx @@ -10,24 +10,25 @@ const dummyData = Array.from({ length: 24 }, (_, i) => ({ description: `This is item ${i + 1}`, })); -function DraggableCard({ item, selectedTitle }) { - const { attributes, listeners, setNodeRef } = useDraggable({ +function DraggableCard({ item }) { + const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id: item.id, - data: { - id: item.id, - title: item.title, - description: item.description, - imageSrc: item.imageSrc, - }, + data: item, // Pass full item data for DragOverlay }); return ( - + @@ -35,8 +36,7 @@ function DraggableCard({ item, selectedTitle }) { ); } - -function ItemGrid({ selectedTitle }) { +function ItemGrid() { return ( - {dummyData.map((item, idx) => ( - + {dummyData.map((item) => ( + ))} ); } -export default ItemGrid; +export default ItemGrid; \ No newline at end of file diff --git a/src/components/DataStudio/ItemGroups.jsx b/src/components/DataStudio/ItemGroups.jsx index 9fda549..ae25348 100644 --- a/src/components/DataStudio/ItemGroups.jsx +++ b/src/components/DataStudio/ItemGroups.jsx @@ -1,14 +1,35 @@ -import { useState } from "react"; import { Box, Flex } from "@chakra-ui/react"; import GroupToggle from "./groupToggle"; import GroupCard from "./groupCard"; +import { useDroppable } from "@dnd-kit/core"; +import { useEffect, useState } from 'react' + +function DroppableGroupCard({ id, title, description, items }) { + const { setNodeRef, isOver } = useDroppable({ id }); + + return ( + + + + ); +} function ItemGroups({ droppedItems }) { const [selectedGroup, setSelectedGroup] = useState("books"); const title = "Book of LIFE"; const description = - "This book contains the life of Mr Joon (AKA B.A.S), a legend forged in caffeine, code, and sheer audacity. From questionable life choices to accidental genius, his story blurs the line between myth and mayhem."; + "This book contains the life of Mr Joon (AKA B.A.S), a legend forged in caffeine, code, and sheer audacity."; return ( - {Array.from({ length: 3 }).map((_, i) => { + {Array.from({ length: 10 }).map((_, i) => { const groupId = `group-${i}`; return ( - { setActiveItem(event.active.data.current); @@ -15,18 +15,22 @@ function DataStudio() { const handleDragEnd = (event) => { const { active, over } = event; - setActiveItem(null); // Clear on drop + setActiveItem(null); if (over && active) { const groupId = over.id; - const itemData = active.data.current; + const itemId = active.id; setDroppedItems((prev) => { - const existing = prev[groupId] || []; - const newList = [...existing, itemData]; + // Avoid duplicates by checking if item already exists in the group + const existingItems = prev[groupId] || []; + if (existingItems.some((item) => item.id === itemId)) { + return prev; + } + return { ...prev, - [groupId]: newList, + [groupId]: [...existingItems, active.data.current], }; }); } @@ -35,7 +39,7 @@ function DataStudio() { return ( - {/* Left Panel */} + {/* Left Panel (Draggable Items) */} Data Studio Batch - - {/* Right Panel */} + {/* Right Panel (Droppable Groups) */} - {/* Drag preview (not clipped by layout) */} + {/* Drag Preview */} {activeItem ? ( @@ -77,4 +80,4 @@ function DataStudio() { ); } -export default DataStudio; +export default DataStudio; \ No newline at end of file From b019c365dc980ce5cde1005705007a1f5f3c396a Mon Sep 17 00:00:00 2001 From: JunHam Date: Tue, 29 Jul 2025 13:44:03 +0800 Subject: [PATCH 13/43] Added GroupView, modified paths --- src/main.jsx | 10 ++++++++-- src/pages/ArtefactEditor.jsx | 6 +++--- src/pages/GroupView.jsx | 11 +++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 src/pages/GroupView.jsx diff --git a/src/main.jsx b/src/main.jsx index 16ee102..bc47867 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -23,6 +23,7 @@ import PublicGallery from './pages/PublicGallery.jsx'; import PublicProfile from './pages/PublicProfile.jsx'; import DataStudio from './pages/DataStudio.jsx'; import ArtefactEditor from './pages/ArtefactEditor.jsx'; +import GroupView from './pages/GroupView.jsx'; const store = configureStore({ reducer: { @@ -48,8 +49,13 @@ createRoot(document.getElementById('root')).render( } /> - } /> - } /> + + } /> + + } /> + } /> + + {/* Protected Pages */} diff --git a/src/pages/ArtefactEditor.jsx b/src/pages/ArtefactEditor.jsx index a3ef9c5..e78b175 100644 --- a/src/pages/ArtefactEditor.jsx +++ b/src/pages/ArtefactEditor.jsx @@ -2,10 +2,10 @@ import { Box, Flex } from "@chakra-ui/react"; import { useParams } from 'react-router-dom'; function ArtefactEditor() { - const { id } = useParams(); - console.log(id) + const { artID } = useParams(); + console.log(artID) return ( - ArtefactEditor for {id} + ArtefactEditor for {artID} ); } diff --git a/src/pages/GroupView.jsx b/src/pages/GroupView.jsx new file mode 100644 index 0000000..ed8b2da --- /dev/null +++ b/src/pages/GroupView.jsx @@ -0,0 +1,11 @@ +import { useParams } from 'react-router-dom'; + +function GroupView() { + const { artID, colID } = useParams(); + console.log(artID) + console.log(colID) + + return
GroupView for collection {colID}, {artID}
; +} + +export default GroupView; From a9114c997dfed5fe90d81ad621b10aa7b7d3a201 Mon Sep 17 00:00:00 2001 From: JunHam Date: Tue, 29 Jul 2025 14:40:35 +0800 Subject: [PATCH 14/43] Added item view toggle --- .../Catalogue/catalogueItemView.jsx | 8 +++- src/components/Catalogue/itemViewToggle.jsx | 47 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/components/Catalogue/itemViewToggle.jsx diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 62025c0..e8e31da 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -2,6 +2,7 @@ import { Portal, CloseButton, Image, Box, Text, Icon, useBreakpointValue } from import { useEffect, useState, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; +import ItemViewToggle from "./itemViewToggle"; const MotionBox = motion.create(Box); const MotionChevron = motion.create(Box); @@ -12,6 +13,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag const [isInitialRender, setIsInitialRender] = useState(true); const [hasRendered, setHasRendered] = useState(false); const isMobile = useBreakpointValue({ base: true, md: false }); + const [selectedGroup, setSelectedGroup] = useState("metadata"); const scrollRef = useRef(null); const modalRef = useRef(null); @@ -269,6 +271,8 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag {item.title} + +
))} @@ -316,9 +320,11 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag overflowY="auto" > - + {title} + +
diff --git a/src/components/Catalogue/itemViewToggle.jsx b/src/components/Catalogue/itemViewToggle.jsx new file mode 100644 index 0000000..51d76c6 --- /dev/null +++ b/src/components/Catalogue/itemViewToggle.jsx @@ -0,0 +1,47 @@ +import { HStack, SegmentGroup, Text } from "@chakra-ui/react"; +import { TbFileDatabase, TbMessageChatbotFilled } from "react-icons/tb"; + +const ItemViewToggle = ({ value, onChange }) => { + const items = [ + { + value: "metadata", + label: ( + + + + Metadata + + + ), + }, + { + value: "chat", + label: ( + + + + Chat + + + ), + }, + ]; + + return ( + onChange(value)} + size="md" + bg="gray.50" + p="1" + rounded="l" + boxShadow="md" + gap="1" + > + + + + ); +}; + +export default ItemViewToggle; From ec41909db0b0436d1e80875f41c6f788673c0a2a Mon Sep 17 00:00:00 2001 From: JunHam Date: Tue, 29 Jul 2025 15:00:18 +0800 Subject: [PATCH 15/43] Refined item view toggle --- .../Catalogue/catalogueItemView.jsx | 65 ++++++++++++++----- src/components/Catalogue/itemViewToggle.jsx | 46 ++++++++++--- 2 files changed, 84 insertions(+), 27 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index e8e31da..9ed44aa 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -13,6 +13,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag const [isInitialRender, setIsInitialRender] = useState(true); const [hasRendered, setHasRendered] = useState(false); const isMobile = useBreakpointValue({ base: true, md: false }); + const isTablet = useBreakpointValue({ base: false, md: true, lg: false }); const [selectedGroup, setSelectedGroup] = useState("metadata"); const scrollRef = useRef(null); const modalRef = useRef(null); @@ -100,13 +101,13 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag }; const modalSize = { - width: isMobile ? "100vw" : "80vw", - height: isMobile ? "100vh" : "80vh", + width: isMobile ? "100vw" : isTablet ? "90vw" : "80vw", + height: isMobile ? "100vh" : isTablet ? "90vh" : "80vh", }; const chevronPosition = { - left: isMobile ? "10px" : "calc(10vw - 60px)", - right: isMobile ? "10px" : "calc(10vw - 60px)", + left: isMobile ? "10px" : isTablet ? "calc(5vw - 50px)" : "calc(10vw - 60px)", + right: isMobile ? "10px" : isTablet ? "calc(5vw - 50px)" : "calc(10vw - 60px)", top: "50%", }; @@ -140,7 +141,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag onClick={onClose} /> - {/* Desktop chevrons */} + {/* Desktop/Tablet chevrons */} {!isMobile && ( <> {currentIndex > 0 && ( @@ -251,7 +252,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag display="flex" flexDirection="column" > - + {item.title} - + {item.title} - + + + ))} @@ -281,7 +291,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag )} ) : ( - // Desktop: sliding animation + // Desktop/Tablet: sliding animation - + {title} - - + + {title} - + + + @@ -340,4 +371,4 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag ); }; -export default CatalogueItemView; +export default CatalogueItemView; \ No newline at end of file diff --git a/src/components/Catalogue/itemViewToggle.jsx b/src/components/Catalogue/itemViewToggle.jsx index 51d76c6..4d5e395 100644 --- a/src/components/Catalogue/itemViewToggle.jsx +++ b/src/components/Catalogue/itemViewToggle.jsx @@ -1,7 +1,9 @@ -import { HStack, SegmentGroup, Text } from "@chakra-ui/react"; -import { TbFileDatabase, TbMessageChatbotFilled } from "react-icons/tb"; +import { HStack, SegmentGroup, Text, useBreakpointValue } from "@chakra-ui/react"; +import { TbFileDatabase, TbMessageChatbot } from "react-icons/tb"; -const ItemViewToggle = ({ value, onChange }) => { +const ItemViewToggle = ({ value, onChange, size = "md" }) => { + const isMobile = useBreakpointValue({ base: true, md: false }); + const items = [ { value: "metadata", @@ -9,7 +11,7 @@ const ItemViewToggle = ({ value, onChange }) => { - Metadata + {isMobile ? "Info" : "Metadata"} ), @@ -18,7 +20,7 @@ const ItemViewToggle = ({ value, onChange }) => { value: "chat", label: ( - + Chat @@ -27,21 +29,45 @@ const ItemViewToggle = ({ value, onChange }) => { }, ]; + // Keep original styling but make responsive + const segmentProps = { + sm: { + p: "1", + rounded: "lg", + width: "180px" + }, + md: { + p: "1", + rounded: "lg", + width: "210px" + }, + lg: { + p: "1.5", + rounded: "lg", + width: "240px" + } + }; + + const currentProps = segmentProps[size] || segmentProps.md; + return ( onChange(value)} - size="md" + size={size} bg="gray.50" - p="1" - rounded="l" + p={currentProps.p} + rounded={currentProps.rounded} boxShadow="md" gap="1" > - + ); }; -export default ItemViewToggle; +export default ItemViewToggle; \ No newline at end of file From 0a78b6a963a5ed5d468d1ca94f089018763bdf19 Mon Sep 17 00:00:00 2001 From: JunHam Date: Tue, 29 Jul 2025 15:31:09 +0800 Subject: [PATCH 16/43] Added item view menu --- .../Catalogue/catalogueItemView.jsx | 10 +++--- src/components/Catalogue/itemViewMenu.jsx | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 src/components/Catalogue/itemViewMenu.jsx diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 9ed44aa..dc027b3 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -2,7 +2,9 @@ import { Portal, CloseButton, Image, Box, Text, Icon, useBreakpointValue } from import { useEffect, useState, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; +import { CgMoreVerticalAlt } from "react-icons/cg"; import ItemViewToggle from "./itemViewToggle"; +import ItemViewMenu from "./itemViewMenu"; const MotionBox = motion.create(Box); const MotionChevron = motion.create(Box); @@ -336,12 +338,8 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag display={"flex"} flexDirection={"column"} > - + + {title} diff --git a/src/components/Catalogue/itemViewMenu.jsx b/src/components/Catalogue/itemViewMenu.jsx new file mode 100644 index 0000000..7e64379 --- /dev/null +++ b/src/components/Catalogue/itemViewMenu.jsx @@ -0,0 +1,35 @@ +import { Box, Menu, Portal } from "@chakra-ui/react"; +import { CgMoreVerticalAlt } from "react-icons/cg"; + +const ItemViewMenu = ({ onClose }) => { + return ( + + + + + + + + + + + Open in Data Studio + + + Metadata Version History + + + + + + ); +}; + +export default ItemViewMenu; \ No newline at end of file From 9b238c03f4a9ab43e40f710875587a28c18f5212 Mon Sep 17 00:00:00 2001 From: JunHam Date: Tue, 29 Jul 2025 15:36:56 +0800 Subject: [PATCH 17/43] Added item view menu for mobile --- src/components/Catalogue/catalogueItemView.jsx | 12 +++++++++++- src/components/Catalogue/itemViewMenu.jsx | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index dc027b3..5c95919 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -269,8 +269,18 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag p={4} overflowY="auto" position="relative" + display={"flex"} + flexDirection={"column"} > - + + + + {item.title} diff --git a/src/components/Catalogue/itemViewMenu.jsx b/src/components/Catalogue/itemViewMenu.jsx index 7e64379..95059de 100644 --- a/src/components/Catalogue/itemViewMenu.jsx +++ b/src/components/Catalogue/itemViewMenu.jsx @@ -13,7 +13,7 @@ const ItemViewMenu = ({ onClose }) => { cursor={"pointer"} mt={2} > - + From e105261a3ed7a64fa753efe0d549348ef44491d6 Mon Sep 17 00:00:00 2001 From: JunHam Date: Tue, 29 Jul 2025 15:43:51 +0800 Subject: [PATCH 18/43] Added skeleton for metadataDisplay and itemChat for catalogueItemView --- src/components/Catalogue/catalogueItemView.jsx | 16 +++++++++++++--- src/components/Catalogue/itemChat.jsx | 5 +++++ src/components/Catalogue/metadataDisplay.jsx | 5 +++++ 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 src/components/Catalogue/itemChat.jsx create mode 100644 src/components/Catalogue/metadataDisplay.jsx diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 5c95919..1195a41 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -5,6 +5,8 @@ import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; import { CgMoreVerticalAlt } from "react-icons/cg"; import ItemViewToggle from "./itemViewToggle"; import ItemViewMenu from "./itemViewMenu"; +import MetadataDisplay from "./metadataDisplay"; +import ItemChat from "./itemChat"; const MotionBox = motion.create(Box); const MotionChevron = motion.create(Box); @@ -16,7 +18,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag const [hasRendered, setHasRendered] = useState(false); const isMobile = useBreakpointValue({ base: true, md: false }); const isTablet = useBreakpointValue({ base: false, md: true, lg: false }); - const [selectedGroup, setSelectedGroup] = useState("metadata"); + const [selectedSegment, setSelectedSegment] = useState("metadata"); const scrollRef = useRef(null); const modalRef = useRef(null); @@ -359,11 +361,19 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag justifyContent="center" > + + + {selectedSegment === "metadata" ? ( + + ) : selectedSegment === "chat" ? ( + + ) : null} + diff --git a/src/components/Catalogue/itemChat.jsx b/src/components/Catalogue/itemChat.jsx new file mode 100644 index 0000000..415be99 --- /dev/null +++ b/src/components/Catalogue/itemChat.jsx @@ -0,0 +1,5 @@ +function ItemChat() { + return
ItemChat
; +} + +export default ItemChat; diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx new file mode 100644 index 0000000..e0c1e0a --- /dev/null +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -0,0 +1,5 @@ +function MetadataDisplay() { + return
MetadataDisplay
; +} + +export default MetadataDisplay; From 24b889a8841e2ebe0f261778e62883c6f1cb0d88 Mon Sep 17 00:00:00 2001 From: JunHam Date: Wed, 30 Jul 2025 15:02:09 +0800 Subject: [PATCH 19/43] Displayed metadata on catalogue item view --- src/components/Catalogue/cardItem.jsx | 8 +- .../Catalogue/catalogueItemView.jsx | 97 ++++++++++++++-- src/components/Catalogue/itemViewMenu.jsx | 10 +- src/components/Catalogue/metadataDisplay.jsx | 106 +++++++++++++++++- src/components/Catalogue/mmSection.jsx | 3 +- src/components/Catalogue/section.jsx | 3 +- src/pages/Catalogue.jsx | 2 +- 7 files changed, 198 insertions(+), 31 deletions(-) diff --git a/src/components/Catalogue/cardItem.jsx b/src/components/Catalogue/cardItem.jsx index e61d334..27b0eae 100644 --- a/src/components/Catalogue/cardItem.jsx +++ b/src/components/Catalogue/cardItem.jsx @@ -5,11 +5,11 @@ import CardComponent from "./cardComponent"; const MotionBox = motion.create(Box); -const CardItem = ({ itemTitle, itemDescription, selectedTitle, imageSrc, expandable = true, descriptionDisplay = false }) => { +const CardItem = ({ itemTitle, itemDescription, imageSrc, expandable = true, descriptionDisplay = false }) => { const isMobile = useBreakpointValue({ base: true, md: false }); const [isHovered, setIsHovered] = useState(false); - const [isSelected, setIsSelected] = useState(selectedTitle === itemTitle); + const [isSelected, setIsSelected] = useState(); const [isLoading, setIsLoading] = useState(true); const cardRef = useRef(null); @@ -17,10 +17,6 @@ const CardItem = ({ itemTitle, itemDescription, selectedTitle, imageSrc, expanda const scrollContainerRef = useRef(null); const hoverTimeoutRef = useRef(null); - useEffect(() => { - setIsSelected(selectedTitle === itemTitle); - }, [selectedTitle, itemTitle]); - const updateHoverPosition = () => { const rect = cardRef.current?.getBoundingClientRect(); if (rect) { diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 1195a41..4f186b7 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -1,12 +1,13 @@ -import { Portal, CloseButton, Image, Box, Text, Icon, useBreakpointValue } from "@chakra-ui/react"; +import { Portal, CloseButton, Image, Box, Text, Icon, Flex, useBreakpointValue } from "@chakra-ui/react"; import { useEffect, useState, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; -import { CgMoreVerticalAlt } from "react-icons/cg"; import ItemViewToggle from "./itemViewToggle"; import ItemViewMenu from "./itemViewMenu"; import MetadataDisplay from "./metadataDisplay"; import ItemChat from "./itemChat"; +import server, { JSONResponse } from "../../networking"; +import ToastWizard from "../toastWizard"; const MotionBox = motion.create(Box); const MotionChevron = motion.create(Box); @@ -21,8 +22,57 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag const [selectedSegment, setSelectedSegment] = useState("metadata"); const scrollRef = useRef(null); const modalRef = useRef(null); + const [metadata, setMetadata] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); const currentIndex = items.findIndex((item) => item.title === title); + const currentItem = items[currentIndex]; + + const fetchMetadata = async () => { + if (!currentItem?.id) return; + + setLoading(true); + setError(null); + + try { + const res = await server.get(`/cdn/artefactMetadata/${currentItem.id}`); + if (res.data instanceof JSONResponse) { + if (res.data.isErrorStatus()) { + console.error("Metadata error:", res.data.fullMessage()); + + if (res.data.userErrorType()) { + ToastWizard.standard("error", "Metadata error", res.data.message); + setError(res.data.message); + } else { + ToastWizard.standard("error", "Metadata error", "Unexpected server error."); + setError("Unexpected server error."); + } + + setMetadata(null); + } else { + setMetadata(res.data.raw.data); + } + } else { + console.warn("Unexpected metadata response:", res.data); + ToastWizard.standard("error", "Metadata error", "Malformed server response."); + setError("Malformed server response."); + setMetadata(null); + } + + } catch (err) { + console.error("Exception during metadata fetch:", err); + ToastWizard.standard("error", "Connection error", "Failed to fetch metadata."); + setError("Failed to fetch metadata."); + setMetadata(null); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchMetadata(); + }, [currentItem?.id]); const prevItem = () => { if (!isNavigating && currentIndex > 0) { @@ -283,7 +333,9 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag - + + + {item.title} @@ -292,11 +344,23 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag justifyContent="center" > + + + {selectedSegment === "metadata" ? ( + + ) : selectedSegment === "chat" ? ( + + ) : null} + ))} @@ -346,15 +410,22 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag height="100%" p={{ md: 4, lg: 6 }} position="relative" - overflowY="auto" display={"flex"} flexDirection={"column"} + overflow={"hidden"} > - + - - {title} - + + + {title} + + + {selectedSegment === "metadata" ? ( - + ) : selectedSegment === "chat" ? ( ) : null} diff --git a/src/components/Catalogue/itemViewMenu.jsx b/src/components/Catalogue/itemViewMenu.jsx index 95059de..4a63799 100644 --- a/src/components/Catalogue/itemViewMenu.jsx +++ b/src/components/Catalogue/itemViewMenu.jsx @@ -1,17 +1,13 @@ import { Box, Menu, Portal } from "@chakra-ui/react"; import { CgMoreVerticalAlt } from "react-icons/cg"; -const ItemViewMenu = ({ onClose }) => { +const ItemViewMenu = () => { return ( - + diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index e0c1e0a..5f52e03 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -1,5 +1,107 @@ -function MetadataDisplay() { - return
MetadataDisplay
; +import { Box, Text } from "@chakra-ui/react"; +import { useEffect } from "react"; +import CentredSpinner from "../CentredSpinner"; +import ToastWizard from "../toastWizard"; + +function MetadataDisplay({ metadata, loading, error }) { + // Show toast on error + useEffect(() => { + if (error) { + ToastWizard.standard("error", "Metadata Error", error); + } + }, [error]); + + if (loading) { + return ; + } + + if (!metadata) { + return ( + + No metadata available. + + ); + } + + const isOCRMetadata = + metadata.english || metadata.tradCN || metadata.simplifiedCN; + const isFigureMetadata = metadata.caption || metadata.figureIDs; + + return ( + + {isOCRMetadata && ( + <> + + Traditional Chinese: + + {metadata.tradCN || "N/A"} + + + Simplified Chinese: + + {metadata.simplifiedCN || "N/A"} + + + English: + + {metadata.english || "N/A"} + + + Summary: + + {metadata.summary || "N/A"} + + + NER Labels: + + + {Array.isArray(metadata.nerLabels) + ? metadata.nerLabels.join(", ") + : "N/A"} + + + )} + + {isFigureMetadata && ( + <> + + Caption: + + {metadata.caption || "N/A"} + + + Figure IDs: + + {Array.isArray(metadata.figureIDs) && + metadata.figureIDs.length > 0 ? ( + + {metadata.figureIDs.map((id) => ( + • {id} + ))} + + ) : ( + N/A + )} + + {metadata.addInfo && ( + <> + + Additional Info: + + {metadata.addInfo} + + )} + + )} + + ); } export default MetadataDisplay; diff --git a/src/components/Catalogue/mmSection.jsx b/src/components/Catalogue/mmSection.jsx index 548d8ca..53a7005 100644 --- a/src/components/Catalogue/mmSection.jsx +++ b/src/components/Catalogue/mmSection.jsx @@ -4,7 +4,7 @@ import CardItem from "./cardItem.jsx"; import ArrowOverlay from "./arrowOverlay.jsx"; import placeholderImage from "../../assets/placeholderImage.png" -const MMSection = ({ books = [], onItemClick, selectedTitle }) => { +const MMSection = ({ books = [], onItemClick }) => { const isMobile = useBreakpointValue({ base: true, md: false }); const scrollRef = useRef(null); const [atStart, setAtStart] = useState(true); @@ -115,7 +115,6 @@ const MMSection = ({ books = [], onItemClick, selectedTitle }) => {
diff --git a/src/components/Catalogue/section.jsx b/src/components/Catalogue/section.jsx index d221dc7..fcb0d8d 100644 --- a/src/components/Catalogue/section.jsx +++ b/src/components/Catalogue/section.jsx @@ -5,7 +5,7 @@ import CatalogueItemView from "./catalogueItemView.jsx"; import ArrowOverlay from "./arrowOverlay.jsx"; import placeholderImage from "../../assets/placeholderImage.png" -const Section = ({ sectionTitle, selectedTitle, onItemClick, artefacts = [] }) => { +const Section = ({ sectionTitle, onItemClick, artefacts = [] }) => { const isMobile = useBreakpointValue({ base: true, md: false }); const items = artefacts.map((art) => ({ @@ -131,7 +131,6 @@ const Section = ({ sectionTitle, selectedTitle, onItemClick, artefacts = [] }) = imageSrc={item.imageSrc} itemTitle={item.title} itemDescription={item.description} - selectedTitle={selectedTitle} /> )) diff --git a/src/pages/Catalogue.jsx b/src/pages/Catalogue.jsx index 59cc4e9..ee77ab1 100644 --- a/src/pages/Catalogue.jsx +++ b/src/pages/Catalogue.jsx @@ -3,7 +3,7 @@ import { Box, Text, Center } from "@chakra-ui/react"; import { AnimatePresence, motion } from "framer-motion"; import MMSection from "../components/Catalogue/mmSection.jsx"; import Section from "../components/Catalogue/section.jsx"; -import CentredSpinner from "../components/centredSpinner.jsx"; +import CentredSpinner from "../components/CentredSpinner.jsx"; import server, { JSONResponse } from '../networking' import ToastWizard from "../components/toastWizard.js"; From 39c8b6c128dbe2358cf550e5e7410e948abc462d Mon Sep 17 00:00:00 2001 From: JunHam Date: Wed, 30 Jul 2025 15:31:59 +0800 Subject: [PATCH 20/43] Fixed mobile view --- .../Catalogue/catalogueItemView.jsx | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 4f186b7..75ad556 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -321,28 +321,27 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag p={4} overflowY="auto" position="relative" - display={"flex"} - flexDirection={"column"} + display="flex" + flexDirection="column" > - - - - - - - - {item.title} - + + + {item.title} + + + + + + - + Date: Wed, 30 Jul 2025 21:14:13 +0800 Subject: [PATCH 21/43] Added transcriptionToggle jsx --- src/components/Catalogue/transcriptionToggle.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/components/Catalogue/transcriptionToggle.jsx diff --git a/src/components/Catalogue/transcriptionToggle.jsx b/src/components/Catalogue/transcriptionToggle.jsx new file mode 100644 index 0000000..267c238 --- /dev/null +++ b/src/components/Catalogue/transcriptionToggle.jsx @@ -0,0 +1,8 @@ + +function TranscriptionToggle() { + return ( +
TranscriptionToggle
+ ) +} + +export default TranscriptionToggle \ No newline at end of file From 9937f0c4952ce7774b3c69a64676164178b38c60 Mon Sep 17 00:00:00 2001 From: JunHam Date: Wed, 30 Jul 2025 21:17:48 +0800 Subject: [PATCH 22/43] Added Batch to groupToggle for studio --- src/components/DataStudio/groupToggle.jsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/DataStudio/groupToggle.jsx b/src/components/DataStudio/groupToggle.jsx index b05361a..9cd9a88 100644 --- a/src/components/DataStudio/groupToggle.jsx +++ b/src/components/DataStudio/groupToggle.jsx @@ -1,5 +1,5 @@ import { HStack, SegmentGroup, Text } from "@chakra-ui/react"; -import { FaBookOpen, FaImages } from "react-icons/fa"; +import { FaBookOpen, FaImages, FaCubes } from "react-icons/fa"; const GroupToggle = ({ value, onChange }) => { const items = [ @@ -25,6 +25,17 @@ const GroupToggle = ({ value, onChange }) => { ), }, + { + value: "batch", + label: ( + + + + Batch + + + ), + }, ]; return ( @@ -39,7 +50,7 @@ const GroupToggle = ({ value, onChange }) => { gap="1" > - + ); }; From 920d8fb8943c453131162fe92178c385d1fe3263 Mon Sep 17 00:00:00 2001 From: JunHam Date: Wed, 30 Jul 2025 22:54:42 +0800 Subject: [PATCH 23/43] Added transcription toggle and rendering of ner labels as highlights in english metadata display --- src/components/Catalogue/metadataDisplay.jsx | 126 +++++++++++++----- .../Catalogue/transcriptionToggle.jsx | 72 +++++++++- 2 files changed, 158 insertions(+), 40 deletions(-) diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index 5f52e03..60ec683 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -1,10 +1,65 @@ -import { Box, Text } from "@chakra-ui/react"; -import { useEffect } from "react"; +import { Box, Flex, Text } from "@chakra-ui/react"; +import { Tooltip } from "../ui/tooltip" +import { useEffect, useState } from "react"; import CentredSpinner from "../CentredSpinner"; import ToastWizard from "../toastWizard"; +import TranscriptionToggle from "./transcriptionToggle"; + +function renderHighlightedText(text, labels) { + if (!labels || !Array.isArray(labels)) return {text}; + + const entityColors = { + PERSON: "yellow.200", + ORGANIZATION: "blue.200", + LOCATION: "green.200", + DATE: "orange.200", + GPE: "purple.200", + EVENT: "pink.200" + }; + + const chunks = []; + let remainingText = text; + + labels.forEach(([entity, label], i) => { + console.log("Highlighting label:", label); + const index = remainingText.indexOf(entity); + if (index === -1) return; + + const before = remainingText.slice(0, index); + const matched = remainingText.slice(index, index + entity.length); + const after = remainingText.slice(index + entity.length); + + if (before) { + chunks.push({before}); + } + + chunks.push( + + + {matched} + + + ); + + remainingText = after; + }); + + if (remainingText) { + chunks.push({remainingText}); + } + + return {chunks}; +} function MetadataDisplay({ metadata, loading, error }) { - // Show toast on error + const [selectedTranscription, setSelectedTranscription] = useState("tradCN"); + useEffect(() => { if (error) { ToastWizard.standard("error", "Metadata Error", error); @@ -26,49 +81,53 @@ function MetadataDisplay({ metadata, loading, error }) { const isOCRMetadata = metadata.english || metadata.tradCN || metadata.simplifiedCN; const isFigureMetadata = metadata.caption || metadata.figureIDs; + console.log(metadata.nerLabels) return ( + {/* Transcription Toggle */} {isOCRMetadata && ( <> - - Traditional Chinese: - - {metadata.tradCN || "N/A"} - - - Simplified Chinese: - - {metadata.simplifiedCN || "N/A"} - - - English: - - {metadata.english || "N/A"} - - - Summary: - - {metadata.summary || "N/A"} + + + - - NER Labels: - - - {Array.isArray(metadata.nerLabels) - ? metadata.nerLabels.join(", ") - : "N/A"} - + {/* Transcription Text with Conditional Highlighting */} + + {selectedTranscription === "english" ? ( + renderHighlightedText( + metadata[selectedTranscription] || "", + metadata.nerLabels + ) + ) : ( + + {metadata[selectedTranscription] || "N/A"} + + )} + )} + {/* Figure Metadata Section */} {isFigureMetadata && ( <> @@ -79,8 +138,7 @@ function MetadataDisplay({ metadata, loading, error }) { Figure IDs: - {Array.isArray(metadata.figureIDs) && - metadata.figureIDs.length > 0 ? ( + {Array.isArray(metadata.figureIDs) && metadata.figureIDs.length > 0 ? ( {metadata.figureIDs.map((id) => ( • {id} diff --git a/src/components/Catalogue/transcriptionToggle.jsx b/src/components/Catalogue/transcriptionToggle.jsx index 267c238..adec14b 100644 --- a/src/components/Catalogue/transcriptionToggle.jsx +++ b/src/components/Catalogue/transcriptionToggle.jsx @@ -1,8 +1,68 @@ +import { SegmentGroup, Text } from "@chakra-ui/react"; -function TranscriptionToggle() { - return ( -
TranscriptionToggle
- ) -} +const TranscriptionToggle = ({ value, onChange }) => { + const items = [ + { + value: "tradCN", + label: ( + + Trad CN + + ), + }, + { + value: "simplifiedCN", + label: ( + + Simp CN + + ), + }, + { + value: "english", + label: ( + + ENG + + ), + }, + { + value: "summary", + label: ( + + SUM + + ), + }, + ]; -export default TranscriptionToggle \ No newline at end of file + return ( + onChange(value)} + size="sm" + bg="gray.100" + p="1" + rounded="lg" + boxShadow="sm" + gap="00" + > + + + + ); +}; + +export default TranscriptionToggle; From 10a1950816ffbf83a59af990eed51c0c6d80f8ae Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 31 Jul 2025 17:37:06 +0800 Subject: [PATCH 24/43] Added hover tooltip for metadata display --- src/components/Catalogue/metadataDisplay.jsx | 56 ++++++++++++++------ 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index 60ec683..1d5257b 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -5,7 +5,7 @@ import CentredSpinner from "../CentredSpinner"; import ToastWizard from "../toastWizard"; import TranscriptionToggle from "./transcriptionToggle"; -function renderHighlightedText(text, labels) { +function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { if (!labels || !Array.isArray(labels)) return {text}; const entityColors = { @@ -21,7 +21,6 @@ function renderHighlightedText(text, labels) { let remainingText = text; labels.forEach(([entity, label], i) => { - console.log("Highlighting label:", label); const index = remainingText.indexOf(entity); if (index === -1) return; @@ -34,17 +33,40 @@ function renderHighlightedText(text, labels) { } chunks.push( - - - {matched} - - + setHoveredIndex(i)} + onMouseLeave={() => setHoveredIndex(null)} + display="inline-block" + bg={entityColors[label] || "gray.200"} + borderRadius="md" + px={1} + mx="0.5px" + > + {matched} + {hoveredIndex === i && ( + + {label} + + )} + ); remainingText = after; @@ -59,6 +81,7 @@ function renderHighlightedText(text, labels) { function MetadataDisplay({ metadata, loading, error }) { const [selectedTranscription, setSelectedTranscription] = useState("tradCN"); + const [hoveredIndex, setHoveredIndex] = useState(null); useEffect(() => { if (error) { @@ -105,18 +128,21 @@ function MetadataDisplay({ metadata, loading, error }) { {/* Transcription Text with Conditional Highlighting */} {selectedTranscription === "english" ? ( renderHighlightedText( metadata[selectedTranscription] || "", - metadata.nerLabels + metadata.nerLabels, + hoveredIndex, + setHoveredIndex ) ) : ( From c460d867c835579b2bd10e9685dbab729bc00367 Mon Sep 17 00:00:00 2001 From: JunHam Date: Thu, 31 Jul 2025 17:44:10 +0800 Subject: [PATCH 25/43] Added animation for hovering of ner labels --- src/components/Catalogue/metadataDisplay.jsx | 69 +++++++++++++------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index 1d5257b..fd0a68d 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -1,6 +1,5 @@ import { Box, Flex, Text } from "@chakra-ui/react"; -import { Tooltip } from "../ui/tooltip" -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import CentredSpinner from "../CentredSpinner"; import ToastWizard from "../toastWizard"; import TranscriptionToggle from "./transcriptionToggle"; @@ -46,26 +45,27 @@ function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { mx="0.5px" > {matched} - {hoveredIndex === i && ( - - {label} - - )} + + {label} + ); @@ -82,6 +82,7 @@ function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { function MetadataDisplay({ metadata, loading, error }) { const [selectedTranscription, setSelectedTranscription] = useState("tradCN"); const [hoveredIndex, setHoveredIndex] = useState(null); + const hoverTimeoutRef = useRef(null); useEffect(() => { if (error) { @@ -89,6 +90,25 @@ function MetadataDisplay({ metadata, loading, error }) { } }, [error]); + const handleMouseEnter = (index) => { + // Clear any existing timeout + if (hoverTimeoutRef.current) { + clearTimeout(hoverTimeoutRef.current); + } + // Set new timeout + hoverTimeoutRef.current = setTimeout(() => { + setHoveredIndex(index); + }, 500); + }; + + const handleMouseLeave = () => { + // Clear timeout if mouse leaves before delay completes + if (hoverTimeoutRef.current) { + clearTimeout(hoverTimeoutRef.current); + } + setHoveredIndex(null); + }; + if (loading) { return ; } @@ -104,7 +124,6 @@ function MetadataDisplay({ metadata, loading, error }) { const isOCRMetadata = metadata.english || metadata.tradCN || metadata.simplifiedCN; const isFigureMetadata = metadata.caption || metadata.figureIDs; - console.log(metadata.nerLabels) return ( @@ -188,4 +207,4 @@ function MetadataDisplay({ metadata, loading, error }) { ); } -export default MetadataDisplay; +export default MetadataDisplay; \ No newline at end of file From 749906bb84653d49e7698da252b8defb6e86624c Mon Sep 17 00:00:00 2001 From: JunHam Date: Fri, 1 Aug 2025 09:48:16 +0800 Subject: [PATCH 26/43] Added showing of hf images --- src/components/Catalogue/metadataDisplay.jsx | 118 +++++++++++++++---- 1 file changed, 93 insertions(+), 25 deletions(-) diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index fd0a68d..dc86f57 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -1,4 +1,4 @@ -import { Box, Flex, Text } from "@chakra-ui/react"; +import { Box, Flex, Text, Image, Skeleton } from "@chakra-ui/react"; import { useEffect, useRef, useState } from "react"; import CentredSpinner from "../CentredSpinner"; import ToastWizard from "../toastWizard"; @@ -13,7 +13,7 @@ function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { LOCATION: "green.200", DATE: "orange.200", GPE: "purple.200", - EVENT: "pink.200" + EVENT: "pink.200", }; const chunks = []; @@ -28,7 +28,11 @@ function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { const after = remainingText.slice(index + entity.length); if (before) { - chunks.push({before}); + chunks.push( + + {before} + + ); } chunks.push( @@ -73,7 +77,11 @@ function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { }); if (remainingText) { - chunks.push({remainingText}); + chunks.push( + + {remainingText} + + ); } return {chunks}; @@ -83,6 +91,7 @@ function MetadataDisplay({ metadata, loading, error }) { const [selectedTranscription, setSelectedTranscription] = useState("tradCN"); const [hoveredIndex, setHoveredIndex] = useState(null); const hoverTimeoutRef = useRef(null); + const [imageStatus, setImageStatus] = useState({}); useEffect(() => { if (error) { @@ -91,22 +100,39 @@ function MetadataDisplay({ metadata, loading, error }) { }, [error]); const handleMouseEnter = (index) => { - // Clear any existing timeout if (hoverTimeoutRef.current) { clearTimeout(hoverTimeoutRef.current); } - // Set new timeout hoverTimeoutRef.current = setTimeout(() => { setHoveredIndex(index); }, 500); }; - const handleMouseLeave = () => { - // Clear timeout if mouse leaves before delay completes - if (hoverTimeoutRef.current) { - clearTimeout(hoverTimeoutRef.current); + // Initialize image status when metadata changes + useEffect(() => { + if (metadata?.figureIDs) { + console.log("Initializing images for figure IDs:", metadata.figureIDs); + const initialStatus = {}; + metadata.figureIDs.forEach(id => { + initialStatus[id] = 'loading'; + console.log(`Image URL for ${id}:`, `${import.meta.env.VITE_BACKEND_URL}/cdn/people/${id}`); + }); + setImageStatus(initialStatus); } - setHoveredIndex(null); + }, [metadata]); + + const handleImageError = (id) => { + console.error(`Image load failed for ID: ${id}`, { + url: `${import.meta.env.VITE_BACKEND_URL}/cdn/people/${id}`, + status: imageStatus[id], + timestamp: new Date().toISOString() + }); + setImageStatus(prev => ({ ...prev, [id]: 'error' })); + }; + + const handleImageLoad = (id) => { + console.log(`Image loaded successfully: ${id}`); + setImageStatus(prev => ({ ...prev, [id]: 'loaded' })); }; if (loading) { @@ -135,7 +161,6 @@ function MetadataDisplay({ metadata, loading, error }) { flexDirection="column" maxH="100%" > - {/* Transcription Toggle */} {isOCRMetadata && ( <> @@ -145,7 +170,6 @@ function MetadataDisplay({ metadata, loading, error }) { /> - {/* Transcription Text with Conditional Highlighting */} )} - {/* Figure Metadata Section */} {isFigureMetadata && ( <> Caption: - {metadata.caption || "N/A"} + {metadata.caption || "N/A"} - - Figure IDs: - - {Array.isArray(metadata.figureIDs) && metadata.figureIDs.length > 0 ? ( - - {metadata.figureIDs.map((id) => ( - • {id} - ))} - + {Array.isArray(metadata.figureIDs) && + metadata.figureIDs.length > 0 ? ( + + {metadata.figureIDs.map((id) => { + console.log(`Rendering image box for ${id}`, { + status: imageStatus[id], + }); + return ( + + {imageStatus[id] === "loading" && ( + + )} + + {`Headshot handleImageLoad(id)} + onError={() => handleImageError(id)} + fallback={ + + } + fallbackStrategy="onError" + loading="lazy" + /> + + ); + })} + ) : ( N/A )} @@ -207,4 +275,4 @@ function MetadataDisplay({ metadata, loading, error }) { ); } -export default MetadataDisplay; \ No newline at end of file +export default MetadataDisplay; From 05a1fabffd4b68bb1d01113a6c6b0cdf7beb1b24 Mon Sep 17 00:00:00 2001 From: JunHam Date: Fri, 1 Aug 2025 09:59:19 +0800 Subject: [PATCH 27/43] Fixed bug for rendering english translation with NER highlights --- src/components/Catalogue/metadataDisplay.jsx | 21 ++++++-------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index dc86f57..d6bae50 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -84,7 +84,11 @@ function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { ); } - return {chunks}; + return ( + + {chunks} + + ); } function MetadataDisplay({ metadata, loading, error }) { @@ -108,19 +112,6 @@ function MetadataDisplay({ metadata, loading, error }) { }, 500); }; - // Initialize image status when metadata changes - useEffect(() => { - if (metadata?.figureIDs) { - console.log("Initializing images for figure IDs:", metadata.figureIDs); - const initialStatus = {}; - metadata.figureIDs.forEach(id => { - initialStatus[id] = 'loading'; - console.log(`Image URL for ${id}:`, `${import.meta.env.VITE_BACKEND_URL}/cdn/people/${id}`); - }); - setImageStatus(initialStatus); - } - }, [metadata]); - const handleImageError = (id) => { console.error(`Image load failed for ID: ${id}`, { url: `${import.meta.env.VITE_BACKEND_URL}/cdn/people/${id}`, @@ -251,7 +242,7 @@ function MetadataDisplay({ metadata, loading, error }) { /> } fallbackStrategy="onError" - loading="lazy" + loading="eager" /> ); From d46db18d8fb223843640d8c08e2b095bf93c6a3c Mon Sep 17 00:00:00 2001 From: JunHam Date: Fri, 1 Aug 2025 10:07:17 +0800 Subject: [PATCH 28/43] Fixed skeleton loading in metadatadisplay --- src/components/Catalogue/metadataDisplay.jsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index d6bae50..40ab2ad 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -210,12 +210,8 @@ function MetadataDisplay({ metadata, loading, error }) { height={100} cursor={"pointer"} > - {imageStatus[id] === "loading" && ( - + {(!imageStatus[id] || imageStatus[id] === "loading") && ( + )} Date: Fri, 1 Aug 2025 16:51:48 +0800 Subject: [PATCH 29/43] Fixed mobile view for catalogue item view --- .../Catalogue/catalogueItemView.jsx | 285 ++++++++++++++---- src/components/Catalogue/metadataDisplay.jsx | 25 +- 2 files changed, 237 insertions(+), 73 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 75ad556..85fca7b 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -8,6 +8,7 @@ import MetadataDisplay from "./metadataDisplay"; import ItemChat from "./itemChat"; import server, { JSONResponse } from "../../networking"; import ToastWizard from "../toastWizard"; +import CentredSpinner from "../CentredSpinner"; const MotionBox = motion.create(Box); const MotionChevron = motion.create(Box); @@ -36,16 +37,26 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag setError(null); try { - const res = await server.get(`/cdn/artefactMetadata/${currentItem.id}`); + const res = await server.get( + `/cdn/artefactMetadata/${currentItem.id}` + ); if (res.data instanceof JSONResponse) { if (res.data.isErrorStatus()) { console.error("Metadata error:", res.data.fullMessage()); if (res.data.userErrorType()) { - ToastWizard.standard("error", "Metadata error", res.data.message); + ToastWizard.standard( + "error", + "Metadata error", + res.data.message + ); setError(res.data.message); } else { - ToastWizard.standard("error", "Metadata error", "Unexpected server error."); + ToastWizard.standard( + "error", + "Metadata error", + "Unexpected server error." + ); setError("Unexpected server error."); } @@ -55,14 +66,21 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag } } else { console.warn("Unexpected metadata response:", res.data); - ToastWizard.standard("error", "Metadata error", "Malformed server response."); + ToastWizard.standard( + "error", + "Metadata error", + "Malformed server response." + ); setError("Malformed server response."); setMetadata(null); } - } catch (err) { console.error("Exception during metadata fetch:", err); - ToastWizard.standard("error", "Connection error", "Failed to fetch metadata."); + ToastWizard.standard( + "error", + "Connection error", + "Failed to fetch metadata." + ); setError("Failed to fetch metadata."); setMetadata(null); } finally { @@ -125,13 +143,52 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag } }, [isMobile, isOpen, hasRendered, currentIndex]); + useEffect(() => { + if (!isMobile || !isOpen || !scrollRef.current) return; + + let debounceTimeout = null; + let isScrolling = false; + + const handleScroll = () => { + // Set loading immediately when scroll starts + if (!isScrolling) { + isScrolling = true; + setLoading(true); + } + + if (debounceTimeout) clearTimeout(debounceTimeout); + + debounceTimeout = setTimeout(() => { + const scrollLeft = scrollRef.current.scrollLeft; + const newIndex = Math.round(scrollLeft / window.innerWidth); + + if (newIndex !== currentIndex && items[newIndex]) { + setDialogTitle(items[newIndex].title); + } + + // Reset scrolling flag after debounce + isScrolling = false; + }, 100); // Keep the debounce time reasonable (100ms) + }; + + const scrollEl = scrollRef.current; + scrollEl.addEventListener("scroll", handleScroll); + + return () => { + scrollEl.removeEventListener("scroll", handleScroll); + if (debounceTimeout) clearTimeout(debounceTimeout); + }; + }, [isMobile, isOpen, currentIndex, items, setDialogTitle]); + // Keyboard navigation useEffect(() => { const handleKeyDown = (e) => { if (!isOpen || isNavigating) return; - if (e.key === "Escape") {onClose()} - else if (e.key === "ArrowLeft" && currentIndex > 0) prevItem(); - else if (e.key === "ArrowRight" && currentIndex < items.length - 1) nextItem(); + if (e.key === "Escape") { + onClose(); + } else if (e.key === "ArrowLeft" && currentIndex > 0) prevItem(); + else if (e.key === "ArrowRight" && currentIndex < items.length - 1) + nextItem(); }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); @@ -160,17 +217,31 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag }; const chevronPosition = { - left: isMobile ? "10px" : isTablet ? "calc(5vw - 50px)" : "calc(10vw - 60px)", - right: isMobile ? "10px" : isTablet ? "calc(5vw - 50px)" : "calc(10vw - 60px)", + left: isMobile + ? "10px" + : isTablet + ? "calc(5vw - 50px)" + : "calc(10vw - 60px)", + right: isMobile + ? "10px" + : isTablet + ? "calc(5vw - 50px)" + : "calc(10vw - 60px)", top: "50%", }; const chevronSize = isMobile ? "30px" : "40px"; const desktopVariants = { - enter: (direction) => ({ x: direction > 0 ? "100%" : "-100%", opacity: 0 }), + enter: (direction) => ({ + x: direction > 0 ? "100%" : "-100%", + opacity: 0, + }), center: { x: 0, opacity: 1 }, - exit: (direction) => ({ x: direction > 0 ? "-100%" : "100%", opacity: 0 }), + exit: (direction) => ({ + x: direction > 0 ? "-100%" : "100%", + opacity: 0, + }), }; return ( @@ -218,10 +289,17 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag justifyContent="center" boxShadow="md" cursor="pointer" - _hover={{ transform: "translateY(-50%) scale(1.2)" }} + _hover={{ + transform: + "translateY(-50%) scale(1.2)", + }} onClick={prevItem} > - + )} @@ -245,10 +323,17 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag justifyContent="center" boxShadow="md" cursor="pointer" - _hover={{ transform: "translateY(-50%) scale(1.2)" }} + _hover={{ + transform: + "translateY(-50%) scale(1.2)", + }} onClick={nextItem} > - + )} @@ -293,8 +378,11 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag scrollSnapType="x mandatory" scrollBehavior="smooth" sx={{ - WebkitOverflowScrolling: "touch", - "&::-webkit-scrollbar": { display: "none" }, + WebkitOverflowScrolling: + "touch", + "&::-webkit-scrollbar": { + display: "none", + }, }} > {items.map((item) => ( @@ -306,7 +394,14 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag display="flex" flexDirection="column" > - + {item.title} - - - + + {item.title} - + - - + - {selectedSegment === "metadata" ? ( - - ) : selectedSegment === "chat" ? ( + ) : selectedSegment === + "chat" ? ( ) : null} @@ -364,21 +477,42 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag ))} {!hasRendered && ( - setHasRendered(true)} /> + + setHasRendered(true) + } + /> )} ) : ( // Desktop/Tablet: sliding animation - - + + { - if (isInitialRender) setIsInitialRender(false); + if (isInitialRender) + setIsInitialRender( + false + ); }} > - - + - - + {title} - - {selectedSegment === "metadata" ? ( - - ) : selectedSegment === "chat" ? ( + ) : selectedSegment === + "chat" ? ( ) : null} @@ -463,4 +626,4 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag ); }; -export default CatalogueItemView; \ No newline at end of file +export default CatalogueItemView; diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index 40ab2ad..db9b878 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -1,6 +1,5 @@ -import { Box, Flex, Text, Image, Skeleton } from "@chakra-ui/react"; +import { Box, Flex, Text, Image, Skeleton, Spinner, Center } from "@chakra-ui/react"; import { useEffect, useRef, useState } from "react"; -import CentredSpinner from "../CentredSpinner"; import ToastWizard from "../toastWizard"; import TranscriptionToggle from "./transcriptionToggle"; @@ -96,6 +95,15 @@ function MetadataDisplay({ metadata, loading, error }) { const [hoveredIndex, setHoveredIndex] = useState(null); const hoverTimeoutRef = useRef(null); const [imageStatus, setImageStatus] = useState({}); + const [color, setColor] = useState('primaryColour'); + + useEffect(() => { + const timer = setInterval(() => { + setColor(prevColor => prevColor === 'primaryColour' ? 'sccciColour' : 'primaryColour'); + }, 500); + + return () => clearInterval(timer); + }, []) useEffect(() => { if (error) { @@ -113,21 +121,17 @@ function MetadataDisplay({ metadata, loading, error }) { }; const handleImageError = (id) => { - console.error(`Image load failed for ID: ${id}`, { - url: `${import.meta.env.VITE_BACKEND_URL}/cdn/people/${id}`, - status: imageStatus[id], - timestamp: new Date().toISOString() - }); setImageStatus(prev => ({ ...prev, [id]: 'error' })); }; const handleImageLoad = (id) => { - console.log(`Image loaded successfully: ${id}`); setImageStatus(prev => ({ ...prev, [id]: 'loaded' })); }; if (loading) { - return ; + return ( +
+ ) } if (!metadata) { @@ -198,9 +202,6 @@ function MetadataDisplay({ metadata, loading, error }) { metadata.figureIDs.length > 0 ? ( {metadata.figureIDs.map((id) => { - console.log(`Rendering image box for ${id}`, { - status: imageStatus[id], - }); return ( Date: Fri, 1 Aug 2025 17:02:23 +0800 Subject: [PATCH 30/43] Changed toast messages --- .../Catalogue/catalogueItemView.jsx | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 85fca7b..0a827bf 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -37,25 +37,21 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag setError(null); try { - const res = await server.get( - `/cdn/artefactMetadata/${currentItem.id}` - ); + const res = await server.get(`/cdn/artefactMetadata/${currentItem.id}`); if (res.data instanceof JSONResponse) { if (res.data.isErrorStatus()) { - console.error("Metadata error:", res.data.fullMessage()); - if (res.data.userErrorType()) { ToastWizard.standard( "error", - "Metadata error", - res.data.message + "We couldn’t load the details", + res.data.message || "There was a problem retrieving this item’s information. Please try again later." ); setError(res.data.message); } else { ToastWizard.standard( "error", - "Metadata error", - "Unexpected server error." + "Something went wrong", + "We’re having trouble loading the information. Please try again in a moment." ); setError("Unexpected server error."); } @@ -65,21 +61,19 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag setMetadata(res.data.raw.data); } } else { - console.warn("Unexpected metadata response:", res.data); ToastWizard.standard( "error", - "Metadata error", - "Malformed server response." + "Couldn’t load item details", + "The information received was not in the expected format. Please try again later." ); setError("Malformed server response."); setMetadata(null); } } catch (err) { - console.error("Exception during metadata fetch:", err); ToastWizard.standard( "error", - "Connection error", - "Failed to fetch metadata." + "Connection issue", + "We couldn’t connect to the server. Please check your internet connection or try again later." ); setError("Failed to fetch metadata."); setMetadata(null); From 44c412c2cdf56df6cc3d43354d77973682fc2b16 Mon Sep 17 00:00:00 2001 From: JunHam Date: Fri, 1 Aug 2025 17:33:20 +0800 Subject: [PATCH 31/43] fixed public gallery and catalofye item view --- .../Catalogue/catalogueItemView.jsx | 25 ++++---------- src/pages/PublicGallery.jsx | 34 +++++++++++-------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 0a827bf..03d52fb 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -283,10 +283,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag justifyContent="center" boxShadow="md" cursor="pointer" - _hover={{ - transform: - "translateY(-50%) scale(1.2)", - }} + _hover={{ transform: "translateY(-50%) scale(1.2)" }} onClick={prevItem} > {items.map((item) => ( @@ -473,9 +464,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag {!hasRendered && ( - setHasRendered(true) - } + ref={() => setHasRendered(true)} /> )} @@ -516,9 +505,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag flexDirection="row" onAnimationComplete={() => { if (isInitialRender) - setIsInitialRender( - false - ); + setIsInitialRender(false); }} > { try { const res = await server.get("/cdn/catalogue"); const data = res.data; if (!(data instanceof JSONResponse) || data.isErrorStatus()) { - ToastWizard.standard("error", "An error occurred", "Please try again later"); + ToastWizard.standard( + "error", + "Couldn’t load gallery", + "Please try again later." + ); return; } - const { categories = {}, books = [] } = data.raw.data || {}; + const { categories = {} } = data.raw.data || {}; setCategories(categories); - setBooks(books.map(book => ({ - ...book, - artefacts: book.mmArtefacts || [], - }))); } catch (err) { - ToastWizard.standard("error", "An error occurred", "Please try again later"); + ToastWizard.standard( + "error", + "Connection issue", + "We couldn’t load the gallery. Please check your connection." + ); } finally { setLoading(false); } - } + }; useEffect(() => { fetchCatalogue(); }, []); - const hasCategories = Object.keys(categories).length > 0; + const hasCategories = + categories && Object.values(categories).some(cat => Array.isArray(cat) && cat.length > 0); if (loading) { return ; @@ -46,7 +52,7 @@ function PublicGallery() { return (
- No Items Uploaded + No items uploaded yet.
); @@ -60,7 +66,7 @@ function PublicGallery() {
))} From c8211c20a7b9c9fb4c14e640a10df1c38eea56e4 Mon Sep 17 00:00:00 2001 From: JunHam Date: Fri, 1 Aug 2025 17:52:41 +0800 Subject: [PATCH 32/43] Added comments for code, removed console logs. --- src/components/Catalogue/metadataDisplay.jsx | 134 +++++++++---------- src/pages/ArtefactEditor.jsx | 1 - src/pages/GroupView.jsx | 2 - 3 files changed, 64 insertions(+), 73 deletions(-) diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index db9b878..ffd0b07 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -3,9 +3,11 @@ import { useEffect, useRef, useState } from "react"; import ToastWizard from "../toastWizard"; import TranscriptionToggle from "./transcriptionToggle"; +// Helper function to render text with labeled entities highlighted function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { if (!labels || !Array.isArray(labels)) return {text}; + // Define background colors for each entity type const entityColors = { PERSON: "yellow.200", ORGANIZATION: "blue.200", @@ -18,6 +20,7 @@ function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { const chunks = []; let remainingText = text; + // Iterate through each labeled entity and highlight it labels.forEach(([entity, label], i) => { const index = remainingText.indexOf(entity); if (index === -1) return; @@ -27,13 +30,10 @@ function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { const after = remainingText.slice(index + entity.length); if (before) { - chunks.push( - - {before} - - ); + chunks.push({before}); } + // Render the matched entity with a tooltip for the label chunks.push( - {remainingText} - - ); + chunks.push({remainingText}); } - return ( - - {chunks} - - ); + return {chunks}; } function MetadataDisplay({ metadata, loading, error }) { @@ -97,20 +89,23 @@ function MetadataDisplay({ metadata, loading, error }) { const [imageStatus, setImageStatus] = useState({}); const [color, setColor] = useState('primaryColour'); + // Toggle spinner color for visual interest while loading useEffect(() => { const timer = setInterval(() => { setColor(prevColor => prevColor === 'primaryColour' ? 'sccciColour' : 'primaryColour'); }, 500); return () => clearInterval(timer); - }, []) + }, []); + // Show toast notification when error changes useEffect(() => { if (error) { ToastWizard.standard("error", "Metadata Error", error); } }, [error]); + // Delay tooltip hover effect for entity labels const handleMouseEnter = (index) => { if (hoverTimeoutRef.current) { clearTimeout(hoverTimeoutRef.current); @@ -120,6 +115,7 @@ function MetadataDisplay({ metadata, loading, error }) { }, 500); }; + // Track image load status to show fallback skeletons const handleImageError = (id) => { setImageStatus(prev => ({ ...prev, [id]: 'error' })); }; @@ -128,12 +124,16 @@ function MetadataDisplay({ metadata, loading, error }) { setImageStatus(prev => ({ ...prev, [id]: 'loaded' })); }; + // Show loading spinner while data is being fetched if (loading) { return ( -
- ) +
+ +
+ ); } + // Display a fallback message if no metadata is available if (!metadata) { return ( @@ -142,8 +142,8 @@ function MetadataDisplay({ metadata, loading, error }) { ); } - const isOCRMetadata = - metadata.english || metadata.tradCN || metadata.simplifiedCN; + // Determine which metadata types are available + const isOCRMetadata = metadata.english || metadata.tradCN || metadata.simplifiedCN; const isFigureMetadata = metadata.caption || metadata.figureIDs; return ( @@ -158,6 +158,7 @@ function MetadataDisplay({ metadata, loading, error }) { > {isOCRMetadata && ( <> + {/* Transcription language switcher */} + {/* OCR text display with optional entity highlighting */} - - Caption: - + {/* Figure caption */} + Caption: {metadata.caption || "N/A"} - {Array.isArray(metadata.figureIDs) && - metadata.figureIDs.length > 0 ? ( + {/* Figure headshots (if available) */} + {Array.isArray(metadata.figureIDs) && metadata.figureIDs.length > 0 ? ( - {metadata.figureIDs.map((id) => { - return ( - - {(!imageStatus[id] || imageStatus[id] === "loading") && ( - - )} + {metadata.figureIDs.map((id) => ( + + {(!imageStatus[id] || imageStatus[id] === "loading") && ( + + )} - {`Headshot handleImageLoad(id)} - onError={() => handleImageError(id)} - fallback={ - - } - fallbackStrategy="onError" - loading="eager" - /> - - ); - })} + {`Headshot handleImageLoad(id)} + onError={() => handleImageError(id)} + fallback={ + + } + fallbackStrategy="onError" + loading="eager" + /> + + ))} ) : ( N/A )} + {/* Additional figure metadata, if present */} {metadata.addInfo && ( <> - - Additional Info: - + Additional Info: {metadata.addInfo} )} diff --git a/src/pages/ArtefactEditor.jsx b/src/pages/ArtefactEditor.jsx index e78b175..5a297fe 100644 --- a/src/pages/ArtefactEditor.jsx +++ b/src/pages/ArtefactEditor.jsx @@ -3,7 +3,6 @@ import { useParams } from 'react-router-dom'; function ArtefactEditor() { const { artID } = useParams(); - console.log(artID) return ( ArtefactEditor for {artID} ); diff --git a/src/pages/GroupView.jsx b/src/pages/GroupView.jsx index ed8b2da..575240f 100644 --- a/src/pages/GroupView.jsx +++ b/src/pages/GroupView.jsx @@ -2,8 +2,6 @@ import { useParams } from 'react-router-dom'; function GroupView() { const { artID, colID } = useParams(); - console.log(artID) - console.log(colID) return
GroupView for collection {colID}, {artID}
; } From 2159e43b1be5e1d09c6603464418cd1e71b56b8b Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 9 Aug 2025 19:15:27 +0800 Subject: [PATCH 33/43] Removed uneccsary states for cardItem and cardComponent --- src/components/Catalogue/cardComponent.jsx | 4 ++-- src/components/Catalogue/cardItem.jsx | 10 ---------- src/components/Catalogue/catalogueItemView.jsx | 1 - 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/components/Catalogue/cardComponent.jsx b/src/components/Catalogue/cardComponent.jsx index aa3b482..8d61273 100644 --- a/src/components/Catalogue/cardComponent.jsx +++ b/src/components/Catalogue/cardComponent.jsx @@ -1,13 +1,13 @@ import { Card, Image, Text, Skeleton } from "@chakra-ui/react"; import { useState } from "react"; -const CardComponent = ({ imageSrc, itemTitle, itemDescription, isSelected, expanded }) => { +const CardComponent = ({ imageSrc, itemTitle, itemDescription, expanded }) => { const [isImageLoaded, setIsImageLoaded] = useState(false); const isLoading = !isImageLoaded; return ( - +
@@ -138,7 +129,6 @@ const CardItem = ({ itemTitle, itemDescription, imageSrc, expandable = true, des imageSrc={imageSrc} itemTitle={itemTitle} itemDescription={itemDescription} - isSelected={isSelected} expanded={true} /> diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 03d52fb..ba9c317 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -8,7 +8,6 @@ import MetadataDisplay from "./metadataDisplay"; import ItemChat from "./itemChat"; import server, { JSONResponse } from "../../networking"; import ToastWizard from "../toastWizard"; -import CentredSpinner from "../CentredSpinner"; const MotionBox = motion.create(Box); const MotionChevron = motion.create(Box); From f47ba426c11f3c72b71f612492f6d8dc4cc5afd6 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 9 Aug 2025 20:00:11 +0800 Subject: [PATCH 34/43] Removed redundant hasRendered state in catalogueItemView --- src/components/Catalogue/catalogueItemView.jsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index ba9c317..dfe9b31 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -16,7 +16,6 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag const [isNavigating, setIsNavigating] = useState(false); const [direction, setDirection] = useState(0); const [isInitialRender, setIsInitialRender] = useState(true); - const [hasRendered, setHasRendered] = useState(false); const isMobile = useBreakpointValue({ base: true, md: false }); const isTablet = useBreakpointValue({ base: false, md: true, lg: false }); const [selectedSegment, setSelectedSegment] = useState("metadata"); @@ -120,7 +119,6 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag // Reset flags when modal closes useEffect(() => { if (!isOpen) { - setHasRendered(false); setDirection(0); setIsInitialRender(true); } @@ -128,13 +126,13 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag // Scroll to correct position on mobile when rendered useEffect(() => { - if (isMobile && isOpen && hasRendered && scrollRef.current) { + if (isMobile && isOpen && scrollRef.current) { scrollRef.current.scrollTo({ left: currentIndex * window.innerWidth, behavior: "instant", }); } - }, [isMobile, isOpen, hasRendered, currentIndex]); + }, [isMobile, isOpen, currentIndex]); useEffect(() => { if (!isMobile || !isOpen || !scrollRef.current) return; @@ -460,12 +458,6 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag
))} - {!hasRendered && ( - setHasRendered(true)} - /> - )} ) : ( // Desktop/Tablet: sliding animation From 57649dc2388fbc5ea2f92900d4bf7d3fe1fdadcc Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 9 Aug 2025 20:12:30 +0800 Subject: [PATCH 35/43] Fixed fetchMetadata() in catalogueItemView --- .../Catalogue/catalogueItemView.jsx | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index dfe9b31..241cf69 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -35,45 +35,47 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag setError(null); try { - const res = await server.get(`/cdn/artefactMetadata/${currentItem.id}`); - if (res.data instanceof JSONResponse) { - if (res.data.isErrorStatus()) { - if (res.data.userErrorType()) { - ToastWizard.standard( - "error", - "We couldn’t load the details", - res.data.message || "There was a problem retrieving this item’s information. Please try again later." - ); - setError(res.data.message); - } else { - ToastWizard.standard( - "error", - "Something went wrong", - "We’re having trouble loading the information. Please try again in a moment." - ); - setError("Unexpected server error."); - } - - setMetadata(null); + const response = await server.get(`/cdn/artefactMetadata/${currentItem.id}`); + + if (response.data instanceof JSONResponse) { + if (response.data.isErrorStatus()) { + const errObject = { + response: { + data: response.data + } + }; + throw errObject; + } + + setMetadata(response.data.raw.data); + } else { + throw new Error("Unexpected response format"); + } + } catch (err) { + if (err.response && err.response.data instanceof JSONResponse) { + if (err.response.data.userErrorType()) { + ToastWizard.standard( + "error", + "We could not load the details", + err.response.data.message || "There was a problem retrieving this item information. Please try again later." + ); + setError(err.response.data.message); } else { - setMetadata(res.data.raw.data); + ToastWizard.standard( + "error", + "Something went wrong", + "We are having trouble loading the information. Please try again in a moment." + ); + setError("Unexpected server error."); } } else { ToastWizard.standard( "error", - "Couldn’t load item details", - "The information received was not in the expected format. Please try again later." + "Connection issue", + "We could not connect to the server. Please check your internet connection or try again later." ); - setError("Malformed server response."); - setMetadata(null); + setError("Failed to fetch metadata."); } - } catch (err) { - ToastWizard.standard( - "error", - "Connection issue", - "We couldn’t connect to the server. Please check your internet connection or try again later." - ); - setError("Failed to fetch metadata."); setMetadata(null); } finally { setLoading(false); @@ -81,8 +83,10 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag }; useEffect(() => { - fetchMetadata(); - }, [currentItem?.id]); + if (isOpen) { + fetchMetadata(); + } + }, [currentItem?.id, isOpen]); const prevItem = () => { if (!isNavigating && currentIndex > 0) { From 46f4291023d535617067eab27dd134e95e81110a Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 9 Aug 2025 20:27:15 +0800 Subject: [PATCH 36/43] Shifted fetchMetadata into MetadataDisplay from CatalogueItemView --- .../Catalogue/catalogueItemView.jsx | 78 +------------------ src/components/Catalogue/metadataDisplay.jsx | 73 ++++++++++++++++- 2 files changed, 75 insertions(+), 76 deletions(-) diff --git a/src/components/Catalogue/catalogueItemView.jsx b/src/components/Catalogue/catalogueItemView.jsx index 241cf69..60f08ab 100644 --- a/src/components/Catalogue/catalogueItemView.jsx +++ b/src/components/Catalogue/catalogueItemView.jsx @@ -6,8 +6,6 @@ import ItemViewToggle from "./itemViewToggle"; import ItemViewMenu from "./itemViewMenu"; import MetadataDisplay from "./metadataDisplay"; import ItemChat from "./itemChat"; -import server, { JSONResponse } from "../../networking"; -import ToastWizard from "../toastWizard"; const MotionBox = motion.create(Box); const MotionChevron = motion.create(Box); @@ -21,72 +19,7 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag const [selectedSegment, setSelectedSegment] = useState("metadata"); const scrollRef = useRef(null); const modalRef = useRef(null); - const [metadata, setMetadata] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const currentIndex = items.findIndex((item) => item.title === title); - const currentItem = items[currentIndex]; - - const fetchMetadata = async () => { - if (!currentItem?.id) return; - - setLoading(true); - setError(null); - - try { - const response = await server.get(`/cdn/artefactMetadata/${currentItem.id}`); - - if (response.data instanceof JSONResponse) { - if (response.data.isErrorStatus()) { - const errObject = { - response: { - data: response.data - } - }; - throw errObject; - } - - setMetadata(response.data.raw.data); - } else { - throw new Error("Unexpected response format"); - } - } catch (err) { - if (err.response && err.response.data instanceof JSONResponse) { - if (err.response.data.userErrorType()) { - ToastWizard.standard( - "error", - "We could not load the details", - err.response.data.message || "There was a problem retrieving this item information. Please try again later." - ); - setError(err.response.data.message); - } else { - ToastWizard.standard( - "error", - "Something went wrong", - "We are having trouble loading the information. Please try again in a moment." - ); - setError("Unexpected server error."); - } - } else { - ToastWizard.standard( - "error", - "Connection issue", - "We could not connect to the server. Please check your internet connection or try again later." - ); - setError("Failed to fetch metadata."); - } - setMetadata(null); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - if (isOpen) { - fetchMetadata(); - } - }, [currentItem?.id, isOpen]); const prevItem = () => { if (!isNavigating && currentIndex > 0) { @@ -148,7 +81,6 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag // Set loading immediately when scroll starts if (!isScrolling) { isScrolling = true; - setLoading(true); } if (debounceTimeout) clearTimeout(debounceTimeout); @@ -450,9 +382,8 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag {selectedSegment === "metadata" ? ( ) : selectedSegment === "chat" ? ( @@ -578,9 +509,8 @@ const CatalogueItemView = ({ isOpen, onClose, title, items, setDialogTitle, imag {selectedSegment === "metadata" ? ( ) : selectedSegment === "chat" ? ( diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index ffd0b07..f5d237b 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -2,6 +2,7 @@ import { Box, Flex, Text, Image, Skeleton, Spinner, Center } from "@chakra-ui/re import { useEffect, useRef, useState } from "react"; import ToastWizard from "../toastWizard"; import TranscriptionToggle from "./transcriptionToggle"; +import server, { JSONResponse } from "../../networking"; // Helper function to render text with labeled entities highlighted function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { @@ -82,12 +83,80 @@ function renderHighlightedText(text, labels, hoveredIndex, setHoveredIndex) { return {chunks}; } -function MetadataDisplay({ metadata, loading, error }) { +function MetadataDisplay({ currentItem, isOpen }) { const [selectedTranscription, setSelectedTranscription] = useState("tradCN"); const [hoveredIndex, setHoveredIndex] = useState(null); const hoverTimeoutRef = useRef(null); const [imageStatus, setImageStatus] = useState({}); const [color, setColor] = useState('primaryColour'); + + // Metadata fetching states + const [metadata, setMetadata] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + // Metadata fetching function + const fetchMetadata = async () => { + if (!currentItem?.id) return; + + setLoading(true); + setError(null); + + try { + const response = await server.get(`/cdn/artefactMetadata/${currentItem.id}`); + + if (response.data instanceof JSONResponse) { + if (response.data.isErrorStatus()) { + const errObject = { + response: { + data: response.data + } + }; + throw errObject; + } + + // Success case + setMetadata(response.data.raw.data); + } else { + throw new Error("Unexpected response format"); + } + } catch (err) { + if (err.response && err.response.data instanceof JSONResponse) { + if (err.response.data.userErrorType()) { + ToastWizard.standard( + "error", + "We could not load the details", + err.response.data.message || "There was a problem retrieving this item information. Please try again later." + ); + setError(err.response.data.message); + } else { + ToastWizard.standard( + "error", + "Something went wrong", + "We are having trouble loading the information. Please try again in a moment." + ); + setError("Unexpected server error."); + } + } else { + ToastWizard.standard( + "error", + "Connection issue", + "We could not connect to the server. Please check your internet connection or try again later." + ); + setError("Failed to fetch metadata."); + } + setMetadata(null); + } finally { + setLoading(false); + } + }; + + // Fetch metadata when currentItem changes or modal opens + useEffect(() => { + if (isOpen) { + fetchMetadata(); + } + }, [currentItem?.id, isOpen]); // Toggle spinner color for visual interest while loading useEffect(() => { @@ -257,4 +326,4 @@ function MetadataDisplay({ metadata, loading, error }) { ); } -export default MetadataDisplay; +export default MetadataDisplay; \ No newline at end of file From 36ff221f3e5a42edc10dfa0285b31c6909da20ce Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 9 Aug 2025 20:34:05 +0800 Subject: [PATCH 37/43] Used SkeletonCircle instead of Skeleton for metadata display --- src/components/Catalogue/metadataDisplay.jsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index f5d237b..43687e7 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -1,4 +1,4 @@ -import { Box, Flex, Text, Image, Skeleton, Spinner, Center } from "@chakra-ui/react"; +import { Box, Flex, Text, Image, SkeletonCircle, Spinner, Center } from "@chakra-ui/react"; import { useEffect, useRef, useState } from "react"; import ToastWizard from "../toastWizard"; import TranscriptionToggle from "./transcriptionToggle"; @@ -213,7 +213,6 @@ function MetadataDisplay({ currentItem, isOpen }) { // Determine which metadata types are available const isOCRMetadata = metadata.english || metadata.tradCN || metadata.simplifiedCN; - const isFigureMetadata = metadata.caption || metadata.figureIDs; return ( )} - {isFigureMetadata && ( + {!isOCRMetadata && ( <> {/* Figure caption */} Caption: @@ -281,7 +280,7 @@ function MetadataDisplay({ currentItem, isOpen }) { cursor="pointer" > {(!imageStatus[id] || imageStatus[id] === "loading") && ( - + )} handleImageLoad(id)} onError={() => handleImageError(id)} fallback={ - + } fallbackStrategy="onError" loading="eager" From 80eec6efbb8c2f5458c39252e2fa503fbd746272 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 9 Aug 2025 20:37:43 +0800 Subject: [PATCH 38/43] use a more suitable font size for card component description --- src/components/Catalogue/cardComponent.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Catalogue/cardComponent.jsx b/src/components/Catalogue/cardComponent.jsx index 8d61273..9a67ce8 100644 --- a/src/components/Catalogue/cardComponent.jsx +++ b/src/components/Catalogue/cardComponent.jsx @@ -24,7 +24,7 @@ const CardComponent = ({ imageSrc, itemTitle, itemDescription, expanded }) => { {expanded && ( - {itemDescription} + {itemDescription} )} From 167e260ed8ff13c28e11d406e01d493019bdc953 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 9 Aug 2025 20:52:08 +0800 Subject: [PATCH 39/43] Removed redundant image loading and error checks in metadata display --- src/components/Catalogue/metadataDisplay.jsx | 22 ++------------------ 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index 43687e7..fc43809 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -87,7 +87,6 @@ function MetadataDisplay({ currentItem, isOpen }) { const [selectedTranscription, setSelectedTranscription] = useState("tradCN"); const [hoveredIndex, setHoveredIndex] = useState(null); const hoverTimeoutRef = useRef(null); - const [imageStatus, setImageStatus] = useState({}); const [color, setColor] = useState('primaryColour'); // Metadata fetching states @@ -184,15 +183,6 @@ function MetadataDisplay({ currentItem, isOpen }) { }, 500); }; - // Track image load status to show fallback skeletons - const handleImageError = (id) => { - setImageStatus(prev => ({ ...prev, [id]: 'error' })); - }; - - const handleImageLoad = (id) => { - setImageStatus(prev => ({ ...prev, [id]: 'loaded' })); - }; - // Show loading spinner while data is being fetched if (loading) { return ( @@ -279,12 +269,8 @@ function MetadataDisplay({ currentItem, isOpen }) { height={100} cursor="pointer" > - {(!imageStatus[id] || imageStatus[id] === "loading") && ( - - )} - {`Headshot handleImageLoad(id)} - onError={() => handleImageError(id)} fallback={ - + } - fallbackStrategy="onError" loading="eager" /> From 1adfa9e6aaf19dc1838b428b25fd5016df2ed986 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 9 Aug 2025 20:59:24 +0800 Subject: [PATCH 40/43] Added loaded state in catalogue to check for session validity before fetching catalogue data --- src/pages/Catalogue.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/Catalogue.jsx b/src/pages/Catalogue.jsx index ee77ab1..fcd50fb 100644 --- a/src/pages/Catalogue.jsx +++ b/src/pages/Catalogue.jsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { useSelector } from "react-redux"; import { Box, Text, Center } from "@chakra-ui/react"; import { AnimatePresence, motion } from "framer-motion"; import MMSection from "../components/Catalogue/mmSection.jsx"; @@ -13,6 +14,7 @@ function Catalogue() { const [books, setBooks] = useState([]); const [categories, setCategories] = useState({}); const [loading, setLoading] = useState(true); + const { loaded } = useSelector(state => state.auth); async function fetchCatalogue() { try { @@ -38,8 +40,10 @@ function Catalogue() { } useEffect(() => { - fetchCatalogue(); - }, []); + if (loaded) { + fetchCatalogue(); + } + }, [loaded]); const handleItemClick = (title, source) => { if (source === "MM") { @@ -88,7 +92,6 @@ function Catalogue() { {hasBooks && ( handleItemClick(title, "MM")} - selectedTitle={selectedMMTitle} books={books} /> )} From 8b7bc41d6a0b11f38e562d5194ae387f09296a1d Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 9 Aug 2025 21:01:25 +0800 Subject: [PATCH 41/43] Refined fetchCatalogue function --- src/pages/Catalogue.jsx | 56 ++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/src/pages/Catalogue.jsx b/src/pages/Catalogue.jsx index fcd50fb..3dd990c 100644 --- a/src/pages/Catalogue.jsx +++ b/src/pages/Catalogue.jsx @@ -18,22 +18,50 @@ function Catalogue() { async function fetchCatalogue() { try { - const res = await server.get("/cdn/catalogue"); - const data = res.data; - - if (!(data instanceof JSONResponse) || data.isErrorStatus()) { - ToastWizard.standard("error", "An error occurred", "Please try again later"); - return; + const response = await server.get("/cdn/catalogue"); + + if (response.data instanceof JSONResponse) { + if (response.data.isErrorStatus()) { + const errObject = { + response: { + data: response.data + } + }; + throw errObject; + } + + // Success case + const { categories = {}, books = [] } = response.data.raw.data || {}; + setCategories(categories); + setBooks(books.map(book => ({ + ...book, + artefacts: book.mmArtefacts || [], + }))); + } else { + throw new Error("Unexpected response format"); } - - const { categories = {}, books = [] } = data.raw.data || {}; - setCategories(categories); - setBooks(books.map(book => ({ - ...book, - artefacts: book.mmArtefacts || [], - }))); } catch (err) { - ToastWizard.standard("error", "An error occurred", "Please try again later"); + if (err.response && err.response.data instanceof JSONResponse) { + if (err.response.data.userErrorType()) { + ToastWizard.standard( + "error", + "Could not load catalogue", + err.response.data.message || "There was a problem loading the catalogue. Please try again later." + ); + } else { + ToastWizard.standard( + "error", + "Something went wrong", + "We are having trouble loading the catalogue. Please try again in a moment." + ); + } + } else { + ToastWizard.standard( + "error", + "Connection issue", + "We could not connect to the server. Please check your internet connection or try again later." + ); + } } finally { setLoading(false); } From 7fac10af129c8983d118183232ea92d5770396b2 Mon Sep 17 00:00:00 2001 From: JunHam Date: Sat, 9 Aug 2025 21:25:31 +0800 Subject: [PATCH 42/43] Removed unused image preloading for mmSection and Section --- src/components/Catalogue/mmSection.jsx | 7 ------- src/components/Catalogue/section.jsx | 5 ----- 2 files changed, 12 deletions(-) diff --git a/src/components/Catalogue/mmSection.jsx b/src/components/Catalogue/mmSection.jsx index 53a7005..392912c 100644 --- a/src/components/Catalogue/mmSection.jsx +++ b/src/components/Catalogue/mmSection.jsx @@ -105,13 +105,6 @@ const MMSection = ({ books = [], onItemClick }) => { borderRadius="md" onClick={() => handleClick(book.title)} > - {/* Hidden Image for preloading */} - - { borderRadius="md" onClick={() => handleCardClick(item.title)} > - Date: Sat, 9 Aug 2025 23:10:20 +0800 Subject: [PATCH 43/43] Removed error state in metadataDisplay --- src/components/Catalogue/metadataDisplay.jsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/components/Catalogue/metadataDisplay.jsx b/src/components/Catalogue/metadataDisplay.jsx index fc43809..9e1972a 100644 --- a/src/components/Catalogue/metadataDisplay.jsx +++ b/src/components/Catalogue/metadataDisplay.jsx @@ -92,14 +92,12 @@ function MetadataDisplay({ currentItem, isOpen }) { // Metadata fetching states const [metadata, setMetadata] = useState(null); const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); // Metadata fetching function const fetchMetadata = async () => { if (!currentItem?.id) return; setLoading(true); - setError(null); try { const response = await server.get(`/cdn/artefactMetadata/${currentItem.id}`); @@ -127,14 +125,12 @@ function MetadataDisplay({ currentItem, isOpen }) { "We could not load the details", err.response.data.message || "There was a problem retrieving this item information. Please try again later." ); - setError(err.response.data.message); } else { ToastWizard.standard( "error", "Something went wrong", "We are having trouble loading the information. Please try again in a moment." ); - setError("Unexpected server error."); } } else { ToastWizard.standard( @@ -142,7 +138,6 @@ function MetadataDisplay({ currentItem, isOpen }) { "Connection issue", "We could not connect to the server. Please check your internet connection or try again later." ); - setError("Failed to fetch metadata."); } setMetadata(null); } finally { @@ -166,13 +161,6 @@ function MetadataDisplay({ currentItem, isOpen }) { return () => clearInterval(timer); }, []); - // Show toast notification when error changes - useEffect(() => { - if (error) { - ToastWizard.standard("error", "Metadata Error", error); - } - }, [error]); - // Delay tooltip hover effect for entity labels const handleMouseEnter = (index) => { if (hoverTimeoutRef.current) {