Tocmai am șters sute de linii de cod pe care le-am scris ieri și le-am înlocuit cu 32 de linii de cod nou. Aceasta a fost pentru o funcție pentru TheOpenPresenter , folosită pentru a indica dacă este redat un sunet.
Din când în când, aș lucra la o funcționalitate care pare destul de simplu de implementat. În acest caz, trebuia doar să arăt această pictogramă când se redă sunetul.
Destul de simplu. Fiecare dintre acestea este o scenă care conține mai multe plugin-uri. Fiecare plugin are propria sa proprietate, cum ar fi isPlaying
. Putem îmbina valorile între pluginuri, iar dacă steag-ul este adevărat, putem afișa pictograma.
Problema principală este cum să accesați aceste date. Vezi, am putea accesa datele direct. Dar fiecare plugin poate avea propria sa schemă. În timp ce unele plugin-uri pot avea o proprietate simplă isPlaying , altele ar putea avea nevoie de ceva mai complicat pentru a-și reprezenta starea de redare.
Simplu, de ce să nu permiteți pluginului să înregistreze un apel invers/funcție care returnează starea?
Acesta este același model pe care TheOpenPresenter îl folosește pentru multe dintre pluginurile sale. Și în timp ce suntem la asta, îl putem abstra într-un obiect SceneState . Deci, dacă avem nevoie vreodată de orice altă stare, o putem adăuga aici. Iată cum ar putea arăta pentru plugin:
// The pattern we use for plugins serverPluginApi.onPluginDataCreated(pluginName, onPluginDataCreated); serverPluginApi.onPluginDataLoaded(pluginName, onPluginDataLoaded); serverPluginApi.registerRemoteViewWebComponent( pluginName, remoteWebComponentTag, ); // Example of how the new API might look like serverPluginApi.registerSceneState( pluginName, (_, rendererData) => { return { audioIsPlaying: !!rendererData.find((x) => x.isPlaying), } }, );
Observați că codul de mai sus este gestionat pe server. Acest lucru se datorează faptului că TheOpenPresenter constă din 3 componente separate:
Telecomanda - unde este afișată această indicație audio
The Renderer - redă sunetul
Serverul - conectează cele două
În mod ideal, ar trebui să ne ocupăm de asta în frontend (la distanță), astfel încât să nu adăugăm încărcare suplimentară serverului. Cu toate acestea, înregistrarea acestei funcții poate fi dezordonată. Frontend-ul nostru folosește o arhitectură micro-frontend încărcată cu componente Web.
Zona roșie de mai jos este o coajă React. Zona verde este încărcată prin componente web și este gestionată de fiecare plugin.
Observați că pictograma audio este situată în partea stângă a shell-ului. Cum oferim shell-ului funcția de care avem nevoie? Am putea include o funcție JS în pachetul de componente web, dar asta pare o mizerie pe termen lung.
Gestionarea acestui lucru pe server pare a fi modalitatea corectă de a face acest lucru.
Cu asta hotărât, este timpul pentru implementare. Există câteva lucruri de făcut:
Nu te voi plictisi cu detalii, așa că iată o prezentare generală. API-ul nu a fost destul de simplu, deoarece datele noastre pot fi destul de confuze. Pe scurt: o scenă poate avea mai multe plugin-uri. Și pot exista mai multe randeri, fiecare vizând o scenă într-un mod diferit. Deci, un plugin ar putea avea mai multe randeri care să îl arate în moduri diferite. Dar cu puțină manipulare a datelor, problema rezolvată.
Consumarea și actualizarea interfeței de utilizare
Consumul valorii a fost simplu. M-am gândit să folosesc protocolul de conștientizare Yjs pentru a furniza datele, deoarece acestea sunt în timp real și cadrul este deja în vigoare. Așa este stocat statul. Cu toate acestea, includerea acestor date de pe server este propria sa problemă. Așa că am decis să folosesc în schimb GraphQL - protocolul pe care îl folosim pentru orice altceva din platformă.
Deci, tot ce trebuie să facem este să apelăm punctul final, să-l ascultăm folosind abonamentul GraphQL și să arătăm pictograma după cum este necesar. Făcut.
Furnizarea acestor date către front-end
Din fericire, folosim Postgraphile , ceea ce face extinderea schemei GraphQL destul de simplă. De asemenea, îl putem face un abonament pur și simplu adăugând @pgSubscription
la schema GraphQL. Apoi va urmări un subiect și va actualiza valoarea ori de câte ori apelăm pg_notify
pe acel subiect. De exemplu:
await pgPool.query( `select pg_notify('graphql:sceneState:${id}','{}');`, [], );
Manipularea datelor a fost enervantă, dar doar puțină răbdare și gata!
Ultima piesă a puzzle-ului este apelarea pg_notify
atunci când avem nevoie.
Pentru aceasta, putem adăuga un ascultător la stare (Yjs) și apelăm notificarea ori de câte ori se schimbă ceva:
state.observeDeep(async () => { // Call pg_notify here });
Singurul lucru rămas de făcut este îmbunătățirea performanței. În acest moment, funcția este apelată pentru fiecare modificare mică, este, de asemenea, actualizată la frontend. Putem calcula starea rezultată și putem compara dacă ceva se schimbă înainte de a împinge actualizarea.
Acum, această soluție cu siguranță funcționează. Dar am urât că ascultăm fiecare schimbare. Este inutil și nu sunt sigur cum se va scala performanța. Există vreo soluție mai bună?
Așa că m-am dat înapoi pentru o secundă și a venit o idee: ce zici să mergem la elementele de bază și să folosim datele de la Yjs?
Problema a fost că fiecare plugin poate folosi moduri diferite pentru a indica starea redării. Deci aveam nevoie de o modalitate de a ști cum să calculăm singuri starea rezultată. Dar, în loc să-l lași pe utilizator să treacă o funcție, de ce să nu rezervi o proprietate pe care o poate folosi pentru a indica acest lucru?
În loc să treacă o funcție pentru a calcula starea, fiecare plugin ar putea seta starea rezervată direct alături de datele existente cu proprietăți precum __audioIsPlaying
. Ei ar putea folosi această valoare direct sau ar putea să o mențină sincronizată cu proprietățile lor existente, astfel:
const onRendererDataLoaded = ( rendererData, ) => { watchYjs( // Watch the isPlaying property (x) => x.isPlaying, () => { // And if it changes, sync the __audioIsPlaying property rendererData.set("__audioIsPlaying", rendererData.get("isPlaying")); }, ); };
Noua metodă este genială. Fără ascultător suplimentar, fără API suplimentar, doar o simplă proprietate rezervată.
Costul? Ei bine, am scris deja 95% din prima implementare 🫣
„Va fi atât de păcat să șterg asta când am lucrat la el atât de mult timp. Orice altceva este perfect în afară de acest lucru!” - Mintea mea
Aceasta nu este prima dată. Nici a doua sau a treia. De data aceasta a fost doar câteva ore de muncă. Cu cât durează mai mult implementarea, cu atât este mai greu să renunți. Dar dacă nu ar trebui să ne atașăm la servere, nu ar trebui să fim atașați nici la codul pe care îl scriem.
Este evident că a doua implementare este mai bună. Este mai rapid, mai puține piese în mișcare, mai puțină suprafață API și mai puțin cod de întreținut. Prima implementare a adăugat 289 de linii noi, în timp ce a doua implementare a adăugat doar 32 de linii noi.
Care este lectia de invatat atunci?
Ei bine, poate găsiți mai întâi cea mai simplă soluție. Dar uneori nu ajungem la cea mai bună soluție doar gândindu-ne la ea. Dacă este cazul, nu vă iubiți codul și nu vă fie teamă să-l aruncați . Și poate scrieți o postare pe blog ca să obțineți ceva din ea!
Dacă ați citit până aici, poate doriți să încercați TheOpenPresenter ! Este un sistem de prezentare open-source care vă permite să controlați oricare dintre ecranele dvs. de la distanță.
Afișați prezentări de diapozitive, redați videoclipuri, utilizați ca tablouri de bord și multe altele. Software-ul este încă foarte devreme în dezvoltare, după cum puteți vedea din această postare, dar este suficient de stabil pentru a fi utilizat în mod regulat. Eu personal îl folosesc pentru întâlnirile mele în fiecare săptămână.
Orice întrebări, întrebați. Sau nu ezitați să raportați probleme în depozitul Github .