Last active
January 25, 2023 21:00
-
-
Save SpencerCooley/f92b8f62981c4d3935802a35159c13bc to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div class="image"> | |
<ProgressSpinner v-if="loading" style="width:50px;height:50px;display:block;margin:0 auto;" strokeWidth="8" fill="var(--surface-ground)" animationDuration=".5s"/> | |
<Card v-if="editMode" style="margin-bottom: 2em"> | |
<template #title> | |
</template> | |
<template #content> | |
<input type="file" @change="chosen" ref="file" style="display: none" multiple="multiple"> | |
<Toast /> | |
<div v-if="!fileChosen" > | |
<div :class="dropAreaClass" @dragover.prevent="dragOver" @dragexit="dragExit" @drop.prevent="dropped" @click="$refs.file.click()" > | |
<div class="drop-zone-label"> | |
<h3>Drag & Drop Files</h3> | |
<h4>Or click to select files</h4> | |
</div> | |
</div> | |
<Button v-if="content" @click="toggleEditMode()" style="margin-top:20px;" label="Cancel" icon="pi pi-times" class="p-button p-button-danger" /> | |
</div> | |
<!--the container that is used once at least one file has been chosen--> | |
<div v-if="fileChosen" class="content-manipulation-module"> | |
<div v-if="step === 1" class="image-holder-area"> | |
<!--this is for existing files.--> | |
<div v-for="(image, index) in processedMedia" :key="image.url" class="preview"> | |
<Button @click="removeImage(index, true)" icon="pi pi-times" class="p-button-rounded p-button-danger remove-image" /> | |
<img :src="image.url" style="width:100%;"> | |
</div> | |
<!--this is for new files.--> | |
<div v-for="(image, index) in chosenFiles" :key="image.url" class="preview"> | |
<Button @click="removeImage(index, false)" icon="pi pi-times" class="p-button-rounded p-button-danger remove-image" /> | |
<img :src="image.url" style="width:100%;"> | |
</div> | |
</div> | |
<div v-if="step === 1" :class="dropAreaClass" @dragover.prevent="dragOver" @dragexit="dragExit" @drop.prevent="dropped"> | |
<div class="drop-zone-label" @click="$refs.file.click()"> | |
<h3>Drag & Drop Files</h3> | |
<h4>Or click to select files</h4> | |
</div> | |
</div> | |
<div v-if="step === 2" class="image-info"> | |
<h5>Title:</h5> | |
<InputText v-model="title"/> | |
<h5>Caption:</h5> | |
<div class="quill-editor" | |
v-model="text" | |
v-quill:myQuillEditor="editorOption"> | |
</div> | |
</div> | |
<div v-if="step === 3"> | |
<h2>Your files are being uploaded.</h2> | |
<div class="image-holder-area"> | |
<div v-for="(image, index) in chosenFiles" :key="image.url" class="preview uploading"> | |
<Button :ref="`checkmark${index}`" icon="pi pi-check" class="p-button-rounded p-button-success successful-upload hide" /> | |
<img :src="image.url" style="width:100%;"> | |
<!--loading cover class is loading state--> | |
<span :ref="`loader${index}`" class="loading-cover"></span> | |
</div> | |
</div> | |
</div> | |
<Button v-if="step === 1" @click="nextStep()" style="margin-top:20px;" label="Next Step" icon="pi pi-chevron-right" class="p-button" /> | |
<Button v-if="step === 2" @click="previousStep()" style="margin-top:20px;" label="Previous Step" icon="pi pi-chevron-left" class="p-button" /> | |
<Button :disabled="!validated" v-if="step === 2" @click="saveContent()" style="margin-top:20px;" label="Post" icon="pi pi-check" class="p-button" /> | |
<Button v-if="step !== 3" @click="toggleEditMode()" style="margin-top:20px;" label="Close" icon="pi pi-times" class="p-button p-button-danger" /> | |
</div> | |
</template> | |
</Card> | |
<div v-if="!editMode && !loading" class="viewable-content"> | |
<ConfirmDialog :group="content.id"></ConfirmDialog> | |
<!--content mode. --> | |
<Card v-if="!editMode && content" style="margin-bottom: 2em"> | |
<template #title> | |
<Menubar v-if="content"> | |
<template #start> | |
<p >Created on: {{formatDate(content.get('createdAt'))}}</p> | |
<p>Edited on: {{formatDate(content.get('createdAt'))}}</p> | |
</template> | |
<template #end> | |
<Button @click="toggleEditMode()" icon="pi pi-pencil" class="p-button-rounded p-button-info p-button-outlined content-creation" /> | |
<Button @click="confirmDelete()" icon="pi pi-times" class="p-button-rounded p-button-danger p-button-outlined content-creation" /> | |
</template> | |
</Menubar> | |
</template> | |
<template #content> | |
<!-- <ContentGallery :gallery="{title: '', images: processedMedia}"/> --> | |
<ContentGallery v-if="content.get('type') === 'image'" :signedurls="signedurls" :content="content"/> | |
</template> | |
</Card> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import Moralis from 'moralis'; | |
export default { | |
data() { | |
return { | |
dropAreaClass: 'image-drop-zone', | |
chosenFiles: [], | |
media: [], | |
step: 1, | |
title: '', | |
text: '', | |
editMode: false, | |
loading:false, | |
editorOption: { | |
// some quill options | |
modules: { | |
toolbar: [ | |
['bold', 'italic', 'underline', 'image', 'video', 'link'], | |
[{ 'list': 'ordered'}, { 'list': 'bullet' }] | |
] | |
} | |
} | |
}; | |
}, | |
props: ['content', 'gate', 'signedurls'], | |
watch: {}, | |
computed: { | |
// fix this for displaying images | |
processedMedia() { | |
let processed = []; | |
if(!this.gate.tokenGated.gated) { | |
this.media.forEach((image) => { | |
processed.push({ | |
url: `https://storage.googleapis.com/${this.gate.object.id.toLowerCase()}/${image}`, | |
title: '' | |
}); | |
}); | |
} | |
// | |
if(this.gate.tokenGated.gated) { | |
const currentUser = Moralis.User.current(); | |
this.media.forEach(async (image) => { | |
// grab the signed url and then throgh the result in the processed array. | |
const theSignedUrl = this.signedurls[image] | |
processed.push({ | |
url: theSignedUrl, | |
title: '' | |
}); | |
}); | |
} | |
// if it is gated we need to fetch the signed urls. | |
return processed; | |
}, | |
fileChosen() { | |
if(this.chosenFiles.length || this.media.length) { | |
return true; | |
} | |
return false; | |
}, | |
validated() { | |
if (this.title) { | |
return true; | |
} | |
return false; | |
} | |
}, | |
methods: { | |
fileExtension(mimetype) { | |
if (mimetype === 'image/png') { | |
return 'png'; | |
} | |
if (mimetype === 'image/jpeg') { | |
return 'jpg'; | |
} | |
if (mimetype === 'image/gif') { | |
return 'gif'; | |
} | |
}, | |
async createMediaDeleteTask(filename, bucketName) { | |
const that = this; | |
const myPromise = new Promise(async (resolve, reject) => { | |
// create a new Task object | |
// this will be something you can reference later in a job | |
const Task = Moralis.Object.extend("Task"); | |
const newTask = new Task(); | |
newTask.set('action', 'delete-media'); | |
newTask.set('file', filename); | |
newTask.set('bucket', bucketName); | |
try { | |
await newTask.save(); | |
resolve(); | |
} catch(error) { | |
reject(error); | |
} | |
}); | |
return myPromise; | |
}, | |
async confirmDelete() { | |
const that = this; | |
this.$confirm.require({ | |
message: 'Do you want to delete this content?', | |
header: 'Delete Confirmation', | |
icon: 'pi pi-info-circle', | |
acceptClass: 'p-button-danger', | |
group: this.content.id, | |
async accept() { | |
that.loading = true; | |
// create a task for each image in the gallery | |
// this create a task so we can delete this media later. | |
const createDeleteTasks = new Promise(async (resolve, reject) => { | |
try { | |
that.media.forEach(async (image) => { | |
await that.createMediaDeleteTask(image, that.gate.object.id.toLowerCase()); | |
}); | |
resolve(); | |
} catch(erorr) { | |
reject(error); | |
} | |
}); | |
await createDeleteTasks; | |
await that.content.destroy(); | |
that.loading = false; | |
that.$emit('save-complete'); | |
}, | |
reject: () => { | |
console.log("don't delete"); | |
} | |
}); | |
}, | |
toggleEditMode() { | |
this.editMode = !this.editMode; | |
this.chosenFiles = []; | |
}, | |
formatDate(date) { | |
return `${date.getUTCMonth()+1}/${date.getUTCDay()+1}/${date.getUTCFullYear()}` | |
}, | |
fileIsValid(file) { | |
// is the file type appropriate? | |
if(!this.isImage(file.type)) { | |
// do toast thing and return false; | |
this.$toast.add({severity:'error', summary: `${file.name} needs to be an image file`, detail:'Wrong file type', life: 7000}); | |
return false; | |
} | |
// is the file size appropriate? | |
if(!this.hasAppropriateSize(file.size)) { | |
this.$toast.add({severity:'error', summary: `${file.name} needs to be under 6mb`, detail:'Image not included', life: 7000}); | |
return false; | |
} | |
// everything checks out. the file is valid for uploading. | |
return true; | |
}, | |
isImage(fileType) { | |
const fileTypesAllowed = ['image/png', 'image/gif', 'image/jpeg']; | |
if(fileTypesAllowed.includes(fileType)) { | |
return true; | |
} | |
return false; | |
}, | |
hasAppropriateSize(size) { | |
if(size > 6000000) { | |
return false; | |
} | |
return true; | |
}, | |
async saveContent() { | |
if(!this.content) { | |
this.step = 3; | |
// what does this look like. | |
const Content = Moralis.Object.extend("Content"); | |
const newImagePost = new Content(); | |
newImagePost.set('caption', this.text); | |
newImagePost.set('title', this.title); | |
newImagePost.set('type', 'image'); | |
newImagePost.set('creator', Moralis.User.current()); | |
newImagePost.set('gate', this.gate.object); | |
newImagePost.set('media', []); | |
newImagePost.setACL(new Moralis.ACL(Moralis.User.current())); | |
try { | |
this.loading = true; | |
await newImagePost.save(); | |
this.loading = false; | |
//this is where it saves all the images. | |
//this is where it saves all the images. | |
//this is where it saves all the images. | |
//this is where it saves all the images. | |
//this is where it saves all the images. | |
// we need this to save to google cloud and not moralis. | |
await this.uploadImages(newImagePost); | |
this.$emit('save-complete'); | |
// close this process out. | |
// not sure what needs to happen yet. Probalby just run that save complet emit function. | |
} catch(error) { | |
console.log(error); | |
} | |
} else { | |
// this is what happens if it is an existing Thang! | |
this.content.set('caption', this.text); | |
this.content.set('title', this.title); | |
try { | |
this.loading = true; | |
await this.content.save(); | |
this.loading = false; | |
if (this.chosenFiles.length) { | |
this.step = 3; | |
await this.uploadImages(this.content); | |
} | |
this.$emit('save-complete'); | |
this.editMode = false; | |
this.step = 1; | |
this.chosenFiles = []; | |
// close this process out. | |
// not sure what needs to happen yet. Probalby just run that save complet emit function. | |
} catch(error) { | |
console.log(error); | |
} | |
} | |
// now actually save the data to the database. | |
// BOOM!!!! | |
}, | |
async uploadImages(moralisObject) { | |
const that = this; | |
const theGate = this.gate.object.id.toLowerCase(); | |
const myPromise = new Promise((resolve, reject) => { | |
// go through each file in the file list array | |
const currentUser = Moralis.User.current(); | |
this.chosenFiles.forEach(async (image, index) => { | |
const randomString = Math.random().toString(36).substring(7); | |
const formData = new FormData(); | |
formData.append("file", image.file); | |
formData.append("id", currentUser.id); | |
formData.append("name", `${randomString}-content-image`); | |
formData.append("signature", currentUser.attributes.authData.moralisEth.signature); | |
formData.append("gate", theGate); | |
// formData.append("gate", ) | |
const theUpload = await this.$axios.post(`${process.env.mediaAPIBaseUrl}/upload`, | |
formData, | |
{headers: {'Content-Type': 'multipart/form-data'}} | |
); | |
const fileExtension = image.file.name.split('.').pop().toLowerCase(); | |
try { | |
// add the file to the image content object | |
moralisObject.get('media').push(`${randomString}-content-image.${fileExtension}`); | |
// save the image content object. | |
await moralisObject.save(); | |
const loaderString = `loader${index}`; | |
const checkMarkString = `checkmark${index}`; | |
that.$refs[loaderString][0].classList.add('hide'); | |
that.$refs[checkMarkString][0].$el.classList.remove('hide'); | |
if(that.chosenFiles.length === index + 1) { | |
resolve() | |
} | |
} catch(error) { | |
console.log(error); | |
} | |
}); | |
}); | |
return myPromise; | |
}, | |
previousStep() { | |
this.step = 1; | |
}, | |
nextStep() { | |
this.step = 2; | |
}, | |
async removeImage(index, existsAlready) { | |
if (existsAlready) { | |
if (this.content.get('media').length === 1) { | |
this.$toast.add({severity:'error', summary: `You need to have at least one image associated with this post.`, detail:'Will not delete', life: 7000}); | |
return; | |
} | |
this.loading = true; | |
await this.createMediaDeleteTask(this.content.get('media')[index], this.gate.object.id.toLowerCase()); | |
// now that a task for deleting the media exists | |
// we can remove the file from the media array and save the object. | |
this.content.get('media').splice(index, 1); | |
await this.content.save(); | |
this.media = this.content.get('media'); | |
this.loading = false; | |
} else { | |
// just remove the image from the chosenFiles array. | |
this.chosenFiles.splice(index, 1); | |
} | |
}, | |
dragOver(){ | |
this.dropAreaClass = 'image-drop-zone drag-over'; | |
}, | |
dragExit(){ | |
this.dropAreaClass = 'image-drop-zone'; | |
}, | |
chosen(e) { | |
const theFiles = e.target.files; | |
this.handleFilePreview(theFiles); | |
}, | |
dropped(e){ | |
console.log('hello world'); | |
this.handleFilePreview(e.dataTransfer.files); | |
}, | |
handleFilePreview(files) { | |
const theFiles = files; | |
console.log(theFiles); | |
const that = this; | |
Array.from(theFiles).forEach((file) => { | |
if(this.fileIsValid(file)) { | |
const reader = new FileReader(); | |
reader.readAsDataURL(file); | |
reader.onloadend = () => { | |
this.chosenFiles.push({ | |
url: reader.result, | |
file: file | |
}); | |
} | |
} | |
}); | |
this.dropAreaClass = `image-drop-zone dropped`; | |
} | |
}, | |
async mounted() { | |
if(!this.content) { | |
this.editMode = true; | |
} else { | |
this.editMode = false; | |
this.text = this.content.get('caption'); | |
this.title = this.content.get('title'); | |
this.media = this.content.get('media'); | |
} | |
} | |
} | |
</script> | |
<style lang="scss"> | |
@keyframes gradient { | |
0% { | |
background-position: 0% 50%; | |
} | |
50% { | |
background-position: 100% 50%; | |
} | |
100% { | |
background-position: 0% 50%; | |
} | |
} | |
.content-manipulation-module { | |
padding:20px; | |
background-color:#f1f1f1; | |
border-radius: 10px; | |
.image-drop-zone { | |
background-color:rgb(210, 210, 210); | |
min-height:60px; | |
padding-top:30px; | |
padding-bottom:30px; | |
} | |
.image-holder-area { | |
padding-top:20px; | |
padding-bottom:20px; | |
display:grid; | |
grid-template-columns: 30% 30% 30%; | |
justify-content: space-around; | |
row-gap: 20px; | |
.uploading { | |
.successful-upload { | |
position:absolute; | |
top:-7px; | |
right:-7px; | |
cursor: default; | |
} | |
.hide{ | |
visibility:hidden; | |
} | |
.loading-cover { | |
width:20px; | |
height:20px; | |
display:block; | |
position:absolute; | |
width:100%; | |
height:100%; | |
top:0px; | |
opacity:.7; | |
background: linear-gradient(-45deg, #000000, #e73c7e, #23a6d5, #23d5ab); | |
background-size: 400% 400%; | |
animation: gradient 3s ease infinite; | |
} | |
} | |
.preview { | |
.remove-image { | |
position:absolute; | |
top:-7px; | |
right:-7px; | |
opacity:.8; | |
&:hover { | |
opacity:1.0; | |
} | |
} | |
position:relative; | |
.p-button.p-button-icon-only.p-button-rounded{ | |
height:0.3rem; | |
} | |
.p-button.p-button-icon-only{ | |
width:1.3rem; | |
} | |
img{ | |
-webkit-box-shadow: 0px 0px 4px 0px #000000; | |
box-shadow: 0px 0px 4px 0px #000000; | |
&:hover { | |
-webkit-box-shadow: 0px 0px 6px 0px #000000; | |
box-shadow: 0px 0px 6px 0px #000000; | |
} | |
} | |
} | |
background-color:#fff; | |
} | |
.upload-process-module { | |
background-color:#fff; | |
} | |
} | |
.image-drop-zone { | |
background-color:#f1f1f1; | |
min-height:250px; | |
padding-top:65px; | |
cursor:pointer; | |
border-radius: 10px; | |
h3, h4{ | |
text-align:center; | |
color:#666; | |
} | |
} | |
.drag-over { | |
-webkit-box-shadow: 0px 0px 5px 0px #000000; | |
box-shadow: 0px 0px 5px 0px #000000; | |
border-style: dashed; | |
border-width:2px; | |
opacity: .7; | |
} | |
.dropped { | |
padding-top:20px; | |
border-style: none; | |
} | |
@media (min-width:675px) { | |
.content-manipulation-module { | |
.image-holder-area { | |
grid-template-columns: 27% 27% 27%; | |
} | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment