diff --git a/modules/bindings/src/lib/servo/servo-binding-edit.component.ts b/modules/bindings/src/lib/servo/servo-binding-edit.component.ts
index 3c82761c..3ff39447 100644
--- a/modules/bindings/src/lib/servo/servo-binding-edit.component.ts
+++ b/modules/bindings/src/lib/servo/servo-binding-edit.component.ts
@@ -15,11 +15,12 @@ import { ControlSchemeBindingType, ValidationErrorsL10nMap, ValidationMessagesDi
import { HideOnSmallScreenDirective, ToggleControlComponent } from '@app/shared-components';
import {
ATTACHED_IO_PROPS_SELECTORS,
- CONTROL_SCHEME_ACTIONS,
CalibrationResult,
CalibrationResultType,
HubMotorPositionFacadeService,
InputPipeType,
+ OutOfRangeCalibrationError,
+ SHOW_NOTIFICATION_ACTIONS,
ServoBindingInputAction
} from '@app/store';
import { BindingControlSelectHubComponent, BindingControlSelectIoComponent, MotorPositionAdjustmentComponent } from '@app/shared-control-schemes';
@@ -224,7 +225,10 @@ export class ServoBindingEditComponent implements IBindingsDetailsEditComponent<
this._form.updateValueAndValidity();
}
if (result.type === CalibrationResultType.error) {
- this.store.dispatch(CONTROL_SCHEME_ACTIONS.servoCalibrationError({ error: result.error }));
+ const errorL10nKey = result.error instanceof OutOfRangeCalibrationError
+ ? 'controlScheme.servoBinding.calibrationOutOfRangeError'
+ : 'controlScheme.servoBinding.calibrationError';
+ this.store.dispatch(SHOW_NOTIFICATION_ACTIONS.error({ l10nKey: errorL10nKey }));
}
});
}
diff --git a/modules/shared/components/src/lib/app-updated-notification/app-updated-notification.component.html b/modules/shared/components/src/lib/app-updated-notification/app-updated-notification.component.html
index 560a2ec2..219eb832 100644
--- a/modules/shared/components/src/lib/app-updated-notification/app-updated-notification.component.html
+++ b/modules/shared/components/src/lib/app-updated-notification/app-updated-notification.component.html
@@ -12,6 +12,6 @@
matSnackBarAction
(click)="onDismiss()"
>
- {{ 'appUpdatedNotification.dismissButton' | transloco }}
+ {{ 'common.dismissNotification' | transloco }}
diff --git a/modules/shared/components/src/lib/error-notification/error-notification.component.html b/modules/shared/components/src/lib/error-notification/error-notification.component.html
new file mode 100644
index 00000000..ca204cda
--- /dev/null
+++ b/modules/shared/components/src/lib/error-notification/error-notification.component.html
@@ -0,0 +1,18 @@
+
+
+
+ {{ error$ | async }}
+
+
+
+
+
+
diff --git a/modules/shared/components/src/lib/error-notification/error-notification.component.scss b/modules/shared/components/src/lib/error-notification/error-notification.component.scss
new file mode 100644
index 00000000..ca246664
--- /dev/null
+++ b/modules/shared/components/src/lib/error-notification/error-notification.component.scss
@@ -0,0 +1,10 @@
+:host {
+ display: flex;
+ align-items: center;
+}
+
+.message {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
diff --git a/modules/shared/components/src/lib/error-notification/error-notification.component.ts b/modules/shared/components/src/lib/error-notification/error-notification.component.ts
new file mode 100644
index 00000000..11b953c1
--- /dev/null
+++ b/modules/shared/components/src/lib/error-notification/error-notification.component.ts
@@ -0,0 +1,37 @@
+import { ChangeDetectionStrategy, Component, Inject, inject } from '@angular/core';
+import { MatButton } from '@angular/material/button';
+import { MAT_SNACK_BAR_DATA, MatSnackBarAction, MatSnackBarActions, MatSnackBarLabel, MatSnackBarRef } from '@angular/material/snack-bar';
+import { TranslocoPipe } from '@ngneat/transloco';
+import { Observable } from 'rxjs';
+import { AsyncPipe } from '@angular/common';
+import { MatIcon } from '@angular/material/icon';
+
+@Component({
+ standalone: true,
+ selector: 'lib-error-notification',
+ templateUrl: './error-notification.component.html',
+ styleUrls: [ './error-notification.component.scss' ],
+ imports: [
+ MatButton,
+ MatSnackBarAction,
+ MatSnackBarActions,
+ MatSnackBarLabel,
+ TranslocoPipe,
+ AsyncPipe,
+ MatIcon
+ ],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ErrorNotificationComponent {
+ public readonly snackBarRef = inject(MatSnackBarRef);
+
+ constructor(
+ @Inject(MAT_SNACK_BAR_DATA) public readonly error$: Observable
+ ) {
+ }
+
+
+ public onDismiss(): void {
+ this.snackBarRef.dismiss();
+ }
+}
diff --git a/modules/shared/components/src/lib/error-notification/index.ts b/modules/shared/components/src/lib/error-notification/index.ts
new file mode 100644
index 00000000..c2b7f21e
--- /dev/null
+++ b/modules/shared/components/src/lib/error-notification/index.ts
@@ -0,0 +1 @@
+export * from './error-notification.component';
diff --git a/modules/shared/components/src/lib/index.ts b/modules/shared/components/src/lib/index.ts
index 20fdb063..ab1b1dea 100644
--- a/modules/shared/components/src/lib/index.ts
+++ b/modules/shared/components/src/lib/index.ts
@@ -23,3 +23,5 @@ export * from './motor-position-adjustment-controls';
export * from './battery-indicator';
export * from './tilt-gauge';
export * from './app-updated-notification';
+export * from './info-notification';
+export * from './error-notification';
diff --git a/modules/shared/components/src/lib/info-notification/index.ts b/modules/shared/components/src/lib/info-notification/index.ts
new file mode 100644
index 00000000..39d7d4ca
--- /dev/null
+++ b/modules/shared/components/src/lib/info-notification/index.ts
@@ -0,0 +1 @@
+export * from './info-notification.component';
diff --git a/modules/shared/components/src/lib/info-notification/info-notification.component.html b/modules/shared/components/src/lib/info-notification/info-notification.component.html
new file mode 100644
index 00000000..110738d1
--- /dev/null
+++ b/modules/shared/components/src/lib/info-notification/info-notification.component.html
@@ -0,0 +1,14 @@
+
+
+ {{ caption$ | async }}
+
+
+
+
+
+
diff --git a/modules/shared/components/src/lib/info-notification/info-notification.component.scss b/modules/shared/components/src/lib/info-notification/info-notification.component.scss
new file mode 100644
index 00000000..a61d6751
--- /dev/null
+++ b/modules/shared/components/src/lib/info-notification/info-notification.component.scss
@@ -0,0 +1,10 @@
+:host {
+ display: flex;
+ align-items: center;
+}
+
+.message {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+}
diff --git a/modules/shared/components/src/lib/info-notification/info-notification.component.ts b/modules/shared/components/src/lib/info-notification/info-notification.component.ts
new file mode 100644
index 00000000..f6aed274
--- /dev/null
+++ b/modules/shared/components/src/lib/info-notification/info-notification.component.ts
@@ -0,0 +1,35 @@
+import { ChangeDetectionStrategy, Component, Inject, inject } from '@angular/core';
+import { MatButton } from '@angular/material/button';
+import { MAT_SNACK_BAR_DATA, MatSnackBarAction, MatSnackBarActions, MatSnackBarLabel, MatSnackBarRef } from '@angular/material/snack-bar';
+import { TranslocoPipe } from '@ngneat/transloco';
+import { Observable } from 'rxjs';
+import { AsyncPipe } from '@angular/common';
+
+@Component({
+ standalone: true,
+ selector: 'lib-info-notification',
+ templateUrl: './info-notification.component.html',
+ styleUrls: [ './info-notification.component.scss' ],
+ imports: [
+ MatButton,
+ MatSnackBarAction,
+ MatSnackBarActions,
+ MatSnackBarLabel,
+ TranslocoPipe,
+ AsyncPipe
+ ],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class InfoNotificationComponent {
+ public readonly snackBarRef = inject(MatSnackBarRef);
+
+ constructor(
+ @Inject(MAT_SNACK_BAR_DATA) public readonly caption$: Observable
+ ) {
+ }
+
+
+ public onDismiss(): void {
+ this.snackBarRef.dismiss();
+ }
+}
diff --git a/modules/store/src/lib/actions/common.actions.ts b/modules/store/src/lib/actions/common.actions.ts
index 4e996cc0..a1004378 100644
--- a/modules/store/src/lib/actions/common.actions.ts
+++ b/modules/store/src/lib/actions/common.actions.ts
@@ -4,8 +4,6 @@ export const COMMON_ACTIONS = createActionGroup({
source: 'Common',
events: {
copyToClipboard: props<{ content: string }>(),
- copyToClipboardSuccess: emptyProps(),
- copyToClipboardFailure: emptyProps(),
appReady: emptyProps(),
}
});
diff --git a/modules/store/src/lib/actions/index.ts b/modules/store/src/lib/actions/index.ts
index c2e7bbce..5b3a6fa2 100644
--- a/modules/store/src/lib/actions/index.ts
+++ b/modules/store/src/lib/actions/index.ts
@@ -14,3 +14,4 @@ export * from './control-scheme-widgets-data.actions';
export * from './settings.actions';
export * from './common.actions';
export * from './app-update.actions';
+export * from './show-notification.actions';
diff --git a/modules/store/src/lib/actions/show-notification.actions.ts b/modules/store/src/lib/actions/show-notification.actions.ts
new file mode 100644
index 00000000..7c56a00a
--- /dev/null
+++ b/modules/store/src/lib/actions/show-notification.actions.ts
@@ -0,0 +1,11 @@
+import { createActionGroup, emptyProps, props } from '@ngrx/store';
+
+export const SHOW_NOTIFICATION_ACTIONS = createActionGroup({
+ source: 'Show Notification',
+ events: {
+ error: props<{ l10nKey: string; l10nPayload?: object }>(),
+ genericError: props<{ error: Error }>(),
+ info: props<{ l10nKey: string; l10nPayload?: object }>(),
+ appUpdated: emptyProps()
+ }
+});
diff --git a/modules/store/src/lib/effects/app-update.effects.ts b/modules/store/src/lib/effects/app-update.effects.ts
index 62a50f52..a9280846 100644
--- a/modules/store/src/lib/effects/app-update.effects.ts
+++ b/modules/store/src/lib/effects/app-update.effects.ts
@@ -6,7 +6,7 @@ import { filter, map, switchMap } from 'rxjs';
// eslint-disable-next-line @nx/enforce-module-boundaries
import packageJson from '../../../../../package.json';
import { IState } from '../i-state';
-import { APP_UPDATE_ACTIONS, COMMON_ACTIONS } from '../actions';
+import { APP_UPDATE_ACTIONS, COMMON_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../actions';
export const APP_UPDATE_EFFECTS: {[k in string]: FunctionalEffect} = {
detectAppUpdate: createEffect((
@@ -24,5 +24,13 @@ export const APP_UPDATE_EFFECTS: {[k in string]: FunctionalEffect} = {
filter(({ prev, current }) => prev !== current),
map(({prev, current}) => APP_UPDATE_ACTIONS.appUpdated({prev, current}))
);
+ }, { functional: true }),
+ notifyOnAppUpdate: createEffect((
+ actions$: Actions = inject(Actions)
+ ) => {
+ return actions$.pipe(
+ ofType(APP_UPDATE_ACTIONS.appUpdated),
+ map(() => SHOW_NOTIFICATION_ACTIONS.appUpdated())
+ );
}, { functional: true })
};
diff --git a/modules/store/src/lib/effects/common.effects.ts b/modules/store/src/lib/effects/common.effects.ts
index 762dff85..1ecdd520 100644
--- a/modules/store/src/lib/effects/common.effects.ts
+++ b/modules/store/src/lib/effects/common.effects.ts
@@ -4,7 +4,7 @@ import { filter, pairwise, switchMap } from 'rxjs';
import { Store } from '@ngrx/store';
import { NAVIGATOR, WakeLockService } from '@app/shared-misc';
-import { COMMON_ACTIONS } from '../actions';
+import { COMMON_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../actions';
import { HUB_RUNTIME_DATA_SELECTORS } from '../selectors';
const COPY_TO_CLIPBOARD_EFFECT = createEffect((
@@ -16,9 +16,13 @@ const COPY_TO_CLIPBOARD_EFFECT = createEffect((
switchMap(async (action) => {
try {
await navigator.clipboard.writeText(action.content);
- return COMMON_ACTIONS.copyToClipboardSuccess();
+ return SHOW_NOTIFICATION_ACTIONS.info({
+ l10nKey: 'common.copyToClipboardSuccessNotification'
+ });
} catch (e) {
- return COMMON_ACTIONS.copyToClipboardFailure();
+ return SHOW_NOTIFICATION_ACTIONS.error({
+ l10nKey: 'common.copyToClipboardErrorNotification'
+ });
}
})
);
diff --git a/modules/store/src/lib/effects/tasks-processing/compose-tasks.effect.ts b/modules/store/src/lib/effects/control-scheme/compose-tasks.effect.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/compose-tasks.effect.ts
rename to modules/store/src/lib/effects/control-scheme/compose-tasks.effect.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/consume-queue.effect.ts b/modules/store/src/lib/effects/control-scheme/consume-queue.effect.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/consume-queue.effect.ts
rename to modules/store/src/lib/effects/control-scheme/consume-queue.effect.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/execute-task.effect.ts b/modules/store/src/lib/effects/control-scheme/execute-task.effect.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/execute-task.effect.ts
rename to modules/store/src/lib/effects/control-scheme/execute-task.effect.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/i-task-factory.ts b/modules/store/src/lib/effects/control-scheme/i-task-factory.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/i-task-factory.ts
rename to modules/store/src/lib/effects/control-scheme/i-task-factory.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/i-task-filter.ts b/modules/store/src/lib/effects/control-scheme/i-task-filter.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/i-task-filter.ts
rename to modules/store/src/lib/effects/control-scheme/i-task-filter.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/i-task-input-extractor.ts b/modules/store/src/lib/effects/control-scheme/i-task-input-extractor.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/i-task-input-extractor.ts
rename to modules/store/src/lib/effects/control-scheme/i-task-input-extractor.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/i-task-runner.ts b/modules/store/src/lib/effects/control-scheme/i-task-runner.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/i-task-runner.ts
rename to modules/store/src/lib/effects/control-scheme/i-task-runner.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/index.ts b/modules/store/src/lib/effects/control-scheme/index.ts
similarity index 64%
rename from modules/store/src/lib/effects/tasks-processing/index.ts
rename to modules/store/src/lib/effects/control-scheme/index.ts
index 65a1894f..9d79259a 100644
--- a/modules/store/src/lib/effects/tasks-processing/index.ts
+++ b/modules/store/src/lib/effects/control-scheme/index.ts
@@ -6,6 +6,8 @@ import { STOP_SCHEME_ON_HUB_DISCONNECT_EFFECT } from './stop-scheme-on-hub-disco
import { STOP_SCHEME_EFFECT } from './stop-scheme.effect';
import { CONSUME_QUEUE_EFFECT } from './consume-queue.effect';
import { EXECUTE_TASK_EFFECT } from './execute-task.effect';
+import { NOTIFY_ON_CONTROL_SCHEME_IMPORTED_EFFECT } from './notify-on-control-scheme-imported.effect';
+import { NOTIFY_ON_CONTROL_SCHEME_START_FAILURE_EFFECT } from './notify-on-control-scheme-start-failure.effect';
export * from './i-task-filter';
export * from './i-task-runner';
@@ -13,11 +15,13 @@ export * from './i-task-factory';
export * from './i-task-input-extractor';
export { IWidgetsReadTasksFactory, WIDGET_READ_TASKS_FACTORY } from './scheme-pre-run';
-export const TASK_PROCESSING_EFFECTS: Record = {
+export const CONTROL_SCHEME_EFFECTS: {[name in string]: FunctionalEffect} = {
preRunScheme: PRE_RUN_SCHEME_EFFECT,
composeTasks: COMPOSE_TASKS_EFFECT,
stopSchemeOnHubDisconnect: STOP_SCHEME_ON_HUB_DISCONNECT_EFFECT,
stopScheme: STOP_SCHEME_EFFECT,
consumeQueue: CONSUME_QUEUE_EFFECT,
- executeTask: EXECUTE_TASK_EFFECT
-};
+ executeTask: EXECUTE_TASK_EFFECT,
+ notifyOnControlSchemeImported: NOTIFY_ON_CONTROL_SCHEME_IMPORTED_EFFECT,
+ notifyOnControlSchemeStartFailure: NOTIFY_ON_CONTROL_SCHEME_START_FAILURE_EFFECT,
+} as const;
diff --git a/modules/store/src/lib/effects/control-scheme/notify-on-control-scheme-imported.effect.ts b/modules/store/src/lib/effects/control-scheme/notify-on-control-scheme-imported.effect.ts
new file mode 100644
index 00000000..26a17a6d
--- /dev/null
+++ b/modules/store/src/lib/effects/control-scheme/notify-on-control-scheme-imported.effect.ts
@@ -0,0 +1,17 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { map } from 'rxjs';
+
+import { CONTROL_SCHEME_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../actions';
+
+export const NOTIFY_ON_CONTROL_SCHEME_IMPORTED_EFFECT = createEffect((
+ actions: Actions = inject(Actions)
+) => {
+ return actions.pipe(
+ ofType(CONTROL_SCHEME_ACTIONS.importControlScheme),
+ map((action) => SHOW_NOTIFICATION_ACTIONS.info({
+ l10nKey: 'controlScheme.importSuccessNotification',
+ l10nPayload: action.scheme
+ }))
+ );
+}, { functional: true });
diff --git a/modules/store/src/lib/effects/control-scheme/notify-on-control-scheme-start-failure.effect.ts b/modules/store/src/lib/effects/control-scheme/notify-on-control-scheme-start-failure.effect.ts
new file mode 100644
index 00000000..ef1474b3
--- /dev/null
+++ b/modules/store/src/lib/effects/control-scheme/notify-on-control-scheme-start-failure.effect.ts
@@ -0,0 +1,23 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { map } from 'rxjs';
+
+import { CONTROL_SCHEME_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../actions';
+import { OutOfRangeCalibrationError } from '../../hub-facades';
+
+export const NOTIFY_ON_CONTROL_SCHEME_START_FAILURE_EFFECT = createEffect((
+ actions: Actions = inject(Actions),
+) => {
+ return actions.pipe(
+ ofType(CONTROL_SCHEME_ACTIONS.schemeStartFailed),
+ map((action) => {
+ const l10nKey = action.reason instanceof OutOfRangeCalibrationError
+ ? 'controlScheme.runFailedCalibrationOutOfRange'
+ : 'controlScheme.runFailed';
+ return SHOW_NOTIFICATION_ACTIONS.error({
+ l10nKey,
+ l10nPayload: action
+ });
+ })
+ );
+}, { functional: true });
diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-pre-run-motor-position-query-tasks.ts b/modules/store/src/lib/effects/control-scheme/scheme-pre-run/create-pre-run-motor-position-query-tasks.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-pre-run-motor-position-query-tasks.ts
rename to modules/store/src/lib/effects/control-scheme/scheme-pre-run/create-pre-run-motor-position-query-tasks.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-pre-run-servo-calibration-tasks.ts b/modules/store/src/lib/effects/control-scheme/scheme-pre-run/create-pre-run-servo-calibration-tasks.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-pre-run-servo-calibration-tasks.ts
rename to modules/store/src/lib/effects/control-scheme/scheme-pre-run/create-pre-run-servo-calibration-tasks.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-pre-run-set-acceleration-profile-tasks.ts b/modules/store/src/lib/effects/control-scheme/scheme-pre-run/create-pre-run-set-acceleration-profile-tasks.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-pre-run-set-acceleration-profile-tasks.ts
rename to modules/store/src/lib/effects/control-scheme/scheme-pre-run/create-pre-run-set-acceleration-profile-tasks.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-pre-run-set-deceleration-profile-tasks.ts b/modules/store/src/lib/effects/control-scheme/scheme-pre-run/create-pre-run-set-deceleration-profile-tasks.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-pre-run-set-deceleration-profile-tasks.ts
rename to modules/store/src/lib/effects/control-scheme/scheme-pre-run/create-pre-run-set-deceleration-profile-tasks.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-widget-read-tasks.ts b/modules/store/src/lib/effects/control-scheme/scheme-pre-run/create-widget-read-tasks.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/scheme-pre-run/create-widget-read-tasks.ts
rename to modules/store/src/lib/effects/control-scheme/scheme-pre-run/create-widget-read-tasks.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/i-widgets-read-tasks-factory.ts b/modules/store/src/lib/effects/control-scheme/scheme-pre-run/i-widgets-read-tasks-factory.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/scheme-pre-run/i-widgets-read-tasks-factory.ts
rename to modules/store/src/lib/effects/control-scheme/scheme-pre-run/i-widgets-read-tasks-factory.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/index.ts b/modules/store/src/lib/effects/control-scheme/scheme-pre-run/index.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/scheme-pre-run/index.ts
rename to modules/store/src/lib/effects/control-scheme/scheme-pre-run/index.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/pre-run-scheme.effect.ts b/modules/store/src/lib/effects/control-scheme/scheme-pre-run/pre-run-scheme.effect.ts
similarity index 99%
rename from modules/store/src/lib/effects/tasks-processing/scheme-pre-run/pre-run-scheme.effect.ts
rename to modules/store/src/lib/effects/control-scheme/scheme-pre-run/pre-run-scheme.effect.ts
index bb02ba87..a64df96b 100644
--- a/modules/store/src/lib/effects/tasks-processing/scheme-pre-run/pre-run-scheme.effect.ts
+++ b/modules/store/src/lib/effects/control-scheme/scheme-pre-run/pre-run-scheme.effect.ts
@@ -65,7 +65,7 @@ export const PRE_RUN_SCHEME_EFFECT = createEffect((
...createPreRunMotorPositionQueryTasks(scheme, hubStorage, store),
...createPreRunSetAccelerationProfileTasks(scheme, hubStorage),
...createPreRunSetDecelerationProfileTasks(scheme, hubStorage),
- ...createWidgetReadTasks(scheme, store, widgetReadTaskFactory),
+ ...createWidgetReadTasks(scheme, store, widgetReadTaskFactory)
]),
timeout(appConfig.schemeStartStopTimeoutMs),
map(() => CONTROL_SCHEME_ACTIONS.schemeStarted({ name: scheme.name })),
diff --git a/modules/store/src/lib/effects/tasks-processing/stop-scheme-on-hub-disconnect.effect.ts b/modules/store/src/lib/effects/control-scheme/stop-scheme-on-hub-disconnect.effect.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/stop-scheme-on-hub-disconnect.effect.ts
rename to modules/store/src/lib/effects/control-scheme/stop-scheme-on-hub-disconnect.effect.ts
diff --git a/modules/store/src/lib/effects/tasks-processing/stop-scheme.effect.ts b/modules/store/src/lib/effects/control-scheme/stop-scheme.effect.ts
similarity index 100%
rename from modules/store/src/lib/effects/tasks-processing/stop-scheme.effect.ts
rename to modules/store/src/lib/effects/control-scheme/stop-scheme.effect.ts
diff --git a/modules/store/src/lib/effects/controllers/capture-input/index.ts b/modules/store/src/lib/effects/controllers/capture-input/index.ts
index e4058a71..998959e3 100644
--- a/modules/store/src/lib/effects/controllers/capture-input/index.ts
+++ b/modules/store/src/lib/effects/controllers/capture-input/index.ts
@@ -1,4 +1,11 @@
-export * from './capture-gamepad-input.effect';
-export * from './capture-keyboard-input.effect';
-export * from './capture-hub-green-button-input.effect';
-export * from './capture-hub-button-groups-input.effect';
+import { CAPTURE_GAMEPAD_INPUT } from './capture-gamepad-input.effect';
+import { CAPTURE_KEYBOARD_INPUT } from './capture-keyboard-input.effect';
+import { CAPTURE_HUB_GREEN_BUTTON_INPUT } from './capture-hub-green-button-input.effect';
+import { CAPTURE_HUB_BUTTON_GROUPS_INPUT } from './capture-hub-button-groups-input.effect';
+
+export const CONTROLLER_CAPTURE_INPUT_EFFECTS = {
+ gamepadInput: CAPTURE_GAMEPAD_INPUT,
+ keyboardInput: CAPTURE_KEYBOARD_INPUT,
+ hubGreenButtonInput: CAPTURE_HUB_GREEN_BUTTON_INPUT,
+ hubButtonGroupsInput: CAPTURE_HUB_BUTTON_GROUPS_INPUT,
+} as const;
diff --git a/modules/store/src/lib/effects/controllers/index.ts b/modules/store/src/lib/effects/controllers/index.ts
index c58d1ae9..bbd95e4d 100644
--- a/modules/store/src/lib/effects/controllers/index.ts
+++ b/modules/store/src/lib/effects/controllers/index.ts
@@ -1,17 +1,11 @@
import { FunctionalEffect } from '@ngrx/effects';
-import { CAPTURE_GAMEPAD_INPUT, CAPTURE_HUB_BUTTON_GROUPS_INPUT, CAPTURE_HUB_GREEN_BUTTON_INPUT, CAPTURE_KEYBOARD_INPUT } from './capture-input';
-import { LISTEN_GAMEPAD_CONNECT, LISTEN_HUB_CONNECT, LISTEN_KEYBOARD_CONNECT } from './listen-connect';
-import { LISTEN_GAMEPAD_DISCONNECT, LISTEN_HUB_DISCONNECT } from './listen-disconnect';
+import { CONTROLLER_LISTEN_CONNECT_EFFECTS } from './listen-connect';
+import { CONTROLLER_LISTEN_DISCONNECT_EFFECTS } from './listen-disconnect';
+import { CONTROLLER_CAPTURE_INPUT_EFFECTS } from './capture-input';
export const CONTROLLER_EFFECTS: { [name: string]: FunctionalEffect } = {
- gamepadInput: CAPTURE_GAMEPAD_INPUT,
- gamepadConnect: LISTEN_GAMEPAD_CONNECT,
- gamepadDisconnect: LISTEN_GAMEPAD_DISCONNECT,
- keyboardConnect: LISTEN_KEYBOARD_CONNECT,
- keyboardInput: CAPTURE_KEYBOARD_INPUT,
- hubConnect: LISTEN_HUB_CONNECT,
- hubDisconnect: LISTEN_HUB_DISCONNECT,
- hubGreenButtonInput: CAPTURE_HUB_GREEN_BUTTON_INPUT,
- hubButtonGroupsInput: CAPTURE_HUB_BUTTON_GROUPS_INPUT
-};
+ ...CONTROLLER_CAPTURE_INPUT_EFFECTS,
+ ...CONTROLLER_LISTEN_CONNECT_EFFECTS,
+ ...CONTROLLER_LISTEN_DISCONNECT_EFFECTS
+} as const;
diff --git a/modules/store/src/lib/effects/controllers/listen-connect/index.ts b/modules/store/src/lib/effects/controllers/listen-connect/index.ts
index 5df6f58f..413ca247 100644
--- a/modules/store/src/lib/effects/controllers/listen-connect/index.ts
+++ b/modules/store/src/lib/effects/controllers/listen-connect/index.ts
@@ -1,3 +1,19 @@
-export * from './listen-gamepad-connect.effect';
-export * from './listen-keyboard-connect.effect';
-export * from './listen-hub-connect.effect';
+import { FunctionalEffect } from '@ngrx/effects';
+
+import { LISTEN_HUB_CONNECT } from './listen-hub-connect.effect';
+import { LISTEN_KEYBOARD_CONNECT } from './listen-keyboard-connect.effect';
+import { LISTEN_GAMEPAD_CONNECT } from './listen-gamepad-connect.effect';
+import { NOTIFY_ON_HUB_KEYBOARD_DISCOVERED_EFFECT } from './notify-on-keyboard-discovered.effect';
+import { NOTIFY_ON_HUB_KEYBOARD_CONNECTED_EFFECT } from './notify-on-keyboard-connected.effect';
+import { NOTIFY_ON_GAMEPAD_CONNECTED_EFFECT } from './notify-on-gamepad-connected.effect';
+import { NOTIFY_ON_GAMEPAD_DISCOVERED_EFFECT } from './notify-on-gamepad-discovered.effect';
+
+export const CONTROLLER_LISTEN_CONNECT_EFFECTS: {[name: string]: FunctionalEffect} = {
+ gamepadConnect: LISTEN_GAMEPAD_CONNECT,
+ keyboardConnect: LISTEN_KEYBOARD_CONNECT,
+ hubConnect: LISTEN_HUB_CONNECT,
+ notifyOnHubKeyboardDiscovered: NOTIFY_ON_HUB_KEYBOARD_DISCOVERED_EFFECT,
+ notifyOnHubKeyboardConnected: NOTIFY_ON_HUB_KEYBOARD_CONNECTED_EFFECT,
+ notifyOnGamepadDiscovered: NOTIFY_ON_GAMEPAD_DISCOVERED_EFFECT,
+ notifyOnGamepadConnected: NOTIFY_ON_GAMEPAD_CONNECTED_EFFECT,
+} as const;
diff --git a/modules/store/src/lib/effects/controllers/listen-connect/notify-on-gamepad-connected.effect.ts b/modules/store/src/lib/effects/controllers/listen-connect/notify-on-gamepad-connected.effect.ts
new file mode 100644
index 00000000..5e98a1e0
--- /dev/null
+++ b/modules/store/src/lib/effects/controllers/listen-connect/notify-on-gamepad-connected.effect.ts
@@ -0,0 +1,26 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { map, mergeMap, take } from 'rxjs';
+import { GamepadProfileFactoryService } from '@app/controller-profiles';
+
+import { CONTROLLERS_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../../actions';
+
+export const NOTIFY_ON_GAMEPAD_CONNECTED_EFFECT = createEffect((
+ actions: Actions = inject(Actions),
+ gamepadProfileFactoryService: GamepadProfileFactoryService = inject(GamepadProfileFactoryService),
+) => {
+ return actions.pipe(
+ ofType(CONTROLLERS_ACTIONS.gamepadConnected),
+ mergeMap((action) =>
+ gamepadProfileFactoryService.getByProfileUid(action.profileUid).name$.pipe(
+ take(1),
+ map((name) => SHOW_NOTIFICATION_ACTIONS.info({
+ l10nKey: 'controller.controllerConnectedNotification',
+ l10nPayload: { name }
+ }))
+ )
+ ),
+ );
+}, { functional: true });
+
+
diff --git a/modules/store/src/lib/effects/controllers/listen-connect/notify-on-gamepad-discovered.effect.ts b/modules/store/src/lib/effects/controllers/listen-connect/notify-on-gamepad-discovered.effect.ts
new file mode 100644
index 00000000..96d18953
--- /dev/null
+++ b/modules/store/src/lib/effects/controllers/listen-connect/notify-on-gamepad-discovered.effect.ts
@@ -0,0 +1,26 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { map, mergeMap, take } from 'rxjs';
+import { GamepadProfileFactoryService } from '@app/controller-profiles';
+
+import { CONTROLLERS_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../../actions';
+
+export const NOTIFY_ON_GAMEPAD_DISCOVERED_EFFECT = createEffect((
+ actions: Actions = inject(Actions),
+ gamepadProfileFactoryService: GamepadProfileFactoryService = inject(GamepadProfileFactoryService),
+) => {
+ return actions.pipe(
+ ofType(CONTROLLERS_ACTIONS.gamepadDiscovered),
+ mergeMap((action) =>
+ gamepadProfileFactoryService.getByProfileUid(action.profileUid).name$.pipe(
+ take(1),
+ map((name) => SHOW_NOTIFICATION_ACTIONS.info({
+ l10nKey: 'controller.controllerDiscoveredNotification',
+ l10nPayload: { name }
+ }))
+ )
+ ),
+ );
+}, { functional: true });
+
+
diff --git a/modules/store/src/lib/effects/controllers/listen-connect/notify-on-keyboard-connected.effect.ts b/modules/store/src/lib/effects/controllers/listen-connect/notify-on-keyboard-connected.effect.ts
new file mode 100644
index 00000000..9975eaed
--- /dev/null
+++ b/modules/store/src/lib/effects/controllers/listen-connect/notify-on-keyboard-connected.effect.ts
@@ -0,0 +1,24 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { map, mergeMap, take } from 'rxjs';
+import { KeyboardProfileFactoryService } from '@app/controller-profiles';
+
+import { CONTROLLERS_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../../actions';
+
+export const NOTIFY_ON_HUB_KEYBOARD_CONNECTED_EFFECT = createEffect((
+ actions: Actions = inject(Actions),
+ keyboardProfileFactoryService: KeyboardProfileFactoryService = inject(KeyboardProfileFactoryService),
+) => {
+ return actions.pipe(
+ ofType(CONTROLLERS_ACTIONS.keyboardConnected),
+ mergeMap(() => keyboardProfileFactoryService.getKeyboardProfile().name$.pipe(
+ take(1),
+ map((name) => SHOW_NOTIFICATION_ACTIONS.info({
+ l10nKey: 'controller.controllerConnectedNotification',
+ l10nPayload: { name }
+ }))
+ )),
+ );
+}, { functional: true });
+
+
diff --git a/modules/store/src/lib/effects/controllers/listen-connect/notify-on-keyboard-discovered.effect.ts b/modules/store/src/lib/effects/controllers/listen-connect/notify-on-keyboard-discovered.effect.ts
new file mode 100644
index 00000000..d7b5399d
--- /dev/null
+++ b/modules/store/src/lib/effects/controllers/listen-connect/notify-on-keyboard-discovered.effect.ts
@@ -0,0 +1,24 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { map, mergeMap, take } from 'rxjs';
+import { KeyboardProfileFactoryService } from '@app/controller-profiles';
+
+import { CONTROLLERS_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../../actions';
+
+export const NOTIFY_ON_HUB_KEYBOARD_DISCOVERED_EFFECT = createEffect((
+ actions: Actions = inject(Actions),
+ keyboardProfileFactoryService: KeyboardProfileFactoryService = inject(KeyboardProfileFactoryService),
+) => {
+ return actions.pipe(
+ ofType(CONTROLLERS_ACTIONS.keyboardDiscovered),
+ mergeMap(() => keyboardProfileFactoryService.getKeyboardProfile().name$.pipe(
+ take(1),
+ map((name) => SHOW_NOTIFICATION_ACTIONS.info({
+ l10nKey: 'controller.controllerDiscoveredNotification',
+ l10nPayload: { name }
+ }))
+ )),
+ );
+}, { functional: true });
+
+
diff --git a/modules/store/src/lib/effects/controllers/listen-disconnect/index.ts b/modules/store/src/lib/effects/controllers/listen-disconnect/index.ts
index d2416f20..09ff533f 100644
--- a/modules/store/src/lib/effects/controllers/listen-disconnect/index.ts
+++ b/modules/store/src/lib/effects/controllers/listen-disconnect/index.ts
@@ -1,2 +1,9 @@
-export * from './listen-gamepad-disconnect.effect';
-export * from './listen-hub-disconnect.effect';
+import { LISTEN_GAMEPAD_DISCONNECT } from './listen-gamepad-disconnect.effect';
+import { LISTEN_HUB_DISCONNECT } from './listen-hub-disconnect.effect';
+import { NOTIFY_ON_GAMEPAD_DISCONNECTED_EFFECT } from './notify-on-gamepad-disconnected.effect';
+
+export const CONTROLLER_LISTEN_DISCONNECT_EFFECTS = {
+ gamepadDisconnect: LISTEN_GAMEPAD_DISCONNECT,
+ hubDisconnect: LISTEN_HUB_DISCONNECT,
+ notifyOnGamepadDisconnected: NOTIFY_ON_GAMEPAD_DISCONNECTED_EFFECT,
+} as const;
diff --git a/modules/store/src/lib/effects/controllers/listen-disconnect/notify-on-gamepad-disconnected.effect.ts b/modules/store/src/lib/effects/controllers/listen-disconnect/notify-on-gamepad-disconnected.effect.ts
new file mode 100644
index 00000000..5e579d30
--- /dev/null
+++ b/modules/store/src/lib/effects/controllers/listen-disconnect/notify-on-gamepad-disconnected.effect.ts
@@ -0,0 +1,32 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { filter, map, mergeMap, switchMap, take } from 'rxjs';
+import { Store } from '@ngrx/store';
+import { GamepadProfileFactoryService } from '@app/controller-profiles';
+
+import { CONTROLLERS_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../../actions';
+import { ControllerModel } from '../../../models';
+import { CONTROLLER_SELECTORS } from '../../../selectors';
+
+export const NOTIFY_ON_GAMEPAD_DISCONNECTED_EFFECT = createEffect((
+ actions: Actions = inject(Actions),
+ store: Store = inject(Store),
+ gamepadProfileFactoryService: GamepadProfileFactoryService = inject(GamepadProfileFactoryService),
+) => {
+ return actions.pipe(
+ ofType(CONTROLLERS_ACTIONS.gamepadDisconnected),
+ mergeMap((action) =>
+ store.select(CONTROLLER_SELECTORS.selectById(action.id)).pipe(
+ filter((controller): controller is ControllerModel => !!controller),
+ switchMap((controller) => gamepadProfileFactoryService.getByProfileUid(controller.profileUid).name$),
+ take(1),
+ map((name) => SHOW_NOTIFICATION_ACTIONS.info({
+ l10nKey: 'controller.controllerDisconnectedNotification',
+ l10nPayload: { name }
+ }))
+ )
+ ),
+ );
+}, { functional: true });
+
+
diff --git a/modules/store/src/lib/effects/hub/index.ts b/modules/store/src/lib/effects/hub/index.ts
index 8dc982da..a84d5cc9 100644
--- a/modules/store/src/lib/effects/hub/index.ts
+++ b/modules/store/src/lib/effects/hub/index.ts
@@ -13,6 +13,10 @@ import { REQUEST_PORT_ABSOLUTE_POSITION_EFFECT } from './request-port-absolute-p
import { SET_MOTOR_POSITION_EFFECT } from './set-motor-position.effect';
import { REQUEST_HUB_FIRMWARE_VERSION_ON_CONNECT } from './request-hub-firmware-version-on-connect';
import { REQUEST_HUB_HARDWARE_VERSION_ON_CONNECT } from './request-hub-hardware-version-on-connect';
+import { NOTIFY_ON_HUB_CONNECT_FAILED_EFFECT } from './notify-on-hub-connect-failed.effect';
+import { NOTIFY_ON_HUB_CONNECTED_EFFECT } from './notify-on-hub-connected.effect';
+import { NOTIFY_ON_HUB_DISCONNECTED_EFFECT } from './notify-on-hub-disconnected.effect';
+import { NOTIFY_ON_HUB_SET_NAME_ERROR_EFFECT } from './notify-on-hub-name-set-error.effect';
export const HUB_EFFECTS: { [k in string]: FunctionalEffect } = {
discoverHub: DISCOVER_HUB_EFFECT,
@@ -28,4 +32,8 @@ export const HUB_EFFECTS: { [k in string]: FunctionalEffect } = {
requestPortPosition: REQUEST_PORT_POSITION_EFFECT,
requestPortAbsolutePosition: REQUEST_PORT_ABSOLUTE_POSITION_EFFECT,
setMotorPosition: SET_MOTOR_POSITION_EFFECT,
+ notifyOnHubConnectFailed: NOTIFY_ON_HUB_CONNECT_FAILED_EFFECT,
+ notifyOnHubConnected: NOTIFY_ON_HUB_CONNECTED_EFFECT,
+ notifyOnHubDisconnected: NOTIFY_ON_HUB_DISCONNECTED_EFFECT,
+ notifyOnHubSetNameError: NOTIFY_ON_HUB_SET_NAME_ERROR_EFFECT
} as const;
diff --git a/modules/store/src/lib/effects/hub/notify-on-hub-connect-failed.effect.ts b/modules/store/src/lib/effects/hub/notify-on-hub-connect-failed.effect.ts
new file mode 100644
index 00000000..d4057408
--- /dev/null
+++ b/modules/store/src/lib/effects/hub/notify-on-hub-connect-failed.effect.ts
@@ -0,0 +1,16 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { map } from 'rxjs';
+
+import { HUBS_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../actions';
+
+export const NOTIFY_ON_HUB_CONNECT_FAILED_EFFECT = createEffect((
+ actions: Actions = inject(Actions)
+) => {
+ return actions.pipe(
+ ofType(HUBS_ACTIONS.deviceConnectFailed),
+ map(() => SHOW_NOTIFICATION_ACTIONS.error({
+ l10nKey: 'hub.connectFailed'
+ }))
+ );
+}, { functional: true });
diff --git a/modules/store/src/lib/effects/hub/notify-on-hub-connected.effect.ts b/modules/store/src/lib/effects/hub/notify-on-hub-connected.effect.ts
new file mode 100644
index 00000000..c4d4444d
--- /dev/null
+++ b/modules/store/src/lib/effects/hub/notify-on-hub-connected.effect.ts
@@ -0,0 +1,17 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { map } from 'rxjs';
+
+import { HUBS_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../actions';
+
+export const NOTIFY_ON_HUB_CONNECTED_EFFECT = createEffect((
+ actions: Actions = inject(Actions)
+) => {
+ return actions.pipe(
+ ofType(HUBS_ACTIONS.connected),
+ map((action) => SHOW_NOTIFICATION_ACTIONS.info({
+ l10nKey: 'hub.connected',
+ l10nPayload: action
+ }))
+ );
+}, { functional: true });
diff --git a/modules/store/src/lib/effects/hub/notify-on-hub-disconnected.effect.ts b/modules/store/src/lib/effects/hub/notify-on-hub-disconnected.effect.ts
new file mode 100644
index 00000000..d91cbac9
--- /dev/null
+++ b/modules/store/src/lib/effects/hub/notify-on-hub-disconnected.effect.ts
@@ -0,0 +1,17 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { map } from 'rxjs';
+
+import { HUBS_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../actions';
+
+export const NOTIFY_ON_HUB_DISCONNECTED_EFFECT = createEffect((
+ actions: Actions = inject(Actions)
+) => {
+ return actions.pipe(
+ ofType(HUBS_ACTIONS.disconnected),
+ map((action) => SHOW_NOTIFICATION_ACTIONS.info({
+ l10nKey: 'hub.disconnected',
+ l10nPayload: action
+ }))
+ );
+}, { functional: true });
diff --git a/modules/store/src/lib/effects/hub/notify-on-hub-name-set-error.effect.ts b/modules/store/src/lib/effects/hub/notify-on-hub-name-set-error.effect.ts
new file mode 100644
index 00000000..5785a40d
--- /dev/null
+++ b/modules/store/src/lib/effects/hub/notify-on-hub-name-set-error.effect.ts
@@ -0,0 +1,16 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { map } from 'rxjs';
+
+import { HUBS_ACTIONS, SHOW_NOTIFICATION_ACTIONS } from '../../actions';
+
+export const NOTIFY_ON_HUB_SET_NAME_ERROR_EFFECT = createEffect((
+ actions: Actions = inject(Actions)
+) => {
+ return actions.pipe(
+ ofType(HUBS_ACTIONS.hubNameSetError),
+ map(() => SHOW_NOTIFICATION_ACTIONS.error({
+ l10nKey: 'hub.hubNameSetError'
+ }))
+ );
+}, { functional: true });
diff --git a/modules/store/src/lib/effects/index.ts b/modules/store/src/lib/effects/index.ts
index 507503fc..23ebe35c 100644
--- a/modules/store/src/lib/effects/index.ts
+++ b/modules/store/src/lib/effects/index.ts
@@ -1,11 +1,11 @@
export * from './controllers';
export * from './attached-ios.effects';
-export * from './tasks-processing';
+export * from './control-scheme';
export * from './hub-attached-ios-state.effects';
export * from './attached-io-modes.effects';
export * from './hub-port-mode-info-effects';
-export * from './notifications.effects';
export * from './hub';
export * from './settings';
export * from './common.effects';
export * from './app-update.effects';
+export * from './notifications';
diff --git a/modules/store/src/lib/effects/notifications.effects.ts b/modules/store/src/lib/effects/notifications.effects.ts
deleted file mode 100644
index 516a22d3..00000000
--- a/modules/store/src/lib/effects/notifications.effects.ts
+++ /dev/null
@@ -1,198 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
-import { Observable, OperatorFunction, filter, switchMap, tap } from 'rxjs';
-import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
-import { TranslocoService } from '@ngneat/transloco';
-import { Action, Store } from '@ngrx/store';
-import { GamepadProfileFactoryService, KeyboardProfileFactoryService } from '@app/controller-profiles';
-import { ScreenSizeObserverService } from '@app/shared-misc';
-import { AppUpdatedNotificationComponent } from '@app/shared-components';
-
-import { APP_UPDATE_ACTIONS, COMMON_ACTIONS, CONTROLLERS_ACTIONS, CONTROL_SCHEME_ACTIONS, HUBS_ACTIONS } from '../actions';
-import { CONTROLLER_SELECTORS } from '../selectors';
-import { ControllerModel } from '../models';
-import { ControllerProfilesFacadeService } from '../controller-profiles-facade.service';
-import { OutOfRangeCalibrationError } from '../hub-facades';
-
-@Injectable()
-export class NotificationsEffects {
- public readonly deviceConnectFailedNotification$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(HUBS_ACTIONS.deviceConnectFailed),
- tap((action) => {
- console.error(action.error);
- }),
- this.showMessage((action) => this.translocoService.selectTranslate('hub.connectFailed', action))
- );
- }, { dispatch: false });
-
- public readonly deviceConnectedNotification$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(HUBS_ACTIONS.connected),
- this.showMessage((action) => this.translocoService.selectTranslate('hub.connected', action))
- );
- }, { dispatch: false });
-
- public readonly deviceDisconnectedNotification$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(HUBS_ACTIONS.disconnected),
- this.showMessage((action) => this.translocoService.selectTranslate('hub.disconnected', action))
- );
- }, { dispatch: false });
-
- public readonly servoCalibrationErrorNotification$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(CONTROL_SCHEME_ACTIONS.servoCalibrationError),
- this.showMessage((action) => {
- return action.error instanceof OutOfRangeCalibrationError
- ? this.translocoService.selectTranslate('controlScheme.servoBinding.calibrationOutOfRangeError')
- : this.translocoService.selectTranslate('controlScheme.servoBinding.calibrationError');
- }, { duration: Number.MAX_SAFE_INTEGER })
- );
- }, { dispatch: false });
-
- public readonly controllerDiscovered$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(CONTROLLERS_ACTIONS.gamepadDiscovered, CONTROLLERS_ACTIONS.keyboardDiscovered),
- this.showMessage((action) => {
- switch (action.type) {
- case CONTROLLERS_ACTIONS.gamepadDiscovered.type:
- return this.gamepadProfileFactoryService.getByProfileUid(action.profileUid).name$.pipe(
- switchMap((name) => this.translocoService.selectTranslate('controller.controllerDiscoveredNotification', { name }))
- );
- case CONTROLLERS_ACTIONS.keyboardDiscovered.type:
- return this.keyboardProfileFactoryService.getByProfileUid(action.profileUid).name$.pipe(
- switchMap((name) => this.translocoService.selectTranslate('controller.controllerDiscoveredNotification', { name }))
- );
- }
- })
- );
- }, { dispatch: false });
-
- public readonly controllerConnected$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(CONTROLLERS_ACTIONS.gamepadConnected, CONTROLLERS_ACTIONS.keyboardConnected),
- this.showMessage((action) => {
- switch (action.type) {
- case CONTROLLERS_ACTIONS.gamepadConnected.type:
- return this.gamepadProfileFactoryService.getByProfileUid(action.profileUid).name$.pipe(
- switchMap((name) => this.translocoService.selectTranslate('controller.controllerConnectedNotification', { name }))
- );
- case CONTROLLERS_ACTIONS.keyboardConnected.type:
- return this.keyboardProfileFactoryService.getByProfileUid(action.profileUid).name$.pipe(
- switchMap((name) => this.translocoService.selectTranslate('controller.controllerConnectedNotification', { name }))
- );
- }
- })
- );
- }, { dispatch: false });
-
- public readonly controllerDisconnected$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(CONTROLLERS_ACTIONS.gamepadDisconnected),
- this.showMessage((action) => this.store.select(CONTROLLER_SELECTORS.selectById(action.id)).pipe(
- filter((controller): controller is ControllerModel => !!controller),
- switchMap((controller) => this.controllerProfilesFacade.getByControllerModel(controller).name$),
- switchMap((name) => this.translocoService.selectTranslate('controller.controllerDisconnectedNotification', { name }))
- ))
- );
- }, { dispatch: false });
-
- public readonly stringCopiedToClipboard$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(COMMON_ACTIONS.copyToClipboardSuccess),
- this.showMessage(() => this.translocoService.selectTranslate('common.copyToClipboardSuccessNotification')),
- );
- }, { dispatch: false });
-
- public readonly stringCopyToClipboardFailed$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(COMMON_ACTIONS.copyToClipboardFailure),
- this.showMessage(() => this.translocoService.selectTranslate('common.copyToClipboardErrorNotification')),
- );
- }, { dispatch: false });
-
- public readonly controlSchemeImported$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(CONTROL_SCHEME_ACTIONS.importControlScheme),
- this.showMessage((action) => this.translocoService.selectTranslate('controlScheme.importSuccessNotification', action.scheme)),
- );
- }, { dispatch: false });
-
- public readonly hubNameSetError$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(HUBS_ACTIONS.hubNameSetError),
- this.showMessage(() => this.translocoService.selectTranslate('hub.hubNameSetError')),
- );
- }, { dispatch: false });
-
- public readonly startSchemeFailed$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(CONTROL_SCHEME_ACTIONS.schemeStartFailed),
- this.showMessage((action) => {
- return action.reason instanceof OutOfRangeCalibrationError
- ? this.translocoService.selectTranslate('controlScheme.runFailedCalibrationOutOfRange')
- : this.translocoService.selectTranslate('controlScheme.runFailed');
- }, { duration: Number.MAX_SAFE_INTEGER }
- ),
- );
- }, { dispatch: false });
-
- public readonly displayAppUpdatedNotification$ = createEffect(() => {
- return this.actions$.pipe(
- ofType(APP_UPDATE_ACTIONS.appUpdated),
- this.showAppUpdatedNotification(),
- );
- }, { dispatch: false });
-
- constructor(
- private readonly actions$: Actions,
- private readonly snackBar: MatSnackBar,
- private readonly translocoService: TranslocoService,
- private readonly store: Store,
- private readonly screenSizeObserverService: ScreenSizeObserverService,
- private readonly controllerProfilesFacade: ControllerProfilesFacadeService,
- private readonly keyboardProfileFactoryService: KeyboardProfileFactoryService,
- private readonly gamepadProfileFactoryService: GamepadProfileFactoryService,
- ) {
- }
-
- private showMessage(
- fn: (action: T) => Observable,
- config: MatSnackBarConfig = {}
- ): OperatorFunction {
- return (source: Observable) => source.pipe(
- concatLatestFrom((action) => [
- fn(action),
- this.screenSizeObserverService.isSmallScreen$
- ]),
- tap(([ , message, isSmallScreen ]) => {
- this.snackBar.open(
- message,
- 'OK',
- {
- horizontalPosition: 'end',
- verticalPosition: isSmallScreen ? 'top' : 'bottom',
- duration: 5000,
- ...config
- }
- );
- }),
- );
- }
-
- private showAppUpdatedNotification(): OperatorFunction {
- return (source: Observable) => source.pipe(
- tap(() => {
- this.snackBar.openFromComponent(
- AppUpdatedNotificationComponent,
- {
- horizontalPosition: 'center',
- verticalPosition: 'bottom',
- duration: Number.MAX_SAFE_INTEGER
- }
- );
- }),
- );
- }
-}
diff --git a/modules/store/src/lib/effects/notifications/index.ts b/modules/store/src/lib/effects/notifications/index.ts
new file mode 100644
index 00000000..890f829c
--- /dev/null
+++ b/modules/store/src/lib/effects/notifications/index.ts
@@ -0,0 +1,11 @@
+import { SHOW_ERROR_NOTIFICATION_EFFECT } from './show-error-notification.effect';
+import { SHOW_GENERIC_ERROR_NOTIFICATION_EFFECT } from './show-generic-error-notification.effect';
+import { SHOW_INFO_NOTIFICATION_EFFECT } from './show-info-notification.effect';
+import { SHOW_APP_UPDATED_NOTIFICATION_EFFECT } from './show-app-updated-notification.effect';
+
+export const NOTIFICATION_EFFECTS = {
+ showErrorNotification: SHOW_ERROR_NOTIFICATION_EFFECT,
+ showGenericErrorNotification: SHOW_GENERIC_ERROR_NOTIFICATION_EFFECT,
+ showInfoNotification: SHOW_INFO_NOTIFICATION_EFFECT,
+ showAppUpdatedNotification: SHOW_APP_UPDATED_NOTIFICATION_EFFECT,
+} as const;
diff --git a/modules/store/src/lib/effects/notifications/show-app-updated-notification.effect.ts b/modules/store/src/lib/effects/notifications/show-app-updated-notification.effect.ts
new file mode 100644
index 00000000..066d4d5c
--- /dev/null
+++ b/modules/store/src/lib/effects/notifications/show-app-updated-notification.effect.ts
@@ -0,0 +1,16 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { tap } from 'rxjs';
+
+import { SHOW_NOTIFICATION_ACTIONS } from '../../actions';
+import { NotificationFacadeService } from '../../notification-facade.service';
+
+export const SHOW_APP_UPDATED_NOTIFICATION_EFFECT = createEffect((
+ actions: Actions = inject(Actions),
+ notificationsFacadeService: NotificationFacadeService = inject(NotificationFacadeService),
+) => {
+ return actions.pipe(
+ ofType(SHOW_NOTIFICATION_ACTIONS.appUpdated),
+ tap(() => notificationsFacadeService.showAppUpdatedNotification())
+ );
+}, { functional: true, dispatch: false });
diff --git a/modules/store/src/lib/effects/notifications/show-error-notification.effect.ts b/modules/store/src/lib/effects/notifications/show-error-notification.effect.ts
new file mode 100644
index 00000000..0a47a7bf
--- /dev/null
+++ b/modules/store/src/lib/effects/notifications/show-error-notification.effect.ts
@@ -0,0 +1,20 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { tap } from 'rxjs';
+import { TranslocoService } from '@ngneat/transloco';
+
+import { SHOW_NOTIFICATION_ACTIONS } from '../../actions';
+import { NotificationFacadeService } from '../../notification-facade.service';
+
+export const SHOW_ERROR_NOTIFICATION_EFFECT = createEffect((
+ actions: Actions = inject(Actions),
+ notificationsFacadeService: NotificationFacadeService = inject(NotificationFacadeService),
+ translocoService: TranslocoService = inject(TranslocoService),
+) => {
+ return actions.pipe(
+ ofType(SHOW_NOTIFICATION_ACTIONS.error),
+ tap((action) => notificationsFacadeService.showErrorNotification(
+ translocoService.selectTranslate(action.l10nKey, action.l10nPayload),
+ ))
+ );
+}, { functional: true, dispatch: false });
diff --git a/modules/store/src/lib/effects/notifications/show-generic-error-notification.effect.ts b/modules/store/src/lib/effects/notifications/show-generic-error-notification.effect.ts
new file mode 100644
index 00000000..bea326c3
--- /dev/null
+++ b/modules/store/src/lib/effects/notifications/show-generic-error-notification.effect.ts
@@ -0,0 +1,18 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { of, tap } from 'rxjs';
+
+import { SHOW_NOTIFICATION_ACTIONS } from '../../actions';
+import { NotificationFacadeService } from '../../notification-facade.service';
+
+export const SHOW_GENERIC_ERROR_NOTIFICATION_EFFECT = createEffect((
+ actions: Actions = inject(Actions),
+ notificationsFacadeService: NotificationFacadeService = inject(NotificationFacadeService),
+) => {
+ return actions.pipe(
+ ofType(SHOW_NOTIFICATION_ACTIONS.genericError),
+ tap((action) => notificationsFacadeService.showErrorNotification(
+ of(action.error.message),
+ ))
+ );
+}, { functional: true, dispatch: false });
diff --git a/modules/store/src/lib/effects/notifications/show-info-notification.effect.ts b/modules/store/src/lib/effects/notifications/show-info-notification.effect.ts
new file mode 100644
index 00000000..bd430d7c
--- /dev/null
+++ b/modules/store/src/lib/effects/notifications/show-info-notification.effect.ts
@@ -0,0 +1,20 @@
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { inject } from '@angular/core';
+import { tap } from 'rxjs';
+import { TranslocoService } from '@ngneat/transloco';
+
+import { SHOW_NOTIFICATION_ACTIONS } from '../../actions';
+import { NotificationFacadeService } from '../../notification-facade.service';
+
+export const SHOW_INFO_NOTIFICATION_EFFECT = createEffect((
+ actions: Actions = inject(Actions),
+ notificationsFacadeService: NotificationFacadeService = inject(NotificationFacadeService),
+ translocoService: TranslocoService = inject(TranslocoService),
+) => {
+ return actions.pipe(
+ ofType(SHOW_NOTIFICATION_ACTIONS.info),
+ tap((action) => notificationsFacadeService.showInfoNotification(
+ translocoService.selectTranslate(action.l10nKey, action.l10nPayload),
+ ))
+ );
+}, { functional: true, dispatch: false });
diff --git a/modules/store/src/lib/notification-facade.service.ts b/modules/store/src/lib/notification-facade.service.ts
new file mode 100644
index 00000000..92849578
--- /dev/null
+++ b/modules/store/src/lib/notification-facade.service.ts
@@ -0,0 +1,61 @@
+import { Injectable } from '@angular/core';
+import { Observable, take } from 'rxjs';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { ScreenSizeObserverService } from '@app/shared-misc';
+import { AppUpdatedNotificationComponent, ErrorNotificationComponent, InfoNotificationComponent } from '@app/shared-components';
+
+@Injectable()
+export class NotificationFacadeService {
+ constructor(
+ private readonly screenSizeObserverService: ScreenSizeObserverService,
+ private readonly snackBar: MatSnackBar,
+ ) {
+ }
+
+ public showInfoNotification(
+ message$: Observable
+ ): void {
+ this.screenSizeObserverService.isSmallScreen$.pipe(
+ take(1)
+ ).subscribe((isSmallScreen) => {
+ this.snackBar.openFromComponent(
+ InfoNotificationComponent,
+ {
+ horizontalPosition: 'end',
+ verticalPosition: isSmallScreen ? 'top' : 'bottom',
+ duration: 5000,
+ data: message$
+ }
+ );
+ });
+ }
+
+ public showErrorNotification(
+ message$: Observable
+ ): void {
+ this.screenSizeObserverService.isSmallScreen$.pipe(
+ take(1)
+ ).subscribe((isSmallScreen) => {
+ this.snackBar.openFromComponent(
+ ErrorNotificationComponent,
+ {
+ horizontalPosition: 'end',
+ verticalPosition: isSmallScreen ? 'top' : 'bottom',
+ duration: 10000,
+ data: message$
+ }
+ );
+ });
+ }
+
+ public showAppUpdatedNotification(): void {
+ this.snackBar.openFromComponent(
+ AppUpdatedNotificationComponent,
+ {
+ horizontalPosition: 'center',
+ verticalPosition: 'bottom',
+ duration: Number.MAX_SAFE_INTEGER
+ }
+ );
+ }
+}
diff --git a/modules/store/src/lib/provide-store.ts b/modules/store/src/lib/provide-store.ts
index 9afecd99..ba94aa10 100644
--- a/modules/store/src/lib/provide-store.ts
+++ b/modules/store/src/lib/provide-store.ts
@@ -36,12 +36,12 @@ import {
AttachedIoModesEffects,
COMMON_EFFECTS,
CONTROLLER_EFFECTS,
+ CONTROL_SCHEME_EFFECTS,
HUB_EFFECTS,
HubAttachedIosStateEffects,
HubPortModeInfoEffects,
- NotificationsEffects,
- SETTINGS_EFFECTS,
- TASK_PROCESSING_EFFECTS
+ NOTIFICATION_EFFECTS,
+ SETTINGS_EFFECTS
} from './effects';
import { bluetoothAvailabilityCheckFactory } from './bluetooth-availability-check-factory';
import { HubStorageService } from './hub-storage.service';
@@ -53,6 +53,7 @@ import { AppStoreVersion } from './app-store-version';
// eslint-disable-next-line @nx/enforce-module-boundaries
import packageJson from '../../../../package.json';
import { CopyToClipboardHandlerService } from './copy-to-clipboard-handler.service';
+import { NotificationFacadeService } from './notification-facade.service';
const REDUCERS: ActionReducerMap = {
bluetoothAvailability: BLUETOOTH_AVAILABILITY_FEATURE.reducer,
@@ -95,14 +96,14 @@ export function provideApplicationStore(): EnvironmentProviders {
AttachedIOsEffects,
HubPortModeInfoEffects,
AttachedIoModesEffects,
- NotificationsEffects,
HubAttachedIosStateEffects,
CONTROLLER_EFFECTS,
SETTINGS_EFFECTS,
HUB_EFFECTS,
- TASK_PROCESSING_EFFECTS,
+ CONTROL_SCHEME_EFFECTS,
COMMON_EFFECTS,
- APP_UPDATE_EFFECTS
+ APP_UPDATE_EFFECTS,
+ NOTIFICATION_EFFECTS
),
{
provide: APP_INITIALIZER,
@@ -119,6 +120,7 @@ export function provideApplicationStore(): EnvironmentProviders {
HubMotorPositionFacadeService,
HubServoCalibrationFacadeService,
ControllerProfilesFacadeService,
+ NotificationFacadeService,
{ provide: COPY_TO_CLIPBOARD_HANDLER, useClass: CopyToClipboardHandlerService },
provideRouterStore({
navigationActionTiming: NavigationActionTiming.PostActivation
diff --git a/package-lock.json b/package-lock.json
index fd757bc4..23c9aebe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "moc-commander",
- "version": "1.2.7",
+ "version": "1.2.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "moc-commander",
- "version": "1.2.7",
+ "version": "1.2.8",
"dependencies": {
"@angular/animations": "^17.2.0",
"@angular/cdk": "^17.2.0",
diff --git a/package.json b/package.json
index 375ca33e..5bc1f4cb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "moc-commander",
- "version": "1.2.7",
+ "version": "1.2.8",
"scripts": {
"start": "nx serve",
"serve:prod": "nx serve -c production",
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 01adf8f9..021afb46 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -10,12 +10,12 @@
"common": {
"copyToClipboard": "Copy to clipboard",
"copyToClipboardSuccessNotification": "Copied to clipboard",
- "copyToClipboardErrorNotification": "Failed to copy to clipboard"
+ "copyToClipboardErrorNotification": "Failed to copy to clipboard",
+ "dismissNotification": "Dismiss"
},
"appUpdatedNotification": {
"prefixMessage": "App has been updated, for details see",
- "linkText": "About page",
- "dismissButton": "Dismiss"
+ "linkText": "About page"
},
"adjustMotorPosition": {
"title": "Motor position adjustment",
@@ -823,6 +823,9 @@
"runningControlSchemePerformanceImprovements": "Performance improvements for running control schemes",
"fixPwaBackgroundColor": "Fixed background color of the PWA app",
"fixHighFrequencyTaskStalling": "Fixed task stalling when processing high-frequency inputs"
+ },
+ "1-2-8": {
+ "minorNotificationImprovements": "Minor improvements to notifications"
}
}
}
diff --git a/src/assets/i18n/ru.json b/src/assets/i18n/ru.json
index 553aacf6..fc39a877 100644
--- a/src/assets/i18n/ru.json
+++ b/src/assets/i18n/ru.json
@@ -10,12 +10,12 @@
"common": {
"copyToClipboard": "Копировать в буфер обмена",
"copyToClipboardSuccessNotification": "Скопировано в буфер обмена",
- "copyToClipboardErrorNotification": "Не удалось скопировать в буфер обмена"
+ "copyToClipboardErrorNotification": "Не удалось скопировать в буфер обмена",
+ "dismissNotification": "Закрыть"
},
"appUpdatedNotification": {
"prefixMessage": "Приложение было обновлено, см. ",
- "linkText": "'О программе'",
- "dismissButton": "Закрыть"
+ "linkText": "'О программе'"
},
"adjustMotorPosition": {
"title": "Подстройка положения мотора",
@@ -823,6 +823,9 @@
"runningControlSchemePerformanceImprovements": "Улучшение производительности при работе схем управления",
"fixPwaBackgroundColor": "Исправлена ошибка цвета фона при установке приложения как PWA",
"fixHighFrequencyTaskStalling": "Исправлено зависание задач при обработке интенсивого ввода"
+ },
+ "1-2-8": {
+ "minorNotificationImprovements": "Небольшие улучшения уведомлений"
}
}
}
diff --git a/src/changelog.ts b/src/changelog.ts
index b5531fff..90c32d5f 100644
--- a/src/changelog.ts
+++ b/src/changelog.ts
@@ -1,6 +1,12 @@
import { IChangelog } from '@app/shared-components';
export const CHANGELOG: IChangelog = [
+ {
+ version: '1.2.8',
+ changeL10nKeys: [
+ 'changelog.1-2-8.minorNotificationImprovements'
+ ]
+ },
{
version: '1.2.7',
changeL10nKeys: [
diff --git a/src/styles/theming.scss b/src/styles/theming.scss
index 30cfff3f..63e21cdb 100644
--- a/src/styles/theming.scss
+++ b/src/styles/theming.scss
@@ -126,7 +126,7 @@ $elevated-dark-color: mat.get-color-from-palette($dark-palette, 800);
}
.mat-mdc-snack-bar-container {
- --mdc-snackbar-container-color: #{mat.get-color-from-palette($dark-palette, 500)};
+ --mdc-snackbar-container-color: #{mat.get-color-from-palette($dark-palette, 700)};
--mdc-snackbar-supporting-text-color: #{$light-primary-text};
--mat-snack-bar-button-color: #{rgba($light-primary-text, 0.87)};
}