diff --git a/frontend/src/components/file-content-view/image.js b/frontend/src/components/file-content-view/image.js
index 076c52195b..ca9ae41026 100644
--- a/frontend/src/components/file-content-view/image.js
+++ b/frontend/src/components/file-content-view/image.js
@@ -61,16 +61,25 @@ class FileContent extends React.Component {
thumbnailURL = `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForOriginal}${Utils.encodePath(filePath)}?mtime=${lastModificationTime}`;
}
- 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)` };
+ const { scale, angle, offset } = this.props;
+ let transforms = [];
+
+ if (scale) {
+ transforms.push(`scale(${scale})`);
+ }
+ if (angle !== undefined) {
+ transforms.push(`rotate(${angle}deg)`);
}
+ let style = {};
+ if (transforms.length > 0) {
+ style.transform = transforms.join(' ');
+ }
+ if (scale > 1 && offset && typeof offset === 'object') {
+ if (offset.x !== undefined) style.left = offset.x;
+ if (offset.y !== undefined) style.top = offset.y;
+ style.position = 'relative';
+ }
return (
@@ -89,7 +98,8 @@ class FileContent extends React.Component {
FileContent.propTypes = {
tip: PropTypes.object.isRequired,
scale: PropTypes.number,
- angle: PropTypes.number
+ angle: PropTypes.number,
+ offset: PropTypes.object,
};
export default FileContent;
diff --git a/frontend/src/file-view.js b/frontend/src/file-view.js
index b9c3e8c8bf..c903dc0803 100644
--- a/frontend/src/file-view.js
+++ b/frontend/src/file-view.js
@@ -22,14 +22,20 @@ class InnerFileView extends React.Component {
super();
this.state = {
imageScale: 1,
- imageAngle: 0
+ imageAngle: 0,
+ imageOffset: { x: 0, y: 0 },
+ isDragging: false,
+ dragStart: null,
+ dragOrigin: null,
};
+ this.imageContainerRef = React.createRef();
}
setImageScale = (scale) => {
- this.setState({
- imageScale: scale
- });
+ this.setState(prevState => ({
+ imageScale: scale,
+ imageOffset: scale === 1 ? { x: 0, y: 0 } : prevState.imageOffset,
+ }));
};
rotateImage = () => {
@@ -46,6 +52,36 @@ class InnerFileView extends React.Component {
});
};
+ handleImageMouseDown = (e) => {
+ if (this.state.imageScale <= 1) return;
+ e.preventDefault();
+ this.setState({
+ isDragging: true,
+ dragStart: { x: e.clientX, y: e.clientY },
+ dragOrigin: { ...this.state.imageOffset }
+ });
+ document.addEventListener('mousedown', this.handleImageMouseDown);
+ document.addEventListener('mouseup', this.handleImageMouseUp);
+ };
+
+ handleImageMouseMove = (e) => {
+ if (!this.state.isDragging) return;
+ const dx = e.clientX - this.state.dragStart.x;
+ const dy = e.clientY - this.state.dragStart.y;
+ this.setState({
+ imageOffset: {
+ x: this.state.dragOrigin.x + dx,
+ y: this.state.dragOrigin.y + dy
+ }
+ });
+ };
+
+ handleImageMouseUp = (e) => {
+ this.setState({ isDragging: false });
+ document.removeEventListener('mousedown', this.handleImageMouseDown);
+ document.removeEventListener('mouseup', this.handleImageMouseUp);
+ };
+
render() {
if (err) {
return (
@@ -53,11 +89,27 @@ class InnerFileView extends React.Component {
);
}
- const { imageScale, imageAngle } = this.state;
+ const { imageScale, imageAngle, imageOffset } = this.state;
let content;
switch (fileType) {
case 'Image':
- content =
} scale={imageScale} angle={imageAngle} />;
+ content = (
+
1 ? 'move' : 'default' }}
+ onMouseDown={this.handleImageMouseDown}
+ onMouseMove={this.handleImageMouseMove}
+ onMouseUp={this.handleImageMouseUp}
+ >
+ }
+ scale={imageScale}
+ angle={imageAngle}
+ offset={imageOffset}
+ />
+
+ );
break;
case 'SVG':
content =
;