0
0
Fork 0
This commit is contained in:
Xeltica 2021-09-24 02:35:58 +09:00
parent 230e952c84
commit ad366c122e
7 changed files with 68 additions and 42 deletions

View file

@ -4,7 +4,7 @@ html
meta(charset="UTF-8") meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0") meta(name="viewport", content="width=device-width, initial-scale=1.0")
block meta block meta
- const title = 'みす廃アラート' - const title = 'Misskey Tools'
- const desc = '✨Misskey での1日のート数、フォロー数、フォロワー数をカウントし、深夜0時にお知らせする便利サービスです。'; - const desc = '✨Misskey での1日のート数、フォロー数、フォロワー数をカウントし、深夜0時にお知らせする便利サービスです。';
title= title title= title
meta(name='description' content=desc) meta(name='description' content=desc)

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from 'react'; import React from 'react';
import { BrowserRouter, Link, Route, Switch, useLocation } from 'react-router-dom'; import { BrowserRouter, Link, Route, Switch, useLocation } from 'react-router-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -7,45 +7,17 @@ import { IndexPage } from './pages';
import { RankingPage } from './pages/ranking'; import { RankingPage } from './pages/ranking';
import { Header } from './components/Header'; import { Header } from './components/Header';
import { TermPage } from './pages/term'; import { TermPage } from './pages/term';
import { store, useSelector } from './store'; import { store } from './store';
import { ModalComponent } from './Modal'; import { ModalComponent } from './Modal';
import { useTheme } from './misc/theme';
import 'xeltica-ui/dist/css/xeltica-ui.min.css'; import 'xeltica-ui/dist/css/xeltica-ui.min.css';
import './style.scss'; import './style.scss';
import { ActualTheme } from './misc/theme';
const AppInner : React.VFC = () => { const AppInner : React.VFC = () => {
const $location = useLocation(); const $location = useLocation();
const theme = useSelector(state => state.screen.theme); useTheme();
const [ osTheme, setOsTheme ] = useState<ActualTheme>('dark');
const applyTheme = useCallback(() => {
const actualTheme = theme === 'system' ? osTheme : theme;
if (actualTheme === 'dark') {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
}, [theme, osTheme]);
// テーマ変更に追従する
useEffect(() => {
applyTheme();
}, [theme, osTheme]);
// システムテーマ変更に追従する
useEffect(() => {
const q = window.matchMedia('(prefers-color-scheme: dark)');
setOsTheme(q.matches ? 'dark' : 'light');
const listener = () => setOsTheme(q.matches ? 'dark' : 'light');
q.addEventListener('change', listener);
return () => {
q.removeEventListener('change', listener);
};
}, [osTheme, setOsTheme]);
const {t} = useTranslation(); const {t} = useTranslation();

View file

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { welcomeMessage } from '../misc/welcome-message';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View file

@ -0,0 +1,14 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Ranking } from './Ranking';
export const RankingPage: React.VFC = () => {
const [limit, setLimit] = useState<number | undefined>(10);
const {t} = useTranslation();
return (
<div className="fade">
<Ranking limit={limit} />
{limit && <button className="btn link" onClick={() => setLimit(undefined)}>{t('_missHai.showAll')}</button>}
</div>
);
};

View file

@ -50,6 +50,7 @@ export const SettingPage: React.VFC = () => {
]; ];
const updateSetting = useCallback((obj: SettingDraftType) => { const updateSetting = useCallback((obj: SettingDraftType) => {
const previousDraft = draft;
dispatchDraft(obj); dispatchDraft(obj);
return fetch(`${API_ENDPOINT}session`, { return fetch(`${API_ENDPOINT}session`, {
method: 'PUT', method: 'PUT',
@ -58,8 +59,16 @@ export const SettingPage: React.VFC = () => {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify(obj), body: JSON.stringify(obj),
})
.catch(e => {
dispatch(showModal({
type: 'dialog',
icon: 'error',
message: 'エラー'
}));
dispatchDraft(previousDraft);
}); });
}, []); }, [draft]);
const updateSettingWithDialog = useCallback((obj: SettingDraftType) => { const updateSettingWithDialog = useCallback((obj: SettingDraftType) => {
updateSetting(obj) updateSetting(obj)
@ -67,10 +76,7 @@ export const SettingPage: React.VFC = () => {
type: 'dialog', type: 'dialog',
icon: 'info', icon: 'info',
message: '保存しました。' message: '保存しました。'
}))) })));
.catch(e => {
alert(e.message);
});
}, [updateSetting]); }, [updateSetting]);
useEffect(() => { useEffect(() => {

View file

@ -1,3 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
import { useSelector } from '../store';
export const actualThemes = [ export const actualThemes = [
'light', 'light',
'dark', 'dark',
@ -11,3 +14,35 @@ export const themes = [
export type Theme = typeof themes[number]; export type Theme = typeof themes[number];
export type ActualTheme = typeof actualThemes[number]; export type ActualTheme = typeof actualThemes[number];
export const useTheme = () => {
const theme = useSelector(state => state.screen.theme);
const [ osTheme, setOsTheme ] = useState<ActualTheme>('dark');
const applyTheme = useCallback(() => {
const actualTheme = theme === 'system' ? osTheme : theme;
if (actualTheme === 'dark') {
document.body.classList.add('dark');
} else {
document.body.classList.remove('dark');
}
}, [theme, osTheme]);
// テーマ変更に追従する
useEffect(() => {
applyTheme();
}, [theme, osTheme]);
// システムテーマ変更に追従する
useEffect(() => {
const q = window.matchMedia('(prefers-color-scheme: dark)');
setOsTheme(q.matches ? 'dark' : 'light');
const listener = () => setOsTheme(q.matches ? 'dark' : 'light');
q.addEventListener('change', listener);
return () => {
q.removeEventListener('change', listener);
};
}, [osTheme, setOsTheme]);
};

View file

@ -2,10 +2,10 @@ import React, { useMemo, useState } from 'react';
import { Header } from '../components/Header'; import { Header } from '../components/Header';
import { SessionDataPage } from '../components/SessionDataPage'; import { SessionDataPage } from '../components/SessionDataPage';
import { Ranking } from '../components/Ranking';
import { Tab, TabItem } from '../components/Tab'; import { Tab, TabItem } from '../components/Tab';
import { SettingPage } from '../components/SettingPage'; import { SettingPage } from '../components/SettingPage';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { RankingPage } from '../components/RankingPage';
export const IndexSessionPage: React.VFC = () => { export const IndexSessionPage: React.VFC = () => {
const [selectedTab, setSelectedTab] = useState<number>(0); const [selectedTab, setSelectedTab] = useState<number>(0);
@ -20,7 +20,7 @@ export const IndexSessionPage: React.VFC = () => {
const component = useMemo(() => { const component = useMemo(() => {
switch (selectedTab) { switch (selectedTab) {
case 0: return <SessionDataPage />; case 0: return <SessionDataPage />;
case 1: return <Ranking limit={10}/>; case 1: return <RankingPage/>;
case 2: return <SettingPage/>; case 2: return <SettingPage/>;
default: return null; default: return null;
} }