mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Allow to copy and paste behaviors from one object to another (#5205)
This commit is contained in:
@@ -845,9 +845,21 @@ void WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors(
|
||||
return;
|
||||
};
|
||||
|
||||
AddRequiredBehaviorsFor(project, object, behaviorName);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::AddRequiredBehaviorsFor(
|
||||
gd::Project& project,
|
||||
gd::Object& object,
|
||||
const gd::String& behaviorName) {
|
||||
if (!object.HasBehaviorNamed(behaviorName)) {
|
||||
return;
|
||||
};
|
||||
gd::Behavior& behavior = object.GetBehavior(behaviorName);
|
||||
|
||||
const gd::Platform& platform = project.GetCurrentPlatform();
|
||||
const gd::BehaviorMetadata& behaviorMetadata =
|
||||
MetadataProvider::GetBehaviorMetadata(platform, behaviorType);
|
||||
MetadataProvider::GetBehaviorMetadata(platform, behavior.GetTypeName());
|
||||
if (MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
|
||||
// Should not happen because the behavior was added successfully (so its
|
||||
// metadata are valid) - but double check anyway and bail out if the
|
||||
@@ -855,7 +867,6 @@ void WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors(
|
||||
return;
|
||||
}
|
||||
|
||||
gd::Behavior& behavior = object.GetBehavior(behaviorName);
|
||||
for (auto const& keyValue : behavior.GetProperties()) {
|
||||
const gd::String& propertyName = keyValue.first;
|
||||
const gd::PropertyDescriptor& property = keyValue.second;
|
||||
|
@@ -212,6 +212,13 @@ class GD_CORE_API WholeProjectRefactorer {
|
||||
gd::Object& object,
|
||||
const gd::String& behaviorType,
|
||||
const gd::String& behaviorName);
|
||||
/**
|
||||
* \brief Add required behaviors if necessary to fill every behavior
|
||||
* properties of the given behaviors.
|
||||
*/
|
||||
static void AddRequiredBehaviorsFor(gd::Project& project,
|
||||
gd::Object& object,
|
||||
const gd::String& behaviorName);
|
||||
|
||||
/**
|
||||
* \brief Find every behavior of the object that needs the given behaviors
|
||||
|
@@ -539,6 +539,9 @@ interface Behavior {
|
||||
[Value] MapStringPropertyDescriptor GetProperties();
|
||||
boolean UpdateProperty([Const] DOMString name, [Const] DOMString value);
|
||||
void InitializeContent();
|
||||
|
||||
void SerializeTo([Ref] SerializerElement element);
|
||||
void UnserializeFrom([Const, Ref] SerializerElement element);
|
||||
};
|
||||
|
||||
[JSImplementation=Behavior]
|
||||
@@ -2300,6 +2303,7 @@ interface WholeProjectRefactorer {
|
||||
void STATIC_EnsureBehaviorEventsFunctionsProperParameters([Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedBehavior eventsBasedBehavior);
|
||||
void STATIC_EnsureObjectEventsFunctionsProperParameters([Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedObject eventsBasedObject);
|
||||
void STATIC_AddBehaviorAndRequiredBehaviors([Ref] Project project, [Ref] gdObject obj, [Const] DOMString behaviorType, [Const] DOMString behaviorName);
|
||||
void STATIC_AddRequiredBehaviorsFor([Ref] Project project, [Ref] gdObject obj, [Const] DOMString behaviorName);
|
||||
[Value] VectorString STATIC_FindDependentBehaviorNames([Const, Ref] Project project, [Const, Ref] gdObject obj, [Const] DOMString behaviorName);
|
||||
[Value] VectorUnfilledRequiredBehaviorPropertyProblem STATIC_FindInvalidRequiredBehaviorProperties([Const, Ref] Project project);
|
||||
[Value] VectorString STATIC_GetBehaviorsWithType([Const, Ref] gdObject obj, [Const] DOMString type);
|
||||
|
@@ -596,6 +596,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
EnsureObjectEventsFunctionsProperParameters
|
||||
#define STATIC_AddBehaviorAndRequiredBehaviors \
|
||||
AddBehaviorAndRequiredBehaviors
|
||||
#define STATIC_AddRequiredBehaviorsFor AddRequiredBehaviorsFor
|
||||
#define STATIC_FindDependentBehaviorNames \
|
||||
FindDependentBehaviorNames
|
||||
#define STATIC_FindInvalidRequiredBehaviorProperties \
|
||||
|
@@ -8,6 +8,8 @@ declare class gdBehavior {
|
||||
getProperties(): gdMapStringPropertyDescriptor;
|
||||
updateProperty(name: string, value: string): boolean;
|
||||
initializeContent(): void;
|
||||
serializeTo(element: gdSerializerElement): void;
|
||||
unserializeFrom(element: gdSerializerElement): void;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
@@ -33,6 +33,7 @@ declare class gdWholeProjectRefactorer {
|
||||
static ensureBehaviorEventsFunctionsProperParameters(eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior): void;
|
||||
static ensureObjectEventsFunctionsProperParameters(eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject): void;
|
||||
static addBehaviorAndRequiredBehaviors(project: gdProject, obj: gdObject, behaviorType: string, behaviorName: string): void;
|
||||
static addRequiredBehaviorsFor(project: gdProject, obj: gdObject, behaviorName: string): void;
|
||||
static findDependentBehaviorNames(project: gdProject, obj: gdObject, behaviorName: string): gdVectorString;
|
||||
static findInvalidRequiredBehaviorProperties(project: gdProject): gdVectorUnfilledRequiredBehaviorPropertyProblem;
|
||||
static getBehaviorsWithType(obj: gdObject, type: string): gdVectorString;
|
||||
|
@@ -12,10 +12,12 @@ import NewBehaviorDialog from './NewBehaviorDialog';
|
||||
import BehaviorsEditorService from './BehaviorsEditorService';
|
||||
import Window from '../Utils/Window';
|
||||
import { Column, Line } from '../UI/Grid';
|
||||
import { ResponsiveLineStackLayout } from '../UI/Layout';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
|
||||
import DismissableTutorialMessage from '../Hints/DismissableTutorialMessage';
|
||||
import { ColumnStackLayout } from '../UI/Layout';
|
||||
import { LineStackLayout } from '../UI/Layout';
|
||||
import useForceUpdate from '../Utils/UseForceUpdate';
|
||||
import { Accordion, AccordionHeader, AccordionBody } from '../UI/Accordion';
|
||||
import { EmptyPlaceholder } from '../UI/EmptyPlaceholder';
|
||||
@@ -32,9 +34,36 @@ import ElementWithMenu from '../UI/Menu/ElementWithMenu';
|
||||
import ThreeDotsMenu from '../UI/CustomSvgIcons/ThreeDotsMenu';
|
||||
import Trash from '../UI/CustomSvgIcons/Trash';
|
||||
import Add from '../UI/CustomSvgIcons/Add';
|
||||
import { mapVector } from '../Utils/MapFor';
|
||||
import Clipboard, { SafeExtractor } from '../Utils/Clipboard';
|
||||
import {
|
||||
serializeToJSObject,
|
||||
unserializeFromJSObject,
|
||||
} from '../Utils/Serializer';
|
||||
import useAlertDialog from '../UI/Alert/useAlertDialog';
|
||||
import PasteIcon from '../UI/CustomSvgIcons/Clipboard';
|
||||
import CopyIcon from '../UI/CustomSvgIcons/Copy';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import { useResponsiveWindowWidth } from '../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
const BEHAVIORS_CLIPBOARD_KIND = 'Behaviors';
|
||||
|
||||
export const useBehaviorOverridingAlertDialog = () => {
|
||||
const { showConfirmation } = useAlertDialog();
|
||||
return async (existingBehaviorNames: Array<string>): Promise<boolean> => {
|
||||
return await showConfirmation({
|
||||
title: t`Existing behaviors`,
|
||||
message: t`These behaviors are already attached to the object:${'\n\n - ' +
|
||||
existingBehaviorNames.join('\n\n - ') +
|
||||
'\n\n'}Do you want to replace their property values?`,
|
||||
confirmButtonLabel: t`Replace`,
|
||||
dismissButtonLabel: t`Omit`,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
eventsFunctionsExtension?: gdEventsFunctionsExtension,
|
||||
@@ -74,91 +103,280 @@ const BehaviorsEditor = (props: Props) => {
|
||||
[justAddedBehaviorName]
|
||||
);
|
||||
|
||||
const windowWidth = useResponsiveWindowWidth();
|
||||
const isSmall = windowWidth === 'small';
|
||||
|
||||
const [newBehaviorDialogOpen, setNewBehaviorDialogOpen] = React.useState(
|
||||
false
|
||||
);
|
||||
|
||||
const { object, project, eventsFunctionsExtension } = props;
|
||||
const showBehaviorOverridingConfirmation = useBehaviorOverridingAlertDialog();
|
||||
|
||||
const {
|
||||
object,
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
onSizeUpdated,
|
||||
onBehaviorsUpdated,
|
||||
onUpdateBehaviorsSharedData,
|
||||
openBehaviorEvents,
|
||||
} = props;
|
||||
const allBehaviorNames = object.getAllBehaviorNames().toJSArray();
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const { values } = React.useContext(PreferencesContext);
|
||||
|
||||
const addBehavior = (type: string, defaultName: string) => {
|
||||
const wasBehaviorAdded = addBehaviorToObject(
|
||||
project,
|
||||
object,
|
||||
type,
|
||||
defaultName
|
||||
);
|
||||
const addBehavior = React.useCallback(
|
||||
(type: string, defaultName: string) => {
|
||||
const wasBehaviorAdded = addBehaviorToObject(
|
||||
project,
|
||||
object,
|
||||
type,
|
||||
defaultName
|
||||
);
|
||||
|
||||
if (wasBehaviorAdded) {
|
||||
setNewBehaviorDialogOpen(false);
|
||||
sendBehaviorAdded({
|
||||
behaviorType: type,
|
||||
parentEditor: 'behaviors-editor',
|
||||
if (wasBehaviorAdded) {
|
||||
setNewBehaviorDialogOpen(false);
|
||||
sendBehaviorAdded({
|
||||
behaviorType: type,
|
||||
parentEditor: 'behaviors-editor',
|
||||
});
|
||||
setJustAddedBehaviorName(defaultName);
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
if (onSizeUpdated) onSizeUpdated();
|
||||
onUpdateBehaviorsSharedData();
|
||||
if (onBehaviorsUpdated) onBehaviorsUpdated();
|
||||
},
|
||||
[
|
||||
forceUpdate,
|
||||
object,
|
||||
onBehaviorsUpdated,
|
||||
onSizeUpdated,
|
||||
onUpdateBehaviorsSharedData,
|
||||
project,
|
||||
]
|
||||
);
|
||||
|
||||
const onChangeBehaviorName = React.useCallback(
|
||||
(behavior: gdBehavior, newName: string) => {
|
||||
// TODO: This is disabled for now as there is no proper refactoring
|
||||
// of events after a behavior renaming. Once refactoring is available,
|
||||
// the text field can be enabled again and refactoring calls added here
|
||||
// (or in a parent).
|
||||
// Renaming a behavior is something that is really rare anyway! :)
|
||||
|
||||
if (object.hasBehaviorNamed(newName)) return;
|
||||
object.renameBehavior(behavior.getName(), newName);
|
||||
forceUpdate();
|
||||
if (onBehaviorsUpdated) onBehaviorsUpdated();
|
||||
},
|
||||
[forceUpdate, object, onBehaviorsUpdated]
|
||||
);
|
||||
|
||||
const onRemoveBehavior = React.useCallback(
|
||||
(behaviorName: string) => {
|
||||
let message =
|
||||
"Are you sure you want to remove this behavior? This can't be undone.";
|
||||
const dependentBehaviors = gd.WholeProjectRefactorer.findDependentBehaviorNames(
|
||||
project,
|
||||
object,
|
||||
behaviorName
|
||||
).toJSArray();
|
||||
if (dependentBehaviors.length > 0) {
|
||||
message +=
|
||||
'\nDependent behaviors will be removed too: ' +
|
||||
dependentBehaviors.join(', ');
|
||||
}
|
||||
const answer = Window.showConfirmDialog(message);
|
||||
|
||||
if (answer) {
|
||||
object.removeBehavior(behaviorName);
|
||||
dependentBehaviors.forEach(name => object.removeBehavior(name));
|
||||
if (onSizeUpdated) onSizeUpdated();
|
||||
}
|
||||
if (onBehaviorsUpdated) onBehaviorsUpdated();
|
||||
},
|
||||
[object, onBehaviorsUpdated, onSizeUpdated, project]
|
||||
);
|
||||
|
||||
const copyBehavior = React.useCallback(
|
||||
(behaviorName: string) => {
|
||||
const behavior = object.getBehavior(behaviorName);
|
||||
Clipboard.set(BEHAVIORS_CLIPBOARD_KIND, [
|
||||
{
|
||||
name: behaviorName,
|
||||
type: behavior.getTypeName(),
|
||||
serializedBehavior: serializeToJSObject(behavior),
|
||||
},
|
||||
]);
|
||||
forceUpdate();
|
||||
},
|
||||
[forceUpdate, object]
|
||||
);
|
||||
|
||||
const copyAllBehaviors = React.useCallback(
|
||||
() => {
|
||||
Clipboard.set(
|
||||
BEHAVIORS_CLIPBOARD_KIND,
|
||||
mapVector(object.getAllBehaviorNames(), behaviorName => {
|
||||
const behavior = object.getBehavior(behaviorName);
|
||||
return {
|
||||
name: behaviorName,
|
||||
type: behavior.getTypeName(),
|
||||
serializedBehavior: serializeToJSObject(behavior),
|
||||
};
|
||||
})
|
||||
);
|
||||
forceUpdate();
|
||||
},
|
||||
[forceUpdate, object]
|
||||
);
|
||||
|
||||
const pasteBehaviors = React.useCallback(
|
||||
async () => {
|
||||
const clipboardContent = Clipboard.get(BEHAVIORS_CLIPBOARD_KIND);
|
||||
const behaviorContents = SafeExtractor.extractArray(clipboardContent);
|
||||
if (!behaviorContents) return;
|
||||
|
||||
const newNamedBehaviors: Array<{
|
||||
name: string,
|
||||
type: string,
|
||||
serializedBehavior: string,
|
||||
}> = [];
|
||||
const existingNamedBehaviors: Array<{
|
||||
name: string,
|
||||
type: string,
|
||||
serializedBehavior: string,
|
||||
}> = [];
|
||||
const existingBehaviorFullNames: Array<string> = [];
|
||||
behaviorContents.forEach(behaviorContent => {
|
||||
const name = SafeExtractor.extractStringProperty(
|
||||
behaviorContent,
|
||||
'name'
|
||||
);
|
||||
const type = SafeExtractor.extractStringProperty(
|
||||
behaviorContent,
|
||||
'type'
|
||||
);
|
||||
const serializedBehavior = SafeExtractor.extractObjectProperty(
|
||||
behaviorContent,
|
||||
'serializedBehavior'
|
||||
);
|
||||
if (!name || !type || !serializedBehavior) {
|
||||
return;
|
||||
}
|
||||
|
||||
const behaviorMetadata = gd.MetadataProvider.getBehaviorMetadata(
|
||||
project.getCurrentPlatform(),
|
||||
type
|
||||
);
|
||||
if (
|
||||
behaviorMetadata.getObjectType() !== '' &&
|
||||
behaviorMetadata.getObjectType() !== object.getType()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (object.hasBehaviorNamed(name)) {
|
||||
const existingBehavior = object.getBehavior(name);
|
||||
if (existingBehavior.getTypeName() !== type) {
|
||||
return;
|
||||
}
|
||||
existingNamedBehaviors.push({ name, type, serializedBehavior });
|
||||
existingBehaviorFullNames.push(behaviorMetadata.getFullName());
|
||||
} else {
|
||||
newNamedBehaviors.push({ name, type, serializedBehavior });
|
||||
}
|
||||
});
|
||||
setJustAddedBehaviorName(defaultName);
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
if (props.onSizeUpdated) props.onSizeUpdated();
|
||||
props.onUpdateBehaviorsSharedData();
|
||||
if (props.onBehaviorsUpdated) props.onBehaviorsUpdated();
|
||||
};
|
||||
let firstAddedBehaviorName: string | null = null;
|
||||
newNamedBehaviors.forEach(({ name, type, serializedBehavior }) => {
|
||||
object.addNewBehavior(project, type, name);
|
||||
if (object.hasBehaviorNamed(name)) {
|
||||
if (!firstAddedBehaviorName) {
|
||||
firstAddedBehaviorName = name;
|
||||
}
|
||||
const behavior = object.getBehavior(name);
|
||||
unserializeFromJSObject(behavior, serializedBehavior);
|
||||
}
|
||||
});
|
||||
// Add missing required behaviors as a 2nd step because these behaviors
|
||||
// could have been in the array.
|
||||
newNamedBehaviors.forEach(({ name }) => {
|
||||
gd.WholeProjectRefactorer.addRequiredBehaviorsFor(
|
||||
project,
|
||||
object,
|
||||
name
|
||||
);
|
||||
});
|
||||
|
||||
const onChangeBehaviorName = (behavior: gdBehavior, newName: string) => {
|
||||
// TODO: This is disabled for now as there is no proper refactoring
|
||||
// of events after a behavior renaming. Once refactoring is available,
|
||||
// the text field can be enabled again and refactoring calls added here
|
||||
// (or in a parent).
|
||||
// Renaming a behavior is something that is really rare anyway! :)
|
||||
let shouldOverrideBehaviors = false;
|
||||
if (existingNamedBehaviors.length > 0) {
|
||||
shouldOverrideBehaviors = await showBehaviorOverridingConfirmation(
|
||||
existingBehaviorFullNames
|
||||
);
|
||||
|
||||
if (object.hasBehaviorNamed(newName)) return;
|
||||
object.renameBehavior(behavior.getName(), newName);
|
||||
forceUpdate();
|
||||
if (props.onBehaviorsUpdated) props.onBehaviorsUpdated();
|
||||
};
|
||||
if (shouldOverrideBehaviors) {
|
||||
existingNamedBehaviors.forEach(
|
||||
({ name, type, serializedBehavior }) => {
|
||||
if (object.hasBehaviorNamed(name)) {
|
||||
const behavior = object.getBehavior(name);
|
||||
// Property values can be replaced directly because the type has been check earlier.
|
||||
unserializeFromJSObject(behavior, serializedBehavior);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const onRemoveBehavior = (behaviorName: string) => {
|
||||
let message =
|
||||
"Are you sure you want to remove this behavior? This can't be undone.";
|
||||
const dependentBehaviors = gd.WholeProjectRefactorer.findDependentBehaviorNames(
|
||||
project,
|
||||
forceUpdate();
|
||||
if (firstAddedBehaviorName) {
|
||||
setJustAddedBehaviorName(firstAddedBehaviorName);
|
||||
if (onSizeUpdated) onSizeUpdated();
|
||||
onUpdateBehaviorsSharedData();
|
||||
} else if (existingNamedBehaviors.length === 1) {
|
||||
setJustAddedBehaviorName(existingNamedBehaviors[0].name);
|
||||
}
|
||||
if (firstAddedBehaviorName || shouldOverrideBehaviors) {
|
||||
if (onBehaviorsUpdated) onBehaviorsUpdated();
|
||||
}
|
||||
},
|
||||
[
|
||||
forceUpdate,
|
||||
object,
|
||||
behaviorName
|
||||
).toJSArray();
|
||||
if (dependentBehaviors.length > 0) {
|
||||
message +=
|
||||
'\nDependent behaviors will be removed too: ' +
|
||||
dependentBehaviors.join(', ');
|
||||
}
|
||||
const answer = Window.showConfirmDialog(message);
|
||||
onBehaviorsUpdated,
|
||||
onSizeUpdated,
|
||||
onUpdateBehaviorsSharedData,
|
||||
project,
|
||||
showBehaviorOverridingConfirmation,
|
||||
]
|
||||
);
|
||||
|
||||
if (answer) {
|
||||
object.removeBehavior(behaviorName);
|
||||
dependentBehaviors.forEach(name => object.removeBehavior(name));
|
||||
if (props.onSizeUpdated) props.onSizeUpdated();
|
||||
}
|
||||
if (props.onBehaviorsUpdated) props.onBehaviorsUpdated();
|
||||
};
|
||||
const openExtension = React.useCallback(
|
||||
(behaviorType: string) => {
|
||||
const elements = behaviorType.split('::');
|
||||
if (elements.length !== 2) {
|
||||
return;
|
||||
}
|
||||
const extensionName = elements[0];
|
||||
const behaviorName = elements[1];
|
||||
|
||||
const openExtension = (behaviorType: string) => {
|
||||
const elements = behaviorType.split('::');
|
||||
if (elements.length !== 2) {
|
||||
return;
|
||||
}
|
||||
const extensionName = elements[0];
|
||||
const behaviorName = elements[1];
|
||||
if (
|
||||
!extensionName ||
|
||||
!project.hasEventsFunctionsExtensionNamed(extensionName)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
openBehaviorEvents(extensionName, behaviorName);
|
||||
},
|
||||
[openBehaviorEvents, project]
|
||||
);
|
||||
|
||||
if (
|
||||
!extensionName ||
|
||||
!props.project.hasEventsFunctionsExtensionNamed(extensionName)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
props.openBehaviorEvents(extensionName, behaviorName);
|
||||
};
|
||||
const isClipboardContainingBehaviors = Clipboard.has(
|
||||
BEHAVIORS_CLIPBOARD_KIND
|
||||
);
|
||||
|
||||
return (
|
||||
<Column noMargin expand useFullHeight noOverflowParent>
|
||||
@@ -171,11 +389,18 @@ const BehaviorsEditor = (props: Props) => {
|
||||
Behaviors add features to objects in a matter of clicks.
|
||||
</Trans>
|
||||
}
|
||||
actionLabel={<Trans>Add a behavior</Trans>}
|
||||
helpPagePath="/behaviors"
|
||||
tutorialId="intro-behaviors-and-functions"
|
||||
actionButtonId="add-behavior-button"
|
||||
actionLabel={<Trans>Add a behavior</Trans>}
|
||||
onAction={() => setNewBehaviorDialogOpen(true)}
|
||||
secondaryActionIcon={<PasteIcon />}
|
||||
secondaryActionLabel={
|
||||
isClipboardContainingBehaviors ? <Trans>Paste</Trans> : null
|
||||
}
|
||||
onSecondaryAction={() => {
|
||||
pasteBehaviors();
|
||||
}}
|
||||
/>
|
||||
</Column>
|
||||
) : (
|
||||
@@ -272,6 +497,15 @@ const BehaviorsEditor = (props: Props) => {
|
||||
label: i18n._(t`Delete`),
|
||||
click: () => onRemoveBehavior(behaviorName),
|
||||
},
|
||||
{
|
||||
label: i18n._(t`Copy`),
|
||||
click: () => copyBehavior(behaviorName),
|
||||
},
|
||||
{
|
||||
label: i18n._(t`Paste`),
|
||||
click: pasteBehaviors,
|
||||
enabled: isClipboardContainingBehaviors,
|
||||
},
|
||||
...(project.hasEventsBasedBehavior(behaviorTypeName)
|
||||
? [
|
||||
{ type: 'separator' },
|
||||
@@ -333,7 +567,7 @@ const BehaviorsEditor = (props: Props) => {
|
||||
resourceManagementProps={
|
||||
props.resourceManagementProps
|
||||
}
|
||||
onBehaviorUpdated={props.onBehaviorsUpdated}
|
||||
onBehaviorUpdated={onBehaviorsUpdated}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
@@ -343,16 +577,37 @@ const BehaviorsEditor = (props: Props) => {
|
||||
})}
|
||||
</ScrollView>
|
||||
<Column>
|
||||
<Line justifyContent="flex-end" expand>
|
||||
<RaisedButton
|
||||
key="add-behavior-line"
|
||||
label={<Trans>Add a behavior</Trans>}
|
||||
primary
|
||||
onClick={() => setNewBehaviorDialogOpen(true)}
|
||||
icon={<Add />}
|
||||
id="add-behavior-button"
|
||||
/>
|
||||
</Line>
|
||||
<LineStackLayout noMargin>
|
||||
<LineStackLayout expand>
|
||||
<FlatButton
|
||||
key={'copy-all-behaviors'}
|
||||
leftIcon={<CopyIcon />}
|
||||
label={isSmall ? '' : <Trans>Copy all behaviors</Trans>}
|
||||
onClick={() => {
|
||||
copyAllBehaviors();
|
||||
}}
|
||||
/>
|
||||
<FlatButton
|
||||
key={'paste-behaviors'}
|
||||
leftIcon={<PasteIcon />}
|
||||
label={isSmall ? '' : <Trans>Paste</Trans>}
|
||||
onClick={() => {
|
||||
pasteBehaviors();
|
||||
}}
|
||||
disabled={!isClipboardContainingBehaviors}
|
||||
/>
|
||||
</LineStackLayout>
|
||||
<LineStackLayout justifyContent="flex-end" expand>
|
||||
<RaisedButton
|
||||
key="add-behavior-line"
|
||||
label={<Trans>Add a behavior</Trans>}
|
||||
primary
|
||||
onClick={() => setNewBehaviorDialogOpen(true)}
|
||||
icon={<Add />}
|
||||
id="add-behavior-button"
|
||||
/>
|
||||
</LineStackLayout>
|
||||
</LineStackLayout>
|
||||
</Column>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
@@ -3,10 +3,10 @@ import * as React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Container from '@material-ui/core/Container';
|
||||
import { ColumnStackLayout } from './Layout';
|
||||
import { LineStackLayout } from '../UI/Layout';
|
||||
import { ResponsiveLineStackLayout } from './Layout';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import { Column, Line, LargeSpacer } from './Grid';
|
||||
import { Column, LargeSpacer } from './Grid';
|
||||
import HelpButton from '../UI/HelpButton';
|
||||
import Text from '../UI/Text';
|
||||
import TutorialButton from './TutorialButton';
|
||||
@@ -64,7 +64,7 @@ export const EmptyPlaceholder = (props: Props) => (
|
||||
</Text>
|
||||
<LargeSpacer />
|
||||
<ColumnStackLayout alignItems="center" noMargin>
|
||||
<LineStackLayout noMargin>
|
||||
<ResponsiveLineStackLayout noMargin>
|
||||
{props.secondaryActionLabel && props.onSecondaryAction && (
|
||||
<FlatButton
|
||||
label={props.secondaryActionLabel}
|
||||
@@ -90,7 +90,7 @@ export const EmptyPlaceholder = (props: Props) => (
|
||||
}
|
||||
id={props.actionButtonId}
|
||||
/>
|
||||
</LineStackLayout>
|
||||
</ResponsiveLineStackLayout>
|
||||
{props.tutorialId ? (
|
||||
<TutorialButton
|
||||
tutorialId={props.tutorialId}
|
||||
|
@@ -63,10 +63,10 @@ const FlatButton = React.forwardRef<Props, ButtonInterface>(
|
||||
ref={ref}
|
||||
>
|
||||
{leftIcon}
|
||||
{leftIcon && <Spacer />}
|
||||
{leftIcon && label && <Spacer />}
|
||||
{/* span element is required to prevent browser auto translators to crash the app - See https://github.com/4ian/GDevelop/issues/3453 */}
|
||||
{label ? <span>{label}</span> : null}
|
||||
{rightIcon && <Spacer />}
|
||||
{rightIcon && label && <Spacer />}
|
||||
{rightIcon}
|
||||
</Button>
|
||||
);
|
||||
|
@@ -7,6 +7,7 @@ import paperDecorator from '../PaperDecorator';
|
||||
|
||||
import { EmptyPlaceholder } from '../../UI/EmptyPlaceholder';
|
||||
import FixedHeightFlexContainer from '../FixedHeightFlexContainer';
|
||||
import PasteIcon from '../../UI/CustomSvgIcons/Clipboard';
|
||||
|
||||
export default {
|
||||
title: 'UI Building Blocks/EmptyPlaceholder',
|
||||
@@ -28,3 +29,22 @@ export const Default = () => (
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
);
|
||||
|
||||
export const WithSecondaryAction = () => (
|
||||
<FixedHeightFlexContainer
|
||||
height={500}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<EmptyPlaceholder
|
||||
title="Add your first event"
|
||||
description="You can use events to create cause and effect."
|
||||
actionLabel="Add something"
|
||||
helpPagePath="/objects/tiled_sprite"
|
||||
onAction={action('onAdd')}
|
||||
secondaryActionIcon={<PasteIcon />}
|
||||
secondaryActionLabel="Paste"
|
||||
onSecondaryAction={action('onAdd')}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
);
|
||||
|
Reference in New Issue
Block a user