diff --git a/client/less/main.less b/client/less/main.less index 9ac5f78e94..3e610dc2dc 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -1018,14 +1018,6 @@ hr { color: @gray-light; } -.asterisk-explanation { - margin-top: -15px; -} - -.map-buttons { - margin-top: -10px; -} - code { padding: 0; } diff --git a/client/less/map.less b/client/less/map.less index 3bb9b58abf..4788375f2d 100644 --- a/client/less/map.less +++ b/client/less/map.less @@ -43,15 +43,9 @@ flex: 1; width: 100%; height: 100%; - border: 0; - overflow: auto; - } -} - -.map-aside-body { - #nested { - margin:0 20px; + overflow: scroll; + -webkit-overflow-scrolling: touch; } } @@ -73,36 +67,73 @@ .map-fixed-header { position: fixed; background: white; - padding-top: 20px; + padding-top: 5px; width: 100%; z-index: 1; left: 0; top: 0; @media (max-width: 720px) { - padding-top:35px; + padding-top:30px; } -} -.map-fixed-header p { - margin: 10px 0 20px; - @media (max-width: 720px) { - margin-bottom:15px; + p { + margin: 5px 0 20px; + @media (max-width: 720px) { + margin-bottom:10px; + } } -} -.map-fixed-header hr { - margin:35px 0; - @media (max-width: 720px) { - margin:25px 0; + hr { + margin:30px 0; + @media (max-width: 720px) { + margin:25px 0; + } } + } + +.map-buttons { + margin-top: -10px; } -.map-buttons button { +.map-buttons button, +.map-buttons .input-group{ width:300px; } +.map-buttons .input-group{ + margin-top: 15px; + margin-left: auto; + margin-right: auto; +} + +#map-filter { + background:#fff; + border-color: darkgreen; +} +.input-group-addon { + width:40px; + color: darkgreen; + background: #fff; + border-color: darkgreen; + &.filled{ + background: darkgreen; + border-color: #000d00; + color: #fff; + cursor: pointer; + } + .fa { + position:absolute; + top:50%; + transform: translateY(-50%); + right:10px; + } +} + .map-accordion { - margin: 140px auto 0; + margin: 135px auto 0; width:700px; overflow-y: auto; position:relative; + #nested { + margin:0 15px; + } h2 > a { width:100%; display:block; @@ -116,7 +147,7 @@ margin:15px 0; padding:0; &:first-child { - margin-top:25px + margin-top:25px } > a { padding-left: 40px; @@ -125,11 +156,11 @@ } } - #nested div :before { + div.chapterBlock :before { margin-right: 15px; } - #nested div p { + div.chapterBlock p { text-indent: -15px; margin-left: 60px; padding-right: 20px @@ -169,7 +200,7 @@ left:0; right:0; width:100%; - margin-top:165px; + margin-top:180px; h2 { margin:15px 0; padding:0; @@ -179,7 +210,7 @@ padding-right:20px; font-size:20px; } - } + } h3 { margin:10px 0; padding:0; @@ -188,7 +219,7 @@ font-size:20px; } } - } + } } .map-aside-action-item { @@ -227,6 +258,18 @@ } } +#noneFound { + display:none; + margin:60px 30px 0; + font-size:30px; + text-align: center; + color:darkgreen; + .fa { + display:block; + font-size:300px; + } +} + .map-aside-action-pop-out { margin-right: -4px; background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMTcxLjQyOSIgZmlsbD0iIzNhMzEzMyI+PHBhdGggZD0iTTE1Ny4xNDMsMTAzLjU3MXYzNS43MTRjMCw4Ljg1NC0zLjE0NCwxNi40MjYtOS40MzEsMjIuNzEzcy0xMy44NTgsOS40MzEtMjIuNzEyLDkuNDMxSDMyLjE0MyBjLTguODU0LDAtMTYuNDI1LTMuMTQ0LTIyLjcxMi05LjQzMVMwLDE0OC4xNCwwLDEzOS4yODVWNDYuNDI5YzAtOC44NTQsMy4xNDQtMTYuNDI1LDkuNDMxLTIyLjcxMiBjNi4yODctNi4yODcsMTMuODU4LTkuNDMxLDIyLjcxMi05LjQzMWg3OC41NzJjMS4wNDEsMCwxLjg5NiwwLjMzNSwyLjU2NiwxLjAwNGMwLjY3LDAuNjcsMS4wMDQsMS41MjUsMS4wMDQsMi41NjdWMjUgYzAsMS4wNDItMC4zMzQsMS44OTctMS4wMDQsMi41NjdjLTAuNjcsMC42Ny0xLjUyNSwxLjAwNC0yLjU2NiwxLjAwNEgzMi4xNDNjLTQuOTExLDAtOS4xMTUsMS43NDktMTIuNjEyLDUuMjQ2IHMtNS4yNDYsNy43MDEtNS4yNDYsMTIuNjEydjkyLjg1NmMwLDQuOTExLDEuNzQ5LDkuMTE1LDUuMjQ2LDEyLjYxMnM3LjcwMSw1LjI0NSwxMi42MTIsNS4yNDVIMTI1YzQuOTEsMCw5LjExNS0xLjc0OCwxMi42MTEtNS4yNDUgYzMuNDk3LTMuNDk3LDUuMjQ2LTcuNzAxLDUuMjQ2LTEyLjYxMnYtMzUuNzE0YzAtMS4wNDIsMC4zMzQtMS44OTcsMS4wMDQtMi41NjdjMC42Ny0wLjY2OSwxLjUyNS0xLjAwNCwyLjU2Ny0xLjAwNGg3LjE0MyBjMS4wNDIsMCwxLjg5NywwLjMzNSwyLjU2NywxLjAwNEMxNTYuODA5LDEwMS42NzQsMTU3LjE0MywxMDIuNTI5LDE1Ny4xNDMsMTAzLjU3MXogTTIwMCw3LjE0M3Y1Ny4xNDMgYzAsMS45MzUtMC43MDcsMy42MDktMi4xMjEsNS4wMjJjLTEuNDEzLDEuNDE0LTMuMDg4LDIuMTIxLTUuMDIxLDIuMTIxYy0xLjkzNSwwLTMuNjA5LTAuNzA3LTUuMDIyLTIuMTIxbC0xOS42NDQtMTkuNjQzIGwtNzIuNzY3LDcyLjc2OWMtMC43NDQsMC43NDQtMS42LDEuMTE1LTIuNTY3LDEuMTE1cy0xLjgyMy0wLjM3MS0yLjU2Ny0xLjExNUw3Ny41NjcsMTA5LjcxYy0wLjc0NC0wLjc0NC0xLjExNi0xLjYtMS4xMTYtMi41NjcgYzAtMC45NjcsMC4zNzItMS44MjIsMS4xMTYtMi41NjZsNzIuNzY4LTcyLjc2OGwtMTkuNjQ0LTE5LjY0M2MtMS40MTMtMS40MTQtMi4xMi0zLjA4OC0yLjEyLTUuMDIyYzAtMS45MzUsMC43MDctMy42MDksMi4xMi01LjAyMiBDMTMyLjEwNSwwLjcwNywxMzMuNzc5LDAsMTM1LjcxNSwwaDU3LjE0M2MxLjkzNCwwLDMuNjA4LDAuNzA3LDUuMDIxLDIuMTIxQzE5OS4yOTMsMy41MzQsMjAwLDUuMjA4LDIwMCw3LjE0M3oiLz48L3N2Zz4=) diff --git a/client/main.js b/client/main.js index a51c262746..3b5c2f4148 100644 --- a/client/main.js +++ b/client/main.js @@ -340,7 +340,10 @@ $(document).ready(function() { $('.map-fixed-header').css('top', '50px'); } - // map + // map global selectors + var mapFilter = $('#map-filter'); + var mapShowAll = $('#showAll'); + $('#nav-map-btn').on('click', showMap); $('.map-aside-action-collapse').on('click', collapseMap); @@ -360,37 +363,122 @@ $(document).ready(function() { } $('#accordion').on('show.bs.collapse', function(e) { - expandCaret(e.target); - if ($('a[data-toggle=collapse]').length === $('.fa-caret-down').length) { - $('#showAll').text('Collapse all challenges'); - $('#showAll').addClass('active'); - } + expandCaret(e.target); + if ($('a[data-toggle=collapse]').length === $('.fa-caret-down').length) { + mapShowAll.text('Collapse all challenges'); + mapShowAll.addClass('active'); + } }).on('hide.bs.collapse', function(e) { - collapseCaret(e.target); - if ($('a[data-toggle=collapse]').length === $('.fa-caret-right').length) { - $('#showAll').text('Expand all challenges'); - $('#showAll').removeClass('active'); - } + collapseCaret(e.target); + if ($('a[data-toggle=collapse]').length === $('.fa-caret-right').length) { + mapShowAll.text('Expand all challenges'); + mapShowAll.removeClass('active'); + } }); - $('#showAll').on('click', () => { - var mapExpanded = $('#showAll').hasClass('active'); + mapShowAll.on('click', () => { + var mapExpanded = mapShowAll.hasClass('active'); if (!mapExpanded) { $.each($('.map-collapse:not(".in")'), function(i, div) { expandBlock(div); }); - $('#showAll').text('Collapse all challenges'); - return $('#showAll').addClass('active'); + mapShowAll.text('Collapse all challenges'); + return mapShowAll.addClass('active'); } else { $.each($('.map-collapse.in'), function(i, div) { collapseBlock(div); }); - $('#showAll').text('Expand all challenges'); - return $('#showAll').removeClass('active'); + mapShowAll.text('Expand all challenges'); + return mapShowAll.removeClass('active'); } }); + // Map live filter + mapFilter.on('keyup', () => { + if (mapFilter.val().length > 0) { + var regex = new RegExp(mapFilter.val().replace(/ /g, '.'), 'i'); + + // Hide/unhide challenges that match the regex + $('.challenge-title').each((index, title) => { + if (regex.test($(title).attr('name'))) { + expandBlock($(title).closest('.chapterBlock')); + expandBlock($(title).closest('.certBlock')); + $(title).removeClass('hidden'); + } else { + $(title).addClass('hidden'); + } + }); + + // Hide/unhide blocks with no matches + $.each($('.chapterBlock'), function(i, div) { + if ($(div).find('.hidden').length === + $(div).find('p').length) { + $(div).addClass('hidden'); + $(div).prev('h3').addClass('hidden'); + } else { + $(div).removeClass('hidden'); + $(div).prev('h3').removeClass('hidden'); + } + }); + + // Hide/unhide superblocks with no matches + $.each($('.certBlock'), function(i, div) { + if ($(div).children('#nested').children('h3.hidden').length === + $(div).children('#nested').children('h3').length) { + $(div).prev('h2').addClass('hidden'); + } else { + $(div).prev('h2').removeClass('hidden'); + } + }); + + // Display "Clear Filter" element + if (mapFilter.next().children().hasClass('fa-search')) { + mapFilter.next() + .children() + .removeClass('fa-search') + .addClass('fa-times'); + mapFilter.next().addClass('filled'); + // Scroll to the top of the page + $('html, body, .map-accordion').scrollTop(0); + } + } else { + clearMapFilter(); + } + + // Display not found if everything is hidden + if ($.find('.certBlock').length === + $('.map-accordion').children('.hidden').length) { + $('#noneFound').show(); + } else { + $('#noneFound').hide(); + } + }); + + // Give focus to the search box by default + mapFilter.focus(); + + // Clicking the search button or x clears the map + $('.map-buttons .input-group-addon').on('click', clearMapFilter); + + function clearMapFilter() { + mapFilter.val(''); + mapFilter.next().children().removeClass('fa-times').addClass('fa-search'); + mapFilter.next().removeClass('filled'); + $('.map-accordion').find('.hidden').removeClass('hidden'); + $('#noneFound').hide(); + } + + // Clear the search on escape key + mapFilter.on('keydown', (e) => { + if (e.keyCode === 27) { + e.preventDefault(); + clearMapFilter(); + } + }); + + window.Mousetrap.bind('esc', clearMapFilter); + // keyboard shortcuts: open map window.Mousetrap.bind('g m', function() { var isCollapsed = $('.map-aside').hasClass('is-collapsed'); diff --git a/server/views/map/show.jade b/server/views/map/show.jade index 9b3134c168..c2aafb974c 100644 --- a/server/views/map/show.jade +++ b/server/views/map/show.jade @@ -1,35 +1,41 @@ extends ../layout-wide block content - .text-center.map-fixed-header.asterisk-explanation + .text-center.map-fixed-header p Challenges required for certifications are marked with a * .row.map-buttons button.center-block.btn.btn-sm.btn-block.btn-primary.active#showAll Collapse all challenges + .row.map-buttons + .input-group + input#map-filter.form-control(type="text" placeholder="Type a challenge name" autocomplete="off" value="") + span.input-group-addon + i.fa.fa-search hr #accordion.map-accordion + #noneFound No results found. Happy Coding! for superBlock, index in superBlocks h2 a(data-toggle='collapse', data-parent='#accordion', href='#collapse'+superBlock.name.split(' ').join('-')) span.no-link-underline - i.fa.fa-caret-down   + i.fa.fa-caret-down | #{superBlock.name} - div.margin-left-10(id = 'collapse'+superBlock.name.split(' ').join('-') class = "collapse in map-collapse no-transition") + div.margin-left-10(id = 'collapse'+superBlock.name.split(' ').join('-') class = "collapse in map-collapse no-transition certBlock") #nested for challengeBlock in superBlock.blocks h3 a(data-toggle='collapse', data-parent='#nested', href='#nested-collapse'+challengeBlock.name.replace(/(\W)/gi, '').split(' ').join('-')) span.no-link-underline - i.fa.fa-caret-down   + i.fa.fa-caret-down | #{challengeBlock.name} span.challengeBlockTime (#{challengeBlock.time}) - div.margin-left-10(id = "nested-collapse"+challengeBlock.name.replace(/\W/gi, '').split(' ').join('-') class = "collapse in map-collapse no-transition") + div.margin-left-10(id = "nested-collapse"+challengeBlock.name.replace(/\W/gi, '').split(' ').join('-') class = "collapse in map-collapse no-transition chapterBlock") for challenge in challengeBlock.challenges if challenge.completed - p.faded.text-primary.ion-checkmark-circled.padded-ionic-icon.negative-15(name="#{challenge.dashedName}") + p.challenge-title.faded.text-primary.ion-checkmark-circled.padded-ionic-icon.negative-15(name="#{challenge.dashedName}") a(href="/challenges/#{challenge.dashedName}" target='_parent') = challenge.title span.sr-only= " Complete" else if challenge.isRequired - p.ion-ios-circle-outline.padded-ionic-icon.negative-15(name="#{challenge.dashedName}") + p.challenge-title.ion-ios-circle-outline.padded-ionic-icon.negative-15(name="#{challenge.dashedName}") a(name="#{challenge.dashedName}" target='_parent' href="/challenges/#{challenge.dashedName}" class=challenge.isComingSoon ? 'disabled' : '') span= challenge.title span.sr-only= " Incomplete" @@ -44,7 +50,7 @@ block content span.text-primary     strong * else - p.ion-ios-circle-outline.padded-ionic-icon.negative-15(name="#{challenge.dashedName}") + p.challenge-title.ion-ios-circle-outline.padded-ionic-icon.negative-15(name="#{challenge.dashedName}") a(name="#{challenge.dashedName}" target='_parent' href="/challenges/#{challenge.dashedName}" class=challenge.isComingSoon ? 'disabled' : '') span= challenge.title span.sr-only= " Incomplete" @@ -61,56 +67,56 @@ block content span.no-link-underline i.fa.fa-caret-down   | Full Stack Development Certification - div.margin-left-10(id = 'collapse-full-stack-development-certification' class = "collapse in map-collapse no-transition") + div.margin-left-10(id = 'collapse-full-stack-development-certification' class = "collapse in map-collapse no-transition certBlock") #nested h3 a(data-toggle='collapse', data-parent='#nested', href='#nested-collapse-nonprofit-projects') span.no-link-underline i.fa.fa-caret-down   - | Nonprofit Projects + | Nonprofit Projects span.challengeBlockTime (800 hours) - div.margin-left-10(id = "nested-collapse-nonprofit-projects" class = "collapse in map-collapse no-transition") - p.challengeBlockDescription To qualify for these nonprofit projects, you must first earn all three foundational certifications: Front End, Data Visualization, and Back End - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Greenfield Nonprofit Project #1") Greenfield Nonprofit Project #1 + div.margin-left-10(id = "nested-collapse-nonprofit-projects" class = "collapse in map-collapse no-transition chapterBlock") + .challengeBlockDescription To qualify for these nonprofit projects, you must first earn all three foundational certifications: Front End, Data Visualization, and Back End + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Greenfield Nonprofit Project #1") Greenfield Nonprofit Project #1 span.text-primary     strong * - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Greenfield Nonprofit Project #2") Greenfield Nonprofit Project #2 - span.text-primary     - strong * - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Legacy Code Nonprofit Project #1") Legacy Code Nonprofit Project #1 + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Greenfield Nonprofit Project #2") Greenfield Nonprofit Project #2 span.text-primary     strong * - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Legacy Code Nonprofit Project #2") Legacy Code Nonprofit Project #2 + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Legacy Code Nonprofit Project #1") Legacy Code Nonprofit Project #1 span.text-primary     strong * - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Claim your Full Stack Development Certification") Claim your Full Stack Development Certification + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Legacy Code Nonprofit Project #2") Legacy Code Nonprofit Project #2 + span.text-primary     + strong * + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Claim your Full Stack Development Certification") Claim your Full Stack Development Certification h2 a(data-toggle='collapse', data-parent='#accordion', href='#collapse-coding-interview-preparation') span.no-link-underline i.fa.fa-caret-down   | Coding Interview Preparation - div.margin-left-10(id = 'collapse-coding-interview-preparation' class = "collapse in map-collapse no-transition") + div.margin-left-10(id = 'collapse-coding-interview-preparation' class = "collapse in map-collapse no-transition certBlock") #nested h3 a(data-toggle='collapse', data-parent='#nested', href='#nested-collapse-coding-interview-training') span.no-link-underline i.fa.fa-caret-down   - | Coding Interview Training + | Coding Interview Training span.challengeBlockTime (70 hours) - div.margin-left-10(id = "nested-collapse-coding-interview-training" class = "collapse in map-collapse no-transition") - p.challengeBlockDescription To qualify for this coding interview training, you must first earn all four certifications: Front End, Data Visualization, Back End, and Full Stack - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Soft Skill Training") Soft Skill Training - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Critical Thinking Training") Critical Thinking Training - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Whiteboard Coding Training") Whiteboard Coding Training + div.margin-left-10(id = "nested-collapse-coding-interview-training" class = "collapse in map-collapse no-transition chapterBlock") + .challengeBlockDescription To qualify for this coding interview training, you must first earn all four certifications: Front End, Data Visualization, Back End, and Full Stack + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Soft Skill Training") Soft Skill Training + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Critical Thinking Training") Critical Thinking Training + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Whiteboard Coding Training") Whiteboard Coding Training h3 a(data-toggle='collapse', data-parent='#nested', href='#nested-collapse-mock-interviews') span.no-link-underline i.fa.fa-caret-down   - | Mock Interviews + | Mock Interviews span.challengeBlockTime (10 hours) - div.margin-left-10(id = "nested-collapse-mock-interviews" class = "collapse in map-collapse no-transition") - p.challengeBlockDescription To qualify for these mock interviews, you must first earn all four certifications: Front End, Data Visualization, Back End, and Full Stack - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Mock Interview #1") Mock Interview #1 - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Mock Interview #2") Mock Interview #2 - p.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Mock Interview #3") Mock Interview #3 + div.margin-left-10(id = "nested-collapse-mock-interviews" class = "collapse in map-collapse no-transition chapterBlock") + .challengeBlockDescription To qualify for these mock interviews, you must first earn all four certifications: Front End, Data Visualization, Back End, and Full Stack + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Mock Interview #1") Mock Interview #1 + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Mock Interview #2") Mock Interview #2 + p.challenge-title.disabled.text-primary.ion-locked.padded-ionic-icon.negative-15(name="Mock Interview #3") Mock Interview #3 .spacer