feat(web): вкладка «Редактирование» с инструментом «Свет»
В правой панели фото появилась вкладка «Редактирование» с инструментом «Свет»: ползунки экспозиции, контраста, светов, теней, белых и чёрных и точечная кривая с выбором канала. Правки сохраняются автоматически, переключатель «Сброс» удаляет параметры правок и отредактированную версию фото. Редактирование неразрушающее: параметры хранятся в метаданных фото, а отредактированная версия рендерится в фоне отдельным объектом (edit, edit_thumb, edit_medium, edit_preview) рядом с исходными вариантами. Оригинал не меняется, скачивание отдаёт исходный файл. Сетка, просмотр и обложки альбомов показывают отредактированную версию, когда она отрендерена.
This commit is contained in:
@@ -25,6 +25,10 @@ const sqlitePhotoColumns = `p.id, COALESCE(p.album_id, ''), p.uploader_id,
|
||||
COALESCE(o_m.storage_key, ''),
|
||||
COALESCE(o_p.storage_key, ''),
|
||||
COALESCE(o_pb.storage_key, ''),
|
||||
COALESCE(o_e.storage_key, ''),
|
||||
COALESCE(o_et.storage_key, ''),
|
||||
COALESCE(o_em.storage_key, ''),
|
||||
COALESCE(o_ep.storage_key, ''),
|
||||
p.original_filename, COALESCE(o_o.size, 0), COALESCE(o_o.width, 0), COALESCE(o_o.height, 0), COALESCE(o_o.mime_type, ''), COALESCE(o_o.checksum, ''),
|
||||
p.kind, COALESCE(p.duration_ms, 0),
|
||||
p.taken_at, p.latitude, p.longitude, COALESCE(p.caption, ''), p.created_at,
|
||||
@@ -33,6 +37,7 @@ const sqlitePhotoColumns = `p.id, COALESCE(p.album_id, ''), p.uploader_id,
|
||||
(SELECT count(*) FROM comments c WHERE c.photo_id = p.id),
|
||||
EXISTS(SELECT 1 FROM likes l WHERE l.photo_id = p.id AND l.user_id = $2),
|
||||
COALESCE(p.exif, '{}'), p.deleted_at,
|
||||
COALESCE(p.edit_params, '{}'),
|
||||
COALESCE((SELECT json_group_array(json_object('id', t.id, 'name', t.name))
|
||||
FROM (SELECT t.id, t.name FROM photo_tags pt JOIN tags t ON t.id = pt.tag_id
|
||||
WHERE pt.photo_id = p.id AND t.user_id = $2
|
||||
@@ -48,21 +53,30 @@ const sqlitePhotoJoins = `
|
||||
LEFT JOIN photo_objects po_p ON po_p.photo_id = p.id AND po_p.role = 'preview'
|
||||
LEFT JOIN objects o_p ON o_p.id = po_p.object_id
|
||||
LEFT JOIN photo_objects po_pb ON po_pb.photo_id = p.id AND po_pb.role = 'playback'
|
||||
LEFT JOIN objects o_pb ON o_pb.id = po_pb.object_id`
|
||||
LEFT JOIN objects o_pb ON o_pb.id = po_pb.object_id
|
||||
LEFT JOIN photo_objects po_e ON po_e.photo_id = p.id AND po_e.role = 'edit'
|
||||
LEFT JOIN objects o_e ON o_e.id = po_e.object_id
|
||||
LEFT JOIN photo_objects po_et ON po_et.photo_id = p.id AND po_et.role = 'edit_thumb'
|
||||
LEFT JOIN objects o_et ON o_et.id = po_et.object_id
|
||||
LEFT JOIN photo_objects po_em ON po_em.photo_id = p.id AND po_em.role = 'edit_medium'
|
||||
LEFT JOIN objects o_em ON o_em.id = po_em.object_id
|
||||
LEFT JOIN photo_objects po_ep ON po_ep.photo_id = p.id AND po_ep.role = 'edit_preview'
|
||||
LEFT JOIN objects o_ep ON o_ep.id = po_ep.object_id`
|
||||
|
||||
func scanSQLitePhoto(scan func(...any) error) (*models.Photo, error) {
|
||||
var p models.Photo
|
||||
var exifText, tagsText, createdAt string
|
||||
var exifText, tagsText, editText, createdAt string
|
||||
var takenAt, deletedAt sql.NullString
|
||||
var lat, long sql.NullFloat64
|
||||
var liked int
|
||||
if err := scan(&p.ID, &p.AlbumID, &p.UploaderID, &p.StorageKey,
|
||||
&p.ThumbKey, &p.MediumKey, &p.PreviewKey, &p.PlaybackKey,
|
||||
&p.EditKey, &p.EditThumbKey, &p.EditMediumKey, &p.EditPreviewKey,
|
||||
&p.OriginalFilename, &p.Size, &p.Width, &p.Height, &p.MimeType, &p.OriginalChecksum,
|
||||
&p.Kind, &p.DurationMS,
|
||||
&takenAt, &lat, &long, &p.Caption, &createdAt,
|
||||
&p.Uploader.ID, &p.Uploader.Name, &p.Uploader.AvatarURL,
|
||||
&p.LikesCount, &p.CommentsCount, &liked, &exifText, &deletedAt, &tagsText); err != nil {
|
||||
&p.LikesCount, &p.CommentsCount, &liked, &exifText, &deletedAt, &editText, &tagsText); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.TakenAt = sqliteutil.ParseNullTime(takenAt)
|
||||
@@ -76,6 +90,7 @@ func scanSQLitePhoto(scan func(...any) error) (*models.Photo, error) {
|
||||
p.DeletedAt = sqliteutil.ParseNullTime(deletedAt)
|
||||
p.LikedByMe = liked != 0
|
||||
setExif(&p, exifText)
|
||||
setEditParams(&p, editText)
|
||||
setTags(&p, tagsText)
|
||||
return &p, nil
|
||||
}
|
||||
@@ -277,17 +292,18 @@ func (s *SQLiteStore) ListTrash(ctx context.Context, userID string) ([]models.Ph
|
||||
out := []models.Photo{}
|
||||
for rows.Next() {
|
||||
var p models.Photo
|
||||
var exifText, tagsText, createdAt, albumTitle string
|
||||
var exifText, tagsText, editText, createdAt, albumTitle string
|
||||
var takenAt, deletedAt sql.NullString
|
||||
var lat, long sql.NullFloat64
|
||||
var liked int
|
||||
if err := rows.Scan(&p.ID, &p.AlbumID, &p.UploaderID, &p.StorageKey,
|
||||
&p.ThumbKey, &p.MediumKey, &p.PreviewKey, &p.PlaybackKey,
|
||||
&p.EditKey, &p.EditThumbKey, &p.EditMediumKey, &p.EditPreviewKey,
|
||||
&p.OriginalFilename, &p.Size, &p.Width, &p.Height, &p.MimeType, &p.OriginalChecksum,
|
||||
&p.Kind, &p.DurationMS,
|
||||
&takenAt, &lat, &long, &p.Caption, &createdAt,
|
||||
&p.Uploader.ID, &p.Uploader.Name, &p.Uploader.AvatarURL,
|
||||
&p.LikesCount, &p.CommentsCount, &liked, &exifText, &deletedAt, &albumTitle, &tagsText); err != nil {
|
||||
&p.LikesCount, &p.CommentsCount, &liked, &exifText, &deletedAt, &editText, &albumTitle, &tagsText); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.TakenAt = sqliteutil.ParseNullTime(takenAt)
|
||||
@@ -302,6 +318,7 @@ func (s *SQLiteStore) ListTrash(ctx context.Context, userID string) ([]models.Ph
|
||||
p.AlbumTitle = albumTitle
|
||||
p.LikedByMe = liked != 0
|
||||
setExif(&p, exifText)
|
||||
setEditParams(&p, editText)
|
||||
setTags(&p, tagsText)
|
||||
out = append(out, p)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user