Proof-of-concept for batch like & delete
Requires additional code clean-up and unit / acceptance tests
This commit is contained in:
parent
9e761549e4
commit
ea3f209f8f
|
@ -79,10 +79,14 @@
|
||||||
</v-speed-dial>
|
</v-speed-dial>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import Event from "pubsub-js";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'p-photo-clipboard',
|
name: 'p-photo-clipboard',
|
||||||
props: {
|
props: {
|
||||||
selection: Array,
|
selection: Array,
|
||||||
|
refresh: Function,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -95,12 +99,32 @@
|
||||||
this.expanded = false;
|
this.expanded = false;
|
||||||
},
|
},
|
||||||
batchLike() {
|
batchLike() {
|
||||||
this.$alert.warning("Not implemented yet");
|
Event.publish("ajax.start");
|
||||||
this.expanded = false;
|
|
||||||
|
const ctx = this;
|
||||||
|
|
||||||
|
axios.post("/api/v1/batch/photos/like", {"ids": this.selection}).then(function () {
|
||||||
|
Event.publish("ajax.end");
|
||||||
|
Event.publish("alert.success", "Photos liked");
|
||||||
|
ctx.clearClipboard();
|
||||||
|
ctx.refresh();
|
||||||
|
}).catch(() => {
|
||||||
|
Event.publish("ajax.end");
|
||||||
|
});
|
||||||
},
|
},
|
||||||
batchDelete() {
|
batchDelete() {
|
||||||
this.$alert.warning("Not implemented yet");
|
Event.publish("ajax.start");
|
||||||
this.expanded = false;
|
|
||||||
|
const ctx = this;
|
||||||
|
|
||||||
|
axios.post("/api/v1/batch/photos/delete", {"ids": this.selection}).then(function () {
|
||||||
|
Event.publish("ajax.end");
|
||||||
|
Event.publish("alert.success", "Photos deleted");
|
||||||
|
ctx.clearClipboard();
|
||||||
|
ctx.refresh();
|
||||||
|
}).catch(() => {
|
||||||
|
Event.publish("ajax.end");
|
||||||
|
});
|
||||||
},
|
},
|
||||||
batchTag() {
|
batchTag() {
|
||||||
this.$alert.warning("Not implemented yet");
|
this.$alert.warning("Not implemented yet");
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
:settings-change="updateQuery"></p-photo-search>
|
:settings-change="updateQuery"></p-photo-search>
|
||||||
|
|
||||||
<v-container fluid>
|
<v-container fluid>
|
||||||
<p-photo-clipboard :selection="selection"></p-photo-clipboard>
|
<p-photo-clipboard :refresh="refresh" :selection="selection"></p-photo-clipboard>
|
||||||
|
|
||||||
<p-photo-mosaic v-if="settings.view === 'mosaic'" :photos="results" :selection="selection"
|
<p-photo-mosaic v-if="settings.view === 'mosaic'" :photos="results" :selection="selection"
|
||||||
:open-photo="openPhoto"></p-photo-mosaic>
|
:open-photo="openPhoto"></p-photo-mosaic>
|
||||||
|
@ -151,6 +151,10 @@
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
},
|
},
|
||||||
|
refresh() {
|
||||||
|
this.lastFilter = {};
|
||||||
|
this.search();
|
||||||
|
},
|
||||||
search() {
|
search() {
|
||||||
this.scrollDisabled = true;
|
this.scrollDisabled = true;
|
||||||
|
|
||||||
|
|
75
internal/api/batch.go
Normal file
75
internal/api/batch.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/models"
|
||||||
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BatchParams struct {
|
||||||
|
Ids []int `json:"ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/v1/batch/photos/delete
|
||||||
|
func BatchPhotosDelete(router *gin.RouterGroup, conf *config.Config) {
|
||||||
|
router.POST("/batch/photos/delete", func(c *gin.Context) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
var params BatchParams
|
||||||
|
|
||||||
|
if err := c.BindJSON(¶ms); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(params.Ids) == 0 {
|
||||||
|
log.Error("no photos selected")
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("deleting photos: %#v", params.Ids)
|
||||||
|
|
||||||
|
db := conf.Db()
|
||||||
|
|
||||||
|
db.Where("id IN (?)", params.Ids).Delete(&models.Photo{})
|
||||||
|
db.Where("photo_id IN (?)", params.Ids).Delete(&models.File{})
|
||||||
|
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("photos deleted in %s", elapsed)})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/v1/batch/photos/like
|
||||||
|
func BatchPhotosLike(router *gin.RouterGroup, conf *config.Config) {
|
||||||
|
router.POST("/batch/photos/like", func(c *gin.Context) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
var params BatchParams
|
||||||
|
|
||||||
|
if err := c.BindJSON(¶ms); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(params.Ids) == 0 {
|
||||||
|
log.Error("no photos selected")
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst("no photos selected")})
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("liking photos: %#v", params.Ids)
|
||||||
|
|
||||||
|
db := conf.Db()
|
||||||
|
|
||||||
|
db.Model(models.Photo{}).Where("id IN (?)", params.Ids).Updates(models.Photo{PhotoFavorite: true})
|
||||||
|
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("photos liked in %s", elapsed)})
|
||||||
|
})
|
||||||
|
}
|
|
@ -32,6 +32,9 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
||||||
|
|
||||||
api.Upload(v1, conf)
|
api.Upload(v1, conf)
|
||||||
api.Import(v1, conf)
|
api.Import(v1, conf)
|
||||||
|
|
||||||
|
api.BatchPhotosDelete(v1, conf)
|
||||||
|
api.BatchPhotosLike(v1, conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default HTML page (client-side routing implemented via Vue.js)
|
// Default HTML page (client-side routing implemented via Vue.js)
|
||||||
|
|
Loading…
Reference in a new issue