diff --git a/index.html b/index.html index ecbf047..3f21364 100644 --- a/index.html +++ b/index.html @@ -6,11 +6,11 @@ - +
- + \ No newline at end of file diff --git a/src/client/.babelrc b/src/client/.babelrc deleted file mode 100644 index 2961d01..0000000 --- a/src/client/.babelrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "presets": [ - "@babel/preset-env", - "@babel/preset-typescript", - "@babel/preset-react" - ], - "plugins": [ - "@babel/proposal-class-properties", - "@babel/proposal-object-rest-spread" - ] -} \ No newline at end of file diff --git a/src/client/accessor/document.ts b/src/client/accessor/document.ts index 89b4f56..f176160 100644 --- a/src/client/accessor/document.ts +++ b/src/client/accessor/document.ts @@ -15,6 +15,8 @@ export class FetchFailError implements Error{ } } export class ClientDocumentAccessor implements DocumentAccessor{ + search: (search_word: string) => Promise; + addList: (content_list: DocumentBody[]) => Promise; async findByPath(basepath: string, filename?: string): Promise{ throw new Error("not allowed"); }; diff --git a/src/client/app.tsx b/src/client/app.tsx index 0c90736..5d0886c 100644 --- a/src/client/app.tsx +++ b/src/client/app.tsx @@ -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([]); - (async ()=>{ - const {username,permission} = await getInitialValue(); - if(username !== user){ + const [user, setUser] = useState(""); + const [userPermission, setUserPermission] = useState([]); + (async () => { + const { username, permission } = await getInitialValue(); + if (username !== user) { setUser(username); setUserPermission(permission); } })(); //useEffect(()=>{}); return ( - - - - }> - }> - }> - }/> - - - - - - - - - ); + + + + + } /> + } /> + }> + } /> + }> + }> + }> + } /> + + + + ); }; ReactDom.render( - , + , document.getElementById("root") -) \ No newline at end of file +); \ No newline at end of file diff --git a/src/client/build.ts b/src/client/build.ts new file mode 100644 index 0000000..ee9fa78 --- /dev/null +++ b/src/client/build.ts @@ -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) => { +}); \ No newline at end of file diff --git a/src/client/component/contentinfo.tsx b/src/client/component/contentinfo.tsx index d858a62..db19935 100644 --- a/src/client/component/contentinfo.tsx +++ b/src/client/component/contentinfo.tsx @@ -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 ( - + - {document.deleted_at === null ? - () - : (Deleted)} + {document.deleted_at === null ? + () + : (Deleted)} - - + + {document.title} - - {props.short ? ({document.tags.map(x => - () - )}) : ( - ) + + {props.short ? ({document.tags.map(x => + () + )}) : ( + ) } ); } -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 ( - {tagKind.map(key=>( + {tagKind.map(key => ( {key} {tagTable[key].length !== 0 ? tagTable[key].join(", ") : "N/A"} ))} Tags - + {allTag.map(x => ())} ); diff --git a/src/client/component/headline.tsx b/src/client/component/headline.tsx index 1d776b8..295be22 100644 --- a/src/client/component/headline.tsx +++ b/src/client/component/headline.tsx @@ -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); - const classes = useStyles(); const theme = useTheme(); const toggleV = () => setv(!v); const handleProfileMenuOpen = (e: React.MouseEvent) => setAnchorEl(e.currentTarget); @@ -137,39 +91,58 @@ export const Headline = (prop: { onClose={handleProfileMenuClose} > Profile - {handleProfileMenuClose(); await doLogout(); user_ctx.setUsername("");}}>Logout + { handleProfileMenuClose(); await doLogout(); user_ctx.setUsername(""); }}>Logout ); const drawer_contents = (<> -
+ {theme.direction === "ltr" ? : } -
+ {prop.menu} ); - return (
+ return (
- + + style={{ marginRight: 36 }} + > - + Ionian -
-
-
+
+ +
- -
+ + { isLogin ? - + {drawer_contents} - + - + {drawer_contents} - + -
-
+
+
{prop.children}
); diff --git a/src/client/component/loading.tsx b/src/client/component/loading.tsx index 639c191..33580fd 100644 --- a/src/client/component/loading.tsx +++ b/src/client/component/loading.tsx @@ -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 ( diff --git a/src/client/component/navlist.tsx b/src/client/component/navlist.tsx index efa8497..0400a1d 100644 --- a/src/client/component/navlist.tsx +++ b/src/client/component/navlist.tsx @@ -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})=>{ - const history = useHistory(); return ( diff --git a/src/client/component/tagchip.tsx b/src/client/component/tagchip.tsx index 1b47bbe..819a63c 100644 --- a/src/client/component/tagchip.tsx +++ b/src/client/component/tagchip.tsx @@ -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 & TagChipStyleProp & { export const ColorChip = (props:ColorChipProp)=>{ const {color,...rest} = props; - const classes = useTagStyles({color : color !== "default" ? color : "#000"}); - return ; + //const classes = useTagStyles({color : color !== "default" ? color : "#000"}); + return ; } type TagChipProp = Omit & { diff --git a/src/client/package.json b/src/client/package.json index c6bef8c..8424c1a 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -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" + } } diff --git a/src/client/page/404.tsx b/src/client/page/404.tsx index f22ec95..c87d3f8 100644 --- a/src/client/page/404.tsx +++ b/src/client/page/404.tsx @@ -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 = ()=>{ diff --git a/src/client/page/contentinfo.tsx b/src/client/page/contentinfo.tsx index 84dd8c1..e2b4b82 100644 --- a/src/client/page/contentinfo.tsx +++ b/src/client/page/contentinfo.tsx @@ -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({ doc: undefined, notfound:false }); const menu_list = (link?:string) => ; @@ -45,7 +45,7 @@ export const DocumentAbout = (prop: { match: MatchType }) => { } })(); }, []); - const classes = useStyles(); + if (isNaN(id)) { return ( @@ -68,23 +68,20 @@ export const DocumentAbout = (prop: { match: MatchType }) => { } else{ const ReaderPage = getPresenter(info.doc); - return ( - + return ( + - - + + - ); + ); } } \ No newline at end of file diff --git a/src/client/page/difference.tsx b/src/client/page/difference.tsx index 9841355..f1e37c5 100644 --- a/src/client/page/difference.tsx +++ b/src/client/page/difference.tsx @@ -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 ( - + return ( + {x.type} {x.value.map(y=>( - +