Browse Source

Fix bad merge (#79)

* Fix bad merge

Some classes were still prefixed by ascii,
and constants not prefixed by c.

* More es6 changes and refactorings

Added helper methods to vector for creating vector from touch event and
mouse event. Added up, down, left, right helper methods.

* Refactor draw.js into one module per draw function
master
Brian Schlenker 1 year ago
parent
commit
8277399c61

+ 5
- 1
compile.sh View File

@@ -1,5 +1,9 @@
1
+#!/bin/bash
2
+
1 3
 java -client -jar closure-compiler.jar \
2
-  --js js-lib/*.js \
4
+  --js js-lib/**.js \
5
+  --js js-lib/**/*.js \
3 6
   --warning_level=VERBOSE --formatting=PRETTY_PRINT --language_in=ECMASCRIPT6 --compilation_level=ADVANCED_OPTIMIZATIONS \
4 7
   --externs=jquery-3.1-externs.js \
5 8
   > js-compiled.js
9
+

+ 3
- 3
js-lib/common.js View File

@@ -20,12 +20,12 @@ export class Box {
20 20
     /** type {number} */ this.endY = Math.max(a.y, b.y);
21 21
   }
22 22
 
23
-  /** @return {Vector} */
23
+  /** @return {!Vector} */
24 24
   topLeft() {
25 25
     return new Vector(this.startX, this.startY);
26 26
   }
27 27
 
28
-  /** @return {Vector} */
28
+  /** @return {!Vector} */
29 29
   bottomRight() {
30 30
     return new Vector(this.endX, this.endY);
31 31
   }
@@ -54,7 +54,7 @@ export class Cell {
54 54
 
55 55
   /** @return {boolean} */
56 56
   isSpecial() {
57
-    return ALL_SPECIAL_VALUES.indexOf(this.getRawValue()) != -1;
57
+    return ALL_SPECIAL_VALUES.includes(this.getRawValue());
58 58
   }
59 59
 
60 60
   /** @return {boolean} */

+ 2
- 2
js-lib/controller.js View File

@@ -2,7 +2,6 @@ import * as c from './constants';
2 2
 import Vector from './vector';
3 3
 import View from './view';
4 4
 import State from './state';
5
-import DrawSelect from './draw-select';
6 5
 import {
7 6
   DrawFunction,
8 7
   DrawBox,
@@ -11,7 +10,8 @@ import {
11 10
   DrawErase,
12 11
   DrawMove,
13 12
   DrawText,
14
-} from './draw';
13
+  DrawSelect,
14
+} from './draw/index';
15 15
 
16 16
 
17 17
 /**

+ 0
- 536
js-lib/draw.js View File

@@ -1,536 +0,0 @@
1
-import * as c from './constants';
2
-import State from './state';
3
-import Vector from './vector';
4
-import { Box } from './common';
5
-
6
-/**
7
- * All drawing classes and functions.
8
- */
9
-
10
-/**
11
- * Draws a line on the diagram state.
12
- *
13
- * @param {State} state
14
- * @param {Vector} startPosition
15
- * @param {Vector} endPosition
16
- * @param {boolean} clockwise
17
- * @param {string=} value
18
- */
19
-function drawLine(state, startPosition, endPosition, clockwise, value = c.SPECIAL_VALUE) {
20
-  var box = new Box(startPosition, endPosition);
21
-  var startX = box.startX;
22
-  var startY = box.startY;
23
-  var endX = box.endX;
24
-  var endY = box.endY;
25
-
26
-  var midX = clockwise ? endPosition.x : startPosition.x;
27
-  var midY = clockwise ? startPosition.y : endPosition.y;
28
-
29
-  while (startX++ < endX) {
30
-    var position = new Vector(startX, midY);
31
-    var context = state.getContext(new Vector(startX, midY));
32
-    // Don't erase any lines that we cross.
33
-    if (value != ' ' || context.up + context.down != 2) {
34
-      state.drawValueIncremental(position, value);
35
-    }
36
-  }
37
-  while (startY++ < endY) {
38
-    var position = new Vector(midX, startY);
39
-    var context = state.getContext(new Vector(midX, startY));
40
-    // Don't erase any lines that we cross.
41
-    if (value != ' ' || context.left + context.right != 2) {
42
-      state.drawValueIncremental(position, value);
43
-    }
44
-  }
45
-
46
-  state.drawValue(startPosition, value);
47
-  state.drawValue(endPosition, value);
48
-  state.drawValueIncremental(new Vector(midX, midY), value);
49
-}
50
-
51
-/**
52
- * Common interface for different drawing functions, e.g. box, line, etc.
53
- * @interface
54
- */
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
-}
70
-
71
-/**
72
- * @implements {DrawFunction}
73
- */
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
-}
110
-
111
-/**
112
- * @implements {DrawFunction}
113
- */
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
-}
160
-
161
-/**
162
- * @implements {DrawFunction}
163
- */
164
-export class DrawFreeform {
165
-  /**
166
-   * @param {State} state
167
-   * @param {?string} value
168
-   */
169
-  constructor(state, value) {
170
-    this.state = state;
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';
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
-}
211
-
212
-/**
213
- * @implements {DrawFunction}
214
- */
215
-export class DrawText {
216
-  /**
217
-   * @param {State} state
218
-   */
219
-  constructor(state, view) {
220
-    this.state = state;
221
-    this.startPosition = null;
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();});});
250
-    }
251
-  }
252
-
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
-    }
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);
291
-    }
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));
310
-  }
311
-}
312
-
313
-/**
314
- * @implements {DrawFunction}
315
- */
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
-      }
346
-    }
347
-  }
348
-
349
-  /** @inheritDoc */
350
-  end() {
351
-    this.state.commitDraw();
352
-  }
353
-
354
-  /** @inheritDoc */
355
-  getCursor(position) {
356
-    return 'crosshair';
357
-  }
358
-
359
-  /** @inheritDoc */
360
-  handleKey(value) {}
361
-}
362
-
363
-/**
364
- * @implements {DrawFunction}
365
- */
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});
402
-          continue;
403
-        }
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});
420
-        }
421
-      }
422
-    }
423
-    this.ends = ends;
424
-    // Redraw the new lines after we have cleared the existing ones.
425
-    this.move(this.startPosition);
426
-  }
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, ' ');
434
-    }
435
-    for (var i in this.ends) {
436
-      drawLine(this.state, position, end.position, end.clockwise);
437
-    }
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
-      }
451
-    }
452
-  }
453
-
454
-  /** @inheritDoc */
455
-  end() {
456
-    this.state.commitDraw();
457
-  }
458
-
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;
478
-      }
479
-
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
-      }
486
-    }
487
-  }
488
-
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;
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);
523
-  }
524
-
525
-  /** @inheritDoc */
526
-  getCursor(position) {
527
-    if (this.state.getCell(position).isSpecial()) {
528
-      return 'pointer';
529
-    } else {
530
-      return 'default';
531
-    }
532
-  }
533
-
534
-  /** @inheritDoc */
535
-  handleKey(value) {}
536
-}

+ 44
- 0
js-lib/draw/box.js View File

@@ -0,0 +1,44 @@
1
+import State from '../state';
2
+import Vector from '../vector';
3
+import DrawFunction from './function';
4
+import { drawLine } from './utils';
5
+
6
+/**
7
+ * @implements {DrawFunction}
8
+ */
9
+export default class DrawBox {
10
+  /**
11
+   * @param {State} state
12
+   */
13
+  constructor(state) {
14
+    this.state = state;
15
+    /** @type {Vector} */ this.startPosition = null;
16
+    /** @type {Vector} */ this.endPosition = null;
17
+  }
18
+
19
+  /** @inheritDoc */
20
+  start(position) {
21
+    this.startPosition = position;
22
+  }
23
+
24
+  /** @inheritDoc */
25
+  move(position) {
26
+    this.endPosition = position;
27
+    this.state.clearDraw();
28
+    drawLine(this.state, this.startPosition, position, true);
29
+    drawLine(this.state, this.startPosition, position, false);
30
+  }
31
+
32
+  /** @inheritDoc */
33
+  end() {
34
+    this.state.commitDraw();
35
+  }
36
+
37
+  /** @inheritDoc */
38
+  getCursor(position) {
39
+    return 'crosshair';
40
+  }
41
+
42
+  /** @inheritDoc */
43
+  handleKey(value) {};
44
+}

+ 54
- 0
js-lib/draw/erase.js View File

@@ -0,0 +1,54 @@
1
+import { DrawFunction } from './function';
2
+import { ERASE_CHAR } from '../constants';
3
+import State from '../state';
4
+import Vector from '../vector';
5
+
6
+/**
7
+ * @implements {DrawFunction}
8
+ */
9
+export default class DrawErase {
10
+  /**
11
+   * @param {State} state
12
+   */
13
+  constructor(state) {
14
+    this.state = state;
15
+    this.startPosition = null;
16
+    this.endPosition = null;
17
+  }
18
+
19
+  /** @inheritDoc */
20
+  start(position) {
21
+    this.startPosition = position;
22
+    this.move(position);
23
+  }
24
+
25
+  /** @inheritDoc */
26
+  move(position) {
27
+    this.state.clearDraw();
28
+    this.endPosition = position;
29
+
30
+    var startX = Math.min(this.startPosition.x, this.endPosition.x);
31
+    var startY = Math.min(this.startPosition.y, this.endPosition.y);
32
+    var endX = Math.max(this.startPosition.x, this.endPosition.x);
33
+    var endY = Math.max(this.startPosition.y, this.endPosition.y);
34
+
35
+    for (var i = startX; i <= endX; i++) {
36
+      for (var j = startY; j <= endY; j++) {
37
+        this.state.drawValue(new Vector(i, j), ERASE_CHAR);
38
+      }
39
+    }
40
+  }
41
+
42
+  /** @inheritDoc */
43
+  end() {
44
+    this.state.commitDraw();
45
+  }
46
+
47
+  /** @inheritDoc */
48
+  getCursor(position) {
49
+    return 'crosshair';
50
+  }
51
+
52
+  /** @inheritDoc */
53
+  handleKey(value) {}
54
+}

+ 54
- 0
js-lib/draw/freeform.js View File

@@ -0,0 +1,54 @@
1
+import { DrawFunction } from './function';
2
+import { TOUCH_ENABLED } from '../constants';
3
+import State from '../state';
4
+
5
+/**
6
+ * @implements {DrawFunction}
7
+ */
8
+export default class DrawFreeform {
9
+  /**
10
+   * @param {State} state
11
+   * @param {?string} value
12
+   */
13
+  constructor(state, value) {
14
+    this.state = state;
15
+    this.value = value;
16
+    if (TOUCH_ENABLED) {
17
+      $('#freeform-tool-input').val('');
18
+      $('#freeform-tool-input').hide(0, function() {$('#freeform-tool-input').show(0, function() {$('#freeform-tool-input').focus();});});
19
+    }
20
+  }
21
+
22
+  /** @inheritDoc */
23
+  start(position) {
24
+    this.state.drawValue(position, this.value);
25
+  }
26
+
27
+  /** @inheritDoc */
28
+  move(position) {
29
+    this.state.drawValue(position, this.value);
30
+  }
31
+
32
+  /** @inheritDoc */
33
+  end() {
34
+    this.state.commitDraw();
35
+  }
36
+
37
+  /** @inheritDoc */
38
+  getCursor(position) {
39
+    return 'crosshair';
40
+  }
41
+
42
+  /** @inheritDoc */
43
+  handleKey(value) {
44
+    if (TOUCH_ENABLED) {
45
+      this.value = $('#freeform-tool-input').val().substr(0, 1);
46
+      $('#freeform-tool-input').blur();
47
+      $('#freeform-tool-input').hide(0);
48
+    }
49
+    if (value.length == 1) {
50
+      // The value is not a special character, so lets use it.
51
+      this.value = value;
52
+    }
53
+  }
54
+}

+ 21
- 0
js-lib/draw/function.js View File

@@ -0,0 +1,21 @@
1
+import Vector from '../vector';
2
+
3
+/**
4
+ * Common interface for different drawing functions, e.g. box, line, etc.
5
+ * @interface
6
+ */
7
+export default class DrawFunction {
8
+  /** Start of drawing. @param {Vector} position */
9
+  start(position) {};
10
+  /** Drawing move. @param {Vector} position */
11
+  move(position) {};
12
+  /** End of drawing. */
13
+  end() {};
14
+  /** Cursor for given cell.
15
+   * @param {Vector} position
16
+   * @return {string}
17
+   */
18
+  getCursor(position) {};
19
+  /** Handle the key with given value being pressed. @param {string} value */
20
+  handleKey(value) {};
21
+}

+ 9
- 0
js-lib/draw/index.js View File

@@ -0,0 +1,9 @@
1
+export { default as DrawFunction } from './function';
2
+export { default as DrawBox } from './box';
3
+export { default as DrawBoxText } from './boxtext';
4
+export { default as DrawErase } from './erase';
5
+export { default as DrawLine } from './line';
6
+export { default as DrawSelect } from './select';
7
+export { default as DrawText } from './text';
8
+export { default as DrawMove } from './move';
9
+export { default as DrawFreeform } from './freeform';

+ 55
- 0
js-lib/draw/line.js View File

@@ -0,0 +1,55 @@
1
+import { DrawFunction } from './function';
2
+import { drawLine } from './utils';
3
+import { ALT_SPECIAL_VALUE } from '../constants';
4
+import State from '../state';
5
+import Vector from '../vector';
6
+
7
+/**
8
+ * @implements {DrawFunction}
9
+ */
10
+export default class DrawLine {
11
+  /**
12
+   * @param {State} state
13
+   * @param {boolean} isArrow
14
+   */
15
+  constructor(state, isArrow) {
16
+    this.state = state;
17
+    this.isArrow = isArrow;
18
+    /** @type {Vector} */ this.startPosition = null;
19
+  }
20
+
21
+  /** @inheritDoc */
22
+  start(position) {
23
+    this.startPosition = position;
24
+  }
25
+
26
+  /** @inheritDoc */
27
+  move(position) {
28
+    this.state.clearDraw();
29
+
30
+    // Try to infer line orientation.
31
+    // TODO: Split the line into two lines if we can't satisfy both ends.
32
+    var startContext = this.state.getContext(this.startPosition);
33
+    var endContext = this.state.getContext(position);
34
+    var clockwise = (startContext.up && startContext.down) ||
35
+        (endContext.left && endContext.right);
36
+
37
+    drawLine(this.state, this.startPosition, position, clockwise);
38
+    if (this.isArrow) {
39
+      this.state.drawValue(position, ALT_SPECIAL_VALUE);
40
+    }
41
+  }
42
+
43
+  /** @inheritDoc */
44
+  end() {
45
+    this.state.commitDraw();
46
+  }
47
+
48
+  /** @inheritDoc */
49
+  getCursor(position) {
50
+    return 'crosshair';
51
+  }
52
+
53
+  /** @inheritDoc */
54
+  handleKey(value) {};
55
+}

+ 180
- 0
js-lib/draw/move.js View File

@@ -0,0 +1,180 @@
1
+import { DrawFunction } from './function';
2
+import { drawLine } from './utils';
3
+import State from '../state';
4
+import Vector from '../vector';
5
+import * as c from '../constants';
6
+
7
+/**
8
+ * @implements {DrawFunction}
9
+ */
10
+export default class DrawMove {
11
+  /**
12
+   * @param {State} state
13
+   */
14
+  constructor(state) {
15
+    this.state = state;
16
+    this.startPosition = null;
17
+    /** @type {!Array<{position, clockwise, startIsAlt, midPointIsAlt, endIsAlt}>} */
18
+    this.ends = [];
19
+  }
20
+
21
+  /** @inheritDoc */
22
+  start(position) {
23
+    this.startPosition =
24
+        c.TOUCH_ENABLED ? this.snapToNearest(position) : position;
25
+    this.ends = [];
26
+
27
+    // If this isn't a special cell then quit, or things get weird.
28
+    if (!this.state.getCell(this.startPosition).isSpecial()) {
29
+      return;
30
+    }
31
+    var context = this.state.getContext(this.startPosition);
32
+
33
+    var ends = [];
34
+    for (var i of c.DIRECTIONS) {
35
+      var midPoints = this.followLine(this.startPosition, i);
36
+      for (var midPoint of midPoints) {
37
+        // Clockwise is a lie, it is true if we move vertically first.
38
+        var clockwise = (i.x != 0);
39
+        var startIsAlt = c.ALT_SPECIAL_VALUES.indexOf(this.state.getCell(position).getRawValue()) != -1;
40
+        var midPointIsAlt = c.ALT_SPECIAL_VALUES.indexOf(this.state.getCell(midPoint).getRawValue()) != -1;
41
+
42
+        var midPointContext = this.state.getContext(midPoint);
43
+        // Special case, a straight line with no turns.
44
+        if (midPointContext.sum() == 1) {
45
+          ends.push({position: midPoint, clockwise, startIsAlt, endIsAlt: midPointIsAlt});
46
+          continue;
47
+        }
48
+        // Continue following lines from the midpoint.
49
+        for (var j of c.DIRECTIONS) {
50
+          if (i.add(j).length() == 0 || i.add(j).length() == 2) {
51
+            // Don't go back on ourselves, or don't carry on in same direction.
52
+            continue;
53
+          }
54
+          var secondEnds = this.followLine(midPoint, j);
55
+          // Ignore any directions that didn't go anywhere.
56
+          if (secondEnds.length == 0) {
57
+            continue;
58
+          }
59
+          var secondEnd = secondEnds[0];
60
+          var endIsAlt = c.ALT_SPECIAL_VALUES.indexOf(this.state.getCell(secondEnd).getRawValue()) != -1;
61
+          // On the second line we don't care about multiple
62
+          // junctions, just the last.
63
+          ends.push({position: secondEnd, clockwise, startIsAlt, midPointIsAlt, endIsAlt});
64
+        }
65
+      }
66
+    }
67
+    this.ends = ends;
68
+    // Redraw the new lines after we have cleared the existing ones.
69
+    this.move(this.startPosition);
70
+  }
71
+
72
+  /** @inheritDoc */
73
+  move(position) {
74
+    this.state.clearDraw();
75
+    // Clear all the lines so we can draw them afresh.
76
+    for (var end of this.ends) {
77
+      drawLine(this.state, this.startPosition, end.position, end.clockwise, ' ');
78
+    }
79
+    for (var i in this.ends) {
80
+      drawLine(this.state, position, end.position, end.clockwise);
81
+    }
82
+    for (var end of this.ends) {
83
+      // If the ends or midpoint of the line was a alt character (arrow), need to preserve that.
84
+      if (end.startIsAlt) {
85
+        this.state.drawValue(position, c.ALT_SPECIAL_VALUE);
86
+      }
87
+      if (end.endIsAlt) {
88
+        this.state.drawValue(end.position, c.ALT_SPECIAL_VALUE);
89
+      }
90
+      if (end.midPointIsAlt) {
91
+        var midX = end.clockwise ? end.position.x : position.x;
92
+        var midY = end.clockwise ? position.y : end.position.y;
93
+        this.state.drawValue(new Vector(midX, midY), c.ALT_SPECIAL_VALUE);
94
+      }
95
+    }
96
+  }
97
+
98
+  /** @inheritDoc */
99
+  end() {
100
+    this.state.commitDraw();
101
+  }
102
+
103
+  /**
104
+   * Follows a line in a given direction from the startPosition.
105
+   * Returns a list of positions that were line 'junctions'. This is a bit of a
106
+   * loose definition, but basically means a point around which we resize things.
107
+   * @param {Vector} startPosition
108
+   * @param {Vector} direction
109
+   * @return {!Array<Vector>}
110
+   */
111
+  followLine(startPosition, direction) {
112
+    var endPosition = startPosition.clone();
113
+    var junctions = [];
114
+    while (true) {
115
+      var nextEnd = endPosition.add(direction);
116
+      if (!this.state.getCell(nextEnd).isSpecial()) {
117
+        // Junctions: Right angles and end T-Junctions.
118
+        if (!startPosition.equals(endPosition)) {
119
+          junctions.push(endPosition);
120
+        }
121
+        return junctions;
122
+      }
123
+
124
+      endPosition = nextEnd;
125
+      var context = this.state.getContext(endPosition);
126
+      // Junctions: Side T-Junctions.
127
+      if (context.sum() == 3) {
128
+        junctions.push(endPosition);
129
+      }
130
+    }
131
+  }
132
+
133
+  /**
134
+   * For a given position, finds the nearest cell that is of any interest to the
135
+   * move tool, e.g. a corner or a line. Will look up to 1 cell in each direction
136
+   * including diagonally.
137
+   * @param {Vector} position
138
+   * @return {Vector}
139
+   */
140
+  snapToNearest(position) {
141
+    if (this.state.getCell(position).isSpecial()) {
142
+      return position;
143
+    }
144
+    var allDirections = c.DIRECTIONS.concat([
145
+      c.DIR_LEFT.add(c.DIR_UP),
146
+      c.DIR_LEFT.add(c.DIR_DOWN),
147
+      c.DIR_RIGHT.add(c.DIR_UP),
148
+      c.DIR_RIGHT.add(c.DIR_DOWN)]);
149
+
150
+    var bestDirection = null;
151
+    var bestContextSum = 0;
152
+    for (var direction of allDirections) {
153
+      // Find the most connected cell, essentially.
154
+      var newPos = position.add(direction);
155
+      var contextSum = this.state.getContext(newPos).sum();
156
+      if (this.state.getCell(newPos).isSpecial() &&
157
+          contextSum > bestContextSum) {
158
+        bestDirection = direction;
159
+        bestContextSum = contextSum;
160
+      }
161
+    }
162
+    if (bestDirection == null) {
163
+      // Didn't find anything, so just return the current cell.
164
+      return position;
165
+    }
166
+    return position.add(bestDirection);
167
+  }
168
+
169
+  /** @inheritDoc */
170
+  getCursor(position) {
171
+    if (this.state.getCell(position).isSpecial()) {
172
+      return 'pointer';
173
+    } else {
174
+      return 'default';
175
+    }
176
+  }
177
+
178
+  /** @inheritDoc */
179
+  handleKey(value) {}
180
+}

js-lib/draw-select.js → js-lib/draw/select.js View File

@@ -1,8 +1,9 @@
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';
1
+import DrawFunction from './function';
2
+import { ERASE_CHAR, KEY_COPY, KEY_CUT, KEY_PASTE } from '../constants';
3
+import Vector from '../vector';
4
+import State from '../state';
5
+import { MappedValue, Box } from '../common';
6
+import DrawErase from './erase';
6 7
 
7 8
 /**
8 9
  * @implements {DrawFunction}
@@ -53,7 +54,7 @@ export default class DrawSelect {
53 54
   copyArea() {
54 55
     var nonEmptyCells = this.state.scratchCells.filter(function(value) {
55 56
       var rawValue = value.cell.getRawValue();
56
-      return value.cell.getRawValue() != null && value.cell.getRawValue() != c.ERASE_CHAR;
57
+      return value.cell.getRawValue() != null && value.cell.getRawValue() != ERASE_CHAR;
57 58
     });
58 59
     var topLeft = this.getSelectedBox().topLeft();
59 60
     this.selectedCells = nonEmptyCells.map(function(value) {
@@ -82,7 +83,7 @@ export default class DrawSelect {
82 83
         // Effectively highlights the cell.
83 84
         var currentValue = this.state.getCell(current).getRawValue();
84 85
         this.state.drawValue(current,
85
-            currentValue == null ? c.ERASE_CHAR : currentValue);
86
+            currentValue == null ? ERASE_CHAR : currentValue);
86 87
       }
87 88
     }
88 89
   }
@@ -129,17 +130,17 @@ export default class DrawSelect {
129 130
   handleKey(value) {
130 131
     if (this.startPosition != null &&
131 132
         this.endPosition != null) {
132
-      if (value == c.KEY_COPY || value == c.KEY_CUT) {
133
+      if (value == KEY_COPY || value == KEY_CUT) {
133 134
         this.copyArea();
134 135
       }
135
-      if (value == c.KEY_CUT) {
136
+      if (value == KEY_CUT) {
136 137
         var eraser = new DrawErase(this.state);
137 138
         eraser.start(this.startPosition);
138 139
         eraser.move(this.endPosition);
139 140
         this.state.commitDraw();
140 141
       }
141 142
     }
142
-    if (value == c.KEY_PASTE) {
143
+    if (value == KEY_PASTE) {
143 144
       this.drawSelected(this.startPosition);
144 145
       this.state.commitDraw();
145 146
     }

+ 102
- 0
js-lib/draw/text.js View File

@@ -0,0 +1,102 @@
1
+import { DrawFunction } from './function';
2
+import { ERASE_CHAR } from '../constants';
3
+import State from '../state';
4
+import Vector from '../vector';
5
+import { drawText } from './utils';
6
+
7
+/**
8
+ * @implements {DrawFunction}
9
+ */
10
+export default class DrawText {
11
+  /**
12
+   * @param {State} state
13
+   */
14
+  constructor(state, view) {
15
+    this.state = state;
16
+    this.startPosition = null;
17
+    this.endPosition = null;
18
+  };
19
+
20
+  /** @inheritDoc */
21
+  start(position) {
22
+    this.state.commitDraw();
23
+    $('#text-tool-input').val('');
24
+    this.startPosition = position;
25
+
26
+    // TODO: Not working yet, needs fixing so that it can
27
+    // remove the underlying text completely.
28
+    // this.loadExistingText(position);
29
+
30
+    // Effectively highlights the starting cell.
31
+    var currentValue = this.state.getCell(this.startPosition).getRawValue();
32
+    this.state.drawValue(this.startPosition,
33
+        currentValue == null ? ERASE_CHAR : currentValue);
34
+  }
35
+
36
+  /** @inheritDoc */
37
+  move(position) {}
38
+
39
+  /** @inheritDoc */
40
+  end() {
41
+    if (this.startPosition != null) {
42
+      this.endPosition = this.startPosition;
43
+      this.startPosition = null;
44
+      // Valid end click/press, show the textbox and focus it.
45
+      $('#text-tool-widget').hide(0, () => {
46
+        $('#text-tool-widget').show(0, () => {
47
+          $('#text-tool-input').focus();
48
+        });
49
+      });
50
+    }
51
+  }
52
+
53
+  /** @inheritDoc */
54
+  getCursor(position) {
55
+    return 'pointer';
56
+  }
57
+
58
+  /** @inheritDoc */
59
+  handleKey(value) {
60
+    var text = /** @type {string} */ ($('#text-tool-input').val());
61
+    this.state.clearDraw();
62
+    drawText(this.state, this.endPosition, text);
63
+  }
64
+
65
+  /**
66
+   * Loads any existing text if it is present.
67
+   * TODO: This is horrible, and does not quite work, fix it.
68
+   */
69
+  loadExistingText(position) {
70
+    var currentPosition = position.clone();
71
+    var cell = this.state.getCell(position);
72
+    var spacesCount = 0;
73
+    // Go back to find the start of the line.
74
+    while ((!cell.isSpecial() && cell.getRawValue() != null) || spacesCount < 1) {
75
+      if (cell.getRawValue() == null) {
76
+        spacesCount++;
77
+      } else if (!cell.isSpecial()) {
78
+        spacesCount = 0;
79
+      }
80
+      currentPosition.x--;
81
+      cell = this.state.getCell(currentPosition);
82
+    }
83
+    this.startPosition = currentPosition.right(spacesCount + 1);
84
+    var text = '';
85
+    spacesCount = 0;
86
+    currentPosition = this.startPosition.clone();
87
+    // Go forward to load the text.
88
+    while ((!cell.isSpecial() && cell.getRawValue() != null) || spacesCount < 1) {
89
+      cell = this.state.getCell(currentPosition);
90
+      if (cell.getRawValue() == null) {
91
+        spacesCount++;
92
+        text += ' ';
93
+      } else if (!cell.isSpecial()) {
94
+        spacesCount = 0;
95
+        text += cell.getRawValue();
96
+        this.state.drawValue(currentPosition, cell.getRawValue());
97
+      }
98
+      currentPosition.x++;
99
+    }
100
+    $('#text-tool-input').val(text.substr(0, text.length - 1));
101
+  }
102
+}

+ 67
- 0
js-lib/draw/utils.js View File

@@ -0,0 +1,67 @@
1
+import { SPECIAL_VALUE } from '../constants';
2
+import { Box } from '../common';
3
+import State from '../state';
4
+import Vector from '../vector';
5
+
6
+/**
7
+ * Draws a line on the diagram state.
8
+ *
9
+ * @param {State} state
10
+ * @param {Vector} startPosition
11
+ * @param {Vector} endPosition
12
+ * @param {boolean} clockwise
13
+ * @param {string=} value
14
+ */
15
+export function drawLine(
16
+    state, startPosition, endPosition, clockwise, value = SPECIAL_VALUE) {
17
+
18
+  var box = new Box(startPosition, endPosition);
19
+  var startX = box.startX;
20
+  var startY = box.startY;
21
+  var endX = box.endX;
22
+  var endY = box.endY;
23
+
24
+  var midX = clockwise ? endPosition.x : startPosition.x;
25
+  var midY = clockwise ? startPosition.y : endPosition.y;
26
+
27
+  while (startX++ < endX) {
28
+    var position = new Vector(startX, midY);
29
+    var context = state.getContext(new Vector(startX, midY));
30
+    // Don't erase any lines that we cross.
31
+    if (value != ' ' || context.up + context.down != 2) {
32
+      state.drawValueIncremental(position, value);
33
+    }
34
+  }
35
+  while (startY++ < endY) {
36
+    var position = new Vector(midX, startY);
37
+    var context = state.getContext(new Vector(midX, startY));
38
+    // Don't erase any lines that we cross.
39
+    if (value != ' ' || context.left + context.right != 2) {
40
+      state.drawValueIncremental(position, value);
41
+    }
42
+  }
43
+
44
+  state.drawValue(startPosition, value);
45
+  state.drawValue(endPosition, value);
46
+  state.drawValueIncremental(new Vector(midX, midY), value);
47
+}
48
+
49
+/**
50
+ * Sets the cells scratch (uncommitted) values to the given text.
51
+ * Handles newlines appropriately.
52
+ * @param {State} state
53
+ * @param {Vector} position
54
+ * @param {string} text
55
+ */
56
+export function drawText(state, position, text) {
57
+  let x = 0, y = 0;
58
+  for (const char of text) {
59
+    if (char == '\n') {
60
+      y++;
61
+      x = 0;
62
+      continue;
63
+    }
64
+    state.drawValue(position.add(new Vector(x, y)), char);
65
+    x++;
66
+  }
67
+}

+ 5
- 5
js-lib/drive-controller.js View File

@@ -42,7 +42,7 @@ export default class DriveController {
42 42
 
43 43
     this.loopSave();
44 44
 
45
-    $(window).on('hashchange', this.loadFromHash);
45
+    $(window).on('hashchange', () => { this.loadFromHash(); });
46 46
 
47 47
     $('#drive-new-file-button').click(() => {
48 48
       this.file = null;
@@ -60,14 +60,14 @@ export default class DriveController {
60 60
     window['gapi']['auth']['authorize']({
61 61
         'client_id': CLIENT_ID,
62 62
         'scope': SCOPES,
63
-        'immediate': immediate},
63
+        immediate },
64 64
         result => {
65 65
           if (result && !result.error && !this.driveEnabled) {
66 66
             this.driveEnabled = true;
67 67
             $('#drive-button').addClass('active');
68 68
             // We are authorized, so let's se if we can load from the URL hash.
69 69
             // This seems to fail if we do it too early.
70
-            window.setTimeout(this.loadFromHash, 500);
70
+            window.setTimeout(() => { this.loadFromHash(); }, 500);
71 71
           }
72 72
         });
73 73
   }
@@ -136,7 +136,7 @@ export default class DriveController {
136 136
   safeExecute(request, callback) {
137 137
     // Could make the API call, don't blow up tho (mobiles n stuff).
138 138
     try {
139
-      request['execute'](function(result) {
139
+      request['execute'](result => {
140 140
         if (!result['error']) {
141 141
           callback(result);
142 142
         }
@@ -220,8 +220,8 @@ export default class DriveController {
220 220
     var method = this.file == null ? 'POST' : 'PUT';
221 221
 
222 222
     return window['gapi']['client']['request']({
223
+        method,
223 224
         'path': '/upload/drive/v2/files' + fileId,
224
-        'method': method,
225 225
         'params': {'uploadType': 'multipart'},
226 226
         'headers': {
227 227
           'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'

+ 21
- 31
js-lib/input-controller.js View File

@@ -29,31 +29,31 @@ export class DesktopController {
29 29
    * Installs input bindings associated with keyboard controls.
30 30
    */
31 31
   installBindings() {
32
-    var canvas = this.controller.view.canvas;
33
-    $(canvas).on('mousewheel', e => {
32
+    var canvas = $(this.controller.view.canvas);
33
+    canvas.on('mousewheel', e => {
34 34
         this.handleZoom(e.originalEvent.wheelDelta);
35 35
     });
36 36
 
37
-    $(canvas).mousedown(e => {
37
+    canvas.mousedown(e => {
38 38
         // Can drag by holding either the control or meta (Apple) key.
39 39
         if (e.ctrlKey || e.metaKey) {
40
-          this.controller.startDrag(new Vector(e.clientX, e.clientY));
40
+          this.controller.startDrag(Vector.fromMouseEvent(e));
41 41
         } else {
42
-          this.controller.startDraw(new Vector(e.clientX, e.clientY));
42
+          this.controller.startDraw(Vector.fromMouseEvent(e));
43 43
         }
44 44
     });
45 45
 
46 46
     // Pass these events through to the main controller.
47
-    $(canvas).mouseup(e => {
47
+    canvas.mouseup(e => {
48 48
         this.controller.endAll();
49 49
     });
50 50
 
51
-    $(canvas).mouseleave(e => {
51
+    canvas.mouseleave(e => {
52 52
         this.controller.endAll();
53 53
     });
54 54
 
55
-    $(canvas).mousemove(e => {
56
-        this.controller.handleMove(new Vector(e.clientX, e.clientY));
55
+    canvas.mousemove(e => {
56
+        this.controller.handleMove(Vector.fromMouseEvent(e));
57 57
     });
58 58
   }
59 59
 }
@@ -152,42 +152,32 @@ export class TouchController {
152 152
    * Installs input bindings associated with touch controls.
153 153
    */
154 154
   installBindings() {
155
-    var canvas = this.controller.view.canvas;
155
+    var canvas = $(this.controller.view.canvas);
156 156
 
157
-    $(canvas).on('touchstart', e => {
157
+    canvas.on('touchstart', e => {
158 158
         e.preventDefault();
159 159
         if (e.originalEvent.touches.length == 1) {
160
-          this.handlePress(new Vector(
161
-            e.originalEvent.touches[0].pageX,
162
-            e.originalEvent.touches[0].pageY));
160
+          this.handlePress(Vector.fromTouchEvent(e));
163 161
         } else if (e.originalEvent.touches.length > 1) {
164
-          this.handlePressMulti(new Vector(
165
-            e.originalEvent.touches[0].pageX,
166
-            e.originalEvent.touches[0].pageY),
167
-            new Vector(
168
-                e.originalEvent.touches[1].pageX,
169
-                e.originalEvent.touches[1].pageY));
162
+          this.handlePressMulti(
163
+            Vector.fromTouchEvent(e, 0),
164
+            Vector.fromTouchEvent(e, 1));
170 165
         }
171 166
     });
172 167
 
173
-    $(canvas).on('touchmove', e => {
168
+    canvas.on('touchmove', e => {
174 169
         e.preventDefault();
175 170
         if (e.originalEvent.touches.length == 1) {
176
-          this.handleMove(new Vector(
177
-            e.originalEvent.touches[0].pageX,
178
-            e.originalEvent.touches[0].pageY));
171
+          this.handleMove(Vector.fromTouchEvent(e));
179 172
         } else if (e.originalEvent.touches.length > 1) {
180
-          this.handleMoveMulti(new Vector(
181
-            e.originalEvent.touches[0].pageX,
182
-            e.originalEvent.touches[0].pageY),
183
-            new Vector(
184
-                e.originalEvent.touches[1].pageX,
185
-                e.originalEvent.touches[1].pageY));
173
+          this.handleMoveMulti(
174
+            Vector.fromTouchEvent(e, 0),
175
+            Vector.fromTouchEvent(e, 1));
186 176
         }
187 177
     });
188 178
 
189 179
     // Pass through, no special handling.
190
-    $(canvas).on('touchend', e => {
180
+    canvas.on('touchend', e => {
191 181
         e.preventDefault();
192 182
         this.reset();
193 183
         this.controller.endAll();

+ 24
- 26
js-lib/state.js View File

@@ -98,8 +98,8 @@ export default class State {
98 98
   getDrawValue(position) {
99 99
     var cell = this.getCell(position);
100 100
     var value = cell.scratchValue != null ? cell.scratchValue : cell.value;
101
-    var isSpecial = c.SPECIAL_VALUES.indexOf(value) != -1;
102
-    var isAltSpecial = c.ALT_SPECIAL_VALUES.indexOf(value) != -1;
101
+    var isSpecial = c.SPECIAL_VALUES.includes(value);
102
+    var isAltSpecial = c.ALT_SPECIAL_VALUES.includes(value);
103 103
     if (!isSpecial && !isAltSpecial) {
104 104
       return value;
105 105
     }
@@ -146,13 +146,13 @@ export default class State {
146 146
       if (!context.up && context.rightdown && context.leftdown) {
147 147
         return c.SPECIAL_LINE_H;
148 148
       }
149
-      var leftupempty = this.getCell(position.add(c.DIR_LEFT).add(c.DIR_UP)).isEmpty();
150
-      var rightupempty = this.getCell(position.add(c.DIR_RIGHT).add(c.DIR_UP)).isEmpty();
149
+      var leftupempty = this.getCell(position.left().up()).isEmpty();
150
+      var rightupempty = this.getCell(position.right().up()).isEmpty();
151 151
       if (context.up && context.left && context.right && (!leftupempty || !rightupempty)) {
152 152
         return c.SPECIAL_LINE_H;
153 153
       }
154
-      var leftdownempty = this.getCell(position.add(c.DIR_LEFT).add(c.DIR_DOWN)).isEmpty();
155
-      var rightdownempty = this.getCell(position.add(c.DIR_RIGHT).add(c.DIR_DOWN)).isEmpty();
154
+      var leftdownempty = this.getCell(position.left().down()).isEmpty();
155
+      var rightdownempty = this.getCell(position.right().down()).isEmpty();
156 156
       if (context.down && context.left && context.right && (!leftdownempty || !rightdownempty)) {
157 157
         return c.SPECIAL_LINE_H;
158 158
       }
@@ -181,10 +181,10 @@ export default class State {
181 181
    * @return {CellContext}
182 182
    */
183 183
   getContext(position) {
184
-    var left = this.getCell(position.add(c.DIR_LEFT)).isSpecial();
185
-    var right = this.getCell(position.add(c.DIR_RIGHT)).isSpecial();
186
-    var up = this.getCell(position.add(c.DIR_UP)).isSpecial();
187
-    var down = this.getCell(position.add(c.DIR_DOWN)).isSpecial();
184
+    var left = this.getCell(position.left()).isSpecial();
185
+    var right = this.getCell(position.right()).isSpecial();
186
+    var up = this.getCell(position.up()).isSpecial();
187
+    var down = this.getCell(position.down()).isSpecial();
188 188
     return new CellContext(left, right, up, down);
189 189
   }
190 190
 
@@ -193,10 +193,10 @@ export default class State {
193 193
    * @param {CellContext} context
194 194
    */
195 195
   extendContext(position, context) {
196
-    context.leftup = this.getCell(position.add(c.DIR_LEFT).add(c.DIR_UP)).isSpecial();
197
-    context.rightup = this.getCell(position.add(c.DIR_RIGHT).add(c.DIR_UP)).isSpecial();
198
-    context.leftdown = this.getCell(position.add(c.DIR_LEFT).add(c.DIR_DOWN)).isSpecial();
199
-    context.rightdown = this.getCell(position.add(c.DIR_RIGHT).add(c.DIR_DOWN)).isSpecial();
196
+    context.leftup = this.getCell(position.left().up()).isSpecial();
197
+    context.rightup = this.getCell(position.right().up()).isSpecial();
198
+    context.leftdown = this.getCell(position.left().down()).isSpecial();
199
+    context.rightdown = this.getCell(position.right().down()).isSpecial();
200 200
   }
201 201
 
202 202
   /**
@@ -207,11 +207,10 @@ export default class State {
207 207
     var oldValues = [];
208 208
 
209 209
     // Dedupe the scratch values, or this causes havoc for history management.
210
-    var positions = this.scratchCells.map(function(value) {
210
+    var positions = this.scratchCells.map(value => {
211 211
       return value.position.x.toString() + value.position.y.toString();
212 212
     });
213
-    var scratchCellsUnique =
214
-        this.scratchCells.filter(function(value, index, arr) {
213
+    var scratchCellsUnique = this.scratchCells.filter((value, index, arr) => {
215 214
       return positions.indexOf(positions[index]) == index;
216 215
     });
217 216
 
@@ -253,9 +252,8 @@ export default class State {
253 252
     if (this.undoStates.length == 0) { return; }
254 253
 
255 254
     var lastState = this.undoStates.pop();
256
-    for (var i in lastState) {
257
-      var mappedValue = lastState[i];
258
-      this.drawValue(mappedValue.position, mappedValue.value);
255
+    for (var { position, value } of lastState) {
256
+      this.drawValue(position, value);
259 257
     }
260 258
     this.commitDraw(true);
261 259
   }
@@ -267,8 +265,8 @@ export default class State {
267 265
     if (this.redoStates.length == 0) { return; }
268 266
 
269 267
     var lastState = this.redoStates.pop();
270
-    for (var mappedValue of lastState) {
271
-      this.drawValue(mappedValue.position, mappedValue.value);
268
+    for (var { position, value } of lastState) {
269
+      this.drawValue(position, value);
272 270
     }
273 271
     this.commitDraw();
274 272
   }
@@ -320,7 +318,7 @@ export default class State {
320 318
    */
321 319
   fromText(value, offset) {
322 320
     var lines = value.split('\n');
323
-    var middle = new ascii.Vector(0, Math.round(lines.length / 2));
321
+    var middle = new Vector(0, Math.round(lines.length / 2));
324 322
     for (var j = 0; j < lines.length; j++) {
325 323
       middle.x = Math.max(middle.x, Math.round(lines[j].length / 2));
326 324
     }
@@ -331,10 +329,10 @@ export default class State {
331 329
         // Convert special output back to special chars.
332 330
         // TODO: This is a horrible hack, need to handle multiple special chars
333 331
         // correctly and preserve them through line drawing etc.
334
-        if (SPECIAL_VALUES.indexOf(char)  != -1) {
335
-          char = SPECIAL_VALUE;
332
+        if (c.SPECIAL_VALUES.includes(char)) {
333
+          char = c.SPECIAL_VALUE;
336 334
         }
337
-        this.drawValue(new ascii.Vector(i, j).add(offset).subtract(middle), char);
335
+        this.drawValue(new Vector(i, j).add(offset).subtract(middle), char);
338 336
       }
339 337
     }
340 338
   }

+ 56
- 2
js-lib/vector.js View File

@@ -12,7 +12,25 @@ export default class Vector {
12 12
   }
13 13
 
14 14
   /**
15
-   * @param {Vector} other
15
+   * @param {jQuery.Event} event
16
+   * @return {!Vector}
17
+   */
18
+  static fromMouseEvent(event) {
19
+    return new Vector(event.clientX, event.clientY);
20
+  }
21
+
22
+  /**
23
+   * @param {jQuery.Event} event
24
+   * @param {number=} index
25
+   * @return {!Vector}
26
+   */
27
+  static fromTouchEvent(event, index = 0) {
28
+    const { pageX, pageY } = event.originalEvent.touches[index];
29
+    return new Vector(pageX, pageY);
30
+  }
31
+
32
+  /**
33
+   * @param {?Vector} other
16 34
    * @return {boolean}
17 35
    */
18 36
   equals(other) {
@@ -36,7 +54,7 @@ export default class Vector {
36 54
   }
37 55
 
38 56
   /**
39
-   * @return {Vector}
57
+   * @return {!Vector}
40 58
    */
41 59
   clone() {
42 60
     return new Vector(this.x, this.y);
@@ -54,4 +72,40 @@ export default class Vector {
54 72
   scale(scale) {
55 73
     return new Vector(this.x * scale, this.y * scale);
56 74
   }
75
+
76
+  /**
77
+   * Move up by value. Defaults to 1.
78
+   * @param {number=} value
79
+   * @return {Vector}
80
+   */
81
+  up(value = 1) {
82
+    return new Vector(this.x, this.y - value);
83
+  }
84
+
85
+  /**
86
+   * Move down by value. Defaults to 1.
87
+   * @param {number=} value
88
+   * @return {Vector}
89
+   */
90
+  down(value = 1) {
91
+    return new Vector(this.x, this.y + value);
92
+  }
93
+
94
+  /**
95
+   * Move left by value. Defaults to 1.
96
+   * @param {number=} value
97
+   * @return {Vector}
98
+   */
99
+  left(value = 1) {
100
+    return new Vector(this.x - value, this.y);
101
+  }
102
+
103
+  /**
104
+   * Move right by value. Defaults to 1.
105
+   * @param {number=} value
106
+   * @return {Vector}
107
+   */
108
+  right(value = 1) {
109
+    return new Vector(this.x + value, this.y);
110
+  }
57 111
 }

+ 1
- 2
js-lib/view.js View File

@@ -45,8 +45,7 @@ export default class View {
45 45
       this.state.dirty = false;
46 46
       this.render();
47 47
     }
48
-    var view = this;
49
-    window.requestAnimationFrame(function() { view.animate(); });
48
+    window.requestAnimationFrame(() => { this.animate(); });
50 49
   }
51 50
 
52 51
   /**

Loading…
Cancel
Save