No More Posting New Topics!

If you have a question or an issue, please start a thread in our Github Discussions Forum.
This forum is closed for new threads/ topics.

Navigation

    Quasar Framework

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search

    Q-Uploader with Composition API

    Framework
    5
    22
    4600
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • P
      pazarbasifatih last edited by

      So, I discovered 2 things.
      First thing is that Q-uploader has secret v-on bindings, @added @rejected uploaded and @finish, which I believe should make their places in the documentation.
      Second is the old way of Quploader for Firebase can be modified to receive vue refs.
      But the following code has some issues with this.

      For example I get Uncaught TypeError: Cannot read property 'files' of undefined error with this code.
      But where do the this.files, this.uploadSize, this.uploadedSize, this.removeFile(file) come from?
      Also this.$emit looks problematic, where should I put the context in this file? There is no setup configuration.

      import { createUploaderComponent } from "quasar";
      import { computed, ref } from "vue";
      
      // export a Vue component
      export default createUploaderComponent({
        // defining the QUploader plugin here
      
        name: "QFirebaseUploader", // your component's name
      
        props: {
          // ...your custom props
        },
      
        emits: [
          // ...your custom events name list
        ],
      
        injectPlugin({ props, emit, helpers }) {
          // can call any other composables here
          // as this function will run in the component's setup()
      
          const uploading = ref(false);
          const uploadTasks = ref([]);
      
          // [ REQUIRED! ]
          // We're working on uploading files
          const isUploading = computed(() => {
            return uploading.value;
          });
      
          // [ optional ]
          // Shows overlay on top of the
          // uploader signaling it's waiting
          // on something (blocks all controls)
          const isBusy = computed(() => {
            return uploading.value;
          });
      
          // [ REQUIRED! ]
          // Abort and clean up any process
          // that is in progress
          function abort() {
            uploadTasks.value.forEach((uploadTask) => {
              uploadTask.cancel();
            });
      
            uploading.value = false;
          }
      
          // [ REQUIRED! ]
          // Start the uploading process
          function upload() {
            uploading.value = true;
      
            this.files.forEach((file) => {
              const datetime = new Date().toISOString().split(".")[0];
              const newRef = this.path + datetime + "_" + file.name;
              const uploadTask = firebaseCs.ref(newRef).put(file);
      
              uploadTasks.value.push(uploadTask);
      
              uploadTask.on(
                "state_changed",
                (snapshot) => {
                  this.uploadSize = snapshot.totalBytes;
                  this.uploadedSize = snapshot.bytesTransferred;
                },
                (error) => console.log(error),
                () => {
                  uploadTask.snapshot.ref.getDownloadURL().then((downloadURL) => {
                    this.$emit("upload", {
                      url: downloadURL,
                      name: file.name,
                      uploadedDate: datetime,
                    });
                    this.removeFile(file);
                  });
      
                  this.uploadedSize - this.uploadSize === 0 &&
                    (uploading.value = false);
                }
              );
            });
          }
      
          return {
            isUploading,
            isBusy,
      
            abort,
            upload,
          };
        },
      });
      
      dobbel 1 Reply Last reply Reply Quote 0
      • dobbel
        dobbel @pazarbasifatih last edited by dobbel

        @pazarbasifatih said in Q-Uploader with Composition API:

        First thing is that Q-uploader has secret v-on bindings, @added @rejected uploaded and @finish, which I believe should make their places in the documentation.

        Those events are actually descibed in the Q-Uploader Qv1 api doc ( under events):
        https://quasar.dev/vue-components/uploader#QUploader-API

        P 1 Reply Last reply Reply Quote 1
        • P
          pazarbasifatih @dobbel last edited by

          @dobbel Yeay, more of them! Thanks for uncovering the mystery 😃 Quasar never ceases to amaze me.
          Do you have any idea about what this refers to in the qv1{QUploaderBase} from "quasar"

          1 Reply Last reply Reply Quote 0
          • P
            pazarbasifatih last edited by

            Maybe I can work with those Events under the Quploader Api. They seem to be offering @uploaded and @uploading… Also they can take files

            dobbel 1 Reply Last reply Reply Quote 0
            • dobbel
              dobbel @pazarbasifatih last edited by dobbel

              @pazarbasifatih

              Do you have any idea about what this refers to in the qv1{QUploaderBase} from “quasar”

              this refers to the Vue component itself.

              Thanks for uncovering the mystery

              Qv2 docs will have the Components Api on top of the page so you can’t miss it 😉

              1 Reply Last reply Reply Quote 1
              • P
                pazarbasifatih last edited by pazarbasifatih

                So, ok. I came up with a solution with factory function but it spits out an error.
                This one is a free project on firebase.

                import firebase from "firebase/app";
                import "firebase/storage";
                
                const firebaseConfig = {
                  apiKey: "AIzaSyCp5sEmtSaDOjIdm5E88Wy41cCqFOoYd2o",
                  authDomain: "someproject-43c3a.firebaseapp.com",
                  projectId: "someproject-43c3a",
                  storageBucket: "someproject-43c3a.appspot.com",
                  messagingSenderId: "445668329191",
                  appId: "1:445668329191:web:9bebd22c71e3ca8c05c442",
                };
                
                // init firebase
                firebase.initializeApp(firebaseConfig);
                
                // init services
                const projectStorage = firebase.storage();
                
                
                export {  projectStorage};
                

                And this is the useStorage composable

                import { projectStorage } from "../boot/firebase";
                import { ref } from "vue";
                
                const useStorage = () => {
                  const error = ref(null);
                  const downloadURL = ref(null);
                  const filePath = ref(null);
                
                  const uploadFile = async (file) => {
                    filePath.value = `covers/${file.name}`;
                    const storageRef = projectStorage.ref(filePath.value);
                    
                  try {
                      const res = await storageRef.put(file);
                      downloadURL.value = await res.ref.getDownloadURL();
                    } catch (err) {
                      console.log(err.message);
                      error.value = err;
                    }
                  };
                  return { uploadFile, downloadURL, error };
                };
                
                export default useStorage;
                

                And this is where it’s placed in the the Quploader as a factory function.

                <template>
                  <q-page class="flex flex-center">
                    <q-uploade
                      multiple
                      :factory="factory"
                      @factory-failed="onFactoryFail"
                    ></q-uploader>
                  </q-page>
                </template>
                
                <script>
                import { ref } from "vue";
                import useStorage from "src/composables/useStorage";
                import { projectStorage } from "src/boot/firebase";
                
                export default {
                  name: "PageIndex",
                
                  setup(props) {
                    const {
                      uploadFile,
                      downloadURL,  
                    } = useStorage();
                
                    //Events
                    async function factory(files) {
                      //   console.log("files[0].name:", files[0].name);
                      await uploadFile(files[0]);
                      console.log("downloadURL.value:", downloadURL.value);
                    }
                    function onFactoryFail(err, files) {
                      console.log("factoryFail error:", err);
                      console.log("factoryFail files:", files);
                    }   
                
                    return {
                      factory, onFactoryFail
                    };
                  },
                };
                </script>
                

                It’s giving me the downloadURL.value, so it’s successful in uploading. @factory-failed is giving me the following errors about url:

                factoryFail error: TypeError: Cannot read property 'url' of undefined at getProp (xhr-uploader-plugin.js?e40a:131) at performUpload (xhr-uploader-plugin.js?e40a:136) at eval (xhr-uploader-plugin.js?e40a:116)

                factoryFail files: [File] (Eventhough the bucket has that file uploaded in it)

                Eventually this is the screen that the user ends up with:

                0d74fbfd-f152-4577-a984-bb8ba497bdae-image.png

                A similar error has been recorded in this topic but didn’t receive a solution.

                1 Reply Last reply Reply Quote 0
                • P
                  pazarbasifatih last edited by pazarbasifatih

                  I made a discovery today. I will be working on it over the next days. It’s the uploader-core which exposes all the functions and references. So I can begin working with the createUploaderComponent Hurray.

                  Edit: Ok ok this thing goes deeper than I’ve expected. There’s this xhr-uploader-plugin which exposes how things are running. I’ve finally found how the heck the queuedFiles can be called in the createUploaderComponent: helpers.queuedFiles.value. So I’ll keep running my expedition from there.

                  1 Reply Last reply Reply Quote 0
                  • P
                    pazarbasifatih last edited by pazarbasifatih

                    I have converted github/birchb 's wonderful q-uploader firebase implementation into the composition api using this files above. And it’s uploading, updating the progess and removing the file on demand. It has 2 minor issues with emitting the ‘uploaded’ event and calculating the blue uploadSize label on top of the component when the item is removed. But I’m sure they’re solvable problems. So the code is…

                    import { createUploaderComponent } from "quasar";
                    import { computed, ref } from "vue";
                    import firebase from "firebase/app";
                    
                    // export a Vue component
                    export default createUploaderComponent({
                      // defining the QUploader plugin here
                    
                      name: "MyUploader", // your component's name
                    
                      props: {
                        pathPrefix: {
                          type: String,
                          default: "",
                        },
                      },
                    
                      emits: ["uploaded", "failed", "removed"],
                    
                      injectPlugin({ props, emit, helpers }) {
                        // can call any other composables here
                        // as this function will run in the component's setup()
                        const storage = ref(firebase.storage().ref());
                        const activeTasks = ref([]);
                        // [ REQUIRED! ]
                        // We're working on uploading files
                        const isUploading = computed(() => {
                          // return <Boolean>
                        });
                    
                        // [ optional ]
                        // Shows overlay on top of the
                        // uploader signaling it's waiting
                        // on something (blocks all controls)
                        const isBusy = computed(() => {
                          // return <Boolean>
                        });
                    
                        // [ REQUIRED! ]
                        // Abort and clean up any process
                        // that is in progress
                        function abort() {
                          // ...
                        }
                    
                        // [ REQUIRED! ]
                        // Start the uploading process
                        function upload() {
                          if (props.disable || !helpers.queuedFiles.value.length) {
                            return;
                          }
                          helpers.queuedFiles.value.forEach(async (file) => {
                            await __uploadSingleFile(file);
                          });
                        }
                        function __uploadSingleFile(file) {
                          let pathPrefix = props.pathPrefix || "";
                          // const fileRef = storage.value.child(pathPrefix + file.name)
                          const fileRef = storage.value.child(`${pathPrefix}/${file.name}`);
                          helpers.updateFileStatus(file, "uploading", 0);
                          const uploadTask = fileRef.put(file);
                          activeTasks.value.push(uploadTask);
                          // Listen for state changes, errors, and completion of the upload.
                          uploadTask.on(
                            firebase.storage.TaskEvent.STATE_CHANGED, // or 'state_changed'
                            (snapshot) => {
                              // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
                              if (file.__status !== "failed") {
                                const loaded = Math.min(
                                  snapshot.totalBytes,
                                  snapshot.bytesTransferred
                                );
                                helpers.uploadedSize.value += loaded - file.__uploaded;
                    
                                helpers.updateFileStatus(file, "uploading", loaded);
                              }
                            },
                            (error) => {
                              // A full list of error codes is available at
                              // https://firebase.google.com/docs/storage/web/handle-errors
                              helpers.queuedFiles.value.push(file);
                              helpers.updateFileStatus(file, "failed");
                              emit("failed", { file, error });
                              helpers.uploadedSize.value -= file.__uploaded;
                              activeTasks.value = activeTasks.value.filter((t) => t !== uploadTask);
                            },
                            () => {
                              // Upload completed successfully, now we can get the download URL
                              uploadTask.snapshot.ref
                                .getDownloadURL()
                                .then((downloadURL) => {
                                  const fullPath = uploadTask.snapshot.ref.fullPath;
                                  const fileName = uploadTask.snapshot.ref.name;
                                  helpers.uploadedFiles.value.push(file);
                                  helpers.updateFileStatus(file, "uploaded");
                                  let uploadTime = _.round(new Date().getTime() / 1000);
                                  console.log("TCL: __uploadSingleFile -> uploadTime", uploadTime);
                                  let [fileSize, fileType] = [file.size, file.type];
                                  console.log(
                                    downloadURL,
                                    fileName,
                                    fileSize,
                                    fileType,
                                    fullPath,
                                    uploadTime
                                  );
                                  emit("uploaded", {
                                    downloadURL,
                                    fileName,
                                    fileSize,
                                    fileType,
                                    fullPath,
                                    uploadTime,
                                  });
                                  helpers.uploadedSize.value += file.size - file.__uploaded;
                                  // helpers.uploadedSize.value = 0;
                                })
                                .catch((error) => {
                                  emit("failed", { file, error });
                                });
                              activeTasks.value = activeTasks.value.filter((t) => t !== uploadTask);
                            }
                          );
                        }
                    
                        function removeFile(file) {
                          if (props.disable) {
                            return;
                          }
                    
                          if (file.__status === "uploaded") {
                            helpers.uploadedFiles.value = helpers.uploadedFiles.value.filter(
                              (f) => f.name !== file.name
                            );
                    
                            // As of Quasar v2 beta7 uploadSize cannot be taken out of helpers/state. So removeFile won't be able to change blue header the label on top.
                            helpers.uploadSize.value -= file.__uploaded;
                          } else if (file.__status === "uploading") {
                            file.__abort();
                          } else {
                            helper.uploadSize.value -= file.size;
                          }
                    
                          helpers.files.value = helpers.files.value.filter((f) => {
                            if (f.name !== file.name) {
                              return true;
                            }
                    
                            f._img !== void 0 && window.URL.revokeObjectURL(f._img.src);
                    
                            return false;
                          });
                          helpers.queuedFiles.value = helpers.queuedFiles.value.filter(
                            (f) => f.name !== file.name
                          );
                          emit("removed", [file]);
                        }
                    
                        return {
                          isUploading,
                          isBusy,
                    
                          abort,
                          upload,
                        };
                      },
                    });
                    

                    It gives the following warning [Vue warn]: Component emitted event "uploaded" but it is neither declared in the emits option nor as an "onUploaded" prop. I don’t know why. But it’s a fully functional Firebase Uploader for Quasar v2 now.

                    Edit: emits: [“uploaded”, “failed”, “removed”],

                    metalsadman 1 Reply Last reply Reply Quote 0
                    • metalsadman
                      metalsadman @pazarbasifatih last edited by metalsadman

                      @pazarbasifatih said in Q-Uploader with Composition API:

                      emits: [
                      // …your custom events name list
                      ],

                      it’s a warning that you should register your custom events in the component events options array. https://v3.vuejs.org/guide/component-custom-events.html#defining-custom-events

                      P 1 Reply Last reply Reply Quote 0
                      • P
                        pazarbasifatih @metalsadman last edited by pazarbasifatih

                        @metalsadman Great! Thank you for point that out.

                        Now let’s me see how I can fix the uploadSizeLabel issue.

                        Uploading (A feature to block parallel uploading would be great)
                        339e67b4-80b8-47eb-94bf-995caf598e06-image.png
                        Uploaded
                        2dcfb634-f8e1-4199-83ff-987a00d1e4aa-image.png
                        Remove File (See that removeFile() does not recalculate the uploadSizeLabel because I can’t reach uploadSize from helpers. It should be configured via a PR in uploader-core.js)
                        ff68ba0e-60a1-4b47-b538-43c6fcd021af-image.png
                        And this problem leads to the following, when all files are removed.
                        eadda5af-0004-427a-b953-26e327618303-image.png
                        And if you add a file, it keeps adding to the old uploadSize.
                        2.8 MB + 400 KB = 3.2 MB // true but this is not the behavior you’d expect to see.
                        7f3a5780-b3fc-4b07-8c06-aecfe74fc699-image.png
                        When done with uploading, the label is a complete(!) mess
                        03610b2d-8478-42d2-8d4e-891013088721-image.png

                        1 Reply Last reply Reply Quote 0
                        • rstoenescu
                          rstoenescu Admin last edited by

                          That’s the whole point with the label. It shows how much you’ve uploaded. There’s also the max-files and max-total-size props. If we remove from the total size (as you proposed) then those props are essentially useless since they can be bypassed easily.

                          But the larger picture is that you also have access to the “reset()” method, which resets the components to its initial state. You could do that after the successful upload.

                          P 1 Reply Last reply Reply Quote 0
                          • P
                            pazarbasifatih @rstoenescu last edited by pazarbasifatih

                            @rstoenescu Great! I tried running reset() method in the MyUploader.js but I am probably doing something wrong because, it’s giving me ReferenceError: reset is not defined at eval error.

                            So I tried creating my own function reset() inside the MyUploader.js but it has the infamous uploadSize.value in the original example, which is not inside const state, so I can’t use the helpers object for reaching it.

                              function reset () {
                                if (props.disable === false) {
                                  state.abort()
                                  state.uploadedSize.value = 0
                                  uploadSize.value = 0
                                  revokeImgURLs()
                                  state.files.value = []
                                  state.queuedFiles.value = []
                                  state.uploadedFiles.value = []
                                }
                              }
                            

                            I’m sure there must be a proper way of calling that function but I lack the knowledge.

                            Sorry to bother for this, but I’ve got kind of obsessed with it now. Wouldn’t putting uploadSize inside the const state = {uploadSize: ref(0)} in the uploader-core be a little bit more flexible?

                            dobbel 1 Reply Last reply Reply Quote 0
                            • dobbel
                              dobbel @pazarbasifatih last edited by dobbel

                              @pazarbasifatih said in Q-Uploader with Composition API:

                              ReferenceError: reset is not defined at eval

                              Add a ref property to the component.

                              <q-uploader ref="myRefToQUploader" ..... />
                              

                              then:

                              this.$refs[myRefToQUploader].reset()
                              

                              See:
                              https://v3.vuejs.org/guide/component-template-refs.html

                              P 1 Reply Last reply Reply Quote 0
                              • P
                                pazarbasifatih @dobbel last edited by

                                @dobbel said in Q-Uploader with Composition API:

                                this.$refs[myRefToQUploader].reset()

                                Thank you, but this doesnt make sense in composition api.

                                metalsadman 1 Reply Last reply Reply Quote 0
                                • P
                                  pazarbasifatih last edited by pazarbasifatih

                                  Anyway, I tried the putting scope.reset() in the queuedFiles list template, (as it seems like only place to access the prescribed reset(). function is through scope in the documentation)
                                  It resets the whole thing… So I needed use the event @finish event but it didn’t fit any place I’ve inserted so far. I probably don’t understand this component at all. Why is it so hard? I am doing guess work here.
                                  This is the Codepen I’ve tried doing it.

                                  1 Reply Last reply Reply Quote 0
                                  • metalsadman
                                    metalsadman @pazarbasifatih last edited by metalsadman

                                    @pazarbasifatih said in Q-Uploader with Composition API:

                                    @dobbel said in Q-Uploader with Composition API:

                                    this.$refs[myRefToQUploader].reset()

                                    Thank you, but this doesnt make sense in composition api.

                                    convert it to composition api, assuming that you know how to use refs inside setup method since you are speaking about compositiion api. ie. someRef.value.reset(). all methods in the api are accessible thru vue refs.

                                    1 Reply Last reply Reply Quote 0
                                    • P
                                      pazarbasifatih last edited by

                                      @metalsadman Actually, I don’t assume myself knowing anything. I look at the documentation and try to find my way. My eyes simply couldn’t pick up anything about the fact that refs could be referred by xyz.value, on neither vue3 documentation nor the quasar’s. I’m a novice with some JS knowledge. Bare with me, please.

                                      And thank you. Of course I didn’t know that. Now I’ll try that 😃

                                      N 1 Reply Last reply Reply Quote 1
                                      • N
                                        nededa @pazarbasifatih last edited by

                                        @pazarbasifatih P.S. in composition api “this” is similar to context which can be accessed as second parameter in setup function

                                        setup(props, context)
                                        

                                        Or can be destructed if only refs is needed

                                        setup(props, { refs })
                                        
                                        1 Reply Last reply Reply Quote 1
                                        • First post
                                          Last post