chore: up

This commit is contained in:
rain9
2026-01-04 14:10:44 +08:00
parent 81579c37b5
commit e088a1718b
3 changed files with 76 additions and 28 deletions

View File

@@ -1,5 +1,5 @@
import React from "react";
import { useState, useEffect, useMemo } from "react";
import { useState, useEffect, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { Focus, ExternalLink } from "lucide-react";
@@ -75,6 +75,8 @@ const ViewExtensionContent: React.FC<ControlsProps> = ({
}
return reversed;
}, [apis]);
const containerRef = useRef<HTMLDivElement>(null);
// Watch for events from iframes - only set up listener when reversedApis is ready
useEffect(() => {
@@ -191,12 +193,18 @@ const ViewExtensionContent: React.FC<ControlsProps> = ({
iframeRef,
focusIframe,
setBaseSize,
} = useViewExtensionWindow({ forceResizable, viewExtension: viewExtensionOpened, isStandalone });
} = useViewExtensionWindow({
forceResizable,
viewExtension: viewExtensionOpened,
isStandalone,
padding: 8,
containerRef: isStandalone ? undefined : containerRef
});
const [iframeReady, setIframeReady] = useState(false);
return (
<div className="relative w-full h-full overflow-hidden">
<div ref={containerRef} className="relative w-full h-full overflow-hidden">
{iframeReady && (
<div className="absolute top-2 right-2 z-10 flex items-center gap-2">
{resizable && showFocus && (

View File

@@ -489,7 +489,7 @@ function SearchChat({
{!hideMiddleBorder && (
<div
className={clsx(
"pointer-events-none absolute left-0 right-0 h-px bg-[#E6E6E6] dark:bg-[#272626]",
"pointer-events-none absolute left-0 right-0 h-px bg-[#E6E6E6] dark:bg-[#272626] mx-0.5",
isTransitioned ? "top-0" : "bottom-0"
)}
/>

View File

@@ -18,14 +18,25 @@ export function useViewExtensionWindow(opts?: {
ignoreExplicitSize?: boolean;
viewExtension?: ViewExtensionOpened | null;
isStandalone?: boolean;
padding?: number;
containerRef?: React.RefObject<HTMLElement>;
}) {
const isTauri = useAppStore((state) => state.isTauri);
const storeViewExtension = useExtensionStore((state) =>
opts?.viewExtension ? undefined : (state.viewExtensions.length > 0 ? state.viewExtensions[state.viewExtensions.length - 1] : undefined)
);
const viewExtensionOpened = opts?.viewExtension ?? storeViewExtension;
if (viewExtensionOpened == null) {
const {
forceResizable = false,
ignoreExplicitSize = false,
viewExtension = null,
isStandalone = false,
padding = 0,
containerRef,
} = opts || {};
const isTauri = useAppStore((state) => state.isTauri);
const storeViewExtension = useExtensionStore((state) =>
viewExtension ? undefined : (state.viewExtensions.length > 0 ? state.viewExtensions[state.viewExtensions.length - 1] : undefined)
);
const viewExtensionOpened = viewExtension ?? storeViewExtension;
if (viewExtensionOpened == null) {
throw new Error(
"ViewExtension Error: viewExtensionOpened is null. This should not happen."
);
@@ -34,14 +45,14 @@ export function useViewExtensionWindow(opts?: {
const ui: ViewExtensionUISettingsOrNull = useMemo(() => {
return viewExtensionOpened[4] as ViewExtensionUISettingsOrNull;
}, [viewExtensionOpened]);
const resizable = opts?.forceResizable ? true : ui?.resizable;
const resizable = forceResizable ? true : ui?.resizable;
const hideScrollbar = ui?.hide_scrollbar ?? true;
const detachable = ui?.detachable ?? false;
const uiWidth = ui && typeof ui.width === "number" ? ui.width : null;
const uiHeight = ui && typeof ui.height === "number" ? ui.height : null;
const hasExplicitWindowSize =
uiWidth != null && uiHeight != null && !opts?.ignoreExplicitSize;
uiWidth != null && uiHeight != null && !ignoreExplicitSize;
const iframeRef = useRef<HTMLIFrameElement | null>(null);
const prevWindowRef = useRef<WindowSnapshot | null>(null);
@@ -83,25 +94,36 @@ export function useViewExtensionWindow(opts?: {
setScale(1);
return;
}
const size = await platformAdapter.getWindowSize();
const ratioW = size.width / baseWidth;
const ratioH = size.height / baseHeight;
let availableWidth: number;
let availableHeight: number;
if (containerRef?.current) {
const rect = containerRef.current.getBoundingClientRect();
availableWidth = Math.max(0, rect.width - padding);
availableHeight = Math.max(0, rect.height - padding);
} else {
const size = await platformAdapter.getWindowSize();
availableWidth = Math.max(0, size.width - padding);
availableHeight = Math.max(0, size.height - padding);
}
const ratioW = availableWidth / baseWidth;
const ratioH = availableHeight / baseHeight;
const nextScale = Math.min(ratioW, ratioH);
setScale(Math.max(nextScale, 0.1));
}, [baseHeight, baseWidth, hasExplicitWindowSize]);
}, [baseHeight, baseWidth, hasExplicitWindowSize, padding, containerRef]);
useEffect(() => {
const applyWindowSettings = async () => {
const size = await platformAdapter.getWindowSize();
const resizable = await platformAdapter.isWindowResizable();
const windowResizable = await platformAdapter.isWindowResizable();
const pos = await platformAdapter.getWindowPosition();
setFallbackViewSize({ width: size.width, height: size.height });
prevWindowRef.current = {
width: size.width,
height: size.height,
resizable,
resizable: windowResizable,
x: pos.x,
y: pos.y,
};
@@ -112,12 +134,15 @@ export function useViewExtensionWindow(opts?: {
if (hasExplicitWindowSize) {
const nextResizable =
opts?.forceResizable
forceResizable
? true
: ui && typeof ui.resizable === "boolean"
? ui.resizable
: true;
await platformAdapter.setWindowSize(uiWidth, uiHeight);
const targetWidth = uiWidth! + (isStandalone ? padding : 0);
const targetHeight = uiHeight! + (isStandalone ? padding : 0);
await platformAdapter.setWindowSize(targetWidth, targetHeight);
await platformAdapter.setWindowResizable(nextResizable);
await platformAdapter.centerOnCurrentMonitor();
await recomputeScale();
@@ -125,11 +150,11 @@ export function useViewExtensionWindow(opts?: {
await recomputeScale();
}
// If we are in the standalone view extension window (not the preview in main window),
// we need to show the window explicitly because it is created hidden to avoid flickering.
// This ensures the window only becomes visible after the initial resize and setup are complete.
if (opts?.isStandalone) {
if (isStandalone) {
await platformAdapter.showWindow();
// Force window to top
await platformAdapter.setAlwaysOnTop(true);
await platformAdapter.setAlwaysOnTop(false);
}
focusIframeSoon();
@@ -153,6 +178,10 @@ export function useViewExtensionWindow(opts?: {
ui,
uiHeight,
uiWidth,
isStandalone,
padding,
forceResizable,
isTauri
]);
useEffect(() => {
@@ -161,11 +190,22 @@ export function useViewExtensionWindow(opts?: {
recomputeScale();
}
};
window.addEventListener("resize", handleResize);
let observer: ResizeObserver | null = null;
if (containerRef?.current) {
observer = new ResizeObserver(() => {
handleResize();
});
observer.observe(containerRef.current);
}
return () => {
window.removeEventListener("resize", handleResize);
observer?.disconnect();
};
}, [hasExplicitWindowSize, recomputeScale]);
}, [hasExplicitWindowSize, recomputeScale, containerRef]);
return {
ui,