feat(shared-scanner): Moved to shared/scanner

feat(common-data-access): takeUnitl operators for keydown

Refs: #5062
This commit is contained in:
Lorenz Hilpert
2025-06-12 16:34:21 +02:00
parent 055cfb67d3
commit 3cf05f04ef
30 changed files with 93 additions and 44 deletions

View File

@@ -1 +1,2 @@
export * from './take-until-aborted';
export * from './take-unitl-keydown';

View File

@@ -0,0 +1,14 @@
import { filter, fromEvent, Observable, takeUntil } from 'rxjs';
export const takeUntilKeydown =
<T>(key: string) =>
(source: Observable<T>): Observable<T> => {
const keydownEvent$ = fromEvent<KeyboardEvent>(document, 'keydown').pipe(
// Filter for the specific key
filter((event) => event.key === key),
);
return source.pipe(takeUntil(keydownEvent$));
};
export const takeUntilKeydownEscape = <T>() => takeUntilKeydown<T>('Escape');

View File

@@ -1,4 +1,4 @@
import { Observable, fromEvent } from 'rxjs';
import { Observable } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
/**
@@ -19,7 +19,6 @@ export const fromAbortSignal = (signal: AbortSignal): Observable<void> => {
return new Observable<void>((subscriber) => {
const abortHandler = () => {
subscriber.next();
subscriber.complete();
};
// Listen for the 'abort' event

View File

@@ -18,7 +18,11 @@ import { ReturnSearchService } from '../services';
import { tapResponse } from '@ngrx/operators';
import { effect, inject } from '@angular/core';
import { QueryTokenSchema } from '../schemas';
import { Callback, ListResponseArgs } from '@isa/common/data-access';
import {
Callback,
ListResponseArgs,
takeUntilKeydownEscape,
} from '@isa/common/data-access';
import { ReceiptListItem } from '../models';
import { Query } from '@isa/shared/filter';
import { SessionStorageProvider, withStorage } from '@isa/core/storage';
@@ -191,6 +195,26 @@ export const ReturnSearchStore = signalStore(
),
);
},
handleSearchCompleted(processId: number) {
const entity = store.getEntity(processId);
if (entity?.status !== ReturnSearchStatus.Pending) {
return;
}
patchState(
store,
updateEntity(
{
id: processId, // Assuming we want to update the first entity
changes: {
status: ReturnSearchStatus.Idle,
},
},
config,
),
);
},
})),
withMethods((store, returnSearchService = inject(ReturnSearchService)) => ({
/**
@@ -219,6 +243,7 @@ export const ReturnSearchStore = signalStore(
}),
)
.pipe(
takeUntilKeydownEscape(),
tapResponse(
(response) => {
store.handleSearchSuccess({ processId, response });
@@ -228,6 +253,9 @@ export const ReturnSearchStore = signalStore(
store.handleSearchError({ processId, error });
cb?.({ error });
},
() => {
store.handleSearchCompleted(processId);
},
),
),
),

View File

@@ -34,7 +34,7 @@ import { tapResponse } from '@ngrx/operators';
import { ProductImageDirective } from '@isa/shared/product-image';
import { provideIcons } from '@ng-icons/core';
import { isaActionScanner } from '@isa/icons';
import { ScannerButtonComponent } from '@isa/core/scanner';
import { ScannerButtonComponent } from '@isa/shared/scanner';
import { ProductRouterLinkDirective } from '@isa/shared/product-router-link';
const eanValidator: ValidatorFn = (

View File

@@ -14,14 +14,13 @@
></filter-filter-menu-button>
@if (mobileBreakpoint()) {
<button
uiIconButton
<ui-icon-button
type="button"
(click)="showOrderByToolbarMobile.set(!showOrderByToolbarMobile())"
[class.active]="showOrderByToolbarMobile()"
data-what="sort-button-mobile"
name="isaActionSort"
></button>
></ui-icon-button>
} @else {
<filter-order-by-toolbar (toggled)="search()"></filter-order-by-toolbar>
}

View File

@@ -18,7 +18,7 @@ import {
import { IconButtonComponent } from '@isa/ui/buttons';
import { EmptyStateComponent } from '@isa/ui/empty-state';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { provideIcons } from '@ng-icons/core';
import { isaActionSort, isaActionFilter } from '@isa/icons';
import {
ReceiptListItem,
@@ -55,7 +55,6 @@ import { breakpoint } from '@isa/ui/layout';
EmptyStateComponent,
FilterMenuButtonComponent,
InViewportDirective,
NgIconComponent,
],
providers: [provideIcons({ isaActionSort, isaActionFilter })],
})

View File

@@ -20,7 +20,7 @@ import { isaActionSearch, isaActionScanner } from '@isa/icons';
import { FilterService, TextFilterInput } from '../../core';
import { InputType } from '../../types';
import { toSignal } from '@angular/core/rxjs-interop';
import { ScannerButtonComponent } from '@isa/core/scanner';
import { ScannerButtonComponent } from '@isa/shared/scanner';
@Component({
selector: 'filter-search-bar-input',

View File

@@ -28,6 +28,7 @@ The Scanner library provides barcode scanning capabilities for the ISA applicati
A button component that integrates with the scanner service to trigger barcode scanning. It implements `OnDestroy` to properly clean up resources.
**Features:**
- Only appears when scanner is ready
- Can be disabled through binding
- Configurable size
@@ -45,11 +46,12 @@ import { ScannerButtonComponent } from '@isa/core/scanner';
<shared-scanner-button
[disabled]="isDisabled"
[size]="'large'"
(scan)="onScan($event)">
(scan)="onScan($event)"
>
</shared-scanner-button>
`,
imports: [ScannerButtonComponent],
standalone: true
standalone: true,
})
export class MyComponent {
isDisabled = false;
@@ -65,6 +67,7 @@ export class MyComponent {
A structural directive (`*sharedScannerReady`) that conditionally renders its content based on the scanner's ready state. Similar to `*ngIf`, but specifically tied to scanner readiness.
**Features:**
- Only renders content when the scanner is ready
- Supports an optional else template for when the scanner is not ready
- Uses Angular's effect system for reactive updates
@@ -94,7 +97,7 @@ import { ScannerReadyDirective } from '@isa/core/scanner';
</ng-template>
`,
imports: [ScannerReadyDirective],
standalone: true
standalone: true,
})
export class MyComponent {
// Component logic
@@ -106,6 +109,7 @@ export class MyComponent {
Internal component used by ScannerService to render the camera view and process barcode scanning.
**Features:**
- Integrates with Scandit SDK
- Handles camera setup and barcode detection
- Emits scanned values
@@ -118,6 +122,7 @@ Internal component used by ScannerService to render the camera view and process
Core service that provides barcode scanning functionality.
**Features:**
- Initializes and configures Scandit SDK
- Checks platform compatibility
- Manages scanner lifecycle
@@ -136,7 +141,7 @@ import { Component, inject } from '@angular/core';
<button (click)="scan()" [disabled]="!isReady()">Scan Barcode</button>
<div *ngIf="result">Last Scan: {{ result }}</div>
`,
standalone: true
standalone: true,
})
export class MyComponent {
private scannerService = inject(ScannerService);
@@ -169,10 +174,7 @@ The scanner module uses injection tokens for configuration:
**Custom Configuration Example:**
```typescript
import {
SCANDIT_LICENSE,
SCANDIT_LIBRARY_LOCATION
} from '@isa/core/scanner';
import { SCANDIT_LICENSE, SCANDIT_LIBRARY_LOCATION } from '@isa/core/scanner';
import { Symbology } from 'scandit-web-datacapture-barcode';
@NgModule({
@@ -180,14 +182,14 @@ import { Symbology } from 'scandit-web-datacapture-barcode';
// Custom license key
{
provide: SCANDIT_LICENSE,
useValue: 'YOUR-SCANDIT-LICENSE-KEY'
useValue: 'YOUR-SCANDIT-LICENSE-KEY',
},
// Custom library location
{
provide: SCANDIT_LIBRARY_LOCATION,
useValue: 'https://cdn.example.com/scandit/'
}
]
useValue: 'https://cdn.example.com/scandit/',
},
],
})
export class AppModule {}
```

View File

@@ -1,8 +1,8 @@
export default {
displayName: 'core-scanner',
displayName: 'shared-scanner',
preset: '../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../coverage/libs/core/scanner',
coverageDirectory: '../../../coverage/libs/shared/scanner',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',

View File

@@ -1,7 +1,7 @@
{
"name": "core-scanner",
"name": "shared-scanner",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/core/scanner/src",
"sourceRoot": "libs/shared/scanner/src",
"prefix": "shared",
"projectType": "library",
"tags": [],
@@ -10,7 +10,7 @@
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/core/scanner/jest.config.ts"
"jestConfig": "libs/shared/scanner/jest.config.ts"
}
},
"lint": {

View File

@@ -174,14 +174,21 @@ export class ScannerService {
libraryLocation: this.#libraryLocation,
moduleLoaders: [barcodeCaptureLoader()],
});
this.#status.set(ScannerStatus.Ready);
this.#logger.info('Scanner ready');
} catch (error) {
if (error instanceof PlatformNotSupportedError) {
this.#logger.warn(error.message, () => ({
error,
}));
return;
}
this.#status.set(ScannerStatus.Error);
this.#logger.error('Failed to configure Scandit', error);
throw error;
}
this.#status.set(ScannerStatus.Ready);
this.#logger.info('Scanner ready');
}
/**

View File

@@ -46,7 +46,6 @@
"@isa/core/logging": ["libs/core/logging/src/index.ts"],
"@isa/core/notifications": ["libs/core/notifications/src/index.ts"],
"@isa/core/process": ["libs/core/process/src/index.ts"],
"@isa/core/scanner": ["libs/core/scanner/src/index.ts"],
"@isa/core/storage": ["libs/core/storage/src/index.ts"],
"@isa/icons": ["libs/icons/src/index.ts"],
"@isa/oms/data-access": ["libs/oms/data-access/src/index.ts"],
@@ -75,6 +74,7 @@
"@isa/shared/product-router-link": [
"libs/shared/product-router-link/src/index.ts"
],
"@isa/shared/scanner": ["libs/shared/scanner/src/index.ts"],
"@isa/ui/buttons": ["libs/ui/buttons/src/index.ts"],
"@isa/ui/datepicker": ["libs/ui/datepicker/src/index.ts"],
"@isa/ui/dialog": ["libs/ui/dialog/src/index.ts"],