frontend: change config values
- does not apply changes to the backend
- allows only leaf's values changing, not creating nor deleting subtrees
diff --git a/frontend/config/config.component.css b/frontend/config/config.component.css
index c2336b0..6a10dab 100644
--- a/frontend/config/config.component.css
+++ b/frontend/config/config.component.css
@@ -92,4 +92,16 @@
.item_action_expand:hover {
color:green;
-}
\ No newline at end of file
+}
+
+.modifications-status {
+ position: fixed;
+ bottom: 2em;
+ right: 2em;
+ background-color: #fafad2;
+ border: 2px solid #e4e4a4;
+}
+.modifications-status-place {
+ margin-bottom: 2em;
+ margin-right: 2em;
+}
diff --git a/frontend/config/config.component.html b/frontend/config/config.component.html
index e1ea27f..7a1ef15 100644
--- a/frontend/config/config.component.html
+++ b/frontend/config/config.component.html
@@ -70,6 +70,14 @@
<tree-view [treeData]="activeSession.data"></tree-view>
<!--<pre>{{activeSession.data | json}}</pre>-->
</div>
+ <div #modificationsStatus *ngIf="activeSession.modifications" class="modifications-status-place">
+ <div *ngIf="activeSession.modifications" class="modifications-status msg-rounded" [style.width.px]="modificationsStatus.offsetWidth">
+ Configuration data were changed. Do you wish to
+ <button (click)="applyChanges()">apply</button> /
+ <button (click)="cancelChanges()">cancel</button>
+ all changes?
+ </div>
+ </div>
</ng-container>
</div>
diff --git a/frontend/config/config.component.ts b/frontend/config/config.component.ts
index e1830f4..d13931f 100644
--- a/frontend/config/config.component.ts
+++ b/frontend/config/config.component.ts
@@ -15,7 +15,6 @@
activeSession: Session;
err_msg = "";
- objectKeys = Object.keys;
constructor(private sessionsService: SessionsService, private router: Router) {}
addSession() {
@@ -155,7 +154,47 @@
this.sessionsService.storeData();
});
}
+
+ cancelChangesNode(node, recursion = true) {
+
+ if (node['path'] in this.activeSession.modifications) {
+ node['dirty'] = false;
+ if (this.activeSession.modifications[node['path']]['type'] == 'change') {
+ node['value'] = this.activeSession.modifications[node['path']]['original'];
+ }
+ delete this.activeSession.modifications[node['path']];
+ if (!Object.keys(this.activeSession.modifications).length) {
+ delete this.activeSession.modifications;
+ return;
+ }
+ }
+ /* recursion */
+ if (recursion && 'children' in node) {
+ for (let child of node['children']) {
+ this.cancelChangesNode(child);
+ if (!this.activeSession.modifications) {
+ return;
+ }
+ }
+ }
+ }
+
+ cancelChanges() {
+ for (let iter of this.activeSession.data) {
+ this.cancelChangesNode(iter);
+ if (!this.activeSession.modifications) {
+ break;
+ }
+ }
+ this.sessionsService.storeData();
+ }
+
+ applyChanges() {
+ /* TODO */
+ this.cancelChanges();
+ }
+
ngOnInit(): void {
this.sessionsService.checkSessions();
this.activeSession = this.sessionsService.getActiveSession();
diff --git a/frontend/config/session.ts b/frontend/config/session.ts
index 9f56289..5bdda36 100644
--- a/frontend/config/session.ts
+++ b/frontend/config/session.ts
@@ -5,6 +5,7 @@
public key: string,
public device: Device,
public data = null,
+ public modifications = null,
public cpblts: string = "",
public dataVisibility: string = 'none',
public statusVisibility: boolean = true,
diff --git a/frontend/config/sessions.service.ts b/frontend/config/sessions.service.ts
index 09edecc..970f417 100644
--- a/frontend/config/sessions.service.ts
+++ b/frontend/config/sessions.service.ts
@@ -106,6 +106,27 @@
.catch((err: Response | any) => Observable.throw(err));
}
+ setDirty(node) {
+ let activeSession = this.getActiveSession();
+ if (!activeSession.modifications) {
+ return;
+ }
+
+ if (node['path'] in activeSession.modifications) {
+ node['dirty'] = true;
+ if (activeSession.modifications[node['path']]['type'] == 'change') {
+ activeSession.modifications[node['path']]['original'] = node['value'];
+ }
+ node['value'] = activeSession.modifications[node['path']]['value'];
+ }
+ /* recursion */
+ if ('children' in node) {
+ for (let child of node['children']) {
+ this.setDirty(child);
+ }
+ }
+ }
+
rpcGetSubtree(key: string, all: boolean, path: string = ""): Observable<string[]> {
let params = new URLSearchParams();
params.set('key', key);
@@ -115,7 +136,22 @@
}
let options = new RequestOptions({ search: params });
return this.http.get('/netopeer/session/rpcGet', options)
- .map((resp: Response) => resp.json())
+ .map((resp: Response) => {
+ let result = resp.json();
+ //console.log(result);
+ if (result['success']) {
+ if (path.length) {
+ for (let iter of result['data']['children']) {
+ this.setDirty(iter);
+ }
+ } else {
+ for (let iter of result['data']) {
+ this.setDirty(iter);
+ }
+ }
+ }
+ return result;
+ })
.catch((err: Response | any) => Observable.throw(err));
}
diff --git a/frontend/config/tree.component.css b/frontend/config/tree.component.css
index 0f98548..d511188 100644
--- a/frontend/config/tree.component.css
+++ b/frontend/config/tree.component.css
@@ -39,6 +39,10 @@
display: inline-block;
}
+.dirty {
+ background-color: #fafad2;
+}
+
.status,
.node_info {
color: grey;
@@ -74,8 +78,7 @@
margin-left: 1.5em;
}
-.value,
-.value_standalone {
+.value {
word-wrap: break-word;
}
diff --git a/frontend/config/tree.component.html b/frontend/config/tree.component.html
index df773cc..1fc786c 100644
--- a/frontend/config/tree.component.html
+++ b/frontend/config/tree.component.html
@@ -4,7 +4,7 @@
<!-- leaf -->
<ng-container *ngSwitchCase="4">
- <div #nodeContainer class="node">
+ <div #nodeContainer class="node" [class.dirty]="node['dirty']">
<ng-container *ngFor="let indent of indentation">
<img *ngIf="!indent" [style.height.px]="nodeContainer.offsetHeight" class="indentation" src="assets/netopeer/icons/tree_cont.svg"/>
<img *ngIf="indent" [style.height.px]="nodeContainer.offsetHeight" class="indentation" src="assets/netopeer/icons/tree_empty.svg"/>
@@ -18,32 +18,38 @@
<img class="icon" src="assets/netopeer/icons/key.svg" alt="key" title="list key"/>
</ng-container>
<ng-container *ngIf="node['info']['config'] && !node['info']['key']">
- <img *ngIf="!node['edit']" (click)="node['edit']=true" class="icon_action" src="assets/netopeer/icons/edit.svg"
+ <img *ngIf="!node['edit']" class="icon_action" src="assets/netopeer/icons/edit.svg"
+ alt="edit" title="edit value" tabindex=0
+ (click)="startEditing(node, $event.target);" (keyup.enter)="startEditing(node, $event.target);"
onmouseover="this.src='assets/netopeer/icons/edit_active.svg'"
- onmouseout="this.src='assets/netopeer/icons/edit.svg'" alt="edit" title="edit value"/>
+ onmouseout="this.src='assets/netopeer/icons/edit.svg'"/>
<img *ngIf="node['edit']" class="icon" src="assets/netopeer/icons/edit.svg" alt="edit" title="editing value"/>
</ng-container>
<div>{{node['info']['name']}}</div> :
- <div class="value" *ngIf="!node['edit']">{{node['value']}}</div>
+ <div class="value" *ngIf="!node['edit']" (click)="startEditing(node, $event.target);">{{node['value']}}</div>
<div class="value" *ngIf="node['edit']">{{node['info']['datatype']}} <span *ngIf="node['info']['datatype'] != node['info']['datatypebase']">({{node['info']['datatypebase']}})</span></div>
<div class="module_name">{{node['info']['module']}}</div>
</div>
- <div #nodeContainer class="node_edit" *ngIf="node['edit']">
+ <div #nodeContainer class="node_edit" [class.dirty]="node['dirty']" *ngIf="node['edit']">
<ng-container *ngFor="let indent of indentation">
<img *ngIf="!indent" class="indentation" src="assets/netopeer/icons/tree_cont.svg"/>
<img *ngIf="indent" class="indentation" src="assets/netopeer/icons/tree_empty.svg"/>
</ng-container>
<img *ngIf="node['last']" class="indentation" src="assets/netopeer/icons/tree_empty.svg"/>
<img *ngIf="!node['last']" class="indentation" src="assets/netopeer/icons/tree_cont.svg"/>
- <img *ngIf="node['edit']" (click)="node['edit']=false" class="icon_action" src="assets/netopeer/icons/close.svg"
+ <img *ngIf="node['edit']" class="icon_action" src="assets/netopeer/icons/close.svg"
+ alt="cancel" title="cancel editing" tabindex=0
+ (click)="node['edit']=false" (keyup.enter)="node['edit']=false"
onmouseover="this.src='assets/netopeer/icons/close_active.svg'"
- onmouseout="this.src='assets/netopeer/icons/close.svg'" alt="cancel" title="cancel editing"/>
- <img *ngIf="node['edit']" (click)="node['edit']=false" class="icon_action icon_hidden" src="assets/netopeer/icons/confirm.svg"
- id="{{node['path']}}_value_confirm"
+ onmouseout="this.src='assets/netopeer/icons/close.svg'" />
+ <img *ngIf="node['edit']" class="icon_action icon_hidden" src="assets/netopeer/icons/confirm.svg"
+ id="{{node['path']}}_value_confirm" alt="done" title="store changes" tabindex=0
+ (click)="changeValue(node, $event.target)" (keyup.enter)="changeValue(node, $event.target)"
onmouseover="this.src='assets/netopeer/icons/confirm_active.svg'"
- onmouseout="this.src='assets/netopeer/icons/confirm.svg'" alt="done" title="store changes" />
- <input type="text" class="value_standalone" value="{{node['value']}}"
- (keyup)="checkValue(node, $event.target)" (change)="checkValue(node, $event.target)"/>
+ onmouseout="this.src='assets/netopeer/icons/confirm.svg'"/>
+ <input type="text" class="value" value="{{node['value']}}" tabindex=0
+ (keyup)="checkValue(node, $event.target)" (change)="checkValue(node, $event.target)"
+ (keyup.enter)="changeValue(node, $event.target)" (keyup.escape)="node['edit']=false"/>
</div>
</ng-container>
diff --git a/frontend/config/tree.component.ts b/frontend/config/tree.component.ts
index f49462b..c61ae1a 100644
--- a/frontend/config/tree.component.ts
+++ b/frontend/config/tree.component.ts
@@ -1,4 +1,4 @@
-import {Component, Input, OnInit} from '@angular/core';
+import {Component, Input, OnInit, ChangeDetectorRef} from '@angular/core';
import {Session} from './session';
import {SessionsService} from './sessions.service';
@@ -14,11 +14,10 @@
@Input() indentation;
c = 1; i = 1;
activeSession: Session;
- objectKeys = Object.keys;
- constructor(private sessionsService: SessionsService) {}
+ constructor(private sessionsService: SessionsService, private changeDetector: ChangeDetectorRef) {}
ngOnInit(): void {
- this.activeSession = this.sessionsService.getActiveSession(this.sessionsService.activeSession);
+ this.activeSession = this.sessionsService.getActiveSession();
}
inheritIndentation(node) {
@@ -36,6 +35,15 @@
}
}
+ startEditing(node, target) {
+ let parent = target.parentElement;
+
+ node['edit'] = true;
+ this.changeDetector.detectChanges();
+
+ parent.nextElementSibling.lastElementChild.focus();
+ }
+
checkValue(node, target) {
let confirm = target.previousElementSibling;
this.sessionsService.checkValue(this.activeSession.key, node['path'], target.value).subscribe(result => {
@@ -48,6 +56,52 @@
}
});
}
+
+ changeValue(node, target) {
+ let input;
+ if (target.classList.contains('value')) {
+ if (target.classList.contains('invalid')) {
+ return;
+ }
+ input = target;
+ } else {
+ input = target.nextElementSibling;
+ }
+
+ if (!this.activeSession.modifications) {
+ this.activeSession.modifications = {};
+ }
+ if (!(node['path'] in this.activeSession.modifications)) {
+ /* new record */
+ if (node['value'] == input['value']) {
+ /* no change to the original value */
+ return;
+ }
+ this.activeSession.modifications[node['path']] = {};
+ this.activeSession.modifications[node['path']]['type'] = 'change';
+ this.activeSession.modifications[node['path']]['original'] = node['value'];
+ this.activeSession.modifications[node['path']]['value'] = input.value;
+ node['dirty'] = true;
+ } else if (this.activeSession.modifications[node['path']]['type'] == 'change' &&
+ this.activeSession.modifications[node['path']]['original'] == input['value']) {
+ /* change to the original value, remove the change record */
+ delete this.activeSession.modifications[node['path']];
+ node['dirty'] = false;
+
+ if (!Object.keys(this.activeSession.modifications).length) {
+ delete this.activeSession.modifications;
+ }
+ } else {
+ /* another change of existing change record */
+ this.activeSession.modifications[node['path']]['value'] = input.value;
+ node['dirty'] = true;
+ }
+ console.log('Modifications list: ' + this.activeSession.modifications);
+
+ node['value'] = input.value;
+ node['edit'] = false;
+ this.sessionsService.storeData();
+ }
expandable(node): boolean {
if (node['info']['type'] == 1 || /* container */
@@ -79,7 +133,7 @@
}
return node['hasHiddenChild'];
}
-
+
collapse(node) {
delete node['children'];
this.activeSession.dataVisibility = 'mixed';