mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
7 Commits
scene-edit
...
refactor/u
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a97948cdf3 | ||
![]() |
0a86424f15 | ||
![]() |
95930c5bfd | ||
![]() |
fac1246445 | ||
![]() |
9772520d24 | ||
![]() |
86eef2dab4 | ||
![]() |
809c04f88e |
@@ -93,6 +93,9 @@ class GD_CORE_API EventsFunctionsContainer
|
||||
void MoveEventsFunction(std::size_t oldIndex, std::size_t newIndex) {
|
||||
return Move(oldIndex, newIndex);
|
||||
};
|
||||
std::size_t GetEventsFunctionPosition(const gd::EventsFunction& eventsFunction) {
|
||||
return GetPosition(eventsFunction);
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Provide a raw access to the vector containing the functions.
|
||||
@@ -141,4 +144,4 @@ class GD_CORE_API EventsFunctionsContainer
|
||||
} // namespace gd
|
||||
|
||||
#endif // GDCORE_EVENTSFUNCTIONSCONTAINER_H
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -425,7 +425,7 @@ class GD_CORE_API ResourcesManager {
|
||||
bool MoveResourceDownInList(const gd::String& name);
|
||||
|
||||
/**
|
||||
* Change the position of the specified resource.
|
||||
* \brief Change the position of the specified resource.
|
||||
*/
|
||||
void MoveResource(std::size_t oldIndex, std::size_t newIndex);
|
||||
|
||||
|
@@ -129,6 +129,11 @@ class SerializableWithNameList {
|
||||
*/
|
||||
bool Has(const gd::String& name) const;
|
||||
|
||||
/**
|
||||
* \brief Get the position of an element in the list
|
||||
*/
|
||||
std::size_t GetPosition(const T& element) const;
|
||||
|
||||
/** \name std::vector-like API
|
||||
* These functions ensure that the class can be used just like a std::vector
|
||||
* for iterations.
|
||||
|
@@ -109,6 +109,15 @@ void SerializableWithNameList<T>::Move(std::size_t oldIndex,
|
||||
elements.insert(elements.begin() + newIndex, std::move(object));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t SerializableWithNameList<T>::GetPosition(const T& element) const {
|
||||
for(std::size_t index = 0;index<elements.size();++index) {
|
||||
if (&element == elements[index].get()) return index;
|
||||
}
|
||||
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SerializableWithNameList<T>::SerializeElementsTo(
|
||||
const gd::String& elementName, SerializerElement& serializerElement) const {
|
||||
|
@@ -1683,6 +1683,7 @@ interface EventsFunctionsContainer {
|
||||
void RemoveEventsFunction([Const] DOMString name);
|
||||
void MoveEventsFunction(unsigned long oldIndex, unsigned long newIndex);
|
||||
unsigned long GetEventsFunctionsCount();
|
||||
unsigned long GetEventsFunctionPosition([Const, Ref] EventsFunction eventsFunction);
|
||||
};
|
||||
|
||||
interface EventsBasedBehavior {
|
||||
@@ -1717,6 +1718,7 @@ interface EventsBasedBehaviorsList {
|
||||
void Remove([Const] DOMString name);
|
||||
void Move(unsigned long oldIndex, unsigned long newIndex);
|
||||
unsigned long GetCount();
|
||||
unsigned long GetPosition([Const, Ref] EventsBasedBehavior item);
|
||||
|
||||
unsigned long size();
|
||||
[Ref] EventsBasedBehavior at(unsigned long index);
|
||||
@@ -1772,6 +1774,7 @@ interface EventsFunctionsExtension {
|
||||
void RemoveEventsFunction([Const] DOMString name);
|
||||
void MoveEventsFunction(unsigned long oldIndex, unsigned long newIndex);
|
||||
unsigned long GetEventsFunctionsCount();
|
||||
unsigned long GetEventsFunctionPosition([Const, Ref] EventsFunction eventsFunction);
|
||||
};
|
||||
|
||||
interface AbstractFileSystem {
|
||||
|
@@ -30,6 +30,10 @@
|
||||
<!-- Root div used for React `App` component rendering-->
|
||||
<div id="root"></div>
|
||||
|
||||
<script>
|
||||
window["GD_STARTUP_TIMES"] = [["indexHtmlFirstScriptStarted", performance.now()]];
|
||||
</script>
|
||||
|
||||
<!-- GDevelop.js core -->
|
||||
<script>
|
||||
//Ensure module is not defined to avoid GDevelop.js to think
|
||||
@@ -40,6 +44,11 @@
|
||||
</script>
|
||||
<script src="%PUBLIC_URL%/libGD.js"></script>
|
||||
|
||||
<script>
|
||||
window["GD_STARTUP_TIMES"] = window["GD_STARTUP_TIMES"] || [];
|
||||
window["GD_STARTUP_TIMES"].push(["libGDLoadedTime", performance.now()]);
|
||||
</script>
|
||||
|
||||
<!-- Monaco Editor support -->
|
||||
<script>
|
||||
// Monaco editor is using amd require. Save the Node.js require if it exists (Electron)
|
||||
@@ -65,5 +74,10 @@
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
window["GD_STARTUP_TIMES"] = window["GD_STARTUP_TIMES"] || [];
|
||||
window["GD_STARTUP_TIMES"].push(["monacoEditorLoaderLoaded", performance.now()]);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -31,6 +31,10 @@ type State = {|
|
||||
searchText: string,
|
||||
|};
|
||||
|
||||
const getEventsBasedBehaviorName = (
|
||||
eventsBasedBehavior: gdEventsBasedBehavior
|
||||
) => eventsBasedBehavior.getName();
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
eventsBasedBehaviorsList: gdEventsBasedBehaviorsList,
|
||||
@@ -69,7 +73,7 @@ export default class EventsBasedBehaviorsList extends React.Component<
|
||||
) => cb(true),
|
||||
};
|
||||
|
||||
sortableList: any;
|
||||
sortableList: ?SortableVirtualizedItemList<gdEventsFunction>;
|
||||
state: State = {
|
||||
renamedEventsBasedBehavior: null,
|
||||
searchText: '',
|
||||
@@ -102,7 +106,9 @@ export default class EventsBasedBehaviorsList extends React.Component<
|
||||
{
|
||||
renamedEventsBasedBehavior,
|
||||
},
|
||||
() => this.sortableList.getWrappedInstance().forceUpdateGrid()
|
||||
() => {
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -131,16 +137,26 @@ export default class EventsBasedBehaviorsList extends React.Component<
|
||||
);
|
||||
};
|
||||
|
||||
_move = (oldIndex: number, newIndex: number) => {
|
||||
const { eventsBasedBehaviorsList } = this.props;
|
||||
eventsBasedBehaviorsList.move(oldIndex, newIndex);
|
||||
_moveSelectionTo = (
|
||||
destinationEventsBasedBehavior: gdEventsBasedBehavior
|
||||
) => {
|
||||
const {
|
||||
eventsBasedBehaviorsList,
|
||||
selectedEventsBasedBehavior,
|
||||
} = this.props;
|
||||
if (!selectedEventsBasedBehavior) return;
|
||||
|
||||
eventsBasedBehaviorsList.move(
|
||||
eventsBasedBehaviorsList.getPosition(selectedEventsBasedBehavior),
|
||||
eventsBasedBehaviorsList.getPosition(destinationEventsBasedBehavior)
|
||||
);
|
||||
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
forceUpdateList = () => {
|
||||
this.forceUpdate();
|
||||
this.sortableList.getWrappedInstance().forceUpdateGrid();
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
};
|
||||
|
||||
_copyEventsBasedBehavior = (eventsBasedBehavior: gdEventsBasedBehavior) => {
|
||||
@@ -250,15 +266,10 @@ export default class EventsBasedBehaviorsList extends React.Component<
|
||||
} = this.props;
|
||||
const { searchText } = this.state;
|
||||
|
||||
const list = [
|
||||
...filterEventsBasedBehaviorsList(
|
||||
enumerateEventsBasedBehaviors(eventsBasedBehaviorsList),
|
||||
searchText
|
||||
),
|
||||
{
|
||||
key: 'add-item-row',
|
||||
},
|
||||
];
|
||||
const list = filterEventsBasedBehaviorsList(
|
||||
enumerateEventsBasedBehaviors(eventsBasedBehaviorsList),
|
||||
searchText
|
||||
);
|
||||
|
||||
// Force List component to be mounted again if project or eventsBasedBehaviorsList
|
||||
// has been changed. Avoid accessing to invalid objects that could
|
||||
@@ -278,16 +289,18 @@ export default class EventsBasedBehaviorsList extends React.Component<
|
||||
height={height}
|
||||
onAddNewItem={this._addNewEventsBasedBehavior}
|
||||
addNewItemLabel={<Trans>Add a new behavior</Trans>}
|
||||
selectedItem={selectedEventsBasedBehavior}
|
||||
getItemName={getEventsBasedBehaviorName}
|
||||
selectedItems={
|
||||
selectedEventsBasedBehavior
|
||||
? [selectedEventsBasedBehavior]
|
||||
: []
|
||||
}
|
||||
onItemSelected={onSelectEventsBasedBehavior}
|
||||
renamedItem={this.state.renamedEventsBasedBehavior}
|
||||
onRename={this._rename}
|
||||
onSortEnd={({ oldIndex, newIndex }) =>
|
||||
this._move(oldIndex, newIndex)
|
||||
}
|
||||
onMoveSelectionToItem={this._moveSelectionTo}
|
||||
buildMenuTemplate={this._renderEventsBasedBehaviorMenuTemplate}
|
||||
helperClass="sortable-helper"
|
||||
distance={20}
|
||||
reactDndType="GD_EVENTS_BASED_BEHAVIOR"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
@@ -26,6 +26,9 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
const getEventsFunctionName = (eventsFunction: gdEventsFunction) =>
|
||||
eventsFunction.getName();
|
||||
|
||||
type State = {|
|
||||
renamedEventsFunction: ?gdEventsFunction,
|
||||
searchText: string,
|
||||
@@ -97,7 +100,9 @@ export default class EventsFunctionsList extends React.Component<Props, State> {
|
||||
{
|
||||
renamedEventsFunction: eventsFunction,
|
||||
},
|
||||
() => this.sortableList.getWrappedInstance().forceUpdateGrid()
|
||||
() => {
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -121,16 +126,25 @@ export default class EventsFunctionsList extends React.Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
_move = (oldIndex: number, newIndex: number) => {
|
||||
const { eventsFunctionsContainer } = this.props;
|
||||
eventsFunctionsContainer.moveEventsFunction(oldIndex, newIndex);
|
||||
_moveSelectionTo = (destinationEventsFunction: gdEventsFunction) => {
|
||||
const { eventsFunctionsContainer, selectedEventsFunction } = this.props;
|
||||
if (!selectedEventsFunction) return;
|
||||
|
||||
eventsFunctionsContainer.moveEventsFunction(
|
||||
eventsFunctionsContainer.getEventsFunctionPosition(
|
||||
selectedEventsFunction
|
||||
),
|
||||
eventsFunctionsContainer.getEventsFunctionPosition(
|
||||
destinationEventsFunction
|
||||
)
|
||||
);
|
||||
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
forceUpdateList = () => {
|
||||
this.forceUpdate();
|
||||
this.sortableList.getWrappedInstance().forceUpdateGrid();
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
};
|
||||
|
||||
_copyEventsFunction = (eventsFunction: gdEventsFunction) => {
|
||||
@@ -243,15 +257,10 @@ export default class EventsFunctionsList extends React.Component<Props, State> {
|
||||
} = this.props;
|
||||
const { searchText } = this.state;
|
||||
|
||||
const list = [
|
||||
...filterEventFunctionsList(
|
||||
enumerateEventsFunctions(eventsFunctionsContainer),
|
||||
searchText
|
||||
),
|
||||
{
|
||||
key: 'add-item-row',
|
||||
},
|
||||
];
|
||||
const list = filterEventFunctionsList(
|
||||
enumerateEventsFunctions(eventsFunctionsContainer),
|
||||
searchText
|
||||
);
|
||||
|
||||
// Force List component to be mounted again if project or objectsContainer
|
||||
// has been changed. Avoid accessing to invalid objects that could
|
||||
@@ -272,16 +281,16 @@ export default class EventsFunctionsList extends React.Component<Props, State> {
|
||||
height={height}
|
||||
onAddNewItem={this._addNewEventsFunction}
|
||||
addNewItemLabel={<Trans>Add a new function</Trans>}
|
||||
selectedItem={selectedEventsFunction}
|
||||
getItemName={getEventsFunctionName}
|
||||
selectedItems={
|
||||
selectedEventsFunction ? [selectedEventsFunction] : []
|
||||
}
|
||||
onItemSelected={onSelectEventsFunction}
|
||||
renamedItem={this.state.renamedEventsFunction}
|
||||
onRename={this._rename}
|
||||
onSortEnd={({ oldIndex, newIndex }) =>
|
||||
this._move(oldIndex, newIndex)
|
||||
}
|
||||
onMoveSelectionToItem={this._moveSelectionTo}
|
||||
buildMenuTemplate={this._renderEventsFunctionMenuTemplate}
|
||||
helperClass="sortable-helper"
|
||||
distance={20}
|
||||
reactDndType="GD_EVENTS_FUNCTION"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
@@ -2,6 +2,9 @@
|
||||
import * as React from 'react';
|
||||
import { dropIndicator, cantDropIndicator } from './ClassNames';
|
||||
|
||||
/**
|
||||
* A Drop indicator line for the events sheet
|
||||
*/
|
||||
export default function DropIndicator({ canDrop }: {| canDrop: boolean |}) {
|
||||
return <div className={canDrop ? dropIndicator : cantDropIndicator} />;
|
||||
}
|
||||
|
@@ -17,19 +17,10 @@ import {
|
||||
type InstructionContext,
|
||||
} from '../SelectionHandler';
|
||||
import InstructionsList from './InstructionsList';
|
||||
import {
|
||||
DragSource,
|
||||
type DragSourceMonitor,
|
||||
type DragSourceConnector,
|
||||
type ConnectDragSource,
|
||||
DropTarget,
|
||||
type DropTargetMonitor,
|
||||
type DropTargetConnector,
|
||||
type ConnectDropTarget,
|
||||
} from 'react-dnd';
|
||||
import DropIndicator from './DropIndicator';
|
||||
import ParameterRenderingService from '../ParameterRenderingService';
|
||||
import InvalidParameterValue from './InvalidParameterValue';
|
||||
import { makeDragSourceAndDropTarget } from '../../UI/DragAndDrop/DragSourceAndDropTarget';
|
||||
const gd = global.gd;
|
||||
const instrFormatter = gd.InstructionSentenceFormatter.get();
|
||||
instrFormatter.loadTypesFormattingFromConfig();
|
||||
@@ -43,15 +34,11 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
type DragSourceProps = {|
|
||||
connectDragSource: ConnectDragSource,
|
||||
|};
|
||||
export const reactDndInstructionType = 'GD_DRAGGED_INSTRUCTION';
|
||||
|
||||
type DropTargetProps = {|
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
|};
|
||||
const DragSourceAndDropTarget = makeDragSourceAndDropTarget<{
|
||||
isCondition: boolean,
|
||||
}>(reactDndInstructionType);
|
||||
|
||||
type Props = {|
|
||||
instruction: gdInstruction,
|
||||
@@ -63,9 +50,6 @@ type Props = {|
|
||||
onContextMenu: (x: number, y: number) => void,
|
||||
onMoveToInstruction: () => void,
|
||||
|
||||
...DragSourceProps,
|
||||
...DropTargetProps,
|
||||
|
||||
// For potential sub-instructions list:
|
||||
selection: Object,
|
||||
onAddNewSubInstruction: InstructionsListContext => void,
|
||||
@@ -91,7 +75,7 @@ type Props = {|
|
||||
renderObjectThumbnail: string => React.Node,
|
||||
|};
|
||||
|
||||
class Instruction extends React.Component<Props, *> {
|
||||
export default class Instruction extends React.Component<Props> {
|
||||
/**
|
||||
* Render the different parts of the text of the instruction.
|
||||
* Parameter can have formatting, be hovered and clicked. The rest
|
||||
@@ -156,12 +140,11 @@ class Instruction extends React.Component<Props, *> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { instruction, isCondition } = this.props;
|
||||
const {
|
||||
connectDragSource,
|
||||
connectDropTarget,
|
||||
isOver,
|
||||
canDrop,
|
||||
instruction,
|
||||
isCondition,
|
||||
onClick,
|
||||
onMoveToInstruction,
|
||||
} = this.props;
|
||||
|
||||
//TODO: Metadata could be cached for performance boost.
|
||||
@@ -175,158 +158,121 @@ class Instruction extends React.Component<Props, *> {
|
||||
instruction.getType()
|
||||
);
|
||||
|
||||
const instructionDiv = connectDropTarget(
|
||||
// $FlowFixMe
|
||||
connectDragSource(
|
||||
<div
|
||||
style={styles.container}
|
||||
className={classNames({
|
||||
[selectableArea]: true,
|
||||
[selectedArea]: this.props.selected,
|
||||
})}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onClick();
|
||||
}}
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onDoubleClick();
|
||||
}}
|
||||
onContextMenu={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onContextMenu(e.clientX, e.clientY);
|
||||
}}
|
||||
onKeyPress={event => {
|
||||
if (event.key === 'Enter') {
|
||||
this.props.onDoubleClick();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
} else if (event.key === ' ') {
|
||||
this.props.onClick();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
{instruction.isInverted() && (
|
||||
<img
|
||||
className={classNames({
|
||||
[icon]: true,
|
||||
})}
|
||||
src="res/contraire.png"
|
||||
alt="Condition is negated"
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
className={classNames({
|
||||
[icon]: true,
|
||||
})}
|
||||
src={metadata.getSmallIconFilename()}
|
||||
alt=""
|
||||
/>
|
||||
{this._renderInstructionText(metadata)}
|
||||
{metadata.canHaveSubInstructions() && (
|
||||
<InstructionsList
|
||||
style={
|
||||
{} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
extraClassName={subInstructionsContainer}
|
||||
instrsList={instruction.getSubInstructions()}
|
||||
areConditions={this.props.isCondition}
|
||||
selection={this.props.selection}
|
||||
onAddNewInstruction={this.props.onAddNewSubInstruction}
|
||||
onPasteInstructions={this.props.onPasteSubInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToSubInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToSubInstructionsList}
|
||||
onInstructionClick={this.props.onSubInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onSubInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onSubInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onSubInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onSubParameterClick}
|
||||
addButtonLabel={<Trans>Add a sub-condition</Trans>}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
return (
|
||||
<DragSourceAndDropTarget
|
||||
beginDrag={() => {
|
||||
onClick(); // Select the dragged instruction
|
||||
|
||||
return isOver ? (
|
||||
<React.Fragment>
|
||||
<DropIndicator canDrop={canDrop} />
|
||||
{instructionDiv}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
instructionDiv || null
|
||||
// No need to save here what is being dragged,
|
||||
// as its the entire selection that is considered to be dragged.
|
||||
return {
|
||||
isCondition,
|
||||
};
|
||||
}}
|
||||
canDrop={draggedItem => draggedItem.isCondition === isCondition}
|
||||
drop={() => {
|
||||
onMoveToInstruction();
|
||||
}}
|
||||
>
|
||||
{({ connectDragSource, connectDropTarget, isOver, canDrop }) => {
|
||||
// The instruction itself can be dragged and is a target for
|
||||
// another instruction to be dropped. It's IMPORTANT NOT to have
|
||||
// the subinstructions list inside the connectDropTarget/connectDragSource
|
||||
// as otherwise this can confuse react-dnd ("Expected to find a valid target")
|
||||
// (surely due to components re-mounting/rerendering ?).
|
||||
const instructionElement = connectDropTarget(
|
||||
connectDragSource(
|
||||
<div
|
||||
style={styles.container}
|
||||
className={classNames({
|
||||
[selectableArea]: true,
|
||||
[selectedArea]: this.props.selected,
|
||||
})}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onClick();
|
||||
}}
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onDoubleClick();
|
||||
}}
|
||||
onContextMenu={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onContextMenu(e.clientX, e.clientY);
|
||||
}}
|
||||
onKeyPress={event => {
|
||||
if (event.key === 'Enter') {
|
||||
this.props.onDoubleClick();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
} else if (event.key === ' ') {
|
||||
this.props.onClick();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
{instruction.isInverted() && (
|
||||
<img
|
||||
className={classNames({
|
||||
[icon]: true,
|
||||
})}
|
||||
src="res/contraire.png"
|
||||
alt="Condition is negated"
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
className={classNames({
|
||||
[icon]: true,
|
||||
})}
|
||||
src={metadata.getSmallIconFilename()}
|
||||
alt=""
|
||||
/>
|
||||
{this._renderInstructionText(metadata)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{isOver && <DropIndicator canDrop={canDrop} />}
|
||||
{instructionElement}
|
||||
{metadata.canHaveSubInstructions() && (
|
||||
<InstructionsList
|
||||
style={
|
||||
{} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
extraClassName={subInstructionsContainer}
|
||||
instrsList={instruction.getSubInstructions()}
|
||||
areConditions={this.props.isCondition}
|
||||
selection={this.props.selection}
|
||||
onAddNewInstruction={this.props.onAddNewSubInstruction}
|
||||
onPasteInstructions={this.props.onPasteSubInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToSubInstruction}
|
||||
onMoveToInstructionsList={
|
||||
this.props.onMoveToSubInstructionsList
|
||||
}
|
||||
onInstructionClick={this.props.onSubInstructionClick}
|
||||
onInstructionDoubleClick={
|
||||
this.props.onSubInstructionDoubleClick
|
||||
}
|
||||
onInstructionContextMenu={
|
||||
this.props.onSubInstructionContextMenu
|
||||
}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onSubInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onSubParameterClick}
|
||||
addButtonLabel={<Trans>Add a sub-condition</Trans>}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}}
|
||||
</DragSourceAndDropTarget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Drag'n'drop support:
|
||||
|
||||
export const reactDndInstructionType = 'GD_DRAGGED_INSTRUCTION';
|
||||
|
||||
type InstructionSourceProps = {
|
||||
onClick: () => void,
|
||||
isCondition: boolean,
|
||||
};
|
||||
|
||||
const instructionSource = {
|
||||
beginDrag(props: InstructionSourceProps) {
|
||||
props.onClick(); // Select the dragged instruction
|
||||
return {
|
||||
// No need to save here what is being dragged,
|
||||
// as its the entire selection that is considered to be dragged.
|
||||
isCondition: props.isCondition,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
function sourceCollect(
|
||||
connect: DragSourceConnector,
|
||||
monitor: DragSourceMonitor
|
||||
): DragSourceProps {
|
||||
return {
|
||||
connectDragSource: connect.dragSource(),
|
||||
};
|
||||
}
|
||||
|
||||
const instructionTarget = {
|
||||
canDrop(props: Props, monitor: DropTargetMonitor) {
|
||||
return (
|
||||
monitor.getItem() && monitor.getItem().isCondition === props.isCondition
|
||||
);
|
||||
},
|
||||
drop(props: Props, monitor: DropTargetMonitor) {
|
||||
if (monitor.didDrop()) {
|
||||
return; // Drop already handled by a subinstruction
|
||||
}
|
||||
props.onMoveToInstruction();
|
||||
},
|
||||
};
|
||||
|
||||
function targetCollect(
|
||||
connect: DropTargetConnector,
|
||||
monitor: DropTargetMonitor
|
||||
): DropTargetProps {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
};
|
||||
}
|
||||
|
||||
// $FlowFixMe - Typing of DragSource/DropTarget is a pain to get correctly
|
||||
export default DragSource(
|
||||
reactDndInstructionType,
|
||||
instructionSource,
|
||||
sourceCollect
|
||||
)(
|
||||
DropTarget(reactDndInstructionType, instructionTarget, targetCollect)(
|
||||
Instruction
|
||||
)
|
||||
);
|
||||
|
@@ -9,15 +9,10 @@ import {
|
||||
type ParameterContext,
|
||||
} from '../SelectionHandler';
|
||||
import { actionsContainer, conditionsContainer } from './ClassNames';
|
||||
import {
|
||||
DropTarget,
|
||||
type DropTargetMonitor,
|
||||
type DropTargetConnector,
|
||||
type ConnectDropTarget,
|
||||
} from 'react-dnd';
|
||||
import DropIndicator from './DropIndicator';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { hasClipboardConditions, hasClipboardActions } from '../ClipboardKind';
|
||||
import { makeDropTarget } from '../../UI/DragAndDrop/DropTarget';
|
||||
|
||||
const styles = {
|
||||
addButton: {
|
||||
@@ -25,12 +20,6 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
type DropTargetProps = {|
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
|};
|
||||
|
||||
type Props = {
|
||||
instrsList: gdInstructionsList,
|
||||
areConditions: boolean,
|
||||
@@ -55,14 +44,17 @@ type Props = {
|
||||
style?: Object,
|
||||
disabled: boolean,
|
||||
renderObjectThumbnail: string => React.Node,
|
||||
...DropTargetProps,
|
||||
};
|
||||
|
||||
type State = {|
|
||||
canPaste: boolean,
|
||||
|};
|
||||
|
||||
class InstructionsList extends React.Component<Props, State> {
|
||||
const DropTarget = makeDropTarget<{
|
||||
isCondition: boolean,
|
||||
}>(reactDndInstructionType);
|
||||
|
||||
export default class InstructionsList extends React.Component<Props, State> {
|
||||
state = { canPaste: false };
|
||||
|
||||
onAddNewInstruction = () => {
|
||||
@@ -101,8 +93,6 @@ class InstructionsList extends React.Component<Props, State> {
|
||||
disabled,
|
||||
} = this.props;
|
||||
|
||||
const { connectDropTarget, isOver, canDrop } = this.props;
|
||||
|
||||
const instructions = mapFor(0, instrsList.size(), i => {
|
||||
const instruction = instrsList.get(i);
|
||||
const instructionContext = {
|
||||
@@ -113,7 +103,6 @@ class InstructionsList extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
return (
|
||||
// $FlowFixMe - Flow don't see that DropTarget hoc is being used in instructions?
|
||||
<Instruction
|
||||
instruction={instruction}
|
||||
isCondition={areConditions}
|
||||
@@ -160,96 +149,72 @@ class InstructionsList extends React.Component<Props, State> {
|
||||
) : (
|
||||
<Trans>Add action</Trans>
|
||||
);
|
||||
const instructionsList = connectDropTarget(
|
||||
<div
|
||||
className={`${
|
||||
areConditions ? conditionsContainer : actionsContainer
|
||||
} ${extraClassName || ''}`}
|
||||
style={style}
|
||||
>
|
||||
{instructions}
|
||||
{isOver && <DropIndicator canDrop={canDrop} />}
|
||||
<span
|
||||
onPointerEnter={() => {
|
||||
const canPaste =
|
||||
(areConditions && hasClipboardConditions()) ||
|
||||
(!areConditions && hasClipboardActions());
|
||||
this.setState({ canPaste });
|
||||
}}
|
||||
onPointerLeave={() => this.setState({ canPaste: false })}
|
||||
>
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
onClick={this.onAddNewInstruction}
|
||||
onContextMenu={e => {
|
||||
e.stopPropagation();
|
||||
onInstructionsListContextMenu(
|
||||
e.clientX,
|
||||
e.clientY,
|
||||
instructionsListContext
|
||||
);
|
||||
}}
|
||||
>
|
||||
{addButtonLabel || addButtonDefaultLabel}
|
||||
</button>
|
||||
{canPaste && (
|
||||
<span>
|
||||
{' '}
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
onClick={this._onPasteInstructions}
|
||||
>
|
||||
{areConditions ? (
|
||||
<Trans>(or paste conditions)</Trans>
|
||||
) : (
|
||||
<Trans>(or paste actions)</Trans>
|
||||
)}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return instructionsList || null;
|
||||
return (
|
||||
<DropTarget
|
||||
canDrop={draggedItem => draggedItem.isCondition === areConditions}
|
||||
drop={() => {
|
||||
onMoveToInstructionsList({
|
||||
isCondition: areConditions,
|
||||
instrsList: instrsList,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{({ connectDropTarget, isOver, canDrop }) =>
|
||||
connectDropTarget(
|
||||
<div
|
||||
className={`${
|
||||
areConditions ? conditionsContainer : actionsContainer
|
||||
} ${extraClassName || ''}`}
|
||||
style={style}
|
||||
>
|
||||
{instructions}
|
||||
{isOver && <DropIndicator canDrop={canDrop} />}
|
||||
<span
|
||||
onPointerEnter={() => {
|
||||
const canPaste =
|
||||
(areConditions && hasClipboardConditions()) ||
|
||||
(!areConditions && hasClipboardActions());
|
||||
this.setState({ canPaste });
|
||||
}}
|
||||
onPointerLeave={() => this.setState({ canPaste: false })}
|
||||
>
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
onClick={this.onAddNewInstruction}
|
||||
onContextMenu={e => {
|
||||
e.stopPropagation();
|
||||
onInstructionsListContextMenu(
|
||||
e.clientX,
|
||||
e.clientY,
|
||||
instructionsListContext
|
||||
);
|
||||
}}
|
||||
>
|
||||
{addButtonLabel || addButtonDefaultLabel}
|
||||
</button>
|
||||
{canPaste && (
|
||||
<span>
|
||||
{' '}
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
onClick={this._onPasteInstructions}
|
||||
>
|
||||
{areConditions ? (
|
||||
<Trans>(or paste conditions)</Trans>
|
||||
) : (
|
||||
<Trans>(or paste actions)</Trans>
|
||||
)}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</DropTarget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Drag'n'drop support:
|
||||
|
||||
const instructionsListTarget = {
|
||||
canDrop(props: Props, monitor: DropTargetMonitor) {
|
||||
return (
|
||||
monitor.getItem() && monitor.getItem().isCondition === props.areConditions
|
||||
);
|
||||
},
|
||||
drop(props: Props, monitor: DropTargetMonitor) {
|
||||
if (monitor.didDrop()) {
|
||||
return; // Drop already handled by an instruction
|
||||
}
|
||||
props.onMoveToInstructionsList({
|
||||
isCondition: props.areConditions,
|
||||
instrsList: props.instrsList,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
function targetCollect(
|
||||
connect: DropTargetConnector,
|
||||
monitor: DropTargetMonitor
|
||||
): DropTargetProps {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
};
|
||||
}
|
||||
|
||||
// $FlowFixMe - Typing of DragSource/DropTarget is a pain to get correctly
|
||||
export default DropTarget(
|
||||
reactDndInstructionType,
|
||||
instructionsListTarget,
|
||||
targetCollect
|
||||
)(InstructionsList);
|
||||
|
@@ -1,17 +0,0 @@
|
||||
export default class DropHandler {
|
||||
constructor({ canvas, onDrop }) {
|
||||
canvas.ondragover = canvas.ondrop = ev => {
|
||||
ev.dataTransfer.dropEffect = 'copy';
|
||||
ev.preventDefault();
|
||||
};
|
||||
|
||||
canvas.ondrop = ev => {
|
||||
const canvasRect = canvas.getBoundingClientRect();
|
||||
const name = ev.dataTransfer.getData('text');
|
||||
if (name)
|
||||
onDrop(ev.clientX - canvasRect.left, ev.clientY - canvasRect.top, name);
|
||||
|
||||
ev.preventDefault();
|
||||
};
|
||||
}
|
||||
}
|
146
newIDE/app/src/InstancesEditor/InstancesAdder.js
Normal file
146
newIDE/app/src/InstancesEditor/InstancesAdder.js
Normal file
@@ -0,0 +1,146 @@
|
||||
// @flow
|
||||
import { roundPosition } from '../Utils/GridHelpers';
|
||||
const gd = global.gd;
|
||||
|
||||
type Props = {|
|
||||
instances: gdInitialInstancesContainer,
|
||||
options: Object,
|
||||
|};
|
||||
|
||||
const roundPositionsToGrid = (
|
||||
pos: [number, number],
|
||||
options: Object
|
||||
): [number, number] => {
|
||||
const newPos = pos;
|
||||
|
||||
if (options.grid && options.snap) {
|
||||
newPos[0] = roundPosition(
|
||||
newPos[0],
|
||||
options.gridWidth,
|
||||
options.gridOffsetX
|
||||
);
|
||||
newPos[1] = roundPosition(
|
||||
newPos[1],
|
||||
options.gridHeight,
|
||||
options.gridOffsetY
|
||||
);
|
||||
}
|
||||
|
||||
return newPos;
|
||||
};
|
||||
|
||||
/**
|
||||
* Allow to add instances on the scene. Supports "temporary" instances,
|
||||
* which are real instances but can be deleted as long as they are not "committed".
|
||||
*/
|
||||
export default class InstancesAdder {
|
||||
_instances: gdInitialInstancesContainer;
|
||||
_temporaryInstances: Array<gdInitialInstance>;
|
||||
_options: Object;
|
||||
_zOrderFinder = new gd.HighestZOrderFinder();
|
||||
|
||||
constructor({ instances, options }: Props) {
|
||||
this._instances = instances;
|
||||
this._options = options;
|
||||
this._temporaryInstances = [];
|
||||
}
|
||||
|
||||
setOptions(options: Object) {
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately create new instance at the specified position
|
||||
* (specified in scene coordinates).
|
||||
*/
|
||||
addInstances = (pos: [number, number], objectNames: Array<string>) => {
|
||||
this._instances.iterateOverInstances(this._zOrderFinder);
|
||||
const zOrder = this._zOrderFinder.getHighestZOrder() + 1;
|
||||
|
||||
const newPos = roundPositionsToGrid(pos, this._options);
|
||||
objectNames.map(objectName => {
|
||||
const instance: gdInitialInstance = this._instances.insertNewInitialInstance();
|
||||
instance.setObjectName(objectName);
|
||||
instance.setX(newPos[0]);
|
||||
instance.setY(newPos[1]);
|
||||
instance.setZOrder(zOrder);
|
||||
|
||||
return instance;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create temporary instances at the specified position
|
||||
* (specified in scene coordinates).
|
||||
*/
|
||||
createOrUpdateTemporaryInstancesFromObjectNames = (
|
||||
pos: [number, number],
|
||||
objectNames: Array<string>
|
||||
) => {
|
||||
if (!objectNames.length) return;
|
||||
|
||||
if (!this._temporaryInstances.length) {
|
||||
this._createTemporaryInstancesFromObjectNames(pos, objectNames);
|
||||
} else {
|
||||
this.updateTemporaryInstancePositions(pos);
|
||||
}
|
||||
};
|
||||
|
||||
_createTemporaryInstancesFromObjectNames = (
|
||||
pos: [number, number],
|
||||
objectNames: Array<string>
|
||||
) => {
|
||||
this.deleteTemporaryInstances();
|
||||
|
||||
this._instances.iterateOverInstances(this._zOrderFinder);
|
||||
const zOrder = this._zOrderFinder.getHighestZOrder() + 1;
|
||||
|
||||
const newPos = roundPositionsToGrid(pos, this._options);
|
||||
this._temporaryInstances = objectNames.map(objectName => {
|
||||
const instance: gdInitialInstance = this._instances.insertNewInitialInstance();
|
||||
instance.setObjectName(objectName);
|
||||
instance.setX(newPos[0]);
|
||||
instance.setY(newPos[1]);
|
||||
instance.setZOrder(zOrder);
|
||||
|
||||
return instance;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the temporary instances positions
|
||||
* (specified in scene coordinates). Useful when dragging these instances.
|
||||
*/
|
||||
updateTemporaryInstancePositions = (pos: [number, number]) => {
|
||||
const newPos = roundPositionsToGrid(pos, this._options);
|
||||
this._temporaryInstances.forEach(instance => {
|
||||
instance.setX(newPos[0]);
|
||||
instance.setY(newPos[1]);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete the temporary instances.
|
||||
*/
|
||||
deleteTemporaryInstances() {
|
||||
this._temporaryInstances.forEach(instance => {
|
||||
this._instances.removeInstance(instance);
|
||||
});
|
||||
this._temporaryInstances = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Consider the temporary instances as not temporary anymore.
|
||||
*/
|
||||
commitTemporaryInstances() {
|
||||
this._temporaryInstances = [];
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this._zOrderFinder.delete();
|
||||
|
||||
// Nothing to do for temporaries instances, that should have been deleted/commited by this moment.
|
||||
// Don't take the risk to delete them now as this._instances might have been deleted/invalidated
|
||||
// already.
|
||||
}
|
||||
}
|
@@ -52,7 +52,12 @@ export default class SelectionRectangle {
|
||||
return this.selectionRectangleStart;
|
||||
}
|
||||
|
||||
makeSelectionRectangle = (lastX, lastY) => {
|
||||
startSelectionRectangle = (x, y) => {
|
||||
this.selectionRectangleStart = { x, y };
|
||||
this.selectionRectangleEnd = { x, y };
|
||||
};
|
||||
|
||||
updateSelectionRectangle = (lastX, lastY) => {
|
||||
if (!this.selectionRectangleStart)
|
||||
this.selectionRectangleStart = { x: lastX, y: lastY };
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import * as PIXI from 'pixi.js';
|
||||
|
||||
type Props = {
|
||||
getLastCursorPosition: () => [number, number],
|
||||
getLastCursorSceneCoordinates: () => [number, number],
|
||||
width: number,
|
||||
height: number,
|
||||
};
|
||||
@@ -10,13 +10,13 @@ type Props = {
|
||||
export default class StatusBar {
|
||||
_width: number;
|
||||
_height: number;
|
||||
_getLastCursorPosition: () => [number, number];
|
||||
_getLastCursorSceneCoordinates: () => [number, number];
|
||||
_statusBarContainer: PIXI.Container;
|
||||
_statusBarBackground: PIXI.Graphics;
|
||||
_statusBarText: PIXI.Text;
|
||||
|
||||
constructor({ getLastCursorPosition, width, height }: Props) {
|
||||
this._getLastCursorPosition = getLastCursorPosition;
|
||||
constructor({ getLastCursorSceneCoordinates, width, height }: Props) {
|
||||
this._getLastCursorSceneCoordinates = getLastCursorSceneCoordinates;
|
||||
this._statusBarContainer = new PIXI.Container();
|
||||
this._statusBarContainer.alpha = 0.8;
|
||||
this._statusBarContainer.hitArea = new PIXI.Rectangle(0, 0, 0, 0);
|
||||
@@ -43,7 +43,7 @@ export default class StatusBar {
|
||||
render() {
|
||||
const padding = 5;
|
||||
const borderRadius = 4;
|
||||
const [x, y] = this._getLastCursorPosition();
|
||||
const [x, y] = this._getLastCursorSceneCoordinates();
|
||||
this._statusBarText.text = `${x.toFixed(0)};${y.toFixed(0)}`;
|
||||
this._statusBarText.position.x = 0 + padding;
|
||||
this._statusBarText.position.y = Math.round(
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import gesture from 'pixi-simple-gesture';
|
||||
import KeyboardShortcuts from '../UI/KeyboardShortcuts';
|
||||
import SimpleDropTarget from '../Utils/DragDropHelpers/SimpleDropTarget';
|
||||
import InstancesRenderer from './InstancesRenderer';
|
||||
import ViewPosition from './ViewPosition';
|
||||
import SelectedInstances from './SelectedInstances';
|
||||
@@ -13,30 +12,29 @@ import InstancesMover from './InstancesMover';
|
||||
import Grid from './Grid';
|
||||
import WindowBorder from './WindowBorder';
|
||||
import WindowMask from './WindowMask';
|
||||
import DropHandler from './DropHandler';
|
||||
import BackgroundColor from './BackgroundColor';
|
||||
import * as PIXI from 'pixi.js';
|
||||
import FpsLimiter from './FpsLimiter';
|
||||
import { startPIXITicker, stopPIXITicker } from '../Utils/PIXITicker';
|
||||
import StatusBar from './StatusBar';
|
||||
import CanvasCursor from './CanvasCursor';
|
||||
import InstancesAdder from './InstancesAdder';
|
||||
import { makeDropTarget } from '../UI/DragAndDrop/DropTarget';
|
||||
import { objectWithContextReactDndType } from '../ObjectsList';
|
||||
|
||||
const styles = {
|
||||
canvasArea: { flex: 1, position: 'absolute', overflow: 'hidden' },
|
||||
dropCursor: { cursor: 'copy' },
|
||||
};
|
||||
|
||||
const DropTarget = makeDropTarget(objectWithContextReactDndType);
|
||||
|
||||
export default class InstancesEditorContainer extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.lastContextMenuX = 0;
|
||||
this.lastContextMenuY = 0;
|
||||
this.lastCursorX = 0;
|
||||
this.lastCursorY = 0;
|
||||
|
||||
this.fpsLimiter = new FpsLimiter(28);
|
||||
}
|
||||
lastContextMenuX = 0;
|
||||
lastContextMenuY = 0;
|
||||
lastCursorX = 0;
|
||||
lastCursorY = 0;
|
||||
fpsLimiter = new FpsLimiter(28);
|
||||
|
||||
componentDidMount() {
|
||||
// Initialize the PIXI renderer, if possible
|
||||
@@ -76,15 +74,6 @@ export default class InstancesEditorContainer extends Component {
|
||||
|
||||
return false;
|
||||
});
|
||||
this.pixiRenderer.view.addEventListener('pointerup', event => {
|
||||
this.props.onPointerUp();
|
||||
});
|
||||
this.pixiRenderer.view.addEventListener('pointerover', event => {
|
||||
this.props.onPointerOver();
|
||||
});
|
||||
this.pixiRenderer.view.addEventListener('pointerout', event => {
|
||||
this.props.onPointerOut();
|
||||
});
|
||||
this.pixiRenderer.view.onmousewheel = event => {
|
||||
if (this.keyboardShortcuts.shouldZoom()) {
|
||||
this.zoomBy(event.wheelDelta / 5000);
|
||||
@@ -170,16 +159,16 @@ export default class InstancesEditorContainer extends Component {
|
||||
onZoomIn: this.props.onZoomIn,
|
||||
});
|
||||
|
||||
this.dropHandler = new DropHandler({
|
||||
canvas: this.canvasArea,
|
||||
onDrop: this._onDrop,
|
||||
});
|
||||
|
||||
this.canvasCursor = new CanvasCursor({
|
||||
canvas: this.canvasArea,
|
||||
shouldMoveView: () => this.keyboardShortcuts.shouldMoveView(),
|
||||
});
|
||||
|
||||
this._instancesAdder = new InstancesAdder({
|
||||
instances: this.props.initialInstances,
|
||||
options: this.props.options,
|
||||
});
|
||||
|
||||
this._mountEditorComponents(this.props);
|
||||
this._renderScene();
|
||||
}
|
||||
@@ -278,7 +267,7 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.statusBar = new StatusBar({
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
getLastCursorPosition: this.getLastCursorPosition,
|
||||
getLastCursorSceneCoordinates: this.getLastCursorSceneCoordinates,
|
||||
});
|
||||
|
||||
this.pixiContainer.addChild(this.selectionRectangle.getPixiObject());
|
||||
@@ -300,6 +289,7 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.keyboardShortcuts.unmount();
|
||||
this.selectionRectangle.delete();
|
||||
this.instancesRenderer.delete();
|
||||
this._instancesAdder.unmount();
|
||||
if (this.nextFrame) cancelAnimationFrame(this.nextFrame);
|
||||
stopPIXITicker();
|
||||
}
|
||||
@@ -330,6 +320,7 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.instancesResizer.setOptions(nextProps.options);
|
||||
this.windowMask.setOptions(nextProps.options);
|
||||
this.viewPosition.setOptions(nextProps.options);
|
||||
this._instancesAdder.setOptions(nextProps.options);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -375,6 +366,17 @@ export default class InstancesEditorContainer extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately add instances for the specified objects at the given
|
||||
* position (in scene coordinates).
|
||||
*/
|
||||
addInstances = (
|
||||
pos /*: [number, number] */,
|
||||
objectNames /*: Array<string> */
|
||||
) => {
|
||||
this._instancesAdder.addInstances(pos, objectNames);
|
||||
};
|
||||
|
||||
_onMouseMove = (x, y) => {
|
||||
this.lastCursorX = x;
|
||||
this.lastCursorY = y;
|
||||
@@ -385,6 +387,13 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.lastCursorY = y;
|
||||
this.pixiRenderer.view.focus();
|
||||
|
||||
// Selection rectangle is only drawn in _onPanMove,
|
||||
// which can happen a few milliseconds after a background
|
||||
// click/touch - enough to have the selection rectangle being
|
||||
// offset from the first click - which looks laggy. Set
|
||||
// the start position now.
|
||||
this.selectionRectangle.startSelectionRectangle(x, y);
|
||||
|
||||
if (
|
||||
!this.keyboardShortcuts.shouldMultiSelect() &&
|
||||
!this.keyboardShortcuts.shouldMoveView()
|
||||
@@ -405,7 +414,7 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.props.onViewPositionChanged(this.viewPosition);
|
||||
}
|
||||
} else {
|
||||
this.selectionRectangle.makeSelectionRectangle(x, y);
|
||||
this.selectionRectangle.updateSelectionRectangle(x, y);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -554,13 +563,6 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.props.onInstancesRotated(selectedInstances);
|
||||
};
|
||||
|
||||
_onDrop = (x, y, objectName) => {
|
||||
const newPos = this.viewPosition.toSceneCoordinates(x, y);
|
||||
if (this.props.onAddInstance) {
|
||||
this.props.onAddInstance(newPos[0], newPos[1], objectName);
|
||||
}
|
||||
};
|
||||
|
||||
clearHighlightedInstance = () => {
|
||||
this.highlightedInstance.setInstance(null);
|
||||
};
|
||||
@@ -593,14 +595,14 @@ export default class InstancesEditorContainer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
getLastContextMenuPosition = () => {
|
||||
getLastContextMenuSceneCoordinates = () => {
|
||||
return this.viewPosition.toSceneCoordinates(
|
||||
this.lastContextMenuX,
|
||||
this.lastContextMenuY
|
||||
);
|
||||
};
|
||||
|
||||
getLastCursorPosition = () => {
|
||||
getLastCursorSceneCoordinates = () => {
|
||||
return this.viewPosition.toSceneCoordinates(
|
||||
this.lastCursorX,
|
||||
this.lastCursorY
|
||||
@@ -652,15 +654,63 @@ export default class InstancesEditorContainer extends Component {
|
||||
if (!this.props.project) return null;
|
||||
|
||||
return (
|
||||
<SimpleDropTarget>
|
||||
<div
|
||||
ref={canvasArea => (this.canvasArea = canvasArea)}
|
||||
style={{
|
||||
...styles.canvasArea,
|
||||
...(this.props.showDropCursor ? styles.dropCursor : undefined),
|
||||
}}
|
||||
/>
|
||||
</SimpleDropTarget>
|
||||
<DropTarget
|
||||
canDrop={() => true}
|
||||
hover={monitor => {
|
||||
const { _instancesAdder, viewPosition, canvasArea } = this;
|
||||
if (!_instancesAdder || !canvasArea || !viewPosition) return;
|
||||
|
||||
const { x, y } = monitor.getClientOffset();
|
||||
const canvasRect = canvasArea.getBoundingClientRect();
|
||||
const pos = viewPosition.toSceneCoordinates(
|
||||
x - canvasRect.left,
|
||||
y - canvasRect.top
|
||||
);
|
||||
_instancesAdder.createOrUpdateTemporaryInstancesFromObjectNames(
|
||||
pos,
|
||||
this.props.selectedObjectNames
|
||||
);
|
||||
}}
|
||||
drop={monitor => {
|
||||
const { _instancesAdder, viewPosition, canvasArea } = this;
|
||||
if (!_instancesAdder || !canvasArea || !viewPosition) return;
|
||||
|
||||
if (monitor.didDrop()) {
|
||||
// Drop was done somewhere else (in a child of the canvas:
|
||||
// should not happen, but still handling this case).
|
||||
_instancesAdder.deleteTemporaryInstances();
|
||||
return;
|
||||
}
|
||||
|
||||
const { x, y } = monitor.getClientOffset();
|
||||
const canvasRect = canvasArea.getBoundingClientRect();
|
||||
const pos = viewPosition.toSceneCoordinates(
|
||||
x - canvasRect.left,
|
||||
y - canvasRect.top
|
||||
);
|
||||
_instancesAdder.updateTemporaryInstancePositions(pos);
|
||||
_instancesAdder.commitTemporaryInstances();
|
||||
}}
|
||||
>
|
||||
{({ connectDropTarget, isOver }) => {
|
||||
// The children are re-rendered when isOver change:
|
||||
// take this opportunity to delete any temporary instances
|
||||
// if the dragging is not done anymore over the canvas.
|
||||
if (this._instancesAdder && !isOver) {
|
||||
this._instancesAdder.deleteTemporaryInstances();
|
||||
}
|
||||
|
||||
return connectDropTarget(
|
||||
<div
|
||||
ref={canvasArea => (this.canvasArea = canvasArea)}
|
||||
style={{
|
||||
...styles.canvasArea,
|
||||
...(this.props.showDropCursor ? styles.dropCursor : undefined),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</DropTarget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import DragDropContextProvider from '../Utils/DragDropHelpers/DragDropContextProvider';
|
||||
import DragAndDropContextProvider from '../UI/DragAndDrop/DragAndDropContextProvider';
|
||||
import { ThemeProvider } from '@material-ui/styles';
|
||||
import { getTheme } from '../UI/Theme';
|
||||
import UserProfileProvider from '../Profile/UserProfileProvider';
|
||||
@@ -48,7 +48,7 @@ export default class Providers extends React.Component<Props, {||}> {
|
||||
eventsFunctionsExtensionOpener,
|
||||
} = this.props;
|
||||
return (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<PreferencesProvider disableCheckForUpdates={disableCheckForUpdates}>
|
||||
<PreferencesContext.Consumer>
|
||||
{({ values }) => {
|
||||
@@ -91,7 +91,7 @@ export default class Providers extends React.Component<Props, {||}> {
|
||||
}}
|
||||
</PreferencesContext.Consumer>
|
||||
</PreferencesProvider>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -83,6 +83,8 @@ import LanguageDialog from './Preferences/LanguageDialog';
|
||||
import PreferencesContext from './Preferences/PreferencesContext';
|
||||
import { getFunctionNameFromType } from '../EventsFunctionsExtensionsLoader';
|
||||
import { type ExportDialogWithoutExportsProps } from '../Export/ExportDialog';
|
||||
import { getStartupTimesSummary } from '../Utils/StartupTimes';
|
||||
const GD_STARTUP_TIMES = global.GD_STARTUP_TIMES || [];
|
||||
|
||||
const gd = global.gd;
|
||||
|
||||
@@ -184,6 +186,8 @@ class MainFrame extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
GD_STARTUP_TIMES.push(['MainFrameComponentDidMount', performance.now()]);
|
||||
|
||||
const { initialPathsOrURLsToOpen } = this.props;
|
||||
|
||||
this._loadExtensions();
|
||||
@@ -193,6 +197,9 @@ class MainFrame extends React.Component<Props, State> {
|
||||
);
|
||||
} else if (this.props.introDialog && !Window.isDev())
|
||||
this._openIntroDialog(true);
|
||||
|
||||
GD_STARTUP_TIMES.push(['MainFrameComponentDidMountFinished', performance.now()]);
|
||||
console.info("Startup times:", getStartupTimesSummary());
|
||||
}
|
||||
|
||||
_languageDidChange() {
|
||||
|
@@ -227,7 +227,9 @@ export default class GroupsListContainer extends React.Component<Props, State> {
|
||||
{
|
||||
renamedGroupWithScope: groupWithContext,
|
||||
},
|
||||
() => this.sortableList.getWrappedInstance().forceUpdateGrid()
|
||||
() => {
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -285,7 +287,7 @@ export default class GroupsListContainer extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
this.forceUpdate();
|
||||
this.sortableList.getWrappedInstance().forceUpdateGrid();
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
};
|
||||
|
||||
_setAsGlobalGroup = (groupWithContext: GroupWithContext) => {
|
||||
|
@@ -26,6 +26,17 @@ export type GroupWithContext = {|
|
||||
export type ObjectWithContextList = Array<ObjectWithContext>;
|
||||
export type GroupWithContextList = Array<GroupWithContext>;
|
||||
|
||||
export const isSameObjectWithContext = (
|
||||
objectWithContext: ?ObjectWithContext
|
||||
) => (other: ?ObjectWithContext) => {
|
||||
return (
|
||||
objectWithContext &&
|
||||
other &&
|
||||
objectWithContext.global === other.global &&
|
||||
objectWithContext.object === other.object
|
||||
);
|
||||
};
|
||||
|
||||
export const enumerateObjects = (
|
||||
project: gdProject,
|
||||
objectsContainer: gdObjectsContainer,
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { AutoSizer, List } from 'react-virtualized';
|
||||
import React from 'react';
|
||||
import { AutoSizer } from 'react-virtualized';
|
||||
import SortableVirtualizedItemList from '../UI/SortableVirtualizedItemList';
|
||||
import Background from '../UI/Background';
|
||||
import SearchBar from '../UI/SearchBar';
|
||||
import ObjectRow from './ObjectRow';
|
||||
import NewObjectDialog from './NewObjectDialog';
|
||||
import VariablesEditorDialog from '../VariablesList/VariablesEditorDialog';
|
||||
import newNameGenerator from '../Utils/NewNameGenerator';
|
||||
@@ -15,9 +15,11 @@ import {
|
||||
unserializeFromJSObject,
|
||||
} from '../Utils/Serializer';
|
||||
import { showWarningBox } from '../UI/Messages/MessageBox';
|
||||
import { AddListItem } from '../UI/ListCommonItem';
|
||||
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
|
||||
import { enumerateObjects, filterObjectsList } from './EnumerateObjects';
|
||||
import {
|
||||
enumerateObjects,
|
||||
filterObjectsList,
|
||||
isSameObjectWithContext,
|
||||
} from './EnumerateObjects';
|
||||
import type {
|
||||
ObjectWithContextList,
|
||||
ObjectWithContext,
|
||||
@@ -25,8 +27,13 @@ import type {
|
||||
import { CLIPBOARD_KIND } from './ClipboardKind';
|
||||
import TagChips from '../UI/TagChips';
|
||||
import EditTagsDialog from '../UI/EditTagsDialog';
|
||||
import { type Tags, getStringFromTags } from '../Utils/TagsHelper';
|
||||
import { listItemWith32PxIconHeight } from '../UI/List';
|
||||
import {
|
||||
type Tags,
|
||||
type SelectedTags,
|
||||
getStringFromTags,
|
||||
buildTagsMenuTemplate,
|
||||
getTagsFromString,
|
||||
} from '../Utils/TagsHelper';
|
||||
|
||||
const styles = {
|
||||
listContainer: {
|
||||
@@ -34,120 +41,29 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
const SortableObjectRow = SortableElement(props => {
|
||||
const { style, ...otherProps } = props;
|
||||
return (
|
||||
<div style={style}>
|
||||
<ObjectRow {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export const objectWithContextReactDndType = 'GD_OBJECT_WITH_CONTEXT';
|
||||
|
||||
const SortableAddObjectRow = SortableElement(props => {
|
||||
const { style, ...otherProps } = props;
|
||||
return (
|
||||
<div style={style}>
|
||||
<AddListItem {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
const getObjectWithContextName = (objectWithContext: ObjectWithContext) =>
|
||||
objectWithContext.object.getName();
|
||||
|
||||
class ObjectsList extends Component<*, *> {
|
||||
list: any;
|
||||
const isObjectWithContextGlobal = (objectWithContext: ObjectWithContext) =>
|
||||
objectWithContext.global;
|
||||
|
||||
forceUpdateGrid() {
|
||||
if (this.list) this.list.forceUpdateGrid();
|
||||
const getPasteLabel = isGlobalObject => {
|
||||
let clipboardObjectName = '';
|
||||
if (Clipboard.has(CLIPBOARD_KIND)) {
|
||||
const clipboardContent = Clipboard.get(CLIPBOARD_KIND);
|
||||
if (clipboardContent) {
|
||||
clipboardObjectName = clipboardContent.name;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let { height, width, fullList, project, selectedObjectNames } = this.props;
|
||||
return isGlobalObject
|
||||
? 'Paste ' + clipboardObjectName + ' as a Global Object'
|
||||
: 'Paste ' + clipboardObjectName;
|
||||
};
|
||||
|
||||
return (
|
||||
<List
|
||||
ref={list => (this.list = list)}
|
||||
height={height}
|
||||
rowCount={fullList.length}
|
||||
rowHeight={listItemWith32PxIconHeight}
|
||||
rowRenderer={({ index, key, style }) => {
|
||||
const objectWithContext = fullList[index];
|
||||
if (objectWithContext.key === 'add-objects-row') {
|
||||
return (
|
||||
<SortableAddObjectRow
|
||||
index={fullList.length}
|
||||
key={key}
|
||||
style={style}
|
||||
disabled
|
||||
onClick={this.props.onAddNewObject}
|
||||
primaryText={<Trans>Click to add an object</Trans>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const nameBeingEdited =
|
||||
this.props.renamedObjectWithContext &&
|
||||
this.props.renamedObjectWithContext.object ===
|
||||
objectWithContext.object &&
|
||||
this.props.renamedObjectWithContext.global ===
|
||||
objectWithContext.global;
|
||||
|
||||
return (
|
||||
<SortableObjectRow
|
||||
index={index}
|
||||
key={objectWithContext.object.ptr}
|
||||
project={project}
|
||||
object={objectWithContext.object}
|
||||
isGlobalObject={objectWithContext.global}
|
||||
style={style}
|
||||
onEdit={
|
||||
this.props.onEditObject
|
||||
? () => this.props.onEditObject(objectWithContext.object)
|
||||
: undefined
|
||||
}
|
||||
onEditVariables={() =>
|
||||
this.props.onEditVariables(objectWithContext.object)
|
||||
}
|
||||
onEditName={() => this.props.onEditName(objectWithContext)}
|
||||
onDelete={() => this.props.onDelete(objectWithContext)}
|
||||
onCopyObject={() => this.props.onCopyObject(objectWithContext)}
|
||||
onCutObject={() => this.props.onCutObject(objectWithContext)}
|
||||
onDuplicateObject={() =>
|
||||
this.props.onDuplicateObject(objectWithContext)
|
||||
}
|
||||
onPasteObject={() => this.props.onPasteObject(objectWithContext)}
|
||||
onRename={newName =>
|
||||
this.props.onRename(objectWithContext, newName)
|
||||
}
|
||||
onSetAsGlobalObject={
|
||||
objectWithContext.global
|
||||
? undefined
|
||||
: () => this.props.onSetAsGlobalObject(objectWithContext)
|
||||
}
|
||||
onAddNewObject={this.props.onAddNewObject}
|
||||
editingName={nameBeingEdited}
|
||||
getThumbnail={this.props.getThumbnail}
|
||||
getAllObjectTags={this.props.getAllObjectTags}
|
||||
onObjectSelected={this.props.onObjectSelected}
|
||||
onEditTags={() => this.props.onEditTags(objectWithContext.object)}
|
||||
onChangeTags={objectTags =>
|
||||
this.props.onChangeTags(objectWithContext.object, objectTags)
|
||||
}
|
||||
selected={
|
||||
selectedObjectNames.indexOf(
|
||||
objectWithContext.object.getName()
|
||||
) !== -1
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SortableObjectsList = SortableContainer(ObjectsList, { withRef: true });
|
||||
|
||||
type StateType = {|
|
||||
type State = {|
|
||||
newObjectDialogOpen: boolean,
|
||||
renamedObjectWithContext: ?ObjectWithContext,
|
||||
variablesEditedObject: ?gdObject,
|
||||
@@ -155,23 +71,37 @@ type StateType = {|
|
||||
tagEditedObject: ?gdObject,
|
||||
|};
|
||||
|
||||
export default class ObjectsListContainer extends React.Component<
|
||||
*,
|
||||
StateType
|
||||
> {
|
||||
static defaultProps = {
|
||||
onDeleteObject: (objectWithContext: ObjectWithContext, cb: Function) =>
|
||||
cb(true),
|
||||
onRenameObject: (
|
||||
objectWithContext: ObjectWithContext,
|
||||
newName: string,
|
||||
cb: Function
|
||||
) => cb(true),
|
||||
};
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
objectsContainer: gdObjectsContainer,
|
||||
onDeleteObject: (
|
||||
objectWithContext: ObjectWithContext,
|
||||
cb: (boolean) => void
|
||||
) => void,
|
||||
onRenameObject: (
|
||||
objectWithContext: ObjectWithContext,
|
||||
newName: string,
|
||||
cb: (boolean) => void
|
||||
) => void,
|
||||
selectedObjectNames: Array<string>,
|
||||
|
||||
sortableList: any;
|
||||
_displayedObjectsList: ObjectWithContextList = [];
|
||||
state: StateType = {
|
||||
selectedObjectTags: SelectedTags,
|
||||
getAllObjectTags: () => Tags,
|
||||
onChangeSelectedObjectTags: SelectedTags => void,
|
||||
|
||||
onEditObject: gdObject => void,
|
||||
onObjectCreated: string => void,
|
||||
onObjectSelected: string => void,
|
||||
onObjectPasted?: gdObject => void,
|
||||
canRenameObject: (newName: string) => boolean,
|
||||
|
||||
getThumbnail: (project: gdProject, object: Object) => string,
|
||||
|};
|
||||
|
||||
export default class ObjectsList extends React.Component<Props, State> {
|
||||
sortableList: ?SortableVirtualizedItemList<ObjectWithContext>;
|
||||
_displayedObjectWithContextsList: ObjectWithContextList = [];
|
||||
state = {
|
||||
newObjectDialogOpen: false,
|
||||
renamedObjectWithContext: null,
|
||||
variablesEditedObject: null,
|
||||
@@ -179,7 +109,7 @@ export default class ObjectsListContainer extends React.Component<
|
||||
tagEditedObject: null,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps: *, nextState: StateType) {
|
||||
shouldComponentUpdate(nextProps: Props, nextState: State) {
|
||||
// The component is costly to render, so avoid any re-rendering as much
|
||||
// as possible.
|
||||
// We make the assumption that no changes to objects list is made outside
|
||||
@@ -351,7 +281,9 @@ export default class ObjectsListContainer extends React.Component<
|
||||
{
|
||||
renamedObjectWithContext: objectWithContext,
|
||||
},
|
||||
() => this.sortableList.getWrappedInstance().forceUpdateGrid()
|
||||
() => {
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -378,48 +310,61 @@ export default class ObjectsListContainer extends React.Component<
|
||||
}
|
||||
};
|
||||
|
||||
_move = (oldIndex: number, newIndex: number) => {
|
||||
// Moving objects can be discarded by the parent (this is used to allow
|
||||
// dropping objects on the scene editor).
|
||||
if (!this.props.canMoveObjects) return;
|
||||
|
||||
const { project, objectsContainer } = this.props;
|
||||
|
||||
const movedObjectWithContext = this._displayedObjectsList[oldIndex];
|
||||
const destinationObjectWithContext = this._displayedObjectsList[newIndex];
|
||||
if (!movedObjectWithContext || !destinationObjectWithContext) return;
|
||||
|
||||
if (movedObjectWithContext.global !== destinationObjectWithContext.global) {
|
||||
// Can't move an object from the objects container to the global objects
|
||||
// or vice-versa.
|
||||
return;
|
||||
}
|
||||
|
||||
const container: gdObjectsContainer = movedObjectWithContext.global
|
||||
? project
|
||||
: objectsContainer;
|
||||
container.moveObject(
|
||||
container.getObjectPosition(movedObjectWithContext.object.getName()),
|
||||
container.getObjectPosition(destinationObjectWithContext.object.getName())
|
||||
_canMoveSelectionTo = (destinationObjectWithContext: ObjectWithContext) => {
|
||||
// Check if at least one element in the selection can be moved.
|
||||
const selectedObjects = this._displayedObjectWithContextsList.filter(
|
||||
objectWithContext =>
|
||||
this.props.selectedObjectNames.indexOf(
|
||||
objectWithContext.object.getName()
|
||||
) !== -1
|
||||
);
|
||||
return (
|
||||
selectedObjects.filter(movedObjectWithContext => {
|
||||
return (
|
||||
movedObjectWithContext.global === destinationObjectWithContext.global
|
||||
);
|
||||
}).length > 0
|
||||
);
|
||||
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
_onStartDraggingObject = ({ index }: { index: number }) => {
|
||||
const draggedObjectWithContext = this._displayedObjectsList[index];
|
||||
if (!draggedObjectWithContext) {
|
||||
return;
|
||||
}
|
||||
_moveSelectionTo = (destinationObjectWithContext: ObjectWithContext) => {
|
||||
const { project, objectsContainer } = this.props;
|
||||
|
||||
this.props.onStartDraggingObject(draggedObjectWithContext.object);
|
||||
const container: gdObjectsContainer = destinationObjectWithContext.global
|
||||
? project
|
||||
: objectsContainer;
|
||||
|
||||
const selectedObjects = this._displayedObjectWithContextsList.filter(
|
||||
objectWithContext =>
|
||||
this.props.selectedObjectNames.indexOf(
|
||||
objectWithContext.object.getName()
|
||||
) !== -1
|
||||
);
|
||||
selectedObjects.forEach(movedObjectWithContext => {
|
||||
if (
|
||||
movedObjectWithContext.global !== destinationObjectWithContext.global
|
||||
) {
|
||||
// Can't move an object from the objects container to the global objects
|
||||
// or vice-versa.
|
||||
return;
|
||||
}
|
||||
|
||||
container.moveObject(
|
||||
container.getObjectPosition(movedObjectWithContext.object.getName()),
|
||||
container.getObjectPosition(
|
||||
destinationObjectWithContext.object.getName()
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
_setAsGlobalObject = (objectWithContext: ObjectWithContext) => {
|
||||
const { object } = objectWithContext;
|
||||
const { project, objectsContainer } = this.props;
|
||||
|
||||
const objectName = object.getName();
|
||||
const objectName: string = object.getName();
|
||||
if (!objectsContainer.hasObjectNamed(objectName)) return;
|
||||
|
||||
if (project.hasObjectNamed(objectName)) {
|
||||
@@ -449,7 +394,7 @@ export default class ObjectsListContainer extends React.Component<
|
||||
|
||||
forceUpdateList = () => {
|
||||
this.forceUpdate();
|
||||
this.sortableList.getWrappedInstance().forceUpdateGrid();
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
};
|
||||
|
||||
_openEditTagDialog = (tagEditedObject: ?gdObject) => {
|
||||
@@ -466,19 +411,102 @@ export default class ObjectsListContainer extends React.Component<
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
_selectObject = (objectWithContext: ?ObjectWithContext) => {
|
||||
this.props.onObjectSelected(
|
||||
objectWithContext ? objectWithContext.object.getName() : ''
|
||||
);
|
||||
};
|
||||
|
||||
_getObjectThumbnail = (objectWithContext: ObjectWithContext) =>
|
||||
this.props.getThumbnail(this.props.project, objectWithContext.object);
|
||||
|
||||
_renderObjectMenuTemplate = (
|
||||
objectWithContext: ObjectWithContext,
|
||||
index: number
|
||||
) => {
|
||||
const { object } = objectWithContext;
|
||||
return [
|
||||
{
|
||||
label: 'Edit object',
|
||||
click: () => this.props.onEditObject(object),
|
||||
},
|
||||
{
|
||||
label: 'Edit object variables',
|
||||
click: () => this._editVariables(object),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Tags',
|
||||
submenu: buildTagsMenuTemplate({
|
||||
noTagLabel: 'No tags',
|
||||
getAllTags: this.props.getAllObjectTags,
|
||||
selectedTags: getTagsFromString(object.getTags()),
|
||||
onChange: objectTags => {
|
||||
this._changeObjectTags(object, objectTags);
|
||||
},
|
||||
editTagsLabel: 'Add/edit tags...',
|
||||
onEditTags: () => this._openEditTagDialog(object),
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'Rename',
|
||||
click: () => this._editName(objectWithContext),
|
||||
},
|
||||
{
|
||||
label: 'Set as a global object',
|
||||
click: () => this._setAsGlobalObject(objectWithContext),
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
click: () => this._deleteObject(objectWithContext),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Add a new object...',
|
||||
click: () => this.onAddNewObject(),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => this._copyObject(objectWithContext),
|
||||
},
|
||||
{
|
||||
label: 'Cut',
|
||||
click: () => this._cutObject(objectWithContext),
|
||||
},
|
||||
{
|
||||
label: getPasteLabel(objectWithContext.global),
|
||||
enabled: Clipboard.has(CLIPBOARD_KIND),
|
||||
click: () => this._paste(objectWithContext),
|
||||
},
|
||||
{
|
||||
label: 'Duplicate',
|
||||
click: () => this._duplicateObject(objectWithContext),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
render() {
|
||||
const { project, objectsContainer, selectedObjectTags } = this.props;
|
||||
const { searchText, tagEditedObject } = this.state;
|
||||
|
||||
const lists = enumerateObjects(project, objectsContainer);
|
||||
this._displayedObjectsList = filterObjectsList(lists.allObjectsList, {
|
||||
searchText,
|
||||
selectedTags: selectedObjectTags,
|
||||
});
|
||||
const fullList = this._displayedObjectsList.concat({
|
||||
key: 'add-objects-row',
|
||||
object: null,
|
||||
});
|
||||
this._displayedObjectWithContextsList = filterObjectsList(
|
||||
lists.allObjectsList,
|
||||
{
|
||||
searchText,
|
||||
selectedTags: selectedObjectTags,
|
||||
}
|
||||
);
|
||||
const selectedObjects = this._displayedObjectWithContextsList.filter(
|
||||
objectWithContext =>
|
||||
this.props.selectedObjectNames.indexOf(
|
||||
objectWithContext.object.getName()
|
||||
) !== -1
|
||||
);
|
||||
const renamedObjectWithContext = this._displayedObjectWithContextsList.find(
|
||||
isSameObjectWithContext(this.state.renamedObjectWithContext)
|
||||
);
|
||||
|
||||
// Force List component to be mounted again if project or objectsContainer
|
||||
// has been changed. Avoid accessing to invalid objects that could
|
||||
@@ -494,38 +522,28 @@ export default class ObjectsListContainer extends React.Component<
|
||||
<div style={styles.listContainer}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<SortableObjectsList
|
||||
<SortableVirtualizedItemList
|
||||
key={listKey}
|
||||
ref={sortableList => (this.sortableList = sortableList)}
|
||||
fullList={fullList}
|
||||
project={project}
|
||||
fullList={this._displayedObjectWithContextsList}
|
||||
width={width}
|
||||
height={height}
|
||||
renamedObjectWithContext={this.state.renamedObjectWithContext}
|
||||
getThumbnail={this.props.getThumbnail}
|
||||
getAllObjectTags={this.props.getAllObjectTags}
|
||||
onEditTags={this._openEditTagDialog}
|
||||
onChangeTags={this._changeObjectTags}
|
||||
selectedObjectNames={this.props.selectedObjectNames}
|
||||
onObjectSelected={this.props.onObjectSelected}
|
||||
onEditObject={this.props.onEditObject}
|
||||
onCopyObject={this._copyObject}
|
||||
onCutObject={this._cutObject}
|
||||
onDuplicateObject={this._duplicateObject}
|
||||
onSetAsGlobalObject={this._setAsGlobalObject}
|
||||
onPasteObject={this._pasteAndRename}
|
||||
onAddNewObject={this.onAddNewObject}
|
||||
onEditName={this._editName}
|
||||
onEditVariables={this._editVariables}
|
||||
onDelete={this._deleteObject}
|
||||
getItemName={getObjectWithContextName}
|
||||
getItemThumbnail={this._getObjectThumbnail}
|
||||
isItemBold={isObjectWithContextGlobal}
|
||||
onEditItem={objectWithContext =>
|
||||
this.props.onEditObject(objectWithContext.object)
|
||||
}
|
||||
onAddNewItem={this.onAddNewObject}
|
||||
addNewItemLabel={<Trans>Add a new object</Trans>}
|
||||
selectedItems={selectedObjects}
|
||||
onItemSelected={this._selectObject}
|
||||
renamedItem={renamedObjectWithContext}
|
||||
onRename={this._rename}
|
||||
onSortStart={this._onStartDraggingObject}
|
||||
onSortEnd={({ oldIndex, newIndex }) => {
|
||||
this.props.onEndDraggingObject();
|
||||
this._move(oldIndex, newIndex);
|
||||
}}
|
||||
helperClass="sortable-helper"
|
||||
distance={20}
|
||||
buildMenuTemplate={this._renderObjectMenuTemplate}
|
||||
onMoveSelectionToItem={this._moveSelectionTo}
|
||||
canMoveSelectionToItem={this._canMoveSelectionTo}
|
||||
reactDndType={objectWithContextReactDndType}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
@@ -28,6 +28,8 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
const getResourceName = (resource: gdResource) => resource.getName();
|
||||
|
||||
type State = {|
|
||||
renamedResource: ?gdResource,
|
||||
searchText: string,
|
||||
@@ -37,7 +39,7 @@ type State = {|
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
selectedResource: ?gdResource,
|
||||
onSelectResource: (resource: gdResource) => void,
|
||||
onSelectResource: (resource: ?gdResource) => void,
|
||||
onDeleteResource: (resource: gdResource) => void,
|
||||
onRenameResource: (
|
||||
resource: gdResource,
|
||||
@@ -164,7 +166,9 @@ export default class ResourcesList extends React.Component<Props, State> {
|
||||
{
|
||||
renamedResource: resource,
|
||||
},
|
||||
() => this.sortableList.getWrappedInstance().forceUpdateGrid()
|
||||
() => {
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -194,16 +198,21 @@ export default class ResourcesList extends React.Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
_move = (oldIndex: number, newIndex: number) => {
|
||||
const { project } = this.props;
|
||||
_moveSelectionTo = (destinationResource: gdResource) => {
|
||||
const { project, selectedResource } = this.props;
|
||||
if (!selectedResource) return;
|
||||
|
||||
project.getResourcesManager().moveResource(oldIndex, newIndex);
|
||||
const resourcesManager = project.getResourcesManager();
|
||||
resourcesManager.moveResource(
|
||||
resourcesManager.getResourcePosition(selectedResource.getName()),
|
||||
resourcesManager.getResourcePosition(destinationResource.getName())
|
||||
);
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
forceUpdateList = () => {
|
||||
this.forceUpdate();
|
||||
this.sortableList.getWrappedInstance().forceUpdateGrid();
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
};
|
||||
|
||||
_renderResourceMenuTemplate = (resource: gdResource, _index: number) => {
|
||||
@@ -348,17 +357,15 @@ export default class ResourcesList extends React.Component<Props, State> {
|
||||
fullList={filteredList}
|
||||
width={width}
|
||||
height={height}
|
||||
selectedItem={selectedResource}
|
||||
getItemName={getResourceName}
|
||||
selectedItems={selectedResource ? [selectedResource] : []}
|
||||
onItemSelected={onSelectResource}
|
||||
renamedItem={this.state.renamedResource}
|
||||
onRename={this._rename}
|
||||
onSortEnd={({ oldIndex, newIndex }) =>
|
||||
this._move(oldIndex, newIndex)
|
||||
}
|
||||
onMoveSelectionToItem={this._moveSelectionTo}
|
||||
buildMenuTemplate={this._renderResourceMenuTemplate}
|
||||
helperClass="sortable-helper"
|
||||
distance={20}
|
||||
erroredItems={this.state.resourcesWithErrors}
|
||||
reactDndType="GD_RESOURCE"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
@@ -36,7 +36,6 @@ import InfoBar from '../UI/Messages/InfoBar';
|
||||
import ContextMenu from '../UI/Menu/ContextMenu';
|
||||
import { showWarningBox } from '../UI/Messages/MessageBox';
|
||||
import { shortenString } from '../Utils/StringHelpers';
|
||||
import { roundPosition } from '../Utils/GridHelpers';
|
||||
import getObjectByName from '../Utils/GetObjectByName';
|
||||
|
||||
import {
|
||||
@@ -61,7 +60,11 @@ import {
|
||||
} from '../ObjectsList/EnumerateObjects';
|
||||
import TagsButton from '../UI/EditorMosaic/TagsButton';
|
||||
import CloseButton from '../UI/EditorMosaic/CloseButton';
|
||||
import { buildTagsMenuTemplate, getTagsFromString } from '../Utils/TagsHelper';
|
||||
import {
|
||||
type SelectedTags,
|
||||
buildTagsMenuTemplate,
|
||||
getTagsFromString,
|
||||
} from '../Utils/TagsHelper';
|
||||
const gd = global.gd;
|
||||
|
||||
const INSTANCES_CLIPBOARD_KIND = 'Instances';
|
||||
@@ -124,14 +127,10 @@ type State = {|
|
||||
variablesEditedInstance: ?gdInitialInstance,
|
||||
variablesEditedObject: ?gdObject,
|
||||
selectedObjectNames: Array<string>,
|
||||
newObjectInstancePosition: ?[number, number],
|
||||
newObjectInstanceSceneCoordinates: ?[number, number],
|
||||
|
||||
editedGroup: ?gdObjectGroup,
|
||||
|
||||
// State for "drag'n'dropping" from the objects list to the instances editor:
|
||||
objectDraggedFromList: ?gdObject,
|
||||
canDropDraggedObject: boolean,
|
||||
|
||||
uiSettings: Object,
|
||||
history: HistoryState,
|
||||
|
||||
@@ -140,7 +139,7 @@ type State = {|
|
||||
showPropertiesInfoBar: boolean,
|
||||
|
||||
// State for tags of objects:
|
||||
selectedObjectTags: Array<string>,
|
||||
selectedObjectTags: SelectedTags,
|
||||
|};
|
||||
|
||||
type CopyCutPasteOptions = { useLastCursorPosition?: boolean };
|
||||
@@ -176,13 +175,9 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
variablesEditedInstance: null,
|
||||
variablesEditedObject: null,
|
||||
selectedObjectNames: [],
|
||||
newObjectInstancePosition: null,
|
||||
newObjectInstanceSceneCoordinates: null,
|
||||
editedGroup: null,
|
||||
|
||||
// State for "drag'n'dropping" from the objects list to the instances editor:
|
||||
objectDraggedFromList: null,
|
||||
canDropDraggedObject: false,
|
||||
|
||||
uiSettings: props.initialUiSettings,
|
||||
history: getHistoryInitialState(props.initialInstances, {
|
||||
historyMaxSize: 50,
|
||||
@@ -358,77 +353,6 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
this.editObject(project.getObject(objectName));
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when an object is started to be dragged from the object list.
|
||||
* See `_onPointerOverInstancesEditor`, `_onPointerOutInstancesEditor` and
|
||||
* `_onPointerUpInstancesEditor` for the drag'n'drop workflow.
|
||||
*/
|
||||
_onStartDraggingObjectFromList = (object: gdObject) => {
|
||||
// "Hijack" the name of the object that is dragged in the objects list.
|
||||
// We'll then listen to "pointer over" events to see if the object
|
||||
// is the dragged on the instances editor.
|
||||
this.setState({
|
||||
objectDraggedFromList: object,
|
||||
});
|
||||
};
|
||||
|
||||
_onEndDraggingObjectFromList = () => {
|
||||
// If the dragged object is not being dropped on the instances editor,
|
||||
// clear the dragged object so that we don't keep it the state (otherwise
|
||||
// we could think later that a dragging is still occuring when cursor
|
||||
// is over the instances editor).
|
||||
if (!this.state.canDropDraggedObject) {
|
||||
this.setState({
|
||||
objectDraggedFromList: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_onPointerOverInstancesEditor = () => {
|
||||
// If an object is dragged, and cursor is over the instances editor,
|
||||
// mark in the state that we can drop an instance of this object.
|
||||
if (this.state.objectDraggedFromList && !this.state.canDropDraggedObject) {
|
||||
this.setState({
|
||||
canDropDraggedObject: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_onPointerOutInstancesEditor = () => {
|
||||
// If cursor is going out of the instances editor,
|
||||
// mark in the state that we cannot drop an instance anymore.
|
||||
if (this.state.canDropDraggedObject) {
|
||||
this.setState({
|
||||
canDropDraggedObject: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_onPointerUpInstancesEditor = () => {
|
||||
if (this.state.canDropDraggedObject) {
|
||||
if (this.editor) {
|
||||
const cursorPosition = this.editor.getLastCursorPosition();
|
||||
|
||||
if (this.state.objectDraggedFromList)
|
||||
this._addInstance(
|
||||
cursorPosition[0],
|
||||
cursorPosition[1],
|
||||
this.state.objectDraggedFromList.getName()
|
||||
);
|
||||
}
|
||||
// Wait 30ms after dropping the object before reseting the canDropDraggedObject state boolean
|
||||
// to ensure ObjectsList will be prevented to actually move object in the list.
|
||||
setTimeout(
|
||||
() =>
|
||||
this.setState({
|
||||
canDropDraggedObject: false,
|
||||
objectDraggedFromList: null,
|
||||
}),
|
||||
30 // This value is very conservative, and timeout may not be needed at all
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
editGroup = (group: ?gdObjectGroup) => {
|
||||
this.setState({ editedGroup: group });
|
||||
};
|
||||
@@ -491,7 +415,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
// Remember where to create the instance, when the object will be created
|
||||
this.setState({
|
||||
newObjectInstancePosition: this.editor.getLastCursorPosition(),
|
||||
newObjectInstanceSceneCoordinates: this.editor.getLastCursorSceneCoordinates(),
|
||||
});
|
||||
|
||||
if (this._objectsList)
|
||||
@@ -504,36 +428,17 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
const objectSelected = this.state.selectedObjectNames[0];
|
||||
const cursorPosition = this.editor.getLastCursorPosition();
|
||||
this._addInstance(cursorPosition[0], cursorPosition[1], objectSelected);
|
||||
const cursorPosition = this.editor.getLastCursorSceneCoordinates();
|
||||
this._addInstance(cursorPosition, objectSelected);
|
||||
this.setState({
|
||||
selectedObjectNames: [objectSelected],
|
||||
});
|
||||
};
|
||||
|
||||
_addInstance = (x: number, y: number, objectName: string) => {
|
||||
if (!objectName) return;
|
||||
_addInstance = (pos: [number, number], objectName: string) => {
|
||||
if (!objectName || !this.editor) return;
|
||||
|
||||
const instance = this.props.initialInstances.insertNewInitialInstance();
|
||||
instance.setObjectName(objectName);
|
||||
if (this.state.uiSettings.grid) {
|
||||
x = roundPosition(
|
||||
x,
|
||||
this.state.uiSettings.gridWidth,
|
||||
this.state.uiSettings.gridOffsetX
|
||||
);
|
||||
y = roundPosition(
|
||||
y,
|
||||
this.state.uiSettings.gridHeight,
|
||||
this.state.uiSettings.gridOffsetY
|
||||
);
|
||||
}
|
||||
instance.setX(x);
|
||||
instance.setY(y);
|
||||
this.props.initialInstances.iterateOverInstances(this.zOrderFinder);
|
||||
if (this.zOrderFinder) {
|
||||
instance.setZOrder(this.zOrderFinder.getHighestZOrder() + 1);
|
||||
}
|
||||
this.editor.addInstances(pos, [objectName]);
|
||||
this.setState(
|
||||
{
|
||||
selectedObjectNames: [],
|
||||
@@ -600,20 +505,16 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
/**
|
||||
* Create an instance of the given object, at the position
|
||||
* previously chosen (see `newObjectInstancePosition`).
|
||||
* previously chosen (see `newObjectInstanceSceneCoordinates`).
|
||||
*/
|
||||
_addNewObjectInstance = (newObjectName: string) => {
|
||||
const { newObjectInstancePosition } = this.state;
|
||||
if (!newObjectInstancePosition) {
|
||||
_addInstanceForNewObject = (newObjectName: string) => {
|
||||
const { newObjectInstanceSceneCoordinates } = this.state;
|
||||
if (!newObjectInstanceSceneCoordinates) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._addInstance(
|
||||
newObjectInstancePosition[0],
|
||||
newObjectInstancePosition[1],
|
||||
newObjectName
|
||||
);
|
||||
this.setState({ newObjectInstancePosition: null });
|
||||
this._addInstance(newObjectInstanceSceneCoordinates, newObjectName);
|
||||
this.setState({ newObjectInstanceSceneCoordinates: null });
|
||||
};
|
||||
|
||||
_onRemoveLayer = (layerName: string, done: boolean => void) => {
|
||||
@@ -784,11 +685,6 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
done(true);
|
||||
};
|
||||
|
||||
_canGroupUseNewName = (groupWithScope: GroupWithContext, newName: string) => {
|
||||
//TODO: implement and launch refactoring (using gd.WholeProjectRefactorer but only on some events)
|
||||
return true;
|
||||
};
|
||||
|
||||
_onRenameGroup = (
|
||||
groupWithContext: GroupWithContext,
|
||||
newName: string,
|
||||
@@ -870,8 +766,8 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
if (this.editor) {
|
||||
const position = useLastCursorPosition
|
||||
? this.editor.getLastCursorPosition()
|
||||
: this.editor.getLastContextMenuPosition();
|
||||
? this.editor.getLastCursorSceneCoordinates()
|
||||
: this.editor.getLastContextMenuSceneCoordinates();
|
||||
Clipboard.set(INSTANCES_CLIPBOARD_KIND, {
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
@@ -890,8 +786,8 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
if (!clipboardContent || !this.editor) return;
|
||||
|
||||
const position = useLastCursorPosition
|
||||
? this.editor.getLastCursorPosition()
|
||||
: this.editor.getLastContextMenuPosition();
|
||||
? this.editor.getLastCursorSceneCoordinates()
|
||||
: this.editor.getLastContextMenuSceneCoordinates();
|
||||
const { x, y } = clipboardContent;
|
||||
clipboardContent.instances
|
||||
.map(serializedInstance => {
|
||||
@@ -997,7 +893,6 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
project={project}
|
||||
layout={layout}
|
||||
initialInstances={initialInstances}
|
||||
onAddInstance={this._addInstance}
|
||||
options={this.state.uiSettings}
|
||||
onChangeOptions={this.setUiSettings}
|
||||
instancesSelection={this.instancesSelection}
|
||||
@@ -1006,9 +901,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
onInstancesMoved={this._onInstancesMoved}
|
||||
onInstancesResized={this._onInstancesResized}
|
||||
onInstancesRotated={this._onInstancesRotated}
|
||||
onPointerUp={this._onPointerUpInstancesEditor}
|
||||
onPointerOver={this._onPointerOverInstancesEditor}
|
||||
onPointerOut={this._onPointerOutInstancesEditor}
|
||||
selectedObjectNames={this.state.selectedObjectNames}
|
||||
onContextMenu={this._onContextMenu}
|
||||
onCopy={() => this.copySelection({ useLastCursorPosition: true })}
|
||||
onCut={() => this.cutSelection({ useLastCursorPosition: true })}
|
||||
@@ -1017,7 +910,6 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
onRedo={this.redo}
|
||||
onZoomOut={this.zoomOut}
|
||||
onZoomIn={this.zoomIn}
|
||||
showDropCursor={this.state.canDropDraggedObject}
|
||||
wrappedEditorRef={editor => (this.editor = editor)}
|
||||
pauseRendering={!isActive}
|
||||
/>
|
||||
@@ -1048,13 +940,10 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
onEditObject={this.props.onEditObject || this.editObject}
|
||||
onDeleteObject={this._onDeleteObject}
|
||||
canRenameObject={this._canObjectOrGroupUseNewName}
|
||||
onObjectCreated={this._addNewObjectInstance}
|
||||
onObjectCreated={this._addInstanceForNewObject}
|
||||
onObjectSelected={this._onObjectSelected}
|
||||
onRenameObject={this._onRenameObject}
|
||||
onObjectPasted={() => this.updateBehaviorsSharedData()}
|
||||
onStartDraggingObject={this._onStartDraggingObjectFromList}
|
||||
onEndDraggingObject={this._onEndDraggingObjectFromList}
|
||||
canMoveObjects={!this.state.canDropDraggedObject}
|
||||
selectedObjectTags={this.state.selectedObjectTags}
|
||||
onChangeSelectedObjectTags={selectedObjectTags =>
|
||||
this.setState({
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { Component } from 'react';
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import TouchBackend from 'react-dnd-touch-backend';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
|
||||
class DragDropContextProvider extends Component {
|
||||
class DragAndDropContextProvider extends Component {
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default DragDropContext(HTML5Backend)(DragDropContextProvider);
|
||||
export default DragDropContext(HTML5Backend)(DragAndDropContextProvider);
|
104
newIDE/app/src/UI/DragAndDrop/DragSourceAndDropTarget.js
Normal file
104
newIDE/app/src/UI/DragAndDrop/DragSourceAndDropTarget.js
Normal file
@@ -0,0 +1,104 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import {
|
||||
DragSource,
|
||||
type DragSourceMonitor,
|
||||
type DragSourceConnector,
|
||||
type ConnectDragSource,
|
||||
DropTarget,
|
||||
type DropTargetMonitor,
|
||||
type DropTargetConnector,
|
||||
type ConnectDropTarget,
|
||||
} from 'react-dnd';
|
||||
|
||||
type Props<DraggedItemType> = {|
|
||||
children: ({
|
||||
connectDragSource: ConnectDragSource,
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
}) => React.Node,
|
||||
beginDrag: () => DraggedItemType,
|
||||
canDrop: (item: DraggedItemType) => boolean,
|
||||
drop: () => void,
|
||||
|};
|
||||
|
||||
type DragSourceProps = {|
|
||||
connectDragSource: ConnectDragSource,
|
||||
|};
|
||||
|
||||
type DropTargetProps = {|
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
|};
|
||||
|
||||
type InnerDragSourceAndDropTargetProps<DraggedItemType> = {|
|
||||
...Props<DraggedItemType>,
|
||||
...DragSourceProps,
|
||||
...DropTargetProps,
|
||||
|};
|
||||
|
||||
export const makeDragSourceAndDropTarget = <DraggedItemType>(
|
||||
reactDndType: string
|
||||
): ((Props<DraggedItemType>) => React.Node) => {
|
||||
const sourceSpec = {
|
||||
beginDrag(props: InnerDragSourceAndDropTargetProps<DraggedItemType>) {
|
||||
return props.beginDrag();
|
||||
},
|
||||
};
|
||||
|
||||
function sourceCollect(
|
||||
connect: DragSourceConnector,
|
||||
monitor: DragSourceMonitor
|
||||
): DragSourceProps {
|
||||
return {
|
||||
connectDragSource: connect.dragSource(),
|
||||
};
|
||||
}
|
||||
|
||||
const targetSpec = {
|
||||
canDrop(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
const item = monitor.getItem();
|
||||
return item && props.canDrop(item);
|
||||
},
|
||||
drop(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
if (monitor.didDrop()) {
|
||||
return; // Drop already handled by another target
|
||||
}
|
||||
props.drop();
|
||||
},
|
||||
};
|
||||
|
||||
function targetCollect(
|
||||
connect: DropTargetConnector,
|
||||
monitor: DropTargetMonitor
|
||||
): DropTargetProps {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
};
|
||||
}
|
||||
|
||||
const InnerDragSourceAndDropTarget = DragSource(
|
||||
reactDndType,
|
||||
sourceSpec,
|
||||
sourceCollect
|
||||
)(
|
||||
DropTarget(reactDndType, targetSpec, targetCollect)(
|
||||
({ children, connectDragSource, connectDropTarget, isOver, canDrop }) => {
|
||||
return children({
|
||||
connectDragSource,
|
||||
connectDropTarget,
|
||||
isOver,
|
||||
canDrop,
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return (props: Props<DraggedItemType>) => (
|
||||
<InnerDragSourceAndDropTarget {...props} />
|
||||
);
|
||||
};
|
68
newIDE/app/src/UI/DragAndDrop/DropTarget.js
Normal file
68
newIDE/app/src/UI/DragAndDrop/DropTarget.js
Normal file
@@ -0,0 +1,68 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import {
|
||||
DropTarget,
|
||||
type DropTargetMonitor,
|
||||
type DropTargetConnector,
|
||||
type ConnectDropTarget,
|
||||
} from 'react-dnd';
|
||||
|
||||
type Props<DraggedItemType> = {|
|
||||
children: ({
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
}) => React.Node,
|
||||
canDrop: (item: DraggedItemType) => boolean,
|
||||
hover?: (monitor: DropTargetMonitor) => void,
|
||||
drop: (monitor: DropTargetMonitor) => void,
|
||||
|};
|
||||
|
||||
type DropTargetProps = {|
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
|};
|
||||
|
||||
export const makeDropTarget = <DraggedItemType>(
|
||||
reactDndType: string
|
||||
): ((Props<DraggedItemType>) => React.Node) => {
|
||||
const targetSpec = {
|
||||
canDrop(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
const item = monitor.getItem();
|
||||
return item && props.canDrop(item);
|
||||
},
|
||||
hover(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
if (props.hover) props.hover(monitor);
|
||||
},
|
||||
drop(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
if (monitor.didDrop()) {
|
||||
return; // Drop already handled by another target
|
||||
}
|
||||
props.drop(monitor);
|
||||
},
|
||||
};
|
||||
|
||||
function targetCollect(
|
||||
connect: DropTargetConnector,
|
||||
monitor: DropTargetMonitor
|
||||
): DropTargetProps {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
};
|
||||
}
|
||||
|
||||
const InnerDropTarget = DropTarget(reactDndType, targetSpec, targetCollect)(
|
||||
({ children, connectDropTarget, isOver, canDrop }) => {
|
||||
return children({
|
||||
connectDropTarget,
|
||||
isOver,
|
||||
canDrop,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return (props: Props<DraggedItemType>) => <InnerDropTarget {...props} />;
|
||||
};
|
@@ -69,6 +69,7 @@ type ListItemProps = {|
|
||||
|},
|
||||
|
||||
style?: {|
|
||||
color?: string,
|
||||
backgroundColor?: string,
|
||||
borderBottom?: string,
|
||||
opacity?: number,
|
||||
|
@@ -0,0 +1,31 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import ThemeConsumer from '../Theme/ThemeConsumer';
|
||||
|
||||
const styles = {
|
||||
dropIndicator: {
|
||||
borderTop: '2px solid #18dcf2',
|
||||
height: 0,
|
||||
marginTop: '-1px',
|
||||
marginBottom: '-1px',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
};
|
||||
|
||||
export default function DropIndicator({ canDrop }: {| canDrop: boolean |}) {
|
||||
return (
|
||||
<ThemeConsumer>
|
||||
{gdevelopTheme => (
|
||||
<div
|
||||
style={{
|
||||
...styles.dropIndicator,
|
||||
borderColor: canDrop
|
||||
? gdevelopTheme.listItem.selectedBackgroundColor
|
||||
: gdevelopTheme.listItem.selectedErrorBackgroundColor,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ThemeConsumer>
|
||||
);
|
||||
}
|
@@ -3,7 +3,6 @@ import React from 'react';
|
||||
import { ListItem } from '../List';
|
||||
import ListIcon from '../ListIcon';
|
||||
import TextField, { noMarginTextFieldInListItemTopOffset } from '../TextField';
|
||||
import { type Item } from '.';
|
||||
import ThemeConsumer from '../Theme/ThemeConsumer';
|
||||
|
||||
const styles = {
|
||||
@@ -17,23 +16,26 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {
|
||||
index: number,
|
||||
const LEFT_MOUSE_BUTTON = 0;
|
||||
|
||||
type Props<Item> = {
|
||||
item: Item,
|
||||
itemName: string,
|
||||
isBold: boolean,
|
||||
onRename: string => void,
|
||||
editingName: boolean,
|
||||
getThumbnail?: () => string,
|
||||
selected: true,
|
||||
selected: boolean,
|
||||
onItemSelected: (?Item) => void,
|
||||
errorStatus: '' | 'error' | 'warning',
|
||||
buildMenuTemplate: () => Array<any>,
|
||||
style: Object,
|
||||
onEdit?: ?(Item) => void,
|
||||
};
|
||||
|
||||
class ItemRow extends React.Component<Props, *> {
|
||||
class ItemRow<Item> extends React.Component<Props<Item>> {
|
||||
textField: ?TextField;
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
componentDidUpdate(prevProps: Props<Item>) {
|
||||
if (!prevProps.editingName && this.props.editingName) {
|
||||
setTimeout(() => {
|
||||
if (this.textField) this.textField.focus();
|
||||
@@ -42,12 +44,20 @@ class ItemRow extends React.Component<Props, *> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { item, selected, style, getThumbnail, errorStatus } = this.props;
|
||||
const {
|
||||
item,
|
||||
itemName,
|
||||
isBold,
|
||||
selected,
|
||||
getThumbnail,
|
||||
errorStatus,
|
||||
onEdit,
|
||||
onItemSelected,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ThemeConsumer>
|
||||
{muiTheme => {
|
||||
const itemName = item.getName();
|
||||
const label = this.props.editingName ? (
|
||||
<TextField
|
||||
id="rename-item-field"
|
||||
@@ -71,6 +81,8 @@ class ItemRow extends React.Component<Props, *> {
|
||||
color: selected
|
||||
? muiTheme.listItem.selectedTextColor
|
||||
: undefined,
|
||||
fontStyle: isBold ? 'italic' : undefined,
|
||||
fontWeight: isBold ? 'bold' : 'normal',
|
||||
}}
|
||||
>
|
||||
{itemName}
|
||||
@@ -96,7 +108,7 @@ class ItemRow extends React.Component<Props, *> {
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
style={{ ...itemStyle, ...style }}
|
||||
style={{ ...itemStyle }}
|
||||
primaryText={label}
|
||||
leftIcon={
|
||||
getThumbnail && <ListIcon iconSize={32} src={getThumbnail()} />
|
||||
@@ -104,10 +116,18 @@ class ItemRow extends React.Component<Props, *> {
|
||||
displayMenuButton
|
||||
buildMenuTemplate={this.props.buildMenuTemplate}
|
||||
onClick={() => {
|
||||
if (!this.props.onItemSelected) return;
|
||||
if (!onItemSelected) return;
|
||||
if (this.props.editingName) return;
|
||||
|
||||
this.props.onItemSelected(selected ? null : item);
|
||||
onItemSelected(selected ? null : item);
|
||||
}}
|
||||
onDoubleClick={event => {
|
||||
if (event.button !== LEFT_MOUSE_BUTTON) return;
|
||||
if (!onEdit) return;
|
||||
if (this.props.editingName) return;
|
||||
|
||||
onItemSelected(null);
|
||||
onEdit(item);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@@ -3,52 +3,41 @@ import * as React from 'react';
|
||||
import { List } from 'react-virtualized';
|
||||
import ItemRow from './ItemRow';
|
||||
import { AddListItem } from '../ListCommonItem';
|
||||
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
|
||||
import { listItemWith32PxIconHeight, listItemWithoutIconHeight } from '../List';
|
||||
import { makeDragSourceAndDropTarget } from '../DragAndDrop/DragSourceAndDropTarget';
|
||||
import DropIndicator from './DropIndicator';
|
||||
|
||||
const SortableItemRow = SortableElement(props => {
|
||||
const { style, ...otherProps } = props;
|
||||
return (
|
||||
<div style={style}>
|
||||
<ItemRow {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const SortableAddItemRow = SortableElement(props => {
|
||||
const { style, ...otherProps } = props;
|
||||
return (
|
||||
<div style={style}>
|
||||
<AddListItem {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export type Item = {
|
||||
key: string | number,
|
||||
getName: () => string,
|
||||
};
|
||||
|
||||
type ItemsListProps = {
|
||||
type Props<Item> = {|
|
||||
height: number,
|
||||
width: number,
|
||||
fullList: Array<Item>,
|
||||
selectedItem: ?Item,
|
||||
selectedItems: Array<Item>,
|
||||
onAddNewItem?: () => void,
|
||||
addNewItemLabel?: React.Node | string,
|
||||
onRename: (Item, string) => void,
|
||||
getThumbnail?: Item => string,
|
||||
getItemName: Item => string,
|
||||
getItemThumbnail?: Item => string,
|
||||
isItemBold?: Item => boolean,
|
||||
onItemSelected: (?Item) => void,
|
||||
onEditItem?: Item => void,
|
||||
renamedItem: ?Item,
|
||||
addNewItemLabel: React.Node | string,
|
||||
erroredItems?: { [string]: '' | 'error' | 'warning' },
|
||||
buildMenuTemplate: (Item, index: number) => any,
|
||||
};
|
||||
onMoveSelectionToItem: (destinationItem: Item) => void,
|
||||
canMoveSelectionToItem?: ?(destinationItem: Item) => boolean,
|
||||
reactDndType: string,
|
||||
|};
|
||||
|
||||
class ItemsList extends React.Component<ItemsListProps, *> {
|
||||
list: any;
|
||||
export default class SortableVirtualizedItemList<Item> extends React.Component<
|
||||
Props<Item>
|
||||
> {
|
||||
_list: ?List;
|
||||
DragSourceAndDropTarget = makeDragSourceAndDropTarget<Item>(
|
||||
this.props.reactDndType
|
||||
);
|
||||
|
||||
forceUpdateGrid() {
|
||||
if (this.list) this.list.forceUpdateGrid();
|
||||
if (this._list) this._list.forceUpdateGrid();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -56,56 +45,105 @@ class ItemsList extends React.Component<ItemsListProps, *> {
|
||||
height,
|
||||
width,
|
||||
fullList,
|
||||
selectedItem,
|
||||
selectedItems,
|
||||
addNewItemLabel,
|
||||
renamedItem,
|
||||
getThumbnail,
|
||||
getItemThumbnail,
|
||||
getItemName,
|
||||
erroredItems,
|
||||
onAddNewItem,
|
||||
isItemBold,
|
||||
onEditItem,
|
||||
onMoveSelectionToItem,
|
||||
canMoveSelectionToItem,
|
||||
} = this.props;
|
||||
const { DragSourceAndDropTarget } = this;
|
||||
|
||||
return (
|
||||
<List
|
||||
ref={list => (this.list = list)}
|
||||
ref={list => (this._list = list)}
|
||||
height={height}
|
||||
rowCount={fullList.length}
|
||||
rowCount={fullList.length + (onAddNewItem ? 1 : 0)}
|
||||
rowHeight={
|
||||
getThumbnail ? listItemWith32PxIconHeight : listItemWithoutIconHeight
|
||||
getItemThumbnail
|
||||
? listItemWith32PxIconHeight
|
||||
: listItemWithoutIconHeight
|
||||
}
|
||||
rowRenderer={({ index, key, style }) => {
|
||||
const item = fullList[index];
|
||||
if (item.key === 'add-item-row') {
|
||||
rowRenderer={({
|
||||
index,
|
||||
key,
|
||||
style,
|
||||
}: {|
|
||||
index: number,
|
||||
key: string,
|
||||
style: Object,
|
||||
|}) => {
|
||||
if (index >= fullList.length) {
|
||||
return (
|
||||
<SortableAddItemRow
|
||||
index={fullList.length}
|
||||
key={key}
|
||||
style={style}
|
||||
disabled
|
||||
onClick={this.props.onAddNewItem}
|
||||
primaryText={addNewItemLabel}
|
||||
/>
|
||||
<div style={style} key={key}>
|
||||
<AddListItem
|
||||
disabled
|
||||
onClick={onAddNewItem}
|
||||
primaryText={addNewItemLabel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const item = fullList[index];
|
||||
const nameBeingEdited = renamedItem === item;
|
||||
const itemName = getItemName(item);
|
||||
|
||||
return (
|
||||
<SortableItemRow
|
||||
index={index}
|
||||
key={key}
|
||||
item={item}
|
||||
style={style}
|
||||
onRename={newName => this.props.onRename(item, newName)}
|
||||
editingName={nameBeingEdited}
|
||||
getThumbnail={getThumbnail ? () => getThumbnail(item) : undefined}
|
||||
selected={item === selectedItem}
|
||||
onItemSelected={this.props.onItemSelected}
|
||||
errorStatus={
|
||||
erroredItems ? erroredItems[item.getName()] || '' : ''
|
||||
}
|
||||
buildMenuTemplate={() =>
|
||||
this.props.buildMenuTemplate(item, index)
|
||||
}
|
||||
/>
|
||||
<div style={style} key={key}>
|
||||
<DragSourceAndDropTarget
|
||||
beginDrag={() => {
|
||||
this.props.onItemSelected(item);
|
||||
return {};
|
||||
}}
|
||||
canDrop={() =>
|
||||
canMoveSelectionToItem ? canMoveSelectionToItem(item) : true
|
||||
}
|
||||
drop={() => {
|
||||
onMoveSelectionToItem(item);
|
||||
}}
|
||||
>
|
||||
{({ connectDragSource, connectDropTarget, isOver, canDrop }) =>
|
||||
// Add an extra div because connectDropTarget/connectDragSource can
|
||||
// only be used on native elements
|
||||
connectDropTarget(
|
||||
connectDragSource(
|
||||
<div>
|
||||
{isOver && <DropIndicator canDrop={canDrop} />}
|
||||
<ItemRow
|
||||
item={item}
|
||||
itemName={itemName}
|
||||
isBold={isItemBold ? isItemBold(item) : false}
|
||||
onRename={newName =>
|
||||
this.props.onRename(item, newName)
|
||||
}
|
||||
editingName={nameBeingEdited}
|
||||
getThumbnail={
|
||||
getItemThumbnail
|
||||
? () => getItemThumbnail(item)
|
||||
: undefined
|
||||
}
|
||||
selected={selectedItems.indexOf(item) !== -1}
|
||||
onItemSelected={this.props.onItemSelected}
|
||||
errorStatus={
|
||||
erroredItems ? erroredItems[itemName] || '' : ''
|
||||
}
|
||||
buildMenuTemplate={() =>
|
||||
this.props.buildMenuTemplate(item, index)
|
||||
}
|
||||
onEdit={onEditItem}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
</DragSourceAndDropTarget>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
width={width}
|
||||
@@ -113,6 +151,3 @@ class ItemsList extends React.Component<ItemsListProps, *> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SortableItemsList = SortableContainer(ItemsList, { withRef: true });
|
||||
export default SortableItemsList;
|
||||
|
@@ -7,9 +7,11 @@ import {
|
||||
getProgramOpeningCount,
|
||||
incrementProgramOpeningCount,
|
||||
} from './LocalStats';
|
||||
import { getStartupTimesSummary } from '../StartupTimes';
|
||||
|
||||
const isDev = Window.isDev();
|
||||
let client = null;
|
||||
let startupTimesSummary = null;
|
||||
|
||||
export const installAnalyticsEvents = (authentification: Authentification) => {
|
||||
if (isDev) {
|
||||
@@ -28,8 +30,12 @@ export const installAnalyticsEvents = (authentification: Authentification) => {
|
||||
});
|
||||
|
||||
client.extendEvents(function() {
|
||||
// Include the user public profile.
|
||||
const userProfile = authentification.getUserProfileSync();
|
||||
|
||||
// Compute the startup times (only once to avoid doing this for every event).
|
||||
startupTimesSummary = startupTimesSummary || getStartupTimesSummary();
|
||||
|
||||
return {
|
||||
user: {
|
||||
uuid: getUserUUID(),
|
||||
@@ -41,6 +47,7 @@ export const installAnalyticsEvents = (authentification: Authentification) => {
|
||||
localStats: {
|
||||
programOpeningCount: getProgramOpeningCount(),
|
||||
},
|
||||
startupTimesSummary,
|
||||
page: {
|
||||
title: document.title,
|
||||
url: document.location.href,
|
||||
|
@@ -1,37 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import { NativeTypes } from 'react-dnd-html5-backend';
|
||||
import { DropTarget } from 'react-dnd';
|
||||
|
||||
class SimpleDropTarget extends Component {
|
||||
render() {
|
||||
const { connectDropTarget } = this.props;
|
||||
return connectDropTarget(this.props.children);
|
||||
}
|
||||
}
|
||||
|
||||
function collect(connect, monitor) {
|
||||
return {
|
||||
// Call this function inside render()
|
||||
// to let React DnD handle the drag events:
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
// You can ask the monitor about the current drag state:
|
||||
isOver: monitor.isOver(),
|
||||
isOverCurrent: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
itemType: monitor.getItemType(),
|
||||
};
|
||||
}
|
||||
const spec = {
|
||||
drop(props, monitor, component) {
|
||||
if (monitor.didDrop()) {
|
||||
// If you want, you can check whether some nested
|
||||
// target already handled drop
|
||||
return;
|
||||
}
|
||||
|
||||
const item = monitor.getItem();
|
||||
return { item };
|
||||
},
|
||||
};
|
||||
|
||||
export default DropTarget(NativeTypes.TEXT, spec, collect)(SimpleDropTarget);
|
39
newIDE/app/src/Utils/StartupTimes.js
Normal file
39
newIDE/app/src/Utils/StartupTimes.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// @flow
|
||||
type RawStepMeasure = [string, number];
|
||||
type SummarizedStep = {|
|
||||
stepName: string,
|
||||
time: number,
|
||||
elapsedTime: number,
|
||||
|};
|
||||
type Summary = {|
|
||||
totalStartupTime: number,
|
||||
steps: Array<SummarizedStep>,
|
||||
|};
|
||||
|
||||
export const GD_STARTUP_TIMES: Array<RawStepMeasure> = global.GD_STARTUP_TIMES;
|
||||
|
||||
if (!GD_STARTUP_TIMES) {
|
||||
console.error(
|
||||
'Could not find GD_STARTUP_TIMES array. Have you declared it in index.html, in a synchronous script?'
|
||||
);
|
||||
}
|
||||
|
||||
export const getStartupTimesSummary = (): Summary => {
|
||||
let previousStep = ['<init>', 0];
|
||||
|
||||
let steps = GD_STARTUP_TIMES.map(step => {
|
||||
const stepSummary = {
|
||||
stepName: step[0],
|
||||
time: step[1],
|
||||
elapsedTime: step[1] - previousStep[1],
|
||||
};
|
||||
|
||||
previousStep = step;
|
||||
return stepSummary;
|
||||
});
|
||||
|
||||
return {
|
||||
steps,
|
||||
totalStartupTime: previousStep[1],
|
||||
};
|
||||
};
|
@@ -13,6 +13,7 @@ import { unregister } from './registerServiceWorker';
|
||||
import './UI/iconmoon-font.css'; // Styles for Iconmoon font.
|
||||
import optionalRequire from './Utils/OptionalRequire.js';
|
||||
import { showErrorBox } from './UI/Messages/MessageBox';
|
||||
const GD_STARTUP_TIMES = global.GD_STARTUP_TIMES || [];
|
||||
|
||||
// No i18n in this file
|
||||
|
||||
@@ -32,6 +33,7 @@ class Bootstrapper extends Component<{}, State> {
|
||||
installAnalyticsEvents(this.authentification);
|
||||
installRaven();
|
||||
installFullstory();
|
||||
GD_STARTUP_TIMES.push(["bootstrapperComponentDidMount", performance.now()]);
|
||||
|
||||
if (electron) {
|
||||
import(/* webpackChunkName: "local-app" */ './LocalApp')
|
||||
@@ -67,8 +69,13 @@ class Bootstrapper extends Component<{}, State> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
if (rootElement) ReactDOM.render(<Bootstrapper />, rootElement);
|
||||
if (rootElement) {
|
||||
GD_STARTUP_TIMES.push(['reactDOMRenderCall', performance.now()]);
|
||||
ReactDOM.render(<Bootstrapper />, rootElement);
|
||||
}
|
||||
else console.error('No root element defined in index.html');
|
||||
|
||||
// registerServiceWorker();
|
||||
|
85
newIDE/app/src/stories/DragAndDropTestBed.js
Normal file
85
newIDE/app/src/stories/DragAndDropTestBed.js
Normal file
@@ -0,0 +1,85 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { makeDragSourceAndDropTarget } from '../UI/DragAndDrop/DragSourceAndDropTarget';
|
||||
import { makeDropTarget } from '../UI/DragAndDrop/DropTarget';
|
||||
|
||||
type Props = {||};
|
||||
|
||||
const DragSourceAndDropTarget = makeDragSourceAndDropTarget<{
|
||||
someData: string,
|
||||
}>('dnd-type1');
|
||||
|
||||
const DragSourceAndDropTargetBox = ({ name }: {| name: string |}) => (
|
||||
<DragSourceAndDropTarget
|
||||
beginDrag={() => {
|
||||
console.log(
|
||||
'Begin dragging' + name + ', which should be added to the selection'
|
||||
);
|
||||
|
||||
return { someData: name };
|
||||
}}
|
||||
canDrop={() => name.indexOf('cant-drop-here') === -1}
|
||||
drop={() => {
|
||||
console.log('Selection to be dropped on' + name);
|
||||
}}
|
||||
>
|
||||
{({ connectDragSource, connectDropTarget, isOver, canDrop }) =>
|
||||
connectDropTarget(
|
||||
connectDragSource(
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'blue',
|
||||
color: 'white',
|
||||
height: 100,
|
||||
width: 100,
|
||||
margin: 20,
|
||||
}}
|
||||
>
|
||||
This is a box called {name}.{isOver && <div>Hovered</div>}
|
||||
{canDrop && <div>Can drop here</div>}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
</DragSourceAndDropTarget>
|
||||
);
|
||||
|
||||
const DropTarget = makeDropTarget<{
|
||||
someData: string,
|
||||
}>('dnd-type1');
|
||||
|
||||
const DropTargetBox = ({ name }: {| name: string |}) => (
|
||||
<DropTarget
|
||||
canDrop={() => name.indexOf('cant-drop-here') === -1}
|
||||
drop={() => {
|
||||
console.log('Selection to be dropped on' + name);
|
||||
}}
|
||||
>
|
||||
{({ connectDropTarget, isOver, canDrop }) =>
|
||||
connectDropTarget(
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'green',
|
||||
color: 'white',
|
||||
height: 100,
|
||||
width: 100,
|
||||
margin: 20,
|
||||
}}
|
||||
>
|
||||
This is a box called {name}.{isOver && <div>Hovered</div>}
|
||||
{canDrop && <div>Can drop here</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</DropTarget>
|
||||
);
|
||||
|
||||
export default (props: Props) => (
|
||||
<div>
|
||||
<DragSourceAndDropTargetBox name="box1" />
|
||||
<DragSourceAndDropTargetBox name="box2, cant-drop-here" />
|
||||
<DragSourceAndDropTargetBox name="box3" />
|
||||
<DropTargetBox name="box4, drop target only" />
|
||||
<DropTargetBox name="box5, drop target but cant-drop-here" />
|
||||
</div>
|
||||
);
|
@@ -65,7 +65,7 @@ import muiDecorator from './ThemeDecorator';
|
||||
import paperDecorator from './PaperDecorator';
|
||||
import ValueStateHolder from './ValueStateHolder';
|
||||
import RefGetter from './RefGetter';
|
||||
import DragDropContextProvider from '../Utils/DragDropHelpers/DragDropContextProvider';
|
||||
import DragAndDropContextProvider from '../UI/DragAndDrop/DragAndDropContextProvider';
|
||||
import ResourcesLoader from '../ResourcesLoader';
|
||||
import VariablesList from '../VariablesList';
|
||||
import ExpressionSelector from '../EventsSheet/InstructionEditor/InstructionOrExpressionSelector/ExpressionSelector';
|
||||
@@ -146,6 +146,7 @@ import Dialog from '../UI/Dialog';
|
||||
import MiniToolbar, { MiniToolbarText } from '../UI/MiniToolbar';
|
||||
import NewObjectDialog from '../ObjectsList/NewObjectDialog';
|
||||
import { Column } from '../UI/Grid';
|
||||
import DragAndDropTestBed from './DragAndDropTestBed';
|
||||
|
||||
// No i18n in this file
|
||||
|
||||
@@ -244,6 +245,12 @@ storiesOf('UI Building Blocks/SemiControlledTextField', module)
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/DragAndDrop', module).add('test bed', () => (
|
||||
<DragAndDropContextProvider>
|
||||
<DragAndDropTestBed />
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/SemiControlledAutoComplete', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default, with text', () => (
|
||||
@@ -716,75 +723,93 @@ storiesOf('UI Building Blocks/ClosableTabs', module)
|
||||
<ValueStateHolder
|
||||
initialValue={0}
|
||||
render={(value, onChange) => (
|
||||
<FixedHeightFlexContainer height={400}>
|
||||
<Column expand>
|
||||
<ClosableTabs>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 1 activated')}
|
||||
closable
|
||||
active={value === 0}
|
||||
label="Tab 1"
|
||||
onClick={() => onChange(0)}
|
||||
onClose={action('Close tab 1')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 2 activated')}
|
||||
closable
|
||||
active={value === 1}
|
||||
label="Tab 2"
|
||||
onClick={() => onChange(1)}
|
||||
onClose={action('Close tab 2')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 3 activated')}
|
||||
closable
|
||||
active={value === 2}
|
||||
label="Tab 3"
|
||||
onClick={() => onChange(2)}
|
||||
onClose={action('Close tab 3')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
</ClosableTabs>
|
||||
{
|
||||
<TabContentContainer active={value === 0}>
|
||||
<div
|
||||
style={{ backgroundColor: 'green', height: '100%', flex: 1 }}
|
||||
>
|
||||
The second tab has a list of objects. Check that the scrolling
|
||||
position is maintained while navigating between tabs.
|
||||
</div>
|
||||
</TabContentContainer>
|
||||
}
|
||||
{
|
||||
<TabContentContainer active={value === 1}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={[]}
|
||||
onChangeSelectedObjectTags={() => {}}
|
||||
getAllObjectTags={() => []}
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={400}>
|
||||
<Column expand>
|
||||
<ClosableTabs>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 1 activated')}
|
||||
closable
|
||||
active={value === 0}
|
||||
label="Tab 1"
|
||||
onClick={() => onChange(0)}
|
||||
onClose={action('Close tab 1')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
</TabContentContainer>
|
||||
}
|
||||
{
|
||||
<TabContentContainer active={value === 2}>
|
||||
<div
|
||||
style={{ backgroundColor: 'green', height: '100%', flex: 1 }}
|
||||
>
|
||||
Tab 3 content
|
||||
</div>
|
||||
</TabContentContainer>
|
||||
}
|
||||
</Column>
|
||||
</FixedHeightFlexContainer>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 2 activated')}
|
||||
closable
|
||||
active={value === 1}
|
||||
label="Tab 2"
|
||||
onClick={() => onChange(1)}
|
||||
onClose={action('Close tab 2')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 3 activated')}
|
||||
closable
|
||||
active={value === 2}
|
||||
label="Tab 3"
|
||||
onClick={() => onChange(2)}
|
||||
onClose={action('Close tab 3')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
</ClosableTabs>
|
||||
{
|
||||
<TabContentContainer active={value === 0}>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'green',
|
||||
height: '100%',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
The second tab has a list of objects. Check that the
|
||||
scrolling position is maintained while navigating between
|
||||
tabs.
|
||||
</div>
|
||||
</TabContentContainer>
|
||||
}
|
||||
{
|
||||
<TabContentContainer active={value === 1}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={[]}
|
||||
onChangeSelectedObjectTags={() => {}}
|
||||
getAllObjectTags={() => []}
|
||||
canRenameObject={() => true}
|
||||
onDeleteObject={(objectWithContext, cb) => cb(true)}
|
||||
onRenameObject={(objectWithContext, newName, cb) =>
|
||||
cb(true)
|
||||
}
|
||||
onObjectCreated={() => {}}
|
||||
onObjectSelected={() => {}}
|
||||
/>
|
||||
</TabContentContainer>
|
||||
}
|
||||
{
|
||||
<TabContentContainer active={value === 2}>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'green',
|
||||
height: '100%',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
Tab 3 content
|
||||
</div>
|
||||
</TabContentContainer>
|
||||
}
|
||||
</Column>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragAndDropContextProvider>
|
||||
)}
|
||||
/>
|
||||
));
|
||||
@@ -1410,7 +1435,7 @@ storiesOf('StartPage', module)
|
||||
storiesOf('DebuggerContent', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('with data', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<DebuggerContent
|
||||
gameData={debuggerGameDataDump}
|
||||
@@ -1425,10 +1450,10 @@ storiesOf('DebuggerContent', module)
|
||||
profilingInProgress={false}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('without data', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<DebuggerContent
|
||||
gameData={null}
|
||||
@@ -1443,13 +1468,13 @@ storiesOf('DebuggerContent', module)
|
||||
profilingInProgress={true}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('Profiler', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('without profiler output', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<Profiler
|
||||
onStart={action('start profiler')}
|
||||
@@ -1458,10 +1483,10 @@ storiesOf('Profiler', module)
|
||||
profilingInProgress={false}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('without profiler output, while profiling', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<Profiler
|
||||
onStart={action('start profiler')}
|
||||
@@ -1470,10 +1495,10 @@ storiesOf('Profiler', module)
|
||||
profilingInProgress={true}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('with profiler output', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<Profiler
|
||||
onStart={action('start profiler')}
|
||||
@@ -1482,10 +1507,10 @@ storiesOf('Profiler', module)
|
||||
profilingInProgress={false}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('with profiler output, while profiling', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<Profiler
|
||||
onStart={action('start profiler')}
|
||||
@@ -1494,7 +1519,7 @@ storiesOf('Profiler', module)
|
||||
profilingInProgress={true}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('MeasuresTable', module)
|
||||
@@ -1532,7 +1557,7 @@ storiesOf('LayoutChooserDialog', module)
|
||||
storiesOf('EventsTree', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default (no scope)', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<div className="gd-events-sheet">
|
||||
<FixedHeightFlexContainer height={500}>
|
||||
<EventsTree
|
||||
@@ -1565,13 +1590,13 @@ storiesOf('EventsTree', module)
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</div>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('EventsSheet', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default (no scope)', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={500}>
|
||||
<EventsSheet
|
||||
project={project}
|
||||
@@ -1596,10 +1621,10 @@ storiesOf('EventsSheet', module)
|
||||
onCreateEventsFunction={action('create events function')}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('empty (no events) (no scope)', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={500}>
|
||||
<EventsSheet
|
||||
project={project}
|
||||
@@ -1624,7 +1649,7 @@ storiesOf('EventsSheet', module)
|
||||
onCreateEventsFunction={action('create events function')}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('EventsSheet/EventsFunctionExtractorDialog', module)
|
||||
@@ -2015,41 +2040,57 @@ storiesOf('ObjectsList', module)
|
||||
.addDecorator(paperDecorator)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
<SerializedObjectDisplay object={testLayout}>
|
||||
<div style={{ height: 250 }}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={[]}
|
||||
onChangeSelectedObjectTags={selectedObjectTags => {}}
|
||||
getAllObjectTags={() => []}
|
||||
/>
|
||||
</div>
|
||||
</SerializedObjectDisplay>
|
||||
<DragAndDropContextProvider>
|
||||
<SerializedObjectDisplay object={testLayout}>
|
||||
<div style={{ height: 250 }}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
onObjectCreated={action('On object created')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={[]}
|
||||
onChangeSelectedObjectTags={selectedObjectTags => {}}
|
||||
getAllObjectTags={() => []}
|
||||
canRenameObject={() => true}
|
||||
onDeleteObject={(objectWithContext, cb) => cb(true)}
|
||||
onRenameObject={(objectWithContext, newName, cb) => cb(true)}
|
||||
onObjectSelected={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</SerializedObjectDisplay>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('with tags', () => (
|
||||
<SerializedObjectDisplay object={testLayout}>
|
||||
<div style={{ height: 250 }}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={['Tag1', 'Tag2']}
|
||||
onChangeSelectedObjectTags={action('on change selected object tags')}
|
||||
getAllObjectTags={() => [
|
||||
'Tag1',
|
||||
'Tag2',
|
||||
'Looooooooooong Tag 3',
|
||||
'Unselected Tag 4',
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</SerializedObjectDisplay>
|
||||
<DragAndDropContextProvider>
|
||||
<SerializedObjectDisplay object={testLayout}>
|
||||
<div style={{ height: 250 }}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
onObjectCreated={action('On object created')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={['Tag1', 'Tag2']}
|
||||
onChangeSelectedObjectTags={action(
|
||||
'on change selected object tags'
|
||||
)}
|
||||
getAllObjectTags={() => [
|
||||
'Tag1',
|
||||
'Tag2',
|
||||
'Looooooooooong Tag 3',
|
||||
'Unselected Tag 4',
|
||||
]}
|
||||
canRenameObject={() => true}
|
||||
onDeleteObject={(objectWithContext, cb) => cb(true)}
|
||||
onRenameObject={(objectWithContext, newName, cb) => cb(true)}
|
||||
onObjectSelected={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</SerializedObjectDisplay>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('ObjectSelector', module)
|
||||
@@ -2624,7 +2665,7 @@ storiesOf('EventsFunctionsList', module)
|
||||
storiesOf('EventsFunctionsExtensionEditor/index', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={500}>
|
||||
<EventsFunctionsExtensionEditor
|
||||
project={project}
|
||||
@@ -2641,7 +2682,7 @@ storiesOf('EventsFunctionsExtensionEditor/index', module)
|
||||
onCreateEventsFunction={action('on create events function')}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('EventsFunctionsExtensionEditor/OptionsEditorDialog', module)
|
||||
|
Reference in New Issue
Block a user