Add videos to main navigation #17
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
afc1e10646
commit
2d1763edbe
28 changed files with 194 additions and 133 deletions
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 != "" {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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{}
|
||||
|
||||
|
|
Loading…
Reference in a new issue