refs #4850 Add next/prev button on image
This commit is contained in:
parent
cb80754681
commit
5b285220ea
@ -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>
|
||||
)
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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>
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user