Commit a6945ca5 authored by erlendoeien's avatar erlendoeien
Browse files

Add vuex and refactor

parent 23d85d01
......@@ -19,7 +19,8 @@
"postcss": "^8.4.5",
"tailwindcss": "^3.0.12",
"vue": "^3.2.25",
"vue-router": "4"
"vue-router": "4",
"vuex": "^4.0.2"
},
"devDependencies": {
"@types/lodash": "^4.14.178",
......
import { getUsername } from "../tools/databaseTools";
import { getAuth } from "@firebase/auth";
import { ref } from "@vue/reactivity";
import { IListeners, IMovie, IMovieRaw } from "../types";
import { getDatabase, ref as fbRef } from "@firebase/database";
import { onMounted, onBeforeUnmount, Ref, watch } from "@vue/runtime-dom";
import { onValue } from "firebase/database";
import unsubscibeListeners from "../tools/unsubscribeListeners";
import { onMounted, computed } from "@vue/runtime-dom";
import { FETCH_MOVIES, useStore } from "../store";
export default function useMovies() {
//TODO: Include Loading param
const movies = ref<IMovie[]>([]);
const store = useStore();
const movies = computed(() => store.state.movies);
const loading = computed(() => store.state.loading || !store.state.hasLoaded);
const db = getDatabase();
const moviesRef = fbRef(db, "movies-list/");
const listeners: IListeners = { movies: () => {} };
onMounted(() => store.dispatch(FETCH_MOVIES));
return {movies, loading}
const fetchMovies = () => {
listeners.movies = onValue(
moviesRef,
(snapshot) => {
const _movies: IMovie[] = [];
snapshot.forEach((movieSnap) => {
const movie = movieSnap.val() as IMovieRaw;
_movies.push({
...movie,
title: movie.title.toString(),
year: movie.year.toString(),
});
});
movies.value = _movies;
},
{ onlyOnce: true }
);
};
onMounted(fetchMovies);
onBeforeUnmount(() => unsubscibeListeners(listeners));
return { movies };
}
import { getDatabase, onValue, push, ref as fbRef, set, update } from "@firebase/database";
import { ref, watch, onMounted, onBeforeUnmount, Ref, computed } from "@vue/runtime-dom";
import { getDatabase, onValue, push, ref as fbRef, set } from "@firebase/database";
import { ref, onMounted, onBeforeUnmount, computed } from "@vue/runtime-dom";
import { getUsername } from "../tools/databaseTools";
import { IListeners, IMovie, IWishlist } from "../types";
import { IListeners } from "../types";
import { getAuth } from "@firebase/auth";
import unsubscibeListeners from "../tools/unsubscribeListeners";
export type TWish = [string, number];
export default function useWishlist() {
//TODO: Include Loading param
const wishlist = ref<TWish[]>([]);
const username = getUsername(getAuth().currentUser);
const username = getUsername(getAuth().currentUser);
const db = getDatabase();
const wishlistRef = fbRef(db, `users/${username}/wishlist`);
const listeners: IListeners = { wishlist: () => {} };
......@@ -22,24 +21,24 @@ export default function useWishlist() {
wishlist.value = Object.entries(snapshot.val() || {});
});
};
onMounted(fetchWishlist);
onBeforeUnmount(() => unsubscibeListeners(listeners));
const isEmpty = computed(() => wishlist.value.length === 0);
const addWish = (id: number) => {
push(wishlistRef, id);
};
//TODO: Lookinto if all mounting/unmounting can happen in the views for better loading?
const deleteWish = (deleteId: number) => {
if (wishlist.value.length === 0) {
return console.log("No wishlist, shoult not be able to delete anything");
}
const newList = Object.fromEntries(wishlist.value.filter(([, id]) => deleteId !== id));
set(wishlistRef, newList);
};
onMounted(fetchWishlist);
onBeforeUnmount(() => unsubscibeListeners(listeners));
return { wishlist, isEmpty, addWish, deleteWish };
}
import { ref, watch, onBeforeUnmount, Ref } from "@vue/runtime-dom";
import { ref, watch, onBeforeUnmount, Ref, ComputedRef } from "@vue/runtime-dom";
import { IListeners, IMovie } from "../types";
import unsubscibeListeners from "../tools/unsubscribeListeners";
import type { TWish } from "./useWishlist";
export default function useFilteredMovies(
movies: Ref<IMovie[]>,
movies: ComputedRef<IMovie[]>,
wishlist: Ref<TWish[]>,
isWishlist: boolean
) {
//TODO: Implement loading state
const wishlistFilteredMovies = ref<IMovie[]>([]);
const listeners: IListeners = { wishlist: () => {} };
......@@ -25,7 +24,10 @@ export default function useFilteredMovies(
wishlistFilteredMovies.value = movies.value.filter(isWishlist ? incWishlist : excWishlist);
};
watch(wishlist, filterMovies);
// Shouldn't strictly be necessary for movies, but seems like there is something wrong with
// my understanding of passing the ref: https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
watch([wishlist, movies], filterMovies);
onBeforeUnmount(() => unsubscibeListeners(listeners));
return { wishlistFilteredMovies };
......
......@@ -4,6 +4,7 @@ import router from "./router";
import { FirebaseOptions, initializeApp } from "firebase/app";
import "./index.css";
import FontAwesomeIcon from "./fontawesome-icons";
import { key, store } from "./store";
const firebaseConfig: FirebaseOptions = {
apiKey: import.meta.env.VITE_APIKEY as string,
......@@ -20,5 +21,8 @@ const firebaseConfig: FirebaseOptions = {
const firebaseApp = initializeApp(firebaseConfig);
// const analytics = getAnalytics(firebaseApp);
const app = createApp(App).component("font-awesome-icon", FontAwesomeIcon).use(router);
const app = createApp(App)
.component("font-awesome-icon", FontAwesomeIcon)
.use(router)
.use(store, key);
app.mount("#app");
import { get, getDatabase, ref } from "firebase/database";
import { InjectionKey } from "vue";
import { createStore, Store, useStore as baseUseStore } from "vuex";
import { IMovie, IMovieRaw } from "./types";
interface IGlobalState {
movies: IMovie[];
loading: boolean;
error: boolean;
hasLoaded: boolean;
}
export const LOAD_MOVIES = "LOAD_MOVIES";
export const SET_LOADING = "SET_LOADING";
export const SET_ERROR = "SET_ERROR";
export const FETCH_MOVIES = "FETCH_MOVIES";
export const store = createStore<IGlobalState>({
state: {
movies: [],
loading: true,
error: false,
hasLoaded: false,
},
mutations: {
[LOAD_MOVIES](state, movies: IMovie[]) {
state.movies = movies;
state.hasLoaded = true;
},
[SET_LOADING](state, isLoading: boolean) {
state.loading = isLoading;
},
[SET_ERROR](state, error: boolean) {
state.loading = error;
},
},
actions: {
async [FETCH_MOVIES]({ commit, state }) {
if (state.hasLoaded) return;
commit(SET_LOADING, true);
commit(SET_ERROR, false);
const db = getDatabase();
const moviesRef = ref(db, "movies-list/");
try {
const moviesSnap = await get(moviesRef);
const _movies: IMovie[] = [];
moviesSnap.forEach((movieSnap) => {
const movie = movieSnap.val() as IMovieRaw;
_movies.push({
...movie,
title: movie.title.toString(),
year: movie.year.toString(),
});
});
commit(LOAD_MOVIES, _movies);
commit(SET_LOADING, false);
} catch (err) {
console.log("ERROR");
commit(SET_LOADING, false);
commit(SET_ERROR, true);
}
},
},
});
// define injection key
export const key: InjectionKey<Store<IGlobalState>> = Symbol();
// typed useStore
export function useStore() {
return baseUseStore(key);
}
......@@ -2,12 +2,14 @@
<h1>Movies</h1>
<input type="search" v-model="searchQuery" />
<div>
<div v-if="movies.length === 0">
<div v-if="loading">
<h3>Loading...</h3>
<font-awesome-icon icon="spinner" spin />
</div>
<Table :data="filteredMovies" :adminFunc="addWish" :isWishlist="false" v-else />
<Button class="mx-auto mb-8" @click="loadMore">Load more</Button>
<template v-else>
<Table :data="filteredMovies" :adminFunc="addWish" :isWishlist="false" />
<Button class="mx-auto mb-8" @click="loadMore">Load more</Button>
</template>
</div>
</template>
......@@ -16,7 +18,7 @@ import { useWishlistMovies, useMovies, useWishlist, useMatchedMovies } from "../
import Table from "../components/Table.vue";
import Button from "../components/Button.vue";
const { movies } = useMovies();
const { movies, loading } = useMovies();
const { wishlist, addWish } = useWishlist();
const { wishlistFilteredMovies } = useWishlistMovies(movies, wishlist, false);
const {
......
<template>
<h1>Wishlist</h1>
<div v-if="isEmpty">
<div v-if="loading" >
<h3>Loading...</h3>
<font-awesome-icon icon="spinner" spin />
</div>
<div v-else-if="isEmpty">
<h3>No movies in wishlist</h3>
<Button @click="redirect">Add new movies</Button>
</div>
......@@ -14,7 +18,7 @@ import { useWishlistMovies, useMovies, useWishlist } from "../hooks";
import Button from "../components/Button.vue";
import Table from "../components/Table.vue";
const { movies } = useMovies();
const { movies, loading } = useMovies();
const { wishlist, deleteWish, isEmpty } = useWishlist();
const { wishlistFilteredMovies: filteredMovies } = useWishlistMovies(movies, wishlist, true);
const router = useRouter();
......
......@@ -673,7 +673,7 @@
"@vue/compiler-dom" "3.2.26"
"@vue/shared" "3.2.26"
"@vue/devtools-api@^6.0.0-beta.18":
"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.0.0-beta.18":
version "6.0.0-beta.21.1"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.21.1.tgz#f1410f53c42aa67fa3b01ca7bdba891f69d7bc97"
integrity sha512-FqC4s3pm35qGVeXRGOjTsRzlkJjrBLriDS9YXbflHLsfA9FrcKzIyWnLXoNm+/7930E8rRakXuAc2QkC50swAw==
......@@ -2282,6 +2282,13 @@ vue@^3.2.25:
"@vue/server-renderer" "3.2.26"
"@vue/shared" "3.2.26"
vuex@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/vuex/-/vuex-4.0.2.tgz#f896dbd5bf2a0e963f00c67e9b610de749ccacc9"
integrity sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==
dependencies:
"@vue/devtools-api" "^6.0.0-beta.11"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment