mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Frontend: Add photos to new album
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
@@ -103,6 +103,13 @@
|
|||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
|
<!-- v-list-tile v-if="config.albums.length === 0"
|
||||||
|
@click.stop="createAlbum">
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>Create Album</v-list-tile-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
</v-list-tile -->
|
||||||
|
|
||||||
<v-list-tile v-for="(album, index) in config.albums"
|
<v-list-tile v-for="(album, index) in config.albums"
|
||||||
:key="index"
|
:key="index"
|
||||||
:to="{ name: 'album', params: { uuid: album.AlbumUUID, slug: album.AlbumSlug } }">
|
:to="{ name: 'album', params: { uuid: album.AlbumUUID, slug: album.AlbumSlug } }">
|
||||||
@@ -225,6 +232,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Album from "../model/album";
|
||||||
|
import {DateTime} from "luxon";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "p-navigation",
|
name: "p-navigation",
|
||||||
data() {
|
data() {
|
||||||
@@ -240,10 +250,17 @@
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showNavigation: function () {
|
showNavigation () {
|
||||||
this.drawer = true;
|
this.drawer = true;
|
||||||
this.mini = false;
|
this.mini = false;
|
||||||
},
|
},
|
||||||
|
createAlbum() {
|
||||||
|
let name = DateTime.local().toFormat("LLLL yyyy");
|
||||||
|
const album = new Album({AlbumName: name, AlbumFavorite: true});
|
||||||
|
album.save().then((a) => {
|
||||||
|
console.log("created", a)
|
||||||
|
});
|
||||||
|
},
|
||||||
logout() {
|
logout() {
|
||||||
this.$session.logout();
|
this.$session.logout();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
@click.stop="dialog.album = true"
|
@click.stop="dialog.album = true"
|
||||||
class="p-photo-clipboard-album"
|
class="p-photo-clipboard-album"
|
||||||
>
|
>
|
||||||
<v-icon>create_new_folder</v-icon>
|
<v-icon>folder</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
v-if="album"
|
v-if="album"
|
||||||
class="p-photo-clipboard-delete"
|
class="p-photo-clipboard-delete"
|
||||||
>
|
>
|
||||||
<v-icon>remove_circle</v-icon>
|
<v-icon>delete_outline</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
fab
|
fab
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
<v-container fluid class="pb-2 pr-2 pl-2">
|
<v-container fluid class="pb-2 pr-2 pl-2">
|
||||||
<v-layout row wrap>
|
<v-layout row wrap>
|
||||||
<v-flex xs3 text-xs-center>
|
<v-flex xs3 text-xs-center>
|
||||||
<v-icon size="54" color="grey lighten-1">folder</v-icon>
|
<v-icon size="54" color="grey lighten-1" v-if="newAlbum">create_new_folder</v-icon>
|
||||||
|
<v-icon size="54" color="grey lighten-1" v-else>folder</v-icon>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs9 text-xs-left align-self-center>
|
<v-flex xs9 text-xs-left align-self-center>
|
||||||
<v-autocomplete
|
<v-autocomplete
|
||||||
v-model="album"
|
v-model="album"
|
||||||
:items="albums"
|
browser-autocomplete="off"
|
||||||
|
hint="Album Name"
|
||||||
|
:items="items"
|
||||||
:search-input.sync="search"
|
:search-input.sync="search"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
hide-details
|
hide-details
|
||||||
@@ -26,7 +29,9 @@
|
|||||||
<translate>Cancel</translate>
|
<translate>Cancel</translate>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn color="blue-grey lighten-2" depressed dark @click.stop="confirm"
|
<v-btn color="blue-grey lighten-2" depressed dark @click.stop="confirm"
|
||||||
class="p-photo-dialog-confirm"><translate>Add to album</translate>
|
class="p-photo-dialog-confirm">
|
||||||
|
<span v-if="newAlbum">{{ labels.createAlbum }}</span>
|
||||||
|
<span v-else>{{ labels.addToAlbum }}</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
@@ -44,12 +49,16 @@
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
loading: false,
|
||||||
search: null,
|
search: null,
|
||||||
|
newAlbum: null,
|
||||||
album: "",
|
album: "",
|
||||||
albums: [],
|
albums: [],
|
||||||
|
items: [],
|
||||||
labels: {
|
labels: {
|
||||||
select: this.$gettext("Select album"),
|
select: this.$gettext("Select album"),
|
||||||
|
addToAlbum: this.$gettext("Add to album"),
|
||||||
|
createAlbum: this.$gettext("Create album"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -58,29 +67,59 @@
|
|||||||
this.$emit('cancel');
|
this.$emit('cancel');
|
||||||
},
|
},
|
||||||
confirm() {
|
confirm() {
|
||||||
this.$emit('confirm', this.album);
|
if(this.album === "" && this.newAlbum) {
|
||||||
|
console.log("NEW", this.album, this.newAlbum);
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
this.newAlbum.save().then((a) => {
|
||||||
|
this.loading = false;
|
||||||
|
this.$emit('confirm', a.AlbumUUID);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("OLD", this.album, this.newAlbum);
|
||||||
|
this.$emit('confirm', this.album);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
queryServer(q) {
|
||||||
updated() {
|
if(this.loading) {
|
||||||
if(this.albums.length > 0) {
|
return;
|
||||||
this.loading = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
q: "",
|
|
||||||
count: 1000,
|
|
||||||
offset: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
Album.search(params).then(response => {
|
|
||||||
if(response.models.length > 0) {
|
|
||||||
this.album = response.models[0].AlbumUUID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.albums = response.models;
|
this.loading = true;
|
||||||
this.loading = false;
|
|
||||||
});
|
const params = {
|
||||||
|
q: q,
|
||||||
|
count: 1000,
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Album.search(params).then(response => {
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
if(response.models.length > 0 && !this.album) {
|
||||||
|
this.album = response.models[0].AlbumUUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.albums = response.models;
|
||||||
|
this.items = [...this.albums];
|
||||||
|
}).catch(() => this.loading = false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
search (q) {
|
||||||
|
const exists = this.albums.findIndex((album) => album.AlbumName === q);
|
||||||
|
|
||||||
|
if (exists !== -1 || !q) {
|
||||||
|
this.items = this.albums;
|
||||||
|
this.newAlbum = null;
|
||||||
|
} else {
|
||||||
|
this.newAlbum = new Album({AlbumName: q, AlbumUUID: ""});
|
||||||
|
this.items = this.albums.concat([this.newAlbum]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.queryServer("");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -94,6 +94,7 @@
|
|||||||
methods: {
|
methods: {
|
||||||
viewType() {
|
viewType() {
|
||||||
let queryParam = this.$route.query['view'];
|
let queryParam = this.$route.query['view'];
|
||||||
|
let defaultType = window.localStorage.getItem("photo_view_type");
|
||||||
let storedType = window.localStorage.getItem("album_view_type");
|
let storedType = window.localStorage.getItem("album_view_type");
|
||||||
|
|
||||||
if (queryParam) {
|
if (queryParam) {
|
||||||
@@ -101,6 +102,8 @@
|
|||||||
return queryParam;
|
return queryParam;
|
||||||
} else if (storedType) {
|
} else if (storedType) {
|
||||||
return storedType;
|
return storedType;
|
||||||
|
} else if (defaultType) {
|
||||||
|
return defaultType;
|
||||||
} else if (window.innerWidth < 960) {
|
} else if (window.innerWidth < 960) {
|
||||||
return 'mosaic';
|
return 'mosaic';
|
||||||
} else if (window.innerWidth > 1600) {
|
} else if (window.innerWidth > 1600) {
|
||||||
|
|||||||
@@ -132,6 +132,7 @@
|
|||||||
import Album from "model/album";
|
import Album from "model/album";
|
||||||
import {DateTime} from "luxon";
|
import {DateTime} from "luxon";
|
||||||
import Util from "common/util";
|
import Util from "common/util";
|
||||||
|
import Event from "pubsub-js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'p-page-albums',
|
name: 'p-page-albums',
|
||||||
@@ -156,6 +157,7 @@
|
|||||||
const settings = {};
|
const settings = {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
subId: null,
|
||||||
results: [],
|
results: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
scrollDisabled: true,
|
scrollDisabled: true,
|
||||||
@@ -313,10 +315,17 @@
|
|||||||
} else {
|
} else {
|
||||||
this.selection.push(uuid)
|
this.selection.push(uuid)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onCount() {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.search();
|
this.search();
|
||||||
|
this.subId = Event.subscribe("count.albums", (ev, data) => this.onCount(ev, data));
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
Event.unsubscribe(this.subId);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -223,17 +223,17 @@
|
|||||||
}).catch(() => this.loading = false);
|
}).catch(() => this.loading = false);
|
||||||
},
|
},
|
||||||
onKeypress(event) {
|
onKeypress(event) {
|
||||||
if (event.key === "Escape") {
|
/* if (event.key === "Escape") {
|
||||||
this.$clipboard.clear();
|
this.$clipboard.clear();
|
||||||
}
|
} */
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.search();
|
this.search();
|
||||||
window.addEventListener('keydown', this.onKeypress);
|
// window.addEventListener('keydown', this.onKeypress);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
window.removeEventListener('keydown', this.onKeypress);
|
// window.removeEventListener('keydown', this.onKeypress);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m := entity.NewAlbum(f.AlbumName)
|
m := entity.NewAlbum(f.AlbumName)
|
||||||
|
m.AlbumFavorite = f.AlbumFavorite
|
||||||
|
|
||||||
|
log.Debugf("create album: %+v %+v", f, m)
|
||||||
|
|
||||||
if res := conf.Db().Create(m); res.Error != nil {
|
if res := conf.Db().Create(m); res.Error != nil {
|
||||||
log.Error(res.Error.Error())
|
log.Error(res.Error.Error())
|
||||||
@@ -93,6 +96,8 @@ func CreateAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
|
|
||||||
event.Success(fmt.Sprintf("album \"%s\" created", m.AlbumName))
|
event.Success(fmt.Sprintf("album \"%s\" created", m.AlbumName))
|
||||||
|
|
||||||
|
// event.Publish("config.updated", event.Data(conf.ClientConfig()))
|
||||||
|
|
||||||
c.JSON(http.StatusOK, m)
|
c.JSON(http.StatusOK, m)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("invalid type: %s", typeName)
|
log.Errorf("invalid type: %s", typeName)
|
||||||
c.Data(http.StatusBadRequest, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,8 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
file, err := r.FindLabelThumbByUUID(labelUUID)
|
file, err := r.FindLabelThumbByUUID(labelUUID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": util.UcFirst(err.Error())})
|
log.Errorf(err.Error())
|
||||||
|
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +154,7 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
|
|
||||||
if !util.Exists(fileName) {
|
if !util.Exists(fileName) {
|
||||||
log.Errorf("could not find original for thumbnail: %s", fileName)
|
log.Errorf("could not find original for thumbnail: %s", fileName)
|
||||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
||||||
|
|
||||||
// Set missing flag so that the file doesn't show up in search results anymore
|
// Set missing flag so that the file doesn't show up in search results anymore
|
||||||
file.FileMissing = true
|
file.FileMissing = true
|
||||||
@@ -166,7 +167,7 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("could not read thumbnail: %s", err)
|
log.Errorf("could not read thumbnail: %s", err)
|
||||||
c.Data(http.StatusInternalServerError, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +179,7 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||||||
} else {
|
} else {
|
||||||
log.Errorf("could not create thumbnail: %s", err)
|
log.Errorf("could not create thumbnail: %s", err)
|
||||||
|
|
||||||
c.Data(http.StatusInternalServerError, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -65,13 +65,13 @@ func TestLabelThumbnail(t *testing.T) {
|
|||||||
LabelThumbnail(router, ctx)
|
LabelThumbnail(router, ctx)
|
||||||
result := PerformRequest(app, "GET", "/api/v1/labels/dog/thumbnail/xxx")
|
result := PerformRequest(app, "GET", "/api/v1/labels/dog/thumbnail/xxx")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusBadRequest, result.Code)
|
assert.Equal(t, http.StatusOK, result.Code)
|
||||||
})
|
})
|
||||||
t.Run("invalid label", func(t *testing.T) {
|
t.Run("invalid label", func(t *testing.T) {
|
||||||
app, router, ctx := NewApiTest()
|
app, router, ctx := NewApiTest()
|
||||||
LabelThumbnail(router, ctx)
|
LabelThumbnail(router, ctx)
|
||||||
result := PerformRequest(app, "GET", "/api/v1/labels/xxx/thumbnail/tile_500")
|
result := PerformRequest(app, "GET", "/api/v1/labels/xxx/thumbnail/tile_500")
|
||||||
|
|
||||||
assert.Equal(t, http.StatusNotFound, result.Code)
|
assert.Equal(t, http.StatusOK, result.Code)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,3 +9,6 @@ var photoIconSvg = []byte(`
|
|||||||
var albumIconSvg = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
var albumIconSvg = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
<path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>
|
<path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>
|
||||||
<path d="M0 0h24v24H0z" fill="none"/></svg>`)
|
<path d="M0 0h24v24H0z" fill="none"/></svg>`)
|
||||||
|
|
||||||
|
var labelIconSvg = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"/><path d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16z"/></svg>`)
|
||||||
|
|||||||
@@ -63,17 +63,26 @@ func (s *Repo) FindLabelThumbBySlug(labelSlug string) (file entity.File, err err
|
|||||||
|
|
||||||
// FindLabelThumbByUUID returns a label preview file based on the label UUID.
|
// FindLabelThumbByUUID returns a label preview file based on the label UUID.
|
||||||
func (s *Repo) FindLabelThumbByUUID(labelUUID string) (file entity.File, err error) {
|
func (s *Repo) FindLabelThumbByUUID(labelUUID string) (file entity.File, err error) {
|
||||||
// s.db.LogMode(true)
|
// Search matching label
|
||||||
|
err = s.db.Where("files.file_primary AND files.deleted_at IS NULL").
|
||||||
if err := s.db.Where("files.file_primary AND files.deleted_at IS NULL").
|
|
||||||
Joins("JOIN labels ON labels.label_uuid = ?", labelUUID).
|
Joins("JOIN labels ON labels.label_uuid = ?", labelUUID).
|
||||||
Joins("JOIN photos_labels ON photos_labels.label_id = labels.id AND photos_labels.photo_id = files.photo_id").
|
Joins("JOIN photos_labels ON photos_labels.label_id = labels.id AND photos_labels.photo_id = files.photo_id").
|
||||||
Order("photos_labels.label_uncertainty ASC").
|
Order("photos_labels.label_uncertainty ASC").
|
||||||
First(&file).Error; err != nil {
|
First(&file).Error
|
||||||
return file, err
|
|
||||||
|
if err == nil {
|
||||||
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return file, nil
|
// If failed, search for category instead
|
||||||
|
err = s.db.Where("files.file_primary AND files.deleted_at IS NULL").
|
||||||
|
Joins("JOIN photos_labels ON photos_labels.photo_id = files.photo_id").
|
||||||
|
Joins("JOIN categories c ON photos_labels.label_id = c.label_id").
|
||||||
|
Joins("JOIN labels ON c.category_id = labels.id AND labels.label_uuid= ?", labelUUID).
|
||||||
|
Order("photos_labels.label_uncertainty ASC").
|
||||||
|
First(&file).Error
|
||||||
|
|
||||||
|
return file, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Labels searches labels based on their name.
|
// Labels searches labels based on their name.
|
||||||
|
|||||||
Reference in New Issue
Block a user