mirror of
https://codeberg.org/SafeTwitch/safetwitch.git
synced 2024-12-22 05:12:57 -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:
commit
f274ba527d
6 changed files with 154 additions and 90 deletions
|
@ -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];
|
||||||
|
}
|
28
src/components/ActionButtons.vue
Normal file
28
src/components/ActionButtons.vue
Normal 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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue