Commit ef06d662 authored by 神楽坂玲奈's avatar 神楽坂玲奈

most works

parent 9d58e148
......@@ -43,3 +43,4 @@ testem.log
# System Files
.DS_Store
Thumbs.db
/bin/
......@@ -38,6 +38,10 @@
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"allowedCommonJsDependencies": [
"glob",
"aria2"
],
"customWebpackConfig": {},
"outputPath": "dist/mycard",
"index": "src/index.html",
......
This diff is collapsed.
This diff is collapsed.
:host {
flex-grow: 1;
position: relative;
padding: 1rem 1rem 0 1rem;
background-blend-mode: color;
background-size: 100% auto !important;
background-repeat: no-repeat !important;
}
.list-group {
width: 20rem;
}
progress {
margin: 2px 0 0;
}
.carousel-inner img {
width: 100%;
}
.dependency {
margin-right: 0.8em;
}
#news p {
margin-bottom: 0;
}
#news a {
display: block;
}
#network {
display: inline-block;
vertical-align: middle;
width: 230px;
}
#network .input-group-btn > .btn:not(:last-child):not(.dropdown-toggle) {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
#network .input-group-btn > .dropdown-toggle {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
.i-b{
display: inline-block;
}
.custom-file {
width: 100%;
}
.custom-file-control:lang(en)::after {
content: initial;
}
.custom-file-control {
overflow: hidden;
white-space: nowrap;
}
h1 {
font-size: 28px;
}
#status {
font-size: 15px;
}
h2 {
font-size: 20px;
margin-bottom: 0;
}
.cover {
width: 128px;
height: 128px;
object-fit: contain;
box-shadow: 0 0 4px #ccc;
}
.banner {
width: 120px;
height: 45px;
object-fit: cover;
}
#main {
display: flex;
flex-direction: row;
}
.panel {
border: 1px solid #eceeef;
border-radius: 6px;
background: rgba(255, 255, 255, .7);
padding: .8rem;
margin-bottom: 1rem;
box-shadow: 0 0 15px rgba(0, 0, 0, .05);
position: relative;
}
#news h3 > .title {
font-size: 1rem;
color: inherit;
}
#news h3 {
padding-top: .8rem;
margin-bottom: 0;
}
#news p {
font-size: 14px;
color: #888;
}
#news a {
font-size: 14px;
color: #00a4d9;
}
#news span {
font-size: 12px;
color: #ccc;
}
.moreinfo {
color: #00a4d9;
display: block;
position: absolute;
top: 12px;
right: 18px;
font-size: 14px;
}
#local h2 {
margin-bottom: .8rem;
}
#main {
display: flex;
flex-direction: row;
}
#right {
margin-left: 1rem;
}
h1 {
font-size: 28px;
margin-bottom: 0;
}
#time {
font-size: 14px;
margin-bottom: .6rem;
visibility: hidden;
}
th {
width: 25%;
}
.moreinfo {
color: #00a4d9;
display: block;
position: absolute;
top: 12px;
right: 18px;
font-size: 14px;
}
#arena {
position: relative;
}
.btn-primary {
background-color: #00a4d9;
border-color: #008dbb;
}
/* 竞技场 */
h2 {
font-size: 20px;
}
dt, dd {
font-size: 14px;
}
table {
margin-top: .5rem;
margin-bottom: 0;
}
table th, table td {
border-top: none;
font-size: 14px;
font-weight: normal;
}
#game_info {
font-size: 14px;
margin-right: 8px;
display: flex;
flex-direction: column;
flex-grow: 1;
}
#game_info p {
flex-grow: 1;
}
#game_info_2 {
width: 160px;
flex-shrink: 0;
}
.tag {
font-size: 12px;
padding: 2px 5px;
margin-right: 5px;
}
table.expansions thead {
color: #ffffff;
background-color: #5bc0de;
}
table.expansions th {
width: auto;
vertical-align: middle;
}
table.expansions td {
line-height: 220%;
vertical-align: middle;
}
table.expansions thead th:first-child{
border-top-left-radius: 5px;
width: 5%;
}
table.expansions thead th:last-child{
border-top-right-radius: 5px;
width: 20%;
}
table.expansions tr:last-child td:first-child{
border-bottom-left-radius: 5px;
}
table.expansions tr:last-child td:last-child{
border-bottom-right-radius: 5px;
}
#purchase-form .form-check {
padding-right: 8px;
}
#purchase-form legend {
font-size: 1rem;
margin-bottom: 0;
margin-top: .5rem;
}
This diff is collapsed.
import { ChangeDetectorRef, Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { AppsService } from '../apps.service';
import { InstallOption } from '../shared/install-option';
import { SettingsService } from '../settings.service';
import { App } from '../shared/app';
import { DownloadService } from '../download.service';
import { clipboard } from 'electron';
import remote from '@electron/remote';
import path from 'path';
import fs from 'fs';
// import { Points } from '../ygopro/ygopro.component';
import { LoginService } from '../login/login.service';
declare const Notification: any;
// declare interface Window {
// adsbygoogle: any[];
// }
//
// declare var adsbygoogle: any[];
@Component({
selector: 'app-detail',
templateUrl: 'app-detail.component.html',
styleUrls: ['app-detail.component.css']
})
export class AppDetailComponent implements OnInit, OnChanges {
@Input()
currentApp: App;
platform = process.platform;
installOption: InstallOption;
availableLibraries: string[] = [];
references: App[];
referencesInstall: { [id: string]: boolean };
// import_path: string;
background: string;
points: any;
tags: {};
payment = 'alipay';
creating_order = false;
constructor(public appsService: AppsService, private settingsService: SettingsService, private downloadService: DownloadService, private ref: ChangeDetectorRef, private el: ElementRef, private loginService: LoginService) {
this.tags = this.settingsService.getLocale().startsWith('zh') ? {
'recommend': '推荐',
'mysterious': '迷之物体',
'touhou': '东方 Project',
'touhou_pc98': '东方旧作',
'language': '语言包',
'ygopro': 'YGOPro'
} : {
'recommend': 'Recommended',
'mysterious': 'Something',
'touhou': 'Touhou Project',
'touhou_pc98': 'Touhou old series',
'language': 'Language Pack',
'ygopro': 'YGOPro'
};
}
async ngOnChanges(changes: SimpleChanges) {
// if (this.currentApp.isBought()) {
// $('#purchase-modal-alipay').modal('hide');
// }
if (changes['currentApp']) {
if (this.currentApp.background) {
this.el.nativeElement.style.background = `url("${this.currentApp.background}") rgba(255,255,255,.8)`;
} else {
this.el.nativeElement.style.background = 'white';
}
this.updateInstallOption(this.currentApp);
// let top = await this.http.get('https://ygobbs.com/top.json').map(response => response.json()).toPromise();
// console.log(top.topic_list.topics);
// (adsbygoogle = window['adsbygoogle'] || []).push({});
}
}
async ngOnInit(): Promise<void> {
let volume = 'A';
for (let i = 0; i < 26; i++) {
await new Promise((resolve, reject) => {
let currentVolume = String.fromCharCode(volume.charCodeAt(0) + i) + ':';
fs.access(currentVolume, (err) => {
if (!err) {
// 判断是否已经存在Library
if (this.libraries.every((library) => !library.startsWith(currentVolume))) {
this.availableLibraries.push(currentVolume);
}
}
resolve(null);
});
});
}
}
updateInstallOption(app: App) {
this.installOption = new InstallOption(app);
this.installOption.installLibrary = this.settingsService.getDefaultLibrary().path;
this.references = Array.from(app.references.values());
this.referencesInstall = {};
for (let reference of this.references) {
if (reference.isLanguage()) {
// 对于语言包,只有在语言包的locales比游戏本身的更加合适的时候才默认勾选
// 这里先偷个懒,中文环境勾选中文语言包,非中文环境勾选非中文语言包
this.referencesInstall[reference.id] =
reference.locales[0].startsWith('zh') === this.settingsService.getLocale().startsWith('zh');
} else {
this.referencesInstall[reference.id] = true;
}
}
}
get libraries(): string[] {
return this.settingsService.getLibraries().map((item) => item.path);
}
get news() {
return this.currentApp.news;
}
get mods(): App[] {
return this.appsService.findChildren(this.currentApp);
}
async installMod(mod: App) {
let option = new InstallOption(mod, path.dirname(mod.parent!.local!.path));
await this.install(mod, option, {});
}
async uninstall(app: App) {
if (confirm('确认删除?')) {
try {
await this.appsService.uninstall(app);
} catch (e) {
alert(e);
}
}
}
async install(targetApp: App, options: InstallOption, referencesInstall: { [id: string]: boolean }) {
// $('#install-modal').modal('hide');
try {
await this.appsService.install(targetApp, options);
for (let [id, install] of Object.entries(referencesInstall)) {
if (install) {
let reference = targetApp.references.get(id)!;
console.log('reference install ', id, targetApp, targetApp.references, reference);
await this.appsService.install(reference, options);
}
}
} catch (e) {
console.error(e);
new Notification(targetApp.name, { body: '下载失败' });
}
}
async selectLibrary() {
if (this.installOption.installLibrary.startsWith('create_')) {
let volume = this.installOption.installLibrary.slice(7);
let library = path.join(volume, 'MyCardLibrary');
try {
await this.appsService.createDirectory(library);
this.installOption.installLibrary = library;
this.settingsService.addLibrary(library, true);
} catch (e) {
this.installOption.installLibrary = this.settingsService.getDefaultLibrary().path;
alert('无法创建指定目录');
} finally {
let index = this.availableLibraries.findIndex((l) => {
return l === volume;
});
this.availableLibraries.splice(index, 1);
}
} else {
this.settingsService.setDefaultLibrary({ path: this.installOption.installLibrary, 'default': true });
}
this.installOption.installLibrary = this.settingsService.getDefaultLibrary().path;
}
selectDir() {
let dir = remote.dialog.showOpenDialog({ properties: ['openFile', 'openDirectory'] });
console.log(dir);
// this.appsService.installOption.installDir = dir[0];
return dir[0];
}
runApp(app: App, action_name = 'main') {
this.appsService.runApp(app, action_name);
}
custom(app: App) {
this.appsService.runApp(app, 'custom');
}
async runRoll(app: App) {
await this.appsService.runApp(app, 'roll_main');
await this.appsService.runApp(app, 'roll');
}
async importGame(origin: string, targetApp: App, option: InstallOption, referencesInstall: { [id: string]: boolean }) {
let dir = path.dirname(origin);
// TODO: 执行依赖和references安装
try {
await this.appsService.importApp(targetApp, dir, option);
for (let [id, install] of Object.entries(referencesInstall)) {
if (install) {
let reference = targetApp.references.get(id)!;
console.log('reference install ', id, targetApp, targetApp.references, reference);
await this.appsService.install(reference, option);
}
}
} catch (e) {
console.error(e);
new Notification(targetApp.name, { body: '导入失败' });
}
}
async verifyFiles(app: App) {
try {
await this.appsService.update(app, true);
let installedMods = this.appsService.findChildren(app).filter((child) => {
return child.parent === app && child.isInstalled() && child.isReady();
});
for (let mod of installedMods) {
await this.appsService.update(mod, true);
}
} catch (e) {
new Notification(app.name, { body: '校验失败' });
console.error(e);
}
}
copy(text: string) {
clipboard.writeText(text);
}
// async selectImport(app: App) {
// let main = app.actions.get('main');
// if (!main) {
// return;
// }
// if (!main.execute) {
// return;
// }
// let filename = main.execute.split('/')[0];
// let extname = path.extname(filename).slice(1);
//
// // let remote = require('electron').remote
// let filePaths = await new Promise<string[]>((resolve, reject) => {
// remote.dialog.showOpenDialog({
// filters: [{name: filename, extensions: [extname]}],
// properties: ['openFile']
// }, resolve);
// });
//
// if (filePaths && filePaths[0]) {
// this.import_path = filePaths[0];
// }
//
// }
onPoints(points: any) {
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://sapi.moecube.com:444/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;
// }
}
......@@ -2,23 +2,33 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule, NO_ERRORS_SCHEMA } from '@angular/cor
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { MyCardComponent } from './mycard.component';
import { MyCardComponent } from './mycard/mycard.component';
// import { WebviewDirective } from './shared/webview.directive';
import { FormsModule } from '@angular/forms';
import { LoginComponent } from './login/login.component';
import { LobbyComponent } from './lobby/lobby.component';
import { AppDetailComponent } from './app-detail/app-detail.component';
import { CandyComponent } from './candy/candy.component';
import { NetworkComponent } from './network/network.component';
import { YGOProComponent } from './ygopro/ygopro.component';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
MyCardComponent,
// WebviewDirective,
LoginComponent,
LobbyComponent
LobbyComponent,
AppDetailComponent,
CandyComponent,
NetworkComponent,
YGOProComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule
FormsModule,
HttpClientModule
],
providers: [],
bootstrap: [MyCardComponent],
......
This diff is collapsed.
<!--<div id="candy" data-MinOrMax="default">-->
<!--</div>-->
<!--<div style="position:absolute; top:5px; right:10px;">-->
<!-- <i id="minimize" class="fa fa-minus hover-color" (click)="minimize()" data-size="" i18n-title title="最小化"></i>-->
<!-- <i id="unminimize" class="fa fa-minus hover-color" (click)="restore()" data-size="" i18n-title title="取消最小化" hidden></i>-->
<!-- <i id="restore" class="fa fa fa-chevron-down hover-color" (click)="restore()" data-size="" i18n-title title="还原" hidden></i>-->
<!-- <i id="maximize" class="fa fa fa-chevron-up hover-color" (click)="maximize()" i18n-title title="最大化"></i>-->
<!--</div>-->
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-candy',
templateUrl: './candy.component.html',
styleUrls: ['./candy.component.css']
})
export class CandyComponent implements OnInit {
constructor() {
}
ngOnInit(): void {
}
}
/**
* Created by weijian on 2016/10/26.
*/
import {EventEmitter, Injectable, NgZone} from '@angular/core';
import { HttpClient } from '@angular/common/http';
// import {error} from 'util';
// import Timer = NodeJS.Timer;
// const Logger = {
// 'error': (message: string) => {
// console.error('DownloadService: ', message);
// }
// };
import Aria2 from 'aria2';
const MAX_LIST_NUM = 1000;
const ARIA2_INTERVAL = 500;
export class DownloadStatus {
completedLength: number;
downloadSpeed: number;
get downloadSpeedText (): string {
if (!isNaN(this.downloadSpeed) && this.downloadSpeed !== 0) {
const speedUnit = ['Byte/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s'];
let currentUnit = Math.floor(Math.log(this.downloadSpeed) / Math.log(1024));
return (this.downloadSpeed / 1024 ** currentUnit).toFixed(1) + ' ' + speedUnit[currentUnit];
}
return '';
};
gid: string;
status: string;
totalLength: number;
totalLengthText: string;
errorCode: string;
errorMessage: string;
combine (...others: DownloadStatus[]): DownloadStatus {
const priority = {
undefined: -1,
'': -1,
'active': 0,
'complete': 0,
'paused': 1,
'waiting': 1,
'removed': 2,
'error': 3
};
let status = Object.assign(new DownloadStatus(), this);
for (let o of others) {
if (priority[o.status] > priority[status.status]) {
status.status = o.status;
if (status.status === 'error') {
status.errorCode = o.errorCode;
status.errorMessage = o.errorMessage;
}
status.downloadSpeed += o.downloadSpeed;
status.totalLength += o.totalLength;
status.completedLength += o.completedLength;
}
}
return status;
}
// 0相等. 1不想等
compareTo (other: DownloadStatus): number {
if (this.status !== other.status ||
this.downloadSpeed !== other.downloadSpeed ||
this.completedLength !== other.completedLength ||
this.totalLength !== other.totalLength) {
return 1;
} else {
return 0;
}
}
constructor (item ?: any) {
if (item) {
this.completedLength = parseInt(item.completedLength) || 0;
this.downloadSpeed = parseInt(item.downloadSpeed) || 0;
this.totalLength = parseInt(item.totalLength) || 0;
this.gid = item.gid;
this.status = item.status;
this.errorCode = item.errorCode;
this.errorMessage = item.errorMessage;
} else {
this.completedLength = 0;
this.downloadSpeed = 0;
this.totalLength = 0;
}
}
}
@Injectable({
providedIn: 'root'
})
export class DownloadService {
// 强制指定IPv4,接到过一个反馈无法监听v6的。默认的host值是localhost,会连v6。
aria2 = new Aria2({host: '127.0.0.1'});
open = this.aria2.open();
updateEmitter = new EventEmitter<void>();
downloadList: Map<string, DownloadStatus> = new Map();
taskMap: Map<string, string[]> = new Map();
async refreshDownloadList () {
let activeList = await this.aria2.tellActive();
let waitList = await this.aria2.tellWaiting(0, MAX_LIST_NUM);
let stoppedList = await this.aria2.tellStopped(0, MAX_LIST_NUM);
this.downloadList.clear();
for (let item of activeList) {
this.downloadList.set(item.gid, new DownloadStatus(item));
}
for (let item of waitList) {
this.downloadList.set(item.gid, new DownloadStatus(item));
}
for (let item of stoppedList) {
this.downloadList.set(item.gid, new DownloadStatus(item));
}
this.updateEmitter.emit();
}
constructor (private ngZone: NgZone) {
ngZone.runOutsideAngular(async () => {
await this.open;
setInterval(async () => {
await this.refreshDownloadList();
}, ARIA2_INTERVAL);
});
}
private createId (): string {
function s4 () {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
async progress (id: string, callback: (downloadStatus: DownloadStatus) => void) {
return new Promise((resolve, reject) => {
let gids = this.taskMap.get(id);
if (gids) {
let allStatus: DownloadStatus;
let subscription = this.updateEmitter.subscribe(() => {
try {
let status: DownloadStatus = new DownloadStatus();
// 合并每个状态信息
status =
gids!.map((value, index, array) => {
let s = this.downloadList.get(value);
if (!s) {
throw 'Gid not exists';
}
return s;
})
.reduce((previousValue, currentValue, currentIndex, array) => {
return previousValue.combine(currentValue);
}, status);
if (!allStatus) {
allStatus = status;
} else {
if (allStatus.compareTo(status) !== 0) {
allStatus = status;
}
}
if (allStatus.status === 'error') {
throw `Download Error: code ${allStatus.errorCode}, message: ${allStatus.errorMessage}`;
} else if (allStatus.status === 'complete') {
resolve(null);
subscription.unsubscribe();
} else {
callback(allStatus);
}
} catch (e) {
reject(e);
subscription.unsubscribe();
}
});
} else {
throw 'Try to access invalid download id';
}
});
}
async getFiles (id: string): Promise<string[]> {
let gids = this.taskMap.get(id)!;
let files: string[] = [];
for (let gid of gids) {
let file = await this.aria2.getFiles(gid);
files.push(file[0].path);
}
return files;
}
async addMetalink (metalink: string, library: string): Promise<string> {
let encodedMeta4 = Buffer.from((metalink)).toString('base64');
let gidList = await this.aria2.addMetalink(encodedMeta4, {dir: library});
let taskId = this.createId();
this.taskMap.set(taskId, gidList);
// 每次添加任务,刷新一下本地任务列表
await this.refreshDownloadList();
return taskId;
}
async addUri (url: string, destination: string): Promise<string> {
await this.open;
let taskId = this.createId();
let gid = await this.aria2.addUri([url], {dir: destination});
this.taskMap.set(taskId, [gid]);
return taskId;
}
async pause (id: string): Promise<void> {
await this.open;
try {
await this.aria2.pause(id);
} catch (e) {
}
}
}
:host {
display: flex;
height: 100%;
}
#right {
display: flex;
flex-direction: column;
flex-grow: 1;
}
#main {
display: flex;
flex-grow: 1;
}
#candy-wrapper {
background-color: #444;
height: 230px;
flex-shrink: 0;
position: relative;
}
roster {
width: 190px;
background-color: #f7f7f9;
flex-shrink: 0;
}
/*a {*/
/*display: block;*/
/*padding: 10px 20px 10px 20px;*/
/*}*/
/*a:focus, a:hover {*/
/*text-decoration: none;*/
/*}*/
/*.active {*/
/*background-color: #428bca;*/
/*}*/
/*.active > a {*/
/*color: #fff;*/
/*}*/
span {
margin: 12px 0 8px 8px;
color: #a7a7a7;
font-size: 14px;
display: block;
}
.actions {
margin-bottom: 1em;
}
.progress {
height: 1em;
width: 1em;
float: right;
margin: 5px;
position: relative;
}
.pie {
height: 100%;
width: 100%;
clip: rect(0, 1em, 1em, 0.5em);
left: 0;
position: absolute;
top: 0;
}
.half-circle {
height: 100%;
width: 100%;
border: 0.2em solid #3498db;
border-radius: 50%;
clip: rect(0, 0.5em, 1em, 0);
left: 0;
position: absolute;
top: 0;
}
.shadow {
height: 100%;
width: 100%;
border: 0.2em solid #bdc3c7;
border-radius: 50%;
}
.right-side {
display: none;
}
.half-circle {
/*border-color: #e74c3c;*/
border-color: rgb(0, 116, 217);
}
.left-side {
/*transform: rotate(1turn);*/
/*在前台用Angular填写*/
}
.second-half {
clip: rect(auto, auto, auto, auto);
}
.second-half > .right-side {
display: inherit;
transform: rotate(0.5turn);
}
.fa-spin {
margin: 5px;
color: #0275d8;
font-weight: bold;
float: right;
}
#nav-wrapper {
width: 190px;
height: 100%;
flex-shrink: 0;
background-color: #f7f7f9;
border-right: 1px solid #eee;
padding-left: 0;
padding-right: 0;
padding-top: 20px;
/*resize: horizontal;*/
position: relative;
}
nav {
height: 100%;
}
.sidebar .nav {
margin-bottom: 20px;
}
.sidebar .nav-item {
width: 100%;
}
.sidebar .nav-item + .nav-item {
margin-left: 0;
}
.sidebar .nav-link {
border-radius: 0;
}
.nav-link {
padding: .3em 1em;
color: black;
font-size: 15px;
}
.nav-link.active {
background-color: #ebf3f8;
color: #00a4d9;
}
a {
cursor: default;
}
.search {
background-color: #ebf3f8;
border: none;
}
i.search {
color: #a7a7a7;
}
input.search {
padding-left: 0;
font-size: 14px;
font-family: -apple-system, Arial, 'Source Sans Pro', "Microsoft YaHei", 'Microsoft JhengHei', "WenQuanYi Micro Hei", sans-serif;
}
input.search::-webkit-input-placeholder {
color: #a7a7a7;
}
#search {
padding: 0 10px;
}
.icon {
width: 16px;
height: 16px;
}
nav::-webkit-resizer {
/*width: 100px;*/
/*height: 100px;*/
/*background-color: red;*/
border: 2px solid yellow;
background: blue;
box-shadow: 0 0 2px 5px red;
outline: 2px dashed green;
/*size does not work*/
display: block;
width: 150px !important;
height: 150px !important;
position: fixed;
}
.resize-wrapper {
position: relative;
}
.resize {
position: absolute;
}
.resize-right .resize {
width: 4px;
right: -2px;
top: 0;
bottom: 0;
cursor: col-resize;
}
.resize-top .resize {
height: 4px;
top: -2px;
left: 0;
right: 0;
cursor: row-resize;
}
#nav-wrapper {
z-index: 90;
box-shadow: 0 0 5px rgba(0, 0, 0, .2);
}
#candy-wrapper {
z-index: 80;
box-shadow: 0 0 5px rgba(0, 0, 0, .2);
}
roster {
z-index: 90;
}
/*#right-shadow {*/
/*width: 190px;*/
/*z-index: 95;*/
/*box-shadow: 0 0 5px rgba(0, 0, 0, .2);*/
/*position: absolute;*/
/*top: 0;*/
/*right: 0;*/
/*bottom: 0;*/
/*}*/
<p>lobby works!</p>
<!-- Begin page content -->
<div #nav class='resize-wrapper resize-right' id='nav-wrapper'>
<nav *ngIf='apps' class='bg-faded sidebar scroll' id='apps'>
<!-- <div id="search" class="input-group">-->
<!-- <i class="fa fa-search input-group-addon search" id="basic-addon1"></i>-->
<!-- <input i18n-placeholder #search id="search-input" type="text" class="form-control search" placeholder="搜索游戏" aria-describedby="basic-addon1">-->
<!-- </div>-->
<span *ngIf='grouped_apps.installed' i18n>已安装</span>
<ul *ngIf='grouped_apps.installed' class='nav nav-pills flex-column'>
<li *ngFor='let app of grouped_apps.installed' class='nav-item'>
<a (click)='$event.preventDefault(); chooseApp(app)' [class.active]='app===currentApp' [href]="'https://mycard.moe/' + app.id"
class='nav-link'>
<img *ngIf='app.icon' [src]='app.icon' class='icon'>
{{app.name}}<i *ngIf='!app.isReady() && !app.status.total'
class='spin fa fa-circle-o-notch fa-spin fa-fw'></i>
<div *ngIf='!app.isReady() && app.status.total' class='progress'>
<div [class.second-half]='app.status.progress/app.status.total>0.5' class='pie'>
<div [style.transform]="'rotate('+(app.status.progress/app.status.total).toString()+'turn)'"
class='left-side half-circle'></div>
<div class='right-side half-circle'></div>
</div>
<div class='shadow'></div>
</div>
</a>
</li>
</ul>
<span *ngIf='grouped_apps.test'>测试</span>
<ul *ngIf='grouped_apps.test' class='nav nav-pills flex-column'>
<li *ngFor='let app of grouped_apps.test' class='nav-item'>
<a (click)='$event.preventDefault(); chooseApp(app)' [class.active]='app===currentApp' [href]="'https://mycard.moe/' + app.id"
class='nav-link'>
<img *ngIf='app.icon' [src]='app.icon' class='icon'>
{{app.name}}
</a>
</li>
</ul>
<span *ngIf='grouped_apps.recommend' i18n>推荐</span>
<ul *ngIf='grouped_apps.recommend' class='nav nav-pills flex-column'>
<li *ngFor='let app of grouped_apps.recommend' class='nav-item'>
<a (click)='$event.preventDefault(); chooseApp(app)' [class.active]='app===currentApp' [href]="'https://mycard.moe/' + app.id"
class='nav-link'>
<img *ngIf='app.icon' [src]='app.icon' class='icon'>
{{app.name}}
</a>
</li>
</ul>
<span *ngIf='grouped_apps.ygopro' i18n>YGOPro 各发行版</span>
<ul *ngIf='grouped_apps.recommend' class='nav nav-pills flex-column'>
<li *ngFor='let app of grouped_apps.ygopro' class='nav-item'>
<a (click)='$event.preventDefault(); chooseApp(app)' [class.active]='app===currentApp' [href]="'https://mycard.moe/' + app.id"
class='nav-link'>
<img *ngIf='app.icon' [src]='app.icon' class='icon'>
{{app.name}}
</a>
</li>
</ul>
<span *ngIf='grouped_apps.mysterious' i18n>迷之物体</span>
<ul *ngIf='grouped_apps.mysterious' class='nav nav-pills flex-column'>
<li *ngFor='let app of grouped_apps.mysterious' class='nav-item'>
<a (click)='$event.preventDefault(); chooseApp(app)' [class.active]='app===currentApp' [href]="'https://mycard.moe/' + app.id"
class='nav-link'>
<img *ngIf='app.icon' [src]='app.icon' class='icon'>
{{app.name}}
</a>
</li>
</ul>
<span *ngIf='grouped_apps.touhou' i18n>东方 Project</span>
<ul *ngIf='grouped_apps.touhou' class='nav nav-pills flex-column'>
<li *ngFor='let app of grouped_apps.touhou' class='nav-item'>
<a (click)='$event.preventDefault(); chooseApp(app)' [class.active]='app===currentApp' [href]="'https://mycard.moe/' + app.id"
class='nav-link'>
<img *ngIf='app.icon' [src]='app.icon' class='icon'>
{{app.name}}
</a>
</li>
</ul>
<span *ngIf='grouped_apps.touhou_pc98' i18n>东方旧作</span>
<ul *ngIf='grouped_apps.touhou_pc98' class='nav nav-pills flex-column'>
<li *ngFor='let app of grouped_apps.touhou_pc98' class='nav-item'>
<a (click)='$event.preventDefault(); chooseApp(app)' [class.active]='app===currentApp' [href]="'https://mycard.moe/' + app.id"
class='nav-link'>
<img *ngIf='app.icon' [src]='app.icon' class='icon'>
{{app.name}}
</a>
</li>
</ul>
<span *ngIf='grouped_apps.runtime_installed' i18n>已安装的运行库</span>
<ul *ngIf='grouped_apps.runtime_installed' class='nav nav-pills flex-column'>
<li *ngFor='let app of grouped_apps.runtime_installed' class='nav-item'>
<a (click)='$event.preventDefault(); chooseApp(app)' [class.active]='app===currentApp' [href]="'https://mycard.moe/' + app.id"
class='nav-link'>
<img *ngIf='app.icon' [src]='app.icon' class='icon'>
{{app.name}}
</a>
</li>
</ul>
</nav>
<div (mousedown)='mousedown($event)' class='resize'></div>
</div>
<div id='right'>
<!-- <div id='main'>-->
<app-detail *ngIf='currentApp' [currentApp]='currentApp' class='scroll'></app-detail>
<!-- <roster class='scroll'></roster>-->
<!-- </div>-->
<div class='resize-wrapper resize-top' id='candy-wrapper' style='max-height: calc( 100% - 180px )'>
<div (mousedown)='mousedown($event)' class='resize'></div>
<candy *ngIf='currentApp' [currentApp]='currentApp'></candy>
</div>
</div>
<div id='right-shadow'></div>
import { Component, OnInit } from '@angular/core';
/**
* Created by zh99998 on 16/9/2.
*/
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { AppsService } from '../apps.service';
import { LoginService } from '../login/login.service';
import { App, Category } from '../shared/app';
import { shell } from 'electron';
import { SettingsService } from '../settings.service';
const ReconnectingWebSocket = require('reconnecting-websocket');
// import 'typeahead.js';
// import Options = Twitter.Typeahead.Options;
@Component({
selector: 'app-lobby',
templateUrl: './lobby.component.html',
styleUrls: ['./lobby.component.css']
selector: 'lobby',
templateUrl: 'lobby.component.html',
styleUrls: ['lobby.component.css']
})
export class LobbyComponent implements OnInit {
constructor() { }
currentApp: App;
resizing: HTMLElement | undefined;
offset: number;
@ViewChild('search')
search: ElementRef;
public apps: Map<string, App>;
//private messages: WebSocket;
constructor(private appsService: AppsService, private loginService: LoginService,
private settingsService: SettingsService, private ref: ChangeDetectorRef) {
}
get grouped_apps(): any {
// @ts-ignore
let contains = ['game', 'music', 'book'].map((value) => Category[value]);
let result = { runtime: [] };
for (let app of this.apps.values()) {
let tags: string[];
if (contains.includes(app.category)) {
if (app.isInstalled()) {
tags = ['installed'];
} else {
tags = app.tags || ['test'];
}
} else {
if (app.isInstalled()) {
tags = ['runtime_installed'];
} else {
tags = ['runtime'];
}
}
for (const tag of tags) {
if (!result[tag]) {
result[tag] = [];
}
result[tag].push(app);
}
}
return result;
}
async ngOnInit() {
this.apps = await this.appsService.loadApps();
if (this.apps.size > 0) {
this.chooseApp(this.appsService.lastVisited || this.apps.get('ygopro')!);
ngOnInit(): void {
await this.appsService.migrate();
for (let app of this.apps.values()) {
await this.appsService.update(app);
}
} else {
if (confirm('获取程序列表失败,是否重试?')) {
location.reload();
} else {
window.close();
}
}
// 特化个 YGOPRO 国际服聊天室。其他的暂时没需求。
if (!this.settingsService.getLocale().startsWith('zh')) {
this.apps.get('ygopro')!.conference = 'ygopro-international';
}
this.ref.detectChanges();
/* let url = new URL('wss://sapi.moecube.com:444: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>{
// minLength: 1,
// highlight: true
// }, {
// name: 'apps',
// source: (query, syncResults) => {
// query = query.toLowerCase();
// let result = Array.from(this.apps.values())
// .filter((app: App) => [Category.game, Category.music, Category.book].includes(app.category))
// .filter((app: App) => app.id.includes(query) || app.name.toLowerCase().includes(query))
// .map((app: App) => app.name);
// console.log(result);
// syncResults(result);
// }
// });
document.addEventListener('mousemove', (event: MouseEvent) => {
if (!this.resizing) {
return;
}
if (this.resizing.classList.contains('resize-right')) {
let width = this.offset + event.clientX;
if (width < 190) {
width = 190;
}
if (width > 400) {
width = 400;
}
this.resizing.style.width = `${width}px`;
} else {
let height = this.offset - event.clientY;
let main_height = event.clientY - document.getElementById('navbar')!.clientHeight;
// console.log(event.clientY);
if (height > 150 && main_height > 180) {
if (height < 230) {
height = 230;
}
this.resizing.style.height = `${height}px`;
if ($('#candy').attr('data-minormax') !== 'default') {
$('#candy').attr('data-minormax', 'default');
$('#mobile-roster-icon').css('display', 'block');
$('#chat-toolbar').css('display', 'block');
$('#chat-rooms').css('display', 'block');
$('#context-menu').css('display', 'block');
$('#mobile-roster-icon').css('display', 'block');
$('#minimize').show();
$('#unminimize').hide();
$('#restore').hide();
$('#maximize').show();
}
} else if (height <= 150) {
$('#candy').attr('data-minormax', 'min');
this.resizing.style.height = '31px';
$('#mobile-roster-icon').css('display', 'none');
$('#chat-toolbar').css('display', 'none');
$('#chat-rooms').css('display', 'none');
$('#context-menu').css('display', 'none');
$('#mobile-roster-icon').css('display', 'none');
$('#minimize').hide();
$('#unminimize').show();
$('#restore').hide();
$('#maximize').show();
} else if (main_height <= 180) {
$('#candy').attr('data-minormax', 'max');
this.resizing.style.height = 'calc( 100% - 180px )';
$('#minimize').show();
$('#unminimize').hide();
$('#restore').show();
$('#maximize').hide();
}
}
});
document.addEventListener('mouseup', (event: MouseEvent) => {
document.body.classList.remove('resizing');
this.resizing = undefined;
});
}
mousedown(event: MouseEvent) {
// console.log(()
document.body.classList.add('resizing');
this.resizing = <HTMLElement>(<HTMLElement>event.target).parentNode;
if (this.resizing.classList.contains('resize-right')) {
this.offset = this.resizing.offsetWidth - event.clientX;
} else {
this.offset = this.resizing.offsetHeight + event.clientY;
}
}
chooseApp(app: App) {
this.currentApp = app;
this.appsService.lastVisited = app;
}
openExternal(url: string) {
shell.openExternal(url);
}
}
:host {
display: flex;
}
webview {
flex-grow: 1;
}
{{url | json}}
<!--<webview [src]="url" (will-navigate)="return_sso($event.url)" (new-window)="openExternal($event.url)"></webview>-->
<webview (new-window)='openExternal($event.url)' (will-navigate)='return_sso($event.url)' [src]='url'></webview>
......@@ -7,7 +7,7 @@ import crypto from 'crypto';
import { shell } from 'electron';
@Component({
selector: 'app-login',
selector: 'login',
templateUrl: 'login.component.html',
styleUrls: ['login.component.css']
})
......
<!--<div class="container">-->
<header class='navbar navbar-light bg-light'>
<a class='navbar-brand' href='#'>
<img class='me-2' src='assets/icon.ico'>
<span class='text-primary'>MyCard</span>
</a>
<ul class="nav me-auto">
<li *ngIf="!loginService.logged_in" class="nav-item active">
<a i18n class="nav-link" href="#">登录</a>
</li>
<!--<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'store'}" class="nav-item">-->
<!--<a (click)="currentPage = 'store'" class="nav-link" href="#">商店</a>-->
<!--</li>-->
<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'lobby'}" class="nav-item">
<a i18n (click)="currentPage='lobby'" class="nav-link" href="#">游戏</a>
</li>
<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'community'}" class="nav-item">
<a i18n (click)="currentPage='community'" class="nav-link" href="#">社区</a>
</li>
<!--<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'moesound'}" class="nav-item">-->
<!--<a i18n (click)="currentPage='moesound'" class="nav-link" href="#">萌音</a>-->
<!--</li>-->
<!--<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'about'}" class="nav-item">-->
<!--<a i18n (click)="currentPage='about'" class="nav-link" href="#">关于</a>-->
<!--</li>-->
</ul>
<div id="navbar-right" class='text-secondary'>
<span id="update-status">
<i #error [hidden]="update_status != 'error'" (click)="update_retry()" class="fa fa-exclamation-circle" data-bs-toggle="tooltip" i18n-title title="更新出错,点击重试"></i>
<i #checking_for_update [hidden]="update_status != 'checking-for-update'" class="fa fa-spinner fa-pulse fa-spin" data-bs-toggle="tooltip" i18n-title title="正在检查更新"></i>
<i #update_available [hidden]="update_status != 'update-available'" class="fa fa-refresh fa-spin" data-bs-toggle="tooltip" i18n-title title="正在下载更新"></i>
<i #update_downloaded [hidden]="update_status != 'update-downloaded'" (click)="update_install()" class="fa fa-angle-double-up" data-bs-toggle="tooltip" i18n-title title="下载更新完成,点击安装"></i>
</span>
<span id="user" *ngIf="loginService.logged_in">
<a href="#" class="profile"><img id="avatar" [src]="loginService.user.avatar_url" alt="image"></a>
<a href="#" class="profile item" id="username">{{loginService.user.username}}</a>
<i i18n (click)="loginService.logout()" class="fa fa-sign-out item-icon" aria-hidden="true" i18n-title title="切换用户"></i>
<i i18n data-bs-toggle="modal" data-bs-target="#settings-modal" class="fa fa-cog item-icon" aria-hidden="true" i18n-title title="设置"></i>
</span>
<span id="border">|</span>
<span id="window-buttons">
<i i18n (click)="currentWindow.minimize()" class="fa fa-minus" i18n-title title="最小化"></i>
<i i18n *ngIf="!currentWindow.isMaximized()" (click)="currentWindow.maximize()" class="fa fa-expand" i18n-title title="最大化"></i>
<i i18n *ngIf="currentWindow.isMaximized()" (click)="currentWindow.unmaximize()" class="fa fa-clone" i18n-title title="还原"></i>
<i i18n (click)="currentWindow.hide()" class="fa fa-times" i18n-title title="关闭"></i>
</span>
</div>
</header>
<app-login id='login' class="page" *ngIf="!loginService.logged_in"></app-login>
<!--<store class="page" *ngIf="loginService.logged_in" [hidden]="currentPage != 'store'"></store>-->
<app-lobby class="page" *ngIf="loginService.logged_in" [hidden]="currentPage != 'lobby'"></app-lobby>
<webview class="page" *ngIf="loginService.logged_in" [hidden]="currentPage != 'community'" src="https://ygobbs.com" (new-window)="openExternal($event.url)"></webview>
<!--<about class="page" *ngIf="loginService.logged_in" [hidden]="currentPage != 'about'"></about>-->
<!-- Modal -->
<div class="modal fade" id="settings-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 i18n class="modal-title" id="myModalLabel">MyCard 设置</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form (submit)="submit()">
<div class="modal-body">
<div class="container">
<div class="form-group row">
<label i18n for="locale" class="col-sm-2 col-form-label">语言</label>
<div class="col-sm-10">
<select class="form-control" id="locale" [(ngModel)]="locale" name="locale">
<option value="en-US">English</option>
<option value="zh-CN">简体中文</option>
</select>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button i18n type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button i18n type="submit" class="btn btn-primary" data-bs-dismiss="modal">确定</button>
</div>
</form>
</div>
</div>
</div>
.navbar {
padding: initial;
}
.navbar-brand {
width: 200px;
font-size: 24px;
text-align: center;
img {
width: 28px;
vertical-align: text-bottom;
}
}
.nav {
.nav-link {
font-size: 18px;
color: #a7a7a7;
padding: .8rem 1.2em;
}
.nav-item.active {
background-color: white;
.nav-link {
color: #00a4d9;
}
}
}
#navbar-right {
display: flex;
#avatar {
height: 1.5rem;
}
.item {
display: block;
//float: left;
//padding-top: .425rem;
//padding-bottom: .425rem;
margin: 0 0.8rem;
text-decoration: none;
color: #a7a7a7;
}
}
/*:host {*/
/*background-color: white;*/
/*}*/
.page {
flex-grow: 1;
/*margin-bottom: 60px;*/
}
#avatar {
display: block;
float: left;
border-radius: 10%;
height: 1.5rem;
margin-top: 0.475rem;
}
.item {
display: block;
float: left;
padding-top: .425rem;
padding-bottom: .425rem;
margin: 0 0.8rem;
text-decoration: none;
color: #a7a7a7;
}
.item-icon {
color: #a7a7a7;
font-size: 18px;
margin: .6rem .3rem;
}
.item:hover, .item-icon:hover, .nav-link:hover {
color: #5e5e5e;
}
#update-status > i {
line-height: 1.5;
color: #5e5e5e;
padding-top: .425rem;
padding-bottom: .425rem;
}
a {
cursor: default;
}
/* https://github.com/electron/electron/issues/7661#event-827104990 */
lobby[hidden], webview[hidden] {
width: 0;
height: 0;
flex: 0 1;
display: inherit !important;
overflow: hidden;
}
/* 不加这个切到有 Webview 的页面,上面的圆角会消失 */
/* 即使加了这个,下面的圆角也会消失 */
/*webview {*/
/*overflow: hidden;*/
/*}*/
#navbar {
background-color: #f7f7f9 !important;
padding: 0;
}
#navbar-brand {
color: #00a4d9;
font-size: 24px;
width: 190px;
margin: 0;
text-align: center;
}
#navbar-brand img {
width: 24px;
margin-top: -5px;
}
#navbar .nav-link {
font-size: 18px;
color: #a7a7a7;
padding: .8rem 1.2em;
}
.nav-item.active {
background-color: white;
}
#navbar .nav-item.active .nav-link {
color: #00a4d9;
}
#border {
margin: .2rem 0.4rem;
color: #a7a7a7;
font-size: 1.2rem;
}
#navbar {
z-index: 100;
}
#settings-modal .modal-dialog {
margin-top: 50px;
}
<header class='navbar navbar-toggleable-md navbar-light' id='navbar'>
<a class='navbar-brand' href='#' id='navbar-brand'>
<img src='assets/icon.ico' /> MyCard
</a>
<ul class='nav me-auto'>
<li *ngIf='!loginService.logged_in' class='nav-item active'>
<a class='nav-link' href='#' i18n>登录</a>
</li>
<!--<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'store'}" class="nav-item">-->
<!--<a (click)="currentPage = 'store'" class="nav-link" href="#">商店</a>-->
<!--</li>-->
<li *ngIf='loginService.logged_in' [ngClass]="{active: currentPage === 'lobby'}" class='nav-item'>
<a (click)="currentPage='lobby'" class='nav-link' href='#' i18n>游戏</a>
</li>
<li *ngIf='loginService.logged_in' [ngClass]="{active: currentPage === 'community'}" class='nav-item'>
<a (click)="currentPage='community'" class='nav-link' href='#' i18n>社区</a>
</li>
<!--<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'moesound'}" class="nav-item">-->
<!--<a i18n (click)="currentPage='moesound'" class="nav-link" href="#">萌音</a>-->
<!--</li>-->
<!--<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'about'}" class="nav-item">-->
<!--<a i18n (click)="currentPage='about'" class="nav-link" href="#">关于</a>-->
<!--</li>-->
</ul>
<div id='navbar-right'>
<div id='update-status'>
<i #error (click)='update_retry()' [hidden]="update_status != 'error'" class='fa fa-exclamation-circle'
data-bs-toggle='tooltip' i18n-title title='更新出错,点击重试'></i>
<i #checking_for_update [hidden]="update_status != 'checking-for-update'" class='fa fa-spinner fa-pulse fa-spin'
data-bs-toggle='tooltip' i18n-title title='正在检查更新'></i>
<i #update_available [hidden]="update_status != 'update-available'" class='fa fa-refresh fa-spin'
data-bs-toggle='tooltip' i18n-title title='正在下载更新'></i>
<i #update_downloaded (click)='update_install()' [hidden]="update_status != 'update-downloaded'"
class='fa fa-angle-double-up' data-bs-toggle='tooltip' i18n-title title='下载更新完成,点击安装'></i>
</div>
<div *ngIf='loginService.logged_in' id='user'>
<a class='profile' href='#'><img [src]='loginService.user.avatar_url' alt='image' id='avatar'></a>
<a class='profile item' href='#' id='username'>{{loginService.user.username}}</a>
<i (click)='loginService.logout()' aria-hidden='true' class='fa fa-sign-out item-icon' i18n i18n-title
title='切换用户'></i>
<i aria-hidden='true' class='fa fa-cog item-icon' data-bs-target='#settings-modal' data-bs-toggle='modal' i18n
i18n-title title='设置'></i>
</div>
<div id='border'>|</div>
<div id='window-buttons'>
<i (click)='currentWindow.minimize()' class='fa fa-minus' i18n i18n-title title='最小化'></i>
<i (click)='currentWindow.maximize()' *ngIf='!currentWindow.isMaximized()' class='fa fa-expand' i18n i18n-title
title='最大化'></i>
<i (click)='currentWindow.unmaximize()' *ngIf='currentWindow.isMaximized()' class='fa fa-clone' i18n i18n-title
title='还原'></i>
<i (click)='currentWindow.hide()' class='fa fa-times' i18n i18n-title title='关闭'></i>
</div>
</div>
</header>
<login *ngIf='!loginService.logged_in' class='page'></login>
<store *ngIf='loginService.logged_in' [hidden]="currentPage != 'store'" class='page'></store>
<lobby *ngIf='loginService.logged_in' [hidden]="currentPage != 'lobby'" class='page'></lobby>
<webview (new-window)='openExternal($event.url)' *ngIf='loginService.logged_in' [hidden]="currentPage != 'community'" class='page'
src='https://ygobbs.com'></webview>
<!--<webview #moesound preload="./moesound.js" class="page" *ngIf="loginService.logged_in" [hidden]="currentPage != 'moesound'" src="http://moesound.com/" (new-window)="moesound_newwindow($event.url)" (did-finish-load)="moesound_loaded()"></webview>-->
<about *ngIf='loginService.logged_in' [hidden]="currentPage != 'about'" class='page'></about>
<!-- Modal -->
<div aria-hidden='true' aria-labelledby='myModalLabel' class='modal fade' id='settings-modal' role='dialog'
tabindex='-1'>
<div class='modal-dialog' role='document'>
<div class='modal-content'>
<div class='modal-header'>
<h5 class='modal-title' i18n id='myModalLabel'>MyCard 设置</h5>
<button aria-label='Close' class='btn-close' data-bs-dismiss='modal' type='button'></button>
</div>
<form (submit)='submit()'>
<div class='modal-body'>
<div class='container'>
<div class='form-group row'>
<label class='col-sm-2 col-form-label' for='locale' i18n>语言</label>
<div class='col-sm-10'>
<select [(ngModel)]='locale' class='form-control' id='locale' name='locale'>
<option value='en-US'>English</option>
<option value='zh-CN'>简体中文</option>
</select>
</div>
</div>
</div>
</div>
<div class='modal-footer'>
<button class='btn btn-secondary' data-bs-dismiss='modal' i18n type='button'>取消</button>
<button class='btn btn-primary' data-bs-dismiss='modal' i18n type='submit'>确定</button>
</div>
</form>
</div>
</div>
</div>
import { LoginService } from './login/login.service';
import { SettingsService } from './settings.service';
import { Tooltip } from 'bootstrap';
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { app, getCurrentWindow, getGlobal, shell } from '@electron/remote';
import Mousetrap from 'mousetrap';
import { shell } from 'electron';
import * as remote from '@electron/remote';
import $ from 'jquery';
import { LoginService } from '../login/login.service';
import { SettingsService } from '../settings.service';
import { Tooltip } from 'bootstrap';
const autoUpdater: Electron.AutoUpdater = getGlobal('autoUpdater');
const autoUpdater: Electron.AutoUpdater = remote.getGlobal('autoUpdater');
const konami_code_logger: string[] = [];
@Component({
selector: 'app-root',
selector: 'mycard',
templateUrl: 'mycard.component.html',
styleUrls: ['mycard.component.scss']
styleUrls: ['mycard.component.css'],
})
export class MyCardComponent implements OnInit {
currentPage: string = 'lobby';
update_status: string | undefined = getGlobal('update_status');
update_status: string | undefined = remote.getGlobal('update_status');
update_error: string | undefined;
currentWindow = getCurrentWindow();
currentWindow = remote.getCurrentWindow();
window = window;
@ViewChild('error')
error: ElementRef;
......@@ -28,7 +30,7 @@ export class MyCardComponent implements OnInit {
update_available: ElementRef;
@ViewChild('update_downloaded')
update_downloaded: ElementRef;
update_elements: Map<string, ElementRef<HTMLElement>>;
update_elements: Map<string, ElementRef>;
locale: string;
......@@ -38,7 +40,44 @@ export class MyCardComponent implements OnInit {
moesound: ElementRef;
lastTooltip: Tooltip;
ngOnInit() {
this.update_elements = new Map(
Object.entries({
error: this.error,
'checking-for-update': this.checking_for_update,
'update-available': this.update_available,
'update-downloaded': this.update_downloaded,
})
);
$('#settings-modal').on('keyup', (event) => {
konami_code_logger.unshift(event.key);
if (
konami_code_logger[9] == 'ArrowUp' &&
konami_code_logger[8] == 'ArrowUp' &&
konami_code_logger[7] == 'ArrowDown' &&
konami_code_logger[6] == 'ArrowDown' &&
konami_code_logger[5] == 'ArrowLeft' &&
konami_code_logger[4] == 'ArrowRight' &&
konami_code_logger[3] == 'ArrowLeft' &&
konami_code_logger[2] == 'ArrowRight' &&
konami_code_logger[1].toLowerCase() == 'b' &&
konami_code_logger[0].toLowerCase() == 'a'
) {
this.currentWindow.webContents.openDevTools();
}
});
// document.addEventListener('drop', (event)=>{
// console.log('drop', event);
// event.preventDefault();
//
// });
}
constructor(public loginService: LoginService, private ref: ChangeDetectorRef, private settingsService: SettingsService) {
// renderer.listenGlobal('window', 'message', (event) => {
// console.log(event);
// // Do something with 'event'
// });
this.currentWindow.on('maximize', () => this.ref.detectChanges());
this.currentWindow.on('unmaximize', () => this.ref.detectChanges());
......@@ -61,19 +100,6 @@ export class MyCardComponent implements OnInit {
});
this.locale = this.settingsService.getLocale();
}
ngOnInit() {
this.update_elements = new Map(Object.entries({
'error': this.error,
'checking-for-update': this.checking_for_update,
'update-available': this.update_available,
'update-downloaded': this.update_downloaded
}));
// https://www.electronjs.org/docs/tutorial/keyboard-shortcuts#%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%93
Mousetrap.bind('up up down down left right left right b a', () => this.currentWindow.webContents.openDevTools());
}
update_retry() {
......@@ -86,7 +112,9 @@ export class MyCardComponent implements OnInit {
set_update_status(status: string) {
console.log('autoUpdater', status);
if (this.lastTooltip) this.lastTooltip.dispose();
if (this.lastTooltip) {
this.lastTooltip.dispose();
}
this.update_status = status;
this.ref.detectChanges();
......@@ -102,8 +130,8 @@ export class MyCardComponent implements OnInit {
submit() {
if (this.locale !== this.settingsService.getLocale()) {
localStorage.setItem(SettingsService.SETTING_LOCALE, this.locale);
app.relaunch();
app.quit();
remote.app.relaunch();
remote.app.quit();
}
}
......
#network {
display: inline-block;
vertical-align: middle;
width: 230px;
}
#network .input-group-btn > .btn:not(:last-child):not(.dropdown-toggle) {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
#network .input-group-btn > .dropdown-toggle {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
<div id="network" *ngIf="currentApp.network && currentApp.network.protocol == 'maotama'">
<div class="input-group input-group-sm">
<input *ngIf="appsService.connections.get(currentApp)" [value]="appsService.connections.get(currentApp)!.address || 'Loading...'" readonly type="text" class="form-control" title="address">
<button i18n *ngIf="!appsService.connections.get(currentApp)" [disabled]="!appsService.allReady(currentApp)" (click)="appsService.network(currentApp, currentApp.network.servers[0])" type="button" class="btn btn-outline-secondary btn-sm">联机</button>
<button i18n *ngIf="appsService.connections.get(currentApp)" (click)="copy(appsService.connections.get(currentApp)!.address!)" [disabled]="!appsService.connections.get(currentApp)!.address" type="button" class="btn btn-outline-secondary btn-sm">复制</button>
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown"></button>
<div class="dropdown-menu" [class.dropdown-menu-right]="appsService.connections.get(currentApp)">
<h6 i18n class="dropdown-header">选择服务器</h6>
<a *ngFor="let server of currentApp.network.servers" (click)="appsService.network(currentApp, server)" class="dropdown-item" href="#">{{server.id}}</a>
<div *ngIf="appsService.connections.get(currentApp)" class="dropdown-divider"></div>
<a i18n *ngIf="appsService.connections.get(currentApp)" (click)="appsService.connections.get(currentApp)!.connection.close()" class="dropdown-item" href="#">取消</a>
</div>
</div>
</div>
import {clipboard} from 'electron';
import {Component, Injectable, Input} from '@angular/core';
import {AppsService} from '../apps.service';
import {App} from '../shared/app';
@Component({
selector: 'network',
templateUrl: 'network.component.html',
styleUrls: ['network.component.css'],
})
@Injectable()
export class NetworkComponent {
@Input()
currentApp: App;
constructor(public appsService: AppsService) {
console.log('constructor');
}
copy(text: string) {
clipboard.writeText(text);
}
}
/**
* Created by weijian on 2016/12/5.
*/
export class ComparableSet<T> extends Set<T> {
constructor(values?: Iterable<T>) {
if (values) {
super(values);
} else {
super();
}
}
isSuperset(subset: Set<T>) {
for (let elem of subset) {
if (!this.has(elem)) {
return false;
}
}
return true;
}
union(setB: Set<T>): Set<T> {
let union = new Set(this);
for (let elem of setB) {
union.add(elem);
}
return union;
}
intersection(setB: Set<T>): Set<T> {
let intersection = new Set<T>();
for (let elem of setB) {
if (this.has(elem)) {
intersection.add(elem);
}
}
return intersection;
}
difference(setB: Set<T>): Set<T> {
let difference = new Set(this);
for (let elem of setB) {
difference.delete(elem);
}
return difference;
}
}
import {App} from './app';
/**
* Created by zh99998 on 16/9/6.
*/
export class AppLocal {
path: string;
version: string;
files: Map<string, string>;
action: Map<string, {interpreter?: string, execute: string, args: string[], env: {}, open: App}>;
update(local: any) {
this.path = local.path;
this.version = local.version;
let files = new Map<string, string>();
for (let filename of Object.keys(local.files)) {
files.set(filename, local.files[filename]);
}
this.files = files;
}
toJSON() {
let t: any = {};
for (let [k, v] of this.files) {
t[k] = v;
}
return {path: this.path, version: this.version, files: t};
}
}
This diff is collapsed.
export namespace AppsJson {
export enum Locale {
zh_CN = 'zh-CN',
en_US = 'en-US',
ja_JP = 'ja-JP',
ko_KR = 'ko-KR',
pt_BR = 'pt-BR',
zh_HK = 'zh-HK',
zh_TW = 'zh-TW',
}
export enum Platform {
Linux = 'linux',
macOS = 'darwin',
Windows = 'win32',
}
export type LocaleWise<T> = Record<Locale, T>;
export type PlatformWise<T> = Record<Platform, T>;
export interface Developer {
name: string;
url: string;
}
export interface Trailer {
type: string;
url: string;
url2?: string;
}
export interface Action {
interpreter?: string;
execute: string;
args: any[];
env: Record<string, string>;
open?: string;
}
export type PlatformAction = Record<string, Action>;
export interface News {
url: string;
image: string;
title: string;
text: string;
updated_at: string;
}
export interface NetworkServer {
id: string;
url: string;
}
export interface Network {
protocol: string;
port: number;
servers: NetworkServer[];
}
export interface Syncable {
sync: boolean;
}
export interface Price {
cny: number;
usd: number;
}
export interface App {
id: string;
key?: string;
name?: LocaleWise<string>;
description?: LocaleWise<string>;
developers?: LocaleWise<Developer[]>;
publishers?: LocaleWise<Developer[]>;
released_at?: string;
category?: string;
tags?: string[];
trailer?: Trailer[];
dependencies?: PlatformWise<string[]>;
references?: PlatformWise<string[]>;
author?: string;
homepage?: string;
locales?: string[];
actions?: PlatformWise<PlatformAction>;
version?: PlatformWise<string>;
news?: LocaleWise<News[]>;
conference?: string;
icon?: string;
cover?: string;
background?: string;
parent?: string;
network?: Network;
updated_at?: string;
files?: Record<string, Syncable>;
data?: any;
price?: Price;
}
}
/**
* Created by break on 2017/6/9.
*/
import $ from 'jquery';
let data_url = (new URL(document.location.toString())).searchParams;
let data_str = data_url.get('data');
// {
// "usernamea": "Joe1991",
// "usernameb": "zh99998",
// "userscorea": 1,
// "userscoreb": 2,
// "expa": 1,
// "expb": 30,
// "expa_ex": 0.5,
// "expb_ex": 29,
// "pta": -2.45677803214143,
// "ptb": 562.760086898395,
// "pta_ex": -1.25048918195558,
// "ptb_ex": 561.553798048209,
// "type": "athletic",
// "start_time": "2017-06-17T12:26:33.000Z",
// "end_time": "2017-06-17T12:26:33.000Z",
// "winner": "zh99998",
// "isfirstwin": false,
// "myname":"zh99998",
// "athletic_win":23,
// "athletic_lose":0,
// "entertain_win":7,
// "entertain_lose":0,
// "exp_rank":"1685",
// "arena_rank":"335",
// "exp_rank_ex":"1685",
// "arena_rank_ex":"335",
// }
let data = JSON.parse(data_str!);
let titleStr;
let icon = 'https://ygobbs.com/user_avatar/ygobbs.com/' + data.myname + '/120/1.png';
let myMame = data.myname;
let winTimes, loseTimes, rank, rank_up, DP, DP_up, DP_up_sum, EXP, EXP_up;
let winOrLose = 0;
let isMyFirstWin;
if (data.type === 'entertain') {
titleStr = '娱乐匹配';
winTimes = data.entertain_win;
loseTimes = data.entertain_lose;
rank = data.exp_rank;
rank_up = data.exp_rank_ex - data.exp_rank ;
} else {
titleStr = '竞技匹配';
winTimes = data.athletic_win;
loseTimes = data.athletic_lose;
rank = data.arena_rank;
rank_up = data.arena_rank_ex - data.arena_rank ;
}
if (data.usernamea === data.myname) {
if ( data.userscorea > data.userscoreb) {
winOrLose = 1;
}else if ( data.userscorea < data.userscoreb) {
winOrLose = -1;
}else {
winOrLose = 0;
}
DP = parseInt(data.pta);
DP_up_sum = Math.floor( data.pta - data.pta_ex );
EXP = parseInt(data.expa);
EXP_up = Math.floor( data.expa - data.expa_ex );
}else {
if ( data.userscorea < data.userscoreb) {
winOrLose = 1;
}else if ( data.userscorea > data.userscoreb) {
winOrLose = -1;
}else {
winOrLose = 0;
}
DP = parseInt(data.ptb);
DP_up_sum = Math.floor( data.ptb - data.ptb_ex );
EXP = parseInt(data.expb);
EXP_up = Math.floor( data.expb - data.expb_ex );
}
isMyFirstWin = (winOrLose > 0 && data.isfirstwin) ? true : false;
DP_up = DP_up_sum - (isMyFirstWin ? 4 : 0);
// =========================================================================
$('#title').html(titleStr);
$('#icon').attr('src', icon);
$('#myName').html(myMame);
$('#' + (winOrLose ? (winOrLose > 0 ? 'win' : 'lose') : 'draw') ).show();
let tr1 = '<tr>' +
'<td>胜:<span class="' + (winOrLose > 0 ? 'green' : '') + '">' + winTimes + '</span></td>' +
'<td>负:<span class="' + (winOrLose < 0 ? 'red' : '') + '">' + loseTimes + '</span></td>' +
'</tr>';
let tr2 = `<tr>
<td>排名:<span id="rank" class="${rank_up ? (rank_up > 0 ? 'green' : 'red') : '' } ">${rank}</span></td>
<td>${data.type === 'entertain' ? 'EXP:' : 'DP:'}
<span id="EXP_DP" ${data.type === 'entertain' ?
('class="' + (EXP_up > 0 ? 'green' : (EXP_up < 0 ? 'red' : '')) + '">' + EXP) :
('class="' + (DP_up_sum > 0 ? 'green' : (DP_up_sum < 0 ? 'red' : '')) + '">' + DP)
}</span>
</td>
</tr>`;
$('#info').append(tr1).append(tr2);
let tr_DP = DP_up ? `
<tr>
<td>D.P</td>
${DP_up > 0 ? `<td class="green">+${ DP_up }</td>` : `<td class="red">${ DP_up }</td>`}
</tr>
` : ``;
let tr_EXP = EXP_up ? `
<tr>
<td>EXP</td>
${EXP_up > 0 ? `<td class="green">+${ EXP_up }</td>` : `<td class="red">${ EXP_up }</td>`}
</tr>
` : ``;
let tr_FirstWin = isMyFirstWin ? `
<tr>
<td>首胜</td>
<td class="green">+4</td>
</tr>
` : ``
let tr_rewards = tr_EXP + tr_DP + tr_FirstWin;
tr_rewards = tr_rewards === '' ? '<tr><td>无</td></tr>' : tr_rewards;
$('#rewards').append(tr_rewards);
function again() {
let {ipcRenderer} = require('electron');
ipcRenderer.send('YGOPro', data.type);
window.opener=null;
window.close();
}
let t = setTimeout(function () {
window.opener = null;
window.close();
}, 5000);
$('html').hover(function () {
clearTimeout(t);
});
/**
* Created by zh99998 on 2017/6/1.
*/
// import Raven from 'raven-js';
// import {ErrorHandler} from '@angular/core';
//
// Raven
// .config('https://2c5fa0d0f13c43b5b96346f4eff2ea60@sentry.io/174769')
// .install();
//
// export class RavenErrorHandler implements ErrorHandler {
// handleError(err: any): void {
// Raven.captureException(err.originalError || err);
// }
// }
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff was suppressed by a .gitattributes entry.
This diff is collapsed.
This diff is collapsed.
......@@ -22,6 +22,8 @@
"dom.iterable"
],
"strictPropertyInitialization": false,
"noImplicitAny": false,
"suppressImplicitAnyIndexErrors": true,
"esModuleInterop": true
},
"angularCompilerOptions": {
......
This diff is collapsed.
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