From 75ac5b92ed518a8852d8d53c41e3351efafc4cf4 Mon Sep 17 00:00:00 2001 From: cramerL Date: Fri, 3 Apr 2026 03:54:53 +0200 Subject: [PATCH 1/6] added "vanilla maps" checkbox directly to map loading instead of settings page to allow toggle between mod/vanilla --- backend/src/server.ts | 2 +- common/src/controllers/api.ts | 7 +- .../dialogs/load-map/load-map.component.html | 25 +++++-- .../dialogs/load-map/load-map.component.ts | 35 ++++++--- .../dialogs/settings/settings.component.html | 11 +-- .../dialogs/settings/settings.component.ts | 72 ++++++++----------- .../src/app/services/http-client.service.ts | 39 +++++----- webapp/src/app/services/settings.service.ts | 18 ++++- 8 files changed, 111 insertions(+), 98 deletions(-) diff --git a/backend/src/server.ts b/backend/src/server.ts index ff871986..1c2e3247 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -24,7 +24,7 @@ app.use(bodyParser.urlencoded({ extended: true })); */ app.get('/api/allFiles', async (_, res) => res.json(await api.getAllFiles(config.pathToCrosscode))); app.get('/api/allTilesets', async (_, res) => res.json(await api.getAllTilesets(config.pathToCrosscode))); -app.get('/api/allMaps', async (req, res) => res.json(await api.getAllMaps(config.pathToCrosscode, req.query['includeVanillaMaps'] == 'true'))); +app.get('/api/allMaps', async (req, res) => res.json(await api.getAllMaps(config.pathToCrosscode, req.query['vanillaMaps'] == 'true'))); app.get('/api/allFilesInFolder', async (req, res) => res.json(await api.getAllFilesInFolder(config.pathToCrosscode, req.query['folder'] as string, req.query['extension'] as string))); app.get('/api/allMods', async (_, res) => res.json(await api.getAllMods(config.pathToCrosscode))); app.get('/api/allModMapEditorConfigs', async (_, res) => res.json(await api.getAllModMapEditorConfigs(config.pathToCrosscode))); diff --git a/common/src/controllers/api.ts b/common/src/controllers/api.ts index 969ce831..0d743efb 100644 --- a/common/src/controllers/api.ts +++ b/common/src/controllers/api.ts @@ -218,14 +218,13 @@ export async function getAllTilesets(dir: string) { return result.sort(); } -export async function getAllMaps(dir: string, includeVanillaMaps: boolean) { +export async function getAllMaps(dir: string, vanillaMaps: boolean) { const path = await pathPromise; const paths: string[] = []; - if (mods.length === 0 || includeVanillaMaps) { + if (mods.length === 0 || vanillaMaps) { await listAllFiles(path.resolve(dir, 'data/maps/'), paths, 'json', path.resolve(dir)); - } - if (mods.length > 0) { + } else { const modDir = path.join(dir, 'mods', mods[0], 'assets'); await listAllFiles(path.resolve(modDir, 'data/maps/'), paths, 'json', path.resolve(modDir)); } diff --git a/webapp/src/app/components/dialogs/load-map/load-map.component.html b/webapp/src/app/components/dialogs/load-map/load-map.component.html index 2b55bc6b..8add1779 100644 --- a/webapp/src/app/components/dialogs/load-map/load-map.component.html +++ b/webapp/src/app/components/dialogs/load-map/load-map.component.html @@ -18,6 +18,17 @@
+ @if (currentMod) { +
+ + Vanilla Maps + +
+ } + @@ -28,7 +39,7 @@
- @if (!loading) { + @if (!loading()) {
  • @@ -71,12 +82,12 @@ } @else { - -
    - -
    - - } + +
    + +
    + + }
  • diff --git a/webapp/src/app/components/dialogs/load-map/load-map.component.ts b/webapp/src/app/components/dialogs/load-map/load-map.component.ts index 2fe38c63..ae67b8be 100644 --- a/webapp/src/app/components/dialogs/load-map/load-map.component.ts +++ b/webapp/src/app/components/dialogs/load-map/load-map.component.ts @@ -1,5 +1,5 @@ import { NestedTreeControl } from '@angular/cdk/tree'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, inject, Input, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, ElementRef, inject, Input, signal, ViewChild } from '@angular/core'; import { MatSidenav } from '@angular/material/sidenav'; import { MatNestedTreeNode, MatTree, MatTreeNestedDataSource, MatTreeNode, MatTreeNodeDef, MatTreeNodeOutlet, MatTreeNodeToggle } from '@angular/material/tree'; @@ -19,6 +19,8 @@ import { MatFormField, MatInput } from '@angular/material/input'; import { FormsModule } from '@angular/forms'; import { HighlightDirective } from '../../../directives/highlight.directive'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; +import { SettingsService } from '../../../services/settings.service'; +import { MatCheckbox } from '@angular/material/checkbox'; @Component({ @@ -41,7 +43,8 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner'; MatNestedTreeNode, MatTreeNodeToggle, MatTreeNodeOutlet, - MatProgressSpinner + MatProgressSpinner, + MatCheckbox ] }) export class LoadMapComponent { @@ -51,6 +54,7 @@ export class LoadMapComponent { private searchFilterService = inject(SearchFilterService); private readonly eventsService = inject(GlobalEventsService); private readonly overlayService = inject(OverlayService); + private readonly settingsService = inject(SettingsService); @ViewChild('fileUpload', {static: true}) @@ -62,7 +66,7 @@ export class LoadMapComponent { @Input() sidenav!: MatSidenav; - loading = false; + loading = signal(false); treeControl = new NestedTreeControl(node => node.children); mapsSource = new MatTreeNestedDataSource(); @@ -71,22 +75,31 @@ export class LoadMapComponent { virtualRoot = new VirtualMapNode(this.root); // To reuse the children filtering. filter = ''; + currentMod = ''; + vanillaMaps = signal(false); + constructor() { + effect(() => { + this.vanillaMaps(); + this.refresh(); + }); + this.mapsSource.data = []; - this.refresh(); + this.currentMod = this.settingsService.sharedService.getSelectedMod(); } focusInput() { this.filterInput.nativeElement.focus(); } - refresh() { - this.loading = true; - this.http.getMaps().subscribe(paths => { - this.loading = false; - this.displayMaps(paths); - this.update(); - }); + async refresh() { + const req = this.vanillaMaps() ? this.http.getVanillaMaps() : this.http.getMaps(); + this.loading.set(true); + const paths = await firstValueFrom(req); + this.loading.set(false); + + this.displayMaps(paths); + this.update(); } update() { diff --git a/webapp/src/app/components/dialogs/settings/settings.component.html b/webapp/src/app/components/dialogs/settings/settings.component.html index c2819fc0..2a93583a 100644 --- a/webapp/src/app/components/dialogs/settings/settings.component.html +++ b/webapp/src/app/components/dialogs/settings/settings.component.html @@ -26,7 +26,7 @@
    Mod - + None @for (mod of mods; track mod) { @@ -35,15 +35,6 @@ Maps will be stored and loaded from the selected mod
    -
    - - Include vanilla maps - -
    this.mods = mods); this.mod = this.sharedService.getSelectedMod(); - this.isIncludeVanillaMapsDisabled = !this.mod; this.settings = JSON.parse(JSON.stringify(this.settingsService.getSettings())); } @@ -136,10 +124,6 @@ export class SettingsComponent implements OnInit { } } - modSelectEvent(selectedMod: string) { - this.isIncludeVanillaMapsDisabled = !selectedMod; - } - save() { if (this.isElectron) { this.electron.saveAssetsPath(this.folderFormControl.value); diff --git a/webapp/src/app/services/http-client.service.ts b/webapp/src/app/services/http-client.service.ts index 80bb1f8c..e8c9e35b 100644 --- a/webapp/src/app/services/http-client.service.ts +++ b/webapp/src/app/services/http-client.service.ts @@ -14,47 +14,50 @@ export class HttpClientService { private http = inject(HttpClient); private electron = inject(ElectronService); private settingsService = inject(SettingsService); - - + + private readonly fileName = 'config.json'; - + getAllFiles(): Observable { return this.request('api/allFiles', api.getAllFiles); } - + getAllTilesets(): Observable { return this.request('api/allTilesets', api.getAllTilesets); } - + getMaps(): Observable { - const includeVanillaMaps = this.settingsService.getSettings().includeVanillaMaps; - return this.request(`api/allMaps?includeVanillaMaps=${includeVanillaMaps}`, api.getAllMaps, includeVanillaMaps); + return this.request('api/allMaps', api.getAllMaps, false); } - + + getVanillaMaps(): Observable { + return this.request('api/allMaps?vanillaMaps=true', api.getAllMaps, true); + } + getProps(): Observable { const folder = 'data/props/'; const extension = 'json'; return this.request(`api/allFilesInFolder?folder=${folder}&extension=${extension}`, api.getAllFilesInFolder, folder, extension); } - + getScalableProps(): Observable { const folder = 'data/scale-props/'; const extension = 'json'; return this.request(`api/allFilesInFolder?folder=${folder}&extension=${extension}`, api.getAllFilesInFolder, folder, extension); } - + getEnemies(): Observable { const folder = 'data/enemies/'; const extension = 'json'; return this.request(`api/allFilesInFolder?folder=${folder}&extension=${extension}`, api.getAllFilesInFolder, folder, extension); } - + getCharacters(): Observable { const folder = 'data/characters/'; const extension = 'json'; return this.request(`api/allFilesInFolder?folder=${folder}&extension=${extension}`, api.getAllFilesInFolder, folder, extension); } - + getMods(): Observable<{ id: string, displayName: string }[]> { return this.request('api/allMods', api.getAllMods); } @@ -62,7 +65,7 @@ export class HttpClientService { getModMapEditorConfigs() { return lastValueFrom(this.request('api/allModMapEditorConfigs', api.getAllModMapEditorConfigs)); } - + getAssetsFile(path: string): Observable { if (!Globals.isElectron) { return this.http.post(Globals.URL + 'api/get', { path }); @@ -70,7 +73,7 @@ export class HttpClientService { const cc = this.electron.getAssetsPath(); return this.toObservable(api.get(cc, path)); } - + resolveFile(path: string): Observable { if (!Globals.isElectron) { return this.http.post(Globals.URL + 'api/resolve', { path }); @@ -78,16 +81,16 @@ export class HttpClientService { const cc = this.electron.getAssetsPath(); return this.toObservable(api.resolve(cc, path)); } - + saveFile(path: string, content: any): Observable { const file = { path: path, content: content }; if (!Globals.isElectron) { return this.http.post(Globals.URL + 'api/saveFile', file); } - + return this.toObservable(api.saveFile(this.electron.getAssetsPath(), file)); } - + /** * Request a resource either from backend when run in the browser or directly from backend when run with Electron. * @param url URL of the backend endpoint relative to Globals.URL @@ -101,7 +104,7 @@ export class HttpClientService { const path = this.electron.getAssetsPath(); return this.toObservable(common(path, ...args)); } - + private toObservable(promise: Promise): Observable { return new Observable(subsriber => { promise diff --git a/webapp/src/app/services/settings.service.ts b/webapp/src/app/services/settings.service.ts index 36fa95a7..8bee4164 100644 --- a/webapp/src/app/services/settings.service.ts +++ b/webapp/src/app/services/settings.service.ts @@ -1,8 +1,11 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; +import { SharedService } from './shared-service'; +import { Globals } from './globals'; +import { ElectronService } from './electron.service'; +import { BrowserService } from './browser.service'; export interface AppSettings { wrapEventEditorLines: boolean; - includeVanillaMaps: boolean; selectionBoxDark: boolean; } @@ -11,9 +14,18 @@ export interface AppSettings { }) export class SettingsService { + public readonly sharedService: SharedService; + + constructor() { + if (Globals.isElectron) { + this.sharedService = inject(ElectronService); + } else { + this.sharedService = inject(BrowserService); + } + } + private settings: AppSettings = { wrapEventEditorLines: this.loadBooleanOrDefault('wrapEventEditorLines', true), - includeVanillaMaps: this.loadBooleanOrDefault('includeVanillaMaps', false), selectionBoxDark: this.loadBooleanOrDefault('selectionBoxDark', true), }; From acd5434e019348786365604f7272248a2d12cd92 Mon Sep 17 00:00:00 2001 From: cramerL Date: Fri, 3 Apr 2026 04:08:33 +0200 Subject: [PATCH 2/6] refactored sharedService into injection token to remove dependency of settingsService --- CHANGELOG.md | 3 +++ .../dialogs/load-map/load-map.component.ts | 6 +++--- .../dialogs/settings/settings.component.ts | 4 ++-- webapp/src/app/services/settings.service.ts | 18 ++---------------- webapp/src/app/services/shared-service.ts | 12 +++++++++++- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 267aa836..f088ed7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ 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] +### Changed +- Moved "vanilla maps" toggle from settings into the map loading dialog to allow quick switching between mod and vanilla maps [#347](https://github.com/CCDirectLink/crosscode-map-editor/issues/347) + ## [2.1.0] 2026-03-20 ### Changed - Changed scaling behaviour for json widget. Depending on content, it scales to 1-5 rows initially. When the user pastes multi-line json it also rescales to fit the content. [#344](https://github.com/CCDirectLink/crosscode-map-editor/issues/344) diff --git a/webapp/src/app/components/dialogs/load-map/load-map.component.ts b/webapp/src/app/components/dialogs/load-map/load-map.component.ts index ae67b8be..0008e456 100644 --- a/webapp/src/app/components/dialogs/load-map/load-map.component.ts +++ b/webapp/src/app/components/dialogs/load-map/load-map.component.ts @@ -19,7 +19,7 @@ import { MatFormField, MatInput } from '@angular/material/input'; import { FormsModule } from '@angular/forms'; import { HighlightDirective } from '../../../directives/highlight.directive'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; -import { SettingsService } from '../../../services/settings.service'; +import { SHARED_SERVICE } from '../../../services/shared-service'; import { MatCheckbox } from '@angular/material/checkbox'; @@ -54,7 +54,7 @@ export class LoadMapComponent { private searchFilterService = inject(SearchFilterService); private readonly eventsService = inject(GlobalEventsService); private readonly overlayService = inject(OverlayService); - private readonly settingsService = inject(SettingsService); + private readonly sharedService = inject(SHARED_SERVICE); @ViewChild('fileUpload', {static: true}) @@ -85,7 +85,7 @@ export class LoadMapComponent { }); this.mapsSource.data = []; - this.currentMod = this.settingsService.sharedService.getSelectedMod(); + this.currentMod = this.sharedService.getSelectedMod(); } focusInput() { diff --git a/webapp/src/app/components/dialogs/settings/settings.component.ts b/webapp/src/app/components/dialogs/settings/settings.component.ts index 43f7ccf5..c485bae3 100644 --- a/webapp/src/app/components/dialogs/settings/settings.component.ts +++ b/webapp/src/app/components/dialogs/settings/settings.component.ts @@ -2,11 +2,11 @@ import { Component, inject, OnInit } from '@angular/core'; import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { BrowserService } from '../../../services/browser.service'; import { ElectronService } from '../../../services/electron.service'; import { Globals } from '../../../services/globals'; import { HttpClientService } from '../../../services/http-client.service'; import { AppSettings, SettingsService } from '../../../services/settings.service'; +import { SHARED_SERVICE } from '../../../services/shared-service'; import { ImageSelectCardComponent, PropListCard } from '../../widgets/shared/image-select-overlay/image-select-card/image-select-card.component'; import { OverlayRefControl } from '../overlay/overlay-ref-control'; import { OverlayPanelComponent } from '../overlay/overlay-panel/overlay-panel.component'; @@ -71,7 +71,7 @@ export class SettingsComponent implements OnInit { imgSrc: 'assets/selection-dark.png', }; - private readonly sharedService = this.settingsService.sharedService; + private readonly sharedService = inject(SHARED_SERVICE); constructor() { const http = inject(HttpClientService); diff --git a/webapp/src/app/services/settings.service.ts b/webapp/src/app/services/settings.service.ts index 8bee4164..b30e72a5 100644 --- a/webapp/src/app/services/settings.service.ts +++ b/webapp/src/app/services/settings.service.ts @@ -1,8 +1,4 @@ -import { inject, Injectable } from '@angular/core'; -import { SharedService } from './shared-service'; -import { Globals } from './globals'; -import { ElectronService } from './electron.service'; -import { BrowserService } from './browser.service'; +import { Injectable } from '@angular/core'; export interface AppSettings { wrapEventEditorLines: boolean; @@ -13,17 +9,7 @@ export interface AppSettings { providedIn: 'root' }) export class SettingsService { - - public readonly sharedService: SharedService; - - constructor() { - if (Globals.isElectron) { - this.sharedService = inject(ElectronService); - } else { - this.sharedService = inject(BrowserService); - } - } - + private settings: AppSettings = { wrapEventEditorLines: this.loadBooleanOrDefault('wrapEventEditorLines', true), selectionBoxDark: this.loadBooleanOrDefault('selectionBoxDark', true), diff --git a/webapp/src/app/services/shared-service.ts b/webapp/src/app/services/shared-service.ts index ec288bc0..dbd3cb24 100644 --- a/webapp/src/app/services/shared-service.ts +++ b/webapp/src/app/services/shared-service.ts @@ -1,6 +1,16 @@ +import { inject, InjectionToken } from '@angular/core'; +import { Globals } from './globals'; +import { ElectronService } from './electron.service'; +import { BrowserService } from './browser.service'; + export interface SharedService { saveModSelect(mod: string): Promise; getSelectedMod(): string; - + relaunch(): void; } + +export const SHARED_SERVICE = new InjectionToken('SharedService', { + providedIn: 'root', + factory: () => Globals.isElectron ? inject(ElectronService) : inject(BrowserService), +}); From 9dad9ccbd9f6677dbe2e4b626bc20c4aace21b40 Mon Sep 17 00:00:00 2001 From: cramerL Date: Fri, 3 Apr 2026 04:19:49 +0200 Subject: [PATCH 3/6] disable checkbox during loading to avoid race conditions --- .../app/components/dialogs/load-map/load-map.component.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/src/app/components/dialogs/load-map/load-map.component.html b/webapp/src/app/components/dialogs/load-map/load-map.component.html index 8add1779..773b4336 100644 --- a/webapp/src/app/components/dialogs/load-map/load-map.component.html +++ b/webapp/src/app/components/dialogs/load-map/load-map.component.html @@ -20,9 +20,10 @@
    @if (currentMod) {
    - Vanilla Maps From d1f461b0290506382644093b39f73eb7feb49d84 Mon Sep 17 00:00:00 2001 From: cramerL Date: Fri, 3 Apr 2026 14:53:08 +0200 Subject: [PATCH 4/6] replaced loading paths with resource --- .../dialogs/load-map/load-map.component.html | 14 +++--- .../dialogs/load-map/load-map.component.ts | 50 ++++++++++--------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/webapp/src/app/components/dialogs/load-map/load-map.component.html b/webapp/src/app/components/dialogs/load-map/load-map.component.html index 773b4336..5ae829d8 100644 --- a/webapp/src/app/components/dialogs/load-map/load-map.component.html +++ b/webapp/src/app/components/dialogs/load-map/load-map.component.html @@ -8,7 +8,7 @@ -
    - @if (!loading()) { + @if (!paths.isLoading()) {
  • @@ -49,11 +49,11 @@ edit @for (name of node.names; track name; let isLast = $last) { - + @if (!isLast) { / } - + }
  • @@ -67,11 +67,11 @@ @for (name of node.names; track name; let isLast = $last) { - + @if (!isLast) { / } - + } @if (treeControl.isExpanded(node)) { diff --git a/webapp/src/app/components/dialogs/load-map/load-map.component.ts b/webapp/src/app/components/dialogs/load-map/load-map.component.ts index 0008e456..49477dd2 100644 --- a/webapp/src/app/components/dialogs/load-map/load-map.component.ts +++ b/webapp/src/app/components/dialogs/load-map/load-map.component.ts @@ -1,5 +1,5 @@ import { NestedTreeControl } from '@angular/cdk/tree'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, ElementRef, inject, Input, signal, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, ElementRef, inject, Input, resource, signal, untracked, ViewChild } from '@angular/core'; import { MatSidenav } from '@angular/material/sidenav'; import { MatNestedTreeNode, MatTree, MatTreeNestedDataSource, MatTreeNode, MatTreeNodeDef, MatTreeNodeOutlet, MatTreeNodeToggle } from '@angular/material/tree'; @@ -44,8 +44,8 @@ import { MatCheckbox } from '@angular/material/checkbox'; MatTreeNodeToggle, MatTreeNodeOutlet, MatProgressSpinner, - MatCheckbox - ] + MatCheckbox, + ], }) export class LoadMapComponent { private mapLoader = inject(MapLoaderService); @@ -57,21 +57,27 @@ export class LoadMapComponent { private readonly sharedService = inject(SHARED_SERVICE); - @ViewChild('fileUpload', {static: true}) + @ViewChild('fileUpload', { static: true }) fileUpload!: ElementRef; - @ViewChild('filterInput', {static: true}) + @ViewChild('filterInput', { static: true }) filterInput!: ElementRef; @Input() sidenav!: MatSidenav; - loading = signal(false); + paths = resource({ + params: () => ({ vanillaMaps: this.vanillaMaps() }), + loader: async ({ params }) => { + const req = params.vanillaMaps ? this.http.getVanillaMaps() : this.http.getMaps(); + return await firstValueFrom(req); + }, + }); treeControl = new NestedTreeControl(node => node.children); mapsSource = new MatTreeNestedDataSource(); - root: MapNodeRoot = {name: '', displayed: true, children: []}; // The root itself is never displayed. It is used as a datasource for virtualRoot. + root: MapNodeRoot = { name: '', displayed: true, children: [] }; // The root itself is never displayed. It is used as a datasource for virtualRoot. virtualRoot = new VirtualMapNode(this.root); // To reuse the children filtering. filter = ''; @@ -79,29 +85,25 @@ export class LoadMapComponent { vanillaMaps = signal(false); constructor() { - effect(() => { - this.vanillaMaps(); - this.refresh(); - }); - this.mapsSource.data = []; this.currentMod = this.sharedService.getSelectedMod(); + + effect(() => { + const paths = this.paths.value(); + if (!paths) { + return; + } + untracked(() => { + this.displayMaps(paths); + this.update(); + }); + }); } focusInput() { this.filterInput.nativeElement.focus(); } - async refresh() { - const req = this.vanillaMaps() ? this.http.getVanillaMaps() : this.http.getMaps(); - this.loading.set(true); - const paths = await firstValueFrom(req); - this.loading.set(false); - - this.displayMaps(paths); - this.update(); - } - update() { for (const node of this.root.children) { this.filterNode(node, this.filter); @@ -120,7 +122,7 @@ export class LoadMapComponent { const dialogRef = this.overlayService.open(ConfirmCloseComponent, { hasBackdrop: true, }); - const result = await firstValueFrom(dialogRef.ref.onClose, {defaultValue: false}); + const result = await firstValueFrom(dialogRef.ref.onClose, { defaultValue: false }); if (result) { this.eventsService.hasUnsavedChanges.next(false); } @@ -159,7 +161,7 @@ export class LoadMapComponent { const node = this.resolve(data, path, lastNode, lastPath); const name = path.substring(path.lastIndexOf('.') + 1); - node.push({name, path, displayed: true}); + node.push({ name, path, displayed: true }); lastPath = path; lastNode = node; From 1a515157bd9a5aaa63abfcbfd58c089be2f42ee0 Mon Sep 17 00:00:00 2001 From: cramerL Date: Fri, 3 Apr 2026 15:26:05 +0200 Subject: [PATCH 5/6] save show vanilla maps setting in localstorage --- .../dialogs/load-map/load-map.component.ts | 6 ++- webapp/src/app/services/settings.service.ts | 50 ++++++++++++++----- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/webapp/src/app/components/dialogs/load-map/load-map.component.ts b/webapp/src/app/components/dialogs/load-map/load-map.component.ts index 49477dd2..24f68b4e 100644 --- a/webapp/src/app/components/dialogs/load-map/load-map.component.ts +++ b/webapp/src/app/components/dialogs/load-map/load-map.component.ts @@ -1,5 +1,5 @@ import { NestedTreeControl } from '@angular/cdk/tree'; -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, ElementRef, inject, Input, resource, signal, untracked, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, ElementRef, inject, Input, resource, untracked, ViewChild } from '@angular/core'; import { MatSidenav } from '@angular/material/sidenav'; import { MatNestedTreeNode, MatTree, MatTreeNestedDataSource, MatTreeNode, MatTreeNodeDef, MatTreeNodeOutlet, MatTreeNodeToggle } from '@angular/material/tree'; @@ -21,6 +21,7 @@ import { HighlightDirective } from '../../../directives/highlight.directive'; import { MatProgressSpinner } from '@angular/material/progress-spinner'; import { SHARED_SERVICE } from '../../../services/shared-service'; import { MatCheckbox } from '@angular/material/checkbox'; +import { SettingsService } from '../../../services/settings.service'; @Component({ @@ -55,6 +56,7 @@ export class LoadMapComponent { private readonly eventsService = inject(GlobalEventsService); private readonly overlayService = inject(OverlayService); private readonly sharedService = inject(SHARED_SERVICE); + private readonly settingsService = inject(SettingsService); @ViewChild('fileUpload', { static: true }) @@ -82,7 +84,7 @@ export class LoadMapComponent { filter = ''; currentMod = ''; - vanillaMaps = signal(false); + vanillaMaps = this.settingsService.signalSettings().showVanillaMaps; constructor() { this.mapsSource.data = []; diff --git a/webapp/src/app/services/settings.service.ts b/webapp/src/app/services/settings.service.ts index b30e72a5..6a3da810 100644 --- a/webapp/src/app/services/settings.service.ts +++ b/webapp/src/app/services/settings.service.ts @@ -1,36 +1,62 @@ -import { Injectable } from '@angular/core'; +import { effect, Injectable, signal, WritableSignal } from '@angular/core'; + +export type Signalify = { + [K in keyof T]: WritableSignal; +}; export interface AppSettings { wrapEventEditorLines: boolean; selectionBoxDark: boolean; + showVanillaMaps: boolean; } @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class SettingsService { - - private settings: AppSettings = { + + private readonly settings: Signalify = { wrapEventEditorLines: this.loadBooleanOrDefault('wrapEventEditorLines', true), selectionBoxDark: this.loadBooleanOrDefault('selectionBoxDark', true), + showVanillaMaps: this.loadBooleanOrDefault('showVanillaMaps', false), }; + constructor() { + for (const [key, val] of Object.entries(this.settings)) { + effect(() => { + localStorage.setItem(key, val().toString()); + }); + } + } + + /** + * @deprecated Use signalSettings instead. + */ getSettings(): Readonly { + const out = {} as AppSettings; + for (const [key, val] of Object.entries(this.settings)) { + out[key as keyof AppSettings] = val(); + } + return out; + } + + signalSettings(): Signalify { return this.settings; } - private loadBooleanOrDefault(key: keyof AppSettings, defaultValue: boolean): boolean { + private loadBooleanOrDefault(key: keyof AppSettings, defaultValue: boolean): WritableSignal { const loadedValue = localStorage.getItem(key); - return loadedValue === null ? defaultValue : (loadedValue === 'true'); + const val = loadedValue === null ? defaultValue : (loadedValue === 'true'); + return signal(val); } + /** + * @deprecated set signals directly instead + */ public updateSettings(newSettings: Partial) { - this.settings = { - ...this.settings, - ...newSettings - }; - for (const [key, value] of Object.entries(this.settings)) { - localStorage.setItem(key, (value as boolean).toString()); + for (const [k, val] of Object.entries(newSettings)) { + const key = k as keyof AppSettings; + this.settings[key].set(val); } } } From d616b65711255a13e5b573f37dc427128773ee3d Mon Sep 17 00:00:00 2001 From: cramerL Date: Fri, 3 Apr 2026 15:40:29 +0200 Subject: [PATCH 6/6] moved deprecated functions into settings component, so it's usage is at least confined to this one component --- .../dialogs/settings/settings.component.ts | 22 ++++++++++++++----- .../editor/event-editor.component.ts | 2 +- .../src/app/services/phaser/BaseTileDrawer.ts | 2 +- webapp/src/app/services/settings.service.ts | 21 ------------------ 4 files changed, 18 insertions(+), 29 deletions(-) diff --git a/webapp/src/app/components/dialogs/settings/settings.component.ts b/webapp/src/app/components/dialogs/settings/settings.component.ts index c485bae3..2861a488 100644 --- a/webapp/src/app/components/dialogs/settings/settings.component.ts +++ b/webapp/src/app/components/dialogs/settings/settings.component.ts @@ -43,8 +43,8 @@ import { MatCheckbox } from '@angular/material/checkbox'; MatOption, ColoredTextDirective, MatCheckbox, - ImageSelectCardComponent - ] + ImageSelectCardComponent, + ], }) export class SettingsComponent implements OnInit { private ref = inject(OverlayRefControl); @@ -78,7 +78,11 @@ export class SettingsComponent implements OnInit { http.getMods().subscribe(mods => this.mods = mods); this.mod = this.sharedService.getSelectedMod(); - this.settings = JSON.parse(JSON.stringify(this.settingsService.getSettings())); + + this.settings = {} as AppSettings; + for (const [key, val] of Object.entries(this.settingsService.signalSettings())) { + this.settings[key as keyof AppSettings] = val(); + } } ngOnInit() { @@ -119,7 +123,7 @@ export class SettingsComponent implements OnInit { this.folderFormControl.setErrors(null); } else { this.folderFormControl.setErrors({ - invalid: true + invalid: true, }); } } @@ -129,10 +133,16 @@ export class SettingsComponent implements OnInit { this.electron.saveAssetsPath(this.folderFormControl.value); } this.sharedService.saveModSelect(this.mod); - this.settingsService.updateSettings(this.settings); + + const settings = this.settingsService.signalSettings(); + for (const [k, val] of Object.entries(this.settings)) { + const key = k as keyof AppSettings; + settings[key].set(val); + } + this.close(); const ref = this.snackBar.open('Changing the path requires to restart the editor', 'Restart', { - duration: 6000 + duration: 6000, }); ref.onAction().subscribe(() => this.sharedService.relaunch()); diff --git a/webapp/src/app/components/widgets/event-widget/event-editor/editor/event-editor.component.ts b/webapp/src/app/components/widgets/event-widget/event-editor/editor/event-editor.component.ts index 9181ad56..f078e8d5 100644 --- a/webapp/src/app/components/widgets/event-widget/event-editor/editor/event-editor.component.ts +++ b/webapp/src/app/components/widgets/event-widget/event-editor/editor/event-editor.component.ts @@ -82,7 +82,7 @@ export class EventEditorComponent implements OnChanges, OnInit { private copiedNode?: EventDisplay; ngOnInit() { - this.wrapText = this.settingsService.getSettings().wrapEventEditorLines; + this.wrapText = this.settingsService.signalSettings().wrapEventEditorLines(); } ngOnChanges() { diff --git a/webapp/src/app/services/phaser/BaseTileDrawer.ts b/webapp/src/app/services/phaser/BaseTileDrawer.ts index c4fa8429..6ef0a882 100644 --- a/webapp/src/app/services/phaser/BaseTileDrawer.ts +++ b/webapp/src/app/services/phaser/BaseTileDrawer.ts @@ -243,7 +243,7 @@ export class BaseTileDrawer extends BaseObject { let textColor = 'rgba(0,0,0,0.6)'; let backgroundColor = 0xffffff; - if (Globals.settingsService.getSettings().selectionBoxDark) { + if (Globals.settingsService.signalSettings().selectionBoxDark()) { textColor = 'rgba(255,255,255,0.9)'; backgroundColor = 0x333333; } diff --git a/webapp/src/app/services/settings.service.ts b/webapp/src/app/services/settings.service.ts index 6a3da810..bc584cf6 100644 --- a/webapp/src/app/services/settings.service.ts +++ b/webapp/src/app/services/settings.service.ts @@ -29,17 +29,6 @@ export class SettingsService { } } - /** - * @deprecated Use signalSettings instead. - */ - getSettings(): Readonly { - const out = {} as AppSettings; - for (const [key, val] of Object.entries(this.settings)) { - out[key as keyof AppSettings] = val(); - } - return out; - } - signalSettings(): Signalify { return this.settings; } @@ -49,14 +38,4 @@ export class SettingsService { const val = loadedValue === null ? defaultValue : (loadedValue === 'true'); return signal(val); } - - /** - * @deprecated set signals directly instead - */ - public updateSettings(newSettings: Partial) { - for (const [k, val] of Object.entries(newSettings)) { - const key = k as keyof AppSettings; - this.settings[key].set(val); - } - } }