From 1fd3c974e0b9c23c60a947a327c60075336e6b36 Mon Sep 17 00:00:00 2001 From: monoid Date: Tue, 5 Jan 2021 04:02:43 +0900 Subject: [PATCH] add gallery page --- package.json | 11 ++ src/client/accessor/contents.ts | 67 +++++++++ src/client/{js => accessor}/util.ts | 7 +- src/client/js/app.tsx | 21 ++- src/client/js/gallery.tsx | 150 ++++++++++++++++++++ src/client/js/headline.tsx | 206 ++++++++++++++++++++++++++++ src/client/js/hello.ts | 4 - src/client/js/test.tsx | 88 ------------ 8 files changed, 456 insertions(+), 98 deletions(-) create mode 100644 src/client/accessor/contents.ts rename src/client/{js => accessor}/util.ts (58%) create mode 100644 src/client/js/gallery.tsx create mode 100644 src/client/js/headline.tsx delete mode 100644 src/client/js/hello.ts delete mode 100644 src/client/js/test.tsx diff --git a/package.json b/package.json index cfea48e..c108138 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,15 @@ "start": "ts-node src/server.ts", "check-types": "tsc" }, + "browserslist":{ + "production":[ + "> 10%" + ], + "development":[ + "last 1 chrome version", + "last 1 firefox version" + ] + }, "author": "", "license": "ISC", "dependencies": { @@ -24,6 +33,7 @@ "node-stream-zip": "^1.12.0", "react": "^17.0.1", "react-dom": "^17.0.1", + "react-router-dom": "^5.2.0", "sqlite3": "^5.0.0", "ts-node": "^9.1.1" }, @@ -41,6 +51,7 @@ "@types/node": "^14.14.16", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", + "@types/react-router-dom": "^5.1.7", "babel-core": "^6.26.3", "babel-loader": "^8.2.2", "css-loader": "^5.0.1", diff --git a/src/client/accessor/contents.ts b/src/client/accessor/contents.ts new file mode 100644 index 0000000..790b13f --- /dev/null +++ b/src/client/accessor/contents.ts @@ -0,0 +1,67 @@ +import {Content, ContentAccessor, ContentContent, QueryListOption} from "../../model/contents"; +import {toQueryString} from './util'; +const baseurl = "/content"; + +export class ClientContentAccessesor implements ContentAccessor{ + async findList(option?: QueryListOption | undefined): Promise{ + let res = await fetch(`${baseurl}/search?${option !== undefined ? toQueryString(option) : ""}`); + //if(res.status !== 200); + let ret = await res.json(); + return ret; + } + async findById(id: number, tagload?: boolean | undefined): Promise{ + let res = await fetch(`${baseurl}/${id}`); + if(res.status !== 200) return undefined; + let ret = await res.json(); + return ret; + } + /** + * not implement + */ + async findListByBasePath(basepath: string): Promise{ + throw new Error(""); + return []; + } + async update(c: Partial & { id: number; }): Promise{ + const {id,...rest} = c; + const res = await fetch(`${baseurl}/${id}`,{ + method: "POST", + body: JSON.stringify(rest) + }); + const ret = await res.json(); + return ret; + } + async add(c: ContentContent): Promise{ + const res = await fetch(`${baseurl}`,{ + method: "POST", + body: JSON.stringify(c) + }); + const ret = await res.json(); + return ret; + } + async del(id: number): Promise{ + const res = await fetch(`${baseurl}/${id}`,{ + method: "DELETE" + }); + const ret = await res.json(); + return ret; + } + async addTag(c: Content, tag_name: string): Promise{ + const {id,...rest} = c; + const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`,{ + method: "POST", + body: JSON.stringify(rest) + }); + const ret = await res.json(); + return ret; + } + async delTag(c: Content, tag_name: string): Promise{ + const {id,...rest} = c; + const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`,{ + method: "DELETE", + body: JSON.stringify(rest) + }); + const ret = await res.json(); + return ret; + } +} \ No newline at end of file diff --git a/src/client/js/util.ts b/src/client/accessor/util.ts similarity index 58% rename from src/client/js/util.ts rename to src/client/accessor/util.ts index 57e3c6b..7f746b8 100644 --- a/src/client/js/util.ts +++ b/src/client/accessor/util.ts @@ -2,11 +2,14 @@ type Representable = string|number|boolean; type ToQueryStringA = { - [name:string]:Representable|Representable[] + [name:string]:Representable|Representable[]|undefined }; export const toQueryString = (obj:ToQueryStringA)=> { - return Object.entries(obj).map(e => + return Object.entries(obj) + .filter((e): e is [string,Representable|Representable[]] => + e[1] !== undefined) + .map(e => e[1] instanceof Array ? e[1].map(f=>`${e[0]}[]=${encodeURIComponent(f)}`).join('&') : `${e[0]}=${encodeURIComponent(e[1])}`) diff --git a/src/client/js/app.tsx b/src/client/js/app.tsx index 53cc3fa..c2334ab 100644 --- a/src/client/js/app.tsx +++ b/src/client/js/app.tsx @@ -1,11 +1,24 @@ -import hello from './hello' import React from 'react'; import ReactDom from 'react-dom'; -import {Headline} from './test'; +import {BrowserRouter, Route} from 'react-router-dom'; +import {Headline} from './headline'; +import {Gallery} from './gallery'; + import style from '../css/style.css'; -hello(); + + +const App = ()=> ( + + + + + +
test profile
+
+
+); ReactDom.render( - , + , document.getElementById("root") ) \ No newline at end of file diff --git a/src/client/js/gallery.tsx b/src/client/js/gallery.tsx new file mode 100644 index 0000000..2267d39 --- /dev/null +++ b/src/client/js/gallery.tsx @@ -0,0 +1,150 @@ +import { Box, CircularProgress, Typography, Paper, Chip, withStyles, colors } from '@material-ui/core'; +import {ChipProps} from '@material-ui/core/Chip'; +import React, { useEffect, useState } from 'react'; +import {QueryListOption,Content} from '../../model/contents'; +import {ClientContentAccessesor} from '../accessor/contents'; +import {useTheme, makeStyles, Theme, emphasize, fade} from '@material-ui/core/styles'; +import {blue , pink} from '@material-ui/core/colors'; + +type TagChipStyleProp = { + color: string +} + +const useTagStyles = makeStyles((theme:Theme)=>({ + root:(props:TagChipStyleProp)=>({ + color: theme.palette.getContrastText(props.color), + backgroundColor: props.color, + }), + clickable:(props:TagChipStyleProp)=>({ + '&:hover, &:focus':{ + backgroundColor:emphasize(props.color,0.08) + } + }), + deletable: { + '&:focus': { + backgroundColor: (props:TagChipStyleProp)=>emphasize(props.color, 0.2), + } + }, + outlined:{ + color: (props:TagChipStyleProp)=>props.color, + border: (props:TagChipStyleProp)=> `1px solid ${props.color}`, + '$clickable&:hover, $clickable&:focus, $deletable&:focus': { + backgroundColor:(props:TagChipStyleProp)=>fade(props.color,theme.palette.action.hoverOpacity), + }, + }, + icon:{ + color:"inherit", + }, + deleteIcon:{ + color:(props:TagChipStyleProp)=>fade(theme.palette.getContrastText(props.color),0.7), + "&:hover, &:active":{ + color:(props:TagChipStyleProp)=>theme.palette.getContrastText(props.color), + } + } +})); + +const getTagColorName = (tagname :string):string=>{ + if(tagname.startsWith("female")){ + return pink['700']; + } + else if(tagname.startsWith("male")){ + return blue[600]; + } + else return "default"; +} + +const TagChip = (props:Omit & {color: string})=>{ + const {color,...rest} = props; + const classes = useTagStyles({color : color !== "default" ? color : "#000"}); + return ; +} + +const useStyles = makeStyles((theme:Theme)=>({ + root:{ + display:"grid", + gridGap: theme.spacing(4), + }, + table_thumbnail:{ + width: 200, + height: 200, + background: '#272733', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + contentPaper:{ + display:'flex', + height:200, + }, + content_thumnail: { + maxWidth: '100%', + maxHeight: '100%', + }, + content_info:{ + padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`, + width: '100%', + display: 'flex', + flexDirection: 'column', + }, + tag_list:{ + display: 'flex', + justifyContent: 'flex-start', + flexWrap: 'wrap', + '& > *': { + margin: theme.spacing(0.5), + }, + + } +})); + +export type GalleryProp = { + option?:QueryListOption; +}; +type GalleryState = { + content:Content[]|undefined; +} +const content_accessor = new ClientContentAccessesor; + +export const Gallery = (props: GalleryProp)=>{ + const [state,setState]= useState({content:undefined}); + useEffect(()=>{ + (async ()=>{ + const c = await content_accessor.findList(props.option); + setState({content:c}); + })() + },[props.option]); + const num = 1; + const classes = useStyles(); + const theme = useTheme(); + if(state.content === undefined){ + return (
+ +
); + } + else{ + return (
{ + state.content.map(x=>{ + const thumbnail_url = `/content/${x.id}/${x.content_type}/thumbnail`; + return ( + { + x.content_type === "manga" ? : + + } + + {x.title} + + {x.tags.map(x=>{ + const tagcolor = getTagColorName(x); + console.log(tagcolor); + return (); + })} + + + ); + }) + } +
); + } +} \ No newline at end of file diff --git a/src/client/js/headline.tsx b/src/client/js/headline.tsx new file mode 100644 index 0000000..e912d70 --- /dev/null +++ b/src/client/js/headline.tsx @@ -0,0 +1,206 @@ +import ReactDom from 'react-dom'; +import React, { useState } from 'react'; +import { + Button, CssBaseline, Divider, IconButton, List, ListItem, Drawer, + AppBar, Toolbar, Typography, InputBase, ListItemIcon, ListItemText, Menu, MenuItem, + Hidden, useMediaQuery, Tooltip +} from '@material-ui/core'; +import { makeStyles, Theme, useTheme, fade } from '@material-ui/core/styles'; +import { ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon, ArrowBack as ArrowBackIcon, AccountCircle } from '@material-ui/icons'; +import {Link} from 'react-router-dom'; + +const drawerWidth = 240; + +const useStyles = makeStyles((theme: Theme) => ({ + root: { + display: 'flex' + }, + appBar: { + zIndex: theme.zIndex.drawer + 1, + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen + }) + }, + menuButton: { + marginRight: 36 + }, + hide: { + display: "none" + }, + drawer: { + flexShrink: 0, + whiteSpace: "nowrap", + [theme.breakpoints.up("sm")]:{ + width: drawerWidth, + }, + }, + drawerPaper: { + width: drawerWidth + }, + drawerClose: { + overflowX: 'hidden', + [theme.breakpoints.up("sm")]:{ + width: theme.spacing(7) + 1, + }, + }, + toolbar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-end', + padding: theme.spacing(0, 1), + ...theme.mixins.toolbar, + }, + content: { + flexGrow: 1, + padding: theme.spacing(3), + }, + title: { + display: 'none', + [theme.breakpoints.up("sm")]: { + display: 'block' + } + }, + search: { + position: 'relative', + borderRadius: theme.shape.borderRadius, + backgroundColor: fade(theme.palette.common.white, 0.15), + '&:hover': { + backgroundColor: fade(theme.palette.common.white, 0.25), + }, + marginRight: theme.spacing(2), + marginLeft: 0, + width: '100%', + [theme.breakpoints.up('sm')]: { + marginLeft: theme.spacing(3), + width: 'auto', + }, + }, + searchIcon: { + padding: theme.spacing(0, 2), + height: '100%', + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + inputRoot: { + color: 'inherit' + }, + inputInput: { + padding: theme.spacing(1, 1, 1, 0), + // vertical padding + font size from searchIcon + paddingLeft: `calc(1em + ${theme.spacing(4)}px)`, + transition: theme.transitions.create('width'), + width: '100%', + [theme.breakpoints.up('md')]: { + width: '20ch', + "&:hover": { + width: '25ch' + } + }, + }, + grow: { + flexGrow: 1, + }, +})); + +export const Headline = (prop: { children?: React.ReactNode }) => { + const [v, setv] = useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); + const classes = useStyles(); + const theme = useTheme(); + const toggleV = () => setv(!v); + const handleProfileMenuOpen = (e:React.MouseEvent)=>setAnchorEl(e.currentTarget); + const handleProfileMenuClose = ()=>setAnchorEl(null); + const isProfileMenuOpened = Boolean(anchorEl); + const menuId = 'primary-search-account-menu'; + + const renderProfileMenu = ( + Profile + Logout + ); + const drawer_contents = (<> +
+ + {theme.direction === "ltr" ? : } + +
+ + + + + + + + + + + + ); + return (
+ + + + + + + + Ionian + +
+
+
+ +
+ +
+ + + +
+
+ {renderProfileMenu} + +
+
+ {prop.children} +
+
); +}; + +export default Headline; \ No newline at end of file diff --git a/src/client/js/hello.ts b/src/client/js/hello.ts deleted file mode 100644 index 480562d..0000000 --- a/src/client/js/hello.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default function (){ - console.log("hello"); - console.log("???"); -}; \ No newline at end of file diff --git a/src/client/js/test.tsx b/src/client/js/test.tsx deleted file mode 100644 index 20689d1..0000000 --- a/src/client/js/test.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import ReactDom from 'react-dom'; -import React, { useState } from 'react'; -import Drawer from '@material-ui/core/Drawer'; -import { Button, Divider, IconButton, List, ListItem } from '@material-ui/core'; -import { makeStyles, Theme, useTheme } from '@material-ui/core/styles'; -import {ChevronLeft, ChevronRight} from '@material-ui/icons'; - -const drawerWidth = 240; - -const useStyles = makeStyles((theme: Theme) => ({ - root: { - display: 'flex' - }, - appBar: { - transition: theme.transitions.create(['width', 'margin'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen - }) - }, - appBarShift: { - width: `calc(100% - ${drawerWidth}px)`, - marginLeft: drawerWidth, - transition: theme.transitions.create(['margin', 'width'], { - easing: theme.transitions.easing.easeOut, - duration: theme.transitions.duration.enteringScreen, - }), - }, - drawer: { - width: drawerWidth, - flexShrink: 0, - }, - drawerPaper: { - width: drawerWidth, - }, - drawerHeader: { - display: 'flex', - alignItems: 'center', - padding: theme.spacing(0, 1), - // necessary for content to be below app bar - ...theme.mixins.toolbar, - justifyContent: 'flex-end', - }, - content: { - flexGrow: 1, - padding: theme.spacing(3), - transition: theme.transitions.create('margin', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - marginLeft: -drawerWidth, - }, - contentShift: { - transition: theme.transitions.create('margin', { - easing: theme.transitions.easing.easeOut, - duration: theme.transitions.duration.enteringScreen, - }), - marginLeft: 0, - }, -})); - -export const Headline = () => { - const [v, setv] = useState(false); - const classes = useStyles(); - const theme = useTheme(); - - return (
- -
- setv(false)}> - {theme.direction === "ltr" ? : } - -
- - - - -
NO
-
-
-
-
-

aaa{`a${v} ${classes.content}`}aaa

- -
-
); -}; - -export default Headline; \ No newline at end of file