Merge branch '17426-plug-ins' refs #17426
[arvados-workbench2.git] / src / plugins / example / exampleComponents.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from 'react';
6 import { WithDialogProps } from '~/store/dialog/with-dialog';
7 import { ServiceRepository } from "~/services/services";
8 import { Dispatch } from "redux";
9 import { RootState } from '~/store/store';
10 import { initialize } from 'redux-form';
11 import { dialogActions } from "~/store/dialog/dialog-actions";
12 import { reduxForm, InjectedFormProps, Field, reset, startSubmit } from 'redux-form';
13 import { TextField } from "~/components/text-field/text-field";
14 import { FormDialog } from '~/components/form-dialog/form-dialog';
15 import { withDialog } from "~/store/dialog/with-dialog";
16 import { compose } from "redux";
17 import { propertiesActions } from "~/store/properties/properties-actions";
18 import { DispatchProp, connect } from 'react-redux';
19 import { MenuItem } from "@material-ui/core";
20 import { Card, CardContent, Typography } from "@material-ui/core";
21
22 // This is the name of the dialog box.  It in store actions that
23 // open/close the dialog box.
24 export const EXAMPLE_DIALOG_FORM_NAME = "exampleFormName";
25
26 // This is the name of the property that will be used to store the
27 // "pressed" count
28 export const propertyKey = "Example_menu_item_pressed_count";
29
30 // The model backing the form.
31 export interface ExampleFormDialogData {
32     pressedCount: number | string;  // Supposed to start as a number but TextField seems to turn this into a string, unfortunately.
33 }
34
35 // The actual component with the editing fields.  Enables editing
36 // the 'pressedCount' field.
37 const ExampleEditFields = () => <span>
38     <Field
39         name='pressedCount'
40         component={TextField}
41         type="number"
42     />
43 </span>;
44
45 // Callback for when the form is submitted.
46 const submitEditedPressedCount = (data: ExampleFormDialogData) =>
47     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
48         dispatch(startSubmit(EXAMPLE_DIALOG_FORM_NAME));
49         dispatch(propertiesActions.SET_PROPERTY({
50             key: propertyKey, value: parseInt(data.pressedCount as string, 10)
51         }));
52         dispatch(dialogActions.CLOSE_DIALOG({ id: EXAMPLE_DIALOG_FORM_NAME }));
53         dispatch(reset(EXAMPLE_DIALOG_FORM_NAME));
54     };
55
56 // Props for the dialog component
57 type DialogExampleProps = WithDialogProps<{ updating: boolean }> & InjectedFormProps<ExampleFormDialogData>;
58
59 // This is the component that renders the dialog.
60 const DialogExample = (props: DialogExampleProps) =>
61     <FormDialog
62         dialogTitle="Edit pressed count"
63         formFields={ExampleEditFields}
64         submitLabel="Update pressed count"
65         {...props}
66     />;
67
68 // This ties it all together, withDialog() determines if the dialog is
69 // visible based on state, and reduxForm manages the values of the
70 // dialog's fields.
71 export const ExampleDialog = compose(
72     withDialog(EXAMPLE_DIALOG_FORM_NAME),
73     reduxForm<ExampleFormDialogData>({
74         form: EXAMPLE_DIALOG_FORM_NAME,
75         onSubmit: (data, dispatch) => {
76             dispatch(submitEditedPressedCount(data));
77         }
78     })
79 )(DialogExample);
80
81
82 // Callback, dispatches an action to set the value of property
83 // "Example_menu_item_pressed_count"
84 const incrementPressedCount = (dispatch: Dispatch, pressedCount: number) => {
85     dispatch(propertiesActions.SET_PROPERTY({ key: propertyKey, value: pressedCount + 1 }));
86 };
87
88 // Callback, dispatches actions required to initialize and open the
89 // dialog box.
90 export const openExampleDialog = (pressedCount: number) =>
91     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
92         dispatch(initialize(EXAMPLE_DIALOG_FORM_NAME, { pressedCount }));
93         dispatch(dialogActions.OPEN_DIALOG({
94             id: EXAMPLE_DIALOG_FORM_NAME, data: {}
95         }));
96     };
97
98 // Props definition used for menu items.
99 interface ExampleProps {
100     pressedCount: number;
101     className?: string;
102 }
103
104 // Called to get the props from the redux state for several of the
105 // following components.
106 // Gets the value of the property "Example_menu_item_pressed_count"
107 // from the state and puts it in 'pressedCount'
108 const exampleMapStateToProps = (state: RootState) => ({ pressedCount: state.properties[propertyKey] || 0 });
109
110 // Define component for the menu item that incremens the count each time it is pressed.
111 export const ExampleMenuComponent = connect(exampleMapStateToProps)(
112     ({ pressedCount, dispatch, className }: ExampleProps & DispatchProp<any>) =>
113         <MenuItem className={className} onClick={() => incrementPressedCount(dispatch, pressedCount)}>Example menu item</MenuItem >
114 );
115
116 // Define component for the menu item that opens the dialog box that lets you edit the count directly.
117 export const ExampleDialogMenuComponent = connect(exampleMapStateToProps)(
118     ({ pressedCount, dispatch, className }: ExampleProps & DispatchProp<any>) =>
119         <MenuItem className={className} onClick={() => dispatch(openExampleDialog(pressedCount))}>Open example dialog</MenuItem >
120 );
121
122 // The central panel.  Displays the "pressed" count.
123 export const ExamplePluginMainPanel = connect(exampleMapStateToProps)(
124     ({ pressedCount }: ExampleProps) =>
125         <Card>
126             <CardContent>
127                 <Typography>
128                     This is a example main panel plugin.  The example menu item has been pressed {pressedCount} times.
129                 </Typography>
130             </CardContent>
131         </Card>);