mui upgrade

This commit is contained in:
monoid 2022-04-20 01:07:15 +09:00
parent 0081229f86
commit 7a3fdce4dc
25 changed files with 336 additions and 455 deletions

View File

@ -6,11 +6,11 @@
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' fonts.googleapis.com;
font-src 'self' fonts.gstatic.com">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/dist/css/style.css">
<link rel="stylesheet" href="/dist/bundle.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
</head>
<body>
<div id="root"></div>
<script src="/dist/js/bundle.js"></script>
<script src="/dist/bundle.js"></script>
</body>
</html>

View File

@ -1,11 +0,0 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-typescript",
"@babel/preset-react"
],
"plugins": [
"@babel/proposal-class-properties",
"@babel/proposal-object-rest-spread"
]
}

View File

@ -15,6 +15,8 @@ export class FetchFailError implements Error{
}
}
export class ClientDocumentAccessor implements DocumentAccessor{
search: (search_word: string) => Promise<Document[]>;
addList: (content_list: DocumentBody[]) => Promise<number[]>;
async findByPath(basepath: string, filename?: string): Promise<Document[]>{
throw new Error("not allowed");
};

View File

@ -1,46 +1,50 @@
import React, { createContext, useEffect, useRef, useState } from 'react';
import ReactDom from 'react-dom';
import {BrowserRouter, Redirect, Route, Switch as RouterSwitch} from 'react-router-dom';
import { Gallery, DocumentAbout, LoginPage, NotFoundPage, ProfilePage,DifferencePage, SettingPage} from './page/mod';
import {getInitialValue, UserContext} from './state';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { Gallery, DocumentAbout, LoginPage, NotFoundPage, ProfilePage, DifferencePage, SettingPage } from './page/mod';
import { getInitialValue, UserContext } from './state';
import { ThemeProvider, createTheme } from '@mui/material';
import './css/style.css';
const theme = createTheme();
const App = () => {
const [user,setUser] = useState("");
const [userPermission,setUserPermission] = useState<string[]>([]);
(async ()=>{
const {username,permission} = await getInitialValue();
if(username !== user){
const [user, setUser] = useState("");
const [userPermission, setUserPermission] = useState<string[]>([]);
(async () => {
const { username, permission } = await getInitialValue();
if (username !== user) {
setUser(username);
setUserPermission(permission);
}
})();
//useEffect(()=>{});
return (
<UserContext.Provider value={{
username:user,
setUsername:setUser,
permission:userPermission,
setPermission:setUserPermission}}>
<BrowserRouter>
<RouterSwitch>
<Route path="/" exact render={()=><Redirect to='/search?'/>}></Route>
<Route path="/search" render={()=><Gallery />}></Route>
<Route path="/doc" render={(prop)=><DocumentAbout {...prop}/>}></Route>
<Route path="/login" render={()=><LoginPage></LoginPage>}/>
<Route path="/profile" component={ProfilePage}></Route>
<Route path="/difference" component={DifferencePage}></Route>
<Route path="/setting" component={SettingPage}></Route>
<Route>
<NotFoundPage/>
</Route>
</RouterSwitch>
</BrowserRouter>
</UserContext.Provider>);
<UserContext.Provider value={{
username: user,
setUsername: setUser,
permission: userPermission,
setPermission: setUserPermission
}}>
<ThemeProvider theme={theme}>
<BrowserRouter>
<Routes>
<Route path="/" element={<Navigate replace to='/search?' />} />
<Route path="/search" element={<Gallery />} />
<Route path="/doc" element={<DocumentAbout />}></Route>
<Route path="/login" element={<LoginPage></LoginPage>} />
<Route path="/profile" element={<ProfilePage />}></Route>
<Route path="/difference" element={<DifferencePage />}></Route>
<Route path="/setting" element={<SettingPage />}></Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
</ThemeProvider>
</UserContext.Provider>);
};
ReactDom.render(
<App/>,
<App />,
document.getElementById("root")
)
);

33
src/client/build.ts Normal file
View File

@ -0,0 +1,33 @@
import esbuild from 'esbuild';
async function main() {
try {
const result = await esbuild.build({
entryPoints: ['app.tsx'],
bundle: true,
outfile: '../../dist/bundle.js',
platform: 'browser',
sourcemap: true,
minify: true,
target: ['chrome100', 'firefox100'],
watch: {
onRebuild: async (err, _result) => {
if (err) {
console.error('watch build failed: ',err);
}
else{
console.log('watch build success');
}
}
}
});
console.log("watching...");
return result;
} catch (error) {
console.error(error);
process.exit(1);
}
}
main().then((res) => {
});

View File

@ -1,23 +1,16 @@
import React, { useState, useEffect } from 'react';
import { Redirect, Route, Switch, useHistory, useRouteMatch, match as MatchType, Link as RouterLink } from 'react-router-dom';
import React, { } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Document } from '../accessor/document';
import { LoadingCircle } from '../component/loading';
import { Link, Paper, makeStyles, Theme, Box, useTheme, Typography } from '@material-ui/core';
import { Link, Paper, Theme, Box, useTheme, Typography } from '@mui/material';
import { ThumbnailContainer } from '../page/reader/reader';
import { TagChip } from '../component/tagchip';
import { ComicReader } from '../page/reader/comic';
export const makeContentInfoUrl = (id: number) => `/doc/${id}`;
export const makeContentReaderUrl = (id: number) => `/doc/${id}/reader`;
const useStyles = makeStyles((theme: Theme) => ({
root: {
display: "flex",
[theme.breakpoints.down("sm")]: {
flexDirection: "column",
alignItems: "center",
}
},
const useStyles = ((theme: Theme) => ({
thumbnail_content: {
maxHeight: '400px',
maxWidth: 'min(400px, 100vw)',
@ -43,26 +36,26 @@ const useStyles = makeStyles((theme: Theme) => ({
overflowY: 'hidden',
alignItems: 'baseline',
},
short_subinfoContainer:{
[theme.breakpoints.down("md")]:{
display:'none',
short_subinfoContainer: {
[theme.breakpoints.down("md")]: {
display: 'none',
},
},
short_root:{
overflowY:'hidden',
display:'flex',
short_root: {
overflowY: 'hidden',
display: 'flex',
flexDirection: 'column',
[theme.breakpoints.up("sm")]:{
height:200,
[theme.breakpoints.up("sm")]: {
height: 200,
flexDirection: 'row',
},
},
short_thumbnail_anchor:{
short_thumbnail_anchor: {
background: '#272733',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
[theme.breakpoints.up("sm")]:{
[theme.breakpoints.up("sm")]: {
width: theme.spacing(25),
height: theme.spacing(25),
flexShrink: 0,
@ -84,66 +77,75 @@ export const ContentInfo = (props: {
infoContainer?: string,
subinfoContainer?: string
},
gallery?:string,
short?:boolean
gallery?: string,
short?: boolean
}) => {
const classes = useStyles();
//const classes = useStyles();
const theme = useTheme();
const document = props.document;
const propclasses = props.classes ?? {};
const rootName = props.short ? classes.short_root : classes.root;
/*const rootName = props.short ? classes.short_root : classes.root;
const thumbnail_anchor = props.short ? classes.short_thumbnail_anchor : "";
const thumbnail_content = props.short ? classes.short_thumbnail_content :
classes.thumbnail_content;
const subinfoContainer = props.short ? classes.short_subinfoContainer :
classes.subinfoContainer;
return (<Paper className={propclasses.root ?? rootName} elevation={4}>
<Link className={propclasses.thumbnail_anchor ?? thumbnail_anchor} component={RouterLink} to={{
pathname:makeContentReaderUrl(document.id)
classes.subinfoContainer;*/
const url = props.gallery === undefined ? makeContentReaderUrl(document.id) : makeContentInfoUrl(document.id);
return (<Paper sx={{
display: "flex",
[theme.breakpoints.down("sm")]: {
flexDirection: "column",
alignItems: "center",
}
}} elevation={4}>
<Link /*className={propclasses.thumbnail_anchor ?? thumbnail_anchor}*/ component={RouterLink} to={{
pathname: makeContentReaderUrl(document.id)
}}>
{document.deleted_at === null ?
(<ThumbnailContainer content={document}
className={propclasses.thumbnail_content ?? thumbnail_content}/>)
: (<Typography className={propclasses.thumbnail_content ?? thumbnail_content} variant='h4'>Deleted</Typography>)}
(<ThumbnailContainer content={document}
style={{
maxHeight: '400px',
maxWidth: 'min(400px, 100vw)',
}}/>)
: (<Typography/* className={propclasses.thumbnail_content ?? thumbnail_content} */ variant='h4'>Deleted</Typography>)}
</Link>
<Box className={propclasses.infoContainer ?? classes.infoContainer}>
<Link variant='h5' color='inherit' component={RouterLink} to={{
pathname: props.gallery === undefined ? makeContentReaderUrl(document.id) : makeContentInfoUrl(document.id),
}}
className={propclasses.title ?? classes.title}>
<Box /*className={propclasses.infoContainer ?? classes.infoContainer}*/>
<Link variant='h5' color='inherit' component={RouterLink} to={{pathname: url}}
/*className={propclasses.title ?? classes.title}*/>
{document.title}
</Link>
<Box className={propclasses.subinfoContainer ?? subinfoContainer}>
{props.short ? (<Box className={propclasses.tag_list ?? classes.tag_list}>{document.tags.map(x =>
(<TagChip key={x} label={x} clickable tagname={x} size="small"></TagChip>)
)}</Box>) : (
<ComicDetailTag tags={document.tags} classes={({tag_list:classes.tag_list})}></ComicDetailTag>)
<Box /*className={propclasses.subinfoContainer ?? subinfoContainer}*/>
{props.short ? (<Box /*className={propclasses.tag_list ?? classes.tag_list}*/>{document.tags.map(x =>
(<TagChip key={x} label={x} clickable tagname={x} size="small"></TagChip>)
)}</Box>) : (
<ComicDetailTag tags={document.tags}/* classes={({tag_list:classes.tag_list})}*/ ></ComicDetailTag>)
}
</Box>
</Box>
</Paper>);
}
function ComicDetailTag(prop:{tags:string[],classes:{
function ComicDetailTag(prop: {
tags: string[]/*classes:{
tag_list:string
}}){
}*/}) {
let allTag = prop.tags;
const tagKind = ["artist","group","series","type","character"];
let tagTable:{[kind:string]:string[]} = {};
for(const kind of tagKind){
const tags = allTag.filter(x => x.startsWith(kind+":")).map(x => x.slice(kind.length+1));
const tagKind = ["artist", "group", "series", "type", "character"];
let tagTable: { [kind: string]: string[] } = {};
for (const kind of tagKind) {
const tags = allTag.filter(x => x.startsWith(kind + ":")).map(x => x.slice(kind.length + 1));
tagTable[kind] = tags;
allTag = allTag.filter(x => !x.startsWith(kind+":"));
allTag = allTag.filter(x => !x.startsWith(kind + ":"));
}
return (<React.Fragment>
{tagKind.map(key=>(
{tagKind.map(key => (
<React.Fragment key={key}>
<Typography variant='subtitle1'>{key}</Typography>
<Box>{tagTable[key].length !== 0 ? tagTable[key].join(", ") : "N/A"}</Box>
</React.Fragment>
))}
<Typography variant='subtitle1'>Tags</Typography>
<Box className={prop.classes.tag_list}>
<Box /*lassName={prop.classes.tag_list}*/>
{allTag.map(x => (<TagChip key={x} label={x} clickable tagname={x} size="small"></TagChip>))}
</Box>
</React.Fragment>);

View File

@ -1,123 +1,77 @@
import ReactDom from 'react-dom';
import React, { ReactNode, useContext, useState } from 'react';
import React, { useContext, useState } from 'react';
import {
Button, CssBaseline, Divider, IconButton, List, ListItem, Drawer,
AppBar, Toolbar, Typography, InputBase, ListItemIcon, ListItemText, Menu, MenuItem,
Hidden, Tooltip, Link
} from '@material-ui/core';
import { makeStyles, Theme, useTheme, fade } from '@material-ui/core/styles';
import { ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon, AccountCircle
} from '@material-ui/icons';
Hidden, Tooltip, Link, styled
} from '@mui/material';
import { alpha, Theme, useTheme } from '@mui/material/styles';
import {
ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon, AccountCircle
} from '@mui/icons-material';
import { Link as RouterLink, useRouteMatch } from 'react-router-dom';
import { Link as RouterLink } from 'react-router-dom';
import { doLogout, UserContext } from '../state';
const drawerWidth = 240;
const drawerWidth = 270;
const useStyles = makeStyles((theme: Theme) => ({
root: {
display: 'flex'
const DrawerHeader = styled('div')(({ theme }) => ({
...theme.mixins.toolbar
}));
const StyledDrawer = styled(Drawer)(({ theme }) => ({
flexShrink: 0,
whiteSpace: "nowrap",
[theme.breakpoints.up("sm")]: {
width: drawerWidth,
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
}
));
const StyledSearchBar = styled('div')(({ theme }) => ({
position: 'relative',
borderRadius: theme.shape.borderRadius,
backgroundColor: alpha(theme.palette.common.white, 0.15),
'&:hover': {
backgroundColor: alpha(theme.palette.common.white, 0.25),
},
menuButton: {
marginRight: 36
marginLeft: 0,
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(1),
width: 'auto',
},
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: {
...theme.mixins.toolbar,
},
content: {
display: 'flex',
flexFlow: 'column',
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: {
}));
const StyledInputBase = styled(InputBase)(({ theme }) => ({
color: 'inherit',
'& .MuiInputBase-input': {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('md')]: {
width: '20ch',
"&:hover": {
width: '25ch'
}
[theme.breakpoints.up('sm')]: {
width: '12ch',
'&:focus': {
width: '20ch',
},
},
},
grow: {
flexGrow: 1,
},
}));
const closedMixin = (theme: Theme) => ({
overflowX: 'hidden',
width: `calc(${theme.spacing(7)} + 1px)`,
});
export const Headline = (prop: {
children?: React.ReactNode,
classes?:{
content?:string,
toolbar?:string,
classes?: {
content?: string,
toolbar?: string,
},
menu: React.ReactNode
}) => {
const [v, setv] = useState(false);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const classes = useStyles();
const theme = useTheme();
const toggleV = () => setv(!v);
const handleProfileMenuOpen = (e: React.MouseEvent<HTMLElement>) => setAnchorEl(e.currentTarget);
@ -137,39 +91,58 @@ export const Headline = (prop: {
onClose={handleProfileMenuClose}
>
<MenuItem component={RouterLink} to='/profile'>Profile</MenuItem>
<MenuItem onClick={async ()=>{handleProfileMenuClose(); await doLogout(); user_ctx.setUsername("");}}>Logout</MenuItem>
<MenuItem onClick={async () => { handleProfileMenuClose(); await doLogout(); user_ctx.setUsername(""); }}>Logout</MenuItem>
</Menu>);
const drawer_contents = (<>
<div className={classes.toolbar}>
<DrawerHeader>
<IconButton onClick={toggleV}>
{theme.direction === "ltr" ? <ChevronLeft /> : <ChevronRight />}
</IconButton>
</div>
</DrawerHeader>
<Divider />
{prop.menu}
</>);
return (<div className={classes.root}>
return (<div style={{ display: 'flex' }}>
<CssBaseline />
<AppBar position="fixed" className={classes.appBar}>
<AppBar position="fixed" sx={{
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
}}>
<Toolbar>
<IconButton color="inherit"
aria-label="open drawer"
onClick={toggleV}
edge="start"
className={classes.menuButton}>
style={{ marginRight: 36 }}
>
<MenuIcon></MenuIcon>
</IconButton>
<Link variant="h5" noWrap className={classes.title} color="inherit" component={RouterLink} to="/">
<Link variant="h5" noWrap sx={{
display: 'none',
[theme.breakpoints.up("sm")]: {
display: 'block'
}
}} color="inherit" component={RouterLink} to="/">
Ionian
</Link>
<div className={classes.grow}></div>
<div className={classes.search}>
<div className={classes.searchIcon}>
<div style={{ flexGrow: 1 }}></div>
<StyledSearchBar >
<div style={{
padding: theme.spacing(0, 2),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<SearchIcon />
</div>
<InputBase placeholder="search"
classes={{ root: classes.inputRoot, input: classes.inputInput }}></InputBase>
</div>
<StyledInputBase placeholder="search"></StyledInputBase>
</StyledSearchBar>
{
isLogin ?
<IconButton
@ -188,21 +161,35 @@ export const Headline = (prop: {
{renderProfileMenu}
<nav>
<Hidden smUp implementation="css">
<Drawer variant="temporary" anchor='left' open={v} onClose={toggleV}
classes={{ paper: classes.drawerPaper }}>
<StyledDrawer variant="temporary" anchor='left' open={v} onClose={toggleV}
sx={{
width: drawerWidth
}}
>
{drawer_contents}
</Drawer>
</StyledDrawer>
</Hidden>
<Hidden xsDown implementation="css">
<Drawer variant='permanent' anchor='left' className={[classes.drawer, classes.drawerClose].join(" ").trim()} classes={{
paper: classes.drawerClose
}}>
<StyledDrawer variant='permanent' anchor='left'
sx={{
overflowX: 'hidden',
width: theme.spacing(7) + 1,
...closedMixin(theme),
'& .MuiDrawer-paper': closedMixin(theme),
}}>
{drawer_contents}
</Drawer>
</StyledDrawer>
</Hidden>
</nav>
<main className={prop.classes?.content ?? classes.content}>
<div className={prop.classes?.toolbar ?? classes.toolbar}></div>
<main style={{
display: 'flex',
flexFlow: 'column',
flexGrow: 1,
padding: theme.spacing(3),
marginTop: theme.spacing(8),
}}>
<div style={{
}} ></div>
{prop.children}
</main>
</div>);

View File

@ -1,5 +1,5 @@
import React from 'react';
import {Box, CircularProgress} from '@material-ui/core';
import {Box, CircularProgress} from '@mui/material';
export const LoadingCircle = ()=>{
return (<Box style={{alignSelf:'center'}}>

View File

@ -1,12 +1,11 @@
import React from 'react';
import {List, ListItem, ListItemIcon, Tooltip, ListItemText, Divider} from '@material-ui/core';
import {List, ListItem, ListItemIcon, Tooltip, ListItemText, Divider} from '@mui/material';
import {ArrowBack as ArrowBackIcon, Settings as SettingIcon,
Collections as CollectionIcon, VideoLibrary as VideoIcon, Home as HomeIcon,
Folder as FolderIcon } from '@material-ui/icons';
import {Link as RouterLink, useHistory} from 'react-router-dom';
Folder as FolderIcon } from '@mui/icons-material';
import {Link as RouterLink} from 'react-router-dom';
export const NavItem = (props:{name:string,to:string, icon:React.ReactElement<any,any>})=>{
const history = useHistory();
return (<ListItem button key={props.name} component={RouterLink} to={props.to}>
<ListItemIcon>
<Tooltip title={props.name.toLocaleLowerCase()} placement="bottom">

View File

@ -1,14 +1,14 @@
import React from 'react';
import {ChipTypeMap} from '@material-ui/core/Chip';
import { Chip, colors } from '@material-ui/core';
import {useTheme, makeStyles, Theme, emphasize, fade} from '@material-ui/core/styles';
import {ChipTypeMap} from '@mui/material/Chip';
import { Chip, colors } from '@mui/material';
import { Theme, emphasize} from '@mui/material/styles';
import {Link as RouterLink} from 'react-router-dom';
type TagChipStyleProp = {
color: string
}
const useTagStyles = makeStyles((theme:Theme)=>({
const useTagStyles = ((theme:Theme)=>({
root:(props:TagChipStyleProp)=>({
color: theme.palette.getContrastText(props.color),
backgroundColor: props.color,
@ -27,14 +27,14 @@ const useTagStyles = makeStyles((theme:Theme)=>({
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),
//backgroundColor:(props:TagChipStyleProp)=> (props.color,theme.palette.action.hoverOpacity),
},
},
icon:{
color:"inherit",
},
deleteIcon:{
color:(props:TagChipStyleProp)=>fade(theme.palette.getContrastText(props.color),0.7),
//color:(props:TagChipStyleProp)=> (theme.palette.getContrastText(props.color),0.7),
"&:hover, &:active":{
color:(props:TagChipStyleProp)=>theme.palette.getContrastText(props.color),
}
@ -59,10 +59,8 @@ type ColorChipProp = Omit<ChipTypeMap['props'],"color"> & TagChipStyleProp & {
export const ColorChip = (props:ColorChipProp)=>{
const {color,...rest} = props;
const classes = useTagStyles({color : color !== "default" ? color : "#000"});
return <Chip color="default" classes={{
...(color !== "default" ? classes : {})
}} {...rest}></Chip>;
//const classes = useTagStyles({color : color !== "default" ? color : "#000"});
return <Chip color="default" {...rest}></Chip>;
}
type TagChipProp = Omit<ChipTypeMap['props'],"color"> & {

View File

@ -1,52 +1,20 @@
{
"name": "ionian-client",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build:dev": "webpack --mode development",
"build:prod": "webpack --mode production",
"build:watch": "webpack --mode development -w",
"type-check": "tsc --noEmit"
},
"author": "",
"license": "ISC",
"browserslist": {
"production": [
"> 10%"
],
"development": [
"last 1 chrome version",
"last 1 firefox version"
]
},
"dependencies": {
"@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.11.2",
"@mui/material": "^5.0.3",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"@babel/preset-typescript": "^7.12.7",
"@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",
"eslint-plugin-node": "^11.1.0",
"mini-css-extract-plugin": "^1.3.3",
"style-loader": "^2.0.0",
"ts-json-schema-generator": "^0.82.0",
"typescript": "^4.1.3",
"webpack": "^5.15.0",
"webpack-cli": "^4.3.1"
}
"name": "ionian_client",
"version": "0.0.1",
"description": "client of ionian",
"dependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mui/icons-material": "^5.6.2",
"@mui/material": "^5.6.2",
"@types/react": "^18.0.5",
"@types/react-dom": "^18.0.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^6.3.0"
},
"devDependencies": {
"esbuild": "^0.14.36",
"ts-node": "^10.7.0"
}
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import {Typography} from '@material-ui/core';
import {ArrowBack as ArrowBackIcon} from '@material-ui/icons';
import {Typography} from '@mui/material';
import {ArrowBack as ArrowBackIcon} from '@mui/icons-material';
import { Headline, BackItem, NavList, CommonMenuList } from '../component/mod';
export const NotFoundPage = ()=>{

View File

@ -1,11 +1,10 @@
import React, { useState, useEffect, useContext } from 'react';
import { Redirect, Route, Switch, useHistory, useRouteMatch, match as MatchType, Link as RouterLink, useParams, useLocation } from 'react-router-dom';
import React, { useState, useEffect} from 'react';
import { Route, Routes, useLocation, useParams } from 'react-router-dom';
import DocumentAccessor, { Document } from '../accessor/document';
import { LoadingCircle } from '../component/loading';
import { Link, Paper, makeStyles, Theme, Box, useTheme, Typography } from '@material-ui/core';
import {ArrowBack as ArrowBackIcon } from '@material-ui/icons';
import { Theme, Typography } from '@mui/material';
import { getPresenter } from './reader/reader';
import { BackItem, CommonMenuList, ContentInfo, Headline, NavItem, NavList } from '../component/mod';
import { CommonMenuList, ContentInfo, Headline } from '../component/mod';
import {NotFoundPage} from './404';
export const makeContentInfoUrl = (id: number) => `/doc/${id}`;
@ -16,7 +15,7 @@ type DocumentState = {
notfound: boolean,
}
const useStyles = makeStyles((theme:Theme)=>({
const styles = ((theme:Theme)=>({
noPaddingContent:{
display:'flex',
flexDirection: 'column',
@ -28,12 +27,13 @@ const useStyles = makeStyles((theme:Theme)=>({
}
}));
export const DocumentAbout = (prop: { match: MatchType }) => {
const match = useRouteMatch<{id:string}>("/doc/:id");
export const DocumentAbout = (prop?: {}) => {
const location = useLocation();
const match = useParams<{id:string}>();
if (match == null) {
throw new Error("unreachable");
}
const id = Number.parseInt(match.params['id']);
const id = Number.parseInt(match.id);
const [info, setInfo] = useState<DocumentState>({ doc: undefined, notfound:false });
const menu_list = (link?:string) => <CommonMenuList url={link}></CommonMenuList>;
@ -45,7 +45,7 @@ export const DocumentAbout = (prop: { match: MatchType }) => {
}
})();
}, []);
const classes = useStyles();
if (isNaN(id)) {
return (
<Headline menu={menu_list()}>
@ -68,23 +68,20 @@ export const DocumentAbout = (prop: { match: MatchType }) => {
}
else{
const ReaderPage = getPresenter(info.doc);
return (<Switch>
<Route exact path={`${prop.match.path}/:id`}>
return (<Routes>
<Route path={`:id`}>
<Headline menu={menu_list()}>
<ContentInfo document={info.doc}></ContentInfo>
</Headline>
</Route>
<Route exact path={`${prop.match.path}/:id/reader`}>
<Headline menu={menu_list(`${prop.match.path}/${id}`)} classes={{
content:classes.noPaddingContent,
toolbar:classes.noPaddingToolbar
}}>
<Route path={`:id/reader`}>
<Headline menu={menu_list(location.pathname)}>
<ReaderPage doc={info.doc}></ReaderPage>
</Headline>
</Route>
<Route>
<NotFoundPage></NotFoundPage>
</Route>
</Switch>);
</Routes>);
}
}

View File

@ -1,10 +1,10 @@
import React, { useContext, useEffect, useState } from 'react';
import { CommonMenuList, Headline } from "../component/mod";
import { UserContext } from "../state";
import { Box, Grid, Paper, Typography,Button, makeStyles, Theme } from "@material-ui/core";
import { Box, Grid, Paper, Typography,Button, Theme } from "@mui/material";
import {Stack} from '@mui/material';
const useStyles = makeStyles((theme:Theme)=>({
const useStyles = ((theme:Theme)=>({
paper:{
padding: theme.spacing(2),
},
@ -30,12 +30,12 @@ function TypeDifference(prop:{
onCommit:(v:{type:string,path:string})=>void,
onCommitAll:(type:string) => void
}){
const classes = useStyles();
//const classes = useStyles();
const x = prop.content;
const [button_disable,set_disable] = useState(false);
return (<Paper className={classes.paper}>
<Box className={classes.contentTitle}>
return (<Paper /*className={classes.paper}*/>
<Box /*className={classes.contentTitle}*/>
<Typography variant='h3' >{x.type}</Typography>
<Button variant="contained" key={x.type} onClick={()=>{
set_disable(true);
@ -44,7 +44,7 @@ function TypeDifference(prop:{
}}>Commit all</Button>
</Box>
{x.value.map(y=>(
<Box className={classes.commitable} key={y.path}>
<Box /*className={classes.commitable}*/ key={y.path}>
<Button variant="contained" onClick={()=>{
set_disable(true);
prop.onCommit(y);
@ -59,7 +59,7 @@ function TypeDifference(prop:{
export function DifferencePage(){
const ctx = useContext(UserContext);
const classes = useStyles();
//const classes = useStyles();
const [diffList,setDiffList] = useState<
FileDifference[]
>([]);

View File

@ -1,22 +1,13 @@
import React, { useContext, useEffect, useState } from 'react';
import { Headline, CommonMenuList,LoadingCircle, ContentInfo, NavList, NavItem, TagChip } from '../component/mod';
import { Box, Paper, Link, useMediaQuery, Portal, List, ListItem, ListItemIcon, Tooltip, ListItemText, Typography, Chip } from '@material-ui/core';
import {useTheme, makeStyles, Theme} from '@material-ui/core/styles';
import { Box, Typography, Chip } from '@mui/material';
import ContentAccessor,{QueryListOption, Document} from '../accessor/document';
import {Link as RouterLink} from 'react-router-dom';
import {toQueryString} from '../accessor/util';
import {useLocation} from 'react-router-dom';
import { QueryStringToMap } from '../accessor/util';
const useStyles = makeStyles((theme:Theme)=>({
root:{
display:"grid",
gridGap: theme.spacing(4),
},
}));
export type GalleryProp = {
option?:QueryListOption;
diff:string;
@ -28,6 +19,7 @@ type GalleryState = {
export const GalleryInfo = (props: GalleryProp)=>{
const [state,setState]= useState<GalleryState>({documents:undefined});
useEffect(()=>{
const abortController = new AbortController();
const load = (async ()=>{
const c = await ContentAccessor.findList(props.option);
//todo : if c is undefined, retry to fetch 3 times. and show error message.
@ -35,14 +27,15 @@ export const GalleryInfo = (props: GalleryProp)=>{
})
load();
},[props.diff]);
const classes = useStyles();
const queryString = toQueryString(props.option??{});
if(state.documents === undefined){
return (<LoadingCircle/>);
}
else{
return (<Box className={classes.root}>
return (<Box sx={{
display:'grid'
}}>
{props.option !== undefined && props.diff !== "" && <Box>
<Typography variant="h6">search for</Typography>
{props.option.word !== undefined && <Chip label={"search : "+props.option.word}></Chip>}

View File

@ -1,9 +1,9 @@
import React, { useContext, useState } from 'react';
import {CommonMenuList, Headline} from '../component/mod';
import { Button, Dialog, DialogActions, DialogContent, DialogContentText,
DialogTitle, MenuList, Paper, TextField, Typography, useTheme } from '@material-ui/core';
DialogTitle, MenuList, Paper, TextField, Typography, useTheme } from '@mui/material';
import { UserContext } from '../state';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import {doLogin as doSessionLogin} from '../state';
export const LoginPage = ()=>{
@ -11,7 +11,7 @@ export const LoginPage = ()=>{
const [userLoginInfo,setUserLoginInfo]= useState({username:"",password:""});
const [openDialog,setOpenDialog] = useState({open:false,message:""});
const {setUsername,setPermission} = useContext(UserContext);
const history = useHistory();
const navigate = useNavigate();
const handleDialogClose = ()=>{
setOpenDialog({...openDialog,open:false});
}
@ -34,7 +34,7 @@ export const LoginPage = ()=>{
else console.error(e);
return;
}
history.push("/");
navigate("/");
}
const menu = CommonMenuList();
return <Headline menu={menu}>

View File

@ -1,10 +1,10 @@
import { CommonMenuList, Headline } from "../component/mod";
import React, { useContext, useState } from 'react';
import { UserContext } from "../state";
import { Chip, Grid, makeStyles, Paper, Theme, Typography, Divider, Button,
Dialog, DialogTitle, DialogContentText, DialogContent, TextField, DialogActions } from "@material-ui/core";
import { Chip, Grid, Paper, Theme, Typography, Divider, Button,
Dialog, DialogTitle, DialogContentText, DialogContent, TextField, DialogActions } from "@mui/material";
const useStyles = makeStyles((theme:Theme)=>({
const useStyles = ((theme:Theme)=>({
paper:{
alignSelf:"center",
padding:theme.spacing(2),
@ -17,7 +17,7 @@ const useStyles = makeStyles((theme:Theme)=>({
export function ProfilePage(){
const userctx = useContext(UserContext);
const classes = useStyles();
//const classes = useStyles();
const menu = CommonMenuList();
const [pw_open,set_pw_open] = useState(false);
const [oldpw,setOldpw] = useState("");
@ -66,7 +66,7 @@ export function ProfilePage(){
handle_close();
}
return (<Headline menu={menu}>
<Paper className={classes.paper}>
<Paper /*className={classes.paper}*/>
<Grid container direction="column" alignItems="center">
<Grid item>
<Typography variant='h4'>{userctx.username}</Typography>
@ -87,7 +87,7 @@ export function ProfilePage(){
<DialogTitle>Password Reset</DialogTitle>
<DialogContent>
<Typography>type the old and new password</Typography>
<div className={classes.formfield}>
<div /*className={classes.formfield}*/>
{(!isElectronContent) && (<TextField autoFocus margin='dense' type="password" label="old password"
value={oldpw} onChange={(e)=>setOldpw(e.target.value)}></TextField>)}
<TextField margin='dense' type="password" label="new password"

View File

@ -1,5 +1,5 @@
import React, {useState, useEffect} from 'react';
import { Typography, useTheme } from '@material-ui/core';
import { Typography, useTheme } from '@mui/material';
import { Document } from '../../accessor/document';
type ComicType = "comic"|"artist cg"|"donjinshi"|"western"
@ -14,7 +14,6 @@ export type PresentableTag = {
}
export const ComicReader = (props:{doc:Document})=>{
const theme = useTheme();
const additional = props.doc.additional;
const [curPage,setCurPage] = useState(0);
if(!('page' in additional)){

View File

@ -1,4 +1,4 @@
import { Typography } from '@material-ui/core';
import { Typography } from '@mui/material';
import React from 'react';
import { Document, makeThumbnailUrl } from '../../accessor/document';
import {ComicReader} from './comic';
@ -22,10 +22,10 @@ export const getPresenter = (content:Document):PagePresenter => {
return ()=><Typography variant='h2'>Not implemented reader</Typography>;
}
export const ThumbnailContainer = (props:{content:Document, className?:string})=>{
export const ThumbnailContainer = (props:{content:Document, className?:string, style?:React.CSSProperties})=>{
const thumbnailurl = makeThumbnailUrl(props.content);
if(props.content.content_type === "video"){
return (<video src={thumbnailurl} muted autoPlay loop className={props.className}></video>)
return (<video src={thumbnailurl} muted autoPlay loop className={props.className} style={props.style}></video>)
}
else return (<img src={thumbnailurl} className={props.className}></img>)
else return (<img src={thumbnailurl} className={props.className} style={props.style}></img>)
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import {Typography, Paper} from '@material-ui/core';
import {ArrowBack as ArrowBackIcon} from '@material-ui/icons';
import {Typography, Paper} from '@mui/material';
import {ArrowBack as ArrowBackIcon} from '@mui/icons-material';
import { Headline, BackItem, NavList, CommonMenuList } from '../component/mod';
export const SettingPage = ()=>{

View File

@ -1,70 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ESNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./build", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
"noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
"isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@ -1,45 +0,0 @@
const path = require("path");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = ()=>{return {
entry: './app.tsx',
output: {
path: path.resolve(__dirname, '../../dist/js'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(js|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
babelrc: true,//why babelrc not working? why is webpack not reading '.babelrc'?
presets: [
'@babel/preset-env',
'@babel/preset-typescript',
'@babel/preset-react'
],
plugins: [
'@babel/proposal-class-properties',
'@babel/proposal-object-rest-spread'
]
}
}
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader,'css-loader']
}
]
},
plugins : [
new MiniCssExtractPlugin({
"filename":'../css/style.css'}),
],
devtool: 'source-map',
resolve: {
extensions: ['.js','.css','.ts','.tsx']
}
};}

13
src/search/indexer.ts Normal file
View File

@ -0,0 +1,13 @@
export interface PaginationOption{
cursor:number;
limit:number;
}
export interface IIndexer{
indexDoc(word:string,doc_id:number):boolean;
indexDoc(word:string[],doc_id:number):boolean;
getDoc(word:string,option?:PaginationOption):number[];
getDoc(word:string[],option?:PaginationOption):number[];
}

10
src/search/tokenizer.ts Normal file
View File

@ -0,0 +1,10 @@
export interface ITokenizer{
tokenize(s:string):string[];
}
export class DefaultTokenizer implements ITokenizer{
tokenize(s: string): string[] {
return s.split(" ");
}
}

View File

@ -119,10 +119,12 @@ class ServerApplication{
}
})};
const setting = get_setting();
static_file_server('dist/css/style.css','css');
static_file_server('dist/js/bundle.js','js');
if(setting.mode === "development")
static_file_server('dist/js/bundle.js.map','text');
static_file_server('dist/bundle.css','css');
static_file_server('dist/bundle.js','js');
if(setting.mode === "development"){
static_file_server('dist/bundle.js.map','text');
static_file_server('dist/bundle.css.map','text');
}
}
start_server(){
let setting = get_setting();