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/agnivade/levenshtein v1.1.1 // indirect
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/felixge/httpsnoop v1.0.2 // indirect
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/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/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/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
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
SideCarHash *string `gorm:"unique"`
Faces []*ImageFace `gorm:"constraint:OnDelete:CASCADE;"`
Blurhash *string `gorm:""`
// Only used internally
CounterpartPath *string `gorm:"-"`
@ -49,6 +50,21 @@ func (m *Media) Date() time.Time {
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
const (

View File

@ -67,3 +67,56 @@ func TestMediaURLGetURL(t *testing.T) {
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!
"The date the image was shot or the date it was imported as a fallback"
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!]!
downloads: [MediaDownload!]!

View File

@ -9,6 +9,7 @@ import (
"github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/graphql/notification"
"github.com/photoview/photoview/api/scanner/scanner_cache"
"github.com/photoview/photoview/api/scanner/scanner_utils"
"github.com/photoview/photoview/api/utils"
"github.com/pkg/errors"
"gorm.io/gorm"
@ -156,8 +157,20 @@ func (queue *ScannerQueue) processQueue(notifyThrottle *utils.Throttle) {
notification.BroadcastNotification(&models.Notification{
Key: "global-scanner-progress",
Type: models.NotificationTypeMessage,
Header: fmt.Sprintf("Scanner complete"),
Content: fmt.Sprintf("All jobs have been scanned"),
Header: "Generating blurhashes",
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,
})
} else {
@ -165,7 +178,7 @@ func (queue *ScannerQueue) processQueue(notifyThrottle *utils.Throttle) {
notification.BroadcastNotification(&models.Notification{
Key: "global-scanner-progress",
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),
})
})
@ -191,7 +204,9 @@ func AddAllToQueue() error {
}
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

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

View File

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

View File

@ -3,102 +3,106 @@
// @generated
// 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
// ====================================================
export interface albumQuery_album_subAlbums_thumbnail_thumbnail {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
}
export interface albumQuery_album_subAlbums_thumbnail {
__typename: "Media";
id: string;
__typename: 'Media'
id: string
/**
* 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 {
__typename: "Album";
id: string;
title: string;
__typename: 'Album'
id: string
title: string
/**
* 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 {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
/**
* Width of the image in pixels
*/
width: number;
width: number
/**
* Height of the image in pixels
*/
height: number;
height: number
}
export interface albumQuery_album_media_highRes {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
}
export interface albumQuery_album_media_videoWeb {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
}
export interface albumQuery_album_media {
__typename: "Media";
id: string;
type: MediaType;
__typename: 'Media'
id: string
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
*/
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
*/
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
*/
videoWeb: albumQuery_album_media_videoWeb | null;
favorite: boolean;
videoWeb: albumQuery_album_media_videoWeb | null
favorite: boolean
}
export interface albumQuery_album {
__typename: "Album";
id: string;
title: string;
__typename: 'Album'
id: string
title: string
/**
* The albums contained in this album
*/
subAlbums: albumQuery_album_subAlbums[];
subAlbums: albumQuery_album_subAlbums[]
/**
* The media inside this album
*/
media: albumQuery_album_media[];
media: albumQuery_album_media[]
}
export interface albumQuery {
@ -106,14 +110,14 @@ export interface albumQuery {
* 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
*/
album: albumQuery_album;
album: albumQuery_album
}
export interface albumQueryVariables {
id: string;
onlyFavorites?: boolean | null;
mediaOrderBy?: string | null;
mediaOrderDirection?: OrderDirection | null;
limit?: number | null;
offset?: number | null;
id: string
onlyFavorites?: boolean | null
mediaOrderBy?: string | null
mediaOrderDirection?: OrderDirection | null
limit?: number | null
offset?: number | null
}

View File

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

View File

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

View File

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

View File

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

View File

@ -3,86 +3,90 @@
// @generated
// This file was automatically generated and should not be edited.
import { MediaType } from "./../../../__generated__/globalTypes";
import { MediaType } from './../../../__generated__/globalTypes'
// ====================================================
// GraphQL query operation: placePageQueryMedia
// ====================================================
export interface placePageQueryMedia_mediaList_thumbnail {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
/**
* Width of the image in pixels
*/
width: number;
width: number
/**
* Height of the image in pixels
*/
height: number;
height: number
}
export interface placePageQueryMedia_mediaList_highRes {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
/**
* Width of the image in pixels
*/
width: number;
width: number
/**
* Height of the image in pixels
*/
height: number;
height: number
}
export interface placePageQueryMedia_mediaList_videoWeb {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
/**
* Width of the image in pixels
*/
width: number;
width: number
/**
* Height of the image in pixels
*/
height: number;
height: number
}
export interface placePageQueryMedia_mediaList {
__typename: "Media";
id: string;
title: string;
__typename: 'Media'
id: 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
*/
thumbnail: placePageQueryMedia_mediaList_thumbnail | null;
thumbnail: placePageQueryMedia_mediaList_thumbnail | null
/**
* 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
*/
videoWeb: placePageQueryMedia_mediaList_videoWeb | null;
type: MediaType;
videoWeb: placePageQueryMedia_mediaList_videoWeb | null
type: MediaType
}
export interface placePageQueryMedia {
/**
* 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 {
mediaIDs: string[];
mediaIDs: string[]
}

View File

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

View File

@ -3,179 +3,200 @@
// @generated
// 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
// ====================================================
export interface shareAlbumQuery_album_subAlbums_thumbnail_thumbnail {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
}
export interface shareAlbumQuery_album_subAlbums_thumbnail {
__typename: "Media";
id: string;
__typename: 'Media'
id: string
/**
* 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 {
__typename: "Album";
id: string;
title: string;
__typename: 'Album'
id: string
title: string
/**
* 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 {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
/**
* Width of the image in pixels
*/
width: number;
width: number
/**
* Height of the image in pixels
*/
height: number;
height: number
}
export interface shareAlbumQuery_album_media_downloads_mediaUrl {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
/**
* Width of the image in pixels
*/
width: number;
width: number
/**
* Height of the image in pixels
*/
height: number;
height: number
/**
* The file size of the resource in bytes
*/
fileSize: number;
fileSize: number
}
export interface shareAlbumQuery_album_media_downloads {
__typename: "MediaDownload";
title: string;
mediaUrl: shareAlbumQuery_album_media_downloads_mediaUrl;
__typename: 'MediaDownload'
title: string
mediaUrl: shareAlbumQuery_album_media_downloads_mediaUrl
}
export interface shareAlbumQuery_album_media_highRes {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* URL for previewing the image
*/
url: string;
url: string
/**
* Width of the image in pixels
*/
width: number;
width: number
/**
* Height of the image in pixels
*/
height: number;
height: number
}
export interface shareAlbumQuery_album_media_videoWeb {
__typename: "MediaURL";
__typename: 'MediaURL'
/**
* 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 {
__typename: "MediaEXIF";
__typename: 'MediaEXIF'
id: string
/**
* The model name of the camera
*/
camera: string | null;
camera: string | null
/**
* The maker of the camera
*/
maker: string | null;
maker: string | null
/**
* The name of the lens
*/
lens: string | null;
dateShot: any | null;
lens: string | null
dateShot: any | null
/**
* The exposure time of the image
*/
exposure: number | null;
exposure: number | null
/**
* The aperature stops of the image
*/
aperture: number | null;
aperture: number | null
/**
* The ISO setting of the image
*/
iso: number | null;
iso: number | null
/**
* 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
*/
flash: number | null;
flash: number | null
/**
* 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 {
__typename: "Media";
id: string;
title: string;
type: MediaType;
__typename: 'Media'
id: string
title: string
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
*/
thumbnail: shareAlbumQuery_album_media_thumbnail | null;
downloads: shareAlbumQuery_album_media_downloads[];
thumbnail: shareAlbumQuery_album_media_thumbnail | null
downloads: shareAlbumQuery_album_media_downloads[]
/**
* 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
*/
videoWeb: shareAlbumQuery_album_media_videoWeb | null;
exif: shareAlbumQuery_album_media_exif | null;
videoWeb: shareAlbumQuery_album_media_videoWeb | null
exif: shareAlbumQuery_album_media_exif | null
}
export interface shareAlbumQuery_album {
__typename: "Album";
id: string;
title: string;
__typename: 'Album'
id: string
title: string
/**
* The albums contained in this album
*/
subAlbums: shareAlbumQuery_album_subAlbums[];
subAlbums: shareAlbumQuery_album_subAlbums[]
/**
* The media inside this album
*/
media: shareAlbumQuery_album_media[];
media: shareAlbumQuery_album_media[]
}
export interface shareAlbumQuery {
@ -183,15 +204,15 @@ export interface shareAlbumQuery {
* 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
*/
album: shareAlbumQuery_album;
album: shareAlbumQuery_album
}
export interface shareAlbumQueryVariables {
id: string;
token: string;
password?: string | null;
mediaOrderBy?: string | null;
mediaOrderDirection?: OrderDirection | null;
limit?: number | null;
offset?: number | null;
id: string
token: string
password?: string | null
mediaOrderBy?: string | null
mediaOrderDirection?: OrderDirection | null
limit?: number | null
offset?: number | null
}

View File

@ -8,13 +8,13 @@
// ====================================================
export interface adminQuery_myUser {
__typename: "User";
admin: boolean;
__typename: 'User'
admin: boolean
}
export interface adminQuery {
/**
* 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
*/
mapboxToken: string | null;
mapboxToken: string | null
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ describe('photo gallery reducer', () => {
id: '1',
highRes: null,
thumbnail: null,
blurhash: null,
type: MediaType.Photo,
},
{
@ -18,6 +19,7 @@ describe('photo gallery reducer', () => {
id: '2',
highRes: null,
thumbnail: null,
blurhash: null,
type: MediaType.Photo,
},
{
@ -25,6 +27,7 @@ describe('photo gallery reducer', () => {
id: '3',
highRes: null,
thumbnail: null,
blurhash: null,
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('img')).toHaveLength(5)
screen.debug()
})

View File

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

View File

@ -60,6 +60,10 @@ export interface myTimeline_myTimeline {
id: string
title: string
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
*/

View File

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