From b3f888b492ecbaab98a0de605f7903b9d633a7f8 Mon Sep 17 00:00:00 2001 From: Maria McLaughlin Date: Tue, 2 Aug 2016 16:00:23 -0700 Subject: [PATCH] initial commit --- .gitignore | 4 + .vscode/tasks.json | 18 ++ configs/dev.json | 5 + configs/release.json | 3 + details.md | 3 + gruntfile.js | 84 ++++++++++ htmlTest.html | 24 +++ img/logo.JPG | Bin 0 -> 28137 bytes index.html | 27 +++ karma.conf.js | 58 +++++++ mariamclaughlin.color-control-dev-0.1.0.vsix | Bin 0 -> 86115 bytes package.json | 29 ++++ readme.md | 50 ++++++ scripts/IOption.ts | 5 + scripts/InputParser.tests.ts | 166 +++++++++++++++++++ scripts/InputParser.ts | 129 ++++++++++++++ scripts/app.ts | 21 +++ scripts/colors.tests.ts | 54 ++++++ scripts/colors.ts | 46 +++++ scripts/control.tests.ts | 3 + scripts/control.ts | 87 ++++++++++ scripts/errorView.ts | 37 +++++ scripts/model.tests.ts | 96 +++++++++++ scripts/model.ts | 79 +++++++++ scripts/view.ts | 156 +++++++++++++++++ styles/style.css | 78 +++++++++ test-main.js | 27 +++ test/scripts/ErrorContainer.js | 35 ++++ test/scripts/IOption.js | 3 + test/scripts/InputParser.js | 63 +++++++ test/scripts/InputParser.test.js | 60 +++++++ test/scripts/InputParser.tests.js | 146 ++++++++++++++++ test/scripts/app.js | 0 test/scripts/colors.js | 35 ++++ test/scripts/colors.tests.js | 38 +++++ test/scripts/control.js | 36 ++++ test/scripts/control.tests.js | 3 + test/scripts/errorView.js | 23 +++ test/scripts/model.js | 46 +++++ test/scripts/model.tests.js | 71 ++++++++ test/scripts/view.js | 113 +++++++++++++ tsconfig.json | 9 + typings.json | 11 ++ vss-extension.json | 114 +++++++++++++ 44 files changed, 2095 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/tasks.json create mode 100644 configs/dev.json create mode 100644 configs/release.json create mode 100644 details.md create mode 100644 gruntfile.js create mode 100644 htmlTest.html create mode 100644 img/logo.JPG create mode 100644 index.html create mode 100644 karma.conf.js create mode 100644 mariamclaughlin.color-control-dev-0.1.0.vsix create mode 100644 package.json create mode 100644 readme.md create mode 100644 scripts/IOption.ts create mode 100644 scripts/InputParser.tests.ts create mode 100644 scripts/InputParser.ts create mode 100644 scripts/app.ts create mode 100644 scripts/colors.tests.ts create mode 100644 scripts/colors.ts create mode 100644 scripts/control.tests.ts create mode 100644 scripts/control.ts create mode 100644 scripts/errorView.ts create mode 100644 scripts/model.tests.ts create mode 100644 scripts/model.ts create mode 100644 scripts/view.ts create mode 100644 styles/style.css create mode 100644 test-main.js create mode 100644 test/scripts/ErrorContainer.js create mode 100644 test/scripts/IOption.js create mode 100644 test/scripts/InputParser.js create mode 100644 test/scripts/InputParser.test.js create mode 100644 test/scripts/InputParser.tests.js create mode 100644 test/scripts/app.js create mode 100644 test/scripts/colors.js create mode 100644 test/scripts/colors.tests.js create mode 100644 test/scripts/control.js create mode 100644 test/scripts/control.tests.js create mode 100644 test/scripts/errorView.js create mode 100644 test/scripts/model.js create mode 100644 test/scripts/model.tests.js create mode 100644 test/scripts/view.js create mode 100644 tsconfig.json create mode 100644 typings.json create mode 100644 vss-extension.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ffa8f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +scripts/*.js +typings +dist \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..88dddcb --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,18 @@ +// A task runner configuration. +{ + "version": "0.1.0", + "command": "grunt", + "isShellCommand": true, + "tasks": [ + { + "taskName": "build", + "isBuildCommand": true, + "problemMatcher": "$msCompile" + }, + { + "taskName": "publish", + "isBuildCommand": false, + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/configs/dev.json b/configs/dev.json new file mode 100644 index 0000000..b6765a0 --- /dev/null +++ b/configs/dev.json @@ -0,0 +1,5 @@ +{ + "id": "color-control-dev", + "name": "Color Form Control (dev)", + "public": false +} \ No newline at end of file diff --git a/configs/release.json b/configs/release.json new file mode 100644 index 0000000..95e22b6 --- /dev/null +++ b/configs/release.json @@ -0,0 +1,3 @@ +{ + "public": true +} \ No newline at end of file diff --git a/details.md b/details.md new file mode 100644 index 0000000..1f290ce --- /dev/null +++ b/details.md @@ -0,0 +1,3 @@ +# vsts-extension-ts-seed-simple # + +Describe your extension here. This description will be shown in the marketplace. You can use *Markdown*. \ No newline at end of file diff --git a/gruntfile.js b/gruntfile.js new file mode 100644 index 0000000..36a05dc --- /dev/null +++ b/gruntfile.js @@ -0,0 +1,84 @@ +module.exports = function (grunt) { + grunt.initConfig({ + ts: { + build: { + tsconfig: true, + "outDir": "./dist/scripts" + }, + buildTest: { + tsconfig: true, + "outDir": "./test/scripts", + src: ["./scripts/**/*.tests.ts"] + }, + options: { + fast: 'never' + } + }, + exec: { + package_dev: { + command: "tfx extension create --root dist --manifest-globs vss-extension.json --overrides-file configs/dev.json", + stdout: true, + stderr: true + }, + package_release: { + command: "tfx extension create --root dist --manifest-globs vss-extension.json --overrides-file configs/release.json", + stdout: true, + stderr: true + }, + publish_dev: { + command: "tfx extension publish --service-url https://marketplace.visualstudio.com --root dist --manifest-globs vss-extension.json --overrides-file configs/dev.json", + stdout: true, + stderr: true + }, + publish_release: { + command: "tfx extension publish --service-url https://marketplace.visualstudio.com --root dist --manifest-globs vss-extension.json --overrides-file configs/release.json", + stdout: true, + stderr: true + } + }, + copy: { + scripts: { + files: [{ + expand: true, + flatten: true, + src: ["node_modules/vss-web-extension-sdk/lib/VSS.SDK.min.js"], + dest: "dist/scripts", + filter: "isFile" + }, + { + expand: true, + flatten: false, + src: ["styles/**", "img/**", "*.html", "vss-extension.json", "*.md"], + dest: "dist" + }] + } + }, + + clean: ["scripts/**/*.js", "*.vsix", "dist", "test"], + + karma: { + unit: { + configFile: 'karma.conf.js', + singleRun: true, + browsers: ["PhantomJS"] + } + } + }); + + grunt.loadNpmTasks("grunt-ts"); + grunt.loadNpmTasks("grunt-exec"); + grunt.loadNpmTasks("grunt-contrib-copy"); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-karma'); + + grunt.registerTask("build", ["ts:build", "copy:scripts"]); + + grunt.registerTask("test", ["ts:buildTest", "karma:unit"]); + + grunt.registerTask("package-dev", ["build", "exec:package_dev"]); + grunt.registerTask("package-release", ["build", "exec:package_release"]); + grunt.registerTask("publish-dev", ["package-dev", "exec:publish_dev"]); + grunt.registerTask("publish-release", ["package-release", "exec:publish_release"]); + + grunt.registerTask("default", ["package-dev"]); +}; \ No newline at end of file diff --git a/htmlTest.html b/htmlTest.html new file mode 100644 index 0000000..1a03317 --- /dev/null +++ b/htmlTest.html @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/img/logo.JPG b/img/logo.JPG new file mode 100644 index 0000000000000000000000000000000000000000..789a7e2ff1e803326f745f9d68b0ddbbc174b1f6 GIT binary patch literal 28137 zcmeHv2Urwax^0n@h$uNV5(Gg+G9u6zNOFiO8CsGg$*GBr7*He#2#6?+AOez!a07T&z%SZwsh^*p7Wip_pBDIOfu9!mX@UPAEuiOc)5pV||Ejl# zyPb!R2mkq-_BK8MK=p{?S6smY;)kL~lz&Y(CdB`mrZ{^B01^`Zmi`;Ypw0Yd$DbJE z|Ht0+v(7&)@Y4c6E%4ITV30e~Lh0Nez8 z01v<&;0LY(-oK{o!0&Iz_<{4_S$n_+{Pm3uVGwol_4QPelyvtwZDa3Y>u}o6!%Y%y z<0&b1`ivx?4265z*tt6R^4mH%I=icgY}BAc_?_)lL`>v#&**wybZ~Ok3i5Wi5~O$4 zF38pHti1?Sm62Wo=ix0jQ!wUM!xkdm^uk+89sk&>{Nk+D~hlTwtv zA!RG@Tk-aGe=Xh9&->=DW!u|HI^1+{b8z?d0j(h=DIod%q5oB7f+qTFp?_$8u#R9J zrHkGUHogv*z)kgAlS`aYkT@fM^)KDvwBlI>Wy!zGr!4u+7(YDdzxK)hQ`S;dwzpHV z`}XV}-rtMA;^6h?TKlH6>K}sb@6`p*eouhqfSr(zj!s$f$4B@_oBVnN?1SKSf9Bu2w4puHs4pt6!Za$IY+`PiP>>L881%yRU zik}ka;+K?>6q6Pa6Bqk-6EYfFT6#Kq76t|uF&+*cv48qQdJizulI>8?Q;-P&O~95*m_6M(X+90aB>Ns5D`5oCMzeepm*wx_#&Fy~z6yq7o7xCncvmNqw4` z^)mZaPHx`olG3vBipqCY)r}vTnp;{wwtedBANV{tG(0joGdnl`Wnpn?c?Gk%wY{^u zhuz10s}~tS@x5Aqt=Zqziy5pJIVB|pCC#^bk&y?08wE2Z)p056qvs82Y`l&MNZ+Dm zxe)*2Z3CU4j1ijE*1MOUO-Ob|81t=azt-$OSFwoyQqBHavA@-86gWgd1~wiAGXMo} z8d0Jd`2<+?I&B#q< zcmT0Fj&R7tRgLFMWbC&1@+E|mYk*z2h;9Z^Ij&>q60HN37Jqcaxk0_+nHWja3iv`r z>{+e-8!>=`Y87wTy*GHOP#>1cQPwTCgyy(p?JjCh&3?t!!SHiRqM70Jt!d3%H~EfR zacMlVlDgK_{rIRfb?7EkY4|nMi{%oR_PjQy82G!Um+2pXxmTe}*9#v17xE^ z5X7vTrtxQp4*Ho>i{#EwtaSc%Hze-tnz%*t-g*w)c-Lv{W^{zC>Y5k+fgI$vZ&Sar zlxw+K!VG1fg14!C*hTVinW4s~+Yjpztm6ea34#~P5OW{)>5FhV_d`Tv9W5OO$_CwD z&Cqm1hDbp1&|r;A#BQJX0cYW9c3+?4#t?HLw64j-zjxe1G2tYO^qHmeMUG9?Ulvkl z-2^7N;~5Lq5735M;_n<2q!qVw>v<=%eADVwZFS7_uJUZtR4#CD6>Qv>?+l#f<@-#> z2>sf?w??y*!GLKi#ujN*y{T2wd%|(w^o0Fmrdbjo6efpyF+8=r>wPhAihFTOJJ#ro z|MlyTR@FKeyp(V)-664~iUVSzYruOJ9PkAXDS)}81eL(L7Yb`<1h`rKqboBwmy`y$ z2ys#75*JMUEd1VVPTV;mRny+^Y0=R#cmT>o0>Iv34c0XtKUp2)N>t;DBm`@dfQ+ZW zY>15AY+mg-*Aq$k78awtL`!`R;V_?cf4+Sc*DGd;P1TxrItQLcX$Mk{Wy5DL6mrgS zh$%rX*G=p1A1#2c=MbY^#%n5KH@jS&@7bl&EA&JgP?|ezx&&F3&zp|P+Ppc(H-|pU z>hf*_7}vg;g5&Ka>_j)^sti2i3AT46QdvFVC)a+@tDSKe*38w6H*ZOM>C_*npdeYT zIn3?i`<3ZH6J3;X2%zh(+OUM(y}BB9asYpx|AerIz?fiVF<~q2U}?zt-*SNe{lG3} zLwv-{OyO3b+fr7A0E?kI;7tPlI;0Bg*uj=T5P~(qmWgV@-X#G7cThW@q0ilxP@6zM z30NJ45-1I*MrU(KKti1x*ru%{K+R>{H3~_*yi-XCh7ibd$@iK>&0(~k+WSr#jx9e! zn2+>2n3ZwU3G(M#v}iylSWmxjV9U$4j; zcE>?PB~*n8RSpEmUa?C)YkTz4%96&#lOLfmK71!IznmGXc%JpHX24xD`61oJm&NFW za|Z@g&`6XrZlIl5q!V9;+ib-}DVvc1!PZE`YAy-rY4(8IX%HKGY|CzPK9K(9{B#uw z1b}#PnjaP?0~%L#76TeqGzlO=cQ$bQkj~ZILDW21E@G$*iK8omfe(pCtWNE0Leqv! z@*4NJ)aRw5Yac0z?g(qKkj4MdEtgyqv4IGdNp2lMfd}i)KWMF>rXxoz-z%i_R!eP zOq_DUF&3tuSxI}j=hH?4m>SELz}52GiurH0owQooRTvr^N-+pb9DUY!Fzj}=Tg?-} zRin-V7^<_7nHcuF$yvy_BG}Uyj$HmK{&D#u{{Z?TZ(XftV~t(R1%X9H;5vOM3vlg3 z@0x|Fm0?EbY*BMcu;&@gU*=HRR$z^D@|@+BYWa+_ZYJg~=~IQ>@fQ2SLe_?i z8sTiN-nsVtc=Q{ZswJ9PPjQ+VV#=;0Q(6@Zokv=SB1dB^3ql4vcq2a#!sBA#%%$dI z!~Nj2JN@(#6&dWDihf_syDTjmtxSX5DAud~6d4&bphdq~_1|i{s;iXGe7jl4@FKAU zRtP|x^)<5APcD6yU?F24z0#X|a5+d$M;%b#eZ1|W+Ls<<)T_oEnytbWSC*4?wakDL z_Gd46&5*mcVMiC}%G*fssHBC1Vq4BT3dnPpkPzHlm*}G;u4%D^U3m51{v7}PHT}Q5 z1Yp2(HHqN-nY9vEx|8PwWJZbp!@JcKECAMlQ;yNlROYFI=Qml&)IJ>aP;%F}b$_;l#rN zJiDF!sGkCkg2odN_19wrSv+rNCM|E{9=$ zSm_DJtm}C_qxzL)q0y1$o;4CM;|`x9-bU=7q#*&!ns|wBZg7$H0Kqgr378d+&U_d4 z)?yzy*_}^pYey|MLD3u}Abx}dG>&6X1ZonnX~aYnh{IZ>D}Z=oig@`v31~9mREL-r z;=4C&2#G0(?)!*oDR2cZB>{J<<`6hW5-|S~f>(E1hH&8x9eiW*YPlYB?)T1`4#(Xv z0HC*u&t*PsGS&mH7YLNMFgktuj;f&!#vEzwq%@Ipzf8yNik@4KAvI#r}(7^<`8E#CdX zVf;tu&G-az4#Kojz1|X&v+}4r*J82b?s;CN>G*rCA;+SCv0pn|e5#?&S^C$~+KPa& z{M}gsO;f+6`U@9BtG1h7Vz~~2VkBa`^SCVVmn z=Mf!sB)|iDaJlwIUD|F02}lhlP9k=b;GH3Z=LoYf$u>v@x)T3ji3E&I;jTtRi5AH$ zlmFI*nGjgeUl5R3B-}xg0CjciG;0Smgm}rK`+yEgjEbt0ug_w@)8Zg^7QrqgTfCy_ z4HakSMci~-i%H4F|Hoh6M*4b8ycg5G!X}`M7 zNBB*!zjUj9zs#PQ4Zx=tu!z1 zNKtTeueVQ-pVbOO>boa>m79T*q2|3nA8s{{I8f~WyMaoruWUTHsqZDw%A#d85lf!S zDTMsRBytsTR9SznF0c$5>7_&UMu_|IzP%rD-;P)qdX5M$+=x>>o~VZ|FG_Sf(K_%_ z)t%{tY=a<17$Il{Y22W1n2DntCF5XE=`Cy?c;0^IBP{cFbkO`>#$jq`cu+-Vx_`}V z(t-B;`rVvGR7Xe7cFqX?IL7gwT-v!pChG&;G80;d*t2}pPJ4_~r!U7xW1jAgg45=k z5RW2DtZPG1Y_V;;wwFuP$5L{GF9Y6WG%t3F!6AmC~BLR%G;(<1;Fq-fupf=cx#Dr=z%~hxPNC z8yxVXRSt<|wbQ7y<2moG{gDwuQ^~5|IK^BZ>M{E&e8On(m-^hgWXg`k^}U!R7q)Zs)yQ8QEWxgpnKWx@ECfA2rlFTvz1#{AG#}uRgm@KV<-;b$akx zsNf=X?xUje^HzSJR=uS1>E|YvgUX7P)*}5w`Y7p=Cu}F8-FOdkB;tl7(D}3S z#dB#B8ocI{pIFOdI2lWn5=mO}v5zwk~g zJIN*{iS-G&b{v&gdQS9EHn!ACl@ndK`zDzLIJ;h@vYn{(I>q9H?V)aFojbBJ#i^`; za_uiX-sd*;*d$8!<9btC8m3wOWr!ih`i5z<5Zs(3G1eT|bNLrK83Gj~!1&v|<3=nX zxS0Qny1yasA2%^k#GuYjp3ASh3_7PDPGgXx3`B>GgFq56dTmZ_ZK{kN-%bK{w@5%) zev9kE+o_GC`(avy%Dbp}eNEyd3FvzXPQG49t-nme!6e{4VxQRqx+X*daAIQHqg#^U z;4~yKi5Ld?jP0w}mdkfIVAGmCco8VL+GQgQGNZ&_VwmW#;Mye^iy{G*h~4bqa=89` zk9{6L@FpNLQZ7D``whHM#8hF+r_;{mxfRJ3CSF+QdAO?`%fZ9^D7|b|GHQ}xz>gtV zT*a~DT~(6J1J(93<7=MOm#=#Vf8MX9n9+GK{}jPAtSuC6HSvP-t%%T53lDO*dDn_~ zniX}+$r&#pZy|n`>)y0`lm;~Czd5-PVLi>L8UEgeuBnoG6`2|m@Yxze7d_TGZ}(|b z-tLuDb0Dl0#iWx9ztL@45nbb4BHdo*Eg0g1l?K_`^XUBM9yqe0qE|L+eV4g_T`IBd zeIu&p*8W2HxgfC76-mHD$fk~5F>V2glX6YQl4X7Hn6a2J%{Wm1+;(5}EOQ1=m_6}A zHS))7BGZoeL{o4|{R&%Fx4mM!Wal$TPFd0N_zy~+xsd& zunJs%ZG!|H*YzR3YbF6NxNz{ahX=3z>?v!-E%4Ct5p8sV(4rLs#H-3xYlrzS!D*#M)wPxtj4=A&bea zm+u-{pOBGVMgB66%*Z(0Z-~vG?v&Pyb@M)He`mF}hG(zc?2Bm~s()eC@16v~1`+Eu@C#MhXwk}#DFf0Aq z{enGfWi{t4w4uR*tseBPoA``l;pcA0y~tp4?X^Ox^-pw}71{}?ZX-_s9`SKX2RTw;$m>1p-qiHFKOHluNFKFUboz4PWtiWtFl)p8292_+ z#8TDL6SFQO4iFnrpVn6uuIQkUxag(gQqdyV8ft?GLF)$)f=ECv0uLgWa#fkk`i{)F z?`JOl?nHjI-oxD0lsxB`Bkg`NJZ>>?zWHs6@u8vZD`vL!C3xs`yz1G+hx0ES7sZJ@ z`CDhZ$>El8%|?Hm>zUw)Un&nNadLCEz5%J6Y9-NPb18$Us1XQqsQ3!A@wM0IKIyH2 z6Jpk}w)&%R@g_bF%!y*ZBGb@h?k5#ZlfEABHoDbqHTlX{Wq&V!Fppa_2e=8>b-13A z&ePQ3JoNJVZEL|Iyby-{?oMez$hjJSHJwx9qGq;`iT!teyUGV#dt#y#A7-()F}9tQ zGFaWB=Ru!vmh;GF4#|13&oO(=-A7m1kS=9zCmP(D7wIDa0|miTI_c79#7A;74tvb9 zB?cG4X|DGBz6&|1>b>N(ac%r!p7LA3lPRbhdnLmqj0>&gjGL$&DZ1MfYq!OURc!c_ zAYKO2IGY|cVAZ%xT67()P;WNN#!}s%p2RPWc?JeZe&TrZaffwP)_^kLh;V1wo69mh zxrULsg4DGXTKypb==>&=-eo1B(ek7Qh9?<;M%O6UJra{{9vd1sFzN9%pk})5r|W2n z#!bTGbh+H-wBB2+I~=FEa9(J!&=W}=!aB=DH?>mYx(l(_xu0bk_niAH|1-j%;_77G zjVPcI6L)8=)?sGEw`Su}Vm)Vg+r9I>kt?)c>57H>TAMcRrIoE(oh*MYXZmDN@u@GT ziit-D>VgVLaqCc3w_r&$k+l-z^IpMP zy06gPH>NPxGherWSWH+|df6e{4S(bWvG}&8fQ;LD@WgIfYDTDs$FGnq$ zDh%{$9HJ&={kl9agln=cKpF60-4A0|%?9Msm`csZn^sxU!S0lL@i6NHp2It!#s5_< z$e$nQRIH2>Tc49}yv`Nl?1Gap>(^v3 zE~#aUG*kD6pp#*UZgIrH7gS3eR!0)Gn1mv1PR&_+;CfR65MI56=dkt-C7k#p5^$&F zfEZ{&0;VL0vWP~rP!gbANCGxMjKu{VjIOCUxP736N}2TAHaSiL=p6R&o0TM>-U9F9 zieT$ph%0a4)LT%FfXZAue4;&DsOsrH-Iw7z#ddj_sfXIU$aNUpH94t~Fip;r!lG+k z)KC3w)LE_^`8Oc!$i7o9F5gLtoUkEqdH+z0(_YqIgb;4gnZ7TeK_5#DUJG;iKR z2P;;Y8WEdxDa0Bify&hzW#48I=*PXQs?UxO47$r2Ij0f^W58ZGFsZ0uzhZHRxk>wj zOm)U`w{h3Gp(kT-$Jy4Xl=abM)QS5EA?-lw3kTkNA6p@4L8SRk?3K~nV#>Oqu;U@( zpWR|nL4MDr?PB`0?9Eiv{JE3fuC@-=eYiZZUjF>VkkK3O-95txc0$*uW9J6LC3=5k zRV@+Cya9&j#qh6wyj*i@LtgD%1!=5k1^|9t^Rga5LpHHhHmg|bzJiP+jS(YS* zyMj7`qX!liGeX|Y=wfxVt#>b@ zWeEM^Nd($rxpzvfEhK=g<#?11gmJivFn9QN)wVSASZ3VNM4^Pu+md^IeNsF}G~Tw{ z&dlYY*vz2I{njW2&LMU)PaNQ{Fl>oD_nWQwaQs(eGh)WMOqF3XHEH*!D=>$XWc%J| z&BgA@JKA~zWF^`vxfHe_9m530f^?lw{T8tX1amKOaP1UNYubl5K@aoQ7JZE{+iA!_%>Z|^Qx}YuGP%d-)?T^LJ z@R}Ls*x=ppzQXNoW()n?QA;5gMcuw4wQWusiZ>Zdp+ksys{zz%AB+GD-3}4OBqOI$ zjIJpupSE;m(XAeC>{F>|Jd@hF^eH2%;4aXO6Zo19O0x-I|D>)}m}ZY$-(dt{scChz zmhZfJ_?eG~59&Q~8NdcIpl*4A(VyjwLG4CQ9*!N-J7|7Y`6LPEKQ6T+P=kz|I#Q?{ zkkiq|G$L8}Mx;`*=63JnJswE5-`9siHxoqdZSEO!QU;ma!pl~2L0UsibjlPfVP)^x z33o$4!L?N#2wwkz8vIAP^!EVtpE=KLfyjq~^!IOszz;m>U-(jWfRzQhDhy5>zwUY{ z%l=BGsslc%XGlN_6bm7?TYmNr6YPIkU!ob#mT*+d@bj(JxKHmIX@!_0ly1WwVx{BK5gmn zplxHtc@RRYeEgM*@z8Gam4e+;p6Xqww6{1p+>gW}CwkoDJAO2M1 zeU~0^BK$O71bhIqOt3^ykLZoD_Q4brFMC9yR-<9Kv-$n1oMz+0AqssG#f4ug-o+{~ zv3xWf+KUdPiEs9Vv6o4}u3#fyST2uRt&cOSb0nBbOnjz&oR~mO!_z~Pf!DnCHH>@p zZIg6kFOs4+%nnCCUk_NTOWxX$xJuk*ShxYZq254X`eOY;)^J=6-SJ{B{&IDCG;IUx z);13IfXE212qv89-Pm@GOY!gfxcuy{x9Vza>*~N!QHrI@Xh~ixO`ijGr?z9%@_YVL zs)s!%uir$>HCAZ0bedR4u7w>#7i_K^Wqc8x?$Q$K$>NCNH#v6t79s<0gI?pRYpWS3 zl9X^S8t0kSOcQb(Cd2J!FlFvc?X&wy;Dve% zt*#9>f!DchwpXXgzR~m=pJjtpoc-M7zc#fA2~%}2^5JQ^6v)yu!&O{rMH)b+%-bAA z@UJ1>s2i=dYpiA~_Bc>;c^cD_63iN%F#fnYIKcGDu@;VK&}gNc3H!93wZ&og|&7PLIo^gd^y8D*1R); zLf9BgpKch(bKh0AqY)oCu3Ei~%d@G0z%Gwe5Rh)sE2EVUP8ZEx*|V!;eio{uuvx4J zhSWsLq8OH4Z8Ap-j@+(__3l&Hdy@VtT<%{1691!n{7C=_WTu|VKjbi#L2y+m*Ou87 zxV6y<-@oS<(f%^)esXc9$&E)jC)b>N&0wQG2!H5DaKeWCH5CY34Kta9dF6awaQWjL zkY6)+hFae!WKAOEDD2tc!P7 z52`#^+^4UC)yzxO7TqNQk2hEFH(N2*rCuN;Wtd#?_eb7*cgv*xHu8CD#I05poJs1# z;u!O4?tHl0_Qx?{8>z;e<;d|5m}2|Px-6My*{P`dlj!360)6#@!(=>nZp_%xuP|}9 zOeGDerjvlnyD$9`|?h1Q$kWC*!n12al z#~asTlFNOvS09;;b$%e;9T?&v|ff(4(H=NwVqKXH$EG(pxp2fF=}o zyr++o^CNLQ}{)r=8P>1dxa3Z#TOVyQVO6#fTv-XyUgCUF+-H zmTo~RbilHsyCH1P^dzUlE6f z$dP4$NLONGrMP1awO>{D@|#0MSu*z4&;XO(9SL6F=SI8D_EAQ&qcd}d&RaSUXXU!~BQih577^iz z>+TlEG0&pCy@!?Os){X&^c%ro2wx=IYUNYo=N`cTe7 zg=GSx?vQ}sFGe)=j6hI13q^eV$3GvC_$T$j?$3xXDRTsIr7}O*z5^B<V=i&!Uco!KwF{@c<@Ve$*;MH!QT5Ku-k5lc@6m7YT4KJh}4$=4AYeAE)+ z&XySdrg*C#j0wKjUUbxq2ccKaM=|TACp!MK)2+-bX8T1+MB)i zB9xHN5`NJwz+JH7{_P~1bn0MM!xcT<&?AR6GzDtM=I%v01W$98o7tWZ%&EO`(rb&= zk^YJ*Qr>Ib97(&w9iuaooGLt)k|2SOv`e^Vc^7|=W=4&-@Y|%r`UOODWMvkF%L|*j z<^=0mwc4P>hmMAQ2?4D=VtN?U+2A-dC{6ir40+_3O-682ZQaFk4hFm5790uF7xLiG4=5b?#>A-7Dpy0Y-J z_yk+atWIsKOjh+>+m1TL3??zO{4*4bs;EoQ!n;?&;#U@Ec^m?T)H!#7V;gZk)(7*g z^MR`7ffoLO!5S0qTBMevFdieLPWZa8L$^*}4+$#nf_4wqm4vI_z_!ht(?0~SI4S#X zkXz+~pmbvv1NZHF^ydyo@|MOXZFj;Y*BPbdtFkUIbtU(m8HkeWF+y&z2%=<)@(9nKu=kp@>Fe^Lwc$4q^uD@c7$q! za0hf}dJT;s>+E2>*CL?~u`V`FK%(Z=z`&(9&yb+9|A7E{f(w=;01WZZ28C$!!3X(z zE_BQ@uS=>!UPn6vq#()(XWH77ryVIkm(kh-1A5Bk6>c(C5H3LDqayxRYF5h%-+R{6tFY&UC0lh`gnJqA)>b=oo*w7TJ=>OO#^I`AC4e4Bv0W^ zx<3BF-R+?{k6s|^>dbUK@Hxl!XpHiPieBRtD0bcVbL-!^x>k4pkU#kg>HeSD{}z$) z9ch>SVNCH=0}@b>0){ny->V({!M`Izz9s?flC3ynznha|Y4^kQYjXrA-;Qt|=G9W2 zMe)pz3%dk9&i|ykw(i(}DG5c<^Px@A^s;DX{iUVvQS3QF9)dP3Z+Bnc5jMJf#>C^z zP3L7KW@TyvIWP9*qAOE8aItMAtXvKZ@3L9E_ueSm&|F(Q{hG$JG0o!`!DjQw+qF>V z*UJ_8zHB>2v+`GNGi!DWe%)k8Vv4+n=JxIhR~pxp4rHWY$lH^!6Sx=BWR*m`$j--=-JcLytW4QOvpM@&GXBQD=0j-^^NCJVyCC_g*=AM8LiQoV65WayN~e71bj)u7nU?D{5U%Qn zH&G6{WtsCHb{INRTx%G@(COVWIWd)3##7@sK8<*<8!XI1cF_u?!~=}L*LAcJ+Z5LN z2j>v2vEab4;wS1`cdG+l5Y`Y*k?4?<70m%IY`@&7v`%;Au2sPQ@G z;{)C#KufUtmupa{lrImv;#?LhSha89ympH9`uJ9?j<)DtVy$t7k9wL=YF%}ot33Pi zO|NgEICLb);d!zK)5!WD_<9O`jT%J7rJ}mHVe0;-E1P_wYK(se3;6}*o8Gn}STlY7 zjMa}ty+l>Gz3G0u*@~pMCfkA>56*oeI+&QW+~2_3A;H+8eM;|4o4`F|+-IFm#ce4d#3RT{n_PbiMYMu z*Grk<%6}Ig^%Ad(9+*6d7XA|RbfYG^04=$fiipHe#^tdN_NwvGp;u==qwdNgu$uTQ zuD%bQ*50)71lm(37!Ewrpo6yaT#Ms|Z?4X=-9p&RxN_h3O;e2NB&^N3e>q=eK=#Zb zd?PXTJ=d-{$e%k^znpZ>d1-2`O*nXH|A=VMRq5*NX6OFK%%a(Yemjoj6)h}c)`o*6FgJhjPC4)U1bM4b_}IW>BWsBR@mMClX5 zKd*vHxkv;!V}foy;kE>7+i2({rkki;fZW+*-znDkHU0;Lc$+%1FdV1?;H2{3{0lUq zKkdr@Z+Ma)Ig|h4^q-6RrZ=z1cguf$8J zco0K{X-F-^O>S5ZD%xc z>?khjcerg9(DWoC`)Y|%sSa4+%`Tp||MdamYecHth)JvM$zZO&SJg7x&v*kKd}u5+t_GKkH%g6BTTr%;8x`oO zhmcFc(bwhmx7QueE%5Mt*MRuwe7;F&5S4Jz4!7%VG z!(xyDfW?5bhOmEmbI2)hJ+WRSqPRgK`SQB!j2GAuuIxaKKS9S&22puW9jSF-ioi=s zEPPQYy>U)4?i`X=YnxYp`rP1dUf!tFs8vf=-Y;)P$6f^T)-bK;tDQ@~G%^mlskj8$0r8}ZNzLyfSN=Czgl0?kzVIG$XB0v>WQf)W9U>&bKMizYQT0LjnvKfqy$nD&}ff@zvqV~M-6$IPn!v7E>vKPJhm{#Q*8Nqd`;XZcUmMw~k^c^q;IRfi5}0--n6 z)GzjxsS|eHfL50GyY2DYIS2JgBCR@{zDO1)YmriJd&=WBEqwBmkvq`6P9Qkux{I|U zdLe$6)>l{4$kdPS6Xl(zMd2>?Q!Ke{I|3D_+~)>l4}#l$o)ig~fk3e2Ju{AL-={gj z3L9rOR10(I+;t7rP_%u-_wXLAW{(DHuP1})bw2$@%Tubj2R3LzCvn2a_Ju@~w7ib* zenb-FL4Nh&-0Go7UxKdG!y4bmw}z7_h<--`_VRGZd=kH%mCK{ z%Y;CYD!C076Xq3&IDBuG27dzA3=*%qj>BOsY4V|29aCG5MZHnGs7NGOff?li4+%tx zYQwJ6owWM?g|BTbQg6L1p{>Y$9$%*4MM1Yq5;?J5t43AtDzUc^lt!Pr;!pM4$_YKn zVjA`WSqg4!W2<>dO)X}9=W|_MlXtuOX2+%11#}lvT4*^I2kS0;4+!7-9YW5UMuI#k z6t}KGyyx`$u!fm1Uc5z5i174SsEAS7fJ6FOA=^Pri^-v;Se@HA1DZOMM`1mnC^^I1 zN88n1F2C{1+8pll6xIns584@NrtW)Uqb_CMmMtm%7eY=RrXhQy z#Yq`kw?yI7u)&RmTy&xZ@vdV-C11MvU?)7_L)-&2*0=);Yl-i?ar4>BE3)=aTD%S4 zFGj$XG25)8t}sqz;aASxa-a~q8`Cx3>Op>HJlNrWW1KZ{fUI!^o3fi($u?fneV9<+ zLyow-&Gc~eM25di{4k3@8svi{8Z`OIW@!}BE4D8yN zMXRGTV;Mt*sG*(uw!s-uijS%gY|R>i+|SJX7yXxXws8X<3o@W5a29gcHH4-&tYO;O zkKIx`&ytO1PpN8qR5GG**`m;|D)^w>{x$_X^nKiP)B94!6;&|-SmPAp1{>_Fd76!RdF~-a6IJU&sojjwjQXGO$U1Y9nuGadf zgf`{5hWq`0@S)!uWD9zp4~qAXtll>rV{Nik7xJCPm?6=3;U>hp&q06)fDu}=uI^Dl z6Bt`0-iX>h1YJg;e5#RJlDEbrrPVy;Qyb0Pf;$x zVnB`9rH&)y5wZ!1cDsbJ`p>)eTRzc_VKUFW2FpOvO6IFFG|SqB0TJ-hv>Tp%d0gja z48M5KJwavvLi*oVko;e)%>6l)?K@ZU=ZW_Jj{E#GpRj5HAfOZ=aQR?NtTM_8}8cgxPb4*K3r>C83;{PG5>kN|7%tPKquwf8)0%s jG~U4mpa0%n`$1*>XJX5K=KZh7<^8nXzpw2eq>=vxnm%71 literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..e194ae5 --- /dev/null +++ b/index.html @@ -0,0 +1,27 @@ + + + + + + + + + Color Control + + + + + + + diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..cc63ae2 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,58 @@ +// Karma configuration +// Generated on Thu Jun 30 2016 14:46:40 GMT-0700 (Pacific Daylight Time) + +module.exports = function (config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha', 'requirejs', 'chai'], + + // list of files / patterns to load in the browser + files: [ + { pattern: 'test/**/*.js', included: false }, + 'test-main.js' + ], + + // list of files to exclude + exclude: [ + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['PhantomJS'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity + }) +} diff --git a/mariamclaughlin.color-control-dev-0.1.0.vsix b/mariamclaughlin.color-control-dev-0.1.0.vsix new file mode 100644 index 0000000000000000000000000000000000000000..35688841b88fe22f5d46082f6c6ca2a861b80f49 GIT binary patch literal 86115 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 o!#M->s6HKyMz?7Qn(m_XlctSCoC3 { + + const bestCaseDict: IDictionaryStringTo = { + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "0;1;2;3", + "Labels": "Critical;High;Medium;Low" + }; + const bestCaseValues = ["1", "2", "3", "4"]; + + it("gets the field name specified in dictionary", () => { + expect(InputParser.getFieldName(bestCaseDict)).to.be.deep.equal("Priority"); + }); + + it("throws when field name not specified", () => { + expect(() => InputParser.getFieldName({ + "FieldName": "" + })).throw("FieldName not specified."); + }); + + it("returns an array of interfaces", () => { + expect(InputParser.getOptions(bestCaseDict, bestCaseValues)).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "orange", label: "High" }, + { value: "3", color: "yellow", label: "Medium" }, + { value: "4", color: "blue", label: "Low" }]); + }); + + it("returns options with empty strings in label key when no labels are provided", () => { + expect(InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "1;2;3;4", + "Labels": "" + }, bestCaseValues)).to.be.deep.equal([ + { value: "1", color: "red", label: "" }, + { value: "2", color: "orange", label: "" }, + { value: "3", color: "yellow", label: "" }, + { value: "4", color: "blue", label: "" }]); + }); + + it("returns 1 default color when 1 value and no colors are provided", () => { + expect(InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "", + "Values": "1", + "Labels": "Critical" + }, ["1"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }]); + }); + + it("returns options with default colors and NO labels when NO colors and NO labels provided.", () => { + expect(InputParser.getOptions({ + + "FieldName": "Priority", + "Colors": "", + "Values": "1;2;3;4", + "Labels": "" + + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "" }, + { value: "2", color: "orange", label: "" }, + { value: "3", color: "yellow", label: "" }, + { value: "4", color: "blue", label: "" }]); + + }); + + it("throws when allowed values are not specified", () => { + expect(() => InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "", + "Labels": "Critical;High;Medium" + }, [])).throw("Allowed values not specified."); + }); + + it("Returns options with some empty labels if less labels than values provided", () => { + expect(InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "1;2;3;4", + "Labels": "Critical;High;Medium" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "orange", label: "High" }, + { value: "3", color: "yellow", label: "Medium" }, + { value: "4", color: "blue", label: "" }]); + }); + + it("throws when less colors than values are provided", () => { + expect(() => InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange", + "Values": "1;2;3;4", + "Labels": "Critical;High;Medium;Low" + }, ["1", "2", "3", "4"])).throw("Not enough colors provided in admin XML file."); + }); + + it("gives one label to every value, and truncates unused labels when MORE Labels THAN values are provided", () => { + expect(InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "1;2;3;4", + "Labels": "Critical;High;Medium;Low;Very Low" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "orange", label: "High" }, + { value: "3", color: "yellow", label: "Medium" }, + { value: "4", color: "blue", label: "Low" }]); + }); + + it("gives one color to every value, and truncates unused colors when MORE colors THAN values are provided", () => { + expect(InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue;magenta;deep-blue", + "Values": "1;2;3;4", + "Labels": "Critical;High;Medium;Low;Very Low" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "orange", label: "High" }, + { value: "3", color: "yellow", label: "Medium" }, + { value: "4", color: "blue", label: "Low" }]); + }); + + it("returns custom positions of labels when label is placed between semicolons.", () => { + expect(InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "1;2;3;4", + "Labels": "Critical;;;Low" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "orange", label: "" }, + { value: "3", color: "yellow", label: "" }, + { value: "4", color: "blue", label: "Low" }]); + }); // + + it("returns custom positions of colors when no color is placed between semicolons.", () => { + expect(InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;;yellow;blue", + "Values": "1;2;3;4", + "Labels": "Critical;High;Medium;Low" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "", label: "High" }, + { value: "3", color: "yellow", label: "Medium" }, + { value: "4", color: "blue", label: "Low" }]); + }); + + it("Returns one option when one value,one label, and one are color provided", () => { + expect(InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red", + "Values": "1", + "Labels": "Critical" + }, ["1"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + ]); + }); +}); \ No newline at end of file diff --git a/scripts/InputParser.ts b/scripts/InputParser.ts new file mode 100644 index 0000000..bbe1438 --- /dev/null +++ b/scripts/InputParser.ts @@ -0,0 +1,129 @@ + +import { IOption } from "./IOption" +import { Colors} from "./colors" + +export class InputParser { + /** + * Parses and gets a FieldName from a dictionary. + * @param {IDictionaryStringTo} inputs - The dictionary has the structure: + * { + * "FieldName": "Priority", + * "Colors": "red;orange;yellow;blue", + * "Values": "0;1;2;3", + * "Labels": "Critical;High;Medium;Low" + * } + * @return {string} The FieldName + * @throws Will throw an {string} error if a FieldName is not specified in the dictionary. + */ + public static getFieldName(inputs: IDictionaryStringTo): string { + if (inputs["FieldName"]) { + return inputs["FieldName"]; + } + throw ("FieldName not specified.") + } + /** + * Parses the inputs from a {IDictionaryStringTo} dictionary. + * @return an array of Interfaces of the structure: { + * value: values[i], + * color: colors[i], + * label: labels[i] + * } + * @throws Will throw an {string} error if allowedValues are not specified. + * @throws Will throw an {string} error if Not enough colors provided in admin XML file. + */ + public static getOptions(inputs: IDictionaryStringTo, allowedValues: string[]): IOption[] { + + if (allowedValues && allowedValues.length) { + + let colors: string[] = []; + let inputColors: string[] = []; + let labels: string[] = []; + let inputLabels: string[] = []; + + inputColors = InputParser._extractInputs(inputs["Colors"]); + inputLabels = InputParser._extractInputs(inputs["Labels"]); + + colors = InputParser._getColors(inputColors, allowedValues); + labels = InputParser._getLabels(inputLabels, allowedValues); + + return InputParser._buildOptions(allowedValues, colors, labels); + + } else { + + throw ("Allowed values not specified."); + } + } + /** + * Parses {string} rawInput, converting the input to an array of values. + * @param {string} rawInput - The string consists of colors or labels + * separated by ";" + * @return {string[]} inputs (either colors or labels) + * @static + * @private + */ + private static _extractInputs(rawInput: string): string[] { + if (rawInput) { + return rawInput.split(";"); + } + return []; + } + /** + * Takes {string[]} inputColors and string{[]} values, and maps {string} colors + * to every value. Also, it checks if the colors were correctly inputed. + * @return {string[]} newColors - An array of {string} colors that match + * the number of values. + * @throws {string} "Not enough colors provided in admin XML file." + * @static + * @private + */ + private static _getColors(inputColors: string[], values: string[]): string[] { + + // Values length can never be 0, colors length can be 0 or more + if (values.length > inputColors.length && inputColors.length !== 0) { + // If values array length is greater, an error will appear + throw ("Not enough colors provided in admin XML file."); + } + + if (inputColors.length === 0) { + //DefaultColors is a static class wich does the processing of colors. + return Colors.getColors(values.length); + } else { + return values.map((v, idx) => inputColors[idx] || ""); + } + } + /** + * Takes {string[]} inputLabels and string{[]} values, and maps {string} labels + * to every value. If more values were provided, it ignores them. If less labels + * than values were provided, it fills the array with empty strings (""); + * @return {string[]} newLabels - An array of {string} labels that match + * the number of values. + * @static + * @private + */ + private static _getLabels(inputLabels: string[], values: string[]): string[] { + // Values length can never be 0, labels length can be 0 or more + // There will be no default labels, just whitespace "" + return values.map((v, idx) => inputLabels[idx] || ""); + } + /** + * Takes {string[]} values, colors and labels; and populates an array of interfaces of the + * form {value: "string", color: "string", label: "string"} + * @return {IOptions []} options + * @static + * @private + */ + private static _buildOptions(values: string[], colors: string[], labels: string[]): IOption[] { + + let options: IOption[] = []; + let valuesLength: number = values.length; + + for (let i = 0; i < valuesLength; i++) { + options.push({ + value: values[i], + color: colors[i], + label: labels[i] + }); + } + return options; + } +} \ No newline at end of file diff --git a/scripts/app.ts b/scripts/app.ts new file mode 100644 index 0000000..78ae238 --- /dev/null +++ b/scripts/app.ts @@ -0,0 +1,21 @@ +/// +import { Controller } from "./control"; +import * as ExtensionContracts from "TFS/WorkItemTracking/ExtensionContracts"; + +var control: Controller; + +var provider = () => { + return { + onLoaded: (workItemLoadedArgs: ExtensionContracts.IWorkItemLoadedArgs) => { + control = new Controller(); + }, + onFieldChanged: (fieldChangedArgs: ExtensionContracts.IWorkItemFieldChangedArgs) => { + var changedValue = fieldChangedArgs.changedFields[control.getFieldName()]; + if (changedValue !== undefined) { + control.updateExternal(changedValue); + } + } + } +}; + +VSS.register("mariamclaughlin.color-control-dev.color-control-contribution", provider); \ No newline at end of file diff --git a/scripts/colors.tests.ts b/scripts/colors.tests.ts new file mode 100644 index 0000000..872ffde --- /dev/null +++ b/scripts/colors.tests.ts @@ -0,0 +1,54 @@ +import { expect } from 'chai'; +import { Colors } from './colors'; + +describe("Colors", () => { + const defaultColors = [ + ["red"], + ["red", "blue"], + ["red", "yellow", "blue"], + ["red", "orange", "yellow", "blue"], + ["red", "orange", "yellow", "blue", "darkblue"], + ["darkred", "red", "orange", "yellow", "blue", "darkblue"], + ["darkred", "red", "orange", "yellow", "blue", "darkblue", "purple"] + ]; + + // Tests for one value, minimum + it("outputs color array for 1 value", () => { + expect(Colors.getColors(1)).to.be.deep.equal((defaultColors[0])); + }); + + // Tests for three values, happy path + it("outputs color array for 3 values", () => { + expect(Colors.getColors(3)).to.be.deep.equal((defaultColors[2])); + }); + + // Tests for seven values, maximum + it("outputs color array for 7 values", () => { + expect(Colors.getColors(7)).to.be.deep.equal((defaultColors[6])); + }); + + // Tests for eight values, exceeds maximum and should repeat last one + it("outputs color array for 8 values", () => { + expect(Colors.getColors(8)).to.be.deep.equal((["dark red", "red", "orange", "yellow", "blue", "dark blue", "purple", "purple"])); + }); + + // Tests for twenty values, extreme case, exceeds maximum as well + it("outputs color array for 15 values", () => { + expect(Colors.getColors(15)).to.be.deep.equal((["dark red", "red", "orange", "yellow", "blue", "dark blue", "purple", "purple", "purple", "purple", "purple", "purple", "purple", "purple", "purple"])); + }); + + // Tests for invalid input of negative + it("throws exception for invalid input of negative", () => { + expect(() => Colors.getColors(-1)).throws(("Incorrect input and no default colors can be provided")); + }); + + // Tests for invalid input of 0 (no input values) + it("throws exception for invalid input of 0", () => { + expect(() => Colors.getColors(0)).throws(("Incorrect input and no default colors can be provided")); + }); + + // Tests for invalid input of null + it("throws exception for invalid input of null/undefined", () => { + expect(() => Colors.getColors(null)).throws(("Incorrect input and no default colors can be provided")); + }); +}); diff --git a/scripts/colors.ts b/scripts/colors.ts new file mode 100644 index 0000000..8996f7d --- /dev/null +++ b/scripts/colors.ts @@ -0,0 +1,46 @@ +export class Colors { + + /** Colors holds a static method called getColors. This method allows InputParser to + * retrieve default colors when the user inputs no colors, but has at least 1 value. + */ + + public static getColors(numberOfValues: number): string[] { + /** Takes in the number of values available in the control and returns an array of + * default colors equal to the number of values. + */ + + // newColors stores array of default colors for method to return + var newColors: string[] = []; + + // defaultColors is an array of default color arrays, allows retrieval of array by index + // Note: Colors need to be changed to official colors, these are just test colors. + const defaultColors = [ + ["red"], + ["red", "blue"], + ["red", "yellow", "blue"], + ["red", "orange", "yellow", "blue"], + ["red", "orange", "yellow", "blue", "darkblue"], + ["darkred", "red", "orange", "yellow", "blue", "darkblue"], + ["darkred", "red", "orange", "yellow", "blue", "darkblue", "purple"] + ]; + + // Check number of values from input + if (numberOfValues > 0 && numberOfValues <= defaultColors.length) { + // Supports between 1 to 7 values for default colors + newColors = defaultColors[numberOfValues - 1]; + return newColors; + } + else if (numberOfValues > defaultColors.length) { + // Does not support beyond the number of default colors, so last color is repeated until all values have an assigned color + newColors = defaultColors[defaultColors.length-1]; + for (var i = defaultColors.length; i < numberOfValues; i++) { + newColors.push(defaultColors[defaultColors.length-1][defaultColors.length-1]); + } + return newColors; + } + else { + // Covers null, negative and undefined numberOfValues + throw "Incorrect input and no default colors can be provided"; + } + } +} \ No newline at end of file diff --git a/scripts/control.tests.ts b/scripts/control.tests.ts new file mode 100644 index 0000000..7fa1fdc --- /dev/null +++ b/scripts/control.tests.ts @@ -0,0 +1,3 @@ +/// + +import { expect } from 'chai'; diff --git a/scripts/control.ts b/scripts/control.ts new file mode 100644 index 0000000..f1c65e3 --- /dev/null +++ b/scripts/control.ts @@ -0,0 +1,87 @@ +/** The class control.ts will orchestrate the classes of InputParser, Model and View + * in order to perform the required actions of the extensions. + */ +import * as VSSService from "VSS/Service"; +import * as WitService from "TFS/WorkItemTracking/Services"; +import * as ExtensionContracts from "TFS/WorkItemTracking/ExtensionContracts"; +import { InputParser } from "./InputParser"; +import { Model } from "./model"; +import { colorControl } from "./view"; +import { ErrorView } from "./errorView"; +import * as Q from "q"; + +export class Controller { + + private _fieldName: string = ""; + + private _inputs: IDictionaryStringTo; + + private _model: Model; + + private _view: colorControl; + + constructor() { + this._initialize(); + } + + private _initialize(): void { + + + this._inputs = VSS.getConfiguration().witInputs; + this._fieldName = InputParser.getFieldName(this._inputs); + + WitService.WorkItemFormService.getService().then( + (service) => { + Q.spread( + [service.getAllowedFieldValues(this._fieldName), service.getFieldValue(this._fieldName)], + (allowedValues: string[], currentValue: (string | number)) => { + if (typeof (currentValue) === 'number') { + allowedValues = allowedValues.sort((a, b) => Number(a) - Number(b)); + } + let options = InputParser.getOptions(this._inputs, allowedValues); + this._model = new Model(options, String(currentValue)); + this._view = new colorControl(this._model, (val) => { + //when value changes by clicking rows + this._updateInternal(val); + }, () => {//when down or right arrow is used + this._model.selectNextOption(); + this._updateInternal(this._model.getSelectedValue()); + }, () => {//when up or left arror is used + this._model.selectPreviousOption(); + this._updateInternal(this._model.getSelectedValue()); + }); + }, this._handleError + ).then(null, this._handleError); + }, + this._handleError); + } + + private _handleError(error: string): void { + let errorView = new ErrorView(error); + } + + private _updateInternal(value: string): void { + WitService.WorkItemFormService.getService().then( + (service) => { + service.setFieldValue(this._fieldName, value).then( + () => { + this._update(value, true); + }, this._handleError) + }, + this._handleError + ); + } + + private _update(value: string, focus: boolean): void { + this._model.setSelectedValue(value); + this._view.update(value, focus); + } + + public updateExternal(value: string): void { + this._update(String(value), false); + } + + public getFieldName(): string { + return this._fieldName; + } +} diff --git a/scripts/errorView.ts b/scripts/errorView.ts new file mode 100644 index 0000000..c46d932 --- /dev/null +++ b/scripts/errorView.ts @@ -0,0 +1,37 @@ +/*************************************************************************** +Purpose: This class is being used to get errors from an input parser and + a model. It takes all the errors and put them in an array in + order to be sent to a view to display them. +***************************************************************************/ + + +// shows the errors in the control container rather than the control. +export class ErrorView { + + constructor(error: string) { + // container div + var container = $("
"); + container.addClass("container"); + + // create an icon and text for the error + var warning = $("

"); + warning.text(error); + warning.attr("title", error); + container.append(warning); + + + // include documentation link for help. + var help = $("

"); + help.text("See "); + + var a = $(" "); + a.attr("href", "https://www.visualstudio.com/en-us/products/visual-studio-team-services-vs.aspx"); + a.attr("target", "_blank"); + a.text("Documentation."); + + help.append(a); + container.append(help); + + $('body').empty().append(container); + } +} \ No newline at end of file diff --git a/scripts/model.tests.ts b/scripts/model.tests.ts new file mode 100644 index 0000000..3a3b9c2 --- /dev/null +++ b/scripts/model.tests.ts @@ -0,0 +1,96 @@ +import { expect } from 'chai'; +import { Model } from './model'; +import {IOption} from './IOption'; + +describe("Model", () => { + let model: Model; + const options: IOption[] = [ + { + value: "1", + color: "Red", + label: "High" + }, + { + value: "2", + color: "Blue", + label: "Medium" + }, + { + value: "3", + color: "Green", + label: "Low" + } + ]; + + const testOption: IOption = { + value: "4", + color: "Purple", + label: "Very Low" + }; + + beforeEach(() => { + model = new Model(options, options[0].value); + }); + + // Tests for if initial value matches selected value and if it is stored + it("outputs selected value for 1st option", () => { + expect(model.getSelectedValue()).to.be.deep.equal(options[0].value); + }); + + // Tests for a selected value that is set and one of the values + it("outputs selected value for 2nd option", () => { + model.setSelectedValue(options[1].value); + expect(model.getSelectedValue()).to.be.deep.equal(options[1].value); + }); + + // Tests for a selected option that is set and one of the options + it("outputs selected option for 2nd option", () => { + model.setSelectedValue(options[1].value); + expect(model.getSelectedOption()).to.be.deep.equal(options[1]); + }); + + // Tests for a selected value that is null + it("Sets selectedValue to null when no value is selected", () => { + model.setSelectedValue("99"); + expect(model.getSelectedValue()).to.be.deep.equal(null); + }); + + // Tests for a selected value that is null + it("Sets selectedValue to null when no value is selected", () => { + model.setSelectedValue(""); + expect(model.getSelectedValue()).to.be.deep.equal(null); + }); + + // Tests for a selected value that is undefined + it("throws exception for selected value that is undefined", () => { + expect(() => model.setSelectedValue(undefined)).throws("Undefined value"); + }); + + // Tests for the previous option of the first selected option, edge case + it("outputs previous option for 1st selected option: gives first option", () => { + model.setSelectedValue(options[0].value); + model.selectPreviousOption(); + expect(model.getSelectedOption()).to.be.deep.equal(options[2]); + }); + + // Tests for the next option of the last selected option, edge case + it("outputs previous option for last selected option: gives last option", () => { + model.setSelectedValue(options[2].value); + model.selectNextOption(); + expect(model.getSelectedOption()).to.be.deep.equal(options[0]); + }); + + // Tests for the previous option of a selected option + it("outputs previous option for 2nd selected option", () => { + model.setSelectedValue(options[1].value); + model.selectPreviousOption(); + expect(model.getSelectedOption()).to.be.deep.equal(options[0]); + }); + + // Tests for the next option of a selected option + it("outputs next option for 2nd selected option", () => { + model.setSelectedValue(options[1].value); + model.selectNextOption(); + expect(model.getSelectedOption()).to.be.deep.equal(options[2]); + }); +}); \ No newline at end of file diff --git a/scripts/model.ts b/scripts/model.ts new file mode 100644 index 0000000..7b7db1b --- /dev/null +++ b/scripts/model.ts @@ -0,0 +1,79 @@ +import { IOption } from "./IOption"; + +export class Model { + + /** + * Model takes inputs of Options, an array of option objects from InputParser for each + * value, along with its respective color and label, if present. Model also takes the + * initialValue from View, which will be set as the selectedView to begin with. + * This will change as click events occur within View. + */ + + constructor(options: IOption[], initialValue: string) { + this._options = options; + this._selectedValue = initialValue; + this._selectedOption = { value: "", color: "", label: "" }; + } + + // Value selected in View + private _selectedValue: string; + + // Option selected in View + private _selectedOption: IOption; + + // Array of objects from InputParser (originates from VSS API) + private _options: IOption[] = []; + + // Checks if the selected value exists in the array of objects from InputParser. + public setSelectedValue(value: string) { + + if(value === undefined){ + throw "Undefined value"; + } + + for (let option of this._options) { + if (option.value === value) { + this._selectedValue = value; + this._selectedOption = option; + return; + } + } + this._selectedValue = null; + this._selectedOption = { value: null, color: "", label: "" }; + } + + public selectPreviousOption() { + let index = this._options.indexOf(this._selectedOption); + + if (index > 0 && index !== -1) { + this.setSelectedValue(this._options[index - 1].value); + } else { + this.setSelectedValue(this._options[this._options.length - 1].value); + } + } + + public selectNextOption() { + let index = this._options.indexOf(this._selectedOption); + + if (index < (this._options.length - 1) && index !== -1) { + this.setSelectedValue(this._options[index + 1].value); + } else { + this.setSelectedValue(this._options[0].value); + } + } + + // Returns the stored selected value to compare with field value from VSS API. + public getSelectedValue(): string { + return this._selectedValue; + } + + // Returns the stored selected option for View to update UI. + public getSelectedOption(): IOption { + return this._selectedOption; + } + + // Returns the stored array of Options for View to update the UI. + public getOptions(): IOption[] { + return this._options; + } +} \ No newline at end of file diff --git a/scripts/view.ts b/scripts/view.ts new file mode 100644 index 0000000..af92c01 --- /dev/null +++ b/scripts/view.ts @@ -0,0 +1,156 @@ +/// + +import { IOption } from "./IOption"; +import { Model } from "./model"; + +/** + * Class colorRow returns the view of a single value, given the parameters allowedValue, color, + * label, and whether or not it's selected. + */ +export class colorRow { + + private _row: JQuery; + + constructor(public allowedValue: string, public color: string, public label: string) { + + } + + // creates the row + public create(): JQuery { + + // row div + this._row = $("

").attr("role", "radio"); + this._row.data("value", this.allowedValue); + this._row.addClass("row"); + + // color div + var valueColor = $("
"); + valueColor.addClass("valueColor"); + var color = this.color; + valueColor.css("background-color", this.color) + this._row.append(valueColor); + + // label div + var valueLabel = $("
"); + valueLabel.addClass("valueLabel"); + valueLabel.attr("title", this.label); + if (!this.label) { + valueLabel.text(this.allowedValue); + } + else { + valueLabel.text(this.allowedValue + " - " + this.label); + }; + this._row.append(valueLabel); + + // return the entire row to the control + return this._row; + } + + public select(focus: boolean): void { + this._row.addClass("selected"); + this._row.attr("aria-checked", "true"); + this._row.attr("tabindex", 0); + if (focus) { + this._row.focus(); + } + } + + public unselect(): void { + this._row.removeClass("selected"); + this._row.attr("aria-checked", "false"); + this._row.attr("tabindex", -1); + } +} + +/** + * Class colorControl returns a container that renders each row, the selected value, + * and a function that allows the user to change the selected value. + */ +export class colorControl { + + public rows: colorRow[] = []; + + constructor(private model: Model, private onItemClicked: Function, private onNextItem: Function, private onPreviousItem: Function) { + this.init(); + } + + // creates the container + public init(): void { + + var container = $("
"); + container.addClass("container"); + container.attr('tabindex', '0'); + + var options = this.model.getOptions(); + + for (let option of options) { + var row = new colorRow(option.value, option.color, option.label); + this.rows.push(row); + container.append(row.create()); + // checks if the row is selected and displays accordingly + if (option.value === this.model.getSelectedValue()) { + row.select(true); + } + else { + row.unselect(); + } + } + + // allows user to click, keyup, or keydown to change the selected value. + $(document).click((evt: JQueryMouseEventObject) => { + this._click(evt); + }).bind('keydown', (evt: JQueryKeyEventObject) => { + if (evt.keyCode == 40 || evt.keyCode == 39) { + // According to ARIA accessibility guide, both down and right arrows should be used. + if (this.onNextItem) { + this.onNextItem(); + evt.preventDefault(); + } + } + else if (evt.keyCode == 38 || evt.keyCode == 37) { + // According to ARIA accessibility guide, both up and left arrows should be used. + if (this.onPreviousItem) { + this.onPreviousItem(); + evt.preventDefault(); + } + } + }); + + $('body').empty().append(container); + + $(document).ready(() => { + this._scroll(); + }); + } + + public update(value: string, focus: boolean): void { + for (let row of this.rows) { + if (row.allowedValue == value) { + row.select(focus); + } + else { + row.unselect(); + } + } + this._scroll(); + } + + private _scroll(): void { + let scrollTo = $("div.row.selected"); + + if (scrollTo.length) { + if (scrollTo.offset().top > $(".container").height()) { + $(".container").scrollTop( + scrollTo.offset().top - $(".container").offset().top + $(".container").scrollTop() + ); + } + } + } + + private _click(evt: JQueryMouseEventObject): void { + let itemClicked = $(evt.target).closest(".row").data("value"); + if (!!itemClicked && !!this.onItemClicked) { + this.onItemClicked(itemClicked); + } + } +} \ No newline at end of file diff --git a/styles/style.css b/styles/style.css new file mode 100644 index 0000000..eafd336 --- /dev/null +++ b/styles/style.css @@ -0,0 +1,78 @@ +div { + font-family: Segoe UI, Helvetica; + font-size: 14px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.container:focus { + outline: solid lightgray 1px; +} + +.row.selected:focus { + background-color: rgb(220, 230, 244); +} + +.row:not(.selected):hover{ + background-color:#E3F2FD +} + +.container { + height:100%; + border: 1px solid transparent; +} + +.container:hover { + overflow-y: auto; +} + +.container:not(:hover):not(:focus) .row:not(.selected) > .valueColor { + background-color: gray !important; +} + +.container:not(:hover) .row.selected > .valueColor { + color: black; +} + +.row { + line-height: 18px; + clear: both; + padding: 3px 2px; + cursor: pointer; + padding: 2px; + outline: none; +} + +.row:hover { + background-color: rgb(220, 230, 244); +} + +.valueColor { + float: left; + width: 15px; + height: 15px; + border-radius: 25%; + position: relative; + margin-right: 4px; + margin-top: 1px; + opacity: .45; + transition: background linear .2s; + border: 1px solid transparent; + padding-top: 2px; +} + + +.valueLabel { + color: gray; +} + +.row:hover > .valueLabel, +.row.selected > .valueLabel { + color: black; +} + +.row:hover > .valueColor, +.row.selected > .valueColor { + opacity: 1; +} \ No newline at end of file diff --git a/test-main.js b/test-main.js new file mode 100644 index 0000000..4e01a19 --- /dev/null +++ b/test-main.js @@ -0,0 +1,27 @@ +var allTestFiles = []; +var TEST_REGEXP = /(spec|tests)\.js$/i; + +// Get a list of all the test files to include +Object.keys(window.__karma__.files).forEach(function (file) { + if (TEST_REGEXP.test(file)) { + // Normalize paths to RequireJS module names. + // If you require sub-dependencies of test files to be loaded as-is (requiring file extension) + // then do not normalize the paths + var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, ''); + allTestFiles.push(normalizedTestModule); + } +}) + +require.config({ + // Karma serves files under /base, which is the basePath from your config file + baseUrl: '/base', + + paths: { + "chai": "node_modules/chai/chai" + }, + + // dynamically load all test files + deps: allTestFiles, + + callback: window.__karma__.start +}); \ No newline at end of file diff --git a/test/scripts/ErrorContainer.js b/test/scripts/ErrorContainer.js new file mode 100644 index 0000000..d300f94 --- /dev/null +++ b/test/scripts/ErrorContainer.js @@ -0,0 +1,35 @@ +define(["require", "exports"], function (require, exports) { + "use strict"; + var ErrorContainer = (function () { + function ErrorContainer(parser, model) { + this._errors = []; + this._errorsInputParser = []; + this._errorsModel = []; + this._parser = parser; + this._model = model; + } + ErrorContainer.prototype.hasErrors = function () { + this._errorsInputParser = this._parser.getErrors(); + this._errorsModel = this._model.getErrors(); + if (this._errorsInputParser || this._errorsModel) + return true; + return false; + }; + ErrorContainer.prototype.getErrors = function () { + for (var _i = 0, _a = this._errorsInputParser; _i < _a.length; _i++) { + var error = _a[_i]; + this._errors.push(error); + } + for (var _b = 0, _c = this._errorsModel; _b < _c.length; _b++) { + var error = _c[_b]; + this._errors.push(error); + } + return this._errors; + }; + ErrorContainer.prototype.push = function (error) { + this._errors.push(error); + }; + return ErrorContainer; + }()); + exports.ErrorContainer = ErrorContainer; +}); diff --git a/test/scripts/IOption.js b/test/scripts/IOption.js new file mode 100644 index 0000000..d4db967 --- /dev/null +++ b/test/scripts/IOption.js @@ -0,0 +1,3 @@ +define(["require", "exports"], function (require, exports) { + "use strict"; +}); diff --git a/test/scripts/InputParser.js b/test/scripts/InputParser.js new file mode 100644 index 0000000..55fb569 --- /dev/null +++ b/test/scripts/InputParser.js @@ -0,0 +1,63 @@ +define(["require", "exports", "./colors"], function (require, exports, colors_1) { + "use strict"; + var InputParser = (function () { + function InputParser() { + } + InputParser.getFieldName = function (inputs) { + if (inputs["FieldName"]) { + return inputs["FieldName"]; + } + throw ("FieldName not specified."); + }; + InputParser.getOptions = function (inputs, allowedValues) { + if (allowedValues && allowedValues.length) { + var colors = []; + var inputColors = []; + var labels = []; + var inputLabels = []; + inputColors = InputParser.extractInputs(inputs["Colors"]); + inputLabels = InputParser.extractInputs(inputs["Labels"]); + colors = InputParser.getColors(inputColors, allowedValues); + labels = InputParser.getLabels(inputLabels, allowedValues); + return InputParser.buildOptions(allowedValues, colors, labels); + } + else { + throw ("Allowed values not specified."); + } + }; + InputParser.extractInputs = function (rawInput) { + if (rawInput) { + return rawInput.split(";"); + } + return []; + }; + InputParser.getColors = function (inputColors, values) { + if (values.length > inputColors.length && inputColors.length !== 0) { + throw ("Not enough colors provided in admin XML file."); + } + if (inputColors.length === 0) { + return colors_1.Colors.getColors(values.length); + } + else { + return values.map(function (v, idx) { return inputColors[idx] || ""; }); + } + }; + InputParser.getLabels = function (inputLabels, values) { + return values.map(function (v, idx) { return inputLabels[idx] || ""; }); + }; + InputParser.buildOptions = function (values, colors, labels) { + var options = []; + var valuesLength = values.length; + for (var i = 0; i < valuesLength; i++) { + options.push({ + value: values[i], + color: colors[i], + label: labels[i] + }); + } + return options; + }; + return InputParser; + }()); + exports.InputParser = InputParser; +}); diff --git a/test/scripts/InputParser.test.js b/test/scripts/InputParser.test.js new file mode 100644 index 0000000..8415de4 --- /dev/null +++ b/test/scripts/InputParser.test.js @@ -0,0 +1,60 @@ +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +define(["require", "exports", 'chai', "./InputParser"], function (require, exports, chai_1, InputParser_1) { + "use strict"; + var TestableInputParser = (function (_super) { + __extends(TestableInputParser, _super); + function TestableInputParser() { + _super.apply(this, arguments); + } + TestableInputParser.prototype.testableExtractInputs = function (customInputs) { + var inputs = customInputs; + this._extractFieldName(inputs); + this._values = this._parseInput(inputs["Values"]); + this._colors = this._parseInput(inputs["Colors"]); + this._labels = this._parseInput(inputs["Labels"]); + }; + return TestableInputParser; + }(InputParser_1.InputParser)); + describe("inputParser", function () { + var inputParser; + beforeEach(function () { + inputParser = new TestableInputParser(); + }); + inputParser.testableExtractInputs({ "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "0;1;2;3", + "Labels": "Critical;High;Medium;Low" }); + it("outputs number when not divisible by 3 or 5", function () { + chai_1.expect(inputParser.getParsedFieldName()).to.be.equal("Priority"); + chai_1.expect(inputParser.getParsedColors()).to.be.equal(["red", "orange", "yellow", "blue"]); + chai_1.expect(inputParser.getParsedValues()).to.be.equal(["0", "1", "2", "3"]); + chai_1.expect(inputParser.getParsedLabels()).to.be.equal(["Critical", "High", "Medium", "Low"]); + chai_1.expect(inputParser.getErrors()).to.be.equal([]); + }); + inputParser.testableExtractInputs({ "FieldName": "", + "Colors": "red;orange;yellow;blue", + "Values": "0;1;2;3", + "Labels": "Critical;High;Medium;Low" }); + it("outputs number when not divisible by 3 or 5", function () { + chai_1.expect(inputParser.getParsedFieldName()).to.be.equal("Priority"); + chai_1.expect(inputParser.getParsedColors()).to.be.equal(["red", "orange", "yellow", "blue"]); + chai_1.expect(inputParser.getParsedValues()).to.be.equal(["0", "1", "2", "3"]); + chai_1.expect(inputParser.getParsedLabels()).to.be.equal(["FieldName not found"]); + }); + inputParser.testableExtractInputs({ "FieldName": "", + "Colors": ";;;", + "Values": ";;;", + "Labels": ";;;" }); + it("Checks if error with empty string", function () { + chai_1.expect(inputParser.getParsedFieldName()).to.be.equal(""); + chai_1.expect(inputParser.getParsedColors()).to.be.equal(["", "", "", ""]); + chai_1.expect(inputParser.getParsedValues()).to.be.equal(["", "", ""]); + chai_1.expect(inputParser.getParsedLabels()).to.be.equal(["", "", "", ""]); + chai_1.expect(inputParser.getParsedLabels()).to.be.equal(["FieldName not found"]); + }); + }); +}); diff --git a/test/scripts/InputParser.tests.js b/test/scripts/InputParser.tests.js new file mode 100644 index 0000000..78d91ee --- /dev/null +++ b/test/scripts/InputParser.tests.js @@ -0,0 +1,146 @@ +define(["require", "exports", 'chai', "./InputParser"], function (require, exports, chai_1, InputParser_1) { + "use strict"; + describe("InputParser", function () { + var bestCaseDict = { + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "0;1;2;3", + "Labels": "Critical;High;Medium;Low" + }; + var bestCaseValues = ["1", "2", "3", "4"]; + it("gets the field name specified in dictionary", function () { + chai_1.expect(InputParser_1.InputParser.getFieldName(bestCaseDict)).to.be.deep.equal("Priority"); + }); + it("throws when field name not specified", function () { + chai_1.expect(function () { return InputParser_1.InputParser.getFieldName({ + "FieldName": "" + }); }).throw("FieldName not specified."); + }); + it("returns an array of interfaces", function () { + chai_1.expect(InputParser_1.InputParser.getOptions(bestCaseDict, bestCaseValues)).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "orange", label: "High" }, + { value: "3", color: "yellow", label: "Medium" }, + { value: "4", color: "blue", label: "Low" }]); + }); + it("returns options with empty strings in label key when no labels are provided", function () { + chai_1.expect(InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "1;2;3;4", + "Labels": "" + }, bestCaseValues)).to.be.deep.equal([ + { value: "1", color: "red", label: "" }, + { value: "2", color: "orange", label: "" }, + { value: "3", color: "yellow", label: "" }, + { value: "4", color: "blue", label: "" }]); + }); + it("returns 1 default color when 1 value and no colors are provided", function () { + chai_1.expect(InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "", + "Values": "1", + "Labels": "Critical" + }, ["1"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }]); + }); + it("returns options with default colors and NO labels when NO colors and NO labels provided.", function () { + chai_1.expect(InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "", + "Values": "1;2;3;4", + "Labels": "" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "" }, + { value: "2", color: "orange", label: "" }, + { value: "3", color: "yellow", label: "" }, + { value: "4", color: "blue", label: "" }]); + }); + it("throws when allowed values are not specified", function () { + chai_1.expect(function () { return InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "", + "Labels": "Critical;High;Medium" + }, []); }).throw("Allowed values not specified."); + }); + it("Returns options with some empty labels if less labels than values provided", function () { + chai_1.expect(InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "1;2;3;4", + "Labels": "Critical;High;Medium" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "orange", label: "High" }, + { value: "3", color: "yellow", label: "Medium" }, + { value: "4", color: "blue", label: "" }]); + }); + it("throws when less colors than values are provided", function () { + chai_1.expect(function () { return InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange", + "Values": "1;2;3;4", + "Labels": "Critical;High;Medium;Low" + }, ["1", "2", "3", "4"]); }).throw("Not enough colors provided in admin XML file."); + }); + it("gives one label to every value, and truncates unused labels when MORE Labels THAN values are provided", function () { + chai_1.expect(InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "1;2;3;4", + "Labels": "Critical;High;Medium;Low;Very Low" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "orange", label: "High" }, + { value: "3", color: "yellow", label: "Medium" }, + { value: "4", color: "blue", label: "Low" }]); + }); + it("gives one color to every value, and truncates unused colors when MORE colors THAN values are provided", function () { + chai_1.expect(InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue;magenta;deep-blue", + "Values": "1;2;3;4", + "Labels": "Critical;High;Medium;Low;Very Low" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "orange", label: "High" }, + { value: "3", color: "yellow", label: "Medium" }, + { value: "4", color: "blue", label: "Low" }]); + }); + it("returns custom positions of labels when label is placed between semicolons.", function () { + chai_1.expect(InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;orange;yellow;blue", + "Values": "1;2;3;4", + "Labels": "Critical;;;Low" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "orange", label: "" }, + { value: "3", color: "yellow", label: "" }, + { value: "4", color: "blue", label: "Low" }]); + }); + it("returns custom positions of colors when no color is placed between semicolons.", function () { + chai_1.expect(InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red;;yellow;blue", + "Values": "1;2;3;4", + "Labels": "Critical;High;Medium;Low" + }, ["1", "2", "3", "4"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + { value: "2", color: "", label: "High" }, + { value: "3", color: "yellow", label: "Medium" }, + { value: "4", color: "blue", label: "Low" }]); + }); + it("Returns one option when one value,one label, and one are color provided", function () { + chai_1.expect(InputParser_1.InputParser.getOptions({ + "FieldName": "Priority", + "Colors": "red", + "Values": "1", + "Labels": "Critical" + }, ["1"])).to.be.deep.equal([ + { value: "1", color: "red", label: "Critical" }, + ]); + }); + }); +}); diff --git a/test/scripts/app.js b/test/scripts/app.js new file mode 100644 index 0000000..e69de29 diff --git a/test/scripts/colors.js b/test/scripts/colors.js new file mode 100644 index 0000000..a60f925 --- /dev/null +++ b/test/scripts/colors.js @@ -0,0 +1,35 @@ +define(["require", "exports"], function (require, exports) { + "use strict"; + var Colors = (function () { + function Colors() { + } + Colors.getColors = function (numberOfValues) { + var newColors = []; + var defaultColors = [ + ["red"], + ["red", "blue"], + ["red", "yellow", "blue"], + ["red", "orange", "yellow", "blue"], + ["red", "orange", "yellow", "blue", "dark blue"], + ["dark red", "red", "orange", "yellow", "blue", "dark blue"], + ["dark red", "red", "orange", "yellow", "blue", "dark blue", "purple"] + ]; + if (numberOfValues > 0 && numberOfValues <= defaultColors.length) { + newColors = defaultColors[numberOfValues - 1]; + return newColors; + } + else if (numberOfValues > defaultColors.length) { + newColors = defaultColors[defaultColors.length - 1]; + for (var i = defaultColors.length; i < numberOfValues; i++) { + newColors.push(defaultColors[defaultColors.length - 1][defaultColors.length - 1]); + } + return newColors; + } + else { + throw "Incorrect input and no default colors can be provided"; + } + }; + return Colors; + }()); + exports.Colors = Colors; +}); diff --git a/test/scripts/colors.tests.js b/test/scripts/colors.tests.js new file mode 100644 index 0000000..be378c0 --- /dev/null +++ b/test/scripts/colors.tests.js @@ -0,0 +1,38 @@ +define(["require", "exports", 'chai', './colors'], function (require, exports, chai_1, colors_1) { + "use strict"; + describe("Colors", function () { + var defaultColors = [ + ["red"], + ["red", "blue"], + ["red", "yellow", "blue"], + ["red", "orange", "yellow", "blue"], + ["red", "orange", "yellow", "blue", "dark blue"], + ["dark red", "red", "orange", "yellow", "blue", "dark blue"], + ["dark red", "red", "orange", "yellow", "blue", "dark blue", "purple"] + ]; + it("outputs color array for 1 value", function () { + chai_1.expect(colors_1.Colors.getColors(1)).to.be.deep.equal((defaultColors[0])); + }); + it("outputs color array for 3 values", function () { + chai_1.expect(colors_1.Colors.getColors(3)).to.be.deep.equal((defaultColors[2])); + }); + it("outputs color array for 7 values", function () { + chai_1.expect(colors_1.Colors.getColors(7)).to.be.deep.equal((defaultColors[6])); + }); + it("outputs color array for 8 values", function () { + chai_1.expect(colors_1.Colors.getColors(8)).to.be.deep.equal((["dark red", "red", "orange", "yellow", "blue", "dark blue", "purple", "purple"])); + }); + it("outputs color array for 15 values", function () { + chai_1.expect(colors_1.Colors.getColors(15)).to.be.deep.equal((["dark red", "red", "orange", "yellow", "blue", "dark blue", "purple", "purple", "purple", "purple", "purple", "purple", "purple", "purple", "purple"])); + }); + it("throws exception for invalid input of negative", function () { + chai_1.expect(function () { return colors_1.Colors.getColors(-1); }).throws(("Incorrect input and no default colors can be provided")); + }); + it("throws exception for invalid input of 0", function () { + chai_1.expect(function () { return colors_1.Colors.getColors(0); }).throws(("Incorrect input and no default colors can be provided")); + }); + it("throws exception for invalid input of null/undefined", function () { + chai_1.expect(function () { return colors_1.Colors.getColors(null); }).throws(("Incorrect input and no default colors can be provided")); + }); + }); +}); diff --git a/test/scripts/control.js b/test/scripts/control.js new file mode 100644 index 0000000..8a3bde6 --- /dev/null +++ b/test/scripts/control.js @@ -0,0 +1,36 @@ +define(["require", "exports", "TFS/WorkItemTracking/Services", "./InputParser", "./model", "./view", "./errorView"], function (require, exports, WitService, InputParser_1, model_1, view_1, errorView_1) { + "use strict"; + var Controller = (function () { + function Controller() { + this._fieldName = ""; + this._initialize(); + } + Controller.prototype._initialize = function () { + var _this = this; + this._inputs = VSS.getConfiguration().witInputs; + this._fieldName = InputParser_1.InputParser.getFieldName(this._inputs); + WitService.WorkItemFormService.getService().then(function (service) { + Q.spread([service.getAllowedFieldValues(_this._fieldName), service.getFieldValue(_this._fieldName)], function (allowedValues, currentValue) { + var options = InputParser_1.InputParser.getOptions(_this._inputs, allowedValues); + _this._model = new model_1.Model(options, currentValue); + _this._view = new view_1.colorControl(_this._model, function (val) { + _this.update(val); + }); + }, _this.handleError); + }, this.handleError); + }; + Controller.prototype.handleError = function (error) { + var errorView = new errorView_1.ErrorView(error); + }; + Controller.prototype.update = function (value) { + var _this = this; + WitService.WorkItemFormService.getService().then(function (service) { + service.setFieldValue(_this._fieldName, value).then(function () { + _this._model.setSelectedValue(value); + _this._view.update(value); + }, _this.handleError); + }, this.handleError); + }; + return Controller; + }()); +}); diff --git a/test/scripts/control.tests.js b/test/scripts/control.tests.js new file mode 100644 index 0000000..d4db967 --- /dev/null +++ b/test/scripts/control.tests.js @@ -0,0 +1,3 @@ +define(["require", "exports"], function (require, exports) { + "use strict"; +}); diff --git a/test/scripts/errorView.js b/test/scripts/errorView.js new file mode 100644 index 0000000..19c2795 --- /dev/null +++ b/test/scripts/errorView.js @@ -0,0 +1,23 @@ +define(["require", "exports"], function (require, exports) { + "use strict"; + var ErrorView = (function () { + function ErrorView(error) { + var container = $("
"); + container.addClass("container"); + var warning = $("

"); + warning.prepend('bowtie-status-warning'); + warning.text(error); + container.append(warning); + var help = $("

"); + help.text("See "); + var a = $(" "); + a.attr("href", "https://www.visualstudio.com/en-us/products/visual-studio-team-services-vs.aspx"); + a.text("Documentation."); + help.append(a); + container.append(help); + $('body').empty().append(container); + } + return ErrorView; + }()); + exports.ErrorView = ErrorView; +}); diff --git a/test/scripts/model.js b/test/scripts/model.js new file mode 100644 index 0000000..3c3577d --- /dev/null +++ b/test/scripts/model.js @@ -0,0 +1,46 @@ +define(["require", "exports"], function (require, exports) { + "use strict"; + var Model = (function () { + function Model(options, initialValue) { + this._options = []; + this._options = options; + this._selectedValue = initialValue; + } + Model.prototype.setSelectedValue = function (value) { + for (var _i = 0, _a = this._options; _i < _a.length; _i++) { + var option = _a[_i]; + if (option.value === value) { + this._selectedValue = value; + this._selectedOption = option; + break; + } + } + if (this._selectedValue !== value) { + throw "Selected value not within original values"; + } + }; + Model.prototype.selectPreviousOption = function () { + var index = this._options.indexOf(this._selectedOption); + if (index > 0) { + this.setSelectedValue(this._options[index - 1].value); + } + }; + Model.prototype.selectNextOption = function () { + var index = this._options.indexOf(this._selectedOption); + if (index < (this._options.length - 1)) { + this.setSelectedValue(this._options[index + 1].value); + } + }; + Model.prototype.getSelectedValue = function () { + return this._selectedValue; + }; + Model.prototype.getSelectedOption = function () { + return this._selectedOption; + }; + Model.prototype.getOptions = function () { + return this._options; + }; + return Model; + }()); + exports.Model = Model; +}); diff --git a/test/scripts/model.tests.js b/test/scripts/model.tests.js new file mode 100644 index 0000000..0ca731c --- /dev/null +++ b/test/scripts/model.tests.js @@ -0,0 +1,71 @@ +define(["require", "exports", 'chai', './model'], function (require, exports, chai_1, model_1) { + "use strict"; + describe("Model", function () { + var model; + var options = [ + { + value: "1", + color: "Red", + label: "High" + }, + { + value: "2", + color: "Blue", + label: "Medium" + }, + { + value: "3", + color: "Green", + label: "Low" + } + ]; + var testOption = { + value: "4", + color: "Purple", + label: "Very Low" + }; + beforeEach(function () { + model = new model_1.Model(options, options[0].value); + }); + it("outputs selected value for 1st option", function () { + chai_1.expect(model.getSelectedValue()).to.be.deep.equal(options[0].value); + }); + it("outputs selected value for 2nd option", function () { + model.setSelectedValue(options[1].value); + chai_1.expect(model.getSelectedValue()).to.be.deep.equal(options[1].value); + }); + it("outputs selected option for 2nd option", function () { + model.setSelectedValue(options[1].value); + chai_1.expect(model.getSelectedOption()).to.be.deep.equal(options[1]); + }); + it("throws exception for selected value that is not one of values", function () { + chai_1.expect(function () { return model.setSelectedValue(testOption.value); }).throws("Selected value not within original values"); + }); + it("throws exception for selected value that is null", function () { + chai_1.expect(function () { return model.setSelectedValue(null); }).throws("Selected value not within original values"); + }); + it("throws exception for selected value that is null", function () { + chai_1.expect(function () { return model.setSelectedValue(undefined); }).throws("Selected value not within original values"); + }); + it("outputs previous option for 1st selected option: gives first option", function () { + model.setSelectedValue(options[0].value); + model.selectPreviousOption(); + chai_1.expect(model.getSelectedOption()).to.be.deep.equal(options[0]); + }); + it("outputs previous option for last selected option: gives last option", function () { + model.setSelectedValue(options[2].value); + model.selectNextOption(); + chai_1.expect(model.getSelectedOption()).to.be.deep.equal(options[2]); + }); + it("outputs previous option for 2nd selected option", function () { + model.setSelectedValue(options[1].value); + model.selectPreviousOption(); + chai_1.expect(model.getSelectedOption()).to.be.deep.equal(options[0]); + }); + it("outputs next option for 2nd selected option", function () { + model.setSelectedValue(options[1].value); + model.selectNextOption(); + chai_1.expect(model.getSelectedOption()).to.be.deep.equal(options[2]); + }); + }); +}); diff --git a/test/scripts/view.js b/test/scripts/view.js new file mode 100644 index 0000000..bb23b1c --- /dev/null +++ b/test/scripts/view.js @@ -0,0 +1,113 @@ +define(["require", "exports"], function (require, exports) { + "use strict"; + var colorRow = (function () { + function colorRow(allowedValue, color, label) { + this.allowedValue = allowedValue; + this.color = color; + this.label = label; + } + colorRow.prototype.create = function () { + this._row = $("

"); + this._row.data("value", this.allowedValue); + this._row.addClass("row"); + var valueColor = $("
"); + valueColor.addClass("valueColor"); + var color = this.color; + valueColor.css("background-color", this.color); + this._row.append(valueColor); + var valueLabel = $("
"); + valueLabel.addClass("valueLabel"); + if (!this.label) { + valueLabel.text(this.allowedValue); + } + else { + valueLabel.text(this.allowedValue + " - " + this.label); + } + ; + this._row.append(valueLabel); + return this._row; + }; + colorRow.prototype.select = function () { + this._row.addClass("selected"); + }; + colorRow.prototype.unselect = function () { + this._row.removeClass("selected"); + }; + return colorRow; + }()); + exports.colorRow = colorRow; + var colorControl = (function () { + function colorControl(model, onItemClicked) { + this.model = model; + this.onItemClicked = onItemClicked; + this.rows = []; + this.init(); + } + colorControl.prototype.init = function () { + var _this = this; + var container = $("
"); + container.addClass("container"); + var rowSelected = this.model.getSelectedOption(); + var options = this.model.getOptions(); + for (var _i = 0, options_1 = options; _i < options_1.length; _i++) { + var option = options_1[_i]; + var row = new colorRow(option.value, option.color, option.label); + this.rows.push(row); + container.append(row.create()); + var selected = option.value === rowSelected.value; + if (selected) { + row.select(); + } + else { + row.unselect(); + } + } + var callback = function (evt) { + if (evt.keyCode == 40) { + if (rowSelected) { + _this.model.selectNextOption(); + var itemClicked = _this.model.getSelectedOption(); + if (_this.onItemClicked) { + _this.onItemClicked(itemClicked.value); + } + } + } + else if (evt.keyCode == 38) { + if (rowSelected) { + _this.model.selectPreviousOption(); + var next = _this.model.getSelectedOption(); + var itemClicked = next.value; + if (_this.onItemClicked) { + _this.onItemClicked(itemClicked.value); + } + } + } + }; + container.click(function (evt) { + var itemClicked = $(evt.target).closest(".row").data("value"); + if (_this.onItemClicked) { + _this.onItemClicked(itemClicked); + } + }); + container.keydown(function (evt) { + callback(evt); + }).keyup(function (evt) { + callback(evt); + }); + $('body').empty().append(container); + }; + colorControl.prototype.update = function (value) { + for (var _i = 0, _a = this.rows; _i < _a.length; _i++) { + var row = _a[_i]; + if (row.allowedValue == value) { + row.select(); + } + else { + row.unselect(); + } + } + }; + return colorControl; + }()); + exports.colorControl = colorControl; +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e240e4d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "module": "amd", + "sourceMap": false + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/typings.json b/typings.json new file mode 100644 index 0000000..2428cde --- /dev/null +++ b/typings.json @@ -0,0 +1,11 @@ +{ + "globalDependencies": { + "chai": "registry:dt/chai#3.4.0+20160601211834", + "jquery": "registry:dt/jquery#1.10.0+20160628074423", + "knockout": "registry:dt/knockout#0.0.0+20160512130947", + "mocha": "registry:dt/mocha#2.2.5+20160619032855", + "q": "registry:dt/q#0.0.0+20160613154756", + "tfs": "npm:vss-web-extension-sdk/typings/tfs.d.ts", + "vss": "npm:vss-web-extension-sdk/typings/vss.d.ts" + } +} diff --git a/vss-extension.json b/vss-extension.json new file mode 100644 index 0000000..536d593 --- /dev/null +++ b/vss-extension.json @@ -0,0 +1,114 @@ +{ + "manifestVersion": 1, + "id": "color-control", + "version": "0.1.0", + "name": "Color Form Control", + "scopes": [ + "vso.work", + "vso.work_write" + ], + "description": "Describe your extension.", + "publisher": "mariamclaughlin", + "icons": { + "default": "img/logo.JPG" + }, + "targets": [ + { + "id": "Microsoft.VisualStudio.Services" + } + ], + "tags": [ + "Sample" + ], + "content": { + "details": { + "path": "details.md" + } + }, + "links": { + "home": { + "uri": "https://bit.ly" + }, + "getstarted": { + "uri": "https://bit.ly" + }, + "learn": { + "uri": "https://bit.ly" + }, + "support": { + "uri": "https://bit.ly" + }, + "repository": { + "uri": "https://bit.ly" + }, + "issues": { + "uri": "https://bit.ly" + } + }, + "branding": { + "color": "rgb(220, 235, 252)", + "theme": "light" + }, + "files": [ + { + "path": "img", + "addressable": true + }, + { + "path": "scripts", + "addressable": true + }, + { + "path": "styles", + "addressable": true + }, + { + "path": "index.html", + "addressable": true + } + ], + "categories": [ + "Integrate" + ], + "contributions": [ + { + "id": "color-control-contribution", + "type": "ms.vss-work-web.work-item-form-control", + "targets": [ + "ms.vss-work-web.work-item-form" + ], + "properties": { + "name": "Priority", + "group": "contributed", + "uri": "index.html", + "height": 90, + "inputs": [ + { + "id": "FieldName", + "description": "The field associated with the control.", + "validation": { + "dataType": "Field", + "isRequired": true + } + }, + { + "id": "Labels", + "description": "The list of values to select from.", + "validation": { + "dataType": "String", + "isRequired": false + } + }, + { + "id": "Colors", + "description": "The field associated with the control.", + "validation": { + "dataType": "String", + "isRequired": false + } + } + ] + } + } + ] +} \ No newline at end of file