<!doctype html>

<html>

<head>
	<link rel="stylesheet" href="./style.css">
	<script src="./index.js">

	</script>
</head>

<body>

  <div class="main">
	<div class="canvas">
		<svg width="500" height="500" viewbox="0 0 500 500" id="svg1">
			<defs>
				<pattern id="Pattern" x="0" y="0" width="25" height="25" patternUnits="userSpaceOnUse">
					<Line x1="0" y1="0" x2="25" y2="0" stroke="gray" stroke-width="1"></Line>
					<Line x1="0" y1="0" x2="0" y2="25" stroke="gray" stroke-width="1"></Line>
				</pattern>
			</defs>
      <rect id="gridContainer" width="500" height="500" fill="url(#Pattern)" />
			<path id="path" stroke="black" fill="none" stroke-width="2" d="" />
      <g id="guideContainer" class="guideContainer">
        <path id="helperPath" stroke-dasharray="2 5" stroke="brown" fill="none" stroke-width="2" d="" />
      </g>
      
		</svg>
	</div>
	<div class="control">
		<button id="btnClear">clear all</button>
    <button id="btnRemove">remove selected point</button>
    <br/>

    <span>
      <input id="radLine" type="radio" name="path" value="Line" checked/>
      <label for="radLine">Line</label>
    </span>
    <span>
      <input id="radQuad" type="radio" name="path" value="Quad"/>
      <label for="radQuad">Quadratic  Curve</label>
    </span>
    <span>
      <input id="radCube" type="radio" name="path" value="Cube"/> 
      <label for="radCube">Bezier Curve </label>
    </span>
    <!-- <span>
      <input id="radArc" type="radio" name="path" value="Arc"/> 
      <label for="radArc">Arc </label>
    </span> -->
    <br/>
    <span>
      <input id="showGuide" type="checkbox" checked/> 
      <label for="showGuide">Show Guides </label>
    </span>
    <span>
      <input id="showGrid" type="checkbox" checked/> 
      <label for="showGrid">Show Grid </label>
    </span>
    <br>
    <p>
      Click on the grid to add a new node
    </p>

    <p>
      Click on a node to delete
    </p>
    
    <p>
      Drag a node to modify
    </p>
</div>
</div>
<div class="pathValue">

<div id="div">
  
</div>
</div>

</body>

</html>
{
    "name": "@plnkr/starter-angular",
    "version": "1.0.3",
    "description": "Angular starter template",
    "dependencies": {
        "@angular/common": "^6.0.9",
        "@angular/compiler": "^6.0.9",
        "@angular/core": "^6.0.9",
        "@angular/platform-browser": "^6.0.9",
        "@angular/platform-browser-dynamic": "^6.0.9",
        "core-js": "^2.5.5",
        "rxjs": "^6.1.0",
        "zone.js": "^0.8.26"
    },
    "main": "./lib/main.ts",
    "plnkr": {
        "runtime": "system",
        "useHotReload": true
    }
}
* {
  box-sizing: content-box;
  padding: 0;
  margin: 0;
}

svg {
  border: solid 1px black;
  
}

.point {
  fill: #0000ff10;
  cursor: move;
}

.point.helperNode {
  fill: #ffff0035;
}

.selectedNode:not(.helperNode):not(.baseNode) {
  fill: green;
}

.main {
  display: flex;
  user-select: none;
}
.canvas {
  padding: 10px;
  height:500px;
  flex: 0 0 500px;
}
.control {
  padding: 10px;
  flex: 0 0 150px;
  flex-direction: column;
  display: flex;
}
.control > * {
  flex: 0 0 15px;
  margin: 4px;
}
.pathValue {
  clear: both;
}

p {
  color: orangered;
  font-size: 13px;
}

#div {
  padding: 1px 10px;
  font-size: 15px;
  color: navy;
}

button {
  border: solid 1px gray;
  outline: none;
  box-shadow: 2px 2px 2px black;
}

button:active {
  border: solid 1px gray;
  outline: none;
  box-shadow: 2px 2px 0px black;
}

.baseNode{
  fill: gray;
}

.hide{display: none;}
import { div, path, svg, btnClear, btnRemove, helperPath, guideContainer, showGuide, gridContainer, showGrid } from "./UIItems";

import { PointStore } from "./PointStore"
import { Point, Line, Quad, Cube, Factory } from "./Point"

PointStore.notify = () => {
  update();
}

showGuide.onclick = function (e) {
  if ((showGuide as HTMLInputElement).checked) {
    guideContainer.classList.remove("hide");
  } else {
    guideContainer.classList.add("hide");
  }
}

showGrid.onclick = function (e) {
  if ((showGrid as HTMLInputElement).checked) {
    gridContainer.classList.remove("hide");
  } else {
    gridContainer.classList.add("hide");
  }
}

svg.onclick = function (e: MouseEvent) {
  let offset = svg.getBoundingClientRect();

  let x = e.x - offset.left;
  let y = e.y - offset.top;

  if (!PointStore.getLastPoint()) {
    let p0 = new Point(svg, x, y);
    PointStore.addNewPoint(p0);
    let domNode = p0.getNode();
    domNode.classList.add("baseNode");
    guideContainer.appendChild(domNode);
    return;
  }

  let radios = document.getElementsByName('path');

  let p;
    for (var i = 0; i < radios.length; i++) {
    let rad: HTMLInputElement = radios[i] as HTMLInputElement;
    if (rad.checked) {
      p = Factory.createPath(rad.value, svg, x, y);
      break;
    }
  }

  PointStore.addNewPoint(p);
  guideContainer.appendChild(p.getNode());
  update();
}

btnClear.onclick = function () {
  let nodes = PointStore.removeAllNodes();
  nodes.forEach((x, i, k) => {
    guideContainer.removeChild(x);
  });
  update();
}

btnRemove.onclick = function () {
  let nodes = PointStore.removeSelectedNode();
  nodes.forEach((x, i, k) => {
    guideContainer.removeChild(x);
  });
  update();
}

function update() {
  let p = PointStore.getPath();
  div.innerText = p.path;
  path.setAttribute("d", p.path);
  helperPath.setAttribute("d", p.guide);
}


// Initialie -- Draw a heart
(function drawInitilPath() {
  let p = Factory.createPath("P", svg, 125, 175);
  PointStore.addNewPoint(p);
  let domNode = p.getNode();
  domNode.classList.add("baseNode");
  guideContainer.appendChild(domNode);

  p = Factory.createPath("C", svg, 250, 150);
  p.metaPoint[0].x = 100;
  p.metaPoint[0].y = 50;
  p.metaPoint[1].x = 250;
  p.metaPoint[1].y = 50;
  PointStore.addNewPoint(p);
  guideContainer.appendChild(p.getNode());

  p = Factory.createPath("C", svg, 375, 175);
  p.metaPoint[0].x = 250;
  p.metaPoint[0].y = 50;
  p.metaPoint[1].x = 400;
  p.metaPoint[1].y = 50;
  PointStore.addNewPoint(p);
  guideContainer.appendChild(p.getNode());

  p = Factory.createPath("C", svg, 250, 350);
  p.metaPoint[0].x = 350;
  p.metaPoint[0].y = 250;
  p.metaPoint[1].x = 275;
  p.metaPoint[1].y = 275;
  PointStore.addNewPoint(p);
  guideContainer.appendChild(p.getNode());

  p = Factory.createPath("C", svg, 125, 175);
  p.metaPoint[0].x = 225;
  p.metaPoint[0].y = 275;
  p.metaPoint[1].x = 150;
  p.metaPoint[1].y = 250;
  PointStore.addNewPoint(p);
  guideContainer.appendChild(p.getNode());

  update();
})()
let svg: any = document.getElementById("svg1");
let path = document.getElementById("path");
let div = document.getElementById("div");
let helperPath = document.getElementById("helperPath");

let btnClear = document.getElementById("btnClear");
let btnRemove = document.getElementById("btnRemove");
let guideContainer = document.getElementById("guideContainer");
let gridContainer = document.getElementById("gridContainer");
let showGuide = document.getElementById("showGuide");
let showGrid = document.getElementById("showGrid") as HTMLInputElement;


export { svg, path, div, btnRemove, btnClear, helperPath, guideContainer, showGuide, gridContainer ,showGrid }
import { PointStore } from "./PointStore"

export class Point {
  x = 0;
  y = 0;
  elm: SVGCircleElement;
  metaPoint: Point[] = [];
  type = "M"
  static points = [];
  container: SVGElement;
  guideContainer: SVGGElement;
  
  constructor(container: SVGElement, x, y) {
    this.container = container;
    this.guideContainer = container.querySelector('#guideContainer');

    this.x = x;
    this.y = y;

    this.elm = this.createPoint();

    this.elm.onclick = e => {
      e.stopPropagation();
      PointStore.points.forEach(i => {
        i.getNode().classList.remove("selectedNode");
      });
      PointStore.selectedPoint = this;
      this.elm.classList.add("selectedNode");
    };

    let mmH = e => {
      e.stopPropagation();
      let offset = container.getBoundingClientRect();
      let x = e.x - offset.left;
      let y = e.y - offset.top;
      if (e.which == 1) {
        this.update(x, y);
        PointStore.notify();
      }
    };

    let muH = e => {
      e.stopPropagation();
      container.removeEventListener("mousemove", mmH);
      container.removeEventListener("mouseup", muH);
    };

    this.elm.onmousedown = e => {
      e.stopPropagation();
      container.addEventListener("mousemove", mmH);
      container.addEventListener("mouseup", muH);
    };
  }
  createPoint() {
    let point = document.createElementNS("http://www.w3.org/2000/svg", "circle");
    point.setAttribute("stroke", "darkgray");
    point.setAttribute("class", "point");
    point.setAttribute("stroke-width", "2");
    point.setAttribute("fill", "transparent");
    point.setAttribute("r", "5");
    return point;
  }

  update(x, y) {
    this.x = x || this.x;
    this.y = y || this.y;

    this.elm.setAttribute("cx", this.x.toString());
    this.elm.setAttribute("cy", this.y.toString());
  }

  getNode() {
    this.update(this.x, this.y);
    return this.elm;
  }

  getPath() {
    return { path: `M${this.x},${this.y}`, guide: '' };
  }
}

export class Line extends Point {
  constructor(container, x, y) {
    super(container, x, y);
    this.type = "l"
  }

  getPath() {
    let pp = PointStore.getPreciousPoint(this);
    let path = `l${this.x - pp.x},${this.y - pp.y}`;
    return { path, guide: '' };
  }
}

export class Quad extends Point {
  sx = 0;
  sy = 0;
  constructor(container: SVGElement, x, y) {
    super(container, x, y);
    let lp = PointStore.getLastPoint();
    this.sx = (lp.x + x) / 2;
    this.sy = 25 + (lp.y + y) / 2;
    this.type = "q"
    let meta = new Point(this.container, this.sx, this.sy)
    this.metaPoint.push(meta);
    let meta1Node = meta.getNode();
    meta1Node.classList.add('helperNode');
    this.guideContainer.appendChild(meta1Node);
  }

  update(x, y) {
    super.update(x, y);
    let q = this.metaPoint[0];
    q && q.update(q.x, q.y);
  }

  getPath() {
    let q = this.metaPoint[0];
    let pp = PointStore.getPreciousPoint(this);
    let xOff = pp.x;
    let yOff = pp.y;

    let path = `q${q.x - xOff},${q.y - yOff} ${this.x - xOff},${this.y - yOff}`;
    let guide = `M${pp.x},${pp.y} L${q.x},${q.y} L${this.x},${this.y}`;

    return {
      path,
      guide
    };
  }
}

export class Cube extends Point {
  sxa = 0;
  sya = 0;
  sxb = 0;
  syb = 0;
  constructor(container: SVGElement, x, y) {
    super(container, x, y);
    let lp = PointStore.getLastPoint();
    this.sxa = -5 + (lp.x + x) / 2;
    this.sya = 25 + (lp.y + y) / 2;
    this.sxb = 5 + (lp.x + x) / 2;
    this.syb = -25 + (lp.y + y) / 2;
    this.type = "c"
    let meta1 = new Point(this.container, this.sxa, this.sya);
    let meta2 = new Point(this.container, this.sxb, this.syb);

    this.metaPoint.push(meta1);
    let meta1Node = meta1.getNode();
    this.guideContainer.appendChild(meta1Node);
    meta1Node.classList.add('helperNode');
    let meta2Node = meta2.getNode();
    this.metaPoint.push(meta2);
    this.guideContainer.appendChild(meta2Node);
    meta2Node.classList.add('helperNode');
  }

  update(x, y) {
    super.update(x, y);
    let q1 = this.metaPoint[0];
    let q2 = this.metaPoint[1];
    q1 && q1.update(q1.x, q1.y);
    q2 && q2.update(q2.x, q2.y);
  }

  getPath() {

    let p1 = this.metaPoint[0];
    let p2 = this.metaPoint[1];

    let pp = PointStore.getPreciousPoint(this);
    let xOff = pp.x;
    let yOff = pp.y;


    let path = `c${p1.x - xOff},${p1.y - yOff} ${p2.x - xOff},${p2.y - yOff} ${this.x - xOff},${this.y - yOff}`;
    let guide = `M${p1.x},${p1.y} L${pp.x},${pp.y}` + `M${p2.x},${p2.y} L${this.x},${this.y}`;

    return {
      path,
      guide
    };

  }
}

export class Arc extends Point {
  rx = 0;
  ry = 0;

  constructor(container: SVGElement, x, y) {
    super(container, x, y);
    let lp = PointStore.getLastPoint();
    this.rx = (lp.x + x) / 2;
    this.ry = (lp.y + y) / 2;
    this.type = "a"
    let meta1 = new Point(this.container, this.rx, this.ry);
    this.metaPoint.push(meta1);
    let meta1Node = meta1.getNode();
    this.guideContainer.appendChild(meta1Node);
    meta1Node.classList.add('helperNode');
  }

  update(x, y) {
    super.update(x, y);
    let q1 = this.metaPoint[0];
    q1 && q1.update(q1.x, q1.y);
  }

  getPath() {

    let p1 = this.metaPoint[0];

    let pp = PointStore.getPreciousPoint(this);
    let xOff = pp.x;
    let yOff = pp.y;

    let x = (p1.x) / 2
    let y = (p1.y) / 2

    let path = `A${x},${y} 0 1 1 ${this.x - xOff},${this.y - yOff}`;
    return {
      path,
      guide: ''
    };

  }
}

export class Factory {
  static createPath(pathName, container, x, y) {
    switch (pathName) {
      case "Point":
      case "P":
        return new Point(container, x, y);
      case "Line":
      case "L":
        return new Line(container, x, y);
        break;
      case "Cube":
      case "C":
        return new Cube(container, x, y);
        break;
      case "Quad":
      case "Q":
        return new Quad(container, x, y);
        break;
      case "Arc":
      case "A":
        return new Arc(container, x, y);
        break;
    }
  }
}
import { Point } from "./Point"

class PS {
  points: Point[] = [];
  guidePoints = [];
  selectedPoint: Point;
  notify() { }

  addNewPoint(point: Point) {
    this.points.push(point);
  }

  getPath() {
    let path = this.points.reduce((a, b, index) => {
      return [...a, b.getPath().path];
    }, []);

    let guide = this.points.reduce((a, b, index) => {
      return [...a, b.getPath().guide];
    }, []);
    return { path: path.join(" "), guide: guide.join(" ") };
  }

  removeSelectedNode() {

    let i = this.points.indexOf(this.selectedPoint);
    let removedNodes: SVGCircleElement[] = [];
    if (i > 0) {
      
      for (let sn of this.selectedPoint.metaPoint) {
        removedNodes.push(sn.getNode());
      }
      removedNodes.push(this.selectedPoint.getNode());
      this.selectedPoint.metaPoint.length = 0;

      this.points.splice(i, 1);
    }
    return removedNodes;
  }

  removeAllNodes() {
    let removedNodes: SVGCircleElement[] = [];
    for (let i = this.points.length - 1; i >= 0; i--) {
      let cr = this.points[i];
      removedNodes.push(cr.getNode());
      for (let sn of cr.metaPoint) {
        removedNodes.push(sn.getNode());
      }
      cr.metaPoint.length = 0;
      this.points.splice(i, 1);
    }
    this.points.length = 0;
    return removedNodes;
  }

  getPreciousPoint(point: Point) {
    let index = this.points.indexOf(point);
    return this.points[index - 1];
  }
  
  getLastPoint() {
    let index = this.points.length - 1;
    return this.points[index];
  }
}

let PointStore = new PS();

export { PointStore }