Skip to content

Commit

Permalink
chore: add tsdoc and correct various bugs
Browse files Browse the repository at this point in the history
Fix issue on localization not available when currentLanguage or lang list is not set
Add tsdoc and json schema description
Handle the app connection state with signals
Add new option.html in nx input/output to avoid future cache issue
  • Loading branch information
cpaulve-1A committed Jul 16, 2024
1 parent e0be70d commit ce42773
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 71 deletions.
6 changes: 4 additions & 2 deletions apps/chrome-devtools/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,15 @@
"outputs": [
"{projectRoot}/dist/assets/**",
"{projectRoot}/dist/manifest.json",
"{projectRoot}/dist/devtools.html"
"{projectRoot}/dist/devtools.html",
"{projectRoot}/src/options.html"
],
"inputs": [
"global",
"{projectRoot}/src/assets/**",
"{projectRoot}/src/manifest.json",
"{projectRoot}/src/devtools.html"
"{projectRoot}/src/devtools.html",
"{projectRoot}/src/options.html"
]
},
"publish-extension": {
Expand Down
6 changes: 6 additions & 0 deletions apps/chrome-devtools/schemas/state.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,27 @@
],
"properties": {
"color": {
"description": "Background color to identify the state in the selection widget.",
"type": "string"
},
"colorContrast": {
"description": "Text color for the state in the selection widget in contrast with the background color.",
"type": "string"
},
"name": {
"description": "User friendly name to identify the state in the Chrome Extension state panel.",
"type": "string"
},
"configurations": {
"type": "object",
"description": "List of the configuration-override to apply on the application.",
"additionalProperties": {
"type": "object"
}
},
"localizations": {
"type": "object",
"description": "List of the localization-override to apply on the application.",
"additionalProperties": {
"type": "object",
"additionalProperties": {
Expand All @@ -34,6 +39,7 @@
}
},
"stylingVariables": {
"description": "List of the css-variable-override to apply on the application.",
"type": "object",
"additionalProperties": {
"type": "string"
Expand Down
2 changes: 1 addition & 1 deletion apps/chrome-devtools/src/app-devtools/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
</ng-template>
</li>
</ul>
<form [formGroup]="form" class="px-4 pt-{{hasLocalChanges() ? '2' : '3'}} ng-pristine ng-valid ng-touched border-bottom ms-auto">
<form [formGroup]="form" class="px-4 {{hasLocalChanges() ? 'pt-2' : 'pt-3'}} ng-pristine ng-valid ng-touched border-bottom ms-auto">
<div>
<ng-select
[ngbTooltip]="
Expand Down
2 changes: 1 addition & 1 deletion apps/chrome-devtools/src/app-devtools/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { ThemingPanelPresComponent } from './theming-panel/theming-panel-pres.co
selector: 'app-root',
templateUrl: './app.component.html',
styles: `
::ng-deep ng-select.local-change .ng-select-container {
:host ::ng-deep ng-select.local-change .ng-select-container {
border-color: var(--bs-recommend-warning-color);
border-width: medium;
box-sizing: content-box;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { DOCUMENT, JsonPipe, KeyValuePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, effect, type ElementRef, inject, type Signal, untracked, viewChild, ViewEncapsulation } from '@angular/core';
import { DOCUMENT, JsonPipe, KeyValuePipe, NgClass } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
effect,
type ElementRef,
inject,
signal,
type Signal,
untracked,
viewChild,
ViewEncapsulation, WritableSignal
} from '@angular/core';
import { type AbstractControl, FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, type ValidationErrors, type ValidatorFn, Validators } from '@angular/forms';
import { DfTooltipModule, DfTriggerClickDirective } from '@design-factory/design-factory';
import { StateService } from '../../services';
Expand Down Expand Up @@ -66,7 +77,8 @@ const createStateForm = (name: string, color?: string | null) => new FormGroup<S
ReactiveFormsModule,
FormsModule,
DfTriggerClickDirective,
DfTooltipModule
DfTooltipModule,
NgClass
]
})
export class StatePanelComponent {
Expand All @@ -80,6 +92,7 @@ export class StatePanelComponent {
public readonly localState = this.stateService.localState;
public readonly hasLocalChanges = this.stateService.hasLocalChanges;
public readonly newStateNameErrorMessage: Signal<string | null>;
public readonly downloadState: WritableSignal<{ text: string; success: boolean } | null> = signal<{ text: string; success: boolean } | null>(null);
public readonly form = this.formBuilder.group<StatesPanelForm>({
newStateName: new FormControl('', stateNameValidators),
newStateColor: new FormControl(''),
Expand Down Expand Up @@ -154,6 +167,10 @@ export class StatePanelComponent {
this.stateService.setActiveState(stateName);
}

/**
* Update a state and save its content in the Chrome Extension store.
* @param stateName
*/
public updateState(stateName: string) {
const control = this.form.controls.states.controls[stateName];
const activeState = this.activeState();
Expand All @@ -162,6 +179,9 @@ export class StatePanelComponent {
}
}

/**
* Create a new state and save its content in the Chrome Extension store.
*/
public saveNewState() {
if (this.form.value.newStateName) {
this.saveState(this.form.value.newStateName, this.form.value.newStateName, this.form.value.newStateColor || 'black');
Expand All @@ -174,12 +194,23 @@ export class StatePanelComponent {
}
}

/**
* Remove a state from the Chrome Extension application and store.
* Note that the active store cannot be deleted.
*
* @param stateName
*/
public deleteState(stateName: string) {
if (this.activeState()?.name !== stateName) {
this.stateService.deleteState(stateName);
}
}

/**
* Download a state as a json file
*
* @param stateName
*/
public exportState(stateName: string) {
const state = this.states()[stateName];
if (!state) {
Expand All @@ -192,6 +223,11 @@ export class StatePanelComponent {
a.click();
}

/**
* Download a state file, add it to the state list and share it .
*
* @param event
*/
public async onFileChange(event: InputEvent) {
try {
const element = event.target as HTMLInputElement;
Expand Down Expand Up @@ -250,30 +286,12 @@ export class StatePanelComponent {
stylingVariables: state.stylingVariables
}
);
const el = this.importStateText();
if (!el) {
return;
}
el.nativeElement.innerHTML = `${state.name} imported correctly`;
if (el.nativeElement.classList.contains('text-danger')) {
el.nativeElement.classList.remove('text-danger');
}
el.nativeElement.classList.add('text-success');
this.downloadState.set({ text: `${state.name} imported correctly`, success: true });
} else {
throw new Error('Invalid state');
}
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
const el = this.importStateText();
if (!el) {
return;
}
el.nativeElement.innerHTML = (e as Error).message;
if (el.nativeElement.classList.contains('text-success')) {
el.nativeElement.classList.remove('text-success');
}
el.nativeElement.classList.add('text-danger');
this.downloadState.set({ text: (e as Error).message, success: false });
} finally {
// Clear the input once processed
(event.target as HTMLInputElement).value = '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
</button>
</div>
</div>
<div #importStateText class="import-state-text mt-2"></div>
@if (downloadState()) {
<div class="import-state-text mt-2" [ngClass]="{'text-danger': !downloadState()!.success, 'text-success': downloadState()!.success}">
{{downloadState()?.text}}
</div>
}
<ul class="list-group list-group-flush mt-4" formGroupName="states">
@for (state of states() | keyvalue; track state.key) {
<li class="list-group-item d-flex align-items-center gap-4" [formGroupName]="state.key">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { AsyncPipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
import { Observable, of, Subscription } from 'rxjs';
import { catchError, map, startWith, take, timeout } from 'rxjs/operators';
import { ChromeExtensionConnectionService, isRuleEngineEventsMessage } from '../../services/connection.service';
import { RulesetHistoryService } from '../../services/ruleset-history.service';

type AppState = 'loading' | 'timeout' | 'connected';
import { ChangeDetectionStrategy, Component, inject, OnDestroy } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { AppState, ChromeExtensionConnectionService } from '../../services/connection.service';

@Component({
selector: 'app-connection',
Expand All @@ -17,32 +13,14 @@ type AppState = 'loading' | 'timeout' | 'connected';
]
})
export class AppConnectionComponent implements OnDestroy {
/** Stream of application's state */
public appState$: Observable<AppState>;

private readonly subscription = new Subscription();
private readonly connectionService = inject(ChromeExtensionConnectionService);

constructor(
connectionService: ChromeExtensionConnectionService,
rulesetHistoryService: RulesetHistoryService
) {
this.subscription.add(
connectionService.message$.subscribe((message) => {
if (isRuleEngineEventsMessage(message)) {
rulesetHistoryService.update(message);
}
})
);

this.appState$ = connectionService.message$.pipe(
map(() => 'connected' as AppState),
take(1),
startWith('loading' as AppState),
timeout(3000),
catchError(() => of('timeout' as AppState))
);
/** Stream of application's state */
public appState$: Observable<AppState> = this.connectionService.appState$;

connectionService.activate();
constructor() {
this.connectionService.activate();
}

/** @inheritDoc */
Expand Down
25 changes: 23 additions & 2 deletions apps/chrome-devtools/src/services/connection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@ import { ApplicationRef, Injectable, OnDestroy } from '@angular/core';
import type { Dictionary } from '@ngrx/entity';
import type { ConfigurationModel } from '@o3r/configuration';
import { otterMessageType } from '@o3r/core';
import { type Observable, ReplaySubject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, filter, map, shareReplay } from 'rxjs/operators';
import {type Observable, of, ReplaySubject, Subscription} from 'rxjs';
import {
catchError,
debounceTime,
distinctUntilChanged,
filter,
map,
shareReplay,
startWith,
take,
timeout
} from 'rxjs/operators';
import type { AvailableMessageContents } from './message.interface';

import type { ApplicationInformationContentMessage } from '@o3r/application';
Expand Down Expand Up @@ -59,6 +69,8 @@ export const filterAndMapMessage = <T extends AvailableMessageContents, R>(
shareReplay({ refCount: true, bufferSize: 1 })
);

export type AppState = 'loading' | 'timeout' | 'connected';

/**
* Service to communicate with the current tab
*/
Expand All @@ -71,6 +83,15 @@ export class ChromeExtensionConnectionService implements OnDestroy {

/** Stream of messages received from the service worker */
public message$ = this.messageSubject.asObservable();
/** Stream the state of the extension connection to the Otter application*/
public appState$ = this.message$.pipe(
map(() => 'connected' as AppState),
take(1),
startWith('loading' as AppState),
timeout(3000),
catchError(() => of('timeout' as AppState))
);


private readonly configurations = new ReplaySubject<Dictionary<ConfigurationModel>>(1);
public configurations$ = this.configurations.asObservable();
Expand Down
26 changes: 23 additions & 3 deletions apps/chrome-devtools/src/services/localization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ChromeExtensionConnectionService, filterAndMapMessage } from './connect
@Injectable({ providedIn: 'root' })
export class LocalizationService {
private readonly connectionService = inject(ChromeExtensionConnectionService);
private readonly lang = signal<string | undefined>(undefined);
public readonly localizationsMetadata = toSignal(
this.connectionService.message$.pipe(
filterAndMapMessage(
Expand Down Expand Up @@ -40,7 +41,6 @@ export class LocalizationService {
),
{ initialValue: false }
);
private readonly lang = signal<string | undefined>(undefined);
public readonly currentLanguage = this.lang.asReadonly();

public readonly translationsForCurrentLanguage: Signal<Record<string, string>> = toSignal(
Expand All @@ -55,9 +55,29 @@ export class LocalizationService {
);

constructor() {
const activated = toSignal(this.connectionService.appState$);
effect(() => {
if (activated() === 'connected') {
this.connectionService.sendMessage(
'requestMessages',
{
only: [
'localizations',
'languages',
'switchLanguage',
'isTranslationDeactivationEnabled'
]
}
);
}
});

effect(() => {
this.connectionService.sendMessage('switchLanguage', { language: this.currentLanguage() });
this.connectionService.sendMessage('requestMessages', { only: ['getTranslationValuesContentMessage'] });
const currentLanguage = this.currentLanguage();
if (currentLanguage) {
this.connectionService.sendMessage('switchLanguage', { language: this.currentLanguage() });
this.connectionService.sendMessage('requestMessages', { only: ['getTranslationValuesContentMessage'] });
}
});
const externalSwitchLanguage = toSignal(
this.connectionService.message$.pipe(
Expand Down
19 changes: 16 additions & 3 deletions apps/chrome-devtools/src/services/ruleset-history.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { Injectable } from '@angular/core';
import { effect, inject, Injectable } from '@angular/core';
import type { RulesEngineDebugEventsContentMessage, RulesetExecutionDebug } from '@o3r/rules-engine';
import { rulesetReportToHistory } from '@o3r/rules-engine';
import { map, Observable, ReplaySubject } from 'rxjs';
import {shareReplay} from 'rxjs/operators';
import { filter, map, Observable, ReplaySubject } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { ChromeExtensionConnectionService, isRuleEngineEventsMessage } from './connection.service';
import { toSignal } from '@angular/core/rxjs-interop';

@Injectable({
providedIn: 'root'
})
export class RulesetHistoryService {
private readonly ruleEngineDebugEventsSubject = new ReplaySubject<RulesEngineDebugEventsContentMessage>(1);
private readonly connectionService = inject(ChromeExtensionConnectionService);

/** Ruleset history stream */
public readonly ruleEngineDebugEvents$ = this.ruleEngineDebugEventsSubject.asObservable();
Expand All @@ -21,6 +24,16 @@ export class RulesetHistoryService {
shareReplay({bufferSize: 1, refCount: true})
);

constructor() {
const extensionMessage = toSignal(this.connectionService.message$.pipe(filter(isRuleEngineEventsMessage)));
effect(() => {
const message = extensionMessage();
if (message) {
this.update(message);
}
});
}

/**
* Update the ruleset history
* @param message Message from the background service
Expand Down
Loading

0 comments on commit ce42773

Please sign in to comment.