Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track links and embeds in the composer reducer #5593

Merged
merged 3 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/view/com/composer/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export const ComposePost = ({
// TODO: Move more state here.
const [composerState, dispatch] = useReducer(
composerReducer,
{initImageUris},
{initImageUris, initQuoteUri: initQuote?.uri},
createComposerState,
)

Expand Down Expand Up @@ -337,6 +337,7 @@ export const ComposePost = ({

const onNewLink = useCallback(
(uri: string) => {
dispatch({type: 'embed_add_uri', uri})
estrattonbailey marked this conversation as resolved.
Show resolved Hide resolved
if (extLink != null) return
setExtLink({uri, isLoading: true})
},
Expand Down Expand Up @@ -582,6 +583,7 @@ export const ComposePost = ({

const onSelectGif = useCallback(
(gif: Gif) => {
dispatch({type: 'embed_add_gif', gif})
setExtLink({
uri: `${gif.media_formats.gif.url}?hh=${gif.media_formats.gif.dims[1]}&ww=${gif.media_formats.gif.dims[0]}`,
isLoading: true,
Expand All @@ -600,6 +602,7 @@ export const ComposePost = ({

const handleChangeGifAltText = useCallback(
(altText: string) => {
dispatch({type: 'embed_update_gif', alt: altText})
setExtLink(ext =>
ext && ext.meta
? {
Expand Down Expand Up @@ -770,6 +773,11 @@ export const ComposePost = ({
link={extLink}
gif={extGif}
onRemove={() => {
if (extGif) {
dispatch({type: 'embed_remove_gif'})
} else {
dispatch({type: 'embed_remove_link'})
}
setExtLink(undefined)
setExtGif(undefined)
}}
Expand Down Expand Up @@ -818,7 +826,12 @@ export const ComposePost = ({
<QuoteEmbed quote={quote} />
</View>
{quote.uri !== initQuote?.uri && (
<QuoteX onRemove={() => setQuote(undefined)} />
<QuoteX
onRemove={() => {
dispatch({type: 'embed_remove_quote'})
setQuote(undefined)
}}
/>
)}
</View>
) : null}
Expand Down
151 changes: 136 additions & 15 deletions src/view/com/composer/state/composer.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,45 @@
import {ImagePickerAsset} from 'expo-image-picker'

import {isBskyPostUrl} from '#/lib/strings/url-helpers'
import {ComposerImage, createInitialImages} from '#/state/gallery'
import {Gif} from '#/state/queries/tenor'
import {ComposerOpts} from '#/state/shell/composer'
import {createVideoState, VideoAction, videoReducer, VideoState} from './video'

type PostRecord = {
uri: string
}

type ImagesMedia = {
type: 'images'
images: ComposerImage[]
labels: string[]
}

type VideoMedia = {
type: 'video'
video: VideoState
}

type ComposerEmbed = {
// TODO: Other record types.
record: PostRecord | undefined
// TODO: Other media types.
media: ImagesMedia | VideoMedia | undefined
type GifMedia = {
type: 'gif'
gif: Gif
alt: string
}

type Link = {
type: 'link'
uri: string
}

// This structure doesn't exactly correspond to the data model.
// Instead, it maps to how the UI is organized, and how we present a post.
type EmbedDraft = {
// We'll always submit quote and actual media (images, video, gifs) chosen by the user.
quote: Link | undefined
media: ImagesMedia | VideoMedia | GifMedia | undefined
// This field may end up ignored if we have more important things to display than a link card:
link: Link | undefined
}

export type ComposerState = {
// TODO: Other draft data.
embed: ComposerEmbed
embed: EmbedDraft
}

export type ComposerAction =
Expand All @@ -42,6 +53,12 @@ export type ComposerAction =
}
| {type: 'embed_remove_video'}
| {type: 'embed_update_video'; videoAction: VideoAction}
| {type: 'embed_add_uri'; uri: string}
| {type: 'embed_remove_quote'}
| {type: 'embed_remove_link'}
| {type: 'embed_add_gif'; gif: Gif}
| {type: 'embed_update_gif'; alt: string}
| {type: 'embed_remove_gif'}

const MAX_IMAGES = 4

Expand All @@ -60,7 +77,6 @@ export function composerReducer(
nextMedia = {
type: 'images',
images: action.images.slice(0, MAX_IMAGES),
labels: [],
}
} else if (prevMedia.type === 'images') {
nextMedia = {
Expand Down Expand Up @@ -171,29 +187,134 @@ export function composerReducer(
},
}
}
case 'embed_add_uri': {
const prevQuote = state.embed.quote
const prevLink = state.embed.link
let nextQuote = prevQuote
let nextLink = prevLink
if (isBskyPostUrl(action.uri)) {
if (!prevQuote) {
nextQuote = {
type: 'link',
uri: action.uri,
}
}
} else {
if (!prevLink) {
nextLink = {
type: 'link',
uri: action.uri,
}
}
}
return {
...state,
embed: {
...state.embed,
quote: nextQuote,
link: nextLink,
},
}
}
case 'embed_remove_link': {
return {
...state,
embed: {
...state.embed,
link: undefined,
},
}
}
case 'embed_remove_quote': {
return {
...state,
embed: {
...state.embed,
quote: undefined,
},
}
}
case 'embed_add_gif': {
const prevMedia = state.embed.media
let nextMedia = prevMedia
if (!prevMedia) {
nextMedia = {
type: 'gif',
gif: action.gif,
alt: '',
}
}
return {
...state,
embed: {
...state.embed,
media: nextMedia,
},
}
}
case 'embed_update_gif': {
const prevMedia = state.embed.media
let nextMedia = prevMedia
if (prevMedia?.type === 'gif') {
nextMedia = {
...prevMedia,
alt: action.alt,
}
}
return {
...state,
embed: {
...state.embed,
media: nextMedia,
},
}
}
case 'embed_remove_gif': {
const prevMedia = state.embed.media
let nextMedia = prevMedia
if (prevMedia?.type === 'gif') {
nextMedia = undefined
}
return {
...state,
embed: {
...state.embed,
media: nextMedia,
},
}
}
default:
return state
}
}

export function createComposerState({
initImageUris,
initQuoteUri,
}: {
initImageUris: ComposerOpts['imageUris']
initQuoteUri: string | undefined
}): ComposerState {
let media: ImagesMedia | undefined
if (initImageUris?.length) {
media = {
type: 'images',
images: createInitialImages(initImageUris),
labels: [],
}
}
// TODO: initial video.
let quote: Link | undefined
if (initQuoteUri) {
quote = {
type: 'link',
uri: initQuoteUri,
}
}
// TODO: Other initial content.
return {
embed: {
record: undefined,
quote,
media,
link: undefined,
},
}
}
Loading