refactor: export CanvasContextProvider
to follow established context pattern
As noted [in the review]( https://github.com/drawdb-io/drawdb/pull/148#discussion_r1681584836 ), I disagree with this pattern. However, I will follow it to get these features added.
This commit is contained in:
parent
471d2ebdf0
commit
5a56c3b354
@ -1,4 +1,4 @@
|
||||
import { useContext, useRef, useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { Button, Popover, Input } from "@douyinfe/semi-ui";
|
||||
import { IconEdit, IconDeleteStroked } from "@douyinfe/semi-icons";
|
||||
import {
|
||||
@ -9,6 +9,7 @@ import {
|
||||
State,
|
||||
} from "../../data/constants";
|
||||
import {
|
||||
useCanvas,
|
||||
useLayout,
|
||||
useSettings,
|
||||
useUndoRedo,
|
||||
@ -19,7 +20,6 @@ import {
|
||||
import ColorPalette from "../ColorPicker";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHover } from "usehooks-ts";
|
||||
import { CanvasContext } from "../../context/CanvasContext";
|
||||
|
||||
export default function Area({
|
||||
data,
|
||||
@ -33,7 +33,7 @@ export default function Area({
|
||||
pointer: {
|
||||
spaces: { diagram: pointer },
|
||||
},
|
||||
} = useContext(CanvasContext);
|
||||
} = useCanvas();
|
||||
const { layout } = useLayout();
|
||||
const { settings } = useSettings();
|
||||
const { setSaveState } = useSaveState();
|
||||
|
@ -11,6 +11,7 @@ import Area from "./Area";
|
||||
import Relationship from "./Relationship";
|
||||
import Note from "./Note";
|
||||
import {
|
||||
useCanvas,
|
||||
useSettings,
|
||||
useTransform,
|
||||
useDiagram,
|
||||
@ -22,14 +23,13 @@ import {
|
||||
} from "../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { diagram } from "../../data/heroDiagram";
|
||||
import { CanvasContext, useCanvasContextProviderValue } from "../../context/CanvasContext";
|
||||
import { useEventListener } from "usehooks-ts";
|
||||
|
||||
export default function Canvas() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const canvasRef = useRef(null);
|
||||
const canvasContextValue = useCanvasContextProviderValue(canvasRef);
|
||||
const canvasContextValue = useCanvas();
|
||||
const {
|
||||
canvas: { viewBox },
|
||||
pointer,
|
||||
@ -481,169 +481,167 @@ export default function Canvas() {
|
||||
const theme = localStorage.getItem("theme");
|
||||
|
||||
return (
|
||||
<CanvasContext.Provider value={canvasContextValue}>
|
||||
<div className="flex-grow h-full touch-none" id="canvas">
|
||||
<div
|
||||
className="w-full h-full"
|
||||
style={{
|
||||
cursor: pointer.style,
|
||||
backgroundColor: theme === "dark" ? "rgba(22, 22, 26, 1)" : "white",
|
||||
}}
|
||||
>
|
||||
{settings.showGrid && (
|
||||
<svg className="absolute w-full h-full">
|
||||
<defs>
|
||||
<pattern
|
||||
id="pattern-circles"
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
patternUnits="userSpaceOnUse"
|
||||
patternContentUnits="userSpaceOnUse"
|
||||
>
|
||||
<circle
|
||||
id="pattern-circle"
|
||||
cx="4"
|
||||
cy="4"
|
||||
r="0.85"
|
||||
fill="rgb(99, 152, 191)"
|
||||
></circle>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect
|
||||
<div className="flex-grow h-full touch-none" id="canvas">
|
||||
<div
|
||||
className="w-full h-full"
|
||||
style={{
|
||||
cursor: pointer.style,
|
||||
backgroundColor: theme === "dark" ? "rgba(22, 22, 26, 1)" : "white",
|
||||
}}
|
||||
>
|
||||
{settings.showGrid && (
|
||||
<svg className="absolute w-full h-full">
|
||||
<defs>
|
||||
<pattern
|
||||
id="pattern-circles"
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
fill="url(#pattern-circles)"
|
||||
></rect>
|
||||
</svg>
|
||||
)}
|
||||
<svg
|
||||
ref={canvasRef}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerUp={handlePointerUp}
|
||||
className="absolute w-full h-full touch-none"
|
||||
viewBox={`${viewBox.left} ${viewBox.top} ${viewBox.width} ${viewBox.height}`}
|
||||
>
|
||||
{areas.map((a) => (
|
||||
<Area
|
||||
key={a.id}
|
||||
data={a}
|
||||
onPointerDown={(e) =>
|
||||
handlePointerDownOnElement(e, a.id, ObjectType.AREA)
|
||||
}
|
||||
setResize={setAreaResize}
|
||||
setInitCoords={setInitCoords}
|
||||
/>
|
||||
))}
|
||||
{relationships.map((e, i) => (
|
||||
<Relationship key={i} data={e} />
|
||||
))}
|
||||
{tables.map((table) => (
|
||||
<Table
|
||||
key={table.id}
|
||||
tableData={table}
|
||||
setHoveredTable={setHoveredTable}
|
||||
handleGripField={handleGripField}
|
||||
setLinkingLine={setLinkingLine}
|
||||
onPointerDown={(e) =>
|
||||
handlePointerDownOnElement(e, table.id, ObjectType.TABLE)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{linking && (
|
||||
<path
|
||||
d={`M ${linkingLine.startX} ${linkingLine.startY} L ${linkingLine.endX} ${linkingLine.endY}`}
|
||||
stroke="red"
|
||||
strokeDasharray="8,8"
|
||||
className="pointer-events-none touch-none"
|
||||
/>
|
||||
)}
|
||||
{notes.map((n) => (
|
||||
<Note
|
||||
key={n.id}
|
||||
data={n}
|
||||
onPointerDown={(e) =>
|
||||
handlePointerDownOnElement(e, n.id, ObjectType.NOTE)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
width="24"
|
||||
height="24"
|
||||
patternUnits="userSpaceOnUse"
|
||||
patternContentUnits="userSpaceOnUse"
|
||||
>
|
||||
<circle
|
||||
id="pattern-circle"
|
||||
cx="4"
|
||||
cy="4"
|
||||
r="0.85"
|
||||
fill="rgb(99, 152, 191)"
|
||||
></circle>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
fill="url(#pattern-circles)"
|
||||
></rect>
|
||||
</svg>
|
||||
</div>
|
||||
{settings.showDebugCoordinates && (
|
||||
<div className="fixed flex flex-col flex-wrap gap-6 bg-[rgba(var(--semi-grey-1),var(--tw-bg-opacity))]/40 border border-color bottom-4 right-4 p-4 rounded-xl backdrop-blur-sm pointer-events-none select-none">
|
||||
<table className="table-auto grow">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left" colSpan={3}>
|
||||
{t("transform")}
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="italic [&_th]:font-normal [&_th]:text-right">
|
||||
<th>pan x</th>
|
||||
<th>pan y</th>
|
||||
<th>scale</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="[&_td]:text-right [&_td]:min-w-[8ch]">
|
||||
<tr>
|
||||
<td>{transform.pan.x.toFixed(2)}</td>
|
||||
<td>{transform.pan.y.toFixed(2)}</td>
|
||||
<td>{transform.zoom.toFixed(4)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table className="table-auto grow [&_th]:text-left [&_th:not(:first-of-type)]:text-right [&_td:not(:first-of-type)]:text-right [&_td]:min-w-[8ch]">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={4}>{t("viewbox")}</th>
|
||||
</tr>
|
||||
<tr className="italic [&_th]:font-normal">
|
||||
<th>left</th>
|
||||
<th>top</th>
|
||||
<th>width</th>
|
||||
<th>height</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{viewBox.left.toFixed(2)}</td>
|
||||
<td>{viewBox.top.toFixed(2)}</td>
|
||||
<td>{viewBox.width.toFixed(2)}</td>
|
||||
<td>{viewBox.height.toFixed(2)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table className="table-auto grow [&_th]:text-left [&_th:not(:first-of-type)]:text-right [&_td:not(:first-of-type)]:text-right [&_td]:min-w-[8ch]">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={3}>{t("cursor_coordinates")}</th>
|
||||
</tr>
|
||||
<tr className="italic [&_th]:font-normal">
|
||||
<th>{t("coordinate_space")}</th>
|
||||
<th>x</th>
|
||||
<th>y</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t("coordinate_space_screen")}</td>
|
||||
<td>{pointer.spaces.screen.x.toFixed(2)}</td>
|
||||
<td>{pointer.spaces.screen.y.toFixed(2)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("coordinate_space_diagram")}</td>
|
||||
<td>{pointer.spaces.diagram.x.toFixed(2)}</td>
|
||||
<td>{pointer.spaces.diagram.y.toFixed(2)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
<svg
|
||||
ref={canvasRef}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerUp={handlePointerUp}
|
||||
className="absolute w-full h-full touch-none"
|
||||
viewBox={`${viewBox.left} ${viewBox.top} ${viewBox.width} ${viewBox.height}`}
|
||||
>
|
||||
{areas.map((a) => (
|
||||
<Area
|
||||
key={a.id}
|
||||
data={a}
|
||||
onPointerDown={(e) =>
|
||||
handlePointerDownOnElement(e, a.id, ObjectType.AREA)
|
||||
}
|
||||
setResize={setAreaResize}
|
||||
setInitCoords={setInitCoords}
|
||||
/>
|
||||
))}
|
||||
{relationships.map((e, i) => (
|
||||
<Relationship key={i} data={e} />
|
||||
))}
|
||||
{tables.map((table) => (
|
||||
<Table
|
||||
key={table.id}
|
||||
tableData={table}
|
||||
setHoveredTable={setHoveredTable}
|
||||
handleGripField={handleGripField}
|
||||
setLinkingLine={setLinkingLine}
|
||||
onPointerDown={(e) =>
|
||||
handlePointerDownOnElement(e, table.id, ObjectType.TABLE)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{linking && (
|
||||
<path
|
||||
d={`M ${linkingLine.startX} ${linkingLine.startY} L ${linkingLine.endX} ${linkingLine.endY}`}
|
||||
stroke="red"
|
||||
strokeDasharray="8,8"
|
||||
className="pointer-events-none touch-none"
|
||||
/>
|
||||
)}
|
||||
{notes.map((n) => (
|
||||
<Note
|
||||
key={n.id}
|
||||
data={n}
|
||||
onPointerDown={(e) =>
|
||||
handlePointerDownOnElement(e, n.id, ObjectType.NOTE)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
</CanvasContext.Provider>
|
||||
{settings.showDebugCoordinates && (
|
||||
<div className="fixed flex flex-col flex-wrap gap-6 bg-[rgba(var(--semi-grey-1),var(--tw-bg-opacity))]/40 border border-color bottom-4 right-4 p-4 rounded-xl backdrop-blur-sm pointer-events-none select-none">
|
||||
<table className="table-auto grow">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left" colSpan={3}>
|
||||
{t("transform")}
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="italic [&_th]:font-normal [&_th]:text-right">
|
||||
<th>pan x</th>
|
||||
<th>pan y</th>
|
||||
<th>scale</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="[&_td]:text-right [&_td]:min-w-[8ch]">
|
||||
<tr>
|
||||
<td>{transform.pan.x.toFixed(2)}</td>
|
||||
<td>{transform.pan.y.toFixed(2)}</td>
|
||||
<td>{transform.zoom.toFixed(4)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table className="table-auto grow [&_th]:text-left [&_th:not(:first-of-type)]:text-right [&_td:not(:first-of-type)]:text-right [&_td]:min-w-[8ch]">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={4}>{t("viewbox")}</th>
|
||||
</tr>
|
||||
<tr className="italic [&_th]:font-normal">
|
||||
<th>left</th>
|
||||
<th>top</th>
|
||||
<th>width</th>
|
||||
<th>height</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{viewBox.left.toFixed(2)}</td>
|
||||
<td>{viewBox.top.toFixed(2)}</td>
|
||||
<td>{viewBox.width.toFixed(2)}</td>
|
||||
<td>{viewBox.height.toFixed(2)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table className="table-auto grow [&_th]:text-left [&_th:not(:first-of-type)]:text-right [&_td:not(:first-of-type)]:text-right [&_td]:min-w-[8ch]">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={3}>{t("cursor_coordinates")}</th>
|
||||
</tr>
|
||||
<tr className="italic [&_th]:font-normal">
|
||||
<th>{t("coordinate_space")}</th>
|
||||
<th>x</th>
|
||||
<th>y</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{t("coordinate_space_screen")}</td>
|
||||
<td>{pointer.spaces.screen.x.toFixed(2)}</td>
|
||||
<td>{pointer.spaces.screen.y.toFixed(2)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("coordinate_space_diagram")}</td>
|
||||
<td>{pointer.spaces.diagram.x.toFixed(2)}</td>
|
||||
<td>{pointer.spaces.diagram.y.toFixed(2)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import ControlPanel from "./EditorHeader/ControlPanel";
|
||||
import Canvas from "./EditorCanvas/Canvas";
|
||||
import { CanvasContextProvider } from "../context/CanvasContext";
|
||||
import SidePanel from "./EditorSidePanel/SidePanel";
|
||||
import { DB, State } from "../data/constants";
|
||||
import { db } from "../data/db";
|
||||
@ -362,7 +363,9 @@ export default function WorkSpace() {
|
||||
<SidePanel resize={resize} setResize={setResize} width={width} />
|
||||
)}
|
||||
<div className="relative w-full h-full overflow-hidden">
|
||||
<Canvas saveState={saveState} setSaveState={setSaveState} />
|
||||
<CanvasContextProvider className="h-full w-full">
|
||||
<Canvas saveState={saveState} setSaveState={setSaveState} />
|
||||
</CanvasContextProvider>
|
||||
{!(layout.sidebar || layout.toolbar || layout.header) && (
|
||||
<div className="fixed right-5 bottom-4">
|
||||
<FloatingControls />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useTransform } from "../hooks";
|
||||
import { createContext, useCallback, useMemo, useState } from "react";
|
||||
import { createContext, useCallback, useMemo, useRef, useState } from "react";
|
||||
import { useEventListener, useResizeObserver } from "usehooks-ts";
|
||||
|
||||
export const CanvasContext = createContext({
|
||||
@ -34,10 +34,11 @@ export const CanvasContext = createContext({
|
||||
},
|
||||
});
|
||||
|
||||
export function useCanvasContextProviderValue(canvasRef) {
|
||||
export function CanvasContextProvider({ children, ...attrs }) {
|
||||
const canvasWrapRef = useRef(null);
|
||||
const { transform } = useTransform();
|
||||
const canvasSize = useResizeObserver({
|
||||
ref: canvasRef,
|
||||
ref: canvasWrapRef,
|
||||
box: "content-box",
|
||||
});
|
||||
const screenSize = useMemo(
|
||||
@ -133,11 +134,11 @@ export function useCanvasContextProviderValue(canvasRef) {
|
||||
}
|
||||
|
||||
// Important for touch screen devices!
|
||||
useEventListener("pointerdown", detectPointerMovement, canvasRef);
|
||||
useEventListener("pointerdown", detectPointerMovement, canvasWrapRef);
|
||||
|
||||
useEventListener("pointermove", detectPointerMovement, canvasRef);
|
||||
useEventListener("pointermove", detectPointerMovement, canvasWrapRef);
|
||||
|
||||
return {
|
||||
const contextValue = {
|
||||
canvas: {
|
||||
screenSize,
|
||||
viewBox,
|
||||
@ -155,4 +156,12 @@ export function useCanvasContextProviderValue(canvasRef) {
|
||||
setStyle: setPointerStyle,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<CanvasContext.Provider value={contextValue}>
|
||||
<div {...attrs} ref={canvasWrapRef}>
|
||||
{children}
|
||||
</div>
|
||||
</CanvasContext.Provider>
|
||||
)
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
export { default as useAreas } from "./useAreas";
|
||||
export { default as useCanvas } from "./useCanvas";
|
||||
export { default as useLayout } from "./useLayout";
|
||||
export { default as useNotes } from "./useNotes";
|
||||
export { default as useSaveState } from "./useSaveState";
|
||||
|
6
src/hooks/useCanvas.js
Normal file
6
src/hooks/useCanvas.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { useContext } from "react";
|
||||
import { CanvasContext } from "../context/CanvasContext";
|
||||
|
||||
export default function useCanvas() {
|
||||
return useContext(CanvasContext);
|
||||
}
|
Loading…
Reference in New Issue
Block a user