Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(list-value): show hierarchy of list node (DEV-1092) #805

Merged
merged 2 commits into from Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,8 +1,11 @@
<span *ngIf="mode !== 'read' && listRootNode !== undefined">

<!-- button to select a list node -->
<span [matTooltip]="(!selectedNode && !listRootNode.children.length) ? 'This list doesn\'t have any node values' : 'Select list value'" [matTooltipPosition]="'above'">
<button mat-stroked-button [matMenuTriggerFor]="mainMenu" type="button" placeholder="List value" [disabled]="!selectedNode && !listRootNode.children.length">
<span
[matTooltip]="(!selectedNode && !listRootNode.children.length) ? 'This list doesn\'t have any node values' : 'Select list value'"
[matTooltipPosition]="'above'">
<button mat-stroked-button [matMenuTriggerFor]="mainMenu" type="button" placeholder="List value"
[disabled]="!selectedNode && !listRootNode.children.length">

<!-- if no node is selected: display list label; if the list does not have any list nodes, the button should be disabled -->
<span *ngIf="!selectedNode">{{listRootNode.label}}</span>
Expand All @@ -17,7 +20,7 @@
<span *ngFor="let child of listRootNode.children">
<span *ngIf="child.children && child.children.length > 0">
<button mat-menu-item [matMenuTriggerFor]="menu.childMenu" (click)="getSelectedNode(child)"
type="button">
type="button">
{{child.label}}
</button>
<app-sublist-value #menu [children]="child.children" (selectedNode)="getSelectedNode($event)">
Expand All @@ -36,29 +39,28 @@
<span class="custom-error-message">New value must be different than the current value.</span>
</mat-error>
<mat-error *ngIf="valueFormControl.hasError('duplicateValue')">
<span class="custom-error-message">This value already exists for this property. Duplicate values are not allowed.</span>
<span class="custom-error-message">This value already exists for this property. Duplicate values are not
allowed.</span>
</mat-error>
</span>
<span *ngIf="mode === 'read'; else showForm" class="read-mode-view">
<span class="rm-value">{{valueFormControl.value}}</span>
<span class="rm-value list">
<span class="hierarchy" *ngFor="let item of selectedNodeHierarchy, let first = first, let last = last">
<mat-icon *ngIf="!first">chevron_right</mat-icon>
<span [class.last]="last">{{ item }}</span>
</span>
</span>
<span class="rm-comment" *ngIf="shouldShowComment">{{commentFormControl.value}}</span>
</span>
<ng-template #showForm>
<span [formGroup]="form">
<mat-form-field class="large-field child-value-component" *ngIf="mode === 'read'" floatLabel="never">
<mat-form-field class="large-field child-value-component" *ngIf="mode === 'read'" floatLabel="never">
<input [formControlName]="'value'" class="value" type="text" placeholder="List value" matInput readonly>
</mat-form-field>
<mat-form-field class="large-field value-component-comment">
<textarea matInput
cdkTextareaAutosize
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="6"
[formControlName]="'comment'"
class="comment"
placeholder="Comment"
type="text"
spellcheck="false">
</mat-form-field>
<mat-form-field class="large-field value-component-comment">
<textarea matInput cdkTextareaAutosize cdkAutosizeMinRows="1" cdkAutosizeMaxRows="6"
[formControlName]="'comment'" class="comment" placeholder="Comment" type="text" spellcheck="false">
</textarea>
</mat-form-field>
</mat-form-field>
</span>
</ng-template>
</ng-template>
Expand Up @@ -5,6 +5,7 @@ import { MatDialogModule } from '@angular/material/dialog';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { By } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
Expand Down Expand Up @@ -40,7 +41,7 @@ class TestHostDisplayValueComponent implements OnInit {

MockResource.getTestThing().subscribe(res => {
const inputVal: ReadListValue =
res.getValuesAs('http://0.0.0.0:3333/ontology/0001/anything/v2#hasListItem', ReadListValue)[0];
res.getValuesAs('http://0.0.0.0:3333/ontology/0001/anything/v2#hasListItem', ReadListValue)[0];
this.displayInputVal = inputVal;
this.mode = 'read';
});
Expand Down Expand Up @@ -92,6 +93,7 @@ describe('ListValueComponent', () => {
MatInputModule,
MatMenuModule,
MatSnackBarModule,
MatTooltipModule,
ReactiveFormsModule,
],
providers: [
Expand All @@ -114,6 +116,18 @@ describe('ListValueComponent', () => {
let commentInputNativeElement;

beforeEach(() => {
const valuesSpy = TestBed.inject(DspApiConnectionToken);
(valuesSpy.v2.list as jasmine.SpyObj<ListsEndpointV2>).getList.and.callFake(
(rootNodeIri: string) => {
const res = new ListNodeV2();
res.id = 'http://rdfh.ch/lists/0001/treeList01';
res.label = 'Tree list node 01';
res.isRootNode = false;
res.children = [];
return of(res);
}
);

testHostFixture = TestBed.createComponent(TestHostDisplayValueComponent);
testHostComponent = testHostFixture.componentInstance;
testHostFixture.detectChanges();
Expand Down Expand Up @@ -158,7 +172,7 @@ describe('ListValueComponent', () => {

expect(testHostComponent.inputValueComponent.mode).toEqual('update');

expect(valuesSpy.v2.list.getList).toHaveBeenCalledTimes(1);
expect(valuesSpy.v2.list.getList).toHaveBeenCalledTimes(3);
expect(valuesSpy.v2.list.getList).toHaveBeenCalledWith('http://rdfh.ch/lists/0001/treeList');
expect(testHostComponent.inputValueComponent.listRootNode.children.length).toEqual(0);

Expand Down
Expand Up @@ -14,7 +14,6 @@ import { Subscription } from 'rxjs';
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
import { BaseValueDirective } from 'src/app/main/directive/base-value.directive';
import { ErrorHandlerService } from 'src/app/main/services/error-handler.service';
import { NotificationService } from 'src/app/main/services/notification.service';

// https://stackoverflow.com/questions/45661010/dynamic-nested-reactive-form-expressionchangedafterithasbeencheckederror
const resolvedPromise = Promise.resolve(null);
Expand Down Expand Up @@ -42,6 +41,8 @@ export class ListValueComponent extends BaseValueDirective implements OnInit, On

customValidators = [];

selectedNodeHierarchy: string[] = [];

constructor(
@Inject(FormBuilder) private _fb: FormBuilder,
@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection,
Expand All @@ -52,6 +53,7 @@ export class ListValueComponent extends BaseValueDirective implements OnInit, On

getInitValue(): string | null {
if (this.displayValue !== undefined) {
this.getReadModeValue(this.displayValue.listNode);
return this.displayValue.listNode;
} else {
return null;
Expand Down Expand Up @@ -160,4 +162,40 @@ export class ListValueComponent extends BaseValueDirective implements OnInit, On
this.valueFormControl.setValue(item.id);
}

getReadModeValue(nodeIri: string): void {
const rootNodeIris = this.propertyDef.guiAttributes;
for (const rootNodeIri of rootNodeIris) {
const trimmedRootNodeIRI = rootNodeIri.substr(7, rootNodeIri.length - (1 + 7));
this._dspApiConnection.v2.list.getList(trimmedRootNodeIRI).subscribe(
(response: ListNodeV2) => {
if (!response.children.length) { // this shouldn't happen since users cannot select the root node
this.selectedNodeHierarchy.push(response.label);
} else {
this.selectedNodeHierarchy = this._getHierarchy(nodeIri, response.children);
}
}, (error: ApiResponseError) => {
this._errorHandler.showMessage(error);
});
}
}

_getHierarchy(selectedNodeIri: string, children: ListNodeV2[]): string[] {
for (let i = 0; i < children.length; i++) {
const node = children[i];
if (node.id !== selectedNodeIri) {
if (node.children) {
const path = this._getHierarchy(selectedNodeIri, node.children);

if (path) {
path.unshift(node.label);
return path;
}
}
} else {
return [node.label];
}
}
}


}
22 changes: 18 additions & 4 deletions src/assets/style/_viewer.scss
Expand Up @@ -2,14 +2,25 @@

.read-mode-view {
font: 400 15px/24px sans-serif;
.rm-value, .rm-comment {

.rm-value,
.rm-comment {
display: block;
margin: 0px;
}

.rm-value.text-value {
white-space: pre-wrap;
}

.rm-value.list,
.rm-value .hierarchy {
display: inline-flex;

.last {
font-weight: bold;
}
}
}

.child-value-component,
Expand All @@ -36,6 +47,7 @@
display: inline-block;
vertical-align: bottom;
width: 49%;

&:nth-child(2) {
padding-left: 2%;
}
Expand Down Expand Up @@ -91,6 +103,7 @@
}

.value-action {

.material-icons,
.mat-icon {
font-size: 18px;
Expand Down Expand Up @@ -139,9 +152,10 @@
background-color: inherit !important;
}

code, pre {
code,
pre {
font-family: monospace !important;
background-color: hsla(0,0%,78%,.3);
background-color: hsla(0, 0%, 78%, .3);
padding: .15em;
border-radius: 2px;
}
}