import React, { useState } from "react"; import he from 'he'; import { useTranslation } from "react-i18next"; import { makeStyles, withStyles, createMuiTheme, ThemeProvider } from '@material-ui/core/styles'; import useMediaQuery from '@material-ui/core/useMediaQuery'; import Autocomplete from '@material-ui/lab/Autocomplete'; import TextField from '@material-ui/core/TextField'; import red from '@material-ui/core/colors/red'; import CssBaseline from '@material-ui/core/CssBaseline'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; import Checkbox from '@material-ui/core/Checkbox'; import FormGroup from '@material-ui/core/FormGroup'; import FormControlLabel from '@material-ui/core/FormControlLabel'; import IconButton from '@material-ui/core/IconButton'; import MenuIcon from '@material-ui/icons/Menu'; import MenuOpenIcon from '@material-ui/icons/MenuOpen'; import Avatar from '@material-ui/core/Avatar'; import Collapse from '@material-ui/core/Collapse'; import Card from '@material-ui/core/Card'; import CardHeader from '@material-ui/core/CardHeader'; import CardActionArea from '@material-ui/core/CardActionArea'; import CardMedia from '@material-ui/core/CardMedia'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import GridList from '@material-ui/core/GridList'; import GridListTile from '@material-ui/core/GridListTile'; import ShoppingCartIcon from '@material-ui/icons/ShoppingCart'; import SearchIcon from '@material-ui/icons/Search'; import Badge from '@material-ui/core/Badge'; import InputBase from '@material-ui/core/InputBase'; import BottomNavigation from '@material-ui/core/BottomNavigation'; import BottomNavigationAction from '@material-ui/core/BottomNavigationAction'; import Skeleton from '@material-ui/lab/Skeleton'; import Paper from '@material-ui/core/Paper'; import Divider from '@material-ui/core/Divider'; import Chip from '@material-ui/core/Chip'; import Rating from '@material-ui/lab/Rating'; import AccountCircleIcon from '@material-ui/icons/AccountCircle'; import StoreIcon from '@material-ui/icons/Store'; import LibraryBooksIcon from '@material-ui/icons/LibraryBooks'; import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; import NavigateNextIcon from '@material-ui/icons/NavigateNext'; import NavigateBeforeIcon from '@material-ui/icons/NavigateBefore'; import Grow from '@material-ui/core/Grow'; import Link from '@material-ui/core/Link'; import Breadcrumbs from '@material-ui/core/Breadcrumbs'; import Snackbar from '@material-ui/core/Snackbar'; import LazyLoad from 'react-lazyload'; import { CarouselProvider, Slider, Slide, ButtonBack, ButtonNext } from 'pure-react-carousel'; import useTimeout from 'use-timeout' import { GlobalState, useGlobalState, useWindowDimensions } from './utils.js'; import 'pure-react-carousel/dist/react-carousel.es.css'; import 'fontsource-roboto'; import { BrowserRouter as Router, Switch, Route, Redirect, Link as RouteLink, useLocation } from "react-router-dom"; const StyledBadge = withStyles((theme) => ({ badge: { right: -3, top: 13, border: `2px solid ${theme.palette.background.paper}`, padding: '0 4px', }, }))(Badge); const global = { store: { data: new GlobalState({data:[], loaded:false}), location: '/store' }, library: { data: new GlobalState({data:[], loaded:false}), location: '/library' }, account: { data: new GlobalState({data:[], loaded:false}), location: '/account' }, status: new GlobalState(null), } function StoreItemDetails(props) { const { t } = useTranslation(); const { height, width } = useWindowDimensions(); const cols = Math.floor(width / 240); const classes = makeStyles((theme) => ({ root: { paddingLeft: cols > 1 ? 18 : 4, paddingRight: cols > 1 ? 18 : 4 }, controls: { position: 'relative', width: '100%', maxWidth: (cols > 4 ? 640 : '100%') }, prev: { position: 'absolute', left: cols > 1 ? -15 : -4, top: '50%', backgroundColor: 'rgba(0, 0, 0, 0.5)', color: 'white' }, next: { position: 'absolute', right: cols > 1 ? -15 : -4, top: '50%', backgroundColor: 'rgba(0, 0, 0, 0.5)', color: 'white' }, img: { width: '100%', height: (height > 1024 ? 480 : 240), objectFit: 'contain', backgroundImage: 'url(/loading.png)', backgroundRepeat: 'no-repeat', backgroundPosition: 'center' } }))(); return (
{t('Store')} author id
{t('Circle')}: Some Circle {t('Release')}: 2020年10月24日 {t('Format')}: WAV / mp3同梱 {t('Size')}: 6.09GB 1 ? 260 : '100%')}}> セール特価: 1500円 ポイント: 200
大廃線により鉄路のほとんどが失われてしまった国――日ノ本。 それでも各地に、大廃線に抗い続ける鉄道事業者はぽつぽつと点在している。、 そうした鉄道各社・各路線の沿線の魅力を、音という切り口から紹介しようという企画、「蓄音レヱル」が、とある大新聞社の主催によって立ち上げられた。 音を探し出して紹介する大役を担うことになるのは、普段はその路線を走る列車の運転制御をサポートしている人形/人形型のモジュール、“レイルロオド”たち。 八ツ城から颯馬仙岱まで、隈元-鹿兒島両県の八ツ城海沿いを縫うように走る第三セクター鉄道事業者、肥薩みかん鉄道を走る紅にも、その依頼が届く。 「みかん鉄道沿線のいい音がする場所を教えたら――それを全国紙の記事で紹介してもらえるの!? 得しかないじゃん! ぼく、やるよ!」 旧南颯鉄道――いまは滅びてしまった鉄道事業者の最後の一両の動態機として、解体・廃棄された妹たちとの思い出を胸に、紅は相棒であるあなたの手をとり、音探しの旅へと駆け出します。 颯馬仙岱の大名物であるぴんこ団子を焼くを聞き、出深(いずみ)の飛来地に群れなすツルたちの鳴き声に耳をそばだて、牛之濱の潮溜まりで波と戯れ、日凪久の足湯でくつろいで―― 紅とふたりで尋ねる沿線の音たちは、きっとあなたの鼓膜を、こころをここちよく揺すり、やわらかにほぐしてくれるでしょう。 まっすぐで純情な紅との音探しの旅。 どうぞ、そのお耳を澄ませてお楽しみください。 なし
); } function StoreItemSkeleton(props) { return (
); } function StoreItem(props) { const { t } = useTranslation(); const classes = makeStyles((theme) => ({ media: { height: 180, position: 'relative' }, cat: { position: 'absolute', bottom: 4, left: 4 }, price: { position: 'absolute', bottom: 4, right: 4 }, rating: { position: 'absolute', top: 4, right: 4, backgroundColor: 'rgba(0, 0, 0, 0.8)', padding: 4, borderRadius: 4, }, content: { paddingLeft: 8 }, text: { textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap', }, avatar: { backgroundColor: red[500] } }))(); return ( }> {props.data.author.substring(0, 2)} } title={props.data.title} titleTypographyProps={{className: classes.text}} subheader={props.data.author} subheaderTypographyProps={{className: classes.text}} />
); } function StoreSeparator(props) { const { t } = useTranslation(); const classes = makeStyles((theme) => ({ root: { margin: 4 }, }))(); return (
{t(props.title)}
); } function StoreCarousel(props) { const { height, width } = useWindowDimensions(); const cols = Math.floor(width / 240); const tw = (cols > 1 ? (cols * 240 > width - 18 ? 240 - 20 : 240) : '100%') const classes = makeStyles((theme) => ({ root: { paddingLeft: cols > 1 ? 18 : 4, paddingRight: cols > 1 ? 18 : 4 }, tile: { width: tw, marginRight: 'auto', marginLeft: 'auto' }, controls: { position: 'relative' }, prev: { position: 'absolute', left: cols > 1 ? -15 : -4, top: '50%', backgroundColor: 'rgba(0, 0, 0, 0.5)', color: 'white' }, next: { position: 'absolute', right: cols > 1 ? -15 : -4, top: '50%', backgroundColor: 'rgba(0, 0, 0, 0.5)', color: 'white' }, }))(); var skeletons = []; for (var i = 0; props.loading && i < cols; i += 1) skeletons.push({}); return (
{props.data.map((item, index) => )} {skeletons.length ? skeletons.map((item, index) => ) : null}
); } function StoreList(props) { const { height, width } = useWindowDimensions(); const cols = Math.floor(width / 240); const tw = (cols > 1 ? (cols * 240 > width - 18 ? 240 - 20 : 240) : '100%') const classes = makeStyles((theme) => ({ list: { justifyContent: 'space-evenly', maxWidth: '100%' }, tile: { width: tw, marginRight: 'auto', marginLeft: 'auto' }, }))(); var extra = []; for (var i = 0; i < cols - (props.data.length % cols); i += 1) extra.push({key: i}); var skeletons = []; for (var i2 = 0; props.loading && i2 < Math.max(cols * (Math.floor(height / 240)) - cols * 2, 1); i2 += 1) skeletons.push({key: i2}); return (
1 ? 18 : 4, paddingRight: cols > 1 ? 18 : 4 }}> {props.data.map(item => )} {extra.map(item => )} {skeletons.map(item => )}
); } function StoreView(props) { const [state, setState] = useGlobalState(global.store.data); const [, setStatus] = useGlobalState(global.status); useTimeout(() => { async function fetchData() { function getRandomInt(max) { return Math.floor(Math.random() * Math.floor(max)); } try { var result = await fetch('https://cors-anywhere.herokuapp.com/https://www.eisys-bcs.jp/data.json?key[]=dlsite-doujin_home_center2').then(result => result.json()); result = result.data.['dlsite-doujin_home_center2'].banners.map(item => { return { id: 'no-id', author: he.decode(item.title), title: he.decode(item.title), shortdesc: 'foobar', desc: 'foobar', img: 'https:' + item.ssl_path, cat: 'Misc', price: getRandomInt(3000) + '円', rating: getRandomInt(5), }}) } catch (err) { result = []; setStatus(err); } setState({data:result, loaded:true}); } fetchData(); }, 600); return ( <> ); } function LibraryView(props) { const [state, setState] = useGlobalState(global.library.data); const [, setStatus] = useGlobalState(global.status); useTimeout(() => { async function fetchData() { function getRandomInt(max) { return Math.floor(Math.random() * Math.floor(max)); } try { var result = await fetch('https://cors-anywhere.herokuapp.com/https://www.eisys-bcs.jp/data.json?key[]=dlsite-doujin_home_center2').then(result => result.json()); result = result.data.['dlsite-doujin_home_center2'].banners.map(item => { return { id: 'no-id', author: he.decode(item.title), title: he.decode(item.title), shortdesc: 'foobar', desc: 'foobar', img: 'https:' + item.ssl_path, cat: 'Misc', price: getRandomInt(3000) + '円', rating: getRandomInt(5), }}) } catch (err) { result = []; setStatus(err); } setState({data:result, loaded:true}); } fetchData(); }, 600); return ( <> ); } function AccountView(props) { return <>; } function ItemSearch(props) { const { t } = useTranslation(); const classes = makeStyles((theme) => ({ search: { padding: '2px 4px', display: 'flex', alignItems: 'center', }, input: { marginLeft: theme.spacing(1), flex: 1, }, iconButton: { padding: 10, }, }))(); const categories = ['Manga', 'CG', 'Audio', 'Video', 'Game'] const audience = [{key: 'All ages', value: true}, {key: 'Heterosexual', value: false}, {key: 'Homosexual', value: false}] const keywords = [ t('RPG'), t('Simulation'), t('Stragety'), t('Action'), t('ASMR'), t('Anime'), t('3D'), t('2D'), t('Pixel art'), t('Live action'), t('Metal'), t('Pop'), t('Rock'), ]; const [menuOpen, setMenuOpen] = useState(false); return ( <> setMenuOpen(!menuOpen)}> {menuOpen ? : } {t('Keywords')} option} defaultValue={[]} renderInput={(params) => } /> {t('Category')} {categories.map(cat => } key={cat} label={t(cat)}/>)} {t('Audience')} {audience.map(cat => } key={cat.key} label={t(cat.key)}/>)} ); } function BottomBar(props) { var location = useLocation(); const components = location.pathname.split('/'); switch (components[1]) { case 'store': case 'library': case 'account': global[components[1]].location = location; } const { t } = useTranslation(); const classes = makeStyles((theme) => ({ stickyBottom: { position: 'fixed', width: '100%', bottom: 0, }, }))(); return ( } component={RouteLink} to={global.store.location}/> } component={RouteLink} to={global.library.location}/> } component={RouteLink} to={global.account.location}/> ); } function TopBar(props) { var location = useLocation(); const components = location.pathname.split('/'); const { t } = useTranslation(); const classes = makeStyles((theme) => ({ title: { flexGrow: 1 }, backButton: { marginRight: theme.spacing(1) }, }))(); const search = (components[1] !== 'account'); return ( 2} unmountOnExit> window.history.back()}> {t('DoujinSea')} ); } function Routing() { const classes = makeStyles((theme) => ({ content: { paddingTop: 105, paddingBottom: 65, minHeight: '100vh', }, }))(); const [status, setStatus] = useGlobalState(global.status); return ( <> setStatus(null)} autoHideDuration={6000} anchorOrigin={{vertical: 'top', horizontal: 'center'}} open={status !== null} message={status ? status : ''} key={status} /> ); } export default function Main() { const prefersDarkMode = false; // useMediaQuery('(prefers-color-scheme: dark)'); const theme = React.useMemo(() => createMuiTheme({ palette: { type: prefersDarkMode ? 'dark' : 'light', primary: red, }, }), [prefersDarkMode]); return ( <> ); }