- 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;
if(fstat(fileno(fd), &s) == -1)
{
fclose(fd);
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);
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'),
watch: {
// important: exclude node_modules
files: ['**','!**/node_modules/**'],
files: ['dist/**','!**/node_modules/**'],
options: {
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))
.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
dist/JSXTransformer.js: dist
dist/JSXTransformer.js:
cd dist && wget --no-check-certificate --output-document JSXTransformer.js http://fb.me/JSXTransformer-$(REACT_VERSION).js
$(addprefix dist/, $(JSLIBS)): dist/%: %

View File

@ -13,7 +13,38 @@ if(require.main === module)
var RS = new RsApi(connection);
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){
test(RS);
@ -39,6 +70,9 @@ function PeersTest(tests, doc)
});
var peers_list = new Type("peers_list",[peer_info]);
doc.header("peers");
doc.paragraph("<pre>"+graphToText(peers_list)+"</pre>");
tests.push(function(RS){
console.log("testing peers module...");
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;
font-family: monospace;
margin: 0em;
padding: 1.5em;
/*padding: 1.5em;*/
padding: 2mm;
font-size: 1.1em;
}
@ -36,12 +37,40 @@ td{
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{
color: lime;
font-family: monospace;
background-color: black;
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{
-webkit-animation-fill-mode: forwards; /* Chrome, Safari, Opera */
animation-fill-mode: forwards;

View File

@ -69,7 +69,7 @@ var signalSlotServer =
/**
* 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 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({
mixins: [AutoUpdateMixin],
getPath: function(){return "peers";},
@ -161,7 +198,7 @@ var Peers2 = React.createClass({
return {data: []};
},
add_friend_handler: function(){
this.emit("url_changed", {url: "add_friend"});
this.emit("change_url", {url: "add_friend"});
},
render: function(){
var component = this;
@ -202,7 +239,7 @@ var Peers2 = React.createClass({
return (
<div>
{/* 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>
<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}/>; })}
@ -465,14 +502,15 @@ var PasswordWidget = React.createClass({
render: function(){
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
{
return(
<div>
<p>Enter password for key {this.state.data.key_name}</p>
<input type="text" ref="password" />
<input type="password" ref="password" />
<input
type="button"
value="ok"
@ -487,7 +525,7 @@ var PasswordWidget = React.createClass({
var AccountSelectWidget = React.createClass({
mixins: [AutoUpdateMixin],
getInitialState: function(){
return {data: []};
return {mode: "list", data: []};
},
getPath: function(){
return "control/locations";
@ -495,14 +533,20 @@ var AccountSelectWidget = React.createClass({
selectAccount: function(id){
console.log("login with id="+id)
RS.request({path: "control/login", data:{id: id}});
var state = this.state;
state.mode = "wait";
this.setState(state);
},
render: function(){
var component = this;
if(this.state.mode === "wait")
return <p>waiting...</p>;
else
return(
<div>
<div><p>select a location to log in</p></div>
{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>
);
@ -541,10 +585,50 @@ var LoginWidget = React.createClass({
},
});
var MainWidget = React.createClass({
var Menu = React.createClass({
mixins: [SignalSlotMixin],
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()
{
@ -553,20 +637,38 @@ var MainWidget = React.createClass({
function(params)
{
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(){
var outer = this;
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")
{
mainpage = <p>
mainpage = <div><p>
A new webinterface for Retroshare. Build with react.js.
React allows to build a modern and user friendly single page application.
The component system makes this very simple.
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")
{
@ -592,27 +694,39 @@ var MainWidget = React.createClass({
{
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 (
<div>
<PasswordWidget/>
<AudioPlayerWidget/>
{/*
<ul className="nav">
<li onClick={function(){outer.emit("url_changed", {url: "main"});}}>
<li onClick={function(){outer.emit("change_url", {url: "main"});}}>
Start
</li>
<li onClick={function(){outer.emit("url_changed", {url: "login"});}}>
<li onClick={function(){outer.emit("change_url", {url: "login"});}}>
Login
</li>
<li onClick={function(){outer.emit("url_changed", {url: "friends"});}}>
<li onClick={function(){outer.emit("change_url", {url: "friends"});}}>
Friends
</li>
<li onClick={function(){outer.emit("url_changed", {url: "downloads"});}}>
<li onClick={function(){outer.emit("change_url", {url: "downloads"});}}>
Downloads
</li>
<li onClick={function(){outer.emit("url_changed", {url: "search"});}}>
<li onClick={function(){outer.emit("change_url", {url: "search"});}}>
Search
</li>
</ul>
*/}
{menubutton}
{mainpage}
</div>
);

View File

@ -14,11 +14,13 @@
<!-- automatic page reload -->
<!--<script src="http://localhost:9091"></script>-->
<!-- 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">
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
</head>
<body>
<p>loading lots of stuff...</p>