Projekt

Obecné

Profil

Stáhnout (15.3 KB) Statistiky
| Větev: | Tag: | Revize:
1
import { Box, HStack, Pressable, View, Text, Flex, IconButton } from 'native-base';
2
import React, { useEffect, useRef, useState } from 'react';
3
import { StyleSheet, Dimensions, TouchableOpacity, GestureResponderEvent } from 'react-native';
4
import { PanGestureHandler, PinchGestureHandler, State, TapGestureHandler, TouchableWithoutFeedback } from 'react-native-gesture-handler';
5

    
6
import { findNodeHandle, UIManager } from 'react-native';
7

    
8
import Animated, {
9
    useAnimatedGestureHandler,
10
    useAnimatedStyle,
11
    useSharedValue,
12
} from 'react-native-reanimated';
13
import { PlanImage, Room } from '../../types/plan';
14
import Svg, { Path, SvgXml, Text as SvgText, G, Circle } from 'react-native-svg';
15

    
16
// @ts-ignore
17
import pointInSvgPolygon from "point-in-svg-polygon"
18
import { ExitfullscreenIcon, FullscreenIcon } from '../general/Icons';
19

    
20

    
21
const CastlePlanView = (props: { mapImage: PlanImage, roomList: Room[], selectedRoom?: Room, fullScreenMode?: boolean, height?: number, setSelectedRoom?: (room: Room) => void }) => {
22

    
23
    const DEFAULT_SCALE = 1
24
    const MIN_SCALE = 0.95
25

    
26
    const { mapImage, roomList, selectedRoom, height, setSelectedRoom } = props;
27

    
28

    
29
    const [fullScreenMode, setFullScreen] = useState(false);
30

    
31

    
32
    useEffect(() => {
33
        if (selectedRoom) {
34
            zoomToRoom(selectedRoom)
35
        }
36
    }, [selectedRoom])
37

    
38
    useEffect(() => {
39
        if (props.fullScreenMode) {
40
            setFullScreen(props.fullScreenMode)
41
        }
42
    }, [props.fullScreenMode])
43

    
44

    
45
    const panRef = useRef<React.ReactElement>();
46
    const pinchRef = useRef<React.ReactElement>();
47

    
48
    const svgRef = useRef<React.ReactElement>();
49

    
50
    const translateX = useSharedValue(0);
51
    const translateY = useSharedValue(0);
52
    const scale = useSharedValue(DEFAULT_SCALE);
53

    
54
    const onPanEvent = useAnimatedGestureHandler({
55
        // handle one finger -> drag / move
56
        onStart: (event, ctx: any) => {
57
            if (event.numberOfPointers === 1) {
58
                ctx.startX = translateX.value;
59
                ctx.startY = translateY.value;
60
            }
61
        },
62
        onActive: (event, ctx: any) => {
63
            if (event.numberOfPointers === 1) {
64
                translateX.value = ctx.startX + event.translationX;
65
                translateY.value = ctx.startY + event.translationY;
66

    
67
                console.log("xTranslate " + translateX.value + " yTranslate " + translateY.value)
68
            }
69
        },
70
    });
71

    
72
    const onGestureEvent = useAnimatedGestureHandler({
73
        onStart: (_, ctx: any) => {
74
            ctx.startScale = scale.value;
75
        },
76
        onActive: (event: any, ctx: any) => {
77
            // handle two fingers -> zoom + -
78
            if (event.numberOfPointers === 2) {
79
                scale.value = ctx.startScale * event.scale;
80
            }
81
        },
82
        onEnd: (event) => {
83
            // handle two fingers -> zoom + -
84
            if (event.numberOfPointers === 2) {
85
                if (scale.value < MIN_SCALE) {
86
                    scale.value = MIN_SCALE;
87
                }
88
                // You may add additional logic for maximum scale if needed
89

    
90
                console.log("scale")
91
                console.log(scale.value)
92
            }
93
        },
94
    });
95

    
96
    const animatedStyle = useAnimatedStyle(() => {
97
        return {
98
            transform: [
99
                { translateX: translateX.value },
100
                { translateY: translateY.value },
101
                { scale: scale.value },
102
            ],
103
        };
104
    });
105

    
106

    
107

    
108
    function calculateStraightLineDistance(x1: number, y1: number, planX: number, planY: number) {
109
        // Apply scale and translations to both points
110

    
111
        const x2 = planX
112
        const y2 = planY
113

    
114
        const x_distance = Math.abs(x1 - x2);
115
        const y_distance = Math.abs(y1 - y2);
116

    
117
        return x_distance + y_distance;
118
    }
119

    
120
    const zoomOut = () => {
121
        translateX.value = 0;
122
        translateY.value = 0;
123
        scale.value = DEFAULT_SCALE;
124
    }
125

    
126
    const zoomToRoom = (room: Room) => {
127
        const mapX = room.number_x
128
        const mapY = room.number_y
129

    
130
        const viewRatio = viewSize.width / viewSize.height;
131
        const svgRatio = mapImage.viewBox.width / mapImage.viewBox.height;
132

    
133
        let locationX = (mapX / mapImage.viewBox.width) * viewSize.width;
134
        let locationY = (mapY / mapImage.viewBox.height) * viewSize.height;
135

    
136
        if (svgRatio > viewRatio) {
137
            // svg is spaced in the middle
138
            // top down is filled
139
            let xRatio = viewSize.width / mapImage.viewBox.width;
140
            let yFilled = xRatio * mapImage.viewBox.height;
141
            let offsetY = (viewSize.height - yFilled) / 2;
142

    
143
            locationY = (mapY / mapImage.viewBox.height) * yFilled + offsetY;
144
        } else if (svgRatio < viewRatio) {
145
            let yRatio = viewSize.height / mapImage.viewBox.height;
146
            let xFilled = yRatio * mapImage.viewBox.width;
147
            let offsetX = (viewSize.width - xFilled) / 2;
148

    
149
            locationX = (mapX / mapImage.viewBox.width) * xFilled + offsetX;
150
        }
151

    
152
        const scaleValue = 3
153

    
154
        scale.value= scaleValue
155

    
156
        const offsetX = (viewSize.width / 2 - locationX) * (scaleValue - 1);
157
        const offsetY = (viewSize.height / 2 - locationY) * (scaleValue - 1);
158
        
159
        // Apply the translation including the offset
160
        translateX.value = viewSize.width / 2 - locationX + offsetX;
161
        translateY.value = viewSize.height / 2 - locationY + offsetY;
162
    }
163

    
164
    const handleFullScreenPressed = () => {
165
        if (scale.value != DEFAULT_SCALE || translateX.value != 0 || translateY.value != 0) {
166
            // console.log("todo zoom out")
167
            zoomOut()
168
        }
169
        else {
170
            // console.log("Toggling fullscreen")
171
            setFullScreen && setFullScreen(!fullScreenMode)
172
        }
173
    }
174

    
175
    const handleSvgPress = (event: GestureResponderEvent) => {
176
        let locationX = event.nativeEvent.locationX
177
        let locationY = event.nativeEvent.locationY
178

    
179
        // console.log("view size:")
180
        // console.log("x " + viewSize.width + " y " + viewSize.height) 
181

    
182
        const viewRatio = viewSize.width / viewSize.height
183
        // console.log("ratio: " + viewSize.width / viewSize.height)       
184
        // console.log("svg size:")
185
        // console.log("x " + mapImage.viewBox.width + " y " + mapImage.viewBox.height)
186
        const svgRatio = mapImage.viewBox.width / mapImage.viewBox.height
187
        // console.log("ratio: " + mapImage.viewBox.width / mapImage.viewBox.height)    
188

    
189

    
190
        let mapX = (locationX / viewSize.width) * mapImage.viewBox.width
191
        let mapY = (locationY / viewSize.height) * mapImage.viewBox.height
192

    
193
        console.log("clicked: x " + locationX + " y " + locationY)
194

    
195
        if (svgRatio > viewRatio) {
196
            // svg is spaced in the middle 
197
            // top down is filled
198
            let xRatio = viewSize.width / mapImage.viewBox.width
199
            let yFilled = xRatio * mapImage.viewBox.height
200
            let offsetY = (viewSize.height - yFilled) / 2
201

    
202
            mapY = ((locationY - offsetY) / yFilled) * mapImage.viewBox.height
203
        }
204
        else if (svgRatio < viewRatio) {
205
            // svg is spaced in the center 
206
            // left right is filled
207
            let yRatio = viewSize.height / mapImage.viewBox.height
208
            let xFilled = yRatio * mapImage.viewBox.width
209
            let offsetX = (viewSize.width - xFilled) / 2
210

    
211
            mapX = ((locationX - offsetX) / xFilled) * mapImage.viewBox.width
212
        }
213

    
214
        // Check if the touch event is within the bounds of a room
215
        let clickedRoom: Room | undefined = undefined
216

    
217
        const maxNumberDistance = 100
218

    
219
        let minDistance = Number.MAX_VALUE
220

    
221
        for (let i = 0; i < roomList.length; i++) {
222
            const room: Room = roomList[i]
223
            if (room.in_plan) {
224
                // TODO
225
                // console.log()
226
                // console.log("Room + " + room.id + " x: " + room.number_x + " y: " + room.number_y)
227

    
228
                const currentDistance = calculateStraightLineDistance(room.number_x, room.number_y, mapX, mapY)
229

    
230
                if (currentDistance < minDistance) {
231
                    minDistance = currentDistance
232
                    clickedRoom = room
233
                }
234
            }
235
        };
236

    
237
        console.log()
238
        console.log('Room clicked with id:', clickedRoom?.id);
239
        console.log("x " + clickedRoom?.number_x + " y " + clickedRoom?.number_y)
240
        console.log(minDistance)
241
        console.log("translated clicked: x " + mapX + " y " + mapY)
242

    
243
        if (clickedRoom && minDistance < maxNumberDistance) {
244
            // TODO        
245

    
246
            // Perform any actions you need with the clicked room
247

    
248
            // TODO fix -> point recognition
249
            setSelectedRoom && setSelectedRoom(clickedRoom)
250
        } else {
251
            console.log("no room found")
252
        }
253
        console.log("")
254
    };
255

    
256
    const [viewSize, setViewSize] = useState({ height: 0, width: 0 });
257

    
258
    useEffect(() => {
259
        setViewSize({
260
            width: fullScreenMode ?
261
                Dimensions.get('window').width - 20 : // full screen
262
                Dimensions.get('window').width - 20, // not full screen
263
            height: fullScreenMode ?
264
                Dimensions.get('window').height - 122.5 : // full scren
265
                Dimensions.get('window').height - 350, // not full screen
266
        })
267
    }, [fullScreenMode])
268

    
269

    
270
    return (
271
        // container
272
        <View
273
            height={viewSize.height}
274
            width={viewSize.width}
275
            // @ts-ignore
276
            style={
277
                fullScreenMode ? {
278
                    position: "absolute",
279
                    top: -230,
280
                    left: 0,
281
                    zIndex: 1000,
282
                    backgroundColor: "white",
283
                }
284
                    :
285
                    {
286
                        overflow: "hidden",
287
                    }
288
            }
289
            borderColor={"light.300"}
290
            borderRadius={10}
291
            borderWidth={fullScreenMode ? 0 : 1}
292
        >
293
            {/* control panel */}
294
            <Flex direction="row" alignItems="center" justify="flex-end" style={{ height: 50, width: 100, top: fullScreenMode ? 10 : 0, right: fullScreenMode ? 20 : 15, position: "absolute", zIndex: 5 }}>
295
                <Pressable padding={1.5} backgroundColor={"#654B07"} borderRadius={5} marginRight={selectedRoom ? 1 : -2} onPress={handleFullScreenPressed}>
296
                    <FullscreenIcon color="white" />
297
                </Pressable>
298
                {
299
                    selectedRoom &&
300
                    <Pressable padding={1.5} backgroundColor={"#654B07"} borderRadius={5} marginRight={-2} onPress={() => { zoomToRoom(selectedRoom) }}>
301
                        <ExitfullscreenIcon color="white" />
302
                    </Pressable>
303
                }
304

    
305
            </Flex>
306

    
307
            <PinchGestureHandler
308
                ref={pinchRef}
309
                // @ts-ignore
310
                onGestureEvent={onGestureEvent}
311
                simultaneousHandlers={[svgRef, panRef]}>
312
                <Animated.View style={[{ flex: 1 }, animatedStyle]}>
313
                    <PanGestureHandler
314
                        ref={panRef}
315
                        simultaneousHandlers={[svgRef, pinchRef]}
316
                        onGestureEvent={onPanEvent}
317
                        onHandlerStateChange={(nativeEvent: any) => {
318
                            if (nativeEvent.state === State.END) {
319
                                console.log(nativeEvent)
320
                            }
321
                        }}
322
                    >
323
                        <Animated.View style={[{
324
                            flex: 1,
325
                        }]}>
326
                            <Svg
327
                                id="svgMap"
328
                                onPress={handleSvgPress}
329
                            >
330
                                {mapImage && mapImage.viewBox &&
331
                                    // background image                    
332
                                    <SvgXml
333
                                        xml={mapImage.svg}
334
                                        width={"100%"}
335
                                        height={"100%"}
336
                                        viewBox={`${0} ${0} ${mapImage.viewBox.width} ${mapImage.viewBox.height}`}
337
                                    // onLayout={(event) => {
338
                                    //     setSvgDimensions({
339
                                    //         width: event.nativeEvent.layout.width,
340
                                    //         height: event.nativeEvent.layout.height,
341
                                    //     });
342
                                    // }}
343
                                    >
344
                                    </SvgXml>
345
                                }
346

    
347
                                {mapImage && roomList && roomList.length > 0 &&
348

    
349
                                    roomList.map((room, index) => {
350
                                        if (!room.in_plan) {
351
                                            return
352
                                        }
353
                                        return (
354
                                            <Path
355
                                                id={"roomList_" + index.toString()}
356
                                                d={room.svg_path}  // The path data defining the shape of the room
357
                                                fill={selectedRoom && room.id == selectedRoom.id ? "#E6F7FF" : "white"}        // Fill the room shape with no color
358
                                                stroke="#66B2FF"       // Outline color
359
                                                strokeWidth={0.3}     // Outline width
360
                                                fillOpacity={selectedRoom && room.id == selectedRoom.id ? 1 : 0.2}
361
                                            />
362
                                        )
363
                                    })}
364

    
365

    
366

    
367
                                {mapImage && roomList && roomList.length > 0 &&
368

    
369
                                    roomList.map((room) => {
370
                                        if (!room.in_plan) {
371
                                            return
372
                                        }
373
                                        return (
374
                                            < SvgText
375
                                                x={room.number_x}     // Adjust the x-coordinate based on your room data
376
                                                y={room.number_y}     // Adjust the y-coordinate based on your room data
377
                                                fill="red"      // Text color
378
                                                fontSize={8}       // Font size
379
                                                textAnchor="middle"  // Center the text horizontally
380
                                                fontStyle='italic'
381
                                                vectorEffect='non-scaling-stroke' // Helps prevent stroke from scaling
382
                                            >
383
                                                {room.id}
384
                                            </SvgText>
385
                                        )
386
                                    })}
387

    
388
                            </Svg>
389
                        </Animated.View>
390
                    </PanGestureHandler>
391
                </Animated.View>
392
            </PinchGestureHandler >
393
        </View >
394
    );
395
};
396

    
397
export default CastlePlanView;
398

    
    (1-1/1)