From fdb791c1f246c6c469cefd4472668fa584ba9e86 Mon Sep 17 00:00:00 2001 From: cbartondock Date: Fri, 25 Oct 2024 17:49:01 -0400 Subject: [PATCH] drag and drop re-ordering for parsers --- CHANGELOG.md | 10 ++ package.json | 1 + src/lang/en-US/langStrings.json | 4 +- src/renderer/app.module.ts | 2 + .../components/nav-parsers.component.ts | 131 +++++++++-------- src/renderer/services/parsers.service.ts | 29 +++- .../styles/nav-parsers.component.scss | 138 ++++++++++-------- src/renderer/styles/themes.global.scss | 7 +- .../templates/nav-parsers.component.html | 90 +++++++----- src/renderer/templates/navarea.component.html | 2 +- src/renderer/templates/parsers.component.html | 37 ----- yarn.lock | 18 +++ 12 files changed, 267 insertions(+), 202 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e3e5c3fc..b530ffa99a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project will be documented in this file. +## 2.5.28 + +### Added + +- Ability to drag/drop to re-arrange parsers in Classic and Deck themes. + +### Fixed + +- Executables on Mac OS should be allowed to be directories (.app folders). + ## 2.5.27 ### Added diff --git a/package.json b/package.json index 043a1d727b..de4e264947 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "@electron/remote": "^2.1.2", "@node-steam/vdf": "^2.2.0", "ajv": "^8.17.1", + "angular-draggable-droppable": "^8.0.0", "async": "^3.2.6", "bcp-47": "^2.1.0", "better-sqlite3": "^11.3.0", diff --git a/src/lang/en-US/langStrings.json b/src/lang/en-US/langStrings.json index 23ece132c6..ca68c364e5 100644 --- a/src/lang/en-US/langStrings.json +++ b/src/lang/en-US/langStrings.json @@ -448,8 +448,8 @@ "component": { "buttons": { "save": "Save", - "copy": "Copy", - "testParser": "Test parser", + "copy": "Clone", + "testParser": "Test", "delete": "Delete", "moveUp": "Move up", "moveDown": "Move down", diff --git a/src/renderer/app.module.ts b/src/renderer/app.module.ts index cffc91509e..8539155175 100644 --- a/src/renderer/app.module.ts +++ b/src/renderer/app.module.ts @@ -13,6 +13,7 @@ import * as Directives from "./directives"; import * as Pipes from "./pipes"; import * as Guards from "./guards"; import { AppRoutes } from "./app.routing"; +import { DragAndDropModule } from "angular-draggable-droppable"; // Unfortunately not usable for declarations right now, as the strictly typed compiler can't evaluate statically // Ideally one would have declarations: [...ngObjectsToArray(importObject: T) { FormsModule, ReactiveFormsModule, ColorPickerModule, + DragAndDropModule ], declarations: [ Components.AboutComponent, diff --git a/src/renderer/components/nav-parsers.component.ts b/src/renderer/components/nav-parsers.component.ts index 7712a07efe..c7d26fcb3c 100644 --- a/src/renderer/components/nav-parsers.component.ts +++ b/src/renderer/components/nav-parsers.component.ts @@ -14,6 +14,7 @@ import { import { UserConfiguration, AppSettings } from "../../models"; import { Subscription } from "rxjs"; import { APP } from "../../variables"; +import { Router } from "@angular/router"; @Component({ selector: "nav-parsers", @@ -30,12 +31,15 @@ export class NavParsersComponent implements OnDestroy { navForm: FormGroup; imageMap: { [k: string]: any } = {}; private subscriptions: Subscription = new Subscription(); - private appSettings: AppSettings; + appSettings: AppSettings; + dragStartIndex: number = -1; + currentId: string = ""; constructor( private parsersService: ParsersService, private languageService: LanguageService, private exceptionsService: UserExceptionsService, + private router: Router, private settingsService: SettingsService, private changeRef: ChangeDetectorRef, private formBuilder: FormBuilder, @@ -50,35 +54,8 @@ export class NavParsersComponent implements OnDestroy { this.parsersService .getUserConfigurations() .subscribe((userConfigurations) => { - for (let userConfiguration of userConfigurations) { - let separatedValues: string[] = - userConfiguration.saved.configTitle.split(" - "); - let separatedValuesImg = separatedValues.length - ? separatedValues[0].replaceAll(/[\/\-\(\)\.\s]/g, "") - : ""; - separatedValuesImg = separatedValuesImg - .replaceAll("3do", "p3do") - .toLowerCase(); - let imgValue = ""; - let alternativeValue = false; - let detailsValue = ""; - try { - imgValue = require( - `../../assets/systems/${separatedValuesImg}.svg`, - ); - detailsValue = userConfiguration.saved.configTitle - .split(" - ") - .slice(1) - .join(" - "); - } catch (e) { - alternativeValue = true; - detailsValue = userConfiguration.saved.configTitle; - } - this.imageMap[userConfiguration.saved.parserId] = { - alternative: alternativeValue, - details: detailsValue, - img: imgValue, - }; + if(this.appSettings.theme == "EmuDeck") { + this.initializeImageMap(userConfigurations) } this.userConfigurations = userConfigurations; @@ -89,20 +66,12 @@ export class NavParsersComponent implements OnDestroy { : false; this.navForm = this.formBuilder.group({ selectAll: someOn, - parserStatuses: this.formBuilder.array( - this.userConfigurations.map( - (config: { - saved: UserConfiguration; - current: UserConfiguration; - }) => { - let singleton: { [k: string]: boolean } = {}; - singleton[config.saved.parserId] = !config.saved.disabled; - return this.formBuilder.group(singleton); - }, - ), - ), + parserStatuses: this.formBuilder.group( + Object.fromEntries(this.userConfigurations.map((config: {saved: UserConfiguration, current: UserConfiguration}) => { + return [config.saved.parserId, !config.saved.disabled] + })) + ) }); - this.navForm .get("selectAll") .valueChanges.subscribe((val: boolean) => { @@ -115,17 +84,13 @@ export class NavParsersComponent implements OnDestroy { this.parsersService.changeEnabledStatusAll(val); } }); - - this.getParserControls().forEach((control: FormGroup) => { - control.valueChanges.subscribe( - (val: { [parserId: string]: boolean }) => { - this.parsersService.changeEnabledStatus( - Object.keys(val)[0], - Object.values(val)[0], - ); - }, - ); - }); + const parserControls = this.getParserControls(); + for(let userConfiguration of this.userConfigurations) { + let parserId = userConfiguration.saved.parserId; + parserControls[parserId].valueChanges.subscribe((val: boolean)=>{ + this.parsersService.changeEnabledStatus(parserId, val); + }) + } this.changeRef.detectChanges(); }), @@ -150,16 +115,66 @@ export class NavParsersComponent implements OnDestroy { } getParserControls() { - return (this.navForm.get("parserStatuses") as FormArray) - .controls as FormGroup[]; + return (this.navForm.get("parserStatuses") as FormGroup).controls; } emuClick(control: FormControl) { - if (this.appSettings.theme == "EmuDeck") { - control.setValue(!control.value); + control.setValue(!control.value); + } + + onClick(index: number, parserId: string) { + this.router.navigate(["/parsers", index]); + this.currentId = parserId; + } + + dragStart(event: Event) { + this.dragStartIndex = parseInt(this.router.url.split("/")[2]); + } + + handleDrop(fromIndex: number, toIndex:number) { + this.parsersService.injectIndex(fromIndex, toIndex); + if(fromIndex < this.dragStartIndex && this.dragStartIndex <= toIndex) { + this.router.navigate(["/parsers", this.dragStartIndex - 1]) + } else if(toIndex <= this.dragStartIndex && this.dragStartIndex < fromIndex) { + this.router.navigate(["/parsers", this.dragStartIndex + 1]) + } else if(this.dragStartIndex==fromIndex) { + this.router.navigate(["/parsers", toIndex]) } } + initializeImageMap(userConfigurations: {current: UserConfiguration, saved: UserConfiguration}[]) { + for (let userConfiguration of userConfigurations) { + let separatedValues: string[] = + userConfiguration.saved.configTitle.split(" - "); + let separatedValuesImg = separatedValues.length + ? separatedValues[0].replaceAll(/[\/\-\(\)\.\s]/g, "") + : ""; + separatedValuesImg = separatedValuesImg + .replaceAll("3do", "p3do") + .toLowerCase(); + let imgValue = ""; + let alternativeValue = false; + let detailsValue = ""; + try { + imgValue = require( + `../../assets/systems/${separatedValuesImg}.svg`, + ); + detailsValue = userConfiguration.saved.configTitle + .split(" - ") + .slice(1) + .join(" - "); + } catch (e) { + alternativeValue = true; + detailsValue = userConfiguration.saved.configTitle; + } + this.imageMap[userConfiguration.saved.parserId] = { + alternative: alternativeValue, + details: detailsValue, + img: imgValue, + }; + } + } + ngOnDestroy() { this.subscriptions.unsubscribe(); } diff --git a/src/renderer/services/parsers.service.ts b/src/renderer/services/parsers.service.ts index fb746da03f..599fcd458a 100644 --- a/src/renderer/services/parsers.service.ts +++ b/src/renderer/services/parsers.service.ts @@ -228,14 +228,31 @@ export class ParsersService { this.saveUserConfigurations(); } - swapIndex(currentIndex: number, newIndex: number) { - let userConfigurations = this.userConfigurations.getValue(); + swapIndex(fromIndex: number, toIndex: number) { + if(fromIndex == toIndex){return;} + const configs = this.userConfigurations.getValue(); + if (fromIndex >= configs.length || toIndex >= configs.length) { + throw 'Index out of bounds'; + } + const from = configs[fromIndex]; + configs[fromIndex] = configs[toIndex]; + configs[toIndex] = from; + this.userConfigurations.next(configs); + this.saveUserConfigurations(); + } - let temp = userConfigurations[currentIndex]; - userConfigurations[currentIndex] = userConfigurations[newIndex]; - userConfigurations[newIndex] = temp; - this.userConfigurations.next(userConfigurations); + injectIndex(fromIndex: number, toIndex: number) { + if (fromIndex == toIndex) {return;} + const configs = this.userConfigurations.getValue(); + if (fromIndex >= configs.length || toIndex >= configs.length) { + throw 'Index out of bounds'; + } + const from = configs[fromIndex]; + const withoutFrom = configs.filter((_,i) => i!==fromIndex); + const newConfigs = withoutFrom.slice(0,toIndex).concat(from).concat(withoutFrom.slice(toIndex)) + this.userConfigurations.next(newConfigs); this.saveUserConfigurations(); + } changeEnabledStatus(parserId: string, enabled: boolean): Promise { diff --git a/src/renderer/styles/nav-parsers.component.scss b/src/renderer/styles/nav-parsers.component.scss index 46586d924e..b169ada410 100644 --- a/src/renderer/styles/nav-parsers.component.scss +++ b/src/renderer/styles/nav-parsers.component.scss @@ -11,7 +11,7 @@ white-space: nowrap; - --link-height: 1.2em; + --link-height: var(--height-nav-link); --link-vPadding: 0.5em; grid-area: nav-parsers; @include webkitScrollbar(nav-scrollbar, 0.375em); @@ -22,12 +22,31 @@ .titlelink { flex: 1; } + + .item { + .reorderer { + &.drop-over-active { + border-left: solid 2px var(--color--filled); + } + } + .inputContainer { + &.drag-active { + background-color: var(--color-nav-background); + z-index: 3; + opacity: 0.5; + } + &.active { + background-color: var(--color-nav-link-background-active) + } + } + } .inputContainer { display: flex; flex-direction: column; margin: 0.1em 0 0.1em 0; padding-left: 0em; + height: 100%; .inlineGroup { display: flex; @@ -86,7 +105,6 @@ &.active { color: var(--color-nav-link-text-active); border-color: var(--color-nav-link-border-active); - svg { & polyline { stroke: var(--color-nav-link-text-active); @@ -99,85 +117,87 @@ nav-link.titlelink { padding: var(--padding-nav-link); } - nav-link:not(.titlelink) { - padding: var(--padding-nav-link); - - position: relative; - - &::before { - top: 50%; - position: absolute; - content: ""; - width: 0.5em; - height: 0.5em; - background-color: var(--color-nav-link-enabled); - border-radius: 50%; - margin: 0 0 0 0em; - transform: translateY(-50%); - } - - &:hover { + .item { + nav-link:not(.titlelink) { + padding: var(--padding-nav-link); + + position: relative; + &::before { - background-color: var(--color-nav-link-enabled-hover); + top: 50%; + position: absolute; + content: ""; + width: 0.5em; + height: 0.5em; + background-color: var(--color-nav-link-enabled); + border-radius: 50%; + margin: 0 0 0 0em; + transform: translateY(-50%); } - - &.active { - &::before { - background-color: var(--color-nav-link-enabled-active); - } - } - } - - &.active { - &::before { - background-color: var(--color-nav-link-enabled-active); - } - } - - &.disabled { - &::before { - background-color: var(--color-nav-link-disabled); - } - + &:hover { &::before { - background-color: var(--color-nav-link-disabled-hover); + background-color: var(--color-nav-link-enabled-hover); } - + &.active { &::before { - background-color: var(--color-nav-link-disabled-active); + background-color: var(--color-nav-link-enabled-active); } } } - + &.active { &::before { - background-color: var(--color-nav-link-disabled-active); + background-color: var(--color-nav-link-enabled-active); } } - } - - &.unsaved { - &::before { - background-color: var(--color-nav-link-unsaved); - } - - &:hover { + + &.disabled { &::before { - background-color: var(--color-nav-link-unsaved-hover); + background-color: var(--color-nav-link-disabled); } - + + &:hover { + &::before { + background-color: var(--color-nav-link-disabled-hover); + } + + &.active { + &::before { + background-color: var(--color-nav-link-disabled-active); + } + } + } + &.active { &::before { - background-color: var(--color-nav-link-unsaved-active); + background-color: var(--color-nav-link-disabled-active); } } } - - &.active { + + &.unsaved { &::before { - background-color: var(--color-nav-link-unsaved-active); + background-color: var(--color-nav-link-unsaved); + } + + &:hover { + &::before { + background-color: var(--color-nav-link-unsaved-hover); + } + + &.active { + &::before { + background-color: var(--color-nav-link-unsaved-active); + } + } + } + + &.active { + &::before { + background-color: var(--color-nav-link-unsaved-active); + } } } } diff --git a/src/renderer/styles/themes.global.scss b/src/renderer/styles/themes.global.scss index 30e157f7c5..e60dba21ba 100644 --- a/src/renderer/styles/themes.global.scss +++ b/src/renderer/styles/themes.global.scss @@ -177,6 +177,7 @@ --color-nav-background: #121212; --color-nav-link-border: rgba(0, 0, 0, 0); --color-nav-link-border-active: #1ed660; + --color-nav-link-background-active: var(--ng-select-background); --color-nav-link-border-hover: rgba(0, 0, 0, 0); --color-nav-link-text: #a0a0a0; --color-nav-link-text-active: #f0fff0; @@ -1028,6 +1029,7 @@ --color-nav-background: #121212; --color-nav-link-border: rgba(0, 0, 0, 0); --color-nav-link-border-active: transparent; + --color-nav-link-background-active: var(--ng-select-background); --color-nav-link-border-hover: rgba(0, 0, 0, 0); --color-nav-link-text: #fff; --color-nav-link-text-active: #fff; @@ -1045,7 +1047,7 @@ --color-nav-link-bg-hover: var(--color-nav-link-background-active); --padding-nav-link: 0; --padding-nav-link-expand: 0px; - --height-nav-link: auto; + --height-nav-link: 100%; --img-nav-link-display: inline-block; --color-nav-scrollbar-thumb: rgba(80, 80, 80, 0.5); --color-nav-scrollbar-track: rgba(80, 80, 80, 0.3); @@ -1428,6 +1430,7 @@ --color-nav-link-border: rgba(0, 0, 0, 0); --color-nav-link-border-active: transparent; --color-nav-link-border-hover: rgba(0, 0, 0, 0); + --color-nav-link-background-active: var(--ng-select-background); --color-nav-link-text: #cccfd4; --color-nav-link-text-active: #f0fff0; --color-nav-link-text-hover: #f0fff0; @@ -1444,7 +1447,7 @@ --color-nav-link-bg-hover: var(--color-nav-link-background-active); --padding-nav-link: 16px; --padding-nav-link-expand: 0px; - --height-nav-link: auto; + --height-nav-link: 1.2em; --img-nav-link-display: inline-block; --color-nav-scrollbar-thumb: rgba(80, 80, 80, 0.5); --color-nav-scrollbar-track: rgba(80, 80, 80, 0.3); diff --git a/src/renderer/templates/nav-parsers.component.html b/src/renderer/templates/nav-parsers.component.html index 439e3ee58c..54cac67b62 100644 --- a/src/renderer/templates/nav-parsers.component.html +++ b/src/renderer/templates/nav-parsers.component.html @@ -14,64 +14,80 @@ -
- + unsaved: userConfiguration.current != null, + disabled: userConfiguration.saved.disabled, + }"> + @if(appSettings.theme=="EmuDeck") {
+ class="inputContainer" + (click)="emuClick(toggle)">
-
- {{ userConfigurations[i].saved.configTitle || lang.noTitle }} -
-
+

- - Non-EmuDeck + + Non-EmuDeck

- {{ - imageMap[userConfigurations[i].saved.parserId].details - }} + {{imageMap[parserId].details}}
+
+
+ } + @else { +
+
+
+ +
{{ userConfiguration.saved.configTitle || lang.noTitle }}
+
- -
+
+ } +
+ } + + diff --git a/src/renderer/templates/navarea.component.html b/src/renderer/templates/navarea.component.html index 9e8b2cf12d..e988558429 100644 --- a/src/renderer/templates/navarea.component.html +++ b/src/renderer/templates/navarea.component.html @@ -1,2 +1,2 @@ - + diff --git a/src/renderer/templates/parsers.component.html b/src/renderer/templates/parsers.component.html index b1f7385c94..90cc0b0aab 100644 --- a/src/renderer/templates/parsers.component.html +++ b/src/renderer/templates/parsers.component.html @@ -79,43 +79,6 @@ > -
- - - -
-
- - - -
{{ lang.buttons.faq }}
diff --git a/yarn.lock b/yarn.lock index d65236da72..b768640b3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -272,6 +272,11 @@ lodash "^4.17.15" tmp-promise "^3.0.2" +"@mattlewis92/dom-autoscroller@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@mattlewis92/dom-autoscroller/-/dom-autoscroller-2.4.2.tgz#ccb753fbcf6b3672b0273e0c3bda7924fe238013" + integrity sha512-YbrUWREPGEjE/FU6foXcAT1YbVwqD/jkYnY1dFb0o4AxtP3s4xKBthlELjndZih8uwsDWgQZx1eNskRNe2BgZQ== + "@node-steam/vdf@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@node-steam/vdf/-/vdf-2.2.0.tgz#94335f7e5c130a80ba4f16cf38239eca755c57db" @@ -865,6 +870,14 @@ ajv@^8.0.0, ajv@^8.17.1, ajv@^8.9.0: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" +angular-draggable-droppable@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/angular-draggable-droppable/-/angular-draggable-droppable-8.0.0.tgz#8adcdf7c6997e736ae496bae5a3b2a53d88c2b27" + integrity sha512-+gpSNBbygjV1pxTxsM3UPJKcXHXJabYoTtKcgQe74rGnb1umKc07XCBD1qDzvlG/kocthvhQ12qfYOYzHnE3ZA== + dependencies: + "@mattlewis92/dom-autoscroller" "^2.4.2" + tslib "^2.4.1" + angular2-template-loader@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/angular2-template-loader/-/angular2-template-loader-0.6.2.tgz#c0d44e90fff0fac95e8b23f043acda7fd1c51d7c" @@ -4999,6 +5012,11 @@ tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@^2.4.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b" + integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"