From 73adc2130fdbff7e1acb6cbaaba78f8aa8f6bf29 Mon Sep 17 00:00:00 2001 From: xeltica Date: Fri, 8 Oct 2021 12:35:19 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=81=AA=E3=81=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/components/MisshaiPage.tsx | 322 ++++++++++++++++++++ src/frontend/components/SessionDataPage.tsx | 77 ----- src/frontend/components/SettingPage.tsx | 228 +------------- src/frontend/langs/ja-JP.json | 7 +- src/frontend/pages/index.session.tsx | 11 +- 5 files changed, 330 insertions(+), 315 deletions(-) create mode 100644 src/frontend/components/MisshaiPage.tsx delete mode 100644 src/frontend/components/SessionDataPage.tsx diff --git a/src/frontend/components/MisshaiPage.tsx b/src/frontend/components/MisshaiPage.tsx new file mode 100644 index 0000000..e4c28d7 --- /dev/null +++ b/src/frontend/components/MisshaiPage.tsx @@ -0,0 +1,322 @@ +import insertTextAtCursor from 'insert-text-at-cursor'; +import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; +import { alertModes } from '../../common/types/alert-mode'; +import { IUser } from '../../common/types/user'; +import { Visibility } from '../../common/types/visibility'; +import { API_ENDPOINT, LOCALSTORAGE_KEY_TOKEN } from '../const'; +import { useGetScoreQuery, useGetSessionQuery } from '../services/session'; +import { showModal } from '../store/slices/screen'; +import { Card } from './Card'; +import { Ranking } from './Ranking'; +import { Skeleton } from './Skeleton'; + +const variables = [ + 'notesCount', + 'followingCount', + 'followersCount', + 'notesDelta', + 'followingDelta', + 'followersDelta', + 'url', + 'username', + 'host', + 'rating', +] as const; + +type SettingDraftType = Partial>; + +type DraftReducer = React.Reducer>; + +export const MisshaiPage: React.VFC = () => { + const dispatch = useDispatch(); + const [limit, setLimit] = useState(10); + + const session = useGetSessionQuery(undefined); + const data = session.data; + const score = useGetScoreQuery(undefined); + const {t} = useTranslation(); + + const [draft, dispatchDraft] = useReducer((state, action) => { + return { ...state, ...action }; + }, { + alertMode: data?.alertMode ?? 'note', + visibility: data?.visibility ?? 'public', + localOnly: data?.localOnly ?? false, + remoteFollowersOnly: data?.remoteFollowersOnly ?? false, + template: data?.template ?? null, + }); + + const templateTextarea = useRef(null); + + const availableVisibilities: Visibility[] = [ + 'public', + 'home', + 'followers' + ]; + + const updateSetting = useCallback((obj: SettingDraftType) => { + const previousDraft = draft; + dispatchDraft(obj); + return fetch(`${API_ENDPOINT}session`, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${localStorage[LOCALSTORAGE_KEY_TOKEN]}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(obj), + }) + .catch(e => { + dispatch(showModal({ + type: 'dialog', + icon: 'error', + message: 'エラー' + })); + dispatchDraft(previousDraft); + }); + }, [draft]); + + const updateSettingWithDialog = useCallback((obj: SettingDraftType) => { + updateSetting(obj) + .then(() => dispatch(showModal({ + type: 'dialog', + icon: 'info', + message: '保存しました。' + }))); + }, [updateSetting]); + + useEffect(() => { + if (data) { + dispatchDraft({ + alertMode: data.alertMode, + visibility: data.visibility, + localOnly: data.localOnly, + remoteFollowersOnly: data.remoteFollowersOnly, + template: data.template, + }); + } + }, [data]); + + const onClickInsertVariables = useCallback((e) => { + dispatch(showModal({ + type: 'menu', + screenX: e.clientX, + screenY: e.clientY, + items: variables.map(key => ({ + name: t('_template._variables.' + key), + onClick: () => { + if (templateTextarea.current) { + insertTextAtCursor(templateTextarea.current, `{${key}}`); + } + }, + })), + })); + }, [dispatch, t, variables, templateTextarea.current]); + + const onClickInsertVariablesHelp = useCallback(() => { + dispatch(showModal({ + type: 'dialog', + icon: 'info', + message: t('_template.insertVariablesHelp'), + })); + }, [dispatch, t]); + + const onClickSendAlert = useCallback(() => { + dispatch(showModal({ + type: 'dialog', + title: t('_sendTest.title'), + message: t('_sendTest.message'), + icon: 'question', + buttons: [ + { + text: t('_sendTest.yes'), + style: 'primary', + }, + { + text: t('_sendTest.no'), + }, + ], + onSelect(i) { + if (i === 0) { + fetch(`${API_ENDPOINT}session/alert`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${localStorage[LOCALSTORAGE_KEY_TOKEN]}`, + }, + }).then(() => { + dispatch(showModal({ + type: 'dialog', + message: t('_sendTest.success'), + icon: 'info', + })); + }).catch((e) => { + console.error(e); + dispatch(showModal({ + type: 'dialog', + message: t('_sendTest.failure'), + icon: 'error', + })); + }); + } + }, + })); + }, [dispatch, t]); + + /** + * Session APIのエラーハンドリング + * このAPIがエラーを返した = トークンが無効 なのでトークンを削除してログアウトする + */ + useEffect(() => { + if (session.error) { + console.error(session.error); + localStorage.removeItem(LOCALSTORAGE_KEY_TOKEN); + location.reload(); + } + }, [session.error]); + + const defaultTemplate = t('_template.default'); + + return session.isLoading || score.isLoading ? ( +
+ + + + +
+ ) : ( +
+ {session.data && ( +
+

{t('welcomeBack', {acct: `@${session.data.username}@${session.data.host}`})}

+

+ + {t('_missHai.rating')}{': '} + + {session.data.rating} +

+
+ )} + {score.data && ( + <> +
+

{t('_missHai.data')}

+ + + + + + + + + + + + + + + + + + + + + + + + + +
{t('_missHai.dataScore')}{t('_missHai.dataDelta')}
{t('notes')}{score.data.notesCount}{score.data.notesDelta}
{t('following')}{score.data.followingCount}{score.data.followingDelta}
{t('followers')}{score.data.followersCount}{score.data.followersDelta}
+
+
+

{t('_missHai.ranking')}

+ + {limit && } +
+
+ +

{t('alertMode')}

+
+ { + alertModes.map((mode) => ( + + )) + } +
+ + { draft.alertMode === 'notification' && ( +
+ + {t('_alertMode.notificationWarning')} +
+ )} + { draft.alertMode === 'note' && ( + <> +

{t('visibility')}

+
+ { + availableVisibilities.map((visibility) => ( + + )) + } +
+ + + )} +
+ +

{t('template')}

+

{t('_template.description')}

+
+ + +
+