diff --git a/frontend/src/assets/icons/rotate.svg b/frontend/src/assets/icons/rotate.svg
new file mode 100644
index 0000000000..574ca7b163
--- /dev/null
+++ b/frontend/src/assets/icons/rotate.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/frontend/src/components/file-content-view/image.js b/frontend/src/components/file-content-view/image.js
index e764066cac..e6fd7a8dee 100644
--- a/frontend/src/components/file-content-view/image.js
+++ b/frontend/src/components/file-content-view/image.js
@@ -13,7 +13,8 @@ const {
xmindImageSrc // for xmind file
} = window.app.pageOptions;
-let previousImageUrl; let nextImageUrl;
+let previousImageUrl;
+let nextImageUrl;
if (previousImage) {
previousImageUrl = `${siteRoot}lib/${repoID}/file${Utils.encodePath(previousImage)}`;
}
@@ -55,7 +56,7 @@ class FileContent extends React.Component {
// request thumbnails for some files
// only for 'file view'. not for 'history/trash file view'
let thumbnailURL = '';
- const fileExtList = ['tif', 'tiff', 'psd'];
+ const fileExtList = ['tif', 'tiff', 'psd', 'heic'];
if (!repoEncrypted && fileExtList.includes(fileExt)) {
thumbnailURL = `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForOriginal}${Utils.encodePath(filePath)}`;
}
@@ -63,6 +64,17 @@ class FileContent extends React.Component {
// for xmind file
const xmindSrc = xmindImageSrc ? `${siteRoot}${xmindImageSrc}` : '';
+ const { scale, angle } = this.props;
+ let style = {};
+ if (scale && angle != undefined) {
+ style = { transform: `scale(${scale}) rotate(${angle}deg)` };
+ } else if (scale) {
+ style = { transform: `scale(${scale})` };
+ } else if (angle != undefined) {
+ style = { transform: `rotate(${angle}deg)` };
+ }
+
+
return (
{previousImage && (
@@ -71,14 +83,16 @@ class FileContent extends React.Component {
{nextImage && (
)}
-

+
);
}
}
FileContent.propTypes = {
- tip: PropTypes.string.isRequired,
+ tip: PropTypes.object.isRequired,
+ scale: PropTypes.number,
+ angle: PropTypes.number
};
export default FileContent;
diff --git a/frontend/src/components/file-view/file-toolbar.js b/frontend/src/components/file-view/file-toolbar.js
index 775ca16d15..97e9831354 100644
--- a/frontend/src/components/file-view/file-toolbar.js
+++ b/frontend/src/components/file-view/file-toolbar.js
@@ -9,6 +9,7 @@ import ShareDialog from '../dialog/share-dialog';
import { seafileAPI } from '../../utils/seafile-api';
import toaster from '../toast';
import Icon from '../../components/icon';
+import ImageZoomer from './image-zoomer';
const propTypes = {
isLocked: PropTypes.bool.isRequired,
@@ -17,13 +18,15 @@ const propTypes = {
isSaving: PropTypes.bool,
needSave: PropTypes.bool,
toggleLockFile: PropTypes.func.isRequired,
- toggleDetailsPanel: PropTypes.func.isRequired
+ toggleDetailsPanel: PropTypes.func.isRequired,
+ setImageScale: PropTypes.func,
+ rotateImage: PropTypes.func
};
const {
canLockUnlockFile,
repoID, repoName, repoEncrypted, parentDir, filePerm, filePath,
- fileType,
+ fileType, fileExt,
fileName,
canEditFile, err,
// fileEnc, // for 'edit', not undefined only for some kinds of files (e.g. text file)
@@ -119,6 +122,19 @@ class FileToolbar extends React.Component {
return (
+ {(fileType == 'Image' && !err) && (
+ <>
+
+ {['psd', 'heic'].indexOf(fileExt) == -1 && (
+
+ )}
+ >
+ )}
{fileType == 'PDF' && (
}
diff --git a/frontend/src/components/file-view/image-zoomer.js b/frontend/src/components/file-view/image-zoomer.js
new file mode 100644
index 0000000000..bbf43e4819
--- /dev/null
+++ b/frontend/src/components/file-view/image-zoomer.js
@@ -0,0 +1,63 @@
+import React, { useState, useCallback } from 'react';
+import PropTypes from 'prop-types';
+import { Button, Input } from 'reactstrap';
+import Icon from '../../components/icon';
+
+import '../../metadata/components/data-process-setter/gallery-slider-setter/index.css';
+
+const SCALE_OPTIONS = [0.25, 0.5, 1, 1.5, 2];
+const SCALE_MIN = SCALE_OPTIONS[0];
+const SCALE_MAX = SCALE_OPTIONS[SCALE_OPTIONS.length - 1];
+
+const ImageZoomer = ({ setImageScale }) => {
+
+ const [curScale, setScale] = useState(1);
+
+ const scaleImage = useCallback((scale) => {
+ setImageScale(scale);
+ }, [setImageScale]);
+
+ const changeScale = useCallback((e) => {
+ const scale = Number(e.target.value);
+ setScale(scale);
+ scaleImage(scale);
+ }, [scaleImage]);
+
+ const zoomIn = useCallback(() => {
+ const scale = SCALE_OPTIONS[SCALE_OPTIONS.indexOf(curScale) + 1];
+ setScale(scale);
+ scaleImage(scale);
+ }, [curScale, scaleImage]);
+
+ const zoomOut = useCallback(() => {
+ const scale = SCALE_OPTIONS[SCALE_OPTIONS.indexOf(curScale) - 1];
+ setScale(scale);
+ scaleImage(scale);
+ }, [curScale, scaleImage]);
+
+ return (
+
+
+
+
+
+ );
+};
+
+ImageZoomer.propTypes = {
+ setImageScale: PropTypes.func
+};
+
+export default ImageZoomer;
diff --git a/frontend/src/css/image-file-view.css b/frontend/src/css/image-file-view.css
index 66fdd2dfe7..182401383f 100644
--- a/frontend/src/css/image-file-view.css
+++ b/frontend/src/css/image-file-view.css
@@ -35,6 +35,7 @@
background: #fff;
border-radius: 100%;
line-height: 50px;
+ z-index: 1;
}
#img-prev {
@@ -49,3 +50,7 @@
#img-next:hover {
color: #212529;
}
+
+.image-zoomer {
+ height: 24px;
+}
diff --git a/frontend/src/file-view.js b/frontend/src/file-view.js
index ea06082fee..0780e05fa3 100644
--- a/frontend/src/file-view.js
+++ b/frontend/src/file-view.js
@@ -7,12 +7,44 @@ import SVG from './components/file-content-view/svg';
import PDF from './components/file-content-view/pdf';
import Video from './components/file-content-view/video';
import Audio from './components/file-content-view/audio';
+import { Utils } from './utils/utils';
+import { gettext } from './utils/constants';
+import ImageAPI from './utils/image-api';
+import toaster from './components/toast';
const {
+ repoID, filePath,
fileType, err
} = window.app.pageOptions;
class InnerFileView extends React.Component {
+ constructor() {
+ super();
+ this.state = {
+ imageScale: 1,
+ imageAngle: 0
+ };
+ }
+
+ setImageScale = (scale) => {
+ this.setState({
+ imageScale: scale
+ });
+ };
+
+ rotateImage = () => {
+ this.setState({
+ imageAngle: (this.state.imageAngle - 90) % 360 // counter-clockwise
+ }, () => {
+ // const angleClockwise = this.state.imageAngle + 360; // keep this line for the moment
+ const angleClockwise = 270; // the API only accept clockwise angles
+ ImageAPI.rotateImage(repoID, filePath, 360 - angleClockwise).then((res) => {
+ toaster.success(gettext('Image saved'), { 'id': 'image-saved-tip' });
+ }).catch(error => {
+ toaster.danger(Utils.getErrorMsg(error));
+ });
+ });
+ };
render() {
if (err) {
@@ -21,10 +53,11 @@ class InnerFileView extends React.Component {
);
}
+ const { imageScale, imageAngle } = this.state;
let content;
switch (fileType) {
case 'Image':
- content = } />;
+ content = } scale={imageScale} angle={imageAngle} />;
break;
case 'XMind':
content = } />;
@@ -46,7 +79,11 @@ class InnerFileView extends React.Component {
}
return (
-
+
);
}
}