Skip to content

Commit 3d81a9f

Browse files
committed
refactor: Don't virtualize extension manager list since it breaks dropdowns being in a stacking context
feat: Expose DropdownCheckboxRow (like logs page) and DropdownStringRow components to ext feat: Add closeDropdown() to Dropdown row props fix: Properly center the arrow in Dropdown buttons
1 parent 3a69ab0 commit 3d81a9f

7 files changed

Lines changed: 111 additions & 54 deletions

File tree

extensions/core-manager/src/components/ExtensionSubsection.tsx

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import { CheckBox, SimpleButton } from 'flashpoint-launcher-renderer-ext/components';
1+
import { DropdownStringRowProps } from 'flashpoint-launcher-renderer';
2+
import { CheckBox, Dropdown, DropdownStringRow, SimpleButton } from 'flashpoint-launcher-renderer-ext/components';
23
import { useAppSelector } from 'flashpoint-launcher-renderer-ext/hooks';
34
import { getExtensionFileURL, runCommand, setExtensionEnabled } from 'flashpoint-launcher-renderer-ext/utils';
45
import { useEffect, useState } from 'react';
5-
import { List, RowComponentProps, useDynamicRowHeight } from 'react-window';
66
import { DownloadExtCommand, UninstallExtCommand } from '../commands';
77
import { loadExtIndexUrl, ManagerExtensionInfo } from '../extensionLoader';
88

99
export type ExtensionRowProps = {
10-
items: ManagerExtensionInfo[];
11-
disabledExtensions: string[];
10+
item: ManagerExtensionInfo;
11+
index: number;
12+
disabled: boolean;
1213
}
1314

1415
export function ExtensionSubsection() {
@@ -32,49 +33,59 @@ export function ExtensionSubsection() {
3233
description: ext.description || 'No Description',
3334
newestVersion: ext.version,
3435
iconUrl: ext.icon ? getExtensionFileURL(ext.id, ext.icon) : undefined,
35-
installed: true
36+
installed: true,
37+
availableVersions: [],
3638
} satisfies ManagerExtensionInfo;
3739
});
40+
3841
for (const ext of availableExtensions) {
3942
const existingIdx = installedExtensions.findIndex(e => e.id === ext.id);
4043
if (existingIdx === -1) {
4144
extensionList.push(ext);
45+
} else {
46+
extensionList[existingIdx].availableVersions = ext.availableVersions;
4247
}
4348
}
4449

4550
extensionList.sort((a, b) => a.title.localeCompare(b.title));
4651

47-
const rowHeight = useDynamicRowHeight({
48-
defaultRowHeight: 24
49-
});
50-
5152
return <div className='manager-page-subsection'>
5253
<div className='manager-page-subsection-header'>Extensions</div>
53-
{ extensionList !== undefined ? (
54-
<List<ExtensionRowProps>
55-
className='simple-scroll'
56-
rowComponent={ExtensionRow}
57-
rowCount={extensionList.length}
58-
rowProps={{
59-
items: extensionList,
60-
disabledExtensions,
61-
}}
62-
rowHeight={rowHeight}/>
63-
) : (
64-
<div>Loading...</div>
65-
)}
54+
<div className='manager-page-subsection-list simple-scroll'>
55+
{ extensionList.length > 0 ? extensionList.map((ext, index) => {
56+
return (
57+
<ExtensionRow
58+
item={ext}
59+
index={index}
60+
disabled={!disabledExtensions.includes(ext.id)}/>
61+
);
62+
}) : <div>Loading...</div>}
63+
</div>
6664
</div>;
6765
}
6866

69-
export function ExtensionRow({ items, disabledExtensions, index, style }: RowComponentProps<ExtensionRowProps>) {
70-
const { id, title, description, installed, newestVersion, iconUrl, getDownloadUrl } = items[index];
67+
export function ExtensionRow({ item, disabled, index }: ExtensionRowProps) {
68+
const { id, title, description, installed, newestVersion, availableVersions, iconUrl, getDownloadUrl } = item;
69+
const [selectedVersion, setSelectedVersion] = useState(newestVersion);
7170
const [busy, setBusy] = useState(false);
7271
const canInstall = getDownloadUrl !== undefined;
73-
const enabled = !disabledExtensions.includes(id);
72+
const enabled = !disabled;
7473
let rowClassName = 'manager-extension-row';
7574
if (index % 2 === 0) { rowClassName += ' manager-extension-row--even'; }
7675

77-
return <div className={rowClassName} style={style}>
76+
const versionSelector = (
77+
<Dropdown<DropdownStringRowProps>
78+
text={`Ver: ${selectedVersion}`}
79+
rowCount={availableVersions.length}
80+
rowProps={{
81+
items: availableVersions,
82+
onSelect: (index) => setSelectedVersion(availableVersions[index])
83+
}}
84+
rowRenderer={DropdownStringRow}
85+
/>
86+
);
87+
88+
return <div className={rowClassName}>
7889
<div className='manager-extension-row-icon'>
7990
{ iconUrl && (
8091
<img src={iconUrl}/>
@@ -110,16 +121,19 @@ export function ExtensionRow({ items, disabledExtensions, index, style }: RowCom
110121
}}/>
111122
)}
112123
{ canInstall && (
113-
<SimpleButton value={'Install'} onClick={() => {
114-
setBusy(true);
115-
runCommand(DownloadExtCommand, getDownloadUrl(newestVersion))
116-
.catch((error) => {
117-
const errorString = `Failed to download and install extension: ${error}`;
118-
alert(errorString);
119-
log.error('Manager', errorString);
120-
})
121-
.finally(() => setBusy(false));
122-
}}/>
124+
<>
125+
<SimpleButton value={'Install'} onClick={() => {
126+
setBusy(true);
127+
runCommand(DownloadExtCommand, getDownloadUrl(newestVersion))
128+
.catch((error) => {
129+
const errorString = `Failed to download and install extension: ${error}`;
130+
alert(errorString);
131+
log.error('Manager', errorString);
132+
})
133+
.finally(() => setBusy(false));
134+
}}/>
135+
{versionSelector}
136+
</>
123137
)}
124138
</>
125139
) : <div>Busy...</div> }

extensions/core-manager/src/extensionLoader.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type ManagerExtensionInfo = {
66
description: string;
77
iconUrl?: string;
88
newestVersion: string;
9+
availableVersions: string[];
910
getDownloadUrl?: (version: string) => string,
1011
installed: boolean;
1112
}
@@ -25,6 +26,7 @@ export async function loadExtIndexUrl(url: string): Promise<ManagerExtensionInfo
2526
getDownloadUrl: (ext.repository && ext.artifactName) ? (version: string) => {
2627
return `${ext.repository}/releases/download/${version}/${ext.artifactName}`;
2728
} : undefined,
29+
availableVersions: ext.availableVersions,
2830
installed: false // Default to false, would need to check against installed extensions
2931
}));
3032
}
@@ -36,34 +38,39 @@ export async function loadExtRepoRaw(url: string): Promise<ManagerExtensionInfo[
3638
title: 'Mock Extension One',
3739
description: 'Mocked Extension',
3840
newestVersion: '',
41+
availableVersions: [],
3942
installed: false,
4043
},
4144
{
4245
id: 'mock-two',
4346
title: 'Mock Extension Two',
4447
description: 'Mocked Extension',
4548
newestVersion: '',
49+
availableVersions: [],
4650
installed: false,
4751
},
4852
{
4953
id: 'mock-three',
5054
title: 'Mock Extension Three',
5155
description: 'Mocked Extension',
5256
newestVersion: '',
57+
availableVersions: [],
5358
installed: false,
5459
},
5560
{
5661
id: 'mock-four',
5762
title: 'Mock Extension Four',
5863
description: 'Mocked Extension',
5964
newestVersion: '',
65+
availableVersions: [],
6066
installed: false,
6167
},
6268
{
6369
id: 'mock-five',
6470
title: 'Mock Extension Five',
6571
description: 'Mocked Extension',
6672
newestVersion: '',
73+
availableVersions: [],
6774
installed: false,
6875
},
6976
];

src/renderer/components/Dropdown.tsx

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { DropdownFrameProps, DropdownProps, DropdownRowProps } from 'flashpoint-launcher-renderer';
2-
import React, { Activity, useEffect, useMemo, useRef, useState } from 'react';
1+
import { DropdownCheckboxRowProps, DropdownFrameProps, DropdownProps, DropdownRowProps, DropdownStringRowProps } from 'flashpoint-launcher-renderer';
2+
import { Activity, useEffect, useMemo, useRef, useState } from 'react';
33

44
// A text element, with a drop-down element that can be shown/hidden.
55
export function DropdownFrame({ children, form, text, className, headerClassName }: DropdownFrameProps) {
@@ -88,7 +88,7 @@ export function Dropdown<T>({ rowCount, rowRenderer: RowRenderer, rowProps, form
8888
const rows = [];
8989
for (let i = 0; i < rowCount; i++) {
9090
rows.push(
91-
<RowRenderer key={i} index={i} {...rowProps} />
91+
<RowRenderer key={i} index={i} closeDropdown={() => setExpanded(false)} {...rowProps} />
9292
);
9393
}
9494
return rows;
@@ -119,13 +119,6 @@ export function Dropdown<T>({ rowCount, rowRenderer: RowRenderer, rowProps, form
119119
);
120120
}
121121

122-
export type DropdownCheckboxRowProps<T> = {
123-
labels: T[],
124-
labelRenderer?: (props: { label: T, index: number }) => React.JSX.Element;
125-
onToggle: (index: number) => void;
126-
isChecked: (index: number) => boolean;
127-
}
128-
129122
export function DropdownCheckboxRow<T>({
130123
labels, labelRenderer: LabelRenderer, onToggle, isChecked, index
131124
}: DropdownRowProps<DropdownCheckboxRowProps<T>>) {
@@ -152,3 +145,26 @@ export function DropdownCheckboxRow<T>({
152145
</label>
153146
);
154147
}
148+
149+
export function DropdownStringRow({
150+
items, onSelect, index, closeDropdown
151+
}: DropdownRowProps<DropdownStringRowProps>) {
152+
const label = items[index];
153+
154+
return (
155+
<label
156+
key={index}
157+
className='log-page__dropdown-item'
158+
onClick={() => {
159+
onSelect(index);
160+
closeDropdown();
161+
}}>
162+
<div className='simple-center'>
163+
<div
164+
className='simple-center__vertical-inner log-page__dropdown-item-text'>
165+
{label}
166+
</div>
167+
</div>
168+
</label>
169+
);
170+
}

src/renderer/components/app.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { BrowsePageDisplayGrid, BrowsePageDisplayList } from './BrowsePageDispla
3737
import { CheckBox } from './CheckBox';
3838
import { Dialog } from './Dialog';
3939
import { GameComponentDropdownSelectField, GameComponentInputField } from './DisplayComponent';
40-
import { Dropdown, DropdownFrame } from './Dropdown';
40+
import { Dropdown, DropdownCheckboxRow, DropdownFrame, DropdownStringRow } from './Dropdown';
4141
import { DynamicComponent } from './DynamicComponent';
4242
import { DynamicComponentProvider, RemoteModule } from './DynamicComponentProvider';
4343
import { DynamicThemeProvider } from './DynamicThemeProvider';
@@ -450,6 +450,8 @@ function addExtIntercepts() {
450450
CheckBox,
451451
Dropdown,
452452
DropdownFrame,
453+
DropdownCheckboxRow,
454+
DropdownStringRow,
453455
} satisfies typeof import('flashpoint-launcher-renderer-ext/components');
454456

455457
(window as any)['flashpoint-launcher-renderer-ext/hooks'] = {

src/renderer/components/pages/LogsPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { clearLogs } from '@renderer/store/logs/slice';
55
import { updatePreferences } from '@renderer/store/preferences/slice';
66
import { BackIn } from '@shared/back/types';
77
import { LogLevel } from '@shared/Log/interface';
8+
import { DropdownCheckboxRowProps } from 'flashpoint-launcher-renderer';
89
import { useState } from 'react';
9-
import { Dropdown, DropdownCheckboxRow, DropdownCheckboxRowProps } from '../Dropdown';
10+
import { Dropdown, DropdownCheckboxRow } from '../Dropdown';
1011
import { LogBox } from '../LogBox';
1112

1213
export type LogsPageProps = any;

static/window/styles/core.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ body {
196196
.simple-dropdown__select-icon {
197197
width: 1.25em;
198198
align-self: center;
199-
flex: 0 0 auto;
200-
margin-top: -0.125em;
199+
flex-grow: 0;
200+
text-align-last: center;
201201
}
202202
.simple-dropdown__select-icon:before {
203203
content: "▾";

typings/flashpoint-launcher.d.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3396,6 +3396,7 @@ declare module 'flashpoint-launcher-renderer' {
33963396

33973397
type DropdownRowProps<T> = T & {
33983398
index: number;
3399+
closeDropdown: () => void;
33993400
}
34003401

34013402
type DropdownCommonProps = {
@@ -3409,7 +3410,7 @@ declare module 'flashpoint-launcher-renderer' {
34093410
form?: boolean;
34103411
}
34113412

3412-
type DropdownFrameProps = DropdownCommonProps &{
3413+
type DropdownFrameProps = DropdownCommonProps & {
34133414
children: React.JSX.Element;
34143415
}
34153416

@@ -3422,6 +3423,18 @@ declare module 'flashpoint-launcher-renderer' {
34223423
rowRenderer: (props: DropdownRowProps<T>) => React.JSX.Element;
34233424
};
34243425

3426+
type DropdownCheckboxRowProps<T> = {
3427+
labels: T[],
3428+
labelRenderer?: (props: { label: T, index: number }) => React.JSX.Element;
3429+
onToggle: (index: number) => void;
3430+
isChecked: (index: number) => boolean;
3431+
}
3432+
3433+
type DropdownStringRowProps = {
3434+
items: string[],
3435+
onSelect: (index: number) => void;
3436+
}
3437+
34253438
declare global {
34263439
interface Window {
34273440
log: LogFuncs;
@@ -3459,8 +3472,10 @@ declare module 'flashpoint-launcher-renderer-ext/components' {
34593472
BrowsePageDisplayGridProps,
34603473
BrowsePageDisplayListProps,
34613474
CheckBoxProps,
3475+
DropdownCheckboxRowProps,
34623476
DropdownFrameProps,
34633477
DropdownProps,
3478+
DropdownStringRowProps,
34643479
GameComponentDropdownSelectFieldProps,
34653480
GameComponentInputFieldProps,
34663481
HomePageBoxProps,
@@ -3470,7 +3485,7 @@ declare module 'flashpoint-launcher-renderer-ext/components' {
34703485
SimpleButtonProps,
34713486
SizeProviderProps,
34723487
SortableColumnProps,
3473-
StateWrapperProps,
3488+
StateWrapperProps
34743489
} from 'flashpoint-launcher-renderer';
34753490
import { ComponentType } from 'react';
34763491

@@ -3490,8 +3505,10 @@ declare module 'flashpoint-launcher-renderer-ext/components' {
34903505
const StateWrapper: ComponentType<StateWrapperProps>;
34913506
const SimpleButton: ComponentType<SimpleButtonProps>;
34923507
const CheckBox: ComponentType<CheckBoxProps>;
3493-
const Dropdown: ComponentType<DropdownProps>;
3494-
const DropdownFrame: ComponentType<DropdownFrameProps>;
3508+
const Dropdown: <T>(props: DropdownProps<T>) => React.ReactElement;
3509+
const DropdownFrame: <T>(props: DropdownFrameProps<T>) => React.ReactElement;
3510+
const DropdownCheckboxRow: <T>(props: DropdownRowProps<DropdownCheckboxRowProps<T>>) => React.ReactElement;
3511+
const DropdownStringRow: (props: DropdownRowProps<DropdownStringRowProps>) => React.ReactElement;
34953512
}
34963513

34973514
declare module 'flashpoint-launcher-renderer-ext/hooks' {

0 commit comments

Comments
 (0)