From 2fbc22af0d030558f700fb30b787a392fa68e239 Mon Sep 17 00:00:00 2001
From: Andrew Lalis
Date: Thu, 5 May 2022 22:21:43 +0200
Subject: [PATCH 1/7] Added new model.
---
pom.xml | 2 +-
.../railsignalapi/dao/BranchRepository.java | 18 ------
.../dao/ComponentRepository.java | 11 ++++
.../railsignalapi/dao/SegmentRepository.java | 11 ++++
.../dao/SignalBranchConnectionRepository.java | 9 ---
.../railsignalapi/dao/SignalRepository.java | 26 --------
.../railsignalapi/dao/TrainRepository.java | 9 ---
.../andrewl/railsignalapi/model/Branch.java | 38 ------------
.../railsignalapi/model/BranchStatus.java | 6 --
.../railsignalapi/model/Direction.java | 3 +
.../railsignalapi/model/RailSystem.java | 6 ++
.../andrewl/railsignalapi/model/Segment.java | 50 +++++++++++++++
.../andrewl/railsignalapi/model/Signal.java | 40 ------------
.../model/SignalBranchConnection.java | 57 -----------------
.../andrewl/railsignalapi/model/Switch.java | 30 ---------
.../nl/andrewl/railsignalapi/model/Train.java | 20 ------
.../model/component/Component.java | 62 +++++++++++++++++++
.../model/component/PathNode.java | 33 ++++++++++
.../model/{ => component}/Position.java | 5 +-
.../model/component/SegmentBoundaryNode.java | 32 ++++++++++
.../railsignalapi/model/component/Signal.java | 43 +++++++++++++
.../railsignalapi/model/component/Switch.java | 29 +++++++++
.../model/component/SwitchConfiguration.java | 31 ++++++++++
.../rest/BranchesController.java | 38 ------------
.../rest/RailSystemsApiController.java | 2 +-
.../rest/SignalsApiController.java | 44 -------------
.../rest/dto/BranchResponse.java | 13 ----
.../dto/SignalConnectionsUpdatePayload.java | 9 ---
.../rest/dto/SignalCreationPayload.java | 13 ----
.../rest/dto/SignalResponse.java | 61 ------------------
.../service/RailSystemService.java | 14 ++---
.../railsignalapi/service/SignalService.java | 1 +
32 files changed, 324 insertions(+), 442 deletions(-)
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/dao/BranchRepository.java
create mode 100644 src/main/java/nl/andrewl/railsignalapi/dao/ComponentRepository.java
create mode 100644 src/main/java/nl/andrewl/railsignalapi/dao/SegmentRepository.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/dao/SignalBranchConnectionRepository.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/dao/SignalRepository.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/dao/TrainRepository.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/model/Branch.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/model/BranchStatus.java
create mode 100644 src/main/java/nl/andrewl/railsignalapi/model/Segment.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/model/Signal.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/model/SignalBranchConnection.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/model/Switch.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/model/Train.java
create mode 100644 src/main/java/nl/andrewl/railsignalapi/model/component/Component.java
create mode 100644 src/main/java/nl/andrewl/railsignalapi/model/component/PathNode.java
rename src/main/java/nl/andrewl/railsignalapi/model/{ => component}/Position.java (68%)
create mode 100644 src/main/java/nl/andrewl/railsignalapi/model/component/SegmentBoundaryNode.java
create mode 100644 src/main/java/nl/andrewl/railsignalapi/model/component/Signal.java
create mode 100644 src/main/java/nl/andrewl/railsignalapi/model/component/Switch.java
create mode 100644 src/main/java/nl/andrewl/railsignalapi/model/component/SwitchConfiguration.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/rest/BranchesController.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/rest/SignalsApiController.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/rest/dto/BranchResponse.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/rest/dto/SignalConnectionsUpdatePayload.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/rest/dto/SignalCreationPayload.java
delete mode 100644 src/main/java/nl/andrewl/railsignalapi/rest/dto/SignalResponse.java
diff --git a/pom.xml b/pom.xml
index 9ee5fa8..7c63610 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.bootspring-boot-starter-parent
- 2.6.0
+ 2.6.6nl.andrewl
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/BranchRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/BranchRepository.java
deleted file mode 100644
index ef475fc..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/dao/BranchRepository.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package nl.andrewl.railsignalapi.dao;
-
-import nl.andrewl.railsignalapi.model.Branch;
-import nl.andrewl.railsignalapi.model.RailSystem;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
-
-import java.util.List;
-import java.util.Optional;
-
-@Repository
-public interface BranchRepository extends JpaRepository {
- Optional findByIdAndRailSystem(long id, RailSystem railSystem);
- Optional findByIdAndRailSystemId(long id, long railSystemId);
- Optional findByNameAndRailSystem(String name, RailSystem railSystem);
- List findAllByRailSystemOrderByName(RailSystem railSystem);
- List findAllByNameAndRailSystem(String name, RailSystem railSystem);
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/ComponentRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/ComponentRepository.java
new file mode 100644
index 0000000..3e5e0c9
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/dao/ComponentRepository.java
@@ -0,0 +1,11 @@
+package nl.andrewl.railsignalapi.dao;
+
+import nl.andrewl.railsignalapi.model.RailSystem;
+import nl.andrewl.railsignalapi.model.component.Component;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ComponentRepository extends JpaRepository {
+ void deleteAllByRailSystem(RailSystem rs);
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/SegmentRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/SegmentRepository.java
new file mode 100644
index 0000000..3cde24a
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/dao/SegmentRepository.java
@@ -0,0 +1,11 @@
+package nl.andrewl.railsignalapi.dao;
+
+import nl.andrewl.railsignalapi.model.RailSystem;
+import nl.andrewl.railsignalapi.model.Segment;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SegmentRepository extends JpaRepository {
+ void deleteAllByRailSystem(RailSystem rs);
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/SignalBranchConnectionRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/SignalBranchConnectionRepository.java
deleted file mode 100644
index 2e1efa7..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/dao/SignalBranchConnectionRepository.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package nl.andrewl.railsignalapi.dao;
-
-import nl.andrewl.railsignalapi.model.SignalBranchConnection;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
-
-@Repository
-public interface SignalBranchConnectionRepository extends JpaRepository {
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/SignalRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/SignalRepository.java
deleted file mode 100644
index 414cb9e..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/dao/SignalRepository.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package nl.andrewl.railsignalapi.dao;
-
-import nl.andrewl.railsignalapi.model.Branch;
-import nl.andrewl.railsignalapi.model.RailSystem;
-import nl.andrewl.railsignalapi.model.Signal;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.stereotype.Repository;
-
-import java.util.List;
-import java.util.Optional;
-
-@Repository
-public interface SignalRepository extends JpaRepository {
- Optional findByIdAndRailSystem(long id, RailSystem railSystem);
- Optional findByIdAndRailSystemId(long id, long railSystemId);
- boolean existsByNameAndRailSystem(String name, RailSystem railSystem);
-
- @Query("SELECT DISTINCT s FROM Signal s " +
- "LEFT JOIN s.branchConnections bc " +
- "WHERE bc.branch = :branch " +
- "ORDER BY s.name")
- List findAllConnectedToBranch(Branch branch);
-
- List findAllByRailSystemOrderByName(RailSystem railSystem);
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/TrainRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/TrainRepository.java
deleted file mode 100644
index 621136f..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/dao/TrainRepository.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package nl.andrewl.railsignalapi.dao;
-
-import nl.andrewl.railsignalapi.model.Train;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
-
-@Repository
-public interface TrainRepository extends JpaRepository {
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/Branch.java b/src/main/java/nl/andrewl/railsignalapi/model/Branch.java
deleted file mode 100644
index 68af2c3..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/model/Branch.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package nl.andrewl.railsignalapi.model;
-
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-import javax.persistence.*;
-import java.util.HashSet;
-import java.util.Set;
-
-@Entity
-@NoArgsConstructor
-@Getter
-public class Branch {
- @Id
- @GeneratedValue
- private Long id;
-
- @ManyToOne(optional = false, fetch = FetchType.LAZY)
- private RailSystem railSystem;
-
- @Column(nullable = false)
- private String name;
-
- @Enumerated(EnumType.STRING)
- @Setter
- private BranchStatus status;
-
- @OneToMany(mappedBy = "branch", orphanRemoval = true)
- private Set signalConnections;
-
- public Branch(RailSystem railSystem, String name, BranchStatus status) {
- this.railSystem = railSystem;
- this.name = name;
- this.status = status;
- this.signalConnections = new HashSet<>();
- }
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/BranchStatus.java b/src/main/java/nl/andrewl/railsignalapi/model/BranchStatus.java
deleted file mode 100644
index cbca38b..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/model/BranchStatus.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package nl.andrewl.railsignalapi.model;
-
-public enum BranchStatus {
- FREE,
- OCCUPIED
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/Direction.java b/src/main/java/nl/andrewl/railsignalapi/model/Direction.java
index 8a12331..f73363b 100644
--- a/src/main/java/nl/andrewl/railsignalapi/model/Direction.java
+++ b/src/main/java/nl/andrewl/railsignalapi/model/Direction.java
@@ -1,5 +1,8 @@
package nl.andrewl.railsignalapi.model;
+/**
+ * A cardinal direction, useful for some components.
+ */
public enum Direction {
NORTH,
SOUTH,
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/RailSystem.java b/src/main/java/nl/andrewl/railsignalapi/model/RailSystem.java
index 6401810..92afd4d 100644
--- a/src/main/java/nl/andrewl/railsignalapi/model/RailSystem.java
+++ b/src/main/java/nl/andrewl/railsignalapi/model/RailSystem.java
@@ -8,6 +8,9 @@ import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
+/**
+ * Represents a closed system that contains a collection of components.
+ */
@Entity
@Getter
@NoArgsConstructor
@@ -16,6 +19,9 @@ public class RailSystem {
@GeneratedValue
private Long id;
+ /**
+ * The name of this system.
+ */
@Column(nullable = false, unique = true)
private String name;
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/Segment.java b/src/main/java/nl/andrewl/railsignalapi/model/Segment.java
new file mode 100644
index 0000000..efe0b8f
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/model/Segment.java
@@ -0,0 +1,50 @@
+package nl.andrewl.railsignalapi.model;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import nl.andrewl.railsignalapi.model.component.SegmentBoundaryNode;
+import nl.andrewl.railsignalapi.model.component.Signal;
+
+import javax.persistence.*;
+import java.util.Set;
+
+/**
+ * Represents a traversable segment of a rail system that components can
+ * connect to.
+ */
+@Entity
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
+public class Segment {
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ @ManyToOne(optional = false, fetch = FetchType.LAZY)
+ private RailSystem railSystem;
+
+ /**
+ * A unique name for this segment.
+ */
+ @Column(unique = true)
+ private String name;
+
+ /**
+ * The signals that are connected to this branch.
+ */
+ @OneToMany(mappedBy = "segment")
+ private Set signals;
+
+ /**
+ * The set of nodes from which trains can enter and exit this segment.
+ */
+ @ManyToMany(mappedBy = "segments", cascade = CascadeType.ALL)
+ private Set boundaryNodes;
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ return o instanceof Segment s && this.id != null && this.id.equals(s.id);
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/Signal.java b/src/main/java/nl/andrewl/railsignalapi/model/Signal.java
deleted file mode 100644
index 9847f9a..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/model/Signal.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package nl.andrewl.railsignalapi.model;
-
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-
-import javax.persistence.*;
-import java.util.Set;
-
-@Entity
-@Getter
-@NoArgsConstructor
-public class Signal {
- @Id
- @GeneratedValue
- private Long id;
-
- @ManyToOne(optional = false, fetch = FetchType.LAZY)
- private RailSystem railSystem;
-
- @Column(nullable = false)
- private String name;
-
- @OneToMany(mappedBy = "signal", orphanRemoval = true, cascade = CascadeType.ALL)
- private Set branchConnections;
-
- @Embedded
- private Position position;
-
- @Column(nullable = false)
- @Setter
- private boolean online = false;
-
- public Signal(RailSystem railSystem, String name, Position position, Set branchConnections) {
- this.railSystem = railSystem;
- this.name = name;
- this.position = position;
- this.branchConnections = branchConnections;
- }
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/SignalBranchConnection.java b/src/main/java/nl/andrewl/railsignalapi/model/SignalBranchConnection.java
deleted file mode 100644
index e4eabc6..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/model/SignalBranchConnection.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package nl.andrewl.railsignalapi.model;
-
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-
-import javax.persistence.*;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
-@Entity
-@NoArgsConstructor
-@Getter
-public class SignalBranchConnection implements Comparable {
- @Id
- @GeneratedValue
- private Long id;
-
- @ManyToOne(optional = false)
- private Signal signal;
-
- @ManyToOne(optional = false, cascade = CascadeType.PERSIST)
- private Branch branch;
-
- @Enumerated(EnumType.STRING)
- private Direction direction;
-
- @ManyToMany
- private Set reachableSignalConnections;
-
- public SignalBranchConnection(Signal signal, Branch branch, Direction direction) {
- this.signal = signal;
- this.branch = branch;
- this.direction = direction;
- reachableSignalConnections = new HashSet<>();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- return this.id != null &&
- o instanceof SignalBranchConnection sbc && sbc.getId() != null &&
- this.id.equals(sbc.getId());
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(id);
- }
-
- @Override
- public int compareTo(SignalBranchConnection o) {
- int c = Long.compare(this.getSignal().getId(), o.getSignal().getId());
- if (c != 0) return c;
- return this.direction.compareTo(o.getDirection());
- }
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/Switch.java b/src/main/java/nl/andrewl/railsignalapi/model/Switch.java
deleted file mode 100644
index 720e85e..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/model/Switch.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package nl.andrewl.railsignalapi.model;
-
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-
-import javax.persistence.*;
-
-@Entity
-@Getter
-@NoArgsConstructor
-public class Switch {
- @Id
- @GeneratedValue
- private Long id;
-
- @ManyToOne(optional = false, fetch = FetchType.LAZY)
- private RailSystem railSystem;
-
- @Column(nullable = false)
- private String name;
-
- @Embedded
- private Position position;
-
- @Column(nullable = false)
- private int state;
-
- @Column(nullable = false)
- private int maxStates;
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/Train.java b/src/main/java/nl/andrewl/railsignalapi/model/Train.java
deleted file mode 100644
index 09a05b8..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/model/Train.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package nl.andrewl.railsignalapi.model;
-
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-
-import javax.persistence.*;
-
-@Entity
-@NoArgsConstructor
-@Getter
-public class Train {
- @Id
- @GeneratedValue
- private Long id;
-
- private String name;
-
- @ManyToOne(fetch = FetchType.LAZY)
- private Branch currentBranch;
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/Component.java b/src/main/java/nl/andrewl/railsignalapi/model/component/Component.java
new file mode 100644
index 0000000..a0ff76c
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/Component.java
@@ -0,0 +1,62 @@
+package nl.andrewl.railsignalapi.model.component;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.andrewl.railsignalapi.model.RailSystem;
+
+import javax.persistence.*;
+
+/**
+ * Represents a physical component of the rail system that the API can interact
+ * with, and send or receive data from. For example, a signal, switch, or
+ * detector.
+ */
+@Entity
+@Inheritance(strategy = InheritanceType.JOINED)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
+public abstract class Component {
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ /**
+ * The rail system that this component belongs to.
+ */
+ @ManyToOne(optional = false, fetch = FetchType.LAZY)
+ private RailSystem railSystem;
+
+ /**
+ * The position of this component in the system.
+ */
+ @Embedded
+ private Position position;
+
+ /**
+ * A human-readable name for the component.
+ */
+ @Column(unique = true)
+ private String name;
+
+ /**
+ * Whether this component is online, meaning that an in-world device is
+ * currently connected to relay information regarding this component.
+ */
+ @Column(nullable = false)
+ @Setter
+ private boolean online = false;
+
+ public Component(RailSystem railSystem, Position position, String name) {
+ this.railSystem = railSystem;
+ this.position = position;
+ this.name = name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ return o instanceof Component c && this.id != null && this.id.equals(c.id);
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/PathNode.java b/src/main/java/nl/andrewl/railsignalapi/model/component/PathNode.java
new file mode 100644
index 0000000..6d82422
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/PathNode.java
@@ -0,0 +1,33 @@
+package nl.andrewl.railsignalapi.model.component;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import nl.andrewl.railsignalapi.model.RailSystem;
+
+import javax.persistence.Entity;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.ManyToMany;
+import java.util.Set;
+
+/**
+ * A node that, together with other nodes, forms a path that trains can follow
+ * to traverse through segments in the rail system.
+ */
+@Entity
+@Inheritance(strategy = InheritanceType.JOINED)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
+public class PathNode extends Component {
+ /**
+ * The set of nodes that this one is connected to.
+ */
+ @ManyToMany
+ private Set connectedNodes;
+
+ public PathNode(RailSystem railSystem, Position position, String name, Set connectedNodes) {
+ super(railSystem, position, name);
+ this.connectedNodes = connectedNodes;
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/Position.java b/src/main/java/nl/andrewl/railsignalapi/model/component/Position.java
similarity index 68%
rename from src/main/java/nl/andrewl/railsignalapi/model/Position.java
rename to src/main/java/nl/andrewl/railsignalapi/model/component/Position.java
index 9060feb..f7bb43f 100644
--- a/src/main/java/nl/andrewl/railsignalapi/model/Position.java
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/Position.java
@@ -1,4 +1,4 @@
-package nl.andrewl.railsignalapi.model;
+package nl.andrewl.railsignalapi.model.component;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -6,6 +6,9 @@ import lombok.NoArgsConstructor;
import javax.persistence.Embeddable;
+/**
+ * A three-dimensional position for a component within a system.
+ */
@Embeddable
@Data
@AllArgsConstructor
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/SegmentBoundaryNode.java b/src/main/java/nl/andrewl/railsignalapi/model/component/SegmentBoundaryNode.java
new file mode 100644
index 0000000..0b9923d
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/SegmentBoundaryNode.java
@@ -0,0 +1,32 @@
+package nl.andrewl.railsignalapi.model.component;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import nl.andrewl.railsignalapi.model.RailSystem;
+import nl.andrewl.railsignalapi.model.Segment;
+
+import javax.persistence.Entity;
+import javax.persistence.ManyToMany;
+import java.util.Set;
+
+/**
+ * Component that relays information about trains traversing from one segment
+ * to another. It links exactly two segments together at a specific point.
+ */
+@Entity
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
+public class SegmentBoundaryNode extends PathNode {
+ /**
+ * The set of segments that this boundary node connects. This should
+ * generally always have exactly two segments.
+ */
+ @ManyToMany
+ private Set segments;
+
+ public SegmentBoundaryNode(RailSystem railSystem, Position position, String name, Set connectedNodes, Set segments) {
+ super(railSystem, position, name, connectedNodes);
+ this.segments = segments;
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/Signal.java b/src/main/java/nl/andrewl/railsignalapi/model/component/Signal.java
new file mode 100644
index 0000000..591542d
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/Signal.java
@@ -0,0 +1,43 @@
+package nl.andrewl.railsignalapi.model.component;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import nl.andrewl.railsignalapi.model.Direction;
+import nl.andrewl.railsignalapi.model.RailSystem;
+import nl.andrewl.railsignalapi.model.Segment;
+
+import javax.persistence.*;
+
+/**
+ * A signal is a component that relays the status of a connected segment to
+ * some sort of in-world representation, whether that be a certain light
+ * color, or electrical signal.
+ */
+@Entity
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Signal extends Component {
+ /**
+ * The direction this signal is facing. This is the direction in which the
+ * signal connects to a branch.
+ *
+ * |-segment A-|-segment B-|
+ * ===================== <- Rail
+ * ]+ ---> Signal is facing East, and shows status on
+ * its western side. It is connected to segment B.
+ *
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/AddSegment.vue b/railsignal-app/src/components/railsystem/AddSegment.vue
new file mode 100644
index 0000000..9e4f637
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/AddSegment.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/Component.vue b/railsignal-app/src/components/railsystem/Component.vue
new file mode 100644
index 0000000..0a4f80e
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/Component.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/MapView.vue b/railsignal-app/src/components/railsystem/MapView.vue
new file mode 100644
index 0000000..c0c1d0f
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/MapView.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/main.js b/railsignal-app/src/main.js
new file mode 100644
index 0000000..f00dcd1
--- /dev/null
+++ b/railsignal-app/src/main.js
@@ -0,0 +1,9 @@
+import { createApp } from 'vue';
+import { createPinia } from 'pinia';
+import App from './App.vue'
+
+const app = createApp(App)
+const pinia = createPinia();
+app.use(pinia);
+
+app.mount('#app')
diff --git a/railsignal-app/src/stores/railSystemsStore.js b/railsignal-app/src/stores/railSystemsStore.js
new file mode 100644
index 0000000..d9747b1
--- /dev/null
+++ b/railsignal-app/src/stores/railSystemsStore.js
@@ -0,0 +1,48 @@
+import { defineStore } from "pinia";
+import axios from "axios";
+
+export const useRailSystemsStore = defineStore('RailSystemsStore', {
+ state: () => ({
+ railSystems: [],
+ selectedRailSystem: null,
+ selectedComponent: null
+ }),
+ actions: {
+ refreshRailSystems() {
+ return new Promise((resolve, reject) => {
+ axios.get(import.meta.env.VITE_API_URL + "/rs")
+ .then(response => {
+ this.railSystems = response.data;
+ resolve();
+ })
+ .catch(error => {
+ reject(error);
+ });
+ });
+ },
+ createRailSystem(name) {
+ return new Promise((resolve, reject) => {
+ axios.post(
+ import.meta.env.VITE_API_URL + "/rs",
+ {name: name}
+ )
+ .then(() => {
+ this.refreshRailSystems()
+ .then(() => resolve())
+ .catch(error => reject(error));
+ })
+ .catch(error => reject(error));
+ });
+ },
+ removeRailSystem(rs) {
+ return new Promise((resolve, reject) => {
+ axios.delete(import.meta.env.VITE_API_URL + "/rs/" + rs.id)
+ .then(() => {
+ this.refreshRailSystems()
+ .then(() => resolve)
+ .catch(error => reject(error));
+ })
+ })
+ }
+ }
+});
\ No newline at end of file
diff --git a/railsignal-app/vite.config.js b/railsignal-app/vite.config.js
new file mode 100644
index 0000000..116273f
--- /dev/null
+++ b/railsignal-app/vite.config.js
@@ -0,0 +1,14 @@
+import { fileURLToPath, URL } from 'url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [vue()],
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./src', import.meta.url))
+ }
+ }
+})
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/ComponentRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/ComponentRepository.java
index 3e5e0c9..862c122 100644
--- a/src/main/java/nl/andrewl/railsignalapi/dao/ComponentRepository.java
+++ b/src/main/java/nl/andrewl/railsignalapi/dao/ComponentRepository.java
@@ -2,10 +2,27 @@ package nl.andrewl.railsignalapi.dao;
import nl.andrewl.railsignalapi.model.RailSystem;
import nl.andrewl.railsignalapi.model.component.Component;
+import nl.andrewl.railsignalapi.model.component.Position;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
+import java.util.List;
+import java.util.Optional;
+
@Repository
-public interface ComponentRepository extends JpaRepository {
+public interface ComponentRepository extends JpaRepository {
+ Optional findByIdAndRailSystemId(long id, long rsId);
+
+ boolean existsByNameAndRailSystem(String name, RailSystem rs);
+
+ @Query("SELECT c FROM Component c " +
+ "WHERE c.railSystem = :rs AND " +
+ "c.position.x >= :#{#lower.x} AND c.position.y >= :#{#lower.y} AND c.position.z >= :#{#lower.z} AND " +
+ "c.position.x <= :#{#upper.x} AND c.position.y <= :#{#upper.y} AND c.position.z <= :#{#upper.z}")
+ List findAllInBounds(RailSystem rs, Position lower, Position upper);
+
+ List findAllByRailSystem(RailSystem rs);
+
void deleteAllByRailSystem(RailSystem rs);
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/SegmentRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/SegmentRepository.java
index 3cde24a..ebba876 100644
--- a/src/main/java/nl/andrewl/railsignalapi/dao/SegmentRepository.java
+++ b/src/main/java/nl/andrewl/railsignalapi/dao/SegmentRepository.java
@@ -5,7 +5,16 @@ import nl.andrewl.railsignalapi.model.Segment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
+import java.util.List;
+import java.util.Optional;
+
@Repository
public interface SegmentRepository extends JpaRepository {
+ boolean existsByNameAndRailSystem(String name, RailSystem rs);
+
+ List findAllByRailSystemId(long rsId);
+
+ Optional findByIdAndRailSystemId(long id, long rsId);
+
void deleteAllByRailSystem(RailSystem rs);
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/SignalRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/SignalRepository.java
new file mode 100644
index 0000000..0bea913
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/dao/SignalRepository.java
@@ -0,0 +1,8 @@
+package nl.andrewl.railsignalapi.dao;
+
+import nl.andrewl.railsignalapi.model.component.Signal;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SignalRepository extends ComponentRepository {
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/SwitchConfigurationRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/SwitchConfigurationRepository.java
new file mode 100644
index 0000000..5c5e52f
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/dao/SwitchConfigurationRepository.java
@@ -0,0 +1,11 @@
+package nl.andrewl.railsignalapi.dao;
+
+import nl.andrewl.railsignalapi.model.component.PathNode;
+import nl.andrewl.railsignalapi.model.component.SwitchConfiguration;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SwitchConfigurationRepository extends JpaRepository {
+ void deleteAllByNodesContaining(PathNode p);
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/dao/SwitchRepository.java b/src/main/java/nl/andrewl/railsignalapi/dao/SwitchRepository.java
new file mode 100644
index 0000000..2738d8d
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/dao/SwitchRepository.java
@@ -0,0 +1,8 @@
+package nl.andrewl.railsignalapi.dao;
+
+import nl.andrewl.railsignalapi.model.component.Switch;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface SwitchRepository extends ComponentRepository {
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/Segment.java b/src/main/java/nl/andrewl/railsignalapi/model/Segment.java
index efe0b8f..f05b9cf 100644
--- a/src/main/java/nl/andrewl/railsignalapi/model/Segment.java
+++ b/src/main/java/nl/andrewl/railsignalapi/model/Segment.java
@@ -7,6 +7,7 @@ import nl.andrewl.railsignalapi.model.component.SegmentBoundaryNode;
import nl.andrewl.railsignalapi.model.component.Signal;
import javax.persistence.*;
+import java.util.HashSet;
import java.util.Set;
/**
@@ -27,7 +28,7 @@ public class Segment {
/**
* A unique name for this segment.
*/
- @Column(unique = true)
+ @Column
private String name;
/**
@@ -39,9 +40,16 @@ public class Segment {
/**
* The set of nodes from which trains can enter and exit this segment.
*/
- @ManyToMany(mappedBy = "segments", cascade = CascadeType.ALL)
+ @ManyToMany(mappedBy = "segments")
private Set boundaryNodes;
+ public Segment(RailSystem railSystem, String name) {
+ this.railSystem = railSystem;
+ this.name = name;
+ this.signals = new HashSet<>();
+ this.boundaryNodes = new HashSet<>();
+ }
+
@Override
public boolean equals(Object o) {
if (o == this) return true;
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/Component.java b/src/main/java/nl/andrewl/railsignalapi/model/component/Component.java
index a0ff76c..3bbe652 100644
--- a/src/main/java/nl/andrewl/railsignalapi/model/component/Component.java
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/Component.java
@@ -37,9 +37,15 @@ public abstract class Component {
/**
* A human-readable name for the component.
*/
- @Column(unique = true)
+ @Column
private String name;
+ /**
+ * The type of this component.
+ */
+ @Enumerated(EnumType.ORDINAL)
+ private ComponentType type;
+
/**
* Whether this component is online, meaning that an in-world device is
* currently connected to relay information regarding this component.
@@ -48,10 +54,11 @@ public abstract class Component {
@Setter
private boolean online = false;
- public Component(RailSystem railSystem, Position position, String name) {
+ public Component(RailSystem railSystem, Position position, String name, ComponentType type) {
this.railSystem = railSystem;
this.position = position;
this.name = name;
+ this.type = type;
}
@Override
@@ -59,4 +66,13 @@ public abstract class Component {
if (this == o) return true;
return o instanceof Component c && this.id != null && this.id.equals(c.id);
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(id);
+ if (name != null) sb.append('[').append(name).append(']');
+ sb.append(String.format("@[x=%.1f,y=%.1f,z=%.1f]", position.getX(), position.getY(), position.getZ()));
+ return sb.toString();
+ }
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/ComponentType.java b/src/main/java/nl/andrewl/railsignalapi/model/component/ComponentType.java
new file mode 100644
index 0000000..af9baeb
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/ComponentType.java
@@ -0,0 +1,7 @@
+package nl.andrewl.railsignalapi.model.component;
+
+public enum ComponentType {
+ SIGNAL,
+ SWITCH,
+ SEGMENT_BOUNDARY
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/PathNode.java b/src/main/java/nl/andrewl/railsignalapi/model/component/PathNode.java
index 6d82422..975e9bf 100644
--- a/src/main/java/nl/andrewl/railsignalapi/model/component/PathNode.java
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/PathNode.java
@@ -19,15 +19,15 @@ import java.util.Set;
@Inheritance(strategy = InheritanceType.JOINED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
-public class PathNode extends Component {
+public abstract class PathNode extends Component {
/**
* The set of nodes that this one is connected to.
*/
@ManyToMany
private Set connectedNodes;
- public PathNode(RailSystem railSystem, Position position, String name, Set connectedNodes) {
- super(railSystem, position, name);
+ public PathNode(RailSystem railSystem, Position position, String name, ComponentType type, Set connectedNodes) {
+ super(railSystem, position, name, type);
this.connectedNodes = connectedNodes;
}
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/SegmentBoundaryNode.java b/src/main/java/nl/andrewl/railsignalapi/model/component/SegmentBoundaryNode.java
index 0b9923d..969e727 100644
--- a/src/main/java/nl/andrewl/railsignalapi/model/component/SegmentBoundaryNode.java
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/SegmentBoundaryNode.java
@@ -26,7 +26,7 @@ public class SegmentBoundaryNode extends PathNode {
private Set segments;
public SegmentBoundaryNode(RailSystem railSystem, Position position, String name, Set connectedNodes, Set segments) {
- super(railSystem, position, name, connectedNodes);
+ super(railSystem, position, name, ComponentType.SEGMENT_BOUNDARY, connectedNodes);
this.segments = segments;
}
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/Signal.java b/src/main/java/nl/andrewl/railsignalapi/model/component/Signal.java
index 591542d..2e1446f 100644
--- a/src/main/java/nl/andrewl/railsignalapi/model/component/Signal.java
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/Signal.java
@@ -1,12 +1,14 @@
package nl.andrewl.railsignalapi.model.component;
import lombok.AccessLevel;
+import lombok.Getter;
import lombok.NoArgsConstructor;
-import nl.andrewl.railsignalapi.model.Direction;
import nl.andrewl.railsignalapi.model.RailSystem;
import nl.andrewl.railsignalapi.model.Segment;
-import javax.persistence.*;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.ManyToOne;
/**
* A signal is a component that relays the status of a connected segment to
@@ -15,29 +17,16 @@ import javax.persistence.*;
*/
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
public class Signal extends Component {
- /**
- * The direction this signal is facing. This is the direction in which the
- * signal connects to a branch.
- *
- * |-segment A-|-segment B-|
- * ===================== <- Rail
- * ]+ ---> Signal is facing East, and shows status on
- * its western side. It is connected to segment B.
- *
- */
- @Enumerated(EnumType.STRING)
- private Direction direction;
-
/**
* The segment that this signal connects to.
*/
@ManyToOne(optional = false, fetch = FetchType.LAZY)
private Segment segment;
- public Signal(RailSystem railSystem, Position position, String name, Segment segment, Direction direction) {
- super(railSystem, position, name);
+ public Signal(RailSystem railSystem, Position position, String name, Segment segment) {
+ super(railSystem, position, name, ComponentType.SIGNAL);
this.segment = segment;
- this.direction = direction;
}
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/Switch.java b/src/main/java/nl/andrewl/railsignalapi/model/component/Switch.java
index afa8c80..ce3b1e9 100644
--- a/src/main/java/nl/andrewl/railsignalapi/model/component/Switch.java
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/Switch.java
@@ -3,6 +3,8 @@ package nl.andrewl.railsignalapi.model.component;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import lombok.Setter;
+import nl.andrewl.railsignalapi.model.RailSystem;
import javax.persistence.*;
import java.util.Set;
@@ -22,8 +24,16 @@ public class Switch extends PathNode {
private Set possibleConfigurations;
/**
- * The switch configuration that this switch is currently in.
+ * The switch configuration that this switch is currently in. If null, then
+ * we don't know what configuration the switch is in.
*/
- @OneToOne(optional = false, fetch = FetchType.LAZY)
+ @OneToOne(fetch = FetchType.LAZY)
+ @Setter
private SwitchConfiguration activeConfiguration;
+
+ public Switch(RailSystem railSystem, Position position, String name, Set connectedNodes, Set possibleConfigurations, SwitchConfiguration activeConfiguration) {
+ super(railSystem, position, name, ComponentType.SWITCH, connectedNodes);
+ this.possibleConfigurations = possibleConfigurations;
+ this.activeConfiguration = activeConfiguration;
+ }
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/model/component/SwitchConfiguration.java b/src/main/java/nl/andrewl/railsignalapi/model/component/SwitchConfiguration.java
index 261f81c..b6a4f23 100644
--- a/src/main/java/nl/andrewl/railsignalapi/model/component/SwitchConfiguration.java
+++ b/src/main/java/nl/andrewl/railsignalapi/model/component/SwitchConfiguration.java
@@ -1,5 +1,7 @@
package nl.andrewl.railsignalapi.model.component;
+import lombok.AccessLevel;
+import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@@ -10,7 +12,8 @@ import java.util.Set;
* as an active configuration in the linked switch component.
*/
@Entity
-@NoArgsConstructor
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
public class SwitchConfiguration {
@Id
@GeneratedValue
@@ -28,4 +31,17 @@ public class SwitchConfiguration {
*/
@ManyToMany(fetch = FetchType.EAGER)
private Set nodes;
+
+ public SwitchConfiguration(Switch switchComponent, Set nodes) {
+ this.switchComponent = switchComponent;
+ this.nodes = nodes;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ return o instanceof SwitchConfiguration sc &&
+ sc.switchComponent.equals(this.switchComponent) &&
+ sc.nodes.equals(this.nodes);
+ }
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/ComponentsApiController.java b/src/main/java/nl/andrewl/railsignalapi/rest/ComponentsApiController.java
new file mode 100644
index 0000000..1a83b3c
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/ComponentsApiController.java
@@ -0,0 +1,39 @@
+package nl.andrewl.railsignalapi.rest;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.RequiredArgsConstructor;
+import nl.andrewl.railsignalapi.rest.dto.component.ComponentResponse;
+import nl.andrewl.railsignalapi.rest.dto.component.SimpleComponentResponse;
+import nl.andrewl.railsignalapi.service.ComponentService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping(path = "/api/rs/{rsId}/c")
+@RequiredArgsConstructor
+public class ComponentsApiController {
+ private final ComponentService componentService;
+
+ @GetMapping
+ public List getAllComponents(@PathVariable long rsId) {
+ return componentService.getComponents(rsId);
+ }
+
+ @GetMapping(path = "/{cId}")
+ public ComponentResponse getComponent(@PathVariable long rsId, @PathVariable long cId) {
+ return componentService.getComponent(rsId, cId);
+ }
+
+ @PostMapping
+ public ComponentResponse createComponent(@PathVariable long rsId, @RequestBody ObjectNode data) {
+ return componentService.create(rsId, data);
+ }
+
+ @DeleteMapping(path = "/{cId}")
+ public ResponseEntity removeComponent(@PathVariable long rsId, @PathVariable long cId) {
+ componentService.removeComponent(rsId, cId);
+ return ResponseEntity.noContent().build();
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/SegmentPayload.java b/src/main/java/nl/andrewl/railsignalapi/rest/SegmentPayload.java
new file mode 100644
index 0000000..9bda7f6
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/SegmentPayload.java
@@ -0,0 +1,4 @@
+package nl.andrewl.railsignalapi.rest;
+
+public record SegmentPayload(String name) {
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/SegmentsApiController.java b/src/main/java/nl/andrewl/railsignalapi/rest/SegmentsApiController.java
new file mode 100644
index 0000000..80418a1
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/SegmentsApiController.java
@@ -0,0 +1,31 @@
+package nl.andrewl.railsignalapi.rest;
+
+import lombok.RequiredArgsConstructor;
+import nl.andrewl.railsignalapi.rest.dto.FullSegmentResponse;
+import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
+import nl.andrewl.railsignalapi.service.SegmentService;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping(path = "/api/rs/{rsId}/s")
+@RequiredArgsConstructor
+public class SegmentsApiController {
+ private final SegmentService segmentService;
+
+ @GetMapping
+ public List getSegments(@PathVariable long rsId) {
+ return segmentService.getSegments(rsId);
+ }
+
+ @GetMapping(path = "/{sId}")
+ public FullSegmentResponse getSegment(@PathVariable long rsId, @PathVariable long sId) {
+ return segmentService.getSegment(rsId, sId);
+ }
+
+ @PostMapping
+ public FullSegmentResponse createSegment(@PathVariable long rsId, @RequestBody SegmentPayload payload) {
+ return segmentService.create(rsId, payload);
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/WebConfig.java b/src/main/java/nl/andrewl/railsignalapi/rest/WebConfig.java
index 1be14a6..22a3e05 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/WebConfig.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/WebConfig.java
@@ -1,6 +1,7 @@
package nl.andrewl.railsignalapi.rest;
import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -13,4 +14,13 @@ public class WebConfig implements WebMvcConfigurer {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
+
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/**")
+ .allowedOrigins("*")
+ .allowedMethods("*");
+ }
+
+
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/FullSegmentResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/FullSegmentResponse.java
new file mode 100644
index 0000000..ae8cbde
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/FullSegmentResponse.java
@@ -0,0 +1,18 @@
+package nl.andrewl.railsignalapi.rest.dto;
+
+import nl.andrewl.railsignalapi.model.Segment;
+import nl.andrewl.railsignalapi.rest.dto.component.SegmentBoundaryNodeResponse;
+import nl.andrewl.railsignalapi.rest.dto.component.SignalResponse;
+
+import java.util.List;
+
+public class FullSegmentResponse extends SegmentResponse {
+ public List signals;
+ public List boundaryNodes;
+
+ public FullSegmentResponse(Segment s) {
+ super(s);
+ this.signals = s.getSignals().stream().map(SignalResponse::new).toList();
+ this.boundaryNodes = s.getBoundaryNodes().stream().map(SegmentBoundaryNodeResponse::new).toList();
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/SegmentResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/SegmentResponse.java
new file mode 100644
index 0000000..617774b
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/SegmentResponse.java
@@ -0,0 +1,17 @@
+package nl.andrewl.railsignalapi.rest.dto;
+
+import nl.andrewl.railsignalapi.model.Segment;
+
+public class SegmentResponse {
+ public long id;
+ public String name;
+
+ public SegmentResponse(long id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public SegmentResponse(Segment s) {
+ this(s.getId(), s.getName());
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/ComponentResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/ComponentResponse.java
new file mode 100644
index 0000000..7fd74f7
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/ComponentResponse.java
@@ -0,0 +1,27 @@
+package nl.andrewl.railsignalapi.rest.dto.component;
+
+import nl.andrewl.railsignalapi.model.component.*;
+
+public abstract class ComponentResponse {
+ public long id;
+ public Position position;
+ public String name;
+ public String type;
+ public boolean online;
+
+ public ComponentResponse(Component c) {
+ this.id = c.getId();
+ this.position = c.getPosition();
+ this.name = c.getName();
+ this.type = c.getType().name();
+ this.online = c.isOnline();
+ }
+
+ public static ComponentResponse of(Component c) {
+ return switch (c.getType()) {
+ case SIGNAL -> new SignalResponse((Signal) c);
+ case SWITCH -> new SwitchResponse((Switch) c);
+ case SEGMENT_BOUNDARY -> new SegmentBoundaryNodeResponse((SegmentBoundaryNode) c);
+ };
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/PathNodeResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/PathNodeResponse.java
new file mode 100644
index 0000000..1c83196
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/PathNodeResponse.java
@@ -0,0 +1,14 @@
+package nl.andrewl.railsignalapi.rest.dto.component;
+
+import nl.andrewl.railsignalapi.model.component.PathNode;
+
+import java.util.List;
+
+public abstract class PathNodeResponse extends ComponentResponse {
+ public List connectedNodes;
+
+ public PathNodeResponse(PathNode p) {
+ super(p);
+ this.connectedNodes = p.getConnectedNodes().stream().map(SimpleComponentResponse::new).toList();
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SegmentBoundaryNodeResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SegmentBoundaryNodeResponse.java
new file mode 100644
index 0000000..ba09712
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SegmentBoundaryNodeResponse.java
@@ -0,0 +1,15 @@
+package nl.andrewl.railsignalapi.rest.dto.component;
+
+import nl.andrewl.railsignalapi.model.component.SegmentBoundaryNode;
+import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
+
+import java.util.List;
+
+public class SegmentBoundaryNodeResponse extends PathNodeResponse {
+ public List segments;
+
+ public SegmentBoundaryNodeResponse(SegmentBoundaryNode n) {
+ super(n);
+ this.segments = n.getSegments().stream().map(SegmentResponse::new).toList();
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SignalResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SignalResponse.java
new file mode 100644
index 0000000..56dd8d5
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SignalResponse.java
@@ -0,0 +1,12 @@
+package nl.andrewl.railsignalapi.rest.dto.component;
+
+import nl.andrewl.railsignalapi.model.component.Signal;
+import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
+
+public class SignalResponse extends ComponentResponse {
+ public SegmentResponse segment;
+ public SignalResponse(Signal s) {
+ super(s);
+ this.segment = new SegmentResponse(s.getSegment());
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SimpleComponentResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SimpleComponentResponse.java
new file mode 100644
index 0000000..6b6a9da
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SimpleComponentResponse.java
@@ -0,0 +1,22 @@
+package nl.andrewl.railsignalapi.rest.dto.component;
+
+import nl.andrewl.railsignalapi.model.component.Component;
+import nl.andrewl.railsignalapi.model.component.Position;
+
+public record SimpleComponentResponse (
+ long id,
+ Position position,
+ String name,
+ String type,
+ boolean online
+) {
+ public SimpleComponentResponse(Component c) {
+ this(
+ c.getId(),
+ c.getPosition(),
+ c.getName(),
+ c.getType().name(),
+ c.isOnline()
+ );
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchConfigurationResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchConfigurationResponse.java
new file mode 100644
index 0000000..f65a5b6
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchConfigurationResponse.java
@@ -0,0 +1,17 @@
+package nl.andrewl.railsignalapi.rest.dto.component;
+
+import nl.andrewl.railsignalapi.model.component.SwitchConfiguration;
+
+import java.util.List;
+
+public record SwitchConfigurationResponse (
+ long id,
+ List nodes
+) {
+ public SwitchConfigurationResponse(SwitchConfiguration sc) {
+ this(
+ sc.getId(),
+ sc.getNodes().stream().map(SimpleComponentResponse::new).toList()
+ );
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchResponse.java
new file mode 100644
index 0000000..b854bc0
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchResponse.java
@@ -0,0 +1,16 @@
+package nl.andrewl.railsignalapi.rest.dto.component;
+
+import nl.andrewl.railsignalapi.model.component.Switch;
+
+import java.util.List;
+
+public class SwitchResponse extends PathNodeResponse {
+ public List possibleConfigurations;
+ public SwitchConfigurationResponse activeConfiguration;
+
+ public SwitchResponse(Switch s) {
+ super(s);
+ this.possibleConfigurations = s.getPossibleConfigurations().stream().map(SwitchConfigurationResponse::new).toList();
+ this.activeConfiguration = new SwitchConfigurationResponse(s.getActiveConfiguration());
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/service/BranchService.java b/src/main/java/nl/andrewl/railsignalapi/service/BranchService.java
deleted file mode 100644
index 3a3dc08..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/service/BranchService.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package nl.andrewl.railsignalapi.service;
-
-import lombok.RequiredArgsConstructor;
-import nl.andrewl.railsignalapi.dao.BranchRepository;
-import nl.andrewl.railsignalapi.dao.RailSystemRepository;
-import nl.andrewl.railsignalapi.dao.SignalRepository;
-import nl.andrewl.railsignalapi.rest.dto.BranchResponse;
-import nl.andrewl.railsignalapi.rest.dto.SignalResponse;
-import org.springframework.http.HttpStatus;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.server.ResponseStatusException;
-
-import java.util.List;
-
-@Service
-@RequiredArgsConstructor
-public class BranchService {
- private final BranchRepository branchRepository;
- private final RailSystemRepository railSystemRepository;
- private final SignalRepository signalRepository;
-
- @Transactional
- public void deleteBranch(long rsId, long branchId) {
- var branch = branchRepository.findByIdAndRailSystemId(branchId, rsId)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
- if (!branch.getSignalConnections().isEmpty()) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Branch should not be connected to any signals.");
- }
- branchRepository.delete(branch);
- }
-
- @Transactional(readOnly = true)
- public List getAllBranches(long rsId) {
- var rs = railSystemRepository.findById(rsId)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
- return branchRepository.findAllByRailSystemOrderByName(rs).stream()
- .map(BranchResponse::new)
- .toList();
- }
-
- @Transactional(readOnly = true)
- public BranchResponse getBranch(long rsId, long branchId) {
- var branch = branchRepository.findByIdAndRailSystemId(branchId, rsId)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
- return new BranchResponse(branch);
- }
-
- @Transactional(readOnly = true)
- public List getConnectedSignals(long rsId, long branchId) {
- var branch = branchRepository.findByIdAndRailSystemId(branchId, rsId)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
- return signalRepository.findAllConnectedToBranch(branch).stream()
- .map(SignalResponse::new)
- .toList();
- }
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/service/ComponentService.java b/src/main/java/nl/andrewl/railsignalapi/service/ComponentService.java
new file mode 100644
index 0000000..c39803c
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/service/ComponentService.java
@@ -0,0 +1,129 @@
+package nl.andrewl.railsignalapi.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.RequiredArgsConstructor;
+import nl.andrewl.railsignalapi.dao.ComponentRepository;
+import nl.andrewl.railsignalapi.dao.RailSystemRepository;
+import nl.andrewl.railsignalapi.dao.SegmentRepository;
+import nl.andrewl.railsignalapi.dao.SwitchConfigurationRepository;
+import nl.andrewl.railsignalapi.model.RailSystem;
+import nl.andrewl.railsignalapi.model.Segment;
+import nl.andrewl.railsignalapi.model.component.*;
+import nl.andrewl.railsignalapi.rest.dto.component.ComponentResponse;
+import nl.andrewl.railsignalapi.rest.dto.component.SimpleComponentResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@Service
+@RequiredArgsConstructor
+public class ComponentService {
+ private final ComponentRepository componentRepository;
+ private final RailSystemRepository railSystemRepository;
+ private final SegmentRepository segmentRepository;
+ private final SwitchConfigurationRepository switchConfigurationRepository;
+
+ @Transactional(readOnly = true)
+ public List getComponents(long rsId) {
+ var rs = railSystemRepository.findById(rsId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ return componentRepository.findAllByRailSystem(rs).stream().map(SimpleComponentResponse::new).toList();
+ }
+
+ @Transactional(readOnly = true)
+ public ComponentResponse getComponent(long rsId, long componentId) {
+ var c = componentRepository.findByIdAndRailSystemId(componentId, rsId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ return ComponentResponse.of(c);
+ }
+
+ @Transactional
+ public void removeComponent(long rsId, long componentId) {
+ var c = componentRepository.findByIdAndRailSystemId(componentId, rsId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ // If this is a path node, check for and remove any switch configurations that use it.
+ if (c instanceof PathNode p) {
+ switchConfigurationRepository.deleteAllByNodesContaining(p);
+ }
+ componentRepository.delete(c);
+ }
+
+ @Transactional
+ public ComponentResponse create(long rsId, ObjectNode data) {
+ RailSystem rs = railSystemRepository.findById(rsId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ String type = data.get("type").asText();
+ Position pos = new Position();
+ pos.setX(data.get("position").get("x").asDouble());
+ pos.setY(data.get("position").get("y").asDouble());
+ pos.setZ(data.get("position").get("z").asDouble());
+ String name = data.get("name").asText();
+
+ if (name != null && componentRepository.existsByNameAndRailSystem(name, rs)) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component with that name already exists.");
+ }
+
+ Component c = switch (type) {
+ case "SIGNAL" -> createSignal(rs, pos, name, data);
+ case "SWITCH" -> createSwitch(rs, pos, name, data);
+ case "SEGMENT_BOUNDARY" -> createSegmentBoundary(rs, pos, name, data);
+ default -> throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unsupported component type: " + type);
+ };
+ c = componentRepository.save(c);
+ return ComponentResponse.of(c);
+ }
+
+ private Component createSignal(RailSystem rs, Position pos, String name, ObjectNode data) {
+ long segmentId = data.get("segment").get("id").asLong();
+ Segment segment = segmentRepository.findById(segmentId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ return new Signal(rs, pos, name, segment);
+ }
+
+ private Component createSwitch(RailSystem rs, Position pos, String name, ObjectNode data) {
+ Switch s = new Switch(rs, pos, name, new HashSet<>(), new HashSet<>(), null);
+ for (JsonNode configJson : data.withArray("possibleConfigurations")) {
+ Set pathNodes = new HashSet<>();
+ for (JsonNode pathNodeJson : configJson.withArray("nodes")) {
+ long pathNodeId = pathNodeJson.get("id").asLong();
+ Component c = componentRepository.findById(pathNodeId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ if (c instanceof PathNode pathNode) {
+ pathNodes.add(pathNode);
+ s.getConnectedNodes().add(pathNode);
+ } else {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Id " + pathNodeId + " does not refer to a PathNode component.");
+ }
+ }
+ s.getPossibleConfigurations().add(new SwitchConfiguration(s, pathNodes));
+ }
+ return s;
+ }
+
+ private Component createSegmentBoundary(RailSystem rs, Position pos, String name, ObjectNode data) {
+ ArrayNode segmentsNode = data.withArray("segments");
+ Set segments = new HashSet<>();
+ for (JsonNode segmentNode : segmentsNode) {
+ long segmentId = segmentNode.get("id").asLong();
+ Segment segment = segmentRepository.findById(segmentId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ segments.add(segment);
+ }
+ if (segments.size() < 1 || segments.size() > 2) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid number of segments.");
+ }
+ return new SegmentBoundaryNode(rs, pos, name, new HashSet<>(), segments);
+ }
+
+ @Transactional
+ public void remove(long rsId, long componentId) {
+
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/service/RailSystemService.java b/src/main/java/nl/andrewl/railsignalapi/service/RailSystemService.java
index 2e9e1f1..b5d7267 100644
--- a/src/main/java/nl/andrewl/railsignalapi/service/RailSystemService.java
+++ b/src/main/java/nl/andrewl/railsignalapi/service/RailSystemService.java
@@ -20,7 +20,7 @@ import java.util.List;
public class RailSystemService {
private final RailSystemRepository railSystemRepository;
private final SegmentRepository segmentRepository;
- private final ComponentRepository componentRepository;
+ private final ComponentRepository> componentRepository;
@Transactional
public List getRailSystems() {
@@ -29,10 +29,13 @@ public class RailSystemService {
@Transactional
public RailSystemResponse createRailSystem(RailSystemCreationPayload payload) {
- if (railSystemRepository.existsByName(payload.name())) {
+ if (payload.name() == null) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing required name.");
+ if (payload.name().isBlank()) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Name cannot be blank.");
+ String name = payload.name().trim();
+ if (railSystemRepository.existsByName(name)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "A rail system with that name already exists.");
}
- RailSystem rs = new RailSystem(payload.name());
+ RailSystem rs = new RailSystem(name);
return new RailSystemResponse(railSystemRepository.save(rs));
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java b/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java
new file mode 100644
index 0000000..3b7dfe7
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java
@@ -0,0 +1,57 @@
+package nl.andrewl.railsignalapi.service;
+
+import lombok.RequiredArgsConstructor;
+import nl.andrewl.railsignalapi.dao.ComponentRepository;
+import nl.andrewl.railsignalapi.dao.RailSystemRepository;
+import nl.andrewl.railsignalapi.dao.SegmentRepository;
+import nl.andrewl.railsignalapi.model.Segment;
+import nl.andrewl.railsignalapi.model.component.Component;
+import nl.andrewl.railsignalapi.rest.SegmentPayload;
+import nl.andrewl.railsignalapi.rest.dto.FullSegmentResponse;
+import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class SegmentService {
+ private final SegmentRepository segmentRepository;
+ private final RailSystemRepository railSystemRepository;
+ private final ComponentRepository componentRepository;
+
+ @Transactional(readOnly = true)
+ public List getSegments(long rsId) {
+ return segmentRepository.findAllByRailSystemId(rsId).stream().map(SegmentResponse::new).toList();
+ }
+
+ @Transactional(readOnly = true)
+ public FullSegmentResponse getSegment(long rsId, long segmentId) {
+ var segment = segmentRepository.findByIdAndRailSystemId(segmentId, rsId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ return new FullSegmentResponse(segment);
+ }
+
+ @Transactional
+ public FullSegmentResponse create(long rsId, SegmentPayload payload) {
+ var rs = railSystemRepository.findById(rsId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ String name = payload.name();
+ if (segmentRepository.existsByNameAndRailSystem(name, rs)) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Segment with that name already exists.");
+ }
+ Segment segment = segmentRepository.save(new Segment(rs, name));
+ return new FullSegmentResponse(segment);
+ }
+
+ @Transactional
+ public void remove(long rsId, long segmentId) {
+ var segment = segmentRepository.findByIdAndRailSystemId(segmentId, rsId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ componentRepository.deleteAll(segment.getSignals());
+ componentRepository.deleteAll(segment.getBoundaryNodes());
+ }
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/service/SignalService.java b/src/main/java/nl/andrewl/railsignalapi/service/SignalService.java
deleted file mode 100644
index 570bae2..0000000
--- a/src/main/java/nl/andrewl/railsignalapi/service/SignalService.java
+++ /dev/null
@@ -1,246 +0,0 @@
-package nl.andrewl.railsignalapi.service;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import nl.andrewl.railsignalapi.dao.BranchRepository;
-import nl.andrewl.railsignalapi.dao.RailSystemRepository;
-import nl.andrewl.railsignalapi.dao.SignalBranchConnectionRepository;
-import nl.andrewl.railsignalapi.dao.SignalRepository;
-import nl.andrewl.railsignalapi.model.*;
-import nl.andrewl.railsignalapi.model.component.SignalBranchConnection;
-import nl.andrewl.railsignalapi.rest.dto.SignalConnectionsUpdatePayload;
-import nl.andrewl.railsignalapi.rest.dto.SignalCreationPayload;
-import nl.andrewl.railsignalapi.rest.dto.SignalResponse;
-import nl.andrewl.railsignalapi.websocket.BranchUpdateMessage;
-import nl.andrewl.railsignalapi.websocket.SignalUpdateMessage;
-import nl.andrewl.railsignalapi.websocket.SignalUpdateType;
-import org.springframework.http.HttpStatus;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.server.ResponseStatusException;
-import org.springframework.web.socket.TextMessage;
-import org.springframework.web.socket.WebSocketMessage;
-import org.springframework.web.socket.WebSocketSession;
-
-import java.io.IOException;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-
-@Service
-@RequiredArgsConstructor
-@Slf4j
-public class SignalService {
- private final RailSystemRepository railSystemRepository;
- private final SignalRepository signalRepository;
- private final BranchRepository branchRepository;
- private final SignalBranchConnectionRepository signalBranchConnectionRepository;
-
- private final ObjectMapper mapper = new ObjectMapper();
- private final Map> signalWebSocketSessions = new ConcurrentHashMap<>();
-
- @Transactional
- public SignalResponse createSignal(long rsId, SignalCreationPayload payload) {
- var rs = railSystemRepository.findById(rsId)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Rail system not found."));
- if (signalRepository.existsByNameAndRailSystem(payload.name(), rs)) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Signal " + payload.name() + " already exists.");
- }
- if (payload.branchConnections().size() != 2) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Exactly two branch connections must be provided.");
- }
- // Ensure that the directions of the connections are opposite each other.
- Direction dir1 = Direction.parse(payload.branchConnections().get(0).direction());
- Direction dir2 = Direction.parse(payload.branchConnections().get(1).direction());
- if (!dir1.isOpposite(dir2)) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Branch connections must be opposite each other.");
-
- Set branchConnections = new HashSet<>();
- Signal signal = new Signal(rs, payload.name(), payload.position(), branchConnections);
- for (var branchData : payload.branchConnections()) {
- Branch branch;
- if (branchData.id() != null) {
- branch = this.branchRepository.findByIdAndRailSystem(branchData.id(), rs)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Branch id " + branchData.id() + " is invalid."));
- } else {
- branch = this.branchRepository.findByNameAndRailSystem(branchData.name(), rs)
- .orElse(new Branch(rs, branchData.name(), BranchStatus.FREE));
- }
- Direction dir = Direction.parse(branchData.direction());
- branchConnections.add(new SignalBranchConnection(signal, branch, dir));
- }
- signal = signalRepository.save(signal);
- return new SignalResponse(signal);
- }
-
- @Transactional
- public SignalResponse updateSignalBranchConnections(long rsId, long sigId, SignalConnectionsUpdatePayload payload) {
- var signal = signalRepository.findByIdAndRailSystemId(sigId, rsId)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
- for (var c : payload.connections()) {
- var fromConnection = signalBranchConnectionRepository.findById(c.from())
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not find signal branch connection: " + c.from()));
- if (!fromConnection.getSignal().getId().equals(signal.getId())) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Can only update signal branch connections originating from the specified signal.");
- }
- var toConnection = signalBranchConnectionRepository.findById(c.to())
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not find signal branch connection: " + c.to()));
- if (!fromConnection.getBranch().getId().equals(toConnection.getBranch().getId())) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Signal branch connections can only path via a mutual branch.");
- }
- fromConnection.getReachableSignalConnections().add(toConnection);
- signalBranchConnectionRepository.save(fromConnection);
- }
- for (var con : signal.getBranchConnections()) {
- Set connectionsToRemove = new HashSet<>();
- for (var reachableCon : con.getReachableSignalConnections()) {
- if (!payload.connections().contains(new SignalConnectionsUpdatePayload.ConnectionData(con.getId(), reachableCon.getId()))) {
- connectionsToRemove.add(reachableCon);
- }
- }
- con.getReachableSignalConnections().removeAll(connectionsToRemove);
- signalBranchConnectionRepository.save(con);
- }
- // Reload the signal.
- signal = signalRepository.findById(signal.getId()).orElseThrow();
- return new SignalResponse(signal);
- }
-
- @Transactional
- public void registerSignalWebSocketSession(Set signalIds, WebSocketSession session) {
- this.signalWebSocketSessions.put(session, signalIds);
- // Instantly send a data packet so that the signals are up-to-date.
- RailSystem rs = null;
- for (var signalId : signalIds) {
- var signal = signalRepository.findById(signalId)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid signal id."));
- if (rs == null) {
- rs = signal.getRailSystem();
- } else if (!rs.getId().equals(signal.getRailSystem().getId())) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot open signal websocket session for signals from different rail systems.");
- }
- for (var branchConnection : signal.getBranchConnections()) {
- try {
- session.sendMessage(new TextMessage(mapper.writeValueAsString(
- new BranchUpdateMessage(
- branchConnection.getBranch().getId(),
- branchConnection.getBranch().getStatus().name()
- )
- )));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- signal.setOnline(true);
- signalRepository.save(signal);
- }
- }
-
- @Transactional
- public void deregisterSignalWebSocketSession(WebSocketSession session) {
- var ids = this.signalWebSocketSessions.remove(session);
- if (ids != null) {
- for (var signalId : ids) {
- signalRepository.findById(signalId).ifPresent(signal -> {
- signal.setOnline(false);
- signalRepository.save(signal);
- });
- }
- }
- }
-
- public WebSocketSession getSignalWebSocketSession(long signalId) {
- for (var entry : signalWebSocketSessions.entrySet()) {
- if (entry.getValue().contains(signalId)) return entry.getKey();
- }
- return null;
- }
-
- @Transactional
- public void handleSignalUpdate(SignalUpdateMessage updateMessage) {
- var signal = signalRepository.findById(updateMessage.signalId())
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
- Branch fromBranch = null;
- Branch toBranch = null;
- for (var con : signal.getBranchConnections()) {
- if (con.getBranch().getId() == updateMessage.fromBranchId()) {
- fromBranch = con.getBranch();
- }
- if (con.getBranch().getId() == updateMessage.toBranchId()) {
- toBranch = con.getBranch();
- }
- }
- if (fromBranch == null || toBranch == null) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid branches.");
- }
- SignalUpdateType updateType = SignalUpdateType.valueOf(updateMessage.type().trim().toUpperCase());
- if (updateType == SignalUpdateType.BEGIN && toBranch.getStatus() != BranchStatus.FREE) {
- log.warn("Warning! Train is entering a non-free branch {}.", toBranch.getName());
- }
- if (toBranch.getStatus() != BranchStatus.OCCUPIED) {
- log.info("Updating branch {} status from {} to {}.", toBranch.getName(), toBranch.getStatus(), BranchStatus.OCCUPIED);
- toBranch.setStatus(BranchStatus.OCCUPIED);
- branchRepository.save(toBranch);
- broadcastToConnectedSignals(toBranch);
- }
- if (updateType == SignalUpdateType.END) {
- if (fromBranch.getStatus() != BranchStatus.FREE) {
- log.info("Updating branch {} status from {} to {}.", fromBranch.getName(), fromBranch.getStatus(), BranchStatus.FREE);
- fromBranch.setStatus(BranchStatus.FREE);
- branchRepository.save(fromBranch);
- broadcastToConnectedSignals(fromBranch);
- }
- } else if (updateType == SignalUpdateType.BEGIN) {
- if (fromBranch.getStatus() != BranchStatus.OCCUPIED) {
- log.info("Updating branch {} status from {} to {}.", fromBranch.getName(), fromBranch.getStatus(), BranchStatus.OCCUPIED);
- fromBranch.setStatus(BranchStatus.OCCUPIED);
- branchRepository.save(fromBranch);
- broadcastToConnectedSignals(fromBranch);
- }
- }
- }
-
- private void broadcastToConnectedSignals(Branch branch) {
- try {
- WebSocketMessage msg = new TextMessage(mapper.writeValueAsString(
- new BranchUpdateMessage(branch.getId(), branch.getStatus().name())
- ));
- signalRepository.findAllConnectedToBranch(branch).stream()
- .map(s -> getSignalWebSocketSession(s.getId()))
- .filter(Objects::nonNull).distinct()
- .forEach(session -> {
- try {
- session.sendMessage(msg);
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
- } catch (JsonProcessingException e) {
- e.printStackTrace();
- }
-
- }
-
- @Transactional(readOnly = true)
- public SignalResponse getSignal(long rsId, long sigId) {
- var s = signalRepository.findByIdAndRailSystemId(sigId, rsId)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
- return new SignalResponse(s);
- }
-
- @Transactional(readOnly = true)
- public List getAllSignals(long rsId) {
- var rs = railSystemRepository.findById(rsId)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Rail system not found."));
- return signalRepository.findAllByRailSystemOrderByName(rs).stream()
- .map(SignalResponse::new)
- .toList();
- }
-
- @Transactional
- public void deleteSignal(long rsId, long sigId) {
- var s = signalRepository.findByIdAndRailSystemId(sigId, rsId)
- .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
- signalRepository.delete(s);
- }
-}
diff --git a/src/main/java/nl/andrewl/railsignalapi/websocket/SignalWebSocketHandler.java b/src/main/java/nl/andrewl/railsignalapi/websocket/SignalWebSocketHandler.java
index daf690e..81b2b1e 100644
--- a/src/main/java/nl/andrewl/railsignalapi/websocket/SignalWebSocketHandler.java
+++ b/src/main/java/nl/andrewl/railsignalapi/websocket/SignalWebSocketHandler.java
@@ -3,7 +3,6 @@ package nl.andrewl.railsignalapi.websocket;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import nl.andrewl.railsignalapi.service.SignalService;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
@@ -18,7 +17,6 @@ import java.util.Set;
@Slf4j
public class SignalWebSocketHandler extends TextWebSocketHandler {
private final ObjectMapper mapper = new ObjectMapper();
- private final SignalService signalService;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
@@ -31,19 +29,19 @@ public class SignalWebSocketHandler extends TextWebSocketHandler {
for (var idStr : signalIdHeader.split(",")) {
ids.add(Long.parseLong(idStr.trim()));
}
- signalService.registerSignalWebSocketSession(ids, session);
+ //signalService.registerSignalWebSocketSession(ids, session);
log.info("Connection established with signals {}.", ids);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
var msg = mapper.readValue(message.getPayload(), SignalUpdateMessage.class);
- signalService.handleSignalUpdate(msg);
+ //signalService.handleSignalUpdate(msg);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
- signalService.deregisterSignalWebSocketSession(session);
+ //signalService.deregisterSignalWebSocketSession(session);
log.info("Closed connection {}. Status: {}", session.getId(), status.toString());
}
}
diff --git a/src/main/resources/static/js/drawing.js b/src/main/resources/static/js/drawing.js
deleted file mode 100644
index 384a667..0000000
--- a/src/main/resources/static/js/drawing.js
+++ /dev/null
@@ -1,144 +0,0 @@
-function worldTransform() {
- const canvasRect = railMapCanvas[0].getBoundingClientRect();
- const scale = SCALE_VALUES[canvasScaleIndex];
- let tx = new DOMMatrix();
- tx.translateSelf(canvasRect.width / 2, canvasRect.height / 2, 0);
- tx.scaleSelf(scale, scale, scale);
- tx.translateSelf(canvasTranslation.x, canvasTranslation.y, 0);
- if (canvasDragOrigin !== null && canvasDragTranslation !== null) {
- tx.translateSelf(canvasDragTranslation.x, canvasDragTranslation.y, 0);
- }
- return tx;
-}
-
-function canvasPointToWorld(p) {
- return worldTransform().invertSelf().transformPoint(p);
-}
-
-function worldPointToCanvas(p) {
- return worldTransform().transformPoint(p);
-}
-
-function signalTransform(worldTx, signal) {
- let tx = DOMMatrix.fromMatrix(worldTx);
- tx.translateSelf(signal.position.x, signal.position.z, 0);
- let direction = signal.branchConnections[0].direction;
- if (direction === "EAST" || direction === "SOUTH" || direction === "SOUTH_EAST" || direction === "SOUTH_WEST") {
- direction = signal.branchConnections[1].direction;
- }
- if (direction === undefined || direction === null || direction === "") {
- direction = "NORTH";
- }
- let angle = 0;
- if (direction === "NORTH") {
- angle = 90;
- } else if (direction === "NORTH_WEST") {
- angle = 45;
- } else if (direction === "NORTH_EAST") {
- angle = 135;
- }
- tx.rotateSelf(0, 0, angle);
- return tx;
-}
-
-function drawRailSystem() {
- let ctx = railMapCanvas[0].getContext("2d");
- ctx.resetTransform();
- ctx.clearRect(0, 0, railMapCanvas.width(), railMapCanvas.height());
- const worldTx = worldTransform();
- ctx.setTransform(worldTx);
- railSystem.signals.forEach(signal => {
- drawReachableConnections(ctx, signal);
- });
- railSystem.signals.forEach(signal => {
- ctx.setTransform(signalTransform(worldTx, signal));
- drawSignal(ctx, signal);
- });
- ctx.resetTransform();
- ctx.fillStyle = 'black';
- ctx.strokeStyle = 'black';
- ctx.font = '24px Serif';
- let textLine = 0;
- hoveredElements.forEach(element => {
- ctx.strokeText(element.name, 10, 20 + textLine * 20);
- ctx.fillText(element.name, 10, 20 + textLine * 20);
- textLine += 1;
- });
-}
-
-function drawSignal(ctx, signal) {
- if (signal.online) {
- ctx.fillStyle = 'black';
- } else {
- ctx.fillStyle = 'gray';
- }
- ctx.scale(2, 2);
- ctx.fillRect(-0.5, -0.5, 1, 1);
- let northWesterlyCon = signal.branchConnections[0];
- let southEasterlyCon = signal.branchConnections[1];
- if (northWesterlyCon.direction === "EAST" || northWesterlyCon.direction === "SOUTH" || northWesterlyCon.direction === "SOUTH_WEST" || northWesterlyCon.direction === "SOUTH_EAST") {
- let tmp = northWesterlyCon;
- northWesterlyCon = southEasterlyCon;
- southEasterlyCon = tmp;
- }
-
- ctx.fillStyle = getSignalColor(signal, southEasterlyCon.branch.status);
- ctx.fillRect(-0.75, -0.4, 0.3, 0.8);
- ctx.fillStyle = getSignalColor(signal, northWesterlyCon.branch.status);
- ctx.fillRect(0.45, -0.4, 0.3, 0.8);
-}
-
-function getSignalColor(signal, branchStatus) {
- if (!signal.online) return 'rgb(0, 0, 255)';
- if (branchStatus === "FREE") {
- return 'rgb(0, 255, 0)';
- } else if (branchStatus === "OCCUPIED") {
- return 'rgb(255, 0, 0)';
- } else {
- return 'rgb(0, 0, 255)';
- }
-}
-
-// Draws lines indicating reachable paths between this signal and others, with arrows for directionality.
-function drawReachableConnections(ctx, signal) {
- ctx.strokeStyle = 'black';
- ctx.lineWidth = 0.25;
- signal.branchConnections.forEach(connection => {
- ctx.resetTransform();
- connection.reachableSignalConnections.forEach(reachableCon => {
- const dx = reachableCon.signalPosition.x - signal.position.x;
- const dy = reachableCon.signalPosition.z - signal.position.z;
- const dist = Math.sqrt(dx * dx + dy * dy);
- let tx = worldTransform();
- tx.translateSelf(signal.position.x, signal.position.z, 0);
- const angle = Math.atan2(dy, dx) * 180 / Math.PI - 90;
- tx.rotateSelf(0, 0, angle);
- ctx.setTransform(tx);
- ctx.beginPath();
- ctx.moveTo(0, 0);
- ctx.lineTo(0, dist);
- const arrowEnd = 5;
- const arrowWidth = 0.5;
- const arrowLength = 1;
- ctx.lineTo(0, arrowEnd);
- ctx.lineTo(arrowWidth, arrowEnd - arrowLength);
- ctx.lineTo(-arrowWidth, arrowEnd - arrowLength);
- ctx.lineTo(0, arrowEnd);
- ctx.stroke();
- ctx.fill();
- });
- });
-}
-
-function getRailSystemBoundingBox() {
- let min = {x: Number.MAX_SAFE_INTEGER, z: Number.MAX_SAFE_INTEGER};
- let max = {x: Number.MIN_SAFE_INTEGER, z: Number.MIN_SAFE_INTEGER};
- railSystem.signals.forEach(signal => {
- let p = signal.position;
- if (p.x < min.x) min.x = p.x;
- if (p.z < min.z) min.z = p.z;
- if (p.x > max.x) max.x = p.x;
- if (p.z > max.z) max.z = p.z;
- });
- return {x: min.x, y: min.z, width: Math.abs(max.x - min.x), height: Math.abs(max.z - min.z)};
-}
\ No newline at end of file
diff --git a/src/main/resources/static/js/main.js b/src/main/resources/static/js/main.js
deleted file mode 100644
index 481ccf4..0000000
--- a/src/main/resources/static/js/main.js
+++ /dev/null
@@ -1,388 +0,0 @@
-const $ = jQuery;
-
-let railSystemSelect;
-let railMapCanvas;
-let railSystem = null;
-let detailPanel = null;
-
-let canvasTranslation = {x: 0, y: 0};
-let canvasDragOrigin = null;
-let canvasDragTranslation = null;
-let hoveredElements = [];
-
-const SCALE_VALUES = [0.01, 0.1, 0.25, 0.5, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0, 10.0, 12.0, 16.0, 20.0, 30.0, 45.0, 60.0, 80.0, 100.0];
-const SCALE_INDEX_NORMAL = 7;
-let canvasScaleIndex = SCALE_INDEX_NORMAL;
-
-$(document).ready(() => {
- railSystemSelect = $('#railSystemSelect');
- railSystemSelect.change(railSystemChanged);
-
- railMapCanvas = $('#railMapCanvas');
- railMapCanvas.on('wheel', onCanvasMouseWheel);
- railMapCanvas.mousedown(onCanvasMouseDown);
- railMapCanvas.mouseup(onCanvasMouseUp);
- railMapCanvas.mousemove(onCanvasMouseMove);
-
- $('#addRailSystemInput').on("input", () => {
- $('#addRailSystemButton').prop("disabled", $('#addRailSystemInput').val() === "");
- });
- $('#addRailSystemButton').click(addRailSystem);
- $('#removeRailSystemButton').click(deleteRailSystem);
-
- detailPanel = $('#railMapDetailPanel');
-
- refreshRailSystems(true);
-});
-
-// Handle mouse scrolling within the context of the canvas.
-function onCanvasMouseWheel(event) {
- let s = event.originalEvent.deltaY;
- if (s > 0) {
- canvasScaleIndex = Math.max(0, canvasScaleIndex - 1);
- } else if (s < 0) {
- canvasScaleIndex = Math.min(SCALE_VALUES.length - 1, canvasScaleIndex + 1);
- }
- drawRailSystem();
- event.stopPropagation();
-}
-
-// Handle mouse clicks on the canvas.
-function onCanvasMouseDown(event) {
- const p = getMousePoint(event);
- canvasDragOrigin = {x: p.x, y: p.y};
-}
-
-// Handle mouse release on the canvas, which stops dragging or indicates that the user may have clicked on something.
-function onCanvasMouseUp(event) {
- if (canvasDragTranslation !== null) {
- canvasTranslation.x += canvasDragTranslation.x;
- canvasTranslation.y += canvasDragTranslation.y;
- } else {
- const p = getMousePoint(event);
- let signalClicked = false;
- railSystem.signals.forEach(signal => {
- const sp = new DOMPoint(signal.position.x, signal.position.z, 0, 1);
- const canvasSp = worldPointToCanvas(sp);
- const dist = Math.sqrt(Math.pow(p.x - canvasSp.x, 2) + Math.pow(p.y - canvasSp.y, 2));
- if (dist < 5) {
- console.log(signal);
- onSignalSelected(signal);
- signalClicked = true;
- }
- });
- if (!signalClicked) {
- onSignalSelected(null);
- }
- }
- canvasDragOrigin = null;
- canvasDragTranslation = null;
-}
-
-// Handle mouse motion over the canvas. This is for dragging and hovering over items.
-function onCanvasMouseMove(event) {
- const rect = railMapCanvas[0].getBoundingClientRect();
- const x = event.clientX - rect.left;
- const y = event.clientY - rect.top;
- if (canvasDragOrigin !== null) {
- const scale = SCALE_VALUES[canvasScaleIndex];
- const dx = x - canvasDragOrigin.x;
- const dy = y - canvasDragOrigin.y;
- canvasDragTranslation = {x: dx / scale, y: dy / scale};
- drawRailSystem();
- } else {
- hoveredElements = [];
- const p = getMousePoint(event);
- railSystem.signals.forEach(signal => {
- const sp = new DOMPoint(signal.position.x, signal.position.z, 0, 1);
- const canvasSp = worldPointToCanvas(sp);
- const dist = Math.sqrt(Math.pow(p.x - canvasSp.x, 2) + Math.pow(p.y - canvasSp.y, 2));
- if (dist < 5) {
- hoveredElements.push(signal);
- }
- });
- drawRailSystem();
- }
-}
-
-function getMousePoint(event) {
- const rect = railMapCanvas[0].getBoundingClientRect();
- const x = event.clientX - rect.left;
- const y = event.clientY - rect.top;
- return new DOMPoint(x, y, 0, 1);
-}
-
-function railSystemChanged() {
- detailPanel.empty();
- railSystem = {};
- railSystem.id = railSystemSelect.val();
- $.get("/api/railSystems/" + railSystem.id + "/signals")
- .done(signals => {
- railSystem.signals = signals;
- let bb = getRailSystemBoundingBox();
- canvasTranslation.x = -1 * (bb.x + (bb.width / 2));
- canvasTranslation.y = -1 * (bb.y + (bb.height / 2));
- canvasScaleIndex = SCALE_INDEX_NORMAL;
- drawRailSystem();
- window.setInterval(railSystemUpdated, 1000);
- });
- $.get("/api/railSystems/" + railSystem.id + "/branches")
- .done(branches => {
- railSystem.branches = branches;
- const branchSelects = $('.js_branch_list');
- branchSelects.empty();
- railSystem.branches.forEach(branch => {
- branchSelects.append($(''))
- });
- });
-}
-
-// Updates the current rail system's information from the API.
-function railSystemUpdated() {
- $.get("/api/railSystems/" + railSystem.id + "/signals")
- .done(signals => {
- railSystem.signals = signals;
- drawRailSystem();
- });
- $.get("/api/railSystems/" + railSystem.id + "/branches")
- .done(branches => {
- railSystem.branches = branches;
- const branchSelects = $('.js_branch_list');
- branchSelects.empty();
- railSystem.branches.forEach(branch => {
- branchSelects.append($(''))
- });
- });
-}
-
-function selectSignalById(id) {
- railSystem.signals.forEach(signal => {
- if (signal.id === id) {
- onSignalSelected(signal);
- }
- });
-}
-
-function onSignalSelected(signal) {
- detailPanel.empty();
- if (signal !== null) {
- const tpl = Handlebars.compile($('#signalTemplate').html());
- detailPanel.html(tpl(signal));
- signal.branchConnections.forEach(con => {
- const select = $('#signalPotentialConnectionsSelect-' + con.id);
- $.get("/api/railSystems/" + railSystem.id + "/branches/" + con.branch.id + "/signals")
- .done(signals => {
- signals = signals.filter(s => s.id !== signal.id);
- let connections = [];
- signals.forEach(s => {
- s.branchConnections
- .filter(c => c.branch.id === con.branch.id && !con.reachableSignalConnections.some(rc => rc.connectionId === c.id))
- .forEach(potentialConnection => {
- potentialConnection.signalName = s.name;
- potentialConnection.signalId = s.id;
- connections.push(potentialConnection);
- });
- });
- select.empty();
- const row = $('#signalPotentialConnectionsRow-' + con.id);
- row.toggle(connections.length > 0);
- connections.forEach(c => {
- select.append($(``))
- });
- });
- });
- }
-}
-
-function addRailSystem() {
- let name = $('#addRailSystemInput').val().trim();
- $.post({
- url: "/api/railSystems",
- data: JSON.stringify({name: name}),
- contentType: "application/json"
- })
- .done((response) => {
- refreshRailSystems();
- })
- .always(() => {
- $('#addRailSystemInput').val("");
- });
-}
-
-function deleteRailSystem() {
- if (railSystem !== null && railSystem.id) {
- confirm("Are you sure you want to permanently remove rail system " + railSystem.id + "?")
- .then(() => {
- $.ajax({
- url: "/api/railSystems/" + railSystem.id,
- type: "DELETE"
- })
- .always(() => {
- refreshRailSystems(true);
- });
- });
- }
-}
-
-function refreshRailSystems(selectFirst) {
- $.get("/api/railSystems")
- .done(railSystems => {
- railSystemSelect.empty();
- railSystems.forEach(railSystem => {
- let option = $(``)
- railSystemSelect.append(option);
- });
- if (selectFirst) {
- railSystemSelect.val(railSystems[0].id);
- railSystemSelect.change();
- }
- });
-}
-
-function addNewSignal() {
- const modalElement = $('#addSignalModal');
- const form = $('#addSignalForm');
- form.validate();
- if (!form.valid()) return;
- const data = {
- name: $('#addSignalName').val().trim(),
- position: {
- x: $('#addSignalPositionX').val(),
- y: $('#addSignalPositionY').val(),
- z: $('#addSignalPositionZ').val()
- },
- branchConnections: [
- {
- direction: $('#addSignalFirstConnectionDirection').val(),
- name: $('#addSignalFirstConnectionBranch').val()
- },
- {
- direction: $('#addSignalSecondConnectionDirection').val(),
- name: $('#addSignalSecondConnectionBranch').val()
- }
- ]
- };
- const modal = bootstrap.Modal.getInstance(modalElement[0]);
- modal.hide();
- modalElement.on("hidden.bs.modal", () => {
- confirm("Are you sure you want to add this new signal to the system?")
- .then(() => {
- $.post({
- url: "/api/railSystems/" + railSystem.id + "/signals",
- data: JSON.stringify(data),
- contentType: "application/json"
- })
- .done(() => {
- form.trigger("reset");
- railSystemUpdated();
- })
- .fail((response) => {
- console.error(response);
- $('#addSignalAlertsContainer').append($('
-
-
-
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/MapView.vue b/railsignal-app/src/components/railsystem/MapView.vue
index c0c1d0f..8a61c5c 100644
--- a/railsignal-app/src/components/railsystem/MapView.vue
+++ b/railsignal-app/src/components/railsystem/MapView.vue
@@ -1,26 +1,32 @@
-
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/SegmentsView.vue b/railsignal-app/src/components/railsystem/SegmentsView.vue
new file mode 100644
index 0000000..65e5673
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/SegmentsView.vue
@@ -0,0 +1,31 @@
+
+
Segments
+
+
+ {{segment.name}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/AddSegmentBoundary.vue b/railsignal-app/src/components/railsystem/component/AddSegmentBoundary.vue
new file mode 100644
index 0000000..ae63966
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/component/AddSegmentBoundary.vue
@@ -0,0 +1,76 @@
+
+
Add Segment Boundary
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/AddSignal.vue b/railsignal-app/src/components/railsystem/component/AddSignal.vue
new file mode 100644
index 0000000..6313c47
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/component/AddSignal.vue
@@ -0,0 +1,62 @@
+
+
Add Signal
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/ComponentView.vue b/railsignal-app/src/components/railsystem/component/ComponentView.vue
new file mode 100644
index 0000000..5332df1
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/component/ComponentView.vue
@@ -0,0 +1,55 @@
+
+
+
{{component.name}}
+
+ Id: {{component.id}}
+
+
+ Position: (x = {{component.position.x}}, y = {{component.position.y}}, z = {{component.position.z}})
+
+
+ Type: {{component.type}}
+
+
+ Online: {{component.online}}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/PathNodeComponentView.vue b/railsignal-app/src/components/railsystem/component/PathNodeComponentView.vue
new file mode 100644
index 0000000..4f4c1ed
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/component/PathNodeComponentView.vue
@@ -0,0 +1,27 @@
+
+
Connected Nodes
+
+
+ {{node.id}} | {{node.name}}
+
+
+
+ There are no connected nodes.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/SegmentBoundaryNodeComponentView.vue b/railsignal-app/src/components/railsystem/component/SegmentBoundaryNodeComponentView.vue
new file mode 100644
index 0000000..b2f3ba9
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/component/SegmentBoundaryNodeComponentView.vue
@@ -0,0 +1,24 @@
+
+
Segments
+
+
+ {{segment.id}} | {{segment.name}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/SignalComponentView.vue b/railsignal-app/src/components/railsystem/component/SignalComponentView.vue
new file mode 100644
index 0000000..a9b0833
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/component/SignalComponentView.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/mapRenderer.js b/railsignal-app/src/components/railsystem/mapRenderer.js
new file mode 100644
index 0000000..17007f8
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/mapRenderer.js
@@ -0,0 +1,248 @@
+/*
+This component is responsible for the rendering of a RailSystem in a 2d map
+view.
+ */
+
+const SCALE_VALUES = [0.01, 0.1, 0.25, 0.5, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0, 10.0, 12.0, 16.0, 20.0, 30.0, 45.0, 60.0, 80.0, 100.0];
+const SCALE_INDEX_NORMAL = 7;
+const HOVER_RADIUS = 10;
+
+let mapCanvas = null;
+let railSystem = null;
+
+let mapScaleIndex = SCALE_INDEX_NORMAL;
+let mapTranslation = {x: 0, y: 0};
+let mapDragOrigin = null;
+let mapDragTranslation = null;
+let lastMousePoint = new DOMPoint(0, 0, 0, 0);
+const hoveredElements = [];
+
+export function initMap(rs) {
+ railSystem = rs;
+ console.log("Initializing map for rail system: " + rs.name);
+ hoveredElements.length = 0;
+ mapCanvas = document.getElementById("railSystemMapCanvas");
+ mapCanvas.removeEventListener("wheel", onMouseWheel);
+ mapCanvas.addEventListener("wheel", onMouseWheel);
+ mapCanvas.removeEventListener("mousedown", onMouseDown);
+ mapCanvas.addEventListener("mousedown", onMouseDown);
+ mapCanvas.removeEventListener("mouseup", onMouseUp);
+ mapCanvas.addEventListener("mouseup", onMouseUp);
+ mapCanvas.removeEventListener("mousemove", onMouseMove);
+ mapCanvas.addEventListener("mousemove", onMouseMove);
+
+ // Do an initial draw.
+ draw();
+}
+
+export function draw() {
+ if (!(mapCanvas && railSystem && railSystem.components)) {
+ console.warn("Attempted to draw map without canvas or railSystem.");
+ return;
+ }
+ const ctx = mapCanvas.getContext("2d");
+ const width = mapCanvas.width;
+ const height = mapCanvas.height;
+ ctx.resetTransform();
+ ctx.fillStyle = `rgb(240, 240, 240)`;
+ ctx.fillRect(0, 0, width, height);
+ const worldTx = getWorldTransform();
+ ctx.setTransform(worldTx);
+
+ for (let i = 0; i < railSystem.components.length; i++) {
+ drawComponent(ctx, worldTx, railSystem.components[i]);
+ }
+
+ // Draw debug info.
+ ctx.resetTransform();
+ ctx.fillStyle = "black";
+ ctx.strokeStyle = "black";
+ ctx.font = "10px Sans-Serif";
+ const lastWorldPoint = mapPointToWorld(lastMousePoint);
+ const lines = [
+ "Scale factor: " + getScaleFactor(),
+ `(x = ${lastWorldPoint.x.toFixed(2)}, y = ${lastWorldPoint.y.toFixed(2)}, z = ${lastWorldPoint.z.toFixed(2)})`,
+ `Components: ${railSystem.components.length}`,
+ `Hovered elements: ${hoveredElements.length}`
+ ]
+ for (let i = 0; i < lines.length; i++) {
+ ctx.fillText(lines[i], 10, 20 + (i * 15));
+ }
+}
+
+function drawComponent(ctx, worldTx, component) {
+ const tx = DOMMatrix.fromMatrix(worldTx);
+ tx.translateSelf(component.position.x, component.position.z, 0);
+ const s = getScaleFactor();
+ tx.scaleSelf(1/s, 1/s, 1/s);
+ tx.scaleSelf(5, 5, 5);
+ ctx.setTransform(tx);
+ if (isComponentHovered(component)) {
+ ctx.fillStyle = `rgba(255, 255, 0, 32)`;
+ ctx.beginPath();
+ ctx.ellipse(0, 0, 1.8, 1.8, 0, 0, Math.PI * 2);
+ ctx.fill();
+ }
+ if (component.type === "SIGNAL") {
+ drawSignal(ctx, component);
+ } else if (component.type === "SEGMENT_BOUNDARY") {
+ drawSegmentBoundary(ctx, component);
+ }
+}
+
+function drawSignal(ctx, signal) {
+ roundedRect(ctx, -0.7, -1, 1.4, 2, 0.25);
+ ctx.fillStyle = "black";
+ ctx.fill();
+
+ // ctx.fillStyle = "green";
+ // ctx.beginPath();
+ // ctx.ellipse(0, 0, 0.8, 0.8, 0, 0, Math.PI * 2);
+ // ctx.fill();
+ //
+ // ctx.strokeStyle = "black";
+ // ctx.lineWidth = 0.5;
+ // ctx.beginPath();
+ // ctx.ellipse(0, 0, 1, 1, 0, 0, Math.PI * 2);
+ // ctx.stroke();
+}
+
+function drawSegmentBoundary(ctx, segmentBoundary) {
+ ctx.fillStyle = "blue";
+ ctx.beginPath();
+ ctx.ellipse(0, 0, 1, 1, 0, 0, Math.PI * 2);
+ ctx.fill();
+}
+
+export function getScaleFactor() {
+ return SCALE_VALUES[mapScaleIndex];
+}
+
+function getWorldTransform() {
+ const canvasRect = mapCanvas.getBoundingClientRect();
+ const scale = getScaleFactor();
+ const tx = new DOMMatrix();
+ tx.translateSelf(canvasRect.width / 2, canvasRect.height / 2, 0);
+ tx.scaleSelf(scale, scale, scale);
+ tx.translateSelf(mapTranslation.x, mapTranslation.y, 0);
+ if (mapDragOrigin !== null && mapDragTranslation !== null) {
+ tx.translateSelf(mapDragTranslation.x, mapDragTranslation.y, 0);
+ }
+ return tx;
+}
+
+function isComponentHovered(component) {
+ for (let i = 0; i < hoveredElements.length; i++) {
+ if (hoveredElements[i].id === component.id) return true;
+ }
+ return false;
+}
+
+/**
+ * Maps a point on the map coordinates to world coordinates.
+ * @param {DOMPoint} p
+ * @returns {DOMPoint}
+ */
+function mapPointToWorld(p) {
+ return getWorldTransform().invertSelf().transformPoint(p);
+}
+
+/**
+ * Maps a point in the world to map coordinates.
+ * @param {DOMPoint} p
+ * @returns {DOMPoint}
+ */
+function worldPointToMap(p) {
+ return getWorldTransform().transformPoint(p);
+}
+
+function roundedRect(ctx, x, y, w, h, r) {
+ if (w < 2 * r) r = w / 2;
+ if (h < 2 * r) r = h / 2;
+ ctx.beginPath();
+ ctx.moveTo(x+r, y);
+ ctx.arcTo(x+w, y, x+w, y+h, r);
+ ctx.arcTo(x+w, y+h, x, y+h, r);
+ ctx.arcTo(x, y+h, x, y, r);
+ ctx.arcTo(x, y, x+w, y, r);
+ ctx.closePath();
+}
+
+/*
+EVENT HANDLING
+*/
+
+/**
+ * @param {WheelEvent} event
+ */
+function onMouseWheel(event) {
+ const s = event.deltaY;
+ if (s > 0) {
+ mapScaleIndex = Math.max(0, mapScaleIndex - 1);
+ } else if (s < 0) {
+ mapScaleIndex = Math.min(SCALE_VALUES.length - 1, mapScaleIndex + 1);
+ }
+ draw();
+ event.stopPropagation();
+}
+
+/**
+ * @param {MouseEvent} event
+ */
+function onMouseDown(event) {
+ const p = getMousePoint(event);
+ mapDragOrigin = {x: p.x, y: p.y};
+}
+
+function onMouseUp() {
+ if (mapDragTranslation !== null) {
+ mapTranslation.x += mapDragTranslation.x;
+ mapTranslation.y += mapDragTranslation.y;
+ }
+ if (hoveredElements.length === 1) {
+ railSystem.selectedComponent = hoveredElements[0];
+ } else {
+ railSystem.selectedComponent = null;
+ }
+ mapDragOrigin = null;
+ mapDragTranslation = null;
+}
+
+/**
+ * @param {MouseEvent} event
+ */
+function onMouseMove(event) {
+ const p = getMousePoint(event);
+ lastMousePoint = p;
+ if (mapDragOrigin !== null) {
+ const scale = getScaleFactor();
+ const dx = p.x - mapDragOrigin.x;
+ const dy = p.y - mapDragOrigin.y;
+ mapDragTranslation = {x: dx / scale, y: dy / scale};
+ } else {
+ hoveredElements.length = 0;
+ // Populate with list of hovered elements.
+ for (let i = 0; i < railSystem.components.length; i++) {
+ const c = railSystem.components[i];
+ const componentPoint = new DOMPoint(c.position.x, c.position.z, 0, 1);
+ const mapComponentPoint = worldPointToMap(componentPoint);
+ const dist2 = (p.x - mapComponentPoint.x) * (p.x - mapComponentPoint.x) + (p.y - mapComponentPoint.y) * (p.y - mapComponentPoint.y);
+ if (dist2 < HOVER_RADIUS * HOVER_RADIUS) {
+ hoveredElements.push(c);
+ }
+ }
+ }
+ draw();
+}
+
+/**
+ * Gets the point at which the user clicked on the map.
+ * @param {MouseEvent} event
+ * @returns {DOMPoint}
+ */
+function getMousePoint(event) {
+ const rect = mapCanvas.getBoundingClientRect();
+ const x = event.clientX - rect.left;
+ const y = event.clientY - rect.top;
+ return new DOMPoint(x, y, 0, 1);
+}
diff --git a/railsignal-app/src/stores/railSystemsStore.js b/railsignal-app/src/stores/railSystemsStore.js
index d9747b1..ac9e411 100644
--- a/railsignal-app/src/stores/railSystemsStore.js
+++ b/railsignal-app/src/stores/railSystemsStore.js
@@ -4,13 +4,16 @@ import axios from "axios";
export const useRailSystemsStore = defineStore('RailSystemsStore', {
state: () => ({
railSystems: [],
+ /**
+ * @type {{segments: [Object], components: [Object], selectedComponent: Object} | null}
+ */
selectedRailSystem: null,
- selectedComponent: null
+ apiUrl: import.meta.env.VITE_API_URL
}),
actions: {
refreshRailSystems() {
return new Promise((resolve, reject) => {
- axios.get(import.meta.env.VITE_API_URL + "/rs")
+ axios.get(this.apiUrl + "/rs")
.then(response => {
this.railSystems = response.data;
resolve();
@@ -22,10 +25,7 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
},
createRailSystem(name) {
return new Promise((resolve, reject) => {
- axios.post(
- import.meta.env.VITE_API_URL + "/rs",
- {name: name}
- )
+ axios.post(this.apiUrl + "/rs", {name: name})
.then(() => {
this.refreshRailSystems()
.then(() => resolve())
@@ -36,13 +36,69 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
},
removeRailSystem(rs) {
return new Promise((resolve, reject) => {
- axios.delete(import.meta.env.VITE_API_URL + "/rs/" + rs.id)
+ axios.delete(this.apiUrl + "/rs/" + rs.id)
.then(() => {
+ this.selectedRailSystem = null;
this.refreshRailSystems()
.then(() => resolve)
.catch(error => reject(error));
})
})
+ },
+ refreshSegments(rs) {
+ return new Promise(resolve => {
+ axios.get(`${this.apiUrl}/rs/${rs.id}/s`)
+ .then(response => {
+ rs.segments = response.data;
+ resolve();
+ });
+ });
+ },
+ refreshComponents(rs) {
+ return new Promise(resolve => {
+ axios.get(`${this.apiUrl}/rs/${rs.id}/c`)
+ .then(response => {
+ rs.components = response.data;
+ resolve();
+ });
+ });
+ },
+ fetchSelectedRailSystemData() {
+ if (!this.selectedRailSystem) return;
+ this.refreshSegments(this.selectedRailSystem);
+ this.refreshComponents(this.selectedRailSystem);
+ },
+ addSegment(name) {
+ const rs = this.selectedRailSystem;
+ axios.post(`${this.apiUrl}/rs/${rs.id}/s`, {name: name})
+ .then(() => this.refreshSegments(rs))
+ .catch(error => console.log(error));
+ },
+ removeSegment(id) {
+ const rs = this.selectedRailSystem;
+ axios.delete(`${this.apiUrl}/rs/${rs.id}/s/${id}`)
+ .then(() => this.refreshSegments(rs))
+ .catch(error => console.log(error));
+ },
+ addComponent(data) {
+ const rs = this.selectedRailSystem;
+ axios.post(`${this.apiUrl}/rs/${rs.id}/c`, data)
+ .then(() => this.refreshComponents(rs))
+ .catch(error => console.log(error));
+ },
+ removeComponent(id) {
+ const rs = this.selectedRailSystem;
+ axios.delete(`${this.apiUrl}/rs/${rs.id}/c/${id}`)
+ .then(() => this.refreshComponents(rs))
+ .catch(error => console.log(error));
+ },
+ fetchComponentData(component) {
+ return new Promise(resolve => {
+ const rs = this.selectedRailSystem;
+ axios.get(`${this.apiUrl}/rs/${rs.id}/c/${component.id}`)
+ .then(response => resolve(response.data))
+ .catch(error => console.log(error));
+ });
}
}
});
\ No newline at end of file
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/ComponentsApiController.java b/src/main/java/nl/andrewl/railsignalapi/rest/ComponentsApiController.java
index 1a83b3c..67b2763 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/ComponentsApiController.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/ComponentsApiController.java
@@ -2,8 +2,8 @@ package nl.andrewl.railsignalapi.rest;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
-import nl.andrewl.railsignalapi.rest.dto.component.ComponentResponse;
-import nl.andrewl.railsignalapi.rest.dto.component.SimpleComponentResponse;
+import nl.andrewl.railsignalapi.rest.dto.PathNodeUpdatePayload;
+import nl.andrewl.railsignalapi.rest.dto.component.out.ComponentResponse;
import nl.andrewl.railsignalapi.service.ComponentService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@@ -17,7 +17,7 @@ public class ComponentsApiController {
private final ComponentService componentService;
@GetMapping
- public List getAllComponents(@PathVariable long rsId) {
+ public List getAllComponents(@PathVariable long rsId) {
return componentService.getComponents(rsId);
}
@@ -36,4 +36,9 @@ public class ComponentsApiController {
componentService.removeComponent(rsId, cId);
return ResponseEntity.noContent().build();
}
+
+ @PatchMapping(path = "/{cId}/connectedNodes")
+ public ComponentResponse updateConnectedNodes(@PathVariable long rsId, @PathVariable long cId, @RequestBody PathNodeUpdatePayload payload) {
+ return componentService.updatePath(rsId, cId, payload);
+ }
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/SegmentsApiController.java b/src/main/java/nl/andrewl/railsignalapi/rest/SegmentsApiController.java
index 80418a1..46f7c40 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/SegmentsApiController.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/SegmentsApiController.java
@@ -2,8 +2,10 @@ package nl.andrewl.railsignalapi.rest;
import lombok.RequiredArgsConstructor;
import nl.andrewl.railsignalapi.rest.dto.FullSegmentResponse;
+import nl.andrewl.railsignalapi.rest.dto.SegmentPayload;
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
import nl.andrewl.railsignalapi.service.SegmentService;
+import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -28,4 +30,10 @@ public class SegmentsApiController {
public FullSegmentResponse createSegment(@PathVariable long rsId, @RequestBody SegmentPayload payload) {
return segmentService.create(rsId, payload);
}
+
+ @DeleteMapping(path = "/{sId}")
+ public ResponseEntity removeSegment(@PathVariable long rsId, @PathVariable long sId) {
+ segmentService.remove(rsId, sId);
+ return ResponseEntity.noContent().build();
+ }
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/FullSegmentResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/FullSegmentResponse.java
index ae8cbde..d23c336 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/FullSegmentResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/FullSegmentResponse.java
@@ -1,8 +1,8 @@
package nl.andrewl.railsignalapi.rest.dto;
import nl.andrewl.railsignalapi.model.Segment;
-import nl.andrewl.railsignalapi.rest.dto.component.SegmentBoundaryNodeResponse;
-import nl.andrewl.railsignalapi.rest.dto.component.SignalResponse;
+import nl.andrewl.railsignalapi.rest.dto.component.out.SegmentBoundaryNodeResponse;
+import nl.andrewl.railsignalapi.rest.dto.component.out.SignalResponse;
import java.util.List;
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/PathNodeUpdatePayload.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/PathNodeUpdatePayload.java
new file mode 100644
index 0000000..9afbb29
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/PathNodeUpdatePayload.java
@@ -0,0 +1,5 @@
+package nl.andrewl.railsignalapi.rest.dto;
+
+public record PathNodeUpdatePayload (
+ long[] connectedNodeIds
+) {}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/SegmentPayload.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/SegmentPayload.java
similarity index 52%
rename from src/main/java/nl/andrewl/railsignalapi/rest/SegmentPayload.java
rename to src/main/java/nl/andrewl/railsignalapi/rest/dto/SegmentPayload.java
index 9bda7f6..ea99db5 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/SegmentPayload.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/SegmentPayload.java
@@ -1,4 +1,4 @@
-package nl.andrewl.railsignalapi.rest;
+package nl.andrewl.railsignalapi.rest.dto;
public record SegmentPayload(String name) {
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/in/ComponentPayload.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/in/ComponentPayload.java
new file mode 100644
index 0000000..a9066b7
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/in/ComponentPayload.java
@@ -0,0 +1,9 @@
+package nl.andrewl.railsignalapi.rest.dto.component.in;
+
+import nl.andrewl.railsignalapi.model.component.Position;
+
+public abstract class ComponentPayload {
+ public String name;
+ public String type;
+ public Position position;
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/in/SignalPayload.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/in/SignalPayload.java
new file mode 100644
index 0000000..c29cbf2
--- /dev/null
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/in/SignalPayload.java
@@ -0,0 +1,5 @@
+package nl.andrewl.railsignalapi.rest.dto.component.in;
+
+public class SignalPayload extends ComponentPayload {
+ public long segmentId;
+}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/ComponentResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/ComponentResponse.java
similarity index 92%
rename from src/main/java/nl/andrewl/railsignalapi/rest/dto/component/ComponentResponse.java
rename to src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/ComponentResponse.java
index 7fd74f7..2d78269 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/ComponentResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/ComponentResponse.java
@@ -1,4 +1,4 @@
-package nl.andrewl.railsignalapi.rest.dto.component;
+package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.*;
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/PathNodeResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/PathNodeResponse.java
similarity index 86%
rename from src/main/java/nl/andrewl/railsignalapi/rest/dto/component/PathNodeResponse.java
rename to src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/PathNodeResponse.java
index 1c83196..3d2bfee 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/PathNodeResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/PathNodeResponse.java
@@ -1,4 +1,4 @@
-package nl.andrewl.railsignalapi.rest.dto.component;
+package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.PathNode;
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SegmentBoundaryNodeResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SegmentBoundaryNodeResponse.java
similarity index 88%
rename from src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SegmentBoundaryNodeResponse.java
rename to src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SegmentBoundaryNodeResponse.java
index ba09712..1af60e4 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SegmentBoundaryNodeResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SegmentBoundaryNodeResponse.java
@@ -1,4 +1,4 @@
-package nl.andrewl.railsignalapi.rest.dto.component;
+package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.SegmentBoundaryNode;
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SignalResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SignalResponse.java
similarity index 84%
rename from src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SignalResponse.java
rename to src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SignalResponse.java
index 56dd8d5..f5c6dab 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SignalResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SignalResponse.java
@@ -1,4 +1,4 @@
-package nl.andrewl.railsignalapi.rest.dto.component;
+package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.Signal;
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SimpleComponentResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SimpleComponentResponse.java
similarity index 87%
rename from src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SimpleComponentResponse.java
rename to src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SimpleComponentResponse.java
index 6b6a9da..c26e66f 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SimpleComponentResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SimpleComponentResponse.java
@@ -1,4 +1,4 @@
-package nl.andrewl.railsignalapi.rest.dto.component;
+package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.Component;
import nl.andrewl.railsignalapi.model.component.Position;
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchConfigurationResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchConfigurationResponse.java
similarity index 86%
rename from src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchConfigurationResponse.java
rename to src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchConfigurationResponse.java
index f65a5b6..eab8df2 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchConfigurationResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchConfigurationResponse.java
@@ -1,4 +1,4 @@
-package nl.andrewl.railsignalapi.rest.dto.component;
+package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.SwitchConfiguration;
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchResponse.java
similarity index 90%
rename from src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchResponse.java
rename to src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchResponse.java
index b854bc0..efb04a1 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/SwitchResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchResponse.java
@@ -1,4 +1,4 @@
-package nl.andrewl.railsignalapi.rest.dto.component;
+package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.Switch;
diff --git a/src/main/java/nl/andrewl/railsignalapi/service/ComponentService.java b/src/main/java/nl/andrewl/railsignalapi/service/ComponentService.java
index c39803c..6d4ba3a 100644
--- a/src/main/java/nl/andrewl/railsignalapi/service/ComponentService.java
+++ b/src/main/java/nl/andrewl/railsignalapi/service/ComponentService.java
@@ -11,8 +11,8 @@ import nl.andrewl.railsignalapi.dao.SwitchConfigurationRepository;
import nl.andrewl.railsignalapi.model.RailSystem;
import nl.andrewl.railsignalapi.model.Segment;
import nl.andrewl.railsignalapi.model.component.*;
-import nl.andrewl.railsignalapi.rest.dto.component.ComponentResponse;
-import nl.andrewl.railsignalapi.rest.dto.component.SimpleComponentResponse;
+import nl.andrewl.railsignalapi.rest.dto.PathNodeUpdatePayload;
+import nl.andrewl.railsignalapi.rest.dto.component.out.ComponentResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -31,10 +31,10 @@ public class ComponentService {
private final SwitchConfigurationRepository switchConfigurationRepository;
@Transactional(readOnly = true)
- public List getComponents(long rsId) {
+ public List getComponents(long rsId) {
var rs = railSystemRepository.findById(rsId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
- return componentRepository.findAllByRailSystem(rs).stream().map(SimpleComponentResponse::new).toList();
+ return componentRepository.findAllByRailSystem(rs).stream().map(ComponentResponse::of).toList();
}
@Transactional(readOnly = true)
@@ -123,7 +123,22 @@ public class ComponentService {
}
@Transactional
- public void remove(long rsId, long componentId) {
-
+ public ComponentResponse updatePath(long rsId, long cId, PathNodeUpdatePayload payload) {
+ var c = componentRepository.findByIdAndRailSystemId(cId, rsId)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
+ if (!(c instanceof PathNode p)) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component is not a PathNode.");
+ Set newNodes = new HashSet<>();
+ for (var id : payload.connectedNodeIds()) {
+ var c1 = componentRepository.findByIdAndRailSystemId(id, rsId);
+ if (c1.isPresent() && c1.get() instanceof PathNode pn) {
+ newNodes.add(pn);
+ } else {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component with id " + id + " is not a PathNode in the same rail system.");
+ }
+ }
+ p.getConnectedNodes().retainAll(newNodes);
+ p.getConnectedNodes().addAll(newNodes);
+ p = componentRepository.save(p);
+ return ComponentResponse.of(p);
}
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java b/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java
index 3b7dfe7..fbad3a2 100644
--- a/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java
+++ b/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java
@@ -6,7 +6,7 @@ import nl.andrewl.railsignalapi.dao.RailSystemRepository;
import nl.andrewl.railsignalapi.dao.SegmentRepository;
import nl.andrewl.railsignalapi.model.Segment;
import nl.andrewl.railsignalapi.model.component.Component;
-import nl.andrewl.railsignalapi.rest.SegmentPayload;
+import nl.andrewl.railsignalapi.rest.dto.SegmentPayload;
import nl.andrewl.railsignalapi.rest.dto.FullSegmentResponse;
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
import org.springframework.http.HttpStatus;
@@ -53,5 +53,6 @@ public class SegmentService {
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
componentRepository.deleteAll(segment.getSignals());
componentRepository.deleteAll(segment.getBoundaryNodes());
+ segmentRepository.delete(segment);
}
}
From a02758ecd4e6baad2cd0aa7e74ec618b5fec277b Mon Sep 17 00:00:00 2001
From: Andrew Lalis
Date: Sun, 8 May 2022 01:37:25 +0200
Subject: [PATCH 4/7] Added proper connection management.
---
railsignal-app/src/components/RailSystem.vue | 2 +-
.../src/components/railsystem/canvasUtils.js | 16 ++++
.../railsystem/component/ComponentView.vue | 8 +-
.../component/PathNodeComponentView.vue | 48 ++++++++++
.../src/components/railsystem/drawing.js | 87 +++++++++++++++++++
.../src/components/railsystem/mapRenderer.js | 68 +++------------
railsignal-app/src/stores/railSystemsStore.js | 54 +++++++++++-
.../model/component/PathNode.java | 7 +-
.../rest/dto/PathNodeUpdatePayload.java | 11 ++-
.../service/ComponentService.java | 22 ++++-
10 files changed, 248 insertions(+), 75 deletions(-)
create mode 100644 railsignal-app/src/components/railsystem/canvasUtils.js
create mode 100644 railsignal-app/src/components/railsystem/drawing.js
diff --git a/railsignal-app/src/components/RailSystem.vue b/railsignal-app/src/components/RailSystem.vue
index 079b94e..11a5da4 100644
--- a/railsignal-app/src/components/RailSystem.vue
+++ b/railsignal-app/src/components/RailSystem.vue
@@ -2,7 +2,7 @@
{{railSystem.name}}
-
+
diff --git a/railsignal-app/src/components/railsystem/canvasUtils.js b/railsignal-app/src/components/railsystem/canvasUtils.js
new file mode 100644
index 0000000..726b394
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/canvasUtils.js
@@ -0,0 +1,16 @@
+export function roundedRect(ctx, x, y, w, h, r) {
+ if (w < 2 * r) r = w / 2;
+ if (h < 2 * r) r = h / 2;
+ ctx.beginPath();
+ ctx.moveTo(x+r, y);
+ ctx.arcTo(x+w, y, x+w, y+h, r);
+ ctx.arcTo(x+w, y+h, x, y+h, r);
+ ctx.arcTo(x, y+h, x, y, r);
+ ctx.arcTo(x, y, x+w, y, r);
+ ctx.closePath();
+}
+
+export function circle(ctx, x, y, r) {
+ ctx.beginPath();
+ ctx.arc(x, y, r, 0, Math.PI * 2);
+}
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/ComponentView.vue b/railsignal-app/src/components/railsystem/component/ComponentView.vue
index 5332df1..d6fc2ed 100644
--- a/railsignal-app/src/components/railsystem/component/ComponentView.vue
+++ b/railsignal-app/src/components/railsystem/component/ComponentView.vue
@@ -15,16 +15,16 @@
-
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/ConfirmModal.vue b/railsignal-app/src/components/ConfirmModal.vue
new file mode 100644
index 0000000..9df94b8
--- /dev/null
+++ b/railsignal-app/src/components/ConfirmModal.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
{{title}}
+
+
+
{{message}}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/RailSystem.vue b/railsignal-app/src/components/RailSystem.vue
index 11a5da4..a7870e8 100644
--- a/railsignal-app/src/components/RailSystem.vue
+++ b/railsignal-app/src/components/RailSystem.vue
@@ -1,26 +1,25 @@
-
{{railSystem.name}}
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/AddRailSystemModal.vue b/railsignal-app/src/components/railsystem/AddRailSystemModal.vue
new file mode 100644
index 0000000..d5e93fc
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/AddRailSystemModal.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
Add New Rail System
+
+
+
+
+
+
+ {{msg}}
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/AddSegment.vue b/railsignal-app/src/components/railsystem/AddSegment.vue
deleted file mode 100644
index f3c3aa1..0000000
--- a/railsignal-app/src/components/railsystem/AddSegment.vue
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
Add Segment
-
-
-
-
-
-
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/AddSegmentModal.vue b/railsignal-app/src/components/railsystem/AddSegmentModal.vue
new file mode 100644
index 0000000..2133137
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/AddSegmentModal.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+
Add Segment
+
+
+
+
+ Add a new segment to this rail system. A segment is the
+ basic organizational unit of any rail system. It is a section of
+ the network that signals can monitor, and segment boundary nodes
+ define the extent of the segment, and monitor trains entering and
+ leaving the segment.
+
+
+ You can think of a segment as a single, secure block of of the rail
+ network that only one train may pass through at once. For example,
+ a junction or station siding.
+
+
+
+
+ {{msg}}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/MapView.vue b/railsignal-app/src/components/railsystem/MapView.vue
index 8a61c5c..3ce9e1a 100644
--- a/railsignal-app/src/components/railsystem/MapView.vue
+++ b/railsignal-app/src/components/railsystem/MapView.vue
@@ -1,5 +1,5 @@
-
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/RailSystemPropertiesView.vue b/railsignal-app/src/components/railsystem/RailSystemPropertiesView.vue
new file mode 100644
index 0000000..29c1edd
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/RailSystemPropertiesView.vue
@@ -0,0 +1,73 @@
+
+
Rail System: {{railSystem.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/SegmentsView.vue b/railsignal-app/src/components/railsystem/SegmentsView.vue
index 65e5673..e8237c9 100644
--- a/railsignal-app/src/components/railsystem/SegmentsView.vue
+++ b/railsignal-app/src/components/railsystem/SegmentsView.vue
@@ -1,22 +1,22 @@
-
Segments
-
-
- {{segment.name}}
+
Segments
+
+
+ {{segment.name}}
+
-
-
-
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/AddSegmentBoundaryModal.vue b/railsignal-app/src/components/railsystem/component/AddSegmentBoundaryModal.vue
new file mode 100644
index 0000000..7b1add1
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/component/AddSegmentBoundaryModal.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
Add Segment Boundary
+
+
+
+
+ A segment boundary is a component that defines a link
+ between one segment and another. This component can be used to
+ monitor trains entering and exiting the connected segments. Usually
+ used in conjunction with signals for classic railway signalling
+ systems.
+
+
+
+
+ {{msg}}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/AddSignal.vue b/railsignal-app/src/components/railsystem/component/AddSignal.vue
deleted file mode 100644
index 6313c47..0000000
--- a/railsignal-app/src/components/railsystem/component/AddSignal.vue
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
Add Signal
-
-
-
-
-
-
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/AddSignalModal.vue b/railsignal-app/src/components/railsystem/component/AddSignalModal.vue
new file mode 100644
index 0000000..dcfe659
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/component/AddSignalModal.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
+
Add Signal
+
+
+
+
+ A signal is a component that relays information about your
+ rail system to in-world devices. Classically, rail signals show a
+ lamp indicator to tell information about the segment of the network
+ they're attached to.
+
+
+
+
+ {{msg}}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/AddSwitchModal.vue b/railsignal-app/src/components/railsystem/component/AddSwitchModal.vue
new file mode 100644
index 0000000..0a95194
--- /dev/null
+++ b/railsignal-app/src/components/railsystem/component/AddSwitchModal.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
Add Switch
+
+
+
+
+
+
+ {{msg}}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/ComponentView.vue b/railsignal-app/src/components/railsystem/component/ComponentView.vue
index d6fc2ed..d830c21 100644
--- a/railsignal-app/src/components/railsystem/component/ComponentView.vue
+++ b/railsignal-app/src/components/railsystem/component/ComponentView.vue
@@ -1,23 +1,42 @@
-
+
{{component.name}}
-
- Id: {{component.id}}
-
-
- Position: (x = {{component.position.x}}, y = {{component.position.y}}, z = {{component.position.z}})
-
-
- Type: {{component.type}}
-
-
- Online: {{component.online}}
-
+ {{component.type}}
+
+
+
+
Id
{{component.id}}
+
+
+
Position
+
+
+
+
+
X = {{component.position.x}}
+
Y = {{component.position.y}}
+
Z = {{component.position.z}}
+
+
+
+
+
+
+
Online
{{component.online}}
+
+
+
-
+
+
\ No newline at end of file
diff --git a/railsignal-app/src/components/railsystem/component/PathNodeComponentView.vue b/railsignal-app/src/components/railsystem/component/PathNodeComponentView.vue
index 7fa0f01..6be36af 100644
--- a/railsignal-app/src/components/railsystem/component/PathNodeComponentView.vue
+++ b/railsignal-app/src/components/railsystem/component/PathNodeComponentView.vue
@@ -1,22 +1,39 @@