@mmmmzxe/react-360-viewer 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/index.js +3590 -65
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/package.json +11 -18
  7. package/dist/inject-styles.js +0 -21
  8. package/src/components/ui/Badge/index.tsx +0 -45
  9. package/src/components/ui/Button/index.tsx +0 -67
  10. package/src/components/ui/Card/index.tsx +0 -74
  11. package/src/components/ui/Item/index.tsx +0 -136
  12. package/src/components/ui/Label/index.tsx +0 -20
  13. package/src/components/ui/Popover/index.tsx +0 -56
  14. package/src/components/ui/Separator/index.tsx +0 -30
  15. package/src/components/ui/Spinner/index.tsx +0 -11
  16. package/src/components/utils/index.ts +0 -6
  17. package/src/constants/viewer360ClassNames.ts +0 -42
  18. package/src/constants/viewer360Config.ts +0 -11
  19. package/src/constants/viewer360Labels.ts +0 -14
  20. package/src/constants/viewer360MarkerLabels.ts +0 -3
  21. package/src/feature/Viewer360.test.tsx +0 -47
  22. package/src/feature/Viewer360.tsx +0 -223
  23. package/src/feature/Viewer360AddModeBanner.tsx +0 -20
  24. package/src/feature/Viewer360FrameIndicator.tsx +0 -20
  25. package/src/feature/Viewer360HotspotOverlay.tsx +0 -57
  26. package/src/feature/Viewer360LoadingOverlay.tsx +0 -28
  27. package/src/feature/Viewer360MarkerPin.tsx +0 -105
  28. package/src/feature/Viewer360Toolbar.tsx +0 -75
  29. package/src/helpers/adjustViewerZoom.test.ts +0 -29
  30. package/src/helpers/adjustViewerZoom.ts +0 -64
  31. package/src/helpers/computeDragFrameIndex.test.ts +0 -20
  32. package/src/helpers/computeDragFrameIndex.ts +0 -23
  33. package/src/helpers/computeViewerImageLayout.test.ts +0 -48
  34. package/src/helpers/computeViewerImageLayout.ts +0 -114
  35. package/src/helpers/computeViewerPanOffset.ts +0 -18
  36. package/src/helpers/markerHelpers.test.ts +0 -38
  37. package/src/helpers/markerHelpers.ts +0 -33
  38. package/src/helpers/viewer360PropsHelpers.ts +0 -46
  39. package/src/helpers/viewerHelpers.ts +0 -74
  40. package/src/hooks/useViewer360.ts +0 -306
  41. package/src/index.ts +0 -68
  42. package/src/styles.css +0 -80
  43. package/src/types/Viewer360Hotspot.ts +0 -67
  44. package/src/types/Viewer360Marker.ts +0 -52
  45. package/src/types/Viewer360Props.ts +0 -108
  46. package/src/types/index.ts +0 -30
  47. package/src/utils/index.ts +0 -6
@@ -1,105 +0,0 @@
1
- import type { JSX, MouseEvent } from 'react';
2
-
3
- import { Trash2 } from 'lucide-react';
4
-
5
- import { defaultViewer360MarkerPinLabels } from '../constants/viewer360MarkerLabels';
6
- import { viewer360MarkerPinClassNames } from '../constants/viewer360ClassNames';
7
- import type { Viewer360MarkerPinProps } from '../types';
8
- import { Button } from '@/components/ui/Button';
9
- import {
10
- Item,
11
- ItemActions,
12
- ItemContent,
13
- ItemDescription,
14
- ItemTitle,
15
- } from '@/components/ui/Item';
16
- import { cn } from '@/components/utils';
17
-
18
- export function Viewer360MarkerPin<TData = unknown>({
19
- marker,
20
- hotspot,
21
- leftPercent,
22
- topPercent,
23
- onDelete,
24
- isDeletePending = false,
25
- onClick,
26
- renderTag,
27
- classNames,
28
- labels,
29
- }: Viewer360MarkerPinProps<TData>): JSX.Element {
30
- // ----------------------------------------------------------------------------------------------------
31
- // MARK: States & Constants
32
- // ----------------------------------------------------------------------------------------------------
33
- const deleteLabel = labels?.delete ?? defaultViewer360MarkerPinLabels.delete;
34
- const showTooltip = Boolean(marker.title || marker.description || onDelete || renderTag);
35
-
36
- // ----------------------------------------------------------------------------------------------------
37
- // MARK: Main Component UI
38
- // ----------------------------------------------------------------------------------------------------
39
- return (
40
- <div
41
- className={cn(viewer360MarkerPinClassNames.root, classNames?.root, 'group/marker')}
42
- style={{ left: `${leftPercent}%`, top: `${topPercent}%` }}
43
- >
44
- <div className="relative size-4 shrink-0">
45
- <span className={cn(viewer360MarkerPinClassNames.ping, classNames?.ping)} aria-hidden="true" />
46
-
47
- <Button
48
- type="button"
49
- variant="destructive"
50
- size="icon-xs"
51
- className={cn(viewer360MarkerPinClassNames.dot, classNames?.dot, 'hover:bg-destructive')}
52
- aria-label={marker.title}
53
- onClick={onClick}
54
- />
55
- </div>
56
-
57
- {showTooltip && (
58
- <div
59
- className={cn(
60
- viewer360MarkerPinClassNames.tooltip,
61
- classNames?.tooltip,
62
- 'pointer-events-none opacity-0 transition-opacity duration-150 group-hover/marker:pointer-events-auto group-hover/marker:opacity-100 group-focus-within/marker:pointer-events-auto group-focus-within/marker:opacity-100'
63
- )}
64
- >
65
- <Item
66
- size="sm"
67
- variant="default"
68
- className={cn(viewer360MarkerPinClassNames.tooltipHeader, classNames?.tooltipHeader, 'w-full border-transparent')}
69
- >
70
- <ItemContent className={cn(viewer360MarkerPinClassNames.tooltipBody, classNames?.tooltipBody)}>
71
- <ItemTitle className={cn(viewer360MarkerPinClassNames.tooltipTitle, classNames?.tooltipTitle)}>
72
- {marker.title}
73
- </ItemTitle>
74
- {renderTag && (
75
- <div className="mt-1 flex w-fit items-center">{renderTag({ marker, hotspot })}</div>
76
- )}
77
- {marker.description && (
78
- <ItemDescription className={cn(viewer360MarkerPinClassNames.tooltipDescription, classNames?.tooltipDescription)}>
79
- {marker.description}
80
- </ItemDescription>
81
- )}
82
- </ItemContent>
83
- {onDelete && (
84
- <ItemActions>
85
- <Button
86
- variant="ghost"
87
- size="icon-sm"
88
- className={classNames?.deleteButton}
89
- disabled={isDeletePending}
90
- aria-label={deleteLabel}
91
- onClick={(event: MouseEvent<HTMLButtonElement>) => {
92
- event.stopPropagation();
93
- onDelete(marker.id);
94
- }}
95
- >
96
- <Trash2 className="size-4" />
97
- </Button>
98
- </ItemActions>
99
- )}
100
- </Item>
101
- </div>
102
- )}
103
- </div>
104
- );
105
- }
@@ -1,75 +0,0 @@
1
- import type { JSX } from 'react';
2
-
3
- import { Crosshair, Minus, Plus, RotateCcw, ZoomIn } from 'lucide-react';
4
-
5
- import { viewer360ClassNames } from '../constants/viewer360ClassNames';
6
- import type { Viewer360ToolbarRenderProps } from '../types';
7
- import { Badge } from '@/components/ui/Badge';
8
- import { Button } from '@/components/ui/Button';
9
- import { CardFooter } from '@/components/ui/Card';
10
- import { Label } from '@/components/ui/Label';
11
- import { Separator } from '@/components/ui/Separator';
12
- import { cn } from '@/components/utils';
13
-
14
- type Viewer360ToolbarProps = Viewer360ToolbarRenderProps;
15
-
16
- export function Viewer360Toolbar({
17
- showDragHint,
18
- showHotspotModeControl,
19
- showZoomControls,
20
- showResetControl,
21
- labels,
22
- isHotspotMode,
23
- zoom,
24
- minZoom,
25
- maxZoom,
26
- isResetDisabled,
27
- onHotspotModeChange,
28
- onZoomIn,
29
- onZoomOut,
30
- onResetView,
31
- }: Viewer360ToolbarProps): JSX.Element {
32
- // ----------------------------------------------------------------------------------------------------
33
- // MARK: Main Component UI
34
- // ----------------------------------------------------------------------------------------------------
35
- return (
36
- <CardFooter className={cn(viewer360ClassNames.toolbar, 'gap-2 border-t px-4 py-3 pt-3')}>
37
- {showDragHint && (
38
- <Label className={cn(viewer360ClassNames.dragHint, 'font-normal text-muted-foreground')}>{labels.dragHint}</Label>
39
- )}
40
-
41
- <div className={viewer360ClassNames.controls}>
42
- {showHotspotModeControl && (
43
- <>
44
- <Button variant={isHotspotMode ? 'default' : 'outline'} size="sm" onClick={() => onHotspotModeChange(!isHotspotMode)}>
45
- <Crosshair className="me-1.5 size-4" />
46
- {labels.addHotspot}
47
- </Button>
48
- <Separator orientation="vertical" className={cn(viewer360ClassNames.divider, 'h-6')} />
49
- </>
50
- )}
51
-
52
- {showZoomControls && (
53
- <>
54
- <Button variant="outline" size="icon-sm" disabled={zoom <= minZoom} aria-label={labels.zoomOut} onClick={onZoomOut}>
55
- <Minus className="size-4" />
56
- </Button>
57
- <Badge variant="outline" className={cn(viewer360ClassNames.zoomDisplay, 'h-8 gap-1 px-2 py-1')}>
58
- <ZoomIn className="size-3 text-muted-foreground" />
59
- {labels.zoom(Math.round(zoom * 100))}
60
- </Badge>
61
- <Button variant="outline" size="icon-sm" disabled={zoom >= maxZoom} aria-label={labels.zoomIn} onClick={onZoomIn}>
62
- <Plus className="size-4" />
63
- </Button>
64
- </>
65
- )}
66
-
67
- {showResetControl && (
68
- <Button variant="outline" size="icon-sm" disabled={isResetDisabled} aria-label={labels.resetView} onClick={onResetView}>
69
- <RotateCcw className="size-4" />
70
- </Button>
71
- )}
72
- </div>
73
- </CardFooter>
74
- );
75
- }
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { applyWheelZoom, isResetDisabled, resolveViewer360Config, stepZoomIn, stepZoomOut } from './adjustViewerZoom';
4
-
5
- describe('adjustViewerZoom', () => {
6
- const config = resolveViewer360Config();
7
-
8
- it('clamps wheel zoom within bounds', () => {
9
- const result = applyWheelZoom(1, -100, { panX: 0, panY: 0 }, config);
10
- expect(result.zoom).toBeGreaterThan(1);
11
- expect(result.zoom).toBeLessThanOrEqual(config.maxZoom);
12
- });
13
-
14
- it('resets pan when zoom returns to minimum', () => {
15
- const result = applyWheelZoom(1.15, 100, { panX: 12, panY: 8 }, config);
16
- expect(result.zoom).toBe(config.minZoom);
17
- expect(result.pan).toEqual({ panX: 0, panY: 0 });
18
- });
19
-
20
- it('steps zoom in and out', () => {
21
- expect(stepZoomIn(1, config)).toBeCloseTo(1.15);
22
- expect(stepZoomOut(1.15, { panX: 4, panY: 2 }, config).zoom).toBeCloseTo(1);
23
- });
24
-
25
- it('detects reset disabled state', () => {
26
- expect(isResetDisabled(1, { panX: 0, panY: 0 }, config)).toBe(true);
27
- expect(isResetDisabled(1.3, { panX: 0, panY: 0 }, config)).toBe(false);
28
- });
29
- });
@@ -1,64 +0,0 @@
1
- import type { Viewer360Config } from '../types';
2
-
3
- import type { PanOffset } from './computeViewerImageLayout';
4
-
5
- type ResolvedViewer360Config = Required<Viewer360Config>;
6
-
7
- export function resolveViewer360Config(config?: Viewer360Config): ResolvedViewer360Config {
8
- return {
9
- minZoom: config?.minZoom ?? 1,
10
- maxZoom: config?.maxZoom ?? 3,
11
- zoomStep: config?.zoomStep ?? 0.15,
12
- dragSensitivity: config?.dragSensitivity ?? 8,
13
- autoRotate: config?.autoRotate ?? false,
14
- autoRotateIntervalMs: config?.autoRotateIntervalMs ?? 100,
15
- autoRotateDirection: config?.autoRotateDirection ?? 'forward',
16
- };
17
- }
18
-
19
- export function clampZoom(zoom: number, config: ResolvedViewer360Config): number {
20
- return Math.min(config.maxZoom, Math.max(config.minZoom, zoom));
21
- }
22
-
23
- export function applyWheelZoom(
24
- currentZoom: number,
25
- deltaY: number,
26
- currentPan: PanOffset,
27
- config: ResolvedViewer360Config
28
- ): { zoom: number; pan: PanOffset } {
29
- const delta = deltaY > 0 ? -config.zoomStep : config.zoomStep;
30
- const zoom = clampZoom(currentZoom + delta, config);
31
-
32
- return {
33
- zoom,
34
- pan: zoom === config.minZoom ? { panX: 0, panY: 0 } : currentPan,
35
- };
36
- }
37
-
38
- export function stepZoomIn(currentZoom: number, config: ResolvedViewer360Config): number {
39
- return clampZoom(currentZoom + config.zoomStep, config);
40
- }
41
-
42
- export function stepZoomOut(
43
- currentZoom: number,
44
- currentPan: PanOffset,
45
- config: ResolvedViewer360Config
46
- ): { zoom: number; pan: PanOffset } {
47
- const zoom = clampZoom(currentZoom - config.zoomStep, config);
48
-
49
- return {
50
- zoom,
51
- pan: zoom === config.minZoom ? { panX: 0, panY: 0 } : currentPan,
52
- };
53
- }
54
-
55
- export function isResetDisabled(zoom: number, pan: PanOffset, config: ResolvedViewer360Config): boolean {
56
- return zoom === config.minZoom && pan.panX === 0 && pan.panY === 0;
57
- }
58
-
59
- export function getViewerCursorClass(isHotspotMode: boolean, zoom: number, isDragging: boolean, minZoom: number): string {
60
- if (isHotspotMode) return 'cursor-crosshair';
61
- if (isDragging) return 'cursor-grabbing';
62
- if (zoom > minZoom) return 'cursor-grab';
63
- return 'cursor-ew-resize';
64
- }
@@ -1,20 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { clampFrameIndex, computeDragFrameIndex } from './computeDragFrameIndex';
4
-
5
- describe('computeDragFrameIndex', () => {
6
- it('wraps frame indices', () => {
7
- expect(clampFrameIndex(-1, 24)).toBe(23);
8
- expect(clampFrameIndex(24, 24)).toBe(0);
9
- });
10
-
11
- it('returns null when drag delta is below sensitivity', () => {
12
- const result = computeDragFrameIndex({ pointerX: 100, frameIndex: 3 }, 104, 24, 8);
13
- expect(result).toBeNull();
14
- });
15
-
16
- it('advances frames based on horizontal drag', () => {
17
- const result = computeDragFrameIndex({ pointerX: 200, frameIndex: 5 }, 120, 24, 8);
18
- expect(result).toBe(15);
19
- });
20
- });
@@ -1,23 +0,0 @@
1
- export function clampFrameIndex(index: number, frameCount: number): number {
2
- if (frameCount === 0) return 0;
3
- return ((index % frameCount) + frameCount) % frameCount;
4
- }
5
-
6
- type DragStart = {
7
- pointerX: number;
8
- frameIndex: number;
9
- };
10
-
11
- export function computeDragFrameIndex(
12
- dragStart: DragStart,
13
- clientX: number,
14
- frameCount: number,
15
- dragSensitivity: number
16
- ): number | null {
17
- const deltaX = clientX - dragStart.pointerX;
18
- const frameDelta = Math.round(-deltaX / dragSensitivity);
19
-
20
- if (frameDelta === 0) return null;
21
-
22
- return clampFrameIndex(dragStart.frameIndex + frameDelta, frameCount);
23
- }
@@ -1,48 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import {
4
- computeHotspotPositionFromClick,
5
- computeHotspotScreenPosition,
6
- computeViewerImageLayout,
7
- } from './computeViewerImageLayout';
8
-
9
- describe('computeViewerImageLayout', () => {
10
- const layout = computeViewerImageLayout({
11
- containerWidth: 800,
12
- containerHeight: 500,
13
- imageWidth: 1600,
14
- imageHeight: 900,
15
- });
16
-
17
- it('letterboxes wide images', () => {
18
- expect(layout.drawWidth).toBe(800);
19
- expect(layout.drawHeight).toBeCloseTo(450);
20
- expect(layout.offsetY).toBeCloseTo(25);
21
- });
22
-
23
- it('maps stored hotspot coordinates to screen space', () => {
24
- const screen = computeHotspotScreenPosition(50, 50, layout, 1);
25
- expect(screen.leftPercent).toBeCloseTo(50, 1);
26
- expect(screen.topPercent).toBeCloseTo(50, 1);
27
- });
28
-
29
- it('derives hotspot coordinates from click position', () => {
30
- const rect = {
31
- left: 0,
32
- top: 0,
33
- width: 800,
34
- height: 500,
35
- right: 800,
36
- bottom: 500,
37
- x: 0,
38
- y: 0,
39
- toJSON: () => ({}),
40
- } as DOMRect;
41
-
42
- const position = computeHotspotPositionFromClick(400, 250, rect, layout, 1);
43
- expect(position.positionX).toBeGreaterThan(0);
44
- expect(position.positionY).toBeGreaterThan(0);
45
- expect(position.positionX).toBeLessThanOrEqual(100);
46
- expect(position.positionY).toBeLessThanOrEqual(100);
47
- });
48
- });
@@ -1,114 +0,0 @@
1
- export type ViewerImageLayout = {
2
- width: number;
3
- height: number;
4
- centerX: number;
5
- centerY: number;
6
- drawWidth: number;
7
- drawHeight: number;
8
- offsetX: number;
9
- offsetY: number;
10
- };
11
-
12
- export type PanOffset = {
13
- panX: number;
14
- panY: number;
15
- };
16
-
17
- type ComputeViewerImageLayoutParams = {
18
- containerWidth: number;
19
- containerHeight: number;
20
- imageWidth: number;
21
- imageHeight: number;
22
- pan?: PanOffset;
23
- };
24
-
25
- export function computeViewerImageLayout({
26
- containerWidth,
27
- containerHeight,
28
- imageWidth,
29
- imageHeight,
30
- pan = { panX: 0, panY: 0 },
31
- }: ComputeViewerImageLayoutParams): ViewerImageLayout {
32
- const imgAspect = imageWidth / imageHeight;
33
- const containerAspect = containerWidth / containerHeight;
34
-
35
- let drawWidth: number;
36
- let drawHeight: number;
37
-
38
- if (imgAspect > containerAspect) {
39
- drawWidth = containerWidth;
40
- drawHeight = containerWidth / imgAspect;
41
- } else {
42
- drawHeight = containerHeight;
43
- drawWidth = containerHeight * imgAspect;
44
- }
45
-
46
- const offsetX = (containerWidth - drawWidth) / 2 + pan.panX;
47
- const offsetY = (containerHeight - drawHeight) / 2 + pan.panY;
48
-
49
- return {
50
- width: containerWidth,
51
- height: containerHeight,
52
- centerX: containerWidth / 2,
53
- centerY: containerHeight / 2,
54
- drawWidth,
55
- drawHeight,
56
- offsetX,
57
- offsetY,
58
- };
59
- }
60
-
61
- export function computeHotspotScreenPosition(
62
- hotspotX: number,
63
- hotspotY: number,
64
- layout: ViewerImageLayout,
65
- zoom: number
66
- ): { leftPercent: number; topPercent: number } {
67
- const baseOffsetX = (layout.width - layout.drawWidth) / 2;
68
- const baseOffsetY = (layout.height - layout.drawHeight) / 2;
69
-
70
- const containerX = (hotspotX / 100) * layout.width;
71
- const containerY = (hotspotY / 100) * layout.height;
72
-
73
- const imageLocalX = (containerX - baseOffsetX) / layout.drawWidth;
74
- const imageLocalY = (containerY - baseOffsetY) / layout.drawHeight;
75
-
76
- const imagePointX = layout.offsetX + imageLocalX * layout.drawWidth;
77
- const imagePointY = layout.offsetY + imageLocalY * layout.drawHeight;
78
-
79
- const screenX = layout.centerX + zoom * (imagePointX - layout.centerX);
80
- const screenY = layout.centerY + zoom * (imagePointY - layout.centerY);
81
-
82
- return {
83
- leftPercent: (screenX / layout.width) * 100,
84
- topPercent: (screenY / layout.height) * 100,
85
- };
86
- }
87
-
88
- export function computeHotspotPositionFromClick(
89
- clientX: number,
90
- clientY: number,
91
- containerRect: DOMRect,
92
- layout: ViewerImageLayout,
93
- zoom: number
94
- ): { positionX: number; positionY: number } {
95
- const clickX = clientX - containerRect.left;
96
- const clickY = clientY - containerRect.top;
97
-
98
- const unzoomedX = layout.centerX + (clickX - layout.centerX) / zoom;
99
- const unzoomedY = layout.centerY + (clickY - layout.centerY) / zoom;
100
-
101
- const baseOffsetX = (layout.width - layout.drawWidth) / 2;
102
- const baseOffsetY = (layout.height - layout.drawHeight) / 2;
103
-
104
- const imageLocalX = Math.min(1, Math.max(0, (unzoomedX - layout.offsetX) / layout.drawWidth));
105
- const imageLocalY = Math.min(1, Math.max(0, (unzoomedY - layout.offsetY) / layout.drawHeight));
106
-
107
- const storedX = baseOffsetX + imageLocalX * layout.drawWidth;
108
- const storedY = baseOffsetY + imageLocalY * layout.drawHeight;
109
-
110
- return {
111
- positionX: Math.min(100, Math.max(0, (storedX / layout.width) * 100)),
112
- positionY: Math.min(100, Math.max(0, (storedY / layout.height) * 100)),
113
- };
114
- }
@@ -1,18 +0,0 @@
1
- import type { PanOffset } from './computeViewerImageLayout';
2
-
3
- type PanStart = {
4
- pointerX: number;
5
- pointerY: number;
6
- panX: number;
7
- panY: number;
8
- };
9
-
10
- export function computeViewerPanOffset(panStart: PanStart, clientX: number, clientY: number): PanOffset {
11
- const deltaX = clientX - panStart.pointerX;
12
- const deltaY = clientY - panStart.pointerY;
13
-
14
- return {
15
- panX: panStart.panX + deltaX,
16
- panY: panStart.panY + deltaY,
17
- };
18
- }
@@ -1,38 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { hotspotToViewer360Marker, toViewer360Hotspots } from './markerHelpers';
4
-
5
- describe('markerHelpers', () => {
6
- it('maps hotspot data to marker content', () => {
7
- const marker = hotspotToViewer360Marker({
8
- id: '1',
9
- frameIndex: 0,
10
- positionX: 40,
11
- positionY: 50,
12
- data: { id: '1', title: 'Scratch', description: 'Front bumper' },
13
- });
14
-
15
- expect(marker).toEqual({ id: '1', title: 'Scratch', description: 'Front bumper' });
16
- });
17
-
18
- it('falls back to hotspot id when data is missing', () => {
19
- const marker = hotspotToViewer360Marker({
20
- id: 'marker-2',
21
- frameIndex: 1,
22
- positionX: 10,
23
- positionY: 20,
24
- });
25
-
26
- expect(marker).toEqual({ id: 'marker-2', title: 'marker-2' });
27
- });
28
-
29
- it('builds hotspot array from marker records', () => {
30
- const hotspots = toViewer360Hotspots([
31
- { id: 'a', frameIndex: 0, positionX: 12, positionY: 34, title: 'Dent' },
32
- ]);
33
-
34
- expect(hotspots).toHaveLength(1);
35
- expect(hotspots[0]?.frameIndex).toBe(0);
36
- expect(hotspots[0]?.positionX).toBe(12);
37
- });
38
- });
@@ -1,33 +0,0 @@
1
- import type { Viewer360Hotspot, Viewer360Marker } from '../types';
2
-
3
- export function hotspotToViewer360Marker<TData>(hotspot: Viewer360Hotspot<TData>): Viewer360Marker {
4
- const data = hotspot.data;
5
-
6
- if (data && typeof data === 'object' && 'title' in data && typeof (data as { title?: unknown }).title === 'string') {
7
- const marker = data as unknown as Viewer360Marker;
8
-
9
- return {
10
- id: marker.id ?? hotspot.id,
11
- title: marker.title,
12
- description: marker.description,
13
- };
14
- }
15
-
16
- return {
17
- id: hotspot.id,
18
- title: hotspot.id,
19
- };
20
- }
21
-
22
- export function toViewer360Hotspots<TData extends Viewer360Marker>(
23
- markers: Array<Viewer360Marker & { frameIndex: number; positionX: number; positionY: number }>,
24
- mapData?: (marker: Viewer360Marker & { frameIndex: number; positionX: number; positionY: number }) => TData
25
- ): Viewer360Hotspot<TData>[] {
26
- return markers.map((marker) => ({
27
- id: marker.id,
28
- frameIndex: marker.frameIndex,
29
- positionX: marker.positionX,
30
- positionY: marker.positionY,
31
- data: mapData ? mapData(marker) : (marker as unknown as TData),
32
- }));
33
- }
@@ -1,46 +0,0 @@
1
- import type { CSSProperties } from 'react';
2
-
3
- import { defaultViewer360Labels } from '../constants/viewer360Labels';
4
- import { viewer360ClassNames } from '../constants/viewer360ClassNames';
5
- import type { Viewer360ClassNames, Viewer360Labels, Viewer360Theme } from '../types';
6
- import { cn } from '@/components/utils';
7
-
8
- export function mergeViewer360Labels(labels?: Viewer360Labels): Required<Viewer360Labels> {
9
- return {
10
- loading: labels?.loading ?? defaultViewer360Labels.loading,
11
- dragHint: labels?.dragHint ?? defaultViewer360Labels.dragHint,
12
- frameIndicator: labels?.frameIndicator ?? defaultViewer360Labels.frameIndicator,
13
- zoom: labels?.zoom ?? defaultViewer360Labels.zoom,
14
- hotspotModeActive: labels?.hotspotModeActive ?? defaultViewer360Labels.hotspotModeActive,
15
- addHotspot: labels?.addHotspot ?? defaultViewer360Labels.addHotspot,
16
- zoomIn: labels?.zoomIn ?? defaultViewer360Labels.zoomIn,
17
- zoomOut: labels?.zoomOut ?? defaultViewer360Labels.zoomOut,
18
- resetView: labels?.resetView ?? defaultViewer360Labels.resetView,
19
- deleteMarker: labels?.deleteMarker ?? defaultViewer360Labels.deleteMarker,
20
- };
21
- }
22
-
23
- export function mergeViewer360ClassNames(classNames?: Viewer360ClassNames): Required<Viewer360ClassNames> {
24
- return {
25
- root: cn(viewer360ClassNames.root, classNames?.root),
26
- viewport: cn(viewer360ClassNames.viewport, classNames?.viewport),
27
- canvas: cn(viewer360ClassNames.canvas, classNames?.canvas),
28
- overlay: cn(viewer360ClassNames.overlay, classNames?.overlay),
29
- loading: cn(viewer360ClassNames.loading, classNames?.loading),
30
- loadingText: cn(viewer360ClassNames.loadingText, classNames?.loadingText),
31
- frameIndicator: cn(viewer360ClassNames.frameIndicator, classNames?.frameIndicator),
32
- hotspotModeBanner: cn(viewer360ClassNames.hotspotModeBanner, classNames?.hotspotModeBanner),
33
- toolbar: cn(viewer360ClassNames.toolbar, classNames?.toolbar),
34
- dragHint: cn(viewer360ClassNames.dragHint, classNames?.dragHint),
35
- controls: cn(viewer360ClassNames.controls, classNames?.controls),
36
- controlButton: cn(viewer360ClassNames.controlButton, classNames?.controlButton),
37
- controlButtonActive: cn(viewer360ClassNames.controlButtonActive, classNames?.controlButtonActive),
38
- controlButtonDisabled: cn(viewer360ClassNames.controlButtonDisabled, classNames?.controlButtonDisabled),
39
- zoomDisplay: cn(viewer360ClassNames.zoomDisplay, classNames?.zoomDisplay),
40
- divider: cn(viewer360ClassNames.divider, classNames?.divider),
41
- };
42
- }
43
-
44
- export function buildViewer360ThemeStyle(theme?: Viewer360Theme): CSSProperties {
45
- return theme ? (theme as CSSProperties) : {};
46
- }