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>-->
<!-- <cre-select [entries]="getAvailableMaterials(mixMaterial)"></cre-select>-->
<cre-select [entries]="getAvailableMaterials(mixMaterial)"></cre-select>
</td>
</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 {RecipeAdd, RecipeEdit} from './recipes';
import {RecipeList} from './list';
import {MixAdd} from "./mix/mix";
const routes: Routes = [{
path: 'list',
@ -17,7 +18,7 @@ const routes: Routes = [{
component: RecipeEdit
}, {
path: 'add/mix/:recipeId',
component: MixAddComponent
component: MixAdd
}, {
path: 'edit/mix/:recipeId/:id',
component: MixEditComponent

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import {
AfterViewInit,
AfterViewInit, ChangeDetectorRef,
Component,
ContentChild,
Directive,
@ -52,6 +52,12 @@ export class CreInputComponent extends _CreInputBase implements AfterViewInit {
fieldRequired = false
constructor(
private cdRef: ChangeDetectorRef
) {
super();
}
ngAfterViewInit() {
const element = this.input.nativeElement
element.type = this.type
@ -60,6 +66,8 @@ export class CreInputComponent extends _CreInputBase implements AfterViewInit {
element.autocomplete = this.autocomplete ? 'on' : 'off'
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',
encapsulation: ViewEncapsulation.None
})
export class CreComboBoxComponent implements OnInit {
export class CreComboBoxComponent {
@Input() control: AbstractControl
@Input() label: string
@Input() icon: string
@Input() required = true
@Input() entries: Observable<CreInputEntry[]>
@ContentChild(TemplateRef) errors: TemplateRef<any>
@ -162,22 +169,38 @@ export class CreComboBoxComponent implements OnInit {
private _destroy$ = new Subject<boolean>();
private _entries: CreInputEntry[]
private _controlsInitialized = false
ngOnInit() {
this.entries.pipe(takeUntil(this._destroy$))
.subscribe({
next: entries => {
this._entries = entries
if (this.control.value) {
this.internalControl.setValue(this.findEntryByKey(this.control.value)?.value)
@Input()
set entries(entries: Observable<CreInputEntry[]> | CreInputEntry[]) {
if (isObservable(this.entries)) {
(entries as Observable<CreInputEntry[]>).pipe(takeUntil(this._destroy$))
.subscribe({
next: entries => {
this._entries = entries
}
})
} else {
this._entries = (entries as CreInputEntry[])
}
if (this.control.disabled) {
this.internalControl.disable()
}
}
})
this.initControls()
}
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({
value: null,
@ -193,6 +216,8 @@ export class CreComboBoxComponent implements OnInit {
}
}
})
this._controlsInitialized = true
}
private findEntryByKey(key: any): CreInputEntry | null {
@ -399,13 +424,9 @@ export class CreSliderInputComponent {
selector: 'cre-select',
templateUrl: 'select.html'
})
export class CreSelectComponent extends _CreInputBase implements AfterViewInit {
export class CreSelectComponent extends _CreInputBase {
@Input() entries: CreInputEntry[] | Observable<CreInputEntry[]>
ngAfterViewInit(): void {
console.log(this.entries)
}
get entriesAreObservable(): boolean {
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 {MatTableModule} from '@angular/material/table'
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({
declarations: [
CreTable,
CreInteractiveCell
CreInteractiveCell,
CrePositionButtons
],
imports: [
MatTableModule,
CommonModule
CommonModule,
MatButtonModule,
MatIconModule
],
exports: [
CreTable,
CreInteractiveCell,
CrePositionButtons
]
})
export class CreTablesModule {

View File

@ -2,9 +2,9 @@ import {
AfterContentInit,
Component,
ContentChildren,
Directive,
Directive, EventEmitter,
HostBinding,
Input,
Input, Output,
QueryList,
ViewChild,
ViewEncapsulation
@ -80,4 +80,31 @@ export class CreTable<T> implements AfterContentInit {
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)
}
}