mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
fix(app): overwrite context menu hover and delete danger item (#2106)
This commit is contained in:
1 parent
8f20b29b16
commit
b1f7c78c7e
5 files changed
+136
-29
No files matched your search
+2
-1
@@ -174,7 +174,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
.overwriteItem:not(.overwriteItemSelected):hover {
|
.overwriteItem:not(.overwriteItemSelected):hover,
|
||||||
|
.overwriteItemContextMenuOpen:not(.overwriteItemSelected) {
|
||||||
background: var(--background-modifier-hover);
|
background: var(--background-modifier-hover);
|
||||||
}
|
}
|
||||||
.overwriteItemSelected {
|
.overwriteItemSelected {
|
||||||
|
|||||||
+30
-20
@@ -519,27 +519,34 @@ const ChannelPermissionsTab: React.FC<{channelId: string}> = observer(({channelI
|
|||||||
});
|
});
|
||||||
setSelectedOverwriteId(id);
|
setSelectedOverwriteId(id);
|
||||||
}, []);
|
}, []);
|
||||||
const handleDeleteOverride = useCallback(() => {
|
const removeOverwrite = useCallback(
|
||||||
if (!selectedOverwrite || !guild || selectedOverwrite.id === guild.id) return;
|
(overwriteId: string) => {
|
||||||
const futureOverwrites = overwritesWithUpdates.filter((o) => o.id !== selectedOverwrite.id);
|
if (!guild || overwriteId === guild.id) return;
|
||||||
const currentIndex = overwritesWithUpdates.findIndex((o) => o.id === selectedOverwrite.id);
|
const futureOverwrites = overwritesWithUpdates.filter((o) => o.id !== overwriteId);
|
||||||
const nextOverwrite = futureOverwrites[currentIndex] ?? futureOverwrites[0];
|
const currentIndex = overwritesWithUpdates.findIndex((o) => o.id === overwriteId);
|
||||||
if (newOverwriteIds.has(selectedOverwrite.id)) {
|
const nextOverwrite = futureOverwrites[currentIndex] ?? futureOverwrites[0];
|
||||||
setNewOverwriteIds((prev) => {
|
if (newOverwriteIds.has(overwriteId)) {
|
||||||
const newSet = new Set(prev);
|
setNewOverwriteIds((prev) => {
|
||||||
newSet.delete(selectedOverwrite.id);
|
const newSet = new Set(prev);
|
||||||
return newSet;
|
newSet.delete(overwriteId);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setDeletedOverwriteIds((prev) => new Set(prev).add(overwriteId));
|
||||||
|
}
|
||||||
|
setOverwriteUpdates((prev) => {
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
newMap.delete(overwriteId);
|
||||||
|
return newMap;
|
||||||
});
|
});
|
||||||
} else {
|
setSelectedOverwriteId((prev) => (prev === overwriteId ? (nextOverwrite?.id ?? null) : prev));
|
||||||
setDeletedOverwriteIds((prev) => new Set(prev).add(selectedOverwrite.id));
|
},
|
||||||
}
|
[guild, overwritesWithUpdates, newOverwriteIds],
|
||||||
setOverwriteUpdates((prev) => {
|
);
|
||||||
const newMap = new Map(prev);
|
const handleDeleteOverride = useCallback(() => {
|
||||||
newMap.delete(selectedOverwrite.id);
|
if (!selectedOverwrite) return;
|
||||||
return newMap;
|
removeOverwrite(selectedOverwrite.id);
|
||||||
});
|
}, [selectedOverwrite, removeOverwrite]);
|
||||||
setSelectedOverwriteId(nextOverwrite?.id ?? null);
|
|
||||||
}, [selectedOverwrite, guild, overwritesWithUpdates, newOverwriteIds]);
|
|
||||||
const getOverwriteName = useCallback(
|
const getOverwriteName = useCallback(
|
||||||
(overwrite: PermissionOverwrite): string => {
|
(overwrite: PermissionOverwrite): string => {
|
||||||
if (!guild) return '';
|
if (!guild) return '';
|
||||||
@@ -585,6 +592,8 @@ const ChannelPermissionsTab: React.FC<{channelId: string}> = observer(({channelI
|
|||||||
selectedOverwriteId={selectedOverwriteId}
|
selectedOverwriteId={selectedOverwriteId}
|
||||||
canManageChannels={canManageChannels}
|
canManageChannels={canManageChannels}
|
||||||
canManageRoles={canManageRoles}
|
canManageRoles={canManageRoles}
|
||||||
|
canManageOverwrites={canManageChannels && canManageRoles}
|
||||||
|
onDeleteOverwrite={removeOverwrite}
|
||||||
isAddOverrideOpen={isAddOverrideOpen}
|
isAddOverrideOpen={isAddOverrideOpen}
|
||||||
setIsAddOverrideOpen={setIsAddOverrideOpen}
|
setIsAddOverrideOpen={setIsAddOverrideOpen}
|
||||||
existingOverwriteIds={existingOverwriteIds}
|
existingOverwriteIds={existingOverwriteIds}
|
||||||
@@ -615,6 +624,7 @@ const ChannelPermissionsTab: React.FC<{channelId: string}> = observer(({channelI
|
|||||||
overwritesWithUpdates,
|
overwritesWithUpdates,
|
||||||
canManageChannels,
|
canManageChannels,
|
||||||
canManageRoles,
|
canManageRoles,
|
||||||
|
removeOverwrite,
|
||||||
isAddOverrideOpen,
|
isAddOverrideOpen,
|
||||||
addOverrideContext,
|
addOverrideContext,
|
||||||
addOverrideFloatingStyles,
|
addOverrideFloatingStyles,
|
||||||
|
|||||||
+6
@@ -17,6 +17,8 @@ interface ChannelPermissionsSidebarProps {
|
|||||||
selectedOverwriteId: string | null;
|
selectedOverwriteId: string | null;
|
||||||
canManageChannels: boolean;
|
canManageChannels: boolean;
|
||||||
canManageRoles: boolean;
|
canManageRoles: boolean;
|
||||||
|
canManageOverwrites: boolean;
|
||||||
|
onDeleteOverwrite: (overwriteId: string) => void;
|
||||||
isAddOverrideOpen: boolean;
|
isAddOverrideOpen: boolean;
|
||||||
setIsAddOverrideOpen: (open: boolean) => void;
|
setIsAddOverrideOpen: (open: boolean) => void;
|
||||||
existingOverwriteIds: Set<string>;
|
existingOverwriteIds: Set<string>;
|
||||||
@@ -40,6 +42,8 @@ export const ChannelPermissionsSidebar: React.FC<ChannelPermissionsSidebarProps>
|
|||||||
selectedOverwriteId,
|
selectedOverwriteId,
|
||||||
canManageChannels,
|
canManageChannels,
|
||||||
canManageRoles,
|
canManageRoles,
|
||||||
|
canManageOverwrites,
|
||||||
|
onDeleteOverwrite,
|
||||||
isAddOverrideOpen,
|
isAddOverrideOpen,
|
||||||
setIsAddOverrideOpen,
|
setIsAddOverrideOpen,
|
||||||
existingOverwriteIds,
|
existingOverwriteIds,
|
||||||
@@ -136,6 +140,8 @@ export const ChannelPermissionsSidebar: React.FC<ChannelPermissionsSidebarProps>
|
|||||||
roleId={roleId}
|
roleId={roleId}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
isEveryone={isEveryone}
|
isEveryone={isEveryone}
|
||||||
|
canDelete={canManageOverwrites && !isEveryone}
|
||||||
|
onDelete={onDeleteOverwrite}
|
||||||
onClick={() => onSelectOverwrite(overwrite.id)}
|
onClick={() => onSelectOverwrite(overwrite.id)}
|
||||||
guildId={guildId}
|
guildId={guildId}
|
||||||
data-flx="channel.channel-tabs.channel-permissions-tab.sidebar-content.overwrite-item.set-selected-overwrite-id"
|
data-flx="channel.channel-tabs.channel-permissions-tab.sidebar-content.overwrite-item.set-selected-overwrite-id"
|
||||||
|
|||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
import type {PermissionOverwrite} from '@app/features/channel/components/modals/channel_tabs/channel_permissions_tab/shared';
|
||||||
|
import {DeleteIcon} from '@app/features/ui/action_menu/ContextMenuIcons';
|
||||||
|
import {CopyRoleIdMenuItem, CopyUserIdMenuItem} from '@app/features/ui/action_menu/items/CopyMenuItems';
|
||||||
|
import {MenuGroup} from '@app/features/ui/action_menu/MenuGroup';
|
||||||
|
import {MenuItem} from '@app/features/ui/action_menu/MenuItem';
|
||||||
|
import type {User} from '@app/features/user/models/User';
|
||||||
|
import {msg} from '@lingui/core/macro';
|
||||||
|
import {useLingui} from '@lingui/react/macro';
|
||||||
|
import {observer} from 'mobx-react-lite';
|
||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
const DELETE_ROLE_DESCRIPTOR = msg({
|
||||||
|
message: 'Delete role',
|
||||||
|
comment:
|
||||||
|
'Danger action in the channel permissions overwrite context menu that removes a role permission override from the channel.',
|
||||||
|
});
|
||||||
|
const DELETE_USER_DESCRIPTOR = msg({
|
||||||
|
message: 'Delete user',
|
||||||
|
comment:
|
||||||
|
'Danger action in the channel permissions overwrite context menu that removes a member permission override from the channel.',
|
||||||
|
});
|
||||||
|
|
||||||
|
interface OverwriteContextMenuProps {
|
||||||
|
overwrite: PermissionOverwrite;
|
||||||
|
roleId: string | null;
|
||||||
|
user: User | null;
|
||||||
|
canDelete: boolean;
|
||||||
|
onDelete: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OverwriteContextMenu: React.FC<OverwriteContextMenuProps> = observer(
|
||||||
|
({overwrite, roleId, user, canDelete, onDelete, onClose}) => {
|
||||||
|
const {i18n} = useLingui();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuGroup data-flx="channel.channel-tabs.channel-permissions-tab.overwrite-context-menu.menu-group">
|
||||||
|
{overwrite.type === 0 && roleId ? (
|
||||||
|
<CopyRoleIdMenuItem
|
||||||
|
roleId={roleId}
|
||||||
|
onClose={onClose}
|
||||||
|
data-flx="channel.channel-tabs.channel-permissions-tab.overwrite-context-menu.copy-role-id-menu-item"
|
||||||
|
/>
|
||||||
|
) : user ? (
|
||||||
|
<CopyUserIdMenuItem
|
||||||
|
user={user}
|
||||||
|
onClose={onClose}
|
||||||
|
data-flx="channel.channel-tabs.channel-permissions-tab.overwrite-context-menu.copy-user-id-menu-item"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</MenuGroup>
|
||||||
|
{canDelete && (
|
||||||
|
<MenuGroup data-flx="channel.channel-tabs.channel-permissions-tab.overwrite-context-menu.menu-group--2">
|
||||||
|
<MenuItem
|
||||||
|
icon={
|
||||||
|
<DeleteIcon data-flx="channel.channel-tabs.channel-permissions-tab.overwrite-context-menu.delete-icon" />
|
||||||
|
}
|
||||||
|
danger
|
||||||
|
onClick={onDelete}
|
||||||
|
data-flx="channel.channel-tabs.channel-permissions-tab.overwrite-context-menu.menu-item.delete"
|
||||||
|
>
|
||||||
|
{overwrite.type === 0 ? i18n._(DELETE_ROLE_DESCRIPTOR) : i18n._(DELETE_USER_DESCRIPTOR)}
|
||||||
|
</MenuItem>
|
||||||
|
</MenuGroup>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
+27
-8
@@ -1,16 +1,18 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import {DEFAULT_ROLE_COLOR_HEX, getRoleColor} from '@app/features/app/components/dialogs/shared/PermissionComponents';
|
import {DEFAULT_ROLE_COLOR_HEX, getRoleColor} from '@app/features/app/components/dialogs/shared/PermissionComponents';
|
||||||
|
import {useContextMenuHoverState} from '@app/features/app/hooks/useContextMenuHoverState';
|
||||||
import styles from '@app/features/channel/components/modals/channel_tabs/ChannelPermissionsTab.module.css';
|
import styles from '@app/features/channel/components/modals/channel_tabs/ChannelPermissionsTab.module.css';
|
||||||
|
import {OverwriteContextMenu} from '@app/features/channel/components/modals/channel_tabs/channel_permissions_tab/OverwriteContextMenu';
|
||||||
import type {PermissionOverwrite} from '@app/features/channel/components/modals/channel_tabs/channel_permissions_tab/shared';
|
import type {PermissionOverwrite} from '@app/features/channel/components/modals/channel_tabs/channel_permissions_tab/shared';
|
||||||
import {openRoleContextMenu} from '@app/features/ui/action_menu/RoleContextMenu';
|
import * as ContextMenuCommands from '@app/features/ui/commands/ContextMenuCommands';
|
||||||
import {Avatar} from '@app/features/ui/components/Avatar';
|
import {Avatar} from '@app/features/ui/components/Avatar';
|
||||||
import type {User} from '@app/features/user/models/User';
|
import type {User} from '@app/features/user/models/User';
|
||||||
import {UsersIcon} from '@phosphor-icons/react';
|
import {UsersIcon} from '@phosphor-icons/react';
|
||||||
import {clsx} from 'clsx';
|
import {clsx} from 'clsx';
|
||||||
import {observer} from 'mobx-react-lite';
|
import {observer} from 'mobx-react-lite';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import {useCallback} from 'react';
|
import {useCallback, useRef} from 'react';
|
||||||
|
|
||||||
interface OverwriteItemProps {
|
interface OverwriteItemProps {
|
||||||
overwrite: PermissionOverwrite;
|
overwrite: PermissionOverwrite;
|
||||||
@@ -20,26 +22,43 @@ interface OverwriteItemProps {
|
|||||||
roleId?: string | null;
|
roleId?: string | null;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
isEveryone: boolean;
|
isEveryone: boolean;
|
||||||
|
canDelete: boolean;
|
||||||
|
onDelete: (overwriteId: string) => void;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
guildId: string;
|
guildId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OverwriteItem: React.FC<OverwriteItemProps> = observer(
|
export const OverwriteItem: React.FC<OverwriteItemProps> = observer(
|
||||||
({overwrite, name, color, user, roleId, isSelected, isEveryone, onClick, guildId}) => {
|
({overwrite, name, color, user, roleId, isSelected, isEveryone, canDelete, onDelete, onClick, guildId}) => {
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
const contextMenuOpen = useContextMenuHoverState(buttonRef);
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(event: React.MouseEvent<HTMLButtonElement>) => {
|
(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
if (!roleId) return;
|
ContextMenuCommands.openFromEvent(event, ({onClose}) => (
|
||||||
openRoleContextMenu(event, roleId);
|
<OverwriteContextMenu
|
||||||
|
overwrite={overwrite}
|
||||||
|
roleId={roleId ?? null}
|
||||||
|
user={user ?? null}
|
||||||
|
canDelete={canDelete}
|
||||||
|
onDelete={() => onDelete(overwrite.id)}
|
||||||
|
onClose={onClose}
|
||||||
|
data-flx="channel.channel-tabs.channel-permissions-tab.overwrite-item.handle-context-menu.overwrite-context-menu"
|
||||||
|
/>
|
||||||
|
));
|
||||||
},
|
},
|
||||||
[roleId],
|
[overwrite, roleId, user, canDelete, onDelete],
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
type="button"
|
type="button"
|
||||||
aria-pressed={isSelected}
|
aria-pressed={isSelected}
|
||||||
className={clsx(styles.overwriteItem, {[styles.overwriteItemSelected]: isSelected})}
|
className={clsx(styles.overwriteItem, {
|
||||||
|
[styles.overwriteItemSelected]: isSelected,
|
||||||
|
[styles.overwriteItemContextMenuOpen]: contextMenuOpen && !isSelected,
|
||||||
|
})}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onContextMenu={roleId ? handleContextMenu : undefined}
|
onContextMenu={handleContextMenu}
|
||||||
data-flx="channel.channel-tabs.channel-permissions-tab.overwrite-item.overwrite-item.click.button"
|
data-flx="channel.channel-tabs.channel-permissions-tab.overwrite-item.overwrite-item.click.button"
|
||||||
>
|
>
|
||||||
{overwrite.type === 0 && !isEveryone ? (
|
{overwrite.type === 0 && !isEveryone ? (
|
||||||
|
|||||||
Reference in new issue
Block a user