Frontend: Add photos to new album

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer
2019-12-17 04:39:23 +01:00
parent 1cc8cefc92
commit 4ab44c5c23
11 changed files with 130 additions and 44 deletions

View File

@@ -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();
}, },

View File

@@ -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

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)
}) })
} }

View File

@@ -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
} }
}) })

View File

@@ -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)
}) })
} }

View File

@@ -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>`)

View File

@@ -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.