Skip to content

Commit

Permalink
Merge pull request #83 from Gkuzin13/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Gkuzin13 authored Jan 20, 2024
2 parents 36b22d8 + 9cc2b0a commit 0aac433
Show file tree
Hide file tree
Showing 19 changed files with 300 additions and 141 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/**
* @vitest-environment happy-dom
*/
import Konva from 'konva';
import { waitFor } from '@testing-library/react';
import { fireEvent, waitFor } from '@testing-library/react';
import { stateGenerator } from '@/test/data-generators';
import { renderWithProviders } from '@/test/test-utils';
import { findCanvas, renderWithProviders } from '@/test/test-utils';
import { createNode } from '@/utils/node';
import DrawingCanvas from './DrawingCanvas';
import { getLayerNodes, getMainLayer } from './helpers/stage';
Expand Down Expand Up @@ -30,8 +33,9 @@ describe('DrawingCanvas', () => {
const text = createNode('text', [120, 120]);
text.text = 'Hello World';

const nodes = [arrow, rectangle, ellipse, draw, text];

it('renders shapes', async () => {
const nodes = [arrow, rectangle, ellipse, draw, text];
const preloadedState = stateGenerator({ canvas: { present: { nodes } } });

renderWithProviders(
Expand All @@ -57,4 +61,50 @@ describe('DrawingCanvas', () => {
);
});
});

it('inherits style from currentNodeStyle', async () => {
const preloadedState = stateGenerator({
canvas: {
present: {
toolType: 'rectangle',
currentNodeStyle: {
color: 'blue700',
line: 'dashed',
fill: 'solid',
opacity: 0.5,
animated: true,
},
},
},
});

const { store } = renderWithProviders(
<DrawingCanvas
width={window.innerWidth}
height={window.innerHeight}
onNodesSelect={() => vi.fn()}
/>,
{ preloadedState },
);

const { canvas } = await findCanvas();

// start at [10, 20]
fireEvent.pointerDown(canvas, { clientX: 10, clientY: 20 });

// move to [30, 40]
fireEvent.pointerMove(canvas, { clientX: 30, clientY: 40 });

// stop at last position
fireEvent.pointerUp(canvas);

await waitFor(() => {
const canvasState = store.getState().canvas.present;
const node = canvasState.nodes[0];

expect(node.style).toEqual(
preloadedState.canvas.present.currentNodeStyle,
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useAppDispatch, useAppSelector, useAppStore } from '@/stores/hooks';
import {
canvasActions,
selectConfig,
selectCurrentNodeStyle,
selectSelectedNodeIds,
selectToolType,
} from '@/services/canvas/slice';
Expand Down Expand Up @@ -71,6 +72,7 @@ const DrawingCanvas = forwardRef<Konva.Stage, Props>(
const stageConfig = useAppSelector(selectConfig);
const toolType = useAppSelector(selectToolType);
const storedSelectedNodeIds = useAppSelector(selectSelectedNodeIds);
const currentNodeStyle = useAppSelector(selectCurrentNodeStyle);
const thisUser = useAppSelector(selectThisUser);
const ws = useWebSocket();

Expand Down Expand Up @@ -188,7 +190,7 @@ const DrawingCanvas = forwardRef<Konva.Stage, Props>(
(toolType: NodeType, position: Point) => {
const shouldResetToolType = toolType === 'text';

const node = createNode(toolType, position);
const node = createNode(toolType, position, currentNodeStyle);

setDrafts({ type: 'create', payload: { node } });
setEditingNodeId(node.nodeProps.id);
Expand All @@ -202,7 +204,7 @@ const DrawingCanvas = forwardRef<Konva.Stage, Props>(
ws.send({ type: 'draft-create', data: { node } });
}
},
[ws, dispatch, setDrafts],
[ws, currentNodeStyle, dispatch, setDrafts],
);

const handleDraftDraw = useCallback(
Expand Down Expand Up @@ -332,7 +334,7 @@ const DrawingCanvas = forwardRef<Konva.Stage, Props>(
handleDraftCreate(toolType, pointerPosition);
}

if (hasSelectedNodes) {
if (hasSelectedNodes && toolType === 'select') {
setSelectedNodeIds([]);
dispatch(canvasActions.setSelectedNodeIds([]));
}
Expand Down
34 changes: 27 additions & 7 deletions apps/client/src/components/Panels/Panels.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('style panel', () => {

describe('colors grid', () => {
GRID_COLORS.forEach((color) => {
it(`dispatches nodes update with ${color.name} color`, async () => {
it(`dispatches updateNodes and setCurrentNodeStyle with ${color.value} color`, async () => {
const { store, user } = renderWithProviders(
<Panels selectedNodeIds={selectedNodeIdsArray} />,
{ preloadedState },
Expand All @@ -69,6 +69,9 @@ describe('style panel', () => {
}),
),
);
expect(store.dispatch).toHaveBeenCalledWith(
canvasActions.setCurrentNodeStyle({ color: color.value }),
);
});
});

Expand All @@ -91,7 +94,7 @@ describe('style panel', () => {
});

describe('opacity', () => {
it('dispatches nodes update with new opacity value', async () => {
it('dispatches updateNodes and setCurrentNodeStyle with new opacity value', async () => {
const { user, store } = renderWithProviders(
<Panels selectedNodeIds={selectedNodeIdsArray} />,
{
Expand All @@ -103,22 +106,27 @@ describe('style panel', () => {

await user.keyboard('[ArrowLeft]');

const updatedOpacity = OPACITY.maxValue - OPACITY.step;

expect(store.dispatch).toHaveBeenCalledWith(
canvasActions.updateNodes(
selectedNodes.map((node) => {
return {
...node,
style: { ...node.style, opacity: 1 - OPACITY.step },
style: { ...node.style, opacity: updatedOpacity },
};
}),
),
);
expect(store.dispatch).toHaveBeenCalledWith(
canvasActions.setCurrentNodeStyle({ opacity: updatedOpacity }),
);
});
});

describe('size', () => {
SIZE.forEach((size) => {
it(`dispatches nodes update with ${size.name} size`, async () => {
it(`dispatches updateNodes and setCurrentNodeStyle with ${size.value} size`, async () => {
const { store, user } = renderWithProviders(
<Panels selectedNodeIds={selectedNodeIdsArray} />,
{ preloadedState },
Expand All @@ -133,13 +141,16 @@ describe('style panel', () => {
}),
),
);
expect(store.dispatch).toHaveBeenCalledWith(
canvasActions.setCurrentNodeStyle({ size: size.value }),
);
});
});
});

describe('fill', () => {
FILL.forEach((fill) => {
it(`dispatches nodes update with ${fill.name} fill`, async () => {
it(`dispatches updateNodes and setCurrentNodeStyle with ${fill.value} fill`, async () => {
const { store, user } = renderWithProviders(
<Panels selectedNodeIds={selectedNodeIdsArray} />,
{ preloadedState },
Expand All @@ -154,13 +165,16 @@ describe('style panel', () => {
}),
),
);
expect(store.dispatch).toHaveBeenCalledWith(
canvasActions.setCurrentNodeStyle({ fill: fill.value }),
);
});
});
});

describe('line', () => {
LINE.forEach((line) => {
it(`dispatches nodes update with ${line.name} fill`, async () => {
it(`dispatches updateNodes and setCurrentNodeStyle with ${line.value} fill`, async () => {
const { store, user } = renderWithProviders(
<Panels selectedNodeIds={selectedNodeIdsArray} />,
{ preloadedState },
Expand All @@ -175,12 +189,15 @@ describe('style panel', () => {
}),
),
);
expect(store.dispatch).toHaveBeenCalledWith(
canvasActions.setCurrentNodeStyle({ line: line.value }),
);
});
});
});

describe('animated', () => {
it(`dispatches nodes update with new animated value`, async () => {
it(`dispatches updateNodes and setCurrentNodeStyle with new animated value`, async () => {
const { store, user } = renderWithProviders(
<Panels selectedNodeIds={selectedNodeIdsArray} />,
{ preloadedState },
Expand All @@ -195,6 +212,9 @@ describe('style panel', () => {
}),
),
);
expect(store.dispatch).toHaveBeenCalledWith(
canvasActions.setCurrentNodeStyle({ animated: true }),
);
});
});
});
Expand Down
14 changes: 10 additions & 4 deletions apps/client/src/components/Panels/Panels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import LibraryDrawer from '../Library/LibraryDrawer/LibraryDrawer';
import HistoryButtons from './HistoryButtons';
import DeleteButton from './DeleteButton';
import {
LOCAL_STORAGE_COLLAB_KEY,
PROJECT_FILE_EXT,
PROJECT_FILE_NAME,
PROJECT_PNG_EXT,
Expand All @@ -32,17 +33,17 @@ import { selectLibrary } from '@/services/library/slice';
import { calculateCenterPoint } from '@/utils/position';
import { calculateStageZoomRelativeToPoint } from '../Canvas/DrawingCanvas/helpers/zoom';
import * as Styled from './Panels.styled';
import Konva from 'konva';
import { shallowEqual } from '@/utils/object';
import { setCursorByToolType } from '../Canvas/DrawingCanvas/helpers/cursor';
import { findStageByName } from '@/utils/node';
import { storage } from '@/utils/storage';
import type { NodeStyle, User } from 'shared';
import type {
HistoryControlKey,
MenuPanelActionType,
ZoomActionKey,
} from '@/constants/panels';
import type { ToolType } from '@/constants/app';
import type { StoredCollabState, ToolType } from '@/constants/app';

type Props = {
selectedNodeIds: string[];
Expand Down Expand Up @@ -80,7 +81,7 @@ const Panels = ({ selectedNodeIds }: Props) => {
(type: ToolType) => {
dispatch(canvasActions.setToolType(type));

const stage = Konva.stages[0];
const stage = findStageByName(DRAWING_CANVAS.NAME);
setCursorByToolType(stage, type);
},
[dispatch],
Expand All @@ -98,6 +99,7 @@ const Panels = ({ selectedNodeIds }: Props) => {
});

dispatch(canvasActions.updateNodes(updatedNodes));
dispatch(canvasActions.setCurrentNodeStyle(style));
};

const handleMenuAction = useCallback(
Expand Down Expand Up @@ -133,7 +135,7 @@ const Panels = ({ selectedNodeIds }: Props) => {

const stage = findStageByName(DRAWING_CANVAS.NAME);

setCursorByToolType(stage, project.toolType);
setCursorByToolType(stage, project.toolType ?? 'select');
} else {
modal.open({
title: 'Error',
Expand Down Expand Up @@ -186,6 +188,10 @@ const Panels = ({ selectedNodeIds }: Props) => {
const handleUserChange = useCallback(
(user: User) => {
dispatch(collaborationActions.updateUser(user));

storage.set<StoredCollabState>(LOCAL_STORAGE_COLLAB_KEY, {
user: { name: user.name, color: user.color },
});
},
[dispatch],
);
Expand Down
16 changes: 8 additions & 8 deletions apps/client/src/components/Panels/StylePanel/StylePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ type Props = {
onStyleChange: (style: Partial<NodeStyle>) => void;
};

const getValueIfAllIdentical = <
T extends string | number | boolean | undefined,
>(
set: Set<T>,
): T | undefined => {
return set.size === 1 ? [...set][0] : undefined;
};

const StylePanel = ({ selectedNodes, onStyleChange }: Props) => {
const mergedStyle = useMemo(() => {
const styles: NodeStyle[] = selectedNodes.map(({ style }) => style);
Expand All @@ -33,14 +41,6 @@ const StylePanel = ({ selectedNodes, onStyleChange }: Props) => {
const opacities = new Set(styles.map(({ opacity }) => opacity));
const allShapesAnimated = styles.every(({ animated }) => animated);

const getValueIfAllIdentical = <
T extends string | number | boolean | undefined,
>(
set: Set<T>,
): T | undefined => {
return set.size === 1 ? [...set][0] : undefined;
};

return {
color: getValueIfAllIdentical(colors),
line: getValueIfAllIdentical(lines),
Expand Down
2 changes: 2 additions & 0 deletions apps/client/src/components/Panels/UsersPanel/UsersPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ const EditableUserInfo = ({
};

const handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
event.stopPropagation();

if (event.key === KEYS.ENTER || event.key === KEYS.ESCAPE) {
handleIsEditingToggle();
}
Expand Down
6 changes: 5 additions & 1 deletion apps/client/src/constants/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Schemas } from 'shared';
import { z } from 'zod';
import type { User } from 'shared';

export const BASE_URL = 'https://drawflux-api.onrender.com';
export const BASE_URL_DEV = 'http://localhost:7456';
Expand All @@ -12,6 +13,7 @@ export const IS_PROD = process.env.NODE_ENV === 'production';
export const LOCAL_STORAGE_KEY = 'drawflux';
export const LOCAL_STORAGE_LIBRARY_KEY = 'drawflux-library';
export const LOCAL_STORAGE_THEME_KEY = 'drawflux-theme';
export const LOCAL_STORAGE_COLLAB_KEY = 'drawflux-collab';

export const WS_THROTTLE_MS = 16;

Expand Down Expand Up @@ -45,6 +47,7 @@ export const appState = z.object({
...CanvasSchema,
toolType: z.union([...ShapeTools, z.literal('hand'), z.literal('select')]),
selectedNodeIds: z.record(z.string(), z.boolean()),
currentNodeStyle: Schemas.Node.shape.style,
}),
});

Expand All @@ -55,4 +58,5 @@ export const libraryState = z.object({
export type AppState = z.infer<typeof appState>;
export type LibraryItem = z.infer<typeof LibraryItem>;
export type Library = z.infer<typeof libraryState>;
export type ToolType = AppState['page']['toolType'];
export type ToolType = AppState['page']['toolType'];
export type StoredCollabState = { user: Omit<User, 'id'> };
Loading

0 comments on commit 0aac433

Please sign in to comment.