@spacesync/client 0.1.0
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.
- package/README.md +39 -0
- package/dist/analog-clock-picker.d.ts +10 -0
- package/dist/analog-clock-picker.js +408 -0
- package/dist/analog-clock-picker.js.map +1 -0
- package/dist/api.d.ts +222 -0
- package/dist/api.js +167 -0
- package/dist/api.js.map +1 -0
- package/dist/app-modal.d.ts +25 -0
- package/dist/app-modal.js +145 -0
- package/dist/app-modal.js.map +1 -0
- package/dist/app-shell-mobile-nav.d.ts +29 -0
- package/dist/app-shell-mobile-nav.js +122 -0
- package/dist/app-shell-mobile-nav.js.map +1 -0
- package/dist/app-web-shell.d.ts +20 -0
- package/dist/app-web-shell.js +45 -0
- package/dist/app-web-shell.js.map +1 -0
- package/dist/booking-schedule-calendars.d.ts +95 -0
- package/dist/booking-schedule-calendars.js +1343 -0
- package/dist/booking-schedule-calendars.js.map +1 -0
- package/dist/chat-stick-to-bottom.d.ts +22 -0
- package/dist/chat-stick-to-bottom.js +190 -0
- package/dist/chat-stick-to-bottom.js.map +1 -0
- package/dist/detail-page-header.d.ts +16 -0
- package/dist/detail-page-header.js +220 -0
- package/dist/detail-page-header.js.map +1 -0
- package/dist/dialog-CIdvylPK.d.ts +16 -0
- package/dist/errors-D1niseaa.d.ts +34 -0
- package/dist/file-picker-field.d.ts +23 -0
- package/dist/file-picker-field.js +165 -0
- package/dist/file-picker-field.js.map +1 -0
- package/dist/file-viewer-dialog.d.ts +15 -0
- package/dist/file-viewer-dialog.js +387 -0
- package/dist/file-viewer-dialog.js.map +1 -0
- package/dist/floor-switcher.d.ts +14 -0
- package/dist/floor-switcher.js +416 -0
- package/dist/floor-switcher.js.map +1 -0
- package/dist/forms.d.ts +52 -0
- package/dist/forms.js +66 -0
- package/dist/forms.js.map +1 -0
- package/dist/image-picker-field.d.ts +11 -0
- package/dist/image-picker-field.js +135 -0
- package/dist/image-picker-field.js.map +1 -0
- package/dist/layout-canvas-view.d.ts +57 -0
- package/dist/layout-canvas-view.js +974 -0
- package/dist/layout-canvas-view.js.map +1 -0
- package/dist/lib/dateDisplay.d.ts +24 -0
- package/dist/lib/dateDisplay.js +84 -0
- package/dist/lib/dateDisplay.js.map +1 -0
- package/dist/live-resource-meta.d.ts +11 -0
- package/dist/live-resource-meta.js +27 -0
- package/dist/live-resource-meta.js.map +1 -0
- package/dist/live-space-pulse-dot.d.ts +8 -0
- package/dist/live-space-pulse-dot.js +18 -0
- package/dist/live-space-pulse-dot.js.map +1 -0
- package/dist/notification-sidebar-preview.d.ts +37 -0
- package/dist/notification-sidebar-preview.js +121 -0
- package/dist/notification-sidebar-preview.js.map +1 -0
- package/dist/page-list-chrome.d.ts +124 -0
- package/dist/page-list-chrome.js +970 -0
- package/dist/page-list-chrome.js.map +1 -0
- package/dist/platform-support-message-composer.d.ts +38 -0
- package/dist/platform-support-message-composer.js +446 -0
- package/dist/platform-support-message-composer.js.map +1 -0
- package/dist/shell-sidebar-nav.d.ts +16 -0
- package/dist/shell-sidebar-nav.js +69 -0
- package/dist/shell-sidebar-nav.js.map +1 -0
- package/dist/shell-theme.d.ts +6 -0
- package/dist/shell-theme.js +9 -0
- package/dist/shell-theme.js.map +1 -0
- package/dist/shell-top-bar-chrome.d.ts +39 -0
- package/dist/shell-top-bar-chrome.js +105 -0
- package/dist/shell-top-bar-chrome.js.map +1 -0
- package/dist/shell-top-bar.d.ts +16 -0
- package/dist/shell-top-bar.js +71 -0
- package/dist/shell-top-bar.js.map +1 -0
- package/dist/styles/theme.css +223 -0
- package/dist/styles/theme.css.map +1 -0
- package/dist/styles/theme.d.ts +2 -0
- package/dist/support-presence-status.d.ts +11 -0
- package/dist/support-presence-status.js +92 -0
- package/dist/support-presence-status.js.map +1 -0
- package/dist/theme-appearance-control.d.ts +5 -0
- package/dist/theme-appearance-control.js +114 -0
- package/dist/theme-appearance-control.js.map +1 -0
- package/dist/theme-boot-script.d.ts +4 -0
- package/dist/theme-boot-script.js +9 -0
- package/dist/theme-boot-script.js.map +1 -0
- package/dist/theme-provider.d.ts +9 -0
- package/dist/theme-provider.js +25 -0
- package/dist/theme-provider.js.map +1 -0
- package/dist/ui.d.ts +765 -0
- package/dist/ui.js +5637 -0
- package/dist/ui.js.map +1 -0
- package/dist/use-client-pagination.d.ts +22 -0
- package/dist/use-client-pagination.js +51 -0
- package/dist/use-client-pagination.js.map +1 -0
- package/dist/use-client-sort.d.ts +20 -0
- package/dist/use-client-sort.js +41 -0
- package/dist/use-client-sort.js.map +1 -0
- package/package.json +208 -0
|
@@ -0,0 +1,974 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useMemo, useState, useRef, useCallback, useEffect } from 'react';
|
|
3
|
+
import { Minus, Plus, Maximize2, Box, DoorOpen, Armchair, Search, Calendar } from 'lucide-react';
|
|
4
|
+
import { clsx } from 'clsx';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
7
|
+
import { cva } from 'class-variance-authority';
|
|
8
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
10
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
11
|
+
import { useTheme } from 'next-themes';
|
|
12
|
+
|
|
13
|
+
// src/live-space/layout-canvas-view.tsx
|
|
14
|
+
|
|
15
|
+
// src/live-space/layout-canvas-types.ts
|
|
16
|
+
var DEFAULT_CANVAS_WIDTH = 1200;
|
|
17
|
+
var DEFAULT_CANVAS_HEIGHT = 900;
|
|
18
|
+
var LIVE_RESOURCE_TYPE_META = {
|
|
19
|
+
desks: {
|
|
20
|
+
label: "Desk",
|
|
21
|
+
rowAccent: "text-blue-700 bg-blue-50 border-blue-100",
|
|
22
|
+
Icon: Armchair,
|
|
23
|
+
iconTile: "bg-blue-50 text-blue-600 border-blue-100 dark:bg-blue-950/50 dark:text-blue-400 dark:border-blue-900/50"
|
|
24
|
+
},
|
|
25
|
+
rooms: {
|
|
26
|
+
label: "Room",
|
|
27
|
+
rowAccent: "text-orange-700 bg-orange-50 border-orange-100",
|
|
28
|
+
Icon: DoorOpen,
|
|
29
|
+
iconTile: "bg-orange-50 text-orange-600 border-orange-100 dark:bg-orange-950/50 dark:text-orange-400 dark:border-orange-900/50"
|
|
30
|
+
},
|
|
31
|
+
other: {
|
|
32
|
+
label: "Other",
|
|
33
|
+
rowAccent: "text-purple-700 bg-purple-50 border-purple-100",
|
|
34
|
+
Icon: Box,
|
|
35
|
+
iconTile: "bg-purple-50 text-purple-600 border-purple-100 dark:bg-purple-950/50 dark:text-purple-400 dark:border-purple-900/50"
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function cn(...inputs) {
|
|
39
|
+
return twMerge(clsx(inputs));
|
|
40
|
+
}
|
|
41
|
+
var buttonVariants = cva(
|
|
42
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-all disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
43
|
+
{
|
|
44
|
+
variants: {
|
|
45
|
+
variant: {
|
|
46
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80 active:scale-[0.98]",
|
|
47
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 active:bg-destructive/80 active:scale-[0.98] focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 dark:active:bg-destructive/70",
|
|
48
|
+
outline: "border bg-background text-foreground hover:bg-accent hover:text-accent-foreground active:bg-accent/80 active:scale-[0.98] dark:bg-input/30 dark:border-input dark:hover:bg-input/50 dark:active:bg-input/60",
|
|
49
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 active:bg-secondary/70 active:scale-[0.98]",
|
|
50
|
+
ghost: "hover:bg-accent hover:text-accent-foreground active:bg-accent/90 active:scale-[0.98] dark:hover:bg-accent/50 dark:active:bg-accent/40",
|
|
51
|
+
link: "text-primary underline-offset-4 hover:underline active:text-primary/80"
|
|
52
|
+
},
|
|
53
|
+
size: {
|
|
54
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
55
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
56
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
57
|
+
icon: "size-9 rounded-md"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
defaultVariants: {
|
|
61
|
+
variant: "default",
|
|
62
|
+
size: "default"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
var Button = React.forwardRef(function Button2({ className, variant, size, asChild = false, ...props }, ref) {
|
|
67
|
+
const Comp = asChild ? Slot : "button";
|
|
68
|
+
return /* @__PURE__ */ jsx(
|
|
69
|
+
Comp,
|
|
70
|
+
{
|
|
71
|
+
ref,
|
|
72
|
+
"data-slot": "button",
|
|
73
|
+
className: cn(buttonVariants({ variant, size }), className),
|
|
74
|
+
...props
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
Button.displayName = "Button";
|
|
79
|
+
var inputVariants = cva(
|
|
80
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-border bg-card text-foreground flex w-full min-w-0 rounded-md border px-3 py-1 text-base transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:border-blue-400 focus-visible:ring-[3px] focus-visible:ring-blue-500/25 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-red-400 aria-invalid:ring-red-500/20 md:text-sm",
|
|
81
|
+
{
|
|
82
|
+
variants: {
|
|
83
|
+
size: {
|
|
84
|
+
sm: "h-8 text-sm",
|
|
85
|
+
default: "h-9",
|
|
86
|
+
lg: "h-10",
|
|
87
|
+
xl: "h-11"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
defaultVariants: {
|
|
91
|
+
size: "default"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
var Input = React.forwardRef(function Input2({ className, type, size, ...props }, ref) {
|
|
96
|
+
return /* @__PURE__ */ jsx(
|
|
97
|
+
"input",
|
|
98
|
+
{
|
|
99
|
+
ref,
|
|
100
|
+
type,
|
|
101
|
+
"data-slot": "input",
|
|
102
|
+
className: cn(inputVariants({ size }), className),
|
|
103
|
+
...props
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
function SearchField({
|
|
108
|
+
value,
|
|
109
|
+
onChange,
|
|
110
|
+
placeholder,
|
|
111
|
+
className,
|
|
112
|
+
size = "default",
|
|
113
|
+
"aria-label": ariaLabel
|
|
114
|
+
}) {
|
|
115
|
+
const heightClass = size === "lg" ? "h-11" : "h-10";
|
|
116
|
+
const iconSize = size === "lg" ? 16 : 16;
|
|
117
|
+
return /* @__PURE__ */ jsxs(
|
|
118
|
+
"div",
|
|
119
|
+
{
|
|
120
|
+
className: cn(
|
|
121
|
+
"flex items-center gap-2 rounded-md border border-border bg-card px-3 w-full focus-within:border-blue-400 focus-within:ring-[3px] focus-within:ring-blue-500/25 transition-all",
|
|
122
|
+
heightClass,
|
|
123
|
+
className
|
|
124
|
+
),
|
|
125
|
+
children: [
|
|
126
|
+
/* @__PURE__ */ jsx(Search, { size: iconSize, className: "text-muted-foreground shrink-0", strokeWidth: 2.25 }),
|
|
127
|
+
/* @__PURE__ */ jsx(
|
|
128
|
+
Input,
|
|
129
|
+
{
|
|
130
|
+
type: "search",
|
|
131
|
+
value,
|
|
132
|
+
onChange: (e) => onChange(e.target.value),
|
|
133
|
+
placeholder,
|
|
134
|
+
"aria-label": ariaLabel,
|
|
135
|
+
className: "flex-1 border-0 bg-transparent shadow-none focus-visible:ring-0 px-0 min-w-0 h-auto"
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
function PageListSearchField({
|
|
143
|
+
value,
|
|
144
|
+
onChange,
|
|
145
|
+
placeholder,
|
|
146
|
+
className
|
|
147
|
+
}) {
|
|
148
|
+
return /* @__PURE__ */ jsx(
|
|
149
|
+
SearchField,
|
|
150
|
+
{
|
|
151
|
+
value,
|
|
152
|
+
onChange,
|
|
153
|
+
placeholder,
|
|
154
|
+
className
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
function Popover({ ...props }) {
|
|
159
|
+
return /* @__PURE__ */ jsx(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
|
|
160
|
+
}
|
|
161
|
+
function PopoverContent({
|
|
162
|
+
className,
|
|
163
|
+
align = "center",
|
|
164
|
+
sideOffset = 4,
|
|
165
|
+
...props
|
|
166
|
+
}) {
|
|
167
|
+
return /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
168
|
+
PopoverPrimitive.Content,
|
|
169
|
+
{
|
|
170
|
+
"data-slot": "popover-content",
|
|
171
|
+
align,
|
|
172
|
+
sideOffset,
|
|
173
|
+
className: cn(
|
|
174
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border border-white/10 p-4 shadow-md outline-hidden",
|
|
175
|
+
className
|
|
176
|
+
),
|
|
177
|
+
...props
|
|
178
|
+
}
|
|
179
|
+
) });
|
|
180
|
+
}
|
|
181
|
+
function PopoverAnchor({ ...props }) {
|
|
182
|
+
return /* @__PURE__ */ jsx(PopoverPrimitive.Anchor, { "data-slot": "popover-anchor", ...props });
|
|
183
|
+
}
|
|
184
|
+
function TooltipProvider({
|
|
185
|
+
delayDuration = 0,
|
|
186
|
+
...props
|
|
187
|
+
}) {
|
|
188
|
+
return /* @__PURE__ */ jsx(
|
|
189
|
+
TooltipPrimitive.Provider,
|
|
190
|
+
{
|
|
191
|
+
"data-slot": "tooltip-provider",
|
|
192
|
+
delayDuration,
|
|
193
|
+
...props
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
function Tooltip({ ...props }) {
|
|
198
|
+
return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props }) });
|
|
199
|
+
}
|
|
200
|
+
function TooltipTrigger({ ...props }) {
|
|
201
|
+
return /* @__PURE__ */ jsx(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
202
|
+
}
|
|
203
|
+
function TooltipContent({
|
|
204
|
+
className,
|
|
205
|
+
sideOffset = 0,
|
|
206
|
+
children,
|
|
207
|
+
...props
|
|
208
|
+
}) {
|
|
209
|
+
return /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
|
|
210
|
+
TooltipPrimitive.Content,
|
|
211
|
+
{
|
|
212
|
+
"data-slot": "tooltip-content",
|
|
213
|
+
sideOffset,
|
|
214
|
+
className: cn(
|
|
215
|
+
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
|
216
|
+
className
|
|
217
|
+
),
|
|
218
|
+
...props,
|
|
219
|
+
children: [
|
|
220
|
+
children,
|
|
221
|
+
/* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { className: "bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
) });
|
|
225
|
+
}
|
|
226
|
+
function matchesCanvasResourceSearch(resourceName, planName, query) {
|
|
227
|
+
const q = query.trim().toLowerCase();
|
|
228
|
+
if (!q) return true;
|
|
229
|
+
const name = resourceName.toLowerCase();
|
|
230
|
+
const plan = planName?.toLowerCase() ?? "";
|
|
231
|
+
return name.includes(q) || plan.includes(q);
|
|
232
|
+
}
|
|
233
|
+
function resourceMatchesSearch(row, query) {
|
|
234
|
+
return matchesCanvasResourceSearch(row.resource.name, row.plan?.name, query);
|
|
235
|
+
}
|
|
236
|
+
var CLICK_THRESHOLD_PX = 5;
|
|
237
|
+
var POPOVER_CLOSE_DELAY_MS = 250;
|
|
238
|
+
var CANVAS_QUICK_BOOK_POPOVER_CLASS = "z-[100] w-56 border-border bg-card p-3 text-card-foreground shadow-lg";
|
|
239
|
+
function usePrefersHover() {
|
|
240
|
+
const [prefersHover, setPrefersHover] = useState(
|
|
241
|
+
() => typeof window !== "undefined" && window.matchMedia("(hover: hover)").matches
|
|
242
|
+
);
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
const mql = window.matchMedia("(hover: hover)");
|
|
245
|
+
const onChange = () => setPrefersHover(mql.matches);
|
|
246
|
+
mql.addEventListener("change", onChange);
|
|
247
|
+
return () => mql.removeEventListener("change", onChange);
|
|
248
|
+
}, []);
|
|
249
|
+
return prefersHover;
|
|
250
|
+
}
|
|
251
|
+
function isCanvasResourceTarget(target) {
|
|
252
|
+
return target instanceof HTMLElement && target.closest("[data-canvas-resource]") != null;
|
|
253
|
+
}
|
|
254
|
+
function ResourceQuickBookPopoverContent({
|
|
255
|
+
row,
|
|
256
|
+
onBook,
|
|
257
|
+
getBookingHref
|
|
258
|
+
}) {
|
|
259
|
+
const { resource, merged } = row;
|
|
260
|
+
const meta = LIVE_RESOURCE_TYPE_META[resource.resourceType];
|
|
261
|
+
const TypeIcon = meta.Icon;
|
|
262
|
+
const bookingHref = getBookingHref?.(resource.id);
|
|
263
|
+
const handleBookPointerDown = (e) => {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
e.stopPropagation();
|
|
266
|
+
onBook?.(resource.id);
|
|
267
|
+
};
|
|
268
|
+
const bookLabel = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
269
|
+
/* @__PURE__ */ jsx(Calendar, { size: 14 }),
|
|
270
|
+
"Book"
|
|
271
|
+
] });
|
|
272
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
273
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [
|
|
274
|
+
/* @__PURE__ */ jsx(
|
|
275
|
+
"span",
|
|
276
|
+
{
|
|
277
|
+
className: cn(
|
|
278
|
+
"flex h-8 w-8 shrink-0 items-center justify-center rounded-lg border",
|
|
279
|
+
meta.iconTile
|
|
280
|
+
),
|
|
281
|
+
"aria-hidden": true,
|
|
282
|
+
children: /* @__PURE__ */ jsx(TypeIcon, { size: 16, strokeWidth: 1.75 })
|
|
283
|
+
}
|
|
284
|
+
),
|
|
285
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
286
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold leading-snug text-card-foreground", children: resource.name }),
|
|
287
|
+
/* @__PURE__ */ jsxs(
|
|
288
|
+
"span",
|
|
289
|
+
{
|
|
290
|
+
className: cn(
|
|
291
|
+
"mt-1.5 inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium",
|
|
292
|
+
merged.occupied ? "border-red-100 bg-red-50 text-red-700 dark:border-red-900/50 dark:bg-red-950/50 dark:text-red-300" : "border-emerald-100 bg-emerald-50 text-emerald-800 dark:border-emerald-900/50 dark:bg-emerald-950/50 dark:text-emerald-300"
|
|
293
|
+
),
|
|
294
|
+
children: [
|
|
295
|
+
/* @__PURE__ */ jsx(
|
|
296
|
+
"span",
|
|
297
|
+
{
|
|
298
|
+
className: cn(
|
|
299
|
+
"h-1.5 w-1.5 shrink-0 rounded-full",
|
|
300
|
+
merged.occupied ? "bg-red-500" : "bg-emerald-500"
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
),
|
|
304
|
+
merged.occupied ? "Occupied" : "Free"
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
),
|
|
308
|
+
merged.occupied && merged.expectedFreeLabel && merged.expectedFreeLabel !== "\u2014" ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: merged.expectedFreeLabel }) : null
|
|
309
|
+
] })
|
|
310
|
+
] }),
|
|
311
|
+
onBook ? bookingHref ? /* @__PURE__ */ jsx(Button, { asChild: true, size: "sm", className: "w-full", children: /* @__PURE__ */ jsx("a", { href: bookingHref, onPointerDown: handleBookPointerDown, children: bookLabel }) }) : /* @__PURE__ */ jsx(
|
|
312
|
+
Button,
|
|
313
|
+
{
|
|
314
|
+
type: "button",
|
|
315
|
+
size: "sm",
|
|
316
|
+
className: "w-full",
|
|
317
|
+
onPointerDown: handleBookPointerDown,
|
|
318
|
+
children: bookLabel
|
|
319
|
+
}
|
|
320
|
+
) : /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: "Booking is not available for your account." })
|
|
321
|
+
] });
|
|
322
|
+
}
|
|
323
|
+
var MIN_ZOOM = 0.12;
|
|
324
|
+
var MAX_ZOOM = 4;
|
|
325
|
+
var MIN_CARD_W = 90;
|
|
326
|
+
var MIN_CARD_H = 56;
|
|
327
|
+
var CANVAS_GRID_SIZE = 40;
|
|
328
|
+
function canvasGridBackground(isDark) {
|
|
329
|
+
const line = isDark ? "oklch(0.35 0 0)" : "#e5e7eb";
|
|
330
|
+
return `linear-gradient(to right, ${line} 1px, transparent 1px),linear-gradient(to bottom, ${line} 1px, transparent 1px)`;
|
|
331
|
+
}
|
|
332
|
+
var CANVAS_GRID_BG_SIZE = `${CANVAS_GRID_SIZE}px ${CANVAS_GRID_SIZE}px`;
|
|
333
|
+
var TYPE_META_CARD = {
|
|
334
|
+
desks: {
|
|
335
|
+
border: "border-amber-200",
|
|
336
|
+
bg: "bg-card",
|
|
337
|
+
iconBg: "bg-amber-100",
|
|
338
|
+
iconColor: "text-amber-600",
|
|
339
|
+
badgeBg: "bg-amber-50",
|
|
340
|
+
badgeText: "text-amber-700"
|
|
341
|
+
},
|
|
342
|
+
rooms: {
|
|
343
|
+
border: "border-blue-200",
|
|
344
|
+
bg: "bg-card",
|
|
345
|
+
iconBg: "bg-blue-100",
|
|
346
|
+
iconColor: "text-blue-600 dark:text-blue-400",
|
|
347
|
+
badgeBg: "bg-blue-50",
|
|
348
|
+
badgeText: "text-blue-700 dark:text-blue-400"
|
|
349
|
+
},
|
|
350
|
+
other: {
|
|
351
|
+
border: "border-slate-200",
|
|
352
|
+
bg: "bg-card",
|
|
353
|
+
iconBg: "bg-slate-100",
|
|
354
|
+
iconColor: "text-slate-600",
|
|
355
|
+
badgeBg: "bg-slate-50",
|
|
356
|
+
badgeText: "text-slate-600"
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
var TYPE_ICON = {
|
|
360
|
+
desks: Armchair,
|
|
361
|
+
rooms: DoorOpen,
|
|
362
|
+
other: Box
|
|
363
|
+
};
|
|
364
|
+
function isFloorResourceType(type) {
|
|
365
|
+
return type === "desks" || type === "rooms";
|
|
366
|
+
}
|
|
367
|
+
function poolSlotsLabel(totalSlots) {
|
|
368
|
+
if (totalSlots == null) return null;
|
|
369
|
+
return `${totalSlots} slot${totalSlots !== 1 ? "s" : ""}`;
|
|
370
|
+
}
|
|
371
|
+
function PoolResourceChip({
|
|
372
|
+
row,
|
|
373
|
+
onClick,
|
|
374
|
+
quickBookConfig,
|
|
375
|
+
searchQuery,
|
|
376
|
+
isMatch
|
|
377
|
+
}) {
|
|
378
|
+
const { resource, merged } = row;
|
|
379
|
+
const m = TYPE_META_CARD.other;
|
|
380
|
+
const slots = poolSlotsLabel(resource.totalSlots);
|
|
381
|
+
const searchActive = searchQuery.trim().length > 0;
|
|
382
|
+
const chip = /* @__PURE__ */ jsxs(
|
|
383
|
+
"button",
|
|
384
|
+
{
|
|
385
|
+
type: "button",
|
|
386
|
+
"data-canvas-resource": "",
|
|
387
|
+
onClick: (e) => {
|
|
388
|
+
e.stopPropagation();
|
|
389
|
+
if (quickBookConfig) {
|
|
390
|
+
if (!quickBookConfig.shouldIgnoreToggle()) quickBookConfig.onToggle();
|
|
391
|
+
} else onClick?.();
|
|
392
|
+
},
|
|
393
|
+
onMouseEnter: () => {
|
|
394
|
+
if (quickBookConfig?.hover.prefersHover) {
|
|
395
|
+
quickBookConfig.hover.clearClose();
|
|
396
|
+
quickBookConfig.onOpen();
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
onMouseLeave: () => {
|
|
400
|
+
if (quickBookConfig?.hover.prefersHover) quickBookConfig.hover.scheduleClose();
|
|
401
|
+
},
|
|
402
|
+
className: cn(
|
|
403
|
+
"inline-flex items-center gap-2 shrink-0 px-3 py-1.5 rounded-lg border bg-card hover:bg-accent transition-colors text-left",
|
|
404
|
+
m.border,
|
|
405
|
+
searchActive && !isMatch && "opacity-40 saturate-50"
|
|
406
|
+
),
|
|
407
|
+
children: [
|
|
408
|
+
/* @__PURE__ */ jsx(
|
|
409
|
+
"div",
|
|
410
|
+
{
|
|
411
|
+
className: cn(
|
|
412
|
+
"w-6 h-6 rounded-md flex items-center justify-center shrink-0",
|
|
413
|
+
m.iconBg,
|
|
414
|
+
m.iconColor
|
|
415
|
+
),
|
|
416
|
+
children: /* @__PURE__ */ jsx(Box, { size: 12 })
|
|
417
|
+
}
|
|
418
|
+
),
|
|
419
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground whitespace-nowrap", children: resource.name }),
|
|
420
|
+
/* @__PURE__ */ jsx(
|
|
421
|
+
"span",
|
|
422
|
+
{
|
|
423
|
+
className: cn(
|
|
424
|
+
"h-2 w-2 rounded-full shrink-0",
|
|
425
|
+
merged.occupied ? "bg-red-500" : "bg-emerald-500"
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
),
|
|
429
|
+
slots && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground whitespace-nowrap", children: slots })
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
if (!quickBookConfig) return chip;
|
|
434
|
+
return /* @__PURE__ */ jsxs(
|
|
435
|
+
Popover,
|
|
436
|
+
{
|
|
437
|
+
modal: false,
|
|
438
|
+
open: quickBookConfig.open,
|
|
439
|
+
onOpenChange: quickBookConfig.onOpenChange,
|
|
440
|
+
children: [
|
|
441
|
+
/* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: chip }),
|
|
442
|
+
/* @__PURE__ */ jsx(
|
|
443
|
+
PopoverContent,
|
|
444
|
+
{
|
|
445
|
+
side: "top",
|
|
446
|
+
align: "center",
|
|
447
|
+
sideOffset: 6,
|
|
448
|
+
className: CANVAS_QUICK_BOOK_POPOVER_CLASS,
|
|
449
|
+
onOpenAutoFocus: (e) => e.preventDefault(),
|
|
450
|
+
onMouseEnter: quickBookConfig.hover.clearClose,
|
|
451
|
+
onMouseLeave: quickBookConfig.hover.scheduleClose,
|
|
452
|
+
children: /* @__PURE__ */ jsx(
|
|
453
|
+
ResourceQuickBookPopoverContent,
|
|
454
|
+
{
|
|
455
|
+
row,
|
|
456
|
+
onBook: quickBookConfig.quickBook.onBook,
|
|
457
|
+
getBookingHref: quickBookConfig.quickBook.getBookingHref
|
|
458
|
+
}
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
)
|
|
462
|
+
]
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
function PlacedCardView({
|
|
467
|
+
id,
|
|
468
|
+
rect,
|
|
469
|
+
row,
|
|
470
|
+
onSelect,
|
|
471
|
+
quickBookConfig,
|
|
472
|
+
searchQuery,
|
|
473
|
+
isMatch
|
|
474
|
+
}) {
|
|
475
|
+
const { resource, merged, plan } = row;
|
|
476
|
+
const type = resource.resourceType;
|
|
477
|
+
const m = TYPE_META_CARD[type];
|
|
478
|
+
const TypeIcon = TYPE_ICON[type];
|
|
479
|
+
const w = Math.max(rect.w, MIN_CARD_W);
|
|
480
|
+
const h = Math.max(rect.h, MIN_CARD_H);
|
|
481
|
+
const statusLabel = merged.occupied ? "Occupied" : merged.manual ? "Vacant" : "Free";
|
|
482
|
+
const pointerStart = useRef(null);
|
|
483
|
+
const searchActive = searchQuery.trim().length > 0;
|
|
484
|
+
const handlePointerDown = (e) => {
|
|
485
|
+
if (e.button !== 0) return;
|
|
486
|
+
e.stopPropagation();
|
|
487
|
+
pointerStart.current = { x: e.clientX, y: e.clientY };
|
|
488
|
+
};
|
|
489
|
+
const handlePointerUp = (e) => {
|
|
490
|
+
e.stopPropagation();
|
|
491
|
+
const start = pointerStart.current;
|
|
492
|
+
pointerStart.current = null;
|
|
493
|
+
if (!start || quickBookConfig) return;
|
|
494
|
+
if (Math.hypot(e.clientX - start.x, e.clientY - start.y) < CLICK_THRESHOLD_PX) {
|
|
495
|
+
onSelect?.(id);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const statusDot = /* @__PURE__ */ jsx(
|
|
499
|
+
"span",
|
|
500
|
+
{
|
|
501
|
+
className: cn(
|
|
502
|
+
"block h-2.5 w-2.5 rounded-full ring-2 ring-white shadow-sm",
|
|
503
|
+
merged.occupied ? "bg-red-500 animate-pulse" : "bg-emerald-500"
|
|
504
|
+
),
|
|
505
|
+
"aria-label": statusLabel
|
|
506
|
+
}
|
|
507
|
+
);
|
|
508
|
+
const card = /* @__PURE__ */ jsxs(
|
|
509
|
+
"button",
|
|
510
|
+
{
|
|
511
|
+
type: "button",
|
|
512
|
+
"data-canvas-resource": "",
|
|
513
|
+
style: {
|
|
514
|
+
position: "absolute",
|
|
515
|
+
left: rect.x,
|
|
516
|
+
top: rect.y,
|
|
517
|
+
width: w,
|
|
518
|
+
height: h,
|
|
519
|
+
touchAction: "manipulation"
|
|
520
|
+
},
|
|
521
|
+
className: cn(
|
|
522
|
+
"rounded-xl border shadow-sm overflow-hidden select-none text-left cursor-pointer hover:shadow-md transition-shadow focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40",
|
|
523
|
+
m.border,
|
|
524
|
+
m.bg,
|
|
525
|
+
searchActive && !isMatch && "opacity-40 saturate-50"
|
|
526
|
+
),
|
|
527
|
+
onPointerDown: handlePointerDown,
|
|
528
|
+
onPointerUp: handlePointerUp,
|
|
529
|
+
onPointerCancel: handlePointerUp,
|
|
530
|
+
onClick: (e) => {
|
|
531
|
+
e.stopPropagation();
|
|
532
|
+
if (quickBookConfig && !quickBookConfig.shouldIgnoreToggle()) {
|
|
533
|
+
quickBookConfig.onToggle();
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
onMouseEnter: () => {
|
|
537
|
+
if (quickBookConfig?.hover.prefersHover) {
|
|
538
|
+
quickBookConfig.hover.clearClose();
|
|
539
|
+
quickBookConfig.onOpen();
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
onMouseLeave: () => {
|
|
543
|
+
if (quickBookConfig?.hover.prefersHover) quickBookConfig.hover.scheduleClose();
|
|
544
|
+
},
|
|
545
|
+
children: [
|
|
546
|
+
/* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 z-10 pointer-events-none", children: quickBookConfig ? statusDot : /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
547
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: statusDot }),
|
|
548
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "text-xs", children: statusLabel })
|
|
549
|
+
] }) }),
|
|
550
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col justify-between p-3 pointer-events-none", children: [
|
|
551
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 min-w-0 pr-4", children: [
|
|
552
|
+
/* @__PURE__ */ jsx(
|
|
553
|
+
"div",
|
|
554
|
+
{
|
|
555
|
+
className: cn(
|
|
556
|
+
"shrink-0 rounded-lg flex items-center justify-center",
|
|
557
|
+
m.iconBg,
|
|
558
|
+
m.iconColor
|
|
559
|
+
),
|
|
560
|
+
style: { width: 28, height: 28 },
|
|
561
|
+
children: /* @__PURE__ */ jsx(TypeIcon, { size: 14, strokeWidth: 2 })
|
|
562
|
+
}
|
|
563
|
+
),
|
|
564
|
+
/* @__PURE__ */ jsx(
|
|
565
|
+
"span",
|
|
566
|
+
{
|
|
567
|
+
className: "text-xs font-semibold text-foreground leading-tight pt-0.5 line-clamp-2 min-w-0 flex-1",
|
|
568
|
+
style: { wordBreak: "break-word" },
|
|
569
|
+
children: resource.name
|
|
570
|
+
}
|
|
571
|
+
)
|
|
572
|
+
] }),
|
|
573
|
+
plan && /* @__PURE__ */ jsx(
|
|
574
|
+
"span",
|
|
575
|
+
{
|
|
576
|
+
className: cn(
|
|
577
|
+
"text-[9px] font-medium px-1.5 py-0.5 rounded-md self-start",
|
|
578
|
+
m.badgeBg,
|
|
579
|
+
m.badgeText
|
|
580
|
+
),
|
|
581
|
+
children: plan.name
|
|
582
|
+
}
|
|
583
|
+
)
|
|
584
|
+
] })
|
|
585
|
+
]
|
|
586
|
+
}
|
|
587
|
+
);
|
|
588
|
+
if (!quickBookConfig) return card;
|
|
589
|
+
return /* @__PURE__ */ jsxs(
|
|
590
|
+
Popover,
|
|
591
|
+
{
|
|
592
|
+
modal: false,
|
|
593
|
+
open: quickBookConfig.open,
|
|
594
|
+
onOpenChange: quickBookConfig.onOpenChange,
|
|
595
|
+
children: [
|
|
596
|
+
/* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: card }),
|
|
597
|
+
/* @__PURE__ */ jsx(
|
|
598
|
+
PopoverContent,
|
|
599
|
+
{
|
|
600
|
+
side: "top",
|
|
601
|
+
align: "center",
|
|
602
|
+
sideOffset: 6,
|
|
603
|
+
className: CANVAS_QUICK_BOOK_POPOVER_CLASS,
|
|
604
|
+
onOpenAutoFocus: (e) => e.preventDefault(),
|
|
605
|
+
onMouseEnter: quickBookConfig.hover.clearClose,
|
|
606
|
+
onMouseLeave: quickBookConfig.hover.scheduleClose,
|
|
607
|
+
children: /* @__PURE__ */ jsx(
|
|
608
|
+
ResourceQuickBookPopoverContent,
|
|
609
|
+
{
|
|
610
|
+
row,
|
|
611
|
+
onBook: quickBookConfig.quickBook.onBook,
|
|
612
|
+
getBookingHref: quickBookConfig.quickBook.getBookingHref
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
}
|
|
616
|
+
)
|
|
617
|
+
]
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
function LayoutCanvasView({
|
|
622
|
+
layout,
|
|
623
|
+
rows,
|
|
624
|
+
onResourceSelect,
|
|
625
|
+
resourceQuickBook,
|
|
626
|
+
searchQuery = "",
|
|
627
|
+
onSearchQueryChange,
|
|
628
|
+
searchPlaceholder = "Search resources\u2026"
|
|
629
|
+
}) {
|
|
630
|
+
const { resolvedTheme } = useTheme();
|
|
631
|
+
const isDarkCanvas = resolvedTheme === "dark";
|
|
632
|
+
const { placedItems, bgImage } = layout;
|
|
633
|
+
const prefersHover = usePrefersHover();
|
|
634
|
+
const canvasSize = useMemo(
|
|
635
|
+
() => ({
|
|
636
|
+
w: layout.canvasWidth ?? DEFAULT_CANVAS_WIDTH,
|
|
637
|
+
h: layout.canvasHeight ?? DEFAULT_CANVAS_HEIGHT
|
|
638
|
+
}),
|
|
639
|
+
[layout.canvasWidth, layout.canvasHeight]
|
|
640
|
+
);
|
|
641
|
+
const [zoom, setZoom] = useState(0.5);
|
|
642
|
+
const [pan, setPan] = useState({ x: 40, y: 40 });
|
|
643
|
+
const [isPanning, setIsPanning] = useState(false);
|
|
644
|
+
const [openResourceId, setOpenResourceId] = useState(null);
|
|
645
|
+
const viewportRef = useRef(null);
|
|
646
|
+
const gestureRef = useRef(null);
|
|
647
|
+
const zoomRef = useRef(zoom);
|
|
648
|
+
const panRef = useRef(pan);
|
|
649
|
+
const popoverCloseTimerRef = useRef(null);
|
|
650
|
+
const suppressResourceToggleRef = useRef(false);
|
|
651
|
+
const clearPopoverCloseTimer = useCallback(() => {
|
|
652
|
+
if (popoverCloseTimerRef.current) {
|
|
653
|
+
clearTimeout(popoverCloseTimerRef.current);
|
|
654
|
+
popoverCloseTimerRef.current = null;
|
|
655
|
+
}
|
|
656
|
+
}, []);
|
|
657
|
+
const schedulePopoverClose = useCallback(() => {
|
|
658
|
+
clearPopoverCloseTimer();
|
|
659
|
+
popoverCloseTimerRef.current = setTimeout(() => {
|
|
660
|
+
setOpenResourceId(null);
|
|
661
|
+
popoverCloseTimerRef.current = null;
|
|
662
|
+
}, POPOVER_CLOSE_DELAY_MS);
|
|
663
|
+
}, [clearPopoverCloseTimer]);
|
|
664
|
+
const popoverHover = useMemo(
|
|
665
|
+
() => ({
|
|
666
|
+
prefersHover,
|
|
667
|
+
clearClose: clearPopoverCloseTimer,
|
|
668
|
+
scheduleClose: schedulePopoverClose
|
|
669
|
+
}),
|
|
670
|
+
[prefersHover, clearPopoverCloseTimer, schedulePopoverClose]
|
|
671
|
+
);
|
|
672
|
+
const openPopover = useCallback(
|
|
673
|
+
(id) => {
|
|
674
|
+
clearPopoverCloseTimer();
|
|
675
|
+
setOpenResourceId(id);
|
|
676
|
+
},
|
|
677
|
+
[clearPopoverCloseTimer]
|
|
678
|
+
);
|
|
679
|
+
const togglePopover = useCallback(
|
|
680
|
+
(id) => {
|
|
681
|
+
clearPopoverCloseTimer();
|
|
682
|
+
setOpenResourceId((current) => current === id ? null : id);
|
|
683
|
+
},
|
|
684
|
+
[clearPopoverCloseTimer]
|
|
685
|
+
);
|
|
686
|
+
useEffect(() => () => clearPopoverCloseTimer(), [clearPopoverCloseTimer]);
|
|
687
|
+
useEffect(() => {
|
|
688
|
+
zoomRef.current = zoom;
|
|
689
|
+
}, [zoom]);
|
|
690
|
+
useEffect(() => {
|
|
691
|
+
panRef.current = pan;
|
|
692
|
+
}, [pan]);
|
|
693
|
+
const rowById = useMemo(() => new Map(rows.map((r) => [r.resource.id, r])), [rows]);
|
|
694
|
+
const otherRows = useMemo(
|
|
695
|
+
() => rows.filter((r) => r.resource.resourceType === "other"),
|
|
696
|
+
[rows]
|
|
697
|
+
);
|
|
698
|
+
const fitCanvas = useCallback(() => {
|
|
699
|
+
const vp = viewportRef.current;
|
|
700
|
+
if (!vp) return;
|
|
701
|
+
const vpRect = vp.getBoundingClientRect();
|
|
702
|
+
const newZoom = Math.max(
|
|
703
|
+
MIN_ZOOM,
|
|
704
|
+
Math.min(
|
|
705
|
+
MAX_ZOOM,
|
|
706
|
+
Math.min((vpRect.width - 80) / canvasSize.w, (vpRect.height - 80) / canvasSize.h)
|
|
707
|
+
)
|
|
708
|
+
);
|
|
709
|
+
setPan({
|
|
710
|
+
x: (vpRect.width - canvasSize.w * newZoom) / 2,
|
|
711
|
+
y: (vpRect.height - canvasSize.h * newZoom) / 2
|
|
712
|
+
});
|
|
713
|
+
setZoom(newZoom);
|
|
714
|
+
}, [canvasSize.h, canvasSize.w]);
|
|
715
|
+
useEffect(() => {
|
|
716
|
+
fitCanvas();
|
|
717
|
+
}, [fitCanvas]);
|
|
718
|
+
useEffect(() => {
|
|
719
|
+
const el = viewportRef.current;
|
|
720
|
+
if (!el) return;
|
|
721
|
+
const onWheel = (e) => {
|
|
722
|
+
e.preventDefault();
|
|
723
|
+
const vpRect = el.getBoundingClientRect();
|
|
724
|
+
const mx = e.clientX - vpRect.left;
|
|
725
|
+
const my = e.clientY - vpRect.top;
|
|
726
|
+
const factor = e.deltaY < 0 ? 1.08 : 1 / 1.08;
|
|
727
|
+
const curZ = zoomRef.current;
|
|
728
|
+
const newZ = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, curZ * factor));
|
|
729
|
+
const curP = panRef.current;
|
|
730
|
+
setZoom(newZ);
|
|
731
|
+
setPan({
|
|
732
|
+
x: mx - (mx - curP.x) * (newZ / curZ),
|
|
733
|
+
y: my - (my - curP.y) * (newZ / curZ)
|
|
734
|
+
});
|
|
735
|
+
};
|
|
736
|
+
el.addEventListener("wheel", onWheel, { passive: false });
|
|
737
|
+
return () => el.removeEventListener("wheel", onWheel);
|
|
738
|
+
}, []);
|
|
739
|
+
const stepZoom = (delta) => {
|
|
740
|
+
const vp = viewportRef.current;
|
|
741
|
+
const curZ = zoomRef.current;
|
|
742
|
+
const newZ = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, curZ + delta));
|
|
743
|
+
if (vp) {
|
|
744
|
+
const vpRect = vp.getBoundingClientRect();
|
|
745
|
+
const cx = vpRect.width / 2;
|
|
746
|
+
const cy = vpRect.height / 2;
|
|
747
|
+
const curP = panRef.current;
|
|
748
|
+
setPan({
|
|
749
|
+
x: cx - (cx - curP.x) * (newZ / curZ),
|
|
750
|
+
y: cy - (cy - curP.y) * (newZ / curZ)
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
setZoom(newZ);
|
|
754
|
+
};
|
|
755
|
+
useEffect(() => {
|
|
756
|
+
const onKeyDown = (e) => {
|
|
757
|
+
const tag = e.target?.tagName;
|
|
758
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
|
|
759
|
+
if (e.key === "+" || e.key === "=") {
|
|
760
|
+
e.preventDefault();
|
|
761
|
+
stepZoom(0.15);
|
|
762
|
+
} else if (e.key === "-" || e.key === "_") {
|
|
763
|
+
e.preventDefault();
|
|
764
|
+
stepZoom(-0.15);
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
window.addEventListener("keydown", onKeyDown);
|
|
768
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
769
|
+
});
|
|
770
|
+
const handleViewportPointerDown = (e) => {
|
|
771
|
+
if (e.button !== 0 && e.button !== 1) return;
|
|
772
|
+
if (isCanvasResourceTarget(e.target)) return;
|
|
773
|
+
clearPopoverCloseTimer();
|
|
774
|
+
setOpenResourceId(null);
|
|
775
|
+
e.preventDefault();
|
|
776
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
777
|
+
setIsPanning(true);
|
|
778
|
+
gestureRef.current = {
|
|
779
|
+
type: "pan",
|
|
780
|
+
startMX: e.clientX,
|
|
781
|
+
startMY: e.clientY,
|
|
782
|
+
startPanX: panRef.current.x,
|
|
783
|
+
startPanY: panRef.current.y
|
|
784
|
+
};
|
|
785
|
+
};
|
|
786
|
+
const handleViewportPointerMove = (e) => {
|
|
787
|
+
const g = gestureRef.current;
|
|
788
|
+
if (g?.type !== "pan") return;
|
|
789
|
+
setPan({
|
|
790
|
+
x: g.startPanX + (e.clientX - g.startMX),
|
|
791
|
+
y: g.startPanY + (e.clientY - g.startMY)
|
|
792
|
+
});
|
|
793
|
+
};
|
|
794
|
+
const handleViewportPointerUp = () => {
|
|
795
|
+
if (gestureRef.current?.type === "pan") {
|
|
796
|
+
gestureRef.current = null;
|
|
797
|
+
setIsPanning(false);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
const handleSelect = (id) => onResourceSelect?.(id);
|
|
801
|
+
const quickBookConfigFor = useCallback(
|
|
802
|
+
(resourceId) => {
|
|
803
|
+
if (!resourceQuickBook) return void 0;
|
|
804
|
+
const wrappedOnBook = resourceQuickBook.onBook ? (id) => {
|
|
805
|
+
suppressResourceToggleRef.current = true;
|
|
806
|
+
clearPopoverCloseTimer();
|
|
807
|
+
setOpenResourceId(null);
|
|
808
|
+
resourceQuickBook.onBook?.(id);
|
|
809
|
+
window.setTimeout(() => {
|
|
810
|
+
suppressResourceToggleRef.current = false;
|
|
811
|
+
}, 400);
|
|
812
|
+
} : void 0;
|
|
813
|
+
return {
|
|
814
|
+
quickBook: {
|
|
815
|
+
...resourceQuickBook,
|
|
816
|
+
onBook: wrappedOnBook
|
|
817
|
+
},
|
|
818
|
+
open: openResourceId === resourceId,
|
|
819
|
+
onOpenChange: (open) => {
|
|
820
|
+
clearPopoverCloseTimer();
|
|
821
|
+
setOpenResourceId(open ? resourceId : null);
|
|
822
|
+
},
|
|
823
|
+
hover: popoverHover,
|
|
824
|
+
onOpen: () => openPopover(resourceId),
|
|
825
|
+
onToggle: () => togglePopover(resourceId),
|
|
826
|
+
shouldIgnoreToggle: () => suppressResourceToggleRef.current
|
|
827
|
+
};
|
|
828
|
+
},
|
|
829
|
+
[
|
|
830
|
+
resourceQuickBook,
|
|
831
|
+
openResourceId,
|
|
832
|
+
clearPopoverCloseTimer,
|
|
833
|
+
popoverHover,
|
|
834
|
+
openPopover,
|
|
835
|
+
togglePopover
|
|
836
|
+
]
|
|
837
|
+
);
|
|
838
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 min-h-0 bg-card", children: [
|
|
839
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 p-2 border-b border-border bg-card shrink-0", children: [
|
|
840
|
+
onSearchQueryChange != null ? /* @__PURE__ */ jsx(
|
|
841
|
+
PageListSearchField,
|
|
842
|
+
{
|
|
843
|
+
value: searchQuery,
|
|
844
|
+
onChange: onSearchQueryChange,
|
|
845
|
+
placeholder: searchPlaceholder,
|
|
846
|
+
className: "min-w-0 flex-1 max-w-md h-9"
|
|
847
|
+
}
|
|
848
|
+
) : null,
|
|
849
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0 ml-auto", children: [
|
|
850
|
+
/* @__PURE__ */ jsx(
|
|
851
|
+
"button",
|
|
852
|
+
{
|
|
853
|
+
type: "button",
|
|
854
|
+
onClick: () => stepZoom(-0.15),
|
|
855
|
+
className: "w-7 h-7 rounded-lg border border-border bg-card flex items-center justify-center text-muted-foreground hover:bg-accent transition-colors",
|
|
856
|
+
"aria-label": "Zoom out",
|
|
857
|
+
children: /* @__PURE__ */ jsx(Minus, { size: 12 })
|
|
858
|
+
}
|
|
859
|
+
),
|
|
860
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground w-10 text-center tabular-nums select-none", children: [
|
|
861
|
+
Math.round(zoom * 100),
|
|
862
|
+
"%"
|
|
863
|
+
] }),
|
|
864
|
+
/* @__PURE__ */ jsx(
|
|
865
|
+
"button",
|
|
866
|
+
{
|
|
867
|
+
type: "button",
|
|
868
|
+
onClick: () => stepZoom(0.15),
|
|
869
|
+
className: "w-7 h-7 rounded-lg border border-border bg-card flex items-center justify-center text-muted-foreground hover:bg-accent transition-colors",
|
|
870
|
+
"aria-label": "Zoom in",
|
|
871
|
+
children: /* @__PURE__ */ jsx(Plus, { size: 12 })
|
|
872
|
+
}
|
|
873
|
+
),
|
|
874
|
+
/* @__PURE__ */ jsxs(
|
|
875
|
+
"button",
|
|
876
|
+
{
|
|
877
|
+
type: "button",
|
|
878
|
+
onClick: fitCanvas,
|
|
879
|
+
className: "inline-flex items-center gap-1 px-2.5 h-7 rounded-lg border border-border bg-card text-xs text-muted-foreground hover:bg-accent transition-colors",
|
|
880
|
+
children: [
|
|
881
|
+
/* @__PURE__ */ jsx(Maximize2, { size: 11 }),
|
|
882
|
+
"Fit"
|
|
883
|
+
]
|
|
884
|
+
}
|
|
885
|
+
)
|
|
886
|
+
] })
|
|
887
|
+
] }),
|
|
888
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 min-w-0 min-h-0", children: [
|
|
889
|
+
/* @__PURE__ */ jsx(
|
|
890
|
+
"div",
|
|
891
|
+
{
|
|
892
|
+
ref: viewportRef,
|
|
893
|
+
tabIndex: 0,
|
|
894
|
+
className: "flex-1 min-h-[420px] overflow-hidden relative bg-card outline-none",
|
|
895
|
+
style: { cursor: isPanning ? "grabbing" : "grab" },
|
|
896
|
+
onPointerDown: handleViewportPointerDown,
|
|
897
|
+
onPointerMove: handleViewportPointerMove,
|
|
898
|
+
onPointerUp: handleViewportPointerUp,
|
|
899
|
+
onPointerCancel: handleViewportPointerUp,
|
|
900
|
+
children: /* @__PURE__ */ jsxs(
|
|
901
|
+
"div",
|
|
902
|
+
{
|
|
903
|
+
style: {
|
|
904
|
+
position: "absolute",
|
|
905
|
+
transformOrigin: "0px 0px",
|
|
906
|
+
transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
|
|
907
|
+
width: canvasSize.w,
|
|
908
|
+
height: canvasSize.h,
|
|
909
|
+
backgroundImage: bgImage ? "none" : canvasGridBackground(isDarkCanvas),
|
|
910
|
+
backgroundSize: bgImage ? "auto" : CANVAS_GRID_BG_SIZE,
|
|
911
|
+
backgroundColor: "var(--card)",
|
|
912
|
+
boxShadow: isDarkCanvas ? "0 2px 16px rgba(0,0,0,0.45)" : "0 2px 16px rgba(0,0,0,0.12)"
|
|
913
|
+
},
|
|
914
|
+
children: [
|
|
915
|
+
bgImage && /* @__PURE__ */ jsx(
|
|
916
|
+
"img",
|
|
917
|
+
{
|
|
918
|
+
src: bgImage,
|
|
919
|
+
alt: "Floor plan",
|
|
920
|
+
draggable: false,
|
|
921
|
+
style: {
|
|
922
|
+
position: "absolute",
|
|
923
|
+
inset: 0,
|
|
924
|
+
width: "100%",
|
|
925
|
+
height: "100%",
|
|
926
|
+
objectFit: "cover",
|
|
927
|
+
pointerEvents: "none",
|
|
928
|
+
userSelect: "none"
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
),
|
|
932
|
+
Object.entries(placedItems).map(([id, rect]) => {
|
|
933
|
+
const row = rowById.get(id);
|
|
934
|
+
if (!row || !isFloorResourceType(row.resource.resourceType)) return null;
|
|
935
|
+
return /* @__PURE__ */ jsx(
|
|
936
|
+
PlacedCardView,
|
|
937
|
+
{
|
|
938
|
+
id,
|
|
939
|
+
rect,
|
|
940
|
+
row,
|
|
941
|
+
onSelect: resourceQuickBook ? void 0 : handleSelect,
|
|
942
|
+
quickBookConfig: quickBookConfigFor(id),
|
|
943
|
+
searchQuery,
|
|
944
|
+
isMatch: resourceMatchesSearch(row, searchQuery)
|
|
945
|
+
},
|
|
946
|
+
id
|
|
947
|
+
);
|
|
948
|
+
})
|
|
949
|
+
]
|
|
950
|
+
}
|
|
951
|
+
)
|
|
952
|
+
}
|
|
953
|
+
),
|
|
954
|
+
otherRows.length > 0 && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t border-border bg-card px-4 py-2.5", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 overflow-x-auto pb-0.5", children: otherRows.map((row) => {
|
|
955
|
+
const resourceId = row.resource.id;
|
|
956
|
+
return /* @__PURE__ */ jsx(
|
|
957
|
+
PoolResourceChip,
|
|
958
|
+
{
|
|
959
|
+
row,
|
|
960
|
+
onClick: resourceQuickBook ? void 0 : () => handleSelect(resourceId),
|
|
961
|
+
quickBookConfig: quickBookConfigFor(resourceId),
|
|
962
|
+
searchQuery,
|
|
963
|
+
isMatch: resourceMatchesSearch(row, searchQuery)
|
|
964
|
+
},
|
|
965
|
+
resourceId
|
|
966
|
+
);
|
|
967
|
+
}) }) })
|
|
968
|
+
] })
|
|
969
|
+
] });
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
export { LayoutCanvasView, matchesCanvasResourceSearch };
|
|
973
|
+
//# sourceMappingURL=layout-canvas-view.js.map
|
|
974
|
+
//# sourceMappingURL=layout-canvas-view.js.map
|