mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-12 02:35:42 +00:00
fix(Map): Copy-Paste restriction: support from FE side
This commit is contained in:
@@ -118,7 +118,11 @@ export const useContextMenuSystemItems = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isShowPingBtn) {
|
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 (
|
return (
|
||||||
@@ -126,7 +130,7 @@ export const useContextMenuSystemItems = ({
|
|||||||
infoTitle="Locked. Ping can be set only for one system."
|
infoTitle="Locked. Ping can be set only for one system."
|
||||||
infoClass="pi-lock text-stone-500 mr-[12px]"
|
infoClass="pi-lock text-stone-500 mr-[12px]"
|
||||||
>
|
>
|
||||||
<WdMenuItem disabled icon={iconClasses}>
|
<WdMenuItem disabled icon={iconClasses} className="!ml-[-2px]">
|
||||||
{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}
|
{!hasPing ? 'Ping: RALLY' : 'Cancel: RALLY'}
|
||||||
</WdMenuItem>
|
</WdMenuItem>
|
||||||
</MenuItemWithInfo>
|
</MenuItemWithInfo>
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import React, { RefObject, useMemo } from 'react';
|
|||||||
import { ContextMenu } from 'primereact/contextmenu';
|
import { ContextMenu } from 'primereact/contextmenu';
|
||||||
import { PrimeIcons } from 'primereact/api';
|
import { PrimeIcons } from 'primereact/api';
|
||||||
import { MenuItem } from 'primereact/menuitem';
|
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 {
|
export interface ContextMenuSystemMultipleProps {
|
||||||
contextMenuRef: RefObject<ContextMenu>;
|
contextMenuRef: RefObject<ContextMenu>;
|
||||||
@@ -14,20 +18,41 @@ export const ContextMenuSystemMultiple: React.FC<ContextMenuSystemMultipleProps>
|
|||||||
onDeleteSystems,
|
onDeleteSystems,
|
||||||
onCopySystems,
|
onCopySystems,
|
||||||
}) => {
|
}) => {
|
||||||
|
const {
|
||||||
|
data: { options, userPermissions },
|
||||||
|
} = useMapRootState();
|
||||||
|
|
||||||
const items: MenuItem[] = useMemo(() => {
|
const items: MenuItem[] = useMemo(() => {
|
||||||
|
const allowCopy = checkPermissions(userPermissions, options.allowed_copy_for);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
icon: clsx(PrimeIcons.TRASH, 'text-red-400'),
|
||||||
|
command: onDeleteSystems,
|
||||||
|
},
|
||||||
|
{ separator: true },
|
||||||
{
|
{
|
||||||
label: 'Copy',
|
label: 'Copy',
|
||||||
icon: PrimeIcons.COPY,
|
icon: PrimeIcons.COPY,
|
||||||
command: onCopySystems,
|
command: onCopySystems,
|
||||||
|
disabled: !allowCopy,
|
||||||
|
template: () => {
|
||||||
|
return (
|
||||||
|
<MenuItemWithInfo
|
||||||
|
infoTitle="Action is blocked because you don’t 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { ContextMenu } from 'primereact/contextmenu';
|
|||||||
import { PrimeIcons } from 'primereact/api';
|
import { PrimeIcons } from 'primereact/api';
|
||||||
import { MenuItem } from 'primereact/menuitem';
|
import { MenuItem } from 'primereact/menuitem';
|
||||||
import { PasteSystemsAndConnections } from '@/hooks/Mapper/components/map/components';
|
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 {
|
export interface ContextMenuRootProps {
|
||||||
contextMenuRef: RefObject<ContextMenu>;
|
contextMenuRef: RefObject<ContextMenu>;
|
||||||
@@ -17,7 +21,13 @@ export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({
|
|||||||
onPasteSystemsAnsConnections,
|
onPasteSystemsAnsConnections,
|
||||||
pasteSystemsAndConnections,
|
pasteSystemsAndConnections,
|
||||||
}) => {
|
}) => {
|
||||||
|
const {
|
||||||
|
data: { options, userPermissions },
|
||||||
|
} = useMapState();
|
||||||
|
|
||||||
const items: MenuItem[] = useMemo(() => {
|
const items: MenuItem[] = useMemo(() => {
|
||||||
|
const allowPaste = checkPermissions(userPermissions, options.allowed_paste_for);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: 'Add System',
|
label: 'Add System',
|
||||||
@@ -27,14 +37,27 @@ export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({
|
|||||||
...(pasteSystemsAndConnections != null
|
...(pasteSystemsAndConnections != null
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: 'Paste',
|
|
||||||
icon: 'pi pi-clipboard',
|
icon: 'pi pi-clipboard',
|
||||||
|
disabled: !allowPaste,
|
||||||
command: onPasteSystemsAnsConnections,
|
command: onPasteSystemsAnsConnections,
|
||||||
|
template: () => {
|
||||||
|
return (
|
||||||
|
<MenuItemWithInfo
|
||||||
|
infoTitle="Action is blocked because you don’t 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { UserPermission, UserPermissions } from '@/hooks/Mapper/types';
|
||||||
|
|
||||||
|
export const checkPermissions = (permissions: Partial<UserPermissions>, targetPermission: UserPermission) => {
|
||||||
|
return targetPermission != null && permissions[targetPermission];
|
||||||
|
};
|
||||||
@@ -4,3 +4,4 @@ export * from './getSystemClassStyles';
|
|||||||
export * from './getShapeClass';
|
export * from './getShapeClass';
|
||||||
export * from './getBackgroundClass';
|
export * from './getBackgroundClass';
|
||||||
export * from './prepareUnsplashedChunks';
|
export * from './prepareUnsplashedChunks';
|
||||||
|
export * from './checkPermissions';
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export const useMapInit = () => {
|
|||||||
user_characters,
|
user_characters,
|
||||||
present_characters,
|
present_characters,
|
||||||
hubs,
|
hubs,
|
||||||
|
options,
|
||||||
|
user_permissions,
|
||||||
}: CommandInit) => {
|
}: CommandInit) => {
|
||||||
const { update } = ref.current;
|
const { update } = ref.current;
|
||||||
|
|
||||||
@@ -63,6 +65,14 @@ export const useMapInit = () => {
|
|||||||
updateData.hubs = hubs;
|
updateData.hubs = hubs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
updateData.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
updateData.userPermissions = user_permissions;
|
||||||
|
}
|
||||||
|
|
||||||
if (systems) {
|
if (systems) {
|
||||||
updateData.systems = systems;
|
updateData.systems = systems;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
|||||||
const { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated } =
|
const { charactersUpdated, presentCharacters, characterAdded, characterRemoved, characterUpdated } =
|
||||||
useCommandsCharacters();
|
useCommandsCharacters();
|
||||||
|
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(
|
||||||
|
ref,
|
||||||
|
() => {
|
||||||
return {
|
return {
|
||||||
command(type, data) {
|
command(type, data) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -131,5 +133,7 @@ export const useMapHandlers = (ref: ForwardedRef<MapHandlers>, onSelectionChange
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, []);
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,8 +4,17 @@ import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrap
|
|||||||
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit/WdTooltip';
|
import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit/WdTooltip';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
type MenuItemWithInfoProps = { infoTitle: ReactNode; infoClass?: string } & WithChildren;
|
type MenuItemWithInfoProps = {
|
||||||
export const MenuItemWithInfo = ({ children, infoClass, infoTitle }: MenuItemWithInfoProps) => {
|
infoTitle: ReactNode;
|
||||||
|
infoClass?: string;
|
||||||
|
tooltipWrapperClassName?: string;
|
||||||
|
} & WithChildren;
|
||||||
|
export const MenuItemWithInfo = ({
|
||||||
|
children,
|
||||||
|
infoClass,
|
||||||
|
infoTitle,
|
||||||
|
tooltipWrapperClassName,
|
||||||
|
}: MenuItemWithInfoProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between w-full h-full items-center">
|
<div className="flex justify-between w-full h-full items-center">
|
||||||
{children}
|
{children}
|
||||||
@@ -13,6 +22,7 @@ export const MenuItemWithInfo = ({ children, infoClass, infoTitle }: MenuItemWit
|
|||||||
content={infoTitle}
|
content={infoTitle}
|
||||||
position={TooltipPosition.top}
|
position={TooltipPosition.top}
|
||||||
className="!opacity-100 !pointer-events-auto"
|
className="!opacity-100 !pointer-events-auto"
|
||||||
|
wrapperClassName={tooltipWrapperClassName}
|
||||||
>
|
>
|
||||||
<div className={clsx('pi text-orange-400', infoClass)} />
|
<div className={clsx('pi text-orange-400', infoClass)} />
|
||||||
</WdTooltipWrapper>
|
</WdTooltipWrapper>
|
||||||
|
|||||||
@@ -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';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
type WdMenuItemProps = { icon?: string; disabled?: boolean } & WithChildren;
|
type WdMenuItemProps = { icon?: string; disabled?: boolean } & WithChildren & WithClassName;
|
||||||
export const WdMenuItem = ({ children, icon, disabled }: WdMenuItemProps) => {
|
export const WdMenuItem = ({ children, icon, disabled, className }: WdMenuItemProps) => {
|
||||||
return (
|
return (
|
||||||
<a
|
<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,
|
'p-disabled': disabled,
|
||||||
})}
|
},
|
||||||
|
className,
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{icon && <div className={clsx('min-w-[20px]', icon)}></div>}
|
{icon && <div className={clsx('min-w-[20px]', icon)}></div>}
|
||||||
<div className="w-full">{children}</div>
|
<div className="w-full">{children}</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type WdTooltipWrapperProps = {
|
|||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
smallPaddings?: boolean;
|
smallPaddings?: boolean;
|
||||||
tooltipClassName?: string;
|
tooltipClassName?: string;
|
||||||
|
wrapperClassName?: string;
|
||||||
} & Omit<HTMLProps<HTMLDivElement>, 'content' | 'size'> &
|
} & Omit<HTMLProps<HTMLDivElement>, 'content' | 'size'> &
|
||||||
Omit<TooltipProps, 'content'>;
|
Omit<TooltipProps, 'content'>;
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperPr
|
|||||||
smallPaddings,
|
smallPaddings,
|
||||||
size,
|
size,
|
||||||
tooltipClassName,
|
tooltipClassName,
|
||||||
|
wrapperClassName,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
@@ -36,7 +38,7 @@ export const WdTooltipWrapper = forwardRef<WdTooltipHandlers, WdTooltipWrapperPr
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(classes.WdTooltipWrapperRoot, className)} {...props}>
|
<div className={clsx(classes.WdTooltipWrapperRoot, className)} {...props}>
|
||||||
{targetSelector ? <>{children}</> : <div className={autoClass}>{children}</div>}
|
{targetSelector ? <>{children}</> : <div className={clsx(autoClass, wrapperClassName)}>{children}</div>}
|
||||||
|
|
||||||
<WdTooltip
|
<WdTooltip
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
import { PingsPlacement } from '@/hooks/Mapper/mapRootProvider/types.ts';
|
||||||
|
import { UserPermission } from '@/hooks/Mapper/types';
|
||||||
export enum SESSION_KEY {
|
|
||||||
viewPort = 'viewPort',
|
|
||||||
windows = 'windows',
|
|
||||||
windowsVisible = 'windowsVisible',
|
|
||||||
routes = 'routes',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SYSTEM_FOCUSED_LIFETIME = 10000;
|
export const SYSTEM_FOCUSED_LIFETIME = 10000;
|
||||||
|
|
||||||
@@ -161,3 +155,9 @@ export const SPACE_TO_CLASS: Record<string, string> = {
|
|||||||
[Spaces.Gallente]: 'Gallente',
|
[Spaces.Gallente]: 'Gallente',
|
||||||
[Spaces.Pochven]: 'Pochven',
|
[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],
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ export * from './connectionPassages';
|
|||||||
export * from './permissions';
|
export * from './permissions';
|
||||||
export * from './comment';
|
export * from './comment';
|
||||||
export * from './ping';
|
export * from './ping';
|
||||||
|
export * from './options';
|
||||||
|
|||||||
@@ -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 { ActivitySummary, CharacterTypeRaw, TrackingCharacter } from '@/hooks/Mapper/types/character.ts';
|
||||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.ts';
|
||||||
import { DetailedKill, Kill } from '@/hooks/Mapper/types/kills.ts';
|
import { DetailedKill, Kill } from '@/hooks/Mapper/types/kills.ts';
|
||||||
@@ -94,7 +94,7 @@ export type CommandInit = {
|
|||||||
hubs: string[];
|
hubs: string[];
|
||||||
user_hubs: string[];
|
user_hubs: string[];
|
||||||
routes: RoutesList;
|
routes: RoutesList;
|
||||||
options: Record<string, string | boolean>;
|
options: MapOptions;
|
||||||
reset?: boolean;
|
reset?: boolean;
|
||||||
is_subscription_active?: boolean;
|
is_subscription_active?: boolean;
|
||||||
main_character_eve_id?: string | null;
|
main_character_eve_id?: string | null;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { CharacterTypeRaw } from '@/hooks/Mapper/types/character.ts';
|
|||||||
import { SolarSystemRawType } from '@/hooks/Mapper/types/system.ts';
|
import { SolarSystemRawType } from '@/hooks/Mapper/types/system.ts';
|
||||||
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
import { RoutesList } from '@/hooks/Mapper/types/routes.ts';
|
||||||
import { SolarSystemConnection } from '@/hooks/Mapper/types/connection.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';
|
import { SystemSignature } from '@/hooks/Mapper/types/signatures';
|
||||||
|
|
||||||
export type MapUnionTypes = {
|
export type MapUnionTypes = {
|
||||||
@@ -23,7 +23,7 @@ export type MapUnionTypes = {
|
|||||||
kills: Record<number, number>;
|
kills: Record<number, number>;
|
||||||
connections: SolarSystemConnection[];
|
connections: SolarSystemConnection[];
|
||||||
userPermissions: Partial<UserPermissions>;
|
userPermissions: Partial<UserPermissions>;
|
||||||
options: Record<string, string | boolean>;
|
options: MapOptions;
|
||||||
isSubscriptionActive: boolean;
|
isSubscriptionActive: boolean;
|
||||||
|
|
||||||
mainCharacterEveId: string | null;
|
mainCharacterEveId: string | null;
|
||||||
|
|||||||
14
assets/js/hooks/Mapper/types/options.ts
Normal file
14
assets/js/hooks/Mapper/types/options.ts
Normal 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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user