Compare commits

...

52 Commits

Author SHA1 Message Date
Chris Chen 3325c63631 Fix reconnect issue. 2025-11-15 15:10:48 -08:00
Chris Chen 3a12b6a4ab WIP 2025-11-14 13:53:44 -08:00
Chris Chen 9ea2278dfb Update boss maint 2025-11-14 13:09:09 -08:00
Chris Chen a4391c84d0 Init Boss Fight 2025-11-14 07:37:48 -08:00
Chris Chen ee6dc58a21 WIP 2025-11-14 07:28:22 -08:00
Chris Chen 0d3995764b Add Hp bar for mob and Boss 2025-11-13 16:09:15 -08:00
Chris Chen f30c41afba Optmize 2025-11-13 15:44:39 -08:00
Chris Chen 2ef9968920 WIP 2025-11-12 18:22:33 -08:00
Chris Chen d8db9f650b WIP 2025-11-06 21:47:04 -08:00
Chris Chen 349510db56 WIP 2025-11-06 16:58:36 -08:00
Chris Chen b44834343a WIP 2025-11-06 07:10:28 -08:00
Chris Chen b41c01e6f7 WIP 2025-11-05 17:42:32 -08:00
Chris Chen 9ad991a70e WIP 2025-11-05 17:28:00 -08:00
Chris Chen 6806eeff8a WIP 2025-11-05 17:06:41 -08:00
Chris Chen 3c3c880a3c WIP 2025-11-05 17:01:51 -08:00
Chris Chen 542f24c12d WIP 2025-11-05 16:43:27 -08:00
Chris Chen 23e6da2808 WIP 2025-11-05 16:39:48 -08:00
Chris Chen 61604355c1 WIP 2025-11-05 16:26:06 -08:00
Chris Chen b4d52283aa WIP 2025-11-05 16:20:08 -08:00
Chris Chen b24753afe7 WIP 2025-11-05 15:49:51 -08:00
Chris Chen 89cb09adb6 WIP 2025-11-05 15:47:14 -08:00
Chris Chen 716e25f0ba WIP 2025-11-05 15:21:42 -08:00
Chris Chen d20f2a37c4 WIP 2025-11-05 08:04:55 -08:00
Chris Chen 701c36112c WIP 2025-11-04 22:14:55 -08:00
Chris Chen e5933104cc WIP 2025-11-04 18:13:43 -08:00
Chris Chen 46ec236ed5 WIP 2025-11-04 12:42:10 -08:00
Chris Chen b8b35645ac WIP 2025-11-04 07:42:24 -08:00
Chris Chen ed3c116d13 WIP 2025-11-03 21:43:24 -08:00
Chris Chen 719108fd6a WIP 2025-11-03 21:11:10 -08:00
Chris Chen f88cd21b33 Update MD2 editor 2025-11-03 15:35:52 -08:00
ChrisChen fd32ae5dcc Update src/app/services/crudServices/crud.service.ts 2025-11-03 17:10:18 +00:00
ChrisChen 9fec45a91f Update src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.html 2025-11-03 16:50:51 +00:00
ChrisChen ba3ad023ad Update src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.ts 2025-11-03 16:50:28 +00:00
Chris Chen ed90250876 MD2 editor 2025-11-02 09:44:59 -08:00
Chris Chen cdceaab2fd WIP 2025-11-02 09:37:55 -08:00
Chris Chen 70aa8adbba WIP 2025-11-02 09:13:42 -08:00
Chris Chen cd9021d9c0 WIP 2024-04-08 16:48:41 -07:00
Chris Chen 0ee2e7e545 Update angular.json 2024-04-07 09:59:09 -07:00
Chris Chen 8961490ff8 Update _units.scss 2024-04-07 09:50:56 -07:00
Chris Chen d1039a409b Upgrade to Angular 17 2024-04-06 17:45:52 -07:00
Chris Chen cca0de9812 Update libs 2024-04-06 16:55:21 -07:00
Chris Chen 3b37d7d798 Remove pages 2024-04-06 10:04:16 -07:00
Chris Chen e2f55f0b8b Upgrade to angular 16 2024-04-06 09:47:55 -07:00
Chris Chen 56d2bd17e4 Upgrade to Angular 15 2024-04-06 09:42:44 -07:00
Chris Chen c68d9ba749 Upgrade to angular 14 2024-04-06 09:35:41 -07:00
Chris Chen dc49c0a958 Upgrade to angular 13 2024-04-06 09:30:39 -07:00
Chris Chen 853b7069f9 Remove tslint-language-service 2024-04-06 09:16:06 -07:00
Chris Chen dfc1f269a0 Stable 2024-04-06 07:26:12 -07:00
Chris Chen d486fe9594 Update boss fight 2024-03-29 08:04:07 -07:00
Chris Chen 6a031ca478 WIP 2024-03-22 11:06:42 -07:00
Chris Chen b46392bc41 WIP 2024-03-21 17:47:13 -07:00
Chris Chen 6301d6008b WIP 2024-02-28 15:17:41 -08:00
616 changed files with 75819 additions and 63789 deletions
+12
View File
@@ -0,0 +1,12 @@
{
"mcpServers": {
"kendoai": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@progress/kendo-angular-mcp@latest"],
"env": {
"TELERIK_LICENSE_PATH":"C:/Users/Chris/AppData/Roaming/Telerik/telerik-license.txt"
}
}
}
}
+1
View File
@@ -23,6 +23,7 @@
!.vscode/extensions.json !.vscode/extensions.json
# misc # misc
/.angular/cache
/.sass-cache /.sass-cache
/connect.lock /connect.lock
/coverage /coverage
+18
View File
@@ -0,0 +1,18 @@
{
"extends": [
"development"
],
"hints": {
"axe/text-alternatives": [
"default",
{
"image-alt": "off"
}
]
},
"browserslist": [
"defaults",
"not ie 11",
"not ie <= 11"
]
}
+1
View File
@@ -5,6 +5,7 @@
"git.decorations.enabled": false, "git.decorations.enabled": false,
"workbench.editor.decorations.badges": false, "workbench.editor.decorations.badges": false,
"workbench.editor.decorations.colors": false, "workbench.editor.decorations.colors": false,
"typescript.preferences.includePackageJsonAutoImports": "on",
"vscode_custom_css.imports": [ "vscode_custom_css.imports": [
"file:///C:/VScode/custom.css" "file:///C:/VScode/custom.css"
], ],
+12 -51
View File
@@ -24,8 +24,8 @@
"src/web.config", "src/web.config",
{ {
"glob": "**/*", "glob": "**/*",
"input": "node_modules/leaflet/dist/images", "input": "node_modules/tinymce",
"output": "/assets/img/markers" "output": "/tinymce/"
} }
], ],
"styles": [ "styles": [
@@ -38,16 +38,13 @@
"node_modules/nebular-icons/scss/nebular-icons.scss", "node_modules/nebular-icons/scss/nebular-icons.scss",
"node_modules/pace-js/templates/pace-theme-flash.tmpl.css", "node_modules/pace-js/templates/pace-theme-flash.tmpl.css",
"node_modules/leaflet/dist/leaflet.css", "node_modules/leaflet/dist/leaflet.css",
"node_modules/@progress/kendo-theme-default/dist/all.css",
"src/app/@theme/styles/styles.scss", "src/app/@theme/styles/styles.scss",
"src/assets/styles/site.scss" "src/assets/styles/site.scss"
], ],
"scripts": [ "scripts": [
"node_modules/pace-js/pace.min.js", "node_modules/pace-js/pace.min.js",
"node_modules/tinymce/tinymce.min.js", "node_modules/tinymce/tinymce.min.js",
"node_modules/tinymce/themes/modern/theme.min.js",
"node_modules/tinymce/plugins/link/plugin.min.js",
"node_modules/tinymce/plugins/paste/plugin.min.js",
"node_modules/tinymce/plugins/table/plugin.min.js",
"node_modules/echarts/dist/echarts.min.js", "node_modules/echarts/dist/echarts.min.js",
"node_modules/echarts/dist/extension/bmap.min.js", "node_modules/echarts/dist/extension/bmap.min.js",
"node_modules/chart.js/dist/Chart.min.js" "node_modules/chart.js/dist/Chart.min.js"
@@ -57,7 +54,10 @@
"echarts", "echarts",
"lodash", "lodash",
"zrender/lib/svg/svg", "zrender/lib/svg/svg",
"zrender/lib/vml/vml" "zrender/lib/vml/vml",
"file-saver",
"eva-icons",
"rxjs-compat"
], ],
"vendorChunk": true, "vendorChunk": true,
"extractLicenses": false, "extractLicenses": false,
@@ -94,18 +94,18 @@
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"options": { "options": {
"browserTarget": "ngx-admin-demo:build" "buildTarget": "ngx-admin-demo:build"
}, },
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "ngx-admin-demo:build:production" "buildTarget": "ngx-admin-demo:build:production"
} }
} }
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
"options": { "options": {
"browserTarget": "ngx-admin-demo:build" "buildTarget": "ngx-admin-demo:build"
} }
}, },
"test": { "test": {
@@ -118,10 +118,6 @@
"scripts": [ "scripts": [
"node_modules/pace-js/pace.min.js", "node_modules/pace-js/pace.min.js",
"node_modules/tinymce/tinymce.min.js", "node_modules/tinymce/tinymce.min.js",
"node_modules/tinymce/themes/modern/theme.min.js",
"node_modules/tinymce/plugins/link/plugin.min.js",
"node_modules/tinymce/plugins/paste/plugin.min.js",
"node_modules/tinymce/plugins/table/plugin.min.js",
"node_modules/echarts/dist/echarts.min.js", "node_modules/echarts/dist/echarts.min.js",
"node_modules/echarts/dist/extension/bmap.min.js", "node_modules/echarts/dist/extension/bmap.min.js",
"node_modules/chart.js/dist/Chart.min.js" "node_modules/chart.js/dist/Chart.min.js"
@@ -144,50 +140,15 @@
"src/web.config", "src/web.config",
{ {
"glob": "**/*", "glob": "**/*",
"input": "node_modules/leaflet/dist/images", "input": "node_modules/tinymce",
"output": "/assets/img/markers" "output": "/tinymce/"
} }
] ]
} }
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"typeCheck": true,
"exclude": []
}
}
}
},
"ngx-admin-demo-e2e": {
"root": "",
"sourceRoot": "",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "./protractor.conf.js",
"devServerTarget": "ngx-admin-demo:serve"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"e2e/tsconfig.e2e.json"
],
"exclude": []
}
} }
} }
} }
}, },
"defaultProject": "ngx-admin-demo",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"prefix": "ngx", "prefix": "ngx",
+60418 -46416
View File
File diff suppressed because it is too large Load Diff
+120 -118
View File
@@ -1,120 +1,122 @@
{ {
"name": "ngx-admin", "name": "ngx-admin",
"version": "8.0.0", "version": "8.0.0",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/akveo/ngx-admin.git" "url": "git+https://github.com/akveo/ngx-admin.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/akveo/ngx-admin/issues" "url": "https://github.com/akveo/ngx-admin/issues"
}, },
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"conventional-changelog": "conventional-changelog", "conventional-changelog": "conventional-changelog",
"start": "ng serve", "start": "ng serve --host=127.0.0.1",
"build": "ng build --output-path \\\\ArkNAS\\docker\\nginx-proxy\\data\\ChurchAngular --prod", "build": "ng build",
"build:prod": "npm run build -- --configuration production --aot", "build:prod": "ng build --output-path \\\\ArkNAS\\docker\\nginx-proxy\\data\\ChurchAngular --configuration production --aot",
"test": "ng test", "test": "ng test",
"test:coverage": "rimraf coverage && npm run test -- --code-coverage", "test:coverage": "rimraf coverage && npm run test -- --code-coverage",
"lint": "ng lint", "lint": "ng lint",
"lint:fix": "ng lint ngx-admin-demo --fix", "lint:fix": "ng lint ngx-admin-demo --fix",
"lint:styles": "stylelint ./src/**/*.scss", "lint:styles": "stylelint ./src/**/*.scss",
"lint:ci": "npm run lint && npm run lint:styles", "lint:ci": "npm run lint && npm run lint:styles",
"pree2e": "webdriver-manager update --standalone false --gecko false", "pree2e": "webdriver-manager update --standalone false --gecko false",
"e2e": "ng e2e", "e2e": "ng e2e",
"docs": "compodoc -p src/tsconfig.app.json -d docs", "docs": "compodoc -p src/tsconfig.app.json -d docs",
"docs:serve": "compodoc -p src/tsconfig.app.json -d docs -s", "docs:serve": "compodoc -p src/tsconfig.app.json -d docs -s",
"prepush": "npm run lint:ci", "prepush": "echo 'Pre-push hook disabled - style linting skipped'",
"release:changelog": "npm run conventional-changelog -- -p angular -i CHANGELOG.md -s", "release:changelog": "npm run conventional-changelog -- -p angular -i CHANGELOG.md -s"
"postinstall": "ngcc --properties es2015 es5 browser module main --first-only --create-ivy-entry-points --tsconfig \"./src/tsconfig.app.json\"" },
}, "dependencies": {
"dependencies": { "@angular/animations": "^17.3.3",
"@angular/animations": "^12.2.16", "@angular/cdk": "17.3.3",
"@angular/cdk": "12.1.0", "@angular/common": "^17.3.3",
"@angular/common": "^12.2.16", "@angular/compiler": "^17.3.3",
"@angular/compiler": "^12.2.16", "@angular/core": "^17.3.3",
"@angular/core": "^12.2.16", "@angular/forms": "^17.3.3",
"@angular/forms": "^12.2.16", "@angular/platform-browser": "^17.3.3",
"@angular/google-maps": "^12.2.13", "@angular/platform-browser-dynamic": "^17.3.3",
"@angular/platform-browser": "^12.2.16", "@angular/router": "^17.3.3",
"@angular/platform-browser-dynamic": "^12.2.16", "@asymmetrik/ngx-leaflet": "3.0.1",
"@angular/router": "^12.2.16", "@microsoft/signalr": "^6.0.8",
"@asymmetrik/ngx-leaflet": "3.0.1", "@nebular/auth": "13.0.0",
"@microsoft/signalr": "^6.0.8", "@nebular/date-fns": "^13.0.0",
"@nebular/auth": "8.0.0", "@nebular/eva-icons": "13.0.0",
"@nebular/date-fns": "^9.0.3", "@nebular/security": "13.0.0",
"@nebular/eva-icons": "8.0.0", "@nebular/theme": "13.0.0",
"@nebular/security": "8.0.0", "@progress/kendo-angular-buttons": "^20.1.1",
"@nebular/theme": "8.0.0", "@progress/kendo-angular-dialog": "^20.1.1",
"@swimlane/ngx-charts": "^14.0.0", "@progress/kendo-angular-dropdowns": "^20.1.1",
"angular2-chartjs": "0.4.1", "@progress/kendo-angular-editor": "^20.1.1",
"angular2-qrcode": "^2.0.3", "@progress/kendo-angular-grid": "^20.1.1",
"bootstrap": "4.3.1", "@progress/kendo-angular-inputs": "^20.1.1",
"chart.js": "2.7.1", "@progress/kendo-angular-toolbar": "^20.1.1",
"ckeditor": "4.7.3", "@progress/kendo-licensing": "^1.7.1",
"classlist.js": "1.1.20150312", "@progress/kendo-svg-icons": "^4.5.0",
"core-js": "2.5.1", "@progress/kendo-theme-default": "^12.2.0",
"echarts": "^4.9.0", "@tinymce/tinymce-angular": "^7.0.0",
"eva-icons": "^1.1.3", "angular2-chartjs": "0.4.1",
"file-saver": "^2.0.5", "angular2-qrcode": "^2.0.3",
"intl": "1.2.5", "bootstrap": "4.3.1",
"ionicons": "2.0.1", "chart.js": "2.7.1",
"leaflet": "1.2.0", "core-js": "2.5.1",
"nebular-icons": "1.1.0", "echarts": "^4.9.0",
"ng-in-viewport": "^13.0.1", "eva-icons": "^1.1.3",
"ng2-ckeditor": "~1.2.9", "file-saver": "^2.0.5",
"ng2-completer": "^9.0.1", "intl": "1.2.5",
"ng2-smart-table": "^1.6.0", "ionicons": "2.0.1",
"ngx-echarts": "^4.2.2", "leaflet": "1.2.0",
"ngx-infinite-scroll": "^13.0.2", "nebular-icons": "1.1.0",
"ngx-mask": "^12.0.0", "ng-in-viewport": "^13.0.1",
"node-sass": "^4.14.1", "ng2-completer": "^9.0.1",
"normalize.css": "6.0.0", "ngx-echarts": "^4.2.2",
"pace-js": "1.0.2", "ngx-infinite-scroll": "^17.0.0",
"roboto-fontface": "0.8.0", "ngx-mask": "^12.0.0",
"rxjs": "6.6.2", "node-sass": "^4.14.1",
"rxjs-compat": "6.3.0", "normalize.css": "6.0.0",
"socicon": "3.0.5", "pace-js": "1.0.2",
"style-loader": "^1.3.0", "roboto-fontface": "0.8.0",
"tinymce": "4.5.7", "rxjs": "6.6.2",
"tslib": "^2.3.1", "rxjs-compat": "6.3.0",
"typeface-exo": "0.0.22", "socicon": "3.0.5",
"typeit": "^8.7.0", "style-loader": "^1.3.0",
"web-animations-js": "^2.3.2", "tinymce": "^7.0.0",
"zone.js": "~0.11.4" "tslib": "^2.3.1",
}, "typeface-exo": "0.0.22",
"devDependencies": { "typeit": "^8.7.0",
"@angular-devkit/build-angular": "^12.1.4", "zone.js": "~0.14.4"
"@angular/cli": "^12.2.17", },
"@angular/compiler-cli": "^12.2.16", "devDependencies": {
"@angular/language-service": "12.1.0", "@angular-devkit/build-angular": "^17.3.3",
"@compodoc/compodoc": "1.0.1", "@angular/cli": "^17.3.3",
"@fortawesome/fontawesome-free": "^5.2.0", "@angular/compiler-cli": "^17.3.3",
"@schematics/angular": "^14.1.3", "@angular/language-service": "17.3.3",
"@types/d3-color": "1.0.5", "@angular/localize": "^17.3.3",
"@types/jasmine": "~3.3.0", "@compodoc/compodoc": "1.0.1",
"@types/jasminewd2": "2.0.3", "@fortawesome/fontawesome-free": "^5.2.0",
"@types/leaflet": "1.2.3", "@schematics/angular": "^14.1.3",
"@types/node": "^12.12.70", "@types/d3-color": "1.0.5",
"codelyzer": "^6.0.2", "@types/jasmine": "~3.3.0",
"conventional-changelog-cli": "1.3.4", "@types/jasminewd2": "2.0.3",
"husky": "0.13.3", "@types/leaflet": "1.2.3",
"jasmine-core": "~3.6.0", "@types/node": "^18.19.30",
"jasmine-spec-reporter": "~5.0.0", "codelyzer": "^6.0.2",
"karma": "~6.3.19", "conventional-changelog-cli": "1.3.4",
"karma-chrome-launcher": "~3.1.1", "husky": "0.13.3",
"karma-cli": "1.0.1", "jasmine-core": "~5.1.2",
"karma-coverage-istanbul-reporter": "~3.0.2", "jasmine-spec-reporter": "~5.0.0",
"karma-jasmine": "~4.0.2", "karma": "~6.3.19",
"karma-jasmine-html-reporter": "^1.7.0", "karma-chrome-launcher": "~3.1.1",
"npm-run-all": "4.0.2", "karma-cli": "1.0.1",
"protractor": "~7.0.0", "karma-coverage-istanbul-reporter": "~3.0.2",
"rimraf": "2.6.1", "karma-jasmine": "~5.1.0",
"stylelint": "7.13.0", "karma-jasmine-html-reporter": "^2.1.0",
"ts-node": "3.2.2", "npm-run-all": "4.0.2",
"tslint": "~6.1.0", "protractor": "~7.0.0",
"tslint-language-service": "^0.9.9", "rimraf": "2.6.1",
"typescript": "~4.2.3||~4.3.0" "stylelint": "7.13.0",
} "ts-node": "3.2.2",
"typescript": "~5.4.4"
}
} }
+2 -2
View File
@@ -10,7 +10,7 @@ import {
LayoutService, LayoutService,
PlayerService, PlayerService,
SeoService, SeoService,
StateService, StateServiceForNB,
} from './utils'; } from './utils';
import { UserData } from './data/users'; import { UserData } from './data/users';
import { ElectricityData } from './data/electricity'; import { ElectricityData } from './data/electricity';
@@ -142,7 +142,7 @@ export const NB_CORE_PROVIDERS = [
LayoutService, LayoutService,
PlayerService, PlayerService,
SeoService, SeoService,
StateService, StateServiceForNB,
]; ];
@NgModule({ @NgModule({
+2 -2
View File
@@ -1,7 +1,7 @@
import { LayoutService } from './layout.service'; import { LayoutService } from './layout.service';
import { AnalyticsService } from './analytics.service'; import { AnalyticsService } from './analytics.service';
import { PlayerService } from './player.service'; import { PlayerService } from './player.service';
import { StateService } from './state.service'; import { StateServiceForNB } from './state.service';
import { SeoService } from './seo.service'; import { SeoService } from './seo.service';
export { export {
@@ -9,5 +9,5 @@ export {
AnalyticsService, AnalyticsService,
PlayerService, PlayerService,
SeoService, SeoService,
StateService, StateServiceForNB,
}; };
+3 -3
View File
@@ -1,11 +1,11 @@
import { Injectable, OnDestroy } from '@angular/core'; import { Injectable, OnDestroy } from '@angular/core';
import { of as observableOf, Observable, BehaviorSubject } from 'rxjs'; import { of as observableOf, Observable, BehaviorSubject } from 'rxjs';
import { takeWhile } from 'rxjs/operators'; import { takeWhile } from 'rxjs/operators';
import { NbLayoutDirectionService, NbLayoutDirection } from '@nebular/theme'; import { NbLayoutDirectionService, NbLayoutDirection } from '@nebular/theme';
@Injectable() @Injectable()
export class StateService implements OnDestroy { export class StateServiceForNB implements OnDestroy {
protected layouts: any = [ protected layouts: any = [
{ {
@@ -58,7 +58,7 @@ export class StateService implements OnDestroy {
} }
private updateSidebarIcons(direction: NbLayoutDirection) { private updateSidebarIcons(direction: NbLayoutDirection) {
const [ startSidebar, endSidebar ] = this.sidebars; const [startSidebar, endSidebar] = this.sidebars;
const isLtr = direction === NbLayoutDirection.LTR; const isLtr = direction === NbLayoutDirection.LTR;
const startIconClass = isLtr ? 'nb-layout-sidebar-left' : 'nb-layout-sidebar-right'; const startIconClass = isLtr ? 'nb-layout-sidebar-left' : 'nb-layout-sidebar-right';
const endIconClass = isLtr ? 'nb-layout-sidebar-right' : 'nb-layout-sidebar-left'; const endIconClass = isLtr ? 'nb-layout-sidebar-right' : 'nb-layout-sidebar-left';
@@ -1,6 +1,6 @@
@import '../../styles/themes'; @import "../../styles/themes";
@import '~@nebular/theme/styles/global/breakpoints'; @import "@nebular/theme/styles/global/breakpoints";
@import '~bootstrap/scss/mixins/breakpoints'; @import "bootstrap/scss/mixins/breakpoints";
@include nb-install-component() { @include nb-install-component() {
width: 100%; width: 100%;
@@ -0,0 +1,34 @@
export const DEFAULT_MEDIA_BREAKPOINTS = [
{
name: 'xs',
width: 0,
},
{
name: 'is',
width: 400,
},
{
name: 'sm',
width: 576,
},
{
name: 'md',
width: 768,
},
{
name: 'lg',
width: 992,
},
{
name: 'xl',
width: 1200,
},
{
name: 'xxl',
width: 1400,
},
{
name: 'xxxl',
width: 1600,
},
];
@@ -1,6 +1,6 @@
@import '~bootstrap/scss/mixins/breakpoints'; @import "bootstrap/scss/mixins/breakpoints";
@import '~@nebular/theme/styles/global/breakpoints'; @import "@nebular/theme/styles/global/breakpoints";
@import '../../styles/themes'; @import "../../styles/themes";
@include nb-install-component() { @include nb-install-component() {
display: flex; display: flex;
@@ -24,7 +24,7 @@
} }
::ng-deep nb-search button { ::ng-deep nb-search button {
padding: 0!important; padding: 0 !important;
} }
.header-container { .header-container {
@@ -6,9 +6,10 @@ import { LayoutService } from '../../../@core/utils';
import { map, takeUntil, first } from 'rxjs/operators'; import { map, takeUntil, first } from 'rxjs/operators';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { HeaderService } from '../../../services/header.service'; import { HeaderService } from '../../../services/header.service';
import { NbAuthService } from '@nebular/auth';
import { AuthService } from '../../../services/auth.service';
import { UserProfileAction } from '../../../entity/Auth'; import { UserProfileAction } from '../../../entity/Auth';
import { Router } from '@angular/router';
import { LoginUserService } from '../../../services/login-user.service';
import { AuthService } from '../../../services/auth.service';
@Component({ @Component({
selector: 'ngx-header', selector: 'ngx-header',
@@ -19,6 +20,7 @@ export class HeaderComponent implements OnInit, OnDestroy {
header: string = ''; header: string = '';
private destroy$: Subject<void> = new Subject<void>(); private destroy$: Subject<void> = new Subject<void>();
userPictureOnly: boolean = false; userPictureOnly: boolean = false;
isLessThanMd: boolean = false;
themes = [ themes = [
{ {
@@ -52,7 +54,7 @@ export class HeaderComponent implements OnInit, OnDestroy {
}]; }];
public get user() { public get user() {
return this.authService.userAccess; return this.loginUserService.userAccess;
} }
public get userFullName() { public get userFullName() {
if (this.user) { if (this.user) {
@@ -68,24 +70,22 @@ export class HeaderComponent implements OnInit, OnDestroy {
return null; return null;
} }
} }
constructor(private sidebarService: NbSidebarService, constructor(
private sidebarService: NbSidebarService,
private menuService: NbMenuService, private menuService: NbMenuService,
private themeService: NbThemeService, private themeService: NbThemeService,
private userService: UserData, private userService: UserData,
private layoutService: LayoutService, private layoutService: LayoutService,
private breakpointService: NbMediaBreakpointsService, private breakpointService: NbMediaBreakpointsService,
private headerService: HeaderService, private headerService: HeaderService,
private oAuthService: NbAuthService, private loginUserService: LoginUserService,
private authService: AuthService, private authService: AuthService,
protected router: Router,
) { ) {
this.headerService.headerChange$.pipe(takeUntil(this.destroy$)).subscribe(result => { this.headerService.headerChange$.pipe(takeUntil(this.destroy$)).subscribe(result => {
this.header = result; this.header = result;
}); });
this.menuService.onItemClick().pipe(takeUntil(this.destroy$))
.subscribe(result => {
if (result.item.title == 'Log out') this.logout();
});
} }
@@ -93,13 +93,18 @@ export class HeaderComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.currentTheme = this.themeService.currentTheme; this.currentTheme = this.themeService.currentTheme;
const { xl } = this.breakpointService.getBreakpointsMap(); const { md, xl } = this.breakpointService.getBreakpointsMap();
this.themeService.onMediaQueryChange() this.themeService.onMediaQueryChange()
.pipe( .pipe(
map(([, currentBreakpoint]) => currentBreakpoint.width < xl), map(([, currentBreakpoint]) => currentBreakpoint.width),
takeUntil(this.destroy$), takeUntil(this.destroy$),
) )
.subscribe((isLessThanXl: boolean) => this.userPictureOnly = isLessThanXl); .subscribe((screenWidth: number) => {
let isLessThanXl = screenWidth < xl;
this.isLessThanMd = screenWidth < md;
this.userPictureOnly = isLessThanXl
});
this.themeService.onThemeChange() this.themeService.onThemeChange()
.pipe( .pipe(
@@ -108,6 +113,22 @@ export class HeaderComponent implements OnInit, OnDestroy {
) )
.subscribe(themeName => this.currentTheme = themeName); .subscribe(themeName => this.currentTheme = themeName);
this.menuService.onItemClick().subscribe(result => {
if (this.isLessThanMd && result.tag == 'NavMenu' && result.item.link) {
this.toggleSidebar();
} else if (result.tag == 'UserProfileMenu') {
switch (result.item.data as UserProfileAction) {
case UserProfileAction.None: break;
case UserProfileAction.GoToProfile:
this.router.navigate(["/myapp/profile"]);
break;
case UserProfileAction.LogOut:
this.logout();
break;
default: break;
}
}
});
} }
ngOnDestroy() { ngOnDestroy() {
@@ -1,6 +1,6 @@
@import '../../styles/themes'; @import "../../styles/themes";
@import '~bootstrap/scss/mixins/breakpoints'; @import "bootstrap/scss/mixins/breakpoints";
@import '~@nebular/theme/styles/global/breakpoints'; @import "@nebular/theme/styles/global/breakpoints";
@include nb-install-component() { @include nb-install-component() {
.menu-sidebar ::ng-deep .scrollable { .menu-sidebar ::ng-deep .scrollable {
@@ -23,4 +23,4 @@ import { Component } from '@angular/core';
</nb-layout> </nb-layout>
`, `,
}) })
export class OneColumnLayoutComponent {} export class OneColumnLayoutComponent { }
@@ -1,6 +1,6 @@
@import '../../styles/themes'; @import "../../styles/themes";
@import '~bootstrap/scss/mixins/breakpoints'; @import "bootstrap/scss/mixins/breakpoints";
@import '~@nebular/theme/styles/global/breakpoints'; @import "@nebular/theme/styles/global/breakpoints";
@include nb-install-component() { @include nb-install-component() {
.menu-sidebar ::ng-deep .scrollable { .menu-sidebar ::ng-deep .scrollable {
@@ -1,6 +1,6 @@
@import '../../styles/themes'; @import "../../styles/themes";
@import '~bootstrap/scss/mixins/breakpoints'; @import "bootstrap/scss/mixins/breakpoints";
@import '~@nebular/theme/styles/global/breakpoints'; @import "@nebular/theme/styles/global/breakpoints";
@include nb-install-component() { @include nb-install-component() {
.menu-sidebar ::ng-deep .scrollable { .menu-sidebar ::ng-deep .scrollable {
@@ -1,6 +1,6 @@
@import '../../styles/themes'; @import "../../styles/themes";
@import '~bootstrap/scss/mixins/breakpoints'; @import "bootstrap/scss/mixins/breakpoints";
@import '~@nebular/theme/styles/global/breakpoints'; @import "@nebular/theme/styles/global/breakpoints";
@include nb-install-component() { @include nb-install-component() {
.menu-sidebar ::ng-deep .scrollable { .menu-sidebar ::ng-deep .scrollable {
+12 -13
View File
@@ -1,26 +1,25 @@
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,600,700&display=swap'); @import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,700&display=swap");
// themes - our custom or/and out of the box themes // themes - our custom or/and out of the box themes
@import 'themes'; @import "themes";
// framework component themes (styles tied to theme variables) // framework component themes (styles tied to theme variables)
@import '~@nebular/theme/styles/globals'; @import "@nebular/theme/styles/globals";
@import '~@nebular/auth/styles/globals'; @import "@nebular/auth/styles/globals";
@import '~bootstrap/scss/functions'; @import "bootstrap/scss/functions";
@import '~bootstrap/scss/variables'; @import "bootstrap/scss/variables";
@import '~bootstrap/scss/mixins'; @import "bootstrap/scss/mixins";
@import '~bootstrap/scss/grid'; @import "bootstrap/scss/grid";
// loading progress bar theme // loading progress bar theme
@import './pace.theme'; @import "./pace.theme";
@import './layout'; @import "./layout";
@import './overrides'; @import "./overrides";
// install the framework and custom global styles // install the framework and custom global styles
@include nb-install() { @include nb-install() {
// framework global styles // framework global styles
@include nb-theme-global(); @include nb-theme-global();
@include nb-auth-global(); @include nb-auth-global();
@@ -30,4 +29,4 @@
@include ngx-pace-theme(); @include ngx-pace-theme();
@include nb-overrides(); @include nb-overrides();
}; } ;
+82 -66
View File
@@ -1,88 +1,104 @@
// @nebular theming framework // @nebular theming framework
@import '~@nebular/theme/styles/theming'; @import "@nebular/theme/styles/theming";
// @nebular out of the box themes // @nebular out of the box themes
@import '~@nebular/theme/styles/themes'; @import "@nebular/theme/styles/themes";
$nb-themes: nb-register-theme(( $nb-themes: nb-register-theme(
layout-padding-top: 2.25rem, (
layout-padding-top: 2.25rem,
menu-item-icon-margin: 0 0.5rem 0 0, menu-item-icon-margin: 0 0.5rem 0 0,
card-height-tiny: 13.5rem, card-height-tiny: 13.5rem,
card-height-small: 21.1875rem, card-height-small: 21.1875rem,
card-height-medium: 28.875rem, card-height-medium: 28.875rem,
card-height-large: 36.5625rem, card-height-large: 36.5625rem,
card-height-giant: 44.25rem, card-height-giant: 44.25rem,
card-margin-bottom: 1.875rem, card-margin-bottom: 1.875rem,
card-header-with-select-padding-top: 0.5rem, card-header-with-select-padding-top: 0.5rem,
card-header-with-select-padding-bottom: 0.5rem, card-header-with-select-padding-bottom: 0.5rem,
select-min-width: 6rem, select-min-width: 6rem,
slide-out-background: #f7f9fc, slide-out-background: #f7f9fc,
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3, slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3, slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
), default, default); ),
default,
default
);
$nb-themes: nb-register-theme(( $nb-themes: nb-register-theme(
layout-padding-top: 2.25rem, (
layout-padding-top: 2.25rem,
menu-item-icon-margin: 0 0.5rem 0 0, menu-item-icon-margin: 0 0.5rem 0 0,
card-height-tiny: 13.5rem, card-height-tiny: 13.5rem,
card-height-small: 21.1875rem, card-height-small: 21.1875rem,
card-height-medium: 28.875rem, card-height-medium: 28.875rem,
card-height-large: 36.5625rem, card-height-large: 36.5625rem,
card-height-giant: 44.25rem, card-height-giant: 44.25rem,
card-margin-bottom: 1.875rem, card-margin-bottom: 1.875rem,
card-header-with-select-padding-top: 0.5rem, card-header-with-select-padding-top: 0.5rem,
card-header-with-select-padding-bottom: 0.5rem, card-header-with-select-padding-bottom: 0.5rem,
select-min-width: 6rem, select-min-width: 6rem,
slide-out-background: #252547, slide-out-background: #252547,
slide-out-shadow-color: 2px 0 3px #29157a, slide-out-shadow-color: 2px 0 3px #29157a,
slide-out-shadow-color-rtl: -2px 0 3px #29157a, slide-out-shadow-color-rtl: -2px 0 3px #29157a,
), cosmic, cosmic); ),
cosmic,
cosmic
);
$nb-themes: nb-register-theme(( $nb-themes: nb-register-theme(
layout-padding-top: 2.25rem, (
layout-padding-top: 2.25rem,
menu-item-icon-margin: 0 0.5rem 0 0, menu-item-icon-margin: 0 0.5rem 0 0,
card-height-tiny: 13.5rem, card-height-tiny: 13.5rem,
card-height-small: 21.1875rem, card-height-small: 21.1875rem,
card-height-medium: 28.875rem, card-height-medium: 28.875rem,
card-height-large: 36.5625rem, card-height-large: 36.5625rem,
card-height-giant: 44.25rem, card-height-giant: 44.25rem,
card-margin-bottom: 1.875rem, card-margin-bottom: 1.875rem,
card-header-with-select-padding-top: 0.5rem, card-header-with-select-padding-top: 0.5rem,
card-header-with-select-padding-bottom: 0.5rem, card-header-with-select-padding-bottom: 0.5rem,
select-min-width: 6rem, select-min-width: 6rem,
slide-out-background: linear-gradient(270deg, #edf1f7 0%, #e4e9f2 100%), slide-out-background: linear-gradient(270deg, #edf1f7 0%, #e4e9f2 100%),
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3, slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3, slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
), corporate, corporate); ),
corporate,
corporate
);
$nb-themes: nb-register-theme(( $nb-themes: nb-register-theme(
layout-padding-top: 2.25rem, (
layout-padding-top: 2.25rem,
menu-item-icon-margin: 0 0.5rem 0 0, menu-item-icon-margin: 0 0.5rem 0 0,
card-height-tiny: 13.5rem, card-height-tiny: 13.5rem,
card-height-small: 21.1875rem, card-height-small: 21.1875rem,
card-height-medium: 28.875rem, card-height-medium: 28.875rem,
card-height-large: 36.5625rem, card-height-large: 36.5625rem,
card-height-giant: 44.25rem, card-height-giant: 44.25rem,
card-margin-bottom: 1.875rem, card-margin-bottom: 1.875rem,
card-header-with-select-padding-top: 0.5rem, card-header-with-select-padding-top: 0.5rem,
card-header-with-select-padding-bottom: 0.5rem, card-header-with-select-padding-bottom: 0.5rem,
select-min-width: 6rem, select-min-width: 6rem,
slide-out-background: linear-gradient(270deg, #222b45 0%, #151a30 100%), slide-out-background: linear-gradient(270deg, #222b45 0%, #151a30 100%),
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3, slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3, slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
), dark, dark); ),
dark,
dark
);
+1 -1
View File
@@ -1,4 +1,4 @@
<ngx-one-column-layout> <ngx-one-column-layout>
<nb-menu [items]="MENU_ITEMS"></nb-menu> <nb-menu [items]="MENU_ITEMS" tag="NavMenu"></nb-menu>
<router-outlet></router-outlet> <router-outlet></router-outlet>
</ngx-one-column-layout> </ngx-one-column-layout>
+1 -11
View File
@@ -6,16 +6,12 @@ import { AdminComponent } from './admin.component';
import { NbMenuModule, NbInputModule, NbCardModule, NbButtonModule, NbActionsModule, NbCheckboxModule, NbRadioModule, NbDatepickerModule, NbSelectModule, NbIconModule, NbTagModule, NbStepperModule, NbListModule, NbSpinnerModule, NbDialogModule, NbUserModule } from '@nebular/theme'; import { NbMenuModule, NbInputModule, NbCardModule, NbButtonModule, NbActionsModule, NbCheckboxModule, NbRadioModule, NbDatepickerModule, NbSelectModule, NbIconModule, NbTagModule, NbStepperModule, NbListModule, NbSpinnerModule, NbDialogModule, NbUserModule } from '@nebular/theme';
import { ThemeModule } from '../@theme/theme.module'; import { ThemeModule } from '../@theme/theme.module';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { BestListDlgComponent } from './happiness-groups/best-list-dlg/best-list-dlg.component';
import { AlertDlgModule } from '../ui/alert-dlg/alert-dlg.module'; import { AlertDlgModule } from '../ui/alert-dlg/alert-dlg.module';
import { HappinessWeekEditorComponent } from './happiness-groups/happiness-week-editor/happiness-week-editor.component';
import { HappinessWeekListDlgComponent } from './happiness-groups/happiness-week-list-dlg/happiness-week-list-dlg.component';
import { CellGroupRoutineEventsComponent } from './cell-group-routine-events/cell-group-routine-events.component'; import { CellGroupRoutineEventsComponent } from './cell-group-routine-events/cell-group-routine-events.component';
import { FancyTableModule } from '../ui/fancy-table/fancy-table.module'; import { FancyTableModule } from '../ui/fancy-table/fancy-table.module';
import { FamilyMembersComponent } from './family-members/family-members.component'; import { FamilyMembersComponent } from './family-members/family-members.component';
import { FamilyMemberEditorComponent } from './family-members/family-member-editor/family-member-editor.component'; import { FamilyMemberEditorComponent } from './family-members/family-member-editor/family-member-editor.component';
import { DropDownListModule } from '../ui/drop-down-list/drop-down-list.module'; import { DropDownListModule } from '../ui/drop-down-list/drop-down-list.module';
import { NgxMaskModule } from 'ngx-mask';
import { PastoralDomainsComponent } from './pastoral-domains/pastoral-domains.component'; import { PastoralDomainsComponent } from './pastoral-domains/pastoral-domains.component';
import { PastoralDomainEditorComponent } from './pastoral-domains/pastoral-domain-editor/pastoral-domain-editor.component'; import { PastoralDomainEditorComponent } from './pastoral-domains/pastoral-domain-editor/pastoral-domain-editor.component';
import { AssignMemberCellGroupComponent } from './family-members/assign-member-cell-group/assign-member-cell-group.component'; import { AssignMemberCellGroupComponent } from './family-members/assign-member-cell-group/assign-member-cell-group.component';
@@ -26,14 +22,9 @@ import { MaskDirectiveModule } from '../directives/mask-directive/mask-directive
import { DateInputModule } from '../ui/date-input/date-input.module'; import { DateInputModule } from '../ui/date-input/date-input.module';
import { LineMessagingAccountComponent } from './lines/line-messaging-account/line-messaging-account.component'; import { LineMessagingAccountComponent } from './lines/line-messaging-account/line-messaging-account.component';
import { LineMessagingAccountEditorComponent } from './lines/line-messaging-account-editor/line-messaging-account-editor.component'; import { LineMessagingAccountEditorComponent } from './lines/line-messaging-account-editor/line-messaging-account-editor.component';
import { WeekTaskEditorComponent } from './happiness-groups/week-task-editor/week-task-editor.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
AdminComponent, AdminComponent,
BestListDlgComponent,
HappinessWeekEditorComponent,
HappinessWeekListDlgComponent,
CellGroupRoutineEventsComponent, CellGroupRoutineEventsComponent,
FamilyMembersComponent, FamilyMembersComponent,
FamilyMemberEditorComponent, FamilyMemberEditorComponent,
@@ -44,7 +35,7 @@ import { WeekTaskEditorComponent } from './happiness-groups/week-task-editor/wee
LogDetailComponent, LogDetailComponent,
LineMessagingAccountComponent, LineMessagingAccountComponent,
LineMessagingAccountEditorComponent, LineMessagingAccountEditorComponent,
WeekTaskEditorComponent], ],
imports: [ imports: [
CommonModule, CommonModule,
FormsModule, FormsModule,
@@ -69,7 +60,6 @@ import { WeekTaskEditorComponent } from './happiness-groups/week-task-editor/wee
AlertDlgModule, AlertDlgModule,
FancyTableModule, FancyTableModule,
DropDownListModule, DropDownListModule,
NgxMaskModule,
CurrencyInputModule, CurrencyInputModule,
MaskDirectiveModule, MaskDirectiveModule,
DateInputModule DateInputModule
@@ -3,14 +3,12 @@ import { ActivatedRoute } from '@angular/router';
import { NbDialogService } from '@nebular/theme'; import { NbDialogService } from '@nebular/theme';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { inherits } from 'util';
import { StateService } from '../../@core/utils';
import { CellGroupRoutineEvents } from '../../entity/CellGroupRoutineEvents'; import { CellGroupRoutineEvents } from '../../entity/CellGroupRoutineEvents';
import { CellGroupRoutineEventsService } from '../../services/crudServices/cell-group-routine-events.service'; import { CellGroupRoutineEventsService } from '../../services/crudServices/cell-group-routine-events.service';
import { MsgBoxService } from '../../services/msg-box.service'; import { MsgBoxService } from '../../services/msg-box.service';
import { StateService } from '../../services/state.service';
import { FancySettings } from '../../ui/fancy-table/fancy-settings.model'; import { FancySettings } from '../../ui/fancy-table/fancy-settings.model';
import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component'; import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component';
import { ObjectUtils } from '../../utilities/object-utils';
@Component({ @Component({
selector: 'ngx-cell-group-routine-events', selector: 'ngx-cell-group-routine-events',
@@ -2,11 +2,10 @@ import { Component, OnInit } from '@angular/core';
import { NbDialogRef } from '@nebular/theme'; import { NbDialogRef } from '@nebular/theme';
import { DropDownOption } from '../../../entity/dropDownOption'; import { DropDownOption } from '../../../entity/dropDownOption';
import { DomainMemberRelationship, PastoralDomain } from '../../../entity/PastoralDomain'; import { DomainMemberRelationship, PastoralDomain } from '../../../entity/PastoralDomain';
import { DialogComponent } from '../../../pages/modal-overlays/dialog/dialog.component';
import { DomainMemberShipService } from '../../../services/crudServices/pastoral-domain.service';
import { ArrayUtils } from '../../../utilities/array-utils'; import { ArrayUtils } from '../../../utilities/array-utils';
import { first } from "rxjs/operators" import { first } from "rxjs/operators"
import { FamilyMember } from '../../../entity/Member'; import { FamilyMember } from '../../../entity/Member';
import { DomainMemberShipService } from '../../../services/crudServices/domain-member-ship.service';
@Component({ @Component({
selector: 'ngx-assign-member-cell-group', selector: 'ngx-assign-member-cell-group',
@@ -3,12 +3,12 @@ import { ActivatedRoute } from '@angular/router';
import { NbDialogService } from '@nebular/theme'; import { NbDialogService } from '@nebular/theme';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { StateService } from '../../@core/utils';
import { FamilyMember, Gender } from '../../entity/Member'; import { FamilyMember, Gender } from '../../entity/Member';
import { PastoralDomain } from '../../entity/PastoralDomain'; import { PastoralDomain } from '../../entity/PastoralDomain';
import { FamilyMemberService } from '../../services/crudServices/family-member.service'; import { FamilyMemberService } from '../../services/crudServices/family-member.service';
import { PastoralDomainService } from '../../services/crudServices/pastoral-domain.service'; import { PastoralDomainService } from '../../services/crudServices/pastoral-domain.service';
import { MsgBoxService } from '../../services/msg-box.service'; import { MsgBoxService } from '../../services/msg-box.service';
import { StateService } from '../../services/state.service';
import { FancySettings } from '../../ui/fancy-table/fancy-settings.model'; import { FancySettings } from '../../ui/fancy-table/fancy-settings.model';
import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component'; import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component';
import { ObjectUtils } from '../../utilities/object-utils'; import { ObjectUtils } from '../../utilities/object-utils';
@@ -1,4 +0,0 @@
nb-card {
max-height: 90vh;
width: 700px;
}
@@ -1,84 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { NbDialogRef, NbToastrService } from '@nebular/theme';
import { first } from 'rxjs/operators';
import { environment } from '../../../../environments/environment';
import { HappinessBEST } from '../../../entity/HappinessGroup';
import { PastoralDomain } from '../../../entity/PastoralDomain';
import { BestService } from '../../../services/crudServices/best.service';
import { HappinessGroupService } from '../../../services/crudServices/happiness-group.service';
import { LineGroup, LineService } from '../../../services/line.service';
import { MsgBoxService } from '../../../services/msg-box.service';
import { ADButtons, AlertDlgComponent } from '../../../ui/alert-dlg/alert-dlg.component';
@Component({
selector: 'ngx-best-list-dlg',
templateUrl: './best-list-dlg.component.html',
styleUrls: ['./best-list-dlg.component.scss']
})
export class BestListDlgComponent implements OnInit {
processing: boolean = false;
isAdding: boolean = false;
group: PastoralDomain;
datum: HappinessBEST;
constructor(
private dlgRef: NbDialogRef<BestListDlgComponent>,
private msgBpxService: MsgBoxService,
private happinessGroupService: HappinessGroupService,
private toastrService: NbToastrService,
private bestService: BestService,
private lineService: LineService
) { }
ngOnInit(): void {
this.group.bests = this.group.bests.sort((a, b) => 0 - (a.name < b.name ? 1 : -1));
}
close() {
this.dlgRef.close();
}
update() {
this.dlgRef.close(this.datum);
}
addBest() {
this.msgBpxService.showInputbox('Add Best', 'Please input Best\'s Name').pipe(first()).subscribe(result => {
if (result) {
let obj = { groupId: this.group.id, id: '', name: result } as HappinessBEST;
this.bestService.createOrUpdate(obj).pipe(first()).subscribe(result => {
this.dlgRef.close();
});
}
});
}
copyBestUrl(best: HappinessBEST) {
var dummy = document.createElement("textarea");
document.body.appendChild(dummy);
dummy.value = `${environment.invitationUrl}${best.id}`;
dummy.select();
document.execCommand("copy");
document.body.removeChild(dummy);
this.toastrService.success(dummy.value, "複製地址至剪貼簿!");
}
copyBestList() {
let list = "".concat(...this.group.bests.map(b => `${b.name}\t\t: ${environment.invitationUrl}${b.id}\r\n`));
var dummy = document.createElement("textarea");
document.body.appendChild(dummy);
dummy.value = list;
dummy.select();
document.execCommand("copy");
document.body.removeChild(dummy);
this.toastrService.success(dummy.value, "複製地址至剪貼簿!");
this.lineService.pushLineMessage(LineGroup.Chris, list);
}
downloadBestQRCode(best: HappinessBEST) {
this.happinessGroupService.getBestQrCode(best.name + ".png", best.id);
}
}
@@ -1,4 +0,0 @@
nb-card {
max-height: 90vh;
width: 700px;
}
@@ -1,52 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { NbDialogRef, NbDialogService } from '@nebular/theme';
import { first } from 'rxjs/operators';
import { HappinessWeek } from '../../../entity/HappinessGroup';
import { PastoralDomain } from '../../../entity/PastoralDomain';
import { NumberUtils } from '../../../utilities/number-utils';
import { HappinessWeekEditorComponent } from '../happiness-week-editor/happiness-week-editor.component';
import { WeekTaskEditorComponent } from '../week-task-editor/week-task-editor.component';
@Component({
selector: 'ngx-happiness-week-list-dlg',
templateUrl: './happiness-week-list-dlg.component.html',
styleUrls: ['./happiness-week-list-dlg.component.scss']
})
export class HappinessWeekListDlgComponent implements OnInit {
group: PastoralDomain;
constructor(
private dlgRef: NbDialogRef<any>,
private dlgService: NbDialogService
) { }
ngOnInit(): void {
}
close() {
this.dlgRef.close();
}
openEditDlg(week: HappinessWeek) {
this.dlgService.open(HappinessWeekEditorComponent, {
context: {
data: week
}
}).onClose.pipe(first()).subscribe(result => {
if (result) this.close();
});
}
openTaskDlg(week: HappinessWeek) {
this.dlgService.open(WeekTaskEditorComponent, {
context: {
data: week,
allData: this.group.happinessWeeks
}
}).onClose.pipe(first()).subscribe(result => {
});
}
weekDisplay(seq: number) {
return `W${seq} ${this.group.happinessWeeks[seq - 1].topic}`;
}
}
@@ -1,38 +0,0 @@
<form #form="ngForm">
<nb-card>
<nb-card-header>
第 {{data.seq}} 周 {{data.topic}} 分工
</nb-card-header>
<nb-card-body *ngIf="data">
<div class="row" *ngFor="let task of data.tasks;let i = index">
<div class="col-md-4">
<div class='form-group'>
<label for='tasker{{i}}' class='label'>{{getTaskTitle(task.type)}}</label>
<op-drop-down name="tasker{{i}}" [editable]="true" [(ngModel)]="task.tasker"
[source]="taskrOptions">
</op-drop-down>
</div>
</div>
<div class="col-md-8">
<div class="form-group">
<label for="content{{i}}" class="label">內容</label>
<input type="text" name="content{{i}}" nbInput fullWidth id="content{{i}}"
[(ngModel)]="task.content">
</div>
</div>
</div>
</nb-card-body>
<nb-card-footer>
<button class="float-right" nbButton hero status="danger" size="small" (click)="close()"
[nbSpinner]="processing">
Close
</button>
<button class="float-right mr-2" nbButton hero status="primary" size="small"
(click)="!form.invalid&&!processing&&update()" [disabled]="form.invalid" [nbSpinner]="processing">
Save
</button>
</nb-card-footer>
</nb-card>
</form>
@@ -1,79 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { NbDialogRef } from '@nebular/theme';
import { HappinessTask, HappinessTaskType, HappinessWeek } from '../../../entity/HappinessGroup';
import { HappinessTaskService } from '../../../services/crudServices/happiness-group.service';
import { first } from 'rxjs/operators';
import { DropDownOption } from '../../../entity/dropDownOption';
import { StringUtils } from '../../../utilities/string-utils';
@Component({
selector: 'ngx-week-task-editor',
templateUrl: './week-task-editor.component.html',
styleUrls: ['./week-task-editor.component.scss']
})
export class WeekTaskEditorComponent implements OnInit {
processing: boolean = false;
allData: HappinessWeek[];
data: HappinessWeek;
taskrOptions: DropDownOption[] = [];
constructor(
private happinessTaskService: HappinessTaskService,
private dlgRef: NbDialogRef<WeekTaskEditorComponent>
) {
}
ngOnInit(): void {
if (this.data.tasks.length == 0) {
this.data.tasks = [];
for (let i = HappinessTaskType.IceBreak; i <= HappinessTaskType.Dessert; i++) {
this.data.tasks.push(
{
weekId: this.data.id,
id: 'new',
type: i as HappinessTaskType,
tasker: '',
content: ''
} as HappinessTask
)
}
}
let names = [] as string[];
this.allData.forEach(week => {
if (week.tasks) {
names = names.concat(week.tasks.map(t => t.tasker.trim()));
}
});
this.taskrOptions = names.filter(function (item, pos) {
return !StringUtils.isNullOrWhitespace(item) && names.indexOf(item) == pos;
}).map(name => new DropDownOption(name, name));
}
close() {
this.dlgRef.close();
}
update() {
if (this.processing == false) {
this.processing = true;
this.happinessTaskService.createOrUpdateAll(this.data.tasks).pipe(first()).subscribe(result => {
this.processing = false;
this.dlgRef.close(true);
});
}
}
getTaskTitle(t: HappinessTaskType) {
switch (t) {
case HappinessTaskType.IceBreak: return '帶遊戲';
case HappinessTaskType.Worship: return '唱歌';
case HappinessTaskType.Testimony: return '見證';
case HappinessTaskType.Message: return '信息';
case HappinessTaskType.Gift: return '準備禮物';
case HappinessTaskType.Dessert: return '準備點心';
default: break;
}
}
}
@@ -33,7 +33,7 @@ export class LineMessagingAccountEditorComponent implements OnInit {
this.processing = true; this.processing = true;
let func = this.isAdding ? this.lineMessagingAccountService.createOrUpdate(this.data) : this.lineMessagingAccountService.update(this.data); let func = this.lineMessagingAccountService.createOrUpdate(this.data);//this.isAdding ? this.lineMessagingAccountService.createOrUpdate(this.data) : this.lineMessagingAccountService.update(this.data);
func.pipe(first()).subscribe(result => { func.pipe(first()).subscribe(result => {
this.processing = false; this.processing = false;
if (result) { if (result) {
+1 -1
View File
@@ -3,11 +3,11 @@ import { ActivatedRoute } from '@angular/router';
import { NbDialogService } from '@nebular/theme'; import { NbDialogService } from '@nebular/theme';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { StateService } from '../../@core/utils';
import { Log, LogLevel } from '../../entity/Log'; import { Log, LogLevel } from '../../entity/Log';
import { ScreenBase } from '../../ScreenBase'; import { ScreenBase } from '../../ScreenBase';
import { LogService } from '../../services/crudServices/log.service'; import { LogService } from '../../services/crudServices/log.service';
import { MsgBoxService } from '../../services/msg-box.service'; import { MsgBoxService } from '../../services/msg-box.service';
import { StateService } from '../../services/state.service';
import { FancyTag } from '../../ui/fancy-table/fancy-row-column.model'; import { FancyTag } from '../../ui/fancy-table/fancy-row-column.model';
import { FancySettings } from '../../ui/fancy-table/fancy-settings.model'; import { FancySettings } from '../../ui/fancy-table/fancy-settings.model';
import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component'; import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component';
@@ -3,17 +3,18 @@ import { ActivatedRoute } from '@angular/router';
import { NbDialogService } from '@nebular/theme'; import { NbDialogService } from '@nebular/theme';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { StateService } from '../../@core/utils';
import { FamilyMember } from '../../entity/Member'; import { FamilyMember } from '../../entity/Member';
import { DomainType, PastoralDomain } from '../../entity/PastoralDomain'; import { DomainType, PastoralDomain } from '../../entity/PastoralDomain';
import { BestListDlgComponent } from '../../happiness/best-list-dlg/best-list-dlg.component';
import { HappinessWeekListDlgComponent } from '../../happiness/happiness-week-list-dlg/happiness-week-list-dlg.component';
import { DomainMemberShipService } from '../../services/crudServices/domain-member-ship.service';
import { FamilyMemberService } from '../../services/crudServices/family-member.service'; import { FamilyMemberService } from '../../services/crudServices/family-member.service';
import { PastoralDomainService } from '../../services/crudServices/pastoral-domain.service'; import { PastoralDomainService } from '../../services/crudServices/pastoral-domain.service';
import { MsgBoxService } from '../../services/msg-box.service'; import { MsgBoxService } from '../../services/msg-box.service';
import { StateService } from '../../services/state.service';
import { FancySettings } from '../../ui/fancy-table/fancy-settings.model'; import { FancySettings } from '../../ui/fancy-table/fancy-settings.model';
import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component'; import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component';
import { ObjectUtils } from '../../utilities/object-utils'; import { ObjectUtils } from '../../utilities/object-utils';
import { BestListDlgComponent } from '../happiness-groups/best-list-dlg/best-list-dlg.component';
import { HappinessWeekListDlgComponent } from '../happiness-groups/happiness-week-list-dlg/happiness-week-list-dlg.component';
import { PastoralDomainEditorComponent } from './pastoral-domain-editor/pastoral-domain-editor.component'; import { PastoralDomainEditorComponent } from './pastoral-domain-editor/pastoral-domain-editor.component';
@Component({ @Component({
@@ -35,7 +36,8 @@ export class PastoralDomainsComponent implements OnInit {
private dlgService: NbDialogService, private dlgService: NbDialogService,
protected stateService: StateService, protected stateService: StateService,
protected route: ActivatedRoute, protected route: ActivatedRoute,
private memberService: FamilyMemberService private memberService: FamilyMemberService,
private memberShipService: DomainMemberShipService
) { ) {
} }
@@ -87,6 +89,15 @@ export class PastoralDomainsComponent implements OnInit {
callback: (datum, element) => { callback: (datum, element) => {
this.openHappinessWeekListDlg(datum); this.openHappinessWeekListDlg(datum);
}, },
},
{
enabled: true,
id: 'members',
title: 'Members',
icon: 'people-outline',
callback: (datum, element) => {
this.openMemberAssignDlg(datum);
},
} }
], ],
onContextMenuOpening: (datum, menuItems) => { onContextMenuOpening: (datum, menuItems) => {
@@ -94,9 +105,18 @@ export class PastoralDomainsComponent implements OnInit {
menuItems.find(i => i.id == 'edit').visible = !!datum; menuItems.find(i => i.id == 'edit').visible = !!datum;
menuItems.find(i => i.id == 'happinessBest').visible = datum.type == DomainType.HappinessGroup; menuItems.find(i => i.id == 'happinessBest').visible = datum.type == DomainType.HappinessGroup;
menuItems.find(i => i.id == 'happinessWeek').visible = datum.type == DomainType.HappinessGroup; menuItems.find(i => i.id == 'happinessWeek').visible = datum.type == DomainType.HappinessGroup;
//menuItems.find(i => i.id == 'members').visible = datum.type == DomainType.HappinessGroup;
return datum; return datum;
}, },
columns: [ columns: [
{
name: 'type',
title: 'Type',
type: 'number',
group: true,
visible: false,
groupValuePrepareFunction: (v) => this.getDomainTypeName(v.type)
},
{ {
name: 'name', name: 'name',
title: 'Name', title: 'Name',
@@ -108,7 +128,7 @@ export class PastoralDomainsComponent implements OnInit {
name: 'description', name: 'description',
title: 'Description', title: 'Description',
type: 'text', type: 'text',
widthPx: 200, widthPct: 70,
//valuePrepareFunction: (item) => , //valuePrepareFunction: (item) => ,
}, },
{ {
@@ -136,7 +156,6 @@ export class PastoralDomainsComponent implements OnInit {
// }, // },
], ],
}; };
//#endregion //#endregion
@@ -222,5 +241,33 @@ export class PastoralDomainsComponent implements OnInit {
}); });
} }
private getDomainTypeName(type: DomainType): string {
switch (type) {
case DomainType.CellGroup: return "細胞小組";
case DomainType.HappinessGroup: return "幸福小組";
case DomainType.CellGroupCoworker: return "細胞同工";
case DomainType.ChurchCoworker: return "教會事工";
case DomainType.Administrator: return "管理員";
default: break;
}
}
private openMemberAssignDlg(datum: PastoralDomain) {
// this.msgBoxService.showListBox<FamilyMember>(
// `Members in ${datum.name}`,
// this.members,
// 'id',
// datum.familyMembers.map(m => m.id),
// (e) => `${e.firstName} ${e.lastName} (${e.email})`
// ).pipe(first()).subscribe(result => {
// if (result) {
// datum.familyMembers = result;
// this.memberShipService.updateMembersInGroup(datum).pipe(first()).subscribe(result => {
// });
// }
// });
}
} }
+2 -7
View File
@@ -17,11 +17,6 @@ export const routes: Routes = [
loadChildren: () => import('./invitation/invitation.module') loadChildren: () => import('./invitation/invitation.module')
.then(m => m.InvitationModule), .then(m => m.InvitationModule),
}, },
{
path: 'pages',
loadChildren: () => import('./pages/pages.module')
.then(m => m.PagesModule),
},
{ {
path: 'auth', path: 'auth',
component: NbAuthComponent, component: NbAuthComponent,
@@ -73,11 +68,11 @@ export const routes: Routes = [
path: 'games', loadChildren: () => import('./games/games.module').then(m => m.GamesModule), path: 'games', loadChildren: () => import('./games/games.module').then(m => m.GamesModule),
canActivateChild: [AuthGuardService], canActivateChild: [AuthGuardService],
data: { data: {
roles: Role.Admin | Role.Pastor roles: Role.All
} }
}, },
{ {
path: 'CellGroup', loadChildren: () => import('./cell-group/cell-group.module').then(m => m.CellGroupModule), path: 'myapp', loadChildren: () => import('./cell-group/cell-group.module').then(m => m.CellGroupModule),
canActivateChild: [AuthGuardService], canActivateChild: [AuthGuardService],
data: { data: {
roles: Role.All roles: Role.All
+4 -1
View File
@@ -70,7 +70,10 @@ const socialLinks = [
NbChatModule.forRoot({ NbChatModule.forRoot({
messageGoogleMapKey: 'AIzaSyA_wNuCzia92MAmdLRemailRGvCF7wCZPY', messageGoogleMapKey: 'AIzaSyA_wNuCzia92MAmdLRemailRGvCF7wCZPY',
}), }),
NgxMaskModule.forRoot(maskConfig), NbDialogModule.forRoot({
closeOnBackdropClick: false,
closeOnEsc: false
}),
NbDateFnsDateModule.forRoot({ format: 'MM/dd/yyyy' }), NbDateFnsDateModule.forRoot({ format: 'MM/dd/yyyy' }),
CoreModule.forRoot(), CoreModule.forRoot(),
ThemeModule.forRoot(), ThemeModule.forRoot(),
+90
View File
@@ -0,0 +1,90 @@
import { ActivatedRoute } from "@angular/router";
import { Observable, Subject } from "rxjs";
import { PastoralDomainService } from "../services/crudServices/pastoral-domain.service";
import { StateService } from "../services/state.service";
import { first } from 'rxjs/operators';
import { PastoralDomain } from "../entity/PastoralDomain";
import { Injectable } from "@angular/core";
@Injectable()
export abstract class MyAppBase {
/**
*
*/
constructor(
protected stateService: StateService,
protected route: ActivatedRoute,
protected pastoralDomainService: PastoralDomainService
) {
// super(pastoralDomainService, stateService, route);
// this.isSnapshotView = true;
}
isLoading: boolean = true;
redirectIfNoDomains: boolean = true;
pageGroupId: string;
private domainInitialized = new Subject();;
ngOnInit(): void {
this.isLoading = true;
// this.route.paramMap.subscribe((params: ParamMap) => {
// this.id = +params.get('id')
// });
this.route.paramMap.pipe(first()).subscribe(params => {
if (params.get('groupId')) {
this.pageGroupId = params.get('groupId');
}
this.pastoralDomainService.initialized.takeUntil(this.domainInitialized).subscribe(result => {
if (result) {
this.domainInitialized.next();
if (this.redirectIfNoDomains) {
if (this.pageGroupId && !this.domains.find(d => d.id == this.pageGroupId)) {
this.pastoralDomainService.joinDomain(this.pageGroupId).pipe(first()).subscribe(result => {
this.pageOnInit();
});
return;
} else if (this.domains.length == 0) {
//TODO:SetUser
return;
}
}
this.pageOnInit();
}
});
});
}
public get domains(): PastoralDomain[] {
return this.pastoralDomainService.domains;
}
abstract pageOnInit(): void;
get saveMethod(): Observable<any> {
throw new Error("Method not implemented.");
}
public get happinessGroup(): PastoralDomain {
return this.pastoralDomainService.myHappinessGroup;
}
public get cellGroup(): PastoralDomain {
return this.pastoralDomainService.myCellGroup;
}
public get currentDomain(): PastoralDomain {
return this.domains.find(d => d.id == this.pageGroupId);
}
public get currentDomainName(): string {
if (this.domains && this.pageGroupId) {
//let group = this.domains.find(d => d.id == this.pageGroupId);
return this.domains.find(d => d.id == this.pageGroupId)?.name;
}
return "";
}
}
@@ -2,25 +2,58 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { NbMenuItem } from '@nebular/theme'; import { NbMenuItem } from '@nebular/theme';
import { Role } from '../entity/Auth'; import { Role } from '../entity/Auth';
import { DomainType } from '../entity/PastoralDomain';
import { CellGroupComponent } from './cell-group.component'; import { CellGroupComponent } from './cell-group.component';
import { DinnerComponent } from './dinner/dinner.component'; import { DinnerComponent } from './dinner/dinner.component';
import { FinanceComponent } from './finance/finance.component';
import { GoogleLoginComponent } from './google-login/google-login.component'; import { GoogleLoginComponent } from './google-login/google-login.component';
import { BestListComponent, HappinessWeekListComponent } from './happiness-group/happiness-group.component';
import { PrayerComponent } from './prayer/prayer.component'; import { PrayerComponent } from './prayer/prayer.component';
import { UserProfileComponent } from './user-profile/user-profile.component';
export class CellGroupRoutingConfig { export class CellGroupRoutingConfig {
public static MENU_ITEMS: NbMenuItem[] = [ public static MENU_ITEMS: NbMenuItem[] = [
{ {
title: '小組禱告', title: '細胞小組',
icon: 'people-outline', icon: 'people-outline',
link: '/CellGroup/prayer', children: [
home: true,
{
title: '小組禱告',
//icon: 'people-outline',
link: '/myapp/prayer'
},
{
title: '小組晚餐',
//icon: 'people-outline',
link: '/myapp/dinner',
},
],
data: DomainType.CellGroup
}, },
{ {
title: '小組晚餐', title: '幸福小組',
icon: 'people-outline', icon: 'smiling-face-outline',
link: '/CellGroup/dinner', children: [
},
{
title: 'Bests',
//icon: 'people-outline',
link: '/myapp/bests'
},
{
title: '幸福周',
//icon: 'people-outline',
link: '/myapp/happinessWeeks',
},
{
title: '財務表',
//icon: 'people-outline',
link: '/myapp/finance',
},
],
data: DomainType.HappinessGroup
},
] ]
} }
const routes: Routes = [ const routes: Routes = [
@@ -29,7 +62,16 @@ const routes: Routes = [
children: children:
[ [
{ path: 'dinner', component: DinnerComponent, }, { path: 'dinner', component: DinnerComponent, },
{ path: 'dinner/:groupId', component: DinnerComponent, },
{ path: 'prayer', component: PrayerComponent, }, { path: 'prayer', component: PrayerComponent, },
{ path: 'prayer/:groupId', component: PrayerComponent, },
{ path: 'profile', component: UserProfileComponent, },
{ path: 'bests', component: BestListComponent, },
{ path: 'bests/:groupId', component: BestListComponent, },
{ path: 'happinessWeeks', component: HappinessWeekListComponent, },
{ path: 'happinessWeeks/:groupId', component: HappinessWeekListComponent, },
{ path: 'finance/:groupId', component: FinanceComponent, },
{ {
path: 'management', component: GoogleLoginComponent, path: 'management', component: GoogleLoginComponent,
data: { data: {
+1 -1
View File
@@ -1,4 +1,4 @@
<ngx-one-column-layout> <ngx-one-column-layout>
<nb-menu [items]="MENU_ITEMS"></nb-menu> <nb-menu [items]="MENU_ITEMS" tag="NavMenu"></nb-menu>
<router-outlet></router-outlet> <router-outlet></router-outlet>
</ngx-one-column-layout> </ngx-one-column-layout>
+43 -4
View File
@@ -1,5 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { NbMenuItem } from '@nebular/theme';
import { PastoralDomainService } from '../services/crudServices/pastoral-domain.service';
import { ObjectUtils } from '../utilities/object-utils';
import { CellGroupRoutingConfig } from './cell-group-routing.module'; import { CellGroupRoutingConfig } from './cell-group-routing.module';
import { first } from 'rxjs/operators';
@Component({ @Component({
selector: 'ngx-cell-group', selector: 'ngx-cell-group',
@@ -8,10 +12,45 @@ import { CellGroupRoutingConfig } from './cell-group-routing.module';
}) })
export class CellGroupComponent implements OnInit { export class CellGroupComponent implements OnInit {
MENU_ITEMS = CellGroupRoutingConfig.MENU_ITEMS; MENU_ITEMS = ObjectUtils.CloneValue(CellGroupRoutingConfig.MENU_ITEMS);
constructor() { } constructor(
private pastoralDomainService: PastoralDomainService
ngOnInit(): void { ) {
let subscription = pastoralDomainService.initialized.subscribe(result => {
if (result) {
this.initializeSideMenu();
subscription.unsubscribe();
}
});
} }
ngOnInit(): void {
}
private initializeSideMenu() {
for (let i = 0; i < this.MENU_ITEMS.length; i++) {
const item = this.MENU_ITEMS[i];
this.checkDisplayItem(item);
}
}
private checkDisplayItem(item: NbMenuItem) {
if (item.data != undefined) {
var group = this.pastoralDomainService.domains.find(d => d.type == item.data);
if (!group) {
item.hidden = true;
return;
}
item.title = `${group.name}`;
item.children.forEach(element => {
element.link += `/${group.id}`;
});
} else if (item.children) {
item.children.forEach(element => {
this.checkDisplayItem(element);
});
}
}
} }
+22 -4
View File
@@ -5,11 +5,19 @@ import { CellGroupRoutingModule } from './cell-group-routing.module';
import { CellGroupComponent } from './cell-group.component'; import { CellGroupComponent } from './cell-group.component';
import { DinnerComponent } from './dinner/dinner.component'; import { DinnerComponent } from './dinner/dinner.component';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { NbButtonModule, NbCardModule, NbIconModule, NbInputModule, NbMenuModule, NbSpinnerModule, NbToastrModule, NbToggleModule } from '@nebular/theme'; import { NbActionsModule, NbButtonModule, NbCardModule, NbCheckboxModule, NbIconModule, NbInputModule, NbMenuModule, NbSpinnerModule, NbToastrModule, NbToggleModule } from '@nebular/theme';
import { ThemeModule } from '../@theme/theme.module'; import { ThemeModule } from '../@theme/theme.module';
import { GoogleLoginComponent } from './google-login/google-login.component'; import { GoogleLoginComponent } from './google-login/google-login.component';
import { PrayerComponent } from './prayer/prayer.component'; import { PrayerComponent } from './prayer/prayer.component';
import { UserProfileComponent } from './user-profile/user-profile.component'; import { UserProfileComponent } from './user-profile/user-profile.component';
import { DateInputModule } from '../ui/date-input/date-input.module';
import { DropDownListModule } from '../ui/drop-down-list/drop-down-list.module';
import { HappinessModule } from '../happiness/happiness.module';
import { BestListComponent, HappinessWeekListComponent } from './happiness-group/happiness-group.component';
import { JoinDomainComponent } from './join-domain/join-domain.component';
import { FinanceComponent } from './finance/finance.component';
import { AddContributionDlgComponent } from './finance/add-contribution-dlg/add-contribution-dlg.component';
import { FancyTableModule } from '../ui/fancy-table/fancy-table.module';
@NgModule({ @NgModule({
@@ -18,7 +26,12 @@ import { UserProfileComponent } from './user-profile/user-profile.component';
DinnerComponent, DinnerComponent,
GoogleLoginComponent, GoogleLoginComponent,
PrayerComponent, PrayerComponent,
UserProfileComponent UserProfileComponent,
BestListComponent,
HappinessWeekListComponent,
JoinDomainComponent,
FinanceComponent,
AddContributionDlgComponent
], ],
imports: [ imports: [
ThemeModule, ThemeModule,
@@ -32,8 +45,13 @@ import { UserProfileComponent } from './user-profile/user-profile.component';
NbToastrModule, NbToastrModule,
NbToggleModule, NbToggleModule,
NbIconModule, NbIconModule,
NbMenuModule NbMenuModule,
NbActionsModule,
DateInputModule,
NbCheckboxModule,
DropDownListModule,
HappinessModule,
FancyTableModule
] ]
}) })
export class CellGroupModule { } export class CellGroupModule { }
+19 -15
View File
@@ -7,18 +7,21 @@ import { SessionService } from '../../services/session.service';
import { LineService } from '../../services/line.service'; import { LineService } from '../../services/line.service';
import { HeaderService } from '../../services/header.service'; import { HeaderService } from '../../services/header.service';
import { StringUtils } from '../../utilities/string-utils'; import { StringUtils } from '../../utilities/string-utils';
import { AuthService } from '../../services/auth.service';
import { DateUtils } from '../../utilities/date-utils'; import { DateUtils } from '../../utilities/date-utils';
import { NumberUtils } from '../../utilities/number-utils'; import { NumberUtils } from '../../utilities/number-utils';
import { CellGroupRoutineEventsService } from '../../services/crudServices/cell-group-routine-events.service'; import { CellGroupRoutineEventsService } from '../../services/crudServices/cell-group-routine-events.service';
import { ActivatedRoute } from '@angular/router';
import { PastoralDomainService } from '../../services/crudServices/pastoral-domain.service';
import { MyAppBase } from '../MyAppBase';
import { StateService } from '../../services/state.service';
import { LoginUserService } from '../../services/login-user.service';
@Component({ @Component({
selector: 'ngx-dinner', selector: 'ngx-dinner',
templateUrl: './dinner.component.html', templateUrl: './dinner.component.html',
styleUrls: ['./dinner.component.scss'] styleUrls: ['./dinner.component.scss']
}) })
export class DinnerComponent implements OnInit { export class DinnerComponent extends MyAppBase {
constructor( constructor(
private cellGroupRoutineEventsService: CellGroupRoutineEventsService, private cellGroupRoutineEventsService: CellGroupRoutineEventsService,
private messageService: MsgBoxService, private messageService: MsgBoxService,
@@ -26,10 +29,12 @@ export class DinnerComponent implements OnInit {
private sessionService: SessionService, private sessionService: SessionService,
private lineService: LineService, private lineService: LineService,
private cdRef: ChangeDetectorRef, private cdRef: ChangeDetectorRef,
private headerService: HeaderService, private loginUserService: LoginUserService,
private authService: AuthService protected stateService: StateService,
protected route: ActivatedRoute,
protected pastoralDomainService: PastoralDomainService
) { ) {
super(stateService, route, pastoralDomainService);
} }
cellGroupEvent: CellGroupRoutineEvents; cellGroupEvent: CellGroupRoutineEvents;
@@ -40,20 +45,22 @@ export class DinnerComponent implements OnInit {
isLoading: boolean = true; isLoading: boolean = true;
processing: boolean = false; processing: boolean = false;
ngOnInit(): void { pageOnInit(): void {
this.headerService.setHeader("晚宴系統") this.stateService.SetPageTitle("晚宴系統");
this.getAllData(); this.getAllData();
} }
getAllData() { getAllData() {
this.isLoading = true; this.isLoading = true;
this.cellGroupRoutineEventsService.getComingEvent().pipe(first()).subscribe(result => { this.cellGroupRoutineEventsService.getComingEvent().pipe(first()).subscribe(result => {
this.cellGroupEvent = result; this.cellGroupEvent = result;
this.data = this.cellGroupEvent.attendees.find(a => a.id == this.authService.userAccess.memberId); this.data = this.cellGroupEvent.attendees.find(a => a.id == this.loginUserService.userAccess.memberId);
if (!this.data) { if (!this.data) {
this.data = new CellGroupRoutineEventAttendee(this.cellGroupEvent.id, this.authService.userAccess.memberId); this.data = new CellGroupRoutineEventAttendee(this.cellGroupEvent.id, this.loginUserService.userAccess.memberId);
this.data.name = this.authService.userAccess.firstName; this.data.name = this.loginUserService.userAccess.firstName;
this.cellGroupEvent.attendees.push(this.data); this.cellGroupEvent.attendees.push(this.data);
} else { } else {
this.potluckItems = this.data.potluckItem.split("|").map(s => new DinnerInfo(s)); this.potluckItems = this.data.potluckItem.split("|").map(s => new DinnerInfo(s));
@@ -70,7 +77,7 @@ export class DinnerComponent implements OnInit {
this.processing = false; this.processing = false;
this.toastrService.success('菜單更新完成!'); this.toastrService.success('菜單更新完成!');
this.lineService.pushCommandMessage('Cac4ac5a8d7fc52daa444d71dc7c360a9', 'dinner'); this.lineService.pushCommandMessage(this.cellGroupEvent.pastoralDomainId, 'dinner');
}); });
} }
@@ -88,9 +95,6 @@ export class DinnerComponent implements OnInit {
this.potluckItems.push(new DinnerInfo('')); this.potluckItems.push(new DinnerInfo(''));
this.cdRef.detectChanges(); this.cdRef.detectChanges();
} }
logout() {
this.authService.logout();
}
} }
export class DinnerInfo { export class DinnerInfo {
@@ -0,0 +1,36 @@
<nb-card status="success">
<nb-card-header>
新增奉獻
</nb-card-header>
<nb-card-body>
<div class="row form-group">
<div class="col-6 col-md-4">
<div class='form-group'>
<label for='contributor' class='label'>奉獻人</label>
<input type="text" name="contributor" nbInput fullWidth id="contributor"
[(ngModel)]="contribution.contributor">
</div>
</div>
<div class="col-6 col-md-3">
<div class="form-group">
<label for="costAmount" class="label">金額</label>
<input type="number" name="costAmount" nbInput fullWidth id="costAmount"
[(ngModel)]="contribution.amount">
</div>
</div>
<div class="col-12 col-md-5">
<div class="form-group">
<label for="costContent" class="label">備註</label>
<input type="text" name="costContent" nbInput fullWidth id="costContent"
[(ngModel)]="contribution.comment">
</div>
</div>
</div>
</nb-card-body>
<nb-card-footer>
<button class="float-right" nbButton hero status="danger" size="small" (click)="close()">Close</button>
<button class="float-right mr-2" nbButton hero status="primary" size="small" (click)="submit()">Submit</button>
</nb-card-footer>
</nb-card>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddContributionDlgComponent } from './add-contribution-dlg.component';
describe('AddContributionDlgComponent', () => {
let component: AddContributionDlgComponent;
let fixture: ComponentFixture<AddContributionDlgComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AddContributionDlgComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddContributionDlgComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,33 @@
import { Component, OnInit } from '@angular/core';
import { NbDialogRef } from '@nebular/theme';
import { Contribution } from '../../../entity/contribution.model';
import { DropDownOption } from '../../../entity/dropDownOption';
import { AuthService } from '../../../services/auth.service';
@Component({
selector: 'ngx-add-contribution-dlg',
templateUrl: './add-contribution-dlg.component.html',
styleUrls: ['./add-contribution-dlg.component.scss']
})
export class AddContributionDlgComponent implements OnInit {
taskrOptions: DropDownOption[] = [];
contribution: Contribution;
constructor(
private authService: AuthService,
private dlgRef: NbDialogRef<AddContributionDlgComponent>
) { }
ngOnInit(): void {
this.contribution = { time: new Date() } as Contribution;
}
close() {
this.dlgRef.close();
}
submit() {
if (this.contribution.contributor && this.contribution.amount > 0) {
this.dlgRef.close(this.contribution);
}
}
}
@@ -0,0 +1,13 @@
<nb-card [nbSpinner]="isLoading" nbSpinnerStatus="info" nbSpinnerSize="giant">
<nb-card-header>
{{currentDomainName}} 財務表
<nb-actions size="small" fullWidth class="float-right">
<nb-action icon="plus-outline" title="Add New" (click)="addContribution()"></nb-action>
</nb-actions>
</nb-card-header>
<nb-card-body>
<ng-container *ngIf="!isLoading">
<fancy-table [data]="contributions" [settings]="contributionSettings" [footerData]="summary"></fancy-table>
</ng-container>
</nb-card-body>
</nb-card>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FinanceComponent } from './finance.component';
describe('FinanceComponent', () => {
let component: FinanceComponent;
let fixture: ComponentFixture<FinanceComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FinanceComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FinanceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,208 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { NbDialogService, NbToastrService } from '@nebular/theme';
import { Contribution } from '../../entity/contribution.model';
import { AuthService } from '../../services/auth.service';
import { ContributionService } from '../../services/crudServices/contribution.service';
import { PastoralDomainService } from '../../services/crudServices/pastoral-domain.service';
import { LineService } from '../../services/line.service';
import { MsgBoxService } from '../../services/msg-box.service';
import { SessionService } from '../../services/session.service';
import { StateService } from '../../services/state.service';
import { MyAppBase } from '../MyAppBase';
import { AddContributionDlgComponent } from './add-contribution-dlg/add-contribution-dlg.component';
import { first } from 'rxjs/operators';
import { FancySettings } from '../../ui/fancy-table/fancy-settings.model';
import { ObjectUtils } from '../../utilities/object-utils';
import { NumberUtils } from '../../utilities/number-utils';
import { StringUtils } from '../../utilities/string-utils';
@Component({
selector: 'ngx-finance',
templateUrl: './finance.component.html',
styleUrls: ['./finance.component.scss']
})
export class FinanceComponent extends MyAppBase {
contributions: Contribution[];
summary: Contribution[];
//#region Table Setting
contributionSettings = <FancySettings<Contribution>>{
contextMenuItems: [
// {
// id: 'add',
// enabled: true,
// title: 'Add New',
// icon: 'plus-circle-outline',
// callback: (datum, element) => {
// this.openEditingDialog(true);
// },
// },
// {
// id: 'edit',
// enabled: true,
// title: 'Edit',
// icon: 'edit',
// callback: (datum, element) => {
// this.openEditingDialog(false);
// },
// },
{
enabled: true,
id: 'delete',
title: 'Delete',
icon: 'trash-2-outline',
callback: (datum, element) => {
this.contributionService.delete(datum.id).pipe(first()).subscribe(result => {
});
},
}],
onContextMenuOpening: (datum, menuItems) => {
menuItems.find(i => i.id == 'delete').visible = !!datum;
//menuItems.find(i => i.id == 'edit').visible = !!datum;
return datum;
},
columns: [
{
name: 'time',
title: 'Time',
type: 'date',
widthPx: 120,
//valuePrepareFunction: (item) => ,
},
{
name: 'contributor',
title: 'Contributor',
type: 'text',
widthPx: 150,
//valuePrepareFunction: (item) => ,
},
{
name: 'amount',
title: 'Amount',
type: 'text',
widthPx: 100,
class: 'text-right',
footerClass: 'text-right',
valuePrepareFunction: getAmountDisplay,
},
{
name: 'comment',
title: 'Comment',
type: 'text',
widthPx: 200,
//valuePrepareFunction: (item) => ,
},
],
showFooter: true
};
//#endregion
constructor(
private contributionService: ContributionService,
private messageService: MsgBoxService,
private toastrService: NbToastrService,
private sessionService: SessionService,
private lineService: LineService,
private cdRef: ChangeDetectorRef,
private authService: AuthService,
private dlgService: NbDialogService,
protected stateService: StateService,
protected route: ActivatedRoute,
protected pastoralDomainService: PastoralDomainService
) {
super(stateService, route, pastoralDomainService);
}
pageOnInit(): void {
this.initializeData();
}
addContribution() {
this.dlgService.open(AddContributionDlgComponent, {
context: {
}
}).onClose.pipe(first()).subscribe(result => {
if (result) {
result.groupId = this.currentDomain.id;
this.isLoading = true;
this.contributionService.createOrUpdate(result).pipe(first()).subscribe(result => {
this.refreshData();
});
}
});
}
private refreshData() {
this.isLoading = true;
this.pastoralDomainService.initialize().pipe(first()).subscribe(result => {
this.initializeData();
});
}
private initializeData() {
this.contributions = ObjectUtils.CloneValue(this.currentDomain.contributions);
this.summary = [];
let totalComment = '';
let totalAmount = this.contributions.map(d => d.amount).reduce((a, b) => a + b, 0);
//Append Cost
if (this.currentDomain.happinessWeeks) {
for (let i = 0; i < this.currentDomain.happinessWeeks.length; i++) {
const week = this.currentDomain.happinessWeeks[i];
let weekCost = 0;
for (let j = 0; j < week.costs.length; j++) {
const weekCostItem = week.costs[j];
this.contributions.push(
{
time: week.date,
contributor: `W${i + 1} - ${weekCostItem.tasker}`,
amount: -1 * weekCostItem.amount,
comment: weekCostItem.content
} as Contribution);
weekCost += weekCostItem.amount;
}
if (weekCost > 0) {
this.contributions.push(
{
time: week.date,
contributor: `${week.topic} - Sub Total`,
amount: 0,
comment: '$ ' + NumberUtils.FormatCurrency(weekCost)
} as Contribution);
}
}
let remainWeeks = this.currentDomain.happinessWeeks.filter(w => w.date >= new Date()).length;
totalAmount = this.contributions.map(d => d.amount).reduce((a, b) => a + b, 0);
totalComment = StringUtils.getHtmlBadge(`剩餘每周(${remainWeeks}) 預算: $${NumberUtils.FormatCurrency(totalAmount / remainWeeks)}`, 'info');
}
this.summary.push(
{
time: null,
contributor: `Summary`,
amount: totalAmount,
comment: totalComment
} as Contribution);
this.isLoading = false;
}
}
function getAmountDisplay(item: Contribution): string {
if (item.amount != 0) {
return StringUtils.getHtmlBadge(NumberUtils.FormatCurrency(item.amount), item.amount > 0 ? 'success' : 'warning');
}
return "---------------";
}
@@ -0,0 +1,48 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { PastoralDomainService } from '../../services/crudServices/pastoral-domain.service';
import { StateService } from '../../services/state.service';
import { MyAppBase } from '../MyAppBase';
@Component({
selector: 'ngx-happiness-group',
template: `
<best-list [group]="happinessGroup"></best-list>
`,
styleUrls: ['./happiness-group.component.scss']
})
export class BestListComponent extends MyAppBase {
constructor(
protected stateService: StateService,
protected route: ActivatedRoute,
protected pastoralDomainService: PastoralDomainService
) {
super(stateService, route, pastoralDomainService);
}
pageOnInit(): void {
this.stateService.SetPageTitle("Best");
}
}
@Component({
selector: 'ngx-happiness-week-group',
template: `<happiness-week-list [group]="happinessGroup"></happiness-week-list>
`,
styleUrls: ['./happiness-group.component.scss']
})
export class HappinessWeekListComponent extends MyAppBase {
constructor(
protected stateService: StateService,
protected route: ActivatedRoute,
protected pastoralDomainService: PastoralDomainService
) {
super(stateService, route, pastoralDomainService);
}
pageOnInit(): void {
this.stateService.SetPageTitle("Happiness Weeks");
}
}
@@ -0,0 +1,17 @@
<nb-card status="success">
<nb-card-header>
加入小組
</nb-card-header>
<nb-card-body>
<div class='col-12 col-md-6'>
<div class='form-group'>
<label for='domain' class='label'>小組</label>
<op-drop-down name='domain' id='domain' [(ngModel)]='domainId' [source]='domainOptions'></op-drop-down>
</div>
</div>
</nb-card-body>
<nb-card-footer>
<button class="float-right mr-2" fullWidth nbButton hero status="primary" size="large"
(click)="joinDomain()">加入小組</button>
</nb-card-footer>
</nb-card>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { JoinDomainComponent } from './join-domain.component';
describe('JoinDomainComponent', () => {
let component: JoinDomainComponent;
let fixture: ComponentFixture<JoinDomainComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ JoinDomainComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(JoinDomainComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,39 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { DomainType, PastoralDomain } from '../../entity/PastoralDomain';
import { PastoralDomainService } from '../../services/crudServices/pastoral-domain.service';
import { StateService } from '../../services/state.service';
import { MyAppBase } from '../MyAppBase';
import { first } from 'rxjs/operators';
import { DropDownOption } from '../../entity/dropDownOption';
@Component({
selector: 'ngx-join-domain',
templateUrl: './join-domain.component.html',
styleUrls: ['./join-domain.component.scss']
})
export class JoinDomainComponent extends MyAppBase {
constructor(
protected stateService: StateService,
protected route: ActivatedRoute,
protected pastoralDomainService: PastoralDomainService
) {
super(stateService, route, pastoralDomainService);
this.redirectIfNoDomains = false;
}
domainId: string;
allDomains: PastoralDomain[];
domainOptions: DropDownOption[] = [];
pageOnInit(): void {
this.isLoading = true;
this.pastoralDomainService.getAll().pipe(first()).subscribe(result => {
this.allDomains = result.filter(d => d.type == DomainType.CellGroup || d.type == DomainType.HappinessGroup);
this.domainOptions = this.allDomains.map(d => new DropDownOption(d.id, d.name));
this.isLoading = false;
});
}
joinDomain() {
}
}
+17 -14
View File
@@ -1,37 +1,40 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { NbToastrService } from '@nebular/theme'; import { NbToastrService } from '@nebular/theme';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { BindingHelper } from '../../entity/BindingHelper'; import { BindingHelper } from '../../entity/BindingHelper';
import { CellGroupRoutineEvents, CellGroupRoutineEventPrayer, CellGroupRoutineEventAttendee } from '../../entity/CellGroupRoutineEvents'; import { CellGroupRoutineEvents, CellGroupRoutineEventPrayer, CellGroupRoutineEventAttendee } from '../../entity/CellGroupRoutineEvents';
import { AuthService } from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
import { CellGroupRoutineEventsService } from '../../services/crudServices/cell-group-routine-events.service'; import { CellGroupRoutineEventsService } from '../../services/crudServices/cell-group-routine-events.service';
import { PastoralDomainService } from '../../services/crudServices/pastoral-domain.service';
import { HeaderService } from '../../services/header.service'; import { HeaderService } from '../../services/header.service';
import { LineService } from '../../services/line.service'; import { LineService } from '../../services/line.service';
import { LoginUserService } from '../../services/login-user.service';
import { MsgBoxService } from '../../services/msg-box.service'; import { MsgBoxService } from '../../services/msg-box.service';
import { SessionService } from '../../services/session.service'; import { SessionService } from '../../services/session.service';
import { StateService } from '../../services/state.service';
import { DateUtils } from '../../utilities/date-utils'; import { DateUtils } from '../../utilities/date-utils';
import { NumberUtils } from '../../utilities/number-utils'; import { NumberUtils } from '../../utilities/number-utils';
import { DinnerInfo } from '../dinner/dinner.component'; import { DinnerInfo } from '../dinner/dinner.component';
import { MyAppBase } from '../MyAppBase';
@Component({ @Component({
selector: 'ngx-prayer', selector: 'ngx-prayer',
templateUrl: './prayer.component.html', templateUrl: './prayer.component.html',
styleUrls: ['./prayer.component.scss'] styleUrls: ['./prayer.component.scss']
}) })
export class PrayerComponent implements OnInit { export class PrayerComponent extends MyAppBase {
constructor( constructor(
private cellGroupRoutineEventsService: CellGroupRoutineEventsService, private cellGroupRoutineEventsService: CellGroupRoutineEventsService,
private messageService: MsgBoxService,
private toastrService: NbToastrService, private toastrService: NbToastrService,
private sessionService: SessionService,
private lineService: LineService,
private cdRef: ChangeDetectorRef, private cdRef: ChangeDetectorRef,
private headerService: HeaderService, private loginUserService: LoginUserService,
private authService: AuthService private authService: AuthService,
protected stateService: StateService,
protected route: ActivatedRoute,
protected pastoralDomainService: PastoralDomainService
) { ) {
super(stateService, route, pastoralDomainService);
} }
cellGroupEvent: CellGroupRoutineEvents; cellGroupEvent: CellGroupRoutineEvents;
@@ -42,8 +45,8 @@ export class PrayerComponent implements OnInit {
isLoading: boolean = true; isLoading: boolean = true;
processing: boolean = false; processing: boolean = false;
ngOnInit(): void { pageOnInit(): void {
this.headerService.setHeader("禱告中心") this.stateService.SetPageTitle("禱告中心");
this.getAllData(); this.getAllData();
} }
getAllData() { getAllData() {
@@ -52,9 +55,9 @@ export class PrayerComponent implements OnInit {
this.cellGroupRoutineEventsService.getLastEvent().pipe(first()).subscribe(result => { this.cellGroupRoutineEventsService.getLastEvent().pipe(first()).subscribe(result => {
this.cellGroupEvent = result; this.cellGroupEvent = result;
this.data = this.cellGroupEvent.prayers.find(a => a.memberId == this.authService.userAccess.memberId); this.data = this.cellGroupEvent.prayers.find(a => a.memberId == this.loginUserService.userAccess.memberId);
if (!this.data) { if (!this.data) {
this.data = new CellGroupRoutineEventPrayer(this.cellGroupEvent.id, this.authService.userAccess.memberId); this.data = new CellGroupRoutineEventPrayer(this.cellGroupEvent.id, this.loginUserService.userAccess.memberId);
this.cellGroupEvent.prayers.push(this.data); this.cellGroupEvent.prayers.push(this.data);
this.prayer = new BindingHelper(['']); this.prayer = new BindingHelper(['']);
} else { } else {
@@ -92,7 +95,7 @@ export class PrayerComponent implements OnInit {
// message += "\n======= 備註 =======" + "\n" + comment; // message += "\n======= 備註 =======" + "\n" + comment;
// } // }
// message += "\n請使用方舟晚宴系統新增菜單唷!" + "\n" + "https://happiness.tours/CellGroup/dinner?openExternalBrowser=1" // message += "\n請使用方舟晚宴系統新增菜單唷!" + "\n" + "https://golife.love/CellGroup/dinner?openExternalBrowser=1"
// this.lineService.pushLineMessage(message); // this.lineService.pushLineMessage(message);
}); });
} }
@@ -5,20 +5,21 @@
</nb-card-header> </nb-card-header>
<nb-card-body> <nb-card-body>
<div class="row" *ngIf="data"> <div class="row" *ngIf="data">
<div class="col-md-3"> <div class="col-6 col-md-3">
<div class="form-group"> <div class="form-group">
<label for="firstName" class="label">First Name</label> <label for="firstName" class="label">First Name</label>
<input type="text" name="firstName" nbInput fullWidth id="firstName" <input type="text" name="firstName" nbInput fullWidth id="firstName"
[(ngModel)]="data.firstName"> [(ngModel)]="data.firstName">
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-6 col-md-3">
<div class="form-group"> <div class="form-group">
<label for="lastName" class="label">Last Name</label> <label for="lastName" class="label">Last Name</label>
<input type="text" name="lastName" nbInput fullWidth id="lastName" [(ngModel)]="data.lastName"> <input type="text" name="lastName" nbInput fullWidth id="lastName" [(ngModel)]="data.lastName">
</div> </div>
</div> </div>
<div class="col-md-2">
<div class="col-4 col-md-2">
<div class="form-group"> <div class="form-group">
<label for="gender" class="label">Gender</label> <label for="gender" class="label">Gender</label>
<op-drop-down id="gender" name="gender" [(ngModel)]='data.gender' [source]="GenderOptions"> <op-drop-down id="gender" name="gender" [(ngModel)]='data.gender' [source]="GenderOptions">
@@ -27,25 +28,21 @@
</div> </div>
</div> </div>
<div class="col-md-2">
<div class="form-group">
<nb-checkbox id="married" name="married" [(ngModel)]="data.married">Married</nb-checkbox>
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<nb-checkbox id="baptized" name="baptized" [(ngModel)]="data.baptized">Baptized</nb-checkbox>
</div>
</div>
<div class="col-md-6"> <div class="col-8 col-md-4">
<div class='form-group'>
<label for='birthday' class='label'>Birthday</label>
<op-date-input id='birthday' name='birthday' [(ngModel)]='data.birthday'></op-date-input>
</div>
</div>
<div class="col-12 col-md-6">
<div class="form-group"> <div class="form-group">
<label for="email" class="label">Email</label> <label for="email" class="label">Email</label>
<input type="text" name="email" nbInput fullWidth id="email" [(ngModel)]="data.email" readonly> <input type="text" name="email" nbInput fullWidth id="email" [(ngModel)]="data.email" readonly>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-12 col-md-6">
<div class="form-group"> <div class="form-group">
<label for="address" class="label">Address</label> <label for="address" class="label">Address</label>
<input type="text" name="address" nbInput fullWidth id="address" [(ngModel)]="data.address"> <input type="text" name="address" nbInput fullWidth id="address" [(ngModel)]="data.address">
@@ -58,30 +55,30 @@
[(ngModel)]="data.avatarImage"> [(ngModel)]="data.avatarImage">
</div> </div>
</div> --> </div> -->
<div class="col-md-4">
<div class='form-group'>
<label for='birthday' class='label'>Birthday</label> <div class="col-6 col-md-2 align-self-md-end">
<input type='text' nbInput fullWidth id='birthday' name='birthday' [(ngModel)]='data.birthday' <div class="form-group">
[nbDatepicker]="dateTimePickerbirthday"> <nb-checkbox id="married" name="married" [(ngModel)]="data.married">Married</nb-checkbox>
<nb-date-timepicker format="yyyy/MM/dd" #dateTimePickerbirthday></nb-date-timepicker>
</div> </div>
</div> </div>
<div class="col-6 col-md-2 align-self-md-end">
<div class="col-md-4"> <div class="form-group">
<div class='form-group'> <nb-checkbox id="baptized" name="baptized" [(ngModel)]="data.baptized">Baptized</nb-checkbox>
<label for='dateOfBaptized' class='label'>Date Of Baptized</label>
<input type='text' nbInput fullWidth id='dateOfBaptized' name='dateOfBaptized'
[(ngModel)]='data.dateOfBaptized' [nbDatepicker]="dateTimePickerdateOfBaptized">
<nb-datepicker format="yyyy/MM/dd" #dateTimePickerdateOfBaptized></nb-datepicker>
</div> </div>
</div> </div>
<div class="col-12 col-md-4">
<div class="col-md-4">
<div class='form-group'> <div class='form-group'>
<label for='dateOfWalkIn' class='label'>Date Of Walk In</label> <label for='dateOfWalkIn' class='label'>Date Of Walk In</label>
<input type='text' nbInput fullWidth id='dateOfWalkIn' name='dateOfWalkIn' <op-date-input id='dateOfWalkIn' name='dateOfWalkIn' [(ngModel)]='data.dateOfWalkIn'>
[(ngModel)]='data.dateOfWalkIn' [nbDatepicker]="dateTimePickerdateOfWalkIn"> </op-date-input>
<nb-date-timepicker format="yyyy/MM/dd" #dateTimePickerdateOfWalkIn></nb-date-timepicker> </div>
</div>
<div class="col-12 col-md-4">
<div class='form-group' *ngIf="data.baptized">
<label for='dateOfBaptized' class='label'>Date Of Baptized</label>
<op-date-input id='dateOfBaptized' name='dateOfBaptized' [(ngModel)]='data.dateOfBaptized'>
</op-date-input>
</div> </div>
</div> </div>
</div> </div>
@@ -89,10 +86,10 @@
</nb-card-body> </nb-card-body>
<nb-card-footer> <nb-card-footer>
<button class="float-right" nbButton hero status="danger" size="small" (click)="close()" <!-- <button class="float-right" nbButton hero status="danger" size="small" (click)="close()"
[nbSpinner]="processing"> [nbSpinner]="processing">
Cancel Cancel
</button> </button> -->
<button class="float-right mr-2" nbButton hero status="primary" size="small" <button class="float-right mr-2" nbButton hero status="primary" size="small"
(click)="!form.invalid&&!processing&&update()" [disabled]="form.invalid" [nbSpinner]="processing"> (click)="!form.invalid&&!processing&&update()" [disabled]="form.invalid" [nbSpinner]="processing">
Save Save
@@ -1,25 +1,31 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FamilyMember } from '../../entity/Member'; import { FamilyMember } from '../../entity/Member';
import { AuthService } from '../../services/auth.service';
import { FamilyMemberService } from '../../services/crudServices/family-member.service'; import { FamilyMemberService } from '../../services/crudServices/family-member.service';
import { SessionService } from '../../services/session.service';
import { first } from "rxjs/operators"; import { first } from "rxjs/operators";
import { DropDownOptions } from '../../entity/dropDownOption';
import { NbToastrService } from '@nebular/theme';
import { LoginUserService } from '../../services/login-user.service';
@Component({ @Component({
selector: 'ngx-user-profile', selector: 'ngx-user-profile',
templateUrl: './user-profile.component.html', templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.scss'] styleUrls: ['./user-profile.component.scss']
}) })
export class UserProfileComponent implements OnInit { export class UserProfileComponent implements OnInit {
GenderOptions = DropDownOptions.GenderOptions;
constructor( constructor(
private memberService: FamilyMemberService, private memberService: FamilyMemberService,
private authService: AuthService private loginUserService: LoginUserService,
private toastrService: NbToastrService,
) { } ) { }
data: FamilyMember; data: FamilyMember;
processing: boolean; processing: boolean;
ngOnInit(): void { ngOnInit(): void {
this.memberService.getById(this.authService.userAccess.memberId).pipe(first()).subscribe(result => { this.memberService.getById(this.loginUserService.userAccess.memberId).pipe(first()).subscribe(result => {
this.data = result; this.data = result;
if (this.data.dateOfBaptized == undefined) this.data.dateOfBaptized = null;
if (this.data.dateOfWalkIn == undefined) this.data.dateOfWalkIn = null;
if (this.data.birthday == undefined) this.data.birthday = null;
}); });
} }
@@ -29,6 +35,7 @@ export class UserProfileComponent implements OnInit {
update() { update() {
this.memberService.createOrUpdate(this.data).pipe(first()).subscribe(result => { this.memberService.createOrUpdate(this.data).pipe(first()).subscribe(result => {
this.toastrService.success('更新完成!', '個人資料');
}); });
} }
} }
@@ -8,15 +8,12 @@ import { ContextMenuModule } from '../../ui/context-menu/context-menu.module';
const components = [RightClickMenuDirective,]; const components = [RightClickMenuDirective,];
@NgModule({ @NgModule({
declarations: [...components], declarations: [...components],
entryComponents: [ imports: [
ContextMenuComponent CommonModule,
], NbDialogModule,
imports: [ ContextMenuModule
CommonModule, ],
NbDialogModule, exports: [...components]
ContextMenuModule
],
exports: [...components],
}) })
export class RightClickMenuModule { } export class RightClickMenuModule { }
+2
View File
@@ -20,6 +20,8 @@ export interface LoginTokenViewModel {
avatarImage: string; avatarImage: string;
role: Role; role: Role;
cellGroup: PastoralDomain; cellGroup: PastoralDomain;
signalRConnectionId;
sessionTabId: string;
} }
export interface GoogleUserInfo { export interface GoogleUserInfo {
+1
View File
@@ -4,6 +4,7 @@ export interface CellGroupRoutineEvents {
address: string; address: string;
attendees: CellGroupRoutineEventAttendee[]; attendees: CellGroupRoutineEventAttendee[];
prayers: CellGroupRoutineEventPrayer[]; prayers: CellGroupRoutineEventPrayer[];
pastoralDomainId: string;
} }
+2
View File
@@ -1,3 +1,4 @@
import { HappinessCost } from "./happiness-cost.model";
import { PastoralDomain } from "./PastoralDomain"; import { PastoralDomain } from "./PastoralDomain";
export interface HappinessWeek { export interface HappinessWeek {
@@ -11,6 +12,7 @@ export interface HappinessWeek {
seq: number; seq: number;
updateRestWeekDate: boolean; updateRestWeekDate: boolean;
tasks: HappinessTask[]; tasks: HappinessTask[];
costs: HappinessCost[];
topic: string topic: string
} }
+3 -1
View File
@@ -1,3 +1,4 @@
import { Contribution } from "./contribution.model";
import { HappinessBEST, HappinessWeek } from "./HappinessGroup"; import { HappinessBEST, HappinessWeek } from "./HappinessGroup";
import { FamilyMember } from "./Member"; import { FamilyMember } from "./Member";
@@ -6,7 +7,7 @@ export enum DomainType {
HappinessGroup, HappinessGroup,
CellGroupCoworker, CellGroupCoworker,
ChurchCoworker, ChurchCoworker,
Person = 99 Administrator = 99
} }
export interface PastoralDomain { export interface PastoralDomain {
@@ -27,6 +28,7 @@ export interface PastoralDomain {
bests: HappinessBEST[]; bests: HappinessBEST[];
happinessWeeks: HappinessWeek[]; happinessWeeks: HappinessWeek[];
contributions: Contribution[];
serviceAddress: AddressInfo; serviceAddress: AddressInfo;
type: DomainType; type: DomainType;
@@ -0,0 +1,7 @@
import { Contribution } from './contribution.model';
describe('Contribution', () => {
it('should create an instance', () => {
expect(new Contribution()).toBeTruthy();
});
});
+8
View File
@@ -0,0 +1,8 @@
export interface Contribution {
groupId: string;
id: string;
contributor: string;
amount: number;
comment: string;
time: Date;
}
@@ -0,0 +1,7 @@
import { HappinessCost } from './happiness-cost.model';
describe('HappinessCost', () => {
it('should create an instance', () => {
expect(new HappinessCost()).toBeTruthy();
});
});
+7
View File
@@ -0,0 +1,7 @@
export class HappinessCost {
weekId: string;
id: string;
tasker: string;
content: string;
amount: number;
}
+7 -7
View File
@@ -8,12 +8,12 @@ import * as signalR from "@microsoft/signalr"
import { StringUtils } from '../../utilities/string-utils'; import { StringUtils } from '../../utilities/string-utils';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { MsgBoxService } from '../../services/msg-box.service'; import { MsgBoxService } from '../../services/msg-box.service';
import { ADButtons, ADIcon } from '../../ui/alert-dlg/alert-dlg.component';
import { UuidUtils } from '../../utilities/uuid-utils'; import { UuidUtils } from '../../utilities/uuid-utils';
import { ObjectUtils } from '../../utilities/object-utils'; import { ObjectUtils } from '../../utilities/object-utils';
import { NbToastrService } from '@nebular/theme'; import { NbToastrService } from '@nebular/theme';
import { AvalonBase } from './avalonBase'; import { AvalonBase } from './avalonBase';
import { HeaderService } from '../../services/header.service'; import { HeaderService } from '../../services/header.service';
import { ADIcon, ADButtons } from '../../ui/alert-dlg/alert-dlg.model';
const minimumPlayers = 5; const minimumPlayers = 5;
const maximumPlayers = 10; const maximumPlayers = 10;
@@ -30,7 +30,7 @@ const teamSize: number[][] = [
const fourthQuestNeed2Failed = 7; const fourthQuestNeed2Failed = 7;
const SIGNAL_R_URL = (id: string = null) => { return `${environment.signalRUrl}/${id}Hub` } const SIGNAL_R_URL = (id: string = null) => { return `${environment.signalRUrl}/${id}Hub` }
//const SIGNAL_R_URL = (id: string = null) => { return `http://localhost:12071/hub` } //const SIGNAL_R_URL = (id: string = null) => { return `http://localhost:12071/hub` }
//const SIGNAL_R_URL = (id: string = null) => { return `http://happiness.tours:8088/${id}hub` } //const SIGNAL_R_URL = (id: string = null) => { return `http://golife.love:8088/${id}hub` }
@Component({ @Component({
selector: 'ngx-avalon', selector: 'ngx-avalon',
@@ -181,7 +181,7 @@ export class AvalonComponent extends AvalonBase implements OnInit {
public showMyRole() { public showMyRole() {
let roleInfo = this.getRoleInfoByRole(this.me.role); let roleInfo = this.getRoleInfoByRole(this.me.role);
this.msgBoxService.show(roleInfo.name, roleInfo.description, ADIcon.INFO) this.msgBoxService.show(roleInfo.name, { text: roleInfo.description, icon: ADIcon.INFO });
} }
public showMySecret() { public showMySecret() {
//let roleInfo = this.getRoleInfoByRole(this.me.role); //let roleInfo = this.getRoleInfoByRole(this.me.role);
@@ -208,7 +208,7 @@ export class AvalonComponent extends AvalonBase implements OnInit {
break; break;
} }
this.msgBoxService.show('噓...', description, ADIcon.INFO); this.msgBoxService.show('噓...', { text: description, icon: ADIcon.INFO });
} }
private initializeQuestInfo() { private initializeQuestInfo() {
@@ -251,7 +251,7 @@ export class AvalonComponent extends AvalonBase implements OnInit {
previousStage() { previousStage() {
this.msgBoxService.show('上一棟', '你確定?', ADIcon.QUESTION, ADButtons.YesNo).pipe(first()).subscribe(result => { this.msgBoxService.show('上一棟', { text: '你確定?', icon: ADIcon.QUESTION, buttons: ADButtons.YesNo }).pipe(first()).subscribe(result => {
if (result) { if (result) {
this.avalonService.data.stage -= 1; this.avalonService.data.stage -= 1;
this.clearVoteStatus(); this.clearVoteStatus();
@@ -264,14 +264,14 @@ export class AvalonComponent extends AvalonBase implements OnInit {
switch (this.avalonService.data.stage) { switch (this.avalonService.data.stage) {
case AvalonStage.JoinGame: case AvalonStage.JoinGame:
if (this.players.length < 5) { if (this.players.length < 5) {
this.msgBoxService.show("人數不足!", "遊戲最少需要五人"); this.msgBoxService.show("人數不足!", { text: "遊戲最少需要五人" });
return; return;
} }
this.gameSize = Math.max(0, this.players.length - 6); this.gameSize = Math.max(0, this.players.length - 6);
if (this.avalonService.data.roles.filter(r => r.enabled && false == r.isGood).length != players_evil[this.gameSize]) { if (this.avalonService.data.roles.filter(r => r.enabled && false == r.isGood).length != players_evil[this.gameSize]) {
this.msgBoxService.show("反派人數錯誤!", "反派人數需要 " + players_evil[this.gameSize].toString() + " 人"); this.msgBoxService.show("反派人數錯誤!", { text: "反派人數需要 " + players_evil[this.gameSize].toString() + " 人" });
return; return;
} }
@@ -4,7 +4,7 @@ import { first } from 'rxjs/operators';
import { GameInfo, Player, Role, RoleInfo } from '../../../entity/Avalon'; import { GameInfo, Player, Role, RoleInfo } from '../../../entity/Avalon';
import { AvalonService } from '../../../services/avalon.service'; import { AvalonService } from '../../../services/avalon.service';
import { MsgBoxService } from '../../../services/msg-box.service'; import { MsgBoxService } from '../../../services/msg-box.service';
import { ADButtons, ADIcon } from '../../../ui/alert-dlg/alert-dlg.component'; import { ADButtons, ADIcon } from '../../../ui/alert-dlg/alert-dlg.model';
import { AvalonBase } from '../avalonBase'; import { AvalonBase } from '../avalonBase';
@Component({ @Component({
@@ -38,7 +38,7 @@ export class ChooseCharacterComponent extends AvalonBase implements OnInit {
// return; // return;
this.ngZone.run( this.ngZone.run(
_ => { _ => {
this.msgBoxService.show(roleInfo.name, roleInfo.description + '<br>你確定你是?', ADIcon.QUESTION, ADButtons.YesNo).pipe(first()).subscribe(result => { this.msgBoxService.show(roleInfo.name, { text: roleInfo.description + '<br>你確定你是?', icon: ADIcon.QUESTION, buttons: ADButtons.YesNo }).pipe(first()).subscribe(result => {
if (result) { if (result) {
if (this.players.find(p => p.role == roleInfo.role) && roleInfo.role != Role.ArthurKnight) { if (this.players.find(p => p.role == roleInfo.role) && roleInfo.role != Role.ArthurKnight) {
this.toastrService.danger('腳色重複:' + roleInfo.name, '你確定沒有選錯腳色??') this.toastrService.danger('腳色重複:' + roleInfo.name, '你確定沒有選錯腳色??')
@@ -26,5 +26,5 @@
<ng-template #WaitingMessage> <ng-template #WaitingMessage>
<h1>等待遊戲開始中...</h1> <h1>等待遊戲開始中...</h1>
<qr-code *ngIf="isHost" [size]="qrCodeWidth" [value]="'http://happiness.tours/games/avalon'"></qr-code> <qr-code *ngIf="isHost" [size]="qrCodeWidth" [value]="'http://golife.love/games/avalon'"></qr-code>
</ng-template> </ng-template>
@@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core';
import { Player, AvalonStage } from '../../../entity/Avalon'; import { Player, AvalonStage } from '../../../entity/Avalon';
import { AvalonService } from '../../../services/avalon.service'; import { AvalonService } from '../../../services/avalon.service';
import { MsgBoxService } from '../../../services/msg-box.service'; import { MsgBoxService } from '../../../services/msg-box.service';
import { ADIcon } from '../../../ui/alert-dlg/alert-dlg.component'; import { ADIcon } from '../../../ui/alert-dlg/alert-dlg.model';
import { AvalonBase } from '../avalonBase'; import { AvalonBase } from '../avalonBase';
@Component({ @Component({
@@ -38,7 +38,7 @@ export class PickTeammateComponent extends AvalonBase implements OnInit {
this.avalonService.applyCdChange$.next(); this.avalonService.applyCdChange$.next();
} else { } else {
this.ngZone.run(_ => { this.ngZone.run(_ => {
this.msgBoxService.show('任務編組錯誤!', `本次任務出場人數為 ${this.currentQuest.teamSize} 人!`, ADIcon.WARNING); this.msgBoxService.show('任務編組錯誤!', { text: `本次任務出場人數為 ${this.currentQuest.teamSize} 人!`, icon: ADIcon.WARNING });
}); });
} }
} }
+39
View File
@@ -1,8 +1,43 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { NbMenuItem } from '@nebular/theme';
import { AvalonComponent } from './avalon/avalon.component'; import { AvalonComponent } from './avalon/avalon.component';
import { GamesComponent } from './games.component'; import { GamesComponent } from './games.component';
import { HeroDashboardComponent } from './massive-darkness2/hero-dashboard/hero-dashboard.component';
import { MassiveDarkness2Component } from './massive-darkness2/massive-darkness2.component';
import { MD2MobInfoMaintenanceComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-maintenance.component';
import { MD2HeroProfileMaintenanceComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-maintenance.component';
export class GameRoomMenuConfig {
public static HostMenu: NbMenuItem[] = [
{
title: 'Dashboard',
icon: 'people-outline',
children: [
{
title: '小組禱告',
//icon: 'people-outline',
link: '/games/MD2'
},
],
},
];
public static PlayerMenu: NbMenuItem[] = [
{
title: 'Hero Dashboard',
icon: 'people-outline',
children: [
{
title: '小組禱告',
//icon: 'people-outline',
link: '/myapp/prayer'
},
],
},
];
}
const routes: Routes = [ const routes: Routes = [
{ {
path: '', component: GamesComponent path: '', component: GamesComponent
@@ -12,6 +47,10 @@ const routes: Routes = [
[ [
{ path: 'avalon', component: AvalonComponent }, { path: 'avalon', component: AvalonComponent },
{ path: 'avalonHost', component: AvalonComponent }, { path: 'avalonHost', component: AvalonComponent },
{ path: 'MD2', component: MassiveDarkness2Component },
{ path: 'MD2_Hero/:roomId', component: HeroDashboardComponent },
{ path: 'MD2MobInfo', component: MD2MobInfoMaintenanceComponent },
{ path: 'MD2HeroProfile', component: MD2HeroProfileMaintenanceComponent },
] ]
}, },
+7 -3
View File
@@ -1,4 +1,8 @@
<ngx-one-column-layout> <ngx-plain-layout>
<!-- <nb-menu [items]="MENU_ITEMS"></nb-menu> --> <!-- <nb-menu [items]="gameRoomService.sideMenu"></nb-menu> -->
<router-outlet></router-outlet> <router-outlet></router-outlet>
</ngx-one-column-layout> </ngx-plain-layout>
<div kendoDialogContainer></div>
<!-- ngx-plain-layout ngx-one-column-layout-->
+4 -1
View File
@@ -1,5 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { GameRoomService } from '../services/game-room.service';
@Component({ @Component({
selector: 'ngx-games', selector: 'ngx-games',
@@ -8,7 +9,9 @@ import { Title } from '@angular/platform-browser';
}) })
export class GamesComponent implements OnInit { export class GamesComponent implements OnInit {
constructor() { } constructor(
public gameRoomService: GameRoomService
) { }
ngOnInit(): void { ngOnInit(): void {
//this.browserTitleService.setTitle('我是遊戲王'); //this.browserTitleService.setTitle('我是遊戲王');
+19
View File
@@ -0,0 +1,19 @@
export interface IGamePlayer {
id: string;
name: string;
gameRoomId: string;
isPlayer: boolean;
signalRClientId: string;
tabId: string;
isDisconnected: boolean;
}
export class GamePlayer implements IGamePlayer {
id: string;
name: string;
gameRoomId: string;
isPlayer: boolean;
signalRClientId: string;
tabId: string;
isDisconnected: boolean;
}
+85 -4
View File
@@ -1,10 +1,9 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { QRCodeModule } from 'angular2-qrcode';
import { GamesRoutingModule } from './games-routing.module'; import { GamesRoutingModule } from './games-routing.module';
import { GamesComponent } from './games.component'; import { GamesComponent } from './games.component';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { NbMenuModule, NbInputModule, NbCardModule, NbButtonModule, NbActionsModule, NbCheckboxModule, NbRadioModule, NbDatepickerModule, NbSelectModule, NbIconModule, NbTagModule, NbStepperModule, NbListModule, NbUserModule, NbSpinnerModule, NbDialogModule, NbToggleModule } from '@nebular/theme'; import { NbMenuModule, NbInputModule, NbCardModule, NbButtonModule, NbActionsModule, NbCheckboxModule, NbRadioModule, NbDatepickerModule, NbSelectModule, NbIconModule, NbTagModule, NbStepperModule, NbListModule, NbUserModule, NbSpinnerModule, NbDialogModule, NbToggleModule, NbAccordionModule, NbBadgeModule, NbAlertModule } from '@nebular/theme';
import { ThemeModule } from '../@theme/theme.module'; import { ThemeModule } from '../@theme/theme.module';
import { AdminRoutingModule } from '../admin/admin-routing.module'; import { AdminRoutingModule } from '../admin/admin-routing.module';
import { AlertDlgModule } from '../ui/alert-dlg/alert-dlg.module'; import { AlertDlgModule } from '../ui/alert-dlg/alert-dlg.module';
@@ -16,6 +15,46 @@ import { TeamVoteComponent } from './avalon/team-vote/team-vote.component';
import { QuestVoteComponent } from './avalon/quest-vote/quest-vote.component'; import { QuestVoteComponent } from './avalon/quest-vote/quest-vote.component';
import { VoteResultComponent } from './avalon/vote-result/vote-result.component'; import { VoteResultComponent } from './avalon/vote-result/vote-result.component';
import { QuestTableComponent } from './avalon/quest-table/quest-table.component'; import { QuestTableComponent } from './avalon/quest-table/quest-table.component';
import { MassiveDarkness2Component } from './massive-darkness2/massive-darkness2.component';
import { TreasureBagComponent } from './massive-darkness2/treasure-bag/treasure-bag.component';
import { MobsComponent } from './massive-darkness2/mobs/mobs.component';
import { CurrencyInputModule } from '../ui/currency-input/currency-input.module';
import { DoorEventsComponent } from './massive-darkness2/door-events/door-events.component';
import { HeroDashboardComponent } from './massive-darkness2/hero-dashboard/hero-dashboard.component';
import { SpawnMobDlgComponent } from './massive-darkness2/mobs/spawn-mob-dlg/spawn-mob-dlg.component';
import { MD2IconComponent } from './massive-darkness2/md2-icon/md2-icon.component';
import { MobDetailInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-detail-info.component';
import { MD2HeroSelectComponent } from './massive-darkness2/md2-hero-select/md2-hero-select.component';
import { DropDownListModule } from '../ui/drop-down-list/drop-down-list.module';
import { BossFightComponent } from './massive-darkness2/boss-fight/boss-fight.component';
import { BossActivationComponent } from './massive-darkness2/boss-fight/boss-activation/boss-activation.component';
import { MobAttackInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-attack-info/mob-attack-info.component';
import { MobDefInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-def-info/mob-def-info.component';
import { MobCombatInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-combat-info/mob-combat-info.component';
import { MobStandInfoComponent } from './massive-darkness2/mobs/mob-stand-info/mob-stand-info.component';
import { HtmlEditorModule } from '../ui/html-editor/html-editor.module';
import { EditorModule } from '@tinymce/tinymce-angular';
import { MD2HtmlEditorComponent } from './massive-darkness2/md2-html-editor/md2-html-editor.component';
import { MD2IconPickerDlgComponent } from './massive-darkness2/md2-html-editor/md2-icon-picker-dlg.component';
import { EditorModule as KendoEditorModule } from '@progress/kendo-angular-editor';
import { ToolBarModule } from '@progress/kendo-angular-toolbar';
import { ButtonsModule } from '@progress/kendo-angular-buttons';
import { GridModule } from '@progress/kendo-angular-grid';
import { DialogModule } from '@progress/kendo-angular-dialog';
import { InputsModule } from '@progress/kendo-angular-inputs';
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { LayoutModule } from '@progress/kendo-angular-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MD2MobInfoMaintenanceComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-maintenance.component';
import { MD2MobInfoEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-editor/md2-mob-info-editor.component';
import { MD2MobInfoDetailComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-detail/md2-mob-info-detail.component';
import { MD2MobSkillEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-skill-editor/md2-mob-skill-editor.component';
import { MD2MobLevelEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-level-editor/md2-mob-level-editor.component';
import { MD2BossFightEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component';
import { MD2PhaseBuffEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-phase-buff-editor/md2-phase-buff-editor.component';
import { MD2HeroProfileMaintenanceComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-maintenance.component';
import { MD2HeroProfileEditorComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-editor/md2-hero-profile-editor.component';
import { GameInitDlgComponent } from './massive-darkness2/game-init-dlg/game-init-dlg.component';
@NgModule({ @NgModule({
@@ -28,12 +67,40 @@ import { QuestTableComponent } from './avalon/quest-table/quest-table.component'
TeamVoteComponent, TeamVoteComponent,
QuestVoteComponent, QuestVoteComponent,
VoteResultComponent, VoteResultComponent,
QuestTableComponent QuestTableComponent,
MassiveDarkness2Component,
TreasureBagComponent,
MobsComponent,
DoorEventsComponent,
HeroDashboardComponent,
SpawnMobDlgComponent,
MD2IconComponent,
MobDetailInfoComponent,
MD2HeroSelectComponent,
BossFightComponent,
BossActivationComponent,
MobAttackInfoComponent,
MobDefInfoComponent,
MobCombatInfoComponent,
MobStandInfoComponent,
MD2HtmlEditorComponent,
MD2IconPickerDlgComponent,
MD2MobInfoMaintenanceComponent,
MD2MobInfoEditorComponent,
MD2MobInfoDetailComponent,
MD2MobSkillEditorComponent,
MD2MobLevelEditorComponent,
MD2BossFightEditorComponent,
MD2PhaseBuffEditorComponent,
MD2HeroProfileMaintenanceComponent,
MD2HeroProfileEditorComponent,
GameInitDlgComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
GamesRoutingModule, GamesRoutingModule,
FormsModule, FormsModule,
ReactiveFormsModule,
AdminRoutingModule, AdminRoutingModule,
ThemeModule, ThemeModule,
NbMenuModule, NbMenuModule,
@@ -52,9 +119,23 @@ import { QuestTableComponent } from './avalon/quest-table/quest-table.component'
NbToggleModule, NbToggleModule,
NbUserModule, NbUserModule,
NbSpinnerModule, NbSpinnerModule,
NbAccordionModule,
NbBadgeModule,
NbAlertModule,
CurrencyInputModule,
NbDialogModule.forRoot(), NbDialogModule.forRoot(),
AlertDlgModule, AlertDlgModule,
QRCodeModule, DropDownListModule,
HtmlEditorModule,
EditorModule,
KendoEditorModule,
ToolBarModule,
ButtonsModule,
GridModule,
DialogModule,
InputsModule,
DropDownsModule,
LayoutModule
] ]
}) })
export class GamesModule { } export class GamesModule { }
+343
View File
@@ -0,0 +1,343 @@
import { ChangeDetectorRef, Injectable } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { Subject } from "rxjs";
import { first, takeUntil } from "rxjs/operators";
import { MD2GameInfo, MD2Service } from "../../services/MD2/md2.service";
import { SignalRMessage } from "../../services/signal-r.service";
import { StateService } from "../../services/state.service";
import { ADIcon, MessageBoxConfig } from "../../ui/alert-dlg/alert-dlg.model";
import { MD2HeroInfo, MD2Icon, MobInfo, RoundPhase } from "./massive-darkness2.model";
import { LoginUserService } from "../../services/login-user.service";
import { GamePlayer } from "../games.model";
@Injectable()
export abstract class MD2Base {
MD2Icon = MD2Icon;
public get gameInfo() {
return this.md2Service.info;
}
protected isHeroDashboard: boolean = false;
protected roomId: string;
protected destroy$: Subject<void> = new Subject<void>();
/**
*
*/
constructor(
protected md2Service: MD2Service,
protected stateService: StateService,
protected route: ActivatedRoute,
protected cdRef: ChangeDetectorRef,
) {
}
ngOnInit(): void {
this.route.paramMap.pipe(first()).subscribe(params => {
if (params.get('roomId')) {
this.roomId = params.get('roomId');
}
});
this.md2Service.refreshUI$.pipe(takeUntil(this.destroy$)).subscribe(result => {
this.cdRef.detectChanges();
});
this.stateService.loginUserService.signalRInitialized.pipe(first()).subscribe(result => {
console.log('signalRInitialized');
this.signalRInitialized();
});
this.md2Service.signalRService.ReceivedSignalRMessageSubject.pipe(takeUntil(this.destroy$)).subscribe(result => this.handleSignalRCallback(result));
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
signalRInitialized() {
}
imgUrl(imgPath: string) {
return this.md2Service.imgUrl(imgPath);
}
fileList(folderPath: string) {
return this.md2Service.fileList(folderPath);
}
iconHtml(icon: MD2Icon, cssClass = '') {
return this.md2Service.iconHtml(icon, cssClass);
}
imgHtml(imgFile: string, cssClass = '') {
return `<img src="${this.imgUrl(imgFile)}" class='${cssClass}'>`
}
detectChanges() {
if (!this.cdRef['destroyed']) {
this.cdRef.detectChanges();
this.refreshUI();
this.md2Service.refreshUI$.next();
}
}
abstract refreshUI();
handleSignalRCallback(message: SignalRMessage): void {
console.log('handleSignalRCallback', message);
if (message.from) {
if (message.from.isGroup) {
if (!this.isHeroDashboard) return;
} else {
if (this.isHeroDashboard && this.md2Service.playerHero?.playerInfo?.signalRClientId == message.from.connectionId) return;
}
}
if (!this.isHeroDashboard) {
}
switch (message.actionType) {
case 'hero':
let heroInfo = new MD2HeroInfo(JSON.parse(message.parameters['hero']));
switch (message.actionName) {
case 'join':
this.md2Service.heros.push(heroInfo);
break;
case 'update':
this.updateHeroInfo(heroInfo);
//Object.assign(heroInfo, exitingHero);
break;
case 'updateMyHero':
if (this.isHeroDashboard) {
this.md2Service.playerHero = heroInfo;
}
break;
default:
break;
}
this.detectChanges();
break;
case 'heroes':
switch (message.actionName) {
case 'updateAll':
if (this.isHeroDashboard) {
let allHeroes = (JSON.parse(message.parameters['heros']) as MD2HeroInfo[]).map(h => new MD2HeroInfo(h));
//Remove heroes that are not in the list
this.md2Service.info.heros = this.md2Service.heros.filter(h => allHeroes.some(h2 => h2.playerInfo.tabId == h.playerInfo.tabId));
allHeroes.forEach(heroInfo => {
this.updateHeroInfo(heroInfo);
});
this.detectChanges();
}
break;
}
break;
case 'GameRoom':
switch (message.actionName) {
case 'Leaving':
let leavingPlayerInfo = message.value as GamePlayer;
let leavingHero = this.md2Service.heros.find(h => h.playerInfo.tabId == leavingPlayerInfo.tabId);
if (leavingHero) {
leavingHero.playerInfo.isDisconnected = true;
}
//var disconnectHero = this.md2Service.heros.splice(this.md2Service.heros.findIndex(h => h.playerInfo.signalRClientId == leavingPlayerInfo.signalRClientId));
//this.md2Service.info.disconnectedHeroes.push(...disconnectHero);
this.detectChanges();
break;
case 'update':
if (this.isHeroDashboard) {
//Before update game info check the current Hero level
let playerHeroLevel = this.md2Service.playerHero?.level;
this.md2Service.info = new MD2GameInfo(JSON.parse(message.parameters['gameInfo']) as MD2GameInfo);
let playerHero = this.md2Service.heros.find(h => h.playerInfo.tabId == this.stateService.loginUserService.sessionTabId);
if (playerHero) {
playerHero.playerInfo = this.md2Service.gameRoomService.currentPlayer();
playerHero.playerInfo.isDisconnected = false;
this.md2Service.playerHero = playerHero;
this.md2Service.broadcastMyHeroInfo();
//When fetch game info, if the hero level is changed, show the level up message
if (playerHeroLevel && playerHeroLevel != playerHero.level) {
//do i--
for (let i = playerHero.level; i > playerHeroLevel; i--) {
this.md2Service.msgBoxService.show(`Level Up Lv.${i}`, { text: 'Please do a skill level up!', icon: ADIcon.INFO });
}
}
}
this.detectChanges();
}
break;
case 'phase':
if (this.isHeroDashboard) {
this.md2Service.info.roundPhase = JSON.parse(message.parameters['phase']);
this.detectChanges();
}
break;
case 'getGameInfo':
if (!this.isHeroDashboard) {
this.md2Service.broadcastGameInfo();
}
break;
case 'sendJoinInfo':
//When hero join the game, or reconnect to the game will receive this message
if (this.isHeroDashboard && this.md2Service.playerHero) {
this.md2Service.playerHero.playerInfo.signalRClientId = message.parameters['signalrconnid'];
//Send fetch game info to the hero
this.md2Service.broadcastFetchGameInfo();
}
break;
default:
break;
}
break;
case 'message':
switch (message.actionName) {
case 'popup':
let msg = JSON.parse(message.parameters['msg']) as MessageBoxConfig;
this.md2Service.msgBoxService.show(msg.title, msg);
break;
default:
break;
}
break;
case 'roundPhase':
switch (message.actionName) {
case 'bossFight':
this.gameInfo.isBossFight = true;
break;
default:
this.gameInfo.roundPhase = Number.parseInt(message.parameters['phase']);
break;
}
this.detectChanges();
break;
case 'mobs':
if (this.isHeroDashboard) {
this.gameInfo.roamingMonsters = JSON.parse(message.parameters['roamingMonsters']);
this.gameInfo.mobs = JSON.parse(message.parameters['mobs']);
this.detectChanges();
}
break;
case 'heroAction':
if (!this.isHeroDashboard) {
//this.md2Service.currentActivateHero = this.md2Service.heros.find(h => h.playerInfo.tabId == message.parameters['tabId']);
switch (message.actionName) {
case 'attackAction':
if (this.gameInfo.isBossFight) {
this.md2Service.heroAttackingSubject.next(this.md2Service.currentActivateHero);
} else {
this.gameInfo.showAttackBtn = true;
}
break;
case 'openDoor':
//Door component listen for it
break;
case 'tradeAction':
this.md2Service.msgBoxService.show('Trade and Equip', {
text: `every one in the <b>same zone with ${this.md2Service.heroFullName(this.md2Service.currentActivateHero)}</b> may freely trade and
equip items!`,
icon: ADIcon.INFO
});
break;
default:
//this.md2Service.roundPhase = Number.parseInt(message.parameters['phase']);
break;
}
this.heroAction(this.md2Service.currentActivateHero, message.actionName);
this.detectChanges();
}
break;
default:
break;
}
}
updateHeroInfo(heroInfo: MD2HeroInfo) {
let exitingHero = this.md2Service.heros.find(h => h.playerInfo.tabId == heroInfo.playerInfo.tabId);
if (exitingHero) {
//For boss fight, if the hero finished activating, activate the boss
let activateBoss = exitingHero.uiActivating && !heroInfo.uiActivating;
this.md2Service.heros[this.md2Service.heros.indexOf(exitingHero)] = heroInfo;
//My hero update
if (this.isHeroDashboard && this.md2Service.loginUserService.sessionTabId == heroInfo.playerInfo.tabId) {
this.md2Service.playerHero = heroInfo;
}
if (!this.isHeroDashboard && this.md2Service.info.isBossFight && activateBoss) {
this.md2Service.activateBoss();
}
} else {
this.md2Service.heros.push(heroInfo);
}
if (!this.isHeroDashboard) {
if (this.gameInfo.roundPhase == RoundPhase.HeroPhase) {
if (!this.md2Service.heros.some(h => h.remainActions > 0) && !this.md2Service.heros.some(h => h.uiActivating)) {
if (!this.md2Service.info.isBossFight) {
if (this.md2Service.mobs.length > 0 || this.md2Service.roamingMonsters.length > 0) {
this.md2Service.msgBoxService.show('Enemy Phase', { icon: ADIcon.WARNING }).pipe(first()).subscribe(result => {
this.md2Service.runNextPhase();
});
} else {
this.md2Service.runNextPhase();
}
}
}
}
}
}
abstract heroAction(hero: MD2HeroInfo, action: string);
}
@Injectable()
export abstract class MD2ComponentBase {
protected roomId: string;
protected destroy$: Subject<void> = new Subject<void>();
/**
*
*/
constructor(
protected md2Service: MD2Service,
protected stateService: StateService,
protected route: ActivatedRoute,
protected cdRef: ChangeDetectorRef,
) {
}
ngOnInit(): void {
this.md2Service.refreshUI$.pipe(takeUntil(this.destroy$)).subscribe(result => {
this.cdRef.detectChanges();
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
imgUrl(imgPath: string) {
return this.md2Service.imgUrl(imgPath);
}
fileList(folderPath: string) {
return this.md2Service.fileList(folderPath);
}
iconHtml(icon: MD2Icon, cssClass = '') {
return this.md2Service.iconHtml(icon, cssClass);
}
detectChanges() {
if (!this.cdRef['destroyed']) {
this.cdRef.detectChanges();
this.refreshUI();
this.md2Service.refreshUI$.next();
}
}
refreshUI() {
}
}
@@ -0,0 +1,36 @@
<nb-card>
<nb-card-body class="g-overflow-hidden">
<div class="row form-group">
<div class="col-md-5 g-height-700px">
<md2-mob-stand-info [mob]="boss" [mode]="mode"></md2-mob-stand-info>
</div>
<div class="col-md-7">
<label class="MD2text g-font-size-40 mt-4" [innerHtml]="bossAction.name">
</label>
<label class="mt-2 g-font-size-20 my-3 MD2IconContainer-lg" [innerHtml]="bossAction.description">
</label>
<hr>
<div class="row">
<div class="col-md-4">
<md2-mob-attack-info [mob]="boss"></md2-mob-attack-info>
</div>
<div class="col-md-8 MD2IconContainer-lg">
<md2-mob-combat-info [mob]="boss"></md2-mob-combat-info>
</div>
</div>
</div>
</div>
</nb-card-body>
<nb-card-footer>
<button class="float-right" nbButton hero status="danger" size="small" (click)="close()">Close</button>
<button class="float-right mr-2" nbButton hero status="primary" size="small" (click)="close()">Submit</button>
</nb-card-footer>
</nb-card>
@@ -0,0 +1,4 @@
nb-card {
height: 70vh;
width: 80vw;
}
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BossActivationComponent } from './boss-activation.component';
describe('BossActivationComponent', () => {
let component: BossActivationComponent;
let fixture: ComponentFixture<BossActivationComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ BossActivationComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(BossActivationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,64 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { NbDialogRef } from '@nebular/theme';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { MD2Service } from '../../../../services/MD2/md2.service';
import { MsgBoxService } from '../../../../services/msg-box.service';
import { StateService } from '../../../../services/state.service';
import { MobDlgType, MD2Icon, MD2HeroInfo, RoundPhase, MobInfo } from '../../massive-darkness2.model';
import { MobSkill, IBossFight } from '../../massive-darkness2.model.boss';
import { MD2ComponentBase } from '../../MD2Base';
import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.component';
import { MD2MobInfo, MD2MobSkill } from '../../massive-darkness2.db.model';
@Component({
selector: 'ngx-boss-activation',
templateUrl: './boss-activation.component.html',
styleUrls: ['./boss-activation.component.scss']
})
export class BossActivationComponent implements OnInit {
boss: MobInfo;
bossAction: MD2MobSkill;
currentAction: number;
allActions: number;
MobDlgType = MobDlgType;
mode: MobDlgType = MobDlgType.Activating;
title: string;
titleHtml: string;
actionHtml: string;
MD2Icon = MD2Icon;
beenAttackedHero = [] as MD2HeroInfo[];
attackTarget: string;
otherAttackTarget: string;
constructor(
private dlgRef: NbDialogRef<SpawnMobDlgComponent>,
public md2Service: MD2Service,
protected stateService: StateService,
protected route: ActivatedRoute,
protected cdRef: ChangeDetectorRef,
) {
this.md2Service.refreshUI$.pipe(takeUntil(this.destroy$)).subscribe(result => {
if (!this.cdRef['destroyed']) {
this.cdRef.detectChanges();
}
});
}
private destroy$: Subject<void> = new Subject<void>();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
ngOnInit(): void {
}
close() {
//this.boss.standUrl
this.md2Service.info.roundPhase = RoundPhase.HeroPhase;
this.dlgRef.close();
}
}
@@ -0,0 +1,56 @@
<nb-card>
<nb-card-header class="MD2text g-font-size-28">
{{boss.name}}
<button nbButton hero status="primary" (click)="activate()">Action</button>
</nb-card-header>
<nb-card-body class="g-overflow-hidden">
<div class="row">
<div class="col-md-5">
<!-- <img src="{{boss.standUrl}}" class="w-100 bossStandImg"> -->
<md2-mob-stand-info [mob]="boss" [mode]="MobDlgType.PreView"></md2-mob-stand-info>
<!-- HP and Mana Bars -->
<div class="hero-stats-overlay">
<div class="stat-bar-overlay hp-bar-overlay">
<div class="stat-bar-label-overlay">
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
<span class="stat-value-overlay">{{boss.unitRemainHp}}/{{boss.hp}}</span>
</div>
<div class="stat-progress-bar-overlay">
<div class="stat-progress-fill-overlay hp-fill-overlay"
[style.width.%]="(boss.unitRemainHp / boss.hp) * 100">
</div>
</div>
</div>
</div>
</div>
<div class="col-md-7">
<div class="row">
<div class="col-md">
<adj-number-input name="mob{{boss.name}}" [(ngModel)]="boss.unitRemainHp" minimum="0"
class="mb-3" title="Boss HP" (hitMinimum)="WIN()">
</adj-number-input>
<md2-mob-attack-info [mob]="boss">
</md2-mob-attack-info>
<md2-mob-def-info [mob]="boss"></md2-mob-def-info>
</div>
<div class="col-md-9 bossSpecialRules" *ngIf="boss.bossFightProfile.specialRules">
<div [innerHtml]="boss.bossFightProfile.specialRules"></div>
</div>
</div>
<md2-mob-combat-info [mob]="boss"></md2-mob-combat-info>
<!--
<button nbButton hero status="danger" size="small" (click)="attack(boss)">Attack It</button> -->
<!-- <label class="MD2Text mt-3" [innerHtml]="boss.combatSkill.skillName">
</label>
<label class="MD2Text" [innerHtml]="boss.combatInfo.skillDescription">
</label> -->
</div>
</div>
</nb-card-body>
</nb-card>
@@ -0,0 +1,137 @@
nb-card {
height: 80vh;
//width: 80vw;
}
.bossStandImg {
max-height: 67vh;
object-fit: contain;
}
::ng-deep .bossSpecialRules {
.MD2Icon {
font-size: 30px;
}
}
// HP and Mana Bars Overlay
.hero-stats-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 0.5rem;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
border-radius: 0 0 8px 8px;
z-index: 2;
width: 95%;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.35rem;
}
}
.stat-bar-overlay {
margin-bottom: 0.5rem;
&:last-child {
margin-bottom: 0;
}
@media (max-height: 450px) and (orientation: landscape) {
margin-bottom: 0.35rem;
}
}
.stat-bar-label-overlay {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.25rem;
gap: 0.5rem;
md2-icon {
flex-shrink: 0;
width: 18px;
height: 18px;
}
.stat-value-overlay {
color: white;
font-size: 0.75rem;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
flex-shrink: 0;
}
@media (max-height: 450px) and (orientation: landscape) {
margin-bottom: 0.15rem;
gap: 0.3rem;
md2-icon {
width: 14px;
height: 14px;
}
.stat-value-overlay {
font-size: 0.65rem;
}
}
}
.stat-progress-bar-overlay {
width: 100%;
height: 20px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
overflow: hidden;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.3);
@media (max-height: 450px) and (orientation: landscape) {
height: 16px;
border-radius: 8px;
}
}
.stat-progress-fill-overlay {
height: 100%;
border-radius: 10px;
transition: width 0.5s ease-out;
position: relative;
overflow: hidden;
@media (max-height: 450px) and (orientation: landscape) {
border-radius: 8px;
}
&::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
animation: shimmer 2s infinite;
}
&.full-stat {
width: 100%;
}
}
.hp-fill-overlay {
background: linear-gradient(90deg, #ff6b6b, #ee5a6f);
box-shadow: 0 0 8px rgba(238, 90, 111, 0.6);
}
.mp-fill-overlay {
background: linear-gradient(90deg, #4ecdc4, #44a08d);
box-shadow: 0 0 8px rgba(68, 160, 141, 0.6);
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BossFightComponent } from './boss-fight.component';
describe('BossFightComponent', () => {
let component: BossFightComponent;
let fixture: ComponentFixture<BossFightComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ BossFightComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(BossFightComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,79 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { NbDialogService } from '@nebular/theme';
import { Subject, Observable } from 'rxjs';
import { first, takeUntil } from 'rxjs/operators';
import { MD2Service } from '../../../services/MD2/md2.service';
import { MsgBoxService } from '../../../services/msg-box.service';
import { StateService } from '../../../services/state.service';
import { ADIcon } from '../../../ui/alert-dlg/alert-dlg.model';
import { MD2Icon, MobDlgType, MobInfo } from '../massive-darkness2.model';
import { MD2ComponentBase } from '../MD2Base';
import { SpawnMobDlgComponent } from '../mobs/spawn-mob-dlg/spawn-mob-dlg.component';
@Component({
selector: 'md2-boss-fight',
templateUrl: './boss-fight.component.html',
styleUrls: ['./boss-fight.component.scss']
})
export class BossFightComponent extends MD2ComponentBase {
MobDlgType = MobDlgType;
MD2Icon = MD2Icon;
public get boss() {
return this.md2Service.info.boss;
}
constructor(
private msgBoxService: MsgBoxService,
private dlgService: NbDialogService,
public md2Service: MD2Service,
protected stateService: StateService,
protected route: ActivatedRoute,
protected cdRef: ChangeDetectorRef,
) {
super(md2Service, stateService, route, cdRef);
}
ngOnInit(): void {
super.ngOnInit();
this.md2Service.heroAttackingSubject.pipe(takeUntil(this.destroy$)).subscribe(result => {
if (this.md2Service.info.isBossFight) {
this.attack(this.boss);
}
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
activate() {
this.md2Service.activateBoss();
}
WIN() {
this.msgBoxService.show('Win', { text: 'You Win the Boss Fight', icon: ADIcon.INFO });
this.md2Service.info.isBossFight = false;
this.md2Service.info.boss = undefined;
this.md2Service.heros.forEach(h => h.uiBossFight = false);
this.md2Service.broadcastGameInfo();
}
attack(mob: MobInfo) {
this.dlgService.open(SpawnMobDlgComponent, { context: { title: `Attack ${this.boss.name}`, mode: MobDlgType.BeenAttacked, mob: mob } })
.onClose.pipe(first()).subscribe(mobResult => {
if (mobResult) {
let attackDamage = mobResult.uiWounds;
if (attackDamage) {
this.boss.unitRemainHp -= attackDamage;
if (this.boss.unitRemainHp <= 0) {
this.WIN();
}
this.cdRef.detectChanges();
}
}
});
}
}
@@ -0,0 +1,38 @@
<nb-card>
<nb-card-header>
<img src="{{imgUrl('DoorEvents/Cover.png')}}" width="40px"> Door Event
<!-- <button nbButton hero status="warning" size="small" (click)="initMobDecks()" class="float-right">Reset
Mobs</button> -->
<button nbButton hero status="info" size="small" (click)="drawDoorCard()" class="float-right mr-2">Draw</button>
<!-- <button nbButton hero status="warning" size="tiny" (click)="resetTreasureBag()"
class="float-right">Reset</button>
<button nbButton hero status="primary" size="tiny" (click)="addTreasure(TreasureType.Epic)"
class="float-right mr-1">Add
Epic</button>
<button nbButton hero status="info" size="tiny" (click)="addTreasure(TreasureType.Rare)"
class="float-right mr-1">Add
Rare</button> -->
</nb-card-header>
<nb-card-body>
<!-- <b>Content of the bag</b>
<hr class="my-1"> -->
<div class="row">
<!-- this.mobs <div class="col" *ngFor="let treasure of treasureBag.drawingItems">
<img src="{{treasure.imageUrl}}" width="40px"> X {{treasure.unitAmount}}
</div> -->
<div class="col col-md-3 mb-4" *ngFor="let door of this.drawDoorEvents">
<img class="g-width-95x" src="{{door.imageUrl}}" (click)="showMobImage(mob)" /><br>
<label class="badge badge-info">{{door.name}}</label>
<button nbButton hero status="primary" size="small" class="float-right"
(click)="discard(door)">Discard</button>
</div>
</div>
<!-- <div class="container">
<img id="clip" src="{{imgUrl('Mobs/CoreGame/CoreGameMob.jpg')}}" />
</div> -->
<!-- <button nbButton hero fullWidth="" status="primary" class="mt-3" (click)="drawTreasure()">I feel
LUCKY!!!!!</button> -->
</nb-card-body>
</nb-card>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DoorEventsComponent } from './door-events.component';
describe('DoorEventsComponent', () => {
let component: DoorEventsComponent;
let fixture: ComponentFixture<DoorEventsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DoorEventsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DoorEventsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,72 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { FileService } from '../../../services/file.service';
import { MsgBoxService } from '../../../services/msg-box.service';
import { ADButtons } from '../../../ui/alert-dlg/alert-dlg.model';
import { DrawingBag, DrawingItem } from '../massive-darkness2.model';
import { first } from 'rxjs/operators';
import { ActivatedRoute } from '@angular/router';
import { MD2Service } from '../../../services/MD2/md2.service';
import { StateService } from '../../../services/state.service';
import { MD2Base, MD2ComponentBase } from '../MD2Base';
import { SignalRMessage } from '../../../services/signal-r.service';
@Component({
selector: 'md2-door-events',
templateUrl: './door-events.component.html',
styleUrls: ['./door-events.component.scss']
})
export class DoorEventsComponent extends MD2ComponentBase implements OnInit {
constructor(
private msgBoxService: MsgBoxService,
public md2Service: MD2Service,
protected stateService: StateService,
protected route: ActivatedRoute,
protected cdRef: ChangeDetectorRef,
) {
super(md2Service, stateService, route, cdRef);
}
drawDoorEvents: DrawingItem[];
treasureBag: DrawingBag<DrawingItem> = new DrawingBag<DrawingItem>();
ngOnInit(): void {
this.initDoorEvents();
this.md2Service.signalRService.ReceivedSignalRMessageSubject.subscribe(result => this.handleSignalRCallback(result));
}
handleSignalRCallback(result: SignalRMessage): void {
if (result.actionName == 'openDoor') {
this.drawDoorCard();
}
}
initDoorEvents() {
this.drawDoorEvents = [];
this.treasureBag = new DrawingBag<DrawingItem>();
for (let i = 1; i <= 30; i++) {
this.treasureBag.AddItem(new DrawingItem(i.toString(), '', this.imgUrl(`DoorEvents/DoorEvent-${i}.png`), 1));
}
this.cdRef.detectChanges();
}
drawDoorCard() {
if (this.treasureBag.bagIsEmpty()) {
this.treasureBag.RestoreRemoveItems();
}
let door = this.treasureBag.DrawAndRemove()[0];
this.msgBoxService.show('', { text: `<img src="${door.imageUrl}" class="g-height-70vh g-max-width-80vw">`, buttons: ADButtons.YesNo, confirmButtonText: 'Keep It', cancelButtonText: 'Discard' })
.pipe(first()).subscribe(result => {
if (result) {
door.name = this.md2Service.heroFullName(this.md2Service.currentActivateHero);
this.drawDoorEvents.push(door);
}
this.cdRef.detectChanges();
});
}
discard(door: DrawingItem) {
this.drawDoorEvents.splice(this.drawDoorEvents.indexOf(door), 1);
this.cdRef.detectChanges();
}
}
@@ -0,0 +1,21 @@
import { ObjectUtils } from "../../../utilities/object-utils";
import { IDrawingItem, MobInfo, TreasureItem } from "../massive-darkness2.model";
export class MD2Clone {
public static CloneDrawingItem(obj: IDrawingItem) {
let type = obj.constructor.name;
let cloneObj = null;
switch (type) {
case "TreasureItem":
//let copy = structuredClone(obj);
return new TreasureItem(obj['type'], 1);
break;
case "MobInfo":
return new MobInfo(obj);
break;
default: break;
}
return ObjectUtils.CloneValue(obj);
}
}
@@ -0,0 +1,303 @@
import { Subject } from "rxjs";
import { first, map } from "rxjs/operators";
import { environment } from "../../../../../environments/environment";
import { ADButtons, ADIcon } from "../../../../ui/alert-dlg/alert-dlg.model";
import { MD2Logic } from "../../massive-darkness2.logic";
import { AttackInfo, AttackTarget, IMobFactory, MD2Icon, MobInfo, MobType, TreasureItem, TreasureType } from "../../massive-darkness2.model";
import { MobSkillType } from "../../massive-darkness2.model.boss";
import { MD2DiceSet, MD2MobSkill } from "../../massive-darkness2.db.model";
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` }
export abstract class CoreGameRMFactory implements IMobFactory {
abstract mobName: string;
abstract generate(level: number): MobInfo
protected mob: MobInfo;
protected loadLevelInfo(mobName: string, level: number) {
let levelInfo = CORE_GAME_MOB_LEVEL.find(m => m.name == mobName && level >= m.level);
this.mob = new MobInfo({
type: MobType.RoamingMonster,
name: mobName, hpPerHero: levelInfo.hp, level: level, rewardTokens: levelInfo.rewardTokens,
attackInfos: levelInfo.attackInfos,
defenseInfo: levelInfo.defenseInfo,
actionSubject: new Subject<string>()
});
this.mob.leaderImgUrl = MD2_IMG_URL(`/CoreGame/RoamingMonsters/${this.mob.name}/Stand.png`);
//this.mob.minionImgUrl = MD2_IMG_URL(`CoreGame/Mobs/${encodeURI(this.mob.name)}/Minion.png`);
if (level < 3) {
this.mob.rewardTokens = 2;
this.mob.fixedCarriedTreasure = [new TreasureItem(TreasureType.Rare)];
} else if (level < 5) {
this.mob.rewardTokens = 2;
this.mob.fixedCarriedTreasure = [new TreasureItem(TreasureType.Epic)];
} else {
this.mob.rewardTokens = 0;
this.mob.fixedCarriedTreasure = [new TreasureItem(TreasureType.Epic, 3)];
}
}
iconHtml(icon: MD2Icon, cssClass = '') {
if (icon == MD2Icon.Fire) {
cssClass += ' g-color-google-plus ';
}
if (icon == MD2Icon.Frost || icon == MD2Icon.Mana) {
cssClass += ' g-color-aqua ';
}
if (icon < MD2Icon.RedDice) {
return `<span class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>`
}
else {
return `<span class='MD2Icon dice ${MD2Icon[icon].replace('Dice', '')} ${cssClass}'></span>`;
}
}
}
export class RMUndeadQueenFactory extends CoreGameRMFactory {
mobName: string = 'Ytheria, Undead Queen';
generate(level: number): MobInfo {
this.loadLevelInfo('Ytheria, Undead Queen', level);
this.mob.activateFunction = (mob, msgBoxService, heros) => {
let actionResult = '';
msgBoxService.show('Is There more than 1 Hero in LoS of Undead Queen?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.actions = 0;
mob.actionSubject.next(
`Undead Queen attacks each Hero in LoS(resolve each attack separately).`
);
} else {
msgBoxService.show('Is There any Hero in LoS of Undead Queen?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
if (level < 3) {
actionResult = `Undead Queen +1 ${this.iconHtml(MD2Icon.YellowDice)} when attack`;
} else if (level < 5) {
actionResult = `Undead Queen +1 ${this.iconHtml(MD2Icon.YellowDice)} 1 ${this.iconHtml(MD2Icon.OrangeDice)} when attack`;
} else {
actionResult = `Undead Queen +2 ${this.iconHtml(MD2Icon.OrangeDice)} when attack`;
}
mob.actions = 0;
mob.actionSubject.next(
actionResult
);
} else {
mob.actions = 2;
mob.actionSubject.next(
`Undead Queen Gains 2 Actions`
);
}
});
}
});
}
this.mob.skills = [
{
description: `Add 1 Minion to each Mob in the Dungeon, if possible.`,
type: MobSkillType.Attack,
skillRoll: 1
} as MD2MobSkill];
return this.mob;
}
}
export class RMAndraFactory extends CoreGameRMFactory {
mobName: string = 'Andra';
generate(level: number): MobInfo {
this.loadLevelInfo('Andra', level);
let damage = 2;
if (level < 3) {
damage = 1;
}
this.mob.activateFunction = (mob, msgBoxService, heros) => {
let actionResult = '';
mob.actions = 0;
msgBoxService.show('Is Andra in the Dungeon?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.actions = 0;
msgBoxService.show('Is Any Hero in the LoS of Andra?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.actionSubject.next(
`Andra attack the Hero with the lowest HP in LoS.<br>then Put Andra to out side of Dungeon.(Hero is not reachable but not dead)`
);
} else {
mob.actions = 0;
mob.actionSubject.next(
`Put Andra to out side of Dungeon.(Hero is not reachable but not dead)`
);
}
});
} else {
let beenAttackHero = MD2Logic.getTargetHerosByFilter(heros, AttackTarget.LeastHp, true)[0];
mob.actionSubject.next(
`Place Andra in the same zone of ${MD2Logic.heroFullName(beenAttackHero)} and attack that Hero.`
);
}
});
}
this.mob.skills = [
{
description: `Deal ${damage} wound to another Hero with the lowest HP in LoS`,
type: MobSkillType.Combat,
skillRoll: 1
} as MD2MobSkill];
return this.mob;
}
}
export class RMTheGhoulFactory extends CoreGameRMFactory {
mobName: string = 'The Ghoul';
generate(level: number): MobInfo {
this.loadLevelInfo('The Ghoul', level);
let health = 2;
if (level < 3) {
health = 5;
} else if (level < 5) {
health = 8;
} else {
health = 10;
}
this.mob.activateFunction = (mob, msgBoxService, heros) => {
let actionResult = '';
mob.actions = 0;
msgBoxService.show('Is there any <b>Mob with minion</b> in The Ghoul zone?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.unitRemainHp += health;
mob.actionSubject.next(
`Kill 1 minion in The Ghoul zone(player choose), The Ghoul heals ${health}.`
);
} else {
mob.actionSubject.next(
`The Ghoul moves 3 Zones toward the closest Hero and attack him/her, if possible.`
);
}
});
}
this.mob.skills = [
{
description: `Move the closest <b>Mob with minion</b> 1 Zone toward The Ghoul.`,
type: MobSkillType.Combat,
skillRoll: 1
} as MD2MobSkill];
return this.mob;
}
}
export class RMLyidanIncubusLordFactory extends CoreGameRMFactory {
mobName: string = 'Lyidan, Incubus Lord';
generate(level: number): MobInfo {
this.loadLevelInfo('Lyidan, Incubus Lord', level);
this.mob.activateFunction = (mob, msgBoxService, heros) => {
let actionResult = '';
mob.actions = 0;
msgBoxService.show('Is Incubus Lord in a Light Zone?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.unitRemainHp -= 3;
mob.actionSubject.next(
`The Incubus Lord got 3 Wounds, Move it to the closest Shadow Zone.`
);
} else {
msgBoxService.show('Is there a Hero up to 3 Zones away(regardless of LoS) from The Incubus Lord?', {
icon: ADIcon.QUESTION,
buttons: ADButtons.YesNo
}).pipe(first()).subscribe(result => {
if (result) {
mob.actionSubject.next(
`Place The Incubus Lord in the zone of furthest Hero up to 3 Zones away.<br>` +
`Add 1 ${this.iconHtml(MD2Icon.Fire)} to that Hero and attack him/her.`
);
} else {
mob.actions = 2;
mob.actionSubject.next(
`The Incubus Lord 2 Actions`
);
}
});
}
});
}
this.mob.skills = [
{
description: `After combat, resolve all ${this.iconHtml(MD2Icon.Fire)} on the defending Hero(once per combat).`,
type: MobSkillType.Attack,
skillRoll: 1
} as MD2MobSkill];
return this.mob;
}
}
export class RMBalrogFactory extends CoreGameRMFactory {
mobName: string = 'Balrog';
generate(level: number): MobInfo {
this.loadLevelInfo('Balrog', level);
this.mob.extraRule = `When Balrog is in the Dungeon, ${this.iconHtml(MD2Icon.Fire)} on Heros can't be removed, Heros still suffer its effects when activating.`
this.mob.activateFunction = (mob, msgBoxService, heros) => {
let actionResult = '';
mob.actions = 0;
let noFireHeros = heros.filter(h => h.fireToken == 0);
if (noFireHeros.length == 0) {
mob.actions = 3;
mob.actionSubject.next(
`The Balrog gains 3 Actions`
);
} else {
let fireTokens = Math.round(Math.random() * 2) + 1;
mob.actionSubject.next(
`Balrog ,moves 2 Zones toward to ${MD2Logic.getTargetHerosHtml(noFireHeros)} and Each Hero in ${this.iconHtml(MD2Icon.Magic)} range takes ${fireTokens} ${this.iconHtml(MD2Icon.Fire)}`
);
}
}
this.mob.skills = [
{
description: `The Hero takes 1 ${this.iconHtml(MD2Icon.Fire)}`,
type: MobSkillType.Combat,
skillRoll: 1
} as MD2MobSkill];
return this.mob;
}
}
export const CoreGameRMFactories = [
new RMUndeadQueenFactory(),
new RMAndraFactory(),
new RMTheGhoulFactory(),
new RMLyidanIncubusLordFactory(),
new RMBalrogFactory()
];
@@ -0,0 +1,40 @@
<nb-card status="info" size="large">
<nb-card-header>
<img src="{{md2Service.imgUrl('HeroIcon.png')}}" width="40px">
<span class="ml-2 g-font-size-17">Initialize Game</span>
</nb-card-header>
<nb-card-body>
<div class="form-group">
<label class="label">Select Game Bundles:</label>
<div class="form-group" *ngFor="let bundle of bundleOptions">
<nb-checkbox [checked]="isBundleEnabled(bundle.value)" (checkedChange)="toggleBundle(bundle.value)">
{{ bundle.label }}
</nb-checkbox>
</div>
<small class="form-text text-muted" *ngIf="enabledBundles.length === 0">
At least one bundle must be selected.
</small>
</div>
<div class="form-group">
<nb-checkbox [(checked)]="enableMobSpecialRule">
Enable Mob Special Rules
</nb-checkbox>
</div>
<div class="form-group">
<nb-checkbox [(checked)]="enableHeroBetrayal">
Enable Hero Betrayal
</nb-checkbox>
</div>
</nb-card-body>
<nb-card-footer>
<button class="float-right" nbButton hero status="warning" size="small" (click)="cancel()">
Cancel
</button>
<button class="float-right mr-2" nbButton hero status="primary" size="small"
[disabled]="enabledBundles.length === 0" (click)="submit()">
Initialize
</button>
</nb-card-footer>
</nb-card>
@@ -0,0 +1,12 @@
nb-card {
min-width: 400px;
}
.form-group {
margin-bottom: 1rem;
}
nb-checkbox {
display: block;
margin-bottom: 0.5rem;
}
@@ -0,0 +1,73 @@
import { Component, OnInit, Input } from '@angular/core';
import { NbDialogRef } from '@nebular/theme';
import { GameBundle } from '../massive-darkness2.db.model';
import { MD2Service } from '../../../services/MD2/md2.service';
import { StringUtils } from '../../../utilities/string-utils';
export interface GameInitConfig {
enabledBundles: GameBundle[];
enableMobSpecialRule: boolean;
enableHeroBetrayal: boolean;
}
@Component({
selector: 'ngx-game-init-dlg',
templateUrl: './game-init-dlg.component.html',
styleUrls: ['./game-init-dlg.component.scss']
})
export class GameInitDlgComponent implements OnInit {
GameBundle = GameBundle;
@Input() initialConfig: GameInitConfig;
enabledBundles: GameBundle[] = [GameBundle.CoreGame];
enableMobSpecialRule: boolean = false;
enableHeroBetrayal: boolean = false;
bundleOptions = [];
constructor(
private dlgRef: NbDialogRef<GameInitDlgComponent>,
public md2Service: MD2Service
) { }
ngOnInit(): void {
//For each GameBundle, create a new option
this.bundleOptions = Object.values(GameBundle).filter(b => !isNaN(Number(b))).map(b => ({
value: b as GameBundle, label: StringUtils.camelToTitle(GameBundle[b as GameBundle] as string)
}));
// Initialize from context if provided
if (this.initialConfig) {
this.enabledBundles = [...this.initialConfig.enabledBundles];
this.enableMobSpecialRule = this.initialConfig.enableMobSpecialRule;
this.enableHeroBetrayal = this.initialConfig.enableHeroBetrayal;
}
}
toggleBundle(bundle: GameBundle): void {
const index = this.enabledBundles.indexOf(bundle);
if (index > -1) {
this.enabledBundles.splice(index, 1);
} else {
this.enabledBundles.push(bundle);
}
}
isBundleEnabled(bundle: GameBundle): boolean {
return this.enabledBundles.includes(bundle);
}
submit(): void {
const config: GameInitConfig = {
enabledBundles: this.enabledBundles,
enableMobSpecialRule: this.enableMobSpecialRule,
enableHeroBetrayal: this.enableHeroBetrayal
};
this.dlgRef.close(config);
}
cancel(): void {
this.dlgRef.close();
}
}
@@ -0,0 +1,324 @@
<!-- Hero Selection Screen - Initial -->
<nb-card *ngIf="!hero && !isSelectingHero" class="hero-selection-card">
<nb-card-body class="hero-selection-body">
<div class="hero-selection-content">
<h2 class="hero-selection-title">Choose Your Hero</h2>
<p class="hero-selection-subtitle">Begin your epic adventure</p>
<button nbButton hero status="primary" size="large" class="hero-selection-btn" (click)="initHero()">
<nb-icon icon="star-outline" class="mr-2"></nb-icon>
Select Hero
</button>
</div>
</nb-card-body>
</nb-card>
<!-- Hero Selection Panel -->
<div *ngIf="!hero && isSelectingHero && currentSelectingHero" class="hero-selection-panel">
<div class="row no-gutters hero-selection-row">
<div class="col-12 hero-selection-left">
<div class="hero-selection-card-wrapper">
<div class="hero-selection-header">
<div class="hero-selection-title-bar">
<h3 class="hero-selection-hero-name">{{currentSelectingHero.name}}</h3>
<span class="hero-selection-class">{{HeroClass[selectedHeroClass]}}</span>
<span class="hero-selection-counter">({{currentHeroIndex + 1}} / {{heros.length}})</span>
</div>
</div>
<div class="hero-selection-content-area">
<div class="row no-gutters h-100">
<div class="col-6 hero-select-image-col">
<div class="hero-select-image-wrapper"
[style.background-image]="'url(' + imgUrl('/Mobs/BG.png') + ')'">
<img src="{{imgUrl('Heros/'+className+'.png')}}" class="hero-select-image"
alt="{{currentSelectingHero.name}}">
<!-- HP and Mana Bars -->
<div class="hero-select-stats-overlay">
<div class="stat-bar-overlay hp-bar-overlay">
<div class="stat-bar-label-overlay">
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
<span class="stat-value-overlay">{{currentSelectingHero.hpMaximum}}</span>
</div>
<div class="stat-progress-bar-overlay">
<div class="stat-progress-fill-overlay hp-fill-overlay full-stat"></div>
</div>
</div>
<div class="stat-bar-overlay mp-bar-overlay">
<div class="stat-bar-label-overlay">
<md2-icon [icon]="MD2Icon.Mana_Color" size="sm"></md2-icon>
<span class="stat-value-overlay">{{currentSelectingHero.mpMaximum}}</span>
</div>
<div class="stat-progress-bar-overlay">
<div class="stat-progress-fill-overlay mp-fill-overlay full-stat"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-6 hero-select-skills-col">
<div class="hero-select-skills">
<div class="skills-title">Abilities</div>
<div class="skill-content" [innerHTML]="currentSelectingHero.skillHtml"></div>
<div class="skills-title shadow-skills-title">Shadow Abilities</div>
<div class="skill-content shadow-skill-content"
[innerHTML]="currentSelectingHero.shadowSkillHtml"></div>
</div>
</div>
</div>
</div>
<div class="hero-selection-actions">
<button nbButton hero status="basic" class="nav-hero-btn" (click)="previousHero()">
<nb-icon icon="chevron-back-outline" class="mr-1"></nb-icon>
Previous
</button>
<button nbButton hero status="primary" class="select-hero-btn" (click)="selectCurrentHero()">
<nb-icon icon="checkmark-circle-outline" class="mr-2"></nb-icon>
It's Me!
</button>
<button nbButton hero status="basic" class="nav-hero-btn" (click)="nextHero()">
Next
<nb-icon icon="chevron-forward-outline" class="ml-1"></nb-icon>
</button>
</div>
</div>
</div>
</div>
</div>
<div *ngIf="hero">
<div class="row no-gutters">
<div class="col-12 col-sm-7">
<div class="tp-wrapper mb-2">
<div class="tp-box" [@flipState]="flip">
<div class="tp-box__side tp-box__front ">
<div class="hero-card-content">
<div class="row no-gutters h-100">
<div class="col-6 hero-image-col">
<div class="hero-image-wrapper"
[style.background-image]="'url(' + imgUrl('/Mobs/BG.png') + ')'">
<img src="{{imgUrl('Heros/'+className+'.png')}}" class="hero-image"
(click)="toggleFlip()" alt="{{hero.name}}">
<!-- HP and Mana Bars -->
<div class="hero-stats-overlay">
<div class="stat-bar-overlay hp-bar-overlay">
<div class="stat-bar-label-overlay">
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
<span
class="stat-value-overlay">{{hero.hp}}/{{hero.hpMaximum}}</span>
</div>
<div class="stat-progress-bar-overlay">
<div class="stat-progress-fill-overlay hp-fill-overlay"
[style.width.%]="(hero.hp / hero.hpMaximum) * 100"></div>
</div>
</div>
<div class="stat-bar-overlay mp-bar-overlay">
<div class="stat-bar-label-overlay">
<md2-icon [icon]="MD2Icon.Mana_Color" size="sm"></md2-icon>
<span
class="stat-value-overlay">{{hero.mp}}/{{hero.mpMaximum}}</span>
</div>
<div class="stat-progress-bar-overlay">
<div class="stat-progress-fill-overlay mp-fill-overlay"
[style.width.%]="(hero.mp / hero.mpMaximum) * 100"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-6 hero-skills-col">
<div class="hero-skills">
<div class="skills-title" (click)="showSkills('abilities')">Abilities</div>
<div class="skill-content" [innerHTML]="hero.skillHtml"></div>
<div class="skills-title shadow-skills-title" (click)="showSkills('shadow')">
Shadow Abilities</div>
<div class="skill-content shadow-skill-content"
[innerHTML]="hero.shadowSkillHtml"></div>
</div>
</div>
</div>
</div>
<!-- <img class="MD2HeroCard " src="{{imgUrl('Heros/'+className+'.jpg')}}" (click)="toggleFlip()"> -->
<!-- Action Buttons (Desktop/Landscape) -->
<div class="hero-actions d-block">
<div class="action-buttons-group" *ngIf="hero.uiActivating && hero.remainActions > 0">
<button nbButton hero class="action-btn" status="info" (click)="moveAction()"
*ngIf="!showMoveAction">
<nb-icon icon="arrow-forward-outline" class="mr-1"></nb-icon>
Move
</button>
<button nbButton hero class="action-btn" status="info" (click)="moveActionEnd()"
*ngIf="showMoveAction">
<nb-icon icon="checkmark-outline" class="mr-1"></nb-icon>
End Move
</button>
<button nbButton hero class="action-btn" status="danger"
(click)="action('attackAction')" *ngIf="!showMoveAction && allowAttack">
<nb-icon icon="flash-outline" class="mr-1"></nb-icon>
Attack!
</button>
<button nbButton hero class="action-btn" status="info" (click)="action('tradeAction')"
*ngIf="!showMoveAction">
<nb-icon icon="swap-outline" class="mr-1"></nb-icon>
Trade
</button>
<button nbButton hero class="action-btn" status="success"
(click)="action('recoveryAction')" *ngIf="!showMoveAction">
<nb-icon icon="heart-outline" class="mr-1"></nb-icon>
Recovery
</button>
</div>
<button nbButton hero class="action-btn special-action-btn" status="info" fullWidth
(click)="openDoor()" *ngIf="showMoveAction">
<nb-icon icon="grid-outline" class="mr-1"></nb-icon>
Open Door
</button>
<button nbButton hero fullWidth status="info" class="start-activation-btn"
*ngIf="allowStartAction" (click)="startActivation()">
<nb-icon icon="play-circle-outline" class="mr-2"></nb-icon>
Start Activation
</button>
<button nbButton hero fullWidth status="warning" class="end-activation-btn"
*ngIf="hero.uiActivating" (click)="endActivation()">
<nb-icon icon="stop-circle-outline" class="mr-2"></nb-icon>
End Activation
</button>
</div>
</div>
<div class="tp-box__side tp-box__back">
<div class="row no-gutters">
<div class="col-6">
<img class="MD2HeroCard " src="{{imgUrl('Heros/Guide/'+className+'.jpg')}}"
(click)="toggleFlip()">
</div>
<div class="col-6">
<img class="MD2HeroCard " src="{{imgUrl('Sets/Shadowbane/'+className+'.png')}}"
(click)="toggleFlip()">
</div>
</div>
</div>
</div>
</div>
<!-- <div class="g-max-height-80vh mb-2">
<img class="MD2HeroCard" src="{{hero.imgUrl}}">
<div class="MD2HeroCard">
<span class="MD2text MD2Name">{{hero.name}}</span>
<span class="MD2text MD2Hp">{{hero.hpMaximum}}</span>
<span class="MD2text MD2Mp">{{hero.mpMaximum}}</span>
</div>
<img class="MD2HeroCard" src="{{hero.imgUrl}}">
<img class="MD2HeroCard HpMpBar" src="{{imgUrl('/Heros/Template/Border.png')}}">
</div> -->
</div>
<div class="col-12 col-sm-5">
<nb-card>
<nb-card-body>
<div class="row no-gutters">
<div class="col-6">
<adj-number-input name="heroHP" [(ngModel)]="hero.hp" [maximum]="hero.hpMaximum" minimum="0"
title="{{imgHtml('HpIcon.png','g-height-25')}}" showMaximum
(blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()">
</adj-number-input>
</div>
<div class="col-6">
<adj-number-input name="heroLevel" [(ngModel)]="hero.level" minimum="1" maximum="5"
title="Level" (blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
</div>
<div class="col-6">
<adj-number-input name="heroMana" [(ngModel)]="hero.mp" [maximum]="hero.mpMaximum"
minimum="0" title="{{imgHtml('HeroIcon.png','g-height-25')}}" showMaximum
(blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
</div>
<div class="col-6">
<adj-number-input name="heroExp" [(ngModel)]="hero.exp" minimum="0" title="Exp"
(blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
</div>
<div class="col-6">
<adj-number-input name="heroFire" [(ngModel)]="hero.fireToken" minimum="0"
title="{{iconHtml(MD2Icon.Fire,'g-color-google-plus mr-1 g-font-size-18')}}Fire Token"
(blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
</div>
<div class="col-6">
<adj-number-input name="heroFrozen" [(ngModel)]="hero.frozenToken" minimum="0"
title="{{iconHtml(MD2Icon.Frost,'g-color-aqua mr-1 g-font-size-18')}}Frozen Token"
(blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
</div>
<div class="col-6">
<adj-number-input name="remainActions" [(ngModel)]="hero.remainActions" minimum="0"
title="Remain Actions" (blur)="heroUpdateDebounceTimer.resetTimer()" hideIncreaseBtn
*ngIf="hero.uiActivating">
</adj-number-input>
</div>
<div class="col-6" *ngIf="hero.class==HeroClass.Berserker">
<adj-number-input name="heroRage" [(ngModel)]="hero.rage" minimum="0" maximum="7"
title="{{iconHtml(MD2Icon.Rage,'g-color-google-plus mr-1 g-font-size-18')}}Rage"
(blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
</div>
<div class="col-6" *ngIf="hero.uiShowExtraToken">
<adj-number-input name="heroExtraToken" [(ngModel)]="hero.extraToken" minimum="0"
title="{{hero.uiExtraTokenHtml}} {{hero.uiExtraTokenName}}"
(blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
</div>
<div class="col-6" *ngIf="hero.uiShowExtraToken2">
<adj-number-input name="heroExtraToken2" [(ngModel)]="hero.extraToken2" minimum="0"
title="{{hero.uiExtraTokenHtml2}} {{hero.uiExtraTokenName2}}"
(blur)="heroUpdateDebounceTimer.resetTimer()">
</adj-number-input>
</div>
</div>
<div *ngIf="md2Service.info.isBossFight"></div>
</nb-card-body>
</nb-card>
</div>
</div>
</div>
<!-- <nb-flip-card *ngIf="hero">
<nb-card-front>
<nb-card>
<nb-card-body>
</nb-card-body>
</nb-card>
</nb-card-front>
<nb-card-back>
<nb-card>
<nb-card-body>
Back card text
</nb-card-body>
</nb-card>
</nb-card-back>
</nb-flip-card> -->
@@ -0,0 +1,815 @@
// Hero Selection Screen
.hero-selection-card {
border-radius: 16px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
overflow: hidden;
}
.hero-selection-body {
padding: 3rem 2rem;
text-align: center;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.9) 0%, rgba(118, 75, 162, 0.9) 100%);
@media (max-height: 450px) and (orientation: landscape) {
padding: 2rem 1.5rem;
}
}
.hero-selection-content {
max-width: 500px;
margin: 0 auto;
}
.hero-selection-title {
color: white;
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.75rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
@media (max-height: 450px) and (orientation: landscape) {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
}
.hero-selection-subtitle {
color: rgba(255, 255, 255, 0.9);
font-size: 1rem;
margin-bottom: 1.5rem;
@media (max-height: 450px) and (orientation: landscape) {
font-size: 0.85rem;
margin-bottom: 1rem;
}
}
.hero-selection-btn {
padding: 0.75rem 2rem;
font-size: 1rem;
font-weight: 600;
border-radius: 50px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: all 0.3s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
}
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.6rem 1.5rem;
font-size: 0.9rem;
}
}
// Hero Selection Panel
.hero-selection-panel {
height: 85vh;
max-height: 85vh;
padding: 0.5rem;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
overflow: hidden;
display: flex;
flex-direction: column;
@media (orientation: landscape) {
height: 85vh;
max-height: 85vh;
padding: 0.25rem;
}
@media (orientation: portrait) {
height: 85vh;
max-height: 85vh;
padding: 0.5rem;
}
@media (orientation: portrait) and (max-height: 667px) {
height: 85vh;
max-height: 85vh;
padding: 0.25rem;
}
@media (max-height: 450px) and (orientation: landscape) {
height: 85vh;
max-height: 85vh;
padding: 0.15rem;
}
}
.hero-selection-row {
height: 100%;
margin: 0;
display: flex;
flex-direction: column;
}
.hero-selection-left {
padding: 0;
height: 100%;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0;
}
}
.hero-selection-card-wrapper {
height: 100%;
background: white;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
overflow: hidden;
@media (max-height: 450px) and (orientation: landscape) {
border-radius: 8px;
}
}
.hero-selection-header {
padding: 0.75rem 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
flex-shrink: 0;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.5rem 0.75rem;
border-bottom-width: 1px;
}
}
.hero-selection-title-bar {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
@media (max-height: 450px) and (orientation: landscape) {
gap: 0.5rem;
}
@media (orientation: portrait) and (max-width: 767px) {
gap: 0.5rem;
}
}
.hero-selection-hero-name {
font-size: 1.2rem;
font-weight: 700;
margin: 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
@media (max-height: 450px) and (orientation: landscape) {
font-size: 0.95rem;
}
@media (orientation: portrait) and (max-width: 767px) {
font-size: 1rem;
}
}
.hero-selection-class {
font-size: 0.85rem;
opacity: 0.9;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 500;
@media (max-height: 450px) and (orientation: landscape) {
font-size: 0.7rem;
}
@media (orientation: portrait) and (max-width: 767px) {
font-size: 0.75rem;
}
}
.hero-selection-counter {
font-size: 0.8rem;
opacity: 0.8;
margin-left: auto;
@media (max-height: 450px) and (orientation: landscape) {
font-size: 0.65rem;
}
@media (orientation: portrait) and (max-width: 767px) {
font-size: 0.7rem;
}
}
.hero-selection-content-area {
flex: 1;
padding: 0.75rem;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 0;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.4rem;
}
@media (orientation: portrait) and (max-width: 767px) {
padding: 0.5rem;
}
}
.hero-select-image-col,
.hero-select-skills-col {
padding: 0.4rem;
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.2rem;
}
@media (orientation: portrait) and (max-width: 767px) {
padding: 0.3rem;
}
}
.hero-select-image-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 8px;
background: #f8f9fa;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
position: relative;
}
.hero-select-image {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
.hero-select-stats-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 0.5rem;
background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
border-radius: 0 0 8px 8px;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.35rem;
}
}
.hero-select-skills {
width: 100%;
height: 100%;
background: #f8f9fa;
border-radius: 8px;
padding: 0.75rem;
overflow-y: auto;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
min-height: 0;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.4rem;
}
@media (orientation: portrait) and (max-width: 767px) {
padding: 0.5rem;
}
}
.hero-selection-actions {
padding: 0.75rem;
border-top: 2px solid #e9ecef;
display: flex;
gap: 0.75rem;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.5rem;
gap: 0.35rem;
border-top-width: 1px;
}
@media (orientation: portrait) and (max-width: 767px) {
padding: 0.5rem;
gap: 0.5rem;
}
}
.nav-hero-btn {
flex: 1;
min-height: 40px;
border-radius: 8px;
font-weight: 600;
font-size: 0.875rem;
transition: all 0.3s;
padding: 0.5rem;
@media (max-height: 450px) and (orientation: landscape) {
min-height: 32px;
font-size: 0.75rem;
padding: 0.35rem 0.5rem;
}
@media (orientation: portrait) and (max-width: 767px) {
min-height: 38px;
font-size: 0.8rem;
}
}
.select-hero-btn {
flex: 2;
min-height: 40px;
border-radius: 8px;
font-weight: 600;
font-size: 0.95rem;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
transition: all 0.3s;
padding: 0.5rem;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.5);
}
@media (max-height: 450px) and (orientation: landscape) {
min-height: 32px;
font-size: 0.8rem;
padding: 0.35rem 0.5rem;
}
@media (orientation: portrait) and (max-width: 767px) {
min-height: 38px;
font-size: 0.85rem;
}
}
.MD2Hp {
font-size: xx-large;
position: fixed;
z-index: 92;
color: white;
bottom: 23%;
left: 46%;
}
.MD2Mp {
font-size: xx-large;
position: fixed;
z-index: 92;
color: white;
bottom: 22%;
left: 7%;
}
.MD2Name {
font-size: xx-large;
position: fixed;
z-index: 92;
color: #2e2e30;
bottom: 20%;
left: 23%;
}
.MD2HeroCard {
//position: absolute;
max-width: 100%;
max-height: 100%;
&.HpMpBar {
z-index: 10;
}
}
.tp-wrapper {
-webkit-perspective: 800px;
perspective: 800px;
height: 40vh; // Default for portrait
@media (orientation: landscape) {
height: 85vh;
}
@media (orientation: portrait) and (max-height: 667px) {
height: 50vh;
}
}
.tp-box {
position: relative;
width: 100%;
height: 100%;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-transform: transform 1s;
-ms-transform: transform 1s;
transform: transform 1s;
}
.tp-box__side {
width: 100%;
height: 100%;
position: absolute;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
display: flex;
flex-direction: column;
overflow: hidden;
}
// Hero Card Content Section
.hero-card-content {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
padding: 0.5rem;
overflow: hidden;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.25rem;
}
}
.hero-image-col {
padding: 0.25rem;
display: flex;
flex-direction: column;
height: 100%;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.15rem;
}
}
.hero-image-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 8px;
background: #f8f9fa;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
position: relative;
}
.hero-image {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
cursor: pointer;
transition: transform 0.3s;
&:hover {
transform: scale(1.02);
}
}
// HP and Mana Bars Overlay
.hero-stats-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 0.5rem;
background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
border-radius: 0 0 8px 8px;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.35rem;
}
}
.stat-bar-overlay {
margin-bottom: 0.5rem;
&:last-child {
margin-bottom: 0;
}
@media (max-height: 450px) and (orientation: landscape) {
margin-bottom: 0.35rem;
}
}
.stat-bar-label-overlay {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.25rem;
gap: 0.5rem;
md2-icon {
flex-shrink: 0;
width: 18px;
height: 18px;
}
.stat-value-overlay {
color: white;
font-size: 0.75rem;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
flex-shrink: 0;
}
@media (max-height: 450px) and (orientation: landscape) {
margin-bottom: 0.15rem;
gap: 0.3rem;
md2-icon {
width: 14px;
height: 14px;
}
.stat-value-overlay {
font-size: 0.65rem;
}
}
}
.stat-progress-bar-overlay {
width: 100%;
height: 20px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
overflow: hidden;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.3);
@media (max-height: 450px) and (orientation: landscape) {
height: 16px;
border-radius: 8px;
}
}
.stat-progress-fill-overlay {
height: 100%;
border-radius: 10px;
transition: width 0.5s ease-out;
position: relative;
overflow: hidden;
@media (max-height: 450px) and (orientation: landscape) {
border-radius: 8px;
}
&::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
animation: shimmer 2s infinite;
}
&.full-stat {
width: 100%;
}
}
.hp-fill-overlay {
background: linear-gradient(90deg, #ff6b6b, #ee5a6f);
box-shadow: 0 0 8px rgba(238, 90, 111, 0.6);
}
.mp-fill-overlay {
background: linear-gradient(90deg, #4ecdc4, #44a08d);
box-shadow: 0 0 8px rgba(68, 160, 141, 0.6);
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.hero-skills-col {
padding: 0.25rem;
display: flex;
flex-direction: column;
height: 100%;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.15rem;
}
}
.hero-skills {
width: 100%;
height: 100%;
background: #f8f9fa;
border-radius: 8px;
padding: 0.75rem;
overflow-y: auto;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.5rem;
}
}
.skills-title {
font-size: 0.95rem;
font-weight: 700;
color: #667eea;
margin-bottom: 0.5rem;
padding-bottom: 0.25rem;
border-bottom: 2px solid #667eea;
@media (max-height: 450px) and (orientation: landscape) {
font-size: 0.75rem;
margin-bottom: 0.3rem;
padding-bottom: 0.15rem;
}
}
.shadow-skills-title {
margin-top: 1rem;
color: #764ba2;
border-bottom-color: #764ba2;
@media (max-height: 450px) and (orientation: landscape) {
margin-top: 0.5rem;
}
}
.skill-content {
font-size: 0.85rem;
line-height: 1.5;
color: #495057;
@media (max-height: 450px) and (orientation: landscape) {
font-size: 0.7rem;
line-height: 1.3;
}
}
.shadow-skill-content {
color: #6c757d;
}
// Action Buttons Section
.hero-actions {
padding: 0.75rem;
border-top: 2px solid #e9ecef;
margin-top: auto;
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.5rem;
border-top-width: 1px;
}
}
.action-buttons-group {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.75rem;
@media (max-height: 450px) and (orientation: landscape) {
gap: 0.25rem;
margin-bottom: 0.5rem;
flex-wrap: nowrap;
}
}
.action-btn {
flex: 1;
min-width: 100px;
min-height: 40px;
border-radius: 8px;
font-weight: 600;
font-size: 0.875rem;
transition: all 0.3s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
@media (max-height: 450px) and (orientation: landscape) {
min-width: 70px;
min-height: 36px;
font-size: 0.75rem;
padding: 0.35rem 0.5rem;
}
}
.special-action-btn {
margin-bottom: 0.5rem;
border-radius: 8px;
font-weight: 600;
min-height: 40px;
font-size: 0.875rem;
transition: all 0.3s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
@media (max-height: 450px) and (orientation: landscape) {
margin-bottom: 0.35rem;
min-height: 32px;
font-size: 0.75rem;
padding: 0.35rem 0.5rem;
}
}
.start-activation-btn {
border-radius: 8px;
font-weight: 600;
padding: 0.75rem;
min-height: 44px;
font-size: 0.95rem;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.5rem;
min-height: 36px;
font-size: 0.8rem;
}
}
.end-activation-btn {
border-radius: 8px;
font-weight: 600;
padding: 0.75rem;
min-height: 44px;
font-size: 0.95rem;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(255, 193, 7, 0.3);
margin-top: 0.5rem;
background: linear-gradient(135deg, #ffc107 0%, #ff9800 100%);
border: none;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.4);
}
@media (max-height: 450px) and (orientation: landscape) {
padding: 0.5rem;
min-height: 36px;
font-size: 0.85rem;
margin-top: 0.35rem;
}
}
.tp-box__front {
-webkit-transform: rotateY(0deg);
-ms-transform: rotateY(0deg);
transform: rotateY(0deg);
background: white;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.tp-box__back {
-webkit-transform: rotateY(-180deg);
-ms-transform: rotateY(-180deg);
transform: rotateY(-180deg);
}
::ng-deep .skill-content .MD2Icon {
font-size: 30px;
}
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeroDashboardComponent } from './hero-dashboard.component';
describe('HeroDashboardComponent', () => {
let component: HeroDashboardComponent;
let fixture: ComponentFixture<HeroDashboardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HeroDashboardComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HeroDashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,307 @@
import { trigger, state, style, transition, animate } from '@angular/animations';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { first } from 'rxjs/operators';
import { DropDownOption } from '../../../entity/dropDownOption';
import { GameRoomService } from '../../../services/game-room.service';
import { MD2Service } from '../../../services/MD2/md2.service';
import { MsgBoxService } from '../../../services/msg-box.service';
import { StateService } from '../../../services/state.service';
import { ADButtonColor, ADButtons } from '../../../ui/alert-dlg/alert-dlg.model';
import { ArrayUtils } from '../../../utilities/array-utils';
import { StringUtils } from '../../../utilities/string-utils';
import { DebounceTimer } from '../../../utilities/timer-utils';
import { HeroClass, MD2HeroInfo, MD2HeroProfile, MD2Icon } from '../massive-darkness2.model';
import { MD2Base } from '../MD2Base';
import { MD2HeroProfileService } from '../service/massive-darkness2.service';
import { SignalRService } from '../../../services/signal-r.service';
import { NbToastrService } from '@nebular/theme';
@Component({
selector: 'ngx-hero-dashboard',
templateUrl: './hero-dashboard.component.html',
styleUrls: ['./hero-dashboard.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
trigger('flipState', [
state('active', style({
transform: 'rotateY(179deg)'
})),
state('inactive', style({
transform: 'rotateY(0)'
})),
transition('active => inactive', animate('500ms ease-out')),
transition('inactive => active', animate('500ms ease-in'))
])
]
})
export class HeroDashboardComponent extends MD2Base implements OnInit {
MD2Icon = MD2Icon;
heroAction(hero: MD2HeroInfo, action: string) {
throw new Error('Method not implemented.');
}
showMoveAction: boolean = false;
HeroClass = HeroClass;
refreshUI() {
console.log('HeroDashboard RefreshUI');
}
heroUpdateDebounceTimer = new DebounceTimer(1000, () => {
this.broadcastHeroInfo();
})
classOptions: DropDownOption[] = [
new DropDownOption(HeroClass.Berserker, 'Berserker'),
new DropDownOption(HeroClass.Paladin, 'Paladin'),
new DropDownOption(HeroClass.Ranger, 'Ranger'),
new DropDownOption(HeroClass.Rogue, 'Rogue'),
new DropDownOption(HeroClass.Wizard, 'Wizard'),
new DropDownOption(HeroClass.Shaman, 'Shaman'),
new DropDownOption(HeroClass.Druid, 'Druid'),
new DropDownOption(HeroClass.Necromancer, 'Necromancer'),
new DropDownOption(HeroClass.Monk, 'Monk'),
new DropDownOption(HeroClass.Thinker, 'Thinker'),
new DropDownOption(HeroClass.Bard, 'Bard'),
];
heros = [] as MD2HeroInfo[];
heroProfiles: MD2HeroProfile[] = [];
currentHeroIndex: number = 0;
isSelectingHero: boolean = false;
selectedHeroClass: HeroClass;
public get hero() {
return this.md2Service.playerHero;
}
public get className() {
if (this.md2Service.playerHero) {
return HeroClass[this.md2Service.playerHero.class];
}
if (this.selectedHeroClass) {
return HeroClass[this.selectedHeroClass];
}
return '';
}
public get currentSelectingHero(): MD2HeroInfo {
return this.heros[this.currentHeroIndex];
}
constructor(
private gameRoomService: GameRoomService,
public md2Service: MD2Service,
private heroProfileService: MD2HeroProfileService,
protected stateService: StateService,
protected route: ActivatedRoute,
protected cdRef: ChangeDetectorRef,
private msgBoxService: MsgBoxService,
private signalRService: SignalRService,
private toastrService: NbToastrService
) {
super(md2Service, stateService, route, cdRef);
this.isHeroDashboard = true;
}
public get allowAttack(): boolean {
return this.hero.uiBossFight || this.md2Service.mobs?.length > 0 || this.md2Service.roamingMonsters?.length > 0;
}
ngOnInit(): void {
super.ngOnInit();
this.gameRoomService.gameRoomId = this.roomId;
this.gameRoomService.joinGameRoom(this.roomId);
//this.fetchGameInfo();
this.signalRService.signalRMessageConnSubject.subscribe(state => {
//fetchGameInfo is called in MD2Base.handleSignalRCallback sendJoinInfo message
// if (state.status == 'connected') {
// this.fetchGameInfo();
// }
});
}
override signalRInitialized() {
// this.gameRoomService.joinGameRoom(this.roomId);
// if (this.md2Service.initialized == false) {
// this.gameRoomService.createGameRoom('MD2');
// this.md2Service.initialized = true;
// }
}
initHero() {
if (!this.md2Service.heros.some(h => h.playerInfo.signalRClientId == this.stateService.loginUserService.userAccess.signalRConnectionId)) {
this.msgBoxService.showInputbox('Select Hero Class', '', { dropDownOptions: this.classOptions, inputType: 'dropdown' })
.pipe(first()).subscribe(heroClass => {
if (heroClass != null) {
// switch (heroClass) {
// case HeroClass.Berserker: break;
// case HeroClass.Wizard:
// this.heros = this.wizards;
// break;
// case HeroClass.Rogue: break;
// case HeroClass.Ranger: break;
// case HeroClass.Shaman: break;
// case HeroClass.Paladin: break;
// default: break;
// }
// this.showHeroList(heroClass, 0);
this.initClassHeroList(heroClass);
}
});
}
}
initClassHeroList(heroClass: HeroClass) {
this.heros = [];
this.selectedHeroClass = heroClass;
this.heroProfileService.getAll().pipe(first()).subscribe(result => {
this.heroProfiles = result.filter(h => h.heroClass == heroClass);
for (let i = 0; i < this.heroProfiles.length; i++) {
const heroProfile = this.heroProfiles[i];
const heroInfo = new MD2HeroInfo({
name: heroProfile.title,
mpMaximum: heroProfile.mana,
hpMaximum: heroProfile.hp,
hp: heroProfile.hp,
mp: heroProfile.mana,
skillHtml: heroProfile.skillHtml,
shadowSkillHtml: heroProfile.shadowSkillHtml.replace("<p>", '<p>' + this.iconHtml(MD2Icon.Shadow) + ' : '),
class: heroClass
});
heroInfo.imgUrl = this.imgUrl('Heros/' + HeroClass[heroClass] + '.jpg');
this.heros.push(heroInfo);
}
this.heros = ArrayUtils.Shuffle(this.heros);//.sort((a, b) => StringUtils.compareSemVer(a.name, b.name));
this.currentHeroIndex = 0;
this.isSelectingHero = true;
this.detectChanges();
});
}
selectCurrentHero() {
if (this.currentSelectingHero) {
this.md2Service.playerJoin(this.currentSelectingHero);
this.md2Service.broadcastMyHeroInfo();
this.isSelectingHero = false;
this.detectChanges();
}
}
showSkills(type: string) {
if (type == 'abilities') {
this.msgBoxService.show('Abilities', { text: this.currentSelectingHero.skillHtml });
} else {
this.msgBoxService.show('Shadow Abilities', { text: this.currentSelectingHero.shadowSkillHtml });
}
}
nextHero() {
this.currentHeroIndex++;
if (this.currentHeroIndex >= this.heros.length) {
this.currentHeroIndex = 0;
}
this.detectChanges();
}
previousHero() {
this.currentHeroIndex--;
if (this.currentHeroIndex < 0) {
this.currentHeroIndex = this.heros.length - 1;
}
this.detectChanges();
}
fetchGameInfo() {
this.md2Service.broadcastFetchGameInfo();
}
broadcastHeroInfo() {
this.md2Service.broadcastMyHeroInfo();
this.heroUpdateDebounceTimer.clearOut();
}
increaseRage() {
if (this.hero.rage < 7) {
this.hero.rage++;
}
}
openDoor() {
this.md2Service.broadcastHeroAction('openDoor');
//this.showMoveAction = false;
this.detectChanges();
}
moveAction() {
this.showMoveAction = true;
this.detectChanges();
}
moveActionEnd() {
this.showMoveAction = false;
this.reduceAction();
this.detectChanges();
}
action(action: string) {
this.showMoveAction = false;
switch (action) {
case 'recoveryAction':
this.msgBoxService.show('Recovery', { text: 'takes the Recover action may gain up to 2 Health or Mana in any combination (either 2 Health, 2 Mana, or 1 of each).' });
break;
case 'attackAction':
this.msgBoxService.show('Attacking', { text: 'Please process attacking action in Dashboard.' });
break;
default:
break;
}
this.md2Service.broadcastHeroAction(action);
this.reduceAction();
}
reduceAction() {
this.hero.remainActions -= 1;
this.detectChanges();
this.broadcastHeroInfo();
}
flip: string = 'inactive';
toggleFlip() {
this.flip = (this.flip == 'inactive') ? 'active' : 'inactive';
}
get allowStartAction() {
return !this.md2Service.heros.some(h => h.uiActivating) && !this.hero.uiActivating && this.hero.remainActions > 0;
}
startActivation() {
this.hero.uiActivating = true;
//this.hero.remainActions = 3;
if (this.hero.fireToken > 0) {
this.msgBoxService.show(`You Are On ${this.iconHtml(MD2Icon.Fire)}!`, {
text: `Roll ${this.iconHtml(MD2Icon.YellowDice)} ${this.hero.fireToken} times.`
});
}
if (this.hero.frozenToken > 0) {
let loseActions = Math.min(this.hero.frozenToken, this.hero.remainActions);
this.hero.remainActions -= loseActions;
this.hero.frozenToken -= loseActions;
this.msgBoxService.show(`It's So Cold ${this.iconHtml(MD2Icon.Frost)}!`, {
text: `Lose ${loseActions} actions.`
});
}
if (this.hero.remainActions == 0) {
this.hero.uiActivating = false;
}
this.broadcastHeroInfo();
}
endActivation() {
if (this.hero.remainActions > 0) {
this.msgBoxService.show('Are you sure?', { text: `End Activation will lose ${this.hero.remainActions} remaining actions.`, buttons: ADButtons.YesNo }).pipe(first()).subscribe(result => {
if (result) {
this.hero.remainActions = 0;
this.endActivation();
}
});
} else {
this.hero.uiActivating = false;
this.broadcastHeroInfo();
this.detectChanges();
}
}
}

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