forked from newthuhole/hole_thu_frontend
update
- add image quality indicator - add error tip - minor enhancement
This commit is contained in:
30
src/Flows.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={
|
||||||
|
|||||||
@@ -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%;
|
||||||
|
|||||||
@@ -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(quality_l,quality_r,'trying quality',quality,'size',new_url.length);
|
||||||
|
if(new_url.length<=MAX_IMG_FILESIZE)
|
||||||
|
quality_l=quality;
|
||||||
|
else
|
||||||
|
quality_r=quality;
|
||||||
|
}
|
||||||
|
if(quality_l>=.101) {
|
||||||
console.log('chosen img quality',quality);
|
console.log('chosen img quality',quality);
|
||||||
return return_url(url);
|
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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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/],...]
|
||||||
|
|||||||
Reference in New Issue
Block a user