aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile5
-rw-r--r--src/App.js139
-rw-r--r--src/PaintArea.js39
-rw-r--r--src/Utils.js53
-rw-r--r--src/Utils.test.js36
5 files changed, 194 insertions, 78 deletions
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..f195bee
--- /dev/null
+++ 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
index 68473ce..98611b2 100644
--- 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
index 1ffec83..3b64beb 100644
--- 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
index f7bba5c..7432c49 100644
--- 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
index 6c9161b..33d63f0 100644
--- 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