Browse Source

update

- add image quality indicator
- add error tip
- minor enhancement
dev
xmcp 6 years ago
parent
commit
47be6d49f6
  1. 30
      src/Flows.js
  2. 5
      src/UserAction.css
  3. 84
      src/UserAction.js
  4. 10
      src/flows_api.js
  5. 2
      src/text_splitter.js

30
src/Flows.js

@ -48,7 +48,8 @@ function load_single_meta(show_sidebar,token) {
show_sidebar( show_sidebar(
'帖子详情', '帖子详情',
<div className="box box-tip"> <div className="box box-tip">
<a onClick={()=>load_single_meta(show_sidebar,token)}>重新加载</a> <p><a onClick={()=>load_single_meta(show_sidebar,token)()}>重新加载</a></p>
<p>{''+e}</p>
</div> </div>
); );
}) })
@ -139,6 +140,7 @@ class FlowSidebar extends PureComponent {
info: props.info, info: props.info,
replies: props.replies, replies: props.replies,
loading_status: 'done', loading_status: 'done',
error_msg: null,
}; };
this.color_picker=props.color_picker; this.color_picker=props.color_picker;
this.show_pid=load_single_meta(this.props.show_sidebar,this.props.token); this.show_pid=load_single_meta(this.props.show_sidebar,this.props.token);
@ -172,6 +174,7 @@ class FlowSidebar extends PureComponent {
load_replies(update_count=true) { load_replies(update_count=true) {
this.setState({ this.setState({
loading_status: 'loading', loading_status: 'loading',
error_msg: null,
}); });
API.load_replies(this.state.info.pid,this.props.token,this.color_picker) API.load_replies(this.state.info.pid,this.props.token,this.color_picker)
.then((json)=>{ .then((json)=>{
@ -182,6 +185,7 @@ class FlowSidebar extends PureComponent {
}) : prev.info, }) : prev.info,
attention: !!json.attention, attention: !!json.attention,
loading_status: 'done', loading_status: 'done',
error_msg: null,
}), ()=>{ }), ()=>{
this.syncState({ this.syncState({
replies: this.state.replies, replies: this.state.replies,
@ -195,6 +199,7 @@ class FlowSidebar extends PureComponent {
this.setState({ this.setState({
replies: [], replies: [],
loading_status: 'done', loading_status: 'done',
error_msg: ''+e,
}); });
}); });
} }
@ -240,8 +245,13 @@ class FlowSidebar extends PureComponent {
show_reply_bar(name,event) { show_reply_bar(name,event) {
if(this.reply_ref.current && event.target.tagName.toLowerCase()!=='a') { if(this.reply_ref.current && event.target.tagName.toLowerCase()!=='a') {
let text=this.reply_ref.current.get(); let text=this.reply_ref.current.get();
if(/^\s*(Re (洞主|\b[A-Z][a-z]+){0,2}:)?\s*$/.test(text)) // text is nearly empty so we can replace it if(/^\s*(Re (洞主|\b[A-Z][a-z]+){0,2}:)?\s*$/.test(text)) {// text is nearly empty so we can replace it
this.reply_ref.current.set('Re '+name+': '); let should_text='Re '+name+': ';
if(should_text===this.reply_ref.current.get())
this.reply_ref.current.set('');
else
this.reply_ref.current.set(should_text);
}
} }
} }
@ -278,7 +288,13 @@ class FlowSidebar extends PureComponent {
set_variant={(variant)=>{this.set_variant(null,variant);}} set_variant={(variant)=>{this.set_variant(null,variant);}}
/> />
</ClickHandler> </ClickHandler>
{(this.props.deletion_detect && parseInt(this.state.info.reply)>this.state.replies.length) && {!!this.state.error_msg &&
<div className="box box-tip flow-item box-danger">
<p>回复加载失败</p>
<p>{this.state.error_msg}</p>
</div>
}
{(this.props.deletion_detect && parseInt(this.state.info.reply)>this.state.replies.length) && !!this.state.replies.length &&
<div className="box box-tip flow-item box-danger"> <div className="box box-tip flow-item box-danger">
{parseInt(this.state.info.reply)-this.state.replies.length} 条回复被删除 {parseInt(this.state.info.reply)-this.state.replies.length} 条回复被删除
</div> </div>
@ -422,6 +438,7 @@ export class Flow extends PureComponent {
data: [], data: [],
}, },
loading_status: 'done', loading_status: 'done',
error_msg: null,
}; };
this.on_scroll_bound=this.on_scroll.bind(this); this.on_scroll_bound=this.on_scroll.bind(this);
window.LATEST_POST_ID=parseInt(localStorage['_LATEST_POST_ID'],10)||0; window.LATEST_POST_ID=parseInt(localStorage['_LATEST_POST_ID'],10)||0;
@ -433,6 +450,7 @@ export class Flow extends PureComponent {
this.setState((prev,props)=>({ this.setState((prev,props)=>({
loaded_pages: prev.loaded_pages-1, loaded_pages: prev.loaded_pages-1,
loading_status: 'failed', loading_status: 'failed',
error_msg: ''+err,
})); }));
}; };
@ -508,6 +526,7 @@ export class Flow extends PureComponent {
this.setState((prev,props)=>({ this.setState((prev,props)=>({
loaded_pages: prev.loaded_pages+1, loaded_pages: prev.loaded_pages+1,
loading_status: 'loading', loading_status: 'loading',
error_msg: null,
})); }));
} }
} }
@ -540,7 +559,8 @@ export class Flow extends PureComponent {
/> />
{this.state.loading_status==='failed' && {this.state.loading_status==='failed' &&
<div className="box box-tip aux-margin"> <div className="box box-tip aux-margin">
<a onClick={()=>{this.load_page(this.state.loaded_pages+1)}}>重新加载</a> <p><a onClick={()=>{this.load_page(this.state.loaded_pages+1)}}>重新加载</a></p>
<p>{this.state.error_msg}</p>
</div> </div>
} }
<TitleLine text={ <TitleLine text={

5
src/UserAction.css

@ -42,6 +42,11 @@
.post-form-bar button { .post-form-bar button {
flex: 0 0 8em; flex: 0 0 8em;
} }
.post-form-img-tip {
font-size: small;
margin-top: -.5em;
margin-bottom: .5em;
}
.post-form textarea { .post-form textarea {
resize: vertical; resize: vertical;
width: 100%; width: 100%;

84
src/UserAction.js

@ -7,8 +7,8 @@ import './UserAction.css';
import {API_BASE} from './Common'; import {API_BASE} from './Common';
const LOGIN_BASE=PKUHELPER_ROOT+'services/login'; const LOGIN_BASE=PKUHELPER_ROOT+'services/login';
const MAX_IMG_PX=2000; const MAX_IMG_PX=2500;
const MAX_IMG_FILESIZE=256000; const MAX_IMG_FILESIZE=300000;
const ISOP_APPKEY='0feb3a8a831e11e8933a0050568508a5'; const ISOP_APPKEY='0feb3a8a831e11e8933a0050568508a5';
const ISOP_APPCODE='0fec960a831e11e8933a0050568508a5'; const ISOP_APPCODE='0fec960a831e11e8933a0050568508a5';
@ -265,10 +265,12 @@ export class PostForm extends Component {
this.state={ this.state={
text: '', text: '',
loading_status: 'done', loading_status: 'done',
img_tip: null,
}; };
this.img_ref=React.createRef(); this.img_ref=React.createRef();
this.area_ref=React.createRef(); this.area_ref=React.createRef();
this.on_change_bound=this.on_change.bind(this); this.on_change_bound=this.on_change.bind(this);
this.on_img_change_bound=this.on_img_change.bind(this);
} }
componentDidMount() { componentDidMount() {
@ -327,7 +329,7 @@ export class PostForm extends Component {
if(idx===-1) if(idx===-1)
throw new Error('img not base64 encoded'); throw new Error('img not base64 encoded');
resolve(url.substr(idx+8)); return url.substr(idx+8);
} }
let reader=new FileReader(); let reader=new FileReader();
@ -339,13 +341,16 @@ export class PostForm extends Component {
image.onload=(()=>{ image.onload=(()=>{
let width=image.width; let width=image.width;
let height=image.height; let height=image.height;
let compressed=false;
if(width>MAX_IMG_PX) { if(width>MAX_IMG_PX) {
height=height*MAX_IMG_PX/width; height=height*MAX_IMG_PX/width;
width=MAX_IMG_PX; width=MAX_IMG_PX;
compressed=true;
} }
if(height>MAX_IMG_PX) { if(height>MAX_IMG_PX) {
width=width*MAX_IMG_PX/height; width=width*MAX_IMG_PX/height;
height=MAX_IMG_PX; height=MAX_IMG_PX;
compressed=true;
} }
let canvas=document.createElement('canvas'); let canvas=document.createElement('canvas');
@ -354,23 +359,58 @@ export class PostForm extends Component {
canvas.height=height; canvas.height=height;
ctx.drawImage(image,0,0,width,height); ctx.drawImage(image,0,0,width,height);
for(let quality=.9;quality>0;quality-=0.1) { let quality_l=.1,quality_r=.9,quality,new_url;
const url=canvas.toDataURL('image/jpeg',quality); while(quality_r-quality_l>=.06) {
console.log('quality',quality,'size',url.length); quality=(quality_r+quality_l)/2;
if(url.length<=MAX_IMG_FILESIZE) { new_url=canvas.toDataURL('image/jpeg',quality);
console.log('chosen img quality',quality); console.log(quality_l,quality_r,'trying quality',quality,'size',new_url.length);
return return_url(url); if(new_url.length<=MAX_IMG_FILESIZE)
} quality_l=quality;
else
quality_r=quality;
}
if(quality_l>=.101) {
console.log('chosen img quality',quality);
resolve({
img: return_url(new_url),
quality: quality,
width: Math.round(width),
height: Math.round(height),
compressed: compressed,
});
} else {
reject('图片过大,无法上传');
} }
// else
alert('图片过大,无法上传');
reject('img too large');
}); });
}); });
reader.readAsDataURL(file); reader.readAsDataURL(file);
}); });
} }
on_img_change() {
if(this.img_ref.current && this.img_ref.current.files.length)
this.setState({
img_tip: '(正在处理图片……)'
},()=>{
this.proc_img(this.img_ref.current.files[0])
.then((d)=>{
this.setState({
img_tip: `${d.compressed?'压缩到':'尺寸'} ${d.width}*${d.height} / `+
`质量 ${Math.floor(d.quality*100)}% / ${Math.floor(d.img.length/1000)}KB)`,
});
})
.catch((e)=>{
this.setState({
img_tip: `图片无效:${e}`,
});
});
});
else
this.setState({
img_tip: null,
});
}
on_submit(event) { on_submit(event) {
if(event) event.preventDefault(); if(event) event.preventDefault();
if(this.state.loading_status==='loading') if(this.state.loading_status==='loading')
@ -380,12 +420,15 @@ export class PostForm extends Component {
loading_status: 'processing', loading_status: 'processing',
}); });
this.proc_img(this.img_ref.current.files[0]) this.proc_img(this.img_ref.current.files[0])
.then((img)=>{ .then((d)=>{
this.setState({ this.setState({
loading_status: 'loading', loading_status: 'loading',
}); });
this.do_post(this.state.text,img); this.do_post(this.state.text,d.img);
}) })
.catch((e)=>{
alert(e);
});
} else { } else {
this.setState({ this.setState({
loading_status: 'loading', loading_status: 'loading',
@ -400,8 +443,9 @@ export class PostForm extends Component {
<div className="post-form-bar"> <div className="post-form-bar">
<label> <label>
图片 图片
<input ref={this.img_ref} type="file" accept="image/*" <input ref={this.img_ref} type="file" accept="image/*" disabled={this.state.loading_status!=='done'}
{...this.state.loading_status!=='done' ? {disabled: true} : {}} /> onChange={this.on_img_change_bound}
/>
</label> </label>
{this.state.loading_status!=='done' ? {this.state.loading_status!=='done' ?
<button disabled="disabled"> <button disabled="disabled">
@ -414,6 +458,12 @@ export class PostForm extends Component {
</button> </button>
} }
</div> </div>
{!!this.state.img_tip &&
<p className="post-form-img-tip">
<a onClick={()=>{this.img_ref.current.value=""; this.on_img_change();}}>删除图片</a>
{this.state.img_tip}
</p>
}
<SafeTextarea ref={this.area_ref} id="new_post" on_change={this.on_change_bound} on_submit={this.on_submit.bind(this)} /> <SafeTextarea ref={this.area_ref} id="new_post" on_change={this.on_change_bound} on_submit={this.on_submit.bind(this)} />
</form> </form>
) )

10
src/flows_api.js

@ -17,8 +17,10 @@ export const API={
) )
.then((res)=>res.json()) .then((res)=>res.json())
.then((json)=>{ .then((json)=>{
if(json.code!==0) if(json.code!==0) {
throw new Error(json); if(json.msg) throw new Error(json.msg);
else throw new Error(json);
}
json.data=json.data json.data=json.data
.sort((a,b)=>{ .sort((a,b)=>{
@ -121,8 +123,8 @@ export const API={
.then((res)=>res.json()) .then((res)=>res.json())
.then((json)=>{ .then((json)=>{
if(json.code!==0) { if(json.code!==0) {
if(json.msg) alert(json.msg); if(json.msg) throw new Error(json.msg);
throw new Error(json); else throw new Error(json);
} }
return json; return json;
}); });

2
src/text_splitter.js

@ -1,6 +1,6 @@
export const PID_RE=/(^|[^\d])([1-9]\d{4,5})(?!\d|\u20e3)/g; export const PID_RE=/(^|[^\d])([1-9]\d{4,5})(?!\d|\u20e3)/g;
export const NICKNAME_RE=/(^|[^A-Za-z])((?:(?:Angry|Baby|Crazy|Diligent|Excited|Fat|Greedy|Hungry|Interesting|Japanese|Kind|Little|Magic|Naïve|Old|Powerful|Quiet|Rich|Superman|THU|Undefined|Valuable|Wifeless|Xiangbuchulai|Young|Zombie)\s)?(?:Alice|Bob|Carol|Dave|Eve|Francis|Grace|Hans|Isabella|Jason|Kate|Louis|Margaret|Nathan|Olivia|Paul|Queen|Richard|Susan|Thomas|Uma|Vivian|Winnie|Xander|Yasmine|Zach)|You Win(?: \d+)?|洞主)(?![A-Za-z])/gi; export const NICKNAME_RE=/(^|[^A-Za-z])((?:(?:Angry|Baby|Crazy|Diligent|Excited|Fat|Greedy|Hungry|Interesting|Japanese|Kind|Little|Magic|Naïve|Old|Powerful|Quiet|Rich|Superman|THU|Undefined|Valuable|Wifeless|Xiangbuchulai|Young|Zombie)\s)?(?:Alice|Bob|Carol|Dave|Eve|Francis|Grace|Hans|Isabella|Jason|Kate|Louis|Margaret|Nathan|Olivia|Paul|Queen|Richard|Susan|Thomas|Uma|Vivian|Winnie|Xander|Yasmine|Zach)|You Win(?: \d+)?|洞主)(?![A-Za-z])/gi;
export const URL_RE=/(?:^|\b)((?:https?:\/\/)?(?:[\w-]+\.)+[a-zA-Z]{2,3}(?::\d{1,5})?(?:\/[\w~!@#$%^&*()-_=+[\];,./?]*)?)(?:$|\b)/gi; export const URL_RE=/(?:^|\b)((?:https?:\/\/)?(?:[\w-]+\.)+[a-zA-Z]{2,3}(?::\d{1,5})?(?:\/[\w~!@#$%^&*()-_=+[\];,./?]*)?)/gi;
export function split_text(txt,rules) { export function split_text(txt,rules) {
// rules: [['name',/regex/],...] // rules: [['name',/regex/],...]

Loading…
Cancel
Save