Skip to content
Snippets Groups Projects
Commit b557e57e authored by Matthias Konitzny's avatar Matthias Konitzny :fire:
Browse files

Merge branch 'master' into merge-graph-data-structures

# Conflicts:
#	src/editor/js/structures/graph/node.ts
#	src/editor/js/structures/manageddata.ts
parents 9aeda83e 5ba4b438
No related branches found
No related tags found
No related merge requests found
Pipeline #56952 failed
......@@ -8,7 +8,7 @@ div#ks-editor #sidepanel {
/* resize: horizontal; */
overflow: auto;
min-width: 300px;
max-width: 40%;
max-width: 20%;
height: inherit;
margin: 0.3rem;
margin-right: 0.3rem;
......
This diff is collapsed.
......@@ -12,3 +12,7 @@ div#ks-editor #nodedetails #node-name {
font-weight: bold;
font-size: large;
}
div#ks-editor #nodedetails .empty-select-option {
display: none;
}
......@@ -5,7 +5,7 @@ import { NodeType } from "../../../common/nodetype";
import "./nodedetails.css";
type propTypes = {
selectedNode: Node;
selectedNodes: Node[];
allTypes: NodeType[];
onChange: { (): void };
};
......@@ -23,10 +23,54 @@ export class NodeDetails extends React.Component<propTypes> {
}
private handleNodeTypeChange(event: any) {
this.props.selectedNode.setType(event.target.value);
this.props.selectedNodes.forEach(
(n: Node) => n.setType(event.target.value) // TODO: Later implement new save point handling to collect them all into a big one
);
this.props.onChange();
}
private get referenceNode(): Node {
if (
this.props.selectedNodes == undefined ||
this.props.selectedNodes.length <= 0
) {
// Nothing selected
return new Node();
} else if (this.props.selectedNodes.length === 1) {
// Single node handling
return this.props.selectedNodes[0];
} else {
// Multiple nodes selected => Create a kind of merged node
const refNode = new Node();
Object.assign(refNode, this.props.selectedNodes[0]);
refNode.banner = this.getCollectiveValue((n: Node) => n.banner);
refNode.icon = this.getCollectiveValue((n: Node) => n.icon);
refNode.video = this.getCollectiveValue((n: Node) => n.video);
refNode.type = this.getCollectiveValue((n: Node) => n.type);
return refNode;
}
}
/**
* Tries to find a representative value for a specific property over all selected nodes.
* @param propGetter Function that returns the value to test for each node.
* @returns If all nodes have the same value, this value is returned. Otherwise undefined is returned.
*/
getCollectiveValue(propGetter: (n: Node) => any): any {
const sameValue: any = propGetter(this.props.selectedNodes[0]);
const differentValueFound = this.props.selectedNodes.some(
(n: Node) => propGetter(n) !== sameValue
);
if (differentValueFound) {
return undefined;
}
return sameValue;
}
/**
* Generic function for handeling a changing text input and applying the new value to the currently selected node.
* @param event Change event of text input.
......@@ -36,22 +80,18 @@ export class NodeDetails extends React.Component<propTypes> {
const newValue = event.target.value;
// Actual change?
if ((this.props.selectedNode as any)[property] == newValue) {
if ((this.referenceNode as any)[property] == newValue) {
return;
}
(this.props.selectedNode as any)[property] = newValue;
this.props.selectedNodes.forEach((n: any) => (n[property] = newValue));
this.props.onChange();
// Save change, but debounce, so it doesn't trigger too quickly
this.debounce(
(property: string) => {
this.props.selectedNode.graph.storeCurrentData(
"Changed " +
property +
" of node [" +
this.props.selectedNode.toString() +
"]"
this.props.selectedNodes[0].graph.storeCurrentData(
"Changed " + property + " of selected nodes"
);
this.props.onChange();
},
......@@ -82,50 +122,62 @@ export class NodeDetails extends React.Component<propTypes> {
}
render(): ReactNode {
if (this.props.selectedNode === undefined) {
if (
this.props.selectedNodes === undefined ||
this.props.selectedNodes.length <= 0
) {
return <p>No Node selected.</p>;
}
return (
<div id="nodedetails">
<div>
<label htmlFor="node-name" hidden>
Name
</label>
<input
type="text"
id="node-name"
name="node-name"
placeholder="Enter name"
className="bottom-space"
value={this.props.selectedNode.name}
onChange={(event) =>
this.handleTextChange(event, "name")
}
></input>
</div>
<div>
<label htmlFor="node-description">Description</label>
<br />
<textarea
id="node-description"
name="node-description"
className="bottom-space"
value={this.props.selectedNode.description}
onChange={(event) =>
this.handleTextChange(event, "description")
}
></textarea>
</div>
{this.props.selectedNodes.length === 1 ? (
<div>
<label htmlFor="node-name" hidden>
Name
</label>
<input
type="text"
id="node-name"
name="node-name"
placeholder="Enter name"
className="bottom-space"
value={this.referenceNode.name}
onChange={(event) =>
this.handleTextChange(event, "name")
}
></input>
</div>
) : (
<h3>{this.props.selectedNodes.length} nodes selected</h3>
)}
{this.props.selectedNodes.length === 1 ? (
<div>
<label htmlFor="node-description">Description</label>
<br />
<textarea
id="node-description"
name="node-description"
className="bottom-space"
value={this.referenceNode.description ?? ""}
onChange={(event) =>
this.handleTextChange(event, "description")
}
></textarea>
</div>
) : (
""
)}
<div>
<label htmlFor="node-image">Icon Image</label>
<br />
{this.props.selectedNode.icon ? (
{this.referenceNode.icon ? (
<div>
<img
id="node-image-preview"
className="preview-image"
src={this.props.selectedNode.icon}
src={this.referenceNode.icon}
/>
<br />
</div>
......@@ -138,7 +190,7 @@ export class NodeDetails extends React.Component<propTypes> {
name="node-image"
placeholder="Image URL"
className="bottom-space"
value={this.props.selectedNode.icon}
value={this.referenceNode.icon ?? ""}
onChange={(event) =>
this.handleTextChange(event, "icon")
}
......@@ -147,12 +199,12 @@ export class NodeDetails extends React.Component<propTypes> {
<div>
<label htmlFor="node-detail-image">Banner Image</label>
<br />
{this.props.selectedNode.banner ? (
{this.referenceNode.banner ? (
<div>
<img
id="node-image-preview"
className="preview-image"
src={this.props.selectedNode.banner}
src={this.referenceNode.banner}
/>
<br />
</div>
......@@ -165,7 +217,7 @@ export class NodeDetails extends React.Component<propTypes> {
name="node-detail-image"
placeholder="Image URL"
className="bottom-space"
value={this.props.selectedNode.banner}
value={this.referenceNode.banner ?? ""}
onChange={(event) =>
this.handleTextChange(event, "banner")
}
......@@ -178,9 +230,18 @@ export class NodeDetails extends React.Component<propTypes> {
id="node-type"
name="node-type"
className="bottom-space"
value={this.props.selectedNode.type.id}
value={
this.referenceNode.type
? this.referenceNode.type.id
: ""
}
onChange={this.handleNodeTypeChange}
>
<option
className="empty-select-option"
disabled
selected
></option>
{this.props.allTypes.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
......@@ -196,26 +257,30 @@ export class NodeDetails extends React.Component<propTypes> {
placeholder="Video URL"
id="node-video"
name="node-video"
value={this.props.selectedNode.video}
value={this.referenceNode.video ?? ""}
onChange={(event) =>
this.handleTextChange(event, "video")
}
></input>
</div>
<div>
<label htmlFor="node-references">References</label>{" "}
<small>One URL per line</small>
<br />
<textarea
id="node-references"
name="node-references"
className="bottom-space"
value={this.props.selectedNode.references}
onChange={(event) =>
this.handleTextChange(event, "references")
}
></textarea>
</div>
{this.props.selectedNodes.length === 1 ? (
<div>
<label htmlFor="node-references">References</label>{" "}
<small>One URL per line</small>
<br />
<textarea
id="node-references"
name="node-references"
className="bottom-space"
value={this.referenceNode.references}
onChange={(event) =>
this.handleTextChange(event, "references")
}
></textarea>
</div>
) : (
""
)}
</div>
);
}
......
......@@ -8,6 +8,7 @@ type propTypes = {
graph: Graph;
type: NodeType;
onChange: { (): void };
onSelectAll: (type: NodeType) => void;
};
type stateTypes = {
temporaryColor: string;
......@@ -82,7 +83,6 @@ export class NodeTypeEntry extends React.Component<propTypes, stateTypes> {
return;
}
//TODO: Make sure, that this event is not triggered to quickly!
(this.props.type as any)[property] = newValue;
this.props.onChange();
......@@ -139,6 +139,9 @@ export class NodeTypeEntry extends React.Component<propTypes, stateTypes> {
}
onChange={(event) => this.handleColorChange(event)}
/>
<button onClick={() => this.props.onSelectAll(this.props.type)}>
Select nodes
</button>
{this.props.graph && this.props.graph.types.length > 1 ? (
<button onClick={this.deleteType}>Delete</button>
) : (
......
......@@ -8,6 +8,7 @@ import { NodeType } from "../../../common/nodetype";
type propTypes = {
graph: Graph;
onChange: { (): void };
onSelectAll: (type: NodeType) => void;
};
export class NodeTypesEditor extends React.Component<propTypes> {
......@@ -35,6 +36,7 @@ export class NodeTypesEditor extends React.Component<propTypes> {
key={type.id}
type={type}
graph={this.props.graph}
onSelectAll={this.props.onSelectAll}
/>
))}
</ul>
......
div#ks-editor #select-layer {
position: relative;
}
div#ks-editor #box-select {
position: absolute;
z-index: 300000;
border-style: dotted;
border-color: rgba(0, 0, 0, 0);
border-width: 2px;
background-color: rgba(0, 0, 0, 0);
pointer-events: none;
-webkit-transition-property: border-color, background-color;
-moz-transition-property: border-color, background-color;
-o-transition-property: border-color, background-color;
transition-property: border-color, background-color;
-webkit-transition-duration: 200ms;
-moz-transition-duration: 200ms;
-o-transition-duration: 200ms;
transition-duration: 200ms;
}
div#ks-editor #box-select.visible {
border-color: #3e74cc;
background-color: rgba(255, 255, 255, 0.5);
}
import React from "react";
import { ReactNode } from "react";
import { Node } from "../structures/graph/node";
import "./selectlayer.css";
type propTypes = {
children: any;
allNodes: Node[];
isEnable: () => boolean;
screen2GraphCoords: (x: number, y: number) => any;
onBoxSelect: (nodes: Node[]) => void;
};
type layerCoordinates = {
x: number;
y: number;
};
export class SelectLayer extends React.Component<propTypes> {
private layerContainer: any;
private layerBox: any;
private initialSelectPoint: layerCoordinates = undefined;
constructor(props: propTypes) {
super(props);
this.isSelecting = this.isSelecting.bind(this);
this.onBoxSelect = this.onBoxSelect.bind(this);
this.boxSelectOnPointerDown = this.boxSelectOnPointerDown.bind(this);
this.boxSelectOnPointerMove = this.boxSelectOnPointerMove.bind(this);
this.boxSelectOnPointerUp = this.boxSelectOnPointerUp.bind(this);
this.layerContainer = React.createRef();
this.layerBox = React.createRef();
}
componentDidMount(): void {
this.setupBoxSelect();
}
setupBoxSelect() {
// Source: https://github.com/vasturiano/force-graph/issues/151#issuecomment-735850938
this.layerContainer.current.onpointerdown = this.boxSelectOnPointerDown;
this.layerContainer.current.onpointermove = this.boxSelectOnPointerMove;
this.layerContainer.current.onpointerup = this.boxSelectOnPointerUp;
}
private isSelecting(): boolean {
if (!this.initialSelectPoint) {
return false;
}
if (!this.props.isEnable()) {
this.initialSelectPoint = undefined;
this.layerBox.current.className = "";
return false;
}
return true;
}
onBoxSelect(left: number, bottom: number, top: number, right: number) {
// Filter out selected nodes
const hitNodes: Node[] = [];
const tl = this.props.screen2GraphCoords(left, top);
const br = this.props.screen2GraphCoords(right, bottom);
this.props.allNodes.forEach((node: any) => {
if (
tl.x < node.x &&
node.x < br.x &&
br.y > node.y &&
node.y > tl.y
) {
// Add node if in box area
hitNodes.push(node);
}
});
this.props.onBoxSelect(hitNodes);
}
boxSelectOnPointerDown(e: any) {
if (!this.props.isEnable()) {
return;
}
e.preventDefault();
this.layerBox.current.style.left = e.offsetX.toString() + "px";
this.layerBox.current.style.top = e.offsetY.toString() + "px";
this.layerBox.current.style.width = "0px";
this.layerBox.current.style.height = "0px";
this.initialSelectPoint = {
x: e.offsetX,
y: e.offsetY,
};
this.layerBox.current.className = "visible";
}
boxSelectOnPointerMove(e: any) {
if (!this.isSelecting()) {
return;
}
e.preventDefault();
if (e.offsetX < this.initialSelectPoint.x) {
this.layerBox.current.style.left = e.offsetX.toString() + "px";
this.layerBox.current.style.width =
(this.initialSelectPoint.x - e.offsetX).toString() + "px";
} else {
this.layerBox.current.style.left =
this.initialSelectPoint.x.toString() + "px";
this.layerBox.current.style.width =
(e.offsetX - this.initialSelectPoint.x).toString() + "px";
}
if (e.offsetY < this.initialSelectPoint.y) {
this.layerBox.current.style.top = e.offsetY.toString() + "px";
this.layerBox.current.style.height =
(this.initialSelectPoint.y - e.offsetY).toString() + "px";
} else {
this.layerBox.current.style.top =
this.initialSelectPoint.y.toString() + "px";
this.layerBox.current.style.height =
(e.offsetY - this.initialSelectPoint.y).toString() + "px";
}
}
boxSelectOnPointerUp(e: any) {
if (!this.isSelecting()) {
return;
}
e.preventDefault();
let left, bottom, top, right;
if (e.offsetX < this.initialSelectPoint.x) {
left = e.offsetX;
right = this.initialSelectPoint.x;
} else {
left = this.initialSelectPoint.x;
right = e.offsetX;
}
if (e.offsetY < this.initialSelectPoint.y) {
top = e.offsetY;
bottom = this.initialSelectPoint.y;
} else {
top = this.initialSelectPoint.y;
bottom = e.offsetY;
}
this.initialSelectPoint = undefined;
this.layerBox.current.className = "";
this.onBoxSelect(left, bottom, top, right);
}
render(): ReactNode {
return (
<div ref={this.layerContainer} id="select-layer">
<div ref={this.layerBox} id="box-select"></div>
{this.props.children}
</div>
);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment