From 69bf8a01c42fc30c88543fcd6ebe5b9b2babdda5 Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Sat, 25 Sep 2021 17:11:54 +0200 Subject: [PATCH] Add date filter to timeline + fix #302 --- api/graphql/models/actions/media_actions.go | 22 +++ .../models/actions/media_actions_test.go | 88 ++++++++++++ api/graphql/resolvers/media.go | 25 +--- api/graphql/resolvers/timeline.go | 128 +----------------- .../TimelinePage.tsx} | 6 +- ui/src/components/album/AlbumFilter.tsx | 4 +- ui/src/components/layout/MainMenu.tsx | 2 +- ui/src/components/routes/Routes.tsx | 14 +- .../timelineGallery/TimelineFilters.tsx | 103 ++++++++++++++ .../timelineGallery/TimelineGallery.tsx | 45 ++++-- .../timelineGallery/TimelineGroupAlbum.tsx | 2 +- .../__generated__/earliestMedia.ts | 24 ++++ .../__generated__/myTimeline.ts | 57 ++++---- .../components/timelineGallery/icons/date.svg | 6 + ui/src/hooks/useScrollPagination.ts | 4 + ui/src/hooks/useURLParameters.ts | 24 +++- 16 files changed, 354 insertions(+), 200 deletions(-) create mode 100644 api/graphql/models/actions/media_actions.go create mode 100644 api/graphql/models/actions/media_actions_test.go rename ui/src/Pages/{PhotosPage/PhotosPage.tsx => TimelinePage/TimelinePage.tsx} (73%) create mode 100644 ui/src/components/timelineGallery/TimelineFilters.tsx create mode 100644 ui/src/components/timelineGallery/__generated__/earliestMedia.ts create mode 100644 ui/src/components/timelineGallery/icons/date.svg diff --git a/api/graphql/models/actions/media_actions.go b/api/graphql/models/actions/media_actions.go new file mode 100644 index 00000000..1593c219 --- /dev/null +++ b/api/graphql/models/actions/media_actions.go @@ -0,0 +1,22 @@ +package actions + +import ( + "github.com/photoview/photoview/api/graphql/models" + "gorm.io/gorm" +) + +func MyMedia(db *gorm.DB, user *models.User, order *models.Ordering, paginate *models.Pagination) ([]*models.Media, error) { + if err := user.FillAlbums(db); err != nil { + return nil, err + } + + query := db.Where("media.album_id IN (SELECT user_albums.album_id FROM user_albums WHERE user_albums.user_id = ?)", user.ID) + query = models.FormatSQL(query, order, paginate) + + var media []*models.Media + if err := query.Find(&media).Error; err != nil { + return nil, err + } + + return media, nil +} diff --git a/api/graphql/models/actions/media_actions_test.go b/api/graphql/models/actions/media_actions_test.go new file mode 100644 index 00000000..7a1eab64 --- /dev/null +++ b/api/graphql/models/actions/media_actions_test.go @@ -0,0 +1,88 @@ +package actions_test + +import ( + "testing" + + "github.com/photoview/photoview/api/graphql/models" + "github.com/photoview/photoview/api/graphql/models/actions" + "github.com/photoview/photoview/api/test_utils" + "github.com/stretchr/testify/assert" +) + +func TestMyMedia(t *testing.T) { + db := test_utils.DatabaseTest(t) + + password := "1234" + user, err := models.RegisterUser(db, "user", &password, false) + assert.NoError(t, err) + + rootAlbum := models.Album{ + Title: "root", + Path: "/photos", + } + + assert.NoError(t, db.Save(&rootAlbum).Error) + + childAlbum := models.Album{ + Title: "subalbum", + Path: "/photos/subalbum", + ParentAlbumID: &rootAlbum.ID, + } + + assert.NoError(t, db.Save(&childAlbum).Error) + + assert.NoError(t, db.Model(&user).Association("Albums").Append(&rootAlbum)) + assert.NoError(t, db.Model(&user).Association("Albums").Append(&childAlbum)) + + media := []models.Media{ + { + Title: "pic1", + Path: "/photos/pic1", + AlbumID: rootAlbum.ID, + }, + { + Title: "pic2", + Path: "/photos/pic2", + AlbumID: rootAlbum.ID, + }, + { + Title: "pic3", + Path: "/photos/subalbum/pic3", + AlbumID: childAlbum.ID, + }, + { + Title: "pic4", + Path: "/photos/subalbum/pic4", + AlbumID: childAlbum.ID, + }, + } + + assert.NoError(t, db.Save(&media).Error) + + anotherUser, err := models.RegisterUser(db, "user2", &password, false) + assert.NoError(t, err) + + anotherAlbum := models.Album{ + Title: "AnotherAlbum", + Path: "/another", + } + + assert.NoError(t, db.Save(&anotherAlbum).Error) + + anotherMedia := models.Media{ + Title: "anotherPic", + Path: "/another/anotherPic", + AlbumID: anotherAlbum.ID, + } + + assert.NoError(t, db.Save(&anotherMedia).Error) + + assert.NoError(t, db.Model(&anotherUser).Association("Albums").Append(&anotherAlbum)) + + t.Run("Simple query", func(t *testing.T) { + myMedia, err := actions.MyMedia(db, user, nil, nil) + + assert.NoError(t, err) + assert.Len(t, myMedia, 4) + }) +} diff --git a/api/graphql/resolvers/media.go b/api/graphql/resolvers/media.go index e68d3f4b..a6601756 100644 --- a/api/graphql/resolvers/media.go +++ b/api/graphql/resolvers/media.go @@ -8,6 +8,7 @@ import ( api "github.com/photoview/photoview/api/graphql" "github.com/photoview/photoview/api/graphql/auth" "github.com/photoview/photoview/api/graphql/models" + "github.com/photoview/photoview/api/graphql/models/actions" "github.com/photoview/photoview/api/scanner/face_detection" "github.com/pkg/errors" "gorm.io/gorm/clause" @@ -19,29 +20,7 @@ func (r *queryResolver) MyMedia(ctx context.Context, order *models.Ordering, pag return nil, errors.New("unauthorized") } - if err := user.FillAlbums(r.Database); err != nil { - return nil, err - } - - userAlbumIDs := make([]int, len(user.Albums)) - for i, album := range user.Albums { - userAlbumIDs[i] = album.ID - } - - var media []*models.Media - - query := r.Database. - Joins("Album"). - Where("albums.id IN (?)", userAlbumIDs). - Where("media.id IN (?)", r.Database.Model(&models.MediaURL{}).Select("id").Where("media_url.media_id = media.id")) - - query = models.FormatSQL(query, order, paginate) - - if err := query.Find(&media).Error; err != nil { - return nil, err - } - - return media, nil + return actions.MyMedia(r.Database, user, order, paginate) } func (r *queryResolver) Media(ctx context.Context, id int, tokenCredentials *models.ShareTokenCredentials) (*models.Media, error) { diff --git a/api/graphql/resolvers/timeline.go b/api/graphql/resolvers/timeline.go index 524b8561..86746168 100644 --- a/api/graphql/resolvers/timeline.go +++ b/api/graphql/resolvers/timeline.go @@ -26,6 +26,10 @@ func (r *queryResolver) MyTimeline(ctx context.Context, paginate *models.Paginat query = query.Where("media.date_shot < ?", fromDate) } + if onlyFavorites != nil && *onlyFavorites == true { + query = query.Where("media.id IN (?)", r.Database.Table("user_media_data").Select("user_media_data.media_id").Where("user_media_data.user_id = ?", user.ID).Where("user_media_data.favorite = 1")) + } + query = models.FormatSQL(query, nil, paginate) var media []*models.Media @@ -35,127 +39,3 @@ func (r *queryResolver) MyTimeline(ctx context.Context, paginate *models.Paginat return media, nil } - -// func (r *queryResolver) MyTimeline(ctx context.Context, paginate *models.Pagination, onlyFavorites *bool) ([]*models.TimelineGroup, error) { -// user := auth.UserFromContext(ctx) -// if user == nil { -// return nil, auth.ErrUnauthorized -// } - -// var timelineGroups []*models.TimelineGroup - -// transactionError := r.Database.Transaction(func(tx *gorm.DB) error { -// // album_id, year, month, day -// daysQuery := tx.Select( -// "albums.id AS album_id", -// fmt.Sprintf("%s AS year", database.DateExtract(tx, database.DateCompYear, "media.date_shot")), -// fmt.Sprintf("%s AS month", database.DateExtract(tx, database.DateCompMonth, "media.date_shot")), -// fmt.Sprintf("%s AS day", database.DateExtract(tx, database.DateCompDay, "media.date_shot")), -// ). -// Table("media"). -// Joins("JOIN albums ON media.album_id = albums.id"). -// Where("albums.id IN (?)", tx.Table("user_albums").Select("user_albums.album_id").Where("user_id = ?", user.ID)) - -// if onlyFavorites != nil && *onlyFavorites == true { -// daysQuery.Where("media.id IN (?)", tx.Table("user_media_data").Select("user_media_data.media_id").Where("user_media_data.user_id = ?", user.ID).Where("user_media_data.favorite = 1")) -// } - -// if paginate != nil { -// if paginate.Limit != nil { -// daysQuery.Limit(*paginate.Limit) -// } - -// if paginate.Offset != nil { -// daysQuery.Offset(*paginate.Offset) -// } -// } - -// rows, err := daysQuery.Group("albums.id").Group( -// fmt.Sprintf("%s, %s, %s", -// database.DateExtract(tx, database.DateCompYear, "media.date_shot"), -// database.DateExtract(tx, database.DateCompMonth, "media.date_shot"), -// database.DateExtract(tx, database.DateCompDay, "media.date_shot")), -// ). -// Order( -// fmt.Sprintf("%s DESC, %s DESC, %s DESC", -// database.DateExtract(tx, database.DateCompYear, "media.date_shot"), -// database.DateExtract(tx, database.DateCompMonth, "media.date_shot"), -// database.DateExtract(tx, database.DateCompDay, "media.date_shot")), -// ).Rows() - -// defer rows.Close() - -// if err != nil { -// return err -// } - -// type group struct { -// albumID int -// year int -// month int -// day int -// } - -// dbGroups := make([]group, 0) - -// for rows.Next() { -// var g group -// rows.Scan(&g.albumID, &g.year, &g.month, &g.day) -// dbGroups = append(dbGroups, g) -// } - -// timelineGroups = make([]*models.TimelineGroup, len(dbGroups)) - -// for i, group := range dbGroups { - -// // Fill album -// var groupAlbum models.Album -// if err := tx.First(&groupAlbum, group.albumID).Error; err != nil { -// return err -// } - -// // Fill media -// var groupMedia []*models.Media -// mediaQuery := tx.Model(&models.Media{}). -// Where("album_id = ?", group.albumID). -// Where(fmt.Sprintf("%s = ?", database.DateExtract(tx, database.DateCompYear, "media.date_shot")), group.year). -// Where(fmt.Sprintf("%s = ?", database.DateExtract(tx, database.DateCompMonth, "media.date_shot")), group.month). -// Where(fmt.Sprintf("%s = ?", database.DateExtract(tx, database.DateCompDay, "media.date_shot")), group.day). -// Order("date_shot DESC") - -// if onlyFavorites != nil && *onlyFavorites == true { -// mediaQuery.Where("media.id IN (?)", tx.Table("user_media_data").Select("user_media_data.media_id").Where("user_media_data.user_id = ?", user.ID).Where("user_media_data.favorite = 1")) -// } - -// if err := mediaQuery.Limit(5).Find(&groupMedia).Error; err != nil { -// return err -// } - -// // Get total media count -// var totalMedia int64 -// if err := mediaQuery.Count(&totalMedia).Error; err != nil { -// return err -// } - -// var date time.Time = groupMedia[0].DateShot -// date = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location()) - -// timelineGroup := models.TimelineGroup{ -// Album: &groupAlbum, -// Media: groupMedia, -// MediaTotal: int(totalMedia), -// Date: date, -// } - -// timelineGroups[i] = &timelineGroup -// } - -// return nil -// }) - -// if transactionError != nil { -// return nil, transactionError -// } - -// return timelineGroups, nil -// } diff --git a/ui/src/Pages/PhotosPage/PhotosPage.tsx b/ui/src/Pages/TimelinePage/TimelinePage.tsx similarity index 73% rename from ui/src/Pages/PhotosPage/PhotosPage.tsx rename to ui/src/Pages/TimelinePage/TimelinePage.tsx index 093ceb7f..47f666c6 100644 --- a/ui/src/Pages/PhotosPage/PhotosPage.tsx +++ b/ui/src/Pages/TimelinePage/TimelinePage.tsx @@ -3,16 +3,16 @@ import Layout from '../../components/layout/Layout' import { useTranslation } from 'react-i18next' import TimelineGallery from '../../components/timelineGallery/TimelineGallery' -const PhotosPage = () => { +const TimelinePage = () => { const { t } = useTranslation() return ( <> - + ) } -export default PhotosPage +export default TimelinePage diff --git a/ui/src/components/album/AlbumFilter.tsx b/ui/src/components/album/AlbumFilter.tsx index d76dedcf..c0109319 100644 --- a/ui/src/components/album/AlbumFilter.tsx +++ b/ui/src/components/album/AlbumFilter.tsx @@ -10,7 +10,7 @@ import { ReactComponent as DirectionIcon } from './icons/direction-arrow.svg' import Dropdown from '../../primitives/form/Dropdown' -type FavoriteCheckboxProps = { +export type FavoriteCheckboxProps = { onlyFavorites: boolean setOnlyFavorites(favorites: boolean): void } @@ -79,7 +79,7 @@ const SortingOptions = ({ setOrdering, ordering }: SortingOptionsProps) => {