mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-26 22:13:32 -04:00
Set up a new loading/progress system for views.
This commit is contained in:
parent
c434325ec0
commit
3b3b825be0
8 changed files with 246 additions and 58 deletions
app
locale
styles
templates
views
|
@ -12,6 +12,7 @@ module.exports = nativeDescription: "English", englishDescription: "English", tr
|
|||
manual: "Manual"
|
||||
fork: "Fork"
|
||||
play: "Play"
|
||||
retry: "Retry"
|
||||
|
||||
units:
|
||||
second: "second"
|
||||
|
@ -602,3 +603,27 @@ module.exports = nativeDescription: "English", englishDescription: "English", tr
|
|||
tutorial: "tutorial"
|
||||
new_to_programming: ". New to programming? Hit our beginner campaign to skill up."
|
||||
so_ready: "I Am So Ready for This"
|
||||
|
||||
loading_error:
|
||||
could_not_load: "Error loading from server"
|
||||
connection_failure: "Connection failed."
|
||||
unauthorized: "You need to be signed in. Do you have cookies disabled?"
|
||||
forbidden: "You do not have the permissions."
|
||||
not_found: "Not found."
|
||||
not_allowed: "Method not allowed."
|
||||
timeout: "Server timeout."
|
||||
conflict: "Resource conflict."
|
||||
bad_input: "Bad input."
|
||||
server_error: "Server error."
|
||||
unknown: "Unknown error."
|
||||
|
||||
resources:
|
||||
your_sessions: "Your Sessions"
|
||||
level: "Level"
|
||||
social_network_apis: "Social Network APIs"
|
||||
facebook_status: "Facebook Status"
|
||||
facebook_friends: "Facebook Friends"
|
||||
facebook_friend_sessions: "Facebook Friend Sessions"
|
||||
gplus_friends: "G+ Friends"
|
||||
gplus_friend_sessions: "G+ Friend Sessions"
|
||||
leaderboard: 'leaderboard'
|
|
@ -167,7 +167,15 @@ a[data-toggle="modal"]
|
|||
width: 50%
|
||||
margin: 0 25%
|
||||
.progress-bar
|
||||
width: 100%
|
||||
width: 0%
|
||||
transition: width 0.1s ease
|
||||
|
||||
.errors .alert
|
||||
padding: 5px
|
||||
display: block
|
||||
margin: 10px auto
|
||||
.btn
|
||||
margin-left: 10px
|
||||
|
||||
.modal
|
||||
.wait
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
.loading-screen
|
||||
h1(data-i18n="common.loading") Loading...
|
||||
.progress.progress-striped.active
|
||||
.progress-bar
|
||||
.progress
|
||||
.progress-bar
|
||||
|
||||
.errors
|
31
app/templates/loading_error.jade
Normal file
31
app/templates/loading_error.jade
Normal file
|
@ -0,0 +1,31 @@
|
|||
.alert.alert-danger.loading-error-alert
|
||||
span(data-i18n="loading_error.could_not_load") Error loading from server
|
||||
span (
|
||||
span(data-i18n="resources.#{name}")
|
||||
span )
|
||||
if !responseText
|
||||
strong(data-i18n="loading_error.connection_failure") Connection failed.
|
||||
else if status === 401
|
||||
strong(data-i18n="loading_error.unauthorized") You need to be signed in. Do you have cookies disabled?
|
||||
else if status === 403
|
||||
strong(data-i18n="loading_error.forbidden") You do not have the permissions.
|
||||
else if status === 404
|
||||
strong(data-i18n="loading_error.not_found") Not found.
|
||||
else if status === 405
|
||||
strong(data-i18n="loading_error.not_allowed") Method not allowed.
|
||||
else if status === 408
|
||||
strong(data-i18n="loading_error.timeout") Server timeout.
|
||||
else if status === 409
|
||||
strong(data-i18n="loading_error.conflict") Resource conflict.
|
||||
else if status === 422
|
||||
strong(data-i18n="loading_error.bad_input") Bad input.
|
||||
else if status >= 500
|
||||
strong(data-i18n="loading_error.server_error") Server error.
|
||||
else
|
||||
strong(data-i18n="loading_error.unknown") Unknown error.
|
||||
|
||||
if resourceIndex !== undefined
|
||||
button.btn.btn-sm.retry-loading-resource(data-i18n="common.retry", data-resource-index=resourceIndex) Retry
|
||||
if requestIndex !== undefined
|
||||
button.btn.btn-sm.retry-loading-request(data-i18n="common.retry", data-request-index=requestIndex) Retry
|
||||
|
|
@ -2,6 +2,7 @@ SuperModel = require 'models/SuperModel'
|
|||
utils = require 'lib/utils'
|
||||
CocoClass = require 'lib/CocoClass'
|
||||
loadingScreenTemplate = require 'templates/loading'
|
||||
loadingErrorTemplate = require 'templates/loading_error'
|
||||
|
||||
visibleModal = null
|
||||
waitingModal = null
|
||||
|
@ -18,13 +19,26 @@ module.exports = class CocoView extends Backbone.View
|
|||
'click a': 'toggleModal'
|
||||
'click button': 'toggleModal'
|
||||
'click li': 'toggleModal'
|
||||
'click .retry-loading-resource': 'onRetryResource'
|
||||
'click .retry-loading-request': 'onRetryRequest'
|
||||
|
||||
subscriptions: {}
|
||||
shortcuts: {}
|
||||
|
||||
# load progress properties
|
||||
loadProgress:
|
||||
num: 0
|
||||
denom: 0
|
||||
showing: false
|
||||
resources: [] # models and collections
|
||||
requests: [] # jqxhr's
|
||||
somethings: [] # everything else
|
||||
progress: 0
|
||||
|
||||
# Setup, Teardown
|
||||
|
||||
constructor: (options) ->
|
||||
@loadProgress = _.cloneDeep @loadProgress
|
||||
@supermodel ?= options?.supermodel or new SuperModel()
|
||||
@options = options
|
||||
@subscriptions = utils.combineAncestralObject(@, 'subscriptions')
|
||||
|
@ -33,6 +47,7 @@ module.exports = class CocoView extends Backbone.View
|
|||
@shortcuts = utils.combineAncestralObject(@, 'shortcuts')
|
||||
@subviews = {}
|
||||
@listenToShortcuts()
|
||||
@updateProgressBar = _.debounce @updateProgressBar, 100
|
||||
# Backbone.Mediator handles subscription setup/teardown automatically
|
||||
super options
|
||||
|
||||
|
@ -74,7 +89,7 @@ module.exports = class CocoView extends Backbone.View
|
|||
return @template if _.isString(@template)
|
||||
@$el.html @template(@getRenderData())
|
||||
@afterRender()
|
||||
@showLoading() if @startsLoading
|
||||
@showLoading() if @startsLoading or @loading() # TODO: Remove startsLoading entirely
|
||||
@$el.i18n()
|
||||
@
|
||||
|
||||
|
@ -89,6 +104,101 @@ module.exports = class CocoView extends Backbone.View
|
|||
context
|
||||
|
||||
afterRender: ->
|
||||
|
||||
# Resource and request loading management for any given view
|
||||
|
||||
addResourceToLoad: (modelOrCollection, name, value=1) ->
|
||||
@loadProgress.resources.push {resource:modelOrCollection, value:value, name:name}
|
||||
@listenToOnce modelOrCollection, 'sync', @updateProgress
|
||||
@listenTo modelOrCollection, 'error', @onResourceLoadFailed
|
||||
@updateProgress()
|
||||
|
||||
addRequestToLoad: (jqxhr, name, retryFunc, value=1) ->
|
||||
@loadProgress.requests.push {request:jqxhr, value:value, name: name, retryFunc: retryFunc}
|
||||
jqxhr.done @updateProgress
|
||||
jqxhr.fail @onRequestLoadFailed
|
||||
|
||||
addSomethingToLoad: (name, value=1) ->
|
||||
@loadProgress.somethings.push {loaded: false, name: name, value: value}
|
||||
@updateProgress()
|
||||
|
||||
somethingLoaded: (name) ->
|
||||
r = _.find @loadProgress.somethings, {name: name}
|
||||
return console.error 'Could not find something called', name if not r
|
||||
r.loaded = true
|
||||
@updateProgress(name)
|
||||
|
||||
loading: ->
|
||||
return false if @loaded
|
||||
for r in @loadProgress.resources
|
||||
return true if not r.resource.loaded
|
||||
for r in @loadProgress.requests
|
||||
return true if not r.request.status
|
||||
for r in @loadProgress.somethings
|
||||
return true if not r.loaded
|
||||
return false
|
||||
|
||||
updateProgress: =>
|
||||
console.debug 'Loaded', r.name if arguments[0] and r = _.find @loadProgress.resources, {resource:arguments[0]}
|
||||
console.debug 'Loaded', r.name if arguments[2] and r = _.find @loadProgress.requests, {request:arguments[2]}
|
||||
console.debug 'Loaded', r.name if arguments[0] and r = _.find @loadProgress.somethings, {name:arguments[0]}
|
||||
|
||||
denom = 0
|
||||
denom += r.value for r in @loadProgress.resources
|
||||
denom += r.value for r in @loadProgress.requests
|
||||
denom += r.value for r in @loadProgress.somethings
|
||||
num = @loadProgress.num
|
||||
num += r.value for r in @loadProgress.resources when r.resource.loaded
|
||||
num += r.value for r in @loadProgress.requests when r.request.status
|
||||
num += r.value for r in @loadProgress.somethings when r.loaded
|
||||
#console.log 'update progress', @, num, denom, arguments
|
||||
|
||||
progress = if denom then num / denom else 0
|
||||
# sometimes the denominator isn't known from the outset, so make sure the overall progress only goes up
|
||||
@loadProgress.progress = progress if progress > @loadProgress.progress
|
||||
@updateProgressBar()
|
||||
if num is denom and not @loaded
|
||||
@loaded = true
|
||||
@onLoaded()
|
||||
|
||||
updateProgressBar: =>
|
||||
prog = "#{parseInt(@loadProgress.progress*100)}%"
|
||||
@$el.find('.loading-screen .progress-bar').css('width', prog)
|
||||
|
||||
onLoaded: ->
|
||||
@render()
|
||||
|
||||
# Error handling for loading
|
||||
|
||||
onResourceLoadFailed: (resource, jqxhr) ->
|
||||
for r, index in @loadProgress.resources
|
||||
break if r.resource is resource
|
||||
@$el.find('.loading-screen .errors').append(loadingErrorTemplate({
|
||||
status:jqxhr.status,
|
||||
name: r.name
|
||||
resourceIndex: index,
|
||||
responseText: jqxhr.responseText
|
||||
})).i18n()
|
||||
|
||||
onRetryResource: (e) ->
|
||||
r = @loadProgress.resources[$(e.target).data('resource-index')]
|
||||
r.resource.fetch()
|
||||
$(e.target).closest('.loading-error-alert').remove()
|
||||
|
||||
onRequestLoadFailed: (jqxhr) =>
|
||||
for r, index in @loadProgress.requests
|
||||
break if r.request is jqxhr
|
||||
@$el.find('.loading-screen .errors').append(loadingErrorTemplate({
|
||||
status:jqxhr.status,
|
||||
name: r.name
|
||||
requestIndex: index,
|
||||
responseText: jqxhr.responseText
|
||||
}))
|
||||
|
||||
onRetryRequest: (e) ->
|
||||
r = @loadProgress.requests[$(e.target).data('request-index')]
|
||||
@[r.retryFunc]?()
|
||||
$(e.target).closest('.loading-error-alert').remove()
|
||||
|
||||
# Modals
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
CocoView = require 'views/kinds/CocoView'
|
||||
CocoClass = require 'lib/CocoClass'
|
||||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
CocoCollection = require 'models/CocoCollection'
|
||||
|
@ -18,7 +19,6 @@ class LevelSessionsCollection extends CocoCollection
|
|||
module.exports = class LadderTabView extends CocoView
|
||||
id: 'ladder-tab-view'
|
||||
template: require 'templates/play/ladder/ladder_tab'
|
||||
startsLoading: true
|
||||
|
||||
events:
|
||||
'click .connect-facebook': 'onConnectFacebook'
|
||||
|
@ -32,6 +32,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
|
||||
constructor: (options, @level, @sessions) ->
|
||||
super(options)
|
||||
@addSomethingToLoad("social_network_apis")
|
||||
@teams = teamDataFromLevel @level
|
||||
@leaderboards = {}
|
||||
@refreshLadder()
|
||||
|
@ -39,15 +40,16 @@ module.exports = class LadderTabView extends CocoView
|
|||
|
||||
checkFriends: ->
|
||||
return if @checked or (not window.FB) or (not window.gapi)
|
||||
@somethingLoaded("social_network_apis")
|
||||
@checked = true
|
||||
|
||||
@loadingFacebookFriends = true
|
||||
|
||||
@addSomethingToLoad("facebook_status")
|
||||
FB.getLoginStatus (response) =>
|
||||
@facebookStatus = response.status
|
||||
if @facebookStatus is 'connected' then @loadFacebookFriendSessions() else @loadingFacebookFriends = false
|
||||
@somethingLoaded("facebook_status")
|
||||
@loadFacebookFriends() if @facebookStatus is 'connected'
|
||||
|
||||
if application.gplusHandler.loggedIn is undefined
|
||||
@loadingGPlusFriends = true
|
||||
@listenToOnce(application.gplusHandler, 'checked-state', @gplusSessionStateLoaded)
|
||||
else
|
||||
@gplusSessionStateLoaded()
|
||||
|
@ -60,16 +62,24 @@ module.exports = class LadderTabView extends CocoView
|
|||
|
||||
onConnectedWithFacebook: -> location.reload() if @connecting
|
||||
|
||||
loadFacebookFriends: ->
|
||||
@addSomethingToLoad("facebook_friends")
|
||||
FB.api '/me/friends', @onFacebookFriendsLoaded
|
||||
|
||||
onFacebookFriendsLoaded: (response) =>
|
||||
@somethingLoaded("facebook_friends")
|
||||
@facebookData = response.data
|
||||
@loadFacebookFriendSessions()
|
||||
|
||||
loadFacebookFriendSessions: ->
|
||||
FB.api '/me/friends', (response) =>
|
||||
@facebookData = response.data
|
||||
levelFrag = "#{@level.get('original')}.#{@level.get('version').major}"
|
||||
url = "/db/level/#{levelFrag}/leaderboard_facebook_friends"
|
||||
$.ajax url, {
|
||||
data: { friendIDs: (f.id for f in @facebookData) }
|
||||
method: 'POST'
|
||||
success: @onFacebookFriendSessionsLoaded
|
||||
}
|
||||
levelFrag = "#{@level.get('original')}.#{@level.get('version').major}"
|
||||
url = "/db/level/#{levelFrag}/leaderboard_facebook_friends"
|
||||
jqxhr = $.ajax url, {
|
||||
data: { friendIDs: (f.id for f in @facebookData) }
|
||||
method: 'POST'
|
||||
success: @onFacebookFriendSessionsLoaded
|
||||
}
|
||||
@addRequestToLoad(jqxhr, 'facebook_friend_sessions', 'loadFacebookFriendSessions')
|
||||
|
||||
onFacebookFriendSessionsLoaded: (result) =>
|
||||
friendsMap = {}
|
||||
|
@ -79,9 +89,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
friend.otherTeam = if friend.team is 'humans' then 'ogres' else 'humans'
|
||||
friend.imageSource = "http://graph.facebook.com/#{friend.facebookID}/picture"
|
||||
@facebookFriendSessions = result
|
||||
@loadingFacebookFriends = false
|
||||
@renderMaybe()
|
||||
|
||||
|
||||
# GOOGLE PLUS
|
||||
|
||||
onConnectGPlus: ->
|
||||
|
@ -93,21 +101,23 @@ module.exports = class LadderTabView extends CocoView
|
|||
|
||||
gplusSessionStateLoaded: ->
|
||||
if application.gplusHandler.loggedIn
|
||||
@loadingGPlusFriends = true
|
||||
@addSomethingToLoad("gplus_friends")
|
||||
application.gplusHandler.loadFriends @gplusFriendsLoaded
|
||||
else
|
||||
@loadingGPlusFriends = false
|
||||
@renderMaybe()
|
||||
|
||||
gplusFriendsLoaded: (friends) =>
|
||||
@somethingLoaded("gplus_friends")
|
||||
@gplusData = friends.items
|
||||
@loadGPlusFriendSessions()
|
||||
|
||||
loadGPlusFriendSessions: ->
|
||||
levelFrag = "#{@level.get('original')}.#{@level.get('version').major}"
|
||||
url = "/db/level/#{levelFrag}/leaderboard_gplus_friends"
|
||||
$.ajax url, {
|
||||
jqxhr = $.ajax url, {
|
||||
data: { friendIDs: (f.id for f in @gplusData) }
|
||||
method: 'POST'
|
||||
success: @onGPlusFriendSessionsLoaded
|
||||
}
|
||||
@addRequestToLoad(jqxhr, 'gplus_friend_sessions', 'loadGPlusFriendSessions')
|
||||
|
||||
onGPlusFriendSessionsLoaded: (result) =>
|
||||
friendsMap = {}
|
||||
|
@ -117,29 +127,15 @@ module.exports = class LadderTabView extends CocoView
|
|||
friend.otherTeam = if friend.team is 'humans' then 'ogres' else 'humans'
|
||||
friend.imageSource = friendsMap[friend.gplusID].image.url
|
||||
@gplusFriendSessions = result
|
||||
@loadingGPlusFriends = false
|
||||
@renderMaybe()
|
||||
|
||||
# LADDER LOADING
|
||||
|
||||
refreshLadder: ->
|
||||
promises = []
|
||||
for team in @teams
|
||||
@leaderboards[team.id]?.off 'sync'
|
||||
@leaderboards[team.id]?.destroy()
|
||||
teamSession = _.find @sessions.models, (session) -> session.get('team') is team.id
|
||||
@leaderboards[team.id] = new LeaderboardData(@level, team.id, teamSession)
|
||||
promises.push @leaderboards[team.id].promise
|
||||
@loadingLeaderboards = true
|
||||
$.when(promises...).then(@leaderboardsLoaded)
|
||||
|
||||
leaderboardsLoaded: =>
|
||||
@loadingLeaderboards = false
|
||||
@renderMaybe()
|
||||
|
||||
renderMaybe: ->
|
||||
return if @loadingFacebookFriends or @loadingLeaderboards or @loadingGPlusFriends
|
||||
@startsLoading = false
|
||||
@render()
|
||||
@addResourceToLoad @leaderboards[team.id], 'leaderboard', 3
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
|
@ -160,9 +156,16 @@ module.exports = class LadderTabView extends CocoView
|
|||
sessions.reverse()
|
||||
sessions
|
||||
|
||||
class LeaderboardData
|
||||
class LeaderboardData extends CocoClass
|
||||
###
|
||||
Consolidates what you need to load for a leaderboard into a single Backbone Model-like object.
|
||||
###
|
||||
|
||||
constructor: (@level, @team, @session) ->
|
||||
_.extend @, Backbone.Events
|
||||
super()
|
||||
@fetch()
|
||||
|
||||
fetch: ->
|
||||
@topPlayers = new LeaderboardCollection(@level, {order:-1, scoreOffset: HIGHEST_SCORE, team: @team, limit: 20})
|
||||
promises = []
|
||||
promises.push @topPlayers.fetch()
|
||||
|
@ -173,18 +176,24 @@ class LeaderboardData
|
|||
promises.push @playersAbove.fetch()
|
||||
@playersBelow = new LeaderboardCollection(@level, {order:-1, scoreOffset: score, limit: 4, team: @team})
|
||||
promises.push @playersBelow.fetch()
|
||||
level = "#{level.get('original')}.#{level.get('version').major}"
|
||||
level = "#{@level.get('original')}.#{@level.get('version').major}"
|
||||
success = (@myRank) =>
|
||||
promises.push $.ajax "/db/level/#{level}/leaderboard_rank?scoreOffset=#{@session.get('totalScore')}&team=#{@team}", {success}
|
||||
@promise = $.when(promises...)
|
||||
@promise.then @onLoad
|
||||
@promise.fail @onFail
|
||||
@promise
|
||||
|
||||
onLoad: =>
|
||||
return if @destroyed
|
||||
@loaded = true
|
||||
@trigger 'sync'
|
||||
@trigger 'sync', @
|
||||
# TODO: cache user ids -> names mapping, and load them here as needed,
|
||||
# and apply them to sessions. Fetching each and every time is too costly.
|
||||
|
||||
onFail: (resource, jqxhr) =>
|
||||
return if @destroyed
|
||||
@trigger 'error', @, jqxhr
|
||||
|
||||
inTopSessions: ->
|
||||
return me.id in (session.attributes.creator for session in @topPlayers.models)
|
||||
|
@ -201,3 +210,7 @@ class LeaderboardData
|
|||
startRank = @myRank - 4
|
||||
session.rank = startRank + i for session, i in l
|
||||
l
|
||||
|
||||
allResources: ->
|
||||
resources = [@topPlayers, @playersAbove, @playersBelow]
|
||||
return (r for r in resources when r)
|
|
@ -24,7 +24,6 @@ class LevelSessionsCollection extends CocoCollection
|
|||
module.exports = class LadderView extends RootView
|
||||
id: 'ladder-view'
|
||||
template: require 'templates/play/ladder'
|
||||
startsLoading: true
|
||||
|
||||
subscriptions:
|
||||
'application:idle-changed': 'onIdleChanged'
|
||||
|
@ -38,18 +37,18 @@ module.exports = class LadderView extends RootView
|
|||
constructor: (options, @levelID) ->
|
||||
super(options)
|
||||
@level = new Level(_id:@levelID)
|
||||
p1 = @level.fetch()
|
||||
@level.fetch()
|
||||
@sessions = new LevelSessionsCollection(levelID)
|
||||
p2 = @sessions.fetch({})
|
||||
@sessions.fetch({})
|
||||
@addResourceToLoad(@sessions, 'your_sessions')
|
||||
@addResourceToLoad(@level, 'level')
|
||||
@simulator = new Simulator()
|
||||
@listenTo(@simulator, 'statusUpdate', @updateSimulationStatus)
|
||||
@teams = []
|
||||
$.when(p1, p2).then @onLoaded
|
||||
|
||||
onLoaded: =>
|
||||
onLoaded: ->
|
||||
@teams = teamDataFromLevel @level
|
||||
@startsLoading = false
|
||||
@render()
|
||||
super()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
|
@ -63,7 +62,7 @@ module.exports = class LadderView extends RootView
|
|||
|
||||
afterRender: ->
|
||||
super()
|
||||
return if @startsLoading
|
||||
return if @loading()
|
||||
@insertSubView(@ladderTab = new LadderTabView({}, @level, @sessions))
|
||||
@insertSubView(@myMatchesTab = new MyMatchesTabView({}, @level, @sessions))
|
||||
@refreshInterval = setInterval(@fetchSessionsAndRefreshViews.bind(@), 10 * 1000)
|
||||
|
@ -72,7 +71,7 @@ module.exports = class LadderView extends RootView
|
|||
@showPlayModal(hash) if @sessions.loaded
|
||||
|
||||
fetchSessionsAndRefreshViews: ->
|
||||
return if @destroyed or application.userIsIdle or @$el.find('#simulate.active').length or (new Date() - 2000 < @lastRefreshTime) or @startsLoading
|
||||
return if @destroyed or application.userIsIdle or @$el.find('#simulate.active').length or (new Date() - 2000 < @lastRefreshTime) or @loading()
|
||||
@sessions.fetch({"success": @refreshViews})
|
||||
|
||||
refreshViews: =>
|
||||
|
|
|
@ -63,7 +63,7 @@ module.exports = class SpellView extends View
|
|||
@createFirepad()
|
||||
else
|
||||
# needs to happen after the code generating this view is complete
|
||||
setTimeout @onLoaded, 1
|
||||
setTimeout @onAllLoaded, 1
|
||||
|
||||
createACE: ->
|
||||
# Test themes and settings here: http://ace.ajax.org/build/kitchen-sink.html
|
||||
|
@ -178,9 +178,9 @@ module.exports = class SpellView extends View
|
|||
else
|
||||
@ace.setValue @previousSource
|
||||
@ace.clearSelection()
|
||||
@onLoaded()
|
||||
@onAllLoaded()
|
||||
|
||||
onLoaded: =>
|
||||
onAllLoaded: =>
|
||||
@spell.transpile @spell.source
|
||||
@spell.loaded = true
|
||||
Backbone.Mediator.publish 'tome:spell-loaded', spell: @spell
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue