Initial commit
3
app/assets/config/manifest.js
Normal file
@@ -0,0 +1,3 @@
|
||||
//= link_tree ../images
|
||||
//= link_directory ../javascripts .js
|
||||
//= link_directory ../stylesheets .css
|
||||
0
app/assets/images/.keep
Normal file
43
app/assets/images/BiG-logo.svg
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 395 100" style="enable-background:new 0 0 395 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#171314;}
|
||||
.st1{fill:#EC1E24;}
|
||||
.st2{fill:#EE363B;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M71.708,90.105h30.994c12.19,0,19.217-4.753,19.217-13.224v-0.414c0-7.851-6.199-13.017-19.629-13.017
|
||||
H71.708V90.105z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M115.926,22.746v-0.414c0-7.851-6.199-12.397-18.183-12.397H71.708V35.35h25.208
|
||||
C109.108,35.35,115.926,31.217,115.926,22.746z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<circle class="st1" cx="191.732" cy="22.976" r="19.801"/>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="0.188" y="-0.438" class="st0" width="32.469" height="100.915"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M220.974,50.02c0-19.586,7.145-37.202,19.286-50.457h-87.75c1.966,4.185,3.088,8.981,3.088,14.505v0.414
|
||||
c0,16.736-9.505,26.654-22.316,32.233c17.564,5.786,28.722,15.704,28.722,35.333v0.414c0,6.768-1.519,12.806-4.415,18.017
|
||||
h14.342v-46.12h39.602v46.12h28.215c-11.807-12.907-18.774-30.146-18.774-50.044V50.02z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M354.491-0.438l-20.494,24.629c-10.331-8.678-20.662-13.637-34.919-13.637
|
||||
c-20.868,0-36.985,17.563-36.985,39.672v0.413c0,23.349,16.323,40.085,39.259,40.085c9.71,0,17.149-2.067,23.142-5.993V67.169
|
||||
h-28.515v-29.34h66.532v62.648H395V-0.438H354.491z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<circle class="st2" cx="191.732" cy="22.976" r="19.801"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/assets/images/anonymous_person.png
Normal file
|
After Width: | Height: | Size: 910 B |
BIN
app/assets/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
7
app/assets/images/folder.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" version="1.1" width="48px" height="48px">
|
||||
<g id="surface1">
|
||||
<path style=" fill:#FFA000;" d="M 40 12 L 22 12 L 18 8 L 8 8 C 5.800781 8 4 9.800781 4 12 L 4 20 L 44 20 L 44 16 C 44 13.800781 42.199219 12 40 12 Z "/>
|
||||
<path style=" fill:#FFCA28;" d="M 40 12 L 8 12 C 5.800781 12 4 13.800781 4 16 L 4 36 C 4 38.199219 5.800781 40 8 40 L 40 40 C 42.199219 40 44 38.199219 44 36 L 44 16 C 44 13.800781 42.199219 12 40 12 Z "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 573 B |
BIN
app/assets/images/logo-deliverME-white.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
app/assets/images/logo-releaseME-black.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
app/assets/images/logo_castme.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
app/assets/images/logo_deliverme.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
app/assets/images/logo_directme.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
app/assets/images/logo_editme.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/assets/images/logo_expenseme.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
app/assets/images/logo_releaseme.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
155
app/assets/images/releaseme-logo.svg
Normal file
@@ -0,0 +1,155 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="1003.765px" height="581.324px" viewBox="0 0 1003.765 581.324" enable-background="new 0 0 1003.765 581.324"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="355.3584" y1="337.6338" x2="667.3279" y2="21.1429">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="0.3602" style="stop-color:#020101"/>
|
||||
<stop offset="0.7097" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_1_)" d="M644.937,312.306c4.518,2.543,8.047,6.601,8.795,11.832c2.063,14.346,3.597,28.706,4.766,43.094
|
||||
V10.74H345.266v326.469c2.965-7.496,6.708-14.713,11.483-21.525c0.657-0.93,1.354-1.755,2.08-2.528V24.308h286.108V312.306z"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="313.311" y1="337.875" x2="663.7488" y2="337.875">
|
||||
<stop offset="0" style="stop-color:#020101"/>
|
||||
<stop offset="1" style="stop-color:#D20000"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_2_)" d="M660.762,367.118v-28.663c0-28.471-12.821-46.185-38.115-52.676
|
||||
c-19.553-5.01-56.349-26.795-67.248-33.399c-11.131-4.921-13.731,8.246-8.204,10.883c9.652,5.906,49.704,29.923,72.083,35.659
|
||||
c19.309,4.944,27.916,17.144,27.916,39.533v28.729c-0.006,0.533-0.526,13.157-24.29,22.262l-3.214,1.231
|
||||
c-26.231,10.081-53.206,20.568-114.142,20.205l-7.334-0.085v0.085c-60.854,0.349-87.893-10.124-114.14-20.205l-3.203-1.231
|
||||
c-23.766-9.104-24.295-21.729-24.304-22.328v-28.663c0-22.39,8.611-34.589,27.924-39.533
|
||||
c22.347-5.727,62.296-29.678,72.032-35.636c5.171-3.533-1.755-15.134-8.293-10.826c-11.066,6.708-47.62,28.323-67.103,33.319
|
||||
C355.829,292.271,343,309.99,343,338.455l0.009,28.428c-0.035,0.905-0.409,22.427,33.014,35.23l3.188,1.222
|
||||
c27.042,10.397,54.949,21.125,116.447,21.12c2.029,0,4.112-0.01,6.224-0.033c65.956,0.793,94.745-10.35,122.676-21.087
|
||||
l3.194-1.222C661.178,389.31,660.79,367.783,660.762,367.118z"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="410.1826" y1="98.9678" x2="410.1826" y2="116.1672">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_3_)" d="M439.963,102.392c-1.394,4.345-2.333,8.878-2.762,13.572h-56.799v-13.572H439.963z"/>
|
||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="593.5845" y1="98.9678" x2="593.5845" y2="116.1672">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_4_)" d="M623.365,102.392v13.572h-56.797c-0.43-4.694-1.369-9.228-2.763-13.572H623.365z"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="408.6396" y1="138.3242" x2="408.6396" y2="152.4173">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<rect x="380.402" y="139.777" fill="url(#SVGID_5_)" width="56.476" height="13.568"/>
|
||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="595.127" y1="138.3242" x2="595.127" y2="152.4173">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<rect x="566.889" y="139.777" fill="url(#SVGID_6_)" width="56.476" height="13.568"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="410.2896" y1="176.3193" x2="410.2896" y2="193.0743">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_7_)" d="M437.254,177.159c0.483,4.694,1.475,9.223,2.923,13.572h-59.775v-13.572H437.254z"/>
|
||||
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="593.4771" y1="176.3193" x2="593.4771" y2="193.0743">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_8_)" d="M623.365,177.159v13.572h-59.775c1.448-4.35,2.441-8.878,2.924-13.572H623.365z"/>
|
||||
</g>
|
||||
<linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="423.3101" y1="146.2383" x2="580.458" y2="146.2383">
|
||||
<stop offset="0" style="stop-color:#D20000"/>
|
||||
<stop offset="1" style="stop-color:#020101"/>
|
||||
</linearGradient>
|
||||
<path fill="url(#SVGID_9_)" d="M580.19,115.964c-0.349-4.671-1.127-9.199-2.254-13.572
|
||||
c-8.794-33.763-39.555-58.784-76.053-58.784s-67.257,25.021-76.053,58.784c-1.126,4.373-1.904,8.901-2.254,13.572
|
||||
c-0.188,2.038-0.267,4.133-0.267,6.223v48.108c0,2.307,0.107,4.614,0.321,6.864c0.375,4.642,1.179,9.171,2.387,13.572
|
||||
c8.983,33.438,39.582,58.139,75.865,58.139c36.284,0,66.882-24.701,75.866-58.139c1.208-4.401,2.012-8.931,2.388-13.572
|
||||
c0.213-2.25,0.321-4.558,0.321-6.864v-48.108C580.458,120.097,580.378,118.002,580.19,115.964z M566.889,170.295
|
||||
c0,2.307-0.134,4.614-0.376,6.864c-0.482,4.694-1.476,9.223-2.924,13.572c-8.555,25.876-32.984,44.595-61.706,44.595
|
||||
c-28.721,0-53.15-18.719-61.706-44.595c-1.448-4.35-2.44-8.878-2.923-13.572c-0.241-2.25-0.376-4.558-0.376-6.864v-48.108
|
||||
c0-2.09,0.107-4.185,0.323-6.223c0.429-4.694,1.368-9.228,2.762-13.572c8.394-26.201,32.958-45.212,61.92-45.212
|
||||
s53.527,19.011,61.921,45.212c1.394,4.345,2.333,8.878,2.763,13.572c0.214,2.038,0.322,4.133,0.322,6.223V170.295z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#1D1D2C" d="M10.784,569.702V469.555H73.22c16.924,0.193,27.735,0.613,32.43,1.245
|
||||
c8.265,1.075,13.988,4.793,17.168,11.151c2.103,4.256,3.155,10.199,3.155,17.828c0,5.773-0.318,10.161-0.954,13.171
|
||||
c-0.637,3.01-1.761,5.416-3.375,7.228c-2.594,2.887-6.652,4.916-12.18,6.09c4.303,0.736,7.728,2.396,10.272,4.991
|
||||
c1.908,2.052,3.13,4.496,3.669,7.336c0.537,2.835,0.807,8.463,0.807,16.874v14.233h-13.354v-9.614
|
||||
c0-8.166-0.172-13.813-0.513-16.945c-0.979-6.359-4.574-10.077-10.787-11.152c-2.446-0.34-4.634-0.552-6.564-0.623
|
||||
c-1.934-0.075-8.524-0.137-19.774-0.184H24.136v38.519H10.784z M24.136,519.441H73.22c13.011,0,21.35-0.179,25.019-0.547
|
||||
c3.669-0.368,6.652-1.283,8.952-2.75c3.422-2.251,5.135-7.779,5.135-16.582c0-7.581-1.588-12.497-4.768-14.747
|
||||
c-2.054-1.468-5.272-2.425-9.648-2.864c-4.378-0.438-12.607-0.66-24.689-0.66H24.136V519.441z"/>
|
||||
<path fill="#1D1D2C" d="M221.71,548.204h11.52c-0.197,8.317-3.131,14.124-8.805,17.427c-5.674,3.302-15.552,4.953-29.64,4.953
|
||||
c-11.642,0-20.482-0.453-26.523-1.358c-6.043-0.906-10.601-2.435-13.683-4.586c-7.044-4.991-10.566-14.846-10.566-29.569
|
||||
c0-10.316,1.492-18.072,4.475-23.257c2.836-4.939,6.935-8.279,12.289-10.016c5.358-1.735,14.295-2.604,26.816-2.604
|
||||
c11.984,0,20.642,0.637,25.975,1.905c5.33,1.274,9.585,3.718,12.765,7.341c2.643,3.033,4.452,6.553,5.431,10.563
|
||||
c0.977,4.01,1.516,10.128,1.613,18.342h-77.551c0.294,8.661,2.005,14.601,5.136,17.832c2.102,2.198,5.466,3.703,10.087,4.51
|
||||
c4.622,0.807,12.143,1.212,22.563,1.212c7.875,0,13.646-0.367,17.314-1.104c3.669-0.73,6.406-2.075,8.217-4.033
|
||||
C220.512,554.247,221.37,551.729,221.71,548.204z M221.197,527.957c-0.048-6.803-1.859-11.544-5.429-14.238
|
||||
c-2.348-1.807-5.639-3.066-9.868-3.778c-4.233-0.708-10.433-1.062-18.6-1.062c-7.142,0-12.583,0.33-16.325,0.991
|
||||
c-3.741,0.66-6.665,1.82-8.768,3.485c-3.473,2.689-5.479,7.553-6.016,14.602H221.197z"/>
|
||||
<path fill="#1D1D2C" d="M252.958,469.555h11.006v100.147h-11.006V469.555z"/>
|
||||
<path fill="#1D1D2C" d="M361.682,548.204h11.52c-0.197,8.317-3.131,14.124-8.805,17.427c-5.674,3.302-15.554,4.953-29.64,4.953
|
||||
c-11.642,0-20.483-0.453-26.523-1.358c-6.043-0.906-10.603-2.435-13.683-4.586c-7.045-4.991-10.566-14.846-10.566-29.569
|
||||
c0-10.316,1.492-18.072,4.475-23.257c2.836-4.939,6.934-8.279,12.289-10.016c5.358-1.735,14.295-2.604,26.816-2.604
|
||||
c11.983,0,20.642,0.637,25.974,1.905c5.331,1.274,9.586,3.718,12.766,7.341c2.643,3.033,4.452,6.553,5.431,10.563
|
||||
c0.977,4.01,1.516,10.128,1.613,18.342h-77.551c0.294,8.661,2.004,14.601,5.135,17.832c2.103,2.198,5.466,3.703,10.088,4.51
|
||||
c4.622,0.807,12.143,1.212,22.561,1.212c7.875,0,13.646-0.367,17.316-1.104c3.667-0.73,6.406-2.075,8.216-4.033
|
||||
C360.484,554.247,361.34,551.729,361.682,548.204z M361.169,527.957c-0.048-6.803-1.86-11.544-5.43-14.238
|
||||
c-2.347-1.807-5.637-3.066-9.868-3.778c-4.231-0.708-10.431-1.062-18.6-1.062c-7.141,0-12.583,0.33-16.324,0.991
|
||||
c-3.741,0.66-6.665,1.82-8.768,3.485c-3.473,2.689-5.479,7.553-6.016,14.602H361.169z"/>
|
||||
<path fill="#1D1D2C" d="M404.447,520.984h-11.445c-0.099-1.27-0.147-2.273-0.147-3.01c0-7.284,2.739-12.252,8.217-14.894
|
||||
c5.477-2.642,15.75-3.962,30.816-3.962c11.836,0,20.347,0.542,25.532,1.613c5.183,1.08,9.245,3.132,12.179,6.165
|
||||
c2.445,2.448,4.035,5.331,4.769,8.657c0.733,3.325,1.1,9.146,1.1,17.464v36.684h-10.93l0.22-8.143h-0.589
|
||||
c-2.739,3.816-6.028,6.27-9.868,7.373c-3.84,1.1-11.089,1.651-21.752,1.651c-12.181,0-20.642-0.297-25.388-0.882
|
||||
c-4.743-0.585-8.339-1.808-10.784-3.671c-2.446-1.76-4.146-3.858-5.1-6.307c-0.953-2.444-1.432-5.869-1.432-10.271
|
||||
c0-5.382,0.722-9.515,2.166-12.402c1.442-2.882,3.875-5.038,7.3-6.453c3.716-1.618,12.4-2.425,26.045-2.425
|
||||
c14.282,0,23.626,0.43,28.028,1.283c4.402,0.858,7.801,2.925,10.198,6.203h0.956v-7.486c0-6.114-1.103-10.516-3.304-13.204
|
||||
c-1.955-2.396-4.927-4.024-8.914-4.883c-3.987-0.854-10.503-1.283-19.552-1.283c-11.887,0-19.579,0.698-23.076,2.095
|
||||
c-3.498,1.392-5.246,4.439-5.246,9.133C404.447,520.276,404.447,520.593,404.447,520.984z M432.621,537.934
|
||||
c-9.05,0-15.385,0.156-19.002,0.477c-3.622,0.316-6.189,0.916-7.705,1.798c-2.887,1.712-4.329,4.868-4.329,9.464
|
||||
c0,4.547,1.712,7.633,5.135,9.246c2.789,1.32,12.179,1.98,28.174,1.98c11.69,0,19.418-0.759,23.186-2.278
|
||||
c3.766-1.514,5.648-4.595,5.648-9.241c0-4.5-2.102-7.529-6.309-9.1C453.213,538.717,444.946,537.934,432.621,537.934z"/>
|
||||
<path fill="#1D1D2C" d="M494.095,545.416h11.74c0,0.981,0,1.642,0,1.981c0,3.77,0.404,6.458,1.212,8.071
|
||||
c0.807,1.613,2.408,2.859,4.803,3.741c3.18,1.128,12.327,1.688,27.442,1.688c9.438,0,16.005-0.207,19.7-0.622
|
||||
c3.691-0.415,6.345-1.236,7.958-2.463c2.203-1.66,3.304-4.227,3.304-7.698c0-4.209-1.321-7.082-3.964-8.624
|
||||
c-2.641-1.543-7.46-2.288-14.452-2.236c-2.201,0.048-12.792-0.075-31.769-0.368c-4.94-0.099-8.891-0.49-11.849-1.174
|
||||
c-2.961-0.685-5.418-1.783-7.374-3.303c-3.718-2.887-5.576-7.751-5.576-14.601c0-7.236,2.322-12.497,6.969-15.775
|
||||
c2.446-1.708,6.456-2.943,12.033-3.703c5.576-0.76,13.598-1.137,24.065-1.137c11.69,0,20.31,0.637,25.863,1.905
|
||||
c5.55,1.274,9.624,3.524,12.216,6.751c1.27,1.566,2.092,3.218,2.459,4.954c0.367,1.735,0.548,4.66,0.548,8.765h-11.593
|
||||
c-0.047-4.255-0.758-7.044-2.126-8.359c-1.516-1.52-4.45-2.618-8.804-3.303c-4.353-0.688-10.664-1.028-18.929-1.028
|
||||
c-15.066,0-24.211,1.053-27.442,3.156c-2.347,1.566-3.52,4.203-3.52,7.921c0,3.816,1.368,6.335,4.107,7.558
|
||||
c2.74,1.222,8.268,1.764,16.582,1.617c8.218-0.15,18.856,0.048,31.916,0.586c7.923,0.292,13.635,2.028,17.131,5.207
|
||||
c3.498,3.18,5.247,8.219,5.247,15.115c0,4.156-0.612,7.534-1.835,10.124c-1.222,2.595-3.204,4.722-5.942,6.383
|
||||
c-2.397,1.467-6.311,2.51-11.739,3.118c-5.43,0.613-13.281,0.92-23.552,0.92c-11.494,0-20.019-0.345-25.569-1.028
|
||||
c-5.552-0.685-9.625-1.906-12.216-3.671c-2.641-1.759-4.487-3.887-5.54-6.382c-1.052-2.496-1.578-5.968-1.578-10.417
|
||||
C494.022,548.496,494.046,547.275,494.095,545.416z"/>
|
||||
<path fill="#1D1D2C" d="M677.144,548.204h11.518c-0.195,8.317-3.13,14.124-8.803,17.427c-5.676,3.302-15.555,4.953-29.642,4.953
|
||||
c-11.642,0-20.482-0.453-26.523-1.358c-6.041-0.906-10.602-2.435-13.683-4.586c-7.043-4.991-10.566-14.846-10.566-29.569
|
||||
c0-10.316,1.493-18.072,4.477-23.257c2.837-4.939,6.934-8.279,12.289-10.016c5.356-1.735,14.295-2.604,26.817-2.604
|
||||
c11.982,0,20.64,0.637,25.972,1.905c5.331,1.274,9.587,3.718,12.767,7.341c2.641,3.033,4.45,6.553,5.429,10.563
|
||||
s1.516,10.128,1.613,18.342h-77.551c0.294,8.661,2.006,14.601,5.136,17.832c2.103,2.198,5.466,3.703,10.088,4.51
|
||||
c4.623,0.807,12.144,1.212,22.562,1.212c7.875,0,13.646-0.367,17.314-1.104c3.669-0.73,6.409-2.075,8.219-4.033
|
||||
C675.946,554.247,676.801,551.729,677.144,548.204z M676.63,527.957c-0.049-6.803-1.858-11.544-5.43-14.238
|
||||
c-2.348-1.807-5.636-3.066-9.868-3.778c-4.23-0.708-10.431-1.062-18.599-1.062c-7.144,0-12.583,0.33-16.326,0.991
|
||||
c-3.741,0.66-6.664,1.82-8.768,3.485c-3.472,2.689-5.477,7.553-6.015,14.602H676.63z"/>
|
||||
<path fill="#E40C2B" d="M709.343,469.555h26.707l54.367,84.886l53.486-84.886h26.854v100.147h-13.354v-87.751h-5.576
|
||||
l-55.907,87.751h-10.86l-56.493-87.751h-5.87v87.751h-13.353V469.555z"/>
|
||||
<path fill="#E40C2B" d="M894.74,569.702V469.555h98.167v11.736h-84.816v30.598h81.221v11.737h-81.221v34.334h84.889v11.742H894.74
|
||||
z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
67
app/assets/javascripts/appearance_release_import.js
Normal file
@@ -0,0 +1,67 @@
|
||||
function beforeUnloadHandler(e){
|
||||
e.preventDefault();
|
||||
e.returnValue = '';
|
||||
}
|
||||
|
||||
function turbolinksClickHandler(){
|
||||
return false;
|
||||
}
|
||||
|
||||
let eventListenersAdded = false;
|
||||
|
||||
function addEventListenersForUploadProgresBar(){
|
||||
if (eventListenersAdded) return;
|
||||
eventListenersAdded = true;
|
||||
addEventListener("direct-upload:initialize", event => {
|
||||
const { detail } = event;
|
||||
const { id, file } = detail;
|
||||
const progressBar = App.FileUploadProgress.createProgressBar(id);
|
||||
|
||||
$('#upload-progress-container').append(progressBar);
|
||||
});
|
||||
|
||||
addEventListener("direct-upload:start", event => {
|
||||
const { id } = event.detail;
|
||||
App.FileUploadProgress.showProgressBar(id);
|
||||
})
|
||||
|
||||
addEventListener("direct-upload:progress", event => {
|
||||
const { id, progress } = event.detail;
|
||||
if (App.FileUploadProgress._findProgressBar(id)) {
|
||||
App.FileUploadProgress.updateProgressBar(id, progress);
|
||||
}
|
||||
})
|
||||
|
||||
addEventListener("direct-upload:error", event => {
|
||||
event.preventDefault();
|
||||
const { id, error } = event.detail;
|
||||
App.FileUploadProgress.showError(id, error);
|
||||
})
|
||||
|
||||
addEventListener("direct-upload:end", event => {
|
||||
const { id } = event.detail;
|
||||
App.FileUploadProgress.removeProgressBar(id)
|
||||
})
|
||||
}
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
addEventListenersForUploadProgresBar();
|
||||
window.removeEventListener('beforeunload', beforeUnloadHandler, true);
|
||||
$(document).off('turbolinks:click', turbolinksClickHandler);
|
||||
|
||||
$("#import_appearance_releases").click(function(e) {
|
||||
$('#appearance_release_file_upload').click();
|
||||
});
|
||||
|
||||
$('#appearance_release_file_upload').change(function(e) {
|
||||
$('#import_appearance_releases')
|
||||
.html('Importing...')
|
||||
.attr('disabled', 'disabled');
|
||||
|
||||
window.addEventListener('beforeunload', beforeUnloadHandler, true);
|
||||
$(document).on('turbolinks:click', turbolinksClickHandler);
|
||||
|
||||
const fileUploadForm = document.getElementById('appearance_releases_import');
|
||||
Rails.fire(fileUploadForm, 'submit');
|
||||
});
|
||||
});
|
||||
21
app/assets/javascripts/application.js
Normal file
@@ -0,0 +1,21 @@
|
||||
//= require rails-ujs
|
||||
//= require jquery3
|
||||
//= require activestorage
|
||||
//= require turbolinks
|
||||
//= require turbolinks-csp
|
||||
//= require popper
|
||||
//= require bootstrap-sprockets
|
||||
//= require bootstrap-datepicker
|
||||
//= require bs-custom-file-input/dist/bs-custom-file-input
|
||||
//= require clappr/dist/clappr
|
||||
//= require clappr-markers-plugin/dist/clappr-markers-plugin
|
||||
//= require clappr-playback-rate-plugin/dist/clappr-playback-rate-plugin
|
||||
//= require spark-md5/spark-md5
|
||||
//= require js-sha256/src/sha256
|
||||
//= require evaporate/evaporate
|
||||
//= require exif-js
|
||||
//= require signature_pad/dist/signature_pad
|
||||
//= require signature_pad-extensions
|
||||
//= require smpte-timecode/smpte-timecode
|
||||
//= require trix/dist/trix
|
||||
//= require_tree .
|
||||
13
app/assets/javascripts/cable.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// Action Cable provides the framework to deal with WebSockets in Rails.
|
||||
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
|
||||
//
|
||||
//= require action_cable
|
||||
//= require_self
|
||||
//= require_tree ./channels
|
||||
|
||||
(function() {
|
||||
this.App || (this.App = {});
|
||||
|
||||
App.cable = ActionCable.createConsumer();
|
||||
|
||||
}).call(this);
|
||||
0
app/assets/javascripts/channels/.keep
Normal file
45
app/assets/javascripts/channels/broadcasts.coffee
Normal file
@@ -0,0 +1,45 @@
|
||||
$(document).on "turbolinks:load", ->
|
||||
# Only connect if a broadcast-token meta tag is present
|
||||
broadcast_meta = document.querySelector("meta[name=broadcast-token]")
|
||||
return unless broadcast_meta
|
||||
|
||||
broadcastToken = broadcast_meta.getAttribute("content")
|
||||
|
||||
App.public = App.cable.subscriptions.create { channel: "BroadcastsChannel", token: broadcastToken },
|
||||
connected: ->
|
||||
# Called when the subscription is ready for use on the server
|
||||
|
||||
disconnected: ->
|
||||
# Called when the subscription has been terminated by the server
|
||||
|
||||
received: (data) ->
|
||||
return unless document.querySelector("meta[name=broadcast-token][content='#{broadcastToken}']")
|
||||
switch data.event
|
||||
when "broadcast_stream_update" then @refreshBroadcastVideo(data)
|
||||
when "stream_recording_ready" then @showBroadcastRecordings(data)
|
||||
when "file_upload_update" then @refreshFilesTab(data)
|
||||
|
||||
refreshBroadcastVideo: (data) ->
|
||||
$("#broadcast_updates").html data.status_content
|
||||
if data.streamer_status == 'recording' && data.status == 'active'
|
||||
$("#broadcast_video").html data.video_content
|
||||
new (Clappr.Player)(
|
||||
parentId: '#broadcast_video'
|
||||
source: data.playback_url
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
mute: true,
|
||||
autoPlay: true,
|
||||
hlsMinimumDvrSize: 1)
|
||||
if data.streamer_status == "idle" && data.status == "idle"
|
||||
$("#broadcast_video").html data.video_content
|
||||
|
||||
showBroadcastRecordings: (data) ->
|
||||
$(".flash-message").html data.flash_content
|
||||
$("#broadcast_recordings").html data.recordings_content
|
||||
$("#broadcast_recordings_nav").html data.recordings_nav_content
|
||||
|
||||
refreshFilesTab: (data) ->
|
||||
$("#broadcast_file_list").html data.files_content
|
||||
$("#broadcast_files_pagination").html data.pagination_content
|
||||
|
||||
25
app/assets/javascripts/channels/projects.coffee
Normal file
@@ -0,0 +1,25 @@
|
||||
# TODO: Need to unsubscribe as project changes
|
||||
$(document).on "turbolinks:load", ->
|
||||
# Only connect if a project-id meta tag is present
|
||||
return unless meta = document.querySelector("meta[name=project-id]")
|
||||
# Get the id of the project to subscribe to
|
||||
projectId = meta.getAttribute("content")
|
||||
|
||||
App.projects = App.cable.subscriptions.create { channel: "ProjectsChannel", id: projectId },
|
||||
connected: ->
|
||||
# Called when the subscription is ready for use on the server
|
||||
|
||||
disconnected: ->
|
||||
# Called when the subscription has been terminated by the server
|
||||
|
||||
received: (data) ->
|
||||
switch data.event
|
||||
when "video_status_update" then @showVideoStatusUpdate(data.content)
|
||||
when "download_status_update" then @showDownloadStatusUpdate(data.content)
|
||||
when "conference_recording_ready" then @showDownloadStatusUpdate(data.content)
|
||||
|
||||
showVideoStatusUpdate: (content) ->
|
||||
$("[data-ujs-target='video-analysis-msg']").replaceWith content
|
||||
|
||||
showDownloadStatusUpdate: (content) ->
|
||||
$(".flash-message").html content
|
||||
18
app/assets/javascripts/clipboard.js
Normal file
@@ -0,0 +1,18 @@
|
||||
$(document).on("click", "[data-behavior=clipboard]", function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Copy the value of the link's `href` attribute
|
||||
var value = $(this).attr("href");
|
||||
|
||||
// Creates a temporary input field to copy from
|
||||
var copyToClipboard = function(value) {
|
||||
var $temp = $("<input>");
|
||||
$("body").append($temp);
|
||||
$temp.val(value).select();
|
||||
document.execCommand("copy");
|
||||
$temp.remove();
|
||||
}
|
||||
|
||||
// Perform the copying
|
||||
copyToClipboard(value);
|
||||
});
|
||||
11
app/assets/javascripts/collapse_select.js
Normal file
@@ -0,0 +1,11 @@
|
||||
$(document).on("change", "[data-toggle=collapse-select]", function(event) {
|
||||
const select = event.target;
|
||||
const target = select.dataset.target;
|
||||
const showValues = JSON.parse(select.dataset.showValues);
|
||||
|
||||
if (showValues.indexOf(select.value) > -1) {
|
||||
$(target).show("fast");
|
||||
} else {
|
||||
$(target).hide("fast");
|
||||
}
|
||||
});
|
||||
64
app/assets/javascripts/collapse_state.js
Normal file
@@ -0,0 +1,64 @@
|
||||
if (typeof(Storage) !== "undefined") {
|
||||
|
||||
class CollapseState {
|
||||
static get _KEY() { return "collapseState"; }
|
||||
|
||||
set(id, value) {
|
||||
const state = this._state;
|
||||
state[id] = value;
|
||||
window.localStorage.setItem(CollapseState._KEY, JSON.stringify(state));
|
||||
}
|
||||
|
||||
get(id) {
|
||||
const state = this._state;
|
||||
return state[id];
|
||||
}
|
||||
|
||||
get _state() {
|
||||
if (window.localStorage.getItem(CollapseState._KEY) === null) {
|
||||
window.localStorage.setItem(CollapseState._KEY, JSON.stringify({}));
|
||||
}
|
||||
|
||||
const data = window.localStorage.getItem(CollapseState._KEY);
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
// EVENT HANDLER - Store state when element is hidden
|
||||
$(document).on("hidden.bs.collapse", (event) => {
|
||||
const state = new CollapseState();
|
||||
state.set(event.target.id, "hide");
|
||||
});
|
||||
|
||||
// EVENT HANDLER - Store state when element is shown
|
||||
$(document).on("shown.bs.collapse", (event) => {
|
||||
const state = new CollapseState();
|
||||
state.set(event.target.id, "show");
|
||||
});
|
||||
|
||||
// Set initial state of elements from storage
|
||||
$(document).on("turbolinks:load", function() {
|
||||
const state = new CollapseState();
|
||||
const collapsibles = document.querySelectorAll("[data-toggle=collapse][data-behavior=stateful]");
|
||||
|
||||
collapsibles.forEach( (collapsible) => {
|
||||
const targetSelector = collapsible.dataset.target.toString().replace("#", "");
|
||||
const target = document.getElementById(targetSelector);
|
||||
const value = state.get(target.id);
|
||||
|
||||
switch (value) {
|
||||
case "show":
|
||||
collapsible.classList.remove("collapsed");
|
||||
target.classList.add("show");
|
||||
break;
|
||||
case "hide":
|
||||
collapsible.classList.add("collapsed");
|
||||
target.classList.remove("show");
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log("No Web Storage support..");
|
||||
}
|
||||
3
app/assets/javascripts/custom_file_input.js
Normal file
@@ -0,0 +1,3 @@
|
||||
$(document).on("turbolinks:load", function() {
|
||||
bsCustomFileInput.init()
|
||||
})
|
||||
55
app/assets/javascripts/digital_signature.js
Normal file
@@ -0,0 +1,55 @@
|
||||
App.DigitalSignature = {
|
||||
init: function(canvas) {
|
||||
var signatureInput = $(canvas).data("signature-input");
|
||||
var $clearButton = $("[data-behavior=clear-digital-signature]")
|
||||
|
||||
var opts = {
|
||||
onEnd: function() {
|
||||
// Save the signature to an input field
|
||||
if ($(signatureInput).length) {
|
||||
$(signatureInput).val(signaturePad.crop().toDataURL());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize using SignaturePad plugin
|
||||
var signaturePad = new SignaturePad(canvas, opts);
|
||||
|
||||
// Handler for when the browser window is resized
|
||||
// Stores the current signature and restores it after the resizing
|
||||
function resizeCanvas() {
|
||||
var cachedSignature = null;
|
||||
|
||||
if (!signaturePad.isEmpty()) {
|
||||
var cachedSignature = signaturePad.toDataURL();
|
||||
}
|
||||
|
||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||
canvas.width = canvas.offsetWidth * ratio;
|
||||
canvas.height = canvas.offsetHeight * ratio;
|
||||
canvas.getContext("2d").scale(ratio, ratio);
|
||||
signaturePad.clear(); // otherwise isEmpty() might return incorrect value
|
||||
|
||||
if (cachedSignature !== null) {
|
||||
signaturePad.fromDataURL(cachedSignature);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach resize handler and trigger it once for an initial sizing
|
||||
window.addEventListener("resize", resizeCanvas);
|
||||
resizeCanvas();
|
||||
|
||||
// Attach clear button handler
|
||||
$clearButton.on("click", function(e) {
|
||||
e.preventDefault();
|
||||
signaturePad.clear();
|
||||
$(signatureInput).val("");
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
$("[data-behavior=digital-signature]").each(function(index, element) {
|
||||
App.DigitalSignature.init(element);
|
||||
});
|
||||
});
|
||||
31
app/assets/javascripts/evaporate.js
Normal file
@@ -0,0 +1,31 @@
|
||||
$(document).on("click", "#video_submit_button", (event) => {
|
||||
const input = $("#video_file");
|
||||
|
||||
if (input[0].files.length > 0) {
|
||||
event.preventDefault();
|
||||
|
||||
const bucket = input.data("aws-bucket");
|
||||
const awsAccessKeyId = input.data("aws-access-key-id");
|
||||
const signerUrl = input.data("signer-url");
|
||||
|
||||
const uploader = Evaporate.create({
|
||||
logging: false,
|
||||
signerUrl: signerUrl,
|
||||
aws_key: awsAccessKeyId,
|
||||
bucket: bucket,
|
||||
cloudfront: false,
|
||||
computeContentMd5: true,
|
||||
cryptoMd5Method: (d) => btoa(SparkMD5.ArrayBuffer.hash(d, true)),
|
||||
cryptoHexEncodedHash256: sha256,
|
||||
});
|
||||
|
||||
const submitButton = $(event.target);
|
||||
submitButton.prop('disabled', true);
|
||||
|
||||
const allFilesCompleted = () => {
|
||||
input.value = '';
|
||||
$("#video_form").submit();
|
||||
}
|
||||
uploadFile(input[0].files[0], uploader, input, allFilesCompleted)
|
||||
}
|
||||
});
|
||||
59
app/assets/javascripts/file_upload_progress.js
Normal file
@@ -0,0 +1,59 @@
|
||||
App.FileUploadProgress = {
|
||||
// Creates a new progress bar element
|
||||
createProgressBar: id => {
|
||||
// <div class="progress">
|
||||
const progress = document.createElement("div")
|
||||
progress.setAttribute("id", `progress_bar_${id}`)
|
||||
progress.setAttribute("hidden", "hidden")
|
||||
progress.classList.add("mt-2")
|
||||
progress.classList.add("progress")
|
||||
|
||||
// <div class="progress-bar">
|
||||
const progressBar = document.createElement("div")
|
||||
progressBar.setAttribute("role", "progressbar")
|
||||
progressBar.setAttribute("aria-valuenow", "0")
|
||||
progressBar.setAttribute("aria-valuemin", "0")
|
||||
progressBar.setAttribute("aria-valuemax", "100")
|
||||
progressBar.classList.add("progress-bar")
|
||||
|
||||
progress.appendChild(progressBar)
|
||||
|
||||
return progress
|
||||
},
|
||||
|
||||
// Unhides an existing progress bar
|
||||
showProgressBar: id => {
|
||||
const progressBar = App.FileUploadProgress._findProgressBar(id)
|
||||
progressBar.removeAttribute("hidden")
|
||||
progressBar.firstChild.setAttribute("style", "width: 0%")
|
||||
},
|
||||
|
||||
// Updates the progress of an existing progress bar
|
||||
updateProgressBar: (id, progress) => {
|
||||
const progressBar = App.FileUploadProgress._findProgressBar(id)
|
||||
progressBar.firstChild.setAttribute("style", `width: ${progress}%`)
|
||||
progressBar.firstChild.setAttribute("aria-valuenow", progress)
|
||||
},
|
||||
|
||||
// Shows an error message
|
||||
showError: (id, error) => {
|
||||
// <div class="text-danger>...</div>
|
||||
const errorMsg = document.createElement("div")
|
||||
errorMsg.classList.add("text-danger")
|
||||
errorMsg.appendChild( document.createTextNode(error) )
|
||||
|
||||
const progressBar = App.FileUploadProgress._findProgressBar(id)
|
||||
progressBar.parentNode.replaceChild(errorMsg, progressBar)
|
||||
},
|
||||
|
||||
// Removes a progress bar element from the document
|
||||
removeProgressBar: id => {
|
||||
const progressBar = App.FileUploadProgress._findProgressBar(id)
|
||||
progressBar.parentNode.removeChild(progressBar)
|
||||
},
|
||||
|
||||
// Finds a progress bar element by the given ID
|
||||
_findProgressBar: id => {
|
||||
return document.getElementById(`progress_bar_${id}`)
|
||||
}
|
||||
}
|
||||
50
app/assets/javascripts/multi_view_broadcasts.js
Normal file
@@ -0,0 +1,50 @@
|
||||
$(document).on("click", "[data-behavior=select_broadcast]", function() {
|
||||
var _this = this;
|
||||
var checkbox = $(this).children("input:checkbox");
|
||||
|
||||
selectBroadcast(_this, checkbox);
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-behavior=select_broadcast] input[type='checkbox']", function(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
var _this = this;
|
||||
var checkbox = $(this);
|
||||
|
||||
selectBroadcast(_this, checkbox);
|
||||
});
|
||||
|
||||
function selectBroadcast(clicked_element, checkbox) {
|
||||
if (clicked_element.hasChildNodes()) {
|
||||
if(checkbox.prop("checked")) {
|
||||
checkbox.prop('checked', false);
|
||||
} else {
|
||||
checkbox.prop('checked', true);
|
||||
}
|
||||
}
|
||||
|
||||
var checked = checkbox.prop("checked");
|
||||
var project_id = JSON.parse($('#multi_view_broadcasts').parent().attr('data-project-id'));
|
||||
var broadcast_ids = JSON.parse($('#multi_view_broadcasts').parent().attr('data-broadcast-ids'));
|
||||
var selected_broadcast_id = checkbox.val();
|
||||
|
||||
if (checked && !broadcast_ids.includes(selected_broadcast_id)) {
|
||||
broadcast_ids.push(selected_broadcast_id);
|
||||
} else if(!checked && broadcast_ids.includes(selected_broadcast_id)) {
|
||||
broadcast_ids.splice( $.inArray(selected_broadcast_id, broadcast_ids), 1 );
|
||||
}
|
||||
|
||||
$('#multi_view_broadcasts').parent().attr('data-broadcast-ids', JSON.stringify(broadcast_ids));
|
||||
|
||||
if (broadcast_ids.length >= 2) {
|
||||
multi_view_ids = $.param({multi_view_ids: broadcast_ids});
|
||||
broadcast_url = "/en/projects/" + project_id + "/broadcasts/" + broadcast_ids[0] + "?" + multi_view_ids
|
||||
|
||||
$("#multi_view_broadcasts").attr("href", broadcast_url);
|
||||
$("#multi_view_broadcasts").attr("target", "_blank");
|
||||
$("#multi_view_broadcasts").removeClass('disabled');
|
||||
} else if (broadcast_ids.length < 2) {
|
||||
$("#multi_view_broadcasts").attr("href", "javascript:void(0);");
|
||||
$("#multi_view_broadcasts").addClass('disabled');
|
||||
}
|
||||
}
|
||||
60
app/assets/javascripts/photo_preview.js
Normal file
@@ -0,0 +1,60 @@
|
||||
App.PhotoPreview = {
|
||||
init: function(element) {
|
||||
var fileInput = $(element).data("file-input");
|
||||
|
||||
// Listen for changes on the file input field
|
||||
$(fileInput).on("change", function(e) {
|
||||
var input = e.target;
|
||||
|
||||
if (input.files && input.files[0]) {
|
||||
var reader = new FileReader();
|
||||
|
||||
// Load a preview of the image and append to the container
|
||||
reader.onload = function (e) {
|
||||
App.PhotoPreview.set(element, e.target.result);
|
||||
}
|
||||
|
||||
reader.readAsDataURL(input.files[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
set: function(element, imgSrc) {
|
||||
var img = $("<img>").attr('src', imgSrc).addClass("img-thumbnail photo-preview");
|
||||
$(element).html(img);
|
||||
App.PhotoPreview.autoOrient(img.get(0));
|
||||
},
|
||||
autoOrient: function(image) {
|
||||
// Get the EXIF data from the image
|
||||
EXIF.getData(image, function() {
|
||||
var orientation = EXIF.getTag(this, "Orientation");
|
||||
|
||||
// Rotate the image to ensure it appears right side up
|
||||
switch (orientation) {
|
||||
case 6:
|
||||
$(image).css('transform', 'rotate(90deg)');
|
||||
break;
|
||||
case 8:
|
||||
$(image).css('transform', 'rotate(270deg)');
|
||||
break;
|
||||
case 3:
|
||||
$(image).css('transform', 'rotate(180deg)');
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
$("[data-behavior=person-photo-preview]").each(function(index, element) {
|
||||
App.PhotoPreview.init(element);
|
||||
});
|
||||
$("[data-behavior=guardian-photo-preview]").each(function(index, element) {
|
||||
App.PhotoPreview.init(element);
|
||||
});
|
||||
$("[data-behavior=take-person-photo]").click(function(e) {
|
||||
$("[data-ujs-target=person-photo-input]").trigger("click");
|
||||
});
|
||||
$("[data-behavior=take-guardian-photo]").click(function(e) {
|
||||
$("[data-ujs-target=guardian-photo-input]").trigger("click");
|
||||
});
|
||||
});
|
||||
24
app/assets/javascripts/play_previous_recordings.js
Normal file
@@ -0,0 +1,24 @@
|
||||
$(document).on("click", "[data-behavior=play_recording]", function() {
|
||||
if ($(this).hasClass('active')){
|
||||
return false;
|
||||
}
|
||||
|
||||
var playback_url = $(this).attr("data-playback-url")
|
||||
$("#broadcast_video").empty();
|
||||
|
||||
new Clappr.Player({
|
||||
parentId: '#broadcast_video',
|
||||
source: playback_url,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
autoPlay: true
|
||||
});
|
||||
|
||||
$(".dropdown-menu").children().removeClass('active');
|
||||
$(".dropdown-menu").children().children('i').remove();
|
||||
$(this).siblings().removeClass('active');
|
||||
$(this).siblings().children("i").remove();
|
||||
$(this).addClass('active');
|
||||
$(this).prepend('<i class="fa fa-check"> </i>');
|
||||
});
|
||||
|
||||
9
app/assets/javascripts/popover.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// $(document).on("turbolinks:load", function() {
|
||||
// $('body').popover({
|
||||
// selector: '[data-toggle=popover]'
|
||||
// });
|
||||
// });
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
$('[data-toggle=popover]').popover();
|
||||
});
|
||||
81
app/assets/javascripts/select_releasables.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// Makes releasables selectable and taggable.
|
||||
|
||||
$(document).on("click", "[data-behavior=all-selectable]", function() {
|
||||
var checkbox = $(this).children("input:checkbox");
|
||||
var checked = checkbox.prop("checked");
|
||||
|
||||
if (checked) {
|
||||
checkbox.prop("checked", false);
|
||||
} else {
|
||||
checkbox.prop("checked", true);
|
||||
}
|
||||
|
||||
var checkedNewValue = checkbox.prop("checked");
|
||||
|
||||
if(checkedNewValue) {
|
||||
$("[data-behavior=select]").children('input:checkbox').each(function() {
|
||||
if (!this.checked) {
|
||||
$(this).parent().trigger('click')
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$("[data-behavior=select]").children('input:checkbox').each(function() {
|
||||
if (this.checked) {
|
||||
$(this).parent().trigger('click')
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-behavior=select]", function() {
|
||||
var checkbox = $(this).children("input:checkbox");
|
||||
var checked = checkbox.prop("checked");
|
||||
|
||||
var releasable_ids = JSON.parse($('#tag_all').parent().attr('data-releasable-ids'));
|
||||
var selected_releasable_id = $(this).children('input').val();
|
||||
|
||||
if (checked) {
|
||||
checkbox.prop("checked", false);
|
||||
} else {
|
||||
checkbox.prop("checked", true);
|
||||
}
|
||||
|
||||
var checkedNewValue = checkbox.prop("checked");
|
||||
|
||||
if (checkedNewValue && !releasable_ids.includes(selected_releasable_id)) {
|
||||
releasable_ids.push(selected_releasable_id);
|
||||
} else if(!checkedNewValue && releasable_ids.includes(selected_releasable_id)) {
|
||||
releasable_ids.splice( $.inArray(selected_releasable_id, releasable_ids), 1 );
|
||||
}
|
||||
|
||||
if (releasable_ids.length >= 1) {
|
||||
$("#tag_all").prop('disabled', false);
|
||||
} else if (releasable_ids.length < 1) {
|
||||
$("#tag_all").prop('disabled', true);
|
||||
}
|
||||
|
||||
$('#tag_all').parent().attr('data-releasable-ids', JSON.stringify(releasable_ids));
|
||||
});
|
||||
|
||||
$(document).on("click", "#tag_all", function(event) {
|
||||
event.preventDefault();
|
||||
var releasable_ids = JSON.parse($(this).parent().attr('data-releasable-ids'));
|
||||
|
||||
if (releasable_ids.length >= 1) {
|
||||
var input_ids = $('<input>').attr({ type: 'hidden', name: 'releasable_ids', value: JSON.stringify(releasable_ids) });
|
||||
var input_name = $('<input>').attr({ type: 'hidden', name: 'releasable_name', value: $("tbody").attr("id") });
|
||||
|
||||
$(this).parent().append(input_ids);
|
||||
$(this).parent().append(input_name);
|
||||
|
||||
Rails.fire($(this).parent()[0], 'submit');
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-behavior=select] input:checkbox", function() {
|
||||
$(this).parent().trigger('click');
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-behavior=all-selectable] input:checkbox", function() {
|
||||
$(this).parent().trigger('click');
|
||||
});
|
||||
5
app/assets/javascripts/tooltip.js
Normal file
@@ -0,0 +1,5 @@
|
||||
$(document).on("turbolinks:load", function() {
|
||||
$('body').tooltip({
|
||||
selector: '[data-toggle=tooltip]'
|
||||
});
|
||||
});
|
||||
4
app/assets/javascripts/trix.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// Do not allow file attachments in rich text content
|
||||
addEventListener("trix-file-accept", function(event) {
|
||||
event.preventDefault();
|
||||
})
|
||||
12
app/assets/javascripts/turbolinks-csp.js
Normal file
@@ -0,0 +1,12 @@
|
||||
// Include CSP nonce for every Turbolinks request (see: content_security_policy.rb)
|
||||
document.addEventListener("turbolinks:request-start", function(event) {
|
||||
var xhr = event.data.xhr;
|
||||
xhr.setRequestHeader("X-Turbolinks-Nonce", $("meta[name='csp-nonce']").prop('content'));
|
||||
});
|
||||
|
||||
// Ensure all <script> tags on Turbolinks cached pages include a nonce
|
||||
document.addEventListener("turbolinks:before-cache", function() {
|
||||
$('script[nonce]').each(function(index, element) {
|
||||
$(element).attr('nonce', element.nonce);
|
||||
})
|
||||
})
|
||||
135
app/assets/javascripts/video_analysis.js
Normal file
@@ -0,0 +1,135 @@
|
||||
// TODO: Do we need to detach events when the page is changed?
|
||||
App.VideoAnalysis = {
|
||||
initPlayerWithAnalysis: function(videoElement, bookmarkData) {
|
||||
// Initialize the video player
|
||||
var player = App.VideoPlayer.init(videoElement);
|
||||
|
||||
// Attach custom keyboard shortcuts
|
||||
var oldBindKeyEvents = player.core.mediaControl.bindKeyEvents.bind(player.core.mediaControl);
|
||||
var oldUnbindKeyEvents = player.core.mediaControl.unbindKeyEvents.bind(player.core.mediaControl);
|
||||
player.core.mediaControl.bindKeyEvents = function() {
|
||||
oldBindKeyEvents();
|
||||
player.core.mediaControl.bindKeyAndShow('d', function() {
|
||||
var markersPlugin = player.getPlugin("markers-plugin");
|
||||
markersPlugin.getAll().forEach(function(marker, index) {
|
||||
if ( Math.abs(marker.getTime() - player.getCurrentTime()) < 2 ) {
|
||||
markersPlugin.removeMarker(marker);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
player.core.mediaControl.unbindKeyEvents = function() {
|
||||
console.log("Unbinding key events");
|
||||
oldUnbindKeyEvents();
|
||||
if (player.core.mediaControl.kibo) {
|
||||
player.core.mediaControl.kibo.off('d')
|
||||
}
|
||||
}
|
||||
|
||||
function ClassyMarker(time, tooltipText, cssClass) {
|
||||
this._cssClass = cssClass || null;
|
||||
ClapprMarkersPlugin.StandardMarker.call(this, time, tooltipText);
|
||||
}
|
||||
ClassyMarker.prototype = Object.create(ClapprMarkersPlugin.StandardMarker.prototype);
|
||||
ClassyMarker.prototype.constructor = ClassyMarker;
|
||||
ClassyMarker.prototype._buildMarkerEl = function() {
|
||||
var $marker = Clappr.$('<div />').addClass('standard-marker' + ' ' + this._cssClass);
|
||||
$marker.append(Clappr.$('<div />').addClass('standard-marker-inner'));
|
||||
return $marker;
|
||||
}
|
||||
|
||||
function RemovableMarker(time, tooltipText, cssClass, url) {
|
||||
this._url = url || null;
|
||||
ClassyMarker.call(this, time, tooltipText, cssClass);
|
||||
}
|
||||
RemovableMarker.prototype = Object.create(ClassyMarker.prototype);
|
||||
RemovableMarker.prototype.constructor = RemovableMarker;
|
||||
RemovableMarker.prototype.onDestroy = function() {
|
||||
$.ajax({ url: this.source._url, type: "DELETE", headers: { 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content') } });
|
||||
};
|
||||
|
||||
// Add existing bookmarks to the timeline and set up handlers for adding new bookmarks
|
||||
var addBookmark = function(bookmark) {
|
||||
var markers = player.getPlugin("markers-plugin");
|
||||
// TODO: Move the remove URL into a serializer?
|
||||
var marker = new RemovableMarker(Number(bookmark.time_elapsed), bookmark.notes + " (Press 'D' to delete)", "bookmark-marker", "/bookmarks/" + bookmark.id);
|
||||
markers.addMarker(marker);
|
||||
}
|
||||
bookmarkData.forEach(function(bookmark, index) {
|
||||
addBookmark(bookmark)
|
||||
});
|
||||
$(document).on("addBookmark-" + $(videoElement).data("video-id"), function(event, bookmark) {
|
||||
addBookmark(bookmark);
|
||||
});
|
||||
|
||||
// TODO: Where to put this??
|
||||
player.on("timeupdate", function(event) {
|
||||
$("[data-ujs-target=bookmark-form] input[name='bookmark[time_elapsed]']").val(event.current);
|
||||
$("[data-ujs-target=unreleased-appearance-form] input[name='unreleased_appearance[time_elapsed]']").val(event.current);
|
||||
$("[data-ujs-target=video-release-confirmation-form] input[name='video_release_confirmation[time_elapsed]']").val(event.current);
|
||||
$("[data-ujs-target=audio-confirmation-form] input[name='audio_confirmation[time_elapsed]']").val(event.current);
|
||||
$("[data-ujs-target=graphics-element-form] input[name='graphics_element[time_elapsed]']").val(event.current);
|
||||
$("[data-ujs-target=edl-event-form] input[name='edl_event[time_elapsed]']").val(event.current);
|
||||
});
|
||||
|
||||
// // Add event handler to show recommendations as the video plays
|
||||
player.on("timeupdate", function(event) {
|
||||
$("#suggested_matches [data-time]").each(function(index, item) {
|
||||
var time = $(item).data("time");
|
||||
if (event.current > time) {
|
||||
$(item).show();
|
||||
} else {
|
||||
$(item).hide();
|
||||
}
|
||||
});
|
||||
$("#graphics_elements_matches [data-time]").each(function(index, item) {
|
||||
var time = $(item).data("time");
|
||||
if (event.current > time) {
|
||||
$(item).show();
|
||||
} else {
|
||||
$(item).hide();
|
||||
}
|
||||
});
|
||||
$("#audio_matches [data-time]").each(function(index, item) {
|
||||
var time = $(item).data("time");
|
||||
if (event.current > time) {
|
||||
$(item).show();
|
||||
} else {
|
||||
$(item).hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on("click", "#seek_button", () => {
|
||||
try {
|
||||
const timecode = new Timecode($("#seek_input").val(), 30);
|
||||
player.seek(timecode.frameCount / timecode.frameRate);
|
||||
} catch(e) {
|
||||
$("#seek_input").val("")
|
||||
}
|
||||
});
|
||||
$(document).on('click',"[data-behavior~=seekable-timecode]", function(e){
|
||||
e.preventDefault();
|
||||
const timeCodeText = e.target.innerText;
|
||||
let extractedTime = timeCodeText;
|
||||
const startOfTimeCode = 8; // MOV TC: 00:00....
|
||||
if (timeCodeText.startsWith('MOV')){
|
||||
extractedTime = timeCodeText.slice(startOfTimeCode);
|
||||
}
|
||||
const timeCode = new Timecode(extractedTime, 30);
|
||||
player.seek(timeCode.frameCount / timeCode.frameRate);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on("click", "#go_button", function () {
|
||||
const timecode = $("#go_to").val()
|
||||
const $edlEvent = $(`[data-timecode-in='${timecode}']`).first()
|
||||
const edlEventContainer = $("#edl_events_list").parent();
|
||||
|
||||
if ($edlEvent.length > 0) {
|
||||
edlEventContainer.scrollTop($edlEvent.offset().top - edlEventContainer.offset().top + edlEventContainer.scrollTop())
|
||||
} else {
|
||||
$("#go_to").val("")
|
||||
}
|
||||
});
|
||||
97
app/assets/javascripts/video_player.js.erb
Normal file
@@ -0,0 +1,97 @@
|
||||
App.VideoPlayer = {
|
||||
init: function(videoElement) {
|
||||
// The source path for the video
|
||||
var videoUrl = $(videoElement).attr("src");
|
||||
|
||||
// Replace the standard <video> element with the Clappr player
|
||||
$(videoElement).replaceWith($("<div>", { id: "player" }));
|
||||
var player = new Clappr.Player({
|
||||
<%= "baseUrl: 'http://cdn.clappr.io/latest'," if Rails.env.test? %>
|
||||
source: videoUrl, parentId: "#player", width: "100%",
|
||||
focusElement: NotTypingSelectiveListenerElement(document), // Allows keyboard shortcuts to work even if the player itself isn't in focus
|
||||
plugins: {
|
||||
core: [ClapprMarkersPlugin, PlaybackRatePlugin]
|
||||
},
|
||||
markersPlugin: { markers: [] }
|
||||
});
|
||||
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on("turbolinks:load", function() {
|
||||
$("[data-behavior=video-player]").each(function(index, element) {
|
||||
App.VideoPlayer.init(element);
|
||||
});
|
||||
});
|
||||
|
||||
// An implementation of SelectiveListenerElement which only listens when text areas are not in focus
|
||||
var NotTypingSelectiveListenerElement = function(element) {
|
||||
var inputInFocus = function() { return $('input:focus, textarea:focus, video:focus').length > 0; }
|
||||
var noInputInFocus = function() { return !inputInFocus(); }
|
||||
|
||||
return new SelectiveListenerElement(element, noInputInFocus);
|
||||
}
|
||||
|
||||
// A helper class that maintains a collection of handlers for a given event and callback
|
||||
var HandlerCollection = function() {
|
||||
this.handlers = [];
|
||||
|
||||
// Stores a handler for a given event and callback
|
||||
this.store = function(eventName, originalCallback, handler) {
|
||||
var item = {
|
||||
eventName: eventName,
|
||||
callback: originalCallback,
|
||||
handler: handler,
|
||||
}
|
||||
this.handlers.push(item);
|
||||
}
|
||||
|
||||
// Finds a handler for a given event and callback
|
||||
this.find = function(eventName, callback) {
|
||||
var handler = null;
|
||||
for (var i = 0; i < this.handlers.length; i++) {
|
||||
var item = this.handlers[i];
|
||||
|
||||
if (item.eventName == eventName && item.callback == callback) {
|
||||
handler = item.handler;
|
||||
}
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
|
||||
// A helper class to allow a DOM element to be wrapped and selectively ignore events that are triggered on it
|
||||
var SelectiveListenerElement = function(element, shouldListenFn) {
|
||||
// The element we are proxying
|
||||
this.element = element;
|
||||
// A function that determines if we should run any event callbacks or not
|
||||
this.shouldRunCallbacks = shouldListenFn;
|
||||
// Stores the functions to be called when an event listener needs to be removed
|
||||
this.removeHandlers = new HandlerCollection();
|
||||
|
||||
this.addEventListener = function(eventName, callback, useCapture) {
|
||||
// Create a new callback function that conditionally calls the original
|
||||
var conditionalCallback = function() {
|
||||
if (this.shouldRunCallbacks()) {
|
||||
callback();
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
// Store the function to be called when removing the event listener
|
||||
this.removeHandlers.store(eventName, callback, function() {
|
||||
this.element.removeEventListener(eventName, conditionalCallback, useCapture);
|
||||
}.bind(this));
|
||||
|
||||
// Attach an event listener to the same event with the conditional callback function
|
||||
this.element.addEventListener(eventName, conditionalCallback, useCapture);
|
||||
}
|
||||
|
||||
this.removeEventListener = function(eventName, callback, useCapture) {
|
||||
var removeHandler = this.removeHandlers.find(eventName, callback);
|
||||
|
||||
if (removeHandler != null) {
|
||||
removeHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
app/assets/stylesheets/_bootstrap_overrides.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
// Bootstrap theme customization
|
||||
$white: #FFFFFF;
|
||||
$gray-100: #F7F8F9;
|
||||
$gray-300: #DEE5EB;
|
||||
$gray-400: #B2B6C2;
|
||||
$gray-500: #A6ACBE;
|
||||
$gray-800: #393C46;
|
||||
$gray-900: #2A2D36;
|
||||
$body-bg: $gray-100;
|
||||
$body-color: #4A4A4A;
|
||||
$primary: #6F89FF;
|
||||
$blue: #0092ff;
|
||||
$red: #F9002B;
|
||||
$green: #51B61B;
|
||||
$teal: #32C498;
|
||||
$purple: #5139EE;
|
||||
$dark: $gray-900;
|
||||
$success: $teal;
|
||||
$link-color: $body-color;
|
||||
$input-border-color: $gray-300;
|
||||
$dropdown-bg: $dark;
|
||||
$dropdown-color: $gray-500;
|
||||
$dropdown-link-color: $gray-500;
|
||||
$dropdown-link-hover-color: lighten($gray-500, 5%);
|
||||
$dropdown-link-hover-bg: $gray-800;
|
||||
$dropdown-link-active-bg: $gray-800;
|
||||
$font-size-base: 1rem;
|
||||
$font-weight-normal: 400;
|
||||
$font-weight-bold: 700;
|
||||
$h1-font-size: $font-size-base * 2.25;
|
||||
$h2-font-size: $font-size-base * 1.85;
|
||||
$h3-font-size: $font-size-base * 1.5;
|
||||
$h4-font-size: $font-size-base * 1.25;
|
||||
$h5-font-size: $font-size-base * 1.15;
|
||||
$h6-font-size: $font-size-base;
|
||||
$headings-font-weight: $font-weight-bold;
|
||||
$table-border-width: 0;
|
||||
$table-head-bg: $white;
|
||||
$table-head-color: $gray-400;
|
||||
$breadcrumb-bg: transparent;
|
||||
$breadcrumb-padding-x: 0;
|
||||
$breadcrumb-padding-y: 0;
|
||||
$breadcrumb-divider: "\f054";
|
||||
$breadcrumb-divider-color: $gray-400;
|
||||
$breadcrumb-active-color: $body-color;
|
||||
$nav-tabs-link-active-bg: $white;
|
||||
|
||||
.thead-light th {
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
font-family: "FontAwesome";
|
||||
}
|
||||
36
app/assets/stylesheets/actiontext.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
|
||||
// the trix-editor content (whether displayed or under editing). Feel free to incorporate this
|
||||
// inclusion directly in any other asset bundle and remove this file.
|
||||
//
|
||||
//= require trix/dist/trix
|
||||
|
||||
// We need to override trix.css’s image gallery styles to accommodate the
|
||||
// <action-text-attachment> element we wrap around attachments. Otherwise,
|
||||
// images in galleries will be squished by the max-width: 33%; rule.
|
||||
.trix-content {
|
||||
.attachment-gallery {
|
||||
> action-text-attachment,
|
||||
> .attachment {
|
||||
flex: 1 0 33%;
|
||||
padding: 0 0.5em;
|
||||
max-width: 33%;
|
||||
}
|
||||
|
||||
&.attachment-gallery--2,
|
||||
&.attachment-gallery--4 {
|
||||
> action-text-attachment,
|
||||
> .attachment {
|
||||
flex-basis: 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action-text-attachment {
|
||||
.attachment {
|
||||
padding: 0 !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
417
app/assets/stylesheets/application.scss
Normal file
@@ -0,0 +1,417 @@
|
||||
@import "bootstrap_overrides";
|
||||
@import "bootstrap";
|
||||
@import "font-awesome";
|
||||
@import "rails_bootstrap_forms";
|
||||
@import 'trix/dist/trix';
|
||||
@import "dropzone/dist/dropzone";
|
||||
@import 'bootstrap-datepicker';
|
||||
|
||||
// Wordmarks
|
||||
.suite-wordmark {
|
||||
font-weight: normal;
|
||||
span:first-child {
|
||||
background-color: $red;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
span:last-child {
|
||||
color: $body-color;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.product-wordmark {
|
||||
span:last-child {
|
||||
font-weight: normal;
|
||||
margin-left: 5px;
|
||||
padding: 0 2px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&.release-me {
|
||||
span:last-child {
|
||||
background-color: $teal;
|
||||
color: $body-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.direct-me {
|
||||
span:last-child {
|
||||
background-color: $green;
|
||||
color: $body-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.edit-me {
|
||||
span:last-child {
|
||||
background-color: $blue;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
&.deliver-me {
|
||||
span:last-child {
|
||||
background-color: $purple;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
span:last-child {
|
||||
background-color: $gray-500 !important;
|
||||
color: $body-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vertically align all direct descendant elements to the middle
|
||||
.align-all-middle tr td {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
// Create horizontal padding on table rows by targeting table cells
|
||||
@each $name, $value in $spacers {
|
||||
.tr-px-#{$name} {
|
||||
&, tr {
|
||||
th, td {
|
||||
&:first-child {
|
||||
padding-left: $value;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show the state of a collapse panel using up and down chevron circle icons
|
||||
.collapse-indicator {
|
||||
cursor: pointer;
|
||||
font-family: "FontAwesome";
|
||||
|
||||
&:after {
|
||||
content: "\f13a";
|
||||
}
|
||||
}
|
||||
.collapsed .collapse-indicator:after {
|
||||
content: "\f139";
|
||||
}
|
||||
|
||||
// Used for the person photo image upload preview
|
||||
.photo-preview {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
// Fix for Trix rich text editor element + Bootstrap
|
||||
// Allows the text area to scroll as content is added
|
||||
trix-editor.form-control {
|
||||
height: 15rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
// Provide some styling for required form fields
|
||||
.required small:after, label.required:after {
|
||||
color: $red;
|
||||
content: "Required";
|
||||
margin-left: 5px;
|
||||
font-size: 0.5rem;
|
||||
}
|
||||
html[lang="es"] .required:after {
|
||||
content: "Necesario";
|
||||
}
|
||||
|
||||
// Used when a release has been confirmed for a given video
|
||||
.releasable[data-confirmed="true"] {
|
||||
button {
|
||||
border-width: 5px !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// Used when a suggested_match has been confirmed for a given video
|
||||
.releasable-match[data-confirmed="true"] {
|
||||
button {
|
||||
border-width: 5px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Overrode the above style for audio matches
|
||||
#audio_matches .releasable-match[data-confirmed="true"] {
|
||||
button {
|
||||
border-width: 1px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Used when hiding checkmark next to releasable-match
|
||||
.releasable-match[data-confirmed="false"] [data-ujs-target=match-confirmed-check] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Used when hiding checkmark next to graphics-match
|
||||
.graphics-match[data-confirmed="false"] [data-ujs-target=graphics-confirmed-check] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Colored borders that correspond to the type of release
|
||||
.border-appearance_release {
|
||||
border-color: rgb(0,255,0) !important;
|
||||
}
|
||||
.border-talent_release {
|
||||
border-color: rgb(0,0,255) !important;
|
||||
}
|
||||
|
||||
// Used to hide confirmed releases
|
||||
.hidden-when-confirmed[data-confirmed="true"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Markers
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.bookmark-marker .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'info');
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.bookmark-marker:hover .standard-marker-inner,
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.bookmark-marker.hover .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'info');
|
||||
box-shadow: 0 0 0 8px rgba(map-get($theme-colors, 'info'), 0.2);
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-appearance-marker .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'danger');
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-appearancebookmark-marker:hover .standard-marker-inner,
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-appearancebookmark-marker.hover .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'danger');
|
||||
box-shadow: 0 0 0 8px rgba(map-get($theme-colors, 'danger'), 0.2);
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-warning-marker .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'warning');
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-warning-marker .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'warning');
|
||||
}
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-warning-marker:hover .standard-marker-inner,
|
||||
.markers-plugin .markers-plugin-markers .standard-marker.unreleased-warning-marker.hover .standard-marker-inner {
|
||||
background-color: map-get($theme-colors, 'warning');
|
||||
box-shadow: 0 0 0 8px rgba(map-get($theme-colors, 'warning'), 0.2);
|
||||
}
|
||||
|
||||
// Uses a dashed border style
|
||||
.border-dashed {
|
||||
border-style: dashed !important;
|
||||
}
|
||||
|
||||
// Increases the width of the border
|
||||
.border-thick {
|
||||
border-width: 2px !important;
|
||||
}
|
||||
.border-thicker {
|
||||
border-width: 5px !important;
|
||||
}
|
||||
|
||||
// Style as a keyboard shortcut key
|
||||
.keyboard-key {
|
||||
background-color: map-get($grays, "100");
|
||||
background-image: linear-gradient($white, map-get($grays, "100"));
|
||||
border-radius: 3px;
|
||||
border: 1px solid map-get($grays, "500");
|
||||
color: $black;
|
||||
font-family: $font-family-monospace;
|
||||
font-size: $font-size-sm;
|
||||
padding: map-get($spacers, 1);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
// Remove default button styles
|
||||
.btn-no-style {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Cursors
|
||||
.cursor-copy {
|
||||
cursor: copy;
|
||||
}
|
||||
.cursor-not-allowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// These borders only appear when an element is hovered
|
||||
@each $color, $value in $theme-colors {
|
||||
.border-hover-#{$color}:hover {
|
||||
border-color: $value !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Customized tooltip colors
|
||||
@each $color, $value in $theme-colors {
|
||||
.tooltip-#{$color} {
|
||||
& > .tooltip-inner {
|
||||
background-color: $value;
|
||||
}
|
||||
&.bs-tooltip-top > .arrow::before {
|
||||
border-top-color: $value;
|
||||
}
|
||||
&.bs-tooltip-right > .arrow::before {
|
||||
border-right-color: $value;
|
||||
}
|
||||
&.bs-tooltip-bottom > .arrow::before {
|
||||
border-bottom-color: $value;
|
||||
}
|
||||
&.bs-tooltip-left > .arrow::before {
|
||||
border-left-color: $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix for custom file input label in an inline form
|
||||
.form-inline .custom-file-label {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
// Customized carousel colors
|
||||
.carousel-bg-black {
|
||||
& > .carousel-inner {
|
||||
background-color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
// Text size
|
||||
.x-small {
|
||||
font-size: 60%;
|
||||
}
|
||||
|
||||
// Fix long name wrapping in file inputs
|
||||
.custom-file {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hidden-file-input {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-behavior=fillable-fields] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[data-behavior=seekable-timecode] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Fixes known issue with collapsible elements which don't normally receive click events on iOS
|
||||
// https://github.com/twbs/bootstrap/issues/16213#issuecomment-88995656
|
||||
[data-toggle=collapse][data-target] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Pins a modal to the right side of the screen on large devices
|
||||
.modal-right {
|
||||
@include media-breakpoint-up(lg) {
|
||||
left: 25%;
|
||||
right: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
// Provides extra padding to main content area when there is a fixed bottom alert
|
||||
.alert.fixed-bottom + main {
|
||||
padding-bottom: $alert-padding-y * 5 !important;
|
||||
}
|
||||
|
||||
// Allows <hr> divider to be used on a dark background
|
||||
.divider-light {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
// Sets line height to 0
|
||||
.no-line-height {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
// Even lighter text color
|
||||
.text-more-muted {
|
||||
color: $gray-500 !important;
|
||||
}
|
||||
|
||||
// A dark version of the pills nav
|
||||
.nav-pills-dark {
|
||||
a {
|
||||
color: $gray-500;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-800;
|
||||
color: lighten($gray-500, 15%);
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A comma separated list
|
||||
ul.list-comma-separated {
|
||||
li:not(:last-child):after {
|
||||
content: ",";
|
||||
}
|
||||
}
|
||||
|
||||
a[data-behavior=seekable-timecode] {
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.embeded-contract-preview {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
// Border radius
|
||||
.rounded-pill-left {
|
||||
border-top-left-radius: $rounded-pill;
|
||||
border-bottom-left-radius: $rounded-pill;
|
||||
}
|
||||
.rounded-pill-right {
|
||||
border-top-right-radius: $rounded-pill;
|
||||
border-bottom-right-radius: $rounded-pill;
|
||||
}
|
||||
|
||||
// A nav-pills component that uses white color for the active element
|
||||
.nav-pills-white {
|
||||
.nav-link.active,
|
||||
.show > .nav-link {
|
||||
color: $body-color;
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// A custom background color that is semi-transparent
|
||||
.bg-semi-transparent {
|
||||
background-color: rgba($black, 0.05);
|
||||
}
|
||||
|
||||
// Custom width
|
||||
.w-65 {
|
||||
width: 65% !important;
|
||||
}
|
||||
|
||||
// Maximum height
|
||||
.mh-30 {
|
||||
max-height: 30rem;
|
||||
}
|
||||
|
||||
// Fix height and width
|
||||
.fix-h-and-w {
|
||||
width: 308px;
|
||||
height:308px;
|
||||
}
|
||||
54
app/assets/stylesheets/contract_pdf.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
dd {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
dt, dd {
|
||||
display: inline-block;
|
||||
padding: 10px 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
ol li, ul li {
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
u {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.heading {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.page {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.logo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.heading-table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.heading-table td {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.serial-number {
|
||||
text-transform: uppercase;
|
||||
padding-right: 15px;
|
||||
}
|
||||
4
app/channels/application_cable/channel.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
module ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
end
|
||||
17
app/channels/application_cable/connection.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
identified_by :current_user
|
||||
|
||||
def connect
|
||||
self.current_user = find_verified_user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_verified_user
|
||||
if verified_user = env['warden'].user
|
||||
verified_user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
47
app/channels/broadcasts_channel.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
class BroadcastsChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
broadcast = Broadcast.find_by_token!(params[:token])
|
||||
stream_for broadcast
|
||||
end
|
||||
|
||||
def unsubscribed
|
||||
# Any cleanup needed when channel is unsubscribed
|
||||
end
|
||||
|
||||
def self.broadcast_stream_updates(broadcast)
|
||||
status_content = ApplicationController.render partial: "broadcasts/broadcast_status", locals: { broadcast: broadcast }
|
||||
video_content = ApplicationController.render partial: "broadcasts/video", locals: { broadcast: broadcast }
|
||||
|
||||
broadcast_to broadcast, {
|
||||
event: :broadcast_stream_update,
|
||||
status: broadcast.status,
|
||||
playback_url: broadcast.stream_playback_url,
|
||||
video_content: video_content,
|
||||
status_content: status_content,
|
||||
streamer_status: broadcast.streamer_status
|
||||
}
|
||||
end
|
||||
|
||||
def self.stream_recording_ready(broadcast, recordings, message)
|
||||
flash_message = OpenStruct.new(notice: message, alert: nil)
|
||||
flash_content = ApplicationController.render partial: "application/flash", locals: { flash: flash_message }
|
||||
recordings_content = ApplicationController.render partial: "broadcasts/broadcast_recordings", locals: { recordings: recordings, broadcast: broadcast }
|
||||
recordings_nav_content = ApplicationController.render partial: "broadcasts/broadcast_recording_nav", collection: recordings, as: :broadcast_recording
|
||||
|
||||
broadcast_to broadcast,
|
||||
event: :stream_recording_ready,
|
||||
flash_content: flash_content,
|
||||
recordings_content: recordings_content,
|
||||
recordings_nav_content: recordings_nav_content
|
||||
end
|
||||
|
||||
def self.file_upload_updates(broadcast, files, pagination_content)
|
||||
files_content = ApplicationController.render partial: "broadcasts/file", collection: files
|
||||
|
||||
broadcast_to broadcast, {
|
||||
event: :file_upload_update,
|
||||
files_content: files_content,
|
||||
pagination_content: pagination_content
|
||||
}
|
||||
end
|
||||
end
|
||||
36
app/channels/projects_channel.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
class ProjectsChannel < ApplicationCable::Channel
|
||||
|
||||
def subscribed
|
||||
# TODO: How can we get the current account in this context
|
||||
project = current_user.accessible_projects_for(current_user.primary_account).find(params[:id])
|
||||
stream_for project
|
||||
end
|
||||
|
||||
def unsubscribed
|
||||
# Any cleanup needed when channel is unsubscribed
|
||||
end
|
||||
|
||||
def self.broadcast_video_analysis_update(video)
|
||||
content = ApplicationController.render partial: "video_analyses/video_status_updated", locals: { video: video }
|
||||
broadcast_to video.project, event: :video_status_update, content: content
|
||||
end
|
||||
|
||||
def self.broadcast_download_generation_update(download, notification)
|
||||
if download.failure?
|
||||
flash = OpenStruct.new(notice: nil, alert: notification)
|
||||
else
|
||||
flash = OpenStruct.new(notice: notification, alert: nil)
|
||||
end
|
||||
|
||||
content = ApplicationController.render partial: "application/flash", locals: { flash: flash }
|
||||
broadcast_to download.project, event: :download_status_update, content: content
|
||||
end
|
||||
|
||||
def self.conference_recording_ready(project, recording)
|
||||
link = ApplicationController.helpers.link_to('Download here.', recording.service_url, target: '_blank', class: 'alert-link')
|
||||
notification = "A recording of your video conference is now available. #{link}"
|
||||
flash = OpenStruct.new(notice: notification)
|
||||
content = ApplicationController.render partial: 'application/flash', locals: { flash: flash }
|
||||
broadcast_to project, event: :conference_recording_ready, content: content
|
||||
end
|
||||
end
|
||||
83
app/controllers/account_auths_controller.rb
Normal file
@@ -0,0 +1,83 @@
|
||||
class AccountAuthsController < ApplicationController
|
||||
before_action :set_account_auth, only: [:update, :destroy]
|
||||
|
||||
def index
|
||||
@account = Current.account
|
||||
if params[:account_id]
|
||||
@account = accounts.find(params[:account_id])
|
||||
end
|
||||
|
||||
@members = account_auths.joins(:user).where(account: @account).order('role DESC, users.email ASC')
|
||||
@account_auth = account_auths.new(user: current_user, account: @account, role: :account_manager)
|
||||
end
|
||||
|
||||
def create
|
||||
email = account_auths_create_params[:user_email]
|
||||
account_id = account_auths_create_params[:account_id]
|
||||
user = User.find_by(email: email)
|
||||
if user.nil?
|
||||
user = sign_up({ email: email, password: SecureRandom.alphanumeric })
|
||||
@account_auth = build_account_auth({ user: user, account_id: account_id, role: :account_manager })
|
||||
@account_auth.save!
|
||||
UserMailer.welcome(@account_auth.user, @account_auth.account).deliver_later
|
||||
else
|
||||
@account_auth = build_account_auth({ user: user, account_id: account_id, role: :account_manager })
|
||||
@account_auth.save!
|
||||
UserMailer.existing_account(@account_auth.user, @account_auth.account).deliver_later
|
||||
end
|
||||
redirect_to account_auths_path({ account_id: @account_auth.account_id }), notice: t(".notice")
|
||||
end
|
||||
|
||||
def update
|
||||
AccountAuth.transaction do
|
||||
if @account_auth.update(account_auth_update_params)
|
||||
if @account_auth.account_manager?
|
||||
@account_auth.user.project_memberships.where(project: @account_auth.account.projects).destroy_all
|
||||
end
|
||||
flash.notice = t(".notice")
|
||||
else
|
||||
flash.alert = t(".alert")
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to account_auths_path
|
||||
end
|
||||
|
||||
def destroy
|
||||
ActiveRecord::Base.transaction do
|
||||
ProjectMembership.where(user: @account_auth.user, project: @account_auth.account.projects).destroy_all
|
||||
@account_auth.destroy
|
||||
end
|
||||
redirect_to account_auths_path, alert: t(".alert")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_account_auth(auth_params)
|
||||
@account_auth = authorize account_auths.build(auth_params)
|
||||
end
|
||||
|
||||
def account_auths_create_params
|
||||
params.require(:account_auth).permit(:user_email, :account_id)
|
||||
end
|
||||
|
||||
def account_auth_update_params
|
||||
params.require(:account_auth).permit(:role)
|
||||
end
|
||||
|
||||
def account_auths
|
||||
policy_scope(AccountAuth)
|
||||
end
|
||||
|
||||
def find_account_auth
|
||||
authorize account_auths.find(params[:id])
|
||||
end
|
||||
|
||||
def accounts
|
||||
policy_scope(Account)
|
||||
end
|
||||
|
||||
def set_account_auth
|
||||
@account_auth = find_account_auth
|
||||
end
|
||||
end
|
||||
13
app/controllers/account_sessions_controller.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class AccountSessionsController < ApplicationController
|
||||
def update
|
||||
authorize :account_session, :update?
|
||||
session[:active_account] = account_session_params[:account_id]
|
||||
redirect_to signed_in_root_path
|
||||
end
|
||||
|
||||
private
|
||||
def account_session_params
|
||||
params.require(:account_session).permit(:account_id)
|
||||
end
|
||||
end
|
||||
|
||||
56
app/controllers/accounts_controller.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
class AccountsController < ApplicationController
|
||||
before_action :set_account, only: [:update]
|
||||
skip_before_action :require_login, only: [:new, :create]
|
||||
skip_after_action :verify_authorized, only: [:new, :create]
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
account = Account.create(account_params_for_create)
|
||||
|
||||
if account.valid?
|
||||
user = sign_up(user_params)
|
||||
|
||||
if user.valid?
|
||||
user.account_auths << AccountAuth.create(user: user, account: account, role: "account_manager")
|
||||
|
||||
if sign_in(user)
|
||||
TrackAnalyticsJob.perform_later(user, user.primary_account, :track_guest_sign_up, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
SubmitHubspotFormJob.perform_later(user.email, account.name, i_m_interested_in: user.interested_product_name)
|
||||
redirect_to signed_in_root_path
|
||||
else
|
||||
redirect_to new_session_path, alert: t(".notice")
|
||||
end
|
||||
else
|
||||
redirect_to new_account_path, alert: t(".error")
|
||||
end
|
||||
else
|
||||
redirect_to new_account_path, alert: t(".error")
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @account
|
||||
|
||||
@account.update(account_params_for_update)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Current.account
|
||||
end
|
||||
|
||||
def account_params_for_create
|
||||
{ name: params[:user][:account_name], plan_uid: :me_suite }
|
||||
end
|
||||
|
||||
def account_params_for_update
|
||||
params.require(:account).permit(:logo)
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).except(:account_name).permit(:email, :password, :first_name, :last_name, :interested_product_name)
|
||||
end
|
||||
end
|
||||
106
app/controllers/acquired_media_releases_controller.rb
Normal file
@@ -0,0 +1,106 @@
|
||||
class AcquiredMediaReleasesController < ApplicationController
|
||||
include ProjectContext, AcquiredMediaReleaseContext
|
||||
|
||||
before_action :set_project, only: [:index, :new, :create]
|
||||
before_action :set_acquired_media_release, only: [:edit, :update, :destroy]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def index
|
||||
@acquired_media_releases = filtered_acquired_media_releases.order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@acquired_media_release = build_acquired_media_release
|
||||
end
|
||||
|
||||
def create
|
||||
@acquired_media_release = build_acquired_media_release(acquired_media_release_params)
|
||||
|
||||
if @acquired_media_release.save
|
||||
log_create_analytics
|
||||
SetTagsForReleasableJob.perform_later(@acquired_media_release)
|
||||
redirect_to [@project, :acquired_media_releases], notice: t(".notice")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@project = @acquired_media_release.project
|
||||
end
|
||||
|
||||
def update
|
||||
@project = @acquired_media_release.project
|
||||
|
||||
if @acquired_media_release.update(acquired_media_release_params)
|
||||
redirect_to [@project, :acquired_media_releases], notice: t(".notice")
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@project = @acquired_media_release.project
|
||||
@acquired_media_release.destroy
|
||||
redirect_to [@project, :acquired_media_releases], alert: t(".alert")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def acquired_media_releases
|
||||
if @project
|
||||
policy_scope(@project.acquired_media_releases)
|
||||
else
|
||||
policy_scope(AcquiredMediaRelease)
|
||||
end
|
||||
end
|
||||
|
||||
def acquired_media_release_params
|
||||
params.require(:acquired_media_release).permit(
|
||||
:name,
|
||||
:territory,
|
||||
:term,
|
||||
:person_name,
|
||||
:person_phone,
|
||||
:person_email,
|
||||
:person_company,
|
||||
:person_title,
|
||||
:person_address_street1,
|
||||
:person_address_street2,
|
||||
:person_address_city,
|
||||
:person_address_state,
|
||||
:person_address_zip,
|
||||
:person_address_country,
|
||||
:contract,
|
||||
:applicable_medium_id, :applicable_medium_text,
|
||||
:territory_id, :territory_text,
|
||||
:term_id, :term_text,
|
||||
:restriction_id, :restriction_text,
|
||||
categories: [],
|
||||
file_infos_attributes: [
|
||||
:filename,
|
||||
:content_type,
|
||||
:byte_size
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def build_acquired_media_release(attrs = {})
|
||||
authorize @project.acquired_media_releases.build(attrs)
|
||||
end
|
||||
|
||||
def filtered_acquired_media_releases
|
||||
results = acquired_media_releases
|
||||
|
||||
if params[:query].present?
|
||||
results = results.search(params[:query])
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_non_native_release, release_type: AcquiredMediaRelease.to_s, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
end
|
||||
73
app/controllers/admin/accounts_controller.rb
Normal file
@@ -0,0 +1,73 @@
|
||||
class Admin::AccountsController < Admin::ApplicationController
|
||||
before_action :set_account, only: [:show, :edit, :update]
|
||||
|
||||
def index
|
||||
@accounts = filtered_accounts.order_by_name.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@account = build_account
|
||||
end
|
||||
|
||||
def create
|
||||
@account = build_account(account_params)
|
||||
if @account.save
|
||||
redirect_to account_auths_path({ account_id: @account.id }), notice: t(".notice")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@videos = filtered_account_videos.order(created_at: :desc, project_id: :desc).paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @account.update(account_params)
|
||||
redirect_to admin_accounts_path, notice: t(".notice")
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = find_account
|
||||
end
|
||||
|
||||
def find_account
|
||||
authorize accounts.find_by(slug: params[:id])
|
||||
end
|
||||
|
||||
def accounts
|
||||
policy_scope(Account)
|
||||
end
|
||||
|
||||
def build_account(params = {})
|
||||
authorize accounts.new(params)
|
||||
end
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:name, :plan_uid)
|
||||
end
|
||||
|
||||
def filtered_accounts
|
||||
if params[:query].present?
|
||||
accounts.search(params[:query])
|
||||
else
|
||||
accounts
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_account_videos
|
||||
if params[:query].present?
|
||||
@account.videos.search(params[:query])
|
||||
else
|
||||
@account.videos
|
||||
end
|
||||
end
|
||||
end
|
||||
18
app/controllers/admin/application_controller.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
class Admin::ApplicationController < ActionController::Base
|
||||
include Oath::ControllerHelpers # Methods for authentication
|
||||
include Pundit # Methods for authorization
|
||||
|
||||
before_action :require_login
|
||||
include SetCurrentRequestDetails
|
||||
before_action :require_admin_login
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
private
|
||||
|
||||
def require_admin_login
|
||||
if !Current.user.admin?
|
||||
redirect_to signed_in_root_url, alert: "You are not authorized to access this"
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/controllers/admin/masquerades_controller.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
class Admin::MasqueradesController < Admin::ApplicationController
|
||||
before_action :set_user, only: [:create]
|
||||
skip_before_action :require_admin_login, only: [:destroy]
|
||||
|
||||
def create
|
||||
authorize :masquerade, :create?
|
||||
session[:admin_id] = current_user.id
|
||||
sign_in @user
|
||||
redirect_to signed_in_root_path
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize :masquerade, :destroy?
|
||||
sign_in User.find session[:admin_id]
|
||||
session.delete(:admin_id)
|
||||
session.delete(:active_account)
|
||||
redirect_to admin_users_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = find_user
|
||||
end
|
||||
|
||||
def users
|
||||
policy_scope User
|
||||
end
|
||||
|
||||
def find_user
|
||||
authorize users.find(params[:user_id])
|
||||
end
|
||||
end
|
||||
90
app/controllers/admin/users_controller.rb
Normal file
@@ -0,0 +1,90 @@
|
||||
class Admin::UsersController < Admin::ApplicationController
|
||||
before_action :set_user, only: [:edit, :update]
|
||||
|
||||
def index
|
||||
@users = filtered_users.order("email")
|
||||
end
|
||||
|
||||
def new
|
||||
@user = build_user
|
||||
@accounts = accounts
|
||||
end
|
||||
|
||||
def create
|
||||
auth_params = user_create_params.slice(:account_id, :role)
|
||||
|
||||
@user = authorize sign_up(user_create_params.except(:account_id, :role))
|
||||
|
||||
if @user.valid?
|
||||
account = accounts.find(auth_params[:account_id])
|
||||
@user.account_auths << AccountAuth.create(user: @user, account: account, role: auth_params[:role])
|
||||
UserMailer.welcome(@user, account).deliver_later
|
||||
redirect_to admin_users_path, notice: t(".notice")
|
||||
else
|
||||
@accounts = accounts
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@accounts = accounts
|
||||
end
|
||||
|
||||
def update
|
||||
set_user_password
|
||||
|
||||
if @user.update(user_update_params.except(:password))
|
||||
redirect_to admin_users_path, notice: t(".notice")
|
||||
else
|
||||
@accounts = accounts
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@user = authorize User.find(params[:id])
|
||||
@user.destroy
|
||||
|
||||
redirect_to admin_users_path, alert: t(".alert")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = find_user
|
||||
end
|
||||
|
||||
def accounts
|
||||
policy_scope Account
|
||||
end
|
||||
|
||||
def users
|
||||
policy_scope User
|
||||
end
|
||||
|
||||
def find_user
|
||||
authorize users.find(params[:id])
|
||||
end
|
||||
|
||||
def build_user(params = {})
|
||||
authorize users.new(params)
|
||||
end
|
||||
|
||||
def user_create_params
|
||||
params.require(:user).permit(:email, :password, :admin, :account_id, :role)
|
||||
end
|
||||
|
||||
def user_update_params
|
||||
params.require(:user).permit(:email, :admin, :password)
|
||||
end
|
||||
|
||||
def set_user_password
|
||||
if user_update_params[:password].present?
|
||||
Oath::Services::PasswordReset.new(@user, user_update_params[:password]).perform
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_users
|
||||
params[:query].present? ? users.search(params[:query]) : users
|
||||
end
|
||||
end
|
||||
11
app/controllers/api/acquired_media_releases_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class Api::AcquiredMediaReleasesController < Api::ReleasesController
|
||||
deserializable_resource :acquired_media_release, only: [:create, :update]
|
||||
|
||||
def model_name
|
||||
"acquired_media_release"
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:name]
|
||||
end
|
||||
end
|
||||
48
app/controllers/api/api_controller.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
class Api::ApiController < ActionController::Base
|
||||
skip_before_action :verify_authenticity_token
|
||||
include Knock::Authenticable
|
||||
include Pundit
|
||||
|
||||
rescue_from Exception, :with => :return_error
|
||||
before_action :authenticate_user
|
||||
before_action do
|
||||
Current.user = current_user if current_user.present?
|
||||
end
|
||||
|
||||
def pundit_user
|
||||
UserContext.new(Current.user, Current.account)
|
||||
end
|
||||
|
||||
# Catch exception and return JSON-formatted error
|
||||
def return_error(exception)
|
||||
raise exception if Rails.env.test?
|
||||
|
||||
logger.error "==Handled======="
|
||||
logger.error exception.message
|
||||
logger.error exception.backtrace.join("\n")
|
||||
logger.error "==Handled======="
|
||||
case exception.class
|
||||
when ActiveRecord::RecordNotFound
|
||||
@status = 404
|
||||
@message = 'Record not found'
|
||||
when ActiveRecord::RecordInvalid
|
||||
@status = 422
|
||||
@message = 'Record invalid'
|
||||
when ArgumentError
|
||||
@status = 400
|
||||
@message = 'Argument Error'
|
||||
else
|
||||
@status = 500
|
||||
@message = 'Internal Error'
|
||||
end
|
||||
|
||||
# for some reason render json_errors is not working
|
||||
# simulating JSON API support
|
||||
render json: {
|
||||
errors: [{
|
||||
status: @status.to_s,
|
||||
title: @message
|
||||
}]
|
||||
}
|
||||
end
|
||||
end
|
||||
23
app/controllers/api/appearance_releases_controller.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
class Api::AppearanceReleasesController < Api::ReleasesController
|
||||
deserializable_resource :appearance_release, only: [:create]
|
||||
|
||||
def model_name
|
||||
"appearance_release"
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:person_name]
|
||||
end
|
||||
|
||||
def handle_attachments(release, _)
|
||||
photo = release_create_params[:person_photo]
|
||||
photo[:io] = StringIO.new(Base64.decode64(photo[:io]))
|
||||
release.person_photo.attach(io: photo[:io], filename: photo[:filename])
|
||||
|
||||
guardian_photo = release_create_params[:guardian_photo]
|
||||
if guardian_photo
|
||||
guardian_photo[:io] = StringIO.new(Base64.decode64(guardian_photo[:io]))
|
||||
release.guardian_photo.attach(io: guardian_photo[:io], filename: guardian_photo[:filename])
|
||||
end
|
||||
end
|
||||
end
|
||||
55
app/controllers/api/broadcasts_controller.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::BroadcastsController < Api::ApiController
|
||||
deserializable_resource :broadcast, only: [:update]
|
||||
|
||||
include ProjectContext
|
||||
|
||||
before_action :set_project
|
||||
before_action :set_broadcast, only: [:show, :update]
|
||||
|
||||
def index
|
||||
render jsonapi: broadcasts, class: { Broadcast: SerializableBroadcast }
|
||||
end
|
||||
|
||||
def show
|
||||
render jsonapi:
|
||||
@broadcast,
|
||||
class: {
|
||||
Broadcast: SerializableBroadcast,
|
||||
"ActiveStorage::Attachment".to_sym => ActiveStorage::SerializableAttachment
|
||||
},
|
||||
include: [:files]
|
||||
end
|
||||
|
||||
def update
|
||||
file_params.each do |file|
|
||||
file[:io] = StringIO.new(Base64.decode64(file[:io]))
|
||||
@broadcast.files.attach(io: file[:io], filename: file[:filename])
|
||||
end
|
||||
@broadcast.save!
|
||||
|
||||
render jsonapi:
|
||||
@broadcast,
|
||||
class: {
|
||||
Broadcast: SerializableBroadcast,
|
||||
"ActiveStorage::Attachment".to_sym => ActiveStorage::SerializableAttachment
|
||||
},
|
||||
include: [:files]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def file_params
|
||||
broadcast_params = params.require(:broadcast).permit!
|
||||
broadcast_params[:files]
|
||||
end
|
||||
|
||||
def broadcasts
|
||||
policy_scope(@project.broadcasts.where(status: %w[created idle]))
|
||||
end
|
||||
|
||||
def set_broadcast
|
||||
@broadcast = authorize policy_scope(@project.broadcasts).find(params[:id])
|
||||
end
|
||||
end
|
||||
63
app/controllers/api/contract_templates_controller.rb
Normal file
@@ -0,0 +1,63 @@
|
||||
class Api::ContractTemplatesController < Api::ApiController
|
||||
include ProjectContext
|
||||
|
||||
before_action :set_project, only: [:index]
|
||||
before_action :set_contract_template, only: [:show]
|
||||
|
||||
def index
|
||||
render jsonapi: contract_templates, class: { "ContractTemplate": index_serializable }
|
||||
end
|
||||
|
||||
def show
|
||||
handle_response(@contract_template)
|
||||
end
|
||||
|
||||
def handle_response(contract_template, status = :ok)
|
||||
mapping = {
|
||||
"ContractTemplate": show_serializable,
|
||||
"ActiveStorage::Attachment".to_sym => ActiveStorage::SerializableAttachment,
|
||||
}
|
||||
|
||||
render jsonapi: contract_template,
|
||||
status: status,
|
||||
class: mapping
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:name]
|
||||
end
|
||||
|
||||
def index_serializable
|
||||
name = "contract_template"
|
||||
attributes_to_send = attributes_for_index
|
||||
Class.new(JSONAPI::Serializable::Resource) do
|
||||
type name
|
||||
|
||||
attributes_to_send.each do |attr|
|
||||
attribute attr.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show_serializable
|
||||
name = "contract_template"
|
||||
Class.new(JSONAPI::Serializable::Resource) do
|
||||
type name
|
||||
|
||||
ContractTemplate.new.attributes.keys.each do |attr|
|
||||
attribute attr.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contract_templates
|
||||
date = Date.parse(params.fetch(:updated_since, Date.new.to_s))
|
||||
policy_scope(ContractTemplate.where(project_id: @project.id, updated_at: date..Float::INFINITY))
|
||||
end
|
||||
|
||||
def set_contract_template
|
||||
@contract_template = ContractTemplate.find(params[:id])
|
||||
end
|
||||
end
|
||||
11
app/controllers/api/location_releases_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class Api::LocationReleasesController < Api::ReleasesController
|
||||
deserializable_resource :location_release, only: [:create, :update]
|
||||
|
||||
def model_name
|
||||
"location_release"
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:name]
|
||||
end
|
||||
end
|
||||
11
app/controllers/api/material_releases_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class Api::MaterialReleasesController < Api::ReleasesController
|
||||
deserializable_resource :material_release, only: [:create, :update]
|
||||
|
||||
def model_name
|
||||
"material_release"
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:name]
|
||||
end
|
||||
end
|
||||
35
app/controllers/api/notes_controller.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
class Api::NotesController < Api::ApiController
|
||||
before_action :set_release
|
||||
deserializable_resource :note, only: [:create]
|
||||
|
||||
def index
|
||||
render jsonapi: @release.notes, class: { Note: SerializableNote }
|
||||
end
|
||||
|
||||
def create
|
||||
note = @release.notes.new(note_create_params)
|
||||
note.user_id = current_user.id
|
||||
note.email = current_user.email
|
||||
note.save!
|
||||
render jsonapi: note, class: { Note: SerializableNote }, status: :created
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def model_name
|
||||
request.path.match(/(\w+_releases)/).captures.first.singularize
|
||||
end
|
||||
|
||||
def model_constant
|
||||
model_name.camelize.constantize
|
||||
end
|
||||
|
||||
def set_release
|
||||
@release = authorize model_constant.find(params["#{model_name}_id"])
|
||||
end
|
||||
|
||||
def note_create_params
|
||||
parameters = params.require(:note).permit!
|
||||
parameters.slice(:content)
|
||||
end
|
||||
end
|
||||
5
app/controllers/api/profiles_controller.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class Api::ProfilesController < Api::ApiController
|
||||
def show
|
||||
render jsonapi: current_user
|
||||
end
|
||||
end
|
||||
6
app/controllers/api/projects_controller.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class Api::ProjectsController < Api::ApiController
|
||||
def index
|
||||
projects = Current.user.accessible_projects_for(Current.account)
|
||||
render jsonapi: projects
|
||||
end
|
||||
end
|
||||
182
app/controllers/api/releases_controller.rb
Normal file
@@ -0,0 +1,182 @@
|
||||
class Api::ReleasesController < Api::ApiController
|
||||
include ProjectContext
|
||||
include CreateReleasableJobs
|
||||
|
||||
before_action :set_project, only: [:index]
|
||||
before_action :set_release, only: [:show, :update]
|
||||
before_action :set_template_and_project, only: [:create]
|
||||
|
||||
def index
|
||||
render jsonapi: releases, class: { "#{model_name.camelize}": index_serializable }
|
||||
end
|
||||
|
||||
def show
|
||||
handle_response(@release)
|
||||
end
|
||||
|
||||
def create
|
||||
params_without_photo = release_create_params.except(:photos, :person_photo)
|
||||
release = filtered_releases.new(params_without_photo)
|
||||
release.project_id = @project.id
|
||||
release.contract_template_id = @contract_template.id
|
||||
handle_attachments(release, release_create_params[:photos])
|
||||
release.save!(context: :native)
|
||||
log_create_analytics
|
||||
after_create(release)
|
||||
handle_response(release, :created)
|
||||
end
|
||||
|
||||
def update
|
||||
if model_name == "acquired_media_release"
|
||||
authorize @release, :update_file_infos?
|
||||
|
||||
@release.attributes = release_create_params
|
||||
@release.save!
|
||||
else
|
||||
authorize @release, :update_photos?
|
||||
|
||||
handle_attachments(@release, release_create_params[:photos])
|
||||
@release.save!
|
||||
end
|
||||
|
||||
handle_response(@release)
|
||||
end
|
||||
|
||||
def handle_response(release, status = :ok)
|
||||
if model_name == "acquired_media_release"
|
||||
mapping = {
|
||||
"#{model_name.camelize}": SerializableAcquiredMediaRelease,
|
||||
FileInfo: SerializableFileInfo
|
||||
}
|
||||
|
||||
render jsonapi: release,
|
||||
status: status,
|
||||
class: mapping,
|
||||
include: [:file_infos]
|
||||
else
|
||||
mapping = {
|
||||
"#{model_name.camelize}": show_serializable,
|
||||
"ActiveStorage::Attachment".to_sym => ActiveStorage::SerializableAttachment,
|
||||
}
|
||||
|
||||
render jsonapi: release,
|
||||
status: status,
|
||||
class: mapping,
|
||||
include: [:photos, :guardian_photos]
|
||||
end
|
||||
end
|
||||
|
||||
def model_name
|
||||
raise "Please specify model name underscored and lowercase e.g. appearance_release"
|
||||
end
|
||||
|
||||
def model_constant
|
||||
model_name.camelize.constantize
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
raise "Please specify attributes for index"
|
||||
end
|
||||
|
||||
def index_serializable
|
||||
name = model_name
|
||||
attributes_to_send = attributes_for_index
|
||||
Class.new(JSONAPI::Serializable::Resource) do
|
||||
type name
|
||||
|
||||
attributes_to_send.each do |attr|
|
||||
attribute attr.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show_serializable
|
||||
name = model_name
|
||||
constant = model_constant
|
||||
Class.new(JSONAPI::Serializable::Resource) do
|
||||
type name
|
||||
|
||||
constant.new.attributes.keys.each do |attr|
|
||||
attribute attr.to_sym
|
||||
end
|
||||
|
||||
if ["appearance_release", "talent_release"].include?(name)
|
||||
has_many :guardian_photos do
|
||||
data do
|
||||
[@object.guardian_photo.try(:attachment)].compact
|
||||
end
|
||||
meta do
|
||||
{ count: @object.try(:guardian_photo).try(:attached?) ? 1 : 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless name == "acquired_media_release"
|
||||
has_many :photos do
|
||||
if name == "appearance_release"
|
||||
data do
|
||||
[@object.photo.attachment]
|
||||
end
|
||||
end
|
||||
meta do
|
||||
{ count: @object.photos.size }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_deserializable
|
||||
constant = model_constant
|
||||
Class.new(JSONAPI::Deserializable::Resource) do
|
||||
constant.new.attributes.keys.except(:created_at, :updated_at, :id, :user_id).each do |attr|
|
||||
attribute attr.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_attachments(release, params_photos)
|
||||
photos = params_photos || []
|
||||
photos.each do |photo|
|
||||
photo[:io] = StringIO.new(Base64.decode64(photo[:io]))
|
||||
release.photos.attach(io: photo[:io], filename: photo[:filename])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def releases
|
||||
date = Date.parse(params.fetch(:updated_since, Date.new.to_s))
|
||||
table = model_constant.table_name
|
||||
policy_scope(model_constant.where(["#{table}.project_id = ? and #{table}.updated_at >= ?", @project.id, date]))
|
||||
end
|
||||
|
||||
def set_release
|
||||
@release = model_constant.find(params[:id])
|
||||
end
|
||||
|
||||
def set_template_and_project
|
||||
@contract_template = ContractTemplate.find(params[:contract_template_id])
|
||||
@project = @contract_template.project
|
||||
end
|
||||
|
||||
def filtered_releases
|
||||
if @project.present?
|
||||
policy_scope(@project.public_send(model_name.pluralize))
|
||||
else
|
||||
releases
|
||||
end
|
||||
end
|
||||
|
||||
def release_create_params
|
||||
parameters = params.require(model_name).permit!
|
||||
keys = model_constant.new.attributes.keys + [:guardian_photo, :person_photo, :photos, :signature, :signature_base64, :file_infos_attributes]
|
||||
parameters[:signature_base64] = parameters[:signature]
|
||||
parameters = parameters.slice(*keys).except(:created_at, :updated_at, :id, :user_id, :signature)
|
||||
parameters
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_native_release, release_type: model_constant.to_s, account: @account, user_agent: request.user_agent, user_ip: request.remote_ip, application: :ios)
|
||||
end
|
||||
end
|
||||
78
app/controllers/api/sync_controller.rb
Normal file
@@ -0,0 +1,78 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::SyncController < Api::ApiController
|
||||
def index
|
||||
accessible_projects = current_user.accessible_projects_for(Current.account)
|
||||
@accounts = filter(current_user.accounts)
|
||||
@projects = filter(Project).all
|
||||
@contract_templates = filter(ContractTemplate.where(project: accessible_projects)).all
|
||||
@acquired_media_releases = (AcquiredMediaRelease.where(project: accessible_projects))
|
||||
@appearance_releases = (AppearanceRelease.where(project: accessible_projects))
|
||||
@location_releases = (LocationRelease.where(project: accessible_projects))
|
||||
@material_releases = (MaterialRelease.where(project: accessible_projects))
|
||||
@talent_releases = (TalentRelease.where(project: accessible_projects))
|
||||
@notes = notes_query(Note.where(notable: @appearance_releases + @location_releases + @material_releases + @talent_releases + @acquired_media_releases ))
|
||||
|
||||
render json: {
|
||||
data: {
|
||||
accounts: @accounts,
|
||||
projects: @projects,
|
||||
contract_templates: @contract_templates,
|
||||
acquired_media_releases: releases_query(@acquired_media_releases),
|
||||
appearance_releases: releases_query(@appearance_releases),
|
||||
location_releases: releases_query(@location_releases),
|
||||
material_releases: releases_query(@material_releases),
|
||||
talent_releases: releases_query(@talent_releases),
|
||||
notes: @notes
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def releases_query(release)
|
||||
filter(release).all.map { |release| release_json(release) }
|
||||
end
|
||||
|
||||
def notes_query(notes)
|
||||
filter(notes).all.map do |note|
|
||||
json = note.as_json
|
||||
json[:attributes][:email] = note.email
|
||||
json
|
||||
end
|
||||
end
|
||||
|
||||
def release_json(release)
|
||||
json = release.as_json
|
||||
|
||||
unless release.model_name.to_s == "AcquiredMediaRelease"
|
||||
json[:attributes][:photos] = release.photos.map do |photo|
|
||||
build_photo_object photo
|
||||
end
|
||||
end
|
||||
|
||||
if release.respond_to?(:guardian_photo)
|
||||
photo = release.guardian_photo
|
||||
json[:attributes][:guardian_photo] = photo.attached? ? build_photo_object(photo) : nil
|
||||
end
|
||||
|
||||
json
|
||||
end
|
||||
|
||||
def filter(relation)
|
||||
policy_scope(relation)
|
||||
end
|
||||
|
||||
def build_photo_object(photo)
|
||||
{
|
||||
id: photo.id.to_s,
|
||||
type: 'active_storage_attachment',
|
||||
attributes: {
|
||||
filename: photo.filename.to_s,
|
||||
content_type: photo.content_type,
|
||||
url: Rails.application.routes.url_helpers.rails_blob_url(photo, host: AppHost.new.domain_with_port),
|
||||
thumbnail_url: Rails.application.routes.url_helpers.rails_representation_url(photo.variant(resize: '150x150'), host: AppHost.new.domain_with_port)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
21
app/controllers/api/talent_releases_controller.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
class Api::TalentReleasesController < Api::ReleasesController
|
||||
deserializable_resource :talent_release, only: [:create, :update]
|
||||
|
||||
def model_name
|
||||
"talent_release"
|
||||
end
|
||||
|
||||
def attributes_for_index
|
||||
[:person_name]
|
||||
end
|
||||
|
||||
def handle_attachments(release, _)
|
||||
super
|
||||
|
||||
guardian_photo = release_create_params[:guardian_photo]
|
||||
if guardian_photo
|
||||
guardian_photo[:io] = StringIO.new(Base64.decode64(guardian_photo[:io]))
|
||||
release.guardian_photo.attach(io: guardian_photo[:io], filename: guardian_photo[:filename])
|
||||
end
|
||||
end
|
||||
end
|
||||
36
app/controllers/api/user_token_controller.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
class Api::UserTokenController < Knock::AuthTokenController
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
rescue_from Exception, :with => :return_error
|
||||
|
||||
# Catch exception and return JSON-formatted error
|
||||
def return_error(exception)
|
||||
logger.error "==Handled======="
|
||||
logger.error exception.message
|
||||
logger.error exception.backtrace.join("\n")
|
||||
logger.error "==Handled======="
|
||||
case exception
|
||||
when ActiveRecord::RecordNotFound
|
||||
@status = 404
|
||||
@message = 'Record not found'
|
||||
when ActiveRecord::RecordInvalid
|
||||
@status = 422
|
||||
@message = 'Record invalid'
|
||||
when ArgumentError
|
||||
@status = 400
|
||||
@message = 'Argument Error'
|
||||
else
|
||||
@status = 500
|
||||
@message = 'Internal Error'
|
||||
end
|
||||
|
||||
# for some reason render json_errors is not working
|
||||
# simulating JSON API support
|
||||
render json: {
|
||||
errors: [{
|
||||
status: @status.to_s,
|
||||
title: @message
|
||||
}]
|
||||
}
|
||||
end
|
||||
end
|
||||
89
app/controllers/appearance_release_imports_controller.rb
Normal file
@@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AppearanceReleaseImportsController < ApplicationController
|
||||
include AppearanceReleaseContext
|
||||
include ProjectContext
|
||||
include CreateReleasableJobs
|
||||
|
||||
before_action :set_project, only: [:create]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def create
|
||||
authorize AppearanceRelease
|
||||
@failed_files = []
|
||||
attachments = appearance_release_params
|
||||
if attachments.nil?
|
||||
alert_message = t 'appearance_releases.create.no_attachments'
|
||||
else
|
||||
attachments.each do |attachment|
|
||||
create_imported_appearance_release attachment
|
||||
end
|
||||
end
|
||||
|
||||
unless @failed_files.empty?
|
||||
alert_message = t 'appearance_releases.create.failed_import'
|
||||
alert_message += '<br><ul>'
|
||||
@failed_files.each { |file_name| alert_message += "<li>#{file_name}</li>" }
|
||||
alert_message += '</ul>'
|
||||
end
|
||||
|
||||
redirect_to [@project, :appearance_releases], alert: alert_message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def appearance_releases
|
||||
if @project
|
||||
policy_scope(@project.appearance_releases)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def appearance_release_params
|
||||
params.require(:attachments)
|
||||
end
|
||||
|
||||
def build_appearance_release(params = {})
|
||||
authorize appearance_releases.build(params)
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_non_native_release, release_type: AppearanceRelease.to_s, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
|
||||
def create_imported_appearance_release(attachment)
|
||||
blob = ActiveStorage::Blob.find_signed(attachment)
|
||||
return if blob.nil?
|
||||
|
||||
extension = blob.filename.extension_with_delimiter
|
||||
unless AppearanceRelease.acceptable_import_file_extensions.include? extension
|
||||
blob.purge
|
||||
@failed_files << blob.filename
|
||||
return
|
||||
end
|
||||
|
||||
random_contract_no = AppearanceRelease.random_contract_number.to_s
|
||||
appearance_release_params = {
|
||||
person_last_name: random_contract_no
|
||||
}
|
||||
|
||||
if blob.image?
|
||||
appearance_release_params[:person_photo] = attachment
|
||||
appearance_release_params[:person_first_name] = I18n.t('appearance_releases.shared.imported_appearance_release_headshot_name')
|
||||
elsif extension == '.pdf'
|
||||
appearance_release_params[:contract] = attachment
|
||||
appearance_release_params[:person_first_name] = I18n.t('appearance_releases.shared.imported_appearance_release_contract_name')
|
||||
end
|
||||
|
||||
appearance_release = build_appearance_release(appearance_release_params)
|
||||
|
||||
if appearance_release.save(context: :non_native)
|
||||
log_create_analytics
|
||||
after_create appearance_release
|
||||
else
|
||||
@failed_files << blob.filename
|
||||
end
|
||||
end
|
||||
end
|
||||
100
app/controllers/appearance_releases_controller.rb
Normal file
@@ -0,0 +1,100 @@
|
||||
class AppearanceReleasesController < ApplicationController
|
||||
include ProjectContext, AppearanceReleaseContext
|
||||
|
||||
before_action :set_project, only: [:index, :new, :create]
|
||||
before_action :set_appearance_release, only: [:edit, :update, :destroy]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def index
|
||||
@appearance_releases = filtered_appearance_releases.order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@appearance_release = build_appearance_release
|
||||
end
|
||||
|
||||
def create
|
||||
@appearance_release = build_appearance_release(appearance_release_params)
|
||||
|
||||
if @appearance_release.save(context: :non_native)
|
||||
log_create_analytics
|
||||
AddHeadshotCollectionUidToProjectJob.perform_later(@project)
|
||||
SetTagsForReleasableJob.perform_later(@appearance_release)
|
||||
redirect_to [@project, :appearance_releases], notice: "The release has been imported. #{link_to_import_another}"
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@project = @appearance_release.project
|
||||
end
|
||||
|
||||
def update
|
||||
@project = @appearance_release.project
|
||||
|
||||
@appearance_release.attributes = appearance_release_params
|
||||
|
||||
if @appearance_release.save(context: :non_native)
|
||||
AddHeadshotCollectionUidToProjectJob.perform_later(@project)
|
||||
redirect_to [@project, :appearance_releases], notice: "The release has been updated"
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@project = @appearance_release.project
|
||||
|
||||
if @appearance_release.destroy
|
||||
AddHeadshotCollectionUidToProjectJob.perform_later(@appearance_release.project)
|
||||
|
||||
redirect_to [@project, :appearance_releases], alert: "The release has been deleted"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def appearance_releases
|
||||
if @project
|
||||
policy_scope(@project.appearance_releases)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_appearance_releases
|
||||
results = case params[:type_filter]
|
||||
when 'complete'
|
||||
appearance_releases.complete
|
||||
when 'incomplete'
|
||||
appearance_releases.incomplete
|
||||
else
|
||||
appearance_releases
|
||||
end
|
||||
results = results.search(params[:query]) if params[:query].present?
|
||||
results
|
||||
end
|
||||
|
||||
def appearance_release_params
|
||||
params.require(:appearance_release).permit(:contract, :guardian_address, :guardian_first_name, :guardian_last_name, :guardian_phone, :guardian_photo, :minor,
|
||||
:person_address, :person_first_name, :person_last_name, :person_phone, :person_email, :person_photo,
|
||||
:applicable_medium_id, :applicable_medium_text,
|
||||
:territory_id, :territory_text,
|
||||
:term_id, :term_text, :person_date_of_birth,
|
||||
:restriction_id, :restriction_text)
|
||||
end
|
||||
|
||||
def build_appearance_release(params = {})
|
||||
authorize appearance_releases.build(params)
|
||||
end
|
||||
|
||||
def link_to_import_another
|
||||
view_context.link_to "Import Another", [:new, @project, :appearance_release]
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_non_native_release, release_type: AppearanceRelease.to_s, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
end
|
||||
70
app/controllers/application_controller.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
include Oath::ControllerHelpers # Methods for authentication
|
||||
include Pundit # Methods for authorization
|
||||
include RememberMe::Controller
|
||||
|
||||
before_action :disable_browser_page_caching
|
||||
before_action :set_locale
|
||||
before_action :require_login
|
||||
|
||||
before_action :set_raven_context
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
include SetCurrentRequestDetails
|
||||
before_action :redirect_accountless
|
||||
|
||||
private
|
||||
|
||||
def require_login
|
||||
if !masquerading? && remembered_user = remember("user")
|
||||
sign_in(remembered_user)
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
def redirect_accountless
|
||||
if Current.user && Current.account.nil?
|
||||
redirect_to accountless_user_path
|
||||
end
|
||||
end
|
||||
|
||||
def signed_in_as_admin?
|
||||
signed_in? && current_user.admin?
|
||||
end
|
||||
helper_method :signed_in_as_admin?
|
||||
|
||||
# Ensure that all url helpers include the current locale
|
||||
def default_url_options
|
||||
super.merge(locale: I18n.locale) # Use merge to avoid clobbering any options set during config
|
||||
end
|
||||
|
||||
# Set the locale for the current request
|
||||
def set_locale
|
||||
I18n.locale = params[:locale] || request.env["rack.locale"] || I18n.default_locale
|
||||
end
|
||||
|
||||
# Run authorization against Current.user which will ensure it works in async jobs and channels as well
|
||||
# All authorization depends on the Current.account as users will have a different role for each
|
||||
def pundit_user
|
||||
UserContext.new(Current.user, Current.account)
|
||||
end
|
||||
|
||||
# Set the context for Sentry exception handling service
|
||||
def set_raven_context
|
||||
account_id = (session[:active_account] || try(:current_user).try(:accounts).try(:first))
|
||||
Raven.user_context(id: current_user.id, account_id: account_id) if signed_in?
|
||||
Raven.extra_context(params: params.to_unsafe_h, url: request.url)
|
||||
end
|
||||
|
||||
def disable_browser_page_caching
|
||||
response.headers["Cache-Control"] = "no-cache, no-store"
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
response.headers["Expires"] = "0"
|
||||
end
|
||||
|
||||
def masquerading?
|
||||
session[:admin_id].present?
|
||||
end
|
||||
helper_method :masquerading?
|
||||
end
|
||||
34
app/controllers/audio_reports_controller.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
class AudioReportsController < ApplicationController
|
||||
def show
|
||||
report = build_report(params[:type])
|
||||
respond_to do |format|
|
||||
format.xlsx { send_data(report.to_xls, download_attributes_xls(report)) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def video
|
||||
authorize policy_scope(Video).find(params[:video_id])
|
||||
end
|
||||
|
||||
def build_report(type)
|
||||
case type
|
||||
when "big"
|
||||
authorize ExcelReports::AudioReports::BrayInnovationGroupMusicCueReport.new(video)
|
||||
when "nat_geo"
|
||||
authorize ExcelReports::AudioReports::NatGeoMusicCueSheet.new(video)
|
||||
when "nat_geo-original"
|
||||
authorize ExcelReports::AudioReports::NatGeoOriginalMusicLog.new(video)
|
||||
else
|
||||
authorize ExcelReports::AudioReports::DiscoveryMusicCueReport.new(video)
|
||||
end
|
||||
end
|
||||
|
||||
def download_attributes_xls(report)
|
||||
{
|
||||
filename: report.filename,
|
||||
type: Mime[:xlsx]
|
||||
}
|
||||
end
|
||||
end
|
||||
58
app/controllers/bookmarks_controller.rb
Normal file
@@ -0,0 +1,58 @@
|
||||
class BookmarksController < ApplicationController
|
||||
before_action :set_video, except: [:destroy, :edit, :update]
|
||||
before_action :set_bookmark, only: [:destroy, :edit, :update]
|
||||
|
||||
def new
|
||||
@bookmark = @video.bookmarks.build(bookmark_params)
|
||||
end
|
||||
|
||||
def create
|
||||
@bookmark = @video.bookmarks.build(bookmark_params)
|
||||
|
||||
@bookmark.save
|
||||
@bookmarks = @video.bookmarks
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
@video = @bookmark.video
|
||||
|
||||
@bookmark.update(bookmark_params)
|
||||
@bookmarks = @video.bookmarks
|
||||
end
|
||||
|
||||
def destroy
|
||||
@video = @bookmark.video
|
||||
|
||||
@bookmark.destroy
|
||||
@bookmarks = @video.bookmarks
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def bookmark_params
|
||||
params.require(:bookmark).permit(:notes, :time_elapsed, :category)
|
||||
end
|
||||
|
||||
def bookmarks
|
||||
if @video
|
||||
policy_scope(@video.bookmarks)
|
||||
else
|
||||
policy_scope(Bookmark)
|
||||
end
|
||||
end
|
||||
|
||||
def videos
|
||||
policy_scope(Video)
|
||||
end
|
||||
|
||||
def set_bookmark
|
||||
@bookmark = authorize bookmarks.find(params[:id])
|
||||
end
|
||||
|
||||
def set_video
|
||||
@video = authorize videos.find(params[:video_id])
|
||||
end
|
||||
end
|
||||
109
app/controllers/broadcasts_controller.rb
Normal file
@@ -0,0 +1,109 @@
|
||||
class BroadcastsController < ApplicationController
|
||||
layout "project"
|
||||
|
||||
before_action :set_project
|
||||
before_action :build_broadcast, only: [:new, :create]
|
||||
before_action :set_broadcast, only: [:show, :destroy, :update]
|
||||
before_action :set_multi_view_broadcasts, only: [:show]
|
||||
|
||||
def index
|
||||
@broadcasts = filtered_broadcasts.order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
@broadcast.attributes = broadcast_params
|
||||
|
||||
if @broadcast.save
|
||||
log_create_analytics
|
||||
redirect_to [@project, :broadcasts], notice: t(".notice")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@broadcast.update(broadcast_params)
|
||||
@files = @broadcast.files.order("created_at DESC").paginate(page: 1)
|
||||
|
||||
pagination_content = ApplicationController.render html: helpers.will_paginate(@files, params: { active_tab: params[:active_tab] })
|
||||
BroadcastsChannel.file_upload_updates(@broadcast, @files, pagination_content)
|
||||
end
|
||||
|
||||
def show
|
||||
@conference_url = url_for [@broadcast.project, @broadcast, :zoom_meeting]
|
||||
@recordings = @broadcast.broadcast_recordings.order_by_recent.paginate(page: params[:page])
|
||||
@files = @broadcast.files.order("created_at DESC").paginate(page: params[:page])
|
||||
render layout: 'application'
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @broadcast.destroy
|
||||
redirect_to [@project, :broadcasts], alert: t(".alert")
|
||||
else
|
||||
redirect_to [@project, :broadcasts], alert: t(".api_error")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def broadcast_params
|
||||
params.require(:broadcast).permit(:name, files: [])
|
||||
end
|
||||
|
||||
def set_project
|
||||
@project = policy_scope(Project).find(params[:project_id])
|
||||
end
|
||||
|
||||
def set_broadcast
|
||||
@broadcast = authorize policy_scope(Broadcast).find(params[:id])
|
||||
end
|
||||
|
||||
def build_broadcast
|
||||
@broadcast = authorize @project.broadcasts.build
|
||||
end
|
||||
|
||||
def broadcasts
|
||||
authorize policy_scope(@project.broadcasts)
|
||||
end
|
||||
|
||||
def set_multi_view_broadcasts
|
||||
authorized_broadcasts = authorize policy_scope(Broadcast).where(id: params[:multi_view_ids]).order_by_recent
|
||||
@multi_view_broadcasts = authorized_broadcasts.map { |b| MultiViewBroadcast.new(b, params[:multi_view_ids]) }
|
||||
end
|
||||
|
||||
def filtered_broadcasts
|
||||
results = broadcasts
|
||||
|
||||
if params[:query].present?
|
||||
results = results.search(params[:query])
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_live_stream, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
|
||||
class MultiViewBroadcast
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
delegate_missing_to :@broadcast
|
||||
|
||||
def initialize(broadcast, multi_view_ids)
|
||||
@broadcast = broadcast
|
||||
@multi_view_ids = multi_view_ids
|
||||
end
|
||||
|
||||
def url
|
||||
project_broadcast_path(@broadcast.project, @broadcast, multi_view_ids: @multi_view_ids, locale: I18n.locale)
|
||||
end
|
||||
|
||||
def uid
|
||||
id
|
||||
end
|
||||
end
|
||||
end
|
||||
40
app/controllers/bulk_taggings_controller.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
class BulkTaggingsController < ApplicationController
|
||||
before_action :set_project
|
||||
before_action :set_releasables
|
||||
before_action :authorize_bulk_taggings, only: [:new, :create]
|
||||
|
||||
def new
|
||||
@releasable_ids = releasable_params[:ids]
|
||||
end
|
||||
|
||||
def create
|
||||
@releasables.each do |releasable|
|
||||
releasable.tag_list.add(params[:name])
|
||||
releasable.save
|
||||
end
|
||||
|
||||
all_releasables
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def releasable_params
|
||||
{ type: params[:releasable_name].classify.constantize, ids: JSON.parse(params[:releasable_ids]) }
|
||||
end
|
||||
|
||||
def set_releasables
|
||||
@releasables = releasable_params[:type].where(id: releasable_params[:ids])
|
||||
end
|
||||
|
||||
def set_project
|
||||
@project = policy_scope(Project).find(params[:project_id])
|
||||
end
|
||||
|
||||
def authorize_bulk_taggings
|
||||
authorize @releasables.first.tags
|
||||
end
|
||||
|
||||
def all_releasables
|
||||
@all_releasables = policy_scope(releasable_params[:type].where(project: @project)).order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
end
|
||||
0
app/controllers/concerns/.keep
Normal file
13
app/controllers/concerns/acquired_media_release_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module AcquiredMediaReleaseContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def acquired_media_releases
|
||||
policy_scope(AcquiredMediaRelease)
|
||||
end
|
||||
|
||||
def set_acquired_media_release
|
||||
acquired_media_release_id = params[:acquired_media_release_id] || params[:id]
|
||||
|
||||
@acquired_media_release = authorize acquired_media_releases.find(acquired_media_release_id)
|
||||
end
|
||||
end
|
||||
13
app/controllers/concerns/appearance_release_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module AppearanceReleaseContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def appearance_releases
|
||||
policy_scope(AppearanceRelease)
|
||||
end
|
||||
|
||||
def set_appearance_release
|
||||
appearance_release_id = params[:appearance_release_id] || params[:id]
|
||||
|
||||
@appearance_release = authorize appearance_releases.find(appearance_release_id)
|
||||
end
|
||||
end
|
||||
21
app/controllers/concerns/create_releasable_jobs.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module CreateReleasableJobs
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def after_create(releasable)
|
||||
if headshots?(releasable)
|
||||
AddHeadshotCollectionUidToProjectJob.perform_later(releasable.project)
|
||||
end
|
||||
SetTagsForReleasableJob.perform_later(releasable)
|
||||
if releasable.contract_template.present?
|
||||
AttachContractToReleasableJob.perform_later(releasable)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def headshots?(releasable)
|
||||
[AppearanceRelease, TalentRelease].include?(releasable.class)
|
||||
end
|
||||
end
|
||||
13
app/controllers/concerns/location_release_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module LocationReleaseContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def location_releases
|
||||
policy_scope(LocationRelease)
|
||||
end
|
||||
|
||||
def set_location_release
|
||||
location_release_id = params[:location_release_id] || params[:id]
|
||||
|
||||
@location_release = authorize location_releases.find(location_release_id)
|
||||
end
|
||||
end
|
||||
13
app/controllers/concerns/music_release_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module MusicReleaseContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def music_releases
|
||||
policy_scope(MusicRelease)
|
||||
end
|
||||
|
||||
def set_music_release
|
||||
music_release_id = params[:music_release_id] || params[:id]
|
||||
|
||||
@music_release = authorize music_releases.find(music_release_id)
|
||||
end
|
||||
end
|
||||
13
app/controllers/concerns/project_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module ProjectContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def projects
|
||||
policy_scope(Project)
|
||||
end
|
||||
|
||||
def set_project
|
||||
project_id = params[:project_id]
|
||||
|
||||
@project = projects.find(project_id)
|
||||
end
|
||||
end
|
||||
15
app/controllers/concerns/project_layout.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
module ProjectLayout
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
layout "project"
|
||||
|
||||
breadcrumb -> { t("shared.files") }, -> { project_for_layout }, match: :exact
|
||||
breadcrumb -> { controller_name.titleize }, -> { [project_for_layout, controller_name] }, match: :exact
|
||||
breadcrumb -> { action_name.titleize }, :url_for, only: [:new, :edit]
|
||||
|
||||
def project_for_layout
|
||||
@project || instance_variable_get("@#{controller_name.singularize}").project
|
||||
end
|
||||
end
|
||||
end
|
||||
10
app/controllers/concerns/set_current_request_details.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
module SetCurrentRequestDetails
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action do
|
||||
Current.user = current_user if signed_in?
|
||||
Current.account = Account.find(session[:active_account]) if session[:active_account]
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/controllers/concerns/talent_release_context.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module TalentReleaseContext
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def talent_releases
|
||||
policy_scope(TalentRelease)
|
||||
end
|
||||
|
||||
def set_talent_release
|
||||
talent_release_id = params[:talent_release_id] || params[:id]
|
||||
|
||||
@talent_release = authorize talent_releases.find(talent_release_id)
|
||||
end
|
||||
end
|
||||
34
app/controllers/contract_downloads_controller.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
class ContractDownloadsController < ApplicationController
|
||||
include ProjectContext
|
||||
|
||||
before_action :set_project, only: [:index, :create]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def create
|
||||
authorize policy_scope(Download).create
|
||||
fetch_releases
|
||||
|
||||
download = @project.downloads.create!(release_type: params[:release_type])
|
||||
other_downloads_in_progress = @project.downloads.unfinished_desc_order.offset(1)
|
||||
|
||||
if other_downloads_in_progress.any?
|
||||
in_progress_downloads_details = render_to_string "_other_pending_downloads", locals: { downloads: other_downloads_in_progress, release_type: params[:release_type] }, :layout => false
|
||||
ProjectsChannel.broadcast_download_generation_update(download, in_progress_downloads_details)
|
||||
else
|
||||
ProjectsChannel.broadcast_download_generation_update(download, I18n.t("contract_downloads.download.pending", release_type: params[:release_type].titleize))
|
||||
end
|
||||
|
||||
GenerateContractsZipJob.perform_later(@project, download, params[:release_type], @releases.ids)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_releases
|
||||
@releases = policy_scope(@project.public_send(releases))
|
||||
end
|
||||
|
||||
def releases
|
||||
params[:release_type].constantize.model_name.plural
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ContractTemplates::BlankContractsController < ApplicationController
|
||||
before_action :set_contract_template
|
||||
|
||||
def show
|
||||
send_file contract.to_pdf, download_attributes
|
||||
end
|
||||
|
||||
def new
|
||||
authorize BlankContract
|
||||
render 'blank_contracts/new'
|
||||
end
|
||||
|
||||
def create
|
||||
if number_of_copies_valid?
|
||||
send_file contract.to_pdf, download_attributes
|
||||
else
|
||||
authorize BlankContract
|
||||
redirect_back fallback_location: [:new, @contract_template, :blank_contracts], notice: t('.number_of_copies_invalid_notice')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def number_of_copies_valid?
|
||||
number_of_copies = params[:number_of_copies].to_i
|
||||
number_of_copies.positive?
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
|
||||
def contract_templates
|
||||
policy_scope(ContractTemplate)
|
||||
end
|
||||
|
||||
def set_contract_template
|
||||
@contract_template = contract_templates.find(params[:contract_template_id])
|
||||
end
|
||||
|
||||
def releasable
|
||||
create_releasable_instance
|
||||
end
|
||||
|
||||
def create_releasable_instance
|
||||
template_release_type = @contract_template[:release_type]
|
||||
releasable = "#{template_release_type}_release".classify.safe_constantize.new
|
||||
releasable.contract_template = @contract_template
|
||||
releasable.project_id = @contract_template.project_id
|
||||
releasable
|
||||
end
|
||||
|
||||
def contract
|
||||
authorize BlankContract.new(releasable, params[:number_of_copies])
|
||||
end
|
||||
|
||||
def download_attributes
|
||||
{
|
||||
disposition: 'inline',
|
||||
filename: contract.filename,
|
||||
type: 'application/pdf'
|
||||
}
|
||||
end
|
||||
|
||||
def render_sample_html
|
||||
# NOTE: For development purposes, this contract renders with the current locale, not the locale of the release itself
|
||||
render contract.render_attributes
|
||||
end
|
||||
end
|
||||
28
app/controllers/contract_templates/qr_codes_controller.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class ContractTemplates::QrCodesController < ApplicationController
|
||||
before_action :set_contract_template
|
||||
|
||||
def show
|
||||
send_file qr_code.to_png, download_attributes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contract_templates
|
||||
policy_scope(ContractTemplate)
|
||||
end
|
||||
|
||||
def set_contract_template
|
||||
@contract_template = contract_templates.find(params[:contract_template_id])
|
||||
end
|
||||
|
||||
def qr_code
|
||||
authorize QrCode.build_from_contract_template(@contract_template)
|
||||
end
|
||||
|
||||
def download_attributes
|
||||
{
|
||||
filename: qr_code.filename,
|
||||
type: "image/png",
|
||||
}
|
||||
end
|
||||
end
|
||||
74
app/controllers/contract_templates_controller.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ContractTemplatesController < ApplicationController
|
||||
include ProjectContext
|
||||
|
||||
layout 'project'
|
||||
|
||||
before_action :set_project, except: [:destroy]
|
||||
before_action :set_contract_template, only: [:destroy]
|
||||
|
||||
def index
|
||||
@contract_templates = contract_templates.non_archived.order_by_name.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@contract_template = build_contract_template
|
||||
end
|
||||
|
||||
def create
|
||||
@contract_template = build_contract_template(contract_template_params)
|
||||
if params[:commit] == 'preview'
|
||||
ctp = ContractTemplatePreview.new(@contract_template)
|
||||
releasable_instance = ctp.build_releasable
|
||||
contract = Contract.new releasable_instance, true
|
||||
send_file contract.to_pdf, download_attributes
|
||||
elsif @contract_template.save
|
||||
redirect_to [@project, :contract_templates], notice: t('.notice')
|
||||
else
|
||||
@release_type = contract_template_params[:release_type]
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@contract_template.archive
|
||||
redirect_to [@contract_template.project, :contract_templates], alert: t('.archived_notice')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_contract_template
|
||||
@contract_template = authorize contract_templates.find(params[:id])
|
||||
end
|
||||
|
||||
def contract_templates
|
||||
if @project
|
||||
policy_scope(@project.contract_templates)
|
||||
else
|
||||
policy_scope(ContractTemplate)
|
||||
end
|
||||
end
|
||||
|
||||
def build_contract_template(attrs = {})
|
||||
authorize contract_templates.build(attrs)
|
||||
end
|
||||
|
||||
def contract_template_params
|
||||
params
|
||||
.require(:contract_template)
|
||||
.permit(:name, :release_type, :body, :guardian_clause, :fee,
|
||||
:applicable_medium_id, :applicable_medium_text,
|
||||
:territory_id, :territory_text,
|
||||
:term_id, :term_text,
|
||||
:restriction_id, :restriction_text)
|
||||
end
|
||||
|
||||
def download_attributes
|
||||
{
|
||||
disposition: 'inline',
|
||||
filename: 'contract-preview.pdf',
|
||||
type: 'application/pdf'
|
||||
}
|
||||
end
|
||||
end
|
||||
55
app/controllers/contracts_controller.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
class ContractsController < ApplicationController
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.pdf { send_contract_pdf }
|
||||
|
||||
if Rails.env.development?
|
||||
format.html { render_sample_html }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def releases
|
||||
if @project
|
||||
policy_scope(@project.public_send(releasable_param.name.pluralize))
|
||||
else
|
||||
policy_scope(releasable_param.type)
|
||||
end
|
||||
end
|
||||
|
||||
def releasable_param
|
||||
@releasable_param ||= ReleasableParam.new(params.to_unsafe_h)
|
||||
end
|
||||
|
||||
def releasable
|
||||
authorize releases.find(releasable_param.id)
|
||||
end
|
||||
|
||||
def contract
|
||||
authorize Contract.new(releasable)
|
||||
end
|
||||
|
||||
def download_attributes
|
||||
{
|
||||
disposition: "inline",
|
||||
filename: contract.filename,
|
||||
type: "application/pdf",
|
||||
}
|
||||
end
|
||||
|
||||
def render_sample_html
|
||||
# NOTE: For development purposes, this contract renders with the current locale, not the locale of the release itself
|
||||
render contract.render_attributes
|
||||
end
|
||||
|
||||
def send_contract_pdf
|
||||
# Native release contracts must be generated on-the-fly; non-native releases have a contract attachment
|
||||
if releasable.native?
|
||||
send_file contract.to_pdf, download_attributes
|
||||
else
|
||||
redirect_to releasable.contract.service_url
|
||||
end
|
||||
end
|
||||
end
|
||||
89
app/controllers/directories_controller.rb
Normal file
@@ -0,0 +1,89 @@
|
||||
class DirectoriesController < ApplicationController
|
||||
before_action :set_project
|
||||
before_action :build_directory, only: [:new, :create]
|
||||
before_action :set_directory, except: [:new, :create]
|
||||
|
||||
layout "project"
|
||||
|
||||
breadcrumb -> { t("shared.files") }, -> { @project }, match: :exact
|
||||
breadcrumb -> { @directory.name.titleize }, -> { [@project, @directory] }, match: :exact, except: [:new, :create]
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def create
|
||||
@directory.attributes = directory_params_with_user
|
||||
|
||||
if @directory.save
|
||||
log_create_analytics
|
||||
redirect_to [@project, @directory], notice: "The folder has been created"
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@files = filtered_files.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @directory.update(directory_params)
|
||||
redirect_to [@project, @directory], notice: t(".notice")
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@directory.destroy
|
||||
redirect_to @project, alert: t(".alert")
|
||||
end
|
||||
|
||||
def new_file
|
||||
end
|
||||
|
||||
def destroy_file
|
||||
file = ActiveStorage::Attachment.find(params[:file_id])
|
||||
file.purge
|
||||
redirect_to [@project, @directory], alert: t(".alert")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_files
|
||||
results = @directory.files
|
||||
|
||||
if params[:query].present?
|
||||
results = @directory.search_files(params[:query])
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def set_project
|
||||
@project = policy_scope(Project).find(params[:project_id])
|
||||
end
|
||||
|
||||
def set_directory
|
||||
@directory = authorize policy_scope(Directory).find(params[:id])
|
||||
end
|
||||
|
||||
def build_directory
|
||||
@directory = authorize @project.directories.build
|
||||
end
|
||||
|
||||
def directory_params_with_user
|
||||
directory_params.merge(user: current_user)
|
||||
end
|
||||
|
||||
def directory_params
|
||||
params.require(:directory).permit(:name, :category, :permissions, files: [])
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_custom_folder, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
end
|
||||
43
app/controllers/downloads_controller.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
class DownloadsController < ApplicationController
|
||||
include ProjectContext
|
||||
|
||||
before_action :set_project, only: [:index, :destroy]
|
||||
before_action :set_download, only: [:destroy]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def index
|
||||
@downloads = filtered_downloads.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @download
|
||||
@project = @download.project
|
||||
|
||||
if @download.destroy
|
||||
redirect_to [@project, :downloads], alert: "The download has been deleted"
|
||||
end
|
||||
end
|
||||
|
||||
def project_for_layout
|
||||
@project || @download.project
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def downloads
|
||||
authorize policy_scope(@project.downloads)
|
||||
end
|
||||
|
||||
def downloads_desc_order
|
||||
downloads.order("created_at DESC")
|
||||
end
|
||||
|
||||
def filtered_downloads
|
||||
params[:query].present? ? downloads_desc_order.search(params[:query]) : downloads_desc_order
|
||||
end
|
||||
|
||||
def set_download
|
||||
@download = policy_scope(@project.downloads).find(params[:id])
|
||||
end
|
||||
end
|
||||
44
app/controllers/file_infos_controller.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
class FileInfosController < ApplicationController
|
||||
before_action :set_releasable
|
||||
|
||||
layout "project"
|
||||
|
||||
breadcrumb -> { t("shared.files") }, -> { @releasable.project }, match: :exact
|
||||
breadcrumb -> { @releasable.model_name.plural.titleize }, -> { [@releasable.project, @releasable.model_name.plural] }, match: :exact
|
||||
breadcrumb -> { t(".heading") }, :url_for
|
||||
|
||||
def edit
|
||||
@project = @releasable.project
|
||||
end
|
||||
|
||||
def update
|
||||
@project = @releasable.project
|
||||
|
||||
if @releasable.update(releasable_params)
|
||||
SetTagsForReleasableJob.perform_later(@releasable)
|
||||
redirect_to [@project, @releasable.model_name.plural], notice: t(".notice")
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def releasable_param
|
||||
@releasable_param ||= ReleasableParam.new(params.to_unsafe_h)
|
||||
end
|
||||
|
||||
def set_releasable
|
||||
@releasable = authorize policy_scope(releasable_param.type).find(releasable_param.id), :"#{action_name}_file_infos?"
|
||||
end
|
||||
|
||||
def releasable_params
|
||||
params.fetch(releasable_param.name, {}).permit(
|
||||
file_infos_attributes: [
|
||||
:filename,
|
||||
:content_type,
|
||||
:byte_size
|
||||
],
|
||||
)
|
||||
end
|
||||
end
|
||||
30
app/controllers/graphic_reports_controller.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
class GraphicReportsController < ApplicationController
|
||||
def show
|
||||
report = build_report(params[:type])
|
||||
respond_to do |format|
|
||||
format.xlsx { send_data(report.to_xls, download_attributes_xls(report)) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def video
|
||||
authorize policy_scope(Video).find(params[:video_id])
|
||||
end
|
||||
|
||||
def build_report(type)
|
||||
case type
|
||||
when "nat_geo"
|
||||
authorize ExcelReports::GraphicReports::NatGeoTextGraphicsLog.new(video)
|
||||
else
|
||||
authorize ExcelReports::GraphicReports::DiscoveryGfxCueList.new(video)
|
||||
end
|
||||
end
|
||||
|
||||
def download_attributes_xls(report)
|
||||
{
|
||||
filename: report.filename,
|
||||
type: Mime[:xlsx]
|
||||
}
|
||||
end
|
||||
end
|
||||
31
app/controllers/issues_and_concerns_reports_controller.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
class IssuesAndConcernsReportsController < ApplicationController
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.xlsx { send_issues_and_concerns_report_xls }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def videos
|
||||
policy_scope(Video)
|
||||
end
|
||||
|
||||
def video
|
||||
authorize videos.find(params[:video_id])
|
||||
end
|
||||
|
||||
def issues_and_concerns_report
|
||||
authorize ExcelReports::IssuesAndConcernsReports::IssuesAndConcernsReport.new(video)
|
||||
end
|
||||
|
||||
def send_issues_and_concerns_report_xls
|
||||
send_data(
|
||||
issues_and_concerns_report.to_xls,
|
||||
{
|
||||
filename: issues_and_concerns_report.filename,
|
||||
type: Mime[:xlsx]
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
23
app/controllers/location_releases/photos_controller.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
class LocationReleases::PhotosController < ApplicationController
|
||||
include LocationReleaseContext
|
||||
|
||||
before_action :set_location_release
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @location_release.update(location_release_params)
|
||||
SetTagsForReleasableJob.perform_later(@location_release)
|
||||
redirect_to @location_release.project, notice: t(".notice")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def location_release_params
|
||||
params.require(:location_release).permit(photos: [])
|
||||
end
|
||||
end
|
||||
96
app/controllers/location_releases_controller.rb
Normal file
@@ -0,0 +1,96 @@
|
||||
class LocationReleasesController < ApplicationController
|
||||
include ProjectContext, LocationReleaseContext
|
||||
|
||||
before_action :set_project, only: [:index, :new, :create]
|
||||
before_action :set_location_release, only: [:edit, :update, :destroy]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def index
|
||||
@location_releases = filtered_location_releases.order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@location_release = build_location_release
|
||||
end
|
||||
|
||||
def create
|
||||
@location_release = build_location_release(location_release_params)
|
||||
|
||||
if @location_release.save
|
||||
log_create_analytics
|
||||
SetTagsForReleasableJob.perform_later(@location_release)
|
||||
redirect_to [@project, :location_releases], notice: t(".notice")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@project = @location_release.project
|
||||
end
|
||||
|
||||
def update
|
||||
@project = @location_release.project
|
||||
|
||||
if @location_release.update(location_release_params)
|
||||
redirect_to [@project, :location_releases], notice: t(".notice")
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@project = @location_release.project
|
||||
|
||||
@location_release.destroy
|
||||
|
||||
redirect_to [@project, :location_releases], alert: t(".alert")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def location_releases
|
||||
if @project
|
||||
policy_scope(@project.location_releases)
|
||||
else
|
||||
policy_scope(LocationRelease)
|
||||
end
|
||||
end
|
||||
|
||||
def location_release_params
|
||||
params.require(:location_release).permit(
|
||||
:name, :address_street1, :address_street2, :address_city, :address_state, :address_zip, :address_country,
|
||||
:person_first_name, :person_last_name, :person_title, :person_company, :person_phone, :person_email,
|
||||
:person_address_street1, :person_address_street2, :person_address_city, :person_address_state, :person_address_zip, :person_address_country,
|
||||
:contract, { photos: [] },
|
||||
:applicable_medium_id, :applicable_medium_text,
|
||||
:territory_id, :territory_text,
|
||||
:term_id, :term_text,
|
||||
:restriction_id, :restriction_text,
|
||||
:filming_started_on, :filming_ended_on
|
||||
)
|
||||
end
|
||||
|
||||
def build_location_release(attrs = {})
|
||||
authorize @project.location_releases.build(attrs)
|
||||
end
|
||||
|
||||
def filtered_location_releases
|
||||
results = location_releases
|
||||
|
||||
if params[:query].present?
|
||||
results = results.search(params[:query])
|
||||
end
|
||||
|
||||
if params[:video_id].present?
|
||||
results = results.appearing_in(params[:video_id])
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_non_native_release, release_type: LocationRelease.to_s, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
end
|
||||
96
app/controllers/material_releases_controller.rb
Normal file
@@ -0,0 +1,96 @@
|
||||
class MaterialReleasesController < ApplicationController
|
||||
include ProjectContext
|
||||
|
||||
before_action :set_project, only: [:index, :new, :create]
|
||||
before_action :set_material_release, only: [:edit, :update, :destroy]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
def index
|
||||
@material_releases = filtered_material_releases.order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@material_release = build_material_release
|
||||
end
|
||||
|
||||
def create
|
||||
@material_release = build_material_release(material_release_params)
|
||||
|
||||
if @material_release.save
|
||||
log_create_analytics
|
||||
SetTagsForReleasableJob.perform_later(@material_release)
|
||||
redirect_to [@project, :material_releases], notice: t(".notice")
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@project = @material_release.project
|
||||
end
|
||||
|
||||
def update
|
||||
@project = @material_release.project
|
||||
|
||||
if @material_release.update(material_release_params)
|
||||
redirect_to [@project, :material_releases], notice: t(".notice")
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@project = @material_release.project
|
||||
|
||||
@material_release.destroy
|
||||
|
||||
redirect_to [@project, :material_releases], alert: t(".alert")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def material_release_params
|
||||
params.require(:material_release).permit(
|
||||
:name, :address_street1, :address_street2, :address_city, :address_state, :address_zip, :address_country,
|
||||
:person_first_name, :person_last_name, :person_title, :person_company, :person_phone, :person_email,
|
||||
:person_address_street1, :person_address_street2, :person_address_city, :person_address_state, :person_address_zip, :person_address_country,
|
||||
:applicable_medium_id, :applicable_medium_text,
|
||||
:territory_id, :territory_text,
|
||||
:term_id, :term_text,
|
||||
:restriction_id, :restriction_text,
|
||||
:description,
|
||||
:contract, { photos: [] }
|
||||
)
|
||||
end
|
||||
|
||||
def material_releases
|
||||
if @project
|
||||
policy_scope(@project.material_releases)
|
||||
else
|
||||
policy_scope(MaterialRelease)
|
||||
end
|
||||
end
|
||||
|
||||
def build_material_release(attrs = {})
|
||||
authorize @project.material_releases.build(attrs)
|
||||
end
|
||||
|
||||
def set_material_release
|
||||
@material_release = authorize material_releases.find(params[:id])
|
||||
end
|
||||
|
||||
def filtered_material_releases
|
||||
results = material_releases
|
||||
|
||||
if params[:query].present?
|
||||
results = results.search(params[:query])
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_non_native_release, release_type: MaterialRelease.to_s, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
end
|
||||
33
app/controllers/multipart_signatures_controller.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
class MultipartSignaturesController < ApplicationController
|
||||
skip_after_action :verify_authorized
|
||||
|
||||
def create
|
||||
render plain: hmac_data, status: 200
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hmac_data
|
||||
aws_secret = ENV['AWS_SECRET_ACCESS_KEY']
|
||||
timestamp = params[:datetime]
|
||||
|
||||
# TESTING UTF-8 encoding
|
||||
aws_secret = aws_secret.encode('UTF-8')
|
||||
details = params[:to_sign].encode('UTF-8')
|
||||
|
||||
date = hmac("AWS4#{aws_secret}", timestamp[0..7])
|
||||
region = hmac(date, ENV["AWS_REGION"])
|
||||
service = hmac(region, "s3")
|
||||
signing = hmac(service, 'aws4_request')
|
||||
|
||||
hexhmac(signing, details)
|
||||
end
|
||||
|
||||
def hmac(key, value)
|
||||
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
|
||||
end
|
||||
|
||||
def hexhmac(key, value)
|
||||
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value)
|
||||
end
|
||||
end
|
||||
118
app/controllers/music_releases_controller.rb
Normal file
@@ -0,0 +1,118 @@
|
||||
class MusicReleasesController < ApplicationController
|
||||
include ProjectContext, MusicReleaseContext
|
||||
|
||||
before_action :set_project, only: [:index, :new, :create]
|
||||
before_action :set_music_release, only: [:edit, :update, :destroy]
|
||||
|
||||
include ProjectLayout
|
||||
|
||||
NUMBER_OF_COMPOSERS = 5
|
||||
NUMBER_OF_PUBLISHERS = 2
|
||||
|
||||
def index
|
||||
@music_releases = filtered_music_releases.order_by_recent.paginate(page: params[:page])
|
||||
end
|
||||
|
||||
def new
|
||||
@music_release = build_music_release
|
||||
prefill_composers_and_publishers
|
||||
end
|
||||
|
||||
def create
|
||||
@music_release = build_music_release(music_release_params)
|
||||
|
||||
if @music_release.save
|
||||
log_create_analytics
|
||||
SetTagsForReleasableJob.perform_later(@music_release)
|
||||
redirect_to [@project, :music_releases], notice: t(".notice")
|
||||
else
|
||||
prefill_composers_and_publishers
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@project = @music_release.project
|
||||
prefill_composers_and_publishers
|
||||
end
|
||||
|
||||
def update
|
||||
@project = @music_release.project
|
||||
|
||||
if @music_release.update(music_release_params)
|
||||
redirect_to [@project, :music_releases], notice: t(".notice")
|
||||
else
|
||||
prefill_composers_and_publishers
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@project = @music_release.project
|
||||
@music_release.destroy
|
||||
redirect_to [@project, :music_releases], alert: t(".alert")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prefill_composers_and_publishers
|
||||
(NUMBER_OF_COMPOSERS - @music_release.composers.size).times { @music_release.composers.build }
|
||||
(NUMBER_OF_PUBLISHERS - @music_release.publishers.size).times { @music_release.publishers.build }
|
||||
end
|
||||
|
||||
def music_releases
|
||||
if @project
|
||||
policy_scope(@project.music_releases)
|
||||
else
|
||||
policy_scope(MusicRelease)
|
||||
end
|
||||
end
|
||||
|
||||
def music_release_params
|
||||
params.require(:music_release).permit(
|
||||
:name, :person_first_name, :person_last_name, :person_phone, :person_email, :person_company, :person_title,
|
||||
:person_address_street1, :person_address_street2, :person_address_city, :person_address_state, :person_address_zip, :person_address_country,
|
||||
:contract,
|
||||
:applicable_medium_id, :applicable_medium_text,
|
||||
:territory_id, :territory_text,
|
||||
:term_id, :term_text,
|
||||
:restriction_id, :restriction_text,
|
||||
file_infos_attributes: [
|
||||
:filename,
|
||||
:content_type,
|
||||
:byte_size
|
||||
],
|
||||
composers_attributes: [
|
||||
:id,
|
||||
:name,
|
||||
:affiliation,
|
||||
:percentage,
|
||||
:cae_number,
|
||||
],
|
||||
publishers_attributes: [
|
||||
:id,
|
||||
:name,
|
||||
:affiliation,
|
||||
:percentage
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def build_music_release(attrs = {})
|
||||
authorize @project.music_releases.build(attrs)
|
||||
end
|
||||
|
||||
def filtered_music_releases
|
||||
results = music_releases
|
||||
|
||||
if params[:query].present?
|
||||
results = results.search(params[:query])
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def log_create_analytics
|
||||
TrackAnalyticsJob.perform_later(Current.user, Current.account, :track_create_non_native_release, release_type: MusicRelease.to_s, user_agent: request.user_agent, user_ip: request.remote_ip)
|
||||
end
|
||||
end
|
||||