- added missing fclose
- fixed makefile
- added support for urls and forward/backward browsing
- started optimization for touch screens


git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@8123 b45a01b8-16f6-495d-af2f-9b41ad6348cc
This commit is contained in:
electron128 2015-04-06 09:44:10 +00:00
parent 059a0b3bf7
commit c5c524862c
7 changed files with 203 additions and 146 deletions

View file

@ -520,6 +520,7 @@ int ApiServerMHD::accessHandlerCallback(MHD_Connection *connection,
struct stat s; struct stat s;
if(fstat(fileno(fd), &s) == -1) if(fstat(fileno(fd), &s) == -1)
{ {
fclose(fd);
const char *error = "<html><body><p>Error: file was opened but stat failed.</p></body></html>"; const char *error = "<html><body><p>Error: file was opened but stat failed.</p></body></html>";
struct MHD_Response* resp = MHD_create_response_from_data(strlen(error), (void*)error, 0, 1); struct MHD_Response* resp = MHD_create_response_from_data(strlen(error), (void*)error, 0, 1);
MHD_add_response_header(resp, "Content-Type", "text/html"); MHD_add_response_header(resp, "Content-Type", "text/html");

View file

@ -3,7 +3,7 @@ module.exports = function(grunt) {
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
watch: { watch: {
// important: exclude node_modules // important: exclude node_modules
files: ['**','!**/node_modules/**'], files: ['dist/**','!**/node_modules/**'],
options: { options: {
livereload: true, livereload: true,
} }

View file

@ -9,10 +9,10 @@ CSS = green-black.css
all: dist $(JSEXTLIBS) $(addprefix dist/, $(JSLIBS)) $(addprefix dist/, $(HTML)) $(addprefix dist/, $(JSGUI)) $(addprefix dist/, $(CSS)) all: dist $(JSEXTLIBS) $(addprefix dist/, $(JSLIBS)) $(addprefix dist/, $(HTML)) $(addprefix dist/, $(JSGUI)) $(addprefix dist/, $(CSS))
.PHONY: all .PHONY: all
dist/react.js: dist dist/react.js:
cd dist && wget --no-check-certificate --output-document react.js http://fb.me/react-$(REACT_VERSION).js cd dist && wget --no-check-certificate --output-document react.js http://fb.me/react-$(REACT_VERSION).js
dist/JSXTransformer.js: dist dist/JSXTransformer.js:
cd dist && wget --no-check-certificate --output-document JSXTransformer.js http://fb.me/JSXTransformer-$(REACT_VERSION).js cd dist && wget --no-check-certificate --output-document JSXTransformer.js http://fb.me/JSXTransformer-$(REACT_VERSION).js
$(addprefix dist/, $(JSLIBS)): dist/%: % $(addprefix dist/, $(JSLIBS)): dist/%: %

View file

@ -13,7 +13,38 @@ if(require.main === module)
var RS = new RsApi(connection); var RS = new RsApi(connection);
var tests = []; var tests = [];
PeersTest(tests); var doc = {
counter: 0,
toc: [],
content: [],
header: function(h){
this.toc.push(h);
this.content.push("<a name=\""+this.counter+"\"><h1>"+h+"</h1></a>");
this.counter += 1;
},
paragraph: function(p){
this.content.push("<p>"+p+"</p>");
},
};
PeersTest(tests, doc);
var docstr = "<!DOCTYPE html><html><body>";
docstr += "<h1>Table of Contents</h1>";
docstr += "<ul>";
for(var i in doc.toc)
{
docstr += "<li><a href=\"#"+i+"\">"+doc.toc[i]+"</a></li>";
}
docstr += "</ul>";
for(var i in doc.content)
{
docstr += doc.content[i];
}
docstr += "</body></html>";
var fs = require('fs');
fs.writeFile("dist/api_documentation.html", docstr);
tests.map(function(test){ tests.map(function(test){
test(RS); test(RS);
@ -39,6 +70,9 @@ function PeersTest(tests, doc)
}); });
var peers_list = new Type("peers_list",[peer_info]); var peers_list = new Type("peers_list",[peer_info]);
doc.header("peers");
doc.paragraph("<pre>"+graphToText(peers_list)+"</pre>");
tests.push(function(RS){ tests.push(function(RS){
console.log("testing peers module..."); console.log("testing peers module...");
console.log("expected schema is:") console.log("expected schema is:")
@ -106,126 +140,3 @@ function PeersTest(tests, doc)
} }
} }
} }
// ************ below is OLD stuff, to be removed ************
var Location =
{
avatar_address: String(),
groups: undefined,
is_online: Boolean(),
location: String(),
name: String(),
peer_id: undefined,
pgp_id: undefined,
};
var PeerInfo =
{
name: String(),
locations: [Location],
};
var PeersList = [PeerInfo];
function checkIfMatch(ref, other)
{
var ok = true;
// sets ok to false on error
function check(subref, subother, path)
{
//console.log("checking");
//console.log("path: " + path);
//console.log("subref: " +subref);
//console.log("subother: "+subother);
if(subref instanceof Array)
{
//console.log("is Array: " + path);
if(!(subother instanceof Array))
{
ok = false;
console.log("Error: not an Array " + path);
return;
}
if(subother.length == 0)
{
console.log("Warning: can't check Array of lentgh 0 " + path);
return;
}
// check first array member
check(subref[0], subother[0], path);
return;
}
// else compare as dict
for(m in subref)
{
if(!(m in subother))
{
ok = false;
console.log("Error: missing member \"" + m + "\" in "+ path);
continue;
}
if(subref[m] === undefined)
{
// undefined = don't care what it is
continue;
}
if(typeof(subref[m]) == typeof(subother[m]))
{
if(typeof(subref[m]) == "object")
{
// make deep object inspection
path.push(m);
check(subref[m], subother[m], path);
path.pop();
}
// else everthing is fine
}
else
{
ok = false;
console.log("Error: member \"" + m + "\" has wrong type in "+ path);
}
}
// TODO: check for additional members and print notice
}
check(ref, other, []);
return ok;
}
function stringifyTypes(obj)
{
if(obj instanceof Array)
{
return [stringifyTypes(obj[0])];
}
var ret = {};
for(m in obj)
{
if(typeof(obj[m]) === "object")
ret[m] = stringifyTypes(obj[m]);
else
ret[m] = typeof obj[m];
}
return ret;
}
// trick to get multiline string constants: use comment as string constant
var input = function(){/*
[{
"locations": [{
"avatar_address": "/5cfed435ebc24d2d0842f50c6443ec76/avatar_image",
"groups": null,
"is_online": true,
"location": "",
"name": "se2",
"peer_id": "5cfed435ebc24d2d0842f50c6443ec76",
"pgp_id": "985CAD914B19A212"
}],
"name": "se2"
}]
*/}.toString().slice(14,-3);
// **************** end of old stuff ***************************

View file

@ -3,7 +3,8 @@ body {
color: lime; color: lime;
font-family: monospace; font-family: monospace;
margin: 0em; margin: 0em;
padding: 1.5em; /*padding: 1.5em;*/
padding: 2mm;
font-size: 1.1em; font-size: 1.1em;
} }
@ -36,12 +37,40 @@ td{
padding: 0.1em; padding: 0.1em;
} }
.btn2{
border-style: solid;
border-color: lime;
border-radius: 3mm;
padding: 2mm;
font-size: 10mm;
cursor: pointer;
margin-bottom: 2mm;
}
.btn2:hover{
background-color: midnightblue;
}
input, textarea{ input, textarea{
color: lime; color: lime;
font-family: monospace; font-family: monospace;
background-color: black; background-color: black;
border-color: lime; border-color: lime;
font-size: 10mm;
border-radius: 3mm;
border-width: 1mm;
padding: 2mm;
margin-bottom: 2mm;
margin-right: 2mm;
/* make the button the whole screen width */
width: 100%;
/* make the text input fit small screens*/
box-sizing: border-box;
} }
input:hover{
background-color: midnightblue;
}
#logo_splash{ #logo_splash{
-webkit-animation-fill-mode: forwards; /* Chrome, Safari, Opera */ -webkit-animation-fill-mode: forwards; /* Chrome, Safari, Opera */
animation-fill-mode: forwards; animation-fill-mode: forwards;

View file

@ -69,7 +69,7 @@ var signalSlotServer =
/** /**
* Unregister a previously registered client. * Unregister a previously registered client.
*/ */
unregisterClient : function(client) // no token as parameter, assuming unregister from all listening tokens unregisterClient : function(client)
{ {
var to_delete = []; var to_delete = [];
var clients = this.clients; var clients = this.clients;
@ -139,6 +139,43 @@ var SignalSlotMixin =
}, },
}; };
// url hash handling
// - allows the backwards/forward button to work
// - restores same view for same url
// dataflow:
// emit change_url event -> event handler sets browser url hash
// -> browser sends hash changed event -> emit url_changed event
var prev_url = "";
function hashChanged(){
var url = window.location.hash.slice(1); // remove #
// prevent double work
//if(url !== prev_url)
if(true)
{
signalSlotServer.emitSignal("url_changed", {url: url});
prev_url = url;
}
}
// Listen on hash change:
window.addEventListener('hashchange', hashChanged);
// Listen on page load:
// this does not work, because the components are not always mounted when the event fires
window.addEventListener('load', hashChanged);
var changeUrlListener = {
onSignal: function(signal_name, parameters){
if(signal_name === "change_url")
{
console.log("changeUrlListener: change url to "+parameters.url);
window.location.hash = parameters.url;
// some browsers dont send an event, so trigger event by hand
// the history does not work then
hashChanged();
}
},
};
signalSlotServer.registerClient(changeUrlListener);
var Peers = React.createClass({ var Peers = React.createClass({
mixins: [AutoUpdateMixin], mixins: [AutoUpdateMixin],
getPath: function(){return "peers";}, getPath: function(){return "peers";},
@ -161,7 +198,7 @@ var Peers2 = React.createClass({
return {data: []}; return {data: []};
}, },
add_friend_handler: function(){ add_friend_handler: function(){
this.emit("url_changed", {url: "add_friend"}); this.emit("change_url", {url: "add_friend"});
}, },
render: function(){ render: function(){
var component = this; var component = this;
@ -202,7 +239,7 @@ var Peers2 = React.createClass({
return ( return (
<div> <div>
{/* span reduces width to only the text length, div does not */} {/* span reduces width to only the text length, div does not */}
<span onClick={this.add_friend_handler} className="btn">&#43; add friend</span> <div onClick={this.add_friend_handler} className="btn2">&#43; add friend</div>
<table> <table>
<tr><th>avatar</th><th> name </th><th> locations</th><th></th></tr> <tr><th>avatar</th><th> name </th><th> locations</th><th></th></tr>
{this.state.data.map(function(peer){ return <Peer key={peer.name} data={peer}/>; })} {this.state.data.map(function(peer){ return <Peer key={peer.name} data={peer}/>; })}
@ -465,14 +502,15 @@ var PasswordWidget = React.createClass({
render: function(){ render: function(){
if(this.state.data.want_password === false) if(this.state.data.want_password === false)
{ {
return(<p>PasswordWidget: nothing to do.</p>); return <div></div>;
//return(<p>PasswordWidget: nothing to do.</p>);
} }
else else
{ {
return( return(
<div> <div>
<p>Enter password for key {this.state.data.key_name}</p> <p>Enter password for key {this.state.data.key_name}</p>
<input type="text" ref="password" /> <input type="password" ref="password" />
<input <input
type="button" type="button"
value="ok" value="ok"
@ -487,7 +525,7 @@ var PasswordWidget = React.createClass({
var AccountSelectWidget = React.createClass({ var AccountSelectWidget = React.createClass({
mixins: [AutoUpdateMixin], mixins: [AutoUpdateMixin],
getInitialState: function(){ getInitialState: function(){
return {data: []}; return {mode: "list", data: []};
}, },
getPath: function(){ getPath: function(){
return "control/locations"; return "control/locations";
@ -495,14 +533,20 @@ var AccountSelectWidget = React.createClass({
selectAccount: function(id){ selectAccount: function(id){
console.log("login with id="+id) console.log("login with id="+id)
RS.request({path: "control/login", data:{id: id}}); RS.request({path: "control/login", data:{id: id}});
var state = this.state;
state.mode = "wait";
this.setState(state);
}, },
render: function(){ render: function(){
var component = this; var component = this;
if(this.state.mode === "wait")
return <p>waiting...</p>;
else
return( return(
<div> <div>
<div><p>select a location to log in</p></div> <div><p>select a location to log in</p></div>
{this.state.data.map(function(location){ {this.state.data.map(function(location){
return <div key={location.id} className="btn" onClick ={function(){component.selectAccount(location.id);}}>{location.name} ({location.location})</div>; return <div className="btn2" key={location.id} onClick ={function(){component.selectAccount(location.id);}}>{location.name} ({location.location})</div>;
})} })}
</div> </div>
); );
@ -541,10 +585,50 @@ var LoginWidget = React.createClass({
}, },
}); });
var MainWidget = React.createClass({ var Menu = React.createClass({
mixins: [SignalSlotMixin], mixins: [SignalSlotMixin],
getInitialState: function(){ getInitialState: function(){
return {page: "login"}; return {};
},
componentWillMount: function()
{
},
render: function(){
var outer = this;
return (
<div>
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "main"});}}>
Start
</div>
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "login"});}}>
Login
</div>
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "friends"});}}>
Friends
</div>
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "downloads"});}}>
Downloads
</div>
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "search"});}}>
Search
</div>
</div>
);
},
});
var MainWidget = React.createClass({
mixins: [SignalSlotMixin, AutoUpdateMixin],
getPath: function(){
return "control/runstate";
},
getInitialState: function(){
// hack: get hash
var url = window.location.hash.slice(1);
if(url === "")
url = "menu";
return {page: url, data: {runstate: "waiting_for_server"}};
//return {page: "login"};
}, },
componentWillMount: function() componentWillMount: function()
{ {
@ -553,20 +637,38 @@ var MainWidget = React.createClass({
function(params) function(params)
{ {
console.log("MainWidget received url_changed. url="+params.url); console.log("MainWidget received url_changed. url="+params.url);
outer.setState({page: params.url}); var url = params.url;
if(url.length === 0)
url = "menu";
outer.setState({page: url});
}); });
}, },
render: function(){ render: function(){
var outer = this; var outer = this;
var mainpage = <p>page not implemented: {this.state.page}</p>; var mainpage = <p>page not implemented: {this.state.page}</p>;
if(this.state.data.runstate === "waiting_for_server")
{
mainpage = <div><p>waiting for reply from server...<br/>please wait...</p></div>;
}
if(this.state.data.runstate === "waiting_init" || this.state.data.runstate === "waiting_account_select")
{
mainpage = <LoginWidget/>;
}
if(this.state.data.runstate === "running_ok")
{
if(this.state.page === "main") if(this.state.page === "main")
{ {
mainpage = <p> mainpage = <div><p>
A new webinterface for Retroshare. Build with react.js. A new webinterface for Retroshare. Build with react.js.
React allows to build a modern and user friendly single page application. React allows to build a modern and user friendly single page application.
The component system makes this very simple. The component system makes this very simple.
Updating the GUI is also very simple: one React mixin can handle updating for all components. Updating the GUI is also very simple: one React mixin can handle updating for all components.
</p>; </p>
<div className="btn2">Div Button</div>
<div className="btn2">Div Button</div>
</div>;
} }
if(this.state.page === "friends") if(this.state.page === "friends")
{ {
@ -592,27 +694,39 @@ var MainWidget = React.createClass({
{ {
mainpage = <LoginWidget/>; mainpage = <LoginWidget/>;
} }
if(this.state.page === "menu")
{
mainpage = <Menu/>;
}
}
var menubutton = <div onClick={function(){outer.emit("change_url", {url: "menu"});}} className="btn2">&lt;- menu</div>;
if(this.state.page === "menu")
menubutton = <div>Retroshare webinterface</div>;
return ( return (
<div> <div>
<PasswordWidget/> <PasswordWidget/>
<AudioPlayerWidget/> <AudioPlayerWidget/>
{/*
<ul className="nav"> <ul className="nav">
<li onClick={function(){outer.emit("url_changed", {url: "main"});}}> <li onClick={function(){outer.emit("change_url", {url: "main"});}}>
Start Start
</li> </li>
<li onClick={function(){outer.emit("url_changed", {url: "login"});}}> <li onClick={function(){outer.emit("change_url", {url: "login"});}}>
Login Login
</li> </li>
<li onClick={function(){outer.emit("url_changed", {url: "friends"});}}> <li onClick={function(){outer.emit("change_url", {url: "friends"});}}>
Friends Friends
</li> </li>
<li onClick={function(){outer.emit("url_changed", {url: "downloads"});}}> <li onClick={function(){outer.emit("change_url", {url: "downloads"});}}>
Downloads Downloads
</li> </li>
<li onClick={function(){outer.emit("url_changed", {url: "search"});}}> <li onClick={function(){outer.emit("change_url", {url: "search"});}}>
Search Search
</li> </li>
</ul> </ul>
*/}
{menubutton}
{mainpage} {mainpage}
</div> </div>
); );

View file

@ -14,11 +14,13 @@
<!-- automatic page reload --> <!-- automatic page reload -->
<!--<script src="http://localhost:9091"></script>--> <!--<script src="http://localhost:9091"></script>-->
<!-- load this last, because it contains errors --> <!-- load this last, because it contains errors -->
<script src="http://localhost:35729/livereload.js"></script> <!--<script src="http://localhost:35729/livereload.js"></script>-->
<script src="http://192.168.1.102:35720/livereload.js"></script>
<link href="green-black.css" rel="stylesheet"> <link href="green-black.css" rel="stylesheet">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
</head> </head>
<body> <body>
<p>loading lots of stuff...</p> <p>loading lots of stuff...</p>