// notatki inżynieryjne i rozwiązania techniczne
Dziennik Inżynieryjny
Rozwiązanie problemu dryfu pinezek (pin-drift) i centrowania widoku podczas powiększania
projektów PDF
Problem: Podczas tworzenia narzędzia do nanoszenia oznaczeń dla firmy Velder, dodaliśmy możliwość umieszczania pinezek na schematach technicznych. Jednak podczas przybliżania (pinch zoom) planu PDF, współrzędne przesuwały się, powodując dryf pinezek z ich oryginalnych pozycji na rysunku.
Przyczyna: Wczesna wersja kodu centrowała punkt powiększenia statycznie (w lewym górnym rogu płótna). Podczas przybliżania przestrzeń współrzędnych skalowała się, ale pozycja przewijania ekranu nie dostosowywała się proporcjonalnie do środka gestu palców użytkownika, powodując przesuwanie się tła.
Rozwiązanie:
- Zaczęliśmy śledzić współrzędne obu palców, aby obliczyć dokładny punkt skupienia (pinch focal point) na początku gestu.
-
Zastosowaliśmy dynamiczne przesunięcie
transform-originna podstawie proporcji punktu fokalnego względem aktualnego obszaru roboczego rysunku w trakcie gestu. -
Po puszczeniu palców, gdy CSS scale jest jeszcze aktywny, zapisujemy współczynniki
fokalne (
focalRatioX,focalRatioY), wymuszamy ponowne wyrenderowanie strony przez PDF.js w nowej skali i natychmiast kompensujemy przesunięcie ekranu za pomocąwindow.scrollBy.
Rezultat: Rysunek i pinezki pozostają idealnie zablokowane pod palcami w trakcie jakiegokolwiek powiększania, co gwarantuje 100% dokładność wprowadzanych danych.
// Zapisywanie współczynników fokalnych przed resetowaniem tymczasowego CSS transform scale
var cRect = canvas.getBoundingClientRect();
var focalRatioX = cRect.width > 0 ? (pinchFocalClientX - cRect.left) / cRect.width : 0.5;
var focalRatioY = cRect.height > 0 ? (pinchFocalClientY - cRect.top) / cRect.height : 0.5;
renderPage().then(function() {
wrap.style.transform = '';
wrap.style.transformOrigin = 'top left';
// Dostosowanie pozycji przewijania, aby punkt skupienia pozostał w tym samym miejscu ekranu
requestAnimationFrame(function() {
var newRect = canvas.getBoundingClientRect();
var newFocalX = newRect.left + focalRatioX * newRect.width;
var newFocalY = newRect.top + focalRatioY * newRect.height;
window.scrollBy(newFocalX - pinchFocalClientX, newFocalY - pinchFocalClientY);
});
});
Lokalny parser NLP dla języka polskiego do głosowego tworzenia przypomnień hands-free
Problem: Technicy firmy Velder wykonują montaż i konserwację sieci gazów medycznych w strefach szpitalnych (SOR, sale operacyjne, maszynownie). Ponieważ pracują w maskach ochronnych i grubych rękawicach roboczych, ręczne wpisywanie raportów na ekranie dotykowym jest bardzo uciążliwe. Dodatkowo żelbetowe ściany piwnic całkowicie blokują zasięg LTE, uniemożliwiając stabilną synchronizację chmurową.
Ograniczenie: Piwnice szpitalne i maszynownie to strefy o bardzo słabym zasięgu komórkowym. Poleganie na ciężkich chmurowych procesorach NLP (typu Dialogflow czy API OpenAI) groziło nieustannymi przerwami w działaniu.
Rozwiązanie: Zbudowaliśmy lekki, działający po stronie klienta (w 100% offline) parser NLP oparty na wyrażeniach regularnych (regex) dostosowany do języka polskiego. Gdy silnik systemowy przetłumaczy mowę na tekst, nasz parser:
- Wykrywa względne określenia daty (np. "jutro" , "dzisiaj" , nazwy dni tygodnia, liczebniki porządkowe typu "pierwszego" ).
- Wyodrębnia porę dnia i godzinę (np. "rano" , "wieczorem" , "o 14" ).
- Oczyszcza zdanie ze spójników i słów pomocniczych, izolując sam tytuł zadania oraz automatycznie tworząc rekord w lokalnej bazie.
Rezultat: Pracownik może powiedzieć: "przypomnij mi jutro o 10 o teście szczelności" , a aplikacja automatycznie i bez internetu utworzy czyste zadanie: "teście szczelności" (test szczelności) zaplanowane na jutro na godzinę 10:00, w pełni offline.
// Uproszczony schemat przetwarzania tekstu i obliczania daty w języku polskim
export function parsePolishDate(input: string): Date {
const now = new Date();
let target = new Date(now);
if (/jutro/i.test(input)) {
target.setDate(now.getDate() + 1);
}
const timeMatch = input.match(/o\s+(\d{1,2})/i);
if (timeMatch) {
target.setHours(parseInt(timeMatch[1], 10), 0, 0, 0);
}
return target;
}
Synchronizacja w tle i bezkonfliktowa kolejka zapisów SQLite do wyceniania offline
Problem: Miejsca instalacji HVAC często znajdują się w piwnicach lub oddalonych obszarach wiejskich bez zasięgu komórkowego. Technicy muszą tworzyć wyceny zawierające setki rur, rozdzielaczy i usług. Utrata połączenia powodowała błędy aplikacji i utratę danych.
Rozwiązanie: Wdrożyliśmy kolejkę transakcji offline-first opartą na Expo SQLite. Gdy technik dodaje materiały offline:
-
Zmiany są zapisywane lokalnie i serializowane w tabeli
offline_mutationsjako mutacje delta. - NetInfo monitoruje status sieci w tle.
- Po odzyskaniu połączenia menedżer kolejki przesyła mutacje sekwencyjnie do Firebase Cloud Functions.
Rezultat: 100% spójność danych z rozwiązywaniem konfliktów (reguła "ostatni zapis wygrywa" dla modyfikowanych pól).
// Sekwencyjne przesyłanie nagromadzonych mutacji offline z SQLite
async function syncOfflineQueue() {
const mutations = await db.getAllAsync('SELECT * FROM offline_mutations ORDER BY timestamp ASC');
for (const mut of mutations) {
try {
await api.post('/sync-mutation', JSON.parse(mut.payload));
await db.runAsync('DELETE FROM offline_mutations WHERE id = ?', mut.id);
} catch (err) {
if (err.status === 409) {
await resolveConflict(mut);
} else {
break; // retry on next network change
}
}
}
}
Kompilacja wielostronicowych PDF z wycenami bezpośrednio na urządzeniu w < 1.5s
Problem: Generowanie dużych, brandowanych PDF-ów z ofertami HVAC na urządzeniu powodowało zawieszanie się interfejsu (UI lag) i awarie pamięci na tańszych telefonach z systemem Android.
Rozwiązanie:
Przeprojektowaliśmy potok kompilacji PDF za pomocą
react-native-html-to-pdf
w osobnym wątku. Przekonwertowaliśmy logo o wysokiej rozdzielczości na zoptymalizowane
ciągi WebP Base64, osadziliśmy lekkie fonty Google Fonts w pliku stylów i generowaliśmy
plik PDF strona po stronie asynchronicznie.
Rezultat: Zużycie pamięci zmniejszyło się o 70%, a czas generowania spadł z 6s do 1.2s, działając całkowicie po stronie klienta.
// Asynchroniczna kompilacja PDF w osobnym wątku
const htmlContent = `
\${renderEstimateTable(estimateData)}
`;
const file = await RNHTMLtoPDF.convert({
html: htmlContent,
fileName: \`estimate_\${estimateId}\`,
directory: 'Documents',
});