Skip to content

Commit

Permalink
Merge branch 'main' into feature/#110-copy-paste-shape
Browse files Browse the repository at this point in the history
  • Loading branch information
brauliodiez committed Aug 5, 2024
2 parents 5e5ffbb + 409943d commit 5ef2534
Show file tree
Hide file tree
Showing 29 changed files with 504 additions and 16 deletions.
12 changes: 12 additions & 0 deletions public/widgets/radiobutton.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/common/components/front-components/button-shape.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const getButtonShapeSizeRestrictions = (): ShapeSizeRestrictions =>
buttonShapeRestrictions;

export const ButtonShape = forwardRef<any, ShapeProps>(
({ x, y, width, height, id, onSelected, ...shapeProps }, ref) => {
({ x, y, width, height, id, onSelected, text, ...shapeProps }, ref) => {
const { width: restrictedWidth, height: restrictedHeight } =
fitSizeToShapeSizeRestrictions(buttonShapeRestrictions, width, height);

Expand Down Expand Up @@ -46,7 +46,7 @@ export const ButtonShape = forwardRef<any, ShapeProps>(
y={20}
width={width}
height={height - 20}
text="Click Me!"
text={text}
fontFamily="Comic Sans MS, cursive"
fontSize={15}
fill="black"
Expand Down
1 change: 1 addition & 0 deletions src/common/components/front-components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './listbox-shape';
export * from './datepickerinput-shape';
export * from './button-shape';
export * from './timepickerinput-shape';
export * from './radiobutton-shape';
74 changes: 74 additions & 0 deletions src/common/components/front-components/radiobutton-shape.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ShapeSizeRestrictions } from '@/core/model';
import { forwardRef, useState } from 'react';
import { Group, Circle, Text } from 'react-konva';
import { ShapeProps } from './shape.model';
import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes/shape-restrictions';

const radioButtonShapeRestrictions: ShapeSizeRestrictions = {
minWidth: 50,
minHeight: 30,
maxWidth: 200,
maxHeight: 50,
defaultWidth: 120,
defaultHeight: 50,
};

export const RadioButtonShape = forwardRef<any, ShapeProps>(
({ x, y, width, height, id, onSelected, ...shapeProps }, ref) => {
const { width: restrictedWidth, height: restrictedHeight } =
fitSizeToShapeSizeRestrictions(
radioButtonShapeRestrictions,
width,
height
);

const [isOn, setIsOn] = useState(false);

const handleSwitch = () => {
setIsOn(!isOn);
};

const radius = restrictedHeight / 2;

return (
<Group
x={x}
y={y}
ref={ref}
width={restrictedWidth}
height={restrictedHeight}
{...shapeProps}
onClick={() => onSelected(id, 'radiobutton')}
>
{/* Círculo exterior del radio button */}
<Circle
x={radius}
y={radius}
radius={radius}
stroke="black"
strokeWidth={2}
/>

{/* Círculo interior del radio button (checked) */}
<Circle
onClick={handleSwitch}
x={radius}
y={radius}
radius={radius * 0.5}
fill={isOn ? 'black' : 'white'}
/>

{/* Texto */}
<Text
x={radius * 2 + 10}
y={radius * 0.5 + 5}
text="Select me!"
fontFamily="Comic Sans MS, cursive"
fontSize={20}
fill="none"
verticalAllign="middle"
/>
</Group>
);
}
);
9 changes: 6 additions & 3 deletions src/common/components/inline-edit/inline-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ export const EditableComponent: React.FC<Props> = props => {
};

const handleKeyDown = (event: KeyboardEvent) => {
if (isEditing && event.key === 'Enter') {
if (isEditing && event.key === 'Escape') {
setIsEditing(false);
setEditText(text);
} else if (isEditing && event.key === 'Enter') {
setIsEditing(false);
onTextSubmit(inputRef.current?.value || '');
}
Expand All @@ -52,12 +55,12 @@ export const EditableComponent: React.FC<Props> = props => {
document.addEventListener('keydown', handleKeyDown);
} else {
document.removeEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleKeyDown);
document.removeEventListener('keydown', handleKeyDown);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleKeyDown);
document.removeEventListener('keydown', handleKeyDown);
};
}, [isEditing]);

Expand Down
3 changes: 2 additions & 1 deletion src/core/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export type ShapeType =
| 'mobilePhone'
| 'tablet'
| 'timepickerinput'
| 'label';
| 'label'
| 'radiobutton';
/* | "text"| "button" | "radio" | "image"*/

export type ShapeRefs = {
Expand Down
2 changes: 1 addition & 1 deletion src/core/providers/canvas/canvas.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export interface CanvasContextModel {
scale: number;
clearCanvas: () => void;
setScale: React.Dispatch<React.SetStateAction<number>>;
addNewShape: (type: ShapeType, x: number, y: number) => void;
pasteShape: (shape: ShapeModel) => void;
addNewShape: (type: ShapeType, x: number, y: number) => string;
updateShapeSizeAndPosition: (id: string, position: Coord, size: Size) => void;
updateShapePosition: (id: string, position: Coord) => void;
selectionInfo: SelectionInfo;
Expand Down
7 changes: 5 additions & 2 deletions src/core/providers/canvas/canvas.provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ export const CanvasProvider: React.FC<Props> = props => {
setShapes([]);
};

const addNewShape = (type: ShapeType, x: number, y: number) => {
setShapes(shapes => [...shapes, createShape({ x, y }, type)]);
const addNewShape = (type: ShapeType, x: number, y: number): string => {
const newShape = createShape({ x, y }, type);
setShapes(shapes => [...shapes, newShape]);

return newShape.id;
};

const pasteShape = (shape: ShapeModel) => {
Expand Down
7 changes: 3 additions & 4 deletions src/layout/main.layout.module.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
.layout {
display: grid;
grid-template-columns: minmax(var(--column-min-size), 2fr) minmax(
var(--canvas-min-size),
8fr
) minmax(var(--column-min-size), 2fr);
grid-template-columns:
minmax(var(--column-min-size), 2fr) minmax(var(--canvas-min-size), 8fr)
minmax(var(--column-min-size), 2fr);
grid-template-rows: auto minmax(0, 1fr) auto;
grid-template-areas:
'toolbar toolbar toolbar'
Expand Down
36 changes: 36 additions & 0 deletions src/pods/canvas/canvas.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const doesShapeAllowInlineEdition = (shapeType: ShapeType): boolean => {
switch (shapeType) {
case 'input':
case 'label':
case 'button':
return true;
default:
return false;
Expand All @@ -109,6 +110,8 @@ const generateDefaultTextValue = (shapeType: ShapeType): string | undefined => {
return '';
case 'label':
return 'Label';
case 'button':
return 'Click Me!';
default:
return undefined;
}
Expand All @@ -132,3 +135,36 @@ export const createShape = (coord: Coord, shapeType: ShapeType): ShapeModel => {
text: generateDefaultTextValue(shapeType),
};
};

// Snap model
export const SNAP_THRESHOLD = 5;

export type SnapLines = {
vertical: number[];
horizontal: number[];
};

export type SnapType = 'center' | 'start' | 'end';

export interface SnapEdge {
guide: number;
offset: number;
snapType: SnapType;
}

export type SnapEdges = {
vertical: SnapEdge[];
horizontal: SnapEdge[];
};

export type SnapLineSubset = {
snapLine: number;
diff: number;
snap: SnapType;
offset: number;
};

export type ClosestSnapLines = {
vertical: SnapLineSubset | null;
horizontal: SnapLineSubset | null;
};
71 changes: 68 additions & 3 deletions src/pods/canvas/canvas.pod.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { createRef, useEffect } from 'react';
import { createRef, useMemo, useEffect, useState } from 'react';
import Konva from 'konva';
import { useCanvasContext } from '@/core/providers';
import { Layer, Stage, Transformer } from 'react-konva';
import { Layer, Line, Stage, Transformer } from 'react-konva';
import { useTransform } from './use-transform.hook';
import { renderShapeComponent } from './shape-renderer';
import { useDropShape } from './use-drop-shape.hook';
import { useMonitorShape } from './use-monitor-shape.hook';
import classes from './canvas.pod.module.css';
import { EditableComponent } from '@/common/components/inline-edit';
import { useClipboard } from './use-clipboard.hook';
import { useSnapIn } from './use-snapin.hook';
import { ShapeType } from '@/core/model';

export const CanvasPod = () => {
const [isTransfomerBeingDragged, setIsTransfomerBeingDragged] =
useState(false);

const {
shapes,
scale,
Expand All @@ -31,8 +36,39 @@ export const CanvasPod = () => {
updateTextOnSelected,
} = selectionInfo;

const addNewShapeAndSetSelected = (type: ShapeType, x: number, y: number) => {
const shapeId = addNewShape(type, x, y);
// TODO add issue enhance this
setTimeout(() => {
handleSelected(shapeId, type);
});
};

const { isDraggedOver, dropRef } = useDropShape();
const { stageRef } = useMonitorShape(dropRef, addNewShape);
const { stageRef } = useMonitorShape(dropRef, addNewShapeAndSetSelected);

const getSelectedShapeKonvaId = (): string => {
let result = '';

if (selectedShapeRef.current) {
result = String(selectedShapeRef.current._id);
}

return result;
};

const selectedShapeKonvaId = useMemo(
() => getSelectedShapeKonvaId(),
[selectedShapeRef.current]
);

const {
handleTransformerDragMove,
showSnapInHorizontalLine,
showSnapInVerticalLine,
yCoordHorizontalLine,
xCoordVerticalLine,
} = useSnapIn(stageRef, transformerRef, selectedShapeKonvaId);

const { handleTransform, handleTransformerBoundBoxFunc } = useTransform(
updateShapeSizeAndPosition,
Expand Down Expand Up @@ -121,7 +157,36 @@ export const CanvasPod = () => {
ref={transformerRef}
flipEnabled={false}
boundBoxFunc={handleTransformerBoundBoxFunc}
onDragStart={() => setIsTransfomerBeingDragged(true)}
onDragMove={handleTransformerDragMove}
onDragEnd={() => setIsTransfomerBeingDragged(false)}
/>
{isTransfomerBeingDragged && showSnapInHorizontalLine && (
<Line
points={[
0,
yCoordHorizontalLine,
stageRef.current?.width() ?? 0,
yCoordHorizontalLine,
]}
stroke="rgb(0,161,255"
dash={[4, 6]}
strokeWidth={1}
/>
)}
{isTransfomerBeingDragged && showSnapInVerticalLine && (
<Line
points={[
xCoordVerticalLine,
0,
xCoordVerticalLine,
stageRef.current?.height() ?? 0,
]}
stroke="rgb(0,161,255"
dash={[4, 6]}
strokeWidth={1}
/>
)}
</Layer>
</Stage>
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/pods/canvas/shape-renderer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
renderDatepickerinput,
renderTimepickerinput,
renderLabel,
renderRadioButton,
} from './simple-component';
import {
renderBrowserWindow,
Expand Down Expand Up @@ -53,6 +54,8 @@ export const renderShapeComponent = (
return renderMobilePhoneContainer(shape, shapeRenderedProps);
case 'label':
return renderLabel(shape, shapeRenderedProps);
case 'radiobutton':
return renderRadioButton(shape, shapeRenderedProps);
default:
return renderNotFound(shape, shapeRenderedProps);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ export const renderButton = (
y={shape.y}
width={shape.width}
height={shape.height}
name="shape"
draggable
onSelected={handleSelected}
ref={shapeRefs.current[shape.id]}
onDragEnd={handleDragEnd(shape.id)}
onTransform={handleTransform}
onTransformEnd={handleTransform}
isEditable={shape.allowsInlineEdition}
text={shape.text}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const renderCheckbox = (
y={shape.y}
width={shape.width}
height={shape.height}
name="shape"
draggable
onSelected={handleSelected}
ref={shapeRefs.current[shape.id]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const renderComboBox = (
y={shape.y}
width={shape.width}
height={shape.height}
name="shape"
draggable
onSelected={handleSelected}
ref={shapeRefs.current[shape.id]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const renderDatepickerinput = (
y={shape.y}
width={shape.width}
height={shape.height}
name="shape"
draggable
onSelected={handleSelected}
ref={shapeRefs.current[shape.id]}
Expand Down
Loading

0 comments on commit 5ef2534

Please sign in to comment.