diff options
-rw-r--r-- | Dockerfile | 5 | ||||
-rw-r--r-- | src/App.js | 139 | ||||
-rw-r--r-- | src/PaintArea.js | 39 | ||||
-rw-r--r-- | src/Utils.js | 53 | ||||
-rw-r--r-- | src/Utils.test.js | 36 |
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 @@ -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 |