Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
M
mycard
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
syntax_j
mycard
Commits
ca9d897e
Commit
ca9d897e
authored
Nov 04, 2016
by
wudizhanche1000
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
海量更改
parent
85e50c1a
Changes
24
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
728 additions
and
466 deletions
+728
-466
app/app-detail.component.html
app/app-detail.component.html
+60
-39
app/app-detail.component.ts
app/app-detail.component.ts
+105
-67
app/app-local.ts
app/app-local.ts
+20
-1
app/app.ts
app/app.ts
+57
-27
app/apps.component.css
app/apps.component.css
+0
-25
app/apps.component.html
app/apps.component.html
+0
-21
app/apps.component.ts
app/apps.component.ts
+0
-44
app/apps.service.ts
app/apps.service.ts
+78
-123
app/download.service.ts
app/download.service.ts
+75
-9
app/install-config.ts
app/install-config.ts
+3
-6
app/install.service.ts
app/install.service.ts
+180
-0
app/lobby.component.css
app/lobby.component.css
+26
-0
app/lobby.component.html
app/lobby.component.html
+27
-2
app/lobby.component.ts
app/lobby.component.ts
+43
-4
app/mycard.module.ts
app/mycard.module.ts
+10
-3
app/settings.sevices.ts
app/settings.sevices.ts
+27
-4
app/ygopro.component.ts
app/ygopro.component.ts
+4
-2
apps.json
apps.json
+6
-1
aria2.js
aria2.js
+0
-36
aria2.js.map
aria2.js.map
+0
-10
aria2.ts
aria2.ts
+0
-40
index.js
index.js
+3
-1
package.json
package.json
+3
-0
tsconfig.json
tsconfig.json
+1
-1
No files found.
app/app-detail.component.html
View file @
ca9d897e
<h1>
{{appsService.currentApp.name}}
</h1>
<div
*ngIf=
"!isInstalled"
>
<div
*ngIf=
"!appsService.getDownloadInfo(appsService.currentApp)"
>
<button
type=
"button"
(click)=
"updateInstallConfig()"
class=
"btn btn-primary"
data-toggle=
"modal"
data-target=
"#install-modal"
>
安装
</button>
<button
type=
"button"
class=
"btn btn-secondary"
>
导入
</button>
<!--<button type="button" class="btn btn-secondary">正版代购</button>-->
</div>
<div
*ngIf=
"appsService.getDownloadInfo(appsService.currentApp)"
>
<div
*ngIf=
"appsService.getDownloadInfo(appsService.currentApp).status === 'install'"
>
正在安装...
</div>
<div
*ngIf=
"appsService.getDownloadInfo(appsService.currentApp).status === 'wait'"
>
等待安装...
</div>
<progress
*ngIf=
"appsService.getDownloadInfo(appsService.currentApp).status === 'active'"
class=
"progress progress-striped progress-animated"
value=
"{{appsService.getDownloadInfo(appsService.currentApp).progress}}"
max=
"100"
></progress>
</div>
<h1>
{{currentApp.name}}
</h1>
<div
*ngIf=
"currentApp.status.status === 'init'"
>
<button
type=
"button"
class=
"btn btn-primary"
data-toggle=
"modal"
(click)=
"updateInstallConfig()"
data-target=
"#install-modal"
>
安装
</button>
<button
type=
"button"
class=
"btn btn-secondary"
>
导入
</button>
<!--<button type="button" class="btn btn-secondary">正版代购</button>-->
</div>
<div
*ngIf=
"isInstalled && (appsService.currentApp.id != 'ygopro')"
>
<button
(click)=
"startApp(appsService.currentApp)"
type=
"button"
class=
"btn btn-primary"
>
运行
</button>
<div
*ngIf=
"currentApp.status.status === 'installing'"
>
正在安装...
</div>
<div
*ngIf=
"currentApp.status.status==='waiting'"
>
等待安装...
</div>
<progress
*ngIf=
"currentApp.status.status === 'downloading'"
class=
"progress progress-striped progress-animated"
value=
"{{currentApp.status.progress}}"
max=
"{{currentApp.status.total}}"
></progress>
<div
*ngIf=
"currentApp.status.status==='ready' && (currentApp.id != 'ygopro')"
>
<button
(click)=
"startApp(currentApp)"
type=
"button"
class=
"btn btn-primary"
>
运行
</button>
<button
type=
"button"
data-toggle=
"modal"
data-target=
"#settings-modal"
class=
"btn btn-secondary"
>
设置
</button>
<!--<button (click)="appsService.browse(
appsService.
currentApp)" type="button" class="btn btn-secondary">游览本地文件</button>-->
<!--<button (click)="appsService.browse(currentApp)" type="button" class="btn btn-secondary">游览本地文件</button>-->
<div
id=
"network"
*ngIf=
"
appsService.currentApp.network && appsService.
currentApp.network.protocol == 'maotama'"
>
<div
id=
"network"
*ngIf=
"
currentApp.network &&
currentApp.network.protocol == 'maotama'"
>
<div
class=
"input-group"
>
<input
*ngIf=
"appsService.connections.get(appsService.currentApp)"
[value]=
"appsService.connections.get(appsService.currentApp).address || 'Loading...'"
readonly
type=
"text"
class=
"form-control"
aria-label=
"Text input with dropdown button"
title=
"address"
>
<input
*ngIf=
"appsService.connections.get(currentApp)"
[value]=
"appsService.connections.get(currentApp).address || 'Loading...'"
readonly
type=
"text"
class=
"form-control"
aria-label=
"Text input with dropdown button"
title=
"address"
>
<div
class=
"input-group-btn"
>
<button
*ngIf=
"!appsService.connections.get(appsService.currentApp)"
(click)=
"appsService.network(appsService.currentApp, appsService.currentApp.network.servers[0])"
type=
"button"
class=
"btn btn-secondary"
>
<button
*ngIf=
"!appsService.connections.get(currentApp)"
(click)=
"appsService.network(currentApp, currentApp.network.servers[0])"
type=
"button"
class=
"btn btn-secondary"
>
联机
</button>
<button
*ngIf=
"appsService.connections.get(appsService.currentApp)"
(click)=
"copy(appsService.connections.get(appsService.currentApp).address)"
[disabled]=
"!appsService.connections.get(appsService.currentApp).address"
type=
"button"
class=
"btn btn-secondary"
>
<button
*ngIf=
"appsService.connections.get(currentApp)"
(click)=
"copy(appsService.connections.get(currentApp).address)"
[disabled]=
"!appsService.connections.get(currentApp).address"
type=
"button"
class=
"btn btn-secondary"
>
复制
</button>
<button
type=
"button"
class=
"btn btn-secondary dropdown-toggle dropdown-toggle-split"
data-toggle=
"dropdown"
aria-haspopup=
"true"
aria-expanded=
"false"
style=
"height: 38px;"
>
<button
type=
"button"
class=
"btn btn-secondary dropdown-toggle dropdown-toggle-split"
data-toggle=
"dropdown"
aria-haspopup=
"true"
aria-expanded=
"false"
style=
"height: 38px;"
>
<span
class=
"sr-only"
>
Toggle Dropdown
</span>
</button>
<div
class=
"dropdown-menu"
[class.dropdown-menu-right]=
"appsService.connections.get(appsService.currentApp)"
>
<div
class=
"dropdown-menu"
[class.dropdown-menu-right]=
"appsService.connections.get(currentApp)"
>
<h6
class=
"dropdown-header"
>
选择服务器
</h6>
<a
*ngFor=
"let server of appsService.currentApp.network.servers"
(click)=
"appsService.network(appsService.currentApp, server)"
class=
"dropdown-item"
href=
"#"
>
{{server.id}}
</a>
<a
*ngFor=
"let server of currentApp.network.servers"
(click)=
"appsService.network(currentApp, server)"
class=
"dropdown-item"
href=
"#"
>
{{server.id}}
</a>
</div>
</div>
</div>
</div>
</div>
<ygopro
*ngIf=
"
isInstalled && (appsService.currentApp.id == 'ygopro')
"
></ygopro>
<ygopro
*ngIf=
"
currentApp.status.status==='ready'&& (currentApp.id == 'ygopro')"
[app]=
"currentApp
"
></ygopro>
<br>
...
...
@@ -82,9 +92,13 @@
<button
type=
"button"
class=
"btn btn-danger btn-sm"
>
卸载
</button>
</td>
<td
*ngIf=
"!checkInstall(mod.id)"
>
<button
*ngIf=
"!appsService.getDownloadInfo(mod.id)"
(click)=
"install(mod.id)"
type=
"button"
class=
"btn btn-primary btn-sm"
>
安装
<button
*ngIf=
"!appsService.getDownloadInfo(mod.id)"
(click)=
"install(mod.id)"
type=
"button"
class=
"btn btn-primary btn-sm"
>
安装
</button>
<progress
*ngIf=
"appsService.getDownloadInfo(mod.id) && appsService.getDownloadInfo(mod.id).status === 'active'"
class=
"progress progress-striped progress-animated"
value=
"{{appsService.getDownloadInfo(mod.id).progress}}"
max=
"100"
></progress>
<progress
*ngIf=
"appsService.getDownloadInfo(mod.id) && appsService.getDownloadInfo(mod.id).status === 'active'"
class=
"progress progress-striped progress-animated"
value=
"{{appsService.getDownloadInfo(mod.id).progress}}"
max=
"100"
></progress>
<div
*ngIf=
"appsService.getDownloadInfo(mod.id) && appsService.getDownloadInfo(mod.id).status === 'wait'"
>
等待安装...
</div>
...
...
@@ -97,10 +111,12 @@
</table>
</div>
<div
*ngIf=
"
isInstalled
"
>
<div
*ngIf=
"
currentApp.isInstalled()
"
>
<h2>
本地文件
</h2>
<button
(click)=
"appsService.browse(appsService.currentApp)"
type=
"button"
class=
"btn btn-secondary"
>
浏览本地文件
</button>
<button
(click)=
"uninstall()"
type=
"button"
class=
"btn btn-secondary"
>
{{'uninstall'|translate}}
</button>
<button
(click)=
"appsService.browse(currentApp)"
type=
"button"
class=
"btn btn-secondary"
>
浏览本地文件
</button>
<button
(click)=
"uninstall(currentApp.id)"
type=
"button"
class=
"btn btn-secondary"
>
{{'uninstall'|translate}}
</button>
</div>
<div
class=
"modal fade"
id=
"settings-modal"
tabindex=
"-1"
>
...
...
@@ -133,7 +149,7 @@
</div>
<div
id=
"local-files"
role=
"tabpanel"
class=
"tab-pane fade"
>
<div
class=
"list-inline"
>
<button
type=
"button"
[disabled]=
"!
isInstalled
"
(click)=
"uninstall()"
>
<button
type=
"button"
[disabled]=
"!
currentApp.isInstalled()
"
(click)=
"uninstall()"
>
{{'uninstall'|translate}}
</button>
<i
*ngIf=
"uninstalling"
class=
"fa fa-circle-o-notch fa-spin fa-3x fa-fw"
></i>
...
...
@@ -148,41 +164,46 @@
</div>
</div>
</div>
<!-- Modal -->
<div
class=
"modal fade"
id=
"install-modal"
tabindex=
"-1"
role=
"dialog"
aria-labelledby=
"myModalLabel"
aria-hidden=
"true"
*ngIf=
"installConfig"
>
<div
class=
"modal fade"
id=
"install-modal"
tabindex=
"-1"
role=
"dialog"
aria-labelledby=
"myModalLabel"
aria-hidden=
"true"
*ngIf=
"installConfig"
>
<div
class=
"modal-dialog"
role=
"document"
>
<form
id=
"install-form"
class=
"modal-content"
(ngSubmit)=
"install()"
#theForm
="
ngForm
"
>
<div
class=
"modal-header"
>
<button
type=
"button"
class=
"close"
data-dismiss=
"modal"
aria-label=
"Close"
>
<span
aria-hidden=
"true"
>
×
</span>
</button>
<h4
class=
"modal-title"
id=
"myModalLabel"
>
{{'install'|translate}} {{name}}
</h4>
<h4
class=
"modal-title"
id=
"myModalLabel"
>
{{'install'|translate}} {{
currentApp.
name}}
</h4>
</div>
<div
class=
"modal-body"
>
<p>
即将开始安装 {{name}}
</p>
<p>
即将开始安装 {{
currentApp.
name}}
</p>
<!-- 安装位置选择只在 windows 下存在, 为了方便调试暂时不加ngif -->
<h4>
{{'install_path'|translate}}
</h4>
<div
class=
"form-group"
>
<select
class=
"form-control"
name=
"installPath"
[(ngModel)]=
"installConfig.installPath"
title=
"path"
>
<option
*ngFor=
"let library of settingsService.getLibraries()"
value=
"{{library['path']}}"
>
{{ library['path']}}
<select
class=
"form-control"
name=
"installPath"
[(ngModel)]=
"installConfig.installLibrary"
title=
"path"
>
<option
*ngFor=
"let library of libraries"
value=
"{{library}}"
>
{{ library}}
</option>
</select>
</div>
<h4>
{{'shortcut'|translate}}
</h4>
<div
class=
"checkbox"
>
<label>
<input
type=
"checkbox"
name=
"application"
[(ngModel)]=
"installConfig.createShortcut"
>
{{'create_shortcut'|translate}}
<input
type=
"checkbox"
name=
"application"
[(ngModel)]=
"installConfig.createShortcut"
>
{{'create_shortcut'|translate}}
</label>
</div>
<div
class=
"checkbox"
>
<label>
<input
type=
"checkbox"
name=
"desktop"
[(ngModel)]=
"installConfig.createDesktopShortcut"
>
{{'create_desktop_shortcut'|translate}}
<input
type=
"checkbox"
name=
"desktop"
[(ngModel)]=
"installConfig.createDesktopShortcut"
>
{{'create_desktop_shortcut'|translate}}
</label>
</div>
<h4>
{{'additions'|translate}}
</h4>
<div
*ngFor=
"let reference of installConfig.references"
>
<label>
<input
type=
"checkbox"
[(ngModel)]=
"reference.id"
name=
"references"
value=
"{{reference.id}}"
>
{{reference.name}}
<input
type=
"checkbox"
[(ngModel)]=
"reference.install"
name=
"references"
value=
"{{reference.app.id}}"
>
{{reference.app.name}}
</label>
</div>
</div>
...
...
app/app-detail.component.ts
View file @
ca9d897e
import
{
Component
,
OnInit
}
from
"
@angular/core
"
;
import
{
Component
,
OnInit
,
Input
,
ChangeDetectorRef
}
from
"
@angular/core
"
;
import
{
AppsService
}
from
"
./apps.service
"
;
import
{
InstallConfig
}
from
"
./install-config
"
;
import
{
SettingsService
}
from
"
./settings.sevices
"
;
import
{
App
}
from
"
./app
"
;
import
{
DownloadService
}
from
"
./download.service
"
;
import
{
clipboard
,
remote
}
from
"
electron
"
;
import
{
clipboard
,
remote
,
ipcRenderer
}
from
"
electron
"
;
import
*
as
path
from
"
path
"
;
import
*
as
child_process
from
"
child_process
"
;
import
{
InstallService
}
from
"
./install.service
"
;
declare
var
Notification
;
declare
var
$
;
@
Component
({
selector
:
'
app-detail
'
,
templateUrl
:
'
app/app-detail.component.html
'
,
styleUrls
:
[
'
app/app-detail.component.css
'
],
providers
:
[
DownloadService
]
})
export
class
AppDetailComponent
implements
OnInit
{
@
Input
()
currentApp
:
App
;
platform
=
process
.
platform
;
installConfig
:
InstallConfig
;
constructor
(
private
appsService
:
AppsService
,
private
settingsService
:
SettingsService
,
private
downloadService
:
DownloadService
)
{
constructor
(
private
appsService
:
AppsService
,
private
settingsService
:
SettingsService
,
private
downloadService
:
DownloadService
,
private
installService
:
InstallService
,
private
ref
:
ChangeDetectorRef
)
{
}
ngOnInit
()
{
this
.
updateInstallConfig
();
// this.updateInstallConfig();
ipcRenderer
.
on
(
'
download-message-reply
'
,
(
event
,
arg
)
=>
{
console
.
log
(
arg
);
});
ipcRenderer
.
send
(
"
download-message
"
,
"
ping
"
)
}
updateInstallConfig
()
{
this
.
installConfig
=
this
.
appsService
.
getInstallConfig
(
this
.
appsService
.
currentApp
);
this
.
installConfig
.
installPath
=
this
.
settingsService
.
getDefaultLibrary
().
path
;
}
get
name
()
{
let
currentApp
=
this
.
appsService
.
currentApp
;
if
(
currentApp
)
{
return
currentApp
.
name
[
this
.
settingsService
.
getLocale
()];
this
.
installConfig
=
new
InstallConfig
(
this
.
currentApp
);
this
.
installConfig
.
installLibrary
=
this
.
settingsService
.
getDefaultLibrary
().
path
;
this
.
installConfig
.
references
=
[];
for
(
let
reference
of
this
.
currentApp
.
references
.
values
())
{
this
.
installConfig
.
references
.
push
(
new
InstallConfig
(
reference
))
}
return
"
Loading
"
;
};
get
isInstalled
()
{
let
currentApp
=
this
.
appsService
.
currentApp
;
return
!!
(
currentApp
.
local
&&
currentApp
.
local
.
path
);
}
get
libraries
():
string
[]
{
return
this
.
settingsService
.
getLibraries
().
map
((
item
)
=>
item
.
path
);
}
get
news
()
{
let
currentApp
=
this
.
appsService
.
currentApp
;
if
(
currentApp
)
{
return
currentApp
.
news
;
}
return
this
.
currentApp
.
news
;
}
get
friends
()
{
...
...
@@ -63,35 +63,36 @@ export class AppDetailComponent implements OnInit {
}
get
mods
()
{
let
contains
=
[
"
optional
"
,
"
language
"
,
"
emulator
"
];
let
currentApp
=
this
.
appsService
.
currentApp
;
if
(
currentApp
)
{
if
(
currentApp
.
references
[
process
.
platform
]
&&
currentApp
.
references
[
process
.
platform
].
length
>
0
)
{
let
refs
=
currentApp
.
references
[
process
.
platform
];
refs
=
refs
.
filter
((
ref
)
=>
{
return
contains
.
includes
(
ref
.
type
);
});
refs
=
refs
.
map
((
ref
)
=>
{
let
tmp
=
Object
.
create
(
ref
);
switch
(
tmp
.
type
)
{
case
"
optional
"
:
tmp
.
type
=
"
选项
"
;
break
;
case
"
language
"
:
tmp
.
type
=
"
语言
"
;
break
;
default
:
break
;
}
//console.log(tmp.type);
return
tmp
;
});
return
refs
;
//return this.currentApp.references[process.platform];
}
}
// let contains = ["optional", "language", "emulator"];
//
// let currentApp = this.appsService.currentApp;
// if (currentApp) {
// if (currentApp.references[process.platform] && currentApp.references[process.platform].length > 0) {
// let refs = currentApp.references[process.platform];
// refs = refs.filter((ref)=> {
// return contains.includes(ref.type);
// });
// refs = refs.map((ref)=> {
// let tmp = Object.create(ref);
// switch (tmp.type) {
// case "optional":
// tmp.type = "选项";
// break;
// case "language":
// tmp.type = "语言";
// break;
// default:
// break;
// }
// //console.log(tmp.type);
// return tmp;
// });
// return refs;
//return this.currentApp.references[process.platform];
// }
// }
return
[];
}
uninstalling
:
boolean
;
...
...
@@ -99,23 +100,64 @@ export class AppDetailComponent implements OnInit {
uninstall
(
id
:
string
)
{
if
(
confirm
(
"
确认删除?
"
))
{
this
.
uninstalling
=
true
;
this
.
appsService
.
uninstall
(
id
).
then
(()
=>
{
this
.
uninstalling
=
false
;
}
);
//
this.appsService.uninstall(id).then(()=> {
//
this.uninstalling = false;
//
}
//
);
}
}
install
()
{
async
install
()
{
$
(
'
#install-modal
'
).
modal
(
'
hide
'
);
this
.
appsService
.
download
();
let
currentApp
=
this
.
currentApp
;
let
options
=
this
.
installConfig
;
let
dependencies
=
currentApp
.
findDependencies
();
let
apps
=
dependencies
.
concat
(
currentApp
).
filter
((
app
)
=>!
app
.
isInstalled
());
for
(
let
reference
of
options
.
references
)
{
if
(
reference
.
install
)
{
apps
.
push
(
reference
.
app
);
apps
.
push
(...
reference
.
app
.
findDependencies
())
}
}
let
downloadPath
=
path
.
join
(
this
.
installConfig
.
installLibrary
,
"
downloading
"
);
try
{
let
downloadApps
=
await
this
.
downloadService
.
addUris
(
apps
,
downloadPath
);
this
.
downloadService
.
getProgress
(
currentApp
)
.
subscribe
((
progress
)
=>
{
currentApp
.
status
.
status
=
"
downloading
"
;
currentApp
.
status
.
progress
=
progress
.
progress
;
currentApp
.
status
.
total
=
progress
.
total
;
this
.
ref
.
detectChanges
();
},
(
error
)
=>
{
},
()
=>
{
currentApp
.
status
.
status
=
"
waiting
"
;
this
.
ref
.
detectChanges
();
});
await
Promise
.
all
(
downloadApps
.
map
((
app
)
=>
{
return
this
.
downloadService
.
getComplete
(
app
)
.
then
((
completeApp
:
App
)
=>
{
return
this
.
installService
.
add
(
completeApp
,
options
);
});
}));
currentApp
.
status
.
status
=
"
ready
"
;
}
catch
(
e
)
{
new
Notification
(
currentApp
.
name
,
{
body
:
"
下载失败
"
});
}
}
selectDir
()
{
let
dir
=
remote
.
dialog
.
showOpenDialog
({
properties
:
[
'
openFile
'
,
'
openDirectory
'
]});
console
.
log
(
dir
);
this
.
appsService
.
installConfig
.
installDir
=
dir
[
0
];
//
this.appsService.installConfig.installDir = dir[0];
return
dir
[
0
];
}
...
...
@@ -131,12 +173,8 @@ export class AppDetailComponent implements OnInit {
let
open
=
''
;
let
openApp
=
app
.
actions
.
get
(
"
main
"
).
open
;
if
(
openApp
)
{
if
(
this
.
isInstalled
)
{
open
=
path
.
join
(
openApp
.
local
.
path
,
openApp
.
actions
.
get
(
"
main
"
).
execute
);
args
.
push
(
execute
);
}
else
{
console
.
error
(
'
open app not found
'
);
}
open
=
path
.
join
(
openApp
.
local
.
path
,
openApp
.
actions
.
get
(
"
main
"
).
execute
);
args
.
push
(
execute
);
}
else
{
//没有需要通过open启动依赖,直接启动程序
open
=
execute
;
...
...
app/app-local.ts
View file @
ca9d897e
...
...
@@ -4,5 +4,24 @@
export
class
AppLocal
{
path
:
string
;
version
:
string
;
files
:
string
[];
files
:
Map
<
string
,
string
>
;
update
(
local
)
{
this
.
path
=
local
.
path
;
this
.
version
=
local
.
version
;
let
files
=
new
Map
<
string
,
string
>
();
for
(
let
filename
of
Object
.
keys
(
local
.
files
))
{
files
.
set
(
filename
,
local
.
files
[
filename
]);
}
this
.
files
=
files
;
}
toJSON
()
{
let
t
=
{};
for
(
let
[
k
,
v
]
of
this
.
files
)
{
t
[
k
]
=
v
;
}
return
{
path
:
this
.
path
,
version
:
this
.
version
,
files
:
t
};
}
}
app/app.ts
View file @
ca9d897e
import
{
AppLocal
}
from
"
./app-local
"
;
/*
export enum Reference_Type {
runtime, // directx
emulator, // wine, np2
dependency, //
optional, // fxtz
language,
host
}
*/
/*
export enum App_Category
{
game
,
music
,
book
,
runtime, // directx
emulator, // wine, np2
language
}
*/
export
enum
Category
{
game
,
// 游戏
music
,
// 音乐
book
,
// 图书
runtime
,
// 运行库
emulator
,
// 模拟器
language
,
// 语言包
expansion
,
// 资料片
module
,
// 创意工坊
}
// export enum Status
{
// downloading
,
// init
,
// installing
,
// ready,
// updating,
// uninstalling,
// waiting,
// }
export
class
AppStatus
{
progress
:
number
;
total
:
number
;
status
:
string
;
}
export
class
App
{
id
:
string
;
name
:
string
;
// i18n
_name
:
string
;
// i18n
get
name
()
{
return
this
.
_name
;
}
set
name
(
a
)
{
this
.
_name
=
a
;
}
description
:
string
;
//i18n
author
:
string
;
// English Only
homepage
:
string
;
category
:
string
;
category
:
Category
;
parent
:
App
;
actions
:
Map
<
string
,{
execute
:
string
,
args
:
string
[],
env
:
{},
open
:
App
}
>
;
references
:
Map
<
string
,
App
>
;
dependencies
:
Map
<
string
,
App
>
;
locales
:
string
[];
download
:
{[
platform
:
string
]:
string
}
;
// meta4 url
download
:
string
;
// meta4 url
news
:
{
title
:
string
,
url
:
string
,
image
:
string
}[];
network
:
any
;
tags
:
string
[];
version
:
{[
platform
:
string
]:
string
}
;
version
:
string
;
local
:
AppLocal
;
status
:
AppStatus
;
isInstalled
():
boolean
{
return
this
.
local
!==
undefined
;
}
constructor
(
app
)
{
this
.
id
=
app
.
id
;
...
...
@@ -46,7 +64,7 @@ export class App {
this
.
description
=
app
.
description
;
this
.
author
=
app
.
author
;
this
.
homepage
=
app
.
homepage
;
this
.
category
=
app
.
category
;
this
.
category
=
Category
[
app
.
category
as
string
]
;
this
.
actions
=
app
.
actions
;
this
.
dependencies
=
app
.
dependencies
;
this
.
parent
=
app
.
parent
;
...
...
@@ -57,8 +75,20 @@ export class App {
this
.
network
=
app
.
network
;
this
.
tags
=
app
.
tags
;
this
.
version
=
app
.
version
;
this
.
local
=
app
.
local
;
}
findDependencies
():
App
[]
{
if
(
this
.
dependencies
&&
this
.
dependencies
.
size
>
0
)
{
let
set
=
new
Set
();
for
(
let
dependency
of
this
.
dependencies
.
values
())
{
dependency
.
findDependencies
()
.
forEach
((
value
)
=>
{
set
.
add
(
value
);
});
set
.
add
(
dependency
);
}
return
Array
.
from
(
set
);
}
return
[];
}
}
app/apps.component.css
deleted
100644 → 0
View file @
85e50c1a
:host
{
/*background-color: darkslategray;*/
width
:
280px
;
flex-shrink
:
0
;
overflow-y
:
auto
;
background-color
:
#f5f5f5
;
border-right
:
1px
solid
#eee
;
}
a
{
display
:
block
;
padding
:
10px
20px
10px
20px
;
}
.active
{
background-color
:
#428bca
;
}
.active
>
a
{
color
:
#fff
;
}
span
{
margin-left
:
8px
;
}
\ No newline at end of file
app/apps.component.html
deleted
100644 → 0
View file @
85e50c1a
<span
*ngIf=
"grouped_apps.installed"
>
已安装
</span>
<ul
*ngIf=
"grouped_apps.installed"
class=
"nav nav-sidebar"
>
<li
*ngFor=
"let app of grouped_apps.installed"
[class.active]=
"app===appsService.currentApp"
>
<a
(click)=
"chooseApp(app)"
href=
"#"
>
{{app.name}}
</a>
</li>
</ul><span
*ngIf=
"grouped_apps.yugioh"
>
游戏王
</span>
<ul
*ngIf=
"grouped_apps.yugioh"
class=
"nav nav-sidebar"
>
<li
*ngFor=
"let app of grouped_apps.yugioh"
[class.active]=
"app===appsService.currentApp"
>
<a
(click)=
"chooseApp(app)"
href=
"#"
>
{{app.name}}
</a>
</li>
</ul><span
*ngIf=
"grouped_apps.touhou"
>
东方 Project
</span>
<ul
*ngIf=
"grouped_apps.touhou"
class=
"nav nav-sidebar"
>
<li
*ngFor=
"let app of grouped_apps.touhou"
[class.active]=
"app===appsService.currentApp"
>
<a
(click)=
"chooseApp(app)"
href=
"#"
>
{{app.name}}
</a>
</li>
</ul><span
*ngIf=
"grouped_apps.touhou_pc98"
>
东方旧作
</span>
<ul
*ngIf=
"grouped_apps.touhou_pc98"
class=
"nav nav-sidebar"
>
<li
*ngFor=
"let app of grouped_apps.touhou_pc98"
[class.active]=
"app===appsService.currentApp"
>
<a
(click)=
"chooseApp(app)"
href=
"#"
>
{{app.name}}
</a>
</li>
</ul>
\ No newline at end of file
app/apps.component.ts
deleted
100644 → 0
View file @
85e50c1a
import
{
Component
,
OnInit
}
from
"
@angular/core
"
;
import
{
AppsService
}
from
"
./apps.service
"
;
import
{
App
}
from
"
./app
"
;
@
Component
({
selector
:
'
apps
'
,
templateUrl
:
'
app/apps.component.html
'
,
styleUrls
:
[
'
app/apps.component.css
'
],
})
export
class
AppsComponent
implements
OnInit
{
constructor
(
private
appsService
:
AppsService
)
{
}
ngOnInit
()
{
}
chooseApp
(
app
:
App
)
{
this
.
appsService
.
currentApp
=
app
;
}
get
grouped_apps
()
{
let
contains
=
[
"
game
"
,
"
music
"
,
"
book
"
];
let
apps
=
Array
.
from
(
this
.
appsService
.
allApps
.
values
());
let
result
=
{};
for
(
let
app
of
apps
)
{
if
(
contains
.
includes
(
app
.
category
))
{
let
tag
;
if
(
app
.
local
)
{
tag
=
'
installed
'
;
}
else
{
tag
=
app
.
tags
[
0
];
}
if
(
!
result
[
tag
])
{
result
[
tag
]
=
[]
}
result
[
tag
].
push
(
app
)
}
}
//console.log(result)
return
result
}
}
\ No newline at end of file
app/apps.service.ts
View file @
ca9d897e
import
{
Injectable
,
ApplicationRef
}
from
"
@angular/core
"
;
import
{
Http
}
from
"
@angular/http
"
;
import
{
App
}
from
"
./app
"
;
import
{
App
,
AppStatus
}
from
"
./app
"
;
import
{
InstallConfig
}
from
"
./install-config
"
;
import
{
SettingsService
}
from
"
./settings.sevices
"
;
import
*
as
os
from
"
os
"
;
...
...
@@ -10,6 +10,9 @@ import * as readline from "readline";
import
*
as
mkdirp
from
"
mkdirp
"
;
import
*
as
child_process
from
"
child_process
"
;
import
{
remote
}
from
"
electron
"
;
import
"
rxjs/Rx
"
;
import
{
AppLocal
}
from
"
./app-local
"
;
const
Aria2
=
require
(
'
aria2
'
);
const
Sudo
=
require
(
'
electron-sudo
'
).
default
;
...
...
@@ -32,30 +35,13 @@ Sudo.prototype.fork = function (modulePath, args, options) {
@
Injectable
()
export
class
AppsService
{
installConfig
:
InstallConfig
;
private
_currentApp
:
App
;
get
currentApp
():
App
{
return
this
.
_currentApp
;
constructor
(
private
http
:
Http
,
private
settingsService
:
SettingsService
,
private
ref
:
ApplicationRef
,)
{
}
set
currentApp
(
app
:
App
)
{
this
.
_currentApp
=
app
;
}
constructor
(
private
http
:
Http
,
private
settingsService
:
SettingsService
,
private
ref
:
ApplicationRef
)
{
this
.
loadApps
(()
=>
{
if
(
this
.
data
.
size
>
0
)
{
this
.
currentApp
=
this
.
data
.
get
(
'
ygopro
'
);
}
});
}
private
data
:
Map
<
string
,
App
>
;
get
allApps
():
Map
<
string
,
App
>
{
return
this
.
data
;
}
// get allApps(): Map<string,App> {
// return this.data;
// }
//[{"id": "th01", "gid": "aria2gid", "status": "active/install/complete/wait", "progress": "0-100"}]
downloadsInfo
=
[];
...
...
@@ -96,11 +82,11 @@ export class AppsService {
let
tarObj
=
{
id
:
this
.
downloadsInfo
[
index
].
id
,
xzFile
:
res
.
files
[
0
].
path
,
installDir
:
this
.
installConfig
.
installPath
// installDir: this.installConfig.installLibrary
};
new
Promise
((
resolve
)
=>
{
let
refs
=
this
.
searchApp
(
this
.
downloadsInfo
[
index
].
id
).
references
;
console
.
log
(
refs
);
//
let refs = this.searchApp(this.downloadsInfo[index].id).references;
//
console.log(refs);
//[{"id": "th01", "wait":["wine", "dx"], resolve: resolve, tarObj: tarObj}]
let
waitObj
;
...
...
@@ -124,7 +110,7 @@ export class AppsService {
// }
// });
// }
//console.log("wait obj:", waitObj);
//
console.log("wait obj:", waitObj);
if
(
waitObj
)
{
this
.
waitInstallQueue
.
push
(
waitObj
);
...
...
@@ -181,40 +167,15 @@ export class AppsService {
return
dir
;
}
loadApps
(
callback
)
{
this
.
http
.
get
(
'
./apps.json
'
)
.
map
(
response
=>
{
let
apps
=
response
.
json
();
let
localAppData
=
JSON
.
parse
(
localStorage
.
getItem
(
"
localAppData
"
));
apps
=
apps
.
map
((
app
:
any
)
=>
{
if
(
localAppData
)
{
localAppData
.
map
((
v
)
=>
{
if
(
v
.
id
===
app
.
id
)
{
app
.
local
=
v
.
local
;
}
});
}
return
app
;
});
return
apps
;
}).
map
(
this
.
loadAppsList
)
.
subscribe
((
apps
)
=>
{
this
.
data
=
apps
;
if
(
typeof
(
callback
)
===
'
function
'
)
{
callback
();
}
loadApps
()
{
return
this
.
http
.
get
(
'
./apps.json
'
)
.
toPromise
()
.
then
((
response
)
=>
{
let
data
=
response
.
json
();
return
this
.
loadAppsList
(
data
);
});
}
getLocalString
(
app
:
App
,
tag
:
string
):
string
{
let
locale
=
this
.
settingsService
.
getLocale
();
let
value
=
app
[
tag
][
locale
];
if
(
!
value
)
{
value
=
app
[
tag
][
"
en-US
"
];
}
return
value
;
}
loadAppsList
=
(
data
:
any
):
Map
<
string
,
App
>
=>
{
let
apps
=
new
Map
<
string
,
App
>
();
let
locale
=
this
.
settingsService
.
getLocale
();
...
...
@@ -222,6 +183,17 @@ export class AppsService {
for
(
let
item
of
data
)
{
let
app
=
new
App
(
item
);
let
local
=
localStorage
.
getItem
(
app
.
id
);
if
(
local
)
{
app
.
local
=
new
AppLocal
();
app
.
local
.
update
(
JSON
.
parse
(
local
));
}
app
.
status
=
new
AppStatus
();
if
(
local
)
{
app
.
status
.
status
=
"
ready
"
;
}
else
{
app
.
status
.
status
=
"
init
"
;
}
// 去除无关语言
[
'
name
'
,
'
description
'
].
forEach
((
key
)
=>
{
...
...
@@ -233,7 +205,7 @@ export class AppsService {
});
// 去除平台无关的内容
[
'
actions
'
,
'
dependencies
'
,
'
references
'
,
'
download
'
].
forEach
((
key
)
=>
{
[
'
actions
'
,
'
dependencies
'
,
'
references
'
,
'
download
'
,
'
version
'
].
forEach
((
key
)
=>
{
if
(
app
[
key
])
{
if
(
app
[
key
][
platform
])
{
app
[
key
]
=
app
[
key
][
platform
];
...
...
@@ -265,31 +237,20 @@ export class AppsService {
let
value
=
app
[
key
];
if
(
value
)
{
if
(
Array
.
isArray
(
value
))
{
let
map
=
new
Map
<
string
,
App
>
();
value
.
forEach
((
appId
,
index
,
array
)
=>
{
array
[
index
]
=
apps
.
get
(
appId
);
})
map
.
set
(
appId
,
apps
.
get
(
appId
));
});
app
[
key
]
=
map
;
}
else
{
app
[
key
]
=
apps
.
get
(
value
);
}
}
});
}
console
.
log
(
apps
);
return
apps
;
};
searchApp
(
id
):
App
{
return
this
.
data
.
get
(
id
);
}
checkInstall
(
id
):
boolean
{
if
(
this
.
searchApp
(
id
))
{
if
(
this
.
searchApp
(
id
).
local
.
path
)
{
return
true
;
}
}
return
false
;
}
deleteFile
(
path
:
string
):
Promise
<
string
>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
...
...
@@ -308,44 +269,53 @@ export class AppsService {
})
}
saveAppLocal
(
app
:
App
,
appLocal
:
AppLocal
)
{
localStorage
.
setItem
(
app
.
id
,
JSON
.
stringify
(
appLocal
));
}
install
(
config
:
InstallConfig
)
{
let
app
=
config
.
app
;
}
uninstall
(
id
:
string
)
{
//let current = this;
if
(
this
.
checkInstall
(
id
))
{
let
files
:
string
[]
=
this
.
searchApp
(
id
).
local
.
files
.
sort
().
reverse
();
// 删除本目录
files
.
push
(
'
.
'
);
let
install_dir
=
this
.
searchApp
(
id
).
local
.
path
;
return
files
.
map
((
file
)
=>
()
=>
path
.
join
(
install_dir
,
file
)
)
.
reduce
((
promise
:
Promise
<
string
>
,
task
)
=>
promise
.
then
(
task
).
then
(
this
.
deleteFile
)
,
Promise
.
resolve
(
''
))
.
then
((
value
)
=>
{
this
.
searchApp
(
id
).
local
=
null
;
localStorage
.
setItem
(
"
localAppData
"
,
JSON
.
stringify
(
this
.
data
));
});
}
// //let current = this;
// if (this.checkInstall(id)) {
// let files: string[] = this.searchApp(id).local.files.sort().reverse();
// // 删除本目录
// files.push('.');
// let install_dir = this.searchApp(id).local.path;
// return files
// .map((file)=>
// ()=>path.join(install_dir, file)
// )
// .reduce((promise: Promise<string>, task)=>
// promise.then(task).then(this.deleteFile)
// , Promise.resolve(''))
// .then((value)=> {
// this.searchApp(id).local = null;
// localStorage.setItem("localAppData", JSON.stringify(this.data));
// return Promise.resolve()
// });
// }
}
download
()
{
let
id
=
this
.
currentApp
.
id
;
if
(
this
.
downloadsInfo
.
findIndex
((
v
)
=>
{
return
v
.
id
==
id
})
!==
-
1
)
{
console
.
log
(
"
this app is downloading
"
)
}
else
{
let
url
=
this
.
currentApp
.
download
;
this
.
aria2
.
addUri
([
url
],
{
'
dir
'
:
this
.
download_dir
},
(
error
,
gid
)
=>
{
console
.
log
(
error
,
gid
);
if
(
error
)
{
console
.
error
(
error
);
}
this
.
downloadsInfo
.
push
({
"
id
"
:
id
,
"
gid
"
:
gid
,
"
status
"
:
"
active
"
,
"
progress
"
:
0
});
});
}
//
let id = this.currentApp.id;
//
if (this.downloadsInfo.findIndex((v)=> {
//
return v.id == id
//
}) !== -1) {
//
console.log("this app is downloading")
//
} else {
//
let url = this.currentApp.download;
//
this.aria2.addUri([url], {'dir': this.download_dir}, (error, gid)=> {
//
console.log(error, gid);
//
if (error) {
//
console.error(error);
//
}
//
this.downloadsInfo.push({"id": id, "gid": gid, "status": "active", "progress": 0});
//
});
//
}
}
getDownloadInfo
(
id
)
{
...
...
@@ -358,21 +328,6 @@ export class AppsService {
}
getInstallConfig
(
app
:
App
):
InstallConfig
{
//let id = app.id;
this
.
installConfig
=
new
InstallConfig
(
app
);
let
platform
=
process
.
platform
;
let
references
:
InstallConfig
[]
=
[];
if
(
app
.
references
[
platform
])
{
// app.references[platform].forEach((item)=> {
// references.push();
// });
}
this
.
installConfig
.
references
=
references
;
return
this
.
installConfig
;
}
// tar
tarQueue
=
[];
isExtracting
=
false
;
...
...
@@ -476,8 +431,8 @@ export class AppsService {
this
.
downloadsInfo
[
downLoadsInfoIndex
].
status
=
"
complete
"
;
// 为了卸载时能重新显示安装条
this
.
downloadsInfo
.
splice
(
downLoadsInfoIndex
,
1
);
this
.
data
.
get
(
tarObj
.
id
).
local
=
appLocal
.
local
;
console
.
log
(
11111
,
this
.
data
.
get
(
tarObj
.
id
),
appLocal
);
//
this.data.get(tarObj.id).local = appLocal.local;
//
console.log(11111, this.data.get(tarObj.id), appLocal);
//[{"id": "th01", "wait":["wine", "dx"], resolve: resolve, tarObj: tarObj}]
this
.
waitInstallQueue
=
this
.
waitInstallQueue
.
map
((
waitObj
)
=>
{
...
...
app/download.service.ts
View file @
ca9d897e
...
...
@@ -2,24 +2,90 @@
* Created by weijian on 2016/10/26.
*/
import
{
Injectable
}
from
"
@angular/core
"
;
import
{
SettingsService
}
from
"
./settings.sevices
"
;
import
{
ipcRenderer
}
from
"
electron
"
;
import
{
Injectable
,
NgZone
}
from
"
@angular/core
"
;
import
{
Http
}
from
"
@angular/http
"
;
import
{
Observable
}
from
"
rxjs/Observable
"
;
import
{
EventEmitter
}
from
"
events
"
;
import
{
App
}
from
"
./app
"
;
const
Aria2
=
require
(
'
aria2
'
);
@
Injectable
()
export
class
DownloadService
{
aria2
=
new
Aria2
();
baseURL
=
'
http://thief.mycard.moe/metalinks/
'
appGidMap
=
new
Map
<
App
,
string
>
();
gidAppMap
=
new
Map
<
string
,
App
>
();
eventEmitter
=
new
EventEmitter
();
open
=
this
.
aria2
.
open
();
constructor
(
private
settingsService
:
SettingsService
)
{
ipcRenderer
.
send
(
"
download-message
"
,
"
123
"
);
constructor
(
private
ngZone
:
NgZone
,
private
http
:
Http
)
{
this
.
aria2
.
onDownloadComplete
=
(
gid
)
=>
{
let
app
=
this
.
gidAppMap
.
get
(
gid
.
gid
);
if
(
app
)
{
this
.
appGidMap
.
delete
(
app
);
this
.
gidAppMap
.
delete
(
gid
.
gid
);
this
.
eventEmitter
.
emit
(
app
.
id
,
'
complete
'
);
}
}
}
sendEvent
(
event
,
args
)
{
ipcRenderer
.
send
(
''
)
getComplete
(
app
:
App
):
Promise
<
App
>
{
if
(
this
.
appGidMap
.
has
(
app
))
{
return
new
Promise
((
resolve
,
reject
)
=>
{
this
.
eventEmitter
.
once
(
app
.
id
,
(
event
)
=>
{
resolve
(
app
);
})
});
}
}
listenEvent
()
{
console
.
log
(
ipcRenderer
);
getProgress
(
app
:
App
):
Observable
<
any
>
{
let
gid
=
this
.
appGidMap
.
get
(
app
);
return
Observable
.
create
((
observer
)
=>
{
let
interval
;
this
.
ngZone
.
runOutsideAngular
(()
=>
{
interval
=
setInterval
(()
=>
{
this
.
aria2
.
tellStatus
(
gid
).
then
((
status
:
any
)
=>
{
if
(
status
.
status
===
'
complete
'
)
{
observer
.
complete
();
}
else
if
(
status
.
status
===
"
active
"
)
{
observer
.
next
({
total
:
status
.
totalLength
,
progress
:
status
.
completedLength
})
}
else
if
(
status
.
status
===
"
error
"
)
{
observer
.
error
(
status
.
errorCode
)
}
});
},
1000
);
});
return
()
=>
{
clearInterval
(
interval
);
}
});
}
async
addUris
(
apps
:
App
[],
path
:
string
):
Promise
<
App
[]
>
{
let
tasks
=
[];
for
(
let
app
of
apps
)
{
let
task
=
await
this
.
addUri
(
app
,
path
);
tasks
.
push
(
task
);
}
return
tasks
;
}
async
addUri
(
app
:
App
,
path
:
string
):
Promise
<
App
>
{
let
id
=
app
.
id
;
await
this
.
open
;
if
(
this
.
appGidMap
.
has
(
app
))
{
return
app
;
}
else
{
let
meta4link
=
`
${
this
.
baseURL
}${
id
}
.meta4`
;
let
response
=
await
this
.
http
.
get
(
meta4link
).
toPromise
();
let
meta4
=
btoa
(
response
.
text
());
let
gid
=
(
await
this
.
aria2
.
addMetalink
(
meta4
,
{
dir
:
path
}))[
0
];
this
.
appGidMap
.
set
(
app
,
gid
);
this
.
gidAppMap
.
set
(
gid
,
app
);
return
app
;
}
}
}
app/install-config.ts
View file @
ca9d897e
...
...
@@ -6,21 +6,18 @@ import {App} from "./app";
export
class
InstallConfig
{
app
:
App
;
install
:
boolean
;
install
Path
:
string
;
install
Library
:
string
;
installDir
:
string
;
createShortcut
:
boolean
;
createDesktopShortcut
:
boolean
;
references
:
InstallConfig
[];
constructor
(
app
:
App
,
install
Path
=
"
.
"
,
installDir
=
""
,
install
=
true
,
shortcut
=
false
,
desktopShortcut
=
false
)
{
constructor
(
app
:
App
,
install
Library
=
"
"
,
installDir
=
""
,
install
=
true
,
shortcut
=
false
,
desktopShortcut
=
false
)
{
this
.
app
=
app
;
this
.
createShortcut
=
shortcut
;
this
.
createDesktopShortcut
=
desktopShortcut
;
this
.
install
=
install
;
this
.
installDir
=
installDir
;
}
updateChecked
()
{
this
.
installLibrary
=
installLibrary
;
}
}
\ No newline at end of file
app/install.service.ts
0 → 100644
View file @
ca9d897e
/**
* Created by weijian on 2016/11/2.
*/
import
{
Injectable
}
from
"
@angular/core
"
;
import
{
App
}
from
"
./app
"
;
import
{
InstallConfig
}
from
"
./install-config
"
;
import
*
as
path
from
"
path
"
;
import
*
as
child_process
from
"
child_process
"
;
import
*
as
mkdirp
from
"
mkdirp
"
;
import
*
as
readline
from
"
readline
"
;
import
*
as
fs
from
'
fs
'
;
import
{
EventEmitter
}
from
"
events
"
;
import
{
AppLocal
}
from
"
./app-local
"
;
import
{
Http
}
from
"
@angular/http
"
;
import
ReadableStream
=
NodeJS
.
ReadableStream
;
@
Injectable
()
export
class
InstallService
{
tarPath
:
string
;
installQueue
:
Map
<
App
,
InstallConfig
>
=
new
Map
();
eventEmitter
:
EventEmitter
=
new
EventEmitter
();
installingQueue
:
Set
<
App
>
=
new
Set
();
checksumUri
=
"
http://thief.mycard.moe/checksums/
"
;
constructor
(
private
http
:
Http
)
{
if
(
process
.
platform
===
"
win32
"
)
{
this
.
tarPath
=
path
.
join
(
process
.
resourcesPath
,
'
bin/tar.exe
'
);
}
else
{
this
.
tarPath
=
"
tar
"
}
}
createDirectory
(
dir
:
string
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
mkdirp
(
dir
,
resolve
);
})
}
getComplete
(
app
:
App
):
Promise
<
App
>
{
return
null
;
}
extract
(
file
:
string
,
destPath
:
string
)
{
return
new
Promise
((
resolve
,
reject
)
=>
{
let
tarProcess
=
child_process
.
spawn
(
this
.
tarPath
,
[
'
xvf
'
,
file
,
'
-C
'
,
destPath
]);
let
rl
=
readline
.
createInterface
({
input
:
<
ReadableStream
>
tarProcess
.
stderr
,
});
rl
.
on
(
'
line
'
,
(
input
)
=>
{
});
tarProcess
.
on
(
'
exit
'
,
(
code
)
=>
{
if
(
code
===
0
)
{
resolve
();
}
else
{
reject
(
code
);
}
})
});
}
async
postInstall
(
app
:
App
,
appPath
:
string
)
{
let
action
=
app
.
actions
.
get
(
'
install
'
);
if
(
action
)
{
let
env
=
Object
.
assign
({},
action
.
env
);
let
command
=
[];
command
.
push
(
path
.
join
(
appPath
,
action
.
execute
));
command
.
push
(...
action
.
args
);
let
open
=
action
.
open
;
if
(
open
)
{
let
openAction
=
open
.
actions
.
get
(
"
main
"
);
env
=
Object
.
assign
(
env
,
openAction
.
env
);
command
.
unshift
(...
openAction
.
args
);
command
.
unshift
(
path
.
join
(
open
.
local
.
path
,
openAction
.
execute
));
}
return
new
Promise
((
resolve
,
reject
)
=>
{
let
child
=
child_process
.
spawn
(
command
.
shift
(),
command
,
{
env
:
env
,
stdio
:
'
inherit
'
,
shell
:
true
,
});
child
.
on
(
'
error
'
,
(
error
)
=>
{
console
.
log
(
error
);
});
child
.
on
(
'
exit
'
,
(
code
)
=>
{
if
(
code
===
0
)
{
resolve
();
}
else
{
reject
();
}
})
})
}
}
saveAppLocal
(
app
:
App
)
{
if
(
app
.
local
)
{
let
a
=
JSON
.
stringify
(
app
.
local
)
console
.
log
(
a
);
localStorage
.
setItem
(
app
.
id
,
a
);
}
}
async
backupFiles
(
app
:
App
,
files
:
Iterable
<
string
>
)
{
let
backupPath
=
path
.
join
(
app
.
local
.
path
,
"
backup
"
);
await
this
.
createDirectory
(
backupPath
);
for
(
let
file
of
files
)
{
await
new
Promise
((
resolve
,
reject
)
=>
{
let
oldPath
=
path
.
join
(
app
.
local
.
path
,
file
);
let
newPath
=
path
.
join
(
backupPath
,
file
);
fs
.
rename
(
oldPath
,
newPath
,
resolve
);
});
}
}
async
doInstall
()
{
for
(
let
app
of
this
.
installQueue
.
keys
())
{
let
depInstalled
=
app
.
findDependencies
()
.
every
((
dependency
)
=>
dependency
.
isInstalled
());
if
(
depInstalled
&&
!
this
.
installingQueue
.
has
(
app
))
{
this
.
installingQueue
.
add
(
app
);
let
options
=
this
.
installQueue
.
get
(
app
);
let
checksumMap
:
Map
<
string
,
string
>
=
await
this
.
http
.
get
(
`
${
this
.
checksumUri
}${
app
.
id
}
`
)
.
map
((
response
)
=>
{
let
map
=
new
Map
<
string
,
string
>
();
for
(
let
line
of
response
.
text
().
split
(
'
\n
'
))
{
if
(
line
!==
""
)
{
let
[
checksum
,
filename
]
=
line
.
split
(
'
'
,
2
);
map
.
set
(
filename
,
checksum
);
}
}
return
map
;
}).
toPromise
();
let
packagePath
=
path
.
join
(
options
.
installLibrary
,
'
downloading
'
,
`
${
app
.
id
}
.tar.xz`
);
let
destPath
:
string
;
if
(
app
.
parent
)
{
let
differenceSet
=
new
Set
<
string
>
();
let
parentFilesMap
=
app
.
parent
.
local
.
files
;
for
(
let
key
of
checksumMap
.
keys
())
{
if
(
parentFilesMap
.
has
(
key
))
{
differenceSet
.
add
(
key
);
}
}
await
this
.
backupFiles
(
app
.
parent
,
differenceSet
);
destPath
=
app
.
parent
.
local
.
path
;
}
else
{
destPath
=
path
.
join
(
options
.
installLibrary
,
app
.
id
);
await
this
.
createDirectory
(
destPath
);
}
this
.
installQueue
.
delete
(
app
);
await
this
.
extract
(
packagePath
,
destPath
);
await
this
.
postInstall
(
app
,
destPath
);
let
local
=
new
AppLocal
();
local
.
path
=
destPath
;
local
.
files
=
checksumMap
;
local
.
version
=
app
.
version
;
app
.
local
=
local
;
this
.
saveAppLocal
(
app
);
this
.
installingQueue
.
delete
(
app
);
if
(
this
.
installQueue
.
size
>
0
)
{
await
this
.
doInstall
()
}
}
}
}
add
(
app
:
App
,
options
:
InstallConfig
)
{
if
(
!
this
.
installQueue
.
has
(
app
))
{
this
.
installQueue
.
set
(
app
,
options
);
if
(
!
app
.
isInstalled
())
{
this
.
doInstall
()
}
}
}
}
\ No newline at end of file
app/lobby.component.css
View file @
ca9d897e
...
...
@@ -12,4 +12,30 @@
background-color
:
#336699
;
height
:
236px
;
flex-shrink
:
0
;
}
#apps
{
/*background-color: darkslategray;*/
width
:
280px
;
flex-shrink
:
0
;
overflow-y
:
auto
;
background-color
:
#f5f5f5
;
border-right
:
1px
solid
#eee
;
}
a
{
display
:
block
;
padding
:
10px
20px
10px
20px
;
}
.active
{
background-color
:
#428bca
;
}
.active
>
a
{
color
:
#fff
;
}
span
{
margin-left
:
8px
;
}
\ No newline at end of file
app/lobby.component.html
View file @
ca9d897e
<!-- Begin page content -->
<div
id=
"main"
>
<apps
*ngIf=
"appsService.allApps"
></apps>
<app-detail
*ngIf=
"appsService._currentApp"
></app-detail>
<div
id=
"apps"
*ngIf=
"apps"
>
<span
*ngIf=
"grouped_apps.installed"
>
已安装
</span>
<ul
*ngIf=
"grouped_apps.installed"
class=
"nav nav-sidebar"
>
<li
*ngFor=
"let app of grouped_apps.installed"
[class.active]=
"app===currentApp"
>
<a
(click)=
"chooseApp(app)"
href=
"#"
>
{{app.name}}
</a>
</li>
</ul>
<span
*ngIf=
"grouped_apps.yugioh"
>
游戏王
</span>
<ul
*ngIf=
"grouped_apps.yugioh"
class=
"nav nav-sidebar"
>
<li
*ngFor=
"let app of grouped_apps.yugioh"
[class.active]=
"app===currentApp"
>
<a
(click)=
"chooseApp(app)"
href=
"#"
>
{{app.name}}
</a>
</li>
</ul>
<span
*ngIf=
"grouped_apps.touhou"
>
东方 Project
</span>
<ul
*ngIf=
"grouped_apps.touhou"
class=
"nav nav-sidebar"
>
<li
*ngFor=
"let app of grouped_apps.touhou"
[class.active]=
"app===currentApp"
>
<a
(click)=
"chooseApp(app)"
href=
"#"
>
{{app.name}}
</a>
</li>
</ul>
<span
*ngIf=
"grouped_apps.touhou_pc98"
>
东方旧作
</span>
<ul
*ngIf=
"grouped_apps.touhou_pc98"
class=
"nav nav-sidebar"
>
<li
*ngFor=
"let app of grouped_apps.touhou_pc98"
[class.active]=
"app===currentApp"
>
<a
(click)=
"chooseApp(app)"
href=
"#"
>
{{app.name}}
</a>
</li>
</ul>
</div>
<app-detail
*ngIf=
"currentApp"
[currentApp]=
"currentApp"
></app-detail>
<roster></roster>
</div>
<webview
id=
"candy"
[src]=
"candy_url"
nodeintegration
></webview>
\ No newline at end of file
app/lobby.component.ts
View file @
ca9d897e
/**
* Created by zh99998 on 16/9/2.
*/
import
{
Component
}
from
"
@angular/core
"
;
import
{
Component
,
OnInit
}
from
"
@angular/core
"
;
import
{
AppsService
}
from
"
./apps.service
"
;
import
{
LoginService
}
from
"
./login.service
"
;
import
{
App
,
Category
}
from
"
./app
"
;
import
{
DownloadService
}
from
"
./download.service
"
;
@
Component
({
selector
:
'
lobby
'
,
templateUrl
:
'
app/lobby.component.html
'
,
styleUrls
:
[
'
app/lobby.component.css
'
],
})
export
class
LobbyComponent
{
candy_url
;
export
class
LobbyComponent
implements
OnInit
{
candy_url
:
string
;
currentApp
:
App
;
private
apps
:
Map
<
string
,
App
>
;
constructor
(
private
appsService
:
AppsService
,
private
loginService
:
LoginService
)
{
constructor
(
private
appsService
:
AppsService
,
private
loginService
:
LoginService
,
private
downloadService
:
DownloadService
)
{
this
.
candy_url
=
'
./candy/index.html?jid=
'
+
this
.
loginService
.
user
.
username
+
'
@mycard.moe&password=
'
+
this
.
loginService
.
user
.
external_id
+
'
&nickname=
'
+
this
.
loginService
.
user
.
username
+
'
&autojoin=ygopro_china_north@conference.mycard.moe
'
}
ngOnInit
()
{
this
.
appsService
.
loadApps
()
.
then
((
apps
)
=>
{
this
.
apps
=
apps
;
this
.
currentApp
=
this
.
apps
.
get
(
"
th06
"
);
})
}
chooseApp
(
app
:
App
)
{
this
.
currentApp
=
app
;
}
get
grouped_apps
()
{
let
contains
=
[
"
game
"
,
"
music
"
,
"
book
"
].
map
((
value
)
=>
Category
[
value
]);
let
result
=
{};
for
(
let
app
of
this
.
apps
.
values
())
{
if
(
contains
.
includes
(
app
.
category
))
{
let
tag
;
if
(
app
.
isInstalled
())
{
tag
=
'
installed
'
;
}
else
{
tag
=
app
.
tags
[
0
];
}
if
(
!
result
[
tag
])
{
result
[
tag
]
=
[]
}
result
[
tag
].
push
(
app
)
}
}
return
result
}
}
app/mycard.module.ts
View file @
ca9d897e
...
...
@@ -6,7 +6,6 @@ import {MyCardComponent} from "./mycard.component";
import
{
LoginComponent
}
from
"
./login.component
"
;
import
{
StoreComponent
}
from
"
./store.component
"
;
import
{
LobbyComponent
}
from
"
./lobby.component
"
;
import
{
AppsComponent
}
from
"
./apps.component
"
;
import
{
AppDetailComponent
}
from
"
./app-detail.component
"
;
import
{
RosterComponent
}
from
"
./roster.component
"
;
import
{
CommunityComponent
}
from
"
./community.component
"
;
...
...
@@ -15,12 +14,20 @@ import {AppsService} from "./apps.service";
import
{
TranslateModule
}
from
"
ng2-translate
"
;
import
{
SettingsService
}
from
"
./settings.sevices
"
;
import
{
LoginService
}
from
"
./login.service
"
;
import
{
DownloadService
}
from
"
./download.service
"
;
import
{
InstallService
}
from
"
./install.service
"
;
@
NgModule
({
imports
:
[
BrowserModule
,
FormsModule
,
ReactiveFormsModule
,
HttpModule
,
TranslateModule
.
forRoot
()],
declarations
:
[
MyCardComponent
,
LoginComponent
,
StoreComponent
,
LobbyComponent
,
CommunityComponent
,
AppsComponent
,
AppDetailComponent
,
RosterComponent
,
YGOProComponent
],
declarations
:
[
MyCardComponent
,
LoginComponent
,
StoreComponent
,
LobbyComponent
,
CommunityComponent
,
AppDetailComponent
,
RosterComponent
,
YGOProComponent
,
],
bootstrap
:
[
MyCardComponent
],
providers
:
[
AppsService
,
SettingsService
,
LoginService
],
providers
:
[
AppsService
,
SettingsService
,
LoginService
,
DownloadService
,
InstallService
],
schemas
:
[
NO_ERRORS_SCHEMA
]
})
export
class
MyCard
{
...
...
app/settings.sevices.ts
View file @
ca9d897e
...
...
@@ -3,16 +3,32 @@
*/
import
{
Injectable
}
from
"
@angular/core
"
;
import
{
remote
}
from
"
electron
"
;
import
*
as
path
from
"
path
"
;
@
Injectable
()
export
class
SettingsService
{
static
SETTING_LIBRARY
=
"
library
"
;
libraries
:
[{
"
default
"
:
boolean
,
path
:
string
}];
static
defaultLibraries
=
[
{
"
default
"
:
true
,
path
:
path
.
join
(
remote
.
app
.
getPath
(
"
appData
"
),
"
library
"
)
},
];
libraries
:
{
"
default
"
:
boolean
,
path
:
string
}[];
getLibraries
()
{
if
(
!
this
.
libraries
)
{
let
data
=
localStorage
.
getItem
(
SettingsService
.
SETTING_LIBRARY
);
this
.
libraries
=
JSON
.
parse
(
data
);
if
(
!
data
)
{
this
.
libraries
=
SettingsService
.
defaultLibraries
;
localStorage
.
setItem
(
SettingsService
.
SETTING_LIBRARY
,
JSON
.
stringify
(
SettingsService
.
defaultLibraries
));
}
else
{
this
.
libraries
=
JSON
.
parse
(
data
);
}
}
return
this
.
libraries
;
}
...
...
@@ -25,16 +41,23 @@ export class SettingsService {
}
static
SETTING_LOCALE
=
"
locale
"
;
static
defaultLocale
=
remote
.
app
.
getLocale
();
locale
:
string
;
getLocale
():
string
{
if
(
!
this
.
locale
)
{
this
.
locale
=
localStorage
.
getItem
(
SettingsService
.
SETTING_LOCALE
);
let
locale
=
localStorage
.
getItem
(
SettingsService
.
SETTING_LOCALE
);
if
(
!
locale
)
{
this
.
locale
=
SettingsService
.
defaultLocale
;
localStorage
.
setItem
(
SettingsService
.
SETTING_LOCALE
,
SettingsService
.
defaultLocale
);
}
else
{
this
.
locale
=
locale
;
}
}
return
this
.
locale
;
}
setLocal
(
locale
:
string
)
{
setLocal
e
(
locale
:
string
)
{
this
.
locale
=
locale
;
localStorage
.
setItem
(
SettingsService
.
SETTING_LOCALE
,
locale
);
}
...
...
app/ygopro.component.ts
View file @
ca9d897e
/**
* Created by zh99998 on 16/9/2.
*/
import
{
Component
,
OnInit
,
ChangeDetectorRef
}
from
"
@angular/core
"
;
import
{
Component
,
OnInit
,
ChangeDetectorRef
,
Input
}
from
"
@angular/core
"
;
import
{
AppsService
}
from
"
./apps.service
"
;
import
*
as
fs
from
"
fs
"
;
import
*
as
path
from
"
path
"
;
...
...
@@ -11,6 +11,7 @@ import {remote} from "electron";
import
*
as
ini
from
"
ini
"
;
import
{
EncodeOptions
}
from
"
ini
"
;
import
{
LoginService
}
from
"
./login.service
"
;
import
{
App
}
from
"
./app
"
;
declare
var
$
;
...
...
@@ -67,7 +68,8 @@ interface Room {
styleUrls
:
[
'
app/ygopro.component.css
'
],
})
export
class
YGOProComponent
implements
OnInit
{
app
=
this
.
appsService
.
searchApp
(
'
ygopro
'
);
@
Input
()
app
:
App
;
decks
:
string
[]
=
[];
current_deck
:
string
;
...
...
apps.json
View file @
ca9d897e
...
...
@@ -94,8 +94,11 @@
"language"
],
"dependencies"
:
{
"win32"
:
[],
"win32"
:
[
"th06"
],
"darwin"
:
[
"th06"
,
"wine"
]
},
...
...
@@ -1384,6 +1387,7 @@
}
},
"version"
:
{
"win32"
:
"1.06"
,
"darwin"
:
"1.06"
},
"download"
:
{
...
...
@@ -2188,6 +2192,7 @@
}
},
"version"
:
{
"win32"
:
"1.06"
,
"darwin"
:
"1.06"
},
"download"
:
{
...
...
aria2.js
deleted
100644 → 0
View file @
85e50c1a
"
use strict
"
;
/**
* Created by weijian on 2016/10/27.
*/
const
Rx
=
require
(
"
rxjs/Rx
"
);
const
{
ipcMain
}
=
require
(
'
electron
'
);
const
child_process_1
=
require
(
"
child_process
"
);
// import * as Aria2 from "aria2";
const
Aria2
=
require
(
"
aria2
"
);
let
a
=
(
createProcess
(
"
D:/Github/mycard/bin/aria2c.exe
"
,
[
'
--enable-rpc
'
,
'
--rpc-allow-origin-all
'
,
"
--continue
"
,
"
--split=10
"
,
"
--min-split-size=1M
"
,
"
--max-connection-per-server=10
"
]));
a
.
on
(
'
error
'
,
(
error
)
=>
{
console
.
log
(
error
);
});
// console.log(Aria2,2);
function
createProcess
(
aria2c_path
,
args
=
[])
{
return
child_process_1
.
spawn
(
aria2c_path
,
args
);
}
let
options
=
{
'
host
'
:
'
localhost
'
,
'
port
'
:
6800
,
'
secure
'
:
false
};
let
aria2
=
new
Aria2
(
options
);
aria2
.
onDownloadComplete
=
(
response
)
=>
{
console
.
log
(
response
);
};
let
open
=
aria2
.
open
();
function
addUri
(
uri
,
path
)
{
return
open
.
then
(()
=>
{
return
aria2
.
addUri
(
uri
,
{
'
dir
'
:
path
});
});
}
function
pause
(
gid
)
{
return
aria2
.
pause
(
gid
);
}
function
reportStatus
()
{
aria2
.
tellActive
();
}
//ipcMain.on()
//# sourceMappingURL=aria2.js.map
\ No newline at end of file
aria2.js.map
deleted
100644 → 0
View file @
85e50c1a
{
"version": 3,
"file": "aria2.js",
"sourceRoot": "",
"sources": [
"aria2.ts"
],
"names": [],
"mappings": ";AAAA;;GAEG;AACH,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AAC9B,MAAM,EAAC,OAAO,EAAC,GAAE,OAAO,CAAC,UAAU,CAAC,CAAC;AACrC,gCAAkC,eAAe,CAAC,CAAA;AAClD,kCAAkC;AAClC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE/B,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,iCAAiC,EAAE,CAAC,cAAc,EAAE,wBAAwB,EAAE,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,gCAAgC,CAAC,CAAC,CAAC,CAAA;AAC3L,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC;AACH,wBAAwB;AACxB,uBAAuB,WAAmB,EAAE,IAAI,GAAa,EAAE;IAC3D,MAAM,CAAC,qBAAK,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,IAAI,OAAO,GAAG,EAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAC,CAAA;AAClE,IAAI,KAAK,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;AAE/B,KAAK,CAAC,kBAAkB,GAAG,CAAC,QAAQ;IAChC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;AACxB,gBAAgB,GAAa,EAAE,IAAY;IACvC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,EAAC,KAAK,EAAE,IAAI,EAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACP,CAAC;AACD,eAAe,GAAW;IACtB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AAC3B,CAAC;AAED;IACI,KAAK,CAAC,UAAU,EAAE,CAAA;AACtB,CAAC;AAED,cAAc"
}
\ No newline at end of file
aria2.ts
deleted
100644 → 0
View file @
85e50c1a
/**
* Created by weijian on 2016/10/27.
*/
const
Rx
=
require
(
"
rxjs/Rx
"
);
const
{
ipcMain
}
=
require
(
'
electron
'
);
import
{
ChildProcess
,
spawn
}
from
"
child_process
"
;
// import * as Aria2 from "aria2";
const
Aria2
=
require
(
"
aria2
"
);
let
a
=
(
createProcess
(
"
D:/Github/mycard/bin/aria2c.exe
"
,
[
'
--enable-rpc
'
,
'
--rpc-allow-origin-all
'
,
"
--continue
"
,
"
--split=10
"
,
"
--min-split-size=1M
"
,
"
--max-connection-per-server=10
"
]))
a
.
on
(
'
error
'
,
(
error
)
=>
{
console
.
log
(
error
);
});
// console.log(Aria2,2);
function
createProcess
(
aria2c_path
:
string
,
args
:
string
[]
=
[]):
ChildProcess
{
return
spawn
(
aria2c_path
,
args
);
}
let
options
=
{
'
host
'
:
'
localhost
'
,
'
port
'
:
6800
,
'
secure
'
:
false
}
let
aria2
=
new
Aria2
(
options
);
aria2
.
onDownloadComplete
=
(
response
)
=>
{
console
.
log
(
response
);
};
let
open
=
aria2
.
open
();
function
addUri
(
uri
:
string
[],
path
:
string
)
{
return
open
.
then
(()
=>
{
return
aria2
.
addUri
(
uri
,
{
'
dir
'
:
path
});
});
}
function
pause
(
gid
:
string
):
Promise
<
string
>
{
return
aria2
.
pause
(
gid
)
}
function
reportStatus
()
{
aria2
.
tellActive
()
}
//ipcMain.on()
index.js
View file @
ca9d897e
...
...
@@ -103,7 +103,9 @@ function createAria2c() {
throw
'
unsupported platform
'
;
}
//--split=10 --min-split-size=1M --max-connection-per-server=10
let
aria2c
=
child_process
.
spawn
(
aria2c_path
,
[
'
--enable-rpc
'
,
'
--rpc-allow-origin-all
'
,
"
--continue
"
,
"
--split=10
"
,
"
--min-split-size=1M
"
,
"
--max-connection-per-server=10
"
]);
let
aria2c
=
child_process
.
spawn
(
aria2c_path
,
[
'
--enable-rpc
'
,
'
--rpc-allow-origin-all
'
,
"
--continue
"
,
"
--split=10
"
,
"
--min-split-size=1M
"
,
"
--max-connection-per-server=10
"
],
{
stdio
:
'
ignore
'
});
aria2c
.
on
(
'
data
'
,
(
data
)
=>
{
console
.
log
(
data
);
});
...
...
package.json
View file @
ca9d897e
...
...
@@ -58,6 +58,9 @@
"productName"
:
"MyCard"
,
"appId"
:
"com.mycard.mycard"
,
"category"
:
"public.app-category.social-networking"
,
"extraResources"
:
[
"bin"
],
"win"
:
{
"iconUrl"
:
"http://mycard.moe/logo.png"
,
"remoteReleases"
:
true
...
...
tsconfig.json
View file @
ca9d897e
...
...
@@ -3,7 +3,7 @@
"target"
:
"es6"
,
"lib"
:
[
"dom"
,
"es201
6
"
"es201
7
"
],
"module"
:
"commonjs"
,
"moduleResolution"
:
"node"
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment