mirror of
https://github.com/wisplite/raster.git
synced 2026-05-01 06:32:44 -05:00
small updates, for some reason it thinks every file is modified
This commit is contained in:
@@ -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
@@ -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>
|
||||||
|
|||||||
Generated
+10
@@ -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",
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 */}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<button onClick={handleBack} className="text-gray-400 hover:text-white transition-colors p-1 cursor-pointer">
|
<button onClick={handleBack} className="text-gray-400 hover:text-white transition-colors p-1 cursor-pointer">
|
||||||
<ArrowLeft size={20} />
|
<ArrowLeft size={20} />
|
||||||
</button>
|
</button>
|
||||||
<span className="text-white font-medium truncate red-hat-mono">
|
<span className="text-white font-medium truncate red-hat-text">
|
||||||
{mediaItem ? mediaItem.Title : 'Loading...'}
|
{mediaItem ? mediaItem.Title : 'Loading...'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
||||||
|
</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>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user