diff options
author | Martin Ashby <martin@ashbysoft.com> | 2018-07-03 12:38:22 +0100 |
---|---|---|
committer | Martin Ashby <martin@ashbysoft.com> | 2018-07-03 12:38:22 +0100 |
commit | 3dc7e2a2e2158c99cb44d1ea3a3a6ff8738255d2 (patch) | |
tree | 65b6718e2fdc12034af02339c8b7742ea1cde15f | |
parent | 4a353d95f6d2dd8a9841bdae6f0721f5b014599e (diff) | |
download | unicornpaint-3dc7e2a2e2158c99cb44d1ea3a3a6ff8738255d2.tar.gz unicornpaint-3dc7e2a2e2158c99cb44d1ea3a3a6ff8738255d2.tar.bz2 unicornpaint-3dc7e2a2e2158c99cb44d1ea3a3a6ff8738255d2.tar.xz unicornpaint-3dc7e2a2e2158c99cb44d1ea3a3a6ff8738255d2.zip |
Better layout with CSS.
Changed favicon & title.
-rw-r--r-- | public/favicon.ico | bin | 3870 -> 318 bytes | |||
-rw-r--r-- | public/index.html | 2 | ||||
-rw-r--r-- | src/App.css | 130 | ||||
-rw-r--r-- | src/App.js | 64 | ||||
-rw-r--r-- | src/ColorIndicator.js | 13 | ||||
-rw-r--r-- | src/FrameControl.js | 78 | ||||
-rw-r--r-- | src/PaintArea.js | 31 | ||||
-rw-r--r-- | src/Palette.js | 32 | ||||
-rw-r--r-- | src/Toolkit.js | 3 |
9 files changed, 185 insertions, 168 deletions
diff --git a/public/favicon.ico b/public/favicon.ico Binary files differindex a11777c..9f9feae 100644 --- a/public/favicon.ico +++ b/public/favicon.ico diff --git a/public/index.html b/public/index.html index ed0ebaf..300c1e9 100644 --- a/public/index.html +++ b/public/index.html @@ -19,7 +19,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - <title>React App</title> + <title>Unicorn Paint!</title> </head> <body> <noscript> diff --git a/src/App.css b/src/App.css index c9a3616..92768d0 100644 --- a/src/App.css +++ b/src/App.css @@ -1,81 +1,117 @@ body { background: black; color: aliceblue; - padding-left: 50px; - padding-right: 50px; + padding: 10px; } .container { - display: grid; - grid-template-columns: 1fr 1fr; - - grid-template-areas: - "header header" - "paint liveFeed"; - grid-gap: 5px; - -} - -@media (max-width: 760px) { - .container { - display: grid; - grid-template-columns: 1fr; - - grid-template-areas: - "header" - "paint" - "liveFeed"; - } + display: flex; + flex-flow: row wrap; + justify-content: center; } .header { - grid-area: header; + width: 100%; + text-align: center; } -.paint { - grid-area: paint; +.liveFeed { + margin-left: 10px; } -.liveFeed { - grid-area: liveFeed; +.framePaintContainer { + flex-direction: column; } .paintContainer { display: grid; - grid-template-columns: 1fr 1fr 1frpx; - - grid-template-areas: - "paint paint tools" - "paintCol palette tools"; + grid-template-columns: 8fr 32fr 5fr; + grid-template-rows: 32fr 8fr; } .paint { - grid-area: paint; + grid-column-start: 1; + grid-column-end: 3; + grid-row-start: 1; + grid-row-end: 2; + + display: grid; + grid-template: repeat(16, 1fr) / repeat(16, 1fr); +} + +.paintAreaCell { + border: 1px solid grey; } + .tools { - grid-area: tools; + grid-column-start: 3; + grid-row-start: 1; + grid-row-end: 3; + display: flex; flex-direction: column; - width: 45px; + align-items: center; +} + +.paintCol { + grid-row-start: 2; + grid-column-start: 1; } .palette { - grid-area: palette; + background: fuchsia; + grid-row-start: 2; + grid-column-start: 2; + + width: 100%; + height: 100%; + display: grid; + grid-template-columns: repeat(8, 1fr); + grid-template-rows: 1fr 1fr; +} + +.paletteItem { + border: 1px solid black; +} + +.paletteItem.selected { + border: 1px solid red; +} + +.frameControl { + grid-row-start: 3; + grid-column-start: 1; + grid-column-end: 4; + display: flex; - flex-direction: row; + flex-flow: row wrap; } -.paintCol { - grid-area: paintCol; +.framePreviewWrapper { + padding: 5px; } -.paintAreaCell { - width: "20px"; - height: "20px"; - min-width: "20px"; - min-height: "20px"; - max-width: "20px"; - max-height: "20px"; - border: "1px solid grey"; +.animationPreviewWrapper { + padding: 5px; +} + +.framePreviewWrapper.selected { + border: 2px solid red; +} + +.framePreview { + display: grid; + grid-template: repeat(16, 1fr) / repeat(16, 1fr); +} +.framePreviewCell { + width: 4px; + height: 4px; +} + +.animationPreview { + width: 64px; + height: 64px; + background-size: 64px 64px; + transform: rotate(180deg) scaleX(-1); }
\ No newline at end of file @@ -47,7 +47,8 @@ const tools = [ name: "pick", icon: "fas fa-eye-dropper", action: function (x, y, frame) { - let color = getPixel(x, y, this.state.pixels) + let pixels = this.state.frames[frame] + let color = getPixel(x, y, pixels) this.setState({ selectedColor: color }) }, }, @@ -115,6 +116,7 @@ class App extends Component { // Data from server frames: [], saves: [], + imageData: "", // Local data selectedFrame: 0, @@ -149,6 +151,7 @@ class App extends Component { frames: frames, saves: saves, selectedFrame: selectedFrame, + imageData: imageData, }) } @@ -169,8 +172,11 @@ class App extends Component { _connectWebsocket() { let webSocketProto = window.location.protocol === "https:" ? "wss:" : "ws:" - let host = window.location.host - // let host = window.location.hostname + ":3001" + var host = window.location.host + if (true) { + console.log("Dev mode overriding port to 3001") + host = window.location.hostname + ":3001" + } this._websocket = new WebSocket(`${webSocketProto}//${host}/ws`) this._websocket.onmessage = this._onMessage this._websocket.onopen = this._onOpen @@ -224,38 +230,43 @@ class App extends Component { } render() { - let frame = this.state.selectedFrame - let pixels = this.state.frames[frame] || [] + let { selectedFrame, + frames, + imageData, + connected, + selectedTool, + selectedColor, + saves, + showingLoad, + showingSave } = this.state + + let pixels = frames[selectedFrame] || [] + return ( <div className="container"> <div className="header"> <h1>Unicorn Paint!</h1> - <ConnectedIndicator connected={this.state.connected} /> + <ConnectedIndicator connected={connected} /> </div> - <div className="paintContainer"> - <FrameControl - frames={this.state.frames} - selectedFrame={this.state.selectedFrame} - onFrameSelected={ frame => this.setState({selectedFrame: frame}) }/> - <div className="paint"> + <div className="framePaintContainer"> + <div className="paintContainer"> <PaintArea data={pixels} onTool={this._applyTool} /> - </div> - <div className="tools"> <Toolkit tools={tools} - selectedTool={this.state.selectedTool} + selectedTool={selectedTool} onSelectTool={this._selectTool} /> - </div> - <div className="palette"> <Palette - selectedColor={this.state.selectedColor} - onSelectColor={(color) => this.setState({ selectedColor: color })} /> - </div> - <div className="paintCol"> - <ColorIndicator color={this.state.selectedColor} /> + selectedColor={selectedColor} + onSelectColor={(color) => this.setState({ selectedColor: color })} /> + <ColorIndicator color={selectedColor} /> </div> + <FrameControl + frames={frames} + selectedFrame={selectedFrame} + onFrameSelected={ frame => this.setState({selectedFrame: frame}) } + imageData={imageData}/> </div> <div className="liveFeed"> <Timeline @@ -271,23 +282,22 @@ class App extends Component { </div> <div> { - this.state.showingLoad + showingLoad && <LoadDialog - saves={this.state.saves} + saves={saves} onLoad={(drawing) => this._loadDrawing(drawing)} onClose={() => this.setState({ showingLoad: false })} /> } </div> <div> { - this.state.showingSave + showingSave && <SaveDialog - saves={this.state.saves} + saves={saves} onSave={(name) => this._saveDrawing(name)} onClose={() => this.setState({ showingSave: false })} /> } </div> - </div> ); } diff --git a/src/ColorIndicator.js b/src/ColorIndicator.js index 2ae15d1..df0a517 100644 --- a/src/ColorIndicator.js +++ b/src/ColorIndicator.js @@ -19,19 +19,12 @@ export default class ColorIndicator extends Component { } return <div + className="paintCol" style={{ background: `rgb(${r},${g},${b})`, - color: foreground, - ...styles.colorIndicator + color: foreground }}> - <span>{colorDesc}</span> + {/* <span>{colorDesc}</span> */} </div> } } - -const styles = { - colorIndicator: { - width: "100px", - height: "100px" - } -}
\ No newline at end of file diff --git a/src/FrameControl.js b/src/FrameControl.js index 45db5d4..181b71b 100644 --- a/src/FrameControl.js +++ b/src/FrameControl.js @@ -3,54 +3,66 @@ import { rgb, getPixel } from './Utils' class FramePreview extends Component { render() { - let width = this.props.pixels.length - let height = this.props.pixels[0].length + let { pixels, selected, index, onClick } = this.props + let width = pixels.length + let height = pixels[0].length - let rows = [] + let cells = [] for (var y=height-1; y>=0; y--) { - let cells = [] for (var x=0; x<width; x++) { - let {r, g, b} = rgb(getPixel(x, y, this.props.pixels)) - cells.push(<td + let {r, g, b} = rgb(getPixel(x, y, pixels)) + cells.push(<div + key={`FramePreview ${index} ${x} ${y}`} + className="framePreviewCell" style={{ background: `rgb(${r},${g},${b})`, - ...styles.previewPixel }}/>) } - rows.push(<tr key={y}>{cells}</tr>) } - let bgColor = this.props.selected ? "red" : "grey" - return <table onClick={this.props.onClick} style={{background: bgColor}}><tbody> {rows} </tbody></table> + let wrapperClassName = selected ? "framePreviewWrapper selected" : "framePreviewWrapper" + return <div + className={wrapperClassName} + onClick={onClick}> + <span>Frame {index + 1}</span> + <div className="framePreview"> + {cells} + </div> + </div> } } +function animationPreview(props) { + let { imageData } = props + let imgUrl = `url(data:image/gif;base64,${imageData})` + return <div className="animationPreviewWrapper"> + <span>Preview:</span> + <div className="animationPreview" + style={{backgroundImage: imgUrl}}/> + </div> +} + export default class FrameControl extends Component { - // frames: [] - // selectedFrame: number render() { // A series of divs, 1 per frame, - let frames = this.props.frames - let selectedFrame = this.props.selectedFrame + let { frames, + selectedFrame, + onFrameSelected, + imageData } = this.props + let framePreviews = frames.map((frame, ix) => - <div> - <span>Frame {ix+1}</span> - <FramePreview - pixels={frame} - selected={ix === this.props.selectedFrame} - onClick={() => this.props.onFrameSelected(ix)}/> - </div>) - return <div style={styles.previewContainer}>{framePreviews}</div> - } -} + <FramePreview + key={ix} + index={ix} + pixels={frame} + selected={ix === selectedFrame} + onClick={() => onFrameSelected(ix)}/>) + + -const styles = { - previewcontainer: { - }, - previewTable: { - }, - previewPixel: { - width: "4px", - height: "4px", + return <div className="frameControl"> + {animationPreview({imageData:imageData})} + {framePreviews} + </div> } -}
\ No newline at end of file +} diff --git a/src/PaintArea.js b/src/PaintArea.js index ac7ce96..e42ec9e 100644 --- a/src/PaintArea.js +++ b/src/PaintArea.js @@ -47,44 +47,27 @@ export default class PaintArea extends Component { render() { let data = this.props.data let height = data[0] ? data[0].length : 0 - let rows = [] + let cells = [] 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 + cells.push(<div + className="paintAreaCell" onMouseMove={() => this.handleMouseMove(ix, iy)} onClick={() => this.props.onTool(ix, iy)} style={{ - background: `rgb(${r},${g},${b})`, - ...styles.paintAreaCell + background: `rgb(${r},${g},${b})` }} key={(ix * 100000) + iy}/>) } - rows.push(<tr key={y}>{cells}</tr>) } return ( - <table - draggable={false}> - <tbody> - {rows} - </tbody> - </table> + <div className="paint"> + {cells} + </div> ) } } - -const styles = { - paintAreaCell: { - width: "20px", - height: "20px", - minWidth: "20px", - minHeight: "20px", - maxWidth: "20px", - maxHeight: "20px", - border: "1px solid grey", - } -}
\ No newline at end of file diff --git a/src/Palette.js b/src/Palette.js index fd4776f..9ffd19b 100644 --- a/src/Palette.js +++ b/src/Palette.js @@ -28,35 +28,17 @@ const colorPalette = [ let selected = rgb(this.props.selectedColor) let isSelected = r === selected.r && g === selected.g && b === selected.b - let style = Object.assign({}, - {background: `rgb(${r},${g},${b})`}, - styles.paletteItem, - isSelected && styles.selected - ) + let className = isSelected ? "paletteItem selected" : "paletteItem" return <div - onClick={() => this.props.onSelectColor([r, g, b])} - style={style} - key={r*10000+g*1000+b}/> + key={r*10000+g*1000+b} + className={className} + style={{background: `rgb(${r},${g},${b})`}} + onClick={() => this.props.onSelectColor([r, g, b])}/> }) - let list1 = paletteListItems.slice(0, paletteListItems.length / 2) - let list2 = paletteListItems.slice(paletteListItems.length / 2) return ( - <div> - {list1}<br/> - {list2} + <div className="palette"> + {paletteListItems} </div>) } - } - - const styles = { - paletteItem: { - width: "30px", - height: "30px", - border: "3px solid black", - display: "inline-block", - }, - selected: { - border: "3px solid red" - } }
\ No newline at end of file diff --git a/src/Toolkit.js b/src/Toolkit.js index e29d71d..c99d2ac 100644 --- a/src/Toolkit.js +++ b/src/Toolkit.js @@ -23,7 +23,8 @@ export default class Toolkit extends Component { selected={tool === this.props.selectedTool} onSelectTool={this.props.onSelectTool}/> }) - return <div style={styles.toolkit}> + return <div className="tools" + style={styles.toolkit}> {toolComponents} </div> } |