webui: switch from react to mithril

This commit is contained in:
zeners 2016-03-24 18:07:21 +01:00
parent cef07fff91
commit 30193fe79a
26 changed files with 49 additions and 40216 deletions

1
libresapi/src/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
webui/*

View File

@ -2,14 +2,13 @@ libresapi: resource_api and new webinterface
============================================
* ./api contains a C++ backend to control retroshare from webinterfaces or scripting
* ./webfiles contains compiled files for the webinterface
* ./webui contains HTML/CSS/JavaScript source files for the webinterface (OLD)
* ./webui contains contains compiled files for the webinterface (after build)
* ./webui-src contains HTML/CSS/JavaScript source files for the webinterface (NEW, webinterface made with mithril.js)
Quickinfo for builders and packagers
====================================
* copy the files in ./webfiles to
* copy the files in ./webui to
* ./webui (Windows)
* /usr/share/RetroShare06/webui (Linux)
* other OS: see RsAccountsDetail::PathDataDirectory()
* other OS: see RsAccountsDetail::PathDataDirectory()

View File

@ -15,21 +15,19 @@ INCLUDEPATH += ../../libretroshare/src
unix {
webui_files.path = "$${DATA_DIR}/webui"
webui_files.files = webui-src/public-gen/app.js
webui_files.files += webui-src/public-gen/app.css
webui_files.files += webui-src/public-gen/index.html
webui_files.files = webfiles/*
INSTALLS += webui_files
webui_img_files.path = "$${DATA_DIR}/webui/img"
webui_img_files.files = ../../retroshare-gui/src/gui/images/logo/logo_splash.png
INSTALLS += webui_img_files
create_webfiles.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_/webui-src
create_webfiles.commands = sh $$_PRO_FILE_PWD_/webui-src/make-src/build.sh $$_PRO_FILE_PWD_
QMAKE_EXTRA_TARGETS += create_webfiles
PRE_TARGETDEPS += create_webfiles
system($$create_webfiles.commands)
# create dummy files
system(webui-src/make-src/init.sh .)
}
win32{

File diff suppressed because one or more lines are too long

View File

@ -1,109 +0,0 @@
/**
* JS Api for Retroshare
* @constructor
* @param {object} connection - an object which implements a request() function.
* The request function should take two parameters: an object to be send as request and a callback.
* The callback should get called with an response object on success.
*/
function RsApi(connection)
{
var runnign = true;
/**
* Send a request to the server
* @param req - the request so send
* @param {Function} cb - callback function which takes the response as parameter
*/
this.request = function(req, cb)
{
connection.request(req, cb);
};
var tokenlisteners = [];
/**
* Register a callback to be called when the state token expired.
* @param {Function} listener - the callback function, which does not take arguments
* @param token - the state token to listen for
*/
this.register_token_listener = function(listener, token)
{
tokenlisteners.push({listener:listener, token:token});
};
/**
* Unregister a previously registered callback.
*/
this.unregister_token_listener = function(listener) // no token as parameter, assuming unregister from all listening tokens
{
var to_delete = [];
for(var i=0; i<tokenlisteners.length; i++){
if(tokenlisteners[i].listener === listener){
to_delete.push(i);
}
}
for(var i=0; i<to_delete.length; i++){
// copy the last element to the current index
var index = to_delete[i];
tokenlisteners[index] = tokenlisteners[tokenlisteners.length-1];
// remove last element
tokenlisteners.pop();
}
};
/**
* start polling for state changes
*/
this.start = function(){
running = true;
setTimeout(tick, TICK_INTERVAL);
}
/**
* stop polling for state changes
*/
this.stop = function(){
running = false;
}
// ************** interal stuff **************
var TICK_INTERVAL = 3000;
function received_tokenstates(resp)
{
if(resp.data){
for(var i=0; i<resp.data.length; i++){
var token = resp.data[i];
// search the listener for this token
for(var j=0; j<tokenlisteners.length; j++){
if(tokenlisteners[j].token === token){
// call the listener
tokenlisteners[j].listener();
}
}
}
}
// schedule new update
if(running)
setTimeout(tick, TICK_INTERVAL);
};
function received_error()
{
// try again, maybe want a better logic later
if(running)
setTimeout(tick, TICK_INTERVAL);
};
function tick()
{
var data = [];
// maybe cache the token list?
// profiler will tell us if we should
for(var i=0; i<tokenlisteners.length; i++){
data.push(tokenlisteners[i].token);
}
connection.request({
path: "statetokenservice",
data: data,
}, received_tokenstates, received_error);
};
};
// with this trick, we should be able to run in browser or nodejs
if(typeof window === 'undefined')
{
// we are running in nodejs, so have to add to export
module.exports = RsApi;
}

View File

@ -1,123 +0,0 @@
/**
* Connection to the RS backend using XHR
* (could add other connections later, for example WebSockets)
* @constructor
*/
function RsXHRConnection(server_hostname, server_port)
{
var debug;
//debug = function(str){console.log(str);};
debug = function(str){};
//server_hostname = "localhost";
//server_port = "9090";
var api_root_path = "/api/v2/";
var status_listeners = [];
function notify_status(status)
{
for(var i = 0; i < status_listeners.length; i++)
{
status_listeners[i](status);
}
}
/**
* Register a callback to be called when the state of the connection changes.
* @param {function} cb - callback which receives a single argument. The arguments value is "connected" or "not_connected".
*/
this.register_status_listener = function(cb)
{
status_listeners.push(cb);
};
/**
* Unregister a status callback function.
* @param {function} cb - a privously registered callback function
*/
this.unregister_status_listener = function(cb)
{
var to_delete = [];
for(var i = 0; i < status_listeners.length; i++)
{
if(status_listeners[i] === cb){
to_delete.push(i);
}
}
for(var i = 0; i < to_delete.length; i++)
{
// copy the last element to the current index
var index = to_delete[i];
status_listeners[i] = status_listeners[status_listeners.length-1];
// remove the last element
status_listeners.pop();
}
};
/**
* Send a request to the backend
* automatically encodes the request as JSON before sending it to the server
* @param {object} req - the request to send to the server
* @param {function} cb - callback function to be called to handle the response. The callback takes one object as parameter. Can be left undefined.
* @param {function} err_cb - callback function to signal a failed request. Can be undefined.
*/
this.request = function(req, cb, err_cb)
{
//var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
// TODO: window is not available in QML
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
//console.log("onreadystatechanged state"+xhr.readyState);
// TODO: figure out how to catch errors like connection refused
// maybe want to have to set a state variable like ok=false
// the gui could then display: "no connection to server"
if (xhr.readyState === 4) {
if(xhr.status !== 200)
{
console.log("RsXHRConnection: request failed with status: "+xhr.status);
console.log("request was:");
console.log(req);
notify_status("not_connected");
if(err_cb !== undefined)
err_cb();
return;
}
// received response
notify_status("connected");
debug("RsXHRConnection received response:");
debug(xhr.responseText);
if(false)//if(xhr.responseText === "")
{
debug("Warning: response is empty");
return;
}
try
{
var respObj = JSON.parse(xhr.responseText);
}
catch(e)
{
debug("Exception during response handling: "+e);
}
if(cb === undefined)
debug("No callback function specified");
else
cb(respObj);
}
}
// post is required for sending data
var method;
if(req.data){
method = "POST";
} else {
method = "GET";
}
xhr.open(method, "http://"+server_hostname+":"+server_port+api_root_path+req.path);
var data = JSON.stringify(req.data);
debug("RsXHRConnection sending data:");
debug(data);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(data);
};
};

View File

@ -1,134 +0,0 @@
body {
background-color: black;
color: lime;
font-family: monospace;
margin: 0em;
/*padding: 1.5em;*/
padding: 2mm;
font-size: 1.1em;
}
#overlay{
z-index: 10;
position: fixed;
top:0;
left:0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.8);
}
.paddingbox{
padding:2mm;
}
.nav{
list-style-type: none;
padding: 0em;
margin: 0em;
}
.nav li{
display: inline;
padding: 0.1em;
margin-right: 1em;
border-width: 0.1em;
border-color: blue;
border-bottom-style: solid;
cursor: pointer;
}
td{
padding: 0.3em;
border-style: solid;
border-width: 0.1em;
border-color: lime;
}
.btn{
border-style: solid;
border-color: lime;
border-width: 0.1em;
cursor: pointer;
padding: 0.1em;
}
.btn2, .box{
border-style: solid;
/*border-color: lime;*/
border-color: limeGreen;
/*border-width: 1px;*/
border-radius: 3mm;
padding: 2mm;
font-size: 10mm;
cursor: pointer;
margin-bottom: 2mm;
}
.btn2:hover{
background-color: midnightblue;
}
.filelink{
color: inherit;
}
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;
}
.checkbox {
width: auto;
}
.flexbox{
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
display: -ms-flexbox; /* TWEENER - IE 10 */
display: -webkit-flex; /* NEW - Chrome */
display: flex; /* NEW, Spec - Opera 12.1, Firefox 20+ */
}
.flexwidemember{
-webkit-box-flex: 1; /* OLD - iOS 6-, Safari 3.1-6 */
-moz-box-flex: 1; /* OLD - Firefox 19- */
width: 20%; /* For old syntax, otherwise collapses. */
-webkit-flex: 1; /* Chrome */
-ms-flex: 1; /* IE 10 */
flex: 1; /* NEW, Spec - Opera 12.1, Firefox 20+ */
}
#logo_splash{
-webkit-animation-fill-mode: forwards; /* Chrome, Safari, Opera */
animation-fill-mode: forwards;
-webkit-animation-name: logo_splash; /* Chrome, Safari, Opera */
-webkit-animation-duration: 3s; /* Chrome, Safari, Opera */
animation-name: logo_splash;
animation-duration: 3s;
text-align: center;
}
/* Chrome, Safari, Opera */
@-webkit-keyframes logo_splash {
from {opacity: 0;}
to {opacity: 1;}
}
/* Standard syntax */
@keyframes logo_splash {
from {opacity: 0;}
to {opacity: 1;}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>New webinterface for Retroshare</title>
<script src="RsXHRConnection.js"></script>
<script src="RsApi.js"></script>
<!-- it seems to work more reliable, if the jsx file is loaded before react -->
<script type="text/jsx" src="gui.jsx"></script>
<script src="react.js"></script>
<script src="JSXTransformer.js"></script>
<link href="green-black.css" rel="stylesheet">
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
</head>
<body>
<script>
document.write("<p>loading lots of stuff...</p>");
</script>
<p><noscript>The Retroshare web interface requires JavaScript. Please enable JavaScript in your browser.</noscript></p>
<!--<div id="logo_splash">
<img src="img/logo_splash.png"></img>
</div>-->
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,2 @@
node_modules/*
public/*
public-gen/*

View File

@ -56,4 +56,4 @@ need 4 master
[X] unsubscribe lobby
[X] unread chat message counter in menu
[X] list chat-lobby participants
[ ] creating app.js on build (no need for npm on regulary build)
[X] creating app.js on build (no need for npm on regulary build)

View File

@ -1,28 +1,31 @@
#!/usr/bin/sh
echo $1
# create webfiles from sources at compile time (works without npm/node.js)
if [ "$1" == "" ];then
publicdest=../public-gen
publicdest=../../webui
src=..
else
publicdest=$1/public-gen
src=$1
publicdest=$1/webui
src=$1/webui-src
fi
echo $publicdest
if [ -d "$publicdest" ]; then
echo remove $publicdest
echo remove existing $publicdest
rm $publicdest -R
fi
echo mkdir $publicdest
mkdir $publicdest
echo copy template.js ...
echo building app.js
echo - copy template.js ...
cp $src/make-src/template.js $publicdest/app.js
for filename in $src/app/*.js; do
fname=$(basename "$filename")
fname="${fname%.*}"
echo adding $fname ...
echo - adding $fname ...
echo require.register\(\"$fname\", function\(exports, require, module\) { >> $publicdest/app.js
cat $filename >> $publicdest/app.js
echo >> $publicdest/app.js

View File

@ -0,0 +1,22 @@
#!/usr/bin/sh
# create dummy webfiles at qmake run
if [ "$1" == "" ];then
publicdest=../../webui
else
publicdest=$1/webui
fi
if [ -d "$publicdest" ]; then
echo remove $publicdest
rm $publicdest -R
fi
echo create $publicdest
mkdir $publicdest
echo touch $publicdest/app.js, $publicdest/app.css, $publicdest/index.html
touch $publicdest/app.js
touch $publicdest/app.css
touch $publicdest/index.html

View File

@ -0,0 +1,7 @@
this folder contains files needed to create webfiles at compile-time and qmake
* init.sh creates dummy files at qmake
* build.sh creates files at compile time
* chat.css compiled version of _chat.sass (.sass should replaced by .scss)
* main.css simple template extracted from main.sass
* template.js start of compiled app.js, containing additional created content

View File

@ -1,36 +0,0 @@
REACT_VERSION = 0.13.1
DISTDIR = ../webfiles
JSEXTLIBS = $(DISTDIR)/react.js $(DISTDIR)/JSXTransformer.js
JSLIBS = RsXHRConnection.js RsApi.js
HTML = index.html
JSGUI = gui.jsx
CSS = green-black.css
all: $(DISTDIR) $(JSEXTLIBS) $(addprefix $(DISTDIR)/, $(JSLIBS)) $(addprefix $(DISTDIR)/, $(HTML)) $(addprefix $(DISTDIR)/, $(JSGUI)) $(addprefix $(DISTDIR)/, $(CSS))
.PHONY: all
$(DISTDIR)/livereload: $(DISTDIR) $(JSEXTLIBS) $(addprefix $(DISTDIR)/, $(JSLIBS)) $(addprefix $(DISTDIR)/, $(HTML)) $(addprefix $(DISTDIR)/, $(JSGUI)) $(addprefix $(DISTDIR)/, $(CSS))
wget -qO- http://localhost:9090/api/v2/livereload/trigger
touch $(DISTDIR)/livereload
$(DISTDIR)/react.js:
cd $(DISTDIR) && wget --no-check-certificate --output-document react.js http://fb.me/react-$(REACT_VERSION).js
$(DISTDIR)/JSXTransformer.js:
cd $(DISTDIR) && wget --no-check-certificate --output-document JSXTransformer.js http://fb.me/JSXTransformer-$(REACT_VERSION).js
$(addprefix $(DISTDIR)/, $(JSLIBS)): $(DISTDIR)/%: %
cp $< $@
$(addprefix $(DISTDIR)/, $(HTML)): $(DISTDIR)/%: %
cp $< $@
$(addprefix $(DISTDIR)/, $(JSGUI)): $(DISTDIR)/%: %
cp $< $@
$(addprefix $(DISTDIR)/, $(CSS)): $(DISTDIR)/%: %
cp $< $@
$(DISTDIR):
mkdir $(DISTDIR)

View File

@ -1,142 +0,0 @@
var TypesMod = require("./Types.js");
var Type = TypesMod.Type;
var string = TypesMod.string;
var bool = TypesMod.bool;
var any = TypesMod.any;
if(require.main === module)
{
var RsNodeHttpConnection = require("./RsNodeHttpConnection.js");
debugger;
var connection = new RsNodeHttpConnection();
var RsApi = require("./RsApi.js");
var RS = new RsApi(connection);
var 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);
});
}
function PeersTest(tests, doc)
{
// compound types
var location = new Type("location",
{
avatar_address: string,
groups: any,
is_online: bool,
location: string,
peer_id: any,
});
var peer_info = new Type("peer_info",
{
name: string,
pgp_id: any,
locations: [location],
});
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:")
console.log(graphToText(peers_list));
RS.request({path: "peers"}, function(resp){
//console.log("got response:"+JSON.stringify(resp));
var ok = peers_list.check(function(str){console.log(str);}, resp.data, [])
if(ok)
console.log("success");
else
console.log("fail");
});
});
function graphToText(top_node)
{
//var dbg = function(str){console.log(str);};
var dbg = function(str){};
dbg("called graphToText with " + top_node);
var res = "";
_visit(top_node.getObj(), 0);
return res;
function _indent(count)
{
var str = "";
for(var i = 0; i < count; i++)
{
str = str + " ";
}
return str;
}
function _visit(node, indent)
{
dbg("_visit");
if(node instanceof Array)
{
dbg("is instanceof Array");
//res = res + "[";
res = res + "array\n";
_visit(node[0], indent);
//res = res + _indent(indent) + "]\n";
}
else if(node instanceof Type && node.isLeaf())
{
dbg("is instanceof Type");
res = res + node.getName() + "\n";
}
else // Object, have to check all children
{
dbg("is Object");
//res = res + "{\n";
for(m in node.getObj())
{
res = res + _indent(indent+1) + m + ": ";
_visit(node.getObj()[m], indent+1);
}
//res = res + _indent(indent) + "}\n";
}
}
}
}

View File

@ -1,48 +0,0 @@
A new approach to build a webinterface for RS
=============================================
1. get JSON encoded data from the backend, data contains a state token
2. render data with react.js
3. ask the backend if the state token from step 1 expired. If yes, then start again with step 1.
Steps 1. and 3. are common for most things, only Step 2. differs. This allows to re-use code for steps 1. and 3.
BUILD / INSTALLATION
------------
- run (requires wget, use MinGW shell on Windows)
make
- all output files are now in libresapi/src/webfiles
- use the --webinterface 9090 command line parameter to enable webui in retroshare-nogui
- set the --docroot parameter of retroshare-nogui to point to the "libresapi/src/webfiles" directory
(or symlink from /usr/share/RetroShare06/webui on Linux, ./webui on Windows)
- retroshare-gui does not have a --docroot parameter. Use symlinks then.
DEVELOPMENT
-----------
- Ubuntu: install nodejs package
sudo apt-get install nodejs
- Windows: download and install nodejs from http://nodejs.org
- Download development tools with the nodejs package manager (short npm)
npm install
- run Retroshare with webinterface on port 9090
- during development, run this command (use MinGW shell on Windows)
while true; do make ../webfiles/livereload --silent; sleep 1; done
- the command will copy the source files to libresapi/src/webfiles if they change
- it will trigger livereload at http://localhost:9090/api/v2/livereload/trigger
API DOCUMENTATION
-----------------
- run
node PeersTest.js
- this will print the expected schema of the api output, and it will try to test it with real data
- run retroshare-nogui with webinterface enabled on port 9090, to test if the real output of the api matches the expected schema
CONTRIBUTE
----------
- if you are a web developer or want to become one
get in contact!
- lots of work to do, i need you!

View File

@ -1,109 +0,0 @@
/**
* JS Api for Retroshare
* @constructor
* @param {object} connection - an object which implements a request() function.
* The request function should take two parameters: an object to be send as request and a callback.
* The callback should get called with an response object on success.
*/
function RsApi(connection)
{
var runnign = true;
/**
* Send a request to the server
* @param req - the request so send
* @param {Function} cb - callback function which takes the response as parameter
*/
this.request = function(req, cb)
{
connection.request(req, cb);
};
var tokenlisteners = [];
/**
* Register a callback to be called when the state token expired.
* @param {Function} listener - the callback function, which does not take arguments
* @param token - the state token to listen for
*/
this.register_token_listener = function(listener, token)
{
tokenlisteners.push({listener:listener, token:token});
};
/**
* Unregister a previously registered callback.
*/
this.unregister_token_listener = function(listener) // no token as parameter, assuming unregister from all listening tokens
{
var to_delete = [];
for(var i=0; i<tokenlisteners.length; i++){
if(tokenlisteners[i].listener === listener){
to_delete.push(i);
}
}
for(var i=0; i<to_delete.length; i++){
// copy the last element to the current index
var index = to_delete[i];
tokenlisteners[index] = tokenlisteners[tokenlisteners.length-1];
// remove last element
tokenlisteners.pop();
}
};
/**
* start polling for state changes
*/
this.start = function(){
running = true;
setTimeout(tick, TICK_INTERVAL);
}
/**
* stop polling for state changes
*/
this.stop = function(){
running = false;
}
// ************** interal stuff **************
var TICK_INTERVAL = 3000;
function received_tokenstates(resp)
{
if(resp.data){
for(var i=0; i<resp.data.length; i++){
var token = resp.data[i];
// search the listener for this token
for(var j=0; j<tokenlisteners.length; j++){
if(tokenlisteners[j].token === token){
// call the listener
tokenlisteners[j].listener();
}
}
}
}
// schedule new update
if(running)
setTimeout(tick, TICK_INTERVAL);
};
function received_error()
{
// try again, maybe want a better logic later
if(running)
setTimeout(tick, TICK_INTERVAL);
};
function tick()
{
var data = [];
// maybe cache the token list?
// profiler will tell us if we should
for(var i=0; i<tokenlisteners.length; i++){
data.push(tokenlisteners[i].token);
}
connection.request({
path: "statetokenservice",
data: data,
}, received_tokenstates, received_error);
};
};
// with this trick, we should be able to run in browser or nodejs
if(typeof window === 'undefined')
{
// we are running in nodejs, so have to add to export
module.exports = RsApi;
}

View File

@ -1,51 +0,0 @@
var http = require('http');
/**
* Connection to the RS backend using http for running under node.js
* Mainly for testing, but could also use it for general purpose scripting.
* @constructor
*/
module.exports = function()
{
var server_hostname = "localhost";
var server_port = "9090";
var api_root_path = "/api/v2/";
this.request = function(request, callback)
{
var data;
if(request.data)
data = JSON.stringify(request.data);
else
data = "";
// NODEJS specific
var req = http.request({
host: server_hostname,
port: server_port,
path: api_root_path + request.path,
headers: {
"Content-Type": "application/json",
"Content-Length": data.length, // content length is required, else Wt will not provide the data (maybe WT does not like chunked encoding?)
}
//method: "POST",
}, function(response){
var databuffer = [];
response.on("data", function(chunk){
//console.log("got some data");
databuffer = databuffer + chunk;
})
response.on("end", function(){
//console.log("finished receiving data");
//console.log("data:"+databuffer);
callback(JSON.parse(databuffer));
})
});
//console.log("uploading data:");
//console.log(data);
req.write(data);
req.end();
}
}

View File

@ -1,123 +0,0 @@
/**
* Connection to the RS backend using XHR
* (could add other connections later, for example WebSockets)
* @constructor
*/
function RsXHRConnection(server_hostname, server_port)
{
var debug;
//debug = function(str){console.log(str);};
debug = function(str){};
//server_hostname = "localhost";
//server_port = "9090";
var api_root_path = "/api/v2/";
var status_listeners = [];
function notify_status(status)
{
for(var i = 0; i < status_listeners.length; i++)
{
status_listeners[i](status);
}
}
/**
* Register a callback to be called when the state of the connection changes.
* @param {function} cb - callback which receives a single argument. The arguments value is "connected" or "not_connected".
*/
this.register_status_listener = function(cb)
{
status_listeners.push(cb);
};
/**
* Unregister a status callback function.
* @param {function} cb - a privously registered callback function
*/
this.unregister_status_listener = function(cb)
{
var to_delete = [];
for(var i = 0; i < status_listeners.length; i++)
{
if(status_listeners[i] === cb){
to_delete.push(i);
}
}
for(var i = 0; i < to_delete.length; i++)
{
// copy the last element to the current index
var index = to_delete[i];
status_listeners[i] = status_listeners[status_listeners.length-1];
// remove the last element
status_listeners.pop();
}
};
/**
* Send a request to the backend
* automatically encodes the request as JSON before sending it to the server
* @param {object} req - the request to send to the server
* @param {function} cb - callback function to be called to handle the response. The callback takes one object as parameter. Can be left undefined.
* @param {function} err_cb - callback function to signal a failed request. Can be undefined.
*/
this.request = function(req, cb, err_cb)
{
//var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
// TODO: window is not available in QML
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
//console.log("onreadystatechanged state"+xhr.readyState);
// TODO: figure out how to catch errors like connection refused
// maybe want to have to set a state variable like ok=false
// the gui could then display: "no connection to server"
if (xhr.readyState === 4) {
if(xhr.status !== 200)
{
console.log("RsXHRConnection: request failed with status: "+xhr.status);
console.log("request was:");
console.log(req);
notify_status("not_connected");
if(err_cb !== undefined)
err_cb();
return;
}
// received response
notify_status("connected");
debug("RsXHRConnection received response:");
debug(xhr.responseText);
if(false)//if(xhr.responseText === "")
{
debug("Warning: response is empty");
return;
}
try
{
var respObj = JSON.parse(xhr.responseText);
}
catch(e)
{
debug("Exception during response handling: "+e);
}
if(cb === undefined)
debug("No callback function specified");
else
cb(respObj);
}
}
// post is required for sending data
var method;
if(req.data){
method = "POST";
} else {
method = "GET";
}
xhr.open(method, "http://"+server_hostname+":"+server_port+api_root_path+req.path);
var data = JSON.stringify(req.data);
debug("RsXHRConnection sending data:");
debug(data);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(data);
};
};

View File

@ -1,146 +0,0 @@
/**
* Construct a new type from an array, object or function.
* Use instances of this class to build a schema graph.
* The schema graph must not contain data other than arrays and instances of this class.
* Use the check function to check if arbitrary JS objects matches the schema.
* @constructor
* @param {String} name - name for the new type
* @param obj - array, object or function
* array: array should contain one instance of class "Type"
* object: object members can be arrays or instances of class "Type".
* Can also have child objects, but the leaf members have to be instances of class "Type"
* function: a function which takes three parameters: log, other, stack
* must return true if other matches the type, can report errors using the function passed in log.
*/
function Type(name, obj)
{
//var dbg = function(str){console.log(str);};
var dbg = function(str){};
this.getName = function(){
return name;
}
this.isLeaf = function(){
return typeof(obj) === "function";
}
this.getObj = function(){
return obj;
}
this.check = function(log, other, stack)
{
if(typeof(obj) === "object")
{
stack.push("<"+name+">");
var ok = _check(log, obj, other, stack);
stack.pop;
return ok;
}
if(typeof(obj) === "function")
return obj(log, other, stack);
log("FATAL Error: wrong usage of new Type(), second parameter should be an object or checker function");
return false;
}
function _check(log, ref, other, stack)
{
dbg("_check");
dbg("ref=" + ref);
dbg("other=" + other);
dbg("stack=[" + stack + "]");
if(ref instanceof Array)
{
dbg("is instanceof Array");
if(other instanceof Array)
{
if(other.length > 0)
{
return _check(log, ref[0], other[0], stack);
}
else
{
log("Warning: can't check array of length 0 in ["+stack+"]");
return true;
}
}
else
{
log("Error: not an Array ["+stack+"]");
return false;
}
}
if(ref instanceof Type)
{
dbg("is instanceof Type");
return ref.check(log, other, stack);
}
else // Object, have to check all children
{
dbg("is Object");
var ok = true;
for(m in ref)
{
if(m in other)
{
stack.push(m);
ok = ok && _check(log, ref[m], other[m], stack);
stack.pop();
}
else
{
log("Error: missing member \""+m+"\" in ["+stack+"]");
ok = false;
}
}
// check for additionally undocumented members
for(m in other)
{
if(!(m in ref))
{
log("Warning: found additional member \""+m+"\" in ["+stack+"]");
}
}
return ok;
}
}
};
// basic data types
// - string
// - bool
// - any (placeholder for unknown type)
var string = new Type("string",
function(log, other, stack)
{
if(typeof(other) !== "string")
{
log("Error: not a string ["+stack+"]");
return false;
}
else
return true;
}
);
var bool = new Type("bool",
function(log, other, stack)
{
if(typeof(other) !== "boolean")
{
log("Error: not a bool ["+stack+"]");
return false;
}
else
return true;
}
);
var any = new Type("any",
function(log, other, stack)
{
return true;
}
);
exports.Type = Type;
exports.string = string;
exports.bool = bool;
exports.any = any;

View File

@ -1,134 +0,0 @@
body {
background-color: black;
color: lime;
font-family: monospace;
margin: 0em;
/*padding: 1.5em;*/
padding: 2mm;
font-size: 1.1em;
}
#overlay{
z-index: 10;
position: fixed;
top:0;
left:0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.8);
}
.paddingbox{
padding:2mm;
}
.nav{
list-style-type: none;
padding: 0em;
margin: 0em;
}
.nav li{
display: inline;
padding: 0.1em;
margin-right: 1em;
border-width: 0.1em;
border-color: blue;
border-bottom-style: solid;
cursor: pointer;
}
td{
padding: 0.3em;
border-style: solid;
border-width: 0.1em;
border-color: lime;
}
.btn{
border-style: solid;
border-color: lime;
border-width: 0.1em;
cursor: pointer;
padding: 0.1em;
}
.btn2, .box{
border-style: solid;
/*border-color: lime;*/
border-color: limeGreen;
/*border-width: 1px;*/
border-radius: 3mm;
padding: 2mm;
font-size: 10mm;
cursor: pointer;
margin-bottom: 2mm;
}
.btn2:hover{
background-color: midnightblue;
}
.filelink{
color: inherit;
}
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;
}
.checkbox {
width: auto;
}
.flexbox{
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
display: -ms-flexbox; /* TWEENER - IE 10 */
display: -webkit-flex; /* NEW - Chrome */
display: flex; /* NEW, Spec - Opera 12.1, Firefox 20+ */
}
.flexwidemember{
-webkit-box-flex: 1; /* OLD - iOS 6-, Safari 3.1-6 */
-moz-box-flex: 1; /* OLD - Firefox 19- */
width: 20%; /* For old syntax, otherwise collapses. */
-webkit-flex: 1; /* Chrome */
-ms-flex: 1; /* IE 10 */
flex: 1; /* NEW, Spec - Opera 12.1, Firefox 20+ */
}
#logo_splash{
-webkit-animation-fill-mode: forwards; /* Chrome, Safari, Opera */
animation-fill-mode: forwards;
-webkit-animation-name: logo_splash; /* Chrome, Safari, Opera */
-webkit-animation-duration: 3s; /* Chrome, Safari, Opera */
animation-name: logo_splash;
animation-duration: 3s;
text-align: center;
}
/* Chrome, Safari, Opera */
@-webkit-keyframes logo_splash {
from {opacity: 0;}
to {opacity: 1;}
}
/* Standard syntax */
@keyframes logo_splash {
from {opacity: 0;}
to {opacity: 1;}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>New webinterface for Retroshare</title>
<script src="RsXHRConnection.js"></script>
<script src="RsApi.js"></script>
<!-- it seems to work more reliable, if the jsx file is loaded before react -->
<script type="text/jsx" src="gui.jsx"></script>
<script src="react.js"></script>
<script src="JSXTransformer.js"></script>
<link href="green-black.css" rel="stylesheet">
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
</head>
<body>
<script>
document.write("<p>loading lots of stuff...</p>");
</script>
<p><noscript>The Retroshare web interface requires JavaScript. Please enable JavaScript in your browser.</noscript></p>
<!--<div id="logo_splash">
<img src="img/logo_splash.png"></img>
</div>-->
</body>
</html>

View File

@ -1,10 +0,0 @@
{
"name": "rswebui",
"version": "0.0.0",
"dependencies": {
},
"devDependencies": {
},
"scripts": {
}
}