Finished MVP of user file upload
This commit is contained in:
@@ -4,7 +4,7 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|||||||
import { incrementFileStorageUsed } from '/imports/api/users/methods/updateFileStorageUsed';
|
import { incrementFileStorageUsed } from '/imports/api/users/methods/updateFileStorageUsed';
|
||||||
import UserImages from '/imports/api/files/userImages/UserImages';
|
import UserImages from '/imports/api/files/userImages/UserImages';
|
||||||
|
|
||||||
const removeArchiveCreature = new ValidatedMethod({
|
const removeUserImage = new ValidatedMethod({
|
||||||
name: 'userImages.methods.remove',
|
name: 'userImages.methods.remove',
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
'fileId': {
|
'fileId': {
|
||||||
@@ -41,4 +41,4 @@ const removeArchiveCreature = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default removeArchiveCreature;
|
export default removeUserImage;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-btn
|
<v-btn
|
||||||
outlined
|
outlined
|
||||||
class="image-upload-button ma-1"
|
class="image-upload-button"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:color="fileUploadError ? 'error' : undefined"
|
:color="fileUploadError ? 'error' : undefined"
|
||||||
:disabled="uploadingInProgress"
|
:disabled="uploadingInProgress"
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<v-progress-linear
|
<v-progress-linear
|
||||||
v-if="uploadingInProgress"
|
v-if="uploadingInProgress"
|
||||||
:value="uploadingInProgress"
|
:value="progress"
|
||||||
:indeterminate="uploadIndeterminate"
|
:indeterminate="uploadIndeterminate"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@@ -48,7 +48,10 @@ export default {
|
|||||||
let uploadInstance = UserImages.insert({
|
let uploadInstance = UserImages.insert({
|
||||||
file: file,
|
file: file,
|
||||||
chunkSize: 'dynamic',
|
chunkSize: 'dynamic',
|
||||||
allowWebWorkers: true
|
allowWebWorkers: true,
|
||||||
|
meta: {
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
self.uploadingInProgress = true;
|
self.uploadingInProgress = true;
|
||||||
@@ -79,7 +82,6 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
uploadInstance.on('progress', function (progress, fileObj) {
|
uploadInstance.on('progress', function (progress, fileObj) {
|
||||||
console.log('Upload Percentage: ' + progress, fileObj)
|
|
||||||
// Update our progress bar with actual progress
|
// Update our progress bar with actual progress
|
||||||
self.uploadIndeterminate = false;
|
self.uploadIndeterminate = false;
|
||||||
self.progress = progress;
|
self.progress = progress;
|
||||||
|
|||||||
@@ -137,18 +137,21 @@ export default {
|
|||||||
this.uploadFile(file);
|
this.uploadFile(file);
|
||||||
},
|
},
|
||||||
handleDragOver(event) {
|
handleDragOver(event) {
|
||||||
event.preventDefault();
|
// TODO
|
||||||
this.dragging = true;
|
// event.preventDefault();
|
||||||
|
// this.dragging = true;
|
||||||
},
|
},
|
||||||
handleDragLeave() {
|
handleDragLeave() {
|
||||||
this.dragging = false;
|
// TODO
|
||||||
|
// this.dragging = false;
|
||||||
},
|
},
|
||||||
handleDrop(event) {
|
handleDrop(event) {
|
||||||
console.log(event);
|
// TODO
|
||||||
event.preventDefault();
|
// console.log(event);
|
||||||
const file = event.dataTransfer.files[0];
|
// event.preventDefault();
|
||||||
this.dragging = false;
|
// const file = event.dataTransfer.files[0];
|
||||||
this.uploadFile(file);
|
// this.dragging = false;
|
||||||
|
// this.uploadFile(file);
|
||||||
},
|
},
|
||||||
uploadFile(file) {
|
uploadFile(file) {
|
||||||
// Implement your file upload logic here
|
// Implement your file upload logic here
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card
|
|
||||||
:data-id="`${model._id}-archive-card`"
|
|
||||||
tile
|
|
||||||
>
|
|
||||||
<v-img
|
|
||||||
:src="model.link"
|
|
||||||
:aspect-ratio="1.4"
|
|
||||||
class="white--text align-end"
|
|
||||||
>
|
|
||||||
<v-card-title>
|
|
||||||
{{ model.name }}
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-subtitle>
|
|
||||||
{{ model.size }}
|
|
||||||
</v-card-subtitle>
|
|
||||||
</v-img>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-flex />
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
@click="remove"
|
|
||||||
>
|
|
||||||
<v-icon>mdi-delete</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
:href="`${model.link}?download=true`"
|
|
||||||
>
|
|
||||||
<v-icon>mdi-download</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="js">
|
|
||||||
import removeUserImage from '/imports/api/files/userImages/methods/removeUserImage';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
model: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
restoreLoading: false,
|
|
||||||
removeLoading: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
remove() {
|
|
||||||
removeUserImage.call({ fileId: this.model._id }, (error) => {
|
|
||||||
if (error) console.error(error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -123,7 +123,11 @@ export default {
|
|||||||
{
|
{
|
||||||
userId,
|
userId,
|
||||||
}, {
|
}, {
|
||||||
sort: {'size': -1},
|
sort: {
|
||||||
|
'meta.createdAt': -1,
|
||||||
|
'name': 1,
|
||||||
|
'size': -1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
).map(f => {
|
).map(f => {
|
||||||
f.size = prettyBytes(f.size);
|
f.size = prettyBytes(f.size);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<img
|
<img
|
||||||
class="preview-image v-sheet v-card elevation-6"
|
class="preview-image v-sheet v-card elevation-6"
|
||||||
|
:class="themeClasses"
|
||||||
:src="href"
|
:src="href"
|
||||||
@click="back"
|
@click="back"
|
||||||
>
|
>
|
||||||
@@ -8,17 +9,32 @@
|
|||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
export default {
|
export default {
|
||||||
|
inject: {
|
||||||
|
theme: {
|
||||||
|
default: {
|
||||||
|
isDark: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
href: {
|
href: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
themeClasses() {
|
||||||
|
return {
|
||||||
|
'theme--dark': this.theme.isDark,
|
||||||
|
'theme--light': !this.theme.isDark,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
back() {
|
back() {
|
||||||
this.$store.dispatch('popDialogStack');
|
this.$store.dispatch('popDialogStack');
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
100
app/imports/client/ui/files/userImages/UserImageCard.vue
Normal file
100
app/imports/client/ui/files/userImages/UserImageCard.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<v-card
|
||||||
|
class="user-image-card d-flex flex-column"
|
||||||
|
@click="previewImage"
|
||||||
|
>
|
||||||
|
<v-img
|
||||||
|
:src="model.link"
|
||||||
|
:data-id="`${model._id}-image`"
|
||||||
|
/>
|
||||||
|
<v-flex />
|
||||||
|
<v-card-title class="no-wrap">
|
||||||
|
{{ model.name }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-subtitle class="no-wrap">
|
||||||
|
{{ model.size }}
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-flex />
|
||||||
|
<v-menu left>
|
||||||
|
<template #activator="{ on }">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
v-on="on"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item @click="removeUserFile">
|
||||||
|
<v-list-item-title>
|
||||||
|
Delete file
|
||||||
|
<v-icon right>
|
||||||
|
mdi-delete
|
||||||
|
</v-icon>
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
:href="`${model.link}?download=true`"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<v-icon>mdi-download</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||||
|
import removeUserImage from '/imports/api/files/userImages/methods/removeUserImage';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
removeLoading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
removeUserFile() {
|
||||||
|
this.removeLoading = true;
|
||||||
|
removeUserImage.call({ fileId: this.model._id }, (error) => {
|
||||||
|
this.removeLoading = false;
|
||||||
|
if (error) {
|
||||||
|
snackbar({text: error.reason || error.message || error.toString()})
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
previewImage() {
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'image-preview-dialog',
|
||||||
|
elementId: `${this.model._id}-image`,
|
||||||
|
data: {
|
||||||
|
href: this.model.link,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.no-wrap {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.user-image-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -64,20 +64,33 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row dense>
|
||||||
|
<template v-if="imageFiles && imageFiles.length">
|
||||||
|
<v-col
|
||||||
|
v-for="file in imageFiles"
|
||||||
|
:key="file._id"
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
md="4"
|
||||||
|
lg="3"
|
||||||
|
xl="2"
|
||||||
|
>
|
||||||
|
<user-image-card :model="file" />
|
||||||
|
</v-col>
|
||||||
|
</template>
|
||||||
<v-col
|
<v-col
|
||||||
sm="12"
|
cols="12"
|
||||||
md="6"
|
sm="6"
|
||||||
lg="4"
|
md="4"
|
||||||
|
lg="3"
|
||||||
|
xl="2"
|
||||||
>
|
>
|
||||||
<smart-image-input
|
<image-upload-input
|
||||||
label="Image input"
|
style="height: 100%; width: 100%; min-height: 120px;"
|
||||||
:value="inputImageHref"
|
|
||||||
@change="(val, ack) => {inputImageHref = val; /*ack()*/}"
|
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<!--
|
<!--
|
||||||
<v-row dense>
|
<v-row dense>
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-subheader> Images </v-subheader>
|
<v-subheader> Images </v-subheader>
|
||||||
@@ -108,6 +121,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
-->
|
-->
|
||||||
|
</v-col>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -118,7 +132,7 @@ import prettyBytes from 'pretty-bytes';
|
|||||||
import ArchiveFileCard from '/imports/client/ui/files/ArchiveFileCard.vue';
|
import ArchiveFileCard from '/imports/client/ui/files/ArchiveFileCard.vue';
|
||||||
import FileStorageStats from '/imports/client/ui/files/FileStorageStats.vue';
|
import FileStorageStats from '/imports/client/ui/files/FileStorageStats.vue';
|
||||||
import ImageUploadInput from '/imports/client/ui/components/ImageUploadInput.vue';
|
import ImageUploadInput from '/imports/client/ui/components/ImageUploadInput.vue';
|
||||||
import UserImageCard from '/imports/client/ui/files/UserImageCard.vue';
|
import UserImageCard from '/imports/client/ui/files/userImages/UserImageCard.vue';
|
||||||
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
import { snackbar } from '/imports/client/ui/components/snackbars/SnackbarQueue';
|
||||||
import { archiveSchema } from '/imports/api/creature/archive/ArchiveCreatureFiles';
|
import { archiveSchema } from '/imports/api/creature/archive/ArchiveCreatureFiles';
|
||||||
import migrateArchive from '/imports/migrations/archive/migrateArchive';
|
import migrateArchive from '/imports/migrations/archive/migrateArchive';
|
||||||
@@ -133,7 +147,8 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
ArchiveFileCard,
|
ArchiveFileCard,
|
||||||
FileStorageStats,
|
FileStorageStats,
|
||||||
SmartImageInput,
|
UserImageCard,
|
||||||
|
ImageUploadInput,
|
||||||
},
|
},
|
||||||
data(){ return {
|
data(){ return {
|
||||||
updateStorageUsedLoading: false,
|
updateStorageUsedLoading: false,
|
||||||
@@ -164,6 +179,24 @@ export default {
|
|||||||
return f;
|
return f;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
imageFiles() {
|
||||||
|
const userId = Meteor.userId();
|
||||||
|
return UserImages.find(
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
}, {
|
||||||
|
sort: {
|
||||||
|
'meta.createdAt': -1,
|
||||||
|
'name': 1,
|
||||||
|
'size': -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).map(f => {
|
||||||
|
f.size = prettyBytes(f.size);
|
||||||
|
f.link = UserImages.link(f);
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
archiveUploadInProgress(val){
|
archiveUploadInProgress(val){
|
||||||
@@ -231,18 +264,14 @@ export default {
|
|||||||
|
|
||||||
// These are the event functions, don't need most of them, it shows where we are in the process
|
// These are the event functions, don't need most of them, it shows where we are in the process
|
||||||
uploadInstance.on('start', function () {
|
uploadInstance.on('start', function () {
|
||||||
console.log('Starting');
|
|
||||||
self.archiveUploadIndeterminate = false;
|
self.archiveUploadIndeterminate = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadInstance.on('end', function (error, fileObj) {
|
uploadInstance.on('end', function (error, fileObj) {
|
||||||
console.log('On end File Object: ', fileObj);
|
|
||||||
self.archiveUploadInProgress = false;
|
self.archiveUploadInProgress = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadInstance.on('uploaded', function (error, fileObj) {
|
uploadInstance.on('uploaded', function (error, fileObj) {
|
||||||
console.log('uploaded: ', fileObj);
|
|
||||||
|
|
||||||
// Remove the file from the input box
|
// Remove the file from the input box
|
||||||
self.file = undefined;
|
self.file = undefined;
|
||||||
|
|
||||||
@@ -251,7 +280,6 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
uploadInstance.on('error', function (error, fileObj) {
|
uploadInstance.on('error', function (error, fileObj) {
|
||||||
console.log('Error during upload: ' + error, fileObj)
|
|
||||||
const text = error.reason || error.message || error;
|
const text = error.reason || error.message || error;
|
||||||
snackbar({text});
|
snackbar({text});
|
||||||
self.archiveFileError = text;
|
self.archiveFileError = text;
|
||||||
@@ -259,12 +287,10 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
uploadInstance.on('progress', function (progress, fileObj) {
|
uploadInstance.on('progress', function (progress, fileObj) {
|
||||||
console.log('Upload Percentage: ' + progress, fileObj)
|
|
||||||
// Update our progress bar
|
|
||||||
self.archiveUploadProgress = progress;
|
self.archiveUploadProgress = progress;
|
||||||
});
|
});
|
||||||
|
|
||||||
uploadInstance.start(); // Must manually start the upload
|
uploadInstance.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
fr.readAsText(file);
|
fr.readAsText(file);
|
||||||
|
|||||||
@@ -3,5 +3,11 @@ import UserImages from '/imports/api/files/userImages/UserImages';
|
|||||||
Meteor.publish('userImages', function () {
|
Meteor.publish('userImages', function () {
|
||||||
return UserImages.find({
|
return UserImages.find({
|
||||||
userId: this.userId,
|
userId: this.userId,
|
||||||
|
}, {
|
||||||
|
sort: {
|
||||||
|
'meta.createdAt': -1,
|
||||||
|
'name': 1,
|
||||||
|
'size': -1,
|
||||||
|
},
|
||||||
}).cursor;
|
}).cursor;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user