smalruby/smalruby-editor

View on GitHub
app/assets/javascripts/views/character_modal_view.js.coffee.erb

Summary

Maintainability
Test Coverage
<%
  require 'smalruby_editor'
  require 'smalruby_editor/blockly_message_helper'
%>

# キャラクター設定ダイアログを表現するビュー
Smalruby.CharacterModalView = Backbone.View.extend
  model: new Smalruby.Character()

  events:
    'click #character-modal-costume-selector a.thumbnail': 'onSelectCostume'
    'click #character-modal-costume-selector a#character-modal-upload-costume': 'onUploadCostume'
    'click #character_rotation_style_free': 'onRotationStyleFree'
    'click #character_rotation_style_left_right': 'onRotationStyleLeftRight'
    'click #character_rotation_style_none': 'onRotationStyleNone'
    'click #character-modal-ok-button': 'onOk'

  previewZoomLevel: 0.5

  initialize: ->
    Smalruby.removeBackdropOnHidden(@$el)

    @target = null

    $('#character-modal-costume-selector img').on 'dragstart', (e) ->
      e.preventDefault()

    setPosition = (pos) =>
      @model.set
        x: parseInt(pos.left / @previewZoomLevel)
        y: parseInt(pos.top / @previewZoomLevel)

    $('#character-modal-character').draggable
      containment: '#character-modal-preview'
      cursor: 'move'
      drag: (event, ui) ->
        setPosition(ui.position)

    $('#character-modal-preview').droppable
      drop: (event, ui) ->
        setPosition(ui.draggable.position())

    Smalruby.ignoreEnterKey(@$el)

    @templateText = $('#character-modal-costume-template').text()

    $('#character-modal-add-costume-button').click (e) =>
      e.preventDefault()

      costumes = @model.get('costumes').slice(0)
      costumes.push(@model.costume())
      costumeNames = @model.get('costumeNames').slice(0)
      costumeNames.push("costume#{costumes.length}")
      @model.set
        costumes: costumes
        costumeNames: costumeNames
        costumeIndex: costumes.length - 1

    self = @
    changeNameFunc = (e) ->
      self.model.set({ name: $(@).val() })
      self.nameChanged = true
    @$el.find('input[name="character[name]"]').change(changeNameFunc)
    @$el.find('input[name="character[name]"]').keyup(changeNameFunc)

    @$el.find('input[name="character[x]"]').change (e) ->
      self.model.set({ x: $(@).val() })

    @$el.find('input[name="character[y]"]').change (e) ->
      self.model.set({ y: $(@).val() })

    @$el.find('input[name="character[angle]"]').change (e) ->
      self.model.set({ angle: $(@).val() })

    changeCostumeNameFunc = (e) ->
      val = $(@).val()
      costumeNames = _.clone(self.model.get('costumeNames'))
      costumeNames[self.model.get('costumeIndex')] = val
      self.model.set({ costumeNames: costumeNames })
    @$el.find('input[name="costume[name]"]').change(changeCostumeNameFunc)
    @$el.find('input[name="costume[name]"]').keyup(changeCostumeNameFunc)

    @listenTo(@model, 'change:name', @onChangeName)
    @listenTo(@model, 'change:x', @onChangeX)
    @listenTo(@model, 'change:y', @onChangeY)
    @listenTo(@model, 'change:angle', @onChangeAngle)
    @listenTo(@model, 'change:costumes', @onChangeCostumes)
    @listenTo(@model, 'change:costumeNames', @onChangeCostumeNames)
    @listenTo(@model, 'change:costumeIndex', @onChangeCostumeIndex)
    @listenTo(@model, 'change:rotationStyle', @onChangeRotationStyle)
    @listenTo(@model, 'change', @onChange)

    @callAllOnChangeAttributes_()

  render: ->
    @onChange(@model)

    @$el.find('.modal-body').block
      message: null

    unblock = =>
      @$el.find('.modal-body').unblock()
      null # HACK: if return unblock(), does not call fail().

    @$el.modal
      backdrop: 'static'
    @$el.modal('show')

    dfr = $.Deferred()
    $.ajax
      url: '/costumes/'
      type: 'GET'
      cache: false
      success: (data, textStatus, jqXHR) -> dfr.resolve(data)
      error: dfr.reject
    dfr.promise()
      .then (data) =>
        @replaceCostumeSelectorHtml_(data)
      .then(unblock, unblock)
      .fail =>
        @$el.modal('hide')
        errorMessage(<%= bm('.error') %>)

  replaceCostumeSelectorHtml_: (html) ->
    @$el.find('#character-modal-costume-selector').replaceWith(html)

    self = @
    @$el.find('#character-modal-costume-selector a.remove-button').click (e) ->
      e.preventDefault()

      link = $(@)
      if confirm(link.attr("data-message"))
        dfr = $.Deferred()
        $.ajax
          url: link.attr("data-url")
          type: "DELETE"
          cache: false
          data: { "_method": "DELETE" },
          success: (data, textStatus, jqXHR) -> dfr.resolve(data)
          error: dfr.reject
        dfr.promise()
          .then (data) ->
            self.replaceCostumeSelectorHtml_(data)
          .fail ->
            errorMessage(<%= bm('.error_remove_costume') %>)

    @$el.find('#character-modal-upload-costume-form').fileupload
      dataType: "html"
      done: (e, data) =>
        @replaceCostumeSelectorHtml_(data.result)

    @setActiveCostume_()
    @renderCostumeSet_()

    # HACK: ダイアログを表示して50ms程度待たないと画像のサイズが取得できなかった
    f = ->
      if @readImageSizeflag
        img = $('#character-modal-costume-selector .active img')
        if img.width() > 0
          $('#character-modal-character').css
            width: "#{img.width() / 2}px"
            height: "#{img.height() / 2}px"
          @readImageSizeflag = false
        else
          setTimeout(_.bind(f, @), 50)
    if @readImageSizeflag
      setTimeout(_.bind(f, @), 1)

  setActiveCostume_: ->
    @$el.find('#character-modal-costume-selector a.thumbnail').removeClass('active')
    thumb = @$el.find("#character-modal-costume-selector img[alt=\"#{@model.costume()}\"]")
    thumb.parent().addClass('active')
    if thumb.width() > 0
      $('#character-modal-character').css
        width: "#{thumb.width() / 2}px"
        height: "#{thumb.height() / 2}px"
    else
      @readImageSizeflag = true

  renderCostumeSet_: ->
    costumesEl = @$el.find('#character-modal-costume-set')
    costumesEl.children().remove()

    costumes = @model.get('costumes')
    $.each costumes, (i, name) =>
      costume =
        name: @model.costumeName(i)
        basename: name
        path: @model.costumeUrl(i)
        index: i + 1

      html = $(_.template(@templateText, costume))
      costumesEl.append(html)

      html.find('a.costume').click (e) =>
        e.preventDefault()
        @model.set({ costumeIndex: i })

      removeButton = html.find('a.remove-button')
      if costumes.length <= 1
        removeButton.hide()
      else
        removeButton.click (e) =>
          e.preventDefault()
          @removeCostume_(i)

    @renderActiveCostume_()

  removeCostume_: (i) ->
    costumes = @model.get('costumes').slice(0)
    costumes.splice(i, 1)
    costumeNames = @model.get('costumeNames').slice(0)
    costumeNames.splice(i, 1)
    costumeIndex = @model.get('costumeIndex')
    if costumeIndex >= costumes.length
      costumeIndex = costumes.length - 1
    @model.set
      costumes: costumes
      costumeNames: costumeNames
      costumeIndex: costumeIndex

  renderActiveCostume_: ->
    costumesEl = @$el.find('#character-modal-costume-set')
    costumesEl.find('.item').removeClass('active')
    costumeIndex = @model.get('costumeIndex')
    costumesEl.find(".item:nth-child(#{costumeIndex + 1})").addClass('active')

  callAllOnChangeAttributes_: ->
    @onChangeName(@model, @model.get('name'))
    @onChangeX(@model, @model.get('x'))
    @onChangeY(@model, @model.get('y'))
    @onChangeAngle(@model, @model.get('angle'))
    @onChangeCostumes(@model, @model.get('costumes'))
    @onChangeRotationStyle(@model, @model.get('rotationStyle'))

  onChangeName: (model, value, options) ->
    @$el.find('input[name="character[name]"]').val(value)

  onChangeX: (model, value, options) ->
    @$el.find('input[name="character[x]"]').val(value)
    $('#character_x_value').text(value)
    $('#character-modal-character').css('left', parseInt(value * @previewZoomLevel))

  onChangeY: (model, value, options) ->
    @$el.find('input[name="character[y]"]').val(value)
    $('#character_y_value').text(value)
    $('#character-modal-character').css('top', parseInt(value * @previewZoomLevel))

  onChangeAngle: (model, value, options) ->
    @$el.find('input[name="character[angle]"]').val(value)
    $('#character_angle_value').text("#{value}°")
    rotate = "rotate(#{value}deg)"
    $('#character_angle_vector').css
      '-moz-transform': rotate
      '-webkit-transform': rotate
      transform: rotate

    @model.rotateImage('#character-modal-character')

  onChangeCostumes: (model, value, options) ->
    @renderCostumeSet_()
    @onChangeCostumeIndex(model, model.get('costumeIndex'))

  onChangeCostumeNames: (model, value, options) ->
    $.each value, (i, costumeName) =>
      @$el.find("#character-modal-costume-set .item:nth-child(#{i + 1}) .name").text(costumeName)

  onChangeCostumeIndex: (model, value, options) ->
    img = $('<img>').attr
      src: model.costumeUrl()
      alt: model.costume()
    $('#character-modal-character img').replaceWith(img)
    $('#character-modal input[name="costume[name]"]').val(model.costumeName())

    @setActiveCostume_()
    @renderActiveCostume_()

  onChangeRotationStyle: (model, value, options) ->
    $('#character_rotation_style button.btn').removeClass('btn-primary')
    $("#character_rotation_style_#{value}").addClass('btn-primary')
    @onChangeAngle(@model, @model.get('angle'))

  onChange: (model, options) ->
    @validate_()

  validate_: ->
    return unless @target

    valid = true

    @$el.find('.control-group').removeClass('error')
    @$el.find('#character-modal-costume-set .item').removeClass('error')

    unless @model.isValid()
      valid = false
      $.each @model.validationError, (i, error) =>
        if error.attr == 'name'
          @$el.find('.control-group[for="character[name]"]').addClass('error')
        if error.attr == 'costumeNames'
          if error.index == @model.get('costumeIndex')
            @$el.find('.control-group[for="costume[name]"]').addClass('error')
          @$el.find("#character-modal-costume-set .item:nth-child(#{error.index + 1})").addClass('error')

    name = @model.get('name')
    if @target.get('name') != name &&
       Smalruby.Collections.CharacterSet.findWhere({ name: name })
      valid = false
      @$el.find('.control-group[for="character[name]"]').addClass('error')

    valid

  onSelectCostume: (e) ->
    e.preventDefault()

    attrs = {}

    costume = $(e.target).attr('alt') || $(e.target).find('img').attr('alt')
    if @model.costume() != costume
      costumes = @model.get('costumes').slice(0)
      costumes[@model.get('costumeIndex')] = costume
      attrs['costumes'] = costumes

    unless @nameChanged
      prefix = Smalruby.Character.costumeToNamePrefix(costume)
      if prefix != @model.namePrefix()
        if prefix == @target.namePrefix()
          attrs['name'] = @target.get('name')
        else
          attrs['name'] = Smalruby.Collections.CharacterSet.uniqueName(costume)

    @model.set(attrs)

  onUploadCostume: (e) ->
    e.preventDefault()

    @$el.find('input[name="costume[file]"]').click()

  onRotationStyleFree: ->
    @model.set({ rotationStyle: 'free' })

  onRotationStyleLeftRight: ->
    @model.set({ rotationStyle: 'left_right' })

  onRotationStyleNone: ->
    @model.set({ rotationStyle: 'none' })

  onOk: ->
    if @validate_()
      @$el.modal('hide')
      if @target
        @target.set(_.clone(@model.attributes))

  setCharacter: (character)->
    @target = character
    @model.set(_.clone(@target.attributes))
    @nameChanged = false
    @