Skip to content

Commit 8e324ee

Browse files
committed
Finish up context menu - prod ready, with native support & SoC
This splits this context menu options completely from the event row UI, separated from the HTML/desktop context triggering & handling, separate from the two actual implementations (one here with react-contexify, one just added in the desktop).
1 parent 5055c82 commit 8e324ee

File tree

10 files changed

+352
-272
lines changed

10 files changed

+352
-272
lines changed

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/html-context-menu.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 { ContextMenuItem, ContextMenuState } from '../model/ui/context-menu';
14+
import { UnreachableCheck } from '../util/error';
15+
16+
@observer
17+
export class HtmlContextMenu<T> extends React.Component<{
18+
menuState: ContextMenuState<T>,
19+
onHidden: () => void
20+
}> {
21+
22+
componentDidMount() {
23+
// Automatically show the menu when this is rendered:
24+
disposeOnUnmount(this, autorun(() => {
25+
const menuState = this.props.menuState;
26+
27+
// Annoyingly, the menu is not listening immediately after the component
28+
// is mounted, so we have to delay this slightly:
29+
setTimeout(() => {
30+
contextMenu.show({
31+
id: 'menu',
32+
event: menuState.event
33+
});
34+
}, 10);
35+
}));
36+
}
37+
38+
render() {
39+
return <Menu
40+
id='menu'
41+
onVisibilityChange={this.onVisibilityChange}
42+
>
43+
{ this.props.menuState.items.map(this.renderItem) }
44+
</Menu>
45+
}
46+
47+
renderItem = (item: ContextMenuItem<T>, i: number) => {
48+
if (item.type === 'separator') {
49+
return <Separator key={i} />;
50+
} else if (item.type === 'option') {
51+
return <Item
52+
key={i}
53+
onClick={() => item.callback(this.props.menuState.data)}
54+
disabled={item.enabled === false}
55+
>
56+
{ item.label }
57+
</Item>
58+
} else if (item.type === 'submenu') {
59+
return <Submenu
60+
key={i}
61+
label={item.label}
62+
disabled={item.enabled === false}
63+
>
64+
{ item.items.map(this.renderItem) }
65+
</Submenu>
66+
} else throw new UnreachableCheck(item, i => i.type);
67+
}
68+
69+
onVisibilityChange = (visible: boolean) => {
70+
if (!visible) this.props.onHidden();
71+
};
72+
73+
}

src/components/view/context-menu.tsx

Lines changed: 0 additions & 103 deletions
This file was deleted.

src/components/view/html-context-menu.tsx

Lines changed: 0 additions & 107 deletions
This file was deleted.

0 commit comments

Comments
 (0)