Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
4b84b12f
Unverified
提交
4b84b12f
authored
7月 10, 2025
作者:
Will Chen
提交者:
GitHub
7月 10, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Extract panel header to title bar (#625)
上级
5f3dea61
显示空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
253 行增加
和
237 行删除
+253
-237
test_helper.ts
e2e-tests/helpers/test_helper.ts
+4
-0
TitleBar.tsx
src/app/TitleBar.tsx
+11
-1
PreviewHeader.tsx
src/components/preview_panel/PreviewHeader.tsx
+214
-0
PreviewIframe.tsx
src/components/preview_panel/PreviewIframe.tsx
+15
-1
PreviewPanel.tsx
src/components/preview_panel/PreviewPanel.tsx
+4
-233
useRunApp.ts
src/hooks/useRunApp.ts
+5
-2
没有找到文件。
e2e-tests/helpers/test_helper.ts
浏览文件 @
4b84b12f
...
@@ -484,6 +484,10 @@ export class PageObject {
...
@@ -484,6 +484,10 @@ export class PageObject {
return
this
.
page
.
getByText
(
"Loading app preview..."
);
return
this
.
page
.
getByText
(
"Loading app preview..."
);
}
}
locateStartingAppPreview
()
{
return
this
.
page
.
getByText
(
"Starting up your app..."
);
}
getPreviewIframeElement
()
{
getPreviewIframeElement
()
{
return
this
.
page
.
getByTestId
(
"preview-iframe-element"
);
return
this
.
page
.
getByTestId
(
"preview-iframe-element"
);
}
}
...
...
src/app/TitleBar.tsx
浏览文件 @
4b84b12f
import
{
useAtom
}
from
"jotai"
;
import
{
useAtom
}
from
"jotai"
;
import
{
selectedAppIdAtom
}
from
"@/atoms/appAtoms"
;
import
{
selectedAppIdAtom
}
from
"@/atoms/appAtoms"
;
import
{
useLoadApps
}
from
"@/hooks/useLoadApps"
;
import
{
useLoadApps
}
from
"@/hooks/useLoadApps"
;
import
{
useRouter
}
from
"@tanstack/react-router"
;
import
{
useRouter
,
useLocation
}
from
"@tanstack/react-router"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
// @ts-ignore
// @ts-ignore
...
@@ -20,11 +20,13 @@ import {
...
@@ -20,11 +20,13 @@ import {
TooltipContent
,
TooltipContent
,
TooltipTrigger
,
TooltipTrigger
,
}
from
"@/components/ui/tooltip"
;
}
from
"@/components/ui/tooltip"
;
import
{
PreviewHeader
}
from
"@/components/preview_panel/PreviewHeader"
;
export
const
TitleBar
=
()
=>
{
export
const
TitleBar
=
()
=>
{
const
[
selectedAppId
]
=
useAtom
(
selectedAppIdAtom
);
const
[
selectedAppId
]
=
useAtom
(
selectedAppIdAtom
);
const
{
apps
}
=
useLoadApps
();
const
{
apps
}
=
useLoadApps
();
const
{
navigate
}
=
useRouter
();
const
{
navigate
}
=
useRouter
();
const
location
=
useLocation
();
const
{
settings
,
refreshSettings
}
=
useSettings
();
const
{
settings
,
refreshSettings
}
=
useSettings
();
const
[
isSuccessDialogOpen
,
setIsSuccessDialogOpen
]
=
useState
(
false
);
const
[
isSuccessDialogOpen
,
setIsSuccessDialogOpen
]
=
useState
(
false
);
const
[
showWindowControls
,
setShowWindowControls
]
=
useState
(
false
);
const
[
showWindowControls
,
setShowWindowControls
]
=
useState
(
false
);
...
@@ -90,6 +92,14 @@ export const TitleBar = () => {
...
@@ -90,6 +92,14 @@ export const TitleBar = () => {
{
displayText
}
{
displayText
}
</
Button
>
</
Button
>
{
isDyadPro
&&
<
DyadProButton
isDyadProEnabled=
{
isDyadProEnabled
}
/>
}
{
isDyadPro
&&
<
DyadProButton
isDyadProEnabled=
{
isDyadProEnabled
}
/>
}
{
/* Preview Header */
}
{
location
.
pathname
===
"/chat"
&&
(
<
div
className=
"flex-1 flex justify-end no-app-region-drag"
>
<
PreviewHeader
/>
</
div
>
)
}
{
showWindowControls
&&
<
WindowsControls
/>
}
{
showWindowControls
&&
<
WindowsControls
/>
}
</
div
>
</
div
>
...
...
src/components/preview_panel/PreviewHeader.tsx
0 → 100644
浏览文件 @
4b84b12f
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
>
);
};
src/components/preview_panel/PreviewIframe.tsx
浏览文件 @
4b84b12f
...
@@ -18,6 +18,7 @@ import {
...
@@ -18,6 +18,7 @@ import {
Lightbulb
,
Lightbulb
,
ChevronRight
,
ChevronRight
,
MousePointerClick
,
MousePointerClick
,
Power
,
}
from
"lucide-react"
;
}
from
"lucide-react"
;
import
{
selectedChatIdAtom
}
from
"@/atoms/chatAtoms"
;
import
{
selectedChatIdAtom
}
from
"@/atoms/chatAtoms"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
...
@@ -38,6 +39,7 @@ import {
...
@@ -38,6 +39,7 @@ import {
TooltipProvider
,
TooltipProvider
,
TooltipTrigger
,
TooltipTrigger
,
}
from
"@/components/ui/tooltip"
;
}
from
"@/components/ui/tooltip"
;
import
{
useRunApp
}
from
"@/hooks/useRunApp"
;
interface
ErrorBannerProps
{
interface
ErrorBannerProps
{
error
:
string
|
undefined
;
error
:
string
|
undefined
;
...
@@ -129,7 +131,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
...
@@ -129,7 +131,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
const
[
availableRoutes
,
setAvailableRoutes
]
=
useState
<
const
[
availableRoutes
,
setAvailableRoutes
]
=
useState
<
Array
<
{
path
:
string
;
label
:
string
}
>
Array
<
{
path
:
string
;
label
:
string
}
>
>
([]);
>
([]);
const
{
restartApp
}
=
useRunApp
();
// Load router related files to extract routes
// Load router related files to extract routes
const
{
content
:
routerContent
}
=
useLoadAppFile
(
const
{
content
:
routerContent
}
=
useLoadAppFile
(
selectedAppId
,
selectedAppId
,
...
@@ -423,6 +425,10 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
...
@@ -423,6 +425,10 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
);
);
}
}
const
onRestart
=
()
=>
{
restartApp
();
};
return
(
return
(
<
div
className=
"flex flex-col h-full"
>
<
div
className=
"flex flex-col h-full"
>
{
/* Browser-style header */
}
{
/* Browser-style header */
}
...
@@ -519,6 +525,14 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
...
@@ -519,6 +525,14 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
{
/* Action Buttons */
}
{
/* Action Buttons */
}
<
div
className=
"flex space-x-1"
>
<
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
<
button
data
-
testid=
"preview-open-browser-button"
data
-
testid=
"preview-open-browser-button"
onClick=
{
()
=>
{
onClick=
{
()
=>
{
...
...
src/components/preview_panel/PreviewPanel.tsx
浏览文件 @
4b84b12f
...
@@ -5,47 +5,15 @@ import {
...
@@ -5,47 +5,15 @@ import {
previewPanelKeyAtom
,
previewPanelKeyAtom
,
selectedAppIdAtom
,
selectedAppIdAtom
,
}
from
"../../atoms/appAtoms"
;
}
from
"../../atoms/appAtoms"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
CodeView
}
from
"./CodeView"
;
import
{
CodeView
}
from
"./CodeView"
;
import
{
PreviewIframe
}
from
"./PreviewIframe"
;
import
{
PreviewIframe
}
from
"./PreviewIframe"
;
import
{
Problems
}
from
"./Problems"
;
import
{
Problems
}
from
"./Problems"
;
import
{
import
{
ChevronDown
,
ChevronUp
,
Logs
}
from
"lucide-react"
;
Eye
,
import
{
useEffect
,
useRef
,
useState
}
from
"react"
;
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
{
PanelGroup
,
Panel
,
PanelResizeHandle
}
from
"react-resizable-panels"
;
import
{
PanelGroup
,
Panel
,
PanelResizeHandle
}
from
"react-resizable-panels"
;
import
{
Console
}
from
"./Console"
;
import
{
Console
}
from
"./Console"
;
import
{
useRunApp
}
from
"@/hooks/useRunApp"
;
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
{
interface
ConsoleHeaderProps
{
isOpen
:
boolean
;
isOpen
:
boolean
;
...
@@ -53,164 +21,6 @@ interface ConsoleHeaderProps {
...
@@ -53,164 +21,6 @@ interface ConsoleHeaderProps {
latestMessage
?:
string
;
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
// Console header component
const
ConsoleHeader
=
({
const
ConsoleHeader
=
({
isOpen
,
isOpen
,
...
@@ -237,11 +47,10 @@ const ConsoleHeader = ({
...
@@ -237,11 +47,10 @@ const ConsoleHeader = ({
// Main PreviewPanel component
// Main PreviewPanel component
export
function
PreviewPanel
()
{
export
function
PreviewPanel
()
{
const
[
previewMode
,
setPreviewMode
]
=
useAtom
(
previewModeAtom
);
const
[
previewMode
]
=
useAtom
(
previewModeAtom
);
const
selectedAppId
=
useAtomValue
(
selectedAppIdAtom
);
const
selectedAppId
=
useAtomValue
(
selectedAppIdAtom
);
const
[
isConsoleOpen
,
setIsConsoleOpen
]
=
useState
(
false
);
const
[
isConsoleOpen
,
setIsConsoleOpen
]
=
useState
(
false
);
const
{
runApp
,
stopApp
,
restartApp
,
loading
,
app
,
refreshAppIframe
}
=
const
{
runApp
,
stopApp
,
loading
,
app
}
=
useRunApp
();
useRunApp
();
const
runningAppIdRef
=
useRef
<
number
|
null
>
(
null
);
const
runningAppIdRef
=
useRef
<
number
|
null
>
(
null
);
const
key
=
useAtomValue
(
previewPanelKeyAtom
);
const
key
=
useAtomValue
(
previewPanelKeyAtom
);
const
appOutput
=
useAtomValue
(
appOutputAtom
);
const
appOutput
=
useAtomValue
(
appOutputAtom
);
...
@@ -250,37 +59,6 @@ export function PreviewPanel() {
...
@@ -250,37 +59,6 @@ export function PreviewPanel() {
const
latestMessage
=
const
latestMessage
=
messageCount
>
0
?
appOutput
[
messageCount
-
1
]?.
message
:
undefined
;
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
(()
=>
{
useEffect
(()
=>
{
const
previousAppId
=
runningAppIdRef
.
current
;
const
previousAppId
=
runningAppIdRef
.
current
;
...
@@ -327,13 +105,6 @@ export function PreviewPanel() {
...
@@ -327,13 +105,6 @@ export function PreviewPanel() {
},
[
selectedAppId
,
runApp
,
stopApp
]);
},
[
selectedAppId
,
runApp
,
stopApp
]);
return
(
return
(
<
div
className=
"flex flex-col h-full"
>
<
div
className=
"flex flex-col h-full"
>
<
PreviewHeader
previewMode=
{
previewMode
}
setPreviewMode=
{
setPreviewMode
}
onRestart=
{
handleRestart
}
onCleanRestart=
{
handleCleanRestart
}
onClearSessionData=
{
handleClearSessionData
}
/>
<
div
className=
"flex-1 overflow-hidden"
>
<
div
className=
"flex-1 overflow-hidden"
>
<
PanelGroup
direction=
"vertical"
>
<
PanelGroup
direction=
"vertical"
>
<
Panel
id=
"content"
minSize=
{
30
}
>
<
Panel
id=
"content"
minSize=
{
30
}
>
...
...
src/hooks/useRunApp.ts
浏览文件 @
4b84b12f
import
{
useState
,
useCallback
}
from
"react"
;
import
{
useCallback
}
from
"react"
;
import
{
atom
}
from
"jotai"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
import
{
import
{
appOutputAtom
,
appOutputAtom
,
...
@@ -11,8 +12,10 @@ import {
...
@@ -11,8 +12,10 @@ import {
import
{
useAtom
,
useAtomValue
,
useSetAtom
}
from
"jotai"
;
import
{
useAtom
,
useAtomValue
,
useSetAtom
}
from
"jotai"
;
import
{
AppOutput
}
from
"@/ipc/ipc_types"
;
import
{
AppOutput
}
from
"@/ipc/ipc_types"
;
const
useRunAppLoadingAtom
=
atom
(
false
);
export
function
useRunApp
()
{
export
function
useRunApp
()
{
const
[
loading
,
setLoading
]
=
use
State
(
false
);
const
[
loading
,
setLoading
]
=
use
Atom
(
useRunAppLoadingAtom
);
const
[
app
,
setApp
]
=
useAtom
(
currentAppAtom
);
const
[
app
,
setApp
]
=
useAtom
(
currentAppAtom
);
const
setAppOutput
=
useSetAtom
(
appOutputAtom
);
const
setAppOutput
=
useSetAtom
(
appOutputAtom
);
const
[
appUrlObj
,
setAppUrlObj
]
=
useAtom
(
appUrlAtom
);
const
[
appUrlObj
,
setAppUrlObj
]
=
useAtom
(
appUrlAtom
);
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论