mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 00:40:56 -05:00
Improve WebDev level image gallery
Improve image gallery Add How to Copy/Paste section Fix modal close button Add specs for image gallery Fix up i18n Fix render resetting scroll Address code review feedback Ensure afterRender is called
This commit is contained in:
parent
f96c3ab00e
commit
73bbe598da
8 changed files with 293 additions and 22 deletions
|
@ -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 '&')
|
||||
|
|
|
@ -1527,7 +1527,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 <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:
|
||||
archmage_title: "Archmage"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
= '<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
|
||||
li
|
||||
span.no-select= '<img>: '
|
||||
kbd= '<img src="' + image.portraitURL + '">'
|
||||
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
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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