-
Notifications
You must be signed in to change notification settings - Fork 22
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
Hide/show child nodes of a branch #325
Changes from 1 commit
8f8e65a
bdfcfc5
c867398
12ff66d
7f5f35b
204583d
068e26a
32d1e1e
b8dfccf
fc9fe67
68141bc
84a6c25
ea2dd7d
5d4e097
ea706ff
4f197de
9ccde3b
36b5319
4f2288c
0d010d5
04e5176
bef9113
250a84c
587016f
5beaf21
d0be27c
85454a7
ee483a7
63f58ec
de316ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,6 +58,29 @@ export default class History { | |
} else if (this.checkSnapshotStructure(snapshot)) { | ||
const previousData = this.map.export.asJSON() | ||
|
||
// Find all nodes where we've set hasHiddenChildNodes in the previous map | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe apply refactoring: extract method, as this is a lot of logic only dedicating to the hiding of nodes. |
||
const nodesWithHiddenChildren = previousData.filter(node => node.hasHiddenChildNodes) | ||
|
||
// This method will recursively hide all children of children until none are left | ||
const hideChildNodes = (parentId: string) => snapshot.filter(node => node.parent === parentId).forEach(node => { | ||
node.hidden = true | ||
hideChildNodes(node.id) | ||
}) | ||
|
||
snapshot.forEach(snapshotNode => { | ||
const nodeWithHiddenChildren = nodesWithHiddenChildren.find(x => snapshotNode.id === x.id) | ||
|
||
if (nodeWithHiddenChildren) { | ||
snapshotNode.hasHiddenChildNodes = true | ||
|
||
// We need to iterate through the snapshot instead of using this.map.nodes.nodeChildren() to see if we need to set hidden attributes as the latter will not have new nodes added yet | ||
snapshot.filter(node => node.parent === snapshotNode.id).forEach(node => { | ||
node.hidden = true | ||
hideChildNodes(node.id) | ||
}) | ||
} | ||
}) | ||
|
||
this.redraw(snapshot) | ||
|
||
this.map.zoom.center('position', 0) | ||
|
@@ -137,6 +160,7 @@ export default class History { | |
locked: mergedProperty.locked, | ||
detached: mergedProperty.detached, | ||
hidden: mergedProperty.hidden, | ||
hasHiddenChildNodes: mergedProperty.hasHiddenChildNodes, | ||
isRoot: mergedProperty.isRoot | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ import MmpMap from '../map' | |
import * as d3 from 'd3' | ||
import DOMPurify from 'dompurify' | ||
import { v4 as uuidv4 } from 'uuid' | ||
import {Event} from './events' | ||
import { Event } from './events' | ||
import Log from '../../utils/log' | ||
import Utils from '../../utils/utils' | ||
|
||
|
@@ -85,8 +85,8 @@ export default class Nodes { | |
*/ | ||
public addNode = (userProperties?: UserNodeProperties, notifyWithEvent: boolean = true, updateHistory: boolean = true, parentId?: string, overwriteId?: string): Node => { | ||
const parentNode: Node = userProperties.detached ? null : | ||
parentId ? this.getNode(parentId) : this.getSelectedNode() | ||
parentId ? this.getNode(parentId) : this.getSelectedNode() | ||
|
||
const properties: NodeProperties = Utils.mergeObjects(this.map.options.defaultNode, userProperties, true) as NodeProperties | ||
|
||
properties.id = overwriteId || uuidv4() | ||
|
@@ -110,7 +110,7 @@ export default class Nodes { | |
this.map.history.save() | ||
} | ||
|
||
if(notifyWithEvent) this.map.events.call(Event.nodeCreate, node.dom, this.getNodeProperties(node)) | ||
if (notifyWithEvent) this.map.events.call(Event.nodeCreate, node.dom, this.getNodeProperties(node)) | ||
return node | ||
} | ||
|
||
|
@@ -162,7 +162,7 @@ export default class Nodes { | |
* @param {string} color | ||
* @returns {void} | ||
*/ | ||
public highlightNodeWithColor = (id: string, color: string, notifyWithEvent: boolean = true): void => { | ||
public highlightNodeWithColor = (id: string, color: string, notifyWithEvent: boolean = true): void => { | ||
if (id !== undefined) { | ||
if (typeof id !== 'string') { | ||
Log.error('The node id must be a string', 'type') | ||
|
@@ -175,7 +175,7 @@ export default class Nodes { | |
if (background.style.stroke !== color) { | ||
background.style.stroke = DOMPurify.sanitize(color) | ||
|
||
if(notifyWithEvent) this.map.events.call(Event.nodeUpdate, node.dom, this.getNodeProperties(node)) | ||
if (notifyWithEvent) this.map.events.call(Event.nodeUpdate, node.dom, this.getNodeProperties(node)) | ||
} | ||
} else { | ||
Log.error('The node id is not correct') | ||
|
@@ -216,7 +216,7 @@ export default class Nodes { | |
public toggleBranchVisibility = () => { | ||
if (this.selectedNode) { | ||
const children = this.getChildren(this.selectedNode); | ||
|
||
const descendants = this.getDescendants(this.selectedNode).filter(x => !children.includes(x)); | ||
|
||
/** | ||
|
@@ -235,13 +235,20 @@ export default class Nodes { | |
if (x.parent.hidden && !x.hidden) { | ||
this.updateNode('hidden', true, false, false, false, x.id) | ||
} | ||
|
||
if (!x.parent.hidden && x.hidden) { | ||
this.updateNode('hidden', false, false, false, false, x.id) | ||
} | ||
}) | ||
} | ||
|
||
// Lengthy but definitive check to see if we have any hidden nodes after toggling | ||
if (this.map.nodes.nodeChildren(this.selectedNode.id)?.filter(x => x.hidden).length > 0) { | ||
this.selectedNode.hasHiddenChildNodes = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can't this be set directly, when the button is clicked (hide children)? Do we have to calculate this dynamically? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I see it, this property makes sense. So we have two new fields:
So I would set them both upon button press? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was a computed property before, but there's an issue with the history if we make it computed: Suppose that parent node A has exactly one child node B which is hidden. If you undo/redo the creation of the child node B, there's no way to tell it's supposed to be hidden, as the previous map (client side) doesn't include child node B, only the snapshot we receive from the server does (which doesn't have the client side "hidden" attribute). That's why we have hasHiddenChildNodes, which is set on parent node A. So now we have two attributes:
The former only affects history and rendering of the hidden eye icon, the latter affects whether the node is actually visible or not. Both of them are set when calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point, maybe you can add this reason as a comment? that there is not only the button that triggers it and thats the reason you have to check here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea, I've added some comments. |
||
} else { | ||
this.selectedNode.hasHiddenChildNodes = false | ||
} | ||
|
||
this.map.draw.update() | ||
this.map.history.save() | ||
} | ||
|
@@ -251,7 +258,7 @@ export default class Nodes { | |
* Deselect the current selected node. | ||
*/ | ||
public deselectNode = () => { | ||
if(this.selectedNode?.id === this.getRoot().id) return | ||
if (this.selectedNode?.id === this.getRoot().id) return | ||
|
||
const oldNodeProps: ExportNodeProperties = this.getNodeProperties(this.selectedNode) | ||
const oldDom: SVGGElement = this.selectedNode.dom | ||
|
@@ -338,7 +345,7 @@ export default class Nodes { | |
if (graphic === false && updated !== false && updateHistory) { | ||
this.map.history.save() | ||
} | ||
|
||
if (graphic === false && updated !== false && notifyWithEvent) { | ||
this.map.events.call(Event.nodeUpdate, node.dom, { nodeProperties: this.getNodeProperties(node), changedProperty: property, previousValue }) | ||
} | ||
|
@@ -371,7 +378,7 @@ export default class Nodes { | |
|
||
this.map.history.save() | ||
|
||
if(notifyWithEvent) this.map.events.call(Event.nodeRemove, null, this.getNodeProperties(node)) | ||
if (notifyWithEvent) this.map.events.call(Event.nodeRemove, null, this.getNodeProperties(node)) | ||
|
||
this.deselectNode() | ||
} else { | ||
|
@@ -419,11 +426,12 @@ export default class Nodes { | |
image: Utils.cloneObject(node.image) as Image, | ||
colors: Utils.cloneObject(node.colors) as Colors, | ||
font: Utils.cloneObject(node.font) as Font, | ||
link: Utils.cloneObject(node.link) as Link, | ||
link: Utils.cloneObject(node.link) as Link, | ||
locked: node.locked, | ||
isRoot: node.isRoot, | ||
detached: node.detached, | ||
hidden: node.hidden, | ||
hasHiddenChildNodes: node.hasHiddenChildNodes, | ||
k: node.k | ||
} | ||
} | ||
|
@@ -601,15 +609,15 @@ export default class Nodes { | |
* @param {string} id | ||
* @returns {Node} | ||
*/ | ||
public getNode = (id: string): Node => { | ||
if (id !== undefined) { | ||
if (typeof id !== 'string') { | ||
Log.error('The node id must be a string', 'type') | ||
return | ||
} | ||
return this.nodes.get(id) | ||
public getNode = (id: string): Node => { | ||
if (id !== undefined) { | ||
if (typeof id !== 'string') { | ||
Log.error('The node id must be a string', 'type') | ||
return | ||
} | ||
return this.nodes.get(id) | ||
} | ||
} | ||
|
||
/** | ||
* Return the siblings of a node. | ||
|
@@ -638,9 +646,9 @@ export default class Nodes { | |
*/ | ||
private calculateCoordinates(node: Node): Coordinates { | ||
let coordinates: Coordinates = { | ||
x: node.parent ? node.parent.coordinates.x : node.coordinates.x || 0, | ||
y: node.parent ? node.parent.coordinates.y : node.coordinates.y || 0 | ||
}, | ||
x: node.parent ? node.parent.coordinates.x : node.coordinates.x || 0, | ||
y: node.parent ? node.parent.coordinates.y : node.coordinates.y || 0 | ||
}, | ||
siblings: Array<Node> = this.getSiblings(node) | ||
|
||
if (node.parent && node.parent.isRoot) { | ||
|
@@ -658,7 +666,7 @@ export default class Nodes { | |
coordinates.x += 200 | ||
siblings = rightNodes | ||
} | ||
} else if(!node.detached) { | ||
} else if (!node.detached) { | ||
if (node.parent && this.getOrientation(node.parent)) { | ||
coordinates.x -= 200 | ||
} else { | ||
|
@@ -670,7 +678,7 @@ export default class Nodes { | |
const lowerNode = this.getLowerNode(siblings) | ||
coordinates.y = lowerNode.coordinates.y + 60 | ||
} else if (!node.detached) { | ||
coordinates.y -= 120 | ||
coordinates.y -= 120 | ||
} | ||
|
||
return coordinates | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method does not only check, but actually hides or even removes Dom icons. That should be reflected in the name, as its otherwise confusing