Redesign the crafting page
This introduces the new crafting page design featuring a new vertical layout and list display of all crafting steps. It also makes it easy to move items between the have/need sections and even from each step into your inventory (causing a new crafting plan to be created which doesn't plan on making that item). * Convert the `BaseController.hide` and `show` methods to be much more robust in the use of CSS transitions (using a timer in case a transition doesn't actually fire). * Change `hide`/`show` to use events for communicating animation state instead of callbacks * Convert several controllers to fire events for communication with parent controllers instead of accepting functions. * Rewrite the `CraftPageController`, `CraftPage` model and related Jade/SCSS files to implement the new design. * Alter the `InventoryController` and `StackController` to allow a variety of action buttons, and for the events from those action buttons to bubble up to parent controllers * Add the `StepController` and its related model, template, and style sheet as part of the new craft page. * Tweak the `ItemSelectorController` and related files to allow for a variety of styles for the launcher button
This commit is contained in:
+32
-19
@@ -37,32 +37,45 @@ Duration.fast = 200
|
||||
Duration.normal = 400
|
||||
Duration.slow = 1200
|
||||
|
||||
exports.Event = Event = {}
|
||||
Event.add = 'add' # collection, item...
|
||||
Event.change = 'change' # model
|
||||
Event.click = 'click' # event
|
||||
Event.load = {}
|
||||
Event.load.started = 'load:started' # controller, url
|
||||
Event.load.succeeded = 'load:succeeded' # controller, book
|
||||
Event.load.failed = 'load:failed' # controller, error message
|
||||
Event.load.finished = 'load:finished' # controller
|
||||
Event.remove = 'remove' # collection, item...
|
||||
Event.request = 'request' # model
|
||||
Event.route = 'route'
|
||||
Event.sort = 'sort'
|
||||
Event.sync = 'sync' # model, response
|
||||
Event.transitionEnd = (->
|
||||
exports.Event = Event = {}
|
||||
Event.add = 'add' # collection, item...
|
||||
Event.animate = {}
|
||||
Event.animate.hide = {}
|
||||
Event.animate.hide.start = 'animate:hide:start' # controller, selector
|
||||
Event.animate.hide.finish = 'animate:hide:finish' # controller, selector
|
||||
Event.animate.show = {}
|
||||
Event.animate.show.start = 'animate:show:start' # controller, selector
|
||||
Event.animate.show.finish = 'animate:show:finish' # controller, selector
|
||||
Event.button = {}
|
||||
Event.button.complete = 'button:complete' # controller
|
||||
Event.button.first = 'button:first' # controller, buttonType
|
||||
Event.button.second = 'button:second' # controller, buttonType
|
||||
Event.change = 'change' # model
|
||||
Event.click = 'click' # event
|
||||
Event.load = {}
|
||||
Event.load.started = 'load:started' # controller, url
|
||||
Event.load.succeeded = 'load:succeeded' # controller, book
|
||||
Event.load.failed = 'load:failed' # controller, error message
|
||||
Event.load.finished = 'load:finished' # controller
|
||||
Event.remove = 'remove' # collection, item...
|
||||
Event.request = 'request' # model
|
||||
Event.route = 'route'
|
||||
Event.sort = 'sort'
|
||||
Event.sync = 'sync' # model, response
|
||||
Event.transitionEnd = (->
|
||||
return unless document?
|
||||
transitions =
|
||||
'transition': 'transitionend',
|
||||
'OTransition': 'oTransitionEnd',
|
||||
'MSTransition': 'msTransitionEnd',
|
||||
'MozTransition': 'transitionend',
|
||||
'WebkitTransition': 'webkitTransitionEnd'
|
||||
'MozTransition': 'transitionend'
|
||||
'MSTransition': 'msTransitionEnd'
|
||||
'OTransition': 'oTransitionEnd'
|
||||
'transition': 'transitionend'
|
||||
|
||||
el = document.createElement 'fakeelement'
|
||||
for styleName, eventName of transitions
|
||||
return eventName if el.style[styleName]?
|
||||
|
||||
throw new Error 'cannot determine transitionEnd event'
|
||||
)()
|
||||
|
||||
exports.Key = Key = {}
|
||||
|
||||
@@ -5,22 +5,22 @@ Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
views = require '../views'
|
||||
{Event} = require '../constants'
|
||||
views = require '../views'
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class BaseController extends Backbone.View
|
||||
|
||||
constructor: (options={})->
|
||||
options.useAnimations ?= true
|
||||
@tryRefresh = _.debounce @_tryRefresh, 100
|
||||
|
||||
@_model = null
|
||||
@_rendered = false
|
||||
@_parent = options.parent
|
||||
@_children = []
|
||||
@_useAnimations = options.useAnimations
|
||||
@_model = null
|
||||
@_parent = options.parent
|
||||
@_rendered = false
|
||||
@_useAnimations = options.useAnimations ?= true
|
||||
|
||||
@_loadTemplate options.templateName
|
||||
super options
|
||||
@@ -36,33 +36,39 @@ module.exports = class BaseController extends Backbone.View
|
||||
@_children.push child
|
||||
return child
|
||||
|
||||
hide: (args...)->
|
||||
{$el, callback} = @_resolveShowHideArgs args
|
||||
hide: ($el)->
|
||||
$el ?= @$el
|
||||
|
||||
$el.addClass 'hideable' unless $el.hasClass 'hideable'
|
||||
if not $el.hasClass 'hideable'
|
||||
$el.addClass 'hideable'
|
||||
_.defer => @hide $el
|
||||
return
|
||||
|
||||
logger.verbose => "#{this} is hiding #{$el.selector}"
|
||||
if $el.hasClass('hiding') or $el.hasClass('hidden')
|
||||
_.defer => callback this
|
||||
else
|
||||
$el.off Event.transitionEnd
|
||||
$el.one Event.transitionEnd, =>
|
||||
if not ($el.hasClass('hiding') or $el.hasClass('hidden'))
|
||||
logger.verbose => "#{this} is hiding \"#{$el.selector}\""
|
||||
@_onAnimationComplete $el, Duration.normal, =>
|
||||
$el.addClass 'hidden'
|
||||
$el.removeClass 'hiding'
|
||||
callback this
|
||||
logger.verbose => "#{this} is finished hiding #{$el.selector}"
|
||||
@trigger Event.animate.hide.finish, this, $el
|
||||
|
||||
$el.addClass 'hiding'
|
||||
@trigger Event.animate.hide.start, this, $el
|
||||
|
||||
refresh: ->
|
||||
logger.verbose => "#{this} refreshing"
|
||||
|
||||
remove: ->
|
||||
if @_useAnimations
|
||||
@hide =>
|
||||
logger.verbose => "#{this} is hiding element before removing it"
|
||||
@once Event.animate.hide.finish, =>
|
||||
logger.verbose => "#{this} is removing its element from the DOM"
|
||||
@$el.remove()
|
||||
@off()
|
||||
@hide()
|
||||
else
|
||||
@$el.remove()
|
||||
@off()
|
||||
|
||||
routeLinkClick: (event)->
|
||||
event.preventDefault()
|
||||
@@ -75,19 +81,22 @@ module.exports = class BaseController extends Backbone.View
|
||||
else
|
||||
router.navigate href, trigger:true
|
||||
|
||||
show: (args...)->
|
||||
{$el, callback} = @_resolveShowHideArgs args
|
||||
show: ($el)->
|
||||
$el ?= @$el
|
||||
$el.addClass 'hideable'
|
||||
|
||||
$el.addClass 'hideable' unless $el.hasClass 'hideable'
|
||||
if $el.hasClass('hidden') or $el.hasClass('hiding')
|
||||
logger.verbose => "#{this} is showing \"#{$el.selector}\""
|
||||
|
||||
logger.verbose => "#{this} is showing #{$el.selector}"
|
||||
if $el.hasClass('hiding') or $el.hasClass('hidden')
|
||||
$el.off Event.transitionEnd
|
||||
$el.one Event.transitionEnd, => callback this
|
||||
$el.removeClass 'hiding'
|
||||
@_onAnimationComplete $el, Duration.normal, =>
|
||||
logger.verbose => "#{this} is finished showing #{$el.selector}"
|
||||
@trigger Event.animate.show.finish, this, $el
|
||||
|
||||
$el.addClass 'hiding'
|
||||
$el.removeClass 'hidden'
|
||||
else
|
||||
_.defer => callback this
|
||||
_.defer =>
|
||||
$el.removeClass 'hiding'
|
||||
@trigger Event.animate.show.start, this, $el
|
||||
|
||||
unrender: ->
|
||||
@undelegateEvents()
|
||||
@@ -164,14 +173,12 @@ module.exports = class BaseController extends Backbone.View
|
||||
$renderedEl = $($(@_template(data))[0])
|
||||
|
||||
@onWillRender()
|
||||
@hide() if @_useAnimations
|
||||
|
||||
@unrender()
|
||||
|
||||
@$el.append $renderedEl.children()
|
||||
@$el.addClass $renderedEl.attr 'class'
|
||||
@$el.data $renderedEl.data()
|
||||
@delegateEvents()
|
||||
@show() if options.show and @_useAnimations
|
||||
|
||||
@_rendered = true
|
||||
@onDidRender()
|
||||
@@ -189,19 +196,20 @@ module.exports = class BaseController extends Backbone.View
|
||||
if templateName?
|
||||
@_template = views[templateName]
|
||||
|
||||
_resolveShowHideArgs: (args)->
|
||||
if args.length is 0
|
||||
return $el:@$el, callback:->
|
||||
else if args.length is 1
|
||||
if _.isFunction args[0]
|
||||
return $el:@$el, callback:args[0]
|
||||
else
|
||||
return $el:args[0], callback:->
|
||||
else if args.length is 2
|
||||
return $el:args[0], callback:args[1]
|
||||
else
|
||||
throw new Error "expected to get 0, 1, or 2 args"
|
||||
|
||||
_tryRefresh: ->
|
||||
return unless @_rendered
|
||||
@refresh()
|
||||
|
||||
_onAnimationComplete: ($el, maxDuration, callback)->
|
||||
$el ?= @$el
|
||||
maxDuration ?= Duration.slow
|
||||
callback ?= -> # do nothing
|
||||
|
||||
$el.off Event.transitionEnd
|
||||
$el.one Event.transitionEnd, callback
|
||||
|
||||
# One would very much like this not to be necessary, but because of the way CSS transitions work, they are
|
||||
# frequently not triggered. Rather than try to avoid the multitude of corner cases where this can happen, this
|
||||
# simply sets a timer to ensure the event does eventually trigger. Since we're only listening for the first
|
||||
# event, if the transition end event *did* happen, then this is a no-op. -- Andrew 2015-04-18
|
||||
setTimeout (-> $el.trigger Event.transitionEnd), maxDuration
|
||||
|
||||
@@ -5,17 +5,13 @@ Copyright (c) 2014-2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
CraftingTableController = require './crafting_table_controller'
|
||||
CraftPage = require '../models/craft_page'
|
||||
{Event} = require '../constants'
|
||||
ImageLoader = require './image_loader'
|
||||
InventoryController = require './inventory_controller'
|
||||
ModPackController = require './mod_pack_controller'
|
||||
NameFinder = require '../models/name_finder'
|
||||
PageController = require './page_controller'
|
||||
Storage = require '../models/storage'
|
||||
{Text} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
AdsenseController = require './adsense_controller'
|
||||
CraftPage = require '../models/craft_page'
|
||||
InventoryController = require './inventory_controller'
|
||||
PageController = require './page_controller'
|
||||
StepController = require './step_controller'
|
||||
{Event} = require '../constants'
|
||||
{Url} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -24,20 +20,56 @@ module.exports = class CraftPageController extends PageController
|
||||
constructor: (options={})->
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
if not options.storage? then throw new Error 'options.storage is required'
|
||||
|
||||
options.model ?= new CraftPage modPack:options.modPack
|
||||
options.storage ?= new Storage storage:window.localStorage
|
||||
options.templateName = 'craft_page'
|
||||
options.model ?= new CraftPage modPack:options.modPack
|
||||
options.templateName = 'craft_page'
|
||||
super options
|
||||
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
@storage = options.storage
|
||||
|
||||
@model.plan.on Event.change, => @refresh()
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onToolsBoxToggled: ->
|
||||
@model.plan.includingTools = @$('.includeTools:checked').length isnt 0
|
||||
onCraftTool: (itemSlug)->
|
||||
# TODO: implement this
|
||||
|
||||
onHaveInventoryChanged: ->
|
||||
@storage.store 'crafting-plan:have', @model.plan.have.unparse()
|
||||
|
||||
onMoveNeedToHave: (itemSlug)->
|
||||
quantity = @model.plan.need.quantityOf itemSlug
|
||||
@model.plan.have.add itemSlug, quantity
|
||||
@_saveHaveInventory()
|
||||
|
||||
onRemoveFromHaveInventory: (itemSlug)->
|
||||
@model.plan.have.remove itemSlug
|
||||
@onHaveInventoryChanged()
|
||||
|
||||
onRemoveFromWant: (itemSlug)->
|
||||
@model.plan.want.remove itemSlug
|
||||
@onWantInventoryChange()
|
||||
|
||||
onStopUsingTool: (itemSlug)->
|
||||
# TODO: implement this
|
||||
|
||||
onStepComplete: (stepController)->
|
||||
step = stepController.model
|
||||
for stack in step.recipe.output
|
||||
@model.plan.have.add stack.itemSlug, stack.quantity * step.multiplier
|
||||
@onHaveInventoryChanged()
|
||||
|
||||
onWantInventoryChange: ->
|
||||
text = @model.plan.want.unparse()
|
||||
url = Url.crafting inventoryText:text
|
||||
router.navigate url
|
||||
|
||||
if @model.plan.want.isEmpty
|
||||
@model.plan.have.clear()
|
||||
@onHaveInventoryChanged()
|
||||
|
||||
# PageController Overrides #####################################################################
|
||||
|
||||
@@ -46,69 +78,105 @@ module.exports = class CraftPageController extends PageController
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@adsenseController = @addChild AdsenseController, '.view__adsense', model:'sidebar_skyscraper'
|
||||
|
||||
@wantInventoryController = @addChild InventoryController, '.want .view__inventory',
|
||||
imageLoader: @imageLoader
|
||||
isAcceptable: (item)=> item.isCraftable
|
||||
model: @model.plan.want
|
||||
modPack: @modPack
|
||||
firstButtonType: 'remove'
|
||||
@wantInventoryController.on Event.button.first, (c, s)=> @onRemoveFromWant(s)
|
||||
@wantInventoryController.on Event.change, (c)=> @onWantInventoryChange()
|
||||
|
||||
# @toolsInUseController = @addChild InventoryController, '.tools .view__inventory',
|
||||
# imageLoader: @imageLoader
|
||||
# model: @model.plan.toolsInUse
|
||||
# modPack: @modPack
|
||||
# firstButtonType: 'down'
|
||||
# secondButtonType: 'up'
|
||||
# @toolsInUseController.on Event.button.first, (c, s)=> @onStopUsingTool(s)
|
||||
# @toolsInUseController.of Event.button.second, (c, s)=> @onCraftTool(s)
|
||||
|
||||
@haveInventoryController = @addChild InventoryController, '.have .view__inventory',
|
||||
imageLoader: @imageLoader
|
||||
model: @model.plan.have
|
||||
modPack: @modPack
|
||||
firstButtonType: 'down'
|
||||
@haveInventoryController.on Event.button.first, (c, s)=>
|
||||
@onRemoveFromHaveInventory(s)
|
||||
@haveInventoryController.on Event.change, (c)=> @onHaveInventoryChanged()
|
||||
|
||||
@needInventoryController = @addChild InventoryController, '.need .view__inventory',
|
||||
editable: false
|
||||
imageLoader: @imageLoader
|
||||
model: @model.plan.need
|
||||
modPack: @modPack
|
||||
firstButtonType: 'up'
|
||||
@needInventoryController.on Event.button.first, (c, s)=> @onMoveNeedToHave(s)
|
||||
|
||||
@$instructionsSection = @$('.instructions.section')
|
||||
@$makeSection = @$('.make.section')
|
||||
@$toolsSection = @$('.tools.section')
|
||||
@$ingredientsSection = @$('.ingredients.section')
|
||||
@$stepsSection = @$('.steps.section')
|
||||
@$stepsContainer = @$('.steps.section .panel')
|
||||
|
||||
super
|
||||
|
||||
onWillRender: ->
|
||||
@storage.register 'crafting-plan', @model.plan, 'includingTools'
|
||||
@model.plan.have.clear()
|
||||
@model.plan.have.parse @storage.load('crafting-plan:have')
|
||||
super
|
||||
|
||||
onDidRender: ->
|
||||
@wantController = @addChild InventoryController, '.want',
|
||||
editable: true
|
||||
icon: '/images/fishing_rod.png'
|
||||
imageLoader: @imageLoader
|
||||
isAcceptable: (item)-> item.isCraftable
|
||||
model: @model.plan.want
|
||||
modPack: @model.modPack
|
||||
onChange: => @_updateLocation()
|
||||
title: 'Items you want'
|
||||
|
||||
@haveController = @addChild InventoryController, '.have',
|
||||
editable: true,
|
||||
imageLoader: @imageLoader
|
||||
model: @model.plan.have
|
||||
modPack: @model.modPack
|
||||
onChange: => @_saveHaveInventory()
|
||||
title: 'Items you have'
|
||||
|
||||
@needController = @addChild InventoryController, '.need',
|
||||
editable: false
|
||||
icon: '/images/boots.png'
|
||||
imageLoader: @imageLoader
|
||||
model: @model.plan.need
|
||||
modPack: @model.modPack
|
||||
title: "Items you'll need"
|
||||
|
||||
@craftingTableController = @addChild CraftingTableController, '.view__crafting_table',
|
||||
imageLoader: @imageLoader
|
||||
model: @model.table
|
||||
modPack: @model.modPack
|
||||
|
||||
@$('.want .toolbar').append '<label><input class="includeTools" type="checkbox"> include tools</label>'
|
||||
@$includeToolsBox = @$('.includeTools')
|
||||
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
if @model.plan.includingTools
|
||||
@$includeToolsBox.attr 'checked', 'checked'
|
||||
else
|
||||
@$includeToolsBox.removeAttr 'checked'
|
||||
|
||||
@_refreshSectionVisibility()
|
||||
@_refreshSteps()
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
# Backbone.View Overrides ########################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'change .includeTools': 'onToolsBoxToggled'
|
||||
'click .instructions a': 'routeLinkClick'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
# Private Methods ################################################################################
|
||||
|
||||
_saveHaveInventory: ->
|
||||
@storage.store 'crafting-plan:have', @model.plan.have.unparse()
|
||||
_isStepCompletable: (controller)->
|
||||
return not @model.plan.want.hasAtLeast controller.model.outputItemSlug
|
||||
|
||||
_updateLocation: ->
|
||||
text = @model.plan.want.unparse()
|
||||
url = Url.crafting inventoryText:text
|
||||
router.navigate url
|
||||
_refreshSectionVisibility: ->
|
||||
if @model.plan.want.isEmpty
|
||||
for $el in [@$toolsSection, @$ingredientsSection, @$stepsSection]
|
||||
@hide $el
|
||||
@show @$instructionsSection
|
||||
else
|
||||
for $el in [@$toolsSection, @$ingredientsSection, @$stepsSection]
|
||||
@show $el
|
||||
@hide @$instructionsSection
|
||||
|
||||
_refreshSteps: ->
|
||||
@_stepControllers ?= []
|
||||
index = 0
|
||||
|
||||
for step in @model.plan.steps
|
||||
controller = @_stepControllers[index]
|
||||
if not controller?
|
||||
controller = new StepController
|
||||
canComplete: (controller)=> @_isStepCompletable(controller)
|
||||
imageLoader: @imageLoader
|
||||
model: step
|
||||
modPack: @modPack
|
||||
controller.on Event.button.complete, (c)=> @onStepComplete(c)
|
||||
controller.render()
|
||||
|
||||
@$stepsContainer.append controller.$el
|
||||
@_stepControllers.push controller
|
||||
else
|
||||
controller.model = step
|
||||
|
||||
index += 1
|
||||
|
||||
while @_stepControllers.length > index
|
||||
@_stepControllers.pop().remove()
|
||||
|
||||
@@ -81,7 +81,7 @@ module.exports = class HeaderController extends BaseController
|
||||
addThisUrl = '//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-54f3c9717b2e530d'
|
||||
$('body').append "<script async src=\"#{addThisUrl}\"></script>"
|
||||
|
||||
@_itemSelector = @addChild ItemSelectorController, '.view__item_selector',
|
||||
@_itemSelector = @addChild ItemSelectorController, '.view__item_selector.search',
|
||||
modPack: @modPack
|
||||
onChoseItem: (itemSlug)=> @_goToItem(itemSlug)
|
||||
|
||||
|
||||
@@ -16,6 +16,14 @@ StackController = require './stack_controller'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
###
|
||||
Events:
|
||||
clear(this)
|
||||
change(this)
|
||||
add(this, itemSlug)
|
||||
button:first(this, itemSlug)
|
||||
button:second(this, itemSlug)
|
||||
###
|
||||
module.exports = class InventoryController extends BaseController
|
||||
|
||||
@MAX_QUANTITY = 9999
|
||||
@@ -27,13 +35,16 @@ module.exports = class InventoryController extends BaseController
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
|
||||
@editable = options.editable ?= true
|
||||
@icon = options.icon ?= '/images/chest_front.png'
|
||||
@imageLoader = options.imageLoader
|
||||
@isAcceptable = options.isAcceptable ?= null
|
||||
@modPack = options.modPack
|
||||
@onChange = options.onChange ?= -> # do nothing
|
||||
@title = options.title ?= 'Inventory'
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
|
||||
@editable = options.editable ?= true
|
||||
@icon = options.icon ?= '/images/chest_front.png'
|
||||
@firstButtonType = options.firstButtonType ?= null
|
||||
@isAcceptable = options.isAcceptable ?= null
|
||||
@secondButtonType = options.secondButtonType ?= null
|
||||
@shouldEnableButton = options.shouldEnableButton ?= (model, button)-> true
|
||||
@title = options.title ?= 'Inventory'
|
||||
|
||||
options.templateName = 'inventory'
|
||||
super options
|
||||
@@ -46,11 +57,19 @@ module.exports = class InventoryController extends BaseController
|
||||
|
||||
onClearButtonClicked: ->
|
||||
@model.clear()
|
||||
@onChange()
|
||||
@trigger Event.clear, this
|
||||
@trigger Event.change, this
|
||||
|
||||
onFirstButtonClicked: (stackController)->
|
||||
@trigger Event.button.first, this, stackController?.model?.itemSlug
|
||||
|
||||
onItemChosen: (itemSlug)->
|
||||
@model.add itemSlug, 1
|
||||
@onChange()
|
||||
@trigger Event.add, this, itemSlug
|
||||
@trigger Event.change, this
|
||||
|
||||
onSecondButtonClicked: (stackController)->
|
||||
@trigger Event.button.second, this, stackController?.model?.itemSlug
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
@@ -60,32 +79,23 @@ module.exports = class InventoryController extends BaseController
|
||||
modPack: @modPack,
|
||||
onChoseItem: (itemSlug)=> @onItemChosen(itemSlug)
|
||||
|
||||
@$clearButton = @$('button[name="clear"]')
|
||||
@$clearButton = @$('button.clear')
|
||||
@$icon = @$('.icon')
|
||||
@$editPanel = @$('.edit')
|
||||
@$scrollbox = @$('.scrollbox')
|
||||
@$table = @$('table')
|
||||
@$toolbar = @$('.toolbar')
|
||||
@$itemContainer = @$('.item_container')
|
||||
@$title = @$('h2 p')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
if @editable
|
||||
@show @$editPanel
|
||||
@show @$toolbar, =>
|
||||
@$scrollbox.css bottom:@$toolbar.height()
|
||||
@selector.show()
|
||||
else
|
||||
if not @$editPanel? then throw new Error "no edit panel"
|
||||
if not @$toolbar? then throw new Error "no toolbar"
|
||||
@hide @$editPanel
|
||||
@hide @$toolbar
|
||||
@$scrollbox.css bottom:0
|
||||
@selector.hide()
|
||||
|
||||
@$icon.attr 'src', @icon
|
||||
@$title.html @title
|
||||
@$clearButton.disabled = @model.isEmpty
|
||||
|
||||
@_refreshStacks()
|
||||
@_refreshButtonState()
|
||||
|
||||
super
|
||||
|
||||
@@ -93,38 +103,37 @@ module.exports = class InventoryController extends BaseController
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click button[name="clear"]': 'onClearButtonClicked'
|
||||
'click button.clear': 'onClearButtonClicked'
|
||||
'click button.first': 'onFirstButtonClicked'
|
||||
'click button.second': 'onSecondButtonClicked'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshButtonState: ->
|
||||
if @model.isEmpty then @$clearButton.attr('disabled', 'disabled') else @$clearButton.removeAttr('disabled')
|
||||
|
||||
_refreshStacks: ->
|
||||
@_stackControllers ?= []
|
||||
index = 0
|
||||
|
||||
$lastRow = @$table.find 'tr:last-child'
|
||||
@model.each (stack)=>
|
||||
controller = @_stackControllers[index]
|
||||
if not controller?
|
||||
controller = new StackController
|
||||
editable: @editable
|
||||
imageLoader: @imageLoader
|
||||
model: stack
|
||||
modPack: @modPack
|
||||
onChange: @onChange
|
||||
onRemove: if not @editable then null else (stack)=> @_removeStack(stack)
|
||||
editable: @editable
|
||||
firstButtonType: @firstButtonType
|
||||
imageLoader: @imageLoader
|
||||
model: stack
|
||||
modPack: @modPack
|
||||
secondButtonType: @secondButtonType
|
||||
shouldEnableButton: @shouldEnableButton
|
||||
controller.on Event.change, => @trigger Event.change, this
|
||||
controller.on Event.button.first, (c)=> @onFirstButtonClicked(c)
|
||||
controller.on Event.button.second, (c)=> @onSecondButtonClicked(c)
|
||||
controller.render()
|
||||
|
||||
@_stackControllers.push controller
|
||||
controller.$el.insertBefore $lastRow
|
||||
controller.render()
|
||||
@$itemContainer.append controller.$el
|
||||
else
|
||||
controller.model = stack
|
||||
index += 1
|
||||
|
||||
while @_stackControllers.length > index
|
||||
@_stackControllers.pop().remove()
|
||||
|
||||
_removeStack: (stack)->
|
||||
@model.remove stack.itemSlug, stack.quantity
|
||||
|
||||
@@ -65,7 +65,6 @@ module.exports = class ItemGroupController extends BaseController
|
||||
_refreshItems: ->
|
||||
@_itemControllers ?= []
|
||||
controllerIndex = 0
|
||||
delay = 0
|
||||
|
||||
for item in @model
|
||||
controller = @_itemControllers[controllerIndex]
|
||||
|
||||
@@ -24,6 +24,7 @@ module.exports = class MinimalRecipeController extends BaseController
|
||||
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
@multiplier = options.multiplier ?= null
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
@@ -36,6 +37,7 @@ module.exports = class MinimalRecipeController extends BaseController
|
||||
imageLoader: @imageLoader
|
||||
modPack: @modPack
|
||||
|
||||
@$multiplier = @$('.multiplier')
|
||||
@$outputImg = @$('.output img')
|
||||
@$outputLink = @$('.output a')
|
||||
@$outputQuantity = @$('.quantity')
|
||||
@@ -46,6 +48,8 @@ module.exports = class MinimalRecipeController extends BaseController
|
||||
@gridController.model = @model
|
||||
@outputSlotController.model = @model?.output?[0]
|
||||
@$el.tooltip show:{delay:Duration.snap, duration:Duration.fast}
|
||||
|
||||
@_refreshMultiplier()
|
||||
@_refreshTools()
|
||||
super
|
||||
|
||||
@@ -57,6 +61,12 @@ module.exports = class MinimalRecipeController extends BaseController
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_refreshMultiplier: ->
|
||||
if @multiplier? and @multiplier > 1
|
||||
@$multiplier.html "x#{@multiplier}"
|
||||
else
|
||||
@$multiplier.html ''
|
||||
|
||||
_refreshTools: ->
|
||||
@$toolContainer.empty()
|
||||
return unless @model?
|
||||
|
||||
@@ -159,7 +159,7 @@ module.exports = class ModPageController extends PageController
|
||||
@$tutorialsContainer.append controller.$el
|
||||
controller.render()
|
||||
else
|
||||
controller.model = model
|
||||
controller.model = tutorial
|
||||
|
||||
index += 1
|
||||
|
||||
|
||||
@@ -6,13 +6,20 @@ All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
ImageLoader = require './image_loader'
|
||||
{Duration} = require '../constants'
|
||||
{Event} = require '../constants'
|
||||
ImageLoader = require './image_loader'
|
||||
{Key} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
###
|
||||
Events:
|
||||
change(this)
|
||||
change:quantity(this, oldQuantity, newQuantity)
|
||||
button:first(this, type)
|
||||
button:second(this, type)
|
||||
###
|
||||
module.exports = class StackController extends BaseController
|
||||
|
||||
@MAX_QUANTITY = 9999
|
||||
@@ -22,60 +29,67 @@ module.exports = class StackController extends BaseController
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
|
||||
options.editable ?= false
|
||||
options.onChange ?= -> # do nothing
|
||||
options.onRemove ?= (stack)-> # do nothing
|
||||
options.tagName = 'tr'
|
||||
options.templateName = 'stack'
|
||||
super options
|
||||
|
||||
@editable = options.editable
|
||||
@modPack = options.modPack
|
||||
@onChange = options.onChange
|
||||
@onRemove = options.onRemove
|
||||
@_imageLoader = options.imageLoader
|
||||
@editable = options.editable ?= false
|
||||
@firstButtonType = options.firstButtonType ?= null
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
@secondButtonType = options.secondButtonType ?= null
|
||||
@shouldEnableButton = options.shouldEnableButton ?= (model, button)-> true
|
||||
|
||||
@modPack.on Event.change, => @tryRefresh()
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onFirstButtonClicked: (event)->
|
||||
@trigger Event.button.first, this, @firstButtonType
|
||||
return false
|
||||
|
||||
onQuantityFieldBlur: ->
|
||||
return unless @editable
|
||||
|
||||
@$bubble.removeClass 'editing'
|
||||
|
||||
oldQuantity = @_priorValue
|
||||
quantityText = @$quantityField.val().trim()
|
||||
if quantityText.length is 0
|
||||
quantity = @_priorValue
|
||||
newQuantity = oldQuantity
|
||||
else if not quantityText.match /^[0-9]*$/
|
||||
quantity = 1
|
||||
newQuantity = 1
|
||||
else
|
||||
quantity = parseInt quantityText, 10
|
||||
if _.isNaN quantity then quantity = 1
|
||||
newQuantity = parseInt quantityText, 10
|
||||
if _.isNaN newQuantity then newQuantity = 1
|
||||
|
||||
quantity = Math.min quantity, StackController.MAX_QUANTITY
|
||||
quantity = Math.max 1, quantity
|
||||
newQuantity = Math.min newQuantity, StackController.MAX_QUANTITY
|
||||
newQuantity = Math.max 1, newQuantity
|
||||
|
||||
if quantity?
|
||||
@$quantityField.val "#{quantity}"
|
||||
@$quantityField.removeClass 'error', Duration.snap
|
||||
@$quantityField.removeClass 'error-new', Duration.snap
|
||||
if newQuantity? and newQuantity isnt oldQuantity
|
||||
@$quantityField.val "#{newQuantity}"
|
||||
@$bubble.removeClass 'error', Duration.snap
|
||||
@$bubble.removeClass 'error-new', Duration.snap
|
||||
|
||||
@model.quantity = quantity
|
||||
@onChange()
|
||||
@model.quantity = newQuantity
|
||||
@trigger Event.change + ':quantity', this, oldQuantity, newQuantity
|
||||
@trigger Event.change, this
|
||||
|
||||
onQuantityFieldChanged: ->
|
||||
return unless @editable
|
||||
|
||||
quantityText = @$quantityField.val().trim()
|
||||
if not quantityText.match /^[0-9]*$/
|
||||
@$quantityField.addClass 'error', 0
|
||||
@$quantityField.addClass 'error-new', 0
|
||||
@$quantityField.removeClass 'error-new', Duration.fast
|
||||
@$bubble.addClass 'error', 0
|
||||
@$bubble.addClass 'error-new', 0
|
||||
@$bubble.removeClass 'error-new', Duration.fast
|
||||
else
|
||||
@$quantityField.removeClass 'error', Duration.snap
|
||||
@$quantityField.removeClass 'error-new', Duration.snap
|
||||
@$bubble.removeClass 'error', Duration.snap
|
||||
@$bubble.removeClass 'error-new', Duration.snap
|
||||
|
||||
onQuantityFieldFocused: ->
|
||||
|
||||
if @editable
|
||||
@$bubble.addClass 'editing'
|
||||
@_priorValue = @model.quantity
|
||||
@$quantityField.val ''
|
||||
else
|
||||
@@ -87,18 +101,20 @@ module.exports = class StackController extends BaseController
|
||||
if event.which is Key.Return
|
||||
@$quantityField.blur()
|
||||
|
||||
onRemoveClicked: ->
|
||||
@onRemove @model
|
||||
@onChange()
|
||||
onSecondButtonClicked: (event)->
|
||||
event.preventDefault()
|
||||
@trigger Event.button.second, this, @secondButtonType
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@$action = @$('.action')
|
||||
@$bubble = @$('.bubble')
|
||||
@$firstButton = @$('button.first')
|
||||
@$image = @$('.icon img')
|
||||
@$nameLink = @$('.name a')
|
||||
@$quantityField = @$('.quantity input')
|
||||
@$removeButton = @$('button.remove')
|
||||
@$secondButton = @$('button.second')
|
||||
super
|
||||
|
||||
refresh: ->
|
||||
@@ -106,19 +122,23 @@ module.exports = class StackController extends BaseController
|
||||
|
||||
display = @modPack.findItemDisplay @model.itemSlug
|
||||
|
||||
@_imageLoader.load display.iconUrl, @$image
|
||||
@imageLoader.load display.iconUrl, @$image
|
||||
@$nameLink.html display.itemName
|
||||
@$nameLink.attr 'href', display.itemUrl
|
||||
@$quantityField.val @model.quantity
|
||||
|
||||
@_updateButtonType @$firstButton, @firstButtonType
|
||||
@_updateButtonType @$secondButton, @secondButtonType
|
||||
|
||||
@$firstButton.prop('disabled', not @shouldEnableButton(@model, 1)) if @firstButtonType?
|
||||
@$secondButton.prop('disabled', not @shouldEnableButton(@model, 2)) if @secondButtonType?
|
||||
|
||||
if @editable
|
||||
@$quantityField.removeAttr 'readonly'
|
||||
@$quantityField.addClass 'editable'
|
||||
@$bubble.addClass 'editable'
|
||||
else
|
||||
@$quantityField.attr 'readonly', 'readonly'
|
||||
@$quantityField.removeClass 'editable'
|
||||
|
||||
@$action.css display:(if @editable then 'table-cell' else 'none')
|
||||
@$bubble.removeClass 'editable'
|
||||
|
||||
super
|
||||
|
||||
@@ -127,8 +147,21 @@ module.exports = class StackController extends BaseController
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'blur .quantity input': 'onQuantityFieldBlur'
|
||||
'click button.remove': 'onRemoveClicked'
|
||||
'click .name a': 'routeLinkClick'
|
||||
'click button.first': 'onFirstButtonClicked'
|
||||
'click button.second': 'onSecondButtonClicked'
|
||||
'focus .quantity input': 'onQuantityFieldFocused'
|
||||
'input .quantity input': 'onQuantityFieldChanged'
|
||||
'keyup .quantity input': 'onQuantityKeyUp'
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_updateButtonType: ($button, type)->
|
||||
for currentType in ['check', 'down', 'remove', 'up']
|
||||
$button.removeClass currentType
|
||||
|
||||
if type?
|
||||
$button.css 'display', null
|
||||
$button.addClass type
|
||||
else
|
||||
$button.css 'display', 'none'
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
###
|
||||
Crafting Guide - step_controller.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseController = require './base_controller'
|
||||
InventoryController = require './inventory_controller'
|
||||
MinimalRecipeController = require './minimal_recipe_controller'
|
||||
{Event} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class StepController extends BaseController
|
||||
|
||||
constructor: (options={})->
|
||||
if not options.model? then throw new Error 'options.model is required'
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
|
||||
options.templateName = 'step'
|
||||
super options
|
||||
|
||||
@canComplete = options.canComplete or (controller)-> true
|
||||
@imageLoader = options.imageLoader
|
||||
@modPack = options.modPack
|
||||
|
||||
# Event Methods ################################################################################
|
||||
|
||||
onCompleteButtonClicked: (event)->
|
||||
event.preventDefault()
|
||||
@trigger Event.button.complete, this
|
||||
|
||||
# BaseController Overrides #####################################################################
|
||||
|
||||
onDidRender: ->
|
||||
@inventoryController = @addChild InventoryController, '.view__inventory',
|
||||
editable: false
|
||||
imageLoader: @imageLoader
|
||||
model: @model.inventory
|
||||
modPack: @modPack
|
||||
|
||||
@recipeController = @addChild MinimalRecipeController, '.view__minimal_recipe',
|
||||
imageLoader: @imageLoader
|
||||
model: @model.recipe
|
||||
modPack: @modPack
|
||||
|
||||
@$header = @$('h3')
|
||||
@$completeButton = @$('button.complete')
|
||||
super
|
||||
|
||||
onWillChangeModel: (oldModel, newModel)->
|
||||
if not newModel? then throw new Error 'model cannot be null'
|
||||
return super
|
||||
|
||||
refresh: ->
|
||||
itemDisplay = @modPack.findItemDisplay @model.outputItemSlug
|
||||
@$header.html "#{@model.number}. #{itemDisplay.itemName}"
|
||||
|
||||
@inventoryController.model = @model.inventory
|
||||
@recipeController.model = @model.recipe
|
||||
@recipeController.multiplier = @model.multiplier
|
||||
|
||||
if @canComplete(this)
|
||||
@$completeButton.removeProp 'disabled'
|
||||
else
|
||||
@$completeButton.prop 'disabled', true
|
||||
|
||||
super
|
||||
|
||||
# Backbone.View Overrides ######################################################################
|
||||
|
||||
events: ->
|
||||
return _.extend super,
|
||||
'click button.complete': 'onCompleteButtonClicked'
|
||||
@@ -224,9 +224,11 @@ module.exports = class CraftingGuideRouter extends Backbone.Router
|
||||
controller.onWillShow()
|
||||
controller.$el = $pageContent
|
||||
controller.render()
|
||||
controller.show()
|
||||
|
||||
if @_controller?
|
||||
@_controller.hide -> switchToNextController()
|
||||
@_controller.once Event.animate.hide.finish, switchToNextController
|
||||
@_controller.hide()
|
||||
else
|
||||
switchToNextController()
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ module.exports = class CraftPage extends BaseModel
|
||||
|
||||
@modPack.on Event.change, => @_consumeParams()
|
||||
@on Event.change + ':params', => @_consumeParams()
|
||||
@plan.on Event.change, => @trigger Event.change, this
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@ All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
{Event} = require '../constants'
|
||||
Inventory = require './inventory'
|
||||
ItemSlug = require './item_slug'
|
||||
Step = require './step'
|
||||
{Event} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
@@ -26,7 +27,7 @@ module.exports = class CraftingPlan extends BaseModel
|
||||
|
||||
recraft = _.debounce (=> @craft()), 100
|
||||
for inventory in [@have, @want]
|
||||
inventory.on 'change', recraft
|
||||
inventory.on Event.change, recraft
|
||||
|
||||
@on Event.change + ':includingTools', recraft
|
||||
|
||||
@@ -140,7 +141,7 @@ module.exports = class CraftingPlan extends BaseModel
|
||||
@_findSteps inputStack.itemSlug, parentSteps
|
||||
|
||||
logger.verbose -> "adding step for: #{recipe.slug}"
|
||||
@steps[recipe.slug] = recipe:recipe, itemSlug:item.slug
|
||||
@steps[recipe.slug] = new Step outputItemSlug:item.slug, recipe:recipe
|
||||
foundValidRecipe = true
|
||||
break
|
||||
catch error
|
||||
@@ -166,16 +167,26 @@ module.exports = class CraftingPlan extends BaseModel
|
||||
return itemSlug
|
||||
|
||||
_removeExtraSteps: ->
|
||||
result = (step for step in @steps when step.multiplier > 0)
|
||||
result = []
|
||||
|
||||
number = 1
|
||||
for step in @steps
|
||||
if step.multiplier > 0
|
||||
result.push step
|
||||
step.number = number
|
||||
number += 1
|
||||
|
||||
@steps = result
|
||||
|
||||
_resolveNeeds: ->
|
||||
for i in [@steps.length-1..0] by -1
|
||||
step = @steps[i]
|
||||
recipe = step.recipe
|
||||
outputQuantity = recipe.getQuantityProducedOf step.itemSlug
|
||||
step.number = i + 1
|
||||
|
||||
step.multiplier = Math.ceil(@need.quantityOf(step.itemSlug) / outputQuantity)
|
||||
recipe = step.recipe
|
||||
outputQuantity = recipe.getQuantityProducedOf step.outputItemSlug
|
||||
|
||||
step.multiplier = Math.ceil(@need.quantityOf(step.outputItemSlug) / outputQuantity)
|
||||
|
||||
if @includingTools
|
||||
recipe.eachToolStack (stack)=>
|
||||
|
||||
@@ -20,9 +20,6 @@ module.exports = class Inventory extends BaseModel
|
||||
attributes.modPack ?= null
|
||||
@clear()
|
||||
|
||||
Object.defineProperties this,
|
||||
isEmpty: { get:-> @_itemSlugs.length is 0 }
|
||||
|
||||
# Class Methods ################################################################################
|
||||
|
||||
@Delimiters =
|
||||
@@ -161,6 +158,14 @@ module.exports = class Inventory extends BaseModel
|
||||
|
||||
return parts.join Inventory.Delimiters.Stack
|
||||
|
||||
# Property Methods #############################################################################
|
||||
|
||||
getIsEmpty: ->
|
||||
return @_itemSlugs.length is 0
|
||||
|
||||
Object.defineProperties @prototype,
|
||||
isEmpty: { get:@prototype.getIsEmpty }
|
||||
|
||||
# Object Overrides #############################################################################
|
||||
|
||||
toString: ->
|
||||
@@ -185,7 +190,7 @@ module.exports = class Inventory extends BaseModel
|
||||
stack = @_stacks[itemSlug]
|
||||
if not stack?
|
||||
stack = new Stack itemSlug:itemSlug, quantity:quantity
|
||||
@listenTo stack, Event.change, => @trigger Event.change, stack
|
||||
@listenTo stack, Event.change, => @trigger Event.change, this
|
||||
@_stacks[itemSlug] = stack
|
||||
@_itemSlugs.push itemSlug
|
||||
@_sort()
|
||||
|
||||
@@ -14,7 +14,7 @@ module.exports = class ItemSelector extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not options.modPack? then throw new Error 'options.modPack is required'
|
||||
options.isAcceptable ?= (itemSlug)-> return true # accept everything by default
|
||||
options.isAcceptable ?= (item)-> return true # accept everything by default
|
||||
super attributes, options
|
||||
|
||||
@_isAcceptable = options.isAcceptable
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
###
|
||||
Crafting Guide - step.coffee
|
||||
|
||||
Copyright (c) 2015 by Redwood Labs
|
||||
All rights reserved.
|
||||
###
|
||||
|
||||
BaseModel = require './base_model'
|
||||
Inventory = require './inventory'
|
||||
{Event} = require '../constants'
|
||||
|
||||
########################################################################################################################
|
||||
|
||||
module.exports = class Step extends BaseModel
|
||||
|
||||
constructor: (attributes={}, options={})->
|
||||
if not attributes.outputItemSlug? then throw new Error 'attributes.outputItemSlug is required'
|
||||
if not attributes.recipe? then throw new Error 'attributes.recipe is required'
|
||||
|
||||
attributes.inventory = new Inventory
|
||||
attributes.number ?= 1
|
||||
attributes.outputItemSlug ?= null
|
||||
attributes.multiplier ?= 1
|
||||
attributes.recipe ?= null
|
||||
super attributes, options
|
||||
|
||||
@_computeInventory()
|
||||
@on Event.change + ':multiplier', => @_computeInventory()
|
||||
|
||||
# Private Methods ##############################################################################
|
||||
|
||||
_computeInventory: ->
|
||||
@inventory.clear()
|
||||
@recipe.eachInputStack (stack)=>
|
||||
@inventory.add stack.itemSlug, stack.quantity * @multiplier
|
||||
Reference in New Issue
Block a user