поправил фотографии
Gitea Actions Demo / build_and_push (push) Successful in 49s Details

This commit is contained in:
artem 2026-09-17 19:53:30 +03:00
parent 76b93dbadc
commit b08870a3ef
1 changed files with 95 additions and 5 deletions

View File

@ -70,7 +70,7 @@
color: 'gray', color: 'gray',
}" }"
/> />
<yandex-map-clusterer :grid-size="64" zoom-on-cluster-click> <yandex-map-clusterer :grid-size="64">
<yandex-map-marker <yandex-map-marker
v-for="photo in photosWithCoords" v-for="photo in photosWithCoords"
:key="'photo-' + photo.id" :key="'photo-' + photo.id"
@ -83,10 +83,19 @@
}" }"
position="bottom-center" position="bottom-center"
> >
<img :src="photo.url" class="map-photo-marker" :alt="photo.id" /> <div class="photo-single">1</div>
</yandex-map-marker> </yandex-map-marker>
<template #cluster="{ length }"> <template #cluster="{ clusterer, length }">
<div class="photo-cluster">{{ length }}</div> <div
:class="
isSamePointCluster(clusterer as ClustererInfo)
? 'photo-cluster photo-cluster-same'
: 'photo-cluster'
"
@click="(e) => handleClusterClick(e, clusterer)"
>
{{ length }}
</div>
</template> </template>
</yandex-map-clusterer> </yandex-map-clusterer>
</yandex-map> </yandex-map>
@ -446,7 +455,7 @@ import {
ftpToZones, ftpToZones,
type PowerZone, type PowerZone,
} from "./PowerZonesPlugin.js"; } from "./PowerZonesPlugin.js";
import type { LngLat, YMap } from "@yandex/ymaps3-types"; import type { LngLat, LngLatBounds, YMap } from "@yandex/ymaps3-types";
import { import {
YandexMap, YandexMap,
YandexMapDefaultSchemeLayer, YandexMapDefaultSchemeLayer,
@ -1204,6 +1213,69 @@ const openPhoto = (photo: WorkoutPhoto) => {
showLightbox.value = true; showLightbox.value = true;
} }
}; };
type ClusterFeature = {
geometry: { coordinates: [number, number, ...number[]] };
};
type ClustererInfo = {
lnglat: [number, number, ...number[]];
features: ClusterFeature[];
};
const CLUSTER_SAME_POINT_EPS = 1e-4;
function isSamePointCluster(c: ClustererInfo): boolean {
if (!c.features || c.features.length === 0) return true;
const coords = c.features.map(
(f) => f.geometry.coordinates as [number, number],
);
const lats = coords.map(([_lng, lat]) => lat);
const lngs = coords.map(([lng]) => lng);
const spreadLat = Math.max(...lats) - Math.min(...lats);
const spreadLng = Math.max(...lngs) - Math.min(...lngs);
return (
spreadLat < CLUSTER_SAME_POINT_EPS && spreadLng < CLUSTER_SAME_POINT_EPS
);
}
function openClusterPhotos(c: ClustererInfo) {
if (!c.features || c.features.length === 0) return;
const [lng, lat] = c.lnglat as [number, number];
const idx = photos.value.findIndex(
(p) =>
p.latitude !== null &&
p.longitude !== null &&
Math.abs(p.longitude - lng) < CLUSTER_SAME_POINT_EPS &&
Math.abs(p.latitude - lat) < CLUSTER_SAME_POINT_EPS,
);
if (idx >= 0) {
lightboxIndex.value = idx;
showLightbox.value = true;
}
}
function zoomToCluster(c: ClustererInfo) {
if (!c.features || c.features.length < 2 || !map.value) return;
const coords = c.features.map(
(f) => f.geometry.coordinates as [number, number],
);
const lngs = coords.map(([lng]) => lng);
const lats = coords.map(([, lat]) => lat);
const minLng = Math.min(...lngs);
const maxLng = Math.max(...lngs);
const minLat = Math.min(...lats);
const maxLat = Math.max(...lats);
const lngPad = (maxLng - minLng) * 0.25 || 1e-5;
const latPad = (maxLat - minLat) * 0.25 || 1e-5;
const bounds: LngLatBounds = [
[minLng - lngPad, maxLat + latPad],
[maxLng + lngPad, minLat - latPad],
];
map.value.setLocation({ bounds, duration: 500 });
}
function handleClusterClick(_event: MouseEvent, clusterer: unknown) {
const c = clusterer as ClustererInfo;
if (isSamePointCluster(c)) {
openClusterPhotos(c);
} else {
zoomToCluster(c);
}
}
function focusPhotoOnMap(photo: WorkoutPhoto) { function focusPhotoOnMap(photo: WorkoutPhoto) {
if (photo.latitude === null || photo.longitude === null) return; if (photo.latitude === null || photo.longitude === null) return;
map.value?.update({ map.value?.update({
@ -1500,6 +1572,24 @@ h3 {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
cursor: pointer; cursor: pointer;
} }
.photo-single {
width: 28px;
height: 28px;
border-radius: 50%;
background: #007afc;
color: #fff;
font-weight: 600;
font-size: 13px;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
cursor: pointer;
}
.photo-cluster-same {
background: #f59e0b;
}
.workout-photo-loc { .workout-photo-loc {
position: absolute; position: absolute;
bottom: 4px; bottom: 4px;