refs #4850 Add next/prev button on image

This commit is contained in:
AkiraFukushima 2024-03-09 15:00:27 +09:00
parent cb80754681
commit 5b285220ea
No known key found for this signature in database
GPG Key ID: B6E51BAC4DE1A957
14 changed files with 131 additions and 35 deletions

View File

@ -1,25 +1,77 @@
import { Dialog, DialogBody } from '@material-tailwind/react'
import { Button, Dialog, DialogBody } from '@material-tailwind/react'
import { Entity } from 'megalodon'
import { useState, useEffect, useCallback } from 'react'
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6'
type Props = {
open: boolean
close: () => void
attachment: Entity.Attachment | null
index: number
media: Array<Entity.Attachment>
}
export default function Media(props: Props) {
const [index, setIndex] = useState<number>(0)
useEffect(() => {
setIndex(props.index)
}, [props.index])
const next = useCallback(() => {
if (index >= props.media.length - 1) {
return
}
setIndex(current => current + 1)
}, [props.media, index, setIndex])
const previous = useCallback(() => {
if (index <= 0) {
return
}
setIndex(current => current - 1)
}, [props.media, index, setIndex])
const handleKeyPress = useCallback(
(event: KeyboardEvent) => {
if (props.open) {
if (event.key === 'ArrowLeft') {
previous()
} else if (event.key === 'ArrowRight') {
next()
}
}
},
[props.open, previous, next]
)
useEffect(() => {
document.addEventListener('keydown', handleKeyPress)
return () => {
document.removeEventListener('keydown', handleKeyPress)
}
}, [handleKeyPress])
return (
<Dialog open={props.open} handler={props.close} size="lg">
<DialogBody className="max-h-full max-w-full">
{props.attachment && (
<img
src={props.attachment.url}
alt={props.attachment.description}
title={props.attachment.description}
className="object-contain max-h-full max-w-full m-auto"
/>
)}
<></>
<div className="flex">
<Button variant="text" onClick={previous} disabled={index < 1}>
<FaChevronLeft />
</Button>
{props.media[index] && (
<img
src={props.media[index].url}
alt={props.media[index].description}
title={props.media[index].description}
className="object-contain max-h-full m-auto"
style={{ maxWidth: '85%' }}
/>
)}
<Button variant="text" onClick={next} disabled={index >= props.media.length - 1}>
<FaChevronRight />
</Button>
</div>
</DialogBody>
</Dialog>
)

View File

@ -12,7 +12,7 @@ import { useIntl } from 'react-intl'
type Props = {
client: MegalodonInterface
account: Account
openMedia: (media: Entity.Attachment) => void
openMedia: (media: Array<Entity.Attachment>, index: number) => void
} & HTMLAttributes<HTMLElement>
export default function Detail(props: Props) {

View File

@ -30,7 +30,7 @@ type Props = {
client: MegalodonInterface
account: Account
user_id: string
openMedia: (media: Entity.Attachment) => void
openMedia: (media: Array<Entity.Attachment>, index: number) => void
}
export default function Profile(props: Props) {

View File

@ -9,7 +9,7 @@ type Props = {
client: MegalodonInterface
account: Account
status_id: string
openMedia: (media: Entity.Attachment) => void
openMedia: (media: Array<Entity.Attachment>, index: number) => void
}
export default function Reply(props: Props) {

View File

@ -8,7 +8,7 @@ type Props = {
client: MegalodonInterface
account: Account
tag: string
openMedia: (media: Entity.Attachment) => void
openMedia: (media: Array<Entity.Attachment>, index: number) => void
}
export default function Tag(props: Props) {

View File

@ -8,7 +8,7 @@ type Props = {
client: MegalodonInterface
account: Account
status_id: string
openMedia: (media: Entity.Attachment) => void
openMedia: (media: Array<Entity.Attachment>, index: number) => void
}
export default function Thread(props: Props) {

View File

@ -7,7 +7,7 @@ type Props = {
client: MegalodonInterface
account: Account
user_id: string
openMedia: (media: Entity.Attachment) => void
openMedia: (media: Array<Entity.Attachment>, index: number) => void
}
export default function Timeline(props: Props) {

View File

@ -1,6 +1,6 @@
import { Account } from '@/db'
import generator, { Entity, MegalodonInterface, WebSocketInterface } from 'megalodon'
import { useEffect, useState, useCallback, useRef, Dispatch, SetStateAction } from 'react'
import { useEffect, useState, useCallback, useRef } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { Virtuoso } from 'react-virtuoso'
import Notification from './notification/Notification'
@ -18,7 +18,7 @@ const TIMELINE_MAX_STATUSES = 2147483647
type Props = {
account: Account
client: MegalodonInterface
setAttachment: Dispatch<SetStateAction<Entity.Attachment | null>>
openMedia: (media: Array<Entity.Attachment>, index: number) => void
}
export default function Notifications(props: Props) {
@ -223,7 +223,7 @@ export default function Notifications(props: Props) {
notification={notification}
onRefresh={updateStatus}
key={notification.id}
openMedia={media => props.setAttachment(media)}
openMedia={props.openMedia}
filters={filters}
/>
</div>
@ -237,7 +237,7 @@ export default function Notifications(props: Props) {
)}
</div>
</section>
<Detail client={props.client} account={props.account} className="detail" openMedia={media => props.setAttachment(media)} />
<Detail client={props.client} account={props.account} className="detail" openMedia={props.openMedia} />
</div>
)
}

View File

@ -1,6 +1,6 @@
import { Account } from '@/db'
import generator, { Entity, MegalodonInterface, WebSocketInterface } from 'megalodon'
import { useEffect, useState, useCallback, useRef, Dispatch, SetStateAction } from 'react'
import { useEffect, useState, useCallback, useRef } from 'react'
import { Virtuoso } from 'react-virtuoso'
import Status from './status/Status'
import { FormattedMessage, useIntl } from 'react-intl'
@ -18,7 +18,7 @@ type Props = {
timeline: string
account: Account
client: MegalodonInterface
setAttachment: Dispatch<SetStateAction<Entity.Attachment | null>>
openMedia: (media: Array<Entity.Attachment>, index: number) => void
}
export default function Timeline(props: Props) {
@ -280,7 +280,7 @@ export default function Timeline(props: Props) {
status={status}
key={status.id}
onRefresh={status => setStatuses(current => updateStatus(current, status))}
openMedia={media => props.setAttachment(media)}
openMedia={props.openMedia}
filters={filters}
/>
)}
@ -296,7 +296,7 @@ export default function Timeline(props: Props) {
</div>
</div>
</section>
<Detail client={props.client} account={props.account} className="detail" openMedia={media => props.setAttachment(media)} />
<Detail client={props.client} account={props.account} className="detail" openMedia={props.openMedia} />
</div>
)
}

View File

@ -9,7 +9,7 @@ type Props = {
account: Account
client: MegalodonInterface
onRefresh: (status: Entity.Status) => void
openMedia: (media: Entity.Attachment) => void
openMedia: (media: Array<Entity.Attachment>, index: number) => void
filters: Array<Entity.Filter>
}

View File

@ -15,7 +15,7 @@ type Props = {
notification: Entity.Notification
client: MegalodonInterface
onRefresh: (status: Entity.Status) => void
openMedia: (media: Entity.Attachment) => void
openMedia: (media: Array<Entity.Attachment>, index: number) => void
}
export default function Reaction(props: Props) {

View File

@ -7,7 +7,7 @@ import { FormattedMessage, useIntl } from 'react-intl'
type Props = {
media: Array<Entity.Attachment>
sensitive: boolean
openMedia: (media: Entity.Attachment) => void
openMedia: (media: Array<Entity.Attachment>, index: number) => void
}
export default function Media(props: Props) {
const [sensitive, setSensitive] = useState(props.sensitive)
@ -32,7 +32,7 @@ export default function Media(props: Props) {
<div className="mt-2 flex flex-wrap gap-2">
{props.media.map((media, key) => (
<div key={key}>
<Attachment attachment={media} openMedia={() => props.openMedia(media)} />
<Attachment attachment={media} openMedia={() => props.openMedia(props.media, key)} />
</div>
))}
</div>

View File

@ -19,7 +19,7 @@ type Props = {
account: Account
client: MegalodonInterface
onRefresh: (status: Entity.Status) => void
openMedia: (media: Entity.Attachment) => void
openMedia: (media: Array<Entity.Attachment>, index: number) => void
filters: Array<Entity.Filter>
}

View File

@ -1,6 +1,6 @@
import { useRouter } from 'next/router'
import Timeline from '@/components/timelines/Timeline'
import { useEffect, useState } from 'react'
import { useEffect, useReducer, useState } from 'react'
import { Account, db } from '@/db'
import generator, { Entity, MegalodonInterface } from 'megalodon'
import Notifications from '@/components/timelines/Notifications'
@ -11,9 +11,10 @@ export default function Page() {
const router = useRouter()
const [account, setAccount] = useState<Account | null>(null)
const [client, setClient] = useState<MegalodonInterface>(null)
const [attachment, setAttachment] = useState<Entity.Attachment | null>(null)
const [report, setReport] = useState<Entity.Status | null>(null)
const [modalState, dispatch] = useReducer(modalReducer, initialModalState)
useEffect(() => {
if (router.query.id) {
if (router.query.id && typeof localStorage !== 'undefined') {
@ -60,12 +61,55 @@ export default function Page() {
return (
<>
{(router.query.timeline as string) === 'notifications' ? (
<Notifications account={account} client={client} setAttachment={setAttachment} />
<Notifications
account={account}
client={client}
openMedia={(media: Array<Entity.Attachment>, index: number) =>
dispatch({ target: 'media', value: true, object: media, index: index })
}
/>
) : (
<Timeline timeline={router.query.timeline as string} account={account} client={client} setAttachment={setAttachment} />
<Timeline
timeline={router.query.timeline as string}
account={account}
client={client}
openMedia={(media: Array<Entity.Attachment>, index: number) =>
dispatch({ target: 'media', value: true, object: media, index: index })
}
/>
)}
<Media open={attachment !== null} close={() => setAttachment(null)} attachment={attachment} />
<Media
open={modalState.media.opened}
close={() => dispatch({ target: 'media', value: false, object: [], index: -1 })}
media={modalState.media.object}
index={modalState.media.index}
/>
{report && <Report open={report !== null} close={closeReport} status={report} client={client} />}
</>
)
}
type ModalState = {
media: {
opened: boolean
object: Array<Entity.Attachment>
index: number
}
}
const initialModalState: ModalState = {
media: {
opened: false,
object: [],
index: 0
}
}
const modalReducer = (current: ModalState, action: { target: string; value: boolean; object?: any; index?: number }): ModalState => {
switch (action.target) {
case 'media':
return { ...current, media: { opened: action.value, object: action.object, index: action.index } }
default:
return current
}
}