Spaces:
Sleeping
Sleeping
Download frontend/src/components/FloatingPanel.tsx from hasimjaneef/omniAI: direct link, hf CLI and curl.
- Browser
- Download file 8.27 kB
-
https://huggingface.co/spaces/hasimjaneef/omniAI/resolve/main/frontend/src/components/FloatingPanel.tsx
- Command line
-
hf download hf://spaces/hasimjaneef/omniAI/frontend/src/components/FloatingPanel.tsx
-
curl -L -o FloatingPanel.tsx https://huggingface.co/spaces/hasimjaneef/omniAI/resolve/main/frontend/src/components/FloatingPanel.tsx
8.27 kB
| import { | |
| useEffect, | |
| useRef, | |
| useState, | |
| type CSSProperties, | |
| type KeyboardEvent, | |
| type PointerEvent, | |
| type ReactNode, | |
| } from "react"; | |
| import { | |
| clampPanelGeometry, | |
| defaultPanelGeometry, | |
| movePanelGeometry, | |
| resizePanelGeometry, | |
| type PanelGeometry, | |
| type PanelMinimums, | |
| type PanelPlacement, | |
| } from "../panelGeometry"; | |
| import { Icon } from "./Icon"; | |
| interface FloatingPanelProps { | |
| panelId: string; | |
| title: string; | |
| eyebrow: string; | |
| subtitle: string; | |
| placement: PanelPlacement; | |
| active: boolean; | |
| analysisActive?: boolean; | |
| open: boolean; | |
| embeddedWhenInactive?: boolean; | |
| stopDisabled?: boolean; | |
| minimumWidth: number; | |
| minimumHeight: number; | |
| children: ReactNode; | |
| onClose: () => void; | |
| onStop: () => void; | |
| } | |
| interface PointerInteraction { | |
| kind: "move" | "resize"; | |
| pointerId: number; | |
| clientX: number; | |
| clientY: number; | |
| geometry: PanelGeometry; | |
| } | |
| let highestPanelLayer = 80; | |
| function viewportSize() { | |
| return { | |
| width: typeof window === "undefined" ? 1280 : window.innerWidth, | |
| height: typeof window === "undefined" ? 800 : window.innerHeight, | |
| }; | |
| } | |
| function keyboardDelta(event: KeyboardEvent<HTMLElement>): { x: number; y: number } | null { | |
| const amount = event.shiftKey ? 24 : 8; | |
| if (event.key === "ArrowLeft") return { x: -amount, y: 0 }; | |
| if (event.key === "ArrowRight") return { x: amount, y: 0 }; | |
| if (event.key === "ArrowUp") return { x: 0, y: -amount }; | |
| if (event.key === "ArrowDown") return { x: 0, y: amount }; | |
| return null; | |
| } | |
| export function FloatingPanel({ | |
| panelId, | |
| title, | |
| eyebrow, | |
| subtitle, | |
| placement, | |
| active, | |
| analysisActive = active, | |
| open, | |
| embeddedWhenInactive = false, | |
| stopDisabled = false, | |
| minimumWidth, | |
| minimumHeight, | |
| children, | |
| onClose, | |
| onStop, | |
| }: FloatingPanelProps) { | |
| const minimums: PanelMinimums = { width: minimumWidth, height: minimumHeight }; | |
| const [geometry, setGeometry] = useState(() => | |
| defaultPanelGeometry(placement, viewportSize(), minimums), | |
| ); | |
| const [layer, setLayer] = useState(() => ++highestPanelLayer); | |
| const interactionRef = useRef<PointerInteraction | null>(null); | |
| const wasActiveRef = useRef(false); | |
| const bringForward = () => setLayer(++highestPanelLayer); | |
| useEffect(() => { | |
| if (active && !wasActiveRef.current) { | |
| setGeometry(defaultPanelGeometry(placement, viewportSize(), minimums)); | |
| bringForward(); | |
| } | |
| wasActiveRef.current = active; | |
| }, [active, minimumHeight, minimumWidth, placement]); | |
| useEffect(() => { | |
| if (active && open) bringForward(); | |
| }, [active, open]); | |
| useEffect(() => { | |
| if (!active) return undefined; | |
| const handleResize = () => { | |
| setGeometry((current) => | |
| clampPanelGeometry(current, viewportSize(), minimums), | |
| ); | |
| }; | |
| window.addEventListener("resize", handleResize); | |
| return () => window.removeEventListener("resize", handleResize); | |
| }, [active, minimumHeight, minimumWidth]); | |
| const beginInteraction = ( | |
| event: PointerEvent<HTMLElement>, | |
| kind: PointerInteraction["kind"], | |
| ) => { | |
| if (!active || !open || event.button !== 0) return; | |
| if ( | |
| kind === "move" && | |
| event.target instanceof Element && | |
| event.target.closest("button") | |
| ) { | |
| return; | |
| } | |
| event.currentTarget.focus({ preventScroll: true }); | |
| event.preventDefault(); | |
| event.currentTarget.setPointerCapture?.(event.pointerId); | |
| interactionRef.current = { | |
| kind, | |
| pointerId: event.pointerId, | |
| clientX: event.clientX, | |
| clientY: event.clientY, | |
| geometry, | |
| }; | |
| bringForward(); | |
| }; | |
| const continueInteraction = (event: PointerEvent<HTMLElement>) => { | |
| const interaction = interactionRef.current; | |
| if (!interaction || interaction.pointerId !== event.pointerId) return; | |
| const deltaX = event.clientX - interaction.clientX; | |
| const deltaY = event.clientY - interaction.clientY; | |
| setGeometry( | |
| interaction.kind === "move" | |
| ? movePanelGeometry( | |
| interaction.geometry, | |
| deltaX, | |
| deltaY, | |
| viewportSize(), | |
| minimums, | |
| ) | |
| : resizePanelGeometry( | |
| interaction.geometry, | |
| deltaX, | |
| deltaY, | |
| viewportSize(), | |
| minimums, | |
| ), | |
| ); | |
| }; | |
| const endInteraction = (event: PointerEvent<HTMLElement>) => { | |
| if (interactionRef.current?.pointerId !== event.pointerId) return; | |
| interactionRef.current = null; | |
| if (event.currentTarget.hasPointerCapture?.(event.pointerId)) { | |
| event.currentTarget.releasePointerCapture?.(event.pointerId); | |
| } | |
| }; | |
| const moveWithKeyboard = (event: KeyboardEvent<HTMLElement>) => { | |
| if (event.key === "Escape") { | |
| event.preventDefault(); | |
| onClose(); | |
| return; | |
| } | |
| const delta = keyboardDelta(event); | |
| if (!delta) return; | |
| event.preventDefault(); | |
| bringForward(); | |
| setGeometry((current) => | |
| movePanelGeometry(current, delta.x, delta.y, viewportSize(), minimums), | |
| ); | |
| }; | |
| const resizeWithKeyboard = (event: KeyboardEvent<HTMLElement>) => { | |
| const delta = keyboardDelta(event); | |
| if (!delta) return; | |
| event.preventDefault(); | |
| bringForward(); | |
| setGeometry((current) => | |
| resizePanelGeometry(current, delta.x, delta.y, viewportSize(), minimums), | |
| ); | |
| }; | |
| const floatingStyle: CSSProperties | undefined = active | |
| ? { | |
| left: geometry.x, | |
| top: geometry.y, | |
| width: geometry.width, | |
| height: geometry.height, | |
| zIndex: layer, | |
| } | |
| : undefined; | |
| const modeClass = active | |
| ? open | |
| ? "is-floating" | |
| : "is-panel-closed" | |
| : embeddedWhenInactive | |
| ? "is-embedded" | |
| : "is-dormant"; | |
| return ( | |
| <section | |
| className={`floating-panel ${modeClass} floating-panel-${placement}`} | |
| style={floatingStyle} | |
| role={active && open ? "region" : undefined} | |
| aria-label={active && open ? `${title} panel` : undefined} | |
| aria-hidden={active && !open ? true : undefined} | |
| data-panel-id={panelId} | |
| onFocusCapture={active && open ? bringForward : undefined} | |
| > | |
| {active && ( | |
| <header | |
| className="floating-panel-header" | |
| tabIndex={open ? 0 : -1} | |
| aria-label={`Move ${title} panel. Use arrow keys, or Shift plus arrow keys for larger steps.`} | |
| onPointerDown={(event) => beginInteraction(event, "move")} | |
| onPointerMove={continueInteraction} | |
| onPointerUp={endInteraction} | |
| onPointerCancel={endInteraction} | |
| onKeyDown={moveWithKeyboard} | |
| > | |
| <span className="floating-panel-grip" aria-hidden="true"><i /><i /><i /><i /><i /><i /></span> | |
| <div className="floating-panel-title"> | |
| <span>{eyebrow}</span> | |
| <strong>{title}</strong> | |
| <small>{subtitle}</small> | |
| </div> | |
| <span className="floating-panel-active"><i /> {analysisActive ? "Analysis active" : "Analysis stopped"}</span> | |
| <button | |
| type="button" | |
| className="floating-panel-stop" | |
| disabled={stopDisabled || !analysisActive} | |
| aria-label={`Stop analysis from ${title.toLowerCase()} panel`} | |
| onClick={onStop} | |
| > | |
| <Icon name="stop" size={12} /> Stop analysis | |
| </button> | |
| <button | |
| type="button" | |
| className="floating-panel-close" | |
| aria-label={`Close ${title.toLowerCase()} panel; ${analysisActive ? "analysis will continue" : "results remain available"}`} | |
| onClick={onClose} | |
| > | |
| <span aria-hidden="true">×</span> | |
| </button> | |
| </header> | |
| )} | |
| <div className="floating-panel-body">{children}</div> | |
| {active && ( | |
| <span | |
| className="floating-panel-resize" | |
| role="button" | |
| tabIndex={open ? 0 : -1} | |
| aria-label={`Resize ${title} panel. Use arrow keys, or Shift plus arrow keys for larger steps.`} | |
| onPointerDown={(event) => beginInteraction(event, "resize")} | |
| onPointerMove={continueInteraction} | |
| onPointerUp={endInteraction} | |
| onPointerCancel={endInteraction} | |
| onKeyDown={resizeWithKeyboard} | |
| > | |
| <i /><i /><i /> | |
| </span> | |
| )} | |
| </section> | |
| ); | |
| } | |