mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
4 Commits
v5.5.239
...
highlight-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ea0726ed17 | ||
![]() |
e32eea7d17 | ||
![]() |
63300e0ff3 | ||
![]() |
9f22721ee1 |
@@ -20,7 +20,7 @@ import { openExampleInWebApp } from './ExampleDialog';
|
||||
import { UserPublicProfileChip } from '../../UI/User/UserPublicProfileChip';
|
||||
import HighlightedText from '../../UI/Search/HighlightedText';
|
||||
import GDevelopThemeContext from '../../UI/Theme/ThemeContext';
|
||||
import { type SearchMatches } from '../../UI/Search/UseSearchStructuredItem';
|
||||
import { type SearchMatch } from '../../UI/Search/UseSearchStructuredItem';
|
||||
|
||||
const electron = optionalRequire('electron');
|
||||
|
||||
@@ -38,7 +38,7 @@ const styles = {
|
||||
|
||||
type Props = {|
|
||||
exampleShortHeader: ExampleShortHeader,
|
||||
matches: ?SearchMatches,
|
||||
matches: ?Array<SearchMatch>,
|
||||
isOpening: boolean,
|
||||
onChoose: () => void,
|
||||
onOpen: () => void,
|
||||
|
@@ -9,14 +9,14 @@ import {
|
||||
import { type Filters } from '../../Utils/GDevelopServices/Filters';
|
||||
import {
|
||||
useSearchItem,
|
||||
type SearchMatches,
|
||||
type SearchMatch,
|
||||
} from '../../UI/Search/UseSearchStructuredItem';
|
||||
|
||||
const defaultSearchText = '';
|
||||
|
||||
type ExampleStoreState = {|
|
||||
filters: ?Filters,
|
||||
searchResults: ?Array<{| item: ExampleShortHeader, matches: SearchMatches |}>,
|
||||
searchResults: ?Array<{| item: ExampleShortHeader, matches: SearchMatch[] |}>,
|
||||
fetchExamplesAndFilters: () => void,
|
||||
allExamples: ?Array<ExampleShortHeader>,
|
||||
error: ?Error,
|
||||
@@ -126,7 +126,7 @@ export const ExampleStoreStateProvider = ({
|
||||
const { chosenCategory, chosenFilters } = filtersState;
|
||||
const searchResults: ?Array<{|
|
||||
item: ExampleShortHeader,
|
||||
matches: SearchMatches,
|
||||
matches: SearchMatch[],
|
||||
|}> = useSearchItem(
|
||||
exampleShortHeadersById,
|
||||
searchText,
|
||||
|
@@ -11,7 +11,7 @@ import { ListSearchResults } from '../../UI/Search/ListSearchResults';
|
||||
import { ExampleListItem } from './ExampleListItem';
|
||||
import { ResponsiveWindowMeasurer } from '../../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
import { ExampleDialog } from './ExampleDialog';
|
||||
import { type SearchMatches } from '../../UI/Search/UseSearchStructuredItem';
|
||||
import { type SearchMatch } from '../../UI/Search/UseSearchStructuredItem';
|
||||
|
||||
const styles = {
|
||||
searchBar: {
|
||||
@@ -73,7 +73,7 @@ export const ExampleStore = ({ isOpening, onOpen, focusOnMount }: Props) => {
|
||||
|
||||
const getExampleMatches = (
|
||||
exampleShortHeader: ExampleShortHeader
|
||||
): SearchMatches => {
|
||||
): SearchMatch[] => {
|
||||
if (!searchResults) return [];
|
||||
const exampleMatches = searchResults.find(
|
||||
result => result.item.id === exampleShortHeader.id
|
||||
|
@@ -9,7 +9,7 @@ import { IconContainer } from '../../UI/IconContainer';
|
||||
import { UserPublicProfileChip } from '../../UI/User/UserPublicProfileChip';
|
||||
import HighlightedText from '../../UI/Search/HighlightedText';
|
||||
import GDevelopThemeContext from '../../UI/Theme/ThemeContext';
|
||||
import { type SearchMatches } from '../../UI/Search/UseSearchStructuredItem';
|
||||
import { type SearchMatch } from '../../UI/Search/UseSearchStructuredItem';
|
||||
|
||||
const styles = {
|
||||
button: { width: '100%' },
|
||||
@@ -25,7 +25,7 @@ const styles = {
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
extensionShortHeader: ExtensionShortHeader,
|
||||
matches: ?SearchMatches,
|
||||
matches: ?Array<SearchMatch>,
|
||||
onChoose: () => void,
|
||||
onHeightComputed: number => void,
|
||||
|};
|
||||
|
@@ -9,7 +9,7 @@ import {
|
||||
import { type Filters } from '../../Utils/GDevelopServices/Filters';
|
||||
import {
|
||||
useSearchItem,
|
||||
type SearchMatches,
|
||||
type SearchMatch,
|
||||
} from '../../UI/Search/UseSearchStructuredItem';
|
||||
|
||||
const defaultSearchText = '';
|
||||
@@ -18,7 +18,7 @@ type ExtensionStoreState = {|
|
||||
filters: ?Filters,
|
||||
searchResults: ?Array<{|
|
||||
item: ExtensionShortHeader,
|
||||
matches: SearchMatches,
|
||||
matches: SearchMatch[],
|
||||
|}>,
|
||||
fetchExtensionsAndFilters: () => void,
|
||||
error: ?Error,
|
||||
@@ -135,7 +135,7 @@ export const ExtensionStoreStateProvider = ({
|
||||
const { chosenCategory, chosenFilters } = filtersState;
|
||||
const searchResults: ?Array<{|
|
||||
item: ExtensionShortHeader,
|
||||
matches: SearchMatches,
|
||||
matches: SearchMatch[],
|
||||
|}> = useSearchItem(
|
||||
extensionShortHeadersByName,
|
||||
searchText,
|
||||
|
@@ -8,7 +8,7 @@ import { ListSearchResults } from '../../UI/Search/ListSearchResults';
|
||||
import { ExtensionListItem } from './ExtensionListItem';
|
||||
import { ResponsiveWindowMeasurer } from '../../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
import ExtensionInstallDialog from './ExtensionInstallDialog';
|
||||
import { type SearchMatches } from '../../UI/Search/UseSearchStructuredItem';
|
||||
import { type SearchMatch } from '../../UI/Search/UseSearchStructuredItem';
|
||||
|
||||
const styles = {
|
||||
searchBar: {
|
||||
@@ -73,7 +73,7 @@ export const ExtensionStore = ({
|
||||
|
||||
const getExtensionsMatches = (
|
||||
extensionShortHeader: ExtensionShortHeader
|
||||
): SearchMatches => {
|
||||
): SearchMatch[] => {
|
||||
if (!searchResults) return [];
|
||||
const extensionMatches = searchResults.find(
|
||||
result => result.item.name === extensionShortHeader.name
|
||||
|
@@ -13,6 +13,7 @@ const styles = {
|
||||
|
||||
type Props = {|
|
||||
onAddEvent: (eventType: string) => void,
|
||||
shouldGlow: boolean,
|
||||
|};
|
||||
|
||||
const makeMenuTemplateBuilderForEvents = (
|
||||
@@ -25,7 +26,7 @@ const makeMenuTemplateBuilderForEvents = (
|
||||
};
|
||||
});
|
||||
|
||||
export default function BottomButtons({ onAddEvent }: Props) {
|
||||
export default function BottomButtons({ onAddEvent, shouldGlow }: Props) {
|
||||
return (
|
||||
<Column>
|
||||
<Line justifyContent="space-between">
|
||||
@@ -34,7 +35,7 @@ export default function BottomButtons({ onAddEvent }: Props) {
|
||||
element={
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
className={`add-link ${shouldGlow ? 'glow' : ''}`}
|
||||
onClick={() => onAddEvent('BuiltinCommonInstructions::Standard')}
|
||||
>
|
||||
<Trans>Add a new event</Trans>
|
||||
|
@@ -371,6 +371,7 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
onAddEvent={(eventType: string) =>
|
||||
this.props.onAddNewEvent(eventType, this.props.events)
|
||||
}
|
||||
shouldGlow={eventsList.getEventsCount() === 0}
|
||||
/>
|
||||
),
|
||||
event: null,
|
||||
|
@@ -4,169 +4,205 @@
|
||||
* Remove the outline visible on the events sheet (contrary to most
|
||||
* controls on screen, we don't want a visible focus there).
|
||||
*/
|
||||
.gd-events-sheet:focus { outline: none; }
|
||||
.gd-events-sheet:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draggable handle on the left of an event
|
||||
*/
|
||||
.gd-events-sheet .rst__moveHandle {
|
||||
width: 10px;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
width: 10px;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 750px) {
|
||||
/**
|
||||
/**
|
||||
* Slightly reduce the size of the handle on small screens
|
||||
* and phones (where drag'n'drop is not supported anyway).
|
||||
*/
|
||||
.gd-events-sheet .rst__moveHandle {
|
||||
width: 4px;
|
||||
}
|
||||
.gd-events-sheet .rst__moveHandle {
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.gd-events-sheet .events-tree {
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Container of an event line (including the scaffolding lines and the event).
|
||||
*/
|
||||
.gd-events-sheet .rst__node {
|
||||
display: flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.gd-events-sheet .rst__nodeContent {
|
||||
/* Ensure the event take all the space after the scaffolding lines */
|
||||
flex: 1;
|
||||
right: 0;
|
||||
/* Ensure the event take all the space after the scaffolding lines */
|
||||
flex: 1;
|
||||
right: 0;
|
||||
|
||||
min-width: 0; /* But don't shrink scaffolding lines */
|
||||
margin-bottom: 2px; /* Margin after an event */
|
||||
min-width: 0; /* But don't shrink scaffolding lines */
|
||||
margin-bottom: 2px; /* Margin after an event */
|
||||
}
|
||||
|
||||
.gd-events-sheet .rst__row {
|
||||
/* The "landing pad" highlight box is done with position: absolute.
|
||||
/* The "landing pad" highlight box is done with position: absolute.
|
||||
* Ensure it will cover all the event row but not the scaffolding lines.
|
||||
*/
|
||||
position: relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Container of the content of an event
|
||||
*/
|
||||
.gd-events-sheet .rst__rowContents {
|
||||
display: flex;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.gd-events-sheet .rst__rowWrapper {
|
||||
padding: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.gd-events-sheet .rst__rowLabel {
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gd-events-sheet .rst__rowTitle {
|
||||
font-weight: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand/collapse buttons
|
||||
*/
|
||||
.gd-events-sheet .rst__collapseButton, .rst__expandButton {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background-repeat: no-repeat;
|
||||
background-color: transparent;
|
||||
height: 12px;
|
||||
width: 22px;
|
||||
.gd-events-sheet .rst__collapseButton,
|
||||
.rst__expandButton {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background-repeat: no-repeat;
|
||||
background-color: transparent;
|
||||
height: 12px;
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
.gd-events-sheet .rst__collapseButton:hover:not(:active), .rst__expandButton:hover:not(:active) {
|
||||
background-size: initial !important; /*Avoid buttons size to change*/
|
||||
height: 12px !important; /*Avoid buttons size to change*/
|
||||
width: 22px !important; /*Avoid buttons size to change*/
|
||||
.gd-events-sheet .rst__collapseButton:hover:not(:active),
|
||||
.rst__expandButton:hover:not(:active) {
|
||||
background-size: initial !important; /*Avoid buttons size to change*/
|
||||
height: 12px !important; /*Avoid buttons size to change*/
|
||||
width: 22px !important; /*Avoid buttons size to change*/
|
||||
}
|
||||
|
||||
.gd-events-sheet .rst__expandButton {
|
||||
background-image: url('./unfold.png');
|
||||
background-position: 11px 3px !important;
|
||||
.gd-events-sheet .rst__expandButton {
|
||||
background-image: url('./unfold.png');
|
||||
background-position: 11px 3px !important;
|
||||
}
|
||||
|
||||
.gd-events-sheet .rst__collapseButton {
|
||||
background-image: url('./fold.png');
|
||||
background-position: center 7px !important;
|
||||
.gd-events-sheet .rst__collapseButton {
|
||||
background-image: url('./fold.png');
|
||||
background-position: center 7px !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lines between events
|
||||
*/
|
||||
.gd-events-sheet .rst__lineHalfHorizontalRight::before, .rst__lineFullVertical::after, .rst__lineHalfVerticalTop::after, .rst__lineHalfVerticalBottom::after {
|
||||
.gd-events-sheet .rst__lineHalfHorizontalRight::before,
|
||||
.rst__lineFullVertical::after,
|
||||
.rst__lineHalfVerticalTop::after,
|
||||
.rst__lineHalfVerticalBottom::after {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links to add a condition or an action
|
||||
*/
|
||||
@keyframes glow {
|
||||
0% {
|
||||
text-shadow: 0 0 10px violet, 0 0 10px violet;
|
||||
}
|
||||
15% {
|
||||
text-shadow: 0 0 15px indigo, 0 0 15px indigo;
|
||||
}
|
||||
30% {
|
||||
text-shadow: 0 0 10px blue, 0 0 10px blue;
|
||||
}
|
||||
45% {
|
||||
text-shadow: 0 0 8px green, 0 0 8px green;
|
||||
}
|
||||
60% {
|
||||
text-shadow: 0 0 10px yellow, 0 0 10px yellow;
|
||||
}
|
||||
75% {
|
||||
text-shadow: 0 0 15px orange, 0 0 15px orange;
|
||||
}
|
||||
90% {
|
||||
text-shadow: 0 0 12px red, 0 0 12px red;
|
||||
}
|
||||
100% {
|
||||
text-shadow: 0 0 10px violet, 0 0 10px violet;
|
||||
}
|
||||
}
|
||||
.gd-events-sheet .add-link {
|
||||
background:none!important;
|
||||
color:inherit;
|
||||
border:none;
|
||||
padding:0!important;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
opacity: 0.3;
|
||||
background: none !important;
|
||||
color: inherit;
|
||||
border: none;
|
||||
padding: 0 !important;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.gd-events-sheet .add-link.glow {
|
||||
animation: glow 5s ease-in-out infinite;
|
||||
}
|
||||
.gd-events-sheet .add-link:hover {
|
||||
opacity: 0.8;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selectable area (instructions)
|
||||
*/
|
||||
.gd-events-sheet .selectable {
|
||||
box-sizing: border-box;
|
||||
border: 1px transparent solid;
|
||||
box-sizing: border-box;
|
||||
border: 1px transparent solid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Large selectable area (events)
|
||||
*/
|
||||
.gd-events-sheet .large-selectable {
|
||||
box-sizing: border-box;
|
||||
border: 1px transparent solid;
|
||||
box-sizing: border-box;
|
||||
border: 1px transparent solid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabled text
|
||||
*/
|
||||
.gd-events-sheet .disabled-text {
|
||||
text-decoration: line-through;
|
||||
opacity: 0.6;
|
||||
text-decoration: line-through;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon (variable, object icon...)
|
||||
*/
|
||||
.gd-events-sheet .icon {
|
||||
vertical-align: sub;
|
||||
object-fit: contain;
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
vertical-align: sub;
|
||||
object-fit: contain;
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
}
|
||||
|
||||
.gd-events-sheet .rst__nodeContent .MuiIconButton-root {
|
||||
font-size: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -39,6 +39,7 @@ const HighlightedText = ({
|
||||
matchesCoordinates: number[][],
|
||||
styleToApply: { backgroundColor?: string, color?: string },
|
||||
|}): React.Node[] => {
|
||||
if (matchesCoordinates.length === 0) return [text];
|
||||
const returnText = [];
|
||||
|
||||
for (let i = 0; i < matchesCoordinates.length; i++) {
|
||||
|
@@ -4,17 +4,56 @@ import { type ChosenCategory } from './FiltersChooser';
|
||||
import shuffle from 'lodash/shuffle';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
export type SearchMatches = Array<{| key: string, indices: number[][] |}>;
|
||||
export type SearchMatch = {| key: string, indices: number[][], value: string |};
|
||||
|
||||
const tuneMatchIndices = (match: SearchMatch, searchText: string) => {
|
||||
const lowerCaseSearchText = searchText.toLowerCase();
|
||||
return match.indices
|
||||
.map(index => {
|
||||
const lowerCaseMatchedText = match.value
|
||||
.slice(index[0], index[1] + 1)
|
||||
.toLowerCase();
|
||||
// if exact match, return indices untouched
|
||||
if (lowerCaseMatchedText === lowerCaseSearchText) return index;
|
||||
const indexOfSearchTextInMatchedText = lowerCaseMatchedText.indexOf(
|
||||
lowerCaseSearchText
|
||||
);
|
||||
|
||||
// if searched text is not in match returned by the fuzzy search
|
||||
// ("too" could be matched when searching for "ot"),
|
||||
// return nothing
|
||||
if (indexOfSearchTextInMatchedText === -1) return undefined;
|
||||
|
||||
// when searched text is included in matched text, changes indices
|
||||
// to highlight only the part that matches exactly
|
||||
return [
|
||||
index[0] + indexOfSearchTextInMatchedText,
|
||||
index[0] + indexOfSearchTextInMatchedText + searchText.length - 1,
|
||||
];
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const tuneMatches = (
|
||||
result: {| item: any, matches: SearchMatch[] |},
|
||||
searchText: string
|
||||
) => {
|
||||
return result.matches.map(match => ({
|
||||
key: match.key,
|
||||
value: match.value,
|
||||
indices: tuneMatchIndices(match, searchText),
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter a list of items according to the chosen category
|
||||
* and the chosen filters.
|
||||
*/
|
||||
export const filterSearchResults = <SearchItem: { tags: Array<string> }>(
|
||||
searchResults: ?Array<{| item: SearchItem, matches: SearchMatches |}>,
|
||||
searchResults: ?Array<{| item: SearchItem, matches: SearchMatch[] |}>,
|
||||
chosenCategory: ?ChosenCategory,
|
||||
chosenFilters: Set<string>
|
||||
): ?Array<{| item: SearchItem, matches: SearchMatches |}> => {
|
||||
): ?Array<{| item: SearchItem, matches: SearchMatch[] |}> => {
|
||||
if (!searchResults) return null;
|
||||
|
||||
const startTime = performance.now();
|
||||
@@ -66,11 +105,11 @@ export const useSearchItem = <SearchItem: { tags: Array<string> }>(
|
||||
searchText: string,
|
||||
chosenCategory: ?ChosenCategory,
|
||||
chosenFilters: Set<string>
|
||||
): ?Array<{| item: SearchItem, matches: SearchMatches |}> => {
|
||||
): ?Array<{| item: SearchItem, matches: SearchMatch[] |}> => {
|
||||
const searchApiRef = React.useRef<?any>(null);
|
||||
const [searchResults, setSearchResults] = React.useState<?Array<{|
|
||||
item: SearchItem,
|
||||
matches: SearchMatches,
|
||||
matches: SearchMatch[],
|
||||
|}>>(null);
|
||||
|
||||
// Keep in memory a list of all the items, shuffled for
|
||||
@@ -168,16 +207,7 @@ export const useSearchItem = <SearchItem: { tags: Array<string> }>(
|
||||
filterSearchResults(
|
||||
results.map(result => ({
|
||||
item: result.item,
|
||||
matches: result.matches.map(match => ({
|
||||
key: match.key,
|
||||
value: match.value,
|
||||
// We only keep the exact matches indices for highlighting.
|
||||
indices: match.indices.filter(
|
||||
index =>
|
||||
match.value.slice(index[0], index[1] + 1).toLowerCase() ===
|
||||
searchText.toLowerCase()
|
||||
),
|
||||
})),
|
||||
matches: tuneMatches(result, searchText),
|
||||
})),
|
||||
chosenCategory,
|
||||
chosenFilters
|
||||
|
Reference in New Issue
Block a user