Skip to content

Commit a4f92d4

Browse files
committed
- Custom game actions can now be edited.
- Added additional profile options to Profile menu bar. - Fixed issue where incorrect game action was deleted when deleting an action. - Fixed issue where game actions were not being run from game binary directory. - Fixed game title ellipsis not working in profile selection dropdown. - All log messages now show up in GUI log. - Updated README.
1 parent cf2870f commit a4f92d4

File tree

9 files changed

+134
-28
lines changed

9 files changed

+134
-28
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ You can choose to override any of the profile paths to point to other directorie
131131

132132
By default, profiles are stored in the `profiles` directory of Starfield Mod Loader. Overriding this path will allow you to store the profile at an alternative location. This can be useful if your game is installed on a different drive and you want to co-locate the profile to the same drive as the game to enable Link mode.
133133

134+
A profile with an overridden root path is called an **external profile**. Existing external profiles can also be added or imported by selecting **Profiles -> Add External Profile** or **Profiles -> Import Profile**.
135+
134136
#### Profile mods path
135137

136138
Overriding this path will allow you to store the profile's mods at an alternative location.

src/app/models/app-message.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export namespace AppMessage {
153153
}
154154

155155
export interface CopyProfileData extends Base {
156-
id: `${Prefix}:copyProfileData`;
156+
id: `${Prefix}:copyProfile`;
157157
data: {
158158
srcProfile: AppProfile;
159159
destProfile: AppProfile;
@@ -614,7 +614,7 @@ export namespace AppMessage {
614614
"app:loadProfile",
615615
"app:loadExternalProfile",
616616
"app:saveProfile",
617-
"app:copyProfileData",
617+
"app:copyProfile",
618618
"app:verifyProfile",
619619
"app:showPreferences",
620620
"app:loadGameDatabase",

src/app/pages/mods-overview/mods-overview.page.html

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@
413413
</div>
414414
}
415415

416+
<!-- Log panel -->
416417
<div id="log-pane" class="mat-app-background" [attr.log-panel-active]="isLogPanelEnabled">
417418
<app-log />
418419
</div>
@@ -472,7 +473,7 @@
472473
</button>
473474

474475
<button mat-list-item (click)="exportActiveProfile(); showProfileMgmtMenuRef?.close()">
475-
<mat-icon matListIcon color="primary">person_remove</mat-icon>
476+
<mat-icon matListIcon color="accent">person_remove</mat-icon>
476477
<span matLine>Export Profile</span>
477478
</button>
478479

@@ -552,13 +553,20 @@
552553
@let gameActions = [profileManager.LAUNCH_GAME_ACTION].concat(activeProfile!.customGameActions ?? []);
553554

554555
@for (gameAction of gameActions; track $index) {
555-
<button mat-list-item (click)="profileManager.setActiveGameAction(gameAction); gameActionsMenuRef?.close()">
556+
<button mat-list-item class="game-action" (click)="profileManager.setActiveGameAction(gameAction); gameActionsMenuRef?.close()">
556557
<mat-icon matListIcon color="accent">play_circle</mat-icon>
557558

558559
<span class="game-action-name">{{ gameAction.name }}</span>
559560

560561
@if ($index > 0) {
561562
<button mat-icon-button
563+
matTooltip="Edit"
564+
(click)="editCustomGameActionByIndex($index - 1, gameAction); gameActionsMenuRef?.close(); $event.stopPropagation()">
565+
<mat-icon color="primary">edit</mat-icon>
566+
</button>
567+
568+
<button mat-icon-button
569+
matTooltip="Delete"
562570
(click)="removeCustomGameActionByIndex($index - 1); gameActionsMenuRef?.close(); $event.stopPropagation()">
563571
<mat-icon color="warn">delete</mat-icon>
564572
</button>

src/app/pages/mods-overview/mods-overview.page.scss

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,8 @@
346346
&#game-action-button {
347347
border-top-right-radius: 0;
348348
border-bottom-right-radius: 0;
349+
max-width: 30rem;
350+
overflow: hidden;
349351
}
350352

351353
&#game-action-menu-button {
@@ -429,6 +431,16 @@
429431
text-overflow: ellipsis;
430432
text-wrap: nowrap;
431433
overflow: hidden;
434+
display: inline-block;
435+
vertical-align: middle;
436+
align-content: center;
437+
}
438+
439+
@at-root .game-actions-popup {
440+
441+
mat-card-content, mat-action-list {
442+
padding: 0;
443+
}
432444
}
433445
}
434446

@@ -444,8 +456,12 @@
444456
}
445457
}
446458

447-
.game-action-name {
459+
.game-action {
448460
@at-root .game-actions-popup &, :host & {
449-
flex-grow: 1;
461+
462+
.game-action-name {
463+
flex-grow: 1;
464+
margin-right: 1rem;
465+
}
450466
}
451467
}

src/app/pages/mods-overview/mods-overview.page.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { AppDialogs } from "../../services/app-dialogs";
2020
import { ActiveProfileState } from "../../state/active-profile/active-profile.state";
2121
import { GamePluginProfileRef } from "../../models/game-plugin-profile-ref";
2222
import { LangUtils } from "../../util/lang-utils";
23+
import { GameAction } from "../../models/game-action";
2324

2425
@Component({
2526
selector: "app-mods-overview-page",
@@ -306,21 +307,11 @@ export class AppModsOverviewPage extends BasePage {
306307
}
307308

308309
protected exportActiveProfile(): void {
309-
this.dialogs.showDefault("Are you sure you want to export this profile?", [
310-
DialogManager.YES_ACTION,
311-
DialogManager.NO_ACTION_PRIMARY
312-
]).pipe(
313-
filterTrue()
314-
).subscribe(() => this.profileManager.exportProfile(this.activeProfile!));
310+
this.profileManager.exportProfileFromUser(this.activeProfile!);
315311
}
316312

317313
protected deleteActiveProfile(): void {
318-
this.dialogs.showDefault("Are you sure you want to delete this profile?", [
319-
DialogManager.YES_ACTION,
320-
DialogManager.NO_ACTION_PRIMARY
321-
]).pipe(
322-
filterTrue()
323-
).subscribe(() => this.profileManager.deleteProfile(this.activeProfile!));
314+
this.profileManager.deleteProfileFromUser(this.activeProfile!);
324315
}
325316

326317
protected addCustomGameAction(): void {
@@ -329,13 +320,19 @@ export class AppModsOverviewPage extends BasePage {
329320
).subscribe(gameAction => this.profileManager.addCustomGameAction(gameAction));
330321
}
331322

323+
protected editCustomGameActionByIndex(index: number, gameAction: GameAction): void {
324+
this.dialogs.showAddCustomGameActionDialog({ ...gameAction }).pipe(
325+
filterDefined()
326+
).subscribe(gameAction => this.profileManager.editCustomGameActionByIndex(index, gameAction));
327+
}
328+
332329
protected removeCustomGameActionByIndex(index: number): void {
333330
this.dialogs.showDefault("Are you sure you want to delete this action?", [
334331
DialogManager.YES_ACTION,
335332
DialogManager.NO_ACTION_PRIMARY
336333
]).pipe(
337334
filterTrue()
338-
).subscribe(() => this.profileManager.removeCustomGameActionByIndex(index - 1));
335+
).subscribe(() => this.profileManager.removeCustomGameActionByIndex(index));
339336
}
340337

341338
protected resolveBackupName(backupEntry: AppProfile.PluginBackupEntry): string {

src/app/services/profile-manager.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,33 @@ export class ProfileManager {
9292
))
9393
).subscribe();
9494

95+
messageHandler.messages$.pipe(
96+
filter(message => message.id === "app:copyProfile"),
97+
withLatestFrom(this.activeProfile$),
98+
filter(([, activeProfile]) => !!activeProfile),
99+
switchMap(([, activeProfile]) => this.copyProfileFromUser(activeProfile!).pipe(
100+
catchError((err) => (log.error("Failed to copy profile: ", err), EMPTY))
101+
))
102+
).subscribe();
103+
104+
messageHandler.messages$.pipe(
105+
filter(message => message.id === "app:exportProfile"),
106+
withLatestFrom(this.activeProfile$),
107+
filter(([, activeProfile]) => !!activeProfile),
108+
switchMap(([, activeProfile]) => this.exportProfileFromUser(activeProfile!).pipe(
109+
catchError((err) => (log.error("Failed to export profile: ", err), EMPTY))
110+
))
111+
).subscribe();
112+
113+
messageHandler.messages$.pipe(
114+
filter(message => message.id === "app:deleteProfile"),
115+
withLatestFrom(this.activeProfile$),
116+
filter(([, activeProfile]) => !!activeProfile),
117+
switchMap(([, activeProfile]) => this.deleteProfileFromUser(activeProfile!).pipe(
118+
catchError((err) => (log.error("Failed to delete profile: ", err), EMPTY))
119+
))
120+
).subscribe();
121+
95122
messageHandler.messages$.pipe(
96123
filter((message): message is AppMessage.BeginModAdd => message.id === "profile:beginModAdd"),
97124
withLatestFrom(this.activeProfile$),
@@ -533,6 +560,16 @@ export class ProfileManager {
533560
));
534561
}
535562

563+
public exportProfileFromUser(profile: AppProfile): Observable<any> {
564+
return runOnce(this.dialogs.showDefault("Are you sure you want to export this profile?", [
565+
DialogManager.YES_ACTION,
566+
DialogManager.NO_ACTION_PRIMARY
567+
]).pipe(
568+
filterTrue(),
569+
switchMap(() => this.exportProfile(profile))
570+
));
571+
}
572+
536573
public deleteProfile(profile: AppProfile): Observable<any> {
537574
const loadingIndicator = this.appManager.showLoadingIndicator("Deleting Profile...");
538575

@@ -542,6 +579,16 @@ export class ProfileManager {
542579
));
543580
}
544581

582+
public deleteProfileFromUser(profile: AppProfile): Observable<any> {
583+
return runOnce(this.dialogs.showDefault("Are you sure you want to delete this profile?", [
584+
DialogManager.YES_ACTION,
585+
DialogManager.NO_ACTION_PRIMARY
586+
]).pipe(
587+
filterTrue(),
588+
switchMap(() => this.deleteProfile(profile))
589+
));
590+
}
591+
545592
public importProfileFromUser(directImport: boolean = false): Observable<AppProfile | undefined> {
546593
// Load the external profile
547594
return runOnce(ElectronUtils.invoke("app:loadExternalProfile", {}).pipe(
@@ -585,7 +632,7 @@ export class ProfileManager {
585632
if (profileToCopy.name !== newProfile.rootPathOverride) {
586633
const loadingIndicator = this.appManager.showLoadingIndicator("Copying Profile...");
587634

588-
return ElectronUtils.invoke("app:copyProfileData", {
635+
return ElectronUtils.invoke("app:copyProfile", {
589636
srcProfile: profileToCopy,
590637
destProfile: newProfile
591638
}).pipe(
@@ -1028,6 +1075,10 @@ export class ProfileManager {
10281075
return this.store.dispatch(new ActiveProfileActions.AddCustomGameAction(gameAction));
10291076
}
10301077

1078+
public editCustomGameActionByIndex(index: number, gameAction: GameAction): Observable<unknown> {
1079+
return this.store.dispatch(new ActiveProfileActions.EditCustomGameAction(index, gameAction));
1080+
}
1081+
10311082
public removeCustomGameActionByIndex(index: number): Observable<unknown> {
10321083
return this.store.dispatch(new ActiveProfileActions.RemoveCustomGameAction(index));
10331084
}

src/app/state/active-profile/active-profile.actions.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ export namespace ActiveProfileActions {
137137
) {}
138138
}
139139

140+
export class EditCustomGameAction {
141+
public static readonly type = `[activeProfile] edit custom game action`;
142+
143+
constructor(
144+
public gameActionIndex: number,
145+
public gameAction: GameAction
146+
) {}
147+
}
148+
140149
export class RemoveCustomGameAction {
141150
public static readonly type = `[activeProfile] remove custom game action`;
142151

src/app/state/active-profile/active-profile.state.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,19 @@ export class ActiveProfileState {
328328
context.setState(state);
329329
}
330330

331+
@Action(ActiveProfileActions.EditCustomGameAction)
332+
public editCustomGameAction(context: ActiveProfileState.Context, { gameActionIndex, gameAction }: ActiveProfileActions.EditCustomGameAction): void {
333+
const state = _.cloneDeep(context.getState()!);
334+
335+
if (state.customGameActions && gameActionIndex < state.customGameActions.length) {
336+
state.customGameActions[gameActionIndex] = gameAction;
337+
} else {
338+
log.error("Tried to edit game action at invalid index", gameActionIndex);
339+
}
340+
341+
context.setState(state);
342+
}
343+
331344
@Action(ActiveProfileActions.RemoveCustomGameAction)
332345
public removeCustomGameAction(context: ActiveProfileState.Context, { gameActionIndex }: ActiveProfileActions.RemoveCustomGameAction): void {
333346
const state = _.cloneDeep(context.getState()!);

src/electron.js

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,9 @@ class ElectronLoader {
8787
constructor() {
8888
log.initialize();
8989

90-
if (DEBUG_MODE) {
91-
log.transports.console.level = "debug";
92-
}
93-
94-
log.transports.file.level = DEBUG_MODE ? "info" : "debug";
90+
log.transports.ipc.level = DEBUG_MODE ? "debug" : "info";
91+
log.transports.console.level = DEBUG_MODE ? "debug" : "info";
92+
log.transports.file.level = DEBUG_MODE ? "debug" : "info";
9593
log.transports.file.resolvePathFn = () => "app.log";
9694

9795
this.menu = this.createMenu();
@@ -331,9 +329,9 @@ class ElectronLoader {
331329
return this.deleteProfile(profile);
332330
});
333331

334-
ipcMain.handle("app:copyProfileData", async (
332+
ipcMain.handle("app:copyProfile", async (
335333
_event,
336-
/** @type {import("./app/models/app-message").AppMessageData<"app:copyProfileData">} */ { srcProfile, destProfile }
334+
/** @type {import("./app/models/app-message").AppMessageData<"app:copyProfile">} */ { srcProfile, destProfile }
337335
) => {
338336
function shouldCopyDir(srcPath, destPath) {
339337
return fs.existsSync(srcPath) && (!fs.existsSync(destPath) || fs.realpathSync(srcPath) !== fs.realpathSync(destPath));
@@ -812,7 +810,7 @@ class ElectronLoader {
812810

813811
// Run the action
814812
try {
815-
exec(gameActionCmd);
813+
exec(gameActionCmd, { cwd: profile.gameBinaryPath });
816814
} catch(error) {
817815
throw new Error(error.toString());
818816
}
@@ -1017,6 +1015,18 @@ class ElectronLoader {
10171015
label: "Import Profile",
10181016
click: () => this.mainWindow.webContents.send("app:importProfile")
10191017
},
1018+
{
1019+
label: "Copy Profile",
1020+
click: () => this.mainWindow.webContents.send("app:copyProfile")
1021+
},
1022+
{
1023+
label: "Export Profile",
1024+
click: () => this.mainWindow.webContents.send("app:exportProfile")
1025+
},
1026+
{
1027+
label: "Delete Profile",
1028+
click: () => this.mainWindow.webContents.send("app:deleteProfile")
1029+
},
10201030
{
10211031
type: "separator"
10221032
},

0 commit comments

Comments
 (0)