Skip to content

Instantly share code, notes, and snippets.

@AllanJeremy
Last active November 6, 2023 12:32
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AllanJeremy/dd5398b1416ac02393a8d896cdcbfe14 to your computer and use it in GitHub Desktop.
Save AllanJeremy/dd5398b1416ac02393a8d896cdcbfe14 to your computer and use it in GitHub Desktop.
Firebase extension of Quasar's QUploader component (that performs firebase uploads)
/** 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,
};
},
});
@saro-saravanan
Copy link

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!

@saro-saravanan
Copy link

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

@guswelter
Copy link

This is great. You might consider putting this into a repo so people can make pull requests. In any case, thanks for sharing.

@noxOfficer
Copy link

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)

@AllanJeremy
Copy link
Author

AllanJeremy commented Nov 6, 2023

Just seen these comments now. A little too late :(
Turning this into a repo @guswelter . Thanks y'all!

Created a repo with the same

@AllanJeremy
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment