Skip to content

Commit 456f17f

Browse files
committed
Migrate editing, polling and resizing functionality
1 parent 341544c commit 456f17f

23 files changed

+909
-222
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
{
77
"editor.formatOnSave": true,
88
"editor.codeActionsOnSave": {
9-
"source.fixAll.eslint": "explicit"
9+
"source.fixAll.eslint": "explicit",
10+
"source.organizeImports": "explicit"
1011
},
1112
"[javascript]": {
1213
"editor.defaultFormatter": "esbenp.prettier-vscode"

eslint.config.mjs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
* terms of the MIT License as outlined in the LICENSE file.
66
**********************************************************************************/
77

8-
import globals from 'globals';
98
import eslint from '@eslint/js';
10-
import tseslint from 'typescript-eslint';
119
import header from 'eslint-plugin-header';
10+
import globals from 'globals';
11+
import tseslint from 'typescript-eslint';
1212

1313
header.rules.header.meta.schema = false;
1414

@@ -32,13 +32,7 @@ export default tseslint.config(
3232
// ESLint Convention
3333
quotes: ['error', 'single'],
3434
semi: ['error', 'always'],
35-
indent: [
36-
'error',
37-
4,
38-
{
39-
SwitchCase: 1
40-
}
41-
],
35+
4236
'block-spacing': ['error', 'always'],
4337
'brace-style': [
4438
'error',

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@vscode/webview-ui-toolkit": "^1.4.0",
2525
"antd": "^5.22.1",
2626
"primeflex": "^3.3.1",
27+
"re-resizable": "^6.11.2",
2728
"react-markdown": "^9.0.1",
2829
"remark-gfm": "^4.0.0",
2930
"throttle-debounce": "5.0.2",

src/base/style-utils.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
/**********************************************************************************
2-
* Copyright (c) 2025 Company and others.
2+
* Copyright (c) 2025 EclipseSource and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the MIT License as outlined in the LICENSE file.
66
**********************************************************************************/
77

8-
export function classNames(...classes: (string | Record<string, boolean>)[]): string {
8+
export function classNames(...classes: (string | Record<string, boolean> | undefined)[]): string {
99
return classes
10-
.filter(c => c !== undefined)
11-
.map(c => {
12-
if (typeof c === 'string') {
13-
return c;
10+
.map(className => {
11+
if (!className) {
12+
return '';
1413
}
15-
16-
return Object.entries(c)
14+
if (typeof className === 'string') {
15+
return className;
16+
}
17+
return Object.entries(className)
1718
.filter(([, value]) => value)
1819
.map(([key]) => key);
1920
})
21+
.filter(className => className.length > 0)
2022
.join(' ');
2123
}

src/label/label-helpers.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**********************************************************************************
2-
* Copyright (c) 2025 Company and others.
2+
* Copyright (c) 2025 EclipseSource and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the MIT License as outlined in the LICENSE file.
@@ -8,7 +8,7 @@
88
import React from 'react';
99
import Markdown from 'react-markdown';
1010
import remarkGfm from 'remark-gfm';
11-
import { Tooltip, TooltipTrigger, TooltipContent } from '../tooltip/tooltip';
11+
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip/tooltip';
1212

1313
export function createHighlightedText(label?: string, highlights?: [number, number][]): React.JSX.Element {
1414
if (label === undefined) {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**********************************************************************************
2+
* Copyright (c) 2025 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the MIT License as outlined in the LICENSE file.
6+
**********************************************************************************/
7+
8+
import React from 'react';
9+
import { CommandDefinition } from '../../../../vscode/webview-types';
10+
import { CDTTreeItem, CDTTreeItemResource, CDTTreeTableActionColumn, CDTTreeTableActionColumnCommand } from '../../../common';
11+
12+
export interface ActionCellProps<T extends CDTTreeItemResource> {
13+
column: CDTTreeTableActionColumn;
14+
record: CDTTreeItem<T>;
15+
actions: CDTTreeTableActionColumnCommand[];
16+
onAction?: (event: React.UIEvent, command: CommandDefinition, value: unknown, record: CDTTreeItem<T>) => void;
17+
}
18+
19+
const ActionCell = <T extends CDTTreeItemResource>({ record, actions, onAction }: ActionCellProps<T>) => {
20+
return (
21+
<div className='tree-actions'>
22+
{actions.map(action => {
23+
const handleAction = (e: React.MouseEvent | React.KeyboardEvent) => {
24+
e.stopPropagation();
25+
e.preventDefault();
26+
onAction?.(e, action, action.value, record);
27+
};
28+
return (
29+
<i
30+
key={action.commandId}
31+
title={action.title}
32+
className={`codicon codicon-${action.icon}`}
33+
onClick={handleAction}
34+
role='button'
35+
tabIndex={0}
36+
onKeyDown={e => e.key === 'Enter' && handleAction(e)}
37+
/>
38+
);
39+
})}
40+
</div>
41+
);
42+
};
43+
44+
export default ActionCell;
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/**********************************************************************************
2+
* Copyright (c) 2025 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the MIT License as outlined in the LICENSE file.
6+
**********************************************************************************/
7+
8+
import '../../../../../style/tree/editable-string-cell.css';
9+
10+
import { Checkbox, Input, Select } from 'antd';
11+
import type { CheckboxChangeEvent } from 'antd/lib/checkbox';
12+
import React, { useCallback, useEffect, useRef } from 'react';
13+
import { CDTTreeItem, CDTTreeItemResource, EditableCDTTreeTableStringColumn } from '../../../common';
14+
import LabelCell from './LabelCell';
15+
16+
interface EditableLabelCellProps<T extends CDTTreeItemResource> {
17+
column: EditableCDTTreeTableStringColumn;
18+
record: CDTTreeItem<T>;
19+
editing: boolean;
20+
autoFocus: boolean;
21+
onSubmit: (newValue: string) => void;
22+
onCancel: () => void;
23+
onEdit?: (edit: boolean) => void;
24+
}
25+
26+
const EditableLabelCell = <T extends CDTTreeItemResource>({
27+
column,
28+
record,
29+
editing,
30+
autoFocus,
31+
onSubmit,
32+
onCancel,
33+
onEdit
34+
}: EditableLabelCellProps<T>) => {
35+
const containerRef = useRef<HTMLDivElement>(null);
36+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
37+
const editorRef = useRef<any>(null);
38+
39+
const commitEdit = useCallback(
40+
(newValue: string, event?: { stopPropagation: () => void; preventDefault: () => void }) => {
41+
event?.stopPropagation();
42+
event?.preventDefault();
43+
onSubmit(newValue);
44+
onEdit?.(false);
45+
},
46+
[onSubmit]
47+
);
48+
49+
const cancelEdit = useCallback(() => {
50+
onCancel();
51+
onEdit?.(false);
52+
}, [column.edit.value, onCancel, onEdit]);
53+
54+
// Cancel the edit only if focus leaves the entire container.
55+
const handleBlur = useCallback(() => {
56+
setTimeout(() => {
57+
if (containerRef.current && document.activeElement && !containerRef.current.contains(document.activeElement)) {
58+
cancelEdit();
59+
}
60+
}, 0);
61+
}, [cancelEdit]);
62+
63+
const handleKeyDown = useCallback(
64+
(e: React.KeyboardEvent) => {
65+
if (e.key === 'Escape') {
66+
cancelEdit();
67+
}
68+
e.stopPropagation();
69+
},
70+
[cancelEdit]
71+
);
72+
73+
// Consume the double-click event so no other handler is triggered.
74+
const handleDoubleClick = useCallback(
75+
(e: React.MouseEvent) => {
76+
e.preventDefault();
77+
e.stopPropagation();
78+
onEdit?.(true);
79+
},
80+
[column, onEdit]
81+
);
82+
83+
// Focus the editor when entering edit mode.
84+
useEffect(() => {
85+
if (editing && editorRef.current && autoFocus) {
86+
editorRef.current.focus();
87+
}
88+
}, [editing, autoFocus]);
89+
90+
if (editing) {
91+
return (
92+
<div className='edit-field-container' ref={containerRef}>
93+
{(() => {
94+
switch (column.edit.type) {
95+
case 'text':
96+
return (
97+
<Input
98+
ref={editorRef}
99+
className={'text-field-cell'}
100+
defaultValue={column.edit.value}
101+
onPressEnter={e => commitEdit(e.currentTarget.value, e)}
102+
onBlur={handleBlur}
103+
onClick={e => e.stopPropagation()}
104+
onKeyDown={handleKeyDown}
105+
/>
106+
);
107+
case 'boolean': {
108+
const checked = column.edit.value === '1';
109+
return (
110+
<Checkbox
111+
ref={editorRef}
112+
checked={checked}
113+
onChange={(e: CheckboxChangeEvent) => commitEdit(e.target.checked ? '1' : '0', e)}
114+
onBlur={handleBlur}
115+
onClick={e => e.stopPropagation()}
116+
onKeyDown={handleKeyDown}
117+
/>
118+
);
119+
}
120+
case 'enum': {
121+
return (
122+
<Select
123+
ref={editorRef}
124+
className={'enum-field-cell'}
125+
placeholder={column.label}
126+
value={column.label} // we want to use 'Write Only' as value even if it is not an option
127+
onChange={newValue => commitEdit(newValue)}
128+
onBlur={handleBlur}
129+
onClick={e => e.stopPropagation()}
130+
onKeyDown={handleKeyDown}
131+
>
132+
{column.edit.options.map(opt => {
133+
return (
134+
<Select.Option key={opt.value} value={opt.value}>
135+
{opt.label}
136+
</Select.Option>
137+
);
138+
})}
139+
</Select>
140+
);
141+
}
142+
default:
143+
return null;
144+
}
145+
})()}
146+
</div>
147+
);
148+
}
149+
150+
return (
151+
<div className='editable-string-cell' onDoubleClick={handleDoubleClick}>
152+
<LabelCell record={record} column={column} />
153+
</div>
154+
);
155+
};
156+
157+
export default React.memo(EditableLabelCell);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**********************************************************************************
2+
* Copyright (c) 2025 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the MIT License as outlined in the LICENSE file.
6+
**********************************************************************************/
7+
8+
import classNames from 'classnames';
9+
import React from 'react';
10+
import { createHighlightedText, createLabelWithTooltip } from '../../../../label/label-helpers';
11+
import { CDTTreeItem, CDTTreeItemResource, CDTTreeTableStringColumn } from '../../../common';
12+
13+
export interface LabelCellProps<T extends CDTTreeItemResource> {
14+
column: CDTTreeTableStringColumn;
15+
record: CDTTreeItem<T>;
16+
}
17+
18+
const LabelCell = <T extends CDTTreeItemResource>({ column }: LabelCellProps<T>) => {
19+
const icon = column.icon && <i className={classNames('cell-icon', column.icon)} />;
20+
21+
const content = column.tooltip
22+
? createLabelWithTooltip(<span>{createHighlightedText(column.label, column.highlight)}</span>, column.tooltip)
23+
: createHighlightedText(column.label, column.highlight);
24+
25+
return (
26+
<div className='tree-cell ant-table-cell-ellipsis' tabIndex={0}>
27+
{icon}
28+
{content}
29+
</div>
30+
);
31+
};
32+
33+
export default React.memo(LabelCell);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**********************************************************************************
2+
* Copyright (c) 2025 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the MIT License as outlined in the LICENSE file.
6+
**********************************************************************************/
7+
8+
import React, { useCallback } from 'react';
9+
import { CDTTreeItem, CDTTreeItemResource, CDTTreeTableStringColumn, EditableCDTTreeTableStringColumn } from '../../../common';
10+
import EditableStringCell from './EditableStringCell';
11+
import LabelCell from './LabelCell';
12+
13+
interface StringCellProps<T extends CDTTreeItemResource> {
14+
column: CDTTreeTableStringColumn;
15+
record: CDTTreeItem<T>;
16+
editing?: boolean;
17+
autoFocus?: boolean;
18+
onSubmit?: (record: CDTTreeItem<T>, newValue: string) => void;
19+
onCancel?: (record: CDTTreeItem<T>) => void;
20+
onEdit?: (record: CDTTreeItem<T>, edit: boolean) => void;
21+
}
22+
23+
const StringCell = <T extends CDTTreeItemResource>({
24+
column,
25+
record,
26+
editing = false,
27+
autoFocus = false,
28+
onSubmit,
29+
onCancel,
30+
onEdit
31+
}: StringCellProps<T>) => {
32+
const handleSubmit = useCallback((newValue: string) => onSubmit?.(record, newValue), [record, onSubmit]);
33+
34+
const handleCancel = useCallback(() => onCancel?.(record), [record, onCancel]);
35+
36+
const handleEdit = useCallback((edit: boolean) => onEdit?.(record, edit), [record, onEdit]);
37+
38+
return column.edit && onSubmit ? (
39+
<EditableStringCell
40+
record={record}
41+
column={column as EditableCDTTreeTableStringColumn}
42+
onSubmit={handleSubmit}
43+
onCancel={handleCancel}
44+
onEdit={handleEdit}
45+
editing={editing}
46+
autoFocus={autoFocus}
47+
/>
48+
) : (
49+
<LabelCell record={record} column={column} />
50+
);
51+
};
52+
53+
export default StringCell;

0 commit comments

Comments
 (0)