mirror of
https://github.com/mmahdium/TBW.git
synced 2025-12-20 04:33:54 +01:00
Compare commits
5 Commits
9375c6a9a7
...
d2255cefb7
| Author | SHA1 | Date | |
|---|---|---|---|
| d2255cefb7 | |||
| 1fe13e5208 | |||
| 73d6143897 | |||
| c5e32fc26f | |||
| 4b1cd06ac7 |
0
.editorconfig
Normal file → Executable file
0
.editorconfig
Normal file → Executable file
0
.gitattributes
vendored
Normal file → Executable file
0
.gitattributes
vendored
Normal file → Executable file
70
.github/workflows/deploy.yml
vendored
Normal file → Executable file
70
.github/workflows/deploy.yml
vendored
Normal file → Executable file
@@ -1,44 +1,44 @@
|
||||
name: Deploy to GitHub Pages
|
||||
# name: Deploy to GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
# on:
|
||||
# push:
|
||||
# branches: [main]
|
||||
# pull_request:
|
||||
# branches: [main]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
# jobs:
|
||||
# build-and-deploy:
|
||||
# runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
# steps:
|
||||
# - name: Checkout code
|
||||
# uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
# - uses: pnpm/action-setup@v4
|
||||
# name: Install pnpm
|
||||
# with:
|
||||
# version: 10
|
||||
# run_install: false
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
# - name: Install Node.js
|
||||
# uses: actions/setup-node@v4
|
||||
# with:
|
||||
# node-version: 20
|
||||
# cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
# - name: Install dependencies
|
||||
# run: pnpm install
|
||||
|
||||
- name: Build project
|
||||
run: DEPLOY_ENV=GH_PAGES pnpm build
|
||||
# - name: Build project
|
||||
# run: DEPLOY_ENV=GH_PAGES pnpm build
|
||||
|
||||
- name: Copy index.html to 404.html
|
||||
run: cp dist/index.html dist/404.html
|
||||
# - name: Copy index.html to 404.html
|
||||
# run: cp dist/index.html dist/404.html
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./dist
|
||||
publish_branch: gh-pages
|
||||
# - name: Deploy to GitHub Pages
|
||||
# if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
# uses: peaceiris/actions-gh-pages@v4
|
||||
# with:
|
||||
# github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# publish_dir: ./dist
|
||||
# publish_branch: gh-pages
|
||||
|
||||
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
.prettierrc.json
Normal file → Executable file
0
.prettierrc.json
Normal file → Executable file
0
.vscode/extensions.json
vendored
Normal file → Executable file
0
.vscode/extensions.json
vendored
Normal file → Executable file
0
eslint.config.ts
Normal file → Executable file
0
eslint.config.ts
Normal file → Executable file
0
index.html
Normal file → Executable file
0
index.html
Normal file → Executable file
7
package.json
Normal file → Executable file
7
package.json
Normal file → Executable file
@@ -8,12 +8,13 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"build": "run-p type-check \"build-only {@}\" -- && npm run inject-preloads",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
"lint": "eslint . --fix",
|
||||
"format": "prettier --write src/"
|
||||
"format": "prettier --write src/",
|
||||
"inject-preloads": "node scripts/inject-preloads.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.9.0",
|
||||
@@ -45,4 +46,4 @@
|
||||
"vite-plugin-vue-devtools": "^8.0.3",
|
||||
"vue-tsc": "^3.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
0
pnpm-lock.yaml
generated
Normal file → Executable file
0
pnpm-lock.yaml
generated
Normal file → Executable file
0
public/favicon.ico
Normal file → Executable file
0
public/favicon.ico
Normal file → Executable file
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
0
public/robots.txt
Normal file → Executable file
0
public/robots.txt
Normal file → Executable file
42
scripts/inject-preloads.js
Executable file
42
scripts/inject-preloads.js
Executable file
@@ -0,0 +1,42 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const distDir = path.resolve('dist')
|
||||
const manifestPath = path.join(distDir, '.vite/manifest.json')
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
|
||||
|
||||
const indexPath = path.join(distDir, 'index.html')
|
||||
let html = fs.readFileSync(indexPath, 'utf-8')
|
||||
|
||||
const filesToPreload = new Set()
|
||||
|
||||
function collectFiles(entryKey) {
|
||||
const chunk = manifest[entryKey]
|
||||
if (!chunk) return
|
||||
filesToPreload.add(chunk.file)
|
||||
for (const imp of chunk.imports || []) {
|
||||
if (imp === 'index.html') continue // skip entrypoint
|
||||
const dep = manifest[imp]
|
||||
if (dep) {
|
||||
filesToPreload.add(dep.file)
|
||||
collectFiles(imp) // recurse for transitive deps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// automatically find all .vue entries that are dynamic (lazy‑loaded)
|
||||
for (const key of Object.keys(manifest)) {
|
||||
if (key.endsWith('.vue') && manifest[key].isDynamicEntry) {
|
||||
collectFiles(key)
|
||||
}
|
||||
}
|
||||
|
||||
for (const f of filesToPreload) {
|
||||
const tag = `<link rel="modulepreload" href="/${f}">`
|
||||
if (!html.includes(tag)) {
|
||||
html = html.replace('</head>', ` ${tag}\n</head>`)
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(indexPath, html)
|
||||
console.log('Preload tags injected')
|
||||
4
src/App.vue
Normal file → Executable file
4
src/App.vue
Normal file → Executable file
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import '@/style.css'
|
||||
import NavBar from './components/NavBar.vue'
|
||||
import { SpeedInsights } from "@vercel/speed-insights/vue"
|
||||
import { SpeedInsights } from '@vercel/speed-insights/vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SpeedInsights/>
|
||||
<SpeedInsights />
|
||||
<NavBar />
|
||||
|
||||
<RouterView />
|
||||
|
||||
0
src/components/AddMoreCard.vue
Normal file → Executable file
0
src/components/AddMoreCard.vue
Normal file → Executable file
0
src/components/ImageWithFallback.vue
Normal file → Executable file
0
src/components/ImageWithFallback.vue
Normal file → Executable file
0
src/components/LoadingSpinner.vue
Normal file → Executable file
0
src/components/LoadingSpinner.vue
Normal file → Executable file
0
src/components/MediaCard.vue
Normal file → Executable file
0
src/components/MediaCard.vue
Normal file → Executable file
0
src/components/MediaDetails.vue
Normal file → Executable file
0
src/components/MediaDetails.vue
Normal file → Executable file
85
src/components/MediaFilters.vue
Executable file
85
src/components/MediaFilters.vue
Executable file
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import type { SearchFilters } from '@/types/SearchFilters'
|
||||
import { reactive, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: SearchFilters // optional initial value
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:filters', value: SearchFilters): void
|
||||
}>()
|
||||
|
||||
const local = reactive<SearchFilters>({
|
||||
includeAdult: props.modelValue?.includeAdult,
|
||||
onlyMovies: props.modelValue?.onlyMovies,
|
||||
onlySeries: props.modelValue?.onlySeries,
|
||||
})
|
||||
|
||||
function toggleIncludeAdult(e: Event) {
|
||||
local.includeAdult = (e.target as HTMLInputElement).checked
|
||||
}
|
||||
function toggleOnlyMovies(e: Event) {
|
||||
const checked = (e.target as HTMLInputElement).checked
|
||||
if (!checked && !local.onlySeries) {
|
||||
local.onlySeries = true
|
||||
}
|
||||
local.onlyMovies = checked
|
||||
}
|
||||
function toggleOnlySeries(e: Event) {
|
||||
const checked = (e.target as HTMLInputElement).checked
|
||||
if (!checked && !local.onlyMovies) {
|
||||
local.onlyMovies = true
|
||||
}
|
||||
local.onlySeries = checked
|
||||
}
|
||||
|
||||
watch(
|
||||
() => ({ ...local }),
|
||||
(next) => {
|
||||
// ensureAtLeastOneGenre()
|
||||
const out: SearchFilters = {
|
||||
includeAdult: next.includeAdult,
|
||||
onlyMovies: next.onlyMovies,
|
||||
onlySeries: next.onlySeries,
|
||||
}
|
||||
emit('update:filters', out)
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="flex gap-2 justify-center mb-8">
|
||||
<label>
|
||||
<input
|
||||
class="btn"
|
||||
:class="local.includeAdult ? 'btn-error' : ''"
|
||||
type="checkbox"
|
||||
:checked="local.includeAdult"
|
||||
@change="toggleIncludeAdult"
|
||||
aria-label="Adult"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input
|
||||
class="btn"
|
||||
type="checkbox"
|
||||
:checked="local.onlyMovies"
|
||||
@change="toggleOnlyMovies"
|
||||
aria-label="Movies"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input
|
||||
class="btn"
|
||||
type="checkbox"
|
||||
:checked="local.onlySeries"
|
||||
@change="toggleOnlySeries"
|
||||
aria-label="TV Series"
|
||||
/>
|
||||
</label>
|
||||
</form>
|
||||
</template>
|
||||
0
src/components/MediaList.vue
Normal file → Executable file
0
src/components/MediaList.vue
Normal file → Executable file
0
src/components/MediaTypeBadge.vue
Normal file → Executable file
0
src/components/MediaTypeBadge.vue
Normal file → Executable file
0
src/components/NavBar.vue
Normal file → Executable file
0
src/components/NavBar.vue
Normal file → Executable file
2
src/components/SearchBar.vue
Normal file → Executable file
2
src/components/SearchBar.vue
Normal file → Executable file
@@ -13,7 +13,7 @@ function onSubmit() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="onSubmit" class="flex justify-center mb-8">
|
||||
<form @submit.prevent="onSubmit" class="flex justify-center mb-3">
|
||||
<div
|
||||
class="flex items-center w-full max-w-md bg-white/70 backdrop-blur-md border border-gray-200/60 shadow-sm hover:shadow-md transition rounded-lg overflow-hidden"
|
||||
>
|
||||
|
||||
0
src/components/alerts/ErrorAlert.vue
Normal file → Executable file
0
src/components/alerts/ErrorAlert.vue
Normal file → Executable file
136
src/lib/api.ts
Normal file → Executable file
136
src/lib/api.ts
Normal file → Executable file
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { MediaResponseType } from '@/types/Media'
|
||||
import { mapMedia } from '@/types/MediaMap'
|
||||
import type { MovieDetailsType } from '@/types/Movie'
|
||||
import { mapMovieDetails } from '@/types/MovieMap'
|
||||
import type { SearchFilters } from '@/types/SearchFilters'
|
||||
import type { TvSeriesDetailsType } from '@/types/TvSeries'
|
||||
import { mapTvSeriesDetails } from '@/types/TvSeriesMap'
|
||||
import axios from 'axios'
|
||||
@@ -18,71 +20,99 @@ const instance = axios.create({
|
||||
},
|
||||
})
|
||||
|
||||
export const searchMovies = async (query: string): Promise<MediaResponseType> => {
|
||||
const response = await instance.get(`/search/multi`, {
|
||||
params: {
|
||||
query: query,
|
||||
include_adult: true,
|
||||
},
|
||||
})
|
||||
export const loadMedia = async (
|
||||
query: string,
|
||||
page = 1,
|
||||
filters: SearchFilters,
|
||||
): Promise<MediaResponseType> => {
|
||||
const params = {
|
||||
query,
|
||||
include_adult: filters.includeAdult || false,
|
||||
page,
|
||||
}
|
||||
const requests: Promise<any>[] = []
|
||||
if (filters.onlyMovies) {
|
||||
requests.push(instance.get(`/search/movie`, { params }))
|
||||
}
|
||||
if (filters.onlySeries) {
|
||||
requests.push(instance.get(`/search/tv`, { params }))
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
const responses = await Promise.all(requests)
|
||||
|
||||
// If only one type was requested, handle that directly
|
||||
if (filters.onlyMovies) {
|
||||
const movieSearch = responses[0]
|
||||
if (movieSearch.status !== 200) {
|
||||
return {
|
||||
Results: [],
|
||||
Page: 0,
|
||||
totalResults: 0,
|
||||
totalPages: 0,
|
||||
ErrorMessage: movieSearch.data.Error,
|
||||
}
|
||||
}
|
||||
const movies = movieSearch.data.results.map((r: any) => mapMedia({ ...r, media_type: 'movie' }))
|
||||
return {
|
||||
Results: movies,
|
||||
Page: movieSearch.data.page,
|
||||
totalResults: movieSearch.data.total_results,
|
||||
totalPages: movieSearch.data.total_pages,
|
||||
ErrorMessage: '',
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.onlySeries) {
|
||||
const tvSearch = responses[0]
|
||||
if (tvSearch.status !== 200) {
|
||||
return {
|
||||
Results: [],
|
||||
Page: 0,
|
||||
totalResults: 0,
|
||||
totalPages: 0,
|
||||
ErrorMessage: tvSearch.data.Error,
|
||||
}
|
||||
}
|
||||
const tv = tvSearch.data.results.map((r: any) => mapMedia({ ...r, media_type: 'tv' }))
|
||||
return {
|
||||
Results: tv,
|
||||
Page: tvSearch.data.page,
|
||||
totalResults: tvSearch.data.total_results,
|
||||
totalPages: tvSearch.data.total_pages,
|
||||
ErrorMessage: '',
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, both
|
||||
const [movieSearch, tvSearch] = responses
|
||||
if (movieSearch.status !== 200 || tvSearch.status !== 200) {
|
||||
return {
|
||||
Results: [],
|
||||
Page: 0,
|
||||
totalResults: 0,
|
||||
totalPages: 0,
|
||||
ErrorMessage: response.data.Error,
|
||||
ErrorMessage: movieSearch?.data?.Error || tvSearch?.data?.Error,
|
||||
}
|
||||
}
|
||||
|
||||
const filtered = response.data.results.filter((result: { media_type: string }) => {
|
||||
return result.media_type === 'movie' || result.media_type === 'tv'
|
||||
})
|
||||
const filteredMovies = movieSearch.data.results.map((r: any) =>
|
||||
mapMedia({ ...r, media_type: 'movie' }),
|
||||
)
|
||||
const filteredTV = tvSearch.data.results.map((r: any) => mapMedia({ ...r, media_type: 'tv' }))
|
||||
|
||||
const data: MediaResponseType = {
|
||||
Results: filtered.map(mapMedia),
|
||||
Page: response.data.page,
|
||||
totalResults: response.data.total_results,
|
||||
totalPages: response.data.total_pages,
|
||||
const res: any[] = []
|
||||
for (let i = 0; i < Math.max(filteredMovies.length, filteredTV.length); i++) {
|
||||
if (i < filteredMovies.length) res.push(filteredMovies[i])
|
||||
if (i < filteredTV.length) res.push(filteredTV[i])
|
||||
}
|
||||
|
||||
return {
|
||||
Results: res,
|
||||
Page: Math.max(movieSearch.data.page, tvSearch.data.page),
|
||||
totalResults: movieSearch.data.total_results + tvSearch.data.total_results,
|
||||
totalPages: Math.max(movieSearch.data.total_pages, tvSearch.data.total_pages),
|
||||
ErrorMessage: '',
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export const loadMoreMovies = async (query: string, page: number): Promise<MediaResponseType> => {
|
||||
const response = await instance.get(`/search/multi`, {
|
||||
params: {
|
||||
query: query,
|
||||
include_adult: true,
|
||||
page: page,
|
||||
},
|
||||
})
|
||||
|
||||
if (response.status !== 200) {
|
||||
return {
|
||||
Results: [],
|
||||
Page: 0,
|
||||
totalResults: 0,
|
||||
totalPages: 0,
|
||||
ErrorMessage: response.data.Error,
|
||||
}
|
||||
}
|
||||
|
||||
const filtered = response.data.results.filter((result: { media_type: string }) => {
|
||||
return result.media_type === 'movie' || result.media_type === 'tv'
|
||||
})
|
||||
|
||||
const data: MediaResponseType = {
|
||||
Results: filtered.map(mapMedia),
|
||||
Page: response.data.page,
|
||||
totalResults: response.data.total_results,
|
||||
totalPages: response.data.total_pages,
|
||||
ErrorMessage: '',
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export const getMovieDetails = async (id: string): Promise<MovieDetailsType | null> => {
|
||||
|
||||
1
src/main.ts
Normal file → Executable file
1
src/main.ts
Normal file → Executable file
@@ -15,3 +15,4 @@ app.use(autoAnimatePlugin)
|
||||
app.use(MotionPlugin)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
|
||||
0
src/router/index.ts
Normal file → Executable file
0
src/router/index.ts
Normal file → Executable file
8
src/stores/media.ts
Normal file → Executable file
8
src/stores/media.ts
Normal file → Executable file
@@ -1,6 +1,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import type { MediaType } from '@/types/Media'
|
||||
import type { SearchFilters } from '@/types/SearchFilters'
|
||||
|
||||
function saveMedias(medias: MediaType[]) {
|
||||
localStorage.setItem('medias', JSON.stringify(medias))
|
||||
@@ -33,11 +34,14 @@ export const useSearchPageStore = defineStore('searchPage', () => {
|
||||
const mediaList = ref<MediaType[]>()
|
||||
const searchPage = ref(0)
|
||||
const searchQuery = ref('')
|
||||
const searchFilters = ref<SearchFilters>()
|
||||
|
||||
function setState(page: number, query: string, medias: MediaType[]) {
|
||||
function setState(page: number, query: string, medias: MediaType[], filters: SearchFilters) {
|
||||
searchPage.value = page
|
||||
searchQuery.value = query
|
||||
mediaList.value = medias
|
||||
searchFilters.value = filters
|
||||
console.log('setState', page, query, medias, filters)
|
||||
}
|
||||
return { searchPage, searchQuery, mediaList, setState }
|
||||
return { searchPage, searchQuery, mediaList, searchFilters, setState }
|
||||
})
|
||||
|
||||
0
src/style.css
Normal file → Executable file
0
src/style.css
Normal file → Executable file
0
src/types/Media.ts
Normal file → Executable file
0
src/types/Media.ts
Normal file → Executable file
0
src/types/MediaMap.ts
Normal file → Executable file
0
src/types/MediaMap.ts
Normal file → Executable file
0
src/types/Movie.ts
Normal file → Executable file
0
src/types/Movie.ts
Normal file → Executable file
0
src/types/MovieMap.ts
Normal file → Executable file
0
src/types/MovieMap.ts
Normal file → Executable file
5
src/types/SearchFilters.ts
Executable file
5
src/types/SearchFilters.ts
Executable file
@@ -0,0 +1,5 @@
|
||||
export type SearchFilters = {
|
||||
includeAdult: boolean
|
||||
onlyMovies: boolean
|
||||
onlySeries: boolean
|
||||
}
|
||||
0
src/types/TvSeries.ts
Normal file → Executable file
0
src/types/TvSeries.ts
Normal file → Executable file
0
src/types/TvSeriesMap.ts
Normal file → Executable file
0
src/types/TvSeriesMap.ts
Normal file → Executable file
39
src/views/AddView.vue
Normal file → Executable file
39
src/views/AddView.vue
Normal file → Executable file
@@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { loadMoreMovies, searchMovies } from '@/lib/api'
|
||||
import { loadMedia } from '@/lib/api'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import SearchBar from '@/components/SearchBar.vue'
|
||||
import MediaList from '@/components/MediaList.vue'
|
||||
import type { MediaType } from '@/types/Media'
|
||||
import ErrorAlert from '@/components/alerts/ErrorAlert.vue'
|
||||
import { useSearchPageStore, useMediaStore } from '@/stores/media'
|
||||
import MediaFilters from '@/components/MediaFilters.vue'
|
||||
import type { SearchFilters } from '@/types/SearchFilters'
|
||||
|
||||
const medias = ref<MediaType[]>()
|
||||
const seachError = ref<string>('')
|
||||
@@ -17,6 +19,13 @@ const isLoadingMore = ref(false)
|
||||
const state = useSearchPageStore()
|
||||
const store = useMediaStore()
|
||||
|
||||
// Initialize filters with stored values from the state, defaulting to initial values if not available
|
||||
const filters = ref<SearchFilters>({
|
||||
includeAdult: state.searchFilters?.includeAdult ?? false,
|
||||
onlyMovies: state.searchFilters?.onlyMovies ?? true,
|
||||
onlySeries: state.searchFilters?.onlySeries ?? false,
|
||||
})
|
||||
|
||||
const handleAddMedia = (media: MediaType) => {
|
||||
store.addMedia(media)
|
||||
}
|
||||
@@ -29,8 +38,7 @@ async function searchMovie() {
|
||||
try {
|
||||
isSearching.value = true
|
||||
searchPage.value = 1
|
||||
const result = await searchMovies(searchQuery.value)
|
||||
console.log(result)
|
||||
const result = await loadMedia(searchQuery.value, searchPage.value, filters.value)
|
||||
if (result.totalResults === 0) {
|
||||
medias.value = []
|
||||
seachError.value = 'No results found'
|
||||
@@ -39,7 +47,7 @@ async function searchMovie() {
|
||||
seachError.value = ''
|
||||
medias.value = result.Results
|
||||
|
||||
state.setState(searchPage.value, searchQuery.value, result.Results)
|
||||
state.setState(searchPage.value, searchQuery.value, result.Results, filters.value)
|
||||
} catch (error) {
|
||||
medias.value = []
|
||||
console.error(error)
|
||||
@@ -53,9 +61,14 @@ async function loadMore() {
|
||||
try {
|
||||
isLoadingMore.value = true
|
||||
searchPage.value++
|
||||
const result = await loadMoreMovies(searchQuery.value, searchPage.value)
|
||||
const result = await loadMedia(searchQuery.value, searchPage.value, filters.value)
|
||||
medias.value?.push(...result.Results)
|
||||
state.setState(searchPage.value, searchQuery.value, medias.value ? medias.value : [])
|
||||
state.setState(
|
||||
searchPage.value,
|
||||
searchQuery.value,
|
||||
medias.value ? medias.value : [],
|
||||
filters.value,
|
||||
)
|
||||
} catch (error) {
|
||||
searchPage.value = 1
|
||||
seachError.value = (error as Error).message
|
||||
@@ -91,6 +104,18 @@ watch(searchQuery, () => {
|
||||
timeoutId = null
|
||||
}, 500)
|
||||
})
|
||||
|
||||
watch(filters, () => {
|
||||
if (searchQuery.value.length > 2) {
|
||||
state.setState(
|
||||
searchPage.value,
|
||||
searchQuery.value,
|
||||
medias.value ? medias.value : [],
|
||||
filters.value,
|
||||
)
|
||||
searchMovie()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -105,6 +130,8 @@ watch(searchQuery, () => {
|
||||
<!-- Search bar -->
|
||||
<SearchBar v-model="searchQuery" @submit="searchMovie" />
|
||||
|
||||
<MediaFilters @update:filters="(f) => (filters = f)" :modelValue="filters" />
|
||||
|
||||
<!-- Loading spinner -->
|
||||
<div v-if="isSearching" class="flex justify-center my-16">
|
||||
<span class="loading loading-ring loading-lg text-primary"></span>
|
||||
|
||||
0
src/views/DetailsView.vue
Normal file → Executable file
0
src/views/DetailsView.vue
Normal file → Executable file
0
src/views/HomeView.vue
Normal file → Executable file
0
src/views/HomeView.vue
Normal file → Executable file
0
src/views/ListView.vue
Normal file → Executable file
0
src/views/ListView.vue
Normal file → Executable file
0
src/views/NotFoundView.vue
Normal file → Executable file
0
src/views/NotFoundView.vue
Normal file → Executable file
0
src/views/WatchView.vue
Normal file → Executable file
0
src/views/WatchView.vue
Normal file → Executable file
0
tsconfig.app.json
Normal file → Executable file
0
tsconfig.app.json
Normal file → Executable file
0
tsconfig.json
Normal file → Executable file
0
tsconfig.json
Normal file → Executable file
0
tsconfig.node.json
Normal file → Executable file
0
tsconfig.node.json
Normal file → Executable file
3
vite.config.ts
Normal file → Executable file
3
vite.config.ts
Normal file → Executable file
@@ -20,4 +20,7 @@ export default defineConfig({
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
manifest: true
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user