Skip to content

Commit c9929a7

Browse files
committed
frontend/process info/tree: dynamically resize table to content size
1 parent a6d862c commit c9929a7

File tree

5 files changed

+91
-48
lines changed

5 files changed

+91
-48
lines changed

src/packages/frontend/cspell.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@
7070
"flyouts",
7171
"buttonbar",
7272
"noconf",
73-
"flyoutdragbar"
73+
"flyoutdragbar",
74+
"ptree",
75+
"pchildren",
76+
"nprocs",
77+
"pids"
7478
],
7579
"flagWords": [],
7680
"ignorePaths": ["node_modules/**", "dist/**", "dist-ts/**", "build/**"],

src/packages/frontend/project/context.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface ProjectContextState {
3232
actions?: ProjectActions;
3333
active_project_tab?: string;
3434
compute_image: string | undefined;
35+
contentSize: { width: number; height: number };
3536
enabledLLMs: LLMServicesAvailable;
3637
flipTabs: [number, React.Dispatch<React.SetStateAction<number>>];
3738
group?: UserGroup;
@@ -47,22 +48,15 @@ export interface ProjectContextState {
4748
onCoCalcDocker: boolean;
4849
project_id: string;
4950
project?: Project;
51+
setContentSize: (size: { width: number; height: number }) => void;
5052
status: ProjectStatus;
5153
}
5254

5355
export const emptyProjectContext = {
5456
actions: undefined,
5557
active_project_tab: undefined,
56-
group: undefined,
57-
project: undefined,
58-
is_active: false,
59-
project_id: "",
60-
isRunning: undefined,
61-
status: INIT_PROJECT_STATE,
62-
hasInternet: undefined,
63-
flipTabs: [0, () => {}],
64-
onCoCalcCom: true,
65-
onCoCalcDocker: false,
58+
compute_image: undefined,
59+
contentSize: { width: 0, height: 0 },
6660
enabledLLMs: {
6761
openai: false,
6862
google: false,
@@ -72,12 +66,22 @@ export const emptyProjectContext = {
7266
custom_openai: false,
7367
user: false,
7468
},
69+
flipTabs: [0, () => {}],
70+
group: undefined,
71+
hasInternet: undefined,
72+
is_active: false,
73+
isRunning: undefined,
7574
mainWidthPx: 0,
7675
manageStarredFiles: {
7776
starred: [],
7877
setStarredPath: () => {},
7978
},
80-
compute_image: undefined,
79+
onCoCalcCom: true,
80+
onCoCalcDocker: false,
81+
project: undefined,
82+
project_id: "",
83+
setContentSize: () => {},
84+
status: INIT_PROJECT_STATE,
8185
} as ProjectContextState;
8286

8387
export const ProjectContext: Context<ProjectContextState> =
@@ -141,10 +145,13 @@ export function useProjectContextProvider({
141145
userDefinedLLM,
142146
]);
143147

148+
const [contentSize, setContentSize] = useState({ width: 0, height: 0 });
149+
144150
return {
145151
actions,
146152
active_project_tab,
147153
compute_image,
154+
contentSize,
148155
enabledLLMs,
149156
flipTabs,
150157
group,
@@ -157,6 +164,7 @@ export function useProjectContextProvider({
157164
onCoCalcDocker,
158165
project_id,
159166
project,
167+
setContentSize,
160168
status,
161169
};
162170
}

src/packages/frontend/project/info/full.tsx

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6+
// cspell:ignore Questionmark
7+
68
declare let DEBUG;
79

810
import { Alert, Button, Form, Modal, Popconfirm, Switch, Table } from "antd";
@@ -13,12 +15,14 @@ import { Col, Row } from "@cocalc/frontend/antd-bootstrap";
1315
import { CSS, ProjectActions, redux } from "@cocalc/frontend/app-framework";
1416
import { A, Loading, Tip } from "@cocalc/frontend/components";
1517
import { SiteName } from "@cocalc/frontend/customize";
18+
import { field_cmp, seconds2hms } from "@cocalc/util/misc";
19+
import { COLORS } from "@cocalc/util/theme";
1620
import {
1721
Process,
1822
ProjectInfo as ProjectInfoType,
1923
} from "@cocalc/util/types/project-info/types";
20-
import { field_cmp, seconds2hms } from "@cocalc/util/misc";
21-
import { COLORS } from "@cocalc/util/theme";
24+
import { useProjectContext } from "../context";
25+
import { ROOT_STYLE } from "../servers/consts";
2226
import { RestartProject } from "../settings/restart-project";
2327
import {
2428
AboutContent,
@@ -30,7 +34,6 @@ import {
3034
} from "./components";
3135
import { CGroupInfo, DUState, PTStats, ProcessRow } from "./types";
3236
import { DETAILS_BTN_TEXT, SSH_KEYS_DOC } from "./utils";
33-
import { ROOT_STYLE } from "../servers/consts";
3437

3538
interface Props {
3639
any_alerts: () => boolean;
@@ -91,46 +94,41 @@ export function Full(props: Readonly<Props>): React.JSX.Element {
9194
onCellProps,
9295
} = props;
9396

97+
const { contentSize } = useProjectContext();
98+
9499
const problemsRef = useRef<HTMLDivElement>(null);
95100
const cgroupRef = useRef<HTMLDivElement>(null);
96101
const headerRef = useRef<HTMLDivElement>(null);
97102
const explanationRef = useRef<HTMLDivElement>(null);
98-
const tableContainerRef = useRef<HTMLDivElement>(null);
103+
const generalStatusRef = useRef<HTMLDivElement>(null);
99104
const [tableHeight, setTableHeight] = useState<number>(400);
100105

101106
useEffect(() => {
102107
const calculateTableHeight = () => {
103-
// Find the parent container with class "smc-vfill"
104-
let parentContainer = headerRef.current?.closest(
105-
".smc-vfill",
106-
) as HTMLElement;
107-
if (!parentContainer) return;
108+
const parentHeight = contentSize.height;
109+
if (parentHeight === 0) return; // Wait until contentSize is measured
108110

109-
const parentHeight = parentContainer.getBoundingClientRect().height;
110111
let usedHeight = 0;
111112

112113
// Add height of ProjectProblems component
113-
if (problemsRef.current) {
114-
usedHeight += problemsRef.current.offsetHeight;
115-
}
114+
usedHeight += problemsRef.current?.offsetHeight ?? 0;
116115

117116
// Add height of CGroup component
118-
if (cgroupRef.current) {
119-
usedHeight += cgroupRef.current.offsetHeight;
120-
}
117+
usedHeight += cgroupRef.current?.offsetHeight ?? 0;
121118

122119
// Add height of header row
123-
if (headerRef.current) {
124-
usedHeight += headerRef.current.offsetHeight;
125-
}
120+
usedHeight += headerRef.current?.offsetHeight ?? 0;
126121

127122
// Add height of explanation row if visible
128-
if (explanationRef.current) {
129-
usedHeight += explanationRef.current.offsetHeight;
123+
usedHeight += explanationRef.current?.offsetHeight ?? 0;
124+
125+
// Add height of general status row if DEBUG is enabled
126+
if (DEBUG) {
127+
usedHeight += generalStatusRef.current?.offsetHeight ?? 0;
130128
}
131129

132130
// Add more buffer for table header, margins, and other spacing
133-
usedHeight += 120;
131+
usedHeight += 100;
134132

135133
const availableHeight = Math.max(300, parentHeight - usedHeight);
136134
setTableHeight(availableHeight);
@@ -141,7 +139,7 @@ export function Full(props: Readonly<Props>): React.JSX.Element {
141139
// Recalculate on window resize
142140
window.addEventListener("resize", calculateTableHeight);
143141
return () => window.removeEventListener("resize", calculateTableHeight);
144-
}, [show_explanation, ptree]);
142+
}, [show_explanation, ptree, contentSize.height, contentSize.width]);
145143

146144
function render_help() {
147145
return (
@@ -376,6 +374,7 @@ export function Full(props: Readonly<Props>): React.JSX.Element {
376374
<Row ref={explanationRef}>{render_explanation()}</Row>
377375
<Row>
378376
<Table<ProcessRow>
377+
key={`table-${contentSize.width}-${contentSize.height}`}
379378
dataSource={ptree}
380379
size={"small"}
381380
pagination={false}
@@ -498,7 +497,7 @@ export function Full(props: Readonly<Props>): React.JSX.Element {
498497
</div>
499498
);
500499
return (
501-
<Col lg={8} lgOffset={2} md={12} mdOffset={0}>
500+
<Col md={12} mdOffset={0}>
502501
<Alert
503502
message={msg}
504503
style={{ margin: "10px 0" }}
@@ -516,14 +515,16 @@ export function Full(props: Readonly<Props>): React.JSX.Element {
516515

517516
function render_general_status() {
518517
return (
519-
<Col md={12} style={{ color: COLORS.GRAY }}>
520-
Timestamp:{" "}
521-
{info?.timestamp != null ? (
522-
<code>{new Date(info.timestamp).toISOString()}</code>
523-
) : (
524-
"no timestamp"
525-
)}{" "}
526-
| Status: <code>{status}</code>
518+
<Col md={12}>
519+
<div ref={generalStatusRef} style={{ color: COLORS.GRAY }}>
520+
Timestamp:{" "}
521+
{info?.timestamp != null ? (
522+
<code>{new Date(info.timestamp).toISOString()}</code>
523+
) : (
524+
"no timestamp"
525+
)}{" "}
526+
| Status: <code>{status}</code>
527+
</div>
527528
</Col>
528529
);
529530
}

src/packages/frontend/project/info/project-info.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6+
// cSpell:ignore chldsum
7+
68
import { Alert } from "antd";
79

810
import { cgroup_stats } from "@cocalc/comm/project-status/utils";
@@ -132,7 +134,7 @@ export const ProjectInfo: React.FC<Props> = React.memo(
132134
set_have_children(pchildren);
133135
break;
134136
case "flyout":
135-
// flyout does not nest children, not enogh space
137+
// flyout does not nest children, not enough space
136138
set_ptree(linearList(info.processes));
137139
break;
138140
default:

src/packages/frontend/project/page/content.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ or Loading... if the file is still being loaded.
1515
*/
1616

1717
import { Map } from "immutable";
18-
import { useEffect, useMemo, useRef } from "react";
18+
import { debounce } from "lodash";
19+
import { useCallback, useEffect, useMemo, useRef } from "react";
1920
import Draggable from "react-draggable";
21+
2022
import { React, redux, useTypedRedux } from "@cocalc/frontend/app-framework";
2123
import { KioskModeBanner } from "@cocalc/frontend/app/kiosk-mode-banner";
2224
import type { ChatState } from "@cocalc/frontend/chat/chat-indicator";
2325
import SideChat from "@cocalc/frontend/chat/side-chat";
2426
import { Loading } from "@cocalc/frontend/components";
2527
import KaTeX from "@cocalc/frontend/components/math/katex";
28+
import getMermaid from "@cocalc/frontend/editors/slate/elements/code-block/get-mermaid";
2629
import { IS_MOBILE, IS_TOUCH } from "@cocalc/frontend/feature";
2730
import { FileContext } from "@cocalc/frontend/lib/file-context";
2831
import {
@@ -38,15 +41,14 @@ import { ProjectSearch } from "@cocalc/frontend/project/search/search";
3841
import { ProjectServers } from "@cocalc/frontend/project/servers";
3942
import { ProjectSettings } from "@cocalc/frontend/project/settings";
4043
import { editor_id } from "@cocalc/frontend/project/utils";
44+
import { webapp_client } from "@cocalc/frontend/webapp-client";
4145
import { hidden_meta_file } from "@cocalc/util/misc";
4246
import { useProjectContext } from "../context";
4347
import getAnchorTagComponent from "./anchor-tag-component";
4448
import HomePage from "./home-page";
4549
import { ProjectCollaboratorsPage } from "./project-collaborators";
4650
import { ProjectLicenses } from "./project-licenses";
4751
import getUrlTransform from "./url-transform";
48-
import { webapp_client } from "@cocalc/frontend/webapp-client";
49-
import getMermaid from "@cocalc/frontend/editors/slate/elements/code-block/get-mermaid";
5052

5153
// Default width of chat window as a fraction of the
5254
// entire window.
@@ -66,12 +68,38 @@ interface Props {
6668

6769
export const Content: React.FC<Props> = (props: Props) => {
6870
const { tab_name, is_visible } = props;
71+
const { setContentSize } = useProjectContext();
72+
const contentRef = useRef<HTMLDivElement>(null);
73+
74+
const debouncedMeasure = useCallback(
75+
debounce((entries: ResizeObserverEntry[]) => {
76+
if (entries.length > 0) {
77+
const { width, height } = entries[0].contentRect;
78+
setContentSize({ width, height });
79+
}
80+
}, 10),
81+
[setContentSize],
82+
);
83+
84+
useEffect(() => {
85+
if (!contentRef.current) return;
86+
87+
const resizeObserver = new ResizeObserver(debouncedMeasure);
88+
resizeObserver.observe(contentRef.current);
89+
90+
return () => {
91+
resizeObserver.disconnect();
92+
debouncedMeasure.cancel();
93+
};
94+
}, [debouncedMeasure]);
95+
6996
// The className below is so we always make this div the remaining height.
7097
// The overflowY is hidden for editors (which don't scroll), but auto
7198
// otherwise, since some tabs (e.g., settings) *do* need to scroll. See
7299
// https://github.com/sagemathinc/cocalc/pull/4708.
73100
return (
74101
<div
102+
ref={contentRef}
75103
style={{
76104
...MAIN_STYLE,
77105
...(!is_visible ? { display: "none" } : undefined),

0 commit comments

Comments
 (0)