mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-12 00:31:21 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
6e0fde23ab
141 changed files with 2412 additions and 2035 deletions
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
164
app/assets/javascripts/web-dev-listener.js
Normal file
164
app/assets/javascripts/web-dev-listener.js
Normal file
|
@ -0,0 +1,164 @@
|
|||
// TODO: don't serve this script from codecombat.com; serve it from a harmless extra domain we don't have yet.
|
||||
|
||||
window.addEventListener('message', receiveMessage, false);
|
||||
|
||||
var concreteDom;
|
||||
var virtualDom;
|
||||
var goalStates;
|
||||
|
||||
var allowedOrigins = [
|
||||
/https:\/\/codecombat\.com/,
|
||||
/http:\/\/localhost:3000/,
|
||||
/http:\/\/direct\.codecombat\.com/,
|
||||
/http:\/\/staging\.codecombat\.com/,
|
||||
/http:\/\/next\.codecombat\.com/,
|
||||
/http:\/\/.*codecombat-staging-codecombat\.runnableapp\.com/,
|
||||
];
|
||||
|
||||
function receiveMessage(event) {
|
||||
var origin = event.origin || event.originalEvent.origin; // For Chrome, the origin property is in the event.originalEvent object.
|
||||
var allowed = false;
|
||||
allowedOrigins.forEach(function(pattern) {
|
||||
allowed = allowed || pattern.test(origin);
|
||||
});
|
||||
if (!allowed) {
|
||||
console.log('Ignoring message from bad origin:', origin);
|
||||
return;
|
||||
}
|
||||
var data = event.data;
|
||||
var source = event.source;
|
||||
switch (data.type) {
|
||||
case 'create':
|
||||
create(_.pick(data, 'dom', 'styles', 'scripts'));
|
||||
checkGoals(data.goals, source, origin);
|
||||
break;
|
||||
case 'update':
|
||||
if (virtualDom)
|
||||
update(_.pick(data, 'dom', 'styles', 'scripts'));
|
||||
else
|
||||
create(_.pick(data, 'dom', 'styles', 'scripts'));
|
||||
checkGoals(data.goals, source, origin);
|
||||
break;
|
||||
case 'log':
|
||||
console.log(data.text);
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown message type:', data.type);
|
||||
}
|
||||
}
|
||||
|
||||
function create({ dom, styles, scripts }) {
|
||||
virtualDom = dom;
|
||||
virtualStyles = styles;
|
||||
virtualScripts = scripts;
|
||||
concreteDom = deku.dom.create(dom);
|
||||
concreteStyles = deku.dom.create(styles);
|
||||
concreteScripts = deku.dom.create(scripts);
|
||||
// TODO: target the actual HTML tag and combine our initial structure for styles/scripts/tags with theirs
|
||||
// TODO: :after elements don't seem to work? (:before do)
|
||||
$('body').first().empty().append(concreteDom);
|
||||
$('#player-styles').first().empty().append(concreteStyles);
|
||||
$('#player-scripts').first().empty().append(concreteScripts);
|
||||
}
|
||||
|
||||
function update({ dom, styles, scripts }) {
|
||||
function dispatch() {} // Might want to do something here in the future
|
||||
var context = {}; // Might want to use this to send shared state to every component
|
||||
|
||||
var domChanges = deku.diff.diffNode(virtualDom, dom);
|
||||
domChanges.reduce(deku.dom.update(dispatch, context), concreteDom); // Rerender
|
||||
|
||||
var scriptChanges = deku.diff.diffNode(virtualScripts, scripts);
|
||||
scriptChanges.reduce(deku.dom.update(dispatch, context), concreteScripts); // Rerender
|
||||
|
||||
var styleChanges = deku.diff.diffNode(virtualStyles, styles);
|
||||
styleChanges.reduce(deku.dom.update(dispatch, context), concreteStyles); // Rerender
|
||||
|
||||
virtualDom = dom;
|
||||
virtualStyles = styles;
|
||||
virtualScripts = scripts;
|
||||
}
|
||||
|
||||
function checkGoals(goals, source, origin) {
|
||||
// Check right now and also in one second, since our 1-second CSS transition might be affecting things until it is done.
|
||||
doCheckGoals(goals, source, origin);
|
||||
_.delay(function() { doCheckGoals(goals, source, origin); }, 1001);
|
||||
}
|
||||
|
||||
function doCheckGoals(goals, source, origin) {
|
||||
var newGoalStates = {};
|
||||
var overallSuccess = true;
|
||||
goals.forEach(function(goal) {
|
||||
var $result = $(goal.html.selector);
|
||||
//console.log('ran selector', goal.html.selector, 'to find element(s)', $result);
|
||||
var success = true;
|
||||
goal.html.valueChecks.forEach(function(check) {
|
||||
//console.log(' ... and should make sure that the value of', check.eventProps, 'is', _.omit(check, 'eventProps'), '?', matchesCheck($result, check))
|
||||
success = success && matchesCheck($result, check);
|
||||
});
|
||||
overallSuccess = overallSuccess && success;
|
||||
newGoalStates[goal.id] = {status: success ? 'success' : 'incomplete'}; // No 'failure' state
|
||||
});
|
||||
if (!_.isEqual(newGoalStates, goalStates)) {
|
||||
goalStates = newGoalStates;
|
||||
var overallStatus = overallSuccess ? 'success' : null; // Can't really get to 'failure', just 'incomplete', which is represented by null here
|
||||
source.postMessage({type: 'goals-updated', goalStates: goalStates, overallStatus: overallStatus}, origin);
|
||||
}
|
||||
}
|
||||
|
||||
function downTheChain(obj, keyChain) {
|
||||
if (!obj)
|
||||
return null;
|
||||
if (!_.isArray(keyChain))
|
||||
return obj[keyChain];
|
||||
var value = obj;
|
||||
while (keyChain.length && value) {
|
||||
if (keyChain[0].match(/\(.*\)$/)) {
|
||||
var args, argsString = keyChain[0].match(/\((.*)\)$/)[1];
|
||||
if (argsString)
|
||||
args = eval(argsString).split(/, ?/g).filter(function(x) { return x !== ''; }); // TODO: can/should we avoid eval here?
|
||||
else
|
||||
args = [];
|
||||
value = value[keyChain[0].split('(')[0]].apply(value, args); // value.text(), value.css('background-color'), etc.
|
||||
}
|
||||
else
|
||||
value = value[keyChain[0]];
|
||||
keyChain = keyChain.slice(1);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
function matchesCheck(value, check) {
|
||||
var v = downTheChain(value, check.eventProps);
|
||||
if ((check.equalTo != null) && v !== check.equalTo) {
|
||||
return false;
|
||||
}
|
||||
if ((check.notEqualTo != null) && v === check.notEqualTo) {
|
||||
return false;
|
||||
}
|
||||
if ((check.greaterThan != null) && !(v > check.greaterThan)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.greaterThanOrEqualTo != null) && !(v >= check.greaterThanOrEqualTo)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.lessThan != null) && !(v < check.lessThan)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.lessThanOrEqualTo != null) && !(v <= check.lessThanOrEqualTo)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.containingString != null) && (!v || v.search(check.containingString) === -1)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.notContainingString != null) && (v != null ? v.search(check.notContainingString) : void 0) !== -1) {
|
||||
return false;
|
||||
}
|
||||
if ((check.containingRegexp != null) && (!v || v.search(new RegExp(check.containingRegexp)) === -1)) {
|
||||
return false;
|
||||
}
|
||||
if ((check.notContainingRegexp != null) && (v != null ? v.search(new RegExp(check.notContainingRegexp)) : void 0) !== -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -19,6 +19,7 @@ var languagesImported = {};
|
|||
|
||||
var ensureLanguageImported = function(language) {
|
||||
if (languagesImported[language]) return;
|
||||
if (language === 'html') return;
|
||||
importScripts("/javascripts/app/vendor/aether-" + language + ".js");
|
||||
languagesImported[language] = true;
|
||||
};
|
||||
|
|
|
@ -80,7 +80,7 @@ var myImportScripts = importScripts;
|
|||
var languagesImported = {};
|
||||
var ensureLanguageImported = function(language) {
|
||||
if (languagesImported[language]) return;
|
||||
if (language === 'javascript') return; // Only has JSHint, but we don't need to lint here.
|
||||
if (language === 'javascript' || language === 'html') return; // Only has JSHint, but we don't need to lint here.
|
||||
myImportScripts("/javascripts/app/vendor/aether-" + language + ".js");
|
||||
languagesImported[language] = true;
|
||||
};
|
||||
|
|
43
app/assets/web-dev-iframe.html
Normal file
43
app/assets/web-dev-iframe.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
||||
|
||||
<title>My CodeCombat Website</title>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js" integrity="sha256-8SeyqJ7ZAZx8WnIgP/bgK6LGIjKjhojNPHSMV/fo29Y=" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="/javascripts/web-dev-listener.js"></script>
|
||||
|
||||
<script src="/javascripts/app/vendor/aether-html.js"></script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
transition: 1s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
||||
|
||||
<!-- Optional theme -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
|
||||
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Extracted player/level styles and scripts -->
|
||||
<style id="player-styles">
|
||||
</style>
|
||||
<script id="player-scripts">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Loading...</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = class RealTimeCollection extends Backbone.Firebase.Collection
|
||||
constructor: (savePath) ->
|
||||
# TODO: Don't hard code this here
|
||||
# TODO: Use prod path in prod
|
||||
@firebase = 'https://codecombat.firebaseio.com/test/db/' + savePath
|
||||
super()
|
|
@ -245,6 +245,12 @@ particleKinds['level-dungeon-game-dev'] = particleKinds['level-dungeon-game-dev-
|
|||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-dungeon-web-dev'] = particleKinds['level-dungeon-web-dev-premium'] = ext particleKinds['level-dungeon-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
||||
particleKinds['level-dungeon-premium-item'] = ext particleKinds['level-dungeon-gate'],
|
||||
emitter:
|
||||
particleCount: 2000
|
||||
|
@ -300,6 +306,12 @@ particleKinds['level-forest-game-dev'] = particleKinds['level-forest-game-dev-pr
|
|||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-forest-web-dev'] = particleKinds['level-forest-web-dev-premium'] = ext particleKinds['level-forest-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
||||
particleKinds['level-forest-premium-item'] = ext particleKinds['level-forest-gate'],
|
||||
emitter:
|
||||
particleCount: 2000
|
||||
|
@ -355,6 +367,12 @@ particleKinds['level-desert-game-dev'] = particleKinds['level-desert-game-dev-pr
|
|||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-desert-web-dev'] = particleKinds['level-desert-web-dev-premium'] = ext particleKinds['level-desert-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
||||
particleKinds['level-mountain-premium-hero'] = ext particleKinds['level-mountain-premium'],
|
||||
emitter:
|
||||
particleCount: 200
|
||||
|
@ -395,6 +413,12 @@ particleKinds['level-mountain-game-dev'] = particleKinds['level-mountain-game-de
|
|||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-mountain-web-dev'] = particleKinds['level-mountain-web-dev-premium'] = ext particleKinds['level-mountain-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
||||
particleKinds['level-glacier-premium-hero'] = ext particleKinds['level-glacier-premium'],
|
||||
emitter:
|
||||
particleCount: 200
|
||||
|
@ -435,6 +459,12 @@ particleKinds['level-glacier-game-dev'] = particleKinds['level-glacier-game-dev-
|
|||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-glacier-web-dev'] = particleKinds['level-glacier-web-dev-premium'] = ext particleKinds['level-glacier-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
||||
particleKinds['level-volcano-premium-hero'] = ext particleKinds['level-volcano-premium'],
|
||||
emitter:
|
||||
particleCount: 200
|
||||
|
@ -474,3 +504,9 @@ particleKinds['level-volcano-game-dev'] = particleKinds['level-volcano-game-dev-
|
|||
colorStart: hsl 0.7, 0.75, 0.7
|
||||
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||
colorEnd: hsl 0.7, 0.75, 0.3
|
||||
|
||||
particleKinds['level-volcano-web-dev'] = particleKinds['level-volcano-web-dev-premium'] = ext particleKinds['level-volcano-hero-ladder'],
|
||||
emitter:
|
||||
colorStart: hsl 0.7, 0.25, 0.7
|
||||
colorMiddle: hsl 0.7, 0.25, 0.5
|
||||
colorEnd: hsl 0.7, 0.25, 0.3
|
||||
|
|
|
@ -126,13 +126,13 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
|
||||
'legal': go('LegalView')
|
||||
|
||||
'multiplayer': go('MultiplayerView')
|
||||
|
||||
'play(/)': go('play/CampaignView') # extra slash is to get Facebook app to work
|
||||
'play/ladder/:levelID/:leagueType/:leagueID': go('ladder/LadderView')
|
||||
'play/ladder/:levelID': go('ladder/LadderView')
|
||||
'play/ladder': go('ladder/MainLadderView')
|
||||
'play/level/:levelID': go('play/level/PlayLevelView')
|
||||
'play/game-dev-level/:levelID/:sessionID': go('play/level/PlayGameDevLevelView')
|
||||
'play/web-dev-level/:levelID/:sessionID': go('play/level/PlayWebDevLevelView')
|
||||
'play/spectate/:levelID': go('play/SpectateView')
|
||||
'play/:map': go('play/CampaignView')
|
||||
|
||||
|
@ -193,7 +193,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
@listenToOnce application.moduleLoader, 'load-complete', ->
|
||||
@routeDirectly(path, args, options)
|
||||
return
|
||||
return @openView @notFoundView() if not ViewClass
|
||||
return go('NotFoundView') if not ViewClass
|
||||
view = new ViewClass(options, args...) # options, then any path fragment args
|
||||
view.render()
|
||||
@openView(view)
|
||||
|
|
|
@ -8,7 +8,6 @@ channelSchemas =
|
|||
'errors': require 'schemas/subscriptions/errors'
|
||||
'ipad': require 'schemas/subscriptions/ipad'
|
||||
'misc': require 'schemas/subscriptions/misc'
|
||||
'multiplayer': require 'schemas/subscriptions/multiplayer'
|
||||
'play': require 'schemas/subscriptions/play'
|
||||
'surface': require 'schemas/subscriptions/surface'
|
||||
'tome': require 'schemas/subscriptions/tome'
|
||||
|
|
|
@ -2,6 +2,7 @@ CocoModel = require 'models/CocoModel'
|
|||
CocoCollection = require 'collections/CocoCollection'
|
||||
{me} = require('core/auth')
|
||||
locale = require 'locale/locale'
|
||||
utils = require 'core/utils'
|
||||
|
||||
initializeFilePicker = ->
|
||||
require('core/services/filepicker')() unless window.application.isIPadApp
|
||||
|
@ -234,21 +235,14 @@ class ImageFileTreema extends TreemaNode.nodeMap.string
|
|||
@refreshDisplay()
|
||||
|
||||
|
||||
codeLanguages =
|
||||
javascript: 'ace/mode/javascript'
|
||||
coffeescript: 'ace/mode/coffee'
|
||||
python: 'ace/mode/python'
|
||||
lua: 'ace/mode/lua'
|
||||
java: 'ace/mode/java'
|
||||
|
||||
class CodeLanguagesObjectTreema extends TreemaNode.nodeMap.object
|
||||
childPropertiesAvailable: ->
|
||||
(key for key in _.keys(codeLanguages) when not @data[key]? and not (key is 'javascript' and @workingSchema.skipJavaScript))
|
||||
(key for key in _.keys(utils.aceEditModes) when not @data[key]? and not (key is 'javascript' and @workingSchema.skipJavaScript))
|
||||
|
||||
class CodeLanguageTreema extends TreemaNode.nodeMap.string
|
||||
buildValueForEditing: (valEl, data) ->
|
||||
super(valEl, data)
|
||||
valEl.find('input').autocomplete(source: _.keys(codeLanguages), minLength: 0, delay: 0, autoFocus: true)
|
||||
valEl.find('input').autocomplete(source: _.keys(utils.aceEditModes), minLength: 0, delay: 0, autoFocus: true)
|
||||
valEl
|
||||
|
||||
class CodeTreema extends TreemaNode.nodeMap.ace
|
||||
|
@ -256,8 +250,8 @@ class CodeTreema extends TreemaNode.nodeMap.ace
|
|||
super(arguments...)
|
||||
@workingSchema.aceTabSize = 4
|
||||
# TODO: Find a less hacky solution for this
|
||||
@workingSchema.aceMode = mode if mode = codeLanguages[@keyForParent]
|
||||
@workingSchema.aceMode = mode if mode = codeLanguages[@parent?.data?.language]
|
||||
@workingSchema.aceMode = mode if mode = utils.aceEditModes[@keyForParent]
|
||||
@workingSchema.aceMode = mode if mode = utils.aceEditModes[@parent?.data?.language]
|
||||
|
||||
class CoffeeTreema extends CodeTreema
|
||||
constructor: ->
|
||||
|
|
|
@ -259,7 +259,7 @@ startsWithVowel = (s) -> s[0] in 'aeiouAEIOU'
|
|||
module.exports.filterMarkdownCodeLanguages = (text, language) ->
|
||||
return '' unless text
|
||||
currentLanguage = language or me.get('aceConfig')?.language or 'python'
|
||||
excludedLanguages = _.without ['javascript', 'python', 'coffeescript', 'clojure', 'lua', 'java', 'io'], currentLanguage
|
||||
excludedLanguages = _.without ['javascript', 'python', 'coffeescript', 'clojure', 'lua', 'java', 'io', 'html'], currentLanguage
|
||||
# Exclude language-specific code blocks like ```python (... code ...)``` for each non-target language.
|
||||
codeBlockExclusionRegex = new RegExp "```(#{excludedLanguages.join('|')})\n[^`]+```\n?", 'gm'
|
||||
# Exclude language-specific images like ![python - image description](image url) for each non-target language.
|
||||
|
@ -290,13 +290,15 @@ module.exports.filterMarkdownCodeLanguages = (text, language) ->
|
|||
return text
|
||||
|
||||
module.exports.aceEditModes = aceEditModes =
|
||||
'javascript': 'ace/mode/javascript'
|
||||
'coffeescript': 'ace/mode/coffee'
|
||||
'python': 'ace/mode/python'
|
||||
'java': 'ace/mode/java'
|
||||
'lua': 'ace/mode/lua'
|
||||
'java': 'ace/mode/java'
|
||||
javascript: 'ace/mode/javascript'
|
||||
coffeescript: 'ace/mode/coffee'
|
||||
python: 'ace/mode/python'
|
||||
lua: 'ace/mode/lua'
|
||||
java: 'ace/mode/java'
|
||||
html: 'ace/mode/html'
|
||||
|
||||
# These ACEs are used for displaying code snippets statically, like in SpellPaletteEntryView popovers
|
||||
# and have short lifespans
|
||||
module.exports.initializeACE = (el, codeLanguage) ->
|
||||
contents = $(el).text().trim()
|
||||
editor = ace.edit el
|
||||
|
|
|
@ -22,6 +22,7 @@ module.exports = Bus = class Bus extends CocoClass
|
|||
'auth:me-synced': 'onMeSynced'
|
||||
|
||||
connect: ->
|
||||
# Put Firebase back in bower if you want to use this
|
||||
Backbone.Mediator.publish 'bus:connecting', {bus: @}
|
||||
Firebase.goOnline()
|
||||
@fireRef = new Firebase(Bus.fireHost + '/' + @docName)
|
||||
|
|
|
@ -94,9 +94,9 @@ module.exports = class God extends CocoClass
|
|||
return if hadPreloader
|
||||
|
||||
@angelsShare.workQueue = []
|
||||
work =
|
||||
work = {
|
||||
userCodeMap: userCodeMap
|
||||
level: @level
|
||||
@level
|
||||
levelSessionIDs: @levelSessionIDs
|
||||
submissionCount: @lastSubmissionCount
|
||||
fixedSeed: @lastFixedSeed
|
||||
|
@ -104,9 +104,10 @@ module.exports = class God extends CocoClass
|
|||
difficulty: @lastDifficulty
|
||||
goals: @angelsShare.goalManager?.getGoals()
|
||||
headless: @angelsShare.headless
|
||||
preload: preload
|
||||
preload
|
||||
synchronous: not Worker? # Profiling world simulation is easier on main thread, or we are IE9.
|
||||
realTime: realTime
|
||||
realTime
|
||||
}
|
||||
@angelsShare.workQueue.push work
|
||||
angel.workIfIdle() for angel in @angelsShare.angels
|
||||
work
|
||||
|
@ -114,9 +115,7 @@ module.exports = class God extends CocoClass
|
|||
getUserCodeMap: (spells) ->
|
||||
userCodeMap = {}
|
||||
for spellKey, spell of spells
|
||||
for thangID, spellThang of spell.thangs
|
||||
continue if spellThang.thang?.programmableMethods[spell.name].cloneOf
|
||||
(userCodeMap[thangID] ?= {})[spell.name] = spellThang.aether.serialize()
|
||||
(userCodeMap[spell.thang.thang.id] ?= {})[spell.name] = spell.thang.aether.serialize()
|
||||
userCodeMap
|
||||
|
||||
|
||||
|
|
|
@ -41,7 +41,6 @@ module.exports = class LevelBus extends Bus
|
|||
@fireScriptsRef = @fireRef?.child('scripts')
|
||||
|
||||
setSession: (@session) ->
|
||||
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
|
||||
@timerIntervalID = setInterval(@incrementSessionPlaytime, 1000)
|
||||
|
||||
onIdleChanged: (e) ->
|
||||
|
@ -53,8 +52,7 @@ module.exports = class LevelBus extends Bus
|
|||
@session.set('playtime', (@session.get('playtime') ? 0) + 1)
|
||||
|
||||
onPoint: ->
|
||||
return true unless @session?.get('multiplayer')
|
||||
super()
|
||||
return true
|
||||
|
||||
onMeSynced: =>
|
||||
super()
|
||||
|
@ -236,17 +234,11 @@ module.exports = class LevelBus extends Bus
|
|||
@changedSessionProperties.chat = true
|
||||
@saveSession()
|
||||
|
||||
onMultiplayerChanged: ->
|
||||
@changedSessionProperties.multiplayer = true
|
||||
@session.updatePermissions()
|
||||
@changedSessionProperties.permissions = true
|
||||
@saveSession()
|
||||
|
||||
# Debounced as saveSession
|
||||
reallySaveSession: ->
|
||||
return if _.isEmpty @changedSessionProperties
|
||||
# don't let peeking admins mess with the session accidentally
|
||||
return unless @session.get('multiplayer') or @session.get('creator') is me.id
|
||||
return unless @session.get('creator') is me.id
|
||||
return if @session.fake
|
||||
Backbone.Mediator.publish 'level:session-will-save', session: @session
|
||||
patch = {}
|
||||
|
|
|
@ -53,6 +53,16 @@ module.exports = class LevelLoader extends CocoClass
|
|||
|
||||
# Supermodel (Level) Loading
|
||||
|
||||
loadWorldNecessities: ->
|
||||
# TODO: Actually trigger loading, instead of in the constructor
|
||||
new Promise((resolve, reject) =>
|
||||
return resolve(@) if @world
|
||||
@once 'world-necessities-loaded', => resolve(@)
|
||||
@once 'world-necessity-load-failed', ({resource}) ->
|
||||
{ jqxhr } = resource
|
||||
reject({message: jqxhr.responseJSON?.message or jqxhr.responseText or 'Unknown Error'})
|
||||
)
|
||||
|
||||
loadLevel: ->
|
||||
@level = @supermodel.getModel(Level, @levelID) or new Level _id: @levelID
|
||||
if @level.loaded
|
||||
|
@ -62,9 +72,18 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@listenToOnce @level, 'sync', @onLevelLoaded
|
||||
|
||||
onLevelLoaded: ->
|
||||
if not @sessionless and @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course']
|
||||
if not @sessionless and @level.isType('hero', 'hero-ladder', 'hero-coop', 'course')
|
||||
@sessionDependenciesRegistered = {}
|
||||
if (@courseID and @level.get('type', true) not in ['course', 'course-ladder']) or window.serverConfig.picoCTF
|
||||
if @level.isType('web-dev')
|
||||
@headless = true
|
||||
if @sessionless
|
||||
# When loading a web-dev level in the level editor, pretend it's a normal hero level so we can put down our placeholder Thang.
|
||||
# TODO: avoid this whole roundabout Thang-based way of doing web-dev levels
|
||||
originalGet = @level.get
|
||||
@level.get = ->
|
||||
return 'hero' if arguments[0] is 'type'
|
||||
originalGet.apply @, arguments
|
||||
if (@courseID and not @level.isType('course', 'course-ladder', 'game-dev', 'web-dev')) or window.serverConfig.picoCTF
|
||||
# Because we now use original hero levels for both hero and course levels, we fake being a course level in this context.
|
||||
originalGet = @level.get
|
||||
@level.get = ->
|
||||
|
@ -169,7 +188,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@consolidateFlagHistory() if @opponentSession?.loaded
|
||||
else if session is @opponentSession
|
||||
@consolidateFlagHistory() if @session.loaded
|
||||
if @level.get('type', true) in ['course'] # course-ladder is hard to handle because there's 2 sessions
|
||||
if @level.isType('course') # course-ladder is hard to handle because there's 2 sessions
|
||||
heroThangType = me.get('heroConfig')?.thangType or ThangType.heroes.captain
|
||||
console.log "Course mode, loading custom hero: ", heroThangType if LOG
|
||||
url = "/db/thang.type/#{heroThangType}/version"
|
||||
|
@ -178,7 +197,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@worldNecessities.push heroResource
|
||||
@sessionDependenciesRegistered[session.id] = true
|
||||
return
|
||||
return unless @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
|
||||
return unless @level.isType('hero', 'hero-ladder', 'hero-coop')
|
||||
heroConfig = session.get('heroConfig')
|
||||
heroConfig ?= me.get('heroConfig') if session is @session and not @headless
|
||||
heroConfig ?= {}
|
||||
|
@ -332,8 +351,8 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@worldNecessities = (r for r in @worldNecessities when r?)
|
||||
@onWorldNecessitiesLoaded() if @checkAllWorldNecessitiesRegisteredAndLoaded()
|
||||
|
||||
onWorldNecessityLoadFailed: (resource) ->
|
||||
@trigger('world-necessity-load-failed', resource: resource)
|
||||
onWorldNecessityLoadFailed: (event) ->
|
||||
@trigger('world-necessity-load-failed', event)
|
||||
|
||||
checkAllWorldNecessitiesRegisteredAndLoaded: ->
|
||||
return false unless _.filter(@worldNecessities).length is 0
|
||||
|
@ -401,7 +420,8 @@ module.exports = class LevelLoader extends CocoClass
|
|||
resource.markLoaded() if resource.spriteSheetKeys.length is 0
|
||||
|
||||
denormalizeSession: ->
|
||||
return if @headless or @sessionDenormalized or @spectateMode or @sessionless or me.isSessionless()
|
||||
return if @sessionDenormalized or @spectateMode or @sessionless or me.isSessionless()
|
||||
return if @headless and not @level.isType('web-dev')
|
||||
# This is a way (the way?) PUT /db/level.sessions/undefined was happening
|
||||
# See commit c242317d9
|
||||
return if not @session.id
|
||||
|
@ -443,7 +463,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@grabTeamConfigs()
|
||||
@thangTypeTeams = {}
|
||||
for thang in @level.get('thangs')
|
||||
if @level.get('type', true) in ['hero', 'course'] and thang.id is 'Hero Placeholder'
|
||||
if @level.isType('hero', 'course') and thang.id is 'Hero Placeholder'
|
||||
continue # No team colors for heroes on single-player levels
|
||||
for component in thang.components
|
||||
if team = component.config?.team
|
||||
|
@ -471,6 +491,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
initWorld: ->
|
||||
return if @initialized
|
||||
@initialized = true
|
||||
return if @level.isType('web-dev')
|
||||
@world = new World()
|
||||
@world.levelSessionIDs = if @opponentSessionID then [@sessionID, @opponentSessionID] else [@sessionID]
|
||||
@world.submissionCount = @session?.get('state')?.submissionCount ? 0
|
||||
|
|
|
@ -74,7 +74,7 @@ module.exports = class LevelSetupManager extends CocoClass
|
|||
@session.set 'heroConfig', {"thangType":raider,"inventory":{}}
|
||||
@onInventoryModalPlayClicked()
|
||||
return
|
||||
if @level.get('type', true) in ['course', 'course-ladder'] or window.serverConfig.picoCTF
|
||||
if @level.isType('course', 'course-ladder', 'game-dev', 'web-dev') or window.serverConfig.picoCTF
|
||||
@onInventoryModalPlayClicked()
|
||||
return
|
||||
@heroesModal = new PlayHeroesModal({supermodel: @supermodel, session: @session, confirmButtonI18N: 'play.next', level: @level, hadEverChosenHero: @options.hadEverChosenHero})
|
||||
|
|
|
@ -195,6 +195,17 @@ module.exports =
|
|||
_.assign(progressData, progressMixin)
|
||||
return progressData
|
||||
|
||||
courseLabelsArray: (courses) ->
|
||||
labels = []
|
||||
courseLabelIndexes = CS: 0, GD: 0, WD: 0
|
||||
for course in courses
|
||||
acronym = switch
|
||||
when /game-dev/.test(course.get('slug')) then 'GD'
|
||||
when /web-dev/.test(course.get('slug')) then 'WD'
|
||||
else 'CS'
|
||||
labels.push acronym + ++courseLabelIndexes[acronym]
|
||||
labels
|
||||
|
||||
progressMixin =
|
||||
get: (options={}) ->
|
||||
{ classroom, course, level, user } = options
|
||||
|
|
|
@ -10,7 +10,6 @@ Letterbox = require './Letterbox'
|
|||
Dimmer = require './Dimmer'
|
||||
CountdownScreen = require './CountdownScreen'
|
||||
PlaybackOverScreen = require './PlaybackOverScreen'
|
||||
WaitingScreen = require './WaitingScreen'
|
||||
DebugDisplay = require './DebugDisplay'
|
||||
CoordinateDisplay = require './CoordinateDisplay'
|
||||
CoordinateGrid = require './CoordinateGrid'
|
||||
|
@ -70,7 +69,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
'level:set-letterbox': 'onSetLetterbox'
|
||||
'application:idle-changed': 'onIdleChanged'
|
||||
'camera:zoom-updated': 'onZoomUpdated'
|
||||
'playback:real-time-playback-waiting': 'onRealTimePlaybackWaiting'
|
||||
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
|
||||
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
||||
'level:flag-color-selected': 'onFlagColorSelected'
|
||||
|
@ -135,7 +133,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer, showsCountdown: @world.showsCountdown
|
||||
@playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer, playerNames: @options.playerNames
|
||||
@normalStage.addChildAt @playbackOverScreen.dimLayer, 0 # Put this below the other layers, actually, so we can more easily read text on the screen.
|
||||
@waitingScreen = new WaitingScreen camera: @camera, layer: @screenLayer
|
||||
@initCoordinates()
|
||||
@webGLStage.enableMouseOver(10)
|
||||
@webGLStage.addEventListener 'stagemousemove', @onMouseMove
|
||||
|
@ -602,9 +599,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
|
||||
#- Real-time playback
|
||||
|
||||
onRealTimePlaybackWaiting: (e) ->
|
||||
@onRealTimePlaybackStarted e
|
||||
|
||||
onRealTimePlaybackStarted: (e) ->
|
||||
return if @realTime
|
||||
@realTimeInputEvents.reset()
|
||||
|
@ -741,7 +735,6 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@dimmer?.destroy()
|
||||
@countdownScreen?.destroy()
|
||||
@playbackOverScreen?.destroy()
|
||||
@waitingScreen?.destroy()
|
||||
@coordinateDisplay?.destroy()
|
||||
@coordinateGrid?.destroy()
|
||||
@normalStage.clear()
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
CocoClass = require 'core/CocoClass'
|
||||
RealTimeCollection = require 'collections/RealTimeCollection'
|
||||
|
||||
module.exports = class WaitingScreen extends CocoClass
|
||||
subscriptions:
|
||||
'playback:real-time-playback-waiting': 'onRealTimePlaybackWaiting'
|
||||
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
|
||||
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
||||
'real-time-multiplayer:player-status': 'onRealTimeMultiplayerPlayerStatus'
|
||||
|
||||
constructor: (options) ->
|
||||
super()
|
||||
options ?= {}
|
||||
@camera = options.camera
|
||||
@layer = options.layer
|
||||
@waitingText = options.text or 'Waiting...'
|
||||
console.error @toString(), 'needs a camera.' unless @camera
|
||||
console.error @toString(), 'needs a layer.' unless @layer
|
||||
@build()
|
||||
|
||||
onCastingBegins: (e) -> @show() unless e.preload
|
||||
onCastingEnds: (e) -> @hide()
|
||||
|
||||
toString: -> '<WaitingScreen>'
|
||||
|
||||
build: ->
|
||||
@dimLayer = new createjs.Container()
|
||||
@dimLayer.mouseEnabled = @dimLayer.mouseChildren = false
|
||||
@dimLayer.addChild @dimScreen = new createjs.Shape()
|
||||
@dimScreen.graphics.beginFill('rgba(0,0,0,0.5)').rect 0, 0, @camera.canvasWidth, @camera.canvasHeight
|
||||
@dimLayer.alpha = 0
|
||||
@dimLayer.addChild @makeWaitingText()
|
||||
|
||||
makeWaitingText: ->
|
||||
size = Math.ceil @camera.canvasHeight / 8
|
||||
text = new createjs.Text @waitingText, "#{size}px Open Sans Condensed", '#F7B42C'
|
||||
text.shadow = new createjs.Shadow '#000', Math.ceil(@camera.canvasHeight / 300), Math.ceil(@camera.canvasHeight / 300), Math.ceil(@camera.canvasHeight / 120)
|
||||
text.textAlign = 'center'
|
||||
text.textBaseline = 'middle'
|
||||
text.x = @camera.canvasWidth / 2
|
||||
text.y = @camera.canvasHeight / 2
|
||||
@text = text
|
||||
return text
|
||||
|
||||
show: ->
|
||||
return if @showing
|
||||
@showing = true
|
||||
@dimLayer.alpha = 0
|
||||
createjs.Tween.removeTweens @dimLayer
|
||||
createjs.Tween.get(@dimLayer).to({alpha: 1}, 500)
|
||||
@layer.addChild @dimLayer
|
||||
|
||||
hide: ->
|
||||
return unless @showing
|
||||
@showing = false
|
||||
createjs.Tween.removeTweens @dimLayer
|
||||
createjs.Tween.get(@dimLayer).to({alpha: 0}, 500).call => @layer.removeChild @dimLayer unless @destroyed
|
||||
|
||||
onRealTimeMultiplayerPlayerStatus: (e) -> @text.text = e.status
|
||||
|
||||
onRealTimePlaybackWaiting: (e) -> @show()
|
||||
|
||||
onRealTimePlaybackStarted: (e) -> @hide()
|
||||
|
||||
onRealTimePlaybackEnded: (e) -> @hide()
|
|
@ -38,6 +38,7 @@ module.exports = class GoalManager extends CocoClass
|
|||
|
||||
subscriptions:
|
||||
'god:new-world-created': 'onNewWorldCreated'
|
||||
'god:new-html-goal-states': 'onNewHTMLGoalStates'
|
||||
'level:restarted': 'onLevelRestarted'
|
||||
|
||||
backgroundSubscriptions:
|
||||
|
@ -86,6 +87,9 @@ module.exports = class GoalManager extends CocoClass
|
|||
@world = e.world
|
||||
@updateGoalStates(e.goalStates) if e.goalStates?
|
||||
|
||||
onNewHTMLGoalStates: (e) ->
|
||||
@updateGoalStates(e.goalStates) if e.goalStates?
|
||||
|
||||
updateGoalStates: (newGoalStates) ->
|
||||
for goalID, goalState of newGoalStates
|
||||
continue unless @goalStates[goalID]?
|
||||
|
@ -114,7 +118,7 @@ module.exports = class GoalManager extends CocoClass
|
|||
goalStates: @goalStates
|
||||
goals: @goals
|
||||
overallStatus: overallStatus
|
||||
timedOut: @world.totalFrames is @world.maxTotalFrames and overallStatus not in ['success', 'failure']
|
||||
timedOut: @world? and (@world.totalFrames is @world.maxTotalFrames and overallStatus not in ['success', 'failure'])
|
||||
Backbone.Mediator.publish('goal-manager:new-goal-states', event)
|
||||
|
||||
checkOverallStatus: (ignoreIncomplete=false) ->
|
||||
|
@ -264,7 +268,7 @@ module.exports = class GoalManager extends CocoClass
|
|||
mostEagerGoal = _.min matchedGoals, 'worldEndsAfter'
|
||||
victory = overallStatus is 'success'
|
||||
tentative = overallStatus is 'success'
|
||||
@world.endWorld victory, mostEagerGoal.worldEndsAfter, tentative if mostEagerGoal isnt Infinity
|
||||
@world?.endWorld victory, mostEagerGoal.worldEndsAfter, tentative if mostEagerGoal isnt Infinity
|
||||
|
||||
updateGoalState: (goalID, thangID, progressObjectName, frameNumber) ->
|
||||
# A thang has done something related to the goal!
|
||||
|
@ -291,7 +295,7 @@ module.exports = class GoalManager extends CocoClass
|
|||
mostEagerGoal = _.min matchedGoals, 'worldEndsAfter'
|
||||
victory = overallStatus is 'success'
|
||||
tentative = overallStatus is 'success'
|
||||
@world.endWorld victory, mostEagerGoal.worldEndsAfter, tentative if mostEagerGoal isnt Infinity
|
||||
@world?.endWorld victory, mostEagerGoal.worldEndsAfter, tentative if mostEagerGoal isnt Infinity
|
||||
|
||||
goalIsPositive: (goalID) ->
|
||||
# Positive goals are completed when all conditions are true (kill all these thangs)
|
||||
|
|
|
@ -450,8 +450,6 @@
|
|||
incomplete: "Incomplete"
|
||||
timed_out: "Ran out of time"
|
||||
failing: "Failing"
|
||||
control_bar_multiplayer: "Multiplayer"
|
||||
control_bar_join_game: "Join Game"
|
||||
reload: "Reload"
|
||||
reload_title: "Reload All Code?"
|
||||
reload_really: "Are you sure you want to reload this level back to the beginning?"
|
||||
|
@ -480,10 +478,7 @@
|
|||
tome_cast_button_running: "Running"
|
||||
tome_cast_button_ran: "Ran"
|
||||
tome_submit_button: "Submit"
|
||||
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: "Select Someone for "
|
||||
tome_reload_method: "Reload original code to restart the level" # {change}
|
||||
tome_available_spells: "Available Spells"
|
||||
tome_your_skills: "Your Skills"
|
||||
tome_current_method: "Current Method"
|
||||
|
@ -781,7 +776,7 @@
|
|||
mission_description_1: "<strong>Programming is magic</strong>. It's the ability to create things from pure imagination. We started CodeCombat to give learners the feeling of wizardly power at their fingertips by using <strong>typed code</strong>."
|
||||
mission_description_2: "As it turns out, that enables them to learn faster too. WAY faster. It's like having a conversation instead of reading a manual. We want to bring that conversation to every school and to <strong>every student</strong>, because everyone should have the chance to learn the magic of programming."
|
||||
team_title: "Meet the CodeCombat team"
|
||||
team_values: "We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our Github contributors, because we value growth and learning in our team."
|
||||
team_values: "We value open and respectful dialog, where the best idea wins. Our decisions are grounded in customer research and our process is focused on delivering tangible results for them. Everyone is hands-on, from our CEO to our GitHub contributors, because we value growth and learning in our team."
|
||||
nick_title: "Cofounder, CEO"
|
||||
nick_blurb: "Motivation Guru"
|
||||
matt_title: "Cofounder, CTO"
|
||||
|
@ -802,6 +797,7 @@
|
|||
phoenix_title: "Software Engineer"
|
||||
nolan_title: "Territory Manager"
|
||||
elliot_title: "Partnership Manager"
|
||||
lisa_title: "Market Development Rep"
|
||||
retrostyle_title: "Illustration"
|
||||
retrostyle_blurb: "RetroStyle Games"
|
||||
jose_title: "Music"
|
||||
|
@ -1477,6 +1473,29 @@
|
|||
status_not_enrolled: "Not Enrolled"
|
||||
status_enrolled: "Expires on {{date}}"
|
||||
select_all: "Select All"
|
||||
projects: "Projects"
|
||||
|
||||
sharing:
|
||||
game: "Game"
|
||||
webpage: "Webpage"
|
||||
share_game: "Share This Game"
|
||||
share_web: "Share This Webpage"
|
||||
victory_share_prefix: "Share this link to invite your friends & family to"
|
||||
victory_share_game: "play your game level"
|
||||
victory_share_web: "view your webpage"
|
||||
victory_share_suffix: "."
|
||||
victory_course_share_prefix: "This link will let your friends & family"
|
||||
victory_course_share_game: "play the game"
|
||||
victory_course_share_web: "view the webpage"
|
||||
victory_course_share_suffix: "you just created."
|
||||
copy_url: "Copy URL"
|
||||
|
||||
game_dev:
|
||||
creator: "Creator"
|
||||
|
||||
web_dev:
|
||||
image_gallery_title: "Image Gallery"
|
||||
image_gallery_description: "Copy these images into your webpage, or find your own image URLs online."
|
||||
|
||||
classes:
|
||||
archmage_title: "Archmage"
|
||||
|
@ -1879,6 +1898,17 @@
|
|||
vectors: "Vectors"
|
||||
while_loops: "While Loops"
|
||||
recursion: "Recursion"
|
||||
basic_html: "Basic HTML" # TODO: these web-dev concepts will change, don't need to translate
|
||||
basic_css: "Basic CSS"
|
||||
basic_web_scripting: "Basic Web Scripting"
|
||||
intermediate_html: "Intermediate HTML"
|
||||
intermediate_css: "Intermediate CSS"
|
||||
intermediate_web_scripting: "Intermediate Web Scripting"
|
||||
advanced_html: "Advanced HTML"
|
||||
advanced_css: "Advanced CSS"
|
||||
advanced_web_scripting: "Advanced Web Scripting"
|
||||
jquery: "jQuery"
|
||||
bootstrap: "Bootstrap"
|
||||
|
||||
delta:
|
||||
added: "Added"
|
||||
|
@ -1890,16 +1920,6 @@
|
|||
merge_conflict_with: "MERGE CONFLICT WITH"
|
||||
no_changes: "No Changes"
|
||||
|
||||
multiplayer:
|
||||
multiplayer_title: "Multiplayer Settings" # We'll be changing this around significantly soon. Until then, it's not important to translate.
|
||||
multiplayer_toggle: "Enable multiplayer"
|
||||
multiplayer_toggle_description: "Allow others to join your game."
|
||||
multiplayer_link_description: "Give this link to anyone to have them join you."
|
||||
multiplayer_hint_label: "Hint:"
|
||||
multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
|
||||
multiplayer_coming_soon: "More multiplayer features to come!"
|
||||
multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
|
||||
|
||||
legal:
|
||||
page_title: "Legal"
|
||||
opensource_intro: "CodeCombat is completely open source."
|
||||
|
|
|
@ -74,7 +74,7 @@ module.exports = class Classroom extends CocoModel
|
|||
}
|
||||
|
||||
getLevels: (options={}) ->
|
||||
# options: courseID, withoutLadderLevels
|
||||
# options: courseID, withoutLadderLevels, projectLevels
|
||||
Levels = require 'collections/Levels'
|
||||
courses = @get('courses')
|
||||
return new Levels() unless courses
|
||||
|
@ -86,6 +86,8 @@ module.exports = class Classroom extends CocoModel
|
|||
levels = new Levels(_.flatten(levelObjects))
|
||||
if options.withoutLadderLevels
|
||||
levels.remove(levels.filter((level) -> level.isLadder()))
|
||||
if options.projectLevels
|
||||
levels.remove(levels.filter((level) -> level.get('shareable') isnt 'project'))
|
||||
return levels
|
||||
|
||||
getLadderLevel: (courseID) ->
|
||||
|
|
|
@ -5,3 +5,10 @@ module.exports = class Course extends CocoModel
|
|||
@className: 'Course'
|
||||
@schema: schema
|
||||
urlRoot: '/db/course'
|
||||
|
||||
fetchForCourseInstance: (courseInstanceID, opts) ->
|
||||
options = {
|
||||
url: "/db/course_instance/#{courseInstanceID}/course"
|
||||
}
|
||||
_.extend options, opts
|
||||
@fetch options
|
||||
|
|
|
@ -34,7 +34,7 @@ module.exports = class Level extends CocoModel
|
|||
for tt in supermodel.getModels ThangType
|
||||
if tmap[tt.get('original')] or
|
||||
(tt.get('kind') isnt 'Hero' and tt.get('kind')? and tt.get('components') and not tt.notInLevel) or
|
||||
(tt.get('kind') is 'Hero' and ((@get('type', true) in ['course', 'course-ladder']) or tt.get('original') in sessionHeroes))
|
||||
(tt.get('kind') is 'Hero' and (@isType('course', 'course-ladder', 'game-dev') or tt.get('original') in sessionHeroes))
|
||||
o.thangTypes.push (original: tt.get('original'), name: tt.get('name'), components: $.extend(true, [], tt.get('components')))
|
||||
@sortThangComponents o.thangTypes, o.levelComponents, 'ThangType'
|
||||
@fillInDefaultComponentConfiguration o.thangTypes, o.levelComponents
|
||||
|
@ -59,7 +59,7 @@ module.exports = class Level extends CocoModel
|
|||
|
||||
denormalize: (supermodel, session, otherSession) ->
|
||||
o = $.extend true, {}, @attributes
|
||||
if o.thangs and @get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev']
|
||||
if o.thangs and @isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev')
|
||||
thangTypesWithComponents = (tt for tt in supermodel.getModels(ThangType) when tt.get('components')?)
|
||||
thangTypesByOriginal = _.indexBy thangTypesWithComponents, (tt) -> tt.get('original') # Optimization
|
||||
for levelThang in o.thangs
|
||||
|
@ -68,7 +68,7 @@ module.exports = class Level extends CocoModel
|
|||
|
||||
denormalizeThang: (levelThang, supermodel, session, otherSession, thangTypesByOriginal) ->
|
||||
levelThang.components ?= []
|
||||
isHero = /Hero Placeholder/.test(levelThang.id) and @get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
|
||||
isHero = /Hero Placeholder/.test(levelThang.id) and @isType('hero', 'hero-ladder', 'hero-coop')
|
||||
if isHero and otherSession
|
||||
# If it's a hero and there's another session, find the right session for it.
|
||||
# If there is no other session (playing against default code, or on single player), clone all placeholders.
|
||||
|
@ -147,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'] and not @headless and not @sessionless
|
||||
if /Hero Placeholder/.test(levelThang.id) and @isType('course') and not @headless and not @sessionless
|
||||
heroThangType = me.get('heroConfig')?.thangType or ThangType.heroes.captain
|
||||
levelThang.thangType = heroThangType if heroThangType
|
||||
|
||||
|
@ -263,6 +263,9 @@ module.exports = class Level extends CocoModel
|
|||
isLadder: ->
|
||||
return @get('type')?.indexOf('ladder') > -1
|
||||
|
||||
isType: (types...) ->
|
||||
return @get('type', true) in types
|
||||
|
||||
fetchNextForCourse: ({ levelOriginalID, courseInstanceID, courseID, sessionID }, options={}) ->
|
||||
if courseInstanceID
|
||||
options.url = "/db/course_instance/#{courseInstanceID}/levels/#{levelOriginalID}/sessions/#{sessionID}/next"
|
||||
|
|
|
@ -15,8 +15,6 @@ module.exports = class LevelSession extends CocoModel
|
|||
updatePermissions: ->
|
||||
permissions = @get 'permissions', true
|
||||
permissions = (p for p in permissions when p.target isnt 'public')
|
||||
if @get('multiplayer')
|
||||
permissions.push {target: 'public', access: 'write'}
|
||||
@set 'permissions', permissions
|
||||
|
||||
getSourceFor: (spellKey) ->
|
||||
|
@ -76,6 +74,7 @@ module.exports = class LevelSession extends CocoModel
|
|||
wait
|
||||
|
||||
recordScores: (scores, level) ->
|
||||
return unless scores
|
||||
state = @get 'state'
|
||||
oldTopScores = state.topScores ? []
|
||||
newTopScores = []
|
||||
|
@ -93,3 +92,17 @@ module.exports = class LevelSession extends CocoModel
|
|||
newTopScores.push oldTopScore
|
||||
state.topScores = newTopScores
|
||||
@set 'state', state
|
||||
|
||||
generateSpellsObject: (options={}) ->
|
||||
{level} = options
|
||||
{createAetherOptions} = require 'lib/aether_utils'
|
||||
aetherOptions = createAetherOptions functionName: 'plan', codeLanguage: @get('codeLanguage'), skipProtectAPI: options.level?.isType('game-dev')
|
||||
spellThang = thang: {id: 'Hero Placeholder'}, aether: new Aether aetherOptions
|
||||
spells = "hero-placeholder/plan": thang: spellThang, name: 'plan'
|
||||
source = @get('code')?['hero-placeholder']?.plan ? ''
|
||||
try
|
||||
spellThang.aether.transpile source
|
||||
catch e
|
||||
console.log "Couldn't transpile!\n#{source}\n", e
|
||||
spellThang.aether.transpile ''
|
||||
spells
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = class RealTimeModel extends Backbone.Firebase.Model
|
||||
constructor: (savePath) ->
|
||||
# TODO: Don't hard code this here
|
||||
# TODO: Use prod path in prod
|
||||
@firebase = 'https://codecombat.firebaseio.com/test/db/' + savePath
|
||||
super()
|
|
@ -248,6 +248,15 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
getResource: (rid) ->
|
||||
return @resources[rid]
|
||||
|
||||
# Promises
|
||||
finishLoading: ->
|
||||
new Promise (resolve, reject) =>
|
||||
return resolve(@) if @finished()
|
||||
@once 'failed', ({resource}) ->
|
||||
jqxhr = resource.jqxhr
|
||||
reject({message: jqxhr.responseJSON?.message or jqxhr.responseText or 'Unknown Error'})
|
||||
@once 'loaded-all', => resolve(@)
|
||||
|
||||
class Resource extends Backbone.Model
|
||||
constructor: (name, value=1) ->
|
||||
@name = name
|
||||
|
|
|
@ -61,12 +61,13 @@ _.extend CampaignSchema.properties, {
|
|||
i18n: { type: 'object', format: 'hidden' }
|
||||
requiresSubscription: { type: 'boolean' }
|
||||
replayable: { type: 'boolean' }
|
||||
type: {'enum': ['ladder', 'ladder-tutorial', 'hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev']}
|
||||
type: {'enum': ['ladder', 'ladder-tutorial', 'hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev']}
|
||||
slug: { type: 'string', format: 'hidden' }
|
||||
original: { type: 'string', format: 'hidden' }
|
||||
adventurer: { type: 'boolean' }
|
||||
practice: { type: 'boolean' }
|
||||
practiceThresholdMinutes: {type: 'number'}
|
||||
shareable: { title: 'Shareable', type: ['string', 'boolean'], enum: [false, true, 'project'], description: 'Whether the level is not shareable, shareable, or a sharing-encouraged project level.' }
|
||||
adminOnly: { type: 'boolean' }
|
||||
disableSpaces: { type: ['boolean','number'] }
|
||||
hidesSubmitUntilRun: { type: 'boolean' }
|
||||
|
|
|
@ -25,7 +25,7 @@ _.extend ClassroomSchema.properties,
|
|||
levels: c.array { title: 'Levels' }, c.object { title: 'Level' }, {
|
||||
practice: {type: 'boolean'}
|
||||
practiceThresholdMinutes: {type: 'number'}
|
||||
shareable: {type: 'boolean'}
|
||||
shareable: { title: 'Shareable', type: ['string', 'boolean'], enum: [false, true, 'project'], description: 'Whether the level is not shareable, shareable, or a sharing-encouraged project level.' }
|
||||
type: c.shortString()
|
||||
original: c.objectId()
|
||||
name: {type: 'string'}
|
||||
|
|
|
@ -114,6 +114,9 @@ GoalSchema = c.object {title: 'Goal', description: 'A goal that the player can a
|
|||
targets: c.array {title: 'Targets', description: 'The target items which the Thangs must not collect.', minItems: 1}, thang
|
||||
codeProblems: c.array {title: 'Code Problems', description: 'A list of Thang IDs that should not have any code problems, or team names.', uniqueItems: true, minItems: 1, 'default': ['humans']}, thang
|
||||
linesOfCode: {title: 'Lines of Code', description: 'A mapping of Thang IDs or teams to how many many lines of code should be allowed (well, statements).', type: 'object', default: {humans: 10}, additionalProperties: {type: 'integer', description: 'How many lines to allow for this Thang.'}}
|
||||
html: c.object {title: 'HTML', description: 'A jQuery selector and what its result should be'},
|
||||
selector: {type: 'string', description: 'jQuery selector to run on the user HTML, like "h1:first-child"'}
|
||||
valueChecks: c.array {title: 'Value checks', description: 'Logical checks on the resulting value for this goal to pass.', format: 'event-prereqs'}, EventPrereqSchema
|
||||
|
||||
ResponseSchema = c.object {title: 'Dialogue Button', description: 'A button to be shown to the user with the dialogue.', required: ['text']},
|
||||
text: {title: 'Title', description: 'The text that will be on the button', 'default': 'Okay', type: 'string', maxLength: 30}
|
||||
|
@ -313,7 +316,7 @@ _.extend LevelSchema.properties,
|
|||
icon: {type: 'string', format: 'image-file', title: 'Icon'}
|
||||
banner: {type: 'string', format: 'image-file', title: 'Banner'}
|
||||
goals: c.array {title: 'Goals', description: 'An array of goals which are visible to the player and can trigger scripts.'}, GoalSchema
|
||||
type: c.shortString(title: 'Type', description: 'What kind of level this is.', 'enum': ['campaign', 'ladder', 'ladder-tutorial', 'hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev'])
|
||||
type: c.shortString(title: 'Type', description: 'What kind of level this is.', 'enum': ['campaign', 'ladder', 'ladder-tutorial', 'hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev'])
|
||||
terrain: c.terrainString
|
||||
showsGuide: c.shortString(title: 'Shows Guide', description: 'If the guide is shown at the beginning of the level.', 'enum': ['first-time', 'always'])
|
||||
requiresSubscription: {title: 'Requires Subscription', description: 'Whether this level is available to subscribers only.', type: 'boolean'}
|
||||
|
@ -325,8 +328,8 @@ _.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.'}
|
||||
shareable: { title: 'Shareable', type: ['string', 'boolean'], enum: [false, true, 'project'], description: 'Whether the level is not shareable, shareable, or a sharing-encouraged project level.' }
|
||||
|
||||
# Admin flags
|
||||
adventurer: { type: 'boolean' }
|
||||
|
|
|
@ -37,8 +37,6 @@ _.extend LevelSessionSchema.properties,
|
|||
type: 'string'
|
||||
levelID:
|
||||
type: 'string'
|
||||
multiplayer:
|
||||
type: 'boolean'
|
||||
creator: c.objectId
|
||||
links:
|
||||
[
|
||||
|
|
|
@ -261,4 +261,15 @@ me.concept = me.shortString enum: [
|
|||
'vectors'
|
||||
'while_loops'
|
||||
'recursion'
|
||||
'basic_html'
|
||||
'basic_css'
|
||||
'basic_web_scripting'
|
||||
'intermediate_html'
|
||||
'intermediate_css'
|
||||
'intermediate_web_scripting'
|
||||
'advanced_html'
|
||||
'advanced_css'
|
||||
'advanced_web_scripting'
|
||||
'jquery'
|
||||
'bootstrap'
|
||||
]
|
||||
|
|
|
@ -45,6 +45,10 @@ module.exports =
|
|||
|
||||
'god:streaming-world-updated': worldUpdatedEventSchema
|
||||
|
||||
'god:new-html-goal-states': c.object {required: ['goalStates', 'overallStatus']},
|
||||
goalStates: goalStatesSchema
|
||||
overallStatus: {type: ['string', 'null'], enum: ['success', 'failure', 'incomplete', null]}
|
||||
|
||||
'god:goals-calculated': c.object {required: ['goalStates', 'god']},
|
||||
god: {type: 'object'}
|
||||
goalStates: goalStatesSchema
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
c = require 'schemas/schemas'
|
||||
|
||||
module.exports =
|
||||
'real-time-multiplayer:created-game': c.object {title: 'Multiplayer created game', required: ['realTimeSessionID']},
|
||||
realTimeSessionID: {type: 'string'}
|
||||
|
||||
'real-time-multiplayer:joined-game': c.object {title: 'Multiplayer joined game', required: ['realTimeSessionID']},
|
||||
realTimeSessionID: {type: 'string'}
|
||||
|
||||
'real-time-multiplayer:left-game': c.object {title: 'Multiplayer left game'},
|
||||
userID: {type: 'string'}
|
||||
|
||||
'real-time-multiplayer:manual-cast': c.object {title: 'Multiplayer manual cast'}
|
||||
|
||||
'real-time-multiplayer:new-opponent-code': c.object {title: 'Multiplayer new opponent code', required: ['code', 'codeLanguage']},
|
||||
code: {type: 'object'}
|
||||
codeLanguage: {type: 'string'}
|
||||
team: {type: 'string'}
|
||||
|
||||
'real-time-multiplayer:player-status': c.object {title: 'Multiplayer player status', required: ['status']},
|
||||
status: {type: 'string'}
|
|
@ -96,8 +96,6 @@ module.exports =
|
|||
|
||||
'playback:stop-real-time-playback': c.object {}
|
||||
|
||||
'playback:real-time-playback-waiting': c.object {}
|
||||
|
||||
'playback:real-time-playback-started': c.object {}
|
||||
|
||||
'playback:real-time-playback-ended': c.object {}
|
||||
|
|
|
@ -39,8 +39,6 @@ module.exports =
|
|||
variableChain: c.array {}, {type: 'string'}
|
||||
frame: {type: 'integer', minimum: 0}
|
||||
|
||||
'tome:toggle-spell-list': c.object {title: 'Toggle Spell List', description: 'Published when you toggle the dropdown for a thang\'s spells'}
|
||||
|
||||
'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: []},
|
||||
spell: {type: 'object'}
|
||||
|
||||
|
@ -91,10 +89,6 @@ module.exports =
|
|||
problems: {type: 'array'}
|
||||
isCast: {type: 'boolean'}
|
||||
|
||||
'tome:spell-shown': c.object {title: 'Spell Shown', description: 'Published when we show a spell', required: ['thang', 'spell']},
|
||||
thang: {type: 'object'}
|
||||
spell: {type: 'object'}
|
||||
|
||||
'tome:change-language': c.object {title: 'Tome Change Language', description: 'Published when the Tome should update its programming language', required: ['language']},
|
||||
language: {type: 'string'}
|
||||
reload: {type: 'boolean', description: 'Whether player code should reload to the default when the language changes.'}
|
||||
|
@ -146,3 +140,7 @@ module.exports =
|
|||
lineOffsetPx: {type: ['number', 'undefined']}
|
||||
'tome:hide-problem-alert': c.object {title: 'Hide Problem Alert'}
|
||||
'tome:jiggle-problem-alert': c.object {title: 'Jiggle Problem Alert'}
|
||||
|
||||
'tome:html-updated': c.object {title: 'HTML Updated', required: ['html', 'create']},
|
||||
html: {type: 'string', description: 'The full HTML to display'}
|
||||
create: {type: 'boolean', description: 'Whether we should (re)create the DOM (as opposed to updating it)'}
|
||||
|
|
|
@ -35,3 +35,6 @@
|
|||
|
||||
h1
|
||||
font-size: 48px
|
||||
|
||||
.btn-view-project-level
|
||||
margin-left: 10px;
|
||||
|
|
|
@ -177,7 +177,7 @@
|
|||
|
||||
// Course Progress tab
|
||||
|
||||
#course-progress-tab
|
||||
#course-progress-tab, #student-projects-tab
|
||||
.course-overview-row
|
||||
margin-top: 50px
|
||||
border: thin solid gray
|
||||
|
@ -222,6 +222,19 @@
|
|||
margin-top: 6.5px
|
||||
margin-bottom: 6.5px
|
||||
|
||||
#student-projects-tab
|
||||
.student-levels-table
|
||||
margin-top: 0px
|
||||
|
||||
.student-info
|
||||
margin-top: 5px
|
||||
|
||||
.student-levels-row
|
||||
padding-top: 10px
|
||||
padding-bottom: 15px
|
||||
|
||||
.btn-view-project-level
|
||||
margin-left: 15px
|
||||
|
||||
// Checkboxes
|
||||
.checkbox-flat
|
||||
|
|
|
@ -6,10 +6,10 @@ $mapWidth: 2350
|
|||
$levelDotWidth: 2%
|
||||
$levelDotHeight: $levelDotWidth * $mapWidth / $mapHeight
|
||||
$levelDotZ: $levelDotHeight * 0.25
|
||||
$levelDotHoverZ: $levelDotZ * 2
|
||||
$levelDotHoverZ: $levelDotZ * 1.5
|
||||
$levelDotShadowWidth: 0.8 * $levelDotWidth
|
||||
$levelDotShadowHeight: 0.8 * $levelDotHeight
|
||||
$levelClickRadius: 40px
|
||||
$levelClickRadius: 20px
|
||||
$gameControlSize: 80px
|
||||
$gameControlMargin: 30px
|
||||
|
||||
|
@ -25,8 +25,10 @@ $gameControlMargin: 30px
|
|||
margin-bottom: -$levelDotHeight / 3 + $levelDotZ
|
||||
|
||||
#campaign-view
|
||||
width: 100%
|
||||
height: 100%
|
||||
top: 0
|
||||
right: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
position: absolute
|
||||
|
||||
.gradient
|
||||
|
@ -105,7 +107,7 @@ $gameControlMargin: 30px
|
|||
|
||||
.level-difficulty-banner-text
|
||||
position: absolute
|
||||
bottom: 170%
|
||||
bottom: 80%
|
||||
pointer-events: none
|
||||
color: rgb(246, 208, 2)
|
||||
text-shadow: 0px 1px 0px black
|
||||
|
@ -117,8 +119,7 @@ $gameControlMargin: 30px
|
|||
img.banner
|
||||
position: absolute
|
||||
bottom: 38%
|
||||
left: -50%
|
||||
width: 200%
|
||||
width: 100%
|
||||
pointer-events: none
|
||||
|
||||
img.star
|
||||
|
@ -182,7 +183,7 @@ $gameControlMargin: 30px
|
|||
border-radius: $levelClickRadius
|
||||
|
||||
.tooltip
|
||||
z-index: 2
|
||||
z-index: 3
|
||||
pointer-events: none
|
||||
|
||||
.tooltip-arrow
|
||||
|
@ -616,6 +617,8 @@ $gameControlMargin: 30px
|
|||
|
||||
.gameplay-container
|
||||
position: absolute
|
||||
height: 100%
|
||||
width: 100%
|
||||
|
||||
body.ipad #campaign-view
|
||||
// iPad only supports up to Kithgard Gates for now.
|
||||
|
@ -624,4 +627,3 @@ body.ipad #campaign-view
|
|||
|
||||
body[lang='ru'] .portals h2
|
||||
font-size: 26px
|
||||
|
||||
|
|
|
@ -124,37 +124,6 @@
|
|||
@include rotate(-15deg)
|
||||
vertical-align: middle
|
||||
|
||||
.multiplayer-area-container
|
||||
position: relative
|
||||
width: 100%
|
||||
height: 50px
|
||||
pointer-events: none
|
||||
|
||||
.multiplayer-area
|
||||
min-width: 200px
|
||||
max-width: 293px
|
||||
height: 60px
|
||||
margin: 0 auto
|
||||
padding: 8px
|
||||
border-style: solid
|
||||
border-image: url(/images/level/control_bar_level_name_background.png) 30 fill round
|
||||
border-width: 0 15px 15px 15px
|
||||
text-align: center
|
||||
position: absolute
|
||||
left: 50%
|
||||
cursor: pointer
|
||||
pointer-events: all
|
||||
@include translate(-50%, 0)
|
||||
|
||||
.multiplayer-label
|
||||
font-size: 12px
|
||||
color: $control-yellow-highlight
|
||||
margin-bottom: -5px
|
||||
|
||||
.multiplayer-status
|
||||
color: white
|
||||
font-size: 18px
|
||||
|
||||
.buttons-area
|
||||
position: absolute
|
||||
right: 35px
|
||||
|
@ -210,11 +179,6 @@ html.no-borderimage
|
|||
background: transparent url(/images/level/control_bar_level_name_background.png)
|
||||
background-size: contain
|
||||
background-repeat: no-repeat
|
||||
#control-bar-view .multiplayer-area
|
||||
border: 0
|
||||
background: transparent url(/images/level/control_bar_level_name_background.png)
|
||||
background-size: contain
|
||||
background-repeat: no-repeat
|
||||
|
||||
|
||||
body:not(.ipad)
|
||||
|
|
|
@ -181,3 +181,13 @@ $UNVEIL_TIME: 1.2s
|
|||
left: 48px
|
||||
right: 77px
|
||||
width: auto
|
||||
|
||||
|
||||
#level-view.web-dev
|
||||
#loading-details.preview
|
||||
@media screen and ( min-height: 900px )
|
||||
background: transparent
|
||||
border: 1px solid transparent
|
||||
border-width: 124px 76px 64px 40px
|
||||
border-image: url(/images/level/code_editor_background.png) 124 76 64 40 fill round
|
||||
padding: 0 35px 0 15px
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
padding-top: 0
|
||||
width: 750px
|
||||
|
||||
@media screen and ( max-height: 625px )
|
||||
margin-top: -50px
|
||||
|
||||
.modal-content
|
||||
position: relative
|
||||
margin-top: -251px
|
||||
|
@ -55,10 +58,14 @@
|
|||
top: 80px
|
||||
margin-top: 80px
|
||||
|
||||
@media screen and ( max-height: 650px )
|
||||
padding-top: 10px
|
||||
|
||||
.well-parchment
|
||||
margin-top: 20px
|
||||
|
||||
|
||||
@media screen and ( max-height: 675px )
|
||||
margin-top: 0
|
||||
|
||||
|
||||
html.no-borderimage
|
||||
|
|
|
@ -298,6 +298,33 @@
|
|||
height: 100%
|
||||
position: absolute
|
||||
|
||||
#share-level-container
|
||||
width: 709px
|
||||
height: 96px
|
||||
background: transparent url(/images/pages/play/level/modal/share_level_parchment.png)
|
||||
position: relative
|
||||
text-align: left
|
||||
padding: 12px 20px 0 20px
|
||||
text-align: center
|
||||
|
||||
.share-level-label
|
||||
color: rgb(103, 92, 76)
|
||||
text-transform: uppercase
|
||||
font-weight: bold
|
||||
font-family: $headings-font-family
|
||||
font-size: 18px
|
||||
margin-top: 13px
|
||||
line-height: 18px
|
||||
text-align: center
|
||||
|
||||
#share-level-input
|
||||
font-size: 12px
|
||||
margin-top: 8px
|
||||
|
||||
#share-level-btn
|
||||
width: 100%
|
||||
margin-top: 7px
|
||||
|
||||
|
||||
//- Footer - other stuff
|
||||
|
||||
|
|
|
@ -4,10 +4,18 @@
|
|||
color: black
|
||||
margin-bottom: 5px
|
||||
|
||||
p
|
||||
margin-top: 30px
|
||||
.next-level-description
|
||||
p
|
||||
margin-top: 30px
|
||||
|
||||
.course-title
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
overflow: hidden
|
||||
|
||||
#share-level-input
|
||||
font-size: 12px
|
||||
margin-top: 5px
|
||||
|
||||
#share-level-btn
|
||||
width: 100%
|
||||
|
|
22
app/styles/play/level/play-game-dev-level-view.sass
Normal file
22
app/styles/play/level/play-game-dev-level-view.sass
Normal file
|
@ -0,0 +1,22 @@
|
|||
#play-game-dev-level-view
|
||||
#canvas-wrapper
|
||||
width: 100%
|
||||
position: relative
|
||||
overflow: hidden
|
||||
z-index: 0
|
||||
|
||||
#webgl-surface
|
||||
background-color: #333
|
||||
|
||||
#normal-surface
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
pointer-events: none
|
||||
|
||||
canvas#webgl-surface, canvas#normal-surface
|
||||
display: block
|
||||
z-index: 2
|
||||
|
||||
#play-btn
|
||||
text-transform: uppercase
|
18
app/styles/play/level/play-web-dev-level-view.sass
Normal file
18
app/styles/play/level/play-web-dev-level-view.sass
Normal file
|
@ -0,0 +1,18 @@
|
|||
#play-web-dev-level-view
|
||||
#web-surface-view
|
||||
position: absolute
|
||||
top: 0
|
||||
right: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
z-index: 0
|
||||
|
||||
#info-bar
|
||||
position: absolute
|
||||
right: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
height: 100px
|
||||
z-index: 1
|
||||
background-color: transparent
|
||||
text-align: center
|
|
@ -113,6 +113,14 @@
|
|||
width: -webkit-calc(100% - 38px)
|
||||
width: calc(100% - 38px)
|
||||
|
||||
&.web-dev.hero .properties
|
||||
.property-entry-item-group
|
||||
width: 100px
|
||||
|
||||
.spell-palette-entry-view
|
||||
margin-left: 0
|
||||
width: 100px
|
||||
|
||||
@media only screen and (max-width: 1100px)
|
||||
#spell-palette-view
|
||||
// Make sure we have enough room for at least two columns
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
@import "app/styles/mixins"
|
||||
@import "app/styles/bootstrap/variables"
|
||||
|
||||
.spell-list-entry-view
|
||||
.method-signature
|
||||
background-color: transparent
|
||||
border: 0
|
||||
font-size: 1.1em
|
||||
display: inline-block
|
||||
padding: 4px
|
||||
|
||||
.spell-list-entry-view.spell-tab
|
||||
#spell-top-bar-view
|
||||
$height: 87px
|
||||
$paddingTop: 10px
|
||||
$paddingBottom: 25px
|
||||
|
@ -46,12 +38,6 @@
|
|||
> *:not(.spell-tool-buttons)
|
||||
@include opacity(0.5)
|
||||
|
||||
.thang-avatar-view
|
||||
width: $childSize - 10px
|
||||
margin: 5px 0.4vw
|
||||
display: inline-block
|
||||
float: left
|
||||
|
||||
.btn.btn-small
|
||||
margin-top: 15px
|
||||
margin-right: 1.3vw
|
||||
|
@ -97,46 +83,8 @@
|
|||
.thang-avatar-wrapper
|
||||
border-width: 0
|
||||
|
||||
.spell-list-entry-view:not(.spell-tab)
|
||||
cursor: pointer
|
||||
@include opacity(0.90)
|
||||
clear: both
|
||||
padding: 5px
|
||||
position: relative
|
||||
|
||||
&:hover
|
||||
@include opacity(1)
|
||||
background-color: hsla(240, 40, 80, 0.25)
|
||||
|
||||
&.shows-top-divider:not(:first-child)
|
||||
border-top: 1px dashed #ccc
|
||||
|
||||
.method-signature
|
||||
margin-top: 5px
|
||||
|
||||
.thang-names
|
||||
float: right
|
||||
margin: 8px
|
||||
font-variant: small-caps
|
||||
color: darken(#ca8, 50%)
|
||||
white-space: nowrap
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
font-size: 13px
|
||||
max-width: 35%
|
||||
text-align: right
|
||||
|
||||
.thang-avatar-view
|
||||
width: 40px
|
||||
float: right
|
||||
|
||||
.thang-avatar-wrapper
|
||||
margin: 0 5px 0 0
|
||||
//margin: 2px 10px 2px 5px
|
||||
|
||||
|
||||
//html.no-borderimage
|
||||
// .spell-list-entry-view.spell-tab
|
||||
// .spell-top-bar-view
|
||||
// border-width: 0
|
||||
// border-image: none
|
||||
// background: transparent url(/images/level/code_editor_tab_background.png) no-repeat
|
|
@ -1,20 +0,0 @@
|
|||
@import "app/styles/mixins"
|
||||
@import "app/styles/bootstrap/variables"
|
||||
|
||||
#spell-list-view
|
||||
display: none
|
||||
position: absolute
|
||||
z-index: 10
|
||||
top: 50px
|
||||
left: 0%
|
||||
right: 10%
|
||||
padding: 4%
|
||||
border-style: solid
|
||||
border-image: url(/images/level/popover_border_background.png) 16 12 fill round
|
||||
border-width: 16px 12px
|
||||
|
||||
html.no-borderimage
|
||||
#spell-list-view
|
||||
background: transparent url(/images/level/popover_background.png)
|
||||
background-size: 100% 100%
|
||||
border: 0
|
|
@ -1,30 +0,0 @@
|
|||
@import "app/styles/mixins"
|
||||
@import "app/styles/bootstrap/variables"
|
||||
|
||||
.spell-list-entry-view
|
||||
.spell-list-entry-thangs-view
|
||||
position: absolute
|
||||
z-index: 11
|
||||
top: 50px
|
||||
right: -10%
|
||||
max-width: 70%
|
||||
max-height: 500px
|
||||
overflow: scroll
|
||||
padding: 4%
|
||||
border-style: solid
|
||||
border-image: url(/images/level/popover_border_background.png) 16 12 fill round
|
||||
border-width: 16px 12px
|
||||
|
||||
.thang-avatar-view
|
||||
cursor: pointer
|
||||
max-width: 100px
|
||||
width: 20%
|
||||
display: inline-block
|
||||
|
||||
|
||||
html.no-borderimage
|
||||
.spell-list-entry-view
|
||||
.spell-list-entry-thangs-view
|
||||
background: transparent url(/images/level/popover_background.png)
|
||||
background-size: 100% 100%
|
||||
border: 0
|
|
@ -54,6 +54,7 @@ body:not(.dialogue-view-active)
|
|||
|
||||
.spell-palette-popover.popover
|
||||
// Only those popovers which are our direct children (spell documentation)
|
||||
left: auto !important
|
||||
max-width: 600px
|
||||
padding: 0
|
||||
border-style: solid
|
||||
|
@ -81,7 +82,6 @@ body:not(.dialogue-view-active)
|
|||
@include animation(jiggle .3s infinite)
|
||||
|
||||
&.pinned
|
||||
left: auto !important
|
||||
top: 50px !important
|
||||
right: 45%
|
||||
// bottom: 151px
|
||||
|
|
6
app/styles/play/level/web-surface-view.sass
Normal file
6
app/styles/play/level/web-surface-view.sass
Normal file
|
@ -0,0 +1,6 @@
|
|||
#web-surface-view
|
||||
background-color: white
|
||||
|
||||
iframe
|
||||
width: 100%
|
||||
height: 100%
|
|
@ -1,8 +0,0 @@
|
|||
#multiplayer-view
|
||||
textarea
|
||||
width: 100%
|
||||
box-sizing: border-box
|
||||
padding: 5px
|
||||
text-align: center
|
||||
height: 30px
|
||||
font-size: 11px
|
11
app/styles/play/modal/image-gallery-modal.sass
Normal file
11
app/styles/play/modal/image-gallery-modal.sass
Normal file
|
@ -0,0 +1,11 @@
|
|||
@import "app/styles/mixins"
|
||||
|
||||
#image-gallery-modal
|
||||
.modal-dialog
|
||||
width: 800px
|
||||
|
||||
li
|
||||
font-size: 12px
|
||||
|
||||
.no-select
|
||||
@include user-select(none)
|
|
@ -272,6 +272,29 @@ $level-resize-transition-time: 0.5s
|
|||
right: 45%
|
||||
z-index: 1000000
|
||||
|
||||
&.web-dev
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
|
||||
#playback-view, #thang-hud, #level-dialogue-view, #play-footer, #level-footer-background, #level-footer-shadow
|
||||
display: none
|
||||
|
||||
.game-container, .level-content, #game-area, #canvas-wrapper
|
||||
height: 100%
|
||||
|
||||
#canvas-wrapper canvas
|
||||
display: none
|
||||
|
||||
#web-surface-view
|
||||
position: absolute
|
||||
top: 0
|
||||
right: 0
|
||||
left: 0
|
||||
bottom: 0
|
||||
|
||||
html.fullscreen-editor
|
||||
#level-view
|
||||
#fullscreen-editor-background-screen
|
||||
|
|
|
@ -143,7 +143,7 @@ block content
|
|||
img(src="/images/pages/about/lisa_small.png").img-thumbnail
|
||||
.team-bio
|
||||
h6.label.team-name Lisa Wu
|
||||
small Marketing Development Rep
|
||||
small(data-i18n="about.lisa_title")
|
||||
br
|
||||
|
||||
// Part time / contract
|
||||
|
@ -274,22 +274,8 @@ block content
|
|||
li.small(data-i18n="about.jobs_benefit_7")
|
||||
.col-sm-6.col-md-5.col-md-offset-1.col-lg-4.col-lg-offset-0
|
||||
.job-listing
|
||||
h5 Marketing Manager
|
||||
.text-center
|
||||
small.label
|
||||
| San Francisco • Fulltime
|
||||
p.small We're looking for an amazing marketer to help us fill our funnel and keep the pipeline growing. As our Marketing Manager, you'll be responsible for driving traffic to our website through content creation and converting those visitors into leads and customers using both automated and personalized content.
|
||||
a.job-link.btn.btn-lg.btn-navy(href="https://jobs.lever.co/codecombat/1033ec13-d4a0-498d-99e0-628afdb56fb5" rel="external")
|
||||
span(data-i18n="about.learn_more")
|
||||
.col-sm-6.col-md-5.col-md-offset-1.col-lg-4.col-lg-offset-0
|
||||
.job-listing
|
||||
h5 Sales Representative
|
||||
.text-center
|
||||
small.label
|
||||
| San Francisco • Fulltime
|
||||
p.small School districts are scrambling to offer computer science classes to all their students as a core subject. They have had no solution, because they can't afford to hire enough programming teachers – until now.
|
||||
a.job-link.btn.btn-lg.btn-navy(href="https://jobs.lever.co/codecombat/3f6ff123-16ce-4ecb-aba3-dcf4e8927c47" rel="external")
|
||||
| Learn More
|
||||
h5 (No Open Roles)
|
||||
p.small Check back later for updates on new positions at CodeCombat.
|
||||
.col-sm-6.col-md-5.col-lg-4
|
||||
.job-listing
|
||||
h5(data-i18n="about.jobs_custom_title")
|
||||
|
|
|
@ -44,7 +44,7 @@ block content
|
|||
a(href="/admin/classroom-levels") Classroom Levels
|
||||
li
|
||||
button.classroom-progress-csv.btn.btn-sm.btn-success Classroom Progress CSV
|
||||
input.classroom-progress-class-code(type=text value="<class code>")
|
||||
input.classroom-progress-class-code(type=text placeholder="<class code>")
|
||||
li
|
||||
a(href="/admin/analytics") Dashboard
|
||||
li
|
||||
|
|
|
@ -104,13 +104,23 @@ block content
|
|||
tr
|
||||
td
|
||||
if previousLevelCompleted || view.teacherMode || !passedLastCompletedLevel || levelStatus
|
||||
- var i18n = level.get('type') === 'course-ladder' ? 'play.compete' : 'home.play';
|
||||
button.btn.btn-success.btn-play-level(data-level-slug=level.get('slug'), data-i18n=i18n, data-level-id=level.get('original'))
|
||||
- var i18nTag = level.isType('course-ladder') ? 'play.compete' : 'home.play';
|
||||
button.btn.btn-success.btn-play-level(data-level-slug=level.get('slug'), data-i18n=i18nTag, data-level-id=level.get('original'))
|
||||
if level.get('shareable')
|
||||
- var levelOriginal = level.get('original');
|
||||
- var session = view.levelSessions.find(function(session) { return session.get('level').original === levelOriginal });
|
||||
if session
|
||||
- var url = '/play/' + level.get('type') + '-level/' + level.get('slug') + '/' + session.id + '?course=' + view.courseID;
|
||||
a.btn.btn-warning.btn-view-project-level(href=url)
|
||||
if level.isType('game-dev')
|
||||
span(data-i18n='sharing.game')
|
||||
else
|
||||
span(data-i18n='sharing.webpage')
|
||||
td
|
||||
if view.userLevelStateMap[me.id]
|
||||
div= view.userLevelStateMap[me.id][level.get('original')]
|
||||
td #{level.get('practice') ? 'practice' : 'required'}
|
||||
td #{levelNumber}. #{level.get('name').replace('Course: ', '')}
|
||||
td #{levelNumber}. #{i18n(level.attributes, 'name').replace('Course: ', '')}
|
||||
td
|
||||
if view.levelConceptMap[level.get('original')]
|
||||
each concept in view.course.get('concepts')
|
||||
|
|
|
@ -122,6 +122,10 @@ block content
|
|||
li(class=(activeTab === "#enrollment-status-tab" ? 'active' : ''))
|
||||
a.course-progress-tab-btn(href='#enrollment-status-tab')
|
||||
.small-details.text-center(data-i18n='teacher.enrollment_status')
|
||||
.tab-spacer
|
||||
li(class=(activeTab === "#student-projects-tab" ? 'active' : ''))
|
||||
a.course-progress-tab-btn(href='#student-projects-tab')
|
||||
.small-details.text-center(data-i18n='teacher.projects')
|
||||
.tab-filler
|
||||
|
||||
.tab-content
|
||||
|
@ -129,8 +133,10 @@ block content
|
|||
+studentsTab
|
||||
else if activeTab === '#course-progress-tab'
|
||||
+courseProgressTab
|
||||
else
|
||||
else if activeTab === '#enrollment-status-tab'
|
||||
+enrollmentStatusTab
|
||||
else
|
||||
+studentProjectsTab
|
||||
|
||||
else
|
||||
.text-center.m-t-5.m-b-5
|
||||
|
@ -150,11 +156,10 @@ mixin breadcrumbs
|
|||
mixin longLevelName(data)
|
||||
if data
|
||||
div.level-name
|
||||
span.spr Course
|
||||
span= data.courseNumber
|
||||
span.spr , Level
|
||||
span= data.levelNumber
|
||||
span.spr :
|
||||
span(data-i18n="courses.course")
|
||||
span= ' ' + data.courseNumber + ', '
|
||||
span(data-i18n="play_level.level")
|
||||
span= ' ' + data.levelNumber + ': '
|
||||
span= data.levelName
|
||||
else
|
||||
div.level-name(data-i18n='teacher.not_applicable')
|
||||
|
@ -223,6 +228,8 @@ mixin studentRow(student)
|
|||
+longLevelName(student.latestCompleteLevel)
|
||||
td
|
||||
if state.get('progressData')
|
||||
- var courses = view.classroom.get('courses').map(function(c) { return view.courses.get(c._id); });
|
||||
- var courseLabelsArray = view.helper.courseLabelsArray(courses);
|
||||
each trimCourse, index in view.classroom.get('courses')
|
||||
- var course = view.courses.get(trimCourse._id);
|
||||
- var instance = view.courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
||||
|
@ -230,7 +237,8 @@ mixin studentRow(student)
|
|||
- var progress = state.get('progressData').get({ classroom: view.classroom, course: course, user: student })
|
||||
- var levelsTotal = trimCourse.levels.length
|
||||
//- - var level = ???
|
||||
+studentCourseProgressDot(progress, levelsTotal, level, 'CS' + (index+1))
|
||||
- var label = courseLabelsArray[index];
|
||||
+studentCourseProgressDot(progress, levelsTotal, level, label)
|
||||
unless student.isEnrolled()
|
||||
+enrollStudentButton(student)
|
||||
//- td
|
||||
|
@ -305,7 +313,7 @@ mixin courseOverview
|
|||
.course-overview-row
|
||||
.course-title.student-name
|
||||
span= course.get('name')
|
||||
span :
|
||||
span= ': '
|
||||
span(data-i18n='teacher.course_overview')
|
||||
.course-overview-progress
|
||||
each level, index in levels
|
||||
|
@ -324,7 +332,7 @@ mixin studentLevelsRow(student)
|
|||
each level, index in levels
|
||||
- var progress = state.get('progressData').get({ classroom: view.classroom, course: course, level: level, user: student })
|
||||
- var levelNumber = view.classroom.getLevelNumber(level.get('original'), index + 1)
|
||||
+studentLevelProgressDot(progress, level, levelNumber, session)
|
||||
+studentLevelProgressDot(progress, level, levelNumber)
|
||||
|
||||
mixin studentCourseProgressDot(progress, levelsTotal, level, label)
|
||||
//- TODO: Refactor with TeacherClassesView jade
|
||||
|
@ -336,7 +344,7 @@ mixin studentCourseProgressDot(progress, levelsTotal, level, label)
|
|||
|
||||
mixin allStudentsLevelProgressDot(progress, level, levelNumber)
|
||||
- dotClass = progress.completed ? 'forest' : (progress.started ? 'gold' : '');
|
||||
- levelName = level.get('name')
|
||||
- levelName = i18n(level.attributes, 'name')
|
||||
- context = _.merge(progress, { levelName: levelName, levelNumber: levelNumber, numStudents: view.students.length })
|
||||
.progress-dot.level-progress-dot(class=dotClass, data-html='true', data-title=view.allStudentsLevelProgressDotTemplate(context))
|
||||
+progressDotLabel(levelNumber)
|
||||
|
@ -344,7 +352,7 @@ mixin allStudentsLevelProgressDot(progress, level, levelNumber)
|
|||
mixin studentLevelProgressDot(progress, level, levelNumber)
|
||||
//- TODO: Refactor with TeacherClassesView jade
|
||||
- dotClass = progress.completed ? 'forest' : (progress.started ? 'gold' : '');
|
||||
- levelName = level.get('name')
|
||||
- levelName = i18n(level.attributes, 'name')
|
||||
- context = _.merge(progress, { levelName: levelName, levelNumber: levelNumber, moment: moment })
|
||||
.progress-dot.level-progress-dot(class=dotClass, data-html='true', data-title=view.singleStudentLevelProgressDotTemplate(context))
|
||||
+progressDotLabel(levelNumber)
|
||||
|
@ -430,3 +438,37 @@ mixin enrollmentStatusTab
|
|||
td.enroll-col
|
||||
if status !== 'enrolled'
|
||||
button.enroll-student-button.btn.btn-navy(data-i18n="teacher.enroll_student", data-user-id=student.id, data-event-action="Teachers Class Enrollment Enroll Student")
|
||||
|
||||
mixin studentProjectsTab
|
||||
#student-projects-tab.m-t-3
|
||||
if state.get('progressData')
|
||||
.render-on-course-sync
|
||||
.student-levels-table
|
||||
+sortButtons
|
||||
each student in state.get('students').models
|
||||
+studentProjectsRow(student)
|
||||
|
||||
mixin studentProjectsRow(student)
|
||||
.row.student-levels-row.alternating-background
|
||||
div.student-info.col-sm-3
|
||||
div.student-name= student.broadName()
|
||||
div.student-email.small-details= student.get('email')
|
||||
div.student-levels-progress.col-sm-9
|
||||
each trimCourse in view.classroom.get('courses')
|
||||
- var course = view.courses.get(trimCourse._id);
|
||||
- var levels = view.classroom.getLevels({courseID: course.id, projectLevels: true}).models
|
||||
each level in levels
|
||||
- var progress = state.get('progressData').get({ classroom: view.classroom, course: course, level: level, user: student })
|
||||
- var levelNumber = view.classroom.getLevelNumber(level.get('original'), index + 1)
|
||||
+studentProjectLink(progress, level, levelNumber, course)
|
||||
|
||||
mixin studentProjectLink(progress, level, levelNumber, course)
|
||||
- var colorClass = progress.completed ? 'btn-primary' : (progress.started ? 'btn-warning' : 'btn-primary');
|
||||
- var levelName = i18n(level.attributes, 'name')
|
||||
- var context = _.merge(progress, { levelName: levelName, levelNumber: levelNumber, moment: moment })
|
||||
- var title = view.singleStudentLevelProgressDotTemplate(context);
|
||||
if context.session
|
||||
- var url = '/play/' + level.get('type') + '-level/' + level.get('slug') + '/' + context.session.id + '?course=' + course.id;
|
||||
a(class="btn btn-lg btn-view-project-level " + colorClass, href=url, data-title=title)= levelName
|
||||
else
|
||||
btn(class="btn btn-lg btn-view-project-level " + colorClass, data-title=title, disabled=true)= levelName
|
||||
|
|
|
@ -76,10 +76,13 @@ mixin classRow(classroom)
|
|||
if classroom.get('members').length == 0
|
||||
+addStudentsButton(classroom)
|
||||
else
|
||||
- var courses = classroom.get('courses').map(function(c) { return view.courses.get(c._id); });
|
||||
- var courseLabelsArray = view.helper.courseLabelsArray(courses);
|
||||
each trimCourse, index in classroom.get('courses') || []
|
||||
- var course = view.courses.get(trimCourse._id);
|
||||
if view.courseInstances.findWhere({ classroomID: classroom.id, courseID: course.id })
|
||||
+progressDot(classroom, course, index)
|
||||
- var label = courseLabelsArray[index];
|
||||
+progressDot(classroom, course, label)
|
||||
.view-class-arrow.col-xs-1
|
||||
a.view-class-arrow-inner.glyphicon.glyphicon-chevron-right.view-class-btn(data-classroom-id=classroom.id data-event-action="Teachers Classes View Class Chevron")
|
||||
|
||||
|
@ -99,8 +102,7 @@ mixin createClassButton
|
|||
a.create-classroom-btn.btn.btn-lg.btn-primary(data-i18n='teacher.create_new_class')
|
||||
| Create a New Class
|
||||
|
||||
mixin progressDot(classroom, course, index)
|
||||
//- TODO: Give classes abbreviations instead of using index?
|
||||
mixin progressDot(classroom, course, label)
|
||||
//- TODO: inefficient. Cache this in the view?
|
||||
- courseInstance = view.courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
||||
- var total = classroom.get('members').length
|
||||
|
@ -113,14 +115,11 @@ mixin progressDot(classroom, course, index)
|
|||
- dotClass = complete === total ? 'forest' : started ? 'gold' : '';
|
||||
- var progressDotContext = {total: total, complete: complete};
|
||||
.progress-dot(class=dotClass, data-title=view.progressDotTemplate(progressDotContext))
|
||||
+progressDotLabel(index)
|
||||
+progressDotLabel(label)
|
||||
|
||||
mixin progressDotLabel(index)
|
||||
mixin progressDotLabel(label)
|
||||
.dot-label
|
||||
.text-h6
|
||||
| CS
|
||||
span
|
||||
= index + 1
|
||||
.text-h6= label
|
||||
|
||||
mixin archivedClassRow(classroom)
|
||||
.class.row
|
||||
|
|
|
@ -64,7 +64,7 @@ block header
|
|||
a
|
||||
span.glyphicon-floppy-disk.glyphicon
|
||||
|
||||
if level.get('type') === 'ladder'
|
||||
if level.isType('ladder')
|
||||
li.dropdown
|
||||
a(data-toggle='dropdown').play-with-team-parent
|
||||
span.glyphicon-play.glyphicon
|
||||
|
|
|
@ -5,7 +5,7 @@ block modal-header-content
|
|||
|
||||
block modal-body-content
|
||||
|
||||
if view.level.get('type') != 'course-ladder'
|
||||
if !view.level.isType('course-ladder')
|
||||
h4.language-selection(data-i18n="ladder.select_your_language") Select your language!
|
||||
.form-group.select-group
|
||||
select#tome-language(name="language")
|
||||
|
|
|
@ -7,24 +7,15 @@
|
|||
.levels-link-area
|
||||
a.levels-link(href=homeLink || "/")
|
||||
.glyphicon.glyphicon-play
|
||||
span(data-i18n=me.isSessionless() ? "nav.courses" : (ladderGame ? "general.ladder" : "nav.play")).home-text Levels
|
||||
span(data-i18n=me.isSessionless() ? "nav.courses" : (ladderGame ? "general.ladder" : "nav.play")).home-text
|
||||
|
||||
if isMultiplayerLevel && !observing
|
||||
.multiplayer-area-container
|
||||
.multiplayer-area
|
||||
.multiplayer-label(data-i18n="play_level.control_bar_multiplayer")
|
||||
if multiplayerStatus
|
||||
.multiplayer-status= multiplayerStatus
|
||||
else
|
||||
.multiplayer-status(data-i18n="play_level.control_bar_join_game")
|
||||
else
|
||||
.level-name-area-container
|
||||
.level-name-area
|
||||
.level-label(data-i18n="play_level.level")
|
||||
.level-name(title=difficultyTitle || "")
|
||||
span #{view.levelNumber ? view.levelNumber + '. ' : ''}#{worldName.replace('Course: ', '')}
|
||||
if levelDifficulty
|
||||
sup.level-difficulty= levelDifficulty
|
||||
.level-name-area-container
|
||||
.level-name-area
|
||||
.level-label(data-i18n="play_level.level")
|
||||
.level-name(title=difficultyTitle || "")
|
||||
span #{view.levelNumber ? view.levelNumber + '. ' : ''}#{worldName.replace('Course: ', '')}
|
||||
if levelDifficulty
|
||||
sup.level-difficulty= levelDifficulty
|
||||
|
||||
.buttons-area
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ block modal-body-content
|
|||
textarea(data-i18n="[placeholder]play_level.victory_review_placeholder")
|
||||
.clearfix
|
||||
|
||||
if level.get('type', true) === 'hero' || level.get('type') == 'hero-ladder'
|
||||
if level.isType('hero', 'hero-ladder', 'game-dev', 'web-dev')
|
||||
for achievement in achievements
|
||||
- var animate = achievement.completed && !achievement.completedAWhileAgo
|
||||
.achievement-panel(class=achievement.completedAWhileAgo ? 'earned' : '' data-achievement-id=achievement.id data-animate=animate)
|
||||
|
@ -108,6 +108,24 @@ block modal-footer-content
|
|||
.total-count#gem-total 0
|
||||
.total-label(data-i18n="play_level.victory_gems_gained") Gems Gained
|
||||
|
||||
if view.shareURL
|
||||
#share-level-container
|
||||
span.share-level-label
|
||||
span(data-i18n='sharing.victory_share_prefix') Share this link to invite your friends & family to
|
||||
span= ' '
|
||||
a(href=view.shareURL, target='_blank')
|
||||
if view.level.isType('game-dev')
|
||||
span(data-i18n='sharing.victory_share_game') play your game level
|
||||
else
|
||||
span(data-i18n='sharing.victory_share_web') view your webpage
|
||||
span(data-i18n='sharing.victory_share_suffix') .
|
||||
.row
|
||||
.col-sm-9
|
||||
input.text-h4.semibold.form-control.input-md#share-level-input(value=view.shareURL)
|
||||
.col-sm-3
|
||||
button#share-level-btn.btn.btn-md.btn-success.btn-illustrated
|
||||
span(data-i18n='sharing.copy_url') Copy URL
|
||||
|
||||
if me.get('anonymous')
|
||||
.sign-up-poke.hide
|
||||
.sign-up-blurb(data-i18n="play_level.victory_sign_up_poke") Want to save your code? Create a free account!
|
||||
|
@ -118,7 +136,7 @@ block modal-footer-content
|
|||
.next-level-buttons
|
||||
if readyToRank
|
||||
.ladder-submission-view
|
||||
else if level.get('type') === 'hero-ladder'
|
||||
else if level.isType('hero-ladder')
|
||||
button.btn.btn-illustrated.btn-primary.btn-lg.return-to-ladder-button(data-href="/play/ladder/#{level.get('slug')}#my-matches", data-dismiss="modal", data-i18n="play_level.victory_return_to_ladder") Return to Ladder
|
||||
else
|
||||
button.btn.btn-illustrated.btn-success.btn-lg.world-map-button.next-level-button.hide#continue-button(data-i18n="common.continue") Continue
|
||||
|
|
23
app/templates/play/level/modal/image-gallery-modal.jade
Normal file
23
app/templates/play/level/modal/image-gallery-modal.jade
Normal file
|
@ -0,0 +1,23 @@
|
|||
extends /templates/core/modal-base-flat
|
||||
|
||||
block modal-header-content
|
||||
h3(data-i18n="web_dev.image_gallery_title")
|
||||
span(data-i18n="web_dev.image_gallery_description")
|
||||
|
||||
block modal-body-content
|
||||
dl.dl-horizontal
|
||||
for image in view.images
|
||||
dt
|
||||
img(src=image.portraitURL)
|
||||
dd
|
||||
ul.list-unstyled
|
||||
li
|
||||
span.no-select= 'URL: '
|
||||
kbd= image.portraitURL
|
||||
br
|
||||
li
|
||||
span.no-select= '<img>: '
|
||||
kbd= '<img src="' + image.portraitURL + '">'
|
||||
|
||||
block modal-footer-content
|
||||
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="modal.close").btn.btn-primary Close
|
|
@ -38,12 +38,37 @@
|
|||
span :
|
||||
h2.text-uppercase= i18n(view.nextLevel.attributes, 'name').replace('Course: ', '')
|
||||
|
||||
div!= view.nextLevelDescription
|
||||
div.next-level-description!= view.nextLevelDescription
|
||||
|
||||
if view.shareURL
|
||||
.well.well-sm.well-parchment
|
||||
h3.text-uppercase
|
||||
if view.level.isType('game-dev')
|
||||
span(data-i18n='sharing.share_game')
|
||||
else
|
||||
span(data-i18n='sharing.share_web')
|
||||
p
|
||||
span(data-i18n='sharing.victory_course_share_prefix')
|
||||
span= ' '
|
||||
a(href=view.shareURL, target='_blank')
|
||||
if view.level.isType('game-dev')
|
||||
span(data-i18n='sharing.victory_course_share_game')
|
||||
else
|
||||
span(data-i18n='sharing.victory_course_share_web')
|
||||
span= ' '
|
||||
span(data-i18n='sharing.victory_course_share_suffix')
|
||||
.row
|
||||
.col-sm-9
|
||||
input.text-h4.semibold.form-control.input-lg#share-level-input(value=view.shareURL)
|
||||
.col-sm-3
|
||||
button#share-level-btn.btn.btn-lg.btn-success.btn-illustrated
|
||||
span(data-i18n='sharing.copy_url')
|
||||
|
||||
.row
|
||||
.col-sm-5.col-sm-offset-2
|
||||
// TODO: Add this and rest of campaign functionality
|
||||
// button#continue-btn.btn.btn-illustrated.btn-default.btn-block.btn-lg.text-uppercase View Leaderboards
|
||||
// TODO: Add rest of campaign functionality
|
||||
if view.level.get('type') === 'course-ladder'
|
||||
button#ladder-btn.btn.btn-illustrated.btn-default.btn-block.btn-lg.text-uppercase Ladder
|
||||
.col-sm-5
|
||||
if !view.nextLevel.isNew()
|
||||
button#next-level-btn.btn.btn-illustrated.btn-primary.btn-block.btn-lg.text-uppercase(data-i18n='play_level.next_level')
|
||||
|
|
|
@ -13,7 +13,7 @@ block modal-body-content
|
|||
block modal-footer-content
|
||||
if readyToRank
|
||||
.ladder-submission-view
|
||||
else if level.get('type') === 'ladder'
|
||||
else if level.isType('ladder')
|
||||
a.btn.btn-primary(href="/play/ladder/#{level.get('slug')}#my-matches", data-dismiss="modal", data-i18n="play_level.victory_return_to_ladder") Return to Ladder
|
||||
else
|
||||
a.btn.btn-primary(href="/", data-dismiss="modal", data-i18n="play_level.victory_go_home") Go Home
|
||||
|
|
38
app/templates/play/level/play-game-dev-level-view.jade
Normal file
38
app/templates/play/level/play-game-dev-level-view.jade
Normal file
|
@ -0,0 +1,38 @@
|
|||
.container-fluid
|
||||
.row
|
||||
.col-xs-9
|
||||
#canvas-wrapper
|
||||
canvas(width=924, height=589)#webgl-surface
|
||||
canvas(width=924, height=589)#normal-surface
|
||||
|
||||
.col-xs-3#info-col.style-flat
|
||||
if view.state.get('errorMessage')
|
||||
.alert.alert-danger= view.state.get('errorMessage')
|
||||
|
||||
else if view.state.get('loading')
|
||||
h1.m-y-1(data-i18n="common.loading")
|
||||
.progress
|
||||
.progress-bar(style="width: #{view.state.get('progress')}")
|
||||
|
||||
else
|
||||
h1.m-y-1 Info
|
||||
ul
|
||||
li
|
||||
b
|
||||
span(data-i18n="play_level.level")
|
||||
span= ': '
|
||||
| #{view.level.get('name')}
|
||||
|
||||
li
|
||||
b
|
||||
span(data-i18n="game_dev.creator")
|
||||
span= ': '
|
||||
| #{view.session.get('creatorName')}
|
||||
|
||||
- var playing = view.state.get('playing')
|
||||
.m-y-3
|
||||
if playing
|
||||
button#play-btn.btn.btn-lg.btn-burgandy(data-i18n="play_level.restart")
|
||||
else
|
||||
button#play-btn.btn.btn-lg.btn-navy(data-i18n="common.play")
|
||||
|
11
app/templates/play/level/play-web-dev-level-view.jade
Normal file
11
app/templates/play/level/play-web-dev-level-view.jade
Normal file
|
@ -0,0 +1,11 @@
|
|||
#web-surface-view
|
||||
|
||||
#info-bar.style-flat
|
||||
if !view.supermodel.finished()
|
||||
h1(data-i18n="common.loading")
|
||||
|
||||
else
|
||||
h1
|
||||
span(data-i18n="game_dev.creator")
|
||||
span= ': '
|
||||
| #{view.session.get('creatorName')}
|
|
@ -3,16 +3,10 @@
|
|||
.hinge.hinge-2
|
||||
.hinge.hinge-3
|
||||
|
||||
if includeSpellList
|
||||
.btn.btn-small.btn-illustrated.spell-list-button(data-i18n="[title]play_level.tome_see_all_methods", title="See all methods you can edit")
|
||||
.glyphicon.glyphicon-chevron-down
|
||||
|
||||
.thang-avatar-placeholder
|
||||
|
||||
.spell-tool-buttons
|
||||
.btn.btn-small.btn-illustrated.btn-warning.reload-code(data-i18n="[title]play_level.tome_reload_method", title="Reload original code for this method")
|
||||
.btn.btn-small.btn-illustrated.btn-warning.reload-code(data-i18n="[title]play_level.tome_reload_method")
|
||||
.glyphicon.glyphicon-repeat
|
||||
span.spl(data-i18n="play_level.reload") Reload
|
||||
span.spl(data-i18n="play_level.restart")
|
||||
|
||||
if me.level() >= 15
|
||||
.btn.btn-small.btn-illustrated.fullscreen-code(title=maximizeShortcutVerbose)
|
||||
|
@ -27,4 +21,17 @@ if includeSpellList
|
|||
.btn.btn-small.btn-illustrated.hints-button
|
||||
span(data-i18n="play_level.hints")
|
||||
|
||||
if view.options.level.isType('web-dev')
|
||||
.btn.btn-small.btn-illustrated.image-gallery-button
|
||||
span(data-i18n='web_dev.image_gallery_title')
|
||||
|
||||
if view.options.level.get('shareable')
|
||||
- var url = '/play/' + view.options.level.get('type') + '-level/' + view.options.level.get('slug') + '/' + view.options.session.id;
|
||||
- if (view.options.courseID) url += '?course=' + view.options.courseID;
|
||||
a.btn.btn-small.btn-illustrated(href=url)
|
||||
if view.options.level.isType('game-dev')
|
||||
span(data-i18n='sharing.game')
|
||||
else
|
||||
span(data-i18n='sharing.webpage')
|
||||
|
||||
.clearfix
|
|
@ -1 +0,0 @@
|
|||
h5(data-i18n="play_level.tome_select_method") Select a Method
|
|
@ -1,7 +0,0 @@
|
|||
if showTopDivider
|
||||
// Don't repeat Thang names when not changed from previous entry
|
||||
.thang-names(title=thangNames)= thangNames
|
||||
|
||||
code #{spell.name}(#{parameters})
|
||||
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
h4
|
||||
span(data-i18n="play_level.tome_select_a_thang") Select Someone for
|
||||
code #{view.spell.name}(#{(view.spell.parameters || []).join(", ")})
|
|
@ -95,7 +95,7 @@ if !selectedMethod
|
|||
else if language == 'io'
|
||||
span= (doc.ownerName == 'this' ? '' : doc.ownerName + ' ') + docName + '(' + argumentExamples.join(', ') + ')'
|
||||
|
||||
if (doc.type != 'function' && doc.type != 'snippet') || doc.name == 'now'
|
||||
if (doc.type != 'function' && doc.type != 'snippet' && doc.owner != 'HTML' && doc.owner != 'CSS') || doc.name == 'now'
|
||||
p.value
|
||||
strong
|
||||
span(data-i18n="skill_docs.current_value") Current Value
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
#spell-list-tab-entry-view
|
||||
|
||||
#spell-list-view
|
||||
#spell-top-bar-view
|
||||
|
||||
#cast-button-view
|
||||
|
||||
#spell-view
|
||||
|
||||
#spell-palette-view
|
||||
|
||||
|
||||
|
|
1
app/templates/play/level/web-surface-view.jade
Normal file
1
app/templates/play/level/web-surface-view.jade
Normal file
|
@ -0,0 +1 @@
|
|||
iframe(src="/web-dev-iframe.html")
|
|
@ -1,90 +0,0 @@
|
|||
if !ladderGame
|
||||
.form
|
||||
.form-group.checkbox
|
||||
label(for="multiplayer")
|
||||
input#multiplayer(name="multiplayer", type="checkbox", checked=multiplayer)
|
||||
span(data-i18n="multiplayer.multiplayer_toggle") Enable multiplayer
|
||||
span.help-block(data-i18n="multiplayer.multiplayer_toggle_description") Allow others to join your game.
|
||||
|
||||
hr
|
||||
|
||||
div#link-area
|
||||
p(data-i18n="multiplayer.multiplayer_link_description") Give this link to anyone to have them join you.
|
||||
|
||||
textarea.well#multiplayer-join-link(readonly=true)= joinLink
|
||||
|
||||
p
|
||||
strong(data-i18n="multiplayer.multiplayer_hint_label") Hint:
|
||||
span(data-i18n="multiplayer.multiplayer_hint") Click the link to select all, then press ⌘-C or Ctrl-C to copy the link.
|
||||
|
||||
p(data-i18n="multiplayer.multiplayer_coming_soon") More multiplayer features to come!
|
||||
|
||||
if ladderGame
|
||||
if me.get('anonymous')
|
||||
p(data-i18n="multiplayer.multiplayer_sign_in_leaderboard") Sign in or create an account and get your solution on the leaderboard.
|
||||
else if realTimeSessions && realTimeSessionsPlayers
|
||||
button#create-game-button Create Game
|
||||
|
||||
hr
|
||||
|
||||
div#created-multiplayer-session
|
||||
h3 Your Game
|
||||
if currentRealTimeSession
|
||||
div
|
||||
span(style="margin:10px")= currentRealTimeSession.get('levelID')
|
||||
span(style="margin:10px")= currentRealTimeSession.get('creatorName')
|
||||
span(style="margin:10px")= currentRealTimeSession.get('state')
|
||||
span(style="margin:10px")= currentRealTimeSession.id
|
||||
button#leave-game-button(data-item=item) Leave Game
|
||||
div
|
||||
- var players = realTimeSessionsPlayers[currentRealTimeSession.id]
|
||||
if players
|
||||
span(style="margin:10px") Players:
|
||||
- for (var i=0; i < players.length; i++) {
|
||||
span(style="margin:10px")= players.at(i).get('name')
|
||||
span(style="margin:10px")= players.at(i).get('team')
|
||||
span(style="margin:10px")= players.at(i).get('state')
|
||||
- }
|
||||
else
|
||||
span No Players?
|
||||
else
|
||||
div Click something above to create a game.
|
||||
|
||||
hr
|
||||
|
||||
div#open-games
|
||||
h3 Open Games
|
||||
//- TODO: do not let you join ones with same-team opponent
|
||||
- var noOpenGames = true
|
||||
- for (var i=0; i < realTimeSessions.length; i++) {
|
||||
if (currentRealTimeSession && realTimeSessions.at(i).id == currentRealTimeSession.id)
|
||||
- continue
|
||||
if levelID === realTimeSessions.at(i).get('levelID') && realTimeSessions.at(i).get('state') === 'creating'
|
||||
- var id = realTimeSessions.at(i).get('id')
|
||||
- var players = realTimeSessionsPlayers[id]
|
||||
if players && players.length === 1
|
||||
- noOpenGames = false
|
||||
- var creatorName = realTimeSessions.at(i).get('creatorName')
|
||||
- var creator = realTimeSessions.at(i).get('creator')
|
||||
- var state = realTimeSessions.at(i).get('state')
|
||||
- var item = realTimeSessions.at(i)
|
||||
div
|
||||
button#join-game-button(data-item=item) Join Game
|
||||
span(style="margin:10px")= levelID
|
||||
span(style="margin:10px")= creatorName
|
||||
span(style="margin:10px")= state
|
||||
span(style="margin:10px")= id
|
||||
div
|
||||
span(style="margin:10px") Players:
|
||||
span(style="margin:10px")= players.at(0).get('name')
|
||||
span(style="margin:10px")= players.at(0).get('team')
|
||||
span(style="margin:10px")= players.at(0).get('state')
|
||||
- }
|
||||
if noOpenGames
|
||||
div No games available.
|
||||
|
||||
hr
|
||||
|
||||
.ladder-submission-view
|
||||
else
|
||||
a.btn.btn-primary(href="/play/ladder/#{levelSlug}#my-matches", data-i18n="multiplayer.victory_go_ladder") Return to Ladder
|
|
@ -24,6 +24,8 @@ if view.showAds()
|
|||
#canvas-wrapper
|
||||
canvas(width=924, height=589)#webgl-surface
|
||||
canvas(width=924, height=589)#normal-surface
|
||||
|
||||
#web-surface-view
|
||||
#ascii-surface
|
||||
#canvas-left-gradient.gradient
|
||||
#canvas-top-gradient.gradient
|
||||
|
|
|
@ -9,6 +9,7 @@ Campaigns = require 'collections/Campaigns'
|
|||
Classroom = require 'models/Classroom'
|
||||
CocoCollection = require 'collections/CocoCollection'
|
||||
Course = require 'models/Course'
|
||||
Courses = require 'collections/Courses'
|
||||
LevelSessions = require 'collections/LevelSessions'
|
||||
User = require 'models/User'
|
||||
Users = require 'collections/Users'
|
||||
|
@ -152,6 +153,7 @@ module.exports = class MainAdminView extends RootView
|
|||
$('.classroom-progress-csv').prop('disabled', true)
|
||||
classCode = $('.classroom-progress-class-code').val()
|
||||
classroom = null
|
||||
courses = null
|
||||
courseLevels = []
|
||||
sessions = null
|
||||
users = null
|
||||
|
@ -161,12 +163,16 @@ module.exports = class MainAdminView extends RootView
|
|||
classroom = new Classroom({ _id: model.data._id })
|
||||
Promise.resolve(classroom.fetch())
|
||||
.then (model) =>
|
||||
courses = new Courses()
|
||||
Promise.resolve(courses.fetch())
|
||||
.then (models) =>
|
||||
for course, index in classroom.get('courses')
|
||||
for level in course.levels
|
||||
courseLevels.push
|
||||
courseIndex: index + 1
|
||||
levelID: level.original
|
||||
slug: level.slug
|
||||
courseSlug: courses.get(course._id).get('slug')
|
||||
users = new Users()
|
||||
Promise.resolve($.when(users.fetchForClassroom(classroom)...))
|
||||
.then (models) =>
|
||||
|
@ -202,12 +208,19 @@ module.exports = class MainAdminView extends RootView
|
|||
|
||||
columnLabels = "Username"
|
||||
currentLevel = 1
|
||||
courseLabelIndexes = CS: 1, GD: 0, WD: 0
|
||||
lastCourseIndex = 1
|
||||
lastCourseLabel = 'CS1'
|
||||
for level in courseLevels
|
||||
unless level.courseIndex is lastCourseIndex
|
||||
currentLevel = 1
|
||||
lastCourseIndex = level.courseIndex
|
||||
columnLabels += ",CS#{level.courseIndex}.#{currentLevel++} #{level.slug}"
|
||||
acronym = switch
|
||||
when /game-dev/.test(level.courseSlug) then 'GD'
|
||||
when /web-dev/.test(level.courseSlug) then 'WD'
|
||||
else 'CS'
|
||||
lastCourseLabel = acronym + ++courseLabelIndexes[acronym]
|
||||
columnLabels += ",#{lastCourseLabel}.#{currentLevel++} #{level.slug}"
|
||||
csvContent = "data:text/csv;charset=utf-8,#{columnLabels}\n"
|
||||
for studentRow in userPlaytimes
|
||||
csvContent += studentRow.join(',') + "\n"
|
||||
|
|
|
@ -195,7 +195,7 @@ module.exports = class ClanDetailsView extends RootView
|
|||
if level.concepts?
|
||||
for concept in level.concepts
|
||||
@conceptsProgression.push concept unless concept in @conceptsProgression
|
||||
if level.type is 'hero-ladder' and level.slug not in ['capture-their-flag']
|
||||
if level.type is 'hero-ladder' and level.slug not in ['capture-their-flag'] # Would use isType, but it's not a Level model
|
||||
@arenas.push level
|
||||
@campaignLevelProgressions.push campaignLevelProgression
|
||||
@render?()
|
||||
|
|
|
@ -496,6 +496,13 @@ module.exports = class CocoView extends Backbone.View
|
|||
playSound: (trigger, volume=1) ->
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: trigger, volume: volume
|
||||
|
||||
tryCopy: ->
|
||||
try
|
||||
document.execCommand('copy')
|
||||
catch err
|
||||
message = 'Oops, unable to copy'
|
||||
noty text: message, layout: 'topCenter', type: 'error', killer: false
|
||||
|
||||
mobileRELong = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i
|
||||
|
||||
mobileREShort = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i
|
||||
|
|
|
@ -52,7 +52,7 @@ module.exports = class CourseDetailsView extends RootView
|
|||
@supermodel.trackRequest(@classroom.fetch())
|
||||
|
||||
levelsLoaded = @supermodel.trackRequest(@levels.fetchForClassroomAndCourse(classroomID, @courseID, {
|
||||
data: { project: 'concepts,practice,type,slug,name,original,description' }
|
||||
data: { project: 'concepts,practice,type,slug,name,original,description,shareable,i18n' }
|
||||
}))
|
||||
|
||||
@supermodel.trackRequest($.when(levelsLoaded, sessionsLoaded).then(=>
|
||||
|
@ -84,9 +84,9 @@ module.exports = class CourseDetailsView extends RootView
|
|||
@levelConceptMap = {}
|
||||
for level in @levels.models
|
||||
@levelConceptMap[level.get('original')] ?= {}
|
||||
for concept in level.get('concepts')
|
||||
for concept in level.get('concepts') or []
|
||||
@levelConceptMap[level.get('original')][concept] = true
|
||||
if level.get('type') is 'course-ladder'
|
||||
if level.isType('course-ladder')
|
||||
@arenaLevel = level
|
||||
|
||||
# console.log 'onLevelSessionsSync'
|
||||
|
@ -130,7 +130,7 @@ module.exports = class CourseDetailsView extends RootView
|
|||
levelID = $(e.target).closest('.btn-play-level').data('level-id')
|
||||
level = @levels.findWhere({original: levelID})
|
||||
window.tracker?.trackEvent 'Students Class Course Play Level', category: 'Students', courseID: @courseID, courseInstanceID: @courseInstanceID, levelSlug: levelSlug, ['Mixpanel']
|
||||
if level.get('type') is 'course-ladder'
|
||||
if level.isType('course-ladder')
|
||||
viewClass = 'views/ladder/LadderView'
|
||||
viewArgs = [{supermodel: @supermodel}, levelSlug]
|
||||
route = '/play/ladder/' + levelSlug
|
||||
|
|
|
@ -23,6 +23,7 @@ CourseInstances = require 'collections/CourseInstances'
|
|||
module.exports = class TeacherClassView extends RootView
|
||||
id: 'teacher-class-view'
|
||||
template: template
|
||||
helper: helper
|
||||
|
||||
events:
|
||||
'click .nav-tabs a': 'onClickNavTabLink'
|
||||
|
@ -119,7 +120,7 @@ module.exports = class TeacherClassView extends RootView
|
|||
@supermodel.trackRequest @courseInstances.fetchForClassroom(classroomID)
|
||||
|
||||
@levels = new Levels()
|
||||
@supermodel.trackRequest @levels.fetchForClassroom(classroomID, {data: {project: 'original,concepts,practice'}})
|
||||
@supermodel.trackRequest @levels.fetchForClassroom(classroomID, {data: {project: 'original,concepts,practice,shareable,i18n'}})
|
||||
|
||||
@attachMediatorEvents()
|
||||
window.tracker?.trackEvent 'Teachers Class Loaded', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
||||
|
@ -177,7 +178,7 @@ module.exports = class TeacherClassView extends RootView
|
|||
|
||||
afterRender: ->
|
||||
super(arguments...)
|
||||
$('.progress-dot').each (i, el) ->
|
||||
$('.progress-dot, .btn-view-project-level').each (i, el) ->
|
||||
dot = $(el)
|
||||
dot.tooltip({
|
||||
html: true
|
||||
|
@ -227,13 +228,6 @@ module.exports = class TeacherClassView extends RootView
|
|||
@$('#join-url-input').val(@state.get('joinURL')).select()
|
||||
@tryCopy()
|
||||
|
||||
tryCopy: ->
|
||||
try
|
||||
document.execCommand('copy')
|
||||
catch err
|
||||
message = 'Oops, unable to copy'
|
||||
noty text: message, layout: 'topCenter', type: 'error', killer: false
|
||||
|
||||
onClickUnarchive: ->
|
||||
window.tracker?.trackEvent 'Teachers Class Unarchive', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
||||
@classroom.save { archived: false }
|
||||
|
@ -328,9 +322,11 @@ module.exports = class TeacherClassView extends RootView
|
|||
window.tracker?.trackEvent 'Teachers Class Export CSV', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
||||
courseLabels = ""
|
||||
courseOrder = []
|
||||
for course, index in @classroom.get('courses')
|
||||
courseLabels += "CS#{index + 1} Playtime,"
|
||||
courseOrder.push(course._id)
|
||||
courses = (@courses.get(c._id) for c in @classroom.get('courses'))
|
||||
courseLabelsArray = helper.courseLabelsArray courses
|
||||
for course, index in courses
|
||||
courseLabels += "#{courseLabelsArray[index]} Playtime,"
|
||||
courseOrder.push(course.id)
|
||||
csvContent = "data:text/csv;charset=utf-8,Username,Email,Total Playtime,#{courseLabels}Concepts\n"
|
||||
levelCourseMap = {}
|
||||
for trimCourse in @classroom.get('courses')
|
||||
|
@ -396,6 +392,7 @@ module.exports = class TeacherClassView extends RootView
|
|||
not @students.get(userID).isEnrolled()
|
||||
assigningToNobody = selectedIDs.length is 0
|
||||
@state.set errors: { assigningToNobody, assigningToUnenrolled }
|
||||
return if assigningToNobody
|
||||
@assignCourse courseID, members
|
||||
window.tracker?.trackEvent 'Teachers Class Students Assign Selected', category: 'Teachers', classroomID: @classroom.id, courseID: courseID, ['Mixpanel']
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ helper = require 'lib/coursesHelper'
|
|||
module.exports = class TeacherClassesView extends RootView
|
||||
id: 'teacher-classes-view'
|
||||
template: template
|
||||
helper: helper
|
||||
|
||||
events:
|
||||
'click .edit-classroom': 'onClickEditClassroom'
|
||||
|
|
|
@ -46,7 +46,7 @@ module.exports = class ThangComponentConfigView extends CocoView
|
|||
schema.default ?= {}
|
||||
_.merge schema.default, @additionalDefaults if @additionalDefaults
|
||||
|
||||
if @level?.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev']
|
||||
if @level?.isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev')
|
||||
schema.required = []
|
||||
treemaOptions =
|
||||
supermodel: @supermodel
|
||||
|
|
|
@ -37,6 +37,7 @@ require 'vendor/aether-python'
|
|||
require 'vendor/aether-coffeescript'
|
||||
require 'vendor/aether-lua'
|
||||
require 'vendor/aether-java'
|
||||
require 'vendor/aether-html'
|
||||
|
||||
module.exports = class LevelEditView extends RootView
|
||||
id: 'editor-level-view'
|
||||
|
|
|
@ -41,7 +41,7 @@ module.exports = class LevelThangEditView extends CocoView
|
|||
level: @level
|
||||
world: @world
|
||||
|
||||
if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev'] then options.thangType = thangType
|
||||
if @level.isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev') then options.thangType = thangType
|
||||
|
||||
@thangComponentEditView = new ThangComponentsEditView options
|
||||
@listenTo @thangComponentEditView, 'components-changed', @onComponentsChanged
|
||||
|
|
|
@ -636,14 +636,14 @@ module.exports = class ThangsTabView extends CocoView
|
|||
if batchInsert
|
||||
if thangType.get('name') is 'Hero Placeholder'
|
||||
thangID = 'Hero Placeholder'
|
||||
return if not (@level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev']) or @getThangByID(thangID)
|
||||
return if not @level.isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev') or @getThangByID(thangID)
|
||||
else
|
||||
thangID = "Random #{thangType.get('name')} #{@thangsBatch.length}"
|
||||
else
|
||||
thangID = Thang.nextID(thangType.get('name'), @world) until thangID and not @getThangByID(thangID)
|
||||
if @cloneSourceThang
|
||||
components = _.cloneDeep @getThangByID(@cloneSourceThang.id).components
|
||||
else if @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev']
|
||||
else if @level.isType('hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder', 'game-dev', 'web-dev')
|
||||
components = [] # Load them all from default ThangType Components
|
||||
else
|
||||
components = _.cloneDeep thangType.get('components') ? []
|
||||
|
|
|
@ -78,7 +78,7 @@ module.exports = class VerifierTest extends CocoClass
|
|||
@listenToOnce @god, 'infinite-loop', @fail
|
||||
@listenToOnce @god, 'user-code-problem', @onUserCodeProblem
|
||||
@listenToOnce @god, 'goals-calculated', @processSingleGameResults
|
||||
@god.createWorld @generateSpellsObject()
|
||||
@god.createWorld @session.generateSpellsObject()
|
||||
@updateCallback? state: 'running'
|
||||
|
||||
processSingleGameResults: (e) ->
|
||||
|
@ -118,18 +118,6 @@ module.exports = class VerifierTest extends CocoClass
|
|||
@updateCallback? state: @state
|
||||
@scheduleCleanup()
|
||||
|
||||
generateSpellsObject: ->
|
||||
aetherOptions = createAetherOptions functionName: 'plan', codeLanguage: @session.get('codeLanguage')
|
||||
spellThang = aether: new Aether aetherOptions
|
||||
spells = "hero-placeholder/plan": thangs: {'Hero Placeholder': spellThang}, name: 'plan'
|
||||
source = @session.get('code')['hero-placeholder'].plan
|
||||
try
|
||||
spellThang.aether.transpile source
|
||||
catch e
|
||||
console.log "Couldn't transpile!\n#{source}\n", e
|
||||
spellThang.aether.transpile ''
|
||||
spells
|
||||
|
||||
scheduleCleanup: ->
|
||||
setTimeout @cleanup, 100
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ module.exports = class VerifierView extends RootView
|
|||
for campaign in @campaigns.models when campaign.get('type') in ['course', 'hero'] and campaign.get('slug') isnt 'picoctf'
|
||||
@levelsByCampaign[campaign.get('slug')] ?= {levels: [], checked: true}
|
||||
campaignInfo = @levelsByCampaign[campaign.get('slug')]
|
||||
for levelID, level of campaign.get('levels') when level.type not in ['hero-ladder', 'course-ladder', 'game-dev']
|
||||
for levelID, level of campaign.get('levels') when level.type not in ['hero-ladder', 'course-ladder', 'game-dev', 'web-dev'] # Would use isType, but it's not a Level model
|
||||
campaignInfo.levels.push level.slug
|
||||
|
||||
filterCodeLanguages: ->
|
||||
|
|
|
@ -26,8 +26,8 @@ module.exports = class LadderPlayModal extends ModalView
|
|||
|
||||
initialize: (options, @level, @session, @team) ->
|
||||
@otherTeam = if @team is 'ogres' then 'humans' else 'ogres'
|
||||
@startLoadingChallengersMaybe()
|
||||
@wizardType = ThangType.loadUniversalWizard()
|
||||
@startLoadingChallengersMaybe()
|
||||
@levelID = @level.get('slug') or @level.id
|
||||
@language = @session?.get('codeLanguage') ? me.get('aceConfig')?.language ? 'python'
|
||||
@languages = [
|
||||
|
|
|
@ -94,52 +94,6 @@ heroArenas = [
|
|||
}
|
||||
]
|
||||
|
||||
oldArenas = [
|
||||
{
|
||||
name: 'Criss-Cross'
|
||||
difficulty: 5
|
||||
id: 'criss-cross'
|
||||
image: '/file/db/level/5391f3d519dc22b8082159b2/banner2.png'
|
||||
description: 'Participate in a bidding war with opponents to reach the other side!'
|
||||
}
|
||||
{
|
||||
name: 'Greed'
|
||||
difficulty: 4
|
||||
id: 'greed'
|
||||
image: '/file/db/level/53558b5a9914f5a90d7ccddb/greed_banner.jpg'
|
||||
description: 'Liked Dungeon Arena and Gold Rush? Put them together in this economic arena!'
|
||||
}
|
||||
{
|
||||
name: 'Sky Span (Testing)'
|
||||
difficulty: 3
|
||||
id: 'sky-span'
|
||||
image: '/file/db/level/53c80fce0ddbef000084c667/sky-Span-banner.jpg'
|
||||
description: 'Preview version of an upgraded Dungeon Arena. Help us with hero balance before release!'
|
||||
}
|
||||
{
|
||||
name: 'Dungeon Arena'
|
||||
difficulty: 3
|
||||
id: 'dungeon-arena'
|
||||
image: '/file/db/level/53173f76c269d400000543c2/Level%20Banner%20Dungeon%20Arena.jpg'
|
||||
description: 'Play head-to-head against fellow Wizards in a dungeon melee!'
|
||||
}
|
||||
{
|
||||
name: 'Gold Rush'
|
||||
difficulty: 3
|
||||
id: 'gold-rush'
|
||||
image: '/file/db/level/533353722a61b7ca6832840c/Gold-Rush.png'
|
||||
description: 'Prove you are better at collecting gold than your opponent!'
|
||||
}
|
||||
{
|
||||
name: 'Brawlwood'
|
||||
difficulty: 4
|
||||
id: 'brawlwood'
|
||||
image: '/file/db/level/52d97ecd32362bc86e004e87/Level%20Banner%20Brawlwood.jpg'
|
||||
description: 'Combat the armies of other Wizards in a strategic forest arena! (Fast computer required.)'
|
||||
}
|
||||
]
|
||||
|
||||
campaigns = [
|
||||
{id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: heroArenas}
|
||||
#{id: 'old_multiplayer', name: '(Deprecated) Old Multiplayer Arenas', description: 'Relics of a more civilized age. No simulations are run for these older, hero-less multiplayer arenas.', levels: oldArenas}
|
||||
]
|
||||
|
|
|
@ -24,7 +24,7 @@ module.exports = class SimulateTabView extends CocoView
|
|||
onLoaded: ->
|
||||
super()
|
||||
@render()
|
||||
if (document.location.hash is '#simulate' or @options.level.get('type') is 'course-ladder') and not @simulator
|
||||
if (document.location.hash is '#simulate' or @options.level.isType('course-ladder')) and not @simulator
|
||||
@startSimulating()
|
||||
|
||||
afterRender: ->
|
||||
|
|
|
@ -397,8 +397,9 @@ module.exports = class CampaignView extends RootView
|
|||
@particleMan.removeEmitters()
|
||||
@particleMan.attach @$el.find('.map')
|
||||
for level in @campaign.renderedLevels ? {}
|
||||
particleKey = ['level', @terrain.replace('-branching-test', '')]
|
||||
particleKey.push level.type if level.type and not (level.type in ['hero', 'course'])
|
||||
terrain = @terrain.replace('-branching-test', '').replace(/(game|web)-dev-\d/, 'forest')
|
||||
particleKey = ['level', terrain]
|
||||
particleKey.push level.type if level.type and not (level.type in ['hero', 'course']) # Would use isType, but it's not a Level model
|
||||
particleKey.push 'replayable' if level.replayable
|
||||
particleKey.push 'premium' if level.requiresSubscription
|
||||
particleKey.push 'gate' if level.slug in ['kithgard-gates', 'siege-of-stonehold', 'clash-of-clones', 'summits-gate']
|
||||
|
@ -532,7 +533,7 @@ module.exports = class CampaignView extends RootView
|
|||
levelElement = $(e.target).parents('.level-info-container')
|
||||
levelSlug = levelElement.data('level-slug')
|
||||
level = _.find _.values(@campaign.get('levels')), slug: levelSlug
|
||||
if level.type in ['hero-ladder', 'course-ladder']
|
||||
if level.type in ['hero-ladder', 'course-ladder'] # Would use isType, but it's not a Level model
|
||||
Backbone.Mediator.publish 'router:navigate', route: "/play/ladder/#{levelSlug}", viewClass: 'views/ladder/LadderView', viewArgs: [{supermodel: @supermodel}, levelSlug]
|
||||
else
|
||||
@showLeaderboard levelSlug
|
||||
|
|
|
@ -144,9 +144,6 @@ module.exports = class SpectateLevelView extends RootView
|
|||
if c then myCode[thang][spell] = c else delete myCode[thang][spell]
|
||||
|
||||
@session.set('code', myCode)
|
||||
if @session.get('multiplayer') and @otherSession?
|
||||
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
|
||||
@session.set 'multiplayer', false
|
||||
|
||||
onLevelStarted: (e) ->
|
||||
go = =>
|
||||
|
@ -181,7 +178,7 @@ module.exports = class SpectateLevelView extends RootView
|
|||
|
||||
@insertSubView new GoldView {}
|
||||
@insertSubView new HUDView {level: @level}
|
||||
@insertSubView new DuelStatsView level: @level, session: @session, otherSession: @otherSession, supermodel: @supermodel, thangs: @world.thangs if @level.get('type') in ['hero-ladder', 'course-ladder']
|
||||
@insertSubView new DuelStatsView level: @level, session: @session, otherSession: @otherSession, supermodel: @supermodel, thangs: @world.thangs if @level.isType('hero-ladder', 'course-ladder')
|
||||
@insertSubView @controlBar = new ControlBarView {worldName: utils.i18n(@level.attributes, 'name'), session: @session, level: @level, supermodel: @supermodel, spectateGame: true}
|
||||
|
||||
# callbacks
|
||||
|
|
|
@ -7,17 +7,13 @@ Classroom = require 'models/Classroom'
|
|||
Course = require 'models/Course'
|
||||
CourseInstance = require 'models/CourseInstance'
|
||||
GameMenuModal = require 'views/play/menu/GameMenuModal'
|
||||
RealTimeModel = require 'models/RealTimeModel'
|
||||
RealTimeCollection = require 'collections/RealTimeCollection'
|
||||
LevelSetupManager = require 'lib/LevelSetupManager'
|
||||
GameMenuModal = require 'views/play/menu/GameMenuModal'
|
||||
|
||||
module.exports = class ControlBarView extends CocoView
|
||||
id: 'control-bar-view'
|
||||
template: template
|
||||
|
||||
subscriptions:
|
||||
'bus:player-states-changed': 'onPlayerStatesChanged'
|
||||
'level:disable-controls': 'onDisableControls'
|
||||
'level:enable-controls': 'onEnableControls'
|
||||
'ipad:memory-warning': 'onIPadMemoryWarning'
|
||||
|
@ -28,7 +24,6 @@ module.exports = class ControlBarView extends CocoView
|
|||
'click': -> Backbone.Mediator.publish 'tome:focus-editor', {}
|
||||
'click .levels-link-area': 'onClickHome'
|
||||
'click .home a': 'onClickHome'
|
||||
'click .multiplayer-area': 'onClickMultiplayer'
|
||||
'click #control-bar-sign-up-button': 'onClickSignupButton'
|
||||
|
||||
constructor: (options) ->
|
||||
|
@ -45,7 +40,7 @@ module.exports = class ControlBarView extends CocoView
|
|||
@observing = options.session.get('creator') isnt me.id
|
||||
|
||||
@levelNumber = ''
|
||||
if @level.get('type') is 'course' and @level.get('campaignIndex')?
|
||||
if @level.isType('course', 'game-dev', 'web-dev') and @level.get('campaignIndex')?
|
||||
@levelNumber = @level.get('campaignIndex') + 1
|
||||
if @courseInstanceID
|
||||
@courseInstance = new CourseInstance(_id: @courseInstanceID)
|
||||
|
@ -64,9 +59,6 @@ module.exports = class ControlBarView extends CocoView
|
|||
@supermodel.trackRequest(@campaign.fetch())
|
||||
)
|
||||
super options
|
||||
if @level.get('type') in ['hero-ladder', 'course-ladder'] and me.isAdmin()
|
||||
@isMultiplayerLevel = true
|
||||
@multiplayerStatusManager = new MultiplayerStatusManager @levelID, @onMultiplayerStateChanged
|
||||
if @level.get 'replayable'
|
||||
@listenTo @session, 'change-difficulty', @onSessionDifficultyChanged
|
||||
|
||||
|
@ -79,25 +71,10 @@ module.exports = class ControlBarView extends CocoView
|
|||
|
||||
setBus: (@bus) ->
|
||||
|
||||
onPlayerStatesChanged: (e) ->
|
||||
# TODO: this doesn't fire any more. Replacement?
|
||||
return unless @bus is e.bus
|
||||
numPlayers = _.keys(e.players).length
|
||||
return if numPlayers is @numPlayers
|
||||
@numPlayers = numPlayers
|
||||
text = 'Multiplayer'
|
||||
text += " (#{numPlayers})" if numPlayers > 1
|
||||
$('#multiplayer-button', @$el).text(text)
|
||||
|
||||
onMultiplayerStateChanged: => @render?()
|
||||
|
||||
getRenderData: (c={}) ->
|
||||
super c
|
||||
c.worldName = @worldName
|
||||
c.multiplayerEnabled = @session.get('multiplayer')
|
||||
c.ladderGame = @level.get('type') in ['ladder', 'hero-ladder', 'course-ladder']
|
||||
if c.isMultiplayerLevel = @isMultiplayerLevel
|
||||
c.multiplayerStatus = @multiplayerStatusManager?.status
|
||||
c.ladderGame = @level.isType('ladder', 'hero-ladder', 'course-ladder')
|
||||
if @level.get 'replayable'
|
||||
c.levelDifficulty = @session.get('state')?.difficulty ? 0
|
||||
if @observing
|
||||
|
@ -110,23 +87,17 @@ module.exports = class ControlBarView extends CocoView
|
|||
if me.isSessionless()
|
||||
@homeLink = "/teachers/courses"
|
||||
@homeViewClass = "views/courses/TeacherCoursesView"
|
||||
else if @level.get('type', true) in ['ladder', 'ladder-tutorial', 'hero-ladder', 'course-ladder']
|
||||
else if @level.isType('ladder', 'ladder-tutorial', 'hero-ladder', 'course-ladder')
|
||||
levelID = @level.get('slug')?.replace(/\-tutorial$/, '') or @level.id
|
||||
@homeLink = '/play/ladder/' + levelID
|
||||
@homeViewClass = 'views/ladder/LadderView'
|
||||
@homeViewArgs.push levelID
|
||||
if leagueID = @getQueryVariable 'league'
|
||||
leagueType = if @level.get('type') is 'course-ladder' then 'course' else 'clan'
|
||||
leagueType = if @level.isType('course-ladder') then 'course' else 'clan'
|
||||
@homeViewArgs.push leagueType
|
||||
@homeViewArgs.push leagueID
|
||||
@homeLink += "/#{leagueType}/#{leagueID}"
|
||||
else if @level.get('type', true) in ['hero', 'hero-coop'] or window.serverConfig.picoCTF
|
||||
@homeLink = '/play'
|
||||
@homeViewClass = 'views/play/CampaignView'
|
||||
campaign = @level.get 'campaign'
|
||||
@homeLink += '/' + campaign
|
||||
@homeViewArgs.push campaign
|
||||
else if @level.get('type', true) in ['course']
|
||||
else if @level.isType('course') or @courseID
|
||||
@homeLink = '/courses'
|
||||
@homeViewClass = 'views/courses/CoursesView'
|
||||
if @courseID
|
||||
|
@ -136,7 +107,12 @@ module.exports = class ControlBarView extends CocoView
|
|||
if @courseInstanceID
|
||||
@homeLink += "/#{@courseInstanceID}"
|
||||
@homeViewArgs.push @courseInstanceID
|
||||
#else if @level.get('type', true) is 'game-dev' # TODO
|
||||
else if @level.isType('hero', 'hero-coop', 'game-dev', 'web-dev') or window.serverConfig.picoCTF
|
||||
@homeLink = '/play'
|
||||
@homeViewClass = 'views/play/CampaignView'
|
||||
campaign = @level.get 'campaign'
|
||||
@homeLink += '/' + campaign
|
||||
@homeViewArgs.push campaign
|
||||
else
|
||||
@homeLink = '/'
|
||||
@homeViewClass = 'views/HomeView'
|
||||
|
@ -153,16 +129,13 @@ module.exports = class ControlBarView extends CocoView
|
|||
@setupManager.open()
|
||||
|
||||
onClickHome: (e) ->
|
||||
if @level.get('type', true) in ['course']
|
||||
if @level.isType('course')
|
||||
category = if me.isTeacher() then 'Teachers' else 'Students'
|
||||
window.tracker?.trackEvent 'Play Level Back To Levels', category: category, levelSlug: @levelSlug, ['Mixpanel']
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
Backbone.Mediator.publish 'router:navigate', route: @homeLink, viewClass: @homeViewClass, viewArgs: @homeViewArgs
|
||||
|
||||
onClickMultiplayer: (e) ->
|
||||
@showGameMenuModal e, 'multiplayer'
|
||||
|
||||
onClickSignupButton: (e) ->
|
||||
window.tracker?.trackEvent 'Started Signup', category: 'Play Level', label: 'Control Bar', level: @levelID
|
||||
|
||||
|
@ -183,62 +156,4 @@ module.exports = class ControlBarView extends CocoView
|
|||
|
||||
destroy: ->
|
||||
@setupManager?.destroy()
|
||||
@multiplayerStatusManager?.destroy()
|
||||
super()
|
||||
|
||||
# MultiplayerStatusManager ######################################################
|
||||
#
|
||||
# Manages the multiplayer status, and calls @statusChangedCallback when it changes.
|
||||
#
|
||||
# It monitors these:
|
||||
# Real-time multiplayer players
|
||||
# Internal multiplayer status
|
||||
#
|
||||
# Real-time state variables:
|
||||
# @playersCollection - Real-time multiplayer players
|
||||
#
|
||||
# TODO: Not currently using player counts. Should remove if we keep simple design.
|
||||
#
|
||||
class MultiplayerStatusManager
|
||||
|
||||
constructor: (@levelID, @statusChangedCallback) ->
|
||||
@status = ''
|
||||
# @players = {}
|
||||
# @playersCollection = new RealTimeCollection('multiplayer_players/' + @levelID)
|
||||
# @playersCollection.on 'add', @onPlayerAdded
|
||||
# @playersCollection.each (player) => @onPlayerAdded player
|
||||
Backbone.Mediator.subscribe 'real-time-multiplayer:player-status', @onMultiplayerPlayerStatus
|
||||
|
||||
destroy: ->
|
||||
Backbone.Mediator.unsubscribe 'real-time-multiplayer:player-status', @onMultiplayerPlayerStatus
|
||||
# @playersCollection?.off 'add', @onPlayerAdded
|
||||
# player.off 'change', @onPlayerChanged for id, player of @players
|
||||
|
||||
onMultiplayerPlayerStatus: (e) =>
|
||||
@status = e.status
|
||||
@statusChangedCallback()
|
||||
|
||||
# onPlayerAdded: (player) =>
|
||||
# unless player.id is me.id
|
||||
# @players[player.id] = new RealTimeModel('multiplayer_players/' + @levelID + '/' + player.id)
|
||||
# @players[player.id].on 'change', @onPlayerChanged
|
||||
# @countPlayers player
|
||||
#
|
||||
# onPlayerChanged: (player) =>
|
||||
# @countPlayers player
|
||||
#
|
||||
# countPlayers: (changedPlayer) =>
|
||||
# # TODO: save this stale hearbeat threshold setting somewhere
|
||||
# staleHeartbeat = new Date()
|
||||
# staleHeartbeat.setMinutes staleHeartbeat.getMinutes() - 3
|
||||
# @playerCount = 0
|
||||
# @playersCollectionAvailable = 0
|
||||
# @playersCollectionUnavailable = 0
|
||||
# @playersCollection.each (player) =>
|
||||
# # Assume changedPlayer is fresher than entry in @playersCollection collection
|
||||
# player = changedPlayer if changedPlayer? and player.id is changedPlayer.id
|
||||
# unless staleHeartbeat >= new Date(player.get('heartbeat'))
|
||||
# @playerCount++
|
||||
# @playersCollectionAvailable++ if player.get('state') is 'available'
|
||||
# @playersCollectionUnavailable++ if player.get('state') is 'unavailable'
|
||||
# @statusChangedCallback()
|
||||
|
|
|
@ -18,6 +18,7 @@ module.exports = class LevelChatView extends CocoView
|
|||
constructor: (options) ->
|
||||
@levelID = options.levelID
|
||||
@session = options.session
|
||||
# TODO: we took out session.multiplayer, so this will not fire. If we want to resurrect it, we'll of course need a new way of activating chat.
|
||||
@listenTo(@session, 'change:multiplayer', @updateMultiplayerVisibility)
|
||||
@sessionID = options.sessionID
|
||||
@bus = LevelBus.get(@levelID, @sessionID)
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
CocoView = require 'views/core/CocoView'
|
||||
template = require 'templates/play/level/level-flags-view'
|
||||
{me} = require 'core/auth'
|
||||
RealTimeCollection = require 'collections/RealTimeCollection'
|
||||
|
||||
multiplayerFlagDelay = 0.5 # Long, static second delay for now; should be more than enough.
|
||||
|
||||
module.exports = class LevelFlagsView extends CocoView
|
||||
id: 'level-flags-view'
|
||||
|
@ -17,7 +14,6 @@ module.exports = class LevelFlagsView extends CocoView
|
|||
'god:new-world-created': 'onNewWorld'
|
||||
'god:streaming-world-updated': 'onNewWorld'
|
||||
'surface:remove-flag': 'onRemoveFlag'
|
||||
'real-time-multiplayer:joined-game': 'onJoinedMultiplayerGame'
|
||||
|
||||
events:
|
||||
'click .green-flag': -> @onFlagSelected color: 'green', source: 'button'
|
||||
|
@ -60,9 +56,8 @@ module.exports = class LevelFlagsView extends CocoView
|
|||
return unless @flagColor and @realTime
|
||||
@playSound 'menu-button-click' # TODO: different flag placement sound?
|
||||
pos = x: e.worldPos.x, y: e.worldPos.y
|
||||
delay = if @realTimeFlags then multiplayerFlagDelay else 0
|
||||
now = @world.dt * @world.frames.length
|
||||
flag = player: me.id, team: me.team, color: @flagColor, pos: pos, time: now + delay, active: true, source: 'click'
|
||||
flag = player: me.id, team: me.team, color: @flagColor, pos: pos, time: now, active: true, source: 'click'
|
||||
@flags[@flagColor] = flag
|
||||
@flagHistory.push flag
|
||||
@realTimeFlags?.create flag
|
||||
|
@ -75,9 +70,8 @@ module.exports = class LevelFlagsView extends CocoView
|
|||
|
||||
onRemoveFlag: (e) ->
|
||||
delete @flags[e.color]
|
||||
delay = if @realTimeFlags then multiplayerFlagDelay else 0
|
||||
now = @world.dt * @world.frames.length
|
||||
flag = player: me.id, team: me.team, color: e.color, time: now + delay, active: false, source: 'click'
|
||||
flag = player: me.id, team: me.team, color: e.color, time: now, active: false, source: 'click'
|
||||
@flagHistory.push flag
|
||||
Backbone.Mediator.publish 'level:flag-updated', flag
|
||||
#console.log e.color, 'deleted at time', flag.time
|
||||
|
@ -85,31 +79,3 @@ module.exports = class LevelFlagsView extends CocoView
|
|||
onNewWorld: (event) ->
|
||||
return unless event.world.name is @world.name
|
||||
@world = @options.world = event.world
|
||||
|
||||
onJoinedMultiplayerGame: (e) ->
|
||||
@realTimeFlags = new RealTimeCollection("multiplayer_level_sessions/#{@levelID}/#{e.realTimeSessionID}/flagHistory")
|
||||
@realTimeFlags.on 'add', @onRealTimeMultiplayerFlagAdded
|
||||
@realTimeFlags.on 'remove', @onRealTimeMultiplayerFlagRemoved
|
||||
|
||||
onLeftMultiplayerGame: (e) ->
|
||||
if @realTimeFlags
|
||||
@realTimeFlags.off 'add', @onRealTimeMultiplayerFlagAdded
|
||||
@realTimeFlags.off 'remove', @onRealTimeMultiplayerFlagRemoved
|
||||
@realTimeFlags = null
|
||||
|
||||
onRealTimeMultiplayerFlagAdded: (e) =>
|
||||
if e.get('player') != me.id
|
||||
# TODO: what is @flags used for?
|
||||
# Build local flag from Backbone.Model flag
|
||||
flag =
|
||||
player: e.get('player')
|
||||
team: e.get('team')
|
||||
color: e.get('color')
|
||||
pos: e.get('pos')
|
||||
time: e.get('time')
|
||||
active: e.get('active')
|
||||
#source: 'click'? e.get('source')? nothing?
|
||||
@flagHistory.push flag
|
||||
Backbone.Mediator.publish 'level:flag-updated', flag
|
||||
|
||||
onRealTimeMultiplayerFlagRemoved: (e) =>
|
||||
|
|
|
@ -49,7 +49,7 @@ module.exports = class LevelGoalsView extends CocoView
|
|||
goals = []
|
||||
for goal in e.goals
|
||||
state = e.goalStates[goal.id]
|
||||
continue if goal.optional and @level.get('type', true) is 'course' and state.status isnt 'success'
|
||||
continue if goal.optional and @level.isType('course') and state.status isnt 'success'
|
||||
if goal.hiddenGoal
|
||||
continue if goal.optional and state.status isnt 'success'
|
||||
continue if not goal.optional and state.status isnt 'failure'
|
||||
|
|
|
@ -100,7 +100,7 @@ module.exports = class LevelHUDView extends CocoView
|
|||
@stage?.stopTalking()
|
||||
|
||||
createProperties: ->
|
||||
if @options.level.get('type') in ['game-dev']
|
||||
if @options.level.isType('game-dev')
|
||||
name = 'Game' # TODO: we don't need the HUD at all
|
||||
else if @thang.id in ['Hero Placeholder', 'Hero Placeholder 1']
|
||||
name = @thangType?.getHeroShortName() or 'Hero'
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue