diff --git a/frontend/src/app/shared/upload-json-button/upload-json-button.component.html b/frontend/src/app/shared/upload-json-button/upload-json-button.component.html
index ee9a97f5a..f4b3b2ce7 100644
--- a/frontend/src/app/shared/upload-json-button/upload-json-button.component.html
+++ b/frontend/src/app/shared/upload-json-button/upload-json-button.component.html
@@ -1,4 +1,8 @@
-
{{node.item}}
+ (change)="itemSelectionToggle(node)">{{node.label}}
diff --git a/frontend/src/app/template/template-builder/components/template-import-tree/template-import-tree.component.ts b/frontend/src/app/template/template-builder/components/template-import-tree/template-import-tree.component.ts
index 21e63d770..7fcd9cf96 100644
--- a/frontend/src/app/template/template-builder/components/template-import-tree/template-import-tree.component.ts
+++ b/frontend/src/app/template/template-builder/components/template-import-tree/template-import-tree.component.ts
@@ -1,6 +1,7 @@
-import { Component, Input } from "@angular/core";
+import { Component, Input, OnInit } from "@angular/core";
import {
- ApiEvaluationTemplate,
+ ApiEvaluationStatus,
+ ApiEvaluationTemplate, ApiEvaluationTemplateOverview,
ApiTaskGroup,
ApiTaskTemplate,
ApiTaskType,
@@ -14,7 +15,7 @@ import { SelectionModel } from "@angular/cdk/collections";
/* See https://v15.material.angular.io/components/tree/examples */
-export enum TemplateImportTreeBranches {
+export enum TemplateImportTreeBranch {
NONE = 0, // 000000
TASK_TYPES = 1 << 0, // 000001
TASK_GROUPS = 1 << 1, // 000010
@@ -33,12 +34,15 @@ export class TemplateTreeFlatNode {
expandable: boolean;
item: T;
label: string;
+ branch: TemplateImportTreeBranch
}
export class TemplateTreeNode {
children: TemplateTreeNode[] | null;
item: T;
label: string;
+ branch: TemplateImportTreeBranch;
+ origin: string; // TemplateID
}
@Component({
@@ -46,11 +50,13 @@ export class TemplateTreeNode {
templateUrl: "./template-import-tree.component.html",
styleUrls: ["./template-import-tree.component.scss"]
})
-export class TemplateImportTreeComponent {
+export class TemplateImportTreeComponent implements OnInit{
flatNodeMap = new Map, TemplateTreeNode>();
nestedNodeMap = new Map, TemplateTreeFlatNode>();
+ templatesMap = new Map();
+
selectedParent: TemplateTreeFlatNode | null = null;
treeControl: FlatTreeControl>
@@ -60,9 +66,9 @@ export class TemplateImportTreeComponent {
selection = new SelectionModel>(true);
@Input()
- template: ApiEvaluationTemplate;
+ templates: ApiEvaluationTemplate[];
@Input()
- branches: TemplateImportTreeBranches;
+ branches: TemplateImportTreeBranch;
constructor() {
this.treeFlattener = new MatTreeFlattener, TemplateTreeFlatNode>(
@@ -70,9 +76,14 @@ export class TemplateImportTreeComponent {
)
this.treeControl = new FlatTreeControl>(this.getLevel, this.isExpandable);
this.dataSource = new MatTreeFlatDataSource, TemplateTreeFlatNode>(this.treeControl, this.treeFlattener);
- this.dataSource.data = TemplateImportTreeComponent.buildTree(this.template, this.branches);
+
}
+ ngOnInit(): void {
+ this.dataSource.data = TemplateImportTreeComponent.buildTrees(this.templates, this.branches);
+ this.templates.forEach(it => this.templatesMap.set(it.id, it));
+ }
+
getLevel = (node: TemplateTreeFlatNode) => node.level;
isExpandable = (node: TemplateTreeFlatNode) => node.expandable;
getChildren = (node: TemplateTreeNode) => node.children;
@@ -84,6 +95,8 @@ export class TemplateImportTreeComponent {
flatNode.item = node.item;
flatNode.level = level;
flatNode.expandable = !!node.children?.length;
+ flatNode.branch = node.branch;
+ flatNode.label = node.label;
this.flatNodeMap.set(flatNode, node);
this.nestedNodeMap.set(node, flatNode);
return flatNode;
@@ -106,7 +119,7 @@ export class TemplateImportTreeComponent {
}
/** Toggle the to-do item selection. Select/deselect all the descendants node */
- todoItemSelectionToggle(node: TemplateTreeFlatNode): void {
+ itemSelectionToggle(node: TemplateTreeFlatNode): void {
this.selection.toggle(node);
const descendants = this.treeControl.getDescendants(node);
this.selection.isSelected(node)
@@ -119,7 +132,7 @@ export class TemplateImportTreeComponent {
}
/** Toggle a leaf to-do item selection. Check all the parents to see if they changed */
- todoLeafItemSelectionToggle(node: TemplateTreeFlatNode): void {
+ leafItemSelectionToggle(node: TemplateTreeFlatNode): void {
this.selection.toggle(node);
this.checkAllParentsSelection(node);
}
@@ -169,61 +182,148 @@ export class TemplateImportTreeComponent {
return null;
}
- public static buildTree(template: ApiEvaluationTemplate, branches: TemplateImportTreeBranches): TemplateTreeNode[] {
+ public getImportTemplate(){
+ const template = {
+ name: "",
+ description: "---Automatically generated template whose elements get imported. If this is seen, there was a programmer's error somewhere---",
+ taskTypes: this.getAllSelectedTaskTypes(),
+ taskGroups: this.getAllSelectedTaskGroups(),
+ tasks: this.getAllSelectedTaskTemplates(),
+ teams: this.getAllSelectedTeams(),
+ teamGroups: this.getAllSelectedTeamGroups(),
+ judges: this.getAllSelectedJudges(),
+ id:"---IMPORT_TEMPLATE_NO_ID---"
+ } as ApiEvaluationTemplate
+
+ /* Sanitisation: For each task, the group and type is required */
+ return template;
+ }
+
+ public getAllSelectedTaskTypes(){
+ return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TASK_TYPES) as ApiTaskType[]
+ }
+
+ public getAllSelectedTaskGroups(){
+ return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TASK_GROUPS) as ApiTaskGroup[];
+ }
+
+ public getAllSelectedTaskTemplates(){
+ return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TASK_TEMPLATES) as ApiTaskTemplate[]
+ }
+
+ public getAllSelectedTeams(){
+ return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TEAMS) as ApiTeam[]
+ }
+
+ public getAllSelectedTeamGroups(){
+ return this.getSelectedItemsForBranch(TemplateImportTreeBranch.TEAM_GROUPS) as ApiTeamGroup[]
+ }
+
+ public getAllSelectedJudges(){
+ return this.getSelectedItemsForBranch(TemplateImportTreeBranch.JUDGES) as ApiUser[]
+ }
+
+ /**
+ *
+ * @param branch A single branch, do not use ALL or NONE here (or any combination)
+ * @private
+ */
+ private getSelectedItemsForBranch(branch: TemplateImportTreeBranch){
+ /* Filter appropriately */
+ const items = this.selection.selected.filter(it => TemplateImportTreeComponent.checkForBranch(it.branch, branch)).map(it => this.flatNodeMap.get(it))
+ switch(branch){
+ case TemplateImportTreeBranch.NONE:
+ case TemplateImportTreeBranch.ALL:
+ throw new Error("Cannot type set for TemplateImportTreeBanches ALL and NONE. This is a programmer's error")
+ case TemplateImportTreeBranch.TASK_TYPES:
+ return items.map(it => it.item)
+ case TemplateImportTreeBranch.TASK_GROUPS:
+ return items.map(it => it.item)
+ case TemplateImportTreeBranch.TASK_TEMPLATES:
+ return items.map(it => {
+ /* Warning: collectionId remains and therefore must exist */
+ const newItem = it.item as ApiTaskTemplate;
+ newItem.id = undefined;
+ return newItem
+ })
+ case TemplateImportTreeBranch.TEAMS:
+ return items.map(it => {
+ const newItem = it.item as ApiTeam
+ newItem.id = undefined;
+ return newItem
+ })
+ case TemplateImportTreeBranch.TEAM_GROUPS:
+ return items.map(it => {
+ const newItem = it.item as ApiTeamGroup
+ newItem.id = undefined
+ return newItem
+ })
+ case TemplateImportTreeBranch.JUDGES:
+ return items.map(it => it.item)
+ }
+ }
+
+
+
+ public static buildTrees(templates: ApiEvaluationTemplate[], branches: TemplateImportTreeBranch): TemplateTreeNode[]{
+ return templates.map(it => this.buildTree(it, branches));
+ }
+
+ public static buildTree(template: ApiEvaluationTemplate, branches: TemplateImportTreeBranch): TemplateTreeNode {
const root = new TemplateTreeNode();
root.item = template;
root.label = template.name;
root.children = [] as TemplateTreeNode[];
- if(this.checkForBranch(branches, TemplateImportTreeBranches.TASK_TYPES)){
+ if(this.checkForBranch(branches, TemplateImportTreeBranch.TASK_TYPES)){
root.children.push(this.buildTaskTypesBranch(template));
}
- if(this.checkForBranch(branches, TemplateImportTreeBranches.TASK_GROUPS)){
+ if(this.checkForBranch(branches, TemplateImportTreeBranch.TASK_GROUPS)){
root.children.push(this.buildTaskGroupsBranch(template));
}
- if(this.checkForBranch(branches, TemplateImportTreeBranches.TASK_TEMPLATES)){
+ if(this.checkForBranch(branches, TemplateImportTreeBranch.TASK_TEMPLATES)){
root.children.push(this.buildTaskTemplatesBranch(template));
}
- if(this.checkForBranch(branches, TemplateImportTreeBranches.TEAMS)){
+ if(this.checkForBranch(branches, TemplateImportTreeBranch.TEAMS)){
root.children.push(this.buildTeamsBranch(template));
}
- if(this.checkForBranch(branches, TemplateImportTreeBranches.TEAM_GROUPS)){
+ if(this.checkForBranch(branches, TemplateImportTreeBranch.TEAM_GROUPS)){
root.children.push(this.buildTeamGroupsBranch(template));
}
- if(this.checkForBranch(branches, TemplateImportTreeBranches.TEAM_GROUPS)){
+ if(this.checkForBranch(branches, TemplateImportTreeBranch.TEAM_GROUPS)){
root.children.push(this.buildJudgesBranch(template));
}
- return [root];
+ return root;
}
- private static checkForBranch(branches: TemplateImportTreeBranches, test: TemplateImportTreeBranches): boolean{
+ public static checkForBranch(branches: TemplateImportTreeBranch, test: TemplateImportTreeBranch): boolean{
return (branches & test) === test
}
public static buildTaskTypesBranch(template: ApiEvaluationTemplate): TemplateTreeNode {
- return this.buildBranch(template, "taskTypes", "Task Types");
+ return this.buildBranch(template, "taskTypes", "Task Types", TemplateImportTreeBranch.TASK_TYPES);
}
public static buildTaskGroupsBranch(template: ApiEvaluationTemplate): TemplateTreeNode {
- return this.buildBranch(template, "taskGroups", "Task Groups");
+ return this.buildBranch(template, "taskGroups", "Task Groups", TemplateImportTreeBranch.TASK_GROUPS);
}
public static buildTaskTemplatesBranch(template: ApiEvaluationTemplate): TemplateTreeNode {
- return this.buildBranch(template, "tasks", "Task Templates");
+ return this.buildBranch(template, "tasks", "Task Templates", TemplateImportTreeBranch.TASK_TEMPLATES);
}
public static buildTeamsBranch(template: ApiEvaluationTemplate): TemplateTreeNode {
- return this.buildBranch(template, "teams", "Teams");
+ return this.buildBranch(template, "teams", "Teams", TemplateImportTreeBranch.TEAMS);
}
public static buildTeamGroupsBranch(template: ApiEvaluationTemplate): TemplateTreeNode {
- return this.buildBranch(template, "teamGroups", "Team Groups");
+ return this.buildBranch(template, "teamGroups", "Team Groups", TemplateImportTreeBranch.TEAM_GROUPS);
}
public static buildJudgesBranch(template: ApiEvaluationTemplate): TemplateTreeNode {
- return this.buildBranch(template, "judges", "Judges");
+ return this.buildBranch(template, "judges", "Judges", TemplateImportTreeBranch.JUDGES);
}
- public static buildBranch(template: ApiEvaluationTemplate, key: string, rootLabel: string): TemplateTreeNode {
+ public static buildBranch(template: ApiEvaluationTemplate, key: string, rootLabel: string, branch: TemplateImportTreeBranch): TemplateTreeNode {
const root = new TemplateTreeNode();
root.label = rootLabel;
root.item = template[key];
@@ -232,6 +332,8 @@ export class TemplateImportTreeComponent {
item.label = it["name"];
item.item = it;
item.children = null;
+ item.branch = branch;
+ item.origin = template.id
return item;
});
return root;
diff --git a/frontend/src/app/template/template-builder/template-builder.component.html b/frontend/src/app/template/template-builder/template-builder.component.html
index 95590d592..6023d3cb3 100644
--- a/frontend/src/app/template/template-builder/template-builder.component.html
+++ b/frontend/src/app/template/template-builder/template-builder.component.html
@@ -29,6 +29,17 @@ Edit evaluation template {{(builderService.templateAsObservable() | async)?.
[inline]="true"
matTooltip="Download the entire evaluation template as JSON">
+
+
+
+ file_upload
+
+
diff --git a/frontend/src/app/template/template-builder/template-builder.component.ts b/frontend/src/app/template/template-builder/template-builder.component.ts
index 4dca67b58..fce4e5fd3 100644
--- a/frontend/src/app/template/template-builder/template-builder.component.ts
+++ b/frontend/src/app/template/template-builder/template-builder.component.ts
@@ -1,14 +1,28 @@
import { Component, HostListener, OnDestroy, OnInit, ViewChild } from "@angular/core";
-import {AbstractTemplateBuilderComponent} from './components/abstract-template-builder.component';
-import {DeactivationGuarded} from '../../services/can-deactivate.guard';
-import {Observable, Subscription} from 'rxjs';
-import { ApiTaskGroup, ApiTaskTemplate, ApiTaskType, DownloadService, TemplateService, UserService } from "../../../../openapi";
-import {ActivatedRoute, Router, RouterStateSnapshot} from '@angular/router';
-import {MatSnackBar} from '@angular/material/snack-bar';
-import {TemplateBuilderService} from './template-builder.service';
-import {take} from 'rxjs/operators';
+import { AbstractTemplateBuilderComponent } from "./components/abstract-template-builder.component";
+import { DeactivationGuarded } from "../../services/can-deactivate.guard";
+import { forkJoin, Observable, Subscription } from "rxjs";
+import {
+ ApiEvaluationTemplate, ApiEvaluationTemplateOverview,
+ ApiTaskGroup,
+ ApiTaskTemplate,
+ ApiTaskType,
+ DownloadService,
+ TemplateService,
+ UserService
+} from "../../../../openapi";
+import { ActivatedRoute, Router, RouterStateSnapshot } from "@angular/router";
+import { MatSnackBar } from "@angular/material/snack-bar";
+import { TemplateBuilderService } from "./template-builder.service";
+import { map, switchMap, take } from "rxjs/operators";
import { TaskTemplateEditorLauncher } from "./components/tasks-list/task-templates-list.component";
import { TaskTemplateEditorComponent } from "./components/task-template-editor/task-template-editor.component";
+import { MatDialog } from "@angular/material/dialog";
+import {
+ TemplateImportDialogComponent,
+ TemplateImportDialogData
+} from "./components/template-import-dialog/template-import-dialog.component";
+import { TemplateImportTreeBranch } from "./components/template-import-tree/template-import-tree.component";
@Component({
selector: 'app-template-builder',
@@ -32,6 +46,7 @@ export class TemplateBuilderComponent extends AbstractTemplateBuilderComponent i
private downloadService: DownloadService,
route: ActivatedRoute,
private router: Router,
+ private dialg: MatDialog,
snackBar: MatSnackBar,
public builderService: TemplateBuilderService
) {
@@ -71,6 +86,33 @@ export class TemplateBuilderComponent extends AbstractTemplateBuilderComponent i
}
}
+ public onUpload(contents: string){
+ console.log("Uploaded "+contents.length+" characters")
+ }
+
+ public import(){
+ console.log("Import open")
+ let templateList : Observable;
+ templateList = this.templateService.getApiV2TemplateList().pipe(
+ map(overviews => overviews.map(o => this.templateService.getApiV2TemplateByTemplateId(o.id))),
+ switchMap(templateList => forkJoin(...templateList))
+ );
+ templateList.subscribe(templates => {
+ console.log("Templates ", templates)
+ const ownIdx = templates.indexOf(this.builderService.getTemplate())
+ templates.splice(ownIdx,1)
+ const dialogref = this.dialg.open(TemplateImportDialogComponent, {width: '800px', data: {templates: templates, branches: TemplateImportTreeBranch.ALL} as TemplateImportDialogData})
+ dialogref.afterClosed().subscribe( d => {
+ this.onImport(d)
+ })
+ })
+
+ }
+
+ public onImport(templateToImportFrom: ApiEvaluationTemplate){
+ console.log("Importing...", templateToImportFrom)
+ }
+
public save(){
// FIXME re-enable form validation. possibly on the form-builder?
this.isSaving = true;
diff --git a/frontend/src/app/template/template-builder/template-builder.module.ts b/frontend/src/app/template/template-builder/template-builder.module.ts
index 1943f3b13..ce27855e5 100644
--- a/frontend/src/app/template/template-builder/template-builder.module.ts
+++ b/frontend/src/app/template/template-builder/template-builder.module.ts
@@ -19,6 +19,7 @@ import { MatChipsModule } from "@angular/material/chips";
import { TemplateImportTreeComponent } from './components/template-import-tree/template-import-tree.component';
import { MatTreeModule } from "@angular/material/tree";
import { MatCheckboxModule } from "@angular/material/checkbox";
+import { TemplateImportDialogComponent } from './components/template-import-dialog/template-import-dialog.component';
@NgModule({
@@ -26,7 +27,8 @@ import { MatCheckboxModule } from "@angular/material/checkbox";
TemplateBuilderComponent,
TeamgroupsListComponent,
TeamgroupsDialogComponent,
- TemplateImportTreeComponent
+ TemplateImportTreeComponent,
+ TemplateImportDialogComponent
],
imports: [