From bcbda6940a55d7d258c7ed9c5f3ae8d32f59ea47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=93=E3=81=91=E3=81=A3=E3=81=A1?= <50144466+sim1222@users.noreply.github.com> Date: Thu, 22 Sep 2022 08:20:31 +0900 Subject: [PATCH] feat: Youtube window player (#9095) * wip: feat: Youtube Player Window * fix: player fill window * fix: improve design * fix: disable at mobile and creanup code * fix: tailing comma * fix: delete debug output * fix: eslint * fix: switch to component * fix(backend): add missing dependency Fix #9101 Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> --- .../server/api/endpoints/admin/show-users.ts | 3 + .../client/src/components/MkUrlPreview.vue | 15 +++- .../client/src/components/MkYoutubePlayer.vue | 72 +++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/components/MkYoutubePlayer.vue diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index d28f9c71b7..5471ef9b4c 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type { UsersRepository } from '@/models/index.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; export const meta = { tags: ['admin'], @@ -45,6 +46,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { constructor( @Inject(DI.usersRepository) private usersRepository: UsersRepository, + + private userEntityService: UserEntityService, ) { super(meta, paramDef, async (ps, me) => { const query = this.usersRepository.createQueryBuilder('user'); diff --git a/packages/client/src/components/MkUrlPreview.vue b/packages/client/src/components/MkUrlPreview.vue index 9b2a785351..af27f644ed 100644 --- a/packages/client/src/components/MkUrlPreview.vue +++ b/packages/client/src/components/MkUrlPreview.vue @@ -10,7 +10,7 @@ <transition :name="$store.state.animation ? 'zoom' : ''" mode="out-in"> <component :is="self ? 'MkA' : 'a'" v-if="!fetching" class="link" :class="{ compact }" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> <div v-if="thumbnail" class="thumbnail" :style="`background-image: url('${thumbnail}')`"> - <button v-if="!playerEnabled && player.url" class="_button" :title="i18n.ts.enablePlayer" @click.prevent="playerEnabled = true"><i class="fas fa-play-circle"></i></button> + <button v-if="!playerEnabled && player.url" class="_button" :title="i18n.ts.enablePlayer" @click.prevent="isMobile? playerEnabled = true : openPlayer()"><i class="fas fa-play-circle"></i></button> </div> <article> <header> @@ -33,9 +33,11 @@ </template> <script lang="ts" setup> -import { onMounted, onUnmounted } from 'vue'; +import { defineAsyncComponent, onMounted, onUnmounted } from 'vue'; import { url as local, lang } from '@/config'; import { i18n } from '@/i18n'; +import * as os from '@/os'; +import { deviceKind } from '@/scripts/device-kind'; const props = withDefaults(defineProps<{ url: string; @@ -46,6 +48,9 @@ const props = withDefaults(defineProps<{ compact: false, }); +const MOBILE_THRESHOLD = 500; +const isMobile = $ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD); + const self = props.url.startsWith(local); const attr = self ? 'to' : 'href'; const target = self ? null : '_blank'; @@ -103,6 +108,12 @@ function adjustTweetHeight(message: any) { if (height) tweetHeight = height; } +const openPlayer = (): void => { + os.popup(defineAsyncComponent(() => import('@/components/MkYoutubePlayer.vue')), { + url: requestUrl.href, + }); +}; + (window as any).addEventListener('message', adjustTweetHeight); onUnmounted(() => { diff --git a/packages/client/src/components/MkYoutubePlayer.vue b/packages/client/src/components/MkYoutubePlayer.vue new file mode 100644 index 0000000000..0d8eb75b87 --- /dev/null +++ b/packages/client/src/components/MkYoutubePlayer.vue @@ -0,0 +1,72 @@ +<template> +<XWindow :initial-width="640" :initial-height="402" :can-resize="true" :close-button="true"> + <template #header> + <i class="icon fa-brands fa-youtube" style="margin-right: 0.5em;"></i> + <span>{{ title ?? 'Youtube Player' }}</span> + </template> + + <div class="poamfof"> + <transition :name="$store.state.animation ? 'fade' : ''" mode="out-in"> + <div v-if="player.url" class="player"> + <iframe v-if="!fetching" :src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen/> + </div> + </transition> + <MkLoading v-if="fetching"/> + <MkError v-else-if="!player.url" @retry="ytFetch()"/> + </div> +</XWindow> +</template> + +<script lang="ts" setup> +import XWindow from '@/components/MkWindow.vue'; +import { lang } from '@/config'; + +const props = defineProps<{ + url: string; +}>(); + +const requestUrl = new URL(props.url); + +let fetching = $ref(true); +let title = $ref<string | null>(null); +let player = $ref({ + url: null, + width: null, + height: null, +}); + +const requestLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP'); + +const ytFetch = (): void => { + fetching = true; + fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).then(res => { + res.json().then(info => { + if (info.url == null) return; + title = info.title; + fetching = false; + player = info.player; + }); + }); +}; + +ytFetch(); + +</script> + +<style lang="scss"> +.poamfof { + position: relative; + overflow: hidden; + height: 100%; + + .player { + position: absolute; + inset: 0; + + iframe { + width: 100%; + height: 100%; + } + } +} +</style>