Pretty nice (IMHO) version of avatars for locations...
This commit is contained in:
parent
9ce7f33cb4
commit
9bb2f48d4a
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import { locationOutline } from 'ionicons/icons';
|
||||||
|
import React, { useId } from 'react';
|
||||||
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { geoPoint, lat2tile, lon2tile, Point } from '../../lib/geo';
|
import { geoPoint, lat2tile, lon2tile, Point } from '../../lib/geo';
|
||||||
|
@ -12,17 +13,15 @@ interface AvatarForLocationProps {
|
||||||
zoom?: number;
|
zoom?: number;
|
||||||
tileProvider?: string;
|
tileProvider?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
name?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AvatarForLocation: React.FC<AvatarForLocationProps> = (
|
const AvatarForLocation: React.FC<AvatarForLocationProps> = (
|
||||||
props: AvatarForLocationProps
|
props: AvatarForLocationProps
|
||||||
) => {
|
) => {
|
||||||
const size = props.size ? props.size : 42;
|
const size = props.size !== undefined ? props.size : 42;
|
||||||
const zoom = props.zoom ? Math.round(props.zoom) : 16;
|
const zoom = props.zoom ? Math.round(props.zoom) : 16;
|
||||||
const tileProvider = props.tileProvider ? props.tileProvider : 'osm';
|
const tileProvider = props.tileProvider ? props.tileProvider : 'osm';
|
||||||
const location = props.location;
|
const location = props.location;
|
||||||
const name = props.name ? props.name : '???';
|
|
||||||
|
|
||||||
const tilesLocation: Point = {
|
const tilesLocation: Point = {
|
||||||
x: lon2tile(location.lon, zoom),
|
x: lon2tile(location.lon, zoom),
|
||||||
|
@ -34,10 +33,10 @@ const AvatarForLocation: React.FC<AvatarForLocationProps> = (
|
||||||
y: 256 * (tilesLocation.y - Math.floor(tilesLocation.y)),
|
y: 256 * (tilesLocation.y - Math.floor(tilesLocation.y)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const eastTileNeeded = locationWithinTile.x > 256 - size;
|
const eastTileNeeded = locationWithinTile.x > 256 - size / 2;
|
||||||
const westTileNeeded = locationWithinTile.x < size;
|
const westTileNeeded = locationWithinTile.x < size / 2;
|
||||||
const southTileNeeded = locationWithinTile.y > 256 - size;
|
const southTileNeeded = locationWithinTile.y > 256 - size / 2;
|
||||||
const northTileNeeded = locationWithinTile.y < size;
|
const northTileNeeded = locationWithinTile.y < size / 2;
|
||||||
|
|
||||||
const getImage = (stepX: number, stepY: number) => (
|
const getImage = (stepX: number, stepY: number) => (
|
||||||
<image
|
<image
|
||||||
|
@ -48,8 +47,8 @@ const AvatarForLocation: React.FC<AvatarForLocationProps> = (
|
||||||
)}
|
)}
|
||||||
height='256'
|
height='256'
|
||||||
width={256}
|
width={256}
|
||||||
x={-locationWithinTile.x + stepX * 256}
|
x={-locationWithinTile.x + size / 2 + stepX * 256}
|
||||||
y={-locationWithinTile.y + stepY * 256}
|
y={-locationWithinTile.y + size / 2 + stepY * 256}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -81,19 +80,16 @@ const AvatarForLocation: React.FC<AvatarForLocationProps> = (
|
||||||
|
|
||||||
console.log(`${images.length} images: ${images}`);
|
console.log(`${images.length} images: ${images}`);
|
||||||
|
|
||||||
|
const id = useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg width={size} height={size}>
|
<svg width={size} height={size}>
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id='cut-off'>
|
<clipPath id={`cut-off-${id}`}>
|
||||||
<circle cx={size / 2} cy={size / 2} r={size / 2} />
|
<circle cx={size / 2} cy={size / 2} r={size / 2} />
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
<g clip-path='url(#cut-off)'>
|
<g clip-path={`url(#cut-off-${id})`}>{[images]}</g>
|
||||||
{[images]}
|
|
||||||
<text x='2' y={size / 2 + 5} visibility='hidden'>
|
|
||||||
{name}
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -78,7 +78,7 @@ const LocationInfo: React.FC<{}> = () => {
|
||||||
setNoteIndex(0);
|
setNoteIndex(0);
|
||||||
const metresPerDegree =
|
const metresPerDegree =
|
||||||
111111 * Math.cos((scope.center.lat * Math.PI) / 180);
|
111111 * Math.cos((scope.center.lat * Math.PI) / 180);
|
||||||
const deltaDegrees = 2000 / metresPerDegree;
|
const deltaDegrees = 5000 / metresPerDegree;
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://api.openstreetmap.org/api/0.6/notes.json?bbox=${
|
`https://api.openstreetmap.org/api/0.6/notes.json?bbox=${
|
||||||
scope.center.lon - deltaDegrees
|
scope.center.lon - deltaDegrees
|
||||||
|
@ -89,6 +89,17 @@ const LocationInfo: React.FC<{}> = () => {
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log(`notes: ${JSON.stringify(data)}`);
|
console.log(`notes: ${JSON.stringify(data)}`);
|
||||||
|
data.features.sort(
|
||||||
|
(first: any, second: any) =>
|
||||||
|
roughDistance(scope.center, {
|
||||||
|
lon: first.geometry.coordinates[0],
|
||||||
|
lat: first.geometry.coordinates[1],
|
||||||
|
}) >
|
||||||
|
roughDistance(scope.center, {
|
||||||
|
lon: second.geometry.coordinates[0],
|
||||||
|
lat: second.geometry.coordinates[1],
|
||||||
|
})
|
||||||
|
);
|
||||||
setNotes(data);
|
setNotes(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -121,24 +132,23 @@ const LocationInfo: React.FC<{}> = () => {
|
||||||
setNoteIndex(index);
|
setNoteIndex(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AvatarForFeature: React.FC<{ feature: any; as: any }> = (props: {
|
const AvatarForFeature: React.FC<{
|
||||||
feature: any;
|
feature: any;
|
||||||
|
size: number;
|
||||||
as: any;
|
as: any;
|
||||||
}) => {
|
}> = (props: { feature: any; size: number; as: any }) => {
|
||||||
const distance = roughDistance(scope.center, {
|
const distance = roughDistance(scope.center, {
|
||||||
lon: props.feature.geometry.coordinates[0],
|
lon: props.feature.geometry.coordinates[0],
|
||||||
lat: props.feature.geometry.coordinates[1],
|
lat: props.feature.geometry.coordinates[1],
|
||||||
});
|
});
|
||||||
const distDark = (255 * distance) / 3000;
|
|
||||||
const distLight = 1 - distDark;
|
|
||||||
return (
|
return (
|
||||||
<Avatar>
|
<Avatar size={props.size <= 50 ? 'sm' : 'lg'}>
|
||||||
<AvatarForLocation
|
<AvatarForLocation
|
||||||
location={{
|
location={{
|
||||||
lon: props.feature.geometry.coordinates[0],
|
lon: props.feature.geometry.coordinates[0],
|
||||||
lat: props.feature.geometry.coordinates[1],
|
lat: props.feature.geometry.coordinates[1],
|
||||||
}}
|
}}
|
||||||
name={Math.round(distance).toString()}
|
size={props.size}
|
||||||
/>
|
/>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
);
|
);
|
||||||
|
@ -205,25 +215,36 @@ const LocationInfo: React.FC<{}> = () => {
|
||||||
<MainContainer responsive>
|
<MainContainer responsive>
|
||||||
<Sidebar position='left' id='chat-sidebar'>
|
<Sidebar position='left' id='chat-sidebar'>
|
||||||
<ConversationList>
|
<ConversationList>
|
||||||
{notes.features!.map((feature: any, index: number) => (
|
{notes
|
||||||
<Conversation
|
.features!.slice(0, 5)
|
||||||
onClick={conversationClickHandlerFactory(index)}
|
.map((feature: any, index: number) => (
|
||||||
key={feature.properties.id}
|
<Conversation
|
||||||
name={i18n.locationInfo!.at!(
|
onClick={conversationClickHandlerFactory(index)}
|
||||||
roughDistance(scope.center, {
|
key={feature.properties.id}
|
||||||
lon: feature.geometry.coordinates[0],
|
name={i18n.locationInfo!.at!(
|
||||||
lat: feature.geometry.coordinates[1],
|
roughDistance(scope.center, {
|
||||||
})
|
lon: feature.geometry.coordinates[0],
|
||||||
)}
|
lat: feature.geometry.coordinates[1],
|
||||||
info={feature.properties.status}
|
})
|
||||||
>
|
)}
|
||||||
<AvatarForFeature feature={feature} as={Avatar} />
|
info={feature.properties.status}
|
||||||
</Conversation>
|
>
|
||||||
))}
|
<AvatarForFeature
|
||||||
|
feature={feature}
|
||||||
|
size={42}
|
||||||
|
as={Avatar}
|
||||||
|
/>
|
||||||
|
</Conversation>
|
||||||
|
))}
|
||||||
</ConversationList>
|
</ConversationList>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
<ChatContainer>
|
<ChatContainer>
|
||||||
<ConversationHeader>
|
<ConversationHeader>
|
||||||
|
<AvatarForFeature
|
||||||
|
feature={notes.features[noteIndex]}
|
||||||
|
size={68}
|
||||||
|
as={Avatar}
|
||||||
|
/>
|
||||||
<ConversationHeader.Content>
|
<ConversationHeader.Content>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
|
@ -240,10 +261,6 @@ const LocationInfo: React.FC<{}> = () => {
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</ConversationHeader.Content>
|
</ConversationHeader.Content>
|
||||||
<AvatarForFeature
|
|
||||||
feature={notes.features[noteIndex]}
|
|
||||||
as={Avatar}
|
|
||||||
/>
|
|
||||||
</ConversationHeader>
|
</ConversationHeader>
|
||||||
<MessageList>
|
<MessageList>
|
||||||
{noteIndex < notes.features.length &&
|
{noteIndex < notes.features.length &&
|
||||||
|
|
|
@ -97,3 +97,8 @@ ion-modal ion-content {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cs-conversation-header__avatar {
|
||||||
|
height: auto !important;
|
||||||
|
width:auto !important;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue