#8 とりあへずは完了
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
import React, { useRef, useLayoutEffect, useEffect, useState, CSSProperties } from 'react'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { Link, useNavigate, useLocation } from 'react-router-dom'
|
||||||
|
import { API_BASE_URL } from '../config'
|
||||||
|
|
||||||
|
type Props = { id: string,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
style?: CSSProperties }
|
||||||
|
|
||||||
|
|
||||||
|
const NicoViewer: React.FC = (props: Props) => {
|
||||||
|
const { id, width, height, style = { } } = props
|
||||||
|
|
||||||
|
const iframeRef = useRef<HTMLIFrameElement> (null)
|
||||||
|
const [screenWidth, setScreenWidth] = useState<CSSProperties['width']> ()
|
||||||
|
const [screenHeight, setScreenHeight] = useState<CSSProperties['height']> ()
|
||||||
|
const [isLandscape, setIsLandScape] = useState<boolean> (false)
|
||||||
|
const [isFullScreen, setIsFullScreen] = useState<boolean> (false)
|
||||||
|
|
||||||
|
const src = `https://embed.nicovideo.jp/watch/${id}?persistence=1&oldScript=1&referer=&from=0&allowProgrammaticFullScreen=1`;
|
||||||
|
|
||||||
|
const styleFullScreen: CSSProperties = isFullScreen ? {
|
||||||
|
top: 0,
|
||||||
|
left: isLandscape ? 0 : '100%',
|
||||||
|
position: 'fixed',
|
||||||
|
width: screenWidth,
|
||||||
|
height: screenHeight,
|
||||||
|
zIndex: 2147483647,
|
||||||
|
maxWidth: 'none',
|
||||||
|
transformOrigin: '0% 0%',
|
||||||
|
transform: isLandscape ? 'none' : 'rotate(90deg)',
|
||||||
|
WebkitTransformOrigin: '0% 0%',
|
||||||
|
WebkitTransform: isLandscape ? 'none' : 'rotate(90deg)',
|
||||||
|
} : {};
|
||||||
|
|
||||||
|
const margedStyle = {
|
||||||
|
border: 'none',
|
||||||
|
maxWidth: '100%',
|
||||||
|
...style,
|
||||||
|
...styleFullScreen,
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onMessage = (event: MessageEvent<any>) => {
|
||||||
|
if (!iframeRef.current || event.source !== iframeRef.current.contentWindow) return;
|
||||||
|
if (event.data.eventName === 'enterProgrammaticFullScreen') {
|
||||||
|
setIsFullScreen(true);
|
||||||
|
} else if (event.data.eventName === 'exitProgrammaticFullScreen') {
|
||||||
|
setIsFullScreen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('message', onMessage);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('message', onMessage);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!isFullScreen) return;
|
||||||
|
|
||||||
|
const initialScrollX = window.scrollX;
|
||||||
|
const initialScrollY = window.scrollY;
|
||||||
|
let timer: NodeJS.Timeout;
|
||||||
|
let ended = false;
|
||||||
|
|
||||||
|
const pollingResize = () => {
|
||||||
|
if (ended) return;
|
||||||
|
|
||||||
|
const isLandscape = window.innerWidth >= window.innerHeight;
|
||||||
|
const windowWidth = `${isLandscape ? window.innerWidth : window.innerHeight}px`;
|
||||||
|
const windowHeight = `${isLandscape ? window.innerHeight : window.innerWidth}px`;
|
||||||
|
|
||||||
|
setIsLandScape(isLandscape);
|
||||||
|
setScreenWidth(windowWidth);
|
||||||
|
setScreenHeight(windowHeight);
|
||||||
|
timer = setTimeout(startPollingResize, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startPollingResize = () => {
|
||||||
|
if (window.requestAnimationFrame) {
|
||||||
|
window.requestAnimationFrame(pollingResize);
|
||||||
|
} else {
|
||||||
|
pollingResize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startPollingResize();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
ended = true;
|
||||||
|
window.scrollTo(initialScrollX, initialScrollY);
|
||||||
|
};
|
||||||
|
}, [isFullScreen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isFullScreen) return;
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, [screenWidth, screenHeight, isFullScreen]);
|
||||||
|
|
||||||
|
return <iframe ref={iframeRef}
|
||||||
|
src={src}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
style={margedStyle}
|
||||||
|
allowFullScreen
|
||||||
|
allow="autoplay" />
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default NicoViewer
|
||||||
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'
|
|||||||
import { Link, useLocation, useParams } from 'react-router-dom'
|
import { Link, useLocation, useParams } from 'react-router-dom'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { API_BASE_URL, SITE_TITLE } from '../config'
|
import { API_BASE_URL, SITE_TITLE } from '../config'
|
||||||
|
import NicoViewer from '../components/NicoViewer'
|
||||||
|
|
||||||
type Post = { id: number
|
type Post = { id: number
|
||||||
url: string
|
url: string
|
||||||
@@ -28,19 +29,17 @@ const PostDetailPage = () => {
|
|||||||
|
|
||||||
document.title = `${ post.url } | ${ SITE_TITLE }`
|
document.title = `${ post.url } | ${ SITE_TITLE }`
|
||||||
|
|
||||||
|
const url = new URL (post.url)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<h1 className="text-2xl font-bold mb-2">{post.url}</h1>
|
{url.hostname.split ('.').slice (-2).join ('.') === 'nicovideo.jp' ? (
|
||||||
<img src={post.thumbnail} alt={post.url} className="mb-4 max-w-lg" />
|
<NicoViewer id={url.pathname.match (/(?<=\/watch\/)[a-zA-Z0-9]+?(?=\/|$)/)[0]}
|
||||||
<p className="mb-2">{post.description}</p>
|
width="640"
|
||||||
<div className="text-sm text-gray-600">
|
height="360" />
|
||||||
タグ:
|
) : (
|
||||||
{post.tags.map(tag => (
|
<img src={post.thumbnail} alt={post.url} className="mb-4 w-full" />
|
||||||
<span key={tag.name} className="ml-2 px-2 py-1 bg-gray-200 rounded">
|
)}
|
||||||
#{tag.name}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user