1
Fork 0

Merge pull request #640 from photoview/blurhash

Add blurhash while image thumbnails are still loading
This commit is contained in:
Viktor Strate Kløvedal 2022-02-02 00:09:25 +01:00 committed by GitHub
commit 4c0adeff79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 2187 additions and 647 deletions

View File

@ -7,6 +7,7 @@ require (
github.com/Kagami/go-face v0.0.0-20210630145111-0c14797b4d0e github.com/Kagami/go-face v0.0.0-20210630145111-0c14797b4d0e
github.com/agnivade/levenshtein v1.1.1 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/barasher/go-exiftool v1.7.0 github.com/barasher/go-exiftool v1.7.0
github.com/buckket/go-blurhash v1.1.0
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/felixge/httpsnoop v1.0.2 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0

View File

@ -15,6 +15,8 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/barasher/go-exiftool v1.7.0 h1:EOGb5D6TpWXmqsnEjJ0ai6+tIW2gZFwIoS9O/33Nixs= github.com/barasher/go-exiftool v1.7.0 h1:EOGb5D6TpWXmqsnEjJ0ai6+tIW2gZFwIoS9O/33Nixs=
github.com/barasher/go-exiftool v1.7.0/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo= github.com/barasher/go-exiftool v1.7.0/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo=
github.com/buckket/go-blurhash v1.1.0 h1:X5M6r0LIvwdvKiUtiNcRL2YlmOfMzYobI3VCKCZc9Do=
github.com/buckket/go-blurhash v1.1.0/go.mod h1:aT2iqo5W9vu9GpyoLErKfTHwgODsZp3bQfXjXJUxNb8=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,7 @@ type Media struct {
SideCarPath *string SideCarPath *string
SideCarHash *string `gorm:"unique"` SideCarHash *string `gorm:"unique"`
Faces []*ImageFace `gorm:"constraint:OnDelete:CASCADE;"` Faces []*ImageFace `gorm:"constraint:OnDelete:CASCADE;"`
Blurhash *string `gorm:""`
// Only used internally // Only used internally
CounterpartPath *string `gorm:"-"` CounterpartPath *string `gorm:"-"`
@ -49,6 +50,21 @@ func (m *Media) Date() time.Time {
return m.DateShot return m.DateShot
} }
func (m *Media) GetThumbnail() (*MediaURL, error) {
if len(m.MediaURL) == 0 {
return nil, errors.New("media.MediaURL is empty")
}
for _, url := range m.MediaURL {
if url.Purpose == PhotoThumbnail || url.Purpose == VideoThumbnail {
url.Media = m
return &url, nil
}
}
return nil, nil
}
type MediaType string type MediaType string
const ( const (

View File

@ -67,3 +67,56 @@ func TestMediaURLGetURL(t *testing.T) {
assert.Equal(t, "video/video.mp4", video.URL()) assert.Equal(t, "video/video.mp4", video.URL())
} }
func TestMediaGetThumbnail(t *testing.T) {
photo := models.Media{
Title: "test.png",
Path: "path/test.png",
Type: models.MediaTypePhoto,
MediaURL: []models.MediaURL{
{
MediaName: "photo.jpg",
ContentType: "image/jpeg",
Purpose: models.PhotoHighRes,
},
{
MediaName: "thumbnail.jpg",
ContentType: "image/jpeg",
Purpose: models.PhotoThumbnail,
},
{
MediaName: "photo.png",
ContentType: "image/png",
Purpose: models.MediaOriginal,
},
},
}
thumb, err := photo.GetThumbnail()
assert.NoError(t, err)
assert.Equal(t, thumb.MediaName, "thumbnail.jpg")
assert.NotNil(t, thumb.Media)
video := models.Media{
Title: "video-test.mp4",
Path: "path/test.mp4",
Type: models.MediaTypePhoto,
MediaURL: []models.MediaURL{
{
MediaName: "video.mp4",
ContentType: "video/mp4",
Purpose: models.VideoWeb,
},
{
MediaName: "video-thumbnail.jpg",
ContentType: "image/jpg",
Purpose: models.VideoThumbnail,
},
},
}
thumb, err = video.GetThumbnail()
assert.NoError(t, err)
assert.Equal(t, thumb.MediaName, "video-thumbnail.jpg")
assert.NotNil(t, thumb.Media)
}

View File

@ -328,6 +328,8 @@ type Media {
type: MediaType! type: MediaType!
"The date the image was shot or the date it was imported as a fallback" "The date the image was shot or the date it was imported as a fallback"
date: Time! date: Time!
"A short string that can be used to generate a blured version of the media, to show while the original is loading"
blurhash: String
shares: [ShareToken!]! shares: [ShareToken!]!
downloads: [MediaDownload!]! downloads: [MediaDownload!]!

View File

@ -9,6 +9,7 @@ import (
"github.com/photoview/photoview/api/graphql/models" "github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/graphql/notification" "github.com/photoview/photoview/api/graphql/notification"
"github.com/photoview/photoview/api/scanner/scanner_cache" "github.com/photoview/photoview/api/scanner/scanner_cache"
"github.com/photoview/photoview/api/scanner/scanner_utils"
"github.com/photoview/photoview/api/utils" "github.com/photoview/photoview/api/utils"
"github.com/pkg/errors" "github.com/pkg/errors"
"gorm.io/gorm" "gorm.io/gorm"
@ -156,8 +157,20 @@ func (queue *ScannerQueue) processQueue(notifyThrottle *utils.Throttle) {
notification.BroadcastNotification(&models.Notification{ notification.BroadcastNotification(&models.Notification{
Key: "global-scanner-progress", Key: "global-scanner-progress",
Type: models.NotificationTypeMessage, Type: models.NotificationTypeMessage,
Header: fmt.Sprintf("Scanner complete"), Header: "Generating blurhashes",
Content: fmt.Sprintf("All jobs have been scanned"), Content: "Generating blurhashes for newly scanned media",
Positive: true,
})
if err := GenerateBlurhashes(queue.db); err != nil {
scanner_utils.ScannerError("Failed to generate blurhashes: %v", err)
}
notification.BroadcastNotification(&models.Notification{
Key: "global-scanner-progress",
Type: models.NotificationTypeMessage,
Header: "Scanner complete",
Content: "All jobs have been scanned",
Positive: true, Positive: true,
}) })
} else { } else {
@ -165,7 +178,7 @@ func (queue *ScannerQueue) processQueue(notifyThrottle *utils.Throttle) {
notification.BroadcastNotification(&models.Notification{ notification.BroadcastNotification(&models.Notification{
Key: "global-scanner-progress", Key: "global-scanner-progress",
Type: models.NotificationTypeMessage, Type: models.NotificationTypeMessage,
Header: fmt.Sprintf("Scanning media"), Header: "Scanning media",
Content: fmt.Sprintf("%d jobs in progress\n%d jobs waiting", in_progress_length, up_next_length), Content: fmt.Sprintf("%d jobs in progress\n%d jobs waiting", in_progress_length, up_next_length),
}) })
}) })
@ -191,7 +204,9 @@ func AddAllToQueue() error {
} }
for _, user := range users { for _, user := range users {
AddUserToQueue(user) if err := AddUserToQueue(user); err != nil {
return errors.Wrapf(err, "failed to add user for scanning (%d)", user.ID)
}
} }
return nil return nil

View File

@ -0,0 +1,98 @@
package scanner
import (
"fmt"
"image"
"log"
"os"
"github.com/buckket/go-blurhash"
"github.com/photoview/photoview/api/graphql/models"
"gorm.io/gorm"
)
// GenerateBlurhashes queries the database for media that are missing a blurhash and computes one for them.
// This function blocks until all hashes have been computed
func GenerateBlurhashes(db *gorm.DB) error {
var results []*models.Media
processErrors := make([]error, 0)
query := db.Model(&models.Media{}).
Preload("MediaURL").
Joins("INNER JOIN media_urls ON media.id = media_urls.media_id").
Where("blurhash IS NULL").
Where("media_urls.purpose = 'thumbnail' OR media_urls.purpose = 'video-thumbnail'")
err := query.FindInBatches(&results, 50, func(tx *gorm.DB, batch int) error {
log.Printf("generating %d blurhashes", len(results))
hashes := make([]*string, len(results))
for i, row := range results {
thumbnail, err := row.GetThumbnail()
if err != nil {
log.Printf("failed to get thumbnail for media to generate blurhash (%d): %v", row.ID, err)
processErrors = append(processErrors, err)
continue
}
hashStr, err := GenerateBlurhashFromThumbnail(thumbnail)
if err != nil {
log.Printf("failed to generate blurhash for media (%d): %v", row.ID, err)
processErrors = append(processErrors, err)
continue
}
hashes[i] = &hashStr
results[i].Blurhash = &hashStr
}
tx.Save(results)
// if err := db.Update("blurhash", hashes).Error; err != nil {
// return err
// }
return nil
}).Error
if err != nil {
return err
}
if len(processErrors) == 0 {
return nil
} else {
return fmt.Errorf("failed to generate %d blurhashes", len(processErrors))
}
}
// GenerateBlurhashFromThumbnail generates a blurhash for a single media and stores it in the database
func GenerateBlurhashFromThumbnail(thumbnail *models.MediaURL) (string, error) {
thumbnail_path, err := thumbnail.CachedPath()
if err != nil {
return "", err
}
imageFile, err := os.Open(thumbnail_path)
if err != nil {
return "", err
}
imageData, _, err := image.Decode(imageFile)
if err != nil {
return "", err
}
hashStr, err := blurhash.Encode(4, 3, imageData)
if err != nil {
return "", err
}
// if err := db.Model(&models.Media{}).Where("id = ?", thumbnail.MediaID).Update("blurhash", hashStr).Error; err != nil {
// return "", err
// }
return hashStr, nil
}

446
ui/package-lock.json generated
View File

@ -26,6 +26,7 @@
"@types/url-join": "^4.0.1", "@types/url-join": "^4.0.1",
"autoprefixer": "^9.8.6", "autoprefixer": "^9.8.6",
"babel-plugin-graphql-tag": "^3.3.0", "babel-plugin-graphql-tag": "^3.3.0",
"blurhash": "^1.1.4",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"connect-history-api-fallback": "^1.6.0", "connect-history-api-fallback": "^1.6.0",
"copy-to-clipboard": "^3.3.1", "copy-to-clipboard": "^3.3.1",
@ -38,6 +39,7 @@
"prettier": "^2.5.1", "prettier": "^2.5.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-blurhash": "^0.1.3",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-hook-form": "^7.25.3", "react-hook-form": "^7.25.3",
@ -124,6 +126,53 @@
"npm": ">=6" "npm": ">=6"
} }
}, },
"node_modules/@apollographql/graphql-language-service-interface": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-interface/-/graphql-language-service-interface-2.0.2.tgz",
"integrity": "sha512-28wePK0hlIVjgmvMXMAUq8qRSjz9O+6lqFp4PzOTHtfJfSsjVe9EfjF98zTpHsTgT3HcOxmbqDZZy8jlXtOqEA==",
"dev": true,
"dependencies": {
"@apollographql/graphql-language-service-parser": "^2.0.0",
"@apollographql/graphql-language-service-types": "^2.0.0",
"@apollographql/graphql-language-service-utils": "^2.0.2"
},
"peerDependencies": {
"graphql": "^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0"
}
},
"node_modules/@apollographql/graphql-language-service-parser": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-parser/-/graphql-language-service-parser-2.0.2.tgz",
"integrity": "sha512-rpTPrEJu1PMaRQxz5P8BZWsixNNhYloS0H0dwTxNBuE3qctbARvR7o8UCKLsmKgTbo+cz3T3a6IAsWlkHgMWGg==",
"dev": true,
"dependencies": {
"@apollographql/graphql-language-service-types": "^2.0.0"
},
"peerDependencies": {
"graphql": "^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0"
}
},
"node_modules/@apollographql/graphql-language-service-types": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-types/-/graphql-language-service-types-2.0.2.tgz",
"integrity": "sha512-vE+Dz8pG+Xa1Z2nMl82LoO66lQ6JqBUjaXqLDvS3eMjvA3N4hf+YUDOWfPdNZ0zjhHhHXzUIIZCkax6bXfFbzQ==",
"dev": true,
"peerDependencies": {
"graphql": "^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0"
}
},
"node_modules/@apollographql/graphql-language-service-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-utils/-/graphql-language-service-utils-2.0.2.tgz",
"integrity": "sha512-fDj5rWlTi/czvUS5t7V7I45Ai6bOO3Z7JARYj21Y2xxfbRGtJi6h8FvLX0N/EbzQgo/fiZc/HAhtfwn+OCjD7A==",
"dev": true,
"dependencies": {
"@apollographql/graphql-language-service-types": "^2.0.0"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.16.7", "version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
@ -2800,9 +2849,9 @@
"integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=" "integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI="
}, },
"node_modules/@mapbox/tiny-sdf": { "node_modules/@mapbox/tiny-sdf": {
"version": "2.0.4", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.5.tgz",
"integrity": "sha512-CBtL2rhZiYmdIryksp0zh4Mmx54iClYfNb0mpYeHrZnq4z84lVjre7LBWGPEjWspEn6AiF0lxC1HaZDye89m3g==" "integrity": "sha512-OhXt2lS//WpLdkqrzo/KwB7SRD8AiNTFFzuo9n14IBupzIMa67yGItcK7I2W9D8Ghpa4T04Sw9FWsKCJG50Bxw=="
}, },
"node_modules/@mapbox/unitbezier": { "node_modules/@mapbox/unitbezier": {
"version": "0.0.0", "version": "0.0.0",
@ -3488,6 +3537,7 @@
"version": "4.9.3", "version": "4.9.3",
"resolved": "https://registry.npmjs.org/cli-ux/-/cli-ux-4.9.3.tgz", "resolved": "https://registry.npmjs.org/cli-ux/-/cli-ux-4.9.3.tgz",
"integrity": "sha512-/1owvF0SZ5Gn54cgrikJ0QskgTzeg30HGjkmjFoaHDJzAqFpuX1DBpFR8aLvsE1J5s9MgeYRENQK4BFwOag5VA==", "integrity": "sha512-/1owvF0SZ5Gn54cgrikJ0QskgTzeg30HGjkmjFoaHDJzAqFpuX1DBpFR8aLvsE1J5s9MgeYRENQK4BFwOag5VA==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@oclif/errors": "^1.2.2", "@oclif/errors": "^1.2.2",
@ -4582,9 +4632,9 @@
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "17.0.13", "version": "17.0.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.13.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.14.tgz",
"integrity": "sha512-Y86MAxASe25hNzlDbsviXl8jQHb0RDvKt4c40ZJQ1Don0AAL0STLZSs4N+6gLEO55pedy7r2cLwS+ZDxPm/2Bw==" "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng=="
}, },
"node_modules/@types/node-fetch": { "node_modules/@types/node-fetch": {
"version": "2.5.12", "version": "2.5.12",
@ -5724,53 +5774,6 @@
"npm": ">=6" "npm": ">=6"
} }
}, },
"node_modules/apollo-language-server/node_modules/@apollographql/graphql-language-service-interface": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-interface/-/graphql-language-service-interface-2.0.2.tgz",
"integrity": "sha512-28wePK0hlIVjgmvMXMAUq8qRSjz9O+6lqFp4PzOTHtfJfSsjVe9EfjF98zTpHsTgT3HcOxmbqDZZy8jlXtOqEA==",
"dev": true,
"dependencies": {
"@apollographql/graphql-language-service-parser": "^2.0.0",
"@apollographql/graphql-language-service-types": "^2.0.0",
"@apollographql/graphql-language-service-utils": "^2.0.2"
},
"peerDependencies": {
"graphql": "^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0"
}
},
"node_modules/apollo-language-server/node_modules/@apollographql/graphql-language-service-parser": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-parser/-/graphql-language-service-parser-2.0.2.tgz",
"integrity": "sha512-rpTPrEJu1PMaRQxz5P8BZWsixNNhYloS0H0dwTxNBuE3qctbARvR7o8UCKLsmKgTbo+cz3T3a6IAsWlkHgMWGg==",
"dev": true,
"dependencies": {
"@apollographql/graphql-language-service-types": "^2.0.0"
},
"peerDependencies": {
"graphql": "^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0"
}
},
"node_modules/apollo-language-server/node_modules/@apollographql/graphql-language-service-types": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-types/-/graphql-language-service-types-2.0.2.tgz",
"integrity": "sha512-vE+Dz8pG+Xa1Z2nMl82LoO66lQ6JqBUjaXqLDvS3eMjvA3N4hf+YUDOWfPdNZ0zjhHhHXzUIIZCkax6bXfFbzQ==",
"dev": true,
"peerDependencies": {
"graphql": "^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0"
}
},
"node_modules/apollo-language-server/node_modules/@apollographql/graphql-language-service-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-utils/-/graphql-language-service-utils-2.0.2.tgz",
"integrity": "sha512-fDj5rWlTi/czvUS5t7V7I45Ai6bOO3Z7JARYj21Y2xxfbRGtJi6h8FvLX0N/EbzQgo/fiZc/HAhtfwn+OCjD7A==",
"dev": true,
"dependencies": {
"@apollographql/graphql-language-service-types": "^2.0.0"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0"
}
},
"node_modules/apollo-language-server/node_modules/@endemolshinegroup/cosmiconfig-typescript-loader": { "node_modules/apollo-language-server/node_modules/@endemolshinegroup/cosmiconfig-typescript-loader": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-1.0.2.tgz",
@ -5811,18 +5814,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/apollo-language-server/node_modules/graphql": {
"version": "14.7.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz",
"integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==",
"dev": true,
"dependencies": {
"iterall": "^1.2.2"
},
"engines": {
"node": ">= 6.x"
}
},
"node_modules/apollo-language-server/node_modules/import-fresh": { "node_modules/apollo-language-server/node_modules/import-fresh": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
@ -6395,11 +6386,6 @@
"url": "https://tidelift.com/funding/github/npm/autoprefixer" "url": "https://tidelift.com/funding/github/npm/autoprefixer"
} }
}, },
"node_modules/autoprefixer/node_modules/picocolors": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
},
"node_modules/await-to-js": { "node_modules/await-to-js": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-2.1.1.tgz", "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-2.1.1.tgz",
@ -6410,9 +6396,9 @@
} }
}, },
"node_modules/axe-core": { "node_modules/axe-core": {
"version": "4.3.5", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.5.tgz", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.0.tgz",
"integrity": "sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA==", "integrity": "sha512-btWy2rze3NnxSSxb7LtNhPYYFrRoFBfjiGzmSc/5Hu47wApO2KNXjP/w7Nv2Uz/Fyr/pfEiwOkcXhDxu0jz5FA==",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
} }
@ -6972,6 +6958,11 @@
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
}, },
"node_modules/blurhash": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/blurhash/-/blurhash-1.1.4.tgz",
"integrity": "sha512-MXIPz6zwYUKayju+Uidf83KhH0vodZfeRl6Ich8Gu+KGl0JgKiFq9LsfqV7cVU5fKD/AotmduZqvOfrGKOfTaA=="
},
"node_modules/bn.js": { "node_modules/bn.js": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
@ -7270,6 +7261,11 @@
"url": "https://opencollective.com/browserslist" "url": "https://opencollective.com/browserslist"
} }
}, },
"node_modules/browserslist/node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/bser": { "node_modules/bser": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@ -7936,6 +7932,7 @@
"version": "5.6.3", "version": "5.6.3",
"resolved": "https://registry.npmjs.org/cli-ux/-/cli-ux-5.6.3.tgz", "resolved": "https://registry.npmjs.org/cli-ux/-/cli-ux-5.6.3.tgz",
"integrity": "sha512-/oDU4v8BiDjX2OKcSunGH0iGDiEtj2rZaGyqNuv9IT4CgcSMyVWAMfn0+rEHaOc4n9ka78B0wo1+N1QX89f7mw==", "integrity": "sha512-/oDU4v8BiDjX2OKcSunGH0iGDiEtj2rZaGyqNuv9IT4CgcSMyVWAMfn0+rEHaOc4n9ka78B0wo1+N1QX89f7mw==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@oclif/command": "^1.6.0", "@oclif/command": "^1.6.0",
@ -8597,9 +8594,9 @@
} }
}, },
"node_modules/core-js": { "node_modules/core-js": {
"version": "3.20.3", "version": "3.21.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.0.tgz",
"integrity": "sha512-vVl8j8ph6tRS3B8qir40H7yw7voy17xL0piAjlbBUsH7WIfzoedL/ZOr1OV9FyZQLWXsayOJyV4tnRyXR85/ag==", "integrity": "sha512-YUdI3fFu4TF/2WykQ2xzSiTQdldLB4KVuL9WeAy5XONZYt5Cun/fpQvctoKbCgvPhmzADeesTk/j2Rdx77AcKQ==",
"hasInstallScript": true, "hasInstallScript": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -8607,9 +8604,9 @@
} }
}, },
"node_modules/core-js-compat": { "node_modules/core-js-compat": {
"version": "3.20.3", "version": "3.21.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.3.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.0.tgz",
"integrity": "sha512-c8M5h0IkNZ+I92QhIpuSijOxGAcj3lgpsWdkCqmUTZNwidujF4r3pi6x1DCN+Vcs5qTS2XWWMfWSuCqyupX8gw==", "integrity": "sha512-OSXseNPSK2OPJa6GdtkMz/XxeXx8/CJvfhQWTqd6neuUraujcL4jVsjkLQz1OWnax8xVQJnRPe0V2jqNWORA+A==",
"dependencies": { "dependencies": {
"browserslist": "^4.19.1", "browserslist": "^4.19.1",
"semver": "7.0.0" "semver": "7.0.0"
@ -8628,9 +8625,9 @@
} }
}, },
"node_modules/core-js-pure": { "node_modules/core-js-pure": {
"version": "3.20.3", "version": "3.21.0",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.20.3.tgz", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.0.tgz",
"integrity": "sha512-Q2H6tQ5MtPtcC7f3HxJ48i4Q7T9ybPKgvWyuH7JXIoNa2pm0KuBnycsET/qw1SLLZYfbsbrZQNMeIOClb+6WIA==", "integrity": "sha512-VaJUunCZLnxuDbo1rNOzwbet9E1K9joiXS5+DQMPtgxd24wfsZbJZMMfQLGYMlCUvSxLfsRUUhoOR2x28mFfeg==",
"hasInstallScript": true, "hasInstallScript": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -9938,9 +9935,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.57", "version": "1.4.60",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.57.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.60.tgz",
"integrity": "sha512-FNC+P5K1n6pF+M0zIK+gFCoXcJhhzDViL3DRIGy2Fv5PohuSES1JHR7T+GlwxSxlzx4yYbsuzCZvHxcBSRCIOw==" "integrity": "sha512-h53hbEiKC6hijelDgxgkgAUC3PKyR7TmIfvjHnBjUGPMg/3sBuTyG6eDormw+lY24uUJvHkUPzB8dpK8b2u3Sw=="
}, },
"node_modules/elegant-spinner": { "node_modules/elegant-spinner": {
"version": "1.0.1", "version": "1.0.1",
@ -16992,9 +16989,9 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
}, },
"node_modules/lint-staged": { "node_modules/lint-staged": {
"version": "12.3.2", "version": "12.3.3",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.2.tgz", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.3.tgz",
"integrity": "sha512-gtw4Cbj01SuVSfAOXC6ivd/7VKHTj51yj5xV8TgktFmYNMsZzXuSd5/brqJEA93v63wL7R6iDlunMANOechC0A==", "integrity": "sha512-OqcLsqcPOqzvsfkxjeBpZylgJ3SRG1RYqc9LxC6tkt6tNsq1bNVkAixBwX09f6CobcHswzqVOCBpFR1Fck0+ag==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"cli-truncate": "^3.1.0", "cli-truncate": "^3.1.0",
@ -17233,9 +17230,9 @@
} }
}, },
"node_modules/listr2": { "node_modules/listr2": {
"version": "4.0.1", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.1.tgz", "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.2.tgz",
"integrity": "sha512-D65Nl+zyYHL2jQBGmxtH/pU8koPZo5C8iCNE8EoB04RwPgQG1wuaKwVbeZv9LJpiH4Nxs0FCp+nNcG8OqpniiA==", "integrity": "sha512-YcgwfCWpvPbj9FLUGqvdFvd3hrFWKpOeuXznRgfWEJ7RNr8b/IKKIKZABHx3aU+4CWN/iSAFFSReziQG6vTeIA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"cli-truncate": "^2.1.0", "cli-truncate": "^2.1.0",
@ -17855,9 +17852,9 @@
} }
}, },
"node_modules/mapbox-gl": { "node_modules/mapbox-gl": {
"version": "2.6.1", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.6.1.tgz", "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.7.0.tgz",
"integrity": "sha512-faGbSZfcFuZ4GWwkWnJrRD3oICZAt/mVKnGuOmeBobCj9onfTRz270qSoOXeRBKd3po5VA2cCPI91YwA8DsAoQ==", "integrity": "sha512-7sNoQIpizMjoQkFIcxpWzFCcMd8GJFN0Po00oFZGm1X7xS5XMrzwu+ClfO/ehZqKLa9IS7E3Pj0e2PT+9KeqaA==",
"dependencies": { "dependencies": {
"@mapbox/geojson-rewind": "^0.5.1", "@mapbox/geojson-rewind": "^0.5.1",
"@mapbox/geojson-types": "^1.0.2", "@mapbox/geojson-types": "^1.0.2",
@ -19608,9 +19605,9 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
@ -20955,14 +20952,19 @@
"url": "https://opencollective.com/postcss/" "url": "https://opencollective.com/postcss/"
} }
}, },
"node_modules/postcss-safe-parser/node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/postcss-safe-parser/node_modules/postcss": { "node_modules/postcss-safe-parser/node_modules/postcss": {
"version": "8.4.5", "version": "8.4.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz",
"integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==",
"dependencies": { "dependencies": {
"nanoid": "^3.1.30", "nanoid": "^3.2.0",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.1" "source-map-js": "^1.0.2"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
@ -21051,11 +21053,6 @@
"node": ">=6.14.4" "node": ">=6.14.4"
} }
}, },
"node_modules/postcss/node_modules/picocolors": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
},
"node_modules/postcss/node_modules/source-map": { "node_modules/postcss/node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -21310,14 +21307,19 @@
"purgecss": "bin/purgecss.js" "purgecss": "bin/purgecss.js"
} }
}, },
"node_modules/purgecss/node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/purgecss/node_modules/postcss": { "node_modules/purgecss/node_modules/postcss": {
"version": "8.4.5", "version": "8.4.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz",
"integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==",
"dependencies": { "dependencies": {
"nanoid": "^3.1.30", "nanoid": "^3.2.0",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.1" "source-map-js": "^1.0.2"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
@ -21532,6 +21534,15 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/react-blurhash": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.1.3.tgz",
"integrity": "sha512-Q9lqbXg92NU6/2DoIl/cBM8YWL+Z4X66OiG4aT9ozOgjBwx104LHFCH5stf6aF+s0Q9Wf310Ul+dG+VXJltmPg==",
"peerDependencies": {
"blurhash": "^1.1.1",
"react": ">=15"
}
},
"node_modules/react-dev-utils": { "node_modules/react-dev-utils": {
"version": "11.0.4", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
@ -28618,6 +28629,42 @@
"integrity": "sha512-KxZiw0Us3k1d0YkJDhOpVH5rJ+mBfjXcgoRoCcslbgirjgLotKMzOcx4PZ7YTEvvEROmvG7X3Aon41GvMmyGsw==", "integrity": "sha512-KxZiw0Us3k1d0YkJDhOpVH5rJ+mBfjXcgoRoCcslbgirjgLotKMzOcx4PZ7YTEvvEROmvG7X3Aon41GvMmyGsw==",
"dev": true "dev": true
}, },
"@apollographql/graphql-language-service-interface": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-interface/-/graphql-language-service-interface-2.0.2.tgz",
"integrity": "sha512-28wePK0hlIVjgmvMXMAUq8qRSjz9O+6lqFp4PzOTHtfJfSsjVe9EfjF98zTpHsTgT3HcOxmbqDZZy8jlXtOqEA==",
"dev": true,
"requires": {
"@apollographql/graphql-language-service-parser": "^2.0.0",
"@apollographql/graphql-language-service-types": "^2.0.0",
"@apollographql/graphql-language-service-utils": "^2.0.2"
}
},
"@apollographql/graphql-language-service-parser": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-parser/-/graphql-language-service-parser-2.0.2.tgz",
"integrity": "sha512-rpTPrEJu1PMaRQxz5P8BZWsixNNhYloS0H0dwTxNBuE3qctbARvR7o8UCKLsmKgTbo+cz3T3a6IAsWlkHgMWGg==",
"dev": true,
"requires": {
"@apollographql/graphql-language-service-types": "^2.0.0"
}
},
"@apollographql/graphql-language-service-types": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-types/-/graphql-language-service-types-2.0.2.tgz",
"integrity": "sha512-vE+Dz8pG+Xa1Z2nMl82LoO66lQ6JqBUjaXqLDvS3eMjvA3N4hf+YUDOWfPdNZ0zjhHhHXzUIIZCkax6bXfFbzQ==",
"dev": true,
"requires": {}
},
"@apollographql/graphql-language-service-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-utils/-/graphql-language-service-utils-2.0.2.tgz",
"integrity": "sha512-fDj5rWlTi/czvUS5t7V7I45Ai6bOO3Z7JARYj21Y2xxfbRGtJi6h8FvLX0N/EbzQgo/fiZc/HAhtfwn+OCjD7A==",
"dev": true,
"requires": {
"@apollographql/graphql-language-service-types": "^2.0.0"
}
},
"@babel/code-frame": { "@babel/code-frame": {
"version": "7.16.7", "version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
@ -30492,9 +30539,9 @@
"integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=" "integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI="
}, },
"@mapbox/tiny-sdf": { "@mapbox/tiny-sdf": {
"version": "2.0.4", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.5.tgz",
"integrity": "sha512-CBtL2rhZiYmdIryksp0zh4Mmx54iClYfNb0mpYeHrZnq4z84lVjre7LBWGPEjWspEn6AiF0lxC1HaZDye89m3g==" "integrity": "sha512-OhXt2lS//WpLdkqrzo/KwB7SRD8AiNTFFzuo9n14IBupzIMa67yGItcK7I2W9D8Ghpa4T04Sw9FWsKCJG50Bxw=="
}, },
"@mapbox/unitbezier": { "@mapbox/unitbezier": {
"version": "0.0.0", "version": "0.0.0",
@ -31837,9 +31884,9 @@
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ=="
}, },
"@types/node": { "@types/node": {
"version": "17.0.13", "version": "17.0.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.13.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.14.tgz",
"integrity": "sha512-Y86MAxASe25hNzlDbsviXl8jQHb0RDvKt4c40ZJQ1Don0AAL0STLZSs4N+6gLEO55pedy7r2cLwS+ZDxPm/2Bw==" "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng=="
}, },
"@types/node-fetch": { "@types/node-fetch": {
"version": "2.5.12", "version": "2.5.12",
@ -32544,7 +32591,7 @@
"git-url-parse": "11.5.0", "git-url-parse": "11.5.0",
"glob": "7.2.0", "glob": "7.2.0",
"global-agent": "2.2.0", "global-agent": "2.2.0",
"graphql": "14.0.2 - 14.2.0 || ^14.3.1 || ^15.0.0", "graphql": "^15.0.0",
"graphql-tag": "2.12.4", "graphql-tag": "2.12.4",
"listr": "0.14.3", "listr": "0.14.3",
"lodash.identity": "3.0.0", "lodash.identity": "3.0.0",
@ -32761,7 +32808,7 @@
"cosmiconfig": "^5.0.6", "cosmiconfig": "^5.0.6",
"dotenv": "^8.0.0", "dotenv": "^8.0.0",
"glob": "^7.1.3", "glob": "^7.1.3",
"graphql": "14.0.2 - 14.2.0 || ^14.3.1 || ^15.0.0", "graphql": "^15.0.0",
"graphql-tag": "^2.10.1", "graphql-tag": "^2.10.1",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lodash.merge": "^4.6.1", "lodash.merge": "^4.6.1",
@ -32771,42 +32818,6 @@
"vscode-uri": "1.0.6" "vscode-uri": "1.0.6"
}, },
"dependencies": { "dependencies": {
"@apollographql/graphql-language-service-interface": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-interface/-/graphql-language-service-interface-2.0.2.tgz",
"integrity": "sha512-28wePK0hlIVjgmvMXMAUq8qRSjz9O+6lqFp4PzOTHtfJfSsjVe9EfjF98zTpHsTgT3HcOxmbqDZZy8jlXtOqEA==",
"dev": true,
"requires": {
"@apollographql/graphql-language-service-parser": "^2.0.0",
"@apollographql/graphql-language-service-types": "^2.0.0",
"@apollographql/graphql-language-service-utils": "^2.0.2"
}
},
"@apollographql/graphql-language-service-parser": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-parser/-/graphql-language-service-parser-2.0.2.tgz",
"integrity": "sha512-rpTPrEJu1PMaRQxz5P8BZWsixNNhYloS0H0dwTxNBuE3qctbARvR7o8UCKLsmKgTbo+cz3T3a6IAsWlkHgMWGg==",
"dev": true,
"requires": {
"@apollographql/graphql-language-service-types": "^2.0.0"
}
},
"@apollographql/graphql-language-service-types": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-types/-/graphql-language-service-types-2.0.2.tgz",
"integrity": "sha512-vE+Dz8pG+Xa1Z2nMl82LoO66lQ6JqBUjaXqLDvS3eMjvA3N4hf+YUDOWfPdNZ0zjhHhHXzUIIZCkax6bXfFbzQ==",
"dev": true,
"requires": {}
},
"@apollographql/graphql-language-service-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@apollographql/graphql-language-service-utils/-/graphql-language-service-utils-2.0.2.tgz",
"integrity": "sha512-fDj5rWlTi/czvUS5t7V7I45Ai6bOO3Z7JARYj21Y2xxfbRGtJi6h8FvLX0N/EbzQgo/fiZc/HAhtfwn+OCjD7A==",
"dev": true,
"requires": {
"@apollographql/graphql-language-service-types": "^2.0.0"
}
},
"@endemolshinegroup/cosmiconfig-typescript-loader": { "@endemolshinegroup/cosmiconfig-typescript-loader": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-1.0.2.tgz",
@ -32837,15 +32848,6 @@
"parse-json": "^4.0.0" "parse-json": "^4.0.0"
} }
}, },
"graphql": {
"version": "14.7.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz",
"integrity": "sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==",
"dev": true,
"requires": {
"iterall": "^1.2.2"
}
},
"import-fresh": { "import-fresh": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
@ -33305,13 +33307,6 @@
"picocolors": "^0.2.1", "picocolors": "^0.2.1",
"postcss": "^7.0.32", "postcss": "^7.0.32",
"postcss-value-parser": "^4.1.0" "postcss-value-parser": "^4.1.0"
},
"dependencies": {
"picocolors": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
}
} }
}, },
"await-to-js": { "await-to-js": {
@ -33321,9 +33316,9 @@
"dev": true "dev": true
}, },
"axe-core": { "axe-core": {
"version": "4.3.5", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.5.tgz", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.0.tgz",
"integrity": "sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA==" "integrity": "sha512-btWy2rze3NnxSSxb7LtNhPYYFrRoFBfjiGzmSc/5Hu47wApO2KNXjP/w7Nv2Uz/Fyr/pfEiwOkcXhDxu0jz5FA=="
}, },
"axobject-query": { "axobject-query": {
"version": "2.2.0", "version": "2.2.0",
@ -33756,6 +33751,11 @@
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
}, },
"blurhash": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/blurhash/-/blurhash-1.1.4.tgz",
"integrity": "sha512-MXIPz6zwYUKayju+Uidf83KhH0vodZfeRl6Ich8Gu+KGl0JgKiFq9LsfqV7cVU5fKD/AotmduZqvOfrGKOfTaA=="
},
"bn.js": { "bn.js": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
@ -34004,6 +34004,13 @@
"escalade": "^3.1.1", "escalade": "^3.1.1",
"node-releases": "^2.0.1", "node-releases": "^2.0.1",
"picocolors": "^1.0.0" "picocolors": "^1.0.0"
},
"dependencies": {
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
}
} }
}, },
"bser": { "bser": {
@ -35053,14 +35060,14 @@
} }
}, },
"core-js": { "core-js": {
"version": "3.20.3", "version": "3.21.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.0.tgz",
"integrity": "sha512-vVl8j8ph6tRS3B8qir40H7yw7voy17xL0piAjlbBUsH7WIfzoedL/ZOr1OV9FyZQLWXsayOJyV4tnRyXR85/ag==" "integrity": "sha512-YUdI3fFu4TF/2WykQ2xzSiTQdldLB4KVuL9WeAy5XONZYt5Cun/fpQvctoKbCgvPhmzADeesTk/j2Rdx77AcKQ=="
}, },
"core-js-compat": { "core-js-compat": {
"version": "3.20.3", "version": "3.21.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.3.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.0.tgz",
"integrity": "sha512-c8M5h0IkNZ+I92QhIpuSijOxGAcj3lgpsWdkCqmUTZNwidujF4r3pi6x1DCN+Vcs5qTS2XWWMfWSuCqyupX8gw==", "integrity": "sha512-OSXseNPSK2OPJa6GdtkMz/XxeXx8/CJvfhQWTqd6neuUraujcL4jVsjkLQz1OWnax8xVQJnRPe0V2jqNWORA+A==",
"requires": { "requires": {
"browserslist": "^4.19.1", "browserslist": "^4.19.1",
"semver": "7.0.0" "semver": "7.0.0"
@ -35074,9 +35081,9 @@
} }
}, },
"core-js-pure": { "core-js-pure": {
"version": "3.20.3", "version": "3.21.0",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.20.3.tgz", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.0.tgz",
"integrity": "sha512-Q2H6tQ5MtPtcC7f3HxJ48i4Q7T9ybPKgvWyuH7JXIoNa2pm0KuBnycsET/qw1SLLZYfbsbrZQNMeIOClb+6WIA==" "integrity": "sha512-VaJUunCZLnxuDbo1rNOzwbet9E1K9joiXS5+DQMPtgxd24wfsZbJZMMfQLGYMlCUvSxLfsRUUhoOR2x28mFfeg=="
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.3", "version": "1.0.3",
@ -36116,9 +36123,9 @@
"integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA=="
}, },
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.4.57", "version": "1.4.60",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.57.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.60.tgz",
"integrity": "sha512-FNC+P5K1n6pF+M0zIK+gFCoXcJhhzDViL3DRIGy2Fv5PohuSES1JHR7T+GlwxSxlzx4yYbsuzCZvHxcBSRCIOw==" "integrity": "sha512-h53hbEiKC6hijelDgxgkgAUC3PKyR7TmIfvjHnBjUGPMg/3sBuTyG6eDormw+lY24uUJvHkUPzB8dpK8b2u3Sw=="
}, },
"elegant-spinner": { "elegant-spinner": {
"version": "1.0.1", "version": "1.0.1",
@ -41462,9 +41469,9 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
}, },
"lint-staged": { "lint-staged": {
"version": "12.3.2", "version": "12.3.3",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.2.tgz", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.3.tgz",
"integrity": "sha512-gtw4Cbj01SuVSfAOXC6ivd/7VKHTj51yj5xV8TgktFmYNMsZzXuSd5/brqJEA93v63wL7R6iDlunMANOechC0A==", "integrity": "sha512-OqcLsqcPOqzvsfkxjeBpZylgJ3SRG1RYqc9LxC6tkt6tNsq1bNVkAixBwX09f6CobcHswzqVOCBpFR1Fck0+ag==",
"dev": true, "dev": true,
"requires": { "requires": {
"cli-truncate": "^3.1.0", "cli-truncate": "^3.1.0",
@ -41645,9 +41652,9 @@
} }
}, },
"listr2": { "listr2": {
"version": "4.0.1", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.1.tgz", "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.2.tgz",
"integrity": "sha512-D65Nl+zyYHL2jQBGmxtH/pU8koPZo5C8iCNE8EoB04RwPgQG1wuaKwVbeZv9LJpiH4Nxs0FCp+nNcG8OqpniiA==", "integrity": "sha512-YcgwfCWpvPbj9FLUGqvdFvd3hrFWKpOeuXznRgfWEJ7RNr8b/IKKIKZABHx3aU+4CWN/iSAFFSReziQG6vTeIA==",
"dev": true, "dev": true,
"requires": { "requires": {
"cli-truncate": "^2.1.0", "cli-truncate": "^2.1.0",
@ -42132,9 +42139,9 @@
} }
}, },
"mapbox-gl": { "mapbox-gl": {
"version": "2.6.1", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.6.1.tgz", "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-2.7.0.tgz",
"integrity": "sha512-faGbSZfcFuZ4GWwkWnJrRD3oICZAt/mVKnGuOmeBobCj9onfTRz270qSoOXeRBKd3po5VA2cCPI91YwA8DsAoQ==", "integrity": "sha512-7sNoQIpizMjoQkFIcxpWzFCcMd8GJFN0Po00oFZGm1X7xS5XMrzwu+ClfO/ehZqKLa9IS7E3Pj0e2PT+9KeqaA==",
"requires": { "requires": {
"@mapbox/geojson-rewind": "^0.5.1", "@mapbox/geojson-rewind": "^0.5.1",
"@mapbox/geojson-types": "^1.0.2", "@mapbox/geojson-types": "^1.0.2",
@ -43531,9 +43538,9 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
}, },
"picocolors": { "picocolors": {
"version": "1.0.0", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
}, },
"picomatch": { "picomatch": {
"version": "2.3.1", "version": "2.3.1",
@ -43693,11 +43700,6 @@
"source-map": "^0.6.1" "source-map": "^0.6.1"
}, },
"dependencies": { "dependencies": {
"picocolors": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
},
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -44638,14 +44640,19 @@
"postcss": "^8.1.0" "postcss": "^8.1.0"
}, },
"dependencies": { "dependencies": {
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"postcss": { "postcss": {
"version": "8.4.5", "version": "8.4.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz",
"integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==",
"requires": { "requires": {
"nanoid": "^3.1.30", "nanoid": "^3.2.0",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.1" "source-map-js": "^1.0.2"
} }
} }
} }
@ -44920,14 +44927,19 @@
"postcss-selector-parser": "^6.0.6" "postcss-selector-parser": "^6.0.6"
}, },
"dependencies": { "dependencies": {
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"postcss": { "postcss": {
"version": "8.4.5", "version": "8.4.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz",
"integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==", "integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==",
"requires": { "requires": {
"nanoid": "^3.1.30", "nanoid": "^3.2.0",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.1" "source-map-js": "^1.0.2"
} }
} }
} }
@ -45080,6 +45092,12 @@
"whatwg-fetch": "^3.4.1" "whatwg-fetch": "^3.4.1"
} }
}, },
"react-blurhash": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.1.3.tgz",
"integrity": "sha512-Q9lqbXg92NU6/2DoIl/cBM8YWL+Z4X66OiG4aT9ozOgjBwx104LHFCH5stf6aF+s0Q9Wf310Ul+dG+VXJltmPg==",
"requires": {}
},
"react-dev-utils": { "react-dev-utils": {
"version": "11.0.4", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",

View File

@ -26,6 +26,7 @@
"@types/url-join": "^4.0.1", "@types/url-join": "^4.0.1",
"autoprefixer": "^9.8.6", "autoprefixer": "^9.8.6",
"babel-plugin-graphql-tag": "^3.3.0", "babel-plugin-graphql-tag": "^3.3.0",
"blurhash": "^1.1.4",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"connect-history-api-fallback": "^1.6.0", "connect-history-api-fallback": "^1.6.0",
"copy-to-clipboard": "^3.3.1", "copy-to-clipboard": "^3.3.1",
@ -38,6 +39,7 @@
"prettier": "^2.5.1", "prettier": "^2.5.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-blurhash": "^0.1.3",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-hook-form": "^7.25.3", "react-hook-form": "^7.25.3",
@ -79,6 +81,9 @@
"lint-staged": "^12.3.2", "lint-staged": "^12.3.2",
"tsc-files": "1.1.2" "tsc-files": "1.1.2"
}, },
"overrides": {
"graphql": "^15.0.0"
},
"prettier": { "prettier": {
"trailingComma": "es5", "trailingComma": "es5",
"tabWidth": 2, "tabWidth": 2,

View File

@ -43,6 +43,7 @@ const ALBUM_QUERY = gql`
) { ) {
id id
type type
blurhash
thumbnail { thumbnail {
url url
width width

View File

@ -3,102 +3,106 @@
// @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
/**
* A short string that can be used to generate a blured version of the media, to show while the original is loading
*/
blurhash: string | null
/** /**
* 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 +110,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

@ -28,6 +28,7 @@ export const SINGLE_FACE_GROUP = gql`
id id
type type
title title
blurhash
thumbnail { thumbnail {
url url
width width

View File

@ -3,80 +3,84 @@
// @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
/**
* A short string that can be used to generate a blured version of the media, to show while the original is loading
*/
blurhash: string | null
/** /**
* 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

@ -15,6 +15,7 @@ const QUERY_MEDIA = gql`
mediaList(ids: $mediaIDs) { mediaList(ids: $mediaIDs) {
id id
title title
blurhash
thumbnail { thumbnail {
url url
width width

View File

@ -11,9 +11,9 @@ export interface placePageMapboxToken {
/** /**
* 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
/** /**
* 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; myMediaGeoJson: any
} }

View File

@ -3,86 +3,90 @@
// @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
/**
* A short string that can be used to generate a blured version of the media, to show while the original is loading
*/
blurhash: string | null
/** /**
* 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

@ -43,6 +43,7 @@ export const SHARE_ALBUM_QUERY = gql`
id id
title title
type type
blurhash
thumbnail { thumbnail {
url url
width width

View File

@ -3,179 +3,200 @@
// @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_coordinates {
__typename: 'Coordinates'
/**
* GPS latitude in degrees
*/
latitude: number
/**
* GPS longitude in degrees
*/
longitude: number
} }
export interface shareAlbumQuery_album_media_exif { export interface shareAlbumQuery_album_media_exif {
__typename: "MediaEXIF"; __typename: 'MediaEXIF'
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
/**
* GPS coordinates of where the image was taken
*/
coordinates: shareAlbumQuery_album_media_exif_coordinates | 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
/**
* A short string that can be used to generate a blured version of the media, to show while the original is loading
*/
blurhash: string | null
/** /**
* 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 +204,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

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

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

@ -38,6 +38,7 @@ type AlbumGalleryAlbum = {
*/ */
videoWeb: albumQuery_album_media_videoWeb | null videoWeb: albumQuery_album_media_videoWeb | null
favorite?: boolean favorite?: boolean
blurhash: string | null
}[] }[]
} }

View File

@ -25,6 +25,7 @@ const StyledPhoto = styled(ProtectedImage)`
type LazyPhotoProps = { type LazyPhotoProps = {
src?: string src?: string
blurhash: string | null
} }
const LazyPhoto = (photoProps: LazyPhotoProps) => { const LazyPhoto = (photoProps: LazyPhotoProps) => {
@ -138,6 +139,7 @@ type MediaThumbnailProps = {
media: { media: {
id: string id: string
type: MediaType type: MediaType
blurhash: string | null
favorite?: boolean favorite?: boolean
thumbnail: null | { thumbnail: null | {
url: string url: string
@ -201,7 +203,7 @@ export const MediaThumbnail = ({
height: `200px`, height: `200px`,
}} }}
> >
<LazyPhoto src={media.thumbnail?.url} /> <LazyPhoto src={media.thumbnail?.url} blurhash={media.blurhash} />
</div> </div>
<PhotoOverlay active={active}> <PhotoOverlay active={active}>
{videoIcon} {videoIcon}

View File

@ -26,6 +26,7 @@ test('photo gallery with media', () => {
}, },
highRes: null, highRes: null,
videoWeb: null, videoWeb: null,
blurhash: null,
favorite: false, favorite: false,
__typename: 'Media', __typename: 'Media',
}, },
@ -35,6 +36,7 @@ test('photo gallery with media', () => {
thumbnail: null, thumbnail: null,
highRes: null, highRes: null,
videoWeb: null, videoWeb: null,
blurhash: null,
favorite: false, favorite: false,
__typename: 'Media', __typename: 'Media',
}, },
@ -44,6 +46,7 @@ test('photo gallery with media', () => {
thumbnail: null, thumbnail: null,
highRes: null, highRes: null,
videoWeb: null, videoWeb: null,
blurhash: null,
favorite: false, favorite: false,
__typename: 'Media', __typename: 'Media',
}, },
@ -100,6 +103,7 @@ describe('photo gallery presenting', () => {
}, },
highRes: null, highRes: null,
videoWeb: null, videoWeb: null,
blurhash: null,
favorite: false, favorite: false,
__typename: 'Media', __typename: 'Media',
}, },

View File

@ -37,6 +37,7 @@ export const PhotoFiller = styled.div`
export interface PhotoGalleryProps_Media extends PresentMediaProps_Media { export interface PhotoGalleryProps_Media extends PresentMediaProps_Media {
thumbnail: sidebarMediaQuery_media_thumbnail | null thumbnail: sidebarMediaQuery_media_thumbnail | null
blurhash: string | null
favorite?: boolean favorite?: boolean
} }

View File

@ -3,6 +3,7 @@ import React, { DetailedHTMLProps, ImgHTMLAttributes } from 'react'
import { useRef } from 'react' import { useRef } from 'react'
import { useState } from 'react' import { useState } from 'react'
import { useEffect } from 'react' import { useEffect } from 'react'
import { BlurhashCanvas } from 'react-blurhash'
import { isNil } from '../../helpers/utils' import { isNil } from '../../helpers/utils'
const isNativeLazyLoadSupported = 'loading' in document.createElement('img') const isNativeLazyLoadSupported = 'loading' in document.createElement('img')
@ -31,6 +32,7 @@ export interface ProtectedImageProps
src?: string src?: string
key?: string key?: string
lazyLoading?: boolean lazyLoading?: boolean
blurhash?: string | null
} }
/** /**
@ -42,10 +44,15 @@ export interface ProtectedImageProps
export const ProtectedImage = ({ export const ProtectedImage = ({
src, src,
lazyLoading, lazyLoading,
blurhash,
...props ...props
}: ProtectedImageProps) => { }: ProtectedImageProps) => {
const [loaded, setLoaded] = useState(false)
const url = getProtectedUrl(src) || placeholder const url = getProtectedUrl(src) || placeholder
const didLoad = () => setLoaded(true)
if (!lazyLoading) { if (!lazyLoading) {
return ( return (
<img {...props} src={url} loading="eager" crossOrigin="use-credentials" /> <img {...props} src={url} loading="eager" crossOrigin="use-credentials" />
@ -53,12 +60,26 @@ export const ProtectedImage = ({
} }
if (!isNativeLazyLoadSupported) { if (!isNativeLazyLoadSupported) {
return <FallbackLazyloadedImage src={url} {...props} /> return <FallbackLazyloadedImage src={url} blurhash={blurhash} {...props} />
} }
// load with native lazy loading // load with native lazy loading
return ( return (
<img {...props} src={url} loading="lazy" crossOrigin="use-credentials" /> <div className="w-full h-full">
<img
{...props}
src={url}
loading="lazy"
crossOrigin="use-credentials"
onLoad={didLoad}
/>
{blurhash && !loaded && (
<BlurhashCanvas
className="absolute w-full h-full top-0"
hash={blurhash}
/>
)}
</div>
) )
} }
@ -68,15 +89,22 @@ interface FallbackLazyloadedImageProps
'src' 'src'
> { > {
src?: string src?: string
blurhash?: string | null
} }
const FallbackLazyloadedImage = ({ const FallbackLazyloadedImage = ({
src, src,
blurhash,
className,
...props ...props
}: FallbackLazyloadedImageProps) => { }: FallbackLazyloadedImageProps) => {
const [inView, setInView] = useState(false) const [inView, setInView] = useState(false)
const [loaded, setLoaded] = useState(false)
const imgRef = useRef<HTMLDivElement>(null) const imgRef = useRef<HTMLDivElement>(null)
const didLoad = () => setLoaded(true)
useEffect(() => { useEffect(() => {
const imgElm = imgRef.current const imgElm = imgRef.current
if (isNil(imgElm) || inView) return if (isNil(imgElm) || inView) return
@ -107,13 +135,33 @@ const FallbackLazyloadedImage = ({
}, [imgRef]) }, [imgRef])
if (inView) { if (inView) {
return <img {...props} src={src} crossOrigin="use-credentials" /> return (
<div className={className}>
<img
className="w-full h-full"
{...props}
src={src}
onLoad={didLoad}
crossOrigin="use-credentials"
/>
{blurhash && !loaded && (
<BlurhashCanvas
className="absolute w-full h-full top-0"
hash={blurhash}
/>
)}
</div>
)
} else { } else {
return ( return (
<div <div ref={imgRef} className={classNames(className, 'bg-[#eee]')}>
ref={imgRef} {blurhash && (
className={classNames(props.className, 'bg-[#eee]')} <BlurhashCanvas
></div> className="absolute w-full h-full top-0"
hash={blurhash}
/>
)}
</div>
) )
} }
} }

View File

@ -11,6 +11,7 @@ describe('photo gallery reducer', () => {
id: '1', id: '1',
highRes: null, highRes: null,
thumbnail: null, thumbnail: null,
blurhash: null,
type: MediaType.Photo, type: MediaType.Photo,
}, },
{ {
@ -18,6 +19,7 @@ describe('photo gallery reducer', () => {
id: '2', id: '2',
highRes: null, highRes: null,
thumbnail: null, thumbnail: null,
blurhash: null,
type: MediaType.Photo, type: MediaType.Photo,
}, },
{ {
@ -25,6 +27,7 @@ describe('photo gallery reducer', () => {
id: '3', id: '3',
highRes: null, highRes: null,
thumbnail: null, thumbnail: null,
blurhash: null,
type: MediaType.Photo, type: MediaType.Photo,
}, },
], ],

View File

@ -34,6 +34,4 @@ test('timeline with media', async () => {
expect(await screen.findAllByRole('link')).toHaveLength(4) expect(await screen.findAllByRole('link')).toHaveLength(4)
expect(await screen.findAllByRole('img')).toHaveLength(5) expect(await screen.findAllByRole('img')).toHaveLength(5)
screen.debug()
}) })

View File

@ -35,6 +35,7 @@ export const MY_TIMELINE_QUERY = gql`
id id
title title
type type
blurhash
thumbnail { thumbnail {
url url
width width

View File

@ -60,6 +60,10 @@ export interface myTimeline_myTimeline {
id: string id: string
title: string title: string
type: MediaType type: MediaType
/**
* A short string that can be used to generate a blured version of the media, to show while the original is loading
*/
blurhash: string | null
/** /**
* URL to display the media in a smaller resolution * URL to display the media in a smaller resolution
*/ */

View File

@ -23,6 +23,7 @@ export const timelineData: myTimeline_myTimeline[] = [
favorite: false, favorite: false,
album: { __typename: 'Album', id: '522', title: 'random' }, album: { __typename: 'Album', id: '522', title: 'random' },
date: '2020-12-13T18:03:40Z', date: '2020-12-13T18:03:40Z',
blurhash: null,
}, },
{ {
__typename: 'Media', __typename: 'Media',
@ -45,6 +46,7 @@ export const timelineData: myTimeline_myTimeline[] = [
favorite: false, favorite: false,
album: { __typename: 'Album', id: '523', title: 'another_album' }, album: { __typename: 'Album', id: '523', title: 'another_album' },
date: '2020-11-25T16:14:33Z', date: '2020-11-25T16:14:33Z',
blurhash: null,
}, },
{ {
__typename: 'Media', __typename: 'Media',
@ -67,6 +69,7 @@ export const timelineData: myTimeline_myTimeline[] = [
favorite: false, favorite: false,
album: { __typename: 'Album', id: '523', title: 'another_album' }, album: { __typename: 'Album', id: '523', title: 'another_album' },
date: '2020-11-25T16:43:59Z', date: '2020-11-25T16:43:59Z',
blurhash: null,
}, },
{ {
__typename: 'Media', __typename: 'Media',
@ -89,6 +92,7 @@ export const timelineData: myTimeline_myTimeline[] = [
favorite: false, favorite: false,
album: { __typename: 'Album', id: '522', title: 'random' }, album: { __typename: 'Album', id: '522', title: 'random' },
date: '2020-11-25T16:14:33Z', date: '2020-11-25T16:14:33Z',
blurhash: null,
}, },
{ {
__typename: 'Media', __typename: 'Media',
@ -111,5 +115,6 @@ export const timelineData: myTimeline_myTimeline[] = [
favorite: false, favorite: false,
album: { __typename: 'Album', id: '522', title: 'random' }, album: { __typename: 'Album', id: '522', title: 'random' },
date: '2020-11-09T15:38:09Z', date: '2020-11-09T15:38:09Z',
blurhash: null,
}, },
] ]