diff options
author | Danijel Anđelković <adanijel99@gmail.com> | 2022-06-06 05:24:26 +0200 |
---|---|---|
committer | Danijel Anđelković <adanijel99@gmail.com> | 2022-06-06 05:24:26 +0200 |
commit | 5dc30c02319ba9fa8e8ddb33e9574272f05598fe (patch) | |
tree | e514ed32fe74b2b47dd04cd78e796daa4e28d6a1 /frontend/src/app | |
parent | d1763481d6c08c955885ed490a284e634a56296b (diff) | |
parent | ec46487761e888935411cf4daa9e740913f2ee9b (diff) |
Merge branch 'redesign' of http://gitlab.pmf.kg.ac.rs/igrannonica/neuronstellar
Diffstat (limited to 'frontend/src/app')
45 files changed, 1458 insertions, 265 deletions
diff --git a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css index 2eea561e..60b341d0 100644 --- a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css +++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css @@ -1,11 +1,47 @@ -canvas{ +.canvas-container { + height: 50%; + margin: 5px; +} - width:100% !important; - height:90% !important; +.bottom { + height: 30px; +} + +canvas { + max-height: 100%; border: 1px solid var(--ns-primary); background-color: var(--ns-bg-dark-100); border-radius: 5px; - margin: 10px; + margin: 0; font-size: 11 !important; - } -
\ No newline at end of file + padding: 0; +} + +.ns-col { + margin: 5px; +} + +.hide { + display: none !important; + background-color: red; +} + +.dl-button { + position: relative; + color: var(--offwhite); + border-radius: 4px; + border: 1px solid var(--ns-primary); + background-color: var(--ns-bg-dark-50); + margin: 5px; + padding: 10px; + cursor: pointer; + z-index: 1001; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.dl-button:hover { + background-color: var(--ns-primary); +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html index 1c711562..505dd50b 100644 --- a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html +++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html @@ -1,4 +1,28 @@ -<div #wrapper class="position-relative" style="width:100%;height:95%;"> - <canvas id="myChart" #canvas> - </canvas> +<div #wrapper class="position-relative" style="width:100%;height:95%; border: 1px solid var(--ns-accent); background-color: var(--ns-bg-dark-100); border-radius: 15px;"> + <div class="d-flex flex-column align-items-stretch" [ngClass]="{'hide':experiment.type !== ProblemType.Regression}" style="width: 100%; height: 90%;"> + <div class="canvas-container"> + <canvas #myChartmae> + </canvas> + </div> + <div class="canvas-container"> + <canvas #myChartmse> + </canvas> + </div> + </div> + <div class="d-flex flex-column align-items-stretch" [ngClass]="{'hide':experiment.type === ProblemType.Regression}" style="width: 100%; height: 90%;"> + <div class="canvas-container"> + <canvas #myChartloss> + </canvas> + </div> + <div class="canvas-container"> + <canvas #myChartacc> + </canvas> + </div> + </div> + <div class="bottom d-flex flex-row" style="justify-content: space-between; align-items: center;"> + <h3 class="mt-5 mx-2"><span *ngIf="modelName.length > 0 && predictor">Model treniran za konfiguraciju: {{modelName}}</span><span *ngIf="modelName.length > 0 && !predictor">Model se trenira za konfiguraciju: {{modelName}}</span></h3> + <button *ngIf="predictor" class="mt-5 mx-2 btn-clear dl-button d-flex flex-row" (click)="downloadFile();" style="display: inline-block;" matTooltip="Preuzmi H5" matTooltipPosition="above"> + <mat-icon>download</mat-icon> <div>H5</div> + </button> + </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts index 7d21129c..b2eec377 100644 --- a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts +++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts @@ -1,5 +1,10 @@ -import { Component, AfterViewInit, ElementRef, ViewChild } from '@angular/core'; +import { Component, AfterViewInit, ElementRef, ViewChild, Input, ViewChildren, QueryList } from '@angular/core'; import { Chart } from 'chart.js'; +import FileSaver from 'file-saver'; +import Experiment from 'src/app/_data/Experiment'; +import Model, { ProblemType } from 'src/app/_data/Model'; +import Predictor from 'src/app/_data/Predictor'; +import { PredictorsService } from 'src/app/_services/predictors.service'; @Component({ selector: 'app-line-chart', @@ -13,35 +18,53 @@ export class LineChartComponent implements AfterViewInit { dataMAE: number[] = []; dataMSE: number[] = []; dataLOSS: number[] = []; - dataValAcc:number[]=[]; - dataValMAE:number[]=[]; - dataValMSE:number[]=[]; - dataValLoss:number[]=[]; + dataValAcc: number[] = []; + dataValMAE: number[] = []; + dataValMSE: number[] = []; + dataValLoss: number[] = []; dataEpoch: number[] = []; @ViewChild('wrapper') wrapper!: ElementRef; - @ViewChild('canvas') - canvas!: ElementRef; - constructor() { - + @ViewChild('myChartacc') myChartacc!: ElementRef; + @ViewChild('myChartloss') myChartloss!: ElementRef; + @ViewChild('myChartmse') myChartmse!: ElementRef; + @ViewChild('myChartmae') myChartmae!: ElementRef; + + @Input() experiment!: Experiment; + @Input() predictor?: Predictor; + + + downloadFile() { + if (!this.predictor) { + return; + } + + const fileId = this.predictor.h5FileId; + if (fileId != undefined) + this.predictorsService.downloadH5(fileId).subscribe((response) => { + FileSaver.saveAs(response, fileId + '.h5'); + }); + } + + constructor(private predictorsService: PredictorsService) { + } width = 700; height = 400; - - myChart!: Chart; + history: any[] = []; + myChartAcc!: Chart; + myChartMae!: Chart; + myChartMse!: Chart; + myChartLoss!: Chart; + ProblemType = ProblemType; resize() { - this.width = this.wrapper.nativeElement.offsetWidth; + //this.width = this.wrapper.nativeElement.offsetWidth; this.height = this.wrapper.nativeElement.offsetHeight; - - if (this.canvas) { - this.canvas.nativeElement.width = this.width; - this.canvas.nativeElement.height = this.height; - } } - update(myEpochs: number[], myAcc: number[], myLoss: number[], myMae: number[], myMse: number[], myValAcc:number[],myValLoss:number[],myValMae:number[],myValMse:number[]) { - + update(myEpochs: number[], myAcc: number[], myLoss: number[], myMae: number[], myMse: number[], myValAcc: number[], myValLoss: number[], myValMae: number[], myValMse: number[]) { + this.dataEpoch.length = 0; this.dataEpoch.push(...myEpochs); @@ -69,14 +92,68 @@ export class LineChartComponent implements AfterViewInit { this.dataValMSE.length = 0; this.dataValMSE.push(...myValMse); - this.myChart.update(); + this.myChartAcc.update(); + this.myChartLoss.update(); + this.myChartMae.update(); + this.myChartMse.update(); } + updateAll(history: any[], totalEpochs: number) { + const myAcc: number[] = []; + const myMae: number[] = []; + const myMse: number[] = []; + const myLoss: number[] = []; + const myValLoss: number[] = []; + const myValAcc: number[] = []; + const myValMAE: number[] = []; + const myValMSE: number[] = []; + const myEpochs: number[] = []; + this.history = history; + this.history.forEach((metrics, epoch) => { + if (totalEpochs > 100) { + let epochEstimate = epoch * Math.round(Math.sqrt(totalEpochs)) + if (epochEstimate > totalEpochs) + epochEstimate = totalEpochs; + myEpochs.push(epochEstimate); + } + else + myEpochs.push(epoch + 1); + for (let key in metrics) { + let value = metrics[key]; + if (key === 'accuracy') { + myAcc.push(parseFloat(value)); + } + else if (key === 'loss') { + myLoss.push(parseFloat(value)); + } + else if (key === 'mae') { + myMae.push(parseFloat(value)); + } + else if (key === 'mse') { + myMse.push(parseFloat(value)); + } + else if (key === 'val_acc') { + myValAcc.push(parseFloat(value)); + } + else if (key === 'val_loss') { + myValLoss.push(parseFloat(value)); + } + else if (key === 'val_mae') { + myValMAE.push(parseFloat(value)); + } + else if (key === 'val_mse') { + myValMSE.push(parseFloat(value)); + } + } + }); + + this.update(myEpochs, myAcc, myLoss, myMae, myMse, myValAcc, myValLoss, myValMAE, myValMSE); + } ngAfterViewInit(): void { - + window.addEventListener('resize', () => { this.resize() }); this.resize(); - this.myChart = new Chart("myChart", + this.myChartAcc = new Chart(this.myChartacc.nativeElement, { type: 'line', data: { @@ -86,58 +163,244 @@ export class LineChartComponent implements AfterViewInit { label: 'Accuracy', data: this.dataAcc, borderWidth: 1, - + }, { label: 'Val_Accuracy', data: this.dataValAcc, borderWidth: 1 + } + ] + }, + options: { + //responsive: true, + //maintainAspectRatio: true, + plugins: { + legend: { + labels: { + // This more specific font property overrides the global property + color: 'white', + font: { + size: 10 + } + } + } }, - { - label: 'Loss', - data: this.dataLOSS, - borderWidth: 1 + scales: { + x: { + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + }, + title: { + display: true, + text: 'Epoha', + color: "white" + } + }, + y: { + beginAtZero: true, + ticks: { + color: 'white' + + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + }, + title: { + display: true, + text: 'Vrednost', + color: "white" + } + } + }, - { - label: 'Val_Loss', - data: this.dataValLoss, - borderWidth: 1 + animation: { + duration: 0 + } + + } + }, + + ); + if (this.experiment.type == ProblemType.BinaryClassification || this.experiment.type == ProblemType.MultiClassification) { } + this.myChartLoss = new Chart(this.myChartloss.nativeElement, + { + type: 'line', + data: { + labels: this.dataEpoch, + datasets: [ + { + label: 'Loss', + data: this.dataLOSS, + borderWidth: 1 + }, + { + label: 'Val_Loss', + data: this.dataValLoss, + borderWidth: 1 + }, + ] + }, + options: { + //responsive: true, + //maintainAspectRatio: true, + + plugins: { + legend: { + labels: { + // This more specific font property overrides the global property + color: 'white', + font: { + size: 10 + } + } + } }, - { - label: 'MAE', - data: this.dataMAE, - borderWidth: 1 + scales: { + x: { + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + }, + title: { + display: true, + text: 'Epoha', + color: "white" + } + }, + y: { + beginAtZero: true, + ticks: { + color: 'white' + + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + }, + title: { + display: true, + text: 'Vrednost', + color: "white" + } + } + }, - { - label: 'Val_MAE', - data: this.dataValMAE, - borderWidth: 1 + animation: { + duration: 0 + } + + } + }, + + ); + this.myChartMse = new Chart(this.myChartmse.nativeElement, + { + type: 'line', + data: { + labels: this.dataEpoch, + datasets: [ + { + label: 'MSE', + data: this.dataMSE, + borderWidth: 1 + }, + { + label: 'Val_MSE', + data: this.dataValMSE, + borderWidth: 1 + } + ] + }, + options: { + //responsive: true, + //maintainAspectRatio: true, + + plugins: { + legend: { + labels: { + // This more specific font property overrides the global property + color: 'white', + font: { + size: 10 + } + } + } }, - { - label: 'MSE', - data: this.dataMSE, - borderWidth: 1 + scales: { + x: { + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + }, + title: { + display: true, + text: 'Epoha', + color: "white" + } + }, + y: { + beginAtZero: true, + ticks: { + color: 'white' + + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + }, + title: { + display: true, + text: 'Vrednost', + color: "white" + } + } + }, - { - label: 'Val_MSE', - data: this.dataValMSE, - borderWidth: 1 + animation: { + duration: 0 } + + } + }, + + ); + this.myChartMae = new Chart(this.myChartmae.nativeElement, + { + type: 'line', + data: { + labels: this.dataEpoch, + datasets: [ + { + label: 'MAE', + data: this.dataMAE, + borderWidth: 1 + }, + { + label: 'Val_MAE', + data: this.dataValMAE, + borderWidth: 1 + }, ] }, options: { - responsive: true, - maintainAspectRatio: true, + //responsive: true, + //maintainAspectRatio: true, plugins: { legend: { - labels: { - // This more specific font property overrides the global property - color:'white', - font: { - size: 10 - } + labels: { + // This more specific font property overrides the global property + color: 'white', + font: { + size: 10 } + } } }, scales: { @@ -148,15 +411,25 @@ export class LineChartComponent implements AfterViewInit { grid: { color: "rgba(0, 99, 171, 0.5)" }, + title: { + display: true, + text: 'Epoha', + color: "white" + } }, y: { beginAtZero: true, ticks: { color: 'white' - + }, grid: { color: "rgba(0, 99, 171, 0.5)" + }, + title: { + display: true, + text: 'Vrednost', + color: "white" } } @@ -166,8 +439,17 @@ export class LineChartComponent implements AfterViewInit { } } - } + }, + ); + + } + + modelName: string = ''; + + setName(name: string) { + this.modelName = name; + this.predictor = undefined; } } diff --git a/frontend/src/app/_elements/column-table/column-table.component.html b/frontend/src/app/_elements/column-table/column-table.component.html index e6a781be..27e0f3bf 100644 --- a/frontend/src/app/_elements/column-table/column-table.component.html +++ b/frontend/src/app/_elements/column-table/column-table.component.html @@ -27,7 +27,9 @@ <th>#</th> <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'header-disabled' : !columnsChecked[i]}"> <div class="cell-align"> - #{{i + 1}} {{colInfo.columnName}} + <div class="text-overflow"> + #{{i + 1}} {{colInfo.columnName}} + </div> <mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> </div> </th> @@ -63,7 +65,9 @@ <th>Kolona</th> <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'header-disabled' : !columnsChecked[i]}"> <div class="cell-align"> - #{{i + 1}} {{colInfo.columnName}} + <div class="text-overflow"> + #{{i + 1}} {{colInfo.columnName}} + </div> <mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> </div> </th> @@ -72,7 +76,7 @@ <tbody> <tr *ngFor="let row of dataset.cMatrix; let i = index"> <th [ngClass]="{'header-disabled col-disabled' : !columnsChecked[i]}"> - <div class="text-left"> + <div class="text-left text-overflow"> {{dataset.columnInfo[i].columnName}} </div> </th> @@ -97,7 +101,9 @@ <th>Naziv</th> <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'header-disabled' : !columnsChecked[i]}"> <div class="cell-align"> - #{{i + 1}} {{colInfo.columnName}} + <div class="text-overflow"> + #{{i + 1}} {{colInfo.columnName}} + </div> <mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> </div> </th> @@ -109,7 +115,7 @@ <td *ngFor="let colInfo of dataset.columnInfo; let i = index" class="pad-fix" [ngClass]="{'text-disabled' : !columnsChecked[i]}"> <p class="verticalAlign text-left" style="font-size:13px;" *ngIf="!colInfo.isNumber">Kategorijski</p> <mat-form-field *ngIf="colInfo.isNumber"> - <mat-select matNativeControl [(value)]="this.experiment.columnTypes[i]" [disabled]="!columnsChecked[i]" (selectionChange)="columnTypeChanged(colInfo.columnName);"> + <mat-select matNativeControl [(value)]="this.experiment.columnTypes[i]" [disabled]="!columnsChecked[i]" (selectionChange)="columnTypeChanged(colInfo.columnName, i);"> <mat-option [value]="ColumnType.categorical">Kategorijski</mat-option> <mat-option [value]="ColumnType.numerical">Numerički</mat-option> </mat-select> @@ -141,8 +147,8 @@ </div> </td> </tr> - <tr style="padding: 0"> - <th class="brighter cell-align long" (click)="openEncodingDialog()"> + <tr style="padding: 0;" *ngIf="experiment.encodings!=undefined"> + <th style="background-color: var(--ns-primary);" (click)="openEncodingDialog()"> <span class="verticalAlign">Enkodiranje</span> <span class="material-icons-round verticalAlign rotate">settings</span> </th> @@ -168,11 +174,13 @@ <div *ngIf="colInfo.numNulls > 0"> <button class="w-100" mat-raised-button [ngClass]="{ 'menu-disabled' : !columnsChecked[i]}" [matMenuTriggerFor]="menu" id="main_{{colInfo.columnName}}" #nullValMenu> - <div class="cell-align"> - {{nullValOption[i]}} - <mat-icon>arrow_drop_down</mat-icon> - </div> - </button> + <div class="cell-align"> + <div [ngClass]="{'text-overflow' : experiment.nullValuesReplacers[i].option == NullValueOptions.Replace}"> + {{nullValOption[i]}} + </div> + <mat-icon>arrow_drop_down</mat-icon> + </div> + </button> <mat-menu #menu="matMenu"> <!--<button mat-menu-item (click)="MissValsDeleteClicked($event, NullValueOptions.DeleteColumns, i)" value={{colInfo.columnName}}>Obriši kolonu</button>--> <button mat-menu-item (click)="MissValsDeleteClicked($event, NullValueOptions.DeleteRows, i)" value={{colInfo.columnName}}>Obriši redove ({{colInfo.numNulls}})</button> @@ -180,12 +188,12 @@ </mat-menu> <mat-menu #fillWith="matMenu"> - <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.mean}}>Mean ({{colInfo.mean}})</button> - <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.median}}>Median ({{colInfo.median}})</button> - <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.max}}>Max ({{colInfo.max}})</button> - <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.min}}>Min ({{colInfo.min}})</button> + <button *ngIf="experiment.columnTypes[i] == ColumnType.numerical" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.mean}}>Mean ({{colInfo.mean}})</button> + <button *ngIf="experiment.columnTypes[i] == ColumnType.numerical" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.median}}>Median ({{colInfo.median}})</button> + <button *ngIf="experiment.columnTypes[i] == ColumnType.numerical" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.max}}>Max ({{colInfo.max}})</button> + <button *ngIf="experiment.columnTypes[i] == ColumnType.numerical" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.min}}>Min ({{colInfo.min}})</button> - <button *ngIf="!colInfo.isNumber" mat-menu-item [matMenuTriggerFor]="uniques">Najčešće vrednosti</button> + <button *ngIf="experiment.columnTypes[i] == ColumnType.categorical" mat-menu-item [matMenuTriggerFor]="uniques">Najčešće vrednosti</button> <button mat-menu-item [matMenuTriggerFor]="replaceWith">Unesi vrednost...</button> </mat-menu> @@ -195,7 +203,8 @@ </mat-menu> <mat-menu #replaceWith="matMenu"> - <input type="text" id={{colInfo.columnName}} mat-menu-item placeholder="Unesi vrednost..." [value] #enterAValue> + <input *ngIf="experiment.columnTypes[i] == ColumnType.categorical" type="text" id={{colInfo.columnName}} mat-menu-item placeholder="Unesi vrednost..." [value] #enterAValue> + <input *ngIf="experiment.columnTypes[i] == ColumnType.numerical" type="number" id={{colInfo.columnName}} mat-menu-item placeholder="Unesi vrednost..." [value] #enterAValue> <button [disabled]="getValue(colInfo.columnName) == ''" mat-menu-item value={{getValue(colInfo.columnName)}} (click)="MissValsReplaceClicked($event, colInfo.columnName, i)">Potvrdi unos</button> </mat-menu> </div> diff --git a/frontend/src/app/_elements/column-table/column-table.component.ts b/frontend/src/app/_elements/column-table/column-table.component.ts index 4b10401d..96818976 100644 --- a/frontend/src/app/_elements/column-table/column-table.component.ts +++ b/frontend/src/app/_elements/column-table/column-table.component.ts @@ -7,7 +7,7 @@ import { MatDialog } from '@angular/material/dialog'; import { MissingvaluesDialogComponent } from 'src/app/_modals/missingvalues-dialog/missingvalues-dialog.component'; import { MatCheckboxChange } from '@angular/material/checkbox'; import { CsvParseService } from 'src/app/_services/csv-parse.service'; -import { ProblemType } from 'src/app/_data/Model'; +import { NullValReplacer, ProblemType } from 'src/app/_data/Model'; import { ExperimentsService } from 'src/app/_services/experiments.service'; import { SaveExperimentDialogComponent } from 'src/app/_modals/save-experiment-dialog/save-experiment-dialog.component'; import { AlertDialogComponent } from 'src/app/_modals/alert-dialog/alert-dialog.component'; @@ -15,6 +15,7 @@ import Shared from 'src/app/Shared'; import { PieChartComponent } from '../_charts/pie-chart/pie-chart.component'; import { BoxPlotComponent } from '../_charts/box-plot/box-plot.component'; import { ActivatedRoute } from '@angular/router'; +import { UpdateExperimentDialogComponent } from 'src/app/_modals/update-experiment-dialog/update-experiment-dialog.component'; @Component({ selector: 'app-column-table', @@ -87,6 +88,7 @@ export class ColumnTableComponent implements AfterViewInit { if (this.route.snapshot.paramMap.get("id") == null && this.route.snapshot.paramMap.get("predictorId") == null) { this.dataset = dataset; this.setColumnTypeInitial(); + this.resetColumnEncodings(Encoding.Label); this.columnsChecked = []; this.dataset.columnInfo.forEach(column => { @@ -95,7 +97,6 @@ export class ColumnTableComponent implements AfterViewInit { this.resetInputColumns(); this.resetOutputColumn(); - this.resetColumnEncodings(Encoding.Label); this.setDeleteRowsForMissingValTreatment(); this.nullValOption = []; @@ -180,8 +181,13 @@ export class ColumnTableComponent implements AfterViewInit { this.columnTableChanged.emit(); } - columnTypeChanged(columnName: string) { - if (this.experiment.outputColumn == columnName) + columnTypeChanged(columnName: string, colIndex: number) { + + this.experiment.nullValuesReplacers[colIndex].option = NullValueOptions.DeleteRows; + this.experiment.nullValuesReplacers[colIndex].value = ""; + this.nullValOption[colIndex] = "Obriši redove (" + this.dataset?.columnInfo[colIndex].numNulls + ")"; + + if (this.experiment.outputColumn == columnName) this.changeProblemType(); else this.columnTableChangeDetected(); @@ -252,9 +258,11 @@ export class ColumnTableComponent implements AfterViewInit { resetColumnEncodings(encodingType: Encoding) { if (this.experiment != undefined && this.dataset != undefined) { this.experiment.encodings = []; + console.log("prvi: RESET COLUMN ENC, DUZINA ENCODINGS NIZA:", this.experiment.encodings.length); for (let i = 0; i < this.dataset.columnInfo.length; i++) { this.experiment.encodings.push(new ColumnEncoding(this.dataset?.columnInfo[i].columnName, encodingType)); } + console.log("drugi: RESET COLUMN ENC, DUZINA ENCODINGS NIZA:", this.experiment.encodings.length); this.columnTableChangeDetected(); } } @@ -360,16 +368,27 @@ export class ColumnTableComponent implements AfterViewInit { Object.assign(this.experiment, experiment); this.experiment._columnsSelected = true; this.experimentChanged.emit(); + this.okPressed.emit(); } }); } openUpdateExperimentDialog() { - this.experimentService.updateExperiment(this.experiment).subscribe((response) => { - Object.assign(this.experiment, response); + const dialogRef = this.dialog.open(UpdateExperimentDialogComponent, { + width: '350px', + data: { experiment: this.experiment } + }); + dialogRef.afterClosed().subscribe(experiment => { + if (experiment == undefined) + return; + if (this.experiment._id != experiment._id) + Shared.openDialog("Novi eksperiment", "Uspešno ste sačuvali novi eksperiment. Nastavite rad na njemu."); + else + Shared.openDialog("Izmena eksperimenta", "Uspešno ste izmenili podatke o eksperimentu."); + Object.assign(this.experiment, experiment); this.experiment._columnsSelected = true; this.experimentChanged.emit(); - Shared.openDialog("Izmena eksperimenta", "Uspešno ste izmenili podatke o eksperimentu."); + this.okPressed.emit(); }); } diff --git a/frontend/src/app/_elements/folder/folder.component.css b/frontend/src/app/_elements/folder/folder.component.css index 682fc645..b2363816 100644 --- a/frontend/src/app/_elements/folder/folder.component.css +++ b/frontend/src/app/_elements/folder/folder.component.css @@ -1,6 +1,5 @@ #folder { width: 100%; - } #tabs { @@ -184,11 +183,18 @@ } .file-bottom-buttons { - position: absolute; - bottom: 5px; - right: 4%; + position: relative; + display: flex; + flex-direction: row-reverse; + height: 0; +} + +.file-bottom-buttons-helper { + position: relative; display: flex; flex-direction: row-reverse; + width: fit-content; + height: 45px; } .file-button { @@ -196,10 +202,11 @@ color: var(--offwhite); border-radius: 4px; border: 1px solid var(--ns-primary); + background-color: var(--ns-bg-dark-50); margin: 5px; padding: 5px; cursor: pointer; - z-index: 1001; + z-index: 801; display: flex; flex-direction: row; align-items: center; @@ -221,4 +228,14 @@ .highlight-exp { /*font-size: 16px;*/ font-weight: 700; +} + +.text-overflow { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.selected-file-tab { + max-width: 230px !important; }
\ No newline at end of file diff --git a/frontend/src/app/_elements/folder/folder.component.html b/frontend/src/app/_elements/folder/folder.component.html index a557edde..e77e05a3 100644 --- a/frontend/src/app/_elements/folder/folder.component.html +++ b/frontend/src/app/_elements/folder/folder.component.html @@ -13,8 +13,8 @@ <a class="m-1 stretched-link tab-link" (click)="selectTab(tab)" (mouseenter)="hoverOverTab(tab)" (mouseleave)="hoverOverTab(TabType.None)">{{tabTitles[tab]}}</a> </div> - <div class="folder-tab p-1 rounded-top" *ngIf="selectedFile" [style]="'z-index:' + (selectedTab == TabType.File ? 11 : (tabsToShow.length)) + ' ;'" [ngClass]="{'selected-tab' : selectedTab == TabType.File, 'hover-tab' : hoverTab == TabType.File}"> - <a class="m-1 stretched-link tab-link" (click)="selectTab(TabType.File)" (mouseenter)="hoverOverTab(TabType.File)" (mouseleave)="hoverOverTab(TabType.None)">{{selectedFile.name}}</a> + <div class="folder-tab p-1 rounded-top selected-file-tab" *ngIf="selectedFile" [style]="'z-index:' + (selectedTab == TabType.File ? 11 : (tabsToShow.length)) + ' ;'" [ngClass]="{'selected-tab' : selectedTab == TabType.File, 'hover-tab' : hoverTab == TabType.File}"> + <a class="m-1 stretched-link tab-link text-overflow" (click)="selectTab(TabType.File)" (mouseenter)="hoverOverTab(TabType.File)" (mouseleave)="hoverOverTab(TabType.None)">{{selectedFile.name}}</a> </div> </div> <div id="selected-content" class="rounded-bottom text-offwhite"> @@ -58,19 +58,13 @@ <!--{{fileToDisplay ? fileToDisplay.name : 'No file selected.'}} {{selectedFileIndex}} {{hoveringOverFileIndex}}--> <div class="folder-inside bg-blur"> <div class="file-content" [ngClass]="{'form-hidden' : listView}"> - <div class="file-bottom-buttons" *ngIf="selectedTab != TabType.NewFile"> - <button *ngIf="this.selectedFile && selectedTab == TabType.File && privacy != Privacy.Public" class="btn-clear file-button" (click)="deleteFile(this.selectedFile, $event)"> - <mat-icon matTooltip="Obriši" matTooltipPosition="right">delete</mat-icon> - </button> - <button *ngIf="this.selectedFile && selectedTab==TabType.File && FolderType.Dataset==this.type" class="btn-clear file-button" (click)="downloadFile(this.selectedFile,$event)" style="display: inline-block;"> - <mat-icon matTooltip="Preuzmi" matTooltipPosition="before">download</mat-icon> - </button> - <!-- <button class="btn-clear file-button"> - <mat-icon>zoom_out_map</mat-icon> - </button> --> - </div> - <app-form-model [ngClass]="{'form-hidden': type != FolderType.Model}" [forExperiment]="forExperiment" [hideProblemType]="(forExperiment ? true : false)" [forProblemType]="(forExperiment ? forExperiment.type : ProblemType.Regression)" (editEvent)="onFileChange()"></app-form-model> - <app-form-dataset [ngClass]="{'form-hidden': type != FolderType.Dataset}" [forExperiment]="forExperiment" (editEvent)="onFileChange()"></app-form-dataset> + <!-- New File --> + <app-form-model #newModel [ngClass]="{'form-hidden': type != FolderType.Model || !newFileSelected}" [forExperiment]="forExperiment" [hideProblemType]="(forExperiment ? true : false)" [forProblemType]="(forExperiment ? forExperiment.type : ProblemType.Regression)"></app-form-model> + <app-form-dataset #newDataset [ngClass]="{'form-hidden': type != FolderType.Dataset || !newFileSelected}" [forExperiment]="forExperiment"></app-form-dataset> + <!-- Selected File --> + <app-form-model #selectedModel [ngClass]="{'form-hidden': type != FolderType.Model || newFileSelected}" [forExperiment]="forExperiment" [hideProblemType]="(forExperiment ? true : false)" [forProblemType]="(forExperiment ? forExperiment.type : ProblemType.Regression)" + (editEvent)="onFileChange()"></app-form-model> + <app-form-dataset #selectedDataset [ngClass]="{'form-hidden': type != FolderType.Dataset || newFileSelected}" [forExperiment]="forExperiment" (editEvent)="onFileChange()"></app-form-dataset> </div> <div [ngClass]="{'form-hidden' : !listView}" class="list-view"> <div *ngFor="let file of filteredFiles; let i = index"> @@ -82,16 +76,19 @@ {{file.lastUpdated | date}} </div> <div class="mx-2 hover-show" *ngIf="selectedTab !== TabType.PublicDatasets && selectedTab !== TabType.PublicModels"> - <button *ngIf="selectedTab==TabType.MyDatasets" class="btn-clear file-button" (click)="downloadFile(file,$event)" style="display: inline-block;"> - <mat-icon matTooltip="Preuzmi" matTooltipPosition="before">download</mat-icon> + <button *ngIf="selectedTab==TabType.MyDatasets || selectedTab==TabType.MyModels" class="btn-clear file-button" (click)="shareFile(file,$event)" style="display: inline-block;" matTooltip="Podeli" matTooltipPosition="above"> + <mat-icon>share</mat-icon> + </button> + <button *ngIf="selectedTab==TabType.MyDatasets" class="btn-clear file-button" (click)="downloadFile(file,$event)" style="display: inline-block;" matTooltip="Preuzmi" matTooltipPosition="above"> + <mat-icon>download</mat-icon> </button> - <button class="btn-clear file-button" (click)="deleteFile(file, $event)" style="display: inline-block;"> - <mat-icon matTooltip="Obriši" matTooltipPosition="right">delete</mat-icon> + <button class="btn-clear file-button" (click)="deleteFile(file, $event)" style="display: inline-block;" matTooltip="Obriši" matTooltipPosition="above"> + <mat-icon>delete</mat-icon> </button> </div> - <div class="mx-2 hover-show" *ngIf="selectedTab == TabType.PublicDatasets || selectedTab == TabType.PublicModels"> + <div class="mx-2 hover-show" *ngIf="selectedTab == TabType.PublicDatasets || selectedTab == TabType.PublicModels" matTooltip="Uvezi" matTooltipPosition="right"> <button class="btn-clear file-button" (click)="addFile(file, $event)"> - <mat-icon matTooltip="Uvezi" matTooltipPosition="right">note_add</mat-icon> + <mat-icon>note_add</mat-icon> </button> </div> </div> @@ -105,7 +102,7 @@ </div> </div> <div class="mx-2 hover-hide"> - {{predictor.lastUpdated | date}} + {{predictor.dateCreated| date}} </div> <div class="mx-2 hover-show"> <button class="btn-clear file-button" (click)="deleteFile(predictor, $event, true)"> @@ -116,7 +113,7 @@ </div> </div> - <div class="list-add" [ngSwitch]="type" *ngIf="privacy != Privacy.Public" > + <div class="list-add" [ngSwitch]="type" *ngIf="privacy != Privacy.Public"> <!-- {{privacy == Privacy.Public ? 'javni ' : ' '}} --> <button mat-raised-button *ngSwitchCase="FolderType.Dataset" (click)="selectNewFile()">Dodaj izvor podataka</button> <button mat-raised-button *ngSwitchCase="FolderType.Model" (click)="selectNewFile()">Dodaj konfiguraciju neuronske mreže</button> @@ -125,8 +122,26 @@ </div> </div> </div> + <div [ngSwitch]="newFileSelected" *ngIf="!listView"> + <div class="file-bottom-buttons" *ngIf="selectedTab != TabType.NewFile"> + <div class="file-bottom-buttons-helper"> + <button *ngIf="this.selectedFile && selectedTab == TabType.File && privacy != Privacy.Public" class="btn-clear file-button" (click)="deleteFile(selectedFile, $event)" matTooltip="Obriši" matTooltipPosition="above"> + <mat-icon>delete</mat-icon> + </button> + <button *ngIf="this.selectedFile && selectedTab==TabType.File && FolderType.Dataset==this.type" class="btn-clear file-button" (click)="downloadFile(selectedFile,$event)" style="display: inline-block;" matTooltip="Preuzmi" matTooltipPosition="above"> + <mat-icon>download</mat-icon> + </button> + <button *ngIf="this.selectedFile && selectedTab == TabType.File" class="btn-clear file-button" (click)="shareFile(selectedFile,$event)" style="display: inline-block;" matTooltip="Podeli" matTooltipPosition="above"> + <mat-icon>share</mat-icon> + </button> + </div> + <!-- <button class="btn-clear file-button"> + <mat-icon>zoom_out_map</mat-icon> + </button> --> + </div> + </div> <div id="footer" [ngSwitch]="newFileSelected" *ngIf="!listView"> - <button mat-button (click)="saveNewFile()" class="bottom-button text-offwhite rounded-bottom" *ngSwitchCase="true"> + <button mat-button (click)="saveNewFile()" class="bottom-button text-offwhite rounded-bottom" [disabled]="saveDisabled" *ngSwitchCase="true"> <div class="f-row"> <div>Sačuvaj</div> <div class="pt-1"> @@ -160,4 +175,6 @@ </button> </ng-container> </div> + + </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/folder/folder.component.ts b/frontend/src/app/_elements/folder/folder.component.ts index c9252caa..9dfabdb3 100644 --- a/frontend/src/app/_elements/folder/folder.component.ts +++ b/frontend/src/app/_elements/folder/folder.component.ts @@ -15,6 +15,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import Predictor from 'src/app/_data/Predictor'; import FileSaver from 'file-saver'; import isEqual from 'lodash.isequal'; +import { ShareDialogComponent } from 'src/app/_modals/share-dialog/share-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'app-folder', @@ -23,8 +25,10 @@ import isEqual from 'lodash.isequal'; }) export class FolderComponent implements AfterViewInit { - @ViewChild(FormDatasetComponent) formDataset!: FormDatasetComponent; - @ViewChild(FormModelComponent) formModel!: FormModelComponent; + @ViewChild('selectedDataset') formDataset!: FormDatasetComponent; + @ViewChild('selectedModel') formModel!: FormModelComponent; + @ViewChild('newDataset') formNewDataset!: FormDatasetComponent; + @ViewChild('newModel') formNewModel!: FormModelComponent; @Input() folderName: string = 'Moji podaci'; @Input() files!: FolderFile[] @@ -40,8 +44,7 @@ export class FolderComponent implements AfterViewInit { selectedFileIndex: number = -1; selectedFile?: FolderFile; hoveringOverFileIndex: number = -1; - - fileToDisplay?: FolderFile; + saveDisabled: boolean = false; @Output() selectedFileChanged: EventEmitter<FolderFile> = new EventEmitter(); @Output() fileFromRoute: EventEmitter<FolderFile> = new EventEmitter(); @@ -49,7 +52,7 @@ export class FolderComponent implements AfterViewInit { searchTerm: string = ''; - constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private router: Router, private route: ActivatedRoute) { + constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private router: Router, private route: ActivatedRoute, public dialog: MatDialog) { this.tabsToShow.forEach(tab => this.folders[tab] = []); } @@ -60,7 +63,10 @@ export class FolderComponent implements AfterViewInit { this.signalRService.hubConnection.on("NotifyDataset", (dName: string, dId: string) => { if (this.type == FolderType.Dataset) { this.refreshFiles(dId); + this.okPressed.emit(); } + this.saveDisabled = false; + console.log("Notify dataset ", this.saveDisabled); }); } else { console.warn("Dataset-Load: No connection!"); @@ -68,59 +74,82 @@ export class FolderComponent implements AfterViewInit { } displayFile() { - if (this.type == FolderType.Dataset) { - this.formDataset.dataset = <Dataset>this.fileToDisplay; - this.formDataset.existingFlag = false; - } - else if (this.type == FolderType.Model) - this.formModel.newModel = <Model>this.fileToDisplay; - } - - hoverOverFile(i: number) { - /*this.hoveringOverFileIndex = i; - if (i != -1) { - this.fileToDisplay = this.files[i]; + if (this.newFileSelected) { + if (this.type == FolderType.Dataset) { + this.formNewDataset.dataset = <Dataset>this.newFile; + this.formNewDataset.existingFlag = false; + } + else if (this.type == FolderType.Model) + this.formNewModel.newModel = <Model>this.newFile; } else { - if (this.newFileSelected) { - this.fileToDisplay = this.newFile; - } else { - this.fileToDisplay = this.files[this.selectedFileIndex]; + if (this.type == FolderType.Dataset) { + this.formDataset.dataset = <Dataset>this.selectedFile; + this.formDataset.existingFlag = false; } + else if (this.type == FolderType.Model) + this.formModel.newModel = <Model>this.selectedFile; } - this.displayFile();*/ } selectNewFile() { if (!this.newFile) { this.createNewFile(); } - this.fileToDisplay = this.newFile; this.newFileSelected = true; this.listView = false; this.displayFile(); + + this.selectedTab = TabType.NewFile; + if (this.type == FolderType.Dataset) { - this.formDataset.clear(); + this.formNewDataset.clear(); } } selectFile(file?: FolderFile) { + if (this.privacy == Privacy.Public) { + if (file) { + if (this.type == FolderType.Dataset) { + this.router.navigate(['dataset', file?._id]); + } else if (this.type == FolderType.Model) { + this.router.navigate(['model', file?._id]); + } + } + + } + + this.formDataset.resetPagging(); this.selectedFile = file; - Object.assign(this.lastFileData, this.selectedFile); - this.fileToDisplay = file; + this.updateLastFileData(file); if (this.type == FolderType.Experiment && file) { this.router.navigate(['/experiment/' + file._id]); } this.newFileSelected = false; this.listView = false; this.selectedFileChanged.emit(this.selectedFile); - this.selectTab(TabType.File); this.displayFile(); + this.selectedTab = TabType.File; + if (this.type == FolderType.Dataset) this.formDataset.loadExisting(); } + updateLastFileData(file: FolderFile | undefined) { + if (!file) return; + + Object.assign(this.lastFileData, file); + if (this.type == FolderType.Model) { + const lastModel = (<Model>this.lastFileData) + lastModel.layers = []; + (<Model>file).layers.forEach(layer => { + const clone = Object.assign({}, layer); + lastModel.layers.push(clone); + }) + } + } + goToExperimentPageWithPredictor(file: FolderFile, predictor: Predictor) { this.router.navigate(['/experiment/p/' + predictor._id]); } @@ -128,6 +157,9 @@ export class FolderComponent implements AfterViewInit { createNewFile() { if (this.type == FolderType.Dataset) { this.newFile = new Dataset(); + this.formNewDataset.files = []; + this.formNewDataset.firstInput = false; + this.formNewDataset.filename = ""; } else if (this.type == FolderType.Model) { this.newFile = new Model(); } @@ -137,6 +169,22 @@ export class FolderComponent implements AfterViewInit { this.okPressed.emit(); } + fileToShare: FolderFile | undefined = undefined; + + shareFile(file: FolderFile, event: Event) { + event.stopPropagation(); + + this.fileToShare = file; + + const dialogRef = this.dialog.open(ShareDialogComponent, { + width: '550px', + data: { file: this.fileToShare, fileType: this.type } + }); + dialogRef.afterClosed().subscribe(experiment => { + this.refreshFiles(); + }); + } + _initialized: boolean = false; refreshFiles(selectedDatasetId: string | null = null, selectedModelId: string | null = null) { @@ -172,7 +220,9 @@ export class FolderComponent implements AfterViewInit { if (!this._initialized) { this.files = this.folders[this.startingTab]; this.filteredFiles = []; - this.selectTab(this.startingTab); + setTimeout(() => { + this.selectTab(this.startingTab); + }); this._initialized = true; } } @@ -229,35 +279,47 @@ export class FolderComponent implements AfterViewInit { }) /* ------------------------------------------------ */ this.searchTermsChanged(); + if (this.selectedTab == TabType.MyExperiments) + this.selectTab(TabType.MyExperiments); }) }); }); } saveNewFile() { + console.log("USAO U saveDisabled: ", this.saveDisabled); + if (this.saveDisabled) { + console.log("USAO U IF"); + return; + } + this.saveDisabled = true; this.loadingAction = true; switch (this.type) { case FolderType.Dataset: - this.formDataset!.uploadDataset((dataset: Dataset) => { + this.formNewDataset!.uploadDataset((dataset: Dataset) => { this.newFile = undefined; this.loadingAction = false; - this.okPressed.emit(); + //this.okPressed.emit(); //Shared.openDialog("Obaveštenje", "Uspešno ste dodali novi izvor podataka u kolekciju. Molimo sačekajte par trenutaka da se obradi."); this.refreshFiles(); + this.createNewFile(); }, () => { Shared.openDialog("Neuspeo pokušaj!", "Izvor podataka sa unetim nazivom već postoji u Vašoj kolekciji. Izmenite naziv ili iskoristite postojeći dataset."); + this.saveDisabled = false; }); break; case FolderType.Model: - this.formModel.newModel.type = this.formModel.forProblemType; - this.modelsService.addModel(this.formModel.newModel).subscribe(model => { + this.formNewModel.newModel.type = this.formModel.forProblemType; + this.modelsService.addModel(this.formNewModel.newModel).subscribe(model => { this.newFile = undefined; this.loadingAction = false; //Shared.openDialog("Obaveštenje", "Uspešno ste dodali novu konfiguraciju neuronske mreže u kolekciju."); this.refreshFiles(null, model._id); // todo select model + this.createNewFile(); }, (err) => { Shared.openDialog("Neuspeo pokušaj!", "Konfiguracija neuronske mreže sa unetim nazivom već postoji u Vašoj kolekciji. Izmenite naziv ili iskoristite postojeću konfiguraciju."); + this.saveDisabled = false; }); break; } @@ -300,10 +362,29 @@ export class FolderComponent implements AfterViewInit { onFileChange() { setTimeout(() => { - this.selectedFileHasChanges = !((this.selectedTab == TabType.NewFile) || isEqual(this.selectedFile, this.lastFileData)); + this.selectedFileHasChanges = !((this.selectedTab == TabType.NewFile) || this.checkFileDataEqualToLastFileData()); }); } + checkFileDataEqualToLastFileData() { + if (this.type == FolderType.Model) { + let layersEqual = true; + const oldModel = (<Model>this.lastFileData); + const selectedModel = (<Model>this.selectedFile) + const oldLayers = oldModel.layers; + oldModel.layers = selectedModel.layers; + const objEqual = isEqual(this.selectedFile, oldModel); + oldLayers.forEach((layer, index) => { + if (!isEqual(layer, selectedModel.layers[index])) { + layersEqual = false; + } + }); + return objEqual && layersEqual; + } else { + return isEqual(this.selectedFile, this.lastFileData); + } + } + updateFile() { const file = this.selectedFile; this.loadingAction = true; @@ -324,7 +405,7 @@ export class FolderComponent implements AfterViewInit { fileUpdatedSuccess() { this.loadingAction = false; this.selectedFileHasChanges = false; - Object.assign(this.lastFileData, this.selectedFile); + this.updateLastFileData(this.selectedFile); this.refreshFiles(); this.selectedFileChanged.emit(this.selectedFile); } @@ -336,22 +417,47 @@ export class FolderComponent implements AfterViewInit { case FolderType.Dataset: const dataset = <Dataset>file; Shared.openYesNoDialog("Obriši izvor podataka", "Eksperimenti i trenirani modeli nad ovim izvorom podataka će takođe biti obrisani, da li ste sigurni da želite da obrišete izvor: " + dataset.name + "?", () => { - this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); - this.files.splice(this.files.indexOf(file), 1); + if (this.selectedTab == TabType.MyDatasets) { + this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); + this.files.splice(this.files.indexOf(file), 1); + } this.loadingAction = true; this.datasetsService.deleteDataset(dataset).subscribe((response) => { this.loadingAction = false; + if (this.selectedTab == TabType.File) { + this.refreshDatasets(null); + this.selectedFile = undefined!; + setTimeout(() => { + this.selectTab(TabType.MyDatasets); + }); + } + if (this.archive) { + this.refreshExperiments(); + } + }); }) break; case FolderType.Model: const model = <Model>file; Shared.openYesNoDialog("Obriši konfiguraciju neuronske mreže", "Trenirani modeli za ovu konfiguraciju će takođe biti obrisani, da li ste sigurni da želite da obrišete konfiguraciju: " + model.name + "?", () => { - this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); - this.files.splice(this.files.indexOf(file), 1); + if (this.selectedTab == TabType.MyModels) { + this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); + this.files.splice(this.files.indexOf(file), 1); + } this.loadingAction = true; this.modelsService.deleteModel(<Model>file).subscribe((response) => { this.loadingAction = false; + if (this.selectedTab == TabType.File) { + this.refreshModels(null); + this.selectedFile = undefined!; + setTimeout(() => { + this.selectTab(TabType.MyModels); + }); + } + if (this.archive) { + this.refreshExperiments(); + } }); }) @@ -370,11 +476,20 @@ export class FolderComponent implements AfterViewInit { } else { const experiment = <Experiment>file; Shared.openYesNoDialog("Obriši eksperiment", "Trenirani modeli za ovaj eksperiment će takođe biti obrisani, da li ste sigurni da želite da obrišete eksperiment: " + experiment.name + "?", () => { - this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); - this.files.splice(this.files.indexOf(file), 1); + if (this.selectedTab == TabType.MyExperiments) { + this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); + this.files.splice(this.files.indexOf(file), 1); + } this.loadingAction = true; this.experimentsService.deleteExperiment(experiment).subscribe((response) => { this.loadingAction = false; + if (this.selectedTab == TabType.File) { + this.refreshExperiments(); + this.selectedFile = undefined!; + setTimeout(() => { + this.selectTab(TabType.MyExperiments); + }); + } }); }); } @@ -461,21 +576,19 @@ export class FolderComponent implements AfterViewInit { hoverTab: TabType = TabType.None; selectTab(tab: TabType) { - setTimeout(() => { - if (tab == TabType.NewFile) { - this.selectNewFile(); - this.selectedFile=undefined!; - } - - this.listView = this.getListView(tab); - this.type = this.getFolderType(tab); - this.privacy = this.getPrivacy(tab); - this.selectedTab = tab; - this.files = this.folders[tab]; + if (tab == TabType.NewFile) { + this.selectNewFile(); + } /*else if (tab == TabType.File) { + this.selectFile(this.selectedFile); + }*/ + this.listView = this.getListView(tab); + this.type = this.getFolderType(tab); + this.privacy = this.getPrivacy(tab); + this.selectedTab = tab; + this.files = this.folders[tab]; - if (tab !== TabType.File && tab !== TabType.NewFile) - this.searchTermsChanged(); - }); + if (tab !== TabType.File && tab !== TabType.NewFile) + this.searchTermsChanged(); } getListView(tab: TabType) { diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.html b/frontend/src/app/_elements/form-dataset/form-dataset.component.html index 6194a1a8..b518f15c 100644 --- a/frontend/src/app/_elements/form-dataset/form-dataset.component.html +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.html @@ -2,7 +2,11 @@ <div class="topBar"> <div class="kolona mb-3"> <div class="fileButton"> - <button type="button" mat-raised-button (click)="fileInput.click()"><span *ngIf="!firstInput">Dodaj izvor podataka</span><span *ngIf="firstInput">{{filename}}</span></button> + <button type="button" mat-raised-button (click)="fileInput.click()" [disabled]="dataset._id != '' || disableAll"> + <span *ngIf="!firstInput && dataset._id == '' ">Dodaj izvor podataka</span> + <span *ngIf="firstInput && dataset._id == '' ">{{filename}}</span> + <span *ngIf="dataset._id != '' ">Fajl je odabran</span> + </button> </div> </div> @@ -10,7 +14,7 @@ <div role="group"> <mat-form-field class="example-full-width" appearance="fill"> <mat-label>Naziv</mat-label> - <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name" (input)="editEvent.emit()"> + <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name" (input)="editEvent.emit()" [readonly]="disableAll"> <mat-error *ngIf="nameFormControl.hasError('required')"> @@ -22,7 +26,7 @@ <div class="kolona"> <mat-form-field appearance="fill"> <mat-label>Delimiter</mat-label> - <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (selectionChange)="update(); editEvent.emit();" value=","> + <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (selectionChange)="update(); editEvent.emit();" value="," [disabled]="disableAll"> <mat-option *ngFor="let option of delimiterOptions" [value]="option"> {{ option }} </mat-option> @@ -38,7 +42,7 @@ <i class="material-icons-outlined icon-display" [ngClass]="{'hidden': tableData.hasInput}">file_upload</i> - <input class="file" id="file-upload" (change)="changeListener($event)" (valueChange)="dataset.isPreProcess = false; editEvent.emit()" #fileInput type="file" accept=".csv"> + <input class="file" id="file-upload" [disabled]="dataset._id != '' || disableAll" (change)="changeListener($event)" (valueChange)="dataset.isPreProcess = false; editEvent.emit()" #fileInput type="file" accept=".csv"> <div class="datatable"> <div [ngClass]="{'hidden': (!existingFlag)}" class="text-center"> diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts index 1a29f74e..3eb6fe39 100644 --- a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts @@ -13,6 +13,7 @@ import { FormControl, Validators } from '@angular/forms'; styleUrls: ['./form-dataset.component.css'] }) export class FormDatasetComponent { + @Input() disableAll: boolean = false; @ViewChild(DatatableComponent) datatable!: DatatableComponent; @@ -89,7 +90,7 @@ export class FormDatasetComponent { update() { this.firstInput = true; - if (this.files.length < 1){ + if (this.files.length < 1) { this.loadExisting(); return; } @@ -155,10 +156,11 @@ export class FormDatasetComponent { uploadDataset(onSuccess: Function = (dataset: Dataset) => { }, onError: Function = () => { }) { if (this.files[0] == undefined) { shared.openDialog("Greška", "Niste izabrali fajl za učitavanje."); + onError(); return; } - return this.modelsService.uploadData(this.files[0]).subscribe((file) => { + return this.datasetsService.uploadData(this.files[0]).subscribe((file) => { this.dataset._id = ""; this.dataset.fileId = file._id; this.dataset.uploaderId = shared.userId; diff --git a/frontend/src/app/_elements/form-model/form-model.component.html b/frontend/src/app/_elements/form-model/form-model.component.html index b986e154..1f0208fa 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.html +++ b/frontend/src/app/_elements/form-model/form-model.component.html @@ -1,4 +1,4 @@ -<div *ngIf="newModel"> +<div *ngIf="newModel!=undefined"> <div id="container"> <div class="ns-row"> @@ -90,8 +90,8 @@ <!-- GRAF --> <div class="m-2"> - <div class="row"> - <div class="col-sm-3 rounded" style="border:1px solid var(--ns-primary);margin-top: 10px; width: fit-content;"> + <div class="ns-row"> + <div class="ns-col rounded" style="border:1px solid var(--ns-primary);margin-top: 10px; min-width: fit-content; max-width: 30%;"> <!-- <label>Veličina skupa za validaciju: </label><b>{{validationSize}} %</b> <div class="text-center pt-3 pb-0 mb-0"><b>{{testSetDistribution}}%</b> : <b>{{100-testSetDistribution}}%</b></div> <div class="text-center pt-0 mt-0 p-0">Trening ////slider////// Test</div> @@ -120,9 +120,10 @@ </div> </div> - <div class="col-sm-9"> + <div class="ns-col"> <!-- {{forExperiment._columnsSelected}} --> - <app-graph [model]="newModel" [inputColumns]="getInputColumns()"></app-graph> + + <app-graph [model]="newModel" [inputColumns]="getInputColumns()" [outputColumn]="(this.forExperiment!=undefined)?this.forExperiment.outputColumn:''"></app-graph> </div> </div> </div> @@ -189,7 +190,7 @@ <!-- LAYERI --> - <div id="layers"> + <div id="layers" style="margin-top: 10px;"> <div class="layer" *ngFor="let item of newModel.layers; let i=index"> @@ -229,7 +230,7 @@ <mat-form-field appearance="fill" class="mat-fix"> <mat-label>Stopa regularizacije</mat-label> - <mat-select [(ngModel)]="newModel.layers[i].regularisationRate"> + <mat-select [(ngModel)]="newModel.layers[i].regularisationRate" (selectionChange)="editEvent.emit();"> <mat-option *ngFor="let option of Object.keys(RegularisationRate); let optionName of Object.values(RegularisationRate)" [value]="option"> {{ optionName }} </mat-option> diff --git a/frontend/src/app/_elements/form-model/form-model.component.ts b/frontend/src/app/_elements/form-model/form-model.component.ts index 7c84d2ba..7831f573 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.ts +++ b/frontend/src/app/_elements/form-model/form-model.component.ts @@ -12,6 +12,8 @@ import { MatSliderChange } from '@angular/material/slider'; styleUrls: ['./form-model.component.css'] }) export class FormModelComponent implements AfterViewInit { + @Input() disableAll: boolean = false; + @ViewChild(GraphComponent) graph!: GraphComponent; @Input() forExperiment!: Experiment; @Output() selectedModelChangeEvent = new EventEmitter<Model>(); @@ -25,11 +27,7 @@ export class FormModelComponent implements AfterViewInit { @Output() editEvent = new EventEmitter(); ngAfterViewInit(): void { - this.lossFunction = this.lossFunctions[this.forProblemType][0]; - this.outputLayerActivationFunction = this.outputLayerActivationFunctions[this.forProblemType][0]; - - this.newModel.lossFunction = this.lossFunction; - this.newModel.outputLayerActivationFunction = this.outputLayerActivationFunction; + } selectFormControl = new FormControl('', Validators.required); @@ -80,10 +78,17 @@ export class FormModelComponent implements AfterViewInit { loadModel(model: Model) { this.newModel = model; this.forProblemType = model.type; + this.lossFunction = this.lossFunctions[this.forProblemType][0]; + this.outputLayerActivationFunction = this.outputLayerActivationFunctions[this.forProblemType][0]; + + this.newModel.lossFunction = this.lossFunction; + this.newModel.outputLayerActivationFunction = this.outputLayerActivationFunction; + this.updateGraph(); } updateGraph() { - this.graph.update(); + if(this.newModel) + this.graph.update(); } removeLayer() { diff --git a/frontend/src/app/_elements/form-predictor/form-predictor.component.css b/frontend/src/app/_elements/form-predictor/form-predictor.component.css new file mode 100644 index 00000000..569a9887 --- /dev/null +++ b/frontend/src/app/_elements/form-predictor/form-predictor.component.css @@ -0,0 +1,24 @@ +th{ + margin-right: 5px; +} +.container{ + background-color: var(--ns-bg-dark-50); + border-radius: 10px; + border: 1px solid var(--ns-primary); + width: 100%; + height: 100%;} +#input{ + color: whitesmoke; + background-color: var(--ns-bg-dark-100); + margin-top: 10px; +} +h3{ + color: whitesmoke; +} +#output{ + background-color: var(--ns-bg-dark-100); + color:whitesmoke; +} +#output b{ + height: 50px; +} diff --git a/frontend/src/app/_elements/form-predictor/form-predictor.component.html b/frontend/src/app/_elements/form-predictor/form-predictor.component.html new file mode 100644 index 00000000..3ebb666c --- /dev/null +++ b/frontend/src/app/_elements/form-predictor/form-predictor.component.html @@ -0,0 +1,32 @@ +<div class="container" id="predictWithInput" *ngIf="predictor"> + <h2 style="margin-top:20px ;color:whitesmoke;"> + Unesi vrednosti za predikciju + </h2> + <div class="row" id="input"> + <div class="col"> + <div class="row" style="margin-left:5px; font-weight:bold; color:whitesmoke"> + + </div> + <div class="row" *ngFor="let i of ['a','b','c','d','e','f']"> + <div class="col"> + <!-- + <mat-form-field> + <mat-label>{{i}}</mat-label> + <input matInput placeholder="Vrednost"> + </mat-form-field>--> + </div> + </div> + + </div> + + + </div> + + <div class="row justify-content-between"> + <button mat-raised-button style="max-width:30%;margin: 20px;">Predvidi rezultat</button> + </div> + <div class="row" id="output"> + <b>Izlazna vrednost za kolonu "output":"input"</b> + </div> + +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/form-predictor/form-predictor.component.spec.ts b/frontend/src/app/_elements/form-predictor/form-predictor.component.spec.ts new file mode 100644 index 00000000..f873e3af --- /dev/null +++ b/frontend/src/app/_elements/form-predictor/form-predictor.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FormPredictorComponent } from './form-predictor.component'; + +describe('FormPredictorComponent', () => { + let component: FormPredictorComponent; + let fixture: ComponentFixture<FormPredictorComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FormPredictorComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(FormPredictorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/form-predictor/form-predictor.component.ts b/frontend/src/app/_elements/form-predictor/form-predictor.component.ts new file mode 100644 index 00000000..fbdce569 --- /dev/null +++ b/frontend/src/app/_elements/form-predictor/form-predictor.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit ,Input,Output, ViewChild} from '@angular/core'; +import Predictor from 'src/app/_data/Predictor'; +import {PredictorsService } from 'src/app/_services/predictors.service'; +import { ActivatedRoute } from '@angular/router'; +@Component({ + selector: 'app-form-predictor', + templateUrl: './form-predictor.component.html', + styleUrls: ['./form-predictor.component.css'] +}) +export class FormPredictorComponent implements OnInit { + inputs : Column[] = []; + + predictor?:Predictor ; + predictorsForExp: { [expId: string]: Predictor[] } = {} + predictorsService: any; + experimentsService: any; + folders: any; + constructor(private predictS : PredictorsService, private route: ActivatedRoute) { + + } + + ngOnInit(): void { + /* this.route.params.subscribe(url => { + console.log("**********************************************"); + this.predictS.getPredictor(url["id"]).subscribe(p => { + + this.predictor = p; + this.predictor.inputs.forEach((p,index)=> this.inputs[index] = new Column(p, "")); + }) + }); + } + +*/ +} + +} + +export class Column { + constructor( + public name : string, + public value : (number | string)){ + }} diff --git a/frontend/src/app/_elements/graph/graph.component.css b/frontend/src/app/_elements/graph/graph.component.css index 456a8df1..f8604125 100644 --- a/frontend/src/app/_elements/graph/graph.component.css +++ b/frontend/src/app/_elements/graph/graph.component.css @@ -10,11 +10,14 @@ /*border: 1px solid red;*/ transform: translate(-50%, -50%); } - -.node-text:not(.inputs) { +.output{ + font-weight: bold; + color: var(--offwhite); +} +.node-text:not(.output) { color: transparent; } -.node-text:not(.inputs):hover { +.node-text:not(.output):hover { color: var(--offwhite); -}
\ No newline at end of file +} diff --git a/frontend/src/app/_elements/graph/graph.component.html b/frontend/src/app/_elements/graph/graph.component.html index 411e8b53..2ba42eba 100644 --- a/frontend/src/app/_elements/graph/graph.component.html +++ b/frontend/src/app/_elements/graph/graph.component.html @@ -1,8 +1,10 @@ <div #graphWrapper class="position-relative" style="height: 14rem; width: 100%;"> - <!-- <ng-container *ngFor="let layer of layers; let i = index"> - <div [ngClass]="{'inputs': i==0}" class="node-text" *ngFor="let node of layer; let j = index" [style.left.%]="node.x * 99.4" [style.top.%]="node.y * 100"> - {{ i == 0 ? (inputColumns && inputColumns.length >= j ? inputColumns[j] : 'nepoznato') : (i > 0 && i - < layers.length - 1 ? model!.layers[i-1].activationFunction : (i==layers.length - 1 ? 'out' : '')) }} </div> - </ng-container> --> + <div *ngIf="outputColumn!='' && inputColumns!=[]"> + <ng-container *ngFor="let layer of layers; let i = index"> + <div [ngClass]="{'inputs': i==0,'output':i==layers.length-1}" class="node-text" *ngFor="let node of layer; let j = index" [style.left.%]="node.x * 100" [style.top.%]="(i!=layers.length-1)?node.y * 100:node.y*70"> + {{ i == 0 ? (inputColumns && inputColumns.length >= j ? inputColumns[j] : 'nepoznato') : (i > 0 && i + < layers.length - 1 ? model!.layers[i-1].activationFunction : (i==layers.length - 1 ? " "+outputColumn : '')) }} </div> + </ng-container> + </div> <canvas #graphCanvas></canvas> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/graph/graph.component.ts b/frontend/src/app/_elements/graph/graph.component.ts index 04be9f04..f14e54e7 100644 --- a/frontend/src/app/_elements/graph/graph.component.ts +++ b/frontend/src/app/_elements/graph/graph.component.ts @@ -28,7 +28,8 @@ export class GraphComponent implements AfterViewInit { @Input() outputNodeColor: string = '#dfd7d7'; private ctx!: CanvasRenderingContext2D; - @Input() inputColumns?: string[]; + @Input() inputColumns?: string[]=[]; + @Input() outputColumn?:string=""; constructor() { } @@ -81,7 +82,7 @@ export class GraphComponent implements AfterViewInit { this.layers.push([new Node(outX, outY, this.outputNodeColor)]) this.draw(); } - + draw() { this.ctx.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height); diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.html b/frontend/src/app/_elements/metric-view/metric-view.component.html index 2ab0a425..139597f9 100644 --- a/frontend/src/app/_elements/metric-view/metric-view.component.html +++ b/frontend/src/app/_elements/metric-view/metric-view.component.html @@ -1,21 +1,2 @@ - <!--<div class="container" style="margin:auto;"> - <div class="row"> - <div class="col-xl"> - <div class="demo-content"><app-line-chart-acc [dataAcc]="myAcc" [dataEpoch]=""[dataValAcc]=""></app-line-chart-acc></div> - </div> - <div class="col-xl"> - <div class="demo-content"><app-line-chart-loss></app-line-chart-loss></div> - </div> - </div> - <div class="row"> - <div class="col-xl"> - <div class="demo-content"><app-line-chart-mae></app-line-chart-mae></div> - </div> - <div class="col-xl"> - <div class="demo-content"><app-line-chart-mse></app-line-chart-mse></div> - </div> -</div> -</div>--> -<app-line-chart></app-line-chart> - + diff --git a/frontend/src/app/_elements/notifications/notifications.component.html b/frontend/src/app/_elements/notifications/notifications.component.html index 3b2f4eaa..d3218a96 100644 --- a/frontend/src/app/_elements/notifications/notifications.component.html +++ b/frontend/src/app/_elements/notifications/notifications.component.html @@ -1,4 +1,4 @@ -<div *ngIf="notifications && notifications.length > 0" class="position-fixed card card-body p-1 m-3" style="bottom: 0; right: 0; width: 18rem;"> +<div *ngIf="notifications && notifications.length > 0" class="position-fixed card card-body p-1 m-3" style="bottom: 0; right: 0; width: 18rem; z-index: 5000;"> <h2 class="m-auto" (click)="closed = !closed;" data-bs-toggle="collapse" href="#collapseNotifs" role="button" aria-expanded="true" aria-controls="collapseNotifs"> Notifikacije <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger"> diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.css b/frontend/src/app/_modals/share-dialog/share-dialog.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.css diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.html b/frontend/src/app/_modals/share-dialog/share-dialog.component.html new file mode 100644 index 00000000..32584197 --- /dev/null +++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.html @@ -0,0 +1,14 @@ +<h1 mat-dialog-title class="text-center">Podeli {{data.fileType == 0 ? 'izvor podataka' : 'konfiguraciju neuronske mreže'}}</h1> +<div mat-dialog-content class="mt-5 mb-3 mx-1"> + <form> + <mat-checkbox class="m-2" [(ngModel)]="data.file.isPublic" [ngModelOptions]="{standalone: true}" (change)="publicChanged()">Svi mogu da vide </mat-checkbox> + <mat-checkbox class="m-2" [(ngModel)]="data.file.accessibleByLink" [ngModelOptions]="{standalone: true}" (change)="linkChanged()">Osobe sa linkom mogu da vide</mat-checkbox> + <div class="input-group my-3"> + <input type="text" [value]="link" class="form-control" placeholder="" aria-label="Copy link button addon" aria-describedby="button-copy"> + <button class="btn btn-outline-primary" type="button" id="button-copy" (click)="copy()" [disabled]="!data.file.accessibleByLink"><div class="mt-1"><mat-icon *ngIf="data.file.accessibleByLink">link</mat-icon><mat-icon *ngIf="!data.file.accessibleByLink">link_off</mat-icon></div></button> + </div> + </form> +</div> +<div mat-dialog-actions class="justify-content-center"> + <button mat-stroked-button (click)="close()">Gotovo</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.spec.ts b/frontend/src/app/_modals/share-dialog/share-dialog.component.spec.ts new file mode 100644 index 00000000..51bd4c9a --- /dev/null +++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ShareDialogComponent } from './share-dialog.component'; + +describe('ShareDialogComponent', () => { + let component: ShareDialogComponent; + let fixture: ComponentFixture<ShareDialogComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ShareDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ShareDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.ts b/frontend/src/app/_modals/share-dialog/share-dialog.component.ts new file mode 100644 index 00000000..2331cd8b --- /dev/null +++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.ts @@ -0,0 +1,70 @@ +import { Component, Inject, Input, OnInit } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import Dataset from 'src/app/_data/Dataset'; +import { FolderType } from 'src/app/_data/FolderFile'; +import Model from 'src/app/_data/Model'; +import { DatasetsService } from 'src/app/_services/datasets.service'; +import { ModelsService } from 'src/app/_services/models.service'; + +interface DialogData { + file: (Dataset | Model); + fileType: FolderType; +} + +@Component({ + selector: 'app-share-dialog', + templateUrl: './share-dialog.component.html', + styleUrls: ['./share-dialog.component.css'] +}) +export class ShareDialogComponent implements OnInit { + constructor(public dialogRef: MatDialogRef<ShareDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData, private modelsService: ModelsService, private datasetsService: DatasetsService) { + + } + + link: string = ''; + + ngOnInit(): void { + let link = window.location.origin; + if (this.data.fileType == FolderType.Dataset) { + link += '/dataset/'; + } else if (this.data.fileType == FolderType.Model) { + link += '/model/'; + } + link += this.data.file._id; + this.link = link; + } + + close() { + this.dialogRef.close(); + } + + copy() { + navigator.clipboard.writeText(this.link); + } + + publicChanged() { + if (this.data.fileType == FolderType.Dataset) { + this.datasetsService.updateDatasetIsPublic(this.data.file._id, this.data.file.isPublic).subscribe(() => { + }); + } else if (this.data.fileType == FolderType.Model) { + this.modelsService.updateModelIsPublic(this.data.file._id, this.data.file.isPublic).subscribe(() => { + }); + } + + if (this.data.file.isPublic) { + this.data.file.accessibleByLink = true; + } + } + + linkChanged() { + if (this.data.fileType == FolderType.Dataset) { + this.datasetsService.updateDatasetAccessibleByLink(this.data.file._id, this.data.file.accessibleByLink).subscribe(() => { + }); + } else if (this.data.fileType == FolderType.Model) { + this.modelsService.updateModelAccessibleByLink(this.data.file._id, this.data.file.accessibleByLink).subscribe(() => { + }); + } + } + + FolderType = FolderType; +} diff --git a/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.css b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.css new file mode 100644 index 00000000..551a4e21 --- /dev/null +++ b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.css @@ -0,0 +1,21 @@ +#btnYes { + background-color: var(--offwhite); + color: var(--ns-bg-dark-100); +} + +#btnNo { + color: gray; +} + +.wrongInput { + color: var(--ns-warn); + font-size: 11px; +} + +::ng-deep.mat-dialog-content { + overflow: visible; +} + +.hidden-class { + opacity: 0; +}
\ No newline at end of file diff --git a/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.html b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.html new file mode 100644 index 00000000..db44dfe1 --- /dev/null +++ b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.html @@ -0,0 +1,24 @@ +<h1 mat-dialog-title class="text-center">Sačuvaj izmene</h1> +<div mat-dialog-content class="mt-5 mb-3 mx-1"> + <form (keydown)="saveWithEnterKey($event)"> + <mat-radio-group [(ngModel)]="selectedOption" [ngModelOptions]="{standalone: true}"> + <!-- I Update the existing experiment --> + <mat-radio-button [value]="1" checked>Izmeni postojeći eksperiment</mat-radio-button> + <!-- II Save as new experiment --> + <mat-radio-button [value]="2" class="mt-4">Sačuvaj kao novi eksperiment</mat-radio-button> + </mat-radio-group> + <div class="d-flex align-items-center justify-content-center mt-3" [ngClass]="{'hidden-class': selectedName.length == 0 && selectedOption == 1}"> + <mat-form-field [style.width.px]=200> + <input type="text" matInput [(ngModel)]="selectedName" (click)="selectedOption = 2" (keydown)="selectedOption = 2" cdkFocusInitial [ngModelOptions]="{standalone: true}" placeholder="Naziv novog eskperimenta"> + </mat-form-field> + </div> + <div class="d-flex align-items-center justify-content-center mt-1 mx-4"> + <p *ngIf="wrongAlreadyExists" class="wrongInput">Eskperiment sa unetim nazivom već postoji u kolekciji.<br>Izaberite drugi naziv.</p> + <p *ngIf="wrongEmptyName" class="wrongInput">Unesite naziv eksperimenta.</p> + </div> + </form> +</div> +<div mat-dialog-actions class="justify-content-center"> + <button id="btnYes" mat-stroked-button color="basic" (click)="onYesClick()">Sačuvaj</button> + <button id="btnNo" mat-stroked-button (click)="onNoClick()">Odustani</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.spec.ts b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.spec.ts new file mode 100644 index 00000000..e0f26b2c --- /dev/null +++ b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { UpdateExperimentDialogComponent } from './update-experiment-dialog.component'; + +describe('UpdateExperimentDialogComponent', () => { + let component: UpdateExperimentDialogComponent; + let fixture: ComponentFixture<UpdateExperimentDialogComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ UpdateExperimentDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UpdateExperimentDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.ts b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.ts new file mode 100644 index 00000000..068aa2ef --- /dev/null +++ b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.ts @@ -0,0 +1,68 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import Experiment from 'src/app/_data/Experiment'; +import { ExperimentsService } from 'src/app/_services/experiments.service'; + +interface DialogData { + experiment: Experiment; + selectedOption: number; +} + +@Component({ + selector: 'app-update-experiment-dialog', + templateUrl: './update-experiment-dialog.component.html', + styleUrls: ['./update-experiment-dialog.component.css'] +}) +export class UpdateExperimentDialogComponent implements OnInit { + + selectedOption: number = 1; + selectedName: string = ''; + wrongAlreadyExists: boolean = false; + wrongEmptyName: boolean = false; + + constructor(public dialogRef: MatDialogRef<UpdateExperimentDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData, private experimentService: ExperimentsService) { + this.wrongAlreadyExists = false; + this.wrongEmptyName = false; + } + + ngOnInit(): void { + } + + onNoClick() { + this.dialogRef.close(); + } + + saveWithEnterKey(keyboardEvent: KeyboardEvent) { + if (keyboardEvent.code == "Enter" || keyboardEvent.code == "NumpadEnter") + this.onYesClick(); + } + + onYesClick() { + if (this.selectedOption == 1) { //update + this.experimentService.updateExperiment(this.data.experiment).subscribe((response) => { + this.data.experiment = response; + this.dialogRef.close(this.data.experiment); + }); + } + else { //save new + if (this.selectedName == "") { + this.wrongEmptyName = true; + return; + } + this.wrongEmptyName = false; + + const newExperiment = new Experiment(); + Object.assign(newExperiment, this.data.experiment); + newExperiment.name = this.selectedName; + newExperiment._id = ''; + this.experimentService.addExperiment(newExperiment!).subscribe((response) => { + this.wrongAlreadyExists = false; + this.dialogRef.close(response); + }, (error) => { + if (error.error == "Experiment with this name exists") { + this.wrongAlreadyExists = true; + } + }); + } + } +} diff --git a/frontend/src/app/_pages/experiment/experiment.component.css b/frontend/src/app/_pages/experiment/experiment.component.css index 59e004e9..37edd3c2 100644 --- a/frontend/src/app/_pages/experiment/experiment.component.css +++ b/frontend/src/app/_pages/experiment/experiment.component.css @@ -74,4 +74,34 @@ mat-stepper { .text-overflow-experiment-name { overflow-wrap: break-word; +} + +#compareButton { + position: absolute; + top: 10px; + right: 10px; +} + +.side-by-side { + display: flex; + flex-direction: row; + width: 100%; + height: 100%; +} + + +/* one item */ + +.side-by-side>*:first-child:nth-last-child(1) { + /* -or- li:only-child { */ + width: 100%; +} + + +/* two items */ + +.side-by-side>*:first-child:nth-last-child(2), +.side-by-side>*:first-child:nth-last-child(2)~li { + width: 50%; + margin: 5px; }
\ No newline at end of file diff --git a/frontend/src/app/_pages/experiment/experiment.component.html b/frontend/src/app/_pages/experiment/experiment.component.html index 17a6539d..3176f58c 100644 --- a/frontend/src/app/_pages/experiment/experiment.component.html +++ b/frontend/src/app/_pages/experiment/experiment.component.html @@ -25,10 +25,10 @@ <ng-template matStepLabel> <span class="label addedElement text-overflow" *ngIf="experiment._id!=''">Predvideti:{{experiment.outputColumn}}</span> <span *ngIf="!this.step1" class="align-middle"><mat-icon>lock</mat-icon></span> - <span class="label text-overflow" *ngIf="experiment._id==''">Odabir kolona </span> + <span class="label text-overflow" *ngIf="experiment._id==''">Priprema podataka </span> </ng-template> <ng-template matStepContent> - <p class="text-left text-overflow">Pripremite podatke i izaberite izlazne kolone</p> + <p class="text-left text-overflow">Pripremite podatke i odaberite ulazne i izlaznu kolonu</p> </ng-template> </mat-step> <mat-step [completed]="this.step3"> @@ -57,18 +57,35 @@ </div> <div #steps id="step_2" class="step-content" *ngIf="step1"> <div class="step-content-inside"> - <app-column-table (okPressed)="goToPage(2); experiment._columnsSelected = true;" (columnTableChanged)="columnTableChangedEvent()" (experimentChanged)="experimentChangedEvent()" [experiment]="experiment" [dataset]="dataset"></app-column-table> + <app-column-table (columnTableChanged)="columnTableChangedEvent()" (experimentChanged)="experimentChangedEvent()" [experiment]="experiment" [dataset]="dataset"></app-column-table> </div> </div> <div #steps id="step_3" class="step-content" *ngIf="step2"> <div class="step-content-inside"> - <app-folder #folderModel [type]="FolderType.Model" [forExperiment]="experiment" [startingTab]="TabType.NewFile" [tabsToShow]="[TabType.MyModels]" (okPressed)="goToPage(3); trainModel();" (selectedFileChanged)="setModel($event)"></app-folder> + <div id="compareButton"> + <button mat-raised-button color="accent" *ngIf="!comparing" (click)="toggleCompare()"><mat-icon>compare</mat-icon> Dodaj konfiguraciju za upoređivanje</button> + <button mat-raised-button *ngIf="comparing" (click)="toggleCompare()"><mat-icon>not_interested</mat-icon> Prekini upoređivanje</button> + </div> + <div class="side-by-side"> + <app-folder #folderModel [type]="FolderType.Model" [forExperiment]="experiment" [startingTab]="TabType.NewFile" [tabsToShow]="[TabType.MyModels]" (okPressed)="trainModel();" (selectedFileChanged)="setModel($event);"></app-folder> + <app-folder #folderModelCompare [type]="FolderType.Model" [forExperiment]="experiment" [startingTab]="TabType.MyModels" [tabsToShow]="[TabType.MyModels]" (okPressed)=" trainModelCmp();" (selectedFileChanged)="setModelCmp($event);" style="width: 50%;" + *ngIf="comparing"></app-folder> + </div> </div> </div> <div #steps id="step_4" class="step-content" *ngIf="step3"> <div class="step-content-inside"> - <app-metric-view #metricView></app-metric-view> + <div class="side-by-side"> + <app-line-chart #linechart [experiment]="experiment" [predictor]="predictor!"></app-line-chart> + <app-line-chart #linechartCompare [experiment]="experiment" style="width: 50%;" *ngIf="comparing"></app-line-chart> + </div> </div> </div> + <!-- + <div #steps id="step_5" class="step-content" *ngIf="step4"> + <div class="step-content-inside"> + <app-form-predictor></app-form-predictor> + </div> + </div>--> </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/experiment/experiment.component.ts b/frontend/src/app/_pages/experiment/experiment.component.ts index ec4275fa..e4d444b2 100644 --- a/frontend/src/app/_pages/experiment/experiment.component.ts +++ b/frontend/src/app/_pages/experiment/experiment.component.ts @@ -15,6 +15,8 @@ import { MetricViewComponent } from 'src/app/_elements/metric-view/metric-view.c import { ActivatedRoute, Router } from '@angular/router'; import { DatasetsService } from 'src/app/_services/datasets.service'; import { PredictorsService } from 'src/app/_services/predictors.service'; +import { LineChartComponent } from 'src/app/_elements/_charts/line-chart/line-chart.component'; +import Predictor from 'src/app/_data/Predictor'; @Component({ selector: 'app-experiment', @@ -30,16 +32,32 @@ export class ExperimentComponent implements AfterViewInit { event: number = 0; experiment: Experiment; dataset?: Dataset; + predictor?: Predictor; + @ViewChild("folderDataset") folderDataset!: FolderComponent; @ViewChild(ColumnTableComponent) columnTable!: ColumnTableComponent; @ViewChild("folderModel") folderModel!: FolderComponent; - @ViewChild("metricView") metricView!: MetricViewComponent; + @ViewChild("folderModelCompare") folderModelCmp!: FolderComponent; + @ViewChild("linechart") linechartComponent!: LineChartComponent; + @ViewChild("linechartCompare") linechartComponentCmp!: LineChartComponent; step1: boolean = false; step2: boolean = false; step3: boolean = false; step4: boolean = false; + comparing: boolean = false; + + toggleCompare() { + this.comparing = !this.comparing; + setTimeout(() => { + if (this.folderModel.formModel) + this.folderModel.formModel.graph.resize(); + if (this.folderModel.formNewModel) + this.folderModel.formNewModel.graph.resize(); + }); + } + constructor(private experimentsService: ExperimentsService, private modelsService: ModelsService, private datasetsService: DatasetsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private route: ActivatedRoute) { this.experiment = new Experiment("exp1"); } @@ -57,7 +75,22 @@ export class ExperimentComponent implements AfterViewInit { Shared.openDialog('Greška', 'Morate odabrati konfiguraciju neuronske mreže'); } else { this.modelsService.trainModel(this.modelToTrain._id, this.experiment._id).subscribe(() => { console.log("pocelo treniranje") }); - this.step4 = true; + this.step3 = true; + setTimeout(() => { + this.goToPage(3); + }); + } + } + + trainModelCmp() { + if (!this.modelToTrainCmp) { + Shared.openDialog('Greška', 'Morate odabrati konfiguraciju neuronske mreže'); + } else { + this.modelsService.trainModel(this.modelToTrainCmp._id, this.experiment._id).subscribe(() => { console.log("pocelo treniranje") }); + this.step3 = true; + setTimeout(() => { + this.goToPage(3); + }); } } @@ -83,16 +116,48 @@ export class ExperimentComponent implements AfterViewInit { if (this.signalRService.hubConnection) { this.signalRService.hubConnection.on("NotifyEpoch", (mName: string, mId: string, stat: string, totalEpochs: number, currentEpoch: number) => { - if (currentEpoch == 0) { - this.history = []; - } + if (this.modelToTrain?._id == mId) { + if (currentEpoch == 0) { + this.linechartComponent.setName(mName); + this.history = []; + } + stat = stat.replace(/'/g, '"'); this.history.push(JSON.parse(stat)); - this.metricView.update(this.history,this.modelToTrain.epochs); + + this.linechartComponent.updateAll(this.history, this.modelToTrain.epochs); + } + + if (this.modelToTrainCmp?._id == mId) { + if (currentEpoch == 0) { + this.linechartComponentCmp.setName(mName); + this.historyCmp = []; + } + + stat = stat.replace(/'/g, '"'); + + this.historyCmp.push(JSON.parse(stat)); + this.linechartComponentCmp.updateAll(this.historyCmp, this.modelToTrainCmp.epochs); } }); + this.signalRService.hubConnection.on("NotifyPredictor", (pId: string, mId: string) => { + console.log("Predictor trained: ", pId, "for model:", mId); + + if (this.modelToTrain && mId == this.modelToTrain._id) { + this.predictorsService.getPredictor(pId).subscribe((predictor) => { + this.linechartComponent.predictor = predictor; + }); + } + + if (this.modelToTrainCmp && mId == this.modelToTrainCmp._id) { + this.predictorsService.getPredictor(pId).subscribe((predictor) => { + this.linechartComponentCmp.predictor = predictor; + }); + } + }) + } this.route.queryParams.subscribe(params => { @@ -102,6 +167,7 @@ export class ExperimentComponent implements AfterViewInit { if (predictorId != null) { this.predictorsService.getPredictor(predictorId!).subscribe((response) => { let predictor = response; + this.predictor = predictor; this.experimentsService.getExperimentById(predictor.experimentId).subscribe((response) => { this.experiment = response; this.datasetsService.getDatasetById(this.experiment.datasetId).subscribe((response: Dataset) => { @@ -111,11 +177,13 @@ export class ExperimentComponent implements AfterViewInit { this.modelsService.getModelById(predictor.modelId).subscribe((response) => { let model = response; - this.folderModel.formModel.newModel = model; + this.folderModel.selectFile(model); + //this.folderModel.formModel.newModel = model; this.step3 = true; - let numOfEpochsArray = Array.from({length: model.epochs}, (_, i) => i + 1); + let numOfEpochsArray = Array.from({ length: model.epochs }, (_, i) => i + 1); setTimeout(() => { - this.metricView.linechartComponent.update(numOfEpochsArray, predictor.metricsAcc, predictor.metricsLoss, predictor.metricsMae, predictor.metricsMse, predictor.metricsValAcc, predictor.metricsValLoss, predictor.metricsValMae, predictor.metricsValMse); + this.linechartComponent.update(numOfEpochsArray, predictor.metricsAcc, predictor.metricsLoss, predictor.metricsMae, predictor.metricsMse, predictor.metricsValAcc, predictor.metricsValLoss, predictor.metricsValMae, predictor.metricsValMse); + this.goToPage(3); }) }); }); @@ -129,6 +197,7 @@ export class ExperimentComponent implements AfterViewInit { this.dataset = response; this.folderDataset.forExperiment = this.experiment; this.folderDataset.selectFile(this.dataset); + this.goToPage(1); }); }); } @@ -137,6 +206,7 @@ export class ExperimentComponent implements AfterViewInit { } history: any[] = []; + historyCmp: any[] = []; updatePageIfScrolled() { if (this.scrolling) return; @@ -197,14 +267,18 @@ export class ExperimentComponent implements AfterViewInit { this.step2 = true; setTimeout(() => { this.folderModel.updateExperiment(); + this.folderModel.selectFile(undefined); + this.folderModel.selectTab(TabType.NewFile); + this.goToPage(2); }); } setDataset(dataset: FolderFile | null) { - if (dataset == null) { + if (dataset == null ||dataset==undefined) { this.columnTable.loaded = false; this.dataset = undefined; this.experiment.datasetId = ''; + this.step1=false; return; } const d = <Dataset>dataset; @@ -215,13 +289,21 @@ export class ExperimentComponent implements AfterViewInit { setTimeout(() => { this.columnTable.loadDataset(d); }); + // REFRESH GRAFIKA (4. KORAKA) URADITI } modelToTrain?: Model; + modelToTrainCmp?: Model; setModel(model: FolderFile) { const m = <Model>model; this.modelToTrain = m; - this.step3 = true; + //this.step3 = true; + } + + setModelCmp(model: FolderFile) { + const m = <Model>model; + this.modelToTrainCmp = m; + //this.step3 = true; } } diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.css b/frontend/src/app/_pages/page-dataset/page-dataset.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.css diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.html b/frontend/src/app/_pages/page-dataset/page-dataset.component.html new file mode 100644 index 00000000..2357a656 --- /dev/null +++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.html @@ -0,0 +1,4 @@ +<div class="force-centered" style="color: var(--offwhite);"> + <app-form-dataset [disableAll]="true"></app-form-dataset> + <button mat-raised-button class="m-3 p-2" (click)="import()">Uvezi</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.spec.ts b/frontend/src/app/_pages/page-dataset/page-dataset.component.spec.ts new file mode 100644 index 00000000..2c961cd5 --- /dev/null +++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageDatasetComponent } from './page-dataset.component'; + +describe('PageDatasetComponent', () => { + let component: PageDatasetComponent; + let fixture: ComponentFixture<PageDatasetComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageDatasetComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageDatasetComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.ts b/frontend/src/app/_pages/page-dataset/page-dataset.component.ts new file mode 100644 index 00000000..b6d0c2a4 --- /dev/null +++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import Shared from 'src/app/Shared'; +import Dataset from 'src/app/_data/Dataset'; +import { FormDatasetComponent } from 'src/app/_elements/form-dataset/form-dataset.component'; +import { DatasetsService } from 'src/app/_services/datasets.service'; + +@Component({ + selector: 'app-page-dataset', + templateUrl: './page-dataset.component.html', + styleUrls: ['./page-dataset.component.css'] +}) +export class PageDatasetComponent implements OnInit { + + @ViewChild(FormDatasetComponent) formDataset!: FormDatasetComponent; + + constructor(private route: ActivatedRoute, private router: Router, private datasetsService: DatasetsService) { } + + ngOnInit(): void { + this.route.queryParams.subscribe(params => { + let id = this.route.snapshot.paramMap.get("id"); + if (id) { + this.datasetsService.getDatasetById(id).subscribe((dataset) => { + this.formDataset.dataset = dataset; + this.formDataset.loadExisting(); + }); + } else { + this.router.navigate(['']); + } + }); + } + + import() { + this.formDataset.dataset._id = ""; + this.formDataset.dataset.isPreProcess = true; + this.formDataset.dataset.isPublic = false; + this.datasetsService.stealDataset(this.formDataset.dataset).subscribe((response) => { + Shared.openDialog("Obaveštenje", "Uspešno ste dodali javni izvor podataka u vašu kolekciju."); + }, (error: any) => { + if (error.error == "Dataset with this name already exists") { + Shared.openDialog("Obaveštenje", "Izvor podataka sa ovim imenom postoji u vašoj kolekciji."); + } + }); + } +} diff --git a/frontend/src/app/_pages/page-model/page-model.component.css b/frontend/src/app/_pages/page-model/page-model.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/page-model/page-model.component.css diff --git a/frontend/src/app/_pages/page-model/page-model.component.html b/frontend/src/app/_pages/page-model/page-model.component.html new file mode 100644 index 00000000..0ac8aaae --- /dev/null +++ b/frontend/src/app/_pages/page-model/page-model.component.html @@ -0,0 +1,6 @@ +<div class="force-centered" style="color: var(--offwhite);"> + <div> + {{JSON.stringify(model)}} + </div> + <button mat-raised-button class="m-3 p-2" (click)="import()">Uvezi</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/page-model/page-model.component.spec.ts b/frontend/src/app/_pages/page-model/page-model.component.spec.ts new file mode 100644 index 00000000..e235de5c --- /dev/null +++ b/frontend/src/app/_pages/page-model/page-model.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageModelComponent } from './page-model.component'; + +describe('PageModelComponent', () => { + let component: PageModelComponent; + let fixture: ComponentFixture<PageModelComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageModelComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageModelComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/page-model/page-model.component.ts b/frontend/src/app/_pages/page-model/page-model.component.ts new file mode 100644 index 00000000..0ccd0f9a --- /dev/null +++ b/frontend/src/app/_pages/page-model/page-model.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import Shared from 'src/app/Shared'; +import Experiment from 'src/app/_data/Experiment'; +import Model from 'src/app/_data/Model'; +import { FormModelComponent } from 'src/app/_elements/form-model/form-model.component'; +import { ModelsService } from 'src/app/_services/models.service'; + +@Component({ + selector: 'app-page-model', + templateUrl: './page-model.component.html', + styleUrls: ['./page-model.component.css'] +}) +export class PageModelComponent implements OnInit { + + constructor(private route: ActivatedRoute, private router: Router, private modelsService: ModelsService) { } + model!:Model; + ngOnInit(): void { + this.route.queryParams.subscribe(params => { + let id = this.route.snapshot.paramMap.get("id"); + if (id) { + this.modelsService.getModelById(id).subscribe((model) => { + this.model = model; + }); + } else { + this.router.navigate(['']); + } + }); + } + + import() { + this.model._id = ""; + this.model.isPublic = false; + this.modelsService.stealModel(this.model).subscribe((response) => { + Shared.openDialog("Obaveštenje", "Uspešno ste dodali javnu konfiguraciju neuronske mreže u vašu kolekciju."); + }, (error: any) => { + Shared.openDialog("Obaveštenje", "Konfiguracija neuronske mreže sa ovim imenom postoji u vašoj kolekciji."); + }); + } + JSON=JSON; +} diff --git a/frontend/src/app/_services/datasets.service.ts b/frontend/src/app/_services/datasets.service.ts index 2775613c..11838b1d 100644 --- a/frontend/src/app/_services/datasets.service.ts +++ b/frontend/src/app/_services/datasets.service.ts @@ -1,4 +1,4 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { Configuration } from './configuration.service'; @@ -12,6 +12,21 @@ export class DatasetsService { constructor(private http: HttpClient, private authService: AuthService) { } + uploadData(file: File): Observable<any> { + let formData = new FormData(); + formData.append('file', file, file.name); + + let params = new HttpParams(); + + const options = { + params: params, + reportProgress: false, + headers: this.authService.authHeader() + }; + + return this.http.post(`${Configuration.settings.apiURL}/file/csv`, formData, options); + } + getPublicDatasets(): Observable<Dataset[]> { return this.http.get<Dataset[]>(`${Configuration.settings.apiURL}/dataset/publicdatasets`, { headers: this.authService.authHeader() }); } @@ -31,10 +46,10 @@ export class DatasetsService { getDatasetFile(fileId: any): any { return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/-1/11`, { headers: this.authService.authHeader(), responseType: 'text' }); } - getDatasetFilePaging(fileId:any,begin:any,end:any){ + getDatasetFilePaging(fileId: any, begin: any, end: any) { return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/${begin}/${end}`, { headers: this.authService.authHeader(), responseType: 'text' }); } - getDatasetHeader(fileId:any){ + getDatasetHeader(fileId: any) { return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/-1/1`, { headers: this.authService.authHeader(), responseType: 'text' }); } getDatasetFilePartial(fileId: any, startRow: number, rowNum: number): Observable<any> { @@ -44,15 +59,23 @@ export class DatasetsService { return this.http.get<Dataset>(`${Configuration.settings.apiURL}/dataset/get/${datasetId}`, { headers: this.authService.authHeader() }); } - editDataset(dataset: Dataset){ - return this.http.put(`${Configuration.settings.apiURL}/dataset/` + dataset._id, dataset, { headers: this.authService.authHeader() ,responseType:'text'}); + editDataset(dataset: Dataset) { + return this.http.put(`${Configuration.settings.apiURL}/dataset/` + dataset._id, dataset, { headers: this.authService.authHeader(), responseType: 'text' }); } deleteDataset(dataset: Dataset) { return this.http.delete(`${Configuration.settings.apiURL}/dataset/` + dataset._id, { headers: this.authService.authHeader(), responseType: "text" }); } - downloadFile(id:string):Observable<Blob>{ - return this.http.get(`${Configuration.settings.apiURL}/file/Download?id=`+id, { headers: this.authService.authHeader(), responseType: 'blob' }); + downloadFile(id: string): Observable<Blob> { + return this.http.get(`${Configuration.settings.apiURL}/file/Download?id=` + id, { headers: this.authService.authHeader(), responseType: 'blob' }); + } + + updateDatasetAccessibleByLink(id: string, acessibleByLink: boolean) { + return this.http.put(`${Configuration.settings.apiURL}/dataset/updateAccessibleByLink/` + id, acessibleByLink, { headers: this.authService.authHeader(), responseType: 'text' }); + } + + updateDatasetIsPublic(id: string, isPublic: boolean) { + return this.http.put(`${Configuration.settings.apiURL}/dataset/updateIsPublic/` + id, isPublic, { headers: this.authService.authHeader(), responseType: 'text' }); } } diff --git a/frontend/src/app/_services/models.service.ts b/frontend/src/app/_services/models.service.ts index 2b8fe8f2..398c7813 100644 --- a/frontend/src/app/_services/models.service.ts +++ b/frontend/src/app/_services/models.service.ts @@ -13,21 +13,6 @@ export class ModelsService { constructor(private http: HttpClient, private authService: AuthService) { } - uploadData(file: File): Observable<any> { - let formData = new FormData(); - formData.append('file', file, file.name); - - let params = new HttpParams(); - - const options = { - params: params, - reportProgress: false, - headers: this.authService.authHeader() - }; - - return this.http.post(`${Configuration.settings.apiURL}/file/csv`, formData, options); - } - addModel(model: Model): Observable<any> { return this.http.post(`${Configuration.settings.apiURL}/model/add`, model, { headers: this.authService.authHeader() }); } @@ -66,4 +51,12 @@ export class ModelsService { return this.http.get<Model>(`${Configuration.settings.apiURL}/model/byid/${modelId}`, { headers: this.authService.authHeader() }); } + updateModelAccessibleByLink(id: string, acessibleByLink: boolean) { + return this.http.put(`${Configuration.settings.apiURL}/model/updateAccessibleByLink/` + id, acessibleByLink, { headers: this.authService.authHeader(), responseType: 'text' }); + } + + updateModelIsPublic(id: string, isPublic: boolean) { + return this.http.put(`${Configuration.settings.apiURL}/model/updateIsPublic/` + id, isPublic, { headers: this.authService.authHeader(), responseType: 'text' }); + } + } diff --git a/frontend/src/app/_services/predictors.service.ts b/frontend/src/app/_services/predictors.service.ts index e2033e3e..5859634d 100644 --- a/frontend/src/app/_services/predictors.service.ts +++ b/frontend/src/app/_services/predictors.service.ts @@ -29,6 +29,10 @@ export class PredictorsService { getMyPredictors(): Observable<Predictor[]> { return this.http.get<Predictor[]>(`${Configuration.settings.apiURL}/predictor/mypredictors`, { headers: this.authService.authHeader() }); } + + downloadH5(id: string): Observable<Blob> { + return this.http.get(`${Configuration.settings.apiURL}/file/Download?id=` + id, { headers: this.authService.authHeader(), responseType: 'blob' }); + } } export class Column { diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index d5552ce9..fa53d5f0 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -9,16 +9,20 @@ import { ExperimentComponent } from './_pages/experiment/experiment.component'; import { ArchiveComponent } from './_pages/archive/archive.component'; import { ColumnTableComponent } from './_elements/column-table/column-table.component'; import { TestComponent } from './_pages/test/test.component'; +import { PageDatasetComponent } from './_pages/page-dataset/page-dataset.component'; +import { PageModelComponent } from './_pages/page-model/page-model.component'; const routes: Routes = [ { path: '', component: HomeComponent, data: { title: 'Početna strana' } }, { path: 'experiment/p/:predictorId', component: ExperimentComponent, data: { title: 'Eksperiment' } }, { path: 'experiment/:id', component: ExperimentComponent, data: { title: 'Eksperiment' } }, { path: 'experiment', component: ExperimentComponent, data: { title: 'Eksperiment' } }, - { path: 'archive', component: ArchiveComponent, data: { title: 'Arhiva' } }, + { path: 'archive', component: ArchiveComponent, data: { title: 'Kolekcije' } }, { path: 'profile', component: ProfileComponent, canActivate: [AuthGuardService], data: { title: 'Profil' } }, { path: 'playground', component: PlaygroundComponent, data: { title: 'Zabava' } }, { path: 'test', component: TestComponent, data: { title: 'Test' } }, + { path: 'dataset/:id', component: PageDatasetComponent, data: { title: 'Izvor podataka' } }, + { path: 'model/:id', component: PageModelComponent, data: { title: 'Konfiguracija neuronske mreže' } }, { path: '**', redirectTo: '' } ]; diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 89d53115..21b09318 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -22,6 +22,7 @@ import { YesNoDialogComponent } from './_modals/yes-no-dialog/yes-no-dialog.comp import { EncodingDialogComponent } from './_modals/encoding-dialog/encoding-dialog.component'; import { MissingvaluesDialogComponent } from './_modals/missingvalues-dialog/missingvalues-dialog.component'; import { SaveExperimentDialogComponent } from './_modals/save-experiment-dialog/save-experiment-dialog.component'; +import { UpdateExperimentDialogComponent } from './_modals/update-experiment-dialog/update-experiment-dialog.component'; // Pages import { HomeComponent } from './_pages/home/home.component'; import { ProfileComponent } from './_pages/profile/profile.component'; @@ -53,6 +54,9 @@ import { HeatmapComponent } from './_elements/_charts/heatmap/heatmap.component' import { HeatMapAllModule } from '@syncfusion/ej2-angular-heatmap'; import { MetricViewComponent } from './_elements/metric-view/metric-view.component'; import { SpinnerComponent } from './_elements/spinner/spinner.component'; +import { PageDatasetComponent } from './_pages/page-dataset/page-dataset.component'; +import { PageModelComponent } from './_pages/page-model/page-model.component'; +import { ShareDialogComponent } from './_modals/share-dialog/share-dialog.component'; export function initializeApp(appConfig: Configuration) { return () => appConfig.load(); @@ -93,7 +97,11 @@ export function initializeApp(appConfig: Configuration) { MetricViewComponent, LineChartComponent, SaveExperimentDialogComponent, - SpinnerComponent + SpinnerComponent, + UpdateExperimentDialogComponent, + PageDatasetComponent, + PageModelComponent, + ShareDialogComponent ], imports: [ BrowserModule, |