Tidied up component structure
This commit is contained in:
parent
c002ceccbd
commit
2327b9b8bf
19 changed files with 224 additions and 379 deletions
222
src/components/viewComponents/galleryComponents/captureCard.vue
Normal file
222
src/components/viewComponents/galleryComponents/captureCard.vue
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
<template>
|
||||
<div class="captureCard uk-card uk-card-default uk-card-hover uk-padding-remove uk-width-medium" v-bind:class="{ 'uk-card-secondary': $store.state.globalSettings.darkMode }">
|
||||
|
||||
<div class="uk-card-media-top">
|
||||
|
||||
<a class="lightbox-link" v-bind:href="imgURL" v-bind:data-caption="metadata.filename">
|
||||
<img class="uk-width-1-1" v-bind:data-src="thumbURL" v-bind:alt="metadata.id" width="300" height="225" uk-img>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="uk-card-body uk-padding-small">
|
||||
<div class="uk-width-1-1 uk-margin-small uk-margin-remove-left uk-margin-remove-right" uk-grid>
|
||||
<div class="uk-margin-remove-top uk-padding-remove uk-width-expand">{{ metadata.filename }}</div>
|
||||
<div class="uk-margin-remove-top uk-padding-remove uk-width-auto">
|
||||
<a href="#" v-on:click="delCaptureConfirm()" class="uk-icon-link" uk-icon="trash"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-expand"><time>{{ betterTimestring }}</time></div>
|
||||
<div class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-auto">
|
||||
<a v-bind:href="metadataModalTarget" uk-toggle>More...</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="uk-card-footer uk-padding-small">
|
||||
<span v-if="temporary" class="uk-label uk-label-danger uk-margin-small-right" uk-tooltip="title: Capture will be removed automatically; delay: 500">Temporary</span>
|
||||
|
||||
<span v-for="tag in tags" :key="tag" v-on:click="delTagConfirm(tag)" class="uk-label uk-margin-small-right deletable-label"> {{ tag }} </span>
|
||||
|
||||
<a v-bind:href="tagModalTarget" uk-toggle>
|
||||
<span class="uk-label uk-label-success uk-margin-small-right">Add</span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-bind:id="metadataModalID" uk-modal>
|
||||
|
||||
<div class="uk-modal-dialog uk-modal-body" v-bind:class="{ 'uk-light uk-background-secondary': $store.state.globalSettings.darkMode }" >
|
||||
<button class="uk-modal-close-default" type="button" uk-close></button>
|
||||
<h2 class="uk-modal-title">{{ metadata.filename }}</h2>
|
||||
<p><b>Path: </b>{{ path }}</p>
|
||||
<p><b>Time: </b>{{ betterTimestring }}</p>
|
||||
<p><b>ID: </b>{{ metadata.id }}</p>
|
||||
<p><b>Format: </b>{{ metadata.format }}</p>
|
||||
|
||||
<div v-for="(value, key) in metadata.custom" :key="key" >
|
||||
<p><b>{{ key }}: </b>{{ value }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-bind:id="tagModalID" uk-modal>
|
||||
|
||||
<form class="uk-modal-dialog uk-modal-body uk-margin-auto-vertical" v-bind:class="{ 'uk-light uk-background-secondary': $store.state.globalSettings.darkMode }" @submit.prevent="handleTagSubmit">
|
||||
|
||||
<div class="uk-inline">
|
||||
<span class="uk-form-icon" uk-icon="icon: tag"></span>
|
||||
<input v-model="newtag" class="uk-input uk-form-width-medium uk-form-small" type="text" name="tagname" placeholder="tag">
|
||||
|
||||
<button class="uk-button uk-button-default uk-margin-left uk-form-small uk-modal-close" type="button">Cancel</button>
|
||||
<button type="submit" class="uk-button uk-button-primary uk-margin-left uk-form-small">Save</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UIkit from 'uikit';
|
||||
import axios from 'axios'
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: 'captureCard',
|
||||
|
||||
props: {
|
||||
temporary: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
metadata: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
tags: [],
|
||||
newtag: "",
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleTagSubmit: function(event) {
|
||||
console.log(this.tagURL);
|
||||
console.log(this.newtag);
|
||||
this.newTagRequest(this.newtag);
|
||||
this.newtag = "";
|
||||
UIkit.modal(event.target.parentNode).hide(); // TODO: Remove somehow
|
||||
},
|
||||
|
||||
delCaptureConfirm: function(tag_string) {
|
||||
var context = this
|
||||
this.modalConfirm('Permanantly delete capture?')
|
||||
.then(function() {
|
||||
context.delCaptureRequest()
|
||||
});
|
||||
},
|
||||
|
||||
delCaptureRequest: function() {
|
||||
// Send tag DELETE request
|
||||
axios.delete(this.captureURL)
|
||||
.then(response => {
|
||||
// Emit signal to update capture list
|
||||
this.$root.$emit('globalUpdateCaptureList')
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error) // Let mixin handle error
|
||||
})
|
||||
},
|
||||
|
||||
newTagRequest: function(tag_string) {
|
||||
// Send tag PUT request
|
||||
axios.put(this.tagURL, [tag_string])
|
||||
.then(response => {
|
||||
// Update tag array
|
||||
this.getTagRequest()
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error) // Let mixin handle error
|
||||
})
|
||||
},
|
||||
|
||||
delTagConfirm: function(tag_string) {
|
||||
var context = this;
|
||||
this.modalConfirm(`Remove tag '${tag_string}'?`).then(function() {
|
||||
context.delTagRequest(tag_string)
|
||||
});
|
||||
},
|
||||
|
||||
delTagRequest: function(tag_string) {
|
||||
console.log(tag_string)
|
||||
// Send tag DELETE request
|
||||
axios.delete(this.tagURL, {data: [tag_string]})
|
||||
.then(response => {
|
||||
// Update tag array
|
||||
this.getTagRequest()
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error) // Let mixin handle error
|
||||
})
|
||||
},
|
||||
|
||||
getTagRequest: function() {
|
||||
// Send tag request
|
||||
axios.get(this.tagURL)
|
||||
.then(response => {
|
||||
this.tags = response.data.metadata.tags
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error) // Let mixin handle error
|
||||
})
|
||||
},
|
||||
|
||||
makeModalName: function(prefix) {
|
||||
return prefix + this.metadata.id
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
created: function () {
|
||||
this.getTagRequest()
|
||||
},
|
||||
|
||||
computed: {
|
||||
tagModalID: function () {
|
||||
return this.makeModalName("tag-modal-")
|
||||
},
|
||||
tagModalTarget: function () {
|
||||
return "#" + this.tagModalID
|
||||
},
|
||||
metadataModalID: function () {
|
||||
return this.makeModalName("metadata-modal-")
|
||||
},
|
||||
metadataModalTarget: function () {
|
||||
return "#" + this.metadataModalID
|
||||
},
|
||||
thumbURL: function () {
|
||||
return this.captureURL + "/download?thumbnail=true"
|
||||
},
|
||||
imgURL: function () {
|
||||
return this.captureURL + "/download/" + this.metadata.filename
|
||||
},
|
||||
tagURL: function () {
|
||||
return this.captureURL + "/tags"
|
||||
},
|
||||
captureURL: function () {
|
||||
return this.$store.getters.uri + "/camera/capture/" + this.metadata.id
|
||||
},
|
||||
betterTimestring: function () {
|
||||
var dtSplit = this.metadata.time.split("_");
|
||||
var date = dtSplit[0]
|
||||
var time = dtSplit[1].replace(/-/g, ":")
|
||||
return date + " " + time
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
93
src/components/viewComponents/galleryComponents/scanCard.vue
Normal file
93
src/components/viewComponents/galleryComponents/scanCard.vue
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<div class="captureCard uk-card uk-card-primary uk-card-hover uk-padding-remove uk-width-medium">
|
||||
|
||||
<div class="uk-card-media-top">
|
||||
|
||||
<a href="#" >
|
||||
<img class="uk-width-1-1" v-bind:data-src="thumbnail" v-bind:alt="metadata.scan_id" width="300" height="225" v-on:click="$root.$emit('globalUpdateCaptureFolder', metadata.custom.scan_id)" uk-img>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="uk-card-body uk-padding-small">
|
||||
<div class="uk-width-1-1 uk-margin-small uk-margin-remove-left uk-margin-remove-right" uk-grid>
|
||||
<div class="uk-margin-remove-top uk-padding-remove uk-width-expand"><b>Scan:</b> {{ metadata.custom.basename }}</div>
|
||||
</div>
|
||||
|
||||
<div class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-expand"><time>{{ betterTimestring }}</time></div>
|
||||
<div class="uk-text-meta uk-margin-remove-top uk-padding-remove uk-width-auto">
|
||||
<a v-bind:href="metadataModalTarget" uk-toggle>More...</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="uk-card-footer uk-padding-small">
|
||||
<span v-for="tag in metadata.tags" :key="tag" class="uk-label uk-margin-small-right deletable-label"> {{ tag }} </span>
|
||||
</div>
|
||||
|
||||
<div v-bind:id="metadataModalID" uk-modal>
|
||||
|
||||
<div class="uk-modal-dialog uk-modal-body">
|
||||
<button class="uk-modal-close-default" type="button" uk-close></button>
|
||||
<h2 class="uk-modal-title">{{ metadata.basename }}</h2>
|
||||
<p><b>Time: </b>{{ betterTimestring }}</p>
|
||||
<p><b>Scan ID: </b>{{ metadata.custom.scan_id }}</p>
|
||||
|
||||
<div v-for="(value, key) in metadata.custom" :key="key" >
|
||||
<p><b>{{ key }}: </b>{{ value }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: 'captureCard',
|
||||
|
||||
props: {
|
||||
metadata: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
thumbnail: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
makeModalName: function(prefix) {
|
||||
return prefix + this.metadata.id
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
tagModalID: function () {
|
||||
return this.makeModalName("tag-modal-")
|
||||
},
|
||||
tagModalTarget: function () {
|
||||
return "#" + this.tagModalID
|
||||
},
|
||||
metadataModalID: function () {
|
||||
return this.makeModalName("metadata-modal-")
|
||||
},
|
||||
metadataModalTarget: function () {
|
||||
return "#" + this.metadataModalID
|
||||
},
|
||||
betterTimestring: function () {
|
||||
var dtSplit = this.metadata.custom.time.split("_");
|
||||
var date = dtSplit[0]
|
||||
var time = dtSplit[1].replace(/-/g, ":")
|
||||
return date + " " + time
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
270
src/components/viewComponents/galleryDisplay.vue
Normal file
270
src/components/viewComponents/galleryDisplay.vue
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
<template>
|
||||
<div class="galleryDisplay uk-padding uk-padding-remove-top">
|
||||
|
||||
<nav class="uk-navbar-container uk-navbar-transparent navbar" uk-navbar="mode: click">
|
||||
<div class="uk-navbar-left uk-padding-remove-top uk-padding-remove-bottom">
|
||||
<ul class="uk-navbar-nav">
|
||||
<li v-bind:class="[sortDescending ? 'uk-active' : '']"><a v-on:click="sortDescending=true;" class="uk-icon-link" href="#" uk-icon="icon: arrow-down"></a></li>
|
||||
<li v-bind:class="[!sortDescending ? 'uk-active' : '']"><a v-on:click="sortDescending=false;" class="uk-icon-link" href="#" uk-icon="icon: arrow-up"></a></li>
|
||||
<li>
|
||||
<a href="#">Filter</a>
|
||||
<div class="uk-navbar-dropdown" v-bind:class="{ 'uk-light uk-background-secondary': $store.state.globalSettings.darkMode }">
|
||||
<ul class="uk-nav uk-navbar-dropdown-nav">
|
||||
<form class="uk-form-stacked">
|
||||
<div v-for="tag in allTags" :key="tag" class="uk-margin-small">
|
||||
<label><input class="uk-checkbox" type="checkbox" v-bind:id="tag" v-bind:value="tag" v-model="checkedTags" checked> {{ tag }}</label>
|
||||
</div>
|
||||
</form>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div v-if="$store.getters.ready" class="uk-padding-remove-top" uk-lightbox="toggle: .lightbox-link">
|
||||
|
||||
<div v-if="(galleryFolder)" class="uk-padding uk-padding-remove-right uk-padding-remove-bottom">
|
||||
<a href="#" v-on:click="galleryFolder=''" class="uk-icon-button" uk-icon="arrow-left"></a>
|
||||
<h3 class="uk-inline uk-float-right uk-margin-remove"><b>SCAN</b> {{ allScans[galleryFolder].metadata.filename }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="uk-grid-medium uk-grid-match uk-margin-top" uk-grid>
|
||||
|
||||
<div v-for="item in sortedItems" :key="item.metadata.id">
|
||||
<scanCard
|
||||
v-if="'isScan' in item"
|
||||
:metadata="item.metadata"
|
||||
:thumbnail="item.thumbnail"
|
||||
/>
|
||||
<captureCard
|
||||
v-else
|
||||
:metadata="item.metadata"
|
||||
:temporary="item.temporary"
|
||||
:path="item.path"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import captureCard from './galleryComponents/captureCard.vue'
|
||||
import scanCard from './galleryComponents/scanCard.vue'
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: 'galleryDisplay',
|
||||
|
||||
components: {
|
||||
captureCard,
|
||||
scanCard
|
||||
},
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
captureList: [],
|
||||
checkedTags: [],
|
||||
sortDescending: true,
|
||||
galleryFolder: "",
|
||||
scanTag: 'scan'
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// A global signal listener to perform a gallery refresh
|
||||
this.$root.$on('globalUpdateCaptureList', () => {
|
||||
this.updateCaptureList()
|
||||
})
|
||||
// A global signal listener to set the gallery folder
|
||||
this.$root.$on('globalUpdateCaptureFolder', (folder) => {
|
||||
this.galleryFolder = folder
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateCaptureList: function() {
|
||||
console.log("Updating capture list...")
|
||||
// Send move request
|
||||
axios.get(this.captureApiUri)
|
||||
.then(response => {
|
||||
this.$store.dispatch('updateState'); // Update store state for good measure
|
||||
this.captureList = response.data; // Update boxes from response
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error) // Let mixin handle error
|
||||
})
|
||||
},
|
||||
|
||||
filterCaptureList: function(list, filterTags) {
|
||||
var result = [];
|
||||
for (var capture of list) {
|
||||
// Assume exclusion
|
||||
var includeCapture = false;
|
||||
|
||||
// Filter by selected tags
|
||||
var tags = capture.metadata.tags;
|
||||
let checker = (arr, target) => target.every(v => arr.includes(v));
|
||||
// True if all tags match
|
||||
includeCapture = checker(tags, filterTags);
|
||||
|
||||
// Add to capture list if matched
|
||||
if (includeCapture == true) {
|
||||
result.push(capture);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
sortCaptureList: function(list) {
|
||||
function compare(a, b) {
|
||||
if (a.metadata.time < b.metadata.time)
|
||||
return -1;
|
||||
if (a.metadata.time > b.metadata.time)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.sortDescending == true) {
|
||||
return list.sort(compare).reverse();
|
||||
}
|
||||
else {
|
||||
return list.sort(compare);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
captureApiUri: function () {
|
||||
return this.$store.getters.uri + "/camera/capture"
|
||||
},
|
||||
|
||||
allTags: function () {
|
||||
// Return an array of unique tags across all captures
|
||||
var tags = [];
|
||||
for (var capture of this.captureList) {
|
||||
for (var tag of capture.metadata.tags) {
|
||||
if (!tags.includes(tag)) {
|
||||
tags.push(tag);
|
||||
};
|
||||
};
|
||||
};
|
||||
return tags.sort()
|
||||
},
|
||||
|
||||
noScanCaptureList: function () {
|
||||
var captures = [];
|
||||
for (var capture of this.captureList) {
|
||||
// Assume exclusion
|
||||
var includeCapture = false;
|
||||
|
||||
// Filter by selected tags
|
||||
var tags = capture.metadata.tags;
|
||||
|
||||
// Add to capture list if matched
|
||||
if (!tags.includes(this.scanTag)) {
|
||||
captures.push(capture);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
return captures
|
||||
},
|
||||
|
||||
allScans: function () {
|
||||
// Return an array of unique tags across all captures
|
||||
var scans = {};
|
||||
|
||||
for (var capture of this.captureList) {
|
||||
var custom = capture.metadata.custom;
|
||||
var tags = capture.metadata.tags;
|
||||
|
||||
if ('scan_id' in custom) {
|
||||
var id = custom['scan_id']
|
||||
|
||||
// If this scan ID hasn't been seen before
|
||||
if (!(id in scans)) {
|
||||
scans[id] = {}
|
||||
scans[id].isScan = true
|
||||
scans[id].captureList = []
|
||||
scans[id].metadata = {
|
||||
filename: custom.basename,
|
||||
time: custom.time,
|
||||
id: custom.scan_id
|
||||
}
|
||||
scans[id].metadata.tags = []
|
||||
scans[id].metadata.custom = {}
|
||||
};
|
||||
|
||||
// Add the capture object to the scan
|
||||
scans[id].captureList.push(capture)
|
||||
|
||||
// Add missing scan metadata, prioritising first capture
|
||||
for (var key of Object.keys(custom)) {
|
||||
if (!(key in scans[id].metadata.custom)) {
|
||||
scans[id].metadata.custom[key] = custom[key]
|
||||
};
|
||||
};
|
||||
|
||||
// Append missing tags
|
||||
for (var tag of tags) {
|
||||
if (!scans[id].metadata.tags.includes(tag)) {
|
||||
scans[id].metadata.tags.push(tag)
|
||||
};
|
||||
};
|
||||
|
||||
// Create a preview thumbnail
|
||||
if (!('thumbnail' in scans[id])) {
|
||||
scans[id].thumbnail = this.$store.getters.uri + "/camera/capture/" + capture.metadata.id + "/download?thumbnail=true"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
return scans
|
||||
},
|
||||
|
||||
scanList: function () {
|
||||
return Object.values(this.allScans)
|
||||
},
|
||||
|
||||
itemList: function () {
|
||||
if (this.galleryFolder) {
|
||||
console.log(this.allScans[this.galleryFolder].captureList)
|
||||
return this.allScans[this.galleryFolder].captureList
|
||||
}
|
||||
else {
|
||||
return this.noScanCaptureList.concat(this.scanList)
|
||||
}
|
||||
},
|
||||
|
||||
filteredItems: function () {
|
||||
return this.filterCaptureList(this.itemList, this.checkedTags)
|
||||
},
|
||||
|
||||
sortedItems: function () {
|
||||
return this.sortCaptureList(this.filteredItems)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.navbar {
|
||||
border-width: 0 0 1px 0;
|
||||
border-style: solid;
|
||||
border-color: rgba(180, 180, 180, 0.25)
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue