全站通知:
MediaWiki:SeedUma Quick.js
刷
历
编
跳到导航
跳到搜索
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。
- Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
- Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
- Internet Explorer或Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
- Opera:按 Ctrl-F5。
//{{React|SeedUma_Quick}}
//本页为种马编辑库-极速版提供:页面显示、服务器切换、筛选逻辑/我的种马 的显示部分
//(筛选逻辑通过隐藏按钮#btn-filter读入#filter-json内的筛选信息json、我的种马通过#btn-mineuma触发)
import FlowDB from "./index.php?title=MediaWiki:FlowDB.js&action=raw&ctype=text/javascript";
import ReactMW from "./index.php?title=MediaWiki:ReactMW-babel.js&action=raw&ctype=text/javascript";
const Parse = ReactMW.Parse;
const Component = React.Component;
const PureComponent = React.PureComponent;
const [R, T, G, Q] = [Symbol("R"), Symbol("T"), Symbol("G"), Symbol("Q")];
const [NODATA, NOFILTER] = [Symbol("NODATA"),Symbol("NOFILTER")];
const _f = new Map();
_f.set(R, new FlowDB(45860));
_f.set(T, new FlowDB(45861));
_f.set(G, new FlowDB(45862));
_f.set(Q, new FlowDB(63773));
const Context = React.createContext({filter: NOFILTER, server: R, sortLike:false});
//入口App组件
class App extends Component {
constructor(props) {
super(props);
this.symbol = Symbol("App");
this.defaultData = function(num) {
let output = [];
for(let i = 0; i < num; i++) {
output.push({NODATA: "NODATA"});
}
return [output];
}
this.defaultPageIdx = 1;
this.defaultCardLimit = 9;
this.state = {
server: R,
defaultCardLimit: this.defaultCardLimit,
data: this.defaultData(this.defaultCardLimit),
pageIdx: this.defaultPageIdx,
pageLimit: 1,
filter: NOFILTER,
cache: new Map(),
sortLike: false
}
}
componentDidMount() {
_f.get(R).listAllTop(this.defaultCardLimit, this.symbol).then(res => this.setState(preState =>{
return {
data: res,
pageLimit: res.length,
cardLimit: res[0].length,
cache: preState.cache.set(R, res)
}
}),err => console.log(err));
// this.setState({
// data: seedDataR,
// pageLimit: seedDataR.length,
// cardLimit: seedDataR[0].length
// });
}
componentWillUnmount() {
_f.get(R).abort(this.symbol);
_f.get(T).abort(this.symbol);
_f.get(G).abort(this.symbol);
_f.get(Q).abort(this.symbol);
}
prePage() {
this.setState((preState) => {
return {pageIdx: preState.pageIdx - 1};
});
this.scrollFit();
}
nextPage() {
this.setState((preState) => {
return {pageIdx: preState.pageIdx + 1};
});
this.scrollFit();
}
gotoPage(idx) {
this.setState({
pageIdx: idx
});
this.scrollFit();
}
scrollFit() {
if ($(".wiki-nav-celling").length !== 0) {
window.scrollTo($(window).scrollLeft(), $($(".card-item")[0]).offset().top-120);
} else {
window.scrollTo($(window).scrollLeft(), $($(".card-item")[0]).offset().top-170);
}
}
handleServerClick(server) {
switch (server){
case R: this.queryServerData(R);break;
case T: this.queryServerData(T);break;
case G: this.queryServerData(G);break;
case Q: this.queryServerData(Q);break;
default: this.queryServerData(R);
}
}
handleSortLike() {
this.setState(preState => {
if (!preState.sortLike) {
let dataBuilder = [];
let list = preState.cache.get(preState.server).flat().sort((a, b) => b.like - a.like);
list.map(card => {
card.order = card.like? -card.like: 0;
return card
});
for(let idx = 0; idx < preState.pageLimit; idx++) {
dataBuilder.push(list.slice(idx * preState.cardLimit, (idx+1) * preState.cardLimit));
}
return {
sortLike: !preState.sortLike,
data: dataBuilder,
pageIdx: 1
}
} else {
return {
sortLike: !preState.sortLike,
data: preState.cache.get(preState.server),
pageIdx: 1
}
}
});
}
queryServerData(server) {
$("#btn-my-seedUma").removeClass("enable")
let seedData = this.defaultData(this.defaultCardLimit);
if (server !== this.state.server) {
this.setState({
data: seedData,
pageLimit: seedData.length,
pageIdx: 1,
cardLimit: seedData[0].length,
filter: NOFILTER,
sortLike: false
});
}
if (server !== this.state.server && !this.state.cache.get(server)) {
_f.get(server).listAllTop(this.defaultCardLimit, this.symbol).then(res => {
this.setState(preState => {
return {
server: server,
data: res,
pageLimit: res.length,
pageIdx: 1,
cardLimit: res.length? res[0].length: 0,
filter: NOFILTER,
cache: preState.cache.set(server, res),
sortLike: false
}
});
},err => console.log(err));
} else if(server !== this.state.server && this.state.cache.get(server)) {
let cache = this.state.cache.get(server);
this.setState({
data: cache,
pageLimit: cache.length,
pageIdx: 1,
cardLimit: cache.length? cache[0].length: 0,
filter: NOFILTER,
sortLike: false
});
}
this.setState({server: server});
// switch (server){
// case R: seedData = seedDataR;break;
// case T: seedData = seedDataT;break;
// case G: seedData = seedDataG;break;
// default: seedData = seedDataR;
// }
}
convertJSON(card) {
let div = $(`<div style="diaplay:none"></div>`);
div.html(card.text);
return JSON.parse(div.text());
}
serverSymbol(serverText) {
switch (serverText){
case "日服": return R;
case "繁中服": return T;
case "简中服": return G;
case "渠道服": return Q;
default: return R;
}
}
serverText(serverSymbol) {
switch (serverSymbol){
case R: return "日服";
case T: return "繁中服";
case G: return "简中服";
case Q: return "渠道服";
default: return "日服";
}
}
updateFilter() {
let filterJSON = JSON.parse($("#filter-json").text());
let filter = Object.keys(filterJSON).includes("NOFILTER")? NOFILTER: filterJSON;
if (filter !== NOFILTER) {
let filterList = this.state.cache.get(this.state.server).map(page => page.filter(card => {
let text = this.convertJSON(card);
let umaMatch = filter.uma.filter(uma => uma === text.uma).length? true: false;
umaMatch = !filter.uma.length || umaMatch;
let requiredMatch = filter.factorRequired.length === filter.factorRequired.filter(factorRequired =>
text.factor.filter(factor => factor.n === factorRequired.n && factor.t === factorRequired.t).length).length;
let factorRequiredPoint = !filter.factorRequired.length? 1: filter.factorRequired.map(factorFilter => {
return text.factor.map(factor => factor.n === factorFilter.n && factor.t === factorFilter.t? parseInt(factor.p): 0).reduce((a, b) => a + b);
}).reduce((a, b) => a + b);
let factorPlusesPoint = !filter.factorPluses.length? 0: filter.factorPluses.map(factorFilter => {
return text.factor.map(factor => factor.n === factorFilter.n && factor.t === factorFilter.t? parseInt(factor.p): 0).reduce((a, b) => a + b);
}).reduce((a, b) => a + b);
let order = -(factorRequiredPoint * 1000 + factorPlusesPoint);
card.order = order;
return umaMatch && requiredMatch && factorRequiredPoint;
}));
filterList = filterList.flat().sort((a, b) => a.order - b.order);
let dataBuilder = [];
let pageNumRaw = filterList.length / this.state.cardLimit
let pageNum = Number.isInteger(pageNumRaw)? pageNumRaw: parseInt(pageNumRaw) + 1;
for(let idx = 0; idx < pageNum; idx++) {
dataBuilder.push(filterList.slice(idx * this.state.cardLimit, (idx+1) * this.state.cardLimit));
}
this.setState({
data: JSON.parse(JSON.stringify(dataBuilder)),
pageLimit: pageNum,
pageIdx: 1,
filter: JSON.parse(JSON.stringify(filter))
});
} else {
const cache = this.state.cache.get(this.state.server)
this.setState({
filter: NOFILTER,
pageLimit: cache.length,
pageIdx: 1,
data: cache
});
}
}
updateMine() {
let mineOnly = $("#btn-my-seedUma").hasClass("enable");
if (mineOnly) {
let mineList = this.state.data.map(page => page.filter(card => {
return document.cookie.includes(card.username)
}));
mineList = mineList.flat();
let dataBuilder = [];
let pageNumRaw = mineList.length / this.state.cardLimit
let pageNum = Number.isInteger(pageNumRaw)? pageNumRaw: parseInt(pageNumRaw) + 1;
for(let idx = 0; idx < pageNum; idx++) {
dataBuilder.push(mineList.slice(idx * this.state.cardLimit, (idx+1) * this.state.cardLimit));
}
this.setState({
data: dataBuilder,
pageLimit: pageNum,
pageIdx: 1
});
} else {
const data = this.state.cache.get(this.state.server)
this.setState({
data: data,
pageLimit: data.length,
pageIdx: 1
});
}
}
updateData(initIdx, ignoreServerCheck) {
let server = this.serverSymbol($("#input-server").val());
// if (this.state.server === server || ignoreServerCheck) {
this.setState({data: this.defaultData(this.defaultCardLimit)});
let serverUpdate = ignoreServerCheck? this.state.server: server;
_f.get(serverUpdate).listAllTop(this.defaultCardLimit, this.symbol).then(res => {
this.setState(preState =>{
return {
server: serverUpdate,
data: res,
pageLimit: res.length,
pageIdx: initIdx? 1: preState.pageIdx,
cardLimit: res.length? res[0].length: 0,
filter: NOFILTER,
cache: preState.cache.set(ignoreServerCheck? preState.server: server, res),
sortLike: false
}
});
},err => console.log(err));
// }
}
updateCache() {
_f.get(this.state.server).listAllTop(this.defaultCardLimit, this.symbol).then(res => {
this.setState(preState => {
return {cache: preState.cache.set(this.state.server, res)}
});
}, err => console.log(err));
}
render() {
return (
<div className="main-area">
<div id="btn-filter" style={{display: "none"}} onClick={(e)=>{this.updateFilter()}}></div>
<div id="btn-mineuma" style={{display: "none"}} onClick={(e)=>{this.updateMine()}}></div>
<div id="btn-update" style={{display: "none"}} onClick={(e)=>{this.updateData(true, false)}}></div>
<div className="map-dh">
<div className="map-dh-left">指定服务器</div>
<input readOnly style={{display:"none"}} id="current-server" value={this.serverText(this.state.server)}></input>
<div className="map-dh-right">
<span className={`map-dh-an ${this.state.server === R? "active":""}`} onClick={()=>this.handleServerClick(R)}>日服{this.state.server === R? "(当前)":""}</span>
<span className={`map-dh-an ${this.state.server === T? "active":""}`} onClick={()=>this.handleServerClick(T)}>繁中服{this.state.server === T? "(当前)":""}</span>
<span className={`map-dh-an ${this.state.server === G? "active":""}`} onClick={()=>this.handleServerClick(G)}>简中服{this.state.server === G? "(当前)":""}</span>
<span className={`map-dh-an ${this.state.server === Q? "active":""}`} onClick={()=>this.handleServerClick(Q)}>渠道服{this.state.server === Q? "(当前)":""}</span>
<span className={`map-dh-an an-sort`} onClick={()=>this.handleSortLike()}>
<div className="glyphicon glyphicon-sort">{this.state.sortLike? "按点赞排序":"按时间排序"}</div>
</span>
</div>
</div>
<Context.Provider value={{filter: this.state.filter, server: this.state.server,
updateData: this.updateData.bind(this), sortLike:this.state.sortLike}}>
<div className="main-content">
{
this.state.data.map((page,idx) => {
return idx+1 === this.state.pageIdx &&
<Page key={`page-${idx}`} data={page} />
})
}
</div>
</Context.Provider>
<div className="main-pagination">
<Pagination defaultPageIdx={this.defaultPageIdx} pageLimit={this.state.pageLimit} pageIdx={this.state.pageIdx}
prePage={this.prePage.bind(this)} nextPage={this.nextPage.bind(this)} gotoPage={this.gotoPage.bind(this)}/>
</div>
</div>
)
}
}
//供翻页器翻页的页面组件
class Page extends PureComponent {
static contextType = Context;
convertJSON(card) {
let div = $(`<div style="diaplay:none"></div>`);
div.html(card.text);
return JSON.parse(div.text());
}
render() {
return (
<div className="main-page">
{this.props.data.map((card, idx) => {
if (card.NODATA) {
return <Card nodata={NODATA} key={`card-${idx}`}/>
} else {
let text = this.convertJSON(card);
return <Card data={card} text={text} key={`card-${card.id}`}
order={card.order && this.context.filter !== NOFILTER? card.order: this.context.sortLike? card.order: -card.timestamp} />
}
})}
</div>
)
}
}
//种马卡片组件
class Card extends Component {
static contextType = Context;
constructor(props) {
super(props);
this.symbol = Symbol("Card");
this.state = {showPop: false, removed: false, delAuth: false};
}
componentDidMount() {
if(this.props.nodata !== NODATA) {
_f.get(this.context.server).isDelAuthority(this.props.data.id)
.then(res => this.setState({delAuth: res}), err => console.log(err));
}
}
componentWillUnmount() {
_f.get(this.context.server).abort(this.symbol);
}
userData() {
return {
id: this.props.data.username,
avatar: this.props.data.avatar,
biliname: this.props.data.uname,
nickname: this.props.text.name,
uid: this.props.text.uid,
}
}
textServer(server) {
switch (server){
case R: return "日服";
case T: return "繁中服";
case G: return "简中服";
case Q: return "渠道服";
default: return "日服";
}
}
handleDel() {
_f.get(this.context.server).remove(this.props.data.id, this.symbol).then(res => {
this.context.updateData(false, true);
}, err => {
$("#hover-3").show();
$("#mb-warning-del").show(300);
this.setState({showPop: false});
})
}
handleLike() {
if (this.state.pending) return;
this.setState({pending: true});
if (!this.state.like) {
_f.get(this.context.server).like(this.props.data.id, this.symbol).then(res => {
this.setState(preState => {
return {like: true, pending: false}
})
}, err => {
console.log(err);
this.setState({pending: false});
});
} else {
_f.get(this.context.server).dislike(this.props.data.id, this.symbol).then(res => {
this.setState(preState => {
return {like: false, pending: false}
})
}, err => {
console.log(err);
this.setState({pending: false});
});
}
}
render() {
return (
<>
{this.props.nodata === NODATA?
<div className="card-item default">
<div className="card-content default">
<div className="uma-avatar default"> </div>
<div className="card-user default">
<div className="user-bilibili default"> </div>
<div className="user-uma default"> </div>
</div>
<div className="card-mark default"> </div>
</div>
<div className="card-panel default">
</div>
</div>:
(!this.state.removed?
<div className="card-item" style={{order: this.props.order}}>
<div className="card-content">
<div className="uma-avatar"><Parse key={`parser`}
wikitext={`[[File:chr_icon_${this.props.text.uma.slice(0, -2)}_${this.props.text.uma}_01.png|90px|link=]]`
/* defaultWidth="74" defaultHeight="76" */}/></div>
<User data={this.userData()} />
<FactorList data={ this.props.text.factor}/>
{this.props.text.mark.length? <div className="card-mark"><span style={{fontWeight:"bold"}}>备注</span> <span>{ this.props.text.mark}</span></div>: null}
</div>
<div className="card-panel">
<div className="panel-info">
<span>{this.textServer(this.context.server)}</span>
<span>{this.props.text.uid}</span>
</div>
{this.state.delAuth?
<div className="panel-del">
<div className="icon-del glyphicon glyphicon-trash" onClick={()=>this.setState({showPop: true})}></div>
<Popup key={`popup-${this.props.data.id}`} show={this.state.showPop} hide={()=>this.setState({showPop: false})} handle={()=>this.handleDel()}/>
</div>: null}
<div className={`panel-like ${this.state.pending? "pending": (this.state.like?"like":"")}`} onClick={()=>this.handleLike()}>
<div className="icon-like glyphicon glyphicon-thumbs-up" style={{width: 25}}><div className="like-num"><sup>{this.state.like?this.props.data.like+1: this.props.data.like}</sup></div></div>
</div>
</div>
</div>: null
)
}
</>
)
}
}
//种马卡片删除弹窗组件
class Popup extends PureComponent {
render() {
return (
<div className={`react-popup-container ${this.props.show ? 'active' : ''}`}>
<div className="react-popup">
<div className="react-popup-info">是否删除?</div>
<div className="react-popup-button">
<div className="btn btn-primary popup-button" onClick={this.props.handle}>确定</div>
<div className="btn btn-warning popup-button" onClick={this.props.hide}>取消</div>
</div>
</div>
</div>
)
}
}
//种马卡片的创建者信息组件
class User extends PureComponent {
render() {
return (
<div className="card-user">
<a className="user-bilibili" href={`https://space.bilibili.com/${this.props.data.id}`} target="_blank" rel="noreferrer">
<div style={{display: "inline-block"}}><img src={this.props.data.avatar/*"https://s1.ax1x.com/2023/01/22/pSJcE5T.png"*/} alt="bili_avatar" width="30" height="30" style={{borderRadius: "50%", width: 30, height: 30}}></img></div>
<div style={{display: "inline-block", fontSize:12, marginLeft:4, wordBreak:"break-all"}}><span>{this.props.data.biliname}</span></div>
</a>
<div className="user-uma">
<div> <span>UID</span> <span style={{color:"#75411a"}}>{this.props.data.uid}</span></div>
<div><span>昵称</span> <span style={{color:"#75411a"}}>{this.props.data.nickname}</span></div>
</div>
</div>
)
}
}
//因子列表组件
class FactorList extends PureComponent {
render() {
return (
<div className="factor-list">
{
this.props.data.map((factor, idx) => {
return <Factor key={`factor-${idx}`} data={factor}/>
})
}
</div>
);
}
}
//因子组件
class Factor extends PureComponent {
static contextType = Context;
convertType(type) {
switch(type) {
case "红": return "red";
case "蓝": return "blue";
case "白": return "white";
default: return "white";
}
}
isFiltered() {
if (this.context.filter && this.context.filter !== NOFILTER) {
return (
this.context.filter.factorRequired.filter(factorFilter => {
return factorFilter.n === this.props.data.n && factorFilter.t === this.props.data.t
}).length +
this.context.filter.factorPluses.filter(factorFilter => {
return factorFilter.n === this.props.data.n && factorFilter.t === this.props.data.t
}).length
)? true: false;
} else {
return false;
}
}
render() {
return (
<div className={`seed-small-c55 seed-${this.convertType(this.props.data.t)} ${this.isFiltered()? "match": ""}`}>
{this.props.data.n}{this.props.data.p}
</div>
)
}
}
//翻页器组件
class Pagination extends PureComponent {
startIdx() {
if (this.props.pageIdx < 3 ) {
return 1;
}
else if (this.props.pageIdx > this.props.pageLimit - 2) {
return this.props.pageLimit - 4 > 0? this.props.pageLimit - 4: 1;
}
else {
return this.props.pageIdx - 2;
}
}
genPageItem() {
let builder =
<>
<div onClick={()=>this.props.prePage()} className={`react-pagination-item react-pagination-back ${this.props.pageIdx > 1? "": "react-pagination-hidden"}`}><</div>
</>;
for (let i = this.startIdx()-1; i < Math.min(this.startIdx()+4, this.props.pageLimit); i++) {
builder = <>{builder}<div onClick={()=>this.props.gotoPage(i+1)}
className={`react-pagination-item ${this.props.pageIdx === i+1? "active": ""}`}>{i+1}</div></>;
}
builder =
<>{builder}
<div onClick={()=>this.props.nextPage()} className={`react-pagination-item react-pagination-fore ${this.props.pageIdx < this.props.pageLimit? "": "react-pagination-hidden"}`}>></div>
</>
return builder;
}
render() {
return (
<div className="react-pagination">
{this.genPageItem()}
</div>
)
}
}
//挂载App
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);