From da0bfe21eca7bd5da932e4c2ce99fd6248b722ee Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Fri, 7 Jun 2024 19:50:21 +0300 Subject: [PATCH] Initial commit --- .gitignore | 21 + VBAMake.txt | 39 ++ script/LocalDevSetup.ps1 | 32 + script/databaseManifest.txt | 75 +++ script/run_menu.bat | 5 + skeleton/!Контент.xlsm | Bin 0 -> 50143 bytes src/DB_Content.cls | 126 ++++ src/DB_Workers.cls | 28 + src/Declarations.bas | 82 +++ src/DevHelper.bas | 20 + src/InfoConfig.cls | 163 +++++ src/IteratorAttribute.cls | 99 +++ src/IteratorCSVContent.cls | 134 ++++ src/IteratorCSVTasks.cls | 118 ++++ src/IteratorContent.cls | 595 +++++++++++++++++ src/Main.bas | 152 +++++ src/MainImpl.bas | 143 ++++ src/z_UIMessages.bas | 61 ++ src/z_UIRibbon.bas | 20 + ui/.rels | 2 + ui/customUI.xml | 66 ++ webapi/.pylintrc | 634 ++++++++++++++++++ webapi/.vscode/launch.json | 67 ++ webapi/.vscode/settings.json | 3 + webapi/configs/!AppData.ini | 14 + webapi/configs/!Menu.ini | 4 + webapi/configs/!Options.ini | 20 + webapi/configs/akostyuk.ini | 5 + webapi/configs/amarzoeva.ini | 5 + webapi/configs/anikitin.ini | 5 + webapi/configs/iborisov.ini | 5 + webapi/exporter.py | 48 ++ webapi/menu.py | 203 ++++++ webapi/portal/__init__.py | 25 + webapi/portal/bre_browser_options.py | 17 + webapi/portal/config.py | 12 + webapi/portal/crypto.py | 53 ++ webapi/portal/data_reader.py | 196 ++++++ webapi/portal/document.py | 75 +++ webapi/portal/electron_loading.py | 171 +++++ webapi/portal/info_models.py | 160 +++++ webapi/portal/portal.py | 347 ++++++++++ webapi/portal/process_typograph.js | 19 + webapi/portal/selenium_wrapper.py | 97 +++ webapi/portal/uploader.py | 946 +++++++++++++++++++++++++++ webapi/pyinstaller_run.bat | 15 + webapi/requirements.txt | Bin 0 -> 108 bytes webapi/requirements_dev.txt | Bin 0 -> 170 bytes webapi/run_cardslot.py | 33 + webapi/run_loader.py | 56 ++ webapi/run_metadata.py | 26 + webapi/test.ini | 29 + 52 files changed, 5271 insertions(+) create mode 100644 .gitignore create mode 100644 VBAMake.txt create mode 100644 script/LocalDevSetup.ps1 create mode 100644 script/databaseManifest.txt create mode 100644 script/run_menu.bat create mode 100644 skeleton/!Контент.xlsm create mode 100644 src/DB_Content.cls create mode 100644 src/DB_Workers.cls create mode 100644 src/Declarations.bas create mode 100644 src/DevHelper.bas create mode 100644 src/InfoConfig.cls create mode 100644 src/IteratorAttribute.cls create mode 100644 src/IteratorCSVContent.cls create mode 100644 src/IteratorCSVTasks.cls create mode 100644 src/IteratorContent.cls create mode 100644 src/Main.bas create mode 100644 src/MainImpl.bas create mode 100644 src/z_UIMessages.bas create mode 100644 src/z_UIRibbon.bas create mode 100644 ui/.rels create mode 100644 ui/customUI.xml create mode 100644 webapi/.pylintrc create mode 100644 webapi/.vscode/launch.json create mode 100644 webapi/.vscode/settings.json create mode 100644 webapi/configs/!AppData.ini create mode 100644 webapi/configs/!Menu.ini create mode 100644 webapi/configs/!Options.ini create mode 100644 webapi/configs/akostyuk.ini create mode 100644 webapi/configs/amarzoeva.ini create mode 100644 webapi/configs/anikitin.ini create mode 100644 webapi/configs/iborisov.ini create mode 100644 webapi/exporter.py create mode 100644 webapi/menu.py create mode 100644 webapi/portal/__init__.py create mode 100644 webapi/portal/bre_browser_options.py create mode 100644 webapi/portal/config.py create mode 100644 webapi/portal/crypto.py create mode 100644 webapi/portal/data_reader.py create mode 100644 webapi/portal/document.py create mode 100644 webapi/portal/electron_loading.py create mode 100644 webapi/portal/info_models.py create mode 100644 webapi/portal/portal.py create mode 100644 webapi/portal/process_typograph.js create mode 100644 webapi/portal/selenium_wrapper.py create mode 100644 webapi/portal/uploader.py create mode 100644 webapi/pyinstaller_run.bat create mode 100644 webapi/requirements.txt create mode 100644 webapi/requirements_dev.txt create mode 100644 webapi/run_cardslot.py create mode 100644 webapi/run_loader.py create mode 100644 webapi/run_metadata.py create mode 100644 webapi/test.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c516907 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +__pycache__ +~$* + +*.spec +*.log +inputs +input.xlsx +output.csv +chromedriver.exe +Users.xlsx + +webapi/\!Контент.xlsm +webapi/bin/ +webapi/build/ +webapi/*.csv +webapi/exporter.ini +webapi/exporter.exe +webapi/*.xlsm +webapi/docx/*.docx +test/ +venv diff --git a/VBAMake.txt b/VBAMake.txt new file mode 100644 index 0000000..51cc9d2 --- /dev/null +++ b/VBAMake.txt @@ -0,0 +1,39 @@ +# == Properties Section == +# configuration properties +# use .ini format to define properties +# mandatory properties: name, artifact_home, source_home + +id = BRE +name = БРЭ +description = Интерфейс взаимодействия с Большой Российской Энциклопедией +artifact_home = BRE +source_home = BRE +install_home = D:\DEV\!WORK\output\BRE + +%% +# === Build section === +# Available commands: +# build LOCAL_MANIFEST +# copy LOCAL_SOURCE -> [LOCAL_ARTIFACT] +# save_as LOCAL_ARTIFACT -> LOCAL_ARTIFACT +# run LOCAL_SOURCE.bat + +run webapi\pyinstaller_run.bat +build script\databaseManifest.txt +copy webapi\bin\exporter.exe -> exporter.exe + +copy webapi\configs -> distr\configs +copy webapi\bin\exporter.exe -> distr\exporter.exe +copy webapi\bin\menu.exe -> distr\menu.exe +copy script\run_menu.bat -> distr\run_menu.bat +save_as !Контент.xlsm -> distr\!Контент.xlsm + +%% +# === Install section == +# Available commands: +# install LOCAL_ARTIFACT -> [INSTALL_PATH] +# add_template LOCAL_ARTIFACT -> [LOCAL_TEMPLATE] +# run LOCAL_ARTIFACT.bat <- [PARAMETERS] +# run APPLICATION <- [PARAMETERS] + +# install !Контент.xlsm \ No newline at end of file diff --git a/script/LocalDevSetup.ps1 b/script/LocalDevSetup.ps1 new file mode 100644 index 0000000..661e428 --- /dev/null +++ b/script/LocalDevSetup.ps1 @@ -0,0 +1,32 @@ +# Create venv and install dependencies + imports + +$webapi = Resolve-Path -Path "$PSScriptRoot\..\webapi" +$envPath = "$webapi\venv" +$python = "$envPath\Scripts\python.exe" + +function LocalDevelopmentSetup() { + Set-Location $webapi + + ClearPrevious + CreateEnv + InstallPips +} + +function ClearPrevious() { + if (Test-Path -Path $envPath) { + Write-Host "Removing previous env: $envPath`n" -ForegroundColor DarkGreen + Remove-Item $envPath -Recurse -Force + } +} + +function CreateEnv() { + Write-Host "Creating python env: $envPath`n" -ForegroundColor DarkGreen + & 'python' -m venv $envPath +} + +function InstallPips() { + & $python -m pip install --upgrade pip + & $python -m pip install -r requirements_dev.txt +} + +LocalDevelopmentSetup \ No newline at end of file diff --git a/script/databaseManifest.txt b/script/databaseManifest.txt new file mode 100644 index 0000000..33e47b2 --- /dev/null +++ b/script/databaseManifest.txt @@ -0,0 +1,75 @@ +# == Properties Section == +# configuration properties +# use .ini format to define properties +# mandatory properties: name, artifact + +name = !Контент.xlsm +artifact = !Контент.xlsm + +%% +# === Imports Section === +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SharedHome path + +api + API_XLWrapper.cls + API_UserInteraction.cls + +utility + ex_MSHook.bas + ex_VBA.bas + ex_DataPreparation.bas + +excel + ex_Excel.bas + +ui + CSE_ProgressBar.frm + CSE_ListSelector.frm + +dev + DevTester.bas + +%% +# === Source Code Section == +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SourceHome path + +src + DevHelper.bas + Declarations.bas + Main.bas + MainImpl.bas + z_UIRibbon.bas + z_UIMessages.bas + + DB_Content.cls + DB_Workers.cls + InfoConfig.cls + IteratorAttribute.cls + IteratorContent.cls + IteratorCSVTasks.cls + IteratorCSVContent.cls + +%% +# ===== UI Section ======= +# Pairs of path to UI elements, use " -> " delimiter +# First component is a path relative to SourceHome\ui folders +# Second component is internal path inside project file + +.rels -> _rels\.rels +customUI.xml -> customUI\customUI.xml + +%% +# === References Section === +# List dependencies in one of the formats +# global : GLOBAL_NAME +# guid : {REGISTERED_GUID} +# file : PATH_TO_LIBRARY + +global : Scripting +global : MSForms +global : ADODB +global : IWshRuntimeLibrary \ No newline at end of file diff --git a/script/run_menu.bat b/script/run_menu.bat new file mode 100644 index 0000000..75aa02b --- /dev/null +++ b/script/run_menu.bat @@ -0,0 +1,5 @@ +@echo off + +menu.exe + +pause \ No newline at end of file diff --git a/skeleton/!Контент.xlsm b/skeleton/!Контент.xlsm new file mode 100644 index 0000000000000000000000000000000000000000..8f635c9dcd306f8bdd0ee81f1cc57ee9e722aa90 GIT binary patch literal 50143 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�@z)3lPAO_p~5Wv;Ua_^UEW~{pydVR?* zeGzXev%l28`^7t7vq%-`;%F_>gwB>HAX{Ctrl+y!VG@}+?w`z1p)z*C8YYV zLt5*_q|Ac#kaDVdb-uW zPTsEQdU2d=-8nY8ZADIvwjnGZ#@waB`qFJP3=1>y8O~064jwgrC?=BPu~e_00~%&c zsWdjb+o^3g4PJiq41c?x^j4#>b+2^207gc@paNcB~Irp?TdR-?6^sqmSOtmy$ zuhku-n?BcZsV@i-3EoYe0bkBxwz2s-aYMA z&~@LxEd_LJyINSAH=1r&^!Qvo*IcaKPfZ17y?cLkkTqj#xLvK5G#i(=Xm|{k&+63_ z5{)a=&d9p);j=?yLqb-GN$6r$gJ3uL)64%+nuY9s$TKj2tv+iW3{$&M3muCT8P3SY za?eIbS4j)I zTIleJ98+f>PwwgK6TI4oQZ`eA$m zn6D<#70qpwgSP3s2Zx``cWfmb2)IX(Nh}L%qa=l#!?@Q*VZAP@d`LC45H=FotySu8 zA%w^WO*x1X!+RI_9m4NuO{K0k#n6*PX^R|0hIi-YusKD`>s)9&FM29KolX!?k~6%@pRHYD7uNVQM-BMT5sU0gCPP^iC2Bgko|% z2{E(@51p*oCyy=7px5lT%&GUoQr+YRldk3u6*=>Ka6AtCnF4qOCJD@3J3)O#juTQb z5OE`i00J9fW?&G`EZj)hs2*HhJKDrJR^G40R!?1j)$?YaH7 z%o-jS-~Uk$v&hcHbMtdrTKgxM@JAB!zq3#OD|+zX`MUonfbgGm-OBjB-@pv;!PoxR z@JBZFW%z`XPYQuLO}zXQQ&+(@qy%A$-Cc?xPLhL?wrQ`UYH#@;@q!3&KDlnb~u}w{N<*i^i>UB$2il%?KL{`d~L}eU*i(Ga}>t1DECZDV>=IR7)N5ov%L>F*Cvf0%Q6tej=WQuY*1U zRy>HgLgd0pbbk{plYRn-=&a{I{WfbMd3`@w2^5U(H{7Dv_EATBj0iF$gsQUPCkSE| zPN(+F;%%lc{qtCSXlG8EIKID5%@lh#$Hyt=i60gURFU~Wf zd^sLyAaE}BlIAdG%8<}f_zbXN%Fi9WHqe$aoX869fGCb;Ol3s^p z;ZA?^0>ZH=gcryX4fP*$g8x1Anc)OV18t&kY`CwZmeS&I}OiZmAo@6cYtDZjw zjv#bEVy|EP5!RYs64eo#W2i1~U#&naa0dSMGBnl8r=0GGUjGwyp=5-gG1&hH@%}NX z`M-(h_Yd*N5*7RpwFL2(Sw8`kb;b$*MZBWd_cOH&!I*y$@AA|mdjSnKI*j_l2^ey; zgjb>V#=GM}hrf>fY%>v@hC6n5!J6%oN=C*aY&cb28LBQwo)|AmpKtF%tt3)uGr^KV&oSCIYXbFNK{R?4?aiGC>V^r+92_}%b7#B&phbC0c9 zOY;389*gEN*(U;o)N=aZ{ndZ8gV46X(=PmhK<%e8$$qXs;yt?`ZHJ?=y`r(B<3Bpt z|Mr=Q*1+5bQqNA`&d|<;+(6t|+=yI%YQQ!dJtHGMCNn2`0t(@$0moQfUi|KA7_I1EirY{Hq_A=Dy-Ce~IC_A|5iYRy zK;zN$O3~?cf12I@@L!X(mN6XtRG7t2{r#7g#ZlkjN2N&j&*k4?unCjV8T80O&q;5v z$Fg)@WI$?RUNqDxU#XOhOt?^S?KnV;DQ4*HZ9pl@bb(qUy~%1dh;s zo?kY`_wr4VHw?-`cOV?eU_nw-@(Dc?dRvx*Py}+B_fYcv_d#sx{L#jbr%^FC<)A0b zs}WbsHiw$4#=w{gN%IFcE*1nC3nwc!R?3smmCO6om@H&*$Z9bNv2no}sJ?%k9Xz+2 zR4CLUnA->vPcE}T0jc&MgP}Ouf9uf+&~@?M=w$a{P!!4KFChUph-fDu=D0a}Y3^w< ziaH3oeUC!ost=)+Ni79a?AvI0Yk>+d=Nxw{vy0fg&X2Ur*`a8-vER>8$8_T7c=`Jh znb4MO7ZqK>li<;VpvcpRtS=vPAudC7I&Qwq!4S*{5|wX-c2tONq{(FPvK14kVNzV5 zKbnn%jDwyvl7la*`Iq?mJXp}dA;m+iF;w$Ya8?gX^3BQ$xDFG~5lZ$m-&c*e#i@Jq zMpV9Tu*Edmsm@k{H=I(0?Y`eQJi9#`x8ixj!2L|=yq}$1SG)p>+q>{)3||drsk(|> zCoTU--MMEbcfCNW78)utos(w!ixJqtvwNjj27>03x>Sa}1?Puy1Y=ZP(HV5oon zxSssaHsJVi&}8k07jplZ7ynLZi=X_LTUpSvcxS+&SNe9b`GVs@@nk7ttzIz89kGld z!g}20M5CZMa7=GPt>G|JqwRpS0%uY?AZwfhqR;b&!}W#9u;qhzl8FNG3JwRleB8Ky zn6*FOhN((m9IG&LVX{A|t1L@*T8}pi(?MP_xDC|Lo^vW`vvrngA`(wL49ZPWVmK0T zjfLg8sJy~SxI(|>xAnuZ77I4-`@Ma7I;ZxkJ)U zmLS*Rx8L;wN@jzW1dSvUP%6!M{Nck`0oF=5Z#``~TH<$3<;?_;Zl!qbFDaCk&07b| zN;E?k1hs@mBV$n5OjO0?x=v>MdNT}Bs_k$8IDG7MCOovC6Tf|qiV2?`lG3w`eJ%Pw zQri)6A&XBP-c98@2otD;Dm@S-zs9vBtkUy0S6-s3IE)Gy<&(QNKKF*>e$m2uo3R>zG&T;ZTf{qx}7(rv#ATqc7!7CL#ORaVY==jP)iPw8iM#p3TrS;FZQgbU@Zx4 zU(veYzv%(@S2eqH>P|Y56F&d37uLNYV%`4Xg3f>D!oQQN6gDl^>CwCJE^q>`P`;Py zq%#q@93ZR;DmNeHtT|8{h>1q4`pF}^N3soSD|m(OL=!HlX%0-*qvB#7vK+eay&}f5 zS$49;ML=b9nHmW2JK;$V*Fn6yC*{HY=2BBy5i*JG#E?6+y1g1^UaARtl^l|KHO^U^ zsg)N3^;wW;c8`h?_wp{5=$Q@V%77@%><^nIlDvXZOu*!~*>c6e+f7J0yp1;% z3Z`!nke69tb-ueg6G$ZWQjvAv{j?AJqF#t_Y)!88imXi4lk}m?Gwn63Lw@^z4}n}X z4jN~L{g)5FPv$gaI^x2L4Ym9P9ksAPiRiB;0%H)^g+R6#0(vKT$h23!CYR4|spD;5F%VAK?Tew-Omf+&)#)%zXu1*X5tfsh$FM)G%~|)HZf@-)hHI4 zgIJEQ@@iOXi5KQfeb`U2sZ%vc!)IaF&^lkQW-rQ4!CHv-o(wrNX`o}MPAl09P*FL+ zB}0Rvujb@aXnb8+EObXMkM3>m3|S$0$(7mO^K&D}r0|@cz0df%7}miNrO>8nVB9!Ho@6)A4Yeu z;mXE1ZWByhgo-=EU=h+@&*bDB+>S+bjDYpy6RP)Qy9Wgy)J>cZSd<};uJq8v_kd&? z%TTPdwA;LUzi)1P__t+u>!VV4udd`AP%We&BERz~r{>Y0dm`P)FnEbZ=g zLuI}4fb9C!@}N!Bi_M$o{Sx_B7tR)QSLF!Xs>j%J`!mb_DpV^O+Ce-+=7%eY7_lQT zOk>P)#Va`M!imV*?q4coS*t~NImBPIb+2KcQ;`9yDMk0i%B}n8((mS-@aeD$vkQW5h6A||AJsZy5HL=gCpg@V?3(n*m zZCPU!OSgD%a3T^SAc}B^W?OE;el>S|q4KRvw9Me6H0UHLUx%rltPHJN^Ad8dmr3jK zb7iNI^**J4$3Dm(y+$J_I2GhsAC+R$>0GqbTF1Lzb7yA$(R}e?#8tVaqVA&C73{+7 ze4e(o7Q<}y$Y#DWWwBBa?{=;nBa0p$)_@a1x@>!cVz85&(^3_u=TeV%maEAoC-#E4 zJneg3b=rZgT=xQU9-HjwP-bSHHVxZbF7wSwc%EEh*7Z<-y9hsd#c$C(^TQB&+DI`XmMz0tP z!D-^PLbwAam9EGuQxfE%pbeM2*)%&~@Q6-@*hP0h7D1YRu-_l{HyGH?S-(8=pO6nk zy@5ppB@$DXsgPP-Na_qA7<(B6!$c_pgDB87cz$X??PZSj;-Yqp(j?gM)yy|Ne)ehD zITS4^qXKr;3xxyqMJi9~MI>!@Yp_w(MjW{p2Jbru+dM3am+VXA#Fn)A%_*}s=_?3U z^ePu%3xpIY_tCb|ZlI68VI@z>iqaZamD4Luv(WY@2=m&YHDu{9OHrJq6>kLSxr|f! zROl6EzsBe7o?~6gG}!}pMCZ~%-5de<<<}u(JUoNisWY#L!9lHWtv44xiV2moD99`_ zIy>Cum5QWv77=_X^&|xs!X7$%?dZ1H=hURvwBAeSMM1sc(o%&$xAMh@ix_qQPG;ru z_TlrS`shX<$zJ_fdUOhXt*TwOe4=t1`YullLk{V z2!;8f+FTo<15%!-Uht4i+KQ06k*{T&;^g0)7%T4q5B+aBOxR>2r62r`MtbF zaytUGLxaPeQRz5tPrD7kgejSpFj*-4uI)P|z#N|xr6S^6eI4^!l?5eB%#>j|cs_OA zo0Z?YD*_2HT@k?bhneccc(RB@alH>XM4*9E!qPCW5BDkrKO~tIeIR!xA6*HKmZgx{ z3-FlBEENqHWp4DKJ$Z|j>Bx5tSmj93WCh8S8PB}a`;M@j_MO5iYiFn`minO+rjx#$ zi0!h3UR_<658Hc^<_7MC@KF6EeynU%5sDb6g}1ycAGV!0 zZ~0n>Q_(YE3ckox4b70RvzwcMPHP`1p-3y&rNu?Uj7D;EbBuvi? zCw;X5-r6uc4hL%LPa#SRA+eHbxuewW=N=t#GNd@Zqc~1F!3Sby@hmhk^HK|J-y)>4%Bto2)7ACWiU-%L zDYw10GmVUt-Cys^q`Fx3H70BFe^BNvWWWg@lhP^=R+Lmq5EzIh$;^iLAUpBVr4zo% zQsOslYdM*|+751%dXN6+JW}8-JW^FqHeLa24;X-d=eZH#&-yk>;`fN)eF^ae($h)# zA<#q#;TL(k`=nq>DXdT?O(qd#xV(1Ow$>M);z0BRESqee`O>JVBvvj<5?6C}{*W^J zX1+^#Lo&d*W*&aXXg)Lvv-($Eria;>hWrAe*p#1rFXh+w`WM)A_z$rIHS5N(N<$2V zON)XMtui4ib3m8mWi7Qe3u;Tv&`5JiDOs8=!A8Z^B;(tKJN+>pKvxA^c2)Ain@Ir@ z@J+b$@IihfR8$f^&^T}`nUMnX&~CpA`bPp-AVV<XVRHm=2Rrj#wy+1ZX_7$sO(uk~A%WZY z%JkbTgx@u_*$NiHmEDbV2K!=S8H!{r5Bf>a;-~9VH2AXSgBn&8eOw`UfzI%wI`BmI z11GWdG-lzL_`F-99k~u|%p9inb^xk-*(|TYd_JRtUZG;CwxDSWq~_d6g18JLSj<~% z_^Ngvr&))*(Av$zcd-_eXp%{1RUw@CvxK0 zCn?}P=NK>1`H$3OA8H=PA9nF?(*T=>-v-%ac_kTSof#hY?!RtCiQz_bF*ZNtYtA6u za{zfDVvc9ZSxuh)l_6eK((j{t%NkgN>M9Mj;A;Eb=3@&van$m&X2xCK>?Q1PzuJFL z(Vvwuz?sZ@eWu?0Z(d6c+bzP`*t7Z0+cDyqNoo1Q^}=xrbO`!fdXO8 zP5;Vlj)wm25L{8b0<7U6g=4X@<28te&o8P%+vR&puJ70MQxacr!nrag z%vEVNj!f_9A2>~x^{vHs9GvC7<)P3;lV@S!F)I@g9RN3NaoB^@(KXDlEaCCbQzuOY zp8G~U640cA zeowuH#Sys~BWgyJ#8a>W@eO2;lO@}Njsg8{DL2y$OYtL}O6dy-_TkiBN90S0$aWsgtKWw`JyeX?ggHQRm^JKNVlIFfZ=t zNE|v+zHD}?>M}TaOZj=w*}>zTIe(5PSK^96sF0JNIZ2m__&kzB>?e&m40ZWS(!Mpe zXVt?Z?}2j&n1?L?!#o7MjE+4ryq_d9YRsGyC1z4lsu; z#Y%(@erF*TkrNOxU>5$7Lk3|6o44F;v*R$|H3^{&7^=z9!Q(fY2~1vf&Fl@u^AVtF zLlm-iF_=_%V+7?%ZNjK4Vs0;f=(Q-4$4$r`s*5M$SY^>~l(piEm}t}18Bo!b@1yMv z{Ftkw>>-M*Wufc;`JRmgHK0zy^hffvNQAL0@;3xEq%m$4Q%sDOLd-+(S?EjhYsusY zxky+##{#m%p&xppR?fNO7au&#Mev7-V{X&bIs(M^h;aNK$FAs${4mUydfmdy zEvA>-3$T+Wn+BBVxvAU$7L{Bd+khVd=V$Nlx%GpUA(uLIcO^L3tPNfMX*6! zj5}1dUv|My_3I)ZQ?$ABay`oUsCRGG%pKSuYwDv|{NpS4%-tB=3anbV?{2RMu8fek zeAY2f@e8NWk#rtuQjw7Jx8+|I)^lGOy@gT!^{hZ7btF%I`{p(OgFsjw%~n7F@n3!x zfI!xMRf|s9D9pIIK2l5!D|kmIImN!U?%g$>VhcUXzW+P$S$`usQ*4i|0M$3YcB~ zwqVa`hfi|=lKJnWQ_TNmbjspSbV^H8cg4V8QyX*w`NPn=iHHtw61fv0`z}Cp8R<=UGYEeV;2@o6yFkfLR9g=~7w`gn4#*%- zFLcdyq^-;?tl#LETj~9lKS3t=3yO3Qz_0v&`*MyP1f)u!`d)e-(d+UKtEs%{vVoye zS4JwPIDBt4k1(R^OEPbl$K$>&=?p^&rBX2(-Wrjx(?c1U^v>%3H06Pp#3W>*yYU6# zdN5mrs&zO-vOdk`9U13d?ZI1JQ9*WM(cX-JUIH2F`6U=bys;1M0ex0TR4)y&P~u(; zCw)$r$J3BnZ92t zB&6;#aM0Q9G2LdvJ-jLqL@{ox(J_IHA>Y{95@PL&OoT9cYQB)H*I2OQO#8YTc3s(@ z2yR_f2OE8Do<)N|^$C6{4(hJH{90sVzyjP=8D{(?}%mP6`+ zL+zGr%3`}Ic*xygXTOjeverpB-Q6##Gt;ki>U9KP&eLVekQCkX&swfcGtHmXE!nk0 zvJ618q<{@zk^ZS$EenhPU$#$OHCcYxZ1NpaD zg*H-J(-eX&NlIY34RQV+Le?V?5{g+S^L?bEQX8@kmxTJRyr+j@#1m+<1) zu1rDv3QLokm1&R_k#p|?i&NXVjm|=Cbno}C87tNLmj#vf&3=f|{DJ;r!}*o&o<)g^ zdq!qo19bY^z^Dus`ltc^x9SHnnLgF1PVhQKv(w_mpNaDHFUxh~jl*Nw8Odw9 zL=1NlM)k4V0w7PItw6#d#I*s4Hgk;oXrmceWq@e*U4A;Sl?OXbFxU({M#Lmw$KY!jot zrmY!XUuT&ZW=G?N+=jHSspY3&2VLn74s@h@x8Ipew)kT5m`6Hy&-e?$^qDlCJ_(igw(+V1VuZHR|%!JiimK7Bt;;h zaPx15SWHZejKP;%ORgQIIvoz5Id>WDB?97?VIEvo?t%`4Vgrf{4lW5@48Ma_-8Fz< zm1<`D_5My&L9ZgqpJ3H=W5R4L)ifYjg#y32DM3ooHP1Z|Q-UpOQ84ZO`Qt+BIsw3j z>IkLR>REn=8Ed}q65W$UX_8t+t_4@AUAsrOVeS0|Am|k_Ep*ei`q~lHpgR10fLIvZ z5W0dOtkv2$yoqS3A^tG(#sfJlu2L@stqu})ZgTbAiMo69)F285=iIDEhjwa51-=ix zD7td^b!d?E6`m3f(`2&i>BLacrM&F7DTmuNMCC}Tmjyv&34ZQ5KD{+xNX_V`*LsI$ zKRB2Q@nuI$NYpT&6VoTHeGE$ey+Av;b7M9fwJWMXvK_3igc-NO1sjdV{HULY2wm?2 zdd~yO8-|n!i5HB*>-Rz|oI}rme$cD0UwUPOkW*mHWrxQut$keuY|^REW{UzR%nOc) zO-wVK+`u=1d<@Z=4SqC}S)D|9bL-IH^6tL&_RB*?)1Ir%a${4g=Usd4Jey@k9gFf$ zv3+5yxw1t99&%FC1j~#M^BjO66*ivLQH{Zsz^*|Ssj2^U)CaOV#U^pcZyeYOchA~d zZpZ~haDZ3Jo|h2YqYW-6@moSm#FmhQx6GE2tQkg}yFY}fw3X<)upLqIYWxrS?DSO# zvsX#{UxR=KKu{@)n0rUX*5=cI=&d`C+`29~C8!lEK^*$a%O68!dVsxHZJ?mg5IEkD z(Dy;22yyhF?hpl^0hr)B>%y->%&9>?uqYy{8L5Yvs&0q_OmP213u`$TfC*k^rB$Q? zSml!vCMp0-aN^%4c&OIU&o)9WrB*W|YK-FE#HESAUE5KJ7lU6`r@gzK<4(E| zzeTDKmkjA4QN z{u|G5%_l|O5%CKAuM%}uZyf|-=IOIaKlm|G#VnB?eOc5QJ;s_E<`-9ab&8R&X%o|w z3AT$9^13ksW~x^ne^!pH{E2 zhR}8PCJBjsQbUw&Q_6!>IKhao0sf>336>%hE|;NlvAO#>byy*1cMn6KuWe3JIKd)= zGN$Hq3(>$|zs5kwxxY7!qp2oh&0tW6lisBccP(6MQ=P{IRf;vp_D0U165ebTk*gFw z_9d(EBZy(mWTT&$C$ zYV)8sen{i{Tqj>T_D|5>_L{FODF>sfrb5w#nC{UupTxU-Kpru}312B}>Wwkh)y*W> z*T#%W3%WMq8pKG8GD*_w=hhgGIwu>A+F#Xrt2&f>oo8|?UZmN46$Ew(_Mpj6J`q^f zk{qDrjn1b()!bYv7+cA9Vbjj;oupf%TYt<{h^GgmX4JrE`3{zg&90SlclaXb6JgV| zQtp>KTUEAD%sr>^fSjt2@Jd?}io1x~YnYIcrh}|8XPkxmI5zN$J%%+Oj$rG4U~QZo zrCpI{>^|?8`G3B;V)%dQ>S~pgcW54<%tEN&R_edhX=R;;t}8?ukb? zCm_YeQ=O@3nzoV?IIJ(6w}#}W{bWR5zAq#QD4J55q(rVrv+;Cj5=%4xGL$2b!+m&6 zA0QZW2qQRSjEFD)izHAWnaUGETOp@~`5=x(;`%MJWlijvU@-BbfDpH!f|+sQ8;GO) zR3t-Gquf$1m2R*2k+;#rZfNAlac}Hy;Ovi1nP*`-2^LeXGf939O4Y6l{47BN!yU+3 z&uCs{hjC8G&rT}MAg0~11Jn5KKU&U0`I0XcOQq;Ir8-We2G!^oLQ)f45uB%;dBIEc z;$^Od?D*y@WAPV?G_p0w`DZGGvNlC$6@2~9_fe8D+J_2Zmp$+C27XGqZz|d@jKdEa zRo3by4Vkef7VGYq>c18m{ES93IDS5ZsZHfu@6o+Q*;-qzBG^I#hWF7hAajjei%1m7 zzFRt7b*3+5LVV4J$1v(L(toR$F3i)y-i*aS+)0h9BHm!v&TLY@DZ7p|Q*E3clQv8r z8PI3R-sDv7TI0il6iGmHAfXn;bdqDrfX4Z6vjtbVfbv$^&$-%(RJv5F>-t$B$bsfj z;2~dmOv~^jP@kXtfWEYcCmhM!7x{Zl%C+W?x#mnT8b=5s!pVfmfJqo(Yf;+`Klh1xN_qe@qC*7MOsc2Zeu`fRM?D z`I>?vT-#++`woJ{V6?Ygnjw$vht0StSSs(Dbr4svvpKIQ8}iDTTGpIRy}I4b#+N&M zT%K6h!R{>OZUp)s=VL_;lBnKK=nnpE{oOxpg~F!}ceaXKb?GbW>56zPGqmpbD-1al zt$P|;DQ>XUqt?DJwcozq8iKuW6@=GWr(5VB=%lfJGHi#YA$m6 zW`P%e#10nfrQZLWke6PP;0v^yEuQZi!5af$QCbQ|At${8hENRLq6qmRN}s z?o6=8&~^cu>K6B?!*~y*p;PWPcmGoO;YIpHKfBvugO1atZ^A<#yDT$X$kxrc?NJ`i zs#^Eunf_Yv=?G`oD68ps<(r}s@v4p1AIw4ajn%1&szw41%6EI$r%TlFLx~0TMqRK+ z`$uv7BgGn@-fB(%q-=A&pS!(fYikE@x@Hz%%X%5++x{6U_<#H-90}05o^w~KW5CL; z0gND>=S^tVM-v*5jT8M25G5mP1uJ+v>?R(SOFs5)BB0Ttx2C`&vp_^4qEOp6)N*fH z@fca{v$iwLW=J!$X;`j$J{P;7%caGkvIcxH%cB8}$pQNs%R4SJ(-}W&hRp|yh~IFC z`a6uIwhaM{x@jtpva$053YeKST9(irT_ueItG8AS67H&nENp+duVO=;JUMi!Jiotw z$;SM}gm%0Y^DCl{_we3Y*R@AOvgHWIU&K`l5rxqim9P#j)cX1rf?pcKP257`|A6?D zE7$@OPr92J`=c{!T{ZIiIAx-6^d%r2Cjp}MGg=lYz21~m*wH?T?Y@F`l4GnvF1|Ol zb&NwsLY`nE*FEF$-TqrfcKu`P8wnv!8Wv(T;;hU<4W) z*mn)KH9R}`Br{D?0ncdoa|CDa&sDuG=n$qX5xqy3597r(3DlKnrla+YV&m5E@LtGY zcZY5mpbf-2xdd0g7$U#C9;iMrDA+NdEJ-K^UAFP+lqA4qJ8rQ?UC%q&yVCo`?do<# z@9K2ruUU8w`zgg&eUD$g} zK|Y^)0_E4IP4|`-rFbcuBILPe)d)552LfwKYMbY8+?~UJDa#{(@eybYk{=vH)j>0<4&b&VbN=ZH)Y@wbn@pXb?6cM zkSZ0rs@04zDd?69Etz^{J8sTeha<>_b1i`0{s14z&SaQpiVNl*2d+|L%qZ#RncLM>yJ1! zTYL|VHGX_IK8s46Js$4M(*{r!a^jS?+1d==H80eH!0+3j;Su>bLN(euxull@4Cj)) z@W#f|+V3IBzC+SKyuT^wydRlTpIr?q71#T;B)F`hC);0ilCWdC4%q4PhtO7*Tyq#S z>G3uDE0SCcFnUIggHHYIX&f1fNZuDd&^n$ZulDylMhbY`eIaKVekCTjGq-=~9&bKC z-j4*If55nM5}cTsMG)TnMj{n>Fn07&0$p*E$-}7YB=8-d3}KvZU>`36)IuFtQh%`Y zkm1=o_F>b_(t2*^58qoY{AiHR!Z_NyHIl3nOYvR3l!A4nhv~)I9P70hXibk zVDNWe`q!H?P^Nlamwfoy7% z0i+rKA94tPV%Cj{WT4Zx58x`lsI9@CqEDd?t29&@;WBaoYbDZl^s8OmdN z3or%vZ2PC%+2(qd@|flTOaVTQ`HAAh_ecRgoEaDZe8S}uz)<87@So1R1O@>g1!_f0_cH8U;)Mu9T4A!0?#s4U7Oz34TK8 zNBxa>^o;={fHO*;5JoY7Bc6Sr!0Cifi1N6<5zjtR;GD82#9_kUh-V)uaMsZi0x|h- z#A65z__F}^pgtieQXdijmoE31tpW`F_f(K4B*3wr1_JU|VhC{ae~-xjv-v>AKbrq5 zJ`da)IFR_%UIvhe1DG5C+25c2On~D^PlV{azX*>}CE$AijwL)1CJG)2K#>LD_W${* z_!j*)Pvz0e4GaZ#AwS)eMe)CG>K_he;MTy7x2M()W&h1BJ-Xq5p}_8jC+JM&BlMro q>e2ZCOaNY-J`wh+0d9=vma5X?5P&*r5D*H$*DS!GJE{Nu)Bgg-X0dnx literal 0 HcmV?d00001 diff --git a/src/DB_Content.cls b/src/DB_Content.cls new file mode 100644 index 0000000..639085b --- /dev/null +++ b/src/DB_Content.cls @@ -0,0 +1,126 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DB_Content" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private data_ As Excel.Worksheet +Private attributes_ As Excel.Worksheet + +Public Function Init(iData As Excel.Worksheet, iAttributes As Excel.Worksheet) + Set data_ = iData + Set attributes_ = iAttributes +End Function + +Public Function Attributes() As IteratorAttribute + Set Attributes = New IteratorAttribute + Call Attributes.Init(attributes_) +End Function + +Public Function IBegin() As IteratorContent + Set IBegin = New IteratorContent + Call IBegin.Init(data_) +End Function + +Public Function ILast() As IteratorContent + Set ILast = New IteratorContent + Call ILast.Init(data_) + Call ILast.GoLast +End Function + +Public Function INew() As IteratorContent + Set INew = New IteratorContent + Call INew.Init(data_) + Call INew.GoLast + Call INew.Increment +End Function + +Public Property Get Count() As Long + Count = ILast.row_ - IBegin.row_ + 1 +End Property + +Public Function FindTaskID(sID$) As IteratorContent + Dim iResult As IteratorContent: Set iResult = IBegin + If iResult.FindTaskID(sID) Then _ + Set FindTaskID = iResult +End Function + +Public Function FindContentName(sName$, sType$) As IteratorContent + Dim iResult As IteratorContent: Set iResult = IBegin + If iResult.FindContentName(sName, sType) Then _ + Set FindContentName = iResult +End Function + +Public Function EnsureDataVisible() + Call XLShowAllData(data_, bKeepColumns:=True) + Call XLShowAllData(attributes_) +End Function + +Public Function ImportCSVTasks(iInput As IteratorCSVTasks) + Call iInput.GoFirst + Dim iOutput As IteratorContent + Do While Not iInput.IsDone + Set iOutput = FindPlaceFor(iInput.TaskID, iInput.ContentName, iInput.TaskType) + If Not iInput.IsCanceled Or iOutput.TaskType <> "" Then + Call iOutput.SyncCSVTasks(iInput) + End If + If CSE_ProgressBar.Visible Then _ + Call CSE_ProgressBar.IncrementA + Call iInput.Increment + Loop +End Function + +Public Function ImportCSVContent(iInput As IteratorCSVContent) + Call iInput.GoFirst + Dim iOutput As IteratorContent + Do While Not iInput.IsDone + Set iOutput = FindPlaceFor(iInput.TaskID) + Call iOutput.SyncCSVContent(iInput) + If CSE_ProgressBar.Visible Then _ + Call CSE_ProgressBar.IncrementA + Call iInput.Increment + Loop +End Function + +Public Function ImportDB(iData As DB_Content) + Dim iInput As IteratorContent: Set iInput = iData.IBegin() + Dim iOutput As IteratorContent + Do While Not iInput.IsDone + Set iOutput = FindPlaceFor(iInput.TaskID, iInput.ContentName, iInput.TaskType) + If Not iInput.IsCanceled Or iOutput.TaskType <> "" Then _ + Call iOutput.SyncContent(iInput) + If CSE_ProgressBar.Visible Then _ + Call CSE_ProgressBar.IncrementA + Call iInput.Increment + Loop + Call ImportAttributes(iData.Attributes) +End Function + +' ======= +Private Function FindPlaceFor(sID$, Optional sName$ = "", Optional sType$ = "") As IteratorContent + Dim iWhere As IteratorContent: Set iWhere = IBegin + If Not iWhere.FindTaskID(sID) Then _ + If Not iWhere.FindContentName(sName, sType) Or iWhere.TaskID <> "" Then _ + Call iWhere.GoEmpty + Set FindPlaceFor = iWhere +End Function + +Private Function ImportAttributes(iInput As IteratorAttribute) + Dim iOutput As IteratorAttribute: Set iOutput = Attributes + Call iOutput.GoEmpty + Do While Not iInput.IsDone + Call iOutput.SyncWith(iInput) + Call iOutput.Increment + Call iInput.Increment + Loop + Call DeleteAttributeDuplicates +End Function + +Private Function DeleteAttributeDuplicates() + +End Function diff --git a/src/DB_Workers.cls b/src/DB_Workers.cls new file mode 100644 index 0000000..8615a04 --- /dev/null +++ b/src/DB_Workers.cls @@ -0,0 +1,28 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "DB_Workers" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private data_ As Excel.ListObject + +Public Function Init(oTable As Excel.ListObject) + Set data_ = oTable +End Function + +Public Function ExpandShortname(sShort$) As String + Dim dataRng As Excel.Range: Set dataRng = data_.Range + Dim nRow& + For nRow = 2 To dataRng.Rows.Count Step 1 + If dataRng.Cells(nRow, 2) = sShort Then + ExpandShortname = dataRng.Cells(nRow, 1) + Exit Function + End If + Next nRow + ExpandShortname = sShort +End Function diff --git a/src/Declarations.bas b/src/Declarations.bas new file mode 100644 index 0000000..58266f0 --- /dev/null +++ b/src/Declarations.bas @@ -0,0 +1,82 @@ +Attribute VB_Name = "Declarations" +Option Private Module +Option Explicit + +Public Const DATA_FIRST_ROW = 2 + +Public Const NO_CONTENT_PLACEHOLDER = " " +Public Const NO_MEDIA_PLACEHOLDER = " " + +Public Const STATUS_CANCELED = "" +Public Const BOOL_TEXT_YES = "" +Public Const BOOL_TEXT_NO = "" + +Public Const SHEET_CONTENT = "" +Public Const SHEET_OPTIONS = "" +Public Const SHEET_ATTRIBUTES = "" +Public Const SHEET_CONFIG = "" + +Public Const TABLE_WORKERS = "t_Person" +Public Const TABLE_MARKERS = "t_Markers" +Public Const TABLE_TAGS = "t_Tags" + +Public Const URL_PREFIX_TASK = "https://rk.greatbook.ru/tasks/" +Public Const URL_PREFIX_CONTENT = "https://rk.greatbook.ru/widgets?link=task&id=" + +Public Const EXPORTER_CONFIG_FILE = "exporter.ini" +Public Const EXPORTER_EXECUTABLE = "exporter.exe" + +Public Const PREFIX_IMMUTABLE = " " +Public Const SUFFIX_IMMUTABLE = " (++)" + +Public Enum TUpdateStatus + T_UPD_UNDEF = 0 + [_First] = 1 + + T_UPD_COMPLETE = 1 + T_UPD_IGNORE = 2 + T_UPD_AUTO = 3 + T_UPD_ONCE = 4 + T_UPD_ALWAYS = 5 + + [_Last] = 5 +End Enum + +Public Function InferContentFromTask(sTask$) As String + Dim sContent$: sContent = sTask + If sContent Like PREFIX_IMMUTABLE & "*" Then _ + sContent = VBA.Right(sContent, VBA.Len(sContent) - VBA.Len(PREFIX_IMMUTABLE)) + If sContent Like "*" & SUFFIX_IMMUTABLE Then _ + sContent = VBA.Left(sContent, VBA.Len(sContent) - VBA.Len(SUFFIX_IMMUTABLE)) + InferContentFromTask = sContent +End Function + +Public Function UpdateStatusFromText(sText$) As TUpdateStatus + If sText = "" Then + UpdateStatusFromText = T_UPD_COMPLETE + ElseIf sText = "" Then + UpdateStatusFromText = T_UPD_IGNORE + ElseIf sText = "" Then + UpdateStatusFromText = T_UPD_AUTO + ElseIf sText = "" Then + UpdateStatusFromText = T_UPD_ONCE + ElseIf sText = "" Then + UpdateStatusFromText = T_UPD_ALWAYS + Else + UpdateStatusFromText = T_UPD_UNDEF + End If +End Function + +Public Function UpdateStatusToText(iStatus As TUpdateStatus) As String + Select Case iStatus + Case T_UPD_UNDEF: UpdateStatusToText = "" + Case T_UPD_COMPLETE: UpdateStatusToText = "" + Case T_UPD_IGNORE: UpdateStatusToText = "" + Case T_UPD_AUTO: UpdateStatusToText = "" + Case T_UPD_ONCE: UpdateStatusToText = "" + Case T_UPD_ALWAYS: UpdateStatusToText = "" + Case Else: UpdateStatusToText = "" + End Select +End Function + + diff --git a/src/DevHelper.bas b/src/DevHelper.bas new file mode 100644 index 0000000..6efaa08 --- /dev/null +++ b/src/DevHelper.bas @@ -0,0 +1,20 @@ +Attribute VB_Name = "DevHelper" +Option Explicit + +Public Function Dev_PrepareSkeleton() + Call ClearData +End Function + +Public Sub Dev_ManualRunTest() + Dim sSuite$: sSuite = "s_Database" + Dim sTest$: sTest = "t_RenameLawFile" + Dim sMsg$: sMsg = Dev_RunTestDebug(sSuite, sTest) + Debug.Print sMsg + Call MsgBox(sMsg) +End Sub + +Public Function Dev_GetTestSuite(sName$) As Object + Select Case sName +' Case "s_ActiveStateExporter": Set Dev_GetTestSuite = New s_ActiveStateExporter + End Select +End Function diff --git a/src/InfoConfig.cls b/src/InfoConfig.cls new file mode 100644 index 0000000..0c5ca40 --- /dev/null +++ b/src/InfoConfig.cls @@ -0,0 +1,163 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "InfoConfig" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Private Const S_CONFIG_KEY = 1 +Private Const S_CONFIG_VALUE = 2 +Private Const S_CONFIG_COMMENT = 3 + +Private Enum AppdataRows + [_First] = 2 + + R_APP_FILTER_TASK = 2 + R_APP_FILTER_DEPARTMENT = 3 + R_APP_FILTER_STATUS = 4 + R_APP_FILTER_RESPONSIBLE = 5 + R_APP_FILTER_SUPERVISOR = 6 + R_APP_FILTER_EXECUTOR = 7 + R_APP_FILTER_OBSERVER = 8 + + R_APP_FILTER_CREATED_BEGIN = 9 + R_APP_FILTER_CREATED_END = 10 + R_APP_FILTER_TARGET_BEGIN = 11 + R_APP_FILTER_TARGET_END = 12 + + R_APP_OUTPUT = 13 + R_APP_SCAN_TASKS = 14 + R_APP_SCAN_CONTENT = 15 + R_APP_OUTPUT_CONTENT = 16 + R_APP_ACCESS_TOKEN = 17 + + [_Last] = 17 +End Enum + +Private Enum UserRows + [_First] = 19 + + R_USER_NAME = 19 + R_USER_LOGIN = 20 + R_USER_PASSWORD = 21 + + [_Last] = 21 +End Enum + +Private Enum OptionsRows + [_First] = 23 + + R_OPT_DEBUG = 23 + R_OPT_TESTRUN = 24 + R_OPT_TIMEOUT = 25 + + [_Last] = 25 +End Enum + +Private data_ As Excel.Worksheet +Private content_ As DB_Content + +Public Function Init(iData As Excel.Worksheet, iContent As DB_Content) + Set data_ = iData + Set content_ = iContent +End Function + +Public Function SetScanContent(bScanContent As Boolean) + data_.Cells(R_APP_SCAN_CONTENT, S_CONFIG_VALUE) = IIf(bScanContent, "true", "false") +End Function + +Public Function SetScanTasks(bScanContent As Boolean) + data_.Cells(R_APP_SCAN_TASKS, S_CONFIG_VALUE) = IIf(bScanContent, "true", "false") +End Function + +Public Function ConfigFilePath() As String + ConfigFilePath = data_.Parent.Path & "\" & EXPORTER_CONFIG_FILE +End Function + +Public Function OutputFileTasks() As String + OutputFileTasks = data_.Parent.Path & "\" & data_.Cells(R_APP_OUTPUT, S_CONFIG_VALUE) +End Function + +Public Function OutputFileContent() As String + OutputFileContent = data_.Parent.Path & "\" & data_.Cells(R_APP_OUTPUT_CONTENT, S_CONFIG_VALUE) +End Function + +Public Function ScanContent() As Boolean + ScanContent = data_.Cells(R_APP_SCAN_CONTENT, S_CONFIG_VALUE) +End Function + +Public Function ScanTasks() As Boolean + ScanTasks = data_.Cells(R_APP_SCAN_TASKS, S_CONFIG_VALUE) +End Function + +Public Function CreateConfigFile() + Dim nRow& + Dim sValue$, sKey$ + Dim iOut As New ADODB.Stream: iOut.Charset = "utf-8" + Call iOut.Open + + Call iOut.WriteText("[AppData]", adWriteLine) + For nRow = AppdataRows.[_First] To AppdataRows.[_Last] Step 1 + sKey = data_.Cells(nRow, S_CONFIG_KEY) + sValue = data_.Cells(nRow, S_CONFIG_VALUE) + Call iOut.WriteText(Fmt("{1}={2}", sKey, sValue), adWriteLine) + Next nRow + Call PrepareLists(iOut) + + Call iOut.WriteText("[Options]", adWriteLine) + For nRow = OptionsRows.[_First] To OptionsRows.[_Last] Step 1 + sKey = data_.Cells(nRow, S_CONFIG_KEY) + sValue = data_.Cells(nRow, S_CONFIG_VALUE) + Call iOut.WriteText(Fmt("{1}={2}", sKey, sValue), adWriteLine) + Next nRow + + Call iOut.WriteText("[UserData]", adWriteLine) + For nRow = UserRows.[_First] To UserRows.[_Last] Step 1 + sKey = data_.Cells(nRow, S_CONFIG_KEY) + sValue = data_.Cells(nRow, S_CONFIG_VALUE) + Call iOut.WriteText(Fmt("{1}={2}", sKey, sValue), adWriteLine) + Next nRow + + Dim iOutNoBOM As New ADODB.Stream + iOutNoBOM.Type = adTypeBinary + Call iOutNoBOM.Open + + iOut.Position = 3 + Call iOut.CopyTo(iOutNoBOM) + + Call iOutNoBOM.SaveToFile(ConfigFilePath, adSaveCreateOverWrite) + Call iOutNoBOM.Close + + Call iOut.Close +End Function + +Public Function DeleteConfigFile() + Call Kill(ConfigFilePath) +End Function + +' ======== +Private Function PrepareLists(iOut As ADODB.Stream) As String + Dim iExclude$: iExclude = "" + Dim iInclude$: iInclude = "" + Dim iContent As IteratorContent: Set iContent = content_.IBegin + Dim iStatus As TUpdateStatus + Do While Not iContent.IsDone + Call iContent.RecalculateStatus + If iContent.IsIgnored Then + If iExclude <> "" Then iExclude = iExclude & ";" + iExclude = iExclude & iContent.TaskID + End If + If iContent.NeedsUpdate Then + If iInclude <> "" Then iInclude = iInclude & ";" + iInclude = iInclude & iContent.TaskID + End If + Call iContent.Increment + Loop + + Call iOut.WriteText(Fmt("{1}={2}", "ExcludeID", iExclude), adWriteLine) + Call iOut.WriteText(Fmt("{1}={2}", "IncludeID", iInclude), adWriteLine) +End Function diff --git a/src/IteratorAttribute.cls b/src/IteratorAttribute.cls new file mode 100644 index 0000000..688872c --- /dev/null +++ b/src/IteratorAttribute.cls @@ -0,0 +1,99 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "IteratorAttribute" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' _A_ Attrtibutes structure +Private Enum AttributesStruct + [_First] = 1 + + S_A_CONTENT_NAME = 1 + S_A_ATTRIBUTE = 2 + S_A_VALUE = 3 + S_A_INPUT_METHOD = 4 + + [_Last] = 4 +End Enum + +Public row_ As Long + +Private data_ As Excel.Worksheet + +Public Sub Init(target As Excel.Worksheet, Optional tRow& = DATA_FIRST_ROW) + Set data_ = target + row_ = tRow +End Sub + +Public Function Increment(Optional inc& = 1) + If row_ + inc > 0 Then _ + row_ = row_ + inc +End Function + +Public Function GoFirst() + row_ = DATA_FIRST_ROW +End Function + +Public Function GoLast() + row_ = GetLastRow +End Function + +Public Function GoEmpty() + Call GoLast + Call Increment +End Function + +Public Function IsDone() As Boolean + IsDone = row_ > GetLastRow +End Function + +Public Function SyncWith(iInput As IteratorAttribute) + ContentName = iInput.ContentName + Attr = iInput.Attr + Value = iInput.Value + InputMethod = iInput.InputMethod +End Function + +' ======== Property Get ========= +Public Property Get ContentName() As String + ContentName = data_.Cells(row_, S_A_CONTENT_NAME) +End Property + +Public Property Get Attr() As String + Attr = data_.Cells(row_, S_A_ATTRIBUTE) +End Property + +Public Property Get Value() As String + Value = data_.Cells(row_, S_A_VALUE) +End Property + +Public Property Get InputMethod() As String + InputMethod = data_.Cells(row_, S_A_INPUT_METHOD) +End Property + +' ==== Property Let ==== +Public Property Let ContentName(newVal$) + data_.Cells(row_, S_A_CONTENT_NAME) = newVal +End Property + +Public Property Let Attr(newVal$) + data_.Cells(row_, S_A_ATTRIBUTE) = newVal +End Property + +Public Property Let Value(newVal$) + data_.Cells(row_, S_A_VALUE) = newVal +End Property + +Public Property Let InputMethod(newVal$) + data_.Cells(row_, S_A_INPUT_METHOD) = newVal +End Property + +' ======= +Private Function GetLastRow() As Long + GetLastRow = data_.Cells(data_.Rows.Count, 1).End(xlUp).Row +End Function diff --git a/src/IteratorCSVContent.cls b/src/IteratorCSVContent.cls new file mode 100644 index 0000000..265a7a4 --- /dev/null +++ b/src/IteratorCSVContent.cls @@ -0,0 +1,134 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "IteratorCSVContent" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' _I_ Input CSV structure +Private Enum InputStruct + [_First] = 1 + + S_I_TASK_ID = 1 + S_I_BIBLIO_NAME = 2 + S_I_CHANGE_SCORE = 3 + S_I_DEPARTMENT = 4 + S_I_RESPONSIBLE = 5 + S_I_DEFINITION = 6 + S_I_OBJECT_TYPE = 7 + S_I_MARKERS = 8 + S_I_TAGS = 9 + S_I_SOURCE = 10 + S_I_ELECTRON_BRE = 11 + S_I_MAIN_PAGE = 12 + S_I_IS_GENERAL = 13 + S_I_ACTUALIZE_PERIOD = 14 + S_I_AGE_RESTRICTION = 15 + S_I_AUTHOR = 16 + S_I_EDITOR = 17 + + [_Last] = 17 +End Enum + +Public row_ As Long + +Private data_ As Excel.Worksheet + +Public Sub Init(target As Excel.Worksheet, Optional tRow& = 1) + Set data_ = target + row_ = tRow +End Sub + +Public Function Increment(Optional inc& = 1) + If row_ + inc > 0 Then _ + row_ = row_ + inc +End Function + +Public Function GoFirst() + row_ = 1 +End Function + +Public Function GoLast() + row_ = data_.Columns(S_I_TASK_ID).Find(vbNullString, LookAt:=xlWhole).Row - 1 +End Function + +Public Property Get CountRows() As Long + CountRows = data_.Columns(S_I_TASK_ID).Find(vbNullString, LookAt:=xlWhole).Row - 1 +End Property + +Public Function IsDone() As Boolean + IsDone = data_.Cells(row_, S_I_TASK_ID) = vbNullString +End Function + +'===== Propertiy Get ===== +Public Property Get TaskID() As String + TaskID = data_.Cells(row_, S_I_TASK_ID) +End Property + +Public Property Get BiblioName() As String + BiblioName = data_.Cells(row_, S_I_BIBLIO_NAME) +End Property + +Public Property Get ChangeScore() As String + ChangeScore = data_.Cells(row_, S_I_CHANGE_SCORE) +End Property + +Public Property Get Department() As String + Department = data_.Cells(row_, S_I_DEPARTMENT) +End Property + +Public Property Get Responsible() As String + Responsible = data_.Cells(row_, S_I_RESPONSIBLE) +End Property + +Public Property Get Definition() As String + Definition = data_.Cells(row_, S_I_DEFINITION) +End Property + +Public Property Get ObjectType() As String + ObjectType = data_.Cells(row_, S_I_OBJECT_TYPE) +End Property + +Public Property Get Markers() As String + Markers = data_.Cells(row_, S_I_MARKERS) +End Property + +Public Property Get Tags() As String + Tags = data_.Cells(row_, S_I_TAGS) +End Property + +Public Property Get Source() As String + Source = data_.Cells(row_, S_I_SOURCE) +End Property + +Public Property Get ElectronBre() As String + ElectronBre = data_.Cells(row_, S_I_ELECTRON_BRE) +End Property + +Public Property Get MainPage() As String + MainPage = data_.Cells(row_, S_I_MAIN_PAGE) +End Property + +Public Property Get IsGeneral() As String + IsGeneral = data_.Cells(row_, S_I_IS_GENERAL) +End Property + +Public Property Get ActualizePeriod() As String + ActualizePeriod = data_.Cells(row_, S_I_ACTUALIZE_PERIOD) +End Property + +Public Property Get AgeRestriction() As String + AgeRestriction = data_.Cells(row_, S_I_AGE_RESTRICTION) +End Property + +Public Property Get Author() As String + Author = data_.Cells(row_, S_I_AUTHOR) +End Property + +Public Property Get Editor() As String + Editor = data_.Cells(row_, S_I_EDITOR) +End Property diff --git a/src/IteratorCSVTasks.cls b/src/IteratorCSVTasks.cls new file mode 100644 index 0000000..14a09da --- /dev/null +++ b/src/IteratorCSVTasks.cls @@ -0,0 +1,118 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "IteratorCSVTasks" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' _I_ Input CSV structure +Private Enum InputStruct + [_First] = 1 + + S_I_TASK_TYPE = 1 + S_I_STATUS = 2 + S_I_CONTENT_NAME_DB = 3 + S_I_SUPERVISOR = 4 + S_I_EXECUTOR = 5 + S_I_TARGET_DATE = 6 + S_I_TASK_ID = 7 + S_I_TASK_NAME = 8 + S_I_PARENT_ID = 9 + + [_Last] = 9 +End Enum + +Public row_ As Long + +Private data_ As Excel.Worksheet +Private workers_ As DB_Workers + +Public Sub Init(target As Excel.Worksheet, dbWorkers As DB_Workers, Optional tRow& = 1) + Set data_ = target + row_ = tRow + Set workers_ = dbWorkers +End Sub + +Public Function Increment(Optional inc& = 1) + If row_ + inc > 0 Then _ + row_ = row_ + inc +End Function + +Public Function GoFirst() + row_ = 1 +End Function + +Public Function GoLast() + row_ = data_.Columns(S_I_TASK_TYPE).Find(vbNullString, LookAt:=xlWhole).Row - 1 +End Function + +Public Property Get CountRows() As Long + CountRows = data_.Columns(S_I_TASK_TYPE).Find(vbNullString, LookAt:=xlWhole).Row - 1 +End Property + +Public Function IsDone() As Boolean + IsDone = data_.Cells(row_, S_I_TASK_TYPE) = vbNullString +End Function + +Public Function IsCanceled() As Boolean + IsCanceled = Status = STATUS_CANCELED +End Function + +Public Function HasContent() As Boolean + Dim sContent$: sContent = ContentNameDB + HasContent = sContent <> NO_CONTENT_PLACEHOLDER And sContent <> NO_MEDIA_PLACEHOLDER +End Function + +'===== Propertiy Get ===== +Public Property Get TaskType() As String + Dim sText$: sText = data_.Cells(row_, S_I_TASK_TYPE) + If sText = " " Then + TaskType = " " + Else + TaskType = CapitalizeFirstLetter(VBA.LCase(sText)) + End If +End Property + +Public Property Get Status() As String + Status = data_.Cells(row_, S_I_STATUS) +End Property + +Public Property Get ContentName() As String + If Not HasContent Then + ContentName = InferContentFromTask(TaskName) + Else + ContentName = ContentNameDB + End If +End Property + +Public Property Get ContentNameDB() As String + ContentNameDB = data_.Cells(row_, S_I_CONTENT_NAME_DB) +End Property + +Public Property Get Supervisor() As String + Supervisor = workers_.ExpandShortname(data_.Cells(row_, S_I_SUPERVISOR)) +End Property + +Public Property Get Executor() As String + Executor = workers_.ExpandShortname(data_.Cells(row_, S_I_EXECUTOR)) +End Property + +Public Property Get TargetDate() As Long + TargetDate = VBA.CDate(data_.Cells(row_, S_I_TARGET_DATE)) +End Property + +Public Property Get TaskID() As String + TaskID = data_.Cells(row_, S_I_TASK_ID) +End Property + +Public Property Get TaskName() As String + TaskName = data_.Cells(row_, S_I_TASK_NAME) +End Property + +Public Property Get ParentID() As String + ParentID = data_.Cells(row_, S_I_PARENT_ID) +End Property diff --git a/src/IteratorContent.cls b/src/IteratorContent.cls new file mode 100644 index 0000000..f2488e0 --- /dev/null +++ b/src/IteratorContent.cls @@ -0,0 +1,595 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "IteratorContent" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +' _C_ Content structure +Private Enum ContentStruct + [_First] = 1 + + S_C_TASK_TYPE = 1 + S_C_STATUS = 2 + S_C_CONTENT_NAME = 3 + S_C_UPDATE_STATUS = 4 + S_C_CHANGE_SCORE = 5 + S_C_BIBLIO_NAME = 6 + S_C_DEFINITION = 7 + S_C_IS_IMMUTABLE = 8 + S_C_OBJECT_TYPE = 9 + S_C_MARKERS = 10 + S_C_TAGS = 11 + S_C_AUTHOR = 12 + S_C_SUPERVISOR = 13 + S_C_EXECUTOR = 14 + S_C_EDITOR = 15 + S_C_RESPONSIBLE = 16 + S_C_DEPARTMENT = 17 + S_C_TARGET_DATE = 18 + S_C_SOURCE = 19 + S_C_ELECTRON_BRE = 20 + S_C_MAIN_PAGE = 21 + S_C_IS_GENERAL = 22 + S_C_ACTUALIZE_PERIOD = 23 + S_C_AGE_RESTRICTION = 24 + S_C_PRIORITY = 25 + S_C_ARTICLE_TYPE = 26 + S_C_DATE_EXCHANGE = 27 + S_C_DATE_EES1 = 28 + S_C_DATE_EX_TOOLS = 29 + S_C_DATE_EES2 = 30 + S_C_EXPERT = 31 + S_C_CONTRACT = 32 + S_C_COMMENT = 33 + S_C_TASK_ID = 34 + S_C_CONTENT_NAME_DB = 35 + S_C_TASK_NAME = 36 + S_C_PARENT_ID = 37 + + [_Last] = 37 +End Enum + +Public row_ As Long + +Private data_ As Excel.Worksheet + +Public Sub Init(target As Excel.Worksheet, Optional tRow& = DATA_FIRST_ROW) + Set data_ = target + row_ = tRow +End Sub + +Public Function Increment(Optional inc& = 1) + If row_ + inc > 0 Then _ + row_ = row_ + inc +End Function + +Public Function GoFirst() + row_ = DATA_FIRST_ROW +End Function + +Public Function GoLast() + row_ = GetLastRow +End Function + +Public Function GoEmpty() + Call GoLast + Call Increment +End Function + +Public Function IsDone() As Boolean + IsDone = row_ > GetLastRow +End Function + +Public Function IsCanceled() As Boolean + IsCanceled = Status = STATUS_CANCELED +End Function + +Public Function HasContent() As Boolean + Dim sContent$: sContent = ContentNameDB + HasContent = sContent <> NO_CONTENT_PLACEHOLDER And sContent <> NO_MEDIA_PLACEHOLDER +End Function + +Public Function RemoveRow() + Call data_.Rows(row_).Delete +End Function + +Public Function FindTaskID(sID$) As Boolean + If sID = "" Then _ + Exit Function + Dim iFound As Excel.Range: + Set iFound = data_.Columns(S_C_TASK_ID).Find(sID, LookAt:=xlWhole) + FindTaskID = Not iFound Is Nothing + If FindTaskID Then _ + row_ = iFound.Row +End Function + +Public Function FindContentName(sName$, sType$) As Boolean + If sName = "" Then _ + Exit Function + Dim iFound As Excel.Range: + Set iFound = data_.Columns(S_C_CONTENT_NAME).Find(sName, LookAt:=xlWhole) + If iFound Is Nothing Then _ + Exit Function + If data_.Cells(iFound.Row, S_C_TASK_TYPE) <> sType Then _ + Exit Function + row_ = iFound.Row + FindContentName = True +End Function + +Public Function SyncCSVTasks(iInput As IteratorCSVTasks) + TaskType = iInput.TaskType + Status = iInput.Status + ContentName = iInput.ContentName + ContentNameDB = iInput.ContentNameDB + Supervisor = iInput.Supervisor + Executor = iInput.Executor + TargetDate = iInput.TargetDate + TaskID = iInput.TaskID + TaskName = iInput.TaskName + ParentID = iInput.ParentID +End Function + +Public Function SyncCSVContent(iInput As IteratorCSVContent) + Call ValidateValue(S_C_BIBLIO_NAME, iInput.BiblioName) + Call ValidateValue(S_C_CHANGE_SCORE, iInput.ChangeScore) + Call ValidateValue(S_C_DEPARTMENT, iInput.Department) + Call ValidateValue(S_C_RESPONSIBLE, iInput.Responsible) + Call ValidateValue(S_C_EDITOR, iInput.Editor) + Call ValidateValue(S_C_DEFINITION, iInput.Definition) + Call ValidateValue(S_C_OBJECT_TYPE, iInput.ObjectType) + Call ValidateListValue(S_C_MARKERS, iInput.Markers) + Call ValidateListValue(S_C_TAGS, iInput.Tags) + Call ValidateValue(S_C_SOURCE, iInput.Source) + Call ValidateValue(S_C_ELECTRON_BRE, iInput.ElectronBre) + Call ValidateValue(S_C_MAIN_PAGE, iInput.MainPage) + Call ValidateValue(S_C_IS_GENERAL, iInput.IsGeneral) + Call ValidateValue(S_C_ACTUALIZE_PERIOD, iInput.ActualizePeriod) + Call ValidateValue(S_C_AGE_RESTRICTION, iInput.AgeRestriction) + Call ValidateValue(S_C_AUTHOR, iInput.Author) + If UpdateStatus = T_UPD_ONCE Then _ + UpdateStatus = T_UPD_UNDEF + Call RecalculateStatus +End Function + +Public Function SyncContent(iInput As IteratorContent) + TaskType = iInput.TaskType + Status = iInput.Status + ContentName = iInput.ContentName + Supervisor = iInput.Supervisor + Executor = iInput.Executor + TargetDate = iInput.TargetDate + + IsImmutable = iInput.IsImmutable + IsMain = iInput.IsMain + IsGeneral = iInput.IsGeneral + IsBRE = iInput.IsBRE + + UpdateStatus = iInput.UpdateStatus + If iInput.BiblioName <> "" Then BiblioName = iInput.BiblioName + If iInput.ChangeScore <> "" Then ChangeScore = iInput.ChangeScore + If iInput.Definition <> "" Then Definition = iInput.Definition + If iInput.ObjectType <> "" Then ObjectType = iInput.ObjectType + If iInput.Markers <> "" Then Markers = iInput.Markers + If iInput.Author <> "" Then Author = iInput.Author + If iInput.Tags <> "" Then Tags = iInput.Tags + If iInput.Editor <> "" Then Editor = iInput.Editor + If iInput.Responsible <> "" Then Responsible = iInput.Responsible + If iInput.Department <> "" Then Department = iInput.Department + If iInput.Source <> "" Then Source = iInput.Source + If iInput.ActualizePeriod <> "" Then ActualizePeriod = iInput.ActualizePeriod + If iInput.AgeRestriction <> "" Then AgeRestriction = iInput.AgeRestriction + If iInput.Priority <> "" Then Priority = iInput.Priority + If iInput.ArticleType <> "" Then ArticleType = iInput.ArticleType + If iInput.Expert <> "" Then Expert = iInput.Expert + If iInput.Contract <> "" Then Contract = iInput.Contract + If iInput.ContentNameDB <> "" Then ContentNameDB = iInput.ContentNameDB + If iInput.TaskID <> "" Then TaskID = iInput.TaskID + If iInput.TaskName <> "" Then TaskName = iInput.TaskName + If iInput.ParentID <> "" Then ParentID = iInput.ParentID + + Call RecalculateStatus +End Function + +Public Function HasBlanks() As Boolean + HasBlanks = True + + If BiblioName = "" Then _ + Exit Function + If Definition = "" Then _ + Exit Function + If Markers = "" Then _ + Exit Function + If ObjectType = "" Then _ + Exit Function + If Supervisor = "" Then _ + Exit Function + If Editor = "" Then _ + Exit Function + + HasBlanks = False +End Function + +Public Function IsIgnored() As Boolean + Dim oldStatus As TUpdateStatus: oldStatus = UpdateStatus + IsIgnored = oldStatus = T_UPD_COMPLETE Or oldStatus = T_UPD_IGNORE +End Function + +Public Function NeedsUpdate() As Boolean + Dim oldStatus As TUpdateStatus: oldStatus = UpdateStatus + NeedsUpdate = oldStatus = T_UPD_AUTO Or oldStatus = T_UPD_ONCE Or oldStatus = T_UPD_ALWAYS +End Function + +Public Function RecalculateStatus() As TUpdateStatus + Dim oldStatus As TUpdateStatus: oldStatus = UpdateStatus + RecalculateStatus = oldStatus + If oldStatus = T_UPD_ONCE Or oldStatus = T_UPD_ALWAYS Or oldStatus = T_UPD_IGNORE Then _ + Exit Function + If TaskID = "" Then + UpdateStatus = T_UPD_UNDEF + ElseIf ContentNameDB = " " Or Status = "" Then + UpdateStatus = T_UPD_UNDEF + ElseIf HasBlanks Then + UpdateStatus = T_UPD_AUTO + Else + UpdateStatus = T_UPD_COMPLETE + End If + RecalculateStatus = UpdateStatus +End Function + +' ======== Property Get ========= +Public Property Get TaskType() As String + TaskType = data_.Cells(row_, S_C_TASK_TYPE) +End Property + +Public Property Get Status() As String + Status = data_.Cells(row_, S_C_STATUS) +End Property + +Public Property Get ContentName() As String + ContentName = data_.Cells(row_, S_C_CONTENT_NAME) +End Property + +Public Property Get UpdateStatus() As TUpdateStatus + UpdateStatus = UpdateStatusFromText(data_.Cells(row_, S_C_UPDATE_STATUS)) +End Property + +Public Property Get ChangeScore() As String + ChangeScore = data_.Cells(row_, S_C_CHANGE_SCORE) +End Property + +Public Property Get BiblioName() As String + BiblioName = data_.Cells(row_, S_C_BIBLIO_NAME) +End Property + +Public Property Get Definition() As String + Definition = data_.Cells(row_, S_C_DEFINITION) +End Property + +Public Property Get IsImmutable() As Boolean + IsImmutable = data_.Cells(row_, S_C_IS_IMMUTABLE) = BOOL_TEXT_YES +End Property + +Public Property Get ObjectType() As String + ObjectType = data_.Cells(row_, S_C_OBJECT_TYPE) +End Property + +Public Property Get Markers() As String + Markers = data_.Cells(row_, S_C_MARKERS) +End Property + +Public Property Get Tags() As String + Tags = data_.Cells(row_, S_C_TAGS) +End Property + +Public Property Get Author() As String + Author = data_.Cells(row_, S_C_AUTHOR) +End Property + +Public Property Get Supervisor() As String + Supervisor = data_.Cells(row_, S_C_SUPERVISOR) +End Property + +Public Property Get Executor() As String + Executor = data_.Cells(row_, S_C_EXECUTOR) +End Property + +Public Property Get Editor() As String + Editor = data_.Cells(row_, S_C_EDITOR) +End Property + +Public Property Get Responsible() As String + Responsible = data_.Cells(row_, S_C_RESPONSIBLE) +End Property + +Public Property Get Department() As String + Department = data_.Cells(row_, S_C_DEPARTMENT) +End Property + +Public Property Get TargetDate() As String + TargetDate = data_.Cells(row_, S_C_TARGET_DATE) +End Property + +Public Property Get Source() As String + Source = data_.Cells(row_, S_C_SOURCE) +End Property + +Public Property Get IsBRE() As Boolean + IsBRE = data_.Cells(row_, S_C_ELECTRON_BRE) = BOOL_TEXT_YES +End Property + +Public Property Get IsMain() As Boolean + IsMain = data_.Cells(row_, S_C_MAIN_PAGE) = BOOL_TEXT_YES +End Property + +Public Property Get IsGeneral() As Boolean + IsGeneral = data_.Cells(row_, S_C_IS_GENERAL) = BOOL_TEXT_YES +End Property + +Public Property Get ActualizePeriod() As String + ActualizePeriod = data_.Cells(row_, S_C_ACTUALIZE_PERIOD) +End Property + +Public Property Get AgeRestriction() As String + AgeRestriction = data_.Cells(row_, S_C_AGE_RESTRICTION) +End Property + +Public Property Get Priority() As String + Priority = data_.Cells(row_, S_C_PRIORITY) +End Property + +Public Property Get ArticleType() As String + ArticleType = data_.Cells(row_, S_C_ARTICLE_TYPE) +End Property + +Public Property Get DateExchange() As String + DateExchange = data_.Cells(row_, S_C_DATE_EXCHANGE) +End Property + +Public Property Get DateEES1() As String + DateEES1 = data_.Cells(row_, S_C_DATE_EES1) +End Property + +Public Property Get DateTools() As String + DateTools = data_.Cells(row_, S_C_DATE_EX_TOOLS) +End Property + +Public Property Get DateEES2() As String + DateEES2 = data_.Cells(row_, S_C_DATE_EES2) +End Property + +Public Property Get Expert() As String + Expert = data_.Cells(row_, S_C_EXPERT) +End Property + +Public Property Get Contract() As String + Contract = data_.Cells(row_, S_C_CONTRACT) +End Property + +Public Property Get Comment() As String + Comment = data_.Cells(row_, S_C_COMMENT) +End Property + +Public Property Get TaskID() As String + TaskID = data_.Cells(row_, S_C_TASK_ID) +End Property + +Public Property Get ContentNameDB() As String + ContentNameDB = data_.Cells(row_, S_C_CONTENT_NAME_DB) +End Property + +Public Property Get TaskName() As String + TaskName = data_.Cells(row_, S_C_TASK_NAME) +End Property + +Public Property Get ParentID() As String + ParentID = data_.Cells(row_, S_C_PARENT_ID) +End Property + +' ==== Property Let ==== +Public Property Let TaskID(newVal$) + Dim oldVal$: oldVal = data_.Cells(row_, S_C_TASK_ID) + data_.Cells(row_, S_C_TASK_ID) = newVal + If newVal <> "" And (oldVal <> newVal Or data_.Cells(row_, S_C_TASK_TYPE).Hyperlinks.Count = 0) Then + Call XLUpdateHyperlink(data_.Cells(row_, S_C_TASK_TYPE), URL_PREFIX_TASK & newVal) + If HasContent Then + Call XLUpdateHyperlink(data_.Cells(row_, S_C_CONTENT_NAME), URL_PREFIX_CONTENT & newVal) + Else + Call data_.Cells(row_, S_C_CONTENT_NAME).Hyperlinks.Delete + End If + End If +End Property + +Public Property Let TaskType(newVal$) + data_.Cells(row_, S_C_TASK_TYPE) = newVal +End Property + +Public Property Let Status(newVal$) + data_.Cells(row_, S_C_STATUS) = newVal +End Property + +Public Property Let ContentName(newVal$) + data_.Cells(row_, S_C_CONTENT_NAME) = newVal +End Property + +Public Property Let UpdateStatus(newVal As TUpdateStatus) + data_.Cells(row_, S_C_UPDATE_STATUS) = UpdateStatusToText(newVal) +End Property + +Public Property Let ChangeScore(newVal$) + data_.Cells(row_, S_C_CHANGE_SCORE) = newVal +End Property + +Public Property Let BiblioName(newVal$) + data_.Cells(row_, S_C_BIBLIO_NAME) = newVal +End Property + +Public Property Let Definition(newVal$) + data_.Cells(row_, S_C_DEFINITION) = newVal +End Property + +Public Property Let IsImmutable(newVal As Boolean) + data_.Cells(row_, S_C_IS_IMMUTABLE) = IIf(newVal, BOOL_TEXT_YES, BOOL_TEXT_NO) +End Property + +Public Property Let ObjectType(newVal$) + data_.Cells(row_, S_C_OBJECT_TYPE) = newVal +End Property + +Public Property Let Markers(newVal$) + data_.Cells(row_, S_C_MARKERS) = newVal +End Property + +Public Property Let Tags(newVal$) + data_.Cells(row_, S_C_TAGS) = newVal +End Property + +Public Property Let Author(newVal$) + data_.Cells(row_, S_C_AUTHOR) = newVal +End Property + +Public Property Let Supervisor(newVal$) + data_.Cells(row_, S_C_SUPERVISOR) = newVal +End Property + +Public Property Let Executor(newVal$) + data_.Cells(row_, S_C_EXECUTOR) = newVal +End Property + +Public Property Let Editor(newVal$) + data_.Cells(row_, S_C_EDITOR) = newVal +End Property + +Public Property Let Responsible(newVal$) + data_.Cells(row_, S_C_RESPONSIBLE) = newVal +End Property + +Public Property Let Department(newVal$) + data_.Cells(row_, S_C_DEPARTMENT) = newVal +End Property + +Public Property Let TargetDate(newVal$) + data_.Cells(row_, S_C_TARGET_DATE) = newVal +End Property + +Public Property Let Source(newVal$) + data_.Cells(row_, S_C_SOURCE) = newVal +End Property + +Public Property Let IsBRE(newVal As Boolean) + data_.Cells(row_, S_C_ELECTRON_BRE) = IIf(newVal, BOOL_TEXT_YES, BOOL_TEXT_NO) +End Property + +Public Property Let IsMain(newVal As Boolean) + data_.Cells(row_, S_C_MAIN_PAGE) = IIf(newVal, BOOL_TEXT_YES, BOOL_TEXT_NO) +End Property + +Public Property Let IsGeneral(newVal As Boolean) + data_.Cells(row_, S_C_IS_GENERAL) = IIf(newVal, BOOL_TEXT_YES, BOOL_TEXT_NO) +End Property + +Public Property Let ActualizePeriod(newVal$) + data_.Cells(row_, S_C_ACTUALIZE_PERIOD) = newVal +End Property + +Public Property Let AgeRestriction(newVal$) + data_.Cells(row_, S_C_AGE_RESTRICTION) = newVal +End Property + +Public Property Let Priority(newVal$) + data_.Cells(row_, S_C_PRIORITY) = newVal +End Property + +Public Property Let ArticleType(newVal$) + data_.Cells(row_, S_C_ARTICLE_TYPE) = newVal +End Property + +Public Property Let DateExchange(newVal$) + data_.Cells(row_, S_C_DATE_EXCHANGE) = newVal +End Property + +Public Property Let DateEES1(newVal$) + data_.Cells(row_, S_C_DATE_EES1) = newVal +End Property + +Public Property Let DateTools(newVal$) + data_.Cells(row_, S_C_DATE_EX_TOOLS) = newVal +End Property + +Public Property Let DateEES2(newVal$) + data_.Cells(row_, S_C_DATE_EES2) = newVal +End Property + +Public Property Let Expert(newVal$) + data_.Cells(row_, S_C_EXPERT) = newVal +End Property + +Public Property Let Contract(newVal$) + data_.Cells(row_, S_C_CONTRACT) = newVal +End Property + +Public Property Let ContentNameDB(newVal$) + data_.Cells(row_, S_C_CONTENT_NAME_DB) = newVal +End Property + +Public Property Let TaskName(newVal$) + data_.Cells(row_, S_C_TASK_NAME) = newVal + If data_.Cells(row_, S_C_IS_IMMUTABLE) = "" Then + If newVal Like " *" Then + data_.Cells(row_, S_C_IS_IMMUTABLE) = BOOL_TEXT_YES + Else + data_.Cells(row_, S_C_IS_IMMUTABLE) = BOOL_TEXT_NO + End If + End If +End Property + +Public Property Let ParentID(newVal$) + data_.Cells(row_, S_C_PARENT_ID) = newVal +End Property + +' ======= +Private Function GetLastRow() As Long + GetLastRow = data_.Cells(data_.Rows.Count, 1).End(xlUp).Row +End Function + +Private Function ColorCell(nColumn&, nColor&) + data_.Cells(row_, nColumn).Interior.Color = nColor +End Function + +Private Function ValidateValue(nColumn&, portalValue$) + Dim sValue$: sValue = data_.Cells(row_, nColumn) + If sValue = portalValue Then _ + Exit Function + + If sValue = "" Then + data_.Cells(row_, nColumn) = portalValue + Exit Function + End If + + If portalValue = "" Then + Call ColorCell(nColumn, RGB(142, 169, 219)) + Else + Call ColorCell(nColumn, RGB(255, 151, 151)) + End If +End Function + +Private Function ValidateListValue(nColumn&, portalValue$) + Dim sValue$: sValue = data_.Cells(row_, nColumn) + If CheckListsEqual(sValue, portalValue) Then _ + Exit Function + + If sValue = "" Then + data_.Cells(row_, nColumn) = portalValue + Exit Function + End If + + If portalValue = "" Then + Call ColorCell(nColumn, RGB(142, 169, 219)) + Else + Call ColorCell(nColumn, RGB(255, 151, 151)) + End If +End Function diff --git a/src/Main.bas b/src/Main.bas new file mode 100644 index 0000000..b366fc5 --- /dev/null +++ b/src/Main.bas @@ -0,0 +1,152 @@ +Attribute VB_Name = "Main" +Option Explicit + +Public Sub RunImportCSV() + Dim sFile$: sFile = UserInteraction.PromptFileFilter(ThisWorkbook.Path, _ + sDescription:=" CSV", _ + sFilter:="*.csv") + If sFile = vbNullString Then _ + Exit Sub + If Not ProcessCSV(sFile) Then _ + Exit Sub + Call UserInteraction.ShowMessage(IM_IMPORT_SUCCESS) +End Sub + +Public Sub RunImportDB() + Dim sFile$: sFile = UserInteraction.PromptFileFilter(ThisWorkbook.Path, _ + sDescription:=" Excel", _ + sFilter:="*.xlsx;*.xls;*.xlsm") + If sFile = vbNullString Then _ + Exit Sub + + Dim xlInput As New API_XLWrapper: Call xlInput.SetApplication(ThisWorkbook.Application) + If xlInput.OpenDocument(sFile, bReadOnly:=True) Is Nothing Then + Call UserInteraction.ShowMessage(EM_FILE_CANNOT_OPEN, sFile) + Exit Sub + End If + + Call xlInput.PauseUI + + Dim iInput As New DB_Content: Call iInput.Init(xlInput.Document.Sheets(SHEET_CONTENT), xlInput.Document.Worksheets(SHEET_ATTRIBUTES)) + Call ImportDataFromDB(iInput, AccessContent) + + Call xlInput.ResumeUI + Call xlInput.ReleaseDocument + Call UserInteraction.ShowMessage(IM_IMPORT_SUCCESS) +End Sub + +Public Sub RunEditConfig() + Call ThisWorkbook.Worksheets(SHEET_CONFIG).Activate +End Sub + +Public Sub RunUpdateTasks() + Dim iConfig As InfoConfig: Set iConfig = AccessConfig + Call iConfig.SetScanTasks(True) + Call iConfig.SetScanContent(False) + Call ExecuteUpdateRequest(iConfig) +End Sub + +Public Sub RunUpdateContent() + Dim iConfig As InfoConfig: Set iConfig = AccessConfig + Call iConfig.SetScanTasks(False) + Call iConfig.SetScanContent(True) + Call ExecuteUpdateRequest(iConfig) +End Sub + +Public Sub RunUpdatePortal() + Dim iConfig As InfoConfig: Set iConfig = AccessConfig + Call iConfig.SetScanTasks(True) + Call iConfig.SetScanContent(True) + Call ExecuteUpdateRequest(iConfig) +End Sub + +Public Sub RunClearData() + Call ClearData + Call UserInteraction.ShowMessage(IM_DATA_DELETED) +End Sub + +Public Sub RunUnstuck() + Dim uiWrap As New API_XLWrapper: Call uiWrap.SetDocument(ThisWorkbook) + Call uiWrap.ResumeUI +End Sub + +Public Sub RunInputMarks() + Dim iTarget As Excel.Range: Set iTarget = Excel.Selection.Cells(1, 1) + Call CSE_ListSelector.Init(ThisWorkbook.Worksheets(SHEET_OPTIONS).ListObjects(TABLE_MARKERS)) + Call CSE_ListSelector.Show + If CSE_ListSelector.isCanceled_ Then _ + Exit Sub + + iTarget = CSE_ListSelector.GetSelectedStr + Call Unload(CSE_ListSelector) +End Sub + +Public Sub RunInputTags() +Dim iTarget As Excel.Range: Set iTarget = Excel.Selection.Cells(1, 1) + Call CSE_ListSelector.Init(ThisWorkbook.Worksheets(SHEET_OPTIONS).ListObjects(TABLE_TAGS)) + Call CSE_ListSelector.Show + If CSE_ListSelector.isCanceled_ Then _ + Exit Sub + + iTarget = CSE_ListSelector.GetSelectedStr + Call Unload(CSE_ListSelector) +End Sub + +' ======= +Private Function ProcessCSV(sFile$) As Boolean + ProcessCSV = False + + Dim dataIn As Excel.Worksheet: Set dataIn = ThisWorkbook.Worksheets.Add + With dataIn.QueryTables.Add(Connection:="TEXT;" & sFile, Destination:=dataIn.Cells(1, 1)) + .TextFileParseType = xlDelimited + .TextFileCommaDelimiter = True + .TextFilePlatform = 65001 ' UTF-8 + .Refresh + End With + + Dim sID$: sID = dataIn.Cells(1, 1) + If sID <> "" Then + If VBA.Left(sID, 1) Like "[0-9a-f]" Then + Dim iContent As New IteratorCSVContent: Call iContent.Init(dataIn) + Call ImportContentFromCSV(iContent, AccessContent) + Else + Dim iTasks As New IteratorCSVTasks: Call iTasks.Init(dataIn, AccessWorkers) + Call ImportTasksFromCSV(iTasks, AccessContent) + End If + End If + + + Dim bAlerts As Boolean: bAlerts = Excel.Application.DisplayAlerts + Excel.Application.DisplayAlerts = False + Call dataIn.QueryTables(1).Delete + Call dataIn.Delete + Excel.Application.DisplayAlerts = bAlerts + + ProcessCSV = True +End Function + +Private Function ExecuteUpdateRequest(iConfig As InfoConfig) + Call iConfig.CreateConfigFile + + Dim bScanPortal As Boolean: bScanPortal = PortalUpdate(iConfig) + ' Call iConfig.DeleteConfigFile + If Not bScanPortal Then _ + Exit Function + + Dim bProcessTasks As Boolean: bProcessTasks = True + If iConfig.ScanTasks Then + Dim sFile$: sFile = iConfig.OutputFileTasks + bProcessTasks = ProcessCSV(sFile) + ' Call Kill(sFile) + End If + + Dim bProcessContent As Boolean: bProcessContent = True + If iConfig.ScanContent Then + sFile = iConfig.OutputFileContent + bProcessContent = ProcessCSV(sFile) + ' Call Kill(sFile) + End If + + If bProcessTasks And bProcessContent Then _ + Call UserInteraction.ShowMessage(IM_IMPORT_SUCCESS) +End Function diff --git a/src/MainImpl.bas b/src/MainImpl.bas new file mode 100644 index 0000000..c5af364 --- /dev/null +++ b/src/MainImpl.bas @@ -0,0 +1,143 @@ +Attribute VB_Name = "MainImpl" +Option Private Module +Option Explicit + +Public Function AccessContent() As DB_Content + Static s_Content As DB_Content + + If s_Content Is Nothing Then + Set s_Content = New DB_Content + Call s_Content.Init(ThisWorkbook.Worksheets(SHEET_CONTENT), ThisWorkbook.Worksheets(SHEET_ATTRIBUTES)) + End If + + Set AccessContent = s_Content +End Function + +Public Function AccessWorkers() As DB_Workers + Static s_Workerks As DB_Workers + + If s_Workerks Is Nothing Then + Set s_Workerks = New DB_Workers + Call s_Workerks.Init(ThisWorkbook.Worksheets(SHEET_OPTIONS).ListObjects(TABLE_WORKERS)) + End If + + Set AccessWorkers = s_Workerks +End Function + +Public Function AccessConfig() As InfoConfig + Set AccessConfig = New InfoConfig + Call AccessConfig.Init(ThisWorkbook.Worksheets(SHEET_CONFIG), AccessContent) +End Function + +Public Function ClearData() + Call XLShowAllData(ThisWorkbook.Sheets(SHEET_CONTENT)) + Call ThisWorkbook.Sheets(SHEET_CONTENT).UsedRange.Offset(1, 0).Rows.EntireRow.Delete + + Call XLShowAllData(ThisWorkbook.Sheets(SHEET_ATTRIBUTES)) + Call ThisWorkbook.Sheets(SHEET_ATTRIBUTES).UsedRange.Offset(1, 0).Rows.EntireRow.Delete +End Function + +Public Function ImportTasksFromCSV(iInput As IteratorCSVTasks, iOutput As DB_Content) + Call CSE_ProgressBar.Init(" CSV", maxVal:=iInput.CountRows) + Call CSE_ProgressBar.ShowModeless + + Call iOutput.EnsureDataVisible + Call iOutput.ImportCSVTasks(iInput) + + Call Unload(CSE_ProgressBar) +End Function + +Public Function ImportContentFromCSV(iInput As IteratorCSVContent, iOutput As DB_Content) + Call CSE_ProgressBar.Init(" CSV", maxVal:=iInput.CountRows) + Call CSE_ProgressBar.ShowModeless + + Call iOutput.EnsureDataVisible + Call iOutput.ImportCSVContent(iInput) + + Call Unload(CSE_ProgressBar) +End Function + +Public Function ImportDataFromDB(iInput As DB_Content, iOutput As DB_Content) + Call CSE_ProgressBar.Init(" ", maxVal:=iInput.Count) + Call CSE_ProgressBar.ShowModeless + + Call iInput.EnsureDataVisible + Call iOutput.EnsureDataVisible + Call iOutput.ImportDB(iInput) + + Call Unload(CSE_ProgressBar) +End Function + +Public Function PortalUpdate(iConfig As InfoConfig) As Boolean + PortalUpdate = False + Dim sExec$: sExec = EXPORTER_EXECUTABLE & " " & EXPORTER_CONFIG_FILE + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(ThisWorkbook.Path & "\" & EXPORTER_EXECUTABLE) Then + Call UserInteraction.ShowMessage(EM_MISSING_EXEC, EXPORTER_EXECUTABLE) + Exit Function + End If + + Dim iShell As New WshShell + iShell.CurrentDirectory = ThisWorkbook.Path + On Error GoTo REPORT_EXEC + If iShell.Run(sExec, WaitOnReturn:=True) <> 0 Then +REPORT_EXEC: + Call UserInteraction.ShowMessage(EM_CANNOT_EXEC, sExec) + Exit Function + End If + On Error GoTo 0 + + Dim sOutput1$: sOutput1 = iConfig.OutputFileTasks + If Not fso.FileExists(sOutput1) Then + Call UserInteraction.ShowMessage(EM_MISSING_FILE, sExec) + Exit Function + End If + + Dim sOutput2$: sOutput2 = iConfig.OutputFileContent + If iConfig.ScanContent And Not fso.FileExists(sOutput2) Then + Call UserInteraction.ShowMessage(EM_MISSING_FILE, sExec) + Exit Function + End If + PortalUpdate = True +End Function + +Public Function CheckListsEqual(list1$, list2$) As Boolean + CheckListsEqual = False + Dim items1 As Variant: items1 = VBA.Split(list1, ";") + Dim items2 As Variant: items2 = VBA.Split(list2, ";") + If UBound(items1) <> UBound(items2) Then _ + Exit Function + + Dim it1 As Variant + Dim it2 As Variant + Dim flagExists As Boolean + For Each it1 In items1 + flagExists = False + + For Each it2 In items2 + If it1 = it2 Then + flagExists = True + Exit For + End If + Next it2 + + If Not flagExists Then _ + Exit Function + Next it1 + + For Each it2 In items2 + flagExists = False + + For Each it1 In items1 + If it1 = it2 Then + flagExists = True + Exit For + End If + Next it1 + + If Not flagExists Then _ + Exit Function + Next it2 + + CheckListsEqual = True +End Function diff --git a/src/z_UIMessages.bas b/src/z_UIMessages.bas new file mode 100644 index 0000000..f1e94a3 --- /dev/null +++ b/src/z_UIMessages.bas @@ -0,0 +1,61 @@ +Attribute VB_Name = "z_UIMessages" +' +Option Explicit + +Public Enum MsgCode + MSG_OK = 0 + + EM_FILE_CANNOT_OPEN + EM_CANNOT_EXEC + EM_MISSING_FILE + EM_ITEM_EXISTS + EM_MISSING_EXEC + + IM_IMPORT_SUCCESS + IM_DATA_DELETED + + ' QM_MERGE_WARNING +End Enum + +Private g_UI As API_UserInteraction + +Public Function UserInteraction() As API_UserInteraction + If g_UI Is Nothing Then _ + Set g_UI = New API_UserInteraction + Set UserInteraction = g_UI +End Function + +Public Function SetUserInteraction(newUI As API_UserInteraction) + Set g_UI = newUI +End Function + +Public Function UIShowMessage(theCode As MsgCode, ParamArray params() As Variant) + Dim unwrapped As Variant: unwrapped = params + unwrapped = FixForwardedParams(unwrapped) + + Select Case theCode + Case EM_FILE_CANNOT_OPEN: Call MsgBox(Fmt(" {1}", unwrapped), vbExclamation) + Case EM_CANNOT_EXEC: Call MsgBox(Fmt(" " & vbNewLine & """{1}""", unwrapped), vbExclamation) + Case EM_MISSING_FILE: Call MsgBox(Fmt(" {1}", unwrapped), vbExclamation) + Case EM_ITEM_EXISTS: Call MsgBox(Fmt(" : {1}", unwrapped), vbExclamation) + Case EM_MISSING_EXEC: Call MsgBox(Fmt(" Python: {1}", unwrapped), vbExclamation) + + Case IM_IMPORT_SUCCESS: Call MsgBox(" ", vbInformation) + Case IM_DATA_DELETED: Call MsgBox(" ", vbInformation) + + Case Else: Call MsgBox(" ", vbCritical) + End Select +End Function + +Public Function UIAskQuestion(theCode As MsgCode, ParamArray params() As Variant) As Boolean + Dim unwrapped As Variant: unwrapped = params + unwrapped = FixForwardedParams(unwrapped) + + Dim answer&: answer = vbNo + Select Case theCode + ' Case QM_DELETE_LAW: answer = MsgBox(Fmt("! {1} !" & vbNewLine & "?", unwrapped), vbYesNo + vbQuestion) + + Case Else: Call MsgBox(" ", vbCritical) + End Select + UIAskQuestion = answer = vbYes +End Function diff --git a/src/z_UIRibbon.bas b/src/z_UIRibbon.bas new file mode 100644 index 0000000..4c1a76f --- /dev/null +++ b/src/z_UIRibbon.bas @@ -0,0 +1,20 @@ +Attribute VB_Name = "z_UIRibbon" +Option Explicit + +Public Sub OnRibbonBtn(iControl As IRibbonControl) + Select Case iControl.ID + Case "ImportCSV": Call RunImportCSV + Case "ImportDB": Call RunImportDB + + Case "EditConfig": Call RunEditConfig + Case "UpdateTasks": Call RunUpdateTasks + Case "UpdateContent": Call RunUpdateContent + Case "UpdatePortal": Call RunUpdatePortal + + Case "InputMarks": Call RunInputMarks + Case "InputTags": Call RunInputTags + + Case "Unstuck": Call RunUnstuck + Case "ClearData": Call RunClearData + End Select +End Sub diff --git a/ui/.rels b/ui/.rels new file mode 100644 index 0000000..3107a8e --- /dev/null +++ b/ui/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ui/customUI.xml b/ui/customUI.xml new file mode 100644 index 0000000..12ec4bc --- /dev/null +++ b/ui/customUI.xml @@ -0,0 +1,66 @@ + + + + + + +