Skip to content

Commit

Permalink
[3982] Add support for command palette
Browse files Browse the repository at this point in the history
Bug: #3982
Signed-off-by: Stéphane Bégaudeau <stephane.begaudeau@obeo.fr>
Signed-off-by: Guillaume Coutable <guillaume.coutable@obeo.fr>
  • Loading branch information
sbegaudeau authored and gcoutable committed Sep 18, 2024
1 parent c03981c commit 19c7681
Show file tree
Hide file tree
Showing 22 changed files with 1,084 additions and 320 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Both `IInput` and `IDomainEvent` implement `ICause` and will thus be used to ind
=== New Features

- https://github.com/eclipse-sirius/sirius-web/issues/3763[#3763] [diagram] Make it possible to display semantic candidates in the selection dialog using a tree
- https://github.com/eclipse-sirius/sirius-web/issues/3982[#3982] [core] Add support for the command palette.


=== Improvements
Expand Down
775 changes: 466 additions & 309 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions packages/core/frontend/sirius-components-omnibox/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"printWidth": 120,
"singleQuote": true,
"bracketSameLine": true,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"overrides": [
{
"files": "*.js",
"options": {
"parser": "babel"
}
},
{
"files": "*.css",
"options": {
"parser": "css"
}
}
]
}
58 changes: 58 additions & 0 deletions packages/core/frontend/sirius-components-omnibox/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "@eclipse-sirius/sirius-components-omnibox",
"author": "Eclipse Sirius",
"version": "2024.9.0",
"license": "EPL-2.0",
"repository": {
"type": "git",
"url": "https://github.com/eclipse-sirius/sirius-web"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
},
"main": "./dist/sirius-components-omnibox.umd.js",
"module": "./dist/sirius-components-omnibox.es.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"exports": {
".": {
"require": "./dist/sirius-components-omnibox.umd.js",
"import": "./dist/sirius-components-omnibox.es.js"
}
},
"scripts": {
"start": "vite build --mode 'development' && tsc",
"build": "vite build && tsc",
"format": "prettier --write \"src/**/*.{js,ts,tsx,css}\"",
"format-lint": "prettier --list-different \"src/**/*.{js,ts,tsx,css}\"",
"publish:local": "yalc push"
},
"peerDependencies": {
"@mui/icons-material": "5.15.19",
"@mui/material": "5.15.19",
"@types/react": "18.3.3",
"@types/react-router-dom": "5.3.3",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.26.0",
"tss-react": "4.9.7"
},
"devDependencies": {
"@eclipse-sirius/sirius-components-tsconfig": "*",
"@mui/icons-material": "5.15.19",
"@mui/material": "5.15.19",
"@types/node": "20.12.12",
"@types/react": "18.3.3",
"@vitejs/plugin-react": "4.3.0",
"jest-junit-reporter": "1.1.0",
"prettier": "2.7.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.26.0",
"rollup-plugin-peer-deps-external": "2.2.4",
"typescript": "5.4.5",
"vite": "5.2.11"
}
}
110 changes: 110 additions & 0 deletions packages/core/frontend/sirius-components-omnibox/src/Omnibox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*******************************************************************************
* Copyright (c) 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import Breadcrumbs from '@mui/material/Breadcrumbs';
import Dialog from '@mui/material/Dialog';
import Divider from '@mui/material/Divider';
import FormControl from '@mui/material/FormControl';
import Input from '@mui/material/Input';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import Typography from '@mui/material/Typography';
import { useRef, useState } from 'react';
import { makeStyles } from 'tss-react/mui';
import { OmniboxProps, OmniboxState } from './Omnibox.types';

const useOmniboxStyles = makeStyles()((theme) => ({
omnibox: {
display: 'flex',
flexDirection: 'column',
},
omniboxInputArea: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
omniboxIcon: {
minWidth: theme.spacing(4),
fontSize: '2.5rem',
},
omniboxFormControl: {
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1),
paddingLeft: theme.spacing(0.5),
paddingRight: theme.spacing(0.5),
},
}));

export const Omnibox = ({ open, initialContextEntries, onClose }: OmniboxProps) => {
const [state, setState] = useState<OmniboxState>({
contextEntries: initialContextEntries,
query: '',
actions: [],
});

const inputRef = useRef<HTMLInputElement>(null);
const listRef = useRef<HTMLUListElement>(null);

const handleChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> = (event) => {
const {
target: { value },
} = event;
setState((prevState) => ({ ...prevState, query: value }));
};

const handleKeyDown: React.KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement> = () => {};

const { classes } = useOmniboxStyles();
return (
<Dialog open={open} onClose={onClose} fullWidth className={classes.omnibox}>
<div className={classes.omniboxInputArea}>
<ChevronRightIcon className={classes.omniboxIcon} />
<Breadcrumbs>
{state.contextEntries.map((contextEntry) => {
return <Typography key={contextEntry.id}>{contextEntry.label}</Typography>;
})}
</Breadcrumbs>
<FormControl variant="standard" fullWidth className={classes.omniboxFormControl}>
<Input
ref={inputRef}
value={state.query}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder="Run commands..."
disableUnderline
autoFocus
fullWidth
inputProps={{
style: {
fontSize: '1.5rem',
},
}}
/>
</FormControl>
</div>
<Divider />
<List ref={listRef} dense disablePadding>
{state.actions.map((action) => {
return (
<ListItemButton key={action.id}>
<ListItemIcon>{action.icon}</ListItemIcon>
<ListItemText>{action.label}</ListItemText>
</ListItemButton>
);
})}
</List>
</Dialog>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/

export interface OmniboxProps {
initialContextEntries: OmniboxContextEntry[];
open: boolean;
onClose: () => void;
}

export interface OmniboxState {
contextEntries: OmniboxContextEntry[];
query: string;
actions: OmniboxAction[];
}

export interface OmniboxContextEntry {
id: string;
label: string;
kind: OmniboxContextKind;
}

export type OmniboxContextKind = 'EditingContext';

export interface OmniboxAction {
id: string;
icon: JSX.Element;
label: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright (c) 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/

import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import Button from '@mui/material/Button';
import { useContext } from 'react';
import { makeStyles } from 'tss-react/mui';
import { OmniboxButtonProps } from './OmniboxButton.types';
import { OmniboxContext } from './OmniboxContext';
import { OmniboxContextValue } from './OmniboxContext.types';

const useOmniboxButtonStyles = makeStyles()((theme) => ({
omniboxButton: {
color: 'inherit',
border: `1px solid ${theme.palette.background.paper}`,
},
placeholder: {
verticalAlign: 'middle',
lineHeight: '16px',
},
omniboxField: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
border: `1px solid ${theme.palette.background.paper}`,
borderRadius: '3px',
marginLeft: theme.spacing(4),
fontSize: '0.75rem',
fontWeight: 700,
lineHeight: '20px',
padding: '0px 4px',
fontFamily: 'sans-serif',
opacity: 0.7,
},
}));

export const OmniboxButton = ({ size = 'small' }: OmniboxButtonProps) => {
const { classes } = useOmniboxButtonStyles();

const { openOmnibox } = useContext<OmniboxContextValue>(OmniboxContext);

const handleClick = () => openOmnibox();

var isApple = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
return (
<Button startIcon={<ChevronRightIcon />} onClick={handleClick} className={classes.omniboxButton} size={size}>
<div className={classes.placeholder}>Run commands...</div>
<div className={classes.omniboxField}>{isApple ? '⌘ ' : 'Ctrl '}+ K</div>
</Button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*******************************************************************************
* Copyright (c) 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/

export interface OmniboxButtonProps {
size?: OmniboxButtonSize;
}

export type OmniboxButtonSize = 'small' | 'medium' | 'large';
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*******************************************************************************
* Copyright (c) 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/

import React from 'react';
import { OmniboxContextValue } from './OmniboxContext.types';

export const OmniboxContext = React.createContext<OmniboxContextValue>({
openOmnibox: () => {},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*******************************************************************************
* Copyright (c) 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/

export interface OmniboxContextValue {
openOmnibox: () => void;
}
Loading

0 comments on commit 19c7681

Please sign in to comment.