1
0
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:
llj
2018-10-08 15:33:40 +08:00
committed by Daniel Pan
parent fb0fc6f7ec
commit 237455f1a8
10 changed files with 439 additions and 9 deletions

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View 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;

View 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('/');
}
};

View File

@@ -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 {

View File

@@ -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 }}'

View File

@@ -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 %}

View File

@@ -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<% } %>">

View File

@@ -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"),