Skip to content

Commit e6303eb

Browse files
committed
feat: Game List icons for extensions
refactor: Migrate Footer to functional component
1 parent ed367aa commit e6303eb

File tree

12 files changed

+229
-149
lines changed

12 files changed

+229
-149
lines changed

extensions/core-nga/src/components.ts

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

33
const components: Record<string, React.ComponentType<any>> = {
44
'NgRating': NgRating,
55
'NgScore': NgScore,
66
'NgViews': NgViews,
77
'NgFaves': NgFaves,
88
'NgCredits': NgCredits,
9+
'NgRatingGridIcon': NgRatingGridIcon,
10+
'NgRatingListIcon': NgRatingListIcon,
911
};
1012

1113

@@ -33,6 +35,10 @@ const components: Record<string, React.ComponentType<any>> = {
3335
// If platforms is not in the middle to put after, just add to end of middle section
3436
window.displaySettings.gameSidebar.middle = sidebarMiddle.concat(compDisplay);
3537
}
38+
39+
// Add role icon
40+
window.displaySettings.gameGrid.upper.unshift('NgRatingGridIcon');
41+
window.displaySettings.gameList.icons.unshift('NgRatingListIcon');
3642
}());
3743

3844
export default components;

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DropdownItem, GameComponentProps } from 'flashpoint-launcher-renderer';
1+
import { DropdownItem, GameComponentProps, GameGridComponentProps, GameListComponentProps } 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

@@ -61,6 +61,28 @@ export function mapNgRatingString(rs: string) {
6161
}
6262
}
6363

64+
export function NgRatingListIcon(props: GameListComponentProps) {
65+
if (props.game) {
66+
const extData: ExtData | undefined = props.game.extData?.nga;
67+
const rating = extData?.rating || '';
68+
return (
69+
<div className={`game-list-item__icon ng-rating-list-icon ng-image-rating_${rating.toLowerCase()}`}/>
70+
);
71+
}
72+
}
73+
74+
export function NgRatingGridIcon(props: GameGridComponentProps) {
75+
if (props.game) {
76+
const extData: ExtData | undefined = props.game.extData?.nga;
77+
const rating = extData?.rating || '';
78+
if (rating) {
79+
return (
80+
<div className={`game-grid-item__thumb__icons__icon ng-rating-grid-icon ng-image-rating_${rating.toLowerCase()}`}/>
81+
);
82+
}
83+
}
84+
}
85+
6486
export function NgCredits(props: GameComponentProps) {
6587
const extData: ExtData | undefined = props.game.extData?.nga;
6688
const credits = extData?.credits || [];

src/renderer/components/Footer.tsx

Lines changed: 129 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,155 @@
1-
import { WithMainStateProps } from '@renderer/containers/withMainState';
1+
import { useView } from '@renderer/hooks/search';
2+
import { useAppDispatch, useAppSelector } from '@renderer/hooks/useAppSelector';
3+
import { usePreferences } from '@renderer/hooks/usePreferences';
4+
import { setMainState } from '@renderer/store/main/slice';
5+
import { GENERAL_VIEW_ID } from '@renderer/store/search/slice';
26
import { BackIn, ComponentState } from '@shared/back/types';
37
import { parseBrowsePageLayout, stringifyBrowsePageLayout } from '@shared/BrowsePageLayout';
48
import { getLibraryItemTitle } from '@shared/library/util';
59
import { updatePreferencesData } from '@shared/preferences/util';
610
import { formatString } from '@shared/utils/StringFormatter';
711
import * as React from 'react';
8-
import { WithPreferencesProps } from '../containers/withPreferences';
12+
import { useContext, useRef } from 'react';
13+
import { useLocation } from 'react-router-dom';
914
import { gameScaleSpan, getViewName } from '../Util';
1015
import { LangContext } from '../util/lang';
11-
import { WithViewProps } from '@renderer/containers/withView';
12-
import { GENERAL_VIEW_ID } from '@renderer/store/search/slice';
13-
import { WithNavigationProps } from '@renderer/containers/withNavigation';
1416

15-
export type FooterProps = WithViewProps & WithPreferencesProps & WithMainStateProps & WithNavigationProps;
17+
const scaleSliderMax = 1000;
1618

17-
/** The footer that is always visible at the bottom of the main window. */
18-
export class Footer extends React.Component<FooterProps> {
19-
static contextType = LangContext;
20-
declare context: React.ContextType<typeof LangContext>;
19+
export function Footer() {
20+
const scaleSliderRef = useRef<HTMLInputElement | null>(null);
21+
const strings = useContext(LangContext);
22+
const dispatch = useAppDispatch();
23+
const main = useAppSelector(state => state.main);
24+
const location = useLocation();
25+
const libraryPath = getViewName(location.pathname);
26+
const { browsePageLayout, browsePageGameScale } = usePreferences();
27+
const view = useView();
2128

22-
static scaleSliderMax = 1000;
23-
/** Reference to the scale slider. */
24-
scaleSliderRef: React.RefObject<HTMLInputElement | null> = React.createRef();
29+
const currentLabel = libraryPath && getLibraryItemTitle(libraryPath, strings.libraries);
30+
const fpmAvailable = main.componentStatuses.length > 0;
31+
const updatesReady = main.componentStatuses.filter(c => c.state === ComponentState.NEEDS_UPDATE).length;
32+
const gamesTotal = (view && view.data.total != undefined) ? view.data.total : -1;
33+
const scale = Math.min(Math.max(0, browsePageGameScale), 1);
2534

26-
componentDidMount() {
27-
window.addEventListener('keydown', this.onGlobalKeydown);
28-
}
35+
const onScaleSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
36+
updatePreferencesData({ browsePageGameScale: +event.currentTarget.value / scaleSliderMax });
37+
};
2938

30-
componentWillUnmount() {
31-
window.removeEventListener('keydown', this.onGlobalKeydown);
32-
}
39+
// Allow ctrl + and ctrl - to change scale
40+
React.useEffect(() => {
41+
const onGlobalKeydown = (event: KeyboardEvent) => {
42+
const scaleDif = 0.1; // How much the scale should change per increase/decrease
43+
// Increase Game Scale (CTRL PLUS)
44+
if (event.ctrlKey && (event.keyCode === 187 || event.keyCode === 61 || event.keyCode === 171)) {
45+
const scale = browsePageGameScale;
46+
setScaleSliderValue(scale + scaleDif);
47+
event.preventDefault();
48+
}
49+
// Decrease Game Scale (CTRL MINUS)
50+
else if (event.ctrlKey && (event.keyCode === 189 || event.keyCode === 173)) {
51+
const scale = browsePageGameScale;
52+
setScaleSliderValue(scale - scaleDif);
53+
event.preventDefault();
54+
}
55+
};
3356

34-
render() {
35-
const strings = this.context.app;
36-
const scale = Math.min(Math.max(0, this.props.preferencesData.browsePageGameScale), 1);
37-
const libraryPath = getViewName(this.props.location.pathname);
38-
const currentLabel = libraryPath && getLibraryItemTitle(libraryPath, this.props.main.lang.libraries);
39-
const view = this.props.currentView;
40-
const gamesTotal = (view && view.data.total != undefined) ? view.data.total : -1;
41-
const fpmAvailable = this.props.main.componentStatuses.length > 0;
42-
const updatesReady = this.props.main.componentStatuses.filter(c => c.state === ComponentState.NEEDS_UPDATE).length;
57+
window.addEventListener('keydown', onGlobalKeydown);
4358

44-
return (
45-
<div className='footer'>
46-
{/* Left Side */}
47-
<div className='footer__wrap footer__left'>
48-
<div className='footer__left__inner'>
49-
{/* Update Panel */}
50-
{ fpmAvailable && (
51-
<div
52-
onClick={() => {
53-
this.props.setMainState({
54-
quitting: true
55-
});
56-
window.Shared.back.send(BackIn.OPEN_FLASHPOINT_MANAGER);
57-
}}
58-
className={`${updatesReady > 0 ? 'footer__update-panel-updates-ready' : 'footer__update-panel-up-to-date'} footer__update-panel footer__wrap`}>
59-
{updatesReady ? formatString(this.context.home.componentUpdatesReady, updatesReady.toString()) : strings.openFlashpointManager }
60-
</div>
61-
)}
62-
{/* Game Count */}
63-
<div className='footer__game-count'>
64-
<p>{`${strings.total}: ${this.props.main.gamesTotal}`}</p>
65-
{currentLabel && view.id !== GENERAL_VIEW_ID && strings.searchResults ? (
66-
<>
67-
<p>|</p>
68-
<p>{`${strings.searchResults}: ${gamesTotal > -1 ? gamesTotal : this.context.misc.searching}`}</p>
69-
</>
70-
) : undefined}
71-
</div>
72-
</div>
73-
</div>
74-
{/* Right Side */}
75-
<div className='footer__wrap footer__right'>
76-
<div className='footer__right__inner'>
77-
{/* Layout Selector */}
78-
<div className='footer__wrap'>
79-
<div className='footer__layout-title'>{strings.layout}</div>
80-
</div>
81-
<div className='footer__wrap'>
82-
<div>
83-
<select
84-
className='footer__layout-selector simple-selector'
85-
value={stringifyBrowsePageLayout(this.props.preferencesData.browsePageLayout)}
86-
onChange={this.onLayoutChange}>
87-
<option value='list'>{strings.list}</option>
88-
<option value='grid'>{strings.grid}</option>
89-
</select>
90-
</div>
91-
</div>
92-
{/* Scale Slider */}
93-
<div className='footer__wrap footer__scale-slider'>
94-
<div className='footer__scale-slider__inner'>
95-
<div className='footer__scale-slider__icon footer__scale-slider__icon--left simple-center'>
96-
<div>-</div>
97-
</div>
98-
<div className='footer__scale-slider__icon footer__scale-slider__icon--center simple-center' />
99-
<div className='footer__scale-slider__icon footer__scale-slider__icon--right simple-center'>
100-
<div>+</div>
101-
</div>
102-
<input
103-
type='range'
104-
className='footer__scale-slider__input hidden-slider'
105-
value={scale * Footer.scaleSliderMax}
106-
min={0}
107-
max={Footer.scaleSliderMax}
108-
ref={this.scaleSliderRef}
109-
onChange={this.onScaleSliderChange} />
110-
</div>
111-
</div>
112-
{/* Slider Percent */}
113-
<div className='footer__wrap footer__scale-percent'>
114-
<p>{Math.round(100 + (scale - 0.5) * 200 * gameScaleSpan)}%</p>
115-
</div>
116-
</div>
117-
</div>
118-
</div>
119-
);
120-
}
121-
122-
onScaleSliderChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
123-
updatePreferencesData({ browsePageGameScale: +event.currentTarget.value / Footer.scaleSliderMax });
124-
};
59+
return () => {
60+
window.removeEventListener('keydown', onGlobalKeydown);
61+
};
62+
});
12563

126-
onLayoutChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
64+
const onLayoutChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
12765
const value = parseBrowsePageLayout(event.target.value);
12866
if (value === undefined) { throw new Error(`Layout selector option has an invalid value (${event.target.value})`); }
12967
updatePreferencesData({ browsePageLayout: value });
13068
};
13169

132-
onGlobalKeydown = (event: KeyboardEvent): void => {
133-
const scaleDif = 0.1; // How much the scale should change per increase/decrease
134-
// Increase Game Scale (CTRL PLUS)
135-
if (event.ctrlKey && (event.keyCode === 187 || event.keyCode === 61 || event.keyCode === 171)) {
136-
const scale = this.props.preferencesData.browsePageGameScale;
137-
this.setScaleSliderValue(scale + scaleDif);
138-
event.preventDefault();
139-
}
140-
// Decrease Game Scale (CTRL MINUS)
141-
else if (event.ctrlKey && (event.keyCode === 189 || event.keyCode === 173)) {
142-
const scale = this.props.preferencesData.browsePageGameScale;
143-
this.setScaleSliderValue(scale - scaleDif);
144-
event.preventDefault();
145-
}
146-
};
147-
148-
/**
149-
* Set the value of the scale slider.
150-
*
151-
* @param scale Value (between 0 and 1).
152-
*/
153-
setScaleSliderValue(scale: number): void {
154-
if (this.scaleSliderRef.current) {
70+
const setScaleSliderValue = (scale: number) => {
71+
if (scaleSliderRef.current) {
15572
if (scale < 0) { scale = 0; }
15673
else if (scale > 1) { scale = 1; }
157-
this.scaleSliderRef.current.value = (Math.min(Math.max(0, scale), 1) * Footer.scaleSliderMax).toFixed(1).toString();
74+
scaleSliderRef.current.value = (Math.min(Math.max(0, scale), 1) * scaleSliderMax).toFixed(1).toString();
15875
updatePreferencesData({ browsePageGameScale: scale });
15976
}
160-
}
77+
};
78+
79+
return (
80+
<div className='footer'>
81+
{/* Left Side */}
82+
<div className='footer__wrap footer__left'>
83+
<div className='footer__left__inner'>
84+
{/* Update Panel */}
85+
{ fpmAvailable && (
86+
<div
87+
onClick={() => {
88+
dispatch(setMainState({
89+
quitting: true
90+
}));
91+
window.Shared.back.send(BackIn.OPEN_FLASHPOINT_MANAGER);
92+
}}
93+
className={`${updatesReady > 0 ? 'footer__update-panel-updates-ready' : 'footer__update-panel-up-to-date'} footer__update-panel footer__wrap`}>
94+
{updatesReady ? formatString(strings.home.componentUpdatesReady, updatesReady.toString()) : strings.app.openFlashpointManager }
95+
</div>
96+
)}
97+
{/* Game Count */}
98+
<div className='footer__game-count'>
99+
<p>{`${strings.app.total}: ${main.gamesTotal}`}</p>
100+
{currentLabel && view.id !== GENERAL_VIEW_ID && strings.app.searchResults ? (
101+
<>
102+
<p>|</p>
103+
<p>{`${strings.app.searchResults}: ${gamesTotal > -1 ? gamesTotal : strings.misc.searching}`}</p>
104+
</>
105+
) : undefined}
106+
</div>
107+
</div>
108+
</div>
109+
{/* Right Side */}
110+
<div className='footer__wrap footer__right'>
111+
<div className='footer__right__inner'>
112+
{/* Layout Selector */}
113+
<div className='footer__wrap'>
114+
<div className='footer__layout-title'>{strings.app.layout}</div>
115+
</div>
116+
<div className='footer__wrap'>
117+
<div>
118+
<select
119+
className='footer__layout-selector simple-selector'
120+
value={stringifyBrowsePageLayout(browsePageLayout)}
121+
onChange={onLayoutChange}>
122+
<option value='list'>{strings.app.list}</option>
123+
<option value='grid'>{strings.app.grid}</option>
124+
</select>
125+
</div>
126+
</div>
127+
{/* Scale Slider */}
128+
<div className='footer__wrap footer__scale-slider'>
129+
<div className='footer__scale-slider__inner'>
130+
<div className='footer__scale-slider__icon footer__scale-slider__icon--left simple-center'>
131+
<div>-</div>
132+
</div>
133+
<div className='footer__scale-slider__icon footer__scale-slider__icon--center simple-center' />
134+
<div className='footer__scale-slider__icon footer__scale-slider__icon--right simple-center'>
135+
<div>+</div>
136+
</div>
137+
<input
138+
type='range'
139+
className='footer__scale-slider__input hidden-slider'
140+
value={scale * scaleSliderMax}
141+
min={0}
142+
max={scaleSliderMax}
143+
ref={scaleSliderRef}
144+
onChange={onScaleSliderChange} />
145+
</div>
146+
</div>
147+
{/* Slider Percent */}
148+
<div className='footer__wrap footer__scale-percent'>
149+
<p>{Math.round(100 + (scale - 0.5) * 200 * gameScaleSpan)}%</p>
150+
</div>
151+
</div>
152+
</div>
153+
</div>
154+
);
161155
}

src/renderer/components/GameGrid.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ export class GameGrid extends React.Component<GameGridProps, GameGridState> {
253253
<GameGridItem
254254
{ ...props }
255255
key={props.key}
256+
game={game}
256257
id={game ? game.id : ''}
257258
title={game ? game.title : ''}
258259
platforms={game ? [game.primaryPlatform] : []}

src/renderer/components/GameGridComponents.tsx

Whitespace-only changes.

0 commit comments

Comments
 (0)