Compare commits

...

4 Commits

Author SHA1 Message Date
AlexandreSi
ea0726ed17 Try at making Add new event button glow 2022-02-01 16:36:32 +01:00
AlexandreSi
e32eea7d17 Prettier 2022-02-01 16:01:26 +01:00
AlexandreSi
63300e0ff3 Increase contrast 2022-02-01 15:56:47 +01:00
AlexandreSi
9f22721ee1 Fix search highlights that made disappear non exact matches 2022-02-01 13:04:39 +01:00
11 changed files with 167 additions and 98 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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++) {

View File

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