マルチアカウント対応
This commit is contained in:
parent
24b419fe60
commit
8a5daa866c
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misshaialert",
|
||||
"version": "1.5.1",
|
||||
"name": "misskey-tools",
|
||||
"version": "2.0.0",
|
||||
"description": "",
|
||||
"main": "built/app.js",
|
||||
"author": "Xeltica",
|
||||
|
@ -1,5 +1,5 @@
|
||||
export default {
|
||||
version: '1.5.1',
|
||||
version: '2.0.0',
|
||||
changelog: [
|
||||
'インスタンスの接続エラーにより後続処理が行えなくなる重大な不具合を修正',
|
||||
'全員分の算出が終わるまで、ランキングを非表示に',
|
||||
|
@ -31,7 +31,14 @@ html
|
||||
|
||||
if token
|
||||
script.
|
||||
localStorage.setItem('token', '#{token}');
|
||||
const token = '#{token}';
|
||||
const previousToken = localStorage.getItem('token');
|
||||
const accounts = JSON.parse(localStorage.getItem('accounts') && '[]');
|
||||
if (previousToken && !accounts.includes(previousToken)) {
|
||||
accounts.push(previousToken);
|
||||
}
|
||||
localStorage.setItem('accounts', JSON.stringify(accounts));
|
||||
localStorage.setItem('token', token);
|
||||
history.replaceState(null, null, '/');
|
||||
|
||||
if error
|
||||
|
67
src/frontend/components/AccountsPage.tsx
Normal file
67
src/frontend/components/AccountsPage.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { LOCALSTORAGE_KEY_ACCOUNTS, LOCALSTORAGE_KEY_TOKEN } from '../const';
|
||||
import { useGetSessionQuery } from '../services/session';
|
||||
import { useSelector } from '../store';
|
||||
import { setAccounts } from '../store/slices/screen';
|
||||
import { LoginForm } from './LoginForm';
|
||||
import { Skeleton } from './Skeleton';
|
||||
|
||||
export const AccountsPage: React.VFC = () => {
|
||||
const {data} = useGetSessionQuery(undefined);
|
||||
const {t} = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {accounts, accountTokens} = useSelector(state => state.screen);
|
||||
|
||||
const switchAccount = (token: string) => {
|
||||
const newAccounts = accountTokens.filter(a => a !== token);
|
||||
newAccounts.push(localStorage.getItem(LOCALSTORAGE_KEY_TOKEN) ?? '');
|
||||
localStorage.setItem(LOCALSTORAGE_KEY_ACCOUNTS, JSON.stringify(newAccounts));
|
||||
localStorage.setItem(LOCALSTORAGE_KEY_TOKEN, token);
|
||||
location.reload();
|
||||
};
|
||||
|
||||
return !data ? (
|
||||
<div className="vstack">
|
||||
<Skeleton />
|
||||
<Skeleton />
|
||||
<Skeleton />
|
||||
</div>
|
||||
) : (
|
||||
<div className="fade vstack">
|
||||
<div className="card">
|
||||
<div className="body">
|
||||
<h1>{t('_accounts.currentAccount')}</h1>
|
||||
<p>@{data.username}@{data.host}</p>
|
||||
</div>
|
||||
</div>
|
||||
<article>
|
||||
<h2>{t('_accounts.switchAccount')}</h2>
|
||||
<div className="menu large fluid mb-2">
|
||||
{
|
||||
accounts.length === accountTokens.length ? (
|
||||
accounts.map(account => (
|
||||
<button className="item fluid" style={{display: 'flex', flexDirection: 'row', alignItems: 'center'}} onClick={() => switchAccount(account.misshaiToken)}>
|
||||
<i className="icon bi bi-chevron-right" />
|
||||
@{account.username}@{account.host}
|
||||
<button className="btn flat text-danger" style={{marginLeft: 'auto'}} onClick={e => {
|
||||
const filteredAccounts = accounts.filter(ac => ac.id !== account.id);
|
||||
dispatch(setAccounts(filteredAccounts));
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<i className="bi bi-trash"/>
|
||||
</button>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="item">...</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<LoginForm />
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -10,7 +10,7 @@ export const LoginForm: React.VFC = () => {
|
||||
<div>
|
||||
<strong>{t('instanceUrl')}</strong>
|
||||
</div>
|
||||
<div className="hgroup">
|
||||
<div className="hgroup login-form">
|
||||
<input
|
||||
className="input-field"
|
||||
type="text"
|
||||
|
@ -1,5 +1,6 @@
|
||||
export const LOCALSTORAGE_KEY_TOKEN = 'token';
|
||||
export const LOCALSTORAGE_KEY_THEME = 'theme';
|
||||
export const LOCALSTORAGE_KEY_LANG = 'lang';
|
||||
export const LOCALSTORAGE_KEY_ACCOUNTS = 'accounts';
|
||||
|
||||
export const API_ENDPOINT = `//${location.host}/api/v1/`;
|
||||
|
@ -59,6 +59,7 @@
|
||||
},
|
||||
"_nav": {
|
||||
"misshai": "ミス廃",
|
||||
"accounts": "アカウント",
|
||||
"settings": "設定"
|
||||
},
|
||||
"_missHai": {
|
||||
@ -73,6 +74,11 @@
|
||||
"order": "順位",
|
||||
"showRanking": "ランキングを見る"
|
||||
},
|
||||
"_accounts": {
|
||||
"currentAccount": "現在ログインしているアカウント",
|
||||
"switchAccount": "アカウント切り替え",
|
||||
"useAnother": "他のアカウントで登録する"
|
||||
},
|
||||
"_developerInfo": {
|
||||
"title": "開発者",
|
||||
"description": "何か困ったことがあったら、以下のアカウントにメッセージを送ってください。"
|
||||
|
@ -1,25 +1,47 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { Header } from '../components/Header';
|
||||
import { MisshaiPage } from '../components/MisshaiPage';
|
||||
import { Tab, TabItem } from '../components/Tab';
|
||||
import { SettingPage } from '../components/SettingPage';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AccountsPage } from '../components/AccountsPage';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { API_ENDPOINT, LOCALSTORAGE_KEY_ACCOUNTS } from '../const';
|
||||
import { IUser } from '../../common/types/user';
|
||||
import { setAccounts } from '../store/slices/screen';
|
||||
|
||||
const getSession = (token: string) => {
|
||||
return fetch(`${API_ENDPOINT}session`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(r => r.json()).then(r => r as IUser);
|
||||
};
|
||||
|
||||
export const IndexSessionPage: React.VFC = () => {
|
||||
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||
const {t, i18n} = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
const accounts = JSON.parse(localStorage.getItem(LOCALSTORAGE_KEY_ACCOUNTS) || '[]') as string[];
|
||||
Promise.all(accounts.map(getSession)).then(a => dispatch(setAccounts(a)));
|
||||
}, [dispatch]);
|
||||
|
||||
const items = useMemo<TabItem[]>(() => ([
|
||||
{ label: t('_nav.misshai') },
|
||||
{ label: t('_nav.accounts') },
|
||||
{ label: t('_nav.settings') },
|
||||
]), [i18n.language]);
|
||||
|
||||
const component = useMemo(() => {
|
||||
switch (selectedTab) {
|
||||
case 0: return <MisshaiPage />;
|
||||
case 1: return <SettingPage/>;
|
||||
case 1: return <AccountsPage />;
|
||||
case 2: return <SettingPage/>;
|
||||
default: return null;
|
||||
}
|
||||
}, [selectedTab]);
|
||||
|
@ -35,7 +35,7 @@ export const IndexWelcomePage: React.VFC = () => {
|
||||
<article>
|
||||
<h3>{t('_welcome.misshaiAlertTitle')}</h3>
|
||||
<p>{t('_welcome.misshaiAlertDescription')}</p>
|
||||
<div className="card ma-2 shadow-2" style={{maxWidth: 320}}>
|
||||
<div className="card ma-2 shadow-2" style={{maxWidth: 360}}>
|
||||
<div className="body">
|
||||
<pre>{example}</pre>
|
||||
</div>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import i18n from 'i18next';
|
||||
import { IUser } from '../../../common/types/user';
|
||||
|
||||
import { LOCALSTORAGE_KEY_LANG, LOCALSTORAGE_KEY_THEME } from '../../const';
|
||||
import { LOCALSTORAGE_KEY_ACCOUNTS, LOCALSTORAGE_KEY_LANG, LOCALSTORAGE_KEY_THEME } from '../../const';
|
||||
import { Theme } from '../../misc/theme';
|
||||
import { Modal } from '../../modal/modal';
|
||||
|
||||
@ -10,6 +11,8 @@ interface ScreenState {
|
||||
modalShown: boolean;
|
||||
theme: Theme;
|
||||
language: string;
|
||||
accounts: IUser[];
|
||||
accountTokens: string[];
|
||||
}
|
||||
|
||||
const initialState: ScreenState = {
|
||||
@ -17,6 +20,8 @@ const initialState: ScreenState = {
|
||||
modalShown: false,
|
||||
theme: localStorage[LOCALSTORAGE_KEY_THEME] ?? 'system',
|
||||
language: localStorage[LOCALSTORAGE_KEY_LANG] ?? i18n.language ?? 'ja_JP',
|
||||
accounts: [],
|
||||
accountTokens: JSON.parse(localStorage.getItem(LOCALSTORAGE_KEY_ACCOUNTS) || '[]') as string[],
|
||||
};
|
||||
|
||||
export const screenSlice = createSlice({
|
||||
@ -40,9 +45,14 @@ export const screenSlice = createSlice({
|
||||
localStorage[LOCALSTORAGE_KEY_LANG] = action.payload;
|
||||
i18n.changeLanguage(action.payload);
|
||||
},
|
||||
setAccounts: (state, action: PayloadAction<ScreenState['accounts']>) => {
|
||||
state.accounts = action.payload;
|
||||
state.accountTokens = action.payload.map(a => a.misshaiToken);
|
||||
localStorage[LOCALSTORAGE_KEY_ACCOUNTS] = JSON.stringify(state.accountTokens);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { showModal, hideModal, changeTheme, changeLang } = screenSlice.actions;
|
||||
export const { showModal, hideModal, changeTheme, changeLang, setAccounts } = screenSlice.actions;
|
||||
|
||||
export default screenSlice.reducer;
|
||||
|
@ -92,3 +92,8 @@ small {
|
||||
padding: calc(var(--margin) / 2);
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.login-form {
|
||||
background: var(--panel);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user