Commit 179df0f7 authored by 神楽坂玲奈's avatar 神楽坂玲奈

将 Candy 作为 Component 而不是 WebView

tslint
parent 648cf20c
<p>时隔多年,终于又跟</p> <!--<h2>MyCard 招募公告</h2>-->
<h2>招人</h2> <!--<p>MyCard 伴随大家已经有 6 年了,在这 6 年间 MyCard 作为一个同人平台很感谢得到大家的支持,现在 MyCard 为了给支持的大家带来更好的体验,正在努力进行全新的改版的开发工作,希望可以得到大家的支持和帮助。</p>--><!--<p>职位:(前端)开发工程师</p>--><!--<p>负责平台客户端的开发,及网站和论坛相关的改版工作。</p>--><!--<p>职位描述:对 ACG 领域有一定的了解,会js等编程领域的专业技能,对软件开发具有一定的热情和自主能动性,认真严谨和团队意识。</p>--><!--<p>联系邮箱:hr@mycard.moe</p>--><!--<p>工作地点:上海市长宁区(工资面议)</p>-->
<h3>前端工程师</h3>
<p>热爱,圈内优先</p>
<p>HTML5, CSS, JavaScript</p>
<p>至少会一个前端框架 Angular React Vue</p>
<p>坐标上海,全职,不远程,工资面议</p>
<h3>后端工程师</h3> <!--<h2>联系我们</h2>--><!--<dl>--><!--<dt>应聘</dt>--><!--<dd>hr@mycard.com</dd>--><!--<dt>投稿、合作、侵权投诉</dt>--><!--<dd>business@mycard.com</dd>--><!--<dt>问题反馈</dt>--><!--<dd>support@mycard.moe</dd>--><!--</dl>-->
<p>热爱,圈内优先</p>
<p>会 php、, CSS, JavaScript</p>
<p>至少会一个前端框架 Angular React Vue</p>
<p>坐标上海,全职,不远程,工资面议</p>
<h2>联系我们</h2>
<dl>
<dt>游戏投稿、合作、应聘、侵权投诉</dt>
<dd>business@mycard.com</dd>
<dt>问题反馈</dt>
<dd>support@mycard.moe</dd>
</dl>
\ No newline at end of file
/** /**
* Created by zh99998 on 16/9/2. * Created by zh99998 on 16/9/2.
*/ */
import {Component} from "@angular/core"; import {Component} from '@angular/core';
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
selector: 'about', selector: 'about',
......
import {Component, OnInit, Input, ChangeDetectorRef} from "@angular/core"; import {Component, OnInit, Input, ChangeDetectorRef} from '@angular/core';
import {AppsService} from "./apps.service"; import {AppsService} from './apps.service';
import {InstallOption} from "./install-option"; import {InstallOption} from './install-option';
import {SettingsService} from "./settings.sevices"; import {SettingsService} from './settings.sevices';
import {App} from "./app"; import {App} from './app';
import {DownloadService} from "./download.service"; import {DownloadService} from './download.service';
import {clipboard, remote} from "electron"; import {clipboard, remote} from 'electron';
import * as path from "path"; import * as path from 'path';
import * as fs from "fs"; import * as fs from 'fs';
import * as $ from 'jquery';
declare const Notification: any; declare const Notification: any;
declare const $: any;
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
...@@ -37,17 +37,17 @@ export class AppDetailComponent implements OnInit { ...@@ -37,17 +37,17 @@ export class AppDetailComponent implements OnInit {
let volume = 'A'; let volume = 'A';
for (let i = 0; i < 26; i++) { for (let i = 0; i < 26; i++) {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
let currentVolume = String.fromCharCode(volume.charCodeAt(0) + i) + ":"; let currentVolume = String.fromCharCode(volume.charCodeAt(0) + i) + ':';
fs.access(currentVolume, (err) => { fs.access(currentVolume, (err) => {
if (!err) { if (!err) {
//判断是否已经存在Library // 判断是否已经存在Library
if (this.libraries.every((library) => !library.startsWith(currentVolume))) { if (this.libraries.every((library) => !library.startsWith(currentVolume))) {
this.availableLibraries.push(currentVolume); this.availableLibraries.push(currentVolume);
} }
} }
resolve() resolve();
}) });
}) });
} }
} }
...@@ -61,7 +61,8 @@ export class AppDetailComponent implements OnInit { ...@@ -61,7 +61,8 @@ export class AppDetailComponent implements OnInit {
if (reference.isLanguage()) { if (reference.isLanguage()) {
// 对于语言包,只有在语言包的locales比游戏本身的更加合适的时候才默认勾选 // 对于语言包,只有在语言包的locales比游戏本身的更加合适的时候才默认勾选
// 这里先偷个懒,中文环境勾选中文语言包,非中文环境勾选非中文语言包 // 这里先偷个懒,中文环境勾选中文语言包,非中文环境勾选非中文语言包
this.referencesInstall[reference.id] = reference.locales[0].startsWith('zh') == this.settingsService.getLocale().startsWith('zh') this.referencesInstall[reference.id] =
reference.locales[0].startsWith('zh') === this.settingsService.getLocale().startsWith('zh');
} else { } else {
this.referencesInstall[reference.id] = true; this.referencesInstall[reference.id] = true;
} }
...@@ -88,7 +89,7 @@ export class AppDetailComponent implements OnInit { ...@@ -88,7 +89,7 @@ export class AppDetailComponent implements OnInit {
} }
async uninstall(app: App) { async uninstall(app: App) {
if (confirm("确认删除?")) { if (confirm('确认删除?')) {
try { try {
await this.appsService.uninstall(app); await this.appsService.uninstall(app);
} catch (e) { } catch (e) {
...@@ -105,35 +106,35 @@ export class AppDetailComponent implements OnInit { ...@@ -105,35 +106,35 @@ export class AppDetailComponent implements OnInit {
for (let [id, install] of Object.entries(referencesInstall)) { for (let [id, install] of Object.entries(referencesInstall)) {
if (install) { if (install) {
let reference = targetApp.references.get(id)!; let reference = targetApp.references.get(id)!;
console.log("reference install ", id, targetApp, targetApp.references, reference); console.log('reference install ', id, targetApp, targetApp.references, reference);
await this.appsService.install(reference, options); await this.appsService.install(reference, options);
} }
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
new Notification(targetApp.name, {body: "下载失败"}); new Notification(targetApp.name, {body: '下载失败'});
} }
} }
async selectLibrary() { async selectLibrary() {
if (this.installOption.installLibrary.startsWith('create_')) { if (this.installOption.installLibrary.startsWith('create_')) {
let volume = this.installOption.installLibrary.slice(7); let volume = this.installOption.installLibrary.slice(7);
let library = path.join(volume, "MyCardLibrary"); let library = path.join(volume, 'MyCardLibrary');
try { try {
await this.appsService.createDirectory(library); await this.appsService.createDirectory(library);
this.installOption.installLibrary = library; this.installOption.installLibrary = library;
this.settingsService.addLibrary(library, true); this.settingsService.addLibrary(library, true);
} catch (e) { } catch (e) {
this.installOption.installLibrary = this.settingsService.getDefaultLibrary().path; this.installOption.installLibrary = this.settingsService.getDefaultLibrary().path;
alert("无法创建指定目录"); alert('无法创建指定目录');
} finally { } finally {
let index = this.availableLibraries.findIndex((l) => { let index = this.availableLibraries.findIndex((l) => {
return l === volume return l === volume;
}); });
this.availableLibraries.splice(index, 1); this.availableLibraries.splice(index, 1);
} }
} else { } else {
this.settingsService.setDefaultLibrary({path: this.installOption.installLibrary, "default": true}) this.settingsService.setDefaultLibrary({path: this.installOption.installLibrary, 'default': true});
} }
this.installOption.installLibrary = this.settingsService.getDefaultLibrary().path; this.installOption.installLibrary = this.settingsService.getDefaultLibrary().path;
} }
...@@ -162,13 +163,13 @@ export class AppDetailComponent implements OnInit { ...@@ -162,13 +163,13 @@ export class AppDetailComponent implements OnInit {
for (let [id, install] of Object.entries(referencesInstall)) { for (let [id, install] of Object.entries(referencesInstall)) {
if (install) { if (install) {
let reference = targetApp.references.get(id)!; let reference = targetApp.references.get(id)!;
console.log("reference install ", id, targetApp, targetApp.references, reference); console.log('reference install ', id, targetApp, targetApp.references, reference);
await this.appsService.install(reference, option); await this.appsService.install(reference, option);
} }
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
new Notification(targetApp.name, {body: "导入失败"}); new Notification(targetApp.name, {body: '导入失败'});
} }
} }
...@@ -182,7 +183,7 @@ export class AppDetailComponent implements OnInit { ...@@ -182,7 +183,7 @@ export class AppDetailComponent implements OnInit {
await this.appsService.update(mod, true); await this.appsService.update(mod, true);
} }
} catch (e) { } catch (e) {
new Notification(app.name, {body: "校验失败"}); new Notification(app.name, {body: '校验失败'});
console.error(e); console.error(e);
} }
} }
...@@ -194,10 +195,10 @@ export class AppDetailComponent implements OnInit { ...@@ -194,10 +195,10 @@ export class AppDetailComponent implements OnInit {
async selectImport(app: App) { async selectImport(app: App) {
let main = app.actions.get('main'); let main = app.actions.get('main');
if (!main) { if (!main) {
return return;
} }
if (!main.execute) { if (!main.execute) {
return return;
} }
let filename = main.execute.split('/')[0]; let filename = main.execute.split('/')[0];
let extname = path.extname(filename).slice(1); let extname = path.extname(filename).slice(1);
...@@ -206,12 +207,12 @@ export class AppDetailComponent implements OnInit { ...@@ -206,12 +207,12 @@ export class AppDetailComponent implements OnInit {
let filePaths = await new Promise((resolve, reject) => { let filePaths = await new Promise((resolve, reject) => {
remote.dialog.showOpenDialog({ remote.dialog.showOpenDialog({
filters: [{name: filename, extensions: [extname]}], filters: [{name: filename, extensions: [extname]}],
properties: ['openFile',] properties: ['openFile']
}, resolve) }, resolve);
}); });
if (filePaths && filePaths[0]) { if (filePaths && filePaths[0]) {
this.import_path = filePaths[0] this.import_path = filePaths[0];
} }
} }
......
import {App} from "./app"; import {App} from './app';
/** /**
* Created by zh99998 on 16/9/6. * Created by zh99998 on 16/9/6.
*/ */
export class AppLocal { export class AppLocal {
path: string; path: string;
version: string; version: string;
files: Map<string,string>; files: Map<string, string>;
action: Map<string,{execute: string, args: string[], env: {}, open: App}>; action: Map<string, {execute: string, args: string[], env: {}, open: App}>;
update(local: any) { update(local: any) {
this.path = local.path; this.path = local.path;
this.version = local.version; this.version = local.version;
let files = new Map<string,string>(); let files = new Map<string, string>();
for (let filename of Object.keys(local.files)) { for (let filename of Object.keys(local.files)) {
files.set(filename, local.files[filename]); files.set(filename, local.files[filename]);
} }
...@@ -20,7 +20,7 @@ export class AppLocal { ...@@ -20,7 +20,7 @@ export class AppLocal {
toJSON() { toJSON() {
let t: any = {}; let t: any = {};
for (let [k,v] of this.files) { for (let [k, v] of this.files) {
t[k] = v; t[k] = v;
} }
return {path: this.path, version: this.version, files: t}; return {path: this.path, version: this.version, files: t};
......
import {AppLocal} from "./app-local"; import {AppLocal} from './app-local';
export enum Category { export enum Category {
game, game,
...@@ -24,7 +24,7 @@ export interface Action { ...@@ -24,7 +24,7 @@ export interface Action {
execute: string; execute: string;
args: string[]; args: string[];
env: {}; env: {};
open?: App open?: App;
} }
export class FileOptions { export class FileOptions {
sync: boolean; sync: boolean;
...@@ -36,7 +36,7 @@ export class AppStatus { ...@@ -36,7 +36,7 @@ export class AppStatus {
total: number; total: number;
private _status: string; private _status: string;
get status(): string { get status(): string {
return this._status return this._status;
} }
set status(status: string) { set status(status: string) {
...@@ -52,56 +52,56 @@ export class App { ...@@ -52,56 +52,56 @@ export class App {
id: string; id: string;
name: string; // i18n name: string; // i18n
description: string; //i18n description: string; // i18n
author: string; // English Only author: string; // English Only
homepage: string; homepage: string;
category: Category; category: Category;
parent?: App; parent?: App;
static downloadUrl(app: App, platform: string, locale: string): string { actions: Map<string, Action>;
if (app.id === "ygopro") { references: Map<string, App>;
return `https://thief.mycard.moe/metalinks/${app.id}-${process.platform}-${locale}/${app.version}` dependencies: Map<string, App>;
} else if (app.id === "desmume") { locales: string[];
return `https://thief.mycard.moe/metalinks/${app.id}-${process.platform}/${app.version}` news: {title: string, url: string, image: string}[];
network: any;
tags: string[];
version: string;
local: AppLocal | null;
status: AppStatus;
conference: string | undefined;
files: Map<string, FileOptions>;
data: any;
static downloadUrl (app: App, platform: string, locale: string): string {
if (app.id === 'ygopro') {
return `https://thief.mycard.moe/metalinks/${app.id}-${process.platform}-${locale}/${app.version}`;
} else if (app.id === 'desmume') {
return `https://thief.mycard.moe/metalinks/${app.id}-${process.platform}/${app.version}`;
} }
return `https://thief.mycard.moe/metalinks/${app.id}/${app.version}`; return `https://thief.mycard.moe/metalinks/${app.id}/${app.version}`;
} }
static checksumUrl(app: App, platform: string, locale: string): string { static checksumUrl (app: App, platform: string, locale: string): string {
if (app.id === "ygopro") { if (app.id === 'ygopro') {
return `https://thief.mycard.moe/checksums/${app.id}-${platform}-${locale}/${app.version}` return `https://thief.mycard.moe/checksums/${app.id}-${platform}-${locale}/${app.version}`;
} else if (app.id === "desmume") { } else if (app.id === 'desmume') {
return `https://thief.mycard.moe/checksums/${app.id}-${platform}/${app.version}` return `https://thief.mycard.moe/checksums/${app.id}-${platform}/${app.version}`;
} }
return `https://thief.mycard.moe/checksums/${app.id}/${app.version}` return `https://thief.mycard.moe/checksums/${app.id}/${app.version}`;
} }
static updateUrl(app: App, platform: string, locale: string): string { static updateUrl (app: App, platform: string, locale: string): string {
if (app.id === "ygopro") { if (app.id === 'ygopro') {
return `https://thief.mycard.moe/update/${app.id}-${platform}-${locale}/${app.version}`; return `https://thief.mycard.moe/update/${app.id}-${platform}-${locale}/${app.version}`;
} else if (app.id === "desmume") { } else if (app.id === 'desmume') {
return `https://thief.mycard.moe/update/${app.id}-${platform}/${app.version}`; return `https://thief.mycard.moe/update/${app.id}-${platform}/${app.version}`;
} }
return `https://thief.mycard.moe/update/${app.id}/${app.version}`; return `https://thief.mycard.moe/update/${app.id}/${app.version}`;
} }
actions: Map<string,Action>;
references: Map<string,App>;
dependencies: Map<string,App>;
locales: string[];
news: {title: string, url: string, image: string}[];
network: any;
tags: string[];
version: string;
local: AppLocal | null;
status: AppStatus;
conference: string | undefined;
files: Map<string,FileOptions>;
data: any;
isLanguage() { isLanguage() {
return this.category == Category.module && this.tags.includes('language'); return this.category === Category.module && this.tags.includes('language');
} }
reset() { reset() {
...@@ -111,31 +111,31 @@ export class App { ...@@ -111,31 +111,31 @@ export class App {
} }
isInstalled(): boolean { isInstalled(): boolean {
return this.status.status != 'init'; return this.status.status !== 'init';
} }
isReady(): boolean { isReady(): boolean {
return this.status.status == 'ready'; return this.status.status === 'ready';
} }
isInstalling(): boolean { isInstalling(): boolean {
return this.status.status == 'installing'; return this.status.status === 'installing';
} }
isWaiting(): boolean { isWaiting(): boolean {
return this.status.status == 'waiting'; return this.status.status === 'waiting';
} }
isDownloading(): boolean { isDownloading(): boolean {
return this.status.status === "downloading"; return this.status.status === 'downloading';
} }
isUninstalling(): boolean { isUninstalling(): boolean {
return this.status.status === "uninstalling"; return this.status.status === 'uninstalling';
} }
isUpdating(): boolean { isUpdating(): boolean {
return this.status.status === "updating"; return this.status.status === 'updating';
} }
runnable(): boolean { runnable(): boolean {
...@@ -187,4 +187,4 @@ export class App { ...@@ -187,4 +187,4 @@ export class App {
return dependencies.every((dependency) => dependency.isReady()); return dependencies.every((dependency) => dependency.isReady());
} }
} }
\ No newline at end of file
import {Injectable, ApplicationRef, EventEmitter, NgZone} from "@angular/core"; import {Injectable, ApplicationRef, EventEmitter, NgZone} from '@angular/core';
import {Http} from "@angular/http"; import {Http} from '@angular/http';
import * as crypto from "crypto"; import * as crypto from 'crypto';
import {App, AppStatus, Action, FileOptions} from "./app"; import {App, AppStatus, Action} from './app';
import {SettingsService} from "./settings.sevices"; import {SettingsService} from './settings.sevices';
import * as fs from "fs"; import * as fs from 'fs';
import {createReadStream, createWriteStream} from "fs"; import {createReadStream, createWriteStream} from 'fs';
import * as path from "path"; import * as path from 'path';
import * as child_process from "child_process"; import * as child_process from 'child_process';
import {ChildProcess} from "child_process"; import {ChildProcess} from 'child_process';
import {remote} from "electron"; import {remote} from 'electron';
import "rxjs/Rx"; import 'rxjs/Rx';
import * as readline from "readline"; import * as readline from 'readline';
import {AppLocal} from "./app-local"; import {AppLocal} from './app-local';
import * as glob from "glob"; import * as glob from 'glob';
import * as ini from "ini"; import * as ini from 'ini';
import {DownloadService, DownloadStatus} from "./download.service"; 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 Timer = NodeJS.Timer; import Timer = NodeJS.Timer;
import ReadableStream = NodeJS.ReadableStream; import ReadableStream = NodeJS.ReadableStream;
import {platform} from "os";
const Aria2 = require('aria2');
const sudo = require('electron-sudo'); const sudo = require('electron-sudo');
const Logger = { const Logger = {
info: (...message: any[]) => { info: (...message: any[]) => {
console.log("AppService [INFO]: ", ...message); console.log('AppService [INFO]: ', ...message);
}, },
error: (...message: any[]) => { error: (...message: any[]) => {
console.error("AppService [ERROR]: ", ...message); console.error('AppService [ERROR]: ', ...message);
} }
}; };
interface InstallTask { interface InstallTask {
...@@ -42,7 +40,8 @@ interface InstallStatus { ...@@ -42,7 +40,8 @@ interface InstallStatus {
lastItem: string; lastItem: string;
} }
interface Connection { interface Connection {
connection: WebSocket, address: string | null connection: WebSocket;
address: string | null;
} }
declare const System: any; declare const System: any;
...@@ -50,16 +49,23 @@ declare const System: any; ...@@ -50,16 +49,23 @@ declare const System: any;
@Injectable() @Injectable()
export class AppsService { export class AppsService {
private apps: Map<string,App>; private apps: Map<string, App>;
eventEmitter = new EventEmitter<void>();
map: Map<string, string> = new Map();
connections = new Map<App, Connection>();
maotama: Promise<ChildProcess>;
readonly tarPath = process.platform === "win32" ? path.join(process.env['NODE_ENV'] == 'production' ? process.resourcesPath : '', 'bin', 'bsdtar.exe') : 'bsdtar'; readonly tarPath = process.platform === 'win32' ?
path.join(process.env['NODE_ENV'] === 'production' ? process.resourcesPath : '', 'bin', 'bsdtar.exe')
: '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) {
} }
get lastVisited(): App|undefined { get lastVisited(): App|undefined {
let id = localStorage.getItem("last_visited"); let id = localStorage.getItem('last_visited');
if (id) { if (id) {
return this.apps.get(id); return this.apps.get(id);
} }
...@@ -68,7 +74,7 @@ export class AppsService { ...@@ -68,7 +74,7 @@ export class AppsService {
set lastVisited(app: App|undefined) { set lastVisited(app: App|undefined) {
if (app) { if (app) {
localStorage.setItem("last_visited", app.id); localStorage.setItem('last_visited', app.id);
} }
} }
...@@ -76,10 +82,10 @@ export class AppsService { ...@@ -76,10 +82,10 @@ export class AppsService {
let appsURL = 'https://api.mycard.moe/apps.json'; let appsURL = 'https://api.mycard.moe/apps.json';
try { try {
let data = await this.http.get(appsURL).map((response) => response.json()).toPromise(); let data = await this.http.get(appsURL).map((response) => response.json()).toPromise();
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(data);
} else { } else {
...@@ -97,7 +103,7 @@ export class AppsService { ...@@ -97,7 +103,7 @@ export class AppsService {
async bundle() { async bundle() {
try { try {
const bundle = require(path.join(remote.app.getPath('appData'), 'mycard', 'bundle.json')); // const bundle = require(path.join(remote.app.getPath('appData'), 'mycard', 'bundle.json'));
// 示例: // 示例:
// [ // [
// { // {
...@@ -158,7 +164,7 @@ export class AppsService { ...@@ -158,7 +164,7 @@ export class AppsService {
// 导入萌卡 v2 的 YGOPRO // 导入萌卡 v2 的 YGOPRO
let app = this.apps.get('ygopro')!; let app = this.apps.get('ygopro')!;
if (app.isInstalled() || localStorage.getItem('migrate_v2_ygopro')) { if (app.isInstalled() || localStorage.getItem('migrate_v2_ygopro')) {
return return;
} }
try { try {
const legacy_ygopro_path = System._nodeRequire(path.join(remote.app.getPath('appData'), 'mycard', 'db.json')).local.ygopro.path; const legacy_ygopro_path = System._nodeRequire(path.join(remote.app.getPath('appData'), 'mycard', 'db.json')).local.ygopro.path;
...@@ -167,19 +173,19 @@ export class AppsService { ...@@ -167,19 +173,19 @@ export class AppsService {
// 示例: "C:\\Users\\a915329096\\AppData\\Roaming\\mycard\\apps\\ygopro" // 示例: "C:\\Users\\a915329096\\AppData\\Roaming\\mycard\\apps\\ygopro"
// 不带任何reference,如果同盘符已有库,安装到那个库里,否则在那个盘符建个库。 // 不带任何reference,如果同盘符已有库,安装到那个库里,否则在那个盘符建个库。
let library: string | undefined; let library: string | undefined;
if (process.platform == 'win32') { if (process.platform === 'win32') {
let volume = legacy_ygopro_path.split(':')[0].toUpperCase(); let volume = legacy_ygopro_path.split(':')[0].toUpperCase();
for (let _library of this.settingsService.getLibraries()) { for (let _library of this.settingsService.getLibraries()) {
if (_library.path.split(':')[0].toUpperCase() == volume) { if (_library.path.split(':')[0].toUpperCase() === volume) {
library = _library.path library = _library.path;
} }
} }
if (!library) { if (!library) {
try { try {
let _library = path.join(volume + ':', "MyCardLibrary"); let _library = path.join(volume + ':', 'MyCardLibrary');
await this.createDirectory(_library); await this.createDirectory(_library);
this.settingsService.addLibrary(_library, true); this.settingsService.addLibrary(_library, true);
library = _library library = _library;
} catch (error) { } catch (error) {
} }
} }
...@@ -190,7 +196,7 @@ export class AppsService { ...@@ -190,7 +196,7 @@ export class AppsService {
let option = new InstallOption(app, library, false, false); let option = new InstallOption(app, library, false, false);
console.log('migrate ygopro', legacy_ygopro_path, library); console.log('migrate ygopro', legacy_ygopro_path, library);
await this.importApp(app, legacy_ygopro_path, option); await this.importApp(app, legacy_ygopro_path, option);
localStorage.setItem('migrate_v2_ygopro', "true") localStorage.setItem('migrate_v2_ygopro', 'true');
} }
} catch (error) { } catch (error) {
} }
...@@ -199,15 +205,15 @@ export class AppsService { ...@@ -199,15 +205,15 @@ export class AppsService {
async migreate_library() { async migreate_library() {
let libraries = this.settingsService.getLibraries(); let libraries = this.settingsService.getLibraries();
for (let library of libraries) { for (let library of libraries) {
if (library.path == path.join(remote.app.getPath("appData"), "library")) { if (library.path === path.join(remote.app.getPath('appData'), 'library')) {
library.path = path.join(remote.app.getPath("appData"), "MyCardLibrary") library.path = path.join(remote.app.getPath('appData'), 'MyCardLibrary');
} }
} }
localStorage.setItem(SettingsService.SETTING_LIBRARY, JSON.stringify(libraries)); localStorage.setItem(SettingsService.SETTING_LIBRARY, JSON.stringify(libraries));
} }
loadAppsList = (data: any): Map<string,App> => { loadAppsList = (data: any): Map<string, App> => {
let apps = new Map<string,App>(); let apps = new Map<string, App>();
let locale = this.settingsService.getLocale(); let locale = this.settingsService.getLocale();
let platform = process.platform; let platform = process.platform;
...@@ -216,7 +222,7 @@ export class AppsService { ...@@ -216,7 +222,7 @@ export class AppsService {
let local = localStorage.getItem(app.id); let local = localStorage.getItem(app.id);
if (item.files) { if (item.files) {
app.files = new Map(Object.entries(item.files)) app.files = new Map(Object.entries(item.files));
} else { } else {
app.files = new Map(); app.files = new Map();
} }
...@@ -227,9 +233,9 @@ export class AppsService { ...@@ -227,9 +233,9 @@ export class AppsService {
} }
app.status = new AppStatus(); app.status = new AppStatus();
if (local) { if (local) {
app.status.status = "ready"; app.status.status = 'ready';
} else { } else {
app.reset() app.reset();
} }
// 去除无关语言 // 去除无关语言
...@@ -237,7 +243,7 @@ export class AppsService { ...@@ -237,7 +243,7 @@ export class AppsService {
if (app[key]) { if (app[key]) {
let value = app[key][locale]; let value = app[key][locale];
if (!value) { if (!value) {
value = app[key]["zh-CN"]; value = app[key]['zh-CN'];
} }
app[key] = value; app[key] = value;
} }
...@@ -248,8 +254,7 @@ export class AppsService { ...@@ -248,8 +254,7 @@ export class AppsService {
if (app[key]) { if (app[key]) {
if (app[key][platform]) { if (app[key][platform]) {
app[key] = app[key][platform]; app[key] = app[key][platform];
} } else {
else {
app[key] = null; app[key] = null;
} }
} }
...@@ -260,13 +265,13 @@ export class AppsService { ...@@ -260,13 +265,13 @@ export class AppsService {
// 设置App关系 // 设置App关系
for (let [id, app] of apps) { for (let app of apps.values()) {
let temp = app.actions; let temp = app.actions;
let map = new Map<string,any>(); let map = new Map<string, any>();
for (let action of Object.keys(temp)) { for (let action of Object.keys(temp)) {
let openId = temp[action]["open"]; let openId = temp[action]['open'];
if (openId) { if (openId) {
temp[action]["open"] = apps.get(openId); temp[action]['open'] = apps.get(openId);
} }
map.set(action, temp[action]); map.set(action, temp[action]);
} }
...@@ -276,7 +281,7 @@ export class AppsService { ...@@ -276,7 +281,7 @@ export class AppsService {
let value = app[key]; let value = app[key];
if (value) { if (value) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
let map = new Map<string,App>(); let map = new Map<string, App>();
for (let appId of value) { for (let appId of value) {
map.set(appId, apps.get(appId)); map.set(appId, apps.get(appId));
} }
...@@ -304,7 +309,7 @@ export class AppsService { ...@@ -304,7 +309,7 @@ export class AppsService {
} }
}; };
if (!app.name && app.parent && app.isLanguage()) { if (!app.name && app.parent && app.isLanguage()) {
app.name = `${app.parent.name} ${lang[locale].language_pack} (${app.locales.map((l) => lang[locale][l]).join(', ')})` app.name = `${app.parent.name} ${lang[locale].language_pack} (${app.locales.map((l) => lang[locale][l]).join(', ')})`;
} }
} }
return apps; return apps;
...@@ -321,17 +326,17 @@ export class AppsService { ...@@ -321,17 +326,17 @@ export class AppsService {
let readable = createReadStream(src); let readable = createReadStream(src);
readable.on('open', () => { readable.on('open', () => {
let writable = createWriteStream(dst); let writable = createWriteStream(dst);
writable.on("error", reject); writable.on('error', reject);
writable.on("close", resolve); writable.on('close', resolve);
readable.pipe(writable); readable.pipe(writable);
}); });
readable.on("error", reject); readable.on('error', reject);
}); });
} }
async importApp(app: App, appPath: string, option: InstallOption) { async importApp(app: App, appPath: string, option: InstallOption) {
if (!app.isInstalled()) { if (!app.isInstalled()) {
app.status.status = "updating"; app.status.status = 'updating';
let checksumFiles = await this.getChecksumFile(app); let checksumFiles = await this.getChecksumFile(app);
for (let [pattern, fileOption] of app.files) { for (let [pattern, fileOption] of app.files) {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
...@@ -339,12 +344,12 @@ export class AppsService { ...@@ -339,12 +344,12 @@ export class AppsService {
for (let file of files) { for (let file of files) {
// 避免被当做文件夹 // 避免被当做文件夹
if (fileOption.sync) { if (fileOption.sync) {
checksumFiles.set(file, "DO_NOT_CARE_HASH"); checksumFiles.set(file, 'DO_NOT_CARE_HASH');
} }
} }
resolve(); resolve();
}); });
}) });
} }
await this.createDirectory(option.installDir); await this.createDirectory(option.installDir);
let sortedFiles = Array.from(checksumFiles.entries()).sort((a: string[], b: string[]): number => { let sortedFiles = Array.from(checksumFiles.entries()).sort((a: string[], b: string[]): number => {
...@@ -366,7 +371,7 @@ export class AppsService { ...@@ -366,7 +371,7 @@ export class AppsService {
for (let [file, checksum] of sortedFiles) { for (let [file, checksum] of sortedFiles) {
let src = path.join(appPath, file); let src = path.join(appPath, file);
let dst = path.join(option.installDir, file); let dst = path.join(option.installDir, file);
if (checksum === "") { if (checksum === '') {
await this.createDirectory(dst); await this.createDirectory(dst);
} else { } else {
try { try {
...@@ -387,7 +392,7 @@ export class AppsService { ...@@ -387,7 +392,7 @@ export class AppsService {
clearInterval(interval); clearInterval(interval);
app.local = new AppLocal(); app.local = new AppLocal();
app.local.path = option.installDir; app.local.path = option.installDir;
app.status.status = "ready"; app.status.status = 'ready';
await this.update(app, true); await this.update(app, true);
this.saveAppLocal(app); this.saveAppLocal(app);
} }
...@@ -396,25 +401,25 @@ export class AppsService { ...@@ -396,25 +401,25 @@ export class AppsService {
sha256sum(file: string): Promise<string> { sha256sum(file: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let input = fs.createReadStream(file); let input = fs.createReadStream(file);
const hash = crypto.createHash("sha256"); const hash = crypto.createHash('sha256');
hash.on("error", (error: Error) => { hash.on('error', (error: Error) => {
reject(error) reject(error);
}); });
input.on("error", (error: Error) => { input.on('error', (error: Error) => {
reject(error); reject(error);
}); });
hash.on('readable', () => { hash.on('readable', () => {
let data = <Buffer>hash.read(); let data = <Buffer>hash.read();
if (data) { if (data) {
resolve(data.toString("hex")); resolve(data.toString('hex'));
} }
}); });
input.pipe(hash); input.pipe(hash);
}); });
} }
async verifyFiles(app: App, checksumFiles: Map<string,string>, callback: () => void): Promise<Map<string,string>> { async verifyFiles (app: App, checksumFiles: Map<string, string>, callback: () => void): Promise<Map<string, string>> {
let result = new Map<string,string>(); let result = new Map<string, string>();
for (let [file, checksum] of checksumFiles) { for (let [file, checksum] of checksumFiles) {
let filePath = path.join(app.local!.path, file); let filePath = path.join(app.local!.path, file);
// 如果文件不存在,随便生成一个checksum // 如果文件不存在,随便生成一个checksum
...@@ -422,8 +427,8 @@ export class AppsService { ...@@ -422,8 +427,8 @@ export class AppsService {
fs.access(filePath, fs.constants.F_OK, async(err: Error) => { fs.access(filePath, fs.constants.F_OK, async(err: Error) => {
if (err) { if (err) {
result.set(file, Math.random().toString()); result.set(file, Math.random().toString());
} else if (checksum === "") { } else if (checksum === '') {
result.set(file, ""); result.set(file, '');
} else { } else {
let sha256sum = await this.sha256sum(filePath); let sha256sum = await this.sha256sum(filePath);
result.set(file, sha256sum); result.set(file, sha256sum);
...@@ -437,7 +442,7 @@ export class AppsService { ...@@ -437,7 +442,7 @@ export class AppsService {
} }
async update(app: App, verify = false) { async update(app: App, verify = false) {
let readyToUpdate: boolean = false; let readyToUpdate = false;
// 已经安装的mod // 已经安装的mod
let mods = this.findChildren(app).filter((mod) => { let mods = this.findChildren(app).filter((mod) => {
return mod.parent === app && mod.isInstalled(); return mod.parent === app && mod.isInstalled();
...@@ -450,13 +455,13 @@ export class AppsService { ...@@ -450,13 +455,13 @@ export class AppsService {
readyToUpdate = app.isReady() && mods.every((mod) => mod.isReady()); readyToUpdate = app.isReady() && mods.every((mod) => mod.isReady());
} }
if (readyToUpdate && (verify || app.local!.version !== app.version )) { if (readyToUpdate && (verify || app.local!.version !== app.version )) {
app.status.status = "updating"; app.status.status = 'updating';
try { try {
Logger.info("Checking updating: ", app); Logger.info('Checking updating: ', app);
let latestFiles = await this.getChecksumFile(app); let latestFiles = await this.getChecksumFile(app);
let localFiles: Map<string,string>|undefined; let localFiles: Map<string, string>|undefined;
if (verify) { if (verify) {
//刷新进度条 // 刷新进度条
let interval = setInterval(() => { let interval = setInterval(() => {
}, 500); }, 500);
app.status.total = latestFiles.size; app.status.total = latestFiles.size;
...@@ -481,11 +486,11 @@ export class AppsService { ...@@ -481,11 +486,11 @@ export class AppsService {
let deletedFiles: Set<string> = new Set<string>(); let deletedFiles: Set<string> = new Set<string>();
// 遍历寻找新增加的文件 // 遍历寻找新增加的文件
for (let [file, checksum] of latestFiles) { for (let [file, checksum] of latestFiles) {
if (checksum !== "" && !localFiles!.has(file)) { if (checksum !== '' && !localFiles!.has(file)) {
addedFiles.add(file); addedFiles.add(file);
// changedFiles包含addedFiles,addedFiles仅供mod更新的时候使用。 // changedFiles包含addedFiles,addedFiles仅供mod更新的时候使用。
changedFiles.add(file); changedFiles.add(file);
} else if (checksum === "" && file != ".") { } else if (checksum === '' && file !== '.') {
await this.createDirectory(path.join(app.local!.path, file)); await this.createDirectory(path.join(app.local!.path, file));
} }
} }
...@@ -508,9 +513,9 @@ export class AppsService { ...@@ -508,9 +513,9 @@ export class AppsService {
for (let [file, checksum] of localFiles!) { for (let [file, checksum] of localFiles!) {
if (latestFiles.has(file)) { if (latestFiles.has(file)) {
let latestChecksum = latestFiles.get(file); let latestChecksum = latestFiles.get(file);
if (!ignoreFiles.has(file) && latestChecksum !== checksum && latestChecksum !== "") { if (!ignoreFiles.has(file) && latestChecksum !== checksum && latestChecksum !== '') {
changedFiles.add(file); changedFiles.add(file);
} else if (latestChecksum === "") { } else if (latestChecksum === '') {
await this.createDirectory(path.join(app.local!.path, file)); await this.createDirectory(path.join(app.local!.path, file));
} }
} else { } else {
...@@ -525,16 +530,16 @@ export class AppsService { ...@@ -525,16 +530,16 @@ export class AppsService {
// 新增加的文件和parent冲突,且不是目录,就添加backup到 // 新增加的文件和parent冲突,且不是目录,就添加backup到
// 改变的文件不做备份 // 改变的文件不做备份
for (let addedFile of addedFiles) { for (let addedFile of addedFiles) {
if (parentFiles.has(addedFile) && parentFiles.get(addedFile) !== "") { if (parentFiles.has(addedFile) && parentFiles.get(addedFile) !== '') {
backupFiles.push(addedFile); backupFiles.push(addedFile);
} }
} }
//如果要删除的文件parent里也有就恢复这个文件 // 如果要删除的文件parent里也有就恢复这个文件
for (let deletedFile of deletedFiles) { for (let deletedFile of deletedFiles) {
restoreFiles.push(deletedFile); restoreFiles.push(deletedFile);
} }
let backupDir = path.join(path.dirname(app.local!.path), "backup", app.parent.id); let backupDir = path.join(path.dirname(app.local!.path), 'backup', app.parent.id);
await this.backupFiles(app.local!.path, backupDir, backupFiles); await this.backupFiles(app.local!.path, backupDir, backupFiles);
await this.restoreFiles(app.local!.path, backupDir, restoreFiles); await this.restoreFiles(app.local!.path, backupDir, restoreFiles);
} else { } else {
...@@ -561,34 +566,34 @@ export class AppsService { ...@@ -561,34 +566,34 @@ export class AppsService {
backupToDelete.push(deletedFile); backupToDelete.push(deletedFile);
} }
} }
let backupDir = path.join(path.dirname(app.local!.path), "mods_backup", app.id); let backupDir = path.join(path.dirname(app.local!.path), 'mods_backup', app.id);
await this.backupFiles(app.local!.path, backupDir, backupFiles); await this.backupFiles(app.local!.path, backupDir, backupFiles);
for (let file of backupToDelete) { for (let file of backupToDelete) {
await this.deleteFile(path.join(app.local!.path, file)) await this.deleteFile(path.join(app.local!.path, file));
} }
} }
} }
await this.doUpdate(app, changedFiles, deletedFiles); await this.doUpdate(app, changedFiles, deletedFiles);
Logger.info("Update extract finished"); Logger.info('Update extract finished');
//如果不是mod,就先把自己目录里最新的冲突文件backup到backup目录 // 如果不是mod,就先把自己目录里最新的冲突文件backup到backup目录
//再把mods_backup里面的文件恢复到游戏目录 // 再把mods_backup里面的文件恢复到游戏目录
if (!app.parent) { if (!app.parent) {
Logger.info("Start to restore files..."); Logger.info('Start to restore files...');
let modsBackupDir = path.join(path.dirname(app.local!.path), "mods_backup", app.id); let modsBackupDir = path.join(path.dirname(app.local!.path), 'mods_backup', app.id);
let appBackupDir = path.join(path.dirname(app.local!.path), "backup", app.id); let appBackupDir = path.join(path.dirname(app.local!.path), 'backup', app.id);
await this.backupFiles(app.local!.path, appBackupDir, backupFiles); await this.backupFiles(app.local!.path, appBackupDir, backupFiles);
await this.restoreFiles(app.local!.path, modsBackupDir, backupFiles); await this.restoreFiles(app.local!.path, modsBackupDir, backupFiles);
} }
app.local!.version = app.version; app.local!.version = app.version;
app.local!.files = latestFiles; app.local!.files = latestFiles;
this.saveAppLocal(app); this.saveAppLocal(app);
app.status.status = "ready"; app.status.status = 'ready';
Logger.info("Update Finished: ", app); Logger.info('Update Finished: ', app);
} catch (e) { } catch (e) {
Logger.error("Update Failed: ", e); Logger.error('Update Failed: ', e);
// 如果导入失败,根据是否安装重置status // 如果导入失败,根据是否安装重置status
if (app.local!.files) { if (app.local!.files) {
app.status.status = "ready"; app.status.status = 'ready';
} else { } else {
app.reset(); app.reset();
} }
...@@ -599,14 +604,14 @@ export class AppsService { ...@@ -599,14 +604,14 @@ export class AppsService {
async doUpdate(app: App, changedFiles?: Set<string>, deletedFiles?: Set<string>) { async doUpdate(app: App, changedFiles?: Set<string>, deletedFiles?: Set<string>) {
if (changedFiles && changedFiles.size > 0) { if (changedFiles && changedFiles.size > 0) {
Logger.info("Update changed files: ", changedFiles); Logger.info('Update changed files: ', changedFiles);
let locale = this.settingsService.getLocale(); let locale = this.settingsService.getLocale();
if (!['zh-CN', 'en-US', 'ja-JP'].includes(locale)) { if (!['zh-CN', 'en-US', 'ja-JP'].includes(locale)) {
locale = 'en-US'; locale = 'en-US';
} }
let updateUrl = App.updateUrl(app, process.platform, locale); let updateUrl = App.updateUrl(app, process.platform, locale);
let metalink = await this.http.post(updateUrl, changedFiles).map((response) => response.text()).toPromise(); let metalink = await this.http.post(updateUrl, changedFiles).map((response) => response.text()).toPromise();
let downloadDir = path.join(path.dirname(app.local!.path), "downloading"); let downloadDir = path.join(path.dirname(app.local!.path), 'downloading');
let downloadId = await this.downloadService.addMetalink(metalink, downloadDir); let downloadId = await this.downloadService.addMetalink(metalink, downloadDir);
await this.downloadService.progress(downloadId, (status: DownloadStatus) => { await this.downloadService.progress(downloadId, (status: DownloadStatus) => {
app.status.progress = status.completedLength; app.status.progress = status.completedLength;
...@@ -633,7 +638,7 @@ export class AppsService { ...@@ -633,7 +638,7 @@ export class AppsService {
clearInterval(interval); clearInterval(interval);
} }
if (deletedFiles && deletedFiles.size > 0) { if (deletedFiles && deletedFiles.size > 0) {
Logger.info("Found files deleted: ", deletedFiles); Logger.info('Found files deleted: ', deletedFiles);
for (let deletedFile of deletedFiles) { for (let deletedFile of deletedFiles) {
await this.deleteFile(path.join(app.local!.path, deletedFile)); await this.deleteFile(path.join(app.local!.path, deletedFile));
} }
...@@ -650,35 +655,35 @@ export class AppsService { ...@@ -650,35 +655,35 @@ export class AppsService {
if (task.app.readyForInstall()) { if (task.app.readyForInstall()) {
resolve(); resolve();
} else if (task.app.findDependencies().find((dependency: App) => !dependency.isInstalled())) { } else if (task.app.findDependencies().find((dependency: App) => !dependency.isInstalled())) {
reject("Dependencies failed"); reject('Dependencies failed');
} }
}); });
}); });
} }
await this.doInstall(task); await this.doInstall(task);
}; };
const addDownloadTask = async(app: App, dir: string): Promise<{app: App, files: string[]} > => { const addDownloadTask = async (_app: App, dir: string): Promise<{app: App, files: string[]} > => {
let locale = this.settingsService.getLocale(); let locale = this.settingsService.getLocale();
if (!['zh-CN', 'en-US', 'ja-JP'].includes(locale)) { if (!['zh-CN', 'en-US', 'ja-JP'].includes(locale)) {
locale = 'en-US'; locale = 'en-US';
} }
let metalinkUrl = App.downloadUrl(app, process.platform, locale); let metalinkUrl = App.downloadUrl(_app, process.platform, locale);
app.status.status = "downloading"; _app.status.status = 'downloading';
let metalink = await this.http.get(metalinkUrl).map((response) => response.text()).toPromise(); let metalink = await this.http.get(metalinkUrl).map((response) => response.text()).toPromise();
let downloadId = await this.downloadService.addMetalink(metalink, dir); let downloadId = await this.downloadService.addMetalink(metalink, dir);
try { try {
await this.downloadService.progress(downloadId, (status: DownloadStatus) => { await this.downloadService.progress(downloadId, (status: DownloadStatus) => {
app.status.progress = status.completedLength; _app.status.progress = status.completedLength;
app.status.total = status.totalLength; _app.status.total = status.totalLength;
app.status.progressMessage = status.downloadSpeedText; _app.status.progressMessage = status.downloadSpeedText;
this.ref.tick(); this.ref.tick();
}); });
} catch (e) { } catch (e) {
throw e; throw e;
} }
let files = await this.downloadService.getFiles(downloadId); let files = await this.downloadService.getFiles(downloadId);
app.status.status = "waiting"; _app.status.status = 'waiting';
return {app: app, files: files} return {app: _app, files: files};
}; };
if (!app.isInstalled()) { if (!app.isInstalled()) {
let apps: App[] = []; let apps: App[] = [];
...@@ -689,12 +694,12 @@ export class AppsService { ...@@ -689,12 +694,12 @@ export class AppsService {
try { try {
let downloadPath = path.join(option.installLibrary, 'downloading'); let downloadPath = path.join(option.installLibrary, 'downloading');
let tasks: Promise<any>[] = []; let tasks: Promise<any>[] = [];
Logger.info("Start to Download", apps); Logger.info('Start to Download', apps);
for (let a of apps) { for (let a of apps) {
tasks.push(addDownloadTask(a, downloadPath)); tasks.push(addDownloadTask(a, downloadPath));
} }
let downloadResults = await Promise.all(tasks); let downloadResults = await Promise.all(tasks);
Logger.info("Download Complete", downloadResults); Logger.info('Download Complete', downloadResults);
let installTasks: Promise<void>[] = []; let installTasks: Promise<void>[] = [];
for (let result of downloadResults) { for (let result of downloadResults) {
let o = new InstallOption(result.app, option.installLibrary); let o = new InstallOption(result.app, option.installLibrary);
...@@ -707,7 +712,7 @@ export class AppsService { ...@@ -707,7 +712,7 @@ export class AppsService {
} catch (e) { } catch (e) {
for (let a of apps) { for (let a of apps) {
if (!a.isReady()) { if (!a.isReady()) {
a.reset() a.reset();
} }
} }
throw e; throw e;
...@@ -717,7 +722,7 @@ export class AppsService { ...@@ -717,7 +722,7 @@ export class AppsService {
findChildren(app: App): App[] { findChildren(app: App): App[] {
let children: App[] = []; let children: App[] = [];
for (let [id, child] of this.apps) { for (let child of this.apps.values()) {
if (child.parent === app || child.dependencies && child.dependencies.has(app.id)) { if (child.parent === app || child.dependencies && child.dependencies.has(app.id)) {
children.push(child); children.push(child);
} }
...@@ -735,18 +740,20 @@ export class AppsService { ...@@ -735,18 +740,20 @@ export class AppsService {
if (child.isInstalled()) { if (child.isInstalled()) {
let _action = child.actions.get(action_name); let _action = child.actions.get(action_name);
if (_action) { if (_action) {
action = _action action = _action;
} }
} }
} }
let execute = path.join(cwd, action.execute); let execute = path.join(cwd, action.execute);
if (app.id == 'th123') { if (app.id === 'th123') {
let th105 = <App>app.references.get('th105'); let th105 = <App>app.references.get('th105');
if (th105.isInstalled()) { if (th105.isInstalled()) {
const config_file = path.join((<AppLocal>app.local).path, 'configex123.ini'); const config_file = path.join((<AppLocal>app.local).path, 'configex123.ini');
let config = await new Promise((resolve, reject) => { let config = await new Promise((resolve, reject) => {
fs.readFile(config_file, {encoding: 'utf-8'}, (error, data) => { fs.readFile(config_file, {encoding: 'utf-8'}, (error, data) => {
if (error) return reject(error); if (error) {
return reject(error);
}
resolve(ini.parse(data)); resolve(ini.parse(data));
}); });
}); });
...@@ -754,11 +761,11 @@ export class AppsService { ...@@ -754,11 +761,11 @@ export class AppsService {
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
fs.writeFile(config_file, ini.stringify(config), (error) => { fs.writeFile(config_file, ini.stringify(config), (error) => {
if (error) { if (error) {
reject(error) reject(error);
} else { } else {
resolve() resolve();
} }
}) });
}); });
} }
} }
...@@ -768,11 +775,13 @@ export class AppsService { ...@@ -768,11 +775,13 @@ export class AppsService {
let openAction: Action; let openAction: Action;
openAction = np2.actions.get('main')!; openAction = np2.actions.get('main')!;
let openPath = np2.local!.path; let openPath = np2.local!.path;
if (action.open.id == 'np2fmgen') { if (action.open.id === 'np2fmgen') {
const config_file = path.join(action.open!.local!.path, 'np21nt.ini'); const config_file = path.join(action.open!.local!.path, 'np21nt.ini');
let config = await new Promise((resolve, reject) => { let config = await new Promise((resolve, reject) => {
fs.readFile(config_file, {encoding: 'utf-8'}, (error, data) => { fs.readFile(config_file, {encoding: 'utf-8'}, (error, data) => {
if (error) return reject(error); if (error) {
return reject(error);
}
resolve(ini.parse(data)); resolve(ini.parse(data));
}); });
}); });
...@@ -785,19 +794,21 @@ export class AppsService { ...@@ -785,19 +794,21 @@ export class AppsService {
windtype: '0' windtype: '0'
}; };
config['NekoProject21'] = Object.assign({}, default_config, config['NekoProject21']); config['NekoProject21'] = Object.assign({}, default_config, config['NekoProject21']);
config['NekoProject21']['HDD1FILE'] = path.win32.join(process.platform == 'win32' ? '' : 'Z:', app.local!.path, action.execute); config['NekoProject21']['HDD1FILE'] =
config['NekoProject21']['fontfile'] = path.win32.join(process.platform == 'win32' ? '' : 'Z:', app.local!.path, 'font.bmp'); path.win32.join(process.platform === 'win32' ? '' : 'Z:', app.local!.path, action.execute);
config['NekoProject21']['fontfile'] =
path.win32.join(process.platform === 'win32' ? '' : 'Z:', app.local!.path, 'font.bmp');
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
fs.writeFile(config_file, ini.stringify(config), (error) => { fs.writeFile(config_file, ini.stringify(config), (error) => {
if (error) { if (error) {
reject(error) reject(error);
} else { } else {
resolve() resolve();
} }
}) });
}); });
if (process.platform != 'win32') { if (process.platform !== 'win32') {
args.push(openAction.execute); args.push(openAction.execute);
args = args.concat(openAction.args); args = args.concat(openAction.args);
let wine = openAction.open!; let wine = openAction.open!;
...@@ -834,13 +845,10 @@ export class AppsService { ...@@ -834,13 +845,10 @@ export class AppsService {
browse(app: App) { browse(app: App) {
if (app.local) { if (app.local) {
remote.shell.showItemInFolder(app.local.path + "/."); remote.shell.showItemInFolder(app.local.path + '/.');
} }
} }
connections = new Map<App, Connection>();
maotama: Promise<ChildProcess>;
async network(app: App, server: any) { async network(app: App, server: any) {
if (!this.maotama) { if (!this.maotama) {
this.maotama = new Promise((resolve, reject) => { this.maotama = new Promise((resolve, reject) => {
...@@ -848,14 +856,14 @@ export class AppsService { ...@@ -848,14 +856,14 @@ export class AppsService {
child.once('message', () => resolve(child)); child.once('message', () => resolve(child));
child.once('error', reject); child.once('error', reject);
child.once('exit', reject); child.once('exit', reject);
}) });
} }
let child: ChildProcess; let child: ChildProcess;
try { try {
child = await this.maotama; child = await this.maotama;
} catch (error) { } catch (error) {
alert(`出错了 ${error}`); alert(`出错了 ${error}`);
return return;
} }
let connection = this.connections.get(app); let connection = this.connections.get(app);
...@@ -879,7 +887,7 @@ export class AppsService { ...@@ -879,7 +887,7 @@ export class AppsService {
child.send({ child.send({
action: 'connect', action: 'connect',
arguments: [app.network.port, port, address] arguments: [app.network.port, port, address]
}) });
}, 200); }, 200);
break; break;
case 'CONNECTED': case 'CONNECTED':
...@@ -895,9 +903,9 @@ export class AppsService { ...@@ -895,9 +903,9 @@ export class AppsService {
clearInterval(id); clearInterval(id);
} }
// 如果还是在界面上显示的那个连接 // 如果还是在界面上显示的那个连接
if (this.connections.get(app) == connection) { if (this.connections.get(app) === connection) {
this.connections.delete(app); this.connections.delete(app);
if (event.code != 1000 && !connection!.address) { if (event.code !== 1000 && !connection!.address) {
alert(`出错了 ${event.code}`); alert(`出错了 ${event.code}`);
} }
} }
...@@ -908,11 +916,9 @@ export class AppsService { ...@@ -908,11 +916,9 @@ export class AppsService {
// tarPath: string; // tarPath: string;
// installingId: string = ''; // installingId: string = '';
eventEmitter = new EventEmitter<void>();
// installQueue: Map<string,InstallTask> = new Map();
map: Map<string,string> = new Map(); // installQueue: Map<string,InstallTask> = new Map();
// 调用前提:应用的依赖均已 Ready,应用处于下载完待安装的状态(waiting)。 // 调用前提:应用的依赖均已 Ready,应用处于下载完待安装的状态(waiting)。
...@@ -921,13 +927,13 @@ export class AppsService { ...@@ -921,13 +927,13 @@ export class AppsService {
let app = task.app; let app = task.app;
if (!app.isWaiting()) { if (!app.isWaiting()) {
console.error('doUninstall', "应用不处于等待安装状态", app); console.error('doUninstall', '应用不处于等待安装状态', app);
throw("应用不处于等待安装状态"); throw('应用不处于等待安装状态');
} }
if (!app.readyForInstall()) { if (!app.readyForInstall()) {
console.error('doInstall', "应用依赖不齐备", app); console.error('doInstall', '应用依赖不齐备', app);
throw("应用依赖不齐备"); throw('应用依赖不齐备');
} }
try { try {
...@@ -935,7 +941,7 @@ export class AppsService { ...@@ -935,7 +941,7 @@ export class AppsService {
let installDir = option.installDir; let installDir = option.installDir;
let checksumFile = await this.getChecksumFile(app); let checksumFile = await this.getChecksumFile(app);
let allFiles = new Set(checksumFile.keys()); let allFiles = new Set(checksumFile.keys());
app.status.status = "installing"; app.status.status = 'installing';
app.status.total = allFiles.size; app.status.total = allFiles.size;
app.status.progress = 0; app.status.progress = 0;
let interval = setInterval(() => { let interval = setInterval(() => {
...@@ -948,7 +954,7 @@ export class AppsService { ...@@ -948,7 +954,7 @@ export class AppsService {
let conflictFiles = appFiles.intersection(parentFiles); let conflictFiles = appFiles.intersection(parentFiles);
app.status.total += conflictFiles.size; app.status.total += conflictFiles.size;
if (conflictFiles.size > 0) { if (conflictFiles.size > 0) {
let backupPath = path.join(option.installLibrary, "backup", app.parent.id); let backupPath = path.join(option.installLibrary, 'backup', app.parent.id);
// 文件夹不需要备份,删除 // 文件夹不需要备份,删除
for (let conflictFile of conflictFiles) { for (let conflictFile of conflictFiles) {
if (checksumFile.get(conflictFile) === '') { if (checksumFile.get(conflictFile) === '') {
...@@ -988,16 +994,16 @@ export class AppsService { ...@@ -988,16 +994,16 @@ export class AppsService {
} }
clearInterval(interval); clearInterval(interval);
await this.postInstall(app, installDir); await this.postInstall(app, installDir);
console.log("post install success"); console.log('post install success');
let local = new AppLocal(); let local = new AppLocal();
local.path = installDir; local.path = installDir;
local.files = checksumFile; local.files = checksumFile;
local.version = app.version; local.version = app.version;
app.local = local; app.local = local;
this.saveAppLocal(app); this.saveAppLocal(app);
app.status.status = "ready"; app.status.status = 'ready';
} catch (e) { } catch (e) {
console.log("exception in doInstall", e); console.log('exception in doInstall', e);
throw e; throw e;
} }
finally { finally {
...@@ -1014,42 +1020,42 @@ export class AppsService { ...@@ -1014,42 +1020,42 @@ export class AppsService {
let stats: fs.Stats; let stats: fs.Stats;
try { try {
stats = await new Promise<fs.Stats>((resolve, reject) => { stats = await new Promise<fs.Stats>((resolve, reject) => {
fs.stat(dir, (error, stats) => { fs.stat(dir, (error, result) => {
if (error) { if (error) {
reject(error) reject(error);
} else { } else {
resolve(stats) resolve(result);
} }
}) });
}); });
} catch (error) { // 路径不存在,先尝试递归上级目录,再创建自己 } catch (error) { // 路径不存在,先尝试递归上级目录,再创建自己
await this.createDirectory(path.dirname(dir)); await this.createDirectory(path.dirname(dir));
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>((resolve, reject) => {
fs.mkdir(dir, (error) => { fs.mkdir(dir, (error) => {
if (error) { if (error) {
reject(error) reject(error);
} else { } else {
resolve(true); resolve(true);
} }
}) });
}) });
} }
if (stats.isDirectory()) { // 路径存在并且已经是目录,成功返回 if (stats.isDirectory()) { // 路径存在并且已经是目录,成功返回
return false; return false;
} else { // 路径存在并且不是目录,失败。 } else { // 路径存在并且不是目录,失败。
throw `#{dir} exists and is not a directory` throw `#{dir} exists and is not a directory`;
} }
} }
extract(file: string, dir: string): Observable<string> { extract(file: string, dir: string): Observable<string> {
return Observable.create((observer: Observer<string>) => { return Observable.create((observer: Observer<string>) => {
Logger.info("Start to extract... Command Line: " + this.tarPath, file, dir); Logger.info('Start to extract... Command Line: ' + this.tarPath, file, dir);
let tarProcess = child_process.spawn(this.tarPath, ['xvf', file, '-C', dir]); let tarProcess = child_process.spawn(this.tarPath, ['xvf', file, '-C', dir]);
let rl = readline.createInterface({ let rl = readline.createInterface({
input: <ReadableStream>tarProcess.stderr, input: <ReadableStream>tarProcess.stderr,
}); });
rl.on('line', (input: string) => { rl.on('line', (input: string) => {
observer.next(input.split(" ", 2)[1]); observer.next(input.split(' ', 2)[1]);
}); });
tarProcess.on('exit', (code) => { tarProcess.on('exit', (code) => {
if (code === 0) { if (code === 0) {
...@@ -1059,8 +1065,8 @@ export class AppsService { ...@@ -1059,8 +1065,8 @@ export class AppsService {
} }
}); });
return () => { return () => {
} };
}) });
} }
// TODO: 与runApp合并,通用处理所有Action。 // TODO: 与runApp合并,通用处理所有Action。
...@@ -1074,7 +1080,7 @@ export class AppsService { ...@@ -1074,7 +1080,7 @@ export class AppsService {
command.push(...action.args); command.push(...action.args);
let open = action.open; let open = action.open;
if (open) { if (open) {
let openAction: any = open.actions.get("main"); let openAction: any = open.actions.get('main');
env = Object.assign(env, openAction.env); env = Object.assign(env, openAction.env);
command.unshift(...openAction.args); command.unshift(...openAction.args);
command.unshift(openAction.execute); command.unshift(openAction.execute);
...@@ -1095,8 +1101,8 @@ export class AppsService { ...@@ -1095,8 +1101,8 @@ export class AppsService {
} else { } else {
reject(code); reject(code);
} }
}) });
}) });
} }
} }
...@@ -1117,7 +1123,7 @@ export class AppsService { ...@@ -1117,7 +1123,7 @@ export class AppsService {
fs.rename(srcPath, backupPath, resolve); fs.rename(srcPath, backupPath, resolve);
}); });
if (callback) { if (callback) {
callback(n) callback(n);
} }
n += 1; n += 1;
}); });
...@@ -1141,7 +1147,7 @@ export class AppsService { ...@@ -1141,7 +1147,7 @@ export class AppsService {
} }
} }
async getChecksumFile(app: App): Promise<Map<string,string> > { async getChecksumFile (app: App): Promise<Map<string, string> > {
let locale = this.settingsService.getLocale(); let locale = this.settingsService.getLocale();
if (!['zh-CN', 'en-US', 'ja-JP'].includes(locale)) { if (!['zh-CN', 'en-US', 'ja-JP'].includes(locale)) {
...@@ -1151,12 +1157,12 @@ export class AppsService { ...@@ -1151,12 +1157,12 @@ export class AppsService {
let checksumUrl = App.checksumUrl(app, process.platform, locale); let checksumUrl = App.checksumUrl(app, process.platform, locale);
return this.http.get(checksumUrl) return this.http.get(checksumUrl)
.map((response) => { .map((response) => {
let map = new Map<string,string>(); let map = new Map<string, string>();
for (let line of response.text().split('\n')) { for (let line of response.text().split('\n')) {
if (line !== "") { if (line !== '') {
let [checksum, filename]=line.split(' ', 2); let [checksum, filename] = line.split(' ', 2);
// checksum文件里没有文件夹,这里添加上 // checksum文件里没有文件夹,这里添加上
map.set(path.dirname(filename), ""); map.set(path.dirname(filename), '');
map.set(filename, checksum); map.set(filename, checksum);
} }
} }
...@@ -1168,27 +1174,29 @@ export class AppsService { ...@@ -1168,27 +1174,29 @@ export class AppsService {
deleteFile(file: string): Promise<string> { deleteFile(file: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.lstat(file, (err, stats) => { fs.lstat(file, (err, stats) => {
if (err) return resolve(path); if (err) {
return resolve(path);
}
if (stats.isDirectory()) { if (stats.isDirectory()) {
fs.rmdir(file, (err) => { fs.rmdir(file, (error) => {
resolve(file); resolve(file);
}); });
} else { } else {
fs.unlink(file, (err) => { fs.unlink(file, (error) => {
resolve(file); resolve(file);
}); });
} }
}); });
}) });
} }
async uninstall(app: App) { async uninstall(app: App) {
let children = this.findChildren(app); let children = this.findChildren(app);
let hasInstalledChild = children.find((child) => { let hasInstalledChild = children.find((child) => {
return child.isInstalled() && child.parent != app; return child.isInstalled() && child.parent !== app;
}); });
if (hasInstalledChild) { if (hasInstalledChild) {
throw "无法卸载,还有依赖此程序的游戏。" throw '无法卸载,还有依赖此程序的游戏。';
} else if (app.isReady()) { } else if (app.isReady()) {
for (let child of children) { for (let child of children) {
if (child.parent === app && child.isReady()) { if (child.parent === app && child.isReady()) {
...@@ -1202,14 +1210,14 @@ export class AppsService { ...@@ -1202,14 +1210,14 @@ export class AppsService {
// 调用前提:应用是 Ready, 不存在依赖这个应用的其他应用 // 调用前提:应用是 Ready, 不存在依赖这个应用的其他应用
async doUninstall(app: App) { async doUninstall(app: App) {
if (!app.isReady()) { if (!app.isReady()) {
console.error('doUninstall', "应用不是 Ready 状态", app); console.error('doUninstall', '应用不是 Ready 状态', app);
throw "应用不是 Ready 状态" throw '应用不是 Ready 状态';
} }
if (this.findChildren(app).find((child) => child.isInstalled())) { if (this.findChildren(app).find((child) => child.isInstalled())) {
console.error('doUninstall', "无法卸载,还有依赖此程序的游戏。", app); console.error('doUninstall', '无法卸载,还有依赖此程序的游戏。', app);
throw "无法卸载,还有依赖此程序的游戏。" throw '无法卸载,还有依赖此程序的游戏。';
} }
app.status.status = "uninstalling"; app.status.status = 'uninstalling';
let appDir = app.local!.path; let appDir = app.local!.path;
let files = Array.from(app.local!.files.keys()).sort().reverse(); let files = Array.from(app.local!.files.keys()).sort().reverse();
...@@ -1226,23 +1234,22 @@ export class AppsService { ...@@ -1226,23 +1234,22 @@ export class AppsService {
} }
if (app.parent) { if (app.parent) {
// TODO: 建立Library模型,把拼路径的事情交给Library // TODO: 建立Library模型,把拼路径的事情交给Library
let backupDir = path.join(path.dirname(appDir), "backup", app.parent.id); let backupDir = path.join(path.dirname(appDir), 'backup', app.parent.id);
let fileSet = new ComparableSet(files); let fileSet = new ComparableSet(files);
let parentSet = new ComparableSet(Array.from(app.parent.local!.files.keys())); let parentSet = new ComparableSet(Array.from(app.parent.local!.files.keys()));
let difference = parentSet.intersection(fileSet); let difference = parentSet.intersection(fileSet);
if (difference) { if (difference) {
await this.restoreFiles(appDir, backupDir, Array.from(difference)) await this.restoreFiles(appDir, backupDir, Array.from(difference));
} }
} }
resolve(); resolve();
} } catch (e) {
catch (e) {
reject(e); reject(e);
} }
}); });
}); });
clearInterval(interval); clearInterval(interval);
app.reset() app.reset();
} }
} }
\ No newline at end of file
/* Turn on custom 8px wide scrollbar */
::-webkit-scrollbar {
width: 8px; /* 1px wider than Lion. */
/* This is more usable for users trying to click it. */
background-color: rgba(0, 0, 0, 0);
-webkit-border-radius: 100px;
}
/* hover effect for both scrollbar area, and scrollbar 'thumb' */
::-webkit-scrollbar:active {
background-color: rgba(0, 0, 0, 0.05);
}
/* The scrollbar 'thumb' ...that marque oval shape in a scrollbar */
::-webkit-scrollbar-thumb:vertical {
/* This is the EXACT color of Mac OS scrollbars.
Yes, I pulled out digital color meter */
background: rgba(0, 0, 0, 0.1);
-webkit-border-radius: 100px;
}
::-webkit-scrollbar-thumb:vertical:active {
background: rgba(0, 0, 0, 0.2); /* Some darker color when you click it */
-webkit-border-radius: 100px;
}
.scroll {
overflow-y: hidden;
}
.scroll:hover {
overflow-y: auto;
}
.message-pane-wrapper, .message-form-wrapper {
margin-right: 190px;
}
.roster-pane, #chat-toolbar {
width: 190px
}
#candy {
background-color: #f7f7f9;
}
#chat-tabs li {
box-shadow: 0 0 1px 1px #ccc;
}
#chat-tabs a {
color: #999;
background-color: #ececec;
}
#chat-tabs .active a.label {
background-color: white;
}
#chat-tabs .active .transition {
background: white none;
}
#chat-tabs .transition {
background: #ececec none;
}
.message-pane-wrapper {
background-color: white;
padding: 0;
}
.message-pane li {
border-bottom: none;
box-shadow: none;
padding: 0 5px;
}
.message-pane li > div {
padding: 0 0 0 150px;
line-height: 28px;
}
.message-pane .label {
margin-left: -150px;
width: 130px;
}
.roster-pane {
background-color: initial;
border-top: none;
box-shadow: none;
margin: 30px 0 32px 0;
}
.roster-pane .user {
border-bottom: none;
box-shadow: none;
color: black;
}
.roster-pane .user:hover {
background-color: #ebf3f8;
}
#chat-toolbar {
position: absolute;
background-color: initial;
border-top: 1px solid #eee;
box-shadow: none;
}
.message-form-wrapper {
position: absolute;
border-top: 1px solid #eee;
}
.message-form {
position: absolute;
margin-right: 20px;
}
.message-form input.submit {
position: absolute;
margin-right: 0
}
.roster-pane .label {
text-shadow: none
}
#candy {
border-top: 1px solid #eee;
box-shadow: inset 0 1px 2px white;
}
.usercount span {
background-color: initial;
color: #a7a7a7;
}
#chat-modal.modal-common {
position: absolute;
}
/*#context-menu {*/
/*margin-top: 0;*/
/*padding-top: 0;*/
/*}*/
#context-menu ul {
overflow-y: auto;
max-height: 170px;
}
/*#context-menu {*/
/**/
/*}*/
\ No newline at end of file
<div id="candy"></div>
\ No newline at end of file
/**
* Created by zh99998 on 16/9/2.
*/
let shadow: ShadowRoot;
const jQueryOriginal = window['jQuery'];
const jQueryShadow = require('../jquery-shadow.js');
jQueryShadow.fn.init = new Proxy(jQueryShadow.fn.init, {
construct(target, argumentsList, newTarget) {
let [selector, context, root] = argumentsList;
if (shadow) {
if (selector === 'body') {
selector = shadow;
} else if (selector === document) {
selector = shadow.querySelector('#candy');
} else if (!context) {
context = shadow;
}
}
return new target(selector, context, root);
}
});
window['jQuery'] = jQueryShadow;
import {Component, ViewEncapsulation, OnInit, Input, OnChanges, SimpleChanges, ElementRef} from '@angular/core';
import {LoginService} from './login.service';
import {SettingsService} from './settings.sevices';
import {App} from './app';
import 'node_modules/candy/libs.min.js';
import 'node_modules/candy/candy.bundle.js';
import 'node_modules/candy-shop/notifyme/candy.js';
import 'node_modules/candy-shop/namecomplete/candy.js';
import 'node_modules/candy-shop/modify-role/candy.js';
import 'node_modules/candy-shop/me-does/candy.js';
import 'node_modules/candy-shop/notifications/candy.js';
import 'node_modules/candy-shop/refocus/candy.js';
import 'electron-cookies';
window['jQuery'] = jQueryOriginal;
declare const Candy: any, CandyShop: any, Base64: any;
Candy.Util.getPosLeftAccordingToWindowBounds = new Proxy(Candy.Util.getPosLeftAccordingToWindowBounds, {
apply(target, thisArg, argumentsList) {
argumentsList[1] -= shadow.host.getBoundingClientRect().left;
return target.apply(thisArg, argumentsList);
}
});
Candy.Util.getPosTopAccordingToWindowBounds = new Proxy(Candy.Util.getPosTopAccordingToWindowBounds, {
apply(target, thisArg, argumentsList) {
argumentsList[1] -= shadow.host.getBoundingClientRect().top;
return target.apply(thisArg, argumentsList);
}
});
@Component({
moduleId: module.id,
selector: 'candy',
templateUrl: 'candy.component.html',
styleUrls: ['candy.component.css'],
encapsulation: ViewEncapsulation.Native
})
export class CandyComponent implements OnInit, OnChanges {
@Input()
currentApp: App;
jid: string;
password: string;
nickname: string;
constructor (private loginService: LoginService, private settingsService: SettingsService, private element: ElementRef) {
}
ngOnInit () {
this.jid = this.loginService.user.username + '@mycard.moe';
this.password = this.loginService.user.external_id.toString();
this.nickname = this.loginService.user.username;
shadow = this.element.nativeElement.shadowRoot;
// 很 Tricky 的加载 Candy 的 css,这里涉及图片等资源的相对路径引用问题,如果丢给 Angular 去加载,会让相对路径找不到
const element = document.createElement('style');
element.innerHTML = `
@import "node_modules/candy/libs.min.css";
@import "node_modules/candy/res/default.css";
@import "node_modules/candy-shop/notifyme/candy.css";
@import "node_modules/candy-shop/namecomplete/candy.css";
@import "node_modules/candy-shop/modify-role/candy.css"
`;
shadow.insertBefore(element, shadow.firstChild);
// Candy fix
Base64.encode = (data: string) => Buffer.from(data).toString('base64');
Base64.decode = (data: string) => Buffer.from(data, 'base64').toString();
Candy.View.Template.Login.form = `
<form method="post" id="login-form" class="login-form">
<input type="hidden" id="nickname" name="nickname" value="' + this.nickname + '"/>
{{#displayUsername}}
<input type="hidden" id="username" name="username" value="' + this.jid + '"/>
{{#displayDomain}}
<span class="at-symbol">@</span>
<select id="domain" name="domain">{{#domains}}<option value="{{domain}}">{{domain}}</option>{{/domains}}</select>
{{/displayDomain}}
{{/displayUsername}}
{{#presetJid}}<input type="hidden" id="username" name="username" value="{{presetJid}}"/>{{/presetJid}}
{{#displayPassword}}<input type="hidden" id="password" name="password" value="' + this.password + '"/>{{/displayPassword}}
<input type="submit" class="button" value="{{_loginSubmit}}" />
</form>
`;
Candy.Util.setCookie('candy-nostatusmessages', '1', 365);
Candy.init('wss://chat.mycard.moe:5280/websocket', {
core: {
debug: false,
autojoin: this.currentApp.conference && [this.currentApp.conference + '@conference.mycard.moe'],
resource: 'mycard-' + Math.random().toString().split('.')[1]
},
view: {
assets: 'node_modules/candy/res/',
language: this.settingsService.getLocale().startsWith('zh') ? 'cn' : 'en',
enableXHTML: true,
}
});
CandyShop.NotifyMe.init();
CandyShop.NameComplete.init();
CandyShop.ModifyRole.init();
CandyShop.MeDoes.init();
CandyShop.Notifications.init();
CandyShop.Refocus.init();
Candy.Core.connect(this.jid, this.password, this.nickname);
}
ngOnChanges (changes: SimpleChanges): void {
if (!Candy.Core.getConnection()) {
return;
}
let conference = changes['currentApp'].currentValue.conference;
if (!conference) {
return;
}
conference += '@conference.mycard.moe';
if (Candy.View.Pane.Chat.rooms[conference]) {
Candy.View.Pane.Room.show(conference);
} else {
Candy.Core.Action.Jabber.Room.Join(conference);
}
}
}
/** /**
* Created by weijian on 2016/10/26. * Created by weijian on 2016/10/26.
*/ */
import {Injectable, NgZone, EventEmitter} from "@angular/core"; import {Injectable, NgZone, EventEmitter} from '@angular/core';
import {Http} from "@angular/http"; import {Http} from '@angular/http';
import {error} from "util"; // import {error} from 'util';
import Timer = NodeJS.Timer; // import Timer = NodeJS.Timer;
const Logger = { // const Logger = {
"error": (message: string) => { // 'error': (message: string) => {
console.error("DownloadService: ", message); // console.error('DownloadService: ', message);
} // }
}; // };
const Aria2 = require('aria2'); const Aria2 = require('aria2');
const MAX_LIST_NUM = 1000; const MAX_LIST_NUM = 1000;
...@@ -19,13 +19,13 @@ export class DownloadStatus { ...@@ -19,13 +19,13 @@ export class DownloadStatus {
completedLength: number; completedLength: number;
downloadSpeed: number; downloadSpeed: number;
get downloadSpeedText(): string { get downloadSpeedText (): string {
if (!isNaN(this.downloadSpeed) && this.downloadSpeed !== 0) { if (!isNaN(this.downloadSpeed) && this.downloadSpeed !== 0) {
const speedUnit = ["Byte/s", "KB/s", "MB/s", "GB/s", "TB/s"]; const speedUnit = ['Byte/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s'];
let currentUnit = Math.floor(Math.log(this.downloadSpeed) / Math.log(1024)); let currentUnit = Math.floor(Math.log(this.downloadSpeed) / Math.log(1024));
return (this.downloadSpeed / 1024 ** currentUnit).toFixed(1) + " " + speedUnit[currentUnit]; return (this.downloadSpeed / 1024 ** currentUnit).toFixed(1) + ' ' + speedUnit[currentUnit];
} }
return ""; return '';
}; };
gid: string; gid: string;
...@@ -35,22 +35,22 @@ export class DownloadStatus { ...@@ -35,22 +35,22 @@ export class DownloadStatus {
errorCode: string; errorCode: string;
errorMessage: string; errorMessage: string;
combine(...others: DownloadStatus[]): DownloadStatus { combine (...others: DownloadStatus[]): DownloadStatus {
const priority = { const priority = {
undefined: -1, undefined: -1,
"": -1, '': -1,
"active": 0, 'active': 0,
"complete": 0, 'complete': 0,
"paused": 1, 'paused': 1,
"waiting": 1, 'waiting': 1,
"removed": 2, 'removed': 2,
"error": 3 'error': 3
}; };
let status = Object.assign(new DownloadStatus(), this); let status = Object.assign(new DownloadStatus(), this);
for (let o of others) { for (let o of others) {
if (priority[o.status] > priority[status.status]) { if (priority[o.status] > priority[status.status]) {
status.status = o.status; status.status = o.status;
if (status.status === "error") { if (status.status === 'error') {
status.errorCode = o.errorCode; status.errorCode = o.errorCode;
status.errorMessage = o.errorMessage; status.errorMessage = o.errorMessage;
} }
...@@ -64,7 +64,7 @@ export class DownloadStatus { ...@@ -64,7 +64,7 @@ export class DownloadStatus {
} }
// 0相等. 1不想等 // 0相等. 1不想等
compareTo(other: DownloadStatus): number { compareTo (other: DownloadStatus): number {
if (this.status !== other.status || if (this.status !== other.status ||
this.downloadSpeed !== other.downloadSpeed || this.downloadSpeed !== other.downloadSpeed ||
this.completedLength !== other.completedLength || this.completedLength !== other.completedLength ||
...@@ -75,7 +75,7 @@ export class DownloadStatus { ...@@ -75,7 +75,7 @@ export class DownloadStatus {
} }
} }
constructor(item ?: any) { constructor (item ?: any) {
if (item) { if (item) {
this.completedLength = parseInt(item.completedLength) || 0; this.completedLength = parseInt(item.completedLength) || 0;
this.downloadSpeed = parseInt(item.downloadSpeed) || 0; this.downloadSpeed = parseInt(item.downloadSpeed) || 0;
...@@ -99,11 +99,11 @@ export class DownloadService { ...@@ -99,11 +99,11 @@ export class DownloadService {
open = this.aria2.open(); open = this.aria2.open();
updateEmitter = new EventEmitter<void>(); updateEmitter = new EventEmitter<void>();
downloadList: Map<string,DownloadStatus> = new Map(); downloadList: Map<string, DownloadStatus> = new Map();
taskMap: Map<string,string[]> = new Map(); taskMap: Map<string, string[]> = new Map();
async refreshDownloadList() { async refreshDownloadList () {
let activeList = await this.aria2.tellActive(); let activeList = await this.aria2.tellActive();
let waitList = await this.aria2.tellWaiting(0, MAX_LIST_NUM); let waitList = await this.aria2.tellWaiting(0, MAX_LIST_NUM);
let stoppedList = await this.aria2.tellStopped(0, MAX_LIST_NUM); let stoppedList = await this.aria2.tellStopped(0, MAX_LIST_NUM);
...@@ -120,17 +120,17 @@ export class DownloadService { ...@@ -120,17 +120,17 @@ export class DownloadService {
this.updateEmitter.emit(); this.updateEmitter.emit();
} }
constructor(private ngZone: NgZone, private http: Http) { constructor (private ngZone: NgZone, private http: Http) {
ngZone.runOutsideAngular(async() => { ngZone.runOutsideAngular(async () => {
await this.open; await this.open;
setInterval(async() => { setInterval(async () => {
await this.refreshDownloadList(); await this.refreshDownloadList();
}, ARIA2_INTERVAL); }, ARIA2_INTERVAL);
}) });
} }
private createId(): string { private createId (): string {
function s4() { function s4 () {
return Math.floor((1 + Math.random()) * 0x10000) return Math.floor((1 + Math.random()) * 0x10000)
.toString(16) .toString(16)
.substring(1); .substring(1);
...@@ -140,7 +140,7 @@ export class DownloadService { ...@@ -140,7 +140,7 @@ export class DownloadService {
s4() + '-' + s4() + s4() + s4(); s4() + '-' + s4() + s4() + s4();
} }
async progress(id: string, callback: (downloadStatus: DownloadStatus) => void) { async progress (id: string, callback: (downloadStatus: DownloadStatus) => void) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let gids = this.taskMap.get(id); let gids = this.taskMap.get(id);
if (gids) { if (gids) {
...@@ -154,7 +154,7 @@ export class DownloadService { ...@@ -154,7 +154,7 @@ export class DownloadService {
gids!.map((value, index, array) => { gids!.map((value, index, array) => {
let s = this.downloadList.get(value); let s = this.downloadList.get(value);
if (!s) { if (!s) {
throw "Gid not exists"; throw 'Gid not exists';
} }
return s; return s;
}) })
...@@ -164,13 +164,13 @@ export class DownloadService { ...@@ -164,13 +164,13 @@ export class DownloadService {
if (!allStatus) { if (!allStatus) {
allStatus = status; allStatus = status;
} else { } else {
if (allStatus.compareTo(status) != 0) { if (allStatus.compareTo(status) !== 0) {
allStatus = status; allStatus = status;
} }
} }
if (allStatus.status === "error") { if (allStatus.status === 'error') {
throw `Download Error: code ${allStatus.errorCode}, message: ${allStatus.errorMessage}`; throw `Download Error: code ${allStatus.errorCode}, message: ${allStatus.errorMessage}`;
} else if (allStatus.status === "complete") { } else if (allStatus.status === 'complete') {
resolve(); resolve();
subscription.unsubscribe(); subscription.unsubscribe();
} else { } else {
...@@ -183,12 +183,12 @@ export class DownloadService { ...@@ -183,12 +183,12 @@ export class DownloadService {
} }
}); });
} else { } else {
throw "Try to access invalid download id"; throw 'Try to access invalid download id';
} }
}) });
} }
async getFiles(id: string): Promise<string[]> { async getFiles (id: string): Promise<string[]> {
let gids = this.taskMap.get(id)!; let gids = this.taskMap.get(id)!;
let files: string[] = []; let files: string[] = [];
for (let gid of gids) { for (let gid of gids) {
...@@ -198,7 +198,7 @@ export class DownloadService { ...@@ -198,7 +198,7 @@ export class DownloadService {
return files; return files;
} }
async addMetalink(metalink: string, library: string): Promise<string> { async addMetalink (metalink: string, library: string): Promise<string> {
let encodedMeta4 = new Buffer((metalink)).toString('base64'); let encodedMeta4 = new Buffer((metalink)).toString('base64');
let gidList = await this.aria2.addMetalink(encodedMeta4, {dir: library}); let gidList = await this.aria2.addMetalink(encodedMeta4, {dir: library});
let taskId = this.createId(); let taskId = this.createId();
...@@ -208,7 +208,7 @@ export class DownloadService { ...@@ -208,7 +208,7 @@ export class DownloadService {
return taskId; return taskId;
} }
async addUri(url: string, destination: string): Promise<string> { async addUri (url: string, destination: string): Promise<string> {
await this.open; await this.open;
let taskId = this.createId(); let taskId = this.createId();
let gid = await this.aria2.addUri([url], {dir: destination}); let gid = await this.aria2.addUri([url], {dir: destination});
...@@ -216,10 +216,10 @@ export class DownloadService { ...@@ -216,10 +216,10 @@ export class DownloadService {
return taskId; return taskId;
} }
async pause(id: string): Promise<void> { async pause (id: string): Promise<void> {
await this.open; await this.open;
try { try {
await this.aria2.pause(id) await this.aria2.pause(id);
} catch (e) { } catch (e) {
} }
......
import {TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID} from "@angular/core"; import {TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID} from '@angular/core';
import {remote} from "electron"; import {remote} from 'electron';
export async function getTranslationProviders(): Promise<Object[]> { export async function getTranslationProviders (): Promise<Object[]> {
let locale = localStorage.getItem('locale'); let locale = localStorage.getItem('locale');
if (!locale) { if (!locale) {
locale = remote.app.getLocale(); locale = remote.app.getLocale();
...@@ -18,12 +18,12 @@ export async function getTranslationProviders(): Promise<Object[]> { ...@@ -18,12 +18,12 @@ export async function getTranslationProviders(): Promise<Object[]> {
{provide: TRANSLATIONS, useValue: translations}, {provide: TRANSLATIONS, useValue: translations},
{provide: TRANSLATIONS_FORMAT, useValue: 'xlf'}, {provide: TRANSLATIONS_FORMAT, useValue: 'xlf'},
{provide: LOCALE_ID, useValue: locale} {provide: LOCALE_ID, useValue: locale}
] ];
} catch (error) { } catch (error) {
return noProviders return noProviders;
} }
} }
declare var System: any; declare const System: any;
function getTranslationsWithSystemJs(file: string) { function getTranslationsWithSystemJs (file: string) {
return System.import(file + '!text'); // relies on text plugin return System.import(file + '!text'); // relies on text plugin
} }
import {App} from "./app"; import {App} from './app';
import * as path from "path" import * as path from 'path';
/** /**
* Created by weijian on 2016/10/24. * Created by weijian on 2016/10/24.
*/ */
...@@ -9,17 +9,17 @@ export class InstallOption { ...@@ -9,17 +9,17 @@ export class InstallOption {
downloadFiles: string[]; downloadFiles: string[];
installLibrary: string; installLibrary: string;
get installDir(): string { get installDir (): string {
return path.join(this.installLibrary, this.app.id); return path.join(this.installLibrary, this.app.id);
} }
createShortcut: boolean; createShortcut: boolean;
createDesktopShortcut: boolean; createDesktopShortcut: boolean;
constructor(app: App, installLibrary = "", shortcut = false, desktopShortcut = false) { constructor (app: App, installLibrary = '', shortcut = false, desktopShortcut = false) {
this.app = app; this.app = app;
this.createShortcut = shortcut; this.createShortcut = shortcut;
this.createDesktopShortcut = desktopShortcut; this.createDesktopShortcut = desktopShortcut;
this.installLibrary = installLibrary; this.installLibrary = installLibrary;
} }
} }
\ No newline at end of file
:host { :host {
display: flex; display: flex;
height: 100%;
} }
#right { #right {
display: flex; display: flex;
...@@ -12,10 +13,11 @@ ...@@ -12,10 +13,11 @@
flex-grow: 1; flex-grow: 1;
} }
#candy { #candy-wrapper {
background-color: #444; background-color: #444;
height: 230px; height: 230px;
flex-shrink: 0; flex-shrink: 0;
position: relative;
} }
roster { roster {
...@@ -117,15 +119,21 @@ span { ...@@ -117,15 +119,21 @@ span {
float: right; float: right;
} }
.sidebar { #nav-wrapper {
width: 190px; width: 190px;
height: 100%;
flex-shrink: 0; flex-shrink: 0;
background-color: #f7f7f9; background-color: #f7f7f9;
border-right: 1px solid #eee; border-right: 1px solid #eee;
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
padding-top: 20px; padding-top: 20px;
/*resize: horizontal;*/
position: relative;
}
nav {
height: 100%;
} }
.sidebar .nav { .sidebar .nav {
...@@ -181,4 +189,44 @@ input.search::-webkit-input-placeholder{ ...@@ -181,4 +189,44 @@ input.search::-webkit-input-placeholder{
.icon { .icon {
width: 16px; width: 16px;
height: 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;
} }
\ No newline at end of file
<!-- Begin page content --> <!-- Begin page content -->
<nav id="apps" *ngIf="apps" class="bg-faded sidebar scroll"> <div #nav id="nav-wrapper" class="resize-wrapper resize-right">
<div id="search" class="input-group"> <nav id="apps" *ngIf="apps" class="bg-faded sidebar scroll">
<i class="fa fa-search input-group-addon search" id="basic-addon1"></i> <div id="search" class="input-group">
<input type="text" class="form-control search" placeholder="搜索游戏" aria-describedby="basic-addon1"> <i class="fa fa-search input-group-addon search" id="basic-addon1"></i>
</div> <input type="text" class="form-control search" placeholder="搜索游戏" aria-describedby="basic-addon1">
</div>
<span i18n *ngIf="grouped_apps.installed">已安装</span> <span i18n *ngIf="grouped_apps.installed">已安装</span>
<ul *ngIf="grouped_apps.installed" class="nav nav-pills flex-column"> <ul *ngIf="grouped_apps.installed" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.installed" class="nav-item"> <li *ngFor="let app of grouped_apps.installed" class="nav-item">
<a (click)="chooseApp(app)" class="nav-link" [class.active]="app===currentApp" href="#"> <a (click)="chooseApp(app)" class="nav-link" [class.active]="app===currentApp" href="#">
<!--<img class="icon" src="https://lh3.googleusercontent.com/-crYEtoQ-4Ho/AAAAAAAAAAI/AAAAAAAAAAA/AKB_U8u0CDmxkVqQgOKesrJIb-6eiXacgA/s32-c-mo/photo.jpg">--> <!--<img class="icon" src="https://lh3.googleusercontent.com/-crYEtoQ-4Ho/AAAAAAAAAAI/AAAAAAAAAAA/AKB_U8u0CDmxkVqQgOKesrJIb-6eiXacgA/s32-c-mo/photo.jpg">-->{{app.name}}<i *ngIf="!app.isReady() && !app.status.total" class="spin fa fa-circle-o-notch fa-spin fa-fw"></i>
{{app.name}}<i <div *ngIf="!app.isReady() && app.status.total" class="progress">
*ngIf="!app.isReady() && !app.status.total" class="spin fa fa-circle-o-notch fa-spin fa-fw"></i> <div class="pie" [class.second-half]="app.status.progress/app.status.total>0.5">
<div *ngIf="!app.isReady() && app.status.total" class="progress"> <div class="left-side half-circle" [style.transform]="'rotate('+(app.status.progress/app.status.total).toString()+'turn)'"></div>
<div class="pie" [class.second-half]="app.status.progress/app.status.total>0.5"> <div class="right-side half-circle"></div>
<div class="left-side half-circle" </div>
[style.transform]="'rotate('+(app.status.progress/app.status.total).toString()+'turn)'"></div> <div class="shadow"></div>
<div class="right-side half-circle"></div>
</div> </div>
<div class="shadow"></div> </a>
</div> </li>
</a> </ul>
</li> <span i18n *ngIf="grouped_apps.recommend">推荐</span>
</ul> <ul *ngIf="grouped_apps.recommend" class="nav nav-pills flex-column">
<span i18n *ngIf="grouped_apps.recommend">推荐</span> <li *ngFor="let app of grouped_apps.recommend" class="nav-item">
<ul *ngIf="grouped_apps.recommend" class="nav nav-pills flex-column"> <a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
<li *ngFor="let app of grouped_apps.recommend" class="nav-item"> </li>
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a> </ul>
</li> <span i18n *ngIf="grouped_apps.mysterious">迷之物体</span>
</ul> <ul *ngIf="grouped_apps.mysterious" class="nav nav-pills flex-column">
<span i18n *ngIf="grouped_apps.mysterious">迷之物体</span> <li *ngFor="let app of grouped_apps.mysterious" class="nav-item">
<ul *ngIf="grouped_apps.mysterious" class="nav nav-pills flex-column"> <a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
<li *ngFor="let app of grouped_apps.mysterious" class="nav-item"> </li>
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a> </ul>
</li> <span i18n *ngIf="grouped_apps.touhou">东方 Project</span>
</ul> <ul *ngIf="grouped_apps.touhou" class="nav nav-pills flex-column">
<span i18n *ngIf="grouped_apps.touhou">东方 Project</span> <li *ngFor="let app of grouped_apps.touhou" class="nav-item">
<ul *ngIf="grouped_apps.touhou" class="nav nav-pills flex-column"> <a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
<li *ngFor="let app of grouped_apps.touhou" class="nav-item"> </li>
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a> </ul>
</li> <span i18n *ngIf="grouped_apps.touhou_pc98">东方旧作</span>
</ul> <ul *ngIf="grouped_apps.touhou_pc98" class="nav nav-pills flex-column">
<span i18n *ngIf="grouped_apps.touhou_pc98">东方旧作</span> <li *ngFor="let app of grouped_apps.touhou_pc98" class="nav-item">
<ul *ngIf="grouped_apps.touhou_pc98" class="nav nav-pills flex-column"> <a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
<li *ngFor="let app of grouped_apps.touhou_pc98" class="nav-item"> </li>
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a> </ul>
</li> <span i18n *ngIf="grouped_apps.runtime_installed">已安装的运行库</span>
</ul> <ul *ngIf="grouped_apps.runtime_installed" class="nav nav-pills flex-column">
<span i18n *ngIf="grouped_apps.runtime_installed">已安装的运行库</span> <li *ngFor="let app of grouped_apps.runtime_installed" class="nav-item">
<ul *ngIf="grouped_apps.runtime_installed" class="nav nav-pills flex-column"> <a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
<li *ngFor="let app of grouped_apps.runtime_installed" class="nav-item"> </li>
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a> </ul>
</li> </nav>
</ul> <div class="resize" (mousedown)="mousedown($event)"></div>
</nav> </div>
<div id="right"> <div id="right">
<div id="main"> <div id="main">
<app-detail *ngIf="currentApp" [currentApp]="currentApp"></app-detail> <app-detail *ngIf="currentApp" [currentApp]="currentApp"></app-detail>
<roster class="scroll"></roster> <roster class="scroll"></roster>
</div> </div>
<webview *ngIf="currentApp" #candy id="candy" [src]="candy_url" (new-window)="openExternal($event.url)" <div id="candy-wrapper" class="resize-wrapper resize-top">
nodeintegration></webview> <div class="resize" (mousedown)="mousedown($event)"></div>
<candy *ngIf="currentApp" [currentApp]="currentApp"></candy>
</div>
</div> </div>
/** /**
* Created by zh99998 on 16/9/2. * Created by zh99998 on 16/9/2.
*/ */
import {Component, OnInit, ElementRef, ViewChild} from "@angular/core"; import {Component, OnInit} from '@angular/core';
import {AppsService} from "./apps.service"; import {AppsService} from './apps.service';
import {LoginService} from "./login.service"; import {LoginService} from './login.service';
import {App, Category} from "./app"; import {App, Category} from './app';
import {URLSearchParams} from "@angular/http"; import {shell} from 'electron';
import {shell} from "electron"; import {SettingsService} from './settings.sevices';
import {SettingsService} from "./settings.sevices";
import WebViewElement = Electron.WebViewElement;
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
...@@ -18,60 +16,73 @@ import WebViewElement = Electron.WebViewElement; ...@@ -18,60 +16,73 @@ import WebViewElement = Electron.WebViewElement;
}) })
export class LobbyComponent implements OnInit { export class LobbyComponent implements OnInit {
@ViewChild('candy')
candy?: ElementRef;
candy_url: URL;
currentApp: App; currentApp: App;
private apps: Map<string,App>; private apps: Map<string, App>;
resizing: HTMLElement | undefined;
offset: number;
constructor(private appsService: AppsService, private loginService: LoginService, private settingsService: SettingsService) { constructor (private appsService: AppsService, private loginService: LoginService, private settingsService: SettingsService) {
} }
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')!);
// 初始化聊天室
let url = new URL('candy.html', location.href);
let params: URLSearchParams = url['searchParams']; // TypeScrpt 缺了 url.searchParams 的定义
params.set('jid', this.loginService.user.username + '@mycard.moe');
params.set('password', this.loginService.user.external_id.toString());
params.set('nickname', this.loginService.user.username);
switch (this.settingsService.getLocale()) {
case 'zh-CN':
params.set('language', 'cn');
break;
default:
params.set('language', 'en');
}
if (this.currentApp.conference) {
params.set('autojoin', this.currentApp.conference + '@conference.mycard.moe');
}
this.candy_url = url;
await this.appsService.migrate(); await this.appsService.migrate();
for (let app of this.apps.values()) { for (let app of this.apps.values()) {
await this.appsService.update(app); await this.appsService.update(app);
} }
} else { } else {
if (confirm("获取程序列表失败,是否重试?")) { if (confirm('获取程序列表失败,是否重试?')) {
location.reload(); location.reload();
} else { } else {
window.close(); window.close();
} }
} }
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;
}
this.resizing.style.width = `${width}px`;
} else {
let height = this.offset - event.clientY;
if (height < 236) {
height = 236;
}
this.resizing.style.height = `${height}px`;
}
});
document.addEventListener('mouseup', (event: MouseEvent) => {
this.resizing = undefined;
});
} }
chooseApp(app: App) { mousedown (event: MouseEvent) {
// console.log(()
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.currentApp = app;
this.appsService.lastVisited = app; this.appsService.lastVisited = app;
if (this.candy && this.currentApp.conference) {
(<WebViewElement>this.candy.nativeElement).send('join', this.currentApp.conference + '@conference.mycard.moe');
}
} }
get grouped_apps() { get grouped_apps () {
let contains = ["game", "music", "book"].map((value) => Category[value]); let contains = ['game', 'music', 'book'].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;
...@@ -90,14 +101,14 @@ export class LobbyComponent implements OnInit { ...@@ -90,14 +101,14 @@ export class LobbyComponent implements OnInit {
} }
if (!result[tag]) { if (!result[tag]) {
result[tag] = [] result[tag] = [];
} }
result[tag].push(app) result[tag].push(app);
} }
return result return result;
} }
openExternal(url: string) { openExternal (url: string) {
shell.openExternal(url); shell.openExternal(url);
} }
} }
/** /**
* Created by zh99998 on 16/9/2. * Created by zh99998 on 16/9/2.
*/ */
import {Component} from "@angular/core"; import {Component} from '@angular/core';
import {LoginService} from "./login.service"; import {LoginService} from './login.service';
import * as crypto from "crypto"; import * as crypto from 'crypto';
import * as querystring from "querystring"; import * as querystring from 'querystring';
import * as url from "url"; import * as url from 'url';
import {shell} from "electron"; import {shell} from 'electron';
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
...@@ -18,9 +18,10 @@ export class LoginComponent { ...@@ -18,9 +18,10 @@ export class LoginComponent {
url: string; url: string;
return_sso_url = 'https://mycard.moe/login_callback'; // 这个url不会真的被使用,可以填写不存在的 return_sso_url = 'https://mycard.moe/login_callback'; // 这个url不会真的被使用,可以填写不存在的
constructor(private loginService: LoginService) { constructor (private loginService: LoginService) {
let payload = new Buffer(querystring.stringify({ let payload = new Buffer(querystring.stringify({
//nonce: nonce, // nonce: nonce,
return_sso_url: this.return_sso_url return_sso_url: this.return_sso_url
})).toString('base64'); })).toString('base64');
...@@ -28,16 +29,14 @@ export class LoginComponent { ...@@ -28,16 +29,14 @@ export class LoginComponent {
'sso': payload, 'sso': payload,
'sig': crypto.createHmac('sha256', 'zsZv6LXHDwwtUAGa').update(payload).digest('hex') 'sig': crypto.createHmac('sha256', 'zsZv6LXHDwwtUAGa').update(payload).digest('hex')
}); });
this.url = "https://ygobbs.com/session/sso_provider?" + request; this.url = 'https://ygobbs.com/session/sso_provider?' + request;
if (this.loginService.logging_out) { if (this.loginService.logging_out) {
let request = querystring.stringify({ this.url = 'https://ygobbs.com/logout?' + querystring.stringify({'redirect': this.url});
'redirect': this.url
});
this.url = "https://ygobbs.com/logout?" + request;
} }
} }
return_sso(return_url: string) { return_sso (return_url: string) {
if (!return_url.startsWith(this.return_sso_url)) { if (!return_url.startsWith(this.return_sso_url)) {
return; return;
} }
...@@ -47,7 +46,7 @@ export class LoginComponent { ...@@ -47,7 +46,7 @@ export class LoginComponent {
this.loginService.login(user); this.loginService.login(user);
} }
openExternal(url: string) { openExternal (url: string) {
shell.openExternal(url); shell.openExternal(url);
} }
} }
/** /**
* Created by zh99998 on 2016/10/25. * Created by zh99998 on 2016/10/25.
*/ */
import {Injectable} from "@angular/core"; import {Injectable} from '@angular/core';
import {Http} from "@angular/http"; import {Http} from '@angular/http';
export interface User { export interface User {
admin: boolean; admin: boolean;
...@@ -20,7 +20,7 @@ export class LoginService { ...@@ -20,7 +20,7 @@ export class LoginService {
logged_in = false; logged_in = false;
logging_out = false; logging_out = false;
constructor(private http: Http) { constructor (private http: Http) {
let data = localStorage.getItem('login'); let data = localStorage.getItem('login');
if (data) { if (data) {
this.user = JSON.parse(data); this.user = JSON.parse(data);
...@@ -28,16 +28,16 @@ export class LoginService { ...@@ -28,16 +28,16 @@ export class LoginService {
} }
} }
login(user: User) { login (user: User) {
this.user = user; this.user = user;
this.logged_in = true; this.logged_in = true;
localStorage.setItem('login', JSON.stringify(user)); localStorage.setItem('login', JSON.stringify(user));
} }
logout() { logout () {
this.logging_out = true; this.logging_out = true;
this.logged_in = false; this.logged_in = false;
localStorage.removeItem('login'); localStorage.removeItem('login');
} }
} }
\ No newline at end of file
import {MyCardNgFactory} from "../aot/app/mycard.module.ngfactory"; // import {MyCardNgFactory} from '../aot/app/mycard.module.ngfactory';
import {getTranslationProviders} from "./i18n-providers"; // import {getTranslationProviders} from './i18n-providers';
import {enableProdMode} from "@angular/core"; // import {enableProdMode} from '@angular/core';
import {platformBrowser} from "@angular/platform-browser"; // import {platformBrowser} from '@angular/platform-browser';
enableProdMode(); // enableProdMode();
//
getTranslationProviders().then(providers => { // getTranslationProviders().then(providers => {
const options = {providers}; // const options = {providers};
platformBrowser().bootstrapModuleFactory(MyCardNgFactory); // platformBrowser().bootstrapModuleFactory(MyCardNgFactory);
}); // });
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic"; window['jQuery'] = require('jquery');
import {getTranslationProviders} from "./i18n-providers"; window['Tether'] = require('tether');
import {MyCard} from "./mycard.module"; import 'node_modules/bootstrap/dist/js/bootstrap.min.js';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {getTranslationProviders} from './i18n-providers';
import {MyCard} from './mycard.module';
getTranslationProviders().then(providers => { getTranslationProviders().then(providers => {
const options = {providers}; const options = {providers};
platformBrowserDynamic().bootstrapModule(MyCard, options); platformBrowserDynamic().bootstrapModule(MyCard, options);
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
</li> </li>
<!--<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'store'}" class="nav-item">--> <!--<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'store'}" class="nav-item">-->
<!--<a (click)="currentPage = 'store'" class="nav-link" href="#">商店</a>--> <!--<a (click)="currentPage = 'store'" class="nav-link" href="#">商店</a>-->
<!--</li>--> <!--</li>-->
<li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'lobby'}" class="nav-item"> <li *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'lobby'}" class="nav-item">
...@@ -64,8 +64,8 @@ ...@@ -64,8 +64,8 @@
<label i18n for="locale" class="col-sm-2 col-form-label">语言</label> <label i18n for="locale" class="col-sm-2 col-form-label">语言</label>
<div class="col-sm-10"> <div class="col-sm-10">
<select class="form-control" id="locale" [(ngModel)]="locale" name="locale"> <select class="form-control" id="locale" [(ngModel)]="locale" name="locale">
<option i18n value="en-US">英文</option> <option value="en-US">English</option>
<option i18n value="zh-CN">简体中文</option> <option value="zh-CN">简体中文</option>
</select> </select>
</div> </div>
</div> </div>
......
import {Component, Renderer, ChangeDetectorRef, OnInit, ElementRef, ViewChild} from "@angular/core"; import {Component, Renderer, ChangeDetectorRef, OnInit, ElementRef, ViewChild} from '@angular/core';
import {remote, shell} from "electron"; import {remote, shell} from 'electron';
import {LoginService} from "./login.service"; import {LoginService} from './login.service';
import {SettingsService} from "./settings.sevices"; import {SettingsService} from './settings.sevices';
import * as $ from 'jquery';
const autoUpdater: Electron.AutoUpdater = remote.getGlobal('autoUpdater'); const autoUpdater: Electron.AutoUpdater = remote.getGlobal('autoUpdater');
declare const $: any;
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
...@@ -13,7 +13,7 @@ declare const $: any; ...@@ -13,7 +13,7 @@ declare const $: any;
}) })
export class MyCardComponent implements OnInit { export class MyCardComponent implements OnInit {
currentPage: string = "lobby"; currentPage: string = 'lobby';
update_status: string | undefined = remote.getGlobal('update_status'); update_status: string | undefined = remote.getGlobal('update_status');
update_error: string | undefined; update_error: string | undefined;
...@@ -32,7 +32,9 @@ export class MyCardComponent implements OnInit { ...@@ -32,7 +32,9 @@ export class MyCardComponent implements OnInit {
locale: string; locale: string;
ngOnInit() { resizing: HTMLElement | null;
ngOnInit () {
this.update_elements = new Map(Object.entries({ this.update_elements = new Map(Object.entries({
'error': this.error, 'error': this.error,
'checking-for-update': this.checking_for_update, 'checking-for-update': this.checking_for_update,
...@@ -41,7 +43,8 @@ export class MyCardComponent implements OnInit { ...@@ -41,7 +43,8 @@ export class MyCardComponent implements OnInit {
})); }));
} }
constructor(private renderer: Renderer, private loginService: LoginService, private ref: ChangeDetectorRef, private settingsService: SettingsService) { constructor (private renderer: Renderer, private loginService: LoginService, private ref: ChangeDetectorRef,
private settingsService: SettingsService) {
// renderer.listenGlobal('window', 'message', (event) => { // renderer.listenGlobal('window', 'message', (event) => {
// console.log(event); // console.log(event);
// // Do something with 'event' // // Do something with 'event'
...@@ -70,20 +73,20 @@ export class MyCardComponent implements OnInit { ...@@ -70,20 +73,20 @@ export class MyCardComponent implements OnInit {
} }
update_retry() { update_retry () {
autoUpdater.checkForUpdates() autoUpdater.checkForUpdates();
} }
update_install() { update_install () {
autoUpdater.quitAndInstall() autoUpdater.quitAndInstall();
} }
set_update_status(status: string) { set_update_status (status: string) {
console.log('autoUpdater', status); console.log('autoUpdater', status);
if (this.update_status) { if (this.update_status) {
let element = this.update_elements.get(this.update_status); let element = this.update_elements.get(this.update_status);
if (element) { if (element) {
$(element.nativeElement).tooltip('dispose') $(element.nativeElement).tooltip('dispose');
} }
} }
this.update_status = status; this.update_status = status;
...@@ -91,19 +94,19 @@ export class MyCardComponent implements OnInit { ...@@ -91,19 +94,19 @@ export class MyCardComponent implements OnInit {
let element = this.update_elements.get(this.update_status); let element = this.update_elements.get(this.update_status);
if (element) { if (element) {
$(element.nativeElement).tooltip({placement: 'bottom', container: 'body'}) $(element.nativeElement).tooltip({placement: 'bottom', container: 'body'});
} }
} }
openExternal(url: string) { openExternal (url: string) {
shell.openExternal(url); shell.openExternal(url);
} }
submit() { submit () {
if (this.locale != this.settingsService.getLocale()) { if (this.locale !== this.settingsService.getLocale()) {
localStorage.setItem(SettingsService.SETTING_LOCALE, this.locale); localStorage.setItem(SettingsService.SETTING_LOCALE, this.locale);
remote.app.relaunch(); remote.app.relaunch();
remote.app.quit() remote.app.quit();
} }
} }
} }
import {NgModule, NO_ERRORS_SCHEMA} from "@angular/core"; import {NgModule, NO_ERRORS_SCHEMA} from '@angular/core';
import {BrowserModule} from "@angular/platform-browser"; import {BrowserModule} from '@angular/platform-browser';
import {FormsModule, ReactiveFormsModule} from "@angular/forms"; import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {HttpModule} from "@angular/http"; import {HttpModule} from '@angular/http';
import {MyCardComponent} from "./mycard.component"; import {MyCardComponent} from './mycard.component';
import {LoginComponent} from "./login.component"; import {LoginComponent} from './login.component';
import {StoreComponent} from "./store.component"; import {StoreComponent} from './store.component';
import {LobbyComponent} from "./lobby.component"; import {LobbyComponent} from './lobby.component';
import {AppDetailComponent} from "./app-detail.component"; import {AppDetailComponent} from './app-detail.component';
import {RosterComponent} from "./roster.component"; import {RosterComponent} from './roster.component';
import {YGOProComponent} from "./ygopro.component"; import {YGOProComponent} from './ygopro.component';
import {AppsService} from "./apps.service"; import {AppsService} from './apps.service';
import {SettingsService} from "./settings.sevices"; import {SettingsService} from './settings.sevices';
import {LoginService} from "./login.service"; import {LoginService} from './login.service';
import {DownloadService} from "./download.service"; import {DownloadService} from './download.service';
import {AboutComponent} from "./about.component"; import {AboutComponent} from './about.component';
import {CandyComponent} from './candy.component';
@NgModule({ @NgModule({
imports: [BrowserModule, FormsModule, ReactiveFormsModule, HttpModule], imports: [BrowserModule, FormsModule, ReactiveFormsModule, HttpModule],
declarations: [ declarations: [
MyCardComponent, LoginComponent, StoreComponent, LobbyComponent, MyCardComponent, LoginComponent, StoreComponent, LobbyComponent,
AppDetailComponent, RosterComponent, YGOProComponent, AboutComponent AppDetailComponent, RosterComponent, YGOProComponent, AboutComponent, CandyComponent
], ],
bootstrap: [MyCardComponent], bootstrap: [MyCardComponent],
providers: [ providers: [
...@@ -28,4 +29,4 @@ import {AboutComponent} from "./about.component"; ...@@ -28,4 +29,4 @@ import {AboutComponent} from "./about.component";
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
export class MyCard { export class MyCard {
} }
\ No newline at end of file
/** /**
* Created by zh99998 on 16/9/2. * Created by zh99998 on 16/9/2.
*/ */
import {Component} from "@angular/core"; import {Component} from '@angular/core';
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
selector: 'roster', selector: 'roster',
......
/** /**
* Created by weijian on 2016/10/24. * Created by weijian on 2016/10/24.
*/ */
import {Injectable} from "@angular/core"; import {Injectable} from '@angular/core';
import {remote} from "electron"; import {remote} from 'electron';
import * as path from "path"; import * as path from 'path';
export interface Library { export interface Library {
"default": boolean,path: string 'default': boolean;
path: string;
} }
@Injectable() @Injectable()
export class SettingsService { export class SettingsService {
static SETTING_LIBRARY = "library"; static SETTING_LIBRARY = 'library';
static defaultLibraries = [ static defaultLibraries = [
{ {
"default": true, 'default': true,
path: path.join(remote.app.getPath("appData"), "MyCardLibrary") path: path.join(remote.app.getPath('appData'), 'MyCardLibrary')
}, },
]; ];
static SETTING_LOCALE = 'locale';
static defaultLocale = remote.app.getLocale();
locale: string;
libraries: Library[]; libraries: Library[];
getLibraries() { getLibraries () {
if (!this.libraries) { if (!this.libraries) {
let data = localStorage.getItem(SettingsService.SETTING_LIBRARY); let data = localStorage.getItem(SettingsService.SETTING_LIBRARY);
if (!data) { if (!data) {
...@@ -36,7 +41,7 @@ export class SettingsService { ...@@ -36,7 +41,7 @@ export class SettingsService {
return this.libraries; return this.libraries;
} }
addLibrary(libraryPath: string, isDefault: boolean) { addLibrary (libraryPath: string, isDefault: boolean) {
let libraries = this.getLibraries(); let libraries = this.getLibraries();
if (isDefault) { if (isDefault) {
...@@ -44,37 +49,33 @@ export class SettingsService { ...@@ -44,37 +49,33 @@ export class SettingsService {
l.default = false; l.default = false;
}); });
} }
libraries.push({"default": isDefault, path: libraryPath}); libraries.push({'default': isDefault, path: libraryPath});
this.libraries = libraries; this.libraries = libraries;
localStorage.setItem(SettingsService.SETTING_LIBRARY, JSON.stringify(libraries)); localStorage.setItem(SettingsService.SETTING_LIBRARY, JSON.stringify(libraries));
} }
setDefaultLibrary(library: Library) { setDefaultLibrary (library: Library) {
let libraries = this.getLibraries(); let libraries = this.getLibraries();
libraries.forEach((l) => { libraries.forEach((l) => {
l.default = library.path == l.path; l.default = library.path === l.path;
}); });
this.libraries = libraries; this.libraries = libraries;
localStorage.setItem(SettingsService.SETTING_LIBRARY, JSON.stringify(libraries)); localStorage.setItem(SettingsService.SETTING_LIBRARY, JSON.stringify(libraries));
} }
getDefaultLibrary(): Library { getDefaultLibrary (): Library {
if (!this.libraries) { if (!this.libraries) {
this.getLibraries() this.getLibraries();
} }
let result = this.libraries.find((item) => item.default === true); let result = this.libraries.find((item) => item.default === true);
if (result) { if (result) {
return result return result;
} else { } else {
throw('no default library found') throw('no default library found');
} }
} }
static SETTING_LOCALE = "locale"; getLocale (): string {
static defaultLocale = remote.app.getLocale();
locale: string;
getLocale(): string {
if (!this.locale) { if (!this.locale) {
let locale = localStorage.getItem(SettingsService.SETTING_LOCALE); let locale = localStorage.getItem(SettingsService.SETTING_LOCALE);
if (!locale) { if (!locale) {
...@@ -87,8 +88,8 @@ export class SettingsService { ...@@ -87,8 +88,8 @@ export class SettingsService {
return this.locale; return this.locale;
} }
setLocale(locale: string) { setLocale (locale: string) {
this.locale = locale; this.locale = locale;
localStorage.setItem(SettingsService.SETTING_LOCALE, locale); localStorage.setItem(SettingsService.SETTING_LOCALE, locale);
} }
} }
\ No newline at end of file
...@@ -11,7 +11,7 @@ export class ComparableSet<T> extends Set<T> { ...@@ -11,7 +11,7 @@ export class ComparableSet<T> extends Set<T> {
} }
isSuperset(subset: Set<T>) { isSuperset(subset: Set<T>) {
for (var elem of subset) { for (let elem of subset) {
if (!this.has(elem)) { if (!this.has(elem)) {
return false; return false;
} }
...@@ -20,16 +20,16 @@ export class ComparableSet<T> extends Set<T> { ...@@ -20,16 +20,16 @@ export class ComparableSet<T> extends Set<T> {
} }
union(setB: Set<T>): Set<T> { union(setB: Set<T>): Set<T> {
var union = new Set(this); let union = new Set(this);
for (var elem of setB) { for (let elem of setB) {
union.add(elem); union.add(elem);
} }
return union; return union;
} }
intersection(setB: Set<T>): Set<T> { intersection(setB: Set<T>): Set<T> {
var intersection = new Set(); let intersection = new Set();
for (var elem of setB) { for (let elem of setB) {
if (this.has(elem)) { if (this.has(elem)) {
intersection.add(elem); intersection.add(elem);
} }
...@@ -38,8 +38,8 @@ export class ComparableSet<T> extends Set<T> { ...@@ -38,8 +38,8 @@ export class ComparableSet<T> extends Set<T> {
} }
difference(setB: Set<T>): Set<T> { difference(setB: Set<T>): Set<T> {
var difference = new Set(this); let difference = new Set(this);
for (var elem of setB) { for (let elem of setB) {
difference.delete(elem); difference.delete(elem);
} }
return difference; return difference;
......
/** /**
* Created by zh99998 on 16/9/2. * Created by zh99998 on 16/9/2.
*/ */
import {Component} from "@angular/core"; import {Component} from '@angular/core';
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
selector: 'store', selector: 'store',
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import {Component, OnInit, ChangeDetectorRef, Input} from '@angular/core'; import {Component, OnInit, ChangeDetectorRef, Input} from '@angular/core';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as crypto from 'crypto';
import * as child_process from 'child_process'; import * as child_process from 'child_process';
import {remote} from 'electron'; import {remote} from 'electron';
import * as ini from 'ini'; import * as ini from 'ini';
...@@ -16,77 +15,76 @@ import 'rxjs/Rx'; ...@@ -16,77 +15,76 @@ import 'rxjs/Rx';
import {ISubscription} from 'rxjs/Subscription'; import {ISubscription} from 'rxjs/Subscription';
import {AppsService} from './apps.service'; import {AppsService} from './apps.service';
import {SettingsService} from './settings.sevices'; import {SettingsService} from './settings.sevices';
import * as $ from 'jquery';
declare const $: any;
interface SystemConf { interface SystemConf {
use_d3d: string use_d3d: string;
antialias: string antialias: string;
errorlog: string errorlog: string;
nickname: string nickname: string;
gamename: string gamename: string;
lastdeck: string lastdeck: string;
textfont: string textfont: string;
numfont: string numfont: string;
serverport: string serverport: string;
lastip: string lastip: string;
lasthost: string lasthost: string;
lastport: string lastport: string;
autopos: string autopos: string;
randompos: string randompos: string;
autochain: string autochain: string;
waitchain: string waitchain: string;
mute_opponent: string mute_opponent: string;
mute_spectators: string mute_spectators: string;
hide_setname: string hide_setname: string;
hide_hint_button: string hide_hint_button: string;
control_mode: string control_mode: string;
draw_field_spell: string draw_field_spell: string;
separate_clear_button: string separate_clear_button: string;
roompass: string roompass: string;
} }
interface Server { interface Server {
id?: string id?: string;
url?: string url?: string;
address: string address: string;
port: number port: number;
} }
interface Room { interface Room {
id?: string id?: string;
title?: string title?: string;
server?: Server server?: Server;
private?: boolean private?: boolean;
options: Options; options: Options;
} }
interface Options { interface Options {
mode: number, mode: number;
rule: number, rule: number;
start_lp: number, start_lp: number;
start_hand: number, start_hand: number;
draw_count: number, draw_count: number;
enable_priority: boolean, enable_priority: boolean;
no_check_deck: boolean, no_check_deck: boolean;
no_shuffle_deck: boolean no_shuffle_deck: boolean;
lflist?: number; lflist?: number;
time_limit?: number time_limit?: number;
} }
interface Points { interface Points {
exp: number, exp: number;
exp_rank: number, exp_rank: number;
pt: number, pt: number;
arena_rank: number, arena_rank: number;
win: number, win: number;
lose: number, lose: number;
draw: number, draw: number;
all: number, all: number;
ratio: number ratio: number;
} }
interface YGOProData { interface YGOProData {
windbot: {[locale: string]: string[]} windbot: {[locale: string]: string[]};
} }
...@@ -109,7 +107,7 @@ export class YGOProComponent implements OnInit { ...@@ -109,7 +107,7 @@ export class YGOProComponent implements OnInit {
textfont: string[]; textfont: string[];
points: Points; points: Points;
windbot: string[]; //["琪露诺", "谜之剑士LV4", "复制植物", "尼亚"]; windbot: string[]; // ["琪露诺", "谜之剑士LV4", "复制植物", "尼亚"];
servers: Server[] = []; servers: Server[] = [];
...@@ -136,7 +134,8 @@ export class YGOProComponent implements OnInit { ...@@ -136,7 +134,8 @@ export class YGOProComponent implements OnInit {
matching: ISubscription | undefined; matching: ISubscription | undefined;
matching_arena: string | undefined; matching_arena: string | undefined;
constructor(private http: Http, private appsService: AppsService, private loginService: LoginService, private settingsService: SettingsService, private ref: ChangeDetectorRef) { constructor (private http: Http, private appsService: AppsService, private loginService: LoginService,
private settingsService: SettingsService, private ref: ChangeDetectorRef) {
switch (process.platform) { switch (process.platform) {
case 'darwin': case 'darwin':
this.numfont = ['/System/Library/Fonts/SFNSTextCondensed-Bold.otf']; this.numfont = ['/System/Library/Fonts/SFNSTextCondensed-Bold.otf'];
...@@ -144,7 +143,11 @@ export class YGOProComponent implements OnInit { ...@@ -144,7 +143,11 @@ export class YGOProComponent implements OnInit {
break; break;
case 'win32': case 'win32':
this.numfont = [path.join(process.env['SystemRoot'], 'Fonts', 'arialbd.ttf')]; this.numfont = [path.join(process.env['SystemRoot'], 'Fonts', 'arialbd.ttf')];
this.textfont = [path.join(process.env['SystemRoot'], 'Fonts', 'msyh.ttc'), path.join(process.env['SystemRoot'], 'Fonts', 'msyh.ttf'), path.join(process.env['SystemRoot'], 'Fonts', 'simsun.ttc')]; this.textfont = [
path.join(process.env['SystemRoot'], 'Fonts', 'msyh.ttc'),
path.join(process.env['SystemRoot'], 'Fonts', 'msyh.ttf'),
path.join(process.env['SystemRoot'], 'Fonts', 'simsun.ttc')
];
break; break;
} }
...@@ -155,27 +158,27 @@ export class YGOProComponent implements OnInit { ...@@ -155,27 +158,27 @@ export class YGOProComponent implements OnInit {
this.servers.push({ this.servers.push({
id: 'tiramisu', id: 'tiramisu',
url: 'wss://tiramisu.mycard.moe:7923', url: 'wss://tiramisu.mycard.moe:7923',
address: "112.124.105.11", address: '112.124.105.11',
port: 7911 port: 7911
}) });
} else { } else {
this.servers.push({ this.servers.push({
id: 'mercury-us-1', id: 'mercury-us-1',
url: 'wss://mercury-us-1.mycard.moe:7923', url: 'wss://mercury-us-1.mycard.moe:7923',
address: "104.237.154.173", address: '104.237.154.173',
port: 7911 port: 7911
}) });
} }
} }
async ngOnInit() { async ngOnInit () {
let locale: string; let locale: string;
if (this.settingsService.getLocale().startsWith('zh')) { if (this.settingsService.getLocale().startsWith('zh')) {
locale = 'zh-CN' locale = 'zh-CN';
} else { } else {
locale = 'en-US' locale = 'en-US';
} }
this.windbot = (<YGOProData>this.app.data).windbot[locale]; this.windbot = (<YGOProData>this.app.data).windbot[locale];
...@@ -188,25 +191,26 @@ export class YGOProComponent implements OnInit { ...@@ -188,25 +191,26 @@ export class YGOProComponent implements OnInit {
this.connections = this.servers.map((server) => { this.connections = this.servers.map((server) => {
let connection = new WebSocket(server.url!); let connection = new WebSocket(server.url!);
connection.onclose = () => { connection.onclose = () => {
this.rooms = this.rooms.filter(room => room.server != server) this.rooms = this.rooms.filter(room => room.server !== server);
}; };
connection.onmessage = (event) => { connection.onmessage = (event) => {
let message = JSON.parse(event.data); let message = JSON.parse(event.data);
//console.log(message)
switch (message.event) { switch (message.event) {
case 'init': case 'init':
this.rooms = this.rooms.filter(room => room.server != server).concat(message.data.map((room: Room) => Object.assign({server: server}, room))); this.rooms = this.rooms.filter(room => room.server !== server).concat(
message.data.map((room: Room) => Object.assign({server: server}, room))
);
break; break;
case 'create': case 'create':
this.rooms.push(Object.assign({server: server}, message.data)); this.rooms.push(Object.assign({server: server}, message.data));
break; break;
case 'update': case 'update':
Object.assign(this.rooms.find(room => room.server == server && room.id == message.data.id), message.data); Object.assign(this.rooms.find(room => room.server === server && room.id === message.data.id), message.data);
break; break;
case 'delete': case 'delete':
this.rooms.splice(this.rooms.findIndex(room => room.server == server && room.id == message.data), 1); this.rooms.splice(this.rooms.findIndex(room => room.server === server && room.id === message.data), 1);
} }
this.ref.detectChanges() this.ref.detectChanges();
}; };
return connection; return connection;
}); });
...@@ -222,11 +226,11 @@ export class YGOProComponent implements OnInit { ...@@ -222,11 +226,11 @@ export class YGOProComponent implements OnInit {
for (let connection of this.connections) { for (let connection of this.connections) {
connection.close(); connection.close();
} }
this.connections = [] this.connections = [];
}); });
} }
async refresh() { async refresh () {
let decks = await this.get_decks(); let decks = await this.get_decks();
this.decks = decks; this.decks = decks;
let system_conf = await this.load_system_conf(); let system_conf = await this.load_system_conf();
...@@ -240,25 +244,27 @@ export class YGOProComponent implements OnInit { ...@@ -240,25 +244,27 @@ export class YGOProComponent implements OnInit {
let params = new URLSearchParams(); let params = new URLSearchParams();
params.set('username', this.loginService.user.username); params.set('username', this.loginService.user.username);
try { try {
this.points = await this.http.get('https://mycard.moe/ygopro/api/user', {search: params}).map((response) => response.json()).toPromise() this.points = await this.http.get('https://mycard.moe/ygopro/api/user', {search: params})
.map((response) => response.json())
.toPromise();
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
}; };
get_decks(): Promise<string[]> { get_decks (): Promise<string[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.readdir(path.join(this.app.local!.path, 'deck'), (error, files) => { fs.readdir(path.join(this.app.local!.path, 'deck'), (error, files) => {
if (error) { if (error) {
resolve([]) resolve([]);
} else { } else {
resolve(files.filter(file => path.extname(file) == ".ydk").map(file => path.basename(file, '.ydk'))); resolve(files.filter(file => path.extname(file) === '.ydk').map(file => path.basename(file, '.ydk')));
} }
}) });
}) });
} }
async get_font(files: string[]): Promise<string | undefined> { async get_font (files: string[]): Promise<string | undefined> {
for (let file of files) { for (let file of files) {
let found = await new Promise((resolve) => fs.access(file, fs.constants.R_OK, error => resolve(!error))); let found = await new Promise((resolve) => fs.access(file, fs.constants.R_OK, error => resolve(!error)));
if (found) { if (found) {
...@@ -267,48 +273,52 @@ export class YGOProComponent implements OnInit { ...@@ -267,48 +273,52 @@ export class YGOProComponent implements OnInit {
} }
} }
async delete_deck(deck: string) { async delete_deck (deck: string) {
if (confirm('确认删除?')) { if (confirm('确认删除?')) {
await new Promise(resolve => fs.unlink(path.join(this.app.local!.path, 'deck', deck + '.ydk'), resolve)); await new Promise(resolve => fs.unlink(path.join(this.app.local!.path, 'deck', deck + '.ydk'), resolve));
return this.refresh() return this.refresh();
} }
} }
async fix_fonts(data: SystemConf) { async fix_fonts (data: SystemConf) {
if (!await this.get_font([data.numfont])) { if (!await this.get_font([data.numfont])) {
let font = await this.get_font(this.numfont); let font = await this.get_font(this.numfont);
if (font) { if (font) {
data['numfont'] = font data['numfont'] = font;
} }
} }
if (data.textfont == 'c:/windows/fonts/simsun.ttc 14' || !await this.get_font([data.textfont.split(' ', 2)[0]])) { if (data.textfont === 'c:/windows/fonts/simsun.ttc 14' || !await this.get_font([data.textfont.split(' ', 2)[0]])) {
let font = await this.get_font(this.textfont); let font = await this.get_font(this.textfont);
if (font) { if (font) {
data['textfont'] = `${font} 14` data['textfont'] = `${font} 14`;
} }
} }
}; };
load_system_conf(): Promise<SystemConf> { load_system_conf (): Promise<SystemConf> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.readFile(this.system_conf, {encoding: 'utf-8'}, (error, data) => { fs.readFile(this.system_conf, {encoding: 'utf-8'}, (error, data) => {
if (error) return reject(error); if (error) {
return reject(error);
}
resolve(ini.parse(data)); resolve(ini.parse(data));
}); });
}) });
}; };
save_system_conf(data: SystemConf) { save_system_conf (data: SystemConf) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.writeFile(this.system_conf, ini.unsafe(ini.stringify(data, <EncodeOptions>{whitespace: true})), (error) => { fs.writeFile(this.system_conf, ini.unsafe(ini.stringify(data, <EncodeOptions>{whitespace: true})), (error) => {
if (error) return reject(error); if (error) {
return reject(error);
}
resolve(data); resolve(data);
}); });
}) });
}; };
async join(name: string, server: Server) { async join (name: string, server: Server) {
let system_conf = await this.load_system_conf(); let system_conf = await this.load_system_conf();
await this.fix_fonts(system_conf); await this.fix_fonts(system_conf);
system_conf.lastdeck = this.current_deck; system_conf.lastdeck = this.current_deck;
...@@ -321,7 +331,7 @@ export class YGOProComponent implements OnInit { ...@@ -321,7 +331,7 @@ export class YGOProComponent implements OnInit {
return this.start_game(['-j']); return this.start_game(['-j']);
}; };
async edit_deck(deck: string) { async edit_deck (deck: string) {
let system_conf = await this.load_system_conf(); let system_conf = await this.load_system_conf();
await this.fix_fonts(system_conf); await this.fix_fonts(system_conf);
system_conf.lastdeck = deck; system_conf.lastdeck = deck;
...@@ -329,11 +339,11 @@ export class YGOProComponent implements OnInit { ...@@ -329,11 +339,11 @@ export class YGOProComponent implements OnInit {
return this.start_game(['-d']); return this.start_game(['-d']);
} }
join_windbot(name: string) { join_windbot (name: string) {
return this.join('AI#' + name, this.servers[0]) return this.join('AI#' + name, this.servers[0]);
} }
async start_game(args: string[]) { async start_game (args: string[]) {
let win = remote.getCurrentWindow(); let win = remote.getCurrentWindow();
win.minimize(); win.minimize();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
...@@ -343,53 +353,60 @@ export class YGOProComponent implements OnInit { ...@@ -343,53 +353,60 @@ export class YGOProComponent implements OnInit {
}); });
child.on('error', (error) => { child.on('error', (error) => {
reject(error); reject(error);
win.restore() win.restore();
}); });
child.on('exit', async(code, signal) => { child.on('exit', async (code, signal) => {
// error 触发之后还可能会触发exit,但是Promise只承认首次状态转移,因此这里无需重复判断是否已经error过。 // error 触发之后还可能会触发exit,但是Promise只承认首次状态转移,因此这里无需重复判断是否已经error过。
await this.refresh(); await this.refresh();
resolve(); resolve();
win.restore() win.restore();
}) });
}) });
}; };
create_room(room: Room) { create_room (room: Room) {
let options_buffer = new Buffer(6); let options_buffer = new Buffer(6);
// 建主密码 https://docs.google.com/document/d/1rvrCGIONua2KeRaYNjKBLqyG9uybs9ZI-AmzZKNftOI/edit // 建主密码 https://docs.google.com/document/d/1rvrCGIONua2KeRaYNjKBLqyG9uybs9ZI-AmzZKNftOI/edit
options_buffer.writeUInt8((room.private ? 2 : 1) << 4, 1); options_buffer.writeUInt8((room.private ? 2 : 1) << 4, 1);
options_buffer.writeUInt8(room.options.rule << 5 | room.options.mode << 3 | (room.options.enable_priority ? 1 << 2 : 0) | (room.options.no_check_deck ? 1 << 1 : 0) | (room.options.no_shuffle_deck ? 1 : 0), 2); options_buffer.writeUInt8(
room.options.rule << 5 |
room.options.mode << 3 |
(room.options.enable_priority ? 1 << 2 : 0) |
(room.options.no_check_deck ? 1 << 1 : 0) |
(room.options.no_shuffle_deck ? 1 : 0)
, 2);
options_buffer.writeUInt16LE(room.options.start_lp, 3); options_buffer.writeUInt16LE(room.options.start_lp, 3);
options_buffer.writeUInt8(room.options.start_hand << 4 | room.options.draw_count, 5); options_buffer.writeUInt8(room.options.start_hand << 4 | room.options.draw_count, 5);
let checksum = 0; let checksum = 0;
for (let i = 1; i < options_buffer.length; i++) { for (let i = 1; i < options_buffer.length; i++) {
checksum -= options_buffer.readUInt8(i) checksum -= options_buffer.readUInt8(i);
} }
options_buffer.writeUInt8(checksum & 0xFF, 0); options_buffer.writeUInt8(checksum & 0xFF, 0);
let secret = this.loginService.user.external_id % 65535 + 1; let secret = this.loginService.user.external_id % 65535 + 1;
for (let i = 0; i < options_buffer.length; i += 2) { for (let i = 0; i < options_buffer.length; i += 2) {
options_buffer.writeUInt16LE(options_buffer.readUInt16LE(i) ^ secret, i) options_buffer.writeUInt16LE(options_buffer.readUInt16LE(i) ^ secret, i);
} }
let password = options_buffer.toString('base64') + (room.title!).replace(/\s/, String.fromCharCode(0xFEFF)); let password = options_buffer.toString('base64') + (room.title!).replace(/\s/, String.fromCharCode(0xFEFF));
let room_id = crypto.createHash('md5').update(password + this.loginService.user.username).digest('base64').slice(0, 10).replace('+', '-').replace('/', '_'); // let room_id = crypto.createHash('md5').update(password + this.loginService.user.username).digest('base64')
// .slice(0, 10).replace('+', '-').replace('/', '_');
this.join(password, this.servers[0]); this.join(password, this.servers[0]);
} }
join_room(room: Room) { join_room (room: Room) {
let options_buffer = new Buffer(6); let options_buffer = new Buffer(6);
options_buffer.writeUInt8(3 << 4, 1); options_buffer.writeUInt8(3 << 4, 1);
let checksum = 0; let checksum = 0;
for (let i = 1; i < options_buffer.length; i++) { for (let i = 1; i < options_buffer.length; i++) {
checksum -= options_buffer.readUInt8(i) checksum -= options_buffer.readUInt8(i);
} }
options_buffer.writeUInt8(checksum & 0xFF, 0); options_buffer.writeUInt8(checksum & 0xFF, 0);
let secret = this.loginService.user.external_id % 65535 + 1; let secret = this.loginService.user.external_id % 65535 + 1;
for (let i = 0; i < options_buffer.length; i += 2) { for (let i = 0; i < options_buffer.length; i += 2) {
options_buffer.writeUInt16LE(options_buffer.readUInt16LE(i) ^ secret, i) options_buffer.writeUInt16LE(options_buffer.readUInt16LE(i) ^ secret, i);
} }
...@@ -398,12 +415,13 @@ export class YGOProComponent implements OnInit { ...@@ -398,12 +415,13 @@ export class YGOProComponent implements OnInit {
this.join(password, room.server!); this.join(password, room.server!);
} }
request_match(arena = 'entertain') { request_match (arena = 'entertain') {
let headers = new Headers(); let headers = new Headers();
headers.append("Authorization", "Basic " + new Buffer(this.loginService.user.username + ":" + this.loginService.user.external_id).toString('base64')); headers.append('Authorization',
'Basic ' + Buffer.from(this.loginService.user.username + ':' + this.loginService.user.external_id).toString('base64'));
let search = new URLSearchParams(); let search = new URLSearchParams();
search.set("arena", arena); search.set('arena', arena);
search.set("locale", this.settingsService.getLocale()); search.set('locale', this.settingsService.getLocale());
this.matching_arena = matching_arena = arena; this.matching_arena = matching_arena = arena;
this.matching = matching = this.http.post('https://api.mycard.moe/ygopro/match', null, { this.matching = matching = this.http.post('https://api.mycard.moe/ygopro/match', null, {
headers: headers, headers: headers,
...@@ -415,15 +433,15 @@ export class YGOProComponent implements OnInit { ...@@ -415,15 +433,15 @@ export class YGOProComponent implements OnInit {
port: data['port'] port: data['port']
}); });
}, (error) => { }, (error) => {
alert(`匹配失败\n${error}`) alert(`匹配失败\n${error}`);
}, () => { }, () => {
this.matching = matching = undefined; this.matching = matching = undefined;
this.matching_arena = matching_arena = undefined; this.matching_arena = matching_arena = undefined;
this.ref.detectChanges() this.ref.detectChanges();
}); });
} }
cancel_match() { cancel_match () {
this.matching!.unsubscribe(); this.matching!.unsubscribe();
this.matching = matching = undefined; this.matching = matching = undefined;
this.matching_arena = matching_arena = undefined; this.matching_arena = matching_arena = undefined;
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Candy - Chats are not dead yet</title>
<link rel="stylesheet" href="node_modules/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="node_modules/candy/libs.min.css"/>
<link rel="stylesheet" type="text/css" href="node_modules/candy/res/default.css"/>
<style>
body {
font-family: -apple-system, Arial, 'Source Sans Pro', "Microsoft YaHei", 'Microsoft JhengHei', "WenQuanYi Micro Hei", sans-serif;
}
/* Turn on custom 8px wide scrollbar */
::-webkit-scrollbar {
width: 8px; /* 1px wider than Lion. */
/* This is more usable for users trying to click it. */
background-color: rgba(0,0,0,0);
-webkit-border-radius: 100px;
}
/* hover effect for both scrollbar area, and scrollbar 'thumb' */
::-webkit-scrollbar:active {
background-color: rgba(0, 0, 0, 0.05);
}
/* The scrollbar 'thumb' ...that marque oval shape in a scrollbar */
::-webkit-scrollbar-thumb:vertical {
/* This is the EXACT color of Mac OS scrollbars.
Yes, I pulled out digital color meter */
background: rgba(0,0,0,0.1);
-webkit-border-radius: 100px;
}
::-webkit-scrollbar-thumb:vertical:active {
background: rgba(0,0,0,0.2); /* Some darker color when you click it */
-webkit-border-radius: 100px;
}
.scroll {
overflow-y: hidden;
}
.scroll:hover {
overflow-y: auto;
}
</style>
<script>delete module.exports</script>
<script type="text/javascript" src="node_modules/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="node_modules/candy/libs.min.js"></script>
<script type="text/javascript" src="node_modules/candy/candy.min.js"></script>
<!-- plugins -->
<!--<script type="text/javascript" src="node_modules/candy-shop/replies/candy.js"></script>-->
<!--<link rel="stylesheet" type="text/css" href="node_modules/candy-shop/replies/candy.css"/>-->
<script type="text/javascript" src="node_modules/candy-shop/notifyme/candy.js"></script>
<link rel="stylesheet" type="text/css" href="node_modules/candy-shop/notifyme/candy.css"/>
<script type="text/javascript" src="node_modules/candy-shop/namecomplete/candy.js"></script>
<link rel="stylesheet" type="text/css" href="node_modules/candy-shop/namecomplete/candy.css"/>
<script type="text/javascript" src="node_modules/candy-shop/modify-role/candy.js"></script>
<link rel="stylesheet" type="text/css" href="node_modules/candy-shop/modify-role/candy.css"/>
<!-- 内联图片插件不好用 -->
<!--<script type="text/javascript" src="node_modules/candy-shop/inline-images/candy.js"></script>-->
<!--<link rel="stylesheet" type="text/css" href="node_modules/candy-shop/inline-images/candy.css"/>-->
<script type="text/javascript" src="node_modules/candy-shop/me-does/candy.js"></script>
<script type="text/javascript" src="node_modules/candy-shop/notifications/candy.js"></script>
<script type="text/javascript" src="node_modules/candy-shop/refocus/candy.js"></script>
<!--<script type="text/javascript" src="node_modules/candy-shop/slash-commands/slash-commands.js"></script>-->
<!--正在输入那个插件不好用-->
<!--<script type="text/javascript" src="node_modules/candy-shop/typingnotifications/typingnotifications.js"></script>-->
<!--<link rel="stylesheet" type="text/css" href="node_modules/candy-shop/typingnotifications/typingnotifications.css" />-->
<style>
.message-pane-wrapper, .message-form-wrapper {
margin-right: 190px;
}
.roster-pane, #chat-toolbar{
width: 190px
}
</style>
<style>
#candy {
background-color: #f7f7f9;
}
#chat-tabs li {
box-shadow: 0 0 1px 1px #ccc;
}
#chat-tabs a {
color: #999;
background-color: #ececec;
}
#chat-tabs .active a.label {
background-color: white;
}
#chat-tabs .active .transition, #chat-tabs .transition {
background: none;
/*background-color: white;*/
}
.message-pane-wrapper{
background-color: white;
padding: 0;
}
.message-pane li {
border-bottom: none;
box-shadow: none;
padding: 0 5px;
}
.message-pane li>div {
padding: 0 0 0 150px;
line-height: 28px;
}
.message-pane .label {
margin-left: -150px;
width: 130px;
}
.roster-pane {
background-color: initial;
border-top: none;
box-shadow: none;
margin: 30px 0 32px 0;
}
.roster-pane .user {
border-bottom: none;
box-shadow: none;
color: black;
}
.roster-pane .user:hover {
background-color: #ebf3f8;
}
#chat-toolbar {
background-color: initial;
border-top: 1px solid #eee;
box-shadow: none;
}
.message-form-wrapper {
border-top: 1px solid #eee;
}
.roster-pane .label{
text-shadow: none
}
#candy {
border-top: 1px solid #eee;
box-shadow: inset 0 1px 2px white;
}
.usercount span {
background-color: initial;
color: #a7a7a7;
}
/*#chat-toolbar #emoticons-icon, #chat-toolbar .usercount {*/
/*background-image: initial;*/
/*}*/
/*#chat-toolbar #emoticons-icon::before, #chat-toolbar .usercount::before {*/
/*font-size: 16px;*/
/*}*/
</style>
</head>
<body>
<div id="candy"></div>
<script type="text/javascript">
const {remote, ipcRenderer} = require('electron');
// remote.getCurrentWebContents().openDevTools();
require('electron-cookies'); // https://github.com/hstove/electron-cookies
ipcRenderer.on('join', (event, message) => {
if (Candy.View.Pane.Chat.rooms[message]) {
Candy.View.Pane.Room.show(message);
} else {
Candy.Core.Action.Jabber.Room.Join(message);
}
});
// fix
Base64.encode = (data) => new Buffer(data).toString('base64');
Base64.decode = (data) => new Buffer(data, 'base64').toString();
// candy init
const params = new URLSearchParams(location.search);
Candy.View.Template.Login.form = '<form method="post" id="login-form" class="login-form">' + '<input type="hidden" id="nickname" name="nickname" value="' + params.get('nickname') + '"/>' + '{{#displayUsername}}<input type="hidden" id="username" name="username" value="' + params.get('jid') + '"/>' + '{{#displayDomain}} <span class="at-symbol">@</span> ' + '<select id="domain" name="domain">{{#domains}}<option value="{{domain}}">{{domain}}</option>{{/domains}}</select>' + "{{/displayDomain}}" + "{{/displayUsername}}" + '{{#presetJid}}<input type="hidden" id="username" name="username" value="{{presetJid}}"/>{{/presetJid}}' + '{{#displayPassword}}<input type="hidden" id="password" name="password" value="' + params.get('password') + '"/>{{/displayPassword}}' + '<input type="submit" class="button" value="{{_loginSubmit}}" /></form>';
// Candy.View.Template.Chat.toolbar = '<ul id="chat-toolbar">' + '<li id="emoticons-icon" class="fa fa-smile-o" data-tooltip="{{tooltipEmoticons}}"></li>' + '<li id="chat-sound-control" class="checked" data-tooltip="{{tooltipSound}}"></li>' + '<li class="checked" id="chat-statusmessage-control" data-tooltip="{{tooltipStatusmessage}}">' + '</li><li class="context" data-tooltip="{{tooltipAdministration}}"></li>' + '<li class="usercount fa fa-user-o" data-tooltip="{{tooltipUsercount}}">' + '<span id="chat-usercount"></span></li></ul>',
Candy.Util.setCookie('candy-nostatusmessages', '1', 365);
Candy.init('wss://chat.mycard.moe:5280/websocket', {
core: {
debug: false,
autojoin: params.get('autojoin') && [params.get('autojoin')],
resource: 'mycard-' + Math.random().toString().split('.')[1],
enableXHTML: true
},
view: {assets: 'node_modules/candy/res/', language: params.get('language')}
});
CandyShop.NotifyMe.init();
CandyShop.NameComplete.init();
CandyShop.ModifyRole.init();
CandyShop.MeDoes.init();
CandyShop.Notifications.init();
CandyShop.Refocus.init();
Candy.Core.connect(params.get('jid'), params.get('password'), params.get('nickname'));
</script>
</body>
</html>
...@@ -17,16 +17,12 @@ ...@@ -17,16 +17,12 @@
<script src="systemjs.config.js"></script> <script src="systemjs.config.js"></script>
<script>delete module.exports</script>
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="node_modules/tether/dist/js/tether.min.js"></script>
<script src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
<script> <script>
System.import('app').catch((error) => { System.import('app').catch((error) => {
$('#loading').hide(); document.getElementById('loading').setAttribute('hidden', 'hidden');
$('#failed').removeAttr('hidden'); document.getElementById('failed').removeAttribute('hidden');
$('#error').removeAttr('hidden').text(error); document.getElementById('error').removeAttribute('hidden');
document.getElementById('error').textContent = error;
}); });
</script> </script>
</head> </head>
......
...@@ -141,6 +141,7 @@ function createWindow() { ...@@ -141,6 +141,7 @@ function createWindow() {
width: 1024, width: 1024,
height: 640, height: 640,
frame: process.platform == 'darwin', frame: process.platform == 'darwin',
transparent: true,
titleBarStyle: process.platform == 'darwin' ? 'hidden' : null titleBarStyle: process.platform == 'darwin' ? 'hidden' : null
}); });
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
"repository": "github:mycard/mycard", "repository": "github:mycard/mycard",
"scripts": { "scripts": {
"start": "tsc && electron .", "start": "tsc && electron .",
"lint": "tslint ./app/*.ts -t verbose",
"pack": "tsc && build --dir", "pack": "tsc && build --dir",
"dist": "tsc && build", "dist": "tsc && build",
"build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js", "build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js",
...@@ -24,9 +25,14 @@ ...@@ -24,9 +25,14 @@
"@angular/platform-browser": "latest", "@angular/platform-browser": "latest",
"@angular/platform-browser-dynamic": "latest", "@angular/platform-browser-dynamic": "latest",
"@angular/router": "latest", "@angular/router": "latest",
"@types/bootstrap": "latest",
"@types/jquery": "latest",
"@types/tether": "latest",
"angular-in-memory-web-api": "latest", "angular-in-memory-web-api": "latest",
"aria2": "latest", "aria2": "latest",
"bootstrap": "next", "bootstrap": "next",
"candy": "https://github.com/mycard/candy/releases/download/v2.2.0/candy.tar.gz",
"candy-shop": "zh99998/candy-plugins#patch-3",
"core-js": "latest", "core-js": "latest",
"electron-auto-updater": "latest", "electron-auto-updater": "latest",
"electron-cookies": "latest", "electron-cookies": "latest",
...@@ -38,14 +44,12 @@ ...@@ -38,14 +44,12 @@
"jquery": "latest", "jquery": "latest",
"raw-socket": "latest", "raw-socket": "latest",
"reflect-metadata": "latest", "reflect-metadata": "latest",
"rxjs": "5.0.0-beta.12", "rxjs": "latest",
"systemjs": "mycard/systemjs#mycard", "systemjs": "mycard/systemjs#mycard",
"systemjs-plugin-text": "latest", "systemjs-plugin-text": "latest",
"tether": "latest", "tether": "latest",
"vue": "latest", "vue": "latest",
"zone.js": "^0.6.26", "zone.js": "latest"
"candy": "https://github.com/mycard/candy/releases/download/v2.2.0/candy.tar.gz",
"candy-shop": "zh99998/candy-plugins#patch-2"
}, },
"devDependencies": { "devDependencies": {
"@angular/compiler-cli": "latest", "@angular/compiler-cli": "latest",
...@@ -61,7 +65,8 @@ ...@@ -61,7 +65,8 @@
"rollup-plugin-commonjs": "latest", "rollup-plugin-commonjs": "latest",
"rollup-plugin-node-resolve": "latest", "rollup-plugin-node-resolve": "latest",
"rollup-plugin-uglify": "latest", "rollup-plugin-uglify": "latest",
"typescript": "latest" "typescript": "latest",
"tslint": "^3.15.1"
}, },
"build": { "build": {
"productName": "MyCard", "productName": "MyCard",
......
...@@ -12,10 +12,14 @@ mycard { ...@@ -12,10 +12,14 @@ mycard {
flex-direction: column; flex-direction: column;
} }
.darwin #window-buttons { .darwin #window-buttons, .darwin #border {
display: none; display: none;
} }
.darwin #navbar {
padding-top: 1rem !important;
}
#window-buttons > i { #window-buttons > i {
color: #a7a7a7; color: #a7a7a7;
font-size: 18px; font-size: 18px;
...@@ -42,17 +46,14 @@ mycard { ...@@ -42,17 +46,14 @@ mycard {
margin: 0 0.3rem; margin: 0 0.3rem;
} }
.darwin #navbar {
padding-left: 80px;
}
/* Turn on custom 8px wide scrollbar */ /* Turn on custom 8px wide scrollbar */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; /* 1px wider than Lion. */ width: 8px; /* 1px wider than Lion. */
/* This is more usable for users trying to click it. */ /* This is more usable for users trying to click it. */
background-color: rgba(0,0,0,0); background-color: rgba(0, 0, 0, 0);
-webkit-border-radius: 100px; -webkit-border-radius: 100px;
} }
/* hover effect for both scrollbar area, and scrollbar 'thumb' */ /* hover effect for both scrollbar area, and scrollbar 'thumb' */
::-webkit-scrollbar:active { ::-webkit-scrollbar:active {
background-color: rgba(0, 0, 0, 0.05); background-color: rgba(0, 0, 0, 0.05);
...@@ -62,11 +63,12 @@ mycard { ...@@ -62,11 +63,12 @@ mycard {
::-webkit-scrollbar-thumb:vertical { ::-webkit-scrollbar-thumb:vertical {
/* This is the EXACT color of Mac OS scrollbars. /* This is the EXACT color of Mac OS scrollbars.
Yes, I pulled out digital color meter */ Yes, I pulled out digital color meter */
background: rgba(0,0,0,0.1); background: rgba(0, 0, 0, 0.1);
-webkit-border-radius: 100px; -webkit-border-radius: 100px;
} }
::-webkit-scrollbar-thumb:vertical:active { ::-webkit-scrollbar-thumb:vertical:active {
background: rgba(0,0,0,0.2); /* Some darker color when you click it */ background: rgba(0, 0, 0, 0.2); /* Some darker color when you click it */
-webkit-border-radius: 100px; -webkit-border-radius: 100px;
} }
......
...@@ -72,7 +72,12 @@ System.config({ ...@@ -72,7 +72,12 @@ System.config({
"ini": "@node/ini", "ini": "@node/ini",
"mkdirp": "@node/mkdirp", "mkdirp": "@node/mkdirp",
"aria2": "@node/aria2", "aria2": "@node/aria2",
"electron-sudo": "@node/electron-sudo" "electron-sudo": "@node/electron-sudo",
"electron-cookies": "@node/electron-cookies",
'jquery': '@node/jquery',
'tether': '@node/tether',
'bootstrap': '@node/bootstrap'
}, },
// 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
packages: { packages: {
......
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
"variables-before-functions" "variables-before-functions"
], ],
"no-arg": true, "no-arg": true,
"no-bitwise": true,
"no-console": [ "no-console": [
true, true,
"debug", "debug",
...@@ -62,8 +61,9 @@ ...@@ -62,8 +61,9 @@
true, true,
"single" "single"
], ],
"radix": true, "semicolon": [
"semicolon": true, "always"
],
"triple-equals": [ "triple-equals": [
true, true,
"allow-null-check" "allow-null-check"
...@@ -88,4 +88,4 @@ ...@@ -88,4 +88,4 @@
"check-type" "check-type"
] ]
} }
} }
\ No newline at end of file
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