Show max 500 files per folder #364

No infinite scrolling in this case... Maybe we add it later. Waiting for user feedback.

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-06-22 21:21:02 +02:00
parent 06b43da8c2
commit f7fbc6e0de
6 changed files with 68 additions and 13 deletions

View File

@ -136,6 +136,8 @@ export class Folder extends RestModel {
}
response.models = [];
response.files = files.length;
response.folders = folders.length;
response.count = count;
response.limit = limit;
response.offset = offset;

View File

@ -163,8 +163,8 @@
dirty: false,
results: [],
loading: true,
pageSize: 24,
offset: 0,
filesLimit: 500,
filesOffset: 0,
page: 0,
selection: [],
settings: settings,
@ -360,6 +360,8 @@
const params = {
files: true,
uncached: true,
count: this.filesLimit,
offset: this.filesOffset,
};
Object.assign(params, this.filter);
@ -387,7 +389,7 @@
Object.assign(this.lastFilter, this.filter);
this.offset = 0;
this.filesOffset = 0;
this.page = 0;
this.loading = true;
this.listen = false;
@ -395,7 +397,7 @@
const params = this.searchParams();
Folder.originals(this.path, params).then(response => {
this.offset = this.pageSize;
this.filesOffset = this.filesLimit;
this.results = response.models;
this.breadcrumbs = this.getBreadcrumbs();
@ -404,8 +406,10 @@
this.$notify.warn(this.$gettext('Directory is empty'));
} else if (response.count === 1) {
this.$notify.info(this.$gettext('One entry found'));
} else {
} else if (response.files < this.filesLimit) {
this.$notify.info(response.count + this.$gettext(' entries found'));
} else {
this.$notify.warn(this.$gettext('Too many files in folder, showing first') + ` ${response.files}`);
}
}).finally(() => {
this.dirty = false;

View File

@ -9,10 +9,13 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/txt"
)
type FoldersResponse struct {
@ -31,11 +34,20 @@ func GetFolders(router *gin.RouterGroup, conf *config.Config, urlPath, rootName,
return
}
var f form.FolderSearch
start := time.Now()
err := c.MustBindWith(&f, binding.Form)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
return
}
cache := service.Cache()
recursive := c.Query("recursive") != ""
listFiles := c.Query("files") != ""
uncached := listFiles || c.Query("uncached") != ""
recursive := f.Recursive
listFiles := f.Files
uncached := listFiles || f.Uncached
resp := FoldersResponse{Root: rootName, Recursive: recursive, Cached: !uncached}
path := c.Param("path")
@ -64,7 +76,7 @@ func GetFolders(router *gin.RouterGroup, conf *config.Config, urlPath, rootName,
}
if listFiles {
if files, err := query.FilesByPath(rootName, path); err != nil {
if files, err := query.FilesByPath(f.Count, f.Offset, rootName, path); err != nil {
log.Errorf("folders: %s", err)
} else {
resp.Files = files
@ -78,8 +90,11 @@ func GetFolders(router *gin.RouterGroup, conf *config.Config, urlPath, rootName,
}
}
c.Header("X-Files", strconv.Itoa(len(resp.Files)))
c.Header("X-Folders", strconv.Itoa(len(resp.Folders)))
c.Header("X-Count", strconv.Itoa(len(resp.Files)+len(resp.Folders)))
c.Header("X-Offset", "0")
c.Header("X-Limit", strconv.Itoa(f.Count))
c.Header("X-Offset", strconv.Itoa(f.Offset))
c.JSON(http.StatusOK, resp)
}

View File

@ -0,0 +1,33 @@
package form
// FolderSearch represents search form fields for "/api/v1/folders".
type FolderSearch struct {
Query string `form:"q"`
Recursive bool `form:"recursive"`
Files bool `form:"files"`
Uncached bool `form:"uncached"`
Count int `form:"count" serialize:"-"`
Offset int `form:"offset" serialize:"-"`
}
func (f *FolderSearch) GetQuery() string {
return f.Query
}
func (f *FolderSearch) SetQuery(q string) {
f.Query = q
}
func (f *FolderSearch) ParseQueryString() error {
return ParseQueryString(f)
}
// Serialize returns a string containing non-empty fields and values of a struct.
func (f *FolderSearch) Serialize() string {
return Serialize(f, false)
}
// SerializeAll returns a string containing all non-empty fields and values of a struct.
func (f *FolderSearch) SerializeAll() string {
return Serialize(f, true)
}

View File

@ -7,7 +7,7 @@ import (
)
// FilesByPath returns a slice of files in a given originals folder.
func FilesByPath(rootName, pathName string) (files entity.Files, err error) {
func FilesByPath(limit, offset int, rootName, pathName string) (files entity.Files, err error) {
if strings.HasPrefix(pathName, "/") {
pathName = pathName[1:]
}
@ -18,13 +18,14 @@ func FilesByPath(rootName, pathName string) (files entity.Files, err error) {
Where("files.file_missing = 0").
Where("files.file_root = ? AND photos.photo_path = ?", rootName, pathName).
Order("files.file_name").
Limit(limit).Offset(offset).
Find(&files).Error
return files, err
}
// Files returns not-missing and not-deleted file entities in the range of limit and offset sorted by id.
func Files(limit int, offset int, pathName string, includeMissing bool) (files entity.Files, err error) {
func Files(limit, offset int, pathName string, includeMissing bool) (files entity.Files, err error) {
if strings.HasPrefix(pathName, "/") {
pathName = pathName[1:]
}

View File

@ -9,7 +9,7 @@ import (
func TestFilesByPath(t *testing.T) {
t.Run("files found", func(t *testing.T) {
files, err := FilesByPath(entity.RootOriginals, "2016/11")
files, err := FilesByPath(10, 0, entity.RootOriginals, "2016/11")
t.Logf("files: %+v", files)