| ... | @@ -2,7 +2,7 @@ | ... | @@ -2,7 +2,7 @@ |
| 2 | | 2 | |
| 3 | import type { ScopedThreadRef } from "@t3tools/contracts"; | 3 | import type { ScopedThreadRef } from "@t3tools/contracts"; |
| 4 | import { PanelRightIcon, PictureInPicture2, XIcon } from "lucide-react"; | 4 | import { PanelRightIcon, PictureInPicture2, XIcon } from "lucide-react"; |
| 5 | import { type PointerEvent as ReactPointerEvent, useLayoutEffect, useRef } from "react"; | 5 | import { type PointerEvent as ReactPointerEvent, useLayoutEffect, useRef, useState } from "react"; |
| 6 | | 6 | |
| 7 | import { BrowserSurfaceSlot } from "~/browser/BrowserSurfaceSlot"; | 7 | import { BrowserSurfaceSlot } from "~/browser/BrowserSurfaceSlot"; |
| 8 | import { previewRuntimeTabId } from "~/browser/previewRuntimeTabId"; | 8 | import { previewRuntimeTabId } from "~/browser/previewRuntimeTabId"; |
| ... | @@ -17,6 +17,7 @@ import { | ... | @@ -17,6 +17,7 @@ import { |
| 17 | clampPreviewMiniPlayerPosition, | 17 | clampPreviewMiniPlayerPosition, |
| 18 | clampPreviewMiniPlayerSize, | 18 | clampPreviewMiniPlayerSize, |
| 19 | PREVIEW_MINI_PLAYER_DEFAULT_SIZE, | 19 | PREVIEW_MINI_PLAYER_DEFAULT_SIZE, |
| | 20 | PREVIEW_MINI_PLAYER_EDGE_GAP, |
| 20 | } from "./previewMiniPlayerLayout"; | 21 | } from "./previewMiniPlayerLayout"; |
| 21 | | 22 | |
| 22 | interface DragState { | 23 | interface DragState { |
| ... | @@ -31,6 +32,8 @@ interface ResizeState { | ... | @@ -31,6 +32,8 @@ interface ResizeState { |
| 31 | readonly pointerId: number; | 32 | readonly pointerId: number; |
| 32 | readonly pointerX: number; | 33 | readonly pointerX: number; |
| 33 | readonly pointerY: number; | 34 | readonly pointerY: number; |
| | 35 | readonly playerX: number; |
| | 36 | readonly playerY: number; |
| 34 | readonly width: number; | 37 | readonly width: number; |
| 35 | readonly height: number; | 38 | readonly height: number; |
| 36 | } | 39 | } |
| ... | @@ -45,6 +48,7 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props | ... | @@ -45,6 +48,7 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props |
| 45 | const rootRef = useRef<HTMLElement | null>(null); | 48 | const rootRef = useRef<HTMLElement | null>(null); |
| 46 | const dragRef = useRef<DragState | null>(null); | 49 | const dragRef = useRef<DragState | null>(null); |
| 47 | const resizeRef = useRef<ResizeState | null>(null); | 50 | const resizeRef = useRef<ResizeState | null>(null); |
| | 51 | const [defaultLayoutVersion, setDefaultLayoutVersion] = useState(""); |
| 48 | const miniPlayer = usePreviewMiniPlayerStore((state) => | 52 | const miniPlayer = usePreviewMiniPlayerStore((state) => |
| 49 | selectThreadPreviewMiniPlayer(state.byThreadKey, threadRef), | 53 | selectThreadPreviewMiniPlayer(state.byThreadKey, threadRef), |
| 50 | ); | 54 | ); |
| ... | @@ -91,8 +95,12 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props | ... | @@ -91,8 +95,12 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props |
| 91 | bottomInset, | 95 | bottomInset, |
| 92 | ); | 96 | ); |
| 93 | usePreviewMiniPlayerStore.getState().resize(threadRef, tabId, nextSize); | 97 | usePreviewMiniPlayerStore.getState().resize(threadRef, tabId, nextSize); |
| | 98 | if (!position) { |
| | 99 | setDefaultLayoutVersion(`${parent.clientWidth}:${parent.clientHeight}`); |
| | 100 | return; |
| | 101 | } |
| 94 | const next = clampPreviewMiniPlayerPosition( | 102 | const next = clampPreviewMiniPlayerPosition( |
| 95 | position ?? { x: root.offsetLeft, y: root.offsetTop }, | 103 | position, |
| 96 | { width: parent.clientWidth, height: parent.clientHeight }, | 104 | { width: parent.clientWidth, height: parent.clientHeight }, |
| 97 | nextSize, | 105 | nextSize, |
| 98 | bottomInset, | 106 | bottomInset, |
| ... | @@ -159,11 +167,16 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props | ... | @@ -159,11 +167,16 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props |
| 159 | const handleResizePointerDown = (event: ReactPointerEvent<HTMLButtonElement>) => { | 167 | const handleResizePointerDown = (event: ReactPointerEvent<HTMLButtonElement>) => { |
| 160 | if (event.button !== 0) return; | 168 | if (event.button !== 0) return; |
| 161 | const root = rootRef.current; | 169 | const root = rootRef.current; |
| 162 | if (!root) return; | 170 | const parent = root?.offsetParent; |
| | 171 | if (!root || !(parent instanceof HTMLElement)) return; |
| | 172 | const rootRect = root.getBoundingClientRect(); |
| | 173 | const parentRect = parent.getBoundingClientRect(); |
| 163 | resizeRef.current = { | 174 | resizeRef.current = { |
| 164 | pointerId: event.pointerId, | 175 | pointerId: event.pointerId, |
| 165 | pointerX: event.clientX, | 176 | pointerX: event.clientX, |
| 166 | pointerY: event.clientY, | 177 | pointerY: event.clientY, |
| | 178 | playerX: rootRect.left - parentRect.left, |
| | 179 | playerY: rootRect.top - parentRect.top, |
| 167 | width: root.offsetWidth, | 180 | width: root.offsetWidth, |
| 168 | height: root.offsetHeight, | 181 | height: root.offsetHeight, |
| 169 | }; | 182 | }; |
| ... | @@ -194,7 +207,7 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props | ... | @@ -194,7 +207,7 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props |
| 194 | ); | 207 | ); |
| 195 | usePreviewMiniPlayerStore.getState().resize(threadRef, tabId, nextSize); | 208 | usePreviewMiniPlayerStore.getState().resize(threadRef, tabId, nextSize); |
| 196 | const nextPosition = clampPreviewMiniPlayerPosition( | 209 | const nextPosition = clampPreviewMiniPlayerPosition( |
| 197 | position ?? { x: root.offsetLeft, y: root.offsetTop }, | 210 | { x: resize.playerX, y: resize.playerY }, |
| 198 | { width: parent.clientWidth, height: parent.clientHeight }, | 211 | { width: parent.clientWidth, height: parent.clientHeight }, |
| 199 | nextSize, | 212 | nextSize, |
| 200 | bottomInset, | 213 | bottomInset, |
| ... | @@ -222,8 +235,8 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props | ... | @@ -222,8 +235,8 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props |
| 222 | position | 235 | position |
| 223 | ? { left: position.x, top: position.y, width: size.width, height: size.height } | 236 | ? { left: position.x, top: position.y, width: size.width, height: size.height } |
| 224 | : { | 237 | : { |
| 225 | right: 16, | 238 | right: PREVIEW_MINI_PLAYER_EDGE_GAP, |
| 226 | top: 16, | 239 | top: PREVIEW_MINI_PLAYER_EDGE_GAP, |
| 227 | width: size.width, | 240 | width: size.width, |
| 228 | height: size.height, | 241 | height: size.height, |
| 229 | } | 242 | } |
| ... | @@ -290,7 +303,11 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props | ... | @@ -290,7 +303,11 @@ export function ThreadPreviewMiniPlayer({ threadRef, tabId, bottomInset }: Props |
| 290 | visible={Boolean(desktopOverlay?.hasWebContents)} | 303 | visible={Boolean(desktopOverlay?.hasWebContents)} |
| 291 | cornerRadius={12} | 304 | cornerRadius={12} |
| 292 | fitSourceContent | 305 | fitSourceContent |
| 293 | layoutVersion={position ? `${position.x}:${position.y}` : `initial:${bottomInset}`} | 306 | layoutVersion={ |
| | 307 | position |
| | 308 | ? `${position.x}:${position.y}` |
| | 309 | : `initial:${bottomInset}:${defaultLayoutVersion}` |
| | 310 | } |
| 294 | className="absolute inset-0" | 311 | className="absolute inset-0" |
| 295 | /> | 312 | /> |
| 296 | <div className="pointer-events-none absolute inset-0 z-[31] rounded-xl ring-1 ring-inset ring-border/80" /> | 313 | <div className="pointer-events-none absolute inset-0 z-[31] rounded-xl ring-1 ring-inset ring-border/80" /> |