fix(Map): Copy-Paste restriction: support from FE side

This commit is contained in:
DanSylvest
2025-10-20 12:32:07 +03:00
parent 02b450325e
commit 872f7dcf48
15 changed files with 212 additions and 108 deletions

View File

@@ -118,7 +118,11 @@ export const useContextMenuSystemItems = ({
});
if (isShowPingBtn) {
return <WdMenuItem icon={iconClasses}>{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}</WdMenuItem>;
return (
<WdMenuItem icon={iconClasses} className="!ml-[-2px]">
{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}
</WdMenuItem>
);
}
return (
@@ -126,7 +130,7 @@ export const useContextMenuSystemItems = ({
infoTitle="Locked. Ping can be set only for one system."
infoClass="pi-lock text-stone-500 mr-[12px]"
>
<WdMenuItem disabled icon={iconClasses}>
<WdMenuItem disabled icon={iconClasses} className="!ml-[-2px]">
{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}
</WdMenuItem>
</MenuItemWithInfo>

View File

@@ -2,6 +2,10 @@ import React, { RefObject, useMemo } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { PrimeIcons } from 'primereact/api';
import { MenuItem } from 'primereact/menuitem';
import { checkPermissions } from '@/hooks/Mapper/components/map/helpers';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { MenuItemWithInfo, WdMenuItem } from '@/hooks/Mapper/components/ui-kit';
import clsx from 'clsx';
export interface ContextMenuSystemMultipleProps {
contextMenuRef: RefObject<ContextMenu>;
@@ -14,20 +18,41 @@ export const ContextMenuSystemMultiple: React.FC<ContextMenuSystemMultipleProps>
onDeleteSystems,
onCopySystems,
}) => {
const {
data: { options, userPermissions },
} = useMapRootState();
const items: MenuItem[] = useMemo(() => {
const allowCopy = checkPermissions(userPermissions, options.allowed_copy_for);
return [
{
label: 'Delete',
icon: clsx(PrimeIcons.TRASH, 'text-red-400'),
command: onDeleteSystems,
},
{ separator: true },
{
label: 'Copy',
icon: PrimeIcons.COPY,
command: onCopySystems,
disabled: !allowCopy,
template: () => {
return (
<MenuItemWithInfo
infoTitle="Action is blocked because you dont have permission to Copy."
infoClass={clsx(PrimeIcons.QUESTION_CIRCLE, 'text-stone-500 mr-[12px]')}
tooltipWrapperClassName="flex"
>
<WdMenuItem disabled icon="pi pi-copy">
Copy
</WdMenuItem>
</MenuItemWithInfo>
);
},
{
label: 'Delete',
icon: PrimeIcons.TRASH,
command: onDeleteSystems,
},
];
}, [onCopySystems, onDeleteSystems]);
}, [onCopySystems, onDeleteSystems, options, userPermissions]);
return (
<>

View File

@@ -3,6 +3,10 @@ import { ContextMenu } from 'primereact/contextmenu';
import { PrimeIcons } from 'primereact/api';
import { MenuItem } from 'primereact/menuitem';
import { PasteSystemsAndConnections } from '@/hooks/Mapper/components/map/components';
import { useMapState } from '@/hooks/Mapper/components/map/MapProvider.tsx';
import { checkPermissions } from '@/hooks/Mapper/components/map/helpers';
import { MenuItemWithInfo, WdMenuItem } from '@/hooks/Mapper/components/ui-kit';
import clsx from 'clsx';
export interface ContextMenuRootProps {
contextMenuRef: RefObject<ContextMenu>;
@@ -17,7 +21,13 @@ export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({
onPasteSystemsAnsConnections,
pasteSystemsAndConnections,
}) => {
const {
data: { options, userPermissions },
} = useMapState();
const items: MenuItem[] = useMemo(() => {
const allowPaste = checkPermissions(userPermissions, options.allowed_paste_for);
return [
{
label: 'Add System',
@@ -27,14 +37,27 @@ export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({
...(pasteSystemsAndConnections != null
? [
{
label: 'Paste',
icon: 'pi pi-clipboard',
disabled: !allowPaste,
command: onPasteSystemsAnsConnections,
template: () => {
return (
<MenuItemWithInfo
infoTitle="Action is blocked because you dont have permission to Paste."
infoClass={clsx(PrimeIcons.QUESTION_CIRCLE, 'text-stone-500 mr-[12px]')}
tooltipWrapperClassName="flex"
>
<WdMenuItem disabled icon="pi pi-clipboard">
Paste
</WdMenuItem>
</MenuItemWithInfo>
);
},
},
]
: []),
];
}, [onAddSystem, onPasteSystemsAnsConnections, pasteSystemsAndConnections]);
}, [userPermissions, options, onAddSystem, pasteSystemsAndConnections, onPasteSystemsAnsConnections]);
return (
<>

View File

@@ -0,0 +1,5 @@
import { UserPermission, UserPermissions } from '@/hooks/Mapper/types';
export const checkPermissions = (permissions: Partial<UserPermissions>, targetPermission: UserPermission) => {
return targetPermission != null && permissions[targetPermission];
};

View File

@@ -4,3 +4,4 @@ export * from './getSystemClassStyles';
export * from './getShapeClass';
export * from './getBackgroundClass';
export * from './prepareUnsplashedChunks';
export * from './checkPermissions';

View File

@@ -38,6 +38,8 @@ export const useMapInit = () => {
user_characters,
present_characters,
hubs,
options,
user_permissions,
}: CommandInit) => {
const { update } = ref.current;
@@ -63,6 +65,14 @@ export const useMapInit = () => {
updateData.hubs = hubs;
}
if (options) {
updateData.options = options;
}
if (options) {
updateData.userPermissions = user_permissions;
}
if (systems) {
updateData.systems = systems;
}

View File

@@ -49,7 +49,9 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
const { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated } =
useCommandsCharacters();
useImperativeHandle(ref, () => {
useImperativeHandle(
ref,
() => {
return {
command(type, data) {
switch (type) {
@@ -131,5 +133,7 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
}
},
};
}, []);
},
[],
);
};

View File

@@ -4,8 +4,17 @@ import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrap
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit/WdTooltip';
import clsx from 'clsx';
type MenuItemWithInfoProps = { infoTitle: ReactNode; infoClass?: string } & WithChildren;
export const MenuItemWithInfo = ({ children, infoClass, infoTitle }: MenuItemWithInfoProps) => {
type MenuItemWithInfoProps = {
infoTitle: ReactNode;
infoClass?: string;
tooltipWrapperClassName?: string;
} & WithChildren;
export const MenuItemWithInfo = ({
children,
infoClass,
infoTitle,
tooltipWrapperClassName,
}: MenuItemWithInfoProps) => {
return (
<div className="flex justify-between w-full h-full items-center">
{children}
@@ -13,6 +22,7 @@ export const MenuItemWithInfo = ({ children, infoClass, infoTitle }: MenuItemWit
content={infoTitle}
position={TooltipPosition.top}
className="!opacity-100 !pointer-events-auto"
wrapperClassName={tooltipWrapperClassName}
>
<div className={clsx('pi text-orange-400', infoClass)} />
</WdTooltipWrapper>

View File

@@ -1,13 +1,18 @@
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
import { WithChildren, WithClassName } from '@/hooks/Mapper/types/common.ts';
import clsx from 'clsx';
type WdMenuItemProps = { icon?: string; disabled?: boolean } & WithChildren;
export const WdMenuItem = ({ children, icon, disabled }: WdMenuItemProps) => {
type WdMenuItemProps = { icon?: string; disabled?: boolean } & WithChildren & WithClassName;
export const WdMenuItem = ({ children, icon, disabled, className }: WdMenuItemProps) => {
return (
<a
className={clsx('flex gap-[6px] w-full h-full items-center px-[12px] !py-0 ml-[-2px]', 'p-menuitem-link', {
className={clsx(
'flex gap-[6px] w-full h-full items-center px-[12px] !py-0',
'p-menuitem-link',
{
'p-disabled': disabled,
})}
},
className,
)}
>
{icon && <div className={clsx('min-w-[20px]', icon)}></div>}
<div className="w-full">{children}</div>

View File

@@ -10,6 +10,7 @@ export type WdTooltipWrapperProps = {
interactive?: boolean;
smallPaddings?: boolean;
tooltipClassName?: string;
wrapperClassName?: string;
} & Omit<HTMLProps<HTMLDivElement>, 'content' | 'size'> &
Omit<TooltipProps, 'content'>;
@@ -26,6 +27,7 @@ export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperPr
smallPaddings,
size,
tooltipClassName,
wrapperClassName,
...props
},
forwardedRef,
@@ -36,7 +38,7 @@ export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperPr
return (
<div className={clsx(classes.WdTooltipWrapperRoot, className)} {...props}>
{targetSelector ? <>{children}</> : <div className={autoClass}>{children}</div>}
{targetSelector ? <>{children}</> : <div className={clsx(autoClass, wrapperClassName)}>{children}</div>}
<WdTooltip
ref={forwardedRef}

View File

@@ -1,11 +1,5 @@
import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
export enum SESSION_KEY {
viewPort = 'viewPort',
windows = 'windows',
windowsVisible = 'windowsVisible',
routes = 'routes',
}
import { UserPermission } from '@/hooks/Mapper/types';
export const SYSTEM_FOCUSED_LIFETIME = 10000;
@@ -161,3 +155,9 @@ export const SPACE_TO_CLASS: Record<string, string> = {
[Spaces.Gallente]: 'Gallente',
[Spaces.Pochven]: 'Pochven',
};
export const PERMISSIONS_POWER_MAP = {
[UserPermission.ADD_SYSTEM]: [UserPermission.ADD_SYSTEM, UserPermission.MANAGE_MAP, UserPermission.ADMIN_MAP],
[UserPermission.MANAGE_MAP]: [UserPermission.MANAGE_MAP, UserPermission.ADMIN_MAP],
[UserPermission.ADMIN_MAP]: [UserPermission.ADMIN_MAP],
};

View File

@@ -9,3 +9,4 @@ export * from './connectionPassages';
export * from './permissions';
export * from './comment';
export * from './ping';
export * from './options';

View File

@@ -1,4 +1,4 @@
import { CommentType, PingData, SystemSignature, UserPermissions } from '@/hooks/Mapper/types';
import { CommentType, MapOptions, PingData, SystemSignature, UserPermissions } from '@/hooks/Mapper/types';
import { ActivitySummary, CharacterTypeRaw, TrackingCharacter } from '@/hooks/Mapper/types/character.ts';
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
import { DetailedKill, Kill } from '@/hooks/Mapper/types/kills.ts';
@@ -94,7 +94,7 @@ export type CommandInit = {
hubs: string[];
user_hubs: string[];
routes: RoutesList;
options: Record<string, string | boolean>;
options: MapOptions;
reset?: boolean;
is_subscription_active?: boolean;
main_character_eve_id?: string | null;

View File

@@ -4,7 +4,7 @@ import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
import { SolarSystemRawType } from '@/hooks/Mapper/types/system.ts';
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
import { PingData, UserPermissions } from '@/hooks/Mapper/types';
import { MapOptions, PingData, UserPermissions } from '@/hooks/Mapper/types';
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
export type MapUnionTypes = {
@@ -23,7 +23,7 @@ export type MapUnionTypes = {
kills: Record<number, number>;
connections: SolarSystemConnection[];
userPermissions: Partial<UserPermissions>;
options: Record<string, string | boolean>;
options: MapOptions;
isSubscriptionActive: boolean;
mainCharacterEveId: string | null;

View File

@@ -0,0 +1,14 @@
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
export type StringBoolean = 'true' | 'false';
export type MapOptions = {
allowed_copy_for: UserPermission;
allowed_paste_for: UserPermission;
layout: string;
restrict_offline_showing: StringBoolean;
show_linked_signature_id: StringBoolean;
show_linked_signature_id_temp_name: StringBoolean;
show_temp_system_name: StringBoolean;
store_custom_labels: StringBoolean;
};