Experimental filters for category and country (photo search)
This commit is contained in:
parent
39ab854672
commit
d3ef7abb54
|
@ -18,7 +18,8 @@
|
||||||
appName: "{{ .title }}",
|
appName: "{{ .title }}",
|
||||||
appVersion: "1.0.0",
|
appVersion: "1.0.0",
|
||||||
debug: {{ .debug }},
|
debug: {{ .debug }},
|
||||||
cameras: {{ .cameras }}
|
cameras: {{ .cameras }},
|
||||||
|
countries: {{ .countries }}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
label="Country"
|
label="Country"
|
||||||
flat solo
|
flat solo
|
||||||
color="blue-grey"
|
color="blue-grey"
|
||||||
|
item-value="LocCountryCode"
|
||||||
|
item-text="LocCountry"
|
||||||
v-model="query.country"
|
v-model="query.country"
|
||||||
:items="options.countries">
|
:items="options.countries">
|
||||||
</v-select>
|
</v-select>
|
||||||
|
@ -77,6 +79,7 @@
|
||||||
direction="top"
|
direction="top"
|
||||||
open-on-hover
|
open-on-hover
|
||||||
transition="slide-y-reverse-transition"
|
transition="slide-y-reverse-transition"
|
||||||
|
style="right: 8px; bottom: 8px;"
|
||||||
>
|
>
|
||||||
<v-btn
|
<v-btn
|
||||||
slot="activator"
|
slot="activator"
|
||||||
|
@ -203,7 +206,11 @@
|
||||||
const cat = query['cat'] ? query['cat'] : '';
|
const cat = query['cat'] ? query['cat'] : '';
|
||||||
const country = query['country'] ? query['country'] : '';
|
const country = query['country'] ? query['country'] : '';
|
||||||
const view = query['view'] ? query['view'] : 'tile';
|
const view = query['view'] ? query['view'] : 'tile';
|
||||||
const cameras = [{ID: 0, CameraModel: 'All Cameras'}].concat( this.$config.getValue('cameras'));
|
const cameras = [{ID: 0, CameraModel: 'All Cameras'}].concat(this.$config.getValue('cameras'));
|
||||||
|
const countries = [{
|
||||||
|
LocCountryCode: '',
|
||||||
|
LocCountry: 'All Countries'
|
||||||
|
}].concat(this.$config.getValue('countries'));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'snackbarVisible': false,
|
'snackbarVisible': false,
|
||||||
|
@ -220,16 +227,14 @@
|
||||||
'options': {
|
'options': {
|
||||||
'categories': [
|
'categories': [
|
||||||
{value: '', text: 'All Categories'},
|
{value: '', text: 'All Categories'},
|
||||||
{value: 'junction', text: 'Junction'},
|
{value: 'airport', text: 'Airport'},
|
||||||
{value: 'tourism', text: 'Tourism'},
|
{value: 'amenity', text: 'Amenity'},
|
||||||
|
{value: 'building', text: 'Building'},
|
||||||
{value: 'historic', text: 'Historic'},
|
{value: 'historic', text: 'Historic'},
|
||||||
|
{value: 'shop', text: 'Shop'},
|
||||||
|
{value: 'tourism', text: 'Tourism'},
|
||||||
],
|
],
|
||||||
'countries': [
|
'countries': countries,
|
||||||
{value: '', text: 'All Countries'},
|
|
||||||
{value: 'de', text: 'Germany'},
|
|
||||||
{value: 'ca', text: 'Canada'},
|
|
||||||
{value: 'us', text: 'United States'}
|
|
||||||
],
|
|
||||||
'cameras': cameras,
|
'cameras': cameras,
|
||||||
'sorting': [
|
'sorting': [
|
||||||
{value: 'newest', text: 'Newest first'},
|
{value: 'newest', text: 'Newest first'},
|
||||||
|
@ -302,7 +307,7 @@
|
||||||
this.refreshList();
|
this.refreshList();
|
||||||
},
|
},
|
||||||
loadMore() {
|
loadMore() {
|
||||||
if(this.loadMoreDisabled) return;
|
if (this.loadMoreDisabled) return;
|
||||||
|
|
||||||
this.loadMoreDisabled = true;
|
this.loadMoreDisabled = true;
|
||||||
|
|
||||||
|
@ -321,7 +326,7 @@
|
||||||
|
|
||||||
this.loadMoreDisabled = (response.models.length < this.pageSize);
|
this.loadMoreDisabled = (response.models.length < this.pageSize);
|
||||||
|
|
||||||
if(this.loadMoreDisabled) {
|
if (this.loadMoreDisabled) {
|
||||||
this.$alert.info('All ' + this.results.length + ' photos loaded');
|
this.$alert.info('All ' + this.results.length + ' photos loaded');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -351,7 +356,7 @@
|
||||||
|
|
||||||
this.loadMoreDisabled = (response.models.length < this.pageSize);
|
this.loadMoreDisabled = (response.models.length < this.pageSize);
|
||||||
|
|
||||||
if(this.loadMoreDisabled) {
|
if (this.loadMoreDisabled) {
|
||||||
this.$alert.info(this.results.length + ' photos found');
|
this.$alert.info(this.results.length + ' photos found');
|
||||||
} else {
|
} else {
|
||||||
this.$alert.info('More than 50 photos found');
|
this.$alert.info('More than 50 photos found');
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
type PhotoSearchForm struct {
|
type PhotoSearchForm struct {
|
||||||
Query string `form:"q"`
|
Query string `form:"q"`
|
||||||
Tags string `form:"tags"`
|
Tags string `form:"tags"`
|
||||||
Category string `form:"cat"`
|
Cat string `form:"cat"`
|
||||||
Country string `form:"country"`
|
Country string `form:"country"`
|
||||||
CameraID int `form:"camera"`
|
CameraID int `form:"camera"`
|
||||||
Order string `form:"order"`
|
Order string `form:"order"`
|
||||||
|
|
|
@ -231,7 +231,15 @@ func (c *Config) GetClientConfig() ConfigValues {
|
||||||
db := c.GetDb()
|
db := c.GetDb()
|
||||||
|
|
||||||
var cameras []*Camera
|
var cameras []*Camera
|
||||||
// var countries map[string]string
|
|
||||||
|
type country struct {
|
||||||
|
LocCountry string
|
||||||
|
LocCountryCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
var countries []country
|
||||||
|
|
||||||
|
db.Model(&Location{}).Select("DISTINCT loc_country_code, loc_country").Scan(&countries)
|
||||||
|
|
||||||
db.Where("deleted_at IS NULL").Limit(1000).Order("camera_model").Find(&cameras)
|
db.Where("deleted_at IS NULL").Limit(1000).Order("camera_model").Find(&cameras)
|
||||||
|
|
||||||
|
@ -242,6 +250,7 @@ func (c *Config) GetClientConfig() ConfigValues {
|
||||||
"title": "PhotoPrism",
|
"title": "PhotoPrism",
|
||||||
"debug": c.Debug,
|
"debug": c.Debug,
|
||||||
"cameras": cameras,
|
"cameras": cameras,
|
||||||
|
"countries": countries,
|
||||||
"jsHash": jsHash,
|
"jsHash": jsHash,
|
||||||
"cssHash": cssHash,
|
"cssHash": cssHash,
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (m *MediaFile) GetExifData() (*ExifData, error) {
|
||||||
file, err := m.openFile()
|
file, err := m.openFile()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return m.exifData, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
@ -49,7 +49,7 @@ func (m *MediaFile) GetExifData() (*ExifData, error) {
|
||||||
x, err := exif.Decode(file)
|
x, err := exif.Decode(file)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return m.exifData, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if artist, err := x.Get(exif.Artist); err == nil {
|
if artist, err := x.Get(exif.Artist); err == nil {
|
||||||
|
|
|
@ -77,7 +77,7 @@ func NewSearch(originalsPath string, db *gorm.DB) *Search {
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Search) Photos(form PhotoSearchForm) ([]PhotoSearchResult, int, error) {
|
func (s *Search) Photos(form PhotoSearchForm) ([]PhotoSearchResult, error) {
|
||||||
q := s.db.NewScope(nil).DB()
|
q := s.db.NewScope(nil).DB()
|
||||||
q = q.Table("photos").
|
q = q.Table("photos").
|
||||||
Select(`SQL_CALC_FOUND_ROWS photos.*,
|
Select(`SQL_CALC_FOUND_ROWS photos.*,
|
||||||
|
@ -98,7 +98,39 @@ func (s *Search) Photos(form PhotoSearchForm) ([]PhotoSearchResult, int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.CameraID > 0 {
|
if form.CameraID > 0 {
|
||||||
q = q.Where("camera_id = ?", form.CameraID)
|
q = q.Where("photos.camera_id = ?", form.CameraID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.Country != "" {
|
||||||
|
q = q.Where("locations.loc_country_code = ?", form.Country)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch form.Cat {
|
||||||
|
case "amenity":
|
||||||
|
q = q.Where("locations.loc_category = 'amenity'")
|
||||||
|
case "bank":
|
||||||
|
q = q.Where("locations.loc_type = 'bank'")
|
||||||
|
case "building":
|
||||||
|
q = q.Where("locations.loc_category = 'building'")
|
||||||
|
case "school":
|
||||||
|
q = q.Where("locations.loc_type = 'school'")
|
||||||
|
case "supermarket":
|
||||||
|
q = q.Where("locations.loc_type = 'supermarket'")
|
||||||
|
case "shop":
|
||||||
|
q = q.Where("locations.loc_category = 'shop'")
|
||||||
|
case "hotel":
|
||||||
|
q = q.Where("locations.loc_type = 'hotel'")
|
||||||
|
case "bar":
|
||||||
|
q = q.Where("locations.loc_type = 'bar'")
|
||||||
|
case "parking":
|
||||||
|
q = q.Where("locations.loc_type = 'parking'")
|
||||||
|
case "airport":
|
||||||
|
q = q.Where("locations.loc_category = 'aeroway'")
|
||||||
|
case "historic":
|
||||||
|
q = q.Where("locations.loc_category = 'historic'")
|
||||||
|
case "tourism":
|
||||||
|
q = q.Where("locations.loc_category = 'tourism'")
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
switch form.Order {
|
switch form.Order {
|
||||||
|
@ -118,28 +150,13 @@ func (s *Search) Photos(form PhotoSearchForm) ([]PhotoSearchResult, int, error)
|
||||||
q = q.Limit(100).Offset(0)
|
q = q.Limit(100).Offset(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make([]PhotoSearchResult, 0, form.Count)
|
var results []PhotoSearchResult
|
||||||
|
|
||||||
rows, err := q.Rows()
|
if result := q.Scan(&results); result.Error != nil {
|
||||||
|
return results, result.Error
|
||||||
if err != nil {
|
|
||||||
return results, 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer rows.Close()
|
return results, nil
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var result PhotoSearchResult
|
|
||||||
s.db.ScanRows(rows, &result)
|
|
||||||
results = append(results, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check if this works properly with concurrent requests and caching
|
|
||||||
count := &SearchCount{}
|
|
||||||
s.db.Raw("SELECT FOUND_ROWS() AS total").Scan(&count)
|
|
||||||
total := count.Total
|
|
||||||
|
|
||||||
return results, total, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Search) FindFiles(count int, offset int) (files []File) {
|
func (s *Search) FindFiles(count int, offset int) (files []File) {
|
||||||
|
|
|
@ -20,23 +20,21 @@ func TestSearch_Photos_Query(t *testing.T) {
|
||||||
form.Count = 3
|
form.Count = 3
|
||||||
form.Offset = 0
|
form.Offset = 0
|
||||||
|
|
||||||
photos, total, err := search.Photos(form)
|
photos, err := search.Photos(form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
t.Logf("Total Count: %d", total)
|
|
||||||
|
|
||||||
photos, total, err = search.Photos(form)
|
photos, err = search.Photos(form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
t.Logf("Total Count: %d", total)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,12 +54,11 @@ func TestSearch_Photos_Camera(t *testing.T) {
|
||||||
form.Count = 3
|
form.Count = 3
|
||||||
form.Offset = 0
|
form.Offset = 0
|
||||||
|
|
||||||
photos, total, err := search.Photos(form)
|
photos, err := search.Photos(form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log(photos)
|
t.Log(photos)
|
||||||
t.Logf("Total Count: %d", total)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ func CreateThumbnailsFromOriginals(originalsPath string, thumbnailsPath string,
|
||||||
if thumbnail, err := mediaFile.GetSquareThumbnail(thumbnailsPath, size); err != nil {
|
if thumbnail, err := mediaFile.GetSquareThumbnail(thumbnailsPath, size); err != nil {
|
||||||
log.Printf("Could not create thumbnail: %s", err.Error())
|
log.Printf("Could not create thumbnail: %s", err.Error())
|
||||||
} else {
|
} else {
|
||||||
thumbnail.GetHeight()
|
|
||||||
log.Printf("Created %dx%d px thumbnail for \"%s\"", thumbnail.GetWidth(), thumbnail.GetHeight(), mediaFile.GetRelativeFilename(originalsPath))
|
log.Printf("Created %dx%d px thumbnail for \"%s\"", thumbnail.GetWidth(), thumbnail.GetHeight(), mediaFile.GetRelativeFilename(originalsPath))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -28,13 +28,12 @@ func ConfigureRoutes(app *gin.Engine, conf *photoprism.Config) {
|
||||||
|
|
||||||
c.MustBindWith(&form, binding.Form)
|
c.MustBindWith(&form, binding.Form)
|
||||||
|
|
||||||
result, total, err := search.Photos(form)
|
result, err := search.Photos(form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(400, gin.H{"error": err.Error()})
|
c.AbortWithStatusJSON(400, gin.H{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Header("x-result-total", strconv.Itoa(total))
|
|
||||||
c.Header("x-result-count", strconv.Itoa(form.Count))
|
c.Header("x-result-count", strconv.Itoa(form.Count))
|
||||||
c.Header("x-result-offset", strconv.Itoa(form.Offset))
|
c.Header("x-result-offset", strconv.Itoa(form.Offset))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue