From 0c54adfb07ff7c862162b918605a60f914ac51ee Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 12 Jan 2016 22:26:19 -0800 Subject: [PATCH] Add map aside to challenges --- client/less/main.less | 1 + client/less/map.less | 125 ++++++++++++++++++++ client/main.js | 9 ++ server/boot/challenge.js | 31 +++-- server/views/partials/challenge-footer.jade | 44 +++++++ server/views/partials/navbar.jade | 4 +- 6 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 client/less/map.less diff --git a/client/less/main.less b/client/less/main.less index 12dc2e3c06..c01dc70836 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -1115,3 +1115,4 @@ code { @import "jobs.less"; @import "challenge.less"; @import "toastr.less"; +@import "map.less"; diff --git a/client/less/map.less b/client/less/map.less new file mode 100644 index 0000000000..92832b339c --- /dev/null +++ b/client/less/map.less @@ -0,0 +1,125 @@ +/* + * based off of https://github.com/gitterHQ/sidecar + * license: MIT + */ +.map-aside { + + z-index: 20000; + position: fixed; + top: 0; + left: 60%; + bottom: 0; + right: 0; + + display: flex; + flex-direction: row; + + background-color: @body-bg; + border-left: 1px solid #333; + box-shadow: -12px 0 18px 0 rgba(50, 50, 50, 0.3); + + transition: transform 0.3s cubic-bezier(0.16, 0.22, 0.22, 1.7); + + &.is-collapsed { + transform: translateX(110%); + } + + /* Add some "extension" so that there isn't a gap + * when we translate(via animation) more than 100% */ + &:after { + content: ''; + + z-index: -1; + position: absolute; + top: 0; + left: 100%; + bottom: 0; + right: -100%; + + background-color: @body-bg; + } + + @media (max-width: 1150px) { + left: 45%; + } + @media (max-width: 944px) { + left: 30%; + } + @media (max-width: 600px) { + left: 15%; + } + @media (max-width: 500px) { + left: 0; + border-left: none; + } + + & > .map-aside-container { + flex: 1; + width: 100%; + height: 100%; + + border: 0; + overflow: auto + } +} + +.map-aside-action-bar { + position: absolute; + top: 0; + left: 0; + right: 0; + + display: flex; + justify-content: flex-end; + + padding-bottom: 0.7em; + + background: linear-gradient(to bottom, #ffffff 0%, #ffffff 50%, rgba(255, 255, 255, 0) 100%); + + z-index: 100; +} + +.map-aside-action-item { + display: flex; + /* main axis */ + justify-content: center; + /* cross axis */ + align-items: center; + + width: 40px; + height: 40px; + + padding-left: 0; + padding-right: 0; + + opacity: 0.65; + background: none; + background-position: center center; + background-repeat: no-repeat; + background-size: 22px 22px; + border: 0; + outline: none; + + cursor: pointer; + cursor: hand; + + transition: all 0.2s ease; + + &:hover, + &:focus { + opacity: 1; + } + + &:active { + filter: hue-rotate(80deg) saturate(150); + } +} + +.map-aside-action-pop-out { + margin-right: -4px; + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMTcxLjQyOSIgZmlsbD0iIzNhMzEzMyI+PHBhdGggZD0iTTE1Ny4xNDMsMTAzLjU3MXYzNS43MTRjMCw4Ljg1NC0zLjE0NCwxNi40MjYtOS40MzEsMjIuNzEzcy0xMy44NTgsOS40MzEtMjIuNzEyLDkuNDMxSDMyLjE0MyBjLTguODU0LDAtMTYuNDI1LTMuMTQ0LTIyLjcxMi05LjQzMVMwLDE0OC4xNCwwLDEzOS4yODVWNDYuNDI5YzAtOC44NTQsMy4xNDQtMTYuNDI1LDkuNDMxLTIyLjcxMiBjNi4yODctNi4yODcsMTMuODU4LTkuNDMxLDIyLjcxMi05LjQzMWg3OC41NzJjMS4wNDEsMCwxLjg5NiwwLjMzNSwyLjU2NiwxLjAwNGMwLjY3LDAuNjcsMS4wMDQsMS41MjUsMS4wMDQsMi41NjdWMjUgYzAsMS4wNDItMC4zMzQsMS44OTctMS4wMDQsMi41NjdjLTAuNjcsMC42Ny0xLjUyNSwxLjAwNC0yLjU2NiwxLjAwNEgzMi4xNDNjLTQuOTExLDAtOS4xMTUsMS43NDktMTIuNjEyLDUuMjQ2IHMtNS4yNDYsNy43MDEtNS4yNDYsMTIuNjEydjkyLjg1NmMwLDQuOTExLDEuNzQ5LDkuMTE1LDUuMjQ2LDEyLjYxMnM3LjcwMSw1LjI0NSwxMi42MTIsNS4yNDVIMTI1YzQuOTEsMCw5LjExNS0xLjc0OCwxMi42MTEtNS4yNDUgYzMuNDk3LTMuNDk3LDUuMjQ2LTcuNzAxLDUuMjQ2LTEyLjYxMnYtMzUuNzE0YzAtMS4wNDIsMC4zMzQtMS44OTcsMS4wMDQtMi41NjdjMC42Ny0wLjY2OSwxLjUyNS0xLjAwNCwyLjU2Ny0xLjAwNGg3LjE0MyBjMS4wNDIsMCwxLjg5NywwLjMzNSwyLjU2NywxLjAwNEMxNTYuODA5LDEwMS42NzQsMTU3LjE0MywxMDIuNTI5LDE1Ny4xNDMsMTAzLjU3MXogTTIwMCw3LjE0M3Y1Ny4xNDMgYzAsMS45MzUtMC43MDcsMy42MDktMi4xMjEsNS4wMjJjLTEuNDEzLDEuNDE0LTMuMDg4LDIuMTIxLTUuMDIxLDIuMTIxYy0xLjkzNSwwLTMuNjA5LTAuNzA3LTUuMDIyLTIuMTIxbC0xOS42NDQtMTkuNjQzIGwtNzIuNzY3LDcyLjc2OWMtMC43NDQsMC43NDQtMS42LDEuMTE1LTIuNTY3LDEuMTE1cy0xLjgyMy0wLjM3MS0yLjU2Ny0xLjExNUw3Ny41NjcsMTA5LjcxYy0wLjc0NC0wLjc0NC0xLjExNi0xLjYtMS4xMTYtMi41NjcgYzAtMC45NjcsMC4zNzItMS44MjIsMS4xMTYtMi41NjZsNzIuNzY4LTcyLjc2OGwtMTkuNjQ0LTE5LjY0M2MtMS40MTMtMS40MTQtMi4xMi0zLjA4OC0yLjEyLTUuMDIyYzAtMS45MzUsMC43MDctMy42MDksMi4xMi01LjAyMiBDMTMyLjEwNSwwLjcwNywxMzMuNzc5LDAsMTM1LjcxNSwwaDU3LjE0M2MxLjkzNCwwLDMuNjA4LDAuNzA3LDUuMDIxLDIuMTIxQzE5OS4yOTMsMy41MzQsMjAwLDUuMjA4LDIwMCw3LjE0M3oiLz48L3N2Zz4=) +} + +.map-aside-action-collapse { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNzEuNDI5IDE3MS40MjkiIGZpbGw9IiMzYTMxMzMiPjxwYXRoIGQ9Ik0xMjIuNDMzLDEwNi4xMzhsLTE2LjI5NSwxNi4yOTVjLTAuNzQ0LDAuNzQ0LTEuNiwxLjExNi0yLjU2NiwxLjExNmMtMC45NjgsMC0xLjgyMy0wLjM3Mi0yLjU2Ny0xLjExNmwtMTUuMjktMTUuMjkgbC0xNS4yOSwxNS4yOWMtMC43NDQsMC43NDQtMS42LDEuMTE2LTIuNTY3LDEuMTE2cy0xLjgyMy0wLjM3Mi0yLjU2Ny0xLjExNmwtMTYuMjk0LTE2LjI5NWMtMC43NDQtMC43NDQtMS4xMTYtMS42LTEuMTE2LTIuNTY2IGMwLTAuOTY4LDAuMzcyLTEuODIzLDEuMTE2LTIuNTY3bDE1LjI5LTE1LjI5bC0xNS4yOS0xNS4yOWMtMC43NDQtMC43NDQtMS4xMTYtMS42LTEuMTE2LTIuNTY3czAuMzcyLTEuODIzLDEuMTE2LTIuNTY3IEw2NS4yOSw0OC45OTZjMC43NDQtMC43NDQsMS42LTEuMTE2LDIuNTY3LTEuMTE2czEuODIzLDAuMzcyLDIuNTY3LDEuMTE2bDE1LjI5LDE1LjI5bDE1LjI5LTE1LjI5IGMwLjc0NC0wLjc0NCwxLjYtMS4xMTYsMi41NjctMS4xMTZjMC45NjcsMCwxLjgyMiwwLjM3MiwyLjU2NiwxLjExNmwxNi4yOTUsMTYuMjk0YzAuNzQ0LDAuNzQ0LDEuMTE2LDEuNiwxLjExNiwyLjU2NyBzLTAuMzcyLDEuODIzLTEuMTE2LDIuNTY3bC0xNS4yOSwxNS4yOWwxNS4yOSwxNS4yOWMwLjc0NCwwLjc0NCwxLjExNiwxLjYsMS4xMTYsMi41NjcgQzEyMy41NDksMTA0LjUzOSwxMjMuMTc3LDEwNS4zOTQsMTIyLjQzMywxMDYuMTM4eiBNMTQ2LjQyOSw4NS43MTRjMC0xMS4wMTItMi43MTctMjEuMTY4LTguMTQ4LTMwLjQ2OSBzLTEyLjc5Ny0xNi42NjctMjIuMDk4LTIyLjA5OFM5Ni43MjYsMjUsODUuNzE0LDI1cy0yMS4xNjgsMi43MTYtMzAuNDY5LDguMTQ3UzM4LjU3OSw0NS45NDUsMzMuMTQ3LDU1LjI0NlMyNSw3NC43MDMsMjUsODUuNzE0IHMyLjcxNiwyMS4xNjgsOC4xNDcsMzAuNDY5czEyLjc5NywxNi42NjYsMjIuMDk4LDIyLjA5OHMxOS40NTcsOC4xNDgsMzAuNDY5LDguMTQ4czIxLjE2OC0yLjcxNywzMC40NjktOC4xNDggczE2LjY2Ni0xMi43OTcsMjIuMDk4LTIyLjA5OFMxNDYuNDI5LDk2LjcyNiwxNDYuNDI5LDg1LjcxNHogTTE3MS40MjksODUuNzE0YzAsMTUuNTUxLTMuODMyLDI5Ljg5My0xMS40OTYsNDMuMDI0IGMtNy42NjQsMTMuMTMzLTE4LjA2MiwyMy41My0zMS4xOTQsMzEuMTk0Yy0xMy4xMzIsNy42NjQtMjcuNDc0LDExLjQ5Ni00My4wMjQsMTEuNDk2cy0yOS44OTItMy44MzItNDMuMDI0LTExLjQ5NiBjLTEzLjEzMy03LjY2NC0yMy41MzEtMTguMDYyLTMxLjE5NC0zMS4xOTRDMy44MzIsMTE1LjYwNywwLDEwMS4yNjUsMCw4NS43MTRTMy44MzIsNTUuODIyLDExLjQ5Niw0Mi42OSBjNy42NjQtMTMuMTMzLDE4LjA2Mi0yMy41MzEsMzEuMTk0LTMxLjE5NEM1NS44MjIsMy44MzIsNzAuMTY0LDAsODUuNzE0LDBzMjkuODkzLDMuODMyLDQzLjAyNCwxMS40OTYgYzEzLjEzMyw3LjY2NCwyMy41MywxOC4wNjIsMzEuMTk0LDMxLjE5NEMxNjcuNTk3LDU1LjgyMiwxNzEuNDI5LDcwLjE2NCwxNzEuNDI5LDg1LjcxNHoiLz48L3N2Zz4=) +} diff --git a/client/main.js b/client/main.js index b677fc2b80..8ce8aedc49 100644 --- a/client/main.js +++ b/client/main.js @@ -259,4 +259,13 @@ $(document).ready(function() { window.ga('send', 'event', 'FB_LINK', 'SHARE', 'Facebook map share'); window.location.href = link; }); + + // map + $('#nav-map-btn').on('click', () => { + $('.map-aside').removeClass('is-collapsed'); + }); + + $('.map-aside-action-collapse').on('click', () => { + $('.map-aside').addClass('is-collapsed'); + }); }); diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 34fc38f5b3..5422711956 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -189,6 +189,15 @@ function getRenderData$(user, challenge$, origChallengeName, solution) { }); } +function getCompletedChallengeIds(user = {}) { + // if user + // get the id's of all the users completed challenges + return !user.completedChallenges ? + [] : + _.uniq(user.completedChallenges) + .map(({ id, _id }) => id || _id); +} + // create a stream of an array of all the challenge blocks function getSuperBlocks$(challenge$, completedChallenges) { return challenge$ @@ -406,8 +415,19 @@ module.exports = function(app) { function showChallenge(req, res, next) { const solution = req.query.solution; + const completedChallenges = getCompletedChallengeIds(req.user); - getRenderData$(req.user, challenge$, req.params.challengeName, solution) + Observable.combineLatest( + getRenderData$(req.user, challenge$, req.params.challengeName, solution), + getSuperBlocks$(challenge$, completedChallenges), + ({ data, ...rest }, superBlocks) => ({ + ...rest, + data: { + ...data, + superBlocks + } + }) + ) .subscribe( ({ type, redirectUrl, message, data }) => { if (message) { @@ -553,14 +573,9 @@ module.exports = function(app) { ); } - function showMap({ user = {} }, res, next) { - // if user - // get the id's of all the users completed challenges - const completedChallenges = !user.completedChallenges ? - [] : - _.uniq(user.completedChallenges).map(({ id, _id }) => id || _id); + function showMap({ user }, res, next) { - getSuperBlocks$(challenge$, completedChallenges) + getSuperBlocks$(challenge$, getCompletedChallengeIds(user)) .subscribe( superBlocks => { res.render('map/show', { diff --git a/server/views/partials/challenge-footer.jade b/server/views/partials/challenge-footer.jade index ed6be20a3f..080b61a409 100644 --- a/server/views/partials/challenge-footer.jade +++ b/server/views/partials/challenge-footer.jade @@ -5,3 +5,47 @@ script. if (typeof localStorage !== 'undefined') { localStorage.setItem('currentDashedName', typeof common !== 'undefined' && common.dashedName || ''); } +aside.map-aside.is-collapsed + .map-aside-action-bar + a.map-aside-action-item.map-aside-action-pop-out(href='/map' target='_blank' aria-label='open map in new tap') + button.map-aside-action-item.map-aside-action-collapse(aria-label='close map aside') + .map-aside-container + .col-xs-10.col-xs-offset-1 + for superBlock, index in superBlocks + for challengeBlock in superBlock.blocks + h4.bold #{challengeBlock.name} (#{challengeBlock.time}) + for challenge in challengeBlock.challenges + if challenge.completed + p.text-primary.ion-checkmark-circled.padded-ionic-icon.negative-15(name="#{challenge.dashedName}")   + a(href="/challenges/#{challenge.dashedName}") + = challenge.title + span.sr-only= " Complete" + else + if challenge.type === "bonfire" + p.ion-asterisk.padded-ionic-icon.negative-15(name="#{challenge.dashedName}")   + a(name="#{challenge.dashedName}" href="/challenges/#{challenge.dashedName}" class=challenge.isComingSoon ? 'disabled' : '') + span= challenge.title + span.sr-only= " Incomplete" + if challenge.markNew + span.text-success.small     + strong + em New + if challengeBlock.isComingSoon + span.text-success.small     + strong + em Coming Soon + else + p.ion-ios-circle-outline.padded-ionic-icon.negative-15(name="#{challenge.dashedName}")   + a(name="#{challenge.dashedName}" href="/challenges/#{challenge.dashedName}" class=challenge.isComingSoon ? 'disabled' : '') + span= challenge.title + span.sr-only= " Incomplete" + if challenge.markNew + span.text-success.small     + strong + em New + if challengeBlock.isComingSoon + span.text-success.small     + strong + em Coming Soon + if (index < superBlocks.length) + hr diff --git a/server/views/partials/navbar.jade b/server/views/partials/navbar.jade index a248fcdf2f..ae73e565c2 100644 --- a/server/views/partials/navbar.jade +++ b/server/views/partials/navbar.jade @@ -9,7 +9,9 @@ nav.navbar.navbar-default.navbar-fixed-top.nav-height ul.nav.navbar-nav.navbar-right.hamburger-dropdown li a.learn-btn(href='#') Learn - li + li.hidden-xs + a#nav-map-btn(href='#' onclick='return false') Map + li.visible-xs a(href='/map') Map li.hidden-xs a#nav-chat-btn(href='#' onclick="return false") Chat