refactor: restructure all components into separate folders/views

This commit is contained in:
2025-07-16 11:11:40 -05:00
parent ccd975d181
commit d5211ef24b
20 changed files with 123 additions and 123 deletions
Vendored
+3 -3
View File
@@ -1,7 +1,7 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module '*.vue' { declare module '*.vue' {
import type { DefineComponent } from 'vue' import type { DefineComponent } from 'vue';
const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown> const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown>;
export default component export default component;
} }
+13 -13
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import SeasonList from '@/components/SeasonList.vue'; import SeasonList from '@/components/layout/SeasonList.vue';
import { ref } from 'vue'; import { ref } from 'vue';
import logoSrc from '@/assets/logo.svg'; import logoSrc from '@/assets/logo.svg';
@@ -21,14 +21,14 @@ const headings = [
<div class="min-h-screen bg-gray-50"> <div class="min-h-screen bg-gray-50">
<!-- Header --> <!-- Header -->
<header <header
class="bg-white px-4 py-3 border-b border-gray-200 fixed top-0 left-0 right-0 z-40 flex items-center h-24" class="fixed top-0 right-0 left-0 z-40 flex h-24 items-center border-b border-gray-200 bg-white px-4 py-3"
> >
<div class="flex items-center w-full justify-between"> <div class="flex w-full items-center justify-between">
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<!-- Mobile menu button --> <!-- Mobile menu button -->
<button <button
@click="toggleSidebar" @click="toggleSidebar"
class="lg:hidden p-2 rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500" class="rounded-md p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900 focus:ring-2 focus:ring-blue-500 focus:outline-none focus:ring-inset lg:hidden"
> >
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path <path
@@ -42,33 +42,33 @@ const headings = [
<!-- Logo/Brand --> <!-- Logo/Brand -->
<div class="flex"> <div class="flex">
<img :src="logoSrc" alt="The Office Logo" class="h-full max-w-[225px] py-4 mr-6" /> <img :src="logoSrc" alt="The Office Logo" class="mr-6 h-full max-w-[225px] py-4" />
</div> </div>
</div> </div>
<!-- Header Navigation --> <!-- Header Navigation -->
<nav <nav
class="hidden text-gray-800 md:flex items-center space-x-2 font-display text-2xl tracking-widest lowercase" class="font-display hidden items-center space-x-2 text-2xl tracking-widest text-gray-800 lowercase md:flex"
> >
<RouterLink <RouterLink
v-for="heading in headings" v-for="heading in headings"
:key="heading.name" :key="heading.name"
:to="heading.href" :to="heading.href"
class="hover:text-blue-600 px-3 py-2 transition-[color]" class="px-3 py-2 transition-[color] hover:text-blue-600"
> >
{{ heading.name }} {{ heading.name }}
</RouterLink> </RouterLink>
</nav> </nav>
<!-- Search bar --> <!-- Search bar -->
<div class="hidden md:flex items-center"> <div class="hidden items-center md:flex">
<div class="relative"> <div class="relative">
<input <input
type="text" type="text"
placeholder="Search..." placeholder="Search..."
class="w-64 pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" class="w-64 rounded-lg border border-gray-300 py-2 pr-4 pl-10 outline-none focus:border-transparent focus:ring-2 focus:ring-blue-500"
/> />
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<svg <svg
class="h-5 w-5 text-gray-400" class="h-5 w-5 text-gray-400"
fill="none" fill="none"
@@ -88,7 +88,7 @@ const headings = [
</div> </div>
</header> </header>
<div class="flex mt-24"> <div class="mt-24 flex">
<!-- Sidebar --> <!-- Sidebar -->
<div class="pl-8"> <div class="pl-8">
<SeasonList /> <SeasonList />
@@ -98,13 +98,13 @@ const headings = [
<div <div
v-if="sidebarOpen" v-if="sidebarOpen"
@click="toggleSidebar" @click="toggleSidebar"
class="fixed inset-0 z-20 bg-black bg-opacity-50 lg:hidden" class="bg-opacity-50 fixed inset-0 z-20 bg-black lg:hidden"
></div> ></div>
<!-- Main Content --> <!-- Main Content -->
<main class="col-span-8 lg:ml-0"> <main class="col-span-8 lg:ml-0">
<div class="p-6"> <div class="p-6">
<h2 class="text-2xl text-gray-900 mb-6">Welcome to The Office</h2> <h2 class="mb-6 text-2xl text-gray-900">Welcome to The Office</h2>
<p class="text-gray-600"> <p class="text-gray-600">
This is your main content area. You can add your router-view or other components here. This is your main content area. You can add your router-view or other components here.
</p> </p>
-25
View File
@@ -1,25 +0,0 @@
<template>
<div>
<ais-hits>
<template v-slot="{ items }">
<div>
<SearchResult v-for="item in items" :key="item.objectID" :item="item" />
</div>
</template>
</ais-hits>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import SearchResult from "@/components/SearchResult.vue";
export default defineComponent({
name: "SearchResults",
components: {
SearchResult,
},
});
</script>
@@ -1,5 +1,5 @@
<template> <template>
<table class="quote-list px-3 w-100"> <table class="quote-list w-100 px-3">
<tr <tr
v-for="(quote, index) in quotes" v-for="(quote, index) in quotes"
:id="`${sceneIndex}-${index}`" :id="`${sceneIndex}-${index}`"
@@ -53,9 +53,9 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { defineComponent } from 'vue';
import DynamicSpeaker from '@/components/DynamicSpeaker.vue' import DynamicSpeaker from '@/components/features/DynamicSpeaker.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -76,16 +76,16 @@ export default defineComponent({
methods: { methods: {
transform(quoteText) { transform(quoteText) {
if (quoteText.includes('[')) { if (quoteText.includes('[')) {
return quoteText.replace(/\[([^\]]+)]/g, ' <i>[$1]</i> ') return quoteText.replace(/\[([^\]]+)]/g, ' <i>[$1]</i> ');
} }
return quoteText return quoteText;
}, },
quote_link(quoteIndex) { quote_link(quoteIndex) {
return `/${this.$route.params.season}/${this.$route.params.episode}#${this.sceneIndex}-${quoteIndex}` return `/${this.$route.params.season}/${this.$route.params.episode}#${this.sceneIndex}-${quoteIndex}`;
}, },
copy(quoteIndex) { copy(quoteIndex) {
this.$copyText(import.meta.env.VUE_APP_BASE_URL + this.quote_link(quoteIndex)) this.$copyText(import.meta.env.VUE_APP_BASE_URL + this.quote_link(quoteIndex));
}, },
}, },
}) });
</script> </script>
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import SeasonListItem from '@/components/SeasonListItem.vue'; import SeasonListItem from '@/components/layout/SeasonListItem.vue';
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
+13 -13
View File
@@ -1,12 +1,12 @@
import Home from '@/components/Home.vue' import Home from '@/views/Home.vue';
import Episode from '@/components/Episode.vue' import Episode from '@/views/Episode.vue';
import SearchResults from '@/components/SearchResults.vue' import SearchResults from '@/views/SearchResults.vue';
import Character from '@/components/Character.vue' import Character from '@/views/Character.vue';
import Season from '@/components/Season.vue' import Season from '@/views/Season.vue';
import Characters from '@/components/Characters.vue' import Characters from '@/views/Characters.vue';
import About from '@/components/About.vue' import About from '@/views/About.vue';
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
@@ -51,16 +51,16 @@ const router = createRouter({
scrollBehavior(to, from, savedPosition) { scrollBehavior(to, from, savedPosition) {
// https://router.vuejs.org/guide/advanced/scroll-behavior.html // https://router.vuejs.org/guide/advanced/scroll-behavior.html
if (to.hash) { if (to.hash) {
return { el: to.hash, behavior: 'smooth' } return { el: to.hash, behavior: 'smooth' };
} }
if (savedPosition) { if (savedPosition) {
return savedPosition return savedPosition;
} }
return { return {
x: 0, x: 0,
y: 0, y: 0,
} };
}, },
}) });
export default router export default router;
@@ -30,15 +30,15 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { defineComponent, nextTick } from 'vue' import { defineComponent, nextTick } from 'vue';
import Skeleton from './Skeleton.vue' import Skeleton from '@/components/common/Skeleton.vue';
import { BBreadcrumb } from 'bootstrap-vue-next' import { BBreadcrumb } from 'bootstrap-vue-next';
import useStore from '@/store' import useStore from '@/store';
interface BreadcrumbItem { interface BreadcrumbItem {
text: string text: string;
to?: { name: string } to?: { name: string };
active?: boolean active?: boolean;
} }
export default defineComponent({ export default defineComponent({
@@ -50,17 +50,17 @@ export default defineComponent({
}, },
setup() { setup() {
const store = useStore() const store = useStore();
return { return {
store, store,
} };
}, },
computed: { computed: {
character() { character() {
return this.store.characters[this.$route.params.character as string] return this.store.characters[this.$route.params.character as string];
}, },
ready(): boolean { ready(): boolean {
return this.character !== undefined return this.character !== undefined;
}, },
breadcrumbs(): BreadcrumbItem[] { breadcrumbs(): BreadcrumbItem[] {
@@ -77,31 +77,31 @@ export default defineComponent({
text: this.character?.name || (this.$route.params.character as string), text: this.character?.name || (this.$route.params.character as string),
active: true, active: true,
}, },
] ];
}, },
}, },
watch: { watch: {
'$route.params.character'() { '$route.params.character'() {
nextTick(() => { nextTick(() => {
this.fetchCharacter() this.fetchCharacter();
}) });
}, },
}, },
mounted() { mounted() {
this.fetchCharacter() this.fetchCharacter();
}, },
methods: { methods: {
async fetchCharacter(): Promise<void> { async fetchCharacter(): Promise<void> {
try { try {
await this.store.preloadCharacters() await this.store.preloadCharacters();
this.character = this.store.characters[this.$route.params.character as string] this.character = this.store.characters[this.$route.params.character as string];
} catch (error) { } catch (error) {
console.error('Error fetching character:', error) console.error('Error fetching character:', error);
} }
}, },
}, },
}) });
</script> </script>
@@ -87,11 +87,11 @@ h4 {
</style> </style>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { defineComponent } from 'vue';
import Skeleton from '@/components/Skeleton.vue' import Skeleton from '@/components/common/Skeleton.vue';
import ImageSkeleton from '@/components/ImageSkeleton.vue' import ImageSkeleton from '@/components/common/ImageSkeleton.vue';
import { BBreadcrumb, BImg } from 'bootstrap-vue-next' import { BBreadcrumb, BImg } from 'bootstrap-vue-next';
export default defineComponent({ export default defineComponent({
name: 'CharactersComponent', name: 'CharactersComponent',
@@ -105,24 +105,24 @@ export default defineComponent({
computed: { computed: {
ready() { ready() {
return this.$store.getters.checkPreloaded('characters') return this.$store.getters.checkPreloaded('characters');
}, },
sorted_character_ids() { sorted_character_ids() {
return this.$store.getters.getSortedCharacters() return this.$store.getters.getSortedCharacters();
}, },
characters() { characters() {
return this.$store.state.characters return this.$store.state.characters;
}, },
breadcrumbs() { breadcrumbs() {
return [ return [
{ text: 'Home', to: { name: 'Home' } }, { text: 'Home', to: { name: 'Home' } },
{ text: 'Characters', active: true }, { text: 'Characters', active: true },
] ];
}, },
}, },
async mounted() { async mounted() {
await this.$store.dispatch(types.PRELOAD_CHARACTERS) await this.$store.dispatch(types.PRELOAD_CHARACTERS);
// Re-compute computed properties since Vuex won't do it // Re-compute computed properties since Vuex won't do it
// this.$forceUpdate(); // this.$forceUpdate();
@@ -130,8 +130,8 @@ export default defineComponent({
methods: { methods: {
faceURL(character, thumbnail = false) { faceURL(character, thumbnail = false) {
return `/img/${character}/` + (thumbnail ? 'face_thumb' : 'face') + '.jpeg' return `/img/${character}/` + (thumbnail ? 'face_thumb' : 'face') + '.jpeg';
}, },
}, },
}) });
</script> </script>
@@ -28,7 +28,7 @@
<QuoteList :quotes="scene.quotes" :scene-index="sceneIndex" /> <QuoteList :quotes="scene.quotes" :scene-index="sceneIndex" />
<span <span
v-if="scene.deleted" v-if="scene.deleted"
class="mt-n2 mb-4 text-muted deleted-scene pl-2" class="mt-n2 text-muted deleted-scene mb-4 pl-2"
:footer="`Deleted Scene ${scene.deleted}`" :footer="`Deleted Scene ${scene.deleted}`"
> >
Deleted Scene {{ scene.deleted }} Deleted Scene {{ scene.deleted }}
@@ -52,12 +52,12 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import { defineComponent, nextTick } from 'vue' import { defineComponent, nextTick } from 'vue';
import QuoteList from '@/components/QuoteList.vue' import QuoteList from '@/components/features/QuoteList.vue';
import CharacterBadges from '@/components/CharacterBadges.vue' import CharacterBadges from '@/components/features/CharacterBadges.vue';
import Skeleton from '@/components/Skeleton.vue' import Skeleton from '@/components/common/Skeleton.vue';
import { BBreadcrumb } from 'bootstrap-vue-next' import { BBreadcrumb } from 'bootstrap-vue-next';
export default defineComponent({ export default defineComponent({
name: 'EpisodeComponent', name: 'EpisodeComponent',
@@ -71,14 +71,14 @@ export default defineComponent({
computed: { computed: {
episode() { episode() {
return this.$store.getters.getEpisode(this.params.season, this.params.episode) return this.$store.getters.getEpisode(this.params.season, this.params.episode);
}, },
// Shorthand - literally useless, why does everything to have such long prefixes in dot notation // Shorthand - literally useless, why does everything to have such long prefixes in dot notation
params() { params() {
return this.$route.params return this.$route.params;
}, },
ready() { ready() {
return this.$store.getters.isFetched(this.params.season, this.params.episode) return this.$store.getters.isFetched(this.params.season, this.params.episode);
}, },
breadcrumbs() { breadcrumbs() {
return [ return [
@@ -104,7 +104,7 @@ export default defineComponent({
}, },
active: true, active: true,
}, },
] ];
}, },
}, },
@@ -112,14 +112,14 @@ export default defineComponent({
// When route changes, fetch data for current Episode route // When route changes, fetch data for current Episode route
$route() { $route() {
nextTick(() => { nextTick(() => {
this.fetch() this.fetch();
}) });
}, },
}, },
created() { created() {
// When page loads directly on this Episode initially, fetch data // When page loads directly on this Episode initially, fetch data
this.fetch() this.fetch();
}, },
methods: { methods: {
@@ -129,17 +129,17 @@ export default defineComponent({
.dispatch(types.FETCH_EPISODE, { season: this.params.season, episode: this.params.episode }) .dispatch(types.FETCH_EPISODE, { season: this.params.season, episode: this.params.episode })
.then(() => { .then(() => {
// Force update, as for some reason it doesn't update naturally. I hate it too. // Force update, as for some reason it doesn't update naturally. I hate it too.
this.$forceUpdate() this.$forceUpdate();
// Scroll down to quote // Scroll down to quote
if (this.$route.hash) { if (this.$route.hash) {
nextTick(() => { nextTick(() => {
const section = document.getElementById(this.$route.hash.substring(1)) const section = document.getElementById(this.$route.hash.substring(1));
this.$scrollTo(section, 500, { easing: 'ease-in' }) this.$scrollTo(section, 500, { easing: 'ease-in' });
}) });
} }
}) });
}, },
}, },
}) });
</script> </script>
+11 -11
View File
@@ -27,11 +27,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { defineComponent } from 'vue';
import axios from 'axios' import axios from 'axios';
import Skeleton from './Skeleton.vue' import Skeleton from '@/components/common/Skeleton.vue';
import { BCardText, BCard } from 'bootstrap-vue-next' import { BCardText, BCard } from 'bootstrap-vue-next';
export default defineComponent({ export default defineComponent({
name: 'HomeComponent', name: 'HomeComponent',
@@ -45,12 +45,12 @@ export default defineComponent({
data() { data() {
return { return {
stats: null, stats: null,
} };
}, },
computed: { computed: {
ready() { ready() {
return true return true;
// return this.stats != null; // return this.stats != null;
}, },
}, },
@@ -61,16 +61,16 @@ export default defineComponent({
methods: { methods: {
getStats() { getStats() {
const path = `${import.meta.env.VUE_APP_API_URL}/api/stats/` const path = `${import.meta.env.VUE_APP_API_URL}/api/stats/`;
axios axios
.get(path) .get(path)
.then((res) => { .then((res) => {
this.stats = res.data this.stats = res.data;
}) })
.catch((error) => { .catch((error) => {
console.error(error) console.error(error);
}) });
}, },
}, },
}) });
</script> </script>
+25
View File
@@ -0,0 +1,25 @@
<template>
<div>
<ais-hits>
<template v-slot="{ items }">
<div>
<SearchResult v-for="item in items" :key="item.objectID" :item="item" />
</div>
</template>
</ais-hits>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import SearchResult from '@/components/features/SearchResult.vue';
export default defineComponent({
name: 'SearchResults',
components: {
SearchResult,
},
});
</script>