diff --git a/app/assets/images/pages/clans/dashboard_preview.png b/app/assets/images/pages/clans/dashboard_preview.png index ecd5f15f9..d45f8911f 100644 Binary files a/app/assets/images/pages/clans/dashboard_preview.png and b/app/assets/images/pages/clans/dashboard_preview.png differ diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 818d35730..9ca9795e7 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -790,6 +790,7 @@ make_private: "Make clan private" subs_only: "subscribers only" create_clan: "Create New Clan" + private_preview: "Preview" public_clans: "Public Clans" my_clans: "My Clans" clan_name: "Clan Name" diff --git a/app/locale/es-419.coffee b/app/locale/es-419.coffee index 18d1d857a..772769f1e 100644 --- a/app/locale/es-419.coffee +++ b/app/locale/es-419.coffee @@ -158,7 +158,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip unwatch: "No seguir" submit_patch: "Enviar Parche" submit_changes: "Enviar cambios" -# save_changes: "Save Changes" + save_changes: "Guardar cambios" general: and: "y" @@ -609,7 +609,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip sub_includes_4: "Soporte de correo electronico Premium" sub_includes_5: "7 heroes nuevos con habilidades unicas que dominar" sub_includes_6: "bonificación de 3500 gemas cada mes" -# sub_includes_7: "Private Clans" + sub_includes_7: "Clanes privados" # monitor_progress_title: "How do I monitor student progress?" # monitor_progress_1: "Student progress can be monitored by creating a" # monitor_progress_2: "for your class." @@ -635,39 +635,39 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip # how_much_6: "for more details." # more_info_title: "Where can I find more information?" # more_info_1: "Our" -# more_info_2: "teachers forum" -# more_info_3: "is a good place to connect with fellow educators who are using CodeCombat." + more_info_2: "el foro de profesores" + more_info_3: "es un buen lugar para connectarse con los educadores que estan usando CodeCombat." sys_requirements_title: "Requerimientos del sistema" sys_requirements_1: "Debido que CodeCombat es un juego, es más difícil para las computadoras correrlo en relación a un tutorial escrito o un video. Para que todos puedan jugar, hemos optimizado la web para correr rápidamente en todos los navegadores modernos y en maquinas antiguas. Dicho esto, aquí están nuestras sugerencias para sacar el máximo provecho de su experiencia en la Hora del Código:" # {change} sys_requirements_2: "Usar una versión actualizada del navegador Chrome o Firefox." # {change} -# teachers_survey: + teachers_survey: # title: "Teacher Survey" # must_be_logged: "You must be logged in first. Please create an account or log in from the menu above." # retrieving: "Retrieving information..." # being_reviewed_1: "Your application for a free trial subscription is being" # being_reviewed_2: "reviewed." # approved_1: "Your application for a free trial subscription was" -# approved_2: "approved." + approved_2: "Aprobada." # approved_3: "Further instructions have been sent to" # denied_1: "Your application for a free trial subscription has been" -# denied_2: "denied." -# contact_1: "Please contact" + denied_2: "denegadae." + contact_1: "Porfavor contactarse" # contact_2: "if you have further questions." # description_1: "We offer free subscriptions to teachers for evaluation purposes. You can find more information on our" # description_2: "teachers" # description_3: "page." # description_4: "Please fill out this quick survey and we’ll email you setup instructions." -# email: "Email Address" -# school: "Name of School" -# location: "Name of City" -# age_students: "How old are your students?" + email: "Dirección de email" + school: "Nombre del colegio" + location: "Nombre de la ciudad" + age_students: "¿Qué edad tienen tus estudiantes?" # under: "Under" # other: "Other:" -# amount_students: "How many students do you teach?" -# hear_about: "How did you hear about CodeCombat?" -# fill_fields: "Please fill out all fields." -# thanks: "Thanks! We'll send you setup instructions shortly." + amount_students: "¿A cuantos alumnos les enseñas?" + hear_about: "¿Donde escuchaste sobre CodeCombat?" + fill_fields: "Porfavor llenar todos los campos." + thanks: "Gracias! Vamos a mandarte instrucciónes para iniciar proximamente." versions: save_version_title: "Guardar nueva versión" @@ -705,7 +705,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip picture_tab: "Imagen" delete_account_tab: "Borra tu cuenta" wrong_email: "Mail Incorrecto" -# wrong_password: "Wrong Password" + wrong_password: "Contraseña incorrecta" upload_picture: "Sube una imagen" delete_this_account: "Borrar esta cuenta permanentemente" god_mode: "Modo Dios" @@ -746,7 +746,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip keyboard_shortcuts: "Atajos de teclado" space: "Barra espaciadora" enter: "Enter" -# press_enter: "press enter" + press_enter: "Toca enter" escape: "Escape" shift: "Shift" run_code: "Ejecutar el código." @@ -781,48 +781,48 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip social_hipchat: "Chatea con nosotros en el chat público de CodeCombat en la sala HipChat" contribute_to_the_project: "Contribuir al proyecto" -# clans: -# clan: "Clan" -# clans: "Clans" -# new_name: "New clan name" -# new_description: "New clan description" -# make_private: "Make clan private" -# subs_only: "subscribers only" -# create_clan: "Create New Clan" -# public_clans: "Public Clans" -# my_clans: "My Clans" -# clan_name: "Clan Name" -# name: "Name" + clans: + clan: "Clan" + clans: "Clanes" + new_name: "Nuevo nombre de clan" + new_description: "descripción del clan" + make_private: "Hacer clan privado" + subs_only: "solo suscriptores" + create_clan: "Crear nuevo clan" + public_clans: "Clanes publicos" + my_clans: "Mis Clanes" + clan_name: "Nombre del clan" + name: "Nombre" # chieftain: "Chieftain" -# type: "Type" -# edit_clan_name: "Edit Clan Name" -# edit_clan_description: "Edit Clan Description" -# edit_name: "edit name" -# edit_description: "edit description" -# private: "(private)" + type: "Tipo" + edit_clan_name: "Editar el nombre del Clan" + edit_clan_description: "Editar descripción del clan" + edit_name: "editar nombre" + edit_description: "editar descripción" + private: "(privado)" # summary: "Summary" -# average_level: "Average Level" -# average_achievements: "Average Achievements" -# delete_clan: "Delete Clan" -# leave_clan: "Leave Clan" -# join_clan: "Join Clan" -# invite_1: "Invite:" -# invite_2: "*Invite players to this Clan by sending them this link." -# members: "Members" -# progress: "Progress" -# not_started_1: "not started" -# started_1: "started" -# complete_1: "complete" -# exp_levels: "Expand levels" -# rem_hero: "Remove Hero" -# status: "Status" -# complete_2: "Complete" -# started_2: "Started" -# not_started_2: "Not Started" -# view_solution: "Click to view solution." -# latest_achievement: "Latest Achievement" -# playtime: "Playtime" -# last_played: "Last played" + average_level: "Nivel Promedio" + average_achievements: "Logros Promedio" + delete_clan: "Borrar Clan" + leave_clan: "Abandonar Clan" + join_clan: "Ingresar Clan" + invite_1: "Invitar:" + invite_2: "*Invitar jugadores al clan, mandandoles este link." + members: "Miembros" + progress: "Progreso" + not_started_1: "no iniciado" + started_1: "iniciado" + complete_1: "completo" + exp_levels: "Expand levels" + rem_hero: "Remover Heróe" + status: "Stado" + complete_2: "Completo" + started_2: "Iniciado" + not_started_2: "No inciiado" + view_solution: "Click para ver la solución." + latest_achievement: "último logro" + playtime: "Tiempo de juego" + last_played: "Último jugado" classes: archmage_title: "Archimago" @@ -1074,7 +1074,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip no_achievements: "Sin Logros todavía." favorite_prefix: "Idioma favorito " favorite_postfix: "." -# not_member_of_clans: "Not a member of any clans yet." + not_member_of_clans: "No se es miembro de ningún clan todavía." achievements: last_earned: "Último Ganado" @@ -1180,7 +1180,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip user_remarks: "Observaciones del usuario" versions: "Versiones" items: "Items" -# hero: "Hero" + hero: "Heróe" heroes: "Héroes" achievement: "Logros" clas: "CLAs" diff --git a/app/locale/ja.coffee b/app/locale/ja.coffee index 45ed39fc0..91892ab03 100644 --- a/app/locale/ja.coffee +++ b/app/locale/ja.coffee @@ -641,7 +641,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese", sys_requirements_1: "モダンなブラウザ。最新の Chrome や FireFox, Safari など。Internet Explorer 9 以上。" sys_requirements_2: "CodeCombat はまだ iPad をサポートしていません。" -# teachers_survey: + teachers_survey: # title: "Teacher Survey" # must_be_logged: "You must be logged in first. Please create an account or log in from the menu above." # retrieving: "Retrieving information..." @@ -659,8 +659,8 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese", # description_3: "page." # description_4: "Please fill out this quick survey and we’ll email you setup instructions." # email: "Email Address" -# school: "Name of School" -# location: "Name of City" + school: "学校名" + location: "市町村" # age_students: "How old are your students?" # under: "Under" # other: "Other:" @@ -705,7 +705,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese", picture_tab: "画像" delete_account_tab: "アカウントの削除" wrong_email: "間違ったメールアドレス" -# wrong_password: "Wrong Password" + wrong_password: "間違ったパスワード" upload_picture: "画像をアップロード" delete_this_account: "アカウントを完全削除する" god_mode: "ゴッドモード" @@ -714,8 +714,8 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese", admin: "管理者" new_password: "新パスワード" new_password_verify: "新パスワードを再入力" - type_in_email: "削除を確認するため、メールを入力して下さい" # {change} -# type_in_password: "Also, type in your password." + type_in_email: "アカウントの削除を確認するために、メールアドレスを入力して下さい" + type_in_password: "そして、パスワードを入力してください。" email_subscriptions: "ニュースレターの購読" email_subscriptions_none: "No Email Subscriptions." email_announcements: "お知らせ" @@ -746,7 +746,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese", keyboard_shortcuts: "キーボードショートカット" space: "スペース" enter: "エンター" -# press_enter: "press enter" + press_enter: "エンターを押す" escape: "エスケープ" shift: "シフト" run_code: "現在のコードを実行" diff --git a/app/styles/clans/clan-details.sass b/app/styles/clans/clan-details.sass index 8ad366396..15bbda173 100644 --- a/app/styles/clans/clan-details.sass +++ b/app/styles/clans/clan-details.sass @@ -55,8 +55,11 @@ vertical-align: middle + .member-header + cursor: pointer + .progress-header - margin-right: 14px + cursor: pointer .progress-key cursor: default @@ -74,6 +77,7 @@ .progress-key-complete background-color: lightgray + margin-left: 14px .expand-progress-checkbox margin-left: 14px @@ -95,9 +99,23 @@ background-color: blanchedalmond font-size: 10pt - .level-progression-campaign + .level-progression-concepts + color: #317EAC font-size: 12pt font-weight: bold + margin-top: 8px + margin-bottom: 4px + + .level-progression-levels + color: #317EAC + font-size: 12pt + font-weight: bold + margin-top: 8px + + .level-progression-campaign + font-size: 10pt + font-weight: bold + margin-bottom: 4px margin-top: 4px .progress-level-cell diff --git a/app/styles/courses/mock1/course-details.sass b/app/styles/courses/mock1/course-details.sass index bfe5a625a..9c26707c5 100644 --- a/app/styles/courses/mock1/course-details.sass +++ b/app/styles/courses/mock1/course-details.sass @@ -10,3 +10,60 @@ .select-session width: 300px display: inline + + .progress-header + margin-right: 14px + + .progress-key + cursor: default + display: inline-block + white-space: nowrap + font-size: 9pt + font-weight: normal + border: 1px solid gray + border-radius: 5px + margin: 0px + padding: 2px + + .progress-key-started + background-color: lightgreen + + .progress-key-complete + background-color: lightgray + + .expand-progress-checkbox + margin-left: 14px + + .expand-progress-label + font-weight: normal + font-size: 14px + + .progress-cell + padding: 2px + padding-bottom: 10px + + .level-popup-container + display: none + position: absolute + padding: 10px + border: 1px solid black + z-index: 3 + background-color: blanchedalmond + font-size: 10pt + + .progress-level-cell + display: inline-block + white-space: nowrap + font-size: 9pt + border: 1px solid gray + border-radius: 5px + margin: 0px + padding: 2px + + .progress-level-cell-started + cursor: pointer + background-color: lightgreen + + .progress-level-cell-complete + cursor: pointer + background-color: lightgray diff --git a/app/templates/clans/clan-details.jade b/app/templates/clans/clan-details.jade index 3905ff19e..e57b58c64 100644 --- a/app/templates/clans/clan-details.jade +++ b/app/templates/clans/clan-details.jade @@ -83,92 +83,111 @@ block content table.table.table-condensed thead tr - th(data-i18n="resources.hero") Hero th - span.progress-header(data-i18n="clans.progress") Progress - span.progress-key(data-i18n="clans.not_started_1") not started + span.member-header.spr(data-i18n="resources.hero") Hero + if memberSort === 'nameAsc' + span.member-header.glyphicon.glyphicon-chevron-up + else if memberSort === 'nameDesc' + span.member-header.glyphicon.glyphicon-chevron-down + th + span.progress-header.spr(data-i18n="clans.progress") Progress + if memberSort === 'progressAsc' + span.progress-header.glyphicon.glyphicon-chevron-up + else if memberSort === 'progressDesc' + span.progress-header.glyphicon.glyphicon-chevron-down + else + span(style='padding-left:16px;') + span.spl.progress-key.progress-key-complete(data-i18n="clans.complete_1") complete span.progress-key.progress-key-started(data-i18n="clans.started_1") started - span.progress-key.progress-key-complete(data-i18n="clans.complete_1") complete + span.progress-key(data-i18n="clans.not_started_1") not started input.expand-progress-checkbox(type='checkbox') span.spl.expand-progress-label(data-i18n="clans.exp_levels") Expand levels tbody each member in members tr - td - div - span.hero-icon-cell - span.spr.player-hero-icon(data-memberid="#{member.id}") - span.code-language-cell - if memberLanguageMap && memberLanguageMap[member.id] - span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id]) - div - a(href="/user/#{member.id}")= member.get('name') || 'Anoner' - div Level #{member.level()} - if isOwner && member.id !== clan.get('ownerID') - button.btn.btn-xs.btn-warning.remove-member-btn(data-id="#{member.id}", data-i18n="clans.rem_hero") Remove Hero - td.progress-cell + td + div + span.hero-icon-cell + span.spr.player-hero-icon(data-memberid="#{member.id}") + span.code-language-cell + if memberLanguageMap && memberLanguageMap[member.id] + span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id]) + div + a(href="/user/#{member.id}")= member.get('name') || 'Anoner' + div Level #{member.level()} + if isOwner && member.id !== clan.get('ownerID') + button.btn.btn-xs.btn-warning.remove-member-btn(data-id="#{member.id}", data-i18n="clans.rem_hero") Remove Hero + td.progress-cell + .level-progression-concepts Concepts + each concept in conceptsProgression + if userConceptsMap[member.id] && userConceptsMap[member.id][concept] === 'complete' + span.spr.progress-level-cell.progress-level-cell-complete(data-i18n="concepts." + concept) + else if userConceptsMap[member.id] && userConceptsMap[member.id][concept] === 'started' + span.spr.progress-level-cell.progress-level-cell-started(data-i18n="concepts." + concept) + else + span.spr.progress-level-cell.progress-level-cell-not-started(data-i18n="concepts." + concept) + .level-progression-levels Levels + each campaign in campaignLevelProgressions + if lastUserCampaignLevelMap[member.id] && lastUserCampaignLevelMap[member.id][campaign.ID] + div.level-progression-campaign= campaign.name + - var i = 0 - each campaign in campaignLevelProgressions - if lastUserCampaignLevelMap[member.id] && lastUserCampaignLevelMap[member.id][campaign.ID] - div.level-progression-campaign= campaign.name - - var i = 0 - - each level in campaign.levels - - i++ - - var state = null, levelInfo = null - if memberLevelStateMap[member.id][level.slug] - - levelInfo = memberLevelStateMap[member.id][level.slug].levelInfo - - state = memberLevelStateMap[member.id][level.slug].state - if state === 'complete' - span.progress-level-cell.progress-level-cell-complete(data-level-info=levelInfo) #{i} - if showExpandedProgress || i === 1 || i === lastUserCampaignLevelMap[member.id][campaign.ID].index + 1 - span.spl #{level.name} - .level-popup-container - h3 #{i}. #{levelInfo.level} - p + each level in campaign.levels + - i++ + - var state = null, levelInfo = null + if memberLevelStateMap[member.id][level.slug] + - levelInfo = memberLevelStateMap[member.id][level.slug].levelInfo + - state = memberLevelStateMap[member.id][level.slug].state + if state === 'complete' + span.progress-level-cell.progress-level-cell-complete(data-level-info=levelInfo) #{i} + if showExpandedProgress || i === 1 || i === lastUserCampaignLevelMap[member.id][campaign.ID].index + 1 + span.spl #{level.name} + .level-popup-container + h3 #{i}. #{levelInfo.level} + p + div + span(data-i18n="clans.status") Status + span.spr : + span(data-i18n="clans.complete_2") Complete + div + span(data-i18n="clans.playtime") Playtime + span : #{levelInfo.playtime}s + div + span(data-i18n="clans.last_played") Last played + span : #{levelInfo.changed} + if isOwner || me.isAdmin() + strong(data-i18n="clans.view_solution") Click to view solution. + else if state === 'started' + span.progress-level-cell.progress-level-cell-started(data-level-info=levelInfo) #{i} + if showExpandedProgress || i === 1 || i === lastUserCampaignLevelMap[member.id][campaign.ID].index + 1 + span.spl #{level.name} + .level-popup-container + h3 #{i}. #{level.name} + p + div + span(data-i18n="clans.status") Status + span.spr : + span(data-i18n="clans.started_2") Started + div + span(data-i18n="clans.playtime") Playtime + span : #{levelInfo.playtime}s + div + span(data-i18n="clans.last_played") Last played + span : #{levelInfo.changed} + if isOwner || me.isAdmin() + strong(data-i18n="clans.view_solution") Click to view solution. + else + span.progress-level-cell.level-progression-level-not-started #{i} + if showExpandedProgress || i === 1 || i === lastUserCampaignLevelMap[member.id][campaign.ID].index + 1 + span.spl #{level.name} + .level-popup-container + h3 #{i}. #{level.name} div - span(data-i18n="choose_hero.status") Status - span.spr : - span(data-i18n="clans.complete_2") Complete - div - span(data-i18n="clans.playtime") Playtime - span : #{levelInfo.playtime}s - div - span(data-i18n="clans.last_played") Last played - span : #{levelInfo.changed} - if isOwner || me.isAdmin() - strong(data-i18n="clans.view_solution") Click to view solution. - else if state === 'started' - span.progress-level-cell.progress-level-cell-started(data-level-info=levelInfo) #{i} - if showExpandedProgress || i === 1 || i === lastUserCampaignLevelMap[member.id][campaign.ID].index + 1 - span.spl #{level.name} - .level-popup-container - h3 #{i}. #{level.name} - p - div - span(data-i18n="choose_hero.status") Status - span.spr : - span(data-i18n="clans.started_2") Started - div - span(data-i18n="clans.playtime") Playtime - span : #{levelInfo.playtime}s - div - span(data-i18n="clans.last_played") Last played - span : #{levelInfo.changed} - if isOwner || me.isAdmin() - strong(data-i18n="clans.view_solution") Click to view solution. - else - span.progress-level-cell.level-progression-level-not-started #{i} - if showExpandedProgress || i === 1 || i === lastUserCampaignLevelMap[member.id][campaign.ID].index + 1 - span.spl #{level.name} - .level-popup-container - h3 #{i}. #{level.name} - div - span(data-i18n="choose_hero.status") Status - span.spr : - span(data-i18n="clans.not_started_2") Not Started - if lastUserCampaignLevelMap[member.id][campaign.ID].levelSlug === level.slug - - break + span(data-i18n="clans.status") Status + span.spr : + span(data-i18n="clans.not_started_2") Not Started + if lastUserCampaignLevelMap[member.id][campaign.ID].levelSlug === level.slug + - break //- Basic dashboard else diff --git a/app/templates/clans/clans.jade b/app/templates/clans/clans.jade index d75f511cf..a0c14444d 100644 --- a/app/templates/clans/clans.jade +++ b/app/templates/clans/clans.jade @@ -10,7 +10,7 @@ block content input(type='checkbox').private-clan-checkbox span.spl(data-i18n="clans.make_private") Make clan private span.spl ( - a.private-more-info(data-i18n="clans.subs_only") subscribers only + a.private-more-info(data-i18n="clans.private_preview") span ) p button.btn.btn-success.create-clan-btn(data-i18n="clans.create_clan") Create New Clan diff --git a/app/templates/courses/mock1/course-details.jade b/app/templates/courses/mock1/course-details.jade index e37f358af..afe981e38 100644 --- a/app/templates/courses/mock1/course-details.jade +++ b/app/templates/courses/mock1/course-details.jade @@ -11,9 +11,11 @@ block content input.student-view-checkbox(type='checkbox') span.spl Student view div TODO: fix ugly tabs - div TODO: add student progress monitoring div TODO: level concepts, status, working play button div TODO: student view + div TODO: aggregate student progress + div TODO: student level progress popups + div TODO: student concept progress div(style='border-bottom: 1px solid black;') h1= course.title @@ -64,24 +66,48 @@ block content table.table.table-condensed thead tr - th Name - th Progress + th + th + span.progress-header Progress + span.progress-key.progress-key-complete complete + span.progress-key.progress-key-started started + span.progress-key not started + if maxLastStartedIndex > 30 + input.expand-progress-checkbox(type='checkbox') + span.spl.expand-progress-label(data-i18n="clans.exp_levels") Expand levels tbody each student in instance.students tr - td - a= student - td TODO: level progress + td + a= student + td.progress-cell + - var i = 0 + each level in course.levels + if i <= userLevelStateMap[student].lastCompletedIndex + span.progress-level-cell.progress-level-cell-complete #{i + 1} + if showExpandedProgress || i === 0 || i === course.levels.length - 1 + span.spl #{level} + else if i <= userLevelStateMap[student].lastStartedIndex + span.progress-level-cell.progress-level-cell-started #{i + 1} + if showExpandedProgress || i === 1 || i === userLevelStateMap[student].lastStartedIndex + span.spl #{level} + else + span.progress-level-cell.level-progression-level-not-started #{i + 1} + if showExpandedProgress || i === 1 || i === userLevelStateMap[student].lastStartedIndex + span.spl #{level} + if i === maxLastStartedIndex + - break + - i++ .tab-pane#invite(role='tabpanel') p Invite students to join this class. if course.title !== 'Introduction to Computer Science' p Student unlock code: #{instance.code} p Class capacity: 34/50 - textarea.textarea-emails(rows=3, placeholder="Enter student emails to invite, one per line") div button.btn.btn-success.btn-invite Send Invites + .tab-pane#levels(role='tabpanel') table.table.table-condensed thead diff --git a/app/templates/teachers.jade b/app/templates/teachers.jade index e540565c7..925f0510b 100644 --- a/app/templates/teachers.jade +++ b/app/templates/teachers.jade @@ -48,11 +48,25 @@ block content span.spl(data-i18n="teachers.monitor_progress_4") p(data-i18n="teachers.monitor_progress_5") h4(data-i18n="teachers.sub_includes_7") - p(data-i18n="teachers.private_clans_1") + ul + li + strong Track concepts + span.spl learned by each student + li Track levels completed for each student + li + span See your students' + strong.spl solutions + li Sort students by name or progress + li + strong Requires invitation + span.spl to join + p + img(src='/images/pages/clans/dashboard_preview.png' height='400') p span.spr(data-i18n="teachers.private_clans_2") a(href='/clans', data-i18n="clans.clan") span(data-i18n="teachers.private_clans_3") + p Private clans require a subscription to create or join. h3(data-i18n="teachers.material_title") if me.get('chinaVersion') @@ -78,19 +92,19 @@ block content tbody tr td Syntax - td If/Else + td If Statements td Arithmetic td Object Literals tr td Methods td Relational Operators - td While-loops + td While Loops td Remote Method Invocation tr td Parameters td Object Properties - td Break - td For-Loops + td Break Statements + td For Loops tr td Strings td Input Handling diff --git a/app/views/clans/ClanDetailsView.coffee b/app/views/clans/ClanDetailsView.coffee index 6946ae4cd..0ffa55e40 100644 --- a/app/views/clans/ClanDetailsView.coffee +++ b/app/views/clans/ClanDetailsView.coffee @@ -27,6 +27,8 @@ module.exports = class ClanDetailsView extends RootView 'click .edit-name-save-btn': 'onEditNameSave' 'click .join-clan-btn': 'onJoinClan' 'click .leave-clan-btn': 'onLeaveClan' + 'click .member-header': 'onClickMemberHeader' + 'click .progress-header': 'onClickProgressHeader' 'click .progress-level-cell': 'onClickLevel' 'click .remove-member-btn': 'onRemoveMember' 'mouseenter .progress-level-cell': 'onMouseEnterPoint' @@ -41,6 +43,7 @@ module.exports = class ClanDetailsView extends RootView initData: -> @showExpandedProgress = false + @memberSort = 'nameAsc' @stats = {} @campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign, comparator:'_id' }) @@ -67,6 +70,7 @@ module.exports = class ClanDetailsView extends RootView context = super() context.campaignLevelProgressions = @campaignLevelProgressions ? [] context.clan = @clan + context.conceptsProgression = @conceptsProgression ? [] if application.isProduction() context.joinClanLink = "https://codecombat.com/clans/#{@clanID}" else @@ -76,21 +80,25 @@ module.exports = class ClanDetailsView extends RootView context.memberLanguageMap = @memberLanguageMap context.memberLevelStateMap = @memberLevelMap ? {} context.memberMaxLevelCount = @memberMaxLevelCount - context.members = @members?.models + context.memberSort = @memberSort context.isOwner = @clan.get('ownerID') is me.id context.isMember = @clanID in (me.get('clans') ? []) context.stats = @stats # Find last campaign level for each user + # TODO: why do we do this for every render? + highestUserLevelCountMap = {} lastUserCampaignLevelMap = {} maxLastUserCampaignLevel = 0 + userConceptsMap = {} if @campaigns.loaded + levelCount = 0 for campaign in @campaigns.models campaignID = campaign.id lastLevelIndex = 0 for levelID, level of campaign.get('levels') levelSlug = level.slug - for member in context.members + for member in @members?.models ? [] if context.memberLevelStateMap[member.id]?[levelSlug] lastUserCampaignLevelMap[member.id] ?= {} lastUserCampaignLevelMap[member.id][campaignID] ?= {} @@ -98,10 +106,20 @@ module.exports = class ClanDetailsView extends RootView levelSlug: levelSlug index: lastLevelIndex maxLastUserCampaignLevel = lastLevelIndex if lastLevelIndex > maxLastUserCampaignLevel + if level.concepts? + userConceptsMap[member.id] ?= {} + for concept in level.concepts + continue if userConceptsMap[member.id][concept] is 'complete' + userConceptsMap[member.id][concept] = context.memberLevelStateMap[member.id][levelSlug].state + highestUserLevelCountMap[member.id] = levelCount lastLevelIndex++ + levelCount++ + @sortMembers(highestUserLevelCountMap, userConceptsMap) if @clan.get('dashboardType') is 'premium' + context.members = @members?.models ? [] context.lastUserCampaignLevelMap = lastUserCampaignLevelMap context.showExpandedProgress = maxLastUserCampaignLevel <= 30 or @showExpandedProgress + context.userConceptsMap = userConceptsMap context afterRender: -> @@ -114,6 +132,42 @@ module.exports = class ClanDetailsView extends RootView @memberAchievements.fetch cache: false @memberSessions.fetch cache: false + sortMembers: (highestUserLevelCountMap, userConceptsMap) -> + # Progress sort precedence: most completed concepts, most started concepts, most levels, name sort + return unless @members? and @memberSort? + switch @memberSort + when "nameDesc" + @members.comparator = (a, b) -> return (b.get('name') or 'Anoner').localeCompare(a.get('name') or 'Anoner') + when "progressAsc" + @members.comparator = (a, b) -> + aComplete = (concept for concept, state of userConceptsMap[a.id] when state is 'complete') + bComplete = (concept for concept, state of userConceptsMap[b.id] when state is 'complete') + aStarted = (concept for concept, state of userConceptsMap[a.id] when state is 'started') + bStarted = (concept for concept, state of userConceptsMap[b.id] when state is 'started') + if aComplete < bComplete then return -1 + else if aComplete > bComplete then return 1 + else if aStarted < bStarted then return -1 + else if aStarted > bStarted then return 1 + if highestUserLevelCountMap[a.id] < highestUserLevelCountMap[b.id] then return -1 + else if highestUserLevelCountMap[a.id] > highestUserLevelCountMap[b.id] then return 1 + (a.get('name') or 'Anoner').localeCompare(b.get('name') or 'Anoner') + when "progressDesc" + @members.comparator = (a, b) -> + aComplete = (concept for concept, state of userConceptsMap[a.id] when state is 'complete') + bComplete = (concept for concept, state of userConceptsMap[b.id] when state is 'complete') + aStarted = (concept for concept, state of userConceptsMap[a.id] when state is 'started') + bStarted = (concept for concept, state of userConceptsMap[b.id] when state is 'started') + if aComplete > bComplete then return -1 + else if aComplete < bComplete then return 1 + else if aStarted > bStarted then return -1 + else if aStarted < bStarted then return 1 + if highestUserLevelCountMap[a.id] > highestUserLevelCountMap[b.id] then return -1 + else if highestUserLevelCountMap[a.id] < highestUserLevelCountMap[b.id] then return 1 + (b.get('name') or 'Anoner').localeCompare(a.get('name') or 'Anoner') + else + @members.comparator = (a, b) -> return (a.get('name') or 'Anoner').localeCompare(b.get('name') or 'Anoner') + @members.sort() + updateHeroIcons: -> return unless @members?.models? for member in @members.models @@ -124,6 +178,7 @@ module.exports = class ClanDetailsView extends RootView onCampaignSync: -> return unless @campaigns.loaded @campaignLevelProgressions = [] + @conceptsProgression = [] for campaign in @campaigns.models continue if campaign.get('slug') is 'auditions' campaignLevelProgression = @@ -136,6 +191,9 @@ module.exports = class ClanDetailsView extends RootView ID: levelID slug: level.slug name: level.name + if level.concepts? + for concept in level.concepts + @conceptsProgression.push concept unless concept in @conceptsProgression @campaignLevelProgressions.push campaignLevelProgression @render?() @@ -272,6 +330,14 @@ module.exports = class ClanDetailsView extends RootView success: (model, response, options) => @refreshData() @supermodel.addRequestResource( 'leave_clan', options).load() + onClickMemberHeader: (e) -> + @memberSort = if @memberSort is 'nameAsc' then 'nameDesc' else 'nameAsc' + @render?() + + onClickProgressHeader: (e) -> + @memberSort = if @memberSort is 'progressAsc' then 'progressDesc' else 'progressAsc' + @render?() + onRemoveMember: (e) -> return unless window.confirm("Remove Hero?") if memberID = $(e.target).data('id') diff --git a/app/views/clans/ClansView.coffee b/app/views/clans/ClansView.coffee index 126086f2c..50c3d806d 100644 --- a/app/views/clans/ClansView.coffee +++ b/app/views/clans/ClansView.coffee @@ -73,9 +73,15 @@ module.exports = class ClansView extends RootView setupPrivateInfoPopover: -> popoverTitle = "

Private Clans

" - popoverContent = "

Invite only

" - popoverContent += "

Detailed dashboard:

" - popoverContent += "

" + popoverContent = "" + popoverContent += "

" + popoverContent += "

Private clans require a subscription to create or join.

" @$el.find('.private-more-info').popover( animation: true html: true diff --git a/app/views/core/SubscribeModal.coffee b/app/views/core/SubscribeModal.coffee index 3a82645e9..d9e3ee8a1 100644 --- a/app/views/core/SubscribeModal.coffee +++ b/app/views/core/SubscribeModal.coffee @@ -78,6 +78,7 @@ module.exports = class SubscribeModal extends ModalView setupPaymentMethodsInfoPopover: -> popoverTitle = $.i18n.t('subscribe.payment_methods_title') + popoverTitle += '' popoverContent = "

" + $.i18n.t('subscribe.payment_methods_blurb1') + "

" popoverContent += "

" + $.i18n.t('subscribe.payment_methods_blurb2') + " support@codecombat.com." @$el.find('#payment-methods-info').popover( diff --git a/app/views/courses/mock1/CourseDetailsView.coffee b/app/views/courses/mock1/CourseDetailsView.coffee index 328b1c2fa..1e0d3d83f 100644 --- a/app/views/courses/mock1/CourseDetailsView.coffee +++ b/app/views/courses/mock1/CourseDetailsView.coffee @@ -7,9 +7,10 @@ module.exports = class CourseDetailsView extends RootView template: template events: + 'change .expand-progress-checkbox': 'onExpandedProgressCheckbox' + 'change .select-session': 'onChangeSession' 'click .edit-class-name-btn': 'onClickEditClassName' 'click .edit-description-btn': 'onClickEditClassDescription' - 'change .select-session': 'onChangeSession' constructor: (options, @courseID) -> super options @@ -20,21 +21,43 @@ module.exports = class CourseDetailsView extends RootView context.course = @course ? {} context.instance = @instances?[@currentInstanceIndex] ? {} context.instances = @instances ? [] + context.maxLastStartedIndex = @maxLastStartedIndex ? 0 + context.userLevelStateMap = @userLevelStateMap ? {} + context.showExpandedProgress = @maxLastStartedIndex <= 30 or @showExpandedProgress context initData: -> mockData = require 'views/courses/mock1/CoursesMockData' @course = mockData.courses[@courseID] - # @instance = mockData.instances[_.random(0, mockData.instances.length - 1)] @currentInstanceIndex = 0 @instances = mockData.instances + @updateLevelMaps() + + updateLevelMaps: -> + @userLevelStateMap = {} + @maxLastStartedIndex = -1 + for student in @instances?[@currentInstanceIndex].students + lastCompletedIndex = _.random(0, @course.levels.length) + lastStartedIndex = lastCompletedIndex + 1 + @userLevelStateMap[student] = + lastCompletedIndex: lastCompletedIndex + lastStartedIndex: lastStartedIndex + @maxLastStartedIndex = lastStartedIndex if lastStartedIndex > @maxLastStartedIndex onChangeSession: (e) -> + @showExpandedProgress = false newSessionValue = $(e.target).val() for val, index in @instances when val.name is newSessionValue @currentInstanceIndex = index + @updateLevelMaps() @render?() + onExpandedProgressCheckbox: (e) -> + @showExpandedProgress = $('.expand-progress-checkbox').prop('checked') + # TODO: why does render reset the checkbox to be unchecked? + @render?() + $('.expand-progress-checkbox').attr('checked', @showExpandedProgress) + onClickEditClassName: (e) -> alert 'TODO: Popup for editing name for this course session'