1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00

feat: add user lang/theme to fatal error screen

This commit is contained in:
Ash Keel 2023-10-29 17:36:23 +01:00
parent 11e7741f83
commit 7e4d358f60
No known key found for this signature in database
GPG key ID: 53A9E9A6035DD109
11 changed files with 130 additions and 78 deletions

View file

@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added an unfinished light mode, check the UI settings
- Added a light theme, can be set during onboarding or in the UI settings
- The fatal error screen now uses the user configured language and theme
- Added the ability to hide viewer count in the dashboard
- Custom chat commands can now be sent as replies, whispers and announcements. Due to some API shenanigans yet to be solved, the latter two will always be sent from your main account, not the bot account (if they are different)
- Added a structured RPC `twitch/bot/@send-message` for sending messages as replies, announcements and whispers.

2
app.go
View file

@ -117,7 +117,7 @@ func (a *App) startup(ctx context.Context) {
go a.forwardLogs()
// Run HTTP server
if err := a.httpServer.Listen(); err != nil {
if err = a.httpServer.Listen(); err != nil {
a.showFatalError(err, "HTTP server stopped")
return
}

View file

@ -98,7 +98,7 @@
"app-category": "Category",
"app-oauth-redirect-url": "OAuth Redirect URLs",
"test-button": "Test connection",
"test-failed": "Test failed: \"{{0}}\". Check your app client IDs and secret!",
"test-failed": "Test failed: \"{{error}}\". Check your app client IDs and secret!",
"test-succeeded": "Test succeeded!",
"bot-chat-cooldown-tip": "Global chat cooldown for commands (in seconds)"
},

View file

@ -359,7 +359,7 @@
}
},
"test-button": "Test connessione",
"test-failed": "Test fallito: \"{{0}}\". \nControlla ID e segreto client dell'app!",
"test-failed": "Test fallito: \"{{error}}\". \nControlla ID e segreto client dell'app!",
"test-succeeded": "Test riuscito!",
"bot-chat-cooldown-tip": "Tempo minimo di attesa tra comandi (in secondi)"
},

View file

@ -220,7 +220,11 @@ export default function App(): JSX.Element {
// Sync UI changes on key change
useEffect(() => {
if (uiConfig?.language) {
void i18n.changeLanguage(uiConfig?.language ?? 'en');
void i18n.changeLanguage(uiConfig.language ?? 'en');
localStorage.setItem('language', uiConfig.language);
}
if (uiConfig?.theme) {
localStorage.setItem('theme', uiConfig.theme);
}
if (!uiConfig?.onboardingDone) {
navigate('/setup');

View file

@ -22,6 +22,7 @@ import {
DialogActions,
Field,
FlexRow,
getTheme,
InputBox,
Label,
MultiToggle,
@ -43,6 +44,8 @@ const Container = styled('div', {
overflow: 'hidden',
height: '100vh',
border: '2px solid $red10',
backgroundColor: '$gray1',
color: '$gray12',
});
const ErrorHeader = styled('h1', {
@ -497,6 +500,7 @@ export default function ErrorWindow(): JSX.Element {
const [recoveryDialogOpen, setRecoveryDialogOpen] = useState(false);
useEffect(() => {
void i18n.changeLanguage(localStorage.getItem('language') ?? 'en');
void GetLastLogs().then((appLogs) => {
setLogs(appLogs.map(processEntry).reverse());
});
@ -509,9 +513,10 @@ export default function ErrorWindow(): JSX.Element {
}, []);
const fatal = logs.find((log) => log.level === 'error');
const theme = getTheme(localStorage.getItem('theme') ?? 'dark');
return (
<Container>
<Container id="app-container" className={theme}>
<ReportDialog
open={reportDialogOpen}
onOpenChange={setReportDialogOpen}
@ -541,8 +546,9 @@ export default function ErrorWindow(): JSX.Element {
))}
</MultiToggle>
</LanguageSelector>
<Scrollbar vertical={true} viewport={{ flex: '1', maxHeight: '100vh' }}>
<div style={{ width: '100vw' }}>
<PageContainer>
<Scrollbar vertical={true} viewport={{ maxHeight: '100vh' }}>
<PageHeader>
<TextBlock>{t('pages.crash.fatal-message')}</TextBlock>
</PageHeader>
@ -595,8 +601,9 @@ export default function ErrorWindow(): JSX.Element {
<LogItem key={log.time.toString()} data={log} />
))}
</LogContainer>
</Scrollbar>
</PageContainer>
</div>
</Scrollbar>
</Container>
);
}

View file

@ -144,6 +144,14 @@ const MenuLink = styled(Link, {
},
});
const AppLogo = styled('img', {
height: '28px',
marginBottom: '-2px',
[`.${lightMode} &`]: {
filter: 'invert(1)',
},
});
function SidebarLink({ route: { title, url, icon } }: { route: Route }) {
const { t } = useTranslation();
const resolved = useResolvedPath(url);
@ -254,10 +262,7 @@ export default function Sidebar({
<Header>
<AppLink to={'/about'} status={matchApp ? 'active' : 'default'}>
<AppName>
<img
src={logo as string}
style={{ height: '28px', marginBottom: '-2px' }}
/>
<AppLogo src={logo as string} />
{APPNAME}
</AppName>
<VersionLabel>

View file

@ -31,6 +31,7 @@ import {
Field,
InputBox,
Label,
lightMode,
MultiToggle,
MultiToggleItem,
PageContainer,
@ -38,6 +39,7 @@ import {
SectionHeader,
styled,
TextBlock,
themes,
} from '../theme';
import { Alert } from '../theme/alert';
@ -86,7 +88,7 @@ const HeroContainer = styled('div', {
overflow: 'hidden',
});
const HeroLanguageSelector = styled('div', {
const HeroSelector = styled('div', {
top: '10px',
left: '10px',
display: 'flex',
@ -95,7 +97,7 @@ const HeroLanguageSelector = styled('div', {
zIndex: '10',
});
const LanguageItem = styled(MultiToggleItem, {
const HeroSelectorItem = styled(MultiToggleItem, {
fontSize: '1rem',
padding: '5px 8px',
});
@ -161,6 +163,10 @@ const StepList = styled('nav', {
flexWrap: 'wrap',
flexDirection: 'row',
justifyContent: 'flex-start',
[`.${lightMode} &`]: {
borderBottom: '1px solid $gray6',
backgroundColor: '$gray2',
},
});
const StepName = styled('div', {
@ -180,6 +186,10 @@ const StepName = styled('div', {
active: {
color: '$gray12',
display: 'inherit',
[`.${lightMode} &`]: {
fontWeight: '500',
},
},
},
interaction: {
@ -403,9 +413,9 @@ function TwitchIntegrationStep() {
variation={testResult.error ? 'danger' : 'default'}
description={
testResult.error
? t('pages.twitch-settings.test-failed', [
testResult.error.message,
])
? t('pages.twitch-settings.test-failed', {
error: testResult.error.message,
})
: t('pages.twitch-settings.test-succeeded')
}
actionText={t('form-actions.ok')}
@ -684,7 +694,7 @@ export default function OnboardingPage() {
<TopBanner>
{landing ? (
<HeroContainer>
<HeroLanguageSelector>
<HeroSelector>
<MultiToggle
value={uiConfig?.language ?? i18n.resolvedLanguage}
type="single"
@ -692,10 +702,11 @@ export default function OnboardingPage() {
void dispatch(
setUiConfig({ ...uiConfig, language: newLang }),
);
localStorage.setItem('language', newLang);
}}
>
{languages.map((lang) => (
<LanguageItem
<HeroSelectorItem
key={lang.code}
aria-label={lang.name}
value={lang.code}
@ -707,10 +718,29 @@ export default function OnboardingPage() {
>
{lang.name}
{lang.keys < maxKeys ? <ExclamationTriangleIcon /> : null}
</LanguageItem>
</HeroSelectorItem>
))}
</MultiToggle>
</HeroLanguageSelector>
<MultiToggle
value={uiConfig?.theme ?? 'dark'}
type="single"
onValueChange={(newTheme) => {
void dispatch(setUiConfig({ ...uiConfig, theme: newTheme }));
localStorage.setItem('theme', newTheme);
}}
>
{themes.map((theme) => (
<HeroSelectorItem
key={theme}
value={theme}
aria-label={t(`pages.uiconfig.themes.${theme}`)}
>
{t(`pages.uiconfig.themes.${theme}`)}
</HeroSelectorItem>
))}
</MultiToggle>
</HeroSelector>
<HeroAnimation>{animationItems}</HeroAnimation>
<HeroTitle>{t('pages.onboarding.welcome-header')}</HeroTitle>
<HeroContent>

View file

@ -357,9 +357,9 @@ function TwitchAPISettings() {
variation={testResult.error ? 'danger' : 'default'}
description={
testResult.error
? t('pages.twitch-settings.test-failed', [
testResult.error.message,
])
? t('pages.twitch-settings.test-failed', {
error: testResult.error.message,
})
: t('pages.twitch-settings.test-succeeded')
}
actionText={t('form-actions.ok')}

View file

@ -13,6 +13,7 @@ import {
PageHeader,
PageTitle,
styled,
themes,
} from '../theme';
const PartialWarning = styled('small', {
@ -42,6 +43,7 @@ export default function UISettingsPage(): React.ReactElement {
value={uiConfig?.language ?? i18n.resolvedLanguage}
onValueChange={(value) => {
void dispatch(setUiConfig({ ...uiConfig, language: value }));
localStorage.setItem('language', value);
}}
values={languages.map((lang) => ({
id: lang.code,
@ -68,8 +70,9 @@ export default function UISettingsPage(): React.ReactElement {
value={uiConfig?.theme ?? 'dark'}
onValueChange={(value) => {
void dispatch(setUiConfig({ ...uiConfig, theme: value }));
localStorage.setItem('theme', value);
}}
values={['dark', 'light'].map((theme) => ({
values={themes.map((theme) => ({
id: theme,
label: t(`pages.uiconfig.themes.${theme}`),
}))}

View file

@ -76,6 +76,8 @@ export const lightMode = createTheme({
},
});
export const themes = ['dark', 'light'];
export function getTheme(themeName: string) {
switch (themeName) {
case 'light':