Skip to content

Commit 4daded9

Browse files
committed
feat: search by shortcut
- add four global hotkeys for accessing the four different searches - introduce ctrl/cmd+f shortcut to search for resources, attributions, signals depending on the focused context - improve general keyboard accessibility - use React context for search-ref because Redux can only handle serializable data - use context for Virtuoso comp. props as they cannot be inlined: petyosi/react-virtuoso#566 closes #2587 Signed-off-by: Maxim Stykow <maxim.stykow@tngtech.com>
1 parent b820106 commit 4daded9

31 files changed

+389
-149
lines changed
254 Bytes
Loading
269 Bytes
Loading

src/ElectronBackend/main/menu.ts

+73-30
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
4040
),
4141
label: 'Open File',
4242
accelerator: 'CmdOrCtrl+O',
43-
click(): void {
43+
click: () => {
4444
void getOpenFileListener(mainWindow)();
4545
},
4646
},
@@ -51,7 +51,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
5151
),
5252
label: 'Save',
5353
accelerator: 'CmdOrCtrl+S',
54-
click(): void {
54+
click: () => {
5555
webContents.send(AllowedFrontendChannels.SaveFileRequest, {
5656
saveFile: true,
5757
});
@@ -70,7 +70,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
7070
'icons/follow-up-white.png',
7171
'icons/follow-up-black.png',
7272
),
73-
click(): void {
73+
click: () => {
7474
setLoadingState(mainWindow.webContents, true);
7575
logger.info('Preparing data for follow-up export');
7676
webContents.send(
@@ -85,7 +85,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
8585
'icons/com-list-black.png',
8686
),
8787
label: 'Compact component list',
88-
click(): void {
88+
click: () => {
8989
setLoadingState(mainWindow.webContents, true);
9090
logger.info('Preparing data for compact component list export');
9191
webContents.send(
@@ -100,7 +100,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
100100
'icons/det-list-black.png',
101101
),
102102
label: 'Detailed component list',
103-
click(): void {
103+
click: () => {
104104
setLoadingState(mainWindow.webContents, true);
105105
logger.info(
106106
'Preparing data for detailed component list export',
@@ -117,7 +117,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
117117
'icons/yaml-black.png',
118118
),
119119
label: 'SPDX (yaml)',
120-
click(): void {
120+
click: () => {
121121
setLoadingState(mainWindow.webContents, true);
122122
logger.info('Preparing data for SPDX (yaml) export');
123123
webContents.send(
@@ -132,7 +132,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
132132
'icons/json-black.png',
133133
),
134134
label: 'SPDX (json)',
135-
click(): void {
135+
click: () => {
136136
setLoadingState(mainWindow.webContents, true);
137137
logger.info('Preparing data for SPDX (json) export');
138138
webContents.send(
@@ -149,7 +149,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
149149
'icons/about-black.png',
150150
),
151151
label: 'Project Metadata',
152-
click(): void {
152+
click: () => {
153153
if (isFileLoaded(getGlobalBackendState())) {
154154
webContents.send(
155155
AllowedFrontendChannels.ShowProjectMetadataPopup,
@@ -166,7 +166,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
166166
'icons/statictics-black.png',
167167
),
168168
label: 'Project Statistics',
169-
click(): void {
169+
click: () => {
170170
if (isFileLoaded(getGlobalBackendState())) {
171171
webContents.send(
172172
AllowedFrontendChannels.ShowProjectStatisticsPopup,
@@ -183,7 +183,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
183183
'icons/restore-black.png',
184184
),
185185
label: 'Set Path to Sources',
186-
click(): void {
186+
click: () => {
187187
getSelectBaseURLListener(mainWindow)();
188188
},
189189
},
@@ -194,7 +194,7 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
194194
),
195195
label: 'Quit',
196196
accelerator: 'CmdOrCtrl+Q',
197-
click(): void {
197+
click: () => {
198198
app.quit();
199199
},
200200
},
@@ -258,6 +258,59 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
258258
accelerator: 'CmdOrCtrl+A',
259259
role: 'selectAll',
260260
},
261+
{ type: 'separator' },
262+
{
263+
icon: getIconBasedOnTheme(
264+
'icons/magnifying-glass-white.png',
265+
'icons/magnifying-glass-black.png',
266+
),
267+
label: 'Search Attributions',
268+
accelerator: 'CmdOrCtrl+Shift+A',
269+
click: () => {
270+
if (isFileLoaded(getGlobalBackendState())) {
271+
webContents.send(AllowedFrontendChannels.SearchAttributions);
272+
}
273+
},
274+
},
275+
{
276+
icon: getIconBasedOnTheme(
277+
'icons/magnifying-glass-white.png',
278+
'icons/magnifying-glass-black.png',
279+
),
280+
label: 'Search Signals',
281+
accelerator: 'CmdOrCtrl+Shift+S',
282+
click: () => {
283+
if (isFileLoaded(getGlobalBackendState())) {
284+
webContents.send(AllowedFrontendChannels.SearchSignals);
285+
}
286+
},
287+
},
288+
{
289+
icon: getIconBasedOnTheme(
290+
'icons/search-white.png',
291+
'icons/search-black.png',
292+
),
293+
label: 'Search All Resources',
294+
accelerator: 'CmdOrCtrl+Shift+R',
295+
click: () => {
296+
if (isFileLoaded(getGlobalBackendState())) {
297+
webContents.send(AllowedFrontendChannels.SearchResources);
298+
}
299+
},
300+
},
301+
{
302+
icon: getIconBasedOnTheme(
303+
'icons/search-white.png',
304+
'icons/search-black.png',
305+
),
306+
label: 'Search Linked Resources',
307+
accelerator: 'CmdOrCtrl+Shift+L',
308+
click: () => {
309+
if (isFileLoaded(getGlobalBackendState())) {
310+
webContents.send(AllowedFrontendChannels.SearchLinkedResources);
311+
}
312+
},
313+
},
261314
],
262315
},
263316
{
@@ -338,31 +391,24 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
338391
'icons/github-black.png',
339392
),
340393
label: 'Open on GitHub',
341-
click: async (): Promise<void> => {
342-
await shell.openExternal(
343-
'https://github.com/opossum-tool/opossumUI',
344-
);
345-
},
394+
click: () =>
395+
shell.openExternal('https://github.com/opossum-tool/opossumUI'),
346396
},
347397
{
348398
icon: getIconBasedOnTheme(
349399
'icons/notice-white.png',
350400
'icons/notice-black.png',
351401
),
352402
label: 'OpossumUI Notices',
353-
click: async (): Promise<void> => {
354-
await shell.openPath(getPathOfNoticeDocument());
355-
},
403+
click: () => shell.openPath(getPathOfNoticeDocument()),
356404
},
357405
{
358406
icon: getIconBasedOnTheme(
359407
'icons/chromium-white.png',
360408
'icons/chromium-black.png',
361409
),
362410
label: 'Chromium Notices',
363-
click: async (): Promise<void> => {
364-
await shell.openPath(getPathOfChromiumNoticeDocument());
365-
},
411+
click: () => shell.openPath(getPathOfChromiumNoticeDocument()),
366412
},
367413
],
368414
},
@@ -375,29 +421,26 @@ export async function createMenu(mainWindow: BrowserWindow): Promise<Menu> {
375421
'icons/user-guide-black.png',
376422
),
377423
label: "User's Guide",
378-
click: async (): Promise<void> => {
379-
await shell.openExternal(
424+
click: () =>
425+
shell.openExternal(
380426
'https://github.com/opossum-tool/OpossumUI/blob/main/USER_GUIDE.md',
381-
);
382-
},
427+
),
383428
},
384429
{
385430
icon: getIconBasedOnTheme(
386431
'icons/log-white.png',
387432
'icons/log-black.png',
388433
),
389434
label: 'Open log files folder',
390-
click: async (): Promise<void> => {
391-
await shell.openPath(app.getPath('logs'));
392-
},
435+
click: () => shell.openPath(app.getPath('logs')),
393436
},
394437
{
395438
icon: getIconBasedOnTheme(
396439
'icons/update-white.png',
397440
'icons/update-black.png',
398441
),
399442
label: 'Check for updates',
400-
click(): void {
443+
click: () => {
401444
webContents.send(AllowedFrontendChannels.ShowUpdateAppPopup, {
402445
showUpdateAppPopup: true,
403446
});

src/Frontend/Components/AttributionPanels/AttributionPanels.tsx

+13-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// SPDX-License-Identifier: Apache-2.0
55
import { useCallback } from 'react';
66

7+
import { AllowedFrontendChannels } from '../../../shared/ipc-channels';
78
import { text } from '../../../shared/text';
89
import {
910
useFilteredAttributions,
@@ -49,17 +50,23 @@ export function AttributionPanels() {
4950
setHeight={setHeight}
5051
upperPanel={{
5152
component: <AttributionsPanel />,
52-
search: attributionSearch,
53-
setSearch: (search) =>
54-
setFilteredAttributions((prev) => ({ ...prev, search })),
53+
search: {
54+
value: attributionSearch,
55+
setValue: (search) =>
56+
setFilteredAttributions((prev) => ({ ...prev, search })),
57+
channel: AllowedFrontendChannels.SearchAttributions,
58+
},
5559
title: text.packageLists.attributionsPanelTitle,
5660
headerTestId: 'attributions-panel-header',
5761
}}
5862
lowerPanel={{
5963
component: <SignalsPanel />,
60-
search: signalSearch,
61-
setSearch: (search) =>
62-
setFilteredSignals((prev) => ({ ...prev, search })),
64+
search: {
65+
value: signalSearch,
66+
setValue: (search) =>
67+
setFilteredSignals((prev) => ({ ...prev, search })),
68+
channel: AllowedFrontendChannels.SearchSignals,
69+
},
6370
title: text.packageLists.signalsPanelTitle,
6471
headerTestId: 'signals-panel-header',
6572
}}

src/Frontend/Components/AttributionPanels/AttributionsPanel/AttributionsList/AttributionsList.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useAttributionIdsForReplacement } from '../../../../state/variables/use
1212
import { isPackageInfoIncomplete } from '../../../../util/is-important-attribution-information-missing';
1313
import { List, ListItemContentProps } from '../../../List/List';
1414
import { PackageCard } from '../../../PackageCard/PackageCard';
15+
import { SearchList } from '../../../SearchList/SearchList';
1516
import { PackagesPanelChildrenProps } from '../../PackagesPanel/PackagesPanel';
1617

1718
export const AttributionsList: React.FC<PackagesPanelChildrenProps> = ({
@@ -31,6 +32,7 @@ export const AttributionsList: React.FC<PackagesPanelChildrenProps> = ({
3132
<List
3233
renderItemContent={renderAttributionCard}
3334
data={activeAttributionIds}
35+
components={{ List: SearchList }}
3436
selectedId={selectedAttributionId}
3537
loading={loading}
3638
sx={{ transition: TRANSITION, height: contentHeight }}

src/Frontend/Components/AttributionPanels/PackagesPanel/PackagesPanel.style.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const TABS_CONTAINER_HEIGHT = 30;
1414

1515
export const Panel = styled(MuiBox)({
1616
flex: 1,
17-
overflowY: 'auto',
17+
overflowY: 'hidden',
1818
});
1919

2020
export const ActionBarContainer = styled(MuiBox)({

src/Frontend/Components/AttributionPanels/PackagesPanel/PackagesPanel.tsx

+14-11
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,20 @@ export const PackagesPanel = ({
123123
[attributionIds, multiSelectedAttributionIds, selectedAttributionId],
124124
);
125125

126-
const areAllAttributionsSelected = useMemo(
127-
() =>
128-
!!attributionIds?.length &&
129-
!difference(
130-
attributionIds.filter(
131-
(id) => attributions?.[id].relation === activeRelation,
132-
),
133-
multiSelectedAttributionIds,
134-
).length,
135-
[activeRelation, attributionIds, attributions, multiSelectedAttributionIds],
136-
);
126+
const areAllAttributionsSelected = useMemo(() => {
127+
const activeAttributionIds = attributionIds?.filter(
128+
(id) => attributions?.[id].relation === activeRelation,
129+
);
130+
return (
131+
!!activeAttributionIds?.length &&
132+
!difference(activeAttributionIds, multiSelectedAttributionIds).length
133+
);
134+
}, [
135+
activeRelation,
136+
attributionIds,
137+
attributions,
138+
multiSelectedAttributionIds,
139+
]);
137140

138141
const effectiveSelectedIds = useMemo(
139142
() => intersection(attributionIds, multiSelectedAttributionIds),

src/Frontend/Components/AttributionPanels/SignalsPanel/SignalsList/SignalsList.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from '../../../GroupedList/GroupedList';
2121
import { SourceIcon } from '../../../Icons/Icons';
2222
import { PackageCard } from '../../../PackageCard/PackageCard';
23+
import { SearchList } from '../../../SearchList/SearchList';
2324
import { PackagesPanelChildrenProps } from '../../PackagesPanel/PackagesPanel';
2425
import { GroupName } from './SignalsList.style';
2526

@@ -71,6 +72,7 @@ export const SignalsList: React.FC<PackagesPanelChildrenProps> = ({
7172
grouped={groupedIds}
7273
selectedId={selectedAttributionId}
7374
renderItemContent={renderAttributionCard}
75+
components={{ List: SearchList }}
7476
renderGroupName={(sourceName) => (
7577
<>
7678
<SourceIcon noTooltip />

src/Frontend/Components/NoResults/NoResults.tsx src/Frontend/Components/EmptyPlaceholder/EmptyPlaceholder.style.ts

+1-10
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,8 @@
44
// SPDX-License-Identifier: Apache-2.0
55
import { styled } from '@mui/material';
66
import MuiBox from '@mui/material/Box';
7-
import MuiTypography from '@mui/material/Typography';
87

9-
import { text } from '../../../shared/text';
10-
11-
export const NoResults = styled((props) => (
12-
<MuiBox {...props}>
13-
<MuiTypography sx={{ textTransform: 'uppercase' }}>
14-
{text.generic.noResults}
15-
</MuiTypography>
16-
</MuiBox>
17-
))({
8+
export const Container = styled(MuiBox)({
189
display: 'flex',
1910
height: '100%',
2011
justifyContent: 'center',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-FileCopyrightText: Meta Platforms, Inc. and its affiliates
2+
// SPDX-FileCopyrightText: TNG Technology Consulting GmbH <https://www.tngtech.com>
3+
//
4+
// SPDX-License-Identifier: Apache-2.0
5+
import MuiTypography from '@mui/material/Typography';
6+
7+
import { text } from '../../../shared/text';
8+
import { useVirtuosoComponent } from '../VirtuosoComponentContext/VirtuosoComponentContext';
9+
import { Container } from './EmptyPlaceholder.style';
10+
11+
export const EmptyPlaceholder: React.FC = () => {
12+
const { loading } = useVirtuosoComponent();
13+
14+
if (loading) {
15+
return null;
16+
}
17+
18+
return (
19+
<Container>
20+
<MuiTypography sx={{ textTransform: 'uppercase' }}>
21+
{text.generic.noResults}
22+
</MuiTypography>
23+
</Container>
24+
);
25+
};

0 commit comments

Comments
 (0)