Commit 5f8f965b authored by 神楽坂玲奈's avatar 神楽坂玲奈

结算页面

parent 8b5f653a
{
"name": "mycard-mobile",
"version": "0.0.0",
"version": "1.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......@@ -222,6 +222,12 @@
"source-map": "0.5.6"
}
},
"@types/lodash": {
"version": "4.14.70",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.70.tgz",
"integrity": "sha512-uvDjWW3m7SFUhSpohfQvj32gRsVgRxA0Z0OfMJh8m/JuX4YJLHeEwRBuHJgvNgTAVBpJDFKVFeyrREuMwkE9Qw==",
"dev": true
},
"@types/node": {
"version": "8.0.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.13.tgz",
......
{
"name": "mycard-mobile",
"version": "1.0.0",
"version": "1.0.2",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --base-href /mobile/index.html --deploy-url /mobile --locale zh-CN --output-path mobile --open",
"build": "ng build --base-href /mobile/index.html --locale zh-CN --aot --prod",
"test": "echo \"Error: no test... minimal project\" && exit 1",
"lint": "echo \"Error: no lint... minimal project\" && exit 1",
"e2e": "echo \"Error: no e2e... minimal project\" && exit 1"
"build": "ng build --base-href /mobile2/index.html --locale zh-CN --aot --prod",
"lint": "ng lint"
},
"private": true,
"dependencies": {
......@@ -37,6 +35,7 @@
"@angular/compiler-cli": "^4.0.0",
"@angular/language-service": "^4.0.0",
"@angular/service-worker": "^1.0.0-beta.16",
"@types/lodash": "^4.14.70",
"@types/node": "^8.0.13",
"codelyzer": "^3.1.2",
"tslint": "^5.5.0",
......
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'app-root',
template: `<router-outlet></router-outlet>`,
template: `
<router-outlet></router-outlet>`,
styles: []
})
export class AppComponent {
......
......@@ -32,6 +32,7 @@ import { ToolbarComponent } from './toolbar/toolbar.component';
import { WatchComponent } from './watch/watch.component';
import { WindbotComponent } from './windbot/windbot.component';
import { YGOProService } from './ygopro.service';
import { ResultDialog } from './result/result.dialog';
@NgModule({
declarations: [
......@@ -43,6 +44,7 @@ import { YGOProService } from './ygopro.service';
WindbotComponent,
WatchComponent,
ToolbarComponent,
ResultDialog,
],
imports: [
BrowserModule,
......@@ -71,7 +73,7 @@ import { YGOProService } from './ygopro.service';
],
providers: [YGOProService],
bootstrap: [AppComponent],
entryComponents: [MatchDialog],
entryComponents: [MatchDialog, ResultDialog],
})
export class AppModule {
}
......@@ -32,7 +32,7 @@ input::placeholder {
/*not works*/
/*input::-webkit-search-cancel-button {*/
/*color: lightgray;*/
/*color: lightgray;*/
/*}*/
md-input-container {
......@@ -89,3 +89,13 @@ a {
color: inherit;
text-decoration: inherit;
}
#points dt, #points dd {
width: 56px;
font-size: 14px;
}
#points dd {
text-align: right;
margin: 0
}
......@@ -19,7 +19,7 @@
<md-icon>add</md-icon>
</button>
<md-menu #menu="mdMenu">
<button md-menu-item>Version 1.0.0</button>
<button md-menu-item>大厅版本 {{version}}</button>
</md-menu>
</md-toolbar>
......@@ -74,6 +74,82 @@
</md-grid-tile>
</md-grid-list>
<md-card *ngIf="ygopro.points">
<md-grid-list id="points" cols="4" rowHeight="20px">
<md-grid-tile>
<dt>竞技排名</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.arena_rank}}</dd>
</md-grid-tile>
<md-grid-tile>
<dt>娱乐排名</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.exp_rank}}</dd>
</md-grid-tile>
<md-grid-tile>
<dt>竞技胜率</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.athletic_wl_ratio}}%</dd>
</md-grid-tile>
<md-grid-tile>
<dt>经验</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.exp}}</dd>
</md-grid-tile>
<md-grid-tile>
<dt>胜场</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.athletic_win}}</dd>
</md-grid-tile>
<md-grid-tile>
<dt>胜场</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.entertain_win}}</dd>
</md-grid-tile>
<md-grid-tile>
<dt>负场</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.athletic_lose}}</dd>
</md-grid-tile>
<md-grid-tile>
<dt>负场</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.entertain_lose}}</dd>
</md-grid-tile>
<md-grid-tile>
<dt>平局</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.athletic_draw}}</dd>
</md-grid-tile>
<md-grid-tile>
<dt>平局</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.entertain_draw}}</dd>
</md-grid-tile>
<md-grid-tile>
<dt>总场</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.athletic_all}}</dd>
</md-grid-tile>
<md-grid-tile>
<dt>总场</dt>
</md-grid-tile>
<md-grid-tile>
<dd>{{ygopro.points.entertain_all}}</dd>
</md-grid-tile>
</md-grid-list>
</md-card>
<md-card *ngFor="let item of ygopro.news" class="example-card">
<a [href]="item.url" target="_blank">
<md-card-header>
......@@ -86,10 +162,11 @@
<md-card *ngFor="let item of ygopro.topics | async">
<a [href]="item.url" target="_blank">
<md-card-header>
<img *ngIf="item.image_url" md-card-avatar [src]="item.image_url">
<md-card-title>{{item.title}}</md-card-title>
<md-card-subtitle>by {{item.last_poster_username}} / {{item.last_posted_at | date:"mediumDate"}}</md-card-subtitle>
</md-card-header>
<md-card-header>
<img *ngIf="item.image_url" md-card-avatar [src]="item.image_url">
<md-card-title>{{item.title}}</md-card-title>
<md-card-subtitle>by {{item.last_poster_username}} / {{item.last_posted_at | date:"mediumDate"}}
</md-card-subtitle>
</md-card-header>
</a>
</md-card>
......@@ -3,22 +3,24 @@ import { FormControl } from '@angular/forms';
import { Http, Jsonp } from '@angular/http';
import { MdDialog } from '@angular/material';
import { ActivatedRoute } from '@angular/router';
import 'rxjs/add/operator/map';
// import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import { environment } from '../../environments/environment';
import { LoginService } from '../login.service';
import { MatchDialog } from '../match/match.component';
import { YGOProService } from '../ygopro.service';
import { routerTransition2 } from '../router.animations';
import { YGOProService } from '../ygopro.service';
@Component({
selector: 'app-lobby',
templateUrl: 'lobby.component.html',
styleUrls: ['lobby.component.css'],
animations: [routerTransition2],
host: {'[@routerTransition2]': ''}
host: { '[@routerTransition2]': '' }
})
export class LobbyComponent {
version = environment.version;
searchCtrl = new FormControl();
suggestion = this.searchCtrl.valueChanges.filter(name => name).flatMap(name => this.jsonp.get('http://www.ourocg.cn/Suggest.aspx', {
params: { callback: 'JSONP_CALLBACK', key: name }
......@@ -36,15 +38,14 @@ export class LobbyComponent {
}
search(key) {
search(key: string) {
const url = new URL('http://www.ourocg.cn/S.aspx');
url.searchParams.set('key', key);
open(url.toString());
}
async request_match(arena: string) {
request_match(arena: string) {
this.dialog.open(MatchDialog, { data: arena, disableClose: true });
}
}
......@@ -46,11 +46,11 @@ export class LoginService {
callback(token: string) {
this.token = token;
this.user = fromPairs(Array.from(new URLSearchParams(Buffer.from(token, 'base64').toString())));
this.user = <any>fromPairs(Array.from(new URLSearchParams(Buffer.from(token, 'base64').toString())));
localStorage.setItem('login', token);
}
avatar(username) {
avatar(username: string) {
return 'https://ygobbs.com/user_avatar/ygobbs.com/' + username + '/25/1.png';
}
......
import { Component, ElementRef, ViewChild } from '@angular/core';
import { MdSnackBar } from '@angular/material';
import { LoginService } from '../login.service';
import { YGOProService } from '../ygopro.service';
import { routerTransition } from '../router.animations';
import { YGOProService } from '../ygopro.service';
@Component({
......@@ -10,7 +10,7 @@ import { routerTransition } from '../router.animations';
templateUrl: 'new-room.component.html',
styleUrls: ['new-room.component.css'],
animations: [routerTransition],
host: {'[@routerTransition]': ''}
host: { '[@routerTransition]': '' }
})
export class NewRoomComponent {
......@@ -34,7 +34,7 @@ export class NewRoomComponent {
this.hostPasswordRef.nativeElement.select();
if (document.execCommand('copy')) {
this.snackBar.open(`房间密码 ${host_password} 已复制到剪贴板`, null, { duration: 3000 });
this.snackBar.open(`房间密码 ${host_password} 已复制到剪贴板`, undefined, { duration: 3000 });
} else {
console.log('Oops, unable to copy');
}
......
md-list-item /deep/ .mat-list-item-content {
justify-content: space-between;
height: 28px !important;
padding: 0
}
md-grid-list {
font-size: 14px;
}
md-grid-tile dt, md-grid-tile dd {
width: 100%;
}
md-grid-tile {
text-align: left;
}
md-grid-tile dd {
margin: 0;
text-align: left;
}
.win dd {
color: green;
}
.lose dd {
color: red;
}
h2 {
text-align: center;
}
<h2 md-dialog-title *ngIf="result === 'win'">胜利</h2>
<h2 md-dialog-title *ngIf="result === 'lose'">败北</h2>
<h2 md-dialog-title *ngIf="result === 'draw'">平局</h2>
<md-dialog-content>
<md-list [ngClass]="result" *ngIf="dp || exp || firstWin">
<md-list-item *ngIf="dp">
<dt>D.P</dt>
<dd>{{dp}}</dd>
</md-list-item>
<md-list-item *ngIf="exp">
<dt>EXP</dt>
<dd>{{exp}}</dd>
</md-list-item>
<md-list-item *ngIf="firstWin">
<dt>D.P (首胜)</dt>
<dd>{{firstWin}}</dd>
</md-list-item>
</md-list>
</md-dialog-content>
<md-dialog-actions>
<button md-button md-dialog-close>关闭</button>
<!-- Can optionally provide a result for the closing dialog. -->
<button md-button (click)="again()">再来一局</button>
</md-dialog-actions>
import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
import { MD_DIALOG_DATA, MdDialog, MdDialogRef } from '@angular/material';
import { LoginService } from '../login.service';
import { MatchDialog } from '../match/match.component';
import { Result } from '../ygopro.service';
@Component({
selector: 'app-result',
templateUrl: './result.dialog.html',
styleUrls: ['./result.dialog.css']
})
export class ResultDialog {
result: 'win' | 'lose' | 'draw';
dp: string | undefined;
exp: string | undefined;
firstWin: string | undefined;
constructor(@Inject(MD_DIALOG_DATA) public last: any, public login: LoginService, private http: Http, public dialog: MdDialog, private dialogRef: MdDialogRef<MatchDialog>) {
if (this.last.userscorea === this.last.userscoreb) {
this.result = 'draw';
} else if (this.last.winner === this.login.user.username) {
this.result = 'win';
} else {
this.result = 'lose';
}
const index = this.last.usernamea == this.login.user.username ? 'a' : 'b';
if (this.last.isfirstwin && this.result === 'win') {
this.dp = this.format(this.last[`pt${index}`] - 4, this.last[`pt${index}_ex`]);
this.firstWin = this.format(4);
} else {
this.dp = this.format(this.last[`pt${index}`], this.last[`pt${index}_ex`]);
}
this.exp = this.format(this.last[`exp${index}`], this.last[`exp${index}_ex`]);
}
format(current: number, ex: number = 0) {
const result = Math.round(current) - Math.round(ex);
return result ? `${(result < 0 ? '' : '+')}${Math.round(result)}` : undefined;
}
again() {
this.dialogRef.close();
this.dialog.open(MatchDialog, { data: this.last.type, disableClose: true });
}
}
import { DataSource } from '@angular/cdk';
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { MdDialog } from '@angular/material';
import { sortBy } from 'lodash';
import { Observable } from 'rxjs/Observable';
import { LoginService } from './login.service';
import { ResultDialog } from './result/result.dialog';
export interface User {
admin: boolean;
......@@ -66,12 +68,52 @@ interface App {
data: any
}
export interface Result {
end_time: string
expa: number
expa_ex: number
expb: number
expb_ex: number
isfirstwin: boolean
pta: number
pta_ex: number
ptb: number
ptb_ex: number
start_time: string
type: 'athletic' | 'entertain'
usernamea: string
usernameb: string
userscorea: number
userscoreb: number
winner: string
}
export interface Points {
arena_rank: number
athletic_all: number
athletic_draw: number
athletic_lose: number
athletic_win: number
athletic_wl_ratio: number
entertain_all: number
entertain_draw: number
entertain_lose: number
entertain_win: number
entertain_wl_ratio: number
exp: number
exp_rank: number
pt: number
}
@Injectable()
export class YGOProService {
news: News[];
windbot: string[];
topics: Observable<any>;
points: Points;
last_game_at: Date;
readonly default_options: Options = {
mode: 1,
......@@ -102,25 +144,93 @@ export class YGOProService {
replay: true
}];
constructor(private login: LoginService, private http: Http) {
constructor(private login: LoginService, private http: Http, private dialog: MdDialog) {
this.load().catch(alert);
}
async load() {
const apps: App[] = await this.http.get('https://api.mycard.moe/apps.json').map(response => response.json()).toPromise();
const app = apps.find(app => app.id === 'ygopro');
const app = apps.find(app => app.id === 'ygopro')!;
this.news = app.news['zh-CN'];
this.windbot = (<YGOProData>app.data).windbot['zh-CN'];
// this.topics = this.http.get('https://ygobbs.com/top.json').flatMap(response => promisify(parseString)(response.text())).map(doc => {
// console.log(doc['rss'].channel[0].item)
// return doc['rss'].channel[0].item;
// });
this.topics = this.http.get('https://ygobbs.com/top/quarterly.json').map(response => response.json().topic_list.topics.slice(0, 5).map(topic => ({
this.topics = this.http.get('https://ygobbs.com/top/quarterly.json').map(response => response.json().topic_list.topics.slice(0, 5).map((topic: any) => ({
...topic,
url: new URL(`/t/${topic.slug}/${topic.id}`, 'https://ygobbs.com').toString(),
image_url: topic.image_url && new URL(topic.image_url, 'https://ygobbs.com').toString()
})));
this.load_points();
await this.load_result(false);
this.listen_result();
}
async load_result(load_points = true) {
const last = await this.http.get('https://mycard.moe/ygopro/api/history', {
params: { username: this.login.user.username, type: 0, page_num: 1 }
}).map((response) => response.json().data[0]).toPromise();
const last_game_at = localStorage.getItem('last_game_at');
localStorage.setItem('last_game_at', last.end_time);
console.log(last);
// 初次运行
if (!last_game_at) {
return;
}
// 无新对局
if (last_game_at === last.end_time) {
return;
}
// 10分钟内有新对局
if (Date.now() - Date.parse(last.end_time) < 10 * 60 * 1000) {
// console.log(last);
if (load_points) {
this.load_points();
}
this.dialog.open(ResultDialog, { data: last });
}
}
listen_result() {
// 那些兼容性的垃圾事儿
// https://www.html5rocks.com/en/tutorials/pagevisibility/intro/
function getHiddenProp() {
const prefixes = ['webkit', 'moz', 'ms', 'o'];
// if 'hidden' is natively supported just return it
if ('hidden' in document) return 'hidden';
// otherwise loop over all the known prefixes until we find one
for (let i = 0; i < prefixes.length; i++) {
if ((prefixes[i] + 'Hidden') in document)
return prefixes[i] + 'Hidden';
}
// otherwise it's not supported
return null;
}
const visProp = getHiddenProp();
if (visProp) {
const evtname = visProp.replace(/[H|h]idden/, '') + 'visibilitychange';
Observable.fromEvent(document, evtname).subscribe(async () => !document[visProp] && this.load_result());
} else {
alert('调试信息1,看到的话请联系zh99998@gmail.com');
}
}
async load_points() {
this.points = await this.http.get('https://api.mycard.moe/ygopro/arena/user', { params: { username: this.login.user.username } }).map(response => response.json()).toPromise();
}
create_room(room: Room, host_password: string) {
......@@ -205,7 +315,7 @@ export class YGOProService {
return this.join('AI#' + name, this.servers[0]);
}
join(password, server) {
join(password: string, server: Server) {
try {
window.ygopro.join(server.address, server.port, this.login.user.username, password);
} catch (error) {
......@@ -290,7 +400,7 @@ export class RoomListDataSource extends DataSource<any> {
connect(): Observable<Room[]> {
return Observable.combineLatest(this.servers.map(server => {
const url = new URL(server.url);
const url = new URL(server.url!);
url.searchParams.set('filter', this.filter);
return Observable.webSocket({ url: url.toString() })
.scan((rooms: Room[], message: Message) => {
......@@ -307,7 +417,7 @@ export class RoomListDataSource extends DataSource<any> {
}
}, []);
}
), (...sources) => [].concat(...sources)).map(rooms => sortBy(rooms, (room) => {
), (...sources: Room[][]) => (<Room[]>[]).concat(...sources)).map(rooms => sortBy(rooms, (room) => {
if (room.arena === 'athletic') {
return 0;
} else if (room.arena === 'entertain') {
......
export const environment = {
production: true
production: true,
version: require('../../package.json').version
};
......@@ -4,5 +4,6 @@
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: false
production: false,
version: require('../../package.json').version
};
......@@ -45,7 +45,6 @@
<div id="loading">
<img src="assets/CubbitLogo.png">
<p>LOADING <span>.</span> <span>.</span> <span>.</span></p>
<span id="version"></span>
</div>
</app-root>
</body>
......
......@@ -9,6 +9,8 @@
"experimentalDecorators": true,
"downlevelIteration": true,
"target": "es5",
"strict": true,
"suppressImplicitAnyIndexErrors": true,
"typeRoots": [
"node_modules/@types"
],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment