Adding a component to take care of translations and scalings around `<TileSet>` components (not used yeti but should simplify `<LayerStack>`).
This commit is contained in:
parent
7322e5f5c4
commit
ef35ffca32
|
@ -0,0 +1,165 @@
|
||||||
|
import { renderHook, act, render, screen } from '@testing-library/react';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
|
import LayerStack from './LayerStack';
|
||||||
|
import { coordinateSystemAtom, relativeCoordinateSystemAtom } from './Map';
|
||||||
|
import TiledLayer from './TiledLayer';
|
||||||
|
|
||||||
|
describe('The TiledLayer component', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
globalThis.cacheForTileSet = new Map();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('generates an empty layer if inactive', () => {
|
||||||
|
// const { result } = renderHook(() => useAtom(tiledLayersAtom));
|
||||||
|
render(
|
||||||
|
<svg data-testid='tiled-layer'>
|
||||||
|
<TiledLayer
|
||||||
|
keyObject={{ provider: 'fake', zoomLevel: 5, x: 2, y: 3 }}
|
||||||
|
shift={{ x: 0.5, y: 0.25 }}
|
||||||
|
zoom={4}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
const svg = screen.getByTestId('tiled-layer');
|
||||||
|
expect(svg).toMatchInlineSnapshot(`
|
||||||
|
<svg
|
||||||
|
data-testid="tiled-layer"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
transform="scale(4) translate(0.5, 0.25)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
test('generates a populated layer if active', () => {
|
||||||
|
// const { result } = renderHook(() => useAtom(tiledLayersAtom));
|
||||||
|
render(
|
||||||
|
<svg data-testid='tiled-layer'>
|
||||||
|
<TiledLayer
|
||||||
|
keyObject={{ provider: 'fake', zoomLevel: 5, x: 2, y: 3 }}
|
||||||
|
shift={{ x: 0, y: 0 }}
|
||||||
|
zoom={1}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
const svg = screen.getByTestId('tiled-layer');
|
||||||
|
expect(svg).toMatchInlineSnapshot(`
|
||||||
|
<svg
|
||||||
|
data-testid="tiled-layer"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
transform="scale(1) translate(0, 0)"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
transform="translate(2, 3)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/2/3.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(3, 3)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/3/3.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(4, 3)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/4/3.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(5, 3)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/5/3.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(2, 4)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/2/4.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(3, 4)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/3/4.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(4, 4)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/4/4.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(5, 4)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/5/4.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(2, 5)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/2/5.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(3, 5)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/3/5.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(4, 5)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/4/5.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
transform="translate(5, 5)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
height="1"
|
||||||
|
href="https://fakeurl/5/5/5.png"
|
||||||
|
width="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
|
import react from 'react';
|
||||||
|
import TileSet from './TileSet';
|
||||||
|
import { Point, TileKeyObject } from './types';
|
||||||
|
import { coordinateSystemAtom } from './Map';
|
||||||
|
|
||||||
|
export interface TiledLayerProperties {
|
||||||
|
/**
|
||||||
|
* A key identifying the top left tile
|
||||||
|
*/
|
||||||
|
keyObject: TileKeyObject;
|
||||||
|
/**
|
||||||
|
* A flag to indicate if the layer is active and should add tiles which are in its viewport or if it is "only" a backup.
|
||||||
|
*/
|
||||||
|
shift: Point;
|
||||||
|
/**
|
||||||
|
* The zoom to apply. If equal to 1, the layer is considered active and should add tiles which are in its viewport
|
||||||
|
*/
|
||||||
|
zoom: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param props
|
||||||
|
* @returns A layer of tiles.
|
||||||
|
* This component wraps a `<TileSet>` in an SVG `<g>` element taking care of the scale and translation.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const TiledLayer: react.FC<TiledLayerProperties> = (
|
||||||
|
props: TiledLayerProperties
|
||||||
|
) => {
|
||||||
|
const [coordinateSystem] = useAtom(coordinateSystemAtom);
|
||||||
|
const viewPort =
|
||||||
|
props.zoom === 1
|
||||||
|
? {
|
||||||
|
topLeft: {
|
||||||
|
x:
|
||||||
|
props.keyObject.x +
|
||||||
|
Math.floor(
|
||||||
|
-coordinateSystem.shift.x / coordinateSystem.zoom / 256
|
||||||
|
),
|
||||||
|
y:
|
||||||
|
props.keyObject.y +
|
||||||
|
Math.floor(
|
||||||
|
-coordinateSystem.shift.y / coordinateSystem.zoom / 256
|
||||||
|
),
|
||||||
|
},
|
||||||
|
bottomRight: {
|
||||||
|
x:
|
||||||
|
props.keyObject.x +
|
||||||
|
Math.ceil(
|
||||||
|
(-coordinateSystem.shift.x + window.innerWidth) /
|
||||||
|
coordinateSystem.zoom /
|
||||||
|
256
|
||||||
|
) -
|
||||||
|
1,
|
||||||
|
y:
|
||||||
|
props.keyObject.y +
|
||||||
|
Math.ceil(
|
||||||
|
(-coordinateSystem.shift.y + window.innerHeight) /
|
||||||
|
coordinateSystem.zoom /
|
||||||
|
256
|
||||||
|
) -
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
return (
|
||||||
|
<g
|
||||||
|
transform={`scale(${props.zoom}) translate(${props.shift.x}, ${props.shift.y})`}
|
||||||
|
>
|
||||||
|
<TileSet
|
||||||
|
keyObject={{
|
||||||
|
provider: props.keyObject.provider,
|
||||||
|
zoomLevel: props.keyObject.zoomLevel,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
}}
|
||||||
|
viewPort={viewPort}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TiledLayer;
|
Loading…
Reference in New Issue