mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 17:45:40 -05:00
Merge branch 'game-dev-levels'
This commit is contained in:
commit
b1277dc95f
139 changed files with 2400 additions and 2008 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) {
|
var ensureLanguageImported = function(language) {
|
||||||
if (languagesImported[language]) return;
|
if (languagesImported[language]) return;
|
||||||
|
if (language === 'html') return;
|
||||||
importScripts("/javascripts/app/vendor/aether-" + language + ".js");
|
importScripts("/javascripts/app/vendor/aether-" + language + ".js");
|
||||||
languagesImported[language] = true;
|
languagesImported[language] = true;
|
||||||
};
|
};
|
||||||
|
|
|
@ -80,7 +80,7 @@ var myImportScripts = importScripts;
|
||||||
var languagesImported = {};
|
var languagesImported = {};
|
||||||
var ensureLanguageImported = function(language) {
|
var ensureLanguageImported = function(language) {
|
||||||
if (languagesImported[language]) return;
|
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");
|
myImportScripts("/javascripts/app/vendor/aether-" + language + ".js");
|
||||||
languagesImported[language] = true;
|
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
|
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||||
colorEnd: hsl 0.7, 0.75, 0.3
|
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'],
|
particleKinds['level-dungeon-premium-item'] = ext particleKinds['level-dungeon-gate'],
|
||||||
emitter:
|
emitter:
|
||||||
particleCount: 2000
|
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
|
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||||
colorEnd: hsl 0.7, 0.75, 0.3
|
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'],
|
particleKinds['level-forest-premium-item'] = ext particleKinds['level-forest-gate'],
|
||||||
emitter:
|
emitter:
|
||||||
particleCount: 2000
|
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
|
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||||
colorEnd: hsl 0.7, 0.75, 0.3
|
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'],
|
particleKinds['level-mountain-premium-hero'] = ext particleKinds['level-mountain-premium'],
|
||||||
emitter:
|
emitter:
|
||||||
particleCount: 200
|
particleCount: 200
|
||||||
|
@ -395,6 +413,12 @@ particleKinds['level-mountain-game-dev'] = particleKinds['level-mountain-game-de
|
||||||
colorMiddle: hsl 0.7, 0.75, 0.5
|
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||||
colorEnd: hsl 0.7, 0.75, 0.3
|
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'],
|
particleKinds['level-glacier-premium-hero'] = ext particleKinds['level-glacier-premium'],
|
||||||
emitter:
|
emitter:
|
||||||
particleCount: 200
|
particleCount: 200
|
||||||
|
@ -435,6 +459,12 @@ particleKinds['level-glacier-game-dev'] = particleKinds['level-glacier-game-dev-
|
||||||
colorMiddle: hsl 0.7, 0.75, 0.5
|
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||||
colorEnd: hsl 0.7, 0.75, 0.3
|
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'],
|
particleKinds['level-volcano-premium-hero'] = ext particleKinds['level-volcano-premium'],
|
||||||
emitter:
|
emitter:
|
||||||
particleCount: 200
|
particleCount: 200
|
||||||
|
@ -474,3 +504,9 @@ particleKinds['level-volcano-game-dev'] = particleKinds['level-volcano-game-dev-
|
||||||
colorStart: hsl 0.7, 0.75, 0.7
|
colorStart: hsl 0.7, 0.75, 0.7
|
||||||
colorMiddle: hsl 0.7, 0.75, 0.5
|
colorMiddle: hsl 0.7, 0.75, 0.5
|
||||||
colorEnd: hsl 0.7, 0.75, 0.3
|
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')
|
'legal': go('LegalView')
|
||||||
|
|
||||||
'multiplayer': go('MultiplayerView')
|
|
||||||
|
|
||||||
'play(/)': go('play/CampaignView') # extra slash is to get Facebook app to work
|
'play(/)': go('play/CampaignView') # extra slash is to get Facebook app to work
|
||||||
'play/ladder/:levelID/:leagueType/:leagueID': go('ladder/LadderView')
|
'play/ladder/:levelID/:leagueType/:leagueID': go('ladder/LadderView')
|
||||||
'play/ladder/:levelID': go('ladder/LadderView')
|
'play/ladder/:levelID': go('ladder/LadderView')
|
||||||
'play/ladder': go('ladder/MainLadderView')
|
'play/ladder': go('ladder/MainLadderView')
|
||||||
'play/level/:levelID': go('play/level/PlayLevelView')
|
'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/spectate/:levelID': go('play/SpectateView')
|
||||||
'play/:map': go('play/CampaignView')
|
'play/:map': go('play/CampaignView')
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
@listenToOnce application.moduleLoader, 'load-complete', ->
|
@listenToOnce application.moduleLoader, 'load-complete', ->
|
||||||
@routeDirectly(path, args, options)
|
@routeDirectly(path, args, options)
|
||||||
return
|
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 = new ViewClass(options, args...) # options, then any path fragment args
|
||||||
view.render()
|
view.render()
|
||||||
@openView(view)
|
@openView(view)
|
||||||
|
|
|
@ -8,7 +8,6 @@ channelSchemas =
|
||||||
'errors': require 'schemas/subscriptions/errors'
|
'errors': require 'schemas/subscriptions/errors'
|
||||||
'ipad': require 'schemas/subscriptions/ipad'
|
'ipad': require 'schemas/subscriptions/ipad'
|
||||||
'misc': require 'schemas/subscriptions/misc'
|
'misc': require 'schemas/subscriptions/misc'
|
||||||
'multiplayer': require 'schemas/subscriptions/multiplayer'
|
|
||||||
'play': require 'schemas/subscriptions/play'
|
'play': require 'schemas/subscriptions/play'
|
||||||
'surface': require 'schemas/subscriptions/surface'
|
'surface': require 'schemas/subscriptions/surface'
|
||||||
'tome': require 'schemas/subscriptions/tome'
|
'tome': require 'schemas/subscriptions/tome'
|
||||||
|
@ -165,5 +164,5 @@ window.onbeforeunload = (e) ->
|
||||||
return leavingMessage
|
return leavingMessage
|
||||||
else
|
else
|
||||||
return
|
return
|
||||||
|
|
||||||
$ -> init()
|
$ -> init()
|
||||||
|
|
|
@ -2,6 +2,7 @@ CocoModel = require 'models/CocoModel'
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
{me} = require('core/auth')
|
{me} = require('core/auth')
|
||||||
locale = require 'locale/locale'
|
locale = require 'locale/locale'
|
||||||
|
utils = require 'core/utils'
|
||||||
|
|
||||||
initializeFilePicker = ->
|
initializeFilePicker = ->
|
||||||
require('core/services/filepicker')() unless window.application.isIPadApp
|
require('core/services/filepicker')() unless window.application.isIPadApp
|
||||||
|
@ -234,21 +235,14 @@ class ImageFileTreema extends TreemaNode.nodeMap.string
|
||||||
@refreshDisplay()
|
@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
|
class CodeLanguagesObjectTreema extends TreemaNode.nodeMap.object
|
||||||
childPropertiesAvailable: ->
|
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
|
class CodeLanguageTreema extends TreemaNode.nodeMap.string
|
||||||
buildValueForEditing: (valEl, data) ->
|
buildValueForEditing: (valEl, data) ->
|
||||||
super(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
|
valEl
|
||||||
|
|
||||||
class CodeTreema extends TreemaNode.nodeMap.ace
|
class CodeTreema extends TreemaNode.nodeMap.ace
|
||||||
|
@ -256,8 +250,8 @@ class CodeTreema extends TreemaNode.nodeMap.ace
|
||||||
super(arguments...)
|
super(arguments...)
|
||||||
@workingSchema.aceTabSize = 4
|
@workingSchema.aceTabSize = 4
|
||||||
# TODO: Find a less hacky solution for this
|
# TODO: Find a less hacky solution for this
|
||||||
@workingSchema.aceMode = mode if mode = codeLanguages[@keyForParent]
|
@workingSchema.aceMode = mode if mode = utils.aceEditModes[@keyForParent]
|
||||||
@workingSchema.aceMode = mode if mode = codeLanguages[@parent?.data?.language]
|
@workingSchema.aceMode = mode if mode = utils.aceEditModes[@parent?.data?.language]
|
||||||
|
|
||||||
class CoffeeTreema extends CodeTreema
|
class CoffeeTreema extends CodeTreema
|
||||||
constructor: ->
|
constructor: ->
|
||||||
|
|
|
@ -259,7 +259,7 @@ startsWithVowel = (s) -> s[0] in 'aeiouAEIOU'
|
||||||
module.exports.filterMarkdownCodeLanguages = (text, language) ->
|
module.exports.filterMarkdownCodeLanguages = (text, language) ->
|
||||||
return '' unless text
|
return '' unless text
|
||||||
currentLanguage = language or me.get('aceConfig')?.language or 'python'
|
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.
|
# Exclude language-specific code blocks like ```python (... code ...)``` for each non-target language.
|
||||||
codeBlockExclusionRegex = new RegExp "```(#{excludedLanguages.join('|')})\n[^`]+```\n?", 'gm'
|
codeBlockExclusionRegex = new RegExp "```(#{excludedLanguages.join('|')})\n[^`]+```\n?", 'gm'
|
||||||
# Exclude language-specific images like ![python - image description](image url) for each non-target language.
|
# 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
|
return text
|
||||||
|
|
||||||
module.exports.aceEditModes = aceEditModes =
|
module.exports.aceEditModes = aceEditModes =
|
||||||
'javascript': 'ace/mode/javascript'
|
javascript: 'ace/mode/javascript'
|
||||||
'coffeescript': 'ace/mode/coffee'
|
coffeescript: 'ace/mode/coffee'
|
||||||
'python': 'ace/mode/python'
|
python: 'ace/mode/python'
|
||||||
'java': 'ace/mode/java'
|
lua: 'ace/mode/lua'
|
||||||
'lua': 'ace/mode/lua'
|
java: 'ace/mode/java'
|
||||||
'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) ->
|
module.exports.initializeACE = (el, codeLanguage) ->
|
||||||
contents = $(el).text().trim()
|
contents = $(el).text().trim()
|
||||||
editor = ace.edit el
|
editor = ace.edit el
|
||||||
|
|
|
@ -22,6 +22,7 @@ module.exports = Bus = class Bus extends CocoClass
|
||||||
'auth:me-synced': 'onMeSynced'
|
'auth:me-synced': 'onMeSynced'
|
||||||
|
|
||||||
connect: ->
|
connect: ->
|
||||||
|
# Put Firebase back in bower if you want to use this
|
||||||
Backbone.Mediator.publish 'bus:connecting', {bus: @}
|
Backbone.Mediator.publish 'bus:connecting', {bus: @}
|
||||||
Firebase.goOnline()
|
Firebase.goOnline()
|
||||||
@fireRef = new Firebase(Bus.fireHost + '/' + @docName)
|
@fireRef = new Firebase(Bus.fireHost + '/' + @docName)
|
||||||
|
|
|
@ -94,9 +94,9 @@ module.exports = class God extends CocoClass
|
||||||
return if hadPreloader
|
return if hadPreloader
|
||||||
|
|
||||||
@angelsShare.workQueue = []
|
@angelsShare.workQueue = []
|
||||||
work =
|
work = {
|
||||||
userCodeMap: userCodeMap
|
userCodeMap: userCodeMap
|
||||||
level: @level
|
@level
|
||||||
levelSessionIDs: @levelSessionIDs
|
levelSessionIDs: @levelSessionIDs
|
||||||
submissionCount: @lastSubmissionCount
|
submissionCount: @lastSubmissionCount
|
||||||
fixedSeed: @lastFixedSeed
|
fixedSeed: @lastFixedSeed
|
||||||
|
@ -104,9 +104,10 @@ module.exports = class God extends CocoClass
|
||||||
difficulty: @lastDifficulty
|
difficulty: @lastDifficulty
|
||||||
goals: @angelsShare.goalManager?.getGoals()
|
goals: @angelsShare.goalManager?.getGoals()
|
||||||
headless: @angelsShare.headless
|
headless: @angelsShare.headless
|
||||||
preload: preload
|
preload
|
||||||
synchronous: not Worker? # Profiling world simulation is easier on main thread, or we are IE9.
|
synchronous: not Worker? # Profiling world simulation is easier on main thread, or we are IE9.
|
||||||
realTime: realTime
|
realTime
|
||||||
|
}
|
||||||
@angelsShare.workQueue.push work
|
@angelsShare.workQueue.push work
|
||||||
angel.workIfIdle() for angel in @angelsShare.angels
|
angel.workIfIdle() for angel in @angelsShare.angels
|
||||||
work
|
work
|
||||||
|
@ -114,9 +115,7 @@ module.exports = class God extends CocoClass
|
||||||
getUserCodeMap: (spells) ->
|
getUserCodeMap: (spells) ->
|
||||||
userCodeMap = {}
|
userCodeMap = {}
|
||||||
for spellKey, spell of spells
|
for spellKey, spell of spells
|
||||||
for thangID, spellThang of spell.thangs
|
(userCodeMap[spell.thang.thang.id] ?= {})[spell.name] = spell.thang.aether.serialize()
|
||||||
continue if spellThang.thang?.programmableMethods[spell.name].cloneOf
|
|
||||||
(userCodeMap[thangID] ?= {})[spell.name] = spellThang.aether.serialize()
|
|
||||||
userCodeMap
|
userCodeMap
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,6 @@ module.exports = class LevelBus extends Bus
|
||||||
@fireScriptsRef = @fireRef?.child('scripts')
|
@fireScriptsRef = @fireRef?.child('scripts')
|
||||||
|
|
||||||
setSession: (@session) ->
|
setSession: (@session) ->
|
||||||
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
|
|
||||||
@timerIntervalID = setInterval(@incrementSessionPlaytime, 1000)
|
@timerIntervalID = setInterval(@incrementSessionPlaytime, 1000)
|
||||||
|
|
||||||
onIdleChanged: (e) ->
|
onIdleChanged: (e) ->
|
||||||
|
@ -53,8 +52,7 @@ module.exports = class LevelBus extends Bus
|
||||||
@session.set('playtime', (@session.get('playtime') ? 0) + 1)
|
@session.set('playtime', (@session.get('playtime') ? 0) + 1)
|
||||||
|
|
||||||
onPoint: ->
|
onPoint: ->
|
||||||
return true unless @session?.get('multiplayer')
|
return true
|
||||||
super()
|
|
||||||
|
|
||||||
onMeSynced: =>
|
onMeSynced: =>
|
||||||
super()
|
super()
|
||||||
|
@ -236,17 +234,11 @@ module.exports = class LevelBus extends Bus
|
||||||
@changedSessionProperties.chat = true
|
@changedSessionProperties.chat = true
|
||||||
@saveSession()
|
@saveSession()
|
||||||
|
|
||||||
onMultiplayerChanged: ->
|
|
||||||
@changedSessionProperties.multiplayer = true
|
|
||||||
@session.updatePermissions()
|
|
||||||
@changedSessionProperties.permissions = true
|
|
||||||
@saveSession()
|
|
||||||
|
|
||||||
# Debounced as saveSession
|
# Debounced as saveSession
|
||||||
reallySaveSession: ->
|
reallySaveSession: ->
|
||||||
return if _.isEmpty @changedSessionProperties
|
return if _.isEmpty @changedSessionProperties
|
||||||
# don't let peeking admins mess with the session accidentally
|
# 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
|
return if @session.fake
|
||||||
Backbone.Mediator.publish 'level:session-will-save', session: @session
|
Backbone.Mediator.publish 'level:session-will-save', session: @session
|
||||||
patch = {}
|
patch = {}
|
||||||
|
|
|
@ -53,6 +53,16 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
|
|
||||||
# Supermodel (Level) Loading
|
# 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: ->
|
loadLevel: ->
|
||||||
@level = @supermodel.getModel(Level, @levelID) or new Level _id: @levelID
|
@level = @supermodel.getModel(Level, @levelID) or new Level _id: @levelID
|
||||||
if @level.loaded
|
if @level.loaded
|
||||||
|
@ -62,9 +72,18 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
@listenToOnce @level, 'sync', @onLevelLoaded
|
@listenToOnce @level, 'sync', @onLevelLoaded
|
||||||
|
|
||||||
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 = {}
|
@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.
|
# 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
|
originalGet = @level.get
|
||||||
@level.get = ->
|
@level.get = ->
|
||||||
|
@ -169,7 +188,7 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
@consolidateFlagHistory() if @opponentSession?.loaded
|
@consolidateFlagHistory() if @opponentSession?.loaded
|
||||||
else if session is @opponentSession
|
else if session is @opponentSession
|
||||||
@consolidateFlagHistory() if @session.loaded
|
@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
|
heroThangType = me.get('heroConfig')?.thangType or ThangType.heroes.captain
|
||||||
console.log "Course mode, loading custom hero: ", heroThangType if LOG
|
console.log "Course mode, loading custom hero: ", heroThangType if LOG
|
||||||
url = "/db/thang.type/#{heroThangType}/version"
|
url = "/db/thang.type/#{heroThangType}/version"
|
||||||
|
@ -178,7 +197,7 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
@worldNecessities.push heroResource
|
@worldNecessities.push heroResource
|
||||||
@sessionDependenciesRegistered[session.id] = true
|
@sessionDependenciesRegistered[session.id] = true
|
||||||
return
|
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 = session.get('heroConfig')
|
||||||
heroConfig ?= me.get('heroConfig') if session is @session and not @headless
|
heroConfig ?= me.get('heroConfig') if session is @session and not @headless
|
||||||
heroConfig ?= {}
|
heroConfig ?= {}
|
||||||
|
@ -332,8 +351,8 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
@worldNecessities = (r for r in @worldNecessities when r?)
|
@worldNecessities = (r for r in @worldNecessities when r?)
|
||||||
@onWorldNecessitiesLoaded() if @checkAllWorldNecessitiesRegisteredAndLoaded()
|
@onWorldNecessitiesLoaded() if @checkAllWorldNecessitiesRegisteredAndLoaded()
|
||||||
|
|
||||||
onWorldNecessityLoadFailed: (resource) ->
|
onWorldNecessityLoadFailed: (event) ->
|
||||||
@trigger('world-necessity-load-failed', resource: resource)
|
@trigger('world-necessity-load-failed', event)
|
||||||
|
|
||||||
checkAllWorldNecessitiesRegisteredAndLoaded: ->
|
checkAllWorldNecessitiesRegisteredAndLoaded: ->
|
||||||
return false unless _.filter(@worldNecessities).length is 0
|
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
|
resource.markLoaded() if resource.spriteSheetKeys.length is 0
|
||||||
|
|
||||||
denormalizeSession: ->
|
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
|
# This is a way (the way?) PUT /db/level.sessions/undefined was happening
|
||||||
# See commit c242317d9
|
# See commit c242317d9
|
||||||
return if not @session.id
|
return if not @session.id
|
||||||
|
@ -443,7 +463,7 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
@grabTeamConfigs()
|
@grabTeamConfigs()
|
||||||
@thangTypeTeams = {}
|
@thangTypeTeams = {}
|
||||||
for thang in @level.get('thangs')
|
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
|
continue # No team colors for heroes on single-player levels
|
||||||
for component in thang.components
|
for component in thang.components
|
||||||
if team = component.config?.team
|
if team = component.config?.team
|
||||||
|
@ -471,6 +491,7 @@ module.exports = class LevelLoader extends CocoClass
|
||||||
initWorld: ->
|
initWorld: ->
|
||||||
return if @initialized
|
return if @initialized
|
||||||
@initialized = true
|
@initialized = true
|
||||||
|
return if @level.isType('web-dev')
|
||||||
@world = new World()
|
@world = new World()
|
||||||
@world.levelSessionIDs = if @opponentSessionID then [@sessionID, @opponentSessionID] else [@sessionID]
|
@world.levelSessionIDs = if @opponentSessionID then [@sessionID, @opponentSessionID] else [@sessionID]
|
||||||
@world.submissionCount = @session?.get('state')?.submissionCount ? 0
|
@world.submissionCount = @session?.get('state')?.submissionCount ? 0
|
||||||
|
|
|
@ -74,7 +74,7 @@ module.exports = class LevelSetupManager extends CocoClass
|
||||||
@session.set 'heroConfig', {"thangType":raider,"inventory":{}}
|
@session.set 'heroConfig', {"thangType":raider,"inventory":{}}
|
||||||
@onInventoryModalPlayClicked()
|
@onInventoryModalPlayClicked()
|
||||||
return
|
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()
|
@onInventoryModalPlayClicked()
|
||||||
return
|
return
|
||||||
@heroesModal = new PlayHeroesModal({supermodel: @supermodel, session: @session, confirmButtonI18N: 'play.next', level: @level, hadEverChosenHero: @options.hadEverChosenHero})
|
@heroesModal = new PlayHeroesModal({supermodel: @supermodel, session: @session, confirmButtonI18N: 'play.next', level: @level, hadEverChosenHero: @options.hadEverChosenHero})
|
||||||
|
|
|
@ -195,6 +195,17 @@ module.exports =
|
||||||
_.assign(progressData, progressMixin)
|
_.assign(progressData, progressMixin)
|
||||||
return progressData
|
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 =
|
progressMixin =
|
||||||
get: (options={}) ->
|
get: (options={}) ->
|
||||||
{ classroom, course, level, user } = options
|
{ classroom, course, level, user } = options
|
||||||
|
|
|
@ -10,7 +10,6 @@ Letterbox = require './Letterbox'
|
||||||
Dimmer = require './Dimmer'
|
Dimmer = require './Dimmer'
|
||||||
CountdownScreen = require './CountdownScreen'
|
CountdownScreen = require './CountdownScreen'
|
||||||
PlaybackOverScreen = require './PlaybackOverScreen'
|
PlaybackOverScreen = require './PlaybackOverScreen'
|
||||||
WaitingScreen = require './WaitingScreen'
|
|
||||||
DebugDisplay = require './DebugDisplay'
|
DebugDisplay = require './DebugDisplay'
|
||||||
CoordinateDisplay = require './CoordinateDisplay'
|
CoordinateDisplay = require './CoordinateDisplay'
|
||||||
CoordinateGrid = require './CoordinateGrid'
|
CoordinateGrid = require './CoordinateGrid'
|
||||||
|
@ -70,7 +69,6 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
'level:set-letterbox': 'onSetLetterbox'
|
'level:set-letterbox': 'onSetLetterbox'
|
||||||
'application:idle-changed': 'onIdleChanged'
|
'application:idle-changed': 'onIdleChanged'
|
||||||
'camera:zoom-updated': 'onZoomUpdated'
|
'camera:zoom-updated': 'onZoomUpdated'
|
||||||
'playback:real-time-playback-waiting': 'onRealTimePlaybackWaiting'
|
|
||||||
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
|
'playback:real-time-playback-started': 'onRealTimePlaybackStarted'
|
||||||
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
||||||
'level:flag-color-selected': 'onFlagColorSelected'
|
'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
|
@countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer, showsCountdown: @world.showsCountdown
|
||||||
@playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer, playerNames: @options.playerNames
|
@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.
|
@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()
|
@initCoordinates()
|
||||||
@webGLStage.enableMouseOver(10)
|
@webGLStage.enableMouseOver(10)
|
||||||
@webGLStage.addEventListener 'stagemousemove', @onMouseMove
|
@webGLStage.addEventListener 'stagemousemove', @onMouseMove
|
||||||
|
@ -570,7 +567,7 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
scaleFactor = 1
|
scaleFactor = 1
|
||||||
if @options.stayVisible
|
if @options.stayVisible
|
||||||
availableHeight = window.innerHeight
|
availableHeight = window.innerHeight
|
||||||
availableHeight -= $('.ad-container').outerHeight()
|
availableHeight -= $('.ad-container').outerHeight()
|
||||||
availableHeight -= $('#game-area').outerHeight() - $('#canvas-wrapper').outerHeight()
|
availableHeight -= $('#game-area').outerHeight() - $('#canvas-wrapper').outerHeight()
|
||||||
scaleFactor = availableHeight / newHeight if availableHeight < newHeight
|
scaleFactor = availableHeight / newHeight if availableHeight < newHeight
|
||||||
newWidth *= scaleFactor
|
newWidth *= scaleFactor
|
||||||
|
@ -602,9 +599,6 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
|
|
||||||
#- Real-time playback
|
#- Real-time playback
|
||||||
|
|
||||||
onRealTimePlaybackWaiting: (e) ->
|
|
||||||
@onRealTimePlaybackStarted e
|
|
||||||
|
|
||||||
onRealTimePlaybackStarted: (e) ->
|
onRealTimePlaybackStarted: (e) ->
|
||||||
return if @realTime
|
return if @realTime
|
||||||
@realTimeInputEvents.reset()
|
@realTimeInputEvents.reset()
|
||||||
|
@ -741,7 +735,6 @@ module.exports = Surface = class Surface extends CocoClass
|
||||||
@dimmer?.destroy()
|
@dimmer?.destroy()
|
||||||
@countdownScreen?.destroy()
|
@countdownScreen?.destroy()
|
||||||
@playbackOverScreen?.destroy()
|
@playbackOverScreen?.destroy()
|
||||||
@waitingScreen?.destroy()
|
|
||||||
@coordinateDisplay?.destroy()
|
@coordinateDisplay?.destroy()
|
||||||
@coordinateGrid?.destroy()
|
@coordinateGrid?.destroy()
|
||||||
@normalStage.clear()
|
@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:
|
subscriptions:
|
||||||
'god:new-world-created': 'onNewWorldCreated'
|
'god:new-world-created': 'onNewWorldCreated'
|
||||||
|
'god:new-html-goal-states': 'onNewHTMLGoalStates'
|
||||||
'level:restarted': 'onLevelRestarted'
|
'level:restarted': 'onLevelRestarted'
|
||||||
|
|
||||||
backgroundSubscriptions:
|
backgroundSubscriptions:
|
||||||
|
@ -86,6 +87,9 @@ module.exports = class GoalManager extends CocoClass
|
||||||
@world = e.world
|
@world = e.world
|
||||||
@updateGoalStates(e.goalStates) if e.goalStates?
|
@updateGoalStates(e.goalStates) if e.goalStates?
|
||||||
|
|
||||||
|
onNewHTMLGoalStates: (e) ->
|
||||||
|
@updateGoalStates(e.goalStates) if e.goalStates?
|
||||||
|
|
||||||
updateGoalStates: (newGoalStates) ->
|
updateGoalStates: (newGoalStates) ->
|
||||||
for goalID, goalState of newGoalStates
|
for goalID, goalState of newGoalStates
|
||||||
continue unless @goalStates[goalID]?
|
continue unless @goalStates[goalID]?
|
||||||
|
@ -114,7 +118,7 @@ module.exports = class GoalManager extends CocoClass
|
||||||
goalStates: @goalStates
|
goalStates: @goalStates
|
||||||
goals: @goals
|
goals: @goals
|
||||||
overallStatus: overallStatus
|
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)
|
Backbone.Mediator.publish('goal-manager:new-goal-states', event)
|
||||||
|
|
||||||
checkOverallStatus: (ignoreIncomplete=false) ->
|
checkOverallStatus: (ignoreIncomplete=false) ->
|
||||||
|
@ -264,7 +268,7 @@ module.exports = class GoalManager extends CocoClass
|
||||||
mostEagerGoal = _.min matchedGoals, 'worldEndsAfter'
|
mostEagerGoal = _.min matchedGoals, 'worldEndsAfter'
|
||||||
victory = overallStatus is 'success'
|
victory = overallStatus is 'success'
|
||||||
tentative = 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) ->
|
updateGoalState: (goalID, thangID, progressObjectName, frameNumber) ->
|
||||||
# A thang has done something related to the goal!
|
# A thang has done something related to the goal!
|
||||||
|
@ -291,7 +295,7 @@ module.exports = class GoalManager extends CocoClass
|
||||||
mostEagerGoal = _.min matchedGoals, 'worldEndsAfter'
|
mostEagerGoal = _.min matchedGoals, 'worldEndsAfter'
|
||||||
victory = overallStatus is 'success'
|
victory = overallStatus is 'success'
|
||||||
tentative = 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) ->
|
goalIsPositive: (goalID) ->
|
||||||
# Positive goals are completed when all conditions are true (kill all these thangs)
|
# Positive goals are completed when all conditions are true (kill all these thangs)
|
||||||
|
|
|
@ -318,7 +318,7 @@
|
||||||
write_this_down: "Write this down:"
|
write_this_down: "Write this down:"
|
||||||
start_playing: "Start Playing!"
|
start_playing: "Start Playing!"
|
||||||
sso_connected: "Successfully connected with:"
|
sso_connected: "Successfully connected with:"
|
||||||
|
|
||||||
recover:
|
recover:
|
||||||
recover_account_title: "Recover Account"
|
recover_account_title: "Recover Account"
|
||||||
send_password: "Send Recovery Password"
|
send_password: "Send Recovery Password"
|
||||||
|
@ -450,8 +450,6 @@
|
||||||
incomplete: "Incomplete"
|
incomplete: "Incomplete"
|
||||||
timed_out: "Ran out of time"
|
timed_out: "Ran out of time"
|
||||||
failing: "Failing"
|
failing: "Failing"
|
||||||
control_bar_multiplayer: "Multiplayer"
|
|
||||||
control_bar_join_game: "Join Game"
|
|
||||||
reload: "Reload"
|
reload: "Reload"
|
||||||
reload_title: "Reload All Code?"
|
reload_title: "Reload All Code?"
|
||||||
reload_really: "Are you sure you want to reload this level back to the beginning?"
|
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_running: "Running"
|
||||||
tome_cast_button_ran: "Ran"
|
tome_cast_button_ran: "Ran"
|
||||||
tome_submit_button: "Submit"
|
tome_submit_button: "Submit"
|
||||||
tome_reload_method: "Reload original code for this method" # Title text for individual method reload button.
|
tome_reload_method: "Reload original code to restart the level" # {change}
|
||||||
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_available_spells: "Available Spells"
|
tome_available_spells: "Available Spells"
|
||||||
tome_your_skills: "Your Skills"
|
tome_your_skills: "Your Skills"
|
||||||
tome_current_method: "Current Method"
|
tome_current_method: "Current Method"
|
||||||
|
@ -1478,6 +1473,29 @@
|
||||||
status_not_enrolled: "Not Enrolled"
|
status_not_enrolled: "Not Enrolled"
|
||||||
status_enrolled: "Expires on {{date}}"
|
status_enrolled: "Expires on {{date}}"
|
||||||
select_all: "Select All"
|
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:
|
classes:
|
||||||
archmage_title: "Archmage"
|
archmage_title: "Archmage"
|
||||||
|
@ -1880,6 +1898,17 @@
|
||||||
vectors: "Vectors"
|
vectors: "Vectors"
|
||||||
while_loops: "While Loops"
|
while_loops: "While Loops"
|
||||||
recursion: "Recursion"
|
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:
|
delta:
|
||||||
added: "Added"
|
added: "Added"
|
||||||
|
@ -1891,16 +1920,6 @@
|
||||||
merge_conflict_with: "MERGE CONFLICT WITH"
|
merge_conflict_with: "MERGE CONFLICT WITH"
|
||||||
no_changes: "No Changes"
|
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:
|
legal:
|
||||||
page_title: "Legal"
|
page_title: "Legal"
|
||||||
opensource_intro: "CodeCombat is completely open source."
|
opensource_intro: "CodeCombat is completely open source."
|
||||||
|
|
|
@ -74,7 +74,7 @@ module.exports = class Classroom extends CocoModel
|
||||||
}
|
}
|
||||||
|
|
||||||
getLevels: (options={}) ->
|
getLevels: (options={}) ->
|
||||||
# options: courseID, withoutLadderLevels
|
# options: courseID, withoutLadderLevels, projectLevels
|
||||||
Levels = require 'collections/Levels'
|
Levels = require 'collections/Levels'
|
||||||
courses = @get('courses')
|
courses = @get('courses')
|
||||||
return new Levels() unless courses
|
return new Levels() unless courses
|
||||||
|
@ -86,6 +86,8 @@ module.exports = class Classroom extends CocoModel
|
||||||
levels = new Levels(_.flatten(levelObjects))
|
levels = new Levels(_.flatten(levelObjects))
|
||||||
if options.withoutLadderLevels
|
if options.withoutLadderLevels
|
||||||
levels.remove(levels.filter((level) -> level.isLadder()))
|
levels.remove(levels.filter((level) -> level.isLadder()))
|
||||||
|
if options.projectLevels
|
||||||
|
levels.remove(levels.filter((level) -> level.get('shareable') isnt 'project'))
|
||||||
return levels
|
return levels
|
||||||
|
|
||||||
getLadderLevel: (courseID) ->
|
getLadderLevel: (courseID) ->
|
||||||
|
|
|
@ -5,3 +5,10 @@ module.exports = class Course extends CocoModel
|
||||||
@className: 'Course'
|
@className: 'Course'
|
||||||
@schema: schema
|
@schema: schema
|
||||||
urlRoot: '/db/course'
|
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
|
for tt in supermodel.getModels ThangType
|
||||||
if tmap[tt.get('original')] or
|
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') 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')))
|
o.thangTypes.push (original: tt.get('original'), name: tt.get('name'), components: $.extend(true, [], tt.get('components')))
|
||||||
@sortThangComponents o.thangTypes, o.levelComponents, 'ThangType'
|
@sortThangComponents o.thangTypes, o.levelComponents, 'ThangType'
|
||||||
@fillInDefaultComponentConfiguration o.thangTypes, o.levelComponents
|
@fillInDefaultComponentConfiguration o.thangTypes, o.levelComponents
|
||||||
|
@ -59,7 +59,7 @@ module.exports = class Level extends CocoModel
|
||||||
|
|
||||||
denormalize: (supermodel, session, otherSession) ->
|
denormalize: (supermodel, session, otherSession) ->
|
||||||
o = $.extend true, {}, @attributes
|
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')?)
|
thangTypesWithComponents = (tt for tt in supermodel.getModels(ThangType) when tt.get('components')?)
|
||||||
thangTypesByOriginal = _.indexBy thangTypesWithComponents, (tt) -> tt.get('original') # Optimization
|
thangTypesByOriginal = _.indexBy thangTypesWithComponents, (tt) -> tt.get('original') # Optimization
|
||||||
for levelThang in o.thangs
|
for levelThang in o.thangs
|
||||||
|
@ -68,7 +68,7 @@ module.exports = class Level extends CocoModel
|
||||||
|
|
||||||
denormalizeThang: (levelThang, supermodel, session, otherSession, thangTypesByOriginal) ->
|
denormalizeThang: (levelThang, supermodel, session, otherSession, thangTypesByOriginal) ->
|
||||||
levelThang.components ?= []
|
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 isHero and otherSession
|
||||||
# If it's a hero and there's another session, find the right session for it.
|
# 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.
|
# 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
|
levelThang.components.push placeholderComponent
|
||||||
|
|
||||||
# Load the user's chosen hero AFTER getting stats from default char
|
# 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
|
heroThangType = me.get('heroConfig')?.thangType or ThangType.heroes.captain
|
||||||
levelThang.thangType = heroThangType if heroThangType
|
levelThang.thangType = heroThangType if heroThangType
|
||||||
|
|
||||||
|
@ -263,6 +263,9 @@ module.exports = class Level extends CocoModel
|
||||||
isLadder: ->
|
isLadder: ->
|
||||||
return @get('type')?.indexOf('ladder') > -1
|
return @get('type')?.indexOf('ladder') > -1
|
||||||
|
|
||||||
|
isType: (types...) ->
|
||||||
|
return @get('type', true) in types
|
||||||
|
|
||||||
fetchNextForCourse: ({ levelOriginalID, courseInstanceID, courseID, sessionID }, options={}) ->
|
fetchNextForCourse: ({ levelOriginalID, courseInstanceID, courseID, sessionID }, options={}) ->
|
||||||
if courseInstanceID
|
if courseInstanceID
|
||||||
options.url = "/db/course_instance/#{courseInstanceID}/levels/#{levelOriginalID}/sessions/#{sessionID}/next"
|
options.url = "/db/course_instance/#{courseInstanceID}/levels/#{levelOriginalID}/sessions/#{sessionID}/next"
|
||||||
|
|
|
@ -15,8 +15,6 @@ module.exports = class LevelSession extends CocoModel
|
||||||
updatePermissions: ->
|
updatePermissions: ->
|
||||||
permissions = @get 'permissions', true
|
permissions = @get 'permissions', true
|
||||||
permissions = (p for p in permissions when p.target isnt 'public')
|
permissions = (p for p in permissions when p.target isnt 'public')
|
||||||
if @get('multiplayer')
|
|
||||||
permissions.push {target: 'public', access: 'write'}
|
|
||||||
@set 'permissions', permissions
|
@set 'permissions', permissions
|
||||||
|
|
||||||
getSourceFor: (spellKey) ->
|
getSourceFor: (spellKey) ->
|
||||||
|
@ -76,6 +74,7 @@ module.exports = class LevelSession extends CocoModel
|
||||||
wait
|
wait
|
||||||
|
|
||||||
recordScores: (scores, level) ->
|
recordScores: (scores, level) ->
|
||||||
|
return unless scores
|
||||||
state = @get 'state'
|
state = @get 'state'
|
||||||
oldTopScores = state.topScores ? []
|
oldTopScores = state.topScores ? []
|
||||||
newTopScores = []
|
newTopScores = []
|
||||||
|
@ -93,3 +92,17 @@ module.exports = class LevelSession extends CocoModel
|
||||||
newTopScores.push oldTopScore
|
newTopScores.push oldTopScore
|
||||||
state.topScores = newTopScores
|
state.topScores = newTopScores
|
||||||
@set 'state', state
|
@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()
|
|
|
@ -247,6 +247,15 @@ module.exports = class SuperModel extends Backbone.Model
|
||||||
|
|
||||||
getResource: (rid) ->
|
getResource: (rid) ->
|
||||||
return @resources[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
|
class Resource extends Backbone.Model
|
||||||
constructor: (name, value=1) ->
|
constructor: (name, value=1) ->
|
||||||
|
|
|
@ -61,12 +61,13 @@ _.extend CampaignSchema.properties, {
|
||||||
i18n: { type: 'object', format: 'hidden' }
|
i18n: { type: 'object', format: 'hidden' }
|
||||||
requiresSubscription: { type: 'boolean' }
|
requiresSubscription: { type: 'boolean' }
|
||||||
replayable: { 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' }
|
slug: { type: 'string', format: 'hidden' }
|
||||||
original: { type: 'string', format: 'hidden' }
|
original: { type: 'string', format: 'hidden' }
|
||||||
adventurer: { type: 'boolean' }
|
adventurer: { type: 'boolean' }
|
||||||
practice: { type: 'boolean' }
|
practice: { type: 'boolean' }
|
||||||
practiceThresholdMinutes: {type: 'number'}
|
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' }
|
adminOnly: { type: 'boolean' }
|
||||||
disableSpaces: { type: ['boolean','number'] }
|
disableSpaces: { type: ['boolean','number'] }
|
||||||
hidesSubmitUntilRun: { type: 'boolean' }
|
hidesSubmitUntilRun: { type: 'boolean' }
|
||||||
|
|
|
@ -25,7 +25,7 @@ _.extend ClassroomSchema.properties,
|
||||||
levels: c.array { title: 'Levels' }, c.object { title: 'Level' }, {
|
levels: c.array { title: 'Levels' }, c.object { title: 'Level' }, {
|
||||||
practice: {type: 'boolean'}
|
practice: {type: 'boolean'}
|
||||||
practiceThresholdMinutes: {type: 'number'}
|
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()
|
type: c.shortString()
|
||||||
original: c.objectId()
|
original: c.objectId()
|
||||||
name: {type: 'string'}
|
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
|
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
|
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.'}}
|
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']},
|
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}
|
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'}
|
icon: {type: 'string', format: 'image-file', title: 'Icon'}
|
||||||
banner: {type: 'string', format: 'image-file', title: 'Banner'}
|
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
|
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
|
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'])
|
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'}
|
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.'}
|
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.'}
|
buildTime: {type: 'number', description: 'How long it has taken to build this level.'}
|
||||||
practice: { type: 'boolean' }
|
practice: { type: 'boolean' }
|
||||||
shareable: { type: 'boolean', title: 'Shareable' }
|
|
||||||
practiceThresholdMinutes: {type: 'number', description: 'Players with larger playtimes may be directed to a practice level.'}
|
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
|
# Admin flags
|
||||||
adventurer: { type: 'boolean' }
|
adventurer: { type: 'boolean' }
|
||||||
|
|
|
@ -37,8 +37,6 @@ _.extend LevelSessionSchema.properties,
|
||||||
type: 'string'
|
type: 'string'
|
||||||
levelID:
|
levelID:
|
||||||
type: 'string'
|
type: 'string'
|
||||||
multiplayer:
|
|
||||||
type: 'boolean'
|
|
||||||
creator: c.objectId
|
creator: c.objectId
|
||||||
links:
|
links:
|
||||||
[
|
[
|
||||||
|
|
|
@ -261,4 +261,15 @@ me.concept = me.shortString enum: [
|
||||||
'vectors'
|
'vectors'
|
||||||
'while_loops'
|
'while_loops'
|
||||||
'recursion'
|
'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: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:goals-calculated': c.object {required: ['goalStates', 'god']},
|
||||||
god: {type: 'object'}
|
god: {type: 'object'}
|
||||||
goalStates: goalStatesSchema
|
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:stop-real-time-playback': c.object {}
|
||||||
|
|
||||||
'playback:real-time-playback-waiting': c.object {}
|
|
||||||
|
|
||||||
'playback:real-time-playback-started': c.object {}
|
'playback:real-time-playback-started': c.object {}
|
||||||
|
|
||||||
'playback:real-time-playback-ended': c.object {}
|
'playback:real-time-playback-ended': c.object {}
|
||||||
|
|
|
@ -39,8 +39,6 @@ module.exports =
|
||||||
variableChain: c.array {}, {type: 'string'}
|
variableChain: c.array {}, {type: 'string'}
|
||||||
frame: {type: 'integer', minimum: 0}
|
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: []},
|
'tome:reload-code': c.object {title: 'Reload Code', description: 'Published when you reset a spell to its original source', required: []},
|
||||||
spell: {type: 'object'}
|
spell: {type: 'object'}
|
||||||
|
|
||||||
|
@ -91,10 +89,6 @@ module.exports =
|
||||||
problems: {type: 'array'}
|
problems: {type: 'array'}
|
||||||
isCast: {type: 'boolean'}
|
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']},
|
'tome:change-language': c.object {title: 'Tome Change Language', description: 'Published when the Tome should update its programming language', required: ['language']},
|
||||||
language: {type: 'string'}
|
language: {type: 'string'}
|
||||||
reload: {type: 'boolean', description: 'Whether player code should reload to the default when the language changes.'}
|
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']}
|
lineOffsetPx: {type: ['number', 'undefined']}
|
||||||
'tome:hide-problem-alert': c.object {title: 'Hide Problem Alert'}
|
'tome:hide-problem-alert': c.object {title: 'Hide Problem Alert'}
|
||||||
'tome:jiggle-problem-alert': c.object {title: 'Jiggle 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
|
h1
|
||||||
font-size: 48px
|
font-size: 48px
|
||||||
|
|
||||||
|
.btn-view-project-level
|
||||||
|
margin-left: 10px;
|
||||||
|
|
|
@ -177,7 +177,7 @@
|
||||||
|
|
||||||
// Course Progress tab
|
// Course Progress tab
|
||||||
|
|
||||||
#course-progress-tab
|
#course-progress-tab, #student-projects-tab
|
||||||
.course-overview-row
|
.course-overview-row
|
||||||
margin-top: 50px
|
margin-top: 50px
|
||||||
border: thin solid gray
|
border: thin solid gray
|
||||||
|
@ -221,7 +221,20 @@
|
||||||
.btn
|
.btn
|
||||||
margin-top: 6.5px
|
margin-top: 6.5px
|
||||||
margin-bottom: 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
|
// Checkboxes
|
||||||
.checkbox-flat
|
.checkbox-flat
|
||||||
|
|
|
@ -25,8 +25,10 @@ $gameControlMargin: 30px
|
||||||
margin-bottom: -$levelDotHeight / 3 + $levelDotZ
|
margin-bottom: -$levelDotHeight / 3 + $levelDotZ
|
||||||
|
|
||||||
#campaign-view
|
#campaign-view
|
||||||
width: 100%
|
top: 0
|
||||||
height: 100%
|
right: 0
|
||||||
|
bottom: 0
|
||||||
|
left: 0
|
||||||
position: absolute
|
position: absolute
|
||||||
|
|
||||||
.gradient
|
.gradient
|
||||||
|
@ -615,6 +617,8 @@ $gameControlMargin: 30px
|
||||||
|
|
||||||
.gameplay-container
|
.gameplay-container
|
||||||
position: absolute
|
position: absolute
|
||||||
|
height: 100%
|
||||||
|
width: 100%
|
||||||
|
|
||||||
body.ipad #campaign-view
|
body.ipad #campaign-view
|
||||||
// iPad only supports up to Kithgard Gates for now.
|
// iPad only supports up to Kithgard Gates for now.
|
||||||
|
|
|
@ -124,37 +124,6 @@
|
||||||
@include rotate(-15deg)
|
@include rotate(-15deg)
|
||||||
vertical-align: middle
|
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
|
.buttons-area
|
||||||
position: absolute
|
position: absolute
|
||||||
right: 35px
|
right: 35px
|
||||||
|
@ -210,11 +179,6 @@ html.no-borderimage
|
||||||
background: transparent url(/images/level/control_bar_level_name_background.png)
|
background: transparent url(/images/level/control_bar_level_name_background.png)
|
||||||
background-size: contain
|
background-size: contain
|
||||||
background-repeat: no-repeat
|
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)
|
body:not(.ipad)
|
||||||
|
|
|
@ -181,3 +181,13 @@ $UNVEIL_TIME: 1.2s
|
||||||
left: 48px
|
left: 48px
|
||||||
right: 77px
|
right: 77px
|
||||||
width: auto
|
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
|
padding-top: 0
|
||||||
width: 750px
|
width: 750px
|
||||||
|
|
||||||
|
@media screen and ( max-height: 625px )
|
||||||
|
margin-top: -50px
|
||||||
|
|
||||||
.modal-content
|
.modal-content
|
||||||
position: relative
|
position: relative
|
||||||
margin-top: -251px
|
margin-top: -251px
|
||||||
|
@ -55,10 +58,14 @@
|
||||||
top: 80px
|
top: 80px
|
||||||
margin-top: 80px
|
margin-top: 80px
|
||||||
|
|
||||||
|
@media screen and ( max-height: 650px )
|
||||||
|
padding-top: 10px
|
||||||
|
|
||||||
.well-parchment
|
.well-parchment
|
||||||
margin-top: 20px
|
margin-top: 20px
|
||||||
|
|
||||||
|
@media screen and ( max-height: 675px )
|
||||||
|
margin-top: 0
|
||||||
|
|
||||||
|
|
||||||
html.no-borderimage
|
html.no-borderimage
|
||||||
|
|
|
@ -298,6 +298,33 @@
|
||||||
height: 100%
|
height: 100%
|
||||||
position: absolute
|
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
|
//- Footer - other stuff
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,18 @@
|
||||||
color: black
|
color: black
|
||||||
margin-bottom: 5px
|
margin-bottom: 5px
|
||||||
|
|
||||||
p
|
.next-level-description
|
||||||
margin-top: 30px
|
p
|
||||||
|
margin-top: 30px
|
||||||
|
|
||||||
.course-title
|
.course-title
|
||||||
white-space: nowrap
|
white-space: nowrap
|
||||||
text-overflow: ellipsis
|
text-overflow: ellipsis
|
||||||
overflow: hidden
|
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: -webkit-calc(100% - 38px)
|
||||||
width: 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)
|
@media only screen and (max-width: 1100px)
|
||||||
#spell-palette-view
|
#spell-palette-view
|
||||||
// Make sure we have enough room for at least two columns
|
// Make sure we have enough room for at least two columns
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
@import "app/styles/mixins"
|
@import "app/styles/mixins"
|
||||||
@import "app/styles/bootstrap/variables"
|
@import "app/styles/bootstrap/variables"
|
||||||
|
|
||||||
.spell-list-entry-view
|
#spell-top-bar-view
|
||||||
.method-signature
|
|
||||||
background-color: transparent
|
|
||||||
border: 0
|
|
||||||
font-size: 1.1em
|
|
||||||
display: inline-block
|
|
||||||
padding: 4px
|
|
||||||
|
|
||||||
.spell-list-entry-view.spell-tab
|
|
||||||
$height: 87px
|
$height: 87px
|
||||||
$paddingTop: 10px
|
$paddingTop: 10px
|
||||||
$paddingBottom: 25px
|
$paddingBottom: 25px
|
||||||
|
@ -46,12 +38,6 @@
|
||||||
> *:not(.spell-tool-buttons)
|
> *:not(.spell-tool-buttons)
|
||||||
@include opacity(0.5)
|
@include opacity(0.5)
|
||||||
|
|
||||||
.thang-avatar-view
|
|
||||||
width: $childSize - 10px
|
|
||||||
margin: 5px 0.4vw
|
|
||||||
display: inline-block
|
|
||||||
float: left
|
|
||||||
|
|
||||||
.btn.btn-small
|
.btn.btn-small
|
||||||
margin-top: 15px
|
margin-top: 15px
|
||||||
margin-right: 1.3vw
|
margin-right: 1.3vw
|
||||||
|
@ -97,46 +83,8 @@
|
||||||
.thang-avatar-wrapper
|
.thang-avatar-wrapper
|
||||||
border-width: 0
|
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
|
//html.no-borderimage
|
||||||
// .spell-list-entry-view.spell-tab
|
// .spell-top-bar-view
|
||||||
// border-width: 0
|
// border-width: 0
|
||||||
// border-image: none
|
// border-image: none
|
||||||
// background: transparent url(/images/level/code_editor_tab_background.png) no-repeat
|
// 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
|
|
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%
|
right: 45%
|
||||||
z-index: 1000000
|
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
|
html.fullscreen-editor
|
||||||
#level-view
|
#level-view
|
||||||
#fullscreen-editor-background-screen
|
#fullscreen-editor-background-screen
|
||||||
|
|
|
@ -44,7 +44,7 @@ block content
|
||||||
a(href="/admin/classroom-levels") Classroom Levels
|
a(href="/admin/classroom-levels") Classroom Levels
|
||||||
li
|
li
|
||||||
button.classroom-progress-csv.btn.btn-sm.btn-success Classroom Progress CSV
|
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
|
li
|
||||||
a(href="/admin/analytics") Dashboard
|
a(href="/admin/analytics") Dashboard
|
||||||
li
|
li
|
||||||
|
|
|
@ -104,13 +104,23 @@ block content
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
if previousLevelCompleted || view.teacherMode || !passedLastCompletedLevel || levelStatus
|
if previousLevelCompleted || view.teacherMode || !passedLastCompletedLevel || levelStatus
|
||||||
- var i18n = level.get('type') === 'course-ladder' ? 'play.compete' : 'home.play';
|
- 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=i18n, data-level-id=level.get('original'))
|
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
|
td
|
||||||
if view.userLevelStateMap[me.id]
|
if view.userLevelStateMap[me.id]
|
||||||
div= view.userLevelStateMap[me.id][level.get('original')]
|
div= view.userLevelStateMap[me.id][level.get('original')]
|
||||||
td #{level.get('practice') ? 'practice' : 'required'}
|
td #{level.get('practice') ? 'practice' : 'required'}
|
||||||
td #{levelNumber}. #{level.get('name').replace('Course: ', '')}
|
td #{levelNumber}. #{i18n(level.attributes, 'name').replace('Course: ', '')}
|
||||||
td
|
td
|
||||||
if view.levelConceptMap[level.get('original')]
|
if view.levelConceptMap[level.get('original')]
|
||||||
each concept in view.course.get('concepts')
|
each concept in view.course.get('concepts')
|
||||||
|
|
|
@ -122,6 +122,10 @@ block content
|
||||||
li(class=(activeTab === "#enrollment-status-tab" ? 'active' : ''))
|
li(class=(activeTab === "#enrollment-status-tab" ? 'active' : ''))
|
||||||
a.course-progress-tab-btn(href='#enrollment-status-tab')
|
a.course-progress-tab-btn(href='#enrollment-status-tab')
|
||||||
.small-details.text-center(data-i18n='teacher.enrollment_status')
|
.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-filler
|
||||||
|
|
||||||
.tab-content
|
.tab-content
|
||||||
|
@ -129,8 +133,10 @@ block content
|
||||||
+studentsTab
|
+studentsTab
|
||||||
else if activeTab === '#course-progress-tab'
|
else if activeTab === '#course-progress-tab'
|
||||||
+courseProgressTab
|
+courseProgressTab
|
||||||
else
|
else if activeTab === '#enrollment-status-tab'
|
||||||
+enrollmentStatusTab
|
+enrollmentStatusTab
|
||||||
|
else
|
||||||
|
+studentProjectsTab
|
||||||
|
|
||||||
else
|
else
|
||||||
.text-center.m-t-5.m-b-5
|
.text-center.m-t-5.m-b-5
|
||||||
|
@ -150,11 +156,10 @@ mixin breadcrumbs
|
||||||
mixin longLevelName(data)
|
mixin longLevelName(data)
|
||||||
if data
|
if data
|
||||||
div.level-name
|
div.level-name
|
||||||
span.spr Course
|
span(data-i18n="courses.course")
|
||||||
span= data.courseNumber
|
span= ' ' + data.courseNumber + ', '
|
||||||
span.spr , Level
|
span(data-i18n="play_level.level")
|
||||||
span= data.levelNumber
|
span= ' ' + data.levelNumber + ': '
|
||||||
span.spr :
|
|
||||||
span= data.levelName
|
span= data.levelName
|
||||||
else
|
else
|
||||||
div.level-name(data-i18n='teacher.not_applicable')
|
div.level-name(data-i18n='teacher.not_applicable')
|
||||||
|
@ -223,6 +228,8 @@ mixin studentRow(student)
|
||||||
+longLevelName(student.latestCompleteLevel)
|
+longLevelName(student.latestCompleteLevel)
|
||||||
td
|
td
|
||||||
if state.get('progressData')
|
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')
|
each trimCourse, index in view.classroom.get('courses')
|
||||||
- var course = view.courses.get(trimCourse._id);
|
- var course = view.courses.get(trimCourse._id);
|
||||||
- var instance = view.courseInstances.findWhere({ courseID: course.id, classroomID: classroom.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 progress = state.get('progressData').get({ classroom: view.classroom, course: course, user: student })
|
||||||
- var levelsTotal = trimCourse.levels.length
|
- var levelsTotal = trimCourse.levels.length
|
||||||
//- - var level = ???
|
//- - var level = ???
|
||||||
+studentCourseProgressDot(progress, levelsTotal, level, 'CS' + (index+1))
|
- var label = courseLabelsArray[index];
|
||||||
|
+studentCourseProgressDot(progress, levelsTotal, level, label)
|
||||||
unless student.isEnrolled()
|
unless student.isEnrolled()
|
||||||
+enrollStudentButton(student)
|
+enrollStudentButton(student)
|
||||||
//- td
|
//- td
|
||||||
|
@ -305,7 +313,7 @@ mixin courseOverview
|
||||||
.course-overview-row
|
.course-overview-row
|
||||||
.course-title.student-name
|
.course-title.student-name
|
||||||
span= course.get('name')
|
span= course.get('name')
|
||||||
span :
|
span= ': '
|
||||||
span(data-i18n='teacher.course_overview')
|
span(data-i18n='teacher.course_overview')
|
||||||
.course-overview-progress
|
.course-overview-progress
|
||||||
each level, index in levels
|
each level, index in levels
|
||||||
|
@ -324,7 +332,7 @@ mixin studentLevelsRow(student)
|
||||||
each level, index in levels
|
each level, index in levels
|
||||||
- var progress = state.get('progressData').get({ classroom: view.classroom, course: course, level: level, user: student })
|
- 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)
|
- 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)
|
mixin studentCourseProgressDot(progress, levelsTotal, level, label)
|
||||||
//- TODO: Refactor with TeacherClassesView jade
|
//- TODO: Refactor with TeacherClassesView jade
|
||||||
|
@ -336,7 +344,7 @@ mixin studentCourseProgressDot(progress, levelsTotal, level, label)
|
||||||
|
|
||||||
mixin allStudentsLevelProgressDot(progress, level, levelNumber)
|
mixin allStudentsLevelProgressDot(progress, level, levelNumber)
|
||||||
- dotClass = progress.completed ? 'forest' : (progress.started ? 'gold' : '');
|
- 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 })
|
- 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))
|
.progress-dot.level-progress-dot(class=dotClass, data-html='true', data-title=view.allStudentsLevelProgressDotTemplate(context))
|
||||||
+progressDotLabel(levelNumber)
|
+progressDotLabel(levelNumber)
|
||||||
|
@ -344,7 +352,7 @@ mixin allStudentsLevelProgressDot(progress, level, levelNumber)
|
||||||
mixin studentLevelProgressDot(progress, level, levelNumber)
|
mixin studentLevelProgressDot(progress, level, levelNumber)
|
||||||
//- TODO: Refactor with TeacherClassesView jade
|
//- TODO: Refactor with TeacherClassesView jade
|
||||||
- dotClass = progress.completed ? 'forest' : (progress.started ? 'gold' : '');
|
- 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 })
|
- context = _.merge(progress, { levelName: levelName, levelNumber: levelNumber, moment: moment })
|
||||||
.progress-dot.level-progress-dot(class=dotClass, data-html='true', data-title=view.singleStudentLevelProgressDotTemplate(context))
|
.progress-dot.level-progress-dot(class=dotClass, data-html='true', data-title=view.singleStudentLevelProgressDotTemplate(context))
|
||||||
+progressDotLabel(levelNumber)
|
+progressDotLabel(levelNumber)
|
||||||
|
@ -430,3 +438,37 @@ mixin enrollmentStatusTab
|
||||||
td.enroll-col
|
td.enroll-col
|
||||||
if status !== 'enrolled'
|
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")
|
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
|
if classroom.get('members').length == 0
|
||||||
+addStudentsButton(classroom)
|
+addStudentsButton(classroom)
|
||||||
else
|
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') || []
|
each trimCourse, index in classroom.get('courses') || []
|
||||||
- var course = view.courses.get(trimCourse._id);
|
- var course = view.courses.get(trimCourse._id);
|
||||||
if view.courseInstances.findWhere({ classroomID: classroom.id, courseID: course.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
|
.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")
|
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')
|
a.create-classroom-btn.btn.btn-lg.btn-primary(data-i18n='teacher.create_new_class')
|
||||||
| Create a New Class
|
| Create a New Class
|
||||||
|
|
||||||
mixin progressDot(classroom, course, index)
|
mixin progressDot(classroom, course, label)
|
||||||
//- TODO: Give classes abbreviations instead of using index?
|
|
||||||
//- TODO: inefficient. Cache this in the view?
|
//- TODO: inefficient. Cache this in the view?
|
||||||
- courseInstance = view.courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
- courseInstance = view.courseInstances.findWhere({ courseID: course.id, classroomID: classroom.id })
|
||||||
- var total = classroom.get('members').length
|
- var total = classroom.get('members').length
|
||||||
|
@ -113,14 +115,11 @@ mixin progressDot(classroom, course, index)
|
||||||
- dotClass = complete === total ? 'forest' : started ? 'gold' : '';
|
- dotClass = complete === total ? 'forest' : started ? 'gold' : '';
|
||||||
- var progressDotContext = {total: total, complete: complete};
|
- var progressDotContext = {total: total, complete: complete};
|
||||||
.progress-dot(class=dotClass, data-title=view.progressDotTemplate(progressDotContext))
|
.progress-dot(class=dotClass, data-title=view.progressDotTemplate(progressDotContext))
|
||||||
+progressDotLabel(index)
|
+progressDotLabel(label)
|
||||||
|
|
||||||
mixin progressDotLabel(index)
|
mixin progressDotLabel(label)
|
||||||
.dot-label
|
.dot-label
|
||||||
.text-h6
|
.text-h6= label
|
||||||
| CS
|
|
||||||
span
|
|
||||||
= index + 1
|
|
||||||
|
|
||||||
mixin archivedClassRow(classroom)
|
mixin archivedClassRow(classroom)
|
||||||
.class.row
|
.class.row
|
||||||
|
|
|
@ -64,7 +64,7 @@ block header
|
||||||
a
|
a
|
||||||
span.glyphicon-floppy-disk.glyphicon
|
span.glyphicon-floppy-disk.glyphicon
|
||||||
|
|
||||||
if level.get('type') === 'ladder'
|
if level.isType('ladder')
|
||||||
li.dropdown
|
li.dropdown
|
||||||
a(data-toggle='dropdown').play-with-team-parent
|
a(data-toggle='dropdown').play-with-team-parent
|
||||||
span.glyphicon-play.glyphicon
|
span.glyphicon-play.glyphicon
|
||||||
|
|
|
@ -5,7 +5,7 @@ block modal-header-content
|
||||||
|
|
||||||
block modal-body-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!
|
h4.language-selection(data-i18n="ladder.select_your_language") Select your language!
|
||||||
.form-group.select-group
|
.form-group.select-group
|
||||||
select#tome-language(name="language")
|
select#tome-language(name="language")
|
||||||
|
|
|
@ -7,24 +7,15 @@
|
||||||
.levels-link-area
|
.levels-link-area
|
||||||
a.levels-link(href=homeLink || "/")
|
a.levels-link(href=homeLink || "/")
|
||||||
.glyphicon.glyphicon-play
|
.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
|
.level-name-area-container
|
||||||
.multiplayer-area-container
|
.level-name-area
|
||||||
.multiplayer-area
|
.level-label(data-i18n="play_level.level")
|
||||||
.multiplayer-label(data-i18n="play_level.control_bar_multiplayer")
|
.level-name(title=difficultyTitle || "")
|
||||||
if multiplayerStatus
|
span #{view.levelNumber ? view.levelNumber + '. ' : ''}#{worldName.replace('Course: ', '')}
|
||||||
.multiplayer-status= multiplayerStatus
|
if levelDifficulty
|
||||||
else
|
sup.level-difficulty= levelDifficulty
|
||||||
.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
|
|
||||||
|
|
||||||
.buttons-area
|
.buttons-area
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ block modal-body-content
|
||||||
textarea(data-i18n="[placeholder]play_level.victory_review_placeholder")
|
textarea(data-i18n="[placeholder]play_level.victory_review_placeholder")
|
||||||
.clearfix
|
.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
|
for achievement in achievements
|
||||||
- var animate = achievement.completed && !achievement.completedAWhileAgo
|
- var animate = achievement.completed && !achievement.completedAWhileAgo
|
||||||
.achievement-panel(class=achievement.completedAWhileAgo ? 'earned' : '' data-achievement-id=achievement.id data-animate=animate)
|
.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-count#gem-total 0
|
||||||
.total-label(data-i18n="play_level.victory_gems_gained") Gems Gained
|
.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')
|
if me.get('anonymous')
|
||||||
.sign-up-poke.hide
|
.sign-up-poke.hide
|
||||||
.sign-up-blurb(data-i18n="play_level.victory_sign_up_poke") Want to save your code? Create a free account!
|
.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
|
.next-level-buttons
|
||||||
if readyToRank
|
if readyToRank
|
||||||
.ladder-submission-view
|
.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
|
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
|
else
|
||||||
button.btn.btn-illustrated.btn-success.btn-lg.world-map-button.next-level-button.hide#continue-button(data-i18n="common.continue") Continue
|
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 :
|
span :
|
||||||
h2.text-uppercase= i18n(view.nextLevel.attributes, 'name').replace('Course: ', '')
|
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
|
.row
|
||||||
.col-sm-5.col-sm-offset-2
|
.col-sm-5.col-sm-offset-2
|
||||||
// TODO: Add this and rest of campaign functionality
|
// TODO: Add rest of campaign functionality
|
||||||
// button#continue-btn.btn.btn-illustrated.btn-default.btn-block.btn-lg.text-uppercase View Leaderboards
|
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
|
.col-sm-5
|
||||||
if !view.nextLevel.isNew()
|
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')
|
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
|
block modal-footer-content
|
||||||
if readyToRank
|
if readyToRank
|
||||||
.ladder-submission-view
|
.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
|
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
|
else
|
||||||
a.btn.btn-primary(href="/", data-dismiss="modal", data-i18n="play_level.victory_go_home") Go Home
|
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-2
|
||||||
.hinge.hinge-3
|
.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
|
.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
|
.glyphicon.glyphicon-repeat
|
||||||
span.spl(data-i18n="play_level.reload") Reload
|
span.spl(data-i18n="play_level.restart")
|
||||||
|
|
||||||
if me.level() >= 15
|
if me.level() >= 15
|
||||||
.btn.btn-small.btn-illustrated.fullscreen-code(title=maximizeShortcutVerbose)
|
.btn.btn-small.btn-illustrated.fullscreen-code(title=maximizeShortcutVerbose)
|
||||||
|
@ -27,4 +21,17 @@ if includeSpellList
|
||||||
.btn.btn-small.btn-illustrated.hints-button
|
.btn.btn-small.btn-illustrated.hints-button
|
||||||
span(data-i18n="play_level.hints")
|
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
|
.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'
|
else if language == 'io'
|
||||||
span= (doc.ownerName == 'this' ? '' : doc.ownerName + ' ') + docName + '(' + argumentExamples.join(', ') + ')'
|
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
|
p.value
|
||||||
strong
|
strong
|
||||||
span(data-i18n="skill_docs.current_value") Current Value
|
span(data-i18n="skill_docs.current_value") Current Value
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
#spell-list-tab-entry-view
|
#spell-top-bar-view
|
||||||
|
|
||||||
#spell-list-view
|
|
||||||
|
|
||||||
#cast-button-view
|
#cast-button-view
|
||||||
|
|
||||||
#spell-view
|
#spell-view
|
||||||
|
|
||||||
#spell-palette-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-wrapper
|
||||||
canvas(width=924, height=589)#webgl-surface
|
canvas(width=924, height=589)#webgl-surface
|
||||||
canvas(width=924, height=589)#normal-surface
|
canvas(width=924, height=589)#normal-surface
|
||||||
|
|
||||||
|
#web-surface-view
|
||||||
#ascii-surface
|
#ascii-surface
|
||||||
#canvas-left-gradient.gradient
|
#canvas-left-gradient.gradient
|
||||||
#canvas-top-gradient.gradient
|
#canvas-top-gradient.gradient
|
||||||
|
|
|
@ -9,6 +9,7 @@ Campaigns = require 'collections/Campaigns'
|
||||||
Classroom = require 'models/Classroom'
|
Classroom = require 'models/Classroom'
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
Course = require 'models/Course'
|
Course = require 'models/Course'
|
||||||
|
Courses = require 'collections/Courses'
|
||||||
LevelSessions = require 'collections/LevelSessions'
|
LevelSessions = require 'collections/LevelSessions'
|
||||||
User = require 'models/User'
|
User = require 'models/User'
|
||||||
Users = require 'collections/Users'
|
Users = require 'collections/Users'
|
||||||
|
@ -152,6 +153,7 @@ module.exports = class MainAdminView extends RootView
|
||||||
$('.classroom-progress-csv').prop('disabled', true)
|
$('.classroom-progress-csv').prop('disabled', true)
|
||||||
classCode = $('.classroom-progress-class-code').val()
|
classCode = $('.classroom-progress-class-code').val()
|
||||||
classroom = null
|
classroom = null
|
||||||
|
courses = null
|
||||||
courseLevels = []
|
courseLevels = []
|
||||||
sessions = null
|
sessions = null
|
||||||
users = null
|
users = null
|
||||||
|
@ -161,12 +163,16 @@ module.exports = class MainAdminView extends RootView
|
||||||
classroom = new Classroom({ _id: model.data._id })
|
classroom = new Classroom({ _id: model.data._id })
|
||||||
Promise.resolve(classroom.fetch())
|
Promise.resolve(classroom.fetch())
|
||||||
.then (model) =>
|
.then (model) =>
|
||||||
|
courses = new Courses()
|
||||||
|
Promise.resolve(courses.fetch())
|
||||||
|
.then (models) =>
|
||||||
for course, index in classroom.get('courses')
|
for course, index in classroom.get('courses')
|
||||||
for level in course.levels
|
for level in course.levels
|
||||||
courseLevels.push
|
courseLevels.push
|
||||||
courseIndex: index + 1
|
courseIndex: index + 1
|
||||||
levelID: level.original
|
levelID: level.original
|
||||||
slug: level.slug
|
slug: level.slug
|
||||||
|
courseSlug: courses.get(course._id).get('slug')
|
||||||
users = new Users()
|
users = new Users()
|
||||||
Promise.resolve($.when(users.fetchForClassroom(classroom)...))
|
Promise.resolve($.when(users.fetchForClassroom(classroom)...))
|
||||||
.then (models) =>
|
.then (models) =>
|
||||||
|
@ -202,12 +208,19 @@ module.exports = class MainAdminView extends RootView
|
||||||
|
|
||||||
columnLabels = "Username"
|
columnLabels = "Username"
|
||||||
currentLevel = 1
|
currentLevel = 1
|
||||||
|
courseLabelIndexes = CS: 1, GD: 0, WD: 0
|
||||||
lastCourseIndex = 1
|
lastCourseIndex = 1
|
||||||
|
lastCourseLabel = 'CS1'
|
||||||
for level in courseLevels
|
for level in courseLevels
|
||||||
unless level.courseIndex is lastCourseIndex
|
unless level.courseIndex is lastCourseIndex
|
||||||
currentLevel = 1
|
currentLevel = 1
|
||||||
lastCourseIndex = level.courseIndex
|
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"
|
csvContent = "data:text/csv;charset=utf-8,#{columnLabels}\n"
|
||||||
for studentRow in userPlaytimes
|
for studentRow in userPlaytimes
|
||||||
csvContent += studentRow.join(',') + "\n"
|
csvContent += studentRow.join(',') + "\n"
|
||||||
|
|
|
@ -195,7 +195,7 @@ module.exports = class ClanDetailsView extends RootView
|
||||||
if level.concepts?
|
if level.concepts?
|
||||||
for concept in level.concepts
|
for concept in level.concepts
|
||||||
@conceptsProgression.push concept unless concept in @conceptsProgression
|
@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
|
@arenas.push level
|
||||||
@campaignLevelProgressions.push campaignLevelProgression
|
@campaignLevelProgressions.push campaignLevelProgression
|
||||||
@render?()
|
@render?()
|
||||||
|
|
|
@ -496,6 +496,13 @@ module.exports = class CocoView extends Backbone.View
|
||||||
playSound: (trigger, volume=1) ->
|
playSound: (trigger, volume=1) ->
|
||||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: trigger, volume: volume
|
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
|
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
|
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())
|
@supermodel.trackRequest(@classroom.fetch())
|
||||||
|
|
||||||
levelsLoaded = @supermodel.trackRequest(@levels.fetchForClassroomAndCourse(classroomID, @courseID, {
|
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(=>
|
@supermodel.trackRequest($.when(levelsLoaded, sessionsLoaded).then(=>
|
||||||
|
@ -62,7 +62,7 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
# need to figure out the next course instance
|
# need to figure out the next course instance
|
||||||
@courseComplete = true
|
@courseComplete = true
|
||||||
@courseInstances.comparator = 'courseID'
|
@courseInstances.comparator = 'courseID'
|
||||||
# TODO: make this logic use locked course content to figure out the next course, then fetch the
|
# TODO: make this logic use locked course content to figure out the next course, then fetch the
|
||||||
# course instance for that
|
# course instance for that
|
||||||
@supermodel.trackRequest(@courseInstances.fetchForClassroom(classroomID).then(=>
|
@supermodel.trackRequest(@courseInstances.fetchForClassroom(classroomID).then(=>
|
||||||
@nextCourseInstance = _.find @courseInstances.models, (ci) => ci.get('courseID') > @courseID
|
@nextCourseInstance = _.find @courseInstances.models, (ci) => ci.get('courseID') > @courseID
|
||||||
|
@ -84,9 +84,9 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
@levelConceptMap = {}
|
@levelConceptMap = {}
|
||||||
for level in @levels.models
|
for level in @levels.models
|
||||||
@levelConceptMap[level.get('original')] ?= {}
|
@levelConceptMap[level.get('original')] ?= {}
|
||||||
for concept in level.get('concepts')
|
for concept in level.get('concepts') or []
|
||||||
@levelConceptMap[level.get('original')][concept] = true
|
@levelConceptMap[level.get('original')][concept] = true
|
||||||
if level.get('type') is 'course-ladder'
|
if level.isType('course-ladder')
|
||||||
@arenaLevel = level
|
@arenaLevel = level
|
||||||
|
|
||||||
# console.log 'onLevelSessionsSync'
|
# console.log 'onLevelSessionsSync'
|
||||||
|
@ -124,13 +124,13 @@ module.exports = class CourseDetailsView extends RootView
|
||||||
for concept, state of conceptStateMap
|
for concept, state of conceptStateMap
|
||||||
@conceptsCompleted[concept] ?= 0
|
@conceptsCompleted[concept] ?= 0
|
||||||
@conceptsCompleted[concept]++
|
@conceptsCompleted[concept]++
|
||||||
|
|
||||||
onClickPlayLevel: (e) ->
|
onClickPlayLevel: (e) ->
|
||||||
levelSlug = $(e.target).closest('.btn-play-level').data('level-slug')
|
levelSlug = $(e.target).closest('.btn-play-level').data('level-slug')
|
||||||
levelID = $(e.target).closest('.btn-play-level').data('level-id')
|
levelID = $(e.target).closest('.btn-play-level').data('level-id')
|
||||||
level = @levels.findWhere({original: levelID})
|
level = @levels.findWhere({original: levelID})
|
||||||
window.tracker?.trackEvent 'Students Class Course Play Level', category: 'Students', courseID: @courseID, courseInstanceID: @courseInstanceID, levelSlug: levelSlug, ['Mixpanel']
|
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'
|
viewClass = 'views/ladder/LadderView'
|
||||||
viewArgs = [{supermodel: @supermodel}, levelSlug]
|
viewArgs = [{supermodel: @supermodel}, levelSlug]
|
||||||
route = '/play/ladder/' + levelSlug
|
route = '/play/ladder/' + levelSlug
|
||||||
|
|
|
@ -23,6 +23,7 @@ CourseInstances = require 'collections/CourseInstances'
|
||||||
module.exports = class TeacherClassView extends RootView
|
module.exports = class TeacherClassView extends RootView
|
||||||
id: 'teacher-class-view'
|
id: 'teacher-class-view'
|
||||||
template: template
|
template: template
|
||||||
|
helper: helper
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click .nav-tabs a': 'onClickNavTabLink'
|
'click .nav-tabs a': 'onClickNavTabLink'
|
||||||
|
@ -43,7 +44,7 @@ module.exports = class TeacherClassView extends RootView
|
||||||
'click .student-checkbox': 'onClickStudentCheckbox'
|
'click .student-checkbox': 'onClickStudentCheckbox'
|
||||||
'keyup #student-search': 'onKeyPressStudentSearch'
|
'keyup #student-search': 'onKeyPressStudentSearch'
|
||||||
'change .course-select, .bulk-course-select': 'onChangeCourseSelect'
|
'change .course-select, .bulk-course-select': 'onChangeCourseSelect'
|
||||||
|
|
||||||
getInitialState: ->
|
getInitialState: ->
|
||||||
{
|
{
|
||||||
sortAttribute: 'name'
|
sortAttribute: 'name'
|
||||||
|
@ -72,21 +73,21 @@ module.exports = class TeacherClassView extends RootView
|
||||||
@singleStudentCourseProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-single-student-course'
|
@singleStudentCourseProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-single-student-course'
|
||||||
@singleStudentLevelProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-single-student-level'
|
@singleStudentLevelProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-single-student-level'
|
||||||
@allStudentsLevelProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-all-students-single-level'
|
@allStudentsLevelProgressDotTemplate = require 'templates/teachers/hovers/progress-dot-all-students-single-level'
|
||||||
|
|
||||||
@debouncedRender = _.debounce @render
|
@debouncedRender = _.debounce @render
|
||||||
|
|
||||||
@state = new State(@getInitialState())
|
@state = new State(@getInitialState())
|
||||||
@updateHash @state.get('activeTab') # TODO: Don't push to URL history (maybe don't use url fragment for default tab)
|
@updateHash @state.get('activeTab') # TODO: Don't push to URL history (maybe don't use url fragment for default tab)
|
||||||
|
|
||||||
@classroom = new Classroom({ _id: classroomID })
|
@classroom = new Classroom({ _id: classroomID })
|
||||||
@supermodel.trackRequest @classroom.fetch()
|
@supermodel.trackRequest @classroom.fetch()
|
||||||
@onKeyPressStudentSearch = _.debounce(@onKeyPressStudentSearch, 200)
|
@onKeyPressStudentSearch = _.debounce(@onKeyPressStudentSearch, 200)
|
||||||
|
|
||||||
@students = new Users()
|
@students = new Users()
|
||||||
@listenTo @classroom, 'sync', ->
|
@listenTo @classroom, 'sync', ->
|
||||||
jqxhrs = @students.fetchForClassroom(@classroom, removeDeleted: true)
|
jqxhrs = @students.fetchForClassroom(@classroom, removeDeleted: true)
|
||||||
@supermodel.trackRequests jqxhrs
|
@supermodel.trackRequests jqxhrs
|
||||||
|
|
||||||
@classroom.sessions = new LevelSessions()
|
@classroom.sessions = new LevelSessions()
|
||||||
requests = @classroom.sessions.fetchForAllClassroomMembers(@classroom)
|
requests = @classroom.sessions.fetchForAllClassroomMembers(@classroom)
|
||||||
@supermodel.trackRequests(requests)
|
@supermodel.trackRequests(requests)
|
||||||
|
@ -96,7 +97,7 @@ module.exports = class TeacherClassView extends RootView
|
||||||
value = @state.get('sortValue')
|
value = @state.get('sortValue')
|
||||||
if value is 'name'
|
if value is 'name'
|
||||||
return (if student1.broadName().toLowerCase() < student2.broadName().toLowerCase() then -dir else dir)
|
return (if student1.broadName().toLowerCase() < student2.broadName().toLowerCase() then -dir else dir)
|
||||||
|
|
||||||
if value is 'progress'
|
if value is 'progress'
|
||||||
# TODO: I would like for this to be in the Level model,
|
# TODO: I would like for this to be in the Level model,
|
||||||
# but it doesn't know about its own courseNumber.
|
# but it doesn't know about its own courseNumber.
|
||||||
|
@ -105,7 +106,7 @@ module.exports = class TeacherClassView extends RootView
|
||||||
return -dir if not level1
|
return -dir if not level1
|
||||||
return dir if not level2
|
return dir if not level2
|
||||||
return dir * (level1.courseNumber - level2.courseNumber or level1.levelNumber - level2.levelNumber)
|
return dir * (level1.courseNumber - level2.courseNumber or level1.levelNumber - level2.levelNumber)
|
||||||
|
|
||||||
if value is 'status'
|
if value is 'status'
|
||||||
statusMap = { expired: 0, 'not-enrolled': 1, enrolled: 2 }
|
statusMap = { expired: 0, 'not-enrolled': 1, enrolled: 2 }
|
||||||
diff = statusMap[student1.prepaidStatus()] - statusMap[student2.prepaidStatus()]
|
diff = statusMap[student1.prepaidStatus()] - statusMap[student2.prepaidStatus()]
|
||||||
|
@ -114,13 +115,13 @@ module.exports = class TeacherClassView extends RootView
|
||||||
|
|
||||||
@courses = new Courses()
|
@courses = new Courses()
|
||||||
@supermodel.trackRequest @courses.fetch()
|
@supermodel.trackRequest @courses.fetch()
|
||||||
|
|
||||||
@courseInstances = new CourseInstances()
|
@courseInstances = new CourseInstances()
|
||||||
@supermodel.trackRequest @courseInstances.fetchForClassroom(classroomID)
|
@supermodel.trackRequest @courseInstances.fetchForClassroom(classroomID)
|
||||||
|
|
||||||
@levels = new Levels()
|
@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()
|
@attachMediatorEvents()
|
||||||
window.tracker?.trackEvent 'Teachers Class Loaded', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
window.tracker?.trackEvent 'Teachers Class Loaded', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
||||||
|
|
||||||
|
@ -160,11 +161,11 @@ module.exports = class TeacherClassView extends RootView
|
||||||
course.instance = @courseInstances.findWhere({ courseID: course.id, classroomID: @classroom.id })
|
course.instance = @courseInstances.findWhere({ courseID: course.id, classroomID: @classroom.id })
|
||||||
course.members = course.instance?.get('members') or []
|
course.members = course.instance?.get('members') or []
|
||||||
null
|
null
|
||||||
|
|
||||||
onLoaded: ->
|
onLoaded: ->
|
||||||
@removeDeletedStudents() # TODO: Move this to mediator listeners? For both classroom and students?
|
@removeDeletedStudents() # TODO: Move this to mediator listeners? For both classroom and students?
|
||||||
@calculateProgressAndLevels()
|
@calculateProgressAndLevels()
|
||||||
|
|
||||||
# render callback setup
|
# render callback setup
|
||||||
@listenTo @courseInstances, 'sync change update', @debouncedRender
|
@listenTo @courseInstances, 'sync change update', @debouncedRender
|
||||||
@listenTo @state, 'sync change', ->
|
@listenTo @state, 'sync change', ->
|
||||||
|
@ -174,17 +175,17 @@ module.exports = class TeacherClassView extends RootView
|
||||||
@debouncedRender()
|
@debouncedRender()
|
||||||
@listenTo @students, 'sort', @debouncedRender
|
@listenTo @students, 'sort', @debouncedRender
|
||||||
super()
|
super()
|
||||||
|
|
||||||
afterRender: ->
|
afterRender: ->
|
||||||
super(arguments...)
|
super(arguments...)
|
||||||
$('.progress-dot').each (i, el) ->
|
$('.progress-dot, .btn-view-project-level').each (i, el) ->
|
||||||
dot = $(el)
|
dot = $(el)
|
||||||
dot.tooltip({
|
dot.tooltip({
|
||||||
html: true
|
html: true
|
||||||
container: dot
|
container: dot
|
||||||
}).delegate '.tooltip', 'mousemove', ->
|
}).delegate '.tooltip', 'mousemove', ->
|
||||||
dot.tooltip('hide')
|
dot.tooltip('hide')
|
||||||
|
|
||||||
calculateProgressAndLevels: ->
|
calculateProgressAndLevels: ->
|
||||||
return unless @supermodel.progress is 1
|
return unless @supermodel.progress is 1
|
||||||
# TODO: How to structure this in @state?
|
# TODO: How to structure this in @state?
|
||||||
|
@ -192,14 +193,14 @@ module.exports = class TeacherClassView extends RootView
|
||||||
# TODO: this is a weird hack
|
# TODO: this is a weird hack
|
||||||
studentsStub = new Users([ student ])
|
studentsStub = new Users([ student ])
|
||||||
student.latestCompleteLevel = helper.calculateLatestComplete(@classroom, @courses, @courseInstances, studentsStub)
|
student.latestCompleteLevel = helper.calculateLatestComplete(@classroom, @courses, @courseInstances, studentsStub)
|
||||||
|
|
||||||
earliestIncompleteLevel = helper.calculateEarliestIncomplete(@classroom, @courses, @courseInstances, @students)
|
earliestIncompleteLevel = helper.calculateEarliestIncomplete(@classroom, @courses, @courseInstances, @students)
|
||||||
latestCompleteLevel = helper.calculateLatestComplete(@classroom, @courses, @courseInstances, @students)
|
latestCompleteLevel = helper.calculateLatestComplete(@classroom, @courses, @courseInstances, @students)
|
||||||
|
|
||||||
classroomsStub = new Classrooms([ @classroom ])
|
classroomsStub = new Classrooms([ @classroom ])
|
||||||
progressData = helper.calculateAllProgress(classroomsStub, @courses, @courseInstances, @students)
|
progressData = helper.calculateAllProgress(classroomsStub, @courses, @courseInstances, @students)
|
||||||
# conceptData: helper.calculateConceptsCovered(classroomsStub, @courses, @campaigns, @courseInstances, @students)
|
# conceptData: helper.calculateConceptsCovered(classroomsStub, @courses, @campaigns, @courseInstances, @students)
|
||||||
|
|
||||||
@state.set {
|
@state.set {
|
||||||
earliestIncompleteLevel
|
earliestIncompleteLevel
|
||||||
latestCompleteLevel
|
latestCompleteLevel
|
||||||
|
@ -212,7 +213,7 @@ module.exports = class TeacherClassView extends RootView
|
||||||
hash = $(e.target).closest('a').attr('href')
|
hash = $(e.target).closest('a').attr('href')
|
||||||
@updateHash(hash)
|
@updateHash(hash)
|
||||||
@state.set activeTab: hash
|
@state.set activeTab: hash
|
||||||
|
|
||||||
updateHash: (hash) ->
|
updateHash: (hash) ->
|
||||||
return if application.testing
|
return if application.testing
|
||||||
window.location.hash = hash
|
window.location.hash = hash
|
||||||
|
@ -227,17 +228,10 @@ module.exports = class TeacherClassView extends RootView
|
||||||
@$('#join-url-input').val(@state.get('joinURL')).select()
|
@$('#join-url-input').val(@state.get('joinURL')).select()
|
||||||
@tryCopy()
|
@tryCopy()
|
||||||
|
|
||||||
tryCopy: ->
|
|
||||||
try
|
|
||||||
document.execCommand('copy')
|
|
||||||
catch err
|
|
||||||
message = 'Oops, unable to copy'
|
|
||||||
noty text: message, layout: 'topCenter', type: 'error', killer: false
|
|
||||||
|
|
||||||
onClickUnarchive: ->
|
onClickUnarchive: ->
|
||||||
window.tracker?.trackEvent 'Teachers Class Unarchive', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
window.tracker?.trackEvent 'Teachers Class Unarchive', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
||||||
@classroom.save { archived: false }
|
@classroom.save { archived: false }
|
||||||
|
|
||||||
onClickEditClassroom: (e) ->
|
onClickEditClassroom: (e) ->
|
||||||
window.tracker?.trackEvent 'Teachers Class Edit Class Started', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
window.tracker?.trackEvent 'Teachers Class Edit Class Started', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
||||||
classroom = @classroom
|
classroom = @classroom
|
||||||
|
@ -328,9 +322,11 @@ module.exports = class TeacherClassView extends RootView
|
||||||
window.tracker?.trackEvent 'Teachers Class Export CSV', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
window.tracker?.trackEvent 'Teachers Class Export CSV', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel']
|
||||||
courseLabels = ""
|
courseLabels = ""
|
||||||
courseOrder = []
|
courseOrder = []
|
||||||
for course, index in @classroom.get('courses')
|
courses = (@courses.get(c._id) for c in @classroom.get('courses'))
|
||||||
courseLabels += "CS#{index + 1} Playtime,"
|
courseLabelsArray = helper.courseLabelsArray courses
|
||||||
courseOrder.push(course._id)
|
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"
|
csvContent = "data:text/csv;charset=utf-8,Username,Email,Total Playtime,#{courseLabels}Concepts\n"
|
||||||
levelCourseMap = {}
|
levelCourseMap = {}
|
||||||
for trimCourse in @classroom.get('courses')
|
for trimCourse in @classroom.get('courses')
|
||||||
|
@ -396,6 +392,7 @@ module.exports = class TeacherClassView extends RootView
|
||||||
not @students.get(userID).isEnrolled()
|
not @students.get(userID).isEnrolled()
|
||||||
assigningToNobody = selectedIDs.length is 0
|
assigningToNobody = selectedIDs.length is 0
|
||||||
@state.set errors: { assigningToNobody, assigningToUnenrolled }
|
@state.set errors: { assigningToNobody, assigningToUnenrolled }
|
||||||
|
return if assigningToNobody
|
||||||
@assignCourse courseID, members
|
@assignCourse courseID, members
|
||||||
window.tracker?.trackEvent 'Teachers Class Students Assign Selected', category: 'Teachers', classroomID: @classroom.id, courseID: courseID, ['Mixpanel']
|
window.tracker?.trackEvent 'Teachers Class Students Assign Selected', category: 'Teachers', classroomID: @classroom.id, courseID: courseID, ['Mixpanel']
|
||||||
|
|
||||||
|
@ -459,7 +456,7 @@ module.exports = class TeacherClassView extends RootView
|
||||||
|
|
||||||
enrolledUsers = @students.filter (user) -> user.isEnrolled()
|
enrolledUsers = @students.filter (user) -> user.isEnrolled()
|
||||||
stats.enrolledUsers = _.size(enrolledUsers)
|
stats.enrolledUsers = _.size(enrolledUsers)
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
studentStatusString: (student) ->
|
studentStatusString: (student) ->
|
||||||
|
|
|
@ -17,6 +17,7 @@ helper = require 'lib/coursesHelper'
|
||||||
module.exports = class TeacherClassesView extends RootView
|
module.exports = class TeacherClassesView extends RootView
|
||||||
id: 'teacher-classes-view'
|
id: 'teacher-classes-view'
|
||||||
template: template
|
template: template
|
||||||
|
helper: helper
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click .edit-classroom': 'onClickEditClassroom'
|
'click .edit-classroom': 'onClickEditClassroom'
|
||||||
|
|
|
@ -46,7 +46,7 @@ module.exports = class ThangComponentConfigView extends CocoView
|
||||||
schema.default ?= {}
|
schema.default ?= {}
|
||||||
_.merge schema.default, @additionalDefaults if @additionalDefaults
|
_.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 = []
|
schema.required = []
|
||||||
treemaOptions =
|
treemaOptions =
|
||||||
supermodel: @supermodel
|
supermodel: @supermodel
|
||||||
|
|
|
@ -37,6 +37,7 @@ require 'vendor/aether-python'
|
||||||
require 'vendor/aether-coffeescript'
|
require 'vendor/aether-coffeescript'
|
||||||
require 'vendor/aether-lua'
|
require 'vendor/aether-lua'
|
||||||
require 'vendor/aether-java'
|
require 'vendor/aether-java'
|
||||||
|
require 'vendor/aether-html'
|
||||||
|
|
||||||
module.exports = class LevelEditView extends RootView
|
module.exports = class LevelEditView extends RootView
|
||||||
id: 'editor-level-view'
|
id: 'editor-level-view'
|
||||||
|
|
|
@ -41,7 +41,7 @@ module.exports = class LevelThangEditView extends CocoView
|
||||||
level: @level
|
level: @level
|
||||||
world: @world
|
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
|
@thangComponentEditView = new ThangComponentsEditView options
|
||||||
@listenTo @thangComponentEditView, 'components-changed', @onComponentsChanged
|
@listenTo @thangComponentEditView, 'components-changed', @onComponentsChanged
|
||||||
|
|
|
@ -251,7 +251,7 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
@dragged = 0
|
@dragged = 0
|
||||||
@willUnselectSprite = false
|
@willUnselectSprite = false
|
||||||
@gameUIState.set('canDragCamera', true)
|
@gameUIState.set('canDragCamera', true)
|
||||||
|
|
||||||
if @addThangLank?.thangType.get('kind') is 'Wall'
|
if @addThangLank?.thangType.get('kind') is 'Wall'
|
||||||
@paintingWalls = true
|
@paintingWalls = true
|
||||||
@gameUIState.set('canDragCamera', false)
|
@gameUIState.set('canDragCamera', false)
|
||||||
|
@ -259,7 +259,7 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
else if @addThangLank
|
else if @addThangLank
|
||||||
# We clicked on the background when we had an add Thang selected, so add it
|
# We clicked on the background when we had an add Thang selected, so add it
|
||||||
@addThang @addThangType, @addThangLank.thang.pos
|
@addThang @addThangType, @addThangLank.thang.pos
|
||||||
|
|
||||||
else if e.onBackground
|
else if e.onBackground
|
||||||
@gameUIState.set('selected', [])
|
@gameUIState.set('selected', [])
|
||||||
|
|
||||||
|
@ -331,18 +331,18 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
@onSpriteContextMenu e
|
@onSpriteContextMenu e
|
||||||
clearInterval(@movementInterval) if @movementInterval?
|
clearInterval(@movementInterval) if @movementInterval?
|
||||||
@movementInterval = null
|
@movementInterval = null
|
||||||
|
|
||||||
return unless _.any(selected)
|
return unless _.any(selected)
|
||||||
|
|
||||||
for singleSelected in selected
|
for singleSelected in selected
|
||||||
pos = singleSelected.thang.pos
|
pos = singleSelected.thang.pos
|
||||||
|
|
||||||
thang = _.find(@level.get('thangs') ? [], {id: singleSelected.thang.id})
|
thang = _.find(@level.get('thangs') ? [], {id: singleSelected.thang.id})
|
||||||
path = "#{@pathForThang(thang)}/components/original=#{LevelComponent.PhysicalID}"
|
path = "#{@pathForThang(thang)}/components/original=#{LevelComponent.PhysicalID}"
|
||||||
physical = @thangsTreema.get path
|
physical = @thangsTreema.get path
|
||||||
continue if not physical or (physical.config.pos.x is pos.x and physical.config.pos.y is pos.y)
|
continue if not physical or (physical.config.pos.x is pos.x and physical.config.pos.y is pos.y)
|
||||||
@thangsTreema.set path + '/config/pos', x: pos.x, y: pos.y, z: pos.z
|
@thangsTreema.set path + '/config/pos', x: pos.x, y: pos.y, z: pos.z
|
||||||
|
|
||||||
if @willUnselectSprite
|
if @willUnselectSprite
|
||||||
clickedSprite = _.find(selected, {sprite: e.sprite})
|
clickedSprite = _.find(selected, {sprite: e.sprite})
|
||||||
@gameUIState.set('selected', _.without(selected, clickedSprite))
|
@gameUIState.set('selected', _.without(selected, clickedSprite))
|
||||||
|
@ -379,7 +379,7 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
thang = selected?.thang
|
thang = selected?.thang
|
||||||
|
|
||||||
previousSprite?.setNameLabel?(null) unless previousSprite is sprite
|
previousSprite?.setNameLabel?(null) unless previousSprite is sprite
|
||||||
|
|
||||||
if thang and not (@addThangLank and @addThangType.get('name') in overlappableThangTypeNames)
|
if thang and not (@addThangLank and @addThangType.get('name') in overlappableThangTypeNames)
|
||||||
# We clicked on a Thang (or its Treema), so select the Thang
|
# We clicked on a Thang (or its Treema), so select the Thang
|
||||||
@selectAddThang(null, true)
|
@selectAddThang(null, true)
|
||||||
|
@ -619,7 +619,7 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
onTreemaThangSelected: (e, selectedTreemas) =>
|
onTreemaThangSelected: (e, selectedTreemas) =>
|
||||||
selectedThangTreemas = _.filter(selectedTreemas, (t) -> t instanceof ThangNode)
|
selectedThangTreemas = _.filter(selectedTreemas, (t) -> t instanceof ThangNode)
|
||||||
thangIDs = (node.data.id for node in selectedThangTreemas)
|
thangIDs = (node.data.id for node in selectedThangTreemas)
|
||||||
lanks = (@surface.lankBoss.lanks[thangID] for thangID in thangIDs when thangID)
|
lanks = (@surface.lankBoss.lanks[thangID] for thangID in thangIDs when thangID)
|
||||||
selected = ({ thang: lank.thang, sprite: lank } for lank in lanks when lank)
|
selected = ({ thang: lank.thang, sprite: lank } for lank in lanks when lank)
|
||||||
@gameUIState.set('selected', selected)
|
@gameUIState.set('selected', selected)
|
||||||
|
|
||||||
|
@ -636,14 +636,14 @@ module.exports = class ThangsTabView extends CocoView
|
||||||
if batchInsert
|
if batchInsert
|
||||||
if thangType.get('name') is 'Hero Placeholder'
|
if thangType.get('name') is 'Hero Placeholder'
|
||||||
thangID = '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
|
else
|
||||||
thangID = "Random #{thangType.get('name')} #{@thangsBatch.length}"
|
thangID = "Random #{thangType.get('name')} #{@thangsBatch.length}"
|
||||||
else
|
else
|
||||||
thangID = Thang.nextID(thangType.get('name'), @world) until thangID and not @getThangByID(thangID)
|
thangID = Thang.nextID(thangType.get('name'), @world) until thangID and not @getThangByID(thangID)
|
||||||
if @cloneSourceThang
|
if @cloneSourceThang
|
||||||
components = _.cloneDeep @getThangByID(@cloneSourceThang.id).components
|
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
|
components = [] # Load them all from default ThangType Components
|
||||||
else
|
else
|
||||||
components = _.cloneDeep thangType.get('components') ? []
|
components = _.cloneDeep thangType.get('components') ? []
|
||||||
|
|
|
@ -78,7 +78,7 @@ module.exports = class VerifierTest extends CocoClass
|
||||||
@listenToOnce @god, 'infinite-loop', @fail
|
@listenToOnce @god, 'infinite-loop', @fail
|
||||||
@listenToOnce @god, 'user-code-problem', @onUserCodeProblem
|
@listenToOnce @god, 'user-code-problem', @onUserCodeProblem
|
||||||
@listenToOnce @god, 'goals-calculated', @processSingleGameResults
|
@listenToOnce @god, 'goals-calculated', @processSingleGameResults
|
||||||
@god.createWorld @generateSpellsObject()
|
@god.createWorld @session.generateSpellsObject()
|
||||||
@updateCallback? state: 'running'
|
@updateCallback? state: 'running'
|
||||||
|
|
||||||
processSingleGameResults: (e) ->
|
processSingleGameResults: (e) ->
|
||||||
|
@ -118,18 +118,6 @@ module.exports = class VerifierTest extends CocoClass
|
||||||
@updateCallback? state: @state
|
@updateCallback? state: @state
|
||||||
@scheduleCleanup()
|
@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: ->
|
scheduleCleanup: ->
|
||||||
setTimeout @cleanup, 100
|
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'
|
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}
|
@levelsByCampaign[campaign.get('slug')] ?= {levels: [], checked: true}
|
||||||
campaignInfo = @levelsByCampaign[campaign.get('slug')]
|
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
|
campaignInfo.levels.push level.slug
|
||||||
|
|
||||||
filterCodeLanguages: ->
|
filterCodeLanguages: ->
|
||||||
|
|
|
@ -26,8 +26,8 @@ module.exports = class LadderPlayModal extends ModalView
|
||||||
|
|
||||||
initialize: (options, @level, @session, @team) ->
|
initialize: (options, @level, @session, @team) ->
|
||||||
@otherTeam = if @team is 'ogres' then 'humans' else 'ogres'
|
@otherTeam = if @team is 'ogres' then 'humans' else 'ogres'
|
||||||
@startLoadingChallengersMaybe()
|
|
||||||
@wizardType = ThangType.loadUniversalWizard()
|
@wizardType = ThangType.loadUniversalWizard()
|
||||||
|
@startLoadingChallengersMaybe()
|
||||||
@levelID = @level.get('slug') or @level.id
|
@levelID = @level.get('slug') or @level.id
|
||||||
@language = @session?.get('codeLanguage') ? me.get('aceConfig')?.language ? 'python'
|
@language = @session?.get('codeLanguage') ? me.get('aceConfig')?.language ? 'python'
|
||||||
@languages = [
|
@languages = [
|
||||||
|
|
|
@ -21,7 +21,7 @@ module.exports = class MainLadderView extends RootView
|
||||||
@campaigns = campaigns
|
@campaigns = campaigns
|
||||||
|
|
||||||
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', {cache: false}, 0).model
|
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', {cache: false}, 0).model
|
||||||
@listenToOnce @sessions, 'sync', @onSessionsLoaded
|
@listenToOnce @sessions, 'sync', @onSessionsLoaded
|
||||||
|
|
||||||
@getLevelPlayCounts()
|
@getLevelPlayCounts()
|
||||||
|
|
||||||
|
@ -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 = [
|
campaigns = [
|
||||||
{id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: heroArenas}
|
{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: ->
|
onLoaded: ->
|
||||||
super()
|
super()
|
||||||
@render()
|
@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()
|
@startSimulating()
|
||||||
|
|
||||||
afterRender: ->
|
afterRender: ->
|
||||||
|
|
|
@ -397,8 +397,9 @@ module.exports = class CampaignView extends RootView
|
||||||
@particleMan.removeEmitters()
|
@particleMan.removeEmitters()
|
||||||
@particleMan.attach @$el.find('.map')
|
@particleMan.attach @$el.find('.map')
|
||||||
for level in @campaign.renderedLevels ? {}
|
for level in @campaign.renderedLevels ? {}
|
||||||
particleKey = ['level', @terrain.replace('-branching-test', '')]
|
terrain = @terrain.replace('-branching-test', '').replace(/(game|web)-dev-\d/, 'forest')
|
||||||
particleKey.push level.type if level.type and not (level.type in ['hero', 'course'])
|
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 'replayable' if level.replayable
|
||||||
particleKey.push 'premium' if level.requiresSubscription
|
particleKey.push 'premium' if level.requiresSubscription
|
||||||
particleKey.push 'gate' if level.slug in ['kithgard-gates', 'siege-of-stonehold', 'clash-of-clones', 'summits-gate']
|
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')
|
levelElement = $(e.target).parents('.level-info-container')
|
||||||
levelSlug = levelElement.data('level-slug')
|
levelSlug = levelElement.data('level-slug')
|
||||||
level = _.find _.values(@campaign.get('levels')), slug: levelSlug
|
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]
|
Backbone.Mediator.publish 'router:navigate', route: "/play/ladder/#{levelSlug}", viewClass: 'views/ladder/LadderView', viewArgs: [{supermodel: @supermodel}, levelSlug]
|
||||||
else
|
else
|
||||||
@showLeaderboard levelSlug
|
@showLeaderboard levelSlug
|
||||||
|
|
|
@ -144,9 +144,6 @@ module.exports = class SpectateLevelView extends RootView
|
||||||
if c then myCode[thang][spell] = c else delete myCode[thang][spell]
|
if c then myCode[thang][spell] = c else delete myCode[thang][spell]
|
||||||
|
|
||||||
@session.set('code', myCode)
|
@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) ->
|
onLevelStarted: (e) ->
|
||||||
go = =>
|
go = =>
|
||||||
|
@ -181,7 +178,7 @@ module.exports = class SpectateLevelView extends RootView
|
||||||
|
|
||||||
@insertSubView new GoldView {}
|
@insertSubView new GoldView {}
|
||||||
@insertSubView new HUDView {level: @level}
|
@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}
|
@insertSubView @controlBar = new ControlBarView {worldName: utils.i18n(@level.attributes, 'name'), session: @session, level: @level, supermodel: @supermodel, spectateGame: true}
|
||||||
|
|
||||||
# callbacks
|
# callbacks
|
||||||
|
|
|
@ -7,17 +7,13 @@ Classroom = require 'models/Classroom'
|
||||||
Course = require 'models/Course'
|
Course = require 'models/Course'
|
||||||
CourseInstance = require 'models/CourseInstance'
|
CourseInstance = require 'models/CourseInstance'
|
||||||
GameMenuModal = require 'views/play/menu/GameMenuModal'
|
GameMenuModal = require 'views/play/menu/GameMenuModal'
|
||||||
RealTimeModel = require 'models/RealTimeModel'
|
|
||||||
RealTimeCollection = require 'collections/RealTimeCollection'
|
|
||||||
LevelSetupManager = require 'lib/LevelSetupManager'
|
LevelSetupManager = require 'lib/LevelSetupManager'
|
||||||
GameMenuModal = require 'views/play/menu/GameMenuModal'
|
|
||||||
|
|
||||||
module.exports = class ControlBarView extends CocoView
|
module.exports = class ControlBarView extends CocoView
|
||||||
id: 'control-bar-view'
|
id: 'control-bar-view'
|
||||||
template: template
|
template: template
|
||||||
|
|
||||||
subscriptions:
|
subscriptions:
|
||||||
'bus:player-states-changed': 'onPlayerStatesChanged'
|
|
||||||
'level:disable-controls': 'onDisableControls'
|
'level:disable-controls': 'onDisableControls'
|
||||||
'level:enable-controls': 'onEnableControls'
|
'level:enable-controls': 'onEnableControls'
|
||||||
'ipad:memory-warning': 'onIPadMemoryWarning'
|
'ipad:memory-warning': 'onIPadMemoryWarning'
|
||||||
|
@ -28,7 +24,6 @@ module.exports = class ControlBarView extends CocoView
|
||||||
'click': -> Backbone.Mediator.publish 'tome:focus-editor', {}
|
'click': -> Backbone.Mediator.publish 'tome:focus-editor', {}
|
||||||
'click .levels-link-area': 'onClickHome'
|
'click .levels-link-area': 'onClickHome'
|
||||||
'click .home a': 'onClickHome'
|
'click .home a': 'onClickHome'
|
||||||
'click .multiplayer-area': 'onClickMultiplayer'
|
|
||||||
'click #control-bar-sign-up-button': 'onClickSignupButton'
|
'click #control-bar-sign-up-button': 'onClickSignupButton'
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
|
@ -45,7 +40,7 @@ module.exports = class ControlBarView extends CocoView
|
||||||
@observing = options.session.get('creator') isnt me.id
|
@observing = options.session.get('creator') isnt me.id
|
||||||
|
|
||||||
@levelNumber = ''
|
@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
|
@levelNumber = @level.get('campaignIndex') + 1
|
||||||
if @courseInstanceID
|
if @courseInstanceID
|
||||||
@courseInstance = new CourseInstance(_id: @courseInstanceID)
|
@courseInstance = new CourseInstance(_id: @courseInstanceID)
|
||||||
|
@ -64,9 +59,6 @@ module.exports = class ControlBarView extends CocoView
|
||||||
@supermodel.trackRequest(@campaign.fetch())
|
@supermodel.trackRequest(@campaign.fetch())
|
||||||
)
|
)
|
||||||
super options
|
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'
|
if @level.get 'replayable'
|
||||||
@listenTo @session, 'change-difficulty', @onSessionDifficultyChanged
|
@listenTo @session, 'change-difficulty', @onSessionDifficultyChanged
|
||||||
|
|
||||||
|
@ -79,25 +71,10 @@ module.exports = class ControlBarView extends CocoView
|
||||||
|
|
||||||
setBus: (@bus) ->
|
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={}) ->
|
getRenderData: (c={}) ->
|
||||||
super c
|
super c
|
||||||
c.worldName = @worldName
|
c.worldName = @worldName
|
||||||
c.multiplayerEnabled = @session.get('multiplayer')
|
c.ladderGame = @level.isType('ladder', 'hero-ladder', 'course-ladder')
|
||||||
c.ladderGame = @level.get('type') in ['ladder', 'hero-ladder', 'course-ladder']
|
|
||||||
if c.isMultiplayerLevel = @isMultiplayerLevel
|
|
||||||
c.multiplayerStatus = @multiplayerStatusManager?.status
|
|
||||||
if @level.get 'replayable'
|
if @level.get 'replayable'
|
||||||
c.levelDifficulty = @session.get('state')?.difficulty ? 0
|
c.levelDifficulty = @session.get('state')?.difficulty ? 0
|
||||||
if @observing
|
if @observing
|
||||||
|
@ -110,23 +87,17 @@ module.exports = class ControlBarView extends CocoView
|
||||||
if me.isSessionless()
|
if me.isSessionless()
|
||||||
@homeLink = "/teachers/courses"
|
@homeLink = "/teachers/courses"
|
||||||
@homeViewClass = "views/courses/TeacherCoursesView"
|
@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
|
levelID = @level.get('slug')?.replace(/\-tutorial$/, '') or @level.id
|
||||||
@homeLink = '/play/ladder/' + levelID
|
@homeLink = '/play/ladder/' + levelID
|
||||||
@homeViewClass = 'views/ladder/LadderView'
|
@homeViewClass = 'views/ladder/LadderView'
|
||||||
@homeViewArgs.push levelID
|
@homeViewArgs.push levelID
|
||||||
if leagueID = @getQueryVariable 'league'
|
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 leagueType
|
||||||
@homeViewArgs.push leagueID
|
@homeViewArgs.push leagueID
|
||||||
@homeLink += "/#{leagueType}/#{leagueID}"
|
@homeLink += "/#{leagueType}/#{leagueID}"
|
||||||
else if @level.get('type', true) in ['hero', 'hero-coop'] or window.serverConfig.picoCTF
|
else if @level.isType('course') or @courseID
|
||||||
@homeLink = '/play'
|
|
||||||
@homeViewClass = 'views/play/CampaignView'
|
|
||||||
campaign = @level.get 'campaign'
|
|
||||||
@homeLink += '/' + campaign
|
|
||||||
@homeViewArgs.push campaign
|
|
||||||
else if @level.get('type', true) in ['course']
|
|
||||||
@homeLink = '/courses'
|
@homeLink = '/courses'
|
||||||
@homeViewClass = 'views/courses/CoursesView'
|
@homeViewClass = 'views/courses/CoursesView'
|
||||||
if @courseID
|
if @courseID
|
||||||
|
@ -136,7 +107,12 @@ module.exports = class ControlBarView extends CocoView
|
||||||
if @courseInstanceID
|
if @courseInstanceID
|
||||||
@homeLink += "/#{@courseInstanceID}"
|
@homeLink += "/#{@courseInstanceID}"
|
||||||
@homeViewArgs.push @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
|
else
|
||||||
@homeLink = '/'
|
@homeLink = '/'
|
||||||
@homeViewClass = 'views/HomeView'
|
@homeViewClass = 'views/HomeView'
|
||||||
|
@ -153,16 +129,13 @@ module.exports = class ControlBarView extends CocoView
|
||||||
@setupManager.open()
|
@setupManager.open()
|
||||||
|
|
||||||
onClickHome: (e) ->
|
onClickHome: (e) ->
|
||||||
if @level.get('type', true) in ['course']
|
if @level.isType('course')
|
||||||
category = if me.isTeacher() then 'Teachers' else 'Students'
|
category = if me.isTeacher() then 'Teachers' else 'Students'
|
||||||
window.tracker?.trackEvent 'Play Level Back To Levels', category: category, levelSlug: @levelSlug, ['Mixpanel']
|
window.tracker?.trackEvent 'Play Level Back To Levels', category: category, levelSlug: @levelSlug, ['Mixpanel']
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopImmediatePropagation()
|
e.stopImmediatePropagation()
|
||||||
Backbone.Mediator.publish 'router:navigate', route: @homeLink, viewClass: @homeViewClass, viewArgs: @homeViewArgs
|
Backbone.Mediator.publish 'router:navigate', route: @homeLink, viewClass: @homeViewClass, viewArgs: @homeViewArgs
|
||||||
|
|
||||||
onClickMultiplayer: (e) ->
|
|
||||||
@showGameMenuModal e, 'multiplayer'
|
|
||||||
|
|
||||||
onClickSignupButton: (e) ->
|
onClickSignupButton: (e) ->
|
||||||
window.tracker?.trackEvent 'Started Signup', category: 'Play Level', label: 'Control Bar', level: @levelID
|
window.tracker?.trackEvent 'Started Signup', category: 'Play Level', label: 'Control Bar', level: @levelID
|
||||||
|
|
||||||
|
@ -183,62 +156,4 @@ module.exports = class ControlBarView extends CocoView
|
||||||
|
|
||||||
destroy: ->
|
destroy: ->
|
||||||
@setupManager?.destroy()
|
@setupManager?.destroy()
|
||||||
@multiplayerStatusManager?.destroy()
|
|
||||||
super()
|
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) ->
|
constructor: (options) ->
|
||||||
@levelID = options.levelID
|
@levelID = options.levelID
|
||||||
@session = options.session
|
@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)
|
@listenTo(@session, 'change:multiplayer', @updateMultiplayerVisibility)
|
||||||
@sessionID = options.sessionID
|
@sessionID = options.sessionID
|
||||||
@bus = LevelBus.get(@levelID, @sessionID)
|
@bus = LevelBus.get(@levelID, @sessionID)
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
CocoView = require 'views/core/CocoView'
|
CocoView = require 'views/core/CocoView'
|
||||||
template = require 'templates/play/level/level-flags-view'
|
template = require 'templates/play/level/level-flags-view'
|
||||||
{me} = require 'core/auth'
|
{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
|
module.exports = class LevelFlagsView extends CocoView
|
||||||
id: 'level-flags-view'
|
id: 'level-flags-view'
|
||||||
|
@ -17,7 +14,6 @@ module.exports = class LevelFlagsView extends CocoView
|
||||||
'god:new-world-created': 'onNewWorld'
|
'god:new-world-created': 'onNewWorld'
|
||||||
'god:streaming-world-updated': 'onNewWorld'
|
'god:streaming-world-updated': 'onNewWorld'
|
||||||
'surface:remove-flag': 'onRemoveFlag'
|
'surface:remove-flag': 'onRemoveFlag'
|
||||||
'real-time-multiplayer:joined-game': 'onJoinedMultiplayerGame'
|
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click .green-flag': -> @onFlagSelected color: 'green', source: 'button'
|
'click .green-flag': -> @onFlagSelected color: 'green', source: 'button'
|
||||||
|
@ -60,9 +56,8 @@ module.exports = class LevelFlagsView extends CocoView
|
||||||
return unless @flagColor and @realTime
|
return unless @flagColor and @realTime
|
||||||
@playSound 'menu-button-click' # TODO: different flag placement sound?
|
@playSound 'menu-button-click' # TODO: different flag placement sound?
|
||||||
pos = x: e.worldPos.x, y: e.worldPos.y
|
pos = x: e.worldPos.x, y: e.worldPos.y
|
||||||
delay = if @realTimeFlags then multiplayerFlagDelay else 0
|
|
||||||
now = @world.dt * @world.frames.length
|
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
|
@flags[@flagColor] = flag
|
||||||
@flagHistory.push flag
|
@flagHistory.push flag
|
||||||
@realTimeFlags?.create flag
|
@realTimeFlags?.create flag
|
||||||
|
@ -75,9 +70,8 @@ module.exports = class LevelFlagsView extends CocoView
|
||||||
|
|
||||||
onRemoveFlag: (e) ->
|
onRemoveFlag: (e) ->
|
||||||
delete @flags[e.color]
|
delete @flags[e.color]
|
||||||
delay = if @realTimeFlags then multiplayerFlagDelay else 0
|
|
||||||
now = @world.dt * @world.frames.length
|
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
|
@flagHistory.push flag
|
||||||
Backbone.Mediator.publish 'level:flag-updated', flag
|
Backbone.Mediator.publish 'level:flag-updated', flag
|
||||||
#console.log e.color, 'deleted at time', flag.time
|
#console.log e.color, 'deleted at time', flag.time
|
||||||
|
@ -85,31 +79,3 @@ module.exports = class LevelFlagsView extends CocoView
|
||||||
onNewWorld: (event) ->
|
onNewWorld: (event) ->
|
||||||
return unless event.world.name is @world.name
|
return unless event.world.name is @world.name
|
||||||
@world = @options.world = event.world
|
@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 = []
|
goals = []
|
||||||
for goal in e.goals
|
for goal in e.goals
|
||||||
state = e.goalStates[goal.id]
|
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
|
if goal.hiddenGoal
|
||||||
continue if goal.optional and state.status isnt 'success'
|
continue if goal.optional and state.status isnt 'success'
|
||||||
continue if not goal.optional and state.status isnt 'failure'
|
continue if not goal.optional and state.status isnt 'failure'
|
||||||
|
|
|
@ -100,7 +100,7 @@ module.exports = class LevelHUDView extends CocoView
|
||||||
@stage?.stopTalking()
|
@stage?.stopTalking()
|
||||||
|
|
||||||
createProperties: ->
|
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
|
name = 'Game' # TODO: we don't need the HUD at all
|
||||||
else if @thang.id in ['Hero Placeholder', 'Hero Placeholder 1']
|
else if @thang.id in ['Hero Placeholder', 'Hero Placeholder 1']
|
||||||
name = @thangType?.getHeroShortName() or 'Hero'
|
name = @thangType?.getHeroShortName() or 'Hero'
|
||||||
|
|
|
@ -59,7 +59,7 @@ module.exports = class LevelLoadingView extends CocoView
|
||||||
goalList = goalContainer.find('ul')
|
goalList = goalContainer.find('ul')
|
||||||
goalCount = 0
|
goalCount = 0
|
||||||
for goalID, goal of @level.get('goals') when (not goal.team or goal.team is (e.team or 'humans')) and not goal.hiddenGoal
|
for goalID, goal of @level.get('goals') when (not goal.team or goal.team is (e.team or 'humans')) and not goal.hiddenGoal
|
||||||
continue if goal.optional and @level.get('type', true) is 'course'
|
continue if goal.optional and @level.isType('course')
|
||||||
name = utils.i18n goal, 'name'
|
name = utils.i18n goal, 'name'
|
||||||
goalList.append $('<li>' + name + '</li>')
|
goalList.append $('<li>' + name + '</li>')
|
||||||
++goalCount
|
++goalCount
|
||||||
|
@ -169,7 +169,7 @@ module.exports = class LevelLoadingView extends CocoView
|
||||||
@playSound 'loading-view-unveil', 0.5
|
@playSound 'loading-view-unveil', 0.5
|
||||||
@$el.find('.left-wing').css left: '-100%', backgroundPosition: 'right -400px top 0'
|
@$el.find('.left-wing').css left: '-100%', backgroundPosition: 'right -400px top 0'
|
||||||
@$el.find('.right-wing').css right: '-100%', backgroundPosition: 'left -400px top 0'
|
@$el.find('.right-wing').css right: '-100%', backgroundPosition: 'left -400px top 0'
|
||||||
$('#level-footer-background').detach().appendTo('#page-container').slideDown(duration)
|
$('#level-footer-background').detach().appendTo('#page-container').slideDown(duration) unless @level.isType('web-dev')
|
||||||
|
|
||||||
unveilIntro: =>
|
unveilIntro: =>
|
||||||
return if @destroyed or not @intro or @unveiled
|
return if @destroyed or not @intro or @unveiled
|
||||||
|
|
|
@ -21,7 +21,6 @@ module.exports = class LevelPlaybackView extends CocoView
|
||||||
'tome:cast-spells': 'onTomeCast'
|
'tome:cast-spells': 'onTomeCast'
|
||||||
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
|
||||||
'playback:stop-real-time-playback': 'onStopRealTimePlayback'
|
'playback:stop-real-time-playback': 'onStopRealTimePlayback'
|
||||||
'real-time-multiplayer:manual-cast': 'onRealTimeMultiplayerCast'
|
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click #music-button': 'onToggleMusic'
|
'click #music-button': 'onToggleMusic'
|
||||||
|
@ -110,11 +109,6 @@ module.exports = class LevelPlaybackView extends CocoView
|
||||||
Backbone.Mediator.publish 'playback:real-time-playback-started', {}
|
Backbone.Mediator.publish 'playback:real-time-playback-started', {}
|
||||||
@playSound 'real-time-playback-start'
|
@playSound 'real-time-playback-start'
|
||||||
|
|
||||||
onRealTimeMultiplayerCast: (e) ->
|
|
||||||
@realTime = true
|
|
||||||
@togglePlaybackControls false
|
|
||||||
Backbone.Mediator.publish 'playback:real-time-playback-waiting', {}
|
|
||||||
|
|
||||||
onWindowResize: (s...) =>
|
onWindowResize: (s...) =>
|
||||||
@barWidth = $('.progress', @$el).width()
|
@barWidth = $('.progress', @$el).width()
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue