mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 08:44:04 +01:00
Rename "Import" to "Library" and add tabs to page
This commit is contained in:
@@ -165,13 +165,13 @@
|
||||
</v-list-tile>
|
||||
</v-list-group>
|
||||
|
||||
<v-list-tile to="/import" @click="">
|
||||
<v-list-tile to="/library" @click="">
|
||||
<v-list-tile-action>
|
||||
<v-icon>camera_roll</v-icon>
|
||||
</v-list-tile-action>
|
||||
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>Import</v-list-tile-title>
|
||||
<v-list-tile-title>Library</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
|
||||
|
||||
@@ -47,6 +47,14 @@ main {
|
||||
color: #00B8D4;
|
||||
}
|
||||
|
||||
#photoprism main a.v-tabs__item {
|
||||
color: #546E7A;
|
||||
}
|
||||
|
||||
#photoprism main a.v-tabs__item--active {
|
||||
color: #455A64;
|
||||
}
|
||||
|
||||
#photoprism .v-badge__badge {
|
||||
font-size: 12px;
|
||||
height: 19px;
|
||||
|
||||
54
frontend/src/pages/library.vue
Normal file
54
frontend/src/pages/library.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="p-page p-page-library">
|
||||
<v-tabs
|
||||
v-model="active"
|
||||
flat
|
||||
grow
|
||||
color="blue-grey lighten-4"
|
||||
slider-color="blue-grey darken-1"
|
||||
height="64"
|
||||
>
|
||||
<v-tab id="tab-upload" ripple>
|
||||
Upload
|
||||
</v-tab>
|
||||
<v-tab-item>
|
||||
<p-tab-upload></p-tab-upload>
|
||||
</v-tab-item>
|
||||
|
||||
<v-tab id="tab-import" ripple>
|
||||
Import
|
||||
</v-tab>
|
||||
<v-tab-item>
|
||||
<p-tab-import></p-tab-import>
|
||||
</v-tab-item>
|
||||
|
||||
<v-tab id="tab-index" ripple>
|
||||
Index
|
||||
</v-tab>
|
||||
<v-tab-item>
|
||||
<p-tab-index></p-tab-index>
|
||||
</v-tab-item>
|
||||
</v-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uploadTab from "pages/library/upload.vue";
|
||||
import importTab from "pages/library/import.vue";
|
||||
import indexTab from "pages/library/index.vue";
|
||||
|
||||
export default {
|
||||
name: 'p-page-library',
|
||||
components: {
|
||||
'p-tab-upload': uploadTab,
|
||||
'p-tab-import': importTab,
|
||||
'p-tab-index': indexTab,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: 0,
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
||||
35
frontend/src/pages/library/import.vue
Normal file
35
frontend/src/pages/library/import.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="p-tab p-tab-import">
|
||||
<v-form ref="form" class="p-photo-import" lazy-validation @submit.prevent="submit" dense>
|
||||
<input type="file" ref="upload" multiple @change.stop="upload()" class="d-none">
|
||||
|
||||
<v-container fluid>
|
||||
<h3 class="subheading">Only available using the command-line interface at the moment</h3>
|
||||
<p class="body-1 mt-2">
|
||||
Issues labeled <a href="https://github.com/photoprism/photoprism/labels/help%20wanted">help
|
||||
wanted</a> /
|
||||
<a href="https://github.com/photoprism/photoprism/labels/easy">easy</a> can be good (first)
|
||||
contributions.
|
||||
Our <a href="https://github.com/photoprism/photoprism/wiki">Developer Guide</a> contains all
|
||||
information
|
||||
necessary to get you started.
|
||||
</p>
|
||||
</v-container>
|
||||
</v-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import Event from "pubsub-js";
|
||||
|
||||
export default {
|
||||
name: 'p-tab-import',
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
35
frontend/src/pages/library/index.vue
Normal file
35
frontend/src/pages/library/index.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="p-tab p-tab-index">
|
||||
<v-form ref="form" class="p-photo-index" lazy-validation @submit.prevent="submit" dense>
|
||||
<input type="file" ref="upload" multiple @change.stop="upload()" class="d-none">
|
||||
|
||||
<v-container fluid>
|
||||
<h3 class="subheading">Only available using the command-line interface at the moment</h3>
|
||||
<p class="body-1 mt-2">
|
||||
Issues labeled <a href="https://github.com/photoprism/photoprism/labels/help%20wanted">help
|
||||
wanted</a> /
|
||||
<a href="https://github.com/photoprism/photoprism/labels/easy">easy</a> can be good (first)
|
||||
contributions.
|
||||
Our <a href="https://github.com/photoprism/photoprism/wiki">Developer Guide</a> contains all
|
||||
information
|
||||
necessary to get you started.
|
||||
</p>
|
||||
</v-container>
|
||||
</v-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import Event from "pubsub-js";
|
||||
|
||||
export default {
|
||||
name: 'p-tab-index',
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,56 +1,20 @@
|
||||
<template>
|
||||
<div class="p-page p-page-import">
|
||||
<v-form ref="form" class="p-photo-import" lazy-validation @submit.prevent="submit" dense>
|
||||
<div class="p-tab p-tab-upload">
|
||||
<v-form ref="form" class="p-photo-upload" lazy-validation @submit.prevent="submit" dense>
|
||||
<input type="file" ref="upload" multiple @change.stop="upload()" class="d-none">
|
||||
|
||||
<v-toolbar flat color="blue-grey lighten-4">
|
||||
<v-toolbar-title>Import</v-toolbar-title>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
</v-toolbar>
|
||||
|
||||
<v-container fluid>
|
||||
<p class="subheading">
|
||||
<span v-if="total === 0">Select photos to start import...</span>
|
||||
<span v-else-if="total > 0 && completed < 100">Adding {{current}} of {{total}}...</span>
|
||||
<span v-if="total === 0">Select photos to start upload...</span>
|
||||
<span v-else-if="total > 0 && completed < 100">Uploaded {{current}} of {{total}}...</span>
|
||||
<span v-else-if="completed === 100">Done.</span>
|
||||
</p>
|
||||
|
||||
<v-progress-linear color="blue-grey" v-model="completed"></v-progress-linear>
|
||||
|
||||
<v-container grid-list-xs fluid class="pa-0 p-photos p-photo-mosaic">
|
||||
<v-layout row wrap>
|
||||
<v-flex
|
||||
v-for="(file, index) in uploads"
|
||||
:key="index"
|
||||
class="p-photo"
|
||||
xs4 sm3 md2 lg1 d-flex
|
||||
>
|
||||
<v-card tile class="elevation-2 ma-2">
|
||||
<v-img :src="file.data"
|
||||
aspect-ratio="1"
|
||||
:title="file.name"
|
||||
class="grey lighten-2"
|
||||
>
|
||||
<v-layout
|
||||
slot="placeholder"
|
||||
fill-height
|
||||
align-center
|
||||
justify-center
|
||||
ma-0
|
||||
>
|
||||
<v-progress-circular indeterminate
|
||||
color="grey lighten-5"></v-progress-circular>
|
||||
</v-layout>
|
||||
</v-img>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
|
||||
<v-btn
|
||||
:disabled="busy"
|
||||
color="blue-grey lighten-2"
|
||||
color="blue-grey"
|
||||
class="white--text ml-0"
|
||||
depressed
|
||||
@click.stop="uploadDialog()"
|
||||
@@ -68,7 +32,7 @@
|
||||
import Event from "pubsub-js";
|
||||
|
||||
export default {
|
||||
name: 'p-page-import',
|
||||
name: 'p-tab-upload',
|
||||
data() {
|
||||
return {
|
||||
selected: [],
|
||||
@@ -77,6 +41,7 @@
|
||||
current: 0,
|
||||
total: 0,
|
||||
completed: 0,
|
||||
started: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -87,6 +52,7 @@
|
||||
this.$refs.upload.click();
|
||||
},
|
||||
upload() {
|
||||
this.started = Date.now();
|
||||
this.selected = this.$refs.upload.files;
|
||||
this.busy = true;
|
||||
this.total = this.selected.length;
|
||||
@@ -94,7 +60,7 @@
|
||||
this.completed = 0;
|
||||
this.uploads = [];
|
||||
|
||||
if(!this.total) {
|
||||
if (!this.total) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -104,24 +70,12 @@
|
||||
|
||||
async function performUpload(ctx) {
|
||||
for (let i = 0; i < ctx.selected.length; i++) {
|
||||
ctx.current = i + 1;
|
||||
ctx.completed = Math.round((ctx.current / ctx.total) * 100);
|
||||
let file = ctx.selected[i];
|
||||
let formData = new FormData();
|
||||
|
||||
formData.append('files', file);
|
||||
|
||||
if (file.type.match('image.*')) {
|
||||
const reader = new FileReader;
|
||||
|
||||
reader.onload = e => {
|
||||
ctx.uploads.push({name: file.name, data: e.target.result});
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
|
||||
await axios.post('/api/v1/upload',
|
||||
await axios.post('/api/v1/upload/' + ctx.started,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
@@ -129,6 +83,8 @@
|
||||
}
|
||||
}
|
||||
).then(function () {
|
||||
ctx.current = i + 1;
|
||||
ctx.completed = Math.round((ctx.current / ctx.total) * 100);
|
||||
}).catch(function () {
|
||||
Event.publish("alert.error", "Upload failed");
|
||||
});
|
||||
@@ -136,8 +92,12 @@
|
||||
}
|
||||
|
||||
performUpload(this).then(() => {
|
||||
axios.post('/api/v1/import/upload/' + this.started).then(function () {
|
||||
Event.publish("alert.success", "Finished indexing upload");
|
||||
});
|
||||
|
||||
Event.publish("ajax.end");
|
||||
Event.publish("alert.success", "Photos uploaded and imported");
|
||||
Event.publish("alert.success", "Upload completed");
|
||||
this.busy = false;
|
||||
});
|
||||
},
|
||||
@@ -3,7 +3,7 @@ import Places from "pages/places.vue";
|
||||
import Labels from "pages/labels.vue";
|
||||
import Events from "pages/events.vue";
|
||||
import People from "pages/people.vue";
|
||||
import Import from "pages/import.vue";
|
||||
import Library from "pages/library.vue";
|
||||
import Share from "pages/share.vue";
|
||||
import Settings from "pages/settings.vue";
|
||||
import Todo from "pages/todo.vue";
|
||||
@@ -64,10 +64,10 @@ export default [
|
||||
meta: {area: "Albums"},
|
||||
},
|
||||
{
|
||||
name: "Import",
|
||||
path: "/import",
|
||||
component: Import,
|
||||
meta: {area: "Import"},
|
||||
name: "Library",
|
||||
path: "/library",
|
||||
component: Library,
|
||||
meta: {area: "Library"},
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
|
||||
52
internal/api/import.go
Normal file
52
internal/api/import.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
)
|
||||
|
||||
var importer *photoprism.Importer
|
||||
|
||||
func initImporter(conf *config.Config) {
|
||||
if importer != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tensorFlow := photoprism.NewTensorFlow(conf)
|
||||
|
||||
indexer := photoprism.NewIndexer(conf, tensorFlow)
|
||||
|
||||
converter := photoprism.NewConverter(conf)
|
||||
|
||||
importer = photoprism.NewImporter(conf, indexer, converter)
|
||||
}
|
||||
|
||||
// POST /api/v1/import
|
||||
func Import(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/import/*path", func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
path := conf.ImportPath()
|
||||
|
||||
if subPath := c.Param("path"); subPath != "" {
|
||||
log.Debugf("import sub path: %s", subPath)
|
||||
path = path + subPath
|
||||
}
|
||||
|
||||
log.Infof("importing photos from %s", path)
|
||||
|
||||
initImporter(conf)
|
||||
|
||||
importer.ImportPhotosFromDirectory(path)
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("completed import in %s", elapsed)})
|
||||
})
|
||||
}
|
||||
@@ -9,33 +9,16 @@ import (
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/util"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
)
|
||||
|
||||
var importer *photoprism.Importer
|
||||
|
||||
func initImporter(conf *config.Config) {
|
||||
if importer != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tensorFlow := photoprism.NewTensorFlow(conf)
|
||||
|
||||
indexer := photoprism.NewIndexer(conf, tensorFlow)
|
||||
|
||||
converter := photoprism.NewConverter(conf)
|
||||
|
||||
importer = photoprism.NewImporter(conf, indexer, converter)
|
||||
}
|
||||
|
||||
// POST /api/v1/upload
|
||||
// POST /api/v1/upload/:path
|
||||
func Upload(router *gin.RouterGroup, conf *config.Config) {
|
||||
router.POST("/upload", func(c *gin.Context) {
|
||||
router.POST("/upload/:path", func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
subPath := c.Param("path")
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
|
||||
@@ -49,7 +32,7 @@ func Upload(router *gin.RouterGroup, conf *config.Config) {
|
||||
|
||||
files := form.File["files"]
|
||||
|
||||
path := fmt.Sprintf("%s/uploads/%s", conf.ImportPath(), uuid.NewV4())
|
||||
path := fmt.Sprintf("%s/upload/%s", conf.ImportPath(), subPath)
|
||||
|
||||
if err := os.MkdirAll(path, os.ModePerm); err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": util.UcFirst(err.Error())})
|
||||
@@ -65,16 +48,10 @@ func Upload(router *gin.RouterGroup, conf *config.Config) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("importing photos from %s", conf.ImportPath())
|
||||
|
||||
initImporter(conf)
|
||||
|
||||
importer.ImportPhotosFromDirectory(conf.ImportPath())
|
||||
|
||||
elapsed := time.Since(start)
|
||||
|
||||
log.Infof("%d files imported in %s", len(files), elapsed)
|
||||
log.Infof("%d files uploaded in %s", len(files), elapsed)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%d files imported in %s", len(files), elapsed)})
|
||||
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%d files uploaded in %s", len(files), elapsed)})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
||||
api.LabelThumbnail(v1, conf)
|
||||
|
||||
api.Upload(v1, conf)
|
||||
api.Import(v1, conf)
|
||||
}
|
||||
|
||||
// Default HTML page (client-side routing implemented via Vue.js)
|
||||
|
||||
Reference in New Issue
Block a user