Kod to nie książka. O projekcie Venue Mapping Tool
Wbrew powszechnemu przekonaniu pisanie aplikacji to nie stukanie w klawiaturę. Kod to nie pamiętnik, w którym zapisujemy wszystko, co przyjdzie nam do głowy. Do zadania trzeba podejść holistycznie, a myślenie koncepcyjne sprawia, że to, co robimy, jest naprawdę interesujące.
Filip Barańczuk. Senior JavaScript Developer w Softjourn. Absolwent Politechniki Częstochowskiej na kierunku Informatyka. Programowaniem zainteresował się już w dzieciństwie na swoim pierwszym Commodore 64. Na poważnie pod koniec liceum. Zawodowo od 11 lat. Od paru lat głównie w języku JavaScript, który ceni za zwięzłą składnię i wszechstronność. Prywatnie pasjonuje się piłką nożną i historią, a gdy ma więcej czasu poszerza swoją wiedzę na temat piwowarstwa domowego, również od strony praktycznej.
Praca Front-end Developera może dosyć szybko zamknąć się w pętli dodawania nieskończonych kontrolek formularza i tworzenia jeszcze jednej „CRUD-owej” aplikacji. Niektórym to odpowiada, jednak u ambitnych jednostek może to wywołać znudzenie, a także chęć zmiany – projektu lub firmy. Jak rozwijać się, żeby czuć się spełnionym? Wystarczy próbować nowych rzeczy. W tym artykule napiszę o projekcie, w którym wiedza ze studiów może się naprawdę przydać.
Venue Mapping Tool to autorski projekt Softjourn stworzony dla właścicieli obiektów, którzy chcieliby wprowadzić system rezerwacji. Narzędzie pozwala użytkownikowi samodzielnie narysować plan lokalu i przypisać typ biletu do poszczególnych miejsc na widowni. Kształt sektorów można dowolnie modyfikować, tak by w pełni odzwierciedlić wygląd obiektu. Aplikacja zawiera kluczowe funkcje, takie jak obrót kątowy sektorów, dowolność w modyfikacji liczby siedzeń i rzędów, możliwość odwracania obszarów, pochylanie i inne.
Tyle z teorii. Do napisania VMT wybraliśmy stos technologiczny, który będzie elastyczny, łatwy w integracji i przyspieszy proces developmentu, a ponadto:
- Zapewni nam najlepszą wydajność (możliwość tworzenia tysięcy obiektów i ich modyfikację bezpośrednio na Canvas),
- Obsłuży wszystkie najpopularniejsze przeglądarki, a także urządzenia stacjonarne i mobilne.
Niektóre frameworki, chociaż spełniały większość z tych punktów, zostały odrzucone ze względu na wydajność. Na przykład, kiedy zaczynaliśmy pracę, przeglądarki nie były zoptymalizowane pod kątem obsługi wielu obiektów SVG – wydajność gwałtownie spadała wraz ze wzrostem ich liczby. Dlatego zdecydowaliśmy się na Pixi.js i React + Redux.
Spis treści
Zbuduj mapę obiektu
Prosty interfejs graficzny umożliwia stworzenie nawet skomplikowanych obiektów laikowi. W kolejnych krokach odtworzymy plan miejsc siedzących w Teatrze Muzycznym Capitol we Wrocławiu.
Stadion może mieć kilkadziesiąt tysięcy miejsc z dość skomplikowanym układem. Aplikacja umożliwia dodanie zdjęcia, i wyznaczenie obszarów, które będziemy mogli szczegółowo edytować. W ten sposób pracujemy na mniejszych sekcjach, co zapewnia lepszą skalowalność i ułatwia nawigację na ekranie.
Eleganckie i proste?
Podczas projektowania aplikacji największym wyzwaniem był State Management pomiędzy klasycznym UI w React, a Canvas. Stan jest obsługiwany przez Redux, który świetnie sprawdza się w tego typu projektach, ponieważ jest elastyczny i można go połączyć z dowolną technologią i biblioteką.
Jedną z głównych zasad Redux jest to, że wszystkie akcje i reduktory powinny pozostać czyste. W przeciwnym razie przyjdzie nam się mierzyć z wieloma „skutkami ubocznymi”, które mogą nam utrudnić śledzenie zmian w stanie oraz sprawią, że całość będzie mniej przewidywalna. Zdecydowaliśmy się obsłużyć „side effects” poprzez mechanizm Redux Middlewares.
Oto przykładowy element UI w React’cie
const ReactComponent = ({ color }) => { const dispatch = useDispatch(); const selectedObjectId = useSelector((state) => state.objectReducer.id); const handleClick = (props) => { // send UI changes dispatch(changeObjectAction({ selectedObjectId, props })); }; return ( <ColorPicker color={color} onChange={handleChange}> Change color </ColorPicker> ); }; ReactComponent.propTypes = { color: PropTypes.number };
Dzięki temu wybrana akcja zostanie przechwycona w Middleware.
const objectMiddleware = (store) => (next) => (action) => { const { type, payload } = action; const { getState, dispatch } = store; const state = getState().objectReducer; switch (type) { // capture canvas object changes case changeObjectAction.type: changeObject(payload.objectId, payload.props); break; default: break; } return next(action); }; const changeObject = (objectId, props) => { const app = Application.instance; // change object on canvas app.changeObject(objectId, props); };
Zwróćcie uwagę, że używamy tutaj wzorca singleton dla Canvas. Jednak jeżeli zdecydujemy się na zastosowanie innego wzorca, możemy w prosty sposób dokonać migracji jedynie wymieniając middlewares, które zajmą się synchronizacją stanu między Canvas a UI.
To działa w dwie strony. Kiedy zmieniamy coś bezpośrednio w Canvas wysyłamy akcje, więc React UI zostanie odpowiednio poinformowany.
Przykładowa funkcja w Canvas:
onRescale(scale: PointInterface) { const points = recalculatePoints(scale); // send action to trigger UI changes if necessary reduxStore.dispatch( changeObjects([ { id: this.id, changes: [ { key: 'points', value: points }, { key: 'x', value: this.x }, { key: 'y', value: this.y } ], }, ]) ); }); }
Uproszczony diagram wygląda następująco:
Rozwiązanie w teorii wygląda na proste, zgrabne i rozsądne, ale w praktyce wszystkie akcje muszą być odpowiednio rozporządzone – w odpowiednim miejscu przechwycone i wywołane. Dlatego zastosowanie takiego schematu sprawia, że całość staje się przewidywalna i przejrzysta, a to chyba najważniejsze.
Kolejnym wyzwaniem było odpowiednie pozycjonowanie obiektów wraz z ich detalami. Tutaj warto było sobie odświeżyć, chociażby podstawy trygonometrii czy geometrii. Bardziej wymagające zadania to na przykład nadawanie nieregularnego kształtu tworzonego sektora oraz odpowiednie dostosowanie rozmieszczenia siedzisk, tak aby pasowały do wyznaczonej figury. Tutaj oczywiście z pomocą przychodzi wspomniany Pixi.js, ale jedynie jako baza dla wyliczonych przekształceń. Szczegółowe wyliczenia to już matematyka, która bądźmy szczerzy, jest rzadkim gościem na front endzie.
Elastyczny model współpracy
W projekcie każdy z zespołu ma równy głos i możliwość przedstawienia swoich koncepcji i rozwiązań. Każde z nich jest gruntownie analizowane. Mogłoby się wydawać, że to chaotyczna forma pracy, ale w tym przypadku wyjątkowo dobrze się sprawdza. Dzięki temu odpowiedzialność za jakość kodu jest równomiernie rozłożona na cały zespół. Takie rozwiązanie zajmuje z pewnością więcej czasu, ale to, co robimy to ewolucja, a nie rewolucja.
Spotykamy się raz w tygodniu i dyskutujemy o zmianach, które naszym zdaniem podniosłyby funkcjonalność aplikacji. Sugestie wpisujemy do tabeli, szacujemy czas potrzebny na zmiany, nadajemy im priorytet i na tej podstawie ustalamy kolejność zadań. Jedną z takich zmian było ustalenie, że wszystko piszemy w TypeScripcie. W dużych projektach jest to pomocne ze względu na właściwości tego języka. Jeżeli zmienisz coś w jednym miejscu TypeScript bardzo szybko wyłapie potencjalną niespójność.
Zakończenie
Z punktu widzenia użytkownika, Venue Mapping Tool to elastyczne rozwiązanie, które sprawdzi się zarówno w małych salach konferencyjnych, jak i na stadionach.
Z punktu widzenia dewelopera jest to szansa na sprawdzenie się w nieszablonowym projekcie i poszerzenie granic swoich umiejętności. Frontend nie musi zamykać się w pętli tych samych formuł. To praca koncepcyjna, która wymaga analizy, a czasem i wyjście poza utarte schematy. Różnorodność wyzwań może jednak przynieść tak bardzo poszukiwaną przez nas radość z procesu twórczego.
Zdjęcie główne artykułu pochodzi z unsplash.com.