commit 10e7ce5764b3b456e1f909cd6b6a90f076c2e014
parent e9ee34abf84a10911b2c6041534c2df7f91f052a
Author: Martin Ashby <martin@martin-laptop.lan>
Date:   Thu, 17 May 2018 20:17:01 +0100
Fixed rotation problem. Added rotate function. Added docker file
Diffstat:
5 files changed, 195 insertions(+), 78 deletions(-)
diff --git a/Dockerfile b/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+RUN pip install unicornhathd Flask Flask-Sockets
+COPY build/ .
+COPY server.py .
+ENTRYPOINT python server.py
+\ No newline at end of file
diff --git a/src/App.js b/src/App.js
@@ -1,6 +1,6 @@
 import React, { Component } from 'react'
 import './App.css'
-import { rgb, xy, findContiguousPixels, getPixel } from './Utils'
+import { rgb, xy, findContiguousPixels, getPixel, rotatePixelsClock, rotatePixelsCounterClock } from './Utils'
 import { setPixel, clear, noop, save, load } from './Actions'
 import Palette from './Palette'
 import PaintArea from './PaintArea'
@@ -11,40 +11,56 @@ import LoadDialog from './LoadDialog'
 import SaveDialog from './SaveDialog'
 
 const tools = [
-  { 
-    name: "paint", 
-    icon: "fas fa-pencil-alt", 
+  {
+    name: "paint",
+    icon: "fas fa-pencil-alt",
     action: function (x, y) {
       let { r, g, b } = rgb(this.state.selectedColor)
-      setPixel(this._websocket, x, y, r, g, b)    
+      setPixel(this._websocket, x, y, r, g, b)
     }
   },
-  { 
-    name: "fill", 
-    icon: "fab fa-bitbucket", 
+  {
+    name: "fill",
+    icon: "fab fa-bitbucket",
     action: function (x, y) {
       let pixelsToColor = findContiguousPixels(x, y, this.state.pixels)
       pixelsToColor.forEach((coord) => {
-        let px = { ...xy(coord), ...rgb(this.state.selectedColor)}
+        let px = { ...xy(coord), ...rgb(this.state.selectedColor) }
         setPixel(this._websocket, px.x, px.y, px.r, px.g, px.b)
       })
     }
   },
-  { 
-    name: "erase", 
-    icon: "fas fa-eraser", 
+  {
+    name: "erase",
+    icon: "fas fa-eraser",
     action: function (x, y) {
-      setPixel(this._websocket, x, y, 0, 0, 0)    
+      setPixel(this._websocket, x, y, 0, 0, 0)
     },
   },
-  { 
-    name: "pick", 
-    icon: "fas fa-eye-dropper", 
+  {
+    name: "pick",
+    icon: "fas fa-eye-dropper",
     action: function (x, y) {
       let color = getPixel(x, y, this.state.pixels)
       this.setState({ selectedColor: color })
     },
   },
+  {
+    name: "rotate-clockwise",
+    icon: "fas fa-redo",
+    onSelect: function () {
+      let newPixels = rotatePixelsClock(this.state.pixels)
+      this._setAllPixels(newPixels)
+    }
+  },
+  {
+    name: "rotate-anticlockwise",
+    icon: "fas fa-undo",
+    onSelect: function () {
+      let newPixels = rotatePixelsCounterClock(this.state.pixels)
+      this._setAllPixels(newPixels)
+    }
+  },
   // { 
   //   name: "lighten", 
   //   icon: "far fa-sun"
@@ -53,23 +69,23 @@ const tools = [
   //   name: "darken", 
   //   icon: "fas fa-sun"
   // },
-  { 
-    name: "save", 
-    icon: "fas fa-save", 
+  {
+    name: "save",
+    icon: "fas fa-save",
     onSelect: function () {
       this.setState({ showingSave: true })
     }
   },
-  { 
-    name: "load", 
-    icon: "fas fa-save", 
+  {
+    name: "load",
+    icon: "fas fa-save",
     onSelect: function () {
       this.setState({ showingLoad: true })
     }
   },
-  { 
-    name: "trash", 
-    icon: "fas fa-trash", 
+  {
+    name: "trash",
+    icon: "fas fa-trash",
     onSelect: function () {
       clear(this._websocket)
     }
@@ -103,7 +119,7 @@ class App extends Component {
     this._connectWebsocket()
   }
 
-  _onMessage({data}) {
+  _onMessage({ data }) {
     let state = JSON.parse(data)
     this.setState({
       ...state // Includes pixels and saves
@@ -111,22 +127,23 @@ class App extends Component {
   }
 
   _onOpen() {
-    this.setState({connected: true})
+    this.setState({ connected: true })
     noop(this._websocket)
   }
 
   _onClose() {
-    this.setState({connected: false})
+    this.setState({ connected: false })
     this._connectWebsocket()
   }
 
   _onError() {
-    this.setState({connected: false})
+    this.setState({ connected: false })
     this._connectWebsocket()
   }
 
   _connectWebsocket() {
-    this._websocket = new WebSocket('ws://' + window.location.hostname + ':3001/ws')
+    // this._websocket = new WebSocket('ws://' + window.location.hostname + ':3001/ws')
+    this._websocket = new WebSocket('ws://shinypi:3001/ws')
     this._websocket.onmessage = this._onMessage
     this._websocket.onopen = this._onOpen
     this._websocket.onclose = this._onClose
@@ -140,7 +157,7 @@ class App extends Component {
     }
     let action = tool.action
     if (!action) {
-      return 
+      return
     }
     action.bind(this)(x, y)
   }
@@ -150,52 +167,64 @@ class App extends Component {
     if (selectAction) {
       selectAction.bind(this)()
     } else {
-      this.setState({selectedTool: tool})
+      this.setState({ selectedTool: tool })
     }
   }
 
   _loadDrawing(name) {
     load(this._websocket, name)
-    this.setState({showingLoad: false})
+    this.setState({ showingLoad: false })
   }
 
   _saveDrawing(name) {
     save(this._websocket, name)
-    this.setState({showingSave: false})
+    this.setState({ showingSave: false })
+  }
+
+  _setAllPixels(newPixels) {
+    let width = newPixels.length
+    let height = newPixels[0].length
+    for (var x = 0; x < width; x++) {
+      for (var y = 0; y < height; y++) {
+        let px = getPixel(x, y, newPixels)
+        let { r, g, b } = rgb(px)
+        setPixel(this._websocket, x, y, r, g, b)
+      }
+    }
   }
 
   render() {
     return (
       <div className="App">
-        <ConnectedIndicator connected={this.state.connected}/>
+        <ConnectedIndicator connected={this.state.connected} />
         <Toolkit
           tools={tools}
           selectedTool={this.state.selectedTool}
-          onSelectTool={this._selectTool}/>
-        <PaintArea 
+          onSelectTool={this._selectTool} />
+        <PaintArea
           data={this.state.pixels}
-          onTool={this._applyTool}/>
-        <Palette 
+          onTool={this._applyTool} />
+        <Palette
           selectedColor={this.state.selectedColor}
-          onSelectColor={(color) => this.setState({selectedColor: color})} />
-        <ColorIndicator color={this.state.selectedColor}/>
+          onSelectColor={(color) => this.setState({ selectedColor: color })} />
+        <ColorIndicator color={this.state.selectedColor} />
         <div>
-        {
-          this.state.showingLoad 
-          && <LoadDialog 
-            saves={this.state.saves}
-            onLoad={(drawing) => this._loadDrawing(drawing)}
-            onClose={() => this.setState({showingLoad: false})}/>
-        }
+          {
+            this.state.showingLoad
+            && <LoadDialog
+              saves={this.state.saves}
+              onLoad={(drawing) => this._loadDrawing(drawing)}
+              onClose={() => this.setState({ showingLoad: false })} />
+          }
         </div>
         <div>
-        {
-          this.state.showingSave
-          && <SaveDialog 
-            saves={this.state.saves}
-            onSave={(name) => this._saveDrawing(name)}
-            onClose={() => this.setState({showingSave: false})}/>
-        }
+          {
+            this.state.showingSave
+            && <SaveDialog
+              saves={this.state.saves}
+              onSave={(name) => this._saveDrawing(name)}
+              onClose={() => this.setState({ showingSave: false })} />
+          }
         </div>
 
       </div>
diff --git a/src/PaintArea.js b/src/PaintArea.js
@@ -1,4 +1,5 @@
 import React, { Component } from 'react'
+import { rgb, getPixel } from './Utils'
 
 /**
  * Expects props: 
@@ -44,29 +45,33 @@ export default class PaintArea extends Component {
   }
 
   render() {
-    let cells = this.props.data.map((row, iy) => {
-      let rowCells = row.map((cell, ix) => {
-          let r = cell[0]
-          let g = cell[1]
-          let b = cell[2]
-          return <td
-            onMouseMove={() => this.handleMouseMove(ix, iy)}
-            onClick={() => this.props.onTool(ix, iy)}
-            className="paintareacell"
-            style={{
-              background: `rgb(${r},${g},${b})`
-            }}
-            key={(ix * 100000) + iy}/>
-      })
-      return <tr key={iy}>{rowCells}</tr>
-    })
+    let data = this.props.data
+    let height = data[0] ? data[0].length : 0
+    let rows = []
+    for (var y=height-1; y>=0; y--) {
+      let cells = []
+      for (var x=0; x<data.length; x++) {
+        let {r, g, b} = rgb(getPixel(x, y, data))
+        let ix = x
+        let iy = y
+        cells.push(<td
+          onMouseMove={() => this.handleMouseMove(ix, iy)}
+          onClick={() => this.props.onTool(ix, iy)}
+          className="paintareacell"
+          style={{
+            background: `rgb(${r},${g},${b})`
+          }}
+          key={(ix * 100000) + iy}/>)
+      }
+      rows.push(<tr key={y}>{cells}</tr>)
+    }
     
     return (
       <table 
         className="paintarea"
         draggable={false}>
         <tbody>
-          {cells}
+          {rows}
         </tbody>
       </table>
     )
diff --git a/src/Utils.js b/src/Utils.js
@@ -52,11 +52,11 @@ function coordsEqual(item1, item2) {
 }
 
 function getPixel(x, y, pixels) {
-    let row = pixels[y]
-    if (!row) {
+    let column = pixels[x]
+    if (!column) {
         return
     }
-    return row[x]
+    return column[y]
 }
 
 function findContiguousPixels(x, y, pixels, targetColor = getPixel(x, y, pixels), contiguousPixels=[[x, y]]) {
@@ -90,7 +90,48 @@ function findContiguousPixels(x, y, pixels, targetColor = getPixel(x, y, pixels)
   
     return contiguousPixels
 }
-  
+
+function rotatePixelsCounterClock(pixels) {
+    let rotateClock = (x, y, width, height) => {
+        return {
+            newx: -y + (width - 1), 
+            newy : x
+        }
+    }
+    return transformPixels(pixels, rotateClock)
+}
+
+function rotatePixelsClock(pixels) {
+    let rotateClock = (x, y, width, height) => {
+        return {
+            newx: y, 
+            newy : - x + (height - 1)
+        }
+    }
+    return transformPixels(pixels, rotateClock)
+}
+
+function transformPixels(pixels, transform) {
+    let width = pixels.length
+    let height = pixels[0].length
+    let newPixels = []
+    for (var x = 0; x < width; x++) {
+        let column = []
+        for (var y = 0; y < height; y++) {
+            column.push([0, 0, 0])
+        }
+        newPixels.push(column)
+    }
+    
+    for (var x = 0; x < width; x++) {
+      for (var y = 0; y < height; y++) {
+        let px = getPixel(x, y, pixels)
+        let {newx, newy} = transform(x, y, width, height)
+        newPixels[newx][newy] = px
+      }
+    }
+    return newPixels
+}
 
 export {
     xy,
@@ -98,6 +139,8 @@ export {
     colorEqual,
     coordsEqual,
     findContiguousPixels,
-    getPixel
+    getPixel,
+    rotatePixelsClock,
+    rotatePixelsCounterClock
 }
 
diff --git a/src/Utils.test.js b/src/Utils.test.js
@@ -1,4 +1,4 @@
-import { colorEqual, coordsEqual, xy, rgb, findContiguousPixels } from './Utils'
+import { colorEqual, coordsEqual, xy, rgb, findContiguousPixels, rotatePixelsClock, rotatePixelsCounterClock } from './Utils'
 
 test('test colorEqual function', () => {
     expect(colorEqual([0, 0, 0], [0, 0, 0]))
@@ -78,4 +78,38 @@ test('contiguousPixels', () => {
         [[0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1]]
     ]).length)
         .toBe(4)
+})
+
+test('rotate clockwise function', () => {
+    let rot1 = rotatePixelsClock([
+        [[0, 0, 1], [0, 0, 0], [0, 0, 0], [0, 0, 2]],
+        [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
+        [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
+        [[0, 0, 4], [0, 0, 0], [0, 0, 0], [0, 0, 3]]
+    ])
+    expect(rot1[0][3][2])
+    .toBe(1)
+    expect(rot1[3][3][2])
+    .toBe(2)
+    expect(rot1[3][0][2])
+    .toBe(3)
+    expect(rot1[0][0][2])
+    .toBe(4)
+})
+
+test('rotate counter-clockwise function', () => {
+    let rot1 = rotatePixelsCounterClock([
+        [[0, 0, 1], [0, 0, 0], [0, 0, 0], [0, 0, 2]],
+        [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
+        [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
+        [[0, 0, 4], [0, 0, 0], [0, 0, 0], [0, 0, 3]]
+    ])
+    expect(rot1[0][3][2])
+    .toBe(3)
+    expect(rot1[3][3][2])
+    .toBe(4)
+    expect(rot1[3][0][2])
+    .toBe(1)
+    expect(rot1[0][0][2])
+    .toBe(2)
 })
 \ No newline at end of file