Skip to content

Commit

Permalink
add detached nodes (#204)
Browse files Browse the repository at this point in the history
  • Loading branch information
JannikStreek authored Dec 5, 2023
1 parent f48821c commit e6bf4a4
Show file tree
Hide file tree
Showing 23 changed files with 115 additions and 36 deletions.
3 changes: 3 additions & 0 deletions teammapper-backend/src/map/entities/mmpNode.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export class MmpNode {
@Column({ nullable: true })
locked: boolean;

@Column({ default: false })
detached: boolean;

@Column({ nullable: true, type: 'float' })
k: number;

Expand Down
3 changes: 3 additions & 0 deletions teammapper-backend/src/map/services/maps.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export class MapsService {
}

async addNode(mapId: string, clientNode: IMmpClientNode): Promise<MmpNode> {
// detached nodes are not allowed to have a parent
if (clientNode.detached && clientNode.parent) return

const existingNode = await this.nodesRepository.findOne({
where: { id: clientNode.id, nodeMapId: mapId },
});
Expand Down
1 change: 1 addition & 0 deletions teammapper-backend/src/map/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface IMmpClientNodeBasics {

export interface IMmpClientNode extends IMmpClientNodeBasics {
coordinates: IMmpClientCoordinates;
detached: boolean;
id: string;
k: number;
link: { href: string }
Expand Down
5 changes: 4 additions & 1 deletion teammapper-backend/src/map/utils/clientServerMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ const mapMmpNodeToClient = (serverNode: MmpNode): IMmpClientNode => ({
href: serverNode.linkHref || ''
},
id: serverNode.id,
detached: serverNode.detached || false,
image: { src: serverNode.imageSrc || '', size: serverNode.imageSize || 0 },
k: serverNode.k || 1,
locked: serverNode.locked || false,
name: serverNode.name || '',
parent: serverNode.nodeParentId || '',
parent: serverNode.nodeParentId || null,
isRoot: serverNode.root || false,
});

Expand Down Expand Up @@ -59,6 +60,7 @@ const mapClientNodeToMmpNode = (clientNode: IMmpClientNode, mapId: string): Obje
k: clientNode.k,
linkHref: clientNode.link?.href,
locked: clientNode.locked,
detached: clientNode.detached,
name: clientNode.name,
nodeParentId: clientNode.parent ? clientNode.parent : null,
root: clientNode.isRoot,
Expand All @@ -80,6 +82,7 @@ const mapClientBasicNodeToMmpRootNode = (clientRootNodeBasics: IMmpClientNodeBas
name: clientRootNodeBasics.name || DEFAULT_NAME,
nodeParentId: null,
root: true,
detached: false,
nodeMapId: mapId,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class AddDetachedPropertyToNodes1701777634545 implements MigrationInterface {
name = 'AddDetachedPropertyToNodes1701777634545'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "mmp_node" ADD "detached" boolean NOT NULL DEFAULT false`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "mmp_node" DROP COLUMN "detached"`);
}

}
1 change: 1 addition & 0 deletions teammapper-backend/test/app.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe('AppController (e2e)', () => {
{
name: 'test',
coordinates: { x: 1.1, y: 2.2 },
detached: false,
font: { style: '', size: 5, weight: '' },
colors: { branch: '', background: '', name: '' },
image: { size: 60, src: '' },
Expand Down
2 changes: 1 addition & 1 deletion teammapper-frontend/mmp/src/map/handlers/draw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export default class Draw {
* @returns {Path} path
*/
public drawBranch(node: Node): Path {
if(node.parent === undefined) return
if(node.parent === undefined || node.parent === null) return

const parent = node.parent,
path = d3.path(),
Expand Down
4 changes: 2 additions & 2 deletions teammapper-frontend/mmp/src/map/handlers/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export default class History {
const mergedProperty = { ...DefaultNodeValues, ...property } as ExportNodeProperties
const properties: NodeProperties = {
id: mergedProperty.id,
parent: this.map.nodes.getNode(mergedProperty.parent),
parent: mergedProperty.parent ? this.map.nodes.getNode(mergedProperty.parent) : null,
k: mergedProperty.k,
name: mergedProperty.name,
coordinates: Utils.cloneObject(mergedProperty.coordinates) as Coordinates,
Expand Down Expand Up @@ -203,7 +203,7 @@ export default class History {
private checkNodeProperties(node: ExportNodeProperties) {
const conditions: boolean[] = [
typeof node.id === 'string',
typeof node.parent === 'string',
typeof node.parent === 'string' || node.parent === null,
typeof node.k === 'number',
typeof node.name === 'string',
typeof node.locked === 'boolean',
Expand Down
31 changes: 14 additions & 17 deletions teammapper-frontend/mmp/src/map/handlers/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default class Nodes {
locked: false,
id: rootId,
parent: null,
detached: false,
isRoot: true
}) as NodeProperties

Expand Down Expand Up @@ -83,16 +84,9 @@ export default class Nodes {
* @param {string} overwriteId
*/
public addNode = (userProperties?: UserNodeProperties, notifyWithEvent: boolean = true, parentId?: string, overwriteId?: string) => {
if (parentId && typeof parentId !== 'string') {
Log.error('The node id must be a string', 'type')
}

const parentNode: Node = parentId ? this.getNode(parentId) : this.selectedNode

if (parentNode === undefined) {
Log.error('There are no nodes with id "' + parentId + '"')
}

const parentNode: Node = userProperties.detached ? null :
parentId ? this.getNode(parentId) : this.getSelectedNode()

const properties: NodeProperties = Utils.mergeObjects(this.map.options.defaultNode, userProperties, true) as NodeProperties

properties.id = overwriteId || uuidv4()
Expand Down Expand Up @@ -377,6 +371,7 @@ export default class Nodes {
link: Utils.cloneObject(node.link) as Link,
locked: node.locked,
isRoot: node.isRoot,
detached: node.detached,
k: node.k
}
}
Expand Down Expand Up @@ -570,7 +565,7 @@ export default class Nodes {
* @returns {Array<Node>} siblings
*/
private getSiblings(node: Node): Array<Node> {
if (!node.isRoot) {
if (!node.isRoot && !node.detached) {
const parentChildren: Array<Node> = this.getChildren(node.parent)

if (parentChildren.length > 1) {
Expand All @@ -591,12 +586,12 @@ export default class Nodes {
*/
private calculateCoordinates(node: Node): Coordinates {
let coordinates: Coordinates = {
x: node.parent.coordinates.x,
y: node.parent.coordinates.y
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.isRoot) {
if (node.parent && node.parent.isRoot) {
const rightNodes: Array<Node> = [],
leftNodes: Array<Node> = []

Expand All @@ -611,8 +606,8 @@ export default class Nodes {
coordinates.x += 200
siblings = rightNodes
}
} else {
if (this.getOrientation(node.parent)) {
} else if(!node.detached) {
if (node.parent && this.getOrientation(node.parent)) {
coordinates.x -= 200
} else {
coordinates.x += 200
Expand All @@ -622,8 +617,10 @@ export default class Nodes {
if (siblings.length > 0) {
const lowerNode = this.getLowerNode(siblings)
coordinates.y = lowerNode.coordinates.y + 60
} else if(node.detached) {
coordinates.y -= 80
} else {
coordinates.y -= 120
coordinates.y -= 120
}

return coordinates
Expand Down
3 changes: 3 additions & 0 deletions teammapper-frontend/mmp/src/map/models/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default class Node implements NodeProperties {
public locked: boolean
public dom: SVGGElement
public isRoot: boolean
public detached: boolean

/**
* Initialize the node properties, the dimensions and the k coefficient.
Expand All @@ -35,6 +36,7 @@ export default class Node implements NodeProperties {
this.link = properties.link
this.locked = properties.locked
this.isRoot = properties.isRoot
this.detached = properties.detached

this.dimensions = {
width: 0,
Expand Down Expand Up @@ -103,6 +105,7 @@ export interface UserNodeProperties {
font?: Font
locked?: boolean
isRoot?: boolean
detached?: boolean
}

export interface NodeProperties extends UserNodeProperties {
Expand Down
16 changes: 14 additions & 2 deletions teammapper-frontend/mmp/src/map/options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Colors, Font, Image, Link} from './models/node'
import {Colors, Coordinates, Font, Image, Link} from './models/node'
import Utils from '../utils/utils'
import Map from './map'
import * as d3 from 'd3'
Expand Down Expand Up @@ -187,6 +187,10 @@ export const DefaultNodeValues: DefaultNodeProperties = {
link: {
href: ''
},
coordinates: {
x: 0,
y: 0
},
image: {
src: '',
size: 60
Expand All @@ -203,14 +207,19 @@ export const DefaultNodeValues: DefaultNodeProperties = {
decoration: ''
},
locked: true,
isRoot: false
detached: false,
isRoot: false,
}

export const DefaultRootNodeValues: DefaultNodeProperties = {
name: 'Root node',
link: {
href: ''
},
coordinates: {
x: 0,
y: 0
},
image: {
src: '',
size: 70
Expand All @@ -227,16 +236,19 @@ export const DefaultRootNodeValues: DefaultNodeProperties = {
decoration: ''
},
locked: true,
detached: false,
isRoot: true
}

export interface DefaultNodeProperties {
name: string
image: Image
coordinates: Coordinates
link: Link
colors: Colors
font: Font
locked: boolean
detached: boolean
isRoot: boolean
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ export class MapSyncService implements OnDestroy {
if (result.clientId === this.socket.id) return;

if (!this.mmpService.existNode(result?.node?.id)) {
this.mmpService.addNode(result.node, false);
this.mmpService.addNodeFromServer(result.node);
}
});

Expand Down
46 changes: 34 additions & 12 deletions teammapper-frontend/src/app/core/services/mmp/mmp.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,33 @@ export class MmpService implements OnDestroy {
}

/**
* Add a node in the mind mmp.
* Adds an already created node on the server
*
* @param properties Given node properties as synced from the server
*/
public addNode(properties?: ExportNodeProperties, notifyWithEvent = true) {
public addNodeFromServer(properties?: ExportNodeProperties) {
this.currentMap.instance.addNode(
properties,
false,
properties?.parent,
properties?.id
);
}

/**
* Add a node in the mind mmp triggered by the user.
*
* Detached nodes can be used as comments and are not assigned to a parent node
*/
public addNode(properties?: UserNodeProperties, notifyWithEvent = true) {
const newProps: UserNodeProperties = properties || { name: '' };
// when the method is called with no params (from shortcut service), use the current selected node as parent
const parent = properties?.parent
? this.getNode(properties.parent)
: this.selectNode();
const parent = !properties?.detached ? this.selectNode() : null;

// detached nodes are not available as parent
if (parent?.detached) {
return;
}

const settings = this.settingsService.getCachedSettings();

if (properties?.colors?.branch) {
Expand All @@ -194,12 +213,15 @@ export class MmpService implements OnDestroy {
};
}

this.currentMap.instance.addNode(
newProps,
notifyWithEvent,
properties?.parent,
properties?.id
);
if (properties?.detached) {
const currentNode = this.selectNode();
newProps.coordinates = {
x: currentNode.coordinates.x,
y: currentNode.coordinates.y,
};
}

this.currentMap.instance.addNode(newProps, notifyWithEvent);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@
<div></div>
</div>

<button
(click)="addDetachedNode()"
[title]="'TOOLTIPS.ADD_DETACHED_NODE' | translate"
[disabled]="editDisabled"
color="primary"
mat-icon-button>
<mat-icon>add_comment</mat-icon>
</button>
<button
(click)="mmpService.copyNode()"
[title]="'TOOLTIPS.COPY_NODE' | translate"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export class ToolbarComponent {
if (this.isValidLink(linkInput)) this.mmpService.addNodeLink(linkInput);
}

public addDetachedNode() {
this.mmpService.addNode({ detached: true, name: '' });
}

public removeLink() {
this.mmpService.removeNodeLink();
}
Expand Down
1 change: 1 addition & 0 deletions teammapper-frontend/src/assets/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"IMAGE_SIZE": "Bildgröße",
"START_EDIT_NODE": "Knoten Text bearbeiten",
"ADD_NODE": "Knoten hinzufügen",
"ADD_DETACHED_NODE": "Freien Knoten hinzufügen (z.b. für Kommentare)",
"REMOVE_NODE": "Knoten löschen",
"ZOOM_IN_MAP": "Mindmap vergrößern",
"ZOOM_OUT_MAP": "Mindmap verkleinern",
Expand Down
1 change: 1 addition & 0 deletions teammapper-frontend/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"IMAGE_SIZE": "Changes the size of the node image",
"START_EDIT_NODE": "Edit node text",
"ADD_NODE": "Adds a node",
"ADD_DETACHED_NODE": "Add a detached node",
"REMOVE_NODE": "Removes a node",
"ZOOM_IN_MAP": "Zooms in the map",
"ZOOM_OUT_MAP": "Zooms out the map",
Expand Down
1 change: 1 addition & 0 deletions teammapper-frontend/src/assets/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"IMAGE_SIZE": "Cambiar el tamaño de la imagen del nodo",
"START_EDIT_NODE": "Edit node text",
"ADD_NODE": "Agregar un nodo",
"ADD_DETACHED_NODE": "Add a detached node",
"REMOVE_NODE": "Quitar un nodo",
"ZOOM_IN_MAP": "Acercar el mapa",
"ZOOM_OUT_MAP": "Alejar el mapa",
Expand Down
Loading

0 comments on commit e6bf4a4

Please sign in to comment.