(external_api) Add command for overwriting config values.

This commit is contained in:
hmuresan 2021-03-10 17:39:35 +02:00 committed by Horatiu Muresan
parent 2e308d67d8
commit ab6790bdaa
19 changed files with 151 additions and 57 deletions

View File

@ -414,6 +414,25 @@ var config = {
// Base URL for a Gravatar-compatible service. Defaults to libravatar. // Base URL for a Gravatar-compatible service. Defaults to libravatar.
// gravatarBaseURL: 'https://seccdn.libravatar.org/avatar/', // gravatarBaseURL: 'https://seccdn.libravatar.org/avatar/',
// Moved from interfaceConfig(TOOLBAR_BUTTONS).
// The name of the toolbar buttons to display in the toolbar, including the
// "More actions" menu. If present, the button will display. Exceptions are
// "livestreaming" and "recording" which also require being a moderator and
// some other values in config.js to be enabled. Also, the "profile" button will
// not display for users with a JWT.
// Notes:
// - it's impossible to choose which buttons go in the "More actions" menu
// - it's impossible to control the placement of buttons
// - 'desktop' controls the "Share your screen" button
// - if `toolbarButtons` is undefined, we fallback to enabling all buttons on the UI
// toolbarButtons: [
// 'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
// 'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
// 'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
// 'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
// 'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
// ],
// Stats // Stats
// //

View File

@ -198,23 +198,16 @@ var interfaceConfig = {
TOOLBAR_ALWAYS_VISIBLE: false, TOOLBAR_ALWAYS_VISIBLE: false,
/** /**
* The name of the toolbar buttons to display in the toolbar, including the * DEPRECATED!
* "More actions" menu. If present, the button will display. Exceptions are * This config was moved to config.js as `toolbarButtons`.
* "livestreaming" and "recording" which also require being a moderator and
* some values in config.js to be enabled. Also, the "profile" button will
* not display for users with a JWT.
* Notes:
* - it's impossible to choose which buttons go in the "More actions" menu
* - it's impossible to control the placement of buttons
* - 'desktop' controls the "Share your screen" button
*/ */
TOOLBAR_BUTTONS: [ // TOOLBAR_BUTTONS: [
'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen', // 'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording', // 'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand', // 'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts', // 'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security' // 'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
], // ],
TOOLBAR_TIMEOUT: 4000, TOOLBAR_TIMEOUT: 4000,

View File

@ -12,6 +12,7 @@ import {
setPassword, setPassword,
setSubject setSubject
} from '../../react/features/base/conference'; } from '../../react/features/base/conference';
import { overwriteConfig, getWhitelistedJSON } from '../../react/features/base/config';
import { parseJWTFromURLParams } from '../../react/features/base/jwt'; import { parseJWTFromURLParams } from '../../react/features/base/jwt';
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet'; import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../../react/features/base/media'; import { MEDIA_TYPE } from '../../react/features/base/media';
@ -356,6 +357,11 @@ function initCommands() {
}, },
'kick-participant': participantId => { 'kick-participant': participantId => {
APP.store.dispatch(kickParticipant(participantId)); APP.store.dispatch(kickParticipant(participantId));
},
'overwrite-config': config => {
const whitelistedConfig = getWhitelistedJSON('config', config);
APP.store.dispatch(overwriteConfig(whitelistedConfig));
} }
}; };
transport.on('event', ({ data, name }) => { transport.on('event', ({ data, name }) => {

View File

@ -37,6 +37,7 @@ const commands = {
intiatePrivateChat: 'initiate-private-chat', intiatePrivateChat: 'initiate-private-chat',
kickParticipant: 'kick-participant', kickParticipant: 'kick-participant',
muteEveryone: 'mute-everyone', muteEveryone: 'mute-everyone',
overwriteConfig: 'overwrite-config',
password: 'password', password: 'password',
pinParticipant: 'pin-participant', pinParticipant: 'pin-participant',
resizeLargeVideo: 'resize-large-video', resizeLargeVideo: 'resize-large-video',

View File

@ -48,3 +48,14 @@ export const SET_CONFIG = 'SET_CONFIG';
* } * }
*/ */
export const UPDATE_CONFIG = 'UPDATE_CONFIG'; export const UPDATE_CONFIG = 'UPDATE_CONFIG';
/**
* The redux action which overwrites configurations represented by the feature
* base/config. The passed on config values overwrite the current values for given props.
*
* {
* type: OVERWRITE_CONFIG,
* config: Object
* }
*/
export const OVERWRITE_CONFIG = 'OVERWRITE_CONFIG';

View File

@ -6,7 +6,13 @@ import type { Dispatch } from 'redux';
import { addKnownDomains } from '../known-domains'; import { addKnownDomains } from '../known-domains';
import { parseURIString } from '../util'; import { parseURIString } from '../util';
import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG, UPDATE_CONFIG } from './actionTypes'; import {
CONFIG_WILL_LOAD,
LOAD_CONFIG_ERROR,
SET_CONFIG,
UPDATE_CONFIG,
OVERWRITE_CONFIG
} from './actionTypes';
import { _CONFIG_STORE_PREFIX } from './constants'; import { _CONFIG_STORE_PREFIX } from './constants';
import { setConfigFromURLParams } from './functions'; import { setConfigFromURLParams } from './functions';
@ -67,6 +73,22 @@ export function loadConfigError(error: Error, locationURL: URL) {
}; };
} }
/**
* Overwrites some config values.
*
* @param {Object} config - The new options (to overwrite).
* @returns {{
* type: OVERWRITE_CONFIG,
* config: Object
* }}
*/
export function overwriteConfig(config: Object) {
return {
type: OVERWRITE_CONFIG,
config
};
}
/** /**
* Sets the configuration represented by the feature base/config. The * Sets the configuration represented by the feature base/config. The
* configuration is defined and consumed by the library lib-jitsi-meet but some * configuration is defined and consumed by the library lib-jitsi-meet but some

View File

@ -157,6 +157,7 @@ export default [
'stereo', 'stereo',
'subject', 'subject',
'testing', 'testing',
'toolbarButtons',
'useHostPageLocalStorage', 'useHostPageLocalStorage',
'useTurnUdp', 'useTurnUdp',
'videoQuality.persist', 'videoQuality.persist',

View File

@ -6,3 +6,17 @@
* @type string * @type string
*/ */
export const _CONFIG_STORE_PREFIX = 'config.js'; export const _CONFIG_STORE_PREFIX = 'config.js';
/**
* The list of all possible UI buttons.
*
* @protected
* @type Array<string>
*/
export const TOOLBAR_BUTTONS = [
'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
];

View File

@ -82,7 +82,7 @@ export function overrideConfigJSON(
} }
if (configObj) { if (configObj) {
const configJSON const configJSON
= _getWhitelistedJSON(configName, json[configName]); = getWhitelistedJSON(configName, json[configName]);
if (!_.isEmpty(configJSON)) { if (!_.isEmpty(configJSON)) {
logger.info( logger.info(
@ -111,11 +111,10 @@ export function overrideConfigJSON(
* @param {string} configName - The config name, one of config, * @param {string} configName - The config name, one of config,
* interfaceConfig, loggingConfig. * interfaceConfig, loggingConfig.
* @param {Object} configJSON - The object with keys and values to override. * @param {Object} configJSON - The object with keys and values to override.
* @private
* @returns {Object} - The result object only with the keys * @returns {Object} - The result object only with the keys
* that are whitelisted. * that are whitelisted.
*/ */
function _getWhitelistedJSON(configName, configJSON) { export function getWhitelistedJSON(configName: string, configJSON: Object): Object {
if (configName === 'interfaceConfig') { if (configName === 'interfaceConfig') {
return _.pick(configJSON, INTERFACE_CONFIG_WHITELIST); return _.pick(configJSON, INTERFACE_CONFIG_WHITELIST);
} else if (configName === 'config') { } else if (configName === 'config') {

View File

@ -1,5 +1,7 @@
// @flow // @flow
import { TOOLBAR_BUTTONS } from './constants';
export * from './functions.any'; export * from './functions.any';
/** /**
@ -30,3 +32,15 @@ export function getDialOutStatusUrl(state: Object): string {
export function getDialOutUrl(state: Object): string { export function getDialOutUrl(state: Object): string {
return state['features/base/config'].guestDialOutUrl; return state['features/base/config'].guestDialOutUrl;
} }
/**
* Returns the list of enabled toolbar buttons.
*
* @param {Object} state - The redux state.
* @returns {Array<string>} - The list of enabled toolbar buttons.
*/
export function getToolbarButtons(state: Object): Array<string> {
const { toolbarButtons } = state['features/base/config'];
return Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
}

View File

@ -4,9 +4,17 @@ import _ from 'lodash';
import { equals, ReducerRegistry, set } from '../redux'; import { equals, ReducerRegistry, set } from '../redux';
import { UPDATE_CONFIG, CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes'; import {
UPDATE_CONFIG,
CONFIG_WILL_LOAD,
LOAD_CONFIG_ERROR,
SET_CONFIG,
OVERWRITE_CONFIG
} from './actionTypes';
import { _cleanupConfig } from './functions'; import { _cleanupConfig } from './functions';
declare var interfaceConfig: Object;
/** /**
* The initial state of the feature base/config when executing in a * The initial state of the feature base/config when executing in a
* non-React Native environment. The mandatory configuration to be passed to * non-React Native environment. The mandatory configuration to be passed to
@ -88,6 +96,12 @@ ReducerRegistry.register('features/base/config', (state = _getInitialState(), ac
case SET_CONFIG: case SET_CONFIG:
return _setConfig(state, action); return _setConfig(state, action);
case OVERWRITE_CONFIG:
return {
...state,
...action.config
};
} }
return state; return state;
@ -197,6 +211,10 @@ function _translateLegacyConfig(oldValue: Object) {
} }
}); });
if (typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) {
newValue.toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS;
}
return newValue; return newValue;
} }

View File

@ -74,7 +74,7 @@ function mapStateToProps(state) {
const hide = interfaceConfig.HIDE_INVITE_MORE_HEADER; const hide = interfaceConfig.HIDE_INVITE_MORE_HEADER;
return { return {
_visible: isToolboxVisible(state) && isButtonEnabled('invite') && isAlone && !hide _visible: isToolboxVisible(state) && isButtonEnabled('invite', state) && isAlone && !hide
}; };
} }

View File

@ -8,6 +8,7 @@ import {
createToolbarEvent, createToolbarEvent,
sendAnalytics sendAnalytics
} from '../../../analytics'; } from '../../../analytics';
import { getToolbarButtons } from '../../../base/config';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { Icon, IconMenuDown, IconMenuUp } from '../../../base/icons'; import { Icon, IconMenuDown, IconMenuUp } from '../../../base/icons';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
@ -54,6 +55,11 @@ type Props = {
*/ */
_hideToolbar: boolean, _hideToolbar: boolean,
/**
* Whether the filmstrip button is enabled.
*/
_isFilmstripButtonEnabled: boolean,
/** /**
* The number of rows in tile view. * The number of rows in tile view.
*/ */
@ -170,7 +176,7 @@ class Filmstrip extends Component <Props> {
let toolbar = null; let toolbar = null;
if (!this.props._hideToolbar && isButtonEnabled('filmstrip')) { if (!this.props._hideToolbar && this.props._isFilmstripButtonEnabled) {
toolbar = this._renderToggleButton(); toolbar = this._renderToggleButton();
} }
@ -288,9 +294,10 @@ class Filmstrip extends Component <Props> {
*/ */
function _mapStateToProps(state) { function _mapStateToProps(state) {
const { iAmSipGateway } = state['features/base/config']; const { iAmSipGateway } = state['features/base/config'];
const toolbarButtons = getToolbarButtons(state);
const { visible } = state['features/filmstrip']; const { visible } = state['features/filmstrip'];
const reduceHeight const reduceHeight
= state['features/toolbox'].visible && interfaceConfig.TOOLBAR_BUTTONS.length; = state['features/toolbox'].visible && toolbarButtons.length;
const remoteVideosVisible = shouldRemoteVideosBeVisible(state); const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
const { isOpen: shiftRight } = state['features/chat']; const { isOpen: shiftRight } = state['features/chat'];
const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${ const className = `${remoteVideosVisible ? '' : 'hide-videos'} ${
@ -306,6 +313,7 @@ function _mapStateToProps(state) {
_filmstripWidth: filmstripWidth, _filmstripWidth: filmstripWidth,
_hideScrollbar: Boolean(iAmSipGateway), _hideScrollbar: Boolean(iAmSipGateway),
_hideToolbar: Boolean(iAmSipGateway), _hideToolbar: Boolean(iAmSipGateway),
_isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state),
_rows: gridDimensions.rows, _rows: gridDimensions.rows,
_videosClassName: videosClassName, _videosClassName: videosClassName,
_visible: visible _visible: visible

View File

@ -420,7 +420,7 @@ function mapStateToProps(state, ownProps): Object {
const name = getDisplayName(state); const name = getDisplayName(state);
const showErrorOnJoin = isDisplayNameRequired(state) && !name; const showErrorOnJoin = isDisplayNameRequired(state) && !name;
const { showJoinActions } = ownProps; const { showJoinActions } = ownProps;
const isInviteButtonEnabled = isButtonEnabled('invite'); const isInviteButtonEnabled = isButtonEnabled('invite', state);
// Hide conference info when interfaceConfig is available and the invite button is disabled. // Hide conference info when interfaceConfig is available and the invite button is disabled.
// In all other cases we want to preserve the behaviour and control the the conference info // In all other cases we want to preserve the behaviour and control the the conference info

View File

@ -1,5 +1,6 @@
// @flow // @flow
import { getToolbarButtons } from '../../../../base/config';
import { translate } from '../../../../base/i18n'; import { translate } from '../../../../base/i18n';
import { connect } from '../../../../base/redux'; import { connect } from '../../../../base/redux';
import AbstractLiveStreamButton, { import AbstractLiveStreamButton, {
@ -7,8 +8,6 @@ import AbstractLiveStreamButton, {
type Props type Props
} from '../AbstractLiveStreamButton'; } from '../AbstractLiveStreamButton';
declare var interfaceConfig: Object;
/** /**
* Maps (parts of) the redux state to the associated props for the * Maps (parts of) the redux state to the associated props for the
* {@code LiveStreamButton} component. * {@code LiveStreamButton} component.
@ -25,10 +24,11 @@ declare var interfaceConfig: Object;
*/ */
function _mapStateToProps(state: Object, ownProps: Props) { function _mapStateToProps(state: Object, ownProps: Props) {
const abstractProps = _abstractMapStateToProps(state, ownProps); const abstractProps = _abstractMapStateToProps(state, ownProps);
const toolbarButtons = getToolbarButtons(state);
let { visible } = ownProps; let { visible } = ownProps;
if (typeof visible === 'undefined') { if (typeof visible === 'undefined') {
visible = interfaceConfig.TOOLBAR_BUTTONS.includes('livestreaming') && abstractProps.visible; visible = toolbarButtons.includes('livestreaming') && abstractProps.visible;
} }
return { return {

View File

@ -1,5 +1,6 @@
// @flow // @flow
import { getToolbarButtons } from '../../../../base/config';
import { translate } from '../../../../base/i18n'; import { translate } from '../../../../base/i18n';
import { connect } from '../../../../base/redux'; import { connect } from '../../../../base/redux';
import AbstractRecordButton, { import AbstractRecordButton, {
@ -7,8 +8,6 @@ import AbstractRecordButton, {
type Props type Props
} from '../AbstractRecordButton'; } from '../AbstractRecordButton';
declare var interfaceConfig: Object;
/** /**
* Maps (parts of) the redux state to the associated props for the * Maps (parts of) the redux state to the associated props for the
* {@code RecordButton} component. * {@code RecordButton} component.
@ -25,10 +24,11 @@ declare var interfaceConfig: Object;
*/ */
export function _mapStateToProps(state: Object, ownProps: Props): Object { export function _mapStateToProps(state: Object, ownProps: Props): Object {
const abstractProps = _abstractMapStateToProps(state, ownProps); const abstractProps = _abstractMapStateToProps(state, ownProps);
const toolbarButtons = getToolbarButtons(state);
let { visible } = ownProps; let { visible } = ownProps;
if (typeof visible === 'undefined') { if (typeof visible === 'undefined') {
visible = interfaceConfig.TOOLBAR_BUTTONS.includes('recording') && abstractProps.visible; visible = toolbarButtons.includes('recording') && abstractProps.visible;
} }
return { return {

View File

@ -89,7 +89,7 @@ function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props> {
return { return {
..._abstractMapStateToProps(state, ownProps), ..._abstractMapStateToProps(state, ownProps),
_hidden: typeof interfaceConfig !== 'undefined' _hidden: typeof interfaceConfig !== 'undefined'
&& (interfaceConfig.DISABLE_PRIVATE_MESSAGES || !isButtonEnabled('chat')) && (interfaceConfig.DISABLE_PRIVATE_MESSAGES || !isButtonEnabled('chat', state))
}; };
} }

View File

@ -8,6 +8,7 @@ import {
createToolbarEvent, createToolbarEvent,
sendAnalytics sendAnalytics
} from '../../../analytics'; } from '../../../analytics';
import { getToolbarButtons } from '../../../base/config';
import { openDialog, toggleDialog } from '../../../base/dialog'; import { openDialog, toggleDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { import {
@ -29,7 +30,7 @@ import {
getParticipants, getParticipants,
participantUpdated participantUpdated
} from '../../../base/participants'; } from '../../../base/participants';
import { connect, equals } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { OverflowMenuItem } from '../../../base/toolbox/components'; import { OverflowMenuItem } from '../../../base/toolbox/components';
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks'; import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
import { isVpaasMeeting } from '../../../billing-counter/functions'; import { isVpaasMeeting } from '../../../billing-counter/functions';
@ -183,9 +184,9 @@ type Props = {
_visible: boolean, _visible: boolean,
/** /**
* Set with the buttons which this Toolbox should display. * Array with the buttons which this Toolbox should display.
*/ */
_visibleButtons: Set<string>, _visibleButtons: Array<string>,
/** /**
* Invoked to active other features of the app. * Invoked to active other features of the app.
@ -210,11 +211,6 @@ type State = {
}; };
declare var APP: Object; declare var APP: Object;
declare var interfaceConfig: Object;
// XXX: We are not currently using state here, but in the future, when
// interfaceConfig is part of redux we will. This will have to be retrieved from the store.
const visibleButtons = new Set(interfaceConfig.TOOLBAR_BUTTONS);
/** /**
* Implements the conference toolbox on React/Web. * Implements the conference toolbox on React/Web.
@ -360,7 +356,7 @@ class Toolbox extends Component<Props, State> {
render() { render() {
const { _chatOpen, _visible, _visibleButtons } = this.props; const { _chatOpen, _visible, _visibleButtons } = this.props;
const rootClassNames = `new-toolbox ${_visible ? 'visible' : ''} ${ const rootClassNames = `new-toolbox ${_visible ? 'visible' : ''} ${
_visibleButtons.size ? '' : 'no-buttons'} ${_chatOpen ? 'shift-right' : ''}`; _visibleButtons.length ? '' : 'no-buttons'} ${_chatOpen ? 'shift-right' : ''}`;
return ( return (
<div <div
@ -1280,7 +1276,7 @@ class Toolbox extends Component<Props, State> {
* @returns {boolean} True if the button should be displayed. * @returns {boolean} True if the button should be displayed.
*/ */
_shouldShowButton(buttonName) { _shouldShowButton(buttonName) {
return this.props._visibleButtons.has(buttonName); return this.props._visibleButtons.includes(buttonName);
} }
} }
@ -1318,10 +1314,6 @@ function _mapStateToProps(state) {
desktopSharingDisabledTooltipKey = 'dialog.shareYourScreenDisabled'; desktopSharingDisabledTooltipKey = 'dialog.shareYourScreenDisabled';
} }
// NB: We compute the buttons again here because if URL parameters were used to
// override them we'd miss it.
const buttons = new Set(interfaceConfig.TOOLBAR_BUTTONS);
return { return {
_chatOpen: state['features/chat'].isOpen, _chatOpen: state['features/chat'].isOpen,
_conference: conference, _conference: conference,
@ -1340,7 +1332,7 @@ function _mapStateToProps(state) {
_raisedHand: localParticipant.raisedHand, _raisedHand: localParticipant.raisedHand,
_screensharing: localVideo && localVideo.videoType === 'desktop', _screensharing: localVideo && localVideo.videoType === 'desktop',
_visible: isToolboxVisible(state), _visible: isToolboxVisible(state),
_visibleButtons: equals(visibleButtons, buttons) ? visibleButtons : buttons _visibleButtons: getToolbarButtons(state)
}; };
} }

View File

@ -1,9 +1,8 @@
// @flow // @flow
import { getToolbarButtons } from '../base/config';
import { hasAvailableDevices } from '../base/devices'; import { hasAvailableDevices } from '../base/devices';
declare var interfaceConfig: Object;
const WIDTH = { const WIDTH = {
MEDIUM: 500, MEDIUM: 500,
SMALL: 390, SMALL: 390,
@ -53,19 +52,16 @@ export function getToolboxHeight() {
* *
* @param {string} name - The name of the setting section as defined in * @param {string} name - The name of the setting section as defined in
* interface_config.js. * interface_config.js.
* @param {Object} state - The redux state.
* @returns {boolean|undefined} - True to indicate that the given toolbar button * @returns {boolean|undefined} - True to indicate that the given toolbar button
* is enabled, false - otherwise. In cases where interfaceConfig is not available * is enabled, false - otherwise.
* undefined is returned.
*/ */
export function isButtonEnabled(name: string) { export function isButtonEnabled(name: string, state: Object) {
if (typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) { const toolbarButtons = getToolbarButtons(state);
return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1;
}
return undefined; return toolbarButtons.indexOf(name) !== -1;
} }
/** /**
* Indicates if the toolbox is visible or not. * Indicates if the toolbox is visible or not.
* *