Commit 004bf591 authored by 神楽坂玲奈's avatar 神楽坂玲奈

Merge branch 'gh-pages'

parents 27471866 f39293cd
...@@ -79,9 +79,10 @@ h2 { ...@@ -79,9 +79,10 @@ h2 {
object-fit: contain; object-fit: contain;
box-shadow: 0 0 4px #ccc; box-shadow: 0 0 4px #ccc;
} }
.banner { .banner {
width: 120px; width: 120px;
height:45px; height: 45px;
object-fit: cover; object-fit: cover;
} }
...@@ -208,16 +209,19 @@ table th, table td { ...@@ -208,16 +209,19 @@ table th, table td {
flex-direction: column; flex-direction: column;
flex-grow: 1; flex-grow: 1;
} }
#game_info p { #game_info p {
flex-grow: 1; flex-grow: 1;
} }
#game_info_2 { #game_info_2 {
width: 160px; width: 160px;
flex-shrink: 0; flex-shrink: 0;
} }
#tags { .tag {
font-size: 14px; font-size: 12px;
padding: 2px 5px;
} }
#purchase-form .form-check { #purchase-form .form-check {
......
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
<!--应用未购买--> <!--应用未购买-->
<div *ngIf="!currentApp.isBought()"> <div *ngIf="!currentApp.isBought()">
<button i18n type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#purchase-modal">{{currentApp.price.cny | currency:'CNY':true}} 购买</button> <button i18n type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#purchase-modal">{{currentApp.price.cny | currency:'CNY':true}} 购买</button>
<button i18n type="button" (click)="updateInstallOption(currentApp)" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="#install-modal">安装试玩版</button>
<!--<button i18n (click)="updateInstallOption(currentApp)" type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#install-modal">我已经购买过</button>--> <!--<button i18n (click)="updateInstallOption(currentApp)" type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#install-modal">我已经购买过</button>-->
</div> </div>
...@@ -108,7 +110,7 @@ ...@@ -108,7 +110,7 @@
<div class="panel" id="game_info"> <div class="panel" id="game_info">
<p [innerHTML]="currentApp.description"></p> <p [innerHTML]="currentApp.description"></p>
<div id="tags" *ngIf="currentApp.tags"> <div id="tags" *ngIf="currentApp.tags">
<div *ngFor="let tag of currentApp.tags" class="btn btn-xs btn-danger" style="padding:3px 10px; margin:2px">{{tags[tag] || tag}}</div> <div *ngFor="let tag of currentApp.tags" class="btn btn-sm btn-danger tag">{{tags[tag] || tag}}</div>
</div> </div>
</div> </div>
<div class="panel" id="game_info_2"> <div class="panel" id="game_info_2">
...@@ -289,6 +291,16 @@ ...@@ -289,6 +291,16 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<!--<div class="form-group">-->
<!--<label for="exampleSelect1">国家/地区</label>-->
<!--<select class="form-control" id="exampleSelect1">-->
<!--<option>中国(¥)</option>-->
<!--<option>美国($)</option>-->
<!--<option>3</option>-->
<!--<option>4</option>-->
<!--<option>5</option>-->
<!--</select>-->
<!--</div>-->
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<img *ngIf="currentApp.cover" [src]="currentApp.cover" class="banner"> <img *ngIf="currentApp.cover" [src]="currentApp.cover" class="banner">
<span class="p-2">{{currentApp.name}}</span> <span class="p-2">{{currentApp.name}}</span>
...@@ -298,11 +310,11 @@ ...@@ -298,11 +310,11 @@
<!--<fieldset class="form-group">--> <!--<fieldset class="form-group">-->
<legend>支付方式</legend> <legend>支付方式</legend>
<div class="form-check"> <div class="form-check">
<input id="alipay" type="radio" class="form-check-input" name="optionsRadios" value="alipay" checked> <input [(ngModel)]="payment" id="alipay" type="radio" class="form-check-input" name="payment" value="alipay" checked>
<label for="alipay" class="form-check-label">支付宝</label> <label for="alipay" class="form-check-label">支付宝</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input id="wechat" type="radio" class="form-check-input" name="optionsRadios" value="alipay" checked> <input [(ngModel)]="payment" id="wechat" type="radio" class="form-check-input" name="payment" value="wechat" checked>
<label for="wechat" class="form-check-label">微信</label> <label for="wechat" class="form-check-label">微信</label>
</div> </div>
<!--<div class="form-check">--> <!--<div class="form-check">-->
...@@ -314,8 +326,31 @@ ...@@ -314,8 +326,31 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button i18n type="button" class="btn btn-secondary" data-dismiss="modal">取消</button> <button i18n class="btn btn-secondary" data-dismiss="modal">取消</button>
<button i18n type="submit" [disabled]="import_path && !theForm.form.valid" class="btn btn-primary">购买</button> <button i18n [disabled]="creating_order" class="btn btn-primary" (click)="purchase()">购买</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="purchase-modal-alipay" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static" *ngIf="!currentApp.isBought()">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 i18n class="modal-title">购买 {{currentApp.name}}</h5>
<!--<button type="button" class="close" data-dismiss="modal" aria-label="Close">-->
<!--<span>&times;</span>-->
<!--</button>-->
</div>
<div class="modal-body">
订单已经创建,请在新窗口中进行支付,支付成功后页面会自动跳转
若支付成功后没有自动跳转 请联系 thdod@mycard.moe
若支付失败,请返回并选择其他支付方式
</div>
<div class="modal-footer">
<button i18n type="button" class="btn btn-secondary" data-dismiss="modal">返回</button>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -9,7 +9,8 @@ import * as path from 'path'; ...@@ -9,7 +9,8 @@ import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import * as $ from 'jquery'; import * as $ from 'jquery';
import {Points} from './ygopro.component'; import {Points} from './ygopro.component';
import {Http} from '@angular/http'; import {Http, URLSearchParams} from '@angular/http';
import {LoginService} from './login.service';
declare const Notification: any; declare const Notification: any;
...@@ -42,9 +43,12 @@ export class AppDetailComponent implements OnInit, OnChanges { ...@@ -42,9 +43,12 @@ export class AppDetailComponent implements OnInit, OnChanges {
tags: {}; tags: {};
payment = 'alipay';
creating_order = false;
constructor(private appsService: AppsService, private settingsService: SettingsService, constructor(private appsService: AppsService, private settingsService: SettingsService,
private downloadService: DownloadService, private ref: ChangeDetectorRef, private el: ElementRef, private downloadService: DownloadService, private ref: ChangeDetectorRef, private el: ElementRef,
private http: Http) { private http: Http, private loginService: LoginService) {
this.tags = this.settingsService.getLocale().startsWith('zh') ? { this.tags = this.settingsService.getLocale().startsWith('zh') ? {
'recommend': '推荐', 'recommend': '推荐',
...@@ -62,6 +66,9 @@ export class AppDetailComponent implements OnInit, OnChanges { ...@@ -62,6 +66,9 @@ export class AppDetailComponent implements OnInit, OnChanges {
} }
async ngOnChanges(changes: SimpleChanges) { async ngOnChanges(changes: SimpleChanges) {
if (this.currentApp.isBought()) {
$('#purchase-modal-alipay').modal('hide');
}
if (changes['currentApp']) { if (changes['currentApp']) {
if (this.currentApp.background) { if (this.currentApp.background) {
this.el.nativeElement.style.background = `url("${this.currentApp.background}") rgba(255,255,255,.8)`; this.el.nativeElement.style.background = `url("${this.currentApp.background}") rgba(255,255,255,.8)`;
...@@ -262,4 +269,29 @@ export class AppDetailComponent implements OnInit, OnChanges { ...@@ -262,4 +269,29 @@ export class AppDetailComponent implements OnInit, OnChanges {
onPoints(points: Points) { onPoints(points: Points) {
this.points = points; this.points = points;
} }
async purchase() {
this.creating_order = true;
let data = new URLSearchParams();
data.set('app_id', this.currentApp.id);
data.set('user_id', this.loginService.user.email);
data.set('currency', 'cny');
data.set('payment', this.payment);
try {
let {url} = await this.http.post('https://api.mycard.moe/orders', data).map(response => response.json()).toPromise();
open(url);
$('#purchase-modal').modal('hide');
$('#purchase-modal-alipay').modal('show');
} catch (error) {
console.log(error);
if (error.status === 409) {
alert('卖完了 /\\');
} else if (error.status === 403) {
alert('已经购买过 /\\');
} else {
alert('出错了 /\\');
}
}
this.creating_order = false;
}
} }
...@@ -78,7 +78,8 @@ export class App { ...@@ -78,7 +78,8 @@ export class App {
cover: string; cover: string;
background: string; background: string;
price: {[currency: string]: string} price: {[currency: string]: string};
key?: string;
static downloadUrl(app: App, platform: string, locale: string): string { static downloadUrl(app: App, platform: string, locale: string): string {
if (app.id === 'ygopro') { if (app.id === 'ygopro') {
...@@ -108,8 +109,9 @@ export class App { ...@@ -108,8 +109,9 @@ export class App {
return `https://thief.mycard.moe/update/${app.id}/${app.version}`; return `https://thief.mycard.moe/update/${app.id}/${app.version}`;
} }
isBought() { isBought(): Boolean {
return !this.price; // 免费或有 Key
return !this.price || !!this.key;
} }
isLanguage() { isLanguage() {
...@@ -185,6 +187,7 @@ export class App { ...@@ -185,6 +187,7 @@ export class App {
this.background = app.background; this.background = app.background;
this.price = app.price; this.price = app.price;
this.key = app.key;
} }
findDependencies(): App[] { findDependencies(): App[] {
......
import {Injectable, ApplicationRef, EventEmitter, NgZone} from '@angular/core'; import {Injectable, ApplicationRef, EventEmitter, NgZone} from '@angular/core';
import {Http} from '@angular/http'; import {Http, URLSearchParams} from '@angular/http';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import {App, AppStatus, Action} from './app'; import {App, AppStatus, Action} from './app';
import {SettingsService} from './settings.sevices'; import {SettingsService} from './settings.sevices';
...@@ -18,6 +18,7 @@ import {DownloadService, DownloadStatus} from './download.service'; ...@@ -18,6 +18,7 @@ import {DownloadService, DownloadStatus} from './download.service';
import {InstallOption} from './install-option'; import {InstallOption} from './install-option';
import {ComparableSet} from './shared/ComparableSet'; import {ComparableSet} from './shared/ComparableSet';
import {Observable, Observer} from 'rxjs/Rx'; import {Observable, Observer} from 'rxjs/Rx';
import {LoginService} from './login.service';
import Timer = NodeJS.Timer; import Timer = NodeJS.Timer;
import ReadableStream = NodeJS.ReadableStream; import ReadableStream = NodeJS.ReadableStream;
const sudo = require('electron-sudo'); const sudo = require('electron-sudo');
...@@ -61,7 +62,7 @@ export class AppsService { ...@@ -61,7 +62,7 @@ export class AppsService {
: 'bsdtar'; : 'bsdtar';
constructor(private http: Http, private settingsService: SettingsService, private ref: ApplicationRef, constructor(private http: Http, private settingsService: SettingsService, private ref: ApplicationRef,
private downloadService: DownloadService, private ngZone: NgZone) { private downloadService: DownloadService, private ngZone: NgZone, private loginService: LoginService) {
} }
get lastVisited(): App|undefined { get lastVisited(): App|undefined {
...@@ -80,15 +81,26 @@ export class AppsService { ...@@ -80,15 +81,26 @@ export class AppsService {
async loadApps() { async loadApps() {
let appsURL = 'https://api.mycard.moe/apps.json'; let appsURL = 'https://api.mycard.moe/apps.json';
let keysURL = 'https://api.mycard.moe/keys';
try { try {
let params = new URLSearchParams();
params.set('user_id', this.loginService.user.email);
let data = await this.http.get(appsURL).map((response) => response.json()).toPromise(); let data = await this.http.get(appsURL).map((response) => response.json()).toPromise();
let keys_data = await this.http.get(keysURL, {search: params}).map((response) => response.json()).toPromise();
for (let item of keys_data) {
let app = data.find((app: any) => app.id === item.app_id);
if (app) {
app.key = item.id;
}
}
localStorage.setItem('apps_json', JSON.stringify(data)); localStorage.setItem('apps_json', JSON.stringify(data));
this.apps = this.loadAppsList(data); this.apps = this.loadAppsList(data);
} catch (e) { } catch (e) {
let data = localStorage.getItem('apps_json'); let data = localStorage.getItem('apps_json');
if (data) { if (data) {
this.apps = this.loadAppsList(data); this.apps = this.loadAppsList(JSON.parse(data));
} else { } else {
alert('读取游戏列表失败,可能是网络不通')
this.apps = new Map(); this.apps = new Map();
} }
} }
......
...@@ -7,6 +7,9 @@ import {LoginService} from './login.service'; ...@@ -7,6 +7,9 @@ import {LoginService} from './login.service';
import {App, Category} from './app'; import {App, Category} from './app';
import {shell} from 'electron'; import {shell} from 'electron';
import {SettingsService} from './settings.sevices'; import {SettingsService} from './settings.sevices';
import {URLSearchParams} from '@angular/http';
const ReconnectingWebSocket = require('reconnecting-websocket');
// import 'typeahead.js'; // import 'typeahead.js';
// import Options = Twitter.Typeahead.Options; // import Options = Twitter.Typeahead.Options;
...@@ -28,11 +31,13 @@ export class LobbyComponent implements OnInit { ...@@ -28,11 +31,13 @@ export class LobbyComponent implements OnInit {
@ViewChild('search') @ViewChild('search')
search: ElementRef; search: ElementRef;
constructor (private appsService: AppsService, private loginService: LoginService, private messages: WebSocket;
private settingsService: SettingsService, private ref: ChangeDetectorRef) {
constructor(private appsService: AppsService, private loginService: LoginService,
private settingsService: SettingsService, private ref: ChangeDetectorRef) {
} }
async ngOnInit () { async ngOnInit() {
this.apps = await this.appsService.loadApps(); this.apps = await this.appsService.loadApps();
if (this.apps.size > 0) { if (this.apps.size > 0) {
this.chooseApp(this.appsService.lastVisited || this.apps.get('ygopro')!); this.chooseApp(this.appsService.lastVisited || this.apps.get('ygopro')!);
...@@ -54,6 +59,18 @@ export class LobbyComponent implements OnInit { ...@@ -54,6 +59,18 @@ export class LobbyComponent implements OnInit {
} }
this.ref.detectChanges(); this.ref.detectChanges();
let url = new URL('wss://api.mycard.moe:3100');
let params: URLSearchParams = url['searchParams'];
params.set('user_id', this.loginService.user.email);
this.messages = new ReconnectingWebSocket(url);
this.messages.onmessage = async(event) => {
let data = JSON.parse(event.data);
console.log(data);
this.apps = await this.appsService.loadApps();
this.currentApp = this.apps.get(this.currentApp.id)!;
};
// $(this.search.nativeElement).typeahead(<any>{ // $(this.search.nativeElement).typeahead(<any>{
// minLength: 1, // minLength: 1,
// highlight: true // highlight: true
...@@ -92,7 +109,7 @@ export class LobbyComponent implements OnInit { ...@@ -92,7 +109,7 @@ export class LobbyComponent implements OnInit {
height = 230; height = 230;
} }
this.resizing.style.height = `${height}px`; this.resizing.style.height = `${height}px`;
if ( $('#candy').attr('data-minormax') !== 'default') { if ($('#candy').attr('data-minormax') !== 'default') {
$('#candy').attr('data-minormax', 'default'); $('#candy').attr('data-minormax', 'default');
$('#mobile-roster-icon').css('display', 'block'); $('#mobile-roster-icon').css('display', 'block');
$('#chat-toolbar').css('display', 'block'); $('#chat-toolbar').css('display', 'block');
...@@ -104,7 +121,7 @@ export class LobbyComponent implements OnInit { ...@@ -104,7 +121,7 @@ export class LobbyComponent implements OnInit {
$('#restore').hide(); $('#restore').hide();
$('#maximize').show(); $('#maximize').show();
} }
}else if ( height <= 150) { } else if (height <= 150) {
$('#candy').attr('data-minormax', 'min'); $('#candy').attr('data-minormax', 'min');
this.resizing.style.height = '31px'; this.resizing.style.height = '31px';
$('#mobile-roster-icon').css('display', 'none'); $('#mobile-roster-icon').css('display', 'none');
...@@ -116,7 +133,7 @@ export class LobbyComponent implements OnInit { ...@@ -116,7 +133,7 @@ export class LobbyComponent implements OnInit {
$('#unminimize').show(); $('#unminimize').show();
$('#restore').hide(); $('#restore').hide();
$('#maximize').show(); $('#maximize').show();
}else if ( main_height <= 180) { } else if (main_height <= 180) {
$('#candy').attr('data-minormax', 'max'); $('#candy').attr('data-minormax', 'max');
this.resizing.style.height = 'calc( 100% - 180px )'; this.resizing.style.height = 'calc( 100% - 180px )';
$('#minimize').show(); $('#minimize').show();
...@@ -132,7 +149,7 @@ export class LobbyComponent implements OnInit { ...@@ -132,7 +149,7 @@ export class LobbyComponent implements OnInit {
}); });
} }
mousedown (event: MouseEvent) { mousedown(event: MouseEvent) {
// console.log(() // console.log(()
document.body.classList.add('resizing'); document.body.classList.add('resizing');
this.resizing = <HTMLElement>(<HTMLElement>event.target).parentNode; this.resizing = <HTMLElement>(<HTMLElement>event.target).parentNode;
...@@ -143,13 +160,13 @@ export class LobbyComponent implements OnInit { ...@@ -143,13 +160,13 @@ export class LobbyComponent implements OnInit {
} }
} }
chooseApp (app: App) { chooseApp(app: App) {
this.currentApp = app; this.currentApp = app;
this.appsService.lastVisited = app; this.appsService.lastVisited = app;
} }
get grouped_apps () { get grouped_apps() {
let contains = ['game', 'music', 'book'].map((value) => Category[value]); let contains = ['game', 'music', 'book', 'test'].map((value) => Category[value]);
let result = {runtime: []}; let result = {runtime: []};
for (let app of this.apps.values()) { for (let app of this.apps.values()) {
let tag: string; let tag: string;
...@@ -175,7 +192,7 @@ export class LobbyComponent implements OnInit { ...@@ -175,7 +192,7 @@ export class LobbyComponent implements OnInit {
return result; return result;
} }
openExternal (url: string) { openExternal(url: string) {
shell.openExternal(url); shell.openExternal(url);
} }
} }
...@@ -77,6 +77,7 @@ System.config({ ...@@ -77,6 +77,7 @@ System.config({
'jquery': 'npm:jquery/dist/jquery.min.js', 'jquery': 'npm:jquery/dist/jquery.min.js',
'tether': 'npm:tether/dist/js/tether.min.js', 'tether': 'npm:tether/dist/js/tether.min.js',
'bootstrap': 'npm:bootstrap/dist/js/bootstrap.min.js', 'bootstrap': 'npm:bootstrap/dist/js/bootstrap.min.js',
'reconnecting-websocket': 'npm:reconnecting-websocket/dist/index.js'
// 'typeahead.js': '@node/typeahead.js' // 'typeahead.js': '@node/typeahead.js'
}, },
// packages tells the System loader how to load when no filename and/or no extension // packages tells the System loader how to load when no filename and/or no extension
......
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