feat: photo multiselect, move/copy, trash and compact chrome
- backend: soft delete + trash (photos.deleted_at, migration 000005), GET /trash, restore, permanent delete with storage-key refcount (S3 objects removed only when no other album references them); batch endpoints photo-batch/{delete,move,copy} - metadata only, copies clone rows sharing storage keys
- frontend: Ctrl/Shift multiselect; right-panel 'Действия' as icon text rows (Открыть (просмотр), Переместить, Копировать, Удалить); drag selected photos onto rail album -> TransferDialog; /trash route; trash entry as last item in the left rail; empty right panel hidden on overview
- frontend: compact top bars (app header and viewer), compact theme switcher
- openspec: sync lightroom-workspace-ui artifacts (photos/ui-workspace specs, design #20-24, tasks 9.11-9.17)
This commit is contained in:
@@ -133,3 +133,76 @@ func (h *Handler) Delete(c *gin.Context) {
|
||||
}
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *Handler) PermanentDelete(c *gin.Context) {
|
||||
if err := h.svc.PermanentDelete(c.Request.Context(), c.Param("id"), auth.UserID(c)); err != nil {
|
||||
httpx.RespondError(c, err)
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *Handler) Restore(c *gin.Context) {
|
||||
p, err := h.svc.Restore(c.Request.Context(), c.Param("id"), auth.UserID(c))
|
||||
if err != nil {
|
||||
httpx.RespondError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"photo": p})
|
||||
}
|
||||
|
||||
func (h *Handler) Trash(c *gin.Context) {
|
||||
list, err := h.svc.Trash(c.Request.Context(), auth.UserID(c))
|
||||
if err != nil {
|
||||
httpx.RespondError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"photos": list})
|
||||
}
|
||||
|
||||
type batchRequest struct {
|
||||
IDs []string `json:"ids" binding:"required"`
|
||||
AlbumID string `json:"album_id"`
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteBatch(c *gin.Context) {
|
||||
var req batchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil || len(req.IDs) == 0 {
|
||||
httpx.RespondError(c, httpx.BadRequest("invalid_request", "ids are required"))
|
||||
return
|
||||
}
|
||||
n, err := h.svc.DeleteBatch(c.Request.Context(), req.IDs, auth.UserID(c))
|
||||
if err != nil {
|
||||
httpx.RespondError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"deleted": n})
|
||||
}
|
||||
|
||||
func (h *Handler) MoveBatch(c *gin.Context) {
|
||||
var req batchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil || len(req.IDs) == 0 || req.AlbumID == "" {
|
||||
httpx.RespondError(c, httpx.BadRequest("invalid_request", "ids and album_id are required"))
|
||||
return
|
||||
}
|
||||
n, err := h.svc.MoveBatch(c.Request.Context(), req.IDs, auth.UserID(c), req.AlbumID)
|
||||
if err != nil {
|
||||
httpx.RespondError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"moved": n})
|
||||
}
|
||||
|
||||
func (h *Handler) CopyBatch(c *gin.Context) {
|
||||
var req batchRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil || len(req.IDs) == 0 || req.AlbumID == "" {
|
||||
httpx.RespondError(c, httpx.BadRequest("invalid_request", "ids and album_id are required"))
|
||||
return
|
||||
}
|
||||
created, err := h.svc.CopyBatch(c.Request.Context(), req.IDs, auth.UserID(c), req.AlbumID)
|
||||
if err != nil {
|
||||
httpx.RespondError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"photos": created})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user