From 54aca433716f95009e47dc3c94d48dffaf452f78 Mon Sep 17 00:00:00 2001 From: FyloZ Date: Fri, 28 May 2021 19:22:13 -0400 Subject: [PATCH] Ajout des configurations --- src/app/app-routing.module.ts | 5 + src/app/app.component.ts | 7 + .../step-list/step-list.component.html | 4 +- .../step-table/step-table.component.html | 2 +- .../colors/pages/list/list.component.ts | 16 +- .../modules/configuration/bool-config.html | 4 + .../modules/configuration/config-actions.html | 3 + .../modules/configuration/config-section.html | 13 + src/app/modules/configuration/config.html | 12 + .../modules/configuration/config.module.ts | 35 +++ src/app/modules/configuration/config.sass | 52 ++++ src/app/modules/configuration/config.ts | 228 ++++++++++++++++++ src/app/modules/configuration/editor.html | 105 ++++++++ .../modules/configuration/image-config.html | 26 ++ .../components/header/header.component.html | 5 +- .../components/header/header.component.ts | 7 + .../shared/components/inputs/checkbox.html | 3 + .../shared/components/inputs/file-input.html | 10 + .../shared/components/inputs/input.html | 9 +- .../shared/components/inputs/input.sass | 14 ++ .../shared/components/inputs/inputs.module.ts | 40 +-- .../shared/components/inputs/inputs.ts | 52 +++- src/app/modules/shared/model/config.model.ts | 24 ++ .../modules/shared/service/config.service.ts | 61 +++++ src/app/modules/shared/utils/utils.ts | 16 +- .../administration.component.ts | 1 + src/assets/favicon.png | Bin 7135 -> 6745 bytes src/index.html | 2 +- src/styles.sass | 2 +- 29 files changed, 731 insertions(+), 27 deletions(-) create mode 100644 src/app/modules/configuration/bool-config.html create mode 100644 src/app/modules/configuration/config-actions.html create mode 100644 src/app/modules/configuration/config-section.html create mode 100644 src/app/modules/configuration/config.html create mode 100644 src/app/modules/configuration/config.module.ts create mode 100644 src/app/modules/configuration/config.sass create mode 100644 src/app/modules/configuration/config.ts create mode 100644 src/app/modules/configuration/editor.html create mode 100644 src/app/modules/configuration/image-config.html create mode 100644 src/app/modules/shared/components/inputs/checkbox.html create mode 100644 src/app/modules/shared/components/inputs/file-input.html create mode 100644 src/app/modules/shared/components/inputs/input.sass create mode 100644 src/app/modules/shared/model/config.model.ts create mode 100644 src/app/modules/shared/service/config.service.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 7057e34..24a2458 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,6 +3,7 @@ import {Routes, RouterModule} from '@angular/router' import {CatalogComponent} from './pages/catalog/catalog.component' import {AdministrationComponent} from './pages/administration/administration.component' import {MiscComponent} from './pages/others/misc.component' +import {CreConfigEditor} from './modules/configuration/config' const routes: Routes = [{ @@ -38,6 +39,10 @@ const routes: Routes = [{ }, { path: 'group', loadChildren: () => import('./modules/groups/group.module').then(m => m.GroupModule) + }, { + path: 'config', + loadChildren: () => import('./modules/configuration/config.module').then(m => m.ConfigModule), + component: CreConfigEditor }, { path: '', pathMatch: 'full', diff --git a/src/app/app.component.ts b/src/app/app.component.ts index df904ac..7f5758f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -4,6 +4,9 @@ import {AppState} from './modules/shared/app-state' import {SubscribingComponent} from './modules/shared/components/subscribing.component' import {ActivatedRoute, Router} from '@angular/router' import {ErrorService} from './modules/shared/service/error.service' +import {ConfigService} from './modules/shared/service/config.service' +import {Config} from './modules/shared/model/config.model' +import {environment} from '../environments/environment' @Component({ selector: 'cre-root', @@ -13,9 +16,11 @@ import {ErrorService} from './modules/shared/service/error.service' export class AppComponent extends SubscribingComponent { isOnline: boolean isServerOnline = true + favIcon: HTMLLinkElement = document.querySelector('#favicon') constructor( @Inject(PLATFORM_ID) private platformId: object, + private configService: ConfigService, private appState: AppState, errorService: ErrorService, router: Router, @@ -32,6 +37,8 @@ export class AppComponent extends SubscribingComponent { this.appState.serverOnline$, online => this.isServerOnline = online ) + + this.favIcon.href = environment.apiUrl + "/file?path=images%2Ficon" } reload() { diff --git a/src/app/modules/colors/components/step-list/step-list.component.html b/src/app/modules/colors/components/step-list/step-list.component.html index efb9c0c..c53850d 100644 --- a/src/app/modules/colors/components/step-list/step-list.component.html +++ b/src/app/modules/colors/components/step-list/step-list.component.html @@ -9,7 +9,7 @@ -

Aucun groupe n'est sélectionné

-

Il n'y a aucune étape définie pour ce groupe

+

Aucun groupe n'est sélectionné

+

Il n'y a aucune étape définie pour ce groupe

diff --git a/src/app/modules/colors/components/step-table/step-table.component.html b/src/app/modules/colors/components/step-table/step-table.component.html index 115f924..800b6a4 100644 --- a/src/app/modules/colors/components/step-table/step-table.component.html +++ b/src/app/modules/colors/components/step-table/step-table.component.html @@ -12,7 +12,7 @@ -

Aucun groupe n'est sélectionné

+

Aucun groupe n'est sélectionné

this.recipes = recipes + this.configService.get(Config.EMERGENCY_MODE), + config => { + if (config.content === "false") { + this.subscribe( + this.recipeService.allSortedByCompany, + recipes => this.recipes = recipes + ) + } else { + this.urlUtils.navigateTo("/admin/config") + } + } ) } diff --git a/src/app/modules/configuration/bool-config.html b/src/app/modules/configuration/bool-config.html new file mode 100644 index 0000000..53c694c --- /dev/null +++ b/src/app/modules/configuration/bool-config.html @@ -0,0 +1,4 @@ +
+ + Dernière mise à jour: {{lastUpdated}} +
diff --git a/src/app/modules/configuration/config-actions.html b/src/app/modules/configuration/config-actions.html new file mode 100644 index 0000000..d152dd7 --- /dev/null +++ b/src/app/modules/configuration/config-actions.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/modules/configuration/config-section.html b/src/app/modules/configuration/config-section.html new file mode 100644 index 0000000..08c2b94 --- /dev/null +++ b/src/app/modules/configuration/config-section.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/app/modules/configuration/config.html b/src/app/modules/configuration/config.html new file mode 100644 index 0000000..12f2ef0 --- /dev/null +++ b/src/app/modules/configuration/config.html @@ -0,0 +1,12 @@ +
+ + +
diff --git a/src/app/modules/configuration/config.module.ts b/src/app/modules/configuration/config.module.ts new file mode 100644 index 0000000..05dee32 --- /dev/null +++ b/src/app/modules/configuration/config.module.ts @@ -0,0 +1,35 @@ +import {NgModule} from '@angular/core' +import { + CreConfig, + CreConfigLabel, + CreConfigEditor, + CreConfigSection, + CreImageConfig, + CreConfigList, + CreConfigActions, + CreConfigTooltip +} from './config' +import {SharedModule} from '../shared/shared.module' +import {CreInputsModule} from '../shared/components/inputs/inputs.module' +import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module' +import {CreButtonsModule} from '../shared/components/buttons/buttons.module' + +@NgModule({ + declarations: [ + CreConfigLabel, + CreConfigTooltip, + CreConfigEditor, + CreConfig, + CreImageConfig, + CreConfigSection, + CreConfigList, + CreConfigActions + ], + imports: [ + SharedModule, + CreInputsModule, + CreActionBarModule, + CreButtonsModule + ] +}) +export class ConfigModule { } diff --git a/src/app/modules/configuration/config.sass b/src/app/modules/configuration/config.sass new file mode 100644 index 0000000..6812169 --- /dev/null +++ b/src/app/modules/configuration/config.sass @@ -0,0 +1,52 @@ +mat-hint + font-size: .8em + +cre-config + display: block + + cre-input.has-hint + margin-bottom: 1em + + mat-hint + font-size: 1em + +cre-image-config + display: block + border: 1px solid rgba(0, 0, 0, 0.42) + border-radius: 4px + padding: 8px + position: relative + margin-bottom: 1.34375em + transition: border-color 300ms + + &:hover + border: 2px solid black + padding: 7px + + .cre-image-config-label + top: -10px + left: 3px + + .cre-image-config-label + position: absolute + top: -9px + left: 4px + margin-bottom: 0 + background-color: white + padding: 0 5px + color: rgba(0, 0, 0, 0.52) + font-size: .8em + + .image-wrapper + width: 200px + text-align: right + + hr + margin: 0 0 .5em + background-color: rgba(0, 0, 0, 0.42) + + img + border: 2px solid black + + mat-hint + margin-top: .2em diff --git a/src/app/modules/configuration/config.ts b/src/app/modules/configuration/config.ts new file mode 100644 index 0000000..5332d13 --- /dev/null +++ b/src/app/modules/configuration/config.ts @@ -0,0 +1,228 @@ +import { + AfterViewInit, + Component, + ContentChild, + Directive, + ElementRef, + EventEmitter, + Input, + Output, + ViewChild, + ViewEncapsulation +} from '@angular/core' +import {ConfigService} from '../shared/service/config.service' +import {Config} from '../shared/model/config.model' +import {ErrorHandlingComponent, SubscribingComponent} from '../shared/components/subscribing.component' +import {ErrorService} from '../shared/service/error.service' +import {ActivatedRoute, Router} from '@angular/router' +import {formatDateTime, readFile} from '../shared/utils/utils' +import {FormControl, Validators} from '@angular/forms' +import {ConfirmBoxComponent} from '../shared/components/confirm-box/confirm-box.component' + +@Directive({ + selector: 'cre-config-label' +}) +export class CreConfigLabel implements AfterViewInit { + content: string + + constructor( + private element: ElementRef + ) { + } + + ngAfterViewInit(): void { + this.content = this.element.nativeElement.innerHTML + } +} + +@Directive({ + selector: 'cre-config-tooltip' +}) +export class CreConfigTooltip implements AfterViewInit { + content: string + + constructor( + private element: ElementRef + ) { + } + + ngAfterViewInit(): void { + this.content = this.element.nativeElement.innerHTML + } +} + +@Directive({ + selector: 'cre-config-list' +}) +export class CreConfigList { +} + +@Component({ + selector: 'cre-config-actions', + templateUrl: 'config-actions.html' +}) +export class CreConfigActions { +} + +@Component({ + selector: 'cre-config-section', + templateUrl: 'config-section.html' +}) +export class CreConfigSection { + @ContentChild(CreConfigActions) actions: CreConfigActions + + get hasActions(): boolean { + return this.actions !== undefined + } +} + +@Component({ + selector: 'cre-config', + templateUrl: 'config.html', + styleUrls: ['config.sass'] +}) +export class CreConfig extends SubscribingComponent { + @Input() config: { key: string, control: FormControl } + + @ContentChild(CreConfigLabel, {static: true}) label: CreConfigLabel + @ContentChild(CreConfigTooltip, {static: true}) tooltip: CreConfigTooltip + + configuration: Config | null + + constructor( + private configService: ConfigService, + errorService: ErrorService, + activatedRoute: ActivatedRoute, + router: Router + ) { + super(errorService, activatedRoute, router) + } + + ngOnInit() { + super.ngOnInit() + + this.subscribe( + this.configService.get(this.config.key), + config => this.setConfig(config) + ) + } + + setConfig(config: Config) { + this.configuration = config + this.config.control.setValue(config.content) + if (!config.editable) { + this.config.control.disable() + } + } + + get lastUpdated(): string { + return formatDateTime(this.configuration.lastUpdated) + } +} + +@Component({ + selector: 'cre-image-config', + templateUrl: 'image-config.html', + styleUrls: ['config.sass'], + encapsulation: ViewEncapsulation.None +}) +export class CreImageConfig extends CreConfig { + @Input() previewWidth: string | null + + @Output() invalidFormat = new EventEmitter() + + updatedImage: any | null + + constructor( + configService: ConfigService, + errorService: ErrorService, + activatedRoute: ActivatedRoute, + router: Router + ) { + super(configService, errorService, activatedRoute, router) + } + + updateImage(file: File): any { + readFile(file, (content) => this.updatedImage = content) + } +} + +@Component({ + selector: 'cre-bool-config', + templateUrl: 'bool-config.html' +}) +export class CreBoolConfig extends CreConfig { + setConfig(config: Config) { + super.setConfig(config); + this.config.control.setValue(config.content === "true") + } +} + +@Component({ + selector: 'cre-config-editor', + templateUrl: 'editor.html' +}) +export class CreConfigEditor extends ErrorHandlingComponent { + @ViewChild('restartingConfirmBox', {static: true}) restartConfirmBox: ConfirmBoxComponent + + keys = { + INSTANCE_NAME: Config.INSTANCE_NAME, + INSTANCE_LOGO_PATH: Config.INSTANCE_LOGO_PATH, + INSTANCE_ICON_PATH: Config.INSTANCE_ICON_PATH, + INSTANCE_URL: Config.INSTANCE_URL, + DATABASE_URL: Config.DATABASE_URL, + DATABASE_USER: Config.DATABASE_USER, + DATABASE_PASSWORD: Config.DATABASE_PASSWORD, + DATABASE_VERSION: Config.DATABASE_VERSION, + TOUCH_UP_KIT_CACHE_PDF: Config.TOUCH_UP_KIT_CACHE_PDF, + APP_VERSION: Config.APP_VERSION, + JAVA_VERSION: Config.JAVA_VERSION, + OPERATING_SYSTEM: Config.OPERATING_SYSTEM + } + controls = new Map() + emergencyMode: string | null + + constructor( + private configService: ConfigService, + errorService: ErrorService, + activatedRoute: ActivatedRoute, + router: Router + ) { + super(errorService, activatedRoute, router) + + for (let key in this.keys) { + this.controls[this.keys[key]] = new FormControl(null, Validators.required) + } + } + + ngOnInit() { + this.subscribe( + this.configService.get(Config.EMERGENCY_MODE), + config => { + this.emergencyMode = config.content + } + ) + } + + getConfig(key: string) { + return {key, control: this.controls[key]} + } + + save() { + this.subscribe( + this.configService.set(this.controls), + () => this.reload() + ) + } + + restart() { + this.subscribe( + this.configService.restart(), + () => this.restartConfirmBox.show() + ) + } + + reload() { + window.location.reload() + } +} diff --git a/src/app/modules/configuration/editor.html b/src/app/modules/configuration/editor.html new file mode 100644 index 0000000..0deb9df --- /dev/null +++ b/src/app/modules/configuration/editor.html @@ -0,0 +1,105 @@ + + + Retour + + + Enregistrer + + + +
+ + Apparence + + + + + + + + + + Logo + + Affiché dans la bannière de l'application web. Il peut être nécessaire de forcer le + rafraîchissement du cache du navigateur pour que ce changement prenne effet (généralement avec les touches + 'Ctrl+F5'). + + + + + Icône + + Affiché dans l'onglet de la page dans le navigateur. Il peut être nécessaire de forcer le + rafraîchissement du cache du navigateur pour que ce changement prenne effet (généralement avec les touches + 'Ctrl+F5'). + + + + + + + Kits de retouche + + + Activer le cache des PDFs générés + + Cette option permet de stocker les PDFs générés sur le disque, ce qui permet d'accélérer + l'accès aux PDFs si la lecture des fichiers cachés sur le disque est plus rapide que la génération d'un + nouveau PDF. + + + + + + + Système + + + URL de l'instance + + Utilisé pour générer l'URL de certaines ressources, comme les images et les fiches signalitiques. + + + + + URL de la base de données + + + + Utilisateur de la base de données + + + + Mot de passe de la base de données + + + + Version de la base de données + + + + Version de Color Recipes Explorer + + + + Version de Java + + + + Système d'exploitation + + + + Redémarrer le serveur + + +
+ + + + diff --git a/src/app/modules/configuration/image-config.html b/src/app/modules/configuration/image-config.html new file mode 100644 index 0000000..6056487 --- /dev/null +++ b/src/app/modules/configuration/image-config.html @@ -0,0 +1,26 @@ +
+

+ +

+
+ +
+ + +
+
+ +
+ Dernière mise à jour:
{{lastUpdated}}
+
+
diff --git a/src/app/modules/shared/components/header/header.component.html b/src/app/modules/shared/components/header/header.component.html index 7a2bb30..a5382f8 100644 --- a/src/app/modules/shared/components/header/header.component.html +++ b/src/app/modules/shared/components/header/header.component.html @@ -19,7 +19,8 @@ Color Recipes Explorer + title="Color Recipes Explorer" + height="70px"/> diff --git a/src/app/modules/shared/components/header/header.component.ts b/src/app/modules/shared/components/header/header.component.ts index 470a396..882d013 100644 --- a/src/app/modules/shared/components/header/header.component.ts +++ b/src/app/modules/shared/components/header/header.component.ts @@ -5,6 +5,8 @@ import {Permission} from '../../model/user' import {AccountService} from '../../../accounts/services/account.service' import {SubscribingComponent} from '../subscribing.component' import {ErrorService} from '../../service/error.service' +import {ConfigService} from '../../service/config.service' +import {environment} from '../../../../../environments/environment' @Component({ selector: 'cre-header', @@ -22,6 +24,7 @@ export class HeaderComponent extends SubscribingComponent { constructor( private accountService: AccountService, + private configService: ConfigService, private appState: AppState, errorService: ErrorService, router: Router, @@ -62,6 +65,10 @@ export class HeaderComponent extends SubscribingComponent { super.ngOnDestroy() } + get logoUrl(): string { + return environment.apiUrl + "/file?path=images%2Flogo" + } + set activeLink(link: string) { this._activeLink = link this.router.navigate([link]) diff --git a/src/app/modules/shared/components/inputs/checkbox.html b/src/app/modules/shared/components/inputs/checkbox.html new file mode 100644 index 0000000..dbadabf --- /dev/null +++ b/src/app/modules/shared/components/inputs/checkbox.html @@ -0,0 +1,3 @@ + + {{label}} + diff --git a/src/app/modules/shared/components/inputs/file-input.html b/src/app/modules/shared/components/inputs/file-input.html new file mode 100644 index 0000000..5bf6c13 --- /dev/null +++ b/src/app/modules/shared/components/inputs/file-input.html @@ -0,0 +1,10 @@ +
+ Choisir un fichier + +
+

{{selectedFileName}}

+

Aucun fichier sélectionné

+
+
+ + diff --git a/src/app/modules/shared/components/inputs/input.html b/src/app/modules/shared/components/inputs/input.html index d7ef1cd..d123b75 100644 --- a/src/app/modules/shared/components/inputs/input.html +++ b/src/app/modules/shared/components/inputs/input.html @@ -9,6 +9,7 @@ [(ngModel)]="value" [required]="required" [autocomplete]="autocomplete ? 'on' : 'off'" + [disabled]="disabled" (change)="valueChange.emit(value)"/> - + + + {{hint}} Ce champ est requis () @@ -174,6 +180,50 @@ export class CreChipComboBoxComponent extends CreChipInputComponent implements O } } +@Component({ + selector: 'cre-checkbox-input', + templateUrl: 'checkbox.html' +}) +export class CreCheckboxInputComponent { + @Input() label: string + @Input() control: FormControl +} + +@Component({ + selector: 'cre-file-input', + templateUrl: 'file-input.html' +}) +export class CreFileInputComponent implements OnInit { + @Input() label: string + @Input() icon: string + @Input() accept = '' + @Input() control: FormControl | null + + @Output() selection = new EventEmitter() + @Output() invalidFormat = new EventEmitter() + + private acceptedMediaTypes: string[] + + ngOnInit(): void { + this.acceptedMediaTypes = this.accept.split(',') + } + + selectedFile: File | null + selectedFileName: string + + onFileSelected(event: any) { + this.selectedFile = event.target.files[0] + if (this.acceptedMediaTypes.indexOf(this.selectedFile.type) >= 0) { + this.selectedFileName = this.selectedFile.name + this.control?.setValue(this.selectedFile) + this.control?.markAsDirty() + this.selection.emit(this.selectedFile) + } else { + this.invalidFormat.emit() + } + } +} + export class ComboBoxEntry { constructor( public key: any, diff --git a/src/app/modules/shared/model/config.model.ts b/src/app/modules/shared/model/config.model.ts new file mode 100644 index 0000000..2d96d4d --- /dev/null +++ b/src/app/modules/shared/model/config.model.ts @@ -0,0 +1,24 @@ +export class Config { + static readonly INSTANCE_NAME = 'instance.name' + static readonly INSTANCE_LOGO_PATH = 'instance.logo.path' + static readonly INSTANCE_ICON_PATH = 'instance.icon.path' + static readonly INSTANCE_URL = 'instance.url' + static readonly DATABASE_URL = 'database.url' + static readonly DATABASE_USER = 'database.user' + static readonly DATABASE_PASSWORD = 'database.password' + static readonly DATABASE_VERSION = 'database.version.supported' + static readonly TOUCH_UP_KIT_CACHE_PDF = 'touchupkit.pdf.cache' + static readonly EMERGENCY_MODE = 'env.emergency' + static readonly APP_VERSION = 'env.version' + static readonly JAVA_VERSION = 'env.java.version' + static readonly OPERATING_SYSTEM = 'env.os' + + constructor( + public key: string, + public content: string, + public lastUpdated: string, + public requireRestart: boolean, + public editable: boolean + ) { + } +} diff --git a/src/app/modules/shared/service/config.service.ts b/src/app/modules/shared/service/config.service.ts new file mode 100644 index 0000000..880b697 --- /dev/null +++ b/src/app/modules/shared/service/config.service.ts @@ -0,0 +1,61 @@ +import {Injectable} from '@angular/core' +import {Config} from '../model/config.model' +import {Observable} from 'rxjs' +import {ApiService} from './api.service' +import {FormControl} from '@angular/forms' + +const imageConfigsKeys = [ + Config.INSTANCE_LOGO_PATH, + Config.INSTANCE_ICON_PATH +] + +@Injectable({ + providedIn: 'root' +}) +export class ConfigService { + constructor( + private api: ApiService + ) { + } + + get(key: string): Observable { + return this.api.get(`/config/${key}`) + } + + set(configs: Map): Observable { + const body = [] + for (let key in configs) { + const control = configs[key] + if (control.dirty && key.indexOf('path') < 0) { + body.push({key, content: control.value}) + } + } + + const subscriptions = [] + imageConfigsKeys.forEach(key => { + if (configs[key].dirty) { + subscriptions.push(this.setImage(key, configs[key].value)) + } + }) + + while (subscriptions.length > 0) { + const subscription = subscriptions.pop().subscribe({ + next: () => subscription.unsubscribe() + }) + } + + return this.api.put('/config', body) + } + + setImage(key: string, image: File): Observable { + const body = new FormData() + body.append('key', key) + body.append('image', image) + + return this.api.put('/config/image', body) + } + + restart(): Observable { + return this.api.post('/config/restart') + } +} diff --git a/src/app/modules/shared/utils/utils.ts b/src/app/modules/shared/utils/utils.ts index e9eaab1..4ff71e9 100644 --- a/src/app/modules/shared/utils/utils.ts +++ b/src/app/modules/shared/utils/utils.ts @@ -1,5 +1,5 @@ /** Returns [value] if it is not null or [or]. */ -import {DateTimeFormatter, LocalDate} from 'js-joda' +import {DateTimeFormatter, LocalDate, LocalDateTime} from 'js-joda' import {TouchUpKit} from '../model/touch-up-kit.model' import {environment} from '../../../../environments/environment' @@ -32,13 +32,27 @@ export function openRawUrl(url: string) { const dateFormatter = DateTimeFormatter .ofPattern('dd-MM-yyyy') +const dateTimeFormatter = DateTimeFormatter + .ofPattern('dd-MM-yyyy HH:mm:ss') export function formatDate(date: string): string { return LocalDate.parse(date).format(dateFormatter) } +export function formatDateTime(dateTime: string): string { + return LocalDateTime.parse(dateTime).format(dateTimeFormatter) +} + export function reduceDashes(arr: string[]): string { return arr.reduce((acc, cur) => { return `${acc} - ${cur}` }) } + +export function readFile(file: File, consumer: (any) => void) { + const reader = new FileReader() + reader.onload = (e: any) => { + consumer(e.target.result) + } + reader.readAsDataURL(file) +} diff --git a/src/app/pages/administration/administration.component.ts b/src/app/pages/administration/administration.component.ts index 3406db0..c67a13e 100644 --- a/src/app/pages/administration/administration.component.ts +++ b/src/app/pages/administration/administration.component.ts @@ -12,5 +12,6 @@ export class AdministrationComponent extends SubMenuComponent { links: NavLink[] = [ {route: '/admin/user', title: 'Utilisateurs', permission: Permission.VIEW_USERS}, {route: '/admin/group', title: 'Groupes', permission: Permission.VIEW_USERS}, + {route: '/admin/config', title: 'Configuration', permission: Permission.ADMIN} ] } diff --git a/src/assets/favicon.png b/src/assets/favicon.png index 22ef30a4b70143ebc688140be4fb623322731067..1e877b7c56f791c9d054152b2bc3f64b2e394de2 100644 GIT binary patch delta 3250 zcmV;j3{CUjH`z3hBmw@BB_DrVcH=q@{O2lW34DPdmjm!|&g@{8e+!hX*oobVyZh^& zu@XzBWj?A31yFAO@4w6a2d~5-yG&e5E5+lLTW(QsQT_3f{T_VY@B5X`ukiVy-CbWq zG^LL_YdP<~wTG*N<0QDEyN+rOd)x^$CQr=B>N982`&ilF|pl< zLsCYYp!GHr0P+kce>W@lnU(7*ylDSKN-9UYL&GDO?_;|*mK%HD>xYcglpbM!%-d7+d=v8-7AhBT8N-! zg&anZu)^Hu9y+F&47;M?)}WxOUZ zNE_yg1&Y_B3#D*s%XE}B(Fr#a&PS{T9%l!D2xAi_6A~EkB_s+Zc#e@n0FD*>2s)|= zny8>45XFI6#+*_CIW^kY_zj1BZ=`rM!?QpHrIHxbv?Solj1BtXT%jSMM45;RF;!|L zC6!#Ll)}Z#PdI;ZX5zxkl^aVhmDlN7IbEH-px7u10!JvYk1-};DAfi!| zW+g4kTD7Tk$fxH{y>#x?_10y?kp_HXl){H`d~2%5LO~HT;zM)#~;#T?`R7xI7O&59r519)Bl8@|K(U9CFPrzAb~iTdV{XU~T?;f#k`0Lvk9c;psK zIHh8S1bMh8)`R}v@7_J{AKkB@tKx^D@Fstjp)F9Q1BpH5ft^8>4sZj>KoW3B867nl zN;pp7y+HV7WmzTPO%uMFdQr&5)Z)Xe!%uT@GPNXAx$AL&<{`b#j#5k%d1KB@kV_1Z zOCyZKLwP2;L}SGuh@otBhZ>_+2g<1^($Ah6HJe(zpBe!BN%Xj1gwI>iX*=Uh%w0ccU`*|knF_wk#w+whb&-C*=m34sYnBs zFRU!YUVK3lPL7PxOYIV}ra1kaObOLsn2--7M7J>bMHHypk+VJ1Z;v!nrhe2NVS~P4 zn>Xt{(>1Jjr|W)HD6n322+iAC-h>>nxIX%K7Re6-9M1mj9HHPdo`D~dwmSes(pK!! z03Zzm(062DRpjkNNYjuxVT*qU&`~@W;HMS%=?8wPZj8PO>`$)o{Olgh=Bt}+yD>LA z%;j`5zk?g5D%&y;e2yOtb)fRUH`LZ14E6BSy1o`)?pgJ-;+F zx?{R~ZSLo6?|{&Axz`BBs*T~!lJ}(bqv(BJDIRl$?EuL~lXhQS8bE&{yiXwEDS+vW zYKl#`0Yo{Y8hbe}*w-PdU6I%Tq~BpZ*O#??v)n5lod4}Q^jW6;?Rk9bNAI2!%41Nq zDiX%HPh=TM;vtWL_~c>U9eXBiEu+G%tV@nqL%=#XpOl>$k*e?;1g@vilh0H#sOgFg zxLBo~bXVHtqtd1c1)P6|=a2?Z$BVihPg=2v`@wc^=lq+R+@I$B`YiYV>U>q^FA8(_ zc4dBFIiGbifLU&r&Uf~DUzKlj-5bxls`*7%?ySOp))l`Ni+fKkuf^iNS4(D00XR<` z_chao_@iiYsG0yVRn#8Eimg67AjX~@r4jaNKYohf%<*rJ3c!D?XJd9hDmaJDnExbu zRIqEcIzr2_!P57r!pU@oJ*^33zNlPZo-tKJ{Tf+%cS#7ymnIEs?ut5{J+?avf@7uF z``(9+JQW%<1UsFCA(Ob#CtC8*pchQjvToCl)$UFaDU}-)~AmCNSN-X%um96d&}-# zKN6VV!Ses-kiXRtimK5N!T$hlT$t3XfrzaD003orR9KTR2_;FPElk~W%;v$H2mc>Q zV}uYW)UNv(wiLJ;b|yQgQ~Z$;!xNigN5ZAZi6*J7ZD>1nB@Pg7vIf?}SGn5@@8%aZ z)y`{l0004llb#738%tFx4t5afkfC+5AS&XhRVYG*P%E_RU~>J0lhz3!e+n&PJUH&h zyL*qjcYx5WGS%!E2UN{6(#eFF&8>>TR|F7&fEfB@X6kd2l!E8@x`&UicX6KOeeTaO zkS~}F@QK8;OgAjz4dUrdOXs{#9A;%XPkc^1X3zzRAGxl${KmQDu)s6JMkYN^93~b^ zU95C5D;p~D6mc}KYLqWze_hUV-r}rQ>#TiG{=!f}TUq8h%?OfM#1bTkP*6t&Ral7A zs*z$MP5TKC|A^z4$fc000!EGn)SyCk{NR7^yIZq3IpHRS5YT&7=F@aLv|!TIZdHh1m4f+n{vR= zEzrN__SW9V=>w3Vu2MI^!67hOqU?2#clUPp_V1Z?e?K=>a*w`#o?-w14eGNl3jqO> zZwz&lLJcsJ7!4|uat*v9H#uWCF=8!cH8MCYG-5J2EnzV=W-T}}I59agF=AyhHaL?| z4p0(7}a&2LBJtApsVP|D8aBgRln+`$_GBPnXIW{#kH8n6WFf)_u4jBP7lO7K~ zlXejCla~-&Bs61UF*0E=FfBG^VKyx^Vl_A|Vl*%~EigGUIW#mhH!x#4F_RY&LK8AD zGBGVMH7znXR5CI;G%z|fG?Q5oFSC9UKL~&D8XTHpvmFn*aa+=1D|BRA}Dqnb{5l zAqYi*^#6a^zBZH8vZ6W z&5|W#%X&^Cl7^5+=lp)>IoETY=RDv0`u_3xT(9^0bHDHVy6y>uS>+UQ@ID33%$tg0 z0ephQUO@H-BxkrlBFB0;omT44)Gy6)C7n^dEz#Y#U%N7KyKdT-wLtQI^z{BLkhXV1 zP+X*@%^d$`VjV%9n2i(2a7(%>&z7D&Vhn)Pbt51+K~ZI}5Mw;ty0qZy!tr z`Gy~AFb_f#1I~9CkkuT;+HrJlhs~+;(Hp0jv&T(TB)IC&)k-r*+>D_B>GwkidJbUq z)j#g=^t=mQkQy%%M}Yv8%bxbDaL}Y{&VGS^6D+bZlXA zs*Jyg?BF|Dw&Kre8@HxcT$^IhZm9)+i&K96%gSEtrlE6+avuY$YJt zXsAG^)Vs(;ZV-oycZVAt8JtZY(A8{|$VKN5o4d?epleu0j(mNZ2`i9#N?`Gx2YNx3 z>sv$WLZCnvgJ?Q}>v@DQ!!@k;U~(QE`vqPj)4-mcK7-agBQ{#_GX(w@(a=o!xRvD;bI%vKfh={3Ut) znbVH)F|Q|Y*~HbCKja%dB0qp|>*K^c3%F_e+{+z`)vpbV3~SzjzdOGNw9)L9kkm0r zTj#?|V;j%U+HX*y;y4B?J9*2LtSAT4g*I}C{F;f-o+9o+4qtQqIXLcb|_lClFUdX;di0>lft$1APn zUT^dlI~g?_Qg{7MYrCHXc53UWbpF^+FWRV#Z-aP8MV=?A0 zL`lLMsmu^S5jVo_`V90AuHBIunP@QXJ1_@rJgsl-5F;Zx z=wEL^N#{9)U+vddlp<4Z( znnp{>pmj694%Ou4Mc{f)Av;fyA-!tU<1=%NV`R}eGXyFGQR%Ner>?UTeAWS}Zp__i zsIJ`AJ~H15Yc(YVC{?9ApW0TrxHagAy)5;VJrLdBmSV(~VuVQ)d(ti5n9z}_#nIaR zE97j4_385~Q)c1cMye-A<`+glMB*c;SyiD~3+D!EL+PaCZ7B~^?}*3!CwbmS%MfD#80o-ll&r<-l_4L%)E*j z65Xc;{kU!86RkLoU(xZP(cN{}nTfySbdJ@=qC6`^3AhJ-!9T`_$Y(H7X^_+#$j9j? z&3`4ZaMw1=iU{e)`FU<6z&w22`@b1CslPSO*c&B9vwx5DnR!XOhqL)C^X}lInRCM% z5k)`TuE=6n`!?UK?Kw?$f8<^oAv)4jgI<%zhXjs$JY8x;AECx}arP?ty3X`Dy^NSX zf$M4!cYtLieUo*BQ)p{)PsgtbZQ(1YvW2EACTKp zgKW_ZDdEqy?S;m^wBtYT&h7iratYl$arJV^rI=8fv!?S_4vMPljMi}Cj3*1Ks^P@_kU*?h zgwJYk(Cji*5&OPvD09M^aBExDaXuV_D0&e1ga>k$!t<^Kb~a1hUrn=IycGLj4k)AX z+9dcyk_0Nh#KP3I@+7~7m+Up6 zsyi#8E@*Mh^D<MOVph$nHH3!7c1QC{)hR2GD2 zhAHs_Aa&@;_$cS$Kx1USFwUfiziv|Nl+N<@h80^Vc-xrYNoFKC90USKQ*xNkK=Vbd zI9cP*yA@pF)4g=Mise!Phu1CZGsZFpk(6;}VSrWIRZV5Rvo18bjuc@gjdt?CLH8flee;z*)gy1y6q>`qB-ex~K<{=#PTK zf7UX7cy~B~=ms}9Dim(`_#XjqgsiNJv@AjzfrL}U*~k+9-hqTaRlWq7Kmz7x>vyfU z8y0>fgUiXtQo7ibDfw)<Z?9Q8`&zc}00?Bt}_DS^?>XlvYu2k(G8s$|JE@MOUc81PZM5)2k{=#t ziZ^xj!k~%&Z1i{VpHAi&cTWNlA8hb9srZgU{u%4vAY}nUqcm`c0K#fNZvtiD&_Qc@-vK0$V!2h4kf8qY3|NUtF{-^w}81RQ&$Im|)@9FMAG$r}|yf z{@@$=nn6cRCZd7KC8i~Me#lwaU`(FpQLE2DSIg34W-UVt(k9GCFE@DzWXwoenP?lb z;j%e5BXTcZr-&OA@|2@aq1i|QpnMSL@Pu%Ik|rx1UyRueZGQG@EJ!e} zU*St?W&>w0-H^cLSqq`QW$9HiN}Ecaq7$xUw3**}I%~|=gklZ;uCA<2SI{_EDn0+q zk1Q%40f5H8!@dTUAxY4p8AFSbdoP!#V{2HVhD*3IColor Recipes Explorer - + diff --git a/src/styles.sass b/src/styles.sass index 5570317..218755c 100644 --- a/src/styles.sass +++ b/src/styles.sass @@ -170,7 +170,7 @@ div.empty .alert p margin-bottom: 0 -.empty-text +.light-text color: rgba(0, 0, 0, 0.54) .dark-background