Skip to content

Commit c084b5d

Browse files
committed
feat: Select version to install in extension manager
1 parent 3d81a9f commit c084b5d

File tree

6 files changed

+108
-115
lines changed

6 files changed

+108
-115
lines changed

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

Lines changed: 72 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import { useAppSelector } from 'flashpoint-launcher-renderer-ext/hooks';
44
import { getExtensionFileURL, runCommand, setExtensionEnabled } from 'flashpoint-launcher-renderer-ext/utils';
55
import { useEffect, useState } from 'react';
66
import { DownloadExtCommand, UninstallExtCommand } from '../commands';
7-
import { loadExtIndexUrl, ManagerExtensionInfo } from '../extensionLoader';
7+
import { loadExtIndexUrl, ManagerExtensionInfo, ManagerExtensionRemoteInfo } from '../extensionLoader';
88

99
export type ExtensionRowProps = {
10-
item: ManagerExtensionInfo;
10+
ext: ManagerExtensionInfo;
1111
index: number;
1212
disabled: boolean;
1313
}
1414

1515
export function ExtensionSubsection() {
16-
const [availableExtensions, setAvailableExtensions] = useState<ManagerExtensionInfo[]>([]);
16+
const [remoteExtensions, setRemoteExtensions] = useState<ManagerExtensionRemoteInfo[]>([]);
1717
const installedExtensions = useAppSelector(state => state.main.extensions);
1818
console.log(installedExtensions);
1919
const disabledExtensions = useAppSelector(state => state.preferences.disabledExtensions);
@@ -22,40 +22,44 @@ export function ExtensionSubsection() {
2222
console.log('loading ext');
2323
loadExtIndexUrl('https://raw.githubusercontent.com/FlashpointProject/FlashpointExtensionIndex/refs/heads/main/extindex.json')
2424
.then((data) => {
25-
setAvailableExtensions(data);
25+
setRemoteExtensions(data);
2626
});
2727
}, []);
2828

2929
const extensionList: ManagerExtensionInfo[] = installedExtensions.map(ext => {
3030
return {
3131
id: ext.id,
32-
title: ext.displayName || ext.name,
33-
description: ext.description || 'No Description',
34-
newestVersion: ext.version,
35-
iconUrl: ext.icon ? getExtensionFileURL(ext.id, ext.icon) : undefined,
36-
installed: true,
37-
availableVersions: [],
32+
local: {
33+
title: ext.displayName || ext.name,
34+
author: ext.author,
35+
description: ext.description || 'No Description',
36+
installedVersion: ext.version,
37+
iconUrl: ext.icon ? getExtensionFileURL(ext.id, ext.icon) : undefined,
38+
}
3839
} satisfies ManagerExtensionInfo;
3940
});
4041

41-
for (const ext of availableExtensions) {
42-
const existingIdx = installedExtensions.findIndex(e => e.id === ext.id);
42+
for (const ext of remoteExtensions) {
43+
const existingIdx = extensionList.findIndex(e => e.id === ext.id);
4344
if (existingIdx === -1) {
44-
extensionList.push(ext);
45+
extensionList.push({
46+
id: ext.id,
47+
remote: ext
48+
});
4549
} else {
46-
extensionList[existingIdx].availableVersions = ext.availableVersions;
50+
extensionList[existingIdx].remote = ext;
4751
}
4852
}
4953

50-
extensionList.sort((a, b) => a.title.localeCompare(b.title));
54+
extensionList.sort((a, b) => (a.local?.title || a.remote?.title || '???').localeCompare(b.local?.title || b.remote?.title || '???'));
5155

5256
return <div className='manager-page-subsection'>
5357
<div className='manager-page-subsection-header'>Extensions</div>
5458
<div className='manager-page-subsection-list simple-scroll'>
5559
{ extensionList.length > 0 ? extensionList.map((ext, index) => {
5660
return (
5761
<ExtensionRow
58-
item={ext}
62+
ext={ext}
5963
index={index}
6064
disabled={!disabledExtensions.includes(ext.id)}/>
6165
);
@@ -64,26 +68,18 @@ export function ExtensionSubsection() {
6468
</div>;
6569
}
6670

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);
71+
export function ExtensionRow({ ext, disabled, index }: ExtensionRowProps) {
72+
const [userSelectedVersion, setUserSelectedVersion] = useState<string>();
7073
const [busy, setBusy] = useState(false);
71-
const canInstall = getDownloadUrl !== undefined;
7274
const enabled = !disabled;
7375
let rowClassName = 'manager-extension-row';
7476
if (index % 2 === 0) { rowClassName += ' manager-extension-row--even'; }
7577

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-
);
78+
const selectedVersion = (userSelectedVersion && ext.remote?.availableVersions.includes(userSelectedVersion)) ?
79+
userSelectedVersion :
80+
(ext.local?.installedVersion || ext.remote?.newestVersion || '???');
81+
82+
const { title, description, iconUrl } = getExtDetails(ext);
8783

8884
return <div className={rowClassName}>
8985
<div className='manager-extension-row-icon'>
@@ -93,12 +89,12 @@ export function ExtensionRow({ item, disabled, index }: ExtensionRowProps) {
9389
</div>
9490
<div className='manager-extension-row-content'>
9591
<div className='manager-extension-row-top'>
96-
<div className='manager-extension-row-title'>{title} - {`${id}`}</div>
92+
<div className='manager-extension-row-title'>{title} - {`${ext.id}`}</div>
9793
{ !busy && (
9894
<>
99-
{ installed && (
95+
{ ext.local && (
10096
<CheckBox
101-
onToggle={() => setExtensionEnabled(id, !enabled)}
97+
onToggle={() => setExtensionEnabled(ext.id, !enabled)}
10298
checked={enabled}/>
10399
)}
104100
</>
@@ -107,11 +103,22 @@ export function ExtensionRow({ item, disabled, index }: ExtensionRowProps) {
107103
<div className='manager-extension-row-inner'>
108104
<div>{description}</div>
109105
{ !busy ? (
110-
<>
111-
{ installed && (
106+
<div className='manager-extension-row-buttons'>
107+
{ ext.remote && (
108+
<Dropdown<DropdownStringRowProps>
109+
text={`Ver: ${selectedVersion}`}
110+
rowCount={ext.remote.availableVersions.length}
111+
rowProps={{
112+
items: ext.remote.availableVersions,
113+
onSelect: (index) => setUserSelectedVersion(ext.remote!.availableVersions[index])
114+
}}
115+
rowRenderer={DropdownStringRow}
116+
/>
117+
)}
118+
{ ext.local && (
112119
<SimpleButton value={'Remove'} onClick={() => {
113120
setBusy(true);
114-
runCommand(UninstallExtCommand, id)
121+
runCommand(UninstallExtCommand, ext.id)
115122
.catch((error) => {
116123
const errorString = `Failed to uninstall extension: ${error}`;
117124
alert(errorString);
@@ -120,24 +127,37 @@ export function ExtensionRow({ item, disabled, index }: ExtensionRowProps) {
120127
.finally(() => setBusy(false));
121128
}}/>
122129
)}
123-
{ canInstall && (
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-
</>
130+
{ ext.remote !== undefined && (
131+
<SimpleButton value={ext.local ? 'Update' : 'Install'} onClick={() => {
132+
setBusy(true);
133+
runCommand(DownloadExtCommand, ext.id, ext.remote!.getDownloadUrl(selectedVersion))
134+
.catch((error) => {
135+
const errorString = `Failed to download and install extension: ${error}`;
136+
alert(errorString);
137+
log.error('Manager', errorString);
138+
})
139+
.finally(() => setBusy(false));
140+
}}/>
137141
)}
138-
</>
142+
</div>
139143
) : <div>Busy...</div> }
140144
</div>
141145
</div>
142146
</div>;
143147
}
148+
149+
type ExtDetails = {
150+
author: string,
151+
title: string;
152+
description: string;
153+
iconUrl?: string;
154+
}
155+
156+
function getExtDetails(ext: ManagerExtensionInfo): ExtDetails {
157+
return {
158+
author: ext.remote?.author || ext.local?.author || '???',
159+
title: ext.remote?.title || ext.local?.title || '???',
160+
description: ext.remote?.description || ext.local?.description || '???',
161+
iconUrl: ext.local?.iconUrl || ext.remote?.iconUrl,
162+
};
163+
}

extensions/core-manager/src/extension.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ export async function activate(context: ExtensionContext): Promise<void> {
1111
};
1212

1313
register(
14-
commands.registerCommand(DownloadExtCommand, async (url: string) => {
14+
commands.registerCommand(DownloadExtCommand, async (extId: string, url: string) => {
15+
// Uninstall extension if it exists
16+
await uninstallExtension(extId);
17+
1518
const tempPath = os.tmpdir();
1619
const tempFilepath = path.join(tempPath, `extension-${Date.now()}.zip`);
1720
const writer = fs.createWriteStream(tempFilepath);

extensions/core-manager/src/extensionLoader.ts

Lines changed: 22 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,49 @@
11
import axios from 'axios';
22

3-
export type ManagerExtensionInfo = {
4-
id: string;
3+
type ManagerExtensionCommonInfo = {
54
title: string;
5+
author: string;
66
description: string;
77
iconUrl?: string;
8+
}
9+
10+
export type ManagerExtensionRemoteInfo = ManagerExtensionCommonInfo & {
11+
id: string;
812
newestVersion: string;
913
availableVersions: string[];
10-
getDownloadUrl?: (version: string) => string,
11-
installed: boolean;
14+
getDownloadUrl: (version: string) => string,
15+
}
16+
17+
export type ManagerExtensionLocalInfo = ManagerExtensionCommonInfo & {
18+
installedVersion: string;
19+
};
20+
21+
export type ManagerExtensionInfo = {
22+
id: string;
23+
local?: ManagerExtensionLocalInfo;
24+
remote?: ManagerExtensionRemoteInfo;
1225
}
1326

14-
export async function loadExtIndexUrl(url: string): Promise<ManagerExtensionInfo[]> {
27+
export async function loadExtIndexUrl(url: string): Promise<ManagerExtensionRemoteInfo[]> {
1528
const res = await axios.get(url);
1629
if (res.status >= 400) {
1730
throw res.statusText;
1831
}
1932

20-
return res.data.map((ext: IndexExtensionInfo): ManagerExtensionInfo => ({
33+
return res.data.map((ext: IndexExtensionInfo): ManagerExtensionRemoteInfo => ({
2134
id: ext.id,
2235
title: ext.title,
36+
author: ext.author,
2337
description: ext.description,
2438
iconUrl: ext.iconUrl,
2539
newestVersion: ext.newestVersion,
26-
getDownloadUrl: (ext.repository && ext.artifactName) ? (version: string) => {
40+
getDownloadUrl: (version: string) => {
2741
return `${ext.repository}/releases/download/${version}/${ext.artifactName}`;
28-
} : undefined,
42+
},
2943
availableVersions: ext.availableVersions,
30-
installed: false // Default to false, would need to check against installed extensions
3144
}));
3245
}
3346

34-
export async function loadExtRepoRaw(url: string): Promise<ManagerExtensionInfo[]> {
35-
const mockData: ManagerExtensionInfo[] = [
36-
{
37-
id: 'mock-one',
38-
title: 'Mock Extension One',
39-
description: 'Mocked Extension',
40-
newestVersion: '',
41-
availableVersions: [],
42-
installed: false,
43-
},
44-
{
45-
id: 'mock-two',
46-
title: 'Mock Extension Two',
47-
description: 'Mocked Extension',
48-
newestVersion: '',
49-
availableVersions: [],
50-
installed: false,
51-
},
52-
{
53-
id: 'mock-three',
54-
title: 'Mock Extension Three',
55-
description: 'Mocked Extension',
56-
newestVersion: '',
57-
availableVersions: [],
58-
installed: false,
59-
},
60-
{
61-
id: 'mock-four',
62-
title: 'Mock Extension Four',
63-
description: 'Mocked Extension',
64-
newestVersion: '',
65-
availableVersions: [],
66-
installed: false,
67-
},
68-
{
69-
id: 'mock-five',
70-
title: 'Mock Extension Five',
71-
description: 'Mocked Extension',
72-
newestVersion: '',
73-
availableVersions: [],
74-
installed: false,
75-
},
76-
];
77-
78-
return new Promise(resolve => {
79-
setTimeout(() => {
80-
resolve(mockData);
81-
}, 500);
82-
});
83-
}
84-
85-
8647
type IndexExtPackageInfo = {
8748
id: string;
8849
author: string;

extensions/core-manager/static/core-manager.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,13 @@
8282

8383
.manager-extension-row-inner {
8484
padding: 0.2rem
85+
}
86+
87+
.manager-extension-row-buttons {
88+
display: flex;
89+
flex-direction: row;
90+
}
91+
92+
.manager-extension-row-buttons > :not(:first-child) {
93+
margin-left: 0.3rem;
8594
}

src/back/extensions/util.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export async function parseAppVar(extId: string, appPath: string, launchCommand:
3333
}
3434

3535
export async function uninstallExtension(state: BackState, extId: string) {
36-
console.log('finding ' + extId);
3736
const ext = await state.extensionsService.getExtension(extId);
3837
if (ext) {
3938
await state.extensionsService.removeExtension(extId);

src/shared/back/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ export type BackOutTemplate = SocketTemplate<BackOut, {
593593

594594
[BackOut.OPEN_DYNAMIC_PAGE]: (componentName: string, props: any) => void;
595595

596+
[BackOut.REMOVED_EXTENSION]: (extId: string) => void;
596597
[BackOut.ADDED_EXTENSION]: (ext: IExtensionDescription) => void;
597598
[BackOut.UPDATE_EXTENSION_STATE]: (extId: string, enabled: boolean) => void;
598599

0 commit comments

Comments
 (0)