Fix frontend for seperate backend, and make preferences page
This commit is contained in:
parent
1e9726a601
commit
edcc3efe1d
2
.env
Normal file
2
.env
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_BACKEND_URL=http://localhost:7000
|
||||
VITE_INSTANCE_URL=http://localhost:5173
|
10
env.d.ts
vendored
10
env.d.ts
vendored
@ -1 +1,11 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_BACKEND_URL: string
|
||||
readonly VITE_INSTANCE_URL: string
|
||||
// more env variables...
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@dragongoose/streamlink": "^1.0.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"oh-vue-icons": "^1.0.0-rc3",
|
||||
"video.js": "^8.0.4",
|
||||
"videojs-contrib-quality-levels": "^3.0.0",
|
||||
@ -25,9 +26,9 @@
|
||||
"@rushstack/eslint-patch": "^1.2.0",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/node": "^18.14.2",
|
||||
"@types/video.js": "^7.3.51",
|
||||
"@types/videojs-contrib-quality-levels": "^2.0.1",
|
||||
"@types/videojs-hls-quality-selector": "^1.1.0",
|
||||
"@types/video.js": "^7.3.51",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vue/eslint-config-prettier": "^7.1.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
|
40
src/components/ErrorMessage.vue
Normal file
40
src/components/ErrorMessage.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: {
|
||||
errMessage: {
|
||||
type: Object,
|
||||
default() {
|
||||
{
|
||||
;('Error')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
return {
|
||||
errorMessage: props.errMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col max-w-prose justify-center text-center mx-auto p-6 bg-ctp-crust rounded-lg text-white"
|
||||
>
|
||||
<div class="mb-6">
|
||||
<h1 class="font-bold text-5xl">oops...</h1>
|
||||
<p class="font-bold text-3xl">this wasn't supposed to happen</p>
|
||||
</div>
|
||||
|
||||
<p class="text-xl">
|
||||
the server was encountered an error while retriving the data, and now we're here :3
|
||||
</p>
|
||||
|
||||
<div class="mt-5">
|
||||
<p class="text-xl">please contact the administrator with this code</p>
|
||||
<p class="text-xl">error identifier: {{ errorMessage.code }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -57,7 +57,9 @@ export default {
|
||||
<div v-if="isLive" class="p-3 bg-ctp-crust rounded-lg w-full max-w-[15.625rem] flex flex-col">
|
||||
<ul class="overflow-y-scroll h-[46.875rem]" ref="chatList">
|
||||
<li>
|
||||
<p ref="initConnectingStatus" class="text-gray-500 text-sm italic"> Connecting to {{ channelName }}.</p>
|
||||
<p ref="initConnectingStatus" class="text-gray-500 text-sm italic">
|
||||
Connecting to {{ channelName }}.
|
||||
</p>
|
||||
</li>
|
||||
<li v-for="message in getChat()" :key="messages.indexOf(message)">
|
||||
<div class="text-white inline-flex">
|
||||
|
@ -4,6 +4,7 @@ import PageNotFound from '../views/PageNotFound.vue'
|
||||
import PrivacyPageView from '../views/PrivacyPageView.vue'
|
||||
import HomepageView from '../views/HomepageView.vue'
|
||||
import CategoryView from '../views/CategoryView.vue'
|
||||
import PreferencesView from '../views/PreferencesView.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@ -13,7 +14,7 @@ const router = createRouter({
|
||||
component: HomepageView
|
||||
},
|
||||
{
|
||||
path:'/game/:game',
|
||||
path: '/game/:game',
|
||||
component: CategoryView
|
||||
},
|
||||
{
|
||||
@ -21,6 +22,10 @@ const router = createRouter({
|
||||
name: 'about',
|
||||
component: PrivacyPageView
|
||||
},
|
||||
{
|
||||
path: '/preferences',
|
||||
component: PreferencesView
|
||||
},
|
||||
{
|
||||
path: '/:username',
|
||||
component: UserView
|
||||
|
@ -1,24 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
export default {
|
||||
export default {
|
||||
async setup() {
|
||||
const route = useRoute()
|
||||
const game = route.params.game
|
||||
const res = await fetch(`http://localhost:7000/api/discover/${game}`)
|
||||
const res = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/discover/${game}`)
|
||||
const data = await res.json()
|
||||
console.log(import.meta.env)
|
||||
|
||||
let frontend_url = import.meta.env.VITE_INSTANCE_URL
|
||||
return {
|
||||
data: await res.json()
|
||||
data,
|
||||
frontend_url
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
abbreviate(text: number) {
|
||||
return Intl.NumberFormat('en-US', {
|
||||
//@ts-ignore
|
||||
notation: "compact",
|
||||
maximumFractionDigits: 1
|
||||
}).format(text)
|
||||
return Intl.NumberFormat('en-US', {
|
||||
//@ts-ignore
|
||||
notation: 'compact',
|
||||
maximumFractionDigits: 1
|
||||
}).format(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,54 +31,58 @@ export default {
|
||||
<template>
|
||||
<div class="flex flex-col max-w-5xl mx-auto">
|
||||
<div class="flex space-x-4 p-3">
|
||||
<img :src="data.cover" class="self-start rounded-md">
|
||||
<img :src="data.cover" class="self-start rounded-md" />
|
||||
|
||||
<div>
|
||||
<h1 class="font-bold text-5xl text-white">{{ data.name }}</h1>
|
||||
<div class="inline-flex my-1 space-x-3">
|
||||
<p class="font-bold text-white text-lg">Followers: {{ abbreviate(data.followers) }}</p>
|
||||
<p class="font-bold text-white text-lg">Viewers: {{ abbreviate(data.viewers) }}</p>
|
||||
</div>
|
||||
|
||||
<ul class="mb-5">
|
||||
<li v-for="tag in data.tags" :key="tag" class="inline-flex">
|
||||
<span class="text-white p-1 py-0.5 mr-1 text-sm font-bold bg-ctp-overlay1 rounded-sm">{{ tag }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="text-md text-gray-400 overflow-y-auto">{{ data.description }}</p>
|
||||
<div>
|
||||
<h1 class="font-bold text-5xl text-white">{{ data.name }}</h1>
|
||||
<div class="inline-flex my-1 space-x-3">
|
||||
<p class="font-bold text-white text-lg">Followers: {{ abbreviate(data.followers) }}</p>
|
||||
<p class="font-bold text-white text-lg">Viewers: {{ abbreviate(data.viewers) }}</p>
|
||||
</div>
|
||||
|
||||
<ul class="mb-5">
|
||||
<li v-for="tag in data.tags" :key="tag" class="inline-flex">
|
||||
<span class="text-white p-1 py-0.5 mr-1 text-sm font-bold bg-ctp-overlay1 rounded-sm">{{
|
||||
tag
|
||||
}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="text-md text-gray-400 overflow-y-auto">{{ data.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-[58rem] mx-auto">
|
||||
<ul>
|
||||
<li v-for="stream in data.streams" :key="stream" class="inline-flex m-2 hover:scale-105 transition-transform">
|
||||
<div class="bg-ctp-crust rounded-lg">
|
||||
<a :href="`http://localhost:5173/${stream.streamer.name}`">
|
||||
<img :src="stream.preview" class="rounded-lg rounded-b-none">
|
||||
</a>
|
||||
<ul>
|
||||
<li
|
||||
v-for="stream in data.streams"
|
||||
:key="stream"
|
||||
class="inline-flex m-2 hover:scale-105 transition-transform"
|
||||
>
|
||||
<div class="bg-ctp-crust rounded-lg">
|
||||
<a :href="`${frontend_url}/${stream.streamer.name}`">
|
||||
<img :src="stream.preview" class="rounded-lg rounded-b-none" />
|
||||
</a>
|
||||
|
||||
<div class="text-white p-2 inline-flex space-x-2 w-full">
|
||||
|
||||
|
||||
<div class="inline-flex w-full">
|
||||
<div class="inline-flex">
|
||||
<img :src="stream.streamer.pfp" class="rounded-full mr-2">
|
||||
<div>
|
||||
<p class="font-bold w-[22.9rem] truncate">{{ stream.title }}</p>
|
||||
<div class="inline-flex w-full justify-between">
|
||||
<p class="text-gray-300">{{ stream.streamer.name }}</p>
|
||||
<p class="self-end float-right"> <v-icon name="io-person"></v-icon> {{ abbreviate(stream.viewers) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-white p-2 inline-flex space-x-2 w-full">
|
||||
<div class="inline-flex w-full">
|
||||
<div class="inline-flex">
|
||||
<img :src="stream.streamer.pfp" class="rounded-full mr-2" />
|
||||
<div>
|
||||
<p class="font-bold w-[22.9rem] truncate">{{ stream.title }}</p>
|
||||
<div class="inline-flex w-full justify-between">
|
||||
<p class="text-gray-300">{{ stream.streamer.name }}</p>
|
||||
<p class="self-end float-right">
|
||||
<v-icon name="io-person"></v-icon> {{ abbreviate(stream.viewers) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,21 +1,22 @@
|
||||
<script lang="ts">
|
||||
|
||||
|
||||
export default {
|
||||
async setup() {
|
||||
const res = await fetch(`http://localhost:7000/api/discover`)
|
||||
const res = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/discover`)
|
||||
console.log(import.meta.env)
|
||||
let frontend_url = import.meta.env.VITE_INSTANCE_URL
|
||||
|
||||
return {
|
||||
data: await res.json()
|
||||
data: await res.json(),
|
||||
frontend_url
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
abbreviate(text: number) {
|
||||
return Intl.NumberFormat('en-US', {
|
||||
//@ts-ignore
|
||||
notation: "compact",
|
||||
maximumFractionDigits: 1
|
||||
}).format(text)
|
||||
return Intl.NumberFormat('en-US', {
|
||||
//@ts-ignore
|
||||
notation: 'compact',
|
||||
maximumFractionDigits: 1
|
||||
}).format(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,17 +25,14 @@ export default {
|
||||
<template>
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="p-2">
|
||||
<h1 class="font-bold text-5xl text-white"> Discover </h1>
|
||||
<p class="text-xl text-white"> Sort through popular categories</p>
|
||||
<h1 class="font-bold text-5xl text-white">Discover</h1>
|
||||
<p class="text-xl text-white">Sort through popular categories</p>
|
||||
|
||||
<div class="pt-5 inline-flex text-white">
|
||||
<p class="mr-2 font-bold text-white">Filter by tag</p>
|
||||
<form class="relative">
|
||||
<label for="searchBar" class="hidden">Search</label>
|
||||
<v-icon
|
||||
name="io-search-outline"
|
||||
class="absolute my-auto inset-y-0 left-2"
|
||||
></v-icon>
|
||||
<v-icon name="io-search-outline" class="absolute my-auto inset-y-0 left-2"></v-icon>
|
||||
<input
|
||||
type="text"
|
||||
id="searchBar"
|
||||
@ -47,21 +45,28 @@ export default {
|
||||
</div>
|
||||
|
||||
<ul class="">
|
||||
<li v-for="category in data" :key="category" class="inline-flex m-2 hover:scale-105 transition-transform">
|
||||
<li
|
||||
v-for="category in data"
|
||||
:key="category"
|
||||
class="inline-flex m-2 hover:scale-105 transition-transform"
|
||||
>
|
||||
<div class="bg-ctp-crust max-w-[13.5rem] rounded-lg">
|
||||
<a :href="`http://localhost:5173/game/${category.name}`">
|
||||
<img :src="category.image" class="rounded-lg rounded-b-none">
|
||||
</a>
|
||||
<router-link :to="`/game/${category.name}`">
|
||||
<img :src="category.image" class="rounded-lg rounded-b-none" />
|
||||
</router-link>
|
||||
|
||||
<div class="p-2">
|
||||
<div>
|
||||
<p class="font-bold text-white text-xl"> {{ category.displayName }}</p>
|
||||
<p class="text-sm text-white"> {{ abbreviate(category.viewers) }} viewers</p>
|
||||
<p class="font-bold text-white text-xl">{{ category.displayName }}</p>
|
||||
<p class="text-sm text-white">{{ abbreviate(category.viewers) }} viewers</p>
|
||||
</div>
|
||||
|
||||
<ul class="h-8 overflow-hidden">
|
||||
<li v-for="tag in category.tags" :key="tag" class="inline-flex">
|
||||
<span class="p-2.5 py-1.5 bg-ctp-surface0 rounded-md m-0.5 text-xs font-bold text-white">{{ tag }}</span>
|
||||
<span
|
||||
class="p-2.5 py-1.5 bg-ctp-surface0 rounded-md m-0.5 text-xs font-bold text-white"
|
||||
>{{ tag }}</span
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
69
src/views/PreferencesView.vue
Normal file
69
src/views/PreferencesView.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center w-full">
|
||||
<div class="flex flex-col w-[30rem] bg-ctp-mantle rounded-lg text-white p-5">
|
||||
<ul class="overflow-y-auto inherit p-2 space-y-1">
|
||||
<li>
|
||||
<p class="font-bold text-2xl">Appearance</p>
|
||||
</li>
|
||||
<hr class="bg-gray-500 border-0 h-px" />
|
||||
<li class="rounded-lg my-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="checkbox" class="text-lg">Compact View</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="w-5 h-5 mt-0.5 rounded focus:ring-ctp-pink bg-ctp-surface0 border-gray-700 checked:bg-ctp-pink checked:border-blue-500 focus:ring-offset-gray-800"
|
||||
id="hs-default-checkbox"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li class="rounded-lg my-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="checkbox" class="text-lg">Compact View</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="w-5 h-5 mt-0.5 rounded focus:ring-ctp-pink bg-ctp-surface0 border-gray-700 checked:bg-ctp-pink checked:border-blue-500 focus:ring-offset-gray-800"
|
||||
id="hs-default-checkbox"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p class="font-bold text-2xl mt-5">Content</p>
|
||||
</li>
|
||||
<hr class="bg-gray-500 border-0 h-px" />
|
||||
<li class="rounded-lg my-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="checkbox" class="text-lg">Show mature streams</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="w-5 h-5 mt-0.5 rounded focus:ring-ctp-pink bg-ctp-surface0 border-gray-700 checked:bg-ctp-pink checked:border-blue-500 focus:ring-offset-gray-800"
|
||||
id="hs-default-checkbox"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li class="rounded-lg my-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="checkbox" class="text-lg">Show chat</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="w-5 h-5 mt-0.5 rounded focus:ring-ctp-pink bg-ctp-surface0 border-gray-700 checked:bg-ctp-pink checked:border-blue-500 focus:ring-offset-gray-800"
|
||||
id="hs-default-checkbox"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="p-2 mt-5 space-y-1">
|
||||
<p class="text-gray-400">
|
||||
<strong class="text-white">Note:</strong> Settings, follows, and any other persistent data
|
||||
is stored with cookies.
|
||||
</p>
|
||||
<button class="w-full bg-ctp-surface0 p-2 rounded-lg">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { StreamerData } from '../../../server/types/scraping/Streamer'
|
||||
import VideoPlayer from '../components/VideoPlayer.vue'
|
||||
import TwitchChat from '../components/TwitchChat.vue'
|
||||
import VideoPlayer from '@/components/VideoPlayer.vue'
|
||||
import TwitchChat from '@/components/TwitchChat.vue'
|
||||
import ErrorMessage from '@/components/ErrorMessage.vue'
|
||||
|
||||
export default {
|
||||
async setup() {
|
||||
@ -11,7 +11,7 @@ export default {
|
||||
const username = route.params.username
|
||||
|
||||
const getUser = async () => {
|
||||
const res = await fetch(`http://localhost:7000/api/users/${username}`)
|
||||
const res = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/users/${username}`)
|
||||
|
||||
if (res.status !== 200) {
|
||||
const data = await res.json()
|
||||
@ -28,8 +28,10 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
const data: StreamerData = await res.json()
|
||||
data.pfp = `http://localhost:7000/proxy/img?imageUrl=${encodeURIComponent(data.pfp)}`
|
||||
const data = await res.json()
|
||||
data.pfp = `${import.meta.env.VITE_BACKEND_URL}/proxy/img?imageUrl=${encodeURIComponent(
|
||||
data.pfp
|
||||
)}`
|
||||
return data
|
||||
}
|
||||
|
||||
@ -46,7 +48,7 @@ export default {
|
||||
controls: true,
|
||||
sources: [
|
||||
{
|
||||
src: `http://localhost:7000/proxy/stream/${username}/hls.m3u8`,
|
||||
src: `${import.meta.env.VITE_BACKEND_URL}/proxy/stream/${username}/hls.m3u8`,
|
||||
type: 'application/vnd.apple.mpegurl'
|
||||
}
|
||||
],
|
||||
@ -56,7 +58,8 @@ export default {
|
||||
},
|
||||
components: {
|
||||
VideoPlayer,
|
||||
TwitchChat
|
||||
TwitchChat,
|
||||
ErrorMessage
|
||||
},
|
||||
methods: {
|
||||
truncate(value: string, length: number) {
|
||||
@ -82,24 +85,7 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="data.status === 'error'"
|
||||
class="flex flex-col max-w-prose justify-center text-center mx-auto p-6 bg-ctp-crust rounded-lg text-white"
|
||||
>
|
||||
<div class="mb-6">
|
||||
<h1 class="font-bold text-5xl">oops...</h1>
|
||||
<p class="font-bold text-3xl">this wasn't supposed to happen</p>
|
||||
</div>
|
||||
|
||||
<p class="text-xl">
|
||||
the server was encountered an error while retriving the data, and now we're here :3
|
||||
</p>
|
||||
|
||||
<div class="mt-5">
|
||||
<p class="text-xl">please contact the administrator with this code</p>
|
||||
<p class="text-xl">error identifier: {{ data.code }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<error-message v-else-if="data.status === 'error'" :errMessage="data"></error-message>
|
||||
|
||||
<div v-else class="w-full justify-center inline-flex space-x-4 p-4">
|
||||
<div class="flex bg-ctp-crust flex-col p-6 rounded-lg max-w-prose min-w-[65ch] text-white">
|
||||
@ -157,7 +143,7 @@ export default {
|
||||
</div>
|
||||
|
||||
<div class="pt-2 pl- inline-flex">
|
||||
<button class="text-white text-sm font-bold p-2 py-1 rounded-md bg-purple-600">
|
||||
<button class="text-white text-sm font-bold p-2 py-1 rounded-md bg-purple-600">
|
||||
<v-icon name="bi-heart-fill" scale="0.85"></v-icon>
|
||||
Follow
|
||||
</button>
|
||||
|
@ -9,8 +9,9 @@ module.exports = {
|
||||
plugins: [
|
||||
require('@catppuccin/tailwindcss')({
|
||||
prefix: 'ctp',
|
||||
defaultFlavour: 'mocha',
|
||||
defaultFlavour: 'mocha'
|
||||
}),
|
||||
require('@tailwindcss/typography')
|
||||
require('@tailwindcss/typography'),
|
||||
require("@tailwindcss/forms")
|
||||
],
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user