diff --git a/app/directives/tc-form-fonts/tc-form-fonts.directive.js b/app/directives/tc-form-fonts/tc-form-fonts.directive.js index a5776871f..2b08d47fd 100644 --- a/app/directives/tc-form-fonts/tc-form-fonts.directive.js +++ b/app/directives/tc-form-fonts/tc-form-fonts.directive.js @@ -15,6 +15,8 @@ scope.submissionForm = formController; }, controller: ['$scope', function($scope) { + var fontsId = 0; + // Must provide React Select component a list with ID, since currently // the onChange callback does not indicate which dropdown called the callback. // There are pull requests pending for react-select which will clean this code up @@ -29,48 +31,75 @@ { label: 'Typography.com', value: 'TYPOGRAPHY_DOT_COM', id: 0 } ]; + var emptyFont = { + source: '', + name: '', + sourceUrl: '', + isFontUrlRequired: false, + isFontUrlDisabled: true, + isFontNameRequired: false, + isFontNameDisabled: true, + isFontSourceRequired: false + }; + $scope.urlRegEx = new RegExp(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/); $scope.selectFont = function(newFont) { - // Find the right font section and change that source value to the value that the user selected - var id = newFont.id; - $scope.formFonts[id].source = newFont.value; + + // Find the right font section, + // and change that source value to the value that the user selected + var targetedFont = $scope.formFonts[newFont.id]; + + targetedFont.source = newFont.value; if (newFont.value === 'STUDIO_STANDARD_FONTS_LIST') { - $scope.formFonts[id].isFontNameRequired = true; - $scope.formFonts[id].isFontNameDisabled = false; - $scope.formFonts[id].isFontUrlRequired = false; - $scope.formFonts[id].isFontUrlDisabled = false; + targetedFont.isFontNameRequired = true; + targetedFont.isFontNameDisabled = false; + targetedFont.isFontUrlRequired = false; + targetedFont.isFontUrlDisabled = false; } else if (newFont.value) { - $scope.formFonts[id].isFontNameRequired = true; - $scope.formFonts[id].isFontNameDisabled = false; - $scope.formFonts[id].isFontUrlRequired = true; - $scope.formFonts[id].isFontUrlDisabled = false; - + targetedFont.isFontNameRequired = true; + targetedFont.isFontNameDisabled = false; + targetedFont.isFontUrlRequired = true; + targetedFont.isFontUrlDisabled = false; } }; $scope.createAdditionalFontFieldset = function() { - var newId = $scope.formFonts.length; + var newId = ++fontsId; // Create copy of list with new, incremented ID var newFontList = $scope['fontList' + newId] = angular.copy($scope['fontList' + (newId - 1)]); - newFontList.forEach(function(font) { font.id++; }); - $scope.formFonts.push({ - id: newId, - source: '', - name: '', - sourceUrl: '', - isFontUrlRequired: false, - isFontUrlDisabled: true, - isFontNameRequired: false, - isFontNameDisabled: true, - isFontSourceRequired: false + // Add empty font with new ID to scope + $scope.formFonts[newId] = _.assign({ id: newId }, angular.copy(emptyFont)); + } + + $scope.deleteFontFieldset = function(index) { + + // If only one font fieldset is there, just reset the values + // so that ng-repeat doesn't refresh and there is no UI flickering + if (Object.keys($scope.formFonts).length === 1) { + $scope.submissionForm['fontName' + index].$setPristine(); + $scope.submissionForm['fontUrl' + index].$setPristine(); + $scope.formFonts[index] = angular.copy(emptyFont); + + } else { + delete $scope.formFonts[index]; + } + } + + $scope.isButtonDisabled = function() { + return _.some($scope.formFonts, function(font) { + if (font.source === 'STUDIO_STANDARD_FONTS_LIST') { + return !font.source || !font.name; + } else { + return !font.source || !font.name || !font.sourceUrl; + } }); } }] diff --git a/app/directives/tc-form-fonts/tc-form-fonts.jade b/app/directives/tc-form-fonts/tc-form-fonts.jade index 387eb6163..efaeeb08f 100644 --- a/app/directives/tc-form-fonts/tc-form-fonts.jade +++ b/app/directives/tc-form-fonts/tc-form-fonts.jade @@ -1,46 +1,49 @@ -.fieldset(ng-repeat="font in formFonts track by font.id") +.fieldset(ng-repeat="(fontId, font) in formFonts") + button.clean.remove-section(type="button", ng-click="deleteFontFieldset(fontId)") + label.tc-label Font Source dropdown( - name="'font-source{{$index}}'", - options="fontList{{$index}}", + name="'font-source{{fontId}}'", + options="fontList{{fontId}}", placeholder="'Select a provider from the list'", searchable="false", clearable="false", on-change="selectFont", - value="formFonts[{{$index}}].source" + value="formFonts[{{fontId}}].source" ) tc-input.fieldset__input( label-text="Font Name", placeholder="Select font source to edit field" input-value="font.name", - input-name="fontName{{$index}}", - input-required="formFonts[$index].isFontNameRequired", - input-disabled="formFonts[$index].isFontNameDisabled" + input-name="fontName{{fontId}}", + input-required="formFonts[fontId].isFontNameRequired", + input-disabled="formFonts[fontId].isFontNameDisabled" ) .tc-error-messages( - ng-show="submissionForm['fontName' + $index].$dirty && submissionForm['fontName' + $index].$invalid" - ng-messages="submissionForm['fontName' + $index].$error" + ng-show="submissionForm['fontName' + fontId].$dirty && submissionForm['fontName' + fontId].$invalid" + ng-messages="submissionForm['fontName' + fontId].$error" ) p(ng-message="required") This field is required. tc-input.fieldset__input( + ng-hide="formFonts[fontId].source === 'STUDIO_STANDARD_FONTS_LIST'", label-text="Font URL", placeholder="Select font source to edit field", input-value="font.sourceUrl", - input-name="fontUrl{{$index}}", - input-required="formFonts[$index].isFontUrlRequired", - input-disabled="formFonts[$index].isFontUrlDisabled", + input-name="fontUrl{{fontId}}", + input-required="formFonts[fontId].isFontUrlRequired", + input-disabled="formFonts[fontId].isFontUrlDisabled", input-pattern="urlRegEx" ) .tc-error-messages( - ng-show="submissionForm['fontUrl' + $index].$dirty && submissionForm['fontUrl' + $index].$invalid" - ng-messages="submissionForm['fontUrl' + $index].$error" + ng-show="submissionForm['fontUrl' + fontId].$dirty && submissionForm['fontUrl' + fontId].$invalid" + ng-messages="submissionForm['fontUrl' + fontId].$error" ) p(ng-message="pattern") Please enter a valid url. p(ng-message="required") This field is required. -button.fieldset__button.tc-btn.tc-btn-s(type="button", ng-click="createAdditionalFontFieldset()") + Add Font +button.fieldset__button.tc-btn.tc-btn-s(type="button", ng-click="createAdditionalFontFieldset()", ng-disabled="isButtonDisabled()") + Add Font diff --git a/app/directives/tc-form-fonts/tc-form-fonts.spec.js b/app/directives/tc-form-fonts/tc-form-fonts.spec.js new file mode 100644 index 000000000..5c17c2df7 --- /dev/null +++ b/app/directives/tc-form-fonts/tc-form-fonts.spec.js @@ -0,0 +1,19 @@ +/* jshint -W117, -W030 */ +describe('Topcoder Form Fonts Directive', function() { + var scope; + + // USE AS TEMPLATE FOR DIRECTIVES + + beforeEach(function() { + bard.appModule('tcUIComponents'); + bard.inject(this, '$compile', '$rootScope'); + }); + + bard.verifyNoOutstandingHttpRequests(); + + xdescribe('', function() { + beforeEach(function() {}); + + it('', function() {}); + }); +}); diff --git a/app/directives/tc-form-stockart/tc-form-stockart.directive.js b/app/directives/tc-form-stockart/tc-form-stockart.directive.js new file mode 100644 index 000000000..a39c5f6c2 --- /dev/null +++ b/app/directives/tc-form-stockart/tc-form-stockart.directive.js @@ -0,0 +1,56 @@ +(function() { + 'use strict'; + + angular.module('tcUIComponents').directive('tcFormStockart', tcFormStockart); + + function tcFormStockart() { + return { + restrict: 'E', + require: '^form', + templateUrl: 'directives/tc-form-stockart/tc-form-stockart.html', + scope: { + formStockarts: '=' + }, + link: function(scope, element, attrs, formController) { + scope.submissionForm = formController; + }, + controller: ['$scope', function($scope) { + var stockartId = 0; + var emptyStockart = { + description: '', + sourceUrl: '', + fileNumber: '' + }; + + $scope.urlRegEx = new RegExp(/^(http(s?):\/\/)?(www\.)?[a-zA-Z0-9\.\-\_]+(\.[a-zA-Z]{2,3})+(\/[a-zA-Z0-9\_\-\s\.\/\?\%\#\&\=]*)?$/); + + $scope.createAdditionalStockartFieldset = function() { + var newId = ++stockartId; + + $scope.formStockarts[newId] = _.assign({ id: newId }, angular.copy(emptyStockart)); + } + + $scope.deleteStockartFieldset = function(index) { + + // If only one stockart fieldset is there, just reset the values + // so that ng-repeat doesn't refresh and there is no UI flickering + if (Object.keys($scope.formStockarts).length === 1) { + $scope.submissionForm['photoDescription' + index].$setPristine(); + $scope.submissionForm['photoURL' + index].$setPristine(); + $scope.submissionForm['fileNumber' + index].$setPristine(); + $scope.formStockarts[index] = angular.copy(emptyStockart); + + } else { + delete $scope.formStockarts[index]; + } + } + + $scope.isButtonDisabled = function() { + return _.some($scope.formStockarts, function(stockart) { + return !stockart.description || !stockart.sourceUrl || !stockart.fileNumber; + }); + } + }] + } + } +})(); diff --git a/app/directives/tc-form-stockart/tc-form-stockart.jade b/app/directives/tc-form-stockart/tc-form-stockart.jade new file mode 100644 index 000000000..403901ce0 --- /dev/null +++ b/app/directives/tc-form-stockart/tc-form-stockart.jade @@ -0,0 +1,29 @@ +.fieldset(ng-repeat="(stockartId, stockart) in formStockarts") + button.clean.remove-section(type="button", ng-click="deleteStockartFieldset(stockartId)") + + tc-input.fieldset__input( + label-text="Photo Description", + placeholder="A picture of a girl", + input-value="stockart.description", + input-name="photoDescription{{stockartId}}" + ) + + tc-input.fieldset__input( + label-text="Photo URL", + placeholder="www.istockphoto.com", + input-value="stockart.sourceUrl", + input-name="photoURL{{stockartId}}", + input-pattern="urlRegEx" + ) + + .tc-error-messages(ng-show="submissionForm['photoURL' + stockartId].$dirty && submissionForm['photoURL' + stockartId].$invalid") + p(ng-show="submissionForm['photoURL' + stockartId].$error.pattern") Please enter a valid url. + + tc-input.fieldset__input( + label-text="File Number", + placeholder="u2434312", + input-value="stockart.fileNumber", + input-name="fileNumber{{stockartId}}" + ) + +button.fieldset__button.tc-btn.tc-btn-s(type="button", ng-click="createAdditionalStockartFieldset()", ng-disabled="isButtonDisabled()") + Add Stock Photo diff --git a/app/directives/tc-form-stockart/tc-form-stockart.spec.js b/app/directives/tc-form-stockart/tc-form-stockart.spec.js new file mode 100644 index 000000000..fe821738a --- /dev/null +++ b/app/directives/tc-form-stockart/tc-form-stockart.spec.js @@ -0,0 +1,19 @@ +/* jshint -W117, -W030 */ +describe('Topcoder Form Stockart Directive', function() { + var scope; + + // USE AS TEMPLATE FOR DIRECTIVES + + beforeEach(function() { + bard.appModule('tcUIComponents'); + bard.inject(this, '$compile', '$rootScope'); + }); + + bard.verifyNoOutstandingHttpRequests(); + + xdescribe('', function() { + beforeEach(function() {}); + + it('', function() {}); + }); +}); diff --git a/app/index.jade b/app/index.jade index 0db7b9d19..6088b9849 100644 --- a/app/index.jade +++ b/app/index.jade @@ -231,6 +231,7 @@ html script(src="directives/tc-endless-paginator/tc-endless-paginator.directive.js") script(src="directives/tc-file-input/tc-file-input.directive.js") script(src="directives/tc-form-fonts/tc-form-fonts.directive.js") + script(src="directives/tc-form-stockart/tc-form-stockart.directive.js") script(src="directives/tc-input/tc-input.directive.js") script(src="directives/tc-paginator/tc-paginator.directive.js") script(src="directives/tc-section/tc-section.directive.js") diff --git a/app/submissions/submit-file/submit-file.controller.js b/app/submissions/submit-file/submit-file.controller.js index 6b00b4eaf..5d7c08fbb 100644 --- a/app/submissions/submit-file/submit-file.controller.js +++ b/app/submissions/submit-file/submit-file.controller.js @@ -19,17 +19,27 @@ vm.finishing = false; vm.showProgress = false; vm.errorInUpload = false; - vm.formFonts = [{ - id: 0, - source: '', - name: '', - sourceUrl: '', - isFontUrlRequired: false, - isFontUrlDisabled: true, - isFontNameRequired: false, - isFontNameDisabled: true, - isFontSourceRequired: false - }]; + vm.formFonts = { + 0: { + id: 0, + source: '', + name: '', + sourceUrl: '', + isFontUrlRequired: false, + isFontUrlDisabled: true, + isFontNameRequired: false, + isFontNameDisabled: true, + isFontSourceRequired: false + } + }; + vm.formStockarts = { + 0: { + id: 1, + description: '', + sourceUrl: '', + fileNumber: '' + } + }; vm.submissionForm = { files: [], @@ -40,12 +50,7 @@ submitterRank: 1, submitterComments: '', fonts: [], - stockArts: [{ - id: 1, - description: '', - sourceUrl: '', - fileNumber: '' - }], + stockArts: [], hasAgreedToTerms: false }; @@ -65,6 +70,8 @@ // Dynamic or static? method: 'DESIGN_CHALLENGE_ZIP_FILE', + + // Can delete below since they are processed and added later? files: [], submitterRank: 1, submitterComments: '', @@ -76,7 +83,6 @@ vm.setRankTo1 = setRankTo1; vm.setFileReference = setFileReference; vm.uploadSubmission = uploadSubmission; - vm.createAnotherStockArtFieldset = createAnotherStockArtFieldset; vm.cancelRetry = cancelRetry; activate(); @@ -127,15 +133,6 @@ } } - function createAnotherStockArtFieldset() { - vm.submissionForm.stockArts.push({ - id: vm.submissionForm.stockArts.length + 1, - description: '', - sourceUrl: '', - fileNumber: '' - }); - } - function uploadSubmission() { vm.errorInUpload = false; vm.uploadProgress = 0; @@ -148,39 +145,35 @@ vm.submissionsBody.data.submitterRank = vm.submissionForm.submitterRank; // Process stock art - if (vm.submissionForm.stockArts[0].description === '') { - vm.submissionsBody.data.stockArts = []; - } else { - var stockArts = angular.copy(vm.submissionForm.stockArts); - vm.submissionsBody.data.stockArts = stockArts.map(function(stockArt) { - delete stockArt.id; - return stockArt; - }); - } + var processedStockarts = _.map(vm.formStockarts, function(formStockart) { + delete formStockart.id; + return formStockart; + }); + + vm.submissionsBody.data.stockArts = processedStockarts; // Process fonts - if (vm.formFonts[0].source === '') { - vm.submissionsBody.data.fonts = []; - } else { - var fonts = angular.copy(vm.formFonts); - vm.submissionsBody.data.fonts = fonts.map(function(font) { - if (font.source) { - delete font.id; - delete font.isFontUrlRequired; - delete font.isFontUrlDisabled; - delete font.isFontNameRequired; - delete font.isFontNameDisabled; - delete font.isFontSourceRequired; - return font; - } - }); - } + var processedFonts = _.reduce(vm.formFonts, function(compiledFonts, formFont) { + if (formFont.source) { + delete formFont.id; + delete formFont.isFontUrlRequired; + delete formFont.isFontUrlDisabled; + delete formFont.isFontNameRequired; + delete formFont.isFontNameDisabled; + delete formFont.isFontSourceRequired; + compiledFonts.push(formFont); + } + + return compiledFonts; + }, []); + + vm.submissionsBody.data.fonts = processedFonts; $log.debug('Body for request: ', vm.submissionsBody); SubmissionsService.getPresignedURL(vm.submissionsBody, files, updateProgress); } - /** + /* * Callback for updating submission upload process. It looks for different phases e.g. PREPARE, UPLOAD, FINISH * of the submission upload and updates the progress UI accordingly. */ diff --git a/app/submissions/submit-file/submit-file.jade b/app/submissions/submit-file/submit-file.jade index a8af832cc..e6921003d 100644 --- a/app/submissions/submit-file/submit-file.jade +++ b/app/submissions/submit-file/submit-file.jade @@ -120,33 +120,7 @@ .form-block__fields .fieldsets - .fieldset(ng-repeat="stockArt in vm.submissionForm.stockArts track by stockArt.id") - tc-input.fieldset__input( - label-text="Photo Description", - placeholder="A picture of a girl", - input-value="stockArt.description", - input-name="photoDescription{{$index}}" - ) - - tc-input.fieldset__input( - label-text="Photo URL", - placeholder="www.istockphoto.com", - input-value="stockArt.sourceUrl", - input-name="photoURL{{$index}}", - input-pattern="vm.urlRegEx" - ) - - .tc-error-messages(ng-show="submissionForm['photoURL' + $index].$dirty && submissionForm['photoURL' + $index].$invalid") - p(ng-show="submissionForm['photoURL' + $index].$error.pattern") Please enter a valid url. - - tc-input.fieldset__input( - label-text="File Number", - placeholder="u2434312", - input-value="stockArt.fileNumber", - input-name="fileNumber{{$index}}" - ) - - button.fieldset__button.tc-btn.tc-btn-s(type="button", ng-click="vm.createAnotherStockArtFieldset()") + Add Stock Photo + tc-form-stockart(form-stockarts="vm.formStockarts") .panel-footer p Submitting your files means you hereby agree to the #[a(ng-href="https://www.{{DOMAIN}}/community/how-it-works/terms/", target="_blank") Topcoder terms of use] and to the extent your uploaded file wins a Topcoder Competition, you hereby assign, grant, and transfer to Topcoder all rights in and title to the Winning Submission (as further described in the terms of use). @@ -164,20 +138,24 @@ modal.transition(show="vm.showProgress", background-click-close="true", style="b .upload-progress(ng-class="{'upload-progress--error': vm.errorInUpload}") .upload-progress__title p Uploading submission for + p.upload-progress-title__challenge-name [Challenge name] - img.upload-progress__image(ng-src="/images/robot.svg", ng-hide="vm.errorInUpload") - img.upload-progress__image--error(ng-src="/images/robot-embarresed.svg", ng-show="vm.errorInUpload") + + img.upload-progress__image(src="/images/robot.svg", ng-hide="vm.errorInUpload") + + img.upload-progress__image--error(src="/images/robot-embarresed.svg", ng-show="vm.errorInUpload") + p.upload-progress__message(ng-hide="vm.errorInUpload") Hey, your work is AWESOME! Please don’t close the window while I’m working, you’ll loose all files! + p.upload-progress__message--error(ng-show="vm.errorInUpload") Oh, that’s embarrassing! The file couldn’t be uploaded, I’m so sorry. progress-bar.upload-progress__progress-bar(completed="vm.uploadProgress", message="of 3 files uploaded") - .upload-progress__preparing(ng-show="vm.preparing && !vm.errorInUpload") - span Preparing... - .upload-progress__finishing(ng-show="vm.finishing && !vm.errorInUpload") - span Finishing... - .upload-progress__error(ng-show="vm.errorInUpload") - span File upload failed + .upload-progress__preparing(ng-show="vm.preparing && !vm.errorInUpload") #[span Preparing...] + .upload-progress__finishing(ng-show="vm.finishing && !vm.errorInUpload") #[span Finishing...] + .upload-progress__error(ng-show="vm.errorInUpload") #[span File upload failed] + .upload-progress__error-action(ng-show="vm.errorInUpload") button.tc-btn.tc-btn-s.tc-btn-ghost(type="button", ng-click="vm.cancelRetry()") Cancel - button.tc-btn.tc-btn-s.tc-btn-secondary(type="button", ng-click="submissionForm.$valid && vm.uploadSubmission()") Try Again \ No newline at end of file + + button.tc-btn.tc-btn-s.tc-btn-secondary(type="button", ng-click="submissionForm.$valid && vm.uploadSubmission()") Try Again diff --git a/assets/css/submissions/submit-file.scss b/assets/css/submissions/submit-file.scss index 60a507f4b..f1e801527 100644 --- a/assets/css/submissions/submit-file.scss +++ b/assets/css/submissions/submit-file.scss @@ -115,12 +115,24 @@ modal { } } -tc-form-fonts { +tc-form-fonts, tc-form-stockart { .fieldset { max-width: 500px; + position: relative; } } +.remove-section { + position: absolute; + width: 20px; + height: 20px; + top: -5px; + right: -35px; + background-image: url(/images/x-mark-gray.svg); + background-size: 20px; + outline: 0; +} + .Select { margin-bottom: 20px; }