Просмотр исходного кода

Fix raise hand toggled state

efficient_tiling
yanas 8 лет назад
Родитель
Сommit
d1737745c2

+ 4
- 0
flow-typed/npm/redux_v3.x.x.js Просмотреть файл

@@ -55,4 +55,8 @@ declare module 'redux' {
55 55
 
56 56
   declare function compose<S, A>(...fns: Array<StoreEnhancer<S, A>>): Function;
57 57
 
58
+  // Utility function in Redux that can be used for function composition
59
+  // e.g. bar(foo(baz)) is equivalent to compose(bar, foo)(baz).
60
+  declare function compose(...fns: Array<Function>): Function;
61
+
58 62
 }

+ 10
- 14
react/features/toolbox/actions.native.js Просмотреть файл

@@ -15,6 +15,13 @@ import {
15 15
     SET_TOOLBOX_VISIBLE
16 16
 } from './actionTypes';
17 17
 
18
+/**
19
+ * FIXME: We should make sure all common functions for native and web are
20
+ * separated in a global functions file, as well as all actions! Currently this
21
+ * file contains actions that are imported in actions.web.
22
+ */
23
+import { getButton } from './functions.web';
24
+
18 25
 /**
19 26
  * Event handler for local raise hand changed event.
20 27
  *
@@ -23,10 +30,8 @@ import {
23 30
  */
24 31
 export function changeLocalRaiseHand(handRaised: boolean): Function {
25 32
     return (dispatch: Dispatch<*>, getState: Function) => {
26
-        const state = getState();
27
-        const { secondaryToolbarButtons } = state['features/toolbox'];
28 33
         const buttonName = 'raisehand';
29
-        const button = secondaryToolbarButtons.get(buttonName);
34
+        const button = getButton(buttonName, getState());
30 35
 
31 36
         button.toggled = handRaised;
32 37
 
@@ -264,10 +269,8 @@ export function showEtherpadButton(): Function {
264 269
  */
265 270
 export function toggleFullScreen(isFullScreen: boolean): Function {
266 271
     return (dispatch: Dispatch<*>, getState: Function) => {
267
-        const state = getState();
268
-        const { primaryToolbarButtons } = state['features/toolbox'];
269 272
         const buttonName = 'fullscreen';
270
-        const button = primaryToolbarButtons.get(buttonName);
273
+        const button = getButton(buttonName, getState());
271 274
 
272 275
         button.toggled = isFullScreen;
273 276
 
@@ -283,14 +286,7 @@ export function toggleFullScreen(isFullScreen: boolean): Function {
283 286
  */
284 287
 export function toggleToolbarButton(buttonName: string): Function {
285 288
     return (dispatch: Dispatch, getState: Function) => {
286
-        const state = getState();
287
-        const {
288
-            primaryToolbarButtons,
289
-            secondaryToolbarButtons
290
-        } = state['features/toolbox'];
291
-        const button
292
-            = primaryToolbarButtons.get(buttonName)
293
-                || secondaryToolbarButtons.get(buttonName);
289
+        const button = getButton(buttonName, getState());
294 290
 
295 291
         dispatch(setToolbarButton(buttonName, {
296 292
             toggled: !button.toggled

+ 97
- 9
react/features/toolbox/actions.web.js Просмотреть файл

@@ -1,5 +1,7 @@
1 1
 /* @flow */
2 2
 
3
+import { compose } from 'redux';
4
+
3 5
 import Recording from '../../../modules/UI/recording/Recording';
4 6
 import SideContainerToggler
5 7
     from '../../../modules/UI/side_pannels/SideContainerToggler';
@@ -7,14 +9,17 @@ import UIEvents from '../../../service/UI/UIEvents';
7 9
 import UIUtil from '../../../modules/UI/util/UIUtil';
8 10
 
9 11
 import {
12
+    changeLocalRaiseHand,
10 13
     clearToolboxTimeout,
11 14
     setSubjectSlideIn,
12 15
     setToolbarButton,
13 16
     setToolboxTimeout,
14 17
     setToolboxTimeoutMS,
15 18
     setToolboxVisible,
19
+    toggleFullScreen,
16 20
     toggleToolbarButton
17 21
 } from './actions.native';
22
+
18 23
 import { SET_DEFAULT_TOOLBOX_BUTTONS } from './actionTypes';
19 24
 import { getDefaultToolboxButtons } from './functions';
20 25
 
@@ -74,6 +79,87 @@ export function dockToolbox(dock: boolean): Function {
74 79
     };
75 80
 }
76 81
 
82
+/**
83
+ * Returns button on mount/unmount handlers with dispatch function stored in
84
+ * closure.
85
+ *
86
+ * @param {Function} dispatch - Redux action dispatcher.
87
+ * @param {Function} getState - The function fetching the Redux state.
88
+ * @returns {Object} Button on mount/unmount handlers.
89
+ * @private
90
+ */
91
+function _getButtonHandlers(dispatch, getState) {
92
+    const { isGuest } = getState()['features/jwt'];
93
+
94
+    const localRaiseHandHandler = compose(dispatch, changeLocalRaiseHand);
95
+    const toggleFullScreenHandler = compose(dispatch, toggleFullScreen);
96
+
97
+    return {
98
+        /**
99
+         * Mount handler for desktop button.
100
+         *
101
+         * @type {Object}
102
+         */
103
+        desktop: {
104
+            onMount: () => dispatch(showDesktopSharingButton())
105
+        },
106
+
107
+        /**
108
+         * Mount/Unmount handler for toggling fullscreen button.
109
+         *
110
+         * @type {Object}
111
+         */
112
+        fullscreen: {
113
+            onMount: () =>
114
+                APP.UI.addListener(
115
+                    UIEvents.FULLSCREEN_TOGGLED,
116
+                    toggleFullScreenHandler),
117
+            onUnmount: () =>
118
+                APP.UI.removeListener(
119
+                    UIEvents.FULLSCREEN_TOGGLED,
120
+                    toggleFullScreenHandler)
121
+        },
122
+
123
+        /**
124
+         * Mount handler for profile button.
125
+         *
126
+         * @type {Object}
127
+         */
128
+        profile: {
129
+            onMount: () =>
130
+            isGuest
131
+            || dispatch(setProfileButtonUnclickable(true))
132
+        },
133
+
134
+        /**
135
+         * Mount/Unmount handlers for raisehand button.
136
+         *
137
+         * @type {button}
138
+         */
139
+        raisehand: {
140
+            onMount: () =>
141
+                APP.UI.addListener(
142
+                    UIEvents.LOCAL_RAISE_HAND_CHANGED,
143
+                    localRaiseHandHandler),
144
+            onUnmount: () =>
145
+                APP.UI.removeListener(
146
+                    UIEvents.LOCAL_RAISE_HAND_CHANGED,
147
+                    localRaiseHandHandler)
148
+        },
149
+
150
+        /**
151
+         * Mount handler for recording button.
152
+         *
153
+         * @type {Object}
154
+         */
155
+        recording: {
156
+            onMount: () =>
157
+            config.enableRecording
158
+            && dispatch(showRecordingButton())
159
+        }
160
+    };
161
+}
162
+
77 163
 /**
78 164
  * Hides the toolbox.
79 165
  *
@@ -114,16 +200,18 @@ export function hideToolbox(force: boolean = false): Function {
114 200
 /**
115 201
  * Sets the default toolbar buttons of the Toolbox.
116 202
  *
117
- * @returns {{
118
- *     type: SET_DEFAULT_TOOLBOX_BUTTONS,
119
- *     primaryToolbarButtons: Map,
120
- *     secondaryToolbarButtons: Map
121
- * }}
203
+ * @returns {Function}
122 204
  */
123
-export function setDefaultToolboxButtons(): Object {
124
-    return {
125
-        type: SET_DEFAULT_TOOLBOX_BUTTONS,
126
-        ...getDefaultToolboxButtons()
205
+export function setDefaultToolboxButtons(): Function {
206
+    return (dispatch: Dispatch, getState: Function) => {
207
+        // Save dispatch function in closure.
208
+        const buttonHandlers = _getButtonHandlers(dispatch, getState);
209
+        const toolboxButtons = getDefaultToolboxButtons(buttonHandlers);
210
+
211
+        dispatch({
212
+            type: SET_DEFAULT_TOOLBOX_BUTTONS,
213
+            ...toolboxButtons
214
+        });
127 215
     };
128 216
 }
129 217
 

+ 2
- 82
react/features/toolbox/components/PrimaryToolbar.web.js Просмотреть файл

@@ -3,13 +3,9 @@
3 3
 import React, { Component } from 'react';
4 4
 import { connect } from 'react-redux';
5 5
 
6
-import UIEvents from '../../../../service/UI/UIEvents';
7
-
8
-import { showDesktopSharingButton, toggleFullScreen } from '../actions';
9 6
 import { getToolbarClassNames } from '../functions';
10 7
 import Toolbar from './Toolbar';
11 8
 
12
-declare var APP: Object;
13 9
 declare var interfaceConfig: Object;
14 10
 
15 11
 /**
@@ -20,15 +16,6 @@ declare var interfaceConfig: Object;
20 16
  */
21 17
 class PrimaryToolbar extends Component {
22 18
     static propTypes = {
23
-        /**
24
-         * Handler for toggling fullscreen mode.
25
-         */
26
-        _onFullScreenToggled: React.PropTypes.func,
27
-
28
-        /**
29
-         * Handler for showing desktop sharing button.
30
-         */
31
-        _onShowDesktopSharingButton: React.PropTypes.func,
32 19
 
33 20
         /**
34 21
          * Contains toolbar buttons for primary toolbar.
@@ -51,43 +38,10 @@ class PrimaryToolbar extends Component {
51 38
     constructor(props) {
52 39
         super(props);
53 40
 
54
-        const buttonHandlers = {
55
-            /**
56
-             * Mount handler for desktop button.
57
-             *
58
-             * @type {Object}
59
-             */
60
-            desktop: {
61
-                onMount: () => this.props._onShowDesktopSharingButton()
62
-            },
63
-
64
-            /**
65
-             * Mount/Unmount handler for toggling fullscreen button.
66
-             *
67
-             * @type {Object}
68
-             */
69
-            fullscreen: {
70
-                onMount: () =>
71
-                    APP.UI.addListener(
72
-                        UIEvents.FULLSCREEN_TOGGLED,
73
-                        this.props._onFullScreenToggled),
74
-                onUnmount: () =>
75
-                    APP.UI.removeListener(
76
-                        UIEvents.FULLSCREEN_TOGGLED,
77
-                        this.props._onFullScreenToggled)
78
-            }
79
-        };
80 41
         const splitterIndex = interfaceConfig.MAIN_TOOLBAR_SPLITTER_INDEX;
81 42
 
82 43
         this.state = {
83 44
 
84
-            /**
85
-             * Object containing on mount/unmount handlers for toolbar buttons.
86
-             *
87
-             * @type {Object}
88
-             */
89
-            buttonHandlers,
90
-
91 45
             /**
92 46
              * If deployment supports toolbar splitter this value contains its
93 47
              * index.
@@ -113,14 +67,13 @@ class PrimaryToolbar extends Component {
113 67
             return null;
114 68
         }
115 69
 
116
-        const { buttonHandlers, splitterIndex } = this.state;
70
+        const { splitterIndex } = this.state;
117 71
         const { primaryToolbarClassName } = getToolbarClassNames(this.props);
118 72
         const tooltipPosition
119 73
             = interfaceConfig.filmStripOnly ? 'left' : 'bottom';
120 74
 
121 75
         return (
122 76
             <Toolbar
123
-                buttonHandlers = { buttonHandlers }
124 77
                 className = { primaryToolbarClassName }
125 78
                 splitterIndex = { splitterIndex }
126 79
                 toolbarButtons = { _primaryToolbarButtons }
@@ -129,39 +82,6 @@ class PrimaryToolbar extends Component {
129 82
     }
130 83
 }
131 84
 
132
-/**
133
- * Maps some of the Redux actions to the component props.
134
- *
135
- * @param {Function} dispatch - Redux action dispatcher.
136
- * @returns {{
137
- *     _onShowDesktopSharingButton: Function
138
- * }}
139
- * @private
140
- */
141
-function _mapDispatchToProps(dispatch: Function): Object {
142
-    return {
143
-        /**
144
-         * Dispatches an action signalling that full screen mode is toggled.
145
-         *
146
-         * @param {boolean} isFullScreen - Show whether fullscreen mode is on.
147
-         * @returns {Object} Dispatched action.
148
-         */
149
-        _onFullScreenToggled(isFullScreen: boolean) {
150
-            return dispatch(toggleFullScreen(isFullScreen));
151
-        },
152
-
153
-        /**
154
-         * Dispatches an action signalling that desktop sharing button
155
-         * should be shown.
156
-         *
157
-         * @returns {Object} Dispatched action.
158
-         */
159
-        _onShowDesktopSharingButton() {
160
-            dispatch(showDesktopSharingButton());
161
-        }
162
-    };
163
-}
164
-
165 85
 /**
166 86
  * Maps part of Redux store to React component props.
167 87
  *
@@ -197,4 +117,4 @@ function _mapStateToProps(state: Object): Object {
197 117
     };
198 118
 }
199 119
 
200
-export default connect(_mapStateToProps, _mapDispatchToProps)(PrimaryToolbar);
120
+export default connect(_mapStateToProps)(PrimaryToolbar);

+ 0
- 117
react/features/toolbox/components/SecondaryToolbar.web.js Просмотреть файл

@@ -7,16 +7,12 @@ import { FeedbackButton } from '../../feedback';
7 7
 import UIEvents from '../../../../service/UI/UIEvents';
8 8
 
9 9
 import {
10
-    changeLocalRaiseHand,
11
-    setProfileButtonUnclickable,
12
-    showRecordingButton,
13 10
     toggleSideToolbarContainer
14 11
 } from '../actions';
15 12
 import { getToolbarClassNames } from '../functions';
16 13
 import Toolbar from './Toolbar';
17 14
 
18 15
 declare var APP: Object;
19
-declare var config: Object;
20 16
 
21 17
 /**
22 18
  * Implementation of secondary toolbar React component.
@@ -39,21 +35,6 @@ class SecondaryToolbar extends Component {
39 35
          */
40 36
         _isGuest: React.PropTypes.bool,
41 37
 
42
-        /**
43
-         * Handler dispatching local "Raise hand".
44
-         */
45
-        _onLocalRaiseHandChanged: React.PropTypes.func,
46
-
47
-        /**
48
-         * Handler setting profile button unclickable.
49
-         */
50
-        _onSetProfileButtonUnclickable: React.PropTypes.func,
51
-
52
-        /**
53
-         * Handler for showing recording button.
54
-         */
55
-        _onShowRecordingButton: React.PropTypes.func,
56
-
57 38
         /**
58 39
          * Handler dispatching toggle toolbar container.
59 40
          */
@@ -70,69 +51,6 @@ class SecondaryToolbar extends Component {
70 51
         _visible: React.PropTypes.bool
71 52
     };
72 53
 
73
-    /**
74
-     * Constructs instance of SecondaryToolbar component.
75
-     *
76
-     * @param {Object} props - React component properties.
77
-     */
78
-    constructor(props) {
79
-        super(props);
80
-
81
-        const buttonHandlers = {
82
-            /**
83
-             * Mount handler for profile button.
84
-             *
85
-             * @type {Object}
86
-             */
87
-            profile: {
88
-                onMount: () => {
89
-                    const {
90
-                        _isGuest,
91
-                        _onSetProfileButtonUnclickable
92
-                    } = this.props;
93
-
94
-                    _isGuest || _onSetProfileButtonUnclickable(true);
95
-                }
96
-            },
97
-
98
-            /**
99
-             * Mount/Unmount handlers for raisehand button.
100
-             *
101
-             * @type {button}
102
-             */
103
-            raisehand: {
104
-                onMount: () =>
105
-                    APP.UI.addListener(
106
-                        UIEvents.LOCAL_RAISE_HAND_CHANGED,
107
-                        this.props._onLocalRaiseHandChanged),
108
-                onUnmount: () =>
109
-                    APP.UI.removeListener(
110
-                        UIEvents.LOCAL_RAISE_HAND_CHANGED,
111
-                        this.props._onLocalRaiseHandChanged)
112
-            },
113
-
114
-            /**
115
-             * Mount handler for recording button.
116
-             *
117
-             * @type {Object}
118
-             */
119
-            recording: {
120
-                onMount: () =>
121
-                    config.enableRecording
122
-                        && this.props._onShowRecordingButton()
123
-            }
124
-        };
125
-
126
-        this.state = {
127
-            /**
128
-             * Object containing on mount/unmount handlers for toolbar buttons.
129
-             *
130
-             * @type {Object}
131
-             */
132
-            buttonHandlers
133
-        };
134
-    }
135
-
136 54
     /**
137 55
      * Register legacy UI listener.
138 56
      *
@@ -170,12 +88,10 @@ class SecondaryToolbar extends Component {
170 88
             return null;
171 89
         }
172 90
 
173
-        const { buttonHandlers } = this.state;
174 91
         const { secondaryToolbarClassName } = getToolbarClassNames(this.props);
175 92
 
176 93
         return (
177 94
             <Toolbar
178
-                buttonHandlers = { buttonHandlers }
179 95
                 className = { secondaryToolbarClassName }
180 96
                 toolbarButtons = { _secondaryToolbarButtons }
181 97
                 tooltipPosition = { 'right' }>
@@ -190,45 +106,12 @@ class SecondaryToolbar extends Component {
190 106
  *
191 107
  * @param {Function} dispatch - Redux action dispatcher.
192 108
  * @returns {{
193
- *     _onLocalRaiseHandChanged: Function,
194
- *     _onSetProfileButtonUnclickable: Function,
195
- *     _onShowRecordingButton: Function,
196 109
  *     _onSideToolbarContainerToggled
197 110
  * }}
198 111
  * @private
199 112
  */
200 113
 function _mapDispatchToProps(dispatch: Function): Object {
201 114
     return {
202
-        /**
203
-         * Dispatches an action that 'hand' is raised.
204
-         *
205
-         * @param {boolean} isRaisedHand - Show whether hand is raised.
206
-         * @returns {Object} Dispatched action.
207
-         */
208
-        _onLocalRaiseHandChanged(isRaisedHand: boolean) {
209
-            return dispatch(changeLocalRaiseHand(isRaisedHand));
210
-        },
211
-
212
-        /**
213
-         * Dispatches an action signalling to set profile button unclickable.
214
-         *
215
-         * @param {boolean} unclickable - Flag showing whether unclickable
216
-         * property is true.
217
-         * @returns {Object} Dispatched action.
218
-         */
219
-        _onSetProfileButtonUnclickable(unclickable: boolean) {
220
-            return dispatch(setProfileButtonUnclickable(unclickable));
221
-        },
222
-
223
-        /**
224
-         * Dispatches an action signalling that recording button should be
225
-         * shown.
226
-         *
227
-         * @returns {Object} Dispatched action.
228
-         */
229
-        _onShowRecordingButton() {
230
-            return dispatch(showRecordingButton());
231
-        },
232 115
 
233 116
         /**
234 117
          * Dispatches an action signalling that side toolbar container is

+ 0
- 36
react/features/toolbox/components/Toolbar.web.js Просмотреть файл

@@ -36,11 +36,6 @@ class Toolbar extends Component {
36 36
          */
37 37
         _onMouseOver: React.PropTypes.func,
38 38
 
39
-        /**
40
-         * Contains button handlers.
41
-         */
42
-        buttonHandlers: React.PropTypes.object,
43
-
44 39
         /**
45 40
          * Children of current React component.
46 41
          */
@@ -77,8 +72,6 @@ class Toolbar extends Component {
77 72
     constructor(props) {
78 73
         super(props);
79 74
 
80
-        this._setButtonHandlers();
81
-
82 75
         // Bind callbacks to preverse this.
83 76
         this._renderToolbarButton = this._renderToolbarButton.bind(this);
84 77
     }
@@ -154,35 +147,6 @@ class Toolbar extends Component {
154 147
 
155 148
         return acc;
156 149
     }
157
-
158
-    /**
159
-     * Sets handlers for some of the buttons.
160
-     *
161
-     * @private
162
-     * @returns {void}
163
-     */
164
-    _setButtonHandlers(): void {
165
-        const {
166
-            buttonHandlers,
167
-            toolbarButtons
168
-        } = this.props;
169
-
170
-        // Only a few buttons have buttonHandlers defined, so it may be
171
-        // undefined or empty depending on the buttons rendered.
172
-        // TODO Merge the buttonHandlers and onClick properties and come up with
173
-        // a consistent event handling property.
174
-        buttonHandlers && Object.keys(buttonHandlers).forEach(key => {
175
-            let button = toolbarButtons.get(key);
176
-
177
-            if (button) {
178
-                button = {
179
-                    ...button,
180
-                    ...buttonHandlers[key]
181
-                };
182
-                toolbarButtons.set(key, button);
183
-            }
184
-        });
185
-    }
186 150
 }
187 151
 
188 152
 /**

+ 42
- 17
react/features/toolbox/functions.web.js Просмотреть файл

@@ -13,6 +13,21 @@ export { abstractMapStateToProps } from './functions.native';
13 13
 
14 14
 /* eslint-disable flowtype/space-before-type-colon */
15 15
 
16
+/**
17
+ * Returns the button object corresponding to the given buttonName.
18
+ *
19
+ * @param {string} buttonName - The name of the button.
20
+ * @param {Object} state - The current state.
21
+ * @returns {Object} - The button object.
22
+ */
23
+export function getButton(buttonName: string, state: Object) {
24
+    const { primaryToolbarButtons, secondaryToolbarButtons }
25
+        = state['features/toolbox'];
26
+
27
+    return primaryToolbarButtons.get(buttonName)
28
+        || secondaryToolbarButtons.get(buttonName);
29
+}
30
+
16 31
 /**
17 32
  * Takes toolbar button props and maps them to HTML attributes to set.
18 33
  *
@@ -52,9 +67,11 @@ export function getButtonAttributesByProps(props: Object = {})
52 67
  * Returns an object which contains the default buttons for the primary and
53 68
  * secondary toolbars.
54 69
  *
70
+ * @param {Object} buttonHandlers - Contains additional toolbox button
71
+ * handlers.
55 72
  * @returns {Object}
56 73
  */
57
-export function getDefaultToolboxButtons(): Object {
74
+export function getDefaultToolboxButtons(buttonHandlers: Object): Object {
58 75
     let toolbarButtons = {
59 76
         primaryToolbarButtons: new Map(),
60 77
         secondaryToolbarButtons: new Map()
@@ -67,13 +84,21 @@ export function getDefaultToolboxButtons(): Object {
67 84
         toolbarButtons
68 85
             = interfaceConfig.TOOLBAR_BUTTONS.reduce(
69 86
                 (acc, buttonName) => {
70
-                    const button = defaultToolbarButtons[buttonName];
87
+                    let button = defaultToolbarButtons[buttonName];
88
+                    const currentButtonHandlers = buttonHandlers[buttonName];
71 89
 
72 90
                     if (button) {
73 91
                         const place = _getToolbarButtonPlace(buttonName);
74 92
 
75 93
                         button.buttonName = buttonName;
76 94
 
95
+                        if (currentButtonHandlers) {
96
+                            button = {
97
+                                ...button,
98
+                                ...currentButtonHandlers
99
+                            };
100
+                        }
101
+
77 102
                         // In filmstrip-only mode we only add a button if it's
78 103
                         // filmstrip-only enabled.
79 104
                         if (!filmStripOnly || button.filmstripOnlyEnabled) {
@@ -89,21 +114,6 @@ export function getDefaultToolboxButtons(): Object {
89 114
     return toolbarButtons;
90 115
 }
91 116
 
92
-/**
93
- * Get place for toolbar button. Now it can be in the primary Toolbar or in the
94
- * secondary Toolbar.
95
- *
96
- * @param {string} btn - Button name.
97
- * @private
98
- * @returns {string}
99
- */
100
-function _getToolbarButtonPlace(btn) {
101
-    return (
102
-        interfaceConfig.MAIN_TOOLBAR_BUTTONS.includes(btn)
103
-            ? 'primaryToolbarButtons'
104
-            : 'secondaryToolbarButtons');
105
-}
106
-
107 117
 /**
108 118
  * Returns toolbar class names to add while rendering.
109 119
  *
@@ -171,3 +181,18 @@ export function showCustomToolbarPopup(
171 181
         AJS.$(popupSelectorID).tooltip('hide');
172 182
     }
173 183
 }
184
+
185
+/**
186
+ * Get place for toolbar button. Now it can be in the primary Toolbar or in the
187
+ * secondary Toolbar.
188
+ *
189
+ * @param {string} btn - Button name.
190
+ * @private
191
+ * @returns {string}
192
+ */
193
+function _getToolbarButtonPlace(btn) {
194
+    return (
195
+        interfaceConfig.MAIN_TOOLBAR_BUTTONS.includes(btn)
196
+            ? 'primaryToolbarButtons'
197
+            : 'secondaryToolbarButtons');
198
+}

Загрузка…
Отмена
Сохранить