Bluesky app fork with some witchin' additions 💫

Render dropdown menu items lazily (#6437)

authored by danabra.mov and committed by

GitHub e9fe8d90 ddf2a64a

+308 -277
+308 -277
src/view/com/util/forms/PostDropdownBtn.tsx
··· 102 timestamp: string 103 threadgateRecord?: AppBskyFeedThreadgate.Record 104 }): React.ReactNode => { 105 - const {hasSession, currentAccount} = useSession() 106 const theme = useTheme() 107 const alf = useAlf() 108 const {gtMobile} = useBreakpoints() 109 const {_} = useLingui() 110 - const defaultCtrlColor = theme.palette.default.postCtrl 111 const langPrefs = useLanguagePrefs() 112 const {mutateAsync: deletePostMutate} = usePostDeleteMutation() 113 const {mutateAsync: pinPostMutate, isPending: isPinPending} = ··· 360 }, [isPinned, pinPostMutate, postCid, postUri]) 361 362 return ( 363 - <EventStopper onKeyDown={false}> 364 - <Menu.Root> 365 - <Menu.Trigger label={_(msg`Open post options menu`)}> 366 - {({props, state}) => { 367 - return ( 368 - <Pressable 369 - {...props} 370 - hitSlop={hitSlop} 371 - testID={testID} 372 - style={[ 373 - style, 374 - a.rounded_full, 375 - (state.hovered || state.pressed) && [ 376 - alf.atoms.bg_contrast_25, 377 - ], 378 - ]}> 379 - <DotsHorizontal 380 - fill={defaultCtrlColor} 381 - style={{pointerEvents: 'none'}} 382 - size={size} 383 - /> 384 - </Pressable> 385 - ) 386 - }} 387 - </Menu.Trigger> 388 389 - <Menu.Outer> 390 - {isAuthor && ( 391 - <> 392 - <Menu.Group> 393 - <Menu.Item 394 - testID="pinPostBtn" 395 - label={ 396 - isPinned 397 - ? _(msg`Unpin from profile`) 398 - : _(msg`Pin to your profile`) 399 - } 400 - disabled={isPinPending} 401 - onPress={onPressPin}> 402 - <Menu.ItemText> 403 - {isPinned 404 - ? _(msg`Unpin from profile`) 405 - : _(msg`Pin to your profile`)} 406 - </Menu.ItemText> 407 - <Menu.ItemIcon 408 - icon={isPinPending ? Loader : PinIcon} 409 - position="right" 410 - /> 411 - </Menu.Item> 412 - </Menu.Group> 413 - <Menu.Divider /> 414 - </> 415 - )} 416 417 - <Menu.Group> 418 - {(!hideInPWI || hasSession) && ( 419 - <> 420 - <Menu.Item 421 - testID="postDropdownTranslateBtn" 422 - label={_(msg`Translate`)} 423 - onPress={onPressTranslate}> 424 - <Menu.ItemText>{_(msg`Translate`)}</Menu.ItemText> 425 - <Menu.ItemIcon icon={Translate} position="right" /> 426 - </Menu.Item> 427 428 - <Menu.Item 429 - testID="postDropdownCopyTextBtn" 430 - label={_(msg`Copy post text`)} 431 - onPress={onCopyPostText}> 432 - <Menu.ItemText>{_(msg`Copy post text`)}</Menu.ItemText> 433 - <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 434 - </Menu.Item> 435 - </> 436 - )} 437 438 - {hasSession && ( 439 - <Menu.Item 440 - testID="postDropdownSendViaDMBtn" 441 - label={_(msg`Send via direct message`)} 442 - onPress={() => sendViaChatControl.open()}> 443 - <Menu.ItemText> 444 - <Trans>Send via direct message</Trans> 445 - </Menu.ItemText> 446 - <Menu.ItemIcon icon={Send} position="right" /> 447 - </Menu.Item> 448 - )} 449 450 <Menu.Item 451 - testID="postDropdownShareBtn" 452 - label={isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} 453 - onPress={() => { 454 - if (showLoggedOutWarning) { 455 - loggedOutWarningPromptControl.open() 456 - } else { 457 - onSharePost() 458 - } 459 - }}> 460 <Menu.ItemText> 461 - {isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} 462 </Menu.ItemText> 463 - <Menu.ItemIcon icon={Share} position="right" /> 464 </Menu.Item> 465 466 - {canEmbed && ( 467 - <Menu.Item 468 - testID="postDropdownEmbedBtn" 469 - label={_(msg`Embed post`)} 470 - onPress={() => embedPostControl.open()}> 471 - <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText> 472 - <Menu.ItemIcon icon={CodeBrackets} position="right" /> 473 - </Menu.Item> 474 - )} 475 </Menu.Group> 476 477 - {hasSession && feedFeedback.enabled && ( 478 - <> 479 - <Menu.Divider /> 480 - <Menu.Group> 481 <Menu.Item 482 - testID="postDropdownShowMoreBtn" 483 - label={_(msg`Show more like this`)} 484 - onPress={onPressShowMore}> 485 - <Menu.ItemText>{_(msg`Show more like this`)}</Menu.ItemText> 486 - <Menu.ItemIcon icon={EmojiSmile} position="right" /> 487 </Menu.Item> 488 - 489 <Menu.Item 490 - testID="postDropdownShowLessBtn" 491 - label={_(msg`Show less like this`)} 492 - onPress={onPressShowLess}> 493 - <Menu.ItemText>{_(msg`Show less like this`)}</Menu.ItemText> 494 - <Menu.ItemIcon icon={EmojiSad} position="right" /> 495 </Menu.Item> 496 - </Menu.Group> 497 - </> 498 - )} 499 500 - {hasSession && ( 501 - <> 502 - <Menu.Divider /> 503 - <Menu.Group> 504 <Menu.Item 505 - testID="postDropdownMuteThreadBtn" 506 label={ 507 - isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`) 508 } 509 - onPress={onToggleThreadMute}> 510 <Menu.ItemText> 511 - {isThreadMuted 512 - ? _(msg`Unmute thread`) 513 - : _(msg`Mute thread`)} 514 </Menu.ItemText> 515 <Menu.ItemIcon 516 - icon={isThreadMuted ? Unmute : Mute} 517 position="right" 518 /> 519 </Menu.Item> 520 521 - <Menu.Item 522 - testID="postDropdownMuteWordsBtn" 523 - label={_(msg`Mute words & tags`)} 524 - onPress={() => mutedWordsDialogControl.open()}> 525 - <Menu.ItemText>{_(msg`Mute words & tags`)}</Menu.ItemText> 526 - <Menu.ItemIcon icon={Filter} position="right" /> 527 - </Menu.Item> 528 - </Menu.Group> 529 - </> 530 - )} 531 532 - {hasSession && 533 - (canHideReplyForEveryone || canDetachQuote || canHidePostForMe) && ( 534 <> 535 - <Menu.Divider /> 536 - <Menu.Group> 537 - {canHidePostForMe && ( 538 - <Menu.Item 539 - testID="postDropdownHideBtn" 540 - label={ 541 - isReply 542 - ? _(msg`Hide reply for me`) 543 - : _(msg`Hide post for me`) 544 - } 545 - onPress={() => hidePromptControl.open()}> 546 - <Menu.ItemText> 547 - {isReply 548 - ? _(msg`Hide reply for me`) 549 - : _(msg`Hide post for me`)} 550 - </Menu.ItemText> 551 - <Menu.ItemIcon icon={EyeSlash} position="right" /> 552 - </Menu.Item> 553 - )} 554 - {canHideReplyForEveryone && ( 555 - <Menu.Item 556 - testID="postDropdownHideBtn" 557 - label={ 558 - isReplyHiddenByThreadgate 559 - ? _(msg`Show reply for everyone`) 560 - : _(msg`Hide reply for everyone`) 561 - } 562 - onPress={ 563 - isReplyHiddenByThreadgate 564 - ? onToggleReplyVisibility 565 - : () => hideReplyConfirmControl.open() 566 - }> 567 - <Menu.ItemText> 568 - {isReplyHiddenByThreadgate 569 - ? _(msg`Show reply for everyone`) 570 - : _(msg`Hide reply for everyone`)} 571 - </Menu.ItemText> 572 - <Menu.ItemIcon 573 - icon={isReplyHiddenByThreadgate ? Eye : EyeSlash} 574 - position="right" 575 - /> 576 - </Menu.Item> 577 - )} 578 - 579 - {canDetachQuote && ( 580 - <Menu.Item 581 - disabled={isDetachPending} 582 - testID="postDropdownHideBtn" 583 - label={ 584 - quoteEmbed.isDetached 585 - ? _(msg`Re-attach quote`) 586 - : _(msg`Detach quote`) 587 - } 588 - onPress={ 589 - quoteEmbed.isDetached 590 - ? onToggleQuotePostAttachment 591 - : () => quotePostDetachConfirmControl.open() 592 - }> 593 - <Menu.ItemText> 594 - {quoteEmbed.isDetached 595 - ? _(msg`Re-attach quote`) 596 - : _(msg`Detach quote`)} 597 - </Menu.ItemText> 598 - <Menu.ItemIcon 599 - icon={ 600 - isDetachPending 601 - ? Loader 602 - : quoteEmbed.isDetached 603 - ? Eye 604 - : EyeSlash 605 - } 606 - position="right" 607 - /> 608 - </Menu.Item> 609 - )} 610 - </Menu.Group> 611 </> 612 )} 613 - 614 - {hasSession && ( 615 - <> 616 - <Menu.Divider /> 617 - <Menu.Group> 618 - {!isAuthor && ( 619 - <Menu.Item 620 - testID="postDropdownReportBtn" 621 - label={_(msg`Report post`)} 622 - onPress={() => reportDialogControl.open()}> 623 - <Menu.ItemText>{_(msg`Report post`)}</Menu.ItemText> 624 - <Menu.ItemIcon icon={Warning} position="right" /> 625 - </Menu.Item> 626 - )} 627 - 628 - {isAuthor && ( 629 - <> 630 - <Menu.Item 631 - testID="postDropdownEditPostInteractions" 632 - label={_(msg`Edit interaction settings`)} 633 - onPress={() => 634 - postInteractionSettingsDialogControl.open() 635 - } 636 - {...(isAuthor 637 - ? Platform.select({ 638 - web: { 639 - onHoverIn: prefetchPostInteractionSettings, 640 - }, 641 - native: { 642 - onPressIn: prefetchPostInteractionSettings, 643 - }, 644 - }) 645 - : {})}> 646 - <Menu.ItemText> 647 - {_(msg`Edit interaction settings`)} 648 - </Menu.ItemText> 649 - <Menu.ItemIcon icon={Gear} position="right" /> 650 - </Menu.Item> 651 - <Menu.Item 652 - testID="postDropdownDeleteBtn" 653 - label={_(msg`Delete post`)} 654 - onPress={() => deletePromptControl.open()}> 655 - <Menu.ItemText>{_(msg`Delete post`)}</Menu.ItemText> 656 - <Menu.ItemIcon icon={Trash} position="right" /> 657 - </Menu.Item> 658 - </> 659 - )} 660 - </Menu.Group> 661 - </> 662 - )} 663 - </Menu.Outer> 664 - </Menu.Root> 665 666 <Prompt.Basic 667 control={deletePromptControl} ··· 745 onConfirm={onToggleReplyVisibility} 746 confirmButtonCta={_(msg`Yes, hide`)} 747 /> 748 - </EventStopper> 749 ) 750 } 751 - 752 - PostDropdownBtn = memo(PostDropdownBtn) 753 - export {PostDropdownBtn}
··· 102 timestamp: string 103 threadgateRecord?: AppBskyFeedThreadgate.Record 104 }): React.ReactNode => { 105 const theme = useTheme() 106 const alf = useAlf() 107 + const {_} = useLingui() 108 + const defaultCtrlColor = theme.palette.default.postCtrl 109 + return ( 110 + <EventStopper onKeyDown={false}> 111 + <Menu.Root> 112 + <Menu.Trigger label={_(msg`Open post options menu`)}> 113 + {({props, state}) => { 114 + return ( 115 + <Pressable 116 + {...props} 117 + hitSlop={hitSlop} 118 + testID={testID} 119 + style={[ 120 + style, 121 + a.rounded_full, 122 + (state.hovered || state.pressed) && [ 123 + alf.atoms.bg_contrast_25, 124 + ], 125 + ]}> 126 + <DotsHorizontal 127 + fill={defaultCtrlColor} 128 + style={{pointerEvents: 'none'}} 129 + size={size} 130 + /> 131 + </Pressable> 132 + ) 133 + }} 134 + </Menu.Trigger> 135 + <Menu.Outer> 136 + <PostDropdownMenuItems 137 + testID={testID} 138 + post={post} 139 + postFeedContext={postFeedContext} 140 + record={record} 141 + richText={richText} 142 + timestamp={timestamp} 143 + threadgateRecord={threadgateRecord} 144 + /> 145 + </Menu.Outer> 146 + </Menu.Root> 147 + </EventStopper> 148 + ) 149 + } 150 + 151 + PostDropdownBtn = memo(PostDropdownBtn) 152 + export {PostDropdownBtn} 153 + 154 + let PostDropdownMenuItems = ({ 155 + post, 156 + postFeedContext, 157 + record, 158 + richText, 159 + timestamp, 160 + threadgateRecord, 161 + }: { 162 + testID: string 163 + post: Shadow<AppBskyFeedDefs.PostView> 164 + postFeedContext: string | undefined 165 + record: AppBskyFeedPost.Record 166 + richText: RichTextAPI 167 + style?: StyleProp<ViewStyle> 168 + hitSlop?: PressableProps['hitSlop'] 169 + size?: 'lg' | 'md' | 'sm' 170 + timestamp: string 171 + threadgateRecord?: AppBskyFeedThreadgate.Record 172 + }): React.ReactNode => { 173 + const {hasSession, currentAccount} = useSession() 174 const {gtMobile} = useBreakpoints() 175 const {_} = useLingui() 176 const langPrefs = useLanguagePrefs() 177 const {mutateAsync: deletePostMutate} = usePostDeleteMutation() 178 const {mutateAsync: pinPostMutate, isPending: isPinPending} = ··· 425 }, [isPinned, pinPostMutate, postCid, postUri]) 426 427 return ( 428 + <> 429 + {isAuthor && ( 430 + <> 431 + <Menu.Group> 432 + <Menu.Item 433 + testID="pinPostBtn" 434 + label={ 435 + isPinned 436 + ? _(msg`Unpin from profile`) 437 + : _(msg`Pin to your profile`) 438 + } 439 + disabled={isPinPending} 440 + onPress={onPressPin}> 441 + <Menu.ItemText> 442 + {isPinned 443 + ? _(msg`Unpin from profile`) 444 + : _(msg`Pin to your profile`)} 445 + </Menu.ItemText> 446 + <Menu.ItemIcon 447 + icon={isPinPending ? Loader : PinIcon} 448 + position="right" 449 + /> 450 + </Menu.Item> 451 + </Menu.Group> 452 + <Menu.Divider /> 453 + </> 454 + )} 455 456 + <Menu.Group> 457 + {(!hideInPWI || hasSession) && ( 458 + <> 459 + <Menu.Item 460 + testID="postDropdownTranslateBtn" 461 + label={_(msg`Translate`)} 462 + onPress={onPressTranslate}> 463 + <Menu.ItemText>{_(msg`Translate`)}</Menu.ItemText> 464 + <Menu.ItemIcon icon={Translate} position="right" /> 465 + </Menu.Item> 466 467 + <Menu.Item 468 + testID="postDropdownCopyTextBtn" 469 + label={_(msg`Copy post text`)} 470 + onPress={onCopyPostText}> 471 + <Menu.ItemText>{_(msg`Copy post text`)}</Menu.ItemText> 472 + <Menu.ItemIcon icon={ClipboardIcon} position="right" /> 473 + </Menu.Item> 474 + </> 475 + )} 476 477 + {hasSession && ( 478 + <Menu.Item 479 + testID="postDropdownSendViaDMBtn" 480 + label={_(msg`Send via direct message`)} 481 + onPress={() => sendViaChatControl.open()}> 482 + <Menu.ItemText> 483 + <Trans>Send via direct message</Trans> 484 + </Menu.ItemText> 485 + <Menu.ItemIcon icon={Send} position="right" /> 486 + </Menu.Item> 487 + )} 488 489 + <Menu.Item 490 + testID="postDropdownShareBtn" 491 + label={isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} 492 + onPress={() => { 493 + if (showLoggedOutWarning) { 494 + loggedOutWarningPromptControl.open() 495 + } else { 496 + onSharePost() 497 + } 498 + }}> 499 + <Menu.ItemText> 500 + {isWeb ? _(msg`Copy link to post`) : _(msg`Share`)} 501 + </Menu.ItemText> 502 + <Menu.ItemIcon icon={Share} position="right" /> 503 + </Menu.Item> 504 505 + {canEmbed && ( 506 + <Menu.Item 507 + testID="postDropdownEmbedBtn" 508 + label={_(msg`Embed post`)} 509 + onPress={() => embedPostControl.open()}> 510 + <Menu.ItemText>{_(msg`Embed post`)}</Menu.ItemText> 511 + <Menu.ItemIcon icon={CodeBrackets} position="right" /> 512 + </Menu.Item> 513 + )} 514 + </Menu.Group> 515 + 516 + {hasSession && feedFeedback.enabled && ( 517 + <> 518 + <Menu.Divider /> 519 + <Menu.Group> 520 <Menu.Item 521 + testID="postDropdownShowMoreBtn" 522 + label={_(msg`Show more like this`)} 523 + onPress={onPressShowMore}> 524 + <Menu.ItemText>{_(msg`Show more like this`)}</Menu.ItemText> 525 + <Menu.ItemIcon icon={EmojiSmile} position="right" /> 526 + </Menu.Item> 527 + 528 + <Menu.Item 529 + testID="postDropdownShowLessBtn" 530 + label={_(msg`Show less like this`)} 531 + onPress={onPressShowLess}> 532 + <Menu.ItemText>{_(msg`Show less like this`)}</Menu.ItemText> 533 + <Menu.ItemIcon icon={EmojiSad} position="right" /> 534 + </Menu.Item> 535 + </Menu.Group> 536 + </> 537 + )} 538 + 539 + {hasSession && ( 540 + <> 541 + <Menu.Divider /> 542 + <Menu.Group> 543 + <Menu.Item 544 + testID="postDropdownMuteThreadBtn" 545 + label={ 546 + isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`) 547 + } 548 + onPress={onToggleThreadMute}> 549 <Menu.ItemText> 550 + {isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`)} 551 </Menu.ItemText> 552 + <Menu.ItemIcon 553 + icon={isThreadMuted ? Unmute : Mute} 554 + position="right" 555 + /> 556 </Menu.Item> 557 558 + <Menu.Item 559 + testID="postDropdownMuteWordsBtn" 560 + label={_(msg`Mute words & tags`)} 561 + onPress={() => mutedWordsDialogControl.open()}> 562 + <Menu.ItemText>{_(msg`Mute words & tags`)}</Menu.ItemText> 563 + <Menu.ItemIcon icon={Filter} position="right" /> 564 + </Menu.Item> 565 </Menu.Group> 566 + </> 567 + )} 568 569 + {hasSession && 570 + (canHideReplyForEveryone || canDetachQuote || canHidePostForMe) && ( 571 + <> 572 + <Menu.Divider /> 573 + <Menu.Group> 574 + {canHidePostForMe && ( 575 <Menu.Item 576 + testID="postDropdownHideBtn" 577 + label={ 578 + isReply 579 + ? _(msg`Hide reply for me`) 580 + : _(msg`Hide post for me`) 581 + } 582 + onPress={() => hidePromptControl.open()}> 583 + <Menu.ItemText> 584 + {isReply 585 + ? _(msg`Hide reply for me`) 586 + : _(msg`Hide post for me`)} 587 + </Menu.ItemText> 588 + <Menu.ItemIcon icon={EyeSlash} position="right" /> 589 </Menu.Item> 590 + )} 591 + {canHideReplyForEveryone && ( 592 <Menu.Item 593 + testID="postDropdownHideBtn" 594 + label={ 595 + isReplyHiddenByThreadgate 596 + ? _(msg`Show reply for everyone`) 597 + : _(msg`Hide reply for everyone`) 598 + } 599 + onPress={ 600 + isReplyHiddenByThreadgate 601 + ? onToggleReplyVisibility 602 + : () => hideReplyConfirmControl.open() 603 + }> 604 + <Menu.ItemText> 605 + {isReplyHiddenByThreadgate 606 + ? _(msg`Show reply for everyone`) 607 + : _(msg`Hide reply for everyone`)} 608 + </Menu.ItemText> 609 + <Menu.ItemIcon 610 + icon={isReplyHiddenByThreadgate ? Eye : EyeSlash} 611 + position="right" 612 + /> 613 </Menu.Item> 614 + )} 615 616 + {canDetachQuote && ( 617 <Menu.Item 618 + disabled={isDetachPending} 619 + testID="postDropdownHideBtn" 620 label={ 621 + quoteEmbed.isDetached 622 + ? _(msg`Re-attach quote`) 623 + : _(msg`Detach quote`) 624 } 625 + onPress={ 626 + quoteEmbed.isDetached 627 + ? onToggleQuotePostAttachment 628 + : () => quotePostDetachConfirmControl.open() 629 + }> 630 <Menu.ItemText> 631 + {quoteEmbed.isDetached 632 + ? _(msg`Re-attach quote`) 633 + : _(msg`Detach quote`)} 634 </Menu.ItemText> 635 <Menu.ItemIcon 636 + icon={ 637 + isDetachPending 638 + ? Loader 639 + : quoteEmbed.isDetached 640 + ? Eye 641 + : EyeSlash 642 + } 643 position="right" 644 /> 645 </Menu.Item> 646 + )} 647 + </Menu.Group> 648 + </> 649 + )} 650 651 + {hasSession && ( 652 + <> 653 + <Menu.Divider /> 654 + <Menu.Group> 655 + {!isAuthor && ( 656 + <Menu.Item 657 + testID="postDropdownReportBtn" 658 + label={_(msg`Report post`)} 659 + onPress={() => reportDialogControl.open()}> 660 + <Menu.ItemText>{_(msg`Report post`)}</Menu.ItemText> 661 + <Menu.ItemIcon icon={Warning} position="right" /> 662 + </Menu.Item> 663 + )} 664 665 + {isAuthor && ( 666 <> 667 + <Menu.Item 668 + testID="postDropdownEditPostInteractions" 669 + label={_(msg`Edit interaction settings`)} 670 + onPress={() => postInteractionSettingsDialogControl.open()} 671 + {...(isAuthor 672 + ? Platform.select({ 673 + web: { 674 + onHoverIn: prefetchPostInteractionSettings, 675 + }, 676 + native: { 677 + onPressIn: prefetchPostInteractionSettings, 678 + }, 679 + }) 680 + : {})}> 681 + <Menu.ItemText> 682 + {_(msg`Edit interaction settings`)} 683 + </Menu.ItemText> 684 + <Menu.ItemIcon icon={Gear} position="right" /> 685 + </Menu.Item> 686 + <Menu.Item 687 + testID="postDropdownDeleteBtn" 688 + label={_(msg`Delete post`)} 689 + onPress={() => deletePromptControl.open()}> 690 + <Menu.ItemText>{_(msg`Delete post`)}</Menu.ItemText> 691 + <Menu.ItemIcon icon={Trash} position="right" /> 692 + </Menu.Item> 693 </> 694 )} 695 + </Menu.Group> 696 + </> 697 + )} 698 699 <Prompt.Basic 700 control={deletePromptControl} ··· 778 onConfirm={onToggleReplyVisibility} 779 confirmButtonCta={_(msg`Yes, hide`)} 780 /> 781 + </> 782 ) 783 } 784 + PostDropdownMenuItems = memo(PostDropdownMenuItems)