commit 3dc7e2a2e2158c99cb44d1ea3a3a6ff8738255d2
parent 4a353d95f6d2dd8a9841bdae6f0721f5b014599e
Author: Martin Ashby <martin@ashbysoft.com>
Date:   Tue,  3 Jul 2018 12:38:22 +0100
Better layout with CSS.
Changed favicon & title.
Diffstat:
9 files changed, 185 insertions(+), 171 deletions(-)
diff --git a/public/favicon.ico b/public/favicon.ico
Binary files differ.
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
   }