Place error message over Surface next to line

This commit is contained in:
Matt Lott 2014-11-06 21:43:39 -08:00
parent f3e4906810
commit b4dde5705b
8 changed files with 69 additions and 29 deletions
app
schemas/subscriptions
styles/play/level/tome
templates/play
views/play/level

View file

@ -126,3 +126,7 @@ module.exports =
'tome:winnability-updated': c.object {title: 'Winnability Updated', description: 'When we think we can now win (or can no longer win), we may want to emphasize the submit button versus the run button (or vice versa), so this fires when we get new goal states (even preloaded goal states) suggesting success or failure change.', required: ['winnable']}, 'tome:winnability-updated': c.object {title: 'Winnability Updated', description: 'When we think we can now win (or can no longer win), we may want to emphasize the submit button versus the run button (or vice versa), so this fires when we get new goal states (even preloaded goal states) suggesting success or failure change.', required: ['winnable']},
winnable: {type: 'boolean'} winnable: {type: 'boolean'}
'tome:show-problem-alert': c.object {title: 'Show Problem Alert', description: 'A problem alert needs to be shown.', required: ['problem']},
problem: {type: 'object'}
lineOffsetPx: {type: ['number', 'undefined']}

View file

@ -1,16 +1,17 @@
@import "app/styles/mixins" @import "app/styles/mixins"
@import "app/styles/bootstrap/variables" @import "app/styles/bootstrap/variables"
.problem-alert
#problem-alert-view.problem-alert
z-index: 10 z-index: 10
position: absolute position: absolute
// Position these at the end of the spell editor, right above the spell toolbar. // Position these at the end of the spell editor, right above the spell toolbar.
bottom: -20px top: 45px
left: 10px right: 500px
right: 10px
background: transparent background: transparent
border: 1px solid transparent border: 1px solid transparent
padding: 0 padding: 0
font-size: 18px
text-shadow: none text-shadow: none
color: white color: white
word-wrap: break-word word-wrap: break-word

View file

@ -20,6 +20,8 @@
#gold-view #gold-view
#problem-alert-view
#level-chat-view #level-chat-view
#multiplayer-status-view #multiplayer-status-view

View file

@ -1,4 +1,4 @@
button.close(type="button", data-dismiss="alert") × button.close(type="button") ×
if hint if hint
span.problem-title!= hint span.problem-title!= hint
br br

View file

@ -23,6 +23,7 @@ RealTimeCollection = require 'collections/RealTimeCollection'
# subviews # subviews
LevelLoadingView = require './LevelLoadingView' LevelLoadingView = require './LevelLoadingView'
ProblemAlertView = require './tome/ProblemAlertView'
TomeView = require './tome/TomeView' TomeView = require './tome/TomeView'
ChatView = require './LevelChatView' ChatView = require './LevelChatView'
HUDView = require './LevelHUDView' HUDView = require './LevelHUDView'
@ -249,6 +250,7 @@ module.exports = class PlayLevelView extends RootView
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session @insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
if @level.get('type') in ['ladder', 'hero-ladder'] if @level.get('type') in ['ladder', 'hero-ladder']
@insertSubView new MultiplayerStatusView levelID: @levelID, session: @session, level: @level @insertSubView new MultiplayerStatusView levelID: @levelID, session: @session, level: @level
@insertSubView new ProblemAlertView {}
worldName = utils.i18n @level.attributes, 'name' worldName = utils.i18n @level.attributes, 'name'
@controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel} @controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel}
#_.delay (=> Backbone.Mediator.publish('level:set-debug', debug: true)), 5000 if @isIPadApp() # if me.displayName() is 'Nick' #_.delay (=> Backbone.Mediator.publish('level:set-debug', debug: true)), 5000 if @isIPadApp() # if me.displayName() is 'Nick'

View file

@ -1,20 +1,14 @@
ProblemAlertView = require './ProblemAlertView'
Range = ace.require('ace/range').Range Range = ace.require('ace/range').Range
module.exports = class Problem module.exports = class Problem
annotation: null annotation: null
alertView: null
markerRange: null markerRange: null
constructor: (@aether, @aetherProblem, @ace, withAlert=false, isCast=false, @levelID) -> constructor: (@aether, @aetherProblem, @ace, isCast=false, @levelID) ->
@buildAnnotation() @buildAnnotation()
@buildAlertView() if withAlert
@buildMarkerRange() if isCast @buildMarkerRange() if isCast
Backbone.Mediator.publish("problem:problem-created", line:@annotation.row, text: @annotation.text) if application.isIPadApp Backbone.Mediator.publish("problem:problem-created", line:@annotation.row, text: @annotation.text) if application.isIPadApp
destroy: -> destroy: ->
unless @alertView?.destroyed
@alertView?.$el?.remove()
@alertView?.destroy()
@removeMarkerRange() @removeMarkerRange()
@userCodeProblem.off() if @userCodeProblem @userCodeProblem.off() if @userCodeProblem
@ -29,11 +23,6 @@ module.exports = class Problem
text: text, text: text,
type: @aetherProblem.level ? 'error' type: @aetherProblem.level ? 'error'
buildAlertView: ->
@alertView = new ProblemAlertView problem: @
@alertView.render()
$(@ace.container).append @alertView.el
buildMarkerRange: -> buildMarkerRange: ->
return unless @aetherProblem.range return unless @aetherProblem.range
[start, end] = @aetherProblem.range [start, end] = @aetherProblem.range

View file

@ -3,32 +3,68 @@ template = require 'templates/play/level/tome/problem_alert'
{me} = require 'lib/auth' {me} = require 'lib/auth'
module.exports = class ProblemAlertView extends CocoView module.exports = class ProblemAlertView extends CocoView
id: 'problem-alert-view'
className: 'problem-alert' className: 'problem-alert'
template: template template: template
subscriptions: {} subscriptions:
'tome:show-problem-alert': 'onShowProblemAlert'
'tome:manual-cast': 'onHideProblemAlert'
'real-time-multiplayer:manual-cast': 'onHideProblemAlert'
events: events:
'click .close': 'onRemoveClicked' 'click .close': 'onRemoveClicked'
constructor: (options) -> constructor: (options) ->
super options super options
@problem = options.problem if options.problem?
@problem = options.problem
@onWindowResize()
else
@$el.hide()
$(window).on 'resize', @onWindowResize
destroy: ->
$(window).off 'resize', @onWindowResize
super()
getRenderData: (context={}) -> getRenderData: (context={}) ->
context = super context context = super context
format = (s) -> s?.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>') if @problem?
context.message = format @problem.aetherProblem.message format = (s) -> s?.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>')
context.hint = format @problem.aetherProblem.hint context.message = format @problem.aetherProblem.message
context.hint = format @problem.aetherProblem.hint
context context
afterRender: -> afterRender: ->
super() super()
@$el.addClass('alert').addClass("alert-#{@problem.aetherProblem.level}").hide().fadeIn('slow') if @problem?
@$el.addClass('no-hint') unless @problem.aetherProblem.hint @$el.addClass('alert').addClass("alert-#{@problem.aetherProblem.level}").hide().fadeIn('slow')
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'error_appear', volume: 1.0 @$el.addClass('no-hint') unless @problem.aetherProblem.hint
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'error_appear', volume: 1.0
onShowProblemAlert: (data) ->
return if @problem? and data.problem.aetherProblem.message is @problem.aetherProblem.message and data.problem.aetherProblem.hint is @problem.aetherProblem.hint
return unless $('#code-area').is(":visible")
@problem = data.problem
@lineOffsetPx = data.lineOffsetPx or 0
@$el.show()
@onWindowResize()
@render()
onHideProblemAlert: ->
@onRemoveClicked()
onRemoveClicked: -> onRemoveClicked: ->
@$el.remove() @$el.hide()
@destroy() @problem = null
#@problem.destroy() # let's try leaving the annotations / marker ranges alone
onWindowResize: (e) =>
# TODO: This all seems a little hacky
if @problem?
@$el.css('left', $('#goals-view').outerWidth(true) + 'px')
@$el.css('right', $('#code-area').outerWidth(true) + 'px')
# 45px from top roughly aligns top of alert with top of first code line
# TODO: calculate this in a more dynamic, less sketchy way
@$el.css('top', (45 + @lineOffsetPx) + 'px')

View file

@ -515,7 +515,13 @@ module.exports = class SpellView extends CocoView
for aetherProblem, problemIndex in aether.getAllProblems() for aetherProblem, problemIndex in aether.getAllProblems()
continue if key = aetherProblem.userInfo?.key and key of seenProblemKeys continue if key = aetherProblem.userInfo?.key and key of seenProblemKeys
seenProblemKeys[key] = true if key seenProblemKeys[key] = true if key
@problems.push problem = new Problem aether, aetherProblem, @ace, isCast and problemIndex is 0, isCast, @spell.levelID @problems.push problem = new Problem aether, aetherProblem, @ace, isCast, @spell.levelID
if isCast and problemIndex is 0
if problem.aetherProblem.range?
# Line height is 20px
# TODO: Can we get line height dynamically from ace?
lineOffsetPx = problem.aetherProblem.range[0].row * 20 - @ace.session.getScrollTop()
Backbone.Mediator.publish 'tome:show-problem-alert', problem: problem, lineOffsetPx: lineOffsetPx
@saveUserCodeProblem(aether, aetherProblem) if isCast @saveUserCodeProblem(aether, aetherProblem) if isCast
annotations.push problem.annotation if problem.annotation annotations.push problem.annotation if problem.annotation
@aceSession.setAnnotations annotations @aceSession.setAnnotations annotations