mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-04 08:28:11 +00:00
React activity (#2315)
This commit is contained in:
committed by
Daniel Pan
parent
99080e95c3
commit
740d6a86cf
53
frontend/src/components/Notification.js
Normal file
53
frontend/src/components/Notification.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const gettext = window.gettext;
|
||||||
|
class Notification extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
showNotice: false,
|
||||||
|
notice_html: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = () => {
|
||||||
|
this.setState({
|
||||||
|
showNotice: !this.state.showNotice
|
||||||
|
})
|
||||||
|
if (!this.state.showNotice) {
|
||||||
|
this.loadNotices()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNotices = () => {
|
||||||
|
this.props.seafileAPI.getPopupNotices().then(res => {
|
||||||
|
this.setState({
|
||||||
|
notice_html: res.data.notice_html
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div id="notifications">
|
||||||
|
<a href="#" onClick={this.onClick} className="no-deco" id="notice-icon" title="Notifications" aria-label="Notifications">
|
||||||
|
<span className="sf2-icon-bell"></span>
|
||||||
|
<span className="num hide">0</span>
|
||||||
|
</a>
|
||||||
|
<div id="notice-popover" className={`sf-popover ${this.state.showNotice ? '': 'hide'}`}>
|
||||||
|
<div className="outer-caret up-outer-caret"><div className="inner-caret"></div></div>
|
||||||
|
<div className="sf-popover-hd ovhd">
|
||||||
|
<h3 className="sf-popover-title">{gettext('Notifications')}</h3>
|
||||||
|
<a href="#" onClick={this.onClick} title={gettext('Close')} aria-label={gettext('Close')} className="sf-popover-close js-close sf2-icon-x1 op-icon float-right"></a>
|
||||||
|
</div>
|
||||||
|
<div className="sf-popover-con">
|
||||||
|
<ul className="notice-list" dangerouslySetInnerHTML={{__html: this.state.notice_html}}></ul>
|
||||||
|
<a href="/notification/list/" className="view-all">{gettext('See All Notifications')}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Notification;
|
@@ -2,8 +2,9 @@ import React, { Component } from 'react';
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import cookie from 'react-cookies';
|
import cookie from 'react-cookies';
|
||||||
import { keyCodes, bytesToSize } from './utils';
|
import { keyCodes, bytesToSize } from './utils';
|
||||||
import { siteRoot, avatarInfo, gettext } from './constance';
|
|
||||||
|
|
||||||
|
const siteRoot = window.app.config.siteRoot;
|
||||||
|
const gettext = window.gettext;
|
||||||
|
|
||||||
class Account extends Component {
|
class Account extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
243
frontend/src/components/files-activities.js
Normal file
243
frontend/src/components/files-activities.js
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
const gettext = window.gettext;
|
||||||
|
const siteRoot = window.app.config.siteRoot;
|
||||||
|
const per_page = 25; // default
|
||||||
|
|
||||||
|
class FileActivitiesContent extends Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {loading, error_msg, events} = this.props.data;
|
||||||
|
if (loading) {
|
||||||
|
return <span className="loading-icon loading-tip"></span>;
|
||||||
|
} else if (error_msg) {
|
||||||
|
return <p className="error text-center">{error_msg}</p>;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<table className="table table-hover table-vcenter activity-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="8%">{/* avatar */}</th>
|
||||||
|
<th width="10%">{gettext("User")}</th>
|
||||||
|
<th width="25%">{gettext("Operation")}</th>
|
||||||
|
<th width="37%">{gettext("File")} / {gettext("Library")}</th>
|
||||||
|
<th width="20%">{gettext("Time")}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<TableBody items={events.items} />
|
||||||
|
</table>
|
||||||
|
{events.has_more ? <span className="loading-icon loading-tip"></span> : ''}
|
||||||
|
{events.error_msg ? <p className="error text-center">{events.error_msg}</p> : ''}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TableBody extends Component {
|
||||||
|
|
||||||
|
encodePath(path) {
|
||||||
|
let path_arr = path.split('/'),
|
||||||
|
path_arr_ = [];
|
||||||
|
for (let i = 0, len = path_arr.length; i < len; i++) {
|
||||||
|
path_arr_.push(encodeURIComponent(path_arr[i]));
|
||||||
|
}
|
||||||
|
return path_arr_.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let listFilesActivities = this.props.items.map(function(item, index) {
|
||||||
|
let op, details;
|
||||||
|
let userProfileURL = `${siteRoot}profile/${encodeURIComponent(item.author_email)}/`;
|
||||||
|
|
||||||
|
let libURL = `${siteRoot}#common/lib/${item.repo_id}`;
|
||||||
|
let libLink = <a href={libURL}>{item.repo_name}</a>;
|
||||||
|
let smallLibLink = <a className="small text-secondary" href={libURL}>{item.repo_name}</a>;
|
||||||
|
|
||||||
|
if (item.obj_type == 'repo') {
|
||||||
|
switch(item.op_type) {
|
||||||
|
case 'create':
|
||||||
|
op = gettext("Created library");
|
||||||
|
details = <td>{libLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'rename':
|
||||||
|
op = gettext("Renamed library");
|
||||||
|
details = <td>{item.old_repo_name} => {libLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
op = gettext("Deleted library");
|
||||||
|
details = <td>{item.repo_name}</td>;
|
||||||
|
break;
|
||||||
|
case 'recover':
|
||||||
|
op = gettext("Restored library");
|
||||||
|
details = <td>{libLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'clean-up-trash':
|
||||||
|
if (item.days == 0) {
|
||||||
|
op = gettext("Removed all items from trash.");
|
||||||
|
} else {
|
||||||
|
op = gettext("Removed items older than {n} days from trash.").replace('{n}', item.days);
|
||||||
|
}
|
||||||
|
details = <td>{libLink}</td>;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (item.obj_type == 'file') {
|
||||||
|
let fileURL = `${siteRoot}lib/${item.repo_id}/file${this.encodePath(item.path)}`;
|
||||||
|
let fileLink = <a href={fileURL}>{item.name}</a>;
|
||||||
|
switch(item.op_type) {
|
||||||
|
case 'create':
|
||||||
|
op = gettext("Created file");
|
||||||
|
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
op = gettext("Deleted file");
|
||||||
|
details = <td>{item.name}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'recover':
|
||||||
|
op = gettext("Restored file");
|
||||||
|
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'rename':
|
||||||
|
op = gettext("Renamed file");
|
||||||
|
details = <td>{item.old_name} => {fileLink}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
let filePathLink = <a href={fileURL}>{item.path}</a>;
|
||||||
|
op = gettext("Moved file");
|
||||||
|
details = <td>{item.old_path} => {filePathLink}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'edit': // update
|
||||||
|
op = gettext("Updated file");
|
||||||
|
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else { // dir
|
||||||
|
let dirURL = `${siteRoot}#common/lib/${item.repo_id}${this.encodePath(item.path)}`;
|
||||||
|
let dirLink = <a href={dirURL}>{item.name}</a>;
|
||||||
|
switch(item.op_type) {
|
||||||
|
case 'create':
|
||||||
|
op = gettext("Created folder");
|
||||||
|
details = <td>{dirLink}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
op = gettext("Deleted folder");
|
||||||
|
details = <td>{item.name}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'recover':
|
||||||
|
op = gettext("Restored folder");
|
||||||
|
details = <td>{dirLink}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'rename':
|
||||||
|
op = gettext("Renamed folder");
|
||||||
|
details = <td>{item.old_name} => {dirLink}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
let dirPathLink = <a href={dirURL}>{item.path}</a>;
|
||||||
|
op = gettext("Moved folder");
|
||||||
|
details = <td>{item.old_path} => {dirPathLink}<br />{smallLibLink}</td>;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={index}>
|
||||||
|
<td className="text-center">
|
||||||
|
<img src={item.avatar_url} alt="" width="24" className="avatar" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href={userProfileURL}>{item.author_name}</a>
|
||||||
|
</td>
|
||||||
|
<td><span className="activity-op">{op}</span></td>
|
||||||
|
{details}
|
||||||
|
<td className="text-secondary" dangerouslySetInnerHTML={{__html:item.time_relative}}></td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tbody>{listFilesActivities}</tbody>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilesActivities extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
error_msg: '',
|
||||||
|
events: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleScroll = this.handleScroll.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const pageNum = 1
|
||||||
|
this.props.seafileAPI.getActivities(pageNum)
|
||||||
|
.then(res => {
|
||||||
|
// not logged in
|
||||||
|
if (res.status == 403) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
error_msg: gettext("Permission denied")
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// {"events":[...]}
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
events: {
|
||||||
|
page: 1,
|
||||||
|
items: res.data.events,
|
||||||
|
has_more: res.data.events.length == per_page ? true : false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMore() {
|
||||||
|
const pageNum = this.state.events.page + 1;
|
||||||
|
this.props.seafileAPI.getActivities(pageNum)
|
||||||
|
.then(res => {
|
||||||
|
this.setState(function(prevState, props) {
|
||||||
|
let events = prevState.events;
|
||||||
|
if (res.status == 403) { // log out
|
||||||
|
events.error_msg = gettext("Permission denied");
|
||||||
|
events.has_more = false;
|
||||||
|
}
|
||||||
|
if (res.ok) {
|
||||||
|
events.page += 1;
|
||||||
|
events.items = events.items.concat(res.data.events);
|
||||||
|
events.has_more = res.data.events.length == per_page ? true : false;
|
||||||
|
}
|
||||||
|
return {events: events};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll(e) {
|
||||||
|
let $el = e.target;
|
||||||
|
if (this.state.events.has_more &&
|
||||||
|
$el.scrollTop > 0 &&
|
||||||
|
$el.clientHeight + $el.scrollTop == $el.scrollHeight) { // scroll to the bottom
|
||||||
|
this.getMore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="main-panel-main" id="activities">
|
||||||
|
<div className="cur-view-path">
|
||||||
|
<h3 className="sf-heading">{gettext("Activities")}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="cur-view-main-con" onScroll={this.handleScroll}>
|
||||||
|
<FileActivitiesContent data={this.state} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FilesActivities;
|
124
frontend/src/components/main-side-nav.js
Normal file
124
frontend/src/components/main-side-nav.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import React from 'react';
|
||||||
|
const siteRoot = window.app.config.siteRoot;
|
||||||
|
const serverRoot = window.app.config.serverRoot;
|
||||||
|
|
||||||
|
class MainSideNav extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
groupsExtended: false,
|
||||||
|
sharedExtended: false,
|
||||||
|
closeSideBar:false,
|
||||||
|
groupItems: []
|
||||||
|
};
|
||||||
|
|
||||||
|
this.listHeight = 24; //for caculate tabheight
|
||||||
|
this.groupsHeight = 0;
|
||||||
|
this.adminHeight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
grpsExtend = () => {
|
||||||
|
this.setState({
|
||||||
|
groupsExtended: !this.state.groupsExtended,
|
||||||
|
})
|
||||||
|
this.loadGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
shExtend = () => {
|
||||||
|
this.setState({
|
||||||
|
sharedExtended: !this.state.sharedExtended,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadGroups = () => {
|
||||||
|
let _this = this;
|
||||||
|
this.props.seafileAPI.getGroups().then(res =>{
|
||||||
|
let data = res.data.groups;
|
||||||
|
this.groupsHeight = (data.length + 1) * _this.listHeight;
|
||||||
|
_this.setState({
|
||||||
|
groupItems: data
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSharedGroups() {
|
||||||
|
let style = {height: 0};
|
||||||
|
if (this.state.groupsExtended) {
|
||||||
|
style = {height: this.groupsHeight};
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ul className={`grp-list ${this.state.groupsExtended ? 'side-panel-slide' : 'side-panel-slide-up'}`} style={style}>
|
||||||
|
<li>
|
||||||
|
<a href={siteRoot + '#groups/'}>
|
||||||
|
<span className="sharp" aria-hidden="true">#</span>All Groups</a>
|
||||||
|
</li>
|
||||||
|
{this.state.groupItems.map(item => {
|
||||||
|
return (
|
||||||
|
<li key={item.id}>
|
||||||
|
<a href={siteRoot + '#group/' + item.id + '/'}>
|
||||||
|
<span className="sharp" aria-hidden="true">#</span>{item.name}</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSharedAdmin() {
|
||||||
|
let height = 0;
|
||||||
|
if (this.state.sharedExtended) {
|
||||||
|
if (!this.adminHeight) {
|
||||||
|
this.adminHeight = 3 * this.listHeight;
|
||||||
|
}
|
||||||
|
height = this.adminHeight;
|
||||||
|
}
|
||||||
|
let style = {height: height};
|
||||||
|
return (
|
||||||
|
<ul className={`${this.state.sharedExtended ? 'side-panel-slide' : 'side-panel-slide-up'}`} style={style} >
|
||||||
|
<li>
|
||||||
|
<a href={siteRoot + '#share-admin-libs/'}><span aria-hidden="true" className="sharp">#</span>Libraries</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href={siteRoot + '#share-admin-folders/'}><span aria-hidden="true" className="sharp">#</span>Folders</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href={siteRoot + '#share-admin-share-links/'}><span aria-hidden="true" className="sharp">#</span>Links</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div id="side-nav" className="home-side-nav">
|
||||||
|
<div className="side-nav-con">
|
||||||
|
<h3 className="sf-heading">Files</h3>
|
||||||
|
<ul className="side-tabnav-tabs">
|
||||||
|
<li className="tab"><a href={siteRoot + '#my-libs'} className="ellipsis" title="My Libraries"><span className="sf2-icon-user" aria-hidden="true"></span>My Libraries</a></li>
|
||||||
|
<li className="tab"><a href={serverRoot + siteRoot + '#shared-libs/'} className="ellipsis" title="Shared with me"><span className="sf2-icon-share" aria-hidden="true"></span>Shared with me</a></li>
|
||||||
|
<li className="tab"><a href={serverRoot + siteRoot + '#org/'} className="ellipsis" title="Shared with all"><span className="sf2-icon-organization" aria-hidden="true"></span>Shared with all</a></li>
|
||||||
|
<li className="tab" id="group-nav">
|
||||||
|
<a className="ellipsis user-select-no" title="Shared with groups" onClick={this.grpsExtend}><span className={`toggle-icon float-right ${this.state.groupsExtended ?'icon-caret-down':'icon-caret-left'}`} aria-hidden="true"></span><span className="sf2-icon-group" aria-hidden="true"></span>Shared with groups</a>
|
||||||
|
{this.renderSharedGroups()}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="hd w-100 o-hidden">
|
||||||
|
<h3 className="float-left sf-heading">Tools</h3>
|
||||||
|
</div>
|
||||||
|
<ul className="side-tabnav-tabs">
|
||||||
|
<li className="tab"><a href={siteRoot + '#starred/'}><span className="sf2-icon-star" aria-hidden="true"></span>Favorites</a></li>
|
||||||
|
<li className="tab"><a href={siteRoot + 'dashboard'}><span className="sf2-icon-clock" aria-hidden="true"></span>Acitivities</a></li>
|
||||||
|
<li className="tab"><a href={siteRoot + '#devices/'} className="ellipsis" title="Linked Devices"><span className="sf2-icon-monitor" aria-hidden="true"></span>Linked Devices</a></li>
|
||||||
|
<li className="tab" id="share-admin-nav">
|
||||||
|
<a className="ellipsis user-select-no" title="Share Admin" onClick={this.shExtend}><span className={`toggle-icon float-right ${this.state.sharedExtended ? 'icon-caret-down':'icon-caret-left'}`} aria-hidden="true"></span><span aria-hidden="true" className="sf2-icon-wrench"></span>Share Admin</a>
|
||||||
|
{this.renderSharedAdmin()}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MainSideNav;
|
@@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { gettext, repoID } from './constance';
|
import { gettext, repoID } from './constance';
|
||||||
import SearchResultItem from './SearchResultItem';
|
import SearchResultItem from './search-result-item';
|
||||||
|
|
||||||
class Search extends Component {
|
class Search extends Component {
|
||||||
|
|
||||||
|
54
frontend/src/components/side-nav-footer.js
Normal file
54
frontend/src/components/side-nav-footer.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
|
||||||
|
|
||||||
|
class About extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
modal: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.toggle = this.toggle.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.setState({
|
||||||
|
modal: !this.state.modal
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<a href="#" className="item" onClick={this.toggle}>About</a>
|
||||||
|
<Modal isOpen={this.state.modal} toggle={this.toggle} className={this.props.className}>
|
||||||
|
<ModalBody>
|
||||||
|
<div className="about-content">
|
||||||
|
<p><img src="/media/img/seafile-logo.png" title="Private Seafile" alt="logo" width="128" height="32" /></p>
|
||||||
|
<p>Server Version: 6.3.3<br /> © 2018 Seafile</p>
|
||||||
|
<p><a href="http://seafile.com/about/" target="_blank">About Us</a></p>
|
||||||
|
</div>
|
||||||
|
</ModalBody>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SideNavFooter extends React.Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="side-nav-footer">
|
||||||
|
<a href="/help/" target="_blank" className="item">Help</a>
|
||||||
|
<About />
|
||||||
|
<a href="/download_client_program/" className="item last-item">
|
||||||
|
<span aria-hidden="true" className="sf2-icon-monitor vam"></span>{' '}
|
||||||
|
<span className="vam">Clients</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SideNavFooter;
|
10
frontend/src/css/dashboard.css
Normal file
10
frontend/src/css/dashboard.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.activity-table a {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-op {
|
||||||
|
color: #707070;
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
52
frontend/src/dashboard.js
Normal file
52
frontend/src/dashboard.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import SidePanel from './pages/dashboard/side-panel';
|
||||||
|
import MainPanel from './pages/dashboard/main-panel';
|
||||||
|
|
||||||
|
import Account from './components/account';
|
||||||
|
import Notification from './components/notification';
|
||||||
|
|
||||||
|
import { SeafileAPI } from './seafile-api';
|
||||||
|
import cookie from 'react-cookies';
|
||||||
|
|
||||||
|
import 'seafile-ui';
|
||||||
|
import './css/dashboard.css';
|
||||||
|
|
||||||
|
const siteRoot = window.app.config.siteRoot;
|
||||||
|
|
||||||
|
let seafileAPI = new SeafileAPI();
|
||||||
|
let xcsrfHeaders = cookie.load('csrftoken');
|
||||||
|
seafileAPI.initForSeahubUsage({ siteRoot, xcsrfHeaders });
|
||||||
|
|
||||||
|
class DashBoard extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isOpen: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isOpen = () => {
|
||||||
|
this.setState({
|
||||||
|
isOpen: !this.state.isOpen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div id="main">
|
||||||
|
<SidePanel isOpen={this.state.isOpen} toggleClose={this.isOpen} seafileAPI={seafileAPI}/>
|
||||||
|
<MainPanel isOpen={this.isOpen} seafileAPI={seafileAPI} >
|
||||||
|
<Notification seafileAPI={seafileAPI} />
|
||||||
|
<Account seafileAPI={seafileAPI}/>
|
||||||
|
</MainPanel>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<DashBoard />,
|
||||||
|
document.getElementById('wrapper')
|
||||||
|
);
|
30
frontend/src/pages/dashboard/main-panel.js
Normal file
30
frontend/src/pages/dashboard/main-panel.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import FilesActivities from '../../components/files-activities';
|
||||||
|
|
||||||
|
class MainPanel extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuClick = () => {
|
||||||
|
this.props.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children } = this.props
|
||||||
|
return (
|
||||||
|
<div className="main-panel o-hidden">
|
||||||
|
<div className="main-panel-top panel-top">
|
||||||
|
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.onMenuClick}></span>
|
||||||
|
<div className="common-toolbar">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FilesActivities seafileAPI={this.props.seafileAPI} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MainPanel;
|
30
frontend/src/pages/dashboard/side-panel.js
Normal file
30
frontend/src/pages/dashboard/side-panel.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import SideNavFooter from '../../components/side-nav-footer';
|
||||||
|
import MainSideNav from '../../components/main-side-nav';
|
||||||
|
|
||||||
|
const siteRoot = window.app.config.siteRoot;
|
||||||
|
const serverRoot = window.app.config.serverRoot;
|
||||||
|
const logoPath = window.app.config.logoPath;
|
||||||
|
const mediaUrl = window.app.config.mediaUrl;
|
||||||
|
const siteTitle = window.app.config.siteTitle;
|
||||||
|
const logoWidth = window.app.config.logoWidth;
|
||||||
|
const logoHeight = window.app.config.logoHeight;
|
||||||
|
|
||||||
|
class SidePanel extends Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={`side-panel ${this.props.isOpen ? "left-zero": ""}`}>
|
||||||
|
<div className="side-panel-top panel-top">
|
||||||
|
<a href={siteRoot} id="logo">
|
||||||
|
<img src={mediaUrl + logoPath} title={siteTitle} alt="logo" width={logoWidth} height={logoHeight} />
|
||||||
|
</a>
|
||||||
|
<a href="#" title="Close" aria-label="Close" onClick={this.props.toggleClose} className="sf2-icon-x1 sf-popover-close side-panel-close op-icon d-md-none "></a>
|
||||||
|
</div>
|
||||||
|
<MainSideNav seafileAPI={this.props.seafileAPI}/>
|
||||||
|
<SideNavFooter />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default SidePanel;
|
@@ -1,8 +1,8 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Search from './search';
|
import Search from '../../components/search';
|
||||||
import MarkdownViewer from './markdown-viewer';
|
import MarkdownViewer from '../../components/markdown-viewer';
|
||||||
import Account from './account';
|
import Account from '../../components/account';
|
||||||
import { gettext, repoID, serviceUrl, slug, siteRoot } from './constance';
|
import { gettext, repoID, serviceUrl, slug, siteRoot } from '../../components/constance';
|
||||||
|
|
||||||
class MainPanel extends Component {
|
class MainPanel extends Component {
|
||||||
|
|
@@ -1,10 +1,10 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import TreeView from './tree-view/tree-view';
|
import TreeView from '../../components/tree-view/tree-view';
|
||||||
import { siteRoot, logoPath, mediaUrl, siteTitle, logoWidth, logoHeight } from './constance';
|
import { siteRoot, logoPath, mediaUrl, siteTitle, logoWidth, logoHeight } from '../../components/constance';
|
||||||
import Tree from './tree-view/tree';
|
import Tree from '../../components/tree-view/tree';
|
||||||
import Node from './tree-view/node'
|
import Node from '../../components/tree-view/node'
|
||||||
import NodeMenu from './menu-component/node-menu';
|
import NodeMenu from '../../components/menu-component/node-menu';
|
||||||
import MenuControl from './menu-component/node-menu-control';
|
import MenuControl from '../../components/menu-component/node-menu-control';
|
||||||
const gettext = window.gettext;
|
const gettext = window.gettext;
|
||||||
|
|
||||||
class SidePanel extends Component {
|
class SidePanel extends Component {
|
@@ -1,7 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import SidePanel from './components/SidePanel';
|
import SidePanel from './pages/wiki/side-panel';
|
||||||
import MainPanel from './components/MainPanel';
|
import MainPanel from './pages/wiki/main-panel';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import cookie from 'react-cookies';
|
import cookie from 'react-cookies';
|
||||||
import { SeafileAPI } from 'seafile-js';
|
import { SeafileAPI } from 'seafile-js';
|
||||||
|
@@ -79,7 +79,10 @@ a:hover { color:#eb8205; }
|
|||||||
.left-zero {
|
.left-zero {
|
||||||
left: 0px !important;
|
left: 0px !important;
|
||||||
}
|
}
|
||||||
|
ul,ol,li {
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
/* common elements */
|
/* common elements */
|
||||||
|
|
||||||
@@ -261,6 +264,7 @@ a:hover { color:#eb8205; }
|
|||||||
padding:12px 20px 16px;
|
padding:12px 20px 16px;
|
||||||
background:#f8f8f8;
|
background:#f8f8f8;
|
||||||
border-top:1px solid #eee;
|
border-top:1px solid #eee;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
}
|
}
|
||||||
.side-nav-footer .item {
|
.side-nav-footer .item {
|
||||||
color:#666;
|
color:#666;
|
||||||
@@ -572,6 +576,14 @@ a.op-icon:focus {
|
|||||||
color:#999;
|
color:#999;
|
||||||
margin-right:15px;
|
margin-right:15px;
|
||||||
}
|
}
|
||||||
|
.side-panel-slide {
|
||||||
|
transition: all .3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side-panel-slide-up {
|
||||||
|
transition: all .3s ease-in-out;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
#group-nav .sharp,
|
#group-nav .sharp,
|
||||||
#share-admin-nav .sharp {
|
#share-admin-nav .sharp {
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
@@ -614,13 +626,33 @@ a.op-icon:focus {
|
|||||||
text-decoration:none;
|
text-decoration:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-select-none {
|
||||||
|
-moz-user-select:none;
|
||||||
|
-webkit-user-select:none;
|
||||||
|
-ms-user-select:none;
|
||||||
|
-khtml-user-select:none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
.common-toolbar {
|
.common-toolbar {
|
||||||
margin-left:auto;
|
margin-left:auto;
|
||||||
display:flex;
|
display:flex;
|
||||||
}
|
}
|
||||||
#notifications {
|
#notifications {
|
||||||
position:relative;
|
position:relative;
|
||||||
margin:5px 0 0 25px;
|
width: 32px;
|
||||||
|
}
|
||||||
|
#notifications .no-deco {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#notifications .sf2-icon-bell {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%,-50%);
|
||||||
}
|
}
|
||||||
@media (max-width: 390px) {
|
@media (max-width: 390px) {
|
||||||
#notifications {
|
#notifications {
|
||||||
@@ -628,6 +660,13 @@ a.op-icon:focus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sf-heading {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #322;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* about dialog */
|
/* about dialog */
|
||||||
.about-content {
|
.about-content {
|
||||||
@@ -679,8 +718,6 @@ a.op-icon:focus {
|
|||||||
padding:1px 30px 1px 5px;
|
padding:1px 30px 1px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.wiki-page-ops {
|
.wiki-page-ops {
|
||||||
position:fixed;
|
position:fixed;
|
||||||
top:10px;
|
top:10px;
|
||||||
@@ -726,3 +763,105 @@ select {
|
|||||||
background:#fff;
|
background:#fff;
|
||||||
line-height:17px;
|
line-height:17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#notifications .sf2-icon-bell {
|
||||||
|
font-size:24px;
|
||||||
|
line-height:1;
|
||||||
|
color:#999;
|
||||||
|
}
|
||||||
|
#notifications .num {
|
||||||
|
position:absolute;
|
||||||
|
color:#fff;
|
||||||
|
font-size:12px;
|
||||||
|
line-height:1;
|
||||||
|
padding:1px 2px;
|
||||||
|
background:#feac74;
|
||||||
|
border:1px solid #cb8a5d;
|
||||||
|
top:0;
|
||||||
|
left:15px;
|
||||||
|
}
|
||||||
|
#notice-popover {
|
||||||
|
top:38px;
|
||||||
|
right:-12px;
|
||||||
|
}
|
||||||
|
#notice-popover .outer-caret {
|
||||||
|
right:18px;
|
||||||
|
}
|
||||||
|
#notice-popover a {
|
||||||
|
font-weight:normal;
|
||||||
|
}
|
||||||
|
#notice-popover li {
|
||||||
|
padding:9px 0 3px;
|
||||||
|
border-bottom:1px solid #dfdfe1;
|
||||||
|
}
|
||||||
|
#notice-popover li.unread {
|
||||||
|
background:#f5f5f7;
|
||||||
|
padding-right:10px;
|
||||||
|
padding-left:8px;
|
||||||
|
border-left:2px solid #feac74;
|
||||||
|
margin:0 -10px;
|
||||||
|
}
|
||||||
|
#notice-popover .avatar {
|
||||||
|
border-radius:1000px;
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
#notice-popover .brief {
|
||||||
|
margin-left:40px;
|
||||||
|
}
|
||||||
|
#notice-popover .time {
|
||||||
|
color:#999;
|
||||||
|
text-align:right;
|
||||||
|
margin:0;
|
||||||
|
clear:both;
|
||||||
|
}
|
||||||
|
#notice-popover .view-all {
|
||||||
|
display:block;
|
||||||
|
padding:7px 0;
|
||||||
|
text-align:center;
|
||||||
|
color:#a4a4a4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notice-popover .sf-popover-close {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**** sf-popover ****/ /* e.g. top notice popup, group members popup */
|
||||||
|
.sf-popover-container {
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
.sf-popover {
|
||||||
|
width:240px;
|
||||||
|
background:#fff;
|
||||||
|
border:1px solid #c9c9c9;
|
||||||
|
border-radius:3px;
|
||||||
|
box-shadow:0 0 4px #ccc;
|
||||||
|
position:absolute;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
.sf-popover-hd {
|
||||||
|
padding:5px 0 3px;
|
||||||
|
border-bottom:1px solid #dfdfe1;
|
||||||
|
margin:0 10px;
|
||||||
|
}
|
||||||
|
.sf-popover-title {
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
.sf-popover-close {
|
||||||
|
font-size:16px;
|
||||||
|
color:#b9b9b9;
|
||||||
|
margin:4px 0 0;
|
||||||
|
}
|
||||||
|
.sf-popover-con {
|
||||||
|
padding:0 10px;
|
||||||
|
overflow:auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-panel-main {
|
||||||
|
flex:auto;
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
overflow:hidden; /* for ff */
|
||||||
|
}
|
||||||
|
7
seahub/templates/dashboard.html
Normal file
7
seahub/templates/dashboard.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends "base_for_react.html" %}
|
||||||
|
{% load render_bundle from webpack_loader %}
|
||||||
|
|
||||||
|
{% block extra_script %}
|
||||||
|
{% render_bundle 'dashboard' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
@@ -178,6 +178,7 @@ urlpatterns = [
|
|||||||
url(r'^modules/toggle/$', toggle_modules, name="toggle_modules"),
|
url(r'^modules/toggle/$', toggle_modules, name="toggle_modules"),
|
||||||
url(r'^download_client_program/$', TemplateView.as_view(template_name="download.html"), name="download_client"),
|
url(r'^download_client_program/$', TemplateView.as_view(template_name="download.html"), name="download_client"),
|
||||||
url(r'^choose_register/$', choose_register, name="choose_register"),
|
url(r'^choose_register/$', choose_register, name="choose_register"),
|
||||||
|
url(r'^dashboard/$', TemplateView.as_view(template_name="dashboard.html"), name="dashboard"),
|
||||||
|
|
||||||
### Ajax ###
|
### Ajax ###
|
||||||
url(r'^ajax/repo/(?P<repo_id>[-0-9a-f]{36})/dirents/$', get_dirents, name="get_dirents"),
|
url(r'^ajax/repo/(?P<repo_id>[-0-9a-f]{36})/dirents/$', get_dirents, name="get_dirents"),
|
||||||
|
Reference in New Issue
Block a user