commited webfiles. The Makefile in webui should probabl ydirectly store the generated files in this directory

git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@8192 b45a01b8-16f6-495d-af2f-9b41ad6348cc
This commit is contained in:
csoler 2015-04-30 18:32:08 +00:00
parent d6e708fdd5
commit 10f5a806e3
7 changed files with 36547 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,109 @@
/**
* 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

@ -0,0 +1,79 @@
/**
* 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/";
/**
* 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);
if(err_cb !== undefined)
err_cb();
return;
}
// received response
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

@ -0,0 +1,93 @@
body {
background-color: black;
color: lime;
font-family: monospace;
margin: 0em;
/*padding: 1.5em;*/
padding: 2mm;
font-size: 1.1em;
}
.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{
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;
-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;}
}

View File

@ -0,0 +1,776 @@
var connection = new RsXHRConnection(window.location.hostname, window.location.port);
var RS = new RsApi(connection);
RS.start();
var api_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/api/v2/";
var filestreamer_url = window.location.protocol + "//" +window.location.hostname + ":" + window.location.port + "/fstream/";
// livereload
function start_live_reload()
{
RS.request({path: "livereload"}, function(resp){
RS.register_token_listener(function(){
// Reload the current page, without using the cache
document.location.reload(true);
},resp.statetoken);
});
}
start_live_reload();
// implements automatic update using the state token system
// components using this mixin should have a member "getPath()" to specify the resource
var AutoUpdateMixin =
{
// react component lifecycle callbacks
componentDidMount: function()
{
this._aum_debug("AutoUpdateMixin did mount path="+this.getPath());
this._aum_on_data_changed();
},
componentWillUnmount: function()
{
this._aum_debug("AutoUpdateMixin will unmount path="+this.getPath());
RS.unregister_token_listener(this._aum_on_data_changed);
},
// private auto update mixin methods
_aum_debug: function(msg)
{
//console.log(msg);
},
_aum_on_data_changed: function()
{
RS.request({path: this.getPath()}, this._aum_response_callback);
},
_aum_response_callback: function(resp)
{
this._aum_debug("Mixin received data: "+JSON.stringify(resp));
// it is impossible to update the state of an unmounted component
// but it may happen that the component is unmounted before a request finishes
// if response is too late, we drop it
if(!this.isMounted())
{
this._aum_debug("AutoUpdateMixin: component not mounted. Discarding response. path="+this.getPath());
return;
}
var state = this.state;
state.data = resp.data;
this.setState(state);
RS.unregister_token_listener(this._aum_on_data_changed);
RS.register_token_listener(this._aum_on_data_changed, resp.statetoken);
},
};
// the signlaSlotServer decouples event senders from event receivers
// senders just send their events
// the server will forwards them to all receivers
// receivers have to register/unregister at the server
var signalSlotServer =
{
clients: [],
/**
* Register a client which wants to participate
* in the signal slot system. Clients must provide a function
* onSignal(signal_name, parameters)
* where signal_name is a string and parameters and array or object
*/
registerClient: function(client)
{
this.clients.push(client);
},
/**
* Unregister a previously registered client.
*/
unregisterClient : function(client)
{
var to_delete = [];
var clients = this.clients;
for(var i=0; i<clients.length; i++){
if(clients[i] === client){
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];
clients[index] = clients[clients.length-1];
// remove last element
clients.pop();
}
},
/**
* Emit the signale given by its name, with the optional parameters in params.
*/
emitSignal: function(signal_name, parameters)
{
var clients = this.clients;
for(var i=0; i<clients.length; i++){
clients[i].onSignal(signal_name, parameters);
}
},
}
var SignalSlotMixin =
{
conected_signals: [],
componentDidMount: function()
{
signalSlotServer.registerClient(this);
},
componentWillUnmount: function()
{
signalSlotServer.unregisterClient(this);
},
/**
* emit a signal
*/
emit: function(signal_name, parameters)
{
signalSlotServer.emitSignal(signal_name, parameters);
},
/**
* connect the callback to the signal
* the connection is automatically destroyed when this component gets unmounted
*/
connect: function(signal_name, callback)
{
this.conected_signals.push({name: signal_name, callback: callback});
},
/**
* callback for signal server
*/
onSignal: function(signal_name, parameters)
{
for(var i=0; i<this.conected_signals.length; i++){
if(this.conected_signals[i].name === signal_name){
this.conected_signals[i].callback(parameters);
}
}
},
};
// 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";},
getInitialState: function(){
return {data: []};
},
render: function(){
var renderOne = function(f){
console.log("make one");
return <p>{f.name} <img src={api_url+f.locations[0].avatar_address} /></p>;
};
return <div>{this.state.data.map(renderOne)}</div>;
},
});
var Peers2 = React.createClass({
mixins: [AutoUpdateMixin, SignalSlotMixin],
getPath: function(){return "peers";},
getInitialState: function(){
return {data: []};
},
add_friend_handler: function(){
this.emit("change_url", {url: "add_friend"});
},
render: function(){
var component = this;
var Peer = React.createClass({
remove_peer_handler: function(){
var yes = window.confirm("Remove "+this.props.data.name+" from friendslist?");
if(yes){
RS.request({path: component.getPath()+"/"+this.props.data.pgp_id+"/delete"});
}
},
render: function(){
var locations = this.props.data.locations.map(function(loc){
var online_style = {
width: "1em",
height: "1em",
borderRadius: "0.5em",
backgroundColor: "grey",
};
if(loc.is_online)
online_style.backgroundColor = "lime";
return(<li key={loc.peer_id}>{loc.location} <div style={online_style}></div></li>);
});
// TODO: fix the url, should get the "../api/v2" prefix from a single variable
var avatar_url = "";
if(this.props.data.locations.length > 0 && this.props.data.locations[0].avatar_address !== "")
avatar_url = api_url + component.getPath() + this.props.data.locations[0].avatar_address
var remove_button_style = {
color: "red",
fontSize: "1.5em",
padding: "0.2em",
cursor: "pointer",
};
var remove_button = <div onClick={this.remove_peer_handler} style={remove_button_style}>X</div>;
return(<tr><td><img src={avatar_url}/></td><td>{this.props.data.name}</td><td><ul>{locations}</ul></td><td>{remove_button}</td></tr>);
}
});
return (
<div>
{/* span reduces width to only the text length, div does not */}
<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}/>; })}
</table>
</div>);
},
});
var AddPeerWidget = React.createClass({
getInitialState: function(){
return {page: "start"};
},
add_friend_handler: function(){
var cert_string = this.refs.cert.getDOMNode().value;
if(cert_string != null){
// global replae all carriage return, because rs does not like them in certstrings
//cert_string = cert_string.replace(/\r/gm, "");
RS.request({path: "peers/examine_cert", data: {cert_string: cert_string}}, this.examine_cert_callback);
this.setState({page:"waiting", cert_string: cert_string});
}
},
examine_cert_callback: function(resp){
this.setState({page: "peer", data: resp.data});
},
final_add_handler: function(){
this.setState({page: "start"});
RS.request({path: "peers", data: {cert_string: this.state.cert_string}});
},
render: function(){
if(this.state.page === "start")
return(
<div>
<p>paste your friends key below</p>
<textarea ref="cert" cols="70" rows="16"></textarea><br/>
<input
type="button"
value="read key"
onClick={this.add_friend_handler}
/>
</div>
);
if(this.state.page === "waiting")
return(
<div>
waiting for response from server...
</div>
);
if(this.state.page === "peer")
return(
<div>
<p>Do you want to add {this.state.data.name} to your friendslist?</p>
<span onClick={this.final_add_handler} className="btn">yes</span>
</div>
);
},
});
var DownloadsWidget = React.createClass({
mixins: [AutoUpdateMixin, SignalSlotMixin],
getPath: function(){ return "transfers/downloads";},
getInitialState: function(){
return {data: []};
},
render: function(){
var widget = this;
var DL = React.createClass({
render: function()
{
var file = this.props.data;
var startFn = function(){
RS.request({
path: "transfers/control_download",
data: {
action: "start",
id: file.id,
}
}, function(){
console.log("start dl callback");
}
)};
var pauseFn = function(){
RS.request({
path: "transfers/control_download",
data: {
action: "pause",
id: file.id,
}
}, function(){
console.log("pause dl callback");
}
)};
var cancelFn = function(){
RS.request({
path: "transfers/control_download",
data: {
action: "cancel",
id: file.id,
}
}, function(){
console.log("cancel dl callback");
}
)};
var playFn = function(){
widget.emit("play_file", {name: file.name, hash: file.hash})
};
var playBtn = <div></div>;
if(file.name.slice(-3) === "mp3")
playBtn = <div className="btn" onClick={playFn}>play</div>;
var ctrlBtn = <div></div>;
if(file.download_status==="paused")
{
ctrlBtn = <div className="btn" onClick={startFn}>start</div>;
}
else
{
ctrlBtn = <div className="btn" onClick={pauseFn}>pause</div>;
}
return(<tr>
<td>{this.props.data.name}</td>
<td>{this.props.data.size}</td>
<td>{this.props.data.transfered / this.props.data.size}</td>
<td>{this.props.data.download_status}</td>
<td>{ctrlBtn} <div className="btn" onClick={cancelFn}>cancel</div> {playBtn}</td>
</tr>);
}
});
return (
<table>
<tr>
<th>name</th>
<th>size</th>
<th>completed</th>
<th>download status</th>
<th>actions</th>
</tr>
{this.state.data.map(function(dl){ return <DL key={dl.hash} data={dl}/>; })}
</table>);
},
});
var SearchWidget = React.createClass({
getInitialState: function(){
return {search_id: undefined};
},
handleSearch: function(){
console.log("searching for: "+this.refs.searchbox.getDOMNode().value);
var search_string = this.refs.searchbox.getDOMNode().value;
RS.request({path: "filesearch/create_search", data:{distant: true, search_string: search_string}}, this.onCreateSearchResponse);
},
onCreateSearchResponse: function(resp){
if(this.isMounted()){
this.setState({search_id: resp.data.search_id});
}
},
render: function(){
var ResultList = React.createClass({
mixins: [AutoUpdateMixin],
getPath: function(){ return "filesearch/"+this.props.id; },
getInitialState: function(){
return {data: []};
},
start_download: function(fileinfo){
RS.request({
path: "transfers/control_download",
data:{
action: "begin",
name: fileinfo.name,
size: fileinfo.size,
hash: fileinfo.hash,
},
});
},
render: function(){
var c2 = this;
var File = React.createClass({
render: function(){
var file = this.props.data;
return(
<tr>
<td>{file.name}</td>
<td>{file.size}</td>
<td><span onClick={function(){c2.start_download(file);}} className="btn">download</span></td>
</tr>);
},
});
return (
<table>
<tr><th>name</th><th>size</th><th></th></tr>
{this.state.data.map(
function(file){
return <File key={file.id} data={file}/>;
}
)}
</table>);
}
});
var results = <div></div>;
if(this.state.search_id !== undefined)
{
results = <ResultList id={this.state.search_id} />;
}
return(
<div>
<p>turtle file search</p>
<div>
<input type="text" ref="searchbox" />
<input
type="button"
value="search"
onClick={this.handleSearch}
/>
</div>
{results}
</div>);
},
});
var AudioPlayerWidget = React.createClass({
mixins: [SignalSlotMixin],
getInitialState: function(){
return {file: undefined};
},
componentWillMount: function(){
this.connect("play_file", this.play_file);
},
play_file: function(file){
this.setState({file: file});
},
render: function(){
if(this.state.file === undefined)
{
return(<div></div>);
}
else
{
return(
<div>
<p>{this.state.file.name}</p>
<audio controls src={filestreamer_url+this.state.file.hash} type="audio/mpeg">
</audio>
</div>
);
}
},
});
var PasswordWidget = React.createClass({
mixins: [AutoUpdateMixin],
getInitialState: function(){
return {data: {want_password: false}};
},
getPath: function(){
return "control/password";
},
sendPassword: function(){
RS.request({path: "control/password", data:{password: this.refs.password.getDOMNode().value}})
},
render: function(){
if(this.state.data.want_password === false)
{
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="password" ref="password" />
<input
type="button"
value="ok"
onClick={this.sendPassword}
/>
</div>
);
}
},
});
var AccountSelectWidget = React.createClass({
mixins: [AutoUpdateMixin],
getInitialState: function(){
return {mode: "list", data: []};
},
getPath: function(){
return "control/locations";
},
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 className="btn2" key={location.id} onClick ={function(){component.selectAccount(location.id);}}>{location.name} ({location.location})</div>;
})}
</div>
);
},
});
var LoginWidget = React.createClass({
mixins: [AutoUpdateMixin],
getInitialState: function(){
return {data: {runstate: "waiting_init"}};
},
getPath: function(){
return "control/runstate";
},
shutdown: function(){
RS.request({path: "control/shutdown"});
},
render: function(){
if(this.state.data.runstate === "waiting_init")
{
return(<p>Retroshare is initialising... please wait...</p>);
}
else if(this.state.data.runstate === "waiting_account_select")
{
return(<AccountSelectWidget/>);
}
else
{
return(
<div>
<p>runstate: {this.state.data.runstate}</p>
<div onClick={this.shutdown} className="btn">shutdown Retroshare</div>
</div>
);
}
},
});
var Menu = React.createClass({
mixins: [SignalSlotMixin],
getInitialState: function(){
return {};
},
componentWillMount: function()
{
},
render: function(){
var outer = this;
return (
<div>
<div className="btn2" onClick={function(){outer.emit("change_url", {url: "main"});}}>
Start
</div>
{function(){
if(outer.props.fullcontrol)
return (<div className="btn2" onClick={function(){outer.emit("change_url", {url: "login"});}}>
Login
</div>);
else return <div></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 className="btn2" onClick={function(){outer.emit("change_url", {url: "testwidget"});}}>
TestWidget
</div>*/}
</div>
);
},
});
var TestWidget = React.createClass({
mixins: [SignalSlotMixin],
getInitialState: function(){
return {s:"one"};
},
componentWillMount: function()
{
},
one: function(){
this.setState({s:"one"});
},
two: function(){
this.setState({s:"two"});
},
render: function(){
var outer = this;
var outercontainerstyle = {borderStyle: "solid", borderColor: "darksalmon", overflow: "hidden", width: "100%"};
var transx = "0px";
if(this.state.s === "two")
transx = "-45%";
var innercontainerstyle = {width: "200%", transform: "translatex("+transx+")", WebkitTransform: "translatex("+transx+")", transition: "all 0.5s ease-in-out", WebkitTransition: "all 0.5s ease-in-out"};
var innerstyle = {float:"left", width: "45%"};
var two = <div></div>;
if(this.state.s === "two")
two = <div style={innerstyle} className="btn2" onClick={function(){outer.one();}}>
two
</div>;
return (
<div style={outercontainerstyle}>
<div style={innercontainerstyle}>
<div style={innerstyle} className="btn2" onClick={function(){outer.two();}}>
one
</div>
{two}
</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()
{
var outer = this;
this.connect("url_changed",
function(params)
{
console.log("MainWidget received url_changed. url="+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" || this.state.data.runstate ==="running_ok_no_full_control")
{
if(this.state.page === "main")
{
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>
</div>;
}
if(this.state.page === "friends")
{
mainpage = <Peers2 />;
}
if(this.state.page === "downloads")
{
mainpage = <DownloadsWidget/>;
}
if(this.state.page === "search")
{
mainpage = <SearchWidget/>;
}
if(this.state.page === "add_friend")
{
mainpage = <AddPeerWidget/>;
}
if(this.state.page === "login")
{
mainpage = <LoginWidget/>;
}
if(this.state.page === "menu")
{
mainpage = <Menu fullcontrol = {this.state.data.runstate === "running_ok"}/>;
}
if(this.state.page === "testwidget")
{
mainpage = <TestWidget/>;
}
}
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/>
{menubutton}
{mainpage}
</div>
);
},
});
React.render(
<MainWidget />,
document.body
);

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>New webinterface for Retroshare</title>
<script src="react.js"></script>
<script src="JSXTransformer.js"></script>
<script src="RsXHRConnection.js"></script>
<script src="RsApi.js"></script>
<script type="text/jsx" src="gui.jsx"></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>
<div id="logo_splash">
<img src="img/logo_splash.png"></img>
</div>
</body>
</html>

19541
libresapi/src/webfiles/react.js vendored Normal file

File diff suppressed because it is too large Load Diff