From b2ae5a33ae62bc5d55e0c7c8f8f13e805ed240c0 Mon Sep 17 00:00:00 2001 From: Aleksei Chichenkov <39502310+DanSylvest@users.noreply.github.com> Date: Fri, 14 Mar 2025 14:34:12 +0300 Subject: [PATCH] System comments & refactoring (#253) * feat(Map): Add widget for comments. Refactor design of Signatures widget. Refactor a lot of code. Add Transition component in ui-kit. Sync versions of react. --------- Co-authored-by: Dmitry Popov Co-authored-by: achichenkov --- .../js/hooks/Mapper/common-styles/fixes.scss | 14 +- .../common-styles/prime-fixes/fix-popup.scss | 45 + .../common-styles/prime-fixes/index.scss | 1 + .../Mapper/components/map/MapProvider.tsx | 2 +- .../ContextMenuConnection.tsx | 2 +- .../useContextMenuConnectionHandlers.ts | 3 +- .../components/Comments/Comments.tsx | 52 + .../MarkdownComment.module.scss | 70 ++ .../MarkdownComment/MarkdownComment.tsx | 95 ++ .../components/MarkdownComment/index.ts | 1 + .../components/Comments/components/index.ts | 1 + .../mapInterface/components/Comments/index.ts | 1 + .../components/Comments/markdownexample.ts | 26 + .../CommentsEditor/CommentsEditor.tsx | 72 ++ .../components/CommentsEditor/index.ts | 1 + .../MarkdownEditor/MarkdownEditor.module.scss | 40 + .../MarkdownEditor/MarkdownEditor.tsx | 87 ++ .../components/MarkdownEditor/index.ts | 1 + .../SystemLinkSignatureDialog.tsx | 27 +- .../components/mapInterface/constants.tsx | 15 +- .../widgets/CommentsWidget/CommentsWidget.tsx | 60 ++ .../widgets/CommentsWidget/index.ts | 1 + .../LocalCharactersItemTemplate.tsx | 1 + .../SystemKillsContent.module.scss | 6 +- .../SignatureView/SignatureView.tsx | 4 +- .../SystemSignatureHeader.tsx | 150 +-- .../SystemSignatureHeader/index.ts | 1 + .../SystemSignatureSettingsDialog.tsx | 77 +- .../SystemSignatures/SystemSignatures.tsx | 214 +--- .../SystemSignaturesContent.tsx | 201 ++-- .../widgets/SystemSignatures/constants.ts | 131 ++- .../helpers/contentHelpers.ts | 8 +- .../helpers/getRowBackgroundColor.ts | 4 +- .../helpers/rowStyles.module.scss | 32 - .../SystemSignatures/helpers/rowStyles.ts | 69 +- .../widgets/SystemSignatures/hooks/types.ts | 5 +- .../hooks/usePendingAdditions.ts | 9 +- .../hooks/usePendingDeletions.ts | 3 +- .../hooks/useSignatureFetching.ts | 10 +- .../hooks/useSystemSignaturesData.ts | 33 +- .../widgets/SystemSignatures/renders/index.ts | 1 - .../renders/renderHeaderLabel.tsx | 5 - .../mapRootContent/MapRootContent.tsx | 2 +- .../CharacterActivity.module.scss | 58 -- .../CharacterActivity/CharacterActivity.tsx | 50 +- .../components/CharacterActivity/index.ts | 1 + .../Connections/PassageCard/PassageCard.tsx | 2 +- .../components/OnTheMap/OnTheMap.tsx | 2 +- .../TrackAndFollow/TrackAndFollow.tsx | 45 +- .../TrackingCharacterWrapper.tsx | 2 +- .../components/mapWrapper/MapWrapper.tsx | 21 +- .../CharacterCard/CharacterCard.module.scss | 49 - .../ui-kit/CharacterCard/CharacterCard.tsx | 123 +-- .../CharacterPortrait/CharacterPortrait.tsx | 47 + .../ui-kit/CharacterPortrait/index.ts | 1 + .../ui-kit/InfoDrawer/InfoDrawer.tsx | 19 +- .../components/ui-kit/TimeAgo/TimeAgo.tsx | 4 +- .../WdImgButton/WdImgButton.module.scss | 7 +- .../ui-kit/WdImgButton/WdImgButton.tsx | 4 +- .../components/ui-kit/WdTooltip/WdTooltip.tsx | 3 +- .../WdTransition/WdTransition.module.scss | 21 + .../ui-kit/WdTransition/WdTransition.tsx | 26 + .../components/ui-kit/WdTransition/index.ts | 1 + .../hooks/Mapper/components/ui-kit/index.ts | 2 + assets/js/hooks/Mapper/hooks/useHotkey.ts | 6 +- .../mapRootProvider/MapRootProvider.tsx | 35 +- .../Mapper/mapRootProvider/hooks/api/index.ts | 2 + .../hooks/api/useCommandComments.ts | 19 + .../hooks/api/useGetCacheCharacter.ts | 27 + .../Mapper/mapRootProvider/hooks/index.ts | 2 + .../hooks/useCharactersCache.ts | 46 + .../mapRootProvider/hooks/useComments.ts | 81 ++ .../hooks/useMapRootHandlers.ts | 210 ++-- assets/js/hooks/Mapper/types/character.ts | 19 + assets/js/hooks/Mapper/types/comment.ts | 21 + assets/js/hooks/Mapper/types/common.ts | 2 + assets/js/hooks/Mapper/types/index.ts | 1 + assets/js/hooks/Mapper/types/mapHandlers.ts | 29 +- assets/js/hooks/Mapper/types/signatures.ts | 9 + assets/js/hooks/Mapper/types/system.ts | 3 +- assets/package.json | 15 +- assets/yarn.lock | 977 +++++++++++++++++- lib/wanderer_app/api.ex | 1 + lib/wanderer_app/api/map_system_comment.ex | 77 ++ lib/wanderer_app/character.ex | 9 + lib/wanderer_app/map/map_server.ex | 12 + .../map/server/map_server_impl.ex | 4 + .../map/server/map_server_systems_impl.ex | 54 +- lib/wanderer_app/maps.ex | 15 +- .../repositories/map_system_comment_repo.ex | 21 + .../map_characters_event_handler.ex | 10 + .../map_system_comments_event_handler.ex | 138 +++ .../live/maps/map_event_handler.ex | 93 +- .../20250304160016_add_system_comments.exs | 48 + .../20250304160016.json | 117 +++ 95 files changed, 3172 insertions(+), 1002 deletions(-) create mode 100644 assets/js/hooks/Mapper/common-styles/prime-fixes/fix-popup.scss create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/Comments/Comments.tsx create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/MarkdownComment.module.scss create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/MarkdownComment.tsx create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/index.ts create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/index.ts create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/Comments/index.ts create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/Comments/markdownexample.ts create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/CommentsEditor/CommentsEditor.tsx create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/CommentsEditor/index.ts create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/MarkdownEditor.module.scss create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/MarkdownEditor.tsx create mode 100644 assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/index.ts create mode 100644 assets/js/hooks/Mapper/components/mapInterface/widgets/CommentsWidget/CommentsWidget.tsx create mode 100644 assets/js/hooks/Mapper/components/mapInterface/widgets/CommentsWidget/index.ts create mode 100644 assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/index.ts delete mode 100644 assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders/renderHeaderLabel.tsx delete mode 100644 assets/js/hooks/Mapper/components/mapRootContent/components/CharacterActivity/CharacterActivity.module.scss create mode 100644 assets/js/hooks/Mapper/components/mapRootContent/components/CharacterActivity/index.ts delete mode 100644 assets/js/hooks/Mapper/components/ui-kit/CharacterCard/CharacterCard.module.scss create mode 100644 assets/js/hooks/Mapper/components/ui-kit/CharacterPortrait/CharacterPortrait.tsx create mode 100644 assets/js/hooks/Mapper/components/ui-kit/CharacterPortrait/index.ts create mode 100644 assets/js/hooks/Mapper/components/ui-kit/WdTransition/WdTransition.module.scss create mode 100644 assets/js/hooks/Mapper/components/ui-kit/WdTransition/WdTransition.tsx create mode 100644 assets/js/hooks/Mapper/components/ui-kit/WdTransition/index.ts create mode 100644 assets/js/hooks/Mapper/mapRootProvider/hooks/api/useCommandComments.ts create mode 100644 assets/js/hooks/Mapper/mapRootProvider/hooks/api/useGetCacheCharacter.ts create mode 100644 assets/js/hooks/Mapper/mapRootProvider/hooks/useCharactersCache.ts create mode 100644 assets/js/hooks/Mapper/mapRootProvider/hooks/useComments.ts create mode 100644 assets/js/hooks/Mapper/types/comment.ts create mode 100644 lib/wanderer_app/api/map_system_comment.ex create mode 100644 lib/wanderer_app/repositories/map_system_comment_repo.ex create mode 100644 lib/wanderer_app_web/live/maps/event_handlers/map_system_comments_event_handler.ex create mode 100644 priv/repo/migrations/20250304160016_add_system_comments.exs create mode 100644 priv/resource_snapshots/repo/map_system_comments_v1/20250304160016.json diff --git a/assets/js/hooks/Mapper/common-styles/fixes.scss b/assets/js/hooks/Mapper/common-styles/fixes.scss index 94b55ad3..0786ff7f 100644 --- a/assets/js/hooks/Mapper/common-styles/fixes.scss +++ b/assets/js/hooks/Mapper/common-styles/fixes.scss @@ -67,16 +67,22 @@ } } -.p-sortable-column { - font-size: 12px; - font-weight: bold; - padding: 3px 4px; +.p-datatable-thead { + th, th.p-sortable-column { + font-size: 12px; + font-weight: bold; + padding: 3px 4px; + } } .p-selectable-row td { padding: 4px 4px; } +.p-datatable.p-datatable-sm .p-datatable-tbody > tr > td { + padding: 3px 4px; +} + .p-sortable-column > .p-column-header-content > span:last-child { transform: scale(0.7); diff --git a/assets/js/hooks/Mapper/common-styles/prime-fixes/fix-popup.scss b/assets/js/hooks/Mapper/common-styles/prime-fixes/fix-popup.scss new file mode 100644 index 00000000..763b4d5c --- /dev/null +++ b/assets/js/hooks/Mapper/common-styles/prime-fixes/fix-popup.scss @@ -0,0 +1,45 @@ +.p-confirm-popup { + display: flex; + flex-direction: column; + gap: 6px; + @apply p-[12px]; + + &::before, &::after { + display: none; + } + + .p-confirm-popup-content, .p-confirm-popup-footer { + @apply p-0 m-0; + } + + .p-confirm-popup-content { + display: flex; + gap: 6px; + } + + .p-confirm-popup-footer { + display: flex; + justify-content: flex-end; + gap: 4px; + } + + .p-confirm-popup-icon { + font-size: 14px; + } + + .p-confirm-popup-message { + @apply m-0; + font-size: 12px; + } + + .p-confirm-popup-reject.p-button-sm, + .p-confirm-popup-accept.p-button-sm { + @apply px-1.5 py-1 m-0; + + & > span { + font-size: 12px; + line-height: 12px; + } + } + +} diff --git a/assets/js/hooks/Mapper/common-styles/prime-fixes/index.scss b/assets/js/hooks/Mapper/common-styles/prime-fixes/index.scss index c95c364f..372c0de5 100644 --- a/assets/js/hooks/Mapper/common-styles/prime-fixes/index.scss +++ b/assets/js/hooks/Mapper/common-styles/prime-fixes/index.scss @@ -1,4 +1,5 @@ @import "fix-dialog"; +@import "fix-popup"; //@import "fix-input"; //@import "theme"; diff --git a/assets/js/hooks/Mapper/components/map/MapProvider.tsx b/assets/js/hooks/Mapper/components/map/MapProvider.tsx index 8d153bfc..a9ae4555 100644 --- a/assets/js/hooks/Mapper/components/map/MapProvider.tsx +++ b/assets/js/hooks/Mapper/components/map/MapProvider.tsx @@ -53,7 +53,7 @@ const MapContext = createContext({ outCommand: async () => void 0, }); -export const MapProvider: React.FC = ({ children, onCommand }) => { +export const MapProvider = ({ children, onCommand }: MapProviderProps) => { const { update, ref } = useContextStore({ ...INITIAL_DATA }); return ( diff --git a/assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/ContextMenuConnection.tsx b/assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/ContextMenuConnection.tsx index b98082a3..b1ddbcf4 100644 --- a/assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/ContextMenuConnection.tsx +++ b/assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/ContextMenuConnection.tsx @@ -2,7 +2,6 @@ import React, { RefObject, useMemo } from 'react'; import { ContextMenu } from 'primereact/contextmenu'; import { PrimeIcons } from 'primereact/api'; import { MenuItem } from 'primereact/menuitem'; -import { Edge } from '@reactflow/core/dist/esm/types/edges'; import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types'; import clsx from 'clsx'; import classes from './ContextMenuConnection.module.scss'; @@ -14,6 +13,7 @@ import { SHIP_SIZES_NAMES_SHORT, SHIP_SIZES_SIZE, } from '@/hooks/Mapper/components/map/constants.ts'; +import { Edge } from 'reactflow'; export interface ContextMenuConnectionProps { contextMenuRef: RefObject; diff --git a/assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/useContextMenuConnectionHandlers.ts b/assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/useContextMenuConnectionHandlers.ts index a3992a93..9b916e66 100644 --- a/assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/useContextMenuConnectionHandlers.ts +++ b/assets/js/hooks/Mapper/components/map/components/ContextMenuConnection/useContextMenuConnectionHandlers.ts @@ -1,9 +1,8 @@ -import { EdgeMouseHandler } from 'reactflow'; +import { Edge, EdgeMouseHandler } from 'reactflow'; import { useCallback, useRef, useState } from 'react'; import { ContextMenu } from 'primereact/contextmenu'; import { useMapState } from '../../MapProvider.tsx'; import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; -import { Edge } from '@reactflow/core/dist/esm/types/edges'; import { ConnectionType, MassState, ShipSizeStatus, SolarSystemConnection, TimeStatus } from '@/hooks/Mapper/types'; import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/Comments/Comments.tsx b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/Comments.tsx new file mode 100644 index 00000000..dd2810c3 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/Comments.tsx @@ -0,0 +1,52 @@ +import { MarkdownComment } from '@/hooks/Mapper/components/mapInterface/components/Comments/components'; +import { useEffect, useRef, useState } from 'react'; +import { CommentType } from '@/hooks/Mapper/types'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; + +export interface CommentsProps {} + +// eslint-disable-next-line no-empty-pattern +export const Comments = ({}: CommentsProps) => { + const [commentsList, setCommentsList] = useState([]); + + const { + data: { selectedSystems }, + comments: { loadComments, comments, lastUpdateKey }, + } = useMapRootState(); + + const [systemId] = selectedSystems; + + const ref = useRef({ loadComments, systemId }); + ref.current = { loadComments, systemId }; + + useEffect(() => { + const commentsBySystem = comments.get(systemId); + if (!commentsBySystem) { + return; + } + + const els = [...commentsBySystem.comments].sort((a, b) => +new Date(b.updated_at) - +new Date(a.updated_at)); + + setCommentsList(els); + }, [systemId, lastUpdateKey, comments]); + + useEffect(() => { + ref.current.loadComments(systemId); + }, [systemId]); + + if (commentsList.length === 0) { + return ( +
+ Not comments found here +
+ ); + } + + return ( +
+ {commentsList.map(({ id, text, updated_at, characterEveId }) => ( + + ))} +
+ ); +}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/MarkdownComment.module.scss b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/MarkdownComment.module.scss new file mode 100644 index 00000000..f5488f16 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/MarkdownComment.module.scss @@ -0,0 +1,70 @@ +.MarkdownCommentRoot { + border-left-width: 3px; + + @apply text-[12px] leading-[1.2] text-stone-300 break-words; + @apply bg-gradient-to-r from-stone-600/40 via-stone-600/10 to-stone-600/0; + + .h1 { + @apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal; + } + + .h2 { + @apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal; + } + + .h3 { + @apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal; + } + + .h4 { + @apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal; + } + + .h5 { + @apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal; + } + + .h6 { + @apply text-[12px] font-normal m-0 p-0 border-none break-words whitespace-normal; + } + + p { + @apply m-0 p-0 break-words whitespace-normal; + } + + ul, ol { + @apply m-0 p-0 list-none; + } + + li { + @apply m-0 break-words whitespace-normal; + } + + blockquote { + @apply border-l-4 border-cyan-400 p-2 m-0 font-normal text-stone-300 italic break-words whitespace-normal; + } + + a { + @apply text-violet-400 cursor-pointer transition-colors duration-200 break-words whitespace-normal; + + &:hover { + @apply underline; + } + } + + b, strong { + @apply font-bold text-green-400 break-words whitespace-normal; + } + + i, em { + @apply italic text-pink-400 break-words whitespace-normal; + } + + del { + @apply line-through text-stone-500 break-words whitespace-normal; + } + + hr { + @apply border-none h-[1px] bg-cyan-400 opacity-50 my-2; + } +} diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/MarkdownComment.tsx b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/MarkdownComment.tsx new file mode 100644 index 00000000..cb16632a --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/MarkdownComment.tsx @@ -0,0 +1,95 @@ +import classes from './MarkdownComment.module.scss'; +import clsx from 'clsx'; +import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import { InfoDrawer, TimeAgo, TooltipPosition, WdImgButton } from '@/hooks/Mapper/components/ui-kit'; +import remarkBreaks from 'remark-breaks'; +import { useGetCacheCharacter } from '@/hooks/Mapper/mapRootProvider/hooks/api'; +import { useCallback, useRef, useState } from 'react'; +import { WdTransition } from '@/hooks/Mapper/components/ui-kit/WdTransition/WdTransition.tsx'; +import { PrimeIcons } from 'primereact/api'; +import { ConfirmPopup } from 'primereact/confirmpopup'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; +import { OutCommand } from '@/hooks/Mapper/types'; + +const TOOLTIP_PROPS = { content: 'Remove comment', position: TooltipPosition.top }; +const REMARK_PLUGINS = [remarkGfm, remarkBreaks]; + +export interface MarkdownCommentProps { + text: string; + time: string; + characterEveId: string; + id: string; +} + +export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownCommentProps) => { + const char = useGetCacheCharacter(characterEveId); + const [hovered, setHovered] = useState(false); + + const cpRemoveBtnRef = useRef(); + const [cpRemoveVisible, setCpRemoveVisible] = useState(false); + + const { outCommand } = useMapRootState(); + const ref = useRef({ outCommand, id }); + ref.current = { outCommand, id }; + + const handleDelete = useCallback(async () => { + await ref.current.outCommand({ + type: OutCommand.deleteSystemComment, + data: ref.current.id, + }); + }, []); + + const handleMouseEnter = useCallback(() => setHovered(true), []); + const handleMouseLeave = useCallback(() => setHovered(false), []); + + const handleShowCP = useCallback(() => setCpRemoveVisible(true), []); + const handleHideCP = useCallback(() => setCpRemoveVisible(false), []); + + return ( + <> + +
+ + by {char?.data?.name ?? ''} + +
+ + +
+ {!hovered && } + {hovered && ( + // @ts-ignore +
+ +
+ )} +
+
+ + } + > + {text} +
+ + + + ); +}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/index.ts b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/index.ts new file mode 100644 index 00000000..d7b3d8e4 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/MarkdownComment/index.ts @@ -0,0 +1 @@ +export * from './MarkdownComment.tsx'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/index.ts b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/index.ts new file mode 100644 index 00000000..5aff993e --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/components/index.ts @@ -0,0 +1 @@ +export * from './MarkdownComment'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/Comments/index.ts b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/index.ts new file mode 100644 index 00000000..ff3845b7 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/index.ts @@ -0,0 +1 @@ +export * from './Comments'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/Comments/markdownexample.ts b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/markdownexample.ts new file mode 100644 index 00000000..db7880c6 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/Comments/markdownexample.ts @@ -0,0 +1,26 @@ +export const markdown = ` + # Heading 1 + ## Heading 2 + ### Heading 3 + #### Heading 4 + ##### Heading 5 + ###### Heading 6 + + --- + + ## Paragraphs + This is a regular text paragraph. + + Another paragraph, but with **bold** and *italic* text, as well as ~~strikethrough~~. + + > This is a block quote. + > Second line of the quote. + + ## Links + [Link to Google](https://www.google.com) + + ## Horizontal Line + A block quote with ~strikethrough~ and a URL: https://reactjs.org. + --- + + `; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/CommentsEditor/CommentsEditor.tsx b/assets/js/hooks/Mapper/components/mapInterface/components/CommentsEditor/CommentsEditor.tsx new file mode 100644 index 00000000..26f54ec5 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/CommentsEditor/CommentsEditor.tsx @@ -0,0 +1,72 @@ +import { TooltipPosition, WdImageSize, WdImgButton } from '@/hooks/Mapper/components/ui-kit'; +import clsx from 'clsx'; +import { PrimeIcons } from 'primereact/api'; +import { MarkdownEditor } from '@/hooks/Mapper/components/mapInterface/components/MarkdownEditor'; +import { useHotkey } from '@/hooks/Mapper/hooks'; +import { useCallback, useRef, useState } from 'react'; +import { OutCommand } from '@/hooks/Mapper/types'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; + +export interface CommentsEditorProps {} + +export const CommentsEditor = ({}: CommentsEditorProps) => { + const [textVal, setTextVal] = useState(''); + + const { + data: { selectedSystems }, + outCommand, + } = useMapRootState(); + + const [systemId] = selectedSystems; + + const ref = useRef({ outCommand, systemId, textVal }); + ref.current = { outCommand, systemId, textVal }; + + const handleFinishEdit = useCallback(async () => { + if (ref.current.textVal === '') { + return; + } + + await ref.current.outCommand({ + type: OutCommand.addSystemComment, + data: { + solarSystemId: ref.current.systemId, + value: ref.current.textVal, + }, + }); + setTextVal(''); + }, []); + + const handleClick = async () => { + await handleFinishEdit(); + }; + + useHotkey(true, ['Enter'], async () => { + await handleFinishEdit(); + }); + + return ( + + + Also you may use Meta + Enter hotkey. + + ), + }} + textSize={WdImageSize.large} + className={clsx(PrimeIcons.SEND, 'text-[14px]')} + onClick={handleClick} + /> + + } + /> + ); +}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/CommentsEditor/index.ts b/assets/js/hooks/Mapper/components/mapInterface/components/CommentsEditor/index.ts new file mode 100644 index 00000000..fe927e18 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/CommentsEditor/index.ts @@ -0,0 +1 @@ +export * from './CommentsEditor'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/MarkdownEditor.module.scss b/assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/MarkdownEditor.module.scss new file mode 100644 index 00000000..c055f775 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/MarkdownEditor.module.scss @@ -0,0 +1,40 @@ +.CERoot { + @apply border border-stone-400/30 rounded-[2px]; + + :global { + .cm-content { + @apply bg-stone-600/40; + } + + .cm-scroller { + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.5) transparent; + } + + .cm-scroller::-webkit-scrollbar { + width: 10px; + } + + .cm-scroller::-webkit-scrollbar-track { + background: transparent; + } + + .cm-scroller::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.5); + border-radius: 5px; + border: 2px solid transparent; + background-clip: content-box; + } + + .cm-scroller::-webkit-scrollbar-thumb:hover { + background-color: rgba(255, 255, 255, 0.7); + } + + .cm-scroller::-webkit-scrollbar-button { + display: none; + height: 0; + width: 0; + } + + } +} diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/MarkdownEditor.tsx b/assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/MarkdownEditor.tsx new file mode 100644 index 00000000..02a00a6d --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/MarkdownEditor.tsx @@ -0,0 +1,87 @@ +import { ReactNode, useCallback, useRef, useState } from 'react'; +import CodeMirror, { ViewPlugin } from '@uiw/react-codemirror'; +import { markdown } from '@codemirror/lang-markdown'; +import { oneDark } from '@codemirror/theme-one-dark'; +import { EditorView, type ViewUpdate } from '@codemirror/view'; + +import classes from './MarkdownEditor.module.scss'; +import clsx from 'clsx'; + +// TODO special plugin which force CodeMirror using capture for paste event +const stopEventPropagationPlugin = ViewPlugin.fromClass( + class { + constructor(view: EditorView) { + // @ts-ignore + this.view = view; + + // @ts-ignore + this.pasteHandler = (event: Event) => { + console.log('Paste done in editor, stopping global listeners.'); + event.stopPropagation(); + }; + + // @ts-ignore + view.dom.addEventListener('paste', this.pasteHandler); + } + + destroy() { + // @ts-ignore + this.view.dom.removeEventListener('paste', this.pasteHandler); + } + }, +); + +const CODE_MIRROR_EXTENSIONS = [ + markdown(), + EditorView.lineWrapping, + EditorView.theme({ + '&': { backgroundColor: 'transparent !important' }, + '& .cm-gutterElement': { display: 'none' }, + }), + stopEventPropagationPlugin, +]; + +export interface MarkdownEditorProps { + overlayContent?: ReactNode; + value: string; + onChange: (value: string) => void; +} + +export const MarkdownEditor = ({ value, onChange, overlayContent }: MarkdownEditorProps) => { + const [hasShift, setHasShift] = useState(false); + + const refData = useRef({ onChange }); + refData.current = { onChange }; + + const handleOnChange = useCallback((value: string, viewUpdate: ViewUpdate) => { + // Rerender happens after change + setTimeout(() => { + const scrollDOM = viewUpdate.view.scrollDOM; + setHasShift(scrollDOM.scrollHeight > scrollDOM.clientHeight); + }, 0); + + refData.current.onChange(value); + }, []); + + return ( +
+ +
+ {overlayContent} +
+
+ ); +}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/index.ts b/assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/index.ts new file mode 100644 index 00000000..966b280f --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/components/MarkdownEditor/index.ts @@ -0,0 +1 @@ +export * from './MarkdownEditor.tsx'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx index 74270851..e29fe153 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/components/SystemLinkSignatureDialog/SystemLinkSignatureDialog.tsx @@ -1,17 +1,10 @@ -import { useCallback, useRef, useMemo } from 'react'; +import { useCallback, useMemo, useRef } from 'react'; import { Dialog } from 'primereact/dialog'; import { OutCommand } from '@/hooks/Mapper/types/mapHandlers.ts'; -import { SystemSignature, TimeStatus } from '@/hooks/Mapper/types'; +import { CommandLinkSignatureToSystem, SignatureGroup, SystemSignature, TimeStatus } from '@/hooks/Mapper/types'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; -import { CommandLinkSignatureToSystem } from '@/hooks/Mapper/types'; import { SystemSignaturesContent } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent'; -import { SHOW_DESCRIPTION_COLUMN_SETTING } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures'; -import { - Setting, - COSMIC_SIGNATURE, -} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog'; -import { SignatureGroup } from '@/hooks/Mapper/types'; import { parseSignatureCustomInfo } from '@/hooks/Mapper/helpers/parseSignatureCustomInfo'; import { getWhSize } from '@/hooks/Mapper/helpers/getWhSize'; import { useSystemInfo } from '@/hooks/Mapper/components/hooks'; @@ -21,6 +14,10 @@ import { WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME, } from '@/hooks/Mapper/components/map/constants.ts'; import { K162_TYPES_MAP } from '@/hooks/Mapper/constants.ts'; +import { + SETTINGS_KEYS, + SignatureSettingsType, +} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts'; const K162_SIGNATURE_TYPE = WORMHOLES_ADDITIONAL_INFO_BY_SHORT_NAME['K162'].shortName; @@ -29,11 +26,11 @@ interface SystemLinkSignatureDialogProps { setVisible: (visible: boolean) => void; } -const signatureSettings: Setting[] = [ - { key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true }, - { key: SignatureGroup.Wormhole, name: 'Wormhole', value: true }, - { key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: true, isFilter: false }, -]; +export const LINK_SIGNTATURE_SETTINGS: SignatureSettingsType = { + [SETTINGS_KEYS.COSMIC_SIGNATURE]: true, + [SETTINGS_KEYS.WORMHOLE]: true, + [SETTINGS_KEYS.SHOW_DESCRIPTION_COLUMN]: true, +}; // Extend the SignatureCustomInfo type to include k162Type interface ExtendedSignatureCustomInfo { @@ -175,7 +172,7 @@ export const SystemLinkSignatureDialog = ({ data, setVisible }: SystemLinkSignat , }, + { + id: WidgetsIds.comments, + position: { x: 10, y: 10 }, + size: { width: 250, height: 300 }, + zIndex: 0, + content: () => , + }, ]; type WidgetsCheckboxesType = { @@ -102,4 +111,8 @@ export const WIDGETS_CHECKBOXES_PROPS: WidgetsCheckboxesType = [ id: WidgetsIds.kills, label: 'Kills', }, + { + id: WidgetsIds.comments, + label: 'Comments', + }, ]; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/CommentsWidget/CommentsWidget.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/CommentsWidget/CommentsWidget.tsx new file mode 100644 index 00000000..21e6058d --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/CommentsWidget/CommentsWidget.tsx @@ -0,0 +1,60 @@ +import { Widget } from '@/hooks/Mapper/components/mapInterface/components'; +import { Comments } from '@/hooks/Mapper/components/mapInterface/components/Comments'; +import { SystemView } from '@/hooks/Mapper/components/ui-kit'; +import { useRef } from 'react'; +import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts'; +import { COMPACT_MAX_WIDTH } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; +import clsx from 'clsx'; +import { CommentsEditor } from '@/hooks/Mapper/components/mapInterface/components/CommentsEditor'; + +export const CommentsWidgetContent = () => { + const { + data: { selectedSystems }, + } = useMapRootState(); + + const isNotSelectedSystem = selectedSystems.length !== 1; + + if (isNotSelectedSystem) { + return ( +
+ System is not selected +
+ ); + } + + return ( +
+ + +
+ ); +}; + +export const CommentsWidget = () => { + const containerRef = useRef(null); + const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH); + + const { + data: { selectedSystems }, + } = useMapRootState(); + const [systemId] = selectedSystems; + const isNotSelectedSystem = selectedSystems.length !== 1; + + return ( + + {!isCompact && ( +
+ Comments {isNotSelectedSystem ? '' : 'in'} +
+ )} + {!isNotSelectedSystem && } + + } + > + +
+ ); +}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/CommentsWidget/index.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/CommentsWidget/index.ts new file mode 100644 index 00000000..e5c44226 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/CommentsWidget/index.ts @@ -0,0 +1 @@ +export * from './CommentsWidget'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersItemTemplate/LocalCharactersItemTemplate.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersItemTemplate/LocalCharactersItemTemplate.tsx index b8a75e6d..8f6f16a9 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersItemTemplate/LocalCharactersItemTemplate.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/LocalCharacters/components/LocalCharactersItemTemplate/LocalCharactersItemTemplate.tsx @@ -13,6 +13,7 @@ export const LocalCharactersItemTemplate = ({ showShipName, ...options }: LocalC className={clsx( classes.CharacterRow, 'box-border flex items-center w-full whitespace-nowrap overflow-hidden text-ellipsis min-w-[0px]', + 'px-1', { 'surface-hover': options.odd, 'border-b border-gray-600 border-opacity-20': !options.last, diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/SystemKillsContent/SystemKillsContent.module.scss b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/SystemKillsContent/SystemKillsContent.module.scss index 4a857720..a25e43f0 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/SystemKillsContent/SystemKillsContent.module.scss +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemKills/SystemKillsContent/SystemKillsContent.module.scss @@ -2,8 +2,6 @@ .scrollerContent { overflow-x: hidden; overflow-y: auto; - padding-right: 8px; - padding-left: 8px; height: 100% !important; } @@ -13,11 +11,11 @@ display: flex !important; flex-direction: column !important; height: 100% !important; - + // Target this specific VirtualScroller instance &:global(.p-virtualscroller) { height: 100% !important; - + :global(.p-virtualscroller-content) { height: 100% !important; } diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView/SignatureView.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView/SignatureView.tsx index 1ee1a27a..030f4029 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView/SignatureView.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView/SignatureView.tsx @@ -10,12 +10,12 @@ export interface SignatureViewProps { export const SignatureView = ({ signature, showCharacterPortrait = false }: SignatureViewProps) => { const isWormhole = signature?.group === SignatureGroup.Wormhole; const hasCharacterInfo = showCharacterPortrait && signature.character_eve_id; - const groupDisplay = isWormhole ? SignatureGroup.Wormhole : signature?.group ?? SignatureGroup.CosmicSignature; + const groupDisplay = isWormhole ? SignatureGroup.Wormhole : (signature?.group ?? SignatureGroup.CosmicSignature); const characterName = signature.character_name || 'Unknown character'; return (
-
+
{renderIcon(signature)}
{signature?.eve_id}
{groupDisplay}
diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/SystemSignatureHeader.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/SystemSignatureHeader.tsx index 4df63ae8..12f0c602 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/SystemSignatureHeader.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/SystemSignatureHeader.tsx @@ -1,15 +1,21 @@ -import React from 'react'; -import { SystemView, WdCheckbox, WdImgButton, TooltipPosition } from '@/hooks/Mapper/components/ui-kit'; +import { useRef } from 'react'; +import { + InfoDrawer, + LayoutEventBlocker, + SystemView, + TooltipPosition, + WdCheckbox, + WdImgButton, +} from '@/hooks/Mapper/components/ui-kit'; import { PrimeIcons } from 'primereact/api'; import { CheckboxChangeEvent } from 'primereact/checkbox'; -import { InfoDrawer, LayoutEventBlocker } from '@/hooks/Mapper/components/ui-kit'; import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper'; +import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth.ts'; +import { COMPACT_MAX_WIDTH } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts'; +import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; export type HeaderProps = { - systemId: string; - isNotSelectedSystem: boolean; sigCount: number; - isCompact: boolean; lazyDeleteValue: boolean; onLazyDeleteChange: (checked: boolean) => void; pendingCount: number; @@ -18,18 +24,25 @@ export type HeaderProps = { onSettingsClick: () => void; }; -function HeaderImpl({ - systemId, - isNotSelectedSystem, +export const SystemSignaturesHeader = ({ sigCount, - isCompact, lazyDeleteValue, onLazyDeleteChange, pendingCount, pendingTimeRemaining, onUndoClick, onSettingsClick, -}: HeaderProps) { +}: HeaderProps) => { + const { + data: { selectedSystems }, + } = useMapRootState(); + + const [systemId] = selectedSystems; + const isNotSelectedSystem = selectedSystems.length !== 1; + + const containerRef = useRef(null); + const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH); + // Format time remaining as seconds const formatTimeRemaining = () => { if (!pendingTimeRemaining) return ''; @@ -38,71 +51,68 @@ function HeaderImpl({ }; return ( -
-
- {!isCompact && ( -
- {sigCount ? `[${sigCount}] ` : ''}Signatures {isNotSelectedSystem ? '' : 'in'} -
- )} - {!isNotSelectedSystem && } -
+
+
+
+ {!isCompact && ( +
+ {sigCount ? `[${sigCount}] ` : ''}Signatures {isNotSelectedSystem ? '' : 'in'} +
+ )} + {!isNotSelectedSystem && } +
- - - onLazyDeleteChange(!!event.checked)} - /> - + + + onLazyDeleteChange(!!event.checked)} + /> + + + {pendingCount > 0 && ( + + )} - {pendingCount > 0 && ( + How to add/update signature?}> + In game you need to select one or more signatures
in the list in{' '} + Probe scanner.
Use next hotkeys: +
+ Shift + LMB or Ctrl + LMB +
or Ctrl + A for select all +
and then use Ctrl + C, after you need to go
+ here, select Solar system and paste it with Ctrl + V +
+ How to select?}> + For selecting any signature, click on it
with hotkeys{' '} + Shift + LMB or Ctrl + LMB +
+ How to delete?}> + To delete any signature, first select it
and then press Del +
+
+ ), }} - onClick={onUndoClick} /> - )} - - How to add/update signature?}> - In game you need to select one or more signatures
in the list in{' '} - Probe scanner.
Use next hotkeys: -
- Shift + LMB or Ctrl + LMB -
or Ctrl + A for select all -
and then use Ctrl + C, after you need to go
- here, select Solar system and paste it with Ctrl + V -
- How to select?}> - For selecting any signature, click on it
with hotkeys{' '} - Shift + LMB or Ctrl + LMB -
- How to delete?}> - To delete any signature, first select it
and then press Del -
-
- ), - }} - /> - - - + + +
); -} - -export const SystemSignaturesHeader = React.memo(HeaderImpl); +}; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/index.ts b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/index.ts new file mode 100644 index 00000000..1a378208 --- /dev/null +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureHeader/index.ts @@ -0,0 +1 @@ +export * from './SystemSignatureHeader'; diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog/SystemSignatureSettingsDialog.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog/SystemSignatureSettingsDialog.tsx index 44d9afb4..2e42537b 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog/SystemSignatureSettingsDialog.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatureSettingsDialog/SystemSignatureSettingsDialog.tsx @@ -5,26 +5,16 @@ import { TabPanel, TabView } from 'primereact/tabview'; import styles from './SystemSignatureSettingsDialog.module.scss'; import { PrettySwitchbox } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components'; import { Dropdown } from 'primereact/dropdown'; - -export type Setting = { - key: string; - name: string; - value: boolean | number; - isFilter?: boolean; - options?: { label: string; value: number }[]; -}; - -export const COSMIC_SIGNATURE = 'Cosmic Signature'; -export const COSMIC_ANOMALY = 'Cosmic Anomaly'; -export const DEPLOYABLE = 'Deployable'; -export const STRUCTURE = 'Structure'; -export const STARBASE = 'Starbase'; -export const SHIP = 'Ship'; -export const DRONE = 'Drone'; +import { + Setting, + SettingsTypes, + SIGNATURE_SETTINGS, + SignatureSettingsType, +} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts'; interface SystemSignatureSettingsDialogProps { - settings: Setting[]; - onSave: (settings: Setting[]) => void; + settings: SignatureSettingsType; + onSave: (settings: SignatureSettingsType) => void; onCancel: () => void; } @@ -34,21 +24,18 @@ export const SystemSignatureSettingsDialog = ({ onCancel, }: SystemSignatureSettingsDialogProps) => { const [activeIndex, setActiveIndex] = useState(0); - const [settings, setSettings] = useState(defaultSettings); + const [settings, setSettings] = useState(defaultSettings); - const filterSettings = settings.filter(setting => setting.isFilter); - const userSettings = settings.filter(setting => !setting.isFilter); - - const handleSettingsChange = (key: string) => { - setSettings(prevState => - prevState.map(item => - item.key === key ? { ...item, value: typeof item.value === 'boolean' ? !item.value : item.value } : item, - ), - ); - }; - - const handleDropdownChange = (key: string, value: number) => { - setSettings(prevState => prevState.map(item => (item.key === key ? { ...item, value } : item))); + const handleSettingsChange = ({ key, type }: Setting, value?: unknown) => { + setSettings(prev => { + switch (type) { + case SettingsTypes.dropdown: + return { ...prev, [key]: value }; + case SettingsTypes.flag: + return { ...prev, [key]: !prev[key] }; + } + return prev; + }); }; const handleSave = useCallback(() => { @@ -56,17 +43,15 @@ export const SystemSignatureSettingsDialog = ({ }, [onSave, settings]); const renderSetting = (setting: Setting) => { + const val = settings[setting.key]; if (setting.options) { return (
({ - ...opt, - label: opt.label.split(' ')[0], // Just take the first part (e.g., "0s" from "Immediate (0s)") - }))} - onChange={e => handleDropdownChange(setting.key, e.value)} + value={val} + options={setting.options} + onChange={e => handleSettingsChange(setting, e.value)} className="w-40" />
@@ -77,8 +62,8 @@ export const SystemSignatureSettingsDialog = ({ handleSettingsChange(setting.key)} + checked={!!val} + setChecked={() => handleSettingsChange(setting)} /> ); }; @@ -94,15 +79,15 @@ export const SystemSignatureSettingsDialog = ({ className={styles.verticalTabView} > -
{filterSettings.map(renderSetting)}
+
+ {SIGNATURE_SETTINGS.filterFlags.map(renderSetting)} +
- {userSettings.filter(setting => !setting.options).map(renderSetting)} - {userSettings.some(setting => setting.options) && ( -
- )} - {userSettings.filter(setting => setting.options).map(renderSetting)} + {SIGNATURE_SETTINGS.uiFlags.map(renderSetting)} +
+ {SIGNATURE_SETTINGS.uiOther.map(renderSetting)}
diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures.tsx index ad0b50cf..fa7f2068 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignatures.tsx @@ -1,130 +1,33 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { Widget } from '@/hooks/Mapper/components/mapInterface/components'; import { SystemSignaturesContent } from './SystemSignaturesContent'; -import { - COSMIC_ANOMALY, - COSMIC_SIGNATURE, - DEPLOYABLE, - DRONE, - Setting, - SHIP, - STARBASE, - STRUCTURE, - SystemSignatureSettingsDialog, -} from './SystemSignatureSettingsDialog'; -import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; +import { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog'; +import { SystemSignature } from '@/hooks/Mapper/types'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; -import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth'; import { useHotkey } from '@/hooks/Mapper/hooks'; +import { SystemSignaturesHeader } from './SystemSignatureHeader'; +import useLocalStorageState from 'use-local-storage-state'; import { - COMPACT_MAX_WIDTH, - DELETION_TIMING_DEFAULT, - DELETION_TIMING_EXTENDED, - DELETION_TIMING_IMMEDIATE, - DELETION_TIMING_SETTING_KEY, -} from './constants'; -import { renderHeaderLabel } from './renders'; + SETTINGS_KEYS, + SETTINGS_VALUES, + SIGNATURE_SETTING_STORE_KEY, + SIGNATURE_WINDOW_ID, + SignatureSettingsType, +} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts'; -const SIGNATURE_SETTINGS_KEY = 'wanderer_system_signature_settings_v5_5'; -export const SIGNATURE_WINDOW_ID = 'system_signatures_window'; -export const SHOW_DESCRIPTION_COLUMN_SETTING = 'show_description_column_setting'; -export const SHOW_UPDATED_COLUMN_SETTING = 'SHOW_UPDATED_COLUMN_SETTING'; -export const SHOW_CHARACTER_COLUMN_SETTING = 'SHOW_CHARACTER_COLUMN_SETTING'; -export const LAZY_DELETE_SIGNATURES_SETTING = 'LAZY_DELETE_SIGNATURES_SETTING'; -export const KEEP_LAZY_DELETE_SETTING = 'KEEP_LAZY_DELETE_ENABLED_SETTING'; -// eslint-disable-next-line react-refresh/only-export-components -export const DELETION_TIMING_SETTING = DELETION_TIMING_SETTING_KEY; -export const COLOR_BY_TYPE_SETTING = 'COLOR_BY_TYPE_SETTING'; -export const SHOW_CHARACTER_PORTRAIT_SETTING = 'SHOW_CHARACTER_PORTRAIT_SETTING'; +export const SystemSignatures = () => { + const [visible, setVisible] = useState(false); + const [sigCount, setSigCount] = useState(0); + const [pendingSigs, setPendingSigs] = useState([]); + const undoPendingFnRef = useRef<() => void>(() => {}); -// Extend the Setting type to include options for dropdown settings -type ExtendedSetting = Setting & { - options?: { label: string; value: number }[]; -}; - -const SETTINGS: ExtendedSetting[] = [ - { key: SHOW_UPDATED_COLUMN_SETTING, name: 'Show Updated Column', value: false, isFilter: false }, - { key: SHOW_DESCRIPTION_COLUMN_SETTING, name: 'Show Description Column', value: false, isFilter: false }, - { key: SHOW_CHARACTER_COLUMN_SETTING, name: 'Show Character Column', value: false, isFilter: false }, - { key: SHOW_CHARACTER_PORTRAIT_SETTING, name: 'Show Character Portrait in Tooltip', value: false, isFilter: false }, - { key: LAZY_DELETE_SIGNATURES_SETTING, name: 'Lazy Delete Signatures', value: false, isFilter: false }, - { key: KEEP_LAZY_DELETE_SETTING, name: 'Keep "Lazy Delete" Enabled', value: false, isFilter: false }, - { key: COLOR_BY_TYPE_SETTING, name: 'Color Signatures by Type', value: false, isFilter: false }, - { - key: DELETION_TIMING_SETTING, - name: 'Deletion Timing', - value: DELETION_TIMING_DEFAULT, - isFilter: false, - options: [ - { label: '0s', value: DELETION_TIMING_IMMEDIATE }, - { label: '10s', value: DELETION_TIMING_DEFAULT }, - { label: '30s', value: DELETION_TIMING_EXTENDED }, - ], - }, - - { key: COSMIC_ANOMALY, name: 'Show Anomalies', value: true, isFilter: true }, - { key: COSMIC_SIGNATURE, name: 'Show Cosmic Signatures', value: true, isFilter: true }, - { key: DEPLOYABLE, name: 'Show Deployables', value: true, isFilter: true }, - { key: STRUCTURE, name: 'Show Structures', value: true, isFilter: true }, - { key: STARBASE, name: 'Show Starbase', value: true, isFilter: true }, - { key: SHIP, name: 'Show Ships', value: true, isFilter: true }, - { key: DRONE, name: 'Show Drones And Charges', value: true, isFilter: true }, - { key: SignatureGroup.Wormhole, name: 'Show Wormholes', value: true, isFilter: true }, - { key: SignatureGroup.RelicSite, name: 'Show Relic Sites', value: true, isFilter: true }, - { key: SignatureGroup.DataSite, name: 'Show Data Sites', value: true, isFilter: true }, - { key: SignatureGroup.OreSite, name: 'Show Ore Sites', value: true, isFilter: true }, - { key: SignatureGroup.GasSite, name: 'Show Gas Sites', value: true, isFilter: true }, - { key: SignatureGroup.CombatSite, name: 'Show Combat Sites', value: true, isFilter: true }, -]; - -function getDefaultSettings(): ExtendedSetting[] { - return [...SETTINGS]; -} - -function getInitialSettings(): ExtendedSetting[] { - const stored = localStorage.getItem(SIGNATURE_SETTINGS_KEY); - if (stored) { - try { - const parsedSettings = JSON.parse(stored) as ExtendedSetting[]; - // Merge stored settings with default settings to ensure new settings are included - const defaultSettings = getDefaultSettings(); - const mergedSettings = defaultSettings.map(defaultSetting => { - const storedSetting = parsedSettings.find(s => s.key === defaultSetting.key); - if (storedSetting) { - // Keep the stored value but ensure options are from default settings - return { - ...defaultSetting, - value: storedSetting.value, - }; - } - return defaultSetting; - }); - return mergedSettings; - } catch (error) { - console.error('Error parsing stored settings', error); - } - } - return getDefaultSettings(); -} - -export const SystemSignatures: React.FC = () => { const { data: { selectedSystems }, } = useMapRootState(); - const [visible, setVisible] = useState(false); - - const [currentSettings, setCurrentSettings] = useState(getInitialSettings); - - useEffect(() => { - localStorage.setItem(SIGNATURE_SETTINGS_KEY, JSON.stringify(currentSettings)); - }, [currentSettings]); - - const [sigCount, setSigCount] = useState(0); - const [pendingSigs, setPendingSigs] = useState([]); - const [minPendingTimeRemaining, setMinPendingTimeRemaining] = useState(undefined); - - const undoPendingFnRef = useRef<() => void>(() => {}); + const [currentSettings, setCurrentSettings] = useLocalStorageState(SIGNATURE_SETTING_STORE_KEY, { + defaultValue: SETTINGS_VALUES, + }); const handleSigCountChange = useCallback((count: number) => { setSigCount(count); @@ -133,49 +36,27 @@ export const SystemSignatures: React.FC = () => { const [systemId] = selectedSystems; const isNotSelectedSystem = selectedSystems.length !== 1; - const lazyDeleteValue = useMemo(() => { - const setting = currentSettings.find(setting => setting.key === LAZY_DELETE_SIGNATURES_SETTING); - return typeof setting?.value === 'boolean' ? setting.value : false; - }, [currentSettings]); - - const deletionTimingValue = useMemo(() => { - const setting = currentSettings.find(setting => setting.key === DELETION_TIMING_SETTING); - return typeof setting?.value === 'number' ? setting.value : DELETION_TIMING_IMMEDIATE; - }, [currentSettings]); - - const colorByTypeValue = useMemo(() => { - const setting = currentSettings.find(setting => setting.key === COLOR_BY_TYPE_SETTING); - return typeof setting?.value === 'boolean' ? setting.value : false; - }, [currentSettings]); - - const handleSettingsChange = useCallback((newSettings: Setting[]) => { - setCurrentSettings(newSettings as ExtendedSetting[]); + const handleSettingsChange = useCallback((newSettings: SignatureSettingsType) => { + setCurrentSettings(newSettings); setVisible(false); }, []); const handleLazyDeleteChange = useCallback((value: boolean) => { - setCurrentSettings(prevSettings => - prevSettings.map(setting => (setting.key === LAZY_DELETE_SIGNATURES_SETTING ? { ...setting, value } : setting)), - ); + setCurrentSettings(prev => ({ ...prev, [SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value })); }, []); - const containerRef = useRef(null); - const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH); - useHotkey(true, ['z'], event => { if (pendingSigs.length > 0) { event.preventDefault(); event.stopPropagation(); undoPendingFnRef.current(); setPendingSigs([]); - setMinPendingTimeRemaining(undefined); } }); const handleUndoClick = useCallback(() => { undoPendingFnRef.current(); setPendingSigs([]); - setMinPendingTimeRemaining(undefined); }, []); const handleSettingsButtonClick = useCallback(() => { @@ -192,49 +73,18 @@ export const SystemSignatures: React.FC = () => { undoPendingFnRef.current = newUndo; }, []); - // Calculate the minimum time remaining for any pending signature - useEffect(() => { - if (pendingSigs.length === 0) { - setMinPendingTimeRemaining(undefined); - return; - } - - const calculateTimeRemaining = () => { - const now = Date.now(); - let minTime: number | undefined = undefined; - - pendingSigs.forEach(sig => { - const extendedSig = sig as unknown as { pendingUntil?: number }; - if (extendedSig.pendingUntil && (minTime === undefined || extendedSig.pendingUntil - now < minTime)) { - minTime = extendedSig.pendingUntil - now; - } - }); - - setMinPendingTimeRemaining(minTime && minTime > 0 ? minTime : undefined); - }; - - calculateTimeRemaining(); - const interval = setInterval(calculateTimeRemaining, 1000); - return () => clearInterval(interval); - }, [pendingSigs]); - return ( - {renderHeaderLabel({ - systemId, - isNotSelectedSystem, - isCompact, - sigCount, - lazyDeleteValue, - pendingCount: pendingSigs.length, - pendingTimeRemaining: minPendingTimeRemaining, - onLazyDeleteChange: handleLazyDeleteChange, - onUndoClick: handleUndoClick, - onSettingsClick: handleSettingsButtonClick, - })} -
+ } windowId={SIGNATURE_WINDOW_ID} > @@ -249,8 +99,6 @@ export const SystemSignatures: React.FC = () => { onLazyDeleteChange={handleLazyDeleteChange} onCountChange={handleSigCountChange} onPendingChange={handlePendingChange} - deletionTiming={deletionTimingValue} - colorByType={colorByTypeValue} /> )} {visible && ( diff --git a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx index 67c4973a..9581c390 100644 --- a/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx +++ b/assets/js/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SystemSignaturesContent/SystemSignaturesContent.tsx @@ -1,29 +1,30 @@ -import { useEffect, useMemo, useRef, useState, useCallback } from 'react'; -import { DataTable, DataTableRowClickEvent, DataTableRowMouseEvent, SortOrder } from 'primereact/datatable'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + DataTable, + DataTableRowClickEvent, + DataTableRowMouseEvent, + DataTableStateEvent, + SortOrder, +} from 'primereact/datatable'; import { Column } from 'primereact/column'; import { PrimeIcons } from 'primereact/api'; import useLocalStorageState from 'use-local-storage-state'; -import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types'; +import { ExtendedSystemSignature, SignatureGroup, SignatureKind, SystemSignature } from '@/hooks/Mapper/types'; import { SignatureSettings } from '@/hooks/Mapper/components/mapRootContent/components/SignatureSettings'; import { WdTooltip, WdTooltipHandlers, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit'; import { SignatureView } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/SignatureView'; import { COMPACT_MAX_WIDTH, + getGroupIdByRawGroup, GROUPS_LIST, MEDIUM_MAX_WIDTH, OTHER_COLUMNS_WIDTH, - getGroupIdByRawGroup, -} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants'; -import { - SHOW_DESCRIPTION_COLUMN_SETTING, - SHOW_UPDATED_COLUMN_SETTING, - SHOW_CHARACTER_COLUMN_SETTING, + SETTINGS_KEYS, SIGNATURE_WINDOW_ID, - SHOW_CHARACTER_PORTRAIT_SETTING, -} from '../SystemSignatures'; + SignatureSettingsType, +} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants'; -import { COSMIC_SIGNATURE } from '../SystemSignatureSettingsDialog'; import { renderAddedTimeLeft, renderDescription, @@ -31,12 +32,13 @@ import { renderInfoColumn, renderUpdatedTimeLeft, } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/renders'; -import { ExtendedSystemSignature } from '../helpers/contentHelpers'; import { useSystemSignaturesData } from '../hooks/useSystemSignaturesData'; import { getSignatureRowClass } from '../helpers/rowStyles'; import useMaxWidth from '@/hooks/Mapper/hooks/useMaxWidth'; import { useClipboard, useHotkey } from '@/hooks/Mapper/hooks'; +const renderColIcon = (sig: SystemSignature) => renderIcon(sig); + type SystemSignaturesSortSettings = { sortField: string; sortOrder: SortOrder; @@ -49,7 +51,7 @@ const SORT_DEFAULT_VALUES: SystemSignaturesSortSettings = { interface SystemSignaturesContentProps { systemId: string; - settings: { key: string; value: boolean | number }[]; + settings: SignatureSettingsType; hideLinkedSignatures?: boolean; selectable?: boolean; onSelect?: (signature: SystemSignature) => void; @@ -57,13 +59,10 @@ interface SystemSignaturesContentProps { onCountChange?: (count: number) => void; onPendingChange?: (pending: ExtendedSystemSignature[], undo: () => void) => void; deletionTiming?: number; - colorByType?: boolean; filterSignature?: (signature: SystemSignature) => boolean; } -const headerInlineStyle = { padding: '2px', fontSize: '12px', lineHeight: '1.333' }; - -export function SystemSignaturesContent({ +export const SystemSignaturesContent = ({ systemId, settings, hideLinkedSignatures, @@ -73,9 +72,26 @@ export function SystemSignaturesContent({ onCountChange, onPendingChange, deletionTiming, - colorByType, filterSignature, -}: SystemSignaturesContentProps) { +}: SystemSignaturesContentProps) => { + const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState(null); + const [showSignatureSettings, setShowSignatureSettings] = useState(false); + const [nameColumnWidth, setNameColumnWidth] = useState('auto'); + const [hoveredSignature, setHoveredSignature] = useState(null); + + const tableRef = useRef(null); + const tooltipRef = useRef(null); + + const isCompact = useMaxWidth(tableRef, COMPACT_MAX_WIDTH); + const isMedium = useMaxWidth(tableRef, MEDIUM_MAX_WIDTH); + + const { clipboardContent, setClipboardContent } = useClipboard(); + + const [sortSettings, setSortSettings] = useLocalStorageState<{ sortField: string; sortOrder: SortOrder }>( + 'window:signatures:sort', + { defaultValue: SORT_DEFAULT_VALUES }, + ); + const { signatures, selectedSignatures, setSelectedSignatures, handleDeleteSelected, handleSelectAll, handlePaste } = useSystemSignaturesData({ systemId, @@ -86,19 +102,6 @@ export function SystemSignaturesContent({ deletionTiming, }); - const [sortSettings, setSortSettings] = useLocalStorageState<{ sortField: string; sortOrder: SortOrder }>( - 'window:signatures:sort', - { defaultValue: SORT_DEFAULT_VALUES }, - ); - - const tableRef = useRef(null); - const tooltipRef = useRef(null); - const [hoveredSignature, setHoveredSignature] = useState(null); - - const isCompact = useMaxWidth(tableRef, COMPACT_MAX_WIDTH); - const isMedium = useMaxWidth(tableRef, MEDIUM_MAX_WIDTH); - - const { clipboardContent, setClipboardContent } = useClipboard(); useEffect(() => { if (selectable) return; if (!clipboardContent?.text) return; @@ -109,6 +112,7 @@ export function SystemSignaturesContent({ }, [selectable, clipboardContent, handlePaste, setClipboardContent]); useHotkey(true, ['a'], handleSelectAll); + useHotkey(false, ['Backspace', 'Delete'], (event: KeyboardEvent) => { const targetWindow = (event.target as HTMLHtmlElement)?.closest(`[data-window-id="${SIGNATURE_WINDOW_ID}"]`); @@ -121,50 +125,45 @@ export function SystemSignaturesContent({ handleDeleteSelected(); }); - const [nameColumnWidth, setNameColumnWidth] = useState('auto'); const handleResize = useCallback(() => { if (!tableRef.current) return; - const tableWidth = tableRef.current.offsetWidth; - const otherColumnsWidth = OTHER_COLUMNS_WIDTH; - setNameColumnWidth(`${tableWidth - otherColumnsWidth}px`); + + setNameColumnWidth(`${tableRef.current.offsetWidth - OTHER_COLUMNS_WIDTH}px`); }, []); + useEffect(() => { if (!tableRef.current) return; + const observer = new ResizeObserver(handleResize); observer.observe(tableRef.current); handleResize(); + return () => { observer.disconnect(); }; }, [handleResize]); - const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState(null); - const [showSignatureSettings, setShowSignatureSettings] = useState(false); - - const handleRowClick = (e: DataTableRowClickEvent) => { + const handleRowClick = useCallback((e: DataTableRowClickEvent) => { setSelectedSignatureForDialog(e.data as SystemSignature); setShowSignatureSettings(true); - }; + }, []); const handleSelectSignatures = useCallback( (e: { value: SystemSignature[] }) => { - if (selectable) { - onSelect?.(e.value[0]); - } else { - setSelectedSignatures(e.value as ExtendedSystemSignature[]); - } + selectable ? onSelect?.(e.value[0]) : setSelectedSignatures(e.value as ExtendedSystemSignature[]); }, - [selectable, onSelect, setSelectedSignatures], + [selectable, onSelect], ); - const showDescriptionColumn = settings.find(s => s.key === SHOW_DESCRIPTION_COLUMN_SETTING)?.value; - const showUpdatedColumn = settings.find(s => s.key === SHOW_UPDATED_COLUMN_SETTING)?.value; - const showCharacterColumn = settings.find(s => s.key === SHOW_CHARACTER_COLUMN_SETTING)?.value; - const showCharacterPortrait = settings.find(s => s.key === SHOW_CHARACTER_PORTRAIT_SETTING)?.value; - - const enabledGroups = settings - .filter(s => GROUPS_LIST.includes(s.key as SignatureGroup) && s.value === true) - .map(s => s.key); + const { showDescriptionColumn, showUpdatedColumn, showCharacterColumn, showCharacterPortrait } = useMemo( + () => ({ + showDescriptionColumn: settings[SETTINGS_KEYS.SHOW_DESCRIPTION_COLUMN] as boolean, + showUpdatedColumn: settings[SETTINGS_KEYS.SHOW_UPDATED_COLUMN] as boolean, + showCharacterColumn: settings[SETTINGS_KEYS.SHOW_CHARACTER_COLUMN] as boolean, + showCharacterPortrait: settings[SETTINGS_KEYS.SHOW_CHARACTER_PORTRAIT] as boolean, + }), + [settings], + ); const filteredSignatures = useMemo(() => { return signatures.filter(sig => { @@ -175,21 +174,58 @@ export function SystemSignaturesContent({ if (hideLinkedSignatures && sig.linked_system) { return false; } - const isCosmicSignature = sig.kind === COSMIC_SIGNATURE; - if (isCosmicSignature) { - const showCosmic = settings.find(y => y.key === COSMIC_SIGNATURE)?.value; - if (!showCosmic) return false; - if (sig.group) { - const preparedGroup = getGroupIdByRawGroup(sig.group); - return enabledGroups.includes(preparedGroup); + if (sig.kind === SignatureKind.CosmicSignature) { + if (!settings[SETTINGS_KEYS.COSMIC_SIGNATURE]) { + return false; } + + if (sig.group) { + const enabledGroups = Object.keys(settings).filter( + x => GROUPS_LIST.includes(x as SignatureGroup) && settings[x as SETTINGS_KEYS], + ); + + return enabledGroups.includes(getGroupIdByRawGroup(sig.group)); + } + return true; - } else { - return settings.find(y => y.key === sig.kind)?.value; } + + return settings[sig.kind]; }); - }, [signatures, hideLinkedSignatures, settings, enabledGroups, filterSignature]); + }, [signatures, hideLinkedSignatures, settings, filterSignature]); + + const onRowMouseEnter = useCallback((e: DataTableRowMouseEvent) => { + setHoveredSignature(e.data as SystemSignature); + tooltipRef.current?.show(e.originalEvent); + }, []); + + const onRowMouseLeave = useCallback(() => { + setHoveredSignature(null); + tooltipRef.current?.hide(); + }, []); + + const refVars = useRef({ settings, selectedSignatures, setSortSettings }); + refVars.current = { settings, selectedSignatures, setSortSettings }; + + // @ts-ignore + const getRowClassName = useCallback(([rowData]) => { + // TODO с какого-то хрена изменился формат. И тут в аргументе прилетает массив вместо даты... а дата лежит первым элементом + if (!rowData) { + return null; + } + + return getSignatureRowClass( + rowData as ExtendedSystemSignature, + refVars.current.selectedSignatures, + refVars.current.settings[SETTINGS_KEYS.COLOR_BY_TYPE] as boolean, + ); + }, []); + + const handleSortSettings = useCallback( + (e: DataTableStateEvent) => refVars.current.setSortSettings({ sortField: e.sortField, sortOrder: e.sortOrder }), + [], + ); return (
@@ -213,31 +249,22 @@ export function SystemSignaturesContent({ onRowDoubleClick={handleRowClick} sortField={sortSettings.sortField} sortOrder={sortSettings.sortOrder} - onSort={e => setSortSettings({ sortField: e.sortField, sortOrder: e.sortOrder })} - onRowMouseEnter={(e: DataTableRowMouseEvent) => { - setHoveredSignature(e.data as SystemSignature); - tooltipRef.current?.show(e.originalEvent); - }} - onRowMouseLeave={() => { - setHoveredSignature(null); - tooltipRef.current?.hide(); - }} - rowClassName={rowData => - getSignatureRowClass(rowData as ExtendedSystemSignature, selectedSignatures, colorByType) - } + onSort={handleSortSettings} + onRowMouseEnter={onRowMouseEnter} + onRowMouseLeave={onRowMouseLeave} + // @ts-ignore + rowClassName={getRowClassName} > renderIcon(sig)} + body={renderColIcon} bodyClassName="p-0 px-1" style={{ maxWidth: 26, minWidth: 26, width: 26 }} /> sig.group ?? ''} @@ -255,7 +281,6 @@ export function SystemSignaturesContent({