Compare commits

..

3 Commits

2 changed files with 82 additions and 10 deletions
+32 -2
View File
@@ -1,7 +1,7 @@
import { PaletteMode } from '@mui/material'
import { alpha, createTheme } from '@mui/material/styles'
export type ThemeScheme = 'blue' | 'graphite' | 'forest' | 'copper' | 'ocean' | 'olive'
export type ThemeScheme = 'blue' | 'graphite' | 'forest' | 'copper' | 'ocean' | 'olive' | 'metallic-blue' | 'rose'
type SchemeTokens = {
primary: string
@@ -109,13 +109,43 @@ const schemeDefinitions: Record<ThemeScheme, SchemeDefinition> = {
backgroundDefault: '#17180f',
backgroundPaper: '#212316'
}
},
'metallic-blue': {
label: 'Metallic Blue',
light: {
primary: '#32527b',
secondary: '#6f879f',
backgroundDefault: '#f2f5f8',
backgroundPaper: '#ffffff'
},
dark: {
primary: '#8fb4d8',
secondary: '#b2c6d8',
backgroundDefault: '#0e151c',
backgroundPaper: '#151f29'
}
},
rose: {
label: 'Rose',
light: {
primary: '#9e3059',
secondary: '#c2607f',
backgroundDefault: '#fdf4f6',
backgroundPaper: '#ffffff'
},
dark: {
primary: '#e8839c',
secondary: '#f0a8b8',
backgroundDefault: '#1a0d11',
backgroundPaper: '#25131a'
}
}
}
export const themeSchemeOptions = Object.entries(schemeDefinitions).map(([value, definition]) => ({
value: value as ThemeScheme,
label: definition.label
}))
})).sort((left, right) => left.label.localeCompare(right.label))
export function isThemeScheme(value: string | null): value is ThemeScheme {
if (!value) {
+50 -8
View File
@@ -15,6 +15,8 @@ import Typography from '@mui/material/Typography'
import useMediaQuery from '@mui/material/useMediaQuery'
import { useTheme } from '@mui/material/styles'
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
import FullscreenIcon from '@mui/icons-material/Fullscreen'
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit'
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'
import KeyboardIcon from '@mui/icons-material/Keyboard'
import MoreVertIcon from '@mui/icons-material/MoreVert'
@@ -86,6 +88,8 @@ type SessionPanelProps = {
isSynced: boolean
syncedSessionIDs: string[]
onToggleSync: (sessionID: string) => void
isFullscreen: boolean
onFullscreenChange: (value: boolean) => void
}
type TerminalInfo = {
@@ -194,7 +198,7 @@ function isSSHSessionStreamActive(status: string): boolean {
}
function SessionTerminalPanel(props: SessionPanelProps) {
const { sessionID, openSessionIDs, layoutToken, registerStream, unregisterStream, sendStreamInput, sendStreamResize, onDuplicate, onClose, onReplace, onSummaryChange, onCheckCanAddPanel, isSynced, syncedSessionIDs, onToggleSync } = props
const { sessionID, openSessionIDs, layoutToken, registerStream, unregisterStream, sendStreamInput, sendStreamResize, onDuplicate, onClose, onReplace, onSummaryChange, onCheckCanAddPanel, isSynced, syncedSessionIDs, onToggleSync, isFullscreen, onFullscreenChange } = props
const [session, setSession] = useState<SSHSession | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
@@ -323,6 +327,18 @@ function SessionTerminalPanel(props: SessionPanelProps) {
}
}, [layoutToken, sessionReady])
useEffect(() => {
let settleTimerOne: number
let settleTimerTwo: number
settleTimerOne = window.setTimeout(() => { sendResize() }, 50)
settleTimerTwo = window.setTimeout(() => { sendResize() }, 200)
return () => {
window.clearTimeout(settleTimerOne)
window.clearTimeout(settleTimerTwo)
}
}, [isFullscreen])
useEffect(() => {
// deal with actual terminal part
@@ -663,20 +679,27 @@ function SessionTerminalPanel(props: SessionPanelProps) {
<TintedPanel
sx={{
p: 0.75,
height: '100%',
minHeight: 200,
height: isFullscreen ? '100vh' : '100%',
minHeight: isFullscreen ? 'unset' : 200,
display: 'flex',
flexDirection: 'column',
gap: 0.75,
overflow: 'hidden',
minWidth: 0,
...(isFullscreen ? {
position: 'fixed',
inset: 0,
zIndex: 1400,
borderRadius: 0,
backgroundColor: 'background.paper',
} : {}),
outline: (openSessionIDs.length > 1 && isSynced) ? (theme) => `2px solid ${theme.palette.warning.main}` : (openSessionIDs.length > 1 && terminalFocused) ? (theme) => `2px solid ${theme.palette.primary.main}` : '2px solid transparent',
outlineOffset: '-2px',
transition: 'outline-color 0.15s ease'
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1, flexWrap: 'wrap', px: 0.25 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, minWidth: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1, overflow: 'hidden', px: 0.25 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, minWidth: 0, overflow: 'hidden' }}>
<Typography variant="subtitle1" noWrap >
{sessionTitle}
</Typography>
@@ -693,7 +716,7 @@ function SessionTerminalPanel(props: SessionPanelProps) {
</Tooltip>
) : null}
</Box>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Box sx={{ display: 'flex', gap: 1, flexShrink: 0 }}>
<Tooltip title={actionPending ? (canReconnect ? 'Connecting...' : 'Disconnecting...') : sessionConnectOrDisconnectLabel}>
<span>
<IconButton size="small" onClick={() => void handleSessionConnectOrDisconnect()} disabled={!canSessionAction}>
@@ -703,7 +726,7 @@ function SessionTerminalPanel(props: SessionPanelProps) {
</Tooltip>
<Tooltip title="Duplicate">
<span>
<IconButton size="small" onClick={() => void handleDuplicate()} disabled={!canDuplicate}>
<IconButton size="small" onClick={() => void handleDuplicate()} disabled={!canDuplicate || isFullscreen}>
<ContentCopyIcon fontSize="small" />
</IconButton>
</span>
@@ -717,6 +740,11 @@ function SessionTerminalPanel(props: SessionPanelProps) {
</span>
</Tooltip>
) : null}
<Tooltip title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}>
<IconButton size="small" onClick={() => onFullscreenChange(!isFullscreen)}>
{isFullscreen ? <FullscreenExitIcon fontSize="small" /> : <FullscreenIcon fontSize="small" />}
</IconButton>
</Tooltip>
<Tooltip title="More options">
<IconButton size="small" onClick={handleOpenActionMenu}>
<MoreVertIcon fontSize="small" />
@@ -825,6 +853,7 @@ export default function SSHSessionPage() {
const theme = useTheme()
const [openSessionIDs, setOpenSessionIDs] = useState<string[]>([])
const [sessionSummaries, setSessionSummaries] = useState<Record<string, SessionSummary>>({})
const [fullscreenSessionID, setFullscreenSessionID] = useState<string | null>(null)
const [profiles, setProfiles] = useState<SSHAccessProfile[]>([])
const [loadingProfiles, setLoadingProfiles] = useState(false)
const [historyItems, setHistoryItems] = useState<SSHSession[]>([])
@@ -969,14 +998,21 @@ export default function SSHSessionPage() {
useEffect(() => {
const state = location.state as { historyOpen?: boolean, connectFailures?: string[] } | null
let consumed: boolean
consumed = false
if (state?.historyOpen) {
setHistoryOpen(true)
consumed = true
}
if (Array.isArray(state?.connectFailures)) {
setConnectFailures(state.connectFailures)
consumed = true
}
}, [location.state])
if (consumed) {
navigate(`${location.pathname}${location.search}`, { replace: true, state: null })
}
}, [location.pathname, location.search, location.state, navigate])
const loadHistory = useCallback(async (signal?: AbortSignal) => {
let response: SSHSessionListResponse
@@ -1160,6 +1196,7 @@ export default function SSHSessionPage() {
next.delete(id)
return next
})
setFullscreenSessionID((prev) => (prev === id ? null : prev))
}, [])
const handleReplaceSession = useCallback((currentID: string, nextID: string) => {
@@ -1169,6 +1206,7 @@ export default function SSHSessionPage() {
delete next[currentID]
return next
})
setFullscreenSessionID((prev) => (prev === currentID ? nextID : prev))
// force to check the stream websocket in case it's closed
setStreamWSCheck((prev) => prev + 1)
@@ -2088,6 +2126,8 @@ export default function SSHSessionPage() {
isSynced={syncedSessionIDs.has(id)}
syncedSessionIDs={syncedSessionIDsArray}
onToggleSync={handleToggleSync}
isFullscreen={fullscreenSessionID === id}
onFullscreenChange={(value) => setFullscreenSessionID(value ? id : null)}
/>
</Box>
))}
@@ -2181,6 +2221,8 @@ export default function SSHSessionPage() {
isSynced={syncedSessionIDs.has(id)}
syncedSessionIDs={syncedSessionIDsArray}
onToggleSync={handleToggleSync}
isFullscreen={fullscreenSessionID === id}
onFullscreenChange={(value) => setFullscreenSessionID(value ? id : null)}
/>
</Box>
))}