mirror of
https://github.com/wisplite/raster.git
synced 2026-05-01 06:32:44 -05:00
Add first metadata values and vastly improve gallery tiling
This commit is contained in:
@@ -1,11 +1,17 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
|
"mime/multipart"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/wisplite/raster/internal/db"
|
"github.com/wisplite/raster/internal/db"
|
||||||
"github.com/wisplite/raster/internal/models"
|
"github.com/wisplite/raster/internal/models"
|
||||||
"mime/multipart"
|
"gorm.io/datatypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UploadMedia(file *multipart.FileHeader, albumID string, accessToken string) (models.Media, error) {
|
func UploadMedia(file *multipart.FileHeader, albumID string, accessToken string) (models.Media, error) {
|
||||||
@@ -24,6 +30,23 @@ func UploadMedia(file *multipart.FileHeader, albumID string, accessToken string)
|
|||||||
if albumID == "" {
|
if albumID == "" {
|
||||||
albumPath = "root"
|
albumPath = "root"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract metadata (dimensions)
|
||||||
|
var meta datatypes.JSON
|
||||||
|
src, err := file.Open()
|
||||||
|
if err == nil {
|
||||||
|
defer src.Close()
|
||||||
|
cfg, _, err := image.DecodeConfig(src)
|
||||||
|
if err == nil {
|
||||||
|
m := map[string]int{
|
||||||
|
"width": cfg.Width,
|
||||||
|
"height": cfg.Height,
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(m)
|
||||||
|
meta = datatypes.JSON(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mediaID := uuid.New().String()
|
mediaID := uuid.New().String()
|
||||||
mediaPath := fmt.Sprintf("media/%s/%s.%s", albumPath, mediaID, file.Filename)
|
mediaPath := fmt.Sprintf("media/%s/%s.%s", albumPath, mediaID, file.Filename)
|
||||||
media := models.Media{
|
media := models.Media{
|
||||||
@@ -31,6 +54,7 @@ func UploadMedia(file *multipart.FileHeader, albumID string, accessToken string)
|
|||||||
AlbumID: albumID,
|
AlbumID: albumID,
|
||||||
Path: mediaPath,
|
Path: mediaPath,
|
||||||
Type: file.Header.Get("Content-Type"),
|
Type: file.Header.Get("Content-Type"),
|
||||||
|
Metadata: meta,
|
||||||
}
|
}
|
||||||
result := db.GetDB().Create(&media)
|
result := db.GetDB().Create(&media)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ export default function MediaList({ albumId, albumName }) {
|
|||||||
const { getAccessToken } = useAccount()
|
const { getAccessToken } = useAccount()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [media, setMedia] = useState([])
|
const [media, setMedia] = useState([])
|
||||||
|
const [aspectRatios, setAspectRatios] = useState({})
|
||||||
const { showError } = useNotifier()
|
const { showError } = useNotifier()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let ignore = false;
|
let ignore = false;
|
||||||
const getMedia = async () => {
|
const getMedia = async () => {
|
||||||
// TODO: Implement media fetching from API
|
|
||||||
// const response = await fetch(...)
|
|
||||||
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: {
|
||||||
@@ -28,7 +27,6 @@ export default function MediaList({ albumId, albumName }) {
|
|||||||
if (data.error) {
|
if (data.error) {
|
||||||
showError(data.error)
|
showError(data.error)
|
||||||
} else {
|
} else {
|
||||||
console.log("data.media", data.media)
|
|
||||||
setMedia(data.media)
|
setMedia(data.media)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,6 +34,16 @@ export default function MediaList({ albumId, albumName }) {
|
|||||||
return () => { ignore = true; }
|
return () => { ignore = true; }
|
||||||
}, [albumId])
|
}, [albumId])
|
||||||
|
|
||||||
|
const handleImageLoad = (id, event) => {
|
||||||
|
const { naturalWidth, naturalHeight } = event.target
|
||||||
|
if (naturalWidth && naturalHeight) {
|
||||||
|
setAspectRatios(prev => ({
|
||||||
|
...prev,
|
||||||
|
[id]: naturalWidth / naturalHeight
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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">
|
||||||
@@ -44,20 +52,41 @@ export default function MediaList({ albumId, albumName }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Media Grid */}
|
{/* Media Grid */}
|
||||||
<div className="flex flex-row items-center justify-start gap-2 w-full px-6 flex-wrap">
|
<div className="flex flex-wrap justify-start gap-2 w-full px-6">
|
||||||
{media.length === 0 && (
|
{media.length === 0 && (
|
||||||
<p className="text-gray-500 red-hat-text">No media in this album</p>
|
<p className="text-gray-500 red-hat-text">No media in this album</p>
|
||||||
)}
|
)}
|
||||||
{media.map((media) => (
|
{media.map((item) => {
|
||||||
<div key={media.id} className="flex flex-col items-center justify-center w-24 h-24 bg-[#1A1A1A] rounded-md border border-[#2B2B2B]">
|
let ar = 1;
|
||||||
|
// Try to get aspect ratio from metadata (new uploads) or fallback to loaded state (old uploads)
|
||||||
|
if (item.Metadata && item.Metadata.width && item.Metadata.height) {
|
||||||
|
ar = item.Metadata.width / item.Metadata.height;
|
||||||
|
} else if (aspectRatios[item.ID]) {
|
||||||
|
ar = aspectRatios[item.ID];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.ID}
|
||||||
|
style={{
|
||||||
|
height: '220px',
|
||||||
|
flexGrow: ar,
|
||||||
|
flexBasis: `${220 * ar}px`,
|
||||||
|
}}
|
||||||
|
className="relative bg-[#1A1A1A] rounded-md overflow-hidden border border-[#2B2B2B] min-w-[100px]"
|
||||||
|
>
|
||||||
<AuthImage
|
<AuthImage
|
||||||
src={`${getServerUrl()}/api/media/${albumId ? albumId : 'root'}/${media.ID}`}
|
src={`${getServerUrl()}/api/media/${albumId ? albumId : 'root'}/${item.ID}`}
|
||||||
token={getAccessToken()}
|
token={getAccessToken()}
|
||||||
alt={media.Title}
|
alt={item.Title}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
|
onLoad={(e) => handleImageLoad(item.ID, e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
|
{/* Spacer to prevent the last row from expanding to fill width if it has few items */}
|
||||||
|
<div style={{ flexGrow: 9999, flexBasis: '50%' }}></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MediaUploadModal
|
<MediaUploadModal
|
||||||
|
|||||||
Reference in New Issue
Block a user