Skip to content

Commit

Permalink
refactor: seperate drawing and collab canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
gkuzin13 committed Jan 19, 2024
1 parent ecf2e69 commit 8529b80
Show file tree
Hide file tree
Showing 18 changed files with 256 additions and 222 deletions.
1 change: 1 addition & 0 deletions apps/client/src/App.styled.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { styled } from "shared";

export const AppWrapper = styled('div', {
position: 'relative',
width: '100%',
height: '100%',
});
50 changes: 35 additions & 15 deletions apps/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ import { useWebSocket } from './contexts/websocket';
import { useNotifications } from './contexts/notifications';
import { urlSearchParam } from './utils/url';
import useWindowSize from './hooks/useWindowSize/useWindowSize';
import useFontFaceObserver from './hooks/useFontFaceObserver';
import useAutoFocus from './hooks/useAutoFocus/useAutoFocus';
import {
LOCAL_STORAGE_KEY,
LOCAL_STORAGE_LIBRARY_KEY,
BASE_WS_URL,
BASE_WS_URL_DEV,
IS_PROD,
LOADING_TEXT,
} from '@/constants/app';
import { CONSTANTS } from 'shared';
import { TOOLS } from './constants/panels';
Expand All @@ -44,14 +46,18 @@ import {
haveIntersection,
} from './components/Canvas/DrawingCanvas/helpers/stage';
import { setCursorByToolType } from './components/Canvas/DrawingCanvas/helpers/cursor';
import { TEXT } from './constants/shape';
import * as Styled from './App.styled';
import type { Library, AppState } from '@/constants/app';
import type { HistoryActionKey } from './stores/reducers/history';
import type Konva from 'konva';
import type { ContextMenuType } from './components/ContextMenu/ContextMenu';

const CollabCanvas = lazy(
() => import('@/components/Canvas/CollabCanvas/CollabCanvas'),
);
const DrawingCanvas = lazy(
() => import('./components/Canvas/DrawingCanvas/DrawingCanvas'),
() => import('@/components/Canvas/DrawingCanvas/DrawingCanvas'),
);

const wsBaseUrl = IS_PROD ? BASE_WS_URL : BASE_WS_URL_DEV;
Expand All @@ -73,6 +79,12 @@ const App = () => {
const windowSize = useWindowSize();
const ws = useWebSocket();

/*
* Triggers re-render when font is loaded
* to make sure font is loaded before rendering nodes
*/
const { loading } = useFontFaceObserver(TEXT.FONT_FAMILY);

const appWrapperRef = useAutoFocus<HTMLDivElement>();
const stageRef = useRef<Konva.Stage>(null);

Expand Down Expand Up @@ -276,20 +288,28 @@ const App = () => {
onKeyDown={handleKeyDown}
>
<Panels selectedNodeIds={selectedNodeIds} />
<Suspense fallback={<Loader fullScreen>Loading Assets...</Loader>}>
<ContextMenu.Root
menuType={menuType}
onContextMenuOpen={handleContextMenuOpen}
>
<ContextMenu.Trigger>
<DrawingCanvas
ref={stageRef}
width={windowSize.width}
height={windowSize.height}
onNodesSelect={setSelectedNodeIds}
/>
</ContextMenu.Trigger>
</ContextMenu.Root>
{loading && <Loader fullScreen>{LOADING_TEXT}</Loader>}
{!loading && (
<Suspense fallback={<Loader fullScreen>{LOADING_TEXT}</Loader>}>
<ContextMenu
menuType={menuType}
onContextMenuOpen={handleContextMenuOpen}
>
<ContextMenu.Trigger>
<DrawingCanvas
ref={stageRef}
width={windowSize.width}
height={windowSize.height}
onNodesSelect={setSelectedNodeIds}
/>
</ContextMenu.Trigger>
</ContextMenu>
</Suspense>
)}
<Suspense>
{ws.isConnected && (
<CollabCanvas width={windowSize.width} height={windowSize.height} />
)}
</Suspense>
</Styled.AppWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { styled } from 'shared';
import { Stage as KonvaStage } from 'react-konva';

export const Stage = styled(KonvaStage, {
pointerEvents: 'none',
position: 'absolute',
inset: 0,
touchAction: 'none',
});
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import { memo, useCallback, useEffect, useState } from 'react';
import { Layer } from 'react-konva';
import { useWebSocket } from '@/contexts/websocket';
import { useAppSelector } from '@/stores/hooks';
import { selectCollaborators } from '@/services/collaboration/slice';
import NodeDraft from '../Node/NodeDraft';
import UserCursor from '../UserCursor';
import UserCursor from './UserCursor';
import useDrafts from '@/hooks/useDrafts';
import useThemeColors from '@/hooks/useThemeColors';
import { selectConfig } from '@/services/canvas/slice';
import { noop } from '@/utils/is';
import type { NodeObject, Point } from 'shared';
import { getColorValue } from '@/utils/shape';
import * as Styled from './CollabCanvas.styled';
import type { NodeObject, Point } from 'shared';
import { COLLAB_CANVAS } from '@/constants/canvas';

type Props = {
stageScale: number;
width: number;
height: number;
};

type UserPosition = { [id: string]: Point };

const CollaborationLayer = ({ stageScale }: Props) => {
const CollaborationCanvas = ({ width, height }: Props) => {
const [userPositions, setUserPositions] = useState<UserPosition>({});
const [drafts, setDrafts] = useDrafts();

const stageConfig = useAppSelector(selectConfig);
const collaborators = useAppSelector(selectCollaborators);
const ws = useWebSocket();

Expand Down Expand Up @@ -83,35 +89,46 @@ const CollaborationLayer = ({ stageScale }: Props) => {
);

return (
<>
{drafts.map(({ node }) => {
return (
<NodeDraft
key={node.nodeProps.id}
node={node}
stageScale={stageScale}
onNodeChange={noop}
onNodeDelete={handleNodeDelete}
/>
);
})}
{collaborators.map((user) => {
const position = userPositions[user.id];
<Styled.Stage
name={COLLAB_CANVAS.NAME}
x={stageConfig.position.x}
y={stageConfig.position.y}
width={width}
height={height}
scaleX={stageConfig.scale}
scaleY={stageConfig.scale}
listening={false}
>
<Layer listening={false}>
{drafts.map(({ node }) => {
return (
<NodeDraft
key={node.nodeProps.id}
node={node}
stageScale={stageConfig.scale}
onNodeChange={noop}
onNodeDelete={handleNodeDelete}
/>
);
})}
{collaborators.map((user) => {
const position = userPositions[user.id];

if (!position) return null;
if (!position) return null;

return (
<UserCursor
key={user.id}
name={user.name}
color={getColorValue(user.color, themeColors)}
position={position}
stageScale={stageScale}
/>
);
})}
</>
return (
<UserCursor
key={user.id}
name={user.name}
color={getColorValue(user.color, themeColors)}
position={position}
stageScale={stageConfig.scale}
/>
);
})}
</Layer>
</Styled.Stage>
);
};

export default memo(CollaborationLayer);
export default memo(CollaborationCanvas);
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { memo } from 'react';
import { Rect } from 'react-konva';
import { getNormalizedInvertedRect } from '@/utils/position';
import type { IRect } from 'konva/lib/types';
import useThemeColors from '@/hooks/useThemeColors';
import type { IRect } from 'konva/lib/types';

type Props = {
rect: IRect;
scale: number;
};
stageScale: number;
} & IRect;

const Background = ({ scale, rect }: Props) => {
const CanvasBackground = ({ x, y, width, height, stageScale }: Props) => {
const themeColors = useThemeColors();

const normalizedRect = getNormalizedInvertedRect(rect, scale);
const normalizedRect = getNormalizedInvertedRect(
{ x, y, width, height },
stageScale,
);

return (
<Rect
Expand All @@ -27,4 +29,4 @@ const Background = ({ scale, rect }: Props) => {
);
};

export default memo(Background);
export default memo(CanvasBackground);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { memo } from 'react';
import NodeDraft from './NodeDraft';
import type { NodeComponentProps } from './Node';
import NodeDraft from '../Node/NodeDraft';
import type { NodeComponentProps } from '../Node/Node';
import type { Draft } from '@/hooks/useDrafts';

type Props = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { styled } from 'shared';
import { Stage as KonvaStage } from 'react-konva';

export const Stage = styled(KonvaStage, {
touchAction: 'none',
});
Loading

0 comments on commit 8529b80

Please sign in to comment.