Преглед изворни кода

[RN] Power to Dialog

* Implement disabling buttons (like Web had the ability).
* Use consistent colors (e.g. for the buttons) like the rest of the app
  (e.g. WelcomePage).
* Enable AbstractDialog to await a thenable from onSubmit and Dialog to
  render a LoadingIndicator in place of the OK/submit button text.
j8
Lyubo Marinov пре 8 година
родитељ
комит
a12984ed6f

+ 85
- 6
react/features/base/dialog/components/AbstractDialog.js Прегледај датотеку

37
     constructor(props) {
37
     constructor(props) {
38
         super(props);
38
         super(props);
39
 
39
 
40
+        this.state = {
41
+        };
42
+
40
         this._onCancel = this._onCancel.bind(this);
43
         this._onCancel = this._onCancel.bind(this);
41
         this._onSubmit = this._onSubmit.bind(this);
44
         this._onSubmit = this._onSubmit.bind(this);
45
+        this._onSubmitFulfilled = this._onSubmitFulfilled.bind(this);
46
+        this._onSubmitRejected = this._onSubmitRejected.bind(this);
47
+    }
48
+
49
+    /**
50
+     * Implements React's {@link Component#componentWillMount()}. Invoked
51
+     * immediately before mounting occurs.
52
+     *
53
+     * @inheritdoc
54
+     */
55
+    componentWillMount() {
56
+        this._mounted = true;
57
+    }
58
+
59
+    /**
60
+     * Implements React's {@link Component#componentWillUnmount()}. Invoked
61
+     * immediately before this component is unmounted and destroyed.
62
+     *
63
+     * @inheritdoc
64
+     */
65
+    componentWillUnmount() {
66
+        this._mounted = false;
42
     }
67
     }
43
 
68
 
44
     /**
69
     /**
48
      * @returns {void}
73
      * @returns {void}
49
      */
74
      */
50
     _onCancel() {
75
     _onCancel() {
51
-        const { onCancel } = this.props;
76
+        const { cancelDisabled, onCancel } = this.props;
52
 
77
 
53
-        if (!onCancel || onCancel()) {
78
+        if ((typeof cancelDisabled === 'undefined' || !cancelDisabled)
79
+                && (!onCancel || onCancel())) {
54
             this.props.dispatch(hideDialog());
80
             this.props.dispatch(hideDialog());
55
         }
81
         }
56
     }
82
     }
57
 
83
 
58
     /**
84
     /**
59
-     * Dispatches a redux action to hide this dialog when it's submitted.
85
+     * Submits this dialog. If the React <tt>Component</tt> prop
86
+     * <tt>onSubmit</tt> is defined, the function that is the value of the prop
87
+     * is invoked. If the function returns a <tt>thenable</tt>, then the
88
+     * resolution of the <tt>thenable</tt> is awaited. If the submission
89
+     * completes successfully, a redux action will be dispatched to hide this
90
+     * dialog.
60
      *
91
      *
61
      * @private
92
      * @private
62
      * @param {string} value - The submitted value if any.
93
      * @param {string} value - The submitted value if any.
63
      * @returns {void}
94
      * @returns {void}
64
      */
95
      */
65
     _onSubmit(value) {
96
     _onSubmit(value) {
66
-        const { onSubmit } = this.props;
97
+        const { okDisabled, onSubmit } = this.props;
67
 
98
 
68
-        if (!onSubmit || onSubmit(value)) {
69
-            this.props.dispatch(hideDialog());
99
+        if (typeof okDisabled === 'undefined' || !okDisabled) {
100
+            this.setState({ submitting: true });
101
+
102
+            // Invoke the React Compnent prop onSubmit if any.
103
+            const r = !onSubmit || onSubmit(value);
104
+
105
+            // If the invocation returns a thenable, await its resolution;
106
+            // otherwise, treat the return value as a boolean indicating whether
107
+            // the submission has completed successfully.
108
+            let then;
109
+
110
+            if (r) {
111
+                switch (typeof r) {
112
+                case 'function':
113
+                case 'object':
114
+                    then = r.then;
115
+                    break;
116
+                }
117
+            }
118
+            if (typeof then === 'function' && then.length === 2) {
119
+                then.call(r, this._onSubmitFulfilled, this._onSubmitRejected);
120
+            } else if (r) {
121
+                this._onSubmitFulfilled();
122
+            } else {
123
+                this._onSubmitRejected();
124
+            }
70
         }
125
         }
71
     }
126
     }
127
+
128
+    /**
129
+     * Notifies this <tt>AbstractDialog</tt> that it has been submitted
130
+     * successfully. Dispatches a redux action to hide this dialog after it has
131
+     * been submitted.
132
+     *
133
+     * @private
134
+     * @returns {void}
135
+     */
136
+    _onSubmitFulfilled() {
137
+        this._mounted && this.setState({ submitting: false });
138
+
139
+        this.props.dispatch(hideDialog());
140
+    }
141
+
142
+    /**
143
+     * Notifies this <tt>AbstractDialog</tt> that its submission has failed.
144
+     *
145
+     * @private
146
+     * @returns {void}
147
+     */
148
+    _onSubmitRejected() {
149
+        this._mounted && this.setState({ submitting: false });
150
+    }
72
 }
151
 }

+ 92
- 52
react/features/base/dialog/components/Dialog.native.js Прегледај датотеку

1
 import PropTypes from 'prop-types';
1
 import PropTypes from 'prop-types';
2
 import React from 'react';
2
 import React from 'react';
3
-import { TextInput } from 'react-native';
3
+import { StyleSheet, TextInput } from 'react-native';
4
 import Prompt from 'react-native-prompt';
4
 import Prompt from 'react-native-prompt';
5
 import { connect } from 'react-redux';
5
 import { connect } from 'react-redux';
6
 
6
 
7
 import { translate } from '../../i18n';
7
 import { translate } from '../../i18n';
8
+import { LoadingIndicator } from '../../react';
9
+import { set } from '../../redux';
8
 
10
 
9
 import AbstractDialog from './AbstractDialog';
11
 import AbstractDialog from './AbstractDialog';
12
+import styles from './styles';
13
+
14
+/**
15
+ * The value of the style property {@link _TAG_KEY} which identifies the
16
+ * OK/submit button of <tt>Prompt</tt>.
17
+ */
18
+const _SUBMIT_TEXT_TAG_VALUE = '_SUBMIT_TEXT_TAG_VALUE';
19
+
20
+/**
21
+ * The name of the style property which identifies ancestors of <tt>Prompt</tt>
22
+ * such as its OK/submit button for the purposes of workarounds implemented by
23
+ * <tt>Dialog</tt>.
24
+ *
25
+ * XXX The value may trigger a react-native warning in the Debug configuration
26
+ * but, unfortunately, I couldn't find a value that wouldn't.
27
+ */
28
+const _TAG_KEY = '_TAG_KEY';
10
 
29
 
11
 /**
30
 /**
12
  * Implements <tt>AbstractDialog</tt> on react-native using <tt>Prompt</tt>.
31
  * Implements <tt>AbstractDialog</tt> on react-native using <tt>Prompt</tt>.
37
             bodyKey,
56
             bodyKey,
38
             cancelDisabled,
57
             cancelDisabled,
39
             cancelTitleKey = 'dialog.Cancel',
58
             cancelTitleKey = 'dialog.Cancel',
40
-            children,
41
             okDisabled,
59
             okDisabled,
42
             okTitleKey = 'dialog.Ok',
60
             okTitleKey = 'dialog.Ok',
43
             t,
61
             t,
45
             titleString
63
             titleString
46
         } = this.props;
64
         } = this.props;
47
 
65
 
48
-        /* eslint-disable react/jsx-wrap-multilines */
49
-
50
-        let element
51
-            = <Prompt
52
-                cancelText = { cancelDisabled ? undefined : t(cancelTitleKey) }
66
+        const cancelButtonTextStyle
67
+            = cancelDisabled ? styles.disabledButtonText : styles.buttonText;
68
+        let submitButtonTextStyle
69
+            = okDisabled ? styles.disabledButtonText : styles.buttonText;
70
+
71
+        submitButtonTextStyle = {
72
+            ...submitButtonTextStyle,
73
+            [_TAG_KEY]: _SUBMIT_TEXT_TAG_VALUE
74
+        };
75
+
76
+        // eslint-disable-next-line no-extra-parens
77
+        let element = (
78
+            <Prompt
79
+                cancelButtonTextStyle = { cancelButtonTextStyle }
80
+                cancelText = { t(cancelTitleKey) }
53
                 onCancel = { this._onCancel }
81
                 onCancel = { this._onCancel }
54
                 onSubmit = { this._onSubmit }
82
                 onSubmit = { this._onSubmit }
55
                 placeholder = { t(bodyKey) }
83
                 placeholder = { t(bodyKey) }
56
-                submitText = { okDisabled ? undefined : t(okTitleKey) }
84
+                submitButtonTextStyle = { submitButtonTextStyle }
85
+                submitText = { t(okTitleKey) }
57
                 title = { titleString || t(titleKey) }
86
                 title = { titleString || t(titleKey) }
58
-                visible = { true } />;
59
-
60
-        /* eslint-enable react/jsx-wrap-multilines */
61
-
62
-        if (React.Children.count(children)) {
63
-            // XXX The following implements a workaround with knowledge of the
64
-            // implementation of react-native-prompt.
65
-            element
66
-                = this._replaceFirstElementOfType(
67
-                    // eslint-disable-next-line no-extra-parens, new-cap
68
-                    (new (element.type)(element.props)).render(),
69
-                    TextInput,
70
-                    children);
71
-        }
87
+                visible = { true } />
88
+        );
89
+
90
+        // XXX The following implements workarounds with knowledge of
91
+        // react-native-prompt/Prompt's implementation.
92
+
93
+        // eslint-disable-next-line no-extra-parens, new-cap
94
+        element = (new (element.type)(element.props)).render();
95
+
96
+        let { children } = this.props;
97
+
98
+        children = React.Children.count(children) ? children : undefined;
99
+
100
+        // eslint-disable-next-line no-shadow
101
+        element = this._mapReactElement(element, element => {
102
+            // * If this Dialog has children, they are to be rendered instead of
103
+            //   Prompt's TextInput.
104
+            if (element.type === TextInput) {
105
+                if (children) {
106
+                    element = children; // eslint-disable-line no-param-reassign
107
+                    children = undefined;
108
+                }
109
+            } else {
110
+                let { style } = element.props;
111
+
112
+                if (style
113
+                        && (style = StyleSheet.flatten(style))
114
+                        && _TAG_KEY in style) {
115
+                    switch (style[_TAG_KEY]) {
116
+                    case _SUBMIT_TEXT_TAG_VALUE:
117
+                        if (this.state.submitting) {
118
+                            // * If this Dialog is submitting, render a
119
+                            //   LoadingIndicator.
120
+                            return (
121
+                                <LoadingIndicator
122
+                                    color = { submitButtonTextStyle.color }
123
+                                    size = { 'small' } />
124
+                            );
125
+                        }
126
+                        break;
127
+                    }
128
+
129
+                    // eslint-disable-next-line no-param-reassign
130
+                    element
131
+                        = React.cloneElement(
132
+                            element,
133
+                            /* props */ {
134
+                                style: set(style, _TAG_KEY, undefined)
135
+                            },
136
+                            ...React.Children.toArray(element.props.children));
137
+                }
138
+            }
139
+
140
+            return element;
141
+        });
72
 
142
 
73
         return element;
143
         return element;
74
     }
144
     }
108
 
178
 
109
         return mapped;
179
         return mapped;
110
     }
180
     }
111
-
112
-    /**
113
-     * Replaces the first <tt>ReactElement</tt> of a specific type found in a
114
-     * specific <tt>ReactElement</tt> tree with a specific replacement
115
-     * <tt>ReactElement</tt>.
116
-     *
117
-     * @param {ReactElement} element - The <tt>ReactElement</tt> tree to search
118
-     * through and replace in.
119
-     * @param {*} type - The type of the <tt>ReactElement</tt> to be replaced.
120
-     * @param {ReactElement} replacement - The <tt>ReactElement</tt> to replace
121
-     * the first <tt>ReactElement</tt> in <tt>element</tt> of the specified
122
-     * <tt>type</tt>.
123
-     * @private
124
-     * @returns {ReactElement}
125
-     */
126
-    _replaceFirstElementOfType(element, type, replacement) {
127
-        // eslint-disable-next-line no-shadow
128
-        return this._mapReactElement(element, element => {
129
-            if (replacement && element.type === type) {
130
-                /* eslint-disable no-param-reassign */
131
-
132
-                element = replacement;
133
-                replacement = undefined;
134
-
135
-                /* eslint-enable no-param-reassign */
136
-            }
137
-
138
-            return element;
139
-        });
140
-    }
141
 }
181
 }
142
 
182
 
143
 export default translate(connect()(Dialog));
183
 export default translate(connect()(Dialog));

+ 21
- 0
react/features/base/dialog/components/styles.js Прегледај датотеку

1
+import { ColorPalette, createStyleSheet } from '../../styles';
2
+
3
+/**
4
+ * The React <tt>Component</tt> styles of the feature base/dialog.
5
+ */
6
+export default createStyleSheet({
7
+    /**
8
+     * The style of the <tt>Text</tt> in a <tt>Dialog</tt> button.
9
+     */
10
+    buttonText: {
11
+        color: ColorPalette.blue
12
+    },
13
+
14
+    /**
15
+     * The style of the <tt>Text</tt> in a <tt>Dialog</tt> button which is
16
+     * disabled.
17
+     */
18
+    disabledButtonText: {
19
+        color: ColorPalette.darkGrey
20
+    }
21
+});

Loading…
Откажи
Сачувај