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()