-
-
Save AllanJeremy/dd5398b1416ac02393a8d896cdcbfe14 to your computer and use it in GitHub Desktop.
/** This component is now maintained via the [quasar-helpers repo](https://github.com/AllanJeremy/quasar-helpers) */ | |
import { createUploaderComponent } from "quasar"; | |
import { computed, ref, watch } from "vue"; | |
// Firebase stuff | |
import { | |
getDownloadURL, | |
ref as firebaseRef, | |
uploadBytesResumable, | |
} from "@firebase/storage"; | |
import { storage } from "../../firebase"; // or import {getStorage} from '@firebase/storage' | |
//? importing 👆 from local firebase file because it is attached to our app ~ `storage` here is the value returned by getStorage(firebaseApp) | |
// Export a Vue component | |
export default createUploaderComponent({ | |
// defining the QUploader plugin here | |
name: "FirebaseUploader", // your component's name | |
props: { | |
/** Whether all inputs should be blocked when upload is in progress or not */ | |
blocking: { | |
type: Boolean, | |
default: true, | |
}, | |
/** The firebase storage directory your files will be uploaded to */ | |
//! This assumes that each instance of FirebaseUploader will only upload to a specific directory - customization implementation would be worth considering | |
directory: { | |
type: String, | |
default: "/", | |
}, | |
}, | |
emits: [ | |
// ...your custom events name list | |
], | |
injectPlugin({ props, emit, helpers }) { | |
let uploadTaskList = ref([]); | |
let uploadProgressList = ref([]); | |
let uploadInProgress = ref(false); | |
let uploadedFiles = ref([]); | |
// Using watcher because computed isn't triggered when the progress list array is updated | |
watch( | |
() => uploadProgressList, | |
() => { | |
uploadInProgress.value = false; | |
if (uploadProgressList.value.length) { | |
uploadInProgress.value = uploadProgressList.value.reduce( | |
(prev, curr) => prev || curr, | |
false | |
); | |
// Uploads complete - emit uploaded event with file details | |
emit("uploaded", uploadedFiles); | |
} | |
}, | |
{ deep: true } | |
); | |
// [ REQUIRED! ] | |
// We're working on uploading files | |
const isUploading = computed(() => uploadInProgress.value); | |
/** Shows overlay on top of the | |
uploader signaling it's waiting | |
on something (blocks all controls) */ | |
const isBusy = computed(() => { | |
return props.blocking ? uploadInProgress.value : false; | |
}); | |
// [ REQUIRED! ] | |
// Cancel all uploads | |
function abort() { | |
uploadTaskList.value.forEach((uploadTask) => { | |
uploadTask.cancel(); | |
}); | |
} | |
// [ REQUIRED! ] | |
// Start the uploading process | |
function upload() { | |
// Reset uploads | |
uploadTaskList.value = []; | |
uploadProgressList.value = []; | |
helpers.queuedFiles.value.forEach((fileToUpload, i) => { | |
// No point uploading the file if it has already been uploaded before | |
if (helpers.uploadedFiles.value.includes(fileToUpload)) return; | |
//? 👇 This can be whatever you want ~ can use UUID to generate unique file names | |
const fileName = `${Date.now()}-${fileToUpload.name}`; | |
const storageRef = firebaseRef( | |
storage, | |
`${props.directory}/${fileName}` | |
); | |
const uploadTask = uploadBytesResumable(storageRef, fileToUpload); | |
uploadTaskList.value = [...uploadTaskList.value, uploadTask]; | |
uploadTask.on( | |
"state_changed", | |
(snapshot) => { | |
helpers.updateFileStatus( | |
fileToUpload, | |
"uploading", | |
snapshot.bytesTransferred | |
); | |
uploadProgressList.value[i] = snapshot.state === "running"; | |
}, | |
(err) => { | |
console.error( | |
"Something went wrong while trying to upload the file.", | |
err | |
); | |
helpers.updateFileStatus( | |
fileToUpload, | |
"failed", | |
uploadTask.snapshot.bytesTransferred | |
); | |
uploadProgressList.value[i] = false; | |
}, | |
async () => { | |
const uploadUrl = await getDownloadURL(uploadTask.snapshot.ref); | |
// The upload for all files will be accessible when all files are done uploading | |
uploadedFiles.value.push({ | |
originalFile: fileToUpload, | |
uploadUrl, | |
}); | |
const { bytesTransferred } = uploadTask.snapshot; | |
helpers.updateFileStatus( | |
fileToUpload, | |
"uploaded", | |
bytesTransferred | |
); | |
helpers.uploadedFiles.value = [ | |
...helpers.uploadedFiles.value, | |
fileToUpload, | |
]; | |
helpers.uploadedSize.value += bytesTransferred; | |
uploadProgressList.value[i] = false; | |
} | |
); | |
}); | |
} | |
return { | |
isUploading, | |
isBusy, | |
abort, | |
upload, | |
}; | |
}, | |
}); |
With the help of a friend, we figured out a problem with the component emitting the "uploaded" signal before the upload was actually completed. The following two lines:
// Uploads complete - emit uploaded event with file details
emit("uploaded", uploadedFiles);
should be replaced with:
// Uploads complete - emit uploaded event with file details
if (uploadedFiles && (uploadedFiles.value.length >= uploadProgressList.value.length)) {
emit("uploaded", uploadedFiles);
}
This is great. You might consider putting this into a repo so people can make pull requests. In any case, thanks for sharing.
Allan thanks for your initiative,... looking your code it fits perfect to quasar devs description and I guess this code will runs smooth but it's my first time implementing quasar's q-upload and like me - maybe - other newbies will find this masterpiece, so the question for helping me and others are: How we mix this to regular q-upload component and about the composable counterpart "html" into template session, can you bring us an example? Early thanks(so much)
Just seen these comments now. A little too late :(
Turning this into a repo @guswelter . Thanks y'all!
Created a repo with the same
@saro-saravanan , really sorry I saw this late amidst the flood of github notifications.
I'm really happy you got to resolve it and I have pushed your suggestion in the repo :)
Feel free to fork the repo and create PRs in case you have any suggestions :)
Allan, this is a great component, thanks for making it. I have run into a problem with the @uploaded callback function - I'm not sure how to access the params.
I posted a question about this in https://www.reddit.com/r/vuejs/comments/vd48lp/quasar_vue3_quploaded_event_callback_function/.
I'm not usually a coder, but I have a lot of front-end development experience in the distant past. I'm really enjoying Vue3 and Quasar to do some rapid prototyping at the moment, but I'm stuck due to my lack of knowledge about how reactivity works in Vue3.
I have an @ uploaded event handler uploadedFiles for q-uploader that gets called correctly, but I'm unable to figure out how to access the parameter that comes back.
uploadedfiles: (info) => {
console.log(JSON.stringify(info.value[0]));
let obj = info.value[0];
console.log(obj);
}
prints the following value for obj:
{
originalFile: {
__key: "1655232940s509PXL_202206ss13_140636960.jpg2713876",
__status: "uploaded",
__uploaded: 2713876,
__progress: 1,
__sizeLabel: "2.6MB",
__progressLabel: "100.00%",
__img: {}
},
uploadUrl: "https://firebasestorage.googleapis.com/v0/b/xxx.appspot.com/o/Jackson-99%%2Fexterior%2F1655325169893-PXL_0636960.jpg?alt=media&token=71fe9229-5ea2-485f-80dc-c4e0a8257e3b"
}
However, if I try to access
obj.uploadUrl
I get an exception for trying to access an unknown attribute of obj.
Do you have an example of how to access the parameters for the @ uploaded function q-uploader?
I probably need to add that I'm using an extended version of q-uploader called FirebaseUploader. I would appreciate any examples or pointers on how to solve this. Thanks!