Compare commits

...

2 Commits

Author SHA1 Message Date
Ash Keel 3c3ea7bdb4
fix: use non-native tooltip for recent event 2024-02-25 21:20:08 +01:00
Ash Keel b5f5c2975c
fix: woops this is different 2024-02-25 16:56:37 +01:00
5 changed files with 99 additions and 17 deletions

View File

@ -8,7 +8,14 @@ import {
import { useLiveKey, useModule } from '~/lib/react'; import { useLiveKey, useModule } from '~/lib/react';
import { useAppDispatch, useAppSelector } from '~/store'; import { useAppDispatch, useAppSelector } from '~/store';
import { modules } from '~/store/api/reducer'; import { modules } from '~/store/api/reducer';
import { PageContainer, SectionHeader, styled, TextBlock } from '../theme'; import * as HoverCard from '@radix-ui/react-hover-card';
import {
PageContainer,
SectionHeader,
styled,
TextBlock,
TooltipContent,
} from '../theme';
import BrowserLink from '../components/BrowserLink'; import BrowserLink from '../components/BrowserLink';
import Scrollbar from '../components/utils/Scrollbar'; import Scrollbar from '../components/utils/Scrollbar';
import RevealLink from '../components/utils/RevealLink'; import RevealLink from '../components/utils/RevealLink';
@ -345,15 +352,21 @@ function TwitchEventLog({ events }: { events: EventSubNotification[] }) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<> <>
<SectionHeader> <HoverCard.Root>
{t('pages.dashboard.twitch-events.header')} <HoverCard.Trigger asChild>
<a <SectionHeader>
style={{ marginLeft: '10px' }} {t('pages.dashboard.twitch-events.header')}
title={t('pages.dashboard.twitch-events.warning')} <a style={{ marginLeft: '10px' }}>
> <InfoCircledIcon />
<InfoCircledIcon /> </a>
</a> </SectionHeader>
</SectionHeader> </HoverCard.Trigger>
<HoverCard.Portal>
<TooltipContent>
{t('pages.dashboard.twitch-events.warning')}
</TooltipContent>
</HoverCard.Portal>
</HoverCard.Root>
<Scrollbar vertical={true} viewport={{ maxHeight: '250px' }}> <Scrollbar vertical={true} viewport={{ maxHeight: '250px' }}>
<EventListContainer> <EventListContainer>
{events {events
@ -427,10 +440,15 @@ function TwitchSection() {
); );
} }
function ProblemList() {
return <></>;
}
export default function Dashboard(): React.ReactElement { export default function Dashboard(): React.ReactElement {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<PageContainer> <PageContainer>
<ProblemList />
<TwitchSection /> <TwitchSection />
<SectionHeader>{t('pages.dashboard.quick-links')}</SectionHeader> <SectionHeader>{t('pages.dashboard.quick-links')}</SectionHeader>
<UsefulLinksMenu> <UsefulLinksMenu>

View File

@ -26,6 +26,8 @@ import extensionsReducer, {
startExtension, startExtension,
stopExtension, stopExtension,
} from '~/store/extensions/reducer'; } from '~/store/extensions/reducer';
import { useModule } from '~/lib/react';
import { modules } from '~/store/api/reducer';
import AlertContent from '../components/AlertContent'; import AlertContent from '../components/AlertContent';
import DialogContent from '../components/DialogContent'; import DialogContent from '../components/DialogContent';
import Loading from '../components/Loading'; import Loading from '../components/Loading';
@ -37,6 +39,7 @@ import {
DialogActions, DialogActions,
Field, Field,
FlexRow, FlexRow,
getTheme,
InputBox, InputBox,
Label, Label,
MultiButton, MultiButton,
@ -220,7 +223,7 @@ function ExtensionListItem(props: ExtensionListItemProps) {
}} }}
showCancel={false} showCancel={false}
> >
<code>{props.error.toString()}</code> <code>{props.error.message}</code>
</AlertContent> </AlertContent>
</Alert> </Alert>
) : null} ) : null}
@ -583,6 +586,7 @@ function ExtensionEditor() {
} }
export default function ExtensionsPage(): React.ReactElement { export default function ExtensionsPage(): React.ReactElement {
const [uiConfig] = useModule(modules.uiConfig);
const { t } = useTranslation(); const { t } = useTranslation();
const extensions = useAppSelector((state) => state.extensions); const extensions = useAppSelector((state) => state.extensions);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -619,12 +623,20 @@ export default function ExtensionsPage(): React.ReactElement {
}; };
if (!extensions.ready) { if (!extensions.ready) {
const theme = getTheme(
uiConfig?.theme ?? localStorage.getItem('theme') ?? 'dark',
);
return ( return (
<PageContainer> <PageContainer>
<PageHeader> <PageHeader>
<PageTitle>{t('pages.extensions.title')}</PageTitle> <PageTitle>{t('pages.extensions.title')}</PageTitle>
</PageHeader> </PageHeader>
<Loading size="fill" message={t('pages.extensions.loading')} /> <Loading
theme={theme}
size="fill"
message={t('pages.extensions.loading')}
/>
</PageContainer> </PageContainer>
); );
} }

View File

@ -1,5 +1,6 @@
/* eslint-disable import/prefer-default-export */ /* eslint-disable import/prefer-default-export */
import { Content as HoverCardContent } from '@radix-ui/react-hover-card';
import { styled, theme } from './theme'; import { styled, theme } from './theme';
export const FlexRow = styled('div', { export const FlexRow = styled('div', {
@ -26,3 +27,15 @@ export const FlexRow = styled('div', {
}, },
}, },
}); });
export const TooltipContent = styled(HoverCardContent, {
borderRadius: 6,
display: 'flex',
padding: '0.5rem',
gap: '0.5rem',
flexDirection: 'column',
border: '2px solid $gray6',
backgroundColor: '$gray2',
alignItems: 'flex-start',
boxShadow: '0px 5px 20px rgba(0,0,0,0.4)',
});

View File

@ -3,6 +3,7 @@ package twitch
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"slices"
"time" "time"
"github.com/nicklaw5/helix/v2" "github.com/nicklaw5/helix/v2"
@ -16,22 +17,60 @@ type AuthResponse struct {
Time time.Time Time time.Time
} }
var scopes = []string{
"bits:read",
"channel:bot",
"channel:moderate",
"channel:read:hype_train",
"channel:read:polls",
"channel:read:predictions",
"channel:read:redemptions",
"channel:read:subscriptions",
"chat:edit",
"chat:read",
"moderator:manage:announcements",
"moderator:read:chatters",
"moderator:read:followers",
"user_read",
"user:bot",
"user:manage:whispers",
"user:read:chat",
"whispers:edit",
"whispers:read",
}
func (c *Client) GetAuthorizationURL() string { func (c *Client) GetAuthorizationURL() string {
if c.API == nil { if c.API == nil {
return "twitch-not-configured" return "twitch-not-configured"
} }
return c.API.GetAuthorizationURL(&helix.AuthorizationURLParams{ return c.API.GetAuthorizationURL(&helix.AuthorizationURLParams{
ResponseType: "code", ResponseType: "code",
Scopes: []string{"bits:read channel:read:subscriptions channel:read:redemptions channel:read:polls channel:read:predictions channel:read:hype_train user_read chat:read chat:edit channel:moderate whispers:read whispers:edit moderator:read:chatters moderator:read:followers user:manage:whispers moderator:manage:announcements"}, Scopes: scopes,
}) })
} }
// CheckScopes checks if the user has authorized all required scopes
// Normally this would be the case but between versions strimertul has changed
// the required scopes, and it's possible that some users have not re-authorized
// the application with the new scopes.
func (c *Client) CheckScopes() (bool, error) {
var authResp AuthResponse
if err := c.db.GetJSON(AuthKey, &authResp); err != nil {
return false, err
}
// Sort scopes for comparison
slices.Sort(authResp.Scope)
return slices.Equal(scopes, authResp.Scope), nil
}
func (c *Client) GetUserClient(forceRefresh bool) (*helix.Client, error) { func (c *Client) GetUserClient(forceRefresh bool) (*helix.Client, error) {
var authResp AuthResponse var authResp AuthResponse
err := c.db.GetJSON(AuthKey, &authResp) if err := c.db.GetJSON(AuthKey, &authResp); err != nil {
if err != nil {
return nil, err return nil, err
} }
// Handle token expiration // Handle token expiration
if forceRefresh || time.Now().After(authResp.Time.Add(time.Duration(authResp.ExpiresIn)*time.Second)) { if forceRefresh || time.Now().After(authResp.Time.Add(time.Duration(authResp.ExpiresIn)*time.Second)) {
// Refresh tokens // Refresh tokens
@ -108,7 +147,7 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return return
} }
w.Header().Add("Content-Type", "text/html") w.Header().Add("Content-Type", "text/html")
fmt.Fprintf(w, `<html><body><h2>All done, you can close me now!</h2><script>window.close();</script></body></html>`) _, _ = fmt.Fprintf(w, `<html><body><h2>All done, you can close me now!</h2><script>window.close();</script></body></html>`)
} }
type RefreshResponse struct { type RefreshResponse struct {

View File

@ -14,7 +14,7 @@ var (
) )
func setStdHandle(stdhandle int32, handle syscall.Handle) error { func setStdHandle(stdhandle int32, handle syscall.Handle) error {
r0, _, e1 := syscall.SyscallN(procSetStdHandle.Addr(), 2, uintptr(stdhandle), uintptr(handle), 0) r0, _, e1 := syscall.SyscallN(procSetStdHandle.Addr(), uintptr(stdhandle), uintptr(handle), 0)
if r0 == 0 { if r0 == 0 {
if e1 != 0 { if e1 != 0 {
return error(e1) return error(e1)