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 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