feat: share local fishing plans
This commit is contained in:
@@ -3,7 +3,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
<Layout title="Мой план рыбалки — RF4 Spotter" description="Сохранённые точки рыбалки RF4 без аккаунта: координаты, наживки, свежесть и доверие." noindex>
|
||||
<section class="plan-hero content-grid"><div><span class="eyebrow">Локально на этом устройстве</span><h1>Мой план<br/><em>рыбалки.</em></h1><p>Соберите до пяти точек, сравните их перед выездом и распечатайте список. Данные не отправляются на сервер.</p></div><div class="plan-hero__mark" aria-hidden="true">♢</div></section>
|
||||
<section class="plan-toolbar content-grid" aria-label="Действия с планом"><span data-plan-count>0 из 5 точек</span><div><button data-action="secondary" type="button" data-plan-print>Печать / PDF</button><button data-action="quiet" type="button" data-plan-clear>Очистить план</button></div></section>
|
||||
<section class="plan-toolbar content-grid" aria-label="Действия с планом"><span data-plan-count>0 из 5 точек</span><div><button data-action="secondary" type="button" data-plan-share>Поделиться планом</button><button data-action="secondary" type="button" data-plan-print>Печать / PDF</button><button data-action="quiet" type="button" data-plan-clear>Очистить план</button><small data-plan-share-status aria-live="polite"></small></div></section>
|
||||
<section class="plan-content content-grid" data-plan-page aria-live="polite"><div class="plan-grid" data-plan-list></div><div class="state" data-plan-empty><h2>План пока пуст</h2><p>Сохраните точку на её detail-странице — она появится здесь.</p><a data-action="secondary" href="/">Посмотреть горячие точки</a></div></section>
|
||||
</Layout>
|
||||
<script>
|
||||
@@ -11,10 +11,20 @@ import Layout from "../layouts/Layout.astro";
|
||||
const list = document.querySelector<HTMLElement>("[data-plan-list]");
|
||||
const empty = document.querySelector<HTMLElement>("[data-plan-empty]");
|
||||
const count = document.querySelector<HTMLElement>("[data-plan-count]");
|
||||
const shareStatus = document.querySelector<HTMLElement>("[data-plan-share-status]");
|
||||
const normalisePlan = (value: unknown): Array<Record<string, string>> => {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value.filter(item => item && typeof item === "object" && typeof item.key === "string" && item.key.startsWith("/spots/")).slice(0, 5).map(item => {
|
||||
const source = item as Record<string, unknown>;
|
||||
return Object.fromEntries(["key", "waterbody", "coordinates", "baits", "freshness", "confidence"].map(key => [key, typeof source[key] === "string" ? source[key] : ""]));
|
||||
});
|
||||
};
|
||||
const readPlan = (): Array<Record<string, string>> => {
|
||||
try { const value = JSON.parse(localStorage.getItem(planStorageKey) || "[]"); return Array.isArray(value) ? value : []; }
|
||||
try { return normalisePlan(JSON.parse(localStorage.getItem(planStorageKey) || "[]")); }
|
||||
catch { return []; }
|
||||
};
|
||||
const shared = new URLSearchParams(location.search).get("plan");
|
||||
if (shared) { try { const imported = normalisePlan(JSON.parse(shared)); if (imported.length) localStorage.setItem(planStorageKey, JSON.stringify(imported)); } catch { if (shareStatus) shareStatus.textContent = "Ссылка плана не распознана"; } }
|
||||
const render = () => {
|
||||
if (!list || !empty) return;
|
||||
const plan = readPlan();
|
||||
@@ -33,6 +43,14 @@ import Layout from "../layouts/Layout.astro";
|
||||
card.append(heading, coordinate, meta, facts, actions); list.append(card);
|
||||
});
|
||||
};
|
||||
document.querySelector<HTMLButtonElement>("[data-plan-share]")?.addEventListener("click", async () => {
|
||||
const url = new URL(location.href); url.search = ""; url.searchParams.set("plan", JSON.stringify(readPlan()));
|
||||
try {
|
||||
if (navigator.share) await navigator.share({ title: "Мой план рыбалки RF4", url: url.toString() });
|
||||
else await navigator.clipboard.writeText(url.toString());
|
||||
if (shareStatus) shareStatus.textContent = "Ссылка готова";
|
||||
} catch { if (shareStatus) shareStatus.textContent = "Скопируйте ссылку из адресной строки"; }
|
||||
});
|
||||
document.querySelector<HTMLButtonElement>("[data-plan-print]")?.addEventListener("click", () => window.print());
|
||||
document.querySelector<HTMLButtonElement>("[data-plan-clear]")?.addEventListener("click", () => { localStorage.removeItem(planStorageKey); render(); });
|
||||
render();
|
||||
|
||||
Reference in New Issue
Block a user