本WIKI由呜呜kurumi申请于2021年03月15日创建,编辑权限开放

如有内容错误,可以联系站长呜呜kurumi提交错误,赛马娘WIKI力求给大家带来最好的体验,也欢迎训练员们和我们一起建设
bugfix0531
全站通知:

MediaWiki:SeedUma Quick.js

来自赛马娘WIKI_BWIKI_哔哩哔哩
跳到导航 跳到搜索

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-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">&nbsp;</div>
              <div className="card-user default">
                <div className="user-bilibili default">&nbsp;</div>
                <div className="user-uma default">&nbsp;</div>
              </div>
              <div className="card-mark default">&nbsp;</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>&nbsp;<span>{ this.props.text.mark}</span></div>: null}
            </div>
            <div className="card-panel">
              <div className="panel-info">
                <span>{this.textServer(this.context.server)}</span>&nbsp;
                <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>&nbsp;<span>UID</span>&nbsp;&nbsp;<span style={{color:"#75411a"}}>{this.props.data.uid}</span></div>
          <div><span>昵称</span>&nbsp;&nbsp;<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"}`}>&lt;</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"}`}>&gt;</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>
);