Display folder and groups content in InstructionOrObjectSelector

This commit is contained in:
AlexandreSi
2023-09-29 14:37:48 +02:00
parent 058dac9faa
commit f0ff63fd07
5 changed files with 226 additions and 20 deletions

View File

@@ -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>
)}

View File

@@ -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
/>
);
};

View File

@@ -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}

View File

@@ -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);
};

View File

@@ -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.