Merge pull request #4816 from h3poteto/feat/account-added
Start/stop streamings when an account is added/deleted
This commit is contained in:
commit
fa573da337
|
@ -1,4 +1,4 @@
|
|||
import { localeType } from '@/utils/i18n'
|
||||
import { localeType } from '@/provider/i18n'
|
||||
import { Dialog, DialogBody, DialogHeader, Input, Option, Select, Typography } from '@material-tailwind/react'
|
||||
import { ChangeEvent, useEffect, useState } from 'react'
|
||||
import { FormattedMessage } from 'react-intl'
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
FaXmark
|
||||
} from 'react-icons/fa6'
|
||||
import { Entity, MegalodonInterface } from 'megalodon'
|
||||
import { useToast } from '@/utils/toast'
|
||||
import { useToast } from '@/provider/toast'
|
||||
import Picker from '@emoji-mart/react'
|
||||
import { data } from '@/utils/emojiData'
|
||||
import EditMedia from './EditMedia'
|
||||
|
|
|
@ -5,9 +5,7 @@ import NewAccount from '@/components/accounts/New'
|
|||
import Settings from '@/components/Settings'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FormattedMessage, useIntl } from 'react-intl'
|
||||
import generateNotification from '@/utils/notification'
|
||||
import generator, { Entity, WebSocketInterface } from 'megalodon'
|
||||
import { Context } from '@/utils/i18n'
|
||||
import { Context } from '@/provider/i18n'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import {
|
||||
Avatar,
|
||||
|
@ -21,8 +19,8 @@ import {
|
|||
PopoverHandler
|
||||
} from '@material-tailwind/react'
|
||||
import Thirdparty from '../Thirdparty'
|
||||
import { useUnreads } from '@/utils/unreads'
|
||||
import { unreadCount } from '@/entities/marker'
|
||||
import { useUnreads } from '@/provider/unreads'
|
||||
import { useAccounts } from '@/provider/accounts'
|
||||
|
||||
type LayoutProps = {
|
||||
children: React.ReactNode
|
||||
|
@ -39,8 +37,8 @@ export default function Layout({ children }: LayoutProps) {
|
|||
const { switchLang } = useContext(Context)
|
||||
const router = useRouter()
|
||||
const { formatMessage } = useIntl()
|
||||
const streamings = useRef<Array<WebSocketInterface>>([])
|
||||
const { unreads, setUnreads } = useUnreads()
|
||||
const { unreads } = useUnreads()
|
||||
const { addAccount, removeAccount, removeAll } = useAccounts()
|
||||
|
||||
for (let i = 1; i < 9; i++) {
|
||||
useHotkeys(`mod+${i}`, () => {
|
||||
|
@ -60,44 +58,13 @@ export default function Layout({ children }: LayoutProps) {
|
|||
setOpenNewModal(true)
|
||||
}
|
||||
acct.forEach(async account => {
|
||||
// Start user streaming for notification
|
||||
const client = generator(account.sns, account.url, account.access_token, 'Whalebird')
|
||||
const instance = await client.getInstance()
|
||||
const notifications = (await client.getNotifications()).data
|
||||
const res = await client.getMarkers(['notifications'])
|
||||
const marker = res.data as Entity.Marker
|
||||
if (marker.notifications) {
|
||||
const count = unreadCount(marker.notifications, notifications)
|
||||
setUnreads(current =>
|
||||
Object.assign({}, current, {
|
||||
[account.id?.toString()]: count
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const ws = generator(account.sns, instance.data.urls.streaming_api, account.access_token, 'Whalebird')
|
||||
const socket = ws.userSocket()
|
||||
streamings.current = [...streamings.current, socket]
|
||||
socket.on('connect', () => {
|
||||
console.log(`connect to user streaming for ${account.domain}`)
|
||||
})
|
||||
socket.on('notification', (notification: Entity.Notification) => {
|
||||
const [title, body] = generateNotification(notification, formatMessage)
|
||||
if (title.length > 0) {
|
||||
new window.Notification(title, { body: body })
|
||||
}
|
||||
})
|
||||
addAccount(account)
|
||||
})
|
||||
}
|
||||
fn()
|
||||
|
||||
return () => {
|
||||
streamings.current.forEach(streaming => {
|
||||
streaming.removeAllListeners()
|
||||
streaming.stop()
|
||||
})
|
||||
streamings.current = []
|
||||
console.log('close user streamings')
|
||||
removeAll()
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
@ -120,8 +87,9 @@ export default function Layout({ children }: LayoutProps) {
|
|||
document.getElementById(`${id}`).click()
|
||||
}
|
||||
|
||||
const removeAccount = async (id: number) => {
|
||||
await db.accounts.delete(id)
|
||||
const deleteAccount = async (account: Account) => {
|
||||
removeAccount(account)
|
||||
await db.accounts.delete(account.id)
|
||||
const acct = await db.accounts.toArray()
|
||||
setAccounts(acct)
|
||||
if (acct.length === 0) {
|
||||
|
@ -164,7 +132,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||
</PopoverHandler>
|
||||
<PopoverContent>
|
||||
<List className="py-2 px-0">
|
||||
<ListItem onClick={() => removeAccount(account.id)} className="py-2 px-4 rounded-none">
|
||||
<ListItem onClick={() => deleteAccount(account)} className="py-2 px-4 rounded-none">
|
||||
<ListItemPrefix>
|
||||
<FaTrash />
|
||||
</ListItemPrefix>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useHotkeys } from 'react-hotkeys-hook'
|
|||
import { FaBell, FaGlobe, FaHouse, FaList, FaUsers } from 'react-icons/fa6'
|
||||
import { useIntl } from 'react-intl'
|
||||
import Jump from '../Jump'
|
||||
import { useUnreads } from '@/utils/unreads'
|
||||
import { useUnreads } from '@/provider/unreads'
|
||||
|
||||
type LayoutProps = {
|
||||
children: React.ReactNode
|
||||
|
|
|
@ -9,8 +9,8 @@ import { useRouter } from 'next/router'
|
|||
import Detail from '../detail/Detail'
|
||||
import { Marker, unreadCount } from '@/entities/marker'
|
||||
import { FaCheck } from 'react-icons/fa6'
|
||||
import { useToast } from '@/utils/toast'
|
||||
import { useUnreads } from '@/utils/unreads'
|
||||
import { useToast } from '@/provider/toast'
|
||||
import { useUnreads } from '@/provider/unreads'
|
||||
|
||||
const TIMELINE_STATUSES_COUNT = 30
|
||||
const TIMELINE_MAX_STATUSES = 2147483647
|
||||
|
|
|
@ -113,7 +113,11 @@ const actionIcon = (notification: Entity.Notification) => {
|
|||
return <FaPenToSquare className="text-blue-600 w-4 mr-2 ml-auto" />
|
||||
}
|
||||
case 'emoji_reaction': {
|
||||
return <span dangerouslySetInnerHTML={{ __html: notification.emoji }} />
|
||||
return (
|
||||
<div className="w-5 mr-2 ml-auto">
|
||||
<span dangerouslySetInnerHTML={{ __html: notification.emoji }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
default:
|
||||
return null
|
||||
|
@ -136,7 +140,7 @@ const actionId = (notification: Entity.Notification) => {
|
|||
return 'notification.status.body'
|
||||
case 'update':
|
||||
return 'notification.update.body'
|
||||
case 'emoji_reqction':
|
||||
case 'emoji_reaction':
|
||||
return 'notification.emoji_reaction.body'
|
||||
|
||||
default:
|
||||
|
|
|
@ -2,10 +2,11 @@ import type { AppProps } from 'next/app'
|
|||
import '../app.css'
|
||||
import AccountLayout from '@/components/layouts/account'
|
||||
import TimelineLayout from '@/components/layouts/timelines'
|
||||
import { IntlProviderWrapper } from '@/utils/i18n'
|
||||
import { IntlProviderWrapper } from '@/provider/i18n'
|
||||
import { ThemeProvider } from '@material-tailwind/react'
|
||||
import { ToastProvider } from '@/utils/toast'
|
||||
import { UnreadsProvider } from '@/utils/unreads'
|
||||
import { ToastProvider } from '@/provider/toast'
|
||||
import { UnreadsProvider } from '@/provider/unreads'
|
||||
import { AccountsProvider } from '@/provider/accounts'
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
const customTheme = {
|
||||
|
@ -106,11 +107,13 @@ export default function MyApp({ Component, pageProps }: AppProps) {
|
|||
<IntlProviderWrapper>
|
||||
<ToastProvider>
|
||||
<UnreadsProvider>
|
||||
<AccountLayout>
|
||||
<TimelineLayout>
|
||||
<Component {...pageProps} />
|
||||
</TimelineLayout>
|
||||
</AccountLayout>
|
||||
<AccountsProvider>
|
||||
<AccountLayout>
|
||||
<TimelineLayout>
|
||||
<Component {...pageProps} />
|
||||
</TimelineLayout>
|
||||
</AccountLayout>
|
||||
</AccountsProvider>
|
||||
</UnreadsProvider>
|
||||
</ToastProvider>
|
||||
</IntlProviderWrapper>
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import { Account } from '@/db'
|
||||
import generateNotification from '@/utils/notification'
|
||||
import { unreadCount } from '@/entities/marker'
|
||||
import generator, { Entity, WebSocketInterface } from 'megalodon'
|
||||
import { createContext, useContext, useRef, useState } from 'react'
|
||||
import { useUnreads } from './unreads'
|
||||
import { useIntl } from 'react-intl'
|
||||
|
||||
type Context = {
|
||||
addAccount: (account: Account) => void
|
||||
removeAccount: (account: Account) => void
|
||||
removeAll: () => void
|
||||
}
|
||||
|
||||
const AccountsContext = createContext<Context>({
|
||||
addAccount: (_: Account) => {},
|
||||
removeAccount: (_: Account) => {},
|
||||
removeAll: () => {}
|
||||
})
|
||||
|
||||
AccountsContext.displayName = 'AccountsContext'
|
||||
|
||||
export const useAccounts = () => {
|
||||
return useContext(AccountsContext)
|
||||
}
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const AccountsProvider: React.FC<Props> = ({ children }) => {
|
||||
const [_accounts, setAccounts] = useState<Array<Account>>([])
|
||||
const streamings = useRef<{ [key: string]: WebSocketInterface }>({})
|
||||
const { setUnreads } = useUnreads()
|
||||
const { formatMessage } = useIntl()
|
||||
|
||||
const addAccount = (account: Account) => {
|
||||
setAccounts(current => [...current, account])
|
||||
startStreaming(account)
|
||||
}
|
||||
|
||||
const removeAccount = (account: Account) => {
|
||||
setAccounts(current => current.filter(a => a.id !== account.id))
|
||||
if (streamings.current[account.id]) {
|
||||
streamings.current[account.id].removeAllListeners()
|
||||
streamings.current[account.id].stop()
|
||||
console.log(`close user streaming for ${account.domain}`)
|
||||
}
|
||||
}
|
||||
|
||||
const removeAll = () => {
|
||||
setAccounts([])
|
||||
Object.keys(streamings.current).map(key => {
|
||||
streamings.current[key].removeAllListeners()
|
||||
streamings.current[key].stop()
|
||||
})
|
||||
console.log('close all user streamings')
|
||||
streamings.current = {}
|
||||
}
|
||||
|
||||
const startStreaming = async (account: Account) => {
|
||||
if (!account.id) return
|
||||
// Start user streaming for notification
|
||||
const client = generator(account.sns, account.url, account.access_token, 'Whalebird')
|
||||
const instance = await client.getInstance()
|
||||
const notifications = (await client.getNotifications()).data
|
||||
const res = await client.getMarkers(['notifications'])
|
||||
const marker = res.data as Entity.Marker
|
||||
if (marker.notifications) {
|
||||
const count = unreadCount(marker.notifications, notifications)
|
||||
setUnreads(current =>
|
||||
Object.assign({}, current, {
|
||||
[account.id?.toString()]: count
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const ws = generator(account.sns, instance.data.urls.streaming_api, account.access_token, 'Whalebird')
|
||||
const socket = ws.userSocket()
|
||||
streamings.current = Object.assign({}, streamings.current, {
|
||||
[account.id]: socket
|
||||
})
|
||||
socket.on('connect', () => {
|
||||
console.log(`connect to user streaming for ${account.domain}`)
|
||||
})
|
||||
socket.on('notification', (notification: Entity.Notification) => {
|
||||
const [title, body] = generateNotification(notification, formatMessage)
|
||||
if (title.length > 0) {
|
||||
new window.Notification(title, { body: body })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return <AccountsContext.Provider value={{ addAccount, removeAccount, removeAll }}>{children}</AccountsContext.Provider>
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import en from '../../locales/en/translation.json'
|
||||
import ja from '../../locales/ja/translation.json'
|
||||
import { flattenMessages } from './flattenMessage'
|
||||
import { flattenMessages } from '../utils/flattenMessage'
|
||||
import { createContext, useState } from 'react'
|
||||
import { IntlProvider } from 'react-intl'
|
||||
|
Loading…
Reference in New Issue