Places: Only show nearby photos in viewer if > 50 results on map
This commit is contained in:
parent
650817a9e0
commit
2d350c190f
|
@ -200,13 +200,52 @@ export default {
|
|||
query: function () {
|
||||
return this.$route.params.q ? this.$route.params.q : "";
|
||||
},
|
||||
openPhoto(id) {
|
||||
openNearbyPhotos(uid) {
|
||||
// Abort if uid is empty or results aren't loaded.
|
||||
if (!uid || this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get request parameters.
|
||||
const options = {
|
||||
params: {
|
||||
near: uid,
|
||||
count: 1000,
|
||||
},
|
||||
};
|
||||
|
||||
this.loading = true;
|
||||
|
||||
// Perform get request to find nearby photos.
|
||||
return Api.get("geo/view", options).then((r) => {
|
||||
if (r && r.data && r.data.length > 0) {
|
||||
// Show photos.
|
||||
this.$viewer.show(r.data, 0);
|
||||
} else {
|
||||
// Don't open viewer if nothing was found.
|
||||
this.$notify.warn(this.$gettext("No pictures found"));
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
openPhoto(uid) {
|
||||
// Abort if uid is empty or results aren't loaded.
|
||||
if (!uid || this.loading || !this.result || !this.result.features) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform a backend request to find nearby photos?
|
||||
if (this.result.features.length > 50) {
|
||||
return this.openNearbyPhotos(uid);
|
||||
}
|
||||
|
||||
if (!this.photos || !this.photos.length) {
|
||||
this.photos = this.result.features.map((f) => new Photo(f.properties));
|
||||
}
|
||||
|
||||
if (this.photos.length > 0) {
|
||||
const index = this.photos.findIndex((p) => p.UID === id);
|
||||
const index = this.photos.findIndex((p) => p.UID === uid);
|
||||
const selected = this.photos[index];
|
||||
|
||||
if (selected.Type === TypeVideo || selected.Type === TypeLive) {
|
||||
|
@ -275,10 +314,11 @@ export default {
|
|||
}
|
||||
|
||||
this.initialized = true;
|
||||
this.loading = false;
|
||||
|
||||
this.updateMarkers();
|
||||
}).catch(() => this.loading = false);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
renderMap() {
|
||||
this.map = new mapboxgl.Map(this.options);
|
||||
|
|
|
@ -9,14 +9,15 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/acl"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/search"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// SearchGeo finds photos and returns results as Geo, so they can be displayed on a map.
|
||||
// SearchGeo finds photos and returns results as JSON, so they can be displayed on a map or in a viewer.
|
||||
//
|
||||
// GET /api/v1/geo
|
||||
func SearchGeo(router *gin.RouterGroup) {
|
||||
router.GET("/geo", func(c *gin.Context) {
|
||||
handler := func(c *gin.Context) {
|
||||
s := Auth(SessionID(c), acl.ResourcePhotos, acl.ActionSearch)
|
||||
|
||||
if s.Invalid() {
|
||||
|
@ -55,8 +56,16 @@ func SearchGeo(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
// Create GeoJSON from search results.
|
||||
resp, err := photos.MarshalJSON()
|
||||
var resp []byte
|
||||
|
||||
// Render JSON response.
|
||||
switch c.Param("format") {
|
||||
case "view":
|
||||
conf := service.Config()
|
||||
resp, err = photos.ViewerJSON(conf.ContentUri(), conf.ApiUri(), conf.PreviewToken(), conf.DownloadToken())
|
||||
default:
|
||||
resp, err = photos.GeoJSON()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(400, gin.H{"error": txt.UcFirst(err.Error())})
|
||||
|
@ -66,5 +75,8 @@ func SearchGeo(router *gin.RouterGroup) {
|
|||
AddTokenHeaders(c)
|
||||
|
||||
c.Data(http.StatusOK, "application/json", resp)
|
||||
})
|
||||
}
|
||||
|
||||
router.GET("/geo", handler)
|
||||
router.GET("/geo/:format", handler)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
func TestSearchGeo(t *testing.T) {
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
t.Run("GeoJSON", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
SearchGeo(router)
|
||||
|
@ -16,4 +16,14 @@ func TestSearchGeo(t *testing.T) {
|
|||
result := PerformRequest(app, "GET", "/api/v1/geo")
|
||||
assert.Equal(t, http.StatusOK, result.Code)
|
||||
})
|
||||
t.Run("ViewerJSON", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
|
||||
SearchGeo(router)
|
||||
|
||||
r := PerformRequest(app, "GET", "/api/v1/geo/view")
|
||||
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
t.Logf("response: %s", r.Body.String())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import "time"
|
|||
// SearchGeo represents search form fields for "/api/v1/geo".
|
||||
type SearchGeo struct {
|
||||
Query string `form:"q"`
|
||||
Near string `form:"near"`
|
||||
Type string `form:"type"`
|
||||
Path string `form:"path"`
|
||||
Folder string `form:"folder"` // Alias for Path
|
||||
|
@ -45,6 +46,8 @@ type SearchGeo struct {
|
|||
Color string `form:"color"`
|
||||
Camera int `form:"camera"`
|
||||
Lens int `form:"lens"`
|
||||
Count int `form:"count" serialize:"-"`
|
||||
Offset int `form:"offset" serialize:"-"`
|
||||
}
|
||||
|
||||
// GetQuery returns the query parameter as string.
|
||||
|
|
|
@ -26,6 +26,17 @@ func Geo(f form.SearchGeo) (results GeoResults, err error) {
|
|||
return GeoResults{}, err
|
||||
}
|
||||
|
||||
// Search for nearby photos?
|
||||
if f.Near != "" {
|
||||
photo := Photo{}
|
||||
|
||||
if err := Db().First(&photo, "photo_uid = ?", f.Near).Error; err != nil {
|
||||
return GeoResults{}, err
|
||||
}
|
||||
|
||||
f.S2 = photo.CellID
|
||||
}
|
||||
|
||||
s := UnscopedDb()
|
||||
|
||||
// s.LogMode(true)
|
||||
|
@ -308,7 +319,7 @@ func Geo(f form.SearchGeo) (results GeoResults, err error) {
|
|||
s2Min, s2Max := s2.PrefixedRange(pluscode.S2(f.Olc), 7)
|
||||
s = s.Where("photos.cell_id BETWEEN ? AND ?", s2Min, s2Max)
|
||||
} else {
|
||||
// Filter by approx distance to coordinates:
|
||||
// Filter by approx distance to coordinate:
|
||||
if f.Lat != 0 {
|
||||
latMin := f.Lat - Radius*float32(f.Dist)
|
||||
latMax := f.Lat + Radius*float32(f.Dist)
|
||||
|
@ -321,16 +332,32 @@ func Geo(f form.SearchGeo) (results GeoResults, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Find photos taken before date?
|
||||
if !f.Before.IsZero() {
|
||||
s = s.Where("photos.taken_at <= ?", f.Before.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
// Find photos taken after date?
|
||||
if !f.After.IsZero() {
|
||||
s = s.Where("photos.taken_at >= ?", f.After.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
s = s.Order("taken_at, photos.photo_uid")
|
||||
// Sort order.
|
||||
if f.Near != "" {
|
||||
// Sort by distance to UID.
|
||||
s = s.Order(gorm.Expr("(photos.photo_uid = ?) DESC, ABS((? - photos.photo_lat)*(? - photos.photo_lng))", f.Near, f.Lat, f.Lng))
|
||||
s = s.Limit(1000)
|
||||
} else {
|
||||
// Default.
|
||||
s = s.Order("taken_at, photos.photo_uid")
|
||||
}
|
||||
|
||||
// Limit result count?
|
||||
if f.Count > 0 {
|
||||
s = s.Limit(f.Count).Offset(f.Offset)
|
||||
}
|
||||
|
||||
// Fetch results.
|
||||
if result := s.Scan(&results); result.Error != nil {
|
||||
return results, result.Error
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/gin-gonic/gin"
|
||||
geojson "github.com/paulmach/go.geojson"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
)
|
||||
|
||||
|
@ -37,8 +38,8 @@ func (photo GeoResult) Lng() float64 {
|
|||
// GeoResults represents a list of geo search results.
|
||||
type GeoResults []GeoResult
|
||||
|
||||
// MarshalJSON returns results as Geo.
|
||||
func (photos GeoResults) MarshalJSON() ([]byte, error) {
|
||||
// GeoJSON returns results as specified on https://geojson.org/.
|
||||
func (photos GeoResults) GeoJSON() ([]byte, error) {
|
||||
fc := geojson.NewFeatureCollection()
|
||||
|
||||
bbox := make([]float64, 4)
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package search
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
)
|
||||
|
||||
func TestGeoResult_Lat(t *testing.T) {
|
||||
|
@ -38,3 +41,63 @@ func TestGeoResult_Lng(t *testing.T) {
|
|||
}
|
||||
assert.Equal(t, 8.774999618530273, geo.Lng())
|
||||
}
|
||||
|
||||
func TestGeoResults_GeoJSON(t *testing.T) {
|
||||
taken := time.Date(2000, 1, 1, 1, 1, 1, 1, time.UTC).UTC().Round(time.Second)
|
||||
items := GeoResults{
|
||||
GeoResult{
|
||||
ID: "1",
|
||||
PhotoLat: 7.775,
|
||||
PhotoLng: 8.775,
|
||||
PhotoUID: "p1",
|
||||
PhotoTitle: "Title 1",
|
||||
PhotoDescription: "Description 1",
|
||||
PhotoFavorite: false,
|
||||
PhotoType: entity.TypeVideo,
|
||||
FileHash: "d2b4a5d18276f96f1b5a1bf17fd82d6fab3807f2",
|
||||
FileWidth: 1920,
|
||||
FileHeight: 1080,
|
||||
TakenAt: taken,
|
||||
},
|
||||
GeoResult{
|
||||
ID: "2",
|
||||
PhotoLat: 1.775,
|
||||
PhotoLng: -5.775,
|
||||
PhotoUID: "p2",
|
||||
PhotoTitle: "Title 2",
|
||||
PhotoDescription: "Description 2",
|
||||
PhotoFavorite: true,
|
||||
PhotoType: entity.TypeImage,
|
||||
FileHash: "da639e836dfa9179e66c619499b0a5e592f72fc1",
|
||||
FileWidth: 3024,
|
||||
FileHeight: 3024,
|
||||
TakenAt: taken,
|
||||
},
|
||||
GeoResult{
|
||||
ID: "3",
|
||||
PhotoLat: -1.775,
|
||||
PhotoLng: 100.775,
|
||||
PhotoUID: "p3",
|
||||
PhotoTitle: "Title 3",
|
||||
PhotoDescription: "Description 3",
|
||||
PhotoFavorite: false,
|
||||
PhotoType: entity.TypeRaw,
|
||||
FileHash: "412fe4c157a82b636efebc5bc4bc4a15c321aad1",
|
||||
FileWidth: 5000,
|
||||
FileHeight: 10000,
|
||||
TakenAt: taken,
|
||||
},
|
||||
}
|
||||
|
||||
b, err := items.GeoJSON()
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := []byte("{\"type\":\"FeatureCollection\",\"bbox\":[-5.775000095367432,-1.774999976158142,100.7750015258789,7.775000095367432]")
|
||||
|
||||
assert.Truef(t, bytes.Contains(b, expected), "GeoJSON not as expected")
|
||||
|
||||
t.Logf("result: %s", b)
|
||||
}
|
||||
|
|
43
internal/search/geojson_result_viewer.go
Normal file
43
internal/search/geojson_result_viewer.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package search
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/internal/viewer"
|
||||
)
|
||||
|
||||
// NewViewerResult creates a new photo viewer result.
|
||||
func NewViewerResult(p GeoResult, contentUri, apiUri, previewToken, downloadToken string) viewer.Result {
|
||||
return viewer.Result{
|
||||
UID: p.PhotoUID,
|
||||
Title: p.PhotoTitle,
|
||||
Taken: p.TakenAt,
|
||||
Description: p.PhotoDescription,
|
||||
Favorite: p.PhotoFavorite,
|
||||
Playable: p.PhotoType == entity.TypeVideo || p.PhotoType == entity.TypeLive,
|
||||
DownloadUrl: viewer.DownloadUrl(p.FileHash, apiUri, downloadToken),
|
||||
OriginalW: p.FileWidth,
|
||||
OriginalH: p.FileHeight,
|
||||
Fit720: viewer.NewThumb(p.FileWidth, p.FileHeight, p.FileHash, thumb.Sizes[thumb.Fit720], contentUri, previewToken),
|
||||
Fit1280: viewer.NewThumb(p.FileWidth, p.FileHeight, p.FileHash, thumb.Sizes[thumb.Fit1280], contentUri, previewToken),
|
||||
Fit1920: viewer.NewThumb(p.FileWidth, p.FileHeight, p.FileHash, thumb.Sizes[thumb.Fit1920], contentUri, previewToken),
|
||||
Fit2048: viewer.NewThumb(p.FileWidth, p.FileHeight, p.FileHash, thumb.Sizes[thumb.Fit2048], contentUri, previewToken),
|
||||
Fit2560: viewer.NewThumb(p.FileWidth, p.FileHeight, p.FileHash, thumb.Sizes[thumb.Fit2560], contentUri, previewToken),
|
||||
Fit3840: viewer.NewThumb(p.FileWidth, p.FileHeight, p.FileHash, thumb.Sizes[thumb.Fit3840], contentUri, previewToken),
|
||||
Fit4096: viewer.NewThumb(p.FileWidth, p.FileHeight, p.FileHash, thumb.Sizes[thumb.Fit4096], contentUri, previewToken),
|
||||
Fit7680: viewer.NewThumb(p.FileWidth, p.FileHeight, p.FileHash, thumb.Sizes[thumb.Fit7680], contentUri, previewToken),
|
||||
}
|
||||
}
|
||||
|
||||
// ViewerJSON returns the results as photo viewer JSON.
|
||||
func (photos GeoResults) ViewerJSON(contentUri, apiUri, previewToken, downloadToken string) ([]byte, error) {
|
||||
results := make(viewer.Results, 0, len(photos))
|
||||
|
||||
for _, p := range photos {
|
||||
results = append(results, NewViewerResult(p, contentUri, apiUri, previewToken, downloadToken))
|
||||
}
|
||||
|
||||
return json.MarshalIndent(results, "", " ")
|
||||
}
|
64
internal/search/geojson_result_viewer_test.go
Normal file
64
internal/search/geojson_result_viewer_test.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package search
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
)
|
||||
|
||||
func TestGeoResults_ViewerJSON(t *testing.T) {
|
||||
taken := time.Date(2000, 1, 1, 1, 1, 1, 1, time.UTC).UTC().Round(time.Second)
|
||||
items := GeoResults{
|
||||
GeoResult{
|
||||
ID: "1",
|
||||
PhotoLat: 7.775,
|
||||
PhotoLng: 8.775,
|
||||
PhotoUID: "p1",
|
||||
PhotoTitle: "Title 1",
|
||||
PhotoDescription: "Description 1",
|
||||
PhotoFavorite: false,
|
||||
PhotoType: entity.TypeVideo,
|
||||
FileHash: "d2b4a5d18276f96f1b5a1bf17fd82d6fab3807f2",
|
||||
FileWidth: 1920,
|
||||
FileHeight: 1080,
|
||||
TakenAt: taken,
|
||||
},
|
||||
GeoResult{
|
||||
ID: "2",
|
||||
PhotoLat: 1.775,
|
||||
PhotoLng: -5.775,
|
||||
PhotoUID: "p2",
|
||||
PhotoTitle: "Title 2",
|
||||
PhotoDescription: "Description 2",
|
||||
PhotoFavorite: true,
|
||||
PhotoType: entity.TypeImage,
|
||||
FileHash: "da639e836dfa9179e66c619499b0a5e592f72fc1",
|
||||
FileWidth: 3024,
|
||||
FileHeight: 3024,
|
||||
TakenAt: taken,
|
||||
},
|
||||
GeoResult{
|
||||
ID: "3",
|
||||
PhotoLat: -1.775,
|
||||
PhotoLng: 100.775,
|
||||
PhotoUID: "p3",
|
||||
PhotoTitle: "Title 3",
|
||||
PhotoDescription: "Description 3",
|
||||
PhotoFavorite: false,
|
||||
PhotoType: entity.TypeRaw,
|
||||
FileHash: "412fe4c157a82b636efebc5bc4bc4a15c321aad1",
|
||||
FileWidth: 5000,
|
||||
FileHeight: 10000,
|
||||
TakenAt: taken,
|
||||
},
|
||||
}
|
||||
|
||||
b, err := items.ViewerJSON("/content", "/api/v1", "preview-token", "download-token")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("result: %s", b)
|
||||
}
|
|
@ -11,6 +11,16 @@ import (
|
|||
)
|
||||
|
||||
func TestGeo(t *testing.T) {
|
||||
t.Run("Near", func(t *testing.T) {
|
||||
query := form.NewGeoSearch("near:pt9jtdre2lvl0y43")
|
||||
|
||||
if result, err := Geo(query); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
t.Logf("RESULT: %#v", result)
|
||||
assert.LessOrEqual(t, 4, len(result))
|
||||
}
|
||||
})
|
||||
t.Run("UnknownFaces", func(t *testing.T) {
|
||||
query := form.NewGeoSearch("face:none")
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ package search
|
|||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
)
|
||||
|
|
|
@ -10,6 +10,11 @@ func (n Name) Jpeg() string {
|
|||
return string(n) + fs.JpegExt
|
||||
}
|
||||
|
||||
// String returns the thumbnail name as string.
|
||||
func (n Name) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
// Names of thumbnail sizes.
|
||||
const (
|
||||
Tile50 Name = "tile_50"
|
||||
|
|
29
internal/viewer/result.go
Normal file
29
internal/viewer/result.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package viewer
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Results represents a list of viewer search results.
|
||||
type Results []Result
|
||||
|
||||
// Result represents a photo viewer result.
|
||||
type Result struct {
|
||||
UID string `json:"uid"`
|
||||
Title string `json:"title"`
|
||||
Taken time.Time `json:"taken"`
|
||||
Description string `json:"description"`
|
||||
Favorite bool `json:"favorite"`
|
||||
Playable bool `json:"playable"`
|
||||
DownloadUrl string `json:"download_url""`
|
||||
OriginalW int `json:"original_w"`
|
||||
OriginalH int `json:"original_h"`
|
||||
Fit720 Thumb `json:"fit_720"`
|
||||
Fit1280 Thumb `json:"fit_1280"`
|
||||
Fit1920 Thumb `json:"fit_1920"`
|
||||
Fit2048 Thumb `json:"fit_2048"`
|
||||
Fit2560 Thumb `json:"fit_2560"`
|
||||
Fit3840 Thumb `json:"fit_3840"`
|
||||
Fit4096 Thumb `json:"fit_4096"`
|
||||
Fit7680 Thumb `json:"fit_7680"`
|
||||
}
|
48
internal/viewer/thumb.go
Normal file
48
internal/viewer/thumb.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package viewer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
)
|
||||
|
||||
// DownloadUrl returns a download url based on hash, api uri, and download token.
|
||||
func DownloadUrl(h, apiUri, downloadToken string) string {
|
||||
return fmt.Sprintf("%s/dl/%s?t=%s", apiUri, h, downloadToken)
|
||||
}
|
||||
|
||||
// ThumbUrl returns a thumbnail url based on hash, thumb name, cdn uri, and preview token.
|
||||
func ThumbUrl(h, name, contentUri, previewToken string) string {
|
||||
return fmt.Sprintf("%s/t/%s/%s/%s", contentUri, h, previewToken, name)
|
||||
}
|
||||
|
||||
// Thumb represents a photo viewer thumbnail.
|
||||
type Thumb struct {
|
||||
W int `json:"w"`
|
||||
H int `json:"h"`
|
||||
Src string `json:"src"`
|
||||
}
|
||||
|
||||
// NewThumb creates a new photo viewer thumb.
|
||||
func NewThumb(w, h int, hash string, s thumb.Size, contentUri, previewToken string) Thumb {
|
||||
if s.Width >= w && s.Height >= h {
|
||||
// Smaller
|
||||
return Thumb{W: w, H: h, Src: ThumbUrl(hash, s.Name.String(), contentUri, previewToken)}
|
||||
}
|
||||
|
||||
srcAspectRatio := float64(w) / float64(h)
|
||||
maxAspectRatio := float64(s.Width) / float64(s.Height)
|
||||
|
||||
var newW, newH int
|
||||
|
||||
if srcAspectRatio > maxAspectRatio {
|
||||
newW = s.Width
|
||||
newH = int(math.Round(float64(newW) / srcAspectRatio))
|
||||
} else {
|
||||
newH = s.Height
|
||||
newW = int(math.Round(float64(newH) * srcAspectRatio))
|
||||
}
|
||||
|
||||
return Thumb{W: newW, H: newH, Src: ThumbUrl(hash, s.Name.String(), contentUri, previewToken)}
|
||||
}
|
31
internal/viewer/thumb_test.go
Normal file
31
internal/viewer/thumb_test.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package viewer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
)
|
||||
|
||||
func TestNewThumb(t *testing.T) {
|
||||
fileHash := "d2b4a5d18276f96f1b5a1bf17fd82d6fab3807f2"
|
||||
contentUri := "/content"
|
||||
previewToken := "preview-token"
|
||||
|
||||
t.Run("Fit1280", func(t *testing.T) {
|
||||
result := NewThumb(1920, 1080, fileHash, thumb.Sizes[thumb.Fit1280], contentUri, previewToken)
|
||||
|
||||
assert.Equal(t, 1280, result.W)
|
||||
assert.Equal(t, 720, result.H)
|
||||
assert.Equal(t, "/content/t/d2b4a5d18276f96f1b5a1bf17fd82d6fab3807f2/preview-token/fit_1280", result.Src)
|
||||
})
|
||||
|
||||
t.Run("Fit3840", func(t *testing.T) {
|
||||
result := NewThumb(1920, 1080, fileHash, thumb.Sizes[thumb.Fit3840], contentUri, previewToken)
|
||||
|
||||
assert.Equal(t, 1920, result.W)
|
||||
assert.Equal(t, 1080, result.H)
|
||||
assert.Equal(t, "/content/t/d2b4a5d18276f96f1b5a1bf17fd82d6fab3807f2/preview-token/fit_3840", result.Src)
|
||||
})
|
||||
}
|
32
internal/viewer/viewer.go
Normal file
32
internal/viewer/viewer.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
|
||||
Package viewer provides photo viewer data structures and utility functions.
|
||||
|
||||
Copyright (c) 2018 - 2021 Michael Mayer <hello@photoprism.org>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
PhotoPrism® is a registered trademark of Michael Mayer. You may use it as required
|
||||
to describe our software, run your own server, for educational purposes, but not for
|
||||
offering commercial goods, products, or services without prior written permission.
|
||||
In other words, please ask.
|
||||
|
||||
Feel free to send an e-mail to hello@photoprism.org if you have questions,
|
||||
want to support our work, or just want to say hello.
|
||||
|
||||
Additional information can be found in our Developer Guide:
|
||||
https://docs.photoprism.org/developer-guide/
|
||||
|
||||
*/
|
||||
package viewer
|
Loading…
Reference in a new issue