Extract panel header to title bar (#625)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useAtom } from "jotai";
|
||||
import { selectedAppIdAtom } from "@/atoms/appAtoms";
|
||||
import { useLoadApps } from "@/hooks/useLoadApps";
|
||||
import { useRouter } from "@tanstack/react-router";
|
||||
import { useRouter, useLocation } from "@tanstack/react-router";
|
||||
import { useSettings } from "@/hooks/useSettings";
|
||||
import { Button } from "@/components/ui/button";
|
||||
// @ts-ignore
|
||||
@@ -20,11 +20,13 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { PreviewHeader } from "@/components/preview_panel/PreviewHeader";
|
||||
|
||||
export const TitleBar = () => {
|
||||
const [selectedAppId] = useAtom(selectedAppIdAtom);
|
||||
const { apps } = useLoadApps();
|
||||
const { navigate } = useRouter();
|
||||
const location = useLocation();
|
||||
const { settings, refreshSettings } = useSettings();
|
||||
const [isSuccessDialogOpen, setIsSuccessDialogOpen] = useState(false);
|
||||
const [showWindowControls, setShowWindowControls] = useState(false);
|
||||
@@ -90,6 +92,14 @@ export const TitleBar = () => {
|
||||
{displayText}
|
||||
</Button>
|
||||
{isDyadPro && <DyadProButton isDyadProEnabled={isDyadProEnabled} />}
|
||||
|
||||
{/* Preview Header */}
|
||||
{location.pathname === "/chat" && (
|
||||
<div className="flex-1 flex justify-end no-app-region-drag">
|
||||
<PreviewHeader />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showWindowControls && <WindowsControls />}
|
||||
</div>
|
||||
|
||||
|
||||
214
src/components/preview_panel/PreviewHeader.tsx
Normal file
214
src/components/preview_panel/PreviewHeader.tsx
Normal file
@@ -0,0 +1,214 @@
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { previewModeAtom, selectedAppIdAtom } from "../../atoms/appAtoms";
|
||||
import { IpcClient } from "@/ipc/ipc_client";
|
||||
|
||||
import {
|
||||
Eye,
|
||||
Code,
|
||||
MoreVertical,
|
||||
Cog,
|
||||
Trash2,
|
||||
AlertTriangle,
|
||||
} from "lucide-react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
|
||||
import { useRunApp } from "@/hooks/useRunApp";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { showError, showSuccess } from "@/lib/toast";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useCheckProblems } from "@/hooks/useCheckProblems";
|
||||
import { isPreviewOpenAtom } from "@/atoms/viewAtoms";
|
||||
|
||||
export type PreviewMode = "preview" | "code" | "problems";
|
||||
|
||||
// Preview Header component with preview mode toggle
|
||||
export const PreviewHeader = () => {
|
||||
const [previewMode, setPreviewMode] = useAtom(previewModeAtom);
|
||||
const [isPreviewOpen, setIsPreviewOpen] = useAtom(isPreviewOpenAtom);
|
||||
const selectedAppId = useAtomValue(selectedAppIdAtom);
|
||||
const previewRef = useRef<HTMLButtonElement>(null);
|
||||
const codeRef = useRef<HTMLButtonElement>(null);
|
||||
const problemsRef = useRef<HTMLButtonElement>(null);
|
||||
const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 });
|
||||
const { problemReport } = useCheckProblems(selectedAppId);
|
||||
const { restartApp, refreshAppIframe } = useRunApp();
|
||||
|
||||
const selectPanel = (panel: PreviewMode) => {
|
||||
if (previewMode === panel) {
|
||||
setIsPreviewOpen(!isPreviewOpen);
|
||||
} else {
|
||||
setPreviewMode(panel);
|
||||
setIsPreviewOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onCleanRestart = useCallback(() => {
|
||||
restartApp({ removeNodeModules: true });
|
||||
}, [restartApp]);
|
||||
|
||||
const useClearSessionData = () => {
|
||||
return useMutation({
|
||||
mutationFn: () => {
|
||||
const ipcClient = IpcClient.getInstance();
|
||||
return ipcClient.clearSessionData();
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await refreshAppIframe();
|
||||
showSuccess("Preview data cleared");
|
||||
},
|
||||
onError: (error) => {
|
||||
showError(`Error clearing preview data: ${error}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const { mutate: clearSessionData } = useClearSessionData();
|
||||
|
||||
const onClearSessionData = useCallback(() => {
|
||||
clearSessionData();
|
||||
}, [clearSessionData]);
|
||||
|
||||
// Get the problem count for the selected app
|
||||
const problemCount = problemReport ? problemReport.problems.length : 0;
|
||||
|
||||
// Format the problem count for display
|
||||
const formatProblemCount = (count: number): string => {
|
||||
if (count === 0) return "";
|
||||
if (count > 100) return "100+";
|
||||
return count.toString();
|
||||
};
|
||||
|
||||
const displayCount = formatProblemCount(problemCount);
|
||||
|
||||
// Update indicator position when mode changes
|
||||
useEffect(() => {
|
||||
const updateIndicator = () => {
|
||||
let targetRef: React.RefObject<HTMLButtonElement | null>;
|
||||
|
||||
switch (previewMode) {
|
||||
case "preview":
|
||||
targetRef = previewRef;
|
||||
break;
|
||||
case "code":
|
||||
targetRef = codeRef;
|
||||
break;
|
||||
case "problems":
|
||||
targetRef = problemsRef;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetRef.current) {
|
||||
const button = targetRef.current;
|
||||
const container = button.parentElement;
|
||||
if (container) {
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const buttonRect = button.getBoundingClientRect();
|
||||
const left = buttonRect.left - containerRect.left;
|
||||
const width = buttonRect.width;
|
||||
|
||||
setIndicatorStyle({ left, width });
|
||||
if (!isPreviewOpen) {
|
||||
setIndicatorStyle({ left: left, width: 0 });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Small delay to ensure DOM is updated
|
||||
const timeoutId = setTimeout(updateIndicator, 10);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [previewMode, displayCount, isPreviewOpen]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between px-4 py-2 mt-1 border-b border-border">
|
||||
<div className="relative flex rounded-md p-0.5 gap-2">
|
||||
<motion.div
|
||||
className="absolute top-0.5 bottom-0.5 bg-[var(--background-lightest)] shadow rounded-md"
|
||||
animate={{
|
||||
left: indicatorStyle.left,
|
||||
width: indicatorStyle.width,
|
||||
}}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 600,
|
||||
damping: 35,
|
||||
mass: 0.6,
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
data-testid="preview-mode-button"
|
||||
ref={previewRef}
|
||||
className="cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10"
|
||||
onClick={() => selectPanel("preview")}
|
||||
>
|
||||
<Eye size={14} />
|
||||
<span>Preview</span>
|
||||
</button>
|
||||
<button
|
||||
data-testid="problems-mode-button"
|
||||
ref={problemsRef}
|
||||
className="cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10"
|
||||
onClick={() => selectPanel("problems")}
|
||||
>
|
||||
<AlertTriangle size={14} />
|
||||
<span>Problems</span>
|
||||
{displayCount && (
|
||||
<span className="ml-0.5 px-1 py-0.5 text-xs font-medium bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-full min-w-[16px] text-center">
|
||||
{displayCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
data-testid="code-mode-button"
|
||||
ref={codeRef}
|
||||
className="cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10"
|
||||
onClick={() => selectPanel("code")}
|
||||
>
|
||||
<Code size={14} />
|
||||
<span>Code</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
data-testid="preview-more-options-button"
|
||||
className="flex items-center justify-center p-1.5 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
|
||||
title="More options"
|
||||
>
|
||||
<MoreVertical size={16} />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-60">
|
||||
<DropdownMenuItem onClick={onCleanRestart}>
|
||||
<Cog size={16} />
|
||||
<div className="flex flex-col">
|
||||
<span>Rebuild</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Re-installs node_modules and restarts
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={onClearSessionData}>
|
||||
<Trash2 size={16} />
|
||||
<div className="flex flex-col">
|
||||
<span>Clear Cache</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Clears cookies and local storage and other app cache
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
Lightbulb,
|
||||
ChevronRight,
|
||||
MousePointerClick,
|
||||
Power,
|
||||
} from "lucide-react";
|
||||
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
|
||||
import { IpcClient } from "@/ipc/ipc_client";
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useRunApp } from "@/hooks/useRunApp";
|
||||
|
||||
interface ErrorBannerProps {
|
||||
error: string | undefined;
|
||||
@@ -129,7 +131,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
|
||||
const [availableRoutes, setAvailableRoutes] = useState<
|
||||
Array<{ path: string; label: string }>
|
||||
>([]);
|
||||
|
||||
const { restartApp } = useRunApp();
|
||||
// Load router related files to extract routes
|
||||
const { content: routerContent } = useLoadAppFile(
|
||||
selectedAppId,
|
||||
@@ -423,6 +425,10 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
|
||||
);
|
||||
}
|
||||
|
||||
const onRestart = () => {
|
||||
restartApp();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Browser-style header */}
|
||||
@@ -519,6 +525,14 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex space-x-1">
|
||||
<button
|
||||
onClick={onRestart}
|
||||
className="flex items-center space-x-1 px-3 py-1 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
|
||||
title="Restart App"
|
||||
>
|
||||
<Power size={16} />
|
||||
<span>Restart</span>
|
||||
</button>
|
||||
<button
|
||||
data-testid="preview-open-browser-button"
|
||||
onClick={() => {
|
||||
|
||||
@@ -5,47 +5,15 @@ import {
|
||||
previewPanelKeyAtom,
|
||||
selectedAppIdAtom,
|
||||
} from "../../atoms/appAtoms";
|
||||
import { IpcClient } from "@/ipc/ipc_client";
|
||||
|
||||
import { CodeView } from "./CodeView";
|
||||
import { PreviewIframe } from "./PreviewIframe";
|
||||
import { Problems } from "./Problems";
|
||||
import {
|
||||
Eye,
|
||||
Code,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Logs,
|
||||
MoreVertical,
|
||||
Cog,
|
||||
Power,
|
||||
Trash2,
|
||||
AlertTriangle,
|
||||
} from "lucide-react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import { ChevronDown, ChevronUp, Logs } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
|
||||
import { Console } from "./Console";
|
||||
import { useRunApp } from "@/hooks/useRunApp";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { showError, showSuccess } from "@/lib/toast";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useCheckProblems } from "@/hooks/useCheckProblems";
|
||||
|
||||
type PreviewMode = "preview" | "code" | "problems";
|
||||
|
||||
interface PreviewHeaderProps {
|
||||
previewMode: PreviewMode;
|
||||
setPreviewMode: (mode: PreviewMode) => void;
|
||||
onRestart: () => void;
|
||||
onCleanRestart: () => void;
|
||||
onClearSessionData: () => void;
|
||||
}
|
||||
|
||||
interface ConsoleHeaderProps {
|
||||
isOpen: boolean;
|
||||
@@ -53,164 +21,6 @@ interface ConsoleHeaderProps {
|
||||
latestMessage?: string;
|
||||
}
|
||||
|
||||
// Preview Header component with preview mode toggle
|
||||
const PreviewHeader = ({
|
||||
previewMode,
|
||||
setPreviewMode,
|
||||
onRestart,
|
||||
onCleanRestart,
|
||||
onClearSessionData,
|
||||
}: PreviewHeaderProps) => {
|
||||
const selectedAppId = useAtomValue(selectedAppIdAtom);
|
||||
const previewRef = useRef<HTMLButtonElement>(null);
|
||||
const codeRef = useRef<HTMLButtonElement>(null);
|
||||
const problemsRef = useRef<HTMLButtonElement>(null);
|
||||
const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 });
|
||||
const { problemReport } = useCheckProblems(selectedAppId);
|
||||
// Get the problem count for the selected app
|
||||
const problemCount = problemReport ? problemReport.problems.length : 0;
|
||||
|
||||
// Format the problem count for display
|
||||
const formatProblemCount = (count: number): string => {
|
||||
if (count === 0) return "";
|
||||
if (count > 100) return "100+";
|
||||
return count.toString();
|
||||
};
|
||||
|
||||
const displayCount = formatProblemCount(problemCount);
|
||||
|
||||
// Update indicator position when mode changes
|
||||
useEffect(() => {
|
||||
const updateIndicator = () => {
|
||||
let targetRef: React.RefObject<HTMLButtonElement | null>;
|
||||
|
||||
switch (previewMode) {
|
||||
case "preview":
|
||||
targetRef = previewRef;
|
||||
break;
|
||||
case "code":
|
||||
targetRef = codeRef;
|
||||
break;
|
||||
case "problems":
|
||||
targetRef = problemsRef;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetRef.current) {
|
||||
const button = targetRef.current;
|
||||
const container = button.parentElement;
|
||||
if (container) {
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const buttonRect = button.getBoundingClientRect();
|
||||
const left = buttonRect.left - containerRect.left;
|
||||
const width = buttonRect.width;
|
||||
|
||||
setIndicatorStyle({ left, width });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Small delay to ensure DOM is updated
|
||||
const timeoutId = setTimeout(updateIndicator, 10);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [previewMode, displayCount]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between px-4 py-2 border-b border-border">
|
||||
<div className="relative flex bg-[var(--background-darkest)] rounded-md p-0.5">
|
||||
<motion.div
|
||||
className="absolute top-0.5 bottom-0.5 bg-[var(--background-lightest)] shadow rounded-md"
|
||||
animate={{
|
||||
left: indicatorStyle.left,
|
||||
width: indicatorStyle.width,
|
||||
}}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 600,
|
||||
damping: 35,
|
||||
mass: 0.6,
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
data-testid="preview-mode-button"
|
||||
ref={previewRef}
|
||||
className="relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10"
|
||||
onClick={() => setPreviewMode("preview")}
|
||||
>
|
||||
<Eye size={14} />
|
||||
<span>Preview</span>
|
||||
</button>
|
||||
<button
|
||||
data-testid="problems-mode-button"
|
||||
ref={problemsRef}
|
||||
className="relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10"
|
||||
onClick={() => setPreviewMode("problems")}
|
||||
>
|
||||
<AlertTriangle size={14} />
|
||||
<span>Problems</span>
|
||||
{displayCount && (
|
||||
<span className="ml-0.5 px-1 py-0.5 text-xs font-medium bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300 rounded-full min-w-[16px] text-center">
|
||||
{displayCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
data-testid="code-mode-button"
|
||||
ref={codeRef}
|
||||
className="relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10"
|
||||
onClick={() => setPreviewMode("code")}
|
||||
>
|
||||
<Code size={14} />
|
||||
<span>Code</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={onRestart}
|
||||
className="flex items-center space-x-1 px-3 py-1 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
|
||||
title="Restart App"
|
||||
>
|
||||
<Power size={16} />
|
||||
<span>Restart</span>
|
||||
</button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
data-testid="preview-more-options-button"
|
||||
className="flex items-center justify-center p-1.5 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
|
||||
title="More options"
|
||||
>
|
||||
<MoreVertical size={16} />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-60">
|
||||
<DropdownMenuItem onClick={onCleanRestart}>
|
||||
<Cog size={16} />
|
||||
<div className="flex flex-col">
|
||||
<span>Rebuild</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Re-installs node_modules and restarts
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={onClearSessionData}>
|
||||
<Trash2 size={16} />
|
||||
<div className="flex flex-col">
|
||||
<span>Clear Cache</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Clears cookies and local storage and other app cache
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Console header component
|
||||
const ConsoleHeader = ({
|
||||
isOpen,
|
||||
@@ -237,11 +47,10 @@ const ConsoleHeader = ({
|
||||
|
||||
// Main PreviewPanel component
|
||||
export function PreviewPanel() {
|
||||
const [previewMode, setPreviewMode] = useAtom(previewModeAtom);
|
||||
const [previewMode] = useAtom(previewModeAtom);
|
||||
const selectedAppId = useAtomValue(selectedAppIdAtom);
|
||||
const [isConsoleOpen, setIsConsoleOpen] = useState(false);
|
||||
const { runApp, stopApp, restartApp, loading, app, refreshAppIframe } =
|
||||
useRunApp();
|
||||
const { runApp, stopApp, loading, app } = useRunApp();
|
||||
const runningAppIdRef = useRef<number | null>(null);
|
||||
const key = useAtomValue(previewPanelKeyAtom);
|
||||
const appOutput = useAtomValue(appOutputAtom);
|
||||
@@ -250,37 +59,6 @@ export function PreviewPanel() {
|
||||
const latestMessage =
|
||||
messageCount > 0 ? appOutput[messageCount - 1]?.message : undefined;
|
||||
|
||||
const handleRestart = useCallback(() => {
|
||||
restartApp();
|
||||
}, [restartApp]);
|
||||
|
||||
const handleCleanRestart = useCallback(() => {
|
||||
restartApp({ removeNodeModules: true });
|
||||
}, [restartApp]);
|
||||
|
||||
const useClearSessionData = () => {
|
||||
return useMutation({
|
||||
mutationFn: () => {
|
||||
const ipcClient = IpcClient.getInstance();
|
||||
return ipcClient.clearSessionData();
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await refreshAppIframe();
|
||||
showSuccess("Preview data cleared");
|
||||
// Optionally invalidate relevant queries
|
||||
},
|
||||
onError: (error) => {
|
||||
showError(`Error clearing preview data: ${error}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const { mutate: clearSessionData } = useClearSessionData();
|
||||
|
||||
const handleClearSessionData = useCallback(() => {
|
||||
clearSessionData();
|
||||
}, [selectedAppId, clearSessionData]);
|
||||
|
||||
useEffect(() => {
|
||||
const previousAppId = runningAppIdRef.current;
|
||||
|
||||
@@ -327,13 +105,6 @@ export function PreviewPanel() {
|
||||
}, [selectedAppId, runApp, stopApp]);
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<PreviewHeader
|
||||
previewMode={previewMode}
|
||||
setPreviewMode={setPreviewMode}
|
||||
onRestart={handleRestart}
|
||||
onCleanRestart={handleCleanRestart}
|
||||
onClearSessionData={handleClearSessionData}
|
||||
/>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<PanelGroup direction="vertical">
|
||||
<Panel id="content" minSize={30}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { atom } from "jotai";
|
||||
import { IpcClient } from "@/ipc/ipc_client";
|
||||
import {
|
||||
appOutputAtom,
|
||||
@@ -11,8 +12,10 @@ import {
|
||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
import { AppOutput } from "@/ipc/ipc_types";
|
||||
|
||||
const useRunAppLoadingAtom = atom(false);
|
||||
|
||||
export function useRunApp() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useAtom(useRunAppLoadingAtom);
|
||||
const [app, setApp] = useAtom(currentAppAtom);
|
||||
const setAppOutput = useSetAtom(appOutputAtom);
|
||||
const [appUrlObj, setAppUrlObj] = useAtom(appUrlAtom);
|
||||
|
||||
Reference in New Issue
Block a user