Naprawa błędów awatara
This commit is contained in:
10
App.tsx
10
App.tsx
@@ -15,6 +15,10 @@ import { getEnvVar } from './utils/envUtils';
|
||||
import { AUTHOR_CONFIG } from './_EDITABLE_CONFIG/author';
|
||||
import { UI_TEXT } from './_EDITABLE_CONFIG/ui_text';
|
||||
|
||||
// --- ASSET IMPORT ---
|
||||
// Importujemy logo bezpośrednio, aby bundler poprawnie rozwiązał ścieżkę
|
||||
import logoImg from './logo.png';
|
||||
|
||||
const STORAGE_KEY = 'gpx-storyteller-state-v6';
|
||||
const AUTH_KEY = 'promptstory-auth-token';
|
||||
|
||||
@@ -131,7 +135,7 @@ const App: React.FC = () => {
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// LOGO & AVATAR STATE: Default to false (no error), so we try to show image first.
|
||||
// LOGO & AVATAR STATE
|
||||
const [logoError, setLogoError] = useState(false);
|
||||
const [avatarError, setAvatarError] = useState(false);
|
||||
|
||||
@@ -245,10 +249,10 @@ const App: React.FC = () => {
|
||||
<header className="bg-white sticky top-0 z-10 border-b border-gray-100">
|
||||
<div className="max-w-4xl mx-auto px-6 py-4 flex justify-between items-center">
|
||||
<div className="flex items-center space-x-3">
|
||||
{/* LOGO LOGIC: Try to show image. If error, fallback to icon/text. */}
|
||||
{/* LOGO LOGIC: Use imported logoImg */}
|
||||
{!logoError ? (
|
||||
<img
|
||||
src="logo.png"
|
||||
src={logoImg}
|
||||
alt="Logo"
|
||||
onError={() => setLogoError(true)}
|
||||
className="h-10 object-contain"
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
|
||||
// Import obrazu, aby bundler go dołączył
|
||||
import avatarImg from '../avatar.jpeg';
|
||||
|
||||
export const AUTHOR_CONFIG = {
|
||||
name: "Arkadiusz AreBynd[] Bykowski",
|
||||
description: "Zamieniam Twoją stronę w maszynkę do zarabiania pieniędzy. Od 12 lat projektuję strony, które sprzedają bez Twojego udziału – póki Ty śpisz, one robią za Ciebie robotę. Specjalizuję się w landing page'ach, które budują zaufanie i zmieniają odwiedzających w płacących klientów.",
|
||||
name: "Arkadiusz Bykowski",
|
||||
description: "Zamieniam Twoją stronę w maszynkę do zarabiania pieniędzy. Od 12 lat projektuję strony, które sprzedają bez Twojego udziału.",
|
||||
tags: ["Product Design", "AI Automation", "No-Code"],
|
||||
websiteUrl: "https://bykowski.pro/",
|
||||
websiteLabel: "Odwiedź stronę",
|
||||
// Nazwa pliku w folderze public
|
||||
avatarImage: "avatar.jpeg"
|
||||
// Używamy zaimportowanego obrazu
|
||||
avatarImage: avatarImg
|
||||
};
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
// Unused. Logic moved to StepDetails.tsx
|
||||
@@ -16,10 +16,15 @@ export const parseGpxFile = async (file: File): Promise<GpxAnalysisResult> => {
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(text, 'text/xml');
|
||||
|
||||
const trkpts = Array.from(xmlDoc.getElementsByTagName('trkpt'));
|
||||
// Obsługa różnych wariantów tagu 'trkpt' (z namespace lub bez)
|
||||
let trkpts = Array.from(xmlDoc.getElementsByTagName('trkpt'));
|
||||
if (trkpts.length === 0) {
|
||||
// Spróbuj znaleźć po localName, jeśli namespace'y przeszkadzają
|
||||
const allElements = xmlDoc.getElementsByTagName('*');
|
||||
trkpts = Array.from(allElements).filter(el => el.localName === 'trkpt');
|
||||
}
|
||||
|
||||
if (trkpts.length === 0) {
|
||||
// Fallback logic could be added here for routes without time, but for storytelling we focus on tracks
|
||||
throw new Error('Nie znaleziono punktów śledzenia (trkpt) w pliku GPX.');
|
||||
}
|
||||
|
||||
@@ -51,44 +56,62 @@ export const parseGpxFile = async (file: File): Promise<GpxAnalysisResult> => {
|
||||
let lastLat: number | null = null;
|
||||
let lastLon: number | null = null;
|
||||
let lastEle: number | null = null;
|
||||
let lastTime: number | null = null;
|
||||
|
||||
// Helper to get text content from child nodes loosely
|
||||
const getChildText = (parent: Element, tagName: string): string | null => {
|
||||
let el = parent.getElementsByTagName(tagName)[0];
|
||||
if (!el) {
|
||||
// Try finding by localName if strictly not found
|
||||
for(let i=0; i<parent.children.length; i++) {
|
||||
if(parent.children[i].localName === tagName) {
|
||||
el = parent.children[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return el ? el.textContent : null;
|
||||
};
|
||||
|
||||
// --- ITERACJA PO PUNKTACH ---
|
||||
for (let i = 0; i < trkpts.length; i++) {
|
||||
const pt = trkpts[i];
|
||||
const lat = parseFloat(pt.getAttribute('lat') || '0');
|
||||
const lon = parseFloat(pt.getAttribute('lon') || '0');
|
||||
const ele = parseFloat(pt.getElementsByTagName('ele')[0]?.textContent || '0');
|
||||
const timeStr = pt.getElementsByTagName('time')[0]?.textContent;
|
||||
|
||||
// Extensions (HR / Cadence)
|
||||
// Note: Namespaces can vary (ns3:hr, gpxtpx:hr), so we search loosely or by standard tag names inside extensions
|
||||
const extensions = pt.getElementsByTagName('extensions')[0];
|
||||
const eleStr = getChildText(pt, 'ele');
|
||||
const ele = eleStr ? parseFloat(eleStr) : NaN;
|
||||
|
||||
const timeStr = getChildText(pt, 'time');
|
||||
|
||||
// Extensions search for HR/Cadence
|
||||
// Przeszukujemy całe poddrzewo punktu w poszukiwaniu tagów hr/cad, bo struktura extensions bywa różna
|
||||
const allChilds = pt.getElementsByTagName('*');
|
||||
let hr = 0;
|
||||
let cad = 0;
|
||||
|
||||
if (extensions) {
|
||||
// Try standard Garmin/GPX schemes
|
||||
const hrNode = extensions.getElementsByTagName('gpxtpx:hr')[0] || extensions.getElementsByTagName('ns3:hr')[0] || extensions.getElementsByTagName('hr')[0];
|
||||
const cadNode = extensions.getElementsByTagName('gpxtpx:cad')[0] || extensions.getElementsByTagName('ns3:cad')[0] || extensions.getElementsByTagName('cad')[0];
|
||||
for (let j = 0; j < allChilds.length; j++) {
|
||||
const node = allChilds[j];
|
||||
const name = node.localName.toLowerCase();
|
||||
if (name === 'hr' || name === 'heartrate') {
|
||||
const val = parseInt(node.textContent || '0');
|
||||
if (!isNaN(val) && val > 0) hr = val;
|
||||
}
|
||||
if (name === 'cad' || name === 'cadence') {
|
||||
const val = parseInt(node.textContent || '0');
|
||||
if (!isNaN(val) && val > 0) cad = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (hrNode?.textContent) {
|
||||
hr = parseInt(hrNode.textContent);
|
||||
if (!isNaN(hr) && hr > 0) {
|
||||
totalHr += hr;
|
||||
hrCount++;
|
||||
if (hr > maxHr) maxHr = hr;
|
||||
currentSplitHrSum += hr;
|
||||
currentSplitHrCount++;
|
||||
}
|
||||
}
|
||||
if (cadNode?.textContent) {
|
||||
cad = parseInt(cadNode.textContent);
|
||||
if (!isNaN(cad) && cad > 0) {
|
||||
totalCadence += cad;
|
||||
cadenceCount++;
|
||||
}
|
||||
}
|
||||
if (hr > 0) {
|
||||
totalHr += hr;
|
||||
hrCount++;
|
||||
if (hr > maxHr) maxHr = hr;
|
||||
currentSplitHrSum += hr;
|
||||
currentSplitHrCount++;
|
||||
}
|
||||
if (cad > 0) {
|
||||
totalCadence += cad;
|
||||
cadenceCount++;
|
||||
}
|
||||
|
||||
let currentTime = 0;
|
||||
@@ -117,8 +140,11 @@ export const parseGpxFile = async (file: File): Promise<GpxAnalysisResult> => {
|
||||
// Elevation Gain
|
||||
if (lastEle !== null && !isNaN(ele) && ele > lastEle) {
|
||||
const gain = ele - lastEle;
|
||||
totalElevationGain += gain;
|
||||
currentSplitElevGain += gain;
|
||||
// Ignorujemy mikro-zmiany (szum GPS) poniżej 0.5m
|
||||
if (gain > 0.2) {
|
||||
totalElevationGain += gain;
|
||||
currentSplitElevGain += gain;
|
||||
}
|
||||
}
|
||||
|
||||
// --- SPLIT LOGIC (Co 1 KM) ---
|
||||
@@ -133,7 +159,7 @@ export const parseGpxFile = async (file: File): Promise<GpxAnalysisResult> => {
|
||||
|
||||
// Reset Split
|
||||
splitIndex++;
|
||||
currentSplitDistance = 0; // or subtract 1.0 specifically for precision, but reset is safer for GPS drift
|
||||
currentSplitDistance = currentSplitDistance - 1.0;
|
||||
currentSplitTimeStart = currentTime;
|
||||
currentSplitHrSum = 0;
|
||||
currentSplitHrCount = 0;
|
||||
@@ -144,7 +170,6 @@ export const parseGpxFile = async (file: File): Promise<GpxAnalysisResult> => {
|
||||
lastLat = lat;
|
||||
lastLon = lon;
|
||||
lastEle = isNaN(ele) ? lastEle : ele;
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
// --- PODSUMOWANIE ---
|
||||
@@ -157,7 +182,7 @@ export const parseGpxFile = async (file: File): Promise<GpxAnalysisResult> => {
|
||||
const uiStats: ActivityStats = {
|
||||
distance: `${totalDistanceKm.toFixed(2)} km`,
|
||||
duration: msToTime(totalDurationMs),
|
||||
elevation: `${Math.round(totalElevationGain)}m (Max: ${Math.round(maxElevation)}m)`
|
||||
elevation: `${Math.round(totalElevationGain)}m (Max: ${Math.round(maxElevation === -Infinity ? 0 : maxElevation)}m)`
|
||||
};
|
||||
|
||||
// --- BUDOWANIE BOGATEGO RAPORTU DLA AI ---
|
||||
@@ -166,7 +191,7 @@ export const parseGpxFile = async (file: File): Promise<GpxAnalysisResult> => {
|
||||
report += `- Dystans Całkowity: ${totalDistanceKm.toFixed(2)} km\n`;
|
||||
report += `- Czas Całkowity: ${msToTime(totalDurationMs)}\n`;
|
||||
report += `- Średnie Tempo: ${formatPace(avgPaceMs)} min/km\n`;
|
||||
report += `- Przewyższenia: +${Math.round(totalElevationGain)}m / Max Wysokość: ${Math.round(maxElevation)}m\n`;
|
||||
report += `- Przewyższenia: +${Math.round(totalElevationGain)}m / Max Wysokość: ${Math.round(maxElevation === -Infinity ? 0 : maxElevation)}m\n`;
|
||||
|
||||
if (avgHr > 0) {
|
||||
report += `- Tętno: Średnie ${avgHr} bpm / Max ${maxHr} bpm\n`;
|
||||
|
||||
Reference in New Issue
Block a user