mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-13 01:01:34 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
bc0b3d0c29
10 changed files with 297 additions and 27 deletions
|
@ -49,6 +49,10 @@ toHex = (n) ->
|
||||||
h = '0'+h if h.length is 1
|
h = '0'+h if h.length is 1
|
||||||
h
|
h
|
||||||
|
|
||||||
|
module.exports.pathToUrl = (path) ->
|
||||||
|
base = location.protocol + '//' + location.hostname + (location.port && ":" + location.port)
|
||||||
|
base + path
|
||||||
|
|
||||||
module.exports.i18n = (say, target, language=me.get('preferredLanguage', true), fallback='en') ->
|
module.exports.i18n = (say, target, language=me.get('preferredLanguage', true), fallback='en') ->
|
||||||
generalResult = null
|
generalResult = null
|
||||||
fallBackResult = null
|
fallBackResult = null
|
||||||
|
@ -184,6 +188,10 @@ if document?.createElement
|
||||||
return
|
return
|
||||||
)(document)
|
)(document)
|
||||||
|
|
||||||
|
# So that we can stub out userAgent in tests
|
||||||
|
module.exports.userAgent = ->
|
||||||
|
window.navigator.userAgent
|
||||||
|
|
||||||
module.exports.getQueryVariable = getQueryVariable = (param, defaultValue) ->
|
module.exports.getQueryVariable = getQueryVariable = (param, defaultValue) ->
|
||||||
query = document.location.search.substring 1
|
query = document.location.search.substring 1
|
||||||
pairs = (pair.split('=') for pair in query.split '&')
|
pairs = (pair.split('=') for pair in query.split '&')
|
||||||
|
|
|
@ -1394,6 +1394,8 @@
|
||||||
select_your_hero: "Select Your Hero"
|
select_your_hero: "Select Your Hero"
|
||||||
select_your_hero_description: "You can always change your hero by going to your Courses page and clicking \"Select Hero\""
|
select_your_hero_description: "You can always change your hero by going to your Courses page and clicking \"Select Hero\""
|
||||||
select_this_hero: "Select this Hero"
|
select_this_hero: "Select this Hero"
|
||||||
|
current_hero: "Current Hero:"
|
||||||
|
change_hero: "Change Hero"
|
||||||
|
|
||||||
teacher:
|
teacher:
|
||||||
course_solution: "Course Solution"
|
course_solution: "Course Solution"
|
||||||
|
@ -1527,7 +1529,18 @@
|
||||||
|
|
||||||
web_dev:
|
web_dev:
|
||||||
image_gallery_title: "Image Gallery"
|
image_gallery_title: "Image Gallery"
|
||||||
image_gallery_description: "Copy these images into your webpage, or find your own image URLs online."
|
select_an_image: "Select an image you want to use"
|
||||||
|
scroll_down_for_more_images: "(Scroll down for more images)"
|
||||||
|
copy_the_url: "Copy the URL below"
|
||||||
|
copy_the_url_description: "Useful if you want to replace an existing image."
|
||||||
|
copy_the_img_tag: "Copy the <img> tag"
|
||||||
|
copy_the_img_tag_description: "Useful if you want to insert a new image."
|
||||||
|
copy_url: "Copy URL"
|
||||||
|
copy_img: "Copy <img>"
|
||||||
|
how_to_copy_paste: "How to Copy/Paste"
|
||||||
|
copy: "Copy"
|
||||||
|
paste: "Paste"
|
||||||
|
back_to_editing: "Back to Editing"
|
||||||
|
|
||||||
classes:
|
classes:
|
||||||
archmage_title: "Archmage"
|
archmage_title: "Archmage"
|
||||||
|
|
|
@ -97,7 +97,7 @@
|
||||||
justify-content: space-between
|
justify-content: space-between
|
||||||
|
|
||||||
.btn
|
.btn
|
||||||
// Undo .style-flat's .btn ~ .btn margin
|
// Undo .style-flat's .btn + .btn margin
|
||||||
margin: 0
|
margin: 0
|
||||||
|
|
||||||
&.just-one
|
&.just-one
|
||||||
|
|
|
@ -1,11 +1,117 @@
|
||||||
@import "app/styles/mixins"
|
@import "app/styles/mixins"
|
||||||
|
@import "app/styles/style-flat-variables"
|
||||||
|
|
||||||
#image-gallery-modal
|
#image-gallery-modal
|
||||||
|
h3
|
||||||
|
font-size: 20px
|
||||||
|
|
||||||
|
.subtitle
|
||||||
|
font-size: 14px
|
||||||
|
line-height: 14px
|
||||||
|
|
||||||
.modal-dialog
|
.modal-dialog
|
||||||
width: 800px
|
width: 850px
|
||||||
|
height: 550px
|
||||||
li
|
max-height: 100vh
|
||||||
font-size: 12px
|
|
||||||
|
.modal-footer
|
||||||
|
display: none
|
||||||
|
|
||||||
|
.modal-header
|
||||||
|
padding: 0
|
||||||
|
min-height: 0
|
||||||
|
|
||||||
|
.modal-body-content
|
||||||
|
height: 485px
|
||||||
|
|
||||||
.no-select
|
.no-select
|
||||||
@include user-select(none)
|
@include user-select(none)
|
||||||
|
|
||||||
|
.image-list
|
||||||
|
overflow: -moz-scrollbars-vertical
|
||||||
|
overflow-y: scroll
|
||||||
|
margin: 0
|
||||||
|
|
||||||
|
// Force scrollbar visible
|
||||||
|
&::-webkit-scrollbar
|
||||||
|
// -webkit-appearance: none
|
||||||
|
border: thin solid gray
|
||||||
|
width: 14px
|
||||||
|
&::-webkit-scrollbar-thumb
|
||||||
|
background-color: rgba(0,0,0,.5)
|
||||||
|
|
||||||
|
.flex-col
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
height: 100%
|
||||||
|
|
||||||
|
.image-list
|
||||||
|
height: 440px
|
||||||
|
max-height: 100vh
|
||||||
|
padding: 0
|
||||||
|
display: flex
|
||||||
|
flex-wrap: wrap
|
||||||
|
|
||||||
|
.image-list-item
|
||||||
|
img
|
||||||
|
width: 72px
|
||||||
|
height: 72px
|
||||||
|
margin: 16px
|
||||||
|
list-style-type: none
|
||||||
|
background-color: #f8f8f8
|
||||||
|
box-shadow: 0 0 0 1px gray
|
||||||
|
|
||||||
|
&.selected
|
||||||
|
box-shadow: 0 0 0 6px $gold
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
.copy-row
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
|
||||||
|
.copy-textarea-col
|
||||||
|
flex-grow: 1
|
||||||
|
|
||||||
|
textarea
|
||||||
|
width: 100%
|
||||||
|
height: 55px
|
||||||
|
|
||||||
|
.copy-button-col
|
||||||
|
padding-left: 10px
|
||||||
|
|
||||||
|
.copyable
|
||||||
|
font-size: 10px
|
||||||
|
line-height: 12px
|
||||||
|
|
||||||
|
.how-to-copy-paste
|
||||||
|
font-size: 13px
|
||||||
|
line-height: 16px
|
||||||
|
font-style: italic
|
||||||
|
color: gray
|
||||||
|
|
||||||
|
.close-button
|
||||||
|
flex-grow: 1
|
||||||
|
align-self: flex-end
|
||||||
|
display: flex
|
||||||
|
align-items: flex-end
|
||||||
|
|
||||||
|
// Fancy text inside horizontal rules
|
||||||
|
|
||||||
|
.hr-text
|
||||||
|
position: relative
|
||||||
|
hr
|
||||||
|
width: 50%
|
||||||
|
padding: 0
|
||||||
|
border: none
|
||||||
|
border-top: thin solid #444
|
||||||
|
color: #444
|
||||||
|
span
|
||||||
|
position: absolute
|
||||||
|
left: 50%
|
||||||
|
top: 0.45em
|
||||||
|
transform: translateX(-50%)
|
||||||
|
padding: 0 0.75em
|
||||||
|
font-weight: bold
|
||||||
|
font-size: 10pt
|
||||||
|
background: white
|
||||||
|
|
|
@ -212,7 +212,7 @@ body[lang='ru'], body[lang='uk'], body[lang='bg'], body[lang^='mk'], body[lang='
|
||||||
.disabled
|
.disabled
|
||||||
opacity: 50%
|
opacity: 50%
|
||||||
|
|
||||||
.btn ~ .btn
|
.btn + .btn
|
||||||
margin-left: 12px
|
margin-left: 12px
|
||||||
|
|
||||||
.btn-primary, .btn-navy
|
.btn-primary, .btn-navy
|
||||||
|
|
|
@ -45,12 +45,10 @@ block content
|
||||||
img(src=view.hero.getPortraitURL())
|
img(src=view.hero.getPortraitURL())
|
||||||
.current-hero-right-col
|
.current-hero-right-col
|
||||||
.semibold.current-hero-text
|
.semibold.current-hero-text
|
||||||
span.spr(data-i18n="TODO")
|
span.spr(data-i18n="courses.current_hero")
|
||||||
| Current Hero:
|
|
||||||
span.current-hero-name= view.hero.getHeroShortName()
|
span.current-hero-name= view.hero.getHeroShortName()
|
||||||
button.change-hero-btn.btn.btn-lg.btn-forest
|
button.change-hero-btn.btn.btn-lg.btn-forest
|
||||||
span(data-i18n="TODO")
|
span(data-i18n="courses.change_hero")
|
||||||
| Change Hero
|
|
||||||
|
|
||||||
if view.classrooms.size()
|
if view.classrooms.size()
|
||||||
h3.text-uppercase(data-i18n="courses.my_classes")
|
h3.text-uppercase(data-i18n="courses.my_classes")
|
||||||
|
|
|
@ -1,23 +1,67 @@
|
||||||
extends /templates/core/modal-base-flat
|
extends /templates/core/modal-base-flat
|
||||||
|
|
||||||
block modal-header-content
|
block modal-header-content
|
||||||
h3(data-i18n="web_dev.image_gallery_title")
|
|
||||||
span(data-i18n="web_dev.image_gallery_description")
|
|
||||||
|
|
||||||
block modal-body-content
|
block modal-body-content
|
||||||
dl.dl-horizontal
|
.row.modal-body-content
|
||||||
for image in view.images
|
div.image-list-container.col-sm-7
|
||||||
dt
|
h3
|
||||||
img(src=image.portraitURL)
|
='1. '
|
||||||
dd
|
span(data-i18n="web_dev.select_an_image")
|
||||||
ul.list-unstyled
|
ul.image-list
|
||||||
li
|
for image in view.images
|
||||||
span.no-select= 'URL: '
|
- var selectedState = state.get('selectedUrl') === image.portraitURL ? 'selected' : ''
|
||||||
kbd= image.portraitURL
|
li.image-list-item.render(data-portrait-url=image.portraitURL, class=selectedState, selected=selectedState)
|
||||||
|
img(src=image.portraitURL)
|
||||||
|
.small.text-center
|
||||||
|
span(data-i18n="web_dev.scroll_down_for_more_images")
|
||||||
|
|
||||||
|
div.col-sm-5.flex-col.render
|
||||||
|
h3
|
||||||
|
='2. '
|
||||||
|
span(data-i18n="web_dev.copy_the_url")
|
||||||
|
.text-h3.subtitle(data-i18n="web_dev.copy_the_url_description")
|
||||||
|
.copy-row.m-t-1
|
||||||
|
.copy-textarea-col
|
||||||
|
textarea.image-url.copyable
|
||||||
|
if view.state.get('selectedUrl')
|
||||||
|
= utils.pathToUrl(view.state.get('selectedUrl'))
|
||||||
|
.copy-button-col
|
||||||
|
button.btn.btn-forest.copy-url-button
|
||||||
|
span(data-i18n="web_dev.copy_url")
|
||||||
|
|
||||||
|
.hr-text.m-t-1
|
||||||
|
hr
|
||||||
|
span(data-i18n="general.or")
|
||||||
|
|
||||||
|
h3(data-i18n="web_dev.copy_the_img_tag")
|
||||||
|
.text-h3.subtitle(data-i18n="web_dev.copy_the_img_tag_description")
|
||||||
|
.copy-row.m-t-1
|
||||||
|
.copy-textarea-col
|
||||||
|
textarea.image-tag.copyable
|
||||||
|
if view.state.get('selectedUrl')
|
||||||
|
= '<img src="' + utils.pathToUrl(view.state.get('selectedUrl')) + '"/>'
|
||||||
|
.copy-button-col
|
||||||
|
button.btn.btn-forest.copy-tag-button
|
||||||
|
span(data-i18n="web_dev.copy_img")
|
||||||
|
|
||||||
|
.how-to-copy-paste.m-t-3
|
||||||
|
div.m-b-1.text-center
|
||||||
|
span(data-i18n="web_dev.how_to_copy_paste")
|
||||||
|
.windows-only
|
||||||
|
span(data-i18n="web_dev.copy")
|
||||||
|
| : Control–C
|
||||||
br
|
br
|
||||||
li
|
span(data-i18n="web_dev.paste")
|
||||||
span.no-select= '<img>: '
|
| : Control–V
|
||||||
kbd= '<img src="' + image.portraitURL + '">'
|
.mac-only.hidden
|
||||||
|
span(data-i18n="web_dev.copy")
|
||||||
|
| : Command ⌘–C
|
||||||
|
br
|
||||||
|
span(data-i18n="web_dev.paste")
|
||||||
|
| : Command ⌘–V
|
||||||
|
|
||||||
|
.close-button
|
||||||
|
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="web_dev.back_to_editing").btn.btn-lg.btn-primary
|
||||||
|
|
||||||
block modal-footer-content
|
block modal-footer-content
|
||||||
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="modal.close").btn.btn-primary Close
|
|
||||||
|
|
|
@ -1,8 +1,42 @@
|
||||||
ModalView = require 'views/core/ModalView'
|
ModalView = require 'views/core/ModalView'
|
||||||
|
State = require 'models/State'
|
||||||
|
utils = require 'core/utils'
|
||||||
|
|
||||||
module.exports = class ImageGalleryModal extends ModalView
|
module.exports = class ImageGalleryModal extends ModalView
|
||||||
id: 'image-gallery-modal'
|
id: 'image-gallery-modal'
|
||||||
template: require 'templates/play/level/modal/image-gallery-modal'
|
template: require 'templates/play/level/modal/image-gallery-modal'
|
||||||
|
|
||||||
|
events:
|
||||||
|
'click .image-list-item': 'onClickImageListItem'
|
||||||
|
'click .copy-url-button': 'onClickCopyUrlButton'
|
||||||
|
'click .copy-tag-button': 'onClickCopyTagButton'
|
||||||
|
|
||||||
|
getRenderData: ->
|
||||||
|
_.merge super(arguments...), { utils }
|
||||||
|
|
||||||
|
initialize: ->
|
||||||
|
@state = new State()
|
||||||
|
@listenTo @state, 'all', =>
|
||||||
|
@renderSelectors('.render')
|
||||||
|
@afterRender()
|
||||||
|
|
||||||
|
afterRender: ->
|
||||||
|
if utils.userAgent().indexOf("Mac") > -1
|
||||||
|
@$('.windows-only').addClass('hidden')
|
||||||
|
@$('.mac-only').removeClass('hidden')
|
||||||
|
|
||||||
|
onClickImageListItem: (e) ->
|
||||||
|
selectedUrl = $(e.currentTarget).data('portrait-url')
|
||||||
|
@state.set { selectedUrl }
|
||||||
|
|
||||||
|
onClickCopyUrlButton: (e) ->
|
||||||
|
$('.image-url').select()
|
||||||
|
@tryCopy()
|
||||||
|
|
||||||
|
onClickCopyTagButton: (e) ->
|
||||||
|
$('.image-tag').select()
|
||||||
|
@tryCopy()
|
||||||
|
|
||||||
# Top most useful Thang portraits
|
# Top most useful Thang portraits
|
||||||
images: [
|
images: [
|
||||||
{slug: 'archer-f', name: 'Archer F', original: '529ab1a24b67a988ad000002', portraitURL: '/file/db/thang.type/529ab1a24b67a988ad000002/portrait.png', kind: 'Unit'}
|
{slug: 'archer-f', name: 'Archer F', original: '529ab1a24b67a988ad000002', portraitURL: '/file/db/thang.type/529ab1a24b67a988ad000002/portrait.png', kind: 'Unit'}
|
||||||
|
|
|
@ -431,7 +431,6 @@ describe 'POST /db/course_instance/-/recent', ->
|
||||||
startDay = utils.createDay(1)
|
startDay = utils.createDay(1)
|
||||||
endDay = utils.createDay(2)
|
endDay = utils.createDay(2)
|
||||||
[res, body] = yield request.postAsync(url, { json: { startDay, endDay } })
|
[res, body] = yield request.postAsync(url, { json: { startDay, endDay } })
|
||||||
console.log startDay, endDay, res.body.courseInstances.length
|
|
||||||
expect(res.body.courseInstances.length).toBe(0)
|
expect(res.body.courseInstances.length).toBe(0)
|
||||||
|
|
||||||
startDay = utils.createDay(-2)
|
startDay = utils.createDay(-2)
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
Course = require 'models/Course'
|
||||||
|
Level = require 'models/Level'
|
||||||
|
LevelSession = require 'models/LevelSession'
|
||||||
|
ImageGalleryModal = require 'views/play/level/modal/ImageGalleryModal'
|
||||||
|
ProgressView = require 'views/play/level/modal/ProgressView'
|
||||||
|
factories = require 'test/app/factories'
|
||||||
|
utils = require 'core/utils'
|
||||||
|
|
||||||
|
describe 'ImageGalleryModal', ->
|
||||||
|
modal = null
|
||||||
|
|
||||||
|
beforeEach (done) ->
|
||||||
|
modal = new ImageGalleryModal()
|
||||||
|
modal.render()
|
||||||
|
_.defer done
|
||||||
|
|
||||||
|
it '(demo)', ->
|
||||||
|
jasmine.demoModal(modal)
|
||||||
|
|
||||||
|
it 'shows a list of images', ->
|
||||||
|
expect(modal.$('img').length).toBeGreaterThan(16)
|
||||||
|
|
||||||
|
describe 'clicking an image', ->
|
||||||
|
beforeEach (done) ->
|
||||||
|
@clickedImage = modal.$('li:nth-child(5)').click()
|
||||||
|
@clickedImagePath = @clickedImage.data('portrait-url')
|
||||||
|
@clickedImageUrl = utils.pathToUrl(@clickedImagePath)
|
||||||
|
@clickedImageTag = '<img src="' + @clickedImageUrl + '"/>'
|
||||||
|
_.defer done
|
||||||
|
|
||||||
|
it 'highlights the chosen image', ->
|
||||||
|
expect(modal.$('li:nth-child(5)').hasClass('selected')).toBe(true)
|
||||||
|
|
||||||
|
it 'displays the URL/image tags in the Copy section', ->
|
||||||
|
expect(modal.$('.image-url').text()).toBe(@clickedImageUrl)
|
||||||
|
expect(modal.$('.image-tag').text()).toBe(@clickedImageTag)
|
||||||
|
|
||||||
|
describe "How to Copy/Paste section", ->
|
||||||
|
userAgents = {
|
||||||
|
windows: 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko'
|
||||||
|
mac: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
|
||||||
|
}
|
||||||
|
|
||||||
|
it 'Shows Windows shortcuts to Windows users', (done) ->
|
||||||
|
spyOn(utils, 'userAgent').and.callFake ->
|
||||||
|
userAgents.windows
|
||||||
|
modal.render()
|
||||||
|
# This test is a little fragile — Only works if the text node is an immediate child to .windows-only
|
||||||
|
expect(modal.$('.how-to-copy-paste :not(.hidden)').text()).toMatch(/Control|Ctrl/i)
|
||||||
|
expect(modal.$('.how-to-copy-paste :not(.hidden)').text()).not.toMatch(/Command|Cmd/i)
|
||||||
|
@clickedImage = modal.$('li:nth-child(5)').click()
|
||||||
|
_.defer ->
|
||||||
|
expect(modal.$('.how-to-copy-paste :not(.hidden)').text()).toMatch(/Control|Ctrl/i)
|
||||||
|
expect(modal.$('.how-to-copy-paste :not(.hidden)').text()).not.toMatch(/Command|Cmd/i)
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'Shows Mac shortcuts to Mac users', (done) ->
|
||||||
|
spyOn(utils, 'userAgent').and.callFake ->
|
||||||
|
userAgents.mac
|
||||||
|
modal.render()
|
||||||
|
# This test is a little fragile — Only works if the text node is an immediate child to .mac-only
|
||||||
|
expect(modal.$('.how-to-copy-paste :not(.hidden)').text()).toMatch(/Command|Cmd/i)
|
||||||
|
expect(modal.$('.how-to-copy-paste :not(.hidden)').text()).not.toMatch(/Control|Ctrl/i)
|
||||||
|
@clickedImage = modal.$('li:nth-child(5)').click()
|
||||||
|
_.defer ->
|
||||||
|
expect(modal.$('.how-to-copy-paste :not(.hidden)').text()).toMatch(/Command|Cmd/i)
|
||||||
|
expect(modal.$('.how-to-copy-paste :not(.hidden)').text()).not.toMatch(/Control|Ctrl/i)
|
||||||
|
done()
|
Loading…
Reference in a new issue