This commit is contained in:
samnyan 2020-01-15 19:56:48 +09:00
commit 6a4ac09721
148 changed files with 18206 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

46
.gitignore vendored Normal file
View File

@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# AquaViewer
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.20.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

132
angular.json Normal file
View File

@ -0,0 +1,132 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"aqua-viewer": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/aqua-viewer",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/pink-bluegrey.css",
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "aqua-viewer:build"
},
"configurations": {
"production": {
"browserTarget": "aqua-viewer:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "aqua-viewer:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/pink-bluegrey.css",
"src/styles.css"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "aqua-viewer:serve"
},
"configurations": {
"production": {
"devServerTarget": "aqua-viewer:serve:production"
}
}
},
"deploy": {
"builder": "angular-cli-ghpages:deploy",
"options": {}
}
}
}
},
"defaultProject": "aqua-viewer"
}

12
browserslist Normal file
View File

@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

33
e2e/protractor.conf.js Normal file
View File

@ -0,0 +1,33 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const {SpecReporter} = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function () {
}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({spec: {displayStacktrace: true}}));
}
};

23
e2e/src/app.e2e-spec.ts Normal file
View File

@ -0,0 +1,23 @@
import {AppPage} from './app.po';
import {browser, logging} from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('aqua-viewer app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

11
e2e/src/app.po.ts Normal file
View File

@ -0,0 +1,11 @@
import {browser, by, element} from 'protractor';
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText() {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}

13
e2e/tsconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

32
karma.conf.js Normal file
View File

@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/aqua-viewer'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

13204
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

52
package.json Normal file
View File

@ -0,0 +1,52 @@
{
"name": "aqua-viewer",
"version": "0.0.1",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~8.2.14",
"@angular/cdk": "~8.2.3",
"@angular/common": "~8.2.14",
"@angular/compiler": "~8.2.14",
"@angular/core": "~8.2.14",
"@angular/flex-layout": "^8.0.0-beta.27",
"@angular/forms": "~8.2.14",
"@angular/material": "^8.2.3",
"@angular/platform-browser": "~8.2.14",
"@angular/platform-browser-dynamic": "~8.2.14",
"@angular/router": "~8.2.14",
"angular-cli-ghpages": "^0.6.2",
"hammerjs": "^2.0.8",
"rxjs": "~6.4.0",
"tslib": "^1.10.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.803.20",
"@angular/cli": "~8.3.20",
"@angular/compiler-cli": "~8.2.14",
"@angular/language-service": "~8.2.14",
"@types/node": "~8.9.4",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.5.3"
}
}

39
src/app/api.service.ts Normal file
View File

@ -0,0 +1,39 @@
import {HttpClient, HttpParams} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {AuthenticationService} from './auth/authentication.service';
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private http: HttpClient,
private authenticationService: AuthenticationService) {
}
get(path: string, params?: HttpParams) {
return this.http.get<any>(this.getHost() + path, {params});
}
post(path: string, data?: object, params?: HttpParams) {
return this.http.post<any>(this.getHost() + path, data, {params});
}
put(path: string, data?: object, params?: HttpParams) {
return this.http.put<any>(this.getHost() + path, data, {params});
}
delete(path: string, params?: HttpParams) {
return this.http.delete<any>(this.getHost() + path, {params});
}
getHost(): string {
return this.authenticationService.currentUserValue.apiServer + '/';
}
}
export class Resp {
status: string;
data: object;
}

View File

@ -0,0 +1,25 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {LoginComponent} from './login/login.component';
import {DashboardComponent} from './dashboard/dashboard.component';
import {AuthGuardService} from './auth/auth-guard.service';
const routes: Routes = [
{path: '', redirectTo: '/login', pathMatch: 'full'},
{path: 'login', component: LoginComponent},
{path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuardService]},
{path: 'diva', loadChildren: () => import('./sega/diva/diva.module').then(mod => mod.DivaModule), canLoad: [AuthGuardService]},
{
path: 'amazon',
loadChildren: () => import('./sega/chunithm/amazon/amazon.module').then(mod => mod.AmazonModule),
canLoad: [AuthGuardService]
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}

60
src/app/app.component.css Normal file
View File

@ -0,0 +1,60 @@
.container {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.is-mobile .toolbar {
position: fixed;
z-index: 2;
}
.sidenav {
flex: 1;
height: auto;
}
mat-sidenav {
min-width: 200px;
}
mat-toolbar {
z-index: 999;
}
.content {
background-origin: content-box;
height: auto;
overflow-y: visible;
width: 92%;
max-width: 1280px;
box-sizing: border-box;
padding: 30px;
margin: auto;
}
.bg {
transition-duration: .218s;
transition-property: background;
transition-timing-function: ease-in;
}
.dark {
background-color: #202124;
}
@media only screen and (max-width: 599px) {
.content {
background-origin: content-box;
height: auto;
overflow-y: visible;
width: 96%;
box-sizing: border-box;
padding: 15px;
margin: auto;
}
}

View File

@ -0,0 +1,39 @@
<div class="container">
<mat-toolbar class="toolbar mat-elevation-z6" color="primary">
<button (click)="drawer.toggle()" mat-icon-button>
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
</button>
<span>Aqua Viewer</span>
<span class="spacer-util"></span>
<button *ngIf="user" [matMenuTriggerFor]="userMenu" mat-icon-button>
<mat-icon aria-hidden="false" aria-label="User menu">face</mat-icon>
</button>
<mat-menu #userMenu="matMenu">
<button (click)="logout()" mat-menu-item>Logout</button>
</mat-menu>
</mat-toolbar>
<mat-sidenav-container class="sidenav">
<mat-sidenav #drawer [fixedInViewport]="true" [mode]="mobileQuery.matches ? 'over' : 'side'"
fixedTopGap="56">
<mat-nav-list *ngIf="user">
<a mat-list-item routerLink="/dashboard">Dashboard</a>
<mat-toolbar>Project DIVA AFT</mat-toolbar>
<a *ngFor="let item of divaMenus" mat-list-item routerLink="/{{item.url}}">{{item.name}}</a>
<mat-divider></mat-divider>
<mat-toolbar>CHUNITHM Amazon</mat-toolbar>
<a *ngFor="let item of amazonMenus" mat-list-item routerLink="/{{item.url}}">{{item.name}}</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<div class="content">
<router-outlet></router-outlet>
</div>
<app-message></app-message>
</mat-sidenav-content>
</mat-sidenav-container>
</div>

View File

@ -0,0 +1,31 @@
import {async, TestBed} from '@angular/core/testing';
import {AppComponent} from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'aqua-viewer'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('aqua-viewer');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('aqua-viewer app is running!');
});
});

97
src/app/app.component.ts Normal file
View File

@ -0,0 +1,97 @@
import {ChangeDetectorRef, Component, OnChanges, OnDestroy} from '@angular/core';
import {AuthenticationService, User} from './auth/authentication.service';
import {MediaMatcher} from '@angular/cdk/layout';
import {Router} from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnChanges, OnDestroy {
title = 'aqua-viewer';
user: User;
mobileQuery: MediaQueryList;
dark = 'dark';
divaMenus: Menu[] = [
{
id: 0,
name: 'Profile',
url: 'diva/profile'
},
{
id: 1,
name: 'PvRecord',
url: 'diva/record'
},
{
id: 2,
name: 'Recent Play',
url: 'diva/recent'
},
{
id: 3,
name: 'Setting',
url: 'diva/setting'
},
{
id: 4,
name: 'Management',
url: 'diva/management'
},
];
amazonMenus: Menu[] = [
{
id: 0,
name: 'Profile',
url: 'amazon/profile'
},
{
id: 1,
name: 'Rating',
url: 'amazon/rating'
},
{
id: 2,
name: 'Recent Play',
url: 'amazon/recent'
}
];
private _mobileQueryListener: () => void;
constructor(
changeDetectorRef: ChangeDetectorRef,
media: MediaMatcher,
private authenticationService: AuthenticationService,
private route: Router
) {
this.mobileQuery = media.matchMedia('(max-width: 600px)');
this._mobileQueryListener = () => changeDetectorRef.detectChanges();
this.mobileQuery.addListener(this._mobileQueryListener);
this.user = authenticationService.currentUserValue;
}
ngOnChanges(): void {
this.user = this.authenticationService.currentUserValue;
}
ngOnDestroy(): void {
this.mobileQuery.removeListener(this._mobileQueryListener);
}
logout() {
this.authenticationService.logout();
}
}
export class Menu {
id: number;
name: string;
url: string;
}

55
src/app/app.module.ts Normal file
View File

@ -0,0 +1,55 @@
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppComponent} from './app.component';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {AppRoutingModule} from './app-routing.module';
import {ReactiveFormsModule} from '@angular/forms';
import {
MatButtonModule,
MatIconModule,
MatListModule,
MatMenuModule,
MatNativeDateModule,
MatSelectModule,
MatSidenavModule,
MatToolbarModule
} from '@angular/material';
import {MessageModule} from './message/message.module';
import {DashboardModule} from './dashboard/dashboard.module';
import {LoginModule} from './login/login.module';
import {HttpClientModule} from '@angular/common/http';
import {DivaModule} from './sega/diva/diva.module';
import {AmazonModule} from './sega/chunithm/amazon/amazon.module';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
MessageModule,
AppRoutingModule,
DashboardModule,
LoginModule,
DivaModule,
AmazonModule,
ReactiveFormsModule,
MatButtonModule,
MatToolbarModule,
MatSidenavModule,
MatIconModule,
MatListModule,
MatSelectModule,
MatMenuModule,
MatNativeDateModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}

View File

@ -0,0 +1,22 @@
/* tslint:disable:no-unused-variable */
import {inject, TestBed} from '@angular/core/testing';
import {AuthGuardService} from './auth-guard.service';
import {HttpClientModule} from '@angular/common/http';
import {RouterTestingModule} from '@angular/router/testing';
describe('Service: AuthGuard', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientModule,
RouterTestingModule
],
providers: [AuthGuardService]
});
});
it('should ...', inject([AuthGuardService], (service: AuthGuardService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,37 @@
import {AuthenticationService} from './authentication.service';
import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, Router, RouterStateSnapshot} from '@angular/router';
import {Route} from '@angular/compiler/src/core';
@Injectable({
providedIn: 'root'
})
export class AuthGuardService {
constructor(
private router: Router,
private authenticationService: AuthenticationService
) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const currentUser = this.authenticationService.currentUserValue;
if (currentUser) {
return true;
}
this.router.navigate(['/login']);
return false;
}
canLoad(route: Route) {
const currentUser = this.authenticationService.currentUserValue;
if (currentUser) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}

View File

@ -0,0 +1,18 @@
/* tslint:disable:no-unused-variable */
import {inject, TestBed} from '@angular/core/testing';
import {AuthenticationService} from './authentication.service';
import {HttpClientModule} from '@angular/common/http';
describe('Service: Authentication', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
providers: [AuthenticationService]
});
});
it('should ...', inject([AuthenticationService], (service: AuthenticationService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,53 @@
import {BehaviorSubject, Observable} from 'rxjs';
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {map} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class AuthenticationService {
public currentUser: Observable<User>;
private currentUserSubject: BehaviorSubject<User>;
constructor(private http: HttpClient) {
this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
this.currentUser = this.currentUserSubject.asObservable();
}
public get currentUserValue(): User {
return this.currentUserSubject.value;
}
login(accessCode: string, server: string) {
return this.http.post<any>(server + '/' + 'api/sega/aime/getByAccessCode', {accessCode})
.pipe(
map(
resp => {
if (resp && resp.extId) {
const user = new User(resp.extId, server);
localStorage.setItem('currentUser', JSON.stringify(user));
this.currentUserSubject.next(user);
return user;
}
}
)
);
}
logout() {
localStorage.removeItem('currentUser');
this.currentUserSubject.next(null);
}
}
export class User {
extId: number;
apiServer: string;
constructor(extId: number, apiServer: string) {
this.extId = extId;
this.apiServer = apiServer;
}
}

View File

@ -0,0 +1,3 @@
<p>
Welcome to Aqua Server WebUI<br>
</p>

View File

@ -0,0 +1,26 @@
/* tslint:disable:no-unused-variable */
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DashboardComponent} from './dashboard.component';
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DashboardComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,16 @@
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
constructor() {
}
ngOnInit() {
}
}

View File

@ -0,0 +1,12 @@
import {NgModule} from '@angular/core';
import {DashboardComponent} from './dashboard.component';
@NgModule({
imports: [],
exports: [],
declarations: [DashboardComponent],
providers: [],
})
export class DashboardModule {
}

View File

@ -0,0 +1,7 @@
.full-width {
width: 100%;
}
.login-card {
min-width: 120px;
}

View File

@ -0,0 +1,19 @@
<mat-card class="login-card">
<mat-card-title>
Login
</mat-card-title>
<mat-card-content>
<form (ngSubmit)="onSubmit()" [formGroup]="loginForm">
<mat-form-field class="full-width">
<mat-label>Access Code</mat-label>
<input formControlName="accessCode" matInput required type="text">
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>ApiServer</mat-label>
<input formControlName="apiServer" matInput required type="text">
</mat-form-field>
<button [disabled]="!loginForm.valid" mat-raised-button type="submit">Login</button>
</form>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,62 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatInputModule} from '@angular/material/input';
import {LoginComponent} from './login.component';
import {AppRoutingModule} from '../app-routing.module';
import {LayoutModule} from '@angular/cdk/layout';
import {DashboardModule} from '../dashboard/dashboard.module';
import {ContainerModule} from '../container/container.module';
import {RegisterModule} from '../register/register.module';
import {HttpClientModule} from '@angular/common/http';
import {PlayerboardListModule} from '../playerboard-list/playerboard-list.module';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [LoginComponent],
imports: [
LayoutModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
DashboardModule,
ContainerModule,
RegisterModule,
ReactiveFormsModule,
MatButtonModule,
MatInputModule,
MatCardModule,
PlayerboardListModule
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should compile', () => {
expect(component).toBeTruthy();
});
it(`form should be invalid`, async(() => {
component.loginForm.controls.email.setValue('');
component.loginForm.controls.password.setValue('');
expect(component.loginForm.invalid).toBeTruthy();
}));
it(`form should be valid`, async(() => {
component.loginForm.controls.email.setValue('test@eamil.com');
component.loginForm.controls.password.setValue('12345');
expect(component.loginForm.invalid).toBeFalsy();
}));
});

View File

@ -0,0 +1,61 @@
import {Router} from '@angular/router';
import {AuthenticationService} from '../auth/authentication.service';
import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {MessageService} from '../message.service';
import {first} from 'rxjs/operators';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
constructor(
private fb: FormBuilder,
private router: Router,
private authenticationService: AuthenticationService,
private messageService: MessageService
) {
}
get f() {
return this.loginForm.controls;
}
ngOnInit() {
this.loginForm = this.fb.group({
accessCode: ['', Validators.required],
apiServer: ['http://localhost:80', Validators.required],
});
if (this.authenticationService.currentUserValue) {
this.router.navigateByUrl('/dashboard');
}
}
onSubmit() {
if (this.loginForm.invalid) {
return;
}
this.authenticationService.login(this.f.accessCode.value, this.f.apiServer.value).pipe(first())
.subscribe(
data => {
if (data != null) {
this.messageService.notice('OK');
location.reload(true);
} else {
this.messageService.notice('No such Card');
}
},
error => {
this.messageService.notice(error.message);
console.warn('login fail', error);
}
);
}
}

View File

@ -0,0 +1,27 @@
import {NgModule} from '@angular/core';
import {LoginComponent} from './login.component';
import {LayoutModule} from '@angular/cdk/layout';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {AppRoutingModule} from '../app-routing.module';
import {MatButtonModule} from '@angular/material/button';
import {MatInputModule} from '@angular/material/input';
import {MatCardModule} from '@angular/material/card';
@NgModule({
imports: [
LayoutModule,
FormsModule,
AppRoutingModule,
ReactiveFormsModule,
MatButtonModule,
MatInputModule,
MatCardModule,
],
exports: [],
declarations: [LoginComponent],
providers: [],
})
export class LoginModule {
}

View File

@ -0,0 +1,14 @@
import {MessageComponent} from './message/message.component';
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MessageService {
constructor(private messageComponent: MessageComponent) {
}
notice(message: string) {
this.messageComponent.openSnackBar(message);
}
}

View File

View File

View File

@ -0,0 +1,31 @@
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MessageModule} from './message.module';
/* tslint:disable:no-unused-variable */
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MessageComponent} from './message.component';
describe('MessageComponent', () => {
let component: MessageComponent;
let fixture: ComponentFixture<MessageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MessageModule,
BrowserAnimationsModule,
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MessageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,31 @@
import {Component, Injectable, OnInit} from '@angular/core';
import {MatSnackBar} from '@angular/material/snack-bar';
@Injectable({
providedIn: 'root'
})
@Component({
selector: 'app-message',
templateUrl: './message.component.html',
styleUrls: ['./message.component.css']
})
export class MessageComponent implements OnInit {
constructor(
private _snackBar: MatSnackBar
) {
}
ngOnInit() {
this.openSnackBar('Initialized');
}
public openSnackBar(message: string) {
this._snackBar.open(message, 'OK', {
duration: 2000,
});
}
}

View File

@ -0,0 +1,17 @@
import {NgModule} from '@angular/core';
import {MessageComponent} from './message.component';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatButtonModule} from '@angular/material/button';
@NgModule({
imports: [
MatSnackBarModule,
MatButtonModule
],
exports: [MessageComponent],
declarations: [MessageComponent],
providers: [],
})
export class MessageModule {
}

View File

@ -0,0 +1,11 @@
/*mat-card {*/
/* background-image: url("https://i.loli.net/2019/11/25/Xv4M5Npl9wib3Em.png");*/
/* background-repeat: no-repeat;*/
/* background-position: right top;*/
/* background-size: 50%;*/
/*}*/
mat-card-content {
font-size: 18px;
line-height: 1.5rem;
}

View File

@ -0,0 +1,39 @@
<mat-card *ngIf="profile">
<mat-card-title>Player Profile</mat-card-title>
<mat-card-content>
<table class="zebra">
<tr>
<th>Player Name</th>
<td>{{profile.userName}}</td>
</tr>
<tr>
<th>Player Rating</th>
<td>{{profile.playerRating / 100}}</td>
</tr>
<tr>
<th>Player Level</th>
<td>{{profile.level}}%</td>
</tr>
<tr>
<th>Total Play Count</th>
<td>{{profile.playCount}}</td>
</tr>
<tr>
<th>Name Plate Id</th>
<td>{{profile.nameplateId}}</td>
</tr>
<tr>
<th>Frame Id</th>
<td>{{profile.frameId}}</td>
</tr>
<tr>
<th>Character Id</th>
<td>{{profile.characterId}}</td>
</tr>
<tr>
<th>Last Play</th>
<td>{{profile.lastPlayDate}}</td>
</tr>
</table>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {AmazonProfileComponent} from './amazon-profile.component';
describe('AmazonProfileComponent', () => {
let component: AmazonProfileComponent;
let fixture: ComponentFixture<AmazonProfileComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AmazonProfileComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AmazonProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,89 @@
import {Component, OnInit} from '@angular/core';
import {MessageService} from '../../../../message.service';
import {AmazonProfile} from '../model/AmazonProfile';
import {ApiService} from '../../../../api.service';
import {AuthenticationService} from '../../../../auth/authentication.service';
import {HttpParams} from '@angular/common/http';
@Component({
selector: 'app-amazon-profile',
templateUrl: './amazon-profile.component.html',
styleUrls: ['./amazon-profile.component.css']
})
export class AmazonProfileComponent implements OnInit {
profile: AmazonProfile;
constructor(
private api: ApiService,
private auth: AuthenticationService,
private messageService: MessageService,
) {
}
ngOnInit() {
const aimeId = String(this.auth.currentUserValue.extId);
const param = new HttpParams().set('aimeId', aimeId);
this.api.get('api/game/chuni/amazon/profile', param).subscribe(
data => {
this.profile = data;
},
error => this.messageService.notice(error.statusText)
);
}
getRatingRank(s: number): number {
switch (true) {
case (s < 2.0):
return 1;
case (s < 4.0):
return 2;
case (s < 7.0):
return 3;
case (s < 10.0):
return 4;
case (s < 12.0):
return 5;
case (s < 13.0):
return 6;
case (s < 14.0):
return 7;
case (s < 14.5):
return 8;
case (s < 15.0):
return 9;
case (s >= 15.0):
return 10;
}
}
formatNumber(value: number, length?: number): string {
let str = value.toString();
while (str.length < length) {
str = '0' + str;
}
return str;
}
getClass(v: number): number {
switch (true) {
case (v === 10):
return 1;
case (v === 11):
return 2;
case (v === 12):
return 3;
case (v === 13):
return 4;
case (v === 14):
return 5;
case (v === 20):
return 6; // infinity
case (v === 21):
return 7; // class unknown
}
}
}

View File

@ -0,0 +1,32 @@
.skill-card table {
width: 100%;
font-size: 16px;
}
.skill-card h3 {
font-size: 20px;
text-align: center;
}
.skill-card img {
width: 18px;
height: 18px;
}
td {
max-width: 130px;
}
@media only screen and (max-width: 599px) {
.skill-card table {
width: 100%;
font-size: 12px;
}
.skill-card img {
width: 18px;
height: 18px;
}
}

View File

@ -0,0 +1,72 @@
<div>
<mat-card>
<h3>Total Rating <span
class="{{(topTotal + recentTotal) / 40 | toRating | ratingClass}}">{{(topTotal + recentTotal) / 40 | toRating }}</span>
</h3>
<h3>Top <span class="{{topTotal / 30 | toRating | ratingClass}}">{{topTotal / 30 | toRating }}</span></h3>
<h3>Recent <span class="{{recentTotal / 10 | toRating | ratingClass}}">{{recentTotal / 10 | toRating }}</span></h3>
</mat-card>
</div>
<div fxLayout="row wrap" fxLayout.sm="column" fxLayout.xs="column">
<div class="skill-card" fxFlex="45">
<mat-card>
<h3>Top</h3>
<table class="zebra">
<thead>
<th>Music</th>
<th>Diff</th>
<th>Score</th>
<th>Rating</th>
</thead>
<tbody>
<tr *ngFor="let item of topRating; index as i" routerLink="/exchain/playrecord/{{item.musicId}}">
<td>
{{i + 1}}: {{item.musicName}}
</td>
<td>
{{item.ratingBase|toRating}}
</td>
<td>
{{item.score}}
</td>
<td>
<span class="{{item.rating|toRating|ratingClass}}">{{item.rating|toRating}}</span>
</td>
</tr>
</tbody>
</table>
</mat-card>
</div>
<div fxFlex="10">
</div>
<div class="skill-card" fxFlex="45">
<mat-card>
<h3>Recent</h3>
<table class="zebra">
<thead>
<th>Music</th>
<th>Diff</th>
<th>Score</th>
<th>Rating</th>
</thead>
<tbody>
<tr *ngFor="let item of recentRating; index as i" routerLink="/exchain/playrecord/{{item.musicId}}">
<td>
{{i + 1}}: {{item.musicName}}
</td>
<td>
{{item.ratingBase|toRating}}
</td>
<td>
{{item.score}}
</td>
<td>
<span class="{{item.rating|toRating|ratingClass}}">{{item.rating|toRating}}</span>
</td>
</tr>
</tbody>
</table>
</mat-card>
</div>
</div>

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {AmazonRatingComponent} from './amazon-rating.component';
describe('AmazonRatingComponent', () => {
let component: AmazonRatingComponent;
let fixture: ComponentFixture<AmazonRatingComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AmazonRatingComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AmazonRatingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,62 @@
import {Component, OnInit} from '@angular/core';
import {MessageService} from '../../../../message.service';
import {ApiService} from '../../../../api.service';
import {HttpParams} from '@angular/common/http';
import {AuthenticationService} from '../../../../auth/authentication.service';
@Component({
selector: 'app-amazon-rating',
templateUrl: './amazon-rating.component.html',
styleUrls: ['./amazon-rating.component.css']
})
export class AmazonRatingComponent implements OnInit {
private topRating: RatingItem[] = [];
private recentRating: RatingItem[] = [];
private topTotal = 0;
private recentTotal = 0;
constructor(
private api: ApiService,
private auth: AuthenticationService,
private messageService: MessageService
) {
}
ngOnInit() {
const aimeId = String(this.auth.currentUserValue.extId);
const param = new HttpParams().set('aimeId', aimeId);
this.api.get('api/game/chuni/amazon/rating', param).subscribe(
data => {
this.topRating = data;
if (this.topRating.length === 0) {
this.messageService.notice('No Data');
}
this.topRating.forEach(item => this.topTotal += item.rating);
},
error => this.messageService.notice(error.statusText)
);
this.api.get('api/game/chuni/amazon/rating/recent', param).subscribe(
data => {
this.recentRating = data;
if (this.recentRating.length === 0) {
this.messageService.notice('No Data');
}
this.recentRating.forEach(item => this.recentTotal += item.rating);
},
error => this.messageService.notice(error.statusText)
);
}
}
export interface RatingItem {
musicId: number;
musicName: string;
artistName: string;
level: number;
score: number;
ratingBase: number;
rating: number;
}

View File

@ -0,0 +1,30 @@
mat-card-title {
font-size: 1.2em;
text-align: right;
}
.song-info .title {
font-size: 1.2em;
}
.song-info .info {
opacity: 70%;
}
.result-content {
display: flex;
}
.result-content .left {
min-width: 30%;
text-align: center;
margin: auto;
}
.achievement-value {
font-size: 1.5rem;
}
.score-value {
font-size: 1.4rem;
}

View File

@ -0,0 +1,56 @@
<div *ngIf="recent">
<mat-card *ngFor="let item of recent">
<mat-card-title>{{item.userPlayDate}}</mat-card-title>
<mat-card-content>
<div class="song-info">
<span class="title">{{item.songInfo != null ? item.songInfo.name : 'musicId:' + item.musicId}}</span><br>
<span
class="info">{{item.songInfo != null ? item.songInfo.artistName : ''}}</span>
</div>
<div class="result-content">
<div class="left">
Rank:<br>
<span class="achievement-value">{{item.rank|toRank}}</span>
<br>
<br>Score:<br>
<span class="score-value">{{item.score}}</span>
<br>
<br>{{item.isNewRecord ? 'NEW RECORD' : ''}}<br>
</div>
<table class="zebra">
<tr>
<th>JUSTICE C.</th>
<td>{{item.judgeCritical}}</td>
<td>TAP</td>
<td>{{item.rateTap / 100}}%</td>
</tr>
<tr>
<th>JUSTICE</th>
<td>{{item.judgeJustice}}</td>
<td>HOLD</td>
<td>{{item.rateHold / 100}}%</td>
</tr>
<tr>
<th>ATTACK</th>
<td>{{item.judgeAttack}}</td>
<td>SLIDE</td>
<td>{{item.rateSlide / 100}}%</td>
</tr>
<tr>
<th>MISS</th>
<td>{{item.judgeGuilty}}</td>
<td>AIR</td>
<td>{{item.rateAir / 100}}%</td>
</tr>
<tr>
<th>Combo</th>
<td>{{item.maxCombo}}</td>
<td>FLICK</td>
<td>{{item.rateFlick / 100}}%</td>
</tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {AmazonRecentComponent} from './amazon-recent.component';
describe('AmazonRecentComponent', () => {
let component: AmazonRecentComponent;
let fixture: ComponentFixture<AmazonRecentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AmazonRecentComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AmazonRecentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,40 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../../../../api.service';
import {AuthenticationService} from '../../../../auth/authentication.service';
import {MessageService} from '../../../../message.service';
import {HttpParams} from '@angular/common/http';
import {AmazonPlayLog} from '../model/AmazonPlayLog';
import {ChuniMusicDbService} from '../chuni-music-db.service';
@Component({
selector: 'app-amazon-recent',
templateUrl: './amazon-recent.component.html',
styleUrls: ['./amazon-recent.component.css']
})
export class AmazonRecentComponent implements OnInit {
recent: AmazonPlayLog[] = [];
constructor(
private api: ApiService,
private auth: AuthenticationService,
private messageService: MessageService,
private musicDb: ChuniMusicDbService
) {
}
ngOnInit() {
const aimeId = String(this.auth.currentUserValue.extId);
const param = new HttpParams().set('aimeId', aimeId);
this.api.get('api/game/chuni/amazon/recent', param).subscribe(
data => {
data.forEach(x => {
x.songInfo = this.musicDb.getMusicDb().get(x.musicId);
this.recent.push(x);
});
},
error => this.messageService.notice(error.statusText)
);
}
}

View File

@ -0,0 +1,69 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {MatCardModule} from '@angular/material/card';
import {MatButtonModule} from '@angular/material/button';
import {MatToolbarModule} from '@angular/material/toolbar';
import {MatSidenavModule} from '@angular/material/sidenav';
import {MatIconModule} from '@angular/material/icon';
import {MatListModule} from '@angular/material/list';
import {MatInputModule} from '@angular/material/input';
import {MatSelectModule} from '@angular/material/select';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatMenuModule} from '@angular/material/menu';
import {MatTableModule} from '@angular/material/table';
import {MatPaginatorModule} from '@angular/material/paginator';
import {MatSortModule} from '@angular/material/sort';
import {MatDialogModule} from '@angular/material/dialog';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {FlexLayoutModule} from '@angular/flex-layout';
import {MatFormFieldModule} from '@angular/material/form-field';
import {AmazonRoutes} from './amazon.routing';
import {AmazonProfileComponent} from './amazon-profile/amazon-profile.component';
import {AmazonRatingComponent} from './amazon-rating/amazon-rating.component';
import {ToRatingPipe} from './util/to-rating.pipe';
import {FormatnumberPipe} from './util/formatnumber.pipe';
import {RatingClass} from './util/rating-class.pipe';
import {CourceIdToClassPipe} from './util/cource-id-to-class.pipe';
import {AmazonRecentComponent} from './amazon-recent/amazon-recent.component';
import {ToRankPipe} from './util/to-rank.pipe';
@NgModule({
imports: [
CommonModule,
FormsModule,
AmazonRoutes,
MatFormFieldModule,
MatCardModule,
MatButtonModule,
MatToolbarModule,
MatSidenavModule,
MatIconModule,
MatListModule,
MatInputModule,
MatSelectModule,
ReactiveFormsModule,
MatMenuModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatDialogModule,
MatSnackBarModule,
FlexLayoutModule
],
declarations: [
AmazonProfileComponent,
AmazonRatingComponent,
ToRatingPipe,
FormatnumberPipe,
RatingClass,
CourceIdToClassPipe,
AmazonRecentComponent,
ToRankPipe
],
entryComponents: [],
exports: []
})
export class AmazonModule {
}

View File

@ -0,0 +1,12 @@
import {RouterModule, Routes} from '@angular/router';
import {AmazonProfileComponent} from './amazon-profile/amazon-profile.component';
import {AmazonRatingComponent} from './amazon-rating/amazon-rating.component';
import {AmazonRecentComponent} from './amazon-recent/amazon-recent.component';
const routes: Routes = [
{path: 'profile', component: AmazonProfileComponent},
{path: 'rating', component: AmazonRatingComponent},
{path: 'recent', component: AmazonRecentComponent},
];
export const AmazonRoutes = RouterModule.forChild(routes);

View File

@ -0,0 +1,38 @@
import {Injectable} from '@angular/core';
import {ChuniMusic} from './model/ChuniMusic';
import {ApiService} from '../../../api.service';
@Injectable({
providedIn: 'root'
})
export class ChuniMusicDbService {
musicDb: Map<number, ChuniMusic>;
constructor(private api: ApiService) {
let db = localStorage.getItem('chuniMusicDb');
if (db == null) {
this.api.get('api/game/chuni/amazon/music').subscribe(
data => {
db = data;
localStorage.setItem('chuniMusicDb', JSON.stringify(db));
this.musicDb = this.parse(JSON.parse(db));
}
);
} else {
this.musicDb = this.parse(JSON.parse(db));
}
}
getMusicDb(): Map<number, ChuniMusic> {
return this.musicDb;
}
private parse(db: ChuniMusic[]): Map<number, ChuniMusic> {
const result: Map<number, ChuniMusic> = new Map();
db.forEach(x => {
result.set(x.musicId, x);
});
return result;
}
}

View File

@ -0,0 +1,38 @@
import {ChuniMusic} from './ChuniMusic';
export interface AmazonPlayLog {
playDate: Date;
userPlayDate: Date;
musicId: number;
songInfo?: ChuniMusic;
level: number;
customId: number;
playedCustom1: number;
playedCustom2: number;
playedCustom3: number;
track: number;
score: number;
rank: number;
maxCombo: number;
maxChain: number;
rateTap: number;
rateHold: number;
rateSlide: number;
rateAir: number;
rateFlick: number;
judgeGuilty: number;
judgeAttack: number;
judgeJustice: number;
judgeCritical: number;
playerRating: number;
fullChainKind: number;
characterId: number;
skillId: number;
playKind: number;
skillLevel: number;
skillEffect: number;
isNewRecord: boolean;
isFullCombo: boolean;
isAllJustice: boolean;
isClear: boolean;
}

View File

@ -0,0 +1,36 @@
export interface AmazonProfile {
userName: string;
level: number;
exp: number;
point: number;
totalPoint: bigint;
playCount: number;
multiPlayCount: number;
multiWinCount: number;
requestResCount: number;
acceptResCount: number;
successResCount: number;
playerRating: number;
highestRating: number;
nameplateId: number;
frameId: number;
characterId: number;
trophyId: number;
playedTutorialBit: number;
firstTutorialCancelNum: number;
masterTutorialCancelNum: number;
totalRepertoireCount: number;
totalMapNum: number;
totalHiScore: bigint;
totalBasicHighScore: bigint;
totalAdvancedHighScore: bigint;
totalExpertHighScore: bigint;
totalMasterHighScore: bigint;
friendCount: number;
firstGameId: string;
firstRomVersion: string;
firstDataVersion: string;
firstPlayDate: Date;
lastPlayDate: Date;
courseClass: number;
}

View File

@ -0,0 +1,9 @@
export interface ChuniMusic {
musicId: number;
name: string;
sotrName: string;
copyright: string;
artistName: string;
genre: string;
releaseVersion: string;
}

View File

@ -0,0 +1,8 @@
import {CourceIdToClassPipe} from './cource-id-to-class.pipe';
describe('CourceIdToClassPipe', () => {
it('create an instance', () => {
const pipe = new CourceIdToClassPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -0,0 +1,27 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'courceIdToClass'
})
export class CourceIdToClassPipe implements PipeTransform {
transform(v: number): number {
switch (true) {
case (v === 10):
return 1;
case (v === 11):
return 2;
case (v === 12):
return 3;
case (v === 13):
return 4;
case (v === 14):
return 5;
case (v === 20):
return 6; // infinity
case (v === 21):
return 7; // class unknown
}
}
}

View File

@ -0,0 +1,10 @@
/* tslint:disable:no-unused-variable */
import {FormatnumberPipe} from './formatnumber.pipe';
describe('Pipe: Formatnumbere', () => {
it('create an instance', () => {
let pipe = new FormatnumberPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -0,0 +1,16 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'formatNumber'
})
export class FormatnumberPipe implements PipeTransform {
public transform(value: number, length?: number): string {
let str = value.toString();
while (str.length < length) {
str = '0' + str;
}
return str;
}
}

View File

@ -0,0 +1,33 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'ratingClass'
})
export class RatingClass implements PipeTransform {
transform(s: number, args?: any): string {
switch (true) {
case (s < 2.0):
return 'lv8';
case (s < 4.0):
return 'lv6';
case (s < 7.0):
return 'lv2';
case (s < 10.0):
return 'lv12';
case (s < 12.0):
return 'lv10';
case (s < 13.0):
return 'lv14';
case (s < 14.0):
return 'lv15';
case (s < 14.5):
return 'lv16';
case (s < 15.0):
return 'lv0';
case (s >= 15.0):
return 'lv17';
}
}
}

View File

@ -0,0 +1,8 @@
import {ToRankPipe} from './to-rank.pipe';
describe('ToRankPipe', () => {
it('create an instance', () => {
const pipe = new ToRankPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -0,0 +1,47 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'toRank'
})
export class ToRankPipe implements PipeTransform {
transform(value: number): string {
if (value === 0) {
return 'D';
}
if (value === 1) {
return 'C';
}
if (value === 2) {
return 'B';
}
if (value === 3) {
return 'BB';
}
if (value === 4) {
return 'BBB';
}
if (value === 5) {
return 'A';
}
if (value === 6) {
return 'AA';
}
if (value === 7) {
return 'AAA';
}
if (value === 8) {
return 'S';
}
if (value === 9) {
return 'SS';
}
if (value === 10) {
return 'SSS';
}
if (value > 10) {
return 'SSS';
}
}
}

View File

@ -0,0 +1,8 @@
import {ToRatingPipe} from './to-rating.pipe';
describe('ToRatingPipe', () => {
it('create an instance', () => {
const pipe = new ToRatingPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -0,0 +1,12 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'toRating'
})
export class ToRatingPipe implements PipeTransform {
transform(value: number): number {
return Math.floor(value) / 100;
}
}

View File

@ -0,0 +1,95 @@
<mat-card>
<mat-card-title>
<div class="title">Contest ID: {{f.id.value}}</div>
</mat-card-title>
<mat-card-content>
<form (ngSubmit)="onSubmit()" [formGroup]="contestForm">
<mat-form-field class="full-width">
<mat-label>Name</mat-label>
<input formControlName="name" matInput required type="text">
</mat-form-field>
<mat-checkbox class="full-width" formControlName="enable">Enabled</mat-checkbox>
<mat-form-field class="full-width">
<mat-label>Description</mat-label>
<textarea formControlName="description" matInput required type="text"></textarea>
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>Start Time</mat-label>
<input [matDatepicker]="startTime" formControlName="startTime" matInput placeholder="Choose a start date">
<mat-datepicker-toggle [for]="startTime" matSuffix></mat-datepicker-toggle>
<mat-datepicker #startTime></mat-datepicker>
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>End Time</mat-label>
<input [matDatepicker]="endTime" formControlName="endTime" matInput placeholder="Choose a end date">
<mat-datepicker-toggle [for]="endTime" matSuffix></mat-datepicker-toggle>
<mat-datepicker #endTime></mat-datepicker>
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>League</mat-label>
<mat-select formControlName="league">
<mat-option value="0">Beginner</mat-option>
<mat-option value="1">Intermediate</mat-option>
<mat-option value="2">Advanced</mat-option>
<mat-option value="3">Professional</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>Stars</mat-label>
<input formControlName="stars" matInput required type="number">
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>minComplexity</mat-label>
<input formControlName="minComplexity" matInput required type="number">
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>maxComplexity</mat-label>
<input formControlName="maxComplexity" matInput required type="number">
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>Stages</mat-label>
<input formControlName="stages" matInput required type="number">
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>Stage Limit</mat-label>
<mat-select formControlName="stageLimit">
<mat-option value="0">Unlimited</mat-option>
<mat-option value="1">Limited</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>Norma Type</mat-label>
<mat-select formControlName="normaType">
<mat-option value="0">Score</mat-option>
<mat-option value="1">Percentage</mat-option>
<mat-option value="2">Cool Percentage</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>bronzeBorders</mat-label>
<input formControlName="bronzeBorders" matInput required type="number">
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>sliverBorders</mat-label>
<input formControlName="sliverBorders" matInput required type="number">
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>goldBorders</mat-label>
<input formControlName="goldBorders" matInput required type="number">
</mat-form-field>
<button [disabled]="!contestForm.valid" mat-flat-button type="submit">Submit</button>
</form>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DivaContestEditComponent} from './diva-contest-edit.component';
describe('DivaContestEditComponent', () => {
let component: DivaContestEditComponent;
let fixture: ComponentFixture<DivaContestEditComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DivaContestEditComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DivaContestEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,64 @@
import {Component, OnInit} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {ApiService} from '../../../../../api.service';
import {MessageService} from '../../../../../message.service';
import {Router} from '@angular/router';
import {Contest} from '../../../model/mannagement/contest';
import {DivaContestService} from '../diva-contest.service';
@Component({
selector: 'app-diva-contest-edit',
templateUrl: './diva-contest-edit.component.html',
styleUrls: ['./diva-contest-edit.component.css']
})
export class DivaContestEditComponent implements OnInit {
contest: Contest;
contestForm: FormGroup;
constructor(
private api: ApiService,
private messageService: MessageService,
private fb: FormBuilder,
private contestService: DivaContestService,
private router: Router
) {
}
get f() {
return this.contestForm.controls;
}
ngOnInit() {
this.contest = this.contestService.contest;
this.contestForm = this.fb.group({
id: [this.contest.id, Validators.required],
enable: [this.contest.enable],
name: [this.contest.name, Validators.required],
description: [this.contest.description, Validators.required],
startTime: [this.contest.startTime, Validators.required],
endTime: [this.contest.endTime, Validators.required],
league: [String(this.contest.league), Validators.required],
stars: [this.contest.stars, Validators.required],
minComplexity: [this.contest.minComplexity, Validators.required],
maxComplexity: [this.contest.maxComplexity, Validators.required],
stages: [this.contest.stages, Validators.required],
stageLimit: [String(this.contest.stageLimit), Validators.required],
normaType: [String(this.contest.normaType), Validators.required],
bronzeBorders: [this.contest.bronzeBorders, Validators.required],
sliverBorders: [this.contest.sliverBorders, Validators.required],
goldBorders: [this.contest.goldBorders, Validators.required],
});
}
onSubmit() {
this.api.put('api/game/diva/manage/contest', this.contestForm.value).subscribe(
data => {
console.log(data);
this.router.navigateByUrl('/diva/management/contest');
},
error => this.messageService.notice(error)
);
}
}

View File

@ -0,0 +1,8 @@
mat-card-title {
font-size: 1.2em;
display: flex;
}
mat-card-title button {
margin-left: auto;
}

View File

@ -0,0 +1,82 @@
<mat-card>
<mat-card-title>
<div>Contest List, Length: {{contests ? contests.length : 0}}</div>
<button (click)="edit(undefined)" mat-flat-button>Add</button>
</mat-card-title>
</mat-card>
<div *ngIf="contests">
<mat-card *ngFor="let contest of contests">
<mat-card-content>
<table class="zebra">
<tr>
<th>ID</th>
<td>{{contest.id}}</td>
</tr>
<tr>
<th>Enabled</th>
<td>{{contest.enable}}</td>
</tr>
<tr>
<th>Name</th>
<td>{{contest.name}}</td>
</tr>
<tr>
<th>Description</th>
<td>{{contest.description}}</td>
</tr>
<tr>
<th>Start Time</th>
<td>{{contest.startTime}}</td>
</tr>
<tr>
<th>End Time</th>
<td>{{contest.endTime}}</td>
</tr>
<tr>
<th>League</th>
<td>{{contestLeague[contest.league]}}</td>
</tr>
<tr>
<th>Star</th>
<td>{{contest.stars}}</td>
</tr>
<tr>
<th>Min Complexity</th>
<td>{{contest.minComplexity}}</td>
</tr>
<tr>
<th>Max Complexity</th>
<td>{{contest.maxComplexity}}</td>
</tr>
<tr>
<th>Stages</th>
<td>{{contest.stages}}</td>
</tr>
<tr>
<th>Stage Limit</th>
<td>{{contestStageLimit[contest.stageLimit]}}</td>
</tr>
<tr>
<th>Norma Type</th>
<td>{{contestNormaType[contest.normaType]}}</td>
</tr>
<tr>
<th>Bronze borders</th>
<td>{{contest.bronzeBorders}}</td>
</tr>
<tr>
<th>Sliver borders</th>
<td>{{contest.sliverBorders}}</td>
</tr>
<tr>
<th>Gold borders</th>
<td>{{contest.goldBorders}}</td>
</tr>
</table>
</mat-card-content>
<mat-card-actions>
<button (click)="edit(contest)" mat-flat-button>Edit</button>
<button (click)="delete(contest.id)" mat-flat-button>Delete</button>
</mat-card-actions>
</mat-card>
</div>

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DivaContestComponent} from './diva-contest.component';
describe('DivaContestComponent', () => {
let component: DivaContestComponent;
let fixture: ComponentFixture<DivaContestComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DivaContestComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DivaContestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,56 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../../../../api.service';
import {MessageService} from '../../../../message.service';
import {Router} from '@angular/router';
import {DivaContestService} from './diva-contest.service';
import {Contest, ContestLeague, ContestNormaType, ContestStageLimit} from '../../model/mannagement/contest';
@Component({
selector: 'app-diva-contest',
templateUrl: './diva-contest.component.html',
styleUrls: ['./diva-contest.component.css']
})
export class DivaContestComponent implements OnInit {
contests: Contest[];
contestLeague = ContestLeague;
contestStageLimit = ContestStageLimit;
contestNormaType = ContestNormaType;
constructor(
private api: ApiService,
private messageService: MessageService,
private contestService: DivaContestService,
private router: Router
) {
}
ngOnInit() {
this.load();
}
load() {
this.api.get('api/game/diva/manage/contest').subscribe(
data => this.contests = data,
error => this.messageService.notice(error)
);
}
delete(id) {
this.api.delete('api/game/diva/manage/contest/' + id).subscribe(
() => {
this.messageService.notice('OK');
this.load();
},
error => {
this.messageService.notice(error.statusText);
this.load();
}
);
}
edit(contest) {
this.contestService.contest = contest;
this.router.navigateByUrl('/diva/management/contest/edit');
}
}

View File

@ -0,0 +1,12 @@
import {TestBed} from '@angular/core/testing';
import {DivaContestService} from './diva-contest.service';
describe('DivaContestService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: DivaContestService = TestBed.get(DivaContestService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,38 @@
import {Injectable} from '@angular/core';
import {Contest, ContestLeague, ContestNormaType, ContestStageLimit} from '../../model/mannagement/contest';
@Injectable({
providedIn: 'root'
})
export class DivaContestService {
currentContest: Contest = undefined;
constructor() {
}
get contest() {
return this.currentContest === undefined ? {
id: -1,
enable: true,
startTime: new Date(),
endTime: new Date(),
name: 'Untitled',
description: 'description',
league: ContestLeague.Intermediate,
stars: 16,
minComplexity: 10,
maxComplexity: 20,
stages: 4,
stageLimit: ContestStageLimit.Limited,
normaType: ContestNormaType.Percentage,
bronzeBorders: 16000,
sliverBorders: 28000,
goldBorders: 32000
} : this.currentContest;
}
set contest(f: Contest) {
this.currentContest = f;
}
}

View File

@ -0,0 +1,48 @@
<mat-card>
<mat-card-title>
<div class="title">Festa</div>
</mat-card-title>
<mat-card-content>
<form (ngSubmit)="onSubmit()" [formGroup]="festaForm">
<mat-form-field class="full-width">
<mat-label>Name</mat-label>
<input formControlName="name" matInput required type="text">
</mat-form-field>
<mat-checkbox class="full-width" formControlName="enable">Enabled</mat-checkbox>
<mat-form-field class="full-width">
<mat-label>Kind</mat-label>
<mat-select formControlName="kind">
<mat-option value="0">PinkFesta</mat-option>
<mat-option value="1">GreenFesta</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>Difficulty Limit</mat-label>
<mat-select formControlName="difficulty">
<mat-option value="-1">Unset</mat-option>
<mat-option value="0">Easy</mat-option>
<mat-option value="1">Normal</mat-option>
<mat-option value="2">Hard</mat-option>
<mat-option value="3">Extreme</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>PV List</mat-label>
<input formControlName="pvList" matInput required type="text">
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>Attributes</mat-label>
<input formControlName="attributes" matInput required type="text">
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>Add VP</mat-label>
<input formControlName="addVP" matInput required type="number">
</mat-form-field>
<mat-form-field class="full-width">
<mat-label>VP Multiplier</mat-label>
<input formControlName="vpMultiplier" matInput required type="number">
</mat-form-field>
<button [disabled]="!festaForm.valid" mat-flat-button type="submit">Submit</button>
</form>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DivaFestaEditComponent} from './diva-festa-edit.component';
describe('DivaFestaEditComponent', () => {
let component: DivaFestaEditComponent;
let fixture: ComponentFixture<DivaFestaEditComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DivaFestaEditComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DivaFestaEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,60 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../../../../../api.service';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {DivaFestaService} from '../diva-festa.service';
import {Festa} from '../../../model/mannagement/Festa';
import {Router} from '@angular/router';
import {MessageService} from '../../../../../message.service';
@Component({
selector: 'app-diva-festa-edit',
templateUrl: './diva-festa-edit.component.html',
styleUrls: ['./diva-festa-edit.component.css']
})
export class DivaFestaEditComponent implements OnInit {
festa: Festa;
festaForm: FormGroup;
constructor(
private api: ApiService,
private messageService: MessageService,
private fb: FormBuilder,
private festaService: DivaFestaService,
private router: Router
) {
}
get f() {
return this.festaForm.controls;
}
ngOnInit() {
this.festa = this.festaService.festa;
this.festaForm = this.fb.group({
id: [this.festa.id, Validators.required],
name: [this.festa.name, Validators.required],
enable: [this.festa.enable],
kind: [String(this.festa.kind), Validators.required],
difficulty: [String(this.festa.difficulty), Validators.required],
pvList: [this.festa.pvList, Validators.required],
attributes: [this.festa.attributes, Validators.required],
addVP: [this.festa.addVP, Validators.required],
vpMultiplier: [this.festa.vpMultiplier, Validators.required],
start: [this.festa.start, Validators.required],
end: [this.festa.end, Validators.required],
createDate: [this.festa.createDate, Validators.required],
});
}
onSubmit() {
this.api.put('api/game/diva/manage/festa', this.festaForm.value).subscribe(
data => {
console.log(data);
this.router.navigateByUrl('/diva/management/festa');
},
error => this.messageService.notice(error)
);
}
}

View File

@ -0,0 +1,8 @@
mat-card-title {
font-size: 1.2em;
display: flex;
}
mat-card-title button {
margin-left: auto;
}

View File

@ -0,0 +1,62 @@
<mat-card>
<mat-card-title>
<div>Festa List, Length: {{festas ? festas.length : 0}}</div>
<button (click)="edit(undefined)" mat-flat-button>Add</button>
</mat-card-title>
</mat-card>
<div *ngIf="festas">
<mat-card *ngFor="let festa of festas">
<mat-card-content>
<table class="zebra">
<tr>
<th>ID</th>
<td>{{festa.id}}</td>
</tr>
<tr>
<th>Name</th>
<td>{{festa.name}}</td>
</tr>
<tr>
<th>FestaKind</th>
<td>{{festaKind[festa.kind]}}</td>
</tr>
<tr>
<th>Difficulty</th>
<td>{{difficulty[festa.difficulty]}}</td>
</tr>
<tr>
<th>PvList</th>
<td>{{festa.pvList}}</td>
</tr>
<tr>
<th>Attributes</th>
<td>{{festa.attributes}}</td>
</tr>
<tr>
<th>Add VP</th>
<td>{{festa.addVP}}</td>
</tr>
<tr>
<th>VP Multiplier</th>
<td>{{festa.vpMultiplier}}</td>
</tr>
<tr>
<th>Start</th>
<td>{{festa.start}}</td>
</tr>
<tr>
<th>End</th>
<td>{{festa.end}}</td>
</tr>
<tr>
<th>Create Date</th>
<td>{{festa.createDate}}</td>
</tr>
</table>
</mat-card-content>
<mat-card-actions>
<button (click)="edit(festa)" mat-flat-button>Edit</button>
<button (click)="delete(festa.id)" mat-flat-button>Delete</button>
</mat-card-actions>
</mat-card>
</div>

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DivaFestaComponent} from './diva-festa.component';
describe('DivaFestaComponent', () => {
let component: DivaFestaComponent;
let fixture: ComponentFixture<DivaFestaComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DivaFestaComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DivaFestaComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,57 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../../../../api.service';
import {MessageService} from '../../../../message.service';
import {Festa, FestaKind} from '../../model/mannagement/Festa';
import {DivaFestaService} from './diva-festa.service';
import {Router} from '@angular/router';
import {Difficulty} from '../../model/DivaPvRecord';
@Component({
selector: 'app-diva-festa',
templateUrl: './diva-festa.component.html',
styleUrls: ['./diva-festa.component.css']
})
export class DivaFestaComponent implements OnInit {
festas: Festa[];
festaKind = FestaKind;
difficulty = Difficulty;
constructor(
private api: ApiService,
private messageService: MessageService,
private festaService: DivaFestaService,
private router: Router
) {
}
ngOnInit() {
this.load();
}
load() {
this.api.get('api/game/diva/manage/festa').subscribe(
data => this.festas = data,
error => this.messageService.notice(error)
);
}
delete(id) {
this.api.delete('api/game/diva/manage/festa/' + id).subscribe(
() => {
this.messageService.notice('OK');
this.load();
},
error => {
this.messageService.notice(error.statusText);
this.load();
}
);
}
edit(festa) {
this.festaService.festa = festa;
this.router.navigateByUrl('/diva/management/festa/edit');
}
}

View File

@ -0,0 +1,34 @@
import {Injectable} from '@angular/core';
import {Festa} from '../../model/mannagement/Festa';
@Injectable({
providedIn: 'root'
})
export class DivaFestaService {
currentFesta: Festa = undefined;
constructor() {
}
get festa() {
return this.currentFesta === undefined ? {
id: -1,
enable: true,
name: 'Untitled',
kind: 0,
difficulty: -1,
pvList: 'ALL',
attributes: '7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
addVP: 0,
vpMultiplier: 1,
start: new Date(),
end: new Date(),
createDate: new Date()
} : this.currentFesta;
}
set festa(f: Festa) {
this.currentFesta = f;
}
}

View File

@ -0,0 +1,9 @@
<h2>Management</h2>
<mat-nav-list>
<mat-list-item routerLink="/diva/management/festa">
<a matLine>Festa</a>
</mat-list-item>
<mat-list-item routerLink="/diva/management/contest">
<a matLine>Contest</a>
</mat-list-item>
</mat-nav-list>

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DivaManagementComponent} from './diva-management.component';
describe('DivaManagementComponent', () => {
let component: DivaManagementComponent;
let fixture: ComponentFixture<DivaManagementComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DivaManagementComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DivaManagementComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,16 @@
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-diva-management',
templateUrl: './diva-management.component.html',
styleUrls: ['./diva-management.component.css']
})
export class DivaManagementComponent implements OnInit {
constructor() {
}
ngOnInit() {
}
}

View File

@ -0,0 +1,38 @@
import {Injectable} from '@angular/core';
import {ApiService} from '../../api.service';
import {DivaPv} from './model/DivaPv';
@Injectable({
providedIn: 'root'
})
export class DivaMusicDbService {
musicDb: Map<number, DivaPv>;
constructor(private api: ApiService) {
let db = localStorage.getItem('divaMusicDb');
if (db == null) {
this.api.get('api/game/diva/data/musicList').subscribe(
data => {
db = data;
localStorage.setItem('divaMusicDb', JSON.stringify(db));
this.musicDb = this.parse(JSON.parse(db));
}
);
} else {
this.musicDb = this.parse(JSON.parse(db));
}
}
getMusicDb(): Map<number, DivaPv> {
return this.musicDb;
}
private parse(db): Map<number, DivaPv> {
const result: Map<number, DivaPv> = new Map();
db.forEach(x => {
result.set(x.pvId, x);
});
return result;
}
}

View File

@ -0,0 +1,23 @@
<mat-card *ngIf="profile">
<mat-card-title>Player Profile</mat-card-title>
<mat-card-content>
<table class="zebra">
<tr>
<th>Player Name</th>
<td>{{profile.playerName}}</td>
</tr>
<tr>
<th>Player Level</th>
<td>{{profile.level}}</td>
</tr>
<tr>
<th>Level Exp</th>
<td>{{profile.levelExp}}%</td>
</tr>
<tr>
<th>Player Title</th>
<td>{{profile.levelTitle}}</td>
</tr>
</table>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DivaProfileComponent} from './diva-profile.component';
describe('DivaProfileComponent', () => {
let component: DivaProfileComponent;
let fixture: ComponentFixture<DivaProfileComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DivaProfileComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DivaProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,33 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../../../api.service';
import {DivaProfile} from '../model/DivaProfile';
import {HttpParams} from '@angular/common/http';
import {AuthenticationService} from '../../../auth/authentication.service';
import {MessageService} from '../../../message.service';
@Component({
selector: 'app-diva-profile',
templateUrl: './diva-profile.component.html',
styleUrls: ['./diva-profile.component.css']
})
export class DivaProfileComponent implements OnInit {
profile: DivaProfile;
constructor(
private api: ApiService,
private auth: AuthenticationService,
private messageService: MessageService
) {
}
ngOnInit() {
const pdId = String(this.auth.currentUserValue.extId);
const param = new HttpParams().set('pdId', pdId);
this.api.get('api/game/diva/playerInfo', param).subscribe(
data => this.profile = data,
error => this.messageService.notice(error)
);
}
}

View File

@ -0,0 +1,30 @@
mat-card-title {
font-size: 1.2em;
display: flex;
}
.level {
margin-left: auto;
}
.song-info .info {
opacity: 70%;
}
.result-content {
display: flex;
}
.result-content .left {
min-width: 30%;
text-align: center;
margin: 10px auto;
}
.achievement-value {
font-size: 1.5rem;
}
.score-value {
font-size: 1.4rem;
}

View File

@ -0,0 +1,30 @@
<div *ngIf="pvRecords">
<mat-card *ngFor="let item of pvRecords" routerLink="{{item.pvId}}">
<mat-card-title>
<div class="title">{{item.songInfo != null ? item.songInfo.song_name : 'pvId:' + item.pvId}}</div>
<div class="level">
{{item.edition === 1 ? edition[item.edition] : ''}}
{{difficulty[item.difficulty]}}
</div>
</mat-card-title>
<mat-card-content>
<div class="song-info">
<span
class="info">{{item.songInfo != null ? 'Lyric: ' + item.songInfo.lyrics + ' Song: ' + item.songInfo.music : ''}}</span>
</div>
<div class="result-content">
<div class="left">
Achievement:<br>
<span class="achievement-value">{{item.maxAttain|divaDecimal}}%</span>
</div>
<div class="left">
Score:<br>
<span class="score-value">{{item.maxScore}}</span>
</div>
</div>
</mat-card-content>
</mat-card>
<button (click)="load()" mat-flat-button>Load More</button>
</div>

View File

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DivaPvRecordComponent} from './diva-pv-record.component';
describe('DivaPvRecordComponent', () => {
let component: DivaPvRecordComponent;
let fixture: ComponentFixture<DivaPvRecordComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DivaPvRecordComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DivaPvRecordComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,56 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../../../api.service';
import {AuthenticationService} from '../../../auth/authentication.service';
import {MessageService} from '../../../message.service';
import {HttpParams} from '@angular/common/http';
import {DivaMusicDbService} from '../diva-music-db.service';
import {Difficulty, DivaPvRecord, Edition} from '../model/DivaPvRecord';
@Component({
selector: 'app-diva-pv-record',
templateUrl: './diva-pv-record.component.html',
styleUrls: ['./diva-pv-record.component.css']
})
export class DivaPvRecordComponent implements OnInit {
edition = Edition;
difficulty = Difficulty;
pvRecords: DivaPvRecord[] = [];
currentPage = 0;
totalPages = 0;
constructor(
private api: ApiService,
private auth: AuthenticationService,
private messageService: MessageService,
private musicDb: DivaMusicDbService
) {
}
ngOnInit() {
this.load();
}
load() {
const pdId = String(this.auth.currentUserValue.extId);
const param = new HttpParams().set('pdId', pdId).set('page', String(this.currentPage));
this.api.get('api/game/diva/pvRecord', param).subscribe(
data => {
if (data.content.length === 0) {
this.messageService.notice('No more record');
return;
}
this.currentPage = data.page + 1;
this.totalPages = data.totalPages;
data.content.forEach(x => {
x.songInfo = this.musicDb.getMusicDb().get(x.pvId);
this.pvRecords.push(x);
});
},
error => this.messageService.notice(error)
);
}
}

View File

@ -0,0 +1,30 @@
mat-card-title {
font-size: 1.2em;
text-align: right;
}
.song-info .title {
font-size: 1.2em;
}
.song-info .info {
opacity: 70%;
}
.result-content {
display: flex;
}
.result-content .left {
min-width: 30%;
text-align: center;
margin: auto;
}
.achievement-value {
font-size: 1.5rem;
}
.score-value {
font-size: 1.4rem;
}

View File

@ -0,0 +1,66 @@
<div *ngIf="playLogList">
<mat-card *ngFor="let item of playLogList" routerLink="/diva/record/{{item.pvId}}">
<mat-card-title>{{item.dateTime}}</mat-card-title>
<mat-card-content>
<div class="song-info">
<span class="title">{{item.songInfo != null ? item.songInfo.song_name : 'pvId:' + item.pvId}}</span><br>
<span
class="info">{{item.songInfo != null ? 'Lyric: ' + item.songInfo.lyrics + ' Song: ' + item.songInfo.music : ''}}</span>
</div>
<div class="result-content">
<div class="left">
Achievement:<br>
<span class="achievement-value">{{item.attainPoint|divaDecimal}}%</span>
<br>
<br>Score:<br>
<span class="score-value">{{item.score}}</span>
</div>
<table class="zebra">
<tr>
<th>Cool</th>
<td>{{item.coolCount}}</td>
<td>{{item.coolPercent|divaDecimal}}%</td>
</tr>
<tr>
<th>Fine</th>
<td>{{item.fineCount}}</td>
<td>{{item.finePercent|divaDecimal}}%</td>
</tr>
<tr>
<th>Safe</th>
<td>{{item.safeCount}}</td>
<td>{{item.safePercent|divaDecimal}}%</td>
</tr>
<tr>
<th>Sad</th>
<td>{{item.sadCount}}</td>
<td>{{item.sadPercent|divaDecimal}}%</td>
</tr>
<tr>
<th>Worst/Wrong</th>
<td>{{item.wrongCount}}</td>
<td>{{item.wrongPercent|divaDecimal}}%</td>
</tr>
<tr>
<th>Combo</th>
<td></td>
<td>{{item.maxCombo}}</td>
</tr>
<tr>
<th>Challenge Time</th>
<td></td>
<td>{{item.chanceTime}}</td>
</tr>
<tr>
<th>Hold</th>
<td></td>
<td>{{item.holdScore}}</td>
</tr>
</table>
</div>
</mat-card-content>
</mat-card>
<button (click)="load()" mat-flat-button>Load More</button>
</div>

Some files were not shown because too many files have changed in this diff Show More