From f88cd21b3311d867b701f56da36905dd0eb3f74c Mon Sep 17 00:00:00 2001 From: Chris Chen Date: Mon, 3 Nov 2025 15:35:52 -0800 Subject: [PATCH] Update MD2 editor --- package-lock.json | 180 ++++-- package.json | 1 + .../cell-group-routine-events.component.ts | 2 - src/app/cell-group/MyAppBase.ts | 3 - .../massive-darkness2.component.html | 2 +- .../massive-darkness2.component.ts | 2 +- .../massive-darkness2.model.boss.ts | 2 +- .../md2-html-editor.component.html | 60 +- .../md2-html-editor.component.ts | 589 +++++++++--------- .../md2-icon-picker-dlg.component.ts | 56 +- .../massive-darkness2/mobs/mobs.component.ts | 2 - src/app/services/MD2/md2-state.service.ts | 4 +- src/app/services/crudServices/crud.service.ts | 324 +++++----- src/app/utilities/string-utils.ts | 1 - src/assets/styles/md2.scss | 1 + src/index.html | 3 + src/main.ts | 2 + src/polyfills.ts | 2 +- src/tsconfig.app.json | 5 +- src/tsconfig.spec.json | 3 +- 20 files changed, 707 insertions(+), 537 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9bed70f..a6c9b71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,7 @@ "@angular/cli": "^17.3.3", "@angular/compiler-cli": "^17.3.3", "@angular/language-service": "17.3.3", + "@angular/localize": "^17.3.3", "@compodoc/compodoc": "1.0.1", "@fortawesome/fontawesome-free": "^5.2.0", "@schematics/angular": "^14.1.3", @@ -856,6 +857,30 @@ "node": "^18.13.0 || >=20.9.0" } }, + "node_modules/@angular/localize": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-17.3.3.tgz", + "integrity": "sha512-gahGKy0VBZ+KP6MUULGQMoi5SN3REwslaPvtomizzz9fdmqHfR8PPd1vOJSNm2IEVlvm1hv1dDRjPcR4DJwvaQ==", + "dev": true, + "dependencies": { + "@babel/core": "7.23.9", + "@types/babel__core": "7.20.5", + "fast-glob": "3.3.2", + "yargs": "^17.2.1" + }, + "bin": { + "localize-extract": "tools/bundles/src/extract/cli.js", + "localize-migrate": "tools/bundles/src/migrate/cli.js", + "localize-translate": "tools/bundles/src/translate/cli.js" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/compiler": "17.3.3", + "@angular/compiler-cli": "17.3.3" + } + }, "node_modules/@angular/platform-browser": { "version": "17.3.3", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.3.tgz", @@ -1325,18 +1350,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "engines": { "node": ">=6.9.0" @@ -2687,14 +2712,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -12453,6 +12477,47 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -29691,15 +29756,6 @@ "node": ">=0.6.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -32340,6 +32396,18 @@ "integrity": "sha512-OtdWNY0Syg4UvA8j2IhQJeq/UjWHYbRiyUcZjGKPRzuqIPjUhsmMyuW3zpi7Pwx2CpBzZXcik1Ra2WZ0gbwigg==", "dev": true }, + "@angular/localize": { + "version": "17.3.3", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-17.3.3.tgz", + "integrity": "sha512-gahGKy0VBZ+KP6MUULGQMoi5SN3REwslaPvtomizzz9fdmqHfR8PPd1vOJSNm2IEVlvm1hv1dDRjPcR4DJwvaQ==", + "dev": true, + "requires": { + "@babel/core": "7.23.9", + "@types/babel__core": "7.20.5", + "fast-glob": "3.3.2", + "yargs": "^17.2.1" + } + }, "@angular/platform-browser": { "version": "17.3.3", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.3.tgz", @@ -32679,15 +32747,15 @@ } }, "@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true }, "@babel/helper-validator-option": { @@ -33607,14 +33675,13 @@ } }, "@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" } }, "@colors/colors": { @@ -40050,6 +40117,47 @@ } } }, + "@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "requires": { + "@babel/types": "^7.28.2" + } + }, "@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -53452,12 +53560,6 @@ "os-tmpdir": "~1.0.2" } }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", diff --git a/package.json b/package.json index 5ba83c5..5a560d5 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "@angular/cli": "^17.3.3", "@angular/compiler-cli": "^17.3.3", "@angular/language-service": "17.3.3", + "@angular/localize": "^17.3.3", "@compodoc/compodoc": "1.0.1", "@fortawesome/fontawesome-free": "^5.2.0", "@schematics/angular": "^14.1.3", diff --git a/src/app/admin/cell-group-routine-events/cell-group-routine-events.component.ts b/src/app/admin/cell-group-routine-events/cell-group-routine-events.component.ts index deef103..1c899b6 100644 --- a/src/app/admin/cell-group-routine-events/cell-group-routine-events.component.ts +++ b/src/app/admin/cell-group-routine-events/cell-group-routine-events.component.ts @@ -3,14 +3,12 @@ import { ActivatedRoute } from '@angular/router'; import { NbDialogService } from '@nebular/theme'; import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; -import { inherits } from 'util'; import { CellGroupRoutineEvents } from '../../entity/CellGroupRoutineEvents'; import { CellGroupRoutineEventsService } from '../../services/crudServices/cell-group-routine-events.service'; import { MsgBoxService } from '../../services/msg-box.service'; import { StateService } from '../../services/state.service'; import { FancySettings } from '../../ui/fancy-table/fancy-settings.model'; import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component'; -import { ObjectUtils } from '../../utilities/object-utils'; @Component({ selector: 'ngx-cell-group-routine-events', diff --git a/src/app/cell-group/MyAppBase.ts b/src/app/cell-group/MyAppBase.ts index e2a0bea..dec3d27 100644 --- a/src/app/cell-group/MyAppBase.ts +++ b/src/app/cell-group/MyAppBase.ts @@ -1,8 +1,5 @@ import { ActivatedRoute } from "@angular/router"; -import { basename } from "path"; import { Observable, Subject } from "rxjs"; -import { ScreenBase } from "../ScreenBase"; -import { ICrudService } from "../services/crudServices/crud.service"; import { PastoralDomainService } from "../services/crudServices/pastoral-domain.service"; import { StateService } from "../services/state.service"; import { first } from 'rxjs/operators'; diff --git a/src/app/games/massive-darkness2/massive-darkness2.component.html b/src/app/games/massive-darkness2/massive-darkness2.component.html index ef90886..09e01bf 100644 --- a/src/app/games/massive-darkness2/massive-darkness2.component.html +++ b/src/app/games/massive-darkness2/massive-darkness2.component.html @@ -12,10 +12,10 @@
-
--> +
diff --git a/src/app/games/massive-darkness2/massive-darkness2.component.ts b/src/app/games/massive-darkness2/massive-darkness2.component.ts index 5bfdce5..5ae0385 100644 --- a/src/app/games/massive-darkness2/massive-darkness2.component.ts +++ b/src/app/games/massive-darkness2/massive-darkness2.component.ts @@ -25,7 +25,7 @@ import { NumberUtils } from '../../utilities/number-utils'; changeDetection: ChangeDetectionStrategy.OnPush }) export class MassiveDarkness2Component extends MD2Base implements OnInit { - HeroClass: HeroClass + HeroClass = HeroClass; constructor( private fileService: FileService, private initService: MD2InitService, diff --git a/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts b/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts index 342e9aa..836de74 100644 --- a/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts +++ b/src/app/games/massive-darkness2/massive-darkness2.model.boss.ts @@ -1,4 +1,4 @@ -import { stringify } from "querystring" + import { Observable, Subject, Subscription } from "rxjs" import { first } from "rxjs/operators" import { MD2Service } from "../../services/MD2/md2.service" diff --git a/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.html b/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.html index 0f1f036..0712807 100644 --- a/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.html +++ b/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.html @@ -1,20 +1,50 @@ - + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - Insert Icon - + \ No newline at end of file diff --git a/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.ts b/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.ts index dc92f09..fb0939a 100644 --- a/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.ts +++ b/src/app/games/massive-darkness2/md2-html-editor/md2-html-editor.component.ts @@ -1,6 +1,6 @@ -import { Component, ElementRef, EventEmitter, Inject, Input, NgZone, Output, PLATFORM_ID, Renderer2, ViewChild } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Inject, Input, NgZone, Output, PLATFORM_ID, Renderer2, ViewChild, AfterViewInit } from '@angular/core'; import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors } from '@angular/forms'; -import { EditorComponent } from '@progress/kendo-angular-editor'; +import { EditorComponent, NodeSpec, schema, Schema, FontSizeItem } from '@progress/kendo-angular-editor'; import { MsgBoxService } from '../../../services/msg-box.service'; import { DropDownOption } from '../../../entity/dropDownOption'; @@ -9,21 +9,42 @@ import { first } from 'rxjs/operators'; import { MD2StateService } from '../../../services/MD2/md2-state.service'; import { MD2IconPickerDlgComponent } from './md2-icon-picker-dlg.component'; import { NbDialogService } from '@nebular/theme'; - +import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model'; @Component({ selector: 'md2-html-editor', templateUrl: './md2-html-editor.component.html', styleUrl: './md2-html-editor.component.scss' }) -export class MD2HtmlEditorComponent implements ControlValueAccessor { +export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewInit { @ViewChild(EditorComponent) editor: EditorComponent; + @ViewChild('fontSizeDropdown') fontSizeDropdown: any; value: string = ''; disabled: boolean = false; - messageTemplateSchema = this.createCustomSchema(); + messageTemplateSchema = this.createCustomSchema(); customCssClass = '.MD2Icon{font-family: "Massive Darkness 2", sans-serif !important; font-size: 40px; margin-left:5px} body{font-size: 30px; }'; + // Default font size: 30px + fontSizeData: FontSizeItem[] = [ + { size: 30, text: '30px' }, + { size: 8, text: '8px' }, + { size: 10, text: '10px' }, + { size: 12, text: '12px' }, + { size: 14, text: '14px' }, + { size: 16, text: '16px' }, + { size: 18, text: '18px' }, + { size: 20, text: '20px' }, + { size: 24, text: '24px' }, + { size: 30, text: '30px' }, + { size: 36, text: '36px' }, + { size: 48, text: '48px' }, + { size: 60, text: '60px' }, + { size: 72, text: '72px' } + ]; + + defaultFontSize: FontSizeItem = { size: 30, text: '30px' }; + // ControlValueAccessor interface private onChangeFn = (value: string) => { }; private onTouchedFn = () => { }; @@ -36,6 +57,23 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor { } + ngAfterViewInit(): void { + // Set default font size after view initialization + // The fontSizeDropdown is the EditorFontSizeComponent instance + setTimeout(() => { + if (this.fontSizeDropdown) { + // Access the fontSizeDropDownList component which has the defaultItem property + if (this.fontSizeDropdown.fontSizeDropDownList) { + this.fontSizeDropdown.fontSizeDropDownList.defaultItem = this.defaultFontSize; + } + // Also try setting it directly on the component if it has the property + if (this.fontSizeDropdown.defaultItem !== undefined) { + this.fontSizeDropdown.defaultItem = this.defaultFontSize; + } + } + }, 0); + } + // ControlValueAccessor implementation writeValue(value: string): void { this.value = value || ''; @@ -68,6 +106,8 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor { closeOnEsc: true }).onClose.pipe(first()).subscribe((html: string) => { if (html && this.editor) { + this.insertAfterSelection(html, true); + return; // Insert the HTML content at the current cursor position // Use ProseMirror's dispatch method to insert content at cursor const view = this.editor.view; @@ -110,303 +150,296 @@ export class MD2HtmlEditorComponent implements ControlValueAccessor { } }); } - - // Text manipulation methods for Kendo Editor - /** - * Parses HTML string to ProseMirror nodes - * @param htmlContent - HTML string to parse - * @returns ProseMirror Fragment - */ - private parseHtmlContent(htmlContent: string): any { - if (!this.editor || !this.editor.view) return null; - const view = this.editor.view; - const element = document.createElement('div'); - element.innerHTML = htmlContent; + // Text manipulation methods for Kendo Editor + /** + * Parses HTML string to ProseMirror nodes + * @param htmlContent - HTML string to parse + * @returns ProseMirror Fragment + */ + private parseHtmlContent(htmlContent: string): any { + if (!this.editor || !this.editor.view) return null; - // Use ProseMirror's DOMParser to parse HTML into nodes - const parser = ProseMirrorDOMParser.fromSchema(view.state.schema); - const slice = parser.parseSlice(element); - return slice.content; + const view = this.editor.view; + const element = document.createElement('div'); + element.innerHTML = htmlContent; + + // Use ProseMirror's DOMParser to parse HTML into nodes + const parser = ProseMirrorDOMParser.fromSchema(view.state.schema); + const slice = parser.parseSlice(element); + return slice.content; + } + + /** + * Inserts HTML content at the beginning of the editor + * @param content - HTML string to insert + * @param asHtml - If true, parses as HTML; if false, inserts as plain text + */ + public insertAtBeginning(content: string, asHtml: boolean = false): void { + if (!this.editor || !this.editor.view) return; + + const view = this.editor.view; + const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); + + if (contentNode) { + const transaction = view.state.tr.insert(0, contentNode); + view.dispatch(transaction); + this.onChange(this.editor.value); + } + } + + /** + * Inserts HTML content at the end of the editor + * @param content - HTML string to insert + * @param asHtml - If true, parses as HTML; if false, inserts as plain text + */ + public insertAtEnd(content: string, asHtml: boolean = false): void { + if (!this.editor || !this.editor.view) return; + + const view = this.editor.view; + const endPos = view.state.doc.content.size; + const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); + + if (contentNode) { + const transaction = view.state.tr.insert(endPos, contentNode); + view.dispatch(transaction); + this.onChange(this.editor.value); + } + } + + /** + * Inserts HTML content before the current selection + * @param content - HTML string to insert + * @param asHtml - If true, parses as HTML; if false, inserts as plain text + */ + public insertBeforeSelection(content: string, asHtml: boolean = false): void { + if (!this.editor || !this.editor.view) return; + + const view = this.editor.view; + const { from } = view.state.selection; + const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); + + if (contentNode) { + const transaction = view.state.tr.insert(from, contentNode); + view.dispatch(transaction); + this.onChange(this.editor.value); + } + } + + /** + * Inserts HTML content after the current selection + * @param content - HTML string to insert + * @param asHtml - If true, parses as HTML; if false, inserts as plain text + */ + public insertAfterSelection(content: string, asHtml: boolean = false): void { + if (!this.editor || !this.editor.view) return; + + const view = this.editor.view; + const { to } = view.state.selection; + const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); + + if (contentNode) { + const transaction = view.state.tr.insert(to, contentNode); + view.dispatch(transaction); + this.onChange(this.editor.value); + } + } + + /** + * Replaces the currently selected text with new content + * @param content - HTML string to replace selection with + * @param asHtml - If true, parses as HTML; if false, inserts as plain text + */ + public replaceSelectedText(content: string, asHtml: boolean = false): void { + if (!this.editor || !this.editor.view) return; + + const view = this.editor.view; + const { from, to } = view.state.selection; + const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); + + if (!contentNode) return; + + // If there's no selection, just insert at cursor position + if (from === to) { + const transaction = view.state.tr.insert(from, contentNode); + view.dispatch(transaction); + } else { + // Replace the selected content + const transaction = view.state.tr.replaceWith(from, to, contentNode); + view.dispatch(transaction); + } + this.onChange(this.editor.value); + } + + /** + * Gets the currently selected text in the editor + * @returns The selected text as a string + */ + public getSelectedText(): string { + if (!this.editor) return ''; + return this.editor.selectionText || ''; + } + public getSelectionTextOrWholeText(): string { + if (!this.editor) return ''; + + // If there's selected text, return it + if (this.editor.selectionText && this.editor.selectionText.trim().length > 0) { + return this.editor.selectionText; } - /** - * Inserts HTML content at the beginning of the editor - * @param content - HTML string to insert - * @param asHtml - If true, parses as HTML; if false, inserts as plain text - */ - public insertAtBeginning(content: string, asHtml: boolean = false): void { - if (!this.editor || !this.editor.view) return; - - const view = this.editor.view; - const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); - - if (contentNode) { - const transaction = view.state.tr.insert(0, contentNode); - view.dispatch(transaction); - this.onValueChanged(this.editor.value); - } + // Otherwise, get the whole text content from the editor's document + if (this.editor.view && this.editor.view.state) { + return this.editor.view.state.doc.textContent; } - /** - * Inserts HTML content at the end of the editor - * @param content - HTML string to insert - * @param asHtml - If true, parses as HTML; if false, inserts as plain text - */ - public insertAtEnd(content: string, asHtml: boolean = false): void { - if (!this.editor || !this.editor.view) return; + // Fallback: strip HTML tags and return plain text + const div = document.createElement('div'); + div.innerHTML = this.editor.value; + return div.textContent || div.innerText || ''; + } - const view = this.editor.view; - const endPos = view.state.doc.content.size; - const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); + /** + * Gets the current cursor position or selection range + * @returns Object with 'from' and 'to' positions + */ + public getSelectionRange(): { from: number; to: number } | null { + if (!this.editor || !this.editor.view) return null; - if (contentNode) { - const transaction = view.state.tr.insert(endPos, contentNode); - view.dispatch(transaction); - this.onValueChanged(this.editor.value); - } + const { from, to } = this.editor.view.state.selection; + return { from, to }; + } + + /** + * Inserts HTML content at a specific position + * @param content - HTML string to insert + * @param position - Position to insert at (0 = beginning) + * @param asHtml - If true, parses as HTML; if false, inserts as plain text + */ + public insertAtPosition(content: string, position: number, asHtml: boolean = false): void { + if (!this.editor || !this.editor.view) return; + + const view = this.editor.view; + const maxPos = view.state.doc.content.size; + const safePos = Math.min(Math.max(0, position), maxPos); + const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); + + if (contentNode) { + const transaction = view.state.tr.insert(safePos, contentNode); + view.dispatch(transaction); + this.onChange(this.editor.value); } + } - /** - * Inserts HTML content before the current selection - * @param content - HTML string to insert - * @param asHtml - If true, parses as HTML; if false, inserts as plain text - */ - public insertBeforeSelection(content: string, asHtml: boolean = false): void { - if (!this.editor || !this.editor.view) return; + /** + * Replaces text in a specific range + * @param content - HTML string to insert + * @param from - Start position + * @param to - End position + * @param asHtml - If true, parses as HTML; if false, inserts as plain text + */ + public replaceRange(content: string, from: number, to: number, asHtml: boolean = false): void { + if (!this.editor || !this.editor.view) return; - const view = this.editor.view; - const { from } = view.state.selection; - const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); + const view = this.editor.view; + const maxPos = view.state.doc.content.size; + const safeFrom = Math.min(Math.max(0, from), maxPos); + const safeTo = Math.min(Math.max(safeFrom, to), maxPos); + const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); - if (contentNode) { - const transaction = view.state.tr.insert(from, contentNode); - view.dispatch(transaction); - this.onValueChanged(this.editor.value); - } - } - - /** - * Inserts HTML content after the current selection - * @param content - HTML string to insert - * @param asHtml - If true, parses as HTML; if false, inserts as plain text - */ - public insertAfterSelection(content: string, asHtml: boolean = false): void { - if (!this.editor || !this.editor.view) return; - - const view = this.editor.view; - const { to } = view.state.selection; - const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); - - if (contentNode) { - const transaction = view.state.tr.insert(to, contentNode); - view.dispatch(transaction); - this.onValueChanged(this.editor.value); - } - } - - /** - * Replaces the currently selected text with new content - * @param content - HTML string to replace selection with - * @param asHtml - If true, parses as HTML; if false, inserts as plain text - */ - public replaceSelectedText(content: string, asHtml: boolean = false): void { - if (!this.editor || !this.editor.view) return; - - const view = this.editor.view; - const { from, to } = view.state.selection; - const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); - - if (!contentNode) return; - - // If there's no selection, just insert at cursor position - if (from === to) { - const transaction = view.state.tr.insert(from, contentNode); - view.dispatch(transaction); - } else { - // Replace the selected content - const transaction = view.state.tr.replaceWith(from, to, contentNode); - view.dispatch(transaction); - } - this.onValueChanged(this.editor.value); - } - - /** - * Gets the currently selected text in the editor - * @returns The selected text as a string - */ - public getSelectedText(): string { - if (!this.editor) return ''; - return this.editor.selectionText || ''; - } - public getSelectionTextOrWholeText(): string { - if (!this.editor) return ''; - - // If there's selected text, return it - if (this.editor.selectionText && this.editor.selectionText.trim().length > 0) { - return this.editor.selectionText; - } - - // Otherwise, get the whole text content from the editor's document - if (this.editor.view && this.editor.view.state) { - return this.editor.view.state.doc.textContent; - } - - // Fallback: strip HTML tags and return plain text - const div = document.createElement('div'); - div.innerHTML = this.editor.value; - return div.textContent || div.innerText || ''; - } - - /** - * Gets the current cursor position or selection range - * @returns Object with 'from' and 'to' positions - */ - public getSelectionRange(): { from: number; to: number } | null { - if (!this.editor || !this.editor.view) return null; - - const { from, to } = this.editor.view.state.selection; - return { from, to }; - } - - /** - * Inserts HTML content at a specific position - * @param content - HTML string to insert - * @param position - Position to insert at (0 = beginning) - * @param asHtml - If true, parses as HTML; if false, inserts as plain text - */ - public insertAtPosition(content: string, position: number, asHtml: boolean = false): void { - if (!this.editor || !this.editor.view) return; - - const view = this.editor.view; - const maxPos = view.state.doc.content.size; - const safePos = Math.min(Math.max(0, position), maxPos); - const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); - - if (contentNode) { - const transaction = view.state.tr.insert(safePos, contentNode); - view.dispatch(transaction); - this.onValueChanged(this.editor.value); - } - } - - /** - * Replaces text in a specific range - * @param content - HTML string to insert - * @param from - Start position - * @param to - End position - * @param asHtml - If true, parses as HTML; if false, inserts as plain text - */ - public replaceRange(content: string, from: number, to: number, asHtml: boolean = false): void { - if (!this.editor || !this.editor.view) return; - - const view = this.editor.view; - const maxPos = view.state.doc.content.size; - const safeFrom = Math.min(Math.max(0, from), maxPos); - const safeTo = Math.min(Math.max(safeFrom, to), maxPos); - const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content); - - if (contentNode) { - const transaction = view.state.tr.replaceWith(safeFrom, safeTo, contentNode); - view.dispatch(transaction); - this.onValueChanged(this.editor.value); - } + if (contentNode) { + const transaction = view.state.tr.replaceWith(safeFrom, safeTo, contentNode); + view.dispatch(transaction); + this.onChange(this.editor.value); } + } - showVariablePicker() { - this.easyEditorService.openTableMultiPicker(this.variables, this.variableTableSettings, "Please select a variable").pipe(first()).subscribe(result => { - if (result) { - result.forEach(c => { - this.insertAfterSelection(`<${RbjTagNode} class="rbj-tag" ${RbjTagIdAttribute}="${c.name}" ${RbjTagValueAttribute}="${c.name}">${c.name}`, true); - }); - } - }); - } + // showVariablePicker() { + // this.easyEditorService.openTableMultiPicker(this.variables, this.variableTableSettings, "Please select a variable").pipe(first()).subscribe(result => { + // if (result) { + // result.forEach(c => { + // this.insertAfterSelection(`<${RbjTagNode} class="rbj-tag" ${RbjTagIdAttribute}="${c.name}" ${RbjTagValueAttribute}="${c.name}">${c.name}`, true); + // }); + // } + // }); + // } - // Private methods - private createCustomSchema(): Schema { - let nodes = schema.spec.nodes.addBefore("div", "rbjTag", rbjTagNodeSpec); - let marks = schema.spec.marks; - //marks = marks.addToStart("rbjSpanTag", rbjTagMarkSpec); - return new Schema({ - nodes: nodes, - marks: marks - }); - } + // Private methods + private createCustomSchema(): Schema { + let nodes = schema.spec.nodes.addBefore("div", "rbjTag", rbjTagNodeSpec); + let marks = schema.spec.marks; + //marks = marks.addToStart("rbjSpanTag", rbjTagMarkSpec); + return new Schema({ + nodes: nodes, + marks: marks + }); + } } // Define the custom node specification for the rbj-tag element. export const rbjTagNodeSpec: NodeSpec = { - // Define the node attributes for the tag - attrs: { - "rbj-tag-id": { default: "" }, - "tag-value": { default: "" }, - "tag-preview": { default: "" } - }, - // Specify that this node should be treated as an inline element - inline: true, - // Allow the node to be part of inline content - group: "inline", - // Make it atomic (non-editable, treated as a single unit) - atom: true, + // Define the node attributes for the tag + attrs: { + "md2-icon": { default: "" }, + "class": { default: "" }, + // "tag-value": { default: "" }, + // "tag-preview": { default: "" } + }, + // Specify that this node should be treated as an inline element + inline: true, + // Allow the node to be part of inline content + group: "inline", + // Make it atomic (non-editable, treated as a single unit) + atom: true, - // Define how the node should be rendered in the DOM - toDOM: (node) => { - let tagPreview = node.attrs["tag-preview"]; - let displayValue = tagPreview == 'true' ? node.attrs["tag-value"] : node.attrs["rbj-tag-id"]; + // Define how the node should be rendered in the DOM + toDOM: (node) => { + let md2IconText = node.attrs["md2-icon"] as string; + let classValue = node.attrs["class"] as string; + if (classValue.includes('dice')) { + md2IconText = ''; + } + // let displayValue = tagPreview == 'true' ? node.attrs["tag-value"] : node.attrs["rbj-tag-id"]; - return [ - "span", - { - class: "rbj-tag", - "rbj-tag-id": node.attrs["rbj-tag-id"], - "tag-marker": node.attrs["tag-marker"], - "tag-value": node.attrs["tag-value"], - "tag-preview": node.attrs["tag-preview"], - //contenteditable: "false", - //spellcheck: "false", - style: "display: inline;" - }, - //node.attrs["tag-marker"] + node.attrs["tag-value"] // Display the tag content directly - displayValue - ]; - }, + return [ + "span", + { + class: classValue, + // "rbj-tag-id": node.attrs["rbj-tag-id"], + // "tag-marker": node.attrs["tag-marker"], + // "tag-value": node.attrs["tag-value"], + // "tag-preview": node.attrs["tag-preview"], + contenteditable: "false", + //spellcheck: "false", style="font-size: 36px;" + style: "display: inline;" + }, + //node.attrs["tag-marker"] + node.attrs["tag-value"] // Display the tag content directly + md2IconText + ]; + }, - // Define how to parse the node from existing DOM elements - parseDOM: [ - { - // Look for span elements with class rbj-tag (higher priority) - tag: "span[rbj-tag-id]", - priority: 51, // Higher priority to catch before other parsers - // Extract attributes from the DOM element - getAttrs: (dom) => { - const element = dom as HTMLElement; - // Must have rbj-tag-id attribute to be valid - if (!element.hasAttribute("rbj-tag-id")) { - return false; - } - return { - "rbj-tag-id": element.getAttribute("rbj-tag-id") || "", - "tag-preview": element.getAttribute("tag-preview") || "false", - "tag-value": element.getAttribute("tag-value") || element.getAttribute("rbj-tag-id") - }; - } - }, - { - // Look for div elements with rbj-tag-id attribute (for backward compatibility) - tag: "div[rbj-tag-id]", - // Extract attributes from the DOM element - getAttrs: (dom) => { - const element = dom as HTMLElement; - return { - "rbj-tag-id": element.getAttribute("rbj-tag-id") || "", - "tag-preview": element.getAttribute("tag-preview") || "false", - "tag-value": element.getAttribute("tag-value") || element.getAttribute("rbj-tag-id") - }; - } + // Define how to parse the node from existing DOM elements + parseDOM: [ + { + // Look for span elements with class rbj-tag (higher priority) + tag: "span[md2-icon]", + priority: 51, // Higher priority to catch before other parsers + // Extract attributes from the DOM element + getAttrs: (dom) => { + const element = dom as HTMLElement; + // Must have rbj-tag-id attribute to be valid + if (!element.hasAttribute("md2-icon")) { + return false; } - ] + return { + "md2-icon": element.getAttribute("md2-icon") || "", + "class": element.className || "", + // "tag-preview": element.getAttribute("tag-preview") || "false", + // "tag-value": element.getAttribute("tag-value") || element.getAttribute("rbj-tag-id") + }; + } + } + ] }; \ No newline at end of file diff --git a/src/app/games/massive-darkness2/md2-html-editor/md2-icon-picker-dlg.component.ts b/src/app/games/massive-darkness2/md2-html-editor/md2-icon-picker-dlg.component.ts index a0aefe5..c41703d 100644 --- a/src/app/games/massive-darkness2/md2-html-editor/md2-icon-picker-dlg.component.ts +++ b/src/app/games/massive-darkness2/md2-html-editor/md2-icon-picker-dlg.component.ts @@ -4,18 +4,18 @@ import { MD2Icon } from '../massive-darkness2.model'; import { MD2StateService } from '../../../services/MD2/md2-state.service'; @Component({ - selector: 'md2-icon-picker-dlg', - template: ` + selector: 'md2-icon-picker-dlg', + template: `
Insert MD2 Icon
-
+
@@ -25,13 +25,14 @@ import { MD2StateService } from '../../../services/MD2/md2-state.service'; `, - styles: [` - .icon-grid { + styles: [` + .md2-icon-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); gap: 10px; max-height: 400px; overflow-y: auto; + width:400px; } .icon-item { cursor: pointer; @@ -42,40 +43,41 @@ import { MD2StateService } from '../../../services/MD2/md2-state.service'; border: 1px solid #ddd; border-radius: 4px; transition: all 0.2s; + font-size: 30px; } .icon-item:hover { background-color: #f0f0f0; border-color: #007bff; - transform: scale(1.1); + //transform: scale(1.1); } `] }) export class MD2IconPickerDlgComponent implements OnInit { - iconList: Array<{ icon: MD2Icon, name: string, html: string }> = []; + iconList: Array<{ icon: MD2Icon, name: string, html: string }> = []; - constructor( - private dlgRef: NbDialogRef, - private md2StateService: MD2StateService - ) { } + constructor( + private dlgRef: NbDialogRef, + private md2StateService: MD2StateService + ) { } - ngOnInit(): void { - // Get all icons - for (let icon of Object.values(MD2Icon).filter(val => typeof val === 'number') as MD2Icon[]) { - this.iconList.push({ - icon: icon, - name: MD2Icon[icon], - html: this.md2StateService.iconHtml(icon) - }); - } + ngOnInit(): void { + // Get all icons + for (let icon of Object.values(MD2Icon).filter(val => typeof val === 'number') as MD2Icon[]) { + this.iconList.push({ + icon: icon, + name: MD2Icon[icon], + html: this.md2StateService.iconHtml(icon) + }); } + } - selectIcon(iconData: { icon: MD2Icon, name: string, html: string }) { - this.dlgRef.close(iconData.html); - } + selectIcon(iconData: { icon: MD2Icon, name: string, html: string }) { + this.dlgRef.close(iconData.html); + } - cancel() { - this.dlgRef.close(null); - } + cancel() { + this.dlgRef.close(null); + } } diff --git a/src/app/games/massive-darkness2/mobs/mobs.component.ts b/src/app/games/massive-darkness2/mobs/mobs.component.ts index e33c853..7190cd1 100644 --- a/src/app/games/massive-darkness2/mobs/mobs.component.ts +++ b/src/app/games/massive-darkness2/mobs/mobs.component.ts @@ -1,7 +1,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { NbDialogService } from '@nebular/theme'; -import { stringify } from 'querystring'; import { first } from 'rxjs/operators'; import { DropDownOption } from '../../../entity/dropDownOption'; import { FileService } from '../../../services/file.service'; @@ -10,7 +9,6 @@ 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 { ArrayUtils } from '../../../utilities/array-utils'; import { NumberUtils } from '../../../utilities/number-utils'; import { StringUtils } from '../../../utilities/string-utils'; import { CoreGameMobFactories } from '../factorys/mobs/CoreGame'; diff --git a/src/app/services/MD2/md2-state.service.ts b/src/app/services/MD2/md2-state.service.ts index 1f1c7a0..3ed0f90 100644 --- a/src/app/services/MD2/md2-state.service.ts +++ b/src/app/services/MD2/md2-state.service.ts @@ -31,9 +31,9 @@ export class MD2StateService { cssClass += ' g-color-aqua '; } if (icon < MD2Icon.RedDice) { - return `${String.fromCharCode(65 + icon)}` + return `${String.fromCharCode(65 + icon)}` } else { - return ``; + return ``; } } diff --git a/src/app/services/crudServices/crud.service.ts b/src/app/services/crudServices/crud.service.ts index ee372a5..b4f1592 100644 --- a/src/app/services/crudServices/crud.service.ts +++ b/src/app/services/crudServices/crud.service.ts @@ -177,166 +177,166 @@ export class CombinedKeyCrudService implements ICrudService { ); } -} -// Type definitions -type TextResponse = { message: string }; -/** -* Base CRUD service that targets the provided controller path. -* -* It mirrors the endpoints of CrudBaseApiController: -* GET /api/{controller} -* GET /api/{controller}/{id} -* POST /api/{controller} -> string -* POST /api/{controller}/batch -> string[] -* PUT /api/{controller} -* PUT /api/{controller}/batch -> number -* DELETE /api/{controller}/{id} -* DELETE /api/{controller}/batch -> text summary -* GET /api/{controller}/{id}/exists -> boolean -* GET /api/{controller}/count -> number -*/ -@Injectable({ providedIn: 'root' }) -export class CrudBaseApiService { - /** - * Example: baseUrl = 'https://your-api', controller = 'Customer' → - * endpoint = 'https://your-api/api/Customer' - */ - protected readonly endpoint: string; - - - /** - * @param http Angular HttpClient - * @param baseUrl API root without trailing slash (e.g., environment.apiBaseUrl) - * @param controllerName Controller name (e.g., 'Customer', 'Orders') - */ - constructor( - protected http: HttpClient, - protected apiConfig: ApiConfigService, - @Inject(String) private controllerName: string - ) { - this.endpoint = apiConfig.getApiUrl(this.controllerName); - } - - - /** Optional default headers (JSON). Override in subclasses if needed. */ - protected get jsonHeaders(): HttpHeaders { - return new HttpHeaders({ 'Content-Type': 'application/json' }); - } - - - /** Shared error handler that surfaces useful messages. */ - protected handleError(error: HttpErrorResponse): Observable { - let msg = 'Unknown error'; - if (error.error instanceof Blob) { - // In case backend returns text/plain; charset=utf-8 as Blob - return throwError(() => new Error('Server returned an error blob')); - } - if (typeof error.error === 'string') msg = error.error; - else if (error.error?.message) msg = error.error.message; - else if (error.message) msg = error.message; - return throwError(() => new Error(msg)); - } - /** Prepare the response for the given entity. Override in subclasses if needed. */ - protected prepareResponse(response: T): T { - // Do nothing by default - return response; - } - - /** GET /api/{controller} */ - getAll(): Observable { - return this.http - .get(this.endpoint) - .pipe( - map(response => { - - for (let i = 0; i < response.length; i++) { - const element = response[i]; - response[i] = this.prepareResponse(element); - } - return response; - }), - catchError(err => this.handleError(err))); - } - - - /** GET /api/{controller}/{id} */ - getById(id: string): Observable { - return this.http - .get(`${this.endpoint}/${id}`) - .pipe( - map(response => this.prepareResponse(response)), - catchError(err => this.handleError(err))); - } - - - - /** POST /api/{controller} -> string */ - create(entity: T): Observable { - return this.http - .post(this.endpoint, entity, { headers: this.jsonHeaders }) - .pipe(catchError(err => this.handleError(err))); - } - - - /** POST /api/{controller}/batch -> string[] */ - createRange(entities: T[]): Observable { - return this.http - .post(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders }) - .pipe(catchError(err => this.handleError(err))); - } - - - /** PUT /api/{controller} */ - update(entity: T): Observable { - return this.http - .put(this.endpoint, entity, { headers: this.jsonHeaders }) - .pipe(catchError(err => this.handleError(err))); - } - - - /** PUT /api/{controller}/batch -> number (updated count) */ - updateRange(entities: T[]): Observable { - return this.http - .put(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders }) - .pipe(catchError(err => this.handleError(err))); - } - - - /** DELETE /api/{controller}/{id} */ - delete(id: string): Observable { - return this.http - .delete(`${this.endpoint}/${id}`) - .pipe(catchError(err => this.handleError(err))); - } - - - /** DELETE /api/{controller}/batch -> text summary */ - deleteRange(ids: string[]): Observable { - // API returns a plain text message; map it into a TextResponse for convenience - return this.http - .delete(`${this.endpoint}/batch`, { - body: ids, - headers: this.jsonHeaders - }) - .pipe( - map((response: any) => ({ message: response || 'Batch delete completed' })), - catchError(err => this.handleError(err)) - ); - } - - - /** GET /api/{controller}/{id}/exists -> boolean */ - exists(id: string): Observable { - return this.http - .get(`${this.endpoint}/${id}/exists`) - .pipe(catchError(err => this.handleError(err))); - } - - - /** GET /api/{controller}/count -> number */ - count(): Observable { - return this.http - .get(`${this.endpoint}/count`) - .pipe(catchError(err => this.handleError(err))); - } } +// // Type definitions +// type TextResponse = { message: string }; +// /** +// * Base CRUD service that targets the provided controller path. +// * +// * It mirrors the endpoints of CrudBaseApiController: +// * GET /api/{controller} +// * GET /api/{controller}/{id} +// * POST /api/{controller} -> string +// * POST /api/{controller}/batch -> string[] +// * PUT /api/{controller} +// * PUT /api/{controller}/batch -> number +// * DELETE /api/{controller}/{id} +// * DELETE /api/{controller}/batch -> text summary +// * GET /api/{controller}/{id}/exists -> boolean +// * GET /api/{controller}/count -> number +// */ +// @Injectable({ providedIn: 'root' }) +// export class CrudBaseApiService { +// /** +// * Example: baseUrl = 'https://your-api', controller = 'Customer' → +// * endpoint = 'https://your-api/api/Customer' +// */ +// protected readonly endpoint: string; + + +// /** +// * @param http Angular HttpClient +// * @param baseUrl API root without trailing slash (e.g., environment.apiBaseUrl) +// * @param controllerName Controller name (e.g., 'Customer', 'Orders') +// */ +// constructor( +// protected http: HttpClient, +// protected apiConfig: ApiConfigService, +// @Inject(String) private controllerName: string +// ) { +// this.endpoint = apiConfig.getApiUrl(this.controllerName); +// } + + +// /** Optional default headers (JSON). Override in subclasses if needed. */ +// protected get jsonHeaders(): HttpHeaders { +// return new HttpHeaders({ 'Content-Type': 'application/json' }); +// } + + +// /** Shared error handler that surfaces useful messages. */ +// protected handleError(error: HttpErrorResponse): Observable { +// let msg = 'Unknown error'; +// if (error.error instanceof Blob) { +// // In case backend returns text/plain; charset=utf-8 as Blob +// return throwError(() => new Error('Server returned an error blob')); +// } +// if (typeof error.error === 'string') msg = error.error; +// else if (error.error?.message) msg = error.error.message; +// else if (error.message) msg = error.message; +// return throwError(() => new Error(msg)); +// } +// /** Prepare the response for the given entity. Override in subclasses if needed. */ +// protected prepareResponse(response: T): T { +// // Do nothing by default +// return response; +// } + +// /** GET /api/{controller} */ +// getAll(): Observable { +// return this.http +// .get(this.endpoint) +// .pipe( +// map(response => { + +// for (let i = 0; i < response.length; i++) { +// const element = response[i]; +// response[i] = this.prepareResponse(element); +// } +// return response; +// }), +// catchError(err => this.handleError(err))); +// } + + +// /** GET /api/{controller}/{id} */ +// getById(id: string): Observable { +// return this.http +// .get(`${this.endpoint}/${id}`) +// .pipe( +// map(response => this.prepareResponse(response)), +// catchError(err => this.handleError(err))); +// } + + + +// /** POST /api/{controller} -> string */ +// create(entity: T): Observable { +// return this.http +// .post(this.endpoint, entity, { headers: this.jsonHeaders }) +// .pipe(catchError(err => this.handleError(err))); +// } + + +// /** POST /api/{controller}/batch -> string[] */ +// createRange(entities: T[]): Observable { +// return this.http +// .post(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders }) +// .pipe(catchError(err => this.handleError(err))); +// } + + +// /** PUT /api/{controller} */ +// update(entity: T): Observable { +// return this.http +// .put(this.endpoint, entity, { headers: this.jsonHeaders }) +// .pipe(catchError(err => this.handleError(err))); +// } + + +// /** PUT /api/{controller}/batch -> number (updated count) */ +// updateRange(entities: T[]): Observable { +// return this.http +// .put(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders }) +// .pipe(catchError(err => this.handleError(err))); +// } + + +// /** DELETE /api/{controller}/{id} */ +// delete(id: string): Observable { +// return this.http +// .delete(`${this.endpoint}/${id}`) +// .pipe(catchError(err => this.handleError(err))); +// } + + +// /** DELETE /api/{controller}/batch -> text summary */ +// deleteRange(ids: string[]): Observable { +// // API returns a plain text message; map it into a TextResponse for convenience +// return this.http +// .delete(`${this.endpoint}/batch`, { +// body: ids, +// headers: this.jsonHeaders +// }) +// .pipe( +// map((response: any) => ({ message: response || 'Batch delete completed' })), +// catchError(err => this.handleError(err)) +// ); +// } + + +// /** GET /api/{controller}/{id}/exists -> boolean */ +// exists(id: string): Observable { +// return this.http +// .get(`${this.endpoint}/${id}/exists`) +// .pipe(catchError(err => this.handleError(err))); +// } + + +// /** GET /api/{controller}/count -> number */ +// count(): Observable { +// return this.http +// .get(`${this.endpoint}/count`) +// .pipe(catchError(err => this.handleError(err))); +// } +// } diff --git a/src/app/utilities/string-utils.ts b/src/app/utilities/string-utils.ts index e46bba8..ead02be 100644 --- a/src/app/utilities/string-utils.ts +++ b/src/app/utilities/string-utils.ts @@ -1,7 +1,6 @@ //import { FtTagType } from "../components/fancy-table/fancy-row-column.model"; //import { AddressInfo } from "../models/contactInfo.model"; -import { stringify } from "querystring"; import { AddressInfo } from "../entity/PastoralDomain"; export class StringUtils { diff --git a/src/assets/styles/md2.scss b/src/assets/styles/md2.scss index 818aeae..2d1f32e 100644 --- a/src/assets/styles/md2.scss +++ b/src/assets/styles/md2.scss @@ -41,6 +41,7 @@ } .MD2Icon { font-family: "Massive Darkness 2", sans-serif !important; + //font-size: 20px; //font-size: 50px; &.attack::before { diff --git a/src/index.html b/src/index.html index 4059b9a..a48fc8f 100644 --- a/src/index.html +++ b/src/index.html @@ -31,6 +31,9 @@ + + + diff --git a/src/main.ts b/src/main.ts index 8fd0e9d..ebfedc1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,5 @@ +/// + /** * @license * Copyright Akveo. All Rights Reserved. diff --git a/src/polyfills.ts b/src/polyfills.ts index 9ca8e90..8aeb789 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -40,7 +40,7 @@ import 'zone.js'; // Included with Angular CLI. */ import 'core-js/es7/array'; import 'core-js/es7/object'; - +import '@angular/localize/init'; if (typeof SVGElement.prototype.contains === 'undefined') { SVGElement.prototype.contains = HTMLDivElement.prototype.contains; } diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json index ba21981..ab90245 100644 --- a/src/tsconfig.app.json +++ b/src/tsconfig.app.json @@ -2,7 +2,10 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", - "baseUrl": "./" + "baseUrl": "./", + "types": [ + "@angular/localize" + ] }, "files": [ "main.ts", diff --git a/src/tsconfig.spec.json b/src/tsconfig.spec.json index c89454b..aff19d9 100644 --- a/src/tsconfig.spec.json +++ b/src/tsconfig.spec.json @@ -5,7 +5,8 @@ "baseUrl": "./", "types": [ "jasmine", - "node" + "node", + "@angular/localize" ] }, "files": [