aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Ashby <martin@ashbysoft.com>2018-07-03 12:38:22 +0100
committerMartin Ashby <martin@ashbysoft.com>2018-07-03 12:38:22 +0100
commit3dc7e2a2e2158c99cb44d1ea3a3a6ff8738255d2 (patch)
tree65b6718e2fdc12034af02339c8b7742ea1cde15f
parent4a353d95f6d2dd8a9841bdae6f0721f5b014599e (diff)
downloadunicornpaint-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.icobin3870 -> 318 bytes
-rw-r--r--public/index.html2
-rw-r--r--src/App.css130
-rw-r--r--src/App.js64
-rw-r--r--src/ColorIndicator.js13
-rw-r--r--src/FrameControl.js78
-rw-r--r--src/PaintArea.js31
-rw-r--r--src/Palette.js32
-rw-r--r--src/Toolkit.js3
9 files changed, 185 insertions, 168 deletions
diff --git a/public/favicon.ico b/public/favicon.ico
index a11777c..9f9feae 100644
--- a/public/favicon.ico
+++ b/public/favicon.ico
Binary files differ
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
diff --git a/src/App.js b/src/App.js
index bbaf5e3..37fd7af 100644
--- a/src/App.js
+++ b/src/App.js
@@ -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>
}