- redesigned friends list
- added friendly units on downloads page
- added progress bar on downloads page
- fixed makefile directories and updated readme

git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@8202 b45a01b8-16f6-495d-af2f-9b41ad6348cc
This commit is contained in:
electron128 2015-05-01 15:24:05 +00:00
parent b999ccc34b
commit 7c1cab07e8
7 changed files with 281 additions and 55 deletions

View File

@ -2,4 +2,13 @@ libresapi: resource_api and new webinterface
============================================ ============================================
* ./api contains a C++ backend to control retroshare from webinterfaces or scripting * ./api contains a C++ backend to control retroshare from webinterfaces or scripting
* ./webui contains HTML/CSS/JavaScript files for the webinterface * ./webfiles contains compiled files for the webinterface
* ./webui contains HTML/CSS/JavaScript source files for the webinterface
Quickinfo for builders and packagers
====================================
* copy the files in ./webfiles to
* ./webui (Windows)
* /usr/share/RetroShare0.6/webui (Linux)
* other OS: see RsAccountsDetail::PathDataDirectory()

View File

@ -71,6 +71,23 @@ input:hover{
background-color: midnightblue; background-color: midnightblue;
} }
.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{ #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

@ -188,21 +188,6 @@ var changeUrlListener = {
}; };
signalSlotServer.registerClient(changeUrlListener); 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({ var Peers2 = React.createClass({
mixins: [AutoUpdateMixin, SignalSlotMixin], mixins: [AutoUpdateMixin, SignalSlotMixin],
getPath: function(){return "peers";}, getPath: function(){return "peers";},
@ -260,6 +245,81 @@ var Peers2 = React.createClass({
}, },
}); });
var Peers3 = 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",
float:"left",
};
if(loc.is_online)
online_style.backgroundColor = "lime";
return(<div key={loc.peer_id} style={{color: loc.is_online? "lime": "grey"}}>{/*<div style={online_style}></div>*/}{loc.location}</div>);
});
var avatars = this.props.data.locations.map(function(loc){
if(loc.is_online)
{
var avatar_url = api_url + component.getPath() + loc.avatar_address;
return <img style={{borderRadius: "3mm", margin: "2mm"}} src={avatar_url}/>;
}
else return <span></span>;
});
var remove_button_style = {
color: "red",
//fontSize: "1.5em",
fontSize: "10mm",
padding: "0.2em",
cursor: "pointer",
};
var remove_button = <div onClick={this.remove_peer_handler} style={remove_button_style}>X</div>;
var is_online = false;
for(var i in this.props.data.locations)
{
if(this.props.data.locations[i].is_online)
is_online = true;
}
return(
<div className="flexbox" style={{color: is_online? "lime": "grey"}}>
<div>{avatars}</div>
<div className="flexwidemember">
<h1 style={{marginBottom: "1mm"}}>{this.props.data.name}</h1>
<div>{locations}</div>
</div>
{remove_button}
</div>
);
}
});
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>
{this.state.data.map(function(peer){ return <Peer key={peer.pgp_id} data={peer}/>; })}
</div>);
},
});
var AddPeerWidget = React.createClass({ var AddPeerWidget = React.createClass({
getInitialState: function(){ getInitialState: function(){
return {page: "start"}; return {page: "start"};
@ -309,6 +369,35 @@ var AddPeerWidget = React.createClass({
}, },
}); });
var ProgressBar = React.createClass({
render: function(){
return(
<div style={{
borderStyle: "solid",
borderColor: "lime",
borderRadius: "3mm",
padding: "2mm",
height: "10mm",
}}>
<div style={{backgroundColor: "lime", height: "100%", width: (this.props.progress*100)+"%",}}></div>
</div>);
}
});
function makeFriendlyUnit(bytes)
{
if(bytes < 1e3)
return bytes.toFixed(1) + "B";
if(bytes < 1e3)
return (bytes/1e3).toFixed(1) + "kB";
if(bytes < 1e9)
return (bytes/1e6).toFixed(1) + "MB";
if(bytes < 1e12)
return (bytes/1e9).toFixed(1) + "GB";
return (bytes/1e12).toFixed(1) + "TB";
}
var DownloadsWidget = React.createClass({ var DownloadsWidget = React.createClass({
mixins: [AutoUpdateMixin, SignalSlotMixin], mixins: [AutoUpdateMixin, SignalSlotMixin],
getPath: function(){ return "transfers/downloads";}, getPath: function(){ return "transfers/downloads";},
@ -372,8 +461,8 @@ var DownloadsWidget = React.createClass({
} }
return(<tr> return(<tr>
<td>{this.props.data.name}</td> <td>{this.props.data.name}</td>
<td>{this.props.data.size}</td> <td>{makeFriendlyUnit(this.props.data.size)}</td>
<td>{this.props.data.transfered / this.props.data.size}</td> <td><ProgressBar progress={this.props.data.transfered / this.props.data.size}/></td>
<td>{this.props.data.download_status}</td> <td>{this.props.data.download_status}</td>
<td>{ctrlBtn} <div className="btn" onClick={cancelFn}>cancel</div> {playBtn}</td> <td>{ctrlBtn} <div className="btn" onClick={cancelFn}>cancel</div> {playBtn}</td>
</tr>); </tr>);
@ -728,7 +817,7 @@ var MainWidget = React.createClass({
} }
if(this.state.page === "friends") if(this.state.page === "friends")
{ {
mainpage = <Peers2 />; mainpage = <Peers3 />;
} }
if(this.state.page === "downloads") if(this.state.page === "downloads")
{ {
@ -765,6 +854,7 @@ var MainWidget = React.createClass({
<AudioPlayerWidget/> <AudioPlayerWidget/>
{menubutton} {menubutton}
{mainpage} {mainpage}
{/*<ProgressBar progress={0.7}/>*/}
</div> </div>
); );
}, },

View File

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

View File

@ -12,9 +12,11 @@ BUILD / INSTALLATION
- run (requires wget, use MinGW shell on Windows) - run (requires wget, use MinGW shell on Windows)
make make
- all output files are now in the "dist" folder - all output files are now in libresapi/src/webfiles
- use the --webinterface 9090 command line parameter to enable webui in retroshare-nogui - use the --webinterface 9090 command line parameter to enable webui in retroshare-nogui
- set the --docroot parameter of retroshare-nogui to point to the "dist" directory - set the --docroot parameter of retroshare-nogui to point to the "libresapi/src/webfiles" directory
(or symlink from /usr/share/RetroShare0.6/webui on Linux, ./webui on Windows)
- retroshare-gui does not have a --docroot parameter. Use symlinks then.
DEVELOPMENT DEVELOPMENT
----------- -----------
@ -26,8 +28,8 @@ DEVELOPMENT
npm install npm install
- run Retroshare with webinterface on port 9090 - run Retroshare with webinterface on port 9090
- during development, run this command (use MinGW shell on Windows) - during development, run this command (use MinGW shell on Windows)
while true; do make dist/livereload --silent; sleep 1; done while true; do make ../webfiles/livereload --silent; sleep 1; done
- the command will copy the source files to the "dist" directory if they change - 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 - it will trigger livereload at http://localhost:9090/api/v2/livereload/trigger
API DOCUMENTATION API DOCUMENTATION

View File

@ -71,6 +71,23 @@ input:hover{
background-color: midnightblue; background-color: midnightblue;
} }
.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{ #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

@ -188,21 +188,6 @@ var changeUrlListener = {
}; };
signalSlotServer.registerClient(changeUrlListener); 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({ var Peers2 = React.createClass({
mixins: [AutoUpdateMixin, SignalSlotMixin], mixins: [AutoUpdateMixin, SignalSlotMixin],
getPath: function(){return "peers";}, getPath: function(){return "peers";},
@ -260,6 +245,81 @@ var Peers2 = React.createClass({
}, },
}); });
var Peers3 = 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",
float:"left",
};
if(loc.is_online)
online_style.backgroundColor = "lime";
return(<div key={loc.peer_id} style={{color: loc.is_online? "lime": "grey"}}>{/*<div style={online_style}></div>*/}{loc.location}</div>);
});
var avatars = this.props.data.locations.map(function(loc){
if(loc.is_online)
{
var avatar_url = api_url + component.getPath() + loc.avatar_address;
return <img style={{borderRadius: "3mm", margin: "2mm"}} src={avatar_url}/>;
}
else return <span></span>;
});
var remove_button_style = {
color: "red",
//fontSize: "1.5em",
fontSize: "10mm",
padding: "0.2em",
cursor: "pointer",
};
var remove_button = <div onClick={this.remove_peer_handler} style={remove_button_style}>X</div>;
var is_online = false;
for(var i in this.props.data.locations)
{
if(this.props.data.locations[i].is_online)
is_online = true;
}
return(
<div className="flexbox" style={{color: is_online? "lime": "grey"}}>
<div>{avatars}</div>
<div className="flexwidemember">
<h1 style={{marginBottom: "1mm"}}>{this.props.data.name}</h1>
<div>{locations}</div>
</div>
{remove_button}
</div>
);
}
});
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>
{this.state.data.map(function(peer){ return <Peer key={peer.pgp_id} data={peer}/>; })}
</div>);
},
});
var AddPeerWidget = React.createClass({ var AddPeerWidget = React.createClass({
getInitialState: function(){ getInitialState: function(){
return {page: "start"}; return {page: "start"};
@ -309,6 +369,35 @@ var AddPeerWidget = React.createClass({
}, },
}); });
var ProgressBar = React.createClass({
render: function(){
return(
<div style={{
borderStyle: "solid",
borderColor: "lime",
borderRadius: "3mm",
padding: "2mm",
height: "10mm",
}}>
<div style={{backgroundColor: "lime", height: "100%", width: (this.props.progress*100)+"%",}}></div>
</div>);
}
});
function makeFriendlyUnit(bytes)
{
if(bytes < 1e3)
return bytes.toFixed(1) + "B";
if(bytes < 1e3)
return (bytes/1e3).toFixed(1) + "kB";
if(bytes < 1e9)
return (bytes/1e6).toFixed(1) + "MB";
if(bytes < 1e12)
return (bytes/1e9).toFixed(1) + "GB";
return (bytes/1e12).toFixed(1) + "TB";
}
var DownloadsWidget = React.createClass({ var DownloadsWidget = React.createClass({
mixins: [AutoUpdateMixin, SignalSlotMixin], mixins: [AutoUpdateMixin, SignalSlotMixin],
getPath: function(){ return "transfers/downloads";}, getPath: function(){ return "transfers/downloads";},
@ -372,8 +461,8 @@ var DownloadsWidget = React.createClass({
} }
return(<tr> return(<tr>
<td>{this.props.data.name}</td> <td>{this.props.data.name}</td>
<td>{this.props.data.size}</td> <td>{makeFriendlyUnit(this.props.data.size)}</td>
<td>{this.props.data.transfered / this.props.data.size}</td> <td><ProgressBar progress={this.props.data.transfered / this.props.data.size}/></td>
<td>{this.props.data.download_status}</td> <td>{this.props.data.download_status}</td>
<td>{ctrlBtn} <div className="btn" onClick={cancelFn}>cancel</div> {playBtn}</td> <td>{ctrlBtn} <div className="btn" onClick={cancelFn}>cancel</div> {playBtn}</td>
</tr>); </tr>);
@ -728,7 +817,7 @@ var MainWidget = React.createClass({
} }
if(this.state.page === "friends") if(this.state.page === "friends")
{ {
mainpage = <Peers2 />; mainpage = <Peers3 />;
} }
if(this.state.page === "downloads") if(this.state.page === "downloads")
{ {
@ -765,6 +854,7 @@ var MainWidget = React.createClass({
<AudioPlayerWidget/> <AudioPlayerWidget/>
{menubutton} {menubutton}
{mainpage} {mainpage}
{/*<ProgressBar progress={0.7}/>*/}
</div> </div>
); );
}, },