{"version":3,"sources":["components/Item.tsx","components/Skill.tsx","components/SearchMenu.tsx","components/Nav.tsx","services/NavService.tsx","components/Job.tsx","components/App.tsx","img/magnifying-glass.png","services/FilteringService.tsx","services/HttpService.tsx","services/ItemsService.tsx","services/JobsService.tsx","services/SkillsService.tsx","index.tsx"],"names":["Item","props","state","onViewCode","bind","onViewLive","url","this","gotoUrl","e","preventDefault","item","codeUrl","liveUrl","description","map","text","index","thumbnailUrl","className","src","alt","name","method","onClick","classes","isActive","push","join","getClassName","ref","summary","renderThumbnail","renderDescription","renderButton","React","Component","Skill","skill","rating","SearchMenu","isMenuOpen","onSearchChange","currentTarget","value","type","placeholder","onChange","Nav","items","onItemClick","key","renderItems","NAV_ITEMS","NavService","activeItem","find","setActiveItem","Job","job","style","getStyle","companyName","backgroundColor","themeColor","jobTitle","jobsService","getJobDateDisplay","App","isScrolling","minActivePercentSeen","maxActivePercentSeen","searchValue","projects","itemsService","getItems","skills","skillsService","navItems","navService","getNavItems","onScroll","toggleMenu","handleNav","window","addEventListener","removeEventListener","setState","getActiveItem","getJobs","setTimeout","scrollTop","document","scrollingElement","documentElement","figureActiveItem","viewportHeight","innerHeight","seenItem","i","length","current","elementOffsetTop","offsetTop","elementHeight","offsetHeight","distance","percentSeen","Math","round","open","getRating","isSkillActive","isItemActive","startDate","navItem","renderSearchMenu","renderSkills","FilteringService","str1","str2","longer","shorter","getDistance","toLowerCase","costs","lastValue","j","newValue","charAt","min","HttpService","baseUrl","response","ok","json","fetch","headers","toJson","ItemsService","filteringService","matchThreshold","createRef","filter","itemName","indexOf","getSimilarity","tags","tag","skillName","forEach","JobsService","jobs","months","date","getMonth","getFullYear","toReadableDate","Date","endDate","SKILL_RATING","SkillsService","usages","_skills","getUsages","sort","lhs","rhs","num","usage","a","httpService","get","allTags","ReactDOM","render","StrictMode","getElementById"],"mappings":"8XAeqBA,G,wDACjB,WAAYC,GAAmB,IAAD,8BAC1B,cAAMA,IAEDC,MAAL,eACOD,GAGP,EAAKE,WAAa,EAAKA,WAAWC,KAAhB,gBAClB,EAAKC,WAAa,EAAKA,WAAWD,KAAhB,gBARQ,E,oDAWdE,GACRA,GAIJC,KAAKN,MAAMO,QAAQF,K,iCAGJG,GACfA,EAAEC,iBAEFH,KAAKC,QAAQD,KAAKL,MAAMS,KAAKC,W,iCAGdH,GACfA,EAAEC,iBAEFH,KAAKC,QAAQD,KAAKL,MAAMS,KAAKE,W,0CAI7B,OAAON,KAAKL,MAAMS,KAAKG,YAAYC,KAAI,SAACC,EAAMC,GAC1C,OACI,4BAAgBD,GAARC,Q,wCAMhB,OAAIV,KAAKL,MAAMS,KAAKO,aAKhB,qBAAKC,UAAU,iBAAiBC,IAAKb,KAAKL,MAAMS,KAAKO,aAAcG,IAAKd,KAAKL,MAAMS,KAAKW,OAJjF,O,mCAQMhB,EAAwBU,EAAaG,EAAmBI,GACzE,OAAIjB,EAKA,wBAAQkB,QAASD,EAAQJ,UAAW,OAASA,EAA7C,SAA0DH,IAJnD,O,qCASX,IAAIS,EAAU,CAAC,QAMf,OAJGlB,KAAKL,MAAMwB,UACVD,EAAQE,KAAK,UAGVF,EAAQG,KAAK,O,+BAIpB,OACI,sBAAKT,UAAWZ,KAAKsB,eAAgBC,IAAKvB,KAAKL,MAAMS,KAAKmB,IAA1D,UACI,oBAAIX,UAAU,YAAd,SAA2BZ,KAAKL,MAAMS,KAAKW,OAC3C,oBAAIH,UAAU,eAAd,SAA8BZ,KAAKL,MAAMS,KAAKoB,UAC7CxB,KAAKyB,kBACN,qBAAKb,UAAU,mBAAf,SACKZ,KAAK0B,sBAEV,sBAAKd,UAAU,mBAAf,UACI,qBAAKA,UAAU,QAAf,SACKZ,KAAK2B,aAAa3B,KAAKL,MAAMS,KAAKC,QAAS,YAAa,WAAYL,KAAKJ,cAE9E,qBAAKgB,UAAU,QAAf,SACKZ,KAAK2B,aAAa3B,KAAKL,MAAMS,KAAKE,QAAS,YAAa,WAAYN,KAAKF,uB,gDAOvDJ,EAAkBC,GACrD,OAAO,eACAD,O,GA7FmBkC,IAAMC,YCDnBC,G,wDACjB,WAAYpC,GAAoB,IAAD,8BAC3B,cAAMA,IAEDC,MAAL,eACOD,GAJoB,E,2DAS3B,IAAIwB,EAAU,CAAC,SAKf,OAJGlB,KAAKL,MAAMwB,UACVD,EAAQE,KAAK,UAGVF,EAAQG,KAAK,O,+BAIpB,OACI,sBAAKT,UAAWZ,KAAKsB,eAArB,UACI,sBAAMV,UAAU,gBAAhB,SAAiCZ,KAAKL,MAAMoC,MAAMhB,KAAO,KAAOf,KAAKL,MAAMqC,OAAS,MAAQhC,KAAKL,MAAMoC,MAAMA,MAAQ,OACrH,qBAAKnB,UAAU,aAAaE,IAAKd,KAAKL,MAAMoC,MAAMhB,KAAMF,IAAK,cAAgBb,KAAKL,MAAMoC,MAAMhB,KAAO,e,gDAK1ErB,EAAmBC,GACtD,OAAO,eACAD,O,GA7BoBkC,IAAMC,YCFpBI,G,8DACjB,WAAYvC,GAAyB,IAAD,8BAChC,cAAMA,IAEDC,MAAQ,CACTuC,WAAYxC,EAAMwC,YAGtB,EAAKC,eAAiB,EAAKA,eAAetC,KAApB,gBAPU,E,2DAUbK,GACnBF,KAAKN,MAAMyC,eAAejC,EAAEkC,cAAcC,S,qCAI1C,IAAInB,EAAU,CAAC,eAKf,OAJGlB,KAAKL,MAAMuC,YACVhB,EAAQE,KAAK,QAGVF,EAAQG,KAAK,O,+BAIpB,OACI,qBAAKT,UAAWZ,KAAKsB,eAArB,SACI,uBAAOgB,KAAK,OAAOC,YAAY,SAASC,SAAUxC,KAAKmC,eAAgBvB,UAAU,2B,gDAKtDlB,EAAwBC,GAC3D,OAAO,2BACAA,GADP,IAEIuC,WAAYxC,EAAMwC,iB,GAnCUN,IAAMC,YCCzBY,G,wDAEjB,WAAY/C,GAAkB,IAAD,8BACzB,cAAMA,IAEDC,MAAQ,CACT+C,MAAOhD,EAAMgD,OAJQ,E,yDAQRtC,GACjB,IAAIc,EAAU,CAAC,gBAMf,OAJGd,EAAKe,UACJD,EAAQE,KAAK,UAGVF,EAAQG,KAAK,O,oCAGD,IAAD,OAClB,OAAOrB,KAAKL,MAAM+C,MAAMlC,KAAI,SAACJ,GACzB,OACI,wBAAuBa,QAAS,SAACf,GAC7B,EAAKR,MAAMiD,YAAYvC,IACxBQ,UAAW,EAAKU,aAAalB,GAFhC,SAEwCA,EAAKK,MAFhCL,EAAKwC,U,+BAQ1B,OACI,qBAAKhC,UAAU,MAAf,SACI,qBAAKA,UAAU,MAAf,SACKZ,KAAK6C,qB,gDAMiBnD,EAAkBC,GACrD,OAAO,2BACAA,GADP,IAEI+C,MAAOhD,EAAMgD,Y,GA3CQd,IAAMC,YCP1BiB,EACC,WADDA,EAEH,OAFGA,EAGD,SAGSC,E,iDACTL,MAAmB,CAAC,CACxBjC,KAAM,WACNmC,IAAKE,EACL3B,UAAU,GACX,CACCV,KAAM,UACNmC,IAAKE,EACL3B,UAAU,GACX,CACCV,KAAM,SACNmC,IAAKE,EACL3B,UAAU,I,0DAIV,OAAOnB,KAAK0C,Q,oCAGKE,GACjB5C,KAAK0C,MAAQ1C,KAAK0C,MAAMlC,KAAI,SAACJ,GACzB,OAAGA,EAAKwC,MAAQA,EACL,2BACAxC,GADP,IAEIe,UAAU,IAIX,2BACAf,GADP,IAEIe,UAAU,S,sCAMlB,IAAI6B,EAAahD,KAAK0C,MAAMO,MAAK,SAAC7C,GAC9B,OAAOA,EAAKe,YAGhB,OAAG6B,IAIHhD,KAAKkD,cAAclD,KAAK0C,MAAM,GAAGE,KAC1B5C,KAAK0C,MAAM,Q,KC1CLS,G,wDACjB,WAAYzD,GAAkB,IAAD,8BACzB,cAAMA,IAEDC,MAAL,eACOD,GAJkB,E,gEASzB,OAAOM,KAAKL,MAAMyD,IAAI7C,YAAYC,KAAI,SAACC,EAAMC,GACzC,OACI,4BAAgBD,GAARC,Q,wCAMhB,OAAIV,KAAKL,MAAMyD,IAAIzC,aAKf,qBAAKC,UAAU,gBAAgByC,MAAOrD,KAAKsD,WAA3C,SACI,qBAAKzC,IAAKb,KAAKL,MAAMyD,IAAIzC,aAAcG,IAAKd,KAAKL,MAAMyD,IAAIG,gBALxD,O,qCAWX,IAAIrC,EAAU,CAAC,OAMf,OAJGlB,KAAKL,MAAMwB,UACVD,EAAQE,KAAK,UAGVF,EAAQG,KAAK,O,iCAIpB,MAAO,CACHmC,gBAAiBxD,KAAKL,MAAMyD,IAAIK,c,+BAKpC,OACI,sBAAK7C,UAAWZ,KAAKsB,eAAgBC,IAAKvB,KAAKL,MAAMyD,IAAI7B,IAAzD,UACI,oBAAIX,UAAU,YAAd,SAA2BZ,KAAKL,MAAMyD,IAAIM,WACzC1D,KAAKyB,kBAIF,oBAAIb,UAAU,YAAd,SAA2BZ,KAAKN,MAAMiE,YAAYC,kBAAkB5D,KAAKL,MAAMyD,OAEnF,qBAAKxC,UAAU,kBAAf,SACKZ,KAAK0B,4B,gDAMiBhC,EAAiBC,GACpD,OAAO,eACAD,O,GAhEkBkC,IAAMC,YCalBgC,E,kDAMjB,WAAYnE,GAAkB,IAAD,uBACzB,cAAMA,IANFoE,aAAsB,EAKD,EAJrBC,qBAAuB,GAIF,EAHrBC,qBAAuB,GAGF,EAFrBC,YAAqB,GAKzB,IAAIC,EAAW,EAAKxE,MAAMyE,aAAaC,WAHd,OAKzB,EAAKzE,MAAQ,CACT0E,OAAQ,EAAK3E,MAAM4E,cAAcD,OACjC3B,MAAOwB,EACPlB,WAAYkB,EAAS,GACrBhC,YAAY,EACZqC,SAAU,EAAK7E,MAAM8E,WAAWC,eAGpC,EAAKC,SAAW,EAAKA,SAAS7E,KAAd,gBAChB,EAAK8E,WAAa,EAAKA,WAAW9E,KAAhB,gBAClB,EAAKsC,eAAiB,EAAKA,eAAetC,KAApB,gBACtB,EAAK+E,UAAY,EAAKA,UAAU/E,KAAf,gBAhBQ,E,gEAoBzBgF,OAAOC,iBAAiB,SAAU9E,KAAK0E,UACvCG,OAAOC,iBAAiB,aAAc9E,KAAK0E,Y,6CAI3CG,OAAOE,oBAAoB,SAAU/E,KAAK0E,UAC1CG,OAAOE,oBAAoB,aAAc/E,KAAK0E,Y,mCAI9C1E,KAAKgF,SAAS,CACV9C,YAAalC,KAAKL,MAAMuC,e,qCAITG,GAAgB,IAAD,OAClCrC,KAAKiE,YAAc5B,EACnB,IAAIK,EAAQ1C,KAAKN,MAAM8E,WAAWS,gBAAgBrC,MAAQE,EAAqB9C,KAAKN,MAAMyE,aAAaC,SAAS/B,GAASrC,KAAKN,MAAMiE,YAAYuB,QAAQ7C,GAExJrC,KAAKgF,SAAS,CACVtC,UACD,WACC,EAAKgC,gB,iCAIO,IAAD,OACfS,YAAW,WACP,IAAG,EAAKrB,YAAR,CAIA,EAAKA,aAAc,EAEnB,IACIsB,GADmBC,SAASC,kBAAoBD,SAASE,iBAC5BH,UAC7BpC,EAAa,EAAKwC,iBAAiBJ,GAEpCpC,EACC,EAAKgC,SAAS,CACVhC,eAIJ,EAAKgC,SAAS,CACVhC,WAAY,OAGpB,EAAKc,aAAc,KACpB,O,uCAGkBsB,GACrB,IAAIK,EAAiBZ,OAAOa,YAExBC,EAAW3F,KAAKL,MAAM+C,MAAM,GAEhC,GAAG0C,GAAa,EACZ,OAAOO,EAIX,IAAI,IAAIC,EAAI,EAAGA,EAAI5F,KAAKL,MAAM+C,MAAMmD,OAAQD,IAAK,CAC7C,IAAIrE,EAAMvB,KAAKL,MAAM+C,MAAMkD,GAAGrE,IAAIuE,QAC9BC,EAAmBxE,EAAIyE,UACvBC,EAAgB1E,EAAI2E,aACpBC,EAAWf,EAAYK,EAAiBM,EACxCK,EAAcC,KAAKC,MAAMH,IAAaV,EAAiBQ,GAAiB,MAEzEG,EAAcpG,KAAK+D,sBAAwBqC,GAAepG,KAAKgE,uBAC9D2B,EAAW3F,KAAKL,MAAM+C,MAAMkD,IAIpC,OAAOD,I,8BAGK5F,GACZ8E,OAAO0B,KAAKxG,EAAK,Y,qCAGG,IAAD,OACnB,OAAOC,KAAKL,MAAM0E,OAAO7D,KAAI,SAACuB,GAC1B,OACI,cAAC,EAAD,CAAOA,MAAOA,EAAOC,OAAQ,EAAKtC,MAAM4E,cAAckC,UAAUzE,GAAyBZ,SAAU,EAAKzB,MAAMyE,aAAasC,cAAc,EAAK9G,MAAMqD,WAAYjB,EAAMhB,OAAzFgB,EAAMhB,W,mCAK1EX,GACjB,OAAOA,IAASJ,KAAKL,MAAMqD,a,oCAGR,IAAD,OAClB,OAAGhD,KAAKN,MAAM8E,WAAWS,gBAAgBrC,MAAQE,EACrC9C,KAAKL,MAAM+C,MAAoBlC,KAAI,SAAC4C,GACxC,OAEI,aADA,CACC,EAAD,CAAKO,YAAa,EAAKjE,MAAMiE,YAAaxC,SAAU,EAAKuF,aAAatD,GAAMA,IAAKA,GAAUA,EAAIG,YAAcH,EAAIuD,cAKrH3G,KAAKL,MAAM+C,MAAqBlC,KAAI,SAACJ,GACzC,OACI,cAAC,EAAD,CAAMH,QAAS,EAAKA,QAASkB,SAAU,EAAKuF,aAAatG,GAAOA,KAAMA,GAAWA,EAAKW,W,iCAM9F,OAAIf,KAAKL,MAAMqD,WAIR,CACHQ,gBAAiBxD,KAAKL,MAAMqD,WAAWS,YAJhC,K,yCASX,OACI,cAAC,EAAD,CAAYtB,eAAgBnC,KAAKmC,eAAgBD,WAAYlC,KAAKL,MAAMuC,e,gCAI9D0E,GAAmB,IAAD,OAG7BA,EAAQhE,MAAQE,GAKnB9C,KAAKN,MAAM8E,WAAWtB,cAAc0D,EAAQhE,KAC5C5C,KAAKgF,SAAS,CACVT,SAAUvE,KAAKN,MAAM8E,WAAWC,cAChC/B,MAAO1C,KAAKN,MAAM8E,WAAWS,gBAAgBrC,MAAQE,EAAiB9C,KAAKN,MAAMiE,YAAYuB,QAAQlF,KAAKiE,aAAejE,KAAKN,MAAMyE,aAAaC,SAASpE,KAAKiE,eAChK,WACC,EAAKS,eATLG,OAAO0B,KAAK,+C,+BAchB,OACI,sBAAK3F,UAAU,sBAAsByC,MAAOrD,KAAKsD,WAAjD,UACI,yBAAQ1C,UAAU,MAAlB,UACI,qBAAKA,UAAU,MAAf,SACI,oDAEJ,qBAAKA,UAAU,4BAAf,SACI,qBAAKC,IC7Md,i8VD6M+BD,UAAU,cAAcE,IAAI,SAASG,QAASjB,KAAK2E,kBAGhF3E,KAAK6G,mBACN,cAAC,EAAD,CAAKlE,YAAa3C,KAAK4E,UAAWlC,MAAO1C,KAAKL,MAAM4E,WACpD,sBAAK3D,UAAU,MAAf,UACI,qBAAKA,UAAU,uBAAf,SACI,qBAAKA,UAAU,2BAAf,SACKZ,KAAK8G,mBAGd,qBAAKlG,UAAU,sBAAf,SACKZ,KAAK6C,0B,GA7LGjB,IAAMC,WE5BlBkF,G,4GACIC,EAAcC,GAC/B,IAAIC,EAASF,EACTG,EAAUF,EAOd,OALID,EAAKnB,OAASoB,EAAKpB,SACnBqB,EAASD,EACTE,EAAUH,GAGQ,IAAlBE,EAAOrB,OACA,GAGHqB,EAAOrB,OAAS7F,KAAKoH,YAAYF,EAAQC,IAAYD,EAAOrB,S,kCAGpDmB,EAAcC,GAC9BD,EAAOA,EAAKK,cACZJ,EAAOA,EAAKI,cAGZ,IADA,IAAIC,EAAQ,GACH1B,EAAI,EAAGA,GAAKoB,EAAKnB,OAAQD,IAAK,CAGnC,IADA,IAAI2B,EAAY3B,EACP4B,EAAI,EAAGA,GAAKP,EAAKpB,OAAQ2B,IAC9B,GAAU,IAAN5B,GAKJ,GAAI4B,EAAI,EAAG,CACP,IAAIC,EAAWH,EAAME,EAAI,GACrBR,EAAKU,OAAO9B,EAAI,KAAOqB,EAAKS,OAAOF,EAAI,KACvCC,EAAWpB,KAAKsB,IAAItB,KAAKsB,IAAIF,EAAUF,GAAYD,EAAME,IAAM,GAEnEF,EAAME,EAAI,GAAKD,EACfA,EAAYE,QAVZH,EAAME,GAAKA,EAcf5B,EAAI,IACJ0B,EAAML,EAAKpB,QAAU0B,GAI7B,OAAOD,EAAML,EAAKpB,Y,uBC5CL+B,E,WAGjB,WAAYC,GAAiB,yBAFrBA,QAAU,GAGd7H,KAAK6H,QAAUA,E,mDAGJC,GACX,GAAGA,EAASC,GACR,OAAOD,EAASE,OAGpB,MAAMF,I,mEAGO/H,G,uFACQkI,IAAMjI,KAAK6H,QAAU9H,EAAK,CAC3CmI,QAAS,CACL,eAAgB,sB,cAFpBJ,E,yBAMG9H,KAAKmI,OAAOL,I,8GCFNM,E,WAKjB,WAAYC,EAAmC3F,GAAgB,yBAJvDA,WAIsD,OAHtD4F,eAAiB,GAGqC,KAFtDD,sBAEsD,EAC1DrI,KAAK0C,MAAQA,EAAMlC,KAAI,SAACJ,GACpB,OAAO,2BACAA,GADP,IAEImB,IAAKK,IAAM2G,iBAInBvI,KAAKqI,iBAAmBA,E,uDAGU,IAAD,OAArBG,EAAqB,uDAAJ,GAC7B,OAAKA,GAAUA,EAAO3C,QAAU,EACrB7F,KAAK0C,OAGhB8F,EAASA,EAAOnB,cAETrH,KAAK0C,MAAM8F,QAAO,SAACpI,GACtB,IAAIqI,EAAWrI,EAAKW,KAAKsG,cACzB,GAAGoB,EAASC,QAAQF,IAAW,GAAK,EAAKH,iBAAiBM,cAAcF,EAAUD,GAAU,EAAKF,eAC7F,OAAO,EAGX,IAAK,IAAI1C,EAAI,EAAGA,EAAIxF,EAAKwI,KAAK/C,OAAQD,IAAK,CACvC,IAAIiD,EAAMzI,EAAKwI,KAAKhD,GAAGyB,cACvB,GAAIwB,EAAIH,QAAQF,IAAW,GAAK,EAAKH,iBAAiBM,cAAcE,EAAKL,GAAU,EAAKF,eACpF,OAAO,EAGf,OAAO,Q,oCAaMlI,EAAyB0I,GAC1C,QAAI1I,GAIGA,EAAKwI,KAAKF,QAAQI,EAAUzB,gBAAkB,I,8BAbrD,IAAIuB,EAAkB,GAKtB,OAJA5I,KAAK0C,MAAMqG,SAAQ,SAAC3I,GAChBwI,EAAKxH,KAAKhB,EAAKwI,SAGZA,M,KCjDMI,E,WAKjB,WAAYX,EAAmCY,GAAc,yBAJrDA,UAIoD,OAHpDX,eAAiB,GAGmC,KAFpDD,sBAEoD,OAwDpDa,OAAS,CAAC,MAAO,MAAO,MAAO,MAAO,MAAO,MAAO,MAAO,MAAO,MAAO,MAAO,MAAO,OAvD3FlJ,KAAKiJ,KAAOA,EAAKzI,KAAI,SAACJ,GAClB,OAAO,2BACAA,GADP,IAEImB,IAAKK,IAAM2G,iBAInBvI,KAAKqI,iBAAmBA,E,sDAGS,IAAD,OAArBG,EAAqB,uDAAJ,GAC5B,OAAKA,GAAUA,EAAO3C,QAAU,EACrB7F,KAAKiJ,MAGhBT,EAASA,EAAOnB,cAETrH,KAAKiJ,KAAKT,QAAO,SAACpF,GACrB,IAAIG,EAAcH,EAAIG,YAAY8D,cAClC,GAAG9D,EAAYmF,QAAQF,IAAW,GAAK,EAAKH,iBAAiBM,cAAcpF,EAAaiF,GAAU,EAAKF,eACnG,OAAO,EAGX,IAAI5E,EAAWN,EAAIM,SAAS2D,cAC5B,GAAG3D,EAASgF,QAAQF,IAAW,GAAK,EAAKH,iBAAiBM,cAAcjF,EAAU8E,GAAU,EAAKF,eAC7F,OAAO,EAGX,IAAK,IAAI1C,EAAI,EAAGA,EAAIxC,EAAIwF,KAAK/C,OAAQD,IAAK,CACtC,IAAIiD,EAAMzF,EAAIwF,KAAKhD,GAAGyB,cACtB,GAAIwB,EAAIH,QAAQF,IAAW,GAAK,EAAKH,iBAAiBM,cAAcE,EAAKL,GAAU,EAAKF,eACpF,OAAO,EAGf,OAAO,Q,oCAaMlF,EAAiB0F,GAClC,QAAI1F,GAIGA,EAAIwF,KAAKF,QAAQI,EAAUzB,gBAAkB,I,qCAIjC8B,GACnB,OAAOnJ,KAAKkJ,OAAOC,EAAKC,YAAc,IAAMD,EAAKE,gB,wCAG5BjG,GAIrB,OAHgBpD,KAAKsJ,eAAe,IAAIC,KAAKnG,EAAIuD,YAG9B,OAFLvD,EAAIoG,QAAUxJ,KAAKsJ,eAAe,IAAIC,KAAKnG,EAAIoG,UAAY,a,8BAvBzE,IAAIZ,EAAkB,GAKtB,OAJA5I,KAAKiJ,KAAKF,SAAQ,SAAC3F,GACfwF,EAAKxH,KAAKgC,EAAIwF,SAGXA,M,KC/DFa,EACD,SADCA,EAEL,KAFKA,EAGH,OAHGA,EAIE,YAJFA,EAKA,UAGQC,E,WAGjB,WAAYrF,GAA0C,IAAD,OAAxBsF,EAAwB,uDAAJ,GAAI,yBAF7CC,QAAmB,GAKvB5J,KAAK4J,QAAUvF,EAAO7D,KAAI,SAACuB,GACvB,OAAO,2BACAA,GADP,IAEI4H,OAAQ,EAAKE,UAAU9H,EAAO4H,QAEnCG,MAAK,SAACC,EAAKC,GACV,OAAOA,EAAIL,OAASI,EAAIJ,UACzBnJ,KAAI,SAACuB,GACJ,MAAO,CACHhB,KAAMgB,EAAMhB,KACZgB,MAAOA,EAAMA,U,sDAKPA,EAAc4H,GAC5B,IAAIM,EAAM,EAQV,OANAN,EAAOZ,SAAQ,SAACmB,GACTA,EAAMxB,QAAQ3G,EAAMhB,OAAS,GAC5BkJ,OAIDA,I,gCAOMlI,GACb,OAAGA,EAAMA,MAAQ,GACN0H,EAGR1H,EAAMA,MAAQ,GACN0H,EAGR1H,EAAMA,MAAQ,GACN0H,EAGR1H,EAAMA,MAAQ,GACN0H,EAGJA,I,6BApBP,OAAOzJ,KAAK4J,Y,KCpCpB,sBAAC,sCAAAO,EAAA,6DACOC,EAAc,IAAIxC,EAAY,IAC9BS,EAAmB,IAAItB,EAF9B,KAGyBiC,EAHzB,KAGqCX,EAHrC,SAG6D+B,EAAYC,IAAI,kBAH7E,0BAGO1G,EAHP,yBAI0ByE,EAJ1B,KAIuCC,EAJvC,UAI+D+B,EAAYC,IAAI,mBAJ/E,2BAIOlG,EAJP,yBAK2BuF,EAL3B,UAK+CU,EAAYC,IAAI,oBAL/D,yBAKoFlG,EAAamG,QAA1FhG,EALP,oBAMOE,EAAa,IAAIzB,EAEvBwH,IAASC,OACP,cAAC,IAAMC,WAAP,UACE,cAAC,EAAD,CAAKtG,aAAcA,EAAcG,cAAeA,EAAeX,YAAaA,EAAaa,WAAYA,MAEvGa,SAASqF,eAAe,SAZ3B,2CAAD,K","file":"static/js/main.838752d0.chunk.js","sourcesContent":["import React, { MouseEvent } from \"react\";\r\nimport { IItemRef } from \"../services/ItemsService\";\r\nimport \"./Item.scss\";\r\n\r\nexport interface IItemProps {\r\n item: IItemRef;\r\n gotoUrl: (url: string) => void;\r\n isActive: boolean;\r\n}\r\n\r\nexport interface IItemState {\r\n item: IItemRef;\r\n isActive: boolean;\r\n}\r\n\r\nexport default class Item extends React.Component {\r\n constructor(props:IItemProps) {\r\n super(props);\r\n\r\n this.state = {\r\n ...props\r\n };\r\n\r\n this.onViewCode = this.onViewCode.bind(this);\r\n this.onViewLive = this.onViewLive.bind(this);\r\n }\r\n \r\n private gotoUrl(url:string | undefined) {\r\n if(!url) {\r\n return;\r\n }\r\n\r\n this.props.gotoUrl(url);\r\n }\r\n\r\n private onViewCode(e:MouseEvent) {\r\n e.preventDefault();\r\n \r\n this.gotoUrl(this.state.item.codeUrl);\r\n }\r\n\r\n private onViewLive(e:MouseEvent) {\r\n e.preventDefault();\r\n\r\n this.gotoUrl(this.state.item.liveUrl);\r\n }\r\n\r\n private renderDescription() {\r\n return this.state.item.description.map((text, index) => {\r\n return (\r\n

{text}

\r\n );\r\n });\r\n }\r\n\r\n private renderThumbnail() {\r\n if(!this.state.item.thumbnailUrl) {\r\n return null;\r\n }\r\n\r\n return (\r\n {this.state.item.name}\r\n );\r\n }\r\n \r\n private renderButton(url:string | undefined, text:string, className: string, method:(e:MouseEvent) => void) {\r\n if(!url) {\r\n return null;\r\n }\r\n\r\n return (\r\n \r\n );\r\n }\r\n\r\n private getClassName() {\r\n let classes = [\"item\"];\r\n\r\n if(this.state.isActive) {\r\n classes.push(\"active\");\r\n }\r\n\r\n return classes.join(\" \");\r\n }\r\n\r\n public render() {\r\n return (\r\n
\r\n

{this.state.item.name}

\r\n

{this.state.item.summary}

\r\n {this.renderThumbnail()}\r\n
\r\n {this.renderDescription()}\r\n
\r\n
\r\n
\r\n {this.renderButton(this.state.item.codeUrl, \"View Code\", \"btn-code\", this.onViewCode)}\r\n
\r\n
\r\n {this.renderButton(this.state.item.liveUrl, \"View Live\", \"btn-live\", this.onViewLive)}\r\n
\r\n
\r\n
\r\n );\r\n }\r\n\r\n public static getDerivedStateFromProps(props:IItemProps, state:IItemState) {\r\n return {\r\n ...props\r\n };\r\n }\r\n}","import React from \"react\";\r\nimport { ISkill } from \"../services/SkillsService\";\r\nimport \"./Skill.scss\";\r\n\r\nexport interface ISkillProps {\r\n skill: ISkill;\r\n isActive: boolean;\r\n rating: string;\r\n}\r\n\r\nexport interface ISkillState extends ISkillProps {\r\n\r\n}\r\n\r\nexport default class Skill extends React.Component {\r\n constructor(props:ISkillProps) {\r\n super(props);\r\n\r\n this.state = {\r\n ...props\r\n };\r\n }\r\n\r\n private getClassName() {\r\n let classes = [\"skill\"];\r\n if(this.state.isActive) {\r\n classes.push(\"active\");\r\n }\r\n\r\n return classes.join(\" \");\r\n }\r\n\r\n public render() {\r\n return (\r\n
\r\n {this.state.skill.name + \" (\" + this.state.rating + \" | \" + this.state.skill.skill + \"%)\"}\r\n {this.state.skill.name}\r\n
\r\n );\r\n }\r\n\r\n public static getDerivedStateFromProps(props:ISkillProps, state:ISkillState) {\r\n return {\r\n ...props\r\n };\r\n }\r\n}","import React, { ChangeEvent } from \"react\";\r\nimport \"./SearchMenu.scss\";\r\n\r\nexport interface ISearchMenuProps {\r\n isMenuOpen: boolean;\r\n onSearchChange: (value:string) => void;\r\n}\r\n\r\nexport interface ISearchMenuState {\r\n isMenuOpen: boolean;\r\n}\r\n\r\nexport default class SearchMenu extends React.Component {\r\n constructor(props:ISearchMenuProps) {\r\n super(props);\r\n\r\n this.state = {\r\n isMenuOpen: props.isMenuOpen\r\n };\r\n\r\n this.onSearchChange = this.onSearchChange.bind(this);\r\n }\r\n \r\n private onSearchChange(e:ChangeEvent) {\r\n this.props.onSearchChange(e.currentTarget.value);\r\n }\r\n\r\n private getClassName() {\r\n let classes = [\"search-menu\"];\r\n if(this.state.isMenuOpen) {\r\n classes.push(\"open\");\r\n }\r\n\r\n return classes.join(\" \");\r\n }\r\n\r\n public render() {\r\n return (\r\n
\r\n \r\n
\r\n );\r\n }\r\n\r\n public static getDerivedStateFromProps(props:ISearchMenuProps, state:ISearchMenuState) {\r\n return {\r\n ...state,\r\n isMenuOpen: props.isMenuOpen\r\n };\r\n }\r\n}","import React from \"react\";\r\nimport { INavItem } from \"../services/NavService\";\r\nimport \"./Nav.scss\";\r\n\r\nexport interface INavProps {\r\n onItemClick: (navItem: INavItem) => void;\r\n items: INavItem[];\r\n}\r\n\r\nexport interface INavState {\r\n items: INavItem[];\r\n}\r\n\r\nexport default class Nav extends React.Component {\r\n\r\n constructor(props:INavProps) {\r\n super(props);\r\n\r\n this.state = {\r\n items: props.items\r\n };\r\n }\r\n\r\n private getClassName(item:INavItem) {\r\n let classes = [\"btn nav-item\"];\r\n\r\n if(item.isActive) {\r\n classes.push(\"active\");\r\n }\r\n\r\n return classes.join(\" \");\r\n }\r\n\r\n private renderItems() {\r\n return this.state.items.map((item) => {\r\n return (\r\n \r\n );\r\n });\r\n }\r\n\r\n public render() {\r\n return (\r\n \r\n );\r\n }\r\n\r\n public static getDerivedStateFromProps(props: INavProps, state: INavState) {\r\n return {\r\n ...state,\r\n items: props.items\r\n };\r\n }\r\n}\r\n","export interface INavItem {\r\n text: string;\r\n key: string;\r\n isActive: boolean;\r\n}\r\n\r\nexport const NAV_ITEMS = {\r\n PROJECTS: \"projects\",\r\n JOBS: \"jobs\",\r\n RESUME: \"resume\"\r\n}\r\n\r\nexport default class NavService {\r\n private items:INavItem[] = [{\r\n text: \"Projects\",\r\n key: NAV_ITEMS.PROJECTS,\r\n isActive: true\r\n }, {\r\n text: \"History\",\r\n key: NAV_ITEMS.JOBS,\r\n isActive: false\r\n }, {\r\n text: \"Resume\",\r\n key: NAV_ITEMS.RESUME,\r\n isActive: false\r\n }];\r\n\r\n public getNavItems():INavItem[] {\r\n return this.items;\r\n }\r\n\r\n public setActiveItem(key:string) {\r\n this.items = this.items.map((item) => {\r\n if(item.key === key) {\r\n return {\r\n ...item,\r\n isActive: true\r\n };\r\n }\r\n\r\n return {\r\n ...item,\r\n isActive: false\r\n };\r\n });\r\n }\r\n\r\n public getActiveItem():INavItem {\r\n let activeItem = this.items.find((item) => {\r\n return item.isActive;\r\n });\r\n\r\n if(activeItem) {\r\n return activeItem;\r\n }\r\n\r\n this.setActiveItem(this.items[0].key);\r\n return this.items[0];\r\n }\r\n}","import React from \"react\";\r\nimport JobsService, { IJobRef } from \"../services/JobsService\";\r\nimport \"./Job.scss\";\r\n\r\nexport interface IJobProps {\r\n job: IJobRef;\r\n isActive: boolean;\r\n jobsService: JobsService;\r\n}\r\n\r\nexport interface IJobState {\r\n job: IJobRef;\r\n isActive: boolean;\r\n}\r\n\r\nexport default class Job extends React.Component {\r\n constructor(props:IJobProps) {\r\n super(props);\r\n\r\n this.state = {\r\n ...props\r\n };\r\n }\r\n\r\n private renderDescription() {\r\n return this.state.job.description.map((text, index) => {\r\n return (\r\n

{text}

\r\n );\r\n });\r\n }\r\n\r\n private renderThumbnail() {\r\n if(!this.state.job.thumbnailUrl) {\r\n return null;\r\n }\r\n\r\n return (\r\n
\r\n {this.state.job.companyName}\r\n
\r\n );\r\n }\r\n\r\n private getClassName() {\r\n let classes = [\"job\"];\r\n\r\n if(this.state.isActive) {\r\n classes.push(\"active\");\r\n }\r\n\r\n return classes.join(\" \");\r\n }\r\n\r\n private getStyle() {\r\n return {\r\n backgroundColor: this.state.job.themeColor\r\n };\r\n }\r\n\r\n public render() {\r\n return (\r\n
\r\n

{this.state.job.jobTitle}

\r\n {this.renderThumbnail()}\r\n {/*
*/}\r\n {/*

{this.state.job.companyName}

*/}\r\n {/*

{this.props.jobsService.getJobDateDisplay(this.state.job)}

*/}\r\n

{this.props.jobsService.getJobDateDisplay(this.state.job)}

\r\n {/*
*/}\r\n
\r\n {this.renderDescription()}\r\n
\r\n
\r\n );\r\n }\r\n\r\n public static getDerivedStateFromProps(props:IJobProps, state:IJobState) {\r\n return {\r\n ...props\r\n };\r\n }\r\n}","import React, { MouseEvent } from \"react\";\r\nimport ItemsService, { IItemRef } from \"../services/ItemsService\";\r\nimport SkillsService, { ISkill } from \"../services/SkillsService\";\r\nimport Item from \"./Item\";\r\nimport Skill from \"./Skill\";\r\nimport \"./App.scss\";\r\nimport searchIcon from \"../img/magnifying-glass.png\";\r\nimport SearchMenu from \"./SearchMenu\";\r\nimport Nav from \"./Nav\";\r\nimport JobsService, { IJobRef } from \"../services/JobsService\";\r\nimport NavService, { INavItem, NAV_ITEMS } from \"../services/NavService\";\r\nimport Job from \"./Job\";\r\n\r\nexport interface IAppProps {\r\n itemsService:ItemsService;\r\n skillsService:SkillsService;\r\n jobsService:JobsService;\r\n navService:NavService;\r\n}\r\n\r\nexport interface IAppState {\r\n skills: ISkill[];\r\n items: IItemRef[] | IJobRef[];\r\n activeItem: IItemRef | IJobRef | null;\r\n navItems: INavItem[];\r\n isMenuOpen: boolean;\r\n}\r\n\r\nexport default class App extends React.Component {\r\n private isScrolling:boolean = false;\r\n private minActivePercentSeen = 20;\r\n private maxActivePercentSeen = 75;\r\n private searchValue:string = \"\";\r\n\r\n constructor(props:IAppProps) {\r\n super(props);\r\n\r\n let projects = this.props.itemsService.getItems();\r\n \r\n this.state = {\r\n skills: this.props.skillsService.skills,\r\n items: projects,\r\n activeItem: projects[0],\r\n isMenuOpen: false,\r\n navItems: this.props.navService.getNavItems()\r\n };\r\n\r\n this.onScroll = this.onScroll.bind(this);\r\n this.toggleMenu = this.toggleMenu.bind(this);\r\n this.onSearchChange = this.onSearchChange.bind(this);\r\n this.handleNav = this.handleNav.bind(this);\r\n }\r\n\r\n public componentDidMount() {\r\n window.addEventListener(\"scroll\", this.onScroll);\r\n window.addEventListener(\"mousewheel\", this.onScroll);\r\n }\r\n\r\n public componentWillUnmount() {\r\n window.removeEventListener(\"scroll\", this.onScroll);\r\n window.removeEventListener(\"mousewheel\", this.onScroll);\r\n }\r\n\r\n private toggleMenu() {\r\n this.setState({\r\n isMenuOpen: !this.state.isMenuOpen\r\n });\r\n }\r\n\r\n private onSearchChange(value: string) {\r\n this.searchValue = value;\r\n let items = this.props.navService.getActiveItem().key === NAV_ITEMS.PROJECTS ? this.props.itemsService.getItems(value) : this.props.jobsService.getJobs(value);\r\n\r\n this.setState({\r\n items\r\n }, () => {\r\n this.onScroll();\r\n });\r\n }\r\n\r\n private onScroll() {\r\n setTimeout(() => {\r\n if(this.isScrolling) {\r\n return;\r\n }\r\n\r\n this.isScrolling = true;\r\n\r\n let scrollingElement = document.scrollingElement || document.documentElement,\r\n scrollTop = scrollingElement.scrollTop,\r\n activeItem = this.figureActiveItem(scrollTop);\r\n\r\n if(activeItem) {\r\n this.setState({\r\n activeItem\r\n });\r\n }\r\n else {\r\n this.setState({\r\n activeItem: null\r\n });\r\n }\r\n this.isScrolling = false;\r\n }, 100);\r\n }\r\n\r\n private figureActiveItem(scrollTop:number) {\r\n let viewportHeight = window.innerHeight;\r\n\r\n let seenItem = this.state.items[0];\r\n\r\n if(scrollTop <= 0) {\r\n return seenItem;\r\n }\r\n\r\n // the first item whose percent seen is > 0 and < 75\r\n for(let i = 0; i < this.state.items.length; i++) {\r\n let ref = this.state.items[i].ref.current,\r\n elementOffsetTop = ref.offsetTop,\r\n elementHeight = ref.offsetHeight,\r\n distance = scrollTop + viewportHeight - elementOffsetTop,\r\n percentSeen = Math.round(distance / ((viewportHeight + elementHeight) / 100));\r\n\r\n if(percentSeen > this.minActivePercentSeen && percentSeen <= this.maxActivePercentSeen) {\r\n seenItem = this.state.items[i];\r\n }\r\n }\r\n\r\n return seenItem;\r\n }\r\n\r\n private gotoUrl(url:string) {\r\n window.open(url, \"_blank\");\r\n }\r\n\r\n private renderSkills() {\r\n return this.state.skills.map((skill) => {\r\n return (\r\n \r\n );\r\n });\r\n }\r\n\r\n private isItemActive(item:IItemRef | IJobRef) {\r\n return item === this.state.activeItem;\r\n }\r\n\r\n private renderItems() {\r\n if(this.props.navService.getActiveItem().key === NAV_ITEMS.JOBS) {\r\n return (this.state.items as IJobRef[]).map((job) => {\r\n return (\r\n // key is composite because i've worked at the same place a few times\r\n \r\n );\r\n });\r\n }\r\n\r\n return (this.state.items as IItemRef[]).map((item) => {\r\n return (\r\n \r\n );\r\n });\r\n }\r\n\r\n private getStyle() {\r\n if(!this.state.activeItem) {\r\n return {};\r\n }\r\n\r\n return {\r\n backgroundColor: this.state.activeItem.themeColor\r\n };\r\n }\r\n\r\n private renderSearchMenu() {\r\n return (\r\n \r\n );\r\n }\r\n\r\n private handleNav(navItem:INavItem) {\r\n \r\n // kludge because this was handled after the fact. could definitely be more elegant.\r\n if(navItem.key === NAV_ITEMS.RESUME) {\r\n window.open(\"http://www.jonathanadamski.com/resume.pdf\");\r\n return;\r\n }\r\n\r\n this.props.navService.setActiveItem(navItem.key);\r\n this.setState({\r\n navItems: this.props.navService.getNavItems(),\r\n items: this.props.navService.getActiveItem().key === NAV_ITEMS.JOBS ? this.props.jobsService.getJobs(this.searchValue) : this.props.itemsService.getItems(this.searchValue)\r\n }, () => {\r\n this.onScroll();\r\n });\r\n }\r\n\r\n public render() {\r\n return (\r\n
\r\n
\r\n
\r\n

Jonathan Adamski

\r\n
\r\n
\r\n \"Search\"\r\n
\r\n
\r\n {this.renderSearchMenu()}\r\n
\r\n );\r\n }\r\n}\r\n","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDYuMC1jMDAzIDc5LjE2NDUyNywgMjAyMC8xMC8xNS0xNzo0ODozMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIyLjEgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjExNThCOUM2NTJGNTExRUJCNzIyOTA1OUFCRUQ5NUY3IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjExNThCOUM3NTJGNTExRUJCNzIyOTA1OUFCRUQ5NUY3Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MTE1OEI5QzQ1MkY1MTFFQkI3MjI5MDU5QUJFRDk1RjciIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MTE1OEI5QzU1MkY1MTFFQkI3MjI5MDU5QUJFRDk1RjciLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7nXsL2AAAdMElEQVR42uxdeZQd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\"","export default class FilteringService {\r\n public getSimilarity(str1: string, str2: string) {\r\n let longer = str1,\r\n shorter = str2;\r\n\r\n if (str1.length < str2.length) {\r\n longer = str2;\r\n shorter = str1;\r\n }\r\n\r\n if (longer.length === 0) {\r\n return 1;\r\n }\r\n\r\n return (longer.length - this.getDistance(longer, shorter)) / longer.length;\r\n }\r\n\r\n private getDistance(str1: string, str2: string) {\r\n str1 = str1.toLowerCase();\r\n str2 = str2.toLowerCase();\r\n\r\n let costs = [];\r\n for (let i = 0; i <= str1.length; i++) {\r\n\r\n let lastValue = i;\r\n for (let j = 0; j <= str2.length; j++) {\r\n if (i === 0) {\r\n costs[j] = j;\r\n continue;\r\n }\r\n\r\n if (j > 0) {\r\n let newValue = costs[j - 1];\r\n if (str1.charAt(i - 1) !== str2.charAt(j - 1)) {\r\n newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;\r\n }\r\n costs[j - 1] = lastValue;\r\n lastValue = newValue;\r\n }\r\n }\r\n\r\n if (i > 0) {\r\n costs[str2.length] = lastValue;\r\n }\r\n }\r\n\r\n return costs[str2.length];\r\n }\r\n}","import fetch from \"cross-fetch\";\r\n\r\nexport default class HttpService {\r\n private baseUrl = \"\";\r\n\r\n constructor(baseUrl:string) {\r\n this.baseUrl = baseUrl;\r\n }\r\n\r\n private toJson(response:Response) {\r\n if(response.ok) {\r\n return response.json();\r\n }\r\n\r\n throw response;\r\n }\r\n\r\n public async get(url:string) {\r\n let response = await fetch(this.baseUrl + url, {\r\n headers: {\r\n \"Content-Type\": \"application/json\"\r\n }\r\n });\r\n \r\n return this.toJson(response);\r\n }\r\n}\r\n","import React, { RefObject } from \"react\";\r\nimport ITaggedItem from \"../interfaces/TaggedItem\";\r\nimport FilteringService from \"./FilteringService\";\r\n\r\nexport interface IItem extends ITaggedItem {\r\n name: string;\r\n summary: string;\r\n description: string[];\r\n codeUrl?: string;\r\n liveUrl?: string;\r\n thumbnailUrl?: string;\r\n awardsUrls?: {\r\n where: string,\r\n url: string\r\n }[];\r\n themeColor: string;\r\n}\r\n\r\nexport interface IItemRef extends IItem {\r\n ref: RefObject;\r\n}\r\n\r\nexport default class ItemsService {\r\n private items:IItemRef[];\r\n private matchThreshold = .6;\r\n private filteringService:FilteringService;\r\n\r\n constructor(filteringService:FilteringService, items:IItem[]) {\r\n this.items = items.map((item) => {\r\n return {\r\n ...item,\r\n ref: React.createRef()\r\n };\r\n });\r\n\r\n this.filteringService = filteringService;\r\n }\r\n\r\n public getItems(filter: string = \"\") {\r\n if (!filter || filter.length <= 3) {\r\n return this.items;\r\n }\r\n\r\n filter = filter.toLowerCase();\r\n\r\n return this.items.filter((item) => {\r\n let itemName = item.name.toLowerCase();\r\n if(itemName.indexOf(filter) > -1 || this.filteringService.getSimilarity(itemName, filter) > this.matchThreshold) {\r\n return true;\r\n }\r\n\r\n for (let i = 0; i < item.tags.length; i++) {\r\n let tag = item.tags[i].toLowerCase();\r\n if (tag.indexOf(filter) > -1 || this.filteringService.getSimilarity(tag, filter) > this.matchThreshold) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n });\r\n }\r\n\r\n public get allTags() {\r\n let tags:string[][] = [];\r\n this.items.forEach((item) => {\r\n tags.push(item.tags);\r\n });\r\n\r\n return tags;\r\n }\r\n\r\n public isSkillActive(item:ITaggedItem | null, skillName:string) {\r\n if(!item) {\r\n return false;\r\n }\r\n\r\n return item.tags.indexOf(skillName.toLowerCase()) > -1;\r\n }\r\n}","import React, { RefObject } from \"react\";\r\nimport ITaggedItem from \"../interfaces/TaggedItem\";\r\nimport FilteringService from \"./FilteringService\";\r\n\r\nexport interface IJob extends ITaggedItem {\r\n companyName: string;\r\n jobTitle: string;\r\n description: string[];\r\n startDate: number;\r\n endDate?: number;\r\n thumbnailUrl?: string;\r\n themeColor: string;\r\n}\r\n\r\nexport interface IJobRef extends IJob {\r\n ref: RefObject;\r\n}\r\n\r\nexport default class JobsService {\r\n private jobs:IJobRef[];\r\n private matchThreshold = .6;\r\n private filteringService:FilteringService;\r\n\r\n constructor(filteringService:FilteringService, jobs:IJob[]) {\r\n this.jobs = jobs.map((item) => {\r\n return {\r\n ...item,\r\n ref: React.createRef()\r\n };\r\n });\r\n\r\n this.filteringService = filteringService;\r\n }\r\n\r\n public getJobs(filter: string = \"\") {\r\n if (!filter || filter.length <= 3) {\r\n return this.jobs;\r\n }\r\n\r\n filter = filter.toLowerCase();\r\n\r\n return this.jobs.filter((job) => {\r\n let companyName = job.companyName.toLowerCase();\r\n if(companyName.indexOf(filter) > -1 || this.filteringService.getSimilarity(companyName, filter) > this.matchThreshold) {\r\n return true;\r\n }\r\n \r\n let jobTitle = job.jobTitle.toLowerCase();\r\n if(jobTitle.indexOf(filter) > -1 || this.filteringService.getSimilarity(jobTitle, filter) > this.matchThreshold) {\r\n return true;\r\n }\r\n\r\n for (let i = 0; i < job.tags.length; i++) {\r\n let tag = job.tags[i].toLowerCase();\r\n if (tag.indexOf(filter) > -1 || this.filteringService.getSimilarity(tag, filter) > this.matchThreshold) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n });\r\n }\r\n\r\n public get allTags() {\r\n let tags:string[][] = [];\r\n this.jobs.forEach((job) => {\r\n tags.push(job.tags);\r\n });\r\n\r\n return tags;\r\n }\r\n\r\n public isSkillActive(job:IJob | null, skillName:string) {\r\n if(!job) {\r\n return false;\r\n }\r\n\r\n return job.tags.indexOf(skillName.toLowerCase()) > -1;\r\n }\r\n\r\n private months = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"];\r\n private toReadableDate(date:Date) {\r\n return this.months[date.getMonth()] + \" \" + date.getFullYear();\r\n }\r\n\r\n public getJobDateDisplay(job:IJob) {\r\n let startDate = this.toReadableDate(new Date(job.startDate)),\r\n endDate = job.endDate ? this.toReadableDate(new Date(job.endDate)) : \"Present\";\r\n\r\n return startDate + \" - \" + endDate;\r\n }\r\n}","export interface ISkill {\r\n name: string;\r\n skill: number;\r\n}\r\n\r\nexport const SKILL_RATING = {\r\n NOVICE: \"novice\",\r\n OK: \"ok\",\r\n GOOD: \"good\",\r\n EXCELLENT: \"excellent\",\r\n MASTERY: \"mastery\"\r\n};\r\n\r\nexport default class SkillsService {\r\n private _skills:ISkill[] = [];\r\n\r\n constructor(skills:ISkill[], usages:string[][] = []) {\r\n \r\n // sort each skill according to it's number of usages\r\n this._skills = skills.map((skill) => {\r\n return {\r\n ...skill,\r\n usages: this.getUsages(skill, usages)\r\n };\r\n }).sort((lhs, rhs) => {\r\n return rhs.usages - lhs.usages;\r\n }).map((skill) => {\r\n return {\r\n name: skill.name,\r\n skill: skill.skill\r\n };\r\n });\r\n }\r\n\r\n private getUsages(skill:ISkill, usages:string[][]) {\r\n let num = 0;\r\n\r\n usages.forEach((usage) => {\r\n if(usage.indexOf(skill.name) > -1) {\r\n num++;\r\n }\r\n });\r\n\r\n return num;\r\n }\r\n\r\n public get skills():ISkill[] {\r\n return this._skills;\r\n }\r\n\r\n public getRating(skill:ISkill) {\r\n if(skill.skill < 60) {\r\n return SKILL_RATING.NOVICE;\r\n }\r\n\r\n if(skill.skill < 70) {\r\n return SKILL_RATING.OK;\r\n }\r\n\r\n if(skill.skill < 80) {\r\n return SKILL_RATING.GOOD\r\n }\r\n\r\n if(skill.skill < 90) {\r\n return SKILL_RATING.EXCELLENT;\r\n }\r\n\r\n return SKILL_RATING.MASTERY;\r\n }\r\n}","import React from \"react\";\r\nimport ReactDOM from \"react-dom\";\r\nimport App from \"./components/App\";\r\nimport \"./index.scss\";\r\nimport FilteringService from \"./services/FilteringService\";\r\nimport HttpService from \"./services/HttpService\";\r\nimport ItemsService from \"./services/ItemsService\";\r\nimport JobsService from \"./services/JobsService\";\r\nimport NavService from \"./services/NavService\";\r\nimport SkillsService from \"./services/SkillsService\";\r\n\r\n(async () => {\r\n const httpService = new HttpService(\"\");\r\n const filteringService = new FilteringService();\r\n const jobsService = new JobsService(filteringService, await httpService.get(\"/api/jobs.json\"));\r\n const itemsService = new ItemsService(filteringService, await httpService.get(\"/api/items.json\"));\r\n const skillsService = new SkillsService(await httpService.get(\"/api/skills.json\"), itemsService.allTags);\r\n const navService = new NavService();\r\n\r\n ReactDOM.render(\r\n \r\n \r\n ,\r\n document.getElementById(\"root\")\r\n );\r\n})();\r\n"],"sourceRoot":""}