From c807fa04cefeaf64be20377f896427de0badd618 Mon Sep 17 00:00:00 2001 From: xmcp Date: Mon, 20 Aug 2018 17:53:45 +0800 Subject: [PATCH] add sidebar and infinite scroll --- src/App.css | 30 -------------------- src/App.js | 24 +++++++++++++--- src/Flows.css | 42 ++++++++++++++++++++++++++-- src/Flows.js | 73 ++++++++++++++++++++++++++++++++++++------------- src/Sidebar.css | 40 +++++++++++++++++++++++++++ src/Sidebar.js | 18 ++++++++++++ src/Title.js | 31 +++++++++++++-------- src/index.css | 4 +++ 8 files changed, 195 insertions(+), 67 deletions(-) delete mode 100644 src/App.css create mode 100644 src/Sidebar.css create mode 100644 src/Sidebar.js diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 00e5f71..0000000 --- a/src/App.css +++ /dev/null @@ -1,30 +0,0 @@ -.box { - background-color: #fff; - border-radius: 5px; - margin: 1em 0; - padding: .5em; - box-shadow: 0 5px 20px #999; -} - -.left-container .centered-line { - width: calc(100% - 2 * 50px); -} -.flow-item { - flex: 0 0 600px; -} -.flow-reply { - flex: 0 0 300px; - max-height: 15em; - overflow-y: hidden; -} - -.left-container .centered-line, -.left-container .flow-item { - margin-left: 50px; -} - -.flow-item-row { - display: flex; - overflow-x: hidden; - align-items: flex-start; -} \ No newline at end of file diff --git a/src/App.js b/src/App.js index 184d65e..44386d2 100644 --- a/src/App.js +++ b/src/App.js @@ -1,20 +1,36 @@ import React, {Component} from 'react'; -import './App.css'; import {Flow} from './Flows'; import {Title} from './Title'; +import {Sidebar} from './Sidebar'; class App extends Component { - show_details(info) { + constructor(props) { + super(props); + this.state={ + sidebar_title: null, + sidebar_content: null, + }; + } + show_sidebar(title,content) { + this.setState({ + sidebar_title: title, + sidebar_content: content, + }); } render() { return (
- + <Title callback={this.show_sidebar.bind(this)} /> <div className="left-container"> - <Flow callback={(info)=>this.show_details(info)} mode="list" /> + <Flow callback={this.show_sidebar.bind(this)} mode="list" /> </div> + <Sidebar do_close={()=>{ + this.setState({ + sidebar_content: null, + }); + }} content={this.state.sidebar_content} title={this.state.sidebar_title} /> </div> ); } diff --git a/src/Flows.css b/src/Flows.css index 5d09871..d63f455 100644 --- a/src/Flows.css +++ b/src/Flows.css @@ -1,3 +1,42 @@ +.box { + background-color: #fff; + border-radius: 5px; + margin: 1em 0; + padding: .5em; + box-shadow: 0 5px 20px #999; +} + +.left-container .centered-line { + width: calc(100% - 2 * 50px); +} +.flow-item { + flex: 0 0 600px; +} +:not(.sidebar-flow-item) .flow-reply { + flex: 0 0 300px; + max-height: 15em; + margin-left: -5px; + overflow-y: hidden; +} + +.left-container .centered-line, +.left-container .flow-item { + margin-left: 50px; +} + +.flow-item-row { + transition: margin-left 200ms ease-out; +} +:not(.sidebar-flow-item).flow-item-row:hover { + margin-left: -10px; +} + +:not(.sidebar-flow-item).flow-item-row { + display: flex; + overflow-x: hidden; + align-items: flex-start; +} + .flow-item-row img { max-width: 100%; } @@ -9,10 +48,7 @@ .box-header-badge { float: right; - border: 1px solid black; - border-radius: 7px; margin: 0 .5em; - padding: 0 .5em; } .box-id { diff --git a/src/Flows.js b/src/Flows.js index 8f24c03..d9f38de 100644 --- a/src/Flows.js +++ b/src/Flows.js @@ -25,7 +25,23 @@ function ReplyPlaceholder(props) { ); } -class FlowChunkItem extends Component { +function FlowItem(props) { + return ( + <div className="flow-item box"> + <div className="box-header"> + {parseInt(props.info.likenum, 10) && <span className="box-header-badge">{props.info.likenum}★</span>} + {parseInt(props.info.reply, 10) && <span className="box-header-badge">{props.info.reply} 回复</span>} + <span className="box-id">#{props.info.pid}</span>  + <Time stamp={props.info.timestamp} /> + </div> + <pre>{props.info.text}</pre> + {props.info.type==='image' ? <img src={IMAGE_BASE+props.info.url} /> : null} + {props.info.type==='audio' ? <audio src={AUDIO_BASE+props.info.url} /> : null} + </div> + ); +} + +class FlowItemRow extends Component { constructor(props) { super(props); this.state={ @@ -33,20 +49,21 @@ class FlowChunkItem extends Component { reply_loading: false, }; this.info=props.info; - if(props.info.reply) { + if(parseInt(props.info.reply,10)) { this.state.reply_loading=true; this.load_replies(); } } load_replies() { + console.log('fetching reply',this.info.pid); fetch('http://www.pkuhelper.com:10301/pkuhelper/../services/pkuhole/api.php?action=getcomment&pid='+this.info.pid) .then((res)=>res.json()) .then((json)=>{ if(json.code!==0) throw new Error(json.code); this.setState({ - replies: json.data, + replies: json.data.slice(0,10), reply_loading: false, }); }); @@ -55,18 +72,14 @@ class FlowChunkItem extends Component { render() { // props.do_show_details return ( - <div className="flow-item-row"> - <div className="flow-item box"> - <div className="box-header"> - <span className="box-header-badge">{this.info.likenum} 赞</span> - <span className="box-header-badge">{this.info.reply} 回复</span> - <span className="box-id">#{this.info.pid}</span>  - <Time stamp={this.info.timestamp} /> - </div> - <pre>{this.info.text}</pre> - {this.info.type==='image' ? <img src={IMAGE_BASE+this.info.url} /> : null} - {this.info.type==='audio' ? <audio src={AUDIO_BASE+this.info.url} /> : null} + <div className="flow-item-row" onClick={()=>{this.props.callback( + '帖子详情', + <div className="flow-item-row sidebar-flow-item"> + <FlowItem info={this.info} /> + {this.state.replies.map((reply)=><Reply info={reply} key={reply.cid} />)} </div> + )}}> + <FlowItem info={this.info} /> {this.state.reply_loading && <ReplyPlaceholder count={this.info.reply} />} {this.state.replies.map((reply)=><Reply info={reply} key={reply.cid} />)} </div> @@ -78,7 +91,7 @@ function FlowChunk(props) { return ( <div className="flow-chunk"> <CenteredLine text={props.title} /> - {props.list.map((info)=><FlowChunkItem key={info.pid} info={info} callback={props.callback} />)} + {props.list.map((info)=><FlowItemRow key={info.pid} info={info} callback={props.callback} />)} </div> ); } @@ -89,9 +102,10 @@ export class Flow extends Component { this.state={ mode: props.mode, loaded_pages: 0, - chunks: [] + chunks: [], + loading: false, }; - this.load_page(1); + setTimeout(this.load_page.bind(this,1), 0); } load_page(page) { @@ -100,7 +114,8 @@ export class Flow extends Component { if(page===this.state.loaded_pages+1) { console.log('fetching page',page); this.setState((prev,props)=>({ - loaded_pages: prev.loaded_pages+1 + loaded_pages: prev.loaded_pages+1, + loading: true, })); fetch('http://www.pkuhelper.com:10301/pkuhelper/../services/pkuhole/api.php?action=getlist&p='+page) .then((res)=>res.json()) @@ -111,7 +126,8 @@ export class Flow extends Component { chunks: prev.chunks.concat([{ title: 'Page '+page, data: json.data, - }]) + }]), + loading: false, })); }) .catch((err)=>{ @@ -121,12 +137,31 @@ export class Flow extends Component { } } + on_scroll(event) { + if(event.target===document) { + //console.log(event); + const avail=document.body.scrollHeight-window.scrollY-window.innerHeight; + if(avail<window.innerHeight && this.state.loading===false) + this.load_page(this.state.loaded_pages+1); + } + } + + componentDidMount() { + window.addEventListener('scroll',this.on_scroll.bind(this)); + window.addEventListener('resize',this.on_scroll.bind(this)); + } + componentWillUnmount() { + window.removeEventListener('scroll',this.on_scroll.bind(this)); + window.removeEventListener('resize',this.on_scroll.bind(this)); + } + render() { return ( <div className="flow-container"> {this.state.chunks.map((chunk)=>( <FlowChunk title={chunk.title} list={chunk.data} key={chunk.title} callback={this.props.callback} /> ))} + <CenteredLine text={this.state.loading ? 'Loading More...' : '© xmcp'} /> </div> ); } diff --git a/src/Sidebar.css b/src/Sidebar.css new file mode 100644 index 0000000..f5bfb4d --- /dev/null +++ b/src/Sidebar.css @@ -0,0 +1,40 @@ +.sidebar-shadow { + opacity: 0; + background-color: black; + pointer-events: none; + transition: opacity 200ms ease-out; + position: fixed; + left: 0; + top: 0; + height: 100%; + width: 100%; + z-index: 2; +} +.sidebar-on .sidebar-shadow { + opacity: .3; + pointer-events: initial; +} + +.sidebar { + transition: left 200ms ease-out; + position: fixed; + left: 100%; + top: 0; + height: 100%; + width: calc(100% - 700px); + padding: 1em; + background-color: #fff; + z-index: 3; + overflow-y: auto; +} +.sidebar-on .sidebar { + left: 700px; +} + +.sidebar-flow-item { + display: block; +} +.sidebar-flow-item .box { + margin: 1em; + width: calc(100% - 2em); +} \ No newline at end of file diff --git a/src/Sidebar.js b/src/Sidebar.js new file mode 100644 index 0000000..3a5e0bf --- /dev/null +++ b/src/Sidebar.js @@ -0,0 +1,18 @@ +import React, {Component} from 'react'; +import './Sidebar.css'; + +export function Sidebar(props) { + return ( + <div className={props.content ? 'sidebar-on' : ''}> + <div className="sidebar-shadow" onClick={props.do_close} /> + <div className="sidebar"> + <p> + <a onClick={props.do_close}>×</a> +  {props.title} + </p> + <hr /> + {props.content} + </div> + </div> + ); +} \ No newline at end of file diff --git a/src/Title.js b/src/Title.js index 3653168..e6c11c2 100644 --- a/src/Title.js +++ b/src/Title.js @@ -1,22 +1,31 @@ import React, {Component} from 'react'; import './Title.css'; -const tos=`P大树洞网页版 - -使用本网站时,您需要了解并同意: - -- 所有数据来自 PKU Helper,本站不对其内容负责 -- 不接受关于修改 UI 的建议 -- 英梨梨是我的,你们都不要抢`; - export function Title(props) { return ( <div className="title"> <div className="title-links"> - <a onClick={()=>{alert(tos);}}>ToS</a> - <a href="https://github.com/xmcp/ashole">GitHub</a> + <a onClick={()=>{props.callback( + '关于 P大树洞(非官方) 网页版', + <div> + <p>使用提示:</p> + <ul> + <li>为保证使用体验,请使用分辨率恰好为 1920*1080 像素的电脑,并用 Chrome 浏览器 stable 分支最新版</li> + <li>在列表中点击帖子可以显示全部回复</li> + <li>搜索帖子功能正在开发中</li> + </ul> + <br /> + <p>使用本网站时,您需要了解并同意:</p> + <ul> + <li>所有数据来自 PKU Helper,本站不对其内容负责</li> + <li>不接受关于修改 UI 的建议</li> + <li>英梨梨是我的,你们都不要抢</li> + </ul> + </div> + )}}>Help</a> + <a href="https://github.com/xmcp/ashole" target="_blank">GitHub</a> </div> - P大树洞 + P大树洞(非官方) </div> ) } \ No newline at end of file diff --git a/src/index.css b/src/index.css index 413b3a7..be5a041 100644 --- a/src/index.css +++ b/src/index.css @@ -5,6 +5,10 @@ body { background-color: #eee; } +body::-webkit-scrollbar { + display: none; +} + * { box-sizing: border-box; }