First real version of item detail page

* Split the InventoryTable view/controller out from the existing
  inventory view/controller. This is a simpler component which only
  includes the table itself, and not the scrollbox, panel or header
  used on the crafting page.
* Create the FullRecipe view/controller to represent the expanded
  version of a recipe shown on the item page (as compared to the
  terser version on the crafting page).
* Rename the existing Recipe view/controller to MinimalRecipe to
  avoid confusing the two.
* Refactor the CSS for the Stack view/controller so that it can be
  shared between the Inventory and InventoryTable views.
* Incorporate all these new controllers into the item page and hook
  up the necessary model logic to drive them.
This commit is contained in:
Andrew Miner
2015-02-09 21:15:09 -08:00
parent 996c8823a3
commit 46427a70f6
23 changed files with 528 additions and 54 deletions
+5 -3
View File
@@ -65,10 +65,12 @@ h2 {
}
h3 {
font-family: $font-family-normal;
font-weight: bold;
margin-bottom: 0.5em;
p { font-size: $font-size-normal; }
p {
font-family: $font-family-header;
font-size: $font-size-normal;
}
}
p {
+1 -1
View File
@@ -18,4 +18,4 @@ All rights reserved.
border: 0.1em solid $color-gray-light;
padding: 0.1em;
}
}
}
+1 -1
View File
@@ -26,7 +26,7 @@ All rights reserved.
}
}
.view__recipe {
.view__minimal_recipe {
position: absolute; top: 50%; left: 50%;
@include transform(translate(-50%, -50%));
}
+39
View File
@@ -0,0 +1,39 @@
/*
Crafting Table - full_recipe.scss
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
*/
.view__full_recipe {
& > div {
position: relative; width: 33%;
display: inline-block;
vertical-align: top;
}
.pattern {
.view__crafting_grid {
position: relative; left: 50%;
@include transform(translateX(-50%));
}
.tool {
margin-top: 0.5em;
text-align: center;
p {
font-weight: bold;
}
}
}
.input {
.view__inventory {
width: 100%;
}
}
.output {
}
}
+4 -2
View File
@@ -6,14 +6,16 @@ All rights reserved.
*/
@import 'crafting_grid';
@import 'crafting_page';
@import 'crafting_table';
@import 'feedback';
@import 'full_recipe';
@import 'inventory';
@import 'inventory_table';
@import 'item';
@import 'item_page';
@import 'crafting_page';
@import 'minimal_recipe';
@import 'mod';
@import 'mod_pack';
@import 'mod_page';
@import 'recipe';
@import 'stack';
+74
View File
@@ -0,0 +1,74 @@
/*
Crafting Guide - inventory_table.scss
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
*/
.view__inventory_table {
table {
width: 100%;
td {
height: 2.4em;
vertical-align: middle;
}
td.quantity {
position: relative;
width: 2.4em;
input {
position: absolute; top: 50%; height: $font-size-small; width: 100%;
@include transform(translateY(-50%));
border-radius: 0.75em;
margin: 0 auto;
text-align: center;
max-width: 2.8em;
}
}
td.name {
text-align: left;
padding-right: 1em;
input {
width: 100%;
border: 0;
border-bottom: 1px dashed $color-gray-light;
outline: 0;
&:focus {
border: 1px solid $color-gray-light;
}
}
}
td.action {
width: 4em;
}
.edit td {
padding-top: 1em;
}
}
.toolbar {
position: relative; width: 100%;
background: $color-background-toolbar;
border-top: 1px solid $color-gray-light;
}
button:disabled {
background: white;
color: $color-gray-light;
}
button[name="clear"] {
position: absolute; bottom: 0.5em; right: 0.5em;
}
input {
border: 1px solid $color-gray-light;
}
}
+10 -2
View File
@@ -31,7 +31,7 @@ All rights reserved.
padding: 1em;
padding-top: 2em;
.name {
h1.name {
font-family: $font-family-header;
font-size: $font-size-x-large;
margin: 0;
@@ -57,12 +57,20 @@ All rights reserved.
}
}
.usedInMaking {
.recipe {
display: none;
.view__full_recipe {
width: 100%;
}
}
.similar {
display: none;
}
.usedInMaking {
display: none;
}
}
}
@@ -1,11 +1,11 @@
/*
Crafting Table - recipe.scss
Crafting Table - minimal_recipe.scss
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
*/
.view__recipe {
.view__minimal_recipe {
position: relative; height: 20em; width: 26em;
.input {
+18 -3
View File
@@ -8,7 +8,11 @@ All rights reserved.
.view__stack {
font-family: $font-family-normal;
.quantity p {
td {
padding: 0.2em 0;
}
td.quantity p {
border-radius: 0.75em;
background: $color-ice;
color: $color-background-panel;
@@ -19,12 +23,23 @@ All rights reserved.
padding: 0.1em 0.5em;
}
.name p {
td.icon {
width: 4.6em;
padding: 0.2em 0.5em;
img {
width: 100%; height: auto;
max-height: 4.8em;
@include pixel-image;
}
}
td.name p {
display: inline;
font-size: $font-size-normal;
}
.action {
td.action {
text-align: right;
button.remove {
+1 -1
View File
@@ -16,7 +16,7 @@ exports.DefaultMods = [
]
exports.Duration = Duration = {}
Duration.snap = 100
Duration.snap = 200
Duration.fast = Duration.snap * 2
Duration.normal = Duration.fast * 2
Duration.slow = Duration.normal * 2
@@ -44,7 +44,7 @@ module.exports = class CraftingTableController extends BaseController
# BaseController Overrides #####################################################################
onDidRender: ->
@recipeController = @addChild MinimalRecipeController, '.view__recipe', imageLoader:@_imageLoader, modPack:@_modPack
@recipeController = @addChild MinimalRecipeController, '.view__minimal_recipe', imageLoader:@_imageLoader, modPack:@_modPack
@$next = @$('.next')
@$prev = @$('.prev')
@@ -0,0 +1,90 @@
###
Crafting Guide - full_recipe_controller.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
CraftingGridController = require './crafting_grid_controller'
{Duration} = require '../constants'
ImageLoader = require './image_loader'
Inventory = require '../models/inventory'
InventoryTableController = require './inventory_table_controller'
########################################################################################################################
module.exports = class FullRecipeController extends BaseController
constructor: (options={})->
if not options.modPack? then throw new Error 'options.modPack is required'
options.imageLoader ?= new ImageLoader defaultUrl:'/images/unknown.png'
options.templateName = 'full_recipe'
super options
@_imageLoader = options.imageLoader
@modPack = options.modPack
# BaseController Overrides #####################################################################
onDidRender: ->
@gridController = @addChild CraftingGridController, '.view__crafting_grid',
modPack: @modPack
imageLoader: @_imageLoader
@inputController = @addChild InventoryTableController, '.input .view__inventory_table',
editable: false
model: new Inventory
modPack: @modPack
@outputController = @addChild InventoryTableController, '.output .view__inventory_table',
editable: false
model: new Inventory
modPack: @modPack
@$tool = @$('.tool p')
super
refresh: ->
@gridController.model = @model
@$tool.html @_findToolNames().join ', '
@$el.tooltip show:{delay:Duration.fast, duration:Duration.fast}
@_refreshInputs()
@_refreshOutputs()
super
# Backbone.View Methods ########################################################################
events: ->
return _.extend super,
'click a': 'routeLinkClick'
# Private Methods ##############################################################################
_findToolNames: ->
result = []
if @model?
for stack in @model.tools
name = @modPack.findName stack.slug
result.push name if name?
return result
_refreshInputs: ->
inputs = @inputController.model
inputs.clear()
if @model?
for stack in @model.input
inputs.add stack.slug, stack.quantity
_refreshOutputs: ->
outputs = @outputController.model
outputs.clear()
if @model?
for stack in @model.output
outputs.add stack.slug, stack.quantity
@@ -0,0 +1,183 @@
###
Crafting Guide - inventory_table_controller.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
{Duration} = require '../constants'
{Key} = require '../constants'
ImageLoader = require './image_loader'
NameFinder = require '../models/name_finder'
StackController = require './stack_controller'
########################################################################################################################
module.exports = class InventoryTableController extends BaseController
@ONLY_DIGITS = /^[0-9]*$/
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'
@editable = options.editable ?= true
@imageLoader = options.imageLoader ?= new ImageLoader defaultUrl:'/images/unknown.png'
@modPack = options.modPack
@nameFinder = options.nameFinder ?= new NameFinder options.modPack
@onChange = options.onChange ?= -> # do nothing
options.templateName = 'inventory_table'
super options
@_stackControllers = []
@listenTo @modPack, 'change', => @refresh()
# Event Methods ################################################################################
onAddButtonClicked: ->
name = @$nameField.val()
return unless @modPack.isValidName name
@model.add _.slugify(name), parseInt(@$quantityField.val())
@$nameField.val ''
@$quantityField.val '1'
@$scrollbox.scrollTop @$scrollbox.prop 'scrollHeight'
@$nameField.autocomplete 'close'
@onChange()
onClearButtonClicked: ->
@model.clear()
@onChange()
onItemSelected: ->
func = =>
@onNameFieldChanged()
@onAddButtonClicked()
@$nameField.blur()
setTimeout func, 10 # needed to allow the autocomplete to finish
return true
onNameFieldBlur: ->
item = @modPack.findItemByName @$nameField.val()
@$nameField.val if item? then item.name else ''
@onNameFieldChanged()
onNameFieldChanged: ->
item = @modPack.findItemByName @$nameField.val()
@_updateButtonState()
onNameFieldFocused: ->
@$nameField.val ''
@$nameField.autocomplete('search')
onNameFieldKeyUp: (event)->
if event.which is Key.Return
@onAddButtonClicked()
onQuantityFieldBlur: ->
value = @$quantityField.val().replace /[^0-9]/g, ''
if value.length is 0 then value = '1'
value = Math.min value, 64
@$quantityField.val value
@onQuantityFieldChanged()
onQuantityFieldChanged: ->
if not @$quantityField.val().match /^[0-9]*$/
@$quantityField.addClass 'error', 0
@$quantityField.addClass 'error-new', 0
@$quantityField.removeClass 'error-new', Duration.slow
@$quantityField.focus()
return
@$quantityField.removeClass 'error', Duration.fast
@$quantityField.removeClass 'error-new', Duration.fast
@_updateButtonState()
onQuantityFieldFocused: ->
@$quantityField.val ''
# BaseController Overrides #####################################################################
onDidRender: ->
@$addButton = @$('button[name="add"]')
@$clearButton = @$('button[name="clear"]')
@$editPanel = @$('.edit')
@$nameField = @$('input[name="name"]')
@$quantityField = @$('input[name="quantity"]')
@$scrollbox = @$('.scrollbox')
@$table = @$('table')
@$toolbar = @$('.toolbar')
super
refresh: ->
@$editPanel.css display:(if @editable then 'table-row' else 'none')
@$toolbar.css display:(if @editable then 'block' else 'none')
@$scrollbox.css bottom:(if @editable then @$toolbar.height() else '0')
if _.isEmpty(@$quantityField.val()) then @$quantityField.val '1'
@$table.find('tr:not(:last-child)').remove()
$lastRow = @$table.find 'tr:last-child'
@_stackControllers = []
@model.each (stack)=>
options =
editable: @editable
imageLoader: @imageLoader
model: stack
modPack: @modPack
onRemove: if not @editable then null else (stack)=> @_removeStack(stack)
controller = new StackController options
controller.render()
controller.$el.insertBefore $lastRow
@_stackControllers.push controller
@_updateNameAutocomplete()
@_updateButtonState()
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'blur input[name="name"]': 'onNameFieldBlur'
'blur input[name="quantity"]': 'onQuantityFieldBlur'
'click button[name="add"]': 'onAddButtonClicked'
'click button[name="clear"]': 'onClearButtonClicked'
'focus input[name="name"]': 'onNameFieldFocused'
'focus input[name="quantity"]': 'onQuantityFieldFocused'
'input input[name="name"]': 'onNameFieldChanged'
'input input[name="quantity"]': 'onQuantityFieldChanged'
'keyup input[name="name"]': 'onNameFieldKeyUp'
# Private Methods ##############################################################################
_removeStack: (stack)->
@model.remove stack.slug, stack.quantity
_updateNameAutocomplete: ->
onChanged = => @onNameFieldChanged()
onSelected = => @onItemSelected()
@$nameField.autocomplete
source: (request, callback)=> callback @nameFinder.search request.term
delay: 0
minLength: 0
change: onChanged
close: onChanged
select: onSelected
_updateButtonState: ->
if @model.isEmpty then @$clearButton.attr('disabled', 'disabled') else @$clearButton.removeAttr('disabled')
itemValid = @modPack.findItemByName(@$nameField.val())?
quantityValid = @$quantityField.val().match(InventoryTableController.ONLY_DIGITS)
disable = not (itemValid and quantityValid)
if disable then @$addButton.attr('disabled', 'disabled') else @$addButton.removeAttr('disabled')
@@ -5,14 +5,15 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
{Duration} = require '../constants'
{Event} = require '../constants'
ImageLoader = require './image_loader'
Item = require '../models/item'
ItemGroupController = require './item_group_controller'
ItemPage = require '../models/item_page'
{Url} = require '../constants'
BaseController = require './base_controller'
{Duration} = require '../constants'
{Event} = require '../constants'
FullRecipeController = require './full_recipe_controller'
ImageLoader = require './image_loader'
Item = require '../models/item'
ItemGroupController = require './item_group_controller'
ItemPage = require '../models/item_page'
{Url} = require '../constants'
########################################################################################################################
@@ -32,25 +33,27 @@ module.exports = class ItemPageController extends BaseController
@_itemSlug = options.itemSlug
@_modPack = options.modPack
@_modPack.on Event.change, => @_resolveItemSlug()
@_resolveItemSlug()
@_modPack.on Event.change, => @refresh()
# BaseController Overrides #####################################################################
onDidRender: ->
@_recipeController = @addChild FullRecipeController, '.view__full_recipe', modPack:@_modPack
@_similarItemsController = @addChild ItemGroupController, '.similar .view__item_group', modPack:@_modPack
@_usedInMakingController = @addChild ItemGroupController, '.usedInMaking .view__item_group', modPack:@_modPack
@_usedInMakingController = @addChild ItemGroupController, '.usedInMaking .view__item_group', modPack:@_modPack
@$byline = @$('.byline')
@$bylineLink = @$('.byline a')
@$byline = @$('.byline')
@$bylineLink = @$('.byline a')
@$usedInMakingContainer = @$('.usedInMaking')
@$name = @$('h1.name')
@$recipeContainer = @$('.recipe')
@$similarContainer = @$('.similar')
@$titleImage = @$('.titleImage img')
@$name = @$('h1.name')
@$recipeContainer = @$('.recipe')
@$similarContainer = @$('.similar')
@$titleImage = @$('.titleImage img')
super
refresh: ->
@_resolveItemSlug()
display = @_modPack.findItemDisplay @model.item?.slug
if display?
@_imageLoader.load display.iconUrl, @$titleImage
@@ -60,6 +63,7 @@ module.exports = class ItemPageController extends BaseController
@$name.html ''
@_refreshByline()
@_refreshRecipe()
@_refreshSimilarItems()
@_refreshComponentIn()
@@ -77,7 +81,7 @@ module.exports = class ItemPageController extends BaseController
@$byline.fadeOut duration:Duration.fast
_refreshComponentIn: ->
@_usedInMakingController.title = 'Used in Making'
@_usedInMakingController.title = 'Used to Make'
@_usedInMakingController.model = @model.findComponentInItems()
if @_usedInMakingController.model?
@@ -85,6 +89,13 @@ module.exports = class ItemPageController extends BaseController
else
@$usedInMakingContainer.fadeOut duration:Duration.fast
_refreshRecipe: ->
@_recipeController.model = @model.primaryRecipe
if @_recipeController.model?
@$recipeContainer.fadeIn duration:Duration.fast
else
@$recipeContainer.fadeOut duration:Duration.fast
_refreshSimilarItems: ->
group = @model.item?.group
if group? and group isnt Item.Group.Other
@@ -99,5 +110,6 @@ module.exports = class ItemPageController extends BaseController
@$similarContainer.fadeOut duration:Duration.fast
_resolveItemSlug: ->
oldItem = @model.item
@model.item = @_modPack.findItem @_itemSlug, includeDisabled:true
newItem = @model.item
@@ -35,6 +35,7 @@ module.exports = class StackController extends BaseController
# BaseController Overrides #####################################################################
onDidRender: ->
@$action = @$('.action')
@$image = @$('.icon img')
@$nameField = @$('.name p')
@$quantityField = @$('.quantity p')
@@ -49,6 +50,8 @@ module.exports = class StackController extends BaseController
@$quantityField.html @model.quantity
@$removeButton.css display:(if @editable then 'inherit' else 'none')
@$action.css display:(if @ediable then 'table-cell' else 'none')
super
# Backbone.View Overrides ######################################################################
+2 -1
View File
@@ -27,7 +27,8 @@ module.exports = class Item extends BaseModel
@_recipes = []
Object.defineProperties this,
'isCraftable': { get:-> @_recipes.length > 0 }
isCraftable: { get:-> @_recipes.length > 0 }
primaryRecipe: { get:@getPrimaryRecipe }
# Public Methods ###############################################################################
+19 -14
View File
@@ -7,7 +7,7 @@ All rights reserved.
BaseModel = require './base_model'
CraftingPlan = require './crafting_plan'
Event = require '../constants'
{Event} = require '../constants'
########################################################################################################################
@@ -25,21 +25,10 @@ module.exports = class ItemPage extends BaseModel
Object.defineProperties this,
craftingRawMaterials: {get:-> @_plan.need}
craftingSteps: {get:-> @_plan.steps}
similarItems: {get:=> @getSimilarItems()}
primaryRecipe: {get:-> @_primaryRecipe}
# Property Methods #############################################################################
findSimilarItems: ->
return null unless @item?.modVersion?
result = []
@item.modVersion.eachItemInGroup @item.group, (item)=>
return if item is @item
result.push item
return null unless result.length > 0
return result
findComponentInItems: ->
return null unless @item?
@@ -56,6 +45,17 @@ module.exports = class ItemPage extends BaseModel
return null unless result.length > 0
return result
findSimilarItems: ->
return null unless @item?.modVersion?
result = []
@item.modVersion.eachItemInGroup @item.group, (item)=>
return if item is @item
result.push item
return null unless result.length > 0
return result
# Private Methods ##############################################################################
_updateCraftingPlan: ->
@@ -63,4 +63,9 @@ module.exports = class ItemPage extends BaseModel
if @item?
@_plan.want.add @item.slug
@_plan.craft()
@_plan.craft()
if @_plan.steps.length > 0
@_primaryRecipe = @_plan.steps[@_plan.steps.length - 1].recipe
else
@_primaryRecipe = null
+1 -1
View File
@@ -12,7 +12,7 @@
.panel
.prev
.view__recipe
.view__minimal_recipe
.next
.problem
+20
View File
@@ -0,0 +1,20 @@
//-
//- Crafting Guide - full_recipe.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- All rights reserved.
//-
.view__full_recipe
.input
h3: p Ingredients
.view__inventory_table
.pattern
table.view__crafting_grid
.tool: p
.output
h3: p Produces
.view__inventory_table
+16
View File
@@ -0,0 +1,16 @@
//-
//- Crafting Guide - inventory_table.jade
//-
//- Copyright (c) 2014-2015 by Redwood Labs
//- All rights reserved.
//-
.view__inventory_table
table
tr.edit
td.quantity: input(name="quantity")
td.icon: img(src="/images/unknown.png")
td.name(width="*"): input(name="name")
td.action: button(name="add") add
.toolbar
button(name="clear") clear
+5 -1
View File
@@ -14,7 +14,11 @@
.byline: p from <a></a>
.description: p
.recipe
.recipe.section
h2 Recipe
.panel
.view__full_recipe
.usedInMaking: .view__item_group
.similar: .view__item_group
.plan
+1 -1
View File
@@ -5,7 +5,7 @@
//- All rights reserved.
//-
.view__recipe
.view__minimal_recipe
.input
table.view__crafting_grid
.tool: p
+1 -1
View File
@@ -8,6 +8,6 @@
tr.view__stack
td.quantity: p
td.icon: img(src='')
td.name: p
td.name(width='*'): p
td.action
button.remove &nbsp;