Albums: Save sort order and description

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer
2020-04-20 10:38:01 +02:00
parent 933c1c19d6
commit f70b506f77
10 changed files with 107 additions and 34 deletions

View File

@@ -39,7 +39,7 @@
<v-icon>view_column</v-icon>
</v-btn>
<v-btn icon @click.stop="searchExpanded = !searchExpanded" class="p-expand-search">
<v-btn icon @click.stop="expand" class="p-expand-search">
<v-icon>{{ searchExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</v-icon>
</v-btn>
</v-toolbar>
@@ -48,7 +48,7 @@
flat
color="secondary-light"
v-show="searchExpanded">
<v-card-text>
<v-card-text class="pb-0">
<v-layout row wrap>
<v-flex xs12 pa-2>
<v-text-field flat solo hide-details
@@ -103,6 +103,17 @@
:items="options.sorting">
</v-select>
</v-flex>
<v-flex xs12 pt-2 px-2>
<v-textarea flat solo auto-grow autofocus
browser-autocomplete="off"
:label="labels.description"
:rows="2"
:key="growDesc"
color="secondary-dark"
v-model="album.AlbumDescription"
@change="updateAlbum">
</v-textarea>
</v-flex>
</v-layout>
</v-card-text>
</v-card>
@@ -146,6 +157,7 @@
],
},
labels: {
description: this.$gettext("Description"),
search: this.$gettext("Search"),
view: this.$gettext("View"),
country: this.$gettext("Country"),
@@ -154,15 +166,28 @@
name: this.$gettext("Album Name"),
},
titleRule: v => v.length <= 25 || this.$gettext("Title too long"),
growDesc: false,
};
},
methods: {
expand() {
this.searchExpanded = !this.searchExpanded;
this.growDesc = !this.growDesc;
},
updateAlbum() {
this.album.update()
},
dropdownChange() {
this.filterChange();
if (window.innerWidth < 600) {
this.searchExpanded = false;
}
if (this.filter.order !== this.album.AlbumOrder) {
this.album.AlbumOrder = this.filter.order;
this.updateAlbum()
}
},
setView(name) {
this.settings.view = name;

View File

@@ -271,9 +271,12 @@
});
},
findAlbum() {
this.model.find(this.uuid).then(m => {
return this.model.find(this.uuid).then(m => {
this.model = m;
this.filter.order = m.AlbumOrder;
window.document.title = `PhotoPrism: ${this.model.AlbumName}`;
return Promise.resolve(this.model)
});
},
onAlbumsUpdated(ev, data) {
@@ -347,8 +350,7 @@
},
},
created() {
this.findAlbum();
this.search();
this.findAlbum().then(() => this.search());
this.subscriptions.push(Event.subscribe("albums.updated", (ev, data) => this.onAlbumsUpdated(ev, data)));
this.subscriptions.push(Event.subscribe("photos", (ev, data) => this.onUpdate(ev, data)));

View File

@@ -214,19 +214,22 @@ func UpdateAccount(router *gin.RouterGroup, conf *config.Config) {
f, err := form.NewAccount(m)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
log.Error(err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
// 2) Update form with values from request
if err := c.BindJSON(&f); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
log.Error(err)
c.AbortWithStatusJSON(http.StatusBadRequest, ErrFormInvalid)
return
}
// 3) Save model with values from form
if err := m.Save(f, conf.Db()); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
log.Error(err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}

View File

@@ -117,30 +117,41 @@ func UpdateAlbum(router *gin.RouterGroup, conf *config.Config) {
return
}
var f form.Album
db := conf.Db()
uuid := c.Param("uuid")
q := query.New(db)
if err := c.BindJSON(&f); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
return
}
id := c.Param("uuid")
q := query.New(conf.Db())
m, err := q.AlbumByUUID(id)
m, err := q.AlbumByUUID(uuid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, ErrAlbumNotFound)
return
}
m.Rename(f.AlbumName)
conf.Db().Save(&m)
f, err := form.NewAlbum(m)
if err != nil {
log.Error(err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
if err := c.BindJSON(&f); err != nil {
log.Error(err)
c.AbortWithStatusJSON(http.StatusBadRequest, ErrFormInvalid)
return
}
if err := m.Save(f, conf.Db()); err != nil {
log.Error(err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
event.Publish("config.updated", event.Data(conf.ClientConfig()))
event.Success("album saved")
PublishAlbumEvent(EntityUpdated, id, c, q)
PublishAlbumEvent(EntityUpdated, uuid, c, q)
c.JSON(http.StatusOK, m)
})

View File

@@ -18,5 +18,7 @@ var (
ErrPhotoNotFound = gin.H{"code": http.StatusNotFound, "error": "Photo not found"}
ErrLabelNotFound = gin.H{"code": http.StatusNotFound, "error": "Label not found"}
ErrUnexpectedError = gin.H{"code": http.StatusInternalServerError, "error": "Unexpected error"}
ErrSaveFailed = gin.H{"code": http.StatusInternalServerError, "error": "Save failed - database error?"}
ErrSaveFailed = gin.H{"code": http.StatusInternalServerError, "error": "Changes could not be saved"}
ErrFormInvalid = gin.H{"code": http.StatusBadRequest, "error": "Changes could not be saved"}
ErrFeatureDisabled = gin.H{"code": http.StatusForbidden, "error": "Feature disabled"}
)

View File

@@ -21,13 +21,13 @@ import (
// POST /api/v1/import*
func StartImport(router *gin.RouterGroup, conf *config.Config) {
router.POST("/import/*path", func(c *gin.Context) {
if conf.ReadOnly() || !conf.Settings().Features.Import {
c.AbortWithStatusJSON(http.StatusForbidden, ErrReadOnly)
if Unauthorized(c, conf) {
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
return
}
if Unauthorized(c, conf) {
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrUnauthorized)
if conf.ReadOnly() || !conf.Settings().Features.Import {
c.AbortWithStatusJSON(http.StatusForbidden, ErrFeatureDisabled)
return
}

View File

@@ -12,7 +12,6 @@ import (
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt"
)
// GET /api/v1/photos/:uuid
@@ -46,8 +45,9 @@ func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) {
return
}
db := conf.Db()
uuid := c.Param("uuid")
q := query.New(conf.Db())
q := query.New(db)
m, err := q.PhotoByUUID(uuid)
@@ -61,19 +61,22 @@ func UpdatePhoto(router *gin.RouterGroup, conf *config.Config) {
f, err := form.NewPhoto(m)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
log.Error(err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}
// 2) Update form with values from request
if err := c.BindJSON(&f); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
log.Error(err)
c.AbortWithStatusJSON(http.StatusBadRequest, ErrFormInvalid)
return
}
// 3) Save model with values from form
if err := entity.SavePhotoForm(m, f, conf.Db(), conf.GeoCodingApi()); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": txt.UcFirst(err.Error())})
if err := entity.SavePhotoForm(m, f, db, conf.GeoCodingApi()); err != nil {
log.Error(err)
c.AbortWithStatusJSON(http.StatusInternalServerError, ErrSaveFailed)
return
}

View File

@@ -28,6 +28,11 @@ func CreateZip(router *gin.RouterGroup, conf *config.Config) {
return
}
if !conf.Settings().Features.Download {
c.AbortWithStatusJSON(http.StatusForbidden, ErrFeatureDisabled)
return
}
var f form.Selection
start := time.Now()

View File

@@ -6,7 +6,9 @@ import (
"github.com/gosimple/slug"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/ulule/deepcopier"
)
// Album represents a photo album
@@ -63,3 +65,16 @@ func (m *Album) Rename(albumName string) {
m.AlbumName = strings.TrimSpace(albumName)
m.AlbumSlug = slug.Make(m.AlbumName)
}
// Save updates the entity using form data and stores it in the database.
func (m *Album) Save(f form.Album, db *gorm.DB) error {
if err := deepcopier.Copy(m).From(f); err != nil {
return err
}
if f.AlbumName != "" {
m.Rename(f.AlbumName)
}
return db.Save(m).Error
}

View File

@@ -1,12 +1,19 @@
package form
import "github.com/ulule/deepcopier"
// Album represents an album edit form.
type Album struct {
AlbumName string `json:"AlbumName"`
AlbumDescription string `json:"AlbumDescription"`
AlbumNotes string `json:"AlbumNotes"`
AlbumFavorite bool `json:"AlbumFavorite"`
AlbumPublic bool `json:"AlbumPublic"`
AlbumOrder string `json:"AlbumOrder"`
AlbumTemplate string `json:"AlbumTemplate"`
AlbumFavorite bool `json:"AlbumFavorite"`
}
func NewAlbum(m interface{}) (f Album, err error) {
err = deepcopier.Copy(m).To(&f)
return f, err
}