update
1
build/assets/achievements/fileRush.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><line class="cls-1" x1="63.03" y1="22.12" x2="65.8" y2="22.12"/><g><line class="cls-1" x1="56.91" y1="32.09" x2="59.68" y2="32.09"/><line class="cls-1" x1="69.15" y1="32.09" x2="71.92" y2="32.09"/></g><g><line class="cls-1" x1="50.79" y1="42.06" x2="53.56" y2="42.06"/><line class="cls-1" x1="63.03" y1="42.06" x2="65.8" y2="42.06"/></g><line class="cls-1" x1="75.27" y1="42.06" x2="78.04" y2="42.06"/><g><g><line class="cls-1" x1="44.67" y1="52.02" x2="47.44" y2="52.02"/><line class="cls-1" x1="56.91" y1="52.02" x2="59.68" y2="52.02"/></g><line class="cls-1" x1="69.15" y1="52.02" x2="71.92" y2="52.02"/><g><line class="cls-1" x1="69.15" y1="52.02" x2="71.92" y2="52.02"/><line class="cls-1" x1="81.39" y1="52.02" x2="84.16" y2="52.02"/></g><g><line class="cls-1" x1="38.55" y1="61.99" x2="41.32" y2="61.99"/><line class="cls-1" x1="50.79" y1="61.99" x2="53.56" y2="61.99"/></g><line class="cls-1" x1="63.03" y1="61.99" x2="65.8" y2="61.99"/><g><line class="cls-1" x1="63.03" y1="61.99" x2="65.8" y2="61.99"/><line class="cls-1" x1="75.27" y1="61.99" x2="78.04" y2="61.99"/></g><line class="cls-1" x1="87.51" y1="61.99" x2="90.28" y2="61.99"/></g><g><g><line class="cls-1" x1="32.43" y1="71.96" x2="35.2" y2="71.96"/><line class="cls-1" x1="44.67" y1="71.96" x2="47.44" y2="71.96"/></g><line class="cls-1" x1="56.91" y1="71.96" x2="59.68" y2="71.96"/><g><line class="cls-1" x1="56.91" y1="71.96" x2="59.68" y2="71.96"/><line class="cls-1" x1="69.15" y1="71.96" x2="71.92" y2="71.96"/></g><g><line class="cls-1" x1="81.39" y1="71.96" x2="84.16" y2="71.96"/><line class="cls-1" x1="93.63" y1="71.96" x2="96.4" y2="71.96"/></g></g><g><g><line class="cls-1" x1="26.31" y1="81.82" x2="29.09" y2="81.82"/><line class="cls-1" x1="38.55" y1="81.82" x2="41.32" y2="81.82"/></g><line class="cls-1" x1="50.79" y1="81.82" x2="53.56" y2="81.82"/><g><line class="cls-1" x1="50.79" y1="81.82" x2="53.56" y2="81.82"/><line class="cls-1" x1="63.03" y1="81.82" x2="65.8" y2="81.82"/></g><g><line class="cls-1" x1="75.27" y1="81.82" x2="78.04" y2="81.82"/><line class="cls-1" x1="87.51" y1="81.82" x2="90.28" y2="81.82"/></g><g><line class="cls-1" x1="87.51" y1="81.82" x2="90.28" y2="81.82"/><line class="cls-1" x1="99.74" y1="81.82" x2="102.52" y2="81.82"/></g></g><g><g><line class="cls-1" x1="20.19" y1="91.67" x2="22.97" y2="91.67"/><line class="cls-1" x1="32.43" y1="91.67" x2="35.2" y2="91.67"/></g><line class="cls-1" x1="44.67" y1="91.67" x2="47.44" y2="91.67"/><g><line class="cls-1" x1="44.67" y1="91.67" x2="47.44" y2="91.67"/><line class="cls-1" x1="56.91" y1="91.67" x2="59.68" y2="91.67"/></g><g><line class="cls-1" x1="69.15" y1="91.67" x2="71.92" y2="91.67"/><line class="cls-1" x1="81.39" y1="91.67" x2="84.16" y2="91.67"/></g><g><line class="cls-1" x1="81.39" y1="91.67" x2="84.16" y2="91.67"/><line class="cls-1" x1="93.63" y1="91.67" x2="96.4" y2="91.67"/></g><g><line class="cls-1" x1="81.39" y1="91.67" x2="84.16" y2="91.67"/><line class="cls-1" x1="93.63" y1="91.67" x2="96.4" y2="91.67"/></g><g><line class="cls-1" x1="93.63" y1="91.67" x2="96.4" y2="91.67"/><line class="cls-1" x1="105.86" y1="91.67" x2="108.64" y2="91.67"/></g></g></svg>
|
After Width: | Height: | Size: 3.3 KiB |
1
build/assets/achievements/longestMessage.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><g><path class="cls-1" d="M71.77,79.68v4.64c0,1.54,.83,2.93,2.17,3.94s3.19,1.63,5.22,1.63c11.11,0,20.12,6.79,20.12,15.16v1.05H25.43v-1.05c0-4.18,2.25-7.97,5.89-10.72,3.64-2.74,8.67-4.44,14.22-4.44,4.08,0,7.39-2.49,7.39-5.57v-4.64"/><line class="cls-1" x1="62.35" y1="106.09" x2="45.54" y2="89.88"/><line class="cls-1" x1="62.35" y1="106.09" x2="79.16" y2="89.88"/><path class="cls-1" d="M41.47,55.08s-.01,.02,0,.03c-.01,.2-.01,.41-.01,.62,0,10.47,4.66,19.54,11.46,23.95h0c2.83,1.85,6.03,2.89,9.42,2.89s6.59-1.04,9.42-2.88h0c3.35-2.18,6.19-5.49,8.19-9.55"/><path class="cls-1" d="M83.23,55.54h0v.2c0,1.81-.14,3.59-.41,5.3"/><path class="cls-1" d="M46.87,37.72c3.81,2.65,9.34,4.32,15.48,4.32s11.66-1.67,15.48-4.32"/><path class="cls-1" d="M47.07,37.65l-5.52,17.89c-2.1-10.03-2.47-23.03,9.52-27.59,3.08-1.17,5.69-4.15,6.13-7.42,1.48,.93,2.21,2.62,2.24,4.36,2.65-1.17,4.51-3.96,4.59-6.85,2.11,1.9,3.47,4.6,3.85,7.41,2.18,.37,4.46-1.11,5.09-3.22,1.23,1.62,1.85,3.68,1.7,5.71,9.11,4.38,11.22,14.09,8.55,27.79"/><path class="cls-1" d="M77.71,37.65l5.52,17.89"/><path class="cls-1" d="M68.63,72.31c-1.66,1.47-3.87,2.36-6.27,2.36s-4.39-.81-6.03-2.17c-2.1-1.73-3.43-4.35-3.43-7.29"/><line class="cls-1" x1="65.09" y1="65.21" x2="52.89" y2="65.21"/><path class="cls-1" d="M40.49,47.7c-3.08,.64-5.45,4.3-5.45,8.7,0,4.86,2.88,8.8,6.42,8.8,.44,0,.87-.06,1.29-.18"/><path class="cls-1" d="M84.26,47.72c3.06,.67,5.4,4.31,5.4,8.69,0,2.18-.59,4.18-1.55,5.72"/><path class="cls-1" d="M68.63,72.31c5.78-3.46,11.46-3.3,18.42-1.75,4.26,.94,8.67,1.68,12.96,1.36s8.64-2.48,9.46-6.13c.31-1.38-.45-2.82-1.88-3.61-2.12,2.46-6.26,2.94-9.88,2.29-2.54-.46-6.32-1.52-9.6-2.34-1.4-.35-2.72-.66-3.8-.86-.5-.09-1-.17-1.49-.23-11.67-1.53-19.78,4.81-26.49,11.46"/></g><line class="cls-1" x1="53.38" y1="51.9" x2="55.02" y2="51.9"/><line class="cls-1" x1="69.44" y1="51.9" x2="71.08" y2="51.9"/></svg>
|
After Width: | Height: | Size: 2.1 KiB |
1
build/assets/achievements/moreAddedFolders.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M100.86,100.16H34.28c-1.79,0-3.4-.78-4.64-2.06-1.44-1.5-2.34-3.68-2.34-6.11V41.04c0-4.52,3.13-8.17,6.98-8.17h18.16c3.85,0,6.98,3.65,6.98,8.17v3.27h35.4c3.33,0,6.03,3.16,6.03,7.06v11.74"/><path class="cls-1" d="M29.64,98.1l7.87-28.89c.99-3.64,3.88-6.1,7.13-6.1h62.9c2.51,0,4.25,2.93,3.37,5.67l-10.06,31.38"/></svg>
|
After Width: | Height: | Size: 576 B |
1
build/assets/achievements/moreLintHint.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><polygon class="cls-1" points="64.42 103.1 25.73 64.42 64.42 25.73 72.27 33.59 41.44 64.42 64.42 87.38 87.39 64.42 79.83 56.86 64.42 72.27 56.56 64.42 79.83 41.14 103.1 64.42 64.42 103.1"/></svg>
|
After Width: | Height: | Size: 435 B |
1
build/assets/achievements/moreOnHoliday.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><circle class="cls-1" cx="64.41" cy="64.41" r="44"/><path class="cls-1" d="M36.93,62.7c-1.51-4.93,.37-10.77,4.27-14.1,2.91-2.48,7.05-3.79,10.78-2.83,1.17,.3,2.29,.79,3.3,1.46,4.13,2.73,5.83,7.87,3.89,12.44-2.16,5.11-9.05,8.03-13.65,4.13-2.09-1.77-3.11-4.68-2.53-7.36,.75-3.44,4.23-6.57,7.89-5.66,2.44,.61,4.54,3.03,4.16,5.61-.35,2.38-2.52,4.31-4.94,4.3-.85,0-1.73-.24-2.35-.82-1.71-1.58-.15-4.54,2.08-4.26"/><path class="cls-1" d="M92.65,48.88c1.51,4.93-.37,10.77-4.27,14.1-2.91,2.48-7.05,3.79-10.78,2.83-1.17-.3-2.29-.79-3.3-1.46-4.13-2.73-5.83-7.87-3.89-12.44,2.16-5.11,9.05-8.03,13.65-4.13,2.09,1.77,3.11,4.68,2.53,7.36-.75,3.44-4.23,6.57-7.89,5.66-2.44-.61-4.54-3.03-4.16-5.61,.35-2.38,2.52-4.31,4.94-4.3,.85,0,1.73,.24,2.35,.82,1.71,1.58,.15,4.54-2.08,4.26"/><polyline class="cls-1" points="42.2 79.85 50.25 85.82 59.67 79.55 69.59 85.62 77.91 79.24 86.63 84.81"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -1 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M87.76,83.68h-31.24c-1.42,0-2.58-1.16-2.58-2.59V25.78c0-1.43,1.16-2.58,2.58-2.58h28.66c1.43,0,2.58,1.15,2.58,2.58v57.78"/><rect class="cls-1" x="58.21" y="28.27" width="25.29" height="11.07"/><circle class="cls-1" cx="70.59" cy="55.52" r="10.36"/><line class="cls-1" x1="75.97" y1="46.65" x2="65.21" y2="64.38"/><rect class="cls-1" x="41.78" y="36.29" width="4.56" height="47.27"/><rect class="cls-1" x="31.52" y="36.29" width="4.56" height="47.27"/><line class="cls-1" x1="44.05" y1="36.18" x2="44.05" y2="26.49"/><line class="cls-1" x1="33.8" y1="36.18" x2="33.8" y2="26.49"/><path class="cls-1" d="M33.8,83.67c4.27,14.03,19.96,20.91,33.52,21.85,9.18,.64,20.36-1.54,26.11-9.38,3.76-5.11,5.05-11.97,2.65-17.88s-7.13-9.98-12.6-12.41"/><path class="cls-1" d="M43.94,83.67c6.29,19.8,55.84,17.23,41.44-8.77"/></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><rect class="cls-1" x="55.37" y="23.33" width="41.25" height="73.77" rx="3.49" ry="3.49"/><rect class="cls-1" x="60.58" y="29.51" width="30.84" height="13.51"/><circle class="cls-1" cx="75.69" cy="62.75" r="12.64"/><line class="cls-1" x1="82.24" y1="51.94" x2="69.13" y2="73.55"/><rect class="cls-1" x="41.53" y="55.66" width="5.56" height="45.84"/><rect class="cls-1" x="29.02" y="55.66" width="5.56" height="45.84"/><line class="cls-1" x1="44.31" y1="111.55" x2="44.31" y2="102.16"/><line class="cls-1" x1="31.8" y1="111.55" x2="31.8" y2="102.16"/></svg>
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 796 B |
1
build/assets/achievements/oneExtension.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M78.83,86.22h-3.92l-10.35,8.16-10.35-8.16h-4.2c-11.6,0-21,7.72-21,17.25v3.45H99.83v-3.45c0-9.52-9.4-17.25-21-17.25Z"/><line class="cls-1" x1="64.55" y1="94.38" x2="64.55" y2="106.91"/><line class="cls-1" x1="54.2" y1="86.22" x2="54.2" y2="78.18"/><line class="cls-1" x1="74.9" y1="86.22" x2="74.9" y2="78.18"/><line class="cls-1" x1="45.07" y1="66.74" x2="45.07" y2="54.89"/><line class="cls-1" x1="64.55" y1="48.49" x2="45.07" y2="54.89"/><line class="cls-1" x1="64.55" y1="72.05" x2="45.07" y2="66.74"/><line class="cls-1" x1="51.75" y1="58.02" x2="51.75" y2="62.65"/><line class="cls-1" x1="58.01" y1="56.66" x2="58.01" y2="64.15"/><line class="cls-1" x1="84.03" y1="66.74" x2="84.03" y2="54.89"/><line class="cls-1" x1="64.55" y1="48.49" x2="84.03" y2="54.89"/><line class="cls-1" x1="64.55" y1="72.05" x2="84.03" y2="66.74"/><line class="cls-1" x1="77.36" y1="58.02" x2="77.36" y2="62.65"/><line class="cls-1" x1="71.09" y1="56.66" x2="71.09" y2="64.15"/><line class="cls-1" x1="64.55" y1="54.16" x2="64.55" y2="66.51"/><path class="cls-1" d="M48.41,53.8c1.56-6.47,5.39-11.64,10.27-13.95,1.79-.85,3.73-1.31,5.74-1.31,4.42,0,8.45,2.22,11.44,5.86,2.08,2.51,3.66,5.7,4.55,9.3"/><path class="cls-1" d="M48.63,67.71c2.32,8.41,8.51,14.42,15.79,14.42s13.43-5.98,15.77-14.34"/><path class="cls-1" d="M58.68,39.85c-.9-2.01-1.27-4.22-.8-6.34,.95-4.24,5-7.16,9.17-8.38s8.59-1.15,12.9-1.75c4.3-.59,8.8-2.04,11.47-5.46-.21,7.17-4.64,14.05-11.07,17.21,4.72-.51,8.59-.81,13.36-2.72-1.49,12.03-11.03,11.64-17.85,11.99"/><line class="cls-1" x1="45.07" y1="106.91" x2="37.96" y2="89.34"/><line class="cls-1" x1="83.24" y1="106.91" x2="90.36" y2="89.34"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
1
build/assets/achievements/workOnWeekends.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M65.9,32.74c5.31,0,9.61-4.3,9.61-9.61s-4.3-9.61-9.61-9.61-9.61,4.3-9.61,9.61,4.3,9.61,9.61,9.61Z"/><path class="cls-1" d="M57.65,73.18h-20.24c.17-7.24,2.05-21.06,12.97-29.25l6.2,12.6c.51,1.03,1.39,1.76,2.39,2.12v.25h25.03c2.34,0,4.24-1.9,4.24-4.24s-1.9-4.24-4.24-4.24h-19.5l-7.98-16.2c-.03-.06-.07-.11-.1-.16-.14-.44-.33-.87-.57-1.29-1.85-3.24-5.97-4.36-9.2-2.51-26.12,14.92-22.68,47.81-22.52,49.2,.11,.97,.43,1.87,.9,2.66,.69,2.19,2.71,3.78,5.13,3.78h21.18v22.76c0,3.67,2.97,6.65,6.65,6.65s6.65-2.98,6.65-6.65v-28.51c0-3.85-3.12-6.98-6.98-6.98Z"/><path class="cls-1" d="M104.68,50.51c.68-1.58-.06-3.41-1.64-4.09-1.58-.67-3.41,.06-4.09,1.64l-6.69,15.64h-16.75c-1.72,0-3.11,1.39-3.11,3.11s1.39,3.11,3.11,3.11h18.8c.19,0,.38-.02,.56-.06,.05,0,.11-.02,.16-.03,.13-.03,.26-.07,.39-.12,.05-.02,.1-.03,.14-.05,.16-.07,.32-.16,.47-.25,.03-.02,.06-.04,.09-.07,.12-.08,.23-.18,.34-.28,.04-.04,.08-.08,.11-.12,.09-.1,.18-.2,.26-.31,.03-.04,.06-.08,.09-.13,.1-.15,.19-.31,.26-.47h0v-.02l7.49-17.51Z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
BIN
build/assets/games/4x3.png
Normal file
After Width: | Height: | Size: 143 B |
BIN
build/assets/sponsor/halo.png
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
build/assets/sponsor/money.jpg
Normal file
After Width: | Height: | Size: 153 KiB |
BIN
build/social/tg.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
build/social/vk.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
build/social/youtube.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
1
public/assets/achievements/fileRush.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><line class="cls-1" x1="63.03" y1="22.12" x2="65.8" y2="22.12"/><g><line class="cls-1" x1="56.91" y1="32.09" x2="59.68" y2="32.09"/><line class="cls-1" x1="69.15" y1="32.09" x2="71.92" y2="32.09"/></g><g><line class="cls-1" x1="50.79" y1="42.06" x2="53.56" y2="42.06"/><line class="cls-1" x1="63.03" y1="42.06" x2="65.8" y2="42.06"/></g><line class="cls-1" x1="75.27" y1="42.06" x2="78.04" y2="42.06"/><g><g><line class="cls-1" x1="44.67" y1="52.02" x2="47.44" y2="52.02"/><line class="cls-1" x1="56.91" y1="52.02" x2="59.68" y2="52.02"/></g><line class="cls-1" x1="69.15" y1="52.02" x2="71.92" y2="52.02"/><g><line class="cls-1" x1="69.15" y1="52.02" x2="71.92" y2="52.02"/><line class="cls-1" x1="81.39" y1="52.02" x2="84.16" y2="52.02"/></g><g><line class="cls-1" x1="38.55" y1="61.99" x2="41.32" y2="61.99"/><line class="cls-1" x1="50.79" y1="61.99" x2="53.56" y2="61.99"/></g><line class="cls-1" x1="63.03" y1="61.99" x2="65.8" y2="61.99"/><g><line class="cls-1" x1="63.03" y1="61.99" x2="65.8" y2="61.99"/><line class="cls-1" x1="75.27" y1="61.99" x2="78.04" y2="61.99"/></g><line class="cls-1" x1="87.51" y1="61.99" x2="90.28" y2="61.99"/></g><g><g><line class="cls-1" x1="32.43" y1="71.96" x2="35.2" y2="71.96"/><line class="cls-1" x1="44.67" y1="71.96" x2="47.44" y2="71.96"/></g><line class="cls-1" x1="56.91" y1="71.96" x2="59.68" y2="71.96"/><g><line class="cls-1" x1="56.91" y1="71.96" x2="59.68" y2="71.96"/><line class="cls-1" x1="69.15" y1="71.96" x2="71.92" y2="71.96"/></g><g><line class="cls-1" x1="81.39" y1="71.96" x2="84.16" y2="71.96"/><line class="cls-1" x1="93.63" y1="71.96" x2="96.4" y2="71.96"/></g></g><g><g><line class="cls-1" x1="26.31" y1="81.82" x2="29.09" y2="81.82"/><line class="cls-1" x1="38.55" y1="81.82" x2="41.32" y2="81.82"/></g><line class="cls-1" x1="50.79" y1="81.82" x2="53.56" y2="81.82"/><g><line class="cls-1" x1="50.79" y1="81.82" x2="53.56" y2="81.82"/><line class="cls-1" x1="63.03" y1="81.82" x2="65.8" y2="81.82"/></g><g><line class="cls-1" x1="75.27" y1="81.82" x2="78.04" y2="81.82"/><line class="cls-1" x1="87.51" y1="81.82" x2="90.28" y2="81.82"/></g><g><line class="cls-1" x1="87.51" y1="81.82" x2="90.28" y2="81.82"/><line class="cls-1" x1="99.74" y1="81.82" x2="102.52" y2="81.82"/></g></g><g><g><line class="cls-1" x1="20.19" y1="91.67" x2="22.97" y2="91.67"/><line class="cls-1" x1="32.43" y1="91.67" x2="35.2" y2="91.67"/></g><line class="cls-1" x1="44.67" y1="91.67" x2="47.44" y2="91.67"/><g><line class="cls-1" x1="44.67" y1="91.67" x2="47.44" y2="91.67"/><line class="cls-1" x1="56.91" y1="91.67" x2="59.68" y2="91.67"/></g><g><line class="cls-1" x1="69.15" y1="91.67" x2="71.92" y2="91.67"/><line class="cls-1" x1="81.39" y1="91.67" x2="84.16" y2="91.67"/></g><g><line class="cls-1" x1="81.39" y1="91.67" x2="84.16" y2="91.67"/><line class="cls-1" x1="93.63" y1="91.67" x2="96.4" y2="91.67"/></g><g><line class="cls-1" x1="81.39" y1="91.67" x2="84.16" y2="91.67"/><line class="cls-1" x1="93.63" y1="91.67" x2="96.4" y2="91.67"/></g><g><line class="cls-1" x1="93.63" y1="91.67" x2="96.4" y2="91.67"/><line class="cls-1" x1="105.86" y1="91.67" x2="108.64" y2="91.67"/></g></g></svg>
|
After Width: | Height: | Size: 3.3 KiB |
1
public/assets/achievements/longestMessage.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><g><path class="cls-1" d="M71.77,79.68v4.64c0,1.54,.83,2.93,2.17,3.94s3.19,1.63,5.22,1.63c11.11,0,20.12,6.79,20.12,15.16v1.05H25.43v-1.05c0-4.18,2.25-7.97,5.89-10.72,3.64-2.74,8.67-4.44,14.22-4.44,4.08,0,7.39-2.49,7.39-5.57v-4.64"/><line class="cls-1" x1="62.35" y1="106.09" x2="45.54" y2="89.88"/><line class="cls-1" x1="62.35" y1="106.09" x2="79.16" y2="89.88"/><path class="cls-1" d="M41.47,55.08s-.01,.02,0,.03c-.01,.2-.01,.41-.01,.62,0,10.47,4.66,19.54,11.46,23.95h0c2.83,1.85,6.03,2.89,9.42,2.89s6.59-1.04,9.42-2.88h0c3.35-2.18,6.19-5.49,8.19-9.55"/><path class="cls-1" d="M83.23,55.54h0v.2c0,1.81-.14,3.59-.41,5.3"/><path class="cls-1" d="M46.87,37.72c3.81,2.65,9.34,4.32,15.48,4.32s11.66-1.67,15.48-4.32"/><path class="cls-1" d="M47.07,37.65l-5.52,17.89c-2.1-10.03-2.47-23.03,9.52-27.59,3.08-1.17,5.69-4.15,6.13-7.42,1.48,.93,2.21,2.62,2.24,4.36,2.65-1.17,4.51-3.96,4.59-6.85,2.11,1.9,3.47,4.6,3.85,7.41,2.18,.37,4.46-1.11,5.09-3.22,1.23,1.62,1.85,3.68,1.7,5.71,9.11,4.38,11.22,14.09,8.55,27.79"/><path class="cls-1" d="M77.71,37.65l5.52,17.89"/><path class="cls-1" d="M68.63,72.31c-1.66,1.47-3.87,2.36-6.27,2.36s-4.39-.81-6.03-2.17c-2.1-1.73-3.43-4.35-3.43-7.29"/><line class="cls-1" x1="65.09" y1="65.21" x2="52.89" y2="65.21"/><path class="cls-1" d="M40.49,47.7c-3.08,.64-5.45,4.3-5.45,8.7,0,4.86,2.88,8.8,6.42,8.8,.44,0,.87-.06,1.29-.18"/><path class="cls-1" d="M84.26,47.72c3.06,.67,5.4,4.31,5.4,8.69,0,2.18-.59,4.18-1.55,5.72"/><path class="cls-1" d="M68.63,72.31c5.78-3.46,11.46-3.3,18.42-1.75,4.26,.94,8.67,1.68,12.96,1.36s8.64-2.48,9.46-6.13c.31-1.38-.45-2.82-1.88-3.61-2.12,2.46-6.26,2.94-9.88,2.29-2.54-.46-6.32-1.52-9.6-2.34-1.4-.35-2.72-.66-3.8-.86-.5-.09-1-.17-1.49-.23-11.67-1.53-19.78,4.81-26.49,11.46"/></g><line class="cls-1" x1="53.38" y1="51.9" x2="55.02" y2="51.9"/><line class="cls-1" x1="69.44" y1="51.9" x2="71.08" y2="51.9"/></svg>
|
After Width: | Height: | Size: 2.1 KiB |
1
public/assets/achievements/moreAddedFolders.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M100.86,100.16H34.28c-1.79,0-3.4-.78-4.64-2.06-1.44-1.5-2.34-3.68-2.34-6.11V41.04c0-4.52,3.13-8.17,6.98-8.17h18.16c3.85,0,6.98,3.65,6.98,8.17v3.27h35.4c3.33,0,6.03,3.16,6.03,7.06v11.74"/><path class="cls-1" d="M29.64,98.1l7.87-28.89c.99-3.64,3.88-6.1,7.13-6.1h62.9c2.51,0,4.25,2.93,3.37,5.67l-10.06,31.38"/></svg>
|
After Width: | Height: | Size: 576 B |
1
public/assets/achievements/moreLintHint.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><polygon class="cls-1" points="64.42 103.1 25.73 64.42 64.42 25.73 72.27 33.59 41.44 64.42 64.42 87.38 87.39 64.42 79.83 56.86 64.42 72.27 56.56 64.42 79.83 41.14 103.1 64.42 64.42 103.1"/></svg>
|
After Width: | Height: | Size: 435 B |
1
public/assets/achievements/moreOnHoliday.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><circle class="cls-1" cx="64.41" cy="64.41" r="44"/><path class="cls-1" d="M36.93,62.7c-1.51-4.93,.37-10.77,4.27-14.1,2.91-2.48,7.05-3.79,10.78-2.83,1.17,.3,2.29,.79,3.3,1.46,4.13,2.73,5.83,7.87,3.89,12.44-2.16,5.11-9.05,8.03-13.65,4.13-2.09-1.77-3.11-4.68-2.53-7.36,.75-3.44,4.23-6.57,7.89-5.66,2.44,.61,4.54,3.03,4.16,5.61-.35,2.38-2.52,4.31-4.94,4.3-.85,0-1.73-.24-2.35-.82-1.71-1.58-.15-4.54,2.08-4.26"/><path class="cls-1" d="M92.65,48.88c1.51,4.93-.37,10.77-4.27,14.1-2.91,2.48-7.05,3.79-10.78,2.83-1.17-.3-2.29-.79-3.3-1.46-4.13-2.73-5.83-7.87-3.89-12.44,2.16-5.11,9.05-8.03,13.65-4.13,2.09,1.77,3.11,4.68,2.53,7.36-.75,3.44-4.23,6.57-7.89,5.66-2.44-.61-4.54-3.03-4.16-5.61,.35-2.38,2.52-4.31,4.94-4.3,.85,0,1.73,.24,2.35,.82,1.71,1.58,.15,4.54-2.08,4.26"/><polyline class="cls-1" points="42.2 79.85 50.25 85.82 59.67 79.55 69.59 85.62 77.91 79.24 86.63 84.81"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -1 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M87.76,83.68h-31.24c-1.42,0-2.58-1.16-2.58-2.59V25.78c0-1.43,1.16-2.58,2.58-2.58h28.66c1.43,0,2.58,1.15,2.58,2.58v57.78"/><rect class="cls-1" x="58.21" y="28.27" width="25.29" height="11.07"/><circle class="cls-1" cx="70.59" cy="55.52" r="10.36"/><line class="cls-1" x1="75.97" y1="46.65" x2="65.21" y2="64.38"/><rect class="cls-1" x="41.78" y="36.29" width="4.56" height="47.27"/><rect class="cls-1" x="31.52" y="36.29" width="4.56" height="47.27"/><line class="cls-1" x1="44.05" y1="36.18" x2="44.05" y2="26.49"/><line class="cls-1" x1="33.8" y1="36.18" x2="33.8" y2="26.49"/><path class="cls-1" d="M33.8,83.67c4.27,14.03,19.96,20.91,33.52,21.85,9.18,.64,20.36-1.54,26.11-9.38,3.76-5.11,5.05-11.97,2.65-17.88s-7.13-9.98-12.6-12.41"/><path class="cls-1" d="M43.94,83.67c6.29,19.8,55.84,17.23,41.44-8.77"/></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><rect class="cls-1" x="55.37" y="23.33" width="41.25" height="73.77" rx="3.49" ry="3.49"/><rect class="cls-1" x="60.58" y="29.51" width="30.84" height="13.51"/><circle class="cls-1" cx="75.69" cy="62.75" r="12.64"/><line class="cls-1" x1="82.24" y1="51.94" x2="69.13" y2="73.55"/><rect class="cls-1" x="41.53" y="55.66" width="5.56" height="45.84"/><rect class="cls-1" x="29.02" y="55.66" width="5.56" height="45.84"/><line class="cls-1" x1="44.31" y1="111.55" x2="44.31" y2="102.16"/><line class="cls-1" x1="31.8" y1="111.55" x2="31.8" y2="102.16"/></svg>
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 796 B |
1
public/assets/achievements/oneExtension.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M78.83,86.22h-3.92l-10.35,8.16-10.35-8.16h-4.2c-11.6,0-21,7.72-21,17.25v3.45H99.83v-3.45c0-9.52-9.4-17.25-21-17.25Z"/><line class="cls-1" x1="64.55" y1="94.38" x2="64.55" y2="106.91"/><line class="cls-1" x1="54.2" y1="86.22" x2="54.2" y2="78.18"/><line class="cls-1" x1="74.9" y1="86.22" x2="74.9" y2="78.18"/><line class="cls-1" x1="45.07" y1="66.74" x2="45.07" y2="54.89"/><line class="cls-1" x1="64.55" y1="48.49" x2="45.07" y2="54.89"/><line class="cls-1" x1="64.55" y1="72.05" x2="45.07" y2="66.74"/><line class="cls-1" x1="51.75" y1="58.02" x2="51.75" y2="62.65"/><line class="cls-1" x1="58.01" y1="56.66" x2="58.01" y2="64.15"/><line class="cls-1" x1="84.03" y1="66.74" x2="84.03" y2="54.89"/><line class="cls-1" x1="64.55" y1="48.49" x2="84.03" y2="54.89"/><line class="cls-1" x1="64.55" y1="72.05" x2="84.03" y2="66.74"/><line class="cls-1" x1="77.36" y1="58.02" x2="77.36" y2="62.65"/><line class="cls-1" x1="71.09" y1="56.66" x2="71.09" y2="64.15"/><line class="cls-1" x1="64.55" y1="54.16" x2="64.55" y2="66.51"/><path class="cls-1" d="M48.41,53.8c1.56-6.47,5.39-11.64,10.27-13.95,1.79-.85,3.73-1.31,5.74-1.31,4.42,0,8.45,2.22,11.44,5.86,2.08,2.51,3.66,5.7,4.55,9.3"/><path class="cls-1" d="M48.63,67.71c2.32,8.41,8.51,14.42,15.79,14.42s13.43-5.98,15.77-14.34"/><path class="cls-1" d="M58.68,39.85c-.9-2.01-1.27-4.22-.8-6.34,.95-4.24,5-7.16,9.17-8.38s8.59-1.15,12.9-1.75c4.3-.59,8.8-2.04,11.47-5.46-.21,7.17-4.64,14.05-11.07,17.21,4.72-.51,8.59-.81,13.36-2.72-1.49,12.03-11.03,11.64-17.85,11.99"/><line class="cls-1" x1="45.07" y1="106.91" x2="37.96" y2="89.34"/><line class="cls-1" x1="83.24" y1="106.91" x2="90.36" y2="89.34"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
1
public/assets/achievements/workOnWeekends.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M65.9,32.74c5.31,0,9.61-4.3,9.61-9.61s-4.3-9.61-9.61-9.61-9.61,4.3-9.61,9.61,4.3,9.61,9.61,9.61Z"/><path class="cls-1" d="M57.65,73.18h-20.24c.17-7.24,2.05-21.06,12.97-29.25l6.2,12.6c.51,1.03,1.39,1.76,2.39,2.12v.25h25.03c2.34,0,4.24-1.9,4.24-4.24s-1.9-4.24-4.24-4.24h-19.5l-7.98-16.2c-.03-.06-.07-.11-.1-.16-.14-.44-.33-.87-.57-1.29-1.85-3.24-5.97-4.36-9.2-2.51-26.12,14.92-22.68,47.81-22.52,49.2,.11,.97,.43,1.87,.9,2.66,.69,2.19,2.71,3.78,5.13,3.78h21.18v22.76c0,3.67,2.97,6.65,6.65,6.65s6.65-2.98,6.65-6.65v-28.51c0-3.85-3.12-6.98-6.98-6.98Z"/><path class="cls-1" d="M104.68,50.51c.68-1.58-.06-3.41-1.64-4.09-1.58-.67-3.41,.06-4.09,1.64l-6.69,15.64h-16.75c-1.72,0-3.11,1.39-3.11,3.11s1.39,3.11,3.11,3.11h18.8c.19,0,.38-.02,.56-.06,.05,0,.11-.02,.16-.03,.13-.03,.26-.07,.39-.12,.05-.02,.1-.03,.14-.05,.16-.07,.32-.16,.47-.25,.03-.02,.06-.04,.09-.07,.12-.08,.23-.18,.34-.28,.04-.04,.08-.08,.11-.12,.09-.1,.18-.2,.26-.31,.03-.04,.06-.08,.09-.13,.1-.15,.19-.31,.26-.47h0v-.02l7.49-17.51Z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/assets/games/4x3.png
Normal file
After Width: | Height: | Size: 143 B |
BIN
public/assets/games/citybuilder.jpg
Normal file
After Width: | Height: | Size: 400 KiB |
BIN
public/assets/games/gameconsole.jpg
Normal file
After Width: | Height: | Size: 127 KiB |
BIN
public/assets/games/quize.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
public/assets/games/races.jpg
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
public/assets/games/wheel.jpg
Normal file
After Width: | Height: | Size: 223 KiB |
BIN
public/assets/sponsor/halo.png
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
public/assets/sponsor/money.jpg
Normal file
After Width: | Height: | Size: 153 KiB |
BIN
public/social/tg.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
public/social/vk.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/social/youtube.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
|
@ -23,7 +23,3 @@ body,
|
|||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
|
|
@ -25,4 +25,11 @@
|
|||
background-repeat: no-repeat;
|
||||
background-size: 180% auto;
|
||||
background-position: center center;
|
||||
|
||||
&_icon {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
margin: 0 var(--space-xs) 0 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,15 +16,15 @@ function Banner({ className }: IBannerProps) {
|
|||
const {
|
||||
ref,
|
||||
link,
|
||||
title,
|
||||
banner,
|
||||
bannerText,
|
||||
text,
|
||||
textIcon,
|
||||
color,
|
||||
backgroundColor,
|
||||
} = config;
|
||||
|
||||
const props = {
|
||||
title,
|
||||
title: text,
|
||||
to: link || '',
|
||||
target: '_blank',
|
||||
className,
|
||||
|
@ -43,7 +43,6 @@ function Banner({ className }: IBannerProps) {
|
|||
);
|
||||
}
|
||||
|
||||
if (!banner) {
|
||||
const textFromRef = (ref || '').split('_').splice(1).join(' ').toUpperCase();
|
||||
const background = backgroundColor
|
||||
? backgroundColor
|
||||
|
@ -52,20 +51,23 @@ function Banner({ className }: IBannerProps) {
|
|||
return (
|
||||
<Link {...props}>
|
||||
<div
|
||||
title={title}
|
||||
title={text}
|
||||
className={style.banner}
|
||||
style={{
|
||||
color: color,
|
||||
color,
|
||||
background,
|
||||
}}
|
||||
>
|
||||
{bannerText || textFromRef || ''}
|
||||
{textIcon ? (
|
||||
<img
|
||||
src={textIcon}
|
||||
className={style.banner_icon}
|
||||
/>
|
||||
) : null}
|
||||
{text || textFromRef || ''}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default Banner;
|
||||
|
|
|
@ -4,6 +4,8 @@ import IHashMap from 'ts/interfaces/HashMap';
|
|||
import Description from 'ts/components/Description';
|
||||
import ShowSymbol from 'ts/components/ShowSymbol';
|
||||
import { shuffle } from 'ts/helpers/random';
|
||||
import GameBanner from 'ts/components/GameBanner';
|
||||
import styleBanner from 'ts/components/GameBanner/index.module.scss';
|
||||
|
||||
import CityMap from './components/CityMap';
|
||||
import style from './style/wrapper.module.scss';
|
||||
|
@ -48,14 +50,16 @@ function CityBuilder({
|
|||
|
||||
return (
|
||||
<>
|
||||
<GameBanner src="./assets/games/citybuilder.jpg">
|
||||
<ShowSymbol
|
||||
text={selected.title}
|
||||
length={20}
|
||||
/>
|
||||
<Description
|
||||
className={style.city_builder_description}
|
||||
className={styleBanner.game_banner_text}
|
||||
text={`Сейчас в проекте есть ${selected.value || 0} файлов созданных этим пользователем. Это примерно ${percent}% от всех файлов в проекте.`}
|
||||
/>
|
||||
</GameBanner>
|
||||
<div className={style.city_builder_control}>
|
||||
<button
|
||||
disabled={!selectedIndex}
|
||||
|
|
36
src/ts/components/CityBuilder/style/banner.module.scss
Normal file
|
@ -0,0 +1,36 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.city_builder_banner {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
margin: var(--space-xxl) auto 0;
|
||||
|
||||
user-select: none;
|
||||
|
||||
background-size: auto 100%;
|
||||
background-repeat: repeat;
|
||||
background-position: top left;
|
||||
|
||||
&_description {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0 var(--space-xxl);
|
||||
}
|
||||
|
||||
&_description {
|
||||
bottom: 0;
|
||||
padding-top: var(--space-xxl);
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
&_text {
|
||||
margin: var(--space-s) auto;
|
||||
color: white;
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
.city_builder_control {
|
||||
position: relative;
|
||||
margin: var(--space-xxl) auto;
|
||||
margin: 0 auto var(--space-xxl);
|
||||
user-select: none;
|
||||
background-color: var(--color-13);
|
||||
|
||||
|
@ -36,10 +36,6 @@
|
|||
background-size: auto 30%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
&_prev {
|
||||
|
|
|
@ -3,22 +3,10 @@ import React, { ReactNode } from 'react';
|
|||
import Button from 'ts/components/UiKit/components/Button';
|
||||
import notificationsStore from 'ts/components/Notifications/store';
|
||||
import localization from 'ts/helpers/Localization';
|
||||
import copyInBuffer from 'ts/helpers/copyInBuffer';
|
||||
|
||||
import style from './index.module.scss';
|
||||
|
||||
function copyInBuffer(value?: string) {
|
||||
if (!value) return;
|
||||
const copyTextarea = document.createElement('textarea');
|
||||
copyTextarea.style.position = 'fixed';
|
||||
copyTextarea.style.opacity = '0';
|
||||
copyTextarea.textContent = value;
|
||||
|
||||
document.body.appendChild(copyTextarea);
|
||||
copyTextarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(copyTextarea);
|
||||
}
|
||||
|
||||
interface IConsoleProps {
|
||||
textForCopy?: string;
|
||||
className?: string;
|
||||
|
@ -36,6 +24,7 @@ function Console({ className, textForCopy, children }: IConsoleProps) {
|
|||
<div className={`${style.console_body}`}>
|
||||
{children || textForCopy}
|
||||
</div>
|
||||
{textForCopy ? (
|
||||
<Button
|
||||
mode="second"
|
||||
className={`${style.console_copy}`}
|
||||
|
@ -46,6 +35,7 @@ function Console({ className, textForCopy, children }: IConsoleProps) {
|
|||
>
|
||||
{localization.get('uiKit.console.button')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ function getTextWithLink(text: string, className?: string) {
|
|||
return (<>{parts}</>) ;
|
||||
}
|
||||
|
||||
function getTextWithStyle(text: string, className?: string) {
|
||||
export function getTextWithStyle(text: string, className?: string) {
|
||||
const parts = (text || '')
|
||||
.split('*')
|
||||
.map((value: string, index: number) => (index % 2
|
||||
|
|
|
@ -52,6 +52,7 @@ export function getOnDrop(setLoading: Function, onChange: Function) {
|
|||
.map((file: any) => file.kind === 'file' ? file?.getAsFile() : null)
|
||||
.filter(file => file);
|
||||
|
||||
console.log(files);
|
||||
setLoading(false);
|
||||
if (!files.length) return;
|
||||
|
||||
|
|
33
src/ts/components/GameBanner/index.module.scss
Normal file
|
@ -0,0 +1,33 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.game_banner {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
margin: var(--space-xxl) auto 0;
|
||||
|
||||
user-select: none;
|
||||
|
||||
background-size: auto 100%;
|
||||
background-repeat: repeat;
|
||||
background-position: top left;
|
||||
|
||||
&_description {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--space-xxl) var(--space-xxl) 0;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
&_text {
|
||||
margin: var(--space-s) auto;
|
||||
color: white;
|
||||
}
|
||||
}
|
30
src/ts/components/GameBanner/index.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
|
||||
import style from './index.module.scss';
|
||||
|
||||
interface IGameBannerProps {
|
||||
src?: string;
|
||||
children?: ReactNode | string | null;
|
||||
}
|
||||
|
||||
function GameBanner({
|
||||
src,
|
||||
children,
|
||||
}: IGameBannerProps): React.ReactElement | null {
|
||||
if (!src) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={style.game_banner}
|
||||
style={{ backgroundImage: `url(${src})` }}
|
||||
>
|
||||
{children ? (
|
||||
<div className={style.game_banner_description}>
|
||||
{children}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GameBanner;
|
53
src/ts/components/GameConsole/components/marquee.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import style from '../styles/index.module.scss';
|
||||
|
||||
const SOURCE_CODE = `JIRA-1227 fix(profile): change validation
|
||||
Counting objects: 100% (22/22), done.
|
||||
Delta compression using up to 8 threads
|
||||
JIRA-323 fix: change validation
|
||||
Writing objects: 100% (12/12), 1.88 KiB | 1.88 MiB/s, done.
|
||||
Total 12 (delta 9), reused 0 (delta 0), pack-reused 0
|
||||
`.split('\n');
|
||||
|
||||
function getRandomText(index: number, messages?: string[]) {
|
||||
const source = messages || SOURCE_CODE;
|
||||
const splitIndex = index % source.length;
|
||||
const start = source.slice(0, splitIndex);
|
||||
const end = source.slice(splitIndex);
|
||||
return [...end, ...start]
|
||||
.slice(0, 13)
|
||||
.map((text: string) => (<p key={text}>{text}</p>));
|
||||
}
|
||||
|
||||
interface IMarqueeProps {
|
||||
commitsInDay: number;
|
||||
messages?: string[];
|
||||
}
|
||||
|
||||
function Marquee({
|
||||
commitsInDay,
|
||||
messages,
|
||||
}: IMarqueeProps) {
|
||||
const [index, setIndex] = useState<number>(0);
|
||||
const text = getRandomText(index, messages);
|
||||
|
||||
useEffect(() => {
|
||||
const delay = 3000 * 6;
|
||||
const speed = Math.ceil(delay / (commitsInDay || 1));
|
||||
const timer = setInterval(() => {
|
||||
setIndex((value: number) => value + 1);
|
||||
}, speed);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [commitsInDay]);
|
||||
|
||||
return (
|
||||
<div className={style.game_console_monitor}>
|
||||
<p id={`game-console-text-${index}`}>{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Marquee;
|
57
src/ts/components/GameConsole/index.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import React, { useState } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import dataGripStore from 'ts/store/DataGrip';
|
||||
import SelectWithButtons from 'ts/components/UiKit/components/SelectWithButtons';
|
||||
|
||||
import Marquee from './components/marquee';
|
||||
|
||||
import style from './styles/index.module.scss';
|
||||
|
||||
|
||||
function getSpeedAndMessages(user: string) {
|
||||
const byTimestamp = dataGripStore.dataGrip.timestamp.statisticByAuthor[user];
|
||||
const commitsInDay = byTimestamp.commitsByTimestampCounter.max;
|
||||
|
||||
const startIndex = byTimestamp.allCommitsByTimestamp.length - 20;
|
||||
const messages = byTimestamp.allCommitsByTimestamp.slice(startIndex)
|
||||
.reduce((acc: string[], commit: any) => acc.concat(commit.messages), []);
|
||||
|
||||
return {
|
||||
speed: commitsInDay,
|
||||
messages: Array.from(new Set(messages)) as string[],
|
||||
};
|
||||
}
|
||||
|
||||
const GameConsole = observer((): React.ReactElement => {
|
||||
const employment = dataGripStore.dataGrip.author.employment;
|
||||
const authors = [
|
||||
...employment.active,
|
||||
...employment.dismissed,
|
||||
].map((title: string) => ({ id: title, title }));
|
||||
|
||||
const [user, setUser] = useState<any>(authors[0].id);
|
||||
|
||||
const { speed, messages } = getSpeedAndMessages(user);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={style.game_console}
|
||||
>
|
||||
<Marquee
|
||||
commitsInDay={speed}
|
||||
messages={messages}
|
||||
/>
|
||||
<SelectWithButtons
|
||||
value={user}
|
||||
options={authors}
|
||||
className={style.game_console_select}
|
||||
onChange={(newUser: any) => {
|
||||
setUser(newUser?.id || newUser);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default GameConsole;
|
39
src/ts/components/GameConsole/styles/index.module.scss
Normal file
|
@ -0,0 +1,39 @@
|
|||
.game_console {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
margin: 0 auto var(--space-xxl);
|
||||
|
||||
text-align: left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: auto 100%;
|
||||
background-position: top left;
|
||||
|
||||
&_select {
|
||||
display: block;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
&_monitor {
|
||||
font-size: var(--font-s);
|
||||
font-weight: 100;
|
||||
|
||||
display: block;
|
||||
width: 500px;
|
||||
height: 250px;
|
||||
padding: 8px;
|
||||
margin: 0 0 var(--space-xxl);
|
||||
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.3;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
border-radius: var(--border-radius-s);
|
||||
|
||||
border: 4px solid var(--color-32);
|
||||
box-shadow: 0 0 10px var(--color-black);
|
||||
color: #00B200;
|
||||
background-color: var(--color-black);
|
||||
}
|
||||
}
|
51
src/ts/components/Locker/index.module.scss
Normal file
|
@ -0,0 +1,51 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.locker {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
&_icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&_border {
|
||||
fill: none;
|
||||
stroke: var(--color-border);
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
&_center {
|
||||
fill: white;
|
||||
stroke: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
&_sector {
|
||||
fill: none;
|
||||
stroke: var(--color-grey);
|
||||
stroke-width: 50px;
|
||||
transition: stroke-dasharray 1s;
|
||||
}
|
||||
|
||||
&_text {
|
||||
font-size: var(--font-s);
|
||||
font-weight: 100;
|
||||
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 0;
|
||||
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: var(--color-black);
|
||||
}
|
||||
}
|
84
src/ts/components/Locker/index.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import style from './index.module.scss';
|
||||
|
||||
interface ILockerProps {
|
||||
delay: number;
|
||||
callback?: Function;
|
||||
className?: string;
|
||||
sectorClassName?: string;
|
||||
borderClassName?: string;
|
||||
}
|
||||
|
||||
function Locker({
|
||||
delay,
|
||||
callback,
|
||||
className,
|
||||
sectorClassName,
|
||||
borderClassName,
|
||||
}: ILockerProps): React.ReactElement | null {
|
||||
const [dash, setDash] = useState<string>('');
|
||||
const [value, setValue] = useState<number>(delay);
|
||||
|
||||
useEffect(() => {
|
||||
const percent = Math.round(100 - (value * 100 / delay));
|
||||
const strokeLength = Math.PI * 49.5;
|
||||
const newDash = (strokeLength / 100) * percent;
|
||||
setDash(`${newDash}, ${strokeLength}`);
|
||||
|
||||
if (percent >= 99) {
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setValue((next: number) => next - 1);
|
||||
}, 1000);
|
||||
}, [value]);
|
||||
|
||||
if (!delay || !value) return null;
|
||||
|
||||
return (
|
||||
<div className={`${style.locker} ${className || ''}`}>
|
||||
<svg
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={style.locker_icon}
|
||||
>
|
||||
<circle
|
||||
r="25"
|
||||
cx="50"
|
||||
cy="50"
|
||||
style={{ strokeDasharray: dash }}
|
||||
className={`${style.locker_sector} ${sectorClassName || ''}`}
|
||||
/>
|
||||
<circle
|
||||
r="49.5"
|
||||
cx="50"
|
||||
cy="50"
|
||||
className={`${style.locker_border} ${borderClassName || ''}`}
|
||||
/>
|
||||
<circle
|
||||
r="40"
|
||||
cx="50"
|
||||
cy="50"
|
||||
className={`${style.locker_center} ${borderClassName || ''}`}
|
||||
/>
|
||||
</svg>
|
||||
<p className={style.locker_text}>
|
||||
{value}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Locker.defaultProps = {
|
||||
delay: 60,
|
||||
callback: undefined,
|
||||
className: '',
|
||||
sectorClassName: '',
|
||||
borderClassName: '',
|
||||
};
|
||||
|
||||
export default Locker;
|
|
@ -1,26 +1,33 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import Locker from 'ts/components/Locker';
|
||||
|
||||
import style from '../styles/index.module.scss';
|
||||
|
||||
interface IHeaderProps {
|
||||
id?: string,
|
||||
className?: string,
|
||||
onClose?: Function,
|
||||
id?: string;
|
||||
delay?: number;
|
||||
className?: string;
|
||||
onClose?: Function;
|
||||
setCanClose?: Function;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
const Header = observer(({
|
||||
id,
|
||||
delay,
|
||||
className,
|
||||
children,
|
||||
onClose,
|
||||
setCanClose,
|
||||
}: IHeaderProps) => (
|
||||
<div
|
||||
id={`${id || ''}-title`}
|
||||
className={`${style.modal_window_title} ${className || ''}`}
|
||||
>
|
||||
{children}
|
||||
|
||||
{onClose ? (
|
||||
<img
|
||||
id={`${id}-close`}
|
||||
|
@ -32,6 +39,16 @@ const Header = observer(({
|
|||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{delay ? (
|
||||
<Locker
|
||||
delay={delay}
|
||||
className={style.modal_window_locker}
|
||||
callback={() => {
|
||||
if (setCanClose) setCanClose(true);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
));
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactNode, useEffect } from 'react';
|
||||
import React, { ReactNode, useEffect, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import isMobile from 'ts/helpers/isMobile';
|
||||
|
@ -11,6 +11,8 @@ import style from './styles/index.module.scss';
|
|||
|
||||
interface IModalProps {
|
||||
id?: string,
|
||||
delay?: number;
|
||||
mode?: string | string[],
|
||||
className?: string,
|
||||
onClose?: Function,
|
||||
children?: ReactNode;
|
||||
|
@ -18,16 +20,20 @@ interface IModalProps {
|
|||
|
||||
function Modal({
|
||||
id,
|
||||
mode,
|
||||
delay,
|
||||
className,
|
||||
onClose,
|
||||
children,
|
||||
}: IModalProps) {
|
||||
const [canClose, setCanClose] = useState<boolean>(!delay);
|
||||
|
||||
useEffect(globalScroll.useOnOff, []);
|
||||
|
||||
const childrenWithProps = React.Children.map(children, (child) => (React.isValidElement(child)
|
||||
? React.cloneElement(
|
||||
child, // @ts-ignore
|
||||
{ onClose },
|
||||
{ onClose, delay, setCanClose },
|
||||
) : child));
|
||||
|
||||
const customClass = isMobile
|
||||
|
@ -41,7 +47,7 @@ function Modal({
|
|||
onClick={(event: any) => {
|
||||
event.stopPropagation();
|
||||
if (event.target?.id !== `${id}-wrapper`) return;
|
||||
if (onClose) onClose();
|
||||
if (onClose && canClose) onClose();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
@ -51,6 +57,12 @@ function Modal({
|
|||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{mode === 'halo' ? (
|
||||
<img
|
||||
className={style.modal_window_halo}
|
||||
src="./assets/sponsor/halo.png"
|
||||
/>
|
||||
) : null}
|
||||
{childrenWithProps}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -61,6 +73,7 @@ Modal.displayName = 'Modal';
|
|||
|
||||
Modal.defaultProps = {
|
||||
id: 'modal-window',
|
||||
delay: undefined,
|
||||
className: '',
|
||||
onClose: undefined,
|
||||
children: undefined,
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
}
|
||||
|
||||
.modal_window {
|
||||
position: relative;
|
||||
|
||||
&_wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -44,6 +46,16 @@
|
|||
background-color: rgba(90, 90, 90, 0.2);
|
||||
}
|
||||
|
||||
&_halo {
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
left: -80px;
|
||||
z-index: -1;
|
||||
display: block;
|
||||
width: 560px;
|
||||
animation: modal_window_halo 40s linear 0s infinite normal none running;
|
||||
}
|
||||
|
||||
&_title,
|
||||
&_body,
|
||||
&_footer {
|
||||
|
@ -52,8 +64,6 @@
|
|||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
}
|
||||
|
@ -61,24 +71,30 @@
|
|||
&_title {
|
||||
position: relative;
|
||||
padding: 24px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
&_body {
|
||||
max-height: 60vh;
|
||||
padding: 0 24px;
|
||||
overflow: auto;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
&_footer {
|
||||
padding: 24px;
|
||||
text-align: right;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
&_locker,
|
||||
&_close {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
&_close {
|
||||
display: block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
@ -91,3 +107,20 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes modal_window_halo {
|
||||
from {
|
||||
transform: rotateZ(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
11% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
89% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
to {
|
||||
transform: rotateZ(360deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
position: fixed;
|
||||
top: 12px;
|
||||
right: 0;
|
||||
z-index: 4;
|
||||
display: inline-block;
|
||||
|
||||
&_item {
|
||||
|
|
51
src/ts/components/Quize/components/Answer.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
import React from 'react';
|
||||
|
||||
import IAnswer from '../interfaces/Answer';
|
||||
import style from '../styles/answer.module.scss';
|
||||
|
||||
const IS_WRAPPER_MODE = {
|
||||
error: true,
|
||||
small: true,
|
||||
};
|
||||
|
||||
interface IAnswerProps {
|
||||
answer: IAnswer;
|
||||
mode: string;
|
||||
onClick: Function;
|
||||
}
|
||||
|
||||
function Answer({
|
||||
answer,
|
||||
mode,
|
||||
onClick,
|
||||
}: IAnswerProps): React.ReactElement | null {
|
||||
const className = [style.quize_answer];
|
||||
if (mode === 'selected') className.push(style.quize_answer_selected);
|
||||
if (mode === 'correct') className.push(style.quize_answer_correct);
|
||||
if (mode === 'error') className.push(style.quize_answer_error);
|
||||
if (mode === 'small') className.push(style.quize_answer_small);
|
||||
|
||||
const wrapperClass = [style.quize_answer_wrapper];
|
||||
if (IS_WRAPPER_MODE[mode]) wrapperClass.push(style.quize_answer_wrapper_small);
|
||||
|
||||
return (
|
||||
<div className={wrapperClass.join(' ')}>
|
||||
<figure
|
||||
className={className.join(' ')}
|
||||
onClick={() => {
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={style.quize_answer_icon}
|
||||
src={answer.icon}
|
||||
/>
|
||||
<figcaption className={style.quize_answer_text}>
|
||||
{answer.title}
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Answer;
|
23
src/ts/components/Quize/components/Progress.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
|
||||
import style from '../styles/progress.module.scss';
|
||||
|
||||
interface IProgressProps {
|
||||
progress: number;
|
||||
}
|
||||
|
||||
function Progress({
|
||||
progress,
|
||||
}: IProgressProps): React.ReactElement | null {
|
||||
return (
|
||||
<div className={style.quize_progress}>
|
||||
<div className={style.quize_progress_line}>
|
||||
<div className={style.quize_progress_text}>
|
||||
{progress}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Progress;
|
103
src/ts/components/Quize/components/Question.tsx
Normal file
|
@ -0,0 +1,103 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import UiKitButton from 'ts/components/UiKit/components/Button';
|
||||
|
||||
import IQuestion from '../interfaces/Question';
|
||||
import IAnswer from '../interfaces/Answer';
|
||||
import Progress from './Progress';
|
||||
import Answer from './Answer';
|
||||
|
||||
import stylePage from '../styles/question.module.scss';
|
||||
import style from '../styles/index.module.scss';
|
||||
|
||||
function getModes(answers: IAnswer[], selected: IAnswer | null) {
|
||||
return (answers || []).map((answer: IAnswer) => {
|
||||
if (answer?.score) return 'correct';
|
||||
if (!answer?.score && answer === selected) return 'error';
|
||||
return 'small';
|
||||
});
|
||||
}
|
||||
|
||||
interface IQuestionProps {
|
||||
question: IQuestion;
|
||||
progress: number;
|
||||
onClick: Function;
|
||||
}
|
||||
|
||||
function Question({
|
||||
question,
|
||||
progress,
|
||||
onClick,
|
||||
}: IQuestionProps): React.ReactElement | null {
|
||||
const [selected, setSelected] = useState<IAnswer | null>(null);
|
||||
const [disabled, setDisabled] = useState<boolean>(false);
|
||||
const [mode, setMode] = useState<string[]>([]);
|
||||
const formattedAnswers = question.answers || [];
|
||||
|
||||
useEffect(() => {
|
||||
setMode([]);
|
||||
setSelected(null);
|
||||
setDisabled(false);
|
||||
}, [question]);
|
||||
|
||||
if (!question) return null;
|
||||
|
||||
const answers = formattedAnswers.map((item: IAnswer, index: number) => (
|
||||
<Answer
|
||||
key={`${item.id || ''}|${item.title}`}
|
||||
mode={mode[index]}
|
||||
answer={item}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
|
||||
if (selected !== item) {
|
||||
const newModes = [];
|
||||
newModes[index] = 'selected';
|
||||
setMode(newModes);
|
||||
setSelected(item);
|
||||
return;
|
||||
}
|
||||
|
||||
setDisabled(true);
|
||||
setTimeout(() => {
|
||||
setMode(getModes(formattedAnswers, selected));
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
onClick(selected);
|
||||
}, 3000);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={stylePage.quize_question}>
|
||||
<Progress progress={progress}/>
|
||||
<div className={stylePage.quize_question_body}>
|
||||
<div className={style.quize_title}>
|
||||
{question.title}
|
||||
</div>
|
||||
<div className={style.quize_question_answer}>
|
||||
{answers}
|
||||
</div>
|
||||
<div className={style.quize_footer}>
|
||||
<UiKitButton
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
setDisabled(true);
|
||||
setTimeout(() => {
|
||||
setMode(getModes(formattedAnswers, selected));
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
onClick(selected);
|
||||
}, 3000);
|
||||
}}
|
||||
>
|
||||
{question.button || 'Next question'}
|
||||
</UiKitButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Question;
|
45
src/ts/components/Quize/components/Result.tsx
Normal file
|
@ -0,0 +1,45 @@
|
|||
import React from 'react';
|
||||
|
||||
import UiKitButton from 'ts/components/UiKit/components/Button';
|
||||
|
||||
import IResult from '../interfaces/Result';
|
||||
|
||||
import stylePage from '../styles/result.module.scss';
|
||||
import style from '../styles/index.module.scss';
|
||||
|
||||
interface IResultProps {
|
||||
result: IResult;
|
||||
onClick: Function;
|
||||
}
|
||||
|
||||
function Result({
|
||||
result,
|
||||
onClick,
|
||||
}: IResultProps): React.ReactElement | null {
|
||||
return (
|
||||
<section className={stylePage.quize_result}>
|
||||
<h4 className={style.quize_title}>
|
||||
{result.title}
|
||||
</h4>
|
||||
<img
|
||||
className={style.quize_icon}
|
||||
style={{ backgroundImage: `url(${result.icon})` }}
|
||||
src="./assets/games/4x3.png"
|
||||
/>
|
||||
<p className={style.quize_description}>
|
||||
{result.description}
|
||||
</p>
|
||||
<div className={style.quize_footer}>
|
||||
<UiKitButton
|
||||
onClick={() => {
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
Replay
|
||||
</UiKitButton>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default Result;
|
45
src/ts/components/Quize/components/Start.tsx
Normal file
|
@ -0,0 +1,45 @@
|
|||
import React from 'react';
|
||||
|
||||
import UiKitButton from 'ts/components/UiKit/components/Button';
|
||||
|
||||
import IQuize from '../interfaces/Quize';
|
||||
|
||||
import stylePage from '../styles/start.module.scss';
|
||||
import style from '../styles/index.module.scss';
|
||||
|
||||
interface IStartProps {
|
||||
quize: IQuize;
|
||||
onClick: Function;
|
||||
}
|
||||
|
||||
function Start({
|
||||
quize,
|
||||
onClick,
|
||||
}: IStartProps): React.ReactElement | null {
|
||||
return (
|
||||
<section className={stylePage.quize_start}>
|
||||
<h4 className={style.quize_title}>
|
||||
{quize.title}
|
||||
</h4>
|
||||
<img
|
||||
className={style.quize_icon}
|
||||
style={{ backgroundImage: `url(${quize.icon})` }}
|
||||
src="./assets/games/4x3.png"
|
||||
/>
|
||||
<p className={style.quize_description}>
|
||||
{quize.description}
|
||||
</p>
|
||||
<div className={style.quize_footer}>
|
||||
<UiKitButton
|
||||
onClick={() => {
|
||||
onClick();
|
||||
}}
|
||||
>
|
||||
{quize.button || 'GO'}
|
||||
</UiKitButton>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default Start;
|
128
src/ts/components/Quize/components/index.tsx
Normal file
|
@ -0,0 +1,128 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
import Result from './Result';
|
||||
import Question from './Question';
|
||||
import Start from './Start';
|
||||
|
||||
import IQuize from '../interfaces/Quize';
|
||||
import IQuestion from '../interfaces/Question';
|
||||
import IAnswer from '../interfaces/Answer';
|
||||
import IResult from '../interfaces/Result';
|
||||
|
||||
import { getResult, getQuestionByGroups } from '../helpers';
|
||||
|
||||
import style from '../styles/index.module.scss';
|
||||
|
||||
function getApplyInAnimation(setShowSlide: Function, delay: number) {
|
||||
return (callback: Function) => {
|
||||
setShowSlide(true);
|
||||
setTimeout(() => {
|
||||
callback();
|
||||
}, delay / 2);
|
||||
setTimeout(() => {
|
||||
setShowSlide(false);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
||||
interface IQuizePageProps {
|
||||
quize: IQuize;
|
||||
onEnd: Function;
|
||||
}
|
||||
|
||||
function QuizePage({
|
||||
quize,
|
||||
onEnd,
|
||||
}: IQuizePageProps): React.ReactElement | null {
|
||||
const [question, setQuestion] = useState<IQuestion>(quize.questions[0]);
|
||||
const [result, setResult] = useState<IResult>(quize.results[0]);
|
||||
const [answers, setAnswers] = useState<IAnswer[]>([]);
|
||||
const [view, setView] = useState<string>('start');
|
||||
const [showSlide, setShowSlide] = useState<boolean>(false);
|
||||
const applyInAnimation = getApplyInAnimation(setShowSlide, 1500);
|
||||
|
||||
const questions = getQuestionByGroups(quize.questions);
|
||||
let page: any = null;
|
||||
|
||||
|
||||
if (view === 'start') {
|
||||
page = (
|
||||
<Start
|
||||
quize={quize}
|
||||
onClick={() => {
|
||||
applyInAnimation(() => {
|
||||
setView('question');
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (view === 'question') {
|
||||
page = (
|
||||
<Question
|
||||
question={question}
|
||||
progress={30}
|
||||
onClick={(answer: IAnswer) => {
|
||||
const nextById = questions.byId[answer.nextQuestionId || ''];
|
||||
const nextByIndex = questions.byIndex[question.index + 1];
|
||||
const newResult = getResult(answers, quize.results);
|
||||
setAnswers([...answers, answer]);
|
||||
|
||||
if (answer.isEnd) {
|
||||
applyInAnimation(() => {
|
||||
setResult(newResult);
|
||||
setView('result');
|
||||
});
|
||||
} if (answer.nextQuestionId && nextById) {
|
||||
applyInAnimation(() => {
|
||||
setQuestion(nextById);
|
||||
});
|
||||
} else if (nextByIndex) {
|
||||
applyInAnimation(() => {
|
||||
setQuestion(nextByIndex);
|
||||
});
|
||||
} else {
|
||||
applyInAnimation(() => {
|
||||
setResult(newResult);
|
||||
setView('result');
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (view === 'result') {
|
||||
page = (
|
||||
<Result
|
||||
result={result}
|
||||
onClick={() => {
|
||||
applyInAnimation(() => {
|
||||
onEnd();
|
||||
setQuestion(quize.questions[0]);
|
||||
setAnswers([]);
|
||||
setView('start');
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const className = showSlide
|
||||
? `${style.quize_slider} ${style.quize_slider_animation}`
|
||||
: style.quize_slider;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={style.quize_container}
|
||||
style={{ backgroundImage: 'url(./assets/games/quize.png)' }}
|
||||
>
|
||||
<div className={className}>
|
||||
{page}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuizePage;
|
71
src/ts/components/Quize/helpers/example.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
export default {
|
||||
title: 'Сотрудники отдела',
|
||||
icon: './assets/games/wheel.jpg',
|
||||
description: 'Текст с каким то описанием на три предложения, которые интригуют и манят пройти этот унылый квиз с небольшим количеством графики.',
|
||||
questions: [
|
||||
{
|
||||
title: 'Сколь директорий создал Anatoliy?',
|
||||
answers: [
|
||||
{
|
||||
title: '17',
|
||||
icon: './assets/games/wheel.jpg',
|
||||
score: 0,
|
||||
},
|
||||
{
|
||||
title: '23',
|
||||
icon: './assets/games/wheel.jpg',
|
||||
score: 1,
|
||||
},
|
||||
{
|
||||
title: '29',
|
||||
icon: './assets/games/wheel.jpg',
|
||||
score: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Albert коммитит чаще Marrie?',
|
||||
answers: [
|
||||
{
|
||||
title: 'Да',
|
||||
icon: './assets/games/wheel.jpg',
|
||||
score: 0,
|
||||
},
|
||||
{
|
||||
title: 'Нет',
|
||||
icon: './assets/games/wheel.jpg',
|
||||
score: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Самое длинное commit message оставил:',
|
||||
answers: [
|
||||
{
|
||||
title: 'Kolya Elow',
|
||||
icon: './assets/games/wheel.jpg',
|
||||
score: 0,
|
||||
},
|
||||
{
|
||||
title: 'Subrine Titan',
|
||||
icon: './assets/games/wheel.jpg',
|
||||
score: 0,
|
||||
},
|
||||
{
|
||||
title: 'Grebenshikov Muz TV',
|
||||
icon: './assets/games/wheel.jpg',
|
||||
score: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
results: [
|
||||
{
|
||||
title: 'Поздравляем, пытка окончена',
|
||||
icon: './assets/games/wheel.jpg',
|
||||
description: 'Вы протестировали этот квиз и готовы написать на него отзыв длинной два или три предложения.',
|
||||
'min': 0,
|
||||
'max': 60,
|
||||
},
|
||||
],
|
||||
};
|
35
src/ts/components/Quize/helpers/index.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import IQuestion from '../interfaces/Question';
|
||||
import IAnswer from '../interfaces/Answer';
|
||||
import IResult from '../interfaces/Result';
|
||||
|
||||
export function getQuestionByGroups(questions: IQuestion[]) {
|
||||
const byId = {};
|
||||
const byIndex = {};
|
||||
|
||||
questions.forEach((question: IQuestion, index: number) => {
|
||||
if (question?.id) byId[question?.id] = question;
|
||||
byIndex[index] = question;
|
||||
question.index = index;
|
||||
});
|
||||
|
||||
return { byId, byIndex };
|
||||
}
|
||||
|
||||
export function getResult(answers: IAnswer[], results: IResult[]) {
|
||||
const total = answers.reduce((sum: number, answer: IAnswer) => (
|
||||
sum + (answer.score || 0)
|
||||
), 0);
|
||||
|
||||
let result = results[0];
|
||||
results.forEach((item: IResult) => {
|
||||
if (item.min && item.max && total >= item.min && total <= item.max) {
|
||||
result = item;
|
||||
} else if (item.min && !item.max && total >= item.min) {
|
||||
result = item;
|
||||
} else if (!item.min && item.max && total <= item.max) {
|
||||
result = item;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
20
src/ts/components/Quize/index.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
|
||||
import IQuize from './interfaces/Quize';
|
||||
import QuizePage from './components/index';
|
||||
import example from './helpers/example';
|
||||
|
||||
interface IQuizeProps {
|
||||
}
|
||||
|
||||
function Quize({}: IQuizeProps): React.ReactElement | null {
|
||||
return (
|
||||
<QuizePage
|
||||
quize={example as IQuize}
|
||||
onEnd={() => {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Quize;
|
8
src/ts/components/Quize/interfaces/Answer.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default interface IAnswer {
|
||||
id?: number;
|
||||
title: string;
|
||||
icon?: string;
|
||||
score?: number;
|
||||
isEnd?: boolean;
|
||||
nextQuestionId?: number;
|
||||
}
|
9
src/ts/components/Quize/interfaces/Question.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import IAnswer from './Answer';
|
||||
|
||||
export default interface IQuestion {
|
||||
id?: number;
|
||||
index: number;
|
||||
title: string;
|
||||
answers?: IAnswer[];
|
||||
button?: string;
|
||||
}
|
12
src/ts/components/Quize/interfaces/Quize.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import IQuestion from './Question';
|
||||
import IResult from './Result';
|
||||
|
||||
export default interface IQuize {
|
||||
id?: number;
|
||||
icon?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
button?: string;
|
||||
questions: IQuestion[];
|
||||
results: IResult[];
|
||||
}
|
9
src/ts/components/Quize/interfaces/Result.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export default interface IResult {
|
||||
id?: number;
|
||||
title: string;
|
||||
icon?: string;
|
||||
description?: string;
|
||||
button?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
112
src/ts/components/Quize/styles/answer.module.scss
Normal file
|
@ -0,0 +1,112 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.quize_answer {
|
||||
width: 250px;
|
||||
padding-bottom: var(--space-m);
|
||||
|
||||
cursor: pointer;
|
||||
border-radius: var(--border-radius-s);
|
||||
border: none;
|
||||
box-shadow: 0 0 5px var(--color-grey);
|
||||
transition: width 0.5s;
|
||||
|
||||
background-color: white;
|
||||
|
||||
animation-duration: 10s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: quize_answer;
|
||||
|
||||
&_wrapper {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
|
||||
vertical-align: top;
|
||||
border-radius: var(--border-radius-s);
|
||||
transition: padding 0.5s;
|
||||
|
||||
&_small {
|
||||
padding: var(--space-s) var(--space-l);
|
||||
}
|
||||
}
|
||||
|
||||
&_selected {
|
||||
box-shadow: 0 0 var(--space-l) var(--color-13), 0 0 var(--space-xxl) var(--color-13);
|
||||
background-color: var(--color-13);
|
||||
animation-name: quize_answer_selected;
|
||||
}
|
||||
|
||||
&_correct {
|
||||
box-shadow: 0 0 var(--space-l) var(--color-23), 0 0 var(--space-xxl) var(--color-23);
|
||||
background-color: var(--color-23);
|
||||
animation-name: quize_answer_selected;
|
||||
}
|
||||
|
||||
&_error {
|
||||
width: 218px;
|
||||
box-shadow: 0 0 var(--space-l) var(--color-12), 0 0 var(--space-xxl) var(--color-12);
|
||||
background-color: var(--color-12);
|
||||
animation-name: quize_answer_selected;
|
||||
}
|
||||
|
||||
&_small {
|
||||
width: 218px;
|
||||
}
|
||||
|
||||
&_icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0 auto var(--space-l);
|
||||
|
||||
border-radius: var(--border-radius-s) var(--border-radius-s) 0 0;
|
||||
background-position: center center;
|
||||
background-size: auto 100%;
|
||||
background-repeat: no-repeat;
|
||||
transition: background-size 0.5s;
|
||||
|
||||
&:hover {
|
||||
background-size: auto 105%;
|
||||
}
|
||||
}
|
||||
|
||||
&_text {
|
||||
font-size: var(--font-m);
|
||||
font-weight: 100;
|
||||
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
|
||||
text-align: center;
|
||||
color: var(--color-black);
|
||||
}
|
||||
}
|
||||
|
||||
.quize_answer_wrapper + .quize_answer_wrapper {
|
||||
margin-left: var(--space-xxl);
|
||||
}
|
||||
|
||||
@keyframes quize_answer {
|
||||
from {
|
||||
box-shadow: none;
|
||||
}
|
||||
86% {
|
||||
box-shadow: none;
|
||||
}
|
||||
93% {
|
||||
box-shadow: 0 0 10px var(--color-13), 0 0 20px var(--color-13);
|
||||
}
|
||||
to {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes quize_answer_selected {
|
||||
from {
|
||||
box-shadow: none;
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 10px var(--color-13), 0 0 20px var(--color-13);
|
||||
}
|
||||
to {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
100
src/ts/components/Quize/styles/index.module.scss
Normal file
|
@ -0,0 +1,100 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.quize {
|
||||
&_container {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
margin: 0 auto var(--space-xxl);
|
||||
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
background-position: center center;
|
||||
background-size: 10%;
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
&_slider {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
animation-duration: 1.5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-fill-mode: both;
|
||||
|
||||
&_animation {
|
||||
animation-name: quize_slider;
|
||||
}
|
||||
}
|
||||
|
||||
&_footer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--space-xxl) 0;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&_title,
|
||||
&_description {
|
||||
font-weight: 100;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
&_title {
|
||||
font-size: var(--font-l);
|
||||
margin: 0 auto var(--space-xxl);
|
||||
}
|
||||
|
||||
&_description {
|
||||
font-size: var(--font-m);
|
||||
}
|
||||
|
||||
&_icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: 0 auto var(--space-xxl);
|
||||
|
||||
border: var(--space-s) solid var(--color-border);
|
||||
box-shadow: 0 0 5px var(--color-grey);
|
||||
background-color: var(--color-border);
|
||||
|
||||
background-position: center center;
|
||||
background-size: auto 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes quize_slider {
|
||||
from {
|
||||
right: 0;
|
||||
left: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
49% {
|
||||
right: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
51% {
|
||||
right: auto;
|
||||
left: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
6
src/ts/components/Quize/styles/progress.module.scss
Normal file
|
@ -0,0 +1,6 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.quize_progress {
|
||||
&_description {
|
||||
}
|
||||
}
|
16
src/ts/components/Quize/styles/question.module.scss
Normal file
|
@ -0,0 +1,16 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.quize_question {
|
||||
&_title {
|
||||
font-size: var(--font-l);
|
||||
font-weight: 100;
|
||||
margin: 0 auto var(--space-xxl);
|
||||
text-align: center;
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
&_answer {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
35
src/ts/components/Quize/styles/result.module.scss
Normal file
|
@ -0,0 +1,35 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.quize_result {
|
||||
&_title,
|
||||
&_description {
|
||||
font-weight: 100;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
&_title {
|
||||
font-size: var(--font-l);
|
||||
margin: 0 auto var(--space-xxl);
|
||||
}
|
||||
|
||||
&_description {
|
||||
font-size: var(--font-m);
|
||||
}
|
||||
|
||||
&_icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin: 0 auto var(--space-xxl);
|
||||
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: 0 0 5px var(--color-grey);
|
||||
background-color: var(--color-border);
|
||||
|
||||
background-position: center center;
|
||||
background-size: auto 100%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
4
src/ts/components/Quize/styles/start.module.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.quize_start {
|
||||
}
|
|
@ -2,6 +2,7 @@ import React, { useState } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import UiKitButton from 'ts/components/UiKit/components/Button';
|
||||
import GameBanner from 'ts/components/GameBanner';
|
||||
import { shuffle } from 'ts/helpers/random';
|
||||
|
||||
import Track from './components/Track';
|
||||
|
@ -37,7 +38,8 @@ function Races({
|
|||
});
|
||||
|
||||
return (
|
||||
<div className={style.races}>
|
||||
<>
|
||||
<GameBanner src="./assets/games/races.jpg">
|
||||
{!showAnimation && (
|
||||
<UiKitButton
|
||||
className={style.races_button}
|
||||
|
@ -48,8 +50,11 @@ function Races({
|
|||
{t('uiKit.races.go')}
|
||||
</UiKitButton>
|
||||
)}
|
||||
</GameBanner>
|
||||
<div className={style.races}>
|
||||
{lines}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,10 +24,7 @@
|
|||
}
|
||||
|
||||
&_button {
|
||||
position: absolute;
|
||||
top: var(--space-l);
|
||||
left: calc(50% - 100px);
|
||||
z-index: 1;
|
||||
margin-bottom: var(--space-xxl);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ const RecommendationDescription = observer(() => {
|
|||
<Footer className={style.recommendations_modal_footer}>
|
||||
<UiKitButton
|
||||
mode={[ isMobile ? 'primary' : 'border', 'full_size']}
|
||||
className={style.recommendations_modal_button}
|
||||
onClick={() => {
|
||||
recommendationStore.close();
|
||||
}}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
}
|
||||
|
||||
&_footer {
|
||||
padding: var(--space-s) var(--space-xxl) var(--space-l) var(--space-xxl);
|
||||
padding: var(--space-s) var(--space-xxl) 0 var(--space-xxl);
|
||||
}
|
||||
|
||||
&_sub_title {
|
||||
|
@ -56,6 +56,11 @@
|
|||
border-bottom-color: var(--color-temp-border);
|
||||
}
|
||||
|
||||
&_button {
|
||||
color: var(--color-temp-title);
|
||||
border-color: var(--color-temp-border);
|
||||
}
|
||||
|
||||
border: 1px solid var(--color-border);
|
||||
border-left-width: var(--space-s);
|
||||
background-color: var(--color-temp-bg);
|
||||
|
|
|
@ -57,13 +57,13 @@ function ShowSymbol({
|
|||
paddingTop: mode === 'table-row' ? '8px' : 0,
|
||||
}}
|
||||
>
|
||||
{line}
|
||||
<Button
|
||||
mode={mode}
|
||||
onClick={() => setShowAll(true)}
|
||||
>
|
||||
»
|
||||
</Button>
|
||||
{line}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
28
src/ts/components/Sponsor/components/buttons.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import getSocialLinks from '../helpers';
|
||||
|
||||
import style from '../styles/index.module.scss';
|
||||
|
||||
function SocialLinks() {
|
||||
const buttons = Object.entries(getSocialLinks())
|
||||
.map(([title, link]: string[]) => (
|
||||
<Link
|
||||
key={title}
|
||||
className={style.sponsor_button}
|
||||
to={link}
|
||||
target="_blank"
|
||||
>
|
||||
{title}
|
||||
</Link>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={style.sponsor_button_wrapper}>
|
||||
{buttons}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SocialLinks;
|
52
src/ts/components/Sponsor/components/money.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import React from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import UiKitButton from 'ts/components/UiKit/components/Button';
|
||||
import { Modal, Header, Body, Footer } from 'ts/components/ModalWindow';
|
||||
|
||||
import sponsorStore from '../store';
|
||||
import style from '../styles/index.module.scss';
|
||||
|
||||
const Money = observer((): React.ReactElement | null => {
|
||||
return (
|
||||
<Modal
|
||||
mode="halo"
|
||||
onClose={() => {
|
||||
sponsorStore.close();
|
||||
}}
|
||||
>
|
||||
<Header className={style.sponsor_title}>
|
||||
Поддержите проект
|
||||
</Header>
|
||||
<Body className={style.sponsor_body}>
|
||||
<p className={style.sponsor_text}>
|
||||
Мы будем рады, если вы поддержите нас любой суммой! Все средства пойдут на дальнейшее развитие проекта.
|
||||
</p>
|
||||
<img
|
||||
className={style.sponsor_cover}
|
||||
src="./assets/sponsor/money.jpg"
|
||||
/>
|
||||
</Body>
|
||||
<Footer className={style.sponsor_footer}>
|
||||
<UiKitButton
|
||||
mode={['primary', 'full_size']}
|
||||
onClick={() => {
|
||||
sponsorStore.close();
|
||||
}}
|
||||
>
|
||||
Разовый платёж (СБП)
|
||||
</UiKitButton>
|
||||
<UiKitButton
|
||||
mode={['border', 'full_size']}
|
||||
onClick={() => {
|
||||
sponsorStore.close();
|
||||
}}
|
||||
>
|
||||
Подписка GitHub Sponsor
|
||||
</UiKitButton>
|
||||
</Footer>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default Money;
|
53
src/ts/components/Sponsor/components/share.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import React from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Modal, Header, Body, Footer } from 'ts/components/ModalWindow';
|
||||
import UiKitButton from 'ts/components/UiKit/components/Button';
|
||||
import { getTextWithStyle } from 'ts/components/Description';
|
||||
import notificationsStore from 'ts/components/Notifications/store';
|
||||
import localization from 'ts/helpers/Localization';
|
||||
import copyInBuffer from 'ts/helpers/copyInBuffer';
|
||||
|
||||
import SocialLinks from './buttons';
|
||||
import sponsorStore from '../store';
|
||||
|
||||
import style from '../styles/index.module.scss';
|
||||
|
||||
const Share = observer((): React.ReactElement | null => {
|
||||
const { t } = useTranslation();
|
||||
const text = localization.get('page.sponsor.share.description');
|
||||
|
||||
return (
|
||||
<Modal
|
||||
mode="halo"
|
||||
delay={10}
|
||||
onClose={() => {
|
||||
sponsorStore.close();
|
||||
}}
|
||||
>
|
||||
<Header className={style.sponsor_title}>
|
||||
{t('page.sponsor.share.title')}
|
||||
</Header>
|
||||
<Body className={style.sponsor_body}>
|
||||
<p className={style.sponsor_text}>
|
||||
{getTextWithStyle(text)}
|
||||
</p>
|
||||
<SocialLinks/>
|
||||
</Body>
|
||||
<Footer className={style.sponsor_footer}>
|
||||
<UiKitButton
|
||||
mode={['primary', 'full_size']}
|
||||
onClick={() => {
|
||||
copyInBuffer('https://github.com/bakhirev/assayo');
|
||||
notificationsStore.show(localization.get('uiKit.console.notification'));
|
||||
}}
|
||||
>
|
||||
{t('page.sponsor.share.button')}
|
||||
</UiKitButton>
|
||||
</Footer>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default Share;
|
35
src/ts/components/Sponsor/helpers/index.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import IHashMap from 'ts/interfaces/HashMap';
|
||||
|
||||
export default function getSocialLinks(): IHashMap<string> {
|
||||
const link = 'https://github.com/bakhirev/assayo';
|
||||
const title = 'Visualization and analysis of git repository';
|
||||
const subTitle = 'Check your git stats!';
|
||||
const description = '';
|
||||
const tags = 'IT,git,statistics,audit,data-visualization,report';
|
||||
|
||||
return {
|
||||
Facebook: `http://www.facebook.com/sharer.php?u=${link}`,
|
||||
VK: `http://vk.com/share.php?url=${link}&title=${title}&comment=${subTitle}`,
|
||||
QQ: `http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=${link}`,
|
||||
Reddit: `https://reddit.com/submit?url=${link}&title=${title}`,
|
||||
X: `https://twitter.com/intent/tweet?url=${link}&text=${description}&via=&hashtags=${tags}`,
|
||||
LinkedIn: `https://www.linkedin.com/sharing/share-offsite/?url=${link}`,
|
||||
OK: `https://connect.ok.ru/dk?st.cmd=WidgetSharePreview&st.shareUrl=${link}`,
|
||||
Tumblr: `https://www.tumblr.com/widgets/share/tool?canonicalUrl=${link}&title=${title}&caption=${subTitle}&tags=${tags}`,
|
||||
Blogger: `https://www.blogger.com/blog-this.g?u=${link}&n=${title}&t=${subTitle}`,
|
||||
Evernote: `https://www.evernote.com/clip.action?url=${link}&title=${description}`,
|
||||
Addthis: `http://www.addthis.com/bookmark.php?url=${link}`,
|
||||
GetPocket: `https://getpocket.com/edit?url=${link}`,
|
||||
YCombinator: `https://news.ycombinator.com/submitlink?u=${link}&t=${title}`,
|
||||
Buffer: `https://buffer.com/add?text=${description}&url=${link}`,
|
||||
Flipboard: `https://share.flipboard.com/bookmarklet/popout?v=2&title=${description}&url=${link}`,
|
||||
Instapaper: `http://www.instapaper.com/edit?url=${link}&title=${title}&description=${subTitle}`,
|
||||
Renren: `http://widget.renren.com/dialog/share?resourceUrl=${link}&srcUrl=${link}&title=${title}&description=${subTitle}`,
|
||||
'The diaspora* Project': `https://share.diasporafoundation.org/?title=${title}&url=${link}`,
|
||||
Weibo: `http://service.weibo.com/share/share.php?url=${link}&appkey=&title=${title}&pic=&ralateUid=`,
|
||||
Douban: `http://www.douban.com/recommend/?url=${link}&title=${description}`,
|
||||
XING: `https://www.xing.com/spi/shares/new?url=${link}`,
|
||||
// Threema: `threema://compose?text=${description}&id=`,
|
||||
Line: `https://lineit.line.me/share/ui?url=${link}&text=${description}`,
|
||||
};
|
||||
}
|
19
src/ts/components/Sponsor/index.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import React from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import Money from './components/money';
|
||||
import Share from './components/share';
|
||||
|
||||
import sponsorStore, { MODAL_TYPE } from './store';
|
||||
|
||||
const SponsorScreen = observer((): React.ReactElement | null => {
|
||||
if (sponsorStore.type === MODAL_TYPE.MONEY) {
|
||||
return <Money/>;
|
||||
}
|
||||
if (sponsorStore.type === MODAL_TYPE.SHARE) {
|
||||
return <Share/>;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
export default SponsorScreen;
|
48
src/ts/components/Sponsor/store/index.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { observable, action, makeObservable } from 'mobx';
|
||||
|
||||
import isMobile from 'ts/helpers/isMobile';
|
||||
import themeSettings from 'ts/store/ThemeSettings';
|
||||
|
||||
export const MODAL_TYPE = {
|
||||
HIDE: 0,
|
||||
MONEY: 1,
|
||||
SHARE: 2,
|
||||
};
|
||||
|
||||
class SponsorStore {
|
||||
type: number = MODAL_TYPE.HIDE;
|
||||
|
||||
constructor() {
|
||||
makeObservable(this, {
|
||||
type: observable,
|
||||
open: action,
|
||||
close: action,
|
||||
});
|
||||
|
||||
if (!isMobile && !themeSettings.getConfig()) {
|
||||
this.setTimer();
|
||||
}
|
||||
}
|
||||
|
||||
setTimer() {
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
setInterval(() => {
|
||||
if (this.type) return;
|
||||
this.type = Math.random() > 0.5
|
||||
? MODAL_TYPE.SHARE
|
||||
: MODAL_TYPE.SHARE;
|
||||
}, 6 * ONE_MINUTE);
|
||||
}
|
||||
|
||||
open() {
|
||||
this.type = MODAL_TYPE.MONEY;
|
||||
}
|
||||
|
||||
close() {
|
||||
this.type = MODAL_TYPE.HIDE;
|
||||
}
|
||||
}
|
||||
|
||||
const sponsorStore = new SponsorStore();
|
||||
|
||||
export default sponsorStore;
|
69
src/ts/components/Sponsor/styles/index.module.scss
Normal file
|
@ -0,0 +1,69 @@
|
|||
@import 'src/styles/variables';
|
||||
@import 'src/ts/components/Description/index.module';
|
||||
|
||||
.sponsor {
|
||||
&_title {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&_body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&_text {
|
||||
font-size: var(--space-l);
|
||||
font-weight: 100;
|
||||
padding: 0 var(--space-xxl) var(--space-xxl);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&_link {
|
||||
color: var(--color-button);
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
&_footer {
|
||||
padding: 0 var(--space-xxl);
|
||||
}
|
||||
|
||||
&_button {
|
||||
font-size: var(--font-xs);
|
||||
font-weight: 100;
|
||||
|
||||
display: inline-block;
|
||||
padding: var(--space-s);
|
||||
margin: var(--space-xxs);
|
||||
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
border-radius: var(--border-radius-s);
|
||||
border: 1px solid var(--color-border);
|
||||
background-color: white;
|
||||
|
||||
color: var(--color-button);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&_wrapper {
|
||||
display: block;
|
||||
padding: var(--space-xs) 0;
|
||||
margin: 0 auto;
|
||||
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
|
||||
border-radius: 0;
|
||||
border-top: 1px solid var(--color-border);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
|
||||
background: linear-gradient(135deg, rgba(26,115,232,1) 0%, rgba(159,167,255,1) 100%);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,7 +23,9 @@ function getFormattedDate(commits: ICommit[]) {
|
|||
|
||||
function getTags(commits: ICommit[]) {
|
||||
const uniqueTypes = new Set(commits.map((commit: ICommit) => commit.type));
|
||||
const tags = Array.from(uniqueTypes).map((title: string) => (
|
||||
const tags = Array.from(uniqueTypes)
|
||||
.filter((title: string) => title && title !== '—')
|
||||
.map((title: string) => (
|
||||
<p
|
||||
key={title}
|
||||
className={style.tempo_task_tag}
|
||||
|
@ -48,14 +50,18 @@ function Task({ title, commits }: ITaskProps) {
|
|||
>
|
||||
<div className={style.tempo_task_header}>
|
||||
<div>
|
||||
{title ? (
|
||||
<ExternalLink
|
||||
text={title}
|
||||
link={`${userSettings?.settings?.linksPrefix?.task || '/'}${title}`}
|
||||
/>
|
||||
) : '—'}
|
||||
{prId ? (
|
||||
<ExternalLink
|
||||
text="PR"
|
||||
link={`${userSettings?.settings?.linksPrefix?.pr || '/'}${prId}`}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={style.tempo_task_tags}>
|
||||
{getTags(commits)}
|
||||
|
|
|
@ -51,8 +51,8 @@ export default class DataGripByTasks {
|
|||
prDelayDays: pr?.delayDays,
|
||||
prAuthor: firstCommit.author === pr?.author ? null : pr?.author,
|
||||
comments: firstCommit.text,
|
||||
types: firstCommit.type && firstCommit.type !== 'не подписан' ? [firstCommit.type] : [],
|
||||
scope: firstCommit.scope && firstCommit.scope !== 'неопределенна' ? [firstCommit.scope] : [],
|
||||
types: firstCommit.type && firstCommit.type !== '—' ? [firstCommit.type] : [],
|
||||
scope: firstCommit.scope && firstCommit.scope !== '—' ? [firstCommit.scope] : [],
|
||||
};
|
||||
|
||||
if (commits.length === 1) return shortInfo;
|
||||
|
@ -64,8 +64,8 @@ export default class DataGripByTasks {
|
|||
commits.forEach((commit: ICommit) => {
|
||||
authors.add(commit.author);
|
||||
messages.add(commit.text);
|
||||
if (commit.type !== 'не подписан') types.add(commit.type);
|
||||
if (commit.scope !== 'неопределенна') scope.add(commit.scope);
|
||||
if (commit.type !== '—') types.add(commit.type);
|
||||
if (commit.scope !== '—') scope.add(commit.scope);
|
||||
});
|
||||
|
||||
const comments = Array.from(messages).join(', ');
|
||||
|
|
|
@ -25,7 +25,8 @@ export default class FileGripByPaths {
|
|||
if (file) {
|
||||
this.#updateDirtyFile(file, fileChange, commit);
|
||||
} else {
|
||||
this.refFileIds[fileChange.id] = this.#getNewDirtyFile(fileChange, commit) as IDirtyFile;
|
||||
file = this.#getNewDirtyFile(fileChange, commit) as IDirtyFile;
|
||||
this.refFileIds[fileChange.id] = file;
|
||||
}
|
||||
|
||||
if (fileChange.newId) {
|
||||
|
|
|
@ -35,8 +35,8 @@ export default function getCommitInfo(logString: string): ICommit | ISystemCommi
|
|||
message,
|
||||
|
||||
text: '',
|
||||
type: 'не подписан',
|
||||
scope: 'неопределенна',
|
||||
type: '—',
|
||||
scope: '—',
|
||||
fileChanges: [],
|
||||
};
|
||||
|
||||
|
@ -98,8 +98,8 @@ export default function getCommitInfo(logString: string): ICommit | ISystemCommi
|
|||
task,
|
||||
taskNumber,
|
||||
text,
|
||||
type: type || 'не подписан',
|
||||
scope: scope || 'неопределенна',
|
||||
type: type || '—',
|
||||
scope: scope || '—',
|
||||
|
||||
changes: 0,
|
||||
added: 0,
|
||||
|
|
|
@ -71,7 +71,7 @@ export function getTypeAndScope(message: string, task: string) {
|
|||
|
||||
// ABC-123, #123, gh-123
|
||||
export function getTask(message: string) {
|
||||
return ((message || '').match(/(([A-Z]+-)|(#)|(gh-)|(GH-))([0-9]+)/gm) || [])[0] || '';
|
||||
return ((message || '').match(/(([A-Z]+[-_])|(#)|(gh-)|(GH-))([0-9]+)/gm) || [])[0] || '';
|
||||
}
|
||||
|
||||
// ABC-123 => '123';
|
||||
|
|
|
@ -10,16 +10,22 @@ class AchievementsByAuthor {
|
|||
this.authors[name] = [];
|
||||
}
|
||||
|
||||
add(authors: Array<[string, number]>, maxAchievementCode: string, minAchievementCode?: string) {
|
||||
add(
|
||||
authors: Array<[string, number]>,
|
||||
maxAchievementCode?: string,
|
||||
minAchievementCode?: string,
|
||||
) {
|
||||
const first = authors?.[0]?.[0];
|
||||
if (!first) return;
|
||||
if (maxAchievementCode) {
|
||||
this.authors?.[first]?.push(maxAchievementCode);
|
||||
|
||||
if (!minAchievementCode) return;
|
||||
}
|
||||
if (minAchievementCode) {
|
||||
const last = authors?.[authors.length - 1]?.[0];
|
||||
this.authors?.[last]?.push(minAchievementCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AchievementsByCompetition {
|
||||
authors: IHashMap<Array<string[]>> = {};
|
||||
|
@ -53,7 +59,7 @@ class AchievementsByCompetition {
|
|||
byAuthor.add(total.allDaysInProject, 'moreDaysInProject', 'lessDaysInProject');
|
||||
|
||||
// Дата первого коммита
|
||||
byAuthor.add(total.firstCommit, 'adam');
|
||||
byAuthor.add(total.firstCommit, null, 'adam');
|
||||
|
||||
// Количество метки «рефакторинг»
|
||||
byAuthor.add(total.moreRefactoring, 'moreRefactoring');
|
||||
|
|
12
src/ts/helpers/copyInBuffer.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export default function copyInBuffer(value?: string) {
|
||||
if (!value) return;
|
||||
const copyTextarea = document.createElement('textarea');
|
||||
copyTextarea.style.position = 'fixed';
|
||||
copyTextarea.style.opacity = '0';
|
||||
copyTextarea.textContent = value;
|
||||
|
||||
document.body.appendChild(copyTextarea);
|
||||
copyTextarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(copyTextarea);
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
interface IBanner {
|
||||
isDefault?: boolean;
|
||||
ref?: string;
|
||||
link?: string;
|
||||
isOpenInNewTab?: boolean;
|
||||
|
||||
/* Логотип */
|
||||
icon?: string;
|
||||
logo?: string;
|
||||
title?: string;
|
||||
|
||||
/* Картинка баннера */
|
||||
banner?: string;
|
||||
|
||||
/* Текстовы баннер */
|
||||
bannerText?: string;
|
||||
text?: string;
|
||||
textIcon?: string;
|
||||
color?: string;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { observer } from 'mobx-react-lite';
|
|||
|
||||
import dataGripStore, { DataParseStatusEnum } from 'ts/store/DataGrip';
|
||||
import DropZone from 'ts/components/DropZone';
|
||||
import Sponsor from 'ts/components/Sponsor';
|
||||
import SplashScreen from 'ts/components/SplashScreen';
|
||||
import Confirm from 'ts/components/ModalWindow/Confirm';
|
||||
|
||||
|
@ -22,6 +23,7 @@ interface IViewWithChartsProps {
|
|||
function ViewWithCharts({ showSplashScreen }: IViewWithChartsProps) {
|
||||
return (
|
||||
<>
|
||||
<Sponsor />
|
||||
<Confirm />
|
||||
<Routes>
|
||||
<Route
|
||||
|
|
|
@ -11,12 +11,11 @@ interface ILogoProps {
|
|||
|
||||
function Logo({ center }: ILogoProps) {
|
||||
const {
|
||||
isDefault,
|
||||
icon,
|
||||
logo,
|
||||
link,
|
||||
title,
|
||||
isOpenInNewTab,
|
||||
} = themeSettings.getLogo();
|
||||
} = themeSettings.getLogo() || {};
|
||||
const isDefault = logo === './assets/logo.svg';
|
||||
|
||||
return (
|
||||
<figure
|
||||
|
@ -26,11 +25,11 @@ function Logo({ center }: ILogoProps) {
|
|||
>
|
||||
<Link
|
||||
to={link || ''}
|
||||
target={isOpenInNewTab ? '_blank' : ''}
|
||||
target={isDefault ? '' : '_blank'}
|
||||
className={style.logo_link}
|
||||
>
|
||||
<img
|
||||
src={icon || ''}
|
||||
src={logo || ''}
|
||||
title={title || ''}
|
||||
className={isDefault
|
||||
? `${style.logo_icon} ${style.logo_default}`
|
||||
|
|
|
@ -12,6 +12,9 @@ import ShowSymbol from 'ts/components/ShowSymbol';
|
|||
import Column from 'ts/components/Table/components/Column';
|
||||
import LineChart from 'ts/components/LineChart';
|
||||
import getOptions from 'ts/components/LineChart/helpers/getOptions';
|
||||
import GameConsole from 'ts/components/GameConsole';
|
||||
import GameBanner from 'ts/components/GameBanner';
|
||||
import Quize from 'ts/components/Quize';
|
||||
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
|
||||
|
||||
const TeamBuilding = observer((): React.ReactElement => {
|
||||
|
@ -38,10 +41,16 @@ const TeamBuilding = observer((): React.ReactElement => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Title title="Скорость коммитов в день"/>
|
||||
<GameConsole />
|
||||
|
||||
<Title title="Квиз"/>
|
||||
<Quize />
|
||||
<Title title="Скорость закрытия задач"/>
|
||||
<Races tracks={tracks} />
|
||||
|
||||
<Title title="Максимальная длинна подписи коммита"/>
|
||||
<GameBanner src="./assets/games/wheel.jpg" />
|
||||
<DataView rows={maxMessageLength}>
|
||||
<Column
|
||||
isFixed
|
||||
|
@ -107,7 +116,7 @@ const TeamBuilding = observer((): React.ReactElement => {
|
|||
)}
|
||||
/>
|
||||
</DataView>
|
||||
|
||||
{'Квиз'}
|
||||
{'Небоскребы вверх ввиде графика'}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,46 +1,57 @@
|
|||
import { makeObservable, observable, action } from 'mobx';
|
||||
|
||||
import IBanner from 'ts/interfaces/Banner';
|
||||
import IHashMap from 'ts/interfaces/HashMap';
|
||||
import IBanner from 'ts/interfaces/Banner';
|
||||
|
||||
const LOGO: IBanner = {
|
||||
isDefault: true,
|
||||
icon: './assets/logo.svg',
|
||||
logo: './assets/logo.svg',
|
||||
isOpenInNewTab: false,
|
||||
};
|
||||
|
||||
const EXTERNAL_LOGO: IHashMap<IBanner> = {
|
||||
vk_frontend_du2: {
|
||||
icon: './social/vk/frontend_du2.png',
|
||||
logo: './social/vk/frontend_du2.png',
|
||||
banner: './social/vk/frontend_du2.jpg',
|
||||
link: 'https://vk.com/frontend_du2',
|
||||
title: 'Сообщество о веб-разработке и программировании',
|
||||
},
|
||||
vk_take_off_staff: {
|
||||
icon: './social/vk/take_off_staff.png',
|
||||
logo: './social/vk/take_off_staff.png',
|
||||
banner: './social/vk/take_off_staff.jpg',
|
||||
link: 'https://vk.com/takeoff_staff',
|
||||
},
|
||||
vk_awesomejs: {
|
||||
icon: './social/vk/awesomejs.png',
|
||||
// banner: './social/vk/awesomejs.jpg',
|
||||
link: 'https://vk.com/awesomejs',
|
||||
bannerText: 'Сбер Банк',
|
||||
color: '#000',
|
||||
backgroundColor: '#C2ECC1',
|
||||
},
|
||||
vk_frontend_dev: {
|
||||
icon: './social/vk/frontend_dev.png',
|
||||
banner: './social/vk/frontend_dev.jpg',
|
||||
link: 'https://vk.com/frontend_dev',
|
||||
},
|
||||
vk_front_work: {
|
||||
icon: './social/vk/front_work.png',
|
||||
banner: './social/vk/front_work.jpg',
|
||||
link: 'https://frontends.work/?ref=assayo',
|
||||
},
|
||||
};
|
||||
|
||||
function getDefaultBanner(ref: string): IBanner | null {
|
||||
if (!ref) return null;
|
||||
|
||||
const parts = ref.split('_');
|
||||
const type = parts.shift() || '';
|
||||
const name = parts.join('_');
|
||||
if (!name) return null;
|
||||
|
||||
return {
|
||||
isOpenInNewTab: true,
|
||||
logo: './assets/logo.svg',
|
||||
link: {
|
||||
vk: `https://vk.com/${name}`,
|
||||
yt: `https://www.youtube.com/@${name}`,
|
||||
tg: `https://t.me/@${name}`,
|
||||
}[type],
|
||||
text: name,
|
||||
textIcon: {
|
||||
vk: './social/vk.png',
|
||||
yt: './social/youtube.png',
|
||||
tg: './social/tg.png',
|
||||
}[type],
|
||||
color: '#FFFFFF',
|
||||
backgroundColor: {
|
||||
vk: '#5181B8',
|
||||
yt: '#FE0000',
|
||||
tg: '#29A6E6',
|
||||
}[type] || '#EFC526',
|
||||
};
|
||||
}
|
||||
|
||||
class ThemeSettings {
|
||||
urlParameters: any = {};
|
||||
|
||||
|
@ -55,23 +66,17 @@ class ThemeSettings {
|
|||
this.urlParameters = urlParameters || {};
|
||||
}
|
||||
|
||||
getLogo(): IBanner {
|
||||
getConfig(): IBanner {
|
||||
const ref = this.urlParameters?.ref || '';
|
||||
let config = EXTERNAL_LOGO[ref];
|
||||
if (!config) return LOGO;
|
||||
return EXTERNAL_LOGO[ref] || getDefaultBanner(ref);
|
||||
}
|
||||
|
||||
config.ref = ref;
|
||||
config.isOpenInNewTab = true;
|
||||
|
||||
return config;
|
||||
getLogo(): IBanner | null {
|
||||
return LOGO || this.getConfig() || LOGO;
|
||||
}
|
||||
|
||||
getBanner(): IBanner | null {
|
||||
let config = EXTERNAL_LOGO[this.urlParameters?.ref || ''];
|
||||
if (!config) return null;
|
||||
|
||||
config.isOpenInNewTab = true;
|
||||
return config;
|
||||
return this.getConfig();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,4 +87,71 @@ export default `
|
|||
§ achievements.moreStyle.description: tends to change CSS more than others
|
||||
§ achievements.moreOnHoliday.title: No life
|
||||
§ achievements.moreOnHoliday.description: relatively many commits in non-working hours
|
||||
§ achievements.morePRMerge.title: Таможня даёт добро
|
||||
§ achievements.morePRMerge.description: more often than others, presses the "Merge" button for PR
|
||||
§ achievements.longWaitPR.title: Завтра точно вольём
|
||||
§ achievements.longWaitPR.description: создал PR, который больше месяца провисел на ревью
|
||||
§ achievements.moreLongWaitPR.title: A long time ago in a galaxy far, far away
|
||||
§ achievements.moreLongWaitPR.description: создал PR, который максимально долго провисел на ревью
|
||||
§ achievements.oneExtension.title: Один в поле воин
|
||||
§ achievements.oneExtension.description: только он работает с файлами определенного расширения
|
||||
§ achievements.fileRush.title: Zerg Rush
|
||||
§ achievements.fileRush.description: created the most files in the project
|
||||
§ achievements.moreLintHint.title: Грамар-наци
|
||||
§ achievements.moreLintHint.description: больше всех создал или изменил в правилах авто-проверки кода
|
||||
§ achievements.moreReadMe.title: Летописец
|
||||
§ achievements.moreReadMe.description: больше всех создал или изменил файлов MD
|
||||
§ achievements.moreDevOps.title: DevOps
|
||||
§ achievements.moreDevOps.description: больше всех создал или изменил файлов для CI/CD
|
||||
§ achievements.moreTests.title: Тестировщик
|
||||
§ achievements.moreTests.description: больше всех создал или изменил файлов для тестирования
|
||||
§ achievements.allRelease.title: Фулл хаус
|
||||
§ achievements.allRelease.description: есть релиз, собранный только из его задач
|
||||
§ achievements.firstCommit.title: First come, first served
|
||||
§ achievements.firstCommit.description: first commit in this project
|
||||
§ achievements.lastCommit.title: I've finished
|
||||
§ achievements.lastCommit.description: последний коммит на проекте
|
||||
§ achievements.firstLastCommit.title: From beginning to end
|
||||
§ achievements.firstLastCommit.description: первый и последний коммит на проекте
|
||||
§ achievements.longFilePath.title: Закрома родины
|
||||
§ achievements.longFilePath.description: the first created the file with the deepest directory
|
||||
§ achievements.longFileName.title: Size matters
|
||||
§ achievements.longFileName.description: created the file with the longest name
|
||||
§ achievements.workOnWeekends.title: Work not walk
|
||||
§ achievements.workOnWeekends.description: хоть раз работал на выходных
|
||||
§ achievements.removeCreateFile.title: Откопал стюардессу
|
||||
§ achievements.removeCreateFile.description: recover removed file
|
||||
§ achievements.renameFile.title: Астана Нур-Султан Астана
|
||||
§ achievements.renameFile.description: переименовывал туда-сюда файл
|
||||
§ achievements.longTask.title: Easy task
|
||||
§ achievements.longTask.description: worked on task more than three months
|
||||
§ achievements.haveNotEmail.title: Mailman
|
||||
§ achievements.haveNotEmail.description: empty email field in git config
|
||||
§ achievements.moreAddedFolders.title: Director
|
||||
§ achievements.moreAddedFolders.description: created the most directories
|
||||
§ achievements.horoscope1.title: Mercury Retrograde for Capricorn
|
||||
§ achievements.horoscope2.title: Mercury Retrograde for Aquarius
|
||||
§ achievements.horoscope3.title: Mercury Retrograde for Pisces
|
||||
§ achievements.horoscope4.title: Mercury Retrograde for Aries
|
||||
§ achievements.horoscope5.title: Mercury Retrograde for Taurus
|
||||
§ achievements.horoscope6.title: Mercury Retrograde for Gemini
|
||||
§ achievements.horoscope7.title: Mercury Retrograde for Cancer
|
||||
§ achievements.horoscope8.title: Mercury Retrograde for Leo
|
||||
§ achievements.horoscope9.title: Mercury Retrograde for Virgo
|
||||
§ achievements.horoscope10.title: Mercury Retrograde for Libra
|
||||
§ achievements.horoscope11.title: Mercury Retrograde for Scorpio
|
||||
§ achievements.horoscope12.title: Mercury Retrograde for Sagittarius
|
||||
§ achievements.horoscope1.description: by the month of the first commit
|
||||
§ achievements.horoscope2.description: by the month of the first commit
|
||||
§ achievements.horoscope3.description: by the month of the first commit
|
||||
§ achievements.horoscope4.description: by the month of the first commit
|
||||
§ achievements.horoscope5.description: by the month of the first commit
|
||||
§ achievements.horoscope6.description: by the month of the first commit
|
||||
§ achievements.horoscope7.description: by the month of the first commit
|
||||
§ achievements.horoscope8.description: by the month of the first commit
|
||||
§ achievements.horoscope9.description: by the month of the first commit
|
||||
§ achievements.horoscope10.description: by the month of the first commit
|
||||
§ achievements.horoscope11.description: by the month of the first commit
|
||||
§ achievements.horoscope12.description: by the month of the first commit
|
||||
§ achievements.111.description: test
|
||||
`;
|
||||
|
|
|
@ -214,4 +214,7 @@ will be marked as a jump in "deleted" and "added" lines.
|
|||
§ page.person.week.days: days
|
||||
§ page.person.week.workDay: weekdays
|
||||
§ page.person.week.weekends: weekends
|
||||
§ page.sponsor.share.title: Please, support this project
|
||||
§ page.sponsor.share.description: Tell about our [project|https://github.com/bakhirev/assayo] on social networks! You can share [article|https://habr.com/ru/articles/763342/], [post|https://www.reddit.com/r/github/comments/1bvtsl3/how_i_parsed_git_statistics/] or make a video review.
|
||||
§ page.sponsor.share.button: Copy the link
|
||||
`;
|
||||
|
|