mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Rework text field styling and some layouts
* Change the default text fields to use filled version * Text fields embedded in InlinePopover or MiniToolbar keep their previous appearance with a single line. * Add Layout.js with some predefined layouts * Improved EventsFunctionPropertiesEditor fields
This commit is contained in:
@@ -41,6 +41,15 @@ const styles = {
|
||||
};
|
||||
|
||||
const validateParameterName = (i18n: I18nType, newName: string) => {
|
||||
if (!newName) {
|
||||
showWarningBox(
|
||||
i18n._(
|
||||
t`The name of a parameter can not be empty. Enter a name for the parameter or you won't be able to use it.`
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gd.Project.validateObjectName(newName)) {
|
||||
showWarningBox(
|
||||
i18n._(
|
||||
@@ -131,7 +140,7 @@ export default class EventsFunctionParametersEditor extends React.Component<
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
margin="none"
|
||||
hintText={t`Enter the parameter name`}
|
||||
hintText={t`Enter the parameter name (mandatory)`}
|
||||
value={parameter.getName()}
|
||||
onChange={text => {
|
||||
if (!validateParameterName(i18n, text)) return;
|
||||
@@ -141,13 +150,6 @@ export default class EventsFunctionParametersEditor extends React.Component<
|
||||
this.props.onParametersUpdated();
|
||||
}}
|
||||
disabled={isParameterDisabled(i)}
|
||||
errorText={
|
||||
parameter.getName() ? null : (
|
||||
<Trans>
|
||||
Name of the parameter is mandatory.
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</Column>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
@@ -13,6 +14,7 @@ import SemiControlledTextField from '../../UI/SemiControlledTextField';
|
||||
import { isBehaviorLifecycleFunction } from '../../EventsFunctionsExtensionsLoader/MetadataDeclarationHelpers';
|
||||
import EmptyMessage from '../../UI/EmptyMessage';
|
||||
import { getParametersIndexOffset } from '../../EventsFunctionsExtensionsLoader';
|
||||
import { type MessageDescriptor } from '../../Utils/i18n/MessageDescriptor.flow';
|
||||
|
||||
const gd = global.gd;
|
||||
|
||||
@@ -72,6 +74,30 @@ const getSentenceErrorText = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getFullNameHintText = (type: any): MessageDescriptor => {
|
||||
if (type === gd.EventsFunction.Condition) {
|
||||
return t`Example: Is flashing?`;
|
||||
} else if (type === gd.EventsFunction.Expression) {
|
||||
return t`Example: Life remaining`;
|
||||
} else if (type === gd.EventsFunction.StringExpression) {
|
||||
return t`Example: Equipped shield name`;
|
||||
}
|
||||
|
||||
return t`Example: Flash the object`;
|
||||
};
|
||||
|
||||
const getDescriptionHintText = (type: any): MessageDescriptor => {
|
||||
if (type === gd.EventsFunction.Condition) {
|
||||
return t`Example: Check if the object is flashing.`;
|
||||
} else if (type === gd.EventsFunction.Expression) {
|
||||
return t`Example: Life remaining for the player.`;
|
||||
} else if (type === gd.EventsFunction.StringExpression) {
|
||||
return t`Example: Name of the shield equipped by the player.`;
|
||||
}
|
||||
|
||||
return t`Example: Make the object flash for 5 seconds.`;
|
||||
};
|
||||
|
||||
export default class EventsFunctionPropertiesEditor extends React.Component<
|
||||
Props,
|
||||
State
|
||||
@@ -109,6 +135,7 @@ export default class EventsFunctionPropertiesEditor extends React.Component<
|
||||
<Column expand>
|
||||
<SelectField
|
||||
value={type}
|
||||
floatingLabelText={<Trans>Function type</Trans>}
|
||||
fullWidth
|
||||
disabled={!!freezeEventsFunctionType}
|
||||
onChange={(e, i, value: string) => {
|
||||
@@ -138,7 +165,10 @@ export default class EventsFunctionPropertiesEditor extends React.Component<
|
||||
<Column expand>
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
hintText={t`Full name displayed in editor`}
|
||||
floatingLabelText={
|
||||
<Trans>Full name displayed in editor</Trans>
|
||||
}
|
||||
hintText={getFullNameHintText(type)}
|
||||
value={eventsFunction.getFullName()}
|
||||
onChange={text => {
|
||||
eventsFunction.setFullName(text);
|
||||
@@ -152,7 +182,10 @@ export default class EventsFunctionPropertiesEditor extends React.Component<
|
||||
<Line noMargin>
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
hintText={t`Description, displayed in editor`}
|
||||
floatingLabelText={
|
||||
<Trans>Description, displayed in editor</Trans>
|
||||
}
|
||||
hintText={getDescriptionHintText(type)}
|
||||
fullWidth
|
||||
multiLine
|
||||
value={eventsFunction.getDescription()}
|
||||
@@ -168,7 +201,8 @@ export default class EventsFunctionPropertiesEditor extends React.Component<
|
||||
type === gd.EventsFunction.Condition ? (
|
||||
<SemiControlledTextField
|
||||
commitOnBlur
|
||||
hintText={t`Sentence in Events Sheet (write _PARAMx_ for parameters, e.g: _PARAM1_)`}
|
||||
floatingLabelText={<Trans>Sentence in Events Sheet</Trans>}
|
||||
hintText={t`Note: write _PARAMx_ for parameters, e.g: Flash _PARAM1_ for 5 seconds`}
|
||||
fullWidth
|
||||
value={eventsFunction.getSentence()}
|
||||
onChange={text => {
|
||||
|
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
import Popper from '@material-ui/core/Popper';
|
||||
import Background from '../UI/Background';
|
||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
|
||||
import { Column } from '../UI/Grid';
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
|
||||
const styles = {
|
||||
popover: {
|
||||
@@ -20,9 +20,6 @@ const styles = {
|
||||
// then. Only one InlinePopover should be shown at a time anyway.
|
||||
zIndex: 2,
|
||||
},
|
||||
contentContainer: {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
};
|
||||
|
||||
export default class InlinePopover extends Component {
|
||||
@@ -36,8 +33,10 @@ export default class InlinePopover extends Component {
|
||||
placement="bottom"
|
||||
>
|
||||
<Background>
|
||||
<Column>
|
||||
<div style={styles.contentContainer}>{this.props.children}</div>
|
||||
<Column expand>
|
||||
<Line>
|
||||
{this.props.children}
|
||||
</Line>
|
||||
</Column>
|
||||
</Background>
|
||||
</Popper>
|
||||
|
@@ -20,18 +20,9 @@ import BackgroundHighlighting, {
|
||||
import debounce from 'lodash/debounce';
|
||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import { TextFieldWithButtonLayout } from '../../../UI/Layout';
|
||||
const gd = global.gd;
|
||||
|
||||
export const textFieldRightButtonMargins = {
|
||||
marginTop: 17, //Properly align with the text field
|
||||
marginLeft: 10,
|
||||
};
|
||||
|
||||
export const textFieldWithLabelRightButtonMargins = {
|
||||
marginTop: 33, //Properly align with the text field
|
||||
marginLeft: 10,
|
||||
};
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
display: 'flex',
|
||||
@@ -53,10 +44,14 @@ const styles = {
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
backgroundHighlighting: {
|
||||
marginTop: 22, //Properly align with the text field
|
||||
marginTop: 13, //Properly align with the text field
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
},
|
||||
backgroundHighlightingWithDescription: {
|
||||
marginTop: 38, //Properly align with the text field
|
||||
marginTop: 29, //Properly align with the text field
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -277,80 +272,85 @@ export default class ExpressionField extends React.Component<Props, State> {
|
||||
: styles.backgroundHighlighting;
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<div style={styles.textFieldContainer}>
|
||||
<div style={styles.textFieldAndHightlightContainer}>
|
||||
<BackgroundHighlighting
|
||||
value={this.state.validatedValue}
|
||||
style={{ ...styles.input, ...backgroundHighlightingStyle }}
|
||||
highlights={this.state.errorHighlights}
|
||||
/>
|
||||
<SemiControlledTextField
|
||||
value={value}
|
||||
floatingLabelText={description}
|
||||
hintText={expressionType === 'string' ? '""' : undefined}
|
||||
inputStyle={styles.input}
|
||||
onChange={this._handleChange}
|
||||
onBlur={this._handleBlur}
|
||||
ref={field => (this._field = field)}
|
||||
onFocus={this._handleFocus}
|
||||
errorText={this.state.errorText}
|
||||
multiLine
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
{this._fieldElement && this.state.popoverOpen && (
|
||||
<ClickAwayListener onClickAway={this._handleRequestClose}>
|
||||
<Popper
|
||||
style={popoverStyle}
|
||||
open={this.state.popoverOpen}
|
||||
anchorEl={this._fieldElement}
|
||||
placement="bottom"
|
||||
disablePortal={
|
||||
true /* Can't use portals as this would put the Popper outside of the Modal, which is keeping the focus in the modal (so the search bar and keyboard browsing won't not work) */
|
||||
}
|
||||
>
|
||||
<Paper style={styles.expressionSelectorPopoverContent}>
|
||||
<ExpressionSelector
|
||||
selectedType=""
|
||||
onChoose={(type, expression) => {
|
||||
this._handleExpressionChosen(expression);
|
||||
}}
|
||||
expressionType={expressionType}
|
||||
focusOnMount
|
||||
scope={scope}
|
||||
/>
|
||||
</Paper>
|
||||
</Popper>
|
||||
</ClickAwayListener>
|
||||
<React.Fragment>
|
||||
<TextFieldWithButtonLayout
|
||||
margin={this.props.isInline ? 'none' : 'dense'}
|
||||
renderTextField={() => (
|
||||
<div style={styles.textFieldContainer}>
|
||||
<div style={styles.textFieldAndHightlightContainer}>
|
||||
<BackgroundHighlighting
|
||||
value={this.state.validatedValue}
|
||||
style={{ ...styles.input, ...backgroundHighlightingStyle }}
|
||||
highlights={this.state.errorHighlights}
|
||||
/>
|
||||
<SemiControlledTextField
|
||||
margin={this.props.isInline ? 'none' : 'dense'}
|
||||
value={value}
|
||||
floatingLabelText={description}
|
||||
hintText={expressionType === 'string' ? '""' : undefined}
|
||||
inputStyle={styles.input}
|
||||
onChange={this._handleChange}
|
||||
onBlur={this._handleBlur}
|
||||
ref={field => (this._field = field)}
|
||||
onFocus={this._handleFocus}
|
||||
errorText={this.state.errorText}
|
||||
multiLine
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
{this._fieldElement && this.state.popoverOpen && (
|
||||
<ClickAwayListener onClickAway={this._handleRequestClose}>
|
||||
<Popper
|
||||
style={popoverStyle}
|
||||
open={this.state.popoverOpen}
|
||||
anchorEl={this._fieldElement}
|
||||
placement="bottom"
|
||||
disablePortal={
|
||||
true /* Can't use portals as this would put the Popper outside of the Modal, which is keeping the focus in the modal (so the search bar and keyboard browsing won't not work) */
|
||||
}
|
||||
>
|
||||
<Paper style={styles.expressionSelectorPopoverContent}>
|
||||
<ExpressionSelector
|
||||
selectedType=""
|
||||
onChoose={(type, expression) => {
|
||||
this._handleExpressionChosen(expression);
|
||||
}}
|
||||
expressionType={expressionType}
|
||||
focusOnMount
|
||||
scope={scope}
|
||||
/>
|
||||
</Paper>
|
||||
</Popper>
|
||||
</ClickAwayListener>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!this.props.isInline &&
|
||||
this.props.renderExtraButton &&
|
||||
this.props.renderExtraButton({
|
||||
style: description
|
||||
? textFieldWithLabelRightButtonMargins
|
||||
: textFieldRightButtonMargins,
|
||||
})}
|
||||
{!this.props.isInline && (
|
||||
<RaisedButton
|
||||
icon={<Functions />}
|
||||
label={
|
||||
expressionType === 'string'
|
||||
? '"ABC"'
|
||||
: expressionType === 'number'
|
||||
? '123'
|
||||
: ''
|
||||
}
|
||||
primary
|
||||
style={
|
||||
description
|
||||
? textFieldWithLabelRightButtonMargins
|
||||
: textFieldRightButtonMargins
|
||||
}
|
||||
onClick={this._openExpressionPopover}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<React.Fragment>
|
||||
{!this.props.isInline &&
|
||||
this.props.renderExtraButton &&
|
||||
this.props.renderExtraButton({
|
||||
style,
|
||||
})}
|
||||
{!this.props.isInline && (
|
||||
<RaisedButton
|
||||
icon={<Functions />}
|
||||
label={
|
||||
expressionType === 'string'
|
||||
? '"ABC"'
|
||||
: expressionType === 'number'
|
||||
? '123'
|
||||
: ''
|
||||
}
|
||||
primary
|
||||
style={style}
|
||||
onClick={this._openExpressionPopover}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
|
||||
{this.state.parametersDialogOpen && this.state.selectedExpressionInfo && (
|
||||
<ExpressionParametersEditorDialog
|
||||
open={true}
|
||||
@@ -380,7 +380,7 @@ export default class ExpressionField extends React.Component<Props, State> {
|
||||
parameterRenderingService={parameterRenderingService}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -7,17 +7,7 @@ import { type ParameterFieldProps } from './ParameterFieldCommons';
|
||||
import classNames from 'classnames';
|
||||
import { icon } from '../EventsTree/ClassNames';
|
||||
import SemiControlledAutoComplete from '../../UI/SemiControlledAutoComplete';
|
||||
import {
|
||||
textFieldRightButtonMargins,
|
||||
textFieldWithLabelRightButtonMargins,
|
||||
} from './GenericExpressionField';
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
};
|
||||
import { TextFieldWithButtonLayout } from '../../UI/Layout';
|
||||
|
||||
type Props = {
|
||||
...ParameterFieldProps,
|
||||
@@ -47,35 +37,35 @@ export default class VariableField extends Component<Props, {||}> {
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<SemiControlledAutoComplete
|
||||
floatingLabelText={description}
|
||||
fullWidth
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
dataSource={enumerateVariables(variablesContainer).map(
|
||||
variableName => ({
|
||||
text: variableName,
|
||||
value: variableName,
|
||||
})
|
||||
)}
|
||||
openOnFocus={!isInline}
|
||||
ref={field => (this._field = field)}
|
||||
/>
|
||||
{onOpenDialog && !isInline && (
|
||||
<RaisedButton
|
||||
icon={<OpenInNew />}
|
||||
disabled={!this.props.variablesContainer}
|
||||
primary
|
||||
style={
|
||||
description
|
||||
? textFieldWithLabelRightButtonMargins
|
||||
: textFieldRightButtonMargins
|
||||
}
|
||||
onClick={onOpenDialog}
|
||||
<TextFieldWithButtonLayout
|
||||
renderTextField={() => (
|
||||
<SemiControlledAutoComplete
|
||||
floatingLabelText={description}
|
||||
fullWidth
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
dataSource={enumerateVariables(variablesContainer).map(
|
||||
variableName => ({
|
||||
text: variableName,
|
||||
value: variableName,
|
||||
})
|
||||
)}
|
||||
openOnFocus={!isInline}
|
||||
ref={field => (this._field = field)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
renderButton={style =>
|
||||
onOpenDialog && !isInline ? (
|
||||
<RaisedButton
|
||||
icon={<OpenInNew />}
|
||||
disabled={!this.props.variablesContainer}
|
||||
primary
|
||||
style={style}
|
||||
onClick={onOpenDialog}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ import React, { PureComponent } from 'react';
|
||||
import Background from '../UI/Background';
|
||||
import TextField from '../UI/TextField';
|
||||
import { Line, Column, Spacer } from '../UI/Grid';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import ChevronLeft from '@material-ui/icons/ChevronLeft';
|
||||
import ChevronRight from '@material-ui/icons/ChevronRight';
|
||||
import IconButton from '../UI/IconButton';
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
type SearchInEventsInputs,
|
||||
type ReplaceInEventsInputs,
|
||||
} from './EventsSearcher';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
|
||||
type Props = {|
|
||||
onSearchInEvents: SearchInEventsInputs => void,
|
||||
@@ -104,15 +104,17 @@ export default class SearchPanel extends PureComponent<Props, State> {
|
||||
<Column>
|
||||
<Line alignItems="baseline">
|
||||
<TextField
|
||||
margin="none"
|
||||
ref={_searchTextField =>
|
||||
(this.searchTextField = _searchTextField)
|
||||
}
|
||||
hintText={t`Text to search`}
|
||||
hintText={t`Text to search in parameters`}
|
||||
onChange={(e, searchText) => this.setState({ searchText })}
|
||||
value={searchText}
|
||||
fullWidth
|
||||
/>
|
||||
<FlatButton
|
||||
<Spacer />
|
||||
<RaisedButton
|
||||
disabled={!searchText}
|
||||
primary
|
||||
label={<Trans>Search</Trans>}
|
||||
@@ -121,12 +123,14 @@ export default class SearchPanel extends PureComponent<Props, State> {
|
||||
</Line>
|
||||
<Line alignItems="baseline">
|
||||
<TextField
|
||||
hintText={t`Text to replace`}
|
||||
margin="none"
|
||||
hintText={t`Text to replace in parameters`}
|
||||
onChange={(e, replaceText) => this.setState({ replaceText })}
|
||||
value={replaceText}
|
||||
fullWidth
|
||||
/>
|
||||
<FlatButton
|
||||
<Spacer />
|
||||
<RaisedButton
|
||||
disabled={
|
||||
!replaceText ||
|
||||
!searchText ||
|
||||
|
@@ -90,6 +90,7 @@ export default class TextEditor extends React.Component<EditorProps, void> {
|
||||
style={styles.checkbox}
|
||||
/>
|
||||
<ResourceSelector
|
||||
margin="none"
|
||||
project={project}
|
||||
resourceSources={resourceSources}
|
||||
onChooseResource={onChooseResource}
|
||||
@@ -110,6 +111,8 @@ export default class TextEditor extends React.Component<EditorProps, void> {
|
||||
<Column expand>
|
||||
<Line>
|
||||
<SemiControlledTextField
|
||||
floatingLabelText={<Trans>Initial text to display</Trans>}
|
||||
floatingLabelFixed
|
||||
commitOnBlur
|
||||
hintText={t`Enter the text to be displayed by the object`}
|
||||
fullWidth
|
||||
|
@@ -13,6 +13,7 @@ import { withSerializableObject } from '../Utils/SerializableObjectEditorContain
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
import SemiControlledTextField from '../UI/SemiControlledTextField';
|
||||
import Text from '../UI/Text';
|
||||
import MiniToolbar, { MiniToolbarText } from '../UI/MiniToolbar';
|
||||
|
||||
type StateType = {|
|
||||
currentTab: string,
|
||||
@@ -83,25 +84,25 @@ export class ObjectEditorDialog extends Component<*, StateType> {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Line alignItems="baseline">
|
||||
<Column>
|
||||
<Text noShrink>Object Name:</Text>
|
||||
</Column>
|
||||
<Column expand>
|
||||
<SemiControlledTextField
|
||||
fullWidth
|
||||
commitOnBlur
|
||||
margin="none"
|
||||
value={this.state.newObjectName}
|
||||
hintText={t`Object Name`}
|
||||
onChange={text => {
|
||||
if (this.props.canRenameObject(text)) {
|
||||
this.setState({ newObjectName: text });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
</Line>
|
||||
<MiniToolbar alignItems="baseline">
|
||||
<MiniToolbarText>
|
||||
<Trans>Object Name:</Trans>
|
||||
</MiniToolbarText>
|
||||
<SemiControlledTextField
|
||||
fullWidth
|
||||
commitOnBlur
|
||||
margin="none"
|
||||
value={this.state.newObjectName}
|
||||
hintText={t`Object Name`}
|
||||
onChange={text => {
|
||||
if (text === this.state.newObjectName) return;
|
||||
|
||||
if (this.props.canRenameObject(text)) {
|
||||
this.setState({ newObjectName: text });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MiniToolbar>
|
||||
{currentTab === 'properties' && EditorComponent && (
|
||||
<EditorComponent
|
||||
object={this.props.object}
|
||||
|
@@ -241,6 +241,7 @@ export default class PlatformSpecificAssetsDialog extends React.Component<
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={<Trans>Project icons</Trans>}
|
||||
actions={actions}
|
||||
open={this.props.open}
|
||||
onRequestClose={this.props.onClose}
|
||||
|
@@ -164,6 +164,7 @@ class ProjectPropertiesDialog extends React.Component<Props, State> {
|
||||
key="help"
|
||||
/>,
|
||||
]}
|
||||
title={<Trans>Project properties</Trans>}
|
||||
open={this.props.open}
|
||||
onRequestClose={this.props.onClose}
|
||||
>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ResponsiveWindowMeasurer,
|
||||
@@ -13,13 +14,14 @@ import FlatButton from '../UI/FlatButton';
|
||||
import SelectField from '../UI/SelectField';
|
||||
import SelectOption from '../UI/SelectOption';
|
||||
import Edit from '@material-ui/icons/Edit';
|
||||
import IconButton from '../UI/IconButton';
|
||||
import {
|
||||
type ResourceKind,
|
||||
type ResourceSource,
|
||||
type ChooseResourceFunction,
|
||||
} from '../ResourcesList/ResourceSource.flow';
|
||||
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
|
||||
import { TextFieldWithButtonLayout } from '../UI/Layout';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
|
||||
// An "instance" here is the objects for which properties are shown
|
||||
export type Instance = Object; // This could be improved using generics.
|
||||
@@ -216,32 +218,39 @@ export default class PropertiesEditor extends React.Component<Props, {||}> {
|
||||
} else {
|
||||
const { onEditButtonClick, setValue } = field;
|
||||
return (
|
||||
<div style={styles.fieldContainer} key={field.name}>
|
||||
<SemiControlledTextField
|
||||
value={getFieldValue(
|
||||
this.props.instances,
|
||||
field,
|
||||
'(Multiple values)'
|
||||
)}
|
||||
id={field.name}
|
||||
floatingLabelText={getFieldLabel(this.props.instances, field)}
|
||||
floatingLabelFixed
|
||||
onChange={newValue => {
|
||||
this.props.instances.forEach(i => setValue(i, newValue || ''));
|
||||
this._onInstancesModified(this.props.instances);
|
||||
}}
|
||||
style={styles.field}
|
||||
disabled={field.disabled}
|
||||
/>
|
||||
{onEditButtonClick && (
|
||||
<IconButton
|
||||
disabled={this.props.instances.length !== 1}
|
||||
onClick={() => onEditButtonClick(this.props.instances[0])}
|
||||
>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
<TextFieldWithButtonLayout
|
||||
key={field.name}
|
||||
renderTextField={() => (
|
||||
<SemiControlledTextField
|
||||
value={getFieldValue(
|
||||
this.props.instances,
|
||||
field,
|
||||
'(Multiple values)'
|
||||
)}
|
||||
id={field.name}
|
||||
floatingLabelText={getFieldLabel(this.props.instances, field)}
|
||||
floatingLabelFixed
|
||||
onChange={newValue => {
|
||||
this.props.instances.forEach(i => setValue(i, newValue || ''));
|
||||
this._onInstancesModified(this.props.instances);
|
||||
}}
|
||||
style={styles.field}
|
||||
disabled={field.disabled}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
renderButton={style =>
|
||||
onEditButtonClick ? (
|
||||
<RaisedButton
|
||||
style={style}
|
||||
primary
|
||||
disabled={this.props.instances.length !== 1}
|
||||
icon={<Edit />}
|
||||
label={<Trans>Edit</Trans>}
|
||||
onClick={() => onEditButtonClick(this.props.instances[0])}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@@ -1,12 +1,10 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import * as React from 'react';
|
||||
import IconButton from '../UI/IconButton';
|
||||
import SemiControlledAutoComplete, {
|
||||
type DataSource,
|
||||
} from '../UI/SemiControlledAutoComplete';
|
||||
import ElementWithMenu from '../UI/Menu/ElementWithMenu';
|
||||
import { Line } from '../UI/Grid';
|
||||
import BackspaceIcon from '@material-ui/icons/Backspace';
|
||||
import Add from '@material-ui/icons/Add';
|
||||
import Brush from '@material-ui/icons/Brush';
|
||||
@@ -19,6 +17,9 @@ import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEd
|
||||
import ResourcesLoader from '../ResourcesLoader';
|
||||
import { applyResourceDefaults } from './ResourceUtils';
|
||||
import { type MessageDescriptor } from '../Utils/i18n/MessageDescriptor.flow';
|
||||
import RaisedButtonWithMenu from '../UI/RaisedButtonWithMenu';
|
||||
import { TextFieldWithButtonLayout } from '../UI/Layout';
|
||||
import IconButton from '../UI/IconButton';
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
@@ -33,7 +34,7 @@ type Props = {|
|
||||
onChange: string => void,
|
||||
floatingLabelText?: React.Node,
|
||||
hintText?: MessageDescriptor,
|
||||
margin?: 'none' | 'normal',
|
||||
margin?: 'none' | 'dense',
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
@@ -41,10 +42,6 @@ type State = {|
|
||||
resourceName: string,
|
||||
|};
|
||||
|
||||
const styles = {
|
||||
container: { display: 'flex', flex: 1, alignItems: 'flex-end' },
|
||||
};
|
||||
|
||||
export default class ResourceSelector extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@@ -259,8 +256,10 @@ export default class ResourceSelector extends React.Component<Props, State> {
|
||||
externalEditor => externalEditor.kind === this.props.resourceKind
|
||||
);
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<Line nomargin expand>
|
||||
<TextFieldWithButtonLayout
|
||||
noFloatingLabelText={!this.props.floatingLabelText}
|
||||
margin={this.props.margin}
|
||||
renderTextField={() => (
|
||||
<SemiControlledAutoComplete
|
||||
floatingLabelText={this.props.floatingLabelText}
|
||||
hintText={this.props.hintText}
|
||||
@@ -273,32 +272,42 @@ export default class ResourceSelector extends React.Component<Props, State> {
|
||||
margin={this.props.margin}
|
||||
ref={autoComplete => (this._autoComplete = autoComplete)}
|
||||
/>
|
||||
{this.props.canBeReset && (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
this._onResetResourceName();
|
||||
}}
|
||||
>
|
||||
<BackspaceIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
{!!externalEditors.length && (
|
||||
<ElementWithMenu
|
||||
element={
|
||||
<IconButton>
|
||||
<Brush />
|
||||
</IconButton>
|
||||
}
|
||||
buildMenuTemplate={() =>
|
||||
externalEditors.map(externalEditor => ({
|
||||
label: externalEditor.displayName,
|
||||
click: () => this._editWith(externalEditor),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Line>
|
||||
</div>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<React.Fragment>
|
||||
{this.props.canBeReset && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
this._onResetResourceName();
|
||||
}}
|
||||
>
|
||||
<BackspaceIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
{!!externalEditors.length ? (
|
||||
<RaisedButtonWithMenu
|
||||
style={style}
|
||||
icon={<Brush />}
|
||||
label={
|
||||
this.state.resourceName ? (
|
||||
<Trans>Edit</Trans>
|
||||
) : (
|
||||
<Trans>Create</Trans>
|
||||
)
|
||||
}
|
||||
primary
|
||||
buildMenuTemplate={() =>
|
||||
externalEditors.map(externalEditor => ({
|
||||
label: externalEditor.displayName,
|
||||
click: () => this._editWith(externalEditor),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ type Props = {|
|
||||
const styles = {
|
||||
container: { flex: 1, display: 'flex', alignItems: 'flex-end' },
|
||||
selectorContainer: { flex: 1 },
|
||||
resourceThumbnail: { marginLeft: 10 },
|
||||
resourceThumbnail: { marginLeft: 10, marginBottom: 4 },
|
||||
};
|
||||
|
||||
const ResourceSelectorWithThumbnail = ({
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import React from 'react';
|
||||
const marginsSize = 4;
|
||||
|
||||
/**
|
||||
* A Line in the standard GDevelop grid to position components.
|
||||
* Check `Layout` first to see if there is already a layout made
|
||||
* specifically for your components (like TextFieldWithButton).
|
||||
*/
|
||||
export const Line = props => (
|
||||
<div
|
||||
style={{
|
||||
@@ -16,6 +21,11 @@ export const Line = props => (
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* A Column in the standard GDevelop grid to position components.
|
||||
* Check `Layout` first to see if there is already a layout made
|
||||
* specifically for your components (like TextFieldWithButton).
|
||||
*/
|
||||
export const Column = props => (
|
||||
<div
|
||||
style={{
|
||||
@@ -32,6 +42,11 @@ export const Column = props => (
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* A Spacer in the standard GDevelop grid to position components.
|
||||
* Check `Layout` first to see if there is already a layout made
|
||||
* specifically for your components (like TextFieldWithButton).
|
||||
*/
|
||||
export const Spacer = props => (
|
||||
<span
|
||||
style={{
|
||||
|
@@ -29,7 +29,11 @@ type Props = {|
|
||||
transform?: string,
|
||||
transition?: string,
|
||||
opacity?: number,
|
||||
margin?: number,
|
||||
marginRight?: number,
|
||||
marginLeft?: number,
|
||||
marginTop?: number,
|
||||
marginBottom?: number,
|
||||
|},
|
||||
size?: 'small',
|
||||
|
||||
|
59
newIDE/app/src/UI/Layout.js
Normal file
59
newIDE/app/src/UI/Layout.js
Normal file
@@ -0,0 +1,59 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
|
||||
type TextFieldWithButtonLayoutProps = {|
|
||||
renderTextField: () => React.Node,
|
||||
renderButton: (style: {|
|
||||
marginTop?: number,
|
||||
marginBottom?: number,
|
||||
marginLeft?: number,
|
||||
marginRight?: number,
|
||||
margin?: number,
|
||||
|}) => React.Node,
|
||||
margin?: 'none' | 'dense',
|
||||
noFloatingLabelText?: boolean,
|
||||
|};
|
||||
|
||||
const textFieldWithButtonLayoutStyles = {
|
||||
container: {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start', // Align from the top to stay at the same position when error/multiline
|
||||
},
|
||||
filledTextFieldRightButtonMargins: {
|
||||
marginTop: 24, // Properly align with the text field (only dense "filled" text fields supported)
|
||||
marginLeft: 10,
|
||||
},
|
||||
standardTextFieldWithLabelRightButtonMargins: {
|
||||
marginTop: 17, // Properly align with the text field (only dense "standard" text fields supported)
|
||||
marginLeft: 10,
|
||||
},
|
||||
standardTextFieldWithoutLabelRightButtonMargins: {
|
||||
marginTop: 0, // Properly align with the text field (only dense "standard" text fields supported)
|
||||
marginLeft: 10,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Position a button on the right of a TextField.
|
||||
* Only compatible with TextField with a label.
|
||||
*/
|
||||
export const TextFieldWithButtonLayout = ({
|
||||
margin,
|
||||
noFloatingLabelText,
|
||||
renderTextField,
|
||||
renderButton,
|
||||
}: TextFieldWithButtonLayoutProps) => {
|
||||
return (
|
||||
<div style={textFieldWithButtonLayoutStyles.container}>
|
||||
{renderTextField()}
|
||||
{renderButton(
|
||||
margin === 'none'
|
||||
? noFloatingLabelText
|
||||
? textFieldWithButtonLayoutStyles.standardTextFieldWithoutLabelRightButtonMargins
|
||||
: textFieldWithButtonLayoutStyles.standardTextFieldWithLabelRightButtonMargins
|
||||
: textFieldWithButtonLayoutStyles.filledTextFieldRightButtonMargins
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
@@ -4,8 +4,8 @@ import { t } from '@lingui/macro';
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import TextField from '../TextField';
|
||||
import FlatButton from '../FlatButton';
|
||||
import optionalRequire from '../../Utils/OptionalRequire.js';
|
||||
import RaisedButton from '../RaisedButton';
|
||||
const electron = optionalRequire('electron');
|
||||
const dialog = electron ? electron.remote.dialog : null;
|
||||
|
||||
@@ -30,7 +30,6 @@ type Props = {|
|
||||
message: string,
|
||||
defaultPath?: string,
|
||||
fullWidth?: boolean,
|
||||
floatingLabelText?: string,
|
||||
filters: Array<{
|
||||
name: string,
|
||||
extensions: Array<string>,
|
||||
@@ -65,15 +64,14 @@ export default class LocalFilePicker extends PureComponent<Props, *> {
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
margin="none"
|
||||
style={styles.textField}
|
||||
floatingLabelText={this.props.floatingLabelText}
|
||||
floatingLabelFixed
|
||||
type="text"
|
||||
hintText={t`Click to choose`}
|
||||
hintText={t`Choose a file`}
|
||||
value={this.props.value}
|
||||
onChange={(event, value) => this.props.onChange(value)}
|
||||
/>
|
||||
<FlatButton
|
||||
<RaisedButton
|
||||
label={<Trans>Choose</Trans>}
|
||||
style={styles.button}
|
||||
onClick={this.onChooseFolder}
|
||||
|
@@ -6,7 +6,7 @@ import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import TextField from '../TextField';
|
||||
import FlatButton from '../FlatButton';
|
||||
import RaisedButton from '../RaisedButton';
|
||||
import optionalRequire from '../../Utils/OptionalRequire.js';
|
||||
const electron = optionalRequire('electron');
|
||||
const dialog = electron ? electron.remote.dialog : null;
|
||||
@@ -31,7 +31,6 @@ type Props = {|
|
||||
onChange: string => void,
|
||||
defaultPath?: string,
|
||||
fullWidth?: boolean,
|
||||
floatingLabelText?: string,
|
||||
|};
|
||||
|
||||
type TitleAndMessage = {|
|
||||
@@ -83,31 +82,32 @@ export default class LocalFolderPicker extends PureComponent<Props, {||}> {
|
||||
render() {
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<div
|
||||
style={{
|
||||
...styles.container,
|
||||
width: this.props.fullWidth ? '100%' : undefined,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
style={styles.textField}
|
||||
floatingLabelText={this.props.floatingLabelText}
|
||||
floatingLabelFixed
|
||||
type="text"
|
||||
hintText={t`Click to choose`}
|
||||
value={this.props.value}
|
||||
onChange={(event, value) => this.props.onChange(value)}
|
||||
/>
|
||||
<FlatButton
|
||||
label={<Trans>Choose folder</Trans>}
|
||||
style={styles.button}
|
||||
onClick={() =>
|
||||
this._onChooseFolder(this._getTitleAndMessage(i18n))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{({ i18n }) => {
|
||||
const titleAndMessage = this._getTitleAndMessage(i18n);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...styles.container,
|
||||
width: this.props.fullWidth ? '100%' : undefined,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
margin="none"
|
||||
style={styles.textField}
|
||||
type="text"
|
||||
hintText={titleAndMessage.title}
|
||||
value={this.props.value}
|
||||
onChange={(event, value) => this.props.onChange(value)}
|
||||
/>
|
||||
<RaisedButton
|
||||
label={<Trans>Choose folder</Trans>}
|
||||
primary={false}
|
||||
style={styles.button}
|
||||
onClick={() => this._onChooseFolder(titleAndMessage)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</I18n>
|
||||
);
|
||||
}
|
||||
|
@@ -37,7 +37,9 @@ const toolbarTextStyle = {
|
||||
};
|
||||
|
||||
export const MiniToolbarText = ({ children }) => (
|
||||
<Text style={toolbarTextStyle}>{children}</Text>
|
||||
<Text noShrink style={toolbarTextStyle}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
|
||||
export default MiniToolbar;
|
||||
|
@@ -5,9 +5,8 @@ import { Spacer } from './Grid';
|
||||
|
||||
// We support a subset of the props supported by Material-UI v0.x RaisedButton
|
||||
// They should be self descriptive - refer to Material UI docs otherwise.
|
||||
type Props = {|
|
||||
export type RaisedButtonPropsWithoutOnClick = {|
|
||||
label?: React.Node,
|
||||
onClick: ?() => void,
|
||||
primary?: boolean,
|
||||
disabled?: boolean,
|
||||
fullWidth?: boolean,
|
||||
@@ -22,6 +21,11 @@ type Props = {|
|
||||
labelPosition?: 'before',
|
||||
|};
|
||||
|
||||
type Props = {
|
||||
...RaisedButtonPropsWithoutOnClick,
|
||||
onClick: ?() => void,
|
||||
};
|
||||
|
||||
/**
|
||||
* A raised button based on Material-UI button.
|
||||
*/
|
||||
|
33
newIDE/app/src/UI/RaisedButtonWithMenu.js
Normal file
33
newIDE/app/src/UI/RaisedButtonWithMenu.js
Normal file
@@ -0,0 +1,33 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import RaisedButton, {
|
||||
type RaisedButtonPropsWithoutOnClick,
|
||||
} from './RaisedButton';
|
||||
import ElementWithMenu from './Menu/ElementWithMenu';
|
||||
|
||||
// We support a subset of the props supported by Material-UI v0.x RaisedButton
|
||||
// They should be self descriptive - refer to Material UI docs otherwise.
|
||||
type Props = {|
|
||||
...RaisedButtonPropsWithoutOnClick,
|
||||
buildMenuTemplate: () => Array<any>,
|
||||
|};
|
||||
|
||||
const shouldNeverBeCalled = () => {
|
||||
throw new Error('This RaisedButtonWithMenu onClick should never be called');
|
||||
};
|
||||
|
||||
/**
|
||||
* A raised button based on Material-UI button, that has a menu displayed when clicked.
|
||||
*/
|
||||
const RaisedButtonWithMenu = (props: Props) => {
|
||||
const { buildMenuTemplate, ...otherProps } = props;
|
||||
|
||||
return (
|
||||
<ElementWithMenu
|
||||
element={<RaisedButton {...otherProps} onClick={shouldNeverBeCalled} />}
|
||||
buildMenuTemplate={buildMenuTemplate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default RaisedButtonWithMenu;
|
@@ -76,7 +76,8 @@ export default class SelectField extends React.Component<Props, {||}> {
|
||||
{({ i18n }) => (
|
||||
<TextField
|
||||
select
|
||||
margin={props.margin || 'normal'}
|
||||
variant={props.margin === 'none' ? 'standard' : 'filled'}
|
||||
margin={props.margin || 'dense'}
|
||||
disabled={props.disabled}
|
||||
fullWidth={props.fullWidth}
|
||||
label={props.floatingLabelText}
|
||||
|
@@ -27,7 +27,6 @@ export type DataSource = Array<
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
position: 'relative',
|
||||
},
|
||||
inputRoot: {
|
||||
@@ -126,7 +125,7 @@ type Props = {|
|
||||
floatingLabelText?: React.Node,
|
||||
hintText?: MessageDescriptor | string,
|
||||
fullWidth?: boolean,
|
||||
margin?: 'none' | 'normal',
|
||||
margin?: 'none' | 'dense',
|
||||
textFieldStyle?: Object,
|
||||
openOnFocus?: boolean,
|
||||
|};
|
||||
@@ -271,7 +270,12 @@ export default class SemiControlledAutoComplete extends React.Component<
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<div
|
||||
style={{
|
||||
...styles.container,
|
||||
flexGrow: props.fullWidth ? 1 : undefined,
|
||||
}}
|
||||
>
|
||||
{renderTextField({
|
||||
disabled: props.disabled,
|
||||
label: props.floatingLabelText,
|
||||
@@ -297,7 +301,8 @@ export default class SemiControlledAutoComplete extends React.Component<
|
||||
// Style:
|
||||
style: props.textFieldStyle,
|
||||
fullWidth: props.fullWidth,
|
||||
margin: props.margin || 'normal',
|
||||
variant: props.margin === 'none' ? 'standard' : 'filled',
|
||||
margin: props.margin || 'dense',
|
||||
|
||||
inputRef: this._input,
|
||||
})}
|
||||
|
@@ -25,7 +25,7 @@ type Props = {|
|
||||
type?: 'text' | 'number',
|
||||
|
||||
// Some TextField props that can be reused:
|
||||
margin?: 'none' | 'normal',
|
||||
margin?: 'none' | 'dense',
|
||||
disabled?: boolean,
|
||||
errorText?: React.Node,
|
||||
floatingLabelFixed?: boolean,
|
||||
|
@@ -72,7 +72,7 @@ type Props = {|
|
||||
rowsMax?: number,
|
||||
|
||||
// Styling:
|
||||
margin?: 'none' | 'normal',
|
||||
margin?: 'none' | 'dense',
|
||||
fullWidth?: boolean,
|
||||
style?: {|
|
||||
fontSize?: 18,
|
||||
@@ -128,6 +128,7 @@ export default class TextField extends React.Component<Props, {||}> {
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<MUITextField
|
||||
variant={props.margin === 'none' ? 'standard' : 'filled'}
|
||||
// Value and change handling:
|
||||
type={props.type !== undefined ? props.type : undefined}
|
||||
value={props.value !== undefined ? props.value : undefined}
|
||||
@@ -157,7 +158,7 @@ export default class TextField extends React.Component<Props, {||}> {
|
||||
rows={props.rows}
|
||||
rowsMax={props.rowsMax}
|
||||
// Styling:
|
||||
margin={props.margin || 'normal'}
|
||||
margin={props.margin || 'dense'}
|
||||
fullWidth={props.fullWidth}
|
||||
InputProps={{
|
||||
disableUnderline:
|
||||
|
34
newIDE/app/src/stories/FakeResourceExternalEditors.js
Normal file
34
newIDE/app/src/stories/FakeResourceExternalEditors.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// @flow
|
||||
import { type ResourceExternalEditor } from '../ResourcesList/ResourceExternalEditor.flow';
|
||||
|
||||
/**
|
||||
* Fake "external editors" to be used in Storybook.
|
||||
*/
|
||||
const fakeResourceExternalEditors: Array<ResourceExternalEditor> = [
|
||||
{
|
||||
name: 'fake-image-editor',
|
||||
displayName: 'Edit with Super Image Editor',
|
||||
kind: 'image',
|
||||
edit: options => {
|
||||
console.log('Open the image editor with these options:', options);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fake-audio-editor',
|
||||
displayName: 'Create/Edit a Sound effect with Super Audio Editor',
|
||||
kind: 'audio',
|
||||
edit: options => {
|
||||
console.log('Open the audio editor with these options:', options);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fake-json-editor',
|
||||
displayName: 'Create/Edit a Dialogue Tree with Super JSON Dialogue Editor',
|
||||
kind: 'json',
|
||||
edit: options => {
|
||||
console.log('Open the json editor with these options:', options);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default fakeResourceExternalEditors;
|
@@ -147,7 +147,7 @@ import SubscriptionPendingDialog from '../Profile/SubscriptionPendingDialog';
|
||||
import Dialog from '../UI/Dialog';
|
||||
import MiniToolbar, { MiniToolbarText } from '../UI/MiniToolbar';
|
||||
import NewObjectDialog from '../ObjectsList/NewObjectDialog';
|
||||
import { Column } from '../UI/Grid';
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
import DragAndDropTestBed from './DragAndDropTestBed';
|
||||
import EditorMosaic from '../UI/EditorMosaic';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
@@ -162,6 +162,16 @@ import GoogleDriveSaveAsDialog from '../ProjectsStorage/GoogleDriveStorageProvid
|
||||
import OpenConfirmDialog from '../ProjectsStorage/OpenConfirmDialog';
|
||||
import CreateAccountDialog from '../Profile/CreateAccountDialog';
|
||||
import BrowserPreviewErrorDialog from '../Export/BrowserExporters/BrowserS3PreviewLauncher/BrowserPreviewErrorDialog';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
import Text from '../UI/Text';
|
||||
import ToolbarIcon from '../UI/ToolbarIcon';
|
||||
import ElementWithMenu from '../UI/Menu/ElementWithMenu';
|
||||
import IconButton from '../UI/IconButton';
|
||||
import FilterList from '@material-ui/icons/FilterList';
|
||||
import Brush from '@material-ui/icons/Brush';
|
||||
import RaisedButtonWithMenu from '../UI/RaisedButtonWithMenu';
|
||||
import fakeResourceExternalEditors from './FakeResourceExternalEditors';
|
||||
import { TextFieldWithButtonLayout } from '../UI/Layout';
|
||||
|
||||
// No i18n in this file
|
||||
|
||||
@@ -194,10 +204,117 @@ const {
|
||||
|
||||
const Placeholder = () => <div>Placeholder component</div>;
|
||||
|
||||
const buildFakeMenuTemplate = () => [
|
||||
{
|
||||
label: 'Option 1',
|
||||
click: action('click option 1'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Option 2',
|
||||
click: action('click option 2'),
|
||||
},
|
||||
];
|
||||
|
||||
storiesOf('Welcome', module).add('to Storybook', () => (
|
||||
<Welcome showApp={linkTo('Button')} />
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/Buttons', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
<Column>
|
||||
<Line>
|
||||
<Text>Buttons:</Text>
|
||||
</Line>
|
||||
<Line>
|
||||
<RaisedButton label="Raised button" onClick={action('onClick')} />
|
||||
<RaisedButton
|
||||
label="Primary Raised button"
|
||||
primary
|
||||
onClick={action('onClick')}
|
||||
/>
|
||||
</Line>
|
||||
<Line>
|
||||
<FlatButton label="Flat button" onClick={action('onClick')} />
|
||||
<FlatButton
|
||||
label="Primary Flat button"
|
||||
primary
|
||||
onClick={action('onClick')}
|
||||
/>
|
||||
</Line>
|
||||
<Line>
|
||||
<Text>Buttons with menus:</Text>
|
||||
</Line>
|
||||
<Line>
|
||||
<RaisedButton
|
||||
label="Traditional Raised button"
|
||||
onClick={action('onClick')}
|
||||
/>
|
||||
<RaisedButtonWithMenu
|
||||
label="Button with menu"
|
||||
buildMenuTemplate={buildFakeMenuTemplate}
|
||||
/>
|
||||
<RaisedButtonWithMenu
|
||||
label="... and with icon"
|
||||
icon={<Brush />}
|
||||
buildMenuTemplate={buildFakeMenuTemplate}
|
||||
/>
|
||||
</Line>
|
||||
<Line>
|
||||
<Text>Icons with menu:</Text>
|
||||
</Line>
|
||||
<Line>
|
||||
<ElementWithMenu
|
||||
element={
|
||||
<ToolbarIcon
|
||||
src="res/ribbon_default/bug32.png"
|
||||
tooltip={'ToolbarIcon with menu'}
|
||||
/>
|
||||
}
|
||||
buildMenuTemplate={buildFakeMenuTemplate}
|
||||
/>
|
||||
<ElementWithMenu
|
||||
element={
|
||||
<IconButton>
|
||||
<FilterList />
|
||||
</IconButton>
|
||||
}
|
||||
buildMenuTemplate={buildFakeMenuTemplate}
|
||||
/>
|
||||
</Line>
|
||||
<Line>
|
||||
<Text>In a mini toolbar:</Text>
|
||||
</Line>
|
||||
<Line>
|
||||
<MiniToolbar>
|
||||
<MiniToolbarText>Some text:</MiniToolbarText>
|
||||
<IconButton>
|
||||
<Brush />
|
||||
</IconButton>
|
||||
<ElementWithMenu
|
||||
element={
|
||||
<IconButton>
|
||||
<FilterList />
|
||||
</IconButton>
|
||||
}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Option 1',
|
||||
click: action('click option 1'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Option 2',
|
||||
click: action('click option 2'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</MiniToolbar>
|
||||
</Line>
|
||||
</Column>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/SemiControlledTextField', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
@@ -258,6 +375,25 @@ storiesOf('UI Building Blocks/SemiControlledTextField', module)
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('reduced margin, in a MiniToolbar', () => (
|
||||
<ValueStateHolder
|
||||
initialValue={'Choice 6'}
|
||||
render={(value, onChange) => (
|
||||
<React.Fragment>
|
||||
<MiniToolbar>
|
||||
<MiniToolbarText>Please enter something:</MiniToolbarText>
|
||||
<SemiControlledTextField
|
||||
margin="none"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
commitOnBlur
|
||||
/>
|
||||
</MiniToolbar>
|
||||
<p>State value is {value}</p>
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/DragAndDrop', module).add('test bed', () => (
|
||||
@@ -448,6 +584,218 @@ storiesOf('UI Building Blocks/SearchBar', module)
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/Layout', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('Empty text field', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
renderTextField={() => (
|
||||
<SemiControlledTextField
|
||||
floatingLabelText="Hello world"
|
||||
value=""
|
||||
onChange={() => {}}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Empty text field, margin=none', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
margin="none"
|
||||
renderTextField={() => (
|
||||
<SemiControlledTextField
|
||||
margin="none"
|
||||
floatingLabelText="Hello world"
|
||||
value=""
|
||||
onChange={() => {}}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Empty auto complete field', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
renderTextField={() => (
|
||||
<SemiControlledAutoComplete
|
||||
floatingLabelText="Hello world"
|
||||
value={''}
|
||||
onChange={() => {}}
|
||||
dataSource={[1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => ({
|
||||
text: `Choice ${i}`,
|
||||
value: `Choice ${i}`,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Empty auto complete field, noFloatingLabelText', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
noFloatingLabelText
|
||||
renderTextField={() => (
|
||||
<SemiControlledAutoComplete
|
||||
value={''}
|
||||
onChange={() => {}}
|
||||
dataSource={[1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => ({
|
||||
text: `Choice ${i}`,
|
||||
value: `Choice ${i}`,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Empty auto complete field, margin=none', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
margin="none"
|
||||
renderTextField={() => (
|
||||
<SemiControlledAutoComplete
|
||||
margin="none"
|
||||
floatingLabelText="Hello world"
|
||||
value={''}
|
||||
onChange={() => {}}
|
||||
dataSource={[1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => ({
|
||||
text: `Choice ${i}`,
|
||||
value: `Choice ${i}`,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Empty auto complete field, margin=none, noFloatingLabelText', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
margin="none"
|
||||
noFloatingLabelText
|
||||
renderTextField={() => (
|
||||
<SemiControlledAutoComplete
|
||||
margin="none"
|
||||
value={''}
|
||||
onChange={() => {}}
|
||||
dataSource={[1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => ({
|
||||
text: `Choice ${i}`,
|
||||
value: `Choice ${i}`,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Empty auto complete field, margin=none, noFloatingLabelText, with a small IconButton', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
margin="none"
|
||||
noFloatingLabelText
|
||||
renderTextField={() => (
|
||||
<SemiControlledAutoComplete
|
||||
margin="none"
|
||||
value={''}
|
||||
onChange={() => {}}
|
||||
dataSource={[1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => ({
|
||||
text: `Choice ${i}`,
|
||||
value: `Choice ${i}`,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<IconButton size="small">
|
||||
<Brush />
|
||||
</IconButton>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Filled text field', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
renderTextField={() => (
|
||||
<SemiControlledTextField
|
||||
floatingLabelText="Hello"
|
||||
value="123"
|
||||
onChange={() => {}}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Filled text field, full width', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
renderTextField={() => (
|
||||
<SemiControlledTextField
|
||||
floatingLabelText="Hello"
|
||||
value="123"
|
||||
onChange={() => {}}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Filled multiline text field', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
renderTextField={() => (
|
||||
<SemiControlledTextField
|
||||
floatingLabelText="Hello"
|
||||
multiLine
|
||||
value={'123\n456\n789\nblablabla bla bla'}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Filled auto complete field', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
renderTextField={() => (
|
||||
<SemiControlledAutoComplete
|
||||
floatingLabelText="Hello world"
|
||||
value={'Choice 5'}
|
||||
onChange={() => {}}
|
||||
dataSource={[1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => ({
|
||||
text: `Choice ${i}`,
|
||||
value: `Choice ${i}`,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('Filled auto complete field, full width', () => (
|
||||
<TextFieldWithButtonLayout
|
||||
renderTextField={() => (
|
||||
<SemiControlledAutoComplete
|
||||
floatingLabelText="Hello world"
|
||||
value={'Choice 5'}
|
||||
onChange={() => {}}
|
||||
dataSource={[1, 2, 3, 4, 5, 6, 7, 8, 9].map(i => ({
|
||||
text: `Choice ${i}`,
|
||||
value: `Choice ${i}`,
|
||||
}))}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
renderButton={style => (
|
||||
<RaisedButton style={style} label="Button" onClick={() => {}} />
|
||||
)}
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/Background', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => <Background>Hello world</Background>);
|
||||
@@ -1143,7 +1491,7 @@ storiesOf('ParameterFields', module)
|
||||
parameterRenderingService={ParameterRenderingService}
|
||||
resourceSources={[]}
|
||||
onChooseResource={() => Promise.reject('unimplemented')}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -2028,7 +2376,7 @@ storiesOf('EventsSheet', module)
|
||||
onChooseResource={source =>
|
||||
action('Choose resource from source', source)
|
||||
}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
onOpenDebugger={action('open debugger')}
|
||||
onOpenLayout={action('open layout')}
|
||||
onOpenSettings={action('open settings')}
|
||||
@@ -2056,7 +2404,7 @@ storiesOf('EventsSheet', module)
|
||||
onChooseResource={source =>
|
||||
action('Choose resource from source', source)
|
||||
}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
onOpenDebugger={action('open debugger')}
|
||||
onOpenLayout={action('open layout')}
|
||||
onOpenSettings={action('open settings')}
|
||||
@@ -2239,7 +2587,7 @@ storiesOf('InstructionEditor', module)
|
||||
objectsContainer={testLayout}
|
||||
isCondition
|
||||
instruction={testInstruction}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
onChooseResource={() => {
|
||||
action('onChooseResource');
|
||||
return Promise.reject();
|
||||
@@ -2258,7 +2606,7 @@ storiesOf('InstructionEditor', module)
|
||||
objectsContainer={testLayout}
|
||||
isCondition
|
||||
instruction={testInstruction}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
onChooseResource={() => {
|
||||
action('onChooseResource');
|
||||
return Promise.reject();
|
||||
@@ -2282,7 +2630,7 @@ storiesOf('NewInstructionEditorDialog', module)
|
||||
isCondition
|
||||
isNewInstruction={false}
|
||||
instruction={testInstruction}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
onChooseResource={() => {
|
||||
action('onChooseResource');
|
||||
return Promise.reject();
|
||||
@@ -2303,7 +2651,7 @@ storiesOf('NewInstructionEditorDialog', module)
|
||||
isCondition
|
||||
isNewInstruction={false}
|
||||
instruction={testInstruction}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
onChooseResource={() => {
|
||||
action('onChooseResource');
|
||||
return Promise.reject();
|
||||
@@ -2324,7 +2672,7 @@ storiesOf('NewInstructionEditorDialog', module)
|
||||
isCondition
|
||||
isNewInstruction={true}
|
||||
instruction={testInstruction}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
onChooseResource={() => {
|
||||
action('onChooseResource');
|
||||
return Promise.reject();
|
||||
@@ -2348,7 +2696,7 @@ storiesOf('TextEditor', module)
|
||||
onChooseResource={source =>
|
||||
action('Choose resource from source', source)
|
||||
}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
/>
|
||||
</SerializedObjectDisplay>
|
||||
));
|
||||
@@ -2365,7 +2713,7 @@ storiesOf('TiledSpriteEditor', module)
|
||||
onChooseResource={source =>
|
||||
action('Choose resource from source', source)
|
||||
}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
/>
|
||||
</SerializedObjectDisplay>
|
||||
));
|
||||
@@ -2382,7 +2730,7 @@ storiesOf('PanelSpriteEditor', module)
|
||||
onChooseResource={source =>
|
||||
action('Choose resource from source', source)
|
||||
}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
/>
|
||||
</SerializedObjectDisplay>
|
||||
));
|
||||
@@ -2399,7 +2747,7 @@ storiesOf('SpriteEditor and related editors', module)
|
||||
onChooseResource={source =>
|
||||
action('Choose resource from source', source)
|
||||
}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
/>
|
||||
</SerializedObjectDisplay>
|
||||
))
|
||||
@@ -2996,7 +3344,7 @@ storiesOf('ResourceSelector (and ResourceSelectorWithThumbnail)', module)
|
||||
project={project}
|
||||
resourceSources={[]}
|
||||
onChooseResource={() => Promise.reject('Unimplemented')}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
initialResourceName="resource-that-does-not-exists-in-the-project"
|
||||
onChange={action('on change')}
|
||||
resourcesLoader={ResourcesLoader}
|
||||
@@ -3008,7 +3356,20 @@ storiesOf('ResourceSelector (and ResourceSelectorWithThumbnail)', module)
|
||||
project={project}
|
||||
resourceSources={[]}
|
||||
onChooseResource={() => Promise.reject('Unimplemented')}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
initialResourceName="icon128.png"
|
||||
onChange={action('on change')}
|
||||
resourcesLoader={ResourcesLoader}
|
||||
/>
|
||||
))
|
||||
.add('image resource, no margin', () => (
|
||||
<ResourceSelector
|
||||
margin="none"
|
||||
resourceKind="image"
|
||||
project={project}
|
||||
resourceSources={[]}
|
||||
onChooseResource={() => Promise.reject('Unimplemented')}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
initialResourceName="icon128.png"
|
||||
onChange={action('on change')}
|
||||
resourcesLoader={ResourcesLoader}
|
||||
@@ -3020,7 +3381,7 @@ storiesOf('ResourceSelector (and ResourceSelectorWithThumbnail)', module)
|
||||
project={project}
|
||||
resourceSources={[]}
|
||||
onChooseResource={() => Promise.reject('Unimplemented')}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
resourceName="icon128.png"
|
||||
onChange={action('on change')}
|
||||
/>
|
||||
@@ -3031,11 +3392,38 @@ storiesOf('ResourceSelector (and ResourceSelectorWithThumbnail)', module)
|
||||
project={project}
|
||||
resourceSources={[]}
|
||||
onChooseResource={() => Promise.reject('Unimplemented')}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
initialResourceName="fake-audio1.mp3"
|
||||
onChange={action('on change')}
|
||||
resourcesLoader={ResourcesLoader}
|
||||
/>
|
||||
))
|
||||
.add('font resource, with reset button', () => (
|
||||
<ResourceSelector
|
||||
canBeReset
|
||||
resourceKind="font"
|
||||
project={project}
|
||||
resourceSources={[]}
|
||||
onChooseResource={() => Promise.reject('Unimplemented')}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
initialResourceName="font.otf"
|
||||
onChange={action('on change')}
|
||||
resourcesLoader={ResourcesLoader}
|
||||
/>
|
||||
))
|
||||
.add('font resource, no margin, with reset button', () => (
|
||||
<ResourceSelector
|
||||
canBeReset
|
||||
margin="none"
|
||||
resourceKind="font"
|
||||
project={project}
|
||||
resourceSources={[]}
|
||||
onChooseResource={() => Promise.reject('Unimplemented')}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
initialResourceName="font.otf"
|
||||
onChange={action('on change')}
|
||||
resourcesLoader={ResourcesLoader}
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('ResourcesList', module)
|
||||
@@ -3139,7 +3527,7 @@ storiesOf('EventsFunctionsExtensionEditor/index', module)
|
||||
onChooseResource={source =>
|
||||
action('Choose resource from source', source)
|
||||
}
|
||||
resourceExternalEditors={[]}
|
||||
resourceExternalEditors={fakeResourceExternalEditors}
|
||||
openInstructionOrExpression={action('open instruction or expression')}
|
||||
initiallyFocusedFunctionName={null}
|
||||
initiallyFocusedBehaviorName={null}
|
||||
|
Reference in New Issue
Block a user