1- import React , { useRef , useState } from "react" ;
1+ import React , { useEffect , useRef , useState } from "react" ;
22import styled from "@emotion/styled" ;
3- import { usePinch } from "@use-gesture/react" ;
3+ import { useGesture } from "@use-gesture/react" ;
44import { Resizable } from "re-resizable" ;
55import { ZoomControl } from "./controller-zoom-control" ;
66
7+ /**
8+ * A React Hook that returns a delta state.
9+ * When user completely stops interacting, after a short delay (600ms), set the value to false.
10+ * When user starts interacting, immidiately set the value to true.
11+ *
12+ * the condition rather if the user is currently interacting or not is set on higher level, which this function accepts the condition as a parameter.
13+ * @param interacting
14+ */
15+ function useIsInteractingDelta ( interacting : boolean ) {
16+ throw new Error ( "Not implemented" ) ;
17+ }
18+
719export function InteractiveCanvas ( {
820 children,
921 defaultSize,
1022} : {
1123 defaultSize : { width : number ; height : number } ;
1224 children ?: React . ReactNode ;
1325} ) {
14- const [ scale , setScale ] = useState ( 1 ) ;
26+ const __canvas_width = 800 ;
27+ const __canvas_height = 900 ;
28+ const __margin = 20 ;
29+ const __y_start =
30+ defaultSize . height < __canvas_height - __margin * 2
31+ ? ( __canvas_height - defaultSize . height ) / 2
32+ : __margin ;
33+ const __initial_xy = [ 0 , __y_start ] as [ number , number ] ;
34+ const __initial_scale =
35+ defaultSize . width > __canvas_width
36+ ? ( __canvas_width - __margin * 2 ) / defaultSize . width
37+ : 1 ;
38+
39+ const [ scale , setScale ] = useState ( __initial_scale ) ;
40+ const [ xy , setXY ] = useState < [ number , number ] > ( __initial_xy ) ;
41+
42+ const [ isPanning , setIsPanning ] = useState ( false ) ;
43+ const [ isZooming , setIsZooming ] = useState ( false ) ;
44+ const isDeltaInteracting = isPanning || isZooming ;
45+
46+ const ref = useRef ( ) ;
47+
48+ useGesture (
49+ {
50+ onPinch : ( state ) => {
51+ setIsZooming ( state . pinching ) ;
52+ setScale ( Math . max ( scale + state . delta [ 0 ] , 0.1 ) ) ;
53+ } ,
54+ onWheel : ( { delta : [ x , y ] , wheeling } ) => {
55+ setIsPanning ( wheeling ) ;
56+ setXY ( [ xy [ 0 ] - x / scale , xy [ 1 ] - y / scale ] ) ;
57+ } ,
58+ } ,
59+ { target : ref }
60+ ) ;
1561
1662 return (
1763 < InteractiveCanvasWrapper id = "interactive-canvas" >
18- < ScalableFrame onRescale = { setScale } scale = { scale } >
64+ < div
65+ id = "event-listener"
66+ ref = { ref }
67+ style = { {
68+ flexGrow : 1 ,
69+ display : "flex" ,
70+ flexDirection : "column" ,
71+ alignItems : "center" ,
72+ } }
73+ >
1974 < Controls >
20- < ZoomControl scale = { scale } onChange = { setScale } />
75+ < ZoomControl
76+ onReset = { ( ) => {
77+ setScale ( __initial_scale ) ;
78+ setXY ( __initial_xy ) ;
79+ } }
80+ scale = { scale }
81+ onChange = { setScale }
82+ />
2183 </ Controls >
22- < ScalingAreaStaticRoot >
23- < ScalingArea scale = { scale } >
24- < ResizableFrame defaultSize = { defaultSize } scale = { scale } >
25- { children }
26- </ ResizableFrame >
27- </ ScalingArea >
28- </ ScalingAreaStaticRoot >
29- </ ScalableFrame >
84+ { /* <ScalingAreaStaticRoot> */ }
85+ < TransformContainer
86+ scale = { scale }
87+ xy = { xy }
88+ isTransitioning = { isDeltaInteracting }
89+ >
90+ < ResizableFrame defaultSize = { defaultSize } scale = { scale } >
91+ { children }
92+ </ ResizableFrame >
93+ </ TransformContainer >
94+ { /* </ScalingAreaStaticRoot> */ }
95+ </ div >
3096 </ InteractiveCanvasWrapper >
3197 ) ;
3298}
3399
34100const InteractiveCanvasWrapper = styled . div `
35101 display: flex;
36102 flex-direction: column;
37- /* overflow-y: auto; */
38- overflow-x: hidden;
39- flex: 1;
103+ overflow: hidden;
104+ flex-grow: 1;
40105` ;
41106
42107const Controls = styled . div `
@@ -46,67 +111,23 @@ const Controls = styled.div`
46111 justify-content: flex-end;
47112` ;
48113
49- const ScalingAreaStaticRoot = styled . div `
50- display: flex;
51- align-items: flex-start; // when transform origin is top center.
52- padding-top: 20px;
53- justify-content: center;
54- align-content: flex-start;
55- align-self: stretch;
56- flex: 1;
57- max-height: 100vh; // TODO: make dynamic
58- ` ;
59-
60- function ScalableFrame ( {
61- children,
62- scale,
63- onRescale,
64- } : {
65- scale : number ;
66- onRescale ?: ( scale : number ) => void ;
67- children ?: React . ReactNode ;
68- } ) {
69- const ref = useRef ( ) ;
70-
71- usePinch (
72- ( state ) => {
73- const prevscale = scale ;
74- const { offset } = state ;
75- const thisscale = offset [ 0 ] ;
76- // const newscale = thisscale - prevscale;
77- onRescale ( thisscale ) ;
78- } ,
79- { target : ref }
80- ) ;
81-
82- return (
83- < div
84- id = "scale-event-listener"
85- ref = { ref }
86- style = { {
87- display : "flex" ,
88- flexDirection : "column" ,
89- flex : 1 ,
90- alignItems : "center" ,
91- alignContent : "center" ,
92- } }
93- >
94- { children }
95- </ div >
96- ) ;
97- }
98-
99- const ScalingArea = ( {
114+ const TransformContainer = ( {
100115 scale,
101116 children,
117+ xy,
118+ isTransitioning,
102119} : {
103120 scale : number ;
121+ xy : [ number , number ] ;
122+ isTransitioning : boolean ;
104123 children : React . ReactNode ;
105124} ) => {
106125 return (
107126 < div
108127 style = { {
109- transform : `scale(${ scale } )` ,
128+ pointerEvents : isTransitioning ? "none" : undefined ,
129+ transform : `scale(${ scale } ) translateX(${ xy [ 0 ] } px) translateY(${ xy [ 1 ] } px)` ,
130+ willChange : "transform" ,
110131 transformOrigin : "top center" ,
111132 } }
112133 >
0 commit comments