diff --git a/README.md b/README.md
deleted file mode 100644
index a9e9d4fc..00000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# excalibur
-Created with CodeSandbox
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..d60ce44c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "react",
+ "version": "1.0.0",
+ "description": "",
+ "keywords": [],
+ "main": "src/index.js",
+ "dependencies": {
+ "react": "16.12.0",
+ "react-dom": "16.12.0",
+ "react-scripts": "3.0.1",
+ "roughjs": "3.1.0"
+ },
+ "devDependencies": {
+ "typescript": "3.3.3"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test --env=jsdom",
+ "eject": "react-scripts eject"
+ },
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
+}
\ No newline at end of file
diff --git a/public/FG_Virgil.ttf b/public/FG_Virgil.ttf
new file mode 100644
index 00000000..80001666
Binary files /dev/null and b/public/FG_Virgil.ttf differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 00000000..42ae2d2d
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+ React App
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 00000000..1444e77a
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,187 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import rough from "roughjs/dist/rough.umd.js";
+
+import "./styles.css";
+
+var elements = [];
+
+function newElement(type, x, y) {
+ const element = {
+ type: type,
+ x: x,
+ y: y,
+ width: 0,
+ height: 0
+ };
+ generateShape(element);
+ return element;
+}
+
+function rotate(x1, y1, x2, y2, angle) {
+ // πβ²π₯=(ππ₯βππ₯)cosπβ(ππ¦βππ¦)sinπ+ππ₯
+ // πβ²π¦=(ππ₯βππ₯)sinπ+(ππ¦βππ¦)cosπ+ππ¦.
+ // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
+ return [
+ (x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2,
+ (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2
+ ];
+}
+
+var generator = rough.generator();
+
+function generateShape(element) {
+ if (element.type === "selection") {
+ element.draw = (rc, context) => {
+ context.fillStyle = "rgba(0, 0, 255, 0.10)";
+ context.fillRect(element.x, element.y, element.width, element.height);
+ };
+ } else if (element.type === "rectangle") {
+ const shape = generator.rectangle(
+ element.x,
+ element.y,
+ element.width,
+ element.height
+ );
+ element.draw = (rc, context) => {
+ rc.draw(shape);
+ };
+ } else if (element.type === "ellipse") {
+ const shape = generator.ellipse(
+ element.x + element.width / 2,
+ element.y + element.height / 2,
+ element.width,
+ element.height
+ );
+ element.draw = (rc, context) => {
+ rc.draw(shape);
+ };
+ } else if (element.type === "arrow") {
+ const x1 = element.x;
+ const y1 = element.y;
+ const x2 = element.x + element.width;
+ const y2 = element.y + element.height;
+
+ const size = 30; // pixels
+ const distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
+ // Scale down the arrow until we hit a certain size so that it doesn't look weird
+ const minSize = Math.min(size, distance / 2);
+ const xs = x2 - ((x2 - x1) / distance) * minSize;
+ const ys = y2 - ((y2 - y1) / distance) * minSize;
+
+ const angle = 20; // degrees
+ const [x3, y3] = rotate(xs, ys, x2, y2, (-angle * Math.PI) / 180);
+ const [x4, y4] = rotate(xs, ys, x2, y2, (angle * Math.PI) / 180);
+
+ const shapes = [
+ generator.line(x1, y1, x2, y2),
+ generator.line(x3, y3, x2, y2),
+ generator.line(x4, y4, x2, y2)
+ ];
+
+ element.draw = (rc, context) => {
+ shapes.forEach(shape => rc.draw(shape));
+ };
+ return;
+ } else if (element.type === "text") {
+ if (element.text === undefined) {
+ element.text = prompt("What text do you want?");
+ }
+ element.draw = (rc, context) => {
+ context.font = "20px Virgil";
+ const measure = context.measureText(element.text);
+ const height =
+ measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
+ context.fillText(
+ element.text,
+ element.x - measure.width / 2,
+ element.y + measure.actualBoundingBoxAscent - height / 2
+ );
+ };
+ } else {
+ throw new Error("Unimplemented type " + element.type);
+ }
+}
+
+function App() {
+ const [draggingElement, setDraggingElement] = React.useState(null);
+ const [elementType, setElementType] = React.useState("selection");
+ const [selectedElements, setSelectedElements] = React.useState([]);
+ function ElementOption({ type, children }) {
+ return (
+
+ );
+ }
+ return (
+
+ Rectangle
+ Ellipse
+ Arrow
+ Text
+ Selection
+
+ );
+}
+const rootElement = document.getElementById("root");
+
+function drawScene() {
+ ReactDOM.render(, rootElement);
+
+ const canvas = document.getElementById("canvas");
+ const rc = rough.canvas(canvas);
+ const context = canvas.getContext("2d");
+ context.clearRect(0, 0, canvas.width, canvas.height);
+
+ elements.forEach(element => {
+ element.draw(rc, context);
+ if (true || element.isSelected) {
+ const margin = 4;
+ context.setLineDash([8, 4]);
+ context.strokeRect(
+ element.x - margin,
+ element.y - margin,
+ element.width + margin * 2,
+ element.height + margin * 2
+ );
+ context.setLineDash([]);
+ }
+ });
+}
+
+drawScene();
diff --git a/src/styles.css b/src/styles.css
new file mode 100644
index 00000000..f96dc647
--- /dev/null
+++ b/src/styles.css
@@ -0,0 +1,5 @@
+/* http://www.eaglefonts.com/fg-virgil-ttf-131249.htm */
+@font-face {
+ font-family: "Virgil";
+ src: url("https://uploads.codesandbox.io/uploads/user/ed077012-e728-4a42-8395-cbd299149d62/AflB-FG_Virgil.ttf");
+}