diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a6889bb..811909da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,8 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
-## [1.4.0] 2024-02-23
+### Added
+- Added UI for Entity Grid (hotkey G) with additional settings [#292](https://github.com/CCDirectLink/crosscode-map-editor/issues/292)
+## [1.4.0] 2024-02-23
### Added
- Added proper support for the event steps `SHOW_MODAL_CHOICE` and `SET_MSG_EXPRESSION`.
- Added rendering of text colors in relevant events (such as `SHOW_MSG`).
diff --git a/webapp/package.json b/webapp/package.json
index 3a0903f1..c1198254 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -71,8 +71,8 @@
"@angular/platform-browser": "^17.1.2",
"@angular/platform-browser-dynamic": "^17.1.2",
"@angular/router": "^17.1.2",
- "@babylonjs/core": "^5.50.1",
- "@babylonjs/gui": "^5.50.1",
+ "@babylonjs/core": "5.50.1",
+ "@babylonjs/gui": "5.50.1",
"@types/earcut": "^2.1.1",
"@types/jasmine": "~4.3.0",
"@types/jsoneditor": "^9.9.0",
diff --git a/webapp/src/app/app.module.ts b/webapp/src/app/app.module.ts
index 6dd38bad..05d624c8 100644
--- a/webapp/src/app/app.module.ts
+++ b/webapp/src/app/app.module.ts
@@ -68,6 +68,8 @@ import { ResizedDirective } from './directives/resized.directive';
import { MaterialModule } from './external-modules/material.module';
import { CombinedTooltipPipe } from './pipes/combined-tooltip.pipe';
import { KeepHtmlPipe } from './pipes/keep-html.pipe';
+import { ToolbarDividerComponent } from './components/toolbar/toolbar-divider/toolbar-divider.component';
+import { GridMenuComponent } from './components/toolbar/grid-menu/grid-menu.component';
const WIDGETS = [
StringWidgetComponent,
@@ -147,6 +149,8 @@ const WIDGETS = [
AutofocusDirective,
CombinedTooltipPipe,
InputWithButtonComponent,
+ ToolbarDividerComponent,
+ GridMenuComponent,
],
bootstrap: [AppComponent],
})
diff --git a/webapp/src/app/components/dialogs/map-settings/map-settings.component.ts b/webapp/src/app/components/dialogs/map-settings/map-settings.component.ts
index e13749c2..1e669b64 100644
--- a/webapp/src/app/components/dialogs/map-settings/map-settings.component.ts
+++ b/webapp/src/app/components/dialogs/map-settings/map-settings.component.ts
@@ -4,6 +4,7 @@ import { CrossCodeMap } from '../../../models/cross-code-map';
import { MapLoaderService } from '../../../services/map-loader.service';
import { CCMap } from '../../../services/phaser/tilemap/cc-map';
import { OverlayRefControl } from '../overlay/overlay-ref-control';
+import { GlobalEventsService } from '../../../services/global-events.service';
@Component({
selector: 'app-map-settings',
@@ -20,7 +21,8 @@ export class MapSettingsComponent {
constructor(
loader: MapLoaderService,
- public ref: OverlayRefControl
+ public ref: OverlayRefControl,
+ private events: GlobalEventsService
) {
const tileMap = loader.tileMap.getValue();
@@ -56,7 +58,10 @@ export class MapSettingsComponent {
tileMap.masterLevel = settings.masterLevel;
tileMap.attributes = settings.attributes;
- tileMap.resize(settings.mapWidth, settings.mapHeight);
+ this.events.resizeMap.next({
+ x: settings.mapWidth,
+ y: settings.mapHeight
+ });
this.ref.close();
}
diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html
new file mode 100644
index 00000000..c27e28db
--- /dev/null
+++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.html
@@ -0,0 +1,61 @@
+
+
+
+ Entity Grid
+
+
+
+
+
+
+
diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss
new file mode 100644
index 00000000..76474a64
--- /dev/null
+++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.scss
@@ -0,0 +1,15 @@
+:host {
+ display: flex;
+ align-items: center;
+ height: 100%;
+}
+
+.point-form-field {
+ width: 170px;
+}
+
+.extended-menu {
+ overflow-x: hidden;
+ min-width: 0;
+ height: 100%;
+}
diff --git a/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts
new file mode 100644
index 00000000..17243a26
--- /dev/null
+++ b/webapp/src/app/components/toolbar/grid-menu/grid-menu.component.ts
@@ -0,0 +1,96 @@
+import { Component, effect, OnInit } from '@angular/core';
+import { MatCheckbox } from '@angular/material/checkbox';
+import { MatIconButton } from '@angular/material/button';
+import { MatIcon } from '@angular/material/icon';
+import { FormsModule } from '@angular/forms';
+import { Globals } from '../../../services/globals';
+import { NgIf } from '@angular/common';
+import { MatFormField, MatLabel } from '@angular/material/form-field';
+import { MatInput } from '@angular/material/input';
+import { PointInputComponent } from '../vec-input/point-input.component';
+import { Helper } from '../../../services/phaser/helper';
+import { Point } from '../../../models/cross-code-map';
+import { animate, state, style, transition, trigger } from '@angular/animations';
+import { GlobalEventsService } from '../../../services/global-events.service';
+
+export interface GridSettings {
+ size: Point;
+ offset: Point;
+ color: string;
+ enableGrid: boolean;
+ showSettings?: boolean;
+ visible?: boolean;
+}
+
+const gridSettingsKey = 'gridSettingsKey';
+
+@Component({
+ selector: 'app-grid-menu',
+ standalone: true,
+ animations: [
+ trigger('openClose', [
+ state('void', style({
+ width: '0',
+ margin: '0'
+ })),
+ transition('* <=> *', [
+ animate('100ms ease'),
+ ]),
+ ])
+ ],
+ imports: [
+ MatCheckbox,
+ MatIconButton,
+ MatIcon,
+ FormsModule,
+ NgIf,
+ MatFormField,
+ MatInput,
+ MatLabel,
+ PointInputComponent
+ ],
+ templateUrl: './grid-menu.component.html',
+ styleUrl: './grid-menu.component.scss'
+})
+export class GridMenuComponent implements OnInit {
+
+ gridSettings = Globals.gridSettings;
+
+ constructor(
+ events: GlobalEventsService
+ ) {
+ effect(() => {
+ const settings = this.gridSettings();
+ localStorage.setItem(gridSettingsKey, JSON.stringify(settings));
+ events.gridSettings.next(settings);
+ });
+ }
+
+ ngOnInit() {
+ try {
+ const settings = JSON.parse(localStorage.getItem(gridSettingsKey)!) as Partial;
+ Globals.gridSettings.update(old => ({
+ ...old,
+ ...settings
+ }));
+ } catch (e) {
+ }
+ }
+
+ update(newSettings: Partial) {
+ Globals.gridSettings.update(old => {
+ const cpy = Helper.copy(old);
+ Object.assign(cpy, newSettings);
+ return cpy;
+ });
+ }
+
+ protected readonly MatCheckbox = MatCheckbox;
+
+ toggleSettings() {
+ Globals.gridSettings.update(old => ({
+ ...old,
+ showSettings: !old.showSettings
+ }));
+ }
+}
diff --git a/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss b/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss
new file mode 100644
index 00000000..d56c938f
--- /dev/null
+++ b/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.scss
@@ -0,0 +1,6 @@
+:host {
+ width: 1px;
+ height: 100%;
+ background: rgba(255, 255, 255, 0.24);
+ margin: 0 28px;
+}
diff --git a/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.ts b/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.ts
new file mode 100644
index 00000000..ec4395cb
--- /dev/null
+++ b/webapp/src/app/components/toolbar/toolbar-divider/toolbar-divider.component.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-toolbar-divider',
+ standalone: true,
+ imports: [],
+ template: '',
+ styleUrl: './toolbar-divider.component.scss'
+})
+export class ToolbarDividerComponent {
+}
diff --git a/webapp/src/app/components/toolbar/toolbar.component.html b/webapp/src/app/components/toolbar/toolbar.component.html
index 9baa59cf..5cc37ea2 100644
--- a/webapp/src/app/components/toolbar/toolbar.component.html
+++ b/webapp/src/app/components/toolbar/toolbar.component.html
@@ -1,7 +1,7 @@
-
-
+
+
@@ -54,14 +54,19 @@
-
-
{{map?.name}}
+
+
+
+
+
+ {{ map?.name }}
-
+
+
- {{error}}
+ {{ error }}
diff --git a/webapp/src/app/components/toolbar/vec-input/point-input.component.html b/webapp/src/app/components/toolbar/vec-input/point-input.component.html
new file mode 100644
index 00000000..34bb244d
--- /dev/null
+++ b/webapp/src/app/components/toolbar/vec-input/point-input.component.html
@@ -0,0 +1,19 @@
+
+ X:
+
+ Y:
+
+
diff --git a/webapp/src/app/components/toolbar/vec-input/point-input.component.scss b/webapp/src/app/components/toolbar/vec-input/point-input.component.scss
new file mode 100644
index 00000000..e27c82fb
--- /dev/null
+++ b/webapp/src/app/components/toolbar/vec-input/point-input.component.scss
@@ -0,0 +1,21 @@
+input {
+ border: none;
+ background: none;
+ padding: 0 0 0 4px;
+ outline: none;
+ font: inherit;
+ color: currentcolor;
+ min-width: 0;
+ flex: 1;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 1px;
+}
+
+.input-label {
+ opacity: 0;
+ transition: opacity 200ms;
+}
+
+:host.floating .input-label {
+ opacity: 1;
+}
diff --git a/webapp/src/app/components/toolbar/vec-input/point-input.component.ts b/webapp/src/app/components/toolbar/vec-input/point-input.component.ts
new file mode 100644
index 00000000..f9349c65
--- /dev/null
+++ b/webapp/src/app/components/toolbar/vec-input/point-input.component.ts
@@ -0,0 +1,147 @@
+import { Component, ElementRef, HostBinding, Input, OnDestroy, Optional, Self } from '@angular/core';
+import { ControlValueAccessor, FormBuilder, FormGroup, FormsModule, NgControl, ReactiveFormsModule } from '@angular/forms';
+import { Point } from '../../../models/cross-code-map';
+import { MatFormFieldControl } from '@angular/material/form-field';
+import { Subject } from 'rxjs';
+import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
+
+@Component({
+ selector: 'app-point-input',
+ standalone: true,
+ imports: [
+ FormsModule,
+ ReactiveFormsModule
+ ],
+ providers: [{provide: MatFormFieldControl, useExisting: PointInputComponent}],
+ templateUrl: './point-input.component.html',
+ styleUrl: './point-input.component.scss'
+})
+export class PointInputComponent implements MatFormFieldControl
, OnDestroy, ControlValueAccessor {
+ parts: FormGroup;
+ stateChanges = new Subject();
+
+ @Input()
+ min = 1;
+
+ onChange = (_: any) => {
+ };
+
+ @Input()
+ get value(): Point | null {
+ return this.parts.value;
+ }
+
+ set value(point: Point | null) {
+ point = point || {x: 1, y: 1};
+ this.parts.setValue({...point});
+ this.stateChanges.next();
+ }
+
+ static nextId = 0;
+ @HostBinding() id = `point-input-${PointInputComponent.nextId++}`;
+
+ readonly placeholder = '';
+
+ focused = false;
+ touched = false;
+ onTouched = () => {
+ };
+
+ onFocusIn(event: FocusEvent) {
+ if (!this.focused) {
+ this.focused = true;
+ this.stateChanges.next();
+ }
+ }
+
+ onFocusOut(event: FocusEvent) {
+ if (!this.elementRef.nativeElement.contains(event.relatedTarget as Element)) {
+ this.touched = true;
+ this.focused = false;
+ this.onTouched();
+ this.stateChanges.next();
+ }
+ }
+
+ get empty() {
+ const p = this.parts.value as Point;
+ return isNaN(p.x) && isNaN(p.y);
+ }
+
+ @HostBinding('class.floating')
+ get shouldLabelFloat() {
+ return this.focused || !this.empty;
+ }
+
+ required = false;
+
+ private _disabled = false;
+
+ @Input()
+ get disabled(): boolean {
+ return this._disabled;
+ }
+
+ set disabled(value: BooleanInput) {
+ this._disabled = coerceBooleanProperty(value);
+ if (this._disabled) {
+ this.parts.disable();
+ } else {
+ this.parts.enable();
+ }
+ this.stateChanges.next();
+ }
+
+ get errorState(): boolean {
+ return this.parts.invalid && this.touched;
+ }
+
+ controlType = 'point-input';
+
+ setDescribedByIds(ids: string[]): void {
+ }
+
+ onContainerClick(event: MouseEvent): void {
+ if ((event.target as Element).tagName.toLowerCase() !== 'input') {
+ this.elementRef.nativeElement.querySelector('input')?.focus();
+ }
+ }
+
+ constructor(
+ fb: FormBuilder,
+ private elementRef: ElementRef,
+ @Optional() @Self() public ngControl: NgControl,
+ ) {
+ this.parts = fb.group({
+ x: 0,
+ y: 0
+ });
+ if (this.ngControl != null) {
+ this.ngControl.valueAccessor = this;
+ }
+ }
+
+ writeValue(p: Point | null): void {
+ this.value = p;
+ }
+
+ registerOnChange(fn: any): void {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: any): void {
+ this.onTouched = fn;
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this.disabled = isDisabled;
+ }
+
+ ngOnDestroy() {
+ this.stateChanges.complete();
+ }
+
+ update() {
+ this.onChange(this.value);
+ }
+}
diff --git a/webapp/src/app/services/global-events.service.ts b/webapp/src/app/services/global-events.service.ts
index edc62133..4c9814ac 100644
--- a/webapp/src/app/services/global-events.service.ts
+++ b/webapp/src/app/services/global-events.service.ts
@@ -3,6 +3,8 @@ import { BehaviorSubject, Subject } from 'rxjs';
import { MapEntity, Point, Point3 } from '../models/cross-code-map';
import { EditorView } from '../models/editor-view';
import { CCEntity } from './phaser/entities/cc-entity';
+import { GridSettings } from '../components/toolbar/grid-menu/grid-menu.component';
+import { Globals } from './globals';
@Injectable({
providedIn: 'root'
@@ -16,17 +18,19 @@ export class GlobalEventsService {
loadComplete = new Subject();
generateHeights = new Subject();
offsetMap = new Subject();
+ resizeMap = new Subject();
offsetEntities = new Subject();
toggleVisibility = new Subject();
showAddEntityMenu = new Subject();
-
+
updateCoords = new Subject();
updateTileSelectionSize = new Subject();
showIngamePreview = new BehaviorSubject(false);
hasUnsavedChanges = new BehaviorSubject(false);
-
+ gridSettings = new BehaviorSubject(Globals.gridSettings());
+
babylonLoading = new BehaviorSubject(false);
is3D = new BehaviorSubject(false);
-
+
constructor() {}
}
diff --git a/webapp/src/app/services/globals.ts b/webapp/src/app/services/globals.ts
index c0a20497..73393a92 100644
--- a/webapp/src/app/services/globals.ts
+++ b/webapp/src/app/services/globals.ts
@@ -8,6 +8,8 @@ import { PhaserEventsService } from './phaser/phaser-events.service';
import { CCMap } from './phaser/tilemap/cc-map';
import { MatSnackBar } from '@angular/material/snack-bar';
import { SettingsService } from './settings.service';
+import { signal } from '@angular/core';
+import { GridSettings } from '../components/toolbar/grid-menu/grid-menu.component';
export class Globals {
static isElectron = false;
@@ -17,10 +19,12 @@ export class Globals {
static map: CCMap;
static TILE_SIZE = 16;
static URL = 'http://localhost:8080/';
- static entitySettings = {
- gridSize: 8,
+ static gridSettings = signal({
+ size: {x: 8, y: 8},
+ offset: {x: 0, y: 0},
+ color: '#222222',
enableGrid: false
- };
+ });
static disablePhaserInput = new Set();
// TODO: remove them from global state
diff --git a/webapp/src/app/services/phaser/entities/cc-entity.ts b/webapp/src/app/services/phaser/entities/cc-entity.ts
index 6b268ff2..65d2f187 100644
--- a/webapp/src/app/services/phaser/entities/cc-entity.ts
+++ b/webapp/src/app/services/phaser/entities/cc-entity.ts
@@ -193,20 +193,20 @@ export abstract class CCEntity extends BaseObject {
container.x = Math.round(p.worldX - this.startOffset.x);
container.y = Math.round(p.worldY - this.startOffset.y);
- const settings = Globals.entitySettings;
+ const settings = Globals.gridSettings();
if (settings.enableGrid) {
- const diffX = container.x % settings.gridSize;
- if (diffX * 2 < settings.gridSize) {
+ const diffX = (container.x - settings.offset.x) % settings.size.x;
+ if (diffX * 2 < settings.size.x) {
container.x -= diffX;
} else {
- container.x += settings.gridSize - diffX;
+ container.x += settings.size.x - diffX;
}
- const diffY = container.y % settings.gridSize;
- if (diffY * 2 < settings.gridSize) {
+ const diffY = (container.y - settings.offset.y) % settings.size.y;
+ if (diffY * 2 < settings.size.y) {
container.y -= diffY;
} else {
- container.y += settings.gridSize - diffY;
+ container.y += settings.size.y - diffY;
}
}
this.updateZIndex();
diff --git a/webapp/src/app/services/phaser/entities/entity-manager.ts b/webapp/src/app/services/phaser/entities/entity-manager.ts
index 3d0d4af1..9b578a56 100644
--- a/webapp/src/app/services/phaser/entities/entity-manager.ts
+++ b/webapp/src/app/services/phaser/entities/entity-manager.ts
@@ -246,7 +246,10 @@ export class EntityManager extends BaseObject {
if (Helper.isInputFocused()) {
return;
}
- Globals.entitySettings.enableGrid = !Globals.entitySettings.enableGrid;
+ Globals.gridSettings.update(settings => ({
+ ...settings,
+ enableGrid: !settings.enableGrid
+ }));
}
});
diff --git a/webapp/src/app/services/phaser/entity-grid.ts b/webapp/src/app/services/phaser/entity-grid.ts
new file mode 100644
index 00000000..0aaf8907
--- /dev/null
+++ b/webapp/src/app/services/phaser/entity-grid.ts
@@ -0,0 +1,72 @@
+import { Scene } from 'phaser';
+import { Globals } from '../globals';
+import { debounceTime, merge, Subscription } from 'rxjs';
+import { Point } from '../../models/cross-code-map';
+
+export class EntityGrid extends Phaser.GameObjects.GameObject {
+ private sub: Subscription;
+ private grid?: Phaser.GameObjects.Grid;
+
+ constructor(scene: Scene) {
+ super(scene, 'EntityGrid');
+
+ this.sub = merge(
+ Globals.globalEventsService.gridSettings,
+ Globals.mapLoaderService.map,
+ Globals.globalEventsService.resizeMap
+ ).pipe(debounceTime(1)).subscribe(() => this.updateGrid());
+ }
+
+ updateGrid() {
+ const settings = Globals.gridSettings();
+ const scale = 4;
+ this.grid?.destroy();
+ if (!settings.enableGrid || !settings.visible) {
+ return;
+ }
+ const pos: Point = {
+ x: settings.offset.x % settings.size.x,
+ y: settings.offset.y % settings.size.y
+ };
+
+ if (pos.x > 0) {
+ pos.x -= settings.size.x;
+ }
+ if (pos.y > 0) {
+ pos.y -= settings.size.y;
+ }
+
+ const width = Globals.map.mapWidth * Globals.TILE_SIZE - pos.x;
+ const height = Globals.map.mapHeight * Globals.TILE_SIZE - pos.y;
+
+ let color = parseInt(settings.color.substring(1), 16);
+ if (isNaN(color)) {
+ color = 0x222222;
+ }
+
+ this.grid = new Phaser.GameObjects.Grid(
+ this.scene,
+ pos.x,
+ pos.y,
+ width * scale,
+ height * scale,
+ settings.size.x * scale,
+ settings.size.y * scale,
+ undefined,
+ 0,
+ color,
+ 0.6
+ );
+ this.grid.depth = 500;
+ this.grid.setOrigin(0, 0);
+ this.grid.setScale(1 / scale, 1 / scale);
+
+ this.scene.add.existing(this.grid);
+ }
+
+ override destroy(fromScene?: boolean) {
+ super.destroy(fromScene);
+ this.grid?.destroy();
+ this.sub.unsubscribe();
+ }
+}
diff --git a/webapp/src/app/services/phaser/main-scene.ts b/webapp/src/app/services/phaser/main-scene.ts
index b853df94..106db6ca 100644
--- a/webapp/src/app/services/phaser/main-scene.ts
+++ b/webapp/src/app/services/phaser/main-scene.ts
@@ -9,6 +9,7 @@ import { CCMap } from './tilemap/cc-map';
import { TileDrawer } from './tilemap/tile-drawer';
import { LayerParallax } from './layer-parallax';
import { IngamePreview } from './ingame-preview';
+import { EntityGrid } from './entity-grid';
export class MainScene extends Phaser.Scene {
@@ -74,20 +75,23 @@ export class MainScene extends Phaser.Scene {
const preview = new IngamePreview(this);
this.add.existing(preview);
this.add.existing(new LayerParallax(this, preview));
-
+
const coordsReporter = new CoordsReporter(this);
this.add.existing(coordsReporter);
+ const grid = new EntityGrid(this);
+ this.add.existing(grid);
+
Globals.globalEventsService.currentView.subscribe(view => {
tileDrawer.setActive(false);
entityManager.setActive(false);
switch (view) {
- case EditorView.Layers:
- tileDrawer.setActive(true);
- break;
- case EditorView.Entities:
- entityManager.setActive(true);
- break;
+ case EditorView.Layers:
+ tileDrawer.setActive(true);
+ break;
+ case EditorView.Entities:
+ entityManager.setActive(true);
+ break;
}
});
diff --git a/webapp/src/app/services/phaser/tilemap/cc-map.ts b/webapp/src/app/services/phaser/tilemap/cc-map.ts
index 026d9ef4..c6b3bbfe 100644
--- a/webapp/src/app/services/phaser/tilemap/cc-map.ts
+++ b/webapp/src/app/services/phaser/tilemap/cc-map.ts
@@ -26,9 +26,7 @@ export class CCMap {
private lastMapId = 1;
private tileMap?: Phaser.Tilemaps.Tilemap;
- private historySub: Subscription;
- private offsetSub: Subscription;
- private offsetSub2: Subscription;
+ private subs: Subscription[] = [];
filename = '';
path?: string;
@@ -41,7 +39,7 @@ export class CCMap {
public entityManager: EntityManager
) {
const stateHistory = Globals.stateHistoryService;
- this.historySub = stateHistory.selectedState.subscribe(async container => {
+ this.subs.push(stateHistory.selectedState.subscribe(async container => {
if (!container || !container.state) {
return;
}
@@ -70,16 +68,18 @@ export class CCMap {
selectedLayer.next(layer);
}
}
- });
+ }));
- this.offsetSub = Globals.globalEventsService.offsetMap.subscribe(offset => this.offsetMap(offset));
- this.offsetSub2 = Globals.globalEventsService.offsetEntities.subscribe(offset => this.offsetEntities(offset));
+ this.subs.push(Globals.globalEventsService.offsetMap.subscribe(offset => this.offsetMap(offset)));
+ this.subs.push(Globals.globalEventsService.offsetEntities.subscribe(offset => this.offsetEntities(offset)));
+ this.subs.push(Globals.globalEventsService.resizeMap.subscribe(size => this.resize(size.x, size.y)));
}
destroy() {
- this.historySub.unsubscribe();
- this.offsetSub.unsubscribe();
- this.offsetSub2.unsubscribe();
+ for (const sub of this.subs) {
+ sub.unsubscribe();
+ }
+ this.subs = [];
}
async loadMap(map: CrossCodeMap, skipInit = false) {
@@ -143,7 +143,7 @@ export class CCMap {
Globals.mapLoaderService.selectedLayer.next(this.layers[0]);
}
- resize(width: number, height: number) {
+ private resize(width: number, height: number) {
this.mapWidth = width;
this.mapHeight = height;
diff --git a/webapp/src/styles.scss b/webapp/src/styles.scss
index 368d5a27..94b9a875 100644
--- a/webapp/src/styles.scss
+++ b/webapp/src/styles.scss
@@ -16,6 +16,14 @@ $config: mat.define-typography-config();
$my-typography: mat.define-typography-config();
@include mat.typography-hierarchy($my-typography);
+
+// tailwind base makes angular material icon buttons off-center. This fixes it
+@layer base {
+ button {
+ line-height: normal;
+ }
+}
+
html * {
font-family: Roboto, "Helvetica Neue", sans-serif;
}