Skip to content

Commit d944590

Browse files
committed
fix(web): keep browser layout within viewport
1 parent a5345a8 commit d944590

11 files changed

Lines changed: 323 additions & 103 deletions

packages/app/src/ui/primitives-web.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const unit = (value: number | string | undefined): string | number | undefined =
99
return typeof value === "number" ? `${value * 8}px` : value
1010
}
1111

12-
const borderRadius = (borderStyle: UiBoxProps["borderStyle"]): string => borderStyle === "rounded" ? "12px" : "0"
12+
const borderRadius = (borderStyle: UiBoxProps["borderStyle"]): string => borderStyle === "rounded" ? "8px" : "0"
1313

1414
const borderValue = (
1515
enabled: boolean | undefined,
@@ -24,8 +24,10 @@ const baseStyle = (props: UiBoxProps): CSSProperties => ({
2424
boxSizing: "border-box",
2525
color: props.fg,
2626
display: "flex",
27+
flexBasis: props.flexBasis,
2728
flexDirection: props.flexDirection ?? "row",
2829
flexGrow: props.flexGrow,
30+
flexShrink: props.flexShrink,
2931
flexWrap: props.flexWrap,
3032
gap: unit(props.gap),
3133
height: unit(props.height),
@@ -34,6 +36,13 @@ const baseStyle = (props: UiBoxProps): CSSProperties => ({
3436
marginLeft: unit(props.marginLeft),
3537
marginRight: unit(props.marginRight),
3638
marginTop: unit(props.marginTop),
39+
maxHeight: unit(props.maxHeight),
40+
maxWidth: unit(props.maxWidth),
41+
minHeight: unit(props.minHeight),
42+
minWidth: unit(props.minWidth),
43+
overflow: props.overflow,
44+
overflowX: props.overflowX,
45+
overflowY: props.overflowY,
3746
padding: unit(props.padding),
3847
width: unit(props.width)
3948
})
@@ -60,7 +69,7 @@ const interactiveStyle = (width: UiBoxProps["width"]): CSSProperties => ({
6069
const inputStyle: CSSProperties = {
6170
background: "#07101c",
6271
border: "1px solid #24537d",
63-
borderRadius: "10px",
72+
borderRadius: "8px",
6473
boxSizing: "border-box",
6574
color: "#56f39a",
6675
font: "inherit",

packages/app/src/ui/primitives.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ export type UiBoxProps = {
1616
readonly borderStyle?: "rounded" | "single"
1717
readonly children?: ReactNode
1818
readonly fg?: string
19+
readonly flexBasis?: CSSProperties["flexBasis"]
1920
readonly flexDirection?: CSSProperties["flexDirection"]
2021
readonly flexGrow?: number
22+
readonly flexShrink?: number
2123
readonly flexWrap?: CSSProperties["flexWrap"]
2224
readonly gap?: number | string
2325
readonly height?: number | string
@@ -26,7 +28,14 @@ export type UiBoxProps = {
2628
readonly marginLeft?: number | string
2729
readonly marginRight?: number | string
2830
readonly marginTop?: number | string
31+
readonly maxHeight?: number | string
32+
readonly maxWidth?: number | string
33+
readonly minHeight?: number | string
34+
readonly minWidth?: number | string
2935
readonly onClick?: MouseEventHandler<HTMLElement> | (() => void)
36+
readonly overflow?: CSSProperties["overflow"]
37+
readonly overflowX?: CSSProperties["overflowX"]
38+
readonly overflowY?: CSSProperties["overflowY"]
3039
readonly padding?: number | string
3140
readonly width?: number | string
3241
}

packages/app/src/web/app-ready-layout.tsx

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import { MainPanels } from "./app-ready-main-panels.js"
77
import { Box, Text } from "./elements.js"
88
import type { BrowserMenuTag } from "./menu.js"
99
import type { ActiveTerminalSession } from "./terminal.js"
10+
import type { ViewportLayout } from "./viewport-layout.js"
1011

1112
export type ReadyLayoutProps = {
1213
readonly actionPrompt: ActionPromptState | null
1314
readonly authSnapshot: AuthSnapshot | null
1415
readonly busyLabel: string | null
15-
readonly compact: boolean
1616
readonly controllerCwd: string
1717
readonly projectsRoot: string
1818
readonly createView: CreateFlowView
@@ -40,39 +40,75 @@ export type ReadyLayoutProps = {
4040
readonly selectedProjectId: string | null
4141
readonly selectedProjectSummary: DashboardData["projects"][number] | undefined
4242
readonly terminalSession: ActiveTerminalSession | null
43+
readonly viewportLayout: ViewportLayout
4344
}
4445

46+
const headerPadding = (viewportLayout: ViewportLayout): number | string =>
47+
viewportLayout.compact || viewportLayout.dense ? "6px" : 1
48+
49+
const headerGap = (viewportLayout: ViewportLayout): number => viewportLayout.compact ? 1 : 2
50+
51+
const headerMetricsTopMargin = (viewportLayout: ViewportLayout): number | string => viewportLayout.compact ? "4px" : 1
52+
53+
const HeaderTitle = ({ compact }: Pick<ViewportLayout, "compact">): JSX.Element => (
54+
<Box flexWrap="wrap" gap={1} justifyContent="space-between">
55+
<Text bold={true} fg="#f6fbff">docker-git browser</Text>
56+
{compact ? null : <Text fg="#7fdfff">Gridland menu shell</Text>}
57+
</Box>
58+
)
59+
60+
const StatusText = ({ busyLabel }: Pick<ReadyLayoutProps, "busyLabel">): JSX.Element => (
61+
<Text fg={busyLabel === null ? "#8fa6c4" : "#ffd166"}>{busyLabel ?? "idle"}</Text>
62+
)
63+
64+
const HeaderMetrics = (
65+
{ busyLabel, dashboard, viewportLayout }: Pick<ReadyLayoutProps, "busyLabel" | "dashboard" | "viewportLayout">
66+
): JSX.Element => (
67+
<Box flexWrap="wrap" gap={headerGap(viewportLayout)} marginTop={headerMetricsTopMargin(viewportLayout)}>
68+
<Text fg="#9fd7ff" wrap="truncate">API: {dashboard.apiBaseUrl}</Text>
69+
<Text fg="#56f39a">health: ok</Text>
70+
<Text fg="#ffd166" wrap="truncate">revision: {dashboard.health.revision ?? "none"}</Text>
71+
<Text fg="#d4e3f4">projects: {dashboard.projects.length}</Text>
72+
{viewportLayout.compact ? null : <Text fg="#7fa8cf">refresh: 15s</Text>}
73+
<StatusText busyLabel={busyLabel} />
74+
</Box>
75+
)
76+
77+
const HeaderMessage = ({ message }: Pick<ReadyLayoutProps, "message">): JSX.Element | null =>
78+
message === null
79+
? null
80+
: <Text fg="#f6d27b" marginTop="4px" wrap="truncate">message: {message}</Text>
81+
4582
const StatusHeader = (
46-
{ busyLabel, dashboard, message }: Pick<ReadyLayoutProps, "busyLabel" | "dashboard" | "message">
83+
{ busyLabel, dashboard, message, viewportLayout }: Pick<
84+
ReadyLayoutProps,
85+
"busyLabel" | "dashboard" | "message" | "viewportLayout"
86+
>
4787
): JSX.Element => (
4888
<Box
4989
backgroundColor="#0a1730"
5090
border={true}
5191
borderColor="#39d0ff"
5292
borderStyle="rounded"
5393
flexDirection="column"
94+
flexShrink={0}
5495
marginBottom={1}
55-
padding={1}
96+
padding={headerPadding(viewportLayout)}
5697
>
57-
<Box justifyContent="space-between">
58-
<Text bold={true} fg="#f6fbff">docker-git browser</Text>
59-
<Text fg="#7fdfff">Gridland menu shell</Text>
60-
</Box>
61-
<Text fg="#9fd7ff">API: {dashboard.apiBaseUrl}</Text>
62-
<Box gap={2} marginTop={1}>
63-
<Text fg="#56f39a">health: ok</Text>
64-
<Text fg="#ffd166">revision: {dashboard.health.revision ?? "none"}</Text>
65-
<Text fg="#d4e3f4">projects: {dashboard.projects.length}</Text>
66-
<Text fg="#7fa8cf">refresh: 15s</Text>
67-
<Text fg={busyLabel === null ? "#8fa6c4" : "#ffd166"}>{busyLabel ?? "idle"}</Text>
68-
</Box>
69-
{message === null ? null : <Text fg="#f6d27b">message: {message}</Text>}
98+
<HeaderTitle compact={viewportLayout.compact} />
99+
<HeaderMetrics busyLabel={busyLabel} dashboard={dashboard} viewportLayout={viewportLayout} />
100+
<HeaderMessage message={message} />
70101
</Box>
71102
)
72103

73104
export const ReadyLayout = ({ busyLabel, message, ...props }: ReadyLayoutProps): JSX.Element => (
74-
<Box flexDirection="column" height="100%" padding={1} width="100%">
75-
<StatusHeader busyLabel={busyLabel} dashboard={props.dashboard} message={message} />
105+
<Box flexDirection="column" height="100%" minHeight={0} overflow="hidden" padding={1} width="100%">
106+
<StatusHeader
107+
busyLabel={busyLabel}
108+
dashboard={props.dashboard}
109+
message={message}
110+
viewportLayout={props.viewportLayout}
111+
/>
76112
<MainPanels {...props} />
77113
</Box>
78114
)

packages/app/src/web/app-ready-main-panels.tsx

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ type CenterPanelProps =
1919
MainPanelsProps,
2020
| "actionPrompt"
2121
| "authSnapshot"
22-
| "compact"
2322
| "createView"
2423
| "controllerCwd"
2524
| "projectsRoot"
@@ -41,6 +40,7 @@ type CenterPanelProps =
4140
| "projectAuthSnapshot"
4241
| "selectedProjectSummary"
4342
| "terminalSession"
43+
| "viewportLayout"
4444
>
4545
& {
4646
readonly showProjectPanel: boolean
@@ -55,7 +55,6 @@ type CenterPanelContentProps = Pick<
5555
CenterPanelProps,
5656
| "actionPrompt"
5757
| "authSnapshot"
58-
| "compact"
5958
| "controllerCwd"
6059
| "createView"
6160
| "currentMenu"
@@ -73,6 +72,7 @@ type CenterPanelContentProps = Pick<
7372
| "projectNavigationArmed"
7473
| "projectsRoot"
7574
| "selectedProjectSummary"
75+
| "viewportLayout"
7676
>
7777

7878
const CenterPanelBody = (
@@ -95,7 +95,6 @@ const CenterPanelContent = (
9595
{
9696
actionPrompt,
9797
authSnapshot,
98-
compact,
9998
controllerCwd,
10099
createView,
101100
currentMenu,
@@ -112,13 +111,14 @@ const CenterPanelContent = (
112111
projectAuthSnapshot,
113112
projectNavigationArmed,
114113
projectsRoot,
115-
selectedProjectSummary
114+
selectedProjectSummary,
115+
viewportLayout
116116
}: CenterPanelContentProps
117117
): JSX.Element => (
118118
<ContentPanel
119119
actionPrompt={actionPrompt}
120120
authSnapshot={authSnapshot}
121-
compact={compact}
121+
compact={viewportLayout.compact}
122122
controllerCwd={controllerCwd}
123123
createView={createView}
124124
currentMenu={currentMenu}
@@ -143,7 +143,7 @@ const centerPanelWidth = (compact: boolean, showProjectPanel: boolean): string =
143143
if (compact) {
144144
return "100%"
145145
}
146-
return showProjectPanel ? "42%" : "69%"
146+
return showProjectPanel ? "48%" : "auto"
147147
}
148148

149149
const CenterPanel = (props: CenterPanelProps): JSX.Element => (
@@ -152,27 +152,45 @@ const CenterPanel = (props: CenterPanelProps): JSX.Element => (
152152
borderColor="#24537d"
153153
borderStyle="single"
154154
flexDirection="column"
155+
flexGrow={1}
156+
minHeight={0}
157+
minWidth={0}
158+
overflow="hidden"
155159
padding={1}
156-
width={centerPanelWidth(props.compact, props.showProjectPanel)}
160+
width={centerPanelWidth(props.viewportLayout.compact, props.showProjectPanel)}
157161
>
158-
<CenterPanelContent {...props} />
159-
<CenterPanelBody {...props} />
162+
<Box
163+
flexDirection="column"
164+
flexShrink={0}
165+
maxHeight={props.viewportLayout.dense ? "38%" : "46%"}
166+
overflowY="auto"
167+
>
168+
<CenterPanelContent {...props} />
169+
</Box>
170+
<Box flexDirection="column" flexGrow={1} minHeight={0} overflow="hidden">
171+
<CenterPanelBody {...props} />
172+
</Box>
160173
</Box>
161174
)
162175

163176
const ProjectPanelSlot = (
164177
{
165-
compact,
166178
currentMenu,
167179
dashboard,
168180
onSelectProject,
169181
projectNavigationArmed,
170182
selectedProjectId,
171-
showProjectPanel
183+
showProjectPanel,
184+
viewportLayout
172185
}:
173186
& Pick<
174187
MainPanelsProps,
175-
"compact" | "currentMenu" | "dashboard" | "onSelectProject" | "projectNavigationArmed" | "selectedProjectId"
188+
| "currentMenu"
189+
| "dashboard"
190+
| "onSelectProject"
191+
| "projectNavigationArmed"
192+
| "selectedProjectId"
193+
| "viewportLayout"
176194
>
177195
& {
178196
readonly showProjectPanel: boolean
@@ -181,7 +199,7 @@ const ProjectPanelSlot = (
181199
showProjectPanel
182200
? (
183201
<ProjectListPanel
184-
compact={compact}
202+
compact={viewportLayout.compact}
185203
currentMenu={currentMenu}
186204
dashboard={dashboard}
187205
onSelectProject={onSelectProject}
@@ -205,7 +223,6 @@ const MainCenterPanel = (
205223
<CenterPanel
206224
actionPrompt={props.actionPrompt}
207225
authSnapshot={props.authSnapshot}
208-
compact={props.compact}
209226
controllerCwd={props.controllerCwd}
210227
projectsRoot={props.projectsRoot}
211228
createView={props.createView}
@@ -228,15 +245,22 @@ const MainCenterPanel = (
228245
selectedProjectSummary={selectedProjectSummary}
229246
showProjectPanel={showProjectPanel}
230247
terminalSession={props.terminalSession}
248+
viewportLayout={props.viewportLayout}
231249
/>
232250
)
233251

234252
export const MainPanels = ({ selectedProjectSummary, ...props }: MainPanelsProps): JSX.Element => {
235253
const showProject = showsProjectPanel(props.currentMenu)
236254
return (
237-
<Box flexDirection={props.compact ? "column" : "row"} flexGrow={1} gap={1}>
255+
<Box
256+
flexDirection={props.viewportLayout.compact ? "column" : "row"}
257+
flexGrow={1}
258+
gap={1}
259+
minHeight={0}
260+
overflow="hidden"
261+
>
238262
<MenuSidebar
239-
compact={props.compact}
263+
compact={props.viewportLayout.compact}
240264
currentMenu={props.currentMenu}
241265
onSelectMenu={props.onSelectMenu}
242266
projectNavigationArmed={props.projectNavigationArmed}
@@ -245,13 +269,13 @@ export const MainPanels = ({ selectedProjectSummary, ...props }: MainPanelsProps
245269
/>
246270
<MainCenterPanel props={props} selectedProjectSummary={selectedProjectSummary} showProjectPanel={showProject} />
247271
<ProjectPanelSlot
248-
compact={props.compact}
249272
currentMenu={props.currentMenu}
250273
dashboard={props.dashboard}
251274
onSelectProject={props.onSelectProject}
252275
projectNavigationArmed={props.projectNavigationArmed}
253276
selectedProjectId={props.selectedProjectId}
254277
showProjectPanel={showProject}
278+
viewportLayout={props.viewportLayout}
255279
/>
256280
</Box>
257281
)

0 commit comments

Comments
 (0)