Browse code

implement frontend

Joseph Weston authored on 15/04/2018 18:32:56
Showing 2 changed files
... ...
@@ -28,11 +28,11 @@
28 28
     "webpack-merge": "^4.1.2"
29 29
   },
30 30
   "dependencies": {
31
-    "bulma": "^0.6.2",
31
+    "normalize.css": "^8.0.0",
32 32
     "react": "^16.2.0",
33
+    "react-animate-height": "^0.10.10",
33 34
     "react-dom": "^16.2.0",
34 35
     "react-simple-maps": "^0.11.1",
35
-    "react-slidedown": "^1.3.0",
36 36
     "react-spinkit": "^3.0.0"
37 37
   }
38 38
 }
... ...
@@ -15,7 +15,7 @@
15 15
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16
 */
17 17
 import React from 'react' ;
18
-import {SlideDown} from 'react-slidedown'
18
+import AnimateHeight from 'react-animate-height'
19 19
 import Spinner from 'react-spinkit'
20 20
 import {
21 21
   ComposableMap,
... ...
@@ -24,8 +24,8 @@ import {
24 24
   Geography,
25 25
 } from "react-simple-maps"
26 26
 
27
-import 'bulma/css/bulma.css' ;
28
-import 'react-slidedown/lib/slidedown.css'
27
+import 'normalize.css/normalize.css'
28
+import './static/styles.css'
29 29
 
30 30
 // Import licence for GEOJSON so as to include it into the webpack output.
31 31
 // This is necessary to avoid violating the license terms.
... ...
@@ -38,6 +38,7 @@ const VPN = Object.freeze({
38 38
   connecting:2,
39 39
   connected:3,
40 40
   disconnecting: 4,
41
+  error: 5,
41 42
 }) ;
42 43
 
43 44
 class Nord extends React.Component {
... ...
@@ -50,17 +51,37 @@ class Nord extends React.Component {
50 51
         status: VPN.disconnected,
51 52
         country: null,
52 53
         host: null,
54
+        viewportWidth: null,
53 55
     } ;
54 56
     this.connection = null
55 57
 
58
+
56 59
     this.connect = this.connect.bind(this) ;
60
+    this.updateDimensions = this.updateDimensions.bind(this) ;
57 61
     this.disconnect = this.disconnect.bind(this) ;
58 62
     this.handleData = this.handleData.bind(this) ;
59 63
   }
60 64
 
65
+  updateDimensions() {
66
+    this.setState({
67
+      viewportWidth: Math.max(document.documentElement.clientWidth,
68
+                              window.innerWidth || 0)
69
+    }) ;
70
+  }
71
+
72
+  componentWillMount() {
73
+    this.updateDimensions() ;
74
+  }
75
+
76
+  componentWillUnmount() {
77
+    window.removeEventListener("resize", this.updateDimensions) ;
78
+  }
79
+
61 80
   componentDidMount() {
81
+    const upstream = location.hostname+(location.port ? ':'+location.port : '')
82
+    window.addEventListener("resize", this.updateDimensions) ;
62 83
     this.setState({enabled: false, loading: true})
63
-    this.connection = new WebSocket('ws://127.0.0.1:5000/api') ;
84
+    this.connection = new WebSocket('ws://'+upstream+'/api') ;
64 85
     this.connection.onopen = () =>
65 86
       this.setState({enabled: true, loading: false}) ;
66 87
     this.connection.onerror = () =>
... ...
@@ -74,21 +95,44 @@ class Nord extends React.Component {
74 95
     msg = JSON.parse(msg.data)
75 96
     switch(msg.state) {
76 97
       case "connected":
77
-        this.setState({status: VPN.connected, host: msg.host}) ;
98
+        this.setState({
99
+            status: VPN.connected,
100
+            host: msg.host
101
+        }) ;
78 102
         break ;
79 103
       case "disconnected":
80
-        this.setState({status: VPN.disconnected}) ;
104
+        this.setState({
105
+            status: VPN.disconnected
106
+        }) ;
107
+        break ;
108
+      case "connecting":
109
+        this.setState({
110
+            status: VPN.connecting,
111
+            country: msg.country,
112
+            host: null,
113
+        }) ;
114
+        break ;
115
+      case "disconnecting":
116
+        this.setState({
117
+            status: VPN.disconnecting,
118
+        }) ;
119
+        break ;
120
+      case "error":
121
+        this.setState({
122
+            status: VPN.error,
123
+            error_message: msg.message
124
+        }) ;
81 125
         break ;
82 126
     }
83 127
   }
84 128
 
85 129
   connect(geography) {
86 130
     const country_info = geography.properties ;
87
-    this.setState({
88
-        status: VPN.connecting,
89
-        country: country_info.NAME,
90
-        host: null,
91
-    }) ;
131
+//    this.setState({
132
+//        status: VPN.connecting,
133
+//        country: country_info.NAME,
134
+//        host: null,
135
+//    }) ;
92 136
 
93 137
     this.connection.send(JSON.stringify({
94 138
       method: 'connect',
... ...
@@ -97,7 +141,7 @@ class Nord extends React.Component {
97 141
   }
98 142
 
99 143
   disconnect() {
100
-    this.setState((prev) => ({ ...prev, status: VPN.disconnecting })) ;
144
+//    this.setState((prev) => ({ ...prev, status: VPN.disconnecting })) ;
101 145
 
102 146
     this.connection.send(JSON.stringify({
103 147
       method: 'disconnect'
... ...
@@ -106,11 +150,15 @@ class Nord extends React.Component {
106 150
 
107 151
   render() {
108 152
     return (
109
-      <div>
110
-        <Title/>
111
-        <VPNStatus connection={ this.state }
112
-                   onDisconnect={ this.disconnect }/>
113
-        <WorldMap onClick={ this.connect }/>
153
+      <div id="grid">
154
+        <Title className="full-width"/>
155
+        <section className="clip">
156
+          <VPNStatus connection={ this.state }
157
+                     onDisconnect={ this.disconnect }
158
+                     className="full-width overlap"/>
159
+          <WorldMap onClick={ this.connect }
160
+                    viewportWidth={ this.state.viewportWidth }/>
161
+        </section>
114 162
       </div>
115 163
     ) ;
116 164
   }
... ...
@@ -118,91 +166,98 @@ class Nord extends React.Component {
118 166
 }
119 167
 
120 168
 
121
-const Title = () => (
122
-  <section className="hero is-primary is-info has-text-centered">
123
-      <div className="container">
124
-        <div className="is-size-1">
125
-          Nord
126
-        </div>
127
-      </div>
128
-  </section>
129
-) ;
130
-
169
+const Title = (props) => {
170
+  return (
171
+    <section id="title" className={props.className}>
172
+    <h1 className="no-margin">Nord</h1>
173
+    </section>
174
+  ) ;
175
+} ;
131 176
 
132
-const VPNStatus = (props) => {
133
-  const connection = props.connection ;
134 177
 
135
-  const ConnectionSpinner = () => (
136
-    <div style={{"display": "inline-block", "margin-bottom": "-5px"}}>
137
-      <Spinner name="double-bounce" />
178
+const ConnectionSpinner = () => {
179
+  return (
180
+    <div>
181
+      <div className="connection-spinner">
182
+        <Spinner name="double-bounce" fadeIn="none" />
183
+      </div>
138 184
     </div>
139 185
   ) ;
186
+} ;
140 187
 
141
-  const DisconnectButton = () => {
142
-    var classes = "button is-danger " ;
143
-    if (connection.status == VPN.disconnecting) {
144
-      classes += "is-loading"
145
-    }
146
-    return (
147
-      <button className={classes} onClick={ props.onDisconnect }>
148
-        Disconnect
188
+const DisconnectButton = (props) => {
189
+  const onClick = props.connected ? props.onDisconnect : null ;
190
+  const content = props.connected ? "Disconnect" : <ConnectionSpinner/> ;
191
+  return (
192
+    <div className="disconnect-button-wrapper">
193
+      <button className="disconnect-button red" onClick={ onClick }>
194
+        { content }
149 195
       </button>
150
-    ) ;
151
-  }
196
+  </div>
197
+  ) ;
198
+} ;
199
+
200
+const VPNStatus = (props) => {
201
+  const connection = props.connection ;
152 202
 
153
-  const style = {"margin-right": "10px", display: "inline"} ;
154 203
   var color = "" ;
155
-  var text = "" ;
204
+  var content = null
156 205
 
157
-  switch(props.connection.status) {
158
-    case VPN.disconnected:
159
-      break ;
206
+  switch(connection.status) {
160 207
     case VPN.connecting:
161
-      color = "is-warning" ;
162
-      text = <div>
163
-              <div style={style}>
164
-                Connecting to servers in {connection.country}
165
-              </div>
166
-              <ConnectionSpinner/>
167
-            </div>
208
+      color = "yellow" ;
209
+      content = <div>
210
+                  Connecting to servers in <b>{ connection.country }</b>
211
+                  <ConnectionSpinner/>
212
+             </div>;
168 213
       break ;
169 214
     case VPN.connected:
170 215
     case VPN.disconnecting:
171
-      color = "is-success" ;
172
-      text = (<div>
173
-                <div style={style}>
174
-                  Connected to {connection.host}
175
-                </div>
176
-                <DisconnectButton/>
177
-              </div>) ;
216
+    case VPN.disconnected:
217
+      color = "green" ;
218
+      content = <div>
219
+                  Connected to <b>{ connection.host }</b>
220
+                  <DisconnectButton
221
+                    connected={ connection.status == VPN.connected }
222
+                    onDisconnect={ props.onDisconnect }/>
223
+               </div> ;
178 224
       break ;
225
+    case VPN.error:
226
+      color = "red" ;
227
+      content = <div>Error on backend: {connection.error_message}</div> ;
179 228
   }
180 229
 
181 230
   if (!connection.enabled && !connection.loading) {
182
-    color = "is-danger"
183
-    text = <div style={style}>Lost connection to server</div>
231
+    color = "red" ;
232
+    content = <div>Lost connection to backend</div> ;
184 233
   }
185 234
 
186
-  const classes = "hero is-primary has-text-centered " + color
187
-
188 235
   return (
189
-    <SlideDown>
190
-      <section className={classes}>
191
-          <div className="container">
192
-            <div className="is-size-4">
193
-              {text}
194
-            </div>
236
+      <AnimateHeight
237
+          className={props.className}
238
+          duration={ 500 }
239
+          height={ connection.status == VPN.disconnected ? '0' : 'auto'}>
240
+          <div className={"padded " + color}>
241
+            {content}
195 242
           </div>
196
-      </section>
197
-    </SlideDown>
243
+      </AnimateHeight>
198 244
   ) ;
199 245
 } ;
200 246
 
201 247
 
202 248
 const WorldMap = (props) => {
203 249
 
204
-  const zoom = 2 ;
205
-  const center = [ 0, 30 ] ;
250
+  var zoom = 7 ;
251
+  var center = [ 5, 65 ] ;
252
+  if (props.viewportWidth > 500) {
253
+    zoom = 4 ;
254
+    center = [5, 50]
255
+  }
256
+  if (props.viewportWidth > 1000) {
257
+    zoom = 2 ;
258
+  }
259
+
260
+
206 261
   const default_map_style = {
207 262
     fill: "#ECEFF1",
208 263
     stroke: "#607D8B",
... ...
@@ -211,7 +266,7 @@ const WorldMap = (props) => {
211 266
   } ;
212 267
   const map_container_style = {
213 268
     width: "100%",
214
-    height: "auto",
269
+    height: "100%",
215 270
   } ;
216 271
   const map_style = {
217 272
     default: default_map_style,