mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 23:48:47 +00:00
[view shared dir] show thumbnail icon & show file with popup for image files (#3224)
This commit is contained in:
57
frontend/src/components/dialog/image-dialog.js
Normal file
57
frontend/src/components/dialog/image-dialog.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
|
||||||
|
import Lightbox from 'react-image-lightbox';
|
||||||
|
import 'react-image-lightbox/style.css';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
imageItems: PropTypes.array.isRequired,
|
||||||
|
imageIndex: PropTypes.number.isRequired,
|
||||||
|
closeImagePopup: PropTypes.func.isRequired,
|
||||||
|
moveToPrevImage: PropTypes.func.isRequired,
|
||||||
|
moveToNextImage: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageDialog extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const imageItems = this.props.imageItems;
|
||||||
|
const imageIndex = this.props.imageIndex;
|
||||||
|
const imageItemsLength = imageItems.length;
|
||||||
|
const imageCaption = imageItemsLength && (
|
||||||
|
<Fragment>
|
||||||
|
<span>{gettext('%curr% of %total%').replace('%curr%', imageIndex + 1).replace('%total%', imageItemsLength)}</span>
|
||||||
|
<br />
|
||||||
|
<a href={imageItems[imageIndex].url} target="_blank">{gettext('Open in New Tab')}</a>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Lightbox
|
||||||
|
mainSrc={imageItems[imageIndex].src}
|
||||||
|
imageTitle={imageItems[imageIndex].name}
|
||||||
|
imageCaption={imageCaption}
|
||||||
|
nextSrc={imageItems[(imageIndex + 1) % imageItemsLength].src}
|
||||||
|
prevSrc={imageItems[(imageIndex + imageItemsLength - 1) % imageItemsLength].src}
|
||||||
|
onCloseRequest={this.props.closeImagePopup}
|
||||||
|
onMovePrevRequest={this.props.moveToPrevImage}
|
||||||
|
onMoveNextRequest={this.props.moveToNextImage}
|
||||||
|
imagePadding={70}
|
||||||
|
imageLoadErrorMessage={gettext('The image could not be loaded.')}
|
||||||
|
prevLabel={gettext('Previous (Left arrow key)')}
|
||||||
|
nextLabel={gettext('Next (Right arrow key)')}
|
||||||
|
closeLabel={gettext('Close (Esc)')}
|
||||||
|
zoomInLabel={gettext('Zoom in')}
|
||||||
|
zoomOutLabel={gettext('Zoom out')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default ImageDialog;
|
@@ -166,7 +166,7 @@ class Item extends Component {
|
|||||||
|
|
||||||
const desktopItem = (
|
const desktopItem = (
|
||||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||||
<td className="alc">
|
<td className="text-center">
|
||||||
{
|
{
|
||||||
data.thumbnail_url ?
|
data.thumbnail_url ?
|
||||||
<img className="thumbnail" src={data.thumbnail_url} alt="" /> :
|
<img className="thumbnail" src={data.thumbnail_url} alt="" /> :
|
||||||
@@ -189,7 +189,7 @@ class Item extends Component {
|
|||||||
|
|
||||||
const mobileItem = (
|
const mobileItem = (
|
||||||
<tr>
|
<tr>
|
||||||
<td className="alc">
|
<td className="text-center">
|
||||||
{
|
{
|
||||||
data.thumbnail_url ?
|
data.thumbnail_url ?
|
||||||
<img className="thumbnail" src={data.thumbnail_url} alt="" /> :
|
<img className="thumbnail" src={data.thumbnail_url} alt="" /> :
|
||||||
|
@@ -3,13 +3,14 @@ import ReactDOM from 'react-dom';
|
|||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Account from './components/common/account';
|
import Account from './components/common/account';
|
||||||
import { gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle } from './utils/constants';
|
import { gettext, siteRoot, mediaUrl, logoPath, logoWidth, logoHeight, siteTitle, thumbnailSizeForOriginal } from './utils/constants';
|
||||||
import { Utils } from './utils/utils';
|
import { Utils } from './utils/utils';
|
||||||
import { seafileAPI } from './utils/seafile-api';
|
import { seafileAPI } from './utils/seafile-api';
|
||||||
import Loading from './components/loading';
|
import Loading from './components/loading';
|
||||||
import toaster from './components/toast';
|
import toaster from './components/toast';
|
||||||
import ModalPortal from './components/modal-portal';
|
import ModalPortal from './components/modal-portal';
|
||||||
import ShareLinkZipDownloadDialog from './components/dialog/share-link-zip-download-dialog';
|
import ShareLinkZipDownloadDialog from './components/dialog/share-link-zip-download-dialog';
|
||||||
|
import ImageDialog from './components/dialog/image-dialog';
|
||||||
|
|
||||||
import './css/shared-dir-view.css';
|
import './css/shared-dir-view.css';
|
||||||
|
|
||||||
@@ -26,8 +27,13 @@ class SharedDirView extends React.Component {
|
|||||||
isLoading: true,
|
isLoading: true,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
items: [],
|
items: [],
|
||||||
|
|
||||||
isZipDialogOpen: false,
|
isZipDialogOpen: false,
|
||||||
zipFolderPath: ''
|
zipFolderPath: '',
|
||||||
|
|
||||||
|
isImagePopupOpen: false,
|
||||||
|
imageItems: [],
|
||||||
|
imageIndex: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +51,7 @@ class SharedDirView extends React.Component {
|
|||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
items: items
|
items: items
|
||||||
});
|
});
|
||||||
|
this.getThumbnails();
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
let errorMsg = '';
|
let errorMsg = '';
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
@@ -63,6 +70,37 @@ class SharedDirView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getThumbnails = () => {
|
||||||
|
let items = this.state.items.filter((item) => {
|
||||||
|
return !item.is_dir && Utils.imageCheck(item.file_name) && !item.encoded_thumbnail_src;
|
||||||
|
});
|
||||||
|
if (items.length == 0) {
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
|
const len = items.length;
|
||||||
|
const thumbnailSize = 48;
|
||||||
|
const _this = this;
|
||||||
|
let getThumbnail = function(i) {
|
||||||
|
const curItem = items[i];
|
||||||
|
seafileAPI.getShareLinkThumbnail(token, curItem.file_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);
|
||||||
|
}
|
||||||
|
|
||||||
renderPath = () => {
|
renderPath = () => {
|
||||||
// path: '/', or '/g/'
|
// path: '/', or '/g/'
|
||||||
if (path == '/') {
|
if (path == '/') {
|
||||||
@@ -104,6 +142,62 @@ class SharedDirView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for image popup
|
||||||
|
prepareImageItem = (item) => {
|
||||||
|
const name = item.file_name;
|
||||||
|
const fileExt = name.substr(name.lastIndexOf('.') + 1).toLowerCase();
|
||||||
|
const isGIF = fileExt == 'gif';
|
||||||
|
|
||||||
|
let src;
|
||||||
|
const fileURL = `${siteRoot}d/${token}/files/?p=${encodeURIComponent(item.file_path)}`;
|
||||||
|
if (!isGIF) {
|
||||||
|
src = `${siteRoot}thumbnail/${token}/${thumbnailSizeForOriginal}${Utils.encodePath(item.file_path)}`;
|
||||||
|
} else {
|
||||||
|
src = `${fileURL}&raw=1`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'name': name,
|
||||||
|
'url': fileURL,
|
||||||
|
'src': src
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
showImagePopup = (curItem) => {
|
||||||
|
const items = this.state.items.filter((item) => {
|
||||||
|
return !item.is_dir && Utils.imageCheck(item.file_name);
|
||||||
|
});
|
||||||
|
const imageItems = items.map((item) => {
|
||||||
|
return this.prepareImageItem(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isImagePopupOpen: true,
|
||||||
|
imageItems: imageItems,
|
||||||
|
imageIndex: items.indexOf(curItem)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeImagePopup = () => {
|
||||||
|
this.setState({
|
||||||
|
isImagePopupOpen: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
moveToPrevImage = () => {
|
||||||
|
const imageItemsLength = this.state.imageItems.length;
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
imageIndex: (prevState.imageIndex + imageItemsLength - 1) % imageItemsLength
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
moveToNextImage = () => {
|
||||||
|
const imageItemsLength = this.state.imageItems.length;
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
imageIndex: (prevState.imageIndex + 1) % imageItemsLength
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -127,6 +221,7 @@ class SharedDirView extends React.Component {
|
|||||||
<Content
|
<Content
|
||||||
data={this.state}
|
data={this.state}
|
||||||
zipDownloadFolder={this.zipDownloadFolder}
|
zipDownloadFolder={this.zipDownloadFolder}
|
||||||
|
showImagePopup={this.showImagePopup}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,6 +235,17 @@ class SharedDirView extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</ModalPortal>
|
</ModalPortal>
|
||||||
}
|
}
|
||||||
|
{this.state.isImagePopupOpen &&
|
||||||
|
<ModalPortal>
|
||||||
|
<ImageDialog
|
||||||
|
imageItems={this.state.imageItems}
|
||||||
|
imageIndex={this.state.imageIndex}
|
||||||
|
closeImagePopup={this.closeImagePopup}
|
||||||
|
moveToPrevImage={this.moveToPrevImage}
|
||||||
|
moveToNextImage={this.moveToNextImage}
|
||||||
|
/>
|
||||||
|
</ModalPortal>
|
||||||
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -174,6 +280,7 @@ class Content extends React.Component {
|
|||||||
key={index}
|
key={index}
|
||||||
item={item}
|
item={item}
|
||||||
zipDownloadFolder={this.props.zipDownloadFolder}
|
zipDownloadFolder={this.props.zipDownloadFolder}
|
||||||
|
showImagePopup={this.props.showImagePopup}
|
||||||
/>;
|
/>;
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -204,6 +311,16 @@ class Item extends React.Component {
|
|||||||
this.props.zipDownloadFolder.bind(this, this.props.item.folder_path)();
|
this.props.zipDownloadFolder.bind(this, this.props.item.folder_path)();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFileNameLinkClick = (e) => {
|
||||||
|
const item = this.props.item;
|
||||||
|
if (!Utils.imageCheck(item.file_name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.showImagePopup(item);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const item = this.props.item;
|
const item = this.props.item;
|
||||||
const { isIconShown } = this.state;
|
const { isIconShown } = this.state;
|
||||||
@@ -228,11 +345,17 @@ class Item extends React.Component {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const fileURL = `${siteRoot}d/${token}/files/?p=${encodeURIComponent(item.file_path)}`;
|
const fileURL = `${siteRoot}d/${token}/files/?p=${encodeURIComponent(item.file_path)}`;
|
||||||
|
const thumbnailURL = item.encoded_thumbnail_src ? `${siteRoot}${item.encoded_thumbnail_src}` : '';
|
||||||
return (
|
return (
|
||||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||||
<td className="text-center"><img src={Utils.getFileIconUrl(item.file_name)} alt="" width="24" /></td>
|
<td className="text-center">
|
||||||
|
{thumbnailURL ?
|
||||||
|
<img className="thumbnail" src={thumbnailURL} alt="" /> :
|
||||||
|
<img src={Utils.getFileIconUrl(item.file_name)} alt="" width="24" />
|
||||||
|
}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href={fileURL}>{item.file_name}</a>
|
<a href={fileURL} onClick={this.handleFileNameLinkClick}>{item.file_name}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{Utils.bytesToSize(item.size)}</td>
|
<td>{Utils.bytesToSize(item.size)}</td>
|
||||||
<td>{moment(item.last_modified).format('YYYY-MM-DD')}</td>
|
<td>{moment(item.last_modified).format('YYYY-MM-DD')}</td>
|
||||||
|
Reference in New Issue
Block a user