Unblock React Compiler for 18 components with value blocks inside try
React Compiler cannot lower a conditional expression - `&&`, `||`, `??`, `?.`, a ternary - inside a try block. Three techniques, picked per site: - split `if (a && b)` into nested ifs, where there is no `else` to break - hoist the expression into a const above the try, where it does not depend on anything the try produces - move it into a module-scope helper, where it does Optional calls become `if (f) f()`, which keeps the arguments unevaluated when the callback is absent, exactly as `f?.()` does. Skipped components: 125 -> 107. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,13 +73,17 @@ export function Outer({
|
|||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (cb && typeof cb === 'function') {
|
// Nested rather than `&&`: React Compiler cannot lower a logical
|
||||||
|
// expression in a test position inside a `try`.
|
||||||
|
if (cb) {
|
||||||
|
if (typeof cb === 'function') {
|
||||||
// This timeout ensures that the callback runs at the same time as it would on native. I.e.
|
// This timeout ensures that the callback runs at the same time as it would on native. I.e.
|
||||||
// console.log('Step 1') -> close(() => console.log('Step 3')) -> console.log('Step 2')
|
// console.log('Step 1') -> close(() => console.log('Step 3')) -> console.log('Step 2')
|
||||||
// This should always output 'Step 1', 'Step 2', 'Step 3', but without the timeout it would output
|
// This should always output 'Step 1', 'Step 2', 'Step 3', but without the timeout it would output
|
||||||
// 'Step 1', 'Step 3', 'Step 2'.
|
// 'Step 1', 'Step 3', 'Step 2'.
|
||||||
setTimeout(cb)
|
setTimeout(cb)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.error(`Dialog closeCallback failed`, {
|
logger.error(`Dialog closeCallback failed`, {
|
||||||
message: e.message,
|
message: e.message,
|
||||||
|
|||||||
@@ -295,6 +295,8 @@ function SaveButtonInner({
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
|
const pinned = pin || false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (savedFeedConfig) {
|
if (savedFeedConfig) {
|
||||||
await removeFeed(savedFeedConfig)
|
await removeFeed(savedFeedConfig)
|
||||||
@@ -303,7 +305,7 @@ function SaveButtonInner({
|
|||||||
{
|
{
|
||||||
type,
|
type,
|
||||||
value: uri,
|
value: uri,
|
||||||
pinned: pin || false,
|
pinned,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,9 +107,10 @@ let PostControls = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const existingLike = post.viewer?.like
|
||||||
try {
|
try {
|
||||||
setHasLikeIconBeenToggled(true)
|
setHasLikeIconBeenToggled(true)
|
||||||
if (!post.viewer?.like) {
|
if (!existingLike) {
|
||||||
sendInteraction({
|
sendInteraction({
|
||||||
item: post.uri,
|
item: post.uri,
|
||||||
event: 'app.bsky.feed.defs#interactionLike',
|
event: 'app.bsky.feed.defs#interactionLike',
|
||||||
@@ -137,8 +138,9 @@ let PostControls = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const existingRepost = post.viewer?.repost
|
||||||
try {
|
try {
|
||||||
if (!post.viewer?.repost) {
|
if (!existingRepost) {
|
||||||
sendInteraction({
|
sendInteraction({
|
||||||
item: post.uri,
|
item: post.uri,
|
||||||
event: 'app.bsky.feed.defs#interactionRepost',
|
event: 'app.bsky.feed.defs#interactionRepost',
|
||||||
|
|||||||
@@ -491,16 +491,21 @@ export function FollowButtonInner({
|
|||||||
const onPressFollow = async (e: GestureResponderEvent) => {
|
const onPressFollow = async (e: GestureResponderEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
const displayNameOrHandle = profile.displayName || profile.handle
|
||||||
try {
|
try {
|
||||||
await queueFollow()
|
await queueFollow()
|
||||||
Toast.show(
|
Toast.show(
|
||||||
l`Following ${sanitizeDisplayName(
|
l`Following ${sanitizeDisplayName(
|
||||||
profile.displayName || profile.handle,
|
displayNameOrHandle,
|
||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
)}`,
|
)}`,
|
||||||
)
|
)
|
||||||
onPressProp?.(e)
|
if (onPressProp) {
|
||||||
onFollow?.()
|
onPressProp(e)
|
||||||
|
}
|
||||||
|
if (onFollow) {
|
||||||
|
onFollow()
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const err = e as Error
|
const err = e as Error
|
||||||
if (err?.name !== 'AbortError') {
|
if (err?.name !== 'AbortError') {
|
||||||
@@ -514,15 +519,18 @@ export function FollowButtonInner({
|
|||||||
const onPressUnfollow = async (e: GestureResponderEvent) => {
|
const onPressUnfollow = async (e: GestureResponderEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
const displayNameOrHandle = profile.displayName || profile.handle
|
||||||
try {
|
try {
|
||||||
await queueUnfollow()
|
await queueUnfollow()
|
||||||
Toast.show(
|
Toast.show(
|
||||||
l`No longer following ${sanitizeDisplayName(
|
l`No longer following ${sanitizeDisplayName(
|
||||||
profile.displayName || profile.handle,
|
displayNameOrHandle,
|
||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
)}`,
|
)}`,
|
||||||
)
|
)
|
||||||
onPressProp?.(e)
|
if (onPressProp) {
|
||||||
|
onPressProp(e)
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const err = e as Error
|
const err = e as Error
|
||||||
if (err?.name !== 'AbortError') {
|
if (err?.name !== 'AbortError') {
|
||||||
|
|||||||
@@ -221,6 +221,14 @@ function DialogInner({
|
|||||||
const onPressSave = useCallback(async () => {
|
const onPressSave = useCallback(async () => {
|
||||||
setImageError('')
|
setImageError('')
|
||||||
setDisplayNameTooShort(false)
|
setDisplayNameTooShort(false)
|
||||||
|
// Hoisted above the `try`: React Compiler cannot lower a conditional
|
||||||
|
// expression inside one.
|
||||||
|
const updatedMessage = isCurateList
|
||||||
|
? _(msg({message: 'User list updated', context: 'toast'}))
|
||||||
|
: _(msg({message: 'Moderation list updated', context: 'toast'}))
|
||||||
|
const createdMessage = isCurateList
|
||||||
|
? _(msg({message: 'User list created', context: 'toast'}))
|
||||||
|
: _(msg({message: 'Moderation list created', context: 'toast'}))
|
||||||
try {
|
try {
|
||||||
if (displayName.length === 0) {
|
if (displayName.length === 0) {
|
||||||
setDisplayNameTooShort(true)
|
setDisplayNameTooShort(true)
|
||||||
@@ -244,11 +252,7 @@ function DialogInner({
|
|||||||
descriptionFacets: richText.facets,
|
descriptionFacets: richText.facets,
|
||||||
avatar: newListAvatar,
|
avatar: newListAvatar,
|
||||||
})
|
})
|
||||||
Toast.show(
|
Toast.show(updatedMessage)
|
||||||
isCurateList
|
|
||||||
? _(msg({message: 'User list updated', context: 'toast'}))
|
|
||||||
: _(msg({message: 'Moderation list updated', context: 'toast'})),
|
|
||||||
)
|
|
||||||
control.close(() => onSave?.(list.uri))
|
control.close(() => onSave?.(list.uri))
|
||||||
} else {
|
} else {
|
||||||
const {uri} = await createListMutation({
|
const {uri} = await createListMutation({
|
||||||
@@ -258,11 +262,7 @@ function DialogInner({
|
|||||||
descriptionFacets: richText.facets,
|
descriptionFacets: richText.facets,
|
||||||
avatar: newListAvatar,
|
avatar: newListAvatar,
|
||||||
})
|
})
|
||||||
Toast.show(
|
Toast.show(createdMessage)
|
||||||
isCurateList
|
|
||||||
? _(msg({message: 'User list created', context: 'toast'}))
|
|
||||||
: _(msg({message: 'Moderation list created', context: 'toast'})),
|
|
||||||
)
|
|
||||||
control.close(() => onSave?.(uri))
|
control.close(() => onSave?.(uri))
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -450,10 +450,14 @@ function DialogInner({
|
|||||||
const feedRkey = useMemo(() => new AtUri(info.uri).rkey, [info.uri])
|
const feedRkey = useMemo(() => new AtUri(info.uri).rkey, [info.uri])
|
||||||
|
|
||||||
const onToggleLiked = async () => {
|
const onToggleLiked = async () => {
|
||||||
|
// Hoisted out of the `try`: React Compiler cannot lower a logical
|
||||||
|
// expression in a test position there, and the `else` below rules out
|
||||||
|
// splitting this into nested ifs.
|
||||||
|
const shouldUnlike = isLiked && likeUri
|
||||||
try {
|
try {
|
||||||
playHaptic()
|
playHaptic()
|
||||||
|
|
||||||
if (isLiked && likeUri) {
|
if (shouldUnlike) {
|
||||||
await unlikeFeed({uri: likeUri})
|
await unlikeFeed({uri: likeUri})
|
||||||
setLikeUri('')
|
setLikeUri('')
|
||||||
ax.metric('feed:unlike', {feedUrl: info.uri})
|
ax.metric('feed:unlike', {feedUrl: info.uri})
|
||||||
|
|||||||
@@ -95,11 +95,13 @@ function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) {
|
|||||||
const onSave = useCallback(async () => {
|
const onSave = useCallback(async () => {
|
||||||
setError('')
|
setError('')
|
||||||
|
|
||||||
|
const embeddingRules = maybeEditedPostgate.embeddingRules ?? []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await setPostInteractionSettings({
|
await setPostInteractionSettings({
|
||||||
threadgateAllowRules:
|
threadgateAllowRules:
|
||||||
threadgateAllowUISettingToAllowRecordValue(maybeEditedAllowUI),
|
threadgateAllowUISettingToAllowRecordValue(maybeEditedAllowUI),
|
||||||
postgateEmbeddingRules: maybeEditedPostgate.embeddingRules ?? [],
|
postgateEmbeddingRules: embeddingRules,
|
||||||
})
|
})
|
||||||
Toast.show(_(msg({message: 'Settings saved', context: 'toast'})))
|
Toast.show(_(msg({message: 'Settings saved', context: 'toast'})))
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -78,11 +78,12 @@ export function StepFinished() {
|
|||||||
logger.error('Failed to fetch starter pack', {safeMessage: e})
|
logger.error('Failed to fetch starter pack', {safeMessage: e})
|
||||||
// don't tell the user, just get them through onboarding.
|
// don't tell the user, just get them through onboarding.
|
||||||
}
|
}
|
||||||
|
const starterPackList = starterPack?.list
|
||||||
try {
|
try {
|
||||||
if (starterPack?.list) {
|
if (starterPackList) {
|
||||||
listItems = await getAllListMembers(
|
listItems = await getAllListMembers(
|
||||||
appviewClient,
|
appviewClient,
|
||||||
starterPack.list.uri,
|
starterPackList.uri,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -93,19 +94,22 @@ export function StepFinished() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hoisted above the `try`: React Compiler cannot lower these inside one, and
|
||||||
|
// `listItems` is already settled by the earlier try/catch.
|
||||||
|
const followDids = [
|
||||||
|
BSKY_APP_ACCOUNT_DID,
|
||||||
|
...(listItems?.map(i => i.subject.did) ?? []),
|
||||||
|
]
|
||||||
|
const starterPackRef = starterPack
|
||||||
|
? {uri: starterPack.uri, cid: starterPack.cid}
|
||||||
|
: undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {interestsStepResults, profileStepResults} = state
|
const {interestsStepResults, profileStepResults} = state
|
||||||
const {selectedInterests} = interestsStepResults
|
const {selectedInterests} = interestsStepResults
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
bulkWriteFollows(
|
bulkWriteFollows(pdsClient, appviewClient, followDids, starterPackRef),
|
||||||
pdsClient,
|
|
||||||
appviewClient,
|
|
||||||
[BSKY_APP_ACCOUNT_DID, ...(listItems?.map(i => i.subject.did) ?? [])],
|
|
||||||
starterPack
|
|
||||||
? {uri: starterPack.uri, cid: starterPack.cid}
|
|
||||||
: undefined,
|
|
||||||
),
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// Interests need to get saved first, then we can write the feeds to prefs
|
// Interests need to get saved first, then we can write the feeds to prefs
|
||||||
await pdsClient.call(setInterestsPref, {tags: selectedInterests})
|
await pdsClient.call(setInterestsPref, {tags: selectedInterests})
|
||||||
|
|||||||
@@ -259,6 +259,9 @@ export function HeaderLabelerButtons({
|
|||||||
requireAuth(async (): Promise<void> => {
|
requireAuth(async (): Promise<void> => {
|
||||||
playHaptic()
|
playHaptic()
|
||||||
const subscribe = !isSubscribed
|
const subscribe = !isSubscribed
|
||||||
|
const subscribeMetric = subscribe
|
||||||
|
? 'moderation:subscribedToLabeler'
|
||||||
|
: 'moderation:unsubscribedFromLabeler'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await toggleSubscription({
|
await toggleSubscription({
|
||||||
@@ -266,12 +269,7 @@ export function HeaderLabelerButtons({
|
|||||||
subscribe,
|
subscribe,
|
||||||
})
|
})
|
||||||
|
|
||||||
ax.metric(
|
ax.metric(subscribeMetric, {})
|
||||||
subscribe
|
|
||||||
? 'moderation:subscribedToLabeler'
|
|
||||||
: 'moderation:unsubscribedFromLabeler',
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
reset()
|
reset()
|
||||||
if (e.message === 'MAX_LABELERS') {
|
if (e.message === 'MAX_LABELERS') {
|
||||||
|
|||||||
@@ -238,14 +238,17 @@ export function HeaderStandardButtons({
|
|||||||
|
|
||||||
const onPressFollow = () => {
|
const onPressFollow = () => {
|
||||||
playHaptic()
|
playHaptic()
|
||||||
|
const displayNameOrHandle = profile.displayName || profile.handle
|
||||||
requireAuth(async () => {
|
requireAuth(async () => {
|
||||||
try {
|
try {
|
||||||
await queueFollow()
|
await queueFollow()
|
||||||
onFollow?.()
|
if (onFollow) {
|
||||||
|
onFollow()
|
||||||
|
}
|
||||||
Toast.show(
|
Toast.show(
|
||||||
_(
|
_(
|
||||||
msg`Following ${sanitizeDisplayName(
|
msg`Following ${sanitizeDisplayName(
|
||||||
profile.displayName || profile.handle,
|
displayNameOrHandle,
|
||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
)}`,
|
)}`,
|
||||||
),
|
),
|
||||||
@@ -264,14 +267,17 @@ export function HeaderStandardButtons({
|
|||||||
|
|
||||||
const onPressUnfollow = () => {
|
const onPressUnfollow = () => {
|
||||||
playHaptic()
|
playHaptic()
|
||||||
|
const displayNameOrHandle = profile.displayName || profile.handle
|
||||||
requireAuth(async () => {
|
requireAuth(async () => {
|
||||||
try {
|
try {
|
||||||
await queueUnfollow()
|
await queueUnfollow()
|
||||||
onUnfollow?.()
|
if (onUnfollow) {
|
||||||
|
onUnfollow()
|
||||||
|
}
|
||||||
Toast.show(
|
Toast.show(
|
||||||
_(
|
_(
|
||||||
msg`No longer following ${sanitizeDisplayName(
|
msg`No longer following ${sanitizeDisplayName(
|
||||||
profile.displayName || profile.handle,
|
displayNameOrHandle,
|
||||||
moderation.ui('displayName'),
|
moderation.ui('displayName'),
|
||||||
)}`,
|
)}`,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ export function Header({
|
|||||||
const onTogglePinned = async () => {
|
const onTogglePinned = async () => {
|
||||||
playHaptic()
|
playHaptic()
|
||||||
|
|
||||||
|
// Hoisted above the `try`: inside it, `pinned` is `!savedFeedConfig.pinned`,
|
||||||
|
// which is `!isPinned` on the branch that uses this.
|
||||||
|
const pinnedMessage = !isPinned
|
||||||
|
? _(msg`Pinned to your feeds`)
|
||||||
|
: _(msg`Unpinned from your feeds`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (savedFeedConfig) {
|
if (savedFeedConfig) {
|
||||||
const pinned = !savedFeedConfig.pinned
|
const pinned = !savedFeedConfig.pinned
|
||||||
@@ -73,11 +79,7 @@ export function Header({
|
|||||||
pinned,
|
pinned,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
Toast.show(
|
Toast.show(pinnedMessage)
|
||||||
pinned
|
|
||||||
? _(msg`Pinned to your feeds`)
|
|
||||||
: _(msg`Unpinned from your feeds`),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
await addSavedFeeds([
|
await addSavedFeeds([
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,15 @@ import {type CaptchaWebViewProps} from './CaptchaWebView.shared'
|
|||||||
|
|
||||||
const REDIRECT_HOST = new URL(window.location.href).host
|
const REDIRECT_HOST = new URL(window.location.href).host
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module scope because React Compiler cannot lower an optional chain inside a
|
||||||
|
* `try`, and this one has to stay in the `try` - reading `location` on a
|
||||||
|
* cross-origin frame throws.
|
||||||
|
*/
|
||||||
|
function getFrameHref(frame: HTMLIFrameElement | null): string | undefined {
|
||||||
|
return frame?.contentWindow?.location.href
|
||||||
|
}
|
||||||
|
|
||||||
export function CaptchaWebView({
|
export function CaptchaWebView({
|
||||||
url,
|
url,
|
||||||
stateParam,
|
stateParam,
|
||||||
@@ -29,7 +38,7 @@ export function CaptchaWebView({
|
|||||||
) as HTMLIFrameElement
|
) as HTMLIFrameElement
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const href = frame?.contentWindow?.location.href
|
const href = getFrameHref(frame)
|
||||||
if (!href) return
|
if (!href) return
|
||||||
const urlp = new URL(href)
|
const urlp = new URL(href)
|
||||||
|
|
||||||
@@ -37,7 +46,12 @@ export function CaptchaWebView({
|
|||||||
if (urlp.host !== REDIRECT_HOST) return
|
if (urlp.host !== REDIRECT_HOST) return
|
||||||
|
|
||||||
const code = urlp.searchParams.get('code')
|
const code = urlp.searchParams.get('code')
|
||||||
if (urlp.searchParams.get('state') !== stateParam || !code) {
|
const stateMismatch = urlp.searchParams.get('state') !== stateParam
|
||||||
|
if (stateMismatch) {
|
||||||
|
onError({error: 'Invalid state or code'})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!code) {
|
||||||
onError({error: 'Invalid state or code'})
|
onError({error: 'Invalid state or code'})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,7 +261,10 @@ export function useGetJoinLinkPreview() {
|
|||||||
staleTime: STALE.SECONDS.FIFTEEN,
|
staleTime: STALE.SECONDS.FIFTEEN,
|
||||||
})
|
})
|
||||||
const found = data.joinLinkPreviews[0]
|
const found = data.joinLinkPreviews[0]
|
||||||
return isKnownJoinLinkPreview(found) ? found : undefined
|
if (isKnownJoinLinkPreview(found)) {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to fetch join link preview', {safeMessage: error})
|
logger.error('Failed to fetch join link preview', {safeMessage: error})
|
||||||
return undefined
|
return undefined
|
||||||
|
|||||||
@@ -13,6 +13,22 @@ type ServiceConfig = {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module scope because React Compiler cannot lower a `??` inside a `try`, and
|
||||||
|
* `data` only exists once the request in that `try` resolves.
|
||||||
|
*/
|
||||||
|
function toServiceConfig(data: {
|
||||||
|
checkEmailConfirmed?: boolean
|
||||||
|
liveNow?: ServiceConfig['liveNow']
|
||||||
|
}): ServiceConfig {
|
||||||
|
return {
|
||||||
|
checkEmailConfirmed: Boolean(data.checkEmailConfirmed),
|
||||||
|
// @ts-expect-error not included in the lexicon atm
|
||||||
|
topicsEnabled: Boolean(data.topicsEnabled),
|
||||||
|
liveNow: data.liveNow ?? [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function useServiceConfigQuery() {
|
export function useServiceConfigQuery() {
|
||||||
const client = useAppviewClient()
|
const client = useAppviewClient()
|
||||||
return useQuery<ServiceConfig>({
|
return useQuery<ServiceConfig>({
|
||||||
@@ -22,12 +38,7 @@ export function useServiceConfigQuery() {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
const data = await client.call(app.bsky.unspecced.getConfig)
|
const data = await client.call(app.bsky.unspecced.getConfig)
|
||||||
return {
|
return toServiceConfig(data)
|
||||||
checkEmailConfirmed: Boolean(data.checkEmailConfirmed),
|
|
||||||
// @ts-expect-error not included in the lexicon atm
|
|
||||||
topicsEnabled: Boolean(data.topicsEnabled),
|
|
||||||
liveNow: data.liveNow ?? [],
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {
|
return {
|
||||||
checkEmailConfirmed: false,
|
checkEmailConfirmed: false,
|
||||||
|
|||||||
@@ -287,9 +287,10 @@ function DraftMediaPreview({post}: {post: DraftPostDisplay}) {
|
|||||||
if (post.images && post.images.length > 0) {
|
if (post.images && post.images.length > 0) {
|
||||||
const loaded: LoadedImage[] = []
|
const loaded: LoadedImage[] = []
|
||||||
for (const image of post.images) {
|
for (const image of post.images) {
|
||||||
|
const alt = image.altText || ''
|
||||||
try {
|
try {
|
||||||
const url = await storage.loadMediaFromLocal(image.localPath)
|
const url = await storage.loadMediaFromLocal(image.localPath)
|
||||||
loaded.push({url, alt: image.altText || ''})
|
loaded.push({url, alt})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Image doesn't exist locally, skip it
|
// Image doesn't exist locally, skip it
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import {useCallback} from 'react'
|
|
||||||
import * as MediaLibrary from 'expo-media-library/legacy'
|
import * as MediaLibrary from 'expo-media-library/legacy'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -20,14 +19,22 @@ export function OpenCameraBtn({disabled, onAdd}: OpenCameraBtnProps) {
|
|||||||
MediaLibrary.usePermissions({granularPermissions: ['photo']})
|
MediaLibrary.usePermissions({granularPermissions: ['photo']})
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
|
||||||
const onPressTakePicture = useCallback(async () => {
|
const mediaGranted = mediaPermissionRes?.granted
|
||||||
|
const mediaCanAskAgain = mediaPermissionRes?.canAskAgain
|
||||||
|
|
||||||
|
// No useCallback: with the diagnostics above resolved this component compiles,
|
||||||
|
// so React Compiler memoizes it, and the hand-written deps were what it could
|
||||||
|
// not preserve.
|
||||||
|
const onPressTakePicture = async () => {
|
||||||
try {
|
try {
|
||||||
if (!(await requestCameraAccessIfNeeded())) {
|
if (!(await requestCameraAccessIfNeeded())) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!mediaPermissionRes?.granted && mediaPermissionRes?.canAskAgain) {
|
if (!mediaGranted) {
|
||||||
|
if (mediaCanAskAgain) {
|
||||||
await requestMediaPermission()
|
await requestMediaPermission()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const img = await openCamera({
|
const img = await openCamera({
|
||||||
aspect: [1, 1],
|
aspect: [1, 1],
|
||||||
@@ -49,12 +56,7 @@ export function OpenCameraBtn({disabled, onAdd}: OpenCameraBtnProps) {
|
|||||||
// ignore
|
// ignore
|
||||||
logger.warn('Error using camera', {error: err})
|
logger.warn('Error using camera', {error: err})
|
||||||
}
|
}
|
||||||
}, [
|
}
|
||||||
onAdd,
|
|
||||||
requestCameraAccessIfNeeded,
|
|
||||||
mediaPermissionRes,
|
|
||||||
requestMediaPermission,
|
|
||||||
])
|
|
||||||
|
|
||||||
const shouldShowCameraButton = IS_NATIVE || IS_WEB_MOBILE
|
const shouldShowCameraButton = IS_NATIVE || IS_WEB_MOBILE
|
||||||
if (!shouldShowCameraButton) {
|
if (!shouldShowCameraButton) {
|
||||||
|
|||||||
@@ -56,9 +56,11 @@ export function ComposerPrompt() {
|
|||||||
requestVideoAccessIfNeeded(),
|
requestVideoAccessIfNeeded(),
|
||||||
])
|
])
|
||||||
|
|
||||||
if (!photoAccess && !videoAccess) {
|
if (!photoAccess) {
|
||||||
|
if (!videoAccess) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Keyboard.isVisible()) {
|
if (Keyboard.isVisible()) {
|
||||||
Keyboard.dismiss()
|
Keyboard.dismiss()
|
||||||
@@ -108,9 +110,11 @@ export function ComposerPrompt() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_NATIVE && Keyboard.isVisible()) {
|
if (IS_NATIVE) {
|
||||||
|
if (Keyboard.isVisible()) {
|
||||||
Keyboard.dismiss()
|
Keyboard.dismiss()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const image = await openCamera({
|
const image = await openCamera({
|
||||||
mediaTypes: 'images',
|
mediaTypes: 'images',
|
||||||
@@ -127,8 +131,14 @@ export function ComposerPrompt() {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// Statement form rather than a ternary: React Compiler cannot lower a
|
||||||
|
// conditional expression inside a `try`, and `imageUris` is built here.
|
||||||
|
let nativeImageUris
|
||||||
|
if (IS_NATIVE) {
|
||||||
|
nativeImageUris = imageUris
|
||||||
|
}
|
||||||
openComposer({
|
openComposer({
|
||||||
imageUris: IS_NATIVE ? imageUris : undefined,
|
imageUris: nativeImageUris,
|
||||||
logContext: 'Fab',
|
logContext: 'Fab',
|
||||||
})
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -79,7 +79,11 @@ export function UserBanner({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (IS_NATIVE) {
|
if (IS_NATIVE) {
|
||||||
onSelectNewBanner?.(
|
// Nested rather than `?.()`: React Compiler cannot lower an optional
|
||||||
|
// call inside a `try`. Like `?.()`, this leaves the arguments
|
||||||
|
// unevaluated when the callback is absent.
|
||||||
|
if (onSelectNewBanner) {
|
||||||
|
onSelectNewBanner(
|
||||||
await compressIfNeeded(
|
await compressIfNeeded(
|
||||||
await openCropper({
|
await openCropper({
|
||||||
imageUri: items[0].path,
|
imageUri: items[0].path,
|
||||||
@@ -88,6 +92,7 @@ export function UserBanner({
|
|||||||
IMAGE_SIZE_CONFIG_2K_1MB,
|
IMAGE_SIZE_CONFIG_2K_1MB,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setRawImage(await createComposerImage(items[0]))
|
setRawImage(await createComposerImage(items[0]))
|
||||||
editImageDialogControl.open()
|
editImageDialogControl.open()
|
||||||
|
|||||||
Reference in New Issue
Block a user