small updates, for some reason it thinks every file is modified

This commit is contained in:
2026-02-19 04:21:31 -06:00
parent 1b394a57a9
commit e829f6b405
32 changed files with 6142 additions and 6115 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ func main() {
// Configure CORS middleware // Configure CORS middleware
r.Use(cors.New(cors.Config{ r.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:3000", "http://localhost:5173"}, AllowOrigins: []string{"http://localhost:3000", "http://localhost:5173", "http://192.168.1.130:5173"},
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"},
ExposeHeaders: []string{"Content-Length"}, ExposeHeaders: []string{"Content-Length"},
+1 -1
View File
@@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link <link
href="https://fonts.googleapis.com/css2?family=Red+Hat+Mono:ital,wght@0,300..700;1,300..700&family=Red+Hat+Text:ital,wght@0,300..700;1,300..700&display=swap" href="https://fonts.googleapis.com/css2?family=Red+Hat+Display:ital,wght@0,300..900;1,300..900&family=Red+Hat+Mono:ital,wght@0,300..700;1,300..700&family=Red+Hat+Text:ital,wght@0,300..700;1,300..700&display=swap"
rel="stylesheet"> rel="stylesheet">
<title>Raster</title> <title>Raster</title>
</head> </head>
+10
View File
@@ -75,6 +75,7 @@
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
@@ -2115,6 +2116,7 @@
"integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==", "integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
@@ -2125,6 +2127,7 @@
"integrity": "sha512-xG7xaBMJCpcK0RpN8jDbAACQo54ycO6h4dSSmgv8+fu6ZIAdANkx/WsawASUjVXYfy+J9AbUpRMNNEsXCDfDBQ==", "integrity": "sha512-xG7xaBMJCpcK0RpN8jDbAACQo54ycO6h4dSSmgv8+fu6ZIAdANkx/WsawASUjVXYfy+J9AbUpRMNNEsXCDfDBQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
@@ -2156,6 +2159,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -2263,6 +2267,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001735", "caniuse-lite": "^1.0.30001735",
"electron-to-chromium": "^1.5.204", "electron-to-chromium": "^1.5.204",
@@ -2528,6 +2533,7 @@
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -3492,6 +3498,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -3552,6 +3559,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -3561,6 +3569,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.26.0" "scheduler": "^0.26.0"
}, },
@@ -3993,6 +4002,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz",
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
+2 -2
View File
@@ -21,7 +21,7 @@ export default function CreateRootUser() {
}) })
const data = await response.json() const data = await response.json()
if (data.error) { if (data.error) {
showError(data.error) showError("Error creating root user: " + data.error)
} else { } else {
const rootResponse = await fetch(`${getServerUrl()}/api/user/setRootUser`, { const rootResponse = await fetch(`${getServerUrl()}/api/user/setRootUser`, {
method: 'POST', method: 'POST',
@@ -34,7 +34,7 @@ export default function CreateRootUser() {
}) })
const rootData = await rootResponse.json() const rootData = await rootResponse.json()
if (rootData.error) { if (rootData.error) {
showError(rootData.error) showError("Error setting root user: " + rootData.error)
} else { } else {
navigate('/gallery') navigate('/gallery')
showSuccess('Root user created successfully') showSuccess('Root user created successfully')
+1 -1
View File
@@ -14,7 +14,7 @@ export default function Login() {
navigate('/gallery') navigate('/gallery')
}) })
.catch((error) => { .catch((error) => {
showError(error.message) showError("Error logging in: " + error.message)
}) })
} }
return ( return (
+2 -2
View File
@@ -10,11 +10,11 @@ export default function NavBar({ path }) {
<div className="flex flex-row items-center justify-between h-[10vh] w-full px-6 py-2 border-b border-[#2B2B2B] shrink-0"> <div className="flex flex-row items-center justify-between h-[10vh] w-full px-6 py-2 border-b border-[#2B2B2B] shrink-0">
<div className="flex flex-row items-center justify-start gap-2"> <div className="flex flex-row items-center justify-start gap-2">
{path.map((item, index) => ( {path.map((item, index) => (
<div className="flex flex-row items-center justify-start gap-2 red-hat-mono"> <div className="flex flex-row items-center justify-start gap-2 text-2xl red-hat-display">
<Link to={`/${path.slice(0, index + 1).join('/')}`} key={item} className={`text-white ${index === path.length - 1 ? 'font-bold' : ''}`}> <Link to={`/${path.slice(0, index + 1).join('/')}`} key={item} className={`text-white ${index === path.length - 1 ? 'font-bold' : ''}`}>
{decodeURIComponent(item)} {decodeURIComponent(item)}
</Link> </Link>
{index !== path.length - 1 && <p className="text-white red-hat-mono">/</p>} {index !== path.length - 1 && <p className="text-white text-xl red-hat-display">/</p>}
</div> </div>
))} ))}
</div> </div>
@@ -23,7 +23,7 @@ export default function AlbumCreateModal({ open, onOpenChange, trigger, parentId
}) })
const data = await response.json() const data = await response.json()
if (data.error) { if (data.error) {
showError(data.error) showError("Error creating album: " + data.error)
} else { } else {
onOpenChange(false) onOpenChange(false)
} }
@@ -28,7 +28,7 @@ export default function AlbumEditModal({ open, onOpenChange, trigger, id, startT
}) })
const data = await response.json() const data = await response.json()
if (data.error) { if (data.error) {
showError(data.error) showError("Error editing album: " + data.error)
} else { } else {
onOpenChange(false) onOpenChange(false)
} }
@@ -54,7 +54,7 @@ export default function AlbumList({ currentAlbumName }) {
return ( return (
<div className="flex flex-col items-center justify-start h-min w-full bg-[#141414]"> <div className="flex flex-col items-center justify-start h-min w-full bg-[#141414]">
<div className="flex flex-row items-center justify-between gap-2 w-full px-6 py-4"> <div className="flex flex-row items-center justify-between gap-2 w-full px-6 py-4">
<h1 className="text-xl font-bold text-white red-hat-mono">Albums</h1> <h1 className="text-xl font-bold text-white red-hat-display">Albums</h1>
<PlusIcon className="w-6 h-6 cursor-pointer" color="white" onClick={() => setOpen(true)} /> <PlusIcon className="w-6 h-6 cursor-pointer" color="white" onClick={() => setOpen(true)} />
</div> </div>
<div className="flex flex-row items-center justify-start gap-2 w-full px-6 flex-wrap"> <div className="flex flex-row items-center justify-start gap-2 w-full px-6 flex-wrap">
@@ -75,7 +75,7 @@ export default function AlbumList({ currentAlbumName }) {
} }
}} }}
> >
<p className="text-white red-hat-mono">{album.Title}</p> <p className="text-white red-hat-text">{album.Title}</p>
<EllipsisVertical className="w-6 h-6 cursor-pointer" color="white" onClick={(e) => { <EllipsisVertical className="w-6 h-6 cursor-pointer" color="white" onClick={(e) => {
e.stopPropagation() e.stopPropagation()
setEditingAlbum(album) setEditingAlbum(album)
@@ -14,7 +14,7 @@ export default function FilePicker({ currentAlbum, onFileSelect }) {
const [media, setMedia] = useState(null) const [media, setMedia] = useState(null)
const [filePickerOpen, setFilePickerOpen] = useState(false) const [filePickerOpen, setFilePickerOpen] = useState(false)
const { getAccessToken } = useAccount() const { getAccessToken } = useAccount()
const { showError } = useNotifier() const { showError, showInfo } = useNotifier()
useEffect(() => { useEffect(() => {
const getAlbum = async () => { const getAlbum = async () => {
const response = await fetch(`${getServerUrl()}/api/albums/getAlbumsInParent`, { const response = await fetch(`${getServerUrl()}/api/albums/getAlbumsInParent`, {
@@ -28,7 +28,7 @@ export default function FilePicker({ currentAlbum, onFileSelect }) {
}) })
const data = await response.json() const data = await response.json()
if (data.error) { if (data.error) {
showError(data.error) showError("Error getting albums in parent: " + data.error)
} else { } else {
setAlbums(data) setAlbums(data)
} }
@@ -45,7 +45,7 @@ export default function FilePicker({ currentAlbum, onFileSelect }) {
}) })
const data = await response.json() const data = await response.json()
if (data.error) { if (data.error) {
showError(data.error) showError("Error getting album info: " + data.error)
} else { } else {
setSelectedAlbum(data) setSelectedAlbum(data)
} }
@@ -59,7 +59,7 @@ export default function FilePicker({ currentAlbum, onFileSelect }) {
}) })
const data = await response.json() const data = await response.json()
if (data.error) { if (data.error) {
showError(data.error) showError("Error getting media: " + data.error)
} else { } else {
if (data.media.length === 0) { if (data.media.length === 0) {
setMedia(null) setMedia(null)
@@ -99,7 +99,7 @@ export default function FilePicker({ currentAlbum, onFileSelect }) {
const data = await response.json() const data = await response.json()
if (data.error) { if (data.error) {
showError(data.error) showError("Error getting album: " + data.error)
break break
} }
@@ -11,21 +11,25 @@ export default function MediaList({ albumId, albumName }) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [media, setMedia] = useState([]) const [media, setMedia] = useState([])
const [aspectRatios, setAspectRatios] = useState({}) const [aspectRatios, setAspectRatios] = useState({})
const { showError } = useNotifier() const { showError, showInfo } = useNotifier()
const navigate = useNavigate() const navigate = useNavigate()
useEffect(() => { useEffect(() => {
let ignore = false; let ignore = false;
const accessToken = getAccessToken()
if (!accessToken || !albumId && albumName !== 'gallery') {
return // assuming state isn't loaded yet
}
const getMedia = async () => { const getMedia = async () => {
const response = await fetch(`${getServerUrl()}/api/media/getAllMediaInAlbum?albumId=${albumId}`, { const response = await fetch(`${getServerUrl()}/api/media/getAllMediaInAlbum?albumId=${albumId}`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Authorization': getAccessToken(), 'Authorization': accessToken,
}, },
}) })
const data = await response.json() const data = await response.json()
if (ignore) return if (ignore) return
if (data.error) { if (data.error) {
showError(data.error) showError("Error getting media in album: " + data.error)
} else { } else {
setMedia(data.media) setMedia(data.media)
} }
@@ -51,7 +55,7 @@ export default function MediaList({ albumId, albumName }) {
return ( return (
<div className="flex flex-col items-center justify-start w-full bg-[#141414]"> <div className="flex flex-col items-center justify-start w-full bg-[#141414]">
<div className="flex flex-row items-center justify-between gap-2 w-full px-6 py-4"> <div className="flex flex-row items-center justify-between gap-2 w-full px-6 py-4">
<h1 className="text-xl font-bold text-white red-hat-mono">Media</h1> <h1 className="text-xl font-bold text-white red-hat-display">Media</h1>
<Upload className="w-6 h-6 cursor-pointer" color="white" onClick={() => setOpen(true)} /> <Upload className="w-6 h-6 cursor-pointer" color="white" onClick={() => setOpen(true)} />
</div> </div>
<MediaUploadModal <MediaUploadModal
@@ -89,7 +93,7 @@ export default function MediaList({ albumId, albumName }) {
}} }}
> >
<div className="absolute top-0 left-0 w-full h-full bg-gradient-to-b from-[#1A1A1A] via-transparent to-transparent flex items-start justify-start opacity-0 hover:opacity-100 transition-all duration-300"> <div className="absolute top-0 left-0 w-full h-full bg-gradient-to-b from-[#1A1A1A] via-transparent to-transparent flex items-start justify-start opacity-0 hover:opacity-100 transition-all duration-300">
<p className="text-white text-sm truncate max-w-[100%] p-2 red-hat-mono">{item.Title}</p> <p className="text-white text-sm truncate max-w-[90%] p-2 red-hat-text">{item.Title}</p>
<button className="text-white px-1 py-1 rounded-md absolute top-2 right-2 cursor-pointer z-50" onClick={(e) => { <button className="text-white px-1 py-1 rounded-md absolute top-2 right-2 cursor-pointer z-50" onClick={(e) => {
e.stopPropagation() e.stopPropagation()
setOpen(true) setOpen(true)
+7
View File
@@ -22,3 +22,10 @@ body,
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
} }
.red-hat-display {
font-family: "Red Hat Display", sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
}
@@ -8,7 +8,7 @@ export default function ImageViewer({ albumId, mediaId, token, title }) {
const [loaded, setLoaded] = useState(false) const [loaded, setLoaded] = useState(false)
return ( return (
<div className="flex-1 flex items-center justify-center bg-black relative overflow-hidden h-full"> <div className="flex-1 flex items-center justify-center bg-black relative overflow-hidden">
<AuthImage <AuthImage
src={src} src={src}
token={token} token={token}
@@ -1,14 +1,14 @@
export default function MetadataPanel({ mediaItem }) { export default function MetadataPanel({ mediaItem, open, onOpenChange }) {
if (!mediaItem) return <div className="w-80 bg-[#1A1A1A] h-full border-l border-[#2B2B2B] p-4 text-white">Loading...</div> if (!mediaItem) return <div className="w-80 bg-[#1A1A1A] h-full border-l border-[#2B2B2B] p-4 text-white">Loading...</div>
return ( return (
<div className="w-80 bg-[#1A1A1A] h-full border-l border-[#2B2B2B] p-6 text-white overflow-y-auto flex-shrink-0"> <div className={`w-full md:w-80 bg-[#1A1A1A] h-72 md:h-full border-t md:border-t-0 md:border-l border-[#2B2B2B] p-6 text-white overflow-y-auto flex-shrink-0 ${open ? 'block' : 'hidden'}`}>
<h2 className="text-xl font-bold mb-6 red-hat-mono break-words">{mediaItem.Title}</h2> <h2 className="text-xl font-bold mb-6 red-hat-display break-words">{mediaItem.Title}</h2>
<div className="space-y-6"> <div className="space-y-6">
<div className="space-y-2"> <div className="space-y-2">
<h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider">Details</h3> <h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider">Details</h3>
<div className="bg-[#222] rounded p-3 space-y-2 text-sm"> <div className="bg-[#222] rounded p-3 space-y-2 text-sm red-hat-mono">
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-gray-500">Type</span> <span className="text-gray-500">Type</span>
<span className="text-white">{mediaItem.Type || 'Unknown'}</span> <span className="text-white">{mediaItem.Type || 'Unknown'}</span>
@@ -25,7 +25,7 @@ export default function MetadataPanel({ mediaItem }) {
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2 red-hat-mono">
<h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider">EXIF Data</h3> <h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider">EXIF Data</h3>
<p className="text-xs text-gray-500 italic">Metadata editing not yet implemented.</p> <p className="text-xs text-gray-500 italic">Metadata editing not yet implemented.</p>
{/* Placeholder for EXIF data */} {/* Placeholder for EXIF data */}
+19 -13
View File
@@ -5,7 +5,7 @@ import MetadataPanel from './components/MetadataPanel'
import { useAccount } from '../contexts/useAccount' import { useAccount } from '../contexts/useAccount'
import { getServerUrl } from '../hooks/getConstants' import { getServerUrl } from '../hooks/getConstants'
import { useNotifier } from '../contexts/useNotifier' import { useNotifier } from '../contexts/useNotifier'
import { ArrowLeft } from 'lucide-react' import { ArrowLeft, PanelRightClose, PanelRightOpen } from 'lucide-react'
export default function Viewer() { export default function Viewer() {
const { albumId, mediaId } = useParams() const { albumId, mediaId } = useParams()
@@ -14,6 +14,7 @@ export default function Viewer() {
const { getAccessToken } = useAccount() const { getAccessToken } = useAccount()
const { showError } = useNotifier() const { showError } = useNotifier()
const [mediaItem, setMediaItem] = useState(location.state?.mediaItem || null) const [mediaItem, setMediaItem] = useState(location.state?.mediaItem || null)
const [metadataPanelOpen, setMetadataPanelOpen] = useState(false)
useEffect(() => { useEffect(() => {
if (mediaItem) return if (mediaItem) return
@@ -32,17 +33,17 @@ export default function Viewer() {
const data = await response.json() const data = await response.json()
if (data.error) { if (data.error) {
showError(data.error) showError("Error getting media details: " + data.error)
} else { } else {
const found = data.media.find(m => m.ID === mediaId) const found = data.media.find(m => m.ID === mediaId)
if (found) { if (found) {
setMediaItem(found) setMediaItem(found)
} else { } else {
showError("Media not found") showError("Media not found in album")
} }
} }
} catch (err) { } catch (err) {
showError("Failed to fetch media details") showError("Failed to fetch media details: " + err.message)
} }
} }
@@ -59,24 +60,29 @@ export default function Viewer() {
} }
return ( return (
<div className="flex flex-col h-screen w-full bg-[#141414]"> <div className="flex flex-col h-dvh w-full bg-[#141414]">
<div className="flex items-center h-14 px-4 border-b border-[#2B2B2B] bg-[#141414] flex-shrink-0 gap-4"> <div className="flex items-center h-14 px-4 border-b border-[#2B2B2B] bg-[#141414] flex-shrink-0 gap-4 justify-between">
<button onClick={handleBack} className="text-gray-400 hover:text-white transition-colors p-1 cursor-pointer"> <div className="flex items-center gap-2">
<ArrowLeft size={20} /> <button onClick={handleBack} className="text-gray-400 hover:text-white transition-colors p-1 cursor-pointer">
<ArrowLeft size={20} />
</button>
<span className="text-white font-medium truncate red-hat-text">
{mediaItem ? mediaItem.Title : 'Loading...'}
</span>
</div>
<button onClick={() => setMetadataPanelOpen(!metadataPanelOpen)} className="text-gray-400 hover:text-white transition-colors p-1 cursor-pointer">
{metadataPanelOpen ? <PanelRightClose size={20} /> : <PanelRightOpen size={20} />}
</button> </button>
<span className="text-white font-medium truncate red-hat-mono">
{mediaItem ? mediaItem.Title : 'Loading...'}
</span>
</div> </div>
<div className="flex flex-1 overflow-hidden"> <div className="flex flex-col md:flex-row flex-1 overflow-hidden">
<ImageViewer <ImageViewer
albumId={albumId === 'root' ? '' : albumId} albumId={albumId === 'root' ? '' : albumId}
mediaId={mediaId} mediaId={mediaId}
token={getAccessToken()} token={getAccessToken()}
title={mediaItem?.Title || ''} title={mediaItem?.Title || ''}
/> />
<MetadataPanel mediaItem={mediaItem} /> <MetadataPanel mediaItem={mediaItem} open={metadataPanelOpen} />
</div> </div>
</div> </div>
) )