Files
website/src/client/site/craft_page/craft_page_controller.coffee
T

273 lines
10 KiB
CoffeeScript

#
# Crafting Guide - craft_page_controller.coffee
#
# Copyright © 2014-2017 by Redwood Labs
# All rights reserved.
#
CraftPage = require "../../models/site/craft_page"
WorkingPanelController = require "./working_panel/working_panel_controller"
{Inventory} = require("crafting-guide-common").models
InventoryController = require "../common/inventory/inventory_controller"
{Observable} = require("crafting-guide-common").util
PageController = require "../page_controller"
StepController = require "./step/step_controller"
########################################################################################################################
module.exports = class CraftPageController extends PageController
@::SCROLL_BUFFER = 8 # px
@::STEP_RENDER_DELAY = 25 # ms
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.router? then throw new Error "options.router is required"
if not options.storage? then throw new Error "options.storage is required"
options.model ?= new CraftPage modPack:options.modPack, params:options.params
options.templateName = 'craft_page'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_router = options.router
@_storage = options.storage
@model.have.on Observable::ANY, this, "onHaveInventoryChanged"
@model.want.on Observable::ANY, this, "onWantInventoryChanged"
# Event Methods ################################################################################
onHaveInventoryChanged: ->
@_storage.store 'crafting-plan:have', @model.have.toUrlString()
onMoveNeedToHave: (item)->
quantity = @_needInventoryController.model.getQuantity item
@model.have.add item, quantity
onRemoveFromHave: (item)->
@model.have.remove item
onRemoveFromWant: (item)->
@model.want.remove item
onSampleClicked: (event)->
$el = $(event.target)
while $el.length > 0
inventoryText = $el.attr "data-slug"
break if inventoryText
$el = $el.parent()
if inventoryText?
try
tracker.trackEvent c.tracking.category.craft, "add-sample", inventoryText
sampleInventory = Inventory.fromUrlString inventoryText, @_modPack
@model.want.merge sampleInventory
@_scrollTo @$workingSection
catch e
logger.error e
return false
onWantInventoryChanged: ->
text = @model.want.toUrlString()
url = c.url.crafting inventoryText:text
@router.navigate url
# PageController Overrides #####################################################################
getMetaDescription: ->
return c.text.craftDescription()
getTitle: ->
description = @model.want.toDescription()
return null unless description?
return "Crafting Plan for #{description}"
# BaseController Overrides #####################################################################
onDidRender: ->
@_wantInventoryController = @addChild InventoryController, '.want .view__inventory',
firstButtonType: 'remove'
imageLoader: @_imageLoader
isAcceptable: (item)=> item.isCraftable
modPack: @_modPack
model: @model.want
router: @_router
trackingContext: c.tracking.category.craftWant
@_wantInventoryController.on c.event.button.first, (controller, item)=> @onRemoveFromWant item
@_haveInventoryController = @addChild InventoryController, '.have .view__inventory',
firstButtonType: 'down'
imageLoader: @_imageLoader
modPack: @_modPack
model: @model.have
router: @_router
trackingContext: c.tracking.category.craftHave
@_haveInventoryController.on c.event.button.first, (controller, item)=> @onRemoveFromHave item
@_needInventoryController = @addChild InventoryController, '.need .view__inventory',
editable: false
firstButtonType: 'up'
imageLoader: @_imageLoader
modPack: @_modPack
model: null
router: @_router
trackingContext: c.tracking.category.craftNeed
@_needInventoryController.on c.event.button.first, (controller, item)=> @onMoveNeedToHave item
@_workingPanelController = @addChild WorkingPanelController, '.view__working_panel', model:@model
@$haveSection = @$('section.have')
@$instructionsSection = @$('section.instructions')
@$makeSection = @$('section.make')
@$needSection = @$('section.need')
@$stepsContainer = @$('section.steps .panel')
@$stepsSection = @$('section.steps')
@$toolsSection = @$('section.tools')
@$workingSection = @$('.view__working_panel')
if c.screen.type.compute() is c.screen.type.mobile
@$('.view__inventory.large').removeClass 'large'
super
onWillRender: ->
@model.have.clear()
@model.have.merge Inventory.fromUrlString @_storage.load('crafting-plan:have')
@model.have.on Observable::ANY, this, "onHaveInventoryChanged"
@model.want.on Observable::ANY, this, "tryRefresh"
super
refresh: ->
@_needInventoryController.model = @model.currentPlan?.need
@_refreshOutdated()
@_refreshSectionVisibility()
@_refreshSteps()
super
# Backbone.View Overrides ########################################################################
events: ->
return _.extend super,
'click .instructions a': 'onSampleClicked'
# Private Methods ################################################################################
_addTools: (controller)->
controller.model.addToolsTo @model.want
_completeStep: (controller)->
controller.markComplete @model.have
_isAddingToolsPossible: (controller)->
tools = (item for itemId, item of controller.model.recipe.tools)
return false unless tools.length > 0
have = @model.have
want = @model.want
for item in tools
return false if @model.have.contains item
return false if @model.want.contains item
return true
_isStepCompletable: (controller)->
userWantsItem = @model.want.contains controller.model.recipe.output.item
return not userWantsItem
_refreshOutdated: ->
if @model.isOutdated
@$el.addClass 'outdated'
else
@$el.removeClass 'outdated'
_refreshSectionVisibility: ->
return unless @_rendered
allSections = [
@$instructionsSection, @$haveSection, @$needSection, @$stepsSection, @$toolsSection, @$workingSection
]
visibleSections = []
# TODO: Figure out what to do if the planner can't make a valid plan
# else if @model.craftsman.stage is Craftsman::STAGE.INVALID
# visibleSections.push el for el in [@$wantSection, @$haveSection, @$workingSection]
# TODO: Play around and see if the algo ever takes too long
# else if not @model.craftsman.complete
# visibleSections.push el for el in [@$wantSection, @$haveSection, @$workingSection]
if @model.want.isEmpty
visibleSections.push el for el in [@$instructionsSection, @$wantSection]
else
visibleSections.push el for el in [@$haveSection, @$wantSection, @$workingSection]
if @model.currentPlan?
visibleSections.push el for el in [@$needSection, @$stepsSection]
@hide $el for $el in allSections
@show $el for $el in visibleSections
_refreshSteps: ->
steps = @model.currentPlan?.steps or []
@_stepControllers ?= []
index = 0
for step in steps
controller = @_stepControllers[index]
if not controller?
controller = new StepController
canAddTools: (controller)=> @_isAddingToolsPossible controller
canComplete: (controller)=> @_isStepCompletable controller
onComplete: (controller)=> @_completeStep controller
onAddTools: (controller)=> @_addTools controller
imageLoader: @_imageLoader
model: step
modPack: @_modPack
router: @_router
do (controller, index)=>
_.delay (=>
controller.render()
@$stepsContainer.append controller.$el
@_stepControllers.push controller
), (index * @STEP_RENDER_DELAY)
else
controller.model = step
index += 1
while @_stepControllers.length > index
@_stepControllers.pop().remove()
_scrollTo: ($targetEl)->
@_$scrollTarget = $targetEl
if @_scrollTimer?
clearTimeout @_scrollTimer
@_scrollTimer = setTimeout(=>
$el = @_$scrollTarget
@_scrollTimer = null
minScrollPosition = $el.offset().top + $el.height() - $(window).height() - @SCROLL_BUFFER
maxScrollPosition = $el.offset().top + @SCROLL_BUFFER
scrollPosition = $(window).scrollTop()
scrollTarget = scrollPosition
if scrollPosition > maxScrollPosition
scrollTarget = maxScrollPosition
else if scrollPosition < minScrollPosition
scrollTarget = minScrollPosition
else
return
$('html, body').animate {scrollTop:scrollTarget}, c.duration.normal
, c.duration.slow)