Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Building Real-World Web Applications with Vue.js 3

You're reading from   Building Real-World Web Applications with Vue.js 3 Build a portfolio of Vue.js and TypeScript web applications to advance your career in web development

Arrow left icon
Product type Paperback
Published in Jan 2024
Publisher Packt
ISBN-13 9781837630394
Length 318 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Joran Quinten Joran Quinten
Author Profile Icon Joran Quinten
Joran Quinten
Arrow right icon
View More author details
Toc

Table of Contents (17) Chapters Close

Preface 1. Part 1: Getting Started with Vue.js Development
2. Chapter 1: Introduction to Vue.js FREE CHAPTER 3. Chapter 2: Creating a Todo List App 4. Chapter 3: Building a Local Weather App 5. Chapter 4: Creating the Marvel Explorer App 6. Part 2: Intermediate Projects
7. Chapter 5: Building a Recipe App with Vuetify 8. Chapter 6: Creating a Fitness Tracker with Data Visualization 9. Chapter 7: Building a Multiplatform Expense Tracker Using Quasar 10. Part 3: Advanced Applications
11. Chapter 8: Building an Interactive Quiz App 12. Chapter 9: Experimental Object Recognition with TensorFlow 13. Part 4: Wrapping Up
14. Chapter 10: Creating a Portfolio with Nuxt.js and Storyblok 15. Index 16. Other Books You May Enjoy

Scavenge Hunter

In this section, we’ll build a small app that can run on a web browser, preferably on a mobile phone. With Scavenge Hunter, the goal is to collect certain items from a list. We can use parts of the classes list to control the items our user needs to collect and in that case, we’re sure to be able to detect those objects!

Once an object has been detected, we’re going to add a score based on the find and certainty of the model. Since we can’t guarantee that objects are being recognized properly, we should also be able to skip an assignment. Instead of uploading an image, we’re going to use the camera stream!

Setting up the project

We can continue using the prototype we built or create a new project if we’d like. In the case of the latter, the dependencies and store are required, so we’d need to repeat the relevant steps provided in the Setting up the project and Performing and displaying a status check sections.

Let’s see how we can turn the foundation of our prototype into a little game, shall we?

Generic changes

We’re going to start with a configuration file. We need to create this file in the root of the project as config.ts:

export default Object.freeze({    MOTIVATIONAL_QUOTES: [
        "Believe in yourself and keep coding!",
        "Every Vue project you complete gets you closer to victory!",
        "You're on the right track, keep it up!",
        "Stay focused and never give up!"
    ],
    DETECTION_ACCURACY_THRESHOLD: 0.70,
    SCORE_ACCURACY_MULTIPLIER: 1.10, // input scores are between DETECTION_ACCURACY_THRESHOLD and 1
    MAX_ROUNDS: 10,
    SCORE_FOUND: 100,
    SCORE_SKIP: -150,
})

It can be very helpful to have this sort of configuration files in a central place so that we don’t have to spend time hunting settings down in individual files. Feel free to modify the game configuration values in the config.ts file!

Let’s also open the ./index.html template so that we can update the title tag to the new project’s name – that is, Scavenge Hunter.

We’ll also create two new view files in the ./views folder. It’s okay to just paste some placeholder content here, like so:

<template>  <div>NAME OF THE VIEW</div>
</template>

We need a view for the finding state, called Find.vue, and one for the end of a game, called End.vue. We’ll add the contents later, in the Building the finish screen and Skipping to the end sections. With the views in place, we can update the ./router/index.ts file with the following contents: https://github.com/PacktPublishing/Building-Real-world-Web-Applications-with-Vue.js-3/blob/main/09.tensorflow/.notes/9.5-index.ts.

We’re also going to simplify the interface a bit more. In the ./layouts/default folder, delete the AppBar.vue and View.vue files. In the Default.vue file, replace its contents with the following:

<template>  <v-app>
    <v-main>
      <router-view />
    </v-main>
  </v-app>
</template>

Now, we should be able to run the app, but there’s not much new to do at the moment. Let’s add some core features via Pinia stores.

Additional stores

I usually start by designing and setting up the stores since they usually act as a central source of information and methods. First, we’re going to replace the contents of the ./store/app.ts file with contents that are very similar to those from Chapter 6: https://github.com/PacktPublishing/Building-Real-world-Web-Applications-with-Vue.js-3/blob/main/09.tensorflow/.notes/9.6-app.ts.

It’s a trimmed-down version of the app store we used to build our fitness tracker, but we’ve removed all the unnecessary features.

Since we’re dealing with a predefined list of classes, we’re going to add those to the object.ts store as an additional value:

// ...abbreviatedexport const useObjectStore = defineStore('object', () => {
    // ...abbreviated
    const loadModel = async () => {
        // ...abbreviated
    }
    loadModel();
    // Full list of available classes listed as displayName on the following link:
    // https://raw.githubusercontent.com/tensorflow/tfjs-models/master/coco-ssd/src/classes.ts
    const objects: string[] = ["person", "backpack", "umbrella", "handbag", "tie", "suitcase", "sports ball", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", "orange", "broccoli", "carrot", "chair", "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "remote", "cell phone", "microwave", "oven", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"];
    return { loadModel, isModelLoading, isModelLoaded, detected, detect, objects }
})

I’ve not added all of the categories and instead selected the classes that we could find in someone’s home. You can change this to what you think is reasonable to have on hand (especially for testing purposes).

Let’s also introduce some game mechanics by adding a ./store/game.ts store file: https://github.com/PacktPublishing/Building-Real-world-Web-Applications-with-Vue.js-3/blob/main/09.tensorflow/.notes/9.7-game.ts.

This store contains references to the rounds that are being played and which are being skipped (lines 19–23), keeps track of the score (line 23), and helps in selecting a category from the list of objects we’ve defined in the object store. In particular, getNewCategory (lines 28–45) is interesting since it pulls a randomized category from the objects collection while making sure it’s always a unique new category.

As a final step in this section, we’ll replace the contents of the ./App.vue file: https://github.com/PacktPublishing/Building-Real-world-Web-Applications-with-Vue.js-3/blob/main/09.tensorflow/.notes/9.8-App.vue.

This connects the app store’s capabilities to the interface. Now, we can continue building up our little game!

Starting a new game

We’ll start by creating a button that triggers the conditions for a new game. In the components folder, we’ll create a StartGame.vue component, which is nothing more than a button with some actions on it:

<template>  <v-btn
    :disabled="!canStart"
    @click="newGame"
    prepend-icon="mdi-trophy"
    append-icon="mdi-trophy"
    size="x-large"
    color="primary"
    ><slot>Start game!</slot></v-btn
  >
</template>
<script lang="ts" setup>
import { useAppStore } from "@/store/app";
import { useGameStore } from "@/store/game";
import { storeToRefs } from "pinia";
const gameStore = useGameStore();
const appStore = useAppStore();
const { canStart } = storeToRefs(gameStore);
const { reset } = gameStore;
const newGame = () => {
  reset();
  appStore.navigateToPage("/find");
};
</script>

As you can see, we’re relying on the store to tell the button whether the button should be disabled. We trigger a new game by calling the reset() function of gameStore and calling a navigateToPage function on appStore. Now, we should be able to place this button component on the Home.vue view. Let’s update that view completely with the following contents:

<template>  <v-card class="pa-4">
    <v-card-title>
      <h1 class="text-h3 text-md-h2 text-wrap">z Scavenge Hunter</h1>
    </v-card-title>
    <v-card-text>
      <p>Welcome to "Scavenge Hunter"! The game where you find things!</p>
    </v-card-text>
    <StatusCheck />
    <v-card-actions class="justify-center">
      <StartGame />
    </v-card-actions>
  </v-card>
</template>
<script lang="ts" setup>
import StartGame from "@/components/StartGame.vue";
import StatusCheck from "@/components/StatusCheck.vue";
</script>

If you’re running the app now, you’ll notice that it’s impossible to start the game. Since we want to use the user’s camera feed, we need to request access. We’re going to expand the StatusCheck.vue file to also make sure we have access to a camera. We can use a composable from the VueUse library for this. So, from the terminal, let’s install the VueUse package with the following command:

npm i @vueuse/core

With this dependency, we can update the StatusCheck.vue file. The changes to that component are quite extensive, so use the source from https://github.com/PacktPublishing/Building-Real-world-Web-Applications-with-Vue.js-3/blob/main/09.tensorflow/.notes/9.9-StatusCheck.vue.

Apart from some additional formatting on our model loading status and some template changes that show the actual status, most changes take place in the script. The usePermission composable returns a reactive property that lets us know if the user has granted access to use the camera. If both the model is loaded and the user has granted camera access, the game can start (lines 61–65). As you can see, we’re using the watch function on multiple values by providing them as arrays (line 61) to the watch function.

In the onMounted hook (lines 67–81), we manually attempt to request a video stream. Once the stream starts, we immediately close it down since we don’t need the stream, just the permission. The permission is persistent throughout our visit.

Building the finish screen

Before we dive into the image streams and object-hunting aspects, we’ll build the final screen. We’ll create a component in the ./components folder to display the result of a game called ScoreCard.vue: https://github.com/PacktPublishing/Building-Real-world-Web-Applications-with-Vue.js-3/blob/main/09.tensorflow/.notes/9.10-ScoreCard.vue.

In the component, we’re just displaying some of the metrics that were being collected on playthrough. They are all properties that are part of gameStore, so we have easy access to them.

In End.vue, we’ll import the ScoreCard.vue file and make some additions to the template:

<template>  <v-card class="pa-4">
    <v-card-title>
      <h1 class="text-h3 text-md-h2 text-wrap">It's over!</h1>
    </v-card-title>
    <v-card-text>
      <p>Let's see how you did!</p>
    </v-card-text>
    <ScoreCard />
    <v-card-actions class="justify-center">
      <StartGame>Play Again?</StartGame>
    </v-card-actions>
  </v-card>
</template>
<script lang="ts" setup>
import ScoreCard from "@/components/ScoreCard.vue";
import StartGame from "@/components/StartGame.vue";
</script>

There’s not much going on here apart from the <StartGame /> component, which we have reused to simply trigger a new game. That’s how you use slots! Now, we can work on the middle section!

Skipping to the end

First, let’s make sure we can complete a (very limited) flow by skipping all assignments. We’re going to implement the basic game flow in the ./views/Find.vue file. Let’s take a look at the script tag since we have a lot going on in this file: https://github.com/PacktPublishing/Building-Real-world-Web-Applications-with-Vue.js-3/blob/main/09.tensorflow/.notes/9.11-Find(script).vue.

At the top of the script tag, we’re loading the properties and methods from the stores (lines 3–15). We use appStore to navigate to different pages and gameStore because that contains information about the progress of the current game.

We have some computed values that help in presenting and formatting data nicely. currentRound (lines 17–19) displays the progress of the game. We use isPlaying (lines 21–23) to determine the boundaries of the rounds versus the maximum set of rounds. Lastly, we have some fun randomized motivational quotes (lines 25–29) that we’ve loaded from our configuration file.

There are two methods in this component. One is to skip (lines 31–39) a round. The skip function tracks the number of rounds skipped (line 32) and modifies the player’s score (lines 33–37). We must make sure the score doesn’t fall below 0. After skipping, we call the newRound method.

The newRound function (lines 41–47) tracks what should happen: either the number of rounds has reached the maximum and we should navigate to the End state, or we should load a new category using the getCategory function from the store. To ensure we get started when we enter this Find state, we will call that newRound function in the onMounted hook.

Next, let’s look at the template of the Find.vue file, where we connect the computed values and methods to a basic interface: https://github.com/PacktPublishing/Building-Real-world-Web-Applications-with-Vue.js-3/blob/main/09.tensorflow/.notes/9.12-Find(template).vue.

Again, there’s not much special going on here. We’re using the <SkipRound /> component with the @skipped event to make sure we can move forward in rounds, regardless of whether we’ve been able to use object recognition.

Running the app at this stage should give us a result similar to the following:

Figure 9.3 – The basic game flow in place

Figure 9.3 – The basic game flow in place

You should be able to complete the entire flow now by skipping all of the rounds. A game like this makes more sense on a mobile device than a laptop or personal computer, so this would be a good time to make sure we can test the app properly.

Testing on a mobile device

If you’re building an app for a specific use case, it makes a lot of sense to test those cases as early as possible! While we can open the app in mobile views in our browser, it would make a lot of sense to run it on a mobile device as well. The first thing we can do is automatically expose the development server host by updating the dev script in the package.json file:

{    "scripts": {
    "dev": "vite --host",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview",
    "lint": "eslint . --fix --ignore-path .gitignore"
  },
  "dependencies": {
    // ...abbreviated
  },
  "devDependencies": {
    // ...abbreviated
  }
}

This change automatically serves the content through your local network, so as long as your mobile device and development server are on the same network, you can access the app via the network address:

Figure 9.4 – Exposing the development server to the network

Figure 9.4 – Exposing the development server to the network

We’re not there yet, though. The media feed is only accessible over a secure connection. Going with Vite’s recommendation in the official documentation (https://vitejs.dev/config/server-options.html#server-https), we’ll install a plugin for this using the terminal:

npm install --save-dev @vitejs/plugin-basic-ssl

Once the installation is completed, we’ll update the vite.confis.ts file so that it can use the plugin:

// Pluginsimport vue from '@vitejs/plugin-vue'
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
import basicSsl from '@vitejs/plugin-basic-ssl'
// Utilities
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    basicSsl(),
    vue({
      template: { transformAssetUrls }
    }),
    // ...abbreviated
  ],
  // ...abbreviated
})

After saving, we can restart the development server. The contents are now served over an HTTPS protocol. It is not using a signed certificate, so you will probably receive a warning from the browser upon first entry. You can now validate each step using your mobile device as well!

With that, we’ve built a basic flow from start to finish and we can test it on a mobile device. The game itself is not very interesting yet though, right? It’s time to add some object recognition to the game!

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image