Skip to content

Commit 005ca87

Browse files
committed
multi-select
1 parent 613602b commit 005ca87

File tree

9 files changed

+254
-38
lines changed

9 files changed

+254
-38
lines changed

examples/jsMind.Server.App/Pages/Index.razor

+13-4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ SelectedNodeId = @_selectedNodeId
3939
readonly MindMapOptions _options = new MindMapOptions
4040
{
4141
Editable = false,
42+
MultiSelect = true,
4243
Theme = MindMapThemes.Primary
4344
};
4445

@@ -49,7 +50,7 @@ SelectedNodeId = @_selectedNodeId
4950
Id = "root",
5051
Topic = "-Root-",
5152
Children = new List<MindMapTreeNode>
52-
{
53+
{
5354
new MindMapTreeNode
5455
{
5556
Id = "sub1.0",
@@ -60,7 +61,7 @@ SelectedNodeId = @_selectedNodeId
6061
Id = "sub1.1",
6162
Topic = "sub1.1-right",
6263
Children = new List<MindMapTreeNode>
63-
{
64+
{
6465
new MindMapTreeNode
6566
{
6667
Id = "sub1.1a",
@@ -86,7 +87,7 @@ SelectedNodeId = @_selectedNodeId
8687
readonly MindMapArrayData _arrayData1 = new MindMapArrayData
8788
{
8889
Nodes = new List<MindMapArrayNode>
89-
{
90+
{
9091
new MindMapArrayNode
9192
{
9293
IsRoot = true,
@@ -146,6 +147,14 @@ SelectedNodeId = @_selectedNodeId
146147

147148
async Task OnShowTree(EventArgs args)
148149
{
150+
var found = await _myTreeNodeContainer.GetNode("root");
151+
var selected = new List<MindMapTreeNode>
152+
{
153+
_treeData.RootNode,
154+
_treeData.RootNode.Children.Last()
155+
};
156+
await _myTreeNodeContainer.SelectNodes(selected);
157+
149158
await _myTreeNodeContainer.Expand();
150159
}
151160

@@ -168,7 +177,7 @@ SelectedNodeId = @_selectedNodeId
168177
{
169178
Id = "newTreeId",
170179
Topic = "new Tree node",
171-
Data = new Dictionary<string, string> { { "background-color", "lightblue" } }
180+
Data = new Dictionary<string, string> { { "background-color", "lightblue" }, { "class", "selected" } }
172181
};
173182

174183
await _myTreeNodeContainer.SetEditable(true);

examples/jsMind.WASM.App/wwwroot/manifest.json

+1-8
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,5 @@
44
"start_url": "./",
55
"display": "standalone",
66
"background_color": "#ffffff",
7-
"theme_color": "#03173d",
8-
"icons": [
9-
{
10-
"src": "icon-512.png",
11-
"type": "image/png",
12-
"sizes": "512x512"
13-
}
14-
]
7+
"theme_color": "#03173d"
158
}

jsMind.Blazor - Solution.sln.DotSettings

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<s:Boolean x:Key="/Default/UserDictionary/Words/=Greensea/@EntryIndexedValue">True</s:Boolean>
55
<s:Boolean x:Key="/Default/UserDictionary/Words/=Heyenrath/@EntryIndexedValue">True</s:Boolean>
66
<s:Boolean x:Key="/Default/UserDictionary/Words/=isroot/@EntryIndexedValue">True</s:Boolean>
7+
<s:Boolean x:Key="/Default/UserDictionary/Words/=jmnode/@EntryIndexedValue">True</s:Boolean>
78
<s:Boolean x:Key="/Default/UserDictionary/Words/=jsmind/@EntryIndexedValue">True</s:Boolean>
89
<s:Boolean x:Key="/Default/UserDictionary/Words/=mindmap/@EntryIndexedValue">True</s:Boolean>
910
<s:Boolean x:Key="/Default/UserDictionary/Words/=nodeid/@EntryIndexedValue">True</s:Boolean>

src/jsMind.Blazor/Components/MindMapContainer.Interop.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Threading.Tasks;
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
23
using Microsoft.JSInterop;
34

45
namespace JsMind.Blazor.Components
@@ -31,6 +32,17 @@ public ValueTask SelectNode(T node)
3132
return Runtime.InvokeVoidAsync("MindMap.selectNode", _containerId, node.Id);
3233
}
3334

35+
public ValueTask SelectNodes(List<T> nodes)
36+
{
37+
SelectedNodes = nodes;
38+
return Runtime.InvokeVoidAsync("MindMap.selectNodes", _containerId, nodes);
39+
}
40+
41+
public ValueTask<T> GetNode(string id)
42+
{
43+
return Runtime.InvokeAsync<T>("MindMap.getNode", _containerId, id);
44+
}
45+
3446
public ValueTask ClearSelect()
3547
{
3648
return Runtime.InvokeVoidAsync("MindMap.clearSelect", _containerId);

src/jsMind.Blazor/Components/MindMapContainer.cs

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
5454

5555
protected abstract object MindMapData { get; }
5656

57+
public List<T> SelectedNodes = new List<T>();
58+
5759
public void Dispose()
5860
{
5961
try

src/jsMind.Blazor/Models/MindMapOptions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ public class MindMapOptions
66
{
77
public bool Editable { get; set; }
88

9+
public bool MultiSelect { get; set; }
10+
911
public string Theme { get; set; } = MindMapThemes.Primary;
1012
}
1113
}

src/jsMind.Blazor/Scripts/jsmind-interop.js

+76-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ MindMap.show = function (dotnetReference, containerId, mindMapOptions, mindMapDa
1414
"version": "1.0"
1515
},
1616
"format": mindMapData.format,
17-
"data": mindMapData.data,
17+
"data": mindMapData.data
1818
};
1919

2020
const options = {
@@ -23,8 +23,8 @@ MindMap.show = function (dotnetReference, containerId, mindMapOptions, mindMapDa
2323
theme: mindMapOptions.theme
2424
}
2525

26-
// Keep a reference to the javascript MindMap object
27-
instances[containerId] = window.jsMind.show(options, mind);
26+
const mm = window.jsMind.show(options, mind);;
27+
mm["multiSelect"] = mindMapOptions.multiSelect;
2828

2929
// Call a callback to indicate that the MindMap is shown
3030
dotnetReference.invokeMethodAsync("OnShowCallback", { evt: "done", node: "", data: [] });
@@ -50,11 +50,50 @@ MindMap.show = function (dotnetReference, containerId, mindMapOptions, mindMapDa
5050
}
5151
}
5252

53-
instances[containerId].add_event_listener(eventHandler);
54-
};
53+
// Custom
54+
if (mindMapOptions.multiSelect) {
55+
mm.selectedNodes = [];
56+
57+
const mousedown_handle = function (e) {
58+
e.preventDefault();
59+
60+
const element = e.target || event.srcElement;
61+
const id = this.view.get_binded_nodeid(element);
62+
if (id && element.tagName.toLowerCase() === "jmnode") {
63+
const node = mm.get_node(id);
64+
65+
// If already selected: remove from selected list and remove class
66+
if (mm.selectedNodes.includes(id)) {
67+
node._data.view.element.className = node._data.view.element.className.replace(/\s*selected\b/g, "");
68+
69+
mm.selectedNodes.pop(id);
70+
} else {
71+
node._data.view.element.className += " selected";
72+
73+
mm.selectedNodes.push(id);
74+
}
75+
76+
//instances[containerId].select_clear();
77+
78+
//mm.selectedNodes.forEach(selectedId => {
79+
// const selectedNode = instances[containerId].get_node(selectedId);
80+
// selectedNode._data.view.element.className += " selected";
81+
//});
82+
}
83+
}
84+
85+
mm.view.add_event(mm, "mousedown", mousedown_handle);
86+
}
87+
88+
mm.add_event_listener(eventHandler);
89+
90+
// Keep a reference to the javascript MindMap object
91+
instances[containerId] = mm;
92+
}
5593

5694
MindMap.destroy = function (containerId) {
5795
instances[containerId] = null;
96+
delete instances[containerId];
5897
}
5998

6099
MindMap.addNode = function (containerId, id, parentId, topic, data) {
@@ -89,6 +128,26 @@ MindMap.selectNode = function (containerId, id) {
89128
instances[containerId].select_node(id);
90129
}
91130

131+
MindMap.selectNodes = function (containerId, nodes) {
132+
if (instances[containerId].multiSelect) {
133+
instances[containerId].selectedNodes = [];
134+
135+
//instances[containerId].select_clear();
136+
137+
nodes.forEach(node => {
138+
const foundNode = instances[containerId].get_node(node.id);
139+
foundNode._data.view.element.className += " selected";
140+
//instances[containerId].clear_node_custom_style(foundNode);
141+
142+
instances[containerId].selectedNodes.push(node.id);
143+
});
144+
}
145+
}
146+
147+
MindMap.getNode = function (containerId, id) {
148+
return mapNode(instances[containerId].get_node(id));
149+
}
150+
92151
MindMap.clearSelect = function (containerId) {
93152
instances[containerId].select_clear();
94153
}
@@ -107,4 +166,16 @@ MindMap.enableEdit = function (containerId) {
107166

108167
MindMap.isEditable = function (containerId) {
109168
return instances[containerId].get_editable();
169+
}
170+
171+
mapNode = function (node) {
172+
// Skip children property (TypeError: Converting circular structure to JSON)
173+
return {
174+
id: node.id,
175+
topic: node.topic,
176+
expanded: node.expanded,
177+
direction: node.direction,
178+
data: node.data,
179+
parentId: node.parentId
180+
};
110181
}

src/jsMind.Blazor/Utils/JsonUtils.cs

+136-19
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,136 @@
1-
//using System;
2-
//using System.Collections.Generic;
3-
//using System.Text;
4-
//using System.Text.Json;
5-
6-
//namespace JsMind.Blazor.Utils
7-
//{
8-
// internal static class JsonUtils
9-
// {
10-
// IDictionary<string, string> ConvertToDictionary(JsonElement element)
11-
// {
12-
// var dictionary = new Dictionary<string,string>();
13-
14-
// element.loo
15-
16-
// return dictionary;
17-
// }
18-
// }
19-
//}
1+
/*
2+
* // tslint:disable-next-line: no-reference
3+
///<reference path="../../assets/js/jsmind.d.ts" />
4+
import { Component, OnInit, Inject, ViewChild, ElementRef, Output, EventEmitter } from '@angular/core';
5+
import { WindowRef } from '../windowref';
6+
import { AuthService } from '../auth.service';
7+
import { GraphService } from '../graph.service';
8+
import { jsMind } from '../../assets/js/jsmind';
9+
10+
import mindmap_cloud from '../../assets/data/mindmap-cloud.json';
11+
12+
@Component({
13+
selector: 'app-mindmap',
14+
templateUrl: './mindmap.component.html',
15+
styleUrls: ['./mindmap.component.scss'],
16+
providers: [ WindowRef, AuthService, GraphService ],
17+
})
18+
export class MindmapComponent implements OnInit {
19+
@ViewChild('jsmind_container') container: ElementRef;
20+
21+
private jm: jsMind;
22+
private selectedSkills: string[] = [];
23+
24+
@Output() loaded = new EventEmitter<any>();
25+
26+
constructor(
27+
private winRef: WindowRef) { }
28+
29+
ngOnInit(): void {
30+
this.init_jsMind();
31+
this.load_mind();
32+
this.register_event();
33+
34+
this.loaded.emit();
35+
}
36+
37+
public get SelectedSkills(): string[] {
38+
return this.selectedSkills;
39+
}
40+
41+
private init_jsMind() {
42+
const options = {
43+
editable: true,
44+
container: 'jsmind_container',
45+
theme: 'mstack'
46+
};
47+
48+
const jsMindType: any = this.winRef.nativeWindow.jsMind;
49+
this.jm = new jsMindType(options);
50+
this.jm.init();
51+
}
52+
53+
public load_mind(subset?: string) {
54+
// when the #mindmap-mapname anchor has been set, load that map when no other was specified
55+
if (! subset && window.location.hash) {
56+
const hash = window.location.hash.substr(1);
57+
if (hash.startsWith('mindmap-')) {
58+
subset = hash;
59+
}
60+
}
61+
62+
// defaults back to the "cloud" mindmap when none is set
63+
subset = subset || 'mindmap-cloud';
64+
65+
switch (subset) {
66+
case 'mindmap-cloud': this.jm.show(mindmap_cloud); break;
67+
case 'mindmap-development': this.jm.show(mindmap_development); break;
68+
case 'mindmap-marktgebieden': this.jm.show(mindmap_marktgebieden); break;
69+
case 'mindmap-projectaanpak': this.jm.show(mindmap_projectaanpak); break;
70+
case 'mindmap-softskills': this.jm.show(mindmap_softskills); break;
71+
case 'mindmap-vakgebied': this.jm.show(mindmap_vakgebied); break;
72+
}
73+
}
74+
75+
private register_event() {
76+
this.jm.add_event_listener(async (type, data) => {
77+
if (type === 6) {
78+
await this.toggleSkill(data.node);
79+
this.updateCSS();
80+
}
81+
});
82+
}
83+
84+
public clearSelection() {
85+
this.selectedSkills = [];
86+
this.updateCSS();
87+
}
88+
89+
public addSkill(skill) {
90+
if (!this.selectedSkills.includes(skill)) {
91+
this.selectedSkills.push(skill);
92+
}
93+
this.updateCSS();
94+
}
95+
96+
private removeSkill(skill) {
97+
if (this.selectedSkills.includes(skill)) {
98+
const index = this.selectedSkills.indexOf(skill);
99+
this.selectedSkills.splice(index, 1);
100+
}
101+
this.updateCSS();
102+
}
103+
104+
public toggleSkill(skill) {
105+
if (this.selectedSkills.includes(skill)) {
106+
this.removeSkill(skill);
107+
} else {
108+
this.addSkill(skill);
109+
}
110+
}
111+
112+
private updateCSS() {
113+
const skills = this.selectedSkills;
114+
const skillsSheetElement: any = document.getElementById('skills-sheet');
115+
116+
skillsSheetElement.innerHTML = '';
117+
118+
const skillsSheet = skillsSheetElement.sheet;
119+
120+
while (skillsSheet.cssRules.length > 0) {
121+
skillsSheet.deleteRule(0);
122+
}
123+
124+
for (let i = 0; i < skills.length; i++) {
125+
const skill = skills[i];
126+
127+
let styles0 = 'jmnodes jmnode[nodeid="' + skill + '"], jmnodes jmnode[nodeid="' + skill + '"].selected {';
128+
styles0 += 'background-color: #2fa0d6;';
129+
styles0 += '}';
130+
131+
console.log('adding style ' + i + ' : ' + styles0);
132+
skillsSheet.insertRule(styles0, i);
133+
}
134+
}
135+
}
136+
*/

0 commit comments

Comments
 (0)