diff --git a/src/components/AckButton.tsx b/src/components/AckButton.tsx index 32756916..a0ff4c55 100644 --- a/src/components/AckButton.tsx +++ b/src/components/AckButton.tsx @@ -36,7 +36,8 @@ export const AckButton = (props: AckButtonProps): JSX.Element | null => { onMouseLeave={() => { setIsHovered(false) }} - onClick={() => { + onClick={(e) => { + e.stopPropagation() if (myAck) { props.user.UnAck().then(() => { forceUpdate() diff --git a/src/components/AckList.tsx b/src/components/AckList.tsx index 7a3bd782..75a040d6 100644 --- a/src/components/AckList.tsx +++ b/src/components/AckList.tsx @@ -1,8 +1,8 @@ import { type User } from '@concurrent-world/client' import { useEffect, useState } from 'react' -import { Box, Chip, Link, Tab, Tabs, Typography } from '@mui/material' +import { Box, Chip, Tab, Tabs, Typography } from '@mui/material' import { CCAvatar } from './ui/CCAvatar' -import { Link as RouterLink } from 'react-router-dom' +import { Link as NavLink } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { useClient } from '../context/ClientContext' import { AckButton } from './AckButton' @@ -78,13 +78,21 @@ export const AckList = (props: AckListProps): JSX.Element => { gap: 1, textDecoration: 'none' }} - component={RouterLink} - to={`/${user.ccid}`} - onClick={props.onNavigated} > - - {user.profile?.username} + + {user.profile?.username} {client.ackers.find((ack) => ack.ccid === user.ccid) && ( { <> { @{entity.id}@{client.host} - + { diff --git a/src/components/Settings/General.tsx b/src/components/Settings/General.tsx index 70d58cc4..85766e94 100644 --- a/src/components/Settings/General.tsx +++ b/src/components/Settings/General.tsx @@ -6,11 +6,11 @@ import { Box, Button, Checkbox, - Divider, FormControlLabel, FormGroup, MenuItem, Select, + Slider, Switch, Typography } from '@mui/material' @@ -22,6 +22,9 @@ import { IssueJWT, Schemas } from '@concurrent-world/client' import { useTranslation } from 'react-i18next' import { type NotificationSubscription } from '../../model' +import TextIncreaseIcon from '@mui/icons-material/TextIncrease' +import TextDecreaseIcon from '@mui/icons-material/TextDecrease' + export const GeneralSettings = (): JSX.Element => { const { client } = useClient() const [invitationCode, setInvitationCode] = useState('') @@ -32,6 +35,7 @@ export const GeneralSettings = (): JSX.Element => { const [enableConcord, setEnableConcord] = usePreference('enableConcord') const [autoSwitchMediaPostType, setAutoSwitchMediaPostType] = usePreference('autoSwitchMediaPostType') const [tutorialCompleted, setTutorialCompleted] = usePreference('tutorialCompleted') + const [baseFontSize, setBaseFontSize] = usePreference('baseFontSize') const tags = client?.user?.tag ? client.user.tag.split(',') : [] const { enqueueSnackbar } = useSnackbar() @@ -72,7 +76,7 @@ export const GeneralSettings = (): JSX.Element => { sx={{ display: 'flex', flexDirection: 'column', - gap: 1 + gap: 2 }} > @@ -83,6 +87,9 @@ export const GeneralSettings = (): JSX.Element => { i18n.changeLanguage(e.target.value) setCurrentLanguage(e.target.value) }} + sx={{ + marginLeft: 2 + }} > English 日本語 @@ -94,7 +101,11 @@ export const GeneralSettings = (): JSX.Element => { {t('basic')} - + { {t('showTutorial')} )} - - {t('notification.title')} - - {notification ? ( - <> - - { - if (e.target.checked) { - setSchemas((prev) => [...prev, Schemas.replyAssociation]) - } else { - setSchemas((prev) => prev.filter((s) => s !== Schemas.replyAssociation)) - } - }} - /> - } - label={t('notification.reply')} - /> - { - if (e.target.checked) { - setSchemas((prev) => [...prev, Schemas.mentionAssociation]) - } else { - setSchemas((prev) => prev.filter((s) => s !== Schemas.mentionAssociation)) - } - }} - /> - } - label={t('notification.mention')} - /> - { - if (e.target.checked) { - setSchemas((prev) => [...prev, Schemas.readAccessRequestAssociation]) - } else { - setSchemas((prev) => - prev.filter((s) => s !== Schemas.readAccessRequestAssociation) - ) - } - }} - /> - } - label={t('notification.viewerRequest')} - /> - { - if (e.target.checked) { - setSchemas((prev) => [...prev, Schemas.likeAssociation]) - } else { - setSchemas((prev) => prev.filter((s) => s !== Schemas.likeAssociation)) - } - }} - /> - } - label={t('notification.fav')} - /> - { - if (e.target.checked) { - setSchemas((prev) => [...prev, Schemas.reactionAssociation]) - } else { - setSchemas((prev) => prev.filter((s) => s !== Schemas.reactionAssociation)) - } - }} - /> - } - label={t('notification.reaction')} - /> - { - if (e.target.checked) { - setSchemas((prev) => [...prev, Schemas.rerouteAssociation]) - } else { - setSchemas((prev) => prev.filter((s) => s !== Schemas.rerouteAssociation)) - } - }} - /> - } - label={t('notification.reroute')} - /> - - + {'文字サイズ'} + + - + { + if (e.target.checked) { + setSchemas((prev) => [...prev, Schemas.replyAssociation]) + } else { + setSchemas((prev) => prev.filter((s) => s !== Schemas.replyAssociation)) + } + }} + /> + } + label={t('notification.reply')} + /> + { + if (e.target.checked) { + setSchemas((prev) => [...prev, Schemas.mentionAssociation]) + } else { + setSchemas((prev) => + prev.filter((s) => s !== Schemas.mentionAssociation) + ) + } + }} + /> + } + label={t('notification.mention')} + /> + { + if (e.target.checked) { + setSchemas((prev) => [...prev, Schemas.readAccessRequestAssociation]) + } else { + setSchemas((prev) => + prev.filter((s) => s !== Schemas.readAccessRequestAssociation) + ) + } + }} + /> + } + label={t('notification.viewerRequest')} + /> + { + if (e.target.checked) { + setSchemas((prev) => [...prev, Schemas.likeAssociation]) + } else { + setSchemas((prev) => prev.filter((s) => s !== Schemas.likeAssociation)) + } + }} + /> + } + label={t('notification.fav')} + /> + { + if (e.target.checked) { + setSchemas((prev) => [...prev, Schemas.reactionAssociation]) + } else { + setSchemas((prev) => + prev.filter((s) => s !== Schemas.reactionAssociation) + ) + } + }} + /> + } + label={t('notification.reaction')} + /> + { + if (e.target.checked) { + setSchemas((prev) => [...prev, Schemas.rerouteAssociation]) + } else { + setSchemas((prev) => + prev.filter((s) => s !== Schemas.rerouteAssociation) + ) + } + }} + /> + } + label={t('notification.reroute')} + /> + + + + + + + + ) : ( + <> - - - ) : ( - <> - - - )} + + )} + {!enableConcord && ( diff --git a/src/context/PreferenceContext.tsx b/src/context/PreferenceContext.tsx index 20df13bf..18b337e8 100644 --- a/src/context/PreferenceContext.tsx +++ b/src/context/PreferenceContext.tsx @@ -28,6 +28,7 @@ export interface Preference { autoSwitchMediaPostType: boolean tutorialProgress: number tutorialCompleted: boolean + baseFontSize: number } export const defaultPreference: Preference = { @@ -57,7 +58,8 @@ export const defaultPreference: Preference = { enableConcord: false, autoSwitchMediaPostType: true, tutorialProgress: 0, - tutorialCompleted: false + tutorialCompleted: false, + baseFontSize: 16 } interface PreferenceState { diff --git a/src/context/Theme.tsx b/src/context/Theme.tsx index 560295a8..a00fbfd3 100644 --- a/src/context/Theme.tsx +++ b/src/context/Theme.tsx @@ -11,10 +11,13 @@ interface ConcrntThemeProps { export const ConcrntThemeProvider = (props: ConcrntThemeProps): JSX.Element => { const [themeName] = usePreference('themeName') const [customThemes] = usePreference('customThemes') + const [baseFontSize] = usePreference('baseFontSize') const [theme, setTheme] = useState(loadConcurrentTheme(themeName, customThemes)) useEffect(() => { - const newtheme = loadConcurrentTheme(themeName, customThemes) + const newtheme = loadConcurrentTheme(themeName, customThemes, { fontSize: baseFontSize }) + newtheme.typography.fontSize = baseFontSize + console.log('fontsize changed:', baseFontSize) localStorage.setItem('theme', JSON.stringify(newtheme)) setTheme(newtheme) let themeColorMetaTag: HTMLMetaElement = document.querySelector('meta[name="theme-color"]') as HTMLMetaElement @@ -24,7 +27,7 @@ export const ConcrntThemeProvider = (props: ConcrntThemeProps): JSX.Element => { document.head.appendChild(themeColorMetaTag) } themeColorMetaTag.content = newtheme.palette.background.default - }, [themeName, customThemes]) + }, [themeName, customThemes, baseFontSize]) return ( diff --git a/src/themes.ts b/src/themes.ts index 148482e9..ecee964a 100644 --- a/src/themes.ts +++ b/src/themes.ts @@ -246,7 +246,7 @@ export const ConcurrentDefaultTheme = { } }, typography: { - fontSize: 14, + fontSize: 16, body1: { fontSize: '1rem' }, @@ -287,7 +287,7 @@ export const ConcurrentDefaultTheme = { breakpoints: { values: { xs: 0, - sm: 450, + sm: 550, md: 960, lg: 1280, xl: 1920 @@ -316,7 +316,8 @@ export const ConcurrentDefaultTheme = { }, html: { overscrollBehaviorY: 'none', - userSelect: 'none' + userSelect: 'none', + fontSize: '16px' }, body: { overflowX: 'hidden', @@ -356,7 +357,7 @@ export function deepMerge(target: Record, source: Record { +export const createConcurrentThemeFromObject = (base: any, options?: any): ConcurrentTheme => { if (base.palette.text !== undefined) { if (base.palette.text.hint === undefined) base.palette.text.hint = alpha(base.palette.text.primary, 0.5) if (base.palette.text.disabled === undefined) base.palette.text.disabled = alpha(base.palette.text.primary, 0.5) @@ -374,14 +375,20 @@ export const createConcurrentThemeFromObject = (base: any): ConcurrentTheme => { } } - const theme: ConcurrentTheme = deepMerge(ConcurrentDefaultTheme, base) + const defaultTheme = ConcurrentDefaultTheme + if (options?.fontSize) { + defaultTheme.components.MuiCssBaseline.styleOverrides.html.fontSize = `${options.fontSize}px` + } + + const theme: ConcurrentTheme = deepMerge(defaultTheme, base) return createTheme(theme) as ConcurrentTheme } export const loadConcurrentTheme = ( name: string, - customs: Record> = {} + customs: Record> = {}, + options?: { fontSize?: number } ): ConcurrentTheme => { const base = customs[name] ?? Themes[name] ?? Themes.blue - return createConcurrentThemeFromObject(base) + return createConcurrentThemeFromObject(base, options) }