From 14c294213466421d2ced37b7e07690779ea3d0be Mon Sep 17 00:00:00 2001 From: Josh Callebaut Date: Wed, 13 Jan 2016 15:54:57 -0800 Subject: [PATCH 01/28] Moves Tasks to their own tab and adds a guide-book icon next to the save icon which displays help and doubles as a level review submission modal. --- .../level/modal/artisan-guide-modal.sass | 3 + app/styles/editor/level/tasks-tab.sass | 15 ++ app/templates/editor/level/edit.jade | 9 +- .../level/modal/artisan-guide-modal.jade | 36 ++++ app/templates/editor/level/tasks_tab.jade | 40 ++++ app/views/editor/level/LevelEditView.coffee | 13 ++ .../level/modals/ArtisanGuideModal.coffee | 47 +++++ .../level/settings/SettingsTabView.coffee | 2 +- .../editor/level/tasks/TasksTabView.coffee | 176 ++++++++++++++++++ 9 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 app/styles/editor/level/modal/artisan-guide-modal.sass create mode 100644 app/styles/editor/level/tasks-tab.sass create mode 100644 app/templates/editor/level/modal/artisan-guide-modal.jade create mode 100644 app/templates/editor/level/tasks_tab.jade create mode 100644 app/views/editor/level/modals/ArtisanGuideModal.coffee create mode 100644 app/views/editor/level/tasks/TasksTabView.coffee diff --git a/app/styles/editor/level/modal/artisan-guide-modal.sass b/app/styles/editor/level/modal/artisan-guide-modal.sass new file mode 100644 index 000000000..0240381c2 --- /dev/null +++ b/app/styles/editor/level/modal/artisan-guide-modal.sass @@ -0,0 +1,3 @@ +#artisan-guide-modal + .centered-stack + text-align: center \ No newline at end of file diff --git a/app/styles/editor/level/tasks-tab.sass b/app/styles/editor/level/tasks-tab.sass new file mode 100644 index 000000000..0852f47d9 --- /dev/null +++ b/app/styles/editor/level/tasks-tab.sass @@ -0,0 +1,15 @@ +#editor-level-tasks-tab-view + td + cursor: pointer + .taskCheck + width: 5% + .taskCheck * + text-align: center + margin: auto + .checkbox + margin: auto + .startEdit + cursor: pointer + text-align:center + .noArticle + color: rgba(160, 0, 0, 1) \ No newline at end of file diff --git a/app/templates/editor/level/edit.jade b/app/templates/editor/level/edit.jade index a7ec9221a..5496527d0 100644 --- a/app/templates/editor/level/edit.jade +++ b/app/templates/editor/level/edit.jade @@ -27,6 +27,8 @@ block header a(href="#editor-level-components-tab-view", data-toggle="tab", data-i18n="editor.level_tab_components")#components-tab Components li a(href="#systems-tab-view", data-toggle="tab", data-i18n="editor.level_tab_systems") Systems + li + a(href="#editor-level-tasks-tab-view", data-toggle="tab", data-i18n="editor.level_tab_tasks")#tasks-tab= "Tasks" + " " + view.getTaskCompletionRatio() li a(href="#editor-level-patches", data-toggle="tab")#patches-tab span(data-i18n="resources.patches").spr Patches @@ -50,6 +52,9 @@ block header li#redo-button a span.glyphicon-arrow-right.glyphicon + li#artisan-guide-button + a + span.glyphicon-book.glyphicon if authorized li#commit-level-start-button a @@ -58,7 +63,7 @@ block header li#level-patch-button a span.glyphicon-floppy-disk.glyphicon - + if level.get('type') === 'ladder' li.dropdown a(data-toggle='dropdown').play-with-team-parent @@ -124,6 +129,8 @@ block outer_content div.tab-pane#editor-level-components-tab-view div.tab-pane#systems-tab-view + + div.tab-pane#editor-level-tasks-tab-view div.tab-pane#editor-level-patches .patches-view diff --git a/app/templates/editor/level/modal/artisan-guide-modal.jade b/app/templates/editor/level/modal/artisan-guide-modal.jade new file mode 100644 index 000000000..f02540e16 --- /dev/null +++ b/app/templates/editor/level/modal/artisan-guide-modal.jade @@ -0,0 +1,36 @@ +extends /templates/core/modal-base + +block modal-header-content + h3 Artisan Compendium +block modal-body-content + p + | Welcome to the Artisan Compendium. Below you will find a series of helpful guides and tutorials for getting your levels to Master Artisan quality, as well as a way to submit your level for official Artisan review! If you have any feedback on the Level Editor, please contact us at: artisans@codecombat.com + div.centered-stack + div + a(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home', target='_blank') Wiki Homepage + div + a(href='https://github.com/codecombat/codecombat/wiki/Artisan-How-To-Index', target='_blank') Basic How-tos + div + a(href='http://direct.codecombat.com/community', target='_blank') Artisan Rankings + h4 Level Submission + p + | Do you want your level to be added to the campaign? Please take a moment to fill out the questions below! Don’t worry, this isn’t a timed quiz. It is just a way for the artisans at CodeCombat HQ to get a feel for the purpose of this level. If it doesn’t quite yet meet our standards for release we will give you feedback to help polish it further! + .form + .form-group + label.control-label(for='credit-name') How would you like to be credited? + input#credit-name.form-control(name='creditName') + .form-group + label.control-label(for='level-purpose') What is the purpose of this level? + textarea#level-purpose.form-control(name='levelPurpose', rows=4) + .form-group + label.control-label(for='level-inspiration') What was the inspiration for the level? + textarea#level-inspiration.form-control(name='levelInspiration', rows=4) + .form-group + label.control-label(for='level-location') Where in the campaign do you think this level belongs? + textarea#level-location.form-control(name='levelLocation', rows=4) + + +block modal-footer-content + div + a(href='#', data-dismiss="modal", aria-hidden="true").btn Close + button.btn.btn-primary#level-submit Submit \ No newline at end of file diff --git a/app/templates/editor/level/tasks_tab.jade b/app/templates/editor/level/tasks_tab.jade new file mode 100644 index 000000000..ef2298515 --- /dev/null +++ b/app/templates/editor/level/tasks_tab.jade @@ -0,0 +1,40 @@ +mixin taskRow(task) + tr.taskRow(data=task.name) + td.taskCheck + div.checkbox + input(type='checkbox', checked=(task.complete || false), data=task.name, placeholder='Enter a description of the task.').taskInput + if task.curEdit == true + td + td + input(type="input", value=task.name)#curEdit + else + td + span.startEdit(data=task.name) ✎ + td + if view.defaultTaskLinks[task.name] + if view.defaultTaskLinks[task.name] === './' + a.noArticle(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home' target='_blank')= task.name + else + a(href=view.defaultTaskLinks[task.name], target='_blank')= task.name + else + span= task.name +block + if view.tasks + table.table.table-striped.table-hover + tr + th.taskCheck Complete + th Edit + th Incomplete Tasks + for task in view.tTasks + if task.reversion !== true + +taskRow(task) + tr + th + th + th Completed Tasks + for task in view.tTasks + if task.reversion === true + +taskRow(task) + button#createTask.btn.btn-default Add Task + else + div Wrong! \ No newline at end of file diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index 7e53803fa..bddb0dcd7 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -15,7 +15,9 @@ SettingsTabView = require './settings/SettingsTabView' ScriptsTabView = require './scripts/ScriptsTabView' ComponentsTabView = require './components/ComponentsTabView' SystemsTabView = require './systems/SystemsTabView' +TasksTabView = require './tasks/TasksTabView' SaveLevelModal = require './modals/SaveLevelModal' +ArtisanGuideModal = require './modals/ArtisanGuideModal' ForkModal = require 'views/editor/ForkModal' SaveVersionModal = require 'views/editor/modal/SaveVersionModal' PatchesView = require 'views/editor/PatchesView' @@ -56,6 +58,7 @@ module.exports = class LevelEditView extends RootView 'mouseenter #redo-button': 'showRedoDescription' 'click #patches-tab': -> @patchesView.load() 'click #components-tab': -> @subviews.editor_level_components_tab_view.refreshLevelThangsTreema @level.get('thangs') + 'click #artisan-guide-button': 'showArtisanGuide' 'click #level-patch-button': 'startPatchingLevel' 'click #level-watch-button': 'toggleWatchLevel' 'click li:not(.disabled) > #pop-level-i18n-button': 'onPopulateI18N' @@ -90,6 +93,7 @@ module.exports = class LevelEditView extends RootView getRenderData: (context={}) -> context = super(context) context.level = @level + console.log context.level context.authorized = me.isAdmin() or @level.hasWriteAccess(me) context.anonymous = me.get('anonymous') context.recentlyPlayedOpponents = storage.load('recently-played-matches')?[@levelID] ? [] @@ -105,10 +109,12 @@ module.exports = class LevelEditView extends RootView @insertSubView new ScriptsTabView world: @world, supermodel: @supermodel, files: @files @insertSubView new ComponentsTabView supermodel: @supermodel @insertSubView new SystemsTabView supermodel: @supermodel, world: @world + @insertSubView new TasksTabView world: @world, supermodel: @supermodel, level: @level @insertSubView new RelatedAchievementsView supermodel: @supermodel, level: @level @insertSubView new ComponentsDocumentationView lazy: true # Don't give it the supermodel, it'll pollute it! @insertSubView new SystemsDocumentationView lazy: true # Don't give it the supermodel, it'll pollute it! @insertSubView new LevelFeedbackView level: @level + Backbone.Mediator.publish 'editor:level-loaded', level: @level @showReadOnly() if me.get('anonymous') @@ -170,6 +176,10 @@ module.exports = class LevelEditView extends RootView @openModalView new SaveLevelModal level: @level, supermodel: @supermodel, buildTime: @levelBuildTime Backbone.Mediator.publish 'editor:view-switched', {} + showArtisanGuide: (e) -> + @openModalView new ArtisanGuideModal() + Backbone.Mediator.publish 'editor:view-switched', {} + startForking: (e) -> @openModalView new ForkModal model: @level, editorPath: 'level' Backbone.Mediator.publish 'editor:view-switched', {} @@ -210,3 +220,6 @@ module.exports = class LevelEditView extends RootView return if application.userIsIdle @levelBuildTime ?= @level.get('buildTime') ? 0 ++@levelBuildTime + + getTaskCompletionRatio: -> + return _.filter(@level.get('tasks'), (_elem) -> return _elem.complete).length + "/" + @level.get('tasks').length diff --git a/app/views/editor/level/modals/ArtisanGuideModal.coffee b/app/views/editor/level/modals/ArtisanGuideModal.coffee new file mode 100644 index 000000000..c12999892 --- /dev/null +++ b/app/views/editor/level/modals/ArtisanGuideModal.coffee @@ -0,0 +1,47 @@ +ModalView = require 'views/core/ModalView' +template = require 'templates/editor/level/modal/artisan-guide-modal' + +forms = require 'core/forms' +{sendContactMessage} = require 'core/contact' + +contactSchema = + additionalProperties: false + required: ['creditName', 'levelPurpose', 'levelInspiration', 'levelLocation'] + properties: + creditName: + type: 'string' + levelPurpose: + type: 'string' + levelInspiration: + type: 'string' + levelLocation: + type: 'string' + +module.exports = class ArtisanGuideModal extends ModalView + id: 'artisan-guide-modal' + template: template + events: + 'click #level-submit': 'levelSubmit' + + constructor: (options) -> + super options + + + levelSubmit: -> + @playSound 'menu-button-click' + forms.clearFormAlerts @$el + contactMessage = forms.formToObject @$el + res = tv4.validateMultiple contactMessage, contactSchema + return forms.applyErrorsToForm @$el, res.errors unless res.valid + @populateBrowserData contactMessage + contactMessage = _.merge contactMessage, @options + contactMessage.country = me.get('country') + window.tracker?.trackEvent 'Sent Feedback', message: contactMessage + sendContactMessage contactMessage, @$el + $.post "/db/user/#{me.id}/track/contact_codecombat" + + populateBrowserData: (context) -> + if $.browser + context.browser = "#{$.browser.platform} #{$.browser.name} #{$.browser.versionNumber}" + context.screenSize = "#{screen?.width ? $(window).width()} x #{screen?.height ? $(window).height()}" + context.screenshotURL = @screenshotURL diff --git a/app/views/editor/level/settings/SettingsTabView.coffee b/app/views/editor/level/settings/SettingsTabView.coffee index f3a42a20d..e67566937 100644 --- a/app/views/editor/level/settings/SettingsTabView.coffee +++ b/app/views/editor/level/settings/SettingsTabView.coffee @@ -15,7 +15,7 @@ module.exports = class SettingsTabView extends CocoView editableSettings: [ 'name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon', 'goals', 'type', 'terrain', 'showsGuide', 'banner', 'employerDescription', 'loadingTip', 'requiresSubscription', - 'tasks', 'helpVideos', 'replayable', 'scoreTypes', 'concepts' + 'helpVideos', 'replayable', 'scoreTypes', 'concepts' ] subscriptions: diff --git a/app/views/editor/level/tasks/TasksTabView.coffee b/app/views/editor/level/tasks/TasksTabView.coffee new file mode 100644 index 000000000..a2e385be3 --- /dev/null +++ b/app/views/editor/level/tasks/TasksTabView.coffee @@ -0,0 +1,176 @@ +CocoView = require 'views/core/CocoView' +template = require 'templates/editor/level/tasks_tab' +Level = require 'models/Level' + +module.exports = class TasksTabView extends CocoView + id: 'editor-level-tasks-tab-view' + className: 'tab-pane' + template: template + events: + 'click .taskRow': 'onTaskRowClicked' + 'click .taskInput': 'onTaskCompletionClicked' + 'click .startEdit': 'onTaskEditClicked' + 'click #createTask': 'onTaskCreateClicked' + 'keydown #curEdit': 'onCurEditKeyDown' + + subscriptions: + 'editor:level-loaded': 'onLevelLoaded' + + defaultTaskLinks: + # Order doesn't matter. + 'Name the level.':'./' + 'Create a Referee stub, if needed.':'./' + 'Build the level.':'./' + 'Set up goals.':'./' + 'Choose the Existence System lifespan and frame rate.':'./' + 'Choose the UI System paths and coordinate hover if needed.':'./' + 'Choose the AI System pathfinding and Vision System line of sight.':'./' + 'Write the sample code.':'./' + 'Do basic set decoration.':'./' + 'Adjust script camera bounds.':'./' + 'Choose music file in Introduction script.':'./' + 'Choose autoplay in Introduction script.':'./' + 'Add to a campaign.':'./' + 'Publish.':'./' + 'Choose level options like required/restricted gear.':'./' + 'Create achievements, including unlocking next level.':'./' + 'Choose leaderboard score types.':'./' + 'Playtest with a slow/tough hero.':'./' + 'Playtest with a fast/weak hero.':'./' + 'Playtest with a couple random seeds.':'./' + 'Make sure the level ends promptly on success and failure.':'./' + 'Remove/simplify unnecessary doodad collision.':'./' + 'Release to adventurers via MailChimp.':'./' + 'Write the description.':'./' + 'Add i18n field for the sample code comments.':'./' + 'Add Clojure/Lua/CoffeeScript.':'./' + 'Write the guide.':'./' + 'Write a loading tip, if needed.':'./' + 'Click the Populate i18n button.':'./' + 'Add programming concepts covered.':'./' + 'Mark whether it requires a subscription.':'./' + 'Release to everyone via MailChimp.':'./' + 'Check completion/engagement/problem analytics.':'./' + 'Do thorough set decoration.':'./' + 'Add a walkthrough video.':'./' + + constructor: (options) -> + super options + @render() + + onLoaded: -> + onLevelLoaded: (e) -> + @level = e.level + if e.level._revertAttributes + @revertTasks = e.level._revertAttributes.tasks + else + @revertTasks = @level.get 'tasks' + @tasks = @level.get 'tasks' + @tTasks = _.clone @tasks, true + for task in @tTasks + if @revertTasks[_.findKey(@revertTasks, {'name':task.name})] + task.reversion = @revertTasks[_.findKey(@revertTasks, {'name':task.name})].complete || null + else + task.reversion = false + @render() + + getRenderData: -> + c = super() + c.tasks = @tasks + c.status + c + + pushTasks: -> + for task in @tTasks + taskKey = @findTaskByName(@tasks, task.name) + oTaskKey = @findTaskByName(@tasks, task.oldName) + if taskKey? + @tasks[taskKey].complete = task.complete + else if oTaskKey? + if task.name is '' + @tasks.splice(oTaskKey, 1) + @tTasks.splice(@tTasks.indexOf(task), 1) + break + @pushTasks() + else + @tasks[oTaskKey].name = task.name + @tasks[oTaskKey].complete = task.complete + else + if task.name is '' + @tasks.splice(oTaskKey, 1) + @tTasks.splice(@tTasks.indexOf(task), 1) + else + @tasks.push + name: task.name + complete: task.complete + @level.set 'tasks', @tasks + @parent.renderSelectors '#tasks-tab' + + onTaskRowClicked: (e) -> + if not $(e.target).is('input') and not $(e.target).is('a') and not $(e.target).hasClass('startEdit') + checkBox = $(e.currentTarget).find('.taskInput')[0] + tTaskKey = @findTaskByName(@tTasks, @getData e) + if tTaskKey? + if checkBox.checked + checkBox.checked = false + else + checkBox.checked = true + console.log(checkBox.checked) + @tTasks[tTaskKey].complete = checkBox.checked + @pushTasks() + + onTaskCompletionClicked: (e) -> + tTaskKey = @findTaskByName(@tTasks, @getData e) + if tTaskKey? + @tTasks[tTaskKey].complete = e.currentTarget.checked + @pushTasks() + + onTaskCreateClicked: (e) -> + if $('#curEdit').length is 0 + @tTasks.push + name: '' + complete: false + reversion: false + curEdit: true + @render() + editDiv = $('#curEdit')[0] + editDiv.focus() + len = editDiv.value.length * 2 + editDiv.setSelectionRange len, len + + onCurEditKeyDown: (e) -> + editDiv = $('#curEdit')[0] + if e.keyCode is 13 + taskIndex = @findTaskByName(@tasks, editDiv.value) + tTaskIndex = _.findKey(@tTasks, {'curEdit':true}) + if taskIndex? and tTaskIndex? and taskIndex isnt tTaskIndex + noty + timeout: 5000 + text: 'Task with name already exists.' + type: 'error' + layout: 'topCenter' + else + @tTasks[tTaskIndex].oldName = @tTasks[tTaskIndex].name + @tTasks[tTaskIndex].name = curEdit.value + @tTasks[tTaskIndex].curEdit = false + + @pushTasks() + @render() + + onTaskEditClicked: (e) -> + if $('#curEdit').length is 0 + taskIndex = @findTaskByName(@tTasks, @getData e) + @tTasks[taskIndex].curEdit = true + @render() + editDiv = $('#curEdit')[0] + editDiv.focus() + len = editDiv.value.length * 2 + editDiv.setSelectionRange len, len + + findTaskByName: (obj, name) -> + return _.findKey(obj, {'name':name}) + + getData: (elem) -> + return elem.currentTarget.getAttribute('data') + + \ No newline at end of file From 89fc6cc078ad34e723eef732abdb2e12e348947b Mon Sep 17 00:00:00 2001 From: Josh Callebaut Date: Wed, 13 Jan 2016 17:45:27 -0800 Subject: [PATCH 02/28] Removes stay logging, implements functionality improvements for artisan modals. --- .../level/modal/artisan-guide-modal.jade | 41 +++++++++++-------- app/views/editor/level/LevelEditView.coffee | 3 +- .../level/modals/ArtisanGuideModal.coffee | 9 +++- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/app/templates/editor/level/modal/artisan-guide-modal.jade b/app/templates/editor/level/modal/artisan-guide-modal.jade index f02540e16..a9a861c35 100644 --- a/app/templates/editor/level/modal/artisan-guide-modal.jade +++ b/app/templates/editor/level/modal/artisan-guide-modal.jade @@ -4,7 +4,10 @@ block modal-header-content h3 Artisan Compendium block modal-body-content p - | Welcome to the Artisan Compendium. Below you will find a series of helpful guides and tutorials for getting your levels to Master Artisan quality, as well as a way to submit your level for official Artisan review! If you have any feedback on the Level Editor, please contact us at: artisans@codecombat.com + | Welcome to the Artisan Compendium. Below you will find a series of helpful guides and tutorials for getting your levels to Master Artisan quality + if creator == meID + |, as well as a way to submit your level for official Artisan review + |! If you have any feedback on the Level Editor, please contact us at: artisans@codecombat.com div.centered-stack div a(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home', target='_blank') Wiki Homepage @@ -12,25 +15,27 @@ block modal-body-content a(href='https://github.com/codecombat/codecombat/wiki/Artisan-How-To-Index', target='_blank') Basic How-tos div a(href='http://direct.codecombat.com/community', target='_blank') Artisan Rankings - h4 Level Submission - p - | Do you want your level to be added to the campaign? Please take a moment to fill out the questions below! Don’t worry, this isn’t a timed quiz. It is just a way for the artisans at CodeCombat HQ to get a feel for the purpose of this level. If it doesn’t quite yet meet our standards for release we will give you feedback to help polish it further! - .form - .form-group - label.control-label(for='credit-name') How would you like to be credited? - input#credit-name.form-control(name='creditName') - .form-group - label.control-label(for='level-purpose') What is the purpose of this level? - textarea#level-purpose.form-control(name='levelPurpose', rows=4) - .form-group - label.control-label(for='level-inspiration') What was the inspiration for the level? - textarea#level-inspiration.form-control(name='levelInspiration', rows=4) - .form-group - label.control-label(for='level-location') Where in the campaign do you think this level belongs? - textarea#level-location.form-control(name='levelLocation', rows=4) + if creator == meID + h4 Level Submission + p + | Do you want your level to be added to the campaign? Please take a moment to fill out the questions below! Don’t worry, this isn’t a timed quiz. It is just a way for the artisans at CodeCombat HQ to get a feel for the purpose of this level. If it doesn’t quite yet meet our standards for release we will give you feedback to help polish it further! + .form + .form-group + label.control-label(for='credit-name') How would you like to be credited? + input#credit-name.form-control(name='creditName') + .form-group + label.control-label(for='level-purpose') What is the purpose of this level? + textarea#level-purpose.form-control(name='levelPurpose', rows=4) + .form-group + label.control-label(for='level-inspiration') What was the inspiration for the level? + textarea#level-inspiration.form-control(name='levelInspiration', rows=4) + .form-group + label.control-label(for='level-location') Where in the campaign do you think this level belongs? + textarea#level-location.form-control(name='levelLocation', rows=4) block modal-footer-content div a(href='#', data-dismiss="modal", aria-hidden="true").btn Close - button.btn.btn-primary#level-submit Submit \ No newline at end of file + if creator == meID + button.btn.btn-primary#level-submit Submit \ No newline at end of file diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index bddb0dcd7..d0034308c 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -93,7 +93,6 @@ module.exports = class LevelEditView extends RootView getRenderData: (context={}) -> context = super(context) context.level = @level - console.log context.level context.authorized = me.isAdmin() or @level.hasWriteAccess(me) context.anonymous = me.get('anonymous') context.recentlyPlayedOpponents = storage.load('recently-played-matches')?[@levelID] ? [] @@ -177,7 +176,7 @@ module.exports = class LevelEditView extends RootView Backbone.Mediator.publish 'editor:view-switched', {} showArtisanGuide: (e) -> - @openModalView new ArtisanGuideModal() + @openModalView new ArtisanGuideModal level: @level Backbone.Mediator.publish 'editor:view-switched', {} startForking: (e) -> diff --git a/app/views/editor/level/modals/ArtisanGuideModal.coffee b/app/views/editor/level/modals/ArtisanGuideModal.coffee index c12999892..dd50438cb 100644 --- a/app/views/editor/level/modals/ArtisanGuideModal.coffee +++ b/app/views/editor/level/modals/ArtisanGuideModal.coffee @@ -25,7 +25,15 @@ module.exports = class ArtisanGuideModal extends ModalView constructor: (options) -> super options + @level = options.level + @options = level: @level.get 'name' + @render() + getRenderData: -> + c = super() + c.creator = @level.get 'creator' + c.meID = me.id + c levelSubmit: -> @playSound 'menu-button-click' @@ -36,7 +44,6 @@ module.exports = class ArtisanGuideModal extends ModalView @populateBrowserData contactMessage contactMessage = _.merge contactMessage, @options contactMessage.country = me.get('country') - window.tracker?.trackEvent 'Sent Feedback', message: contactMessage sendContactMessage contactMessage, @$el $.post "/db/user/#{me.id}/track/contact_codecombat" From ad28be1c6432d65eb3090afc4e0b1f5162616028 Mon Sep 17 00:00:00 2001 From: Josh Callebaut Date: Fri, 15 Jan 2016 13:54:08 -0800 Subject: [PATCH 03/28] Adds casing check for unloaded/non-tasked levels. --- app/views/editor/level/LevelEditView.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index d0034308c..73f8a803b 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -221,4 +221,7 @@ module.exports = class LevelEditView extends RootView ++@levelBuildTime getTaskCompletionRatio: -> - return _.filter(@level.get('tasks'), (_elem) -> return _elem.complete).length + "/" + @level.get('tasks').length + if !@level.get('tasks')? + return {} + else + return _.filter(@level.get('tasks'), (_elem) -> return _elem.complete).length + "/" + @level.get('tasks').length From 80c2a9e539ac499dd11dbd9d6ed16f24af3ed7db Mon Sep 17 00:00:00 2001 From: Josh Callebaut Date: Wed, 20 Jan 2016 10:48:53 -0800 Subject: [PATCH 04/28] New models with default tasks are preintialized with these tasks. --- app/schemas/models/level.coffee | 1 + app/views/editor/modal/NewModelModal.coffee | 3 +++ 2 files changed, 4 insertions(+) diff --git a/app/schemas/models/level.coffee b/app/schemas/models/level.coffee index f0cfd1cfe..5712e7442 100644 --- a/app/schemas/models/level.coffee +++ b/app/schemas/models/level.coffee @@ -254,6 +254,7 @@ LevelSchema = c.object { 'default': name: 'Ineffable Wizardry' description: 'This level is indescribably flarmy.' + tasks: (name: t, complete: false for t in defaultTasks) documentation: {} scripts: [] thangs: [] diff --git a/app/views/editor/modal/NewModelModal.coffee b/app/views/editor/modal/NewModelModal.coffee index 49cba4036..aa3fc7215 100644 --- a/app/views/editor/modal/NewModelModal.coffee +++ b/app/views/editor/modal/NewModelModal.coffee @@ -12,6 +12,7 @@ module.exports = class NewModelModal extends ModalView 'submit form': 'onModelSubmitted' constructor: (options) -> + console.log options.model.schema super options @modelClass = options.model @modelLabel = options.modelLabel @@ -22,6 +23,8 @@ module.exports = class NewModelModal extends ModalView model = new @modelClass name = @$el.find('#name').val() model.set('name', name) + if @modelClass.schema.default? + model.set('tasks', @modelClass.schema.default.tasks) if model.schema().properties.permissions model.set 'permissions', [{access: 'owner', target: me.id}] model.set(key, prop) for key, prop of @properties if @properties? From c28720c6402d8af9b767bfd6e4ce3b600f0a6f44 Mon Sep 17 00:00:00 2001 From: Josh Callebaut Date: Wed, 20 Jan 2016 15:32:21 -0800 Subject: [PATCH 05/28] Implements fixes based on code review. --- .../level/modal/artisan-guide-modal.sass | 2 +- app/styles/editor/level/tasks-tab.sass | 6 +- .../level/modal/artisan-guide-modal.jade | 15 +- app/templates/editor/level/tasks-tab.jade | 41 +++++ app/templates/editor/level/tasks_tab.jade | 40 ----- app/templates/editor/thangTasksView.jade | 2 +- .../level/modals/ArtisanGuideModal.coffee | 12 +- .../editor/level/tasks/TasksTabView.coffee | 159 ++++++++---------- 8 files changed, 125 insertions(+), 152 deletions(-) create mode 100644 app/templates/editor/level/tasks-tab.jade delete mode 100644 app/templates/editor/level/tasks_tab.jade diff --git a/app/styles/editor/level/modal/artisan-guide-modal.sass b/app/styles/editor/level/modal/artisan-guide-modal.sass index 0240381c2..d87e6898f 100644 --- a/app/styles/editor/level/modal/artisan-guide-modal.sass +++ b/app/styles/editor/level/modal/artisan-guide-modal.sass @@ -1,3 +1,3 @@ #artisan-guide-modal .centered-stack - text-align: center \ No newline at end of file + text-align: center diff --git a/app/styles/editor/level/tasks-tab.sass b/app/styles/editor/level/tasks-tab.sass index 0852f47d9..96d5c6e85 100644 --- a/app/styles/editor/level/tasks-tab.sass +++ b/app/styles/editor/level/tasks-tab.sass @@ -2,7 +2,9 @@ td cursor: pointer .taskCheck - width: 5% + width: 5em + .editCell + width: 5em .taskCheck * text-align: center margin: auto @@ -12,4 +14,4 @@ cursor: pointer text-align:center .noArticle - color: rgba(160, 0, 0, 1) \ No newline at end of file + color: rgba(160, 0, 0, 1) diff --git a/app/templates/editor/level/modal/artisan-guide-modal.jade b/app/templates/editor/level/modal/artisan-guide-modal.jade index a9a861c35..92f740f79 100644 --- a/app/templates/editor/level/modal/artisan-guide-modal.jade +++ b/app/templates/editor/level/modal/artisan-guide-modal.jade @@ -2,12 +2,16 @@ extends /templates/core/modal-base block modal-header-content h3 Artisan Compendium + block modal-body-content + p + p | Welcome to the Artisan Compendium. Below you will find a series of helpful guides and tutorials for getting your levels to Master Artisan quality - if creator == meID + if view.creator === view.meID |, as well as a way to submit your level for official Artisan review - |! If you have any feedback on the Level Editor, please contact us at: artisans@codecombat.com + |! If you have any feedback on the Level Editor, please contact us at: + a(href='mailto:artisans@codecombat.com') artisans@codecombat.com div.centered-stack div a(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home', target='_blank') Wiki Homepage @@ -15,7 +19,7 @@ block modal-body-content a(href='https://github.com/codecombat/codecombat/wiki/Artisan-How-To-Index', target='_blank') Basic How-tos div a(href='http://direct.codecombat.com/community', target='_blank') Artisan Rankings - if creator == meID + if view.creator === view.meID h4 Level Submission p | Do you want your level to be added to the campaign? Please take a moment to fill out the questions below! Don’t worry, this isn’t a timed quiz. It is just a way for the artisans at CodeCombat HQ to get a feel for the purpose of this level. If it doesn’t quite yet meet our standards for release we will give you feedback to help polish it further! @@ -33,9 +37,8 @@ block modal-body-content label.control-label(for='level-location') Where in the campaign do you think this level belongs? textarea#level-location.form-control(name='levelLocation', rows=4) - block modal-footer-content div a(href='#', data-dismiss="modal", aria-hidden="true").btn Close - if creator == meID - button.btn.btn-primary#level-submit Submit \ No newline at end of file + if view.creator === view.meID + button.btn.btn-primary#level-submit Submit diff --git a/app/templates/editor/level/tasks-tab.jade b/app/templates/editor/level/tasks-tab.jade new file mode 100644 index 000000000..3555720be --- /dev/null +++ b/app/templates/editor/level/tasks-tab.jade @@ -0,0 +1,41 @@ +mixin task-row(task) + - var taskName = task.get('name'); + - var isComplete = task.get('complete') + - var taskLink = view.defaultTaskLinks[taskName] + tr.taskRow(data-task=task) + td.taskCheck + div.checkbox + input(type='checkbox', checked=(isComplete || false)).taskInput + if task.get('curEdit') === true + td.editCell + td + input(type="input", value=taskName)#curEdit + else + td.editCell + span.startEdit ✎ + td + if taskLink + if taskLink === './' + a.noArticle(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home', target='blank')= taskName + else + a(href=taskLink, target='_blank')= taskName + else + span= taskName + +block + table.table.table-striped.table-hover + tr + th.taskCheck Complete + th Edit + th Incomplete Tasks + for task in (view.taskArray() || []) + if task.get('revert').complete !== true + +task-row(task) + tr + th + th + th Completed Tasks + for task in (view.taskArray() || []) + if task.get('revert').complete === true + +task-row(task) + button#createTask.btn.btn-default Add Task diff --git a/app/templates/editor/level/tasks_tab.jade b/app/templates/editor/level/tasks_tab.jade deleted file mode 100644 index ef2298515..000000000 --- a/app/templates/editor/level/tasks_tab.jade +++ /dev/null @@ -1,40 +0,0 @@ -mixin taskRow(task) - tr.taskRow(data=task.name) - td.taskCheck - div.checkbox - input(type='checkbox', checked=(task.complete || false), data=task.name, placeholder='Enter a description of the task.').taskInput - if task.curEdit == true - td - td - input(type="input", value=task.name)#curEdit - else - td - span.startEdit(data=task.name) ✎ - td - if view.defaultTaskLinks[task.name] - if view.defaultTaskLinks[task.name] === './' - a.noArticle(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home' target='_blank')= task.name - else - a(href=view.defaultTaskLinks[task.name], target='_blank')= task.name - else - span= task.name -block - if view.tasks - table.table.table-striped.table-hover - tr - th.taskCheck Complete - th Edit - th Incomplete Tasks - for task in view.tTasks - if task.reversion !== true - +taskRow(task) - tr - th - th - th Completed Tasks - for task in view.tTasks - if task.reversion === true - +taskRow(task) - button#createTask.btn.btn-default Add Task - else - div Wrong! \ No newline at end of file diff --git a/app/templates/editor/thangTasksView.jade b/app/templates/editor/thangTasksView.jade index 6574a4807..48a216782 100644 --- a/app/templates/editor/thangTasksView.jade +++ b/app/templates/editor/thangTasksView.jade @@ -26,4 +26,4 @@ mixin thangRow(thang) for task in (thang.tasks || []) if !task.complete tr - td= task.name \ No newline at end of file + td= task.name diff --git a/app/views/editor/level/modals/ArtisanGuideModal.coffee b/app/views/editor/level/modals/ArtisanGuideModal.coffee index dd50438cb..a0887a3c4 100644 --- a/app/views/editor/level/modals/ArtisanGuideModal.coffee +++ b/app/views/editor/level/modals/ArtisanGuideModal.coffee @@ -23,17 +23,11 @@ module.exports = class ArtisanGuideModal extends ModalView events: 'click #level-submit': 'levelSubmit' - constructor: (options) -> - super options + initialize: (options) -> @level = options.level @options = level: @level.get 'name' - @render() - - getRenderData: -> - c = super() - c.creator = @level.get 'creator' - c.meID = me.id - c + @creator = @level.get 'creator' + @meID = me.id levelSubmit: -> @playSound 'menu-button-click' diff --git a/app/views/editor/level/tasks/TasksTabView.coffee b/app/views/editor/level/tasks/TasksTabView.coffee index a2e385be3..c67751cf9 100644 --- a/app/views/editor/level/tasks/TasksTabView.coffee +++ b/app/views/editor/level/tasks/TasksTabView.coffee @@ -1,5 +1,5 @@ CocoView = require 'views/core/CocoView' -template = require 'templates/editor/level/tasks_tab' +template = require 'templates/editor/level/tasks-tab' Level = require 'models/Level' module.exports = class TasksTabView extends CocoView @@ -7,11 +7,11 @@ module.exports = class TasksTabView extends CocoView className: 'tab-pane' template: template events: - 'click .taskRow': 'onTaskRowClicked' - 'click .taskInput': 'onTaskCompletionClicked' - 'click .startEdit': 'onTaskEditClicked' - 'click #createTask': 'onTaskCreateClicked' - 'keydown #curEdit': 'onCurEditKeyDown' + 'click .taskRow': 'onClickTaskRow' + 'click .taskInput': 'onClickTaskInput' + 'click .startEdit': 'onClickStartEdit' + 'click #createTask': 'onClickCreateTask' + 'keydown #curEdit': 'onKeyDownCurEdit' subscriptions: 'editor:level-loaded': 'onLevelLoaded' @@ -54,123 +54,96 @@ module.exports = class TasksTabView extends CocoView 'Do thorough set decoration.':'./' 'Add a walkthrough video.':'./' - constructor: (options) -> - super options - @render() + taskMap: -> + return @tasks?.map((_obj) -> return (name: _obj.get('name'), complete: (_obj.get('complete') || false))) + + taskArray: -> + return @tasks?.toArray() + + findTask: (_name) -> + return @tasks.findWhere(name:_name) onLoaded: -> onLevelLoaded: (e) -> @level = e.level - if e.level._revertAttributes - @revertTasks = e.level._revertAttributes.tasks - else - @revertTasks = @level.get 'tasks' - @tasks = @level.get 'tasks' - @tTasks = _.clone @tasks, true - for task in @tTasks - if @revertTasks[_.findKey(@revertTasks, {'name':task.name})] - task.reversion = @revertTasks[_.findKey(@revertTasks, {'name':task.name})].complete || null - else - task.reversion = false + Task = Backbone.Model.extend({ + initialize: -> + if e?.level?._revertAttributes?.tasks? + if _.find(e.level._revertAttributes.tasks, {name:arguments[0].name}) + @set 'revert', _.find(e.level._revertAttributes.tasks, {name:arguments[0].name}) + else + @set 'revert', arguments[0] + else + @set 'revert', arguments[0] + }) + TaskList = Backbone.Collection.extend({ + model: Task + }) + @tasks = new TaskList(@level.get 'tasks') @render() - - getRenderData: -> - c = super() - c.tasks = @tasks - c.status - c + pushTasks: -> - for task in @tTasks - taskKey = @findTaskByName(@tasks, task.name) - oTaskKey = @findTaskByName(@tasks, task.oldName) - if taskKey? - @tasks[taskKey].complete = task.complete - else if oTaskKey? - if task.name is '' - @tasks.splice(oTaskKey, 1) - @tTasks.splice(@tTasks.indexOf(task), 1) - break - @pushTasks() - else - @tasks[oTaskKey].name = task.name - @tasks[oTaskKey].complete = task.complete - else - if task.name is '' - @tasks.splice(oTaskKey, 1) - @tTasks.splice(@tTasks.indexOf(task), 1) - else - @tasks.push - name: task.name - complete: task.complete - @level.set 'tasks', @tasks - @parent.renderSelectors '#tasks-tab' + @level.set 'tasks', @taskMap() - onTaskRowClicked: (e) -> + onClickTaskRow: (e) -> if not $(e.target).is('input') and not $(e.target).is('a') and not $(e.target).hasClass('startEdit') - checkBox = $(e.currentTarget).find('.taskInput')[0] - tTaskKey = @findTaskByName(@tTasks, @getData e) - if tTaskKey? - if checkBox.checked - checkBox.checked = false - else - checkBox.checked = true - console.log(checkBox.checked) - @tTasks[tTaskKey].complete = checkBox.checked + task = @findTask($(e.target).closest('tr').data('task').name) + checkbox = $(e.currentTarget).find('.taskInput')[0] + if task.get 'complete' + task.set 'complete', false + else + task.set 'complete', true + checkbox.checked = task.get 'complete' @pushTasks() - onTaskCompletionClicked: (e) -> - tTaskKey = @findTaskByName(@tTasks, @getData e) - if tTaskKey? - @tTasks[tTaskKey].complete = e.currentTarget.checked + onClickTaskInput: (e) -> + task = @findTask($(e.target).closest('tr').data('task').name) + task.set 'complete', e.currentTarget.checked @pushTasks() - onTaskCreateClicked: (e) -> + onClickStartEdit: (e) -> if $('#curEdit').length is 0 - @tTasks.push - name: '' - complete: false - reversion: false - curEdit: true + task = @findTask($(e.target).closest('tr').data('task').name) + task.set 'curEdit', true @render() editDiv = $('#curEdit')[0] editDiv.focus() len = editDiv.value.length * 2 editDiv.setSelectionRange len, len - onCurEditKeyDown: (e) -> - editDiv = $('#curEdit')[0] + onKeyDownCurEdit: (e) -> if e.keyCode is 13 - taskIndex = @findTaskByName(@tasks, editDiv.value) - tTaskIndex = _.findKey(@tTasks, {'curEdit':true}) - if taskIndex? and tTaskIndex? and taskIndex isnt tTaskIndex + editDiv = $('#curEdit')[0] + task = @findTask($(e.target).closest('tr').data('task').name) + potentialTask = @findTask(editDiv.value) + if potentialTask and potentialTask isnt task noty timeout: 5000 - text: 'Task with name already exists.' + text: 'Task with name already exists!' type: 'error' layout: 'topCenter' + else if editDiv.value is '' + @tasks.remove task + @pushTasks() + @render() else - @tTasks[tTaskIndex].oldName = @tTasks[tTaskIndex].name - @tTasks[tTaskIndex].name = curEdit.value - @tTasks[tTaskIndex].curEdit = false + task.set 'name', editDiv.value + task.set 'curEdit', false + @pushTasks() + @render() - @pushTasks() - @render() - - onTaskEditClicked: (e) -> + onClickCreateTask: (e) -> if $('#curEdit').length is 0 - taskIndex = @findTaskByName(@tTasks, @getData e) - @tTasks[taskIndex].curEdit = true + @tasks.add + name: '' + complete: false + curEdit: true + revert: + name: 'null' + complete: false @render() editDiv = $('#curEdit')[0] editDiv.focus() len = editDiv.value.length * 2 editDiv.setSelectionRange len, len - - findTaskByName: (obj, name) -> - return _.findKey(obj, {'name':name}) - - getData: (elem) -> - return elem.currentTarget.getAttribute('data') - - \ No newline at end of file From ab93dc8564b56ac3834447e66f9fb2417a37c0fd Mon Sep 17 00:00:00 2001 From: Josh Callebaut Date: Thu, 21 Jan 2016 12:14:37 -0800 Subject: [PATCH 06/28] Bug fixes and style fixes. --- app/styles/editor/level/tasks-tab.sass | 18 ++-- app/templates/editor/level/tasks-tab.jade | 39 ++++---- app/views/editor/level/LevelEditView.coffee | 4 +- .../editor/level/tasks/TasksTabView.coffee | 99 +++++++++++-------- app/views/editor/modal/NewModelModal.coffee | 3 +- 5 files changed, 89 insertions(+), 74 deletions(-) diff --git a/app/styles/editor/level/tasks-tab.sass b/app/styles/editor/level/tasks-tab.sass index 96d5c6e85..5c65193d1 100644 --- a/app/styles/editor/level/tasks-tab.sass +++ b/app/styles/editor/level/tasks-tab.sass @@ -1,17 +1,19 @@ #editor-level-tasks-tab-view td cursor: pointer - .taskCheck + .task-check width: 5em - .editCell - width: 5em - .taskCheck * + .task-check, .task-check * + margin: auto text-align: center + .edit-cell + width: 5em + .edit-cell, .edit-cell * margin: auto - .checkbox + text-align: center + .task-name margin: auto - .startEdit + .start-edit cursor: pointer - text-align:center - .noArticle + .no-article color: rgba(160, 0, 0, 1) diff --git a/app/templates/editor/level/tasks-tab.jade b/app/templates/editor/level/tasks-tab.jade index 3555720be..edc876e10 100644 --- a/app/templates/editor/level/tasks-tab.jade +++ b/app/templates/editor/level/tasks-tab.jade @@ -1,22 +1,23 @@ -mixin task-row(task) +mixin task-row(cid) + - var task = view.getTaskByCID(cid) - var taskName = task.get('name'); - var isComplete = task.get('complete') - var taskLink = view.defaultTaskLinks[taskName] - tr.taskRow(data-task=task) - td.taskCheck + tr.task-row(data-task-cid=cid) + td.task-check div.checkbox - input(type='checkbox', checked=(isComplete || false)).taskInput - if task.get('curEdit') === true - td.editCell - td - input(type="input", value=taskName)#curEdit + input(type='checkbox', checked=(isComplete || false)).task-input + if task.get('curEdit') == true + td.edit-cell + td.task-name + input(type="input", value=taskName)#cur-edit else - td.editCell - span.startEdit ✎ - td + td.edit-cell + span.glyphicon.glyphicon-edit.start-edit + td.task-name if taskLink if taskLink === './' - a.noArticle(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home', target='blank')= taskName + a.no-article(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home', target='blank')= taskName else a(href=taskLink, target='_blank')= taskName else @@ -25,17 +26,17 @@ mixin task-row(task) block table.table.table-striped.table-hover tr - th.taskCheck Complete - th Edit + th.task-check Complete + th.edit-cell Edit th Incomplete Tasks for task in (view.taskArray() || []) if task.get('revert').complete !== true - +task-row(task) + +task-row(task.cid) tr - th - th + th.task-check + th.edit-cell th Completed Tasks for task in (view.taskArray() || []) if task.get('revert').complete === true - +task-row(task) - button#createTask.btn.btn-default Add Task + +task-row(task.cid) + button#create-task.btn.btn-default Add Task diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index 73f8a803b..53152052e 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -221,7 +221,7 @@ module.exports = class LevelEditView extends RootView ++@levelBuildTime getTaskCompletionRatio: -> - if !@level.get('tasks')? - return {} + if not @level.get('tasks')? + return [] else return _.filter(@level.get('tasks'), (_elem) -> return _elem.complete).length + "/" + @level.get('tasks').length diff --git a/app/views/editor/level/tasks/TasksTabView.coffee b/app/views/editor/level/tasks/TasksTabView.coffee index c67751cf9..06f516dba 100644 --- a/app/views/editor/level/tasks/TasksTabView.coffee +++ b/app/views/editor/level/tasks/TasksTabView.coffee @@ -7,11 +7,12 @@ module.exports = class TasksTabView extends CocoView className: 'tab-pane' template: template events: - 'click .taskRow': 'onClickTaskRow' - 'click .taskInput': 'onClickTaskInput' - 'click .startEdit': 'onClickStartEdit' - 'click #createTask': 'onClickCreateTask' - 'keydown #curEdit': 'onKeyDownCurEdit' + 'click .task-row': 'onClickTaskRow' + 'click .task-input': 'onClickTaskInput' + 'click .start-edit': 'onClickStartEdit' + 'click #create-task': 'onClickCreateTask' + 'keydown #cur-edit': 'onKeyDownCurEdit' + 'blur #cur-edit': 'onBlurCurEdit' subscriptions: 'editor:level-loaded': 'onLevelLoaded' @@ -54,20 +55,48 @@ module.exports = class TasksTabView extends CocoView 'Do thorough set decoration.':'./' 'Add a walkthrough video.':'./' + applyTaskName: (_task, _input) -> + name = _input.value + potentialTask = @findTask(name) + if potentialTask and potentialTask isnt _task + noty + timeout: 5000 + text: 'Task with name already exists!' + type: 'error' + layout: 'topCenter' + _input.focus() + else if name is '' + @tasks.remove _task + @pushTasks() + @render() + else + _task.set 'name', name + _task.set 'curEdit', false + @pushTasks() + @render() + + focusEditInput: -> + editInput = $('cur-edit')[0] + editInput.focus() + len = editInput.value.length * 2 + editInput.setSelectionRange len, len + + getTaskByCID: (_cid) -> + return @tasks.get _cid + taskMap: -> return @tasks?.map((_obj) -> return (name: _obj.get('name'), complete: (_obj.get('complete') || false))) taskArray: -> return @tasks?.toArray() - findTask: (_name) -> - return @tasks.findWhere(name:_name) - - onLoaded: -> onLevelLoaded: (e) -> @level = e.level Task = Backbone.Model.extend({ initialize: -> + # We want to keep track of the revertAttributes easily without digging back into the level every time. + # So per TaskModel we check to see if there is a revertAttribute associated with the task's name. + # If there is a reversion available, we use it, otherwise (e.g. new tasks without a reversion) we just use the Task's current name/completion status. if e?.level?._revertAttributes?.tasks? if _.find(e.level._revertAttributes.tasks, {name:arguments[0].name}) @set 'revert', _.find(e.level._revertAttributes.tasks, {name:arguments[0].name}) @@ -82,14 +111,13 @@ module.exports = class TasksTabView extends CocoView @tasks = new TaskList(@level.get 'tasks') @render() - pushTasks: -> @level.set 'tasks', @taskMap() onClickTaskRow: (e) -> - if not $(e.target).is('input') and not $(e.target).is('a') and not $(e.target).hasClass('startEdit') - task = @findTask($(e.target).closest('tr').data('task').name) - checkbox = $(e.currentTarget).find('.taskInput')[0] + if not $(e.target).is('input') and not $(e.target).is('a') and not $(e.target).hasClass('start-edit') and $('#cur-edit').length is 0 + task = @tasks.get $(e.target).closest('tr').data('task-cid') + checkbox = $(e.currentTarget).find('.task-input')[0] if task.get 'complete' task.set 'complete', false else @@ -98,43 +126,31 @@ module.exports = class TasksTabView extends CocoView @pushTasks() onClickTaskInput: (e) -> - task = @findTask($(e.target).closest('tr').data('task').name) + task = @tasks.get $(e.target).closest('tr').data('task-cid') task.set 'complete', e.currentTarget.checked @pushTasks() + + onClickStartEdit: (e) -> - if $('#curEdit').length is 0 - task = @findTask($(e.target).closest('tr').data('task').name) + if $('#cur-edit').length is 0 + task = @tasks.get $(e.target).closest('tr').data('task-cid') task.set 'curEdit', true @render() - editDiv = $('#curEdit')[0] - editDiv.focus() - len = editDiv.value.length * 2 - editDiv.setSelectionRange len, len + @focusEditInput() onKeyDownCurEdit: (e) -> if e.keyCode is 13 - editDiv = $('#curEdit')[0] - task = @findTask($(e.target).closest('tr').data('task').name) - potentialTask = @findTask(editDiv.value) - if potentialTask and potentialTask isnt task - noty - timeout: 5000 - text: 'Task with name already exists!' - type: 'error' - layout: 'topCenter' - else if editDiv.value is '' - @tasks.remove task - @pushTasks() - @render() - else - task.set 'name', editDiv.value - task.set 'curEdit', false - @pushTasks() - @render() + editInput = $('#cur-edit')[0] + editInput.blur() + + onBlurCurEdit: (e) -> + editInput = $('#cur-edit')[0] + task = @tasks.get $(e.target).closest('tr').data('task-cid') + @applyTaskName(task, editInput) onClickCreateTask: (e) -> - if $('#curEdit').length is 0 + if $('#cur-edit').length is 0 @tasks.add name: '' complete: false @@ -143,7 +159,4 @@ module.exports = class TasksTabView extends CocoView name: 'null' complete: false @render() - editDiv = $('#curEdit')[0] - editDiv.focus() - len = editDiv.value.length * 2 - editDiv.setSelectionRange len, len + @focusEditInput() diff --git a/app/views/editor/modal/NewModelModal.coffee b/app/views/editor/modal/NewModelModal.coffee index aa3fc7215..ea2b3776d 100644 --- a/app/views/editor/modal/NewModelModal.coffee +++ b/app/views/editor/modal/NewModelModal.coffee @@ -12,7 +12,6 @@ module.exports = class NewModelModal extends ModalView 'submit form': 'onModelSubmitted' constructor: (options) -> - console.log options.model.schema super options @modelClass = options.model @modelLabel = options.modelLabel @@ -23,7 +22,7 @@ module.exports = class NewModelModal extends ModalView model = new @modelClass name = @$el.find('#name').val() model.set('name', name) - if @modelClass.schema.default? + if @modelClass.name is 'Level' model.set('tasks', @modelClass.schema.default.tasks) if model.schema().properties.permissions model.set 'permissions', [{access: 'owner', target: me.id}] From aea23072e3b662808e9f27f6765bec6d05d82eb1 Mon Sep 17 00:00:00 2001 From: Josh Callebaut Date: Fri, 22 Jan 2016 11:27:38 -0800 Subject: [PATCH 07/28] Fix #3348 - don't display score button for levels without a score type --- app/schemas/models/campaign.schema.coffee | 3 +++ app/styles/play/campaign-view.sass | 2 +- app/templates/play/campaign-view.jade | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/schemas/models/campaign.schema.coffee b/app/schemas/models/campaign.schema.coffee index cbef4a7c4..b8419f352 100644 --- a/app/schemas/models/campaign.schema.coffee +++ b/app/schemas/models/campaign.schema.coffee @@ -118,6 +118,9 @@ _.extend CampaignSchema.properties, { campaign: c.shortString title: 'Campaign', description: 'Which campaign this level is part of (like "desert").', format: 'hidden' # Automatically set by campaign editor. campaignIndex: c.int title: 'Campaign Index', description: 'The 0-based index of this level in its campaign.', format: 'hidden' # Automatically set by campaign editor. + scoreTypes: c.array {title: 'Score Types', description: 'What metric to show leaderboards for.', uniqueItems: true}, + c.shortString(title: 'Score Type', 'enum': ['time', 'damage-taken', 'damage-dealt', 'gold-collected', 'difficulty']) # TODO: good version of LoC; total gear value. + tasks: c.array {title: 'Tasks', description: 'Tasks to be completed for this level.'}, c.task concepts: c.array {title: 'Programming Concepts', description: 'Which programming concepts this level covers.'}, c.concept diff --git a/app/styles/play/campaign-view.sass b/app/styles/play/campaign-view.sass index 844dcd2f5..76bee6a65 100644 --- a/app/styles/play/campaign-view.sass +++ b/app/styles/play/campaign-view.sass @@ -280,7 +280,7 @@ $gameControlMargin: 30px opacity: 1 padding: 3px 9px - &.complete + &.shows-leaderboard .start-level, .view-solutions min-width: calc(50% - 5px) display: inline-block diff --git a/app/templates/play/campaign-view.jade b/app/templates/play/campaign-view.jade index 21becd68e..2f8bdd60f 100644 --- a/app/templates/play/campaign-view.jade +++ b/app/templates/play/campaign-view.jade @@ -30,7 +30,9 @@ if campaign - var playCount = levelPlayCountMap[level.slug] .progress.progress-striped.active.hide .progress-bar(style="width: 100%") - div(class="level-info " + (levelStatusMap[level.slug] || "") + (level.requiresSubscription ? " premium" : "")) + - var showsLeaderboard = levelStatusMap[level.slug] === 'complete' && ((level.scoreTypes && level.scoreTypes.length) || ['hero-ladder', 'course-ladder'].indexOf(level.type) !== -1); + + div(class="level-info " + (levelStatusMap[level.slug] || "") + (level.requiresSubscription ? " premium" : "") + (showsLeaderboard ? " shows-leaderboard" : "")) .level-status h3= i18n(level, 'name') + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : "")) - var description = i18n(level, 'description') || level.description || "" @@ -53,7 +55,7 @@ if campaign span(data-i18n="play.players") players span.spr , #{Math.round(playCount.playtime / 3600)} span(data-i18n="play.hours_played") hours played - if levelStatusMap[level.slug] === 'complete' + if showsLeaderboard button.btn.btn-warning.btn.btn-lg.btn-illustrated.view-solutions(data-level-slug=level.slug) span(data-i18n="leaderboard.scores") button.btn.btn-success.btn.btn-lg.btn-illustrated.start-level(data-i18n="common.play") Play From 6dfc02980aa49b7f538ee995d178604f688128e9 Mon Sep 17 00:00:00 2001 From: Josh Callebaut Date: Fri, 22 Jan 2016 13:54:20 -0800 Subject: [PATCH 08/28] Fixes non-existant task array, bad focus, adds a button to add default tasks. --- .../level/modal/artisan-guide-modal.jade | 2 - app/templates/editor/level/tasks-tab.jade | 4 +- app/views/editor/level/LevelEditView.coffee | 4 +- .../editor/level/tasks/TasksTabView.coffee | 59 +++++++++++++------ 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/app/templates/editor/level/modal/artisan-guide-modal.jade b/app/templates/editor/level/modal/artisan-guide-modal.jade index 92f740f79..29c685853 100644 --- a/app/templates/editor/level/modal/artisan-guide-modal.jade +++ b/app/templates/editor/level/modal/artisan-guide-modal.jade @@ -4,8 +4,6 @@ block modal-header-content h3 Artisan Compendium block modal-body-content - p - p | Welcome to the Artisan Compendium. Below you will find a series of helpful guides and tutorials for getting your levels to Master Artisan quality if view.creator === view.meID diff --git a/app/templates/editor/level/tasks-tab.jade b/app/templates/editor/level/tasks-tab.jade index edc876e10..8902434a3 100644 --- a/app/templates/editor/level/tasks-tab.jade +++ b/app/templates/editor/level/tasks-tab.jade @@ -39,4 +39,6 @@ block for task in (view.taskArray() || []) if task.get('revert').complete === true +task-row(task.cid) - button#create-task.btn.btn-default Add Task + button#create-task.btn.btn-primary Add Task + if view.missingDefaults().length !== 0 + button#add-default-tasks.btn.btn-default Add Default Tasks diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index 53152052e..e6da43ea4 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -222,6 +222,6 @@ module.exports = class LevelEditView extends RootView getTaskCompletionRatio: -> if not @level.get('tasks')? - return [] + return '0/0' else - return _.filter(@level.get('tasks'), (_elem) -> return _elem.complete).length + "/" + @level.get('tasks').length + return _.filter(@level.get('tasks'), (_elem) -> return _elem.complete).length + '/' + @level.get('tasks').length diff --git a/app/views/editor/level/tasks/TasksTabView.coffee b/app/views/editor/level/tasks/TasksTabView.coffee index 06f516dba..005e19a9f 100644 --- a/app/views/editor/level/tasks/TasksTabView.coffee +++ b/app/views/editor/level/tasks/TasksTabView.coffee @@ -13,6 +13,7 @@ module.exports = class TasksTabView extends CocoView 'click #create-task': 'onClickCreateTask' 'keydown #cur-edit': 'onKeyDownCurEdit' 'blur #cur-edit': 'onBlurCurEdit' + 'click #add-default-tasks': 'onClickAddDefaultTasks' subscriptions: 'editor:level-loaded': 'onLevelLoaded' @@ -55,9 +56,30 @@ module.exports = class TasksTabView extends CocoView 'Do thorough set decoration.':'./' 'Add a walkthrough video.':'./' + missingDefaults: -> + missingTasks = [] + if @level + for task in @level.schema().properties.tasks.default + unless _.findIndex(@level.get('tasks'), {name: task.name}) isnt -1 + missingTasks.push task + return missingTasks + + onClickAddDefaultTasks: -> + results = @missingDefaults() + for result in results + @tasks.add + name: result.name + complete: result.complete + curEdit: false + revert: + name: result.name + complete: false + @pushTasks() + @render() + applyTaskName: (_task, _input) -> name = _input.value - potentialTask = @findTask(name) + potentialTask = @tasks.findWhere({'name':_input}) if potentialTask and potentialTask isnt _task noty timeout: 5000 @@ -76,10 +98,11 @@ module.exports = class TasksTabView extends CocoView @render() focusEditInput: -> - editInput = $('cur-edit')[0] - editInput.focus() - len = editInput.value.length * 2 - editInput.setSelectionRange len, len + editInput = @$el.find('#cur-edit')[0] + if editInput + editInput.focus() + len = editInput.value.length * 2 + editInput.setSelectionRange len, len getTaskByCID: (_cid) -> return @tasks.get _cid @@ -115,9 +138,9 @@ module.exports = class TasksTabView extends CocoView @level.set 'tasks', @taskMap() onClickTaskRow: (e) -> - if not $(e.target).is('input') and not $(e.target).is('a') and not $(e.target).hasClass('start-edit') and $('#cur-edit').length is 0 - task = @tasks.get $(e.target).closest('tr').data('task-cid') - checkbox = $(e.currentTarget).find('.task-input')[0] + if not @$el.find(e.target).is('input') and not @$el.find(e.target).is('a') and not @$el.find(e.target).hasClass('start-edit') and @$el.find('#cur-edit').length is 0 + task = @tasks.get @$el.find(e.target).closest('tr').data('task-cid') + checkbox = @$el.find(e.currentTarget).find('.task-input')[0] if task.get 'complete' task.set 'complete', false else @@ -126,31 +149,29 @@ module.exports = class TasksTabView extends CocoView @pushTasks() onClickTaskInput: (e) -> - task = @tasks.get $(e.target).closest('tr').data('task-cid') + task = @tasks.get @$el.find(e.target).closest('tr').data('task-cid') task.set 'complete', e.currentTarget.checked @pushTasks() - - onClickStartEdit: (e) -> - if $('#cur-edit').length is 0 - task = @tasks.get $(e.target).closest('tr').data('task-cid') + if @$el.find('#cur-edit').length is 0 + task = @tasks.get @$el.find(e.target).closest('tr').data('task-cid') task.set 'curEdit', true @render() - @focusEditInput() + @focusEditInput() onKeyDownCurEdit: (e) -> if e.keyCode is 13 - editInput = $('#cur-edit')[0] + editInput = @$el.find('#cur-edit')[0] editInput.blur() onBlurCurEdit: (e) -> - editInput = $('#cur-edit')[0] - task = @tasks.get $(e.target).closest('tr').data('task-cid') + editInput = @$el.find('#cur-edit')[0] + task = @tasks.get @$el.find(e.target).closest('tr').data('task-cid') @applyTaskName(task, editInput) onClickCreateTask: (e) -> - if $('#cur-edit').length is 0 + if @$el.find('#cur-edit').length is 0 @tasks.add name: '' complete: false @@ -159,4 +180,4 @@ module.exports = class TasksTabView extends CocoView name: 'null' complete: false @render() - @focusEditInput() + @focusEditInput() From 6e7b34c4fd4a6844bcd8cbe64bdbc154c22a0ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Olivera?= Date: Tue, 26 Jan 2016 18:14:24 -0300 Subject: [PATCH 09/28] Translate new strings --- app/locale/es-419.coffee | 140 +++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/app/locale/es-419.coffee b/app/locale/es-419.coffee index cce647d95..ea140bf90 100644 --- a/app/locale/es-419.coffee +++ b/app/locale/es-419.coffee @@ -116,7 +116,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip login_switch: "¿Ya tienes una cuenta?" school_name: "Nombre de Escuela y Ciudad" optional: "opcional" -# school_name_placeholder: "Example High School, Springfield, IL" + school_name_placeholder: "Ejemplo: High School, Springfield, IL" recover: recover_account_title: "recuperar cuenta" @@ -560,14 +560,14 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip cat_blurb: "Maestro del Aire" scott_title: "Cofundador" # {change} scott_blurb: "Razonable" -# maka_title: "Customer Advocate" -# maka_blurb: "Storyteller" + maka_title: "Defensor del Consumidor" + maka_blurb: "Cuentista" rob_title: "Ingeniero de Compilación" # {change} rob_blurb: "Hace código y demás" josh_c_title: "Diseñador de Juegos" josh_c_blurb: "Diseña juegos" -# robin_title: "UX Design & Research" -# robin_blurb: "Scaffolding" + robin_title: "Diseñadora de UX & Investigadora" + robin_blurb: "Scaffolding" josh_title: "Diseñador de Juegos" josh_blurb: "El piso es Lava" retrostyle_title: "Ilustración" @@ -594,9 +594,9 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip being_reviewed_2: "revisada." approved_1: "Su aplicación a una suscripción gratuita fue" # {change} approved_2: "aprobada." # {change} -# approved_4: "You can now enroll your students on the" + approved_4: "Ahora puedes inscribir a tus estudiantes en los" approved_5: "cursos" -# approved_6: "page." + approved_6: "página."." denied_1: "Su aplicación a una suscripción gratuita ha sido" # {change} denied_2: "denegada." contact_1: "Por favor contactarse" @@ -736,7 +736,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip subs_only: "solo suscriptores" create_clan: "Crear nuevo clan" private_preview: "Vista previa" -# private_clans: "Private Clans" + private_clans: "Clanes Privados" public_clans: "Clanes publicos" my_clans: "Mis Clanes" clan_name: "Nombre del clan" @@ -867,14 +867,14 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip get_free: "Obtenga curso GRATIS" enroll_paid: "Anotar estudiantes en cursos pagos." you_have1: "Tiene" -# you_have2: "unused paid enrollments" -# use_one: "Use 1 paid enrollment for" -# use_multiple: "Use paid enrollments for the following students:" -# already_enrolled: "already enrolled" + you_have2: "matrículas pagadas no utilizadas" + use_one: "Utilizar 1 matrícula pagada para" + use_multiple: "Utilizar matrículas pagadas para los siguientes estudiantes:" + already_enrolled: "ya matriculados" licenses_remaining: "licencias restantes:" -# insufficient_enrollments: "insufficient paid enrollments" -# enroll_students: "Enroll Students" -# get_enrollments: "Get More Enrollments" + insufficient_enrollments: "matrículas pagas insuficientes" + enroll_students: "Matricular Estudiantes" + get_enrollments: "Obtener Más Matrículas" change_language: "Cambiar idioma del curso" keep_using: "Seguir Usando" switch_to: "Cambiar a" @@ -885,24 +885,24 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip back_classrooms: "Volver a mis aulas" back_courses: "Volver a mis cursos" edit_details: "Editar detallesde clase" -# enrolled_courses: "enrolled in paid courses:" -# purchase_enrollments: "Purchase Enrollments" + enrolled_courses: "matriculados en cursos pagos:" + purchase_enrollments: "Comprar Matrículas" remove_student: "Quitar alumno" assign: "Asignar" -# to_assign: "to assign paid courses." + to_assign: "para asignar cursos pagos." teacher: "Maestro" complete: "Completado" -# none: "None" + none: "Ninguno" save: "Guardar" play_campaign_title: "Jugar Campaña" play_campaign_description: "Estas listo para dar el siguiente paso! Explora cientos de desafiantes niveles, aprende habilidades avanzadas de programación, y compite en arenas multijugador!" create_account_title: "Crea una Cuenta" create_account_description: "Registrate gratis con una cuenta CodeCombat y obten acceso a mas niveles, mas habilidades de programacion, y mas diversion!" preview_campaign_title: "Previsualizar campaña" -# preview_campaign_description: "Take a sneak peek at all that CodeCombat has to offer before signing up for your FREE account." + preview_campaign_description: "Echa un vistazo a todo lo que CodeCombat tiene para ofrecer antes de registrarse con una cuenta GRATUITA." arena: "Arena" arena_soon_title: "Arena pronto disponible" -# arena_soon_description: "We are working on a multiplayer arena for classrooms at the end of" + arena_soon_description: "Estamos trabajando en una arena multijugador para las aulas para finales de" not_enrolled1: "No inscrito" not_enrolled2: "Preguntale a tu maestro para anotarte en el proximo curso." next_course: "Próximo curso" @@ -914,7 +914,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip start_new_game: "Iniciar un Nuevo Juego" play_now_learn_header: "Juega y Aprende" play_now_learn_1: "Sintaxis básica para controlar a tu personaje" -# play_now_learn_2: "while loops to solve pesky puzzles" + play_now_learn_2: "bucles while para resolver rompecabezas molestos" play_now_learn_3: "cadenas & variables para personalizar acciones" play_now_learn_4: "como vencer a un ogro (habilidades importantes en la vida!)" welcome_to_page: "¡Bienvenido a tu página de cursose!" @@ -931,7 +931,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip view_levels: "ver niveles" join_class: "Unirse a clase" ask_teacher_for_code: "Preguntalé a tu profesor si tu tienes un código de CodeCombat! Si lo tiene, ingresalo debajo:" -# enter_c_code: "" + enter_c_code: "" join: "Unirse" joining: "Uniendose a claseJoining class" course_complete: "Curso completo" @@ -945,72 +945,72 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip continue_playing: "Seguir jugando" more_options: "Másopciones:" option1_header: "Opción 1: Invitar estudiantes vía email" -# option1_body: "Students will automatically be sent an invitation to join this class, and will need to create an account with a username and password." + option1_body: "Se enviará automaticamente una invitación a los estudiantes para unirse a esta clase, y tendrán que crear una cuenta con un nombre de usuario y una contraseña." option2_header: "Opción 2: Enviarles la URL a tus estudiantes" -# option2_body: "Students will be asked to enter an email address, username and password to create an account." + option2_body: "Se pedirá a los estudiantes que ingresen un correo electrónico, un nombre de usuario y una contraseña para crear una cuenta." option3_header: "Opción 3: Dirigir estudiantes a to codecombat.com/courses" -# option3_body: "Give students the following passcode to enter along with an email address, username and password when they create an account." + option3_body: "Dar a los estudiantes el siguiente código de acceso para ingresar junto con el correo electrónico, el nombre de usuario y la contraseña cuando creen una cuenta." thank_you_pref: "Gracias por tu compra! Ahora puedes asignar" thank_you_suff: "más estudiantes a cursos pagos." -# return_to_class: "Return to classroom" -# return_to_course_man: "Return to course management." -# students_not_enrolled: "students not enrolled" -# total_all_classes: "Total Across All Classes" -# how_many_enrollments: "How many additional paid enrollments do you need?" -# each_student_access: "Each student in a class will get access to Courses 2-4 once they are enrolled in paid courses. You may assign each course to each student individually." + return_to_class: "Regresar al aula" + return_to_course_man: "Regresar a la administración del curso." + students_not_enrolled: "estudiantes no matriculados" + total_all_classes: "Total Por Todas las Clases" + how_many_enrollments: "¿Cuántas matriculas pagas adicionales necesitas?" + each_student_access: "Cada estudiante en una clase obtendrá acceso a los Cursos 2-4 una vez que esten matriculados en los cursos pagos. Puedes asignar cada curso a cada estudiante de forma individual." purchase_now: "Comprar Ahora" -# enrollments: "enrollments" + enrollments: "matrículas" remove_student1: "Quitar alumno" are_you_sure: "¿Estás seguro que quieres quitar este alumno de tu clase?" -# remove_description1: "Student will lose access to this classroom and assigned classes. Progress and gameplay is NOT lost, and the student can be added back to the classroom at any time." -# remove_description2: "The activated paid license will not be returned." + remove_description1: "El estudiante perderá acceso a esta aula y a sus clases asignadas. El progreso y la experiencia del juego NO se pierde, y el estudiante puede ser agregado de vuelta al aula en cualquier momento." + remove_description2: "La matrícula paga activada no será devuelta." keep_student: "Mantener alumno" removing_user: "Removiendo usuario" to_join_ask: "Para ingresar a una clase, preguntale a tu maestro por un código de acceso." join_this_class: "Ingresar clase" enter_here: "" -# successfully_joined: "Successfully joined" -# click_to_start: "Click here to start taking" + successfully_joined: "Ingresado exitosamente" + click_to_start: "Click aquí para comenzar a hablar" my_courses: "Mis Cursos" classroom: "Aulas" -# use_school_email: "use your school email if you have one" -# unique_name: "a unique name no one has chosen" + use_school_email: "utilize su correo electrónico de la escuela si tiene uno" + unique_name: "un nombre único, no uno que ya esté escogido" pick_something: "Escoge algo que recuerdes" -# class_code: "Class Code" -# optional_ask: "optional - ask your teacher to give you one!" + class_code: "Código de acceso" + optional_ask: "opcional - pregunta a tu maestro para que te de uno!" optional_school: "opcional - a qué escuela vas?" start_playing: "Comienza a Jugar" skip_this: "Saltar esto, Crearé una cuenta mas tarde!" welcome: "Bienvenido" -# getting_started: "Getting Started with Courses" -# download_getting_started: "Download Getting Started Guide [PDF]" -# getting_started_1: "Create a new class by clicking the green 'Create New Class' button below." -# getting_started_2: "Once you've created a class, click the blue 'Add Students' button." -# getting_started_3: "You'll see student's progress below as they sign up and join your class." -# additional_resources: "Additional Resources" -# additional_resources_1_pref: "Download/print our" -# additional_resources_1_mid: "Course 1 Teacher's Guide" -# additional_resources_1_suff: "explanations and solutions to each level." -# additional_resources_2_pref: "Complete our" -# additional_resources_2_suff: "to get two free enrollments for the rest of our paid courses." -# additional_resources_3_pref: "Visit our" -# additional_resources_3_mid: "Teacher Forums" -# additional_resources_3_suff: "to connect to fellow educators who are using CodeCombat." -# additional_resources_4_pref: "Check out our" -# additional_resources_4_mid: "Schools Page" -# additional_resources_4_suff: "to learn more about CodeCombat's classroom offerings." -# your_classes: "Your Classes" -# no_classes: "No classes yet!" -# create_new_class1: "create new class" + getting_started: "Cómo empezar con los Cursos" + download_getting_started: "Descargar Guía de Introducción [PDF]" + getting_started_1: "Crea una nueva clase haciendo click en el botón verde 'Crear Nueva Clase' de más abajo." + getting_started_2: "Una vez que creaste una clase, haz click en el botón azul 'Añadir Estudiantes'" + getting_started_3: "Verás los progresos de los estudiantes más abajo cuando se registren e ingresen a tu clase." + additional_resources: "Recursos Adicionales" + additional_resources_1_pref: "Descargar/imprimir nuestra" + additional_resources_1_mid: "Guía para maestros: Curso 1" + additional_resources_1_suff: "explicaciones y soluciones para cada nivel." + additional_resources_2_pref: "Complete nuestro" + additional_resources_2_suff: "para obtener dos matrículas gratuitas para el resto de nuestros cursos pagos." + additional_resources_3_pref: "Visite nuestro" + additional_resources_3_mid: "Foro de Maestros" + additional_resources_3_suff: "para relacionarse con sus colegas educadores que están usando CodeCombat." + additional_resources_4_pref: "Consulte nuestra" + additional_resources_4_mid: "Página de Escuelas" + additional_resources_4_suff: "para aprender más sobre la oferta para el aula de CodeCombat." + your_classes: "Tus Clases" + no_classes: "Aún no hay clases!" + create_new_class1: "crear nueva clase" available_courses: "Cursos Disponibles" -# unused_enrollments: "Unused enrollments available:" -# students_access: "All students get access to Introduction to Computer Science for free. One enrollment per student is required to assign them to paid CodeCombat courses. A single student does not need multiple enrollments to access all paid courses." -# active_courses: "active courses" + unused_enrollments: "Matrículas no utilizadas disponibles:" + students_access: "Todos los estudiantes obtienen acceso gratuito a Introducción a la Informática. Una matrícula por estudiante es necesaria para asignarles los cursos pagos de CodeCombat. Un solo estudiante no necesita múltiples matrículas para acceder a todos los cursos pagos." + active_courses: "cursos activos" no_students: "Aún no hay alumnos!" add_students1: "añadir alumnos" view_edit: "ver/editar" -# students_enrolled: "students enrolled" -# length: "Length:" + students_enrolled: "estudiantes matriculados" + length: "Duración:" classes: archmage_title: "Archimago" @@ -1408,8 +1408,8 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip user_polls_record: "Historia de Visitas de Encuestas" course: "Curso" courses: "Cursos" -# course_instance: "Course Instance" -# course_instances: "Course Instances" + course_instance: "Muestra del Curso" + course_instances: "Muestras del Curso" classroom: "Salón" classrooms: "Salones" clan: "Clan" @@ -1516,8 +1516,8 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip nutshell_title: "En una palabra" nutshell_description: "Cualquier recurso que te proveamos en el Editor de Niveles es gratis de usar como te plazca para la creación de Niveles. Sin embargo, nos reservamos el derecho de restringir la distribución de los niveles por sí mismos (aquellos creados en codecombat.com) para así poder cobrar por ellos en el futuro, si es que eso es lo que termina pasando." canonical: "La versión en inglés de este documento es la versión canónica y definitiva. Si hay alguna discrepancia entre las traducciones, la versión en inglés toma precedencia." -# third_party_title: "Third Party Services" -# third_party_description: "CodeCombat uses the following third party services (among others):" + third_party_title: "Servicios de Terceros" + third_party_description: "CodeCombat utiliza los siguientes servicios de terceros (entre otros):" ladder_prizes: title: "Premios de Torneos" # This section was for an old tournament and doesn't need new translations now. From 0ab7f48611b5e2f88feff3b253714be182e213c6 Mon Sep 17 00:00:00 2001 From: Binary-idiot Date: Tue, 26 Jan 2016 19:46:04 -0500 Subject: [PATCH 10/28] Add names for #53 --- app/lib/world/names.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/lib/world/names.coffee b/app/lib/world/names.coffee index 48cdc51d2..451be76ba 100644 --- a/app/lib/world/names.coffee +++ b/app/lib/world/names.coffee @@ -98,6 +98,7 @@ module.exports.thangNames = thangNames = # Female 'Vyrryx' 'Yzzrith' + 'Xith' ] 'Ogre Chieftain': [ # Female @@ -146,6 +147,8 @@ module.exports.thangNames = thangNames = # Animal 'Nevermore' 'Baltimore' + 'Columbia' + 'Dawnstar' ] 'Cougar': [ # Animal @@ -368,6 +371,7 @@ module.exports.thangNames = thangNames = 'Goliath': [ # Male 'Okar' + 'Ivan' ] 'Guardian': [ # Female From a009b4aae647e8dfc89e5622fc2d789b5947c954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Olivera?= Date: Wed, 27 Jan 2016 13:34:50 -0300 Subject: [PATCH 11/28] Fix strings with the 'change' label --- app/locale/es-419.coffee | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/app/locale/es-419.coffee b/app/locale/es-419.coffee index ea140bf90..be36594ed 100644 --- a/app/locale/es-419.coffee +++ b/app/locale/es-419.coffee @@ -251,7 +251,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip victory_title_suffix: " Completo!" victory_sign_up: "Regístrate para recibir actualizaciones" victory_sign_up_poke: "¿Quieres recibir las ultimas noticias por correo? ¡Crea una cuenta gratuita y te mantendremos informado!" - victory_rate_the_level: "Valora el nivel: " # {change} + victory_rate_the_level: "¿Cómo de divertido estuvo este nivel?" victory_return_to_ladder: "Volver a la escalera" victory_saving_progress: "Guardando Progreso" victory_go_home: "Ir al Inicio" @@ -403,7 +403,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip comparison_blurb: "Agudiza tus habilidades con la suscripción a CodeCombat!" feature1: "Más de 110 niveles basicos a lo largo de 4 mundos" feature2: "10 poderosos nuevos heroés con habilidades unicas!" - feature3: "Más de 70 niveles extras" # {change} + feature3: "+80 niveles extras" feature4: "{{gems}} gemas de bono cada mes!" feature5: "Video tutoriales" feature6: "Soporte Premium vía email" @@ -436,12 +436,12 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip parents_blurb3: "Sin Riesgo: Garantía de 100% de devolución, fácil 1-click y des- suscribirse." payment_methods: "Metodos de pago" payment_methods_title: "Metodos de pago aceptados." - payment_methods_blurb1: "Actualmente aceptamos tarjetas de credito y Alipay." # {change} + payment_methods_blurb1: "Actualmente aceptamos tarjetas de credito y Alipay. Tambíen puedes usar PayPal, enviando a nick@codecombat tu correo electrónico, y pudiendo adquirir por {{three_month_price}} USD una suscripción de tres meses y gemas, o por ${{year_price}} una de un año." payment_methods_blurb2: "Si necesitas una forma alternativa de pago, por favor contactarse" sale_button: "Venta!" - sale_button_title: "Ahorra ${{discount}} al adquirir una suscripción por 1 año" # {change} + sale_button_title: "Ahorre $21 al adquirir una suscripción por 1 año" stripe_description: "Suscripción Mensual" - stripe_description_year_sale: "Suscripción por 1 año (${{discount}} descuento)" # {change} + stripe_description_year_sale: "Suscripción por 1 año (${{discount}} de descuento)" subscription_required_to_play: "Necesitas una suscripción para jugar este nivel." unlock_help_videos: "Suscríbete para desbloquear todos los video tutoriales." personal_sub: "Suscripción Personal" # Accounts Subscription View below @@ -454,7 +454,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip managed_subs: "Suscripciones administradas" subscribing: "Suscribiendo..." current_recipients: "Recipientes actuales" - unsubscribing: "Desuscribiendo..." # {change} + unsubscribing: "Dando de baja..." subscribe_prepaid: "Click en suscribirse para utlizar un código prepago" using_prepaid: "Usar código prepago para una suscribción mensual" @@ -552,17 +552,17 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip press_paragraph_1_link: "paquete de prensa" press_paragraph_1_suffix: ". Todos los logos e imágenes pueden ser usados sin contactarnos directamente." team: "Equipo" - nick_title: "Cofundador" # {change} + nick_title: "Cofundador, CEO" nick_blurb: "Gurú motivacional" - matt_title: "Cofundador" # {change} + matt_title: "Cofundador, CTO" matt_blurb: "Bicicletero" - cat_title: "Jefe Artesano" # {change} + cat_title: "Diseñadora de Juegos" cat_blurb: "Maestro del Aire" - scott_title: "Cofundador" # {change} + scott_title: "Cofundador, Ingeniero de Software" scott_blurb: "Razonable" maka_title: "Defensor del Consumidor" maka_blurb: "Cuentista" - rob_title: "Ingeniero de Compilación" # {change} + rob_title: "Ingeniero de Software" rob_blurb: "Hace código y demás" josh_c_title: "Diseñador de Juegos" josh_c_blurb: "Diseña juegos" @@ -579,8 +579,8 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip teachers: who_for_title: "¿Para quienes es CodeCombat?" - who_for_1: "Recomendamos CodeCombat para estudiantes de edades 9 y arriba. No se require experiencia en programación." # {change} - who_for_2: "Hemos diseñado a CodeCombat para atraer a niños y niñas." # {change} + who_for_1: "Recomendamos CodeCombat para estudiantes con +9 años de edad. No se require experiencia en programación. Diseñamos CodeCombat para que sea atractivo tanto para los chicos como para las chicas." + who_for_2: "Nuestro sistema de Cursos permite a los maestros configurar las clases, monitorizar el progreso y asignar contenido adicional a los estudiantes a través de una interfaz dedicada." more_info_title: "¿Dónde puedo encontrar más información?" more_info_1: "Nuestro" more_info_2: "el foro de profesores" @@ -590,14 +590,14 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip title: "Encuesta de Maestros" must_be_logged: "Debes ingresar primero. Por favor, crea una cuenta o ingresa desde el menú de arriba." retrieving: "Obteniendo información..." - being_reviewed_1: "Su aplicación a una suscripción gratuita está siendo" # {change} + being_reviewed_1: "Su aplicación a una suscripción gratuita está siendo" being_reviewed_2: "revisada." - approved_1: "Su aplicación a una suscripción gratuita fue" # {change} - approved_2: "aprobada." # {change} + approved_1: "Su aplicación para una prueba gratuita está siendo" + approved_2: "revisada." approved_4: "Ahora puedes inscribir a tus estudiantes en los" approved_5: "cursos" approved_6: "página."." - denied_1: "Su aplicación a una suscripción gratuita ha sido" # {change} + denied_1: "Su aplicación para una prueba gratuita ha sido" denied_2: "denegada." contact_1: "Por favor contactarse" contact_2: "si tiene más preguntas." @@ -856,7 +856,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip see_the: "Vea la" more_info: "para más información." choose_course: "Elige tu Curso:" - enter_code: "Introducir un código de desbloqueo" # {change} + enter_code: "Introducir un código de desbloqueo para ingresar a una clase" enter_code1: "Introducir código de desbloqueo" enroll: "Inscribirse" pick_from_classes: "Elije de tus clases actuales" @@ -1232,7 +1232,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip tutorial_skip: "Saltar Tutorial" tutorial_not_sure: "¿No estás seguro de que sucede?" tutorial_play_first: "Juega el Tutorial primero." - simple_ai: "IA Simple" # {change} + simple_ai: "IA Simple" warmup: "Calentamiento" friends_playing: "Amigos Jugando" log_in_for_friends: "Ingresa para jugar con tus amigos!" @@ -1255,7 +1255,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip winners: "Ganadores" league: "Liga" red_ai: "IA Roja" # "Red AI Wins", at end of multiplayer match playback - blue_ai: "IA Azul" # {change} + blue_ai: "IA Azul" wins: "Gana" # At end of multiplayer match playback humans: "Rojo" # Ladder page display team name ogres: "Azul" @@ -1329,7 +1329,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip months: "Meses" purchase_total: "Total" purchase_button: "Enviar Adquisición" - your_codes: "Tus Códigos:" # {change} + your_codes: "Tus Códigos:" redeem_codes: "Reclamar un Código de Suscripción" prepaid_code: "Código Prepagado" lookup_code: "Buscar código prepagado" From cef0ed0185181f9ea7027f27183a67d5e47697b9 Mon Sep 17 00:00:00 2001 From: Imperadeiro98 Date: Wed, 27 Jan 2016 18:12:34 +0000 Subject: [PATCH 12/28] Fix build --- app/locale/es-419.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/locale/es-419.coffee b/app/locale/es-419.coffee index be36594ed..23bb19470 100644 --- a/app/locale/es-419.coffee +++ b/app/locale/es-419.coffee @@ -596,7 +596,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip approved_2: "revisada." approved_4: "Ahora puedes inscribir a tus estudiantes en los" approved_5: "cursos" - approved_6: "página."." + approved_6: "página." denied_1: "Su aplicación para una prueba gratuita ha sido" denied_2: "denegada." contact_1: "Por favor contactarse" From 29350bf1de5ff27761ead780fcf9f3bd927b88e4 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Mon, 25 Jan 2016 16:52:14 -0800 Subject: [PATCH 13/28] Make network error handling more generic --- app/core/Tracker.coffee | 2 +- app/locale/en.coffee | 20 +++- app/models/SuperModel.coffee | 21 +++- app/styles/core/loading-error.sass | 15 +++ app/templates/base.jade | 2 +- app/templates/core/loading-error.jade | 113 +++++++++++++++----- app/templates/test-view.jade | 3 +- app/views/core/CocoView.coffee | 69 ++++++------ app/views/core/RootView.coffee | 7 -- app/views/courses/ClassroomView.coffee | 14 +-- app/views/courses/CoursesView.coffee | 10 +- test/app/views/core/CocoView.spec.coffee | 128 +++++++++++++++++++++++ 12 files changed, 315 insertions(+), 89 deletions(-) create mode 100644 app/styles/core/loading-error.sass create mode 100644 test/app/views/core/CocoView.spec.coffee diff --git a/app/core/Tracker.coffee b/app/core/Tracker.coffee index f94669d6f..988313353 100644 --- a/app/core/Tracker.coffee +++ b/app/core/Tracker.coffee @@ -142,7 +142,7 @@ module.exports = class Tracker $.post("#{window.location.protocol or 'http:'}//analytics.codecombat.com/analytics", dataToSend).fail -> console.error "Analytics post failed!" else - request = @supermodel.addRequestResource 'log_event', { + request = @supermodel.addRequestResource { url: '/db/analytics.log.event/-/log_event' data: {event: event, properties: properties} method: 'POST' diff --git a/app/locale/en.coffee b/app/locale/en.coffee index af98d1afa..a588588b5 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -34,6 +34,11 @@ twitter_follow: "Follow" teachers: "Teachers" careers: "Careers" + facebook: "Facebook" + twitter: "Twitter" + create_a_class: "Create a Class" + other: "Other" + learn_to_code: "Learn to Code!" modal: close: "Close" @@ -1352,17 +1357,22 @@ loading_error: could_not_load: "Error loading from server" - connection_failure: "Connection failed." + connection_failure: "Connection Failed" # {change} + login_required: "Login Required" + login_required_desc: " You need to be logged in to access this page." unauthorized: "You need to be signed in. Do you have cookies disabled?" - forbidden: "You do not have the permissions." - not_found: "Not found." + forbidden: "Forbidden" # {change} + forbidden_desc: "Oh no, there’s nothing we can show you here! Make sure you’re logged into the correct account, or visit one of the links below to get back to programming!" + not_found: "Not Found" # {change} + not_found_desc: "Hm, there’s nothing here. Visit one of the following links to get back to programming!" not_allowed: "Method not allowed." - timeout: "Server timeout." + timeout: "Server Timeout" # {change} conflict: "Resource conflict." bad_input: "Bad input." server_error: "Server error." - unknown: "Unknown error." + unknown: "Unknown Error" # {change} error: "ERROR" + general_desc: "Something went wrong, and it’s probably our fault. Try waiting a bit and then refreshing the page, or visit one of the following links to get back to programming!" resources: sessions: "Sessions" diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index f381bdfd1..097335c31 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -28,6 +28,10 @@ module.exports = class SuperModel extends Backbone.Model unfinished loadModel: (model, name, fetchOptions, value=1) -> + # Deprecating name. Handle if name is not included + value = fetchOptions if _.isNumber(fetchOptions) + fetchOptions = name if _.isObject(name) + # hero-ladder levels need remote opponent_session for latest session data (e.g. code) # Can't apply to everything since other features rely on cached models being more recent (E.g. level_session) # E.g.#2 heroConfig isn't necessarily saved to db in world map inventory modal, so we need to load the cached session on level start @@ -48,6 +52,10 @@ module.exports = class SuperModel extends Backbone.Model return res loadCollection: (collection, name, fetchOptions, value=1) -> + # Deprecating name. Handle if name is not included + value = fetchOptions if _.isNumber(fetchOptions) + fetchOptions = name if _.isObject(name) + url = collection.getURL() if cachedCollection = @collections[url] console.debug 'Collection cache hit', url, 'already loaded', cachedCollection.loaded @@ -135,6 +143,10 @@ module.exports = class SuperModel extends Backbone.Model return @progress is 1.0 or not @denom addModelResource: (modelOrCollection, name, fetchOptions, value=1) -> + # Deprecating name. Handle if name is not included + value = fetchOptions if _.isNumber(fetchOptions) + fetchOptions = name if _.isObject(name) + modelOrCollection.saveBackups = modelOrCollection.saveBackups or @shouldSaveBackups(modelOrCollection) @checkName(name) res = new ModelResource(modelOrCollection, name, fetchOptions, value) @@ -145,20 +157,25 @@ module.exports = class SuperModel extends Backbone.Model @removeResource _.find(@resources, (resource) -> resource?.model is modelOrCollection) addRequestResource: (name, jqxhrOptions, value=1) -> + # Deprecating name. Handle if name is not included + value = jqxhrOptions if _.isNumber(jqxhrOptions) + jqxhrOptions = name if _.isObject(name) + @checkName(name) res = new RequestResource(name, jqxhrOptions, value) @storeResource(res, value) return res addSomethingResource: (name, value=1) -> + value = name if _.isNumber(name) @checkName(name) res = new SomethingResource(name, value) @storeResource(res, value) return res checkName: (name) -> - if not name - throw new Error('Resource name should not be empty.') + if _.isString(name) + console.warn("SuperModel name property deprecated. Remove '#{name}' from code.") storeResource: (resource, value) -> @rid++ diff --git a/app/styles/core/loading-error.sass b/app/styles/core/loading-error.sass new file mode 100644 index 000000000..e9a51fc98 --- /dev/null +++ b/app/styles/core/loading-error.sass @@ -0,0 +1,15 @@ +#loading-error + padding: 20px + + .btn + margin-top: 20px + + .login-btn + margin-right: 10px + + #not-found-img + max-width: 20% + margin: 20px 0 + + #links-row + margin-top: 50px \ No newline at end of file diff --git a/app/templates/base.jade b/app/templates/base.jade index c8bd3c72b..af84fb03f 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -39,7 +39,7 @@ block header li a(href="/account/prepaid", data-i18n="account.prepaid_codes") Prepaid Codes li - a#logout-button(data-i18n="login.log_out") + a#logout-button(data-i18n="login.log_out") else button.btn.btn-sm.btn-primary.header-font.signup-button(data-i18n="login.sign_up") diff --git a/app/templates/core/loading-error.jade b/app/templates/core/loading-error.jade index d0d2ac5ca..e36ed3004 100644 --- a/app/templates/core/loading-error.jade +++ b/app/templates/core/loading-error.jade @@ -1,28 +1,89 @@ -.alert.alert-danger.loading-error-alert - span(data-i18n="loading_error.could_not_load") Error loading from server - span ( - span(data-i18n="resources.#{name}") - span ) - if !responseText - strong(data-i18n="loading_error.connection_failure") Connection failed. - else if status === 401 - strong(data-i18n="loading_error.unauthorized") You need to be signed in. Do you have cookies disabled? - else if status === 403 - strong(data-i18n="loading_error.forbidden") You do not have the permissions. - else if status === 404 - strong(data-i18n="loading_error.not_found") Not found. - else if status === 405 - strong(data-i18n="loading_error.not_allowed") Method not allowed. - else if status === 408 - strong(data-i18n="loading_error.timeout") Server timeout. - else if status === 409 - strong(data-i18n="loading_error.conflict") Resource conflict. - else if status === 422 - strong(data-i18n="loading_error.bad_input") Bad input. - else if status >= 500 - strong(data-i18n="loading_error.server_error") Server error. +#loading-error.text-center + if jqxhr.status === 401 + h1 + span.spr 401: + span(data-i18n="loading_error.login_required") + p(data-i18n="loading_error.login_required_desc") + button.login-btn.btn.btn-primary(data-i18n="login.log_in") + button#create-account-btn.btn.btn-primary(data-i18n="login.sign_up") + + // 402 currently not in use. TODO: Set it up + else if jqxhr.status === 402 + h2 402: Payment Required + + else if jqxhr.status === 403 + h1 + span.spr 403: + span(data-i18n="loading_error.forbidden") Forbidden + p(data-i18n="loading_error.forbidden_desc") + + // this should make no diff... but sometimes the server returns 403 when it should return 401 + if !me.isAnonymous() + button#logout-btn.btn.btn-primary(data-i18n="login.log_out") + + else if jqxhr.status === 404 + h1 + span.spr 404: + span(data-i18n="loading_error.not_found") + - var num = Math.floor(Math.random() * 3) + 1; + img#not-found-img(src="/images/pages/not_found/404_#{num}.png") + p(data-i18n="loading_error.not_found_desc") + + else if !jqxhr.status + h1(data-i18n="loading_error.connection_failure") + p It doesn’t look like you’re connected to the internet! Check your network connection and then reload this page. + else - strong(data-i18n="loading_error.unknown") Unknown error. + if jqxhr.status === 408 + h1 + span.spr 408: + span(data-i18n="loading_error.timeout") + else if jqxhr.status >= 500 && jqxhr.status <= 599 + h1 + span.spr #{jqxhr.status}: + span(data-i18n="loading_error.server_error") + else + h1(data-i18n="loading_error.unknown") + + p(data-i18n="loading_error.general_desc") - button.btn.btn-xs.retry-loading-resource(data-i18n="common.retry", data-resource-index=resourceIndex) Retry - button.btn.btn-xs.skip-loading-resource(data-i18n="play_level.skip", data-resource-index=resourceIndex) Skip + #links-row.row + .col-sm-3 + strong(data-i18n="cmomon.help") Help + br + a(href="/", data-i18n="nav.home") + br + a(href=view.forumLink(), data-i18n="nav.forum") + br + a(tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact") + br + a(href='/community', data-i18n="nav.community") + + .col-sm-3 + strong(data-i18n="courses.students") + br + a(href="/courses/students", data-i18n="nav.learn_to_code") + if me.isAnonymous() + br + a.login-btn(data-i18n="login.log_in") + br + a(href="/courses", data-i18n="courses.join_class") + + .col-sm-3 + strong(data-i18n="nav.teachers") + br + a(href="/schools", data-i18n="about.why_codecombat") + if me.isAnonymous() + br + a.login-btn(data-i18n="login.log_in") + br + a(href="/courses/teachers", data-i18n="nav.create_a_class") + + .col-sm-3 + strong(data-i18n="nav.other") + br + a(href="http://blog.codecombat.com/", data-i18n="nav.blog") + br + a(href="https://www.facebook.com/codecombat", data-i18n="nav.facebook") + br + a(href="https://twitter.com/codecombat", data-i18n="nav.twitter") \ No newline at end of file diff --git a/app/templates/test-view.jade b/app/templates/test-view.jade index 9851af483..c7b850f8e 100644 --- a/app/templates/test-view.jade +++ b/app/templates/test-view.jade @@ -6,7 +6,8 @@ ol.breadcrumb a(href=path.url)= path.name li.active= currentFolder -.well.pull-left#test-wrapper +#test-wrapper.well.pull-left + #demo-area #testing-area .nav.nav-pills.nav-stacked.pull-right.well#test-nav diff --git a/app/views/core/CocoView.coffee b/app/views/core/CocoView.coffee index 10fb9bc91..a76479681 100644 --- a/app/views/core/CocoView.coffee +++ b/app/views/core/CocoView.coffee @@ -3,6 +3,7 @@ utils = require 'core/utils' CocoClass = require 'core/CocoClass' loadingScreenTemplate = require 'templates/core/loading' loadingErrorTemplate = require 'templates/core/loading-error' +auth = require 'core/auth' lastToggleModalCall = 0 visibleModal = null @@ -16,8 +17,9 @@ module.exports = class CocoView extends Backbone.View template: -> '' events: - 'click .retry-loading-resource': 'onRetryResource' - 'click .skip-loading-resource': 'onSkipResource' + 'click #loading-error .login-btn': 'onClickLoadingErrorLoginButton' + 'click #loading-error #create-account-btn': 'onClickLoadingErrorCreateAccountButton' + 'click #loading-error #logout-btn': 'onClickLoadingErrorLogoutButton' subscriptions: {} shortcuts: {} @@ -157,43 +159,25 @@ module.exports = class CocoView extends Backbone.View onResourceLoadFailed: (e) -> r = e.resource return if r.jqxhr?.status is 402 # payment-required failures are handled separately - if r.jqxhr?.status is 0 - r.retries ?= 0 - r.retries += 1 - if r.retries > 20 - msg = 'Your computer or our servers appear to be offline. Please try refreshing.' - noty text: msg, layout: 'center', type: 'error', killer: true - return - else - @warnConnectionError() - return _.delay (=> r.load()), 3000 - - @$el.find('.loading-container .errors').append(loadingErrorTemplate({ - status: r.jqxhr?.status - name: r.name - resourceIndex: r.rid, - responseText: r.jqxhr?.responseText - })).i18n() - @$el.find('.progress').hide() + @showError(r.jqxhr) warnConnectionError: -> msg = $.i18n.t 'loading_error.connection_failure', defaultValue: 'Connection failed.' noty text: msg, layout: 'center', type: 'error', killer: true, timeout: 3000 - onRetryResource: (e) -> - res = @supermodel.getResource($(e.target).data('resource-index')) - # different views may respond to this call, and not all have the resource to reload - return unless res and res.isFailed - res.load() - @$el.find('.progress').show() - $(e.target).closest('.loading-error-alert').remove() - - onSkipResource: (e) -> - res = @supermodel.getResource($(e.target).data('resource-index')) - return unless res and res.isFailed - res.markLoaded() - @$el.find('.progress').show() - $(e.target).closest('.loading-error-alert').remove() + onClickLoadingErrorLoginButton: (e) -> + e.stopPropagation() # Backbone subviews and superviews will handle this call repeatedly otherwise + AuthModal = require 'views/core/AuthModal' + @openModalView(new AuthModal({mode: 'login'})) + + onClickLoadingErrorCreateAccountButton: (e) -> + e.stopPropagation() + AuthModal = require 'views/core/AuthModal' + @openModalView(new AuthModal({mode: 'signup'})) + + onClickLoadingErrorLogoutButton: (e) -> + e.stopPropagation() + auth.logoutUser() # Modals @@ -254,6 +238,23 @@ module.exports = class CocoView extends Backbone.View @_lastLoading.find('.loading-screen').remove() @_lastLoading.find('>').removeClass('hidden') @_lastLoading = null + + showError: (jqxhr) -> + return unless @_lastLoading? + context = { + jqxhr: jqxhr + view: @ + me: me + } + @_lastLoading.find('.loading-screen').replaceWith((loadingErrorTemplate(context))) + @_lastLoading.i18n() + + forumLink: -> + link = 'http://discourse.codecombat.com/' + lang = (me.get('preferredLanguage') or 'en-US').split('-')[0] + if lang in ['zh', 'ru', 'es', 'fr', 'pt', 'de', 'nl', 'lt'] + link += "c/other-languages/#{lang}" + link showReadOnly: -> return if me.isAdmin() or me.isArtisan() diff --git a/app/views/core/RootView.coffee b/app/views/core/RootView.coffee index 632887c9a..5773b14ac 100644 --- a/app/views/core/RootView.coffee +++ b/app/views/core/RootView.coffee @@ -100,13 +100,6 @@ module.exports = class RootView extends CocoView c.usesSocialMedia = @usesSocialMedia c - forumLink: -> - link = 'http://discourse.codecombat.com/' - lang = (me.get('preferredLanguage') or 'en-US').split('-')[0] - if lang in ['zh', 'ru', 'es', 'fr', 'pt', 'de', 'nl', 'lt'] - link += "c/other-languages/#{lang}" - link - afterRender: -> if @$el.find('#site-nav').length # hack... @$el.addClass('site-chrome') diff --git a/app/views/courses/ClassroomView.coffee b/app/views/courses/ClassroomView.coffee index 8ff59da9d..7175f4805 100644 --- a/app/views/courses/ClassroomView.coffee +++ b/app/views/courses/ClassroomView.coffee @@ -32,23 +32,23 @@ module.exports = class ClassroomView extends RootView initialize: (options, classroomID) -> return if me.isAnonymous() @classroom = new Classroom({_id: classroomID}) - @supermodel.loadModel @classroom, 'classroom' + @supermodel.loadModel @classroom @courses = new CocoCollection([], { url: "/db/course", model: Course}) @courses.comparator = '_id' - @supermodel.loadCollection(@courses, 'courses') + @supermodel.loadCollection(@courses) @campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign }) @courses.comparator = '_id' - @supermodel.loadCollection(@campaigns, 'campaigns', { data: { type: 'course' }}) + @supermodel.loadCollection(@campaigns, { data: { type: 'course' }}) @courseInstances = new CocoCollection([], { url: "/db/course_instance", model: CourseInstance}) @courseInstances.comparator = 'courseID' - @supermodel.loadCollection(@courseInstances, 'course_instances', { data: { classroomID: classroomID } }) + @supermodel.loadCollection(@courseInstances, { data: { classroomID: classroomID } }) @prepaids = new Prepaids() @prepaids.comparator = '_id' @prepaids.fetchByCreator(me.id) - @supermodel.loadCollection(@prepaids, 'prepaids') + @supermodel.loadCollection(@prepaids) @users = new CocoCollection([], { url: "/db/classroom/#{classroomID}/members", model: User }) @users.comparator = (user) => user.broadName().toLowerCase() - @supermodel.loadCollection(@users, 'users') + @supermodel.loadCollection(@users) @listenToOnce @courseInstances, 'sync', @onCourseInstancesSync @sessions = new CocoCollection([], { model: LevelSession }) @@ -56,7 +56,7 @@ module.exports = class ClassroomView extends RootView @sessions = new CocoCollection([], { model: LevelSession }) for courseInstance in @courseInstances.models sessions = new CocoCollection([], { url: "/db/course_instance/#{courseInstance.id}/level_sessions", model: LevelSession }) - @supermodel.loadCollection(sessions, 'sessions', { data: { project: ['level', 'playtime', 'creator', 'changed', 'state.complete'].join(' ') } }) + @supermodel.loadCollection(sessions, { data: { project: ['level', 'playtime', 'creator', 'changed', 'state.complete'].join(' ') } }) courseInstance.sessions = sessions sessions.courseInstance = courseInstance courseInstance.sessionsByUser = {} diff --git a/app/views/courses/CoursesView.coffee b/app/views/courses/CoursesView.coffee index 3de817c28..55e0158e7 100644 --- a/app/views/courses/CoursesView.coffee +++ b/app/views/courses/CoursesView.coffee @@ -31,13 +31,13 @@ module.exports = class CoursesView extends RootView @courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance}) @courseInstances.comparator = (ci) -> return ci.get('classroomID') + ci.get('courseID') @listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded - @supermodel.loadCollection(@courseInstances, 'course_instances') + @supermodel.loadCollection(@courseInstances) @classrooms = new CocoCollection([], { url: "/db/classroom", model: Classroom }) - @supermodel.loadCollection(@classrooms, 'classrooms', { data: {memberID: me.id} }) + @supermodel.loadCollection(@classrooms, { data: {memberID: me.id} }) @courses = new CocoCollection([], { url: "/db/course", model: Course}) - @supermodel.loadCollection(@courses, 'courses') + @supermodel.loadCollection(@courses) @campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign }) - @supermodel.loadCollection(@campaigns, 'campaigns', { data: { type: 'course' }}) + @supermodel.loadCollection(@campaigns, { data: { type: 'course' }}) onCourseInstancesLoaded: -> map = {} @@ -51,7 +51,7 @@ module.exports = class CoursesView extends RootView model: LevelSession }) courseInstance.sessions.comparator = 'changed' - @supermodel.loadCollection(courseInstance.sessions, 'sessions', { data: { project: 'state.complete level.original playtime changed' }}) + @supermodel.loadCollection(courseInstance.sessions, { data: { project: 'state.complete level.original playtime changed' }}) @hocCourseInstance = @courseInstances.findWhere({hourOfCode: true}) if @hocCourseInstance diff --git a/test/app/views/core/CocoView.spec.coffee b/test/app/views/core/CocoView.spec.coffee new file mode 100644 index 000000000..7a383938e --- /dev/null +++ b/test/app/views/core/CocoView.spec.coffee @@ -0,0 +1,128 @@ +CocoView = require 'views/core/CocoView' +User = require 'models/User' + +BlandView = class BlandView extends CocoView + template: -> '' + initialize: -> + @user = new User() + @supermodel.loadModel(@user) + afterRender: -> + @$el.css({ + 'border': '2px solid black' + 'background': 'white' + 'padding': '20px' + }) + +describe 'CocoView', -> + describe 'network error handling', -> + view = null + respond = (code, json) -> + request = jasmine.Ajax.requests.mostRecent() + view.render() + request.respondWith({status: code, responseText: JSON.stringify(json or {})}) + + beforeEach -> + view = new BlandView() + + + describe 'when the server returns 401', -> + beforeEach -> respond(401) + + it 'shows a login button which opens the AuthModal', -> + button = view.$el.find('.login-btn') + expect(button.length).toBe(1) + spyOn(view, 'openModalView').and.callFake (modal) -> expect(modal.mode).toBe('login') + button.click() + expect(view.openModalView).toHaveBeenCalled() + + it 'shows a create account button which opens the AuthModal', -> + button = view.$el.find('#create-account-btn') + expect(button.length).toBe(1) + spyOn(view, 'openModalView').and.callFake (modal) -> expect(modal.mode).toBe('signup') + button.click() + expect(view.openModalView).toHaveBeenCalled() + + it 'says "Login Required"', -> + expect(view.$el.text().indexOf('Login Required')).toBeGreaterThan(-1) + + it '(demo)', -> + $('#demo-area').append(view.$el) + + + describe 'when the server returns 402', -> + + beforeEach -> respond(402) + + it 'does nothing, because it is up to the view to handle payment next steps' + + + describe 'when the server returns 403', -> + + beforeEach -> respond(403) + + it 'includes a logout button which logs out the account', -> + button = view.$el.find('#logout-btn') + expect(button.length).toBe(1) + button.click() + request = jasmine.Ajax.requests.mostRecent() + expect(request.url).toBe('/auth/logout') + + it '(demo)', -> + $('#demo-area').append(view.$el) + + + describe 'when the server returns 404', -> + + beforeEach -> respond(404) + + it 'includes one of the 404 images', -> + img = view.$el.find('#not-found-img') + expect(img.length).toBe(1) + + it '(demo)', -> + $('#demo-area').append(view.$el) + + + describe 'when the server returns 408', -> + + beforeEach -> respond(408) + + it 'includes "Server Timeout" in the header', -> + expect(view.$el.text().indexOf('Server Timeout')).toBeGreaterThan(-1) + + it 'shows a message encouraging refreshing the page or following links', -> + expect(view.$el.text().indexOf('refresh')).toBeGreaterThan(-1) + + it '(demo)', -> + $('#demo-area').append(view.$el) + + + describe 'when no connection is made', -> + + beforeEach -> + respond() + + it 'shows "Connection Failed"', -> + expect(view.$el.text().indexOf('Connection Failed')).toBeGreaterThan(-1) + + it '(demo)', -> + $('#demo-area').append(view.$el) + + + describe 'when the server returns any other number >= 400', -> + + beforeEach -> respond(9001) + + it 'includes "Unknown Error" in the header', -> + expect(view.$el.text().indexOf('Unknown Error')).toBeGreaterThan(-1) + + it 'shows a message encouraging refreshing the page or following links', -> + expect(view.$el.text().indexOf('refresh')).toBeGreaterThan(-1) + + it '(demo)', -> + $('#demo-area').append(view.$el) + + + + + \ No newline at end of file From d00f8344bef341b08872cd4b85ea311bda2e4a09 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 26 Jan 2016 11:02:11 -0800 Subject: [PATCH 14/28] Add demo controls to TestView --- app/templates/test-view.jade | 6 ++++ app/views/TestView.coffee | 34 ++++++++++++++++--- test/app/views/core/CocoView.spec.coffee | 29 ++++++++-------- .../modal/CourseVictoryModal.spec.coffee | 8 ++--- 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/app/templates/test-view.jade b/app/templates/test-view.jade index c7b850f8e..edc9cf0f8 100644 --- a/app/templates/test-view.jade +++ b/app/templates/test-view.jade @@ -11,6 +11,12 @@ ol.breadcrumb #testing-area .nav.nav-pills.nav-stacked.pull-right.well#test-nav + if view.demosOn + button#hide-demos-btn.btn.btn-danger.btn-block Hide Demos + else + button#show-demos-btn.btn.btn-info.btn-block Show Demos + + hr for child in children li(class=child.type) a(href=child.url).small diff --git a/app/views/TestView.coffee b/app/views/TestView.coffee index defeba422..a7574e759 100644 --- a/app/views/TestView.coffee +++ b/app/views/TestView.coffee @@ -1,6 +1,7 @@ RootView = require 'views/core/RootView' template = require 'templates/test-view' requireUtils = require 'lib/requireUtils' +storage = require 'core/storage' require 'vendor/jasmine-bundle' require 'tests' @@ -13,18 +14,32 @@ module.exports = TestView = class TestView extends RootView template: template reloadOnClose: true loadedFileIDs: [] + + events: + 'click #show-demos-btn': 'onClickShowDemosButton' + 'click #hide-demos-btn': 'onClickHideDemosButton' # INITIALIZE - constructor: (options, @subPath='') -> - super(options) + initialize: (options, @subPath='') -> @subPath = @subPath[1..] if @subPath[0] is '/' - + @demosOn = storage.load('demos-on') + afterInsert: -> @initSpecFiles() @render() - TestView.runTests(@specFiles) + TestView.runTests(@specFiles, @demosOn) window.runJasmine() + + # EVENTS + + onClickShowDemosButton: -> + storage.save('demos-on', true) + document.location.reload() + + onClickHideDemosButton: -> + storage.remove('demos-on') + document.location.reload() # RENDER DATA @@ -44,8 +59,17 @@ module.exports = TestView = class TestView extends RootView prefix = TEST_REQUIRE_PREFIX + @subPath @specFiles = (f for f in @specFiles when _.string.startsWith f, prefix) - @runTests: (specFiles) -> + @runTests: (specFiles, demosOn=false) -> specFiles ?= @getAllSpecFiles() + if demosOn + jasmine.demoEl = ($el) -> + $('#demo-area').append($el) + jasmine.demoModal = _.once (modal) -> + currentView.openModalView(modal) + else + jasmine.demoEl = _.noop + jasmine.demoModal = _.noop + describe 'CodeCombat Client', => jasmine.Ajax.install() beforeEach -> diff --git a/test/app/views/core/CocoView.spec.coffee b/test/app/views/core/CocoView.spec.coffee index 7a383938e..f4c8c41d0 100644 --- a/test/app/views/core/CocoView.spec.coffee +++ b/test/app/views/core/CocoView.spec.coffee @@ -26,11 +26,13 @@ describe 'CocoView', -> describe 'when the server returns 401', -> - beforeEach -> respond(401) + beforeEach -> + me.set('anonymous', true) + respond(401) it 'shows a login button which opens the AuthModal', -> button = view.$el.find('.login-btn') - expect(button.length).toBe(1) + expect(button.length).toBe(3) # including the two in the links section spyOn(view, 'openModalView').and.callFake (modal) -> expect(modal.mode).toBe('login') button.click() expect(view.openModalView).toHaveBeenCalled() @@ -44,9 +46,9 @@ describe 'CocoView', -> it 'says "Login Required"', -> expect(view.$el.text().indexOf('Login Required')).toBeGreaterThan(-1) + + it '(demo)', -> jasmine.demoEl(view.$el) - it '(demo)', -> - $('#demo-area').append(view.$el) describe 'when the server returns 402', -> @@ -58,7 +60,9 @@ describe 'CocoView', -> describe 'when the server returns 403', -> - beforeEach -> respond(403) + beforeEach -> + me.set('anonymous', false) + respond(403) it 'includes a logout button which logs out the account', -> button = view.$el.find('#logout-btn') @@ -67,8 +71,7 @@ describe 'CocoView', -> request = jasmine.Ajax.requests.mostRecent() expect(request.url).toBe('/auth/logout') - it '(demo)', -> - $('#demo-area').append(view.$el) + it '(demo)', -> jasmine.demoEl(view.$el) describe 'when the server returns 404', -> @@ -79,8 +82,7 @@ describe 'CocoView', -> img = view.$el.find('#not-found-img') expect(img.length).toBe(1) - it '(demo)', -> - $('#demo-area').append(view.$el) + it '(demo)', -> jasmine.demoEl(view.$el) describe 'when the server returns 408', -> @@ -93,8 +95,7 @@ describe 'CocoView', -> it 'shows a message encouraging refreshing the page or following links', -> expect(view.$el.text().indexOf('refresh')).toBeGreaterThan(-1) - it '(demo)', -> - $('#demo-area').append(view.$el) + it '(demo)', -> jasmine.demoEl(view.$el) describe 'when no connection is made', -> @@ -105,8 +106,7 @@ describe 'CocoView', -> it 'shows "Connection Failed"', -> expect(view.$el.text().indexOf('Connection Failed')).toBeGreaterThan(-1) - it '(demo)', -> - $('#demo-area').append(view.$el) + it '(demo)', -> jasmine.demoEl(view.$el) describe 'when the server returns any other number >= 400', -> @@ -119,8 +119,7 @@ describe 'CocoView', -> it 'shows a message encouraging refreshing the page or following links', -> expect(view.$el.text().indexOf('refresh')).toBeGreaterThan(-1) - it '(demo)', -> - $('#demo-area').append(view.$el) + it '(demo)', -> jasmine.demoEl(view.$el) diff --git a/test/app/views/play/level/modal/CourseVictoryModal.spec.coffee b/test/app/views/play/level/modal/CourseVictoryModal.spec.coffee index fda2de1eb..75ae097be 100644 --- a/test/app/views/play/level/modal/CourseVictoryModal.spec.coffee +++ b/test/app/views/play/level/modal/CourseVictoryModal.spec.coffee @@ -51,7 +51,7 @@ describe 'CourseVictoryModal', -> expect(_.size(modal.views)).toBe(1) expect(modal.views[0] instanceof ProgressView).toBe(true) - xit '(demo)', -> currentView.openModalView(modal) + it '(demo)', -> jasmine.demoModal(modal) describe 'its ProgressView', -> it 'has a next level button which navigates to the next level on click', -> @@ -93,7 +93,7 @@ describe 'CourseVictoryModal', -> button.click() expect(application.router.navigate).toHaveBeenCalled() - xit '(demo)', -> currentView.openModalView(modal) + it '(demo)', -> jasmine.demoModal(modal) describe 'given a course level with a new item', -> @@ -120,5 +120,5 @@ describe 'CourseVictoryModal', -> expect(modal.currentView instanceof NewItemView).toBe(true) modal.$el.find('#continue-btn').click() expect(modal.currentView instanceof ProgressView).toBe(true) - - xit '(demo)', -> currentView.openModalView(modal) + + it '(demo)', -> jasmine.demoModal(modal) From 3dda5710deb94503f37ba45fa50c22990734b29a Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 26 Jan 2016 11:47:39 -0800 Subject: [PATCH 15/28] Move demo border to TestView to be used generally --- app/views/TestView.coffee | 5 +++++ test/app/views/core/CocoView.spec.coffee | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/views/TestView.coffee b/app/views/TestView.coffee index a7574e759..54bfca28b 100644 --- a/app/views/TestView.coffee +++ b/app/views/TestView.coffee @@ -63,6 +63,11 @@ module.exports = TestView = class TestView extends RootView specFiles ?= @getAllSpecFiles() if demosOn jasmine.demoEl = ($el) -> + $el.css({ + 'border': '2px solid black' + 'background': 'white' + 'padding': '20px' + }) $('#demo-area').append($el) jasmine.demoModal = _.once (modal) -> currentView.openModalView(modal) diff --git a/test/app/views/core/CocoView.spec.coffee b/test/app/views/core/CocoView.spec.coffee index f4c8c41d0..a63f4fe65 100644 --- a/test/app/views/core/CocoView.spec.coffee +++ b/test/app/views/core/CocoView.spec.coffee @@ -6,12 +6,6 @@ BlandView = class BlandView extends CocoView initialize: -> @user = new User() @supermodel.loadModel(@user) - afterRender: -> - @$el.css({ - 'border': '2px solid black' - 'background': 'white' - 'padding': '20px' - }) describe 'CocoView', -> describe 'network error handling', -> From 4ef5f40fa1e6fcbb6d2e72bd72a4e7d0acdc5ef0 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 26 Jan 2016 13:27:05 -0800 Subject: [PATCH 16/28] Network error handling fixes * Make work there are multiple network requests and only one fails * Test for i18n attributes instead of English translations so Travis works * Various PR changes --- app/locale/en.coffee | 53 +---------------- app/models/SuperModel.coffee | 3 +- app/templates/base.jade | 2 +- app/templates/core/loading-error.jade | 74 +++++++++++++----------- app/views/core/CocoView.coffee | 1 + test/app/views/core/CocoView.spec.coffee | 53 +++++++++++++---- 6 files changed, 88 insertions(+), 98 deletions(-) diff --git a/app/locale/en.coffee b/app/locale/en.coffee index a588588b5..6b14b20f7 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -1358,8 +1358,9 @@ loading_error: could_not_load: "Error loading from server" connection_failure: "Connection Failed" # {change} + connection_failure_desc: "It doesn’t look like you’re connected to the internet! Check your network connection and then reload this page." login_required: "Login Required" - login_required_desc: " You need to be logged in to access this page." + login_required_desc: "You need to be logged in to access this page." unauthorized: "You need to be signed in. Do you have cookies disabled?" forbidden: "Forbidden" # {change} forbidden_desc: "Oh no, there’s nothing we can show you here! Make sure you’re logged into the correct account, or visit one of the links below to get back to programming!" @@ -1375,65 +1376,15 @@ general_desc: "Something went wrong, and it’s probably our fault. Try waiting a bit and then refreshing the page, or visit one of the following links to get back to programming!" resources: - sessions: "Sessions" - your_sessions: "Your Sessions" level: "Level" - social_network_apis: "Social Network APIs" - facebook_status: "Facebook Status" - facebook_friends: "Facebook Friends" - facebook_friend_sessions: "Facebook Friend Sessions" - gplus_friends: "G+ Friends" - gplus_friend_sessions: "G+ Friend Sessions" - leaderboard: "Leaderboard" - user_schema: "User Schema" - user_profile: "User Profile" patch: "Patch" patches: "Patches" - patched_model: "Source Document" - model: "Model" system: "System" systems: "Systems" component: "Component" components: "Components" - thang: "Thang" - thangs: "Thangs" - level_session: "Your Session" - opponent_session: "Opponent Session" - article: "Article" - user_names: "User Names" - thang_names: "Thang Names" - files: "Files" - top_simulators: "Top Simulators" - source_document: "Source Document" - document: "Document" - sprite_sheet: "Sprite Sheet" - employers: "Employers" - candidates: "Candidates" - candidate_sessions: "Candidate Sessions" - user_remark: "User Remark" - user_remarks: "User Remarks" - versions: "Versions" - items: "Items" hero: "Hero" - heroes: "Heroes" - achievement: "Achievement" - clas: "CLAs" - play_counts: "Play Counts" - feedback: "Feedback" - payment_info: "Payment Info" campaigns: "Campaigns" - poll: "Poll" - user_polls_record: "Poll Voting History" - course: "Course" - courses: "Courses" - course_instance: "Course Instance" - course_instances: "Course Instances" - classroom: "Classroom" - classrooms: "Classrooms" - clan: "Clan" - clans: "Clans" - members: "Members" - users: "Users" concepts: advanced_strings: "Advanced Strings" diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee index 097335c31..3da8f37e9 100644 --- a/app/models/SuperModel.coffee +++ b/app/models/SuperModel.coffee @@ -140,7 +140,7 @@ module.exports = class SuperModel extends Backbone.Model # Tracking resources being loaded for this supermodel finished: -> - return @progress is 1.0 or not @denom + return (@progress is 1.0) or (not @denom) or @failed addModelResource: (modelOrCollection, name, fetchOptions, value=1) -> # Deprecating name. Handle if name is not included @@ -203,6 +203,7 @@ module.exports = class SuperModel extends Backbone.Model onResourceFailed: (r) -> return unless @resources[r.rid] + @failed = true @trigger('failed', resource: r) r.clean() diff --git a/app/templates/base.jade b/app/templates/base.jade index af84fb03f..c8bd3c72b 100644 --- a/app/templates/base.jade +++ b/app/templates/base.jade @@ -39,7 +39,7 @@ block header li a(href="/account/prepaid", data-i18n="account.prepaid_codes") Prepaid Codes li - a#logout-button(data-i18n="login.log_out") + a#logout-button(data-i18n="login.log_out") else button.btn.btn-sm.btn-primary.header-font.signup-button(data-i18n="login.sign_up") diff --git a/app/templates/core/loading-error.jade b/app/templates/core/loading-error.jade index e36ed3004..7674ba266 100644 --- a/app/templates/core/loading-error.jade +++ b/app/templates/core/loading-error.jade @@ -31,7 +31,7 @@ else if !jqxhr.status h1(data-i18n="loading_error.connection_failure") - p It doesn’t look like you’re connected to the internet! Check your network connection and then reload this page. + p(data-i18n="loading_error.connection_failure_desc") else if jqxhr.status === 408 @@ -49,41 +49,49 @@ #links-row.row .col-sm-3 - strong(data-i18n="cmomon.help") Help - br - a(href="/", data-i18n="nav.home") - br - a(href=view.forumLink(), data-i18n="nav.forum") - br - a(tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact") - br - a(href='/community', data-i18n="nav.community") + ul.list-unstyled + li + strong(data-i18n="common.help") + li + a(href="/", data-i18n="nav.home") + li + a(href=view.forumLink(), data-i18n="nav.forum") + li + a(tabindex=-1, data-toggle="coco-modal", data-target="core/ContactModal", data-i18n="nav.contact") + li + a(href='/community', data-i18n="nav.community") .col-sm-3 - strong(data-i18n="courses.students") - br - a(href="/courses/students", data-i18n="nav.learn_to_code") - if me.isAnonymous() - br - a.login-btn(data-i18n="login.log_in") - br - a(href="/courses", data-i18n="courses.join_class") + ul.list-unstyled + li + strong(data-i18n="courses.students") + li + a(href="/courses/students", data-i18n="nav.learn_to_code") + if me.isAnonymous() + li + a.login-btn(data-i18n="login.log_in") + li + a(href="/courses", data-i18n="courses.join_class") .col-sm-3 - strong(data-i18n="nav.teachers") - br - a(href="/schools", data-i18n="about.why_codecombat") - if me.isAnonymous() - br - a.login-btn(data-i18n="login.log_in") - br - a(href="/courses/teachers", data-i18n="nav.create_a_class") + ul.list-unstyled + li + strong(data-i18n="nav.teachers") + li + a(href="/schools", data-i18n="about.why_codecombat") + if me.isAnonymous() + li + a.login-btn(data-i18n="login.log_in") + li + a(href="/courses/teachers", data-i18n="nav.create_a_class") .col-sm-3 - strong(data-i18n="nav.other") - br - a(href="http://blog.codecombat.com/", data-i18n="nav.blog") - br - a(href="https://www.facebook.com/codecombat", data-i18n="nav.facebook") - br - a(href="https://twitter.com/codecombat", data-i18n="nav.twitter") \ No newline at end of file + ul.list-unstyled + li + strong(data-i18n="nav.other") + li + a(href="http://blog.codecombat.com/", data-i18n="nav.blog") + li + a(href="https://www.facebook.com/codecombat", data-i18n="nav.facebook") + li + a(href="https://twitter.com/codecombat", data-i18n="nav.twitter") \ No newline at end of file diff --git a/app/views/core/CocoView.coffee b/app/views/core/CocoView.coffee index a76479681..fd6e7f0d5 100644 --- a/app/views/core/CocoView.coffee +++ b/app/views/core/CocoView.coffee @@ -158,6 +158,7 @@ module.exports = class CocoView extends Backbone.View # Error handling for loading onResourceLoadFailed: (e) -> r = e.resource + @stopListening @supermodel return if r.jqxhr?.status is 402 # payment-required failures are handled separately @showError(r.jqxhr) diff --git a/test/app/views/core/CocoView.spec.coffee b/test/app/views/core/CocoView.spec.coffee index a63f4fe65..50b03c777 100644 --- a/test/app/views/core/CocoView.spec.coffee +++ b/test/app/views/core/CocoView.spec.coffee @@ -2,22 +2,51 @@ CocoView = require 'views/core/CocoView' User = require 'models/User' BlandView = class BlandView extends CocoView - template: -> '' + template: -> + return if @specialMessage then '
custom message
' else '
normal message
' + initialize: -> - @user = new User() - @supermodel.loadModel(@user) + @user1 = new User({_id: _.uniqueId()}) + @supermodel.loadModel(@user1) + @user2 = new User({_id: _.uniqueId()}) + @supermodel.loadModel(@user2) + + onResourceLoadFailed: (e) -> + resource = e.resource + if resource.jqxhr.status is 400 and resource.model is @user1 + @specialMessage = true + @render() + else + super(arguments...) + describe 'CocoView', -> describe 'network error handling', -> view = null - respond = (code, json) -> - request = jasmine.Ajax.requests.mostRecent() + respond = (code, index=0) -> view.render() - request.respondWith({status: code, responseText: JSON.stringify(json or {})}) + requests = jasmine.Ajax.requests.all() + requests[index].respondWith({status: code, responseText: JSON.stringify({})}) beforeEach -> view = new BlandView() + + describe 'when the view overrides onResourceLoadFailed', -> + beforeEach -> + view.render() + expect(view.$('#content').hasClass('hidden')).toBe(true) + respond(400) + + it 'can show a custom message for a given error and model', -> + expect(view.$('#content').hasClass('hidden')).toBe(false) + expect(view.$('#content').text()).toBe('custom message') + respond(200, 1) + expect(view.$('#content').hasClass('hidden')).toBe(false) + expect(view.$('#content').text()).toBe('custom message') + + it '(demo)', -> jasmine.demoEl(view.$el) + describe 'when the server returns 401', -> beforeEach -> @@ -39,7 +68,7 @@ describe 'CocoView', -> expect(view.openModalView).toHaveBeenCalled() it 'says "Login Required"', -> - expect(view.$el.text().indexOf('Login Required')).toBeGreaterThan(-1) + expect(view.$('[data-i18n="loading_error.login_required"]').length).toBeTruthy() it '(demo)', -> jasmine.demoEl(view.$el) @@ -84,10 +113,10 @@ describe 'CocoView', -> beforeEach -> respond(408) it 'includes "Server Timeout" in the header', -> - expect(view.$el.text().indexOf('Server Timeout')).toBeGreaterThan(-1) + expect(view.$('[data-i18n="loading_error.timeout"]').length).toBeTruthy() it 'shows a message encouraging refreshing the page or following links', -> - expect(view.$el.text().indexOf('refresh')).toBeGreaterThan(-1) + expect(view.$('[data-i18n="loading_error.general_desc"]').length).toBeTruthy() it '(demo)', -> jasmine.demoEl(view.$el) @@ -98,7 +127,7 @@ describe 'CocoView', -> respond() it 'shows "Connection Failed"', -> - expect(view.$el.text().indexOf('Connection Failed')).toBeGreaterThan(-1) + expect(view.$('[data-i18n="loading_error.connection_failure"]').length).toBeTruthy() it '(demo)', -> jasmine.demoEl(view.$el) @@ -108,10 +137,10 @@ describe 'CocoView', -> beforeEach -> respond(9001) it 'includes "Unknown Error" in the header', -> - expect(view.$el.text().indexOf('Unknown Error')).toBeGreaterThan(-1) + expect(view.$('[data-i18n="loading_error.unknown"]').length).toBeTruthy() it 'shows a message encouraging refreshing the page or following links', -> - expect(view.$el.text().indexOf('refresh')).toBeGreaterThan(-1) + expect(view.$('[data-i18n="loading_error.general_desc"]').length).toBeTruthy() it '(demo)', -> jasmine.demoEl(view.$el) From dd6f9711a4ab7a32727bb4fed30e9e2d7d8dcac9 Mon Sep 17 00:00:00 2001 From: Philippe Rigovanov Date: Wed, 27 Jan 2016 23:15:52 +0300 Subject: [PATCH 17/28] Update ru.coffee --- app/locale/ru.coffee | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/locale/ru.coffee b/app/locale/ru.coffee index 76b6f2891..84a1d22fd 100644 --- a/app/locale/ru.coffee +++ b/app/locale/ru.coffee @@ -908,7 +908,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi next_course: "Следующий курс" coming_soon1: "Скоро появится" # coming_soon2: "We are hard at work making more courses for you!" -# available_levels: "Available Levels" + available_levels: "Доступные уровни" welcome_to_courses: "Искатели приключений, добро пожаловать на курсы!" ready_to_play: "Готовы приступить к игре?" start_new_game: "Начать новую игру" @@ -930,19 +930,19 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi view_class: "смотреть класс" view_levels: "смотреть уровни" join_class: "Присоединиться к классу" -# ask_teacher_for_code: "Ask your teacher if you have a CodeCombat class code! If so, enter it below:" -# enter_c_code: "" -# join: "Присоединиться" + ask_teacher_for_code: "Спросите у вашего учителя код класса для CodeCombat! Введите его ниже:" + enter_c_code: "<Введите Код Класса>" + join: "Присоединиться" # joining: "Joining class" course_complete: "Курс завершён" play_arena: "Играть Арену" -# start: "Старт" -# last_level: "Последний уровень" + start: "Старт" + last_level: "Последний уровень" welcome_to_hoc: "Искатели приключений, добро пожаловать на Час кода!" -# logged_in_as: "Logged in as:" -# not_you: "Не вы?" -# welcome_back: "Привет, искатель приключений, добро пожаловать!" -# continue_playing: "Continue Playing" + logged_in_as: "Вошли как:" + not_you: "Не вы?" + welcome_back: "Привет, искатель приключений, добро пожаловать!" + continue_playing: "Играть Дальше" more_options: "Другие варианты:" option1_header: "Вариант 1: Пригласить учеников по email" # option1_body: "Students will automatically be sent an invitation to join this class, and will need to create an account with a username and password." @@ -952,7 +952,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi # option3_body: "Give students the following passcode to enter along with an email address, username and password when they create an account." # thank_you_pref: "Thank you for your purchase! You can now assign" # thank_you_suff: "more students to paid courses." -# return_to_class: "Return to classroom" + return_to_class: "Вернуться в класс" return_to_course_man: "Вернуться к управлению курсом." # students_not_enrolled: "students not enrolled" total_all_classes: "Общее по всем классам" @@ -968,7 +968,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi # removing_user: "Removing user" # to_join_ask: "To join a class, ask your teacher for an unlock code." # join_this_class: "Join Class" -# enter_here: "<Ввести здесь код разблокировки>" + enter_here: "<Ввести здесь код разблокировки>" # successfully_joined: "Successfully joined" # click_to_start: "Click here to start taking" my_courses: "Мои курсы" @@ -976,7 +976,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi # use_school_email: "use your school email if you have one" # unique_name: "a unique name no one has chosen" # pick_something: "pick something you can remember" -# class_code: "Class Code" + class_code: "Код класса" optional_ask: "Как вариант - попросите учителя дать вам какой-нибудь!" optional_school: "Не обязательное - в какую школу вы ходите?" start_playing: "Начать играть" @@ -1000,17 +1000,17 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi # additional_resources_4_mid: "Schools Page" # additional_resources_4_suff: "to learn more about CodeCombat's classroom offerings." your_classes: "Ваши классы" -# no_classes: "No classes yet!" + no_classes: "Ещё нет классов!" create_new_class1: "создать новый класс" -# available_courses: "Available Courses" + available_courses: "Доступные курсы" # unused_enrollments: "Unused enrollments available:" # students_access: "All students get access to Introduction to Computer Science for free. One enrollment per student is required to assign them to paid CodeCombat courses. A single student does not need multiple enrollments to access all paid courses." -# active_courses: "active courses" + active_courses: "активные курсы" no_students: "Ещё нет учеников!" add_students1: "добавить учеников" view_edit: "смотреть/редактировать" students_enrolled: "учеников зачислено" -# length: "Length:" + length: "Длительность:" classes: archmage_title: "Архимаг" From 0d9b7a634b004a8f2d6ed007cb1be777d6b77e60 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Thu, 28 Jan 2016 09:25:30 -0800 Subject: [PATCH 18/28] Add homepage to Mixpanel page view tracking --- app/core/Tracker.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/Tracker.coffee b/app/core/Tracker.coffee index 988313353..3679e664e 100644 --- a/app/core/Tracker.coffee +++ b/app/core/Tracker.coffee @@ -93,7 +93,7 @@ module.exports = class Tracker ga? 'send', 'pageview', url # Mixpanel - mixpanelIncludes = ['courses', 'courses/purchase', 'courses/teachers', 'courses/students', 'schools', 'teachers', 'teachers/freetrial'] + mixpanelIncludes = ['', 'courses', 'courses/purchase', 'courses/teachers', 'courses/students', 'schools', 'teachers', 'teachers/freetrial'] mixpanel.track('page viewed', 'page name' : name, url : url) if name in mixpanelIncludes trackEvent: (action, properties={}, includeIntegrations=[]) => From 94c0c27a03d7e9710ed1253b5e52b0501c3c9560 Mon Sep 17 00:00:00 2001 From: Philippe Rigovanov Date: Fri, 29 Jan 2016 00:45:16 +0300 Subject: [PATCH 19/28] Update ru.coffee --- app/locale/ru.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/locale/ru.coffee b/app/locale/ru.coffee index 84a1d22fd..8c372138e 100644 --- a/app/locale/ru.coffee +++ b/app/locale/ru.coffee @@ -1256,7 +1256,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi league: "Лига" red_ai: "CPU красного" # "Red AI Wins", at end of multiplayer match playback blue_ai: "CPU синего" - wins: "Победы" # At end of multiplayer match playback + wins: "Побеждает" # At end of multiplayer match playback humans: "Красный" # Ladder page display team name ogres: "Синий" From ce05659ab508c1f7a7faa97be8bbe5762f2aff6a Mon Sep 17 00:00:00 2001 From: Philippe Rigovanov Date: Fri, 29 Jan 2016 01:26:24 +0300 Subject: [PATCH 20/28] Update ru.coffee --- app/locale/ru.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/locale/ru.coffee b/app/locale/ru.coffee index 8c372138e..4fb6c6cd8 100644 --- a/app/locale/ru.coffee +++ b/app/locale/ru.coffee @@ -1257,8 +1257,8 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi red_ai: "CPU красного" # "Red AI Wins", at end of multiplayer match playback blue_ai: "CPU синего" wins: "Побеждает" # At end of multiplayer match playback - humans: "Красный" # Ladder page display team name - ogres: "Синий" + humans: "Красных" # Ladder page display team name + ogres: "Синих" user: stats: "Характеристики" From f657669dbc6ab686e9ed3fe77b92db54f8eed706 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Thu, 21 Jan 2016 14:41:45 -0800 Subject: [PATCH 21/28] Extend forms * formToObject handles checkboxes and radio buttons * applyErrorsToForms handles required fields * setErrorToField sets helpblock according to new forms best practices: before input and any help blocks --- app/core/forms.coffee | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/app/core/forms.coffee b/app/core/forms.coffee index 5782c8c51..1e58f3efa 100644 --- a/app/core/forms.coffee +++ b/app/core/forms.coffee @@ -1,22 +1,35 @@ -module.exports.formToObject = (el) -> +module.exports.formToObject = ($el, options) -> + options = _.extend({ trim: true, ignoreEmptyString: true }, options) obj = {} - inputs = $('input', el).add('textarea', el) + inputs = $('input, textarea, select', $el) for input in inputs input = $(input) continue unless name = input.attr('name') - obj[name] = input.val() - obj[name] = obj[name].trim() if obj[name]?.trim - + if input.attr('type') is 'checkbox' + obj[name] ?= [] + if input.is(':checked') + obj[name].push(input.val()) + else if input.attr('type') is 'radio' + continue unless input.is('checked') + obj[name] = input.val() + else + value = input.val() or '' + value = _.string.trim(value) if options.trim + if value or (not options.ignoreEmptyString) + obj[name] = value obj module.exports.applyErrorsToForm = (el, errors, warning=false) -> errors = [errors] if not $.isArray(errors) missingErrors = [] for error in errors - if error.dataPath + if error.code is tv4.errorCodes.OBJECT_REQUIRED + prop = _.last(_.string.words(error.message)) # hack + message = 'Required field' + + else if error.dataPath prop = error.dataPath[1..] - console.log prop message = error.message else @@ -35,8 +48,13 @@ module.exports.setErrorToField = setErrorToField = (el, message, warning=false) return console.error el, " did not contain a form group, so couldn't show message:", message kind = if warning then 'warning' else 'error' + afterEl = $(formGroup.find('.help-block, .form-control, input, select, textarea')[0]) formGroup.addClass "has-#{kind}" - formGroup.append $("#{message}") + helpBlock = $("#{message}") + if afterEl.length + afterEl.before helpBlock + else + formGroup.append helpBlock module.exports.setErrorToProperty = setErrorToProperty = (el, property, message, warning=false) -> input = $("[name='#{property}']", el) From e62e49754352e46f195bd9b33a0cd2a3489197e5 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Thu, 21 Jan 2016 14:41:57 -0800 Subject: [PATCH 22/28] Replace TeachersFreeTrialView with RequestQuoteView --- app/collections/TrialRequests.coffee | 11 ++ app/core/Router.coffee | 3 +- app/core/Tracker.coffee | 2 +- app/locale/en.coffee | 31 ++++ app/models/TrialRequest.coffee | 14 ++ app/styles/request-quote-view.sass | 33 ++++ app/templates/admin/trial-requests.jade | 17 +- app/templates/core/auth.jade | 4 +- .../courses/teacher-courses-view.jade | 2 +- app/templates/request-quote-view.jade | 150 ++++++++++++++++++ app/templates/sales-view.jade | 6 +- app/views/RequestQuoteView.coffee | 96 +++++++++++ app/views/SalesView.coffee | 2 +- app/views/core/AuthModal.coffee | 1 + 14 files changed, 355 insertions(+), 17 deletions(-) create mode 100644 app/collections/TrialRequests.coffee create mode 100644 app/styles/request-quote-view.sass create mode 100644 app/templates/request-quote-view.jade create mode 100644 app/views/RequestQuoteView.coffee diff --git a/app/collections/TrialRequests.coffee b/app/collections/TrialRequests.coffee new file mode 100644 index 000000000..25467e301 --- /dev/null +++ b/app/collections/TrialRequests.coffee @@ -0,0 +1,11 @@ +CocoCollection = require 'collections/CocoCollection' +TrialRequest = require 'models/TrialRequest' + +module.exports = class TrialRequestCollection extends CocoCollection + url: '/db/trial.request' + model: TrialRequest + + fetchOwn: (options) -> + options = _.extend({data: {}}, options) + options.url = _.result(@, 'url') + '/-/own' + @fetch(options) diff --git a/app/core/Router.coffee b/app/core/Router.coffee index e4025a118..77ae180a5 100644 --- a/app/core/Router.coffee +++ b/app/core/Router.coffee @@ -121,7 +121,8 @@ module.exports = class CocoRouter extends Backbone.Router 'schools': go('SalesView') 'teachers': go('TeachersView') - 'teachers/freetrial': go('TeachersFreeTrialView') + 'teachers/freetrial': go('RequestQuoteView') + 'teachers/quote': go('RequestQuoteView') 'test(/*subpath)': go('TestView') diff --git a/app/core/Tracker.coffee b/app/core/Tracker.coffee index 3679e664e..7f59242a7 100644 --- a/app/core/Tracker.coffee +++ b/app/core/Tracker.coffee @@ -93,7 +93,7 @@ module.exports = class Tracker ga? 'send', 'pageview', url # Mixpanel - mixpanelIncludes = ['', 'courses', 'courses/purchase', 'courses/teachers', 'courses/students', 'schools', 'teachers', 'teachers/freetrial'] + mixpanelIncludes = ['', 'courses', 'courses/purchase', 'courses/teachers', 'courses/students', 'schools', 'teachers', 'teachers/freetrial', 'teachers/quote'] mixpanel.track('page viewed', 'page name' : name, url : url) if name in mixpanelIncludes trackEvent: (action, properties={}, includeIntegrations=[]) => diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 6b14b20f7..1b6ce98ef 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -622,6 +622,37 @@ hear_about: "How did you hear about CodeCombat?" fill_fields: "Please fill out all fields." thanks: "Thanks! We'll send you setup instructions shortly." + + teachers_quote: + name: "Quote Form" + title: "Request a Quote" + subtitle: "Get CodeCombat in your classroom, club, school or district!" + phone_number: "Phone number" + phone_number_help: "Where can we reach you during the workday?" + role_label: "Your role" + role_help: "Select your primary role." + tech_coordinator: "Technology coordinator" + advisor: "Advisor" + principal: "Principal" + superintendent: "Superintendent" + parent: "Parent" + organization_label: "Name of School/District" + city: "City" + state: "State" + country: "Country" + num_students_help: "How many do you anticipate enrolling in CodeCombat?" + education_level_label: "Education Level of Students" + education_level_help: "Choose as many as apply." + elementary_school: "Elementary School" + high_school: "High School" + please_explain: "(please explain)" + middle_school: "Middle School" + college_plus: "College or higher" + anything_else: "Anything else we should know?" + thanks_header: "Thanks for requesting a quote!" + thanks_p: "We'll be in touch soon. Questions? Email us:" + thanks_anon: "Login or sign up with your account below to access your two free enrollments (we’ll notify you by email when they have been approved, which usually takes less than 48 hours). As always, the first hour of content is free for an unlimited number of students." + thanks_logged_in: "Your two free enrollments are pending approval. We’ll notify you by email when they have been approved (usually within 48 hours). As always, the first hour of content is free for an unlimited number of students." versions: save_version_title: "Save New Version" diff --git a/app/models/TrialRequest.coffee b/app/models/TrialRequest.coffee index 5ea9cbeb4..d408c9d03 100644 --- a/app/models/TrialRequest.coffee +++ b/app/models/TrialRequest.coffee @@ -5,3 +5,17 @@ module.exports = class TrialRequest extends CocoModel @className: 'TrialRequest' @schema: schema urlRoot: '/db/trial.request' + + nameString: -> + props = @get('properties') + values = _.filter(_.at(props, 'name', 'email')) + return values.join(' / ') + + locationString: -> + props = @get('properties') + values = _.filter(_.at(props, 'city', 'state', 'country')) + return values.join(' ') + + educationLevelString: -> + levels = @get('properties').educationLevel or [] + return levels.join(', ') diff --git a/app/styles/request-quote-view.sass b/app/styles/request-quote-view.sass new file mode 100644 index 000000000..ce0750610 --- /dev/null +++ b/app/styles/request-quote-view.sass @@ -0,0 +1,33 @@ +#request-quote-view + + label + margin-bottom: 2px + + .row + margin: 10px 0 + + .help-block + margin: 0 + + p + margin: 0 0 20px + + hr + margin: 30px 0 + + .checkbox, .checkbox-inline + margin: 0 + + #anything-else-row + margin: 50px 0 20px + + #other-education-level-input + display: inline-block + width: 200px + margin-left: 5px + + #submit-request-btn + margin-left: 10px + + #login-btn + margin-right: 10px \ No newline at end of file diff --git a/app/templates/admin/trial-requests.jade b/app/templates/admin/trial-requests.jade index b41c6461f..718848df9 100644 --- a/app/templates/admin/trial-requests.jade +++ b/app/templates/admin/trial-requests.jade @@ -18,9 +18,9 @@ block content th Applicant th School th Location - th Age + th Age / Level th Students - th How Found + th How Found / Notes th Status tbody - var numReviewed = 0 @@ -35,13 +35,14 @@ block content td.reviewed if trialRequest.get('reviewDate') span= trialRequest.get('reviewDate').substring(0, 10) + - var props = trialRequest.get('properties') td - a(href="/user/#{trialRequest.get('applicant')}")= trialRequest.get('properties').email - td= trialRequest.get('properties').school - td= trialRequest.get('properties').location - td= trialRequest.get('properties').age - td= trialRequest.get('properties').numStudents - td= trialRequest.get('properties').heardAbout + a(href="/user/#{trialRequest.get('applicant')}")= trialRequest.nameString() + td= props.school || props.organization + td= props.location || trialRequest.locationString() + td= props.age || trialRequest.educationLevelString() + td= props.numStudents + td= props.heardAbout || props.notes td.status-cell if trialRequest.get('status') === 'submitted' button.btn.btn-xs.btn-success.btn-approve(data-trial-request-id=trialRequest.id) Approve diff --git a/app/templates/core/auth.jade b/app/templates/core/auth.jade index 27dd8894a..b3a39e769 100644 --- a/app/templates/core/auth.jade +++ b/app/templates/core/auth.jade @@ -50,7 +50,7 @@ | : .input-border if me.get('name') - input#name.input-large.form-control(name="name", type="text", value="#{me.get('name')}") + input#name.input-large.form-control(name="name", type="text", value=me.get('name')) else input#name.input-large.form-control(name="name", type="text", value="", placeholder="e.g. Alex W the Skater") .col-md-6 @@ -62,7 +62,7 @@ span(data-i18n="signup.optional") optional | ): .input-border - input#school-input.input-large.form-control(name="schoolName", data-i18n="[placeholder]signup.school_name_placeholder") + input#school-input.input-large.form-control(name="schoolName", data-i18n="[placeholder]signup.school_name_placeholder", value=formValues.schoolName || '') .form-group.checkbox label.control-label(for="subscribe") .input-border diff --git a/app/templates/courses/teacher-courses-view.jade b/app/templates/courses/teacher-courses-view.jade index 3d656a146..cd678dbec 100644 --- a/app/templates/courses/teacher-courses-view.jade +++ b/app/templates/courses/teacher-courses-view.jade @@ -43,7 +43,7 @@ block content span.spl(data-i18n="courses.educator_wiki_suff") li span.spr(data-i18n="courses.additional_resources_2_pref") - a(href='/teachers/freetrial', data-i18n="teachers_survey.title") + a(href='/teachers/quote', data-i18n="teachers_quote.name") span.spl(data-i18n="courses.additional_resources_2_suff") li span.spr(data-i18n="courses.additional_resources_3_pref") diff --git a/app/templates/request-quote-view.jade b/app/templates/request-quote-view.jade new file mode 100644 index 000000000..1592919e2 --- /dev/null +++ b/app/templates/request-quote-view.jade @@ -0,0 +1,150 @@ +extends /templates/base + +block content + form.form(class=view.trialRequest.isNew() ? '' : 'hide') + h1.text-center(data-i18n="teachers_quote.title") + p.text-center(data-i18n="teachers_quote.subtitle") + + .row + .col-sm-offset-2.col-sm-4 + .form-group + label.control-label(data-i18n="general.name") + input.form-control(name="name") + + .col-sm-4 + .form-group + label.control-label(data-i18n="general.email") + input.form-control(name="email") + + .row + .col-sm-offset-2.col-sm-4 + .form-group + label.control-label + span(data-i18n="teachers_quote.phone_number") + span.spl.text-muted(data-i18n="signup.optional") + .help-block + em.text-info(data-i18n="teachers_quote.phone_number_help") + input.form-control(name="phoneNumber") + + .col-sm-4 + .form-group + label.control-label(data-i18n="teachers_quote.role_label") + .help-block + em.text-info(data-i18n="teachers_quote.role_help") + select.form-control(name="role") + option + option(data-i18n="courses.teacher", value="Teacher") + option(data-i18n="teachers_quote.tech_coordinator", value="Technology coordinator") + option(data-i18n="teachers_quote.advisor", value="Advisor") + option(data-i18n="teachers_quote.principal", value="Principal") + option(data-i18n="teachers_quote.superintendent", value="Superintendent") + option(data-i18n="teachers_quote.parent", value="Parent") + + .row + .col-sm-offset-2.col-sm-8 + hr + + .row + .col-sm-offset-2.col-sm-4 + .form-group + label.control-label(data-i18n="teachers_quote.organization_label") + input.form-control(name="organization") + + .col-sm-4 + .form-group + label.control-label(data-i18n="teachers_quote.city") + input.form-control(name="city") + + .row + .col-sm-offset-2.col-sm-4 + .form-group + label.control-label(data-i18n="teachers_quote.state") + input.form-control(name="state") + + .col-sm-4 + .form-group + label.control-labellabel.control-label(data-i18n="teachers_quote.country") + input.form-control(name="country") + + .row + .col-sm-offset-2.col-sm-8 + hr + + .row + .col-sm-offset-2.col-sm-5 + .form-group + label.control-label(data-i18n="courses.number_students") + .help-block + em.text-info(data-i18n="teachers_quote.num_students_help") + select.form-control(name="numStudents") + option + option 1-10 + option 11-50 + option 51-100 + option 101-200 + option 201-500 + option 501-1000 + option 1000+ + + .form-group + + .row + .col-sm-offset-2.col-sm-4 + label.control-label(data-i18n="teachers_quote.education_level_label") + .help-block + em.text-info(data-i18n="teachers_quote.education_level_help") + + .row + .col-sm-offset-2.col-sm-2 + label.control-label.checkbox + input(type="checkbox" name="educationLevel" value="Elementary") + span(data-i18n="teachers_quote.elementary_school") + .col-sm-2 + label.control-label.checkbox + input(type="checkbox" name="educationLevel" value="High") + span(data-i18n="teachers_quote.high_school") + .col-sm-6 + .checkbox-inline + label.control-label + input#other-education-level-checkbox(type="checkbox") + span(data-i18n="nav.other") + br + span(data-i18n="teachers_quote.please_explain") + input#other-education-level-input.form-control + + .row + .col-sm-offset-2.col-sm-2 + label.control-label.checkbox + input(type="checkbox" name="educationLevel" value="Middle") + span(data-i18n="teachers_quote.middle_school") + .col-sm-2 + label.control-label.checkbox + input(type="checkbox" name="educationLevel" value="College+") + span(data-i18n="teachers_quote.college_plus") + + #anything-else-row.row + .col-sm-offset-2.col-sm-8 + label.control-label + span(data-i18n="teachers_quote.anything_else") + span.spl.text-muted(data-i18n="signup.optional") + + textarea.form-control(rows=8, name="notes") + + #buttons-row.row.text-center + input#submit-request-btn.btn.btn-primary(type="submit" data-i18n="[value]common.send") + + + #form-submit-success.text-center(class=view.trialRequest.isNew() ? 'hide' : '') + h1.text-center(data-i18n="teachers_quote.thanks_header") + p.text-center + span.spr(data-i18n="teachers_quote.thanks_p") + a.spl(href="mailto:team@codecombat.com") team@codecombat.com + + if me.isAnonymous() + p.text-center(data-i18n="teachers_quote.thanks_anon") + + p.text-center + button#login-btn.btn.btn-info(data-i18n="login.log_in") + button#signup-btn.btn.btn-info(data-i18n="login.sign_up") + else + p.text-center(data-i18n="teachers_quote.thanks_logged_in") \ No newline at end of file diff --git a/app/templates/sales-view.jade b/app/templates/sales-view.jade index 068c8b3a3..11ca74341 100644 --- a/app/templates/sales-view.jade +++ b/app/templates/sales-view.jade @@ -7,9 +7,9 @@ block content br br .text-right - button.btn-contact-us(href='/teachers/freetrial') + button.btn-contact-us(href='/teachers/quote') img(src='/images/pages/sales/chat_icon.png') - div contact us for pricing and a free trial + div contact us for a quote table tr @@ -216,5 +216,5 @@ block content br p.text-center - button.btn-contact-us contact us for a free trial + button.btn-contact-us contact us for a quote br diff --git a/app/views/RequestQuoteView.coffee b/app/views/RequestQuoteView.coffee new file mode 100644 index 000000000..bdcb92a71 --- /dev/null +++ b/app/views/RequestQuoteView.coffee @@ -0,0 +1,96 @@ +RootView = require 'views/core/RootView' +forms = require 'core/forms' +TrialRequest = require 'models/TrialRequest' +TrialRequests = require 'collections/TrialRequests' +AuthModal = require 'views/core/AuthModal' + +formSchema = { + type: 'object' + required: ['name', 'email', 'organization', 'role', 'numStudents'] + properties: + name: { type: 'string', minLength: 1 } + email: { type: 'string', format: 'email' } + phoneNumber: { type: 'string' } + role: { type: 'string' } + organization: { type: 'string' } + city: { type: 'string' } + state: { type: 'string' } + country: { type: 'string' } + numStudents: { type: 'string' } + educationLevel: { + type: 'array' + items: { type: 'string' } + } + notes: { type: 'string' } +} + +module.exports = class RequestQuoteView extends RootView + id: 'request-quote-view' + template: require 'templates/request-quote-view' + + events: + 'submit form': 'onSubmitForm' + 'click #login-btn': 'onClickLoginButton' + 'click #signup-btn': 'onClickSignupButton' + + initialize: -> + @trialRequest = new TrialRequest() + @trialRequests = new TrialRequests() + @trialRequests.fetchOwn() + @supermodel.loadCollection(@trialRequests) + + onLoaded: -> + if @trialRequests.size() + @trialRequest = @trialRequests.first() + + super() + + onSubmitForm: (e) -> + e.preventDefault() + form = @$('form') + attrs = forms.formToObject(form) + if @$('#other-education-level-checkbox').is(':checked') + attrs.educationLevel.push(@$('#other-education-level-input').val()) + forms.clearFormAlerts(form) + result = tv4.validateMultiple(attrs, formSchema) + if not result.valid + return forms.applyErrorsToForm(form, result.errors) + if not /^.+@.+\..+$/.test(attrs.email) + return forms.setErrorToProperty(form, 'email', 'Invalid email.') + if not _.size(attrs.educationLevel) + return forms.setErrorToProperty(form, 'educationLevel', 'Check at least one.') + @trialRequest = new TrialRequest({ + type: 'course' + properties: attrs + }) + @$('#submit-request-btn').text('Sending').attr('disabled', true) + @trialRequest.save() + @trialRequest.on 'sync', @onTrialRequestSubmit, @ + @trialRequest.on 'error', @onTrialRequestError, @ + + onTrialRequestError: -> + @$('#submit-request-btn').text('Submit').attr('disabled', false) + + onTrialRequestSubmit: -> + @$('form, #form-submit-success').toggleClass('hide') + + onClickLoginButton: -> + modal = new AuthModal({ + mode: 'login' + initialValues: { email: @trialRequest.get('properties')?.email } + }) + @openModalView(modal) + window.nextURL = '/courses/teachers' unless @trialRequest.isNew() + + onClickSignupButton: -> + props = @trialRequest.get('properties') or {} + me.set('name', props.name) + modal = new AuthModal({ + mode: 'signup' + initialValues: { + email: props.email + schoolName: props.organization + } + }) + @openModalView(modal) + window.nextURL = '/courses/teachers' unless @trialRequest.isNew() \ No newline at end of file diff --git a/app/views/SalesView.coffee b/app/views/SalesView.coffee index 53089c973..410ce7f09 100644 --- a/app/views/SalesView.coffee +++ b/app/views/SalesView.coffee @@ -17,7 +17,7 @@ module.exports = class SalesView extends RootView 'CodeCombat' onClickContactUs: (e) -> - app.router.navigate '/teachers/freetrial', trigger: true + app.router.navigate '/teachers/quote', trigger: true onClickLogin: (e) -> @openModalView new AuthModal(mode: 'login') if me.get('anonymous') diff --git a/app/views/core/AuthModal.coffee b/app/views/core/AuthModal.coffee index f67af6033..55242eacb 100644 --- a/app/views/core/AuthModal.coffee +++ b/app/views/core/AuthModal.coffee @@ -30,6 +30,7 @@ module.exports = class AuthModal extends ModalView @onNameChange = _.debounce @checkNameExists, 500 super options @mode = options.mode if options.mode + @previousFormInputs = options.initialValues or {} getRenderData: -> c = super() From ab681252e48c4e3aee5b1707ddeb5f22e3078b43 Mon Sep 17 00:00:00 2001 From: Ivanmateja Date: Fri, 29 Jan 2016 12:46:19 +0100 Subject: [PATCH 23/28] Update sr.coffee --- app/locale/sr.coffee | 152 +++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/app/locale/sr.coffee b/app/locale/sr.coffee index 14fa1fcd8..05f0bac91 100644 --- a/app/locale/sr.coffee +++ b/app/locale/sr.coffee @@ -9,22 +9,22 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # old_browser_suffix: "You can try anyway, but it probably won't work." # ipad_browser: "Bad news: CodeCombat doesn't run on iPad in the browser. Good news: our native iPad app is awaiting Apple approval." # campaign: "Campaign" -# for_beginners: "For Beginners" -# multiplayer: "Multiplayer" # Not currently shown on home page -# for_developers: "For Developers" # Not currently shown on home page. -# or_ipad: "Or download for iPad" +# for_beginners: "За почетнике" +# multiplayer: "Мултиплејер" # Not currently shown on home page +# for_developers: "За Девелопере" # Not currently shown on home page. +# or_ipad: "Или скини за iPad" nav: play: "Нивои" # The top nav bar entry where players choose which levels to play -# community: "Community" -# courses: "Courses" +# community: "Заједница" +# courses: "Курсеви" editor: "Уређивач" blog: "Блог" forum: "Форум" -# account: "Account" -# profile: "Profile" +# account: "Налог" +# profile: "Профил" # stats: "Stats" -# code: "Code" +# code: "Код" admin: "Админ" # Only shows up when you are an admin home: "Почетна" contribute: "Допринеси" @@ -32,8 +32,8 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian about: "О нама" contact: "Контакт" twitter_follow: "Прати" -# teachers: "Teachers" -# careers: "Careers" +# teachers: "Учитељи" +# careers: "Каријере" modal: close: "Затвори" @@ -51,29 +51,29 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian subscribe_as_diplomat: "Претплати се као Дипломата" play: -# play_as: "Play As" # Ladder page -# compete: "Compete!" # Course details page -# spectate: "Spectate" # Ladder page -# players: "players" # Hover over a level on /play -# hours_played: "hours played" # Hover over a level on /play -# items: "Items" # Tooltip on item shop button from /play -# unlock: "Unlock" # For purchasing items and heroes -# confirm: "Confirm" -# owned: "Owned" # For items you own -# locked: "Locked" -# purchasable: "Purchasable" # For a hero you unlocked but haven't purchased -# available: "Available" -# skills_granted: "Skills Granted" # Property documentation details -# heroes: "Heroes" # Tooltip on hero shop button from /play -# achievements: "Achievements" # Tooltip on achievement list button from /play -# account: "Account" # Tooltip on account button from /play -# settings: "Settings" # Tooltip on settings button from /play -# poll: "Poll" # Tooltip on poll button from /play -# next: "Next" # Go from choose hero to choose inventory before playing a level -# change_hero: "Change Hero" # Go back from choose inventory to choose hero -# buy_gems: "Buy Gems" -# subscription_required: "Subscription Required" -# anonymous: "Anonymous Player" +# play_as: "Играј као" # Ladder page +# compete: "Такмичи се!" # Course details page +# spectate: "Посматрај" # Ladder page +# players: "играчи" # Hover over a level on /play +# hours_played: "потребно сати играња" # Hover over a level on /play +# items: "Ствари" # Tooltip on item shop button from /play +# unlock: "Откључај" # For purchasing items and heroes +# confirm: "Потврди" +# owned: "У поседу" # For items you own +# locked: "Закључани" +# purchasable: "Могуће купити" # For a hero you unlocked but haven't purchased +# available: "Доступни" +# skills_granted: "Обезбеђени скилови" # Property documentation details +# heroes: "Хероји" # Tooltip on hero shop button from /play +# achievements: "Достигнућа" # Tooltip on achievement list button from /play +# account: "Налог" # Tooltip on account button from /play +# settings: "Подешавања" # Tooltip on settings button from /play +# poll: "Покрени" # Tooltip on poll button from /play +# next: "Следећи" # Go from choose hero to choose inventory before playing a level +# change_hero: "Промени Хероја" # Go back from choose inventory to choose hero +# buy_gems: "Купи драгуље" +# subscription_required: "Потребна пријава" +# anonymous: "Анонимни играч" level_difficulty: "Тежина: " # play_classroom_version: "Play Classroom Version" # Choose a level in campaign version that you also can play in one of your courses campaign_beginner: "Почетничка кампања" @@ -97,39 +97,39 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian login: sign_up: "Направи Налог" log_in: "Улогуј Се" -# logging_in: "Logging In" +# logging_in: "Логовање" log_out: "Излогуј Се" -# forgot_password: "Forgot your password?" +# forgot_password: "Да ли си заборавио шифру?" # authenticate_gplus: "Authenticate G+" # load_profile: "Load G+ Profile" -# finishing: "Finishing" -# sign_in_with_facebook: "Sign in with Facebook" -# sign_in_with_gplus: "Sign in with G+" -# signup_switch: "Want to create an account?" +# finishing: "Завршавање" +# sign_in_with_facebook: "Учлани се преко Фејсбука" +# sign_in_with_gplus: "Учлани се преко Гугл +" +# signup_switch: "Да ли желиш да направиш налог?" signup: email_announcements: "Примај обавештења на мејл" creating: "Прављење налога..." sign_up: "Упиши се" log_in: "улогуј се са шифром" -# required: "You need to log in before you can go that way." -# login_switch: "Already have an account?" -# school_name: "School Name and City" -# optional: "optional" +# required: "Мораш да се улогујеш пре него што наставиш" +# login_switch: "Већ имаш налог?" +# school_name: "Име школе и града" +# optional: "опционо" # school_name_placeholder: "Example High School, Springfield, IL" recover: recover_account_title: "Поврати налог" -# send_password: "Send Recovery Password" -# recovery_sent: "Recovery email sent." +# send_password: "Пошаљи помоћну шифру" +# recovery_sent: "Послат мејл за потврду опоравка." # items: -# primary: "Primary" -# secondary: "Secondary" -# armor: "Armor" -# accessories: "Accessories" +# primary: "Примарни" +# secondary: "Секундарни" +# armor: "Оклоп" +# accessories: "Опрема" # misc: "Misc" -# books: "Books" +# books: "Књиге" common: # back: "Back" # When used as an action verb, like "Navigate backward" @@ -137,22 +137,22 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian loading: "Учитавање" saving: "Чување..." sending: "Шаље се..." -# send: "Send" +# send: "Пошаљи" cancel: "Откажи" -# save: "Save" -# publish: "Publish" -# create: "Create" +# save: "Сачувај" +# publish: "Објави" +# create: "Створи" # fork: "Fork" play: "Нивои" # When used as an action verb, like "Play next level" # retry: "Retry" # actions: "Actions" -# info: "Info" -# help: "Help" -# watch: "Watch" -# unwatch: "Unwatch" -# submit_patch: "Submit Patch" -# submit_changes: "Submit Changes" -# save_changes: "Save Changes" +# info: "Инфо" +# help: "Помоћ" +# watch: "Одгледај" +# unwatch: "Не гледај" +# submit_patch: "Потврди закрпу" +# submit_changes: "Потврди измене" +# save_changes: "Сачувај измене" general: # and: "and" @@ -259,22 +259,22 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # victory_review_placeholder: "How was the level?" victory_hour_of_code_done: "Јеси ли завршио?" victory_hour_of_code_done_yes: "Да, завршио сам свој Сат Кода!" -# victory_experience_gained: "XP Gained" -# victory_gems_gained: "Gems Gained" -# victory_new_item: "New Item" +# victory_experience_gained: "Добијено искуство" +# victory_gems_gained: "Добијени драгуљи" +# victory_new_item: "Нови Итем" # victory_viking_code_school: "Holy smokes, that was a hard level you just beat! If you aren't already a software developer, you should be. You just got fast-tracked for acceptance with Viking Code School, where you can take your skills to the next level and become a professional web developer in 14 weeks." -# victory_become_a_viking: "Become a Viking" +# victory_become_a_viking: "Постани Викинг" guide_title: "Водич" # tome_cast_button_run: "Run" # tome_cast_button_running: "Running" # tome_cast_button_ran: "Ran" -# tome_submit_button: "Submit" +# tome_submit_button: "Потврди" # tome_reload_method: "Reload original code for this method" # Title text for individual method reload button. # tome_select_method: "Select a Method" # tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methods). tome_select_a_thang: "Изабери неког за " tome_available_spells: "Доступне чини" -# tome_your_skills: "Your Skills" +# tome_your_skills: "Твоје вештине" # tome_current_method: "Current Method" # code_saved: "Code Saved" # skip_tutorial: "Skip (esc)" @@ -375,16 +375,16 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # gold_collected: "Gold Collected" # inventory: -# equipped_item: "Equipped" -# required_purchase_title: "Required" -# available_item: "Available" -# restricted_title: "Restricted" -# should_equip: "(double-click to equip)" -# equipped: "(equipped)" +# equipped_item: "Опремљен" +# required_purchase_title: "Потребно" +# available_item: "Доступно" +# restricted_title: "Забрањено" +# should_equip: "(Дупли-клик за опремање)" +# equipped: "(опремљен/а)" # locked: "(locked)" # restricted: "(restricted in this level)" -# equip: "Equip" -# unequip: "Unequip" +# equip: "Опреми" +# unequip: "Скини" # buy_gems: # few_gems: "A few gems" From 4962c171079831929adaeb84e2caf0efe0603e83 Mon Sep 17 00:00:00 2001 From: Ivanmateja Date: Fri, 29 Jan 2016 15:21:22 +0100 Subject: [PATCH 24/28] Update sr.coffee I have removed hashtags and signed CLA. Hope that everything will work fine now. Sory for mistakes this Github is a little confusing at beginning. Greetings --- app/locale/sr.coffee | 152 +++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/app/locale/sr.coffee b/app/locale/sr.coffee index 05f0bac91..86b4fe9e1 100644 --- a/app/locale/sr.coffee +++ b/app/locale/sr.coffee @@ -9,22 +9,22 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # old_browser_suffix: "You can try anyway, but it probably won't work." # ipad_browser: "Bad news: CodeCombat doesn't run on iPad in the browser. Good news: our native iPad app is awaiting Apple approval." # campaign: "Campaign" -# for_beginners: "За почетнике" -# multiplayer: "Мултиплејер" # Not currently shown on home page -# for_developers: "За Девелопере" # Not currently shown on home page. -# or_ipad: "Или скини за iPad" + for_beginners: "За почетнике" + multiplayer: "Мултиплејер" # Not currently shown on home page + for_developers: "За Девелопере" # Not currently shown on home page. + or_ipad: "Или скини за iPad" nav: play: "Нивои" # The top nav bar entry where players choose which levels to play -# community: "Заједница" -# courses: "Курсеви" + community: "Заједница" + courses: "Курсеви" editor: "Уређивач" blog: "Блог" forum: "Форум" -# account: "Налог" -# profile: "Профил" -# stats: "Stats" -# code: "Код" + account: "Налог" + profile: "Профил" + stats: "Статистика" + code: "Код" admin: "Админ" # Only shows up when you are an admin home: "Почетна" contribute: "Допринеси" @@ -32,8 +32,8 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian about: "О нама" contact: "Контакт" twitter_follow: "Прати" -# teachers: "Учитељи" -# careers: "Каријере" + teachers: "Учитељи" + careers: "Каријере" modal: close: "Затвори" @@ -51,29 +51,29 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian subscribe_as_diplomat: "Претплати се као Дипломата" play: -# play_as: "Играј као" # Ladder page -# compete: "Такмичи се!" # Course details page -# spectate: "Посматрај" # Ladder page -# players: "играчи" # Hover over a level on /play -# hours_played: "потребно сати играња" # Hover over a level on /play -# items: "Ствари" # Tooltip on item shop button from /play -# unlock: "Откључај" # For purchasing items and heroes -# confirm: "Потврди" -# owned: "У поседу" # For items you own -# locked: "Закључани" -# purchasable: "Могуће купити" # For a hero you unlocked but haven't purchased -# available: "Доступни" -# skills_granted: "Обезбеђени скилови" # Property documentation details -# heroes: "Хероји" # Tooltip on hero shop button from /play -# achievements: "Достигнућа" # Tooltip on achievement list button from /play -# account: "Налог" # Tooltip on account button from /play -# settings: "Подешавања" # Tooltip on settings button from /play -# poll: "Покрени" # Tooltip on poll button from /play -# next: "Следећи" # Go from choose hero to choose inventory before playing a level -# change_hero: "Промени Хероја" # Go back from choose inventory to choose hero -# buy_gems: "Купи драгуље" -# subscription_required: "Потребна пријава" -# anonymous: "Анонимни играч" + play_as: "Играј као" # Ladder page + compete: "Такмичи се!" # Course details page + spectate: "Посматрај" # Ladder page + players: "играчи" # Hover over a level on /play + hours_played: "потребно сати играња" # Hover over a level on /play + items: "Ствари" # Tooltip on item shop button from /play + unlock: "Откључај" # For purchasing items and heroes + confirm: "Потврди" + owned: "У поседу" # For items you own + locked: "Закључани" + purchasable: "Могуће купити" # For a hero you unlocked but haven't purchased + available: "Доступни" + skills_granted: "Обезбеђени скилови" # Property documentation details + heroes: "Хероји" # Tooltip on hero shop button from /play + achievements: "Достигнућа" # Tooltip on achievement list button from /play + account: "Налог" # Tooltip on account button from /play + settings: "Подешавања" # Tooltip on settings button from /play + poll: "Покрени" # Tooltip on poll button from /play + next: "Следећи" # Go from choose hero to choose inventory before playing a level + change_hero: "Промени Хероја" # Go back from choose inventory to choose hero + buy_gems: "Купи драгуље" + subscription_required: "Потребна пријава" + anonymous: "Анонимни играч" level_difficulty: "Тежина: " # play_classroom_version: "Play Classroom Version" # Choose a level in campaign version that you also can play in one of your courses campaign_beginner: "Почетничка кампања" @@ -97,39 +97,39 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian login: sign_up: "Направи Налог" log_in: "Улогуј Се" -# logging_in: "Логовање" + logging_in: "Логовање" log_out: "Излогуј Се" -# forgot_password: "Да ли си заборавио шифру?" + forgot_password: "Да ли си заборавио шифру?" # authenticate_gplus: "Authenticate G+" # load_profile: "Load G+ Profile" -# finishing: "Завршавање" -# sign_in_with_facebook: "Учлани се преко Фејсбука" -# sign_in_with_gplus: "Учлани се преко Гугл +" -# signup_switch: "Да ли желиш да направиш налог?" + finishing: "Завршавање" + sign_in_with_facebook: "Учлани се преко Фејсбука" + sign_in_with_gplus: "Учлани се преко Гугл +" + signup_switch: "Да ли желиш да направиш налог?" signup: email_announcements: "Примај обавештења на мејл" creating: "Прављење налога..." sign_up: "Упиши се" log_in: "улогуј се са шифром" -# required: "Мораш да се улогујеш пре него што наставиш" -# login_switch: "Већ имаш налог?" -# school_name: "Име школе и града" -# optional: "опционо" + required: "Мораш да се улогујеш пре него што наставиш" + login_switch: "Већ имаш налог?" + school_name: "Име школе и града" + optional: "опционо" # school_name_placeholder: "Example High School, Springfield, IL" recover: recover_account_title: "Поврати налог" -# send_password: "Пошаљи помоћну шифру" -# recovery_sent: "Послат мејл за потврду опоравка." + send_password: "Пошаљи помоћну шифру" + recovery_sent: "Послат мејл за потврду опоравка." # items: -# primary: "Примарни" -# secondary: "Секундарни" -# armor: "Оклоп" -# accessories: "Опрема" + primary: "Примарни" + secondary: "Секундарни" + armor: "Оклоп" + accessories: "Опрема" # misc: "Misc" -# books: "Књиге" + books: "Књиге" common: # back: "Back" # When used as an action verb, like "Navigate backward" @@ -137,22 +137,22 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian loading: "Учитавање" saving: "Чување..." sending: "Шаље се..." -# send: "Пошаљи" + send: "Пошаљи" cancel: "Откажи" -# save: "Сачувај" -# publish: "Објави" -# create: "Створи" + save: "Сачувај" + publish: "Објави" + create: "Створи" # fork: "Fork" play: "Нивои" # When used as an action verb, like "Play next level" # retry: "Retry" # actions: "Actions" -# info: "Инфо" -# help: "Помоћ" -# watch: "Одгледај" -# unwatch: "Не гледај" -# submit_patch: "Потврди закрпу" -# submit_changes: "Потврди измене" -# save_changes: "Сачувај измене" + info: "Инфо" + help: "Помоћ" + watch: "Одгледај" + unwatch: "Не гледај" + submit_patch: "Потврди закрпу" + submit_changes: "Потврди измене" + save_changes: "Сачувај измене" general: # and: "and" @@ -259,11 +259,11 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # victory_review_placeholder: "How was the level?" victory_hour_of_code_done: "Јеси ли завршио?" victory_hour_of_code_done_yes: "Да, завршио сам свој Сат Кода!" -# victory_experience_gained: "Добијено искуство" -# victory_gems_gained: "Добијени драгуљи" -# victory_new_item: "Нови Итем" + victory_experience_gained: "Добијено искуство" + victory_gems_gained: "Добијени драгуљи" + victory_new_item: "Нови Итем" # victory_viking_code_school: "Holy smokes, that was a hard level you just beat! If you aren't already a software developer, you should be. You just got fast-tracked for acceptance with Viking Code School, where you can take your skills to the next level and become a professional web developer in 14 weeks." -# victory_become_a_viking: "Постани Викинг" + victory_become_a_viking: "Постани Викинг" guide_title: "Водич" # tome_cast_button_run: "Run" # tome_cast_button_running: "Running" @@ -274,7 +274,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methods). tome_select_a_thang: "Изабери неког за " tome_available_spells: "Доступне чини" -# tome_your_skills: "Твоје вештине" + tome_your_skills: "Твоје вештине" # tome_current_method: "Current Method" # code_saved: "Code Saved" # skip_tutorial: "Skip (esc)" @@ -375,16 +375,16 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # gold_collected: "Gold Collected" # inventory: -# equipped_item: "Опремљен" -# required_purchase_title: "Потребно" -# available_item: "Доступно" -# restricted_title: "Забрањено" -# should_equip: "(Дупли-клик за опремање)" -# equipped: "(опремљен/а)" + equipped_item: "Опремљен" + required_purchase_title: "Потребно" + available_item: "Доступно" + restricted_title: "Забрањено" + should_equip: "(Дупли-клик за опремање)" + equipped: "(опремљен/а)" # locked: "(locked)" # restricted: "(restricted in this level)" -# equip: "Опреми" -# unequip: "Скини" + equip: "Опреми" + unequip: "Скини" # buy_gems: # few_gems: "A few gems" From 546fdfa2b6c32748b95e849df2b0f2723760c1d9 Mon Sep 17 00:00:00 2001 From: Imperadeiro98 Date: Fri, 29 Jan 2016 14:58:08 +0000 Subject: [PATCH 25/28] Fix build --- app/locale/sr.coffee | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/locale/sr.coffee b/app/locale/sr.coffee index 86b4fe9e1..d38c85a59 100644 --- a/app/locale/sr.coffee +++ b/app/locale/sr.coffee @@ -54,7 +54,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian play_as: "Играј као" # Ladder page compete: "Такмичи се!" # Course details page spectate: "Посматрај" # Ladder page - players: "играчи" # Hover over a level on /play + players: "играчи" # Hover over a level on /play hours_played: "потребно сати играња" # Hover over a level on /play items: "Ствари" # Tooltip on item shop button from /play unlock: "Откључај" # For purchasing items and heroes @@ -64,7 +64,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian purchasable: "Могуће купити" # For a hero you unlocked but haven't purchased available: "Доступни" skills_granted: "Обезбеђени скилови" # Property documentation details - heroes: "Хероји" # Tooltip on hero shop button from /play + heroes: "Хероји" # Tooltip on hero shop button from /play achievements: "Достигнућа" # Tooltip on achievement list button from /play account: "Налог" # Tooltip on account button from /play settings: "Подешавања" # Tooltip on settings button from /play @@ -123,7 +123,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian send_password: "Пошаљи помоћну шифру" recovery_sent: "Послат мејл за потврду опоравка." -# items: + items: primary: "Примарни" secondary: "Секундарни" armor: "Оклоп" @@ -141,7 +141,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian cancel: "Откажи" save: "Сачувај" publish: "Објави" - create: "Створи" + create: "Створи" # fork: "Fork" play: "Нивои" # When used as an action verb, like "Play next level" # retry: "Retry" @@ -150,7 +150,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian help: "Помоћ" watch: "Одгледај" unwatch: "Не гледај" - submit_patch: "Потврди закрпу" + submit_patch: "Потврди закрпу" submit_changes: "Потврди измене" save_changes: "Сачувај измене" @@ -268,7 +268,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # tome_cast_button_run: "Run" # tome_cast_button_running: "Running" # tome_cast_button_ran: "Ran" -# tome_submit_button: "Потврди" + tome_submit_button: "Потврди" # tome_reload_method: "Reload original code for this method" # Title text for individual method reload button. # tome_select_method: "Select a Method" # tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methods). @@ -374,13 +374,13 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # difficulty: "Difficulty" # gold_collected: "Gold Collected" -# inventory: + inventory: equipped_item: "Опремљен" required_purchase_title: "Потребно" available_item: "Доступно" restricted_title: "Забрањено" should_equip: "(Дупли-клик за опремање)" - equipped: "(опремљен/а)" + equipped: "(опремљен/а)" # locked: "(locked)" # restricted: "(restricted in this level)" equip: "Опреми" From b441161edb000b38ef981971058904ca04b79e01 Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 29 Jan 2016 21:29:13 +0100 Subject: [PATCH 26/28] Update sr.coffee Little more translation for today. Best regards --- app/locale/sr.coffee | 160 +++++++++++++++++++++---------------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/app/locale/sr.coffee b/app/locale/sr.coffee index d38c85a59..d7bdc92e8 100644 --- a/app/locale/sr.coffee +++ b/app/locale/sr.coffee @@ -8,7 +8,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # old_browser: "Uh oh, your browser is too old to run CodeCombat. Sorry!" # Warning that shows up on really old Firefox/Chrome/Safari # old_browser_suffix: "You can try anyway, but it probably won't work." # ipad_browser: "Bad news: CodeCombat doesn't run on iPad in the browser. Good news: our native iPad app is awaiting Apple approval." -# campaign: "Campaign" + campaign: "Кампања" for_beginners: "За почетнике" multiplayer: "Мултиплејер" # Not currently shown on home page for_developers: "За Девелопере" # Not currently shown on home page. @@ -128,11 +128,11 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian secondary: "Секундарни" armor: "Оклоп" accessories: "Опрема" -# misc: "Misc" + misc: "Остало" books: "Књиге" common: -# back: "Back" # When used as an action verb, like "Navigate backward" + back: "Уназад" # When used as an action verb, like "Navigate backward" # continue: "Continue" # When used as an action verb, like "Continue forward" loading: "Учитавање" saving: "Чување..." @@ -157,12 +157,12 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian general: # and: "and" name: "Име" -# date: "Date" -# body: "Body" -# version: "Version" -# pending: "Pending" -# accepted: "Accepted" -# rejected: "Rejected" + date: "Датум" + body: "Тело" + version: "Верзија" + pending: "Учитавање" + accepted: "Прихваћен" + rejected: "Одбијен" # withdrawn: "Withdrawn" # accept: "Accept" # reject: "Reject" @@ -178,85 +178,85 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # redo_prefix: "Redo" # redo_shortcut: "(Ctrl+Shift+Z)" # play_preview: "Play preview of current level" -# result: "Result" -# results: "Results" -# description: "Description" + result: "Резултат" + results: "Резултати" + description: "Опис" or: "или" -# subject: "Subject" + subject: "Субјекат" email: "Мејл" -# password: "Password" + password: "Шифра" message: "Порука" -# code: "Code" + code: "Код" # ladder: "Ladder" # when: "When" -# opponent: "Opponent" -# rank: "Rank" -# score: "Score" -# win: "Win" -# loss: "Loss" -# tie: "Tie" -# easy: "Easy" -# medium: "Medium" -# hard: "Hard" -# player: "Player" -# player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard -# warrior: "Warrior" -# ranger: "Ranger" -# wizard: "Wizard" + opponent: "Противник" + rank: "Чин" + score: "Поен" + win: "Победа" + loss: "Пораз" + tie: "Нерешено" + easy: "Лако" + medium: "Средње" + hard: "Тешко" + player: "Играч" + player_level: "Ранг играча" # Like player level 5, not like level: Dungeons of Kithgard + warrior: "Ратник" + ranger: "Извиђач" + wizard: "Маг" # units: -# second: "second" -# seconds: "seconds" -# minute: "minute" -# minutes: "minutes" -# hour: "hour" -# hours: "hours" -# day: "day" -# days: "days" -# week: "week" -# weeks: "weeks" -# month: "month" -# months: "months" -# year: "year" -# years: "years" + second: "секунда" + seconds: "секунде" + minute: "минут" + minutes: "минути" + hour: "сат" + hours: "сати" + day: "дан" + days: "дани" + week: "недеља" + weeks: "недеље" + month: "месец" + months: "месеци" + year: "година" + years: "године" play_level: -# completed_level: "Completed Level:" -# course: "Course:" + completed_level: "Завршен ниво:" + course: "Курс:" done: "Урађено" -# next_level: "Next Level:" -# next_game: "Next game" -# show_menu: "Show game menu" + next_level: "Следећи ниво:" + next_game: "Следећа игра" + show_menu: "Види мени игре" home: "Почетна" # Not used any more, will be removed soon. -# level: "Level" # Like "Level: Dungeons of Kithgard" -# skip: "Skip" -# game_menu: "Game Menu" + level: "Ниво" # Like "Level: Dungeons of Kithgard" + skip: "Прескочи" + game_menu: "Мени игре" guide: "Водич" restart: "Поновно учитавање" goals: "Циљеви" -# goal: "Goal" -# running: "Running..." -# success: "Success!" -# incomplete: "Incomplete" -# timed_out: "Ran out of time" -# failing: "Failing" -# control_bar_multiplayer: "Multiplayer" -# control_bar_join_game: "Join Game" -# reload: "Reload" + goal: "Циљ" + running: "Покретање..." + success: "Успех!" + incomplete: "Недовољно" + timed_out: "Истекло времe" + failing: "Губљење" + control_bar_multiplayer: "Мултиплејер" + control_bar_join_game: "Прикључи се игри" + reload: "Поновно учитавање" reload_title: "Поновно учитавање целог кода?" reload_really: "Да ли сте сигурни да желите да кренете ниво испочетка?" reload_confirm: "Поновно учитавање свега" -# victory: "Victory" -# victory_title_prefix: "" + victory: "Победа" + victory_title_prefix: "Урааа победааа!" victory_title_suffix: " Завршено" victory_sign_up: "Пријави се за новости" victory_sign_up_poke: "Желиш ли да примаш најновије вести на мејл? Направи бесплатан налог и ми ћемо те обавештавати!" victory_rate_the_level: "Оцени ниво: " # {change} -# victory_return_to_ladder: "Return to Ladder" -# victory_saving_progress: "Saving Progress" + victory_return_to_ladder: "Врати се на лествицу" + victory_saving_progress: "Чување напретка" victory_go_home: "Иди на почетну" victory_review: "Реци нам више!" -# victory_review_placeholder: "How was the level?" + victory_review_placeholder: "Колико је био тежак ниво?" victory_hour_of_code_done: "Јеси ли завршио?" victory_hour_of_code_done_yes: "Да, завршио сам свој Сат Кода!" victory_experience_gained: "Добијено искуство" @@ -362,17 +362,17 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # auth_caption: "Save your progress." # leaderboard: -# view_other_solutions: "View Leaderboards" -# scores: "Scores" -# top_players: "Top Players by" -# day: "Today" -# week: "This Week" -# all: "All-Time" -# time: "Time" -# damage_taken: "Damage Taken" -# damage_dealt: "Damage Dealt" -# difficulty: "Difficulty" -# gold_collected: "Gold Collected" + view_other_solutions: "Види табелу напредовања" + scores: "Резултати" + top_players: "Рангирање играча по" + day: "Данас" + week: "Ове недеље" + all: "Све-време" + time: "Време" + damage_taken: "Претпљена штета" + damage_dealt: "Нанета штета" + difficulty: "Тежина" + gold_collected: "Сакупљено злата" inventory: equipped_item: "Опремљен" @@ -459,11 +459,11 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # using_prepaid: "Using prepaid code for monthly subscription" # choose_hero: -# choose_hero: "Choose Your Hero" -# programming_language: "Programming Language" -# programming_language_description: "Which programming language do you want to use?" + choose_hero: "Изабери свог хероја" + programming_language: "Програмски језик" + programming_language_description: "Који програмски језик желиш да користиш у игри?" # default: "Default" -# experimental: "Experimental" + experimental: "Експеро+иментални" # python_blurb: "Simple yet powerful, great for beginners and experts." # javascript_blurb: "The language of the web. (Not the same as Java.)" # coffeescript_blurb: "Nicer JavaScript syntax." From 5a1d1e93dd6a197066f2b9b4139e0cec24da2c99 Mon Sep 17 00:00:00 2001 From: Imperadeiro98 Date: Sat, 30 Jan 2016 10:21:36 +0000 Subject: [PATCH 27/28] Fix build --- app/locale/sr.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/locale/sr.coffee b/app/locale/sr.coffee index d7bdc92e8..3ff415cf0 100644 --- a/app/locale/sr.coffee +++ b/app/locale/sr.coffee @@ -204,7 +204,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian ranger: "Извиђач" wizard: "Маг" -# units: + units: second: "секунда" seconds: "секунде" minute: "минут" @@ -212,7 +212,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian hour: "сат" hours: "сати" day: "дан" - days: "дани" + days: "дани" week: "недеља" weeks: "недеље" month: "месец" @@ -241,7 +241,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian timed_out: "Истекло времe" failing: "Губљење" control_bar_multiplayer: "Мултиплејер" - control_bar_join_game: "Прикључи се игри" + control_bar_join_game: "Прикључи се игри" reload: "Поновно учитавање" reload_title: "Поновно учитавање целог кода?" reload_really: "Да ли сте сигурни да желите да кренете ниво испочетка?" @@ -361,9 +361,9 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # multiplayer_caption: "Play with friends!" # auth_caption: "Save your progress." -# leaderboard: + leaderboard: view_other_solutions: "Види табелу напредовања" - scores: "Резултати" + scores: "Резултати" top_players: "Рангирање играча по" day: "Данас" week: "Ове недеље" @@ -458,7 +458,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian # subscribe_prepaid: "Click Subscribe to use prepaid code" # using_prepaid: "Using prepaid code for monthly subscription" -# choose_hero: + choose_hero: choose_hero: "Изабери свог хероја" programming_language: "Програмски језик" programming_language_description: "Који програмски језик желиш да користиш у игри?" From c03875763e9d1e288c81483fd03116ce312f33e2 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Sun, 31 Jan 2016 09:07:36 -0800 Subject: [PATCH 28/28] Add Get Started button For teacher trial approved mail --- app/assets/images/common/button-get-started.png | Bin 0 -> 2648 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/assets/images/common/button-get-started.png diff --git a/app/assets/images/common/button-get-started.png b/app/assets/images/common/button-get-started.png new file mode 100644 index 0000000000000000000000000000000000000000..78a4ad1993692acee9a5c0e069a78726658c550e GIT binary patch literal 2648 zcmb`J=OY^m8^w)kt+aNG+BHg1qk^iUA=KVNjTY&ZC_+^dH7>CUsZEXAyQ$dQb=9a% zsI6%2rf8(4xZeNa{qUUg`|x}{&vRnUO(6hger6gP8i1hz*z(e=E=QU1>ZNWi{Q_MY zt-mEim!@{~<~9utGlwBq#~MYu@5E$pL*h9f>rhgol@VK`xgKN1{LP>+NoRyhu55am zU^!Jy?Z|j!_V1qfh<5P%6O1+aw!)N>i|NyRb%ApA_`f*|)z`4hYVYW%H4T*IW?zN3 z2!*AS{ry1o&F{of4aCLi!`{{W6RbS#LG>7AAq z@Ddpxl@O6$f29v^)zLgjgEah&-+9A8%giK6l*@M97$9!g1iLF5&%I-lP7Jd(Uz%#F z)M0N>QK^KeUNPqOQ+ST9z&>t7TO5DZ)5eqJRd&L>7^UlmRK>5Zioy zlKEiYZ~UtLR4?H5nN-b-OwNceOS;9vwhVJ}D*UXxB-xuia@hsJQ~Kp5?zd%R{+S?d z;xT)-I-eYIHkJw_QM^CZ4NvT`hO=llG4Z=kU42kC{=mbz7WC%~G)MeQnd|R#CmuoE zR{h(J?or{AE}Mvx@_zPO6dL@zwh9dSONt}**e;K|Rrd&Ggn6M57u5Ru}Hqrtn zZWEGx3e<)6U8`-Jcqw4xNy4CE>83_A32%&oF3 zBN=L}5H=Aj^@_`XZdq*dndN2HyL%+HuI3^cz4^joV##jg2^YC7BE;zf1(XC+FYqt3 z={+f5hdO2aOeQ?{P4G^(@Yj+`3K`=htEEDBoVbgglcYeTY%|8ZU%-R_$LR7{7&Nxa#R(ZQ4547 zH$A0qCPGnzt=XE6XvN*hR6*2!%iZdP+%TI3>RQTI1mTDfz}wB_4Atn}ZtYuWr04sb zc9-+Z3kQdRq5fb5y0FB>>1DnOeIN_7T$wKi@NAMr4SD{?Mz^01^KsbkJ>*qHM)du8 zT&|e!*G}O^a&JS6n}73%VZY^4LtXcDoF~gWIq7BG+oLjdDh802u>YEt20oW^q#_;nzO>4J2G=}UxPdcTP4m2BE5u|(QpyWxLf$9mE|!dss{6~uzeKq z5O8|k^{-#b9y`93uHd}^ozKWEY5n1oX|Yevq(qLq3&_;{Z&N^GU-;uegkN9JU#Y76 z^-6XQvKd~>IBU#CKPx$@ltKx%(nMc~Vl4+Noj;6s!|SOlZ_7}4M?q#5p<+H(eoQ@r z;kwQ6NV+K*F64{!JkOO~N#wUh?ozLNFR<1ojPIIi86I74sq1*2)^pvD`88lIv&)h0X+$L=?Q9-_4t zd1V_3$~z*V4_S+;qSlyszffrUdi_L4@9hXGjhWbP$1B;cR0*4khsa5L>(U>EbyZb_ zFLTE=iral!)-ZTN#Zi~uNo$saOlqknd%ON+@|PjYiTx7Aq1E0P`wd|xmS z$`*KUiVSd_0j1}^s;leo@RQ(o1md-whXy$*4k6fG!6SB=^~BwU+@1PF-ya)%?7jYS z8ZdBZ-6y6e?^ZLaM+*2Z-nCG0txb2tE7G0_zgB>=iOp+DaNP1YNtVjdamjAy^gnMK zdKSFDBb0gH8=HwbhA&>8eege2L`}@yJ7QOSC_%PdJ1u#;4_p)UqS4Xu`$o4L-JkJL zv6=_S_%5Ik0R~;3pB&`IHeI9W+qFX9mh_~RCPNb>GI&D>t5mynqm=-rz zZ!U^ZhL3hmrZlLb_Gr=ElH_l42U%fj4FhX_?PE!Bm!U8p4)zurZy$zAEjf`N%(l>2 zKt_A>Ut@#A>WAso#A=w4)XGc#vvm}es`4?cN}w!0QUn%^=^s`k#-`)mYprg|49^B- zF7)H7Z*blWkZWOct$Uo~I>?Y#r+@W--#e4RBSQWX0(y$Emwf|o69qL_-O{}Tpz&ja z|N9E-&&2jSPH!F?i2fL08)sfb{CeU(*1CzGE0C{d8~>Z;Y}xH3_QH4!#0w}z9o8oG z2OqsXs@&w4H!NClYAqd zOh0w;HP5mFx+=-3VpdosK)E>n#QrL^+&i*cI4Z2vV~R?$+yWvbHzXxS@40M-uDFhZ Uht({1FRLPrp`IzYR@XKBKQQnhLjV8( literal 0 HcmV?d00001