Skip to content

Commit f1d2d12

Browse files
authored
Merge pull request #85 from mitchcapper/feature_context_right_click_menu
Add a context menu to the event list on the View Page
2 parents 0544427 + 1b8e588 commit f1d2d12

33 files changed

+704
-196
lines changed

automation/webpack.common.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ export default <Webpack.Configuration>{
7171
}, {
7272
test: /\.(woff2|ttf|png|svg)$/,
7373
loader: 'file-loader'
74+
}, {
75+
test: /\.mjs$/,
76+
include: /node_modules/,
77+
type: "javascript/auto"
7478
}, {
7579
test: /\.css$/,
7680
use: ['style-loader', 'css-loader']

package-lock.json

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
"react": "^16.14.0",
122122
"react-autosuggest": "^10.0.4",
123123
"react-beautiful-dnd": "^12.2.0",
124+
"react-contexify": "^6.0.0",
124125
"react-dom": "^16.14.0",
125126
"react-hotkeys-hook": "^2.1.3",
126127
"react-monaco-editor": "^0.45.0",

src/components/account/pro-placeholders.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { inject, observer } from "mobx-react";
44
import { styled } from "../../styles";
55
import { Icon } from "../../icons";
66

7-
import { UiStore } from "../../model/ui-store";
7+
import { UiStore } from "../../model/ui/ui-store";
88

99
import { Pill } from "../common/pill";
1010
import { UnstyledButton } from "../common/inputs";

src/components/app.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { appHistory } from '../routing';
1616
import { useHotkeys, Ctrl } from '../util/ui';
1717

1818
import { AccountStore } from '../model/account/account-store';
19+
import { UiStore } from '../model/ui/ui-store';
1920
import {
2021
serverVersion,
2122
versionSatisfies,
@@ -33,6 +34,7 @@ import { SettingsPage } from './settings/settings-page';
3334
import { PlanPicker } from './account/plan-picker';
3435
import { ModalOverlay } from './account/modal-overlay';
3536
import { CheckoutSpinner } from './account/checkout-spinner';
37+
import { HtmlContextMenu } from './html-context-menu';
3638

3739
const AppContainer = styled.div<{ inert?: boolean }>`
3840
display: flex;
@@ -82,8 +84,12 @@ const AppKeyboardShortcuts = (props: {
8284
};
8385

8486
@inject('accountStore')
87+
@inject('uiStore')
8588
@observer
86-
class App extends React.Component<{ accountStore: AccountStore }> {
89+
class App extends React.Component<{
90+
accountStore: AccountStore,
91+
uiStore: UiStore
92+
}> {
8793

8894
@computed
8995
get canVisitSettings() {
@@ -198,6 +204,11 @@ class App extends React.Component<{ accountStore: AccountStore }> {
198204
cancelCheckout
199205
} = this.props.accountStore;
200206

207+
const {
208+
contextMenuState,
209+
clearHtmlContextMenu
210+
} = this.props.uiStore;
211+
201212
return <LocationProvider history={appHistory}>
202213
<AppKeyboardShortcuts
203214
navigate={appHistory.navigate}
@@ -242,13 +253,21 @@ class App extends React.Component<{ accountStore: AccountStore }> {
242253
onCancel={cancelCheckout}
243254
/>
244255
}
256+
257+
{
258+
contextMenuState &&
259+
<HtmlContextMenu
260+
menuState={contextMenuState}
261+
onHidden={clearHtmlContextMenu}
262+
/>
263+
}
245264
</LocationProvider>;
246265
}
247266
}
248267

249268
// Annoying cast required to handle the store prop nicely in our types
250269
const AppWithStoreInjected = (
251-
App as unknown as WithInjected<typeof App, 'accountStore'>
270+
App as unknown as WithInjected<typeof App, 'accountStore' | 'uiStore'>
252271
);
253272

254273
export { AppWithStoreInjected as App };

src/components/common/copy-button.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ import * as React from 'react';
22

33
import { Icon } from "../../icons";
44
import { styled } from '../../styles';
5-
import { logError } from '../../errors';
65

76
import { clickOnEnter } from '../component-utils';
87
import { PillButton } from './pill';
98
import { IconButton } from './icon-button';
10-
11-
const clipboardSupported = !!navigator.clipboard;
9+
import { copyToClipboard } from '../../util/ui';
1210

1311
const CopyIconButton = styled(IconButton)`
1412
color: ${p => p.theme.mainColor};
@@ -49,8 +47,6 @@ export const CopyButtonIcon = (p: {
4947
content: string,
5048
onClick: () => void
5149
}) => {
52-
if (!clipboardSupported) return null;
53-
5450
const [success, showSuccess] = useTemporaryFlag();
5551

5652
return <CopyIconButton
@@ -67,8 +63,6 @@ export const CopyButtonIcon = (p: {
6763
}
6864

6965
export const CopyButtonPill = (p: { content: string, children?: React.ReactNode }) => {
70-
if (!clipboardSupported) return null;
71-
7266
const [success, showSuccess] = useTemporaryFlag();
7367

7468
return <PillButton
@@ -85,13 +79,4 @@ export const CopyButtonPill = (p: { content: string, children?: React.ReactNode
8579
/>
8680
{ p.children }
8781
</PillButton>;
88-
}
89-
90-
async function copyToClipboard(content: string) {
91-
try {
92-
await navigator.clipboard!.writeText(content);
93-
} catch (e) {
94-
console.log('Failed to copy to the clipboard');
95-
logError(e);
96-
}
9782
}

src/components/common/text-content.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { styled } from "../../styles";
55
import { Html } from '../../types';
66
import { suggestionIconHtml, warningIconHtml } from '../../icons';
77

8-
import { fromMarkdown } from '../../model/markdown';
8+
import { fromMarkdown } from '../../model/ui/markdown';
99

1010
export const ContentLabel = styled.h2`
1111
text-transform: uppercase;

src/components/html-context-menu.tsx

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import * as React from 'react';
2+
import { autorun } from 'mobx';
3+
import { disposeOnUnmount, observer } from 'mobx-react';
4+
5+
import {
6+
Menu,
7+
Submenu,
8+
Separator,
9+
Item,
10+
contextMenu
11+
} from 'react-contexify';
12+
13+
import { styled } from '../styles';
14+
15+
import { UnreachableCheck } from '../util/error';
16+
17+
import { ContextMenuItem, ContextMenuState } from '../model/ui/context-menu';
18+
19+
@observer
20+
export class HtmlContextMenu<T> extends React.Component<{
21+
menuState: ContextMenuState<T>,
22+
onHidden: () => void
23+
}> {
24+
25+
componentDidMount() {
26+
// Automatically show the menu when this is rendered:
27+
disposeOnUnmount(this, autorun(() => {
28+
const menuState = this.props.menuState;
29+
30+
// Annoyingly, the menu is not listening immediately after the component
31+
// is mounted, so we have to delay this slightly:
32+
setTimeout(() => {
33+
contextMenu.show({
34+
id: 'menu',
35+
event: menuState.event
36+
});
37+
}, 10);
38+
}));
39+
}
40+
41+
render() {
42+
return <ThemedMenu
43+
id='menu'
44+
onVisibilityChange={this.onVisibilityChange}
45+
>
46+
{ this.props.menuState.items.map(this.renderItem) }
47+
</ThemedMenu>
48+
}
49+
50+
renderItem = (item: ContextMenuItem<T>, i: number) => {
51+
if (item.type === 'separator') {
52+
return <Separator key={i} />;
53+
} else if (item.type === 'option') {
54+
return <Item
55+
key={i}
56+
onClick={() => item.callback(this.props.menuState.data)}
57+
disabled={item.enabled === false}
58+
>
59+
{ item.label }
60+
</Item>
61+
} else if (item.type === 'submenu') {
62+
return <Submenu
63+
key={i}
64+
label={item.label}
65+
disabled={item.enabled === false}
66+
>
67+
{ item.items.map(this.renderItem) }
68+
</Submenu>
69+
} else throw new UnreachableCheck(item, i => i.type);
70+
}
71+
72+
onVisibilityChange = (visible: boolean) => {
73+
if (!visible) this.props.onHidden();
74+
};
75+
76+
}
77+
78+
const ThemedMenu = styled(Menu)`
79+
--contexify-menu-bgColor: ${p => p.theme.mainLowlightBackground};
80+
--contexify-item-color: ${p => p.theme.mainColor};
81+
--contexify-separator-color: ${p => p.theme.containerBorder};
82+
83+
--contexify-rightSlot-color: ${p => p.theme.containerWatermark};
84+
--contexify-activeRightSlot-color: ${p => p.theme.mainColor};
85+
86+
--contexify-arrow-color: ${p => p.theme.containerWatermark};
87+
--contexify-activeArrow-color: ${p => p.theme.mainColor};
88+
89+
--contexify-activeItem-color: #fff;
90+
--contexify-activeItem-bgColor: #3498db;
91+
`;

src/components/intercept/config/electron-config.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { Icon } from '../../../icons';
77
import { logError } from '../../../errors';
88

99
import { Interceptor } from '../../../model/interception/interceptors';
10-
import { UiStore } from '../../../model/ui-store';
10+
import { UiStore } from '../../../model/ui/ui-store';
11+
import { DesktopApi } from '../../../services/desktop-api';
1112

1213
import { uploadFile } from '../../../util/ui';
1314
import { Button, SecondaryButton, UnstyledButton } from '../../common/inputs';
@@ -120,12 +121,6 @@ function getReadablePath(path: string) {
120121
}
121122
}
122123

123-
declare global {
124-
interface Window {
125-
desktopApi?: { selectApplication: () => Promise<string | undefined> };
126-
}
127-
}
128-
129124
@inject('uiStore')
130125
@observer
131126
class ElectronConfig extends React.Component<{
@@ -147,13 +142,9 @@ class ElectronConfig extends React.Component<{
147142
}
148143

149144
selectApplication = async () => {
150-
const useNativePicker = window.desktopApi?.selectApplication;
145+
const appPicker = DesktopApi.selectApplication ?? (() => uploadFile('path'));
151146

152-
const pathToApplication = await(
153-
useNativePicker
154-
? window.desktopApi?.selectApplication()
155-
: uploadFile('path')
156-
);
147+
const pathToApplication = await(appPicker());
157148

158149
if (!pathToApplication) {
159150
this.props.closeSelf();

src/components/intercept/config/existing-terminal-config.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from '../../../services/service-versions';
1212

1313
import { AccountStore } from '../../../model/account/account-store';
14-
import { UiStore } from '../../../model/ui-store';
14+
import { UiStore } from '../../../model/ui/ui-store';
1515
import { InterceptorStore } from '../../../model/interception/interceptor-store';
1616
import { Interceptor } from '../../../model/interception/interceptors';
1717

0 commit comments

Comments
 (0)