From c1949eb5f92fa1d1e9ec5a410a5d56155cdeda15 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Mon, 17 Mar 2025 14:27:33 -0700 Subject: [PATCH] Add parsing of AD1 files. Memorize window size and location. --- _reference/ems/MPI_1/3698420.ENV | Bin 0 -> 1065 bytes _reference/ems/MPI_1/3698420.LIN | Bin 0 -> 17699 bytes _reference/ems/MPI_1/3698420.PFH | Bin 0 -> 785 bytes _reference/ems/MPI_1/3698420.PFL | Bin 0 -> 1239 bytes _reference/ems/MPI_1/3698420.PFM | Bin 0 -> 2123 bytes _reference/ems/MPI_1/3698420.PFO | Bin 0 -> 911 bytes _reference/ems/MPI_1/3698420.PFP | Bin 0 -> 868 bytes _reference/ems/MPI_1/3698420.PFT | Bin 0 -> 4799 bytes _reference/ems/MPI_1/3698420.STL | Bin 0 -> 3712 bytes _reference/ems/MPI_1/3698420.TTL | Bin 0 -> 609 bytes _reference/ems/MPI_1/3698420.VEN | Bin 0 -> 2068 bytes _reference/ems/MPI_1/3698420A.AD1 | Bin 0 -> 6120 bytes _reference/ems/MPI_1/3698420A.dbt | Bin 0 -> 24 bytes _reference/ems/MPI_1/3698420B.AD2 | Bin 0 -> 5613 bytes _reference/ems/MPI_1/3698420B.dbt | Bin 0 -> 24 bytes _reference/ems/MPI_1/3698420V.VEH | Bin 0 -> 1302 bytes _reference/ems/MPI_1/3698420V.dbt | Bin 0 -> 1024 bytes electron-builder.yml | 10 +- package-lock.json | 8 + package.json | 1 + src/main/decoder/decode-ad1.interface.ts | 147 ++++++++++++ src/main/decoder/decode-ad1.ts | 219 ++++++++++++++++++ src/main/decoder/decoder.ts | 19 ++ src/main/index.test.ts | 4 +- src/main/index.ts | 27 ++- src/main/ipc/ipcMainConfig.ts | 24 +- src/main/store/store.ts | 10 +- src/main/watcher/watcher.ts | 12 +- src/renderer/src/App.tsx | 3 +- src/renderer/src/components/Home/Home.tsx | 21 ++ .../Settings/Settings.WatchedPaths.tsx | 4 +- src/util/deepLowercaseKeys.ts | 32 +++ src/util/ipcTypes.json | 3 + 33 files changed, 524 insertions(+), 20 deletions(-) create mode 100644 _reference/ems/MPI_1/3698420.ENV create mode 100644 _reference/ems/MPI_1/3698420.LIN create mode 100644 _reference/ems/MPI_1/3698420.PFH create mode 100644 _reference/ems/MPI_1/3698420.PFL create mode 100644 _reference/ems/MPI_1/3698420.PFM create mode 100644 _reference/ems/MPI_1/3698420.PFO create mode 100644 _reference/ems/MPI_1/3698420.PFP create mode 100644 _reference/ems/MPI_1/3698420.PFT create mode 100644 _reference/ems/MPI_1/3698420.STL create mode 100644 _reference/ems/MPI_1/3698420.TTL create mode 100644 _reference/ems/MPI_1/3698420.VEN create mode 100644 _reference/ems/MPI_1/3698420A.AD1 create mode 100644 _reference/ems/MPI_1/3698420A.dbt create mode 100644 _reference/ems/MPI_1/3698420B.AD2 create mode 100644 _reference/ems/MPI_1/3698420B.dbt create mode 100644 _reference/ems/MPI_1/3698420V.VEH create mode 100644 _reference/ems/MPI_1/3698420V.dbt create mode 100644 src/main/decoder/decode-ad1.interface.ts create mode 100644 src/main/decoder/decode-ad1.ts create mode 100644 src/main/decoder/decoder.ts create mode 100644 src/renderer/src/components/Home/Home.tsx create mode 100644 src/util/deepLowercaseKeys.ts diff --git a/_reference/ems/MPI_1/3698420.ENV b/_reference/ems/MPI_1/3698420.ENV new file mode 100644 index 0000000000000000000000000000000000000000..392749fb8d42421b5afa8060c4dcc05ff02544be GIT binary patch literal 1065 zcmbV~u};G<5Qc*y5Fi*>nLII8j+@e=P9|~HNQoWnYfvXY3D3)G;IrGxbCDj&iJ7%#*{l}?s{zv7w-4Kp*x)K zF7fC0?*Io(exDCeC+1Edwgmh~F literal 0 HcmV?d00001 diff --git a/_reference/ems/MPI_1/3698420.LIN b/_reference/ems/MPI_1/3698420.LIN new file mode 100644 index 0000000000000000000000000000000000000000..de703f207c16f7e331ab8abb6aa56cda104b5205 GIT binary patch literal 17699 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 NMr!5uiOIU%;RDmhUcCSS literal 0 HcmV?d00001 diff --git a/_reference/ems/MPI_1/3698420.PFL b/_reference/ems/MPI_1/3698420.PFL new file mode 100644 index 0000000000000000000000000000000000000000..d97d93c3127c3f512e283b48ee74fa3ae5ce3d2e GIT binary patch literal 1239 zcma)(-A;or6o7Fv#>9AG;+0qR0VMoQ=ap$`*=pKC3MAVVPvX1V!;ZrtNKXQ7`1+ln zvi@2>+yz1Ky!vqOzoD4znqws#$$?&vjsWMP2G;Y(32tcvz5#Tzx;)V5r-x3OKmobB z!~yckeeV6+Q3Jp!|NNIX2RA}|>Tgv3lpo3?pR5mlvfv}1_0t6(`K+HU_{eAd&4Q17 z?B5eb89b$ZVI@D3A-3BvOB+Jo!pH?75w61t*eP27(!2UheRYev6@@gi-eUq^eTVwa zH8K<@zIuUrVW}49-pRWg`qg80jk*^3o*J1`1v%m<4L6a8-lAS{JJfbNxawlK@wuB% uQKzca(zN;~ho_#Qo>R+8Lkh`6--f3iqu$m|=}+HNinO=@ literal 0 HcmV?d00001 diff --git a/_reference/ems/MPI_1/3698420.PFM b/_reference/ems/MPI_1/3698420.PFM new file mode 100644 index 0000000000000000000000000000000000000000..5aaa1520cf99046f9c2f013c34dc34ac0cfd24e1 GIT binary patch literal 2123 zcmchV!Arw16vm^QOb;FePo8q|TD!Je=`ps2PTH-s0qdy@olF=EHo=nz|04fI^O6+0 zu33iA1=i7g?|b>>>+5~@ZUzXUpWZKf?yrQh>#Czzft0oxb$ZMlZAfPq6c9P zUhcnb(GyYxafYTKU7hb1K>swGq->Y#mkj{W^Uzo7jK5?xN?r61X(Z2Y>!0TIpw2{+ zP{B87;g27)WHJ?czY!3e4>~Ch>KP4%e$mo@mhvya^EG$lLzEQdWE#M343Ea~2=ZYy z%6vd4!P7yV7HmR>;`=u6M1Me#dh7Y%w={mKEB&^{FLkBg(fFmV^p7-tsW<)CI3X#q zq7BaGp9}o;K$n}vcDY`?VT?Vt49~;ZG)%Vk{zqeD?AG_8+=S}tXU@{0%{m>uD8_E% zuw8FL^5~zqtg~g;arwO}RQ}QdZuG3cW%qIn&)rLvgD_0TGg!E_)K7wIHXg*g%Pup8e4E`x{iM7mLNpa4V_T6T`-mJ%e{hs-nc6*D;T{Lt+-Hetpw#nyw8w!`&%gim_0pl&4t+zscIaE;wL{+#uO0fHcYy E2X*~jBme*a literal 0 HcmV?d00001 diff --git a/_reference/ems/MPI_1/3698420.PFP b/_reference/ems/MPI_1/3698420.PFP new file mode 100644 index 0000000000000000000000000000000000000000..42bff6114e7c5b151b39ebaff32643d440c5ac12 GIT binary patch literal 868 zcmb7<%L>9U5JksU(2WbXuErlI^@ZyY6^d;b(g=0cFY({a)VPUFV`h;A&OO6T{>s-2 z0N7@QJ%5L^>CKa_mPn>^FEVh^^_7B}(Ja8V0mW$PEnvU7KZsz8+XfKcf2mqI<(tla zVfX2ujrHUDj*s?de;@H35Bi6Q?|9HZMtsME{wd-+?)xhwq0k_ni_#8aIo!AJ1HB

cb&Bs^6Fg$+v`#PSw$9nhsVZHo39fsj+JG_3;dO19--%sZ_|AFV<&Zql> z&fi|WLEE42*7p9}{@t7ZKSDdd|JB}}_st(Gf8>4hy$3&zdEfl0@+aOm-+S=mnD@<} zD}UyF^SuW@j(OkwrSccvH{W~k;~4gPpYacZKF6ZZI8XZ=i$2GqPx^}A> z-~0Z4Hb3c8zWYKS`;_lJ_(`Ag-52`Ur+n|hPx_SazR<@$<$Di)*=Jl3^f?uM#(CQ3 zRP;F&ead%V{r&7d_9@@{{(d$;=~KS@LLd8-?>+cQpYq)o`q-y@@4-*{l<&UK$3EqI z4}RHad_d@PF8Yk~w9mQdb1wRn@4ou`*?sI&zW4q8Y<|+GeD{Su_9@?c@RL5}yD#*y zPx;=1pY$o;eW8zi%J&}pvd?${^tlv$#(CQ3QuMhLead%V{r&7d_9@@{{(d$;=~KS@ zLLd8-?>+cQpYq)o`q-y@@4-*{l<&UK$3EqI4}RI_5&JwAea3m(=dtMXSoA61ef9UV z``D*^@B91N{G?C$?hAeFQ@;1$Cw^1TN?=~KS@LLd8-?>+d~=ljp`xNTow ZZsVzaZ_oPm-w)69_ZRu)wx6}UPfuab^a=m~ literal 0 HcmV?d00001 diff --git a/_reference/ems/MPI_1/3698420.STL b/_reference/ems/MPI_1/3698420.STL new file mode 100644 index 0000000000000000000000000000000000000000..27f2d55afee89553c375cd54506d1c1b5202bf96 GIT binary patch literal 3712 zcmd5;%T59@6h+X53m0x&xat=)eGHwMUv3I4P9wo{-m9jTgF5}4A{ zb9>Ic>75Qf2Df*;UhlpC3BRWwt!G>PwweG^BYJtX#NqWgfN!h0wwwh%uKRufy?tD7 z4)CjsW4s!VXQMUD2KeKrO>OoF;PdB~RTn;W0owW1t8>tbFSLW(^#Se-8ve0AZFm48EK6@l-F5W?9|P$>xKw1mHeFv?iu z04Tp$NsFQByha=Jqi^~tR~c36V6oT2a6TBLp$bKTFO4p!B~ z1pN0tW5p~|5(DWWvVhk?7FDK`GMrx~NA(ysGl?`kD`u8bneYn1rO3_s@iK=hgGb^7 zil=%En;E=9>~{6={qzp+7{pUOh80uep3bL-@27Wo#~_~SF{~JDdc$1iRKdNRFa)v`6b$qX43HT{22c?*V^ci~WGPESBQrfC eWL<{lrY3r(Cdk?`4Ky<}&@(g!sxbi@CG9XJVEEG3iO2@0?HdYeH#HMLj`XoFY3(tk~32ogV1Y7Z7 z%lEs#@9gv0ulBWVS=QU>`-xum#)?N9l5bdKS)_(%J02b9S;y}T9HOY0cKrSTcSbm2 zs?xG1{;feT6)4*AQv-itqP8~rO9LmW7|yp1T*Ny2PJdzM>zWSK$SY8RI_cT`hO`{*B`K{ z-){Y{P&3lx=`o>|R5_<86Rfpv%~KC!Bta&iNK8xM2v&5mLB#Oa0ZaBZhC#23X=(Xg?q5<#T4k(wf4!4vRQ zycy@58N2Qr-P-*9lB;b9^tAfx_YSoEc&hs5t%`1_>5r^FvxcqqqB{S^x@hA?`q zU$gV0nm@2|m>sM4S^XeDtj2HaA2_QIWi`sQzKfMw7rj4O4LB|nIf{9GF=WOE{TIU{ zHGbRr!~qS6-VePgV|;0x=I{tC=6UA)i+q%XlPZG-q&NK$#ZeWcWDuC5=dBtBL0-y0 z9+paPU@s^SGODiYKf@xei+B`BSRaUp=b7^(E+<93ewelP6^>F}-adZh`5?pq()`T) z)f@syyxT4g_sF^Y)!daoiA)nZJfR@`s$uUlB~=xXRK}`~T4J7w}5i+nyifDjxQHAP|qLeAv}IhP-*&*TR=mmkfY{#V?cAI(`k zmmkz~`9aR*M{{R>GHt{x2jXFX8x-ly{egIE z|G01DL)DK<5R-o0;BpubWR+yl`3R9&AG&7u9|;{Oxm{2Fs{mT}=kTxu1CFt4>-^yS z5z*;H^Dee*UBvP2a$J2NslM@f@JJ5_Sl9kYTm_u;8?Crar1nlIR>25;{nyJR{?kHRlwbP6)@uF z`7eK$jX8f1XZ%5&@dt6nAH*4dG~D42Ri8EhnxE+p;*39tGyYKZ-TolX_@m~o_p7+fs};T)(h#HJ9F&aVHezrD4y^-}b% zxAld;E&9D(cZ=)om%skw6|nmZ9o8($)m`&wd2_o~03yHD6QUP}YFy#`^mMkE&Cl6k z4p`c`!XoEeF6lpB0l7H&G`+l>UbySNIX;^%-1x?1fBv=O@9y2W{ht~R!FtE%9~{ln n{joUc_(y;DP5CzeeDP(rm|iSie>^*$oo=4Y&m0}z=<)Mk%*BjR literal 0 HcmV?d00001 diff --git a/_reference/ems/MPI_1/3698420A.dbt b/_reference/ems/MPI_1/3698420A.dbt new file mode 100644 index 0000000000000000000000000000000000000000..dd431fe3b5feee74cd2cde6d5583c4fe83c9f6b3 GIT binary patch literal 24 acmZQ%00Jfk24gcz3lk#)M<9n0#0LNsd;z)u literal 0 HcmV?d00001 diff --git a/_reference/ems/MPI_1/3698420B.AD2 b/_reference/ems/MPI_1/3698420B.AD2 new file mode 100644 index 0000000000000000000000000000000000000000..b606e0b7f7b3f550d4274ebcd84416757ac0b99c GIT binary patch literal 5613 zcmeH~O>fgc5QYN*34wax2nS>-7fvX3`A}~-w$oVHvE^)0+lv)S5S2=ULc)z-&_B)2 zhl@4Y9VE(uiu&NVdHhbsGdr)hmp}TC@AP`T`Of77`>}Pzd10D-3g5?Y8o;S@ARqsR zo6O`X>F;*5aEbc3Y^H@0KT48%m-us+9(btmI5Wqb|1+0AHhwX zgYq|Z8^7OSaEZK{?7}7d{SZ!_qvc1;WfPc(R-)-cRX>g_R`iyu`ZR)!@%Nz+-6Nfu z#*UzcF#ASa$Pwgcx`wN`fOI-FB^T+lQ?>q9R z_7a!<1-<20^_Tr64(-+S&|cy$J+;@br}p~wF-YN8EB-<8{r-Vm@sF1K=9jtG zKU%Kp75_lJ;vdKr|3Ds>Ny_W%o~!>!I2Mrupi>GzR1zO6g}FPSI_q4)w6wh^=x0n2iwob_T}YlU&N(<+2=FS(XaQLqd#nK zq<8R9RD=C_xP5m{8(hH7^;;vy1&r%o&WGjEp_#x_!y4V%Ul{x2G#>8x08ZmU`8ndp zMWyCvxj#QpHsv8~u$<2aO6Kn88#ne9nH8p{{A7QNcjllm$5lJuaz2pHYKV>Tr9Ln1 zy9q1j=RDx3m-7pHdVP@I#kKtj=(YU`aBY7AoCh59>%$`G9Kf~x33KoMg1CQw!d%t! zfMb5rUqH_Tj$HZ+a2{~vgU++TC3Elo1UUWs;qPyS<3rq)Up4XjAH>)8KRdzgaa)1E zRDscIbFr9jv}o1YVzpXmcLZPYmbc&5r}NEv`Qmi>MZNw1f@Zb&yjWd$Dp-u_;Xx3- zAO3SinJ(8s^nHGIKL2p;N#33F%mi^B9VTgD_Yvi&Opdqi+kah|Nf2&*|EFKf)lb^N7~UlotBHg27di*%G=fn@>B z4;t}QHXn(6w0%EZP=6Tc0d@RIGhT8WWN00K(c~(>vg&xX=uP2*<*8hrl$QPn8OtN( zp?Nbu8-=v)&*X~A6-7xopULHX5hm^NTm&%*;~n*B7zIA5&i_!4Qa`vzQWhq0)^dN; z`Kji1%$MlumZh(OdC9rRn6BQ!Q$LKEKv@0&dVgniYg((gJeQr^ZQ*i%>%XIAzEfaB z+kp=Bz;9yrA#_~_3`q`p+Mf;}VbFO%#6x$m+jpT;B_WB@WZv7#0sx_?{wTGf)3t4I zk3l-0KEJ(yY(6WBal>uHz=j^c%VhyjFeRDg^SBKdm}6D2k(YHt8v~n%c;Asrrh%R5`aqAhEH+ zO}6j+eLsKlFrpep(fz~IPZq^`B`k0k-UYb2x$c?cu zH_gVyEX#Oicgi7TMtf_5*+lRfJKRdI8aS)d => { + let dbf; + try { + dbf = await DBFFile.open(`${extensionlessFilePath}A.AD1`); + } catch (error) { + log.error("Error opening AD1 File.", error); + dbf = await DBFFile.open(`${extensionlessFilePath}.AD1`); + log.log("Found AD1 file using regular CIECA Id."); + } + + if (!dbf) { + log.error(`Could not find any AD1 files at ${extensionlessFilePath}`); + return { + id: 0, + }; + } + + const rawDBFRecord = await dbf.readRecords(1); + + //AD1 will always have only 1 row. + //Commented lines have been cross referenced with existing partner fields. + + const rawAd1Data = deepLowerCaseKeys( + _.pick(rawDBFRecord[0], [ + //TODO: Add typings for EMS File Formats. + "INS_CO_ID", + "INS_CO_NM", + "INS_ADDR1", + "INS_ADDR2", + "INS_CITY", + "INS_ST", + "INS_ZIP", + "INS_CTRY", + "INS_EA", + "POLICY_NO", + "DED_AMT", + "DED_STATUS", + "ASGN_NO", + "ASGN_DATE", + "ASGN_TYPE", + "CLM_NO", + "CLM_OFC_ID", + "CLM_OFC_NM", + "CLM_ADDR1", + "CLM_ADDR2", + "CLM_CITY", + "CLM_ST", + "CLM_ZIP", + "CLM_CTRY", + "CLM_PH1", + "CLM_PH1X", + "CLM_PH2", + "CLM_PH2X", + "CLM_FAX", + "CLM_FAXX", + "CLM_CT_LN", + "CLM_CT_FN", + "CLM_TITLE", + "CLM_CT_PH", + "CLM_CT_PHX", + "CLM_EA", + "PAYEE_NMS", + "PAY_TYPE", + "PAY_DATE", + "PAY_CHKNM", + "PAY_AMT", + "AGT_CO_ID", + "AGT_CO_NM", + "AGT_ADDR1", + "AGT_ADDR2", + "AGT_CITY", + "AGT_ST", + "AGT_ZIP", + "AGT_CTRY", + "AGT_PH1", + "AGT_PH1X", + "AGT_PH2", + "AGT_PH2X", + "AGT_FAX", + "AGT_FAXX", + "AGT_CT_LN", + "AGT_CT_FN", + "AGT_CT_PH", + "AGT_CT_PHX", + "AGT_EA", + "AGT_LIC_NO", + "LOSS_DATE", + "LOSS_TYPE", + "LOSS_DESC", + "THEFT_IND", + "CAT_NO", + "TLOS_IND", + "CUST_PR", + "INSD_LN", + "INSD_FN", + "INSD_TITLE", + "INSD_CO_NM", + "INSD_ADDR1", + "INSD_ADDR2", + "INSD_CITY", + "INSD_ST", + "INSD_ZIP", + "INSD_CTRY", + "INSD_PH1", + //"INSD_PH1X", + "INSD_PH2", + //"INSD_PH2X", + "INSD_FAX", + "INSD_FAXX", + "INSD_EA", + "OWNR_LN", + "OWNR_FN", + "OWNR_TITLE", + "OWNR_CO_NM", + "OWNR_ADDR1", + "OWNR_ADDR2", + "OWNR_CITY", + "OWNR_ST", + "OWNR_ZIP", + "OWNR_CTRY", + "OWNR_PH1", + //"OWNR_PH1X", + "OWNR_PH2", + //"OWNR_PH2X", + //"OWNR_FAX", + //"OWNR_FAXX", + "OWNR_EA", + "INS_PH1", + "INS_PH1X", + "INS_PH2", + "INS_PH2X", + "INS_FAX", + "INS_FAXX", + "INS_CT_LN", + "INS_CT_FN", + "INS_TITLE", + "INS_CT_PH", + "INS_CT_PHX", + "LOSS_CAT", + ]) + ); + + //Copy specific logic for manipulation. + //If ownr_ph1 is missing, use ownr_ph2 + if (!rawAd1Data.ownr_ph1) { + rawAd1Data.ownr_ph1 = rawAd1Data.ownr_ph2; + } + + let ownerRecord: OwnerRecordInterface; + //Check if the owner information is there. If not, use the insured information as a fallback. + if ( + _.isEmpty(rawAd1Data.ownr_ln) && + _.isEmpty(rawAd1Data.ownr_fn) && + _.isEmpty(rawAd1Data.ownr_co_nm) + ) { + //They're all empty. Using the insured information as a fallback. + // //Build up the owner record to insert it alongside the job. + ownerRecord = { + ownr_ln: rawAd1Data.insd_ln, + ownr_fn: rawAd1Data.insd_fn, + ownr_title: rawAd1Data.insd_title, + ownr_co_nm: rawAd1Data.insd_co_nm, + ownr_addr1: rawAd1Data.insd_addr1, + ownr_addr2: rawAd1Data.insd_addr2, + ownr_city: rawAd1Data.insd_city, + ownr_st: rawAd1Data.insd_st, + ownr_zip: rawAd1Data.insd_zip, + ownr_ctry: rawAd1Data.insd_ctry, + ownr_ph1: rawAd1Data.insd_ph1, + ownr_ph2: rawAd1Data.insd_ph2, + ownr_ea: rawAd1Data.insd_ea, + + shopid: "UUID", //TODO: Need to add the shop uuid to this set of functions. + }; + } else { + //Use the owner information. + ownerRecord = { + ownr_ln: rawAd1Data.ownr_ln, + ownr_fn: rawAd1Data.ownr_fn, + ownr_title: rawAd1Data.ownr_title, + ownr_co_nm: rawAd1Data.ownr_co_nm, + ownr_addr1: rawAd1Data.ownr_addr1, + ownr_addr2: rawAd1Data.ownr_addr2, + ownr_city: rawAd1Data.ownr_city, + ownr_st: rawAd1Data.ownr_st, + ownr_zip: rawAd1Data.ownr_zip, + ownr_ctry: rawAd1Data.ownr_ctry, + ownr_ph1: rawAd1Data.ownr_ph1, + ownr_ph2: rawAd1Data.ownr_ph2, + ownr_ea: rawAd1Data.ownr_ea, + shopid: "UUID", + }; + } + + return { ...rawAd1Data, owner: { data: ownerRecord } }; +}; +export default DecodeAD1; + +interface OwnerRecordInterface { + ownr_ln: string; + ownr_fn: string; + ownr_title: string; + ownr_co_nm: string; + ownr_addr1: string; + ownr_addr2: string; + ownr_city: string; + ownr_st: string; + ownr_zip: string; + ownr_ctry: string; + ownr_ph1: string; + ownr_ph2: string; + ownr_ea: string; + shopid: string; +} diff --git a/src/main/decoder/decoder.ts b/src/main/decoder/decoder.ts new file mode 100644 index 0000000..2e23fd9 --- /dev/null +++ b/src/main/decoder/decoder.ts @@ -0,0 +1,19 @@ +import log from "electron-log/main"; +import path from "path"; +import DecodeAD1 from "./decode-ad1"; + +async function ImportJob(filepath: string): Promise { + const parsedFilePath = path.parse(filepath); + const extensionlessFilePath = path.join( + parsedFilePath.dir, + parsedFilePath.name + ); + log.debug("Importing Job", extensionlessFilePath); + + const decodedJob = {}; + + const ad1: ParsedAD1 = await DecodeAD1(extensionlessFilePath); + log.debug("AD1", ad1); +} + +export default ImportJob; diff --git a/src/main/index.test.ts b/src/main/index.test.ts index 083cb32..f80e836 100644 --- a/src/main/index.test.ts +++ b/src/main/index.test.ts @@ -1,7 +1,7 @@ import { _electron as electron } from "playwright"; import { test, expect } from "@playwright/test"; -test("example test", async () => { +test("Basic Electron app compilation.", async () => { const electronApp = await electron.launch({ args: ["."] }); const isPackaged = await electronApp.evaluate(async ({ app }) => { // This runs in Electron's main process, parameter here is always @@ -14,8 +14,6 @@ test("example test", async () => { // Wait for the first BrowserWindow to open // and return its Page object const window = await electronApp.firstWindow(); - await window.screenshot({ path: "intro.png" }); - // close app await electronApp.close(); }); diff --git a/src/main/index.ts b/src/main/index.ts index a2a5616..300a370 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -4,13 +4,23 @@ import log from "electron-log/main"; import { join } from "path"; import icon from "../../resources/icon.png?asset"; import ErrorTypeCheck from "../util/errorTypeCheck"; -import "./store/store"; +import store from "./store/store"; + log.initialize(); function createWindow(): void { // Create the browser window. + const { width, height, x, y } = store.get("app.windowBounds") as { + width: number; + height: number; + x: number | undefined; + y: number | undefined; + }; + const mainWindow = new BrowserWindow({ - width: 900, - height: 670, + width, + height, + x, + y, show: false, autoHideMenuBar: true, ...(process.platform === "linux" ? { icon } : {}), @@ -21,6 +31,17 @@ function createWindow(): void { }, }); + // Store window properties for later + const storeWindowState = (): void => { + const [width, height] = mainWindow.getSize(); + const [x, y] = mainWindow.getPosition(); + store.set("app.windowBounds", { width, height, x, y }); + }; + mainWindow.on("resized", storeWindowState); + mainWindow.on("maximize", storeWindowState); + mainWindow.on("unmaximize", storeWindowState); + mainWindow.on("moved", storeWindowState); + mainWindow.on("ready-to-show", () => { mainWindow.show(); }); diff --git a/src/main/ipc/ipcMainConfig.ts b/src/main/ipc/ipcMainConfig.ts index ccfae5a..0eaff7c 100644 --- a/src/main/ipc/ipcMainConfig.ts +++ b/src/main/ipc/ipcMainConfig.ts @@ -1,6 +1,8 @@ -import { ipcMain } from "electron"; +import { app, ipcMain } from "electron"; import log from "electron-log/main"; +import path from "path"; import ipcTypes from "../../util/ipcTypes.json"; +import ImportJob from "../decoder/decoder"; import { StartWatcher } from "../watcher/watcher"; import { SettingsWatchedFilePathsAdd, @@ -10,7 +12,7 @@ import { import { ipcMainHandleAuthStateChanged } from "./ipcMainHandler.user"; // Log all IPC messages and their payloads -const logIpcMessages = () => { +const logIpcMessages = (): void => { // Get all message types from ipcTypes.toMain Object.keys(ipcTypes.toMain).forEach((key) => { const messageType = ipcTypes.toMain[key]; @@ -42,6 +44,24 @@ ipcMain.on(ipcTypes.toMain.test, (payload: any) => //Auth handler ipcMain.on(ipcTypes.toMain.authStateChanged, ipcMainHandleAuthStateChanged); +//Add debug handlers if in development +if (import.meta.env.DEV) { + log.debug("[IPC Debug Functions] Adding Debug Handlers"); + + ipcMain.on( + ipcTypes.toMain.debug.decodeEstimate, + async (event, payload): Promise => { + const relativeEmsFilepath = `_reference/ems/MPI_1/3698420.ENV`; + // Get the app's root directory and create an absolute path + const rootDir = app.getAppPath(); + const absoluteFilepath = path.join(rootDir, relativeEmsFilepath); + + log.debug("[IPC Debug Function] Decode test Estimate", absoluteFilepath); + await ImportJob(absoluteFilepath); + } + ); +} + //Settings Handlers ipcMain.handle( ipcTypes.toMain.settings.filepaths.get, diff --git a/src/main/store/store.ts b/src/main/store/store.ts index 7ddde6c..997214c 100644 --- a/src/main/store/store.ts +++ b/src/main/store/store.ts @@ -11,7 +11,15 @@ const store = new Store({ pollingInterval: 30000, }, }, - user: null, + app: { + windowBounds: { + width: 800, + height: 600, + x: undefined, + y: undefined, + }, + user: null, + }, }, }); diff --git a/src/main/watcher/watcher.ts b/src/main/watcher/watcher.ts index a833ee3..eb8f0ee 100644 --- a/src/main/watcher/watcher.ts +++ b/src/main/watcher/watcher.ts @@ -4,8 +4,9 @@ import log from "electron-log/main"; import path from "path"; import errorTypeCheck from "../../util/errorTypeCheck"; import store from "../store/store"; +import ImportJob from "../decoder/decoder"; -var watcher: FSWatcher; +let watcher: FSWatcher; async function StartWatcher(): Promise { const filePaths = store.get("settings.filepaths") || []; @@ -34,8 +35,7 @@ async function StartWatcher(): Promise { watcher = chokidar.watch(filePaths, { ignored: (filepath, stats) => { const p = path.parse(filepath); - - return !stats?.isFile() && p.ext !== "" && p.ext.toUpperCase() !== ".ENV"; + return !stats?.isFile() && p.ext !== "" && p.ext.toUpperCase() !== ".ENV"; //Only watch for .ENV files. }, usePolling: store.get("settings.polling").enabled || false, interval: store.get("settings.polling").pollingInterval || 1000, @@ -77,7 +77,7 @@ async function StartWatcher(): Promise { return true; } -function onWatcherReady() { +function onWatcherReady(): void { log.info("Watcher ready!"); // const b = BrowserWindow.getAllWindows()[0]; // b.webContents.send(ipcTypes.default.fileWatcher.toRenderer.startSuccess); @@ -102,8 +102,8 @@ async function StopWatcher(): Promise { return false; } -async function HandleNewFile(path) { - //await ImportJob(path); +async function HandleNewFile(path): Promise { + await ImportJob(path); log.log("Received a new file", path); } diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 225ab52..60dcfe0 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -11,6 +11,7 @@ import {} from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary"; import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback/ErrorBoundaryFallback"; import Settings from "./components/Settings/Settings"; +import Home from "./components/Home/Home"; const App: React.FC = () => { const [user, setUser] = useState(null); @@ -36,7 +37,7 @@ const App: React.FC = () => { <> - AuthHome} /> + } /> } /> diff --git a/src/renderer/src/components/Home/Home.tsx b/src/renderer/src/components/Home/Home.tsx new file mode 100644 index 0000000..0b37986 --- /dev/null +++ b/src/renderer/src/components/Home/Home.tsx @@ -0,0 +1,21 @@ +import { Button } from "antd"; +import ipcTypes from "../../../../util/ipcTypes.json"; + +const Home: React.FC = () => { + return ( +

+

Home

+ +
+ ); +}; + +export default Home; diff --git a/src/renderer/src/components/Settings/Settings.WatchedPaths.tsx b/src/renderer/src/components/Settings/Settings.WatchedPaths.tsx index 282f7a6..a661306 100644 --- a/src/renderer/src/components/Settings/Settings.WatchedPaths.tsx +++ b/src/renderer/src/components/Settings/Settings.WatchedPaths.tsx @@ -16,7 +16,7 @@ const SettingsWatchedPaths: React.FC = () => { }); }, []); - const handleAddPath = () => { + const handleAddPath = (): void => { window.electron.ipcRenderer .invoke(ipcTypes.toMain.settings.filepaths.add) .then((paths: string[]) => { @@ -24,7 +24,7 @@ const SettingsWatchedPaths: React.FC = () => { }); }; - const handleRemovePath = (path: string) => { + const handleRemovePath = (path: string): void => { window.electron.ipcRenderer .invoke(ipcTypes.toMain.settings.filepaths.remove, path) .then((paths: string[]) => { diff --git a/src/util/deepLowercaseKeys.ts b/src/util/deepLowercaseKeys.ts new file mode 100644 index 0000000..9847d6e --- /dev/null +++ b/src/util/deepLowercaseKeys.ts @@ -0,0 +1,32 @@ +/** + * Deep renames all keys in an object to lowercase + * @param obj - The object to transform + * @returns A new object with all keys converted to lowercase + */ +function deepLowerCaseKeys(obj: any): T { + if (!obj || typeof obj !== "object") { + return obj; + } + + // Handle arrays + if (Array.isArray(obj)) { + return obj.map((item) => deepLowerCaseKeys(item)) as unknown as T; + } + + // Handle objects + return Object.keys(obj).reduce( + (result, key) => { + const value = obj[key]; + const lowercaseKey = key.toLowerCase(); + + result[lowercaseKey] = + typeof value === "object" && value !== null + ? deepLowerCaseKeys(value) + : value; + + return result; + }, + {} as Record + ) as T; +} +export default deepLowerCaseKeys; diff --git a/src/util/ipcTypes.json b/src/util/ipcTypes.json index bc1096c..3466e7f 100644 --- a/src/util/ipcTypes.json +++ b/src/util/ipcTypes.json @@ -2,6 +2,9 @@ "toMain": { "test": "toMain_test", "authStateChanged": "toMain_authStateChanged", + "debug": { + "decodeEstimate": "toMain_debug_decodeEstimate" + }, "watcher": { "start": "toMain_watcher_start", "stop": "toMain_watcher_stop"