mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-04 16:31:13 +00:00
update image dialog, add download, delete and view original image button (#6670)
* update image dialog, add download, delete and view original image button * update react-image-lightbox * add-image-rotate-api * support restore in image previewer * support image rotate * upgrade react-image-lightbox * fix bug - first image auto previewed in list layout, improve delete and restore action * remove raise * fix last line --------- Co-authored-by: r350178982 <32759763+r350178982@users.noreply.github.com> Co-authored-by: Michael An <2331806369@qq.com>
This commit is contained in:
@@ -2,4 +2,6 @@ export const EVENT_BUS_TYPE = {
|
||||
// library
|
||||
CURRENT_LIBRARY_CHANGED: 'current_library_changed',
|
||||
SEARCH_LIBRARY_CONTENT: 'search_library_content',
|
||||
|
||||
RESTORE_IMAGE: 'restore_image',
|
||||
};
|
||||
|
@@ -9,11 +9,38 @@ const propTypes = {
|
||||
imageIndex: PropTypes.number.isRequired,
|
||||
closeImagePopup: PropTypes.func.isRequired,
|
||||
moveToPrevImage: PropTypes.func.isRequired,
|
||||
moveToNextImage: PropTypes.func.isRequired
|
||||
moveToNextImage: PropTypes.func.isRequired,
|
||||
onDeleteImage: PropTypes.func,
|
||||
onRotateImage: PropTypes.func,
|
||||
};
|
||||
|
||||
class ImageDialog extends React.Component {
|
||||
|
||||
downloadImage = (imageSrc) => {
|
||||
let downloadUrl = imageSrc.indexOf('?dl=1') > -1 ? imageSrc : imageSrc + '?dl=1';
|
||||
|
||||
if (document.getElementById('downloadFrame')) {
|
||||
document.body.removeChild(document.getElementById('downloadFrame'));
|
||||
}
|
||||
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('id', 'downloadFrame');
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = downloadUrl;
|
||||
document.body.appendChild(iframe);
|
||||
};
|
||||
|
||||
onViewOriginal = () => {
|
||||
const imageSrc = this.props.imageItems[this.props.imageIndex].url;
|
||||
window.open(imageSrc, '_blank');
|
||||
};
|
||||
|
||||
onRotateImage = (angle) => {
|
||||
if (this.props.onRotateImage) {
|
||||
this.props.onRotateImage(this.props.imageIndex, angle);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const imageItems = this.props.imageItems;
|
||||
const imageIndex = this.props.imageIndex;
|
||||
@@ -23,6 +50,7 @@ class ImageDialog extends React.Component {
|
||||
|
||||
return (
|
||||
<Lightbox
|
||||
wrapperClassName='custom-image-previewer'
|
||||
imageTitle={imageTitle}
|
||||
mainSrc={imageItems[imageIndex].src}
|
||||
nextSrc={imageItems[(imageIndex + 1) % imageItemsLength].src}
|
||||
@@ -37,6 +65,12 @@ class ImageDialog extends React.Component {
|
||||
closeLabel={gettext('Close (Esc)')}
|
||||
zoomInLabel={gettext('Zoom in')}
|
||||
zoomOutLabel={gettext('Zoom out')}
|
||||
enableRotate={true}
|
||||
onClickDownload={() => this.downloadImage(imageItems[imageIndex].url)}
|
||||
onClickDelete={this.props.onDeleteImage ? () => this.props.onDeleteImage(imageItems[imageIndex].name) : null}
|
||||
onViewOriginal={this.onViewOriginal}
|
||||
viewOriginalImageLabel={gettext('View original image')}
|
||||
onRotateImage={this.onRotateImage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@@ -40,6 +40,7 @@ const propTypes = {
|
||||
selectedDirentList: PropTypes.array.isRequired,
|
||||
onItemsMove: PropTypes.func.isRequired,
|
||||
getMenuContainerSize: PropTypes.func,
|
||||
direntList: PropTypes.array
|
||||
};
|
||||
|
||||
class DirColumnNav extends React.Component {
|
||||
@@ -61,12 +62,29 @@ class DirColumnNav extends React.Component {
|
||||
isDisplayFiles: false,
|
||||
};
|
||||
this.isNodeMenuShow = true;
|
||||
this.imageItemsSnapshot = [];
|
||||
this.imageIndexSnapshot = 0;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initMenuList();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.direntList.length < this.props.direntList.length && this.state.isNodeImagePopupOpen) {
|
||||
if (this.state.imageNodeItems.length === 0) {
|
||||
this.setState({
|
||||
isNodeImagePopupOpen: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
imageNodeItems: this.imageItemsSnapshot,
|
||||
imageIndex: this.imageIndexSnapshot,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initMenuList = () => {
|
||||
const menuList = this.getMenuList();
|
||||
this.setState({ operationList: menuList });
|
||||
@@ -248,7 +266,8 @@ class DirColumnNav extends React.Component {
|
||||
return {
|
||||
'name': name,
|
||||
'url': `${siteRoot}lib/${repoID}/file${path}`,
|
||||
'src': src
|
||||
'src': src,
|
||||
'node': items.find(item => item.path.split('/').pop() === name),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -275,6 +294,29 @@ class DirColumnNav extends React.Component {
|
||||
}));
|
||||
};
|
||||
|
||||
deleteImage = () => {
|
||||
this.imageItemsSnapshot = this.state.imageNodeItems;
|
||||
this.imageIndexSnapshot = this.state.imageIndex;
|
||||
|
||||
if (this.state.imageNodeItems.length > this.state.imageIndex) {
|
||||
this.props.onDeleteNode(this.state.imageNodeItems[this.state.imageIndex].node);
|
||||
}
|
||||
const imageNodeItems = this.state.imageNodeItems.filter((item, index) => index !== this.state.imageIndex);
|
||||
|
||||
if (!imageNodeItems.length) {
|
||||
this.setState({
|
||||
isNodeImagePopupOpen: false,
|
||||
imageNodeItems: [],
|
||||
imageIndex: 0
|
||||
});
|
||||
} else {
|
||||
this.setState((prevState) => ({
|
||||
imageNodeItems: imageNodeItems,
|
||||
imageIndex: (prevState.imageIndex + 1) % imageNodeItems.length,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
stopTreeScrollPropagation = (e) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
@@ -406,6 +448,7 @@ class DirColumnNav extends React.Component {
|
||||
closeImagePopup={this.closeNodeImagePopup}
|
||||
moveToPrevImage={this.moveToPrevImage}
|
||||
moveToNextImage={this.moveToNextImage}
|
||||
onDeleteImage={this.deleteImage}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
|
@@ -74,7 +74,8 @@ const propTypes = {
|
||||
showDirentDetail: PropTypes.func.isRequired,
|
||||
fullDirentList: PropTypes.array,
|
||||
onItemsScroll: PropTypes.func.isRequired,
|
||||
isDirentDetailShow: PropTypes.bool.isRequired
|
||||
isDirentDetailShow: PropTypes.bool.isRequired,
|
||||
eventBus: PropTypes.object,
|
||||
};
|
||||
|
||||
class DirColumnView extends React.Component {
|
||||
@@ -170,6 +171,7 @@ class DirColumnView extends React.Component {
|
||||
selectedDirentList={this.props.selectedDirentList}
|
||||
onItemsMove={this.props.onItemsMove}
|
||||
getMenuContainerSize={this.getMenuContainerSize}
|
||||
direntList={this.props.direntList}
|
||||
/>
|
||||
<ResizeBar
|
||||
resizeBarRef={this.resizeBarRef}
|
||||
@@ -239,6 +241,7 @@ class DirColumnView extends React.Component {
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
showDirentDetail={this.props.showDirentDetail}
|
||||
getMenuContainerSize={this.getMenuContainerSize}
|
||||
eventBus={this.props.eventBus}
|
||||
/> :
|
||||
<DirGridView
|
||||
path={this.props.path}
|
||||
@@ -274,6 +277,7 @@ class DirColumnView extends React.Component {
|
||||
onItemRename={this.props.onItemRename}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
getMenuContainerSize={this.getMenuContainerSize}
|
||||
eventBus={this.props.eventBus}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -38,6 +38,7 @@ const propTypes = {
|
||||
onItemRename: PropTypes.func.isRequired,
|
||||
fullDirentList: PropTypes.array,
|
||||
getMenuContainerSize: PropTypes.func,
|
||||
eventBus: PropTypes.object,
|
||||
};
|
||||
|
||||
class DirGridView extends React.Component {
|
||||
@@ -95,6 +96,7 @@ class DirGridView extends React.Component {
|
||||
repoTags={this.props.repoTags}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
getMenuContainerSize={this.props.getMenuContainerSize}
|
||||
eventBus={this.props.eventBus}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
@@ -42,6 +42,7 @@ const propTypes = {
|
||||
loadDirentList: PropTypes.func,
|
||||
fullDirentList: PropTypes.array,
|
||||
getMenuContainerSize: PropTypes.func,
|
||||
eventBus: PropTypes.object,
|
||||
};
|
||||
|
||||
class DirListView extends React.Component {
|
||||
@@ -105,6 +106,7 @@ class DirListView extends React.Component {
|
||||
showDirentDetail={this.props.showDirentDetail}
|
||||
loadDirentList={this.props.loadDirentList}
|
||||
getMenuContainerSize={this.props.getMenuContainerSize}
|
||||
eventBus={this.props.eventBus}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
@@ -289,8 +289,8 @@ class DirentGridItem extends React.Component {
|
||||
onDrop={this.onGridItemDragDrop}
|
||||
>
|
||||
{(this.canPreview && dirent.encoded_thumbnail_src) ?
|
||||
<img src={`${siteRoot}${fileUrl}`} ref={this.gridIcon} className="thumbnail" onClick={this.onItemClick} alt=""/> :
|
||||
<img src={iconUrl} ref={this.gridIcon} width="96" alt='' />
|
||||
<img src={`${siteRoot}${fileUrl}`} className="thumbnail" onClick={this.onItemClick} alt=""/> :
|
||||
<img src={iconUrl} width="96" alt='' />
|
||||
}
|
||||
{dirent.is_locked && <img className="grid-file-locked-icon" src={lockedImageUrl} alt={lockedMessage} title={lockedInfo}/>}
|
||||
</div>
|
||||
|
@@ -21,7 +21,9 @@ import CreateFile from '../dialog/create-file-dialog';
|
||||
import CreateFolder from '../dialog/create-folder-dialog';
|
||||
import LibSubFolderPermissionDialog from '../dialog/lib-sub-folder-permission-dialog';
|
||||
import toaster from '../toast';
|
||||
import imageAPI from '../../utils/image-api';
|
||||
import FileAccessLog from '../dialog/file-access-log';
|
||||
import { EVENT_BUS_TYPE } from '../common/event-bus-type';
|
||||
|
||||
import '../../css/grid-view.css';
|
||||
|
||||
@@ -59,6 +61,7 @@ const propTypes = {
|
||||
posY: PropTypes.number,
|
||||
dirent: PropTypes.object,
|
||||
getMenuContainerSize: PropTypes.func,
|
||||
eventBus: PropTypes.object,
|
||||
};
|
||||
|
||||
const DIRENT_GRID_CONTAINER_MENU_ID = 'dirent-grid-container-menu';
|
||||
@@ -103,10 +106,24 @@ class DirentGridView extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('mouseup', this.onGlobalMouseUp);
|
||||
this.unsubscribeEvent = this.props.eventBus.subscribe(EVENT_BUS_TYPE.RESTORE_IMAGE, this.recalculateImageItems);
|
||||
}
|
||||
|
||||
recalculateImageItems = () => {
|
||||
if (!this.state.isImagePopupOpen) return;
|
||||
let imageItems = this.props.direntList
|
||||
.filter((item) => Utils.imageCheck(item.name))
|
||||
.map((item) => this.prepareImageItem(item));
|
||||
|
||||
this.setState({
|
||||
imageItems: imageItems,
|
||||
imageIndex: this.state.imageIndex % imageItems.length,
|
||||
});
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('mouseup', this.onGlobalMouseUp);
|
||||
this.unsubscribeEvent();
|
||||
}
|
||||
|
||||
onGridContainerMouseDown = (event) => {
|
||||
@@ -550,7 +567,8 @@ class DirentGridView extends React.Component {
|
||||
const repoID = this.props.repoID;
|
||||
const path = Utils.encodePath(Utils.joinPath(this.props.path, name));
|
||||
|
||||
const src = `${siteRoot}repo/${repoID}/raw${path}`;
|
||||
const cacheBuster = new Date().getTime();
|
||||
const src = `${siteRoot}repo/${repoID}/raw${path}?t=${cacheBuster}`;
|
||||
|
||||
return {
|
||||
'name': name,
|
||||
@@ -593,6 +611,50 @@ class DirentGridView extends React.Component {
|
||||
}));
|
||||
};
|
||||
|
||||
deleteImage = (name) => {
|
||||
const item = this.props.fullDirentList.find((item) => item.name === name);
|
||||
this.props.onItemDelete(item);
|
||||
|
||||
const newImageItems = this.props.fullDirentList
|
||||
.filter((item) => item.name !== name && Utils.imageCheck(item.name))
|
||||
.map((item) => this.prepareImageItem(item));
|
||||
|
||||
this.setState((prevState) => ({
|
||||
isImagePopupOpen: newImageItems.length > 0,
|
||||
imageItems: newImageItems,
|
||||
imageIndex: prevState.imageIndex % newImageItems.length,
|
||||
}));
|
||||
};
|
||||
|
||||
rotateImage = (imageIndex, angle) => {
|
||||
if (imageIndex >= 0 && angle !== 0) {
|
||||
const path = this.state.path === '/' ? this.props.path + this.state.imageItems[imageIndex].name : this.props.path + '/' + this.state.imageItems[imageIndex].name;
|
||||
imageAPI.rotateImage(this.props.repoID, path, 360 - angle).then((res) => {
|
||||
seafileAPI.createThumbnail(this.props.repoID, path, 48).then((res) => {
|
||||
// Generate a unique query parameter to bust the cache
|
||||
const cacheBuster = new Date().getTime();
|
||||
const newThumbnailSrc = `${res.data.encoded_thumbnail_src}?t=${cacheBuster}`;
|
||||
|
||||
this.setState((prevState) => {
|
||||
const updatedImageItems = [...prevState.imageItems];
|
||||
updatedImageItems[imageIndex].src = newThumbnailSrc;
|
||||
return { imageItems: updatedImageItems };
|
||||
});
|
||||
|
||||
// Update the thumbnail URL with the cache-busting query parameter
|
||||
const item = this.props.direntList.find((item) => item.name === this.state.imageItems[imageIndex].name);
|
||||
this.props.updateDirent(item, 'encoded_thumbnail_src', newThumbnailSrc);
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
checkDuplicatedName = (newName) => {
|
||||
return Utils.checkDuplicatedNameInList(this.props.direntList, newName);
|
||||
};
|
||||
@@ -896,7 +958,7 @@ class DirentGridView extends React.Component {
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
{this.state.isImagePopupOpen && (
|
||||
{this.state.isImagePopupOpen && this.state.imageItems.length && (
|
||||
<ModalPortal>
|
||||
<ImageDialog
|
||||
imageItems={this.state.imageItems}
|
||||
@@ -904,6 +966,8 @@ class DirentGridView extends React.Component {
|
||||
closeImagePopup={this.closeImagePopup}
|
||||
moveToPrevImage={this.moveToPrevImage}
|
||||
moveToNextImage={this.moveToNextImage}
|
||||
onDeleteImage={this.deleteImage}
|
||||
onRotateImage={this.rotateImage}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
|
@@ -17,6 +17,7 @@ import DirentListItem from './dirent-list-item';
|
||||
import ContextMenu from '../context-menu/context-menu';
|
||||
import { hideMenu, showMenu } from '../context-menu/actions';
|
||||
import DirentsDraggedPreview from '../draggable/dirents-dragged-preview';
|
||||
import { EVENT_BUS_TYPE } from '../common/event-bus-type';
|
||||
|
||||
const propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
@@ -55,6 +56,7 @@ const propTypes = {
|
||||
posX: PropTypes.string,
|
||||
posY: PropTypes.string,
|
||||
getMenuContainerSize: PropTypes.func,
|
||||
eventBus: PropTypes.object,
|
||||
};
|
||||
|
||||
class DirentListView extends React.Component {
|
||||
@@ -99,6 +101,26 @@ class DirentListView extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubscribeEvent = this.props.eventBus.subscribe(EVENT_BUS_TYPE.RESTORE_IMAGE, this.recalculateImageItems);
|
||||
}
|
||||
|
||||
recalculateImageItems = () => {
|
||||
if (!this.state.isImagePopupOpen) return;
|
||||
let imageItems = this.props.direntList
|
||||
.filter((item) => Utils.imageCheck(item.name))
|
||||
.map((item) => this.prepareImageItem(item));
|
||||
|
||||
this.setState({
|
||||
imageItems: imageItems,
|
||||
imageIndex: this.state.imageIndex % imageItems.length,
|
||||
});
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribeEvent();
|
||||
}
|
||||
|
||||
freezeItem = () => {
|
||||
this.setState({ isItemFreezed: true });
|
||||
};
|
||||
@@ -199,6 +221,21 @@ class DirentListView extends React.Component {
|
||||
}));
|
||||
};
|
||||
|
||||
deleteImage = (name) => {
|
||||
const item = this.props.fullDirentList.find((item) => item.name === name);
|
||||
this.props.onItemDelete(item);
|
||||
|
||||
const newImageItems = this.props.fullDirentList
|
||||
.filter((item) => item.name !== name && Utils.imageCheck(item.name))
|
||||
.map((item) => this.prepareImageItem(item));
|
||||
|
||||
this.setState((prevState) => ({
|
||||
isImagePopupOpen: newImageItems.length > 0,
|
||||
imageItems: newImageItems,
|
||||
imageIndex: prevState.imageIndex % newImageItems.length,
|
||||
}));
|
||||
};
|
||||
|
||||
closeImagePopup = () => {
|
||||
this.setState({ isImagePopupOpen: false });
|
||||
};
|
||||
@@ -727,6 +764,7 @@ class DirentListView extends React.Component {
|
||||
closeImagePopup={this.closeImagePopup}
|
||||
moveToPrevImage={this.moveToPrevImage}
|
||||
moveToNextImage={this.moveToNextImage}
|
||||
onDeleteImage={this.deleteImage}
|
||||
/>
|
||||
</ModalPortal>
|
||||
)}
|
||||
|
Reference in New Issue
Block a user