diff --git a/app/core/utils.coffee b/app/core/utils.coffee
index ebeca671c..175bd2537 100644
--- a/app/core/utils.coffee
+++ b/app/core/utils.coffee
@@ -49,6 +49,10 @@ toHex = (n) ->
h = '0'+h if h.length is 1
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') ->
generalResult = null
fallBackResult = null
@@ -184,6 +188,10 @@ if document?.createElement
return
)(document)
+# So that we can stub out userAgent in tests
+module.exports.userAgent = ->
+ window.navigator.userAgent
+
module.exports.getQueryVariable = getQueryVariable = (param, defaultValue) ->
query = document.location.search.substring 1
pairs = (pair.split('=') for pair in query.split '&')
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index f64795613..c23b87ee9 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -1394,6 +1394,8 @@
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_this_hero: "Select this Hero"
+ current_hero: "Current Hero:"
+ change_hero: "Change Hero"
teacher:
course_solution: "Course Solution"
@@ -1527,7 +1529,18 @@
web_dev:
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 tag"
+ copy_the_img_tag_description: "Useful if you want to insert a new image."
+ copy_url: "Copy URL"
+ copy_img: "Copy "
+ how_to_copy_paste: "How to Copy/Paste"
+ copy: "Copy"
+ paste: "Paste"
+ back_to_editing: "Back to Editing"
classes:
archmage_title: "Archmage"
diff --git a/app/styles/modal/create-account-modal/create-account-modal.sass b/app/styles/modal/create-account-modal/create-account-modal.sass
index 373dbbd78..5cd9ca20f 100644
--- a/app/styles/modal/create-account-modal/create-account-modal.sass
+++ b/app/styles/modal/create-account-modal/create-account-modal.sass
@@ -97,7 +97,7 @@
justify-content: space-between
.btn
- // Undo .style-flat's .btn ~ .btn margin
+ // Undo .style-flat's .btn + .btn margin
margin: 0
&.just-one
diff --git a/app/styles/play/modal/image-gallery-modal.sass b/app/styles/play/modal/image-gallery-modal.sass
index caf5d0eef..588d28136 100644
--- a/app/styles/play/modal/image-gallery-modal.sass
+++ b/app/styles/play/modal/image-gallery-modal.sass
@@ -1,11 +1,117 @@
@import "app/styles/mixins"
+@import "app/styles/style-flat-variables"
#image-gallery-modal
+ h3
+ font-size: 20px
+
+ .subtitle
+ font-size: 14px
+ line-height: 14px
+
.modal-dialog
- width: 800px
-
- li
- font-size: 12px
+ width: 850px
+ height: 550px
+ max-height: 100vh
+
+ .modal-footer
+ display: none
+
+ .modal-header
+ padding: 0
+ min-height: 0
+
+ .modal-body-content
+ height: 485px
.no-select
@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
diff --git a/app/styles/style-flat.sass b/app/styles/style-flat.sass
index 050599954..33638e78f 100644
--- a/app/styles/style-flat.sass
+++ b/app/styles/style-flat.sass
@@ -212,7 +212,7 @@ body[lang='ru'], body[lang='uk'], body[lang='bg'], body[lang^='mk'], body[lang='
.disabled
opacity: 50%
- .btn ~ .btn
+ .btn + .btn
margin-left: 12px
.btn-primary, .btn-navy
diff --git a/app/templates/courses/courses-view.jade b/app/templates/courses/courses-view.jade
index 19597c3c1..b98c4361f 100644
--- a/app/templates/courses/courses-view.jade
+++ b/app/templates/courses/courses-view.jade
@@ -45,12 +45,10 @@ block content
img(src=view.hero.getPortraitURL())
.current-hero-right-col
.semibold.current-hero-text
- span.spr(data-i18n="TODO")
- | Current Hero:
+ span.spr(data-i18n="courses.current_hero")
span.current-hero-name= view.hero.getHeroShortName()
button.change-hero-btn.btn.btn-lg.btn-forest
- span(data-i18n="TODO")
- | Change Hero
+ span(data-i18n="courses.change_hero")
if view.classrooms.size()
h3.text-uppercase(data-i18n="courses.my_classes")
diff --git a/app/templates/play/level/modal/image-gallery-modal.jade b/app/templates/play/level/modal/image-gallery-modal.jade
index 7899ea8c2..8c830eced 100644
--- a/app/templates/play/level/modal/image-gallery-modal.jade
+++ b/app/templates/play/level/modal/image-gallery-modal.jade
@@ -1,23 +1,67 @@
extends /templates/core/modal-base-flat
block modal-header-content
- h3(data-i18n="web_dev.image_gallery_title")
- span(data-i18n="web_dev.image_gallery_description")
block modal-body-content
- dl.dl-horizontal
- for image in view.images
- dt
- img(src=image.portraitURL)
- dd
- ul.list-unstyled
- li
- span.no-select= 'URL: '
- kbd= image.portraitURL
+ .row.modal-body-content
+ div.image-list-container.col-sm-7
+ h3
+ ='1. '
+ span(data-i18n="web_dev.select_an_image")
+ ul.image-list
+ for image in view.images
+ - var selectedState = state.get('selectedUrl') === image.portraitURL ? 'selected' : ''
+ 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')
+ = ''
+ .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
- li
- span.no-select= ': '
- kbd= ''
+ span(data-i18n="web_dev.paste")
+ | : Control–V
+ .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
- a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="modal.close").btn.btn-primary Close
diff --git a/app/views/play/level/modal/ImageGalleryModal.coffee b/app/views/play/level/modal/ImageGalleryModal.coffee
index 50a300b59..d37133413 100644
--- a/app/views/play/level/modal/ImageGalleryModal.coffee
+++ b/app/views/play/level/modal/ImageGalleryModal.coffee
@@ -1,8 +1,42 @@
ModalView = require 'views/core/ModalView'
+State = require 'models/State'
+utils = require 'core/utils'
module.exports = class ImageGalleryModal extends ModalView
id: '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
images: [
{slug: 'archer-f', name: 'Archer F', original: '529ab1a24b67a988ad000002', portraitURL: '/file/db/thang.type/529ab1a24b67a988ad000002/portrait.png', kind: 'Unit'}
diff --git a/spec/server/functional/course_instance.spec.coffee b/spec/server/functional/course_instance.spec.coffee
index e8aef634e..27c1dfc18 100644
--- a/spec/server/functional/course_instance.spec.coffee
+++ b/spec/server/functional/course_instance.spec.coffee
@@ -431,7 +431,6 @@ describe 'POST /db/course_instance/-/recent', ->
startDay = utils.createDay(1)
endDay = utils.createDay(2)
[res, body] = yield request.postAsync(url, { json: { startDay, endDay } })
- console.log startDay, endDay, res.body.courseInstances.length
expect(res.body.courseInstances.length).toBe(0)
startDay = utils.createDay(-2)
diff --git a/test/app/views/play/level/modal/ImageGalleryModal.spec.coffee b/test/app/views/play/level/modal/ImageGalleryModal.spec.coffee
new file mode 100644
index 000000000..455ba0fb6
--- /dev/null
+++ b/test/app/views/play/level/modal/ImageGalleryModal.spec.coffee
@@ -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 = ''
+ _.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()