mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-09 02:42:47 +00:00
[favorites] rewrote it with React (#2417)
This commit is contained in:
@@ -6,6 +6,7 @@ import SidePanel from './components/side-panel';
|
||||
import MainPanel from './components/main-panel';
|
||||
import DraftsView from './pages/drafts/drafts-view';
|
||||
import FilesActivities from './pages/dashboard/files-activities';
|
||||
import Starred from './pages/starred/starred';
|
||||
|
||||
import 'seafile-ui';
|
||||
import './assets/css/fa-solid.css';
|
||||
@@ -49,6 +50,7 @@ class App extends Component {
|
||||
<Router>
|
||||
<FilesActivities path={siteRoot + 'dashboard'} />
|
||||
<DraftsView path={siteRoot + 'drafts'} />
|
||||
<Starred path={siteRoot + 'starred'} />
|
||||
</Router>
|
||||
</MainPanel>
|
||||
</div>
|
||||
|
@@ -2,6 +2,7 @@ export const dirPath = '/';
|
||||
export const gettext = window.gettext;
|
||||
|
||||
export const siteRoot = window.app.config.siteRoot;
|
||||
export const loginUrl = window.app.config.loginUrl;
|
||||
export const avatarInfo = window.app.config.avatarInfo;
|
||||
export const logoPath = window.app.config.logoPath;
|
||||
export const mediaUrl = window.app.config.mediaUrl;
|
||||
|
@@ -146,8 +146,8 @@ class MainSideNav extends React.Component {
|
||||
|
||||
<h3 className="sf-heading">Tools</h3>
|
||||
<ul className="side-tabnav-tabs">
|
||||
<li className={`tab ${this.state.currentTab === 'favorites' ? 'tab-cur' : ''}`}>
|
||||
<a href={siteRoot + '#starred/'} title={gettext('Favorites')} onClick={() => this.tabItemClick('favorites')}>
|
||||
<li className={`tab ${this.state.currentTab === 'starred' ? 'tab-cur' : ''}`}>
|
||||
<a href={siteRoot + 'starred/'} title={gettext('Favorites')} onClick={() => this.tabItemClick('favorites')}>
|
||||
<span className="sf2-icon-star" aria-hidden="true"></span>
|
||||
{gettext('Favorites')}
|
||||
</a>
|
||||
|
284
frontend/src/pages/starred/starred.js
Normal file
284
frontend/src/pages/starred/starred.js
Normal file
@@ -0,0 +1,284 @@
|
||||
import React, { Component } from 'react';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { common } from '../../utils/common';
|
||||
import { gettext, siteRoot, loginUrl } from '../../components/constants';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
render() {
|
||||
const {loading, errorMsg, items} = this.props.data;
|
||||
|
||||
if (loading) {
|
||||
return <span className="loading-icon loading-tip"></span>;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const desktopThead = (
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%"></th>
|
||||
<th width="40%">{gettext("File Name")}</th>
|
||||
<th width="32%">{gettext("Library")}</th>
|
||||
<th width="18%">{gettext("Last Update")}</th>
|
||||
<th width="5%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
const mobileThead = (
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%"></th>
|
||||
<th width="90%">{gettext("File Name")}</th>
|
||||
<th width="5%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
|
||||
return (
|
||||
<table className="table table-hover table-vcenter">
|
||||
{window.innerWidth >= 768 ? desktopThead : mobileThead}
|
||||
<TableBody items={items} />
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TableBody extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
items: this.props.items
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getThumbnails();
|
||||
}
|
||||
|
||||
getThumbnails() {
|
||||
let items = this.state.items.filter((item) => {
|
||||
const name = item.file_name;
|
||||
return common.imageCheck(name) || common.videoCheck(name);
|
||||
});
|
||||
if (items.length == 0) {
|
||||
return ;
|
||||
}
|
||||
|
||||
const len = items.length;
|
||||
const thumbnailSize = 48;
|
||||
const _this = this;
|
||||
let getThumbnail = function(i) {
|
||||
const curItem = items[i];
|
||||
seafileAPI.createThumbnail(curItem.repo_id, curItem.path, thumbnailSize).
|
||||
then((res) => {
|
||||
curItem.encoded_thumbnail_src = res.data.encoded_thumbnail_src;
|
||||
})
|
||||
.catch((error) => {
|
||||
// do nothing
|
||||
})
|
||||
.then(() => {
|
||||
if (i < len - 1) {
|
||||
getThumbnail(++i);
|
||||
} else {
|
||||
// when done, `setState()`
|
||||
_this.setState({
|
||||
items: _this.state.items
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
getThumbnail(0);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let listFilesActivities = this.state.items.map(function(item, index) {
|
||||
let fileIconSize = common.isHiDPI() ? 48 : 24;
|
||||
|
||||
item.file_icon_url = common.getFileIconUrl(item.file_name, fileIconSize);
|
||||
item.is_img = common.imageCheck(item.file_name);
|
||||
item.encoded_path = common.encodePath(item.path);
|
||||
|
||||
item.thumbnail_url = item.encoded_thumbnail_src ? `${siteRoot}${item.encoded_thumbnail_src}` : '';
|
||||
item.file_view_url = `${siteRoot}lib/${item.repo_id}/file${item.encoded_path}`;
|
||||
item.file_raw_url = `${siteRoot}repo/${item.repo_id}/raw${item.encoded_path}`;
|
||||
|
||||
return <Item key={index} data={item} />;
|
||||
}, this);
|
||||
|
||||
return (
|
||||
<tbody>{listFilesActivities}</tbody>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showOpIcon: false,
|
||||
unstarred: false
|
||||
};
|
||||
|
||||
this.handleMouseOver = this.handleMouseOver.bind(this);
|
||||
this.handleMouseOut = this.handleMouseOut.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
handleMouseOver() {
|
||||
this.setState({
|
||||
showOpIcon: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOut() {
|
||||
this.setState({
|
||||
showOpIcon: false
|
||||
});
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const data = this.props.data;
|
||||
seafileAPI.unStarFile(data.repo_id, data.path)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
unstarred: true
|
||||
});
|
||||
// TODO: show feedback msg
|
||||
})
|
||||
.catch((error) => {
|
||||
// TODO: show feedback msg
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
if (this.state.unstarred) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = this.props.data;
|
||||
|
||||
let opClasses = 'sf2-icon-delete unstar op-icon';
|
||||
opClasses += this.state.showOpIcon ? '' : ' invisible';
|
||||
|
||||
const desktopItem = (
|
||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||
<td className="alc">
|
||||
{
|
||||
data.thumbnail_url ?
|
||||
<img className="thumbnail" src={data.thumbnail_url} alt="" /> :
|
||||
<img src={data.file_icon_url} alt={gettext("icon")} width="24" />
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{
|
||||
data.is_img ?
|
||||
<a className="img-name-link normal" href={data.file_view_url} target="_blank" data-mfp-src={data.file_raw_url}>{data.file_name}</a> :
|
||||
<a className="normal" href={data.file_view_url} target="_blank">{data.file_name}</a>
|
||||
}
|
||||
</td>
|
||||
<td>{data.repo_name}</td>
|
||||
<td dangerouslySetInnerHTML={{__html:data.mtime_relative}}></td>
|
||||
<td>
|
||||
<a href="#" className={opClasses} title={gettext("Unstar")} aria-label={gettext("Unstar")} onClick={this.handleClick}></a>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
const mobileItem = (
|
||||
<tr>
|
||||
<td className="alc">
|
||||
{
|
||||
data.thumbnail_url ?
|
||||
<img className="thumbnail" src={data.thumbnail_url} alt="" /> :
|
||||
<img src={data.file_icon_url} alt={gettext("icon")} width="24" />
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{
|
||||
data.is_img ?
|
||||
<a className="img-name-link normal" href={data.file_view_url} target="_blank" data-mfp-src={data.file_raw_url}>{data.file_name}</a> :
|
||||
<a className="normal" href={data.file_view_url} target="_blank">{data.file_name}</a>
|
||||
}
|
||||
<br />
|
||||
<span className="dirent-meta-info">{data.repo_name}</span>
|
||||
<span className="dirent-meta-info" dangerouslySetInnerHTML={{__html:data.mtime_relative}}></span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" className="sf2-icon-delete unstar op-icon" title={gettext("Unstar")} aria-label={gettext("Unstar")} onClick={this.handleClick}></a>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
if (window.innerWidth >= 768) {
|
||||
return desktopItem;
|
||||
} else {
|
||||
return mobileItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Starred extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
items: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.listStarred()
|
||||
.then((res) => {
|
||||
// res: {data: Array(2), status: 200, statusText: "OK", headers: {…}, config: {…}, …}
|
||||
this.setState({
|
||||
loading: false,
|
||||
items: res.data
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext("Permission denied")
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext("Error")
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext("Please check the network.")
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="cur-view-container" id="starred">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading">{gettext("Favorites")}</h3>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<Content data={this.state} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Starred;
|
137
frontend/src/utils/common.js
Normal file
137
frontend/src/utils/common.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import { mediaUrl } from '../components/constants';
|
||||
|
||||
export const common = {
|
||||
|
||||
isHiDPI: function() {
|
||||
var pixelRatio = window.devicePixelRatio ? window.devicePixelRatio : 1;
|
||||
if (pixelRatio > 1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
FILEEXT_ICON_MAP: {
|
||||
|
||||
// text file
|
||||
'md': 'txt.png',
|
||||
'txt': 'txt.png',
|
||||
|
||||
// pdf file
|
||||
'pdf' : 'pdf.png',
|
||||
|
||||
// document file
|
||||
'doc' : 'word.png',
|
||||
'docx' : 'word.png',
|
||||
'odt' : 'word.png',
|
||||
'fodt' : 'word.png',
|
||||
|
||||
'ppt' : 'ppt.png',
|
||||
'pptx' : 'ppt.png',
|
||||
'odp' : 'ppt.png',
|
||||
'fodp' : 'ppt.png',
|
||||
|
||||
'xls' : 'excel.png',
|
||||
'xlsx' : 'excel.png',
|
||||
'ods' : 'excel.png',
|
||||
'fods' : 'excel.png',
|
||||
|
||||
// video
|
||||
'mp4': 'video.png',
|
||||
'ogv': 'video.png',
|
||||
'webm': 'video.png',
|
||||
'mov': 'video.png',
|
||||
'flv': 'video.png',
|
||||
'wmv': 'video.png',
|
||||
'rmvb': 'video.png',
|
||||
|
||||
// music file
|
||||
'mp3' : 'music.png',
|
||||
'oga' : 'music.png',
|
||||
'ogg' : 'music.png',
|
||||
'flac' : 'music.png',
|
||||
'aac' : 'music.png',
|
||||
'ac3' : 'music.png',
|
||||
'wma' : 'music.png',
|
||||
|
||||
// image file
|
||||
'jpg' : 'pic.png',
|
||||
'jpeg' : 'pic.png',
|
||||
'png' : 'pic.png',
|
||||
'svg' : 'pic.png',
|
||||
'gif' : 'pic.png',
|
||||
'bmp' : 'pic.png',
|
||||
'ico' : 'pic.png',
|
||||
|
||||
// default
|
||||
'default' : 'file.png'
|
||||
},
|
||||
|
||||
getFileIconUrl: function(filename, size) {
|
||||
if (size > 24) {
|
||||
size = 192;
|
||||
} else {
|
||||
size = 24;
|
||||
}
|
||||
|
||||
var file_ext;
|
||||
if (filename.lastIndexOf('.') == -1) {
|
||||
return mediaUrl + "img/file/" + size + "/"
|
||||
+ this.FILEEXT_ICON_MAP['default'];
|
||||
} else {
|
||||
file_ext = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase();
|
||||
}
|
||||
|
||||
if (this.FILEEXT_ICON_MAP[file_ext]) {
|
||||
return mediaUrl + "img/file/" + size + "/" + this.FILEEXT_ICON_MAP[file_ext];
|
||||
} else {
|
||||
return mediaUrl + "img/file/" + size + "/" + this.FILEEXT_ICON_MAP['default'];
|
||||
}
|
||||
},
|
||||
|
||||
// check if a file is an image
|
||||
imageCheck: function (filename) {
|
||||
// no file ext
|
||||
if (filename.lastIndexOf('.') == -1) {
|
||||
return false;
|
||||
}
|
||||
var file_ext = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase();
|
||||
var image_exts = ['gif', 'jpeg', 'jpg', 'png', 'ico', 'bmp'];
|
||||
if (image_exts.indexOf(file_ext) != -1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// check if a file is a video
|
||||
videoCheck: function (filename) {
|
||||
// no file ext
|
||||
if (filename.lastIndexOf('.') == -1) {
|
||||
return false;
|
||||
}
|
||||
var file_ext = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase();
|
||||
var exts = ['mp4', 'ogv', 'webm', 'mov'];
|
||||
if (exts.indexOf(file_ext) != -1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
encodePath: function(path) {
|
||||
// IE8 does not support 'map()'
|
||||
/*
|
||||
return path.split('/').map(function(e) {
|
||||
return encodeURIComponent(e);
|
||||
}).join('/');
|
||||
*/
|
||||
|
||||
var path_arr = path.split('/'),
|
||||
path_arr_ = [];
|
||||
for (var i = 0, len = path_arr.length; i < len; i++) {
|
||||
path_arr_.push(encodeURIComponent(path_arr[i]));
|
||||
}
|
||||
return path_arr_.join('/');
|
||||
}
|
||||
};
|
@@ -50,6 +50,7 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.sf2-icon-delete:before { content:"\e006"; }
|
||||
.sf2-icon-menu:before { content: "\e031"; }
|
||||
.sf2-icon-more:before { content: "\e032"; }
|
||||
.sf2-icon-x1:before { content:"\e01d"; }
|
||||
@@ -80,6 +81,7 @@ a:hover { color:#eb8205; }
|
||||
.flex-auto { flex:auto; }
|
||||
.flex-1 { flex:1; }
|
||||
.hide { display:none; }
|
||||
.error { color:red; }
|
||||
.no-deco,
|
||||
.no-deco:hover,
|
||||
.no-deco:focus {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{% load seahub_tags avatar_tags group_avatar_tags i18n staticfiles %}
|
||||
{% load seahub_tags i18n staticfiles %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ LANGUAGE_CODE }}">
|
||||
@@ -29,6 +29,7 @@
|
||||
logoHeight: '{{ logo_height }}',
|
||||
siteTitle: '{{ site_title }}',
|
||||
siteRoot: '{{ SITE_ROOT }}',
|
||||
loginUrl: '{{ LOGIN_URL }}',
|
||||
isPro: '{{ is_pro }}',
|
||||
lang: '{{ LANGUAGE_CODE }}',
|
||||
fileServerRoot: '{{ FILE_SERVER_ROOT }}'
|
||||
|
@@ -38,7 +38,7 @@
|
||||
<h3 class="fleft">{% trans "Tools" %}</h3>
|
||||
</div>
|
||||
<ul class="side-tabnav-tabs">
|
||||
<li class="tab"><a href="{{ SITE_ROOT }}#starred/"><span class="sf2-icon-star" aria-hidden="true"></span>{% trans "Favorites" %}</a></li>
|
||||
<li class="tab"><a href="{{ SITE_ROOT }}starred/"><span class="sf2-icon-star" aria-hidden="true"></span>{% trans "Favorites" %}</a></li>
|
||||
{% if events_enabled %}
|
||||
<li class="tab"><a href="{{ SITE_ROOT }}dashboard/"><span class="sf2-icon-clock" aria-hidden="true"></span>{% trans "Activities" %}</a></li>
|
||||
{% endif %}
|
||||
|
@@ -1524,7 +1524,7 @@
|
||||
</div>
|
||||
<ul class="side-tabnav-tabs">
|
||||
<li class="tab<% if (cur_tab == 'starred') { %> tab-cur<% } %>">
|
||||
<a href="{{ SITE_ROOT }}#starred/"><span aria-hidden="true" class="sf2-icon-star"></span>{% trans "Favorites" %}</a>
|
||||
<a href="{{ SITE_ROOT }}starred/"><span aria-hidden="true" class="sf2-icon-star"></span>{% trans "Favorites" %}</a>
|
||||
</li>
|
||||
{% if events_enabled %}
|
||||
<li class="tab<% if (cur_tab == 'activities') { %> tab-cur<% } %>">
|
||||
|
@@ -182,8 +182,11 @@ urlpatterns = [
|
||||
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'^choose_register/$', choose_register, name="choose_register"),
|
||||
url(r'^dashboard/$', TemplateView.as_view(template_name="react_app.html"), name="app"),
|
||||
url(r'^drafts/$', TemplateView.as_view(template_name="react_app.html"), name="app"),
|
||||
|
||||
### React ###
|
||||
url(r'^dashboard/$', TemplateView.as_view(template_name="react_app.html"), name="dashboard"),
|
||||
url(r'^drafts/$', TemplateView.as_view(template_name="react_app.html"), name="drafts"),
|
||||
url(r'^starred/$', TemplateView.as_view(template_name="react_app.html"), name="starred"),
|
||||
|
||||
### Ajax ###
|
||||
url(r'^ajax/repo/(?P<repo_id>[-0-9a-f]{36})/dirents/$', get_dirents, name="get_dirents"),
|
||||
|
Reference in New Issue
Block a user