App.js (7879B)
1 import React, { Component } from 'react' 2 import './App.css' 3 import { rgb, xy, findContiguousPixels, getPixel, rotatePixelsClock, rotatePixelsCounterClock, readGifFrames } from './Utils' 4 import { setPixel, clear, noop, save, load, addFrame, removeFrame } from './Actions' 5 import Palette from './Palette' 6 import PaintArea from './PaintArea' 7 import Toolkit from './Toolkit' 8 import ColorIndicator from './ColorIndicator' 9 import ConnectedIndicator from './ConnectedIndicator' 10 import LoadDialog from './LoadDialog' 11 import SaveDialog from './SaveDialog' 12 import FrameControl from './FrameControl' 13 import '@fortawesome/fontawesome-free-webfonts/css/fontawesome.css' 14 import '@fortawesome/fontawesome-free-webfonts/css/fa-solid.css' 15 import '@fortawesome/fontawesome-free-webfonts/css/fa-regular.css' 16 import '@fortawesome/fontawesome-free-webfonts/css/fa-brands.css' 17 18 const tools = [ 19 { 20 name: "paint", 21 icon: "fas fa-pencil-alt", 22 action: function (x, y, frame) { 23 let { r, g, b } = rgb(this.state.selectedColor) 24 setPixel(this._websocket, x, y, r, g, b, frame) 25 } 26 }, 27 { 28 name: "fill", 29 icon: "fab fa-bitbucket", 30 action: function (x, y, frame) { 31 let pixelsToColor = findContiguousPixels(x, y, this.state.frames[frame]) 32 pixelsToColor.forEach((coord) => { 33 let px = { ...xy(coord), ...rgb(this.state.selectedColor) } 34 setPixel(this._websocket, px.x, px.y, px.r, px.g, px.b, frame) 35 }) 36 } 37 }, 38 { 39 name: "erase", 40 icon: "fas fa-eraser", 41 action: function (x, y, frame) { 42 setPixel(this._websocket, x, y, 0, 0, 0, frame) 43 }, 44 }, 45 { 46 name: "pick", 47 icon: "fas fa-eye-dropper", 48 action: function (x, y, frame) { 49 let pixels = this.state.frames[frame] 50 let color = getPixel(x, y, pixels) 51 this.setState({ selectedColor: color }) 52 }, 53 }, 54 { 55 name: "add frame", 56 icon: "far fa-plus-square", 57 onSelect: function () { 58 addFrame(this._websocket, this.state.selectedFrame+1, 50) 59 } 60 }, 61 { 62 name: "remove frame", 63 icon: "far fa-minus-square", 64 onSelect: function () { 65 removeFrame(this._websocket, this.state.selectedFrame) 66 } 67 }, 68 { 69 name: "rotate-clockwise", 70 icon: "fas fa-redo", 71 onSelect: function () { 72 let pixels = this.state.frames[this.state.selectedFrame] 73 let newPixels = rotatePixelsClock(pixels) 74 this._setAllPixels(newPixels) 75 } 76 }, 77 { 78 name: "rotate-anticlockwise", 79 icon: "fas fa-undo", 80 onSelect: function () { 81 let pixels = this.state.frames[this.state.selectedFrame] 82 let newPixels = rotatePixelsCounterClock(pixels) 83 this._setAllPixels(newPixels) 84 } 85 }, 86 { 87 name: "save", 88 icon: "fas fa-save", 89 onSelect: function () { 90 this.setState({ showingSave: true }) 91 } 92 }, 93 { 94 name: "load", 95 icon: "fas fa-save", 96 onSelect: function () { 97 this.setState({ showingLoad: true }) 98 } 99 }, 100 { 101 name: "trash", 102 icon: "fas fa-trash", 103 onSelect: function () { 104 clear(this._websocket) 105 } 106 }, 107 ] 108 109 class App extends Component { 110 constructor(props) { 111 super(props) 112 113 this.state = { 114 connected: false, 115 // Data from server 116 frames: [], 117 saves: [], 118 imageData: "", 119 120 // Local data 121 selectedFrame: 0, 122 selectedColor: [0, 0, 0], 123 selectedTool: tools[0], 124 showingSave: false, 125 showingLoad: false, 126 } 127 128 129 this._applyTool = this._applyTool.bind(this) 130 this._selectTool = this._selectTool.bind(this) 131 this._connectWebsocket = this._connectWebsocket.bind(this) 132 this._onMessage = this._onMessage.bind(this) 133 this._onOpen = this._onOpen.bind(this) 134 this._onClose = this._onClose.bind(this) 135 this._onError = this._onError.bind(this) 136 this._loadDrawing = this._loadDrawing.bind(this) 137 this._saveDrawing = this._saveDrawing.bind(this) 138 this._connectWebsocket() 139 } 140 141 _onMessage({ data }) { 142 let { imageData, saves } = JSON.parse(data) 143 let frames = readGifFrames(imageData) 144 145 let selectedFrame = this.state.selectedFrame >= frames.length ? 146 frames.length - 1 : 147 this.state.selectedFrame 148 149 this.setState({ 150 frames: frames, 151 saves: saves, 152 selectedFrame: selectedFrame, 153 imageData: imageData, 154 }) 155 } 156 157 _onOpen() { 158 this.setState({ connected: true }) 159 noop(this._websocket) 160 } 161 162 _onClose() { 163 this.setState({ connected: false }) 164 this._connectWebsocket() 165 } 166 167 _onError() { 168 this.setState({ connected: false }) 169 this._connectWebsocket() 170 } 171 172 _connectWebsocket() { 173 let webSocketProto = window.location.protocol === "https:" ? "wss:" : "ws:" 174 var host = window.location.host 175 // if (true) { 176 // console.log("Dev mode overriding port to 3001") 177 // host = window.location.hostname + ":3001" 178 // } 179 this._websocket = new WebSocket(`${webSocketProto}//${host}/ws`) 180 this._websocket.onmessage = this._onMessage 181 this._websocket.onopen = this._onOpen 182 this._websocket.onclose = this._onClose 183 this._websocket.onerror = this._onError 184 } 185 186 _applyTool(x, y) { 187 let tool = this.state.selectedTool 188 if (!tool) { 189 return 190 } 191 let action = tool.action 192 if (!action) { 193 return 194 } 195 let selectedFrame = this.state.selectedFrame 196 action.bind(this)(x, y, selectedFrame) 197 } 198 199 _selectTool(tool) { 200 let selectAction = tool.onSelect 201 if (selectAction) { 202 selectAction.bind(this)() 203 } else { 204 this.setState({ selectedTool: tool }) 205 } 206 } 207 208 _loadDrawing(name) { 209 load(this._websocket, name) 210 this.setState({ showingLoad: false }) 211 } 212 213 _saveDrawing(name) { 214 save(this._websocket, name) 215 this.setState({ showingSave: false }) 216 } 217 218 _setAllPixels(newPixels) { 219 let width = newPixels.length 220 let height = newPixels[0].length 221 for (var x = 0; x < width; x++) { 222 for (var y = 0; y < height; y++) { 223 let px = getPixel(x, y, newPixels) 224 let { r, g, b } = rgb(px) 225 let selectedFrame = this.state.selectedFrame 226 setPixel(this._websocket, x, y, r, g, b, selectedFrame) 227 } 228 } 229 } 230 231 render() { 232 let { selectedFrame, 233 frames, 234 imageData, 235 connected, 236 selectedTool, 237 selectedColor, 238 saves, 239 showingLoad, 240 showingSave } = this.state 241 242 let pixels = frames[selectedFrame] || [] 243 244 return ( 245 <div className="container"> 246 <div className="header"> 247 <h1>Unicorn Paint!</h1> 248 <ConnectedIndicator connected={connected} /> 249 </div> 250 <div className="framePaintContainer"> 251 <div className="paintContainer"> 252 <PaintArea 253 data={pixels} 254 onTool={this._applyTool} /> 255 <Toolkit 256 tools={tools} 257 selectedTool={selectedTool} 258 onSelectTool={this._selectTool} /> 259 <Palette 260 selectedColor={selectedColor} 261 onSelectColor={(color) => this.setState({ selectedColor: color })} /> 262 <ColorIndicator color={selectedColor} /> 263 </div> 264 <FrameControl 265 frames={frames} 266 selectedFrame={selectedFrame} 267 onFrameSelected={ frame => this.setState({selectedFrame: frame}) } 268 imageData={imageData}/> 269 </div> 270 <div> 271 { 272 showingLoad 273 && <LoadDialog 274 saves={saves} 275 onLoad={(drawing) => this._loadDrawing(drawing)} 276 onClose={() => this.setState({ showingLoad: false })} /> 277 } 278 </div> 279 <div> 280 { 281 showingSave 282 && <SaveDialog 283 saves={saves} 284 onSave={(name) => this._saveDrawing(name)} 285 onClose={() => this.setState({ showingSave: false })} /> 286 } 287 </div> 288 </div> 289 ); 290 } 291 } 292 293 export default App;