diff --git a/README.md b/README.md index d6e11e265..0e451ff48 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ so we can accept your pull requests. It is easy. ![Josh Callebaut](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/avatars/Josh%20Callebaut/josh_callebaut_100.png "Josh Callebaut") ![Michael Schmatz](http://codecombat.com/images/pages/about/michael_small.png "Michael Schmatz") ![Josh Lee](http://codecombat.com/images/pages/about/josh_small.png "Josh Lee") +![Dan TDM](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/avatars/Dan_TDM/dan_tdm_100.png "Dan TDM") ![Alex Cotsarelis](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/avatars/Alex%20Cotsarelis/alex_100.png "Alex Cotsarelis") ![Alex Crooks](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/avatars/Alex%20Crooks/alex_100.png "Alex Crooks") ![Alexandru Caciulescu](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/avatars/Alexandru%20Caciulescu/alexandru_100.png "Alexandru Caciulescu") diff --git a/app/collections/LevelSessions.coffee b/app/collections/LevelSessions.coffee index dbbc343a5..20de80e42 100644 --- a/app/collections/LevelSessions.coffee +++ b/app/collections/LevelSessions.coffee @@ -5,12 +5,6 @@ module.exports = class LevelSessionCollection extends CocoCollection url: '/db/level.session' model: LevelSession - fetchMineForCourseInstance: (courseInstanceID, options) -> - options = _.extend({ - url: "/db/course_instance/#{courseInstanceID}/my-course-level-sessions" - }, options) - @fetch(options) - fetchForCourseInstance: (courseInstanceID, options) -> options = _.extend({ url: "/db/course_instance/#{courseInstanceID}/my-course-level-sessions" diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index 6a15df091..02b0b0753 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -488,7 +488,7 @@ module.exports = class LevelLoader extends CocoClass @world.difficulty = @session?.get('state')?.difficulty ? 0 if @observing @world.difficulty = Math.max 0, @world.difficulty - 1 # Show the difficulty they won, not the next one. - serializedLevel = @level.serialize(@supermodel, @session, @opponentSession) + serializedLevel = @level.serialize {@supermodel, @session, @opponentSession, @headless, @sessionless} @world.loadFromLevel serializedLevel, false console.log 'World has been initialized from level loader.' if LOG diff --git a/app/lib/simulator/Simulator.coffee b/app/lib/simulator/Simulator.coffee index 473ea06af..16e48dd6a 100644 --- a/app/lib/simulator/Simulator.coffee +++ b/app/lib/simulator/Simulator.coffee @@ -226,7 +226,7 @@ module.exports = class Simulator extends CocoClass @levelLoader = null setupGod: -> - @god.setLevel @level.serialize(@supermodel, @session, @otherSession) + @god.setLevel @level.serialize {@supermodel, @session, @otherSession, headless: true, sessionless: false} @god.setLevelSessionIDs (session.sessionID for session in @task.getSessions()) @god.setWorldClassMap @world.classMap @god.setGoalManager new GoalManager @world, @level.get('goals'), null, {headless: true} diff --git a/app/models/Level.coffee b/app/models/Level.coffee index ab02889d3..acad5b8a6 100644 --- a/app/models/Level.coffee +++ b/app/models/Level.coffee @@ -12,7 +12,8 @@ module.exports = class Level extends CocoModel urlRoot: '/db/level' editableByArtisans: true - serialize: (supermodel, session, otherSession, cached=false) -> + serialize: (options) -> + {supermodel, session, otherSession, @headless, @sessionless, cached=false} = options o = @denormalize supermodel, session, otherSession # hot spot to optimize # Figure out Components @@ -146,7 +147,7 @@ module.exports = class Level extends CocoModel levelThang.components.push placeholderComponent # Load the user's chosen hero AFTER getting stats from default char - if /Hero Placeholder/.test(levelThang.id) and @get('type', true) in ['course'] + if /Hero Placeholder/.test(levelThang.id) and @get('type', true) in ['course'] and not @headless and not @sessionless heroThangType = me.get('heroConfig')?.thangType or ThangType.heroes.captain levelThang.thangType = heroThangType if heroThangType diff --git a/app/schemas/models/classroom.schema.coffee b/app/schemas/models/classroom.schema.coffee index c06aebe2e..9f3e63ca7 100644 --- a/app/schemas/models/classroom.schema.coffee +++ b/app/schemas/models/classroom.schema.coffee @@ -25,6 +25,7 @@ _.extend ClassroomSchema.properties, levels: c.array { title: 'Levels' }, c.object { title: 'Level' }, { practice: {type: 'boolean'} practiceThresholdMinutes: {type: 'number'} + shareable: {type: 'boolean'} type: c.shortString() original: c.objectId() name: {type: 'string'} diff --git a/app/schemas/models/level.coffee b/app/schemas/models/level.coffee index 5d605948f..707bf6cd4 100644 --- a/app/schemas/models/level.coffee +++ b/app/schemas/models/level.coffee @@ -267,7 +267,7 @@ LevelSchema = c.object { victory: {} type: 'hero' goals: [ - {id: 'ogres-die', name: 'Ogres must die.', killThangs: ['ogres'], worldEndsAfter: 3} + {id: 'ogres-die', name: 'Defeat the ogres.', killThangs: ['ogres'], worldEndsAfter: 3} {id: 'humans-survive', name: 'Your hero must survive.', saveThangs: ['Hero Placeholder'], howMany: 1, worldEndsAfter: 3, hiddenGoal: true} ] concepts: ['basic_syntax'] @@ -325,6 +325,7 @@ _.extend LevelSchema.properties, replayable: {type: 'boolean', title: 'Replayable', description: 'Whether this (hero) level infinitely scales up its difficulty and can be beaten over and over for greater rewards.'} buildTime: {type: 'number', description: 'How long it has taken to build this level.'} practice: { type: 'boolean' } + shareable: { type: 'boolean', title: 'Shareable' } practiceThresholdMinutes: {type: 'number', description: 'Players with larger playtimes may be directed to a practice level.'} # Admin flags diff --git a/app/styles/modal/create-account-modal/basic-info-view.sass b/app/styles/modal/create-account-modal/basic-info-view.sass index a344ea89e..cf4a202d0 100644 --- a/app/styles/modal/create-account-modal/basic-info-view.sass +++ b/app/styles/modal/create-account-modal/basic-info-view.sass @@ -15,6 +15,16 @@ .form-group text-align: left + margin: 0 + + input + max-height: 5vh + + .form-container + > .form-group, > .row + max-height: 84px + flex-grow: 1 + align-self: flex-start .btn-illustrated img // Undo previous opacity-toggling hover behavior diff --git a/app/styles/modal/create-account-modal/choose-account-type-view.sass b/app/styles/modal/create-account-modal/choose-account-type-view.sass index 555f3970a..4e7a3408d 100644 --- a/app/styles/modal/create-account-modal/choose-account-type-view.sass +++ b/app/styles/modal/create-account-modal/choose-account-type-view.sass @@ -1,8 +1,16 @@ @import "app/styles/style-flat-variables" #choose-account-type-view + .choose-type-title + display: flex + flex-direction: column + flex-grow: 0.5 + justify-content: flex-end + + h4 + padding-bottom: 10px + .path-cards - margin-top: 15px display: flex .path-card ~ .path-card @@ -13,7 +21,8 @@ flex-direction: column justify-content: space-between width: 235px - min-height: 340px + height: 340px + max-height: 42vh border-style: solid border-width: thin border-radius: 5px @@ -35,6 +44,7 @@ align-items: center justify-content: center height: 50px + max-height: 5vh color: white font-weight: bold text-align: center @@ -43,7 +53,8 @@ flex-grow: 1 display: flex flex-direction: column - margin: 50px 20px 0 + justify-content: center + margin: 0 20px ul align-self: center @@ -55,14 +66,26 @@ left: -5px .card-footer - margin: 20px + margin: 0 20px 20px + min-height: 62px + display: flex + flex-direction: column + justify-content: flex-end .individual-section - margin-top: 50px + display: flex + flex-grow: 1 + flex-direction: column + align-items: center + justify-content: center max-width: 425px .individual-title font-weight: bold + + .individual-description + margin: 0 + flex-grow: 0.2 .text-h6 color: white diff --git a/app/styles/modal/create-account-modal/create-account-modal.sass b/app/styles/modal/create-account-modal/create-account-modal.sass index f40cb6f46..34607c9f4 100644 --- a/app/styles/modal/create-account-modal/create-account-modal.sass +++ b/app/styles/modal/create-account-modal/create-account-modal.sass @@ -17,6 +17,7 @@ display: flex flex-direction: column height: 850px + max-height: 90vh width: 850px text-align: center padding: 0 @@ -44,6 +45,7 @@ align-items: center justify-content: flex-end height: 100px + max-height: 10.5vh padding: 0 background-color: $navy diff --git a/app/templates/admin/analytics.jade b/app/templates/admin/analytics.jade index 51dc0c100..f5f7a1d4e 100644 --- a/app/templates/admin/analytics.jade +++ b/app/templates/admin/analytics.jade @@ -18,7 +18,7 @@ block content .col-md-5.big-stat.classroom-active-users div.description Classroom Monthly Active Users if view.activeUsers.length > 0 - - var classroomBigMAU = 0; + - var classroomBigMAU = 0; each count, event in view.activeUsers[0].events if event.indexOf('MAU classroom') >= 0 - classroomBigMAU += count; @@ -26,24 +26,24 @@ block content .col-md-5.big-stat.campaign-active-users div.description Campaign Monthly Active Users if view.activeUsers.length > 0 - - var campaignBigMAU = 0; + - var campaignBigMAU = 0; each count, event in view.activeUsers[0].events if event.indexOf('MAU campaign') >= 0 - campaignBigMAU += count; div.count= campaignBigMAU ul.nav.nav-tabs - li.active + li.active a(data-target="#tab_kpis", data-toggle="tab") KPIs - li + li a(data-target="#tab_active_classes", data-toggle="tab") Active Classes - li + li a(data-target="#tab_revenue", data-toggle="tab") Revenue - li + li a(data-target="#tab_classroom", data-toggle="tab") Classroom - li + li a(data-target="#tab_campaign", data-toggle="tab") Campaign - li + li a(data-target="#tab_campaign_vs_classroom", data-toggle="tab") Campaign vs Classroom .tab-content @@ -165,7 +165,7 @@ block content .small Paid teacher: at least one paid student in course instance .small Trial teacher: at least one trial student in course instance, and no paid students .small Free teacher: no paid students, no trial students - .small Paid status takes precedent over furthest course, so teacher furthest course is furthest course of highest paid status student + .small Paid status takes precedent over furthest course, so teacher furthest course is furthest course of highest paid status student if view.courseDistributionsRecent table.table.table-striped.table-condensed tr @@ -267,9 +267,9 @@ block content h1 Active Users if view.activeUsers.length > 0 - var eventNames = []; - each count, event in view.activeUsers[0].events + each event in view.activeUserEventNames if event.indexOf('classroom') >= 0 - - eventNames.push(event) + - eventNames.push(event); - eventNames.sort(function (a, b) {return a.localeCompare(b);}); table.table.table-striped.table-condensed tr @@ -293,7 +293,7 @@ block content each day in view.enrollmentDays tr td= day - if view.dayEnrollmentsMap[day] + if view.dayEnrollmentsMap[day] td= view.dayEnrollmentsMap[day].paidIssued || 0 td= view.dayEnrollmentsMap[day].paidRedeemed || 0 td= view.dayEnrollmentsMap[day].trialIssued || 0 @@ -322,9 +322,9 @@ block content h1 Active Users if view.activeUsers.length > 0 - var eventNames = []; - each count, event in view.activeUsers[0].events + each event in view.activeUserEventNames if event.indexOf('campaign') >= 0 - - eventNames.push(event) + - eventNames.push(event); - eventNames.sort(function (a, b) {return a.localeCompare(b);}); table.table.table-striped.table-condensed tr @@ -346,27 +346,13 @@ block content h1 Active Users if view.activeUsers.length > 0 - - var eventNames = []; - each count, event in view.activeUsers[0].events - - eventNames.push(event) - - eventNames.sort(function (a, b) { - - if (a.indexOf('campaign') == b.indexOf('campaign') || a.indexOf('classroom') == b.indexOf('classroom')) { - - return a.localeCompare(b); - - } - - else if (a.indexOf('campaign') > b.indexOf('campaign')) { - - return 1; - - } - - else { - - return -1; - - } - - }); table.table.table-striped.table-condensed tr th(style='min-width:85px;') Day - each eventName in eventNames + each eventName in view.activeUserEventNames th= eventName each activeUser in view.activeUsers tr td= activeUser.day - each eventName in eventNames + each eventName in view.activeUserEventNames td= activeUser.events[eventName] || 0 diff --git a/app/templates/core/create-account-modal/choose-account-type-view.jade b/app/templates/core/create-account-modal/choose-account-type-view.jade index af9917379..67a827854 100644 --- a/app/templates/core/create-account-modal/choose-account-type-view.jade +++ b/app/templates/core/create-account-modal/choose-account-type-view.jade @@ -1,5 +1,5 @@ .modal-body-content - h4 + h4.choose-type-title span(data-i18n="signup.choose_type") .path-cards .path-card.navy diff --git a/app/templates/core/create-account-modal/confirmation-view.jade b/app/templates/core/create-account-modal/confirmation-view.jade index 2ab37d834..cd38a0eaf 100644 --- a/app/templates/core/create-account-modal/confirmation-view.jade +++ b/app/templates/core/create-account-modal/confirmation-view.jade @@ -10,7 +10,7 @@ else p(data-i18n="signup.confirm_individual_blurb") - .signup-info-box-wrapper.m-y-3 + .signup-info-box-wrapper .text-burgandy(data-i18n="signup.write_this_down") .signup-info-box.text-center if me.get('name') @@ -32,4 +32,4 @@ span(data-i18n="general.email") | : #{me.get('email')} - button#start-btn.btn.btn-navy.btn-lg.m-y-3(data-i18n="signup.start_playing") + button#start-btn.btn.btn-navy.btn-lg.m-t-3(data-i18n="signup.start_playing") diff --git a/app/templates/core/create-account-modal/segment-check-view.jade b/app/templates/core/create-account-modal/segment-check-view.jade index 2311bdd36..e79b1eeee 100644 --- a/app/templates/core/create-account-modal/segment-check-view.jade +++ b/app/templates/core/create-account-modal/segment-check-view.jade @@ -45,7 +45,7 @@ form.modal-body.segment-check option(value='',data-i18n="calendar.day") for day in _.range(1,32) option(selected=(day == view.signupState.get('birthdayDay'))) #{day} - select#birthday-year-input.input-large.form-control(name="birthdayYear", style="width: 90px;") + select#birthday-year-input.input-large.form-control(name="birthdayYear", style="width: 90px; float: left") option(value='',data-i18n="calendar.year") - var thisYear = new Date().getFullYear() for year in _.range(thisYear, thisYear - 100, -1) diff --git a/app/templates/courses/teacher-class-view.jade b/app/templates/courses/teacher-class-view.jade index 8faf7e7a4..e6e025574 100644 --- a/app/templates/courses/teacher-class-view.jade +++ b/app/templates/courses/teacher-class-view.jade @@ -182,7 +182,8 @@ mixin studentsTab th.checkbox-col.select-all.small.text-center span(data-i18n="teacher.select_all") .checkbox-flat - input(type='checkbox' id='checkbox-all-students') + - var allStudentsChecked = _.all(state.get('checkboxStates')) + input(type='checkbox', id='checkbox-all-students', checked=allStudentsChecked) label.checkmark(for='checkbox-all-students') th +sortButtons @@ -201,7 +202,7 @@ mixin studentRow(student) tr.student-row.alternating-background td.checkbox-col.student-checkbox .checkbox-flat - input(type='checkbox' id='checkbox-student-' + student.id, data-student-id=student.id) + input(type='checkbox' id='checkbox-student-' + student.id, data-student-id=student.id, checked=state.get('checkboxStates')[student.id]) label.checkmark(for='checkbox-student-' + student.id) td.student-info-col .student-info diff --git a/app/templates/editor/level/edit.jade b/app/templates/editor/level/edit.jade index 5496527d0..e64012f06 100644 --- a/app/templates/editor/level/edit.jade +++ b/app/templates/editor/level/edit.jade @@ -132,8 +132,9 @@ block outer_content div.tab-pane#editor-level-tasks-tab-view - div.tab-pane#editor-level-patches - .patches-view + div.tab-pane#editor-level-patches.nano + .nano-content + .patches-view div.tab-pane#related-achievements-view diff --git a/app/views/admin/AnalyticsView.coffee b/app/views/admin/AnalyticsView.coffee index f23c0e58a..c38e9ba28 100644 --- a/app/views/admin/AnalyticsView.coffee +++ b/app/views/admin/AnalyticsView.coffee @@ -73,6 +73,7 @@ module.exports = class AnalyticsView extends RootView # Add campaign/classroom DAU 30-day averages and daily totals campaignDauTotals = [] classroomDauTotals = [] + eventMap = {} for entry in @activeUsers day = entry.day campaignDauTotal = 0 @@ -82,18 +83,31 @@ module.exports = class AnalyticsView extends RootView campaignDauTotal += count else if event.indexOf('DAU classroom') >= 0 classroomDauTotal += count + eventMap[event] = true entry.events['DAU campaign total'] = campaignDauTotal + eventMap['DAU campaign total'] = true campaignDauTotals.unshift(campaignDauTotal) campaignDauTotals.pop() while campaignDauTotals.length > 30 if campaignDauTotals.length is 30 entry.events['DAU campaign 30-day average'] = Math.round(_.reduce(campaignDauTotals, (a, b) -> a + b) / 30) + eventMap['DAU campaign 30-day average'] = true entry.events['DAU classroom total'] = classroomDauTotal + eventMap['DAU classroom total'] = true classroomDauTotals.unshift(classroomDauTotal) classroomDauTotals.pop() while classroomDauTotals.length > 30 if classroomDauTotals.length is 30 entry.events['DAU classroom 30-day average'] = Math.round(_.reduce(classroomDauTotals, (a, b) -> a + b) / 30) + eventMap['DAU classroom 30-day average'] = true @activeUsers.sort (a, b) -> b.day.localeCompare(a.day) + @activeUserEventNames = Object.keys(eventMap) + @activeUserEventNames.sort (a, b) -> + if a.indexOf('campaign') is b.indexOf('campaign') or a.indexOf('classroom') is b.indexOf('classroom') + a.localeCompare(b) + else if a.indexOf('campaign') > b.indexOf('campaign') + 1 + else + -1 @updateAllKPIChartData() @updateActiveUsersChartData() @@ -134,13 +148,13 @@ module.exports = class AnalyticsView extends RootView return unless @revenue.length > 0 # Add monthly recurring revenue values - + # For each daily group, add up monthly values walking forward through time, and add to revenue groups monthlyDailyGroupMap = {} dailyGroupIndexMap = {} for group, i in @revenueGroups monthlyDailyGroupMap[group.replace('DRR', 'MRR')] = group - dailyGroupIndexMap[group] = i + dailyGroupIndexMap[group] = i for monthlyGroup, dailyGroup of monthlyDailyGroupMap monthlyValues = [] for i in [@revenue.length-1..0] @@ -183,7 +197,7 @@ module.exports = class AnalyticsView extends RootView @supermodel.addRequestResource({ url: '/db/prepaid/-/courses' method: 'POST' - data: {project: {maxRedeemers: 1, properties: 1, redeemers: 1}} + data: {project: {endDate: 1, maxRedeemers: 1, properties: 1, redeemers: 1}} success: (prepaids) => paidDayMaxMap = {} paidDayRedeemedMap = {} @@ -201,14 +215,13 @@ module.exports = class AnalyticsView extends RootView redeemDay = redeemer.date.substring(0, 10) trialDayRedeemedMap[redeemDay] ?= 0 trialDayRedeemedMap[redeemDay]++ - else + else if not prepaid.endDate? or new Date(prepaid.endDate) > new Date() paidDayMaxMap[day] ?= 0 paidDayMaxMap[day] += prepaid.maxRedeemers for redeemer in prepaid.redeemers redeemDay = redeemer.date.substring(0, 10) paidDayRedeemedMap[redeemDay] ?= 0 paidDayRedeemedMap[redeemDay]++ - @dayEnrollmentsMap = {} @paidCourseTotalEnrollments = [] for day, count of paidDayMaxMap @@ -218,7 +231,7 @@ module.exports = class AnalyticsView extends RootView @paidCourseTotalEnrollments.sort (a, b) -> a.day.localeCompare(b.day) @paidCourseRedeemedEnrollments = [] for day, count of paidDayRedeemedMap - @paidCourseRedeemedEnrollments.push({day: day, count: count}) + @paidCourseRedeemedEnrollments.push({day: day, count: count}) @dayEnrollmentsMap[day] ?= {paidIssued: 0, paidRedeemed: 0, trialIssued: 0, trialRedeemed: 0} @dayEnrollmentsMap[day].paidRedeemed += count @paidCourseRedeemedEnrollments.sort (a, b) -> a.day.localeCompare(b.day) @@ -239,7 +252,7 @@ module.exports = class AnalyticsView extends RootView }, 0).load() @courses = new CocoCollection([], { url: "/db/course", model: Course}) - @courses.comparator = "_id" + @courses.comparator = "_id" @listenToOnce @courses, 'sync', @onCoursesSync @supermodel.loadCollection(@courses) @@ -276,7 +289,7 @@ module.exports = class AnalyticsView extends RootView studentFurthestCourseMap = {} studentPaidStatusMap = {} for courseInstance in data.courseInstances - continue if utils.objectIdToDate(courseInstance._id) < startDate + continue if utils.objectIdToDate(courseInstance._id) < startDate courseID = courseInstance.courseID teacherID = courseInstance.ownerID for studentID in courseInstance.members @@ -306,7 +319,7 @@ module.exports = class AnalyticsView extends RootView # Paid teacher: at least one paid student # Trial teacher: at least one trial student in course instance, and no paid students # Free teacher: no paid students, no trial students - # Teacher furthest course is furthest course of highest paid status student + # Teacher furthest course is furthest course of highest paid status student teacherFurthestCourseMap = {} teacherPaidStatusMap = {} for teacher, students of teacherStudentsMap @@ -368,14 +381,17 @@ module.exports = class AnalyticsView extends RootView # Trim points preceding days if points.length and days.length and points[0].day.localeCompare(days[0]) < 0 - for point, i in points - if point.day.localeCompare(days[0]) >= 0 - points.splice(0, i) - break + if points[points.length - 1].day.localeCompare(days[0]) < 0 + points = [] + else + for point, i in points + if point.day.localeCompare(days[0]) >= 0 + points.splice(0, i) + break # Ensure points for each day for day, i in days - if points.length <= i or points[i].day isnt day + if points.length <= i or points[i]?.day isnt day prevY = if i > 0 then points[i - 1].y else 0.0 points.splice i, 0, day: day @@ -534,7 +550,7 @@ module.exports = class AnalyticsView extends RootView day = entry.day for event, count of entry.events eventDataMap[event] ?= [] - eventDataMap[event].push + eventDataMap[event].push day: entry.day value: count @@ -550,7 +566,7 @@ module.exports = class AnalyticsView extends RootView lines.push points: points description: event - lineColor: @lineColors[colorIndex++ % @lineColors.length] + lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 showYScale: showYScale @@ -577,7 +593,7 @@ module.exports = class AnalyticsView extends RootView day = entry.day for event, count of entry.events eventDataMap[event] ?= [] - eventDataMap[event].push + eventDataMap[event].push day: entry.day value: count @@ -591,7 +607,7 @@ module.exports = class AnalyticsView extends RootView @campaignVsClassroomMonthlyActiveUsersRecentChartLines.push points: points description: event - lineColor: @lineColors[colorIndex++ % @lineColors.length] + lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 showYScale: true @@ -601,7 +617,7 @@ module.exports = class AnalyticsView extends RootView @campaignVsClassroomMonthlyActiveUsersRecentChartLines.push points: points description: event - lineColor: @lineColors[colorIndex++ % @lineColors.length] + lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 showYScale: false @@ -619,7 +635,7 @@ module.exports = class AnalyticsView extends RootView @campaignVsClassroomMonthlyActiveUsersChartLines.push points: points description: event - lineColor: @lineColors[colorIndex++ % @lineColors.length] + lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 showYScale: true @@ -629,7 +645,7 @@ module.exports = class AnalyticsView extends RootView @campaignVsClassroomMonthlyActiveUsersChartLines.push points: points description: event - lineColor: @lineColors[colorIndex++ % @lineColors.length] + lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 showYScale: false @@ -648,16 +664,14 @@ module.exports = class AnalyticsView extends RootView dailyMax = 0 data = [] - total = 0 for entry in @paidCourseTotalEnrollments - total += entry.count data.push day: entry.day - value: total + value: entry.count points = @createLineChartPoints(days, data) @enrollmentsChartLines.push points: points - description: 'Total paid enrollments issued' + description: 'Paid enrollments issued' lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 @@ -666,16 +680,14 @@ module.exports = class AnalyticsView extends RootView dailyMax = _.max([dailyMax, _.max(points, 'y').y]) data = [] - total = 0 for entry in @paidCourseRedeemedEnrollments - total += entry.count data.push day: entry.day - value: total + value: entry.count points = @createLineChartPoints(days, data) @enrollmentsChartLines.push points: points - description: 'Total paid enrollments redeemed' + description: 'Paid enrollments redeemed' lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 @@ -684,16 +696,14 @@ module.exports = class AnalyticsView extends RootView dailyMax = _.max([dailyMax, _.max(points, 'y').y]) data = [] - total = 0 for entry in @trialCourseTotalEnrollments - total += entry.count data.push day: entry.day - value: total - points = @createLineChartPoints(days, data) + value: entry.count + points = @createLineChartPoints(days, data, true) @enrollmentsChartLines.push points: points - description: 'Total trial enrollments issued' + description: 'Trial enrollments issued' lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 @@ -702,16 +712,14 @@ module.exports = class AnalyticsView extends RootView dailyMax = _.max([dailyMax, _.max(points, 'y').y]) data = [] - total = 0 for entry in @trialCourseRedeemedEnrollments - total += entry.count data.push day: entry.day - value: total + value: entry.count points = @createLineChartPoints(days, data) @enrollmentsChartLines.push points: points - description: 'Total trial enrollments redeemed' + description: 'Trial enrollments redeemed' lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 diff --git a/app/views/admin/MainAdminView.coffee b/app/views/admin/MainAdminView.coffee index 4a9d96145..dd4b33b32 100644 --- a/app/views/admin/MainAdminView.coffee +++ b/app/views/admin/MainAdminView.coffee @@ -23,6 +23,7 @@ module.exports = class MainAdminView extends RootView 'submit #user-search-form': 'onSubmitUserSearchForm' 'click #stop-spying-btn': 'onClickStopSpyingButton' 'click #increment-button': 'incrementUserAttribute' + 'click .user-spy-button': 'onClickUserSpyButton' 'click #user-search-result': 'onClickUserSearchResult' 'click #create-free-sub-btn': 'onClickFreeSubLink' 'click #terminal-create': 'onClickTerminalSubLink' @@ -31,31 +32,10 @@ module.exports = class MainAdminView extends RootView getTitle: -> return $.i18n.t('account_settings.admin') initialize: -> - @campaigns = new Campaigns() - @courses = new CocoCollection([], { url: "/db/course", model: Course}) - if window.amActually @amActually = new User({_id: window.amActually}) @amActually.fetch() @supermodel.trackModel(@amActually) - if me.isAdmin() - @supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels' } }) - @supermodel.loadCollection(@courses, 'courses') - super() - - onLoaded: -> - campaignCourseIndexMap = {} - for course, index in @courses.models - campaignCourseIndexMap[course.get('campaignID')] = index + 1 - @courseLevels = [] - for campaign in @campaigns.models - continue unless campaignCourseIndexMap[campaign.id] - for levelID, level of campaign.get('levels') - @courseLevels.push({ - levelID - slug: level.slug - courseIndex: campaignCourseIndexMap[campaign.id] - }) super() onClickStopSpyingButton: -> @@ -80,6 +60,18 @@ module.exports = class MainAdminView extends RootView errors.showNotyNetworkError(arguments...) }) + onClickUserSpyButton: (e) -> + e.stopPropagation() + userID = $(e.target).closest('tr').data('user-id') + button = $(e.currentTarget) + forms.disableSubmit(button) + me.spy(userID, { + success: -> window.location.reload() + error: -> + forms.enableSubmit(button) + errors.showNotyNetworkError(arguments...) + }) + onSubmitUserSearchForm: (e) -> e.preventDefault() searchValue = @$el.find('#user-search').val() @@ -97,7 +89,7 @@ module.exports = class MainAdminView extends RootView forms.enableSubmit(@$('#user-search-button')) result = '' if users.length - result = ("#{user._id}#{_.escape(user.name or 'Anonymous')}#{_.escape(user.email)}" for user in users) + result = ("#{user._id}#{_.escape(user.name or 'Anonymous')}#{_.escape(user.email)}" for user in users) result = "#{result.join('\n')}
" @$el.find('#user-search-result').html(result) @@ -157,42 +149,31 @@ module.exports = class MainAdminView extends RootView @supermodel.addRequestResource('create_prepaid', options, 0).load() onClickExportProgress: -> - return unless @courseLevels?.length > 0 $('.classroom-progress-csv').prop('disabled', true) - classCode = $('.classroom-progress-class-code').val() + classroom = null + courseLevels = [] + sessions = null + users = null userMap = {} - new Promise((resolve, reject) => - new Classroom().fetchByCode(classCode, { - success: resolve - error: (model, response, options) => reject(response) - }) - ) - .then (classroom) => - new Promise((resolve, reject) => - new Classroom({ _id: classroom.id }).fetch({ - success: resolve - error: (model, response, options) => reject(response) - }) - ) - .then (classroom) => - new Promise((resolve, reject) => - new Users().fetchForClassroom(classroom, { - success: (models, response, options) => - resolve([classroom, models]) if models?.loaded - error: (models, response, options) => reject(response) - }) - ) - .then ([classroom, users]) => + Promise.resolve(new Classroom().fetchByCode(classCode)) + .then (model) => + classroom = new Classroom({ _id: model.data._id }) + Promise.resolve(classroom.fetch()) + .then (model) => + for course, index in classroom.get('courses') + for level in course.levels + courseLevels.push + courseIndex: index + 1 + levelID: level.original + slug: level.slug + users = new Users() + Promise.resolve($.when(users.fetchForClassroom(classroom)...)) + .then (models) => userMap[user.id] = user for user in users.models - new Promise((resolve, reject) => - new LevelSessions().fetchForAllClassroomMembers(classroom, { - success: (models, response, options) => - resolve(models) if models?.loaded - error: (models, response, options) => reject(response) - }) - ) - .then (sessions) => + sessions = new LevelSessions() + Promise.resolve($.when(sessions.fetchForAllClassroomMembers(classroom)...)) + .then (models) => userLevelPlaytimeMap = {} for session in sessions.models continue unless session.get('state')?.complete @@ -205,7 +186,7 @@ module.exports = class MainAdminView extends RootView userPlaytimes = [] for userID, user of userMap playtimes = [user.get('name') ? 'Anonymous'] - for level in @courseLevels + for level in courseLevels if userLevelPlaytimeMap[userID]?[level.levelID]? rawSeconds = parseInt(userLevelPlaytimeMap[userID][level.levelID]) hours = Math.floor(rawSeconds / 60 / 60) @@ -222,7 +203,7 @@ module.exports = class MainAdminView extends RootView columnLabels = "Username" currentLevel = 1 lastCourseIndex = 1 - for level in @courseLevels + for level in courseLevels unless level.courseIndex is lastCourseIndex currentLevel = 1 lastCourseIndex = level.courseIndex @@ -238,3 +219,4 @@ module.exports = class MainAdminView extends RootView .catch (error) -> $('.classroom-progress-csv').prop('disabled', false) console.error error + throw error diff --git a/app/views/core/CreateAccountModal/BasicInfoView.coffee b/app/views/core/CreateAccountModal/BasicInfoView.coffee index 4f95533b2..d33993706 100644 --- a/app/views/core/CreateAccountModal/BasicInfoView.coffee +++ b/app/views/core/CreateAccountModal/BasicInfoView.coffee @@ -28,6 +28,7 @@ module.exports = class BasicInfoView extends CocoView events: 'change input[name="email"]': 'onChangeEmail' 'change input[name="name"]': 'onChangeName' + 'change input[name="password"]': 'onChangePassword' 'click .back-button': 'onClickBackButton' 'submit form': 'onSubmitForm' 'click .use-suggested-name-link': 'onClickUseSuggestedNameLink' @@ -50,8 +51,15 @@ module.exports = class BasicInfoView extends CocoView @listenTo @state, 'change:error', -> @renderSelectors('.error-area') @listenTo @signupState, 'change:facebookEnabled', -> @renderSelectors('.auth-network-logins') @listenTo @signupState, 'change:gplusEnabled', -> @renderSelectors('.auth-network-logins') + + # These values are passed along to AuthModal if the user clicks "Sign In" (handled by CreateAccountModal) + updateAuthModalInitialValues: (values) -> + @signupState.set { + authModalInitialValues: _.merge @signupState.get('authModalInitialValues'), values + }, { silent: true } - onChangeEmail: -> + onChangeEmail: (e) -> + @updateAuthModalInitialValues { email: @$(e.currentTarget).val() } @checkEmail() checkEmail: -> @@ -85,7 +93,8 @@ module.exports = class BasicInfoView extends CocoView }) return @state.get('checkEmailPromise') - onChangeName: -> + onChangeName: (e) -> + @updateAuthModalInitialValues { name: @$(e.currentTarget).val() } @checkName() checkName: -> @@ -120,7 +129,10 @@ module.exports = class BasicInfoView extends CocoView ) }) - return @state.get('checkNamePromise') + return @state.get('checkNamePromise') + + onChangePassword: (e) -> + @updateAuthModalInitialValues { password: @$(e.currentTarget).val() } checkBasicInfo: (data) -> # TODO: Move this to somewhere appropriate diff --git a/app/views/core/CreateAccountModal/CreateAccountModal.coffee b/app/views/core/CreateAccountModal/CreateAccountModal.coffee index 690d7ca45..9a86d979e 100644 --- a/app/views/core/CreateAccountModal/CreateAccountModal.coffee +++ b/app/views/core/CreateAccountModal/CreateAccountModal.coffee @@ -62,6 +62,7 @@ module.exports = class CreateAccountModal extends ModalView gplusEnabled: application.gplusHandler.apiLoaded classCode birthday: new Date('') # so that birthday.getTime() is NaN + authModalInitialValues: {} } { startOnPath } = options @@ -112,5 +113,4 @@ module.exports = class CreateAccountModal extends ModalView document.location.reload() onClickLoginLink: -> - # TODO: Make sure the right information makes its way into the state. - @openModalView(new AuthModal({ initialValues: @signupState.pick(['email', 'name', 'password']) })) + @openModalView(new AuthModal({ initialValues: @signupState.get('authModalInitialValues') })) diff --git a/app/views/core/CreateAccountModal/SegmentCheckView.coffee b/app/views/core/CreateAccountModal/SegmentCheckView.coffee index 753dcff67..72cad159d 100644 --- a/app/views/core/CreateAccountModal/SegmentCheckView.coffee +++ b/app/views/core/CreateAccountModal/SegmentCheckView.coffee @@ -11,7 +11,7 @@ module.exports = class SegmentCheckView extends CocoView events: 'click .back-to-account-type': -> @trigger 'nav-back' 'input .class-code-input': 'onInputClassCode' - 'input .birthday-form-group': 'onInputBirthday' + 'change .birthday-form-group': 'onInputBirthday' 'submit form.segment-check': 'onSubmitSegmentCheck' 'click .individual-path-button': -> @trigger 'choose-path', 'individual' diff --git a/app/views/courses/TeacherClassView.coffee b/app/views/courses/TeacherClassView.coffee index 53f3ce9ba..05318572a 100644 --- a/app/views/courses/TeacherClassView.coffee +++ b/app/views/courses/TeacherClassView.coffee @@ -56,6 +56,7 @@ module.exports = class TeacherClassView extends RootView assigningToNobody: false assigningToUnenrolled: false selectedCourse: undefined + checkboxStates: {} classStats: averagePlaytime: "" totalPlaytime: "" @@ -145,6 +146,10 @@ module.exports = class TeacherClassView extends RootView classStats = @calculateClassStats() @state.set classStats: classStats if classStats @state.set students: @students + checkboxStates = {} + for student in @students.models + checkboxStates[student.id] = @state.get('checkboxStates')[student.id] or false + @state.set { checkboxStates } @listenTo @students, 'sort', -> @state.set students: @students @listenTo @, 'course-select:change', ({ selectedCourse }) -> @@ -291,8 +296,7 @@ module.exports = class TeacherClassView extends RootView @trigger 'course-select:change', { selectedCourse: @courses.get($(e.currentTarget).val()) } getSelectedStudentIDs: -> - @$('.student-row .checkbox-flat input:checked').map (index, checkbox) -> - $(checkbox).data('student-id') + Object.keys(_.pick @state.get('checkboxStates'), (checked) -> checked) ensureInstance: (courseID) -> @@ -304,7 +308,7 @@ module.exports = class TeacherClassView extends RootView window.tracker?.trackEvent $(e.currentTarget).data('event-action'), category: 'Teachers', classroomID: @classroom.id, userID: userID, ['Mixpanel'] onClickBulkEnroll: -> - userIDs = @getSelectedStudentIDs().toArray() + userIDs = @getSelectedStudentIDs() selectedUsers = new Users(@students.get(userID) for userID in userIDs) @enrollStudents(selectedUsers) window.tracker?.trackEvent 'Teachers Class Students Enroll Selected', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel'] @@ -385,10 +389,9 @@ module.exports = class TeacherClassView extends RootView onClickBulkAssign: -> courseID = @$('.bulk-course-select').val() selectedIDs = @getSelectedStudentIDs() - members = selectedIDs.filter((index, userID) => + members = selectedIDs.filter (userID) => user = @students.get(userID) user.isEnrolled() - ).toArray() assigningToUnenrolled = _.any selectedIDs, (userID) => not @students.get(userID).isEnrolled() assigningToNobody = selectedIDs.length is 0 @@ -417,23 +420,22 @@ module.exports = class TeacherClassView extends RootView onClickSelectAll: (e) -> e.preventDefault() - checkboxes = @$('.student-checkbox input') - if _.all(checkboxes, 'checked') - @$('.select-all input').prop('checked', false) - checkboxes.prop('checked', false) + checkboxStates = _.clone @state.get('checkboxStates') + if _.all(checkboxStates) + for studentID of checkboxStates + checkboxStates[studentID] = false else - @$('.select-all input').prop('checked', true) - checkboxes.prop('checked', true) - null + for studentID of checkboxStates + checkboxStates[studentID] = true + @state.set { checkboxStates } onClickStudentCheckbox: (e) -> e.preventDefault() - # $(e.target).$() checkbox = $(e.currentTarget).find('input') - checkbox.prop('checked', not checkbox.prop('checked')) - # checkboxes.prop('checked', false) - checkboxes = @$('.student-checkbox input') - @$('.select-all input').prop('checked', _.all(checkboxes, 'checked')) + studentID = checkbox.data('student-id') + checkboxStates = _.clone @state.get('checkboxStates') + checkboxStates[studentID] = not checkboxStates[studentID] + @state.set { checkboxStates } calculateClassStats: -> return {} unless @classroom.sessions?.loaded and @students.loaded diff --git a/app/views/editor/level/settings/SettingsTabView.coffee b/app/views/editor/level/settings/SettingsTabView.coffee index 56fe1609a..88951e2a5 100644 --- a/app/views/editor/level/settings/SettingsTabView.coffee +++ b/app/views/editor/level/settings/SettingsTabView.coffee @@ -16,6 +16,7 @@ module.exports = class SettingsTabView extends CocoView 'name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon', 'goals', 'type', 'terrain', 'showsGuide', 'banner', 'employerDescription', 'loadingTip', 'requiresSubscription', 'helpVideos', 'replayable', 'scoreTypes', 'concepts', 'picoCTFProblem', 'practice', 'practiceThresholdMinutes' + 'shareable' ] subscriptions: diff --git a/app/views/editor/level/thangs/ThangsTabView.coffee b/app/views/editor/level/thangs/ThangsTabView.coffee index 4dd28b836..1e794a40d 100644 --- a/app/views/editor/level/thangs/ThangsTabView.coffee +++ b/app/views/editor/level/thangs/ThangsTabView.coffee @@ -595,7 +595,7 @@ module.exports = class ThangsTabView extends CocoView @level.set 'thangs', thangs return if @editThangView return if skipSerialization - serializedLevel = @level.serialize @supermodel, null, null, true + serializedLevel = @level.serialize {@supermodel, session: null, otherSession: null, headless: false, sessionless: true, cached: true} try @world.loadFromLevel serializedLevel, false catch error diff --git a/app/views/editor/verifier/VerifierTest.coffee b/app/views/editor/verifier/VerifierTest.coffee index 03c6be4a7..6f0922b5f 100644 --- a/app/views/editor/verifier/VerifierTest.coffee +++ b/app/views/editor/verifier/VerifierTest.coffee @@ -25,9 +25,9 @@ module.exports = class VerifierTest extends CocoClass @loadStartTime = new Date() @god = new God maxAngels: 1, headless: true @levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, headless: true, fakeSessionConfig: {codeLanguage: @language, callback: @configureSession} - @listenToOnce @levelLoader, 'world-necessities-loaded', @onWorldNecessitiesLoaded + @listenToOnce @levelLoader, 'world-necessities-loaded', -> _.defer @onWorldNecessitiesLoaded - onWorldNecessitiesLoaded: -> + onWorldNecessitiesLoaded: => # Called when we have enough to build the world, but not everything is loaded @grabLevelLoaderData() @@ -62,7 +62,7 @@ module.exports = class VerifierTest extends CocoClass @solution = @levelLoader.session.solution setupGod: -> - @god.setLevel @level.serialize @supermodel, @session + @god.setLevel @level.serialize {@supermodel, @session, otherSession: null, headless: true, sessionless: false} @god.setLevelSessionIDs [@session.id] @god.setWorldClassMap @world.classMap @god.lastFlagHistory = @session.get('state').flagHistory @@ -122,8 +122,10 @@ module.exports = class VerifierTest extends CocoClass setTimeout @cleanup, 100 cleanup: => + if @levelLoader + @stopListening @levelLoader + @levelLoader.destroy() if @god @stopListening @god @god.destroy() - @world = null diff --git a/app/views/ladder/LadderView.coffee b/app/views/ladder/LadderView.coffee index b2bb6e9c6..d192a892a 100644 --- a/app/views/ladder/LadderView.coffee +++ b/app/views/ladder/LadderView.coffee @@ -42,6 +42,7 @@ module.exports = class LadderView extends RootView initialize: (options, @levelID, @leagueType, @leagueID) -> @level = @supermodel.loadModel(new Level(_id: @levelID)).model @level.once 'sync', => + return if @destroyed @levelDescription = marked(@level.get('description')) if @level.get('description') @teams = teamDataFromLevel @level @sessions = @supermodel.loadCollection(new LevelSessionsCollection(@levelID), 'your_sessions', {cache: false}).model diff --git a/app/views/play/SpectateView.coffee b/app/views/play/SpectateView.coffee index 769f75324..272b69d5b 100644 --- a/app/views/play/SpectateView.coffee +++ b/app/views/play/SpectateView.coffee @@ -69,7 +69,7 @@ module.exports = class SpectateLevelView extends RootView @load() setLevel: (@level, @supermodel) -> - serializedLevel = @level.serialize @supermodel, @session, @otherSession + serializedLevel = @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false} @god?.setLevel serializedLevel if @world @world.loadFromLevel serializedLevel, false @@ -106,7 +106,7 @@ module.exports = class SpectateLevelView extends RootView #at this point, all requisite data is loaded, and sessions are not denormalized team = @world.teamForPlayer(0) @loadOpponentTeam(team) - @god.setLevel @level.serialize @supermodel, @session, @otherSession + @god.setLevel @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false} @god.setLevelSessionIDs if @otherSession then [@session.id, @otherSession.id] else [@session.id] @god.setWorldClassMap @world.classMap @setTeam team diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee index ef48795d7..45340f0d7 100644 --- a/app/views/play/level/PlayLevelView.coffee +++ b/app/views/play/level/PlayLevelView.coffee @@ -128,7 +128,7 @@ module.exports = class PlayLevelView extends RootView @supermodel.collections = givenSupermodel.collections @supermodel.shouldSaveBackups = givenSupermodel.shouldSaveBackups - serializedLevel = @level.serialize @supermodel, @session, @otherSession + serializedLevel = @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false} @god?.setLevel serializedLevel if @world @world.loadFromLevel serializedLevel, false @@ -244,7 +244,7 @@ module.exports = class PlayLevelView extends RootView @session.set 'multiplayer', false setupGod: -> - @god.setLevel @level.serialize @supermodel, @session, @otherSession + @god.setLevel @level.serialize {@supermodel, @session, @otherSession, headless: false, sessionless: false} @god.setLevelSessionIDs if @otherSession then [@session.id, @otherSession.id] else [@session.id] @god.setWorldClassMap @world.classMap diff --git a/app/views/play/level/modal/CourseVictoryModal.coffee b/app/views/play/level/modal/CourseVictoryModal.coffee index 60c68d716..8df142bd1 100644 --- a/app/views/play/level/modal/CourseVictoryModal.coffee +++ b/app/views/play/level/modal/CourseVictoryModal.coffee @@ -1,15 +1,9 @@ ModalView = require 'views/core/ModalView' template = require 'templates/play/level/modal/course-victory-modal' -Achievements = require 'collections/Achievements' Level = require 'models/Level' Course = require 'models/Course' -ThangType = require 'models/ThangType' -ThangTypes = require 'collections/ThangTypes' LevelSessions = require 'collections/LevelSessions' -EarnedAchievement = require 'models/EarnedAchievement' -LocalMongo = require 'lib/LocalMongo' ProgressView = require './ProgressView' -NewItemView = require './NewItemView' Classroom = require 'models/Classroom' utils = require 'core/utils' @@ -18,7 +12,6 @@ module.exports = class CourseVictoryModal extends ModalView template: template closesOnClickOutside: false - initialize: (options) -> @courseID = options.courseID @courseInstanceID = options.courseInstanceID @@ -26,20 +19,10 @@ module.exports = class CourseVictoryModal extends ModalView @session = options.session @level = options.level - @newItems = new ThangTypes() - @newHeroes = new ThangTypes() - + if @courseInstanceID @classroom = new Classroom() @supermodel.trackRequest(@classroom.fetchForCourseInstance(@courseInstanceID)) - @achievements = options.achievements - if not @achievements - @achievements = new Achievements() - @achievements.fetchRelatedToLevel(@session.get('level').original) - @achievements = @supermodel.loadCollection(@achievements, 'achievements').model - @listenToOnce @achievements, 'sync', @onAchievementsLoaded - else - @onAchievementsLoaded() @playSound 'victory' @nextLevel = new Level() @@ -68,69 +51,12 @@ module.exports = class CourseVictoryModal extends ModalView return super(arguments...) - - onAchievementsLoaded: -> - @achievements.models = _.filter @achievements.models, (m) -> not m.get('query')?.ladderAchievementDifficulty # Don't show higher AI difficulty achievements - itemOriginals = [] - heroOriginals = [] - achievementIDs = [] - for achievement in @achievements.models - rewards = achievement.get('rewards') or {} - heroOriginals.push rewards.heroes or [] - itemOriginals.push rewards.items or [] - achievement.completed = LocalMongo.matchesQuery(@session.attributes, achievement.get('query')) - achievementIDs.push(achievement.id) if achievement.completed - - itemOriginals = _.uniq _.flatten itemOriginals - heroOriginals = _.uniq _.flatten heroOriginals - #project = ['original', 'rasterIcon', 'name', 'soundTriggers', 'i18n'] # This is what we need, but the PlayHeroesModal needs more, and so we load more to fill up the supermodel. - project = ['original', 'rasterIcon', 'name', 'slug', 'soundTriggers', 'featureImages', 'gems', 'heroClass', 'description', 'components', 'extendedName', 'unlockLevelName', 'i18n'] - for [newThangTypeCollection, originals] in [[@newItems, itemOriginals], [@newHeroes, heroOriginals]] - for original in originals - thang= new ThangType() - thang.url = "/db/thang.type/#{original}/version" - thang.project = project - @supermodel.loadModel(thang) - newThangTypeCollection.add(thang) - - @newEarnedAchievements = [] - for achievement in @achievements.models - continue unless achievement.completed - ea = new EarnedAchievement({ - collection: achievement.get('collection') - triggeredBy: @session.id - achievement: achievement.id - }) - if me.isSessionless() - @newEarnedAchievements.push ea - else - ea.save() - # Can't just add models to supermodel because each ea has the same url - ea.sr = @supermodel.addSomethingResource(ea.cid) - @newEarnedAchievements.push ea - @listenToOnce ea, 'sync', (model) -> - model.sr.markLoaded() - if _.all((ea.id for ea in @newEarnedAchievements)) - unless me.loading - @supermodel.loadModel(me, {cache: false}) - @newEarnedAchievementsResource.markLoaded() - - unless me.isSessionless() - # have to use a something resource because addModelResource doesn't handle models being upserted/fetched via POST like we're doing here - @newEarnedAchievementsResource = @supermodel.addSomethingResource('earned achievements') if @newEarnedAchievements.length - - onLoaded: -> super() @views = [] - # TODO: Add main victory view - # TODO: Add level up view - # TODO: Add new hero view? - - for newItem in @newItems.models - @views.push(new NewItemView({item: newItem})) - + @levelSessions?.remove(@session) + @levelSessions?.add(@session) progressView = new ProgressView({ level: @level nextLevel: @nextLevel diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee index 14edde8cf..c3e2b7e14 100644 --- a/app/views/play/level/tome/SpellView.coffee +++ b/app/views/play/level/tome/SpellView.coffee @@ -275,78 +275,77 @@ module.exports = class SpellView extends CocoView e.editor.execCommand 'gotolineend' return true - if me.level() < 20 or aceConfig.indentGuides - # Add visual ident guides - language = @spell.language - ensureLineStartsBlock = (line) -> - return false unless language is "python" - match = /^\s*([^#]+)/.exec(line) - return false if not match? - return /:\s*$/.test(match[1]) + # Add visual indent guides + language = @spell.language + ensureLineStartsBlock = (line) -> + return false unless language is "python" + match = /^\s*([^#]+)/.exec(line) + return false if not match? + return /:\s*$/.test(match[1]) - @aceSession.addDynamicMarker - update: (html, markerLayer, session, config) => - Range = ace.require('ace/range').Range + @aceSession.addDynamicMarker + update: (html, markerLayer, session, config) => + Range = ace.require('ace/range').Range - foldWidgets = @aceSession.foldWidgets - return if not foldWidgets? + foldWidgets = @aceSession.foldWidgets + return if not foldWidgets? - lines = @aceDoc.getAllLines() - startOfRow = (r) -> - str = lines[r] - ar = str.match(/^\s*/) - ar.pop().length + lines = @aceDoc.getAllLines() + startOfRow = (r) -> + str = lines[r] + ar = str.match(/^\s*/) + ar.pop().length - colors = [{border: '74,144,226', fill: '108,162,226'}, {border: '132,180,235', fill: '230,237,245'}] + colors = [{border: '74,144,226', fill: '108,162,226'}, {border: '132,180,235', fill: '230,237,245'}] - for row in [0..@aceSession.getLength()] - foldWidgets[row] = @aceSession.getFoldWidget(row) unless foldWidgets[row]? - continue unless foldWidgets? and foldWidgets[row] is "start" - try - docRange = @aceSession.getFoldWidgetRange(row) - catch error - console.warn "Couldn't find fold widget docRange for row #{row}:", error - if not docRange? - guess = startOfRow(row) - docRange = new Range(row,guess,row,guess+4) + for row in [0..@aceSession.getLength()] + foldWidgets[row] = @aceSession.getFoldWidget(row) unless foldWidgets[row]? + continue unless foldWidgets? and foldWidgets[row] is "start" + try + docRange = @aceSession.getFoldWidgetRange(row) + catch error + console.warn "Couldn't find fold widget docRange for row #{row}:", error + if not docRange? + guess = startOfRow(row) + docRange = new Range(row,guess,row,guess+4) - continue unless ensureLineStartsBlock(lines[row]) + continue unless ensureLineStartsBlock(lines[row]) - if /^\s+$/.test lines[docRange.end.row+1] - docRange.end.row += 1 + if /^\s+$/.test lines[docRange.end.row+1] + docRange.end.row += 1 - xstart = startOfRow(row) - if language is 'python' - requiredIndent = new RegExp '^' + new Array(Math.floor(xstart / 4 + 1)).join('( |\t)') + '( |\t)+(\\S|\\s*$)' - for crow in [docRange.start.row+1..docRange.end.row] - unless requiredIndent.test lines[crow] - docRange.end.row = crow - 1 - break + xstart = startOfRow(row) + if language is 'python' + requiredIndent = new RegExp '^' + new Array(Math.floor(xstart / 4 + 1)).join('( |\t)') + '( |\t)+(\\S|\\s*$)' + for crow in [docRange.start.row+1..docRange.end.row] + unless requiredIndent.test lines[crow] + docRange.end.row = crow - 1 + break - rstart = @aceSession.documentToScreenPosition docRange.start.row, docRange.start.column - rend = @aceSession.documentToScreenPosition docRange.end.row, docRange.end.column - range = new Range rstart.row, rstart.column, rend.row, rend.column - level = Math.floor(xstart / 4) - color = colors[level % colors.length] - bw = 3 - to = markerLayer.$getTop(range.start.row, config) - t = markerLayer.$getTop(range.start.row + 1, config) - h = config.lineHeight * (range.end.row - range.start.row) - l = markerLayer.$padding + xstart * config.characterWidth - # w = (data.i - data.b) * config.characterWidth - w = 4 * config.characterWidth - fw = config.characterWidth * ( @aceSession.getScreenLastRowColumn(range.start.row) - xstart ) + rstart = @aceSession.documentToScreenPosition docRange.start.row, docRange.start.column + rend = @aceSession.documentToScreenPosition docRange.end.row, docRange.end.column + range = new Range rstart.row, rstart.column, rend.row, rend.column + level = Math.floor(xstart / 4) + color = colors[level % colors.length] + bw = 3 + to = markerLayer.$getTop(range.start.row, config) + t = markerLayer.$getTop(range.start.row + 1, config) + h = config.lineHeight * (range.end.row - range.start.row) + l = markerLayer.$padding + xstart * config.characterWidth + # w = (data.i - data.b) * config.characterWidth + w = 4 * config.characterWidth + fw = config.characterWidth * ( @aceSession.getScreenLastRowColumn(range.start.row) - xstart ) - html.push """ -
-
- """ + html.push """ +
+
+ """ fillACE: -> @ace.setValue @spell.source diff --git a/scripts/analytics/mongodb/insertAnalyticsActiveClasses.js b/scripts/analytics/mongodb/insertAnalyticsActiveClasses.js index b26e89af1..9599ffd94 100644 --- a/scripts/analytics/mongodb/insertAnalyticsActiveClasses.js +++ b/scripts/analytics/mongodb/insertAnalyticsActiveClasses.js @@ -6,51 +6,49 @@ // Usage: // mongo
:/