Skip to content

Commit 619b55f

Browse files
authored
Alternative venue downloads (#110)
* Add ability for profiles to have variants * Persist selected alternative so updates download the correct version
1 parent 73e0edc commit 619b55f

8 files changed

Lines changed: 744 additions & 16 deletions

File tree

package-lock.json

Lines changed: 492 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@radix-ui/react-tabs": "^1.1.0",
4646
"@radix-ui/react-toggle-group": "^1.0.4",
4747
"@radix-ui/react-tooltip": "1.0.7",
48+
"@radix-ui/react-radio-group": "1.3.8",
4849
"@tanstack/query-sync-storage-persister": "^5.14.0",
4950
"@tanstack/react-query": "^5.14.0",
5051
"@tanstack/react-query-persist-client": "^5.14.0",

src/assets/Icons/Dropdown.svg

Lines changed: 1 addition & 1 deletion
Loading

src/components/Button/DropdownButton/DropdownButton.module.css

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
align-items: flex-start;
55
flex-shrink: 0;
66

7-
border-radius: 8px;
7+
/*border-radius: 8px;*/
88

99
/* Masking works WAY better than below */
1010
/*overflow: none;*/
@@ -13,24 +13,30 @@
1313

1414
.button {
1515
/* Disable the button's border radius and replace it with our own */
16-
--borderRadius: 0;
16+
/*--borderRadius: 0;*/
17+
border-radius: var(--borderRadius) 0 0 var(--borderRadius);
18+
border-right: 1px solid var(--sideBarBackground);
1719

1820
width: 100%;
1921
}
2022

2123
.dropdown_button {
2224
display: flex;
2325
box-sizing: border-box;
24-
padding: 10px;
26+
padding: 17.5px;
2527
height: 100%;
2628
flex-direction: column;
2729
justify-content: center;
2830
align-items: center;
2931
gap: 10px;
3032
flex-shrink: 0;
3133

32-
background: var(--sideBarBackground);
33-
border: none;
34+
background: var(--background);
35+
color: var(--color);
36+
border-radius: 0 50px 50px 0;
37+
border: 2px solid var(--border);
38+
border-left: 1px solid var(--sideBarBackground);
39+
3440
cursor: pointer;
3541
}
3642

@@ -66,10 +72,85 @@
6672
cursor: pointer;
6773
}
6874

75+
.dropdown_radio_root {
76+
display: flex;
77+
flex-direction: column;
78+
/*gap: 10px;*/
79+
}
80+
81+
.dropdown_radio_item {
82+
background-color: white;
83+
width: 25px;
84+
height: 25px;
85+
border: 1px solid var(--sideBarBackground);
86+
border-radius: 100%;
87+
}
88+
89+
.dropdown_radio_indicator {
90+
display: flex;
91+
align-items: center;
92+
width: 100%;
93+
height: 100%;
94+
position: relative;
95+
}
96+
97+
.dropdown_radio_label {
98+
padding-left: 10px;
99+
}
100+
101+
.dropdown_radio_label_text {
102+
padding-left: 10px;
103+
}
104+
105+
.dropdown_radio_indicator::after {
106+
content: "";
107+
display: block;
108+
width: 11px;
109+
height: 11px;
110+
border-radius: 50%;
111+
background-color: var(--buttonBlue);
112+
}
113+
69114
.dropdown_item[data-highlighted] {
70115
background: var(--sideBarSelection);
71116
}
72117

73118
.dropdown_arrow {
74119
fill: var(--sideBarBackground);
75120
}
121+
122+
.colorsGreen {
123+
--background: var(--buttonGreen);
124+
--color: var(--buttonGreenText);
125+
--border: var(--buttonLightBorder);
126+
}
127+
128+
.colorsBlue {
129+
--background: var(--buttonBlue);
130+
--color: var(--buttonBlueText);
131+
--border: var(--buttonLightBorder);
132+
}
133+
134+
.colorsYellow {
135+
--background: var(--buttonYellow);
136+
--color: var(--buttonYellowText);
137+
--border: var(--buttonLightBorder);
138+
}
139+
140+
.colorsDark {
141+
--background: var(--buttonDark);
142+
--color: var(--buttonDarkText);
143+
--border: var(--buttonDarkBorder);
144+
}
145+
146+
.colorsLight {
147+
--background: var(--buttonLight);
148+
--color: var(--buttonLightText);
149+
--border: var(--buttonLightBorder);
150+
}
151+
152+
.colorsRed {
153+
--background: var(--buttonRed);
154+
--color: var(--buttonRedText);
155+
--border: var(--buttonLightBorder);
156+
}

src/components/Button/DropdownButton/index.tsx

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { DropdownIcon } from "@app/assets/Icons";
2-
import Button, { ButtonProps } from "..";
2+
import Button, {ButtonColor, ButtonProps} from "..";
33
import styles from "./DropdownButton.module.css";
44
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
5+
import * as RadioGroup from "@radix-ui/react-radio-group";
6+
import {DropdownMenuItemIndicatorProps} from "@radix-ui/react-dropdown-menu";
7+
import {RadioGroupItemProps, RadioGroupProps} from "@radix-ui/react-radio-group";
58

69
interface DropdownProps extends ButtonProps {
710
dropdownChildren: React.ReactNode
@@ -16,14 +19,47 @@ const DropdownButton: React.FC<DropdownProps> = (props: DropdownProps) => {
1619
...buttonProps
1720
} = props;
1821

22+
let classes;
23+
switch (buttonProps.color) {
24+
case ButtonColor.BLUE:
25+
classes = [styles.colorsBlue];
26+
break;
27+
case ButtonColor.GREEN:
28+
classes = [styles.colorsGreen];
29+
break;
30+
case ButtonColor.YELLOW:
31+
classes = [styles.colorsYellow];
32+
break;
33+
case ButtonColor.LIGHT:
34+
classes = [styles.colorsLight];
35+
break;
36+
case ButtonColor.DARK:
37+
classes = [styles.colorsDark];
38+
break;
39+
case ButtonColor.RED:
40+
classes = [styles.colorsRed];
41+
break;
42+
default:
43+
classes = [styles.colorsBlue];
44+
break;
45+
}
46+
47+
if (buttonProps.border) {
48+
classes.push(styles.border);
49+
}
50+
51+
if (buttonProps.rounded) {
52+
classes.push(styles.rounded);
53+
}
54+
1955
return <DropdownMenu.Root>
20-
<div className={[styles.container, className].join(" ")} style={style}>
56+
<div className={[styles.container, ...classes, className].join(" ")} style={style}>
2157
<Button {...buttonProps as ButtonProps} className={styles.button}>
2258
{children}
2359
</Button>
2460
<DropdownMenu.Trigger asChild>
25-
<button className={styles.dropdown_button}>
26-
<DropdownIcon width={12} height={12} />
61+
<button className={[styles.dropdown_button, ...classes].join(" ")}>
62+
<DropdownIcon width={12} height={12} className={[...classes].join(" ")}/>
2763
</button>
2864
</DropdownMenu.Trigger>
2965
</div>
@@ -46,6 +82,10 @@ type ItemProps = React.PropsWithChildren<{
4682
onClick?: React.MouseEventHandler<HTMLDivElement>,
4783
}>;
4884

85+
interface RadioItemProps extends RadioGroupItemProps {
86+
label: string,
87+
}
88+
4989
const DropdownItem: React.FC<ItemProps> = (props: ItemProps) => {
5090
const {
5191
children,
@@ -57,4 +97,38 @@ const DropdownItem: React.FC<ItemProps> = (props: ItemProps) => {
5797
</DropdownMenu.Item>;
5898
};
5999

60-
export { DropdownButton, DropdownItem };
100+
const DropdownRadioGroup: React.FC<RadioGroupProps> = (props: RadioGroupProps) => {
101+
const {
102+
children,
103+
defaultValue,
104+
onValueChange
105+
} = props;
106+
107+
return <RadioGroup.Root className={styles.dropdown_radio_root} defaultValue={defaultValue} onValueChange={onValueChange}>
108+
{children}
109+
</RadioGroup.Root>;
110+
};
111+
112+
const DropdownRadioGroupIndicator: React.FC<DropdownMenuItemIndicatorProps> = () => {
113+
return <DropdownMenu.ItemIndicator className={styles.dropdown_item} />;
114+
};
115+
116+
const DropdownRadioItem: React.FC<RadioItemProps> = (props: RadioItemProps) => {
117+
const {
118+
onClick,
119+
checked,
120+
value,
121+
label
122+
} = props;
123+
124+
return <>
125+
<label className={[styles.dropdown_item, styles.dropdown_radio_label].join(" ")}>
126+
<RadioGroup.Item className={styles.dropdown_radio_item} onClick={onClick} value={value}>
127+
<RadioGroup.Indicator className={styles.dropdown_radio_indicator} />
128+
</RadioGroup.Item>
129+
<span className={styles.dropdown_radio_label_text}>{label}</span>
130+
</label>
131+
</>;
132+
};
133+
134+
export { DropdownButton, DropdownItem, DropdownRadioItem, DropdownRadioGroup, DropdownRadioGroupIndicator };

src/profiles/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface Version {
2323
tag: string,
2424
release: string,
2525
content: ReleaseContent[],
26+
alternatives?: Alternative[],
2627
launchOptions?: {
2728
[platform in OS]?: {
2829
executablePath: string,
@@ -132,12 +133,20 @@ export interface VenueProfile {
132133
version: VersionInfo,
133134
}
134135

136+
export interface Alternative {
137+
label: string,
138+
uuid: string,
139+
default?: boolean,
140+
content: ReleaseContent[],
141+
}
142+
135143
export interface ActiveProfile {
136144
uuid: string,
137145
originalUrl: string,
138146

139147
displayName?: string,
140148
selectedVersion?: string,
149+
selectedAlternative?: string,
141150
launchArguments: string,
142151
useObsVkcapture: boolean,
143152

src/routes/AppProfile/LaunchButton.tsx

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,23 @@ import { usePayload } from "@app/tasks/payload";
77
import PayloadProgress from "@app/components/PayloadProgress";
88
import { useEffect, useRef, useState } from "react";
99
import { useOfflineStatus } from "@app/hooks/useOfflineStatus";
10+
import {
11+
DropdownButton,
12+
DropdownRadioGroup, DropdownRadioGroupIndicator,
13+
DropdownRadioItem
14+
} from "@app/components/Button/DropdownButton";
15+
import {useProfileStore} from "@app/profiles/store";
16+
import {ActiveProfile} from "@app/profiles/types";
17+
1018

1119
interface Props {
1220
profileState: ProfileState
1321
}
1422

23+
async function saveProfile(profile: ActiveProfile) {
24+
await useProfileStore.getState().updateProfile(profile);
25+
}
26+
1527
export function LaunchButton({ profileState }: Props) {
1628
const {
1729
loading,
@@ -71,18 +83,61 @@ export function LaunchButton({ profileState }: Props) {
7183
</Button>;
7284
}
7385

74-
return <Button color={ButtonColor.GREEN} rounded border onClick={async () => await downloadAndInstall()}>
86+
// No alternatives
87+
if (profileState.activeProfile.version.alternatives === undefined || profileState.activeProfile.version.alternatives.length < 2) {
88+
return <Button color={ButtonColor.GREEN} rounded border onClick={async () => await downloadAndInstall()}>
89+
{folderState === ProfileFolderState.UpdateRequired &&
90+
<>
91+
<UpdateIcon/> Update {releaseName}
92+
</>
93+
}
94+
{folderState === ProfileFolderState.FirstDownload &&
95+
<>
96+
<UpdateIcon/> Install {releaseName}
97+
</>
98+
}
99+
</Button>;
100+
}
101+
102+
// Use a DropdownButton so the user can select their preferred alternative download
103+
104+
let dropdownDefault;
105+
if (profileState.activeProfile.selectedAlternative === undefined) {
106+
// dropdownDefault = profileState.activeProfile.version.alternatives.findLastIndex((alternative) => alternative.default === true);
107+
dropdownDefault = profileState.activeProfile.version.alternatives.find(alternative => alternative.default === true)?.uuid;
108+
} else {
109+
dropdownDefault = profileState.activeProfile.selectedAlternative;
110+
// dropdownDefault = profileState.activeProfile.version.alternatives.findIndex((alternative) => alternative.label === profileState.activeProfile.selectedAlternative.label);
111+
112+
}
113+
114+
const dropdownElements = profileState.activeProfile.version.alternatives.map((alternative) => {
115+
return <DropdownRadioItem key={alternative.uuid} label={alternative.label} value={alternative.uuid} onClick={() => {
116+
profileState.activeProfile.selectedAlternative = alternative.uuid;
117+
saveProfile(profileState.activeProfile);
118+
}}>
119+
{alternative.label}
120+
<DropdownRadioGroupIndicator />
121+
</DropdownRadioItem>;
122+
});
123+
124+
const dropdownItems = <><DropdownRadioGroup defaultValue={dropdownDefault} value={profileState.activeProfile.selectedAlternative} onValueChange={() => {}}>{dropdownElements}</DropdownRadioGroup></>;
125+
126+
return <DropdownButton color={ButtonColor.GREEN} rounded border
127+
onClick={async () => await downloadAndInstall()}
128+
dropdownChildren={dropdownItems}>
75129
{folderState === ProfileFolderState.UpdateRequired &&
76130
<>
77-
<UpdateIcon /> Update {releaseName}
131+
<UpdateIcon/> Update {releaseName}
78132
</>
79133
}
80134
{folderState === ProfileFolderState.FirstDownload &&
81135
<>
82-
<UpdateIcon /> Install {releaseName}
136+
<UpdateIcon/> Install {releaseName}
83137
</>
84138
}
85-
</Button>;
139+
140+
</DropdownButton>;
86141
}
87142

88143
// Launch/up-to-date button

0 commit comments

Comments
 (0)