Skip to content

Commit

Permalink
ISSUE #5198 - plain text works, but callout is not immidiately shaped
Browse files Browse the repository at this point in the history
  • Loading branch information
Amantini1997 committed Oct 24, 2024
1 parent debb9b2 commit a85dba9
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 76 deletions.
1 change: 0 additions & 1 deletion frontend/src/v4/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ module.exports = {
'brace-style': ['error', '1tbs'],
'block-scoped-var': 'error',
'comma-style': ['error', 'last'],
curly: 'error',
eqeqeq: 'error',
'import/order': [
'error',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ import { isEmpty } from 'lodash';
import { renderWhenTrue } from '../../../../../../helpers/rendering';
import { batchGroupBy } from '../../../../../../modules/canvasHistory/canvasHistory.helpers';
import { COLOR } from '../../../../../../styles';
import { MODES } from '../../../markupStage/markupStage.helpers';

import { SHAPE_TYPES } from '../../shape/shape.constants';
import { TypingHandler } from '../../typingHandler/typingHandler.component';
import { TypingCalloutHandler } from '../../typingHandler/typingCalloutHandler.component';
import { createDrawnLine, createShape, getDrawFunction } from '../drawingHandler.helpers';
import {
HandleBaseDrawing, IHandleBaseDrawingProps, IHandleBaseDrawingStates,
Expand Down Expand Up @@ -171,37 +170,35 @@ export class HandleCalloutDrawing
}

public handleMouseMoveLine = () => {
if (this.state.isCurrentlyDrawn) {
if (isEmpty(this.lastShape)) {
this.layer.clearBeforeDraw();
const { x, y } = this.pointerPosition;

this.lastPointerPosition = this.initialPointerPosition = {
x,
y
};

const initialPositionProps = {
x: this.lastPointerPosition.x,
y: this.initialPointerPosition.y
};

const commonProps = {
stroke: this.props.color,
strokeWidth: this.props.size,
draggable: false,
fill: COLOR.WHITE,
};

this.lastShape = createShape(SHAPE_TYPES.RECTANGLE, commonProps, initialPositionProps);
this.layer.add(this.lastShape);
this.setState({
lastShape : this.lastShape,
});
} else {
this.lastLine.points(getLinePoints(this.shape, this.lastShape));
this.layer.batchDraw();
}
if (this.state.isCurrentlyDrawn && isEmpty(this.lastShape)) {
this.layer.clearBeforeDraw();
const { x, y } = this.pointerPosition;

this.lastPointerPosition = this.initialPointerPosition = {
x,
y
};

const initialPositionProps = {
x: this.lastPointerPosition.x,
y: this.initialPointerPosition.y
};

const commonProps = {
stroke: this.props.color,
strokeWidth: this.props.size,
draggable: false,
fill: COLOR.WHITE,
};

this.lastShape = createShape(SHAPE_TYPES.RECTANGLE, commonProps, initialPositionProps);
this.layer.add(this.lastShape);
this.setState({
lastShape : this.lastShape,
});
} else {
this.lastLine.points(getLinePoints(this.shape, this.lastShape));
this.layer.batchDraw();
}
}

Expand Down Expand Up @@ -297,21 +294,26 @@ export class HandleCalloutDrawing
}

public renderEditableTextarea = renderWhenTrue(() => (
<TypingHandler
mode={MODES.TEXT}
<>
{console.log(this.lastShape)}
<TypingCalloutHandler
stage={this.props.stage}
layer={this.layer}
color={this.props.color}
fontSize={this.props.textSize}
size={this.props.size}
onRefreshDrawingLayer={this.handleRefreshDrawingLayer}
onAddNewText={this.addText}
selected={this.props.selected}
boxRef={this.state.lastShape}
/>
</>
));

public render() {
return this.renderEditableTextarea(this.state.calloutState === CalloutState.POSITIONING_TEXT_BOX);
return this.renderEditableTextarea(
this.state.calloutState === CalloutState.POSITIONING_TEXT_BOX &&
this.props.stage &&
!this.props.selected
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,29 @@ import { TextBox } from './editableText.styles';

interface IProps {
styles: CSSProperties;
onAddText: (newtext: string, width: number) => void;
onChange?: ({ width, height }) => void;
disabled?: boolean;
onAddText?: (newtext: string, width: number) => void;
onResize?: ({ width, height }) => void;
onClick?: () => void;
}

const pxToNumber = (size: string) => +size.replaceAll('px', '');
export const EditableText = ({ styles, onAddText, onChange, onClick }: IProps) => {
export const EditableText = ({ styles, disabled, onAddText, onClick, onResize }: IProps) => {
const ref = useRef<HTMLDivElement>(null);
const [size, setSize] = useState({ width: 0, height: 0 });

const updateSize = () => {
if (!ref.current) return;
const compStyles = getComputedStyle(ref.current);
setSize({
const newSize = {
width: pxToNumber(compStyles.width),
height: pxToNumber(compStyles.height),
});
};
onResize?.(newSize);
setSize(newSize);
}

const saveText = () => onAddText(ref.current.innerText, size.width);
const saveText = () => onAddText?.(ref.current.innerText, size.width);

const handleKeyDown = (e) => {
if (e.keyCode === 13 && !e.shiftKey) {
Expand All @@ -50,14 +54,12 @@ export const EditableText = ({ styles, onAddText, onChange, onClick }: IProps) =

useEffect(() => {
setTimeout(() => {
ref.current?.focus();
updateSize();
new ResizeObserver(updateSize).observe(ref.current);
if (!disabled) {
ref.current?.focus();
}
});
}, []);

useEffect(() => {
onChange?.(size);
}, [size]);
}, [disabled]);

return (
<ClickAwayListener onClickAway={saveText}>
Expand All @@ -67,9 +69,9 @@ export const EditableText = ({ styles, onAddText, onChange, onClick }: IProps) =
$placeholder={EDITABLE_TEXTAREA_PLACEHOLDER}
style={styles}
onKeyDown={handleKeyDown}
onInput={updateSize}
onClick={onClick}
contentEditable
contentEditable={!disabled}
disabled={disabled}
/>
</ClickAwayListener>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import styled from 'styled-components';
import styled, { css } from 'styled-components';

export const TextBox = styled.div<{ $placeholder: string }>`
export const TextBox = styled.div<{ $placeholder: string; disabled?: boolean }>`
font-family: 'Arial', sans-serif;
line-height: 1;
position: absolute;
Expand All @@ -33,6 +33,10 @@ export const TextBox = styled.div<{ $placeholder: string }>`
position: sticky;
bottom: 0;
${({ disabled }) => disabled && css`
pointer-events: none;
`}
&:focus {
outline: none;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Copyright (C) 2024 3D Repo Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useState, useEffect, CSSProperties } from 'react';
import Konva from 'konva';
import { isEmpty } from 'lodash';
import { EditableText } from '../editableText/editableText.component';

interface IProps {
stage: Konva.Stage;
layer: Konva.Layer;
boxRef?: any;
fontSize: number;
size?: number;
color: string;
onRefreshDrawingLayer?: () => void;
onAddNewText: (position, text: string, width: number, updateState?: boolean) => void;
}

export const TypingCalloutHandler = ({
stage, layer, boxRef, fontSize, size, color, onRefreshDrawingLayer, onAddNewText
}: IProps) => {
const [isPositionLocked, setIsPositionLocked] = useState(false);
const [offset, setOffset] = useState<CSSProperties>({});
const [maxSizes, setMaxSizes] = useState({ maxWidth: 0, maxHeight: 0 });

const setTextPosition = () => {
let left = 0, top = 0;
if (stage && layer) {
const position = stage.getPointerPosition();
left = position.x - layer.x();
top = position.y - layer.y();
}
setOffset({ top, left });
const { width, height } = stage.attrs;
setMaxSizes({
maxWidth: width - left,
maxHeight: height - top,
});
};

const onTextChange = (newText: string, width: number) => {
onAddNewText({ x: offset.left, y: offset.top }, newText, width);
setIsPositionLocked(false);
};

const redrawCalloutBox = ({ width, height }) => {
if (!isEmpty(boxRef) && onRefreshDrawingLayer) {
boxRef.width(width + Math.max(6, size * 2));
boxRef.height(height + Math.max(6, size * 2));
onRefreshDrawingLayer();
}
}

useEffect(() => {
if (isPositionLocked) return;

const handleMouseMove = () => setTextPosition();
const handleClick = () => setTimeout(() => setIsPositionLocked(true));

stage.on('mousemove touchmove', handleMouseMove);
stage.on('click touchstart', handleClick);

return () => {
stage.off('mousemove touchmove', handleMouseMove);
stage.off('click touchstart', handleClick);
}
}, [isPositionLocked]);

useEffect(() => {
if (!isEmpty(boxRef) && !isEmpty(offset) && onRefreshDrawingLayer) {
boxRef.x(Number(offset.left) - Math.max(3, size));
boxRef.y(Number(offset.top) - Math.max(3, size));
onRefreshDrawingLayer();
}
}, [offset]);

const styles = {
...offset,
...maxSizes,
color,
fontSize: `${fontSize}px`,
};

if (!isPositionLocked) return <EditableText disabled styles={styles} onResize={redrawCalloutBox} />;

return (
<EditableText
onAddText={onTextChange}
styles={styles}
onResize={redrawCalloutBox}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useState, useEffect, useCallback, CSSProperties, useRef } from 'react';
import { useState, useEffect, useCallback, CSSProperties } from 'react';
import Konva from 'konva';
import { isEmpty } from 'lodash';

Expand All @@ -41,14 +41,11 @@ export const TypingHandler = ({
const [offset, setOffset] = useState<CSSProperties>({});
const [positionLocked, setPositionLocked] = useState<boolean>(false);
const [maxSizes, setMaxSizes] = useState({ maxWidth: 0, maxHeight: 0 });
const isCallout = !!boxRef;

useEffect(() => {
if (stage) {
if (mode === MODES.TEXT && !visible && !positionLocked && !selected) {
stage.on('mousemove touchmove', handleMouseMove);
stage.on('click touchstart', handleClick);
}
if (stage && mode === MODES.TEXT && !visible && !positionLocked && !selected) {
stage.on('mousemove touchmove', handleMouseMove);
stage.on('click touchstart', handleClick);

return () => {
stage.off('mousemove touchmove', handleMouseMove);
Expand All @@ -72,14 +69,6 @@ export const TypingHandler = ({
});
};

const redrawCalloutBox = ({ width, height }) => {
if (!isEmpty(boxRef) && onRefreshDrawingLayer) {
boxRef.width(width + Math.max(6, size * 2));
boxRef.height(height + Math.max(6, size * 2));
onRefreshDrawingLayer();
}
}

const handleMouseMove = () => {
if (!positionLocked) {
setTextPosition();
Expand All @@ -106,20 +95,15 @@ export const TypingHandler = ({
setPositionLocked(false);
};

if (!visible) {
return null;
}
if (!visible) return null;

return (
<EditableText
onChange={redrawCalloutBox}
onAddText={onTextChange}
onClick={handleClick}
styles={{
...offset,
color,
fontSize: `${fontSize}px`,
visibility: visible ? 'visible' : 'hidden',
...maxSizes,
}}
/>
Expand Down

0 comments on commit a85dba9

Please sign in to comment.