Browse code

remove commented codeblocks and a minor refactoring

Joseph Weston authored on 07/06/2018 11:03:10
Showing 1 changed files
... ...
@@ -127,22 +127,13 @@ class Nord extends React.Component {
127 127
   }
128 128
 
129 129
   connect(geography) {
130
-    const country_info = geography.properties ;
131
-//    this.setState({
132
-//        status: VPN.connecting,
133
-//        country: country_info.NAME,
134
-//        host: null,
135
-//    }) ;
136
-
137 130
     this.connection.send(JSON.stringify({
138 131
       method: 'connect',
139
-      country: country_info.ISO_A2
132
+      country: geography.properties.ISO_A2
140 133
     })) ;
141 134
   }
142 135
 
143 136
   disconnect() {
144
-//    this.setState((prev) => ({ ...prev, status: VPN.disconnecting })) ;
145
-
146 137
     this.connection.send(JSON.stringify({
147 138
       method: 'disconnect'
148 139
     })) ;
Browse code

implement frontend

Joseph Weston authored on 15/04/2018 18:32:56
Showing 1 changed files
... ...
@@ -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,
Browse code

add mock web API

Joseph Weston authored on 25/02/2018 21:07:51
Showing 1 changed files
... ...
@@ -45,13 +45,41 @@ class Nord extends React.Component {
45 45
   constructor(props) {
46 46
     super(props) ;
47 47
     this.state = {
48
+        enabled: false,
49
+        loading: true,
48 50
         status: VPN.disconnected,
49 51
         country: null,
50 52
         host: null,
51 53
     } ;
54
+    this.connection = null
52 55
 
53 56
     this.connect = this.connect.bind(this) ;
54 57
     this.disconnect = this.disconnect.bind(this) ;
58
+    this.handleData = this.handleData.bind(this) ;
59
+  }
60
+
61
+  componentDidMount() {
62
+    this.setState({enabled: false, loading: true})
63
+    this.connection = new WebSocket('ws://127.0.0.1:5000/api') ;
64
+    this.connection.onopen = () =>
65
+      this.setState({enabled: true, loading: false}) ;
66
+    this.connection.onerror = () =>
67
+      this.setState({enabled: false, loading: false}) ;
68
+    this.connection.onclose = () =>
69
+      this.setState({enabled: false, loading: false}) ;
70
+    this.connection.onmessage = this.handleData ;
71
+  }
72
+
73
+  handleData(msg) {
74
+    msg = JSON.parse(msg.data)
75
+    switch(msg.state) {
76
+      case "connected":
77
+        this.setState({status: VPN.connected, host: msg.host}) ;
78
+        break ;
79
+      case "disconnected":
80
+        this.setState({status: VPN.disconnected}) ;
81
+        break ;
82
+    }
55 83
   }
56 84
 
57 85
   connect(geography) {
... ...
@@ -61,23 +89,19 @@ class Nord extends React.Component {
61 89
         country: country_info.NAME,
62 90
         host: null,
63 91
     }) ;
64
-    // TODO: add API call to connect to VPN and remove this stub
65
-    setTimeout((prev) => (
66
-      this.setState({
67
-        ...prev,
68
-        status: VPN.connected,
69
-        host: country_info.ISO_A2.toLowerCase() + "#123",
70
-      })), 5000) ;
92
+
93
+    this.connection.send(JSON.stringify({
94
+      method: 'connect',
95
+      country: country_info.ISO_A2
96
+    })) ;
71 97
   }
72 98
 
73 99
   disconnect() {
74 100
     this.setState((prev) => ({ ...prev, status: VPN.disconnecting })) ;
75
-    // TODO: add API call to disconnect from VPN and remove this stub
76
-    setTimeout((prev) => (
77
-      this.setState({
78
-        ...prev,
79
-        status: VPN.disconnected,
80
-      })), 2000) ;
101
+
102
+    this.connection.send(JSON.stringify({
103
+      method: 'disconnect'
104
+    })) ;
81 105
   }
82 106
 
83 107
   render() {
... ...
@@ -154,6 +178,11 @@ const VPNStatus = (props) => {
154 178
       break ;
155 179
   }
156 180
 
181
+  if (!connection.enabled && !connection.loading) {
182
+    color = "is-danger"
183
+    text = <div style={style}>Lost connection to server</div>
184
+  }
185
+
157 186
   const classes = "hero is-primary has-text-centered " + color
158 187
 
159 188
   return (
Browse code

make minimal frontend

Mock backend communication.

Joseph Weston authored on 25/02/2018 15:45:51
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,213 @@
1
+/*
2
+ Copyright 2018 Joseph Weston
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU General Public License as published by
6
+ the Free Software Foundation, either version 3 of the License, or
7
+ (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
+ GNU General Public License for more details.
13
+
14
+ You should have received a copy of the GNU General Public License
15
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
+*/
17
+import React from 'react' ;
18
+import {SlideDown} from 'react-slidedown'
19
+import Spinner from 'react-spinkit'
20
+import {
21
+  ComposableMap,
22
+  ZoomableGroup,
23
+  Geographies,
24
+  Geography,
25
+} from "react-simple-maps"
26
+
27
+import 'bulma/css/bulma.css' ;
28
+import 'react-slidedown/lib/slidedown.css'
29
+
30
+// Import licence for GEOJSON so as to include it into the webpack output.
31
+// This is necessary to avoid violating the license terms.
32
+import './static/LICENSE'
33
+import World from './static/world.geo.json'
34
+
35
+
36
+const VPN = Object.freeze({
37
+  disconnected:1,
38
+  connecting:2,
39
+  connected:3,
40
+  disconnecting: 4,
41
+}) ;
42
+
43
+class Nord extends React.Component {
44
+
45
+  constructor(props) {
46
+    super(props) ;
47
+    this.state = {
48
+        status: VPN.disconnected,
49
+        country: null,
50
+        host: null,
51
+    } ;
52
+
53
+    this.connect = this.connect.bind(this) ;
54
+    this.disconnect = this.disconnect.bind(this) ;
55
+  }
56
+
57
+  connect(geography) {
58
+    const country_info = geography.properties ;
59
+    this.setState({
60
+        status: VPN.connecting,
61
+        country: country_info.NAME,
62
+        host: null,
63
+    }) ;
64
+    // TODO: add API call to connect to VPN and remove this stub
65
+    setTimeout((prev) => (
66
+      this.setState({
67
+        ...prev,
68
+        status: VPN.connected,
69
+        host: country_info.ISO_A2.toLowerCase() + "#123",
70
+      })), 5000) ;
71
+  }
72
+
73
+  disconnect() {
74
+    this.setState((prev) => ({ ...prev, status: VPN.disconnecting })) ;
75
+    // TODO: add API call to disconnect from VPN and remove this stub
76
+    setTimeout((prev) => (
77
+      this.setState({
78
+        ...prev,
79
+        status: VPN.disconnected,
80
+      })), 2000) ;
81
+  }
82
+
83
+  render() {
84
+    return (
85
+      <div>
86
+        <Title/>
87
+        <VPNStatus connection={ this.state }
88
+                   onDisconnect={ this.disconnect }/>
89
+        <WorldMap onClick={ this.connect }/>
90
+      </div>
91
+    ) ;
92
+  }
93
+
94
+}
95
+
96
+
97
+const Title = () => (
98
+  <section className="hero is-primary is-info has-text-centered">
99
+      <div className="container">
100
+        <div className="is-size-1">
101
+          Nord
102
+        </div>
103
+      </div>
104
+  </section>
105
+) ;
106
+
107
+
108
+const VPNStatus = (props) => {
109
+  const connection = props.connection ;
110
+
111
+  const ConnectionSpinner = () => (
112
+    <div style={{"display": "inline-block", "margin-bottom": "-5px"}}>
113
+      <Spinner name="double-bounce" />
114
+    </div>
115
+  ) ;
116
+
117
+  const DisconnectButton = () => {
118
+    var classes = "button is-danger " ;
119
+    if (connection.status == VPN.disconnecting) {
120
+      classes += "is-loading"
121
+    }
122
+    return (
123
+      <button className={classes} onClick={ props.onDisconnect }>
124
+        Disconnect
125
+      </button>
126
+    ) ;
127
+  }
128
+
129
+  const style = {"margin-right": "10px", display: "inline"} ;
130
+  var color = "" ;
131
+  var text = "" ;
132
+
133
+  switch(props.connection.status) {
134
+    case VPN.disconnected:
135
+      break ;
136
+    case VPN.connecting:
137
+      color = "is-warning" ;
138
+      text = <div>
139
+              <div style={style}>
140
+                Connecting to servers in {connection.country}
141
+              </div>
142
+              <ConnectionSpinner/>
143
+            </div>
144
+      break ;
145
+    case VPN.connected:
146
+    case VPN.disconnecting:
147
+      color = "is-success" ;
148
+      text = (<div>
149
+                <div style={style}>
150
+                  Connected to {connection.host}
151
+                </div>
152
+                <DisconnectButton/>
153
+              </div>) ;
154
+      break ;
155
+  }
156
+
157
+  const classes = "hero is-primary has-text-centered " + color
158
+
159
+  return (
160
+    <SlideDown>
161
+      <section className={classes}>
162
+          <div className="container">
163
+            <div className="is-size-4">
164
+              {text}
165
+            </div>
166
+          </div>
167
+      </section>
168
+    </SlideDown>
169
+  ) ;
170
+} ;
171
+
172
+
173
+const WorldMap = (props) => {
174
+
175
+  const zoom = 2 ;
176
+  const center = [ 0, 30 ] ;
177
+  const default_map_style = {
178
+    fill: "#ECEFF1",
179
+    stroke: "#607D8B",
180
+    strokeWidth: 0.75,
181
+    outline: "none",
182
+  } ;
183
+  const map_container_style = {
184
+    width: "100%",
185
+    height: "auto",
186
+  } ;
187
+  const map_style = {
188
+    default: default_map_style,
189
+    hover: { ...default_map_style, fill: "#CFD8DC" },
190
+    pressed: { ...default_map_style, fill: "#CFD8DC" },
191
+  } ;
192
+
193
+  return (
194
+    <ComposableMap style={ map_container_style }>
195
+      <ZoomableGroup zoom={ zoom } center={ center }>
196
+        <Geographies geography={ World }>
197
+          {(geographies, projection) => geographies.map(geography => (
198
+            <Geography
199
+              key={ geography.id }
200
+              geography={ geography }
201
+              projection={ projection }
202
+              style={ map_style }
203
+              onClick={ props.onClick }
204
+              />
205
+          ))}
206
+        </Geographies>
207
+      </ZoomableGroup>
208
+    </ComposableMap>
209
+  ) ;
210
+} ;
211
+
212
+
213
+export default Nord ;