diff --git a/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx b/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx index d18d37cd5..2269ba90b 100644 --- a/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx +++ b/extensions/ql-vscode/src/stories/model-editor/MethodRow.stories.tsx @@ -5,7 +5,6 @@ import { Meta, StoryFn } from "@storybook/react"; import { MethodRow as MethodRowComponent } from "../../view/model-editor/MethodRow"; import { CallClassification, Method } from "../../model-editor/method"; import { ModeledMethod } from "../../model-editor/modeled-method"; -import { VSCodeDataGrid } from "@vscode/webview-ui-toolkit/react"; import { MULTIPLE_MODELS_GRID_TEMPLATE_COLUMNS, SINGLE_MODEL_GRID_TEMPLATE_COLUMNS, @@ -13,6 +12,7 @@ import { import { ModelEditorViewState } from "../../model-editor/shared/view-state"; import { createMockExtensionPack } from "../../../test/factories/model-editor/extension-pack"; import { Mode } from "../../model-editor/shared/mode"; +import { DataGrid } from "../../view/common/DataGrid"; export default { title: "CodeQL Model Editor/Method Row", @@ -24,9 +24,9 @@ const Template: StoryFn = (args) => { ? MULTIPLE_MODELS_GRID_TEMPLATE_COLUMNS : SINGLE_MODEL_GRID_TEMPLATE_COLUMNS; return ( - + - + ); }; diff --git a/extensions/ql-vscode/src/view/common/DataGrid.tsx b/extensions/ql-vscode/src/view/common/DataGrid.tsx new file mode 100644 index 000000000..f8cfdf31c --- /dev/null +++ b/extensions/ql-vscode/src/view/common/DataGrid.tsx @@ -0,0 +1,79 @@ +import * as React from "react"; +import { styled } from "styled-components"; + +/* + * A drop-in replacement for the VSCodeDataGrid family of components. + * + * The difference is that the `display: grid` styling is applied to `DataGrid`, whereas + * in the VS Code version that styling is applied to `VSCodeDataGridRow`. This gives + * column alignment across rows in situation with dynamic contents. It also allows + * for cells to span multiple rows and all the other features of data grids. + */ + +const StyledDataGrid = styled.div<{ $gridTemplateColumns: string | number }>` + display: grid; + grid-template-columns: ${(props) => props.$gridTemplateColumns}; + padding: calc((var(--design-unit) / 4) * 1px) 0; + box-sizing: border-box; + width: 100%; + background: transparent; +`; + +interface DataGridProps { + gridTemplateColumns: string | number; + children: React.ReactNode; +} + +export function DataGrid(props: DataGridProps) { + const { gridTemplateColumns, children } = props; + + return ( + + {children} + + ); +} + +export const DataGridRow = styled.div<{ $focused?: boolean }>` + display: contents; + + &:hover > * { + background-color: var(--list-hover-background); + } + + & > * { + // Use !important to override the background color set by the hover state + background-color: ${(props) => + props.$focused + ? "var(--vscode-editor-selectionBackground) !important" + : "inherit"}; + } +`; + +const StyledDataGridCell = styled.div<{ + $gridRow: string | number; + $gridColumn: string | number; +}>` + grid-row: ${(props) => props.$gridRow}; + grid-column: ${(props) => props.$gridColumn}; + padding: calc(var(--design-unit) * 1px) calc(var(--design-unit) * 3px); +`; + +interface DataGridCellProps { + gridRow: string | number; + gridColumn: string | number; + children: React.ReactNode; +} + +export function DataGridCell(props: DataGridCellProps) { + const { gridRow, gridColumn, children } = props; + + return ( + + {children} + + ); +} diff --git a/extensions/ql-vscode/src/view/model-editor/HiddenMethodsRow.tsx b/extensions/ql-vscode/src/view/model-editor/HiddenMethodsRow.tsx index 1be079473..5356c43c9 100644 --- a/extensions/ql-vscode/src/view/model-editor/HiddenMethodsRow.tsx +++ b/extensions/ql-vscode/src/view/model-editor/HiddenMethodsRow.tsx @@ -1,35 +1,41 @@ -import { - VSCodeDataGridCell, - VSCodeDataGridRow, -} from "@vscode/webview-ui-toolkit/react"; import * as React from "react"; import { styled } from "styled-components"; import { pluralize } from "../../common/word"; +import { DataGridCell, DataGridRow } from "../common/DataGrid"; +import { ModelEditorViewState } from "../../model-editor/shared/view-state"; -const HiddenMethodsCell = styled(VSCodeDataGridCell)` +const HiddenMethodsText = styled.div` text-align: center; `; interface Props { + gridRow: number; numHiddenMethods: number; someMethodsAreVisible: boolean; + viewState: ModelEditorViewState; } export function HiddenMethodsRow({ + gridRow, numHiddenMethods, someMethodsAreVisible, + viewState, }: Props) { if (numHiddenMethods === 0) { return null; } + const gridColumn = viewState.showMultipleModels ? "span 6" : "span 5"; + return ( - - - {someMethodsAreVisible && "And "} - {pluralize(numHiddenMethods, "method", "methods")} modeled in other - CodeQL packs - - + + + + {someMethodsAreVisible && "And "} + {pluralize(numHiddenMethods, "method", "methods")} modeled in other + CodeQL packs + + + ); } diff --git a/extensions/ql-vscode/src/view/model-editor/MethodName.tsx b/extensions/ql-vscode/src/view/model-editor/MethodName.tsx index ab9f00868..b46ef0653 100644 --- a/extensions/ql-vscode/src/view/model-editor/MethodName.tsx +++ b/extensions/ql-vscode/src/view/model-editor/MethodName.tsx @@ -4,6 +4,7 @@ import { Method } from "../../model-editor/method"; const Name = styled.span` font-family: var(--vscode-editor-font-family); + word-break: break-all; `; export const MethodName = (method: Method): JSX.Element => { diff --git a/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx b/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx index 49e5768af..49abc0969 100644 --- a/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx +++ b/extensions/ql-vscode/src/view/model-editor/MethodRow.tsx @@ -1,7 +1,5 @@ import { VSCodeButton, - VSCodeDataGridCell, - VSCodeDataGridRow, VSCodeLink, VSCodeProgressRing, } from "@vscode/webview-ui-toolkit/react"; @@ -25,8 +23,9 @@ import { ModelOutputDropdown } from "./ModelOutputDropdown"; import { ModelEditorViewState } from "../../model-editor/shared/view-state"; import { Codicon } from "../common"; import { canAddNewModeledMethod } from "../../model-editor/shared/multiple-modeled-methods"; +import { DataGridCell, DataGridRow } from "../common/DataGrid"; -const MultiModelColumn = styled(VSCodeDataGridCell)` +const MultiModelColumn = styled.div` display: flex; flex-direction: column; gap: 0.5em; @@ -63,12 +62,8 @@ const CodiconRow = styled(VSCodeButton)` align-items: center; `; -const DataGridRow = styled(VSCodeDataGridRow)<{ focused?: boolean }>` - outline: ${(props) => - props.focused ? "1px solid var(--vscode-focusBorder)" : "none"}; -`; - export type MethodRowProps = { + gridRow: number; method: Method; methodCanBeModeled: boolean; modeledMethods: ModeledMethod[]; @@ -103,6 +98,7 @@ export const MethodRow = (props: MethodRowProps) => { const ModelableMethodRow = forwardRef( (props, ref) => { const { + gridRow, method, modeledMethods: modeledMethodsProp, methodIsUnsaved, @@ -166,9 +162,9 @@ const ModelableMethodRow = forwardRef( - + @@ -181,97 +177,107 @@ const ModelableMethodRow = forwardRef( View {props.modelingInProgress && } - + {props.modelingInProgress && ( <> - + - - + + - - + + - - + + - + {viewState.showMultipleModels && ( - + - + )} )} {!props.modelingInProgress && ( <> - - {modeledMethods.map((modeledMethod, index) => ( - - ))} - - - {modeledMethods.map((modeledMethod, index) => ( - - ))} - - - {modeledMethods.map((modeledMethod, index) => ( - - ))} - - - {modeledMethods.map((modeledMethod, index) => ( - - ))} - - {viewState.showMultipleModels && ( - - {modeledMethods.map((_, index) => - index === modeledMethods.length - 1 ? ( - - - - ) : ( - - - - ), - )} + + + {modeledMethods.map((modeledMethod, index) => ( + + ))} + + + + {modeledMethods.map((modeledMethod, index) => ( + + ))} + + + + + {modeledMethods.map((modeledMethod, index) => ( + + ))} + + + + + {modeledMethods.map((modeledMethod, index) => ( + + ))} + + + {viewState.showMultipleModels && ( + + + {modeledMethods.map((_, index) => + index === modeledMethods.length - 1 ? ( + + + + ) : ( + + + + ), + )} + + )} )} @@ -285,7 +291,7 @@ const UnmodelableMethodRow = forwardRef< HTMLElement | undefined, MethodRowProps >((props, ref) => { - const { method, viewState, revealedMethodSignature } = props; + const { gridRow, method, viewState, revealedMethodSignature } = props; const jumpToMethod = useCallback( () => sendJumpToMethodMessage(method), @@ -296,9 +302,9 @@ const UnmodelableMethodRow = forwardRef< - + @@ -310,10 +316,10 @@ const UnmodelableMethodRow = forwardRef< View - - + + Method already modeled - + ); }); diff --git a/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx b/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx index 5b5117848..740babdf5 100644 --- a/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx +++ b/extensions/ql-vscode/src/view/model-editor/ModeledMethodDataGrid.tsx @@ -1,9 +1,4 @@ import * as React from "react"; -import { - VSCodeDataGrid, - VSCodeDataGridCell, - VSCodeDataGridRow, -} from "@vscode/webview-ui-toolkit/react"; import { MethodRow } from "./MethodRow"; import { Method, canMethodBeModeled } from "../../model-editor/method"; import { ModeledMethod } from "../../model-editor/modeled-method"; @@ -12,6 +7,7 @@ import { sortMethods } from "../../model-editor/shared/sorting"; import { HiddenMethodsRow } from "./HiddenMethodsRow"; import { ModelEditorViewState } from "../../model-editor/shared/view-state"; import { ScreenReaderOnly } from "../common/ScreenReaderOnly"; +import { DataGrid, DataGridCell } from "../common/DataGrid"; export const SINGLE_MODEL_GRID_TEMPLATE_COLUMNS = "0.5fr 0.125fr 0.125fr 0.125fr 0.125fr"; @@ -72,53 +68,56 @@ export const ModeledMethodDataGrid = ({ : SINGLE_MODEL_GRID_TEMPLATE_COLUMNS; return ( - + {someMethodsAreVisible && ( <> - - - API or method - - - Model type - - - Input - - - Output - - - Kind - - {viewState.showMultipleModels && ( - - Add or remove models - - )} - - {methodsWithModelability.map(({ method, methodCanBeModeled }) => { - const modeledMethods = modeledMethodsMap[method.signature] ?? []; - return ( - - ); - })} + + API or method + + + Model type + + + Input + + + Output + + + Kind + + {viewState.showMultipleModels && ( + + Add or remove models + + )} + {methodsWithModelability.map( + ({ method, methodCanBeModeled }, index) => { + const modeledMethods = modeledMethodsMap[method.signature] ?? []; + return ( + + ); + }, + )} )} - + ); }; diff --git a/extensions/ql-vscode/src/view/model-editor/__tests__/HiddenMethodsRow.spec.tsx b/extensions/ql-vscode/src/view/model-editor/__tests__/HiddenMethodsRow.spec.tsx index f8549cf60..11a15c4f8 100644 --- a/extensions/ql-vscode/src/view/model-editor/__tests__/HiddenMethodsRow.spec.tsx +++ b/extensions/ql-vscode/src/view/model-editor/__tests__/HiddenMethodsRow.spec.tsx @@ -1,11 +1,28 @@ import * as React from "react"; import { render, screen } from "@testing-library/react"; import { HiddenMethodsRow } from "../HiddenMethodsRow"; +import { createMockExtensionPack } from "../../../../test/factories/model-editor/extension-pack"; +import { ModelEditorViewState } from "../../../model-editor/shared/view-state"; +import { Mode } from "../../../model-editor/shared/mode"; describe(HiddenMethodsRow.name, () => { + const viewState: ModelEditorViewState = { + mode: Mode.Application, + showFlowGeneration: false, + showLlmButton: false, + showMultipleModels: false, + extensionPack: createMockExtensionPack(), + sourceArchiveAvailable: true, + }; + it("does not render with 0 hidden methods", () => { const { container } = render( - , + , ); expect(container).toBeEmptyDOMElement(); @@ -13,7 +30,12 @@ describe(HiddenMethodsRow.name, () => { it("renders with 1 hidden methods and no visible methods", () => { render( - , + , ); expect( @@ -23,7 +45,12 @@ describe(HiddenMethodsRow.name, () => { it("renders with 1 hidden methods and visible methods", () => { render( - , + , ); expect( @@ -33,7 +60,12 @@ describe(HiddenMethodsRow.name, () => { it("renders with 3 hidden methods and no visible methods", () => { render( - , + , ); expect( @@ -43,7 +75,12 @@ describe(HiddenMethodsRow.name, () => { it("renders with 3 hidden methods and visible methods", () => { render( - , + , ); expect( diff --git a/extensions/ql-vscode/src/view/model-editor/__tests__/MethodRow.spec.tsx b/extensions/ql-vscode/src/view/model-editor/__tests__/MethodRow.spec.tsx index bd690ce8c..63939708f 100644 --- a/extensions/ql-vscode/src/view/model-editor/__tests__/MethodRow.spec.tsx +++ b/extensions/ql-vscode/src/view/model-editor/__tests__/MethodRow.spec.tsx @@ -45,6 +45,7 @@ describe(MethodRow.name, () => { const render = (props: Partial = {}) => reactRender(