1
Fork 0

Merge pull request #530 from photoview/improved-timeline

Improved timeline
This commit is contained in:
Viktor Strate Kløvedal 2021-09-25 18:21:56 +02:00 committed by GitHub
commit 77e1a45f0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 2450 additions and 1118 deletions

3
.gitignore vendored
View File

@ -37,6 +37,7 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
.eslintcache .eslintcache
# vscode # IDEs
.vscode
__debug_bin __debug_bin
.idea .idea

View File

@ -1,3 +0,0 @@
{
"eslint.workingDirectories": ["ui"]
}

View File

@ -38,6 +38,8 @@ models:
resolver: true resolver: true
type: type:
resolver: true resolver: true
album:
resolver: true
MediaURL: MediaURL:
model: github.com/photoview/photoview/api/graphql/models.MediaURL model: github.com/photoview/photoview/api/graphql/models.MediaURL
MediaEXIF: MediaEXIF:

View File

@ -98,6 +98,7 @@ type ComplexityRoot struct {
Media struct { Media struct {
Album func(childComplexity int) int Album func(childComplexity int) int
Date func(childComplexity int) int
Downloads func(childComplexity int) int Downloads func(childComplexity int) int
Exif func(childComplexity int) int Exif func(childComplexity int) int
Faces func(childComplexity int) int Faces func(childComplexity int) int
@ -188,7 +189,7 @@ type ComplexityRoot struct {
MyFaceGroups func(childComplexity int, paginate *models.Pagination) int MyFaceGroups func(childComplexity int, paginate *models.Pagination) int
MyMedia func(childComplexity int, order *models.Ordering, paginate *models.Pagination) int MyMedia func(childComplexity int, order *models.Ordering, paginate *models.Pagination) int
MyMediaGeoJSON func(childComplexity int) int MyMediaGeoJSON func(childComplexity int) int
MyTimeline func(childComplexity int, paginate *models.Pagination, onlyFavorites *bool) int MyTimeline func(childComplexity int, paginate *models.Pagination, onlyFavorites *bool, fromDate *time.Time) int
MyUser func(childComplexity int) int MyUser func(childComplexity int) int
MyUserPreferences func(childComplexity int) int MyUserPreferences func(childComplexity int) int
Search func(childComplexity int, query string, limitMedia *int, limitAlbums *int) int Search func(childComplexity int, query string, limitMedia *int, limitAlbums *int) int
@ -287,11 +288,12 @@ type MediaResolver interface {
Thumbnail(ctx context.Context, obj *models.Media) (*models.MediaURL, error) Thumbnail(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
HighRes(ctx context.Context, obj *models.Media) (*models.MediaURL, error) HighRes(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
VideoWeb(ctx context.Context, obj *models.Media) (*models.MediaURL, error) VideoWeb(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
Album(ctx context.Context, obj *models.Media) (*models.Album, error)
Exif(ctx context.Context, obj *models.Media) (*models.MediaEXIF, error) Exif(ctx context.Context, obj *models.Media) (*models.MediaEXIF, error)
Favorite(ctx context.Context, obj *models.Media) (bool, error) Favorite(ctx context.Context, obj *models.Media) (bool, error)
Type(ctx context.Context, obj *models.Media) (models.MediaType, error) Type(ctx context.Context, obj *models.Media) (models.MediaType, error)
Shares(ctx context.Context, obj *models.Media) ([]*models.ShareToken, error) Shares(ctx context.Context, obj *models.Media) ([]*models.ShareToken, error)
Downloads(ctx context.Context, obj *models.Media) ([]*models.MediaDownload, error) Downloads(ctx context.Context, obj *models.Media) ([]*models.MediaDownload, error)
Faces(ctx context.Context, obj *models.Media) ([]*models.ImageFace, error) Faces(ctx context.Context, obj *models.Media) ([]*models.ImageFace, error)
@ -332,7 +334,7 @@ type QueryResolver interface {
MyMedia(ctx context.Context, order *models.Ordering, paginate *models.Pagination) ([]*models.Media, error) MyMedia(ctx context.Context, order *models.Ordering, paginate *models.Pagination) ([]*models.Media, error)
Media(ctx context.Context, id int, tokenCredentials *models.ShareTokenCredentials) (*models.Media, error) Media(ctx context.Context, id int, tokenCredentials *models.ShareTokenCredentials) (*models.Media, error)
MediaList(ctx context.Context, ids []int) ([]*models.Media, error) MediaList(ctx context.Context, ids []int) ([]*models.Media, error)
MyTimeline(ctx context.Context, paginate *models.Pagination, onlyFavorites *bool) ([]*models.TimelineGroup, error) MyTimeline(ctx context.Context, paginate *models.Pagination, onlyFavorites *bool, fromDate *time.Time) ([]*models.Media, error)
MyMediaGeoJSON(ctx context.Context) (interface{}, error) MyMediaGeoJSON(ctx context.Context) (interface{}, error)
MapboxToken(ctx context.Context) (*string, error) MapboxToken(ctx context.Context) (*string, error)
ShareToken(ctx context.Context, credentials models.ShareTokenCredentials) (*models.ShareToken, error) ShareToken(ctx context.Context, credentials models.ShareTokenCredentials) (*models.ShareToken, error)
@ -567,6 +569,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Media.Album(childComplexity), true return e.complexity.Media.Album(childComplexity), true
case "Media.date":
if e.complexity.Media.Date == nil {
break
}
return e.complexity.Media.Date(childComplexity), true
case "Media.downloads": case "Media.downloads":
if e.complexity.Media.Downloads == nil { if e.complexity.Media.Downloads == nil {
break break
@ -1226,7 +1235,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return 0, false return 0, false
} }
return e.complexity.Query.MyTimeline(childComplexity, args["paginate"].(*models.Pagination), args["onlyFavorites"].(*bool)), true return e.complexity.Query.MyTimeline(childComplexity, args["paginate"].(*models.Pagination), args["onlyFavorites"].(*bool), args["fromDate"].(*time.Time)), true
case "Query.myUser": case "Query.myUser":
if e.complexity.Query.MyUser == nil { if e.complexity.Query.MyUser == nil {
@ -1723,7 +1732,15 @@ type Query {
"Get a list of media by their ids, user must own the media or be admin" "Get a list of media by their ids, user must own the media or be admin"
mediaList(ids: [ID!]!): [Media!]! mediaList(ids: [ID!]!): [Media!]!
myTimeline(paginate: Pagination, onlyFavorites: Boolean): [TimelineGroup!]! @isAuthorized """
Get a list of media, ordered first by day, then by album if multiple media was found for the same day.
"""
myTimeline(
paginate: Pagination,
onlyFavorites: Boolean,
"Only fetch media that is older than this date"
fromDate: Time
): [Media!]! @isAuthorized
"Get media owned by the logged in user, returned in GeoJson format" "Get media owned by the logged in user, returned in GeoJson format"
myMediaGeoJson: Any! @isAuthorized myMediaGeoJson: Any! @isAuthorized
@ -1980,6 +1997,8 @@ type Media {
videoMetadata: VideoMetadata videoMetadata: VideoMetadata
favorite: Boolean! favorite: Boolean!
type: MediaType! type: MediaType!
"The date the image was shot or the date it was imported as a fallback"
date: Time!
shares: [ShareToken!]! shares: [ShareToken!]!
downloads: [MediaDownload!]! downloads: [MediaDownload!]!
@ -2843,6 +2862,15 @@ func (ec *executionContext) field_Query_myTimeline_args(ctx context.Context, raw
} }
} }
args["onlyFavorites"] = arg1 args["onlyFavorites"] = arg1
var arg2 *time.Time
if tmp, ok := rawArgs["fromDate"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fromDate"))
arg2, err = ec.unmarshalOTime2ᚖtimeᚐTime(ctx, tmp)
if err != nil {
return nil, err
}
}
args["fromDate"] = arg2
return args, nil return args, nil
} }
@ -4067,14 +4095,14 @@ func (ec *executionContext) _Media_album(ctx context.Context, field graphql.Coll
Object: "Media", Object: "Media",
Field: field, Field: field,
Args: nil, Args: nil,
IsMethod: false, IsMethod: true,
IsResolver: false, IsResolver: true,
} }
ctx = graphql.WithFieldContext(ctx, fc) ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children ctx = rctx // use context from middleware stack in children
return obj.Album, nil return ec.resolvers.Media().Album(rctx, obj)
}) })
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
@ -4086,9 +4114,9 @@ func (ec *executionContext) _Media_album(ctx context.Context, field graphql.Coll
} }
return graphql.Null return graphql.Null
} }
res := resTmp.(models.Album) res := resTmp.(*models.Album)
fc.Result = res fc.Result = res
return ec.marshalNAlbum2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res) return ec.marshalNAlbum2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res)
} }
func (ec *executionContext) _Media_exif(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) { func (ec *executionContext) _Media_exif(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) {
@ -4225,6 +4253,41 @@ func (ec *executionContext) _Media_type(ctx context.Context, field graphql.Colle
return ec.marshalNMediaType2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMediaType(ctx, field.Selections, res) return ec.marshalNMediaType2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMediaType(ctx, field.Selections, res)
} }
func (ec *executionContext) _Media_date(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Media",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Date(), nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(time.Time)
fc.Result = res
return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res)
}
func (ec *executionContext) _Media_shares(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) { func (ec *executionContext) _Media_shares(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -7112,7 +7175,7 @@ func (ec *executionContext) _Query_myTimeline(ctx context.Context, field graphql
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().MyTimeline(rctx, args["paginate"].(*models.Pagination), args["onlyFavorites"].(*bool)) return ec.resolvers.Query().MyTimeline(rctx, args["paginate"].(*models.Pagination), args["onlyFavorites"].(*bool), args["fromDate"].(*time.Time))
} }
directive1 := func(ctx context.Context) (interface{}, error) { directive1 := func(ctx context.Context) (interface{}, error) {
if ec.directives.IsAuthorized == nil { if ec.directives.IsAuthorized == nil {
@ -7128,10 +7191,10 @@ func (ec *executionContext) _Query_myTimeline(ctx context.Context, field graphql
if tmp == nil { if tmp == nil {
return nil, nil return nil, nil
} }
if data, ok := tmp.([]*models.TimelineGroup); ok { if data, ok := tmp.([]*models.Media); ok {
return data, nil return data, nil
} }
return nil, fmt.Errorf(`unexpected type %T from directive, should be []*github.com/photoview/photoview/api/graphql/models.TimelineGroup`, tmp) return nil, fmt.Errorf(`unexpected type %T from directive, should be []*github.com/photoview/photoview/api/graphql/models.Media`, tmp)
}) })
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
@ -7143,9 +7206,9 @@ func (ec *executionContext) _Query_myTimeline(ctx context.Context, field graphql
} }
return graphql.Null return graphql.Null
} }
res := resTmp.([]*models.TimelineGroup) res := resTmp.([]*models.Media)
fc.Result = res fc.Result = res
return ec.marshalNTimelineGroup2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐTimelineGroup(ctx, field.Selections, res) return ec.marshalNMedia2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐMedia(ctx, field.Selections, res)
} }
func (ec *executionContext) _Query_myMediaGeoJson(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Query_myMediaGeoJson(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
@ -10576,10 +10639,19 @@ func (ec *executionContext) _Media(ctx context.Context, sel ast.SelectionSet, ob
return res return res
}) })
case "album": case "album":
out.Values[i] = ec._Media_album(ctx, field, obj) field := field
if out.Values[i] == graphql.Null { out.Concurrently(i, func() (res graphql.Marshaler) {
atomic.AddUint32(&invalids, 1) defer func() {
} if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Media_album(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "exif": case "exif":
field := field field := field
out.Concurrently(i, func() (res graphql.Marshaler) { out.Concurrently(i, func() (res graphql.Marshaler) {
@ -10621,6 +10693,11 @@ func (ec *executionContext) _Media(ctx context.Context, sel ast.SelectionSet, ob
} }
return res return res
}) })
case "date":
out.Values[i] = ec._Media_date(ctx, field, obj)
if out.Values[i] == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
case "shares": case "shares":
field := field field := field
out.Concurrently(i, func() (res graphql.Marshaler) { out.Concurrently(i, func() (res graphql.Marshaler) {
@ -12466,53 +12543,6 @@ func (ec *executionContext) marshalNTime2timeᚐTime(ctx context.Context, sel as
return res return res
} }
func (ec *executionContext) marshalNTimelineGroup2ᚕᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐTimelineGroupᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.TimelineGroup) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
fc := &graphql.FieldContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithFieldContext(ctx, fc)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNTimelineGroup2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐTimelineGroup(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
return ret
}
func (ec *executionContext) marshalNTimelineGroup2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐTimelineGroup(ctx context.Context, sel ast.SelectionSet, v *models.TimelineGroup) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._TimelineGroup(ctx, sel, v)
}
func (ec *executionContext) marshalNUser2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx context.Context, sel ast.SelectionSet, v models.User) graphql.Marshaler { func (ec *executionContext) marshalNUser2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx context.Context, sel ast.SelectionSet, v models.User) graphql.Marshaler {
return ec._User(ctx, sel, &v) return ec._User(ctx, sel, &v)
} }

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -45,6 +45,10 @@ func (m *Media) BeforeSave(tx *gorm.DB) error {
return nil return nil
} }
func (m *Media) Date() time.Time {
return m.DateShot
}
type MediaType string type MediaType string
const ( const (

View File

@ -8,6 +8,7 @@ import (
api "github.com/photoview/photoview/api/graphql" api "github.com/photoview/photoview/api/graphql"
"github.com/photoview/photoview/api/graphql/auth" "github.com/photoview/photoview/api/graphql/auth"
"github.com/photoview/photoview/api/graphql/models" "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/photoview/photoview/api/scanner/face_detection"
"github.com/pkg/errors" "github.com/pkg/errors"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
@ -19,29 +20,7 @@ func (r *queryResolver) MyMedia(ctx context.Context, order *models.Ordering, pag
return nil, errors.New("unauthorized") return nil, errors.New("unauthorized")
} }
if err := user.FillAlbums(r.Database); err != nil { return actions.MyMedia(r.Database, user, order, paginate)
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
} }
func (r *queryResolver) Media(ctx context.Context, id int, tokenCredentials *models.ShareTokenCredentials) (*models.Media, error) { func (r *queryResolver) Media(ctx context.Context, id int, tokenCredentials *models.ShareTokenCredentials) (*models.Media, error) {
@ -115,6 +94,15 @@ func (r *mediaResolver) Type(ctx context.Context, media *models.Media) (models.M
return formattedType, nil return formattedType, nil
} }
func (r *mediaResolver) Album(ctx context.Context, obj *models.Media) (*models.Album, error) {
var album models.Album
err := r.Database.Find(&album, obj.AlbumID).Error
if err != nil {
return nil, err
}
return &album, nil
}
func (r *mediaResolver) Shares(ctx context.Context, media *models.Media) ([]*models.ShareToken, error) { func (r *mediaResolver) Shares(ctx context.Context, media *models.Media) ([]*models.ShareToken, error) {
var shareTokens []*models.ShareToken var shareTokens []*models.ShareToken
if err := r.Database.Where("media_id = ?", media.ID).Find(&shareTokens).Error; err != nil { if err := r.Database.Where("media_id = ?", media.ID).Find(&shareTokens).Error; err != nil {

View File

@ -2,135 +2,40 @@ package resolvers
import ( import (
"context" "context"
"fmt"
"time" "time"
"github.com/photoview/photoview/api/database"
"github.com/photoview/photoview/api/graphql/auth" "github.com/photoview/photoview/api/graphql/auth"
"github.com/photoview/photoview/api/graphql/models" "github.com/photoview/photoview/api/graphql/models"
"gorm.io/gorm"
) )
func (r *queryResolver) MyTimeline(ctx context.Context, paginate *models.Pagination, onlyFavorites *bool) ([]*models.TimelineGroup, error) { func (r *queryResolver) MyTimeline(ctx context.Context, paginate *models.Pagination, onlyFavorites *bool, fromDate *time.Time) ([]*models.Media, error) {
user := auth.UserFromContext(ctx) user := auth.UserFromContext(ctx)
if user == nil { if user == nil {
return nil, auth.ErrUnauthorized return nil, auth.ErrUnauthorized
} }
var timelineGroups []*models.TimelineGroup query := r.Database.
Joins("JOIN albums ON media.album_id = albums.id").
Where("albums.id IN (?)", r.Database.Table("user_albums").Select("user_albums.album_id").Where("user_id = ?", user.ID)).
Order("YEAR(media.date_shot) DESC").
Order("MONTH(media.date_shot) DESC").
Order("DAY(media.date_shot) DESC").
Order("albums.title ASC")
transactionError := r.Database.Transaction(func(tx *gorm.DB) error { if fromDate != nil {
// album_id, year, month, day query = query.Where("media.date_shot < ?", fromDate)
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 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
if err := query.Find(&media).Error; err != nil {
return nil, err
}
return media, nil
} }

View File

@ -63,7 +63,15 @@ type Query {
"Get a list of media by their ids, user must own the media or be admin" "Get a list of media by their ids, user must own the media or be admin"
mediaList(ids: [ID!]!): [Media!]! mediaList(ids: [ID!]!): [Media!]!
myTimeline(paginate: Pagination, onlyFavorites: Boolean): [TimelineGroup!]! @isAuthorized """
Get a list of media, ordered first by day, then by album if multiple media was found for the same day.
"""
myTimeline(
paginate: Pagination,
onlyFavorites: Boolean,
"Only fetch media that is older than this date"
fromDate: Time
): [Media!]! @isAuthorized
"Get media owned by the logged in user, returned in GeoJson format" "Get media owned by the logged in user, returned in GeoJson format"
myMediaGeoJson: Any! @isAuthorized myMediaGeoJson: Any! @isAuthorized
@ -318,6 +326,8 @@ type Media {
videoMetadata: VideoMetadata videoMetadata: VideoMetadata
favorite: Boolean! favorite: Boolean!
type: MediaType! type: MediaType!
"The date the image was shot or the date it was imported as a fallback"
date: Time!
shares: [ShareToken!]! shares: [ShareToken!]!
downloads: [MediaDownload!]! downloads: [MediaDownload!]!

697
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -63,7 +63,7 @@
"lint:types": "tsc --noemit", "lint:types": "tsc --noemit",
"jest": "craco test --setupFilesAfterEnv ./testing/setupTests.ts", "jest": "craco test --setupFilesAfterEnv ./testing/setupTests.ts",
"jest:ci": "CI=true craco test --setupFilesAfterEnv ./testing/setupTests.ts --verbose --ci --coverage", "jest:ci": "CI=true craco test --setupFilesAfterEnv ./testing/setupTests.ts --verbose --ci --coverage",
"genSchemaTypes": "npx apollo client:codegen --target=typescript --globalTypesFile=src/__generated__/globalTypes.ts", "genSchemaTypes": "apollo client:codegen --target=typescript --globalTypesFile=src/__generated__/globalTypes.ts",
"extractTranslations": "i18next -c i18next-parser.config.js", "extractTranslations": "i18next -c i18next-parser.config.js",
"prepare": "(cd .. && npx husky install)" "prepare": "(cd .. && npx husky install)"
}, },
@ -75,7 +75,9 @@
"husky": "^6.0.0", "husky": "^6.0.0",
"i18next-parser": "^4.2.0", "i18next-parser": "^4.2.0",
"lint-staged": "^11.0.1", "lint-staged": "^11.0.1",
"tsc-files": "^1.1.2" "tsc-files": "^1.1.2",
"apollo": "2.33.4",
"apollo-language-server": "1.26.3"
}, },
"prettier": { "prettier": {
"trailingComma": "es5", "trailingComma": "es5",

View File

@ -3,102 +3,102 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { OrderDirection, MediaType } from './../../../__generated__/globalTypes' import { OrderDirection, MediaType } from "./../../../__generated__/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: albumQuery // GraphQL query operation: albumQuery
// ==================================================== // ====================================================
export interface albumQuery_album_subAlbums_thumbnail_thumbnail { export interface albumQuery_album_subAlbums_thumbnail_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
} }
export interface albumQuery_album_subAlbums_thumbnail { export interface albumQuery_album_subAlbums_thumbnail {
__typename: 'Media' __typename: "Media";
id: string id: string;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: albumQuery_album_subAlbums_thumbnail_thumbnail | null thumbnail: albumQuery_album_subAlbums_thumbnail_thumbnail | null;
} }
export interface albumQuery_album_subAlbums { export interface albumQuery_album_subAlbums {
__typename: 'Album' __typename: "Album";
id: string id: string;
title: string title: string;
/** /**
* An image in this album used for previewing this album * An image in this album used for previewing this album
*/ */
thumbnail: albumQuery_album_subAlbums_thumbnail | null thumbnail: albumQuery_album_subAlbums_thumbnail | null;
} }
export interface albumQuery_album_media_thumbnail { export interface albumQuery_album_media_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface albumQuery_album_media_highRes { export interface albumQuery_album_media_highRes {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
} }
export interface albumQuery_album_media_videoWeb { export interface albumQuery_album_media_videoWeb {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
} }
export interface albumQuery_album_media { export interface albumQuery_album_media {
__typename: 'Media' __typename: "Media";
id: string id: string;
type: MediaType type: MediaType;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: albumQuery_album_media_thumbnail | null thumbnail: albumQuery_album_media_thumbnail | null;
/** /**
* URL to display the photo in full resolution, will be null for videos * URL to display the photo in full resolution, will be null for videos
*/ */
highRes: albumQuery_album_media_highRes | null highRes: albumQuery_album_media_highRes | null;
/** /**
* URL to get the video in a web format that can be played in the browser, will be null for photos * URL to get the video in a web format that can be played in the browser, will be null for photos
*/ */
videoWeb: albumQuery_album_media_videoWeb | null videoWeb: albumQuery_album_media_videoWeb | null;
favorite: boolean favorite: boolean;
} }
export interface albumQuery_album { export interface albumQuery_album {
__typename: 'Album' __typename: "Album";
id: string id: string;
title: string title: string;
/** /**
* The albums contained in this album * The albums contained in this album
*/ */
subAlbums: albumQuery_album_subAlbums[] subAlbums: albumQuery_album_subAlbums[];
/** /**
* The media inside this album * The media inside this album
*/ */
media: albumQuery_album_media[] media: albumQuery_album_media[];
} }
export interface albumQuery { export interface albumQuery {
@ -106,14 +106,14 @@ export interface albumQuery {
* Get album by id, user must own the album or be admin * Get album by id, user must own the album or be admin
* If valid tokenCredentials are provided, the album may be retrived without further authentication * If valid tokenCredentials are provided, the album may be retrived without further authentication
*/ */
album: albumQuery_album album: albumQuery_album;
} }
export interface albumQueryVariables { export interface albumQueryVariables {
id: string id: string;
onlyFavorites?: boolean | null onlyFavorites?: boolean | null;
mediaOrderBy?: string | null mediaOrderBy?: string | null;
mediaOrderDirection?: OrderDirection | null mediaOrderDirection?: OrderDirection | null;
limit?: number | null limit?: number | null;
offset?: number | null offset?: number | null;
} }

View File

@ -8,35 +8,35 @@
// ==================================================== // ====================================================
export interface getMyAlbums_myAlbums_thumbnail_thumbnail { export interface getMyAlbums_myAlbums_thumbnail_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
} }
export interface getMyAlbums_myAlbums_thumbnail { export interface getMyAlbums_myAlbums_thumbnail {
__typename: 'Media' __typename: "Media";
id: string id: string;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: getMyAlbums_myAlbums_thumbnail_thumbnail | null thumbnail: getMyAlbums_myAlbums_thumbnail_thumbnail | null;
} }
export interface getMyAlbums_myAlbums { export interface getMyAlbums_myAlbums {
__typename: 'Album' __typename: "Album";
id: string id: string;
title: string title: string;
/** /**
* An image in this album used for previewing this album * An image in this album used for previewing this album
*/ */
thumbnail: getMyAlbums_myAlbums_thumbnail | null thumbnail: getMyAlbums_myAlbums_thumbnail | null;
} }
export interface getMyAlbums { export interface getMyAlbums {
/** /**
* List of albums owned by the logged in user. * List of albums owned by the logged in user.
*/ */
myAlbums: getMyAlbums_myAlbums[] myAlbums: getMyAlbums_myAlbums[];
} }

View File

@ -8,13 +8,13 @@
// ==================================================== // ====================================================
export interface CheckInitialSetup_siteInfo { export interface CheckInitialSetup_siteInfo {
__typename: 'SiteInfo' __typename: "SiteInfo";
/** /**
* Whether or not the initial setup wizard should be shown * Whether or not the initial setup wizard should be shown
*/ */
initialSetup: boolean initialSetup: boolean;
} }
export interface CheckInitialSetup { export interface CheckInitialSetup {
siteInfo: CheckInitialSetup_siteInfo siteInfo: CheckInitialSetup_siteInfo;
} }

View File

@ -3,80 +3,80 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { MediaType } from './../../../../__generated__/globalTypes' import { MediaType } from "./../../../../__generated__/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: singleFaceGroup // GraphQL query operation: singleFaceGroup
// ==================================================== // ====================================================
export interface singleFaceGroup_faceGroup_imageFaces_rectangle { export interface singleFaceGroup_faceGroup_imageFaces_rectangle {
__typename: 'FaceRectangle' __typename: "FaceRectangle";
minX: number minX: number;
maxX: number maxX: number;
minY: number minY: number;
maxY: number maxY: number;
} }
export interface singleFaceGroup_faceGroup_imageFaces_media_thumbnail { export interface singleFaceGroup_faceGroup_imageFaces_media_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface singleFaceGroup_faceGroup_imageFaces_media_highRes { export interface singleFaceGroup_faceGroup_imageFaces_media_highRes {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
} }
export interface singleFaceGroup_faceGroup_imageFaces_media { export interface singleFaceGroup_faceGroup_imageFaces_media {
__typename: 'Media' __typename: "Media";
id: string id: string;
type: MediaType type: MediaType;
title: string title: string;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: singleFaceGroup_faceGroup_imageFaces_media_thumbnail | null thumbnail: singleFaceGroup_faceGroup_imageFaces_media_thumbnail | null;
/** /**
* URL to display the photo in full resolution, will be null for videos * URL to display the photo in full resolution, will be null for videos
*/ */
highRes: singleFaceGroup_faceGroup_imageFaces_media_highRes | null highRes: singleFaceGroup_faceGroup_imageFaces_media_highRes | null;
favorite: boolean favorite: boolean;
} }
export interface singleFaceGroup_faceGroup_imageFaces { export interface singleFaceGroup_faceGroup_imageFaces {
__typename: 'ImageFace' __typename: "ImageFace";
id: string id: string;
rectangle: singleFaceGroup_faceGroup_imageFaces_rectangle rectangle: singleFaceGroup_faceGroup_imageFaces_rectangle;
media: singleFaceGroup_faceGroup_imageFaces_media media: singleFaceGroup_faceGroup_imageFaces_media;
} }
export interface singleFaceGroup_faceGroup { export interface singleFaceGroup_faceGroup {
__typename: 'FaceGroup' __typename: "FaceGroup";
id: string id: string;
label: string | null label: string | null;
imageFaces: singleFaceGroup_faceGroup_imageFaces[] imageFaces: singleFaceGroup_faceGroup_imageFaces[];
} }
export interface singleFaceGroup { export interface singleFaceGroup {
faceGroup: singleFaceGroup_faceGroup faceGroup: singleFaceGroup_faceGroup;
} }
export interface singleFaceGroupVariables { export interface singleFaceGroupVariables {
id: string id: string;
limit: number limit: number;
offset: number offset: number;
} }

View File

@ -8,59 +8,59 @@
// ==================================================== // ====================================================
export interface myFaces_myFaceGroups_imageFaces_rectangle { export interface myFaces_myFaceGroups_imageFaces_rectangle {
__typename: 'FaceRectangle' __typename: "FaceRectangle";
minX: number minX: number;
maxX: number maxX: number;
minY: number minY: number;
maxY: number maxY: number;
} }
export interface myFaces_myFaceGroups_imageFaces_media_thumbnail { export interface myFaces_myFaceGroups_imageFaces_media_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface myFaces_myFaceGroups_imageFaces_media { export interface myFaces_myFaceGroups_imageFaces_media {
__typename: 'Media' __typename: "Media";
id: string id: string;
title: string title: string;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: myFaces_myFaceGroups_imageFaces_media_thumbnail | null thumbnail: myFaces_myFaceGroups_imageFaces_media_thumbnail | null;
} }
export interface myFaces_myFaceGroups_imageFaces { export interface myFaces_myFaceGroups_imageFaces {
__typename: 'ImageFace' __typename: "ImageFace";
id: string id: string;
rectangle: myFaces_myFaceGroups_imageFaces_rectangle rectangle: myFaces_myFaceGroups_imageFaces_rectangle;
media: myFaces_myFaceGroups_imageFaces_media media: myFaces_myFaceGroups_imageFaces_media;
} }
export interface myFaces_myFaceGroups { export interface myFaces_myFaceGroups {
__typename: 'FaceGroup' __typename: "FaceGroup";
id: string id: string;
label: string | null label: string | null;
imageFaceCount: number imageFaceCount: number;
imageFaces: myFaces_myFaceGroups_imageFaces[] imageFaces: myFaces_myFaceGroups_imageFaces[];
} }
export interface myFaces { export interface myFaces {
myFaceGroups: myFaces_myFaceGroups[] myFaceGroups: myFaces_myFaceGroups[];
} }
export interface myFacesVariables { export interface myFacesVariables {
limit?: number | null limit?: number | null;
offset?: number | null offset?: number | null;
} }

View File

@ -3,86 +3,86 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { MediaType } from './../../../__generated__/globalTypes' import { MediaType } from "./../../../__generated__/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: placePageQueryMedia // GraphQL query operation: placePageQueryMedia
// ==================================================== // ====================================================
export interface placePageQueryMedia_mediaList_thumbnail { export interface placePageQueryMedia_mediaList_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface placePageQueryMedia_mediaList_highRes { export interface placePageQueryMedia_mediaList_highRes {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface placePageQueryMedia_mediaList_videoWeb { export interface placePageQueryMedia_mediaList_videoWeb {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface placePageQueryMedia_mediaList { export interface placePageQueryMedia_mediaList {
__typename: 'Media' __typename: "Media";
id: string id: string;
title: string title: string;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: placePageQueryMedia_mediaList_thumbnail | null thumbnail: placePageQueryMedia_mediaList_thumbnail | null;
/** /**
* URL to display the photo in full resolution, will be null for videos * URL to display the photo in full resolution, will be null for videos
*/ */
highRes: placePageQueryMedia_mediaList_highRes | null highRes: placePageQueryMedia_mediaList_highRes | null;
/** /**
* URL to get the video in a web format that can be played in the browser, will be null for photos * URL to get the video in a web format that can be played in the browser, will be null for photos
*/ */
videoWeb: placePageQueryMedia_mediaList_videoWeb | null videoWeb: placePageQueryMedia_mediaList_videoWeb | null;
type: MediaType type: MediaType;
} }
export interface placePageQueryMedia { export interface placePageQueryMedia {
/** /**
* Get a list of media by their ids, user must own the media or be admin * Get a list of media by their ids, user must own the media or be admin
*/ */
mediaList: placePageQueryMedia_mediaList[] mediaList: placePageQueryMedia_mediaList[];
} }
export interface placePageQueryMediaVariables { export interface placePageQueryMediaVariables {
mediaIDs: string[] mediaIDs: string[];
} }

View File

@ -8,15 +8,15 @@
// ==================================================== // ====================================================
export interface changeUserPassword_updateUser { export interface changeUserPassword_updateUser {
__typename: 'User' __typename: "User";
id: string id: string;
} }
export interface changeUserPassword { export interface changeUserPassword {
updateUser: changeUserPassword_updateUser updateUser: changeUserPassword_updateUser;
} }
export interface changeUserPasswordVariables { export interface changeUserPasswordVariables {
userId: string userId: string;
password: string password: string;
} }

View File

@ -8,17 +8,17 @@
// ==================================================== // ====================================================
export interface createUser_createUser { export interface createUser_createUser {
__typename: 'User' __typename: "User";
id: string id: string;
username: string username: string;
admin: boolean admin: boolean;
} }
export interface createUser { export interface createUser {
createUser: createUser_createUser createUser: createUser_createUser;
} }
export interface createUserVariables { export interface createUserVariables {
username: string username: string;
admin: boolean admin: boolean;
} }

View File

@ -8,15 +8,15 @@
// ==================================================== // ====================================================
export interface deleteUser_deleteUser { export interface deleteUser_deleteUser {
__typename: 'User' __typename: "User";
id: string id: string;
username: string username: string;
} }
export interface deleteUser { export interface deleteUser {
deleteUser: deleteUser_deleteUser deleteUser: deleteUser_deleteUser;
} }
export interface deleteUserVariables { export interface deleteUserVariables {
id: string id: string;
} }

View File

@ -8,18 +8,18 @@
// ==================================================== // ====================================================
export interface updateUser_updateUser { export interface updateUser_updateUser {
__typename: 'User' __typename: "User";
id: string id: string;
username: string username: string;
admin: boolean admin: boolean;
} }
export interface updateUser { export interface updateUser {
updateUser: updateUser_updateUser updateUser: updateUser_updateUser;
} }
export interface updateUserVariables { export interface updateUserVariables {
id: string id: string;
username?: string | null username?: string | null;
admin?: boolean | null admin?: boolean | null;
} }

View File

@ -3,22 +3,22 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { LanguageTranslation } from './../../../__generated__/globalTypes' import { LanguageTranslation } from "./../../../__generated__/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: changeUserPreferences // GraphQL mutation operation: changeUserPreferences
// ==================================================== // ====================================================
export interface changeUserPreferences_changeUserPreferences { export interface changeUserPreferences_changeUserPreferences {
__typename: 'UserPreferences' __typename: "UserPreferences";
id: string id: string;
language: LanguageTranslation | null language: LanguageTranslation | null;
} }
export interface changeUserPreferences { export interface changeUserPreferences {
changeUserPreferences: changeUserPreferences_changeUserPreferences changeUserPreferences: changeUserPreferences_changeUserPreferences;
} }
export interface changeUserPreferencesVariables { export interface changeUserPreferencesVariables {
language?: string | null language?: string | null;
} }

View File

@ -3,18 +3,18 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { LanguageTranslation } from './../../../__generated__/globalTypes' import { LanguageTranslation } from "./../../../__generated__/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: myUserPreferences // GraphQL query operation: myUserPreferences
// ==================================================== // ====================================================
export interface myUserPreferences_myUserPreferences { export interface myUserPreferences_myUserPreferences {
__typename: 'UserPreferences' __typename: "UserPreferences";
id: string id: string;
language: LanguageTranslation | null language: LanguageTranslation | null;
} }
export interface myUserPreferences { export interface myUserPreferences {
myUserPreferences: myUserPreferences_myUserPreferences myUserPreferences: myUserPreferences_myUserPreferences;
} }

View File

@ -3,172 +3,172 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { MediaType } from './../../../__generated__/globalTypes' import { MediaType } from "./../../../__generated__/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: SharePageToken // GraphQL query operation: SharePageToken
// ==================================================== // ====================================================
export interface SharePageToken_shareToken_album { export interface SharePageToken_shareToken_album {
__typename: 'Album' __typename: "Album";
id: string id: string;
} }
export interface SharePageToken_shareToken_media_thumbnail { export interface SharePageToken_shareToken_media_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface SharePageToken_shareToken_media_downloads_mediaUrl { export interface SharePageToken_shareToken_media_downloads_mediaUrl {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
/** /**
* The file size of the resource in bytes * The file size of the resource in bytes
*/ */
fileSize: number fileSize: number;
} }
export interface SharePageToken_shareToken_media_downloads { export interface SharePageToken_shareToken_media_downloads {
__typename: 'MediaDownload' __typename: "MediaDownload";
title: string title: string;
mediaUrl: SharePageToken_shareToken_media_downloads_mediaUrl mediaUrl: SharePageToken_shareToken_media_downloads_mediaUrl;
} }
export interface SharePageToken_shareToken_media_highRes { export interface SharePageToken_shareToken_media_highRes {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface SharePageToken_shareToken_media_videoWeb { export interface SharePageToken_shareToken_media_videoWeb {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface SharePageToken_shareToken_media_exif { export interface SharePageToken_shareToken_media_exif {
__typename: 'MediaEXIF' __typename: "MediaEXIF";
id: string id: string;
/** /**
* The model name of the camera * The model name of the camera
*/ */
camera: string | null camera: string | null;
/** /**
* The maker of the camera * The maker of the camera
*/ */
maker: string | null maker: string | null;
/** /**
* The name of the lens * The name of the lens
*/ */
lens: string | null lens: string | null;
dateShot: any | null dateShot: any | null;
/** /**
* The exposure time of the image * The exposure time of the image
*/ */
exposure: number | null exposure: number | null;
/** /**
* The aperature stops of the image * The aperature stops of the image
*/ */
aperture: number | null aperture: number | null;
/** /**
* The ISO setting of the image * The ISO setting of the image
*/ */
iso: number | null iso: number | null;
/** /**
* The focal length of the lens, when the image was taken * The focal length of the lens, when the image was taken
*/ */
focalLength: number | null focalLength: number | null;
/** /**
* A formatted description of the flash settings, when the image was taken * A formatted description of the flash settings, when the image was taken
*/ */
flash: number | null flash: number | null;
/** /**
* An index describing the mode for adjusting the exposure of the image * An index describing the mode for adjusting the exposure of the image
*/ */
exposureProgram: number | null exposureProgram: number | null;
} }
export interface SharePageToken_shareToken_media { export interface SharePageToken_shareToken_media {
__typename: 'Media' __typename: "Media";
id: string id: string;
title: string title: string;
type: MediaType type: MediaType;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: SharePageToken_shareToken_media_thumbnail | null thumbnail: SharePageToken_shareToken_media_thumbnail | null;
downloads: SharePageToken_shareToken_media_downloads[] downloads: SharePageToken_shareToken_media_downloads[];
/** /**
* URL to display the photo in full resolution, will be null for videos * URL to display the photo in full resolution, will be null for videos
*/ */
highRes: SharePageToken_shareToken_media_highRes | null highRes: SharePageToken_shareToken_media_highRes | null;
/** /**
* URL to get the video in a web format that can be played in the browser, will be null for photos * URL to get the video in a web format that can be played in the browser, will be null for photos
*/ */
videoWeb: SharePageToken_shareToken_media_videoWeb | null videoWeb: SharePageToken_shareToken_media_videoWeb | null;
exif: SharePageToken_shareToken_media_exif | null exif: SharePageToken_shareToken_media_exif | null;
} }
export interface SharePageToken_shareToken { export interface SharePageToken_shareToken {
__typename: 'ShareToken' __typename: "ShareToken";
token: string token: string;
/** /**
* The album this token shares * The album this token shares
*/ */
album: SharePageToken_shareToken_album | null album: SharePageToken_shareToken_album | null;
/** /**
* The media this token shares * The media this token shares
*/ */
media: SharePageToken_shareToken_media | null media: SharePageToken_shareToken_media | null;
} }
export interface SharePageToken { export interface SharePageToken {
shareToken: SharePageToken_shareToken shareToken: SharePageToken_shareToken;
} }
export interface SharePageTokenVariables { export interface SharePageTokenVariables {
token: string token: string;
password?: string | null password?: string | null;
} }

View File

@ -3,179 +3,179 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { OrderDirection, MediaType } from './../../../__generated__/globalTypes' import { OrderDirection, MediaType } from "./../../../__generated__/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: shareAlbumQuery // GraphQL query operation: shareAlbumQuery
// ==================================================== // ====================================================
export interface shareAlbumQuery_album_subAlbums_thumbnail_thumbnail { export interface shareAlbumQuery_album_subAlbums_thumbnail_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
} }
export interface shareAlbumQuery_album_subAlbums_thumbnail { export interface shareAlbumQuery_album_subAlbums_thumbnail {
__typename: 'Media' __typename: "Media";
id: string id: string;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: shareAlbumQuery_album_subAlbums_thumbnail_thumbnail | null thumbnail: shareAlbumQuery_album_subAlbums_thumbnail_thumbnail | null;
} }
export interface shareAlbumQuery_album_subAlbums { export interface shareAlbumQuery_album_subAlbums {
__typename: 'Album' __typename: "Album";
id: string id: string;
title: string title: string;
/** /**
* An image in this album used for previewing this album * An image in this album used for previewing this album
*/ */
thumbnail: shareAlbumQuery_album_subAlbums_thumbnail | null thumbnail: shareAlbumQuery_album_subAlbums_thumbnail | null;
} }
export interface shareAlbumQuery_album_media_thumbnail { export interface shareAlbumQuery_album_media_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface shareAlbumQuery_album_media_downloads_mediaUrl { export interface shareAlbumQuery_album_media_downloads_mediaUrl {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
/** /**
* The file size of the resource in bytes * The file size of the resource in bytes
*/ */
fileSize: number fileSize: number;
} }
export interface shareAlbumQuery_album_media_downloads { export interface shareAlbumQuery_album_media_downloads {
__typename: 'MediaDownload' __typename: "MediaDownload";
title: string title: string;
mediaUrl: shareAlbumQuery_album_media_downloads_mediaUrl mediaUrl: shareAlbumQuery_album_media_downloads_mediaUrl;
} }
export interface shareAlbumQuery_album_media_highRes { export interface shareAlbumQuery_album_media_highRes {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface shareAlbumQuery_album_media_videoWeb { export interface shareAlbumQuery_album_media_videoWeb {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
} }
export interface shareAlbumQuery_album_media_exif { export interface shareAlbumQuery_album_media_exif {
__typename: 'MediaEXIF' __typename: "MediaEXIF";
/** /**
* The model name of the camera * The model name of the camera
*/ */
camera: string | null camera: string | null;
/** /**
* The maker of the camera * The maker of the camera
*/ */
maker: string | null maker: string | null;
/** /**
* The name of the lens * The name of the lens
*/ */
lens: string | null lens: string | null;
dateShot: any | null dateShot: any | null;
/** /**
* The exposure time of the image * The exposure time of the image
*/ */
exposure: number | null exposure: number | null;
/** /**
* The aperature stops of the image * The aperature stops of the image
*/ */
aperture: number | null aperture: number | null;
/** /**
* The ISO setting of the image * The ISO setting of the image
*/ */
iso: number | null iso: number | null;
/** /**
* The focal length of the lens, when the image was taken * The focal length of the lens, when the image was taken
*/ */
focalLength: number | null focalLength: number | null;
/** /**
* A formatted description of the flash settings, when the image was taken * A formatted description of the flash settings, when the image was taken
*/ */
flash: number | null flash: number | null;
/** /**
* An index describing the mode for adjusting the exposure of the image * An index describing the mode for adjusting the exposure of the image
*/ */
exposureProgram: number | null exposureProgram: number | null;
} }
export interface shareAlbumQuery_album_media { export interface shareAlbumQuery_album_media {
__typename: 'Media' __typename: "Media";
id: string id: string;
title: string title: string;
type: MediaType type: MediaType;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: shareAlbumQuery_album_media_thumbnail | null thumbnail: shareAlbumQuery_album_media_thumbnail | null;
downloads: shareAlbumQuery_album_media_downloads[] downloads: shareAlbumQuery_album_media_downloads[];
/** /**
* URL to display the photo in full resolution, will be null for videos * URL to display the photo in full resolution, will be null for videos
*/ */
highRes: shareAlbumQuery_album_media_highRes | null highRes: shareAlbumQuery_album_media_highRes | null;
/** /**
* URL to get the video in a web format that can be played in the browser, will be null for photos * URL to get the video in a web format that can be played in the browser, will be null for photos
*/ */
videoWeb: shareAlbumQuery_album_media_videoWeb | null videoWeb: shareAlbumQuery_album_media_videoWeb | null;
exif: shareAlbumQuery_album_media_exif | null exif: shareAlbumQuery_album_media_exif | null;
} }
export interface shareAlbumQuery_album { export interface shareAlbumQuery_album {
__typename: 'Album' __typename: "Album";
id: string id: string;
title: string title: string;
/** /**
* The albums contained in this album * The albums contained in this album
*/ */
subAlbums: shareAlbumQuery_album_subAlbums[] subAlbums: shareAlbumQuery_album_subAlbums[];
/** /**
* The media inside this album * The media inside this album
*/ */
media: shareAlbumQuery_album_media[] media: shareAlbumQuery_album_media[];
} }
export interface shareAlbumQuery { export interface shareAlbumQuery {
@ -183,15 +183,15 @@ export interface shareAlbumQuery {
* Get album by id, user must own the album or be admin * Get album by id, user must own the album or be admin
* If valid tokenCredentials are provided, the album may be retrived without further authentication * If valid tokenCredentials are provided, the album may be retrived without further authentication
*/ */
album: shareAlbumQuery_album album: shareAlbumQuery_album;
} }
export interface shareAlbumQueryVariables { export interface shareAlbumQueryVariables {
id: string id: string;
token: string token: string;
password?: string | null password?: string | null;
mediaOrderBy?: string | null mediaOrderBy?: string | null;
mediaOrderDirection?: OrderDirection | null mediaOrderDirection?: OrderDirection | null;
limit?: number | null limit?: number | null;
offset?: number | null offset?: number | null;
} }

View File

@ -1,18 +1,18 @@
import React from 'react' import React from 'react'
import Layout from '../../components/layout/Layout' import Layout from '../../components/layout/Layout'
import TimelineGallery from '../../components/timelineGallery/TimelineGallery'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import TimelineGallery from '../../components/timelineGallery/TimelineGallery'
const PhotosPage = () => { const TimelinePage = () => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<> <>
<Layout title={t('photos_page.title', 'Photos')}> <Layout title={t('photos_page.title', 'Timeline')}>
<TimelineGallery /> <TimelineGallery />
</Layout> </Layout>
</> </>
) )
} }
export default PhotosPage export default TimelinePage

View File

@ -8,34 +8,34 @@
//============================================================== //==============================================================
export enum LanguageTranslation { export enum LanguageTranslation {
Danish = 'Danish', Danish = "Danish",
English = 'English', English = "English",
French = 'French', French = "French",
German = 'German', German = "German",
Italian = 'Italian', Italian = "Italian",
Polish = 'Polish', Polish = "Polish",
Portuguese = 'Portuguese', Portuguese = "Portuguese",
Russian = 'Russian', Russian = "Russian",
SimplifiedChinese = 'SimplifiedChinese', SimplifiedChinese = "SimplifiedChinese",
Spanish = 'Spanish', Spanish = "Spanish",
Swedish = 'Swedish', Swedish = "Swedish",
TraditionalChinese = 'TraditionalChinese', TraditionalChinese = "TraditionalChinese",
} }
export enum MediaType { export enum MediaType {
Photo = 'Photo', Photo = "Photo",
Video = 'Video', Video = "Video",
} }
export enum NotificationType { export enum NotificationType {
Close = 'Close', Close = "Close",
Message = 'Message', Message = "Message",
Progress = 'Progress', Progress = "Progress",
} }
export enum OrderDirection { export enum OrderDirection {
ASC = 'ASC', ASC = "ASC",
DESC = 'DESC', DESC = "DESC",
} }
//============================================================== //==============================================================

View File

@ -3,18 +3,18 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { LanguageTranslation } from './globalTypes' import { LanguageTranslation } from "./globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: siteTranslation // GraphQL query operation: siteTranslation
// ==================================================== // ====================================================
export interface siteTranslation_myUserPreferences { export interface siteTranslation_myUserPreferences {
__typename: 'UserPreferences' __typename: "UserPreferences";
id: string id: string;
language: LanguageTranslation | null language: LanguageTranslation | null;
} }
export interface siteTranslation { export interface siteTranslation {
myUserPreferences: siteTranslation_myUserPreferences myUserPreferences: siteTranslation_myUserPreferences;
} }

View File

@ -10,7 +10,7 @@ import { ReactComponent as DirectionIcon } from './icons/direction-arrow.svg'
import Dropdown from '../../primitives/form/Dropdown' import Dropdown from '../../primitives/form/Dropdown'
type FavoriteCheckboxProps = { export type FavoriteCheckboxProps = {
onlyFavorites: boolean onlyFavorites: boolean
setOnlyFavorites(favorites: boolean): void setOnlyFavorites(favorites: boolean): void
} }
@ -79,7 +79,7 @@ const SortingOptions = ({ setOrdering, ordering }: SortingOptionsProps) => {
<fieldset> <fieldset>
<legend id="filter_group_sort-label" className="inline-block mb-1"> <legend id="filter_group_sort-label" className="inline-block mb-1">
<SortingIcon <SortingIcon
className="inline-block align-baseline mr-1" className="inline-block align-baseline mr-1 mt-1"
aria-hidden="true" aria-hidden="true"
/> />
<span>{t('album_filter.sort', 'Sort')}</span> <span>{t('album_filter.sort', 'Sort')}</span>

View File

@ -8,15 +8,15 @@
// ==================================================== // ====================================================
export interface albumPathQuery_album_path { export interface albumPathQuery_album_path {
__typename: 'Album' __typename: "Album";
id: string id: string;
title: string title: string;
} }
export interface albumPathQuery_album { export interface albumPathQuery_album {
__typename: 'Album' __typename: "Album";
id: string id: string;
path: albumPathQuery_album_path[] path: albumPathQuery_album_path[];
} }
export interface albumPathQuery { export interface albumPathQuery {
@ -24,9 +24,9 @@ export interface albumPathQuery {
* Get album by id, user must own the album or be admin * Get album by id, user must own the album or be admin
* If valid tokenCredentials are provided, the album may be retrived without further authentication * If valid tokenCredentials are provided, the album may be retrived without further authentication
*/ */
album: albumPathQuery_album album: albumPathQuery_album;
} }
export interface albumPathQueryVariables { export interface albumPathQueryVariables {
id: string id: string;
} }

View File

@ -80,7 +80,7 @@ export const MainMenu = () => {
<div className="fixed w-full bottom-0 lg:bottom-auto lg:top-[84px] z-30 bg-white shadow-separator lg:shadow-none lg:w-[240px] lg:ml-8 lg:mr-5 flex-shrink-0"> <div className="fixed w-full bottom-0 lg:bottom-auto lg:top-[84px] z-30 bg-white shadow-separator lg:shadow-none lg:w-[240px] lg:ml-8 lg:mr-5 flex-shrink-0">
<ul className="flex justify-around py-2 px-2 max-w-lg mx-auto lg:flex-col lg:p-0"> <ul className="flex justify-around py-2 px-2 max-w-lg mx-auto lg:flex-col lg:p-0">
<MenuButton <MenuButton
to="/photos" to="/timeline"
exact exact
label={t('sidemenu.photos', 'Timeline')} label={t('sidemenu.photos', 'Timeline')}
background="#8ac5f4" background="#8ac5f4"

View File

@ -8,13 +8,13 @@
// ==================================================== // ====================================================
export interface adminQuery_myUser { export interface adminQuery_myUser {
__typename: 'User' __typename: "User";
admin: boolean admin: boolean;
} }
export interface adminQuery { export interface adminQuery {
/** /**
* Information about the currently logged in user * Information about the currently logged in user
*/ */
myUser: adminQuery_myUser myUser: adminQuery_myUser;
} }

View File

@ -8,13 +8,13 @@
// ==================================================== // ====================================================
export interface faceDetectionEnabled_siteInfo { export interface faceDetectionEnabled_siteInfo {
__typename: 'SiteInfo' __typename: "SiteInfo";
/** /**
* Whether or not face detection is enabled and working * Whether or not face detection is enabled and working
*/ */
faceDetectionEnabled: boolean faceDetectionEnabled: boolean;
} }
export interface faceDetectionEnabled { export interface faceDetectionEnabled {
siteInfo: faceDetectionEnabled_siteInfo siteInfo: faceDetectionEnabled_siteInfo;
} }

View File

@ -11,5 +11,5 @@ export interface mapboxEnabledQuery {
/** /**
* Get the mapbox api token, returns null if mapbox is not enabled * Get the mapbox api token, returns null if mapbox is not enabled
*/ */
mapboxToken: string | null mapboxToken: string | null;
} }

View File

@ -3,27 +3,27 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { NotificationType } from './../../../__generated__/globalTypes' import { NotificationType } from "./../../../__generated__/globalTypes";
// ==================================================== // ====================================================
// GraphQL subscription operation: notificationSubscription // GraphQL subscription operation: notificationSubscription
// ==================================================== // ====================================================
export interface notificationSubscription_notification { export interface notificationSubscription_notification {
__typename: 'Notification' __typename: "Notification";
key: string key: string;
type: NotificationType type: NotificationType;
header: string header: string;
content: string content: string;
progress: number | null progress: number | null;
positive: boolean positive: boolean;
negative: boolean negative: boolean;
/** /**
* Time in milliseconds before the notification will close * Time in milliseconds before the notification will close
*/ */
timeout: number | null timeout: number | null;
} }
export interface notificationSubscription { export interface notificationSubscription {
notification: notificationSubscription_notification notification: notificationSubscription_notification;
} }

View File

@ -30,7 +30,7 @@ const Gallery = styled.div`
} }
` `
const PhotoFiller = styled.div` export const PhotoFiller = styled.div`
height: 200px; height: 200px;
flex-grow: 999999; flex-grow: 999999;
` `

View File

@ -8,19 +8,19 @@
// ==================================================== // ====================================================
export interface markMediaFavorite_favoriteMedia { export interface markMediaFavorite_favoriteMedia {
__typename: 'Media' __typename: "Media";
id: string id: string;
favorite: boolean favorite: boolean;
} }
export interface markMediaFavorite { export interface markMediaFavorite {
/** /**
* Mark or unmark a media as being a favorite * Mark or unmark a media as being a favorite
*/ */
favoriteMedia: markMediaFavorite_favoriteMedia favoriteMedia: markMediaFavorite_favoriteMedia;
} }
export interface markMediaFavoriteVariables { export interface markMediaFavoriteVariables {
mediaId: string mediaId: string;
favorite: boolean favorite: boolean;
} }

View File

@ -12,7 +12,9 @@ const AlbumsPage = React.lazy(
() => import('../../Pages/AllAlbumsPage/AlbumsPage') () => import('../../Pages/AllAlbumsPage/AlbumsPage')
) )
const AlbumPage = React.lazy(() => import('../../Pages/AlbumPage/AlbumPage')) const AlbumPage = React.lazy(() => import('../../Pages/AlbumPage/AlbumPage'))
const PhotosPage = React.lazy(() => import('../../Pages/PhotosPage/PhotosPage')) const TimelinePage = React.lazy(
() => import('../../Pages/TimelinePage/TimelinePage')
)
const PlacesPage = React.lazy(() => import('../../Pages/PlacesPage/PlacesPage')) const PlacesPage = React.lazy(() => import('../../Pages/PlacesPage/PlacesPage'))
const SharePage = React.lazy(() => import('../../Pages/SharePage/SharePage')) const SharePage = React.lazy(() => import('../../Pages/SharePage/SharePage'))
const PeoplePage = React.lazy(() => import('../../Pages/PeoplePage/PeoplePage')) const PeoplePage = React.lazy(() => import('../../Pages/PeoplePage/PeoplePage'))
@ -49,11 +51,17 @@ const Routes = () => {
<Route path="/share" component={SharePage} /> <Route path="/share" component={SharePage} />
<AuthorizedRoute exact path="/albums" component={AlbumsPage} /> <AuthorizedRoute exact path="/albums" component={AlbumsPage} />
<AuthorizedRoute path="/album/:id" component={AlbumPage} /> <AuthorizedRoute path="/album/:id" component={AlbumPage} />
<AuthorizedRoute path="/photos" component={PhotosPage} /> <AuthorizedRoute path="/timeline" component={TimelinePage} />
<AuthorizedRoute path="/places" component={PlacesPage} /> <AuthorizedRoute path="/places" component={PlacesPage} />
<AuthorizedRoute path="/people/:person?" component={PeoplePage} /> <AuthorizedRoute path="/people/:person?" component={PeoplePage} />
<AuthorizedRoute path="/settings" component={SettingsPage} /> <AuthorizedRoute path="/settings" component={SettingsPage} />
<Route path="/" exact render={() => <Redirect to="/photos" />} /> <Route path="/" exact render={() => <Redirect to="/timeline" />} />
{/* For backwards compatibility */}
<Route
path="/photos"
exact
render={() => <Redirect to="/timeline" />}
/>
<Route <Route
render={() => ( render={() => (
<div>{t('routes.page_not_found', 'Page not found')}</div> <div>{t('routes.page_not_found', 'Page not found')}</div>

View File

@ -8,38 +8,38 @@
// ==================================================== // ====================================================
export interface resetAlbumCover_resetAlbumCover_thumbnail_thumbnail { export interface resetAlbumCover_resetAlbumCover_thumbnail_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
} }
export interface resetAlbumCover_resetAlbumCover_thumbnail { export interface resetAlbumCover_resetAlbumCover_thumbnail {
__typename: 'Media' __typename: "Media";
id: string id: string;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: resetAlbumCover_resetAlbumCover_thumbnail_thumbnail | null thumbnail: resetAlbumCover_resetAlbumCover_thumbnail_thumbnail | null;
} }
export interface resetAlbumCover_resetAlbumCover { export interface resetAlbumCover_resetAlbumCover {
__typename: 'Album' __typename: "Album";
id: string id: string;
/** /**
* An image in this album used for previewing this album * An image in this album used for previewing this album
*/ */
thumbnail: resetAlbumCover_resetAlbumCover_thumbnail | null thumbnail: resetAlbumCover_resetAlbumCover_thumbnail | null;
} }
export interface resetAlbumCover { export interface resetAlbumCover {
/** /**
* Reset the assigned cover photo for an album * Reset the assigned cover photo for an album
*/ */
resetAlbumCover: resetAlbumCover_resetAlbumCover resetAlbumCover: resetAlbumCover_resetAlbumCover;
} }
export interface resetAlbumCoverVariables { export interface resetAlbumCoverVariables {
albumID: string albumID: string;
} }

View File

@ -8,38 +8,38 @@
// ==================================================== // ====================================================
export interface setAlbumCover_setAlbumCover_thumbnail_thumbnail { export interface setAlbumCover_setAlbumCover_thumbnail_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
} }
export interface setAlbumCover_setAlbumCover_thumbnail { export interface setAlbumCover_setAlbumCover_thumbnail {
__typename: 'Media' __typename: "Media";
id: string id: string;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: setAlbumCover_setAlbumCover_thumbnail_thumbnail | null thumbnail: setAlbumCover_setAlbumCover_thumbnail_thumbnail | null;
} }
export interface setAlbumCover_setAlbumCover { export interface setAlbumCover_setAlbumCover {
__typename: 'Album' __typename: "Album";
id: string id: string;
/** /**
* An image in this album used for previewing this album * An image in this album used for previewing this album
*/ */
thumbnail: setAlbumCover_setAlbumCover_thumbnail | null thumbnail: setAlbumCover_setAlbumCover_thumbnail | null;
} }
export interface setAlbumCover { export interface setAlbumCover {
/** /**
* Assign a cover photo to an album * Assign a cover photo to an album
*/ */
setAlbumCover: setAlbumCover_setAlbumCover setAlbumCover: setAlbumCover_setAlbumCover;
} }
export interface setAlbumCoverVariables { export interface setAlbumCoverVariables {
coverID: string coverID: string;
} }

View File

@ -8,19 +8,19 @@
// ==================================================== // ====================================================
export interface sidebarAlbumAddShare_shareAlbum { export interface sidebarAlbumAddShare_shareAlbum {
__typename: 'ShareToken' __typename: "ShareToken";
token: string token: string;
} }
export interface sidebarAlbumAddShare { export interface sidebarAlbumAddShare {
/** /**
* Generate share token for album * Generate share token for album
*/ */
shareAlbum: sidebarAlbumAddShare_shareAlbum shareAlbum: sidebarAlbumAddShare_shareAlbum;
} }
export interface sidebarAlbumAddShareVariables { export interface sidebarAlbumAddShareVariables {
id: string id: string;
password?: string | null password?: string | null;
expire?: any | null expire?: any | null;
} }

View File

@ -8,19 +8,19 @@
// ==================================================== // ====================================================
export interface sidebarGetAlbumShares_album_shares { export interface sidebarGetAlbumShares_album_shares {
__typename: 'ShareToken' __typename: "ShareToken";
id: string id: string;
token: string token: string;
/** /**
* Whether or not a password is needed to access the share * Whether or not a password is needed to access the share
*/ */
hasPassword: boolean hasPassword: boolean;
} }
export interface sidebarGetAlbumShares_album { export interface sidebarGetAlbumShares_album {
__typename: 'Album' __typename: "Album";
id: string id: string;
shares: sidebarGetAlbumShares_album_shares[] shares: sidebarGetAlbumShares_album_shares[];
} }
export interface sidebarGetAlbumShares { export interface sidebarGetAlbumShares {
@ -28,9 +28,9 @@ export interface sidebarGetAlbumShares {
* Get album by id, user must own the album or be admin * Get album by id, user must own the album or be admin
* If valid tokenCredentials are provided, the album may be retrived without further authentication * If valid tokenCredentials are provided, the album may be retrived without further authentication
*/ */
album: sidebarGetAlbumShares_album album: sidebarGetAlbumShares_album;
} }
export interface sidebarGetAlbumSharesVariables { export interface sidebarGetAlbumSharesVariables {
id: string id: string;
} }

View File

@ -8,19 +8,19 @@
// ==================================================== // ====================================================
export interface sidebarGetPhotoShares_media_shares { export interface sidebarGetPhotoShares_media_shares {
__typename: 'ShareToken' __typename: "ShareToken";
id: string id: string;
token: string token: string;
/** /**
* Whether or not a password is needed to access the share * Whether or not a password is needed to access the share
*/ */
hasPassword: boolean hasPassword: boolean;
} }
export interface sidebarGetPhotoShares_media { export interface sidebarGetPhotoShares_media {
__typename: 'Media' __typename: "Media";
id: string id: string;
shares: sidebarGetPhotoShares_media_shares[] shares: sidebarGetPhotoShares_media_shares[];
} }
export interface sidebarGetPhotoShares { export interface sidebarGetPhotoShares {
@ -28,9 +28,9 @@ export interface sidebarGetPhotoShares {
* Get media by id, user must own the media or be admin. * Get media by id, user must own the media or be admin.
* If valid tokenCredentials are provided, the media may be retrived without further authentication * If valid tokenCredentials are provided, the media may be retrived without further authentication
*/ */
media: sidebarGetPhotoShares_media media: sidebarGetPhotoShares_media;
} }
export interface sidebarGetPhotoSharesVariables { export interface sidebarGetPhotoSharesVariables {
id: string id: string;
} }

View File

@ -3,155 +3,155 @@
// @generated // @generated
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { MediaType } from './../../../__generated__/globalTypes' import { MediaType } from "./../../../__generated__/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: sidebarPhoto // GraphQL query operation: sidebarPhoto
// ==================================================== // ====================================================
export interface sidebarPhoto_media_highRes { export interface sidebarPhoto_media_highRes {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface sidebarPhoto_media_thumbnail { export interface sidebarPhoto_media_thumbnail {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface sidebarPhoto_media_videoWeb { export interface sidebarPhoto_media_videoWeb {
__typename: 'MediaURL' __typename: "MediaURL";
/** /**
* URL for previewing the image * URL for previewing the image
*/ */
url: string url: string;
/** /**
* Width of the image in pixels * Width of the image in pixels
*/ */
width: number width: number;
/** /**
* Height of the image in pixels * Height of the image in pixels
*/ */
height: number height: number;
} }
export interface sidebarPhoto_media_videoMetadata { export interface sidebarPhoto_media_videoMetadata {
__typename: 'VideoMetadata' __typename: "VideoMetadata";
id: string id: string;
width: number width: number;
height: number height: number;
duration: number duration: number;
codec: string | null codec: string | null;
framerate: number | null framerate: number | null;
bitrate: string | null bitrate: string | null;
colorProfile: string | null colorProfile: string | null;
audio: string | null audio: string | null;
} }
export interface sidebarPhoto_media_exif { export interface sidebarPhoto_media_exif {
__typename: 'MediaEXIF' __typename: "MediaEXIF";
id: string id: string;
/** /**
* The model name of the camera * The model name of the camera
*/ */
camera: string | null camera: string | null;
/** /**
* The maker of the camera * The maker of the camera
*/ */
maker: string | null maker: string | null;
/** /**
* The name of the lens * The name of the lens
*/ */
lens: string | null lens: string | null;
dateShot: any | null dateShot: any | null;
/** /**
* The exposure time of the image * The exposure time of the image
*/ */
exposure: number | null exposure: number | null;
/** /**
* The aperature stops of the image * The aperature stops of the image
*/ */
aperture: number | null aperture: number | null;
/** /**
* The ISO setting of the image * The ISO setting of the image
*/ */
iso: number | null iso: number | null;
/** /**
* The focal length of the lens, when the image was taken * The focal length of the lens, when the image was taken
*/ */
focalLength: number | null focalLength: number | null;
/** /**
* A formatted description of the flash settings, when the image was taken * A formatted description of the flash settings, when the image was taken
*/ */
flash: number | null flash: number | null;
/** /**
* An index describing the mode for adjusting the exposure of the image * An index describing the mode for adjusting the exposure of the image
*/ */
exposureProgram: number | null exposureProgram: number | null;
} }
export interface sidebarPhoto_media_faces_rectangle { export interface sidebarPhoto_media_faces_rectangle {
__typename: 'FaceRectangle' __typename: "FaceRectangle";
minX: number minX: number;
maxX: number maxX: number;
minY: number minY: number;
maxY: number maxY: number;
} }
export interface sidebarPhoto_media_faces_faceGroup { export interface sidebarPhoto_media_faces_faceGroup {
__typename: 'FaceGroup' __typename: "FaceGroup";
id: string id: string;
} }
export interface sidebarPhoto_media_faces { export interface sidebarPhoto_media_faces {
__typename: 'ImageFace' __typename: "ImageFace";
id: string id: string;
rectangle: sidebarPhoto_media_faces_rectangle rectangle: sidebarPhoto_media_faces_rectangle;
faceGroup: sidebarPhoto_media_faces_faceGroup faceGroup: sidebarPhoto_media_faces_faceGroup;
} }
export interface sidebarPhoto_media { export interface sidebarPhoto_media {
__typename: 'Media' __typename: "Media";
id: string id: string;
title: string title: string;
type: MediaType type: MediaType;
/** /**
* URL to display the photo in full resolution, will be null for videos * URL to display the photo in full resolution, will be null for videos
*/ */
highRes: sidebarPhoto_media_highRes | null highRes: sidebarPhoto_media_highRes | null;
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: sidebarPhoto_media_thumbnail | null thumbnail: sidebarPhoto_media_thumbnail | null;
/** /**
* URL to get the video in a web format that can be played in the browser, will be null for photos * URL to get the video in a web format that can be played in the browser, will be null for photos
*/ */
videoWeb: sidebarPhoto_media_videoWeb | null videoWeb: sidebarPhoto_media_videoWeb | null;
videoMetadata: sidebarPhoto_media_videoMetadata | null videoMetadata: sidebarPhoto_media_videoMetadata | null;
exif: sidebarPhoto_media_exif | null exif: sidebarPhoto_media_exif | null;
faces: sidebarPhoto_media_faces[] faces: sidebarPhoto_media_faces[];
} }
export interface sidebarPhoto { export interface sidebarPhoto {
@ -159,9 +159,9 @@ export interface sidebarPhoto {
* Get media by id, user must own the media or be admin. * Get media by id, user must own the media or be admin.
* If valid tokenCredentials are provided, the media may be retrived without further authentication * If valid tokenCredentials are provided, the media may be retrived without further authentication
*/ */
media: sidebarPhoto_media media: sidebarPhoto_media;
} }
export interface sidebarPhotoVariables { export interface sidebarPhotoVariables {
id: string id: string;
} }

View File

@ -8,19 +8,19 @@
// ==================================================== // ====================================================
export interface sidebarPhotoAddShare_shareMedia { export interface sidebarPhotoAddShare_shareMedia {
__typename: 'ShareToken' __typename: "ShareToken";
token: string token: string;
} }
export interface sidebarPhotoAddShare { export interface sidebarPhotoAddShare {
/** /**
* Generate share token for media * Generate share token for media
*/ */
shareMedia: sidebarPhotoAddShare_shareMedia shareMedia: sidebarPhotoAddShare_shareMedia;
} }
export interface sidebarPhotoAddShareVariables { export interface sidebarPhotoAddShareVariables {
id: string id: string;
password?: string | null password?: string | null;
expire?: any | null expire?: any | null;
} }

View File

@ -8,22 +8,22 @@
// ==================================================== // ====================================================
export interface sidebarProtectShare_protectShareToken { export interface sidebarProtectShare_protectShareToken {
__typename: 'ShareToken' __typename: "ShareToken";
token: string token: string;
/** /**
* Whether or not a password is needed to access the share * Whether or not a password is needed to access the share
*/ */
hasPassword: boolean hasPassword: boolean;
} }
export interface sidebarProtectShare { export interface sidebarProtectShare {
/** /**
* Set a password for a token, if null is passed for the password argument, the password will be cleared * Set a password for a token, if null is passed for the password argument, the password will be cleared
*/ */
protectShareToken: sidebarProtectShare_protectShareToken protectShareToken: sidebarProtectShare_protectShareToken;
} }
export interface sidebarProtectShareVariables { export interface sidebarProtectShareVariables {
token: string token: string;
password?: string | null password?: string | null;
} }

View File

@ -8,17 +8,17 @@
// ==================================================== // ====================================================
export interface sidebareDeleteShare_deleteShareToken { export interface sidebareDeleteShare_deleteShareToken {
__typename: 'ShareToken' __typename: "ShareToken";
token: string token: string;
} }
export interface sidebareDeleteShare { export interface sidebareDeleteShare {
/** /**
* Delete a share token by it's token value * Delete a share token by it's token value
*/ */
deleteShareToken: sidebareDeleteShare_deleteShareToken deleteShareToken: sidebareDeleteShare_deleteShareToken;
} }
export interface sidebareDeleteShareVariables { export interface sidebareDeleteShareVariables {
token: string token: string;
} }

View File

@ -0,0 +1,103 @@
import { useQuery } from '@apollo/client'
import gql from 'graphql-tag'
import React from 'react'
import { useTranslation } from 'react-i18next'
import Dropdown, { DropdownItem } from '../../primitives/form/Dropdown'
import { FavoriteCheckboxProps, FavoritesCheckbox } from '../album/AlbumFilter'
import { ReactComponent as DateIcon } from './icons/date.svg'
import { earliestMedia } from './__generated__/earliestMedia'
const EARLIEST_MEDIA_QUERY = gql`
query earliestMedia {
myMedia(
order: { order_by: "date_shot", order_direction: ASC }
paginate: { limit: 1 }
) {
id
date
}
}
`
type DateSelectorProps = {
filterDate: string | null
setFilterDate(date: string | null): void
}
const DateSelector = ({ filterDate, setFilterDate }: DateSelectorProps) => {
const { t } = useTranslation()
const { data, loading } = useQuery<earliestMedia>(EARLIEST_MEDIA_QUERY)
let items: DropdownItem[] = [
{
value: 'all',
label: t('timeline_filter.date.dropdown_all', 'From today'),
},
]
if (data) {
const dateStr = data.myMedia[0].date
const date = new Date(dateStr)
const now = new Date()
const currentYear = now.getFullYear()
const earliestYear = date.getFullYear()
const years: number[] = []
for (let i = currentYear - 1; i >= earliestYear; i--) {
years.push(i)
}
const yearItems = years.map<DropdownItem>(x => ({
value: `${x}`,
label: `${x} and earlier`,
}))
items = [...items, ...yearItems]
}
return (
<fieldset>
<legend id="filter_group_date-label" className="inline-block mb-1">
<DateIcon
className="inline-block align-baseline mr-1"
aria-hidden="true"
/>
<span>{t('timeline_filter.date.label', 'Date')}</span>
</legend>
<div>
<Dropdown
aria-labelledby="filter_group_date-label"
setSelected={date =>
date == 'all' ? setFilterDate(null) : setFilterDate(date)
}
value={filterDate || 'all'}
items={items}
disabled={loading}
/>
</div>
</fieldset>
)
}
type TimelineFiltersProps = DateSelectorProps & FavoriteCheckboxProps
const TimelineFilters = ({
onlyFavorites,
setOnlyFavorites,
filterDate,
setFilterDate,
}: TimelineFiltersProps) => {
return (
<div className="flex items-end gap-4 flex-wrap mb-4">
<DateSelector filterDate={filterDate} setFilterDate={setFilterDate} />
<FavoritesCheckbox
onlyFavorites={onlyFavorites}
setOnlyFavorites={setOnlyFavorites}
/>
</div>
)
}
export default TimelineFilters

View File

@ -0,0 +1,39 @@
import { MockedProvider } from '@apollo/client/testing'
import { render, screen } from '@testing-library/react'
import React from 'react'
import { MemoryRouter } from 'react-router-dom'
import TimelineGallery, { MY_TIMELINE_QUERY } from './TimelineGallery'
import { timelineData } from './timelineTestData'
jest.mock('../../hooks/useScrollPagination')
test('timeline with media', async () => {
const graphqlMocks = [
{
request: {
query: MY_TIMELINE_QUERY,
variables: { onlyFavorites: false, offset: 0, limit: 200 },
},
result: {
data: {
myTimeline: timelineData,
},
},
},
]
render(
<MemoryRouter initialEntries={['/timeline']}>
<MockedProvider mocks={graphqlMocks}>
<TimelineGallery />
</MockedProvider>
</MemoryRouter>
)
expect(screen.queryByLabelText('Show only favorites')).toBeInTheDocument()
expect(await screen.findAllByRole('link')).toHaveLength(4)
expect(await screen.findAllByRole('img')).toHaveLength(5)
screen.debug()
})

View File

@ -4,7 +4,6 @@ import { useQuery, gql } from '@apollo/client'
import TimelineGroupDate from './TimelineGroupDate' import TimelineGroupDate from './TimelineGroupDate'
import PresentView from '../photoGallery/presentView/PresentView' import PresentView from '../photoGallery/presentView/PresentView'
import useURLParameters from '../../hooks/useURLParameters' import useURLParameters from '../../hooks/useURLParameters'
import { FavoritesCheckbox } from '../album/AlbumFilter'
import useScrollPagination from '../../hooks/useScrollPagination' import useScrollPagination from '../../hooks/useScrollPagination'
import PaginateLoader from '../PaginateLoader' import PaginateLoader from '../PaginateLoader'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -20,37 +19,42 @@ import {
import MediaSidebar from '../sidebar/MediaSidebar' import MediaSidebar from '../sidebar/MediaSidebar'
import { SidebarContext } from '../sidebar/Sidebar' import { SidebarContext } from '../sidebar/Sidebar'
import { urlPresentModeSetupHook } from '../photoGallery/photoGalleryReducer' import { urlPresentModeSetupHook } from '../photoGallery/photoGalleryReducer'
import TimelineFilters from './TimelineFilters'
import client from '../../apolloClient'
const MY_TIMELINE_QUERY = gql` export const MY_TIMELINE_QUERY = gql`
query myTimeline($onlyFavorites: Boolean, $limit: Int, $offset: Int) { query myTimeline(
$onlyFavorites: Boolean
$limit: Int
$offset: Int
$fromDate: Time
) {
myTimeline( myTimeline(
onlyFavorites: $onlyFavorites onlyFavorites: $onlyFavorites
fromDate: $fromDate
paginate: { limit: $limit, offset: $offset } paginate: { limit: $limit, offset: $offset }
) { ) {
id
title
type
thumbnail {
url
width
height
}
highRes {
url
width
height
}
videoWeb {
url
}
favorite
album { album {
id id
title title
} }
media {
id
title
type
thumbnail {
url
width
height
}
highRes {
url
width
height
}
videoWeb {
url
}
favorite
}
mediaTotal
date date
} }
} }
@ -63,7 +67,13 @@ export type TimelineActiveIndex = {
export type TimelineGroup = { export type TimelineGroup = {
date: string date: string
groups: myTimeline_myTimeline[] albums: TimelineGroupAlbum[]
}
export type TimelineGroupAlbum = {
id: string
title: string
media: myTimeline_myTimeline[]
} }
const TimelineGallery = () => { const TimelineGallery = () => {
@ -74,7 +84,10 @@ const TimelineGallery = () => {
const onlyFavorites = getParam('favorites') == '1' ? true : false const onlyFavorites = getParam('favorites') == '1' ? true : false
const setOnlyFavorites = (favorites: boolean) => const setOnlyFavorites = (favorites: boolean) =>
setParam('favorites', favorites ? '1' : '0') setParam('favorites', favorites ? '1' : null)
const filterDate = getParam('date')
const setFilterDate = (x: string) => setParam('date', x)
const favoritesNeedsRefresh = useRef(false) const favoritesNeedsRefresh = useRef(false)
@ -94,8 +107,11 @@ const TimelineGallery = () => {
>(MY_TIMELINE_QUERY, { >(MY_TIMELINE_QUERY, {
variables: { variables: {
onlyFavorites, onlyFavorites,
fromDate: filterDate
? `${parseInt(filterDate) + 1}-01-01T00:00:00Z`
: undefined,
offset: 0, offset: 0,
limit: 50, limit: 200,
}, },
}) })
@ -123,6 +139,20 @@ const TimelineGallery = () => {
} }
}, [mediaState.activeIndex]) }, [mediaState.activeIndex])
useEffect(() => {
;(async () => {
await client.resetStore()
await refetch({
onlyFavorites,
fromDate: filterDate
? `${parseInt(filterDate) + 1}-01-01T00:00:00Z`
: undefined,
offset: 0,
limit: 200,
})
})()
}, [filterDate])
urlPresentModeSetupHook({ urlPresentModeSetupHook({
dispatchMedia, dispatchMedia,
openPresentMode: event => { openPresentMode: event => {
@ -155,12 +185,12 @@ const TimelineGallery = () => {
return ( return (
<div className="overflow-x-hidden"> <div className="overflow-x-hidden">
<div className="mb-2"> <TimelineFilters
<FavoritesCheckbox onlyFavorites={onlyFavorites}
onlyFavorites={onlyFavorites} setOnlyFavorites={setOnlyFavorites}
setOnlyFavorites={setOnlyFavorites} filterDate={filterDate}
/> setFilterDate={setFilterDate}
</div> />
<div className="-mx-3 flex flex-wrap" ref={containerElem}> <div className="-mx-3 flex flex-wrap" ref={containerElem}>
{timelineGroups} {timelineGroups}
</div> </div>

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import styled from 'styled-components'
import { MediaThumbnail } from '../photoGallery/MediaThumbnail' import { MediaThumbnail } from '../photoGallery/MediaThumbnail'
import { PhotoFiller } from '../photoGallery/PhotoGallery'
import { import {
toggleFavoriteAction, toggleFavoriteAction,
useMarkFavoriteMutation, useMarkFavoriteMutation,
@ -13,20 +13,6 @@ import {
TimelineGalleryState, TimelineGalleryState,
} from './timelineGalleryReducer' } from './timelineGalleryReducer'
const TotalItemsBubble = styled(Link)`
position: absolute;
top: 24px;
right: 6px;
background-color: white;
border-radius: 50%;
padding: 8px 0;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.3);
color: black;
width: 36px;
height: 36px;
text-align: center;
`
type TimelineGroupAlbumProps = { type TimelineGroupAlbumProps = {
dateIndex: number dateIndex: number
albumIndex: number albumIndex: number
@ -40,8 +26,11 @@ const TimelineGroupAlbum = ({
mediaState, mediaState,
dispatchMedia, dispatchMedia,
}: TimelineGroupAlbumProps) => { }: TimelineGroupAlbumProps) => {
const { media, mediaTotal, album } = const {
mediaState.timelineGroups[dateIndex].groups[albumIndex] media,
title: albumTitle,
id: albumID,
} = mediaState.timelineGroups[dateIndex].albums[albumIndex]
const [markFavorite] = useMarkFavoriteMutation() const [markFavorite] = useMarkFavoriteMutation()
@ -79,24 +68,14 @@ const TimelineGroupAlbum = ({
/> />
)) ))
let itemsBubble = null
const mediaVisibleCount = media.length
if (mediaTotal > mediaVisibleCount) {
itemsBubble = (
<TotalItemsBubble to={`/album/${album.id}`}>
{`+${Math.min(mediaTotal - mediaVisibleCount, 99)}`}
</TotalItemsBubble>
)
}
return ( return (
<div className="mx-2"> <div className="mx-2">
<Link to={`/album/${album.id}`} className="hover:underline"> <Link to={`/album/${albumID}`} className="hover:underline">
{album.title} {albumTitle}
</Link> </Link>
<div className="flex flex-wrap items-center h-[210px] relative -mx-1 pr-4 overflow-hidden"> <div className="flex flex-wrap items-center relative -mx-1 overflow-hidden">
{mediaElms} {mediaElms}
{itemsBubble} <PhotoFiller />
</div> </div>
</div> </div>
) )

View File

@ -27,9 +27,9 @@ const TimelineGroupDate = ({
const group = mediaState.timelineGroups[groupIndex] const group = mediaState.timelineGroups[groupIndex]
const albumGroupElms = group.groups.map((group, i) => ( const albumGroupElms = group.albums.map((album, i) => (
<TimelineGroupAlbum <TimelineGroupAlbum
key={`${group.date}_${group.album.id}`} key={`${group.date}_${album.id}`}
dateIndex={groupIndex} dateIndex={groupIndex}
albumIndex={i} albumIndex={i}
mediaState={mediaState} mediaState={mediaState}

View File

@ -0,0 +1,24 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: earliestMedia
// ====================================================
export interface earliestMedia_myMedia {
__typename: 'Media'
id: string
/**
* The date the image was shot or the date it was imported as a fallback
*/
date: any
}
export interface earliestMedia {
/**
* List of media owned by the logged in user
*/
myMedia: earliestMedia_myMedia[]
}

View File

@ -9,53 +9,53 @@ import { MediaType } from './../../../__generated__/globalTypes'
// GraphQL query operation: myTimeline // GraphQL query operation: myTimeline
// ==================================================== // ====================================================
export interface myTimeline_myTimeline_thumbnail {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
/**
* Width of the image in pixels
*/
width: number
/**
* Height of the image in pixels
*/
height: number
}
export interface myTimeline_myTimeline_highRes {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
/**
* Width of the image in pixels
*/
width: number
/**
* Height of the image in pixels
*/
height: number
}
export interface myTimeline_myTimeline_videoWeb {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
}
export interface myTimeline_myTimeline_album { export interface myTimeline_myTimeline_album {
__typename: 'Album' __typename: 'Album'
id: string id: string
title: string title: string
} }
export interface myTimeline_myTimeline_media_thumbnail { export interface myTimeline_myTimeline {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
/**
* Width of the image in pixels
*/
width: number
/**
* Height of the image in pixels
*/
height: number
}
export interface myTimeline_myTimeline_media_highRes {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
/**
* Width of the image in pixels
*/
width: number
/**
* Height of the image in pixels
*/
height: number
}
export interface myTimeline_myTimeline_media_videoWeb {
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string
}
export interface myTimeline_myTimeline_media {
__typename: 'Media' __typename: 'Media'
id: string id: string
title: string title: string
@ -63,27 +63,30 @@ export interface myTimeline_myTimeline_media {
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */
thumbnail: myTimeline_myTimeline_media_thumbnail | null thumbnail: myTimeline_myTimeline_thumbnail | null
/** /**
* URL to display the photo in full resolution, will be null for videos * URL to display the photo in full resolution, will be null for videos
*/ */
highRes: myTimeline_myTimeline_media_highRes | null highRes: myTimeline_myTimeline_highRes | null
/** /**
* URL to get the video in a web format that can be played in the browser, will be null for photos * URL to get the video in a web format that can be played in the browser, will be null for photos
*/ */
videoWeb: myTimeline_myTimeline_media_videoWeb | null videoWeb: myTimeline_myTimeline_videoWeb | null
favorite: boolean favorite: boolean
} /**
* The album that holds the media
export interface myTimeline_myTimeline { */
__typename: 'TimelineGroup'
album: myTimeline_myTimeline_album album: myTimeline_myTimeline_album
media: myTimeline_myTimeline_media[] /**
mediaTotal: number * The date the image was shot or the date it was imported as a fallback
*/
date: any date: any
} }
export interface myTimeline { export interface myTimeline {
/**
* Get a list of media, ordered first by day, then by album if multiple media was found for the same day.
*/
myTimeline: myTimeline_myTimeline[] myTimeline: myTimeline_myTimeline[]
} }
@ -91,4 +94,5 @@ export interface myTimelineVariables {
onlyFavorites?: boolean | null onlyFavorites?: boolean | null
limit?: number | null limit?: number | null
offset?: number | null offset?: number | null
fromDate?: any | null
} }

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="13px" height="15px" viewBox="0 0 13 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M9.16666667,5.68434189e-14 C9.41979718,5.68434189e-14 9.62899397,0.188102588 9.66210226,0.432152962 L9.66666667,0.5 L9.666,1.333 L11.1666667,1.33333333 C12.1285626,1.33333333 12.9174396,2.07411552 12.9939226,3.01630483 L13,3.16666667 L13,12.5 C13,13.512522 12.1791887,14.3333333 11.1666667,14.3333333 L11.1666667,14.3333333 L1.83333333,14.3333333 C0.820811292,14.3333333 0,13.512522 0,12.5 L0,12.5 L0,3.16666667 C0,2.15414463 0.820811292,1.33333333 1.83333333,1.33333333 L1.83333333,1.33333333 L3.333,1.333 L3.33333333,0.5 C3.33333333,0.223857625 3.55719096,5.68434189e-14 3.83333333,5.68434189e-14 C4.08646384,5.68434189e-14 4.29566064,0.188102588 4.32876892,0.432152962 L4.33333333,0.5 L4.333,1.333 L8.666,1.333 L8.66666667,0.5 C8.66666667,0.223857625 8.89052429,5.68434189e-14 9.16666667,5.68434189e-14 Z M12,6.333 L1,6.333 L1,12.5 C1,12.9248344 1.31790432,13.2754183 1.72880177,13.3268405 L1.83333333,13.3333333 L11.1666667,13.3333333 C11.626904,13.3333333 12,12.9602373 12,12.5 L12,12.5 L12,6.333 Z M3.333,2.333 L1.83333333,2.33333333 C1.37309604,2.33333333 1,2.70642938 1,3.16666667 L1,3.16666667 L1,5.333 L12,5.333 L12,3.16666667 C12,2.74183224 11.6820957,2.39124835 11.2711982,2.33982618 L11.1666667,2.33333333 L9.666,2.333 L9.66666667,3.16666667 C9.66666667,3.44280904 9.44280904,3.66666667 9.16666667,3.66666667 C8.91353616,3.66666667 8.70433936,3.47856408 8.67123108,3.2345137 L8.66666667,3.16666667 L8.666,2.333 L4.333,2.333 L4.33333333,3.16666667 C4.33333333,3.44280904 4.10947571,3.66666667 3.83333333,3.66666667 C3.58020282,3.66666667 3.37100603,3.47856408 3.33789774,3.2345137 L3.33333333,3.16666667 L3.333,2.333 Z" fill="currentColor" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,10 +1,10 @@
import { myTimeline_myTimeline } from './__generated__/myTimeline'
import { MediaType } from '../../__generated__/globalTypes' import { MediaType } from '../../__generated__/globalTypes'
import { import {
timelineGalleryReducer, timelineGalleryReducer,
TimelineGalleryState, TimelineGalleryState,
TimelineMediaIndex, TimelineMediaIndex,
} from './timelineGalleryReducer' } from './timelineGalleryReducer'
import { timelineData } from './timelineTestData'
describe('timeline gallery reducer', () => { describe('timeline gallery reducer', () => {
const defaultEmptyState: TimelineGalleryState = { const defaultEmptyState: TimelineGalleryState = {
@ -17,125 +17,6 @@ describe('timeline gallery reducer', () => {
timelineGroups: [], timelineGroups: [],
} }
const timelineData: myTimeline_myTimeline[] = [
{
album: {
id: '5',
title: 'first album',
__typename: 'Album',
},
media: [
{
id: '165',
title: '3666760020.jpg',
type: MediaType.Photo,
thumbnail: {
url: 'http://localhost:4001/photo/thumbnail_3666760020_jpg_x76GG5pS.jpg',
width: 768,
height: 1024,
__typename: 'MediaURL',
},
highRes: {
url: 'http://localhost:4001/photo/3666760020_wijGDNZ2.jpg',
width: 3024,
height: 4032,
__typename: 'MediaURL',
},
videoWeb: null,
favorite: false,
__typename: 'Media',
},
{
id: '184',
title: '7414455077.jpg',
type: MediaType.Photo,
thumbnail: {
url: 'http://localhost:4001/photo/thumbnail_7414455077_jpg_9JYHHYh6.jpg',
width: 768,
height: 1024,
__typename: 'MediaURL',
},
highRes: {
url: 'http://localhost:4001/photo/7414455077_0ejDBiKr.jpg',
width: 3024,
height: 4032,
__typename: 'MediaURL',
},
videoWeb: null,
favorite: false,
__typename: 'Media',
},
],
mediaTotal: 5,
date: '2019-09-21T00:00:00Z',
__typename: 'TimelineGroup',
},
{
album: {
id: '5',
title: 'another album',
__typename: 'Album',
},
media: [
{
id: '165',
title: '3666760020.jpg',
type: MediaType.Photo,
thumbnail: {
url: 'http://localhost:4001/photo/thumbnail_3666760020_jpg_x76GG5pS.jpg',
width: 768,
height: 1024,
__typename: 'MediaURL',
},
highRes: {
url: 'http://localhost:4001/photo/3666760020_wijGDNZ2.jpg',
width: 3024,
height: 4032,
__typename: 'MediaURL',
},
videoWeb: null,
favorite: false,
__typename: 'Media',
},
],
mediaTotal: 7,
date: '2019-09-21T00:00:00Z',
__typename: 'TimelineGroup',
},
{
__typename: 'TimelineGroup',
album: {
__typename: 'Album',
id: '5',
title: 'album on another day',
},
date: '2019-09-13T00:00:00Z',
mediaTotal: 1,
media: [
{
__typename: 'Media',
favorite: false,
videoWeb: null,
thumbnail: {
url: 'http://localhost:4001/photo/thumbnail_3666760020_jpg_x76GG5pS.jpg',
width: 768,
height: 1024,
__typename: 'MediaURL',
},
highRes: {
url: 'http://localhost:4001/photo/3666760020_wijGDNZ2.jpg',
width: 3024,
height: 4032,
__typename: 'MediaURL',
},
id: '321',
title: 'asdfimg.jpg',
type: MediaType.Photo,
},
],
},
]
const defaultState = timelineGalleryReducer(defaultEmptyState, { const defaultState = timelineGalleryReducer(defaultEmptyState, {
type: 'replaceTimelineGroups', type: 'replaceTimelineGroups',
timeline: timelineData, timeline: timelineData,
@ -151,68 +32,167 @@ describe('timeline gallery reducer', () => {
}, },
timelineGroups: [ timelineGroups: [
{ {
date: '2019-09-21T00:00:00Z', date: '2020-12-13T00:00:00Z',
groups: [ albums: [
{ {
album: { id: '522',
id: '5',
title: 'first album',
},
date: '2019-09-21T00:00:00Z',
media: [ media: [
{ {
__typename: 'Media',
album: {
__typename: 'Album',
id: '522',
title: 'random',
},
date: '2020-12-13T18:03:40Z',
favorite: false, favorite: false,
highRes: {}, highRes: {
id: '165', __typename: 'MediaURL',
thumbnail: {}, height: 4480,
title: '3666760020.jpg', url: 'http://localhost:4001/photo/122A2876_5cSPMiKL.jpg',
type: 'Photo', width: 6720,
}, },
{ id: '1058',
highRes: {}, thumbnail: {
id: '184', __typename: 'MediaURL',
thumbnail: {}, height: 682,
title: '7414455077.jpg', url: 'http://localhost:4001/photo/thumbnail_122A2876_jpg_Kp1U80vD.jpg',
width: 1024,
},
title: '122A2876.jpg',
type: 'Photo', type: 'Photo',
videoWeb: null,
}, },
], ],
mediaTotal: 5, title: 'random',
},
{
album: {
id: '5',
title: 'another album',
},
date: '2019-09-21T00:00:00Z',
media: [
{
id: '165',
},
],
mediaTotal: 7,
}, },
], ],
}, },
{ {
date: '2019-09-13T00:00:00Z', date: '2020-11-25T00:00:00Z',
groups: [ albums: [
{ {
album: { id: '523',
id: '5', title: 'another_album',
title: 'album on another day',
},
date: '2019-09-13T00:00:00Z',
media: [ media: [
{ {
__typename: 'Media',
album: {
__typename: 'Album',
id: '523',
title: 'another_album',
},
date: '2020-11-25T16:14:33Z',
favorite: false, favorite: false,
highRes: {}, highRes: {
id: '321', __typename: 'MediaURL',
thumbnail: {}, height: 4118,
title: 'asdfimg.jpg', url: 'http://localhost:4001/photo/122A2630-Edit_ySQWFAgE.jpg',
width: 6177,
},
id: '1059',
thumbnail: {
__typename: 'MediaURL',
height: 682,
url: 'http://localhost:4001/photo/thumbnail_122A2630-Edit_jpg_pwjtMkpy.jpg',
width: 1024,
},
title: '122A2630-Edit.jpg',
type: 'Photo', type: 'Photo',
videoWeb: null,
},
{
__typename: 'Media',
album: {
__typename: 'Album',
id: '523',
title: 'another_album',
},
date: '2020-11-25T16:43:59Z',
favorite: false,
highRes: {
__typename: 'MediaURL',
height: 884,
url: 'http://localhost:4001/photo/122A2785-2_mCnWjLdb.jpg',
width: 884,
},
id: '1060',
thumbnail: {
__typename: 'MediaURL',
height: 1024,
url: 'http://localhost:4001/photo/thumbnail_122A2785-2_jpg_CevmxEXf.jpg',
width: 1024,
},
title: '122A2785-2.jpg',
type: 'Photo',
videoWeb: null,
},
],
},
{
id: '522',
title: 'random',
media: [
{
__typename: 'Media',
album: {
__typename: 'Album',
id: '522',
title: 'random',
},
date: '2020-11-25T16:14:33Z',
favorite: false,
highRes: {
__typename: 'MediaURL',
height: 4118,
url: 'http://localhost:4001/photo/122A2630-Edit_em9g89qg.jpg',
width: 6177,
},
id: '1056',
thumbnail: {
__typename: 'MediaURL',
height: 682,
url: 'http://localhost:4001/photo/thumbnail_122A2630-Edit_jpg_aJPCSDDl.jpg',
width: 1024,
},
title: '122A2630-Edit.jpg',
type: 'Photo',
videoWeb: null,
},
],
},
],
},
{
date: '2020-11-09T00:00:00Z',
albums: [
{
id: '522',
title: 'random',
media: [
{
__typename: 'Media',
id: '1054',
title: '122A2559.jpg',
type: MediaType.Photo,
thumbnail: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/thumbnail_122A2559_jpg_MsOJtPi8.jpg',
width: 1024,
height: 712,
},
highRes: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/122A2559_FDsQHuBN.jpg',
width: 6246,
height: 4346,
},
videoWeb: null,
favorite: false,
album: { __typename: 'Album', id: '522', title: 'random' },
date: '2020-11-09T15:38:09Z',
}, },
], ],
mediaTotal: 1,
}, },
], ],
}, },
@ -267,20 +247,20 @@ describe('timeline gallery reducer', () => {
media: 0, media: 0,
}, },
out: { out: {
date: 0, date: 1,
album: 0, album: 0,
media: 1, media: 0,
}, },
}, },
{ {
name: 'next album', name: 'next album',
in: { in: {
date: 0, date: 1,
album: 0, album: 0,
media: 1, media: 1,
}, },
out: { out: {
date: 0, date: 1,
album: 1, album: 1,
media: 0, media: 0,
}, },
@ -288,12 +268,12 @@ describe('timeline gallery reducer', () => {
{ {
name: 'next date', name: 'next date',
in: { in: {
date: 0, date: 1,
album: 1, album: 1,
media: 1, media: 0,
}, },
out: { out: {
date: 1, date: 2,
album: 0, album: 0,
media: 0, media: 0,
}, },
@ -301,12 +281,12 @@ describe('timeline gallery reducer', () => {
{ {
name: 'reached end', name: 'reached end',
in: { in: {
date: 1, date: 2,
album: 0, album: 0,
media: 0, media: 0,
}, },
out: { out: {
date: 1, date: 2,
album: 0, album: 0,
media: 0, media: 0,
}, },
@ -366,12 +346,12 @@ describe('timeline gallery reducer', () => {
{ {
name: 'previous album', name: 'previous album',
in: { in: {
date: 0, date: 1,
album: 1, album: 1,
media: 0, media: 0,
}, },
out: { out: {
date: 0, date: 1,
album: 0, album: 0,
media: 1, media: 1,
}, },
@ -379,12 +359,12 @@ describe('timeline gallery reducer', () => {
{ {
name: 'previous date', name: 'previous date',
in: { in: {
date: 1, date: 2,
album: 0, album: 0,
media: 0, media: 0,
}, },
out: { out: {
date: 0, date: 1,
album: 1, album: 1,
media: 0, media: 0,
}, },

View File

@ -1,10 +1,8 @@
import React from 'react' import React from 'react'
import { import { myTimeline_myTimeline } from './__generated__/myTimeline'
myTimeline_myTimeline, import { TimelineGroup, TimelineGroupAlbum } from './TimelineGallery'
myTimeline_myTimeline_media,
} from './__generated__/myTimeline'
import { TimelineGroup } from './TimelineGallery'
import { GalleryAction } from '../photoGallery/photoGalleryReducer' import { GalleryAction } from '../photoGallery/photoGalleryReducer'
import { isNil } from '../../helpers/utils'
export interface TimelineMediaIndex { export interface TimelineMediaIndex {
date: number date: number
@ -30,18 +28,7 @@ export function timelineGalleryReducer(
): TimelineGalleryState { ): TimelineGalleryState {
switch (action.type) { switch (action.type) {
case 'replaceTimelineGroups': { case 'replaceTimelineGroups': {
const dateGroupedAlbums = action.timeline.reduce((acc, val) => { const timelineGroups = convertMediaToTimelineGroups(action.timeline)
if (acc.length == 0 || acc[acc.length - 1].date != val.date) {
acc.push({
date: val.date,
groups: [val],
})
} else {
acc[acc.length - 1].groups.push(val)
}
return acc
}, [] as TimelineGroup[])
return { return {
...state, ...state,
@ -50,7 +37,7 @@ export function timelineGalleryReducer(
date: -1, date: -1,
media: -1, media: -1,
}, },
timelineGroups: dateGroupedAlbums, timelineGroups,
} }
} }
case 'nextImage': { case 'nextImage': {
@ -64,7 +51,7 @@ export function timelineGalleryReducer(
return state return state
} }
const albumGroups = timelineGroups[activeIndex.date].groups const albumGroups = timelineGroups[activeIndex.date].albums
const albumMedia = albumGroups[activeIndex.album].media const albumMedia = albumGroups[activeIndex.album].media
if (activeIndex.media < albumMedia.length - 1) { if (activeIndex.media < albumMedia.length - 1) {
@ -124,7 +111,7 @@ export function timelineGalleryReducer(
} }
if (activeIndex.album > 0) { if (activeIndex.album > 0) {
const albumGroups = state.timelineGroups[activeIndex.date].groups const albumGroups = state.timelineGroups[activeIndex.date].albums
const albumMedia = albumGroups[activeIndex.album - 1].media const albumMedia = albumGroups[activeIndex.album - 1].media
return { return {
@ -138,7 +125,7 @@ export function timelineGalleryReducer(
} }
if (activeIndex.date > 0) { if (activeIndex.date > 0) {
const albumGroups = state.timelineGroups[activeIndex.date - 1].groups const albumGroups = state.timelineGroups[activeIndex.date - 1].albums
const albumMedia = albumGroups[albumGroups.length - 1].media const albumMedia = albumGroups[albumGroups.length - 1].media
return { return {
@ -181,9 +168,9 @@ export const getTimelineImage = ({
}: { }: {
mediaState: TimelineGalleryState mediaState: TimelineGalleryState
index: TimelineMediaIndex index: TimelineMediaIndex
}): myTimeline_myTimeline_media => { }): myTimeline_myTimeline => {
const { date, album, media } = index const { date, album, media } = index
return mediaState.timelineGroups[date].groups[album].media[media] return mediaState.timelineGroups[date].albums[album].media[media]
} }
export const getActiveTimelineImage = ({ export const getActiveTimelineImage = ({
@ -203,6 +190,74 @@ export const getActiveTimelineImage = ({
return getTimelineImage({ mediaState, index: mediaState.activeIndex }) return getTimelineImage({ mediaState, index: mediaState.activeIndex })
} }
function convertMediaToTimelineGroups(
timelineMedia: myTimeline_myTimeline[]
): TimelineGroup[] {
const timelineGroups: TimelineGroup[] = []
let albums: TimelineGroupAlbum[] = []
let nextAlbum: TimelineGroupAlbum | null = null
const sameDay = (a: string, b: string) => {
return (
a.replace(/\d{2}:\d{2}:\d{2}/, '00:00:00') ==
b.replace(/\d{2}:\d{2}:\d{2}/, '00:00:00')
)
}
for (const media of timelineMedia) {
if (nextAlbum == null) {
nextAlbum = {
id: media.album.id,
title: media.album.title,
media: [media],
}
continue
}
// if date changes
if (!sameDay(nextAlbum.media[0].date, media.date)) {
albums.push(nextAlbum)
timelineGroups.push({
date: albums[0].media[0].date.replace(/\d{2}:\d{2}:\d{2}/, '00:00:00'),
albums: albums,
})
albums = []
nextAlbum = {
id: media.album.id,
title: media.album.title,
media: [media],
}
continue
}
// if album changes
if (nextAlbum.id != media.album.id) {
albums.push(nextAlbum)
nextAlbum = {
id: media.album.id,
title: media.album.title,
media: [media],
}
continue
}
// same album and date
nextAlbum.media.push(media)
}
if (!isNil(nextAlbum)) {
albums.push(nextAlbum)
timelineGroups.push({
date: albums[0].media[0].date.replace(/\d{2}:\d{2}:\d{2}/, '00:00:00'),
albums: albums,
})
}
return timelineGroups
}
export const openTimelinePresentMode = ({ export const openTimelinePresentMode = ({
dispatchMedia, dispatchMedia,
activeIndex, activeIndex,

View File

@ -0,0 +1,115 @@
import { MediaType } from '../../__generated__/globalTypes'
import { myTimeline_myTimeline } from './__generated__/myTimeline'
export const timelineData: myTimeline_myTimeline[] = [
{
__typename: 'Media',
id: '1058',
title: '122A2876.jpg',
type: MediaType.Photo,
thumbnail: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/thumbnail_122A2876_jpg_Kp1U80vD.jpg',
width: 1024,
height: 682,
},
highRes: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/122A2876_5cSPMiKL.jpg',
width: 6720,
height: 4480,
},
videoWeb: null,
favorite: false,
album: { __typename: 'Album', id: '522', title: 'random' },
date: '2020-12-13T18:03:40Z',
},
{
__typename: 'Media',
id: '1059',
title: '122A2630-Edit.jpg',
type: MediaType.Photo,
thumbnail: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/thumbnail_122A2630-Edit_jpg_pwjtMkpy.jpg',
width: 1024,
height: 682,
},
highRes: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/122A2630-Edit_ySQWFAgE.jpg',
width: 6177,
height: 4118,
},
videoWeb: null,
favorite: false,
album: { __typename: 'Album', id: '523', title: 'another_album' },
date: '2020-11-25T16:14:33Z',
},
{
__typename: 'Media',
id: '1060',
title: '122A2785-2.jpg',
type: MediaType.Photo,
thumbnail: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/thumbnail_122A2785-2_jpg_CevmxEXf.jpg',
width: 1024,
height: 1024,
},
highRes: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/122A2785-2_mCnWjLdb.jpg',
width: 884,
height: 884,
},
videoWeb: null,
favorite: false,
album: { __typename: 'Album', id: '523', title: 'another_album' },
date: '2020-11-25T16:43:59Z',
},
{
__typename: 'Media',
id: '1056',
title: '122A2630-Edit.jpg',
type: MediaType.Photo,
thumbnail: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/thumbnail_122A2630-Edit_jpg_aJPCSDDl.jpg',
width: 1024,
height: 682,
},
highRes: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/122A2630-Edit_em9g89qg.jpg',
width: 6177,
height: 4118,
},
videoWeb: null,
favorite: false,
album: { __typename: 'Album', id: '522', title: 'random' },
date: '2020-11-25T16:14:33Z',
},
{
__typename: 'Media',
id: '1054',
title: '122A2559.jpg',
type: MediaType.Photo,
thumbnail: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/thumbnail_122A2559_jpg_MsOJtPi8.jpg',
width: 1024,
height: 712,
},
highRes: {
__typename: 'MediaURL',
url: 'http://localhost:4001/photo/122A2559_FDsQHuBN.jpg',
width: 6246,
height: 4346,
},
videoWeb: null,
favorite: false,
album: { __typename: 'Album', id: '522', title: 'random' },
date: '2020-11-09T15:38:09Z',
},
]

View File

@ -61,17 +61,17 @@
"description": "Simpelt og Brugervenligt Photo-galleri for Personlige Servere" "description": "Simpelt og Brugervenligt Photo-galleri for Personlige Servere"
}, },
"people_page": { "people_page": {
"action_label": {
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
},
"face_group": { "face_group": {
"label_placeholder": "Navn", "label_placeholder": "Navn",
"unlabeled": "Ikke navngivet", "unlabeled": "Ikke navngivet",
"unlabeled_person": "Ikke navngivet person" "unlabeled_person": "Ikke navngivet person"
}, },
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"modal": { "modal": {
"action": { "action": {
"merge": "Sammenflet" "merge": "Sammenflet"
@ -215,6 +215,10 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"album_cover": null,
"cover_photo": null,
"reset_cover": null,
"set_cover": null,
"title_placeholder": "Albumtitel" "title_placeholder": "Albumtitel"
}, },
"download": { "download": {
@ -295,6 +299,12 @@
"places": "Kort", "places": "Kort",
"settings": "Indstillinger" "settings": "Indstillinger"
}, },
"timeline_filter": {
"date": {
"dropdown_all": null,
"label": null
}
},
"title": { "title": {
"loading_album": "Loader album", "loading_album": "Loader album",
"login": "Log ind", "login": "Log ind",

View File

@ -36,6 +36,12 @@
"select_image_faces": { "select_image_faces": {
"search_images_placeholder": "Søg billeder..." "search_images_placeholder": "Søg billeder..."
} }
},
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
} }
}, },
"settings": { "settings": {
@ -53,6 +59,14 @@
}, },
"sharing": { "sharing": {
"table_header": "Offentlige delinger" "table_header": "Offentlige delinger"
},
"download": {
"filesize": {
"giga_byte_plural": null,
"kilo_byte_plural": null,
"mega_byte_plural": null,
"tera_byte_plural": null
}
} }
} }
} }

View File

@ -61,17 +61,17 @@
"description": "Einfache und nutzerfreundliche Fotogallerie für Homeserver" "description": "Einfache und nutzerfreundliche Fotogallerie für Homeserver"
}, },
"people_page": { "people_page": {
"action_label": {
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
},
"face_group": { "face_group": {
"label_placeholder": "Zuordnung", "label_placeholder": "Zuordnung",
"unlabeled": "Nicht zugeordnet", "unlabeled": "Nicht zugeordnet",
"unlabeled_person": null "unlabeled_person": null
}, },
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"modal": { "modal": {
"action": { "action": {
"merge": null "merge": null
@ -215,6 +215,10 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"album_cover": null,
"cover_photo": null,
"reset_cover": null,
"set_cover": null,
"title_placeholder": null "title_placeholder": null
}, },
"download": { "download": {
@ -295,6 +299,12 @@
"places": "Orte", "places": "Orte",
"settings": "Einstellungen" "settings": "Einstellungen"
}, },
"timeline_filter": {
"date": {
"dropdown_all": null,
"label": null
}
},
"title": { "title": {
"loading_album": "Lade Album", "loading_album": "Lade Album",
"login": "Login", "login": "Login",

View File

@ -15,7 +15,8 @@
"header": { "header": {
"search": { "search": {
"result_type": { "result_type": {
"photos": "Fotos" "photos": "Fotos",
"media": null
} }
} }
}, },
@ -69,23 +70,55 @@
"select_image_faces": { "select_image_faces": {
"search_images_placeholder": null "search_images_placeholder": null
} }
},
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"tableselect_face_group": {
"search_faces_placeholder": null
},
"tableselect_image_faces": {
"search_images_placeholder": null
} }
}, },
"settings": { "settings": {
"users": { "users": {
"table": { "table": {
"column_names": { "column_names": {
"admin": "Administrator" "admin": "Administrator",
"capabilities": null
} }
} }
} }
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"title": "Album Optionen" "title": "Album Optionen",
"title_placeholder": null
}, },
"sharing": { "sharing": {
"table_header": "Öffentliche Freigabe" "table_header": "Öffentliche Freigabe",
"delete": null,
"more": null
},
"download": {
"filesize": {
"giga_byte_plural": null,
"kilo_byte_plural": null,
"mega_byte_plural": null,
"tera_byte_plural": null
}
}
},
"album_filter": {
"sort": null
},
"share_page": {
"protected_share": {
"password_required_error": null
} }
} }
} }

View File

@ -61,17 +61,17 @@
"description": "Simple and User-friendly Photo Gallery for Personal Servers" "description": "Simple and User-friendly Photo Gallery for Personal Servers"
}, },
"people_page": { "people_page": {
"action_label": {
"change_label": "Change label",
"detach_face": "Detach face",
"merge_face": "Merge face",
"move_faces": "Move faces"
},
"face_group": { "face_group": {
"label_placeholder": "Label", "label_placeholder": "Label",
"unlabeled": "Unlabeled", "unlabeled": "Unlabeled",
"unlabeled_person": "Unlabeled person" "unlabeled_person": "Unlabeled person"
}, },
"action_label": {
"change_label": "Change label",
"merge_face": "Merge face",
"detach_face": "Detach face",
"move_faces": "Move faces"
},
"modal": { "modal": {
"action": { "action": {
"merge": "Merge" "merge": "Merge"
@ -215,6 +215,10 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"album_cover": "Album cover",
"cover_photo": "Album cover",
"reset_cover": "Reset cover photo",
"set_cover": "Set as album cover photo",
"title_placeholder": "Album title" "title_placeholder": "Album title"
}, },
"download": { "download": {
@ -295,6 +299,12 @@
"places": "Places", "places": "Places",
"settings": "Settings" "settings": "Settings"
}, },
"timeline_filter": {
"date": {
"dropdown_all": "From today",
"label": "Date"
}
},
"title": { "title": {
"loading_album": "Loading album", "loading_album": "Loading album",
"login": "Login", "login": "Login",

View File

@ -61,17 +61,17 @@
"description": "Una galería de fotos sencilla y fácil de usar para servidores personales" "description": "Una galería de fotos sencilla y fácil de usar para servidores personales"
}, },
"people_page": { "people_page": {
"action_label": {
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
},
"face_group": { "face_group": {
"label_placeholder": "Etiqueta", "label_placeholder": "Etiqueta",
"unlabeled": "Sin etiquetar", "unlabeled": "Sin etiquetar",
"unlabeled_person": null "unlabeled_person": null
}, },
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"modal": { "modal": {
"action": { "action": {
"merge": null "merge": null
@ -215,6 +215,10 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"album_cover": null,
"cover_photo": null,
"reset_cover": null,
"set_cover": null,
"title_placeholder": null "title_placeholder": null
}, },
"download": { "download": {
@ -295,6 +299,12 @@
"places": "Lugares", "places": "Lugares",
"settings": "Opciones" "settings": "Opciones"
}, },
"timeline_filter": {
"date": {
"dropdown_all": null,
"label": null
}
},
"title": { "title": {
"loading_album": "Cargando álbum", "loading_album": "Cargando álbum",
"login": null, "login": null,

View File

@ -15,7 +15,8 @@
"header": { "header": {
"search": { "search": {
"result_type": { "result_type": {
"photos": "Fotos" "photos": "Fotos",
"media": null
} }
} }
}, },
@ -69,13 +70,26 @@
"select_image_faces": { "select_image_faces": {
"search_images_placeholder": null "search_images_placeholder": null
} }
},
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"tableselect_face_group": {
"search_faces_placeholder": null
},
"tableselect_image_faces": {
"search_images_placeholder": null
} }
}, },
"settings": { "settings": {
"users": { "users": {
"table": { "table": {
"column_names": { "column_names": {
"admin": "Administrador" "admin": "Administrador",
"capabilities": null
} }
} }
}, },
@ -87,13 +101,32 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"title": "opciones de álbum" "title": "opciones de álbum",
"title_placeholder": null
}, },
"sharing": { "sharing": {
"table_header": "Compartidos públicos" "table_header": "Compartidos públicos",
"delete": null,
"more": null
},
"download": {
"filesize": {
"giga_byte_plural": null,
"kilo_byte_plural": null,
"mega_byte_plural": null,
"tera_byte_plural": null
}
} }
}, },
"title": { "title": {
"login": null "login": null
},
"album_filter": {
"sort": null
},
"share_page": {
"protected_share": {
"password_required_error": null
}
} }
} }

View File

@ -61,17 +61,17 @@
"description": "Galerie Photo simple et convivial pour les Serveurs Personnels" "description": "Galerie Photo simple et convivial pour les Serveurs Personnels"
}, },
"people_page": { "people_page": {
"action_label": {
"change_label": "Changer le label",
"detach_face": "Détacher le visage",
"merge_face": "Fusionner le visage",
"move_faces": "Déplacer les visages"
},
"face_group": { "face_group": {
"label_placeholder": "Étiquette", "label_placeholder": "Étiquette",
"unlabeled": "Sans étiquette", "unlabeled": "Sans étiquette",
"unlabeled_person": "Personne sans étiquette" "unlabeled_person": "Personne sans étiquette"
}, },
"action_label": {
"change_label": "Changer le label",
"merge_face": "Fusionner le visage",
"detach_face": "Détacher le visage",
"move_faces": "Déplacer les visages"
},
"modal": { "modal": {
"action": { "action": {
"merge": "Fusionner" "merge": "Fusionner"
@ -215,6 +215,10 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"album_cover": null,
"cover_photo": null,
"reset_cover": null,
"set_cover": null,
"title_placeholder": "Titre de l'Album" "title_placeholder": "Titre de l'Album"
}, },
"download": { "download": {
@ -295,6 +299,12 @@
"places": "Lieux", "places": "Lieux",
"settings": "Réglages" "settings": "Réglages"
}, },
"timeline_filter": {
"date": {
"dropdown_all": null,
"label": null
}
},
"title": { "title": {
"loading_album": "Chargement de l'album", "loading_album": "Chargement de l'album",
"login": "Connexion", "login": "Connexion",

View File

@ -61,17 +61,17 @@
"description": "Galleria fotografica semplice e user-friendly per il tuo server" "description": "Galleria fotografica semplice e user-friendly per il tuo server"
}, },
"people_page": { "people_page": {
"action_label": {
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
},
"face_group": { "face_group": {
"label_placeholder": "Etichetta", "label_placeholder": "Etichetta",
"unlabeled": "Senza etichetta", "unlabeled": "Senza etichetta",
"unlabeled_person": "Persona senza etichetta" "unlabeled_person": "Persona senza etichetta"
}, },
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"modal": { "modal": {
"action": { "action": {
"merge": "Unisci" "merge": "Unisci"
@ -215,6 +215,10 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"album_cover": null,
"cover_photo": null,
"reset_cover": null,
"set_cover": null,
"title_placeholder": null "title_placeholder": null
}, },
"download": { "download": {
@ -295,6 +299,12 @@
"places": "Luoghi", "places": "Luoghi",
"settings": "Impostazioni" "settings": "Impostazioni"
}, },
"timeline_filter": {
"date": {
"dropdown_all": null,
"label": null
}
},
"title": { "title": {
"loading_album": "Caricamento album", "loading_album": "Caricamento album",
"login": "Login", "login": "Login",

View File

@ -15,7 +15,8 @@
"header": { "header": {
"search": { "search": {
"result_type": { "result_type": {
"photos": "Foto" "photos": "Foto",
"media": null
} }
} }
}, },
@ -36,23 +37,55 @@
"select_image_faces": { "select_image_faces": {
"search_images_placeholder": "Cerca immagini..." "search_images_placeholder": "Cerca immagini..."
} }
},
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"tableselect_face_group": {
"search_faces_placeholder": null
},
"tableselect_image_faces": {
"search_images_placeholder": null
} }
}, },
"settings": { "settings": {
"users": { "users": {
"table": { "table": {
"column_names": { "column_names": {
"admin": "Admin" "admin": "Admin",
"capabilities": null
} }
} }
} }
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"title": "Opzioni Album" "title": "Opzioni Album",
"title_placeholder": null
}, },
"sharing": { "sharing": {
"table_header": "Condivisioni pubbliche" "table_header": "Condivisioni pubbliche",
"delete": null,
"more": null
},
"download": {
"filesize": {
"giga_byte_plural": null,
"kilo_byte_plural": null,
"mega_byte_plural": null,
"tera_byte_plural": null
}
}
},
"album_filter": {
"sort": null
},
"share_page": {
"protected_share": {
"password_required_error": null
} }
} }
} }

View File

@ -61,17 +61,17 @@
"description": "Prosta i przyjazna dla użytkownika galeria zdjęć" "description": "Prosta i przyjazna dla użytkownika galeria zdjęć"
}, },
"people_page": { "people_page": {
"action_label": {
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
},
"face_group": { "face_group": {
"label_placeholder": "Etykieta", "label_placeholder": "Etykieta",
"unlabeled": "Nieoznakowany", "unlabeled": "Nieoznakowany",
"unlabeled_person": null "unlabeled_person": null
}, },
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"modal": { "modal": {
"action": { "action": {
"merge": null "merge": null
@ -215,6 +215,10 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"album_cover": null,
"cover_photo": null,
"reset_cover": null,
"set_cover": null,
"title_placeholder": null "title_placeholder": null
}, },
"download": { "download": {
@ -300,6 +304,12 @@
"places": "Miejsca", "places": "Miejsca",
"settings": "Ustawienia" "settings": "Ustawienia"
}, },
"timeline_filter": {
"date": {
"dropdown_all": null,
"label": null
}
},
"title": { "title": {
"loading_album": "Ładowanie albumu", "loading_album": "Ładowanie albumu",
"login": null, "login": null,

View File

@ -15,7 +15,8 @@
"header": { "header": {
"search": { "search": {
"result_type": { "result_type": {
"photos": "Zdjęcia" "photos": "Zdjęcia",
"media": null
} }
} }
}, },
@ -69,13 +70,26 @@
"select_image_faces": { "select_image_faces": {
"search_images_placeholder": null "search_images_placeholder": null
} }
},
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"tableselect_face_group": {
"search_faces_placeholder": null
},
"tableselect_image_faces": {
"search_images_placeholder": null
} }
}, },
"settings": { "settings": {
"users": { "users": {
"table": { "table": {
"column_names": { "column_names": {
"admin": "Admin" "admin": "Admin",
"capabilities": null
} }
} }
}, },
@ -87,7 +101,8 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"title": "Opcje albumu" "title": "Opcje albumu",
"title_placeholder": null
}, },
"download": { "download": {
"filesize": { "filesize": {
@ -99,14 +114,36 @@
"giga_byte": "{{count}} GB", "giga_byte": "{{count}} GB",
"kilo_byte": "{{count}} KB", "kilo_byte": "{{count}} KB",
"mega_byte": "{{count}} MB", "mega_byte": "{{count}} MB",
"tera_byte": "{{count}} TB" "tera_byte": "{{count}} TB",
"giga_byte_0": null,
"giga_byte_1": null,
"giga_byte_2": null,
"kilo_byte_0": null,
"kilo_byte_1": null,
"kilo_byte_2": null,
"mega_byte_0": null,
"mega_byte_1": null,
"mega_byte_2": null,
"tera_byte_0": null,
"tera_byte_1": null,
"tera_byte_2": null
} }
}, },
"sharing": { "sharing": {
"table_header": "Publiczne udostępnienia" "table_header": "Publiczne udostępnienia",
"delete": null,
"more": null
} }
}, },
"title": { "title": {
"login": null "login": null
},
"album_filter": {
"sort": null
},
"share_page": {
"protected_share": {
"password_required_error": null
}
} }
} }

View File

@ -61,17 +61,17 @@
"description": "Простая и Удобная Фото Галерея для Личного Сервера" "description": "Простая и Удобная Фото Галерея для Личного Сервера"
}, },
"people_page": { "people_page": {
"action_label": {
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
},
"face_group": { "face_group": {
"label_placeholder": "Метка", "label_placeholder": "Метка",
"unlabeled": "Без метки", "unlabeled": "Без метки",
"unlabeled_person": "Человек без метки" "unlabeled_person": "Человек без метки"
}, },
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"modal": { "modal": {
"action": { "action": {
"merge": "Объединить" "merge": "Объединить"
@ -215,6 +215,10 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"album_cover": null,
"cover_photo": null,
"reset_cover": null,
"set_cover": null,
"title_placeholder": null "title_placeholder": null
}, },
"download": { "download": {
@ -300,6 +304,12 @@
"places": "Места", "places": "Места",
"settings": "Настройки" "settings": "Настройки"
}, },
"timeline_filter": {
"date": {
"dropdown_all": null,
"label": null
}
},
"title": { "title": {
"loading_album": "Загрузка альбома", "loading_album": "Загрузка альбома",
"login": "Имя пользователя", "login": "Имя пользователя",

View File

@ -15,7 +15,8 @@
"header": { "header": {
"search": { "search": {
"result_type": { "result_type": {
"photos": "Фото" "photos": "Фото",
"media": null
} }
} }
}, },
@ -36,20 +37,34 @@
"select_image_faces": { "select_image_faces": {
"search_images_placeholder": "Поиск фото..." "search_images_placeholder": "Поиск фото..."
} }
},
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"tableselect_face_group": {
"search_faces_placeholder": null
},
"tableselect_image_faces": {
"search_images_placeholder": null
} }
}, },
"settings": { "settings": {
"users": { "users": {
"table": { "table": {
"column_names": { "column_names": {
"admin": "Администрирование" "admin": "Администрирование",
"capabilities": null
} }
} }
} }
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"title": "Свойства альбома" "title": "Свойства альбома",
"title_placeholder": null
}, },
"download": { "download": {
"filesize": { "filesize": {
@ -58,11 +73,36 @@
"giga_byte": "{{count}} ГБ", "giga_byte": "{{count}} ГБ",
"kilo_byte": "{{count}} КБ", "kilo_byte": "{{count}} КБ",
"mega_byte": "{{count}} МБ", "mega_byte": "{{count}} МБ",
"tera_byte": "{{count}} ТБ" "tera_byte": "{{count}} ТБ",
"byte_0": null,
"byte_1": null,
"byte_2": null,
"giga_byte_0": null,
"giga_byte_1": null,
"giga_byte_2": null,
"kilo_byte_0": null,
"kilo_byte_1": null,
"kilo_byte_2": null,
"mega_byte_0": null,
"mega_byte_1": null,
"mega_byte_2": null,
"tera_byte_0": null,
"tera_byte_1": null,
"tera_byte_2": null
} }
}, },
"sharing": { "sharing": {
"table_header": "Общий доступ" "table_header": "Общий доступ",
"delete": null,
"more": null
}
},
"album_filter": {
"sort": null
},
"share_page": {
"protected_share": {
"password_required_error": null
} }
} }
} }

View File

@ -61,17 +61,17 @@
"description": "Enkelt och Användarvänligt fotogalleri för personliga servrar" "description": "Enkelt och Användarvänligt fotogalleri för personliga servrar"
}, },
"people_page": { "people_page": {
"action_label": {
"change_label": null,
"detach_face": null,
"merge_face": null,
"move_faces": null
},
"face_group": { "face_group": {
"label_placeholder": "Märkning", "label_placeholder": "Märkning",
"unlabeled": "Omärkt", "unlabeled": "Omärkt",
"unlabeled_person": null "unlabeled_person": null
}, },
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"modal": { "modal": {
"action": { "action": {
"merge": null "merge": null
@ -215,6 +215,10 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"album_cover": null,
"cover_photo": null,
"reset_cover": null,
"set_cover": null,
"title_placeholder": null "title_placeholder": null
}, },
"download": { "download": {
@ -295,6 +299,12 @@
"places": "Platser", "places": "Platser",
"settings": "Inställningar" "settings": "Inställningar"
}, },
"timeline_filter": {
"date": {
"dropdown_all": null,
"label": null
}
},
"title": { "title": {
"loading_album": "Laddar album", "loading_album": "Laddar album",
"login": null, "login": null,

View File

@ -15,7 +15,8 @@
"header": { "header": {
"search": { "search": {
"result_type": { "result_type": {
"photos": "Bilder" "photos": "Bilder",
"media": null
} }
} }
}, },
@ -69,6 +70,18 @@
"select_image_faces": { "select_image_faces": {
"search_images_placeholder": null "search_images_placeholder": null
} }
},
"action_label": {
"change_label": null,
"merge_face": null,
"detach_face": null,
"move_faces": null
},
"tableselect_face_group": {
"search_faces_placeholder": null
},
"tableselect_image_faces": {
"search_images_placeholder": null
} }
}, },
"places_page": { "places_page": {
@ -78,7 +91,8 @@
"users": { "users": {
"table": { "table": {
"column_names": { "column_names": {
"admin": "Admin" "admin": "Admin",
"capabilities": null
} }
} }
}, },
@ -90,13 +104,32 @@
}, },
"sidebar": { "sidebar": {
"album": { "album": {
"title": "Albuminställningar" "title": "Albuminställningar",
"title_placeholder": null
}, },
"sharing": { "sharing": {
"table_header": "Publika delningar" "table_header": "Publika delningar",
"delete": null,
"more": null
},
"download": {
"filesize": {
"giga_byte_plural": null,
"kilo_byte_plural": null,
"mega_byte_plural": null,
"tera_byte_plural": null
}
} }
}, },
"title": { "title": {
"login": null "login": null
},
"album_filter": {
"sort": null
},
"share_page": {
"protected_share": {
"password_required_error": null
}
} }
} }

View File

@ -90,6 +90,10 @@ function useScrollPagination<D>({
reconfigureIntersectionObserver() reconfigureIntersectionObserver()
}, [fetchMore, data, finished]) }, [fetchMore, data, finished])
useEffect(() => {
setFinished(false)
}, [data])
return { return {
containerElem, containerElem,
finished, finished,

View File

@ -1,10 +1,10 @@
import { useState } from 'react' import { useState } from 'react'
export type UrlKeyValuePair = { key: string; value: string } export type UrlKeyValuePair = { key: string; value: string | null }
export type UrlParams = { export type UrlParams = {
getParam(key: string, defaultValue?: string | null): string | null getParam(key: string, defaultValue?: string | null): string | null
setParam(key: string, value: string): void setParam(key: string, value: string | null): void
setParams(pairs: UrlKeyValuePair[]): void setParams(pairs: UrlKeyValuePair[]): void
} }
@ -19,18 +19,30 @@ function useURLParameters(): UrlParams {
} }
const updateParams = () => { const updateParams = () => {
history.replaceState({}, '', url.pathname + '?' + params.toString()) if (params.toString()) {
history.replaceState({}, '', url.pathname + '?' + params.toString())
} else {
history.replaceState({}, '', url.pathname)
}
setUrlString(document.location.href) setUrlString(document.location.href)
} }
const setParam = (key: string, value: string) => { const setParam = (key: string, value: string | null) => {
params.set(key, value) if (value) {
params.set(key, value)
} else {
params.delete(key)
}
updateParams() updateParams()
} }
const setParams = (pairs: UrlKeyValuePair[]) => { const setParams = (pairs: UrlKeyValuePair[]) => {
for (const pair of pairs) { for (const pair of pairs) {
params.set(pair.key, pair.value) if (pair.value) {
params.set(pair.key, pair.value)
} else {
params.delete(pair.key)
}
} }
updateParams() updateParams()
} }