mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-06-18 20:10:09 +02:00
cfbeabda1e
* feat(telegram): auto-translate OSINT channel posts to English Cherry-picked from @Bobpick PR #391 (telegram-only slice): server-side translation during fetch, SHOW ORIGINAL toggle in TelegramOsintPopup, and on-demand /api/telegram-feed?lang=. Co-authored-by: Robert Pickett <bobpickettsr@yahoo.com> Co-authored-by: Cursor <cursoragent@cursor.com> * feat(gt): experimental Derived OSINT analytics with lean-node safeguards Cherry-picked from @Bobpick PR #391 (GT + OpenClaw slice): Bayesian strategic-risk engine, map overlay, OpenClaw commands, and telegram_rhetoric watchdog. Off by default (GT_ANALYTICS_ENABLED=false, gt_risk layer false). 1 vCPU nodes get cgroup detection, UI warning on layer toggle, and lean profile that skips scheduled ingest/Louvain unless GT_ANALYTICS_ACK_LOW_CPU=true. Backtest HUD removed from dashboard (OpenClaw/API regression only). Co-authored-by: Robert Pickett <bobpickettsr@yahoo.com> Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Robert Pickett <bobpickettsr@yahoo.com> Co-authored-by: Cursor <cursoragent@cursor.com>
121 lines
3.1 KiB
TypeScript
121 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
|
export interface FloatingPanelPosition {
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
interface StoredFloatingPanelState {
|
|
position?: FloatingPanelPosition;
|
|
isMinimized?: boolean;
|
|
}
|
|
|
|
interface UseFloatingPanelOptions {
|
|
defaultPosition?: FloatingPanelPosition;
|
|
minVisible?: number;
|
|
}
|
|
|
|
export function useFloatingPanel(
|
|
storageKey: string,
|
|
{ defaultPosition = { x: 24, y: 380 }, minVisible = 48 }: UseFloatingPanelOptions = {},
|
|
) {
|
|
const [position, setPosition] = useState<FloatingPanelPosition>(defaultPosition);
|
|
const [isMinimized, setIsMinimized] = useState(false);
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
const dragStartRef = useRef({ x: 0, y: 0, posX: 0, posY: 0 });
|
|
const hydratedRef = useRef(false);
|
|
|
|
useEffect(() => {
|
|
try {
|
|
const raw = localStorage.getItem(storageKey);
|
|
if (!raw) return;
|
|
const parsed = JSON.parse(raw) as StoredFloatingPanelState;
|
|
if (
|
|
parsed.position &&
|
|
Number.isFinite(parsed.position.x) &&
|
|
Number.isFinite(parsed.position.y)
|
|
) {
|
|
setPosition(parsed.position);
|
|
}
|
|
if (typeof parsed.isMinimized === 'boolean') {
|
|
setIsMinimized(parsed.isMinimized);
|
|
}
|
|
} catch {
|
|
/* non-fatal */
|
|
} finally {
|
|
hydratedRef.current = true;
|
|
}
|
|
}, [storageKey]);
|
|
|
|
useEffect(() => {
|
|
if (!hydratedRef.current) return;
|
|
try {
|
|
localStorage.setItem(
|
|
storageKey,
|
|
JSON.stringify({ position, isMinimized } satisfies StoredFloatingPanelState),
|
|
);
|
|
} catch {
|
|
/* non-fatal */
|
|
}
|
|
}, [storageKey, position, isMinimized]);
|
|
|
|
const clampPosition = useCallback(
|
|
(next: FloatingPanelPosition): FloatingPanelPosition => {
|
|
const maxX = Math.max(0, window.innerWidth - minVisible);
|
|
const maxY = Math.max(0, window.innerHeight - minVisible);
|
|
return {
|
|
x: Math.min(Math.max(0, next.x), maxX),
|
|
y: Math.min(Math.max(0, next.y), maxY),
|
|
};
|
|
},
|
|
[minVisible],
|
|
);
|
|
|
|
const onDragStart = useCallback(
|
|
(event: React.MouseEvent) => {
|
|
event.preventDefault();
|
|
setIsDragging(true);
|
|
dragStartRef.current = {
|
|
x: event.clientX,
|
|
y: event.clientY,
|
|
posX: position.x,
|
|
posY: position.y,
|
|
};
|
|
},
|
|
[position.x, position.y],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!isDragging) return undefined;
|
|
|
|
const handleMove = (event: MouseEvent) => {
|
|
const dx = event.clientX - dragStartRef.current.x;
|
|
const dy = event.clientY - dragStartRef.current.y;
|
|
setPosition(
|
|
clampPosition({
|
|
x: dragStartRef.current.posX + dx,
|
|
y: dragStartRef.current.posY + dy,
|
|
}),
|
|
);
|
|
};
|
|
|
|
const handleUp = () => setIsDragging(false);
|
|
|
|
window.addEventListener('mousemove', handleMove);
|
|
window.addEventListener('mouseup', handleUp);
|
|
return () => {
|
|
window.removeEventListener('mousemove', handleMove);
|
|
window.removeEventListener('mouseup', handleUp);
|
|
};
|
|
}, [isDragging, clampPosition]);
|
|
|
|
return {
|
|
position,
|
|
isMinimized,
|
|
setIsMinimized,
|
|
isDragging,
|
|
onDragStart,
|
|
};
|
|
} |