|
|
@ -1,6 +1,6 @@ |
|
|
|
import { |
|
|
|
import { |
|
|
|
DesktopWindows as DesktopWindowsIcon, |
|
|
|
DesktopWindows as DesktopWindowsIcon, |
|
|
|
PowerOffOutlined as PowerOffOutlinedIcon, |
|
|
|
PowerSettingsNewOutlined as PowerSettingsNewOutlinedIcon, |
|
|
|
} from '@mui/icons-material'; |
|
|
|
} from '@mui/icons-material'; |
|
|
|
import { |
|
|
|
import { |
|
|
|
Box, |
|
|
|
Box, |
|
|
@ -9,18 +9,24 @@ import { |
|
|
|
} from '@mui/material'; |
|
|
|
} from '@mui/material'; |
|
|
|
import { FC, ReactNode, useEffect, useMemo, useState } from 'react'; |
|
|
|
import { FC, ReactNode, useEffect, useMemo, useState } from 'react'; |
|
|
|
|
|
|
|
|
|
|
|
import API_BASE_URL from '../../lib/consts/API_BASE_URL'; |
|
|
|
import { |
|
|
|
import { BORDER_RADIUS, GREY } from '../../lib/consts/DEFAULT_THEME'; |
|
|
|
BORDER_RADIUS, |
|
|
|
|
|
|
|
GREY, |
|
|
|
|
|
|
|
UNSELECTED, |
|
|
|
|
|
|
|
} from '../../lib/consts/DEFAULT_THEME'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import api from '../../lib/api'; |
|
|
|
import FlexBox from '../FlexBox'; |
|
|
|
import FlexBox from '../FlexBox'; |
|
|
|
import IconButton, { IconButtonProps } from '../IconButton'; |
|
|
|
import IconButton, { IconButtonProps } from '../IconButton'; |
|
|
|
import { InnerPanel, InnerPanelHeader, Panel, PanelHeader } from '../Panels'; |
|
|
|
import { InnerPanel, InnerPanelHeader, Panel, PanelHeader } from '../Panels'; |
|
|
|
import Spinner from '../Spinner'; |
|
|
|
import Spinner from '../Spinner'; |
|
|
|
import { BodyText, HeaderText } from '../Text'; |
|
|
|
import { BodyText, HeaderText } from '../Text'; |
|
|
|
|
|
|
|
import { last } from '../../lib/time'; |
|
|
|
|
|
|
|
|
|
|
|
type PreviewOptionalProps = { |
|
|
|
type PreviewOptionalProps = { |
|
|
|
externalPreview?: string; |
|
|
|
externalPreview?: string; |
|
|
|
headerEndAdornment?: ReactNode; |
|
|
|
headerEndAdornment?: ReactNode; |
|
|
|
|
|
|
|
isExternalLoading?: boolean; |
|
|
|
isExternalPreviewStale?: boolean; |
|
|
|
isExternalPreviewStale?: boolean; |
|
|
|
isFetchPreview?: boolean; |
|
|
|
isFetchPreview?: boolean; |
|
|
|
isShowControls?: boolean; |
|
|
|
isShowControls?: boolean; |
|
|
@ -28,6 +34,7 @@ type PreviewOptionalProps = { |
|
|
|
onClickConnectButton?: IconButtonProps['onClick']; |
|
|
|
onClickConnectButton?: IconButtonProps['onClick']; |
|
|
|
onClickPreview?: MUIIconButtonProps['onClick']; |
|
|
|
onClickPreview?: MUIIconButtonProps['onClick']; |
|
|
|
serverName?: string; |
|
|
|
serverName?: string; |
|
|
|
|
|
|
|
serverState?: string; |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
type PreviewProps = PreviewOptionalProps & { |
|
|
|
type PreviewProps = PreviewOptionalProps & { |
|
|
@ -40,6 +47,7 @@ const PREVIEW_DEFAULT_PROPS: Required< |
|
|
|
Pick<PreviewOptionalProps, 'onClickConnectButton' | 'onClickPreview'> = { |
|
|
|
Pick<PreviewOptionalProps, 'onClickConnectButton' | 'onClickPreview'> = { |
|
|
|
externalPreview: '', |
|
|
|
externalPreview: '', |
|
|
|
headerEndAdornment: null, |
|
|
|
headerEndAdornment: null, |
|
|
|
|
|
|
|
isExternalLoading: false, |
|
|
|
isExternalPreviewStale: false, |
|
|
|
isExternalPreviewStale: false, |
|
|
|
isFetchPreview: true, |
|
|
|
isFetchPreview: true, |
|
|
|
isShowControls: true, |
|
|
|
isShowControls: true, |
|
|
@ -47,6 +55,7 @@ const PREVIEW_DEFAULT_PROPS: Required< |
|
|
|
onClickConnectButton: undefined, |
|
|
|
onClickConnectButton: undefined, |
|
|
|
onClickPreview: undefined, |
|
|
|
onClickPreview: undefined, |
|
|
|
serverName: '', |
|
|
|
serverName: '', |
|
|
|
|
|
|
|
serverState: '', |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const PreviewPanel: FC<{ isUseInnerPanel: boolean }> = ({ |
|
|
|
const PreviewPanel: FC<{ isUseInnerPanel: boolean }> = ({ |
|
|
@ -78,12 +87,14 @@ const PreviewPanelHeader: FC<{ |
|
|
|
const Preview: FC<PreviewProps> = ({ |
|
|
|
const Preview: FC<PreviewProps> = ({ |
|
|
|
externalPreview = PREVIEW_DEFAULT_PROPS.externalPreview, |
|
|
|
externalPreview = PREVIEW_DEFAULT_PROPS.externalPreview, |
|
|
|
headerEndAdornment, |
|
|
|
headerEndAdornment, |
|
|
|
|
|
|
|
isExternalLoading = PREVIEW_DEFAULT_PROPS.isExternalLoading, |
|
|
|
isExternalPreviewStale = PREVIEW_DEFAULT_PROPS.isExternalPreviewStale, |
|
|
|
isExternalPreviewStale = PREVIEW_DEFAULT_PROPS.isExternalPreviewStale, |
|
|
|
isFetchPreview = PREVIEW_DEFAULT_PROPS.isFetchPreview, |
|
|
|
isFetchPreview = PREVIEW_DEFAULT_PROPS.isFetchPreview, |
|
|
|
isShowControls = PREVIEW_DEFAULT_PROPS.isShowControls, |
|
|
|
isShowControls = PREVIEW_DEFAULT_PROPS.isShowControls, |
|
|
|
isUseInnerPanel = PREVIEW_DEFAULT_PROPS.isUseInnerPanel, |
|
|
|
isUseInnerPanel = PREVIEW_DEFAULT_PROPS.isUseInnerPanel, |
|
|
|
onClickPreview: previewClickHandler, |
|
|
|
onClickPreview: previewClickHandler, |
|
|
|
serverName, |
|
|
|
serverName, |
|
|
|
|
|
|
|
serverState = PREVIEW_DEFAULT_PROPS.serverState, |
|
|
|
serverUUID, |
|
|
|
serverUUID, |
|
|
|
onClickConnectButton: connectButtonClickHandle = previewClickHandler, |
|
|
|
onClickConnectButton: connectButtonClickHandle = previewClickHandler, |
|
|
|
}) => { |
|
|
|
}) => { |
|
|
@ -93,58 +104,66 @@ const Preview: FC<PreviewProps> = ({ |
|
|
|
|
|
|
|
|
|
|
|
const previewButtonContent = useMemo( |
|
|
|
const previewButtonContent = useMemo( |
|
|
|
() => |
|
|
|
() => |
|
|
|
preview ? ( |
|
|
|
serverState === 'running' ? ( |
|
|
|
<Box |
|
|
|
<> |
|
|
|
alt="" |
|
|
|
<Box |
|
|
|
component="img" |
|
|
|
alt="" |
|
|
|
src={`data:image/png;base64,${preview}`} |
|
|
|
component="img" |
|
|
|
sx={{ |
|
|
|
src={`data:image;base64,${preview}`} |
|
|
|
height: '100%', |
|
|
|
sx={{ |
|
|
|
opacity: isPreviewStale ? '0.4' : '1', |
|
|
|
height: '100%', |
|
|
|
padding: isUseInnerPanel ? '.2em' : 0, |
|
|
|
opacity: isPreviewStale ? '0.4' : '1', |
|
|
|
width: '100%', |
|
|
|
padding: isUseInnerPanel ? '.2em' : 0, |
|
|
|
}} |
|
|
|
width: '100%', |
|
|
|
/> |
|
|
|
}} |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
{isPreviewStale && ( |
|
|
|
|
|
|
|
<BodyText sx={{ position: 'absolute' }}>Outdated</BodyText> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
</> |
|
|
|
) : ( |
|
|
|
) : ( |
|
|
|
<PowerOffOutlinedIcon |
|
|
|
<PowerSettingsNewOutlinedIcon |
|
|
|
sx={{ |
|
|
|
sx={{ |
|
|
|
height: '100%', |
|
|
|
color: UNSELECTED, |
|
|
|
width: '100%', |
|
|
|
height: '80%', |
|
|
|
|
|
|
|
width: '80%', |
|
|
|
}} |
|
|
|
}} |
|
|
|
/> |
|
|
|
/> |
|
|
|
), |
|
|
|
), |
|
|
|
[isPreviewStale, isUseInnerPanel, preview], |
|
|
|
[isPreviewStale, isUseInnerPanel, preview, serverState], |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
if (isFetchPreview) { |
|
|
|
if (isFetchPreview) { |
|
|
|
(async () => { |
|
|
|
(async () => { |
|
|
|
try { |
|
|
|
try { |
|
|
|
const response = await fetch( |
|
|
|
const { data } = await api.get<{ |
|
|
|
`${API_BASE_URL}/server/${serverUUID}?ss=1`, |
|
|
|
screenshot: string; |
|
|
|
{ |
|
|
|
timestamp: number; |
|
|
|
method: 'GET', |
|
|
|
}>(`/server/${serverUUID}?ss=1`); |
|
|
|
headers: { |
|
|
|
|
|
|
|
'Content-Type': 'application/json', |
|
|
|
const { screenshot, timestamp } = data; |
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
const { screenshot: fetchedScreenshot } = await response.json(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setPreview(fetchedScreenshot); |
|
|
|
setPreview(screenshot); |
|
|
|
setIsPreviewStale(false); |
|
|
|
setIsPreviewStale(!last(timestamp, 300)); |
|
|
|
} catch { |
|
|
|
} catch { |
|
|
|
setIsPreviewStale(true); |
|
|
|
setIsPreviewStale(true); |
|
|
|
} finally { |
|
|
|
} finally { |
|
|
|
setIsPreviewLoading(false); |
|
|
|
setIsPreviewLoading(false); |
|
|
|
} |
|
|
|
} |
|
|
|
})(); |
|
|
|
})(); |
|
|
|
} else if (externalPreview) { |
|
|
|
} else if (!isExternalLoading) { |
|
|
|
setPreview(externalPreview); |
|
|
|
setPreview(externalPreview); |
|
|
|
setIsPreviewStale(isExternalPreviewStale); |
|
|
|
setIsPreviewStale(isExternalPreviewStale); |
|
|
|
setIsPreviewLoading(false); |
|
|
|
setIsPreviewLoading(false); |
|
|
|
} |
|
|
|
} |
|
|
|
}, [externalPreview, isExternalPreviewStale, isFetchPreview, serverUUID]); |
|
|
|
}, [ |
|
|
|
|
|
|
|
externalPreview, |
|
|
|
|
|
|
|
isExternalLoading, |
|
|
|
|
|
|
|
isExternalPreviewStale, |
|
|
|
|
|
|
|
isFetchPreview, |
|
|
|
|
|
|
|
serverUUID, |
|
|
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<PreviewPanel isUseInnerPanel={isUseInnerPanel}> |
|
|
|
<PreviewPanel isUseInnerPanel={isUseInnerPanel}> |
|
|
@ -153,12 +172,13 @@ const Preview: FC<PreviewProps> = ({ |
|
|
|
</PreviewPanelHeader> |
|
|
|
</PreviewPanelHeader> |
|
|
|
<FlexBox row sx={{ '& > :first-child': { flexGrow: 1 } }}> |
|
|
|
<FlexBox row sx={{ '& > :first-child': { flexGrow: 1 } }}> |
|
|
|
{/* Box wrapper below is required to keep external preview size sane. */} |
|
|
|
{/* Box wrapper below is required to keep external preview size sane. */} |
|
|
|
<Box> |
|
|
|
<Box textAlign="center"> |
|
|
|
{isPreviewLoading ? ( |
|
|
|
{isPreviewLoading ? ( |
|
|
|
<Spinner mt="1em" mb="1em" /> |
|
|
|
<Spinner mt="1em" mb="1em" /> |
|
|
|
) : ( |
|
|
|
) : ( |
|
|
|
<MUIIconButton |
|
|
|
<MUIIconButton |
|
|
|
component="span" |
|
|
|
component="span" |
|
|
|
|
|
|
|
disabled={!preview} |
|
|
|
onClick={previewClickHandler} |
|
|
|
onClick={previewClickHandler} |
|
|
|
sx={{ |
|
|
|
sx={{ |
|
|
|
borderRadius: BORDER_RADIUS, |
|
|
|
borderRadius: BORDER_RADIUS, |
|
|
@ -170,7 +190,7 @@ const Preview: FC<PreviewProps> = ({ |
|
|
|
</MUIIconButton> |
|
|
|
</MUIIconButton> |
|
|
|
)} |
|
|
|
)} |
|
|
|
</Box> |
|
|
|
</Box> |
|
|
|
{isShowControls && ( |
|
|
|
{isShowControls && preview && ( |
|
|
|
<FlexBox> |
|
|
|
<FlexBox> |
|
|
|
<IconButton onClick={connectButtonClickHandle}> |
|
|
|
<IconButton onClick={connectButtonClickHandle}> |
|
|
|
<DesktopWindowsIcon /> |
|
|
|
<DesktopWindowsIcon /> |
|
|
|