Skip to content

Commit 19e8a71

Browse files
committed
feat: Searchables
refactor: More memo cleanup
1 parent e6303eb commit 19e8a71

File tree

19 files changed

+463
-117
lines changed

19 files changed

+463
-117
lines changed

extensions/core-nga/src/components.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NgCredits, NgFaves, NgRating, NgScore, NgViews, NgRatingGridIcon, NgRatingListIcon } from './components/components';
1+
import { NgCredits, NgFaves, NgRating, NgScore, NgViews, NgRatingGridIcon, NgRatingListIcon, NgRatingSearchableSelect } from './components/components';
22

33
const components: Record<string, React.ComponentType<any>> = {
44
'NgRating': NgRating,
@@ -8,9 +8,9 @@ const components: Record<string, React.ComponentType<any>> = {
88
'NgCredits': NgCredits,
99
'NgRatingGridIcon': NgRatingGridIcon,
1010
'NgRatingListIcon': NgRatingListIcon,
11+
'NgRatingSearchableSelect': NgRatingSearchableSelect,
1112
};
1213

13-
1414
(function() {
1515
// Update the display settings with our components
1616
const compDisplay = [
@@ -39,6 +39,8 @@ const components: Record<string, React.ComponentType<any>> = {
3939
// Add role icon
4040
window.displaySettings.gameGrid.upper.unshift('NgRatingGridIcon');
4141
window.displaySettings.gameList.icons.unshift('NgRatingListIcon');
42+
43+
window.displaySettings.searchComponents.push('NgRatingSearchableSelect');
4244
}());
4345

4446
export default components;

extensions/core-nga/src/components/components.tsx

Lines changed: 103 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DropdownItem, GameComponentProps, GameGridComponentProps, GameListComponentProps } from 'flashpoint-launcher-renderer';
1+
import { DropdownItem, GameComponentProps, GameGridComponentProps, GameListComponentProps, SearchableSelectItem, SearchComponentProps } from 'flashpoint-launcher-renderer';
22
import { IconType } from 'react-icons';
33
import { FaBug, FaChalkboardTeacher, FaCog, FaCrown, FaDesktop, FaDollarSign, FaFilm, FaMicrophone, FaMusic, FaPaintBrush, FaPenAlt, FaPencilAlt, FaSun, FaTruck, FaVolumeUp } from 'react-icons/fa';
44

@@ -50,17 +50,97 @@ type ExtData = {
5050

5151
const numFormat = new Intl.NumberFormat();
5252

53-
export function mapNgRatingString(rs: string) {
54-
switch (rs) {
55-
case '':
56-
return 'None';
53+
export function mapNgRatingString(s: string) {
54+
switch (s) {
5755
case 'e':
58-
return 'Standalone';
56+
return 'Everyone';
57+
case 't':
58+
return 'Teen';
59+
case 'm':
60+
return 'Mature';
61+
case 'a':
62+
return 'Adult';
5963
default:
60-
return 'Broken Value';
64+
return s;
6165
}
6266
}
6367

68+
const genSelectItem = (missing: string): SearchableSelectItem => {
69+
return {
70+
value: missing,
71+
orderVal: `zzzzzzz${missing}`,
72+
};
73+
};
74+
75+
export function NgRatingSearchableSelect(props: SearchComponentProps) {
76+
const { SearchableSelect } = window.ext.components;
77+
const { onBlacklistFactory, onWhitelistFactory, onClearFactory, onSetAndToggleFactory } = window.ext.utils.search;
78+
const { advancedFilter, setAdvancedFilter } = props;
79+
const extFilter = advancedFilter.ext.toggles['nga'];
80+
const extAndToggles = advancedFilter.andToggles.ext['nga'];
81+
82+
const labelRenderer = (item: SearchableSelectItem) => {
83+
const label = mapNgRatingString(item.value);
84+
return (
85+
<div className='platform-label-row'>
86+
<div
87+
className={`dropdown-icon dropdown-icon-image ng-image-rating_${item.value}`}>
88+
</div>
89+
<div className="searchable-select-dropdown-item-title">
90+
{label}
91+
</div>
92+
</div>
93+
);
94+
};
95+
96+
const ratingItems: SearchableSelectItem[] = [
97+
{
98+
value: 'e',
99+
orderVal: '1'
100+
}, {
101+
value: 't',
102+
orderVal: '2'
103+
}, {
104+
value: 'm',
105+
orderVal: '3'
106+
}, {
107+
value: 'a',
108+
orderVal: '4'
109+
}
110+
];
111+
112+
const onWhitelist = onWhitelistFactory('nga', 'rating', advancedFilter, setAdvancedFilter);
113+
const onBlacklist = onBlacklistFactory('nga', 'rating', advancedFilter, setAdvancedFilter);
114+
const onClear = onClearFactory('nga', 'rating', advancedFilter, setAdvancedFilter);
115+
const onSetAndToggle = onSetAndToggleFactory('nga', 'rating', advancedFilter, setAdvancedFilter);
116+
117+
const andToggle = !!(extAndToggles?.rating);
118+
const selected = extFilter?.rating || {};
119+
120+
return (
121+
<SearchableSelect
122+
title={'NG Rating'}
123+
items={ratingItems}
124+
andToggle={andToggle}
125+
selected={selected}
126+
labelRenderer={labelRenderer}
127+
generateItem={genSelectItem}
128+
onWhitelist={onWhitelist}
129+
onBlacklist={onBlacklist}
130+
onClear={onClear}
131+
onSetAndToggle={onSetAndToggle}
132+
mapName={(item) => {
133+
switch (item) {
134+
case 'e': return 'Everyone';
135+
case 't': return 'Teen';
136+
case 'm': return 'Mature';
137+
case 'a': return 'Adult';
138+
default: return '';
139+
}
140+
}}/>
141+
);
142+
}
143+
64144
export function NgRatingListIcon(props: GameListComponentProps) {
65145
if (props.game) {
66146
const extData: ExtData | undefined = props.game.extData?.nga;
@@ -174,9 +254,9 @@ export function NgScore(props: GameComponentProps) {
174254
const extData: ExtData | undefined = props.game.extData?.nga;
175255
const { GameComponentInputField } = window.ext.components;
176256
const score = Number(extData?.score) || 0;
177-
const { updateGameExtData } = props;
257+
const { updateGameExtData, editable } = props;
178258

179-
return (
259+
return editable ? (
180260
<GameComponentInputField
181261
header='NG Score'
182262
text={score.toString()}
@@ -190,6 +270,20 @@ export function NgScore(props: GameComponentProps) {
190270
}
191271
}}
192272
{...props} />
273+
) : (
274+
<div className='browse-right-sidebar__row browse-right-sidebar__row--one-line'>
275+
<p>NG Score: </p>
276+
{!score ? (
277+
<p>None</p>
278+
) : (
279+
<div className={'ng-score-sidebar'}>
280+
<div className='ng-score-value'>{score}</div>
281+
<div className='ng-score-stars'>
282+
<div className='ng-score-stars-filled' style={{ clipPath: `inset(0 ${(1 - (score / 5)) * 100}% 0 0)` }}></div>
283+
</div>
284+
</div>
285+
)}
286+
</div>
193287
);
194288
}
195289

1.3 KB
Binary file not shown.

extensions/core-nga/static/nga.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@
1818
background-image: url("assets/rating_adult.png");
1919
}
2020

21+
.ng-score-stars {
22+
background: url("assets/score.webp") left top repeat-x;
23+
background-size: 20%;
24+
width: calc(93px * 1.5);
25+
height: calc(17px * 1.5);
26+
}
27+
28+
.ng-score-stars-filled {
29+
background: url("assets/score.webp") left top repeat-x;
30+
background-position-y: 90%;
31+
background-size: 20%;
32+
width: 100%;
33+
height: 100%;
34+
}
35+
2136
.nga-credit {
2237
display: flex;
2338
flex-direction: row;

src/renderer/components/CurateBox.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ export function CurateBox(props: CurateBoxProps) {
5959
const dispatch = useAppDispatch();
6060
const folder = props.curation.folder;
6161

62-
const splitStatus = React.useMemo(() => props.curation.game.status ? props.curation.game.status.split(';').map(s => s.trim()).sort() : [], [props.curation.game.status]);
63-
const splitPlayMode = React.useMemo(() => props.curation.game.playMode ? props.curation.game.playMode.split(';').map(s => s.trim()).sort() : [], [props.curation.game.playMode]);
62+
const splitStatus = props.curation.game.status ? props.curation.game.status.split(';').map(s => s.trim()).sort() : [];
63+
const splitPlayMode = props.curation.game.playMode ? props.curation.game.playMode.split(';').map(s => s.trim()).sort() : [];
6464
const tags = props.curation.game.tags;
6565
let sortedTags: Tag[] = [];
6666
if (tags) {

src/renderer/components/GameListItem.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,11 @@ export function GameListItem(props: GameListItemProps) {
4141
// Get the platform icon path
4242
const platformIcon = getPlatformIconURL(platform, props.logoVersion);
4343
// Pick class names
44-
const className = React.useMemo(() => {
45-
let className = 'game-list-item';
46-
if (index % 2 === 0) { className += ' game-list-item--even'; }
47-
if (isSelected) { className += ' game-list-item--selected'; }
48-
if (isDragged) { className += ' game-list-item--dragged'; }
49-
return className;
50-
}, [index, isSelected, isDragged]);
51-
// Set element attributes
44+
let className = 'game-list-item';
45+
if (index % 2 === 0) { className += ' game-list-item--even'; }
46+
if (isSelected) { className += ' game-list-item--selected'; }
47+
if (isDragged) { className += ' game-list-item--dragged'; }
48+
// Set element attributes
5249
const attributes: any = {};
5350
attributes[GameListItem.idAttribute] = id;
5451
attributes[GameListItem.indexAttribute] = index;

src/renderer/components/ProgressComponents.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ export function StatusBar(props: ProgressComponentProps) {
4747
// Large top text `Percent% Complete`, medium progress bar, small underneath primary text.
4848
export function ProgressBar(props: ProgressComponentProps) {
4949
const strings = React.useContext(LangContext);
50-
const barCssProps: React.CSSProperties = React.useMemo(() => ({
50+
const barCssProps: React.CSSProperties = {
5151
width: `${props.progressData.percentDone}%`
52-
}), [props.progressData.percentDone]);
52+
};
5353

5454
return (
5555
<div className={'progress-component__wrapper' + (props.wrapperClass ? ' ' + props.wrapperClass : '')}>

src/renderer/components/RandomGames.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export function RandomGames(props: RandomGamesProps) {
5050

5151
const gameItems = props.games.slice(0, 6).map(game => (
5252
<GameGridItem
53+
game={game}
5354
key={game.id}
5455
id={game.id}
5556
title={game.title}

src/renderer/components/SearchBar.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { AutoSizer, List, ListRowProps } from 'react-virtualized';
1313
import { GameOrder } from './GameOrder';
1414
import { OpenIcon } from './OpenIcon';
1515
import { SimpleButton } from './SimpleButton';
16+
import { DynamicComponent } from './DynamicComponent';
17+
import { SearchComponentProps } from 'flashpoint-launcher-renderer';
1618

1719
function TestComponent2() {
1820
const onClearFactory = (key: string) => {
@@ -366,6 +368,16 @@ export function SearchBar() {
366368
}
367369
}, [view.id]);
368370

371+
const searchComponentProps: SearchComponentProps = {
372+
advancedFilter: view.advancedFilter,
373+
setAdvancedFilter: (advFilter) => {
374+
dispatch(setAdvancedFilter({
375+
view: view.id,
376+
filter: advFilter,
377+
}));
378+
}
379+
};
380+
369381
return (
370382
<div className='search-bar-wrapper search-bar-wrapper--expanded-simple'>
371383
<div className="search-bar">
@@ -517,6 +529,11 @@ export function SearchBar() {
517529
onBlacklist={onBlacklistTag}
518530
onClear={onClearTags}
519531
onSetAndToggle={onSetAndToggleTags} />
532+
{ window.displaySettings.searchComponents.map((name) => {
533+
return (
534+
<DynamicComponent name={name} props={searchComponentProps}/>
535+
);
536+
}) }
520537
</div>
521538
)}
522539
</div>
@@ -587,7 +604,7 @@ type TagSelectItem = {
587604
tag: Tag;
588605
} & SearchableSelectItem;
589606

590-
function SearchableSelect<T extends SearchableSelectItem>(props: SearchableSelectProps<T>) {
607+
export function SearchableSelect<T extends SearchableSelectItem>(props: SearchableSelectProps<T>) {
591608
const { title, items, selected, andToggle, onWhitelist, onBlacklist, onClear, onSetAndToggle, mapName, labelRenderer } = props;
592609
const [expanded, setExpanded] = React.useState(false);
593610
const dropdownRef = React.useRef<HTMLDivElement>(null);

src/renderer/components/ServiceBox.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ export function ServiceBox(props: ServiceBoxProps) {
2323
const strings = lang.developer;
2424
// Log
2525
const entries = window.Shared.log.entries;
26-
const logData = React.useMemo(() => {
27-
return stringifyServiceLogEntries(entries, service.name);
28-
}, [entries, service.name]);
26+
const logData = stringifyServiceLogEntries(entries, service.name);
2927
// Uptime
3028
const uptimeRef = React.useRef<HTMLDivElement>(null);
3129
useInterval(() => { // (Update the value of the timer at an interval)

0 commit comments

Comments
 (0)