Start rewriting mix editor

This commit is contained in:
William Nolin 2021-11-16 19:58:52 -05:00
parent cb51e66d11
commit c3122c2e3e
14 changed files with 447 additions and 32 deletions

View File

@ -71,7 +71,7 @@
<!-- </mat-form-field>--> <!-- </mat-form-field>-->
<!-- <cre-select [entries]="getAvailableMaterials(mixMaterial)"></cre-select>--> <cre-select [entries]="getAvailableMaterials(mixMaterial)"></cre-select>
</td> </td>
</ng-container> </ng-container>

View File

@ -0,0 +1,6 @@
<cre-mix-form
*ngIf="recipe"
[recipe]="recipe"
[materialTypes]="materialTypes$"
[materials]="materials$">
</cre-mix-form>

View File

@ -0,0 +1,8 @@
<cre-mix-info-form
[recipe]="recipe"
[materialTypes]="materialTypes">
</cre-mix-info-form>
<cre-mix-materials-form
[materials]="materials">
</cre-mix-materials-form>

View File

@ -0,0 +1,10 @@
<cre-form [formControls]="controls" class="mx-auto">
<cre-form-title>
Ajouter un mélange à la couleur {{recipe.company.name}} - {{recipe.name}}
</cre-form-title>
<cre-form-content>
<cre-input [control]="controls.name" label="Name" icon="form-textbox"></cre-input>
<cre-select [control]="controls.materialType" label="Type de produit" [entries]="materialTypeEntries"></cre-select>
</cre-form-content>
</cre-form>

View File

@ -0,0 +1,44 @@
<cre-table class="mx-auto mt-5" [dataSource]="mixMaterials" [columns]="columns">
<ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef>Position</th>
<td mat-cell *matCellDef="let mixMaterial">{{mixMaterial.position + 1}}</td>
</ng-container>
<ng-container matColumnDef="positionButtons">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let mixMaterial">
<cre-table-position-buttons
[position]="mixMaterial.position"
[min]="0"
[max]="mixMaterials.length - 1"
(positionChange)="updatePosition(mixMaterial, $event)">
</cre-table-position-buttons>
</td>
</ng-container>
<ng-container matColumnDef="material">
<th mat-header-cell *matHeaderCellDef>Produit</th>
<td mat-cell *matCellDef="let mixMaterial">
<cre-combo-box
[control]="getControls(mixMaterial.position).materialId"
[entries]="getAvailableMaterialEntries(mixMaterial.position)">
</cre-combo-box>
</td>
</ng-container>
<ng-container matColumnDef="quantity">
<th mat-header-cell *matHeaderCellDef>Quantité</th>
<td mat-cell *matCellDef="let mixMaterial">
<cre-input [control]="getControls(mixMaterial.position).quantity" type="number"></cre-input>
</td>
</ng-container>
<ng-container matColumnDef="endButton">
<th mat-header-cell *matHeaderCellDef>
<cre-accent-button (click)="addRow()" [disabled]="materialCount - mixMaterials.length <= 0">Ajouter</cre-accent-button>
</th>
<td mat-cell *matCellDef="let mixMaterial">
<cre-warn-button (click)="removeRow(mixMaterial)" [disabled]="mixMaterials.length === 1">Retirer</cre-warn-button>
</td>
</ng-container>
</cre-table>

View File

@ -0,0 +1,162 @@
import {AfterViewInit, ChangeDetectorRef, Component, Input, OnDestroy, ViewChild} from "@angular/core";
import {CreTable} from "../../shared/components/tables/tables";
import {MixMaterialDto, sortMixMaterialsDto} from "../../shared/model/recipe.model";
import {Observable, Subject} from "rxjs";
import {Material} from "../../shared/model/material.model";
import {FormControl, Validators} from "@angular/forms";
import {takeUntil} from "rxjs/operators";
import {CreInputEntry} from "../../shared/components/inputs/inputs";
@Component({
selector: 'cre-mix-materials-form',
templateUrl: 'materials-form.html'
})
export class MixMaterialsForm implements AfterViewInit, OnDestroy {
@ViewChild(CreTable) table: CreTable<MixMaterialDto>
@Input() materials: Observable<Material[]>
mixMaterials: MixMaterialDto[] = []
columns = ['position', 'positionButtons', 'material', 'quantity', 'endButton']
private _allMaterials: Material[]
private _controls: ControlsByPosition[] = []
private _availableMaterialsEntries: MaterialEntriesByPosition[] = []
private _destroy$ = new Subject<boolean>()
constructor(
private cdRef: ChangeDetectorRef
) {
}
ngAfterViewInit() {
this.materials.subscribe({
next: materials => {
this._allMaterials = materials
this.addRow()
this.cdRef.detectChanges()
}
})
}
ngOnDestroy() {
this._destroy$.next(true)
this._destroy$.complete()
}
addRow() {
const position = this.nextPosition;
const mixMaterial = new MixMaterialDto(0, 0, false, position);
const materialIdControl = new FormControl(null, Validators.required)
const quantityControl = new FormControl(null, Validators.required)
materialIdControl.valueChanges
.pipe(takeUntil(this._destroy$))
.subscribe({
next: materialId => {
this.mixMaterials.filter(x => x.materialId === mixMaterial.materialId)[0].materialId = materialId
this.refreshAvailableMaterials()
}
})
this._controls.push({
position,
controls: {
materialId: materialIdControl,
quantity: quantityControl
}
})
this._availableMaterialsEntries.push({
position,
entries: this.filterMaterials(mixMaterial)
})
this.mixMaterials.push(mixMaterial)
this.table.renderRows()
}
removeRow(mixMaterial: MixMaterialDto) {
this.mixMaterials = this.mixMaterials.filter(x => x.position != mixMaterial.position)
for (let position = mixMaterial.position + 1; position < this.mixMaterials.length; position++) {
this.updatePosition(this.getMixMaterialByPosition(position), position - 1, false)
}
}
updatePosition(mixMaterial: MixMaterialDto, newPosition: number, switchPositions = true) {
const currentPosition = mixMaterial.position
this.getControlsByPosition(currentPosition).position = newPosition
this.getMaterialEntriesByPosition(currentPosition).position = newPosition
if (switchPositions) {
this.updatePosition(this.getMixMaterialByPosition(newPosition), currentPosition, false)
}
mixMaterial.position = newPosition
this.sortTable()
}
getControls(position: number): MixMaterialControls {
return this.getControlsByPosition(position).controls
}
getAvailableMaterialEntries(position: number): CreInputEntry[] {
return this.getMaterialEntriesByPosition(position).entries
}
get materialCount(): number {
return this._allMaterials ? this._allMaterials.length : 0;
}
private get nextPosition(): number {
return this.mixMaterials.length
}
private getMixMaterialByPosition(position: number): MixMaterialDto {
return this.mixMaterials.filter(x => x.position === position)[0]
}
private getControlsByPosition(position: number): ControlsByPosition {
return this._controls.filter(control => control.position === position)[0]
}
private getMaterialEntriesByPosition(position: number): MaterialEntriesByPosition {
return this._availableMaterialsEntries.filter(control => control.position === position)[0]
}
private refreshAvailableMaterials() {
this.mixMaterials
.sort((a, b) => a.position - b.position)
.forEach(mixMaterial => {
this.getMaterialEntriesByPosition(mixMaterial.position).entries = this.filterMaterials(mixMaterial)
})
}
private sortTable() {
this.mixMaterials = sortMixMaterialsDto(this.mixMaterials)
this.table.renderRows()
}
private filterMaterials(mixMaterial: MixMaterialDto): CreInputEntry[] {
return this._allMaterials
.filter(material => mixMaterial.materialId === material.id || this.mixMaterials.filter(mm => mm.materialId === material.id).length === 0)
.map(material => new CreInputEntry(material.id, material.name))
}
}
interface MixMaterialControls {
materialId: FormControl
quantity: FormControl
}
interface ControlsByPosition {
position: number
controls: MixMaterialControls
}
interface MaterialEntriesByPosition {
position: number
entries: CreInputEntry[]
}

View File

@ -0,0 +1,105 @@
import {AfterViewInit, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {SubscribingComponent} from "../../shared/components/subscribing.component";
import {MixMaterialDto, Recipe, sortMixMaterialsDto} from "../../shared/model/recipe.model";
import {ErrorService} from "../../shared/service/error.service";
import {ActivatedRoute, Router} from "@angular/router";
import {RecipeService} from "../services/recipe.service";
import {FormControl, Validators} from "@angular/forms";
import {Observable, Subject} from "rxjs";
import {MaterialType} from "../../shared/model/materialtype.model";
import {MaterialTypeService} from "../../material-type/service/material-type.service";
import {CreInputEntry} from "../../shared/components/inputs/inputs";
import {filter, map, takeUntil, tap} from "rxjs/operators";
import {Material} from "../../shared/model/material.model";
import {MaterialService} from "../../material/service/material.service";
import {CreTable} from "../../shared/components/tables/tables";
import {MatTable} from "@angular/material/table";
@Component({
selector: 'cre-mix-add',
templateUrl: 'add.html'
})
export class MixAdd extends SubscribingComponent {
materialTypes$ = this.materialTypeService.all
materials$: Observable<Material[]>
private _recipe: Recipe | null
constructor(
private recipeService: RecipeService,
private materialTypeService: MaterialTypeService,
private materialService: MaterialService,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
) {
super(errorService, activatedRoute, router)
}
ngOnInit() {
this.fetchRecipe()
}
private fetchRecipe() {
const recipeId = this.urlUtils.parseIntUrlParam('recipeId')
this.subscribe(
this.recipeService.getById(recipeId),
recipe => this.recipe = recipe
)
}
set recipe(recipe: Recipe) {
this._recipe = recipe;
this.materials$ = this.materialService.getAllForMixCreation(recipe.id)
}
get recipe(): Recipe {
return this._recipe;
}
}
@Component({
selector: 'cre-mix-form',
templateUrl: 'form.html'
})
export class MixForm {
@Input() recipe: Recipe
@Input() materialTypes: Observable<MaterialType[]>
@Input() materials: Observable<Material[]>
}
@Component({
selector: 'cre-mix-info-form',
templateUrl: 'info-form.html'
})
export class MixInfoForm implements OnInit {
@Input() recipe: Recipe
@Input() materialTypes: Observable<MaterialType[]>
materialTypeEntries: Observable<CreInputEntry[]>
controls: any
ngOnInit() {
this.materialTypeEntries = this.materialTypes.pipe(
map(materialTypes => {
return materialTypes.map(materialType => new CreInputEntry(materialType.id, materialType.name))
})
)
this.controls = {
name: new FormControl(null, Validators.required),
materialType: new FormControl(null, Validators.required)
}
}
get mixName(): string {
return this.controls.name.value
}
get mixMaterialTypeId(): number {
return this.controls.materialType.value
}
}

View File

@ -5,6 +5,7 @@ import {MixEditComponent} from './pages/mix/mix-edit/mix-edit.component';
import {MixAddComponent} from './pages/mix/mix-add/mix-add.component'; import {MixAddComponent} from './pages/mix/mix-add/mix-add.component';
import {RecipeAdd, RecipeEdit} from './recipes'; import {RecipeAdd, RecipeEdit} from './recipes';
import {RecipeList} from './list'; import {RecipeList} from './list';
import {MixAdd} from "./mix/mix";
const routes: Routes = [{ const routes: Routes = [{
path: 'list', path: 'list',
@ -17,7 +18,7 @@ const routes: Routes = [{
component: RecipeEdit component: RecipeEdit
}, { }, {
path: 'add/mix/:recipeId', path: 'add/mix/:recipeId',
component: MixAddComponent component: MixAdd
}, { }, {
path: 'edit/mix/:recipeId/:id', path: 'edit/mix/:recipeId/:id',
component: MixEditComponent component: MixEditComponent

View File

@ -21,6 +21,9 @@ import {CreButtonsModule} from '../shared/components/buttons/buttons.module';
import {RecipeAdd, RecipeEdit, RecipeForm} from './recipes'; import {RecipeAdd, RecipeEdit, RecipeForm} from './recipes';
import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module'; import {CreActionBarModule} from '../shared/components/action-bar/action-bar.module';
import {RecipeList} from './list'; import {RecipeList} from './list';
import {MixAdd, MixForm, MixInfoForm} from "./mix/mix";
import {CreTablesModule} from "../shared/components/tables/tables.module";
import {MixMaterialsForm} from "./mix/materials-form";
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -38,7 +41,11 @@ import {RecipeList} from './list';
RecipeForm, RecipeForm,
RecipeAdd, RecipeAdd,
RecipeEdit, RecipeEdit,
RecipeList RecipeList,
MixAdd,
MixForm,
MixInfoForm,
MixMaterialsForm
], ],
exports: [ exports: [
UnitSelectorComponent UnitSelectorComponent
@ -51,7 +58,8 @@ import {RecipeList} from './list';
MatSortModule, MatSortModule,
CreInputsModule, CreInputsModule,
CreButtonsModule, CreButtonsModule,
CreActionBarModule CreActionBarModule,
CreTablesModule
] ]
}) })
export class RecipesModule { export class RecipesModule {

View File

@ -16,8 +16,8 @@
</mat-error> </mat-error>
<mat-autocomplete #auto="matAutocomplete"> <mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let entry of entries | async" [value]="entry.value"> <mat-option *ngFor="let entry of getEntries()" [value]="entry.value">
{{entry.display || entry.value}} {{entry.value}}
</mat-option> </mat-option>
</mat-autocomplete> </mat-autocomplete>
</mat-form-field> </mat-form-field>

View File

@ -1,5 +1,5 @@
import { import {
AfterViewInit, AfterViewInit, ChangeDetectorRef,
Component, Component,
ContentChild, ContentChild,
Directive, Directive,
@ -52,6 +52,12 @@ export class CreInputComponent extends _CreInputBase implements AfterViewInit {
fieldRequired = false fieldRequired = false
constructor(
private cdRef: ChangeDetectorRef
) {
super();
}
ngAfterViewInit() { ngAfterViewInit() {
const element = this.input.nativeElement const element = this.input.nativeElement
element.type = this.type element.type = this.type
@ -60,6 +66,8 @@ export class CreInputComponent extends _CreInputBase implements AfterViewInit {
element.autocomplete = this.autocomplete ? 'on' : 'off' element.autocomplete = this.autocomplete ? 'on' : 'off'
this.fieldRequired = this.control ? this.control.validator && this.control.validator({} as AbstractControl)?.required : this.required this.fieldRequired = this.control ? this.control.validator && this.control.validator({} as AbstractControl)?.required : this.required
this.cdRef.detectChanges()
} }
} }
@ -148,12 +156,11 @@ export class CreChipInputComponent implements OnInit {
templateUrl: 'combo-box.html', templateUrl: 'combo-box.html',
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class CreComboBoxComponent implements OnInit { export class CreComboBoxComponent {
@Input() control: AbstractControl @Input() control: AbstractControl
@Input() label: string @Input() label: string
@Input() icon: string @Input() icon: string
@Input() required = true @Input() required = true
@Input() entries: Observable<CreInputEntry[]>
@ContentChild(TemplateRef) errors: TemplateRef<any> @ContentChild(TemplateRef) errors: TemplateRef<any>
@ -162,22 +169,38 @@ export class CreComboBoxComponent implements OnInit {
private _destroy$ = new Subject<boolean>(); private _destroy$ = new Subject<boolean>();
private _entries: CreInputEntry[] private _entries: CreInputEntry[]
private _controlsInitialized = false
ngOnInit() { @Input()
this.entries.pipe(takeUntil(this._destroy$)) set entries(entries: Observable<CreInputEntry[]> | CreInputEntry[]) {
.subscribe({ if (isObservable(this.entries)) {
next: entries => { (entries as Observable<CreInputEntry[]>).pipe(takeUntil(this._destroy$))
this._entries = entries .subscribe({
next: entries => {
if (this.control.value) { this._entries = entries
this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value)
} }
})
} else {
this._entries = (entries as CreInputEntry[])
}
if (this.control.disabled) { this.initControls()
this.internalControl.disable() }
}
} getEntries(): CreInputEntry[] {
}) return this._entries
}
private initControls() {
if (this._controlsInitialized) return
if (this.control.value) {
this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value)
}
if (this.control.disabled) {
this.internalControl.disable()
}
this.internalControl = new FormControl({ this.internalControl = new FormControl({
value: null, value: null,
@ -193,6 +216,8 @@ export class CreComboBoxComponent implements OnInit {
} }
} }
}) })
this._controlsInitialized = true
} }
private findEntryByKey(key: any): CreInputEntry | null { private findEntryByKey(key: any): CreInputEntry | null {
@ -399,13 +424,9 @@ export class CreSliderInputComponent {
selector: 'cre-select', selector: 'cre-select',
templateUrl: 'select.html' templateUrl: 'select.html'
}) })
export class CreSelectComponent extends _CreInputBase implements AfterViewInit { export class CreSelectComponent extends _CreInputBase {
@Input() entries: CreInputEntry[] | Observable<CreInputEntry[]> @Input() entries: CreInputEntry[] | Observable<CreInputEntry[]>
ngAfterViewInit(): void {
console.log(this.entries)
}
get entriesAreObservable(): boolean { get entriesAreObservable(): boolean {
return isObservable(this.entries) return isObservable(this.entries)
} }

View File

@ -0,0 +1,17 @@
<ng-container *ngIf="!hidden">
<button
mat-mini-fab
color="primary"
class="mr-1"
[disabled]="position <= min"
(click)="decreasePosition()">
<mat-icon svgIcon="arrow-up"></mat-icon>
</button>
<button
mat-mini-fab
color="primary"
[disabled]="position >= max"
(click)="increasePosition()">
<mat-icon svgIcon="arrow-down"></mat-icon>
</button>
</ng-container>

View File

@ -1,20 +1,26 @@
import {NgModule} from '@angular/core' import {NgModule} from '@angular/core'
import {MatTableModule} from '@angular/material/table' import {MatTableModule} from '@angular/material/table'
import {CommonModule} from '@angular/common' import {CommonModule} from '@angular/common'
import {CreInteractiveCell, CreTable} from './tables' import {CreInteractiveCell, CrePositionButtons, CreTable} from './tables'
import {MatButtonModule} from "@angular/material/button";
import {MatIconModule} from "@angular/material/icon";
@NgModule({ @NgModule({
declarations: [ declarations: [
CreTable, CreTable,
CreInteractiveCell CreInteractiveCell,
CrePositionButtons
], ],
imports: [ imports: [
MatTableModule, MatTableModule,
CommonModule CommonModule,
MatButtonModule,
MatIconModule
], ],
exports: [ exports: [
CreTable, CreTable,
CreInteractiveCell, CreInteractiveCell,
CrePositionButtons
] ]
}) })
export class CreTablesModule { export class CreTablesModule {

View File

@ -2,9 +2,9 @@ import {
AfterContentInit, AfterContentInit,
Component, Component,
ContentChildren, ContentChildren,
Directive, Directive, EventEmitter,
HostBinding, HostBinding,
Input, Input, Output,
QueryList, QueryList,
ViewChild, ViewChild,
ViewEncapsulation ViewEncapsulation
@ -80,4 +80,31 @@ export class CreTable<T> implements AfterContentInit {
this.interactiveCells.forEach(cell => cell.selectedIndex = index) this.interactiveCells.forEach(cell => cell.selectedIndex = index)
} }
} }
renderRows() {
this.table.renderRows()
}
}
@Component({
selector: 'cre-table-position-buttons',
templateUrl: 'position-buttons.html'
})
export class CrePositionButtons {
@Input() position = 0
@Input() min = 0
@Input() max: number
@Input() hidden = false
@Output() positionChange = new EventEmitter<number>();
increasePosition() {
this.position += 1;
this.positionChange.emit(this.position)
}
decreasePosition() {
this.position -= 1;
this.positionChange.emit(this.position)
}
} }