feat: add timeline filter support to replay dispatcher and clean up unused dependencies

This commit is contained in:
taDuc
2026-05-27 12:48:17 +07:00
parent ef3766bc2a
commit 4c60e2d773
8 changed files with 915 additions and 454 deletions
+884
View File
@@ -0,0 +1,884 @@
> fe_admin_history_web@2.2.3 lint
> eslint .
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/jsvectormap.d.ts
2:24 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/(full-width-pages)/(auth)/layout.tsx
1:8 warning 'GridShape' is defined but never used @typescript-eslint/no-unused-vars
5:8 warning 'Image' is defined but never used @typescript-eslint/no-unused-vars
6:8 warning 'Link' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/(full-width-pages)/(auth)/reset-password/page.tsx
48:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/editor/[id]/page.tsx
6:20 warning 'MapFeaturePayload' is defined but never used @typescript-eslint/no-unused-vars
13:8 warning 'ReplayPreviewOverlay' is defined but never used @typescript-eslint/no-unused-vars
14:8 warning 'ReplayPreviewLayerPanel' is defined but never used @typescript-eslint/no-unused-vars
16:8 warning 'PublicWikiSidebar' is defined but never used @typescript-eslint/no-unused-vars
22:8 warning 'PresentPlaceSearch' is defined but never used @typescript-eslint/no-unused-vars
22:35 warning 'HistoricalGeometryFocusPayload' is defined but never used @typescript-eslint/no-unused-vars
22:72 warning 'PresentPlaceSelection' is defined but never used @typescript-eslint/no-unused-vars
26:10 warning 'fetchWikiById' is defined but never used @typescript-eslint/no-unused-vars
53:5 warning 'persistBackgroundLayerVisibility' is defined but never used @typescript-eslint/no-unused-vars
89:10 warning 'fitMapToFeatureCollection' is defined but never used @typescript-eslint/no-unused-vars
119:6 warning 'PreviewLinkEntityPopupState' is defined but never used @typescript-eslint/no-unused-vars
545:11 warning 'handleViewerPreviewTimelineYearChange' is assigned a value but never used @typescript-eslint/no-unused-vars
556:11 warning 'handleViewerPreviewTimelineFilterChange' is assigned a value but never used @typescript-eslint/no-unused-vars
583:37 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
616:23 warning 'replayPreviewActiveCursor' is assigned a value but never used @typescript-eslint/no-unused-vars
618:22 warning 'replayPreviewSidebarOpen' is assigned a value but never used @typescript-eslint/no-unused-vars
664:11 warning 'replayPreviewActiveEntity' is assigned a value but never used @typescript-eslint/no-unused-vars
1199:57 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1203:62 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
2420:8 warning React Hook useCallback has a missing dependency: 'setSelectedFeatureIds'. Either include it or remove the dependency array react-hooks/exhaustive-deps
3047:10 warning 'extractWikiBlockquoteText' is defined but never used @typescript-eslint/no-unused-vars
3083:10 warning 'computeFixedPopupPosition' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/user/account/applications/page.tsx
42:9 warning 'config' is assigned a value but never used @typescript-eslint/no-unused-vars
62:15 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
101:23 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
170:25 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/user/account/page.tsx
15:10 warning 'loading' is assigned a value but never used @typescript-eslint/no-unused-vars
31:6 warning React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/user/layout.tsx
4:8 warning 'AppHeader' is defined but never used @typescript-eslint/no-unused-vars
30:6 warning React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/user/projects/[id]/page.tsx
10:8 warning 'PageBreadcrumb' is defined but never used @typescript-eslint/no-unused-vars
31:26 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
36:23 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
51:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
60:6 warning React Hook useEffect has a missing dependency: 'fetchProject'. Either include it or remove the dependency array react-hooks/exhaustive-deps
68:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
107:16 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
121:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
129:26 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
138:21 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
168:21 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
204:24 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
402:74 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
427:25 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
529:51 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/app/user/role-upgrade/page.tsx
3:8 warning 'PageBreadcrumb' is defined but never used @typescript-eslint/no-unused-vars
180:9 warning 'handleItemClick' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/auth/tokenStore.ts
48:59 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
65:11 warning 'refresh' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/auth/SignInForm.tsx
82:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/auth/SignUpForm.tsx
75:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
109:13 warning 'signupRes' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/calendar/Calendar.tsx
44:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/calendar/Calendar.tsx:44:5
42 | useEffect(() => {
43 | // Initialize with some events
> 44 | setEvents([
| ^^^^^^^^^ Avoid calling setState() directly within an effect
45 | {
46 | id: "1",
47 | title: "Event Conf.", react-hooks/set-state-in-effect
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/ecommerce/StatisticsChart.tsx
157:23 error `'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;` react/no-unescaped-entities
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/form/role-upgrade/Editor.tsx
10:12 error Component definition is missing display name react/display-name
10:67 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
10:88 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
36:27 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
82:6 warning React Hook useEffect has a missing dependency: 'onEditorReady'. Either include it or remove the dependency array. If 'onEditorReady' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/header/UserDropdown.tsx
7:10 warning 'fullDataUser' is defined but never used @typescript-eslint/no-unused-vars
14:9 warning 'router' is assigned a value but never used @typescript-eslint/no-unused-vars
18:10 warning 'loading' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationDetailModal.tsx
91:14 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx
182:33 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx:182:33
180 | onClick={() => onSort("status")}
181 | >
> 182 | Trạng thái <SortIcon column="status" />
| ^^^^^^^^ This component is created during render
183 | </div>
184 | </TableCell>
185 | <TableCell
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx:48:20
46 | };
47 |
> 48 | const SortIcon = ({ column }: { column: AppSortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 49 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 50 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 79 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 80 | };
| ^^^^ The component is created during render here
81 |
82 | const getStatusBadge = (status: string | number) => {
83 | const s = status?.toString(); react-hooks/static-components
193:31 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx:193:31
191 | onClick={() => onSort("created_at")}
192 | >
> 193 | Ngày nộp <SortIcon column="created_at" />
| ^^^^^^^^ This component is created during render
194 | </div>
195 | </TableCell>
196 | <TableCell
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx:48:20
46 | };
47 |
> 48 | const SortIcon = ({ column }: { column: AppSortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 49 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 50 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 79 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 80 | };
| ^^^^ The component is created during render here
81 |
82 | const getStatusBadge = (status: string | number) => {
83 | const s = status?.toString(); react-hooks/static-components
204:31 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx:204:31
202 | onClick={() => onSort("reviewed_at")}
203 | >
> 204 | Cập nhật <SortIcon column="reviewed_at" />
| ^^^^^^^^ This component is created during render
205 | </div>
206 | </TableCell>
207 | <TableCell
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/ApplicationTable.tsx:48:20
46 | };
47 |
> 48 | const SortIcon = ({ column }: { column: AppSortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 49 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 50 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 79 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 80 | };
| ^^^^ The component is created during render here
81 |
82 | const getStatusBadge = (status: string | number) => {
83 | const s = status?.toString(); react-hooks/static-components
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx
101:33 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:101:33
99 | onClick={() => onSort("display_name")}
100 | >
> 101 | Người dùng <SortIcon column="display_name" />
| ^^^^^^^^ This component is created during render
102 | </div>
103 | </TableCell>
104 |
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:52:20
50 | };
51 |
> 52 | const SortIcon = ({ column }: { column: SortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 53 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 54 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 83 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 84 | };
| ^^^^ The component is created during render here
85 |
86 | return (
87 | <div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]"> react-hooks/static-components
113:28 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:113:28
111 | onClick={() => onSort("email")}
112 | >
> 113 | Email <SortIcon column="email" />
| ^^^^^^^^ This component is created during render
114 | </div>
115 | </TableCell>
116 |
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:52:20
50 | };
51 |
> 52 | const SortIcon = ({ column }: { column: SortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 53 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 54 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 83 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 84 | };
| ^^^^ The component is created during render here
85 |
86 | return (
87 | <div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]"> react-hooks/static-components
165:36 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:165:36
163 | onClick={() => onSort("created_at")}
164 | >
> 165 | Ngày tham gia <SortIcon column="created_at" />
| ^^^^^^^^ This component is created during render
166 | </div>
167 | </TableCell>
168 |
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:52:20
50 | };
51 |
> 52 | const SortIcon = ({ column }: { column: SortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 53 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 54 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 83 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 84 | };
| ^^^^ The component is created during render here
85 |
86 | return (
87 | <div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]"> react-hooks/static-components
177:31 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:177:31
175 | onClick={() => onSort("updated_at")}
176 | >
> 177 | Cập nhật <SortIcon column="updated_at" />
| ^^^^^^^^ This component is created during render
178 | </div>
179 | </TableCell>
180 |
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/BasicTableOne.tsx:52:20
50 | };
51 |
> 52 | const SortIcon = ({ column }: { column: SortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 53 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 54 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 83 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 84 | };
| ^^^^ The component is created during render here
85 |
86 | return (
87 | <div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]"> react-hooks/static-components
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx
141:30 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:141:30
139 | <TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
140 | <div className="flex items-center cursor-pointer select-none" onClick={() => onSort("original_name")}>
> 141 | Tên tệp <SortIcon column="original_name" />
| ^^^^^^^^ This component is created during render
142 | </div>
143 | </TableCell>
144 | <TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:76:20
74 | };
75 |
> 76 | const SortIcon = ({ column }: { column: MediaSortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 77 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 78 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 97 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 98 | };
| ^^^^ The component is created during render here
99 |
100 | const getMimeTypeBadge = (mimeType: string) => {
101 | if (mimeType.includes("image")) { react-hooks/static-components
146:32 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:146:32
144 | <TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
145 | <div className="flex items-center cursor-pointer select-none" onClick={() => onSort("mime_type")}>
> 146 | Định dạng <SortIcon column="mime_type" />
| ^^^^^^^^ This component is created during render
147 | </div>
148 | </TableCell>
149 | <TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:76:20
74 | };
75 |
> 76 | const SortIcon = ({ column }: { column: MediaSortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 77 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 78 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 97 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 98 | };
| ^^^^ The component is created during render here
99 |
100 | const getMimeTypeBadge = (mimeType: string) => {
101 | if (mimeType.includes("image")) { react-hooks/static-components
151:33 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:151:33
149 | <TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
150 | <div className="flex items-center cursor-pointer select-none" onClick={() => onSort("size")}>
> 151 | Kích thước <SortIcon column="size" />
| ^^^^^^^^ This component is created during render
152 | </div>
153 | </TableCell>
154 | <TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:76:20
74 | };
75 |
> 76 | const SortIcon = ({ column }: { column: MediaSortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 77 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 78 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 97 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 98 | };
| ^^^^ The component is created during render here
99 |
100 | const getMimeTypeBadge = (mimeType: string) => {
101 | if (mimeType.includes("image")) { react-hooks/static-components
156:35 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:156:35
154 | <TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
155 | <div className="flex items-center cursor-pointer select-none" onClick={() => onSort("created_at")}>
> 156 | Ngày tải lên <SortIcon column="created_at" />
| ^^^^^^^^ This component is created during render
157 | </div>
158 | </TableCell>
159 | <TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:76:20
74 | };
75 |
> 76 | const SortIcon = ({ column }: { column: MediaSortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 77 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 78 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 97 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 98 | };
| ^^^^ The component is created during render here
99 |
100 | const getMimeTypeBadge = (mimeType: string) => {
101 | if (mimeType.includes("image")) { react-hooks/static-components
161:31 error Error: Cannot create components during render
Components created during render will reset their state each time they are created. Declare components outside of render.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:161:31
159 | <TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
160 | <div className="flex items-center cursor-pointer select-none" onClick={() => onSort("updated_at")}>
> 161 | Cập nhật <SortIcon column="updated_at" />
| ^^^^^^^^ This component is created during render
162 | </div>
163 | </TableCell>
164 | <TableCell isHeader className="px-5 py-3 font-medium text-center text-gray-500 text-theme-xs dark:text-gray-400 min-w-[120px]">
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/MediaTable.tsx:76:20
74 | };
75 |
> 76 | const SortIcon = ({ column }: { column: MediaSortColumn }) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 77 | const isActive = sortBy === column;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 78 | return (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 97 | );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 98 | };
| ^^^^ The component is created during render here
99 |
100 | const getMimeTypeBadge = (mimeType: string) => {
101 | if (mimeType.includes("image")) { react-hooks/static-components
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/tables/UserDetailModal.tsx
39:32 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/ui/table/index.tsx
58:50 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/AccountDetails.tsx
31:7 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/AccountDetails.tsx:31:7
29 | useEffect(() => {
30 | if (!isOpen) {
> 31 | setFormValues({
| ^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
32 | old_password: "",
33 | new_password: "",
34 | confirm_password: "", react-hooks/set-state-in-effect
71:44 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
76:19 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/ApplicationList.tsx
142:19 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/Media.tsx
33:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/Media.tsx:33:5
31 |
32 | useEffect(() => {
> 33 | setLocalMedia(data?.data || []);
| ^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
34 | }, [data]);
35 |
36 | const isImageFile = (file: MediaItem) => { react-hooks/set-state-in-effect
117:16 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
159:16 warning 'error' is defined but never used @typescript-eslint/no-unused-vars
210:13 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/UserInfoCard.tsx
28:7 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/UserInfoCard.tsx:28:7
26 | useEffect(() => {
27 | if (data?.data?.profile) {
> 28 | setFormData({
| ^^^^^^^^^^^ Avoid calling setState() directly within an effect
29 | display_name: data.data.profile.display_name || "",
30 | phone: data.data.profile.phone || "",
31 | bio: data.data.profile.bio || "", react-hooks/set-state-in-effect
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/components/user-profile/UserMetaCard.tsx
19:9 warning 'router' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/config/config.ts
25:12 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
31:31 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
39:39 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
44:20 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
82:43 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
99:43 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
110:56 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
128:23 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
142:24 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/context/ThemeContext.tsx
26:5 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/context/ThemeContext.tsx:26:5
24 | const initialTheme = savedTheme || "light"; // Default to light theme
25 |
> 26 | setTheme(initialTheme);
| ^^^^^^^^ Avoid calling setState() directly within an effect
27 | setIsInitialized(true);
28 | }, []);
29 | react-hooks/set-state-in-effect
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/interface/common.ts
1:37 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
5:12 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
18:12 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
28:12 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/layout/AppHeader.tsx
2:10 warning 'ThemeToggleButton' is defined but never used @typescript-eslint/no-unused-vars
3:8 warning 'NotificationDropdown' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/service/mediaService.ts
54:9 warning 'res' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/store/StoreProvider.tsx
13:29 error Error: Cannot access refs during render
React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/store/StoreProvider.tsx:13:29
11 | }) {
12 | const storeRef = useRef(store);
> 13 | return <Provider store={storeRef.current}>{children}</Provider>;
| ^^^^^^^^^^^^^^^^ Cannot access ref value during render
14 | } react-hooks/refs
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/store/features/userSlice.ts
15:26 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
32:63 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/api/config.ts
6:7 warning 'GOONG_PROXY_BASE_PATH' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/api/http.ts
47:39 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
52:14 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
67:19 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
104:29 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
104:78 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
106:52 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/Panel.tsx
12:5 warning 'badge' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/ReplayEffectsSidebar.tsx
22:34 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
111:7 warning 'uiInputOptionValues' is assigned a value but never used @typescript-eslint/no-unused-vars
165:25 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
183:25 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
212:16 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
213:16 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
221:64 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
226:49 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
316:20 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
761:11 warning 'safeYear' is assigned a value but never used @typescript-eslint/no-unused-vars
822:11 warning 'firstId' is assigned a value but never used @typescript-eslint/no-unused-vars
1167:27 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1180:41 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1190:13 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/ReplayEffectsSidebar.tsx:1190:13
1188 | if (actions.length > 0) {
1189 | const first = actions[0];
> 1190 | setComposerFunctionName(first.function_name);
| ^^^^^^^^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
1191 | const def = definitions[first.function_name];
1192 | if (def) {
1193 | setComposerDraftValues(def.deserialize(first.params)); react-hooks/set-state-in-effect
1354:27 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1380:53 warning Compilation Skipped: `this` is not supported syntax
React Compiler does not support compiling functions that use `this`.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/ReplayEffectsSidebar.tsx:1380:53
1378 | ],
1379 | handlers: {
> 1380 | link: function (this: { quill?: any }) {
| ^^^^^^^^^^^^^^^^^^^^^ `this` was used here
1381 | onLinkClick?.(this?.quill);
1382 | },
1383 | }, react-hooks/unsupported-syntax
1380:69 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1541:10 warning 'toInputNumber' is defined but never used @typescript-eslint/no-unused-vars
1554:10 warning 'toNumberOr' is defined but never used @typescript-eslint/no-unused-vars
1566:10 warning 'emptyToNull' is defined but never used @typescript-eslint/no-unused-vars
1570:10 warning 'emptyToUndefined' is defined but never used @typescript-eslint/no-unused-vars
1574:10 warning 'compactTrailingUndefined' is defined but never used @typescript-eslint/no-unused-vars
1582:10 warning 'normalizeSelectValue' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/ReplayPreviewLayerPanel.tsx
5:10 warning 'GEO_TYPE_KEYS' is defined but never used @typescript-eslint/no-unused-vars
168:7 warning 'buttonClassName' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/editor/ReplayPreviewOverlay.tsx
112:25 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/map/mapUtils.ts
321:47 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
487:46 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1065:44 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1179:36 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/map/useMapInstance.ts
65:34 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/map/useMapLayers.ts
12:10 warning 'FeatureCollection' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/map/useMapSync.ts
5:10 warning 'EMPTY_FEATURE_COLLECTION' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PinnedWikiPopup.tsx
24:5 warning 'featureId' is defined but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx
4:8 warning 'EditorMap' is defined but never used @typescript-eslint/no-unused-vars
10:10 warning 'useReplayPreview' is defined but never used @typescript-eslint/no-unused-vars
20:10 warning 'persistBackgroundLayerVisibility' is defined but never used @typescript-eslint/no-unused-vars
28:10 warning 'deepClone' is defined but never used @typescript-eslint/no-unused-vars
55:20 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
81:23 error Component definition is missing display name react/display-name
81:34 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
90:5 warning 'entityWikiLinks' is defined but never used @typescript-eslint/no-unused-vars
96:5 warning 'onViewModeChange' is defined but never used @typescript-eslint/no-unused-vars
98:5 warning 'isGlobalLoading' is assigned a value but never used @typescript-eslint/no-unused-vars
101:5 warning 'selectedStageId' is assigned a value but never used @typescript-eslint/no-unused-vars
102:5 warning 'selectedStepIndex' is assigned a value but never used @typescript-eslint/no-unused-vars
106:5 warning 'mapHandleRef' is defined but never used @typescript-eslint/no-unused-vars
110:5 warning 'previewEntityFocusToken' is defined but never used @typescript-eslint/no-unused-vars
114:5 warning 'isLargeScreen' is defined but never used @typescript-eslint/no-unused-vars
115:5 warning 'setIsLargeScreen' is defined but never used @typescript-eslint/no-unused-vars
143:9 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:143:9
141 | useEffect(() => {
142 | setPreviewWikiCache({});
> 143 | setPreviewWikiError(null);
| ^^^^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
144 | setIsPreviewWikiLoading(false);
145 | setPreviewPinnedWikiPopupAnchor(null);
146 | setPreviewActiveEntityId(null); react-hooks/set-state-in-effect
209:13 error Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
* Update external systems with the latest state from React.
* Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:209:13
207 | useEffect(() => {
208 | if (!mode || !replayPreviewSidebarOpen) {
> 209 | setPreviewWikiError(null);
| ^^^^^^^^^^^^^^^^^^^ Avoid calling setState() directly within an effect
210 | setIsPreviewWikiLoading(false);
211 | return;
212 | } react-hooks/set-state-in-effect
261:8 warning React Hook useEffect has a missing dependency: 'setPreviewWikiCache'. Either include it or remove the dependency array. If 'setPreviewWikiCache' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
305:11 warning 'replayPreviewActiveEntityGeometries' is assigned a value but never used @typescript-eslint/no-unused-vars
315:51 error Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `setPreviewActiveEntityId`, but the source dependencies were [openReplayPreviewWikiPanelById, previewRelations.entitiesById, previewRelations.entityWikisById, setSelectedFeatureIds]. Inferred different dependency than source.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:315:51
313 |
314 | // Handle replay preview entity selection
> 315 | const selectReplayPreviewEntity = useCallback((
| ^
> 316 | entityId: string,
| ^^^^^^^^^^^^^^^^^^^^^^^^^
> 317 | options?: {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
> 352 | }
| ^^^^^^^^^^^^^^^^^^^^^^^^^
> 353 | }, [
| ^^^^^^ Could not preserve existing manual memoization
354 | openReplayPreviewWikiPanelById,
355 | previewRelations.entitiesById,
356 | previewRelations.entityWikisById, react-hooks/preserve-manual-memoization
315:51 error Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `setPreviewEntityFocusToken`, but the source dependencies were [openReplayPreviewWikiPanelById, previewRelations.entitiesById, previewRelations.entityWikisById, setSelectedFeatureIds]. Inferred different dependency than source.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:315:51
313 |
314 | // Handle replay preview entity selection
> 315 | const selectReplayPreviewEntity = useCallback((
| ^
> 316 | entityId: string,
| ^^^^^^^^^^^^^^^^^^^^^^^^^
> 317 | options?: {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
> 352 | }
| ^^^^^^^^^^^^^^^^^^^^^^^^^
> 353 | }, [
| ^^^^^^ Could not preserve existing manual memoization
354 | openReplayPreviewWikiPanelById,
355 | previewRelations.entitiesById,
356 | previewRelations.entityWikisById, react-hooks/preserve-manual-memoization
353:8 warning React Hook useCallback has missing dependencies: 'setPreviewActiveEntityId' and 'setPreviewEntityFocusToken'. Either include them or remove the dependency array. If 'setPreviewActiveEntityId' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
361:51 error Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `setPreviewActiveEntityId`, but the source dependencies were [closeReplayPreviewWikiPanel, setSelectedFeatureIds]. Inferred different dependency than source.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:361:51
359 |
360 | // Handle close sidebar
> 361 | const closeReplayPreviewSidebar = useCallback(() => {
| ^^^^^^^
> 362 | closeReplayPreviewWikiPanel();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 363 | setPreviewActiveEntityId(null);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 364 | setIsPreviewEntitySidebarOpen(false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 365 | setPreviewWikiError(null);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 366 | setPreviewLinkEntityPopup(null);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 367 | setSelectedFeatureIds([]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 368 | }, [closeReplayPreviewWikiPanel, setSelectedFeatureIds]);
| ^^^^^^ Could not preserve existing manual memoization
369 |
370 | // Play selected battle replay
371 | const handlePlaySelectedReplay = useCallback((replay: BattleReplay) => { react-hooks/preserve-manual-memoization
368:8 warning React Hook useCallback has a missing dependency: 'setPreviewActiveEntityId'. Either include it or remove the dependency array. If 'setPreviewActiveEntityId' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
517:11 warning 'mapRenderDraft' is assigned a value but never used @typescript-eslint/no-unused-vars
575:11 warning 'mapLabelContextDraft' is assigned a value but never used @typescript-eslint/no-unused-vars
580:11 warning 'viewerPreviewSelectedReplay' is assigned a value but never used @typescript-eslint/no-unused-vars
600:55 error Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `setPreviewEntityFocusToken`, but the source dependencies were [previewRelations.geometryEntityIds, selectReplayPreviewEntity]. Inferred different dependency than source.
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/components/preview/PreviewLayout.tsx:600:55
598 | }, []);
599 |
> 600 | const handleFocusHistoricalGeometry = useCallback((payload: HistoricalGeometryFocusPayload) => {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 601 | setFocusedPresentPlace(null);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 602 | setSelectedFeatureIds([payload.geometry.id]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 612 | }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 613 | }, [previewRelations.geometryEntityIds, selectReplayPreviewEntity]);
| ^^^^^^ Could not preserve existing manual memoization
614 |
615 | const effectiveGeometryVisibility = useMemo(() => {
616 | return geometryVisibility; react-hooks/preserve-manual-memoization
613:8 warning React Hook useCallback has a missing dependency: 'setPreviewEntityFocusToken'. Either include it or remove the dependency array. If 'setPreviewEntityFocusToken' changes too often, find the parent component that defines it and wrap that definition in useCallback react-hooks/exhaustive-deps
619:11 warning 'handleSetMode' is assigned a value but never used @typescript-eslint/no-unused-vars
884:10 warning 'buildPreviewRelationIndex' is defined but never used @typescript-eslint/no-unused-vars
909:11 warning 'wikiMap' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/lib/editor/snapshot/editorSnapshot.ts
1229:46 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1230:44 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1231:49 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1232:53 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
1281:27 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/lib/map/styles/shared/pointStyle.ts
26:6 warning 'PointIconVariant' is defined but never used @typescript-eslint/no-unused-vars
45:7 warning 'DRAFT_FILL' is assigned a value but never used @typescript-eslint/no-unused-vars
46:7 warning 'DRAFT_RIM' is assigned a value but never used @typescript-eslint/no-unused-vars
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/lib/replay/replayDispatcher.ts
4:5 warning 'GeoFunctionName' is defined but never used @typescript-eslint/no-unused-vars
5:5 warning 'MapFunctionName' is defined but never used @typescript-eslint/no-unused-vars
6:5 warning 'NarrativeFunctionName' is defined but never used @typescript-eslint/no-unused-vars
8:5 warning 'UIOptionName' is defined but never used @typescript-eslint/no-unused-vars
22:14 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
50:29 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
196:40 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
196:59 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/home/amoratran/wsp/ultimate-history-map/FrontEndUser/src/uhm/lib/replay/useReplayPreview.ts
258:8 warning React Hook useEffect has a missing dependency: 'setDialogWithRef'. Either include it or remove the dependency array react-hooks/exhaustive-deps
✖ 204 problems (99 errors, 105 warnings)
+2 -76
View File
@@ -3,27 +3,23 @@
import { useCallback, useEffect, useMemo, useRef, useState, type SetStateAction } from "react"; import { useCallback, useEffect, useMemo, useRef, useState, type SetStateAction } from "react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
import { useShallow } from "zustand/react/shallow"; import { useShallow } from "zustand/react/shallow";
import Map, { type MapFeaturePayload, type MapHandle } from "@/uhm/components/Map"; import Map, { type MapHandle } from "@/uhm/components/Map";
import Editor from "@/uhm/components/Editor"; import Editor from "@/uhm/components/Editor";
import BackgroundLayersPanel from "@/uhm/components/editor/BackgroundLayersPanel"; import BackgroundLayersPanel from "@/uhm/components/editor/BackgroundLayersPanel";
import TimelineBar from "@/uhm/components/ui/TimelineBar"; import TimelineBar from "@/uhm/components/ui/TimelineBar";
import SelectedGeometryPanel from "@/uhm/components/editor/SelectedGeometryPanel"; import SelectedGeometryPanel from "@/uhm/components/editor/SelectedGeometryPanel";
import ReplayTimelineSidebar from "@/uhm/components/editor/ReplayTimelineSidebar"; import ReplayTimelineSidebar from "@/uhm/components/editor/ReplayTimelineSidebar";
import ReplayEffectsSidebar from "@/uhm/components/editor/ReplayEffectsSidebar"; import ReplayEffectsSidebar from "@/uhm/components/editor/ReplayEffectsSidebar";
import ReplayPreviewOverlay from "@/uhm/components/editor/ReplayPreviewOverlay";
import ReplayPreviewLayerPanel from "@/uhm/components/editor/ReplayPreviewLayerPanel";
import PreviewLayout from "@/uhm/components/preview/PreviewLayout"; import PreviewLayout from "@/uhm/components/preview/PreviewLayout";
import PublicWikiSidebar from "@/uhm/components/wiki/PublicWikiSidebar";
import WikiSidebarPanel from "@/uhm/components/wiki/WikiSidebarPanel"; import WikiSidebarPanel from "@/uhm/components/wiki/WikiSidebarPanel";
import ProjectEntityRefsPanel from "@/uhm/components/editor/ProjectEntityRefsPanel"; import ProjectEntityRefsPanel from "@/uhm/components/editor/ProjectEntityRefsPanel";
import EntityWikiBindingsPanel from "@/uhm/components/editor/EntityWikiBindingsPanel"; import EntityWikiBindingsPanel from "@/uhm/components/editor/EntityWikiBindingsPanel";
import GeometryBindingPanel from "@/uhm/components/editor/GeometryBindingPanel"; import GeometryBindingPanel from "@/uhm/components/editor/GeometryBindingPanel";
import ImageOverlayPanel from "@/uhm/components/editor/ImageOverlayPanel"; import ImageOverlayPanel from "@/uhm/components/editor/ImageOverlayPanel";
import PresentPlaceSearch, { type HistoricalGeometryFocusPayload, type PresentPlaceSelection } from "@/uhm/components/editor/PresentPlaceSearch";
import { Entity, fetchEntities, searchEntitiesByName } from "@/uhm/api/entities"; import { Entity, fetchEntities, searchEntitiesByName } from "@/uhm/api/entities";
import { ApiError } from "@/uhm/api/http"; import { ApiError } from "@/uhm/api/http";
import { fetchCurrentUser } from "@/uhm/api/auth"; import { fetchCurrentUser } from "@/uhm/api/auth";
import { fetchWikiById, searchWikisByTitle, type Wiki } from "@/uhm/api/wikis"; import { searchWikisByTitle, type Wiki } from "@/uhm/api/wikis";
import { searchGeometriesByEntityName, fetchGeometriesByBBox, type EntityGeometriesSearchItem, type EntityGeometrySearchGeo } from "@/uhm/api/geometries"; import { searchGeometriesByEntityName, fetchGeometriesByBBox, type EntityGeometriesSearchItem, type EntityGeometrySearchGeo } from "@/uhm/api/geometries";
import { WORLD_BBOX } from "@/uhm/lib/map/geo/constants"; import { WORLD_BBOX } from "@/uhm/lib/map/geo/constants";
import { import {
@@ -50,7 +46,6 @@ import { buildFeatureEntityPatch } from "@/uhm/lib/editor/entity/entityBinding";
import { newId } from "@/uhm/lib/utils/id"; import { newId } from "@/uhm/lib/utils/id";
import { import {
loadBackgroundLayerVisibilityFromStorage, loadBackgroundLayerVisibilityFromStorage,
persistBackgroundLayerVisibility,
} from "@/uhm/lib/editor/background/backgroundVisibilityStorage"; } from "@/uhm/lib/editor/background/backgroundVisibilityStorage";
import { deepClone } from "@/uhm/lib/editor/draft/draftDiff"; import { deepClone } from "@/uhm/lib/editor/draft/draftDiff";
import { useProjectCommands } from "@/uhm/lib/editor/project/useProjectCommands"; import { useProjectCommands } from "@/uhm/lib/editor/project/useProjectCommands";
@@ -86,7 +81,6 @@ import {
normalizeReplaysForCompare, normalizeReplaysForCompare,
normalizeWikisForCompare, normalizeWikisForCompare,
} from "@/uhm/lib/editor/editorPageUtils"; } from "@/uhm/lib/editor/editorPageUtils";
import { fitMapToFeatureCollection } from "@/uhm/components/map/mapUtils";
const CURRENT_YEAR = new Date().getUTCFullYear(); const CURRENT_YEAR = new Date().getUTCFullYear();
const DEFAULT_EDITOR_USER_ID = "local-editor"; const DEFAULT_EDITOR_USER_ID = "local-editor";
@@ -116,12 +110,6 @@ type PreviewRelationIndex = {
wikiBySlug: Record<string, Wiki>; wikiBySlug: Record<string, Wiki>;
}; };
type PreviewLinkEntityPopupState = {
slug: string;
entities: Entity[];
top: number;
left: number;
};
export default function Page() { export default function Page() {
return ( return (
@@ -542,27 +530,6 @@ function EditorPageContent() {
setTimelineDraftYear(clampYearToFixedRange(Math.trunc(nextYear))); setTimelineDraftYear(clampYearToFixedRange(Math.trunc(nextYear)));
}, [setTimelineDraftYear]); }, [setTimelineDraftYear]);
const handleViewerPreviewTimelineYearChange = useCallback((nextYear: number) => {
setPreviewSession((prev) =>
prev
? {
...prev,
timelineYear: clampYearToFixedRange(Math.trunc(nextYear)),
}
: prev
);
}, []);
const handleViewerPreviewTimelineFilterChange = useCallback((enabled: boolean) => {
setPreviewSession((prev) =>
prev
? {
...prev,
timelineFilterEnabled: enabled,
}
: prev
);
}, []);
// Preview specific UI states // Preview specific UI states
const [previewActiveEntityId, setPreviewActiveEntityId] = useState<string | null>(null); const [previewActiveEntityId, setPreviewActiveEntityId] = useState<string | null>(null);
@@ -613,9 +580,7 @@ function EditorPageContent() {
hiddenGeometryIds: replayPreviewHiddenGeometryIds, hiddenGeometryIds: replayPreviewHiddenGeometryIds,
timelineYear: replayPreviewTimelineYear, timelineYear: replayPreviewTimelineYear,
timelineFilterEnabled: replayPreviewTimelineFilterEnabled, timelineFilterEnabled: replayPreviewTimelineFilterEnabled,
activeCursor: replayPreviewActiveCursor,
activeWikiId: replayPreviewActiveWikiId, activeWikiId: replayPreviewActiveWikiId,
sidebarOpen: replayPreviewSidebarOpen,
} = replayPreview; } = replayPreview;
// Draft hiển thị trong preview có thể ẩn bớt geometry theo action replay. // Draft hiển thị trong preview có thể ẩn bớt geometry theo action replay.
@@ -661,12 +626,6 @@ function EditorPageContent() {
return activeWikiEntityIds[0] || previewActiveEntityId; return activeWikiEntityIds[0] || previewActiveEntityId;
}, [previewActiveEntityId, previewRelations.wikiEntityIdsById, replayPreviewActiveWikiId]); }, [previewActiveEntityId, previewRelations.wikiEntityIdsById, replayPreviewActiveWikiId]);
const replayPreviewActiveEntity = useMemo(() => {
return replayPreviewActiveEntityId
? previewRelations.entitiesById[replayPreviewActiveEntityId] || null
: null;
}, [replayPreviewActiveEntityId, previewRelations.entitiesById]);
const replayPreviewActiveEntityGeometries = useMemo(() => { const replayPreviewActiveEntityGeometries = useMemo(() => {
return replayPreviewActiveEntityId return replayPreviewActiveEntityId
? previewRelations.entityGeometriesById[replayPreviewActiveEntityId] || EMPTY_FEATURE_COLLECTION ? previewRelations.entityGeometriesById[replayPreviewActiveEntityId] || EMPTY_FEATURE_COLLECTION
@@ -3044,25 +3003,6 @@ function snapshotWikiToWiki(snapshot: WikiSnapshot, wikiCache: Record<string, Wi
}; };
} }
function extractWikiBlockquoteText(content: string | null | undefined): string {
if (!content) return "";
const blockquoteMatch = content.match(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/i);
const rawText = blockquoteMatch?.[1]?.trim() || "";
if (!rawText) return "";
return rawText
.replace(/<[^>]*>/g, "")
.replace(/&nbsp;/gi, " ")
.replace(/\u00a0/g, " ")
.replace(/&amp;/gi, "&")
.replace(/&lt;/gi, "<")
.replace(/&gt;/gi, ">")
.replace(/&quot;/gi, '"')
.replace(/&#39;/g, "'")
.replace(/\s+/g, " ")
.trim();
}
function pushUniqueString(target: Record<string, string[]>, key: string, value: string) { function pushUniqueString(target: Record<string, string[]>, key: string, value: string) {
if (!target[key]) { if (!target[key]) {
@@ -3080,20 +3020,6 @@ function normalizeRelationArrays(target: Record<string, string[]>) {
} }
} }
function computeFixedPopupPosition(rect: DOMRect, width: number, height: number) {
const margin = 12;
const viewportWidth = typeof window !== "undefined" ? window.innerWidth : 1440;
const viewportHeight = typeof window !== "undefined" ? window.innerHeight : 900;
const preferredLeft = rect.right + margin;
const maxLeft = Math.max(margin, viewportWidth - width - margin);
const left = Math.min(preferredLeft, maxLeft);
const preferredTop = rect.top;
const maxTop = Math.max(margin, viewportHeight - height - margin);
const top = Math.max(margin, Math.min(preferredTop, maxTop));
return { top, left };
}
function isTypingTarget(target: EventTarget | null): boolean { function isTypingTarget(target: EventTarget | null): boolean {
if (!(target instanceof HTMLElement)) return false; if (!(target instanceof HTMLElement)) return false;
+17 -1
View File
@@ -41,7 +41,23 @@ export function Panel({
gap: 8, gap: 8,
}} }}
> >
<span>{title}</span> <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
<span>{title}</span>
{badge && (
<span
style={{
fontSize: 11,
fontWeight: 500,
background: "#374151",
color: "#cbd5e1",
padding: "2px 6px",
borderRadius: 4,
}}
>
{badge}
</span>
)}
</div>
<span <span
style={{ style={{
width: 22, width: 22,
@@ -108,11 +108,6 @@ const uiSimpleOptionValues: UIOptionName[] = [
"zoom_panel", "zoom_panel",
]; ];
const uiInputOptionValues: UIOptionName[] = [
"wiki",
"toast",
];
const mapCameraOptionChoices: Array<{ label: string; value: MapCameraOptionName }> = [ const mapCameraOptionChoices: Array<{ label: string; value: MapCameraOptionName }> = [
{ label: "LngLat", value: "center" }, { label: "LngLat", value: "center" },
{ label: "Zoom", value: "zoom" }, { label: "Zoom", value: "zoom" },
@@ -758,8 +753,6 @@ function MapFunctionShortcutPanel({
currentTimelineYear: number; currentTimelineYear: number;
onAppendActions: (actions: ReplayAction<MapFunctionName>[], label: string) => void; onAppendActions: (actions: ReplayAction<MapFunctionName>[], label: string) => void;
}) { }) {
const safeYear = Math.trunc(currentTimelineYear);
return ( return (
<Panel title="Map Functions" defaultOpen> <Panel title="Map Functions" defaultOpen>
<div style={{ display: "grid", gap: 10 }}> <div style={{ display: "grid", gap: 10 }}>
@@ -819,7 +812,6 @@ function GeoFunctionShortcutPanel({
}) { }) {
const selectedIds = selectedGeometries.map((item) => item.id); const selectedIds = selectedGeometries.map((item) => item.id);
const selectedCount = selectedIds.length; const selectedCount = selectedIds.length;
const firstId = selectedIds[0] || "";
const hasSelection = selectedCount > 0; const hasSelection = selectedCount > 0;
return ( return (
@@ -1538,24 +1530,6 @@ function asString(value: unknown) {
return typeof value === "string" ? value : value == null ? "" : String(value); return typeof value === "string" ? value : value == null ? "" : String(value);
} }
function toInputNumber(value: unknown, fallback: string) {
if (typeof value === "number" && Number.isFinite(value)) return String(value);
if (typeof value === "string" && value.trim().length) return value;
return fallback;
}
function toOptionalNumber(value: unknown) {
const raw = asString(value).trim();
if (!raw.length) return undefined;
const parsed = Number(raw);
return Number.isFinite(parsed) ? parsed : undefined;
}
function toNumberOr(value: unknown, fallback: number) {
const parsed = toOptionalNumber(value);
return parsed == null ? fallback : parsed;
}
function toStringArray(value: unknown): string[] { function toStringArray(value: unknown): string[] {
if (!Array.isArray(value)) return []; if (!Array.isArray(value)) return [];
return value return value
@@ -1563,26 +1537,6 @@ function toStringArray(value: unknown): string[] {
.filter((item) => item.length > 0); .filter((item) => item.length > 0);
} }
function emptyToNull(value: string) {
return value.trim().length ? value : null;
}
function emptyToUndefined(value: string) {
return value.trim().length ? value : undefined;
}
function compactTrailingUndefined(values: unknown[]) {
const next = [...values];
while (next.length > 0 && next[next.length - 1] === undefined) {
next.pop();
}
return next;
}
function normalizeSelectValue(value: string, fallback: string) {
return value.trim().length ? value : fallback;
}
function buildUiEffectsDraftState(actions: ReplayAction<UIOptionName>[]): UiEffectsDraftState { function buildUiEffectsDraftState(actions: ReplayAction<UIOptionName>[]): UiEffectsDraftState {
const selected = buildEmptyUiOptionSelection(); const selected = buildEmptyUiOptionSelection();
const visible = buildDefaultUiVisibilityState(); const visible = buildDefaultUiVisibilityState();
+9 -309
View File
@@ -1,32 +1,23 @@
"use client"; "use client";
import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from "react"; import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from "react";
import EditorMap, { type MapFeaturePayload, type MapHandle } from "@/uhm/components/Map"; import { type MapFeaturePayload, type MapHandle } from "@/uhm/components/Map";
import PresentPlaceSearch, { type HistoricalGeometryFocusPayload, type PresentPlaceSelection } from "@/uhm/components/editor/PresentPlaceSearch"; import PresentPlaceSearch, { type HistoricalGeometryFocusPayload, type PresentPlaceSelection } from "@/uhm/components/editor/PresentPlaceSearch";
import ReplayPreviewOverlay from "@/uhm/components/editor/ReplayPreviewOverlay"; import ReplayPreviewOverlay from "@/uhm/components/editor/ReplayPreviewOverlay";
import ReplayPreviewLayerPanel from "@/uhm/components/editor/ReplayPreviewLayerPanel"; import ReplayPreviewLayerPanel from "@/uhm/components/editor/ReplayPreviewLayerPanel";
import PublicWikiSidebar from "@/uhm/components/wiki/PublicWikiSidebar"; import PublicWikiSidebar from "@/uhm/components/wiki/PublicWikiSidebar";
import TimelineBar from "@/uhm/components/ui/TimelineBar"; import TimelineBar from "@/uhm/components/ui/TimelineBar";
import { useReplayPreview } from "@/uhm/lib/replay/useReplayPreview";
import RelatedEntityPopup from "./RelatedEntityPopup"; import RelatedEntityPopup from "./RelatedEntityPopup";
import PinnedWikiPopup from "./PinnedWikiPopup"; import PinnedWikiPopup from "./PinnedWikiPopup";
import { fetchWikiById, type Wiki } from "@/uhm/api/wikis"; import { type Wiki } from "@/uhm/api/wikis";
import type { Entity } from "@/uhm/api/entities"; import type { Entity } from "@/uhm/api/entities";
import type { Feature, FeatureCollection } from "@/uhm/types/geo"; import type { FeatureCollection } from "@/uhm/types/geo";
import type { BattleReplay, EntityWikiLinkSnapshot } from "@/uhm/types/projects"; import type { BattleReplay } from "@/uhm/types/projects";
import type { WikiSnapshot } from "@/uhm/types/wiki"; import type { WikiSnapshot } from "@/uhm/types/wiki";
import { type BackgroundLayerVisibility } from "@/uhm/lib/map/styles/backgroundLayers"; import { type BackgroundLayerVisibility } from "@/uhm/lib/map/styles/backgroundLayers";
import { persistBackgroundLayerVisibility } from "@/uhm/lib/editor/background/backgroundVisibilityStorage";
import { EMPTY_FEATURE_COLLECTION } from "@/uhm/lib/map/geo/constants"; import { EMPTY_FEATURE_COLLECTION } from "@/uhm/lib/map/geo/constants";
import {
clampNumber,
isFeatureVisibleAtYear,
} from "@/uhm/lib/editor/editorPageUtils";
import { normalizeFeatureEntityIds } from "@/uhm/lib/editor/snapshot/editorSnapshot"; import { normalizeFeatureEntityIds } from "@/uhm/lib/editor/snapshot/editorSnapshot";
import { normalizeTimelineYearValue } from "@/uhm/lib/utils/timeline";
import { deepClone } from "@/uhm/lib/editor/draft/draftDiff";
import type { EditorMode } from "@/uhm/lib/editor/session/sessionTypes";
type Props = { type Props = {
projectId: string; projectId: string;
@@ -37,32 +28,20 @@ type Props = {
replays: BattleReplay[]; replays: BattleReplay[];
entities: Entity[]; entities: Entity[];
wikis: WikiSnapshot[]; wikis: WikiSnapshot[];
entityWikiLinks: EntityWikiLinkSnapshot[];
backgroundVisibility: BackgroundLayerVisibility; backgroundVisibility: BackgroundLayerVisibility;
onBackgroundVisibilityChange: (vis: BackgroundLayerVisibility) => void; onBackgroundVisibilityChange: (vis: BackgroundLayerVisibility) => void;
geometryVisibility: Record<string, boolean>; geometryVisibility: Record<string, boolean>;
onGeometryVisibilityChange: (vis: Record<string, boolean>) => void; onGeometryVisibilityChange: (vis: Record<string, boolean>) => void;
viewMode: "local" | "global";
onViewModeChange?: (mode: "local" | "global") => void;
globalGeometries?: FeatureCollection;
isGlobalLoading?: boolean;
baseline?: FeatureCollection;
activeReplay?: BattleReplay | null; activeReplay?: BattleReplay | null;
selectedStageId?: number | null;
selectedStepIndex?: number | null;
autoplayMode?: "start" | "selection" | null; autoplayMode?: "start" | "selection" | null;
replayPreview: any; replayPreview: any;
mapHandleRef: React.RefObject<MapHandle | null>;
previewRelations: PreviewRelationIndex; previewRelations: PreviewRelationIndex;
previewActiveEntityId: string | null; previewActiveEntityId: string | null;
setPreviewActiveEntityId: (id: string | null) => void; setPreviewActiveEntityId: (id: string | null) => void;
previewEntityFocusToken: number;
setPreviewEntityFocusToken: React.Dispatch<React.SetStateAction<number>>; setPreviewEntityFocusToken: React.Dispatch<React.SetStateAction<number>>;
previewSidebarWidth: number; previewSidebarWidth: number;
setPreviewSidebarWidth: React.Dispatch<React.SetStateAction<number>>; setPreviewSidebarWidth: React.Dispatch<React.SetStateAction<number>>;
isLargeScreen: boolean;
setIsLargeScreen: (isLarge: boolean) => void;
previewWikiCache: Record<string, Wiki>; previewWikiCache: Record<string, Wiki>;
setPreviewWikiCache: React.Dispatch<React.SetStateAction<Record<string, Wiki>>>; setPreviewWikiCache: React.Dispatch<React.SetStateAction<Record<string, Wiki>>>;
}; };
@@ -83,36 +62,21 @@ const PreviewLayout = forwardRef<any, Props>(({
mode, mode,
onModeChange, onModeChange,
onExitPreview, onExitPreview,
draft,
replays,
entities,
wikis, wikis,
entityWikiLinks,
backgroundVisibility, backgroundVisibility,
onBackgroundVisibilityChange, onBackgroundVisibilityChange,
geometryVisibility, geometryVisibility,
onGeometryVisibilityChange, onGeometryVisibilityChange,
viewMode,
onViewModeChange,
globalGeometries = EMPTY_FEATURE_COLLECTION,
isGlobalLoading = false,
baseline = EMPTY_FEATURE_COLLECTION,
activeReplay, activeReplay,
selectedStageId = null,
selectedStepIndex = null,
autoplayMode = null, autoplayMode = null,
replayPreview, replayPreview,
mapHandleRef,
previewRelations, previewRelations,
previewActiveEntityId, previewActiveEntityId,
setPreviewActiveEntityId, setPreviewActiveEntityId,
previewEntityFocusToken,
setPreviewEntityFocusToken, setPreviewEntityFocusToken,
previewSidebarWidth, previewSidebarWidth,
setPreviewSidebarWidth, setPreviewSidebarWidth,
isLargeScreen,
setIsLargeScreen,
previewWikiCache, previewWikiCache,
setPreviewWikiCache, setPreviewWikiCache,
}: Props, ref) => { }: Props, ref) => {
@@ -167,9 +131,7 @@ const PreviewLayout = forwardRef<any, Props>(({
}, [autoplayMode, isReplayPreviewMode, currentActiveReplay, replayPreview]); }, [autoplayMode, isReplayPreviewMode, currentActiveReplay, replayPreview]);
const { const {
hiddenGeometryIds: replayPreviewHiddenGeometryIds,
timelineYear: replayPreviewTimelineYear, timelineYear: replayPreviewTimelineYear,
timelineFilterEnabled: replayPreviewTimelineFilterEnabled,
resetPreview: resetReplayPreview, resetPreview: resetReplayPreview,
playbackSpeed: replayPreviewPlaybackSpeed, playbackSpeed: replayPreviewPlaybackSpeed,
activeCursor: replayPreviewActiveCursor, activeCursor: replayPreviewActiveCursor,
@@ -181,7 +143,6 @@ const PreviewLayout = forwardRef<any, Props>(({
// Timeline bar parameters // Timeline bar parameters
const activeTimelineYear = isReplayPreviewMode ? replayPreviewTimelineYear : replayPreviewTimelineYear; const activeTimelineYear = isReplayPreviewMode ? replayPreviewTimelineYear : replayPreviewTimelineYear;
const activeTimelineFilterEnabled = isReplayPreviewMode ? replayPreviewTimelineFilterEnabled : true;
// Timeline bar visibility // Timeline bar visibility
const timelineBarVisible = !isReplayPreviewMode || replayPreview.timelineVisible; const timelineBarVisible = !isReplayPreviewMode || replayPreview.timelineVisible;
@@ -302,15 +263,9 @@ const PreviewLayout = forwardRef<any, Props>(({
? previewRelations.entitiesById[replayPreviewActiveEntityId] || null ? previewRelations.entitiesById[replayPreviewActiveEntityId] || null
: null; : null;
const replayPreviewActiveEntityGeometries = replayPreviewActiveEntityId
? previewRelations.entityGeometriesById[replayPreviewActiveEntityId] || EMPTY_FEATURE_COLLECTION
: EMPTY_FEATURE_COLLECTION;
const isReplayPreviewWikiSidebarOpen = mode && (replayPreviewSidebarOpen || isPreviewEntitySidebarOpen); const isReplayPreviewWikiSidebarOpen = mode && (replayPreviewSidebarOpen || isPreviewEntitySidebarOpen);
// Selected feature ids
const [selectedFeatureIds, setSelectedFeatureIds] = useState<(string | number)[]>([]);
// Handle replay preview entity selection // Handle replay preview entity selection
const selectReplayPreviewEntity = useCallback(( const selectReplayPreviewEntity = useCallback((
entityId: string, entityId: string,
@@ -344,9 +299,6 @@ const PreviewLayout = forwardRef<any, Props>(({
if (options?.focusMap === true) { if (options?.focusMap === true) {
setPreviewEntityFocusToken((prev) => (prev ?? 0) + 1); setPreviewEntityFocusToken((prev) => (prev ?? 0) + 1);
} }
if (options?.selectGeometry && options.sourceFeatureId != null) {
setSelectedFeatureIds([options.sourceFeatureId]);
}
if (nextWiki) { if (nextWiki) {
openReplayPreviewWikiPanelById(nextWiki.id); openReplayPreviewWikiPanelById(nextWiki.id);
} }
@@ -354,7 +306,8 @@ const PreviewLayout = forwardRef<any, Props>(({
openReplayPreviewWikiPanelById, openReplayPreviewWikiPanelById,
previewRelations.entitiesById, previewRelations.entitiesById,
previewRelations.entityWikisById, previewRelations.entityWikisById,
setSelectedFeatureIds, setPreviewActiveEntityId,
setPreviewEntityFocusToken,
]); ]);
// Handle close sidebar // Handle close sidebar
@@ -364,8 +317,7 @@ const PreviewLayout = forwardRef<any, Props>(({
setIsPreviewEntitySidebarOpen(false); setIsPreviewEntitySidebarOpen(false);
setPreviewWikiError(null); setPreviewWikiError(null);
setPreviewLinkEntityPopup(null); setPreviewLinkEntityPopup(null);
setSelectedFeatureIds([]); }, [closeReplayPreviewWikiPanel, setPreviewActiveEntityId]);
}, [closeReplayPreviewWikiPanel, setSelectedFeatureIds]);
// Play selected battle replay // Play selected battle replay
const handlePlaySelectedReplay = useCallback((replay: BattleReplay) => { const handlePlaySelectedReplay = useCallback((replay: BattleReplay) => {
@@ -499,94 +451,7 @@ const PreviewLayout = forwardRef<any, Props>(({
wikis, wikis,
]); ]);
// Render Draft geometries builder
const replayPreviewDraft = useMemo(() => {
const sourceDraft = draft;
if (!isReplayPreviewMode || replayPreviewHiddenGeometryIds.length === 0) {
return sourceDraft;
}
const hiddenIds = new Set(replayPreviewHiddenGeometryIds);
return {
...sourceDraft,
features: sourceDraft.features.filter(
(feature) => !hiddenIds.has(String(feature.properties.id))
),
};
}, [isReplayPreviewMode, draft, replayPreviewHiddenGeometryIds]);
const mapRenderDraft = useMemo(() => {
if (isReplayPreviewMode) {
return replayPreviewDraft;
}
const sourceDraft = draft;
if (!activeTimelineFilterEnabled) {
return sourceDraft;
}
return {
...sourceDraft,
features: sourceDraft.features.filter((feature) =>
isFeatureVisibleAtYear(feature, activeTimelineYear)
),
};
}, [
activeTimelineFilterEnabled,
activeTimelineYear,
draft,
isReplayPreviewMode,
replayPreviewDraft,
]);
// Build label context
const labelContextBaseDraft = useMemo(() => {
if (viewMode === "local") {
return draft;
}
const localFeatureIds = new Set<string>();
for (const f of draft.features) {
if (f.properties?.id != null) {
localFeatureIds.add(String(f.properties.id));
}
}
if (baseline && baseline.features) {
for (const f of baseline.features) {
if (f.properties?.id != null) {
localFeatureIds.add(String(f.properties.id));
}
}
}
const mergedFeatures = [...draft.features];
for (const globalFeature of globalGeometries.features) {
const globalId = globalFeature.properties?.id != null ? String(globalFeature.properties.id) : null;
if (globalId === null || !localFeatureIds.has(globalId)) {
mergedFeatures.push(globalFeature);
}
}
return {
...draft,
features: mergedFeatures,
};
}, [viewMode, draft, baseline, globalGeometries.features]);
const mapLabelContextDraft = useMemo(() => {
return buildEntityLabelContextDraft(labelContextBaseDraft, entities);
}, [entities, labelContextBaseDraft]);
// Replay matching the selected feature
const viewerPreviewSelectedReplay = useMemo(() => {
if (isReplayPreviewMode || !selectedFeatureIds.length) return null;
const selectedGeometryId = String(selectedFeatureIds[0] ?? "").trim();
if (!selectedGeometryId.length) return null;
return replays.find(
(r) =>
String(r?.geometry_id || "").trim() === selectedGeometryId &&
hasPlayableReplaySteps(r)
) || null;
}, [isReplayPreviewMode, replays, selectedFeatureIds]);
// Search and focus place // Search and focus place
const handleFocusPresentPlace = useCallback((place: PresentPlaceSelection) => { const handleFocusPresentPlace = useCallback((place: PresentPlaceSelection) => {
@@ -599,7 +464,6 @@ const PreviewLayout = forwardRef<any, Props>(({
const handleFocusHistoricalGeometry = useCallback((payload: HistoricalGeometryFocusPayload) => { const handleFocusHistoricalGeometry = useCallback((payload: HistoricalGeometryFocusPayload) => {
setFocusedPresentPlace(null); setFocusedPresentPlace(null);
setSelectedFeatureIds([payload.geometry.id]);
setPreviewEntityFocusToken((prev) => (prev ?? 0) + 1); setPreviewEntityFocusToken((prev) => (prev ?? 0) + 1);
const linkedEntityIds = previewRelations.geometryEntityIds[String(payload.geometry.id)] || []; const linkedEntityIds = previewRelations.geometryEntityIds[String(payload.geometry.id)] || [];
@@ -610,17 +474,13 @@ const PreviewLayout = forwardRef<any, Props>(({
selectGeometry: false, selectGeometry: false,
}); });
} }
}, [previewRelations.geometryEntityIds, selectReplayPreviewEntity]); }, [previewRelations.geometryEntityIds, selectReplayPreviewEntity, setPreviewEntityFocusToken]);
const effectiveGeometryVisibility = useMemo(() => { const effectiveGeometryVisibility = useMemo(() => {
return geometryVisibility; return geometryVisibility;
}, [geometryVisibility]); }, [geometryVisibility]);
const handleSetMode = useCallback((m: EditorMode) => {
if (m === "preview" || m === "replay_preview") {
onModeChange(m);
}
}, [onModeChange]);
// Popup PinnedWikiPopup rows // Popup PinnedWikiPopup rows
const previewPinnedWikiPopupRows = useMemo(() => { const previewPinnedWikiPopupRows = useMemo(() => {
@@ -810,26 +670,6 @@ export default PreviewLayout;
// Helper functions // Helper functions
// ========================================== // ==========================================
function snapshotWikiToWiki(snapshot: WikiSnapshot, wikiCache: Record<string, Wiki>, projectId: string): Wiki {
if (typeof snapshot.doc === "string") {
return {
id: snapshot.id,
project_id: projectId,
title: snapshot.title,
slug: snapshot.slug ?? null,
content: snapshot.doc || "",
};
}
return wikiCache[snapshot.id] || {
id: snapshot.id,
project_id: projectId,
title: snapshot.title,
slug: snapshot.slug ?? null,
content: "",
};
}
function extractWikiBlockquoteText(content: string | null | undefined): string { function extractWikiBlockquoteText(content: string | null | undefined): string {
if (!content) return ""; if (!content) return "";
@@ -850,22 +690,6 @@ function extractWikiBlockquoteText(content: string | null | undefined): string {
.trim(); .trim();
} }
function pushUniqueString(target: Record<string, string[]>, key: string, value: string) {
if (!target[key]) {
target[key] = [value];
return;
}
if (!target[key].includes(value)) {
target[key].push(value);
}
}
function normalizeRelationArrays(target: Record<string, string[]>) {
for (const key of Object.keys(target)) {
target[key] = Array.from(new Set(target[key]));
}
}
function computeFixedPopupPosition(rect: DOMRect, width: number, height: number) { function computeFixedPopupPosition(rect: DOMRect, width: number, height: number) {
const margin = 12; const margin = 12;
const viewportWidth = typeof window !== "undefined" ? window.innerWidth : 1440; const viewportWidth = typeof window !== "undefined" ? window.innerWidth : 1440;
@@ -880,127 +704,3 @@ function computeFixedPopupPosition(rect: DOMRect, width: number, height: number)
return { top, left }; return { top, left };
} }
function buildPreviewRelationIndex(options: {
draft: FeatureCollection;
entities: Entity[];
wikis: WikiSnapshot[];
entityWikiLinks: EntityWikiLinkSnapshot[];
wikiCache: Record<string, Wiki>;
projectId: string;
}): PreviewRelationIndex {
const next: PreviewRelationIndex = {
entitiesById: {},
entityGeometriesById: {},
entityWikisById: {},
geometryEntityIds: {},
wikiEntityIdsById: {},
wikiEntityIdsBySlug: {},
wikiById: {},
wikiBySlug: {},
};
for (const entity of options.entities || []) {
const id = String(entity?.id || "").trim();
if (!id) continue;
next.entitiesById[id] = entity;
}
const wikiMap = new Map<string, Wiki>();
for (const wikiSnapshot of options.wikis || []) {
if (!wikiSnapshot || wikiSnapshot.operation === "delete") continue;
const wiki = snapshotWikiToWiki(wikiSnapshot, options.wikiCache, options.projectId);
if (!wiki?.id) continue;
next.wikiById[wiki.id] = wiki;
const slug = String(wiki.slug || "").trim();
if (slug) next.wikiBySlug[slug] = wiki;
}
for (const feature of options.draft.features || []) {
const geometryId = String(feature.properties.id);
for (const entityId of normalizeFeatureEntityIds(feature)) {
if (!next.entitiesById[entityId]) {
next.entitiesById[entityId] = { id: entityId, name: entityId };
}
pushUniqueString(next.geometryEntityIds, geometryId, entityId);
if (!next.entityGeometriesById[entityId]) {
next.entityGeometriesById[entityId] = { type: "FeatureCollection", features: [] };
}
if (!next.entityGeometriesById[entityId].features.some((item) => String(item.properties.id) === geometryId)) {
next.entityGeometriesById[entityId].features.push(feature);
}
}
}
for (const link of options.entityWikiLinks || []) {
if (!link || link.operation === "delete") continue;
const entityId = String(link.entity_id || "").trim();
const wikiId = String(link.wiki_id || "").trim();
const entity = next.entitiesById[entityId] || null;
const wiki = next.wikiById[wikiId] || null;
if (!entity || !wiki) continue;
if (!next.entityWikisById[entityId]) next.entityWikisById[entityId] = [];
if (!next.entityWikisById[entityId].some((item) => item.id === wiki.id)) {
next.entityWikisById[entityId].push(wiki);
}
pushUniqueString(next.wikiEntityIdsById, wiki.id, entityId);
const slug = String(wiki.slug || "").trim();
if (slug) pushUniqueString(next.wikiEntityIdsBySlug, slug, entityId);
}
normalizeRelationArrays(next.geometryEntityIds);
normalizeRelationArrays(next.wikiEntityIdsById);
normalizeRelationArrays(next.wikiEntityIdsBySlug);
return next;
}
function hasPlayableReplaySteps(replay: BattleReplay | null | undefined) {
return Boolean(
replay?.detail?.some((stage) => Array.isArray(stage?.steps) && stage.steps.length > 0)
);
}
function buildEntityLabelContextDraft(draft: FeatureCollection, entities: Entity[]): FeatureCollection {
if (!draft.features.length) return draft;
const entityById = new globalThis.Map<string, Entity>();
for (const entity of entities || []) {
const id = String(entity?.id || "").trim();
if (!id) continue;
entityById.set(id, entity);
}
return {
...draft,
features: draft.features.map((feature) => {
const entityIds = normalizeFeatureEntityIds(feature);
if (!entityIds.length) return feature;
const candidates = entityIds.map((id) => {
const entity = entityById.get(id) || null;
const name = String(entity?.name || id).trim();
if (!name) return null;
return {
id,
name,
time_start: normalizeTimelineYearValue(entity?.time_start),
time_end: normalizeTimelineYearValue(entity?.time_end),
};
}).filter((candidate) => candidate !== null);
return {
...feature,
properties: {
...feature.properties,
entity_id: entityIds[0] || null,
entity_ids: entityIds,
entity_name: candidates[0]?.name || null,
entity_names: candidates.map((candidate) => candidate.name),
entity_label_candidates: candidates,
},
};
}),
};
}
@@ -23,7 +23,6 @@ export const POINT_GEOTYPE_ICON_PATHS: Partial<Record<PointGeotypeId, string>> =
port: "/images/mapIcon/point/port.png", port: "/images/mapIcon/point/port.png",
}; };
type PointIconVariant = "default" | "draft";
type PointLayerOptions = { type PointLayerOptions = {
iconScale?: number; iconScale?: number;
@@ -42,8 +41,6 @@ const TYPE_MATCH_EXPR: maplibregl.ExpressionSpecification = ["coalesce", ["get",
const SELECTED_EXPR: maplibregl.ExpressionSpecification = ["boolean", ["feature-state", "selected"], false]; const SELECTED_EXPR: maplibregl.ExpressionSpecification = ["boolean", ["feature-state", "selected"], false];
const ICON_CANVAS_SIZE = 48; const ICON_CANVAS_SIZE = 48;
const DRAFT_FILL = "#ef4444";
const DRAFT_RIM = "#7f1d1d";
const POINT_GEOMETRY_FILTER: maplibregl.ExpressionSpecification = [ const POINT_GEOMETRY_FILTER: maplibregl.ExpressionSpecification = [
"any", "any",
["==", ["geometry-type"], "Point"], ["==", ["geometry-type"], "Point"],
-15
View File
@@ -1,15 +0,0 @@
import type { UIOptionName } from "@/uhm/types/projects";
export const REPLAY_UI_OPTIONS = [
"timeline",
"layer_panel",
"zoom_panel",
"wiki",
"toast",
] as const satisfies UIOptionName[];
export function normalizeReplayUiOption(value: unknown): UIOptionName | null {
return REPLAY_UI_OPTIONS.includes(value as UIOptionName)
? value as UIOptionName
: null;
}
+3 -4
View File
@@ -1,11 +1,7 @@
import type maplibregl from "maplibre-gl"; import type maplibregl from "maplibre-gl";
import type { FeatureCollection } from "@/uhm/types/geo"; import type { FeatureCollection } from "@/uhm/types/geo";
import type { import type {
GeoFunctionName,
MapFunctionName,
NarrativeFunctionName,
ReplayAction, ReplayAction,
UIOptionName,
DialogState, DialogState,
} from "@/uhm/types/projects"; } from "@/uhm/types/projects";
import { mapActions } from "./mapActions"; import { mapActions } from "./mapActions";
@@ -64,6 +60,9 @@ export const dispatchReplayAction = (
case "set_labels_visible": case "set_labels_visible":
mapActions.set_labels_visible(map, asBooleanValue(params[0], true)); mapActions.set_labels_visible(map, asBooleanValue(params[0], true));
return; return;
case "set_timeline_filter":
controllers.setTimelineFilterEnabled(asBooleanValue(params[0], true));
return;
case "fly_to_geometries": case "fly_to_geometries":
mapActions.fly_to_geometries( mapActions.fly_to_geometries(
map, map,