forked from code-dot-org/code-dot-org
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinitApp.js
More file actions
206 lines (192 loc) · 7.88 KB
/
Copy pathinitApp.js
File metadata and controls
206 lines (192 loc) · 7.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// TODO (brent) - way too many globals
/* global script_path, CDOSounds, dashboard, appOptions, trackEvent, Applab, Blockly, ga*/
var timing = require('./timing');
var chrome34Fix = require('./chrome34Fix');
var loadApp = require('./loadApp');
var project = require('./project');
var userAgentParser = require('./userAgentParser');
var clientState = require('../clientState');
var createCallouts = require('../callouts');
var reporting = require('../reporting');
var Dialog = require('../dialog');
var showVideoDialog = require('../videos').showVideoDialog;
window.dashboard = window.dashboard || {};
window.dashboard.project = project;
window.apps = {
// Loads the dependencies for the current app based on values in `appOptions`.
// This function takes a callback which is called once dependencies are ready.
load: loadApp,
// Legacy Blockly initialization that was moved here from _blockly.html.haml.
// Modifies `appOptions` with some default values in `baseOptions`.
// TODO(dave): Move blockly-specific setup function out of shared and back into dashboard.
setupApp: function (appOptions) {
if (!window.dashboard) {
throw new Error('Assume existence of window.dashboard');
}
timing.startTiming('Puzzle', script_path, '');
var lastSavedProgram;
// Sets up default options and initializes blockly
var baseOptions = {
containerId: 'codeApp',
Dialog: Dialog,
cdoSounds: CDOSounds,
position: {blockYCoordinateInterval: 25},
onInitialize: function () {
createCallouts(this.level.callouts || this.callouts);
if (userAgentParser.isChrome34()) {
chrome34Fix.fixup();
}
if (appOptions.level.projectTemplateLevelName || appOptions.app === 'applab' || appOptions.app === 'gamelab') {
$('#clear-puzzle-header').hide();
// Only show Version History button if the user owns this project
if (project.isEditable()) {
$('#versions-header').show();
}
}
$(document).trigger('appInitialized');
},
onAttempt: function (report) {
if (appOptions.level.isProjectLevel) {
return;
}
if (appOptions.channel && !appOptions.level.edit_blocks) {
// Don't send the levelSource or image to Dashboard for channel-backed levels,
// unless we are actually editing blocks and not really completing a level
// (The levelSource is already stored in the channels API.)
delete report.program;
delete report.image;
} else {
// Only locally cache non-channel-backed levels. Use a client-generated
// timestamp initially (it will be updated with a timestamp from the server
// if we get a response.
lastSavedProgram = decodeURIComponent(report.program);
clientState.writeSourceForLevel(appOptions.scriptName, appOptions.serverLevelId, +new Date(), lastSavedProgram);
}
report.scriptName = appOptions.scriptName;
report.fallbackResponse = appOptions.report.fallback_response;
report.callback = appOptions.report.callback;
// Track puzzle attempt event
trackEvent('Puzzle', 'Attempt', script_path, report.pass ? 1 : 0);
if (report.pass) {
trackEvent('Puzzle', 'Success', script_path, report.attempt);
timing.stopTiming('Puzzle', script_path, '');
}
trackEvent('Activity', 'Lines of Code', script_path, report.lines);
reporting.sendReport(report);
},
onComplete: function (response) {
if (!appOptions.channel) {
// Update the cache timestamp with the (more accurate) value from the server.
clientState.writeSourceForLevel(appOptions.scriptName, appOptions.serverLevelId, response.timestamp, lastSavedProgram);
}
},
onResetPressed: function () {
reporting.cancelReport();
},
onContinue: function () {
var lastServerResponse = reporting.getLastServerResponse();
if (lastServerResponse.videoInfo) {
showVideoDialog(lastServerResponse.videoInfo);
} else if (lastServerResponse.nextRedirect) {
window.location.href = lastServerResponse.nextRedirect;
}
},
backToPreviousLevel: function () {
var lastServerResponse = reporting.getLastServerResponse();
if (lastServerResponse.previousLevelRedirect) {
window.location.href = lastServerResponse.previousLevelRedirect;
}
},
showInstructionsWrapper: function (showInstructions) {
// Always skip all pre-level popups on share levels or when configured thus
if (this.share || appOptions.level.skipInstructionsPopup) {
return;
}
var afterVideoCallback = showInstructions;
if (appOptions.level.afterVideoBeforeInstructionsFn) {
afterVideoCallback = function () {
appOptions.level.afterVideoBeforeInstructionsFn(showInstructions);
};
}
var hasVideo = !!appOptions.autoplayVideo;
var hasInstructions = !!(appOptions.level.instructions ||
appOptions.level.aniGifURL);
if (hasVideo) {
if (hasInstructions) {
appOptions.autoplayVideo.onClose = afterVideoCallback;
}
showVideoDialog(appOptions.autoplayVideo);
} else if (hasInstructions) {
afterVideoCallback();
}
}
};
$.extend(true, appOptions, baseOptions);
// Turn string values into functions for keys that begin with 'fn_' (JSON can't contain function definitions)
// E.g. { fn_example: 'function () { return; }' } becomes { example: function () { return; } }
(function fixUpFunctions(node) {
if (typeof node !== 'object') {
return;
}
for (var i in node) {
if (/^fn_/.test(i)) {
try {
// eslint-disable-next-line no-eval
node[i.replace(/^fn_/, '')] = eval('(' + node[i] + ')');
} catch (e) {
}
} else {
fixUpFunctions(node[i]);
}
}
})(appOptions.level);
// Previously, this was set by dashboard based on route and user agent. We
// stopped being able to use the user agent on the server, and thus try
// to have the same logic on the client.
appOptions.noPadding = userAgentParser.isMobile();
},
// Set up projects, skipping blockly-specific steps. Designed for use
// by levels of type "external".
setupProjectsExternal: function () {
if (!window.dashboard) {
throw new Error('Assume existence of window.dashboard');
}
},
// Define blockly/droplet-specific callbacks for projects to access
// level source, HTML and headers.
// TODO(dave): Extract blockly-specific handler code into _blockly.html.haml.
sourceHandler: {
setInitialLevelHtml: function (levelHtml) {
appOptions.level.levelHtml = levelHtml;
},
getLevelHtml: function () {
return window.Applab && Applab.getHtml();
},
setInitialLevelSource: function (levelSource) {
appOptions.level.lastAttempt = levelSource;
},
getLevelSource: function (currentLevelSource) {
var source;
if (window.Blockly) {
// If we're readOnly, source hasn't changed at all
source = Blockly.mainBlockSpace.isReadOnly() ? currentLevelSource :
Blockly.Xml.domToText(Blockly.Xml.blockSpaceToDom(Blockly.mainBlockSpace));
} else if (appOptions.getCode) {
source = appOptions.getCode();
}
return source;
},
setInitialAnimationMetadata: function (animationMetadata) {
appOptions.initialAnimationMetadata = animationMetadata;
},
getAnimationMetadata: function () {
return appOptions.getAnimationMetadata &&
appOptions.getAnimationMetadata();
}
},
// Initialize the Blockly or Droplet app.
init: function () {
project.init(window.apps.sourceHandler);
window[appOptions.app + 'Main'](appOptions);
}
};