Browse Source

Updated project to use es6 classes, arrow functions, latest closure-compiler, latest jquery (#77)

* Update jquer and closure compiler

* Move to es6 classes, arrow functions, for/of

* Move to es6 style modules

* Add .DS_Store to .gitignore

* Remove note to update compile.sh
master
Brian Schlenker 2 years ago
parent
commit
141b8eff92
19 changed files with 4739 additions and 2622 deletions
  1. 1
    0
      .gitignore
  2. 0
    1
      README
  3. BIN
      closure-compiler.jar
  4. 3
    11
      compile.sh
  5. 4
    4
      index.html
  6. 2045
    0
      jquery-3.1-externs.js
  7. 4
    0
      jquery-3.1.1.min.js
  8. 716
    706
      js-compiled.js
  9. 117
    218
      js-lib/common.js
  10. 51
    0
      js-lib/constants.js
  11. 243
    225
      js-lib/controller.js
  12. 121
    112
      js-lib/draw-select.js
  13. 444
    426
      js-lib/draw.js
  14. 223
    226
      js-lib/drive-controller.js
  15. 176
    170
      js-lib/input-controller.js
  16. 14
    10
      js-lib/launch.js
  17. 284
    283
      js-lib/state.js
  18. 57
    0
      js-lib/vector.js
  19. 236
    230
      js-lib/view.js

+ 1
- 0
.gitignore View File

@@ -3,3 +3,4 @@ closure-library/*
3 3
 .settings/*
4 4
 *~
5 5
 _site/*
6
+.DS_Store

+ 0
- 1
README View File

@@ -13,4 +13,3 @@ Goto: http://localhost:8000/index.html
13 13
 
14 14
 When developing, use the Google JS linter, gjslint.
15 15
 
16
-Adding new source files will require a change to compile.sh.

BIN
closure-compiler.jar View File


+ 3
- 11
compile.sh View File

@@ -1,13 +1,5 @@
1 1
 java -client -jar closure-compiler.jar \
2
-  --js js-lib/common.js \
3
-  --js js-lib/view.js \
4
-  --js js-lib/draw.js \
5
-  --js js-lib/draw-select.js \
6
-  --js js-lib/state.js \
7
-  --js js-lib/controller.js \
8
-  --js js-lib/drive-controller.js \
9
-  --js js-lib/input-controller.js \
10
-  --js js-lib/launch.js \
11
-  --warning_level=VERBOSE --formatting=PRETTY_PRINT --language_in=ECMASCRIPT5 --compilation_level=ADVANCED_OPTIMIZATIONS \
12
-  --externs=jquery-1.9-externs.js \
2
+  --js js-lib/*.js \
3
+  --warning_level=VERBOSE --formatting=PRETTY_PRINT --language_in=ECMASCRIPT6 --compilation_level=ADVANCED_OPTIMIZATIONS \
4
+  --externs=jquery-3.1-externs.js \
13 5
   > js-compiled.js

+ 4
- 4
index.html View File

@@ -90,7 +90,7 @@ button {
90 90
 }
91 91
 
92 92
 .info-description {
93
-  vertical-align: text-bottom;    
93
+  vertical-align: text-bottom;
94 94
   margin-left: 10px;
95 95
   display: inline-block;
96 96
   height: 30px;
@@ -141,7 +141,7 @@ button {
141 141
   text-align: center;
142 142
   padding-left: 0px;
143 143
 }
144
-  
144
+
145 145
   /* Move file tools to the bottom. */
146 146
 #file-tools {
147 147
   left: 0px;
@@ -557,7 +557,7 @@ textarea {
557 557
     <div class="dialog-button-bar">
558 558
         <button class="close-dialog-button">Close</button>
559 559
         <button id="import-submit-button">Import</button>
560
-  </div>  
560
+  </div>
561 561
 </div>
562 562
 
563 563
 <!-- These dialogs are handled seperately. -->
@@ -587,7 +587,7 @@ textarea {
587 587
 
588 588
 <canvas id="ascii-canvas"></canvas>
589 589
 
590
-<script src="jquery-1.9.1.min.js"></script>
590
+<script src="jquery-3.1.1.min.js"></script>
591 591
 <script src="js-compiled.js"></script>
592 592
 <script src="https://apis.google.com/js/client.js?onload=window.gapiCallback"></script>
593 593
 

+ 2045
- 0
jquery-3.1-externs.js
File diff suppressed because it is too large
View File


+ 4
- 0
jquery-3.1.1.min.js
File diff suppressed because it is too large
View File


+ 716
- 706
js-compiled.js
File diff suppressed because it is too large
View File


+ 117
- 218
js-lib/common.js View File

@@ -2,241 +2,140 @@
2 2
  * Common classes and constants.
3 3
  */
4 4
 
5
-// Define namespace for closure compiler but don't make it a requirement.
6
-try {
7
-  goog.provide('ascii');
8
-  throw 1;
9
-} catch (e) {
10
-  /** type {Object} */
11
-  window.ascii = window.ascii || {};
12
-}
13
-
14
-/** @const */ var MAX_GRID_WIDTH = 2000;
15
-/** @const */ var MAX_GRID_HEIGHT = 600;
16
-
17
-/** @const */ var SPECIAL_VALUE = '+';
18
-/** @const */ var ALT_SPECIAL_VALUE = '^';
19
-/** @const */ var SPECIAL_ARROW_LEFT = '<';
20
-/** @const */ var SPECIAL_ARROW_UP = '^';
21
-/** @const */ var SPECIAL_ARROW_RIGHT = '>';
22
-/** @const */ var SPECIAL_ARROW_DOWN = 'v';
23
-/** @const */ var SPECIAL_VALUES = ['+', '\u2012', '\u2013', '-', '|'];
24
-/** @const */ var ALT_SPECIAL_VALUES = ['>', '<', '^', 'v'];
25
-/** @const */ var ALL_SPECIAL_VALUES = SPECIAL_VALUES.concat(ALT_SPECIAL_VALUES);
26
-
27
-/** @const */ var MAX_UNDO = 50;
28
-
29
-/** @const */ var SPECIAL_LINE_H = '-';
30
-/** @const */ var SPECIAL_LINE_V = '|';
31
-
32
-/** @const */ var ERASE_CHAR = '\u2009';
33
-
34
-/** @const */ var DRAG_LATENCY = 150; // Milliseconds.
35
-/** @const */ var DRAG_ACCURACY = 6; // Pixels.
36
-
37
-/** @const */ var CHAR_PIXELS_H = 9;
38
-/** @const */ var CHAR_PIXELS_V = 17;
39
-
40
-/** @const */ var RENDER_PADDING_CELLS = 3;
41
-
42
-/** @const */ var KEY_RETURN = '<enter>';
43
-/** @const */ var KEY_BACKSPACE = '<backspace>';
44
-/** @const */ var KEY_COPY = '<copy>';
45
-/** @const */ var KEY_PASTE = '<paste>';
46
-/** @const */ var KEY_CUT = '<cut>';
47
-/** @const */ var KEY_UP = '<up>';
48
-/** @const */ var KEY_DOWN = '<down>';
49
-/** @const */ var KEY_LEFT = '<left>';
50
-/** @const */ var KEY_RIGHT = '<right>';
51
-
52
-// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
53
-/** @const */ var TOUCH_ENABLED =
54
-    'ontouchstart' in window ||
55
-    'onmsgesturechange' in window;
56
-
57
-/**
58
- * Stores a 2D vector.
59
- *
60
- * @constructor
61
- * @param {number} x
62
- * @param {number} y
63
- */
64
-ascii.Vector = function(x, y) {
65
-  /** type {Number} */ this.x = x;
66
-  /** type {Number} */ this.y = y;
67
-};
68
-
69
-/**
70
- * @param {ascii.Vector} other
71
- * @return {boolean}
72
- */
73
-ascii.Vector.prototype.equals = function(other) {
74
-  return (other != null) && (this.x == other.x) && (this.y == other.y);
75
-};
76
-
77
-/**
78
- * @param {ascii.Vector} other
79
- * @return {ascii.Vector}
80
- */
81
-ascii.Vector.prototype.subtract = function(other) {
82
-  return new ascii.Vector(this.x - other.x, this.y - other.y);
83
-};
84
-
85
-/**
86
- * @param {ascii.Vector} other
87
- * @return {ascii.Vector}
88
- */
89
-ascii.Vector.prototype.add = function(other) {
90
-  return new ascii.Vector(this.x + other.x, this.y + other.y);
91
-};
92
-
93
-/**
94
- * @return {ascii.Vector}
95
- */
96
-ascii.Vector.prototype.clone = function() {
97
-  return new ascii.Vector(this.x, this.y);
98
-};
99
-
100
-/** @return {number} */
101
-ascii.Vector.prototype.length = function() {
102
-  return Math.sqrt(this.x * this.x + this.y * this.y);
103
-};
104
-
105
-/**
106
- * @param {number} scale
107
- * @return {ascii.Vector}
108
- */
109
-ascii.Vector.prototype.scale = function(scale) {
110
-  return new ascii.Vector(this.x * scale, this.y * scale);
111
-};
5
+import { ERASE_CHAR, ALL_SPECIAL_VALUES } from './constants';
6
+import Vector from './vector';
112 7
 
113 8
 /**
114 9
  * Represents a box with normalized position vectors.
115
- *
116
- * @constructor
117
- * @param {ascii.Vector} a
118
- * @param {ascii.Vector} b
119 10
  */
120
-ascii.Box = function(a, b) {
121
-  /** type {Number} */ this.startX = Math.min(a.x, b.x);
122
-  /** type {Number} */ this.startY = Math.min(a.y, b.y);
123
-  /** type {Number} */ this.endX = Math.max(a.x, b.x);
124
-  /** type {Number} */ this.endY = Math.max(a.y, b.y);
125
-};
126
-
127
-/** @return {ascii.Vector} */
128
-ascii.Box.prototype.topLeft = function() {
129
-  return new ascii.Vector(this.startX, this.startY);
130
-};
131
-
132
-/** @return {ascii.Vector} */
133
-ascii.Box.prototype.bottomRight = function() {
134
-  return new ascii.Vector(this.endX, this.endY);
135
-};
136
-
137
-/** @return {boolean} */
138
-ascii.Box.prototype.contains = function(position) {
139
-  return position.x >= this.startX && position.x <= this.endX && position.y >= this.startY && position.y <= this.endY;
140
-};
141
-
142
-/** @const */ var DIR_LEFT = new ascii.Vector(-1, 0);
143
-/** @const */ var DIR_RIGHT = new ascii.Vector(1, 0);
144
-/** @const */ var DIR_UP = new ascii.Vector(0, -1);
145
-/** @const */ var DIR_DOWN = new ascii.Vector(0, 1);
146
-
147
-/** @const */ var DIRECTIONS = [DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN];
11
+export class Box {
12
+  /**
13
+   * @param {Vector} a
14
+   * @param {Vector} b
15
+   */
16
+  constructor(a, b) {
17
+    /** type {number} */ this.startX = Math.min(a.x, b.x);
18
+    /** type {number} */ this.startY = Math.min(a.y, b.y);
19
+    /** type {number} */ this.endX = Math.max(a.x, b.x);
20
+    /** type {number} */ this.endY = Math.max(a.y, b.y);
21
+  }
22
+
23
+  /** @return {Vector} */
24
+  topLeft() {
25
+    return new Vector(this.startX, this.startY);
26
+  }
27
+
28
+  /** @return {Vector} */
29
+  bottomRight() {
30
+    return new Vector(this.endX, this.endY);
31
+  }
32
+
33
+  /** @return {boolean} */
34
+  contains(position) {
35
+    return position.x >= this.startX && position.x <= this.endX
36
+        && position.y >= this.startY && position.y <= this.endY;
37
+  }
38
+}
148 39
 
149 40
 /**
150 41
  * An individual cell within the diagram and it's current value.
151
- *
152
- * @constructor
153 42
  */
154
-ascii.Cell = function() {
155
-  /** @type {?string} */ this.value = null;
156
-  /** @type {?string} */ this.scratchValue = null;
157
-};
158
-
159
-/** @return {?string} */
160
-ascii.Cell.prototype.getRawValue = function() {
161
-  return (this.scratchValue != null ? this.scratchValue : this.value);
162
-};
163
-
164
-/** @return {boolean} */
165
-ascii.Cell.prototype.isSpecial = function() {
166
-  return ALL_SPECIAL_VALUES.indexOf(this.getRawValue()) != -1;
167
-};
168
-
169
-/** @return {boolean} */
170
-ascii.Cell.prototype.isEmpty = function() {
171
-  return this.value == null && this.scratchValue == null;
172
-};
173
-
174
-/** @return {boolean} */
175
-ascii.Cell.prototype.hasScratch = function() {
176
-  return this.scratchValue != null;
177
-};
178
-
179
-/** @return {boolean} */
180
-ascii.Cell.prototype.isErase = function() {
181
-  return this.scratchValue == ERASE_CHAR;
182
-};
43
+export class Cell {
44
+
45
+  constructor() {
46
+    /** @type {?string} */ this.value = null;
47
+    /** @type {?string} */ this.scratchValue = null;
48
+  }
49
+
50
+  /** @return {?string} */
51
+  getRawValue() {
52
+    return (this.scratchValue != null ? this.scratchValue : this.value);
53
+  }
54
+
55
+  /** @return {boolean} */
56
+  isSpecial() {
57
+    return ALL_SPECIAL_VALUES.indexOf(this.getRawValue()) != -1;
58
+  }
59
+
60
+  /** @return {boolean} */
61
+  isEmpty() {
62
+    return this.value == null && this.scratchValue == null;
63
+  }
64
+
65
+  /** @return {boolean} */
66
+  hasScratch() {
67
+    return this.scratchValue != null;
68
+  }
69
+
70
+  /** @return {boolean} */
71
+  isErase() {
72
+    return this.scratchValue == ERASE_CHAR;
73
+  }
74
+}
183 75
 
184 76
 /**
185 77
  * The context for a cell, i.e. the status of the cells around it.
186
- *
187
- * @param {boolean} left
188
- * @param {boolean} right
189
- * @param {boolean} up
190
- * @param {boolean} down
191
- * @constructor
192
- */
193
-ascii.CellContext = function(left, right, up, down) {
194
-  /** @type {boolean} */ this.left = left;
195
-  /** @type {boolean} */ this.right = right;
196
-  /** @type {boolean} */ this.up = up;
197
-  /** @type {boolean} */ this.down = down;
198
-  /** @type {boolean} */ this.leftup = false;
199
-  /** @type {boolean} */ this.rightup = false;
200
-  /** @type {boolean} */ this.leftdown = false;
201
-  /** @type {boolean} */ this.rightdown = false;
202
-};
203
-
204
-/**
205
- * Returns the total number of surrounding special cells.
206
- * @return {number}
207
- */
208
-ascii.CellContext.prototype.sum = function() {
209
-  return this.left + this.right + this.up + this.down;
210
-};
211
-
212
-/**
213
- * Returns the total number of surrounding special cells.
214
- * @return {number}
215 78
  */
216
-ascii.CellContext.prototype.extendedSum = function() {
217
-  return this.left + this.right + this.up + this.down + this.leftup + this.leftdown + this.rightup + this.rightdown;
218
-};
79
+export class CellContext {
80
+  /**
81
+   * @param {boolean} left
82
+   * @param {boolean} right
83
+   * @param {boolean} up
84
+   * @param {boolean} down
85
+   */
86
+  constructor(left, right, up, down) {
87
+    /** @type {boolean} */ this.left = left;
88
+    /** @type {boolean} */ this.right = right;
89
+    /** @type {boolean} */ this.up = up;
90
+    /** @type {boolean} */ this.down = down;
91
+    /** @type {boolean} */ this.leftup = false;
92
+    /** @type {boolean} */ this.rightup = false;
93
+    /** @type {boolean} */ this.leftdown = false;
94
+    /** @type {boolean} */ this.rightdown = false;
95
+  }
96
+
97
+  /**
98
+   * Returns the total number of surrounding special cells.
99
+   * @return {number}
100
+   */
101
+  sum() {
102
+    return this.left + this.right + this.up + this.down;
103
+  }
104
+
105
+  /**
106
+   * Returns the total number of surrounding special cells.
107
+   * @return {number}
108
+   */
109
+  extendedSum() {
110
+    return this.left + this.right + this.up + this.down
111
+         + this.leftup + this.leftdown + this.rightup + this.rightdown;
112
+  }
113
+}
219 114
 
220 115
 /**
221 116
  * A pair of a vector and a string value. Used in history management.
222
- * @constructor
223
- * @struct
224
- * @param {ascii.Vector} position
225
- * @param {string|null} value
226 117
  */
227
-ascii.MappedValue = function(position, value) {
228
-  this.position = position;
229
-  this.value = value;
230
-};
118
+export class MappedValue {
119
+  /**
120
+   * @param {Vector} position
121
+   * @param {string|null} value
122
+   */
123
+  constructor(position, value) {
124
+    this.position = position;
125
+    this.value = value;
126
+  }
127
+}
231 128
 
232 129
 /**
233 130
  * A pair of a vector and a cell. Used in history management.
234
- * @constructor
235
- * @struct
236
- * @param {ascii.Vector} position
237
- * @param {ascii.Cell} cell
238 131
  */
239
-ascii.MappedCell = function(position, cell) {
240
-  this.position = position;
241
-  this.cell = cell;
242
-};
132
+export class MappedCell {
133
+  /**
134
+   * @param {Vector} position
135
+   * @param {Cell} cell
136
+   */
137
+  constructor(position, cell) {
138
+    this.position = position;
139
+    this.cell = cell;
140
+  }
141
+}

+ 51
- 0
js-lib/constants.js View File

@@ -0,0 +1,51 @@
1
+import Vector from './vector';
2
+
3
+export const MAX_GRID_WIDTH = 2000;
4
+export const MAX_GRID_HEIGHT = 600;
5
+
6
+export const SPECIAL_VALUE = '+';
7
+export const ALT_SPECIAL_VALUE = '^';
8
+export const SPECIAL_ARROW_LEFT = '<';
9
+export const SPECIAL_ARROW_UP = '^';
10
+export const SPECIAL_ARROW_RIGHT = '>';
11
+export const SPECIAL_ARROW_DOWN = 'v';
12
+export const SPECIAL_VALUES = ['+', '\u2012', '\u2013', '-', '|'];
13
+export const ALT_SPECIAL_VALUES = ['>', '<', '^', 'v'];
14
+export const ALL_SPECIAL_VALUES = SPECIAL_VALUES.concat(ALT_SPECIAL_VALUES);
15
+
16
+export const MAX_UNDO = 50;
17
+
18
+export const SPECIAL_LINE_H = '-';
19
+export const SPECIAL_LINE_V = '|';
20
+
21
+export const ERASE_CHAR = '\u2009';
22
+
23
+export const DRAG_LATENCY = 150; // Milliseconds.
24
+export const DRAG_ACCURACY = 6; // Pixels.
25
+
26
+export const CHAR_PIXELS_H = 9;
27
+export const CHAR_PIXELS_V = 17;
28
+
29
+export const RENDER_PADDING_CELLS = 3;
30
+
31
+export const KEY_RETURN = '<enter>';
32
+export const KEY_BACKSPACE = '<backspace>';
33
+export const KEY_COPY = '<copy>';
34
+export const KEY_PASTE = '<paste>';
35
+export const KEY_CUT = '<cut>';
36
+export const KEY_UP = '<up>';
37
+export const KEY_DOWN = '<down>';
38
+export const KEY_LEFT = '<left>';
39
+export const KEY_RIGHT = '<right>';
40
+
41
+// http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
42
+export const TOUCH_ENABLED =
43
+    'ontouchstart' in window ||
44
+    'onmsgesturechange' in window;
45
+
46
+export const DIR_LEFT  = new Vector(-1,  0);
47
+export const DIR_RIGHT = new Vector( 1,  0);
48
+export const DIR_UP    = new Vector( 0, -1);
49
+export const DIR_DOWN  = new Vector( 0,  1);
50
+
51
+export const DIRECTIONS = [DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN];

+ 243
- 225
js-lib/controller.js View File

@@ -1,8 +1,23 @@
1
+import * as c from './constants';
2
+import Vector from './vector';
3
+import View from './view';
4
+import State from './state';
5
+import DrawSelect from './draw-select';
6
+import {
7
+  DrawFunction,
8
+  DrawBox,
9
+  DrawLine,
10
+  DrawFreeform,
11
+  DrawErase,
12
+  DrawMove,
13
+  DrawText,
14
+} from './draw';
15
+
16
+
1 17
 /**
2 18
  * Different modes of control.
3
- * @const
4 19
  */
5
-var Mode = {
20
+const Mode = {
6 21
     NONE: 0,
7 22
     DRAG: 1,
8 23
     DRAW: 2
@@ -10,253 +25,256 @@ var Mode = {
10 25
 
11 26
 /**
12 27
  * Handles user input events and modifies state.
13
- *
14
- * @constructor
15
- * @param {ascii.View} view
16
- * @param {ascii.State} state
17 28
  */
18
-ascii.Controller = function(view, state) {
19
-  /** @type {ascii.View} */ this.view = view;
20
-  /** @type {ascii.State} */ this.state = state;
21
-
22
-  /** @type {ascii.DrawFunction} */ this.drawFunction =
23
-      new ascii.DrawBox(state);
24
-
25
-  /** @type {number} */ this.mode = Mode.NONE;
26
-  /** @type {ascii.Vector} */ this.dragOrigin;
27
-  /** @type {ascii.Vector} */ this.dragOriginCell;
29
+export default class Controller {
30
+  /**
31
+   * @param {View} view
32
+   * @param {State} state
33
+   */
34
+  constructor(view, state) {
35
+    /** @type {View} */ this.view = view;
36
+    /** @type {State} */ this.state = state;
28 37
 
29
-  this.installBindings();
30
-};
31
-
32
-/**
33
- * @param {ascii.Vector} position
34
- */
35
-ascii.Controller.prototype.startDraw = function(position) {
36
-  this.mode = Mode.DRAW;
37
-  this.drawFunction.start(this.view.screenToCell(position));
38
-};
38
+    /** @type {DrawFunction} */ this.drawFunction = new DrawBox(state);
39 39
 
40
-/**
41
- * @param {ascii.Vector} position
42
- */
43
-ascii.Controller.prototype.startDrag = function(position) {
44
-  this.mode = Mode.DRAG;
45
-  this.dragOrigin = position;
46
-  this.dragOriginCell = this.view.offset;
47
-};
40
+    /** @type {number} */ this.mode = Mode.NONE;
41
+    /** @type {Vector} */ this.dragOrigin;
42
+    /** @type {Vector} */ this.dragOriginCell;
48 43
 
49
-/**
50
- * @param {ascii.Vector} position
51
- */
52
-ascii.Controller.prototype.handleMove = function(position) {
53
-  var moveCell = this.view.screenToCell(position);
44
+    /** @type {Vector} */ this.lastMoveCell = null;
54 45
 
55
-  // First move event, make sure we don't blow up here.
56
-  if (this.lastMoveCell == null) {
57
-    this.lastMoveCell = moveCell;
46
+    this.installBindings();
58 47
   }
59 48
 
60
-  // Update the cursor pointer, depending on the draw function.
61
-  if (!moveCell.equals(this.lastMoveCell)) {
62
-    this.view.canvas.style.cursor = this.drawFunction.getCursor(moveCell);
49
+  /**
50
+   * @param {Vector} position
51
+   */
52
+  startDraw(position) {
53
+    this.mode = Mode.DRAW;
54
+    this.drawFunction.start(this.view.screenToCell(position));
63 55
   }
64 56
 
65
-  // In drawing mode, so pass the mouse move on, but remove duplicates.
66
-  if (this.mode == Mode.DRAW && !moveCell.equals(this.lastMoveCell)) {
67
-    this.drawFunction.move(moveCell);
57
+  /**
58
+   * @param {Vector} position
59
+   */
60
+  startDrag(position) {
61
+    this.mode = Mode.DRAG;
62
+    this.dragOrigin = position;
63
+    this.dragOriginCell = this.view.offset;
68 64
   }
69 65
 
70
-  // Drag in progress, update the view origin.
71
-  if (this.mode == Mode.DRAG) {
72
-    this.view.setOffset(this.dragOriginCell.add(
73
-        this.dragOrigin
74
-            .subtract(position)
75
-            .scale(1 / this.view.zoom)));
66
+  /**
67
+   * @param {Vector} position
68
+   */
69
+  handleMove(position) {
70
+    var moveCell = this.view.screenToCell(position);
71
+
72
+    // First move event, make sure we don't blow up here.
73
+    if (this.lastMoveCell == null) {
74
+      this.lastMoveCell = moveCell;
75
+    }
76
+
77
+    // Update the cursor pointer, depending on the draw function.
78
+    if (!moveCell.equals(this.lastMoveCell)) {
79
+      this.view.canvas.style.cursor = this.drawFunction.getCursor(moveCell);
80
+    }
81
+
82
+    // In drawing mode, so pass the mouse move on, but remove duplicates.
83
+    if (this.mode == Mode.DRAW && !moveCell.equals(this.lastMoveCell)) {
84
+      this.drawFunction.move(moveCell);
85
+    }
86
+
87
+    // Drag in progress, update the view origin.
88
+    if (this.mode == Mode.DRAG) {
89
+      this.view.setOffset(this.dragOriginCell.add(
90
+          this.dragOrigin
91
+              .subtract(position)
92
+              .scale(1 / this.view.zoom)));
93
+    }
94
+    this.lastMoveCell = moveCell;
76 95
   }
77
-  this.lastMoveCell = moveCell;
78
-};
79 96
 
80
-/**
81
- * Ends the current operation.
82
- */
83
-ascii.Controller.prototype.endAll = function() {
84
-  if (this.mode == Mode.DRAW) {
85
-    this.drawFunction.end();
97
+  /**
98
+   * Ends the current operation.
99
+   */
100
+  endAll() {
101
+    if (this.mode == Mode.DRAW) {
102
+      this.drawFunction.end();
103
+    }
104
+    // Cleanup state.
105
+    this.mode = Mode.NONE;
106
+    this.dragOrigin = null;
107
+    this.dragOriginCell = null;
108
+    this.lastMoveCell = null;
86 109
   }
87
-  // Cleanup state.
88
-  this.mode = Mode.NONE;
89
-  this.dragOrigin = null;
90
-  this.dragOriginCell = null;
91
-  this.lastMoveCell = null;
92
-};
93
-
94
-/**
95
- * Installs input bindings for common use cases devices.
96
- */
97
-ascii.Controller.prototype.installBindings = function() {
98
-  var controller = this;
99
-
100
-  $(window).resize(function(e) { controller.view.resizeCanvas() });
101 110
 
102
-  $('#draw-tools > button.tool').click(function(e) {
103
-    $('#text-tool-widget').hide(0);
104
-    this.handleDrawButton(e.target.id);
105
-  }.bind(this));
106
-
107
-  $('#file-tools > button.tool').click(function(e) {
108
-    this.handleFileButton(e.target.id);
109
-  }.bind(this));
110
-
111
-  $('button.close-dialog-button').click(function(e) {
112
-    $('.dialog').removeClass('visible');
113
-  }.bind(this));
114
-
115
-  $('#import-submit-button').click(function(e) {
116
-    this.state.clear();
117
-    this.state.fromText($('#import-area').val(),
118
-        this.view.screenToCell(new ascii.Vector(
119
-            this.view.canvas.width / 2,
120
-            this.view.canvas.height / 2)));
121
-    this.state.commitDraw();
122
-    $('#import-area').val('');
123
-    $('.dialog').removeClass('visible');
124
-  }.bind(this));
111
+  /**
112
+   * Installs input bindings for common use cases devices.
113
+   */
114
+  installBindings() {
115
+    $(window).resize(e => { this.view.resizeCanvas() });
116
+
117
+    $('#draw-tools > button.tool').click(e => {
118
+      $('#text-tool-widget').hide(0);
119
+      this.handleDrawButton(e.target.id);
120
+    });
121
+
122
+    $('#file-tools > button.tool').click(e => {
123
+      this.handleFileButton(e.target.id);
124
+    });
125
+
126
+    $('button.close-dialog-button').click(e => {
127
+      $('.dialog').removeClass('visible');
128
+    });
129
+
130
+    $('#import-submit-button').click(e => {
131
+      this.state.clear();
132
+      this.state.fromText(
133
+          /** @type {string} */
134
+          ($('#import-area').val()),
135
+          this.view.screenToCell(new Vector(
136
+              this.view.canvas.width / 2,
137
+              this.view.canvas.height / 2)));
138
+      this.state.commitDraw();
139
+      $('#import-area').val('');
140
+      $('.dialog').removeClass('visible');
141
+    });
142
+
143
+    $('#use-lines-button').click(e => {
144
+      $('.dialog').removeClass('visible');
145
+      this.view.setUseLines(true);
146
+    });
147
+
148
+    $('#use-ascii-button').click(e => {
149
+      $('.dialog').removeClass('visible');
150
+      this.view.setUseLines(false);
151
+    });
152
+
153
+    $(window).keypress(e => {
154
+      this.handleKeyPress(e);
155
+    });
156
+
157
+    $(window).keydown(e => {
158
+      this.handleKeyDown(e);
159
+    });
160
+
161
+    // Bit of a hack, just triggers the text tool to get a new value.
162
+    $('#text-tool-input, #freeform-tool-input').keyup(() => {
163
+        this.drawFunction.handleKey('');
164
+    });
165
+    $('#text-tool-input, #freeform-tool-input').change(() => {
166
+        this.drawFunction.handleKey('');
167
+    });
168
+    $('#text-tool-close').click(() => {
169
+      $('#text-tool-widget').hide();
170
+      this.state.commitDraw();
171
+    });
172
+  }
125 173
 
126
-  $('#use-lines-button').click(function(e) {
174
+  /**
175
+   * Handles the buttons in the UI.
176
+   * @param {string} id The ID of the element clicked.
177
+   */
178
+  handleDrawButton(id) {
179
+    $('#draw-tools > button.tool').removeClass('active');
180
+    $('#' + id).toggleClass('active');
127 181
     $('.dialog').removeClass('visible');
128
-    this.view.setUseLines(true);
129
-  }.bind(this));
130 182
 
131
-  $('#use-ascii-button').click(function(e) {
132
-    $('.dialog').removeClass('visible');
133
-    this.view.setUseLines(false);
134
-  }.bind(this));
135
-
136
-  $(window).keypress(function(e) {
137
-    this.handleKeyPress(e);
138
-  }.bind(this));
139
-
140
-  $(window).keydown(function(e) {
141
-    this.handleKeyDown(e);
142
-  }.bind(this));
143
-
144
-  // Bit of a hack, just triggers the text tool to get a new value.
145
-  $('#text-tool-input, #freeform-tool-input').keyup(function(){
146
-      this.drawFunction.handleKey('');
147
-  }.bind(this));
148
-  $('#text-tool-input, #freeform-tool-input').change(function(){
149
-      this.drawFunction.handleKey('');
150
-  }.bind(this));
151
-  $('#text-tool-close').click(function(){
152
-    $('#text-tool-widget').hide();
183
+    // Install the right draw tool based on button pressed.
184
+    if (id == 'box-button') {
185
+      this.drawFunction = new DrawBox(this.state);
186
+    }
187
+    if (id == 'line-button') {
188
+      this.drawFunction = new DrawLine(this.state, false);
189
+    }
190
+    if (id == 'arrow-button') {
191
+      this.drawFunction = new DrawLine(this.state, true);
192
+    }
193
+    if (id == 'freeform-button') {
194
+      this.drawFunction = new DrawFreeform(this.state, "X");
195
+    }
196
+    if (id == 'erase-button') {
197
+      this.drawFunction = new DrawErase(this.state);
198
+    }
199
+    if (id == 'move-button') {
200
+      this.drawFunction = new DrawMove(this.state);
201
+    }
202
+    if (id == 'text-button') {
203
+      this.drawFunction = new DrawText(this.state, this.view);
204
+    }
205
+    if (id == 'select-button') {
206
+      this.drawFunction = new DrawSelect(this.state);
207
+    }
153 208
     this.state.commitDraw();
154
-  }.bind(this));
155
-};
156
-
157
-/**
158
- * Handles the buttons in the UI.
159
- * @param {string} id The ID of the element clicked.
160
- */
161
-ascii.Controller.prototype.handleDrawButton = function(id) {
162
-  $('#draw-tools > button.tool').removeClass('active');
163
-  $('#' + id).toggleClass('active');
164
-  $('.dialog').removeClass('visible');
165
-
166
-  // Install the right draw tool based on button pressed.
167
-  if (id == 'box-button') {
168
-    this.drawFunction = new ascii.DrawBox(this.state);
169
-  }
170
-  if (id == 'line-button') {
171
-    this.drawFunction = new ascii.DrawLine(this.state, false);
172
-  }
173
-  if (id == 'arrow-button') {
174
-    this.drawFunction = new ascii.DrawLine(this.state, true);
175
-  }
176
-  if (id == 'freeform-button') {
177
-    this.drawFunction = new ascii.DrawFreeform(this.state, "X");
178
-  }
179
-  if (id == 'erase-button') {
180
-    this.drawFunction = new ascii.DrawErase(this.state);
181
-  }
182
-  if (id == 'move-button') {
183
-    this.drawFunction = new ascii.DrawMove(this.state);
184
-  }
185
-  if (id == 'text-button') {
186
-    this.drawFunction = new ascii.DrawText(this.state, this.view);
187
-  }
188
-  if (id == 'select-button') {
189
-    this.drawFunction = new ascii.DrawSelect(this.state);
190
-  }
191
-  this.state.commitDraw();
192
-  this.view.canvas.focus();
193
-};
194
-
195
-/**
196
- * Handles the buttons in the UI.
197
- * @param {string} id The ID of the element clicked.
198
- */
199
-ascii.Controller.prototype.handleFileButton = function(id) {
200
-  $('.dialog').removeClass('visible');
201
-  $('#' + id + '-dialog').toggleClass('visible');
202
-
203
-  if (id == 'import-button') {
204
-    $('#import-area').val('');
205
-    $('#import-area').focus();
206
-  }
207
-
208
-  if (id == 'export-button') {
209
-    $('#export-area').val(this.state.outputText());
210
-    $('#export-area').select();
209
+    this.view.canvas.focus();
211 210
   }
212
-  if (id == 'clear-button') {
213
-    this.state.clear();
214
-  }
215
-  if (id == 'undo-button') {
216
-    this.state.undo();
217
-  }
218
-  if (id == 'redo-button') {
219
-    this.state.redo();
220
-  }
221
-};
222 211
 
223
-/**
224
- * Handles key presses.
225
- * @param {Object} event
226
- */
227
-ascii.Controller.prototype.handleKeyPress = function(event) {
228
-  if (!event.ctrlKey && !event.metaKey && event.keyCode != 13) {
229
-    this.drawFunction.handleKey(String.fromCharCode(event.keyCode));
212
+  /**
213
+   * Handles the buttons in the UI.
214
+   * @param {string} id The ID of the element clicked.
215
+   */
216
+  handleFileButton(id) {
217
+    $('.dialog').removeClass('visible');
218
+    $('#' + id + '-dialog').toggleClass('visible');
219
+
220
+    if (id == 'import-button') {
221
+      $('#import-area').val('');
222
+      $('#import-area').focus();
223
+    }
224
+
225
+    if (id == 'export-button') {
226
+      $('#export-area').val(this.state.outputText());
227
+      $('#export-area').select();
228
+    }
229
+    if (id == 'clear-button') {
230
+      this.state.clear();
231
+    }
232
+    if (id == 'undo-button') {
233
+      this.state.undo();
234
+    }
235
+    if (id == 'redo-button') {
236
+      this.state.redo();
237
+    }
230 238
   }
231
-};
232 239
 
233
-/**
234
- * Handles key down events.
235
- * @param {Object} event
236
- */
237
-ascii.Controller.prototype.handleKeyDown = function(event) {
238
-  // Override some special characters so that they can be handled in one place.
239
-  var specialKeyCode = null;
240
-
241
-  if (event.ctrlKey || event.metaKey) {
242
-    if (event.keyCode == 67) { specialKeyCode = KEY_COPY; }
243
-    if (event.keyCode == 86) { specialKeyCode = KEY_PASTE; }
244
-    if (event.keyCode == 90) { this.state.undo(); }
245
-    if (event.keyCode == 89) { this.state.redo(); }
246
-    if (event.keyCode == 88) { specialKeyCode = KEY_CUT; }
240
+  /**
241
+   * Handles key presses.
242
+   * @param {jQuery.Event} event
243
+   */
244
+  handleKeyPress(event) {
245
+    if (!event.ctrlKey && !event.metaKey && event.keyCode != 13) {
246
+      this.drawFunction.handleKey(String.fromCharCode(event.keyCode));
247
+    }
247 248
   }
248 249
 
249
-  if (event.keyCode == 8) { specialKeyCode = KEY_BACKSPACE; }
250
-  if (event.keyCode == 13) { specialKeyCode = KEY_RETURN; }
251
-  if (event.keyCode == 38) { specialKeyCode = KEY_UP; }
252
-  if (event.keyCode == 40) { specialKeyCode = KEY_DOWN; }
253
-  if (event.keyCode == 37) { specialKeyCode = KEY_LEFT; }
254
-  if (event.keyCode == 39) { specialKeyCode = KEY_RIGHT; }
255
-
256
-  if (specialKeyCode != null) {
257
-    //event.preventDefault();
258
-    //event.stopPropagation();
259
-    this.drawFunction.handleKey(specialKeyCode);
250
+  /**
251
+   * Handles key down events.
252
+   * @param {jQuery.Event} event
253
+   */
254
+  handleKeyDown(event) {
255
+    // Override some special characters so that they can be handled in one place.
256
+    var specialKeyCode = null;
257
+
258
+    if (event.ctrlKey || event.metaKey) {
259
+      if (event.keyCode == 67) { specialKeyCode = c.KEY_COPY; }
260
+      if (event.keyCode == 86) { specialKeyCode = c.KEY_PASTE; }
261
+      if (event.keyCode == 90) { this.state.undo(); }
262
+      if (event.keyCode == 89) { this.state.redo(); }
263
+      if (event.keyCode == 88) { specialKeyCode = c.KEY_CUT; }
264
+    }
265
+
266
+    if (event.keyCode == 8) { specialKeyCode = c.KEY_BACKSPACE; }
267
+    if (event.keyCode == 13) { specialKeyCode = c.KEY_RETURN; }
268
+    if (event.keyCode == 38) { specialKeyCode = c.KEY_UP; }
269
+    if (event.keyCode == 40) { specialKeyCode = c.KEY_DOWN; }
270
+    if (event.keyCode == 37) { specialKeyCode = c.KEY_LEFT; }
271
+    if (event.keyCode == 39) { specialKeyCode = c.KEY_RIGHT; }
272
+
273
+    if (specialKeyCode != null) {
274
+      //event.preventDefault();
275
+      //event.stopPropagation();
276
+      this.drawFunction.handleKey(specialKeyCode);
277
+    }
260 278
   }
261
-};
279
+}
262 280
 

+ 121
- 112
js-lib/draw-select.js View File

@@ -1,138 +1,147 @@
1
+import * as c from './constants';
2
+import Vector from './vector';
3
+import State from './state';
4
+import { MappedValue, Box } from './common';
5
+import { DrawFunction, DrawErase } from './draw';
6
+
1 7
 /**
2
- * @constructor
3
- * @implements {ascii.DrawFunction}
4
- * @param {ascii.State} state
8
+ * @implements {DrawFunction}
5 9
  */
6
-ascii.DrawSelect = function(state) {
7
-  this.state = state;
8
-  /** @type {ascii.Vector} */
9
-  this.startPosition = null;
10
-  /** @type {ascii.Vector} */
11
-  this.endPosition = null;
12
-  /** @type {ascii.Vector} */
13
-  this.dragStart = null;
14
-  /** @type {ascii.Vector} */
15
-  this.dragEnd = null;
16
-
17
-  /** @type {boolean} */
18
-  this.finished = true;
10
+export default class DrawSelect {
11
+  /**
12
+   * @param {State} state
13
+   */
14
+  constructor(state) {
15
+    this.state = state;
16
+    /** @type {Vector} */
17
+    this.startPosition = null;
18
+    /** @type {Vector} */
19
+    this.endPosition = null;
20
+    /** @type {Vector} */
21
+    this.dragStart = null;
22
+    /** @type {Vector} */
23
+    this.dragEnd = null;
19 24
 
20
-  /** @type {Array.<ascii.MappedValue>} */
21
-  this.selectedCells = null;
22
-};
25
+    /** @type {boolean} */
26
+    this.finished = true;
23 27
 
24
-/** @inheritDoc */
25
-ascii.DrawSelect.prototype.start = function(position) {
26
-  // Must be dragging.
27
-  if (this.startPosition != null &&
28
-      this.endPosition != null &&
29
-      this.getSelectedBox().contains(position)) {
30
-    this.dragStart = position;
31
-    this.copyArea();
32
-    this.dragMove(position);
33
-  } else {
34
-    this.startPosition = position;
35
-    this.endPosition = null;
36
-    this.finished = false;
37
-    this.move(position);
28
+    /** @type {!Array<MappedValue>} */
29
+    this.selectedCells = [];
38 30
   }
39
-};
40
-
41
-ascii.DrawSelect.prototype.getSelectedBox = function() {
42
-  return new ascii.Box(this.startPosition, this.endPosition);
43
-};
44 31
 
45
-ascii.DrawSelect.prototype.copyArea = function() {
46
-  var nonEmptyCells = this.state.scratchCells.filter(function(value) {
47
-    var rawValue = value.cell.getRawValue();
48
-    return value.cell.getRawValue() != null && value.cell.getRawValue() != ERASE_CHAR;
49
-  });
50
-  var topLeft = this.getSelectedBox().topLeft();
51
-  this.selectedCells = nonEmptyCells.map(function(value) {
52
-    return new ascii.MappedValue(value.position.subtract(topLeft), value.cell.getRawValue());
53
-  });
54
-};
32
+  /** @inheritDoc */
33
+  start(position) {
34
+    // Must be dragging.
35
+    if (this.startPosition != null &&
36
+        this.endPosition != null &&
37
+        this.getSelectedBox().contains(position)) {
38
+      this.dragStart = position;
39
+      this.copyArea();
40
+      this.dragMove(position);
41
+    } else {
42
+      this.startPosition = position;
43
+      this.endPosition = null;
44
+      this.finished = false;
45
+      this.move(position);
46
+    }
47
+  }
55 48
 
56
-/** @inheritDoc */
57
-ascii.DrawSelect.prototype.move = function(position) {
58
-  if (this.dragStart != null) {
59
-    this.dragMove(position);
60
-    return;
49
+  getSelectedBox() {
50
+    return new Box(this.startPosition, this.endPosition);
61 51
   }
62 52
 
63
-  if (this.finished == true) {
64
-    return;
53
+  copyArea() {
54
+    var nonEmptyCells = this.state.scratchCells.filter(function(value) {
55
+      var rawValue = value.cell.getRawValue();
56
+      return value.cell.getRawValue() != null && value.cell.getRawValue() != c.ERASE_CHAR;
57
+    });
58
+    var topLeft = this.getSelectedBox().topLeft();
59
+    this.selectedCells = nonEmptyCells.map(function(value) {
60
+      return new MappedValue(value.position.subtract(topLeft), value.cell.getRawValue());
61
+    });
65 62
   }
66
-  this.endPosition = position;
67
-  this.state.clearDraw();
68 63
 
69
-  var box = new ascii.Box(this.startPosition, position);
64
+  /** @inheritDoc */
65
+  move(position) {
66
+    if (this.dragStart != null) {
67
+      this.dragMove(position);
68
+      return;
69
+    }
70
+
71
+    if (this.finished == true) {
72
+      return;
73
+    }
74
+    this.endPosition = position;
75
+    this.state.clearDraw();
76
+
77
+    var box = new Box(this.startPosition, position);
70 78
 
71
-  for (var i = box.startX; i <= box.endX; i++) {
72
-    for (var j = box.startY; j <= box.endY; j++) {
73
-      var current = new ascii.Vector(i, j);
74
-      // Effectively highlights the cell.
75
-      var currentValue = this.state.getCell(current).getRawValue();
76
-      this.state.drawValue(current,
77
-          currentValue == null ? ERASE_CHAR : currentValue);
79
+    for (var i = box.startX; i <= box.endX; i++) {
80
+      for (var j = box.startY; j <= box.endY; j++) {
81
+        var current = new Vector(i, j);
82
+        // Effectively highlights the cell.
83
+        var currentValue = this.state.getCell(current).getRawValue();
84
+        this.state.drawValue(current,
85
+            currentValue == null ? c.ERASE_CHAR : currentValue);
86
+      }
78 87
     }
79 88
   }
80
-};
81 89
 
82
-ascii.DrawSelect.prototype.dragMove = function(position) {
83
-  this.dragEnd = position;
84
-  this.state.clearDraw();
85
-  var eraser = new ascii.DrawErase(this.state);
86
-  eraser.start(this.startPosition);
87
-  eraser.move(this.endPosition);
88
-  var startPos = this.dragEnd.subtract(this.dragStart).add(this.getSelectedBox().topLeft());
89
-  this.drawSelected(startPos);
90
-};
90
+  dragMove(position) {
91
+    this.dragEnd = position;
92
+    this.state.clearDraw();
93
+    var eraser = new DrawErase(this.state);
94
+    eraser.start(this.startPosition);
95
+    eraser.move(this.endPosition);
96
+    var startPos = this.dragEnd.subtract(this.dragStart).add(this.getSelectedBox().topLeft());
97
+    this.drawSelected(startPos);
98
+  }
91 99
 
92
-ascii.DrawSelect.prototype.drawSelected = function(startPos) {
93
- for (var i in this.selectedCells) {
94
-    this.state.drawValue(this.selectedCells[i].position.add(startPos), this.selectedCells[i].value);
100
+  drawSelected(startPos) {
101
+    for (var { position, value } of this.selectedCells) {
102
+      this.state.drawValue(position.add(startPos), value);
103
+    }
95 104
   }
96
-};
97 105
 
98
-/** @inheritDoc */
99
-ascii.DrawSelect.prototype.end = function() {
100
-  if (this.dragStart != null) {
101
-    this.state.commitDraw();
102
-    this.startPosition = null;
103
-    this.endPosition = null;
106
+  /** @inheritDoc */
107
+  end() {
108
+    if (this.dragStart != null) {
109
+      this.state.commitDraw();
110
+      this.startPosition = null;
111
+      this.endPosition = null;
112
+    }
113
+    this.dragStart = null;
114
+    this.dragEnd = null;
115
+    this.finished = true;
104 116
   }
105
-  this.dragStart = null;
106
-  this.dragEnd = null;
107
-  this.finished = true;
108
-};
109 117
 
110
-/** @inheritDoc */
111
-ascii.DrawSelect.prototype.getCursor = function(position) {
112
-  if (this.startPosition != null &&
113
-      this.endPosition != null &&
114
-      new ascii.Box(this.startPosition, this.endPosition).contains(position)) {
115
-    return 'pointer';
118
+  /** @inheritDoc */
119
+  getCursor(position) {
120
+    if (this.startPosition != null &&
121
+        this.endPosition != null &&
122
+        new Box(this.startPosition, this.endPosition).contains(position)) {
123
+      return 'pointer';
124
+    }
125
+    return 'default';
116 126
   }
117
-  return 'default';
118
-};
119 127
 
120
-/** @inheritDoc */
121
-ascii.DrawSelect.prototype.handleKey = function(value) {
122
-  if (this.startPosition != null &&
123
-      this.endPosition != null) {
124
-    if (value == KEY_COPY || value == KEY_CUT) {
125
-      this.copyArea();
128
+  /** @inheritDoc */
129
+  handleKey(value) {
130
+    if (this.startPosition != null &&
131
+        this.endPosition != null) {
132
+      if (value == c.KEY_COPY || value == c.KEY_CUT) {
133
+        this.copyArea();
134
+      }
135
+      if (value == c.KEY_CUT) {
136
+        var eraser = new DrawErase(this.state);
137
+        eraser.start(this.startPosition);
138
+        eraser.move(this.endPosition);
139
+        this.state.commitDraw();
140
+      }
126 141
     }
127
-    if (value == KEY_CUT) {
128
-      var eraser = new ascii.DrawErase(this.state);
129
-      eraser.start(this.startPosition);
130
-      eraser.move(this.endPosition);
142
+    if (value == c.KEY_PASTE) {
143
+      this.drawSelected(this.startPosition);
131 144
       this.state.commitDraw();
132 145
     }
133 146
   }
134
-  if (value == KEY_PASTE) {
135
-    this.drawSelected(this.startPosition);
136
-    this.state.commitDraw();
137
-  }
138
-};
147
+}

+ 444
- 426
js-lib/draw.js View File

@@ -1,3 +1,8 @@
1
+import * as c from './constants';
2
+import State from './state';
3
+import Vector from './vector';
4
+import { Box } from './common';
5
+
1 6
 /**
2 7
  * All drawing classes and functions.
3 8
  */
@@ -5,16 +10,14 @@
5 10
 /**
6 11
  * Draws a line on the diagram state.
7 12
  *
8
- * @param {ascii.State} state
9
- * @param {ascii.Vector} startPosition
10
- * @param {ascii.Vector} endPosition
13
+ * @param {State} state
14
+ * @param {Vector} startPosition
15
+ * @param {Vector} endPosition
11 16
  * @param {boolean} clockwise
12
- * @param {string=} opt_value
17
+ * @param {string=} value
13 18
  */
14
-function drawLine(state, startPosition, endPosition, clockwise, opt_value) {
15
-  var value = opt_value || SPECIAL_VALUE;
16
-
17
-  var box = new ascii.Box(startPosition, endPosition);
19
+function drawLine(state, startPosition, endPosition, clockwise, value = c.SPECIAL_VALUE) {
20
+  var box = new Box(startPosition, endPosition);
18 21
   var startX = box.startX;
19 22
   var startY = box.startY;
20 23
   var endX = box.endX;
@@ -24,16 +27,16 @@ function drawLine(state, startPosition, endPosition, clockwise, opt_value) {
24 27
   var midY = clockwise ? startPosition.y : endPosition.y;
25 28
 
26 29
   while (startX++ < endX) {
27
-    var position = new ascii.Vector(startX, midY);
28
-    var context = state.getContext(new ascii.Vector(startX, midY));
30
+    var position = new Vector(startX, midY);
31
+    var context = state.getContext(new Vector(startX, midY));
29 32
     // Don't erase any lines that we cross.
30 33
     if (value != ' ' || context.up + context.down != 2) {
31 34
       state.drawValueIncremental(position, value);
32 35
     }
33 36
   }
34 37
   while (startY++ < endY) {
35
-    var position = new ascii.Vector(midX, startY);
36
-    var context = state.getContext(new ascii.Vector(midX, startY));
38
+    var position = new Vector(midX, startY);
39
+    var context = state.getContext(new Vector(midX, startY));
37 40
     // Don't erase any lines that we cross.
38 41
     if (value != ' ' || context.left + context.right != 2) {
39 42
       state.drawValueIncremental(position, value);
@@ -42,477 +45,492 @@ function drawLine(state, startPosition, endPosition, clockwise, opt_value) {
42 45
 
43 46
   state.drawValue(startPosition, value);
44 47
   state.drawValue(endPosition, value);
45
-  state.drawValueIncremental(new ascii.Vector(midX, midY), value);
48
+  state.drawValueIncremental(new Vector(midX, midY), value);
46 49
 }
47 50
 
48 51
 /**
49 52
  * Common interface for different drawing functions, e.g. box, line, etc.
50 53
  * @interface
51 54
  */
52
-ascii.DrawFunction = function() {};
53
-/** Start of drawing. @param {ascii.Vector} position */
54
-ascii.DrawFunction.prototype.start = function(position) {};
55
-/** Drawing move. @param {ascii.Vector} position */
56
-ascii.DrawFunction.prototype.move = function(position) {};
57
-/** End of drawing. */
58
-ascii.DrawFunction.prototype.end = function() {};
59
-/** Cursor for given cell.
60
- * @param {ascii.Vector} position
61
- * @return {string}
62
- */
63
-ascii.DrawFunction.prototype.getCursor = function(position) {};
64
-/** Handle the key with given value being pressed. @param {string} value */
65
-ascii.DrawFunction.prototype.handleKey = function(value) {};
55
+export class DrawFunction {
56
+  /** Start of drawing. @param {Vector} position */
57
+  start(position) {};
58
+  /** Drawing move. @param {Vector} position */
59
+  move(position) {};
60
+  /** End of drawing. */
61
+  end() {};
62
+  /** Cursor for given cell.
63
+   * @param {Vector} position
64
+   * @return {string}
65
+   */
66
+  getCursor(position) {};
67
+  /** Handle the key with given value being pressed. @param {string} value */
68
+  handleKey(value) {};
69
+}
66 70
 
67 71
 /**
68
- * @constructor
69
- * @implements {ascii.DrawFunction}
70
- * @param {ascii.State} state
72
+ * @implements {DrawFunction}
71 73
  */
72
-ascii.DrawBox = function(state) {
73
-  this.state = state;
74
-  /** @type {ascii.Vector} */ this.startPosition = null;
75
-};
76
-
77
-/** @inheritDoc */
78
-ascii.DrawBox.prototype.start = function(position) {
79
-  this.startPosition = position;
80
-};
81
-
82
-/** @inheritDoc */
83
-ascii.DrawBox.prototype.move = function(position) {
84
-  this.endPosition = position;
85
-  this.state.clearDraw();
86
-  drawLine(this.state, this.startPosition, position, true);
87
-  drawLine(this.state, this.startPosition, position, false);
88
-};
89
-
90
-/** @inheritDoc */
91
-ascii.DrawBox.prototype.end = function() {
92
-  this.state.commitDraw();
93
-};
94
-
95
-/** @inheritDoc */
96
-ascii.DrawBox.prototype.getCursor = function(position) {
97
-  return 'crosshair';
98
-};
99
-
100
-/** @inheritDoc */
101
-ascii.DrawBox.prototype.handleKey = function(value) {};
74
+export class DrawBox {
75
+  /**
76
+   * @param {State} state
77
+   */
78
+  constructor(state) {
79
+    this.state = state;
80
+    /** @type {Vector} */ this.startPosition = null;
81
+    /** @type {Vector} */ this.endPosition = null;
82
+  }
83
+
84
+  /** @inheritDoc */
85
+  start(position) {
86
+    this.startPosition = position;
87
+  }
88
+
89
+  /** @inheritDoc */
90
+  move(position) {
91
+    this.endPosition = position;
92
+    this.state.clearDraw();
93
+    drawLine(this.state, this.startPosition, position, true);
94
+    drawLine(this.state, this.startPosition, position, false);
95
+  }
96
+
97
+  /** @inheritDoc */
98
+  end() {
99
+    this.state.commitDraw();
100
+  }
101
+
102
+  /** @inheritDoc */
103
+  getCursor(position) {
104
+    return 'crosshair';
105
+  }
106
+
107
+  /** @inheritDoc */
108
+  handleKey(value) {};
109
+}
102 110
 
103 111
 /**
104
- * @constructor
105
- * @implements {ascii.DrawFunction}
106
- * @param {ascii.State} state
107
- * @param {boolean} isArrow
112
+ * @implements {DrawFunction}
108 113
  */
109
-ascii.DrawLine = function(state, isArrow) {
110
-  this.state = state;
111
-  this.isArrow = isArrow;
112
-  /** @type {ascii.Vector} */ this.startPosition = null;
113
-};
114
-
115
-/** @inheritDoc */
116
-ascii.DrawLine.prototype.start = function(position) {
117
-  this.startPosition = position;
118
-};
119
-
120
-/** @inheritDoc */
121
-ascii.DrawLine.prototype.move = function(position) {
122
-  this.state.clearDraw();
123
-
124
-  // Try to infer line orientation.
125
-  // TODO: Split the line into two lines if we can't satisfy both ends.
126
-  var startContext = this.state.getContext(this.startPosition);
127
-  var endContext = this.state.getContext(position);
128
-  var clockwise = (startContext.up && startContext.down) ||
129
-      (endContext.left && endContext.right);
130
-
131
-  drawLine(this.state, this.startPosition, position, clockwise);
132
-  if (this.isArrow) {
133
-    this.state.drawValue(position, ALT_SPECIAL_VALUE);
134
-  }
135
-};
136
-
137
-/** @inheritDoc */
138
-ascii.DrawLine.prototype.end = function() {
139
-  this.state.commitDraw();
140
-};
141
-
142
-/** @inheritDoc */
143
-ascii.DrawLine.prototype.getCursor = function(position) {
144
-  return 'crosshair';
145
-};
146
-
147
-/** @inheritDoc */
148
-ascii.DrawLine.prototype.handleKey = function(value) {};
114
+export class DrawLine {
115
+  /**
116
+   * @param {State} state
117
+   * @param {boolean} isArrow
118
+   */
119
+  constructor(state, isArrow) {
120
+    this.state = state;
121
+    this.isArrow = isArrow;
122
+    /** @type {Vector} */ this.startPosition = null;
123
+  }
124
+
125
+  /** @inheritDoc */
126
+  start(position) {
127
+    this.startPosition = position;
128
+  }
129
+
130
+  /** @inheritDoc */
131
+  move(position) {
132
+    this.state.clearDraw();
133
+
134
+    // Try to infer line orientation.
135
+    // TODO: Split the line into two lines if we can't satisfy both ends.
136
+    var startContext = this.state.getContext(this.startPosition);
137
+    var endContext = this.state.getContext(position);
138
+    var clockwise = (startContext.up && startContext.down) ||
139
+        (endContext.left && endContext.right);
140
+
141
+    drawLine(this.state, this.startPosition, position, clockwise);
142
+    if (this.isArrow) {
143
+      this.state.drawValue(position, c.ALT_SPECIAL_VALUE);
144
+    }
145
+  }
146
+
147
+  /** @inheritDoc */
148
+  end() {
149
+    this.state.commitDraw();
150
+  }
151
+
152
+  /** @inheritDoc */
153
+  getCursor(position) {
154
+    return 'crosshair';
155
+  }
156
+
157
+  /** @inheritDoc */
158
+  handleKey(value) {};
159
+}
149 160
 
150 161
 /**
151
- * @constructor
152
- * @implements {ascii.DrawFunction}
153
- * @param {ascii.State} state
154
- * @param {?string} value
162
+ * @implements {DrawFunction}
155 163
  */
156
-ascii.DrawFreeform = function(state, value) {
157
-  this.state = state;
158
-  this.value = value;
159
-  if (TOUCH_ENABLED) {
160
-    $('#freeform-tool-input').val('');
161
-    $('#freeform-tool-input').hide(0, function() {$('#freeform-tool-input').show(0, function() {$('#freeform-tool-input').focus();});});
162
-  }
163
-};
164
-
165
-/** @inheritDoc */
166
-ascii.DrawFreeform.prototype.start = function(position) {
167
-  this.state.drawValue(position, this.value);
168
-};
169
-
170
-/** @inheritDoc */
171
-ascii.DrawFreeform.prototype.move = function(position) {
172
-  this.state.drawValue(position, this.value);
173
-};
174
-
175
-/** @inheritDoc */
176
-ascii.DrawFreeform.prototype.end = function() {
177
-  this.state.commitDraw();
178
-};
179
-
180
-/** @inheritDoc */
181
-ascii.DrawFreeform.prototype.getCursor = function(position) {
182
-  return 'crosshair';
183
-};
184
-
185
-/** @inheritDoc */
186
-ascii.DrawFreeform.prototype.handleKey = function(value) {
187
-  if (TOUCH_ENABLED) {
188
-    this.value = $('#freeform-tool-input').val().substr(0, 1);
189
-    $('#freeform-tool-input').blur();
190
-    $('#freeform-tool-input').hide(0);
191
-  }
192
-  if (value.length == 1) {
193
-    // The value is not a special character, so lets use it.
164
+export class DrawFreeform {
165
+  /**
166
+   * @param {State} state
167
+   * @param {?string} value
168
+   */
169
+  constructor(state, value) {
170
+    this.state = state;
194 171
     this.value = value;
172
+    if (c.TOUCH_ENABLED) {
173
+      $('#freeform-tool-input').val('');
174
+      $('#freeform-tool-input').hide(0, function() {$('#freeform-tool-input').show(0, function() {$('#freeform-tool-input').focus();});});
175
+    }
176
+  }
177
+
178
+  /** @inheritDoc */
179
+  start(position) {
180
+    this.state.drawValue(position, this.value);
181
+  }
182
+
183
+  /** @inheritDoc */
184
+  move(position) {
185
+    this.state.drawValue(position, this.value);
186
+  }
187
+
188
+  /** @inheritDoc */
189
+  end() {
190
+    this.state.commitDraw();
191
+  }
192
+
193
+  /** @inheritDoc */
194
+  getCursor(position) {
195
+    return 'crosshair';
195 196
   }
196
-};
197
+
198
+  /** @inheritDoc */
199
+  handleKey(value) {
200
+    if (c.TOUCH_ENABLED) {
201
+      this.value = $('#freeform-tool-input').val().substr(0, 1);
202
+      $('#freeform-tool-input').blur();
203
+      $('#freeform-tool-input').hide(0);
204
+    }
205
+    if (value.length == 1) {
206
+      // The value is not a special character, so lets use it.
207
+      this.value = value;
208
+    }
209
+  }
210
+}
197 211
 
198 212
 /**
199
- * @constructor
200
- * @implements {ascii.DrawFunction}
201
- * @param {ascii.State} state
213
+ * @implements {DrawFunction}
202 214
  */
203
-ascii.DrawText = function(state, view) {
204
-  this.state = state;
205
-  this.startPosition = null;
206
-};
207
-
208
-/** @inheritDoc */
209
-ascii.DrawText.prototype.start = function(position) {
210
-  this.state.commitDraw();
211
-  $('#text-tool-input').val('');
212
-  this.startPosition = position;
213
-
214
-  // Not working yet, needs fixing so that it can remove the underlying text completely.
215
-  //this.loadExistingText(position);
216
-
217
-  // Effectively highlights the starting cell.
218
-  var currentValue = this.state.getCell(this.startPosition).getRawValue();
219
-  this.state.drawValue(this.startPosition,
220
-      currentValue == null ? ERASE_CHAR : currentValue);
221
-};
222
-
223
-/** @inheritDoc */
224
-ascii.DrawText.prototype.move = function(position) {};
225
-
226
-/** @inheritDoc */
227
-ascii.DrawText.prototype.end = function() {
228
-  if (this.startPosition != null) {
229
-    this.endPosition = this.startPosition;
215
+export class DrawText {
216
+  /**
217
+   * @param {State} state
218
+   */
219
+  constructor(state, view) {
220
+    this.state = state;
230 221
     this.startPosition = null;
231
-    // Valid end click/press, show the textbox and focus it.
232
-    $('#text-tool-widget').hide(0, function() {$('#text-tool-widget').show(0, function() {$('#text-tool-input').focus();});});
233
-  }
234
-};
235
-
236
-/** @inheritDoc */
237
-ascii.DrawText.prototype.getCursor = function(position) {
238
-  return 'pointer';
239
-};
240
-
241
-/** @inheritDoc */
242
-ascii.DrawText.prototype.handleKey = function(value) {
243
-  var text = $('#text-tool-input').val();
244
-  this.state.clearDraw();
245
-  var x = 0, y = 0;
246
-  for(var i = 0; i < text.length; i++) {
247
-    if (text[i] == '\n') {
248
-      y++;
249
-      x = 0;
250
-      continue;
222
+    this.endPosition = null;
223
+  };
224
+
225
+  /** @inheritDoc */
226
+  start(position) {
227
+    this.state.commitDraw();
228
+    $('#text-tool-input').val('');
229
+    this.startPosition = position;
230
+
231
+    // Not working yet, needs fixing so that it can remove the underlying text completely.
232
+    //this.loadExistingText(position);
233
+
234
+    // Effectively highlights the starting cell.
235
+    var currentValue = this.state.getCell(this.startPosition).getRawValue();
236
+    this.state.drawValue(this.startPosition,
237
+        currentValue == null ? c.ERASE_CHAR : currentValue);
238
+  }
239
+
240
+  /** @inheritDoc */
241
+  move(position) {}
242
+
243
+  /** @inheritDoc */
244
+  end() {
245
+    if (this.startPosition != null) {
246
+      this.endPosition = this.startPosition;
247
+      this.startPosition = null;
248
+      // Valid end click/press, show the textbox and focus it.
249
+      $('#text-tool-widget').hide(0, function() {$('#text-tool-widget').show(0, function() {$('#text-tool-input').focus();});});
251 250
     }
252
-    this.state.drawValue(this.endPosition.add(new ascii.Vector(x, y)), text[i]);
253
-    x++;
254 251
   }
255
-};
256 252
 
257
-/**
258
- * Loads any existing text if it is present.
259
- * TODO: This is horrible, and does not quite work, fix it.
260
- */
261
-ascii.DrawText.prototype.loadExistingText = function(position) {
262
-  var currentPosition = new ascii.Vector(position.x, position.y);
263
-  var cell = this.state.getCell(position);
264
-  var spacesCount = 0;
265
-  // Go back to find the start of the line.
266
-  while ((!cell.isSpecial() && cell.getRawValue() != null) || spacesCount < 1) {
267
-    if (cell.getRawValue() == null) {
268
-      spacesCount++;
269
-    } else if (!cell.isSpecial()) {
270
-      spacesCount = 0;
253
+  /** @inheritDoc */
254
+  getCursor(position) {
255
+    return 'pointer';
256
+  }
257
+
258
+  /** @inheritDoc */
259
+  handleKey(value) {
260
+    var text = $('#text-tool-input').val();
261
+    this.state.clearDraw();
262
+    var x = 0, y = 0;
263
+    for(var i = 0; i < text.length; i++) {
264
+      if (text[i] == '\n') {
265
+        y++;
266
+        x = 0;
267
+        continue;
268
+      }
269
+      this.state.drawValue(this.endPosition.add(new Vector(x, y)), text[i]);
270
+      x++;
271 271
     }
272
-    currentPosition.x--;
273
-    cell = this.state.getCell(currentPosition);
274
-  }
275
-  this.startPosition = currentPosition.add(new ascii.Vector(spacesCount + 1, 0));
276
-  var text = '';
277
-  spacesCount = 0;
278
-  currentPosition = this.startPosition.clone();
279
-  // Go forward to load the text.
280
-  while ((!cell.isSpecial() && cell.getRawValue() != null) || spacesCount < 1) {
281
-    cell = this.state.getCell(currentPosition);
282
-    if (cell.getRawValue() == null) {
283
-      spacesCount++;
284
-      text += ' ';
285
-    } else if (!cell.isSpecial()) {
286
-      spacesCount = 0;
287
-      text += cell.getRawValue();
288
-      this.state.drawValue(currentPosition, cell.getRawValue());
272
+  }
273
+
274
+  /**
275
+   * Loads any existing text if it is present.
276
+   * TODO: This is horrible, and does not quite work, fix it.
277
+   */
278
+  loadExistingText(position) {
279
+    var currentPosition = new Vector(position.x, position.y);
280
+    var cell = this.state.getCell(position);
281
+    var spacesCount = 0;
282
+    // Go back to find the start of the line.
283
+    while ((!cell.isSpecial() && cell.getRawValue() != null) || spacesCount < 1) {
284
+      if (cell.getRawValue() == null) {
285
+        spacesCount++;
286
+      } else if (!cell.isSpecial()) {
287
+        spacesCount = 0;
288
+      }
289
+      currentPosition.x--;
290
+      cell = this.state.getCell(currentPosition);
289 291
     }
290
-    currentPosition.x++;
292
+    this.startPosition = currentPosition.add(new Vector(spacesCount + 1, 0));
293
+    var text = '';
294
+    spacesCount = 0;
295
+    currentPosition = this.startPosition.clone();
296
+    // Go forward to load the text.
297
+    while ((!cell.isSpecial() && cell.getRawValue() != null) || spacesCount < 1) {
298
+      cell = this.state.getCell(currentPosition);
299
+      if (cell.getRawValue() == null) {
300
+        spacesCount++;
301
+        text += ' ';
302
+      } else if (!cell.isSpecial()) {
303
+        spacesCount = 0;
304
+        text += cell.getRawValue();
305
+        this.state.drawValue(currentPosition, cell.getRawValue());
306
+      }
307
+      currentPosition.x++;
308
+    }
309
+    $('#text-tool-input').val(text.substr(0, text.length - 1));
291 310
   }
292
-  $('#text-tool-input').val(text.substr(0, text.length - 1));
293
-};
311
+}
294 312
 
295 313
 /**
296
- * @constructor
297
- * @implements {ascii.DrawFunction}
298
- * @param {ascii.State} state
314
+ * @implements {DrawFunction}
299 315
  */
300
-ascii.DrawErase = function(state) {
301
-  this.state = state;
302
-  this.startPosition = null;
303
-  this.endPosition = null;
304
-};
305
-
306
-/** @inheritDoc */
307
-ascii.DrawErase.prototype.start = function(position) {
308
-  this.startPosition = position;
309
-  this.move(position);
310
-};
311
-
312
-/** @inheritDoc */
313
-ascii.DrawErase.prototype.move = function(position) {
314
-  this.state.clearDraw();
315
-  this.endPosition = position;
316
-
317
-  var startX = Math.min(this.startPosition.x, this.endPosition.x);
318
-  var startY = Math.min(this.startPosition.y, this.endPosition.y);
319
-  var endX = Math.max(this.startPosition.x, this.endPosition.x);
320
-  var endY = Math.max(this.startPosition.y, this.endPosition.y);
321
-
322
-  for (var i = startX; i <= endX; i++) {
323
-    for (var j = startY; j <= endY; j++) {
324
-      this.state.drawValue(new ascii.Vector(i, j), ERASE_CHAR);
316
+export class DrawErase {
317
+  /**
318
+   * @param {State} state
319
+   */
320
+  constructor(state) {
321
+    this.state = state;
322
+    this.startPosition = null;
323
+    this.endPosition = null;
324
+  }
325
+
326
+  /** @inheritDoc */
327
+  start(position) {
328
+    this.startPosition = position;
329
+    this.move(position);
330
+  }
331
+
332
+  /** @inheritDoc */
333
+  move(position) {
334
+    this.state.clearDraw();
335
+    this.endPosition = position;
336
+
337
+    var startX = Math.min(this.startPosition.x, this.endPosition.x);
338
+    var startY = Math.min(this.startPosition.y, this.endPosition.y);
339
+    var endX = Math.max(this.startPosition.x, this.endPosition.x);
340
+    var endY = Math.max(this.startPosition.y, this.endPosition.y);
341
+
342
+    for (var i = startX; i <= endX; i++) {
343
+      for (var j = startY; j <= endY; j++) {
344
+        this.state.drawValue(new Vector(i, j), c.ERASE_CHAR);
345
+      }
325 346
     }
326 347
   }
327
-};
328 348
 
329
-/** @inheritDoc */
330
-ascii.DrawErase.prototype.end = function() {
331
-  this.state.commitDraw();
332
-};
349
+  /** @inheritDoc */
350
+  end() {
351
+    this.state.commitDraw();
352
+  }
333 353
 
334
-/** @inheritDoc */
335
-ascii.DrawErase.prototype.getCursor = function(position) {
336
-  return 'crosshair';
337
-};
354
+  /** @inheritDoc */
355
+  getCursor(position) {
356
+    return 'crosshair';
357
+  }
338 358
 
339
-/** @inheritDoc */
340
-ascii.DrawErase.prototype.handleKey = function(value) {};
359
+  /** @inheritDoc */
360
+  handleKey(value) {}
361
+}
341 362
 
342 363
 /**
343
- * @constructor
344
- * @implements {ascii.DrawFunction}
345
- * @param {ascii.State} state
364
+ * @implements {DrawFunction}
346 365
  */
347
-ascii.DrawMove = function(state) {
348
-  this.state = state;
349
-  this.startPosition = null;
350
-  this.ends = null;
351
-};
352
-
353
-/** @inheritDoc */
354
-ascii.DrawMove.prototype.start = function(position) {
355
-  this.startPosition =
356
-      TOUCH_ENABLED ? this.snapToNearest(position) : position;
357
-  this.ends = null;
358
-
359
-  // If this isn't a special cell then quit, or things get weird.
360
-  if (!this.state.getCell(this.startPosition).isSpecial()) {
361
-    return;
362
-  }
363
-  var context = this.state.getContext(this.startPosition);
364
-
365
-  var ends = [];
366
-  for (var i in DIRECTIONS) {
367
-    var midPoints = this.followLine(this.startPosition, DIRECTIONS[i]);
368
-    for (var k in midPoints) {
369
-      var midPoint = midPoints[k];
370
-
371
-      // Clockwise is a lie, it is true if we move vertically first.
372
-      var clockwise = (DIRECTIONS[i].x != 0);
373
-      var startIsAlt = ALT_SPECIAL_VALUES.indexOf(this.state.getCell(position).getRawValue()) != -1;
374
-      var midPointIsAlt = ALT_SPECIAL_VALUES.indexOf(this.state.getCell(midPoint).getRawValue()) != -1;
375
-
376
-      var midPointContext = this.state.getContext(midPoint);
377
-      // Special case, a straight line with no turns.
378
-      if (midPointContext.sum() == 1) {
379
-        ends.push({position: midPoint, clockwise: clockwise, startIsAlt: startIsAlt, endIsAlt: midPointIsAlt});
380
-        continue;
381
-      }
382
-      // Continue following lines from the midpoint.
383
-      for (var j in DIRECTIONS) {
384
-        if (DIRECTIONS[i].add(DIRECTIONS[j]).length() == 0 ||
385
-          DIRECTIONS[i].add(DIRECTIONS[j]).length() == 2) {
386
-          // Don't go back on ourselves, or don't carry on in same direction.
366
+export class DrawMove {
367
+  /**
368
+   * @param {State} state
369
+   */
370
+  constructor(state) {
371
+    this.state = state;
372
+    this.startPosition = null;
373
+    /** @type {!Array<{position, clockwise, startIsAlt, midPointIsAlt, endIsAlt}>} */
374
+    this.ends = [];
375
+  }
376
+
377
+  /** @inheritDoc */
378
+  start(position) {
379
+    this.startPosition =
380
+        c.TOUCH_ENABLED ? this.snapToNearest(position) : position;
381
+    this.ends = [];
382
+
383
+    // If this isn't a special cell then quit, or things get weird.
384
+    if (!this.state.getCell(this.startPosition).isSpecial()) {
385
+      return;
386
+    }
387
+    var context = this.state.getContext(this.startPosition);
388
+
389
+    var ends = [];
390
+    for (var i of c.DIRECTIONS) {
391
+      var midPoints = this.followLine(this.startPosition, i);
392
+      for (var midPoint of midPoints) {
393
+        // Clockwise is a lie, it is true if we move vertically first.
394
+        var clockwise = (i.x != 0);
395
+        var startIsAlt = c.ALT_SPECIAL_VALUES.indexOf(this.state.getCell(position).getRawValue()) != -1;
396
+        var midPointIsAlt = c.ALT_SPECIAL_VALUES.indexOf(this.state.getCell(midPoint).getRawValue()) != -1;
397
+
398
+        var midPointContext = this.state.getContext(midPoint);
399
+        // Special case, a straight line with no turns.
400
+        if (midPointContext.sum() == 1) {
401
+          ends.push({position: midPoint, clockwise, startIsAlt, endIsAlt: midPointIsAlt});
387 402
           continue;
388 403
         }
389
-        var secondEnds = this.followLine(midPoint, DIRECTIONS[j]);
390
-        // Ignore any directions that didn't go anywhere.
391
-        if (secondEnds.length == 0) {
392
-          continue;
404
+        // Continue following lines from the midpoint.
405
+        for (var j of c.DIRECTIONS) {
406
+          if (i.add(j).length() == 0 || i.add(j).length() == 2) {
407
+            // Don't go back on ourselves, or don't carry on in same direction.
408
+            continue;
409
+          }
410
+          var secondEnds = this.followLine(midPoint, j);
411
+          // Ignore any directions that didn't go anywhere.
412
+          if (secondEnds.length == 0) {
413
+            continue;
414
+          }
415
+          var secondEnd = secondEnds[0];
416
+          var endIsAlt = c.ALT_SPECIAL_VALUES.indexOf(this.state.getCell(secondEnd).getRawValue()) != -1;
417
+          // On the second line we don't care about multiple
418
+          // junctions, just the last.
419
+          ends.push({position: secondEnd, clockwise, startIsAlt, midPointIsAlt, endIsAlt});
393 420
         }
394
-        var secondEnd = secondEnds[0];
395
-        var endIsAlt = ALT_SPECIAL_VALUES.indexOf(this.state.getCell(secondEnd).getRawValue()) != -1;
396
-        // On the second line we don't care about multiple
397
-        // junctions, just the last.
398
-        ends.push({position: secondEnd,
399
-            clockwise: clockwise, startIsAlt: startIsAlt, midPointIsAlt: midPointIsAlt, endIsAlt: endIsAlt});
400 421
       }
401 422
     }
423
+    this.ends = ends;
424
+    // Redraw the new lines after we have cleared the existing ones.
425
+    this.move(this.startPosition);
402 426
   }
403
-  this.ends = ends;
404
-  // Redraw the new lines after we have cleared the existing ones.
405
-  this.move(this.startPosition);
406
-};
407
-
408
-/** @inheritDoc */
409
-ascii.DrawMove.prototype.move = function(position) {
410
-  this.state.clearDraw();
411
-  // Clear all the lines so we can draw them afresh.
412
-  for (var i in this.ends) {
413
-    drawLine(this.state, this.startPosition, this.ends[i].position,
414
-        this.ends[i].clockwise, ' ');
415
-  }
416
-  for (var i in this.ends) {
417
-    drawLine(this.state, position, this.ends[i].position,
418
-        this.ends[i].clockwise);
419
-  }
420
-  for (var i in this.ends) {
421
-    // If the ends or midpoint of the line was a alt character (arrow), need to preserve that.
422
-    if (this.ends[i].startIsAlt) {
423
-      this.state.drawValue(position, ALT_SPECIAL_VALUE);
427
+
428
+  /** @inheritDoc */
429
+  move(position) {
430
+    this.state.clearDraw();
431
+    // Clear all the lines so we can draw them afresh.
432
+    for (var end of this.ends) {
433
+      drawLine(this.state, this.startPosition, end.position, end.clockwise, ' ');
424 434
     }
425
-    if (this.ends[i].endIsAlt) {
426
-      this.state.drawValue(this.ends[i].position, ALT_SPECIAL_VALUE);
435
+    for (var i in this.ends) {
436
+      drawLine(this.state, position, end.position, end.clockwise);
427 437
     }
428
-    if (this.ends[i].midPointIsAlt) {
429
-      var midX = this.ends[i].clockwise ? this.ends[i].position.x : position.x;
430
-      var midY = this.ends[i].clockwise ? position.y : this.ends[i].position.y;
431
-      this.state.drawValue(new ascii.Vector(midX, midY), ALT_SPECIAL_VALUE);
438
+    for (var end of this.ends) {
439
+      // If the ends or midpoint of the line was a alt character (arrow), need to preserve that.
440
+      if (end.startIsAlt) {
441
+        this.state.drawValue(position, c.ALT_SPECIAL_VALUE);
442
+      }
443
+      if (end.endIsAlt) {
444
+        this.state.drawValue(end.position, c.ALT_SPECIAL_VALUE);
445
+      }
446
+      if (end.midPointIsAlt) {
447
+        var midX = end.clockwise ? end.position.x : position.x;
448
+        var midY = end.clockwise ? position.y : end.position.y;
449
+        this.state.drawValue(new Vector(midX, midY), c.ALT_SPECIAL_VALUE);
450
+      }
432 451
     }
433 452
   }
434
-};
435 453
 
436
-/** @inheritDoc */
437
-ascii.DrawMove.prototype.end = function() {
438
-  this.state.commitDraw();
439
-};
454
+  /** @inheritDoc */
455
+  end() {
456
+    this.state.commitDraw();
457
+  }
440 458
 
441
-/**
442
- * Follows a line in a given direction from the startPosition.
443
- * Returns a list of positions that were line 'junctions'. This is a bit of a
444
- * loose definition, but basically means a point around which we resize things.
445
- * @param {ascii.Vector} startPosition
446
- * @param {ascii.Vector} direction
447
- * @return {Array.<ascii.Vector>}
448
- */
449
-ascii.DrawMove.prototype.followLine = function(startPosition, direction) {
450
-  var endPosition = startPosition.clone();
451
-  var junctions = [];
452
-  while (true) {
453
-    var nextEnd = endPosition.add(direction);
454
-    if (!this.state.getCell(nextEnd).isSpecial()) {
455
-      // Junctions: Right angles and end T-Junctions.
456
-      if (!startPosition.equals(endPosition)) {
457
-        junctions.push(endPosition);
459
+  /**
460
+   * Follows a line in a given direction from the startPosition.
461
+   * Returns a list of positions that were line 'junctions'. This is a bit of a
462
+   * loose definition, but basically means a point around which we resize things.
463
+   * @param {Vector} startPosition
464
+   * @param {Vector} direction
465
+   * @return {!Array<Vector>}
466
+   */
467
+  followLine(startPosition, direction) {
468
+    var endPosition = startPosition.clone();
469
+    var junctions = [];
470
+    while (true) {
471
+      var nextEnd = endPosition.add(direction);
472
+      if (!this.state.getCell(nextEnd).isSpecial()) {
473
+        // Junctions: Right angles and end T-Junctions.
474
+        if (!startPosition.equals(endPosition)) {
475
+          junctions.push(endPosition);
476
+        }
477
+        return junctions;
458 478
       }
459
-      return junctions;
460
-    }
461 479
 
462
-    endPosition = nextEnd;
463
-    var context = this.state.getContext(endPosition);
464
-    // Junctions: Side T-Junctions.
465
-    if (context.sum() == 3) {
466
-      junctions.push(endPosition);
480
+      endPosition = nextEnd;
481
+      var context = this.state.getContext(endPosition);
482
+      // Junctions: Side T-Junctions.
483
+      if (context.sum() == 3) {
484
+        junctions.push(endPosition);
485
+      }
467 486
     }
468 487
   }
469
-};
470 488
 
471
-/**
472
- * For a given position, finds the nearest cell that is of any interest to the
473
- * move tool, e.g. a corner or a line. Will look up to 1 cell in each direction
474
- * including diagonally.
475
- * @param {ascii.Vector} position
476
- * @return {ascii.Vector}
477
- */
478
-ascii.DrawMove.prototype.snapToNearest = function(position) {
479
-  if (this.state.getCell(position).isSpecial()) {
480
-    return position;
481
-  }
482
-  var allDirections = DIRECTIONS.concat([
483
-    DIR_LEFT.add(DIR_UP),
484
-    DIR_LEFT.add(DIR_DOWN),
485
-    DIR_RIGHT.add(DIR_UP),
486
-    DIR_RIGHT.add(DIR_DOWN)]);
487
-
488
-  var bestDirection = null;
489
-  var bestContextSum = 0;
490
-  for (var i in allDirections) {
491
-    // Find the most connected cell, essentially.
492
-    var newPos = position.add(allDirections[i]);
493
-    var contextSum = this.state.getContext(newPos).sum();
494
-    if (this.state.getCell(newPos).isSpecial() &&
495
-        contextSum > bestContextSum) {
496
-      bestDirection = allDirections[i];
497
-      bestContextSum = contextSum;
489
+  /**
490
+   * For a given position, finds the nearest cell that is of any interest to the
491
+   * move tool, e.g. a corner or a line. Will look up to 1 cell in each direction
492
+   * including diagonally.
493
+   * @param {Vector} position
494
+   * @return {Vector}
495
+   */
496
+  snapToNearest(position) {
497
+    if (this.state.getCell(position).isSpecial()) {
498
+      return position;
498 499
     }
500
+    var allDirections = c.DIRECTIONS.concat([
501
+      c.DIR_LEFT.add(c.DIR_UP),
502
+      c.DIR_LEFT.add(c.DIR_DOWN),
503
+      c.DIR_RIGHT.add(c.DIR_UP),
504
+      c.DIR_RIGHT.add(c.DIR_DOWN)]);
505
+
506
+    var bestDirection = null;
507
+    var bestContextSum = 0;
508
+    for (var direction of allDirections) {
509
+      // Find the most connected cell, essentially.
510
+      var newPos = position.add(direction);
511
+      var contextSum = this.state.getContext(newPos).sum();
512
+      if (this.state.getCell(newPos).isSpecial() &&
513
+          contextSum > bestContextSum) {
514
+        bestDirection = direction;
515
+        bestContextSum = contextSum;
516
+      }
517
+    }
518
+    if (bestDirection == null) {
519
+      // Didn't find anything, so just return the current cell.
520
+      return position;
521
+    }
522
+    return position.add(bestDirection);
499 523
   }
500
-  if (bestDirection == null) {
501
-    // Didn't find anything, so just return the current cell.
502
-    return position;
503
-  }
504
-  return position.add(bestDirection);
505
-};
506 524
 
507
-/** @inheritDoc */
508
-ascii.DrawMove.prototype.getCursor = function(position) {
509
-  if (this.state.getCell(position).isSpecial()) {
510
-    return 'pointer';
511
-  } else {
512
-    return 'default';
525
+  /** @inheritDoc */
526
+  getCursor(position) {
527
+    if (this.state.getCell(position).isSpecial()) {
528
+      return 'pointer';
529
+    } else {
530
+      return 'default';
531
+    }
513 532
   }
514
-};
515
-
516
-/** @inheritDoc */
517
-ascii.DrawMove.prototype.handleKey = function(value) {};
518 533
 
534
+  /** @inheritDoc */
535
+  handleKey(value) {}
536
+}

+ 223
- 226
js-lib/drive-controller.js View File

@@ -1,266 +1,263 @@
1
-/** @const */
2
-var CLIENT_ID = '125643747010-9s9n1ne2fnnuh5v967licfkt83r4vba5.apps.googleusercontent.com';
3
-/** @const */
4
-var SCOPES = 'https://www.googleapis.com/auth/drive';
5
-/** @const */
6
-var DEVELOPER_KEY = 'AIzaSyBbKO_v9p-G9StQjYmtUYLP6Px4MkGions';
1
+import Vector from './vector';
2
+import State from './state';
3
+import View from './view';
7 4
 
8
-/**
9
- * 
10
- * @constructor
11
- */
12
-ascii.DriveController = function(state, view) {
13
-  /** @type {boolean} */
14
-  this.driveEnabled = false;
15
-  /** @type {ascii.State} */
16
-  this.state = state;
17
-  /** @type {ascii.View} */
18
-  this.view = view;
19
-  // This is a file resource, as defined by the Drive API.
20
-  /** @type {Object} */
21
-  this.file = null;
22
-  /** @type {string} */
23
-  this.cachedContent = '';
5
+const CLIENT_ID = '125643747010-9s9n1ne2fnnuh5v967licfkt83r4vba5.apps.googleusercontent.com';
6
+const SCOPES = 'https://www.googleapis.com/auth/drive';
7
+const DEVELOPER_KEY = 'AIzaSyBbKO_v9p-G9StQjYmtUYLP6Px4MkGions';
24 8
 
25
-  this.tryInitialAuth();
9
+export default class DriveController {
10
+  constructor(state, view) {
11
+    /** @type {boolean} */
12
+    this.driveEnabled = false;
13
+    /** @type {State} */
14
+    this.state = state;
15
+    /** @type {View} */
16
+    this.view = view;
17
+    // This is a file resource, as defined by the Drive API.
18
+    /** @type {Object} */
19
+    this.file = null;
20
+    /** @type {string} */
21
+    this.cachedText = '';
26 22
 
27
-  $('#drive-button').click(function() {
28
-    if (!this.driveEnabled) {
29
-      // Haven't been able to immediately auth yet, so try full auth.
30
-      this.checkAuth(false);
31
-      this.waitForFullAuth();
32
-    } else {
33
-      this.loadDialog();
34
-    }
35
-  }.bind(this));
23
+    this.tryInitialAuth();
36 24
 
37
-  $('#drive-filename').click(function() {
38
-    var currentTitle = '' + $('#drive-filename').text();
39
-    var title = prompt('Enter new filename:', currentTitle);
40
-    this.file['title'] = title;
41
-    this.save();
42
-    this.loadFileList();
43
-  }.bind(this));
25
+    $('#drive-button').click(() => {
26
+      if (!this.driveEnabled) {
27
+        // Haven't been able to immediately auth yet, so try full auth.
28
+        this.checkAuth(false);
29
+        this.waitForFullAuth();
30
+      } else {
31
+        this.loadDialog();
32
+      }
33
+    });
44 34
 
45
-  this.loopSave();
35
+    $('#drive-filename').click(() => {
36
+      var currentTitle = '' + $('#drive-filename').text();
37
+      var title = prompt('Enter new filename:', currentTitle);
38
+      this.file['title'] = title;
39
+      this.save();
40
+      this.loadFileList();
41
+    });
46 42
 
47
-  $(window).bind('hashchange', function() {
48
-    this.loadFromHash();
49
-  }.bind(this));
43
+    this.loopSave();
50 44
 
51
-  $('#drive-new-file-button').click(function() {
52
-    this.file = null;
53
-    this.state.clear();
54
-    window.location.hash = '';
55
-    this.save();
56
-    $('#drive-dialog').removeClass('visible');
57
-  }.bind(this));
58
-};
45
+    $(window).on('hashchange', this.loadFromHash);
59 46
 
60
-/**
61
- * Check if the current user has authorized the application.
62
- */
63
-ascii.DriveController.prototype.checkAuth = function(immediate) {
64
-  window['gapi']['auth']['authorize']({
65
-      'client_id': CLIENT_ID,
66
-      'scope': SCOPES,
67
-      'immediate': immediate},
68
-      function(result) {
69
-        if (result && !result.error && !this.driveEnabled) {
70
-          this.driveEnabled = true;
71
-          $('#drive-button').addClass('active');
72
-          // We are authorized, so let's se if we can load from the URL hash.
73
-          // This seems to fail if we do it too early.
74
-          window.setTimeout(function() { this.loadFromHash(); }.bind(this), 500);
75
-        }
76
-      }.bind(this));
77
-};
47
+    $('#drive-new-file-button').click(() => {
48
+      this.file = null;
49
+      this.state.clear();
50
+      window.location.hash = '';
51
+      this.save();
52
+      $('#drive-dialog').removeClass('visible');
53
+    });
54
+  }
78 55
 
79
-ascii.DriveController.prototype.tryInitialAuth = function() {
80
-  if (window['gapi'] && window['gapi']['auth'] && window['gapi']['auth']['authorize']) {
81
-    this.checkAuth(true);
82
-  } else {
83
-    window.setTimeout(function() {
84
-      this.tryInitialAuth();
85
-    }.bind(this), 500);
56
+  /**
57
+   * Check if the current user has authorized the application.
58
+   */
59
+  checkAuth(immediate) {
60
+    window['gapi']['auth']['authorize']({
61
+        'client_id': CLIENT_ID,
62
+        'scope': SCOPES,
63
+        'immediate': immediate},
64
+        result => {
65
+          if (result && !result.error && !this.driveEnabled) {
66
+            this.driveEnabled = true;
67
+            $('#drive-button').addClass('active');
68
+            // We are authorized, so let's se if we can load from the URL hash.
69
+            // This seems to fail if we do it too early.
70
+            window.setTimeout(this.loadFromHash, 500);
71
+          }
72
+        });
86 73
   }
87
-};
88 74
 
89
-ascii.DriveController.prototype.waitForFullAuth = function() {
90
-  window.setTimeout(function() {
91
-    if (!this.driveEnabled) {
75
+  tryInitialAuth() {
76
+    if (window['gapi'] && window['gapi']['auth'] && window['gapi']['auth']['authorize']) {
92 77
       this.checkAuth(true);
93
-      this.waitForFullAuth();
94 78
     } else {
95
-      this.loadDialog();
79
+      window.setTimeout(() => {
80
+        this.tryInitialAuth();
81
+      }, 500);
96 82
     }
97
-  }.bind(this), 1000);
98
-};
83
+  }
99 84
 
100
-/**
101
- * Handles a file resource being returned from Drive.
102
- */
103
-ascii.DriveController.prototype.handleFile = function(file) {
104
-  this.file = file;
105
-  $('#drive-filename').text(file['title']);
106
-  window.location.hash = file['id'];
107
-};
85
+  waitForFullAuth() {
86
+    window.setTimeout(() => {
87
+      if (!this.driveEnabled) {
88
+        this.checkAuth(true);
89
+        this.waitForFullAuth();
90
+      } else {
91
+        this.loadDialog();
92
+      }
93
+    }, 1000);
94
+  }
108 95
 
96
+  /**
97
+   * Handles a file resource being returned from Drive.
98
+   */
99
+  handleFile(file) {
100
+    this.file = file;
101
+    $('#drive-filename').text(file['title']);
102
+    window.location.hash = file['id'];
103
+  }
109 104
 
110
-/**
111
- * Loads the drive dialog.
112
- */
113
-ascii.DriveController.prototype.loadDialog = function() {
114
-  $('#drive-dialog').addClass('visible');
115 105
 
116
-  var text = this.state.outputText();
117
-  // Don't save diagram if empty, just get's annoying.
118
-  if (text.length > 5 && text != this.cachedText) {
119
-    this.save();
120
-  }
121
-  this.loadFileList();
122
-};
106
+  /**
107
+   * Loads the drive dialog.
108
+   */
109
+  loadDialog() {
110
+    $('#drive-dialog').addClass('visible');
123 111
 
124
-ascii.DriveController.prototype.loadFileList = function() {
125
-  this.safeExecute(this.getListRequest(), function(result) {
126
-    $('#drive-file-list').children().remove();
127
-    var items = result['items'];
128
-    for (var i in items) {
129
-      var entry = document.createElement('li');
130
-      var title = document.createElement('a');
131
-      entry.appendChild(title);
132
-      title.href = '#' + items[i]['id'];
133
-      $(title).click(function() { $('#drive-dialog').removeClass('visible'); });
134
-      title.innerHTML = items[i]['title'];
135
-      $('#drive-file-list').append(entry);
112
+    var text = this.state.outputText();
113
+    // Don't save diagram if empty, just get's annoying.
114
+    if (text.length > 5 && text != this.cachedText) {
115
+      this.save();
136 116
     }
137
-  }.bind(this));
138
-}
117
+    this.loadFileList();
118
+  }
139 119
 
140
-ascii.DriveController.prototype.safeExecute = function(request, callback) {
141
-  // Could make the API call, don't blow up tho (mobiles n stuff).
142
-  try {
143
-    request['execute'](function(result) {
144
-      if (!result['error']) {
145
-        callback(result);
120
+  loadFileList() {
121
+    this.safeExecute(this.getListRequest(), result => {
122
+      $('#drive-file-list').children().remove();
123
+      var items = result['items'];
124
+      for (var i in items) {
125
+        var entry = document.createElement('li');
126
+        var title = document.createElement('a');
127
+        entry.appendChild(title);
128
+        title.href = '#' + items[i]['id'];
129
+        $(title).click(function() { $('#drive-dialog').removeClass('visible'); });
130
+        title.innerHTML = items[i]['title'];
131
+        $('#drive-file-list').append(entry);
146 132
       }
147 133
     });
148
-  } catch (e) {}
149
-};
134
+  }
150 135
 
151
-/**
152
- * Repeatedly save the diagram if it is editable and loaded.
153
- */
154
-ascii.DriveController.prototype.loopSave = function() {
155
-  var text = this.state.outputText();
156
-  if (text != this.cachedText && this.file && this.file['editable']) {
157
-    this.save();
136
+  safeExecute(request, callback) {
137
+    // Could make the API call, don't blow up tho (mobiles n stuff).
138
+    try {
139
+      request['execute'](function(result) {
140
+        if (!result['error']) {
141
+          callback(result);
142
+        }
143
+      });
144
+    } catch (e) {}
158 145
   }
159
-  window.setTimeout(function() {
160
-    this.loopSave();
161
-  }.bind(this), 5000);
162
-}
163 146
 
164
-/**
165
- * Saves the current diagram to drive.
166
- */
167
-ascii.DriveController.prototype.save = function() {
168
-  var text = this.state.outputText();
169
-  $('#drive-save-state').text('Saving...');
170
-  this.safeExecute(this.getSaveRequest(text), function(result) {
171
-    this.handleFile(result);
172
-    $('#drive-save-state').text('Saved');
173
-    this.cachedText = text;
174
-  }.bind(this));
175
-};
147
+  /**
148
+   * Repeatedly save the diagram if it is editable and loaded.
149
+   */
150
+  loopSave() {
151
+    var text = this.state.outputText();
152
+    if (text != this.cachedText && this.file && this.file['editable']) {
153
+      this.save();
154
+    }
155
+    window.setTimeout(() => {
156
+      this.loopSave();
157
+    }, 5000);
158
+  }
176 159
 
177
-ascii.DriveController.prototype.loadFromHash = function() {
178
-  if (window.location.hash.length > 1) {
179
-    $('#drive-save-state').text('Loading...');
180
-    var fileId = window.location.hash.substr(1, window.location.hash.length - 1);
181
-    this.safeExecute(this.getLoadRequest(fileId), function(result) {
160
+  /**
161
+   * Saves the current diagram to drive.
162
+   */
163
+  save() {
164
+    var text = this.state.outputText();
165
+    $('#drive-save-state').text('Saving...');
166
+    this.safeExecute(this.getSaveRequest(text), result => {
182 167
       this.handleFile(result);
183
-      this.reloadFileContent();
184
-    }.bind(this));
168
+      $('#drive-save-state').text('Saved');
169
+      this.cachedText = text;
170
+    });
185 171
   }
186
-};
187 172
 
188
-ascii.DriveController.prototype.reloadFileContent = function() {
189
-  this.downloadFile(this.file['downloadUrl'], function(content) {
190
-    $('#drive-save-state').text('Loaded');
191
-    this.state.clear();
192
-    this.state.fromText(content, this.view.screenToCell(new ascii.Vector(
193
-            this.view.canvas.width / 2,
194
-            this.view.canvas.height / 2)));
195
-    this.state.commitDraw();
196
-    this.cachedText = this.state.outputText();
197
-  }.bind(this));
198
-};
173
+  loadFromHash() {
174
+    if (window.location.hash.length > 1) {
175
+      $('#drive-save-state').text('Loading...');
176
+      var fileId = window.location.hash.substr(1, window.location.hash.length - 1);
177
+      this.safeExecute(this.getLoadRequest(fileId), result => {
178
+        this.handleFile(result);
179
+        this.reloadFileContent();
180
+      });
181
+    }
182
+  }
199 183
 
200
-ascii.DriveController.prototype.getSaveRequest = function(text) {
201
-  var boundary = '-------314159265358979323846';
202
-  var delimiter = "\r\n--" + boundary + "\r\n";
203
-  var close_delim = "\r\n--" + boundary + "--";
184
+  reloadFileContent() {
185
+    this.downloadFile(this.file['downloadUrl'], content => {
186
+      $('#drive-save-state').text('Loaded');
187
+      this.state.clear();
188
+      this.state.fromText(content, this.view.screenToCell(new Vector(
189
+              this.view.canvas.width / 2,
190
+              this.view.canvas.height / 2)));
191
+      this.state.commitDraw();
192
+      this.cachedText = this.state.outputText();
193
+    });
194
+  }
204 195