DEV Community

loading...
Cover image for CurateBot Devlog 7: Curating Tweets

CurateBot Devlog 7: Curating Tweets

meseta profile image Yuan Gao ・3 min read

With tweets loaded, I now need to display them one-by-one to the user, so the user can decide what to do with each of them. The code can be seen here, most of the changes are in the new Curate.vue file

The Curate view

The curate isn't that different from the load - it gains a couple of buttons, so I won't go into too much detail here (take a look at the code)

Pre-fetch tweets

The more interesting thing is how it loads the tweets. Since I expect a user to go through the tweets relatively quickly, I want to pre-load the next tweet so that the user doesn't have to wait for a load. So, as soon as the component is mounted, I have some code that runs on mount, which loads 2 tweets (.limit(2)):

  mounted() {
    firestore.collection('users').doc(this.uid).get()
    .then(doc => {
      this.count = doc?.get("newCount") || 0;
    })
    this.currentLoading = true;
    this.nextLoading = true;
    return firestore.collection('users').doc(this.uid).collection('tweets')
    .where('queued', '==', false).orderBy('added').limit(2).get()
    .then(query => {
      if (query.size) {
        this.currentId = query.docs[0].id;
        this.currentTweet = query.docs[0].get("tweet");
        this.currentDocRef = query.docs[0].ref;
        if (query.size > 1) {
          this.nextId = query.docs[1].id;
          this.nextTweet = query.docs[1].get("tweet");
          this.nextDocRef = query.docs[1].ref;
        }
      }
      this.currentLoading = false;
      this.nextLoading = false;
    })
    .catch(err => {
      console.error(err);
      this.showError("Something went wrong, could not load tweets");
    })
  }
Enter fullscreen mode Exit fullscreen mode

This then sets the currentTweet and nextTweet variables accordingly. Then, when the currentTweet is dealt with, it's cleared, the nextTweet takes it's place, and then a new request issued to fetch the next tweet after that:


  advance(): any { // eslint-disable-line @typescript-eslint/no-explicit-any
    if (this.nextLoading) {
      // if we're already loading, that means the next one will appear shortly,
      // so just clear display and wait
      this.currentId = "";
      this.currentTweet = "";
      this.currentDocRef = null;
      return
    }

    this.currentId = this.nextId;
    this.currentTweet = this.nextTweet;
    this.currentDocRef = this.nextDocRef;
    this.nextId = ""
    this.nextTweet = ""
    this.nextDocRef = null
    if (this.currentDocRef) {
      this.nextLoading = true;
      return firestore.collection('users').doc(this.uid).collection('tweets')
      .where('queued', '==', false).orderBy('added').limit(2).get()
      .then(query => {
        this.nextLoading = false;
        if (query.size > 1) {
          this.nextId = query.docs[1].id;
          this.nextTweet = query.docs[1].get("tweet");
          this.nextDocRef = query.docs[1].ref;
          if(!this.currentDocRef) { // in case current tweet was already dealt with while a document was being fetched
            return this.advance()
          }
        }
      })
      .catch(err => {
        console.error(err)
        this.showError("Something went wrong, could not load next tweet");
      })
    }
  }
Enter fullscreen mode Exit fullscreen mode

I actually improve this mechanism by using .beginAfter() method rather than .limit(2), but this appears in a later commit.

Editing

Allowing editing the tweet is relatively straightfoward - I already opted to display the tweet using a textarea, so it was a case of enabling the textarea, and then providing a function to write the results into the Firestore database once completed editing.

   saveAction() {
    if (this.currentDocRef && this.editing && this.currentTweet) {
      this.currentLoading = true;
      this.currentDocRef.update({
        tweet: this.currentTweet
      })
      .then(() => {
        this.currentLoading = false;
        this.editing = false;
      })
      .catch(err => {
        console.error(err)
        this.showError("Something went wrong, could not edit tweet");
      })
    }
  }
Enter fullscreen mode Exit fullscreen mode

Enqueue and Delete

Enqueueing a tweet is simply a case of updating it's property to queued: true, and then running the above advance function. Conversely, Deleting a tweet actually deletes it from the database using a delete() method.

The result:
curate

I can actually make this go faster, as it synchronously waits for the delete/enqueue action to complete before switching. However, I think it is going fast enough, and lets users get feedback of any failures when they happen.

Swipe Controls

Since I want to be able to use this on my phone, I enabled swiping left/right to delete/enqueue. Vuetify has these out of the box. The visual feedback of swipe isn't very good (I should add transitions later), but it's very easy to add, the simply gains a v-touch directive, to trigger the already existing deleteAction() and enqueueAction(), the true argument causes those functions to show an alert to give users some better feedback. I am considering replacing this with a transition animation in future.

      <v-textarea
        v-model="displayTweet"
        outlined
        label="Tweet"
        counter
        :readonly="!editing"
        :disabled="currentLoading"
        :loading="loading"
        v-touch="{
          left: () => deleteAction(true),
          right: () => enqueueAction(true),
        }"
      >
Enter fullscreen mode Exit fullscreen mode

Discussion (0)

pic
Editor guide