From aa956f153247c21a077a96c220cb8ac6ebeb30d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Ro=C3=9F?= Date: Tue, 19 Nov 2024 22:08:16 +0100 Subject: [PATCH 1/6] feat: update map pin clusters (#3855) Css and JS Logic --- src/pages/Maps/Content/MapView/Sprites.tsx | 29 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/pages/Maps/Content/MapView/Sprites.tsx b/src/pages/Maps/Content/MapView/Sprites.tsx index 0c76f7fa59..532f59685f 100644 --- a/src/pages/Maps/Content/MapView/Sprites.tsx +++ b/src/pages/Maps/Content/MapView/Sprites.tsx @@ -4,6 +4,7 @@ import clusterIcon from 'src/assets/icons/map-cluster.svg' import AwaitingModerationHighlight from 'src/assets/icons/map-unpproved-pin.svg' import { logger } from 'src/logger' import Workspace from 'src/pages/User/workspace/Workspace' +import { useThemeUI } from 'theme-ui' import type { MarkerCluster } from 'leaflet' import type { IMapPin } from 'oa-shared' @@ -16,19 +17,41 @@ import './sprites.css' * such as total pins. Currently none used, but retaining */ export const createClusterIcon = () => { + const { theme } = useThemeUI() as any return (cluster: MarkerCluster) => { const className = ['icon'] let icon: any - if (cluster.getChildCount() > 1) { + let outlineSize: number = 0 + const clusterChildCount: number = cluster.getChildCount() + if (clusterChildCount > 1) { className.push('icon-cluster-many') icon = clusterIcon - } else if (cluster.getChildCount() === 1) { + // Calcute Outline CSS + if (clusterChildCount > 49) { + outlineSize = 24 + } else { + outlineSize = 4 + ((clusterChildCount - 2) / 50) * 20 + } + } else if (clusterChildCount === 1) { icon = cluster[0].pinType.icon } const { fontSize, iconSize, lineHeight } = getClusterSizes(cluster) + // Prepare Outline CSS for groups + const borderRadius = lineHeight / 2 + const themeBaseColorForRgba: RegExpExecArray | null = + /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(theme.colors.accent.base) + return L.divIcon({ - html: `${cluster.getChildCount()}`, + html: `${clusterChildCount}`, className: className.join(' '), iconSize: L.point(iconSize, iconSize, true), }) From 51a8b7f877169adc8a378dfedf898cc1e87f9237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Ro=C3=9F?= Date: Wed, 20 Nov 2024 17:51:46 +0100 Subject: [PATCH 2/6] feat: update map pin clusters (ONEARMY#3855) added Unit Test --- .../Maps/Content/MapView/Sprites.test.tsx | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/pages/Maps/Content/MapView/Sprites.test.tsx diff --git a/src/pages/Maps/Content/MapView/Sprites.test.tsx b/src/pages/Maps/Content/MapView/Sprites.test.tsx new file mode 100644 index 0000000000..58b70d6b38 --- /dev/null +++ b/src/pages/Maps/Content/MapView/Sprites.test.tsx @@ -0,0 +1,84 @@ +import { act, waitFor } from "@testing-library/react"; +import { testingThemeStyles } from "src/test/utils/themeUtils"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { createClusterIcon } from "./Sprites" + +import type { DivIcon, MarkerCluster } from "leaflet"; +import type { MockedObject} from "vitest"; + +const Theme = testingThemeStyles; + +vi.mock('theme-ui', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + __esModule: true, + useThemeUI: () => ({theme: Theme}), + })) + +describe('Sprites', () => { + describe("createClusterIcon", () => { + const mockCluster: unknown = { + getChildCount: vi.fn() + } + beforeEach(() => { + (mockCluster as MockedObject) + .getChildCount.mockReset() + }); + it('calculates only a valid colour', async () => { + (mockCluster as MockedObject) + .getChildCount.mockReturnValue(2); + let resultIcon: DivIcon | null = null + act(() => { + resultIcon = createClusterIcon()(mockCluster as MarkerCluster); + }); + await waitFor(() => { + expect((mockCluster as MockedObject) + .getChildCount).toHaveBeenCalledTimes(2); + expect(resultIcon).not.toBeNull(); + expect(resultIcon!.options.html).not.toContain("NaN"); + }); + }); + it('outline is 4px when at least 2', async () => { + (mockCluster as MockedObject) + .getChildCount.mockReturnValue(2); + let resultIcon: DivIcon | null = null + act(() => { + resultIcon = createClusterIcon()(mockCluster as MarkerCluster); + }); + await waitFor(() => { + expect((mockCluster as MockedObject) + .getChildCount).toHaveBeenCalledTimes(2); + expect(resultIcon).not.toBeNull(); + expect(resultIcon!.options.html).toContain("outline: 4px"); + }); + }); + it('outline is 24px when 50', async () => { + (mockCluster as MockedObject) + .getChildCount.mockReturnValue(50); + let resultIcon: DivIcon | null = null + act(() => { + resultIcon = createClusterIcon()(mockCluster as MarkerCluster); + }); + await waitFor(() => { + expect((mockCluster as MockedObject) + .getChildCount).toHaveBeenCalledTimes(2); + expect(resultIcon).not.toBeNull(); + expect(resultIcon!.options.html).toContain("outline: 24px"); + }); + }); + it('outline doesn\'t go past 24px', async () => { + (mockCluster as MockedObject) + .getChildCount.mockReturnValue(Math.floor(Math.random() * 30) + 50); + let resultIcon: DivIcon | null = null + act(() => { + resultIcon = createClusterIcon()(mockCluster as MarkerCluster); + }); + await waitFor(() => { + expect((mockCluster as MockedObject) + .getChildCount).toHaveBeenCalledTimes(2); + expect(resultIcon).not.toBeNull(); + expect(resultIcon!.options.html).toContain("outline: 24px"); + }); + }); + }); +}); \ No newline at end of file From 6b32659e863921387628be16da00bdb87eeecf87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Ro=C3=9F?= Date: Thu, 21 Nov 2024 19:35:06 +0100 Subject: [PATCH 3/6] feat: update map pin clusters (ONEARMY#3855) removed Unit Test --- .../Maps/Content/MapView/Sprites.test.tsx | 84 ------------------- 1 file changed, 84 deletions(-) delete mode 100644 src/pages/Maps/Content/MapView/Sprites.test.tsx diff --git a/src/pages/Maps/Content/MapView/Sprites.test.tsx b/src/pages/Maps/Content/MapView/Sprites.test.tsx deleted file mode 100644 index 58b70d6b38..0000000000 --- a/src/pages/Maps/Content/MapView/Sprites.test.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { act, waitFor } from "@testing-library/react"; -import { testingThemeStyles } from "src/test/utils/themeUtils"; -import { beforeEach, describe, expect, it, vi } from "vitest"; - -import { createClusterIcon } from "./Sprites" - -import type { DivIcon, MarkerCluster } from "leaflet"; -import type { MockedObject} from "vitest"; - -const Theme = testingThemeStyles; - -vi.mock('theme-ui', () => ({ - // eslint-disable-next-line @typescript-eslint/naming-convention - __esModule: true, - useThemeUI: () => ({theme: Theme}), - })) - -describe('Sprites', () => { - describe("createClusterIcon", () => { - const mockCluster: unknown = { - getChildCount: vi.fn() - } - beforeEach(() => { - (mockCluster as MockedObject) - .getChildCount.mockReset() - }); - it('calculates only a valid colour', async () => { - (mockCluster as MockedObject) - .getChildCount.mockReturnValue(2); - let resultIcon: DivIcon | null = null - act(() => { - resultIcon = createClusterIcon()(mockCluster as MarkerCluster); - }); - await waitFor(() => { - expect((mockCluster as MockedObject) - .getChildCount).toHaveBeenCalledTimes(2); - expect(resultIcon).not.toBeNull(); - expect(resultIcon!.options.html).not.toContain("NaN"); - }); - }); - it('outline is 4px when at least 2', async () => { - (mockCluster as MockedObject) - .getChildCount.mockReturnValue(2); - let resultIcon: DivIcon | null = null - act(() => { - resultIcon = createClusterIcon()(mockCluster as MarkerCluster); - }); - await waitFor(() => { - expect((mockCluster as MockedObject) - .getChildCount).toHaveBeenCalledTimes(2); - expect(resultIcon).not.toBeNull(); - expect(resultIcon!.options.html).toContain("outline: 4px"); - }); - }); - it('outline is 24px when 50', async () => { - (mockCluster as MockedObject) - .getChildCount.mockReturnValue(50); - let resultIcon: DivIcon | null = null - act(() => { - resultIcon = createClusterIcon()(mockCluster as MarkerCluster); - }); - await waitFor(() => { - expect((mockCluster as MockedObject) - .getChildCount).toHaveBeenCalledTimes(2); - expect(resultIcon).not.toBeNull(); - expect(resultIcon!.options.html).toContain("outline: 24px"); - }); - }); - it('outline doesn\'t go past 24px', async () => { - (mockCluster as MockedObject) - .getChildCount.mockReturnValue(Math.floor(Math.random() * 30) + 50); - let resultIcon: DivIcon | null = null - act(() => { - resultIcon = createClusterIcon()(mockCluster as MarkerCluster); - }); - await waitFor(() => { - expect((mockCluster as MockedObject) - .getChildCount).toHaveBeenCalledTimes(2); - expect(resultIcon).not.toBeNull(); - expect(resultIcon!.options.html).toContain("outline: 24px"); - }); - }); - }); -}); \ No newline at end of file From 7150fa3f168687038929a71b054fc8867772749e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Ro=C3=9F?= Date: Thu, 21 Nov 2024 21:08:33 +0100 Subject: [PATCH 4/6] feat: update map pin clusters (ONEARMY#3855) pin clusters now change colour based on theme --- src/pages/Maps/Content/MapView/Sprites.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pages/Maps/Content/MapView/Sprites.tsx b/src/pages/Maps/Content/MapView/Sprites.tsx index 532f59685f..74da78e469 100644 --- a/src/pages/Maps/Content/MapView/Sprites.tsx +++ b/src/pages/Maps/Content/MapView/Sprites.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react' import L from 'leaflet' import { IModerationStatus } from 'oa-shared' import clusterIcon from 'src/assets/icons/map-cluster.svg' @@ -18,6 +19,15 @@ import './sprites.css' */ export const createClusterIcon = () => { const { theme } = useThemeUI() as any + const path = clusterIcon + let iconAsString:string = "" + useEffect(() => { + fetch(path) + .then(response => response.text()) + .then(data => { + iconAsString = data.replaceAll(/#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})/g, theme.colors.accent.base) + }) + }, [path, theme]); return (cluster: MarkerCluster) => { const className = ['icon'] let icon: any @@ -25,7 +35,7 @@ export const createClusterIcon = () => { const clusterChildCount: number = cluster.getChildCount() if (clusterChildCount > 1) { className.push('icon-cluster-many') - icon = clusterIcon + icon = iconAsString // Calcute Outline CSS if (clusterChildCount > 49) { outlineSize = 24 @@ -33,7 +43,7 @@ export const createClusterIcon = () => { outlineSize = 4 + ((clusterChildCount - 2) / 50) * 20 } } else if (clusterChildCount === 1) { - icon = cluster[0].pinType.icon + icon = ` { fetch(path) - .then(response => response.text()) - .then(data => { - iconAsString = data.replaceAll(/#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})/g, theme.colors.accent.base) + .then((response) => response.text()) + .then((data) => { + iconAsString = data.replaceAll( + /#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})/g, + theme.colors.accent.base, + ) }) - }, [path, theme]); + }, [path, theme]) return (cluster: MarkerCluster) => { const className = ['icon'] let icon: any From 8c677a524dd00c7f682c913b7887cbf53329204f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Ro=C3=9F?= Date: Thu, 21 Nov 2024 21:30:56 +0100 Subject: [PATCH 6/6] feat: update map pin clusters (ONEARMY#3855) fixed tests --- src/pages/Maps/Content/MapView/Sprites.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Maps/Content/MapView/Sprites.tsx b/src/pages/Maps/Content/MapView/Sprites.tsx index a3a4c2c733..8af613ee95 100644 --- a/src/pages/Maps/Content/MapView/Sprites.tsx +++ b/src/pages/Maps/Content/MapView/Sprites.tsx @@ -30,6 +30,7 @@ export const createClusterIcon = () => { theme.colors.accent.base, ) }) + .catch((fetchError) => console.error(fetchError)) }, [path, theme]) return (cluster: MarkerCluster) => { const className = ['icon']