Add mobile save sheet with same-origin photo downloads.
Phones cannot land downloads in the photo library, so the gallery opens a share sheet for a cacheable preview while streaming via a dedicated download endpoint and recording preview sizes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -42,6 +42,13 @@ type photoURLs struct {
|
||||
Thumb string `json:"thumb,omitempty"`
|
||||
Preview string `json:"preview,omitempty"`
|
||||
Original string `json:"original"`
|
||||
// Download / DownloadOriginal hit the download endpoint (files.go), which
|
||||
// always streams the bytes same-origin instead of redirecting to S3, so a
|
||||
// fetch() can read them into a Blob without CORS. Download serves the
|
||||
// preview variant and doubles as the lightbox's <img> source, so the save
|
||||
// fetch is answered from the HTTP cache rather than the network.
|
||||
Download string `json:"download,omitempty"`
|
||||
DownloadOriginal string `json:"downloadOriginal"`
|
||||
}
|
||||
|
||||
type photoJSON struct {
|
||||
@@ -51,6 +58,9 @@ type photoJSON struct {
|
||||
OriginalFilename string `json:"originalFilename,omitempty"`
|
||||
ContentType string `json:"contentType"`
|
||||
SizeBytes int64 `json:"sizeBytes"`
|
||||
// PreviewSizeBytes lets the mobile save sheet label both of its rows with
|
||||
// a real file size without fetching anything. Omitted while unknown.
|
||||
PreviewSizeBytes int64 `json:"previewSizeBytes,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
TakenAt string `json:"takenAt,omitempty"`
|
||||
@@ -91,6 +101,22 @@ func fileURL(photoID, variant, token string) string {
|
||||
return u
|
||||
}
|
||||
|
||||
// downloadURL builds the download endpoint's URL. size is left off for the
|
||||
// preview, which the endpoint serves by default — a shorter, stabler string
|
||||
// for the URL the lightbox also renders.
|
||||
func downloadURL(photoID, size, token string) string {
|
||||
u := "/api/photos/files/" + photoID + "/download"
|
||||
sep := "?"
|
||||
if size != "" && size != downloadSizePreview {
|
||||
u += "?size=" + size
|
||||
sep = "&"
|
||||
}
|
||||
if token != "" {
|
||||
u += sep + "token=" + token
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (s *Server) photoToJSON(p store.Photo, token string, admin bool) photoJSON {
|
||||
out := photoJSON{
|
||||
ID: p.ID,
|
||||
@@ -101,16 +127,21 @@ func (s *Server) photoToJSON(p store.Photo, token string, admin bool) photoJSON
|
||||
SizeBytes: p.SizeBytes,
|
||||
Width: p.Width,
|
||||
Height: p.Height,
|
||||
PreviewSizeBytes: p.PreviewSizeBytes,
|
||||
TakenAt: isoTime(p.TakenAt),
|
||||
Status: p.Status,
|
||||
CreatedAt: isoTime(p.CreatedAt),
|
||||
URLs: photoURLs{Original: fileURL(p.ID, "original", token)},
|
||||
URLs: photoURLs{
|
||||
Original: fileURL(p.ID, "original", token),
|
||||
DownloadOriginal: downloadURL(p.ID, downloadSizeOriginal, token),
|
||||
},
|
||||
}
|
||||
if p.ThumbKey != "" {
|
||||
out.URLs.Thumb = fileURL(p.ID, "thumb", token)
|
||||
}
|
||||
if p.PreviewKey != "" {
|
||||
out.URLs.Preview = fileURL(p.ID, "preview", token)
|
||||
out.URLs.Download = downloadURL(p.ID, downloadSizePreview, token)
|
||||
}
|
||||
if admin {
|
||||
out.LastError = p.LastError
|
||||
|
||||
Reference in New Issue
Block a user