├── .gitignore ├── README.md ├── index.html ├── src └── yome.js └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | .module-cache 2 | node_modules/ 3 | \#* 4 | \.\#* 5 | build/* 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Functional React Yome Widget 2 | 3 | I wrote this widget to demonstrate how to write straightforward 4 | functional code with [React.js](https://facebook.github.io/react/). 5 | 6 | The the blogpost. ;; not published link needed 7 | 8 | If you would like to experiment with this code you will need to 9 | install [Babel](http://babeljs.io/). 10 | 11 | ``` 12 | npm install -g babel 13 | ``` 14 | 15 | To auto-compile this code each time you save the `yome.js` file you 16 | will need to run babel like so: 17 | 18 | ``` 19 | babel src/ -w --out-dir build/ 20 | ``` 21 | 22 | The top of the `yome.js` file has a little auto-loading script that 23 | will continuously reload the code. This allows you to change the code 24 | and experiment with code and see your changes **without reloading the 25 | browser**. 26 | 27 | Have fun!! 28 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/yome.js: -------------------------------------------------------------------------------- 1 | var Reloader = Reloader || {}; 2 | 3 | Reloader.reloadFile = (path) => { 4 | var x = document.createElement("script"); 5 | x.setAttribute("src",path + "?rel=" + (new Date().getTime())); 6 | document.body.appendChild(x); 7 | setTimeout(function(){ document.body.removeChild(x);}, 500); 8 | } 9 | 10 | Reloader.startReloading = (files) => { 11 | setTimeout(function() { 12 | console.log("--- reloading ---"); 13 | files.map(Reloader.reloadFile); 14 | }, 1000); 15 | } 16 | 17 | Reloader.startReloading(["build/yome.js"]) 18 | 19 | function l(x) { console.log(x); return x; } 20 | 21 | var Yome = Yome || {}; 22 | 23 | Yome.initialState = () => { 24 | return { sides: [1,2,3,4,5,6,7,8].map( () => new Object() ) } 25 | } 26 | 27 | Yome.state = Yome.state || Yome.initialState(); 28 | //l(Yome.state) 29 | 30 | Yome.sideCount = (st) => st.sides.length 31 | //l(Yome.sideCount(Yome.state)) 32 | 33 | Yome.sliceTheta = (st) => 2 * Math.PI / Yome.sideCount(st) 34 | //l(Yome.sliceTheta(Yome.state)) 35 | 36 | 37 | Yome.rotate = (theta, point) => { 38 | const sint = Math.sin(theta), cost = Math.cos(theta); 39 | return { x: (point.x * cost) - (point.y * sint), 40 | y: (point.x * sint) + (point.y * cost) }; 41 | } 42 | //l(Yome.rotate(Math.PI, {x: 0, y: 1})); 43 | 44 | Yome.radialPoint = (radius, theta) => 45 | Yome.rotate(theta, {x: 0, y: radius}) 46 | //l(Yome.radialPoint(100, Math.PI)); 47 | 48 | Yome.sidePoints = (st) => 49 | st.sides.map((_,i) => Yome.radialPoint(180, i * Yome.sliceTheta(st))) 50 | //l(Yome.sidePoints(Yome.initialState())) 51 | 52 | Yome.pointsToPointsString = (points) => 53 | points.map(p => p.x + "," + p.y).join(" ") 54 | //l(Yome.pointsToPointsString(Yome.sidePoints(Yome.initialState()))) 55 | 56 | Yome.drawWalls = (state) => 57 | 58 | 59 | 60 | Yome.svgWorld = (children) => 61 | 63 | {children} 64 | 65 | 66 | Yome.playArea = (children) => 67 | React.render(Yome.svgWorld(children), document.getElementById("playarea")) 68 | 69 | Yome.clearPlayArea = () => 70 | React.unmountComponentAtNode(document.getElementById("playarea")) 71 | 72 | //Yome.playArea(Yome.drawWalls({sides: [1,2,3,4,5,6]})) 73 | //Yome.playArea(Yome.drawWalls({sides: [1,2,3,4,5,6,7]})) 74 | //Yome.playArea(Yome.drawWalls({sides: [1,2,3,4,5,6,7,8]})) 75 | 76 | //Yome.clearPlayArea() 77 | 78 | Yome.windowPoints = (st) => { 79 | const theta = Yome.sliceTheta(st), 80 | indent = theta / 6; 81 | return [Yome.radialPoint(160, indent), 82 | Yome.radialPoint(160, theta - indent), 83 | Yome.radialPoint(100, theta / 2)]; 84 | } 85 | //l(Yome.windowPoints(Yome.initialState())) 86 | 87 | Yome.drawWindow = (st) => 88 | 89 | 90 | 91 | //Yome.playArea({Yome.drawWindow(Yome.initialState())} 92 | // {Yome.drawWalls(Yome.initialState())}) 93 | 94 | Yome.doorPoints = (st) => { 95 | const indent = Yome.sliceTheta(st) / 8; 96 | return [Yome.radialPoint(165, indent ), 97 | Yome.radialPoint(165, -indent), 98 | Yome.radialPoint(90, -indent), 99 | Yome.radialPoint(90, indent)]; 100 | } 101 | 102 | Yome.drawDoor = (st) => 103 | 104 | 105 | 106 | //Yome.playArea({Yome.drawDoor(Yome.state)} 107 | // {Yome.drawWindow(Yome.state)} 108 | // {Yome.drawWalls(Yome.state)}) 109 | 110 | Yome.drawLine = (line) => 111 | 113 | 114 | 115 | Yome.drawZipDoor = (st) => { 116 | const theta = Yome.sliceTheta(st), 117 | indent = 0.15 * (theta / 6), 118 | lines = [0,1,2,3,4,5,6,7,8].map((x) => { 119 | const dist = 170 - (10 * x); 120 | return {start: Yome.radialPoint(dist, -indent), 121 | end: Yome.radialPoint(dist, indent)}}); 122 | lines.push({start: Yome.radialPoint(180, 0), 123 | end: Yome.radialPoint(90, 0)}); 124 | return {lines.map(Yome.drawLine)}; 125 | } 126 | 127 | //Yome.playArea({Yome.drawZipDoor(Yome.state)} 128 | // {Yome.drawWalls(Yome.state)}) 129 | 130 | Yome.drawStoveVent = (st) => { 131 | const theta = Yome.sliceTheta(st), 132 | point = Yome.radialPoint(155, 0); 133 | return 135 | } 136 | 137 | //Yome.playArea({Yome.drawStoveVent(Yome.state)} 138 | // {Yome.drawWalls(Yome.state)}) 139 | 140 | Yome.itemRenderDispatch = { 141 | "window": Yome.drawWindow, 142 | "door-frame": Yome.drawDoor, 143 | "zip-door": Yome.drawZipDoor, 144 | "stove-vent": Yome.drawStoveVent, 145 | } 146 | 147 | Yome.itemRender = (type, st) => 148 | (Yome.itemRenderDispatch[type] || (x => null))(st) 149 | 150 | Yome.exampleData = ((state)=>{ 151 | state.sides[0].face = "window" 152 | state.sides[0].corner = "zip-door" 153 | state.sides[3].face = "window" 154 | state.sides[5].corner = "door-frame" 155 | state.sides[5].face = "window" 156 | state.sides[7].corner = "stove-vent" 157 | return state 158 | })(Yome.initialState()) 159 | 160 | //l(JSON.stringify(Yome.exampleData)) 161 | 162 | Yome.sliceDeg = (st) => 360 / Yome.sideCount(st) 163 | 164 | Yome.sideSlice = (st, i) => { 165 | const side = st.sides[i]; 166 | if(side.corner || side.face) 167 | return 168 | {Yome.itemRender(side.corner, st)} 169 | {Yome.itemRender(side.face, st)} 170 | 171 | } 172 | 173 | //Yome.playArea(Yome.sideSlice(Yome.exampleData, 5)) 174 | //Yome.playArea(Yome.sideSlice(Yome.exampleData, 0)) 175 | 176 | Yome.drawYome = (st) => 177 | 178 | { Yome.drawWalls(st) } 179 | { st.sides.map((side, i) => Yome.sideSlice(st,i)) } 180 | 181 | 182 | //Yome.playArea(Yome.drawYome(Yome.exampleData)) 183 | //Yome.clearPlayArea() 184 | 185 | //side effecting 186 | Yome.eventHandler = (f) => 187 | (e => {e.preventDefault(); f(e.target.value); Yome.render()}) 188 | 189 | //side effecting 190 | Yome.changeSideCount = (new_count) => { 191 | let nArray = Array.apply(null, Array(parseInt(new_count))); 192 | Yome.state.sides = nArray.map((_,i) => Yome.state.sides[i] || {}); 193 | } 194 | //Yome.changeSideCount(6) 195 | //Yome.changeSideCount(7) 196 | //Yome.changeSideCount(8) 197 | 198 | Yome.sideOptions = () => 199 | ["HexaYome", "SeptaYome","OctaYome"].map( 200 | (l, v) => ) 201 | 202 | Yome.sideCountInput = st => 203 |
204 | Size of Yome 205 | 209 |
210 | //React.render(Yome.sideCountInput(Yome.state), 211 | // document.getElementById("playarea")) 212 | //Yome.clearPlayArea() 213 | 214 | Yome.worldPosition = (point) => ({ x: point.x + 250, y: point.y + 250}) 215 | 216 | Yome.addRemoveWindow = (i) => 217 | (_) => { 218 | const side = Yome.state.sides[i]; 219 | side.face = (!side.face ? "window" : null); 220 | } 221 | 222 | Yome.windowControl = (st, side, i) => { 223 | let theta = Yome.sliceTheta(st) * (i + 1), 224 | pos = Yome.worldPosition(Yome.radialPoint(200, theta)), 225 | add = !side.face; 226 | return
228 | 232 | { add ? "+ window" : "- window" } 233 | 234 |
235 | } 236 | 237 | Yome.windowControls = (st) => 238 | st.sides.map((side, i) => Yome.windowControl(st, side, i)) 239 | 240 | // --- Corner Controls 241 | 242 | // SIDE EFFECT 243 | Yome.addRemoveCornerItem = (type, side) => 244 | (_) => side.corner = (side.corner ? null : type) 245 | 246 | Yome.cornerControlStateClass = (type, corner_type) => 247 | ((! corner_type) && "add") || ((corner_type == type) && "remove") || "hidden" 248 | 249 | Yome.cornerControlLink = (type, side) => 250 | 253 | { (side.corner ? "- " : "+ ") + type } 254 | 255 | 256 | Yome.cornerControlLinks = (side, i) => 257 | ["stove-vent", "zip-door", "door-frame"].map( 258 | (t) => Yome.cornerControlLink(t, side)) 259 | 260 | Yome.cornerControl = (st, side, i) => { 261 | let theta = Yome.sliceTheta(st) * (i + 0.5), 262 | pos = Yome.worldPosition(Yome.radialPoint(221, theta)); 263 | return
264 |
265 | { Yome.cornerControlLinks(side, i) } 266 |
267 |
268 | } 269 | 270 | Yome.cornerControls = (st) => 271 | st.sides.map((side, i) => Yome.cornerControl(st, side, i)) 272 | 273 | // --- Add new code above this line --- 274 | 275 | Yome.widget = (st) => 276 |
277 | { Yome.sideCountInput(st) } 278 |
279 | { Yome.windowControls(st) } 280 | { Yome.cornerControls(st) } 281 | { Yome.svgWorld(Yome.drawYome(st)) } 282 |
283 |
284 | 285 | Yome.render = () => 286 | React.render(Yome.widget(Yome.state), document.getElementById('app')) 287 | 288 | Yome.render(); 289 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(24,26,38); 3 | color: white; 4 | font-family: sans-serif; 5 | } 6 | 7 | svg line, svg polygon, svg ellipse { 8 | stroke: #2997ab; 9 | stroke-width: 2; 10 | fill: transparent; 11 | -webkit-animation: appear 0.7s; 12 | } 13 | 14 | .yome-widget-body { position: relative; } 15 | .control-holder { position: absolute; } 16 | 17 | .window-control-offset { 18 | position: relative; 19 | top: -7px; 20 | left: -25px; 21 | } 22 | 23 | .corner-control-offset { 24 | position: relative; 25 | top: -21px; 26 | left: -35px; 27 | } 28 | 29 | a { 30 | color: white; 31 | text-decoration: none; 32 | display: block; 33 | font-size: 12px; 34 | white-space: nowrap; 35 | } 36 | 37 | a.remove { color: rgb(239, 131, 75);} 38 | a.hidden { visibility: hidden;} 39 | 40 | @-webkit-keyframes appear { 41 | 0% { opacity: 0; } 42 | 100% { opacity: 1;} 43 | } 44 | --------------------------------------------------------------------------------