mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Display folder and groups content in InstructionOrObjectSelector
This commit is contained in:
@@ -51,6 +51,13 @@ import {
|
||||
} from '../../UI/Search/UseSearchStructuredItem';
|
||||
import { Column, Line } from '../../UI/Grid';
|
||||
import Add from '../../UI/CustomSvgIcons/Add';
|
||||
import getObjectByName from '../../Utils/GetObjectByName';
|
||||
import {
|
||||
enumerateFoldersInContainer,
|
||||
getObjectsInFolder,
|
||||
} from '../../ObjectsList/EnumerateObjectFolderOrObject';
|
||||
import { renderFolderListItem } from './SelectorListItems/FolderListItem';
|
||||
import Text from '../../UI/Text';
|
||||
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
@@ -64,6 +71,13 @@ type State = {|
|
||||
objects: Array<SearchResult<ObjectWithContext>>,
|
||||
groups: Array<SearchResult<GroupWithContext>>,
|
||||
instructions: Array<SearchResult<EnumeratedInstructionMetadata>>,
|
||||
folders: Array<
|
||||
SearchResult<{|
|
||||
path: string,
|
||||
folder: gdObjectFolderOrObject,
|
||||
global: boolean,
|
||||
|}>
|
||||
>,
|
||||
},
|
||||
|};
|
||||
|
||||
@@ -99,7 +113,7 @@ export default class InstructionOrObjectSelector extends React.PureComponent<
|
||||
> {
|
||||
state = {
|
||||
searchText: '',
|
||||
searchResults: { objects: [], groups: [], instructions: [] },
|
||||
searchResults: { objects: [], groups: [], instructions: [], folders: [] },
|
||||
};
|
||||
_searchBar = React.createRef<SearchBarInterface>();
|
||||
_scrollView = React.createRef<ScrollViewInterface>();
|
||||
@@ -124,6 +138,7 @@ export default class InstructionOrObjectSelector extends React.PureComponent<
|
||||
instructionSearchApi = null;
|
||||
objectSearchApi = null;
|
||||
groupSearchApi = null;
|
||||
folderSearchApi = null;
|
||||
|
||||
reEnumerateInstructions = (i18n: I18nType) => {
|
||||
this.freeInstructionsInfo = filterEnumeratedInstructionOrExpressionMetadataByScope(
|
||||
@@ -153,6 +168,15 @@ export default class InstructionOrObjectSelector extends React.PureComponent<
|
||||
this.props.objectsContainer
|
||||
);
|
||||
|
||||
const allFolders = [
|
||||
...enumerateFoldersInContainer(this.props.globalObjectsContainer).map(
|
||||
folderWithPath => ({ ...folderWithPath, global: true })
|
||||
),
|
||||
...enumerateFoldersInContainer(this.props.objectsContainer).map(
|
||||
folderWithPath => ({ ...folderWithPath, global: false })
|
||||
),
|
||||
];
|
||||
|
||||
this.instructionSearchApi = new Fuse(
|
||||
deduplicateInstructionsList(this.allInstructionsInfo),
|
||||
{
|
||||
@@ -173,6 +197,11 @@ export default class InstructionOrObjectSelector extends React.PureComponent<
|
||||
getFn: (item, property) => item.group.getName(),
|
||||
keys: ['name'], // Not used as we only use the name of the group
|
||||
});
|
||||
this.folderSearchApi = new Fuse(allFolders, {
|
||||
...sharedFuseConfiguration,
|
||||
getFn: (item, property) => item.path,
|
||||
keys: ['name'], // Not used as we only use the path to the folder
|
||||
});
|
||||
}
|
||||
|
||||
_search = (searchText: string) => {
|
||||
@@ -194,6 +223,12 @@ export default class InstructionOrObjectSelector extends React.PureComponent<
|
||||
matches: tuneMatches(result, searchText),
|
||||
}))
|
||||
: [],
|
||||
folders: this.folderSearchApi
|
||||
? this.folderSearchApi.search(extendedSearchText).map(result => ({
|
||||
item: result.item,
|
||||
matches: tuneMatches(result, searchText),
|
||||
}))
|
||||
: [],
|
||||
instructions: this.instructionSearchApi
|
||||
? this.instructionSearchApi
|
||||
.search(
|
||||
@@ -242,11 +277,13 @@ export default class InstructionOrObjectSelector extends React.PureComponent<
|
||||
let filteredObjectsList = [];
|
||||
let displayedObjectGroupsList = [];
|
||||
let filteredInstructionsList = [];
|
||||
let filteredFoldersList = [];
|
||||
|
||||
if (isSearching) {
|
||||
filteredObjectsList = searchResults.objects;
|
||||
displayedObjectGroupsList = searchResults.groups;
|
||||
filteredInstructionsList = searchResults.instructions;
|
||||
filteredFoldersList = searchResults.folders;
|
||||
} else {
|
||||
filteredObjectsList = allObjectsList.map(object => ({
|
||||
item: object,
|
||||
@@ -273,7 +310,8 @@ export default class InstructionOrObjectSelector extends React.PureComponent<
|
||||
!isSearching ||
|
||||
!!filteredObjectsList.length ||
|
||||
!!displayedObjectGroupsList.length ||
|
||||
!!displayedInstructionsList.length;
|
||||
!!displayedInstructionsList.length ||
|
||||
!!filteredFoldersList;
|
||||
|
||||
const onSubmitSearch = () => {
|
||||
if (!isSearching) return;
|
||||
@@ -389,21 +427,138 @@ export default class InstructionOrObjectSelector extends React.PureComponent<
|
||||
</Subheader>
|
||||
)}
|
||||
{displayedObjectGroupsList.map(
|
||||
({ item: groupWithContext, matches }) =>
|
||||
renderGroupObjectsListItem({
|
||||
groupWithContext: groupWithContext,
|
||||
iconSize: iconSize,
|
||||
onClick: () =>
|
||||
onChooseObject(groupWithContext.group.getName()),
|
||||
matchesCoordinates: matches.length
|
||||
? matches[0].indices // Only field for groups is their name
|
||||
: [],
|
||||
selectedValue: chosenObjectName
|
||||
? getObjectOrObjectGroupListItemValue(
|
||||
chosenObjectName
|
||||
)
|
||||
: undefined,
|
||||
})
|
||||
({ item: groupWithContext, matches }) => {
|
||||
const results = [];
|
||||
|
||||
results.push(
|
||||
renderGroupObjectsListItem({
|
||||
groupWithContext,
|
||||
iconSize,
|
||||
onClick: () =>
|
||||
onChooseObject(
|
||||
groupWithContext.group.getName()
|
||||
),
|
||||
matchesCoordinates: matches.length
|
||||
? matches[0].indices // Only field for groups is their name
|
||||
: [],
|
||||
selectedValue: chosenObjectName
|
||||
? getObjectOrObjectGroupListItemValue(
|
||||
chosenObjectName
|
||||
)
|
||||
: undefined,
|
||||
})
|
||||
);
|
||||
if (isSearching) {
|
||||
const { group, global } = groupWithContext;
|
||||
const groupName = group.getName();
|
||||
const objectsInGroup = group
|
||||
.getAllObjectsNames()
|
||||
.toJSArray()
|
||||
.map(objectName => {
|
||||
// A global object group can contain scene objects so we cannot use
|
||||
// the group context to get directly get the object knowing the
|
||||
// appropriate container.
|
||||
const object = getObjectByName(
|
||||
globalObjectsContainer,
|
||||
objectsContainer,
|
||||
objectName
|
||||
);
|
||||
if (!object) return null;
|
||||
|
||||
return renderObjectListItem({
|
||||
project,
|
||||
objectWithContext: {
|
||||
object,
|
||||
global,
|
||||
},
|
||||
keyPrefix: `group-${groupName}`,
|
||||
withIndent: true,
|
||||
iconSize,
|
||||
onClick: () => onChooseObject(objectName),
|
||||
matchesCoordinates: [],
|
||||
selectedValue: chosenObjectName
|
||||
? getObjectOrObjectGroupListItemValue(
|
||||
chosenObjectName
|
||||
)
|
||||
: undefined,
|
||||
});
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (objectsInGroup.length === 0) {
|
||||
results.push(
|
||||
<ListItem
|
||||
key={`${group.getName()}-empty`}
|
||||
primaryText={
|
||||
<Text style={{ opacity: 0.6 }} noMargin>
|
||||
<Trans>No object in group</Trans>
|
||||
</Text>
|
||||
}
|
||||
style={{ paddingLeft: 45 }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
results.push(...objectsInGroup);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
)}
|
||||
{filteredFoldersList.length > 0 && (
|
||||
<Subheader>
|
||||
<Trans>Folders</Trans>
|
||||
</Subheader>
|
||||
)}
|
||||
{filteredFoldersList.map(
|
||||
({ item: folderWithPath, matches }) => {
|
||||
const results = [];
|
||||
|
||||
results.push(
|
||||
renderFolderListItem({
|
||||
folderWithPath,
|
||||
iconSize,
|
||||
matchesCoordinates: matches.length
|
||||
? matches[0].indices
|
||||
: [],
|
||||
})
|
||||
);
|
||||
const objectsInFolder = getObjectsInFolder(
|
||||
folderWithPath.folder
|
||||
);
|
||||
if (objectsInFolder.length === 0) {
|
||||
results.push(
|
||||
<ListItem
|
||||
key={`${folderWithPath.path}-empty`}
|
||||
primaryText={
|
||||
<Text style={{ opacity: 0.6 }} noMargin>
|
||||
<Trans>No object in folder</Trans>
|
||||
</Text>
|
||||
}
|
||||
style={{ paddingLeft: 45 }}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
results.push(
|
||||
...objectsInFolder.map(object =>
|
||||
renderObjectListItem({
|
||||
project,
|
||||
selectedValue: '',
|
||||
keyPrefix: `folder-${folderWithPath.path}`,
|
||||
iconSize,
|
||||
matchesCoordinates: [],
|
||||
objectWithContext: {
|
||||
object,
|
||||
global: folderWithPath.global,
|
||||
},
|
||||
withIndent: true,
|
||||
onClick: () =>
|
||||
onChooseObject(object.getName()),
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
@@ -0,0 +1,33 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { ListItem } from '../../../UI/List';
|
||||
import HighlightedText from '../../../UI/Search/HighlightedText';
|
||||
import Folder from '../../../UI/CustomSvgIcons/Folder';
|
||||
|
||||
type Props = {|
|
||||
folderWithPath: {| path: string, folder: gdObjectFolderOrObject, global: boolean |},
|
||||
iconSize: number,
|
||||
matchesCoordinates: number[][],
|
||||
|};
|
||||
|
||||
export const renderFolderListItem = ({
|
||||
folderWithPath,
|
||||
iconSize,
|
||||
matchesCoordinates,
|
||||
}: Props) => {
|
||||
const folderPath: string = folderWithPath.path;
|
||||
return (
|
||||
<ListItem
|
||||
key={folderPath}
|
||||
selected={false}
|
||||
primaryText={
|
||||
<HighlightedText
|
||||
text={folderPath}
|
||||
matchesCoordinates={matchesCoordinates}
|
||||
/>
|
||||
}
|
||||
leftIcon={<Folder width={iconSize} />}
|
||||
disableAutoTranslate
|
||||
/>
|
||||
);
|
||||
};
|
@@ -20,6 +20,8 @@ type Props = {|
|
||||
matchesCoordinates: number[][],
|
||||
id?: ?string,
|
||||
data?: HTMLDataset,
|
||||
withIndent?: boolean,
|
||||
keyPrefix?: string,
|
||||
|};
|
||||
|
||||
export const renderObjectListItem = ({
|
||||
@@ -31,16 +33,19 @@ export const renderObjectListItem = ({
|
||||
matchesCoordinates,
|
||||
id,
|
||||
data,
|
||||
withIndent,
|
||||
keyPrefix
|
||||
}: Props) => {
|
||||
const objectName: string = objectWithContext.object.getName();
|
||||
return (
|
||||
<ListItem
|
||||
id={id}
|
||||
data={data}
|
||||
key={getObjectListItemKey(objectWithContext)}
|
||||
key={(keyPrefix || '') + getObjectListItemKey(objectWithContext)}
|
||||
selected={
|
||||
selectedValue === getObjectOrObjectGroupListItemValue(objectName)
|
||||
}
|
||||
style={withIndent ? { paddingLeft: 45 } : undefined}
|
||||
primaryText={
|
||||
<HighlightedText
|
||||
text={objectName}
|
||||
|
@@ -23,7 +23,6 @@ export const enumerateObjectFolderOrObjects = (
|
||||
|} => {
|
||||
const projectRootFolder = project.getRootFolder();
|
||||
const containerRootFolder = objectsContainer.getRootFolder();
|
||||
|
||||
const containerObjectFolderOrObjectsList: ObjectFolderOrObjectWithContext[] = mapFor(
|
||||
0,
|
||||
containerRootFolder.getChildrenCount(),
|
||||
@@ -103,3 +102,16 @@ export const enumerateFoldersInContainer = (
|
||||
recursivelyEnumerateFoldersInFolder(rootFolder, '', result);
|
||||
return result;
|
||||
};
|
||||
|
||||
export const getObjectsInFolder = (
|
||||
objectFolderOrObject: gdObjectFolderOrObject
|
||||
): gdObject[] => {
|
||||
if (!objectFolderOrObject.isFolder()) return [];
|
||||
return mapFor(0, objectFolderOrObject.getChildrenCount(), i => {
|
||||
const child = objectFolderOrObject.getChildAt(i);
|
||||
if (child.isFolder()) {
|
||||
return null;
|
||||
}
|
||||
return child.getObject();
|
||||
}).filter(Boolean);
|
||||
};
|
||||
|
@@ -104,6 +104,7 @@ type ListItemProps = {|
|
||||
backgroundColor?: string,
|
||||
borderBottom?: string,
|
||||
opacity?: number,
|
||||
paddingLeft?: number,
|
||||
|},
|
||||
|
||||
leftIcon?: React.Node,
|
||||
@@ -115,7 +116,7 @@ type ListItemProps = {|
|
||||
data?: HTMLDataset,
|
||||
|};
|
||||
|
||||
export type ListItemRefType = any; // Should be a material-ui ListIten
|
||||
export type ListItemRefType = any; // Should be a material-ui ListItem
|
||||
|
||||
/**
|
||||
* A ListItem to be used in a List.
|
||||
|
Reference in New Issue
Block a user