Skip to content

Commit

Permalink
Add more edit controls for specific types
Browse files Browse the repository at this point in the history
- Enumeration Dropdown for fields with enumeration values
- Boolean Checkbox edit for non-enums of width 1

Closes eclipse-cdt-cloud#22
  • Loading branch information
martin-fleck-at committed Jul 1, 2024
1 parent 53bb8fe commit f6ba344
Show file tree
Hide file tree
Showing 15 changed files with 351 additions and 52 deletions.
59 changes: 59 additions & 0 deletions src/components/tree/components/BooleanCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/********************************************************************************
* Copyright (C) 2024 Arm Limited and others.
*
* This program and the accompanying materials are made available under the
* terms of the MIT License as outlined in the LICENSE File
********************************************************************************/

import { ReactWrapperProps } from '@microsoft/fast-react-wrapper';
import { Checkbox } from '@vscode/webview-ui-toolkit';
import { VSCodeCheckbox } from '@vscode/webview-ui-toolkit/react';
import React from 'react';
import { CDTTreeItem, CDTTreeTableColumn, EditableBooleanData } from '../types';
import { AsEditable, AsTreeTableCell, EditableComponentProps, EditableComponentRef } from './TreeTableCell';
import './boolean-cell.css';

export type VSCodeCheckboxComponent = React.Component<ReactWrapperProps<Checkbox, { onChange: unknown; onInput: unknown; }>, unknown, unknown> & Checkbox;

export interface BooleanCellProps extends EditableComponentProps {
row: CDTTreeItem;
cell: CDTTreeTableColumn;
data: EditableBooleanData;
}

const BooleanCellComponent = React.forwardRef<EditableComponentRef, BooleanCellProps>(({ row, data, ...props }, ref) => {
const checkboxRef = React.useRef<VSCodeCheckboxComponent>(null);

React.useImperativeHandle(ref, () => ({
focus: () => checkboxRef.current?.focus()
}));

const onChange = () => {
const value = checkboxRef.current?.checked ? '1' : '0';
props.onSubmitValue(value);
};

const onKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Escape') {
event.stopPropagation();
props.onCancelEdit();
}
};

const onBlur = () => {
props.onCancelEdit();
};

return <VSCodeCheckbox
ref={checkboxRef}
className='boolean-cell'
id={`${row.id}-boolean-field`}
checked={data.value === '1'}
onChange={onChange}
onKeyDown={onKeyDown}
onClick={event => event.stopPropagation()}
onBlur={onBlur}
/>;
});

export const BooleanCell = AsEditable(AsTreeTableCell(BooleanCellComponent));
75 changes: 75 additions & 0 deletions src/components/tree/components/EnumCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/********************************************************************************
* Copyright (C) 2024 Arm Limited and others.
*
* This program and the accompanying materials are made available under the
* terms of the MIT License as outlined in the LICENSE File
********************************************************************************/

import { Dropdown, DropdownChangeEvent, DropdownProps } from 'primereact/dropdown';
import { SelectItem } from 'primereact/selectitem';
import React from 'react';
import { CDTTreeItem, CDTTreeTableColumn, EditableEnumData } from '../types';
import { EditableComponentProps, EditableComponentRef, AsEditable, AsTreeTableCell } from './TreeTableCell';
import './enum-cell.css';
import { ChevronDownIcon } from 'primereact/icons/chevrondown';

export interface EnumCellProps extends EditableComponentProps {
row: CDTTreeItem;
cell: CDTTreeTableColumn;
data: EditableEnumData;
}

export type FooterProps = DropdownProps & { focusedOptionIndex?: number };

const EnumCellComponent = React.forwardRef<EditableComponentRef, EnumCellProps>(({ row, data, ...props }, ref) => {
const [options] = React.useState<SelectItem[]>(data.options.map(option => ({ label: option.value, title: option.detail })));
const dropdownRef = React.useRef<Dropdown>(null);

React.useImperativeHandle(ref, () => ({
focus: () => dropdownRef.current?.focus()
}));

const onChange = (event: DropdownChangeEvent) => {
const item = event.value as SelectItem;
if (item.label) {
props.onSubmitValue(item.label);
}
};

const onKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Escape') {
event.stopPropagation();
props.onCancelEdit();
}
};

const detailPanel = (params: DropdownProps) => {
const props = params as FooterProps;
const detail = options[props?.focusedOptionIndex ?? - 1]?.title;
return detail;
};

const onBlur = () => {
if (!dropdownRef.current?.getOverlay()?.contains(document.activeElement) && !dropdownRef.current?.getElement()?.contains(document.activeElement)) {
// focus lost and overlay/popup is not visible
// props.onCancelEdit();
}
};

return <Dropdown
ref={dropdownRef}
options={options}
className='enum-cell vscode-dropdown'
id={`${row.id}-enum-field`}
value={options.find(option => option.label === data.value)}
onChange={onChange}
onKeyDown={onKeyDown}
onClick={event => event.stopPropagation()}
onBlur={onBlur}
onHide={props.onCancelEdit}
panelFooterTemplate={detailPanel}
dropdownIcon={(opts) => { return <ChevronDownIcon {...opts.iconProps} ref={null} scale={0.7} />; }}
/>;
});

export const EnumCell = AsEditable(AsTreeTableCell(EnumCellComponent));
2 changes: 1 addition & 1 deletion src/components/tree/components/TextFieldCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const TextFieldComponent = React.forwardRef<EditableComponentRef, TextFielCellPr
id={`${row.id}-text-field`}
initialValue={cell.value}
value={cell.value}
onKeyDown={event => onKeyDown(event)}
onKeyDown={onKeyDown}
onClick={event => event.stopPropagation()}
onBlur={props.onCancelEdit}
/>;
Expand Down
1 change: 1 addition & 0 deletions src/components/tree/components/TreeTableCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const AsEditable = <P extends EditableComponentProps>(EditComponent: Comp

const onStartEdit = (event: React.MouseEvent) => {
setEditMode(true);
event.preventDefault();
event.stopPropagation();
};

Expand Down
3 changes: 3 additions & 0 deletions src/components/tree/components/boolean-cell.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
vscode-checkbox {
margin: 0px;
}
112 changes: 112 additions & 0 deletions src/components/tree/components/enum-cell.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/* Base Dropdown Styling */
.p-dropdown {
background-color: var(--vscode-settings-dropdownBackground);
color: var(--vscode-settings-dropdownForeground);
border: 1px solid var(--vscode-settings-dropdownBorder);
border-radius: var(--corner-radius);
font-family: var(--vscode-font-family);
width: 100%;
height: 22px;
box-sizing: border-box;
font-size: var(--vscode-font-size);
display: flex;
align-items: center;
}

.p-dropdown-label {
padding: 2px 6px;
font-size: var(--vscode-font-size);
}

/* Dropdown Panel Styling */
.p-dropdown-panel {
background-color: var(--vscode-settings-dropdownBackground);
border: 1px solid var(--vscode-settings-focusedRowBorder);
border-radius: 0;
width: min-content;
font-size: var(--vscode-font-size);
line-height: 1.4em;
}

/* Dropdown Items Styling */
.p-dropdown-item {
padding: 0 6px;
display: flex;
align-items: center;
color: var(--vscode-settings-dropdownForeground);
background-color: var(--vscode-settings-dropdownBackground);
height: 22px;
}

.p-dropdown-item:hover {
background-color: var(--vscode-list-activeSelectionBackground);
}

.p-dropdown-item.p-highlight {
background-color: var(--vscode-settings-focusedRowBackground) !important;
}

.p-dropdown-item.p-highlight:hover {
background-color: var(--vscode-list-activeSelectionBackground) !important;
}

/* Disabled Dropdown Item Styling */
.p-dropdown-item.p-disabled {
color: var(--vscode-settings-dropdownForeground);
background-color: var(--vscode-settings-dropdownBackground);
}

/* Adjust Arrow Icon */
.p-dropdown-trigger {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 0 6px;
width: auto;
color: var(--vscode-settings-dropdownForeground);
}

.p-dropdown-trigger:hover {
color: #ffffff;
}

.p-dropdown .p-dropdown-clearable .p-dropdown-trigger {
padding: 0 6px;
}

.p-dropdown-trigger .p-icon.p-dropdown-trigger-icon {
transform: scale(0.7, 0.7);
}

/* Dropdown Footer Styling */
.p-dropdown-footer {
padding: 4px 6px;
background-color: var(--vscode-settings-dropdownBackground);
color: var(--vscode-settings-dropdownForeground);
border-top: 1px solid var(--vscode-settings-dropdownBorder);
}

.p-dropdown-footer:first-letter {
text-transform: capitalize;
}

/* Ensure .p-dropdown-items has no padding */
.p-dropdown-items {
padding: 0;
}

/* Focus State */
.p-focus .p-dropdown,
.p-inputwrapper-focus,
.p-dropdown:focus {
border-color: var(--vscode-settings-focusedRowBorder);
box-shadow: 0 0 0 1px var(--vscode-settings-focusedRowBorder);
}

.p-focus .p-dropdown-panel,
.p-dropdown-panel:focus {
border-color: var(--vscode-settings-focusedRowBorder);
box-shadow: 0 0 0 1px var(--vscode-settings-focusedRowBorder);
}

12 changes: 10 additions & 2 deletions src/components/tree/components/treetable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { CDTTreeItem, CDTTreeTableColumnDefinition, CTDTreeMessengerType } from
import { LabelCell } from './LabelCell';
import { TextFieldCell } from './TextFieldCell';
import { createActions } from './utils';
import { EnumCell } from './EnumCell';
import { BooleanCell } from './BooleanCell';

export type ComponentTreeTableProps = {
nodes?: CDTTreeItem[];
Expand Down Expand Up @@ -62,9 +64,15 @@ export const ComponentTreeTable = (props: ComponentTreeTableProps) => {
}

if (column.edit?.type === 'text') {
return <TextFieldCell key={node.id} row={node} cell={column} expander={expander} field={field} />;
return <TextFieldCell key={node.id + '-' + field} row={node} cell={column} expander={expander} field={field} />;
}
return <LabelCell key={node.id} row={node} cell={column} expander={expander} />;
if (column.edit?.type === 'enum') {
return <EnumCell key={node.id + '-' + field} row={node} cell={column} expander={expander} field={field} data={column.edit} />;
}
if (column.edit?.type === 'boolean') {
return <BooleanCell key={node.id + '-' + field} row={node} cell={column} expander={expander} field={field} data={column.edit} />;
}
return <LabelCell key={node.id + '-' + field} row={node} cell={column} expander={expander} />;
};

const togglerTemplate = () => {
Expand Down
20 changes: 19 additions & 1 deletion src/components/tree/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,30 @@ export interface EditableTextData extends EditableCellData {
type: 'text';
}

export interface EditableEnumData extends EditableCellData {
type: 'enum';
options: EditableEnumDataOption[];
value: string;
}

export interface EditableBooleanData extends EditableCellData {
type: 'boolean';
value: '0' | '1';
}

export type EditableData = NoEditableData | EditableTextData | EditableEnumData | EditableBooleanData;

export interface EditableEnumDataOption {
value: string;
detail?: string;
}

export interface CDTTreeTableColumn {
value: string;
highlight?: [number, number][];
tooltip?: string;
icon?: string;
edit?: EditableTextData | NoEditableData;
edit?: EditableData;
}

export interface CDTTreeItem extends PrimeTreeNode {
Expand Down
20 changes: 18 additions & 2 deletions src/plugin/peripheral/nodes/basenode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,23 @@ import { Command, DebugSession, TreeItem } from 'vscode';
import { AddrRange } from '../../../addrranges';
import { EnumerationMap } from '../../../api-types';
import { CommandDefinition, MaybePromise, NodeSetting, NumberFormat } from '../../../common';
import { CDTTreeItem } from '../../../components/tree/types';
import { CDTTreeItem, CDTTreeTableColumn } from '../../../components/tree/types';

export interface PeripheralTreeItem extends CDTTreeItem {
columns?: {
'title': CDTTreeTableColumn,
'value': CDTTreeTableColumn
};
}

export namespace PeripheralTreeItem {
export function create(options: Omit<PeripheralTreeItem, '__type'>): PeripheralTreeItem {
return {
__type: 'CDTTreeItem',
...options
};
}
}

export abstract class BaseNode {
public expanded: boolean;
Expand All @@ -24,7 +40,7 @@ export abstract class BaseNode {

public abstract getChildren(): BaseNode[] | Promise<BaseNode[]>;
public abstract getTreeItem(): TreeItem | Promise<TreeItem>;
public abstract getCDTTreeItem(): MaybePromise<CDTTreeItem>;
public abstract getCDTTreeItem(): MaybePromise<PeripheralTreeItem>;


public getCommand(): Command | undefined {
Expand Down
7 changes: 3 additions & 4 deletions src/plugin/peripheral/nodes/messagenode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
********************************************************************************/

import * as vscode from 'vscode';
import { PERIPHERAL_ID_SEP, PeripheralBaseNode } from './basenode';
import { PERIPHERAL_ID_SEP, PeripheralBaseNode, PeripheralTreeItem } from './basenode';
import { AddrRange } from '../../../addrranges';
import { NodeSetting } from '../../../common';
import { CDTTreeItem } from '../../../components/tree/types';

export class MessageNode extends PeripheralBaseNode {

Expand All @@ -33,8 +32,8 @@ export class MessageNode extends PeripheralBaseNode {
return ti;
}

public getCDTTreeItem(): CDTTreeItem {
return CDTTreeItem.create({
public getCDTTreeItem(): PeripheralTreeItem {
return PeripheralTreeItem.create({
id: this.getId(),
key: this.getId(),
path: this.getId().split(PERIPHERAL_ID_SEP),
Expand Down
Loading

0 comments on commit f6ba344

Please sign in to comment.