Add videos to main navigation #17

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-05-14 19:03:12 +02:00
parent afc1e10646
commit 2d1763edbe
28 changed files with 194 additions and 133 deletions

View file

@ -54,6 +54,9 @@ class Config {
const type = ev.split(".")[1];
switch (type) {
case "videos":
this.values.count.videos += data.count;
break;
case "favorites":
this.values.count.favorites += data.count;
break;

View file

@ -55,9 +55,7 @@
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Photos</translate>
</v-list-tile-title>
<v-list-tile-title>{{ $gettext('Photos') }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
@ -65,37 +63,44 @@
<v-list-tile slot="activator" to="/photos" @click.stop="" class="p-navigation-photos">
<v-list-tile-content>
<v-list-tile-title>
<translate>Photos</translate>
<translate>{{ $gettext('Photos') }}</translate>
<span v-if="config.count.photos > 0" class="p-navigation-count">{{ config.count.photos }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile :to="{name: 'photos', query: { q: 'mono:true quality:3' }}" :exact="true" @click="">
<v-list-tile :to="{name: 'photos', query: { q: 'mono:true quality:3 photo:true' }}" :exact="true" @click="">
<v-list-tile-content>
<v-list-tile-title>
<translate>Monochrome</translate>
</v-list-tile-title>
<v-list-tile-title>{{ $gettext('Monochrome') }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/review" @click="" v-if="$config.feature('review')">
<v-list-tile-content>
<v-list-tile-title>
<translate>Review</translate>
</v-list-tile-title>
<v-list-tile-title>{{ $gettext('Review') }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/archive" @click="" class="p-navigation-archive" v-if="$config.feature('archive')">
<v-list-tile to="/archive" @click="" class="p-navigation-archive" v-show="$config.feature('archive')">
<v-list-tile-content>
<v-list-tile-title>
<translate>Archive</translate>
</v-list-tile-title>
<v-list-tile-title>{{ $gettext('Archive') }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list-group>
<v-list-tile to="/videos" @click="" class="p-navigation-video">
<v-list-tile-action>
<v-icon>slideshow</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<span>{{ $gettext('Videos') }}</span>
<span v-show="config.count.videos > 0" class="p-navigation-count">{{ config.count.videos }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/favorites" @click="" class="p-navigation-favorites">
<v-list-tile-action>
<v-icon>favorite</v-icon>
@ -103,21 +108,21 @@
<v-list-tile-content>
<v-list-tile-title>
<translate>Favorites</translate>
<span v-if="config.count.favorites > 0" class="p-navigation-count">{{ config.count.favorites }}</span>
<span>{{ $gettext('Favorites') }}</span>
<span v-show="config.count.favorites > 0" class="p-navigation-count">{{ config.count.favorites }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/private" @click="" class="p-navigation-private" v-if="$config.feature('private')" >
<v-list-tile to="/private" @click="" class="p-navigation-private" v-show="$config.feature('private')" >
<v-list-tile-action>
<v-icon>lock</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<span v-translate>Private</span>
<span v-if="config.count.private > 0" class="p-navigation-count">{{ config.count.private }}</span>
<span>{{ $gettext('Private') }}</span>
<span v-show="config.count.private > 0" class="p-navigation-count">{{ config.count.private }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
@ -129,7 +134,7 @@
<v-list-tile-content>
<v-list-tile-title>
<translate>Albums</translate>
<span>{{ $gettext('Albums') }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
@ -138,7 +143,7 @@
<v-list-tile slot="activator" to="/albums" @click.stop="">
<v-list-tile-content>
<v-list-tile-title>
<translate>Albums</translate>
<span>{{ $gettext('Albums') }}</span>
<span v-if="config.count.albums > 0" class="p-navigation-count">{{ config.count.albums }}</span>
</v-list-tile-title>
</v-list-tile-content>
@ -154,36 +159,36 @@
</v-list-tile>
</v-list-group>
<v-list-tile to="/labels" @click="" class="p-navigation-labels" v-if="$config.feature('labels')">
<v-list-tile to="/labels" @click="" class="p-navigation-labels" v-show="$config.feature('labels')">
<v-list-tile-action>
<v-icon>label</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Labels</translate>
<span v-if="config.count.labels > 0"
<span>{{ $gettext('Labels') }}</span>
<span v-show="config.count.labels > 0"
class="p-navigation-count">{{ config.count.labels }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile :to="{ name: 'places' }" @click="" class="p-navigation-places"
v-if="$config.feature('places')">
v-show="$config.feature('places')">
<v-list-tile-action>
<v-icon>place</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Places</translate>
<span v-if="config.count.places > 0"
<span>{{ $gettext('Places') }}</span>
<span v-show="config.count.places > 0"
class="p-navigation-count">{{ config.count.places }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<!-- v-list-tile to="/discover" @click="" class="p-navigation-discover" v-if="config.experimental">
<!-- v-list-tile to="/discover" @click="" class="p-navigation-discover" v-show="config.experimental">
<v-list-tile-action>
<v-icon>color_lens</v-icon>
</v-list-tile-action>
@ -222,43 +227,43 @@
<v-list-tile-content>
<v-list-tile-title>
<translate>Library</translate>
<span>{{ $gettext('Library') }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/settings" @click="" class="p-navigation-settings" v-if="!config.disableSettings">
<v-list-tile to="/settings" @click="" class="p-navigation-settings" v-show="!config.disableSettings">
<v-list-tile-action>
<v-icon>settings</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Settings</translate>
<span>{{ $gettext('Settings') }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile @click="logout" class="p-navigation-logout" v-if="!public && auth">
<v-list-tile @click="logout" class="p-navigation-logout" v-show="!public && auth">
<v-list-tile-action>
<v-icon>power_settings_new</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Logout</translate>
<span>{{ $gettext('Logout') }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile to="/login" @click="" class="p-navigation-login" v-if="!auth">
<v-list-tile to="/login" @click="" class="p-navigation-login" v-show="!auth">
<v-list-tile-action>
<v-icon>lock</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>
<translate>Login</translate>
<span>{{ $gettext('Login') }}</span>
</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>

View file

@ -68,12 +68,12 @@
<v-icon v-else color="accent lighten-3" class="t-like t-off">favorite_border</v-icon>
</v-btn>
<v-btn v-if="photo.PhotoVideo" color="white" :ripple="false"
<v-btn v-if="photo.PhotoVideo && photo.isMP4()" color="white" :ripple="false"
outline large fab absolute class="p-photo-play opacity-75" :depressed="false"
@click.stop.prevent="openPhoto(index, true)">
<v-icon color="white" class="action-play">play_arrow</v-icon>
</v-btn>
<v-btn v-else-if="photo.Files.length > 1" :ripple="false"
<v-btn v-else-if="!photo.PhotoVideo && photo.Files.length > 1" :ripple="false"
icon flat large absolute class="p-photo-merged opacity-75"
@click.stop.prevent="openPhoto(index, true)">
<v-icon color="white" class="action-burst">burst_mode</v-icon>

View file

@ -32,7 +32,7 @@
flat icon large absolute class="p-photo-select">
<v-icon color="white" class="t-select t-on">check_circle</v-icon>
</v-btn>
<v-btn v-else-if="!selection.length && props.item.PhotoVideo" :ripple="false"
<v-btn v-else-if="!selection.length && props.item.PhotoVideo && props.item.isMP4()" :ripple="false"
flat icon large absolute class="p-photo-play opacity-75"
@click.stop.prevent="openPhoto(props.index, true)">
<v-icon color="white" class="action-play">play_arrow</v-icon>
@ -144,7 +144,7 @@
} else if(this.photos[index]) {
let photo = this.photos[index];
if(photo.PhotoVideo) {
if(photo.PhotoVideo && photo.isMP4()) {
this.openPhoto(index, true);
} else {
this.openPhoto(index, false);

View file

@ -67,12 +67,12 @@
<v-icon v-else color="accent lighten-3" class="t-like t-off">favorite_border</v-icon>
</v-btn>
<v-btn v-if="photo.PhotoVideo" color="white"
<v-btn v-if="photo.PhotoVideo && photo.isMP4()" color="white"
outline fab absolute class="p-photo-play opacity-75" :depressed="false" :ripple="false"
@click.stop.prevent="openPhoto(index, true)">
<v-icon color="white" class="action-play">play_arrow</v-icon>
</v-btn>
<v-btn v-else-if="photo.Files.length > 1" :ripple="false"
<v-btn v-else-if="!photo.PhotoVideo && photo.Files.length > 1" :ripple="false"
icon flat small absolute class="p-photo-merged opacity-75"
@click.stop.prevent="openPhoto(index, true)">
<v-icon color="white" class="action-burst">burst_mode</v-icon>

View file

@ -105,6 +105,14 @@ class Photo extends RestModel {
this.FileHeight = file.FileHeight;
}
isMP4() {
if (!this.Files) {
return false;
}
return this.Files.findIndex(f => f.FileType === "mp4") !== -1;
}
videoFile() {
if (!this.Files) {
return false;

View file

@ -142,7 +142,11 @@
}
if (showMerged && this.results[index].PhotoVideo) {
Event.publish("dialog.video", {play: this.results[index], album: null});
if(this.results[index].isMP4()) {
Event.publish("dialog.video", {play: this.results[index], album: this.album});
} else {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
}
} else if (showMerged) {
this.$viewer.show(Thumb.fromFiles([this.results[index]]), 0)
} else {

View file

@ -115,16 +115,16 @@
computed: {
context: function () {
if (!this.staticFilter) {
return "photos"
return "photos";
}
if (this.staticFilter.archived) {
return "archive"
} else if (this.staticFilter.favorites) {
return "favorites"
return "archive";
} else if (this.staticFilter.favorite) {
return "favorites";
}
return ""
return "";
}
},
methods: {
@ -179,7 +179,11 @@
}
if (showMerged && this.results[index].PhotoVideo) {
Event.publish("dialog.video", {play: this.results[index], album: null});
if(this.results[index].isMP4()) {
Event.publish("dialog.video", {play: this.results[index], album: null});
} else {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
}
} else if (showMerged) {
this.$viewer.show(Thumb.fromFiles([this.results[index]]), 0)
} else {

View file

@ -49,7 +49,14 @@ export default [
path: "/favorites",
component: Photos,
meta: {title: "Favorites", auth: true},
props: {staticFilter: {favorites: true}},
props: {staticFilter: {favorite: true}},
},
{
name: "videos",
path: "/videos",
component: Photos,
meta: {title: "Videos", auth: true},
props: {staticFilter: {video: true}},
},
{
name: "review",

View file

@ -12,8 +12,7 @@ func TestGetImportOptions(t *testing.T) {
app, router, conf := NewApiTest()
GetImportOptions(router, conf)
r := PerformRequest(app, "GET", "/api/v1/import")
val := gjson.Get(r.Body.String(), "dirs")
assert.Equal(t, "[\"/raw\"]", val.String())
assert.True(t, gjson.Get(r.Body.String(), "dirs").IsArray())
assert.Equal(t, http.StatusOK, r.Code)
})
}

View file

@ -12,8 +12,7 @@ func TestGetIndexingOptions(t *testing.T) {
app, router, conf := NewApiTest()
GetIndexingOptions(router, conf)
r := PerformRequest(app, "GET", "/api/v1/index")
val := gjson.Get(r.Body.String(), "dirs")
assert.Contains(t, val.String(), "/2011")
assert.True(t, gjson.Get(r.Body.String(), "dirs").IsArray())
assert.Equal(t, http.StatusOK, r.Code)
})
}

View file

@ -26,7 +26,7 @@ import (
// offset: int Result offset
// before: date Find photos taken before (format: "2006-01-02")
// after: date Find photos taken after (format: "2006-01-02")
// favorites: bool Find favorites only
// favorite: bool Find favorites only
func GetPhotos(router *gin.RouterGroup, conf *config.Config) {
router.GET("/photos", func(c *gin.Context) {
if Unauthorized(c, conf) {

View file

@ -59,6 +59,7 @@ func (c *Config) PublicClientConfig() ClientConfig {
var count = struct {
Photos uint `json:"photos"`
Videos uint `json:"videos"`
Hidden uint `json:"hidden"`
Favorites uint `json:"favorites"`
Private uint `json:"private"`
@ -132,6 +133,7 @@ func (c *Config) ClientConfig() ClientConfig {
var count = struct {
Photos uint `json:"photos"`
Videos uint `json:"videos"`
Hidden uint `json:"hidden"`
Favorites uint `json:"favorites"`
Private uint `json:"private"`
@ -143,7 +145,7 @@ func (c *Config) ClientConfig() ClientConfig {
}{}
db.Table("photos").
Select("SUM(photo_quality = -1) AS hidden, SUM(photo_quality >= 0) AS photos, SUM(photo_favorite) AS favorites, SUM(photo_private) AS private").
Select("SUM(photo_video = 1 AND photo_quality >= 0) AS videos, SUM(photo_quality = -1) AS hidden, SUM(photo_quality >= 0) AS photos, SUM(photo_favorite) AS favorites, SUM(photo_private) AS private").
Where("deleted_at IS NULL").
Take(&count)

View file

@ -2,14 +2,14 @@ package form
// AlbumSearch represents search form fields for "/api/v1/albums".
type AlbumSearch struct {
Query string `form:"q"`
ID string `form:"id"`
Slug string `form:"slug"`
Name string `form:"name"`
Favorites bool `form:"favorites"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
Query string `form:"q"`
ID string `form:"id"`
Slug string `form:"slug"`
Name string `form:"name"`
Favorite bool `form:"favorite"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
}
func (f *AlbumSearch) GetQuery() string {

View file

@ -16,7 +16,7 @@ func TestAlbumSearchForm(t *testing.T) {
func TestParseQueryStringAlbum(t *testing.T) {
t.Run("valid query", func(t *testing.T) {
form := &AlbumSearch{Query: "slug:album1 favorites:true count:10"}
form := &AlbumSearch{Query: "slug:album1 favorite:true count:10"}
err := form.ParseQueryString()
@ -27,11 +27,11 @@ func TestParseQueryStringAlbum(t *testing.T) {
}
assert.Equal(t, "album1", form.Slug)
assert.Equal(t, true, form.Favorites)
assert.Equal(t, true, form.Favorite)
assert.Equal(t, 10, form.Count)
})
t.Run("valid query 2", func(t *testing.T) {
form := &AlbumSearch{Query: "name:album1 favorites:false offset:100 order:newest query:\"query text\""}
form := &AlbumSearch{Query: "name:album1 favorite:false offset:100 order:newest query:\"query text\""}
err := form.ParseQueryString()
@ -42,7 +42,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
}
assert.Equal(t, "album1", form.Name)
assert.Equal(t, false, form.Favorites)
assert.Equal(t, false, form.Favorite)
assert.Equal(t, 100, form.Offset)
assert.Equal(t, "newest", form.Order)
assert.Equal(t, "query text", form.Query)
@ -74,7 +74,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
assert.Equal(t, "unknown filter: Xxx", err.Error())
})
t.Run("query for favorites with uncommon bool value", func(t *testing.T) {
form := &AlbumSearch{Query: "favorites:cat"}
form := &AlbumSearch{Query: "favorite:cat"}
err := form.ParseQueryString()
@ -82,7 +82,7 @@ func TestParseQueryStringAlbum(t *testing.T) {
t.Fatal("err should be nil")
}
assert.True(t, form.Favorites)
assert.True(t, form.Favorite)
})
t.Run("query for count with invalid type", func(t *testing.T) {
form := &AlbumSearch{Query: "count:cat"}

View file

@ -2,15 +2,15 @@ package form
// PhotoSearch represents search form fields for "/api/v1/labels".
type LabelSearch struct {
Query string `form:"q"`
ID string `form:"id"`
Slug string `form:"slug"`
Name string `form:"name"`
All bool `form:"all"`
Favorites bool `form:"favorites"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
Query string `form:"q"`
ID string `form:"id"`
Slug string `form:"slug"`
Name string `form:"name"`
All bool `form:"all"`
Favorite bool `form:"favorite"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
}
func (f *LabelSearch) GetQuery() string {

View file

@ -16,7 +16,7 @@ func TestLabelSearchForm(t *testing.T) {
func TestParseQueryStringLabel(t *testing.T) {
t.Run("valid query", func(t *testing.T) {
form := &LabelSearch{Query: "name:cat favorites:true count:10 all:false query:\"query text\""}
form := &LabelSearch{Query: "name:cat favorite:true count:10 all:false query:\"query text\""}
err := form.ParseQueryString()
@ -26,13 +26,13 @@ func TestParseQueryStringLabel(t *testing.T) {
t.Fatal("err should be nil")
}
assert.Equal(t, "cat", form.Name)
assert.Equal(t, true, form.Favorites)
assert.Equal(t, true, form.Favorite)
assert.Equal(t, 10, form.Count)
assert.Equal(t, false, form.All)
assert.Equal(t, "query text", form.Query)
})
t.Run("valid query 2", func(t *testing.T) {
form := &LabelSearch{Query: "slug:cat favorites:false offset:2 order:oldest"}
form := &LabelSearch{Query: "slug:cat favorite:false offset:2 order:oldest"}
err := form.ParseQueryString()
@ -42,7 +42,7 @@ func TestParseQueryStringLabel(t *testing.T) {
t.Fatal("err should be nil")
}
assert.Equal(t, "cat", form.Slug)
assert.Equal(t, false, form.Favorites)
assert.Equal(t, false, form.Favorite)
assert.Equal(t, 2, form.Offset)
assert.Equal(t, "oldest", form.Order)
})
@ -73,7 +73,7 @@ func TestParseQueryStringLabel(t *testing.T) {
assert.Equal(t, "unknown filter: Xxx", err.Error())
})
t.Run("query for favorites with uncommon bool value", func(t *testing.T) {
form := &LabelSearch{Query: "favorites:0.99"}
form := &LabelSearch{Query: "favorite:0.99"}
err := form.ParseQueryString()
@ -81,7 +81,7 @@ func TestParseQueryStringLabel(t *testing.T) {
t.Fatal("err should be nil")
}
assert.True(t, form.Favorites)
assert.True(t, form.Favorite)
})
t.Run("query for count with invalid type", func(t *testing.T) {
form := &LabelSearch{Query: "count:2019-01-15"}

View file

@ -10,6 +10,8 @@ type PhotoSearch struct {
ID string `form:"id"`
Title string `form:"title"`
Hash string `form:"hash"`
Video bool `form:"video"`
Photo bool `form:"photo"`
Duplicate bool `form:"duplicate"`
Archived bool `form:"archived"`
Error bool `form:"error"`
@ -25,24 +27,24 @@ type PhotoSearch struct {
Location bool `form:"location"`
Album string `form:"album"`
Label string `form:"label"`
Country string `form:"country"`
Year uint `form:"year"`
Month uint `form:"month"`
Color string `form:"color"`
Quality int `form:"quality"`
Review bool `form:"review"`
Camera int `form:"camera"`
Lens int `form:"lens"`
Before time.Time `form:"before" time_format:"2006-01-02"`
After time.Time `form:"after" time_format:"2006-01-02"`
Favorites bool `form:"favorites"`
Public bool `form:"public"`
Private bool `form:"private"`
Safe bool `form:"safe"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
Merged bool `form:"merged"`
Country string `form:"country"`
Year uint `form:"year"`
Month uint `form:"month"`
Color string `form:"color"`
Quality int `form:"quality"`
Review bool `form:"review"`
Camera int `form:"camera"`
Lens int `form:"lens"`
Before time.Time `form:"before" time_format:"2006-01-02"`
After time.Time `form:"after" time_format:"2006-01-02"`
Favorite bool `form:"favorite"`
Public bool `form:"public"`
Private bool `form:"private"`
Safe bool `form:"safe"`
Count int `form:"count" binding:"required"`
Offset int `form:"offset"`
Order string `form:"order"`
Merged bool `form:"merged"`
}
func (f *PhotoSearch) GetQuery() string {

View file

@ -18,7 +18,7 @@ func TestPhotoSearchForm(t *testing.T) {
func TestParseQueryString(t *testing.T) {
t.Run("valid query", func(t *testing.T) {
form := &PhotoSearch{Query: "label:cat query:\"fooBar baz\" before:2019-01-15 camera:23 favorites:false dist:25000 lat:33.45343166666667"}
form := &PhotoSearch{Query: "label:cat query:\"fooBar baz\" before:2019-01-15 camera:23 favorite:false dist:25000 lat:33.45343166666667"}
err := form.ParseQueryString()
@ -32,12 +32,12 @@ func TestParseQueryString(t *testing.T) {
assert.Equal(t, "foobar baz", form.Query)
assert.Equal(t, 23, form.Camera)
assert.Equal(t, time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC), form.Before)
assert.Equal(t, false, form.Favorites)
assert.Equal(t, false, form.Favorite)
assert.Equal(t, uint(0x61a8), form.Dist)
assert.Equal(t, float32(33.45343), form.Lat)
})
t.Run("valid query 2", func(t *testing.T) {
form := &PhotoSearch{Query: "chroma:200 title:\"test\" after:2018-01-15 duplicate:false favorites:true lng:33.45343166666667"}
form := &PhotoSearch{Query: "chroma:200 title:\"test\" after:2018-01-15 duplicate:false favorite:true lng:33.45343166666667"}
err := form.ParseQueryString()
@ -80,7 +80,7 @@ func TestParseQueryString(t *testing.T) {
assert.Equal(t, "unknown filter: Xxx", err.Error())
})
t.Run("query for favorites with uncommon bool value", func(t *testing.T) {
form := &PhotoSearch{Query: "favorites:cat"}
form := &PhotoSearch{Query: "favorite:cat"}
err := form.ParseQueryString()
@ -88,7 +88,7 @@ func TestParseQueryString(t *testing.T) {
t.Fatal("err should NOT be nil")
}
assert.True(t, form.Favorites)
assert.True(t, form.Favorite)
})
t.Run("query for lat with invalid type", func(t *testing.T) {
form := &PhotoSearch{Query: "lat:cat"}

View file

@ -357,6 +357,12 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
})
}
if photo.PhotoVideo {
event.Publish("count.videos", event.Data{
"count": 1,
})
}
event.EntitiesCreated("photos", []entity.Photo{photo})
}

View file

@ -556,6 +556,11 @@ func (m *MediaFile) IsVideo() bool {
return m.MediaType() == fs.MediaVideo
}
// IsPlayableVideo returns true if this is a supported video file format.
func (m *MediaFile) IsPlayableVideo() bool {
return m.MediaType() == fs.MediaVideo && m.HasFileType(fs.TypeMP4)
}
// IsPhoto returns true if this file is a photo / image.
func (m *MediaFile) IsPhoto() bool {
return m.IsJpeg() || m.IsRaw() || m.IsHEIF() || m.IsImageOther()

View file

@ -85,7 +85,7 @@ func Albums(f form.AlbumSearch) (results []AlbumResult, err error) {
s = s.Where("LOWER(albums.album_name) LIKE ?", likeString)
}
if f.Favorites {
if f.Favorite {
s = s.Where("albums.album_favorite = 1")
}

View file

@ -67,7 +67,7 @@ func TestAlbums(t *testing.T) {
})
t.Run("favorites true", func(t *testing.T) {
query := form.NewAlbumSearch("favorites:true count:10000")
query := form.NewAlbumSearch("favorite:true count:10000")
result, err := Albums(query)
@ -102,14 +102,14 @@ func TestAlbums(t *testing.T) {
})
t.Run("search for existing ID", func(t *testing.T) {
f := form.AlbumSearch{
Query: "",
ID: "at9lxuqxpogaaba7",
Slug: "",
Name: "",
Favorites: false,
Count: 0,
Offset: 0,
Order: "",
Query: "",
ID: "at9lxuqxpogaaba7",
Slug: "",
Name: "",
Favorite: false,
Count: 0,
Offset: 0,
Order: "",
}
result, err := Albums(f)

View file

@ -67,7 +67,7 @@ func Labels(f form.LabelSearch) (results []LabelResult, err error) {
}
}
if f.Favorites {
if f.Favorite {
s = s.Where("labels.label_favorite = 1")
}

View file

@ -62,7 +62,7 @@ func TestLabels(t *testing.T) {
}
})
t.Run("search for favorites", func(t *testing.T) {
query := form.NewLabelSearch("Favorites:true Count:15")
query := form.NewLabelSearch("Favorite:true Count:15")
result, err := Labels(query)
if err != nil {
@ -109,15 +109,15 @@ func TestLabels(t *testing.T) {
t.Run("search for ID", func(t *testing.T) {
f := form.LabelSearch{
Query: "",
ID: "lt9k3pw1wowuy3c4",
Slug: "",
Name: "",
All: false,
Favorites: false,
Count: 0,
Offset: 0,
Order: "",
Query: "",
ID: "lt9k3pw1wowuy3c4",
Slug: "",
Name: "",
All: false,
Favorite: false,
Count: 0,
Offset: 0,
Order: "",
}
result, err := Labels(f)

View file

@ -24,6 +24,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
// s.LogMode(true)
// Main search query, avoids (slow) left joins.
s = s.Table("photos").
Select(`photos.*,
files.id AS file_id, files.file_uuid, files.file_primary, files.file_missing, files.file_name, files.file_hash,
@ -33,11 +34,12 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
cameras.camera_make, cameras.camera_model,
lenses.lens_make, lenses.lens_model,
places.loc_label, places.loc_city, places.loc_state, places.loc_country`).
Joins("JOIN files ON photos.id = files.photo_id AND files.file_missing = 0 AND files.deleted_at IS NULL AND (files.file_type = 'jpg' OR files.file_video)").
Joins("JOIN files ON photos.id = files.photo_id AND files.file_missing = 0 AND files.deleted_at IS NULL").
Joins("JOIN cameras ON photos.camera_id = cameras.id").
Joins("JOIN lenses ON photos.lens_id = lenses.id").
Joins("JOIN places ON photos.place_id = places.id")
// Shortcut for known photo ids.
if f.ID != "" {
s = s.Where("photos.photo_uuid = ?", f.ID)
s = s.Order("files.file_primary DESC")
@ -53,6 +55,16 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
return results, len(results), nil
}
// Filter by media type.
if f.Video {
s = s.Where("(files.file_type = 'jpg' OR files.file_video = 1) AND photos.photo_video = 1")
} else if f.Photo {
s = s.Where("files.file_type = 'jpg' AND photos.photo_video = 1")
} else {
s = s.Where("(files.file_type = 'jpg' OR files.file_video = 1)")
}
// Filter by label, label category and keywords.
var categories []entity.Category
var label entity.Label
var labels []entity.Label
@ -76,6 +88,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
}
}
// Filter by location.
if f.Location == true {
s = s.Where("location_id > 0")
@ -115,6 +128,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
}
}
// Filter by status.
if f.Archived {
s = s.Where("photos.deleted_at IS NOT NULL")
} else {
@ -133,6 +147,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
}
}
// Filter by additional flags and metadata.
if f.Error {
s = s.Where("files.file_error <> ''")
}
@ -161,7 +176,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
s = s.Where("files.file_main_color = ?", strings.ToLower(f.Color))
}
if f.Favorites {
if f.Favorite {
s = s.Where("photos.photo_favorite = 1")
}
@ -211,7 +226,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
f.Dist = 5000
}
// Inaccurate distance search, but probably 'good enough' for now
// Filter by distance (approximation).
if f.Lat > 0 {
latMin := f.Lat - SearchRadius*float32(f.Dist)
latMax := f.Lat + SearchRadius*float32(f.Dist)
@ -232,6 +247,7 @@ func Photos(f form.PhotoSearch) (results PhotosResults, count int, err error) {
s = s.Where("photos.taken_at >= ?", f.After.Format("2006-01-02"))
}
// Set sort order for results.
switch f.Order {
case entity.SortOrderRelevance:
if f.Label != "" {

View file

@ -288,7 +288,7 @@ func TestPhotos(t *testing.T) {
})
t.Run("form.favorites", func(t *testing.T) {
var f form.PhotoSearch
f.Query = "favorites:true"
f.Query = "favorite:true"
f.Count = 10
f.Offset = 0

View file

@ -11,6 +11,7 @@ import (
)
func Dirs(root string, recursive bool) (result []string, err error) {
result = []string{}
ignore := NewIgnoreList(".ppignore", true, false)
mutex := sync.Mutex{}