mirror of
https://github.com/Xevion/the-office.git
synced 2026-01-31 02:26:13 -06:00
refactor: restructure all components into separate folders/views
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown>
|
||||
export default component
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
+13
-13
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import SeasonList from '@/components/SeasonList.vue';
|
||||
import SeasonList from '@/components/layout/SeasonList.vue';
|
||||
import { ref } from 'vue';
|
||||
import logoSrc from '@/assets/logo.svg';
|
||||
|
||||
@@ -21,14 +21,14 @@ const headings = [
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- 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">
|
||||
<!-- Mobile menu button -->
|
||||
<button
|
||||
@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">
|
||||
<path
|
||||
@@ -42,33 +42,33 @@ const headings = [
|
||||
|
||||
<!-- Logo/Brand -->
|
||||
<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>
|
||||
|
||||
<!-- Header Navigation -->
|
||||
<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
|
||||
v-for="heading in headings"
|
||||
:key="heading.name"
|
||||
: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 }}
|
||||
</RouterLink>
|
||||
</nav>
|
||||
|
||||
<!-- Search bar -->
|
||||
<div class="hidden md:flex items-center">
|
||||
<div class="hidden items-center md:flex">
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
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
|
||||
class="h-5 w-5 text-gray-400"
|
||||
fill="none"
|
||||
@@ -88,7 +88,7 @@ const headings = [
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex mt-24">
|
||||
<div class="mt-24 flex">
|
||||
<!-- Sidebar -->
|
||||
<div class="pl-8">
|
||||
<SeasonList />
|
||||
@@ -98,13 +98,13 @@ const headings = [
|
||||
<div
|
||||
v-if="sidebarOpen"
|
||||
@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>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="col-span-8 lg:ml-0">
|
||||
<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">
|
||||
This is your main content area. You can add your router-view or other components here.
|
||||
</p>
|
||||
|
||||
@@ -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>
|
||||
<table class="quote-list px-3 w-100">
|
||||
<table class="quote-list w-100 px-3">
|
||||
<tr
|
||||
v-for="(quote, index) in quotes"
|
||||
:id="`${sceneIndex}-${index}`"
|
||||
@@ -53,9 +53,9 @@
|
||||
</style>
|
||||
|
||||
<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({
|
||||
components: {
|
||||
@@ -76,16 +76,16 @@ export default defineComponent({
|
||||
methods: {
|
||||
transform(quoteText) {
|
||||
if (quoteText.includes('[')) {
|
||||
return quoteText.replace(/\[([^\]]+)]/g, ' <i>[$1]</i> ')
|
||||
return quoteText.replace(/\[([^\]]+)]/g, ' <i>[$1]</i> ');
|
||||
}
|
||||
return quoteText
|
||||
return quoteText;
|
||||
},
|
||||
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) {
|
||||
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>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import SeasonListItem from '@/components/SeasonListItem.vue';
|
||||
import SeasonListItem from '@/components/layout/SeasonListItem.vue';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
+13
-13
@@ -1,12 +1,12 @@
|
||||
import Home from '@/components/Home.vue'
|
||||
import Episode from '@/components/Episode.vue'
|
||||
import SearchResults from '@/components/SearchResults.vue'
|
||||
import Character from '@/components/Character.vue'
|
||||
import Season from '@/components/Season.vue'
|
||||
import Characters from '@/components/Characters.vue'
|
||||
import About from '@/components/About.vue'
|
||||
import Home from '@/views/Home.vue';
|
||||
import Episode from '@/views/Episode.vue';
|
||||
import SearchResults from '@/views/SearchResults.vue';
|
||||
import Character from '@/views/Character.vue';
|
||||
import Season from '@/views/Season.vue';
|
||||
import Characters from '@/views/Characters.vue';
|
||||
import About from '@/views/About.vue';
|
||||
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
@@ -51,16 +51,16 @@ const router = createRouter({
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
// https://router.vuejs.org/guide/advanced/scroll-behavior.html
|
||||
if (to.hash) {
|
||||
return { el: to.hash, behavior: 'smooth' }
|
||||
return { el: to.hash, behavior: 'smooth' };
|
||||
}
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
return savedPosition;
|
||||
}
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
};
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
export default router
|
||||
export default router;
|
||||
|
||||
@@ -30,15 +30,15 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, nextTick } from 'vue'
|
||||
import Skeleton from './Skeleton.vue'
|
||||
import { BBreadcrumb } from 'bootstrap-vue-next'
|
||||
import useStore from '@/store'
|
||||
import { defineComponent, nextTick } from 'vue';
|
||||
import Skeleton from '@/components/common/Skeleton.vue';
|
||||
import { BBreadcrumb } from 'bootstrap-vue-next';
|
||||
import useStore from '@/store';
|
||||
|
||||
interface BreadcrumbItem {
|
||||
text: string
|
||||
to?: { name: string }
|
||||
active?: boolean
|
||||
text: string;
|
||||
to?: { name: string };
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
@@ -50,17 +50,17 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const store = useStore();
|
||||
return {
|
||||
store,
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
character() {
|
||||
return this.store.characters[this.$route.params.character as string]
|
||||
return this.store.characters[this.$route.params.character as string];
|
||||
},
|
||||
ready(): boolean {
|
||||
return this.character !== undefined
|
||||
return this.character !== undefined;
|
||||
},
|
||||
|
||||
breadcrumbs(): BreadcrumbItem[] {
|
||||
@@ -77,31 +77,31 @@ export default defineComponent({
|
||||
text: this.character?.name || (this.$route.params.character as string),
|
||||
active: true,
|
||||
},
|
||||
]
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
'$route.params.character'() {
|
||||
nextTick(() => {
|
||||
this.fetchCharacter()
|
||||
})
|
||||
this.fetchCharacter();
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchCharacter()
|
||||
this.fetchCharacter();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchCharacter(): Promise<void> {
|
||||
try {
|
||||
await this.store.preloadCharacters()
|
||||
this.character = this.store.characters[this.$route.params.character as string]
|
||||
await this.store.preloadCharacters();
|
||||
this.character = this.store.characters[this.$route.params.character as string];
|
||||
} catch (error) {
|
||||
console.error('Error fetching character:', error)
|
||||
console.error('Error fetching character:', error);
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@@ -87,11 +87,11 @@ h4 {
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import Skeleton from '@/components/Skeleton.vue'
|
||||
import ImageSkeleton from '@/components/ImageSkeleton.vue'
|
||||
import { BBreadcrumb, BImg } from 'bootstrap-vue-next'
|
||||
import Skeleton from '@/components/common/Skeleton.vue';
|
||||
import ImageSkeleton from '@/components/common/ImageSkeleton.vue';
|
||||
import { BBreadcrumb, BImg } from 'bootstrap-vue-next';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CharactersComponent',
|
||||
@@ -105,24 +105,24 @@ export default defineComponent({
|
||||
|
||||
computed: {
|
||||
ready() {
|
||||
return this.$store.getters.checkPreloaded('characters')
|
||||
return this.$store.getters.checkPreloaded('characters');
|
||||
},
|
||||
sorted_character_ids() {
|
||||
return this.$store.getters.getSortedCharacters()
|
||||
return this.$store.getters.getSortedCharacters();
|
||||
},
|
||||
characters() {
|
||||
return this.$store.state.characters
|
||||
return this.$store.state.characters;
|
||||
},
|
||||
breadcrumbs() {
|
||||
return [
|
||||
{ text: 'Home', to: { name: 'Home' } },
|
||||
{ text: 'Characters', active: true },
|
||||
]
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
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
|
||||
// this.$forceUpdate();
|
||||
@@ -130,8 +130,8 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
faceURL(character, thumbnail = false) {
|
||||
return `/img/${character}/` + (thumbnail ? 'face_thumb' : 'face') + '.jpeg'
|
||||
return `/img/${character}/` + (thumbnail ? 'face_thumb' : 'face') + '.jpeg';
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@@ -28,7 +28,7 @@
|
||||
<QuoteList :quotes="scene.quotes" :scene-index="sceneIndex" />
|
||||
<span
|
||||
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}`"
|
||||
>
|
||||
Deleted Scene {{ scene.deleted }}
|
||||
@@ -52,12 +52,12 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, nextTick } from 'vue'
|
||||
import { defineComponent, nextTick } from 'vue';
|
||||
|
||||
import QuoteList from '@/components/QuoteList.vue'
|
||||
import CharacterBadges from '@/components/CharacterBadges.vue'
|
||||
import Skeleton from '@/components/Skeleton.vue'
|
||||
import { BBreadcrumb } from 'bootstrap-vue-next'
|
||||
import QuoteList from '@/components/features/QuoteList.vue';
|
||||
import CharacterBadges from '@/components/features/CharacterBadges.vue';
|
||||
import Skeleton from '@/components/common/Skeleton.vue';
|
||||
import { BBreadcrumb } from 'bootstrap-vue-next';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EpisodeComponent',
|
||||
@@ -71,14 +71,14 @@ export default defineComponent({
|
||||
|
||||
computed: {
|
||||
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
|
||||
params() {
|
||||
return this.$route.params
|
||||
return this.$route.params;
|
||||
},
|
||||
ready() {
|
||||
return this.$store.getters.isFetched(this.params.season, this.params.episode)
|
||||
return this.$store.getters.isFetched(this.params.season, this.params.episode);
|
||||
},
|
||||
breadcrumbs() {
|
||||
return [
|
||||
@@ -104,7 +104,7 @@ export default defineComponent({
|
||||
},
|
||||
active: true,
|
||||
},
|
||||
]
|
||||
];
|
||||
},
|
||||
},
|
||||
|
||||
@@ -112,14 +112,14 @@ export default defineComponent({
|
||||
// When route changes, fetch data for current Episode route
|
||||
$route() {
|
||||
nextTick(() => {
|
||||
this.fetch()
|
||||
})
|
||||
this.fetch();
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
// When page loads directly on this Episode initially, fetch data
|
||||
this.fetch()
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -129,17 +129,17 @@ export default defineComponent({
|
||||
.dispatch(types.FETCH_EPISODE, { season: this.params.season, episode: this.params.episode })
|
||||
.then(() => {
|
||||
// Force update, as for some reason it doesn't update naturally. I hate it too.
|
||||
this.$forceUpdate()
|
||||
this.$forceUpdate();
|
||||
|
||||
// Scroll down to quote
|
||||
if (this.$route.hash) {
|
||||
nextTick(() => {
|
||||
const section = document.getElementById(this.$route.hash.substring(1))
|
||||
this.$scrollTo(section, 500, { easing: 'ease-in' })
|
||||
})
|
||||
const section = document.getElementById(this.$route.hash.substring(1));
|
||||
this.$scrollTo(section, 500, { easing: 'ease-in' });
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@@ -27,11 +27,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import axios from 'axios'
|
||||
import Skeleton from './Skeleton.vue'
|
||||
import { BCardText, BCard } from 'bootstrap-vue-next'
|
||||
import axios from 'axios';
|
||||
import Skeleton from '@/components/common/Skeleton.vue';
|
||||
import { BCardText, BCard } from 'bootstrap-vue-next';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HomeComponent',
|
||||
@@ -45,12 +45,12 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
stats: null,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
ready() {
|
||||
return true
|
||||
return true;
|
||||
// return this.stats != null;
|
||||
},
|
||||
},
|
||||
@@ -61,16 +61,16 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
getStats() {
|
||||
const path = `${import.meta.env.VUE_APP_API_URL}/api/stats/`
|
||||
const path = `${import.meta.env.VUE_APP_API_URL}/api/stats/`;
|
||||
axios
|
||||
.get(path)
|
||||
.then((res) => {
|
||||
this.stats = res.data
|
||||
this.stats = res.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user