0
Fork 0
mirror of https://codeberg.org/SafeTwitch/safetwitch.git synced 2024-12-21 21:03:00 -05:00

Merge pull request 'Theatre Mode' (#129) from Splashy5/safetwitch:master into master

Reviewed-on: https://codeberg.org/SafeTwitch/safetwitch/pulls/129
Reviewed-by: dragongoose <dragongoose@noreply.codeberg.org>
This commit is contained in:
dragongoose 2024-08-17 19:31:24 +00:00
commit f274ba527d
6 changed files with 154 additions and 90 deletions

View file

@ -1,3 +1,15 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
.content-container {
@apply flex bg-crust flex-col p-6 rounded-lg w-full text-contrast;
}
.content-container-theatre {
@apply md:w-[62vw] lg:w-[75vw] xl:w-[75vw] 2xl:w-[68vw];
}
.content-container-normal {
@apply md:w-[70vw] lg:w-[70vw] xl:w-[60vw] 2xl:w-[50vw] max-w-[1200px];
}

View file

@ -0,0 +1,28 @@
<template>
<div class="space-x-1">
<a v-if="showDownload" :href="downloadUrl" download>
<button class="px-2 py-1.5 rounded-lg bg-purple">
<v-icon name="md-download-round"></v-icon>
</button>
</a>
<button v-if="showTheatreMode" @click="$emit('toggleTheatreMode')" class="hidden xl:inline px-2 py-1.5 rounded-lg bg-purple">
<v-icon name="fa-expand"></v-icon>
</button>
<button v-if="showShare" @click="$emit('toggleShareModal')" class="px-2 py-1.5 rounded-lg bg-purple">
<v-icon name="fa-share-alt"></v-icon>
</button>
</div>
</template>
<script lang="ts">
export default {
props: {
showDownload: Boolean,
showTheatreMode: Boolean,
showShare: Boolean,
downloadUrl: String
},
emits: ['toggleTheatreMode', 'toggleShareModal']
}
</script>

View file

@ -36,7 +36,8 @@ import {
FaShareAlt, FaShareAlt,
IoCloseSharp, IoCloseSharp,
MdDownloadRound, MdDownloadRound,
IoPerson IoPerson,
FaExpand
} from 'oh-vue-icons/icons' } from 'oh-vue-icons/icons'
addIcons( addIcons(
@ -55,7 +56,8 @@ addIcons(
FaShareAlt, FaShareAlt,
IoCloseSharp, IoCloseSharp,
MdDownloadRound, MdDownloadRound,
IoPerson IoPerson,
FaExpand
) )
app.component('v-icon', OhVueIcon) app.component('v-icon', OhVueIcon)

View file

@ -9,11 +9,11 @@ import LoadingScreen from '@/components/LoadingScreen.vue'
import AboutTab from '@/components/user/AboutTab.vue' import AboutTab from '@/components/user/AboutTab.vue'
import ShareModal from '@/components/popups/ShareButtonModal.vue' import ShareModal from '@/components/popups/ShareButtonModal.vue'
import VueTitle from '@/components/VueTitle.vue' import VueTitle from '@/components/VueTitle.vue'
import ActionButtons from '@/components/ActionButtons.vue'
import type { Video } from '@/types' import type { Video } from '@/types'
import { truncate, abbreviate, getEndpoint } from '@/mixins' import { truncate, abbreviate, getEndpoint } from '@/mixins'
export default { export default {
inject: ['rootBackendUrl'], inject: ['rootBackendUrl'],
async setup() { async setup() {
@ -21,6 +21,7 @@ export default {
const clipSlug = route.params.slug const clipSlug = route.params.slug
const data = ref<Video>() const data = ref<Video>()
const status = ref<'ok' | 'error'>() const status = ref<'ok' | 'error'>()
const isTheatreMode = ref(false)
let srcUrl let srcUrl
await getEndpoint(`api/clips/cliplink/${clipSlug}`) await getEndpoint(`api/clips/cliplink/${clipSlug}`)
@ -31,8 +32,6 @@ export default {
status.value = 'error' status.value = 'error'
}) })
console.log(srcUrl)
const videoOptions = { const videoOptions = {
autoplay: true, autoplay: true,
controls: true, controls: true,
@ -51,7 +50,8 @@ export default {
videoOptions, videoOptions,
time: ref(0), time: ref(0),
srcUrl, srcUrl,
shareModalVisible: ref(false) shareModalVisible: ref(false),
isTheatreMode
} }
}, },
async mounted() { async mounted() {
@ -72,13 +72,17 @@ export default {
LoadingScreen, LoadingScreen,
AboutTab, AboutTab,
ShareModal, ShareModal,
VueTitle VueTitle,
ActionButtons
}, },
methods: { methods: {
truncate, truncate,
abbreviate, abbreviate,
toggleShareModal() { toggleShareModal() {
this.shareModalVisible = !this.shareModalVisible this.shareModalVisible = !this.shareModalVisible
},
toggleTheatreMode() {
this.isTheatreMode = !this.isTheatreMode
} }
} }
} }
@ -95,7 +99,10 @@ export default {
> >
<VueTitle :title=" 'Clip - ' + data.title"></VueTitle> <VueTitle :title=" 'Clip - ' + data.title"></VueTitle>
<div <div
class="flex bg-crust flex-col p-6 rounded-lg w-[99vw] md:max-w-prose md:min-w-[65ch] lg:max-w-[70rem] text-contrast" :class="[
'content-container',
isTheatreMode ? 'content-container-theatre' : 'content-container-normal'
]"
> >
<div class="w-full mx-auto rounded-lg mb-5"> <div class="w-full mx-auto rounded-lg mb-5">
<video-player :options="videoOptions"> </video-player> <video-player :options="videoOptions"> </video-player>
@ -129,17 +136,14 @@ export default {
</p> </p>
</div> </div>
<div class="space-x-1"> <action-buttons
<a :href="srcUrl" download> :showDownload="true"
<button class="px-2 py-1.5 rounded-lg bg-purple"> :showTheatreMode="true"
<v-icon name="md-download-round"></v-icon> :showShare="true"
</button> @toggleTheatreMode="toggleTheatreMode"
</a> @toggleShareModal="toggleShareModal"
:downloadUrl="srcUrl"
<button @click="toggleShareModal" class="px-2 py-1.5 rounded-lg bg-purple"> />
<v-icon name="fa-share-alt"></v-icon>
</button>
</div>
</div> </div>
</div> </div>
@ -147,4 +151,4 @@ export default {
<about-tab :socials="data.streamer.socials" :about="data.streamer.about"></about-tab> <about-tab :socials="data.streamer.socials" :about="data.streamer.about"></about-tab>
</div> </div>
</div> </div>
</template> </template>

View file

@ -12,6 +12,7 @@ import AudioPlayer from '@/components/AudioPlayer.vue'
import AboutTab from '@/components/user/AboutTab.vue' import AboutTab from '@/components/user/AboutTab.vue'
import ShareModal from '@/components/popups/ShareButtonModal.vue' import ShareModal from '@/components/popups/ShareButtonModal.vue'
import VueTitle from '@/components/VueTitle.vue' import VueTitle from '@/components/VueTitle.vue'
import ActionButtons from '@/components/ActionButtons.vue'
import type { StreamerData } from '@/types' import type { StreamerData } from '@/types'
import { truncate, abbreviate, getEndpoint } from '@/mixins' import { truncate, abbreviate, getEndpoint } from '@/mixins'
@ -25,6 +26,7 @@ export default {
const data = ref<StreamerData>() const data = ref<StreamerData>()
const status = ref<'ok' | 'error'>() const status = ref<'ok' | 'error'>()
const rootBackendUrl = inject('rootBackendUrl') const rootBackendUrl = inject('rootBackendUrl')
const isTheatreMode = ref(false)
const videoOptions = { const videoOptions = {
autoplay: getSetting('autoplay'), autoplay: getSetting('autoplay'),
controls: true, controls: true,
@ -43,7 +45,8 @@ export default {
status, status,
videoOptions, videoOptions,
audioOptions, audioOptions,
shareModalVisible: ref(false) shareModalVisible: ref(false),
isTheatreMode
} }
}, },
async mounted() { async mounted() {
@ -73,7 +76,8 @@ export default {
AudioPlayer, AudioPlayer,
AboutTab, AboutTab,
ShareModal, ShareModal,
VueTitle VueTitle,
ActionButtons
}, },
methods: { methods: {
truncate, truncate,
@ -81,6 +85,9 @@ export default {
getSetting, getSetting,
toggleShareModal() { toggleShareModal() {
this.shareModalVisible = !this.shareModalVisible this.shareModalVisible = !this.shareModalVisible
},
toggleTheatreMode() {
this.isTheatreMode = !this.isTheatreMode
} }
} }
} }
@ -102,7 +109,10 @@ export default {
> >
<vue-title :title="data.username"></vue-title> <vue-title :title="data.username"></vue-title>
<div <div
class="flex bg-crust flex-col p-6 rounded-lg w-[99vw] md:max-w-prose md:min-w-[65ch] lg:max-w-[70rem] text-contrast" :class="[
'content-container',
isTheatreMode ? 'content-container-theatre' : 'content-container-normal'
]"
> >
<div v-if="data.isLive" class="w-full mx-auto rounded-lg mb-5"> <div v-if="data.isLive" class="w-full mx-auto rounded-lg mb-5">
<video-player v-if="Boolean($route.query['audio-only']) === false" :options="videoOptions"> <video-player v-if="Boolean($route.query['audio-only']) === false" :options="videoOptions">
@ -118,73 +128,67 @@ export default {
/> />
<div class="w-full flex-wrap md:p-3"> <div class="w-full flex-wrap md:p-3">
<div class="inline-flex md:w-4/5"> <div class="flex flex-col md:flex-row justify-between">
<div class="w-20 h-20 relative"> <div class="md:w-3/5">
<img <div class="inline-flex">
:src="data.pfp" <div class="w-20 h-20 relative">
class="rounded-full border-4 p-0.5 w-auto h-20" <img
:style="`border-color: ${data.colorHex};`" :src="data.pfp"
/> class="rounded-full border-4 p-0.5 w-auto h-20"
:style="`border-color: ${data.colorHex};`"
<span />
v-if="data.isLive" <span
class="absolute flex left-1/2 translate-x-[-50%] whitespace-nowrap uppercase top-16 bg-red font-bold text-sm p-1.5 py-0.5 rounded-md" v-if="data.isLive"
>{{ $t('main.live') }}</span class="absolute flex left-1/2 translate-x-[-50%] whitespace-nowrap uppercase top-16 bg-red font-bold text-sm p-1.5 py-0.5 rounded-md"
> >{{ $t('main.live') }}</span>
</div> </div>
<div class="ml-3 content-between">
<div class="ml-3 content-between"> <div>
<div class=""> <h1 class="text-2xl md:text-4xl font-bold inline-block">{{ data.username }}</h1>
<h1 class="text-2xl md:text-4xl font-bold inline-block">{{ data.username }}</h1> <a v-if="$route.query['audio-only'] !== 'true'" href="?audio-only=true">
<a v-if="$route.query['audio-only'] !== 'true'" href="?audio-only=true"> <v-icon name="bi-headphones" class="ml-1 w-8 h-8 inline-block"></v-icon>
<v-icon name="bi-headphones" class="ml-1 w-8 h-8 inline-block"></v-icon> </a>
</a> <a v-else :href="$route.params.username.toString()">
<a v-else :href="$route.params.username.toString()"> <v-icon name="bi-camera-video-fill" class="ml-1 w-8 h-8 inline-block"></v-icon>
<v-icon name="bi-camera-video-fill" class="ml-1 w-8 h-8 inline-block"></v-icon> </a>
</a> </div>
<div v-if="data.stream" class="w-full md:w-[17rem]">
<p class="text-sm font-bold text-neutral self-end">
{{ truncate(data.stream.title, 130) }}
</p>
</div>
</div>
</div> </div>
<div v-if="data.stream" class="w-[14rem] md:w-[17rem]"> </div>
<p class="text-sm font-bold text-neutral self-end"> <div class="md:w-2/5 mt-4 md:mt-0">
{{ truncate(data.stream.title, 130) }} <div v-if="!data.isLive" class="w-full">
<p class="font-bold bg-overlay0 p-3 py-2 rounded-lg w-min float-right border-2 border-red">
OFFLINE
</p> </p>
</div> </div>
<div v-else class="w-full">
<ul class="flex flex-wrap justify-end gap-2 text-xs font-bold" v-if="getSetting('streamTagsVisible')">
<li v-for="tag in data.stream!.tags" :key="tag" class="inline-flex bg-overlay0 p-1.5 px-2 rounded-md">
{{ tag }}
</li>
</ul>
</div>
</div> </div>
</div> </div>
<div class="flex justify-between items-center mt-4">
<div class="flex-col md:inline-flex md:w-1/5 float-right h-full text-right"> <div class="pt-2 inline-flex">
<div v-if="!data.isLive" class="w-full"> <follow-button :username="data.login"></follow-button>
<p <p class="align-baseline font-bold ml-3">
class="font-bold bg-overlay0 p-3 py-2 rounded-lg w-min float-right border-2 border-red" {{ abbreviate(data.followers) }} {{ $t('main.followers') }}
>
OFFLINE
</p> </p>
</div> </div>
<div v-else class="w-full"> <action-buttons
<ul :showDownload="false"
class="text-xs font-bold text-left md:text-right space-x-1 space-y-1 overflow-y-auto" :showTheatreMode="true"
v-if="getSetting('streamTagsVisible')" :showShare="true"
> @toggleTheatreMode="toggleTheatreMode"
<li @toggleShareModal="toggleShareModal"
v-for="tag in data.stream!.tags" />
:key="tag"
class="inline-flex bg-overlay0 p-1.5 px-2 rounded-md"
>
{{ tag }}
</li>
</ul>
</div>
</div>
<div class="pt-2 space-x-2 items-center inline-flex">
<follow-button :username="data.login"></follow-button>
<p class="align-baseline font-bold ml-3">
{{ abbreviate(data.followers) }} {{ $t('main.followers') }}
</p>
<button @click="toggleShareModal" class="px-2 py-1.5 rounded-lg bg-purple">
<v-icon name="fa-share-alt"></v-icon>
</button>
</div> </div>
</div> </div>
@ -203,3 +207,4 @@ export default {
></twitch-chat> ></twitch-chat>
</div> </div>
</template> </template>

View file

@ -9,11 +9,12 @@ import FollowButton from '@/components/FollowButton.vue'
import LoadingScreen from '@/components/LoadingScreen.vue' import LoadingScreen from '@/components/LoadingScreen.vue'
import AboutTab from '@/components/user/AboutTab.vue' import AboutTab from '@/components/user/AboutTab.vue'
import ShareModal from '@/components/popups/ShareButtonModal.vue' import ShareModal from '@/components/popups/ShareButtonModal.vue'
import VueTitle from '@/components/VueTitle.vue'
import ActionButtons from '@/components/ActionButtons.vue'
import type { Video } from '@/types' import type { Video } from '@/types'
import { truncate, abbreviate, getEndpoint } from '@/mixins' import { truncate, abbreviate, getEndpoint } from '@/mixins'
import { getSetting } from '@/settingsManager' import { getSetting } from '@/settingsManager'
import VueTitle from '@/components/VueTitle.vue'
interface ChatComponent { interface ChatComponent {
updateVodComments: (time: number) => void updateVodComments: (time: number) => void
@ -41,7 +42,8 @@ export default {
status: ref<'ok' | 'error'>(), status: ref<'ok' | 'error'>(),
videoOptions, videoOptions,
time: ref(0), time: ref(0),
shareModalVisible: ref(false) shareModalVisible: ref(false),
isTheatreMode: ref(false)
} }
}, },
async mounted() { async mounted() {
@ -63,7 +65,8 @@ export default {
LoadingScreen, LoadingScreen,
AboutTab, AboutTab,
ShareModal, ShareModal,
VueTitle VueTitle,
ActionButtons
}, },
methods: { methods: {
truncate, truncate,
@ -77,6 +80,9 @@ export default {
getSetting, getSetting,
toggleShareModal() { toggleShareModal() {
this.shareModalVisible = !this.shareModalVisible this.shareModalVisible = !this.shareModalVisible
},
toggleTheatreMode() {
this.isTheatreMode = !this.isTheatreMode
} }
} }
} }
@ -98,7 +104,10 @@ export default {
> >
<vue-title :title="'VOD - ' + data.title"></vue-title> <vue-title :title="'VOD - ' + data.title"></vue-title>
<div <div
class="flex bg-crust flex-col p-6 rounded-lg w-[99vw] md:max-w-prose md:min-w-[65ch] lg:max-w-[70rem] text-contrast" :class="[
'content-container',
isTheatreMode ? 'content-container-theatre' : 'content-container-normal'
]"
> >
<div class="w-full mx-auto rounded-lg mb-5"> <div class="w-full mx-auto rounded-lg mb-5">
<video-player :options="videoOptions" @PlayerTimeUpdate="handlePlayerTimeUpdate"> <video-player :options="videoOptions" @PlayerTimeUpdate="handlePlayerTimeUpdate">
@ -133,9 +142,13 @@ export default {
</p> </p>
</div> </div>
<button @click="toggleShareModal" class="px-2 py-1.5 rounded-lg bg-purple"> <action-buttons
<v-icon name="fa-share-alt"></v-icon> :showDownload="false"
</button> :showTheatreMode="true"
:showShare="true"
@toggleTheatreMode="toggleTheatreMode"
@toggleShareModal="toggleShareModal"
/>
</div> </div>
</div> </div>