fix(app): backport rendering improvements (#1580)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-13 19:52:00 +02:00
1 parent 0c291a01da
commit 42cdc1f877
513 files changed
+98743 -70397

No files matched your search

@@ -4,6 +4,7 @@ import * as Modal from '@app/features/app/components/dialogs/Modal';
import {useCursorAtEnd} from '@app/features/app/hooks/useCursorAtEnd';
import Guilds from '@app/features/guild/state/Guilds';
import {CANCEL_DESCRIPTOR, FOLDER_SETTINGS_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Button} from '@app/features/ui/button/Button';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {ColorPickerField} from '@app/features/ui/components/form/ColorPickerField';
@@ -92,23 +93,33 @@ const SAVE_DESCRIPTOR = msg({
});
const FOLDER_ICON_MAP: Record<GuildFolderIcon, ReactNode> = {
[GuildFolderIcons.FOLDER]: (
<FolderIcon weight="fill" size={18} data-flx="guild.guild-folder-settings-modal.folder-icon" />
<FolderIcon weight="fill" size={remFromPx(18)} data-flx="guild.guild-folder-settings-modal.folder-icon" />
),
[GuildFolderIcons.STAR]: (
<StarIcon weight="fill" size={remFromPx(18)} data-flx="guild.guild-folder-settings-modal.star-icon" />
),
[GuildFolderIcons.STAR]: <StarIcon weight="fill" size={18} data-flx="guild.guild-folder-settings-modal.star-icon" />,
[GuildFolderIcons.HEART]: (
<HeartIcon weight="fill" size={18} data-flx="guild.guild-folder-settings-modal.heart-icon" />
<HeartIcon weight="fill" size={remFromPx(18)} data-flx="guild.guild-folder-settings-modal.heart-icon" />
),
[GuildFolderIcons.BOOKMARK]: (
<BookmarkSimpleIcon weight="fill" size={18} data-flx="guild.guild-folder-settings-modal.bookmark-simple-icon" />
<BookmarkSimpleIcon
weight="fill"
size={remFromPx(18)}
data-flx="guild.guild-folder-settings-modal.bookmark-simple-icon"
/>
),
[GuildFolderIcons.GAME_CONTROLLER]: (
<GameControllerIcon weight="fill" size={18} data-flx="guild.guild-folder-settings-modal.game-controller-icon" />
<GameControllerIcon
weight="fill"
size={remFromPx(18)}
data-flx="guild.guild-folder-settings-modal.game-controller-icon"
/>
),
[GuildFolderIcons.SHIELD]: (
<ShieldIcon weight="fill" size={18} data-flx="guild.guild-folder-settings-modal.shield-icon" />
<ShieldIcon weight="fill" size={remFromPx(18)} data-flx="guild.guild-folder-settings-modal.shield-icon" />
),
[GuildFolderIcons.MUSIC_NOTE]: (
<MusicNoteIcon weight="fill" size={18} data-flx="guild.guild-folder-settings-modal.music-note-icon" />
<MusicNoteIcon weight="fill" size={remFromPx(18)} data-flx="guild.guild-folder-settings-modal.music-note-icon" />
),
};
@@ -172,7 +183,7 @@ export const GuildFolderSettingsModal = observer(({folderId}: GuildFolderSetting
const renderIconOption = useCallback(
(option: ComboboxOption<GuildFolderIcon>, _isSelected: boolean) => (
<span
style={{display: 'flex', alignItems: 'center', gap: 8}}
style={{display: 'flex', alignItems: 'center', gap: remFromPx(8)}}
data-flx="guild.guild-folder-settings-modal.render-icon-option.span"
>
{FOLDER_ICON_MAP[option.value]}
@@ -17,6 +17,7 @@ import {
} from '@app/features/i18n/utils/CommonMessageDescriptors';
import * as GuildMemberCommands from '@app/features/member/commands/GuildMemberCommands';
import GuildMembers from '@app/features/member/state/GuildMembers';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Checkbox} from '@app/features/ui/checkbox/Checkbox';
import {Combobox} from '@app/features/ui/components/form/FormCombobox';
import {Switch} from '@app/features/ui/components/form/FormSwitch';
@@ -490,7 +491,7 @@ export const GuildNotificationSettingsModal = observer(({guildId}: {guildId: str
>
{isCategory ? (
<FolderIcon
size={20}
size={remFromPx(20)}
className={styles.channelIcon}
data-flx="guild.guild-notification-settings-modal.channel-icon"
/>
@@ -527,7 +528,11 @@ export const GuildNotificationSettingsModal = observer(({guildId}: {guildId: str
aria-label={i18n._(REMOVE_OVERRIDE_BUTTON_ARIA_DESCRIPTOR)}
data-flx="guild.guild-notification-settings-modal.remove-button.remove-override"
>
<XIcon size={14} weight="bold" data-flx="guild.guild-notification-settings-modal.x-icon" />
<XIcon
size={remFromPx(14)}
weight="bold"
data-flx="guild.guild-notification-settings-modal.x-icon"
/>
</button>
</div>
<div
@@ -38,6 +38,7 @@ import {
} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {openFilePicker} from '@app/features/messaging/utils/FilePickerUtils';
import * as NavigationCommands from '@app/features/navigation/commands/NavigationCommands';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Button} from '@app/features/ui/button/Button';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {modal} from '@app/features/ui/commands/ModalCommands';
@@ -198,7 +199,7 @@ export const GuildCreateForm = observer(() => {
data-flx="guild.add-guild-modal.guild-create-form.verification-notice"
>
<EnvelopeSimpleIcon
size={32}
size={remFromPx(32)}
weight="fill"
data-flx="guild.add-guild-modal.guild-create-form.envelope-simple-icon"
/>
@@ -223,7 +224,7 @@ export const GuildCreateForm = observer(() => {
data-flx="guild.add-guild-modal.guild-create-form.verification-notice--2"
>
<EnvelopeSimpleIcon
size={32}
size={remFromPx(32)}
weight="fill"
data-flx="guild.add-guild-modal.guild-create-form.envelope-simple-icon--2"
/>
@@ -11,6 +11,7 @@ import {
CREATE_COMMUNITY_DESCRIPTOR,
JOIN_COMMUNITY_DESCRIPTOR,
} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {THE_OTHER_PLATFORM} from '@fluxer/constants/src/ExternalPlatformConstants';
import {Trans, useLingui} from '@lingui/react/macro';
import {DownloadSimpleIcon, HouseIcon, LinkIcon} from '@phosphor-icons/react';
@@ -52,20 +53,27 @@ export const LandingView = observer(({onViewChange}: {onViewChange: (view: AddGu
<div className={styles.actionButtons} data-flx="guild.add-guild-modal.landing-view.action-buttons">
<ActionButton
onClick={() => onViewChange('create_guild')}
icon={<HouseIcon size={24} data-flx="guild.add-guild-modal.landing-view.house-icon" />}
icon={<HouseIcon size={remFromPx(24)} data-flx="guild.add-guild-modal.landing-view.house-icon" />}
label={i18n._(CREATE_COMMUNITY_DESCRIPTOR)}
data-flx="guild.add-guild-modal.landing-view.action-button.view-change"
/>
<ActionButton
onClick={() => onViewChange('join_guild')}
icon={<LinkIcon size={24} weight="bold" data-flx="guild.add-guild-modal.landing-view.link-icon" />}
icon={
<LinkIcon size={remFromPx(24)} weight="bold" data-flx="guild.add-guild-modal.landing-view.link-icon" />
}
label={i18n._(JOIN_COMMUNITY_DESCRIPTOR)}
data-flx="guild.add-guild-modal.landing-view.action-button.view-change--2"
/>
</div>
<ActionButton
onClick={() => onViewChange('import_template')}
icon={<DownloadSimpleIcon size={24} data-flx="guild.add-guild-modal.landing-view.download-simple-icon" />}
icon={
<DownloadSimpleIcon
size={remFromPx(24)}
data-flx="guild.add-guild-modal.landing-view.download-simple-icon"
/>
}
label={i18n._(IMPORT_THE_OTHER_PLATFORM_TEMPLATE_DESCRIPTOR, {theOtherPlatform: THE_OTHER_PLATFORM})}
data-flx="guild.add-guild-modal.landing-view.action-button.view-change--3"
/>
@@ -54,6 +54,7 @@ import {
import {openExternalUrlWithWarning} from '@app/features/messaging/utils/ExternalLinkUtils';
import {openFilePicker} from '@app/features/messaging/utils/FilePickerUtils';
import * as NavigationCommands from '@app/features/navigation/commands/NavigationCommands';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Button} from '@app/features/ui/button/Button';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {modal} from '@app/features/ui/commands/ModalCommands';
@@ -423,7 +424,7 @@ export const TemplateImportForm = observer(() => {
data-flx="guild.add-guild-modal.template-import-form.verification-notice"
>
<EnvelopeSimpleIcon
size={32}
size={remFromPx(32)}
weight="fill"
data-flx="guild.add-guild-modal.template-import-form.envelope-simple-icon"
/>
@@ -448,7 +449,7 @@ export const TemplateImportForm = observer(() => {
data-flx="guild.add-guild-modal.template-import-form.verification-notice--2"
>
<EnvelopeSimpleIcon
size={32}
size={remFromPx(32)}
weight="fill"
data-flx="guild.add-guild-modal.template-import-form.envelope-simple-icon--2"
/>
@@ -518,7 +519,10 @@ export const TemplateImportForm = observer(() => {
className={styles.openApiUrlButton}
data-flx="guild.add-guild-modal.template-import-form.open-api-url-button"
>
<ArrowSquareOutIcon size={16} data-flx="guild.add-guild-modal.template-import-form.arrow-square-out-icon" />
<ArrowSquareOutIcon
size={remFromPx(16)}
data-flx="guild.add-guild-modal.template-import-form.arrow-square-out-icon"
/>
<Trans>Open template JSON</Trans>
</span>
</Button>
@@ -40,6 +40,7 @@ import {
import {TRY_AGAIN_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import GuildMembers from '@app/features/member/state/GuildMembers';
import {Logger} from '@app/features/platform/utils/AppLogger';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Button} from '@app/features/ui/button/Button';
import {Avatar} from '@app/features/ui/components/Avatar';
import {Combobox, type ComboboxOption} from '@app/features/ui/components/form/FormCombobox';
@@ -337,7 +338,11 @@ const GuildAuditLogTab: React.FC<{guildId: string}> = observer(({guildId}) => {
aria-hidden
data-flx="guild.guild-tabs.guild-audit-log-tab.render-content.action-select-icon"
>
<Icon size={18} weight="bold" data-flx="guild.guild-tabs.guild-audit-log-tab.render-content.icon" />
<Icon
size={remFromPx(18)}
weight="bold"
data-flx="guild.guild-tabs.guild-audit-log-tab.render-content.icon"
/>
</span>
<span
className={styles.actionSelectLabel}
@@ -547,7 +552,7 @@ const GuildAuditLogTab: React.FC<{guildId: string}> = observer(({guildId}) => {
data-flx="guild.guild-tabs.guild-audit-log-tab.change-bullet"
>
<ChangeIcon
size={12}
size={remFromPx(12)}
weight="bold"
className={styles.changeBulletIcon}
data-flx="guild.guild-tabs.guild-audit-log-tab.change-bullet-icon"
@@ -582,7 +587,7 @@ const GuildAuditLogTab: React.FC<{guildId: string}> = observer(({guildId}) => {
>
<span className={styles.icon} aria-hidden data-flx="guild.guild-tabs.guild-audit-log-tab.icon">
<ActionIcon
size={20}
size={remFromPx(20)}
weight="bold"
className={styles.iconGlyph}
data-flx="guild.guild-tabs.guild-audit-log-tab.icon-glyph"
@@ -620,7 +625,7 @@ const GuildAuditLogTab: React.FC<{guildId: string}> = observer(({guildId}) => {
</div>
</div>
<CaretDownIcon
size={20}
size={remFromPx(20)}
weight="bold"
className={clsx(styles.chevron, {[styles.chevronExpanded]: isExpandedView})}
data-flx="guild.guild-tabs.guild-audit-log-tab.chevron"
@@ -631,7 +636,7 @@ const GuildAuditLogTab: React.FC<{guildId: string}> = observer(({guildId}) => {
<div className={headerClasses} data-flx="guild.guild-tabs.guild-audit-log-tab.div--2">
<span className={styles.icon} aria-hidden data-flx="guild.guild-tabs.guild-audit-log-tab.icon--2">
<ActionIcon
size={20}
size={remFromPx(20)}
weight="bold"
className={styles.iconGlyph}
data-flx="guild.guild-tabs.guild-audit-log-tab.icon-glyph--2"
@@ -708,7 +713,7 @@ const GuildAuditLogTab: React.FC<{guildId: string}> = observer(({guildId}) => {
data-flx="guild.guild-tabs.guild-audit-log-tab.change-bullet--2"
>
<ChangeIcon
size={12}
size={remFromPx(12)}
weight="bold"
className={styles.changeBulletIcon}
data-flx="guild.guild-tabs.guild-audit-log-tab.change-bullet-icon--2"
@@ -10,6 +10,7 @@ import styles from '@app/features/guild/components/modals/guild_tabs/MemberListS
import {UserListItem} from '@app/features/guild/components/modals/guild_tabs/UserListItem';
import {CANCEL_DESCRIPTOR, TRY_AGAIN_IN_A_MOMENT_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {Logger} from '@app/features/platform/utils/AppLogger';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {BannedUserContextMenu} from '@app/features/ui/action_menu/BannedUserContextMenu';
import * as ContextMenuCommands from '@app/features/ui/commands/ContextMenuCommands';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
@@ -183,7 +184,7 @@ const GuildBansTab: React.FC<{guildId: string}> = observer(({guildId}) => {
onChange={(e) => setSearchQuery(e.target.value)}
leftIcon={
<MagnifyingGlassIcon
size={16}
size={remFromPx(16)}
weight="bold"
data-flx="guild.guild-tabs.guild-bans-tab.magnifying-glass-icon"
/>
@@ -6,6 +6,7 @@ import {showGuildErrorModal} from '@app/features/guild/components/alerts/GuildEr
import styles from '@app/features/guild/components/modals/guild_tabs/GuildDiscoveryTab.module.css';
import {TRY_AGAIN_IN_A_MOMENT_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {Logger} from '@app/features/platform/utils/AppLogger';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Button} from '@app/features/ui/button/Button';
import * as ToastCommands from '@app/features/ui/commands/ToastCommands';
import {Form} from '@app/features/ui/components/form/Form';
@@ -334,7 +335,11 @@ const GuildDiscoveryTab: React.FC<{guildId: string}> = ({guildId}) => {
<div className={styles.warning} data-flx="guild.guild-tabs.guild-discovery-tab.warning">
<div className={styles.warningContent} data-flx="guild.guild-tabs.guild-discovery-tab.warning-content">
<div className={styles.warningIcon} data-flx="guild.guild-tabs.guild-discovery-tab.warning-icon">
<WarningIcon size={20} weight="fill" data-flx="guild.guild-tabs.guild-discovery-tab.warning-icon--2" />
<WarningIcon
size={remFromPx(20)}
weight="fill"
data-flx="guild.guild-tabs.guild-discovery-tab.warning-icon--2"
/>
</div>
<div className={styles.warningBody} data-flx="guild.guild-tabs.guild-discovery-tab.warning-body">
<p className={styles.warningTitle} data-flx="guild.guild-tabs.guild-discovery-tab.warning-title">
@@ -368,7 +373,11 @@ const GuildDiscoveryTab: React.FC<{guildId: string}> = ({guildId}) => {
<div className={styles.info} data-flx="guild.guild-tabs.guild-discovery-tab.info">
<div className={styles.infoContent} data-flx="guild.guild-tabs.guild-discovery-tab.info-content">
<div className={styles.infoIcon} data-flx="guild.guild-tabs.guild-discovery-tab.info-icon">
<InfoIcon size={20} weight="fill" data-flx="guild.guild-tabs.guild-discovery-tab.info-icon--2" />
<InfoIcon
size={remFromPx(20)}
weight="fill"
data-flx="guild.guild-tabs.guild-discovery-tab.info-icon--2"
/>
</div>
<p className={styles.infoText} data-flx="guild.guild-tabs.guild-discovery-tab.info-text">
<Trans>
@@ -383,7 +392,11 @@ const GuildDiscoveryTab: React.FC<{guildId: string}> = ({guildId}) => {
<div className={styles.info} data-flx="guild.guild-tabs.guild-discovery-tab.info--2">
<div className={styles.infoContent} data-flx="guild.guild-tabs.guild-discovery-tab.info-content--2">
<div className={styles.infoIcon} data-flx="guild.guild-tabs.guild-discovery-tab.info-icon--3">
<InfoIcon size={20} weight="fill" data-flx="guild.guild-tabs.guild-discovery-tab.info-icon--4" />
<InfoIcon
size={remFromPx(20)}
weight="fill"
data-flx="guild.guild-tabs.guild-discovery-tab.info-icon--4"
/>
</div>
<p className={styles.infoText} data-flx="guild.guild-tabs.guild-discovery-tab.info-text--2">
<Trans>
@@ -25,6 +25,7 @@ import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
import Permission from '@app/features/permissions/state/Permission';
import {Logger} from '@app/features/platform/utils/AppLogger';
import {failureValidationErrors} from '@app/features/platform/utils/ResponseInspection';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {modal} from '@app/features/ui/commands/ModalCommands';
import {Input} from '@app/features/ui/components/form/FormInput';
@@ -423,7 +424,7 @@ const GuildEmojiTab: React.FC<{guildId: string}> = observer(function GuildEmojiT
onChange={(e) => setSearchQuery(e.target.value)}
leftIcon={
<MagnifyingGlassIcon
size={16}
size={remFromPx(16)}
weight="bold"
data-flx="guild.guild-tabs.guild-emoji-tab.magnifying-glass-icon"
/>
@@ -436,7 +437,7 @@ const GuildEmojiTab: React.FC<{guildId: string}> = observer(function GuildEmojiT
<div className={styles.notice} data-flx="guild.guild-tabs.guild-emoji-tab.notice">
<p className={styles.noticeText} data-flx="guild.guild-tabs.guild-emoji-tab.notice-text">
<WarningCircleIcon
size={32}
size={remFromPx(32)}
weight="fill"
data-flx="guild.guild-tabs.guild-emoji-tab.warning-circle-icon"
/>
@@ -24,6 +24,7 @@ import {openFilePicker} from '@app/features/messaging/utils/FilePickerUtils';
import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
import Permission from '@app/features/permissions/state/Permission';
import {Logger} from '@app/features/platform/utils/AppLogger';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import {modal} from '@app/features/ui/commands/ModalCommands';
import {Input} from '@app/features/ui/components/form/FormInput';
@@ -238,7 +239,7 @@ const GuildStickersTab: React.FC<{guildId: string}> = observer(function GuildSti
onChange={(e) => setSearchQuery(e.target.value)}
leftIcon={
<MagnifyingGlassIcon
size={16}
size={remFromPx(16)}
weight="bold"
data-flx="guild.guild-tabs.guild-stickers-tab.magnifying-glass-icon"
/>
@@ -10,6 +10,7 @@ import styles from '@app/features/guild/components/modals/guild_tabs/GuildVanity
import Guilds from '@app/features/guild/state/Guilds';
import * as PermissionUtils from '@app/features/permissions/utils/PermissionUtils';
import {Logger} from '@app/features/platform/utils/AppLogger';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Button} from '@app/features/ui/button/Button';
import * as ToastCommands from '@app/features/ui/commands/ToastCommands';
import {Form} from '@app/features/ui/components/form/Form';
@@ -179,7 +180,7 @@ const GuildVanityURLTab: React.FC<{guildId: string}> = observer(({guildId}) => {
<div className={styles.container} data-flx="guild.guild-tabs.guild-vanity-url-tab.container">
{errorMessage && (
<div className={styles.errorAlert} role="alert" data-flx="guild.guild-tabs.guild-vanity-url-tab.error-alert">
<WarningIcon size={16} weight="fill" data-flx="guild.guild-tabs.guild-vanity-url-tab.error-icon" />
<WarningIcon size={remFromPx(16)} weight="fill" data-flx="guild.guild-tabs.guild-vanity-url-tab.error-icon" />
<span data-flx="guild.guild-tabs.guild-vanity-url-tab.error-message">{errorMessage}</span>
</div>
)}
@@ -187,7 +188,11 @@ const GuildVanityURLTab: React.FC<{guildId: string}> = observer(({guildId}) => {
<div className={styles.warning} data-flx="guild.guild-tabs.guild-vanity-url-tab.warning">
<div className={styles.warningContent} data-flx="guild.guild-tabs.guild-vanity-url-tab.warning-content">
<div className={styles.warningIcon} data-flx="guild.guild-tabs.guild-vanity-url-tab.warning-icon">
<WarningIcon size={20} weight="fill" data-flx="guild.guild-tabs.guild-vanity-url-tab.warning-icon--2" />
<WarningIcon
size={remFromPx(20)}
weight="fill"
data-flx="guild.guild-tabs.guild-vanity-url-tab.warning-icon--2"
/>
</div>
<div className={styles.warningBody} data-flx="guild.guild-tabs.guild-vanity-url-tab.warning-body">
<p className={styles.warningTitle} data-flx="guild.guild-tabs.guild-vanity-url-tab.warning-title">
@@ -257,7 +262,9 @@ const GuildVanityURLTab: React.FC<{guildId: string}> = observer(({guildId}) => {
variant="secondary"
submitting={isRemoving}
disabled={!vanityCode || isSubmitting}
leftIcon={<LinkBreakIcon size={16} data-flx="guild.guild-tabs.guild-vanity-url-tab.link-break-icon" />}
leftIcon={
<LinkBreakIcon size={remFromPx(16)} data-flx="guild.guild-tabs.guild-vanity-url-tab.link-break-icon" />
}
onClick={handleRemoveVanityURL}
data-flx="guild.guild-tabs.guild-vanity-url-tab.button.remove"
>
@@ -4,6 +4,7 @@ import {LongPressable} from '@app/features/app/components/LongPressable';
import {usePressable} from '@app/features/app/hooks/usePressable';
import styles from '@app/features/guild/components/modals/guild_tabs/MemberListStyles.module.css';
import {MORE_OPTIONS_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Tooltip} from '@app/features/ui/tooltip/Tooltip';
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
import {useLingui} from '@lingui/react/macro';
@@ -108,7 +109,7 @@ export const UserListItem: React.FC<UserListItemProps> = observer(
{isMobile ? (
<CaretRightIcon
weight="bold"
size={20}
size={remFromPx(20)}
className={styles.chevron}
data-flx="guild.guild-tabs.user-list-item.chevron"
/>
@@ -2,6 +2,7 @@
import styles from '@app/features/guild/components/modals/guild_tabs/guild_overview_tab/GuildOverviewTab.module.css';
import type {FormInputs} from '@app/features/guild/utils/guild_tabs/GuildOverviewTabUtils';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Button} from '@app/features/ui/button/Button';
import {CardAlignmentControls} from '@app/features/ui/card_alignment_controls/CardAlignmentControls';
import {GuildSplashCardAlignment} from '@fluxer/constants/src/GuildConstants';
@@ -57,7 +58,7 @@ export const GuildInviteSplashSettingsField: React.FC<{
data-flx="guild.guild-tabs.guild-overview-tab.fields.guild-invite-splash-settings-field.invite-page-button.preview-invite-page"
>
<EyeIcon
size={16}
size={remFromPx(16)}
weight="bold"
data-flx="guild.guild-tabs.guild-overview-tab.fields.guild-invite-splash-settings-field.eye-icon"
/>
@@ -73,7 +74,7 @@ export const GuildInviteSplashSettingsField: React.FC<{
data-flx="guild.guild-tabs.guild-overview-tab.fields.guild-invite-splash-settings-field.button.preview-invite-modal"
>
<EyeIcon
size={16}
size={remFromPx(16)}
weight="bold"
data-flx="guild.guild-tabs.guild-overview-tab.fields.guild-invite-splash-settings-field.eye-icon--2"
/>
@@ -8,6 +8,7 @@ import {
} from '@app/features/guild/components/modals/guild_tabs/guild_roles_tab/shared';
import type {GuildRole} from '@app/features/guild/models/GuildRole';
import {ROLES_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {openRoleContextMenu} from '@app/features/ui/action_menu/RoleContextMenu';
import {Button} from '@app/features/ui/button/Button';
import {Trans, useLingui} from '@lingui/react/macro';
@@ -37,7 +38,9 @@ export const MobileRoleList: React.FC<MobileRoleListProps> = observer(
<Button
variant="secondary"
small={true}
leftIcon={<PlusIcon size={18} weight="bold" data-flx="guild.guild-tabs.guild-roles-tab.plus-icon" />}
leftIcon={
<PlusIcon size={remFromPx(18)} weight="bold" data-flx="guild.guild-tabs.guild-roles-tab.plus-icon" />
}
onClick={onCreateRole}
disabled={!canManageRoles}
data-flx="guild.guild-tabs.guild-roles-tab.button.create-role"
@@ -74,7 +77,7 @@ export const MobileRoleList: React.FC<MobileRoleListProps> = observer(
)}
<CaretRightIcon
className={styles.mobileRoleChevron}
size={20}
size={remFromPx(20)}
weight="bold"
data-flx="guild.guild-tabs.guild-roles-tab.mobile-role-chevron"
/>
@@ -6,6 +6,7 @@ import type {RoleUpdate} from '@app/features/guild/components/modals/guild_tabs/
import type {GuildRole} from '@app/features/guild/models/GuildRole';
import PermissionLayout from '@app/features/permissions/state/PermissionLayout';
import type * as PermissionUtils from '@app/features/permissions/utils/PermissionUtils';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Button} from '@app/features/ui/button/Button';
import {ColorPickerField} from '@app/features/ui/components/form/ColorPickerField';
import {Input} from '@app/features/ui/components/form/FormInput';
@@ -162,7 +163,9 @@ export const RoleEditor: React.FC<RoleEditorProps> = observer(
<Button
variant="secondary"
small={true}
leftIcon={<TrashIcon size={18} data-flx="guild.guild-tabs.guild-roles-tab.trash-icon" />}
leftIcon={
<TrashIcon size={remFromPx(18)} data-flx="guild.guild-tabs.guild-roles-tab.trash-icon" />
}
onClick={onDeleteRole}
disabled={selectedRoleLocked || !canManageRoles}
data-flx="guild.guild-tabs.guild-roles-tab.button.delete-role"
@@ -253,7 +256,7 @@ export const RoleEditor: React.FC<RoleEditorProps> = observer(
onChange={(e) => onPermissionSearchQueryChange(e.target.value)}
leftIcon={
<MagnifyingGlassIcon
size={16}
size={remFromPx(16)}
weight="bold"
data-flx="guild.guild-tabs.guild-roles-tab.magnifying-glass-icon"
/>
@@ -280,9 +283,17 @@ export const RoleEditor: React.FC<RoleEditorProps> = observer(
data-flx="guild.guild-tabs.guild-roles-tab.layout-button.toggle-layout-mode"
>
{PermissionLayout.isComfy ? (
<RowsIcon size={20} weight="bold" data-flx="guild.guild-tabs.guild-roles-tab.rows-icon" />
<RowsIcon
size={remFromPx(20)}
weight="bold"
data-flx="guild.guild-tabs.guild-roles-tab.rows-icon"
/>
) : (
<ListIcon size={20} weight="bold" data-flx="guild.guild-tabs.guild-roles-tab.list-icon" />
<ListIcon
size={remFromPx(20)}
weight="bold"
data-flx="guild.guild-tabs.guild-roles-tab.list-icon"
/>
)}
</button>
</Tooltip>
@@ -302,7 +313,7 @@ export const RoleEditor: React.FC<RoleEditorProps> = observer(
data-flx="guild.guild-tabs.guild-roles-tab.layout-button.toggle-grid-mode"
>
<GridFourIcon
size={20}
size={remFromPx(20)}
weight={PermissionLayout.isGrid ? 'fill' : 'bold'}
data-flx="guild.guild-tabs.guild-roles-tab.grid-four-icon"
/>
@@ -4,19 +4,34 @@ import styles from '@app/features/guild/components/modals/guild_tabs/GuildRolesT
import {RoleItem} from '@app/features/guild/components/modals/guild_tabs/guild_roles_tab/RoleItem';
import {
applyRoleUpdate,
ROLE_DND_TYPE,
type RoleUpdate,
} from '@app/features/guild/components/modals/guild_tabs/guild_roles_tab/shared';
import type {RoleMovePreview} from '@app/features/guild/components/modals/guild_tabs/RoleMoveOperation';
import type {GuildRole} from '@app/features/guild/models/GuildRole';
import {ROLES_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
import {Button} from '@app/features/ui/button/Button';
import {useDragAutoScroll} from '@app/features/ui/hooks/useDragAutoScroll';
import {Trans, useLingui} from '@lingui/react/macro';
import {ArrowsDownUpIcon, PlusIcon} from '@phosphor-icons/react';
import {observer} from 'mobx-react-lite';
import type React from 'react';
import {DndProvider} from 'react-dnd';
import {useCallback, useRef} from 'react';
import {DndProvider, useDragLayer} from 'react-dnd';
import {HTML5Backend} from 'react-dnd-html5-backend';
const RoleDragAutoScroll: React.FC<{listRef: React.RefObject<HTMLDivElement | null>}> = ({listRef}) => {
const isDraggingRole = useDragLayer((monitor) => monitor.isDragging() && monitor.getItemType() === ROLE_DND_TYPE);
const getScrollElement = useCallback(() => {
const list = listRef.current;
if (list == null) return null;
return list.closest<HTMLElement>('[data-fluxer-scroll-container="true"]');
}, [listRef]);
useDragAutoScroll({active: isDraggingRole, getScrollElement});
return null;
};
interface RoleSidebarProps {
roles: Array<GuildRole>;
hoistedRoles: Array<GuildRole>;
@@ -68,18 +83,24 @@ export const RoleSidebar: React.FC<RoleSidebarProps> = observer(
onHoistDrop,
}) => {
const {i18n} = useLingui();
const listRef = useRef<HTMLDivElement>(null);
if (hoistOrderMode) {
return (
<div data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.div">
<div
className={styles.leftTitle}
style={{padding: '6px 8px'}}
style={{padding: `${remFromPx(6)} ${remFromPx(8)}`}}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.left-title"
>
<Trans>Hoist order</Trans>
</div>
<div
style={{padding: '0 8px 8px 8px', display: 'flex', gap: '8px', flexDirection: 'column'}}
style={{
padding: `0 ${remFromPx(8)} ${remFromPx(8)} ${remFromPx(8)}`,
display: 'flex',
gap: remFromPx(8),
flexDirection: 'column',
}}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.div--2"
>
<Button
@@ -102,10 +123,14 @@ export const RoleSidebar: React.FC<RoleSidebarProps> = observer(
</Button>
)}
</div>
<div style={{padding: '0 8px 8px 8px'}} data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.div--3">
<div
ref={listRef}
style={{padding: `0 ${remFromPx(8)} ${remFromPx(8)} ${remFromPx(8)}`}}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.div--3"
>
<p
className={styles.subtleText}
style={{marginBottom: '8px'}}
style={{marginBottom: remFromPx(8)}}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.subtle-text"
>
<Trans>Drag roles to customize the order they appear in the member list.</Trans>
@@ -114,6 +139,7 @@ export const RoleSidebar: React.FC<RoleSidebarProps> = observer(
backend={HTML5Backend}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.dnd-provider"
>
<RoleDragAutoScroll listRef={listRef} />
{hoistedRoles.map((role, index) => {
const roleWithUpdates = applyRoleUpdate(role, roleUpdates.get(role.id));
return (
@@ -149,20 +175,29 @@ export const RoleSidebar: React.FC<RoleSidebarProps> = observer(
<div data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.div--4">
<div
className={styles.leftTitle}
style={{padding: '6px 8px'}}
style={{padding: `${remFromPx(6)} ${remFromPx(8)}`}}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.left-title--2"
>
{i18n._(ROLES_DESCRIPTOR)}
</div>
<div
style={{padding: '0 8px 8px 8px', display: 'flex', gap: '8px', flexDirection: 'column'}}
style={{
padding: `0 ${remFromPx(8)} ${remFromPx(8)} ${remFromPx(8)}`,
display: 'flex',
gap: remFromPx(8),
flexDirection: 'column',
}}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.div--5"
>
<Button
variant="secondary"
small={true}
leftIcon={
<PlusIcon size={18} weight="bold" data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.plus-icon" />
<PlusIcon
size={remFromPx(18)}
weight="bold"
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.plus-icon"
/>
}
onClick={onCreateRole}
disabled={!canManageRoles}
@@ -176,7 +211,7 @@ export const RoleSidebar: React.FC<RoleSidebarProps> = observer(
small={true}
leftIcon={
<ArrowsDownUpIcon
size={18}
size={remFromPx(18)}
weight="bold"
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.arrows-down-up-icon"
/>
@@ -188,11 +223,16 @@ export const RoleSidebar: React.FC<RoleSidebarProps> = observer(
</Button>
)}
</div>
<div style={{padding: '0 8px 8px 8px'}} data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.div--6">
<div
ref={listRef}
style={{padding: `0 ${remFromPx(8)} ${remFromPx(8)} ${remFromPx(8)}`}}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.div--6"
>
<DndProvider
backend={HTML5Backend}
data-flx="guild.guild-tabs.guild-roles-tab.sidebar-content.dnd-provider--2"
>
<RoleDragAutoScroll listRef={listRef} />
{roles.map((role: GuildRole, index) => {
const roleWithUpdates = applyRoleUpdate(role, roleUpdates.get(role.id));
return (
@@ -30,6 +30,12 @@ type GuildIconStyleVars = React.CSSProperties & {
'--guild-icon-image'?: string;
};
function resolveInitiallyLoadedImageUrl(url: string | null): string | null {
if (url == null) return null;
if (ImageCacheUtils.hasImage(url)) return url;
return null;
}
export const GuildIcon = observer(function GuildIcon({
id,
name,
@@ -44,40 +50,36 @@ export const GuildIcon = observer(function GuildIcon({
const [hoverRef, isHovering] = useHover();
const iconUrl = useMemo(() => (icon ? AvatarUtils.getGuildIconURL({id, icon}) : null), [id, icon]);
const hoverIconUrl = useMemo(() => (icon ? AvatarUtils.getGuildIconURL({id, icon}, true) : null), [id, icon]);
const [isStaticLoaded, setIsStaticLoaded] = useState(() => (iconUrl ? ImageCacheUtils.hasImage(iconUrl) : false));
const [isAnimatedLoaded, setIsAnimatedLoaded] = useState(() =>
hoverIconUrl ? ImageCacheUtils.hasImage(hoverIconUrl) : false,
const [loadedStaticUrl, setLoadedStaticUrl] = useState<string | null>(() => resolveInitiallyLoadedImageUrl(iconUrl));
const [loadedAnimatedUrl, setLoadedAnimatedUrl] = useState<string | null>(() =>
resolveInitiallyLoadedImageUrl(hoverIconUrl),
);
const [shouldPlayAnimated, setShouldPlayAnimated] = useState(false);
const isStaticLoaded = iconUrl != null && loadedStaticUrl === iconUrl;
const isAnimatedLoaded = hoverIconUrl != null && loadedAnimatedUrl === hoverIconUrl;
useEffect(() => {
setIsStaticLoaded(iconUrl ? ImageCacheUtils.hasImage(iconUrl) : false);
setIsAnimatedLoaded(hoverIconUrl ? ImageCacheUtils.hasImage(hoverIconUrl) : false);
setShouldPlayAnimated(false);
}, [iconUrl, hoverIconUrl]);
useEffect(() => {
if (!iconUrl || isStaticLoaded) return;
let cancelled = false;
ImageCacheUtils.loadImage(iconUrl, () => {
if (!cancelled) setIsStaticLoaded(true);
if (iconUrl == null || iconUrl.length === 0 || isStaticLoaded) return;
let active = true;
const cleanup = ImageCacheUtils.loadImage(iconUrl, () => {
if (active) setLoadedStaticUrl(iconUrl);
});
return () => {
cancelled = true;
active = false;
cleanup();
};
}, [iconUrl, isStaticLoaded]);
useEffect(() => {
if (!isHovering || !hoverIconUrl || isAnimatedLoaded) return;
let cancelled = false;
ImageCacheUtils.loadImage(hoverIconUrl, () => {
if (!cancelled) setIsAnimatedLoaded(true);
if (!isHovering || hoverIconUrl == null || hoverIconUrl.length === 0 || isAnimatedLoaded) return;
let active = true;
const cleanup = ImageCacheUtils.loadImage(hoverIconUrl, () => {
if (active) setLoadedAnimatedUrl(hoverIconUrl);
});
return () => {
cancelled = true;
active = false;
cleanup();
};
}, [isHovering, hoverIconUrl, isAnimatedLoaded]);
useEffect(() => {
setShouldPlayAnimated(Boolean(isHovering && isAnimatedLoaded));
}, [isHovering, isAnimatedLoaded]);
const activeUrl = shouldPlayAnimated && hoverIconUrl ? hoverIconUrl : iconUrl;
const shouldPlayAnimated = isHovering && isAnimatedLoaded;
const activeUrl = shouldPlayAnimated && hoverIconUrl != null ? hoverIconUrl : iconUrl;
const styleVars: GuildIconStyleVars = {};
if (sizePx != null) {
styleVars['--guild-icon-size'] = remFromPx(sizePx);
@@ -49,7 +49,9 @@ export function handleGuildCreate(data: GuildReadyData, _context: GatewayHandler
GuildAvailability.setGuildAvailable(data.id);
Guilds.handleGuildCreate(data);
GuildCount.handleGuildCreate(data);
MemberSidebar.handleGuildCreate(data.id);
if (!isSync) {
MemberSidebar.handleGuildCreate(data.id);
}
if (!data.unavailable) {
Channels.handleGuildCreate(data);
}
@@ -34,6 +34,7 @@ export function handleGuildDelete(data: GuildDeletePayload, _context: GatewayHan
GuildAvailability.handleGuildAvailability(data.id, data.unavailable, data.unavailable_hidden);
Guilds.handleGuildDelete({guildId: data.id, unavailable: data.unavailable});
GuildList.handleGuildDelete(data.id, data.unavailable);
MemberSearch.handleGuildDelete(data.id);
GuildMembers.handleGuildDelete(data.id);
GuildCount.handleGuildDelete(data.id);
ChannelMemberCount.handleGuildDelete(data.id);
@@ -51,6 +52,5 @@ export function handleGuildDelete(data: GuildDeletePayload, _context: GatewayHan
Messages.handleGuildUnavailable(data.id, data.unavailable ?? false);
Messages.handleCleanup();
MentionFeed.handleGuildDelete(data.id);
MemberSearch.handleGuildDelete(data.id);
QuickSwitcher.recomputeIfOpen();
}
@@ -2,6 +2,7 @@
import type {GatewayHandlerContext} from '@app/features/gateway/events/EventRouter';
import GuildMembers from '@app/features/member/state/GuildMembers';
import MemberSearch from '@app/features/member/state/MemberSearch';
import Permission from '@app/features/permissions/state/Permission';
import Presence from '@app/features/presence/state/Presence';
import Users from '@app/features/user/state/Users';
@@ -14,6 +15,7 @@ interface GuildMemberRemovePayload {
export function handleGuildMemberRemove(data: GuildMemberRemovePayload, _context: GatewayHandlerContext): void {
Users.handleUserUpdate(data.user);
MemberSearch.handleMemberRemove(data.guild_id, data.user.id);
GuildMembers.handleMemberRemove(data.guild_id, data.user.id);
Permission.handleGuildMemberUpdate(data.user.id);
Presence.handleGuildMemberRemove(data.guild_id, data.user.id);