diff --git a/e2e/mocked/images.spec.ts b/e2e/mocked/images.spec.ts index 5180d18db..8db9710ff 100644 --- a/e2e/mocked/images.spec.ts +++ b/e2e/mocked/images.spec.ts @@ -111,6 +111,8 @@ test('user can change the false colour parameters of an image', async ({ const imgAltText = title.split(' - ')[1]; const image = await popup.getByAltText(imgAltText); + // assert src has loaded before storing the old image src + await expect(image).toHaveAttribute('src'); const oldImageSrc = await image.getAttribute('src'); const colourbar = await popup.getByAltText('Colour bar'); @@ -142,7 +144,7 @@ test('user can change the false colour parameters of an image', async ({ }, }); - expect(await slider.nth(0).getAttribute('value')).toBe(`${0.4 * 255}`); + await expect(slider.nth(0)).toHaveValue(`${0.4 * 255}`); const ulSliderThumb = await popup .locator('.MuiSlider-thumb', { @@ -157,7 +159,7 @@ test('user can change the false colour parameters of an image', async ({ }, }); - expect(await slider.nth(1).getAttribute('value')).toBe(`${0.8 * 255}`); + await expect(slider.nth(1)).toHaveValue(`${0.8 * 255}`); // blur to avoid focus tooltip appearing in snapshot await slider.nth(0).blur(); @@ -193,20 +195,30 @@ test('user can change the false colour to use reverse', async ({ page }) => { const imgAltText = title.split(' - ')[1]; const image = await popup.getByAltText(imgAltText); - const oldImageSrc = await image.getAttribute('src'); + // assert src has loaded before storing the old image src + await expect(image).toHaveAttribute('src'); + let oldImageSrc = await image.getAttribute('src'); const colourbar = await popup.getByAltText('Colour bar'); await popup.getByLabel('Colour Map').click(); await popup.getByRole('option', { name: 'cividis' }).click(); - expect( - await popup.getByRole('checkbox', { name: 'Reverse Colour' }) + // wait for new image to have loaded + await expect + .poll(async () => await image.getAttribute('src')) + .not.toBe(oldImageSrc); + await image.click(); + oldImageSrc = await image.getAttribute('src'); + + await expect( + popup.getByRole('checkbox', { name: 'Reverse Colour' }) ).not.toBeChecked(); await popup.getByRole('checkbox', { name: 'Reverse Colour' }).click(); - expect( - await popup.getByRole('checkbox', { name: 'Reverse Colour' }) + await expect( + popup.getByRole('checkbox', { name: 'Reverse Colour' }) ).toBeChecked(); + // wait for new image to have loaded await expect .poll(async () => await image.getAttribute('src')) @@ -239,6 +251,8 @@ test('user can change the false colour to colourmap in extended list', async ({ const imgAltText = title.split(' - ')[1]; const image = await popup.getByAltText(imgAltText); + // assert src has loaded before storing the old image src + await expect(image).toHaveAttribute('src'); const oldImageSrc = await image.getAttribute('src'); await popup @@ -281,14 +295,16 @@ test('user can disable false colour', async ({ page }) => { const imgAltText = title.split(' - ')[1]; const image = await popup.getByAltText(imgAltText); + // assert src has loaded before storing the old image src + await expect(image).toHaveAttribute('src'); const oldImageSrc = await image.getAttribute('src'); - expect( - await popup.getByRole('checkbox', { name: 'False colour' }) + await expect( + popup.getByRole('checkbox', { name: 'False colour' }) ).toBeChecked(); await popup.getByRole('checkbox', { name: 'False colour' }).click(); - expect( - await popup.getByRole('checkbox', { name: 'False colour' }) + await expect( + popup.getByRole('checkbox', { name: 'False colour' }) ).not.toBeChecked(); // wait for new image to have loaded @@ -378,9 +394,12 @@ test('user can change image via clicking on a thumbnail', async ({ page }) => { const canvas = await popup.getByTestId('overlay'); - const oldImageSrc = await popup - .getByAltText((await popup.title()).split(' - ')[1]) - .getAttribute('src'); + const oldImage = await popup.getByAltText( + (await popup.title()).split(' - ')[1] + ); + // assert src has loaded before storing the old image src + await expect(oldImage).toHaveAttribute('src'); + const oldImageSrc = await oldImage.getAttribute('src'); await popup .getByAltText('Channel_BCDEF image', { exact: false }) @@ -449,3 +468,297 @@ test('user can set their default colourmap', async ({ page }) => { }) ).toMatchSnapshot(); }); + +test('user can use crosshairs mode and view intensity graphs', async ({ + page, + browserName, +}) => { + // open up popup + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.getByAltText('Channel_BCDEF image', { exact: false }).first().click(), + ]); + + const title = await popup.title(); + const imgAltText = title.split(' - ')[1]; + + const image = await popup.getByAltText(imgAltText); + // use image parent div as this is what crops the image to the correct size + const imageDiv = await popup + .locator('div', { + has: image, + }) + // last is to get the most specific div i.e. direct parent + .last(); + + // get into cross hairs mode + await expect( + popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }) + ).not.toBeChecked(); + await popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }).click(); + await expect( + popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }) + ).toBeChecked(); + + const charts = await popup.locator('.chartjs-chart'); + await expect(charts).toHaveCount(2); + await expect(charts.first()).toBeVisible(); + await expect(charts.last()).toBeVisible(); + + // see msw mock imageCrosshair.json + const centroidPosition = [226, 187]; + const FWHMs = [61, 56]; + await expect( + popup.getByText( + `Position: (${centroidPosition[0]}, ${centroidPosition[1]})` + ) + ).toBeVisible(); + await expect(popup.getByText(`X FWHM: ${FWHMs[0]}`)).toBeVisible(); + await expect(popup.getByText(`Y FWHM: ${FWHMs[1]}`)).toBeVisible(); + + // expect crosshairs to be drawn on image at the centroid & intensity plots to be drawn & positioned correctly + expect( + await popup.getByTestId('image-panel').screenshot({ + type: 'png', + style: + // hide image controls panel & top buttons from the screenshot as it's not important + '[data-testid="image-controls-panel"], [aria-label="image actions"] { display: none !important; }', + }) + ).toMatchSnapshot({ maxDiffPixels: 150 }); + + // check that clicking the image changes the crosshairs position & causes a data fetch + // for some reason playwright has an off by 1 error in the y-pos in chrome, it works fine when testing manually + // i.e. clicking top left-most pixel results in (0,0) + await image.click({ + position: { x: 100, y: browserName === 'chromium' ? 301 : 300 }, + }); + + await expect(popup.getByText('Position: (100, 300)')).toBeVisible(); + + expect( + await popup.getByTestId('image-panel').screenshot({ + type: 'png', + style: + // hide image controls panel & top buttons from the screenshot as it's not important + '[data-testid="image-controls-panel"], [aria-label="image actions"] { display: none !important; }', + }) + ).toMatchSnapshot({ maxDiffPixels: 150 }); + + // check reset view goes back to the centroid + await popup.locator('text=Reset View').click(); + + await expect( + popup.getByText( + `Position: (${centroidPosition[0]}, ${centroidPosition[1]})` + ) + ).toBeVisible(); + + expect( + await popup.getByTestId('image-panel').screenshot({ + type: 'png', + style: + // hide image controls panel & top buttons from the screenshot as it's not important + '[data-testid="image-controls-panel"], [aria-label="image actions"] { display: none !important; }', + }) + ).toMatchSnapshot({ maxDiffPixels: 150 }); + + // can switch out of crosshairs mode and crosshair disappears + await popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }).click(); + await expect( + popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }) + ).not.toBeChecked(); + + expect( + await imageDiv.screenshot({ + type: 'png', + }) + ).toMatchSnapshot({ maxDiffPixels: 150 }); + + await expect(charts.first()).not.toBeVisible(); + await expect(charts.last()).not.toBeVisible(); +}); + +test('user can switch images via thumbnails whilst in crosshairs mode', async ({ + page, + browserName, +}) => { + // open up popup + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.getByAltText('Channel_BCDEF image', { exact: false }).first().click(), + ]); + + const title = await popup.title(); + const imgAltText = title.split(' - ')[1]; + + const oldImage = await popup.getByAltText(imgAltText); + + // get into cross hairs mode + await expect( + popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }) + ).not.toBeChecked(); + await popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }).click(); + await expect( + popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }) + ).toBeChecked(); + + // expect intensity plots to be drawn + const charts = await popup.locator('.chartjs-chart'); + await expect(charts).toHaveCount(2); + await expect(charts.first()).toBeVisible(); + await expect(charts.last()).toBeVisible(); + + // expect crosshairs to be drawn on image at the centroid + + let centroidPosition = [226, 187]; + await expect( + popup.getByText( + `Position: (${centroidPosition[0]}, ${centroidPosition[1]})` + ) + ).toBeVisible(); + + expect( + await popup.getByTestId('image-panel').screenshot({ + type: 'png', + style: + // hide image controls panel & top buttons from the screenshot as it's not important + '[data-testid="image-controls-panel"], [aria-label="image actions"] { display: none !important; }', + }) + ).toMatchSnapshot({ maxDiffPixels: 150 }); + + // click to move the crosshair so we check when switching images it resets to the new image's centroid + // for some reason playwright has an off by 1 error in the y-pos in chrome, it works fine when testing manually + // i.e. clicking top left-most pixel results in (0,0) + await oldImage.click({ + position: { x: 200, y: browserName === 'chromium' ? 201 : 200 }, + }); + await expect(popup.getByText('Position: (200, 200)')).toBeVisible(); + + await page.evaluate(async () => { + // from: https://stackoverflow.com/a/49434653 - generate "random" bell curve + function create_intensity_plot(min: number, max: number) { + // from https://stackoverflow.com/a/19303725 - basic seeded "random" number generator + let seed = 1; + function random() { + const x = Math.sin(seed++) * 10000; + return x - Math.floor(x); + } + + const n = 10000; + const step = 1; + const data: Record = {}; + + const randn_bm = (min, max, skew) => { + let u = 0, + v = 0; + while (u === 0) u = random(); //Converting [0,1) to (0,1) + while (v === 0) v = random(); + let num = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v); + + num = num / 10.0 + 0.5; // Translate to 0 -> 1 + if (num > 1 || num < 0) num = randn_bm(min, max, skew); // resample between 0 and 1 if out of range + num = Math.pow(num, skew); // Skew + num *= max - min; // Stretch to fill range + num += min; // offset to min + return num; + }; + + const round_to_precision = (x, precision) => { + const y = +x + (precision === undefined ? 0.5 : precision / 2); + return y - (y % (precision === undefined ? 1 : +precision)); + }; + + // Seed data with a bunch of 0s + for (let j = min; j < max; j += step) { + data[j] = 0; + } + + // Create n samples between min and max + for (let i = 0; i < n; i += step) { + const rand_num = randn_bm(min, max, 1); + const rounded = round_to_precision(rand_num, step); + data[rounded] += 1; + } + + // Count number of samples at each increment + let points: { x: number; y: number }[] = []; + for (const [key, val] of Object.entries(data)) { + points.push({ + x: parseFloat(key), + y: val / n <= 20 / n ? random() * (1 / n) * 20 : val / n, // make the tail a bit "wiggly" + }); + } + + // Sort + points = points.sort(function (a, b) { + if (a.x < b.x) return -1; + if (a.x > b.x) return 1; + return 0; + }); + + const unnormalised_y = points.map((v) => v.y); + + const y_min = Math.min(...unnormalised_y); + const y_max = Math.max(...unnormalised_y); + + const normalised_y = unnormalised_y.map((n) => + Math.round(((n - y_min) / (y_max - y_min)) * 255) + ); + + const intensity_data: { x: number[]; y: number[] } = { + x: points.map((v) => v.x), + y: normalised_y, + }; + + return intensity_data; + } + + const { msw } = window; + + msw.worker.use( + msw.http.get('/images/:recordId/:channelName/crosshair', async () => { + const responseJson = { + row: { + position: 250, + intensity: create_intensity_plot(0, 656), // 656 = height of image + fwhm: 79, + }, + column: { + position: 320, + intensity: create_intensity_plot(0, 494), // 494 = width of image + fwhm: 22, + }, + }; + return msw.HttpResponse.json(responseJson, { status: 200 }); + }) + ); + }); + + await popup + .getByAltText('Channel_BCDEF image', { exact: false }) + .last() + .click(); + + centroidPosition = [320, 250]; + const FWHMs = [22, 79]; + await expect( + popup.getByText( + `Position: (${centroidPosition[0]}, ${centroidPosition[1]})` + ) + ).toBeVisible(); + await expect(popup.getByText(`X FWHM: ${FWHMs[0]}`)).toBeVisible(); + await expect(popup.getByText(`Y FWHM: ${FWHMs[1]}`)).toBeVisible(); + + await expect(charts.first()).toBeVisible(); + await expect(charts.last()).toBeVisible(); + + // check that crosshair is repositioned and new intensity plots load & are positioned correctly + expect( + await popup.getByTestId('image-panel').screenshot({ + type: 'png', + style: + // hide image controls panel & top buttons from the screenshot as it's not important + '[data-testid="image-controls-panel"], [aria-label="image actions"] { display: none !important; }', + }) + ).toMatchSnapshot({ maxDiffPixels: 150 }); +}); diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-1-chromium-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-1-chromium-linux.png new file mode 100644 index 000000000..f7a7964f6 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-1-chromium-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-1-firefox-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-1-firefox-linux.png new file mode 100644 index 000000000..d261bf607 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-1-firefox-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-1-webkit-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-1-webkit-linux.png new file mode 100644 index 000000000..7d2bae7a8 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-1-webkit-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-2-chromium-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-2-chromium-linux.png new file mode 100644 index 000000000..4e3b0ffaa Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-2-chromium-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-2-firefox-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-2-firefox-linux.png new file mode 100644 index 000000000..31fbe9239 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-2-firefox-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-2-webkit-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-2-webkit-linux.png new file mode 100644 index 000000000..7b8db3de8 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-switch-images-via-thumbnails-whilst-in-crosshairs-mode-2-webkit-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-chromium-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-chromium-linux.png new file mode 100644 index 000000000..f7a7964f6 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-chromium-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-firefox-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-firefox-linux.png new file mode 100644 index 000000000..d261bf607 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-firefox-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-webkit-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-webkit-linux.png new file mode 100644 index 000000000..7d2bae7a8 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-webkit-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-chromium-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-chromium-linux.png new file mode 100644 index 000000000..e8701df50 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-chromium-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-firefox-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-firefox-linux.png new file mode 100644 index 000000000..ad4d916f0 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-firefox-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-webkit-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-webkit-linux.png new file mode 100644 index 000000000..64b032311 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-webkit-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-3-chromium-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-3-chromium-linux.png new file mode 100644 index 000000000..c43e59f0f Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-3-chromium-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-3-firefox-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-3-firefox-linux.png new file mode 100644 index 000000000..a37105d14 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-3-firefox-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-3-webkit-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-3-webkit-linux.png new file mode 100644 index 000000000..c1dea8a31 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-3-webkit-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-4-chromium-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-4-chromium-linux.png new file mode 100644 index 000000000..4cfb3c5db Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-4-chromium-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-4-firefox-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-4-firefox-linux.png new file mode 100644 index 000000000..5cedc84d9 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-4-firefox-linux.png differ diff --git a/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-4-webkit-linux.png b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-4-webkit-linux.png new file mode 100644 index 000000000..0f0dc3998 Binary files /dev/null and b/e2e/mocked/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-4-webkit-linux.png differ diff --git a/e2e/mocked/plotting.spec.ts b/e2e/mocked/plotting.spec.ts index ee6b2551e..d25425ca2 100644 --- a/e2e/mocked/plotting.spec.ts +++ b/e2e/mocked/plotting.spec.ts @@ -34,7 +34,7 @@ test('plots a time vs shotnum graph and change the plot colour', async ({ // wait for open settings button to be visible i.e. menu is fully closed await popup.locator('[aria-label="open settings"]').click({ trial: true }); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); expect( await chart.screenshot({ type: 'png', @@ -80,7 +80,7 @@ test('plots a shotnum vs channel graph with logarithmic scales', async ({ // wait for open settings button to be visible i.e. menu is fully closed await popup.locator('[aria-label="open settings"]').click({ trial: true }); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); expect( await chart.screenshot({ type: 'png', @@ -108,11 +108,11 @@ test('user can zoom and pan the graph', async ({ page }) => { await popup.locator('[aria-label="close settings"]').click(); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); await chart.click(); // test drag to zoom - await popup.dragAndDrop('#my-chart', '#my-chart', { + await popup.dragAndDrop('.chartjs-chart', '.chartjs-chart', { sourcePosition: { x: 250, y: 120, @@ -130,7 +130,7 @@ test('user can zoom and pan the graph', async ({ page }) => { await popup.mouse.wheel(-10, 0); await popup.keyboard.down('Shift'); - await popup.dragAndDrop('#my-chart', '#my-chart', { + await popup.dragAndDrop('.chartjs-chart', '.chartjs-chart', { sourcePosition: { x: 150, y: 150, @@ -221,7 +221,7 @@ test('plots multiple channels on the y axis', async ({ page }) => { await popup.locator('[aria-label="close settings"]').click(); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); // need this to wait for canvas animations to execute await popup.waitForTimeout(1000); @@ -268,7 +268,7 @@ test('user can hide gridlines and axes labels', async ({ page }) => { // need this to wait for canvas animations to execute await popup.waitForTimeout(1000); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); expect( await chart.screenshot({ @@ -314,7 +314,7 @@ test('user can add from and to dates to timestamp on x-axis', async ({ // wait for open settings button to be visible i.e. menu is fully closed await popup.locator('[aria-label="open settings"]').click({ trial: true }); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); expect( await chart.screenshot({ @@ -363,7 +363,7 @@ test('user can add min and max limits to x- and y-axis', async ({ page }) => { // wait for open settings button to be visible i.e. menu is fully closed await popup.locator('[aria-label="open settings"]').click({ trial: true }); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); expect( await chart.screenshot({ type: 'png', @@ -412,7 +412,7 @@ test('user can change line style of plotted channels', async ({ page }) => { await popup.locator('[aria-label="close settings"]').click(); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); // need this to wait for canvas animations to execute await popup.waitForTimeout(1000); @@ -470,7 +470,7 @@ test('user can change the marker style and size of plotted channels', async ({ await popup.locator('[aria-label="close settings"]').click(); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); // need this to wait for canvas animations to execute await popup.waitForTimeout(1000); @@ -526,7 +526,7 @@ test('changes to and from dateTimes to use 0 seconds and 59 seconds respectively // wait for open settings button to be visible i.e. menu is fully closed await popup.locator('[aria-label="open settings"]').click({ trial: true }); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); expect( await chart.screenshot({ type: 'png', @@ -571,7 +571,7 @@ test('user can change the line width of plotted channels', async ({ page }) => { await popup.locator('[aria-label="close settings"]').click(); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); // need this to wait for canvas animations to execute await popup.waitForTimeout(1000); @@ -655,7 +655,7 @@ test('user can plot channels on the right y axis', async ({ page }) => { await popup.locator('[aria-label="close settings"]').click(); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); // need this to wait for canvas animations to execute await popup.waitForTimeout(1000); @@ -685,7 +685,7 @@ test('user can customize left y axis label', async ({ page }) => { await popup.getByRole('option', { name: 'Shot Number', exact: true }).click(); await popup.getByRole('textbox', { name: 'Label' }).type('left y axis'); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); expect( await chart.screenshot({ @@ -713,7 +713,7 @@ test('user can customize right y axis label', async ({ page }) => { await popup.getByRole('option', { name: 'Shot Number', exact: true }).click(); await popup.getByRole('textbox', { name: 'Label' }).type('right y axis'); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); expect( await chart.screenshot({ @@ -749,7 +749,7 @@ test('user can customize both left and right y axis labels', async ({ .click(); await popup.getByRole('textbox', { name: 'Label' }).type('right y axis'); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); expect( await chart.screenshot({ diff --git a/e2e/mocked/traces.spec.ts b/e2e/mocked/traces.spec.ts index ee15a9c06..d729a4444 100644 --- a/e2e/mocked/traces.spec.ts +++ b/e2e/mocked/traces.spec.ts @@ -30,7 +30,7 @@ test('user can show points for the trace', async ({ page }) => { await popup.getByRole('button', { name: 'Show points' }).click(); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); // need this to wait for canvas animations to execute await popup.waitForTimeout(1000); @@ -54,11 +54,11 @@ test('user can zoom and pan the trace', async ({ page }) => { .click(), ]); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); await chart.click(); // test drag to zoom - await popup.dragAndDrop('#my-chart', '#my-chart', { + await popup.dragAndDrop('.chartjs-chart', '.chartjs-chart', { sourcePosition: { x: 250, y: 180, @@ -76,7 +76,7 @@ test('user can zoom and pan the trace', async ({ page }) => { await popup.mouse.wheel(-10, 0); await popup.keyboard.down('Shift'); - await popup.dragAndDrop('#my-chart', '#my-chart', { + await popup.dragAndDrop('.chartjs-chart', '.chartjs-chart', { sourcePosition: { x: 150, y: 150, @@ -135,7 +135,7 @@ test('user can change trace via clicking on a thumbnail', async ({ page }) => { .click(), ]); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); // wait for first chart to load before loading new chart await chart.click(); diff --git a/e2e/real/functions.spec.ts b/e2e/real/functions.spec.ts index a25084320..f2ebd6885 100644 --- a/e2e/real/functions.spec.ts +++ b/e2e/real/functions.spec.ts @@ -51,7 +51,7 @@ test('scalar functions can be plotted', async ({ page }) => { // wait for open settings button to be visible i.e. menu is fully closed await popup.locator('[aria-label="open settings"]').click({ trial: true }); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); expect( await chart.screenshot({ type: 'png', diff --git a/e2e/real/images.spec.ts b/e2e/real/images.spec.ts index c0c6cc1e7..622a6b280 100644 --- a/e2e/real/images.spec.ts +++ b/e2e/real/images.spec.ts @@ -11,6 +11,9 @@ test.beforeEach(async ({ page }) => { // add trace channel to the table so we can click on a trace await page.getByRole('button', { name: 'Data channels' }).click(); + // check that channels have loaded before searching for our channels to add + await expect(page.getByRole('button', { name: 'system' })).toBeVisible(); + await page .getByRole('combobox', { name: 'Search data channels' }) .fill('PA1-CAM'); @@ -18,7 +21,17 @@ test.beforeEach(async ({ page }) => { await page.keyboard.press('ArrowDown'); await page.keyboard.press('Enter'); - for (const row of await page.getByRole('checkbox').all()) await row.check(); + await page.getByRole('button', { name: 'Add this channel' }).click(); + + await page.getByRole('combobox', { name: 'Search data channels' }).fill(''); + await page + .getByRole('combobox', { name: 'Search data channels' }) + .fill('CAM-2'); + + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('Enter'); + + await page.getByRole('button', { name: 'Add this channel' }).click(); await page.getByRole('button', { name: 'Add Channels' }).click(); }); @@ -52,6 +65,8 @@ test('user can change the false colour parameters of an image', async ({ const imgAltText = title.split(' - ')[1]; const image = await popup.getByAltText(imgAltText); + // assert src has loaded before storing the old image src + await expect(image).toHaveAttribute('src'); const oldImageSrc = await image.getAttribute('src'); const colourbar = await popup.getByAltText('Colour bar'); @@ -59,12 +74,12 @@ test('user can change the false colour parameters of an image', async ({ await popup.getByRole('option', { name: 'cividis' }).click(); - expect( - await popup.getByRole('checkbox', { name: 'Reverse Colour' }) + await expect( + popup.getByRole('checkbox', { name: 'Reverse Colour' }) ).not.toBeChecked(); await popup.getByRole('checkbox', { name: 'Reverse Colour' }).click(); - expect( - await popup.getByRole('checkbox', { name: 'Reverse Colour' }) + await expect( + popup.getByRole('checkbox', { name: 'Reverse Colour' }) ).toBeChecked(); const slider = await popup.getByRole('slider', { @@ -91,7 +106,7 @@ test('user can change the false colour parameters of an image', async ({ }, }); - expect(await slider.nth(0).getAttribute('value')).toBe(`${0.4 * 255}`); + await expect(slider.nth(0)).toHaveValue(`${0.4 * 255}`); const ulSliderThumb = await popup .locator('.MuiSlider-thumb', { @@ -106,7 +121,7 @@ test('user can change the false colour parameters of an image', async ({ }, }); - expect(await slider.nth(1).getAttribute('value')).toBe(`${0.8 * 255}`); + await expect(slider.nth(1)).toHaveValue(`${0.8 * 255}`); // blur to avoid focus tooltip appearing in snapshot await slider.nth(0).blur(); @@ -145,14 +160,16 @@ test('user can disable false colour', async ({ page }) => { const imgAltText = title.split(' - ')[1]; const image = await popup.getByAltText(imgAltText); + // assert src has loaded before storing the old image src + await expect(image).toHaveAttribute('src'); const oldImageSrc = await image.getAttribute('src'); - expect( - await popup.getByRole('checkbox', { name: 'False colour' }) + await expect( + popup.getByRole('checkbox', { name: 'False colour' }) ).toBeChecked(); await popup.getByRole('checkbox', { name: 'False colour' }).click(); - expect( - await popup.getByRole('checkbox', { name: 'False colour' }) + await expect( + popup.getByRole('checkbox', { name: 'False colour' }) ).not.toBeChecked(); // wait for new image to have loaded @@ -181,9 +198,12 @@ test('user can change image via clicking on a thumbnail', async ({ page }) => { const canvas = await popup.getByTestId('overlay'); - const oldImageSrc = await popup - .getByAltText((await popup.title()).split(' - ')[1]) - .getAttribute('src'); + const oldImage = await popup.getByAltText( + (await popup.title()).split(' - ')[1] + ); + // assert src has loaded before storing the old image src + await expect(oldImage).toHaveAttribute('src'); + const oldImageSrc = await oldImage.getAttribute('src'); await popup .getByAltText('PM-201-PA1-CAM-2 image', { exact: false }) @@ -283,6 +303,79 @@ test('user can set their default colourmap', async ({ page }) => { ).toMatchSnapshot(); }); +test('user can use crosshairs mode and view intensity graphs', async ({ + page, +}) => { + // open up popup + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page + .getByAltText('D100 front-end FF image', { exact: false }) + .first() + .click(), + ]); + + const title = await popup.title(); + const imgAltText = title.split(' - ')[1]; + + const image = await popup.getByAltText(imgAltText); + + // get into cross hairs mode + await expect( + popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }) + ).not.toBeChecked(); + await popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }).click(); + await expect( + popup.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }) + ).toBeChecked(); + + const charts = await popup.locator('.chartjs-chart'); + await expect(charts).toHaveCount(2); + await expect(charts.first()).toBeVisible(); + await expect(charts.last()).toBeVisible(); + + const centroidPosition = [734, 516]; + const FWHMs = [214, 201]; + await expect( + popup.getByText( + `Position: (${centroidPosition[0]}, ${centroidPosition[1]})` + ) + ).toBeVisible(); + await expect(popup.getByText(`X FWHM: ${FWHMs[0]}`)).toBeVisible(); + await expect(popup.getByText(`Y FWHM: ${FWHMs[1]}`)).toBeVisible(); + + // expect crosshairs to be drawn on image at the centroid & intensity plots to be drawn & positioned correctly + expect( + await popup.getByTestId('image-panel').screenshot({ + type: 'png', + style: + // hide image controls panel & top buttons from the screenshot as it's not important + '[data-testid="image-controls-panel"], [aria-label="image actions"] { display: none !important; }', + }) + ).toMatchSnapshot({ maxDiffPixels: 150 }); + + // check that clicking the image changes the crosshairs position & causes a data fetch + // for some reason playwright has an off by 1 error in the y-pos in chrome, it works fine when testing manually + // i.e. clicking top left-most pixel results in (0,0) + await image.click({ + position: { x: 750, y: 301 }, + }); + + await expect(popup.getByText('Position: (750, 300)')).toBeVisible(); + const newFWHMs = [204, 180]; + await expect(popup.getByText(`X FWHM: ${newFWHMs[0]}`)).toBeVisible(); + await expect(popup.getByText(`Y FWHM: ${newFWHMs[1]}`)).toBeVisible(); + + expect( + await popup.getByTestId('image-panel').screenshot({ + type: 'png', + style: + // hide image controls panel & top buttons from the screenshot as it's not important + '[data-testid="image-controls-panel"], [aria-label="image actions"] { display: none !important; }', + }) + ).toMatchSnapshot({ maxDiffPixels: 150 }); +}); + test('user can export image', async ({ page }) => { // open up popup const [popup] = await Promise.all([ diff --git a/e2e/real/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-E2E-tests-linux.png b/e2e/real/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-E2E-tests-linux.png new file mode 100644 index 000000000..84040ecbb Binary files /dev/null and b/e2e/real/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-1-E2E-tests-linux.png differ diff --git a/e2e/real/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-E2E-tests-linux.png b/e2e/real/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-E2E-tests-linux.png new file mode 100644 index 000000000..aedaf529b Binary files /dev/null and b/e2e/real/images.spec.ts-snapshots/user-can-use-crosshairs-mode-and-view-intensity-graphs-2-E2E-tests-linux.png differ diff --git a/e2e/real/plotting.spec.ts b/e2e/real/plotting.spec.ts index 83304a037..6f08c8cca 100644 --- a/e2e/real/plotting.spec.ts +++ b/e2e/real/plotting.spec.ts @@ -34,7 +34,7 @@ test('plots a time vs channel graph', async ({ page }) => { // wait for open settings button to be visible i.e. menu is fully closed await popup.locator('[aria-label="open settings"]').click({ trial: true }); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); // need this to wait for canvas animations to execute await popup.waitForTimeout(1000); @@ -90,7 +90,7 @@ test('plots a channel vs channel graph', async ({ page }) => { // wait for open settings button to be visible i.e. menu is fully closed await popup.locator('[aria-label="open settings"]').click({ trial: true }); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); // need this to wait for canvas animations to execute await popup.waitForTimeout(1000); diff --git a/e2e/real/traces.spec.ts b/e2e/real/traces.spec.ts index 7618e71c2..418ad8975 100644 --- a/e2e/real/traces.spec.ts +++ b/e2e/real/traces.spec.ts @@ -35,7 +35,7 @@ test('user can view traces and change trace via clicking on a thumbnail', async .click(), ]); - const chart = await popup.locator('#my-chart'); + const chart = await popup.locator('.chartjs-chart'); // wait for first chart to load await expect(popup.getByRole('progressbar')).toBeVisible(); @@ -85,7 +85,7 @@ test('user can export trace image and data', async ({ page }) => { .click(), ]); - await popup.locator('#my-chart'); + await popup.locator('.chartjs-chart'); const title = await popup.title(); const traceName = title.split(' - ')[1]; diff --git a/package.json b/package.json index 933e54aea..83cc478bc 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "browserslist": "4.24.2", "browserslist-to-esbuild": "2.1.1", "chart.js": "4.4.1", + "chartjs-plugin-annotation": "3.1.0", "chartjs-plugin-zoom": "2.2.0", "date-fns": "3.6.0", "hacktimer": "1.1.3", diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index b4281b637..ec47a9a50 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -8,7 +8,7 @@ * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.6.7' +const PACKAGE_VERSION = '2.7.0' const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/src/api/images.test.tsx b/src/api/images.test.tsx index fc406771d..2cc219bd3 100644 --- a/src/api/images.test.tsx +++ b/src/api/images.test.tsx @@ -1,5 +1,6 @@ import { renderHook, waitFor } from '@testing-library/react'; import colourMapsJson from '../mocks/colourMaps.json'; +import imageCrosshairJson from '../mocks/imageCrosshair.json'; import { RootState } from '../state/store'; import { @@ -7,7 +8,12 @@ import { hooksWrapperWithProviders, waitForRequest, } from '../testUtils'; -import { useColourBar, useColourMaps, useImage } from './images'; +import { + useColourBar, + useColourMaps, + useImage, + useImageCrosshair, +} from './images'; describe('images api functions', () => { afterEach(() => { @@ -211,4 +217,81 @@ describe('images api functions', () => { 'sends axios request to fetch colourmaps and throws an appropriate error on failure' ); }); + + describe('useImageCrosshair', () => { + let params: URLSearchParams; + + let state: RootState; + + beforeEach(() => { + params = new URLSearchParams(); + state = getInitialState(); + }); + + it('sends request to fetch crosshair info for centroid and returns successful response', async () => { + const { result } = renderHook( + () => useImageCrosshair('1', 'TEST', undefined, true), + { + wrapper: hooksWrapperWithProviders(), + } + ); + + await waitFor(() => { + expect(result.current.isSuccess).toBeTruthy(); + }); + + expect(result.current.data).toEqual(imageCrosshairJson); + }); + + it('sends request to fetch crosshair info for a function with position set and returns successful response', async () => { + state = { + ...state, + functions: { + appliedFunctions: [ + { + id: '1', + name: 'b', + expression: [ + { + type: 'channel', + label: 'CHANNEL_EFGHI', + value: 'CHANNEL_EFGHI', + }, + ], + dataType: 'image', + channels: ['CHANNEL_EFGHI'], + }, + ], + }, + }; + + const pendingRequest = waitForRequest('GET', '/images/1/TEST/crosshair'); + + const { result } = renderHook( + () => useImageCrosshair('1', 'TEST', { x: 1, y: 2 }, true), + { + wrapper: hooksWrapperWithProviders(state), + } + ); + + await waitFor(() => { + expect(result.current.isSuccess).toBeTruthy(); + }); + + const request = await pendingRequest; + + params.append( + 'functions', + JSON.stringify({ name: 'b', expression: 'CHANNEL_EFGHI' }) + ); + params.set('position', '[1,2]'); + + expect(result.current.data).toEqual(imageCrosshairJson); + expect(new URL(request.url).searchParams).toEqual(params); + }); + + it.todo( + 'sends axios request to fetch crosshair info and throws an appropriate error on failure' + ); + }); }); diff --git a/src/api/images.tsx b/src/api/images.tsx index 90937635b..f8e8c47b0 100644 --- a/src/api/images.tsx +++ b/src/api/images.tsx @@ -98,6 +98,43 @@ export const fetchColourMaps = async ( }); }; +export interface CrosshairDimensionType { + position: number; + intensity: { x: number[]; y: number[] }; + fwhm: number; +} + +interface CrosshairResponse { + row: CrosshairDimensionType; + column: CrosshairDimensionType; +} + +export const fetchCrosshair = async ( + apiUrl: string, + recordId: string, + channelName: string, + functionsState: APIFunctionState, + position?: { x: number; y: number } +): Promise => { + const params = new URLSearchParams(); + + functionsState.functions.forEach((func) => { + params.append('functions', JSON.stringify(func)); + }); + if (position) params.set('position', `[${position.x},${position.y}]`); + + return axios + .get(`${apiUrl}/images/${recordId}/${channelName}/crosshair`, { + params, + headers: { + Authorization: `Bearer ${readSciGatewayToken()}`, + }, + }) + .then((response) => { + return response.data; + }); +}; + export const useImage = ( recordId: string, channelName: string, @@ -155,3 +192,22 @@ export const useColourMaps = (): UseQueryResult< }, }); }; + +export const useImageCrosshair = ( + recordId: string, + channelName: string, + position: { x: number; y: number } | undefined, + enabled: boolean +): UseQueryResult => { + const { functions } = useAppSelector(selectQueryParams); + const { apiUrl } = useAppSelector(selectUrls); + + return useQuery({ + queryKey: ['imageCrosshair', recordId, channelName, position, functions], + + queryFn: () => { + return fetchCrosshair(apiUrl, recordId, channelName, functions, position); + }, + enabled, + }); +}; diff --git a/src/images/__snapshots__/falseColourPanel.component.test.tsx.snap b/src/images/__snapshots__/imageControlsPanel.component.test.tsx.snap similarity index 89% rename from src/images/__snapshots__/falseColourPanel.component.test.tsx.snap rename to src/images/__snapshots__/imageControlsPanel.component.test.tsx.snap index 769be1bf7..751945922 100644 --- a/src/images/__snapshots__/falseColourPanel.component.test.tsx.snap +++ b/src/images/__snapshots__/imageControlsPanel.component.test.tsx.snap @@ -1,9 +1,10 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`False colour panel component > renders correctly 1`] = ` +exports[`Image controls panel component > renders correctly 1`] = `
renders correctly 1`] = ` Show extended colourmap options +
renders a canvas element with no annotation if crosshairPosition is undefined 1`] = ` + +
+ +
+
+`; + +exports[`Image plot component > renders a canvas element with the correct attributes passed the correct props for a Y axis plot 1`] = ` + +
+ +
+
+`; + +exports[`Image plot component > renders a canvas element with the correct attributes passed the correct props for an X axis plot 1`] = ` + +
+ +
+
+`; diff --git a/src/images/__snapshots__/imageView.component.test.tsx.snap b/src/images/__snapshots__/imageView.component.test.tsx.snap index 332d869fe..6108e0f79 100644 --- a/src/images/__snapshots__/imageView.component.test.tsx.snap +++ b/src/images/__snapshots__/imageView.component.test.tsx.snap @@ -7,9 +7,7 @@ exports[`Image view component > renders an image and a canvas overlay 1`] = ` >
{ - let props: React.ComponentProps; +describe('Image controls panel component', () => { + let props: React.ComponentProps; const changeColourMap = vi.fn(); const changeLowerLevel = vi.fn(); const changeUpperLevel = vi.fn(); + const changeCrosshairsMode = vi.fn(); beforeEach(() => { props = { colourMap: 'cividis', lowerLevel: 0, upperLevel: 255, + crosshairsMode: false, + crosshairData: undefined, changeColourMap, changeLowerLevel, changeUpperLevel, + changeCrosshairsMode, }; vi.clearAllMocks(); @@ -28,7 +33,7 @@ describe('False colour panel component', () => { }); const createView = () => { - return renderComponentWithProviders(); + return renderComponentWithProviders(); }; it('renders correctly', async () => { @@ -93,7 +98,7 @@ describe('False colour panel component', () => { expect(changeUpperLevel).toHaveBeenCalledWith(50); }); - it('can be disabled and re-enabled', async () => { + it('false colour can be disabled and re-enabled', async () => { const user = userEvent.setup(); createView(); @@ -232,4 +237,49 @@ describe('False colour panel component', () => { expect(changeColourMap).toHaveBeenCalledWith('afmhot'); expect(reverseColourSwitch).toBeDisabled(); }); + + it('calls changeCrosshairsMode when crosshair checkbox is clicked', async () => { + const user = userEvent.setup(); + createView(); + + // "load" requests + await flushPromises(); + + const crosshairsSwitch = screen.getByRole('checkbox', { + name: 'Centroid / Cross Hairs', + }); + + expect(crosshairsSwitch).not.toBeChecked(); + + await user.click(crosshairsSwitch); + + expect(props.changeCrosshairsMode).toHaveBeenCalled(); + }); + + it('renders crosshairs info when crosshairs mode is active', async () => { + props.crosshairData = imageCrosshairJson; + props.crosshairsMode = true; + createView(); + + // "load" requests + await flushPromises(); + + expect( + screen.getByRole('checkbox', { + name: 'Centroid / Cross Hairs', + }) + ).toBeChecked(); + + expect( + screen.getByText( + `Position: (${imageCrosshairJson.column.position}, ${imageCrosshairJson.row.position})` + ) + ).toBeVisible(); + expect( + screen.getByText(`X FWHM: ${imageCrosshairJson.column.fwhm}`) + ).toBeVisible(); + expect( + screen.getByText(`Y FWHM: ${imageCrosshairJson.row.fwhm}`) + ).toBeVisible(); + }); }); diff --git a/src/images/falseColourPanel.component.tsx b/src/images/imageControlsPanel.component.tsx similarity index 85% rename from src/images/falseColourPanel.component.tsx rename to src/images/imageControlsPanel.component.tsx index c020d5807..8ea350968 100644 --- a/src/images/falseColourPanel.component.tsx +++ b/src/images/imageControlsPanel.component.tsx @@ -13,6 +13,7 @@ import { Slider, Stack, Switch, + Typography, } from '@mui/material'; import React from 'react'; import { @@ -20,6 +21,7 @@ import { FalseColourParams, useColourBar, useColourMaps, + useImageCrosshair, } from '../api/images'; const marks = [ @@ -57,10 +59,13 @@ const marks = [ }, ]; -interface FalseColourPanelProps extends FalseColourParams { +interface ImageControlsPanelProps extends FalseColourParams { + crosshairsMode: boolean; + changeCrosshairsMode: (value: boolean) => void; changeColourMap: (colourMap: string | undefined) => void; changeLowerLevel: (value: number | undefined) => void; changeUpperLevel: (value: number | undefined) => void; + crosshairData: ReturnType['data']; } export function filterNamesWithSuffixR( @@ -120,14 +125,17 @@ export const ColourMapSelect = ( ); }; -const FalseColourPanel = (props: FalseColourPanelProps) => { +const ImageControlsPanel = (props: ImageControlsPanelProps) => { const { colourMap, lowerLevel, upperLevel, + crosshairsMode, changeColourMap, changeLowerLevel, changeUpperLevel, + changeCrosshairsMode, + crosshairData, } = props; const { data: colourMaps } = useColourMaps(); @@ -191,6 +199,13 @@ const FalseColourPanel = (props: FalseColourPanelProps) => { setExtendedColourMap(checked); }; + const handleChangeCrosshairMode = ( + _event: React.ChangeEvent, + checked: boolean + ) => { + changeCrosshairsMode(checked); + }; + const handleColourMapChange = (event: SelectChangeEvent) => { const newValue = event.target.value as string; setSelectColourMap(newValue); @@ -218,7 +233,7 @@ const FalseColourPanel = (props: FalseColourPanelProps) => { ); return ( - + { } label="Show extended colourmap options" /> + + } + label="Centroid / Cross Hairs" + /> + + {crosshairsMode && crosshairData && ( + <> + + Position: ({crosshairData.column.position},{' '} + {crosshairData.row.position}) + + X FWHM: {crosshairData.column.fwhm} + Y FWHM: {crosshairData.row.fwhm} + + )} @@ -295,4 +330,4 @@ const FalseColourPanel = (props: FalseColourPanelProps) => { ); }; -export default FalseColourPanel; +export default ImageControlsPanel; diff --git a/src/images/imagePlot.component.test.tsx b/src/images/imagePlot.component.test.tsx new file mode 100644 index 000000000..d1b824957 --- /dev/null +++ b/src/images/imagePlot.component.test.tsx @@ -0,0 +1,45 @@ +import { render } from '@testing-library/react'; +import { ImagePlotProps, XImagePlot, YImagePlot } from './imagePlot.component'; +import imageCrosshairJson from '../mocks/imageCrosshair.json'; + +describe('Image plot component', () => { + let props: ImagePlotProps; + + beforeEach(() => { + props = { + imageDims: { width: 100, height: 100 }, + data: imageCrosshairJson.column.intensity, + crosshairPosition: 1, + }; + }); + + it('renders a canvas element with the correct attributes passed the correct props for an X axis plot', () => { + // emulate loading first with no image dimensions loaded and then the image dimensions loaded + const { rerender, asFragment } = render( + + ); + + rerender(); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('renders a canvas element with the correct attributes passed the correct props for a Y axis plot', () => { + // emulate loading first with no image dimensions loaded and then the image dimensions loaded + const { rerender, asFragment } = render( + + ); + + rerender(); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('renders a canvas element with no annotation if crosshairPosition is undefined', () => { + const { asFragment } = render( + + ); + + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/src/images/imagePlot.component.tsx b/src/images/imagePlot.component.tsx new file mode 100644 index 000000000..f4b1599cd --- /dev/null +++ b/src/images/imagePlot.component.tsx @@ -0,0 +1,208 @@ +import React from 'react'; +// only import types as we don't actually run any chart.js code in React +import { type ChartData, type ChartOptions } from 'chart.js'; +import { CrosshairDimensionType } from '../api/images'; + +// In order for the plot area to match pixel to pixel to the image +// we need to offset/adjust for the width/height of the axis ticks. +// These were determined by trial and error aka how small can they be +// before the axis tick labels start to get cut off +/** + * The width offset for XImagePlot + */ +export const XIMAGEPLOT_OFFSET = 42; // 42 needed for 16-bit images (theoretically have 5 digits on the intensity axis) +/** + * The height offset for YImagePlot + */ +export const YIMAGEPLOT_OFFSET = 22; + +export interface ImagePlotProps { + data: CrosshairDimensionType['intensity']; + crosshairPosition?: number; + imageDims: { width: number; height: number }; +} + +const commonChartOptions: ChartOptions<'line'> = { + responsive: true, // we don't actually care about resizing - this is just here to help when switching between retina & non-retina displays + maintainAspectRatio: false, + interaction: { + mode: 'index', + intersect: true, + }, + plugins: { + legend: { + display: false, + }, + annotation: { annotations: {} }, + }, +}; + +const YChartOptions: ChartOptions<'line'> = { + ...commonChartOptions, + indexAxis: 'y', + scales: { + y: { + type: 'linear', + ticks: { padding: 0, align: 'start' }, + reverse: true, + }, + x: { + type: 'linear', + min: 0, + ticks: { padding: 0 }, + }, + }, +}; + +const XChartOptions: ChartOptions<'line'> = { + ...commonChartOptions, + scales: { + y: { + type: 'linear', + ticks: { + padding: 0, + z: 1, + }, + min: 0, + position: 'right', + }, + x: { + type: 'linear', + ticks: { padding: 0, align: 'start' }, + }, + }, +}; + +export const XImagePlot = (props: ImagePlotProps) => { + const { data, imageDims, crosshairPosition } = props; + return ( + + ); +}; + +export const YImagePlot = (props: ImagePlotProps) => { + const { data, imageDims, crosshairPosition } = props; + return ( + + ); +}; + +const ImagePlot = ( + props: ImagePlotProps & { + type: 'x' | 'y'; + chartOptions: ChartOptions<'line'>; + } +) => { + const { data, crosshairPosition, imageDims, type, chartOptions } = props; + + // set the initial options + const [optionsString, setOptionsString] = React.useState( + JSON.stringify(chartOptions) + ); + + const [dataString, setDataString] = React.useState( + JSON.stringify({ + labels: data.x, + datasets: [ + { + data: data.y, + borderColor: 'red', // same colour as crosshairPosition + borderWidth: 1, + pointRadius: 0, + pointHitRadius: 2, + }, + ], + } satisfies ChartData<'line'>) + ); + + React.useEffect(() => { + setDataString( + JSON.stringify({ + labels: data.x, + datasets: [ + { + data: data.y, + borderColor: 'red', // same colour as crosshairPosition + borderWidth: 1, + pointRadius: 0, + pointHitRadius: 2, + }, + ], + } satisfies ChartData<'line'>) + ); + }, [data]); + + React.useEffect(() => { + // need to create a deep clone so that any common options between x and y charts + // can be updated without a race condition (e.g. annotations) + const newChartOptions = JSON.parse(JSON.stringify(chartOptions)); + + if (newChartOptions.plugins?.annotation && crosshairPosition) + newChartOptions.plugins.annotation.annotations = { + line: { + type: 'line', + ...(type === 'x' + ? { xMin: crosshairPosition, xMax: crosshairPosition } + : { + yMin: crosshairPosition, + yMax: crosshairPosition, + }), + borderColor: 'red', + borderWidth: 1, + }, + }; + + if (imageDims.width && imageDims.height) { + const limit = { + min: 0, + max: (type === 'x' ? imageDims.width : imageDims.height) - 1, + }; + if (newChartOptions.scales?.[type]) + // use Object.assign here as otherwise typescript gets unhappy about chartOptions.scales?.[type] potentially being undefined + // so can't use a normal chartOptions.scales.[type] = command as it won't allow potential undefined on the LHS + Object.assign(newChartOptions.scales?.[type], { + ...newChartOptions.scales?.[type], + ...limit, + }); + } + setOptionsString(JSON.stringify(newChartOptions)); + }, [chartOptions, crosshairPosition, imageDims, type]); + + /* This canvas is turned into a Chart.js plot via code in windowPortal.component.tsx */ + return ( +
+ +
+ ); +}; diff --git a/src/images/imageView.component.test.tsx b/src/images/imageView.component.test.tsx index c018f48fb..c27fb5e34 100644 --- a/src/images/imageView.component.test.tsx +++ b/src/images/imageView.component.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { flushPromises } from '../testUtils'; import ImageView, { ImageViewProps } from './imageView.component'; @@ -19,6 +19,8 @@ describe('Image view component', () => { Object.defineProperty(global.Image.prototype, 'width', { get: () => 300, }); + global.HTMLCanvasElement.prototype.getBoundingClientRect = () => + new DOMRect(0, 0, 300, 200); // match image dimensions }); beforeEach(() => { @@ -26,6 +28,9 @@ describe('Image view component', () => { image: 'testSrc', title: 'Test image', viewReset: false, + crosshairsMode: false, + changeCrosshair: vi.fn(), + changeImageDims: vi.fn(), }; }); @@ -36,275 +41,370 @@ describe('Image view component', () => { await flushPromises(); expect(asFragment()).toMatchSnapshot(); + expect(props.changeImageDims).toHaveBeenCalledWith({ + width: 300, + height: 200, + }); }); - it('can zoom into image', async () => { - render(); + describe('Zoom mode', () => { + it('can zoom into image', async () => { + render(); - // "load" image - await flushPromises(); + // "load" image + await flushPromises(); - const image = screen.getByAltText('Test image'); + const image = screen.getByAltText('Test image'); - fireEvent.mouseDown(image, { button: 0, clientX: 0, clientY: 0 }); - fireEvent.mouseMove(image, { button: 0, clientX: 100, clientY: 100 }); - fireEvent.mouseUp(image, { button: 0, clientX: 100, clientY: 100 }); + fireEvent.mouseDown(image, { button: 0, clientX: 0, clientY: 0 }); + fireEvent.mouseMove(image, { button: 0, clientX: 100, clientY: 100 }); + fireEvent.mouseUp(image, { button: 0, clientX: 100, clientY: 100 }); - expect(image).toHaveStyle({ - transform: 'translate(0px,0px) scale(2)', + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(2)', + }); + fireEvent.mouseDown(image, { button: 0, clientX: 100, clientY: 100 }); + fireEvent.mouseMove(image, { button: 0, clientX: 40, clientY: 80 }); + fireEvent.mouseUp(image, { button: 0, clientX: 0, clientY: 0 }); + + expect(image).toHaveStyle({ + transform: 'translate(-200px,-300px) scale(10)', + }); }); - fireEvent.mouseDown(image, { button: 0, clientX: 100, clientY: 100 }); - fireEvent.mouseMove(image, { button: 0, clientX: 40, clientY: 80 }); - fireEvent.mouseUp(image, { button: 0, clientX: 0, clientY: 0 }); - expect(image).toHaveStyle({ - transform: 'translate(-200px,-300px) scale(10)', + it('can pan around zoomed image', async () => { + render(); + + // "load" image + await flushPromises(); + + const image = screen.getByAltText('Test image'); + + // zoom into image + fireEvent.mouseDown(image, { button: 0, clientX: 150, clientY: 100 }); + fireEvent.mouseMove(image, { button: 0, clientX: 0, clientY: 0 }); + fireEvent.mouseUp(image, { button: 0, clientX: 0, clientY: 0 }); + + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(2)', + }); + + fireEvent.mouseDown(image, { + shiftKey: true, + button: 0, + clientX: 100, + clientY: 100, + }); + fireEvent.mouseMove(image, { + shiftKey: true, + button: 0, + clientX: 50, + clientY: 50, + }); + fireEvent.mouseUp(image, { + shiftKey: true, + button: 0, + clientX: 50, + clientY: 50, + }); + + expect(image).toHaveStyle({ + transform: 'translate(-50px,-50px) scale(2)', + }); }); - }); - it('can pan around zoomed image', async () => { - render(); + it('resets view when viewReset is changed', async () => { + const { rerender } = render(); - // "load" image - await flushPromises(); + // "load" image + await flushPromises(); - const image = screen.getByAltText('Test image'); + const image = screen.getByAltText('Test image'); - // zoom into image - fireEvent.mouseDown(image, { button: 0, clientX: 150, clientY: 100 }); - fireEvent.mouseMove(image, { button: 0, clientX: 0, clientY: 0 }); - fireEvent.mouseUp(image, { button: 0, clientX: 0, clientY: 0 }); + fireEvent.mouseDown(image, { button: 0, clientX: 100, clientY: 100 }); + fireEvent.mouseMove(image, { button: 0, clientX: 50, clientY: 100 }); + fireEvent.mouseUp(image, { button: 0, clientX: 50, clientY: 100 }); - expect(image).toHaveStyle({ - transform: 'translate(0px,0px) scale(2)', - }); + expect(image).toHaveStyle({ + transform: 'translate(-300px,-600px) scale(6)', + }); - fireEvent.mouseDown(image, { - shiftKey: true, - button: 0, - clientX: 100, - clientY: 100, - }); - fireEvent.mouseMove(image, { - shiftKey: true, - button: 0, - clientX: 50, - clientY: 50, - }); - fireEvent.mouseUp(image, { - shiftKey: true, - button: 0, - clientX: 50, - clientY: 50, - }); + props.viewReset = !props.viewReset; + rerender(); - expect(image).toHaveStyle({ - transform: 'translate(-50px,-50px) scale(2)', + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(1)', + }); }); - }); - it('resets view when viewReset is changed', async () => { - const { rerender } = render(); + it('ignores non-left mouse clicks and mouse move events without corresponding mouse down events', async () => { + render(); - // "load" image - await flushPromises(); + // "load" image + await flushPromises(); - const image = screen.getByAltText('Test image'); + const image = screen.getByAltText('Test image'); - fireEvent.mouseDown(image, { button: 0, clientX: 100, clientY: 100 }); - fireEvent.mouseMove(image, { button: 0, clientX: 50, clientY: 100 }); - fireEvent.mouseUp(image, { button: 0, clientX: 50, clientY: 100 }); + fireEvent.mouseDown(image, { button: 2, clientX: 50, clientY: 100 }); - expect(image).toHaveStyle({ - transform: 'translate(-300px,-600px) scale(6)', - }); + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(1)', + }); - props.viewReset = !props.viewReset; - rerender(); + fireEvent.mouseMove(image, { button: 0, clientX: 50, clientY: 100 }); - expect(image).toHaveStyle({ - transform: 'translate(0px,0px) scale(1)', + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(1)', + }); }); - }); - it('ignores non-left mouse clicks and mouse move events without corresponding mouse down events', async () => { - render(); + it("doesn't let you pan out of bounds", async () => { + render(); + + // "load" image + await flushPromises(); + + const image = screen.getByAltText('Test image'); + + fireEvent.mouseDown(image, { button: 0, clientX: 75, clientY: 50 }); + fireEvent.mouseMove(image, { button: 0, clientX: 225, clientY: 150 }); + fireEvent.mouseUp(image, { button: 0, clientX: 225, clientY: 150 }); + + expect(image).toHaveStyle({ + transform: 'translate(-150px,-100px) scale(2)', + }); + + // try to pan out of bounds past 300,200 + fireEvent.mouseDown(image, { + shiftKey: true, + button: 0, + clientX: 200, + clientY: 200, + }); + fireEvent.mouseMove(image, { + shiftKey: true, + button: 0, + clientX: 0, + clientY: 0, + }); + fireEvent.mouseUp(image, { + shiftKey: true, + button: 0, + clientX: 0, + clientY: 0, + }); + + expect(image).toHaveStyle({ + transform: 'translate(-150px,-100px) scale(2)', + }); + + // try to pan out of bounds past 0,0 + fireEvent.mouseDown(image, { + shiftKey: true, + button: 0, + clientX: 0, + clientY: 0, + }); + fireEvent.mouseMove(image, { + shiftKey: true, + button: 0, + clientX: 200, + clientY: 200, + }); + fireEvent.mouseUp(image, { + shiftKey: true, + button: 0, + clientX: 200, + clientY: 200, + }); + + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(2)', + }); + }); - // "load" image - await flushPromises(); + it("doesn't let you zoom out of bounds", async () => { + const { rerender } = render(); - const image = screen.getByAltText('Test image'); + // "load" image + await flushPromises(); - fireEvent.mouseDown(image, { button: 2, clientX: 50, clientY: 100 }); + const image = screen.getByAltText('Test image'); - expect(image).toHaveStyle({ - transform: 'translate(0px,0px) scale(1)', - }); + // try to zoom out of bounds past 300,200 on x axis + fireEvent.mouseDown(image, { button: 0, clientX: 285, clientY: 184 }); + fireEvent.mouseMove(image, { button: 0, clientX: 285, clientY: 200 }); + fireEvent.mouseUp(image, { button: 0, clientX: 285, clientY: 200 }); - fireEvent.mouseMove(image, { button: 0, clientX: 50, clientY: 100 }); + expect(image).toHaveStyle({ + transform: 'translate(-3450px,-2300px) scale(12.5)', + }); - expect(image).toHaveStyle({ - transform: 'translate(0px,0px) scale(1)', - }); - }); + props.viewReset = !props.viewReset; + rerender(); - it("doesn't let you pan out of bounds", async () => { - render(); + // try to zoom out of bounds past 300,200 on y axis + fireEvent.mouseDown(image, { button: 0, clientX: 285, clientY: 195 }); + fireEvent.mouseMove(image, { button: 0, clientX: 300, clientY: 195 }); + fireEvent.mouseUp(image, { button: 0, clientX: 300, clientY: 195 }); - // "load" image - await flushPromises(); + expect(image).toHaveStyle({ + transform: 'translate(-5700px,-3800px) scale(20)', + }); - const image = screen.getByAltText('Test image'); + props.viewReset = !props.viewReset; + rerender(); - fireEvent.mouseDown(image, { button: 0, clientX: 75, clientY: 50 }); - fireEvent.mouseMove(image, { button: 0, clientX: 225, clientY: 150 }); - fireEvent.mouseUp(image, { button: 0, clientX: 225, clientY: 150 }); + // try to zoom out of bounds past 0 on x axis + fireEvent.mouseDown(image, { button: 0, clientX: 15, clientY: 16 }); + fireEvent.mouseMove(image, { button: 0, clientX: 14, clientY: 0 }); + fireEvent.mouseUp(image, { button: 0, clientX: 14, clientY: 0 }); - expect(image).toHaveStyle({ - transform: 'translate(-150px,-100px) scale(2)', - }); + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(12.5)', + }); - // try to pan out of bounds past 300,200 - fireEvent.mouseDown(image, { - shiftKey: true, - button: 0, - clientX: 200, - clientY: 200, - }); - fireEvent.mouseMove(image, { - shiftKey: true, - button: 0, - clientX: 0, - clientY: 0, - }); - fireEvent.mouseUp(image, { - shiftKey: true, - button: 0, - clientX: 0, - clientY: 0, - }); + props.viewReset = !props.viewReset; + rerender(); - expect(image).toHaveStyle({ - transform: 'translate(-150px,-100px) scale(2)', - }); + // try to zoom out of bounds past 0 on Y axis + fireEvent.mouseDown(image, { button: 0, clientX: 15, clientY: 5 }); + fireEvent.mouseMove(image, { button: 0, clientX: 0, clientY: 4 }); + fireEvent.mouseUp(image, { button: 0, clientX: 0, clientY: 4 }); - // try to pan out of bounds past 0,0 - fireEvent.mouseDown(image, { - shiftKey: true, - button: 0, - clientX: 0, - clientY: 0, - }); - fireEvent.mouseMove(image, { - shiftKey: true, - button: 0, - clientX: 200, - clientY: 200, - }); - fireEvent.mouseUp(image, { - shiftKey: true, - button: 0, - clientX: 200, - clientY: 200, + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(20)', + }); }); - expect(image).toHaveStyle({ - transform: 'translate(0px,0px) scale(2)', - }); - }); + it("doesn't let you zoom if zoom box is too small or zoom box too far out of bounds", async () => { + render(); - it("doesn't let you zoom out of bounds", async () => { - const { rerender } = render(); + // "load" image + await flushPromises(); - // "load" image - await flushPromises(); + const image = screen.getByAltText('Test image'); - const image = screen.getByAltText('Test image'); + // try a zoom box which is too small + fireEvent.mouseDown(image, { button: 0, clientX: 100, clientY: 100 }); + fireEvent.mouseMove(image, { button: 0, clientX: 101, clientY: 101 }); + fireEvent.mouseUp(image, { button: 0, clientX: 101, clientY: 101 }); - // try to zoom out of bounds past 300,200 on x axis - fireEvent.mouseDown(image, { button: 0, clientX: 285, clientY: 184 }); - fireEvent.mouseMove(image, { button: 0, clientX: 285, clientY: 200 }); - fireEvent.mouseUp(image, { button: 0, clientX: 285, clientY: 200 }); + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(1)', + }); - expect(image).toHaveStyle({ - transform: 'translate(-3450px,-2300px) scale(12.5)', - }); + // try a zoom box which exceeds 300,200 + fireEvent.mouseDown(image, { button: 0, clientX: 290, clientY: 100 }); + fireEvent.mouseMove(image, { button: 0, clientX: 291, clientY: 150 }); + fireEvent.mouseUp(image, { button: 0, clientX: 291, clientY: 150 }); - props.viewReset = !props.viewReset; - rerender(); + fireEvent.mouseDown(image, { button: 0, clientX: 150, clientY: 190 }); + fireEvent.mouseMove(image, { button: 0, clientX: 250, clientY: 191 }); + fireEvent.mouseUp(image, { button: 0, clientX: 250, clientY: 191 }); - // try to zoom out of bounds past 300,200 on y axis - fireEvent.mouseDown(image, { button: 0, clientX: 285, clientY: 195 }); - fireEvent.mouseMove(image, { button: 0, clientX: 300, clientY: 195 }); - fireEvent.mouseUp(image, { button: 0, clientX: 300, clientY: 195 }); + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(1)', + }); - expect(image).toHaveStyle({ - transform: 'translate(-5700px,-3800px) scale(20)', - }); + // try a zoom box which exceeds 0,0 + fireEvent.mouseDown(image, { button: 0, clientX: 1, clientY: 150 }); + fireEvent.mouseMove(image, { button: 0, clientX: 0, clientY: 100 }); + fireEvent.mouseUp(image, { button: 0, clientX: 0, clientY: 100 }); - props.viewReset = !props.viewReset; - rerender(); + fireEvent.mouseDown(image, { button: 0, clientX: 250, clientY: 1 }); + fireEvent.mouseMove(image, { button: 0, clientX: 150, clientY: 0 }); + fireEvent.mouseUp(image, { button: 0, clientX: 150, clientY: 0 }); - // try to zoom out of bounds past 0 on x axis - fireEvent.mouseDown(image, { button: 0, clientX: 15, clientY: 16 }); - fireEvent.mouseMove(image, { button: 0, clientX: 14, clientY: 0 }); - fireEvent.mouseUp(image, { button: 0, clientX: 14, clientY: 0 }); + expect(image).toHaveStyle({ + transform: 'translate(0px,0px) scale(1)', + }); + }); + }); - expect(image).toHaveStyle({ - transform: 'translate(0px,0px) scale(12.5)', + describe('Crosshairs mode', () => { + beforeEach(() => { + props = { + ...props, + crosshairsMode: true, + crosshair: { x: 1, y: 1 }, + changeCrosshair: vi.fn(), + }; }); - props.viewReset = !props.viewReset; - rerender(); + it('can click on image and changeCrosshair should be called', async () => { + render(); - // try to zoom out of bounds past 0 on Y axis - fireEvent.mouseDown(image, { button: 0, clientX: 15, clientY: 5 }); - fireEvent.mouseMove(image, { button: 0, clientX: 0, clientY: 4 }); - fireEvent.mouseUp(image, { button: 0, clientX: 0, clientY: 4 }); + // "load" image + await flushPromises(); - expect(image).toHaveStyle({ - transform: 'translate(0px,0px) scale(20)', + const image = screen.getByAltText('Test image'); + + fireEvent.click(image, { button: 0, clientX: 2, clientY: 2 }); + + expect(props.changeCrosshair).toHaveBeenCalledWith({ x: 2, y: 2 }); + }); + + it('resize observer works', async () => { + const observeSpy = vi.fn(); + + // Override of default constructor behavior + // use a timeout to trigger a "resize" event + global.ResizeObserver = class MockedResizeObserver { + constructor(cb: ResizeObserverCallback) { + setTimeout(() => { + cb( + [ + { + contentRect: { + height: 101, + width: 102, + }, + }, + ] as ResizeObserverEntry[], + + this + ); + }, 150); + } + + // Attaching spy to "observe" function. + observe = observeSpy; + unobserve = vi.fn(); + disconnect = vi.fn(); + }; + + render(); + + // "load" image + await flushPromises(); + + const overlay: HTMLCanvasElement = screen.getByTestId('overlay'); + expect(overlay.width).not.toBe(102); + + // wait for "resize" event + await waitFor(() => expect(overlay.width).toBe(102)); }); }); - it("doesn't let you zoom if zoom box is too small or zoom box too far out of bounds", async () => { - render(); + it('resets zoom when switching to crosshairs mode', async () => { + const { rerender } = render(); // "load" image await flushPromises(); const image = screen.getByAltText('Test image'); - // try a zoom box which is too small fireEvent.mouseDown(image, { button: 0, clientX: 100, clientY: 100 }); - fireEvent.mouseMove(image, { button: 0, clientX: 101, clientY: 101 }); - fireEvent.mouseUp(image, { button: 0, clientX: 101, clientY: 101 }); - - expect(image).toHaveStyle({ - transform: 'translate(0px,0px) scale(1)', - }); - - // try a zoom box which exceeds 300,200 - fireEvent.mouseDown(image, { button: 0, clientX: 290, clientY: 100 }); - fireEvent.mouseMove(image, { button: 0, clientX: 291, clientY: 150 }); - fireEvent.mouseUp(image, { button: 0, clientX: 291, clientY: 150 }); - - fireEvent.mouseDown(image, { button: 0, clientX: 150, clientY: 190 }); - fireEvent.mouseMove(image, { button: 0, clientX: 250, clientY: 191 }); - fireEvent.mouseUp(image, { button: 0, clientX: 250, clientY: 191 }); + fireEvent.mouseMove(image, { button: 0, clientX: 50, clientY: 100 }); + fireEvent.mouseUp(image, { button: 0, clientX: 50, clientY: 100 }); expect(image).toHaveStyle({ - transform: 'translate(0px,0px) scale(1)', + transform: 'translate(-300px,-600px) scale(6)', }); - // try a zoom box which exceeds 0,0 - fireEvent.mouseDown(image, { button: 0, clientX: 1, clientY: 150 }); - fireEvent.mouseMove(image, { button: 0, clientX: 0, clientY: 100 }); - fireEvent.mouseUp(image, { button: 0, clientX: 0, clientY: 100 }); - - fireEvent.mouseDown(image, { button: 0, clientX: 250, clientY: 1 }); - fireEvent.mouseMove(image, { button: 0, clientX: 150, clientY: 0 }); - fireEvent.mouseUp(image, { button: 0, clientX: 150, clientY: 0 }); + props.crosshairsMode = true; + rerender(); expect(image).toHaveStyle({ transform: 'translate(0px,0px) scale(1)', diff --git a/src/images/imageView.component.tsx b/src/images/imageView.component.tsx index 63de20c1a..41c522d1e 100644 --- a/src/images/imageView.component.tsx +++ b/src/images/imageView.component.tsx @@ -4,10 +4,50 @@ export interface ImageViewProps { image: string | undefined; title: string; viewReset: boolean; + crosshairsMode: boolean; + crosshair?: { x: number; y: number }; + changeCrosshair: (value: { x: number; y: number }) => void; + changeImageDims: (value: { width: number; height: number }) => void; } +const drawCrosshair = ( + crosshair: { x: number; y: number }, + canvas: HTMLCanvasElement +): void => { + const ctx = canvas.getContext('2d'); + + if (ctx?.strokeStyle) ctx.strokeStyle = 'red'; + if (ctx?.lineWidth) ctx.lineWidth = 1; + if (ctx?.lineCap) ctx.lineCap = 'square'; + + const { width: overlayWidth, height: overlayHeight } = + canvas.getBoundingClientRect(); + // clear the overlay + ctx?.clearRect(0, 0, overlayWidth, overlayHeight); + + // draw vertical line + ctx?.beginPath(); + ctx?.moveTo(crosshair.x + 0.5, 0.5); // adding half a pixel makes the lines sharper, see: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors#a_linewidth_example + ctx?.lineTo(crosshair.x + 0.5, overlayHeight + 0.5); + ctx?.stroke(); + + // draw horizontal line + ctx?.beginPath(); + ctx?.moveTo(0.5, crosshair.y + 0.5); + ctx?.lineTo(overlayWidth + 0.5, crosshair.y + 0.5); + ctx?.stroke(); +}; + const ImageView = (props: ImageViewProps) => { - const { image, viewReset, title } = props; + const { + image, + viewReset, + title, + crosshairsMode, + crosshair, + changeCrosshair, + changeImageDims, + } = props; const overlayPropsRef = React.useRef<{ startX: number; @@ -44,22 +84,105 @@ const ImageView = (props: ImageViewProps) => { const imgRef = React.useCallback((node: HTMLImageElement) => { setImg(node); }, []); + const crosshairRef = React.useRef(crosshair); // set up the overlay React.useEffect(() => { if (overlay && img) { img.onload = () => { - overlay.width = img.width; - overlay.height = img.height; + changeImageDims({ width: img.width, height: img.height }); + overlay.style.width = `${img.width}px`; + overlay.style.height = `${img.height}px`; + overlay.style.imageRendering = 'pixelated'; + + // from: https://webgl2fundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html + // although we simplify that code into doing all the canvas manip in the resize func + const onResize: ResizeObserverCallback = (entries) => { + if (overlay) { + for (const entry of entries) { + let width; + let height; + let dpr = + overlay.ownerDocument.defaultView?.devicePixelRatio ?? 1; + if (entry.devicePixelContentBoxSize) { + // NOTE: Only this path gives the correct answer + // The other paths are imperfect fallbacks + // for browsers that don't provide anyway to do this + width = entry.devicePixelContentBoxSize[0].inlineSize; + height = entry.devicePixelContentBoxSize[0].blockSize; + dpr = 1; // it's already in width and height + } else if (entry.contentBoxSize) { + if (entry.contentBoxSize[0]) { + width = entry.contentBoxSize[0].inlineSize; + height = entry.contentBoxSize[0].blockSize; + } else { + // @ts-expect-error we expect an error here as this code is covering old browsers where the type was different + width = entry.contentBoxSize.inlineSize; + // @ts-expect-error we expect an error here as this code is covering old browsers where the type was different + height = entry.contentBoxSize.blockSize; + } + } else { + width = entry.contentRect.width; + height = entry.contentRect.height; + } + const displayWidth = Math.round(width * dpr); + const displayHeight = Math.round(height * dpr); + + overlay.width = displayWidth; + overlay.height = displayHeight; + + const ctx = overlay.getContext('2d'); + ctx?.setTransform(1, 0, 0, 1, 0, 0); + ctx?.scale( + overlay.ownerDocument.defaultView?.devicePixelRatio ?? 1, + overlay.ownerDocument.defaultView?.devicePixelRatio ?? 1 + ); + if (crosshairRef.current) + drawCrosshair(crosshairRef.current, overlay); + } + } + }; + + const resizeObserver = new ResizeObserver(onResize); + try { + // only call if the number of device pixels changed + resizeObserver.observe(overlay, { box: 'device-pixel-content-box' }); + } catch { + // device-pixel-content-box is not supported so fallback to this + resizeObserver.observe(overlay, { box: 'content-box' }); + } + + return () => { + resizeObserver.disconnect(); + }; }; } - }, [img, image, overlay]); + }, [img, image, overlay, changeImageDims]); React.useEffect(() => { setPan([0, 0]); setZoom(1); }, [viewReset]); + React.useEffect(() => { + if (crosshairsMode) { + setPan([0, 0]); + setZoom(1); + } else if (overlay) { + const { width: overlayWidth, height: overlayHeight } = + overlay.getBoundingClientRect(); + const ctx = overlay.getContext('2d'); + ctx?.clearRect(0, 0, overlayWidth, overlayHeight); + } + }, [crosshairsMode, overlay]); + + React.useEffect(() => { + crosshairRef.current = crosshair; + if (crosshair && overlay) { + drawCrosshair(crosshair, overlay); + } + }, [crosshair, overlay]); + const mouseDownHandler: React.MouseEventHandler = React.useCallback( (e) => { if (e.button !== 0) { @@ -104,8 +227,7 @@ const ImageView = (props: ImageViewProps) => { const mouseX = e.clientX - rect.left; const mouseY = e.clientY - rect.top; - const originalWidth = overlay.width; - const originalHeight = overlay.height; + const { width: overlayWidth, height: overlayHeight } = rect; if (overlayProps.isZooming) { // calculate the rectangle width/height based @@ -116,11 +238,11 @@ const ImageView = (props: ImageViewProps) => { const ctx = overlay.getContext('2d'); if (ctx?.strokeStyle) ctx.strokeStyle = 'red'; // clear the overlay - ctx?.clearRect(0, 0, originalWidth, originalHeight); + ctx?.clearRect(0, 0, overlayWidth, overlayHeight); // enforce a zoom region that matches the original ratio of the image - const aspectRatio = originalWidth / originalHeight; + const aspectRatio = overlayWidth / overlayHeight; const h2 = Math.abs(width) / aspectRatio; const w2 = Math.abs(height) * aspectRatio; @@ -137,8 +259,8 @@ const ImageView = (props: ImageViewProps) => { // draw a new rect from the start position // to the current mouse position ctx?.strokeRect( - overlayProps.startX, - overlayProps.startY, + overlayProps.startX + 0.5, // adding half a pixel makes lines sharper, see: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors#a_linewidth_example + overlayProps.startY + 0.5, adjustedWidth, adjustedHeight ); @@ -157,8 +279,8 @@ const ImageView = (props: ImageViewProps) => { // make sure pan doesn't go out of bounds if (newX > 0) newX = 0; if (newY > 0) newY = 0; - if (-originalWidth * (zoom - 1) > newX) newX = oldPan[0]; - if (-originalHeight * (zoom - 1) > newY) newY = oldPan[1]; + if (-overlayWidth * (zoom - 1) > newX) newX = oldPan[0]; + if (-overlayHeight * (zoom - 1) > newY) newY = oldPan[1]; return [newX, newY]; }); @@ -187,17 +309,20 @@ const ImageView = (props: ImageViewProps) => { const boxLeft = prevWidth < 0 ? startX - boxWidth : startX; const boxTop = prevHeight < 0 ? startY - boxHeight : startY; + const { width: overlayWidth, height: overlayHeight } = + overlay.getBoundingClientRect(); + // don't perform zoom if zoom box is too small, or if zoom box is out of bounds if ( (boxWidth > 10 || boxHeight > 10) && - boxLeft + boxWidth < overlay.width + 10 && - boxTop + boxHeight < overlay.height + 10 && + boxLeft + boxWidth < overlayWidth + 10 && + boxTop + boxHeight < overlayHeight + 10 && boxLeft > -10 && boxTop > -10 ) { // zoomFactor is the same for both axis due to us enforcing same aspect ratio // so arbitrarily pick one of width or height here - const zoomFactor = overlay.width / boxWidth; + const zoomFactor = overlayWidth / boxWidth; setZoom((oldZoom) => zoomFactor * oldZoom); setPan((oldPan) => { @@ -207,10 +332,10 @@ const ImageView = (props: ImageViewProps) => { // make sure pan doesn't go out of bounds if (newX > 0) newX = 0; if (newY > 0) newY = 0; - if (-overlay.width * (zoomFactor * zoom - 1) > newX) - newX = -overlay.width * (zoomFactor * zoom - 1); - if (-overlay.height * (zoomFactor * zoom - 1) > newY) - newY = -overlay.height * (zoomFactor * zoom - 1); + if (-overlayWidth * (zoomFactor * zoom - 1) > newX) + newX = -overlayWidth * (zoomFactor * zoom - 1); + if (-overlayHeight * (zoomFactor * zoom - 1) > newY) + newY = -overlayHeight * (zoomFactor * zoom - 1); return [newX, newY]; }); @@ -218,7 +343,7 @@ const ImageView = (props: ImageViewProps) => { // clear the overlay const ctx = overlay.getContext('2d'); - ctx?.clearRect(0, 0, overlay.width, overlay.height); + ctx?.clearRect(0, 0, overlayWidth, overlayHeight); } if (overlayProps.isPanning) { overlayProps.isPanning = false; @@ -235,6 +360,21 @@ const ImageView = (props: ImageViewProps) => { [overlay, overlayProps, zoom] ); + const mouseClickHandler: React.MouseEventHandler = React.useCallback( + (e) => { + e.preventDefault(); + e.stopPropagation(); + + const rect = overlay?.getBoundingClientRect(); + + changeCrosshair({ + x: Math.round(e.clientX - (rect?.left ?? 0)), + y: Math.round(e.clientY - (rect?.top ?? 0)), + }); + }, + [changeCrosshair, overlay] + ); + return (
{ style={{ position: 'absolute', zIndex: 2, pointerEvents: 'none' }} />
- {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions */} {title} { transformOrigin: 'top left', imageRendering: 'pixelated', }} - onMouseDown={mouseDownHandler} - onMouseMove={mouseMoveHandler} - onMouseUp={mouseUpOutHandler} - // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events - onMouseOut={mouseUpOutHandler} + {...(!crosshairsMode + ? { + onMouseDown: mouseDownHandler, + onMouseMove: mouseMoveHandler, + onMouseUp: mouseUpOutHandler, + onMouseOut: mouseUpOutHandler, + } + : { onClick: mouseClickHandler })} />
diff --git a/src/images/imageWindow.component.test.tsx b/src/images/imageWindow.component.test.tsx index 244430fe1..4f4e04971 100644 --- a/src/images/imageWindow.component.test.tsx +++ b/src/images/imageWindow.component.test.tsx @@ -1,4 +1,8 @@ -import { screen, waitForElementToBeRemoved } from '@testing-library/react'; +import { + screen, + waitFor, + waitForElementToBeRemoved, +} from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { DEFAULT_WINDOW_VARS } from '../app.types'; import { TraceOrImageWindow } from '../state/slices/windowSlice'; @@ -25,6 +29,19 @@ vi.mock('./imageView.component', () => ({ ), })); +vi.mock('./imagePlot.component', async () => { + const imagePlot = await vi.importActual('./imagePlot.component'); + return { + ...imagePlot, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + XImagePlot: () => , + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + YImagePlot: () => , + }; +}); + describe('Image Window component', () => { let testImageConfig: TraceOrImageWindow; @@ -56,6 +73,24 @@ describe('Image Window component', () => { expect(screen.getByTestId('mock-image-view')).toBeVisible(); }); + it('renders intensity plots correctly', async () => { + const user = userEvent.setup(); + createView(); + + expect(screen.getByTestId('mock-x-image-plot')).not.toBeVisible(); + expect(screen.getByTestId('mock-y-image-plot')).not.toBeVisible(); + + await user.click( + screen.getByRole('checkbox', { name: 'Centroid / Cross Hairs' }) + ); + + // use waitFor to wait for the api calls to complete + await waitFor(() => + expect(screen.getByTestId('mock-x-image-plot')).toBeVisible() + ); + expect(screen.getByTestId('mock-y-image-plot')).toBeVisible(); + }); + it('renders correctly while image is loading', () => { createView(); screen.getByLabelText('Image loading'); diff --git a/src/images/imageWindow.component.tsx b/src/images/imageWindow.component.tsx index 557de3caa..eb36b5d99 100644 --- a/src/images/imageWindow.component.tsx +++ b/src/images/imageWindow.component.tsx @@ -1,6 +1,6 @@ import { Backdrop, CircularProgress, Grid } from '@mui/material'; import React from 'react'; -import { useImage } from '../api/images'; +import { useImage, useImageCrosshair } from '../api/images'; import { useAppDispatch } from '../state/hooks'; import { TraceOrImageWindow, updateWindow } from '../state/slices/windowSlice'; import ThumbnailSelector from '../windows/thumbnailSelector.component'; @@ -8,8 +8,9 @@ import { ImageButtons } from '../windows/windowButtons.component'; import WindowPortal, { WindowPortal as WindowPortalClass, } from '../windows/windowPortal.component'; -import FalseColourPanel from './falseColourPanel.component'; +import ImageControlsPanel from './imageControlsPanel.component'; import ImageView from './imageView.component'; +import { XImagePlot, YImagePlot } from './imagePlot.component'; interface ImageWindowProps { onClose: () => void; @@ -28,6 +29,10 @@ const ImageWindow = (props: ImageWindowProps) => { ); const [lowerLevel, setLowerLevel] = React.useState(0); const [upperLevel, setUpperLevel] = React.useState(255); + const [crosshairsMode, setCrosshairsMode] = React.useState(false); + const [crosshair, setCrosshair] = React.useState< + { x: number; y: number } | undefined + >(undefined); const { data: image, isLoading: imageLoading } = useImage( recordId, @@ -39,10 +44,31 @@ const ImageWindow = (props: ImageWindowProps) => { } ); + const { data: crosshairData } = useImageCrosshair( + recordId, + channelName, + crosshair, + crosshairsMode + ); + + React.useEffect(() => { + if (crosshairsMode && crosshairData && typeof crosshair === 'undefined') { + setCrosshair({ + x: crosshairData.column.position, + y: crosshairData.row.position, + }); + } else if (!crosshairsMode) { + // reset when we switch out of the mode + setCrosshair(undefined); + } + }, [crosshair, crosshairData, crosshairsMode]); + const [viewFlag, setViewFlag] = React.useState(false); const resetView = React.useCallback(() => { setViewFlag((viewFlag) => !viewFlag); + // reset back to centroid + setCrosshair(undefined); }, []); const updateImageConfig = React.useCallback( @@ -60,10 +86,13 @@ const ImageWindow = (props: ImageWindowProps) => { : {}), }; dispatch(updateWindow(configToSave)); + setCrosshair(undefined); }, [imageConfig, dispatch] ); + const [imageDims, setImageDims] = React.useState({ width: 0, height: 0 }); + return ( { - - + - + + + + + + + + + + + + + + + + - - - diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 2a978ff6c..2fe276592 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -17,6 +17,7 @@ import functionsTokensJson from './functionTokens.json'; import functionsJson from './functions.json'; import recordsJson from './records.json'; import sessionsJson from './sessionsList.json'; +import imageCrosshairJson from './imageCrosshair.json'; // have to add undefined here due to how TS JSON parsing works type RecordsJSONType = (Omit & { @@ -393,4 +394,18 @@ export const handlers = [ http.get('/users/filters', async () => { return HttpResponse.json(favouriteFiltersJson, { status: 201 }); }), + http.get('/images/:recordId/:channelName/crosshair', async ({ request }) => { + const url = new URL(request.url); + const position = url.searchParams.get('position'); + + // if position is null, then return the "centroid" we have in the mock data + // otherwise, the position is given to us via the position param + if (position) { + const positionArr = JSON.parse(position); + imageCrosshairJson.column.position = positionArr[0]; + imageCrosshairJson.row.position = positionArr[1]; + } + + return HttpResponse.json(imageCrosshairJson, { status: 200 }); + }), ]; diff --git a/src/mocks/imageCrosshair.json b/src/mocks/imageCrosshair.json new file mode 100644 index 000000000..f3948f4f9 --- /dev/null +++ b/src/mocks/imageCrosshair.json @@ -0,0 +1,170 @@ +{ + "row": { + "position": 187, + "intensity": { + "x": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, + 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, + 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, + 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, + 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, + 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, + 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, + 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, + 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, + 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, + 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, + 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, + 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, + 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, + 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, + 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, + 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, + 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, + 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, + 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, + 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, + 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, + 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, + 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, + 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, + 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, + 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, + 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, + 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, + 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, + 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, + 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, + 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, + 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, + 654, 655 + ], + "y": [ + 44, 20, 69, 60, 43, 1, 71, 21, 17, 15, 8, 27, 18, 17, 51, 6, 58, 15, 10, + 73, 59, 44, 36, 69, 31, 17, 50, 8, 20, 21, 26, 24, 27, 66, 29, 29, 36, + 16, 30, 73, 43, 69, 29, 45, 10, 26, 30, 22, 17, 14, 50, 40, 24, 28, 0, + 8, 21, 43, 46, 51, 63, 71, 32, 18, 32, 29, 61, 56, 0, 50, 14, 3, 18, 32, + 13, 54, 1, 46, 9, 56, 24, 31, 63, 51, 63, 69, 45, 42, 53, 3, 60, 11, 45, + 58, 65, 24, 60, 73, 37, 52, 61, 12, 53, 5, 21, 7, 68, 19, 15, 58, 70, + 36, 39, 56, 50, 28, 31, 41, 68, 2, 60, 31, 30, 30, 1, 2, 26, 6, 7, 30, + 78, 31, 61, 103, 81, 85, 100, 81, 96, 35, 78, 40, 42, 100, 17, 118, 81, + 111, 111, 129, 111, 96, 118, 107, 137, 118, 111, 144, 114, 122, 96, 107, + 137, 159, 129, 155, 181, 151, 170, 170, 96, 151, 133, 144, 188, 211, + 177, 229, 207, 151, 196, 181, 137, 181, 163, 214, 155, 196, 207, 248, + 177, 170, 111, 133, 200, 181, 218, 192, 203, 211, 185, 170, 177, 137, + 248, 177, 166, 140, 229, 222, 181, 218, 196, 196, 214, 174, 196, 203, + 229, 222, 192, 218, 251, 188, 200, 244, 185, 174, 188, 211, 244, 185, + 188, 251, 218, 225, 185, 255, 229, 255, 222, 192, 251, 200, 214, 177, + 181, 196, 240, 222, 225, 248, 218, 225, 185, 225, 207, 207, 207, 188, + 181, 155, 196, 166, 163, 200, 177, 192, 177, 151, 174, 144, 211, 151, + 170, 137, 151, 151, 163, 196, 137, 133, 177, 196, 129, 148, 174, 140, + 144, 166, 133, 118, 155, 133, 163, 144, 137, 96, 144, 129, 133, 118, + 126, 144, 144, 144, 126, 122, 92, 81, 27, 111, 100, 151, 100, 96, 78, + 100, 107, 85, 3, 100, 96, 103, 85, 85, 81, 61, 114, 92, 22, 78, 107, 81, + 55, 78, 78, 49, 23, 17, 15, 56, 52, 12, 49, 36, 31, 43, 62, 27, 39, 48, + 65, 17, 31, 46, 61, 44, 36, 20, 56, 58, 20, 1, 12, 33, 11, 61, 65, 14, + 8, 58, 20, 12, 11, 13, 1, 44, 67, 39, 44, 71, 74, 31, 11, 23, 67, 48, + 53, 54, 45, 2, 6, 36, 18, 28, 0, 60, 29, 52, 29, 50, 52, 17, 73, 24, 4, + 51, 63, 21, 20, 42, 31, 54, 61, 62, 54, 66, 53, 21, 57, 4, 51, 10, 19, + 38, 0, 12, 49, 24, 54, 2, 55, 63, 9, 41, 21, 35, 4, 61, 43, 29, 49, 58, + 65, 70, 74, 68, 1, 56, 65, 24, 33, 15, 10, 28, 37, 42, 21, 11, 42, 46, + 34, 43, 26, 36, 59, 12, 31, 63, 52, 52, 10, 8, 19, 44, 68, 28, 48, 36, + 16, 6, 17, 33, 70, 29, 38, 19, 71, 29, 59, 53, 9, 25, 9, 30, 26, 52, 47, + 1, 65, 14, 7, 23, 40, 25, 3, 7, 64, 34, 44, 69, 73, 47, 23, 32, 37, 2, + 35, 10, 3, 31, 10, 24, 28, 32, 62, 18, 66, 1, 30, 51, 38, 40, 53, 48, 9, + 51, 2, 66, 22, 4, 6, 57, 36, 50, 57, 18, 45, 44, 39, 47, 21, 19, 63, 3, + 61, 49, 30, 26, 51, 15, 39, 9, 16, 66, 26, 8, 7, 31, 41, 35, 63, 2, 23, + 11, 70, 64, 10, 58, 41, 9, 38, 18, 10, 61, 68, 33, 69, 20, 67, 49, 59, + 15, 60, 59, 52, 54, 43, 54, 54, 19, 61, 64, 51, 49, 12, 15, 18, 7, 55, + 27, 32, 41, 31, 67, 18, 25, 57, 66, 42, 14, 39, 60, 52, 2, 8, 16, 39, 9, + 24, 30, 10, 39, 60, 32, 7, 25, 11, 49, 67, 27, 40, 70, 34, 23, 25, 6, + 49, 1, 48, 48, 3, 1, 47, 51 + ] + }, + "fwhm": 56 + }, + "column": { + "position": 226, + "intensity": { + "x": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, + 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, + 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, + 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, + 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, + 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, + 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, + 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, + 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, + 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, + 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, + 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, + 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, + 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, + 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, + 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, + 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, + 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, + 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, + 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, + 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, + 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, + 486, 487, 488, 489, 490, 491, 492, 493 + ], + "y": [ + 51, 8, 35, 23, 30, 20, 44, 20, 16, 37, 9, 23, 28, 3, 1, 21, 38, 29, 17, + 23, 31, 23, 7, 16, 50, 17, 43, 6, 15, 30, 41, 3, 23, 44, 4, 33, 3, 26, + 48, 27, 45, 17, 51, 7, 44, 23, 7, 44, 48, 2, 23, 8, 32, 23, 13, 24, 42, + 34, 1, 22, 31, 43, 19, 9, 41, 50, 30, 38, 26, 43, 40, 0, 11, 5, 39, 44, + 42, 43, 8, 6, 40, 22, 37, 32, 2, 7, 35, 57, 62, 8, 50, 26, 31, 70, 54, + 14, 62, 9, 75, 67, 82, 85, 77, 85, 82, 111, 100, 82, 77, 121, 116, 113, + 126, 90, 111, 108, 111, 149, 108, 93, 88, 116, 113, 113, 129, 118, 134, + 144, 162, 113, 173, 206, 134, 167, 137, 137, 139, 152, 170, 126, 219, + 175, 160, 137, 167, 198, 196, 144, 180, 162, 196, 219, 152, 183, 175, + 209, 191, 160, 183, 203, 232, 196, 185, 167, 191, 188, 201, 185, 165, + 211, 188, 222, 185, 211, 214, 198, 191, 216, 193, 255, 203, 211, 147, + 198, 152, 211, 162, 185, 198, 198, 180, 155, 157, 173, 211, 188, 214, + 180, 137, 152, 142, 185, 139, 160, 152, 121, 185, 137, 142, 178, 118, + 165, 139, 149, 144, 121, 121, 142, 147, 144, 134, 129, 124, 103, 131, + 131, 118, 124, 113, 113, 116, 124, 90, 118, 142, 100, 85, 72, 82, 111, + 75, 80, 85, 62, 72, 75, 62, 80, 90, 64, 80, 64, 75, 75, 70, 70, 32, 67, + 59, 57, 67, 41, 12, 16, 23, 37, 39, 48, 59, 4, 4, 41, 57, 2, 34, 44, 45, + 19, 35, 45, 11, 20, 45, 18, 11, 40, 14, 23, 17, 5, 36, 2, 12, 12, 5, 41, + 24, 25, 23, 16, 8, 7, 38, 43, 0, 40, 27, 22, 51, 44, 21, 44, 43, 40, 12, + 3, 29, 2, 21, 7, 39, 9, 37, 32, 8, 7, 34, 21, 49, 16, 25, 31, 44, 3, 15, + 13, 41, 43, 13, 21, 29, 5, 9, 20, 42, 33, 17, 45, 33, 4, 20, 30, 42, 33, + 50, 27, 38, 13, 8, 41, 9, 49, 44, 48, 39, 17, 32, 19, 41, 44, 22, 14, + 51, 26, 48, 27, 20, 21, 12, 40, 20, 2, 41, 7, 29, 39, 24, 22, 38, 25, + 30, 48, 38, 17, 49, 6, 27, 3, 14, 24, 40, 45, 41, 35, 25, 40, 23, 38, + 43, 36, 47, 20, 7, 15, 12, 32, 1, 33, 47, 15, 45, 20, 41, 30, 45, 22, 0, + 25, 48, 45, 28, 32, 1, 31, 8, 11, 8, 7, 9, 40, 29, 14, 34, 41, 47, 51, + 32, 17, 19, 39, 16, 41, 24, 21, 30, 3, 13, 4, 21, 3, 42, 35, 25, 48, 42, + 8, 16, 13, 10, 3, 34, 32, 42, 51, 46, 9, 14, 22, 26, 9, 5, 16, 11, 3, + 11, 0, 16, 12, 13, 48, 24, 7, 13, 38 + ] + }, + "fwhm": 61 + } +} diff --git a/src/plotting/__snapshots__/plot.component.test.tsx.snap b/src/plotting/__snapshots__/plot.component.test.tsx.snap index 08176f6fa..6765b3e63 100644 --- a/src/plotting/__snapshots__/plot.component.test.tsx.snap +++ b/src/plotting/__snapshots__/plot.component.test.tsx.snap @@ -6,12 +6,12 @@ exports[`Plot component > renders a canvas element with the correct attributes p style="flex: 1 0 0px; max-height: calc(100% - 38px); max-width: 100%;" >
@@ -24,12 +24,12 @@ exports[`Plot component > updates data object correctly by setting borderDash pr style="flex: 1 0 0px; max-height: calc(100% - 38px); max-width: 100%;" >
@@ -42,12 +42,12 @@ exports[`Plot component > updates data object correctly by setting borderDash, p style="flex: 1 0 0px; max-height: calc(100% - 38px); max-width: 100%;" >
@@ -60,12 +60,12 @@ exports[`Plot component > updates data object correctly by setting opacity to 0 style="flex: 1 0 0px; max-height: calc(100% - 38px); max-width: 100%;" > @@ -78,12 +78,12 @@ exports[`Plot component > updates data object correctly by setting the y axis co style="flex: 1 0 0px; max-height: calc(100% - 38px); max-width: 100%;" > @@ -96,12 +96,12 @@ exports[`Plot component > updates options object correctly 1`] = ` style="flex: 1 0 0px; max-height: calc(100% - 38px); max-width: 100%;" > diff --git a/src/plotting/plot.component.tsx b/src/plotting/plot.component.tsx index 3977afbf0..2a27aff20 100644 --- a/src/plotting/plot.component.tsx +++ b/src/plotting/plot.component.tsx @@ -322,7 +322,7 @@ const Plot = (props: PlotProps) => { > {/* This canvas is turned into a Chart.js plot via code in windowPortal.component.tsx */} for plotting import 'vitest-canvas-mock'; import failOnConsole from 'vitest-fail-on-console'; import { server } from './mocks/server'; diff --git a/src/testUtils.tsx b/src/testUtils.tsx index ced9f4241..a53f41547 100644 --- a/src/testUtils.tsx +++ b/src/testUtils.tsx @@ -9,8 +9,6 @@ import React from 'react'; // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; -// need to mock for plotting -import 'jest-canvas-mock'; import { Provider } from 'react-redux'; import { staticChannels } from './api/channels'; import { diff --git a/src/traces/__snapshots__/tracePlot.component.test.tsx.snap b/src/traces/__snapshots__/tracePlot.component.test.tsx.snap index a2a835c32..36d6bbd97 100644 --- a/src/traces/__snapshots__/tracePlot.component.test.tsx.snap +++ b/src/traces/__snapshots__/tracePlot.component.test.tsx.snap @@ -6,12 +6,12 @@ exports[`Trace plot component > renders a canvas element with the correct attrib style="flex: 1 0 0px; max-height: calc(100vh - 38px); max-width: calc(100% - 150px);" > @@ -24,12 +24,12 @@ exports[`Trace plot component > updates data object correctly when points are se style="flex: 1 0 0px; max-height: calc(100vh - 38px); max-width: calc(100% - 150px);" > diff --git a/src/traces/tracePlot.component.tsx b/src/traces/tracePlot.component.tsx index a2db988e0..6883b8eb8 100644 --- a/src/traces/tracePlot.component.tsx +++ b/src/traces/tracePlot.component.tsx @@ -123,7 +123,7 @@ const TracePlot = (props: TracePlotProps) => { > {/* This canvas is turned into a Chart.js plot via code in windowPortal.component.tsx */} +/// diff --git a/src/windows/__snapshots__/windowButtons.component.test.tsx.snap b/src/windows/__snapshots__/windowButtons.component.test.tsx.snap index 416e6c259..3c611dfb4 100644 --- a/src/windows/__snapshots__/windowButtons.component.test.tsx.snap +++ b/src/windows/__snapshots__/windowButtons.component.test.tsx.snap @@ -3,7 +3,7 @@ exports[`Window buttons components > Image buttons component > renders image buttons group 1`] = `
diff --git a/src/windows/windowButtons.component.tsx b/src/windows/windowButtons.component.tsx index 14d04507e..5f27fc887 100644 --- a/src/windows/windowButtons.component.tsx +++ b/src/windows/windowButtons.component.tsx @@ -311,7 +311,7 @@ export const ImageButtons = (props: ImageButtonsProps) => { const { data, title, resetView } = props; return ( - + diff --git a/src/windows/windowPortal.component.test.tsx b/src/windows/windowPortal.component.test.tsx index 2b3d882d4..cf0bb4073 100644 --- a/src/windows/windowPortal.component.test.tsx +++ b/src/windows/windowPortal.component.test.tsx @@ -58,14 +58,15 @@ describe('Window portal component', () => { /* eslint-disable testing-library/no-node-access */ const scriptTags = newDocument.querySelectorAll('script'); - expect(scriptTags).toHaveLength(5); + expect(scriptTags).toHaveLength(6); expect(scriptTags[0].src).toContain('Chart.js'); expect(scriptTags[1].src).toContain('hammer.js'); expect(scriptTags[2].src).toContain('chartjs-plugin-zoom'); - expect(scriptTags[3].src).toContain('chartjs-adapter-date-fns'); + expect(scriptTags[3].src).toContain('chartjs-plugin-annotation'); + expect(scriptTags[4].src).toContain('chartjs-adapter-date-fns'); - expect(scriptTags[4].type).toEqual('text/javascript'); - expect(scriptTags[4].textContent).toBeTruthy(); + expect(scriptTags[5].type).toEqual('text/javascript'); + expect(scriptTags[5].textContent).toBeTruthy(); /* eslint-enable testing-library/no-node-access */ unmount(); diff --git a/src/windows/windowPortal.component.tsx b/src/windows/windowPortal.component.tsx index 60b042c22..203bd0cd9 100644 --- a/src/windows/windowPortal.component.tsx +++ b/src/windows/windowPortal.component.tsx @@ -78,6 +78,16 @@ export class WindowPortal extends React.PureComponent< chartjsZoomScript.defer = false; externalWindow.document.head.appendChild(chartjsZoomScript); + const chartjsAnnotationScript = + externalWindow.document.createElement('script'); + chartjsAnnotationScript.src = + 'https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-annotation/3.1.0/chartjs-plugin-annotation.min.js'; + chartjsAnnotationScript.crossOrigin = 'anonymous'; + chartjsAnnotationScript.referrerPolicy = 'no-referrer'; + chartjsAnnotationScript.async = false; + chartjsAnnotationScript.defer = false; + externalWindow.document.head.appendChild(chartjsAnnotationScript); + const chartjsDateFnsScript = document.createElement('script'); // TODO: switch this to cdnjs once it's added - this is for consistency and so we can use renovate to update it chartjsDateFnsScript.src = @@ -100,8 +110,8 @@ export class WindowPortal extends React.PureComponent< /** * This code in the below string (which gets inserted into the script tag) * does the following: - * `waitForElm` - given a selector, returns a promise that resolves with the element - * using a `MutationObserver` to inspect DOM changes - used to wait for Chart.js canvas element to be loaded by React + * `waitForElm` - given a selector, returns a promise that resolves with the elements + * using a `MutationObserver` to inspect DOM changes - used to wait for Chart.js canvas element(s) to be loaded by React * `waitForChartJS` - is a simple `setInterval` that checks if the chart.js object has loaded before running any Chart.js code * `MutationObserver` code - we need a way to pass the `data` and `options` variables from * React in the main window to the Chart.js code. We do this by using data-* attributes on the canvas element, @@ -110,16 +120,17 @@ export class WindowPortal extends React.PureComponent< * `addLegendAndTooltipFilters` - given a Chart.js options object, this returns the object with the legend and tooltip filter functions filled * which filter out datasets that have been set to transparent (which is done via the show/hide buttons) */ + /* eslint-disable no-irregular-whitespace */ const code = ` function waitForElm(selector) { return new Promise(resolve => { - if (document.querySelector(selector)) { - return resolve(document.querySelector(selector)); + if (document.querySelectorAll(selector).length !== 0) { + return resolve(document.querySelectorAll(selector)); } const observer = new MutationObserver(mutations => { - if (document.querySelector(selector)) { - resolve(document.querySelector(selector)); + if (document.querySelectorAll(selector).length !== 0) { + resolve(document.querySelectorAll(selector)); observer.disconnect(); } }); @@ -169,6 +180,26 @@ export class WindowPortal extends React.PureComponent< }, }, }, + scales: { + ...options?.scales, + y: { + ...options?.scales?.y, + ...(options?.scales?.y?.ticks?.z === 1 ? { + ticks: { + ...options?.scales?.y?.ticks, + z: 0, + callback: (tickValue, index, ticks) => { + const stringifiedTick = tickValue.toString(); + // pad ticks with Figure space/U+2007 character + // it's the space of 1 numerical digit and isn't stripped by Chart.js + // lets us pad out smaller numbers to ensure alignment with + // both 8-bit & 16-bit image intensity plot + return stringifiedTick.padEnd(5, ' '); + }, + } + } : {}) + } + }, }; } @@ -214,47 +245,49 @@ export class WindowPortal extends React.PureComponent< attributes: true }); - waitForElm("#my-chart").then((canvas) => { - if (canvas && canvas.getContext('2d')) { - const chart = new Chart(canvas.getContext('2d'), { - type: canvas.dataset.type, - data: JSON.parse(canvas.dataset.data), - options: addLegendAndTooltipFilters(JSON.parse(canvas.dataset.options)), - }); - window.chart = chart; - - const observer = new MutationObserver(mutations => { - for(let mutation of mutations) { - if (mutation.type === 'attributes') { - if(mutation.attributeName === "data-options"){ - chart.options = addLegendAndTooltipFilters(JSON.parse(canvas.dataset.options)); - chart.update("none"); - } - else if(mutation.attributeName === "data-data"){ - chart.data = JSON.parse(canvas.dataset.data); - chart.update("none"); - } - else if(mutation.attributeName === "data-type"){ - chart.config.type = canvas.dataset.type; - chart.update(); - } - else if(mutation.attributeName === "data-view"){ - chart.resetZoom("none"); - chart.update("none"); + waitForElm(".chartjs-chart").then((canvases) => { + for (const canvas of canvases) { + if (canvas && canvas.getContext('2d')) { + const chart = new Chart(canvas.getContext('2d'), { + type: canvas.dataset.type, + data: JSON.parse(canvas.dataset.data), + options: addLegendAndTooltipFilters(JSON.parse(canvas.dataset.options)), + }); + + const observer = new MutationObserver(mutations => { + for(let mutation of mutations) { + if (mutation.type === 'attributes') { + if(mutation.attributeName === "data-options"){ + chart.options = addLegendAndTooltipFilters(JSON.parse(canvas.dataset.options)); + chart.update("none"); + } + else if(mutation.attributeName === "data-data"){ + chart.data = JSON.parse(canvas.dataset.data); + chart.update("none"); + } + else if(mutation.attributeName === "data-type"){ + chart.config.type = canvas.dataset.type; + chart.update(); + } + else if(mutation.attributeName === "data-view"){ + chart.resetZoom("none"); + chart.update("none"); + } } } - } - }); - - observer.observe(canvas, { - attributes: true - }); - } + }); + + observer.observe(canvas, { + attributes: true + }); + } + } }); clearInterval(waitForChartJS); } }, 10); `; + /* eslint-enable no-irregular-whitespace */ chartjsCode.text = code; externalWindow.document.head.appendChild(chartjsCode); diff --git a/yarn.lock b/yarn.lock index 8ee5e350b..5c8c27dfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6,9 +6,9 @@ __metadata: cacheKey: 10c0 "@adobe/css-tools@npm:^4.4.0": - version: 4.4.1 - resolution: "@adobe/css-tools@npm:4.4.1" - checksum: 10c0/1a68ad9af490f45fce7b6e50dd2d8ac0c546d74431649c0d42ee4ceb1a9fa057fae0a7ef1e148effa12d84ec00ed71869ebfe0fb1dcdcc80bfcb6048c12abcc0 + version: 4.4.0 + resolution: "@adobe/css-tools@npm:4.4.0" + checksum: 10c0/d65ddc719389bf469097df80fb16a8af48a973dea4b57565789d70ac8e7ab4987e6dc0095da3ed5dc16c1b6f8960214a7590312eeda8abd543d91fd0f59e6c94 languageName: node linkType: hard @@ -183,7 +183,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.6, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.26.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.6, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": version: 7.26.0 resolution: "@babel/runtime@npm:7.26.0" dependencies: @@ -307,9 +307,9 @@ __metadata: linkType: hard "@date-io/core@npm:^3.0.0": - version: 3.2.0 - resolution: "@date-io/core@npm:3.2.0" - checksum: 10c0/e320e5eb1dc5cdafbebdb5b3a4c80fcf18cef1ccf39bd57e63de7826c966b89814c984b59f7fb1fc3f7650859fb0ba890e39b0e77bfb671f4b01991432ccc06e + version: 3.0.0 + resolution: "@date-io/core@npm:3.0.0" + checksum: 10c0/c8a725ad5729b38d3f73b7c2bf852ac18a980d6b610832db9f87eb6821e6377854112ba4b5f8510d3e3f49d5290a50dd79ae7724bbafeb146bfdfb5b4c8a13ed languageName: node linkType: hard @@ -346,7 +346,7 @@ __metadata: languageName: node linkType: hard -"@emotion/cache@npm:11.14.0, @emotion/cache@npm:^11.13.5, @emotion/cache@npm:^11.14.0": +"@emotion/cache@npm:11.14.0, @emotion/cache@npm:^11.11.0, @emotion/cache@npm:^11.14.0": version: 11.14.0 resolution: "@emotion/cache@npm:11.14.0" dependencies: @@ -367,11 +367,11 @@ __metadata: linkType: hard "@emotion/is-prop-valid@npm:^1.3.0": - version: 1.3.1 - resolution: "@emotion/is-prop-valid@npm:1.3.1" + version: 1.3.0 + resolution: "@emotion/is-prop-valid@npm:1.3.0" dependencies: "@emotion/memoize": "npm:^0.9.0" - checksum: 10c0/123215540c816ff510737ec68dcc499c53ea4deb0bb6c2c27c03ed21046e2e69f6ad07a7a174d271c6cfcbcc9ea44e1763e0cf3875c92192f7689216174803cd + checksum: 10c0/4620b62aaca4b3b610202513652872756d7f4a8b84b2cea6b798dd6e8ccdfe43944b956c6a6a8cb5da0b0fe61bef6caca273d198ba32b5c658df22a6c7371b1b languageName: node linkType: hard @@ -635,13 +635,13 @@ __metadata: linkType: hard "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": - version: 4.4.1 - resolution: "@eslint-community/eslint-utils@npm:4.4.1" + version: 4.4.0 + resolution: "@eslint-community/eslint-utils@npm:4.4.0" dependencies: - eslint-visitor-keys: "npm:^3.4.3" + eslint-visitor-keys: "npm:^3.3.0" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/2aa0ac2fc50ff3f234408b10900ed4f1a0b19352f21346ad4cc3d83a1271481bdda11097baa45d484dd564c895e0762a27a8240be7a256b3ad47129e96528252 + checksum: 10c0/7e559c4ce59cd3a06b1b5a517b593912e680a7f981ae7affab0d01d709e99cd5647019be8fafa38c350305bc32f1f7d42c7073edde2ab536c745e365f37b607e languageName: node linkType: hard @@ -665,22 +665,20 @@ __metadata: linkType: hard "@eslint/config-array@npm:^0.19.0": - version: 0.19.1 - resolution: "@eslint/config-array@npm:0.19.1" + version: 0.19.0 + resolution: "@eslint/config-array@npm:0.19.0" dependencies: - "@eslint/object-schema": "npm:^2.1.5" + "@eslint/object-schema": "npm:^2.1.4" debug: "npm:^4.3.1" minimatch: "npm:^3.1.2" - checksum: 10c0/43b01f596ddad404473beae5cf95c013d29301c72778d0f5bf8a6699939c8a9a5663dbd723b53c5f476b88b0c694f76ea145d1aa9652230d140fe1161e4a4b49 + checksum: 10c0/def23c6c67a8f98dc88f1b87e17a5668e5028f5ab9459661aabfe08e08f2acd557474bbaf9ba227be0921ae4db232c62773dbb7739815f8415678eb8f592dbf5 languageName: node linkType: hard "@eslint/core@npm:^0.9.0": - version: 0.9.1 - resolution: "@eslint/core@npm:0.9.1" - dependencies: - "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/638104b1b5833a9bbf2329f0c0ddf322e4d6c0410b149477e02cd2b78c04722be90c14b91b8ccdef0d63a2404dff72a17b6b412ce489ea429ae6a8fcb8abff28 + version: 0.9.0 + resolution: "@eslint/core@npm:0.9.0" + checksum: 10c0/6d8e8e0991cef12314c49425d8d2d9394f5fb1a36753ff82df7c03185a4646cb7c8736cf26638a4a714782cedf4b23cfc17667d282d3e5965b3920a0e7ce20d4 languageName: node linkType: hard @@ -708,57 +706,57 @@ __metadata: languageName: node linkType: hard -"@eslint/object-schema@npm:^2.1.5": - version: 2.1.5 - resolution: "@eslint/object-schema@npm:2.1.5" - checksum: 10c0/5320691ed41ecd09a55aff40ce8e56596b4eb81f3d4d6fe530c50fdd6552d88102d1c1a29d970ae798ce30849752a708772de38ded07a6f25b3da32ebea081d8 +"@eslint/object-schema@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/object-schema@npm:2.1.4" + checksum: 10c0/e9885532ea70e483fb007bf1275968b05bb15ebaa506d98560c41a41220d33d342e19023d5f2939fed6eb59676c1bda5c847c284b4b55fce521d282004da4dda languageName: node linkType: hard "@eslint/plugin-kit@npm:^0.2.3": - version: 0.2.4 - resolution: "@eslint/plugin-kit@npm:0.2.4" + version: 0.2.3 + resolution: "@eslint/plugin-kit@npm:0.2.3" dependencies: levn: "npm:^0.4.1" - checksum: 10c0/1bcfc0a30b1df891047c1d8b3707833bded12a057ba01757a2a8591fdc8d8fe0dbb8d51d4b0b61b2af4ca1d363057abd7d2fb4799f1706b105734f4d3fa0dbf1 + checksum: 10c0/89a8035976bb1780e3fa8ffe682df013bd25f7d102d991cecd3b7c297f4ce8c1a1b6805e76dd16465b5353455b670b545eff2b4ec3133e0eab81a5f9e99bd90f languageName: node linkType: hard "@floating-ui/core@npm:^1.6.0": - version: 1.6.8 - resolution: "@floating-ui/core@npm:1.6.8" + version: 1.6.7 + resolution: "@floating-ui/core@npm:1.6.7" dependencies: - "@floating-ui/utils": "npm:^0.2.8" - checksum: 10c0/d6985462aeccae7b55a2d3f40571551c8c42bf820ae0a477fc40ef462e33edc4f3f5b7f11b100de77c9b58ecb581670c5c3f46d0af82b5e30aa185c735257eb9 + "@floating-ui/utils": "npm:^0.2.7" + checksum: 10c0/5c9ae274854f87ed09a61de758377d444c2b13ade7fd1067d74287b3e66de5340ae1281e48604b631c540855a2595cfc717adf9a2331eaadc4fa6d28e8571f64 languageName: node linkType: hard "@floating-ui/dom@npm:^1.0.0": - version: 1.6.12 - resolution: "@floating-ui/dom@npm:1.6.12" + version: 1.6.10 + resolution: "@floating-ui/dom@npm:1.6.10" dependencies: "@floating-ui/core": "npm:^1.6.0" - "@floating-ui/utils": "npm:^0.2.8" - checksum: 10c0/c67b39862175b175c6ac299ea970f17a22c7482cfdf3b1bc79313407bf0880188b022b878953fa69d3ce166ff2bd9ae57c86043e5dd800c262b470d877591b7d + "@floating-ui/utils": "npm:^0.2.7" + checksum: 10c0/ed7d7b400e00b2f31f1b8f11863af2cb95d0d3cd84635186ca31b41d8d9fe7fe12c85e4985617d7df7ed365abad48b327d0bae35934842007b4e1052d9780576 languageName: node linkType: hard "@floating-ui/react-dom@npm:^2.0.8": - version: 2.1.2 - resolution: "@floating-ui/react-dom@npm:2.1.2" + version: 2.1.1 + resolution: "@floating-ui/react-dom@npm:2.1.1" dependencies: "@floating-ui/dom": "npm:^1.0.0" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 10c0/e855131c74e68cab505f7f44f92cd4e2efab1c125796db3116c54c0859323adae4bf697bf292ee83ac77b9335a41ad67852193d7aeace90aa2e1c4a640cafa60 + checksum: 10c0/732ab64600c511ceb0563b87bc557aa61789fec4f416a3f092bab89e508fa1d3ee5ade0f42051cc56eb5e4db867b87ab7fd48ce82db9fd4c01d94ffa08f60115 languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.8": - version: 0.2.8 - resolution: "@floating-ui/utils@npm:0.2.8" - checksum: 10c0/a8cee5f17406c900e1c3ef63e3ca89b35e7a2ed645418459a73627b93b7377477fc888081011c6cd177cac45ec2b92a6cab018c14ea140519465498dddd2d3f9 +"@floating-ui/utils@npm:^0.2.7": + version: 0.2.7 + resolution: "@floating-ui/utils@npm:0.2.7" + checksum: 10c0/0559ea5df2dc82219bad26e3509e9d2b70f6987e552dc8ddf7d7f5923cfeb7c44bf884567125b1f9cdb122a4c7e6e7ddbc666740bc30b0e4091ccbca63c6fb1c languageName: node linkType: hard @@ -835,23 +833,23 @@ __metadata: linkType: hard "@inquirer/confirm@npm:^5.0.0": - version: 5.1.1 - resolution: "@inquirer/confirm@npm:5.1.1" + version: 5.1.0 + resolution: "@inquirer/confirm@npm:5.1.0" dependencies: - "@inquirer/core": "npm:^10.1.2" - "@inquirer/type": "npm:^3.0.2" + "@inquirer/core": "npm:^10.1.1" + "@inquirer/type": "npm:^3.0.1" peerDependencies: "@types/node": ">=18" - checksum: 10c0/acca658c2b0a4546560d4c22e405aa7a94644a1126fd0ca895c7d2d11a3a5c836e85ffb45b7b2f9c955c5c0cc44975dbefa17d66e82de01b545e73d6f8de5c80 + checksum: 10c0/c75e91a84839c800a7176e3c790368656c505f6f8c1f8e7cd022055eb31d75d73ac847224061791f6c35e71be35fac52d2efb976e4709884d00d4968e37630c7 languageName: node linkType: hard -"@inquirer/core@npm:^10.1.2": - version: 10.1.2 - resolution: "@inquirer/core@npm:10.1.2" +"@inquirer/core@npm:^10.1.1": + version: 10.1.1 + resolution: "@inquirer/core@npm:10.1.1" dependencies: - "@inquirer/figures": "npm:^1.0.9" - "@inquirer/type": "npm:^3.0.2" + "@inquirer/figures": "npm:^1.0.8" + "@inquirer/type": "npm:^3.0.1" ansi-escapes: "npm:^4.3.2" cli-width: "npm:^4.1.0" mute-stream: "npm:^2.0.0" @@ -859,23 +857,23 @@ __metadata: strip-ansi: "npm:^6.0.1" wrap-ansi: "npm:^6.2.0" yoctocolors-cjs: "npm:^2.1.2" - checksum: 10c0/95eeb5955a85026ae947d52d5c9b3c116954567fd7b989fad76e8908aca836eb63a3ce463e12690a05fb467d60dec732f831ba19493bc80cb0ab3a55990567a5 + checksum: 10c0/7c3b50b5a8c673d2b978684c39b8d65249145cbc859b598d4d0be9af1d2f30731228996ff8143a8fca1b776f76040d83ae241807291144f6205c23b93e33d408 languageName: node linkType: hard -"@inquirer/figures@npm:^1.0.9": - version: 1.0.9 - resolution: "@inquirer/figures@npm:1.0.9" - checksum: 10c0/21e1a7c902b2b77f126617b501e0fe0d703fae680a9df472afdae18a3e079756aee85690cef595a14e91d18630118f4a3893aab6832b9232fefc6ab31c804a68 +"@inquirer/figures@npm:^1.0.8": + version: 1.0.8 + resolution: "@inquirer/figures@npm:1.0.8" + checksum: 10c0/34d287ff1fd16476c58bbd5b169db315f8319b5ffb09f81a1bb9aabd4165114e7406b1f418d021fd9cd48923008446e3eec274bb818f378ea132a0450bbc91d4 languageName: node linkType: hard -"@inquirer/type@npm:^3.0.2": - version: 3.0.2 - resolution: "@inquirer/type@npm:3.0.2" +"@inquirer/type@npm:^3.0.1": + version: 3.0.1 + resolution: "@inquirer/type@npm:3.0.1" peerDependencies: "@types/node": ">=18" - checksum: 10c0/fe348db2977fff92cad0ade05b36ec40714326fccd4a174be31663f8923729b4276f1736d892a449627d7fb03235ff44e8aac5aa72b09036d993593b813ef313 + checksum: 10c0/c8612362d382114a318dbb523de7b1f54dc6bc6d3016c6eaf299b6a32486b92b0dfb1b4cfc6fe9d99496d15fbb721873a1bd66819f796c8bb09853a3b808812d languageName: node linkType: hard @@ -893,15 +891,6 @@ __metadata: languageName: node linkType: hard -"@isaacs/fs-minipass@npm:^4.0.0": - version: 4.0.1 - resolution: "@isaacs/fs-minipass@npm:4.0.1" - dependencies: - minipass: "npm:^7.0.4" - checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 - languageName: node - linkType: hard - "@istanbuljs/schema@npm:^0.1.2": version: 0.1.3 resolution: "@istanbuljs/schema@npm:0.1.3" @@ -910,13 +899,13 @@ __metadata: linkType: hard "@jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.8 - resolution: "@jridgewell/gen-mapping@npm:0.3.8" + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" dependencies: "@jridgewell/set-array": "npm:^1.2.1" "@jridgewell/sourcemap-codec": "npm:^1.4.10" "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/c668feaf86c501d7c804904a61c23c67447b2137b813b9ce03eca82cb9d65ac7006d766c218685d76e3d72828279b6ee26c347aa1119dab23fbaf36aed51585a + checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb languageName: node linkType: hard @@ -952,15 +941,15 @@ __metadata: linkType: hard "@kurkle/color@npm:^0.3.0": - version: 0.3.4 - resolution: "@kurkle/color@npm:0.3.4" - checksum: 10c0/0e9fd55c614b005c5f0c4c755bca19ec0293bc7513b4ea3ec1725234f9c2fa81afbc78156baf555c8b9cb0d305619253c3f5bca016067daeebb3d00ebb4ea683 + version: 0.3.2 + resolution: "@kurkle/color@npm:0.3.2" + checksum: 10c0/a9e8e3e35dcd59dec4dd4f0105919c05e24823a96347bcf152965c29e48d6290b66d5fb96c071875db752e10930724c48ce6d338fefbd65e0ce5082d5c78970e languageName: node linkType: hard "@mswjs/interceptors@npm:^0.37.0": - version: 0.37.5 - resolution: "@mswjs/interceptors@npm:0.37.5" + version: 0.37.3 + resolution: "@mswjs/interceptors@npm:0.37.3" dependencies: "@open-draft/deferred-promise": "npm:^2.2.0" "@open-draft/logger": "npm:^0.3.0" @@ -968,7 +957,7 @@ __metadata: is-node-process: "npm:^1.2.0" outvariant: "npm:^1.4.3" strict-event-emitter: "npm:^0.5.1" - checksum: 10c0/f618077be622069c44e046d99be12c7d51a77fb774f843f4f36875bce36653799721e5047f5e13c5fa2a3259560e648d0d00515f5daf56c824806aa98e849f2d + checksum: 10c0/5a8d9ab7c491d14dff996f23bda0fa7b7059f68a1d4981b804b6114f1c0c0490bc35860df135ed36da1720984323878b1a9d13dbc80936bbfa67b3d3c3476f6c languageName: node linkType: hard @@ -1017,9 +1006,9 @@ __metadata: linkType: hard "@mui/core-downloads-tracker@npm:^5.16.2": - version: 5.16.13 - resolution: "@mui/core-downloads-tracker@npm:5.16.13" - checksum: 10c0/bc2c623c030bb65bdad7a497f0a77d983e04a8cc47fe31e32b5ea52738185e55a496ffb480a9b488be97c700dba8eeb13fd5d805a9ecfe52a6115c69fe32b276 + version: 5.16.7 + resolution: "@mui/core-downloads-tracker@npm:5.16.7" + checksum: 10c0/4644c850160d01232c1abdbed141e4fa70e155891a9c68f0c2cc3054b4a3cdc1d28cf2d6665366fd8c725b2b091db677e11831552889a4e4e14f1e44450cf654 languageName: node linkType: hard @@ -1071,12 +1060,12 @@ __metadata: languageName: node linkType: hard -"@mui/private-theming@npm:^5.16.13, @mui/private-theming@npm:^5.16.2": - version: 5.16.13 - resolution: "@mui/private-theming@npm:5.16.13" +"@mui/private-theming@npm:^5.16.2, @mui/private-theming@npm:^5.16.6": + version: 5.16.8 + resolution: "@mui/private-theming@npm:5.16.8" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/utils": "npm:^5.16.13" + "@mui/utils": "npm:^5.16.8" prop-types: "npm:^15.8.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1084,16 +1073,16 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/914e1545fb4a8e7e0d64cbb30b8dfa31d935f5b1e65ded92fda6024cfece49b7b2aad2b6b1183c200371ff6c23cb4e0fe494e34324b2a539530199b0928019cc + checksum: 10c0/737e6930be1dbd1dff01702e6583b8a507ca01a4a0ec140d766985b7ebaf8c444d78da52cdada89989728be444d4cd13509ba8950c4397af87c4fc8c8162741b languageName: node linkType: hard -"@mui/styled-engine@npm:^5.16.13, @mui/styled-engine@npm:^5.16.2": - version: 5.16.13 - resolution: "@mui/styled-engine@npm:5.16.13" +"@mui/styled-engine@npm:^5.16.2, @mui/styled-engine@npm:^5.16.6": + version: 5.16.8 + resolution: "@mui/styled-engine@npm:5.16.8" dependencies: "@babel/runtime": "npm:^7.23.9" - "@emotion/cache": "npm:^11.13.5" + "@emotion/cache": "npm:^11.11.0" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" peerDependencies: @@ -1105,7 +1094,7 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 10c0/a55be4a972723a7c943f65d57ff872692c68f0cbfa84711f4885f52268ebe28f050854e532518fe0bf2d52e29f5ec47e26444d1dd1d500e6191a4a1b6b3d2428 + checksum: 10c0/9c1866cf94fed61013324458be623f2de0c0cb6be065730c12441e798172a08389524b4b011c9e5a120bcc50c07cc7a835d365bd75a8b60f9a553d1de9c8af20 languageName: node linkType: hard @@ -1138,22 +1127,22 @@ __metadata: linkType: hard "@mui/system@npm:^5.16.2": - version: 5.16.13 - resolution: "@mui/system@npm:5.16.13" + version: 5.16.7 + resolution: "@mui/system@npm:5.16.7" dependencies: "@babel/runtime": "npm:^7.23.9" - "@mui/private-theming": "npm:^5.16.13" - "@mui/styled-engine": "npm:^5.16.13" + "@mui/private-theming": "npm:^5.16.6" + "@mui/styled-engine": "npm:^5.16.6" "@mui/types": "npm:^7.2.15" - "@mui/utils": "npm:^5.16.13" + "@mui/utils": "npm:^5.16.6" clsx: "npm:^2.1.0" csstype: "npm:^3.1.3" prop-types: "npm:^15.8.1" peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 peerDependenciesMeta: "@emotion/react": optional: true @@ -1161,11 +1150,11 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10c0/e5ae9a798103c492f2b0df9d07dc4291bcb64689deedf23318b9be9a240efc6e32ccc8956c70cf1fba8769b88074095d0c888dd0235ccd167e11ca2f50ad2620 + checksum: 10c0/c07479c0728433847c1e3d7f57b96d9e0770cc814dfd1c9e070304955984a0b706832703b22388eb83906d1a01691f37047e2bac6a5e5c083e8c29a54302d476 languageName: node linkType: hard -"@mui/types@npm:^7.2.14, @mui/types@npm:^7.2.14-dev.20240529-082515-213b5e33ab, @mui/types@npm:^7.2.15, @mui/types@npm:^7.2.21": +"@mui/types@npm:^7.2.14, @mui/types@npm:^7.2.14-dev.20240529-082515-213b5e33ab, @mui/types@npm:^7.2.15, @mui/types@npm:^7.2.16": version: 7.2.21 resolution: "@mui/types@npm:7.2.21" peerDependencies: @@ -1177,43 +1166,43 @@ __metadata: languageName: node linkType: hard -"@mui/utils@npm:^5.14.16, @mui/utils@npm:^5.15.14, @mui/utils@npm:^5.16.13, @mui/utils@npm:^5.16.2": - version: 5.16.13 - resolution: "@mui/utils@npm:5.16.13" +"@mui/utils@npm:^5.14.16, @mui/utils@npm:^5.15.14, @mui/utils@npm:^5.16.2, @mui/utils@npm:^5.16.6, @mui/utils@npm:^5.16.8": + version: 5.16.8 + resolution: "@mui/utils@npm:5.16.8" dependencies: "@babel/runtime": "npm:^7.23.9" "@mui/types": "npm:^7.2.15" "@types/prop-types": "npm:^15.7.12" clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" - react-is: "npm:^19.0.0" + react-is: "npm:^18.3.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/35e57df90bfbe35c59f96a7092ce8f3d19f7bbd818db64aa45dda983c95bc10b67d9b5ceef6ea7ea92dfae3bb718eff4f7e189b100b9db6631346092efe27677 + checksum: 10c0/86a1daf249a1dc766c0babe439c6874e092ce8239bdd67a57e81fc349934bdee0c98ee1833b282286847ffc16966edff88d9e2a47ac98577fb1010245191888a languageName: node linkType: hard "@mui/utils@npm:^6.0.0-dev.20240529-082515-213b5e33ab": - version: 6.3.1 - resolution: "@mui/utils@npm:6.3.1" + version: 6.0.2 + resolution: "@mui/utils@npm:6.0.2" dependencies: - "@babel/runtime": "npm:^7.26.0" - "@mui/types": "npm:^7.2.21" - "@types/prop-types": "npm:^15.7.14" + "@babel/runtime": "npm:^7.25.0" + "@mui/types": "npm:^7.2.16" + "@types/prop-types": "npm:^15.7.12" clsx: "npm:^2.1.1" prop-types: "npm:^15.8.1" - react-is: "npm:^19.0.0" + react-is: "npm:^18.3.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/b111bca7ad065b1028714d55a8df90267c47a72ffb2bfad7a1709cef0d5d9036b463855d431b3606967c9351c7ee23f1dee02457b2f3ed02513744f0173eb00c + checksum: 10c0/7a7d45878727bb154cd2bed4b8bbe536f24cda9431688e64a87f82dfe313e82cb948865cacc3c416588a3988d5715f3546c636e2b341c6fa0f5409bbe287a133 languageName: node linkType: hard @@ -1340,25 +1329,25 @@ __metadata: languageName: node linkType: hard -"@npmcli/agent@npm:^3.0.0": - version: 3.0.0 - resolution: "@npmcli/agent@npm:3.0.0" +"@npmcli/agent@npm:^2.0.0": + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" dependencies: agent-base: "npm:^7.1.0" http-proxy-agent: "npm:^7.0.0" https-proxy-agent: "npm:^7.0.1" lru-cache: "npm:^10.0.1" socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 + checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae languageName: node linkType: hard -"@npmcli/fs@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/fs@npm:4.0.0" +"@npmcli/fs@npm:^3.1.0": + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" dependencies: semver: "npm:^7.3.5" - checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5 + checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99 languageName: node linkType: hard @@ -1438,135 +1427,114 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.30.0" +"@rollup/rollup-android-arm-eabi@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.22.4" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-android-arm64@npm:4.30.0" +"@rollup/rollup-android-arm64@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-android-arm64@npm:4.22.4" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-darwin-arm64@npm:4.30.0" +"@rollup/rollup-darwin-arm64@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-darwin-arm64@npm:4.22.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-darwin-x64@npm:4.30.0" +"@rollup/rollup-darwin-x64@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-darwin-x64@npm:4.22.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.30.0" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-freebsd-x64@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-freebsd-x64@npm:4.30.0" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-gnueabihf@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.30.0" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.22.4" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.30.0" +"@rollup/rollup-linux-arm-musleabihf@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.22.4" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.30.0" +"@rollup/rollup-linux-arm64-gnu@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.22.4" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.30.0" +"@rollup/rollup-linux-arm64-musl@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.22.4" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loongarch64-gnu@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.30.0" - conditions: os=linux & cpu=loong64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.30.0" +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.22.4" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.30.0" +"@rollup/rollup-linux-riscv64-gnu@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.22.4" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.30.0" +"@rollup/rollup-linux-s390x-gnu@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.22.4" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.30.0" +"@rollup/rollup-linux-x64-gnu@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.22.4" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.30.0" +"@rollup/rollup-linux-x64-musl@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.22.4" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.30.0" +"@rollup/rollup-win32-arm64-msvc@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.22.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.30.0" +"@rollup/rollup-win32-ia32-msvc@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.22.4" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.30.0": - version: 4.30.0 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.30.0" +"@rollup/rollup-win32-x64-msvc@npm:4.22.4": + version: 4.22.4 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.22.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -1823,13 +1791,20 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:1.0.6, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6": +"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6": version: 1.0.6 resolution: "@types/estree@npm:1.0.6" checksum: 10c0/cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a languageName: node linkType: hard +"@types/estree@npm:1.0.5": + version: 1.0.5 + resolution: "@types/estree@npm:1.0.5" + checksum: 10c0/b3b0e334288ddb407c7b3357ca67dbee75ee22db242ca7c56fe27db4e1a31989cb8af48a84dd401deb787fe10cc6b2ab1ee82dc4783be87ededbe3d53c79c70d + languageName: node + linkType: hard + "@types/hammerjs@npm:^2.0.45": version: 2.0.46 resolution: "@types/hammerjs@npm:2.0.46" @@ -1844,16 +1819,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*": - version: 22.10.5 - resolution: "@types/node@npm:22.10.5" - dependencies: - undici-types: "npm:~6.20.0" - checksum: 10c0/6a0e7d1fe6a86ef6ee19c3c6af4c15542e61aea2f4cee655b6252efb356795f1f228bc8299921e82924e80ff8eca29b74d9dd0dd5cc1a90983f892f740b480df - languageName: node - linkType: hard - -"@types/node@npm:22.10.1": +"@types/node@npm:*, @types/node@npm:22.10.1": version: 22.10.1 resolution: "@types/node@npm:22.10.1" dependencies: @@ -1869,7 +1835,7 @@ __metadata: languageName: node linkType: hard -"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.12, @types/prop-types@npm:^15.7.14": +"@types/prop-types@npm:*, @types/prop-types@npm:^15.7.12": version: 15.7.14 resolution: "@types/prop-types@npm:15.7.14" checksum: 10c0/1ec775160bfab90b67a782d735952158c7e702ca4502968aa82565bd8e452c2de8601c8dfe349733073c31179116cf7340710160d3836aa8a1ef76d1532893b1 @@ -1886,11 +1852,11 @@ __metadata: linkType: hard "@types/react-transition-group@npm:^4.4.10, @types/react-transition-group@npm:^4.4.8": - version: 4.4.12 - resolution: "@types/react-transition-group@npm:4.4.12" - peerDependencies: - "@types/react": "*" - checksum: 10c0/0441b8b47c69312c89ec0760ba477ba1a0808a10ceef8dc1c64b1013ed78517332c30f18681b0ec0b53542731f1ed015169fed1d127cc91222638ed955478ec7 + version: 4.4.11 + resolution: "@types/react-transition-group@npm:4.4.11" + dependencies: + "@types/react": "npm:*" + checksum: 10c0/8fbf0dcc1b81985cdcebe3c59d769fe2ea3f4525f12c3a10a7429a59f93e303c82b2abb744d21cb762879f4514969d70a7ab11b9bf486f92213e8fe70e04098d languageName: node linkType: hard @@ -1912,9 +1878,9 @@ __metadata: linkType: hard "@types/sizzle@npm:^2.3.2": - version: 2.3.9 - resolution: "@types/sizzle@npm:2.3.9" - checksum: 10c0/db0277ff62e8ebe6cdae2020fd045fd7fd19f29a3a2ce13c555b14fb00e105e79004883732118b9f2e8b943cb302645e9eddb4e7bdeef1a171da679cd4c32b72 + version: 2.3.8 + resolution: "@types/sizzle@npm:2.3.8" + checksum: 10c0/ab5460147ae6680cc20c2223a8f17d9f7c97144b70f00a222a1c32d68b5207696d48177ab9784dda88c74d93ed5a78dd31f74d271b15382520b423c81b4aac89 languageName: node linkType: hard @@ -2284,7 +2250,7 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": version: 7.1.3 resolution: "agent-base@npm:7.1.3" checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 @@ -2367,9 +2333,9 @@ __metadata: linkType: hard "ansi-regex@npm:^6.0.1": - version: 6.1.0 - resolution: "ansi-regex@npm:6.1.0" - checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 10c0/cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08 languageName: node linkType: hard @@ -2488,26 +2454,26 @@ __metadata: linkType: hard "array.prototype.flat@npm:^1.3.1": - version: 1.3.3 - resolution: "array.prototype.flat@npm:1.3.3" + version: 1.3.2 + resolution: "array.prototype.flat@npm:1.3.2" dependencies: - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/d90e04dfbc43bb96b3d2248576753d1fb2298d2d972e29ca7ad5ec621f0d9e16ff8074dae647eac4f31f4fb7d3f561a7ac005fb01a71f51705a13b5af06a7d8a + call-bind: "npm:^1.0.2" + define-properties: "npm:^1.2.0" + es-abstract: "npm:^1.22.1" + es-shim-unscopables: "npm:^1.0.0" + checksum: 10c0/a578ed836a786efbb6c2db0899ae80781b476200617f65a44846cb1ed8bd8b24c8821b83703375d8af639c689497b7b07277060024b9919db94ac3e10dc8a49b languageName: node linkType: hard "array.prototype.flatmap@npm:^1.3.2": - version: 1.3.3 - resolution: "array.prototype.flatmap@npm:1.3.3" + version: 1.3.2 + resolution: "array.prototype.flatmap@npm:1.3.2" dependencies: - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/ba899ea22b9dc9bf276e773e98ac84638ed5e0236de06f13d63a90b18ca9e0ec7c97d622d899796e3773930b946cd2413d098656c0c5d8cc58c6f25c21e6bd54 + call-bind: "npm:^1.0.2" + define-properties: "npm:^1.2.0" + es-abstract: "npm:^1.22.1" + es-shim-unscopables: "npm:^1.0.0" + checksum: 10c0/67b3f1d602bb73713265145853128b1ad77cc0f9b833c7e1e056b323fbeac41a4ff1c9c99c7b9445903caea924d9ca2450578d9011913191aa88cc3c3a4b54f4 languageName: node linkType: hard @@ -2621,9 +2587,9 @@ __metadata: linkType: hard "axe-core@npm:^4.10.0": - version: 4.10.2 - resolution: "axe-core@npm:4.10.2" - checksum: 10c0/0e20169077de96946a547fce0df39d9aeebe0077f9d3eeff4896518b96fde857f80b98f0d4279274a7178791744dd5a54bb4f322de45b4f561ffa2586ff9a09d + version: 4.10.0 + resolution: "axe-core@npm:4.10.0" + checksum: 10c0/732c171d48caaace5e784895c4dacb8ca6155e9d98045138ebe3952f78457dd05b92c57d05b41ce2a570aff87dbd0471e8398d2c0f6ebe79617b746c8f658998 languageName: node linkType: hard @@ -2853,11 +2819,11 @@ __metadata: languageName: node linkType: hard -"cacache@npm:^19.0.1": - version: 19.0.1 - resolution: "cacache@npm:19.0.1" +"cacache@npm:^18.0.0": + version: 18.0.4 + resolution: "cacache@npm:18.0.4" dependencies: - "@npmcli/fs": "npm:^4.0.0" + "@npmcli/fs": "npm:^3.1.0" fs-minipass: "npm:^3.0.0" glob: "npm:^10.2.2" lru-cache: "npm:^10.0.1" @@ -2865,11 +2831,11 @@ __metadata: minipass-collect: "npm:^2.0.1" minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^7.0.2" - ssri: "npm:^12.0.0" - tar: "npm:^7.4.3" - unique-filename: "npm:^4.0.0" - checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c + p-map: "npm:^4.0.0" + ssri: "npm:^10.0.0" + tar: "npm:^6.1.11" + unique-filename: "npm:^3.0.0" + checksum: 10c0/6c055bafed9de4f3dcc64ac3dc7dd24e863210902b7c470eb9ce55a806309b3efff78033e3d8b4f7dcc5d467f2db43c6a2857aaaf26f0094b8a351d44c42179f languageName: node linkType: hard @@ -3012,6 +2978,15 @@ __metadata: languageName: node linkType: hard +"chartjs-plugin-annotation@npm:3.1.0": + version: 3.1.0 + resolution: "chartjs-plugin-annotation@npm:3.1.0" + peerDependencies: + chart.js: ">=4.0.0" + checksum: 10c0/5494a008a013888b122ac6754c1314e100b6368f55110f08401e3cbdb5969b372ece9569ed3b166b8fbad8c2dc84f15795602079613e2f5f65f787a0931bd676 + languageName: node + linkType: hard + "chartjs-plugin-zoom@npm:2.2.0": version: 2.2.0 resolution: "chartjs-plugin-zoom@npm:2.2.0" @@ -3038,10 +3013,10 @@ __metadata: languageName: node linkType: hard -"chownr@npm:^3.0.0": - version: 3.0.0 - resolution: "chownr@npm:3.0.0" - checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 languageName: node linkType: hard @@ -3617,7 +3592,7 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": +"define-properties@npm:^1.1.3, define-properties@npm:^1.2.0, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" dependencies: @@ -3801,7 +3776,7 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.5.0": +"entities@npm:^4.4.0": version: 4.5.0 resolution: "entities@npm:4.5.0" checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 @@ -3838,7 +3813,7 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9": +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.22.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.9": version: 1.23.9 resolution: "es-abstract@npm:1.23.9" dependencies: @@ -3929,33 +3904,32 @@ __metadata: linkType: hard "es-iterator-helpers@npm:^1.1.0": - version: 1.2.1 - resolution: "es-iterator-helpers@npm:1.2.1" + version: 1.2.0 + resolution: "es-iterator-helpers@npm:1.2.0" dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bind: "npm:^1.0.7" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.6" + es-abstract: "npm:^1.23.3" es-errors: "npm:^1.3.0" es-set-tostringtag: "npm:^2.0.3" function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.6" + get-intrinsic: "npm:^1.2.4" globalthis: "npm:^1.0.4" - gopd: "npm:^1.2.0" + gopd: "npm:^1.0.1" has-property-descriptors: "npm:^1.0.2" - has-proto: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - internal-slot: "npm:^1.1.0" - iterator.prototype: "npm:^1.1.4" - safe-array-concat: "npm:^1.1.3" - checksum: 10c0/97e3125ca472d82d8aceea11b790397648b52c26d8768ea1c1ee6309ef45a8755bb63225a43f3150c7591cffc17caf5752459f1e70d583b4184370a8f04ebd2f + has-proto: "npm:^1.0.3" + has-symbols: "npm:^1.0.3" + internal-slot: "npm:^1.0.7" + iterator.prototype: "npm:^1.1.3" + safe-array-concat: "npm:^1.1.2" + checksum: 10c0/2bd60580dfeae353f5b80445d2808da745e97eeacdb663a8c4d99a12046873830a06d377e9d5e88fe54eece7c94319a5ce5a01220e24d71394ceca8d3ef621d7 languageName: node linkType: hard "es-module-lexer@npm:^1.5.4": - version: 1.6.0 - resolution: "es-module-lexer@npm:1.6.0" - checksum: 10c0/667309454411c0b95c476025929881e71400d74a746ffa1ff4cb450bd87f8e33e8eef7854d68e401895039ac0bac64e7809acbebb6253e055dd49ea9e3ea9212 + version: 1.5.4 + resolution: "es-module-lexer@npm:1.5.4" + checksum: 10c0/300a469488c2f22081df1e4c8398c78db92358496e639b0df7f89ac6455462aaf5d8893939087c1a1cbcbf20eed4610c70e0bcb8f3e4b0d80a5d2611c539408c languageName: node linkType: hard @@ -3980,7 +3954,7 @@ __metadata: languageName: node linkType: hard -"es-shim-unscopables@npm:^1.0.2": +"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2": version: 1.0.2 resolution: "es-shim-unscopables@npm:1.0.2" dependencies: @@ -4241,7 +4215,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.4.3": +"eslint-visitor-keys@npm:^3.3.0": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 @@ -4558,15 +4532,15 @@ __metadata: linkType: hard "fast-glob@npm:^3.3.2": - version: 3.3.3 - resolution: "fast-glob@npm:3.3.3" + version: 3.3.2 + resolution: "fast-glob@npm:3.3.2" dependencies: "@nodelib/fs.stat": "npm:^2.0.2" "@nodelib/fs.walk": "npm:^1.2.3" glob-parent: "npm:^5.1.2" merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.8" - checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe + micromatch: "npm:^4.0.4" + checksum: 10c0/42baad7b9cd40b63e42039132bde27ca2cb3a4950d0a0f9abe4639ea1aa9d3e3b40f98b1fe31cbc0cc17b664c9ea7447d911a152fa34ec5b72977b125a6fc845 languageName: node linkType: hard @@ -4594,11 +4568,11 @@ __metadata: linkType: hard "fastq@npm:^1.6.0": - version: 1.18.0 - resolution: "fastq@npm:1.18.0" + version: 1.17.1 + resolution: "fastq@npm:1.17.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10c0/7be87ecc41762adbddf558d24182f50a4b1a3ef3ee807d33b7623da7aee5faecdcc94fce5aa13fe91df93e269f383232bbcdb2dc5338cd1826503d6063221f36 + checksum: 10c0/1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 languageName: node linkType: hard @@ -4681,19 +4655,19 @@ __metadata: linkType: hard "flatted@npm:^3.2.9": - version: 3.3.2 - resolution: "flatted@npm:3.3.2" - checksum: 10c0/24cc735e74d593b6c767fe04f2ef369abe15b62f6906158079b9874bdb3ee5ae7110bb75042e70cd3f99d409d766f357caf78d5ecee9780206f5fdc5edbad334 + version: 3.3.1 + resolution: "flatted@npm:3.3.1" + checksum: 10c0/324166b125ee07d4ca9bcf3a5f98d915d5db4f39d711fba640a3178b959919aae1f7cfd8aabcfef5826ed8aa8a2aa14cc85b2d7d18ff638ddf4ae3df39573eaf languageName: node linkType: hard "follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.6": - version: 1.15.9 - resolution: "follow-redirects@npm:1.15.9" + version: 1.15.6 + resolution: "follow-redirects@npm:1.15.6" peerDependenciesMeta: debug: optional: true - checksum: 10c0/5829165bd112c3c0e82be6c15b1a58fa9dcfaede3b3c54697a82fe4a62dd5ae5e8222956b448d2f98e331525f05d00404aba7d696de9e761ef6e42fdc780244f + checksum: 10c0/9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071 languageName: node linkType: hard @@ -4724,13 +4698,13 @@ __metadata: linkType: hard "form-data@npm:^4.0.0, form-data@npm:~4.0.0": - version: 4.0.1 - resolution: "form-data@npm:4.0.1" + version: 4.0.0 + resolution: "form-data@npm:4.0.0" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" mime-types: "npm:^2.1.12" - checksum: 10c0/bb102d570be8592c23f4ea72d7df9daa50c7792eb0cf1c5d7e506c1706e7426a4e4ae48a35b109e91c85f1c0ec63774a21ae252b66f4eb981cb8efef7d0463c8 + checksum: 10c0/cb6f3ac49180be03ff07ba3ff125f9eba2ff0b277fb33c7fc47569fc5e616882c5b1c69b9904c4c4187e97dd0419dd03b134174756f296dec62041e6527e2c6e languageName: node linkType: hard @@ -4767,6 +4741,15 @@ __metadata: languageName: node linkType: hard +"fs-minipass@npm:^2.0.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 + languageName: node + linkType: hard + "fs-minipass@npm:^3.0.0": version: 3.0.3 resolution: "fs-minipass@npm:3.0.3" @@ -4857,13 +4840,13 @@ __metadata: linkType: hard "get-east-asian-width@npm:^1.0.0": - version: 1.3.0 - resolution: "get-east-asian-width@npm:1.3.0" - checksum: 10c0/1a049ba697e0f9a4d5514c4623781c5246982bdb61082da6b5ae6c33d838e52ce6726407df285cdbb27ec1908b333cf2820989bd3e986e37bb20979437fdf34b + version: 1.2.0 + resolution: "get-east-asian-width@npm:1.2.0" + checksum: 10c0/914b1e217cf38436c24b4c60b4c45289e39a45bf9e65ef9fd343c2815a1a02b8a0215aeec8bf9c07c516089004b6e3826332481f40a09529fcadbf6e579f286b languageName: node linkType: hard -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7": +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7": version: 1.2.7 resolution: "get-intrinsic@npm:1.2.7" dependencies: @@ -4961,7 +4944,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1": +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.1": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -4986,7 +4969,7 @@ __metadata: languageName: node linkType: hard -"globals@npm:15.13.0": +"globals@npm:15.13.0, globals@npm:^15.11.0": version: 15.13.0 resolution: "globals@npm:15.13.0" checksum: 10c0/640365115ca5f81d91e6a7667f4935021705e61a1a5a76a6ec5c3a5cdf6e53f165af7f9db59b7deb65cf2e1f83d03ac8d6660d0b14c569c831a9b6483eeef585 @@ -5007,13 +4990,6 @@ __metadata: languageName: node linkType: hard -"globals@npm:^15.11.0": - version: 15.14.0 - resolution: "globals@npm:15.14.0" - checksum: 10c0/039deb8648bd373b7940c15df9f96ab7508fe92b31bbd39cbd1c1a740bd26db12457aa3e5d211553b234f30e9b1db2fee3683012f543a01a6942c9062857facb - languageName: node - linkType: hard - "globalthis@npm:^1.0.4": version: 1.0.4 resolution: "globalthis@npm:1.0.4" @@ -5046,9 +5022,9 @@ __metadata: linkType: hard "graphql@npm:^16.8.1": - version: 16.10.0 - resolution: "graphql@npm:16.10.0" - checksum: 10c0/303730675538c8bd6c76b447dc6f03e61242e2d2596b408c34759666ec4877409e5593a7a0467d590ac5407b8c663b093b599556a77f24f281abea69ddc53de6 + version: 16.9.0 + resolution: "graphql@npm:16.9.0" + checksum: 10c0/a8850f077ff767377237d1f8b1da2ec70aeb7623cdf1dfc9e1c7ae93accc0c8149c85abe68923be9871a2934b1bce5a2496f846d4d56e1cfb03eaaa7ddba9b6a languageName: node linkType: hard @@ -5089,7 +5065,7 @@ __metadata: languageName: node linkType: hard -"has-proto@npm:^1.2.0": +"has-proto@npm:^1.0.3, has-proto@npm:^1.2.0": version: 1.2.0 resolution: "has-proto@npm:1.2.0" dependencies: @@ -5105,7 +5081,7 @@ __metadata: languageName: node linkType: hard -"has-tostringtag@npm:^1.0.2": +"has-tostringtag@npm:^1.0.0, has-tostringtag@npm:^1.0.2": version: 1.0.2 resolution: "has-tostringtag@npm:1.0.2" dependencies: @@ -5197,12 +5173,12 @@ __metadata: linkType: hard "https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5": - version: 7.0.6 - resolution: "https-proxy-agent@npm:7.0.6" + version: 7.0.5 + resolution: "https-proxy-agent@npm:7.0.5" dependencies: - agent-base: "npm:^7.1.2" + agent-base: "npm:^7.0.2" debug: "npm:4" - checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + checksum: 10c0/2490e3acec397abeb88807db52cac59102d5ed758feee6df6112ab3ccd8325e8a1ce8bce6f4b66e5470eca102d31e425ace904242e4fa28dbe0c59c4bafa7b2c languageName: node linkType: hard @@ -5320,7 +5296,7 @@ __metadata: languageName: node linkType: hard -"internal-slot@npm:^1.1.0": +"internal-slot@npm:^1.0.4, internal-slot@npm:^1.0.7, internal-slot@npm:^1.1.0": version: 1.1.0 resolution: "internal-slot@npm:1.1.0" dependencies: @@ -5349,12 +5325,12 @@ __metadata: linkType: hard "is-arguments@npm:^1.1.1": - version: 1.2.0 - resolution: "is-arguments@npm:1.2.0" + version: 1.1.1 + resolution: "is-arguments@npm:1.1.1" dependencies: - call-bound: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.2" - checksum: 10c0/6377344b31e9fcb707c6751ee89b11f132f32338e6a782ec2eac9393b0cbd32235dad93052998cda778ee058754860738341d8114910d50ada5615912bb929fc + call-bind: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.0" + checksum: 10c0/5ff1f341ee4475350adfc14b2328b38962564b7c2076be2f5bac7bd9b61779efba99b9f844a7b82ba7654adccf8e8eb19d1bb0cc6d1c1a085e498f6793d4328f languageName: node linkType: hard @@ -5377,14 +5353,11 @@ __metadata: linkType: hard "is-async-function@npm:^2.0.0": - version: 2.1.0 - resolution: "is-async-function@npm:2.1.0" + version: 2.0.0 + resolution: "is-async-function@npm:2.0.0" dependencies: - call-bound: "npm:^1.0.3" - get-proto: "npm:^1.0.1" - has-tostringtag: "npm:^1.0.2" - safe-regex-test: "npm:^1.1.0" - checksum: 10c0/5209b858c6d18d88a9fb56dea202a050d53d4b722448cc439fdca859b36e23edf27ee8c18958ba49330f1a71b8846576273f4581e1c0bb9d403738129d852fdb + has-tostringtag: "npm:^1.0.0" + checksum: 10c0/787bc931576aad525d751fc5ce211960fe91e49ac84a5c22d6ae0bc9541945fbc3f686dc590c3175722ce4f6d7b798a93f6f8ff4847fdb2199aea6f4baf5d668 languageName: node linkType: hard @@ -5414,7 +5387,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.0": +"is-core-module@npm:^2.13.0": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" dependencies: @@ -5461,11 +5434,11 @@ __metadata: linkType: hard "is-finalizationregistry@npm:^1.1.0": - version: 1.1.1 - resolution: "is-finalizationregistry@npm:1.1.1" + version: 1.1.0 + resolution: "is-finalizationregistry@npm:1.1.0" dependencies: - call-bound: "npm:^1.0.3" - checksum: 10c0/818dff679b64f19e228a8205a1e2d09989a98e98def3a817f889208cfcbf918d321b251aadf2c05918194803ebd2eb01b14fc9d0b2bea53d984f4137bfca5e97 + call-bind: "npm:^1.0.7" + checksum: 10c0/1cd94236bfb6e060fe2b973c8726a2782727f7d495b3e8e1d51d3e619c5a3345413706f555956eb5b12af15eba0414118f64a1b19d793ec36b5e6767a13836ac languageName: node linkType: hard @@ -5493,14 +5466,11 @@ __metadata: linkType: hard "is-generator-function@npm:^1.0.10": - version: 1.1.0 - resolution: "is-generator-function@npm:1.1.0" + version: 1.0.10 + resolution: "is-generator-function@npm:1.0.10" dependencies: - call-bound: "npm:^1.0.3" - get-proto: "npm:^1.0.0" - has-tostringtag: "npm:^1.0.2" - safe-regex-test: "npm:^1.1.0" - checksum: 10c0/fdfa96c8087bf36fc4cd514b474ba2ff404219a4dd4cfa6cf5426404a1eed259bdcdb98f082a71029a48d01f27733e3436ecc6690129a7ec09cb0434bee03a2a + has-tostringtag: "npm:^1.0.0" + checksum: 10c0/df03514df01a6098945b5a0cfa1abff715807c8e72f57c49a0686ad54b3b74d394e2d8714e6f709a71eb00c9630d48e73ca1796c1ccc84ac95092c1fecc0d98b languageName: node linkType: hard @@ -5523,6 +5493,13 @@ __metadata: languageName: node linkType: hard +"is-lambda@npm:^1.0.1": + version: 1.0.1 + resolution: "is-lambda@npm:1.0.1" + checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d + languageName: node + linkType: hard + "is-map@npm:^2.0.2, is-map@npm:^2.0.3": version: 2.0.3 resolution: "is-map@npm:2.0.3" @@ -5678,12 +5655,12 @@ __metadata: linkType: hard "is-weakset@npm:^2.0.3": - version: 2.0.4 - resolution: "is-weakset@npm:2.0.4" + version: 2.0.3 + resolution: "is-weakset@npm:2.0.3" dependencies: - call-bound: "npm:^1.0.3" - get-intrinsic: "npm:^1.2.6" - checksum: 10c0/6491eba08acb8dc9532da23cb226b7d0192ede0b88f16199e592e4769db0a077119c1f5d2283d1e0d16d739115f70046e887e477eb0e66cd90e1bb29f28ba647 + call-bind: "npm:^1.0.7" + get-intrinsic: "npm:^1.2.4" + checksum: 10c0/8ad6141b6a400e7ce7c7442a13928c676d07b1f315ab77d9912920bf5f4170622f43126f111615788f26c3b1871158a6797c862233124507db0bcc33a9537d1a languageName: node linkType: hard @@ -5763,17 +5740,16 @@ __metadata: languageName: node linkType: hard -"iterator.prototype@npm:^1.1.4": - version: 1.1.5 - resolution: "iterator.prototype@npm:1.1.5" +"iterator.prototype@npm:^1.1.3": + version: 1.1.3 + resolution: "iterator.prototype@npm:1.1.3" dependencies: - define-data-property: "npm:^1.1.4" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.6" - get-proto: "npm:^1.0.0" - has-symbols: "npm:^1.1.0" - set-function-name: "npm:^2.0.2" - checksum: 10c0/f7a262808e1b41049ab55f1e9c29af7ec1025a000d243b83edf34ce2416eedd56079b117fa59376bb4a724110690f13aa8427f2ee29a09eec63a7e72367626d0 + define-properties: "npm:^1.2.1" + get-intrinsic: "npm:^1.2.1" + has-symbols: "npm:^1.0.3" + reflect.getprototypeof: "npm:^1.0.4" + set-function-name: "npm:^2.0.1" + checksum: 10c0/68b0320c14291fbb3d8ed5a17e255d3127e7971bec19108076667e79c9ff4c7d69f99de4b0b3075c789c3f318366d7a0a35bb086eae0f2cf832dd58465b2f9e6 languageName: node linkType: hard @@ -5880,11 +5856,11 @@ __metadata: linkType: hard "jsesc@npm:^3.0.2": - version: 3.1.0 - resolution: "jsesc@npm:3.1.0" + version: 3.0.2 + resolution: "jsesc@npm:3.0.2" bin: jsesc: bin/jsesc - checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + checksum: 10c0/ef22148f9e793180b14d8a145ee6f9f60f301abf443288117b4b6c53d0ecd58354898dc506ccbb553a5f7827965cd38bc5fb726575aae93c5e8915e2de8290e1 languageName: node linkType: hard @@ -6210,11 +6186,11 @@ __metadata: linkType: hard "magic-string@npm:^0.30.12": - version: 0.30.17 - resolution: "magic-string@npm:0.30.17" + version: 0.30.15 + resolution: "magic-string@npm:0.30.15" dependencies: "@jridgewell/sourcemap-codec": "npm:^1.5.0" - checksum: 10c0/16826e415d04b88378f200fe022b53e638e3838b9e496edda6c0e086d7753a44a6ed187adc72d19f3623810589bf139af1a315541cd6a26ae0771a0193eaf7b8 + checksum: 10c0/7d10403cb0b403c0453d7af57d8d01a58c334b260e64653c5f5c2311800f4c6b1b7f4502153f9051dd8a87116acd50e5e3fce4bf79ec9d7127f087aa1c08b96b languageName: node linkType: hard @@ -6238,22 +6214,23 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^14.0.3": - version: 14.0.3 - resolution: "make-fetch-happen@npm:14.0.3" +"make-fetch-happen@npm:^13.0.0": + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" dependencies: - "@npmcli/agent": "npm:^3.0.0" - cacache: "npm:^19.0.1" + "@npmcli/agent": "npm:^2.0.0" + cacache: "npm:^18.0.0" http-cache-semantics: "npm:^4.1.1" + is-lambda: "npm:^1.0.1" minipass: "npm:^7.0.2" - minipass-fetch: "npm:^4.0.0" + minipass-fetch: "npm:^3.0.0" minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^1.0.0" - proc-log: "npm:^5.0.0" + negotiator: "npm:^0.6.3" + proc-log: "npm:^4.2.0" promise-retry: "npm:^2.0.1" - ssri: "npm:^12.0.0" - checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 + ssri: "npm:^10.0.0" + checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e languageName: node linkType: hard @@ -6330,7 +6307,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.8": +"micromatch@npm:^4.0.4": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -6450,18 +6427,18 @@ __metadata: languageName: node linkType: hard -"minipass-fetch@npm:^4.0.0": - version: 4.0.0 - resolution: "minipass-fetch@npm:4.0.0" +"minipass-fetch@npm:^3.0.0": + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" dependencies: encoding: "npm:^0.1.13" minipass: "npm:^7.0.3" minipass-sized: "npm:^1.0.3" - minizlib: "npm:^3.0.1" + minizlib: "npm:^2.1.2" dependenciesMeta: encoding: optional: true - checksum: 10c0/7fa30ce7c373fb6f94c086b374fff1589fd7e78451855d2d06c2e2d9df936d131e73e952163063016592ed3081444bd8d1ea608533313b0149156ce23311da4b + checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b languageName: node linkType: hard @@ -6501,29 +6478,36 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": version: 7.1.2 resolution: "minipass@npm:7.1.2" checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 languageName: node linkType: hard -"minizlib@npm:^3.0.1": - version: 3.0.1 - resolution: "minizlib@npm:3.0.1" +"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" dependencies: - minipass: "npm:^7.0.4" - rimraf: "npm:^5.0.5" - checksum: 10c0/82f8bf70da8af656909a8ee299d7ed3b3372636749d29e105f97f20e88971be31f5ed7642f2e898f00283b68b701cc01307401cdc209b0efc5dd3818220e5093 + minipass: "npm:^3.0.0" + yallist: "npm:^4.0.0" + checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 languageName: node linkType: hard -"mkdirp@npm:^3.0.1": - version: 3.0.1 - resolution: "mkdirp@npm:3.0.1" +"mkdirp@npm:^1.0.3": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" bin: - mkdirp: dist/cjs/src/bin.js - checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d + mkdirp: bin/cmd.js + checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf languageName: node linkType: hard @@ -6598,11 +6582,11 @@ __metadata: linkType: hard "nanoid@npm:^3.3.7": - version: 3.3.8 - resolution: "nanoid@npm:3.3.8" + version: 3.3.7 + resolution: "nanoid@npm:3.3.7" bin: nanoid: bin/nanoid.cjs - checksum: 10c0/4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120 + checksum: 10c0/e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3 languageName: node linkType: hard @@ -6613,37 +6597,30 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:0.6.3": +"negotiator@npm:0.6.3, negotiator@npm:^0.6.3": version: 0.6.3 resolution: "negotiator@npm:0.6.3" checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 languageName: node linkType: hard -"negotiator@npm:^1.0.0": - version: 1.0.0 - resolution: "negotiator@npm:1.0.0" - checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b - languageName: node - linkType: hard - "node-gyp@npm:latest": - version: 11.0.0 - resolution: "node-gyp@npm:11.0.0" + version: 10.2.0 + resolution: "node-gyp@npm:10.2.0" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" glob: "npm:^10.3.10" graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^14.0.3" - nopt: "npm:^8.0.0" - proc-log: "npm:^5.0.0" + make-fetch-happen: "npm:^13.0.0" + nopt: "npm:^7.0.0" + proc-log: "npm:^4.1.0" semver: "npm:^7.3.5" - tar: "npm:^7.4.3" - which: "npm:^5.0.0" + tar: "npm:^6.2.1" + which: "npm:^4.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10c0/a3b885bbee2d271f1def32ba2e30ffcf4562a3db33af06b8b365e053153e2dd2051b9945783c3c8e852d26a0f20f65b251c7e83361623383a99635c0280ee573 + checksum: 10c0/00630d67dbd09a45aee0a5d55c05e3916ca9e6d427ee4f7bc392d2d3dc5fad7449b21fc098dd38260a53d9dcc9c879b36704a1994235d4707e7271af7e9a835b languageName: node linkType: hard @@ -6654,14 +6631,14 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^8.0.0": - version: 8.0.0 - resolution: "nopt@npm:8.0.0" +"nopt@npm:^7.0.0": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" dependencies: abbrev: "npm:^2.0.0" bin: nopt: bin/nopt.js - checksum: 10c0/19cb986f79abaca2d0f0b560021da7b32ee6fcc3de48f3eaeb0c324d36755c17754f886a754c091f01f740c17caf7d6aea8237b7fbaf39f476ae5e30a249f18f + checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81 languageName: node linkType: hard @@ -6759,14 +6736,13 @@ __metadata: linkType: hard "object.values@npm:^1.1.6, object.values@npm:^1.2.0": - version: 1.2.1 - resolution: "object.values@npm:1.2.1" + version: 1.2.0 + resolution: "object.values@npm:1.2.0" dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bind: "npm:^1.0.7" define-properties: "npm:^1.2.1" es-object-atoms: "npm:^1.0.0" - checksum: 10c0/3c47814fdc64842ae3d5a74bc9d06bdd8d21563c04d9939bf6716a9c00596a4ebc342552f8934013d1ec991c74e3671b26710a0c51815f0b603795605ab6b2c9 + checksum: 10c0/15809dc40fd6c5529501324fec5ff08570b7d70fb5ebbe8e2b3901afec35cf2b3dc484d1210c6c642cd3e7e0a5e18dd1d6850115337fef46bdae14ab0cb18ac3 languageName: node linkType: hard @@ -6859,6 +6835,7 @@ __metadata: browserslist: "npm:4.24.2" browserslist-to-esbuild: "npm:2.1.1" chart.js: "npm:4.4.1" + chartjs-plugin-annotation: "npm:3.1.0" chartjs-plugin-zoom: "npm:2.2.0" cross-env: "npm:7.0.3" cypress: "npm:13.16.0" @@ -6965,17 +6942,10 @@ __metadata: languageName: node linkType: hard -"p-map@npm:^7.0.2": - version: 7.0.3 - resolution: "p-map@npm:7.0.3" - checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c - languageName: node - linkType: hard - "package-json-from-dist@npm:^1.0.0": - version: 1.0.1 - resolution: "package-json-from-dist@npm:1.0.1" - checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + version: 1.0.0 + resolution: "package-json-from-dist@npm:1.0.0" + checksum: 10c0/e3ffaf6ac1040ab6082a658230c041ad14e72fabe99076a2081bb1d5d41210f11872403fc09082daf4387fc0baa6577f96c9c0e94c90c394fd57794b66aa4033 languageName: node linkType: hard @@ -7001,11 +6971,11 @@ __metadata: linkType: hard "parse5@npm:^7.1.2": - version: 7.2.1 - resolution: "parse5@npm:7.2.1" + version: 7.1.2 + resolution: "parse5@npm:7.1.2" dependencies: - entities: "npm:^4.5.0" - checksum: 10c0/829d37a0c709215a887e410a7118d754f8e1afd7edb529db95bc7bbf8045fb0266a7b67801331d8e8d9d073ea75793624ec27ce9ff3b96862c3b9008f4d68e80 + entities: "npm:^4.4.0" + checksum: 10c0/297d7af8224f4b5cb7f6617ecdae98eeaed7f8cbd78956c42785e230505d5a4f07cef352af10d3006fa5c1544b76b57784d3a22d861ae071bbc460c649482bf4 languageName: node linkType: hard @@ -7126,7 +7096,7 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.0, picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": +"picocolors@npm:^1.0.0, picocolors@npm:^1.0.1, picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 @@ -7188,13 +7158,13 @@ __metadata: linkType: hard "postcss@npm:^8.4.43": - version: 8.4.49 - resolution: "postcss@npm:8.4.49" + version: 8.4.45 + resolution: "postcss@npm:8.4.45" dependencies: nanoid: "npm:^3.3.7" - picocolors: "npm:^1.1.1" - source-map-js: "npm:^1.2.1" - checksum: 10c0/f1b3f17aaf36d136f59ec373459f18129908235e65dbdc3aee5eef8eba0756106f52de5ec4682e29a2eab53eb25170e7e871b3e4b52a8f1de3d344a514306be3 + picocolors: "npm:^1.0.1" + source-map-js: "npm:^1.2.0" + checksum: 10c0/ad6f8b9b1157d678560373696109745ab97a947d449f8a997acac41c7f1e4c0f3ca4b092d6df1387f430f2c9a319987b1780dbdc27e35800a88cde9b606c1e8f languageName: node linkType: hard @@ -7241,10 +7211,10 @@ __metadata: languageName: node linkType: hard -"proc-log@npm:^5.0.0": - version: 5.0.0 - resolution: "proc-log@npm:5.0.0" - checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3 +"proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 languageName: node linkType: hard @@ -7312,21 +7282,19 @@ __metadata: linkType: hard "psl@npm:^1.1.33": - version: 1.15.0 - resolution: "psl@npm:1.15.0" - dependencies: - punycode: "npm:^2.3.1" - checksum: 10c0/d8d45a99e4ca62ca12ac3c373e63d80d2368d38892daa40cfddaa1eb908be98cd549ac059783ef3a56cfd96d57ae8e2fd9ae53d1378d90d42bc661ff924e102a + version: 1.9.0 + resolution: "psl@npm:1.9.0" + checksum: 10c0/6a3f805fdab9442f44de4ba23880c4eba26b20c8e8e0830eff1cb31007f6825dace61d17203c58bfe36946842140c97a1ba7f67bc63ca2d88a7ee052b65d97ab languageName: node linkType: hard "pump@npm:^3.0.0": - version: 3.0.2 - resolution: "pump@npm:3.0.2" + version: 3.0.0 + resolution: "pump@npm:3.0.0" dependencies: end-of-stream: "npm:^1.1.0" once: "npm:^1.3.1" - checksum: 10c0/5ad655cb2a7738b4bcf6406b24ad0970d680649d996b55ad20d1be8e0c02394034e4c45ff7cd105d87f1e9b96a0e3d06fd28e11fae8875da26e7f7a8e2c9726f + checksum: 10c0/bbdeda4f747cdf47db97428f3a135728669e56a0ae5f354a9ac5b74556556f5446a46f720a8f14ca2ece5be9b4d5d23c346db02b555f46739934cc6c093a5478 languageName: node linkType: hard @@ -7466,13 +7434,6 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^19.0.0": - version: 19.0.0 - resolution: "react-is@npm:19.0.0" - checksum: 10c0/d1be8e8500cf04f76df71942a21ef3a71266397a383d7ec8885f35190df818d35c65efd35aed7be47a89ad99aaff2c52e0c4e39e8930844a6b997622e50625a8 - languageName: node - linkType: hard - "react-redux@npm:9.2.0, react-redux@npm:^9.1.2": version: 9.2.0 resolution: "react-redux@npm:9.2.0" @@ -7549,7 +7510,7 @@ __metadata: languageName: node linkType: hard -"reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": +"reflect.getprototypeof@npm:^1.0.4, reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": version: 1.0.10 resolution: "reflect.getprototypeof@npm:1.0.10" dependencies: @@ -7572,7 +7533,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.3": +"regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.2, regexp.prototype.flags@npm:^1.5.3": version: 1.5.4 resolution: "regexp.prototype.flags@npm:1.5.4" dependencies: @@ -7650,15 +7611,15 @@ __metadata: linkType: hard "resolve@npm:^1.19.0": - version: 1.22.10 - resolution: "resolve@npm:1.22.10" + version: 1.22.8 + resolution: "resolve@npm:1.22.8" dependencies: - is-core-module: "npm:^2.16.0" + is-core-module: "npm:^2.13.0" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10c0/8967e1f4e2cc40f79b7e080b4582b9a8c5ee36ffb46041dccb20e6461161adf69f843b43067b4a375de926a2cd669157e29a29578191def399dd5ef89a1b5203 + checksum: 10c0/07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a languageName: node linkType: hard @@ -7676,15 +7637,15 @@ __metadata: linkType: hard "resolve@patch:resolve@npm%3A^1.19.0#optional!builtin": - version: 1.22.10 - resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" + version: 1.22.8 + resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" dependencies: - is-core-module: "npm:^2.16.0" + is-core-module: "npm:^2.13.0" path-parse: "npm:^1.0.7" supports-preserve-symlinks-flag: "npm:^1.0.0" bin: resolve: bin/resolve - checksum: 10c0/52a4e505bbfc7925ac8f4cd91fd8c4e096b6a89728b9f46861d3b405ac9a1ccf4dcbf8befb4e89a2e11370dacd0160918163885cbc669369590f2f31f4c58939 + checksum: 10c0/0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 languageName: node linkType: hard @@ -7742,41 +7703,27 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^5.0.5": - version: 5.0.10 - resolution: "rimraf@npm:5.0.10" - dependencies: - glob: "npm:^10.3.7" - bin: - rimraf: dist/esm/bin.mjs - checksum: 10c0/7da4fd0e15118ee05b918359462cfa1e7fe4b1228c7765195a45b55576e8c15b95db513b8466ec89129666f4af45ad978a3057a02139afba1a63512a2d9644cc - languageName: node - linkType: hard - "rollup@npm:^4.20.0": - version: 4.30.0 - resolution: "rollup@npm:4.30.0" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.30.0" - "@rollup/rollup-android-arm64": "npm:4.30.0" - "@rollup/rollup-darwin-arm64": "npm:4.30.0" - "@rollup/rollup-darwin-x64": "npm:4.30.0" - "@rollup/rollup-freebsd-arm64": "npm:4.30.0" - "@rollup/rollup-freebsd-x64": "npm:4.30.0" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.30.0" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.30.0" - "@rollup/rollup-linux-arm64-gnu": "npm:4.30.0" - "@rollup/rollup-linux-arm64-musl": "npm:4.30.0" - "@rollup/rollup-linux-loongarch64-gnu": "npm:4.30.0" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.30.0" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.30.0" - "@rollup/rollup-linux-s390x-gnu": "npm:4.30.0" - "@rollup/rollup-linux-x64-gnu": "npm:4.30.0" - "@rollup/rollup-linux-x64-musl": "npm:4.30.0" - "@rollup/rollup-win32-arm64-msvc": "npm:4.30.0" - "@rollup/rollup-win32-ia32-msvc": "npm:4.30.0" - "@rollup/rollup-win32-x64-msvc": "npm:4.30.0" - "@types/estree": "npm:1.0.6" + version: 4.22.4 + resolution: "rollup@npm:4.22.4" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.22.4" + "@rollup/rollup-android-arm64": "npm:4.22.4" + "@rollup/rollup-darwin-arm64": "npm:4.22.4" + "@rollup/rollup-darwin-x64": "npm:4.22.4" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.22.4" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.22.4" + "@rollup/rollup-linux-arm64-gnu": "npm:4.22.4" + "@rollup/rollup-linux-arm64-musl": "npm:4.22.4" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.22.4" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.22.4" + "@rollup/rollup-linux-s390x-gnu": "npm:4.22.4" + "@rollup/rollup-linux-x64-gnu": "npm:4.22.4" + "@rollup/rollup-linux-x64-musl": "npm:4.22.4" + "@rollup/rollup-win32-arm64-msvc": "npm:4.22.4" + "@rollup/rollup-win32-ia32-msvc": "npm:4.22.4" + "@rollup/rollup-win32-x64-msvc": "npm:4.22.4" + "@types/estree": "npm:1.0.5" fsevents: "npm:~2.3.2" dependenciesMeta: "@rollup/rollup-android-arm-eabi": @@ -7787,10 +7734,6 @@ __metadata: optional: true "@rollup/rollup-darwin-x64": optional: true - "@rollup/rollup-freebsd-arm64": - optional: true - "@rollup/rollup-freebsd-x64": - optional: true "@rollup/rollup-linux-arm-gnueabihf": optional: true "@rollup/rollup-linux-arm-musleabihf": @@ -7799,8 +7742,6 @@ __metadata: optional: true "@rollup/rollup-linux-arm64-musl": optional: true - "@rollup/rollup-linux-loongarch64-gnu": - optional: true "@rollup/rollup-linux-powerpc64le-gnu": optional: true "@rollup/rollup-linux-riscv64-gnu": @@ -7821,7 +7762,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/72d01bda640ca075d98c52fa103af05091366377433b1e5be481472e3ef9b5c656aef9f0118d4187ece9c26d998557786523f228fae75caf02e0330624e793a9 + checksum: 10c0/4c96b6e2e0c5dbe73b4ba899cea894a05115ab8c65ccff631fbbb944e2b3a9f2eb3b99c2dce3dd91b179647df1892ffc44ecee29381ccf155ba8000b22712a32 languageName: node linkType: hard @@ -7850,7 +7791,7 @@ __metadata: languageName: node linkType: hard -"safe-array-concat@npm:^1.1.3": +"safe-array-concat@npm:^1.1.2, safe-array-concat@npm:^1.1.3": version: 1.1.3 resolution: "safe-array-concat@npm:1.1.3" dependencies: @@ -8058,7 +7999,7 @@ __metadata: languageName: node linkType: hard -"set-function-name@npm:^2.0.2": +"set-function-name@npm:^2.0.1, set-function-name@npm:^2.0.2": version: 2.0.2 resolution: "set-function-name@npm:2.0.2" dependencies: @@ -8241,13 +8182,13 @@ __metadata: linkType: hard "socks-proxy-agent@npm:^8.0.3": - version: 8.0.5 - resolution: "socks-proxy-agent@npm:8.0.5" + version: 8.0.4 + resolution: "socks-proxy-agent@npm:8.0.4" dependencies: - agent-base: "npm:^7.1.2" + agent-base: "npm:^7.1.1" debug: "npm:^4.3.4" socks: "npm:^2.8.3" - checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + checksum: 10c0/345593bb21b95b0508e63e703c84da11549f0a2657d6b4e3ee3612c312cb3a907eac10e53b23ede3557c6601d63252103494caa306b66560f43af7b98f53957a languageName: node linkType: hard @@ -8261,7 +8202,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": +"source-map-js@npm:^1.2.0": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf @@ -8312,12 +8253,12 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^12.0.0": - version: 12.0.0 - resolution: "ssri@npm:12.0.0" +"ssri@npm:^10.0.0": + version: 10.0.6 + resolution: "ssri@npm:10.0.6" dependencies: minipass: "npm:^7.0.3" - checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d + checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227 languageName: node linkType: hard @@ -8363,12 +8304,11 @@ __metadata: linkType: hard "stop-iteration-iterator@npm:^1.0.0": - version: 1.1.0 - resolution: "stop-iteration-iterator@npm:1.1.0" + version: 1.0.0 + resolution: "stop-iteration-iterator@npm:1.0.0" dependencies: - es-errors: "npm:^1.3.0" - internal-slot: "npm:^1.1.0" - checksum: 10c0/de4e45706bb4c0354a4b1122a2b8cc45a639e86206807ce0baf390ee9218d3ef181923fa4d2b67443367c491aa255c5fbaa64bb74648e3c5b48299928af86c09 + internal-slot: "npm:^1.0.4" + checksum: 10c0/c4158d6188aac510d9e92925b58709207bd94699e9c31186a040c80932a687f84a51356b5895e6dc72710aad83addb9411c22171832c9ae0e6e11b7d61b0dfb9 languageName: node linkType: hard @@ -8440,23 +8380,22 @@ __metadata: linkType: hard "string.prototype.matchall@npm:^4.0.11": - version: 4.0.12 - resolution: "string.prototype.matchall@npm:4.0.12" + version: 4.0.11 + resolution: "string.prototype.matchall@npm:4.0.11" dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bind: "npm:^1.0.7" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.6" + es-abstract: "npm:^1.23.2" es-errors: "npm:^1.3.0" es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.6" - gopd: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - internal-slot: "npm:^1.1.0" - regexp.prototype.flags: "npm:^1.5.3" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-symbols: "npm:^1.0.3" + internal-slot: "npm:^1.0.7" + regexp.prototype.flags: "npm:^1.5.2" set-function-name: "npm:^2.0.2" - side-channel: "npm:^1.1.0" - checksum: 10c0/1a53328ada73f4a77f1fdf1c79414700cf718d0a8ef6672af5603e709d26a24f2181208144aed7e858b1bcc1a0d08567a570abfb45567db4ae47637ed2c2f85c + side-channel: "npm:^1.0.6" + checksum: 10c0/915a2562ac9ab5e01b7be6fd8baa0b2b233a0a9aa975fcb2ec13cc26f08fb9a3e85d5abdaa533c99c6fc4c5b65b914eba3d80c4aff9792a4c9fed403f28f7d9d languageName: node linkType: hard @@ -8603,26 +8542,26 @@ __metadata: linkType: hard "synckit@npm:^0.9.1": - version: 0.9.2 - resolution: "synckit@npm:0.9.2" + version: 0.9.1 + resolution: "synckit@npm:0.9.1" dependencies: "@pkgr/core": "npm:^0.1.0" tslib: "npm:^2.6.2" - checksum: 10c0/e0c262817444e5b872708adb6f5ad37951ba33f6b2d1d4477d45db1f57573a784618ceed5e6614e0225db330632b1f6b95bb74d21e4d013e45ad4bde03d0cb59 + checksum: 10c0/d8b89e1bf30ba3ffb469d8418c836ad9c0c062bf47028406b4d06548bc66af97155ea2303b96c93bf5c7c0f0d66153a6fbd6924c76521b434e6a9898982abc2e languageName: node linkType: hard -"tar@npm:^7.4.3": - version: 7.4.3 - resolution: "tar@npm:7.4.3" +"tar@npm:^6.1.11, tar@npm:^6.2.1": + version: 6.2.1 + resolution: "tar@npm:6.2.1" dependencies: - "@isaacs/fs-minipass": "npm:^4.0.0" - chownr: "npm:^3.0.0" - minipass: "npm:^7.1.2" - minizlib: "npm:^3.0.1" - mkdirp: "npm:^3.0.1" - yallist: "npm:^5.0.0" - checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d + chownr: "npm:^2.0.0" + fs-minipass: "npm:^2.0.0" + minipass: "npm:^5.0.0" + minizlib: "npm:^2.1.1" + mkdirp: "npm:^1.0.3" + yallist: "npm:^4.0.0" + checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 languageName: node linkType: hard @@ -8666,9 +8605,9 @@ __metadata: linkType: hard "tinyexec@npm:^0.3.1": - version: 0.3.2 - resolution: "tinyexec@npm:0.3.2" - checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 + version: 0.3.1 + resolution: "tinyexec@npm:0.3.1" + checksum: 10c0/11e7a7c5d8b3bddf8b5cbe82a9290d70a6fad84d528421d5d18297f165723cb53d2e737d8f58dcce5ca56f2e4aa2d060f02510b1f8971784f97eb3e9aec28f09 languageName: node linkType: hard @@ -8693,21 +8632,21 @@ __metadata: languageName: node linkType: hard -"tldts-core@npm:^6.1.71": - version: 6.1.71 - resolution: "tldts-core@npm:6.1.71" - checksum: 10c0/68c4e9ea7f02f14f811f5be5b50b176b099bc8385cae177b8265c1bb0c45215efecf54195a267326848024a24e204bba6d9f47cb899da1d81fd00da0c7f661b7 +"tldts-core@npm:^6.1.66": + version: 6.1.66 + resolution: "tldts-core@npm:6.1.66" + checksum: 10c0/8bc781d8e209db1c776d5c3b816bdd14dade27078756ce616a71760f9cf3859ac3cb251afd6687163cda434047045077d6558cb7058adef53f525611ddde061c languageName: node linkType: hard "tldts@npm:^6.1.32": - version: 6.1.71 - resolution: "tldts@npm:6.1.71" + version: 6.1.66 + resolution: "tldts@npm:6.1.66" dependencies: - tldts-core: "npm:^6.1.71" + tldts-core: "npm:^6.1.66" bin: tldts: bin/cli.js - checksum: 10c0/fb1bfb6ec78ce334b9d7b0c8813ab553a9f9f8759d681e6f109dd55caced45f901a19d162d8bc2f12b37afc366e438154248ae1dab67ad091565146b8e57d217 + checksum: 10c0/2b195dcda6b309666d24b1157f88a92167cf4b5c1504a84838a495d02c4c11c672e149924ca4ef8b087132395b6df0e651daa7e031c087105c020bccfd5574ba languageName: node linkType: hard @@ -8774,18 +8713,18 @@ __metadata: linkType: hard "ts-api-utils@npm:^1.3.0": - version: 1.4.3 - resolution: "ts-api-utils@npm:1.4.3" + version: 1.3.0 + resolution: "ts-api-utils@npm:1.3.0" peerDependencies: typescript: ">=4.2.0" - checksum: 10c0/e65dc6e7e8141140c23e1dc94984bf995d4f6801919c71d6dc27cf0cd51b100a91ffcfe5217626193e5bea9d46831e8586febdc7e172df3f1091a7384299e23a + checksum: 10c0/f54a0ba9ed56ce66baea90a3fa087a484002e807f28a8ccb2d070c75e76bde64bd0f6dce98b3802834156306050871b67eec325cb4e918015a360a3f0868c77c languageName: node linkType: hard "tslib@npm:^2.1.0, tslib@npm:^2.6.2": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + version: 2.7.0 + resolution: "tslib@npm:2.7.0" + checksum: 10c0/469e1d5bf1af585742128827000711efa61010b699cb040ab1800bcd3ccdd37f63ec30642c9e07c4439c1db6e46345582614275daca3e0f4abae29b0083f04a6 languageName: node linkType: hard @@ -8829,9 +8768,9 @@ __metadata: linkType: hard "type-fest@npm:^4.26.1": - version: 4.31.0 - resolution: "type-fest@npm:4.31.0" - checksum: 10c0/a5bb69e3b0f82e068af8c645ac3d50b1fa5c588ebc83735a6add4ef6dacf277bb3605801f66c72c069af20120ee7387a3ae6dd84e12c152f5982784c710b4051 + version: 4.30.0 + resolution: "type-fest@npm:4.30.0" + checksum: 10c0/9441fbbc971f92a53d7dfdb0db3f9c71a5a33ac3e021ca605cba8ad0b5c0a1e191cc778b4980c534b098ccb4e3322809100baf763be125510c993c9b8361f60e languageName: node linkType: hard @@ -8951,21 +8890,21 @@ __metadata: languageName: node linkType: hard -"unique-filename@npm:^4.0.0": - version: 4.0.0 - resolution: "unique-filename@npm:4.0.0" +"unique-filename@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-filename@npm:3.0.0" dependencies: - unique-slug: "npm:^5.0.0" - checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc + unique-slug: "npm:^4.0.0" + checksum: 10c0/6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f languageName: node linkType: hard -"unique-slug@npm:^5.0.0": - version: 5.0.0 - resolution: "unique-slug@npm:5.0.0" +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" dependencies: imurmurhash: "npm:^0.1.4" - checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293 + checksum: 10c0/cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635 languageName: node linkType: hard @@ -9271,12 +9210,12 @@ __metadata: linkType: hard "whatwg-url@npm:^14.0.0": - version: 14.1.0 - resolution: "whatwg-url@npm:14.1.0" + version: 14.0.0 + resolution: "whatwg-url@npm:14.0.0" dependencies: tr46: "npm:^5.0.0" webidl-conversions: "npm:^7.0.0" - checksum: 10c0/f00104f1c67ce086ba8ffedab529cbbd9aefd8c0a6555320026de7aeff31f91c38680f95818b140a7c9cc657cde3781e567835dda552ddb1e2b8faaba0ac3cb6 + checksum: 10c0/ac32e9ba9d08744605519bbe9e1371174d36229689ecc099157b6ba102d4251a95e81d81f3d80271eb8da182eccfa65653f07f0ab43ea66a6934e643fd091ba9 languageName: node linkType: hard @@ -9351,14 +9290,14 @@ __metadata: languageName: node linkType: hard -"which@npm:^5.0.0": - version: 5.0.0 - resolution: "which@npm:5.0.0" +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" dependencies: isexe: "npm:^3.1.1" bin: node-which: bin/which.js - checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b + checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a languageName: node linkType: hard @@ -9491,13 +9430,6 @@ __metadata: languageName: node linkType: hard -"yallist@npm:^5.0.0": - version: 5.0.0 - resolution: "yallist@npm:5.0.0" - checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 - languageName: node - linkType: hard - "yaml@npm:2.3.4": version: 2.3.4 resolution: "yaml@npm:2.3.4"