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

将 Candy 作为 Component 而不是 WebView

tslint
parent 648cf20c
<p>时隔多年,终于又跟</p>
<!--<h2>MyCard 招募公告</h2>-->
<h2>招人</h2>
<h3>前端工程师</h3>
<p>热爱,圈内优先</p>
<p>HTML5, CSS, JavaScript</p>
<p>至少会一个前端框架 Angular React Vue</p>
<p>坐标上海,全职,不远程,工资面议</p>
<!--<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>会 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
<!--<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>-->
/**
* Created by zh99998 on 16/9/2.
*/
import {Component} from "@angular/core";
import {Component} from '@angular/core';
@Component({
moduleId: module.id,
selector: 'about',
......
import {Component, OnInit, Input, ChangeDetectorRef} from "@angular/core";
import {AppsService} from "./apps.service";
import {InstallOption} from "./install-option";
import {SettingsService} from "./settings.sevices";
import {App} from "./app";
import {DownloadService} from "./download.service";
import {clipboard, remote} from "electron";
import * as path from "path";
import * as fs from "fs";
import {Component, OnInit, Input, ChangeDetectorRef} from '@angular/core';
import {AppsService} from './apps.service';
import {InstallOption} from './install-option';
import {SettingsService} from './settings.sevices';
import {App} from './app';
import {DownloadService} from './download.service';
import {clipboard, remote} from 'electron';
import * as path from 'path';
import * as fs from 'fs';
import * as $ from 'jquery';
declare const Notification: any;
declare const $: any;
@Component({
moduleId: module.id,
......@@ -37,17 +37,17 @@ export class AppDetailComponent implements OnInit {
let volume = 'A';
for (let i = 0; i < 26; i++) {
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) => {
if (!err) {
//判断是否已经存在Library
// 判断是否已经存在Library
if (this.libraries.every((library) => !library.startsWith(currentVolume))) {
this.availableLibraries.push(currentVolume);
}
}
resolve()
})
})
resolve();
});
});
}
}
......@@ -61,7 +61,8 @@ export class AppDetailComponent implements OnInit {
if (reference.isLanguage()) {
// 对于语言包,只有在语言包的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 {
this.referencesInstall[reference.id] = true;
}
......@@ -88,7 +89,7 @@ export class AppDetailComponent implements OnInit {
}
async uninstall(app: App) {
if (confirm("确认删除?")) {
if (confirm('确认删除?')) {
try {
await this.appsService.uninstall(app);
} catch (e) {
......@@ -105,35 +106,35 @@ export class AppDetailComponent implements OnInit {
for (let [id, install] of Object.entries(referencesInstall)) {
if (install) {
let reference = targetApp.references.get(id)!;
console.log("reference install ", id, targetApp, targetApp.references, reference);
console.log('reference install ', id, targetApp, targetApp.references, reference);
await this.appsService.install(reference, options);
}
}
} catch (e) {
console.error(e);
new Notification(targetApp.name, {body: "下载失败"});
new Notification(targetApp.name, {body: '下载失败'});
}
}
async selectLibrary() {
if (this.installOption.installLibrary.startsWith('create_')) {
let volume = this.installOption.installLibrary.slice(7);
let library = path.join(volume, "MyCardLibrary");
let library = path.join(volume, 'MyCardLibrary');
try {
await this.appsService.createDirectory(library);
this.installOption.installLibrary = library;
this.settingsService.addLibrary(library, true);
} catch (e) {
this.installOption.installLibrary = this.settingsService.getDefaultLibrary().path;
alert("无法创建指定目录");
alert('无法创建指定目录');
} finally {
let index = this.availableLibraries.findIndex((l) => {
return l === volume
return l === volume;
});
this.availableLibraries.splice(index, 1);
}
} 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;
}
......@@ -162,13 +163,13 @@ export class AppDetailComponent implements OnInit {
for (let [id, install] of Object.entries(referencesInstall)) {
if (install) {
let reference = targetApp.references.get(id)!;
console.log("reference install ", id, targetApp, targetApp.references, reference);
console.log('reference install ', id, targetApp, targetApp.references, reference);
await this.appsService.install(reference, option);
}
}
} catch (e) {
console.error(e);
new Notification(targetApp.name, {body: "导入失败"});
new Notification(targetApp.name, {body: '导入失败'});
}
}
......@@ -182,7 +183,7 @@ export class AppDetailComponent implements OnInit {
await this.appsService.update(mod, true);
}
} catch (e) {
new Notification(app.name, {body: "校验失败"});
new Notification(app.name, {body: '校验失败'});
console.error(e);
}
}
......@@ -194,10 +195,10 @@ export class AppDetailComponent implements OnInit {
async selectImport(app: App) {
let main = app.actions.get('main');
if (!main) {
return
return;
}
if (!main.execute) {
return
return;
}
let filename = main.execute.split('/')[0];
let extname = path.extname(filename).slice(1);
......@@ -206,12 +207,12 @@ export class AppDetailComponent implements OnInit {
let filePaths = await new Promise((resolve, reject) => {
remote.dialog.showOpenDialog({
filters: [{name: filename, extensions: [extname]}],
properties: ['openFile',]
}, resolve)
properties: ['openFile']
}, resolve);
});
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.
*/
export class AppLocal {
path: string;
version: string;
files: Map<string,string>;
action: Map<string,{execute: string, args: string[], env: {}, open: App}>;
files: Map<string, string>;
action: Map<string, {execute: string, args: string[], env: {}, open: App}>;
update(local: any) {
this.path = local.path;
this.version = local.version;
let files = new Map<string,string>();
let files = new Map<string, string>();
for (let filename of Object.keys(local.files)) {
files.set(filename, local.files[filename]);
}
......@@ -20,7 +20,7 @@ export class AppLocal {
toJSON() {
let t: any = {};
for (let [k,v] of this.files) {
for (let [k, v] of this.files) {
t[k] = v;
}
return {path: this.path, version: this.version, files: t};
......
import {AppLocal} from "./app-local";
import {AppLocal} from './app-local';
export enum Category {
game,
......@@ -24,7 +24,7 @@ export interface Action {
execute: string;
args: string[];
env: {};
open?: App
open?: App;
}
export class FileOptions {
sync: boolean;
......@@ -36,7 +36,7 @@ export class AppStatus {
total: number;
private _status: string;
get status(): string {
return this._status
return this._status;
}
set status(status: string) {
......@@ -52,56 +52,56 @@ export class App {
id: string;
name: string; // i18n
description: string; //i18n
description: string; // i18n
author: string; // English Only
homepage: string;
category: Category;
parent?: App;
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}`
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;
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}`;
}
static checksumUrl(app: App, platform: string, locale: string): string {
if (app.id === "ygopro") {
return `https://thief.mycard.moe/checksums/${app.id}-${platform}-${locale}/${app.version}`
} else if (app.id === "desmume") {
return `https://thief.mycard.moe/checksums/${app.id}-${platform}/${app.version}`
static checksumUrl (app: App, platform: string, locale: string): string {
if (app.id === 'ygopro') {
return `https://thief.mycard.moe/checksums/${app.id}-${platform}-${locale}/${app.version}`;
} else if (app.id === 'desmume') {
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 {
if (app.id === "ygopro") {
static updateUrl (app: App, platform: string, locale: string): string {
if (app.id === 'ygopro') {
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}/${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() {
return this.category == Category.module && this.tags.includes('language');
return this.category === Category.module && this.tags.includes('language');
}
reset() {
......@@ -111,31 +111,31 @@ export class App {
}
isInstalled(): boolean {
return this.status.status != 'init';
return this.status.status !== 'init';
}
isReady(): boolean {
return this.status.status == 'ready';
return this.status.status === 'ready';
}
isInstalling(): boolean {
return this.status.status == 'installing';
return this.status.status === 'installing';
}
isWaiting(): boolean {
return this.status.status == 'waiting';
return this.status.status === 'waiting';
}
isDownloading(): boolean {
return this.status.status === "downloading";
return this.status.status === 'downloading';
}
isUninstalling(): boolean {
return this.status.status === "uninstalling";
return this.status.status === 'uninstalling';
}
isUpdating(): boolean {
return this.status.status === "updating";
return this.status.status === 'updating';
}
runnable(): boolean {
......@@ -187,4 +187,4 @@ export class App {
return dependencies.every((dependency) => dependency.isReady());
}
}
\ No newline at end of file
}
This diff is collapsed.
/* 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.
*/
import {Injectable, NgZone, EventEmitter} from "@angular/core";
import {Http} from "@angular/http";
import {error} from "util";
import Timer = NodeJS.Timer;
const Logger = {
"error": (message: string) => {
console.error("DownloadService: ", message);
}
};
import {Injectable, NgZone, EventEmitter} from '@angular/core';
import {Http} from '@angular/http';
// import {error} from 'util';
// import Timer = NodeJS.Timer;
// const Logger = {
// 'error': (message: string) => {
// console.error('DownloadService: ', message);
// }
// };
const Aria2 = require('aria2');
const MAX_LIST_NUM = 1000;
......@@ -19,13 +19,13 @@ export class DownloadStatus {
completedLength: number;
downloadSpeed: number;
get downloadSpeedText(): string {
get downloadSpeedText (): string {
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));
return (this.downloadSpeed / 1024 ** currentUnit).toFixed(1) + " " + speedUnit[currentUnit];
return (this.downloadSpeed / 1024 ** currentUnit).toFixed(1) + ' ' + speedUnit[currentUnit];
}
return "";
return '';
};
gid: string;
......@@ -35,22 +35,22 @@ export class DownloadStatus {
errorCode: string;
errorMessage: string;
combine(...others: DownloadStatus[]): DownloadStatus {
combine (...others: DownloadStatus[]): DownloadStatus {
const priority = {
undefined: -1,
"": -1,
"active": 0,
"complete": 0,
"paused": 1,
"waiting": 1,
"removed": 2,
"error": 3
'': -1,
'active': 0,
'complete': 0,
'paused': 1,
'waiting': 1,
'removed': 2,
'error': 3
};
let status = Object.assign(new DownloadStatus(), this);
for (let o of others) {
if (priority[o.status] > priority[status.status]) {
status.status = o.status;
if (status.status === "error") {
if (status.status === 'error') {
status.errorCode = o.errorCode;
status.errorMessage = o.errorMessage;
}
......@@ -64,7 +64,7 @@ export class DownloadStatus {
}
// 0相等. 1不想等
compareTo(other: DownloadStatus): number {
compareTo (other: DownloadStatus): number {
if (this.status !== other.status ||
this.downloadSpeed !== other.downloadSpeed ||
this.completedLength !== other.completedLength ||
......@@ -75,7 +75,7 @@ export class DownloadStatus {
}
}
constructor(item ?: any) {
constructor (item ?: any) {
if (item) {
this.completedLength = parseInt(item.completedLength) || 0;
this.downloadSpeed = parseInt(item.downloadSpeed) || 0;
......@@ -99,11 +99,11 @@ export class DownloadService {
open = this.aria2.open();
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 waitList = await this.aria2.tellWaiting(0, MAX_LIST_NUM);
let stoppedList = await this.aria2.tellStopped(0, MAX_LIST_NUM);
......@@ -120,17 +120,17 @@ export class DownloadService {
this.updateEmitter.emit();
}
constructor(private ngZone: NgZone, private http: Http) {
ngZone.runOutsideAngular(async() => {
constructor (private ngZone: NgZone, private http: Http) {
ngZone.runOutsideAngular(async () => {
await this.open;
setInterval(async() => {
setInterval(async () => {
await this.refreshDownloadList();
}, ARIA2_INTERVAL);
})
});
}
private createId(): string {
function s4() {
private createId (): string {
function s4 () {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
......@@ -140,7 +140,7 @@ export class DownloadService {
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) => {
let gids = this.taskMap.get(id);
if (gids) {
......@@ -154,7 +154,7 @@ export class DownloadService {
gids!.map((value, index, array) => {
let s = this.downloadList.get(value);
if (!s) {
throw "Gid not exists";
throw 'Gid not exists';
}
return s;
})
......@@ -164,13 +164,13 @@ export class DownloadService {
if (!allStatus) {
allStatus = status;
} else {
if (allStatus.compareTo(status) != 0) {
if (allStatus.compareTo(status) !== 0) {
allStatus = status;
}
}
if (allStatus.status === "error") {
if (allStatus.status === 'error') {
throw `Download Error: code ${allStatus.errorCode}, message: ${allStatus.errorMessage}`;
} else if (allStatus.status === "complete") {
} else if (allStatus.status === 'complete') {
resolve();
subscription.unsubscribe();
} else {
......@@ -183,12 +183,12 @@ export class DownloadService {
}
});
} 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 files: string[] = [];
for (let gid of gids) {
......@@ -198,7 +198,7 @@ export class DownloadService {
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 gidList = await this.aria2.addMetalink(encodedMeta4, {dir: library});
let taskId = this.createId();
......@@ -208,7 +208,7 @@ export class DownloadService {
return taskId;
}
async addUri(url: string, destination: string): Promise<string> {
async addUri (url: string, destination: string): Promise<string> {
await this.open;
let taskId = this.createId();
let gid = await this.aria2.addUri([url], {dir: destination});
......@@ -216,10 +216,10 @@ export class DownloadService {
return taskId;
}
async pause(id: string): Promise<void> {
async pause (id: string): Promise<void> {
await this.open;
try {
await this.aria2.pause(id)
await this.aria2.pause(id);
} catch (e) {
}
......
import {TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID} from "@angular/core";
import {remote} from "electron";
import {TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID} from '@angular/core';
import {remote} from 'electron';
export async function getTranslationProviders(): Promise<Object[]> {
export async function getTranslationProviders (): Promise<Object[]> {
let locale = localStorage.getItem('locale');
if (!locale) {
locale = remote.app.getLocale();
......@@ -18,12 +18,12 @@ export async function getTranslationProviders(): Promise<Object[]> {
{provide: TRANSLATIONS, useValue: translations},
{provide: TRANSLATIONS_FORMAT, useValue: 'xlf'},
{provide: LOCALE_ID, useValue: locale}
]
];
} catch (error) {
return noProviders
return noProviders;
}
}
declare var System: any;
function getTranslationsWithSystemJs(file: string) {
declare const System: any;
function getTranslationsWithSystemJs (file: string) {
return System.import(file + '!text'); // relies on text plugin
}
import {App} from "./app";
import * as path from "path"
import {App} from './app';
import * as path from 'path';
/**
* Created by weijian on 2016/10/24.
*/
......@@ -9,17 +9,17 @@ export class InstallOption {
downloadFiles: string[];
installLibrary: string;
get installDir(): string {
get installDir (): string {
return path.join(this.installLibrary, this.app.id);
}
createShortcut: boolean;
createDesktopShortcut: boolean;
constructor(app: App, installLibrary = "", shortcut = false, desktopShortcut = false) {
constructor (app: App, installLibrary = '', shortcut = false, desktopShortcut = false) {
this.app = app;
this.createShortcut = shortcut;
this.createDesktopShortcut = desktopShortcut;
this.installLibrary = installLibrary;
}
}
\ No newline at end of file
}
:host {
display: flex;
height: 100%;
}
#right {
display: flex;
......@@ -12,10 +13,11 @@
flex-grow: 1;
}
#candy {
#candy-wrapper {
background-color: #444;
height: 230px;
flex-shrink: 0;
position: relative;
}
roster {
......@@ -117,15 +119,21 @@ span {
float: right;
}
.sidebar {
#nav-wrapper {
width: 190px;
height: 100%;
flex-shrink: 0;
background-color: #f7f7f9;
border-right: 1px solid #eee;
padding-left: 0;
padding-right: 0;
padding-top: 20px;
/*resize: horizontal;*/
position: relative;
}
nav {
height: 100%;
}
.sidebar .nav {
......@@ -181,4 +189,44 @@ input.search::-webkit-input-placeholder{
.icon {
width: 16px;
height: 16px;
}
nav::-webkit-resizer {
/*width: 100px;*/
/*height: 100px;*/
/*background-color: red;*/
border: 2px solid yellow;
background: blue;
box-shadow: 0 0 2px 5px red;
outline: 2px dashed green;
/*size does not work*/
display: block;
width: 150px !important;
height: 150px !important;
position: fixed;
}
.resize-wrapper {
position: relative;
}
.resize {
position: absolute;
}
.resize-right .resize {
width: 4px;
right: -2px;
top: 0;
bottom: 0;
cursor: col-resize;
}
.resize-top .resize {
height: 4px;
top: -2px;
left: 0;
right: 0;
cursor: row-resize;
}
\ No newline at end of file
<!-- Begin page content -->
<nav id="apps" *ngIf="apps" class="bg-faded sidebar scroll">
<div id="search" class="input-group">
<i class="fa fa-search input-group-addon search" id="basic-addon1"></i>
<input type="text" class="form-control search" placeholder="搜索游戏" aria-describedby="basic-addon1">
</div>
<div #nav id="nav-wrapper" class="resize-wrapper resize-right">
<nav id="apps" *ngIf="apps" class="bg-faded sidebar scroll">
<div id="search" class="input-group">
<i class="fa fa-search input-group-addon search" id="basic-addon1"></i>
<input type="text" class="form-control search" placeholder="搜索游戏" aria-describedby="basic-addon1">
</div>
<span i18n *ngIf="grouped_apps.installed">已安装</span>
<ul *ngIf="grouped_apps.installed" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.installed" class="nav-item">
<a (click)="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">-->
{{app.name}}<i
*ngIf="!app.isReady() && !app.status.total" class="spin fa fa-circle-o-notch fa-spin fa-fw"></i>
<div *ngIf="!app.isReady() && app.status.total" class="progress">
<div class="pie" [class.second-half]="app.status.progress/app.status.total>0.5">
<div class="left-side half-circle"
[style.transform]="'rotate('+(app.status.progress/app.status.total).toString()+'turn)'"></div>
<div class="right-side half-circle"></div>
<span i18n *ngIf="grouped_apps.installed">已安装</span>
<ul *ngIf="grouped_apps.installed" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.installed" class="nav-item">
<a (click)="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">-->{{app.name}}<i *ngIf="!app.isReady() && !app.status.total" class="spin fa fa-circle-o-notch fa-spin fa-fw"></i>
<div *ngIf="!app.isReady() && app.status.total" class="progress">
<div class="pie" [class.second-half]="app.status.progress/app.status.total>0.5">
<div class="left-side half-circle" [style.transform]="'rotate('+(app.status.progress/app.status.total).toString()+'turn)'"></div>
<div class="right-side half-circle"></div>
</div>
<div class="shadow"></div>
</div>
<div class="shadow"></div>
</div>
</a>
</li>
</ul>
<span i18n *ngIf="grouped_apps.recommend">推荐</span>
<ul *ngIf="grouped_apps.recommend" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.recommend" class="nav-item">
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
</li>
</ul>
<span i18n *ngIf="grouped_apps.mysterious">迷之物体</span>
<ul *ngIf="grouped_apps.mysterious" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.mysterious" class="nav-item">
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
</li>
</ul>
<span i18n *ngIf="grouped_apps.touhou">东方 Project</span>
<ul *ngIf="grouped_apps.touhou" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.touhou" class="nav-item">
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
</li>
</ul>
<span i18n *ngIf="grouped_apps.touhou_pc98">东方旧作</span>
<ul *ngIf="grouped_apps.touhou_pc98" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.touhou_pc98" class="nav-item">
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
</li>
</ul>
<span i18n *ngIf="grouped_apps.runtime_installed">已安装的运行库</span>
<ul *ngIf="grouped_apps.runtime_installed" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.runtime_installed" class="nav-item">
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
</li>
</ul>
</nav>
</a>
</li>
</ul>
<span i18n *ngIf="grouped_apps.recommend">推荐</span>
<ul *ngIf="grouped_apps.recommend" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.recommend" class="nav-item">
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
</li>
</ul>
<span i18n *ngIf="grouped_apps.mysterious">迷之物体</span>
<ul *ngIf="grouped_apps.mysterious" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.mysterious" class="nav-item">
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
</li>
</ul>
<span i18n *ngIf="grouped_apps.touhou">东方 Project</span>
<ul *ngIf="grouped_apps.touhou" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.touhou" class="nav-item">
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
</li>
</ul>
<span i18n *ngIf="grouped_apps.touhou_pc98">东方旧作</span>
<ul *ngIf="grouped_apps.touhou_pc98" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.touhou_pc98" class="nav-item">
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
</li>
</ul>
<span i18n *ngIf="grouped_apps.runtime_installed">已安装的运行库</span>
<ul *ngIf="grouped_apps.runtime_installed" class="nav nav-pills flex-column">
<li *ngFor="let app of grouped_apps.runtime_installed" class="nav-item">
<a (click)="chooseApp(app)" href="#" class="nav-link" [class.active]="app===currentApp">{{app.name}}</a>
</li>
</ul>
</nav>
<div class="resize" (mousedown)="mousedown($event)"></div>
</div>
<div id="right">
<div id="main">
<app-detail *ngIf="currentApp" [currentApp]="currentApp"></app-detail>
<roster class="scroll"></roster>
</div>
<div id="main">
<app-detail *ngIf="currentApp" [currentApp]="currentApp"></app-detail>
<roster class="scroll"></roster>
</div>
<webview *ngIf="currentApp" #candy id="candy" [src]="candy_url" (new-window)="openExternal($event.url)"
nodeintegration></webview>
<div id="candy-wrapper" class="resize-wrapper resize-top">
<div class="resize" (mousedown)="mousedown($event)"></div>
<candy *ngIf="currentApp" [currentApp]="currentApp"></candy>
</div>
</div>
/**
* Created by zh99998 on 16/9/2.
*/
import {Component, OnInit, ElementRef, ViewChild} from "@angular/core";
import {AppsService} from "./apps.service";
import {LoginService} from "./login.service";
import {App, Category} from "./app";
import {URLSearchParams} from "@angular/http";
import {shell} from "electron";
import {SettingsService} from "./settings.sevices";
import WebViewElement = Electron.WebViewElement;
import {Component, OnInit} from '@angular/core';
import {AppsService} from './apps.service';
import {LoginService} from './login.service';
import {App, Category} from './app';
import {shell} from 'electron';
import {SettingsService} from './settings.sevices';
@Component({
moduleId: module.id,
......@@ -18,60 +16,73 @@ import WebViewElement = Electron.WebViewElement;
})
export class LobbyComponent implements OnInit {
@ViewChild('candy')
candy?: ElementRef;
candy_url: URL;
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();
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();
for (let app of this.apps.values()) {
await this.appsService.update(app);
}
} else {
if (confirm("获取程序列表失败,是否重试?")) {
if (confirm('获取程序列表失败,是否重试?')) {
location.reload();
} else {
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.appsService.lastVisited = app;
if (this.candy && this.currentApp.conference) {
(<WebViewElement>this.candy.nativeElement).send('join', this.currentApp.conference + '@conference.mycard.moe');
}
}
get grouped_apps() {
let contains = ["game", "music", "book"].map((value) => Category[value]);
get grouped_apps () {
let contains = ['game', 'music', 'book'].map((value) => Category[value]);
let result = {runtime: []};
for (let app of this.apps.values()) {
let tag: string;
......@@ -90,14 +101,14 @@ export class LobbyComponent implements OnInit {
}
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);
}
}
/**
* Created by zh99998 on 16/9/2.
*/
import {Component} from "@angular/core";
import {LoginService} from "./login.service";
import * as crypto from "crypto";
import * as querystring from "querystring";
import * as url from "url";
import {shell} from "electron";
import {Component} from '@angular/core';
import {LoginService} from './login.service';
import * as crypto from 'crypto';
import * as querystring from 'querystring';
import * as url from 'url';
import {shell} from 'electron';
@Component({
moduleId: module.id,
......@@ -18,9 +18,10 @@ export class LoginComponent {
url: string;
return_sso_url = 'https://mycard.moe/login_callback'; // 这个url不会真的被使用,可以填写不存在的
constructor(private loginService: LoginService) {
constructor (private loginService: LoginService) {
let payload = new Buffer(querystring.stringify({
//nonce: nonce,
// nonce: nonce,
return_sso_url: this.return_sso_url
})).toString('base64');
......@@ -28,16 +29,14 @@ export class LoginComponent {
'sso': payload,
'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) {
let request = querystring.stringify({
'redirect': this.url
});
this.url = "https://ygobbs.com/logout?" + request;
this.url = 'https://ygobbs.com/logout?' + querystring.stringify({'redirect': this.url});
}
}
return_sso(return_url: string) {
return_sso (return_url: string) {
if (!return_url.startsWith(this.return_sso_url)) {
return;
}
......@@ -47,7 +46,7 @@ export class LoginComponent {
this.loginService.login(user);
}
openExternal(url: string) {
openExternal (url: string) {
shell.openExternal(url);
}
}
/**
* Created by zh99998 on 2016/10/25.
*/
import {Injectable} from "@angular/core";
import {Http} from "@angular/http";
import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
export interface User {
admin: boolean;
......@@ -20,7 +20,7 @@ export class LoginService {
logged_in = false;
logging_out = false;
constructor(private http: Http) {
constructor (private http: Http) {
let data = localStorage.getItem('login');
if (data) {
this.user = JSON.parse(data);
......@@ -28,16 +28,16 @@ export class LoginService {
}
}
login(user: User) {
login (user: User) {
this.user = user;
this.logged_in = true;
localStorage.setItem('login', JSON.stringify(user));
}
logout() {
logout () {
this.logging_out = true;
this.logged_in = false;
localStorage.removeItem('login');
}
}
\ No newline at end of file
}
import {MyCardNgFactory} from "../aot/app/mycard.module.ngfactory";
import {getTranslationProviders} from "./i18n-providers";
import {enableProdMode} from "@angular/core";
import {platformBrowser} from "@angular/platform-browser";
enableProdMode();
getTranslationProviders().then(providers => {
const options = {providers};
platformBrowser().bootstrapModuleFactory(MyCardNgFactory);
});
// import {MyCardNgFactory} from '../aot/app/mycard.module.ngfactory';
// import {getTranslationProviders} from './i18n-providers';
// import {enableProdMode} from '@angular/core';
// import {platformBrowser} from '@angular/platform-browser';
// enableProdMode();
//
// getTranslationProviders().then(providers => {
// const options = {providers};
// platformBrowser().bootstrapModuleFactory(MyCardNgFactory);
// });
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {getTranslationProviders} from "./i18n-providers";
import {MyCard} from "./mycard.module";
window['jQuery'] = require('jquery');
window['Tether'] = require('tether');
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 => {
const options = {providers};
platformBrowserDynamic().bootstrapModule(MyCard, options);
......
......@@ -6,7 +6,7 @@
</li>
<!--<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 *ngIf="loginService.logged_in" [ngClass]="{active: currentPage === 'lobby'}" class="nav-item">
......@@ -64,8 +64,8 @@
<label i18n for="locale" class="col-sm-2 col-form-label">语言</label>
<div class="col-sm-10">
<select class="form-control" id="locale" [(ngModel)]="locale" name="locale">
<option i18n value="en-US">英文</option>
<option i18n value="zh-CN">简体中文</option>
<option value="en-US">English</option>
<option value="zh-CN">简体中文</option>
</select>
</div>
</div>
......
import {Component, Renderer, ChangeDetectorRef, OnInit, ElementRef, ViewChild} from "@angular/core";
import {remote, shell} from "electron";
import {LoginService} from "./login.service";
import {SettingsService} from "./settings.sevices";
import {Component, Renderer, ChangeDetectorRef, OnInit, ElementRef, ViewChild} from '@angular/core';
import {remote, shell} from 'electron';
import {LoginService} from './login.service';
import {SettingsService} from './settings.sevices';
import * as $ from 'jquery';
const autoUpdater: Electron.AutoUpdater = remote.getGlobal('autoUpdater');
declare const $: any;
@Component({
moduleId: module.id,
......@@ -13,7 +13,7 @@ declare const $: any;
})
export class MyCardComponent implements OnInit {
currentPage: string = "lobby";
currentPage: string = 'lobby';
update_status: string | undefined = remote.getGlobal('update_status');
update_error: string | undefined;
......@@ -32,7 +32,9 @@ export class MyCardComponent implements OnInit {
locale: string;
ngOnInit() {
resizing: HTMLElement | null;
ngOnInit () {
this.update_elements = new Map(Object.entries({
'error': this.error,
'checking-for-update': this.checking_for_update,
......@@ -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) => {
// console.log(event);
// // Do something with 'event'
......@@ -70,20 +73,20 @@ export class MyCardComponent implements OnInit {
}
update_retry() {
autoUpdater.checkForUpdates()
update_retry () {
autoUpdater.checkForUpdates();
}
update_install() {
autoUpdater.quitAndInstall()
update_install () {
autoUpdater.quitAndInstall();
}
set_update_status(status: string) {
set_update_status (status: string) {
console.log('autoUpdater', status);
if (this.update_status) {
let element = this.update_elements.get(this.update_status);
if (element) {
$(element.nativeElement).tooltip('dispose')
$(element.nativeElement).tooltip('dispose');
}
}
this.update_status = status;
......@@ -91,19 +94,19 @@ export class MyCardComponent implements OnInit {
let element = this.update_elements.get(this.update_status);
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);
}
submit() {
if (this.locale != this.settingsService.getLocale()) {
submit () {
if (this.locale !== this.settingsService.getLocale()) {
localStorage.setItem(SettingsService.SETTING_LOCALE, this.locale);
remote.app.relaunch();
remote.app.quit()
remote.app.quit();
}
}
}
import {NgModule, NO_ERRORS_SCHEMA} from "@angular/core";
import {BrowserModule} from "@angular/platform-browser";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {HttpModule} from "@angular/http";
import {MyCardComponent} from "./mycard.component";
import {LoginComponent} from "./login.component";
import {StoreComponent} from "./store.component";
import {LobbyComponent} from "./lobby.component";
import {AppDetailComponent} from "./app-detail.component";
import {RosterComponent} from "./roster.component";
import {YGOProComponent} from "./ygopro.component";
import {AppsService} from "./apps.service";
import {SettingsService} from "./settings.sevices";
import {LoginService} from "./login.service";
import {DownloadService} from "./download.service";
import {AboutComponent} from "./about.component";
import {NgModule, NO_ERRORS_SCHEMA} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';
import {MyCardComponent} from './mycard.component';
import {LoginComponent} from './login.component';
import {StoreComponent} from './store.component';
import {LobbyComponent} from './lobby.component';
import {AppDetailComponent} from './app-detail.component';
import {RosterComponent} from './roster.component';
import {YGOProComponent} from './ygopro.component';
import {AppsService} from './apps.service';
import {SettingsService} from './settings.sevices';
import {LoginService} from './login.service';
import {DownloadService} from './download.service';
import {AboutComponent} from './about.component';
import {CandyComponent} from './candy.component';
@NgModule({
imports: [BrowserModule, FormsModule, ReactiveFormsModule, HttpModule],
declarations: [
MyCardComponent, LoginComponent, StoreComponent, LobbyComponent,
AppDetailComponent, RosterComponent, YGOProComponent, AboutComponent
AppDetailComponent, RosterComponent, YGOProComponent, AboutComponent, CandyComponent
],
bootstrap: [MyCardComponent],
providers: [
......@@ -28,4 +29,4 @@ import {AboutComponent} from "./about.component";
schemas: [NO_ERRORS_SCHEMA]
})
export class MyCard {
}
\ No newline at end of file
}
/**
* Created by zh99998 on 16/9/2.
*/
import {Component} from "@angular/core";
import {Component} from '@angular/core';
@Component({
moduleId: module.id,
selector: 'roster',
......
/**
* Created by weijian on 2016/10/24.
*/
import {Injectable} from "@angular/core";
import {remote} from "electron";
import * as path from "path";
import {Injectable} from '@angular/core';
import {remote} from 'electron';
import * as path from 'path';
export interface Library {
"default": boolean,path: string
'default': boolean;
path: string;
}
@Injectable()
export class SettingsService {
static SETTING_LIBRARY = "library";
static SETTING_LIBRARY = 'library';
static defaultLibraries = [
{
"default": true,
path: path.join(remote.app.getPath("appData"), "MyCardLibrary")
'default': true,
path: path.join(remote.app.getPath('appData'), 'MyCardLibrary')
},
];
static SETTING_LOCALE = 'locale';
static defaultLocale = remote.app.getLocale();
locale: string;
libraries: Library[];
getLibraries() {
getLibraries () {
if (!this.libraries) {
let data = localStorage.getItem(SettingsService.SETTING_LIBRARY);
if (!data) {
......@@ -36,7 +41,7 @@ export class SettingsService {
return this.libraries;
}
addLibrary(libraryPath: string, isDefault: boolean) {
addLibrary (libraryPath: string, isDefault: boolean) {
let libraries = this.getLibraries();
if (isDefault) {
......@@ -44,37 +49,33 @@ export class SettingsService {
l.default = false;
});
}
libraries.push({"default": isDefault, path: libraryPath});
libraries.push({'default': isDefault, path: libraryPath});
this.libraries = libraries;
localStorage.setItem(SettingsService.SETTING_LIBRARY, JSON.stringify(libraries));
}
setDefaultLibrary(library: Library) {
setDefaultLibrary (library: Library) {
let libraries = this.getLibraries();
libraries.forEach((l) => {
l.default = library.path == l.path;
l.default = library.path === l.path;
});
this.libraries = libraries;
localStorage.setItem(SettingsService.SETTING_LIBRARY, JSON.stringify(libraries));
}
getDefaultLibrary(): Library {
getDefaultLibrary (): Library {
if (!this.libraries) {
this.getLibraries()
this.getLibraries();
}
let result = this.libraries.find((item) => item.default === true);
if (result) {
return result
return result;
} else {
throw('no default library found')
throw('no default library found');
}
}
static SETTING_LOCALE = "locale";
static defaultLocale = remote.app.getLocale();
locale: string;
getLocale(): string {
getLocale (): string {
if (!this.locale) {
let locale = localStorage.getItem(SettingsService.SETTING_LOCALE);
if (!locale) {
......@@ -87,8 +88,8 @@ export class SettingsService {
return this.locale;
}
setLocale(locale: string) {
setLocale (locale: string) {
this.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> {
}
isSuperset(subset: Set<T>) {
for (var elem of subset) {
for (let elem of subset) {
if (!this.has(elem)) {
return false;
}
......@@ -20,16 +20,16 @@ export class ComparableSet<T> extends Set<T> {
}
union(setB: Set<T>): Set<T> {
var union = new Set(this);
for (var elem of setB) {
let union = new Set(this);
for (let elem of setB) {
union.add(elem);
}
return union;
}
intersection(setB: Set<T>): Set<T> {
var intersection = new Set();
for (var elem of setB) {
let intersection = new Set();
for (let elem of setB) {
if (this.has(elem)) {
intersection.add(elem);
}
......@@ -38,8 +38,8 @@ export class ComparableSet<T> extends Set<T> {
}
difference(setB: Set<T>): Set<T> {
var difference = new Set(this);
for (var elem of setB) {
let difference = new Set(this);
for (let elem of setB) {
difference.delete(elem);
}
return difference;
......
/**
* Created by zh99998 on 16/9/2.
*/
import {Component} from "@angular/core";
import {Component} from '@angular/core';
@Component({
moduleId: module.id,
selector: 'store',
......
This diff is collapsed.
<!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 @@
<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>
System.import('app').catch((error) => {
$('#loading').hide();
$('#failed').removeAttr('hidden');
$('#error').removeAttr('hidden').text(error);
document.getElementById('loading').setAttribute('hidden', 'hidden');
document.getElementById('failed').removeAttribute('hidden');
document.getElementById('error').removeAttribute('hidden');
document.getElementById('error').textContent = error;
});
</script>
</head>
......
......@@ -141,6 +141,7 @@ function createWindow() {
width: 1024,
height: 640,
frame: process.platform == 'darwin',
transparent: true,
titleBarStyle: process.platform == 'darwin' ? 'hidden' : null
});
......
This diff is collapsed.
......@@ -9,6 +9,7 @@
"repository": "github:mycard/mycard",
"scripts": {
"start": "tsc && electron .",
"lint": "tslint ./app/*.ts -t verbose",
"pack": "tsc && build --dir",
"dist": "tsc && build",
"build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js",
......@@ -24,9 +25,14 @@
"@angular/platform-browser": "latest",
"@angular/platform-browser-dynamic": "latest",
"@angular/router": "latest",
"@types/bootstrap": "latest",
"@types/jquery": "latest",
"@types/tether": "latest",
"angular-in-memory-web-api": "latest",
"aria2": "latest",
"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",
"electron-auto-updater": "latest",
"electron-cookies": "latest",
......@@ -38,14 +44,12 @@
"jquery": "latest",
"raw-socket": "latest",
"reflect-metadata": "latest",
"rxjs": "5.0.0-beta.12",
"rxjs": "latest",
"systemjs": "mycard/systemjs#mycard",
"systemjs-plugin-text": "latest",
"tether": "latest",
"vue": "latest",
"zone.js": "^0.6.26",
"candy": "https://github.com/mycard/candy/releases/download/v2.2.0/candy.tar.gz",
"candy-shop": "zh99998/candy-plugins#patch-2"
"zone.js": "latest"
},
"devDependencies": {
"@angular/compiler-cli": "latest",
......@@ -61,7 +65,8 @@
"rollup-plugin-commonjs": "latest",
"rollup-plugin-node-resolve": "latest",
"rollup-plugin-uglify": "latest",
"typescript": "latest"
"typescript": "latest",
"tslint": "^3.15.1"
},
"build": {
"productName": "MyCard",
......
......@@ -12,10 +12,14 @@ mycard {
flex-direction: column;
}
.darwin #window-buttons {
.darwin #window-buttons, .darwin #border {
display: none;
}
.darwin #navbar {
padding-top: 1rem !important;
}
#window-buttons > i {
color: #a7a7a7;
font-size: 18px;
......@@ -42,17 +46,14 @@ mycard {
margin: 0 0.3rem;
}
.darwin #navbar {
padding-left: 80px;
}
/* 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);
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);
......@@ -62,11 +63,12 @@ mycard {
::-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);
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 */
background: rgba(0, 0, 0, 0.2); /* Some darker color when you click it */
-webkit-border-radius: 100px;
}
......
......@@ -72,7 +72,12 @@ System.config({
"ini": "@node/ini",
"mkdirp": "@node/mkdirp",
"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: {
......
......@@ -25,7 +25,6 @@
"variables-before-functions"
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
......@@ -62,8 +61,9 @@
true,
"single"
],
"radix": true,
"semicolon": true,
"semicolon": [
"always"
],
"triple-equals": [
true,
"allow-null-check"
......@@ -88,4 +88,4 @@
"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