Refactor website with new design & structure

This commit is contained in:
Andrew Miner
2016-04-03 19:30:15 -07:00
parent ec7e8c883d
commit d69585facd
406 changed files with 7411 additions and 37859 deletions
@@ -1,31 +1,36 @@
###
Crafting Guide - main.coffee
#
# Crafting Guide - client.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
############################################################################################################
require './polyfill'
# Allow Node.js-style `global` in addition to `window`
if typeof(global) is 'undefined'
window.global = window
$ = require 'jquery'
CraftingGuideClient = require('crafting-guide-common').CraftingGuideClient
CraftingGuideRouter = require './crafting_guide_router'
FeedbackController = require './controllers/feedback_controller'
Logger = require('crafting-guide-common').Logger
_ = require './underscore_mixins'
backbone = require 'backbone'
marked = require 'marked'
views = require './views'
global._ = require '../common/underscore'
global.$ = require 'jquery'
global.c = require '../common/constants'
global.π = Math.PI
global.ε = 0.0001
global.w = require 'when'
global.Backbone = Backbone = require 'backbone'
Backbone.$ = $
{Logger} = require 'crafting-guide-common'
global.logger = new Logger
marked = require 'marked'
marked.setOptions
sanitize: true
########################################################################################################################
if typeof(global) is 'undefined'
window.global = window
global = window.global
global.hostName = window.location.hostname
global.logger = new Logger
switch global.hostName
when 'prerender.crafting-guide.com'
global.env = 'prerender'
@@ -46,25 +51,23 @@ switch global.hostName
else
throw new Error "cannot determine the environment of: #{window.location.hostname}"
client = _.extend new CraftingGuideClient(baseUrl:apiBaseUrl), backbone.Events
########################################################################################################################
{CraftingGuideClient} = require 'crafting-guide-common'
client = _.extend new CraftingGuideClient(baseUrl:apiBaseUrl), Backbone.Events
client.onStatusChanged = (c, oldStatus, newStatus)->
logger.info "Crafting Guide server status changed from #{oldStatus} to #{newStatus}"
client.trigger 'change:status', client, oldStatus, newStatus
client.trigger 'change', client
backbone.$ = $
marked.setOptions
sanitize: true
global.feedbackController = new FeedbackController el:'.view__feedback'
feedbackController.render()
global.router = new CraftingGuideRouter client:client
global.router.loadCurrentUser()
global.router.loadDefaultModPack()
backbone.history.start pushState:true
client.checkStatus()
########################################################################################################################
SiteController = require './site/site_controller'
global.site = site = new SiteController client: client
site.render()
site.loadDefaultModPack()
site.loadCurrentUser()
Backbone.history.start pushState:true
logger.info -> "CraftingGuide is ready"
@@ -1,7 +1,7 @@
//-
//- Crafting Guide - _google_adsense.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- Copyright (c) 2015-2016 by Redwood Labs
//- All rights reserved.
//-
@@ -1,7 +1,7 @@
//-
//- Crafting Guide - _google_analytics.jade
//-
//- Copyright (c) 2014-2015 by Redwood Labs
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
@@ -1,19 +1,13 @@
###
Crafting Guide - base_model.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
$ = require 'jquery'
backbone = require 'backbone'
w = require 'when'
{Event} = require '../constants'
{ModelState} = require '../constants'
#
# Crafting Guide - base_model.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
module.exports = class BaseModel extends backbone.Model
module.exports = class BaseModel extends Backbone.Model
constructor: (attributes={}, options={})->
options.logEvents ?= true
@@ -26,15 +20,15 @@ module.exports = class BaseModel extends backbone.Model
Object.defineProperty this, name, get:makeGetter(name), set:makeSetter(name)
@logEvents = options.logEvents or false
@state = ModelState.unloaded
@state = c.modelState.unloaded
@loading = null
Object.defineProperties this,
isUnloaded: { get:-> @state is ModelState.unloaded }
isLoading: { get:-> @state is ModelState.loading }
isLoaded: { get:-> @state is ModelState.loaded }
isError: { get:-> @state is ModelState.error }
isUnloaded: { get:-> @state is c.modelState.unloaded }
isLoading: { get:-> @state is c.modelState.loading }
isLoaded: { get:-> @state is c.modelState.loaded }
isError: { get:-> @state is c.modelState.error }
# Event Methods ################################################################################
@@ -42,18 +36,18 @@ module.exports = class BaseModel extends backbone.Model
try
@set @parse text
@state = ModelState.loaded
@trigger Event.change, this
@trigger Event.sync, this
@state = c.modelState.loaded
@trigger c.event.change, this
@trigger c.event.sync, this
logger.info => "#{@constructor.name}.#{@cid} loaded successfully"
catch e
logger.error -> "A parsing error occured: #{e.stack}"
@onLoadFailed e.message, 'parsing failed', xhr
onLoadFailed: (error, status, xhr)->
@state = ModelState.error
@state = c.modelState.error
logger.error => "#{@constructor.name}.#{@cid} failed to load: status:#{status}, message:#{error}"
@trigger Event.error, this, error
@trigger c.event.error, this, error
# Backbone.Model Overrides #####################################################################
@@ -64,8 +58,8 @@ module.exports = class BaseModel extends backbone.Model
url = @url()
logger.info => "#{@constructor.name}.#{@cid} reading from url: #{url}"
@state = ModelState.loading
@trigger Event.request, this
@state = c.modelState.loading
@trigger c.event.request, this
@loading = w.promise (resolve, reject)=>
$.ajax
url: url
@@ -1,11 +1,9 @@
###
Crafting Guide - crafting_node.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
#
# Crafting Guide - crafting_node.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
@@ -0,0 +1,44 @@
#
# Crafting Guide - crafting_node.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
fixtures = require './fixtures.test'
########################################################################################################################
root = null
########################################################################################################################
describe 'crafting_node.coffee', ->
describe 'acceptVisitor', ->
it 'can provide a prefix, depth-first traversal', ->
root = fixtures.makeTree 'test__iron_ingot'
visitor =
nodes: []
onEnterOtherNode: (node)-> visitor.nodes.push node
root.acceptVisitor visitor
(v.constructor.name.replace('Node', '') for v in visitor.nodes).should.eql [
"Inventory", "Item", "Recipe", "Item", "Item", "Recipe", "Item", "Item", "Recipe", "Item", "Item"
]
it 'can provide a postfix, depth-first traversal', ->
root = fixtures.makeTree 'test__iron_ingot'
visitor =
nodes: []
onLeaveOtherNode: (node)-> visitor.nodes.push node
root.acceptVisitor visitor
(n.constructor.name.replace('Node', '') for n in visitor.nodes).should.eql [
"Item", "Item", "Item", "Recipe", "Item", "Recipe", "Item", "Item", "Recipe", "Item", "Inventory"
]
@@ -1,12 +1,11 @@
###
Crafting Guide - crafting_plan.coffee
#
# Crafting Guide - crafting_plan.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require '../../underscore_mixins'
SimpleInventory = require '../simple_inventory'
SimpleInventory = require './simple_inventory'
########################################################################################################################
@@ -0,0 +1,107 @@
#
# Crafting Guide - crafting_plan.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CraftingPlan = require './crafting_plan'
fixtures = require './fixtures.test'
ItemSlug = require '../game/item_slug'
########################################################################################################################
describe 'crafting_plan.coffee', ->
it 'requires wanted item if gatherable', ->
plans = fixtures.makePlans [1, 'test__coal']
plans.length.should.equal 1
plan = plans[0]
plan.computeRequired()
plan.need.unparse().should.equal 'coal'
plan.made.unparse().should.equal 'coal'
it 'can compute a single item with one single step plan', ->
plans = fixtures.makePlans [1, 'test__charcoal']
plans.length.should.equal 1
plan = plans[0]
plan.computeRequired()
plan.need.unparse().should.equal 'coal:8.oak_wood'
plan.made.unparse().should.equal '8.charcoal'
(s.toString() for s in plan.steps).should.eql ['1x 8 test__oak_wood,test__coal>.0. ... .1.>>8 test__charcoal']
it 'can compute a large quantity of a single item with one single step plan', ->
plans = fixtures.makePlans [15, 'test__charcoal']
plans.length.should.equal 1
plan = plans[0]
plan.computeRequired()
plan.need.unparse().should.equal '2.coal:16.oak_wood'
plan.made.unparse().should.equal '16.charcoal'
(s.toString() for s in plan.steps).should.eql ['2x 8 test__oak_wood,test__coal>.0. ... .1.>>8 test__charcoal']
it 'can compute a single item with multiple plans', ->
plans = fixtures.makePlans [1, 'test__iron_ingot']
plans.length.should.equal 2
plan = plans[0]
plan.computeRequired()
plan.need.unparse().should.equal 'coal:8.iron_ore:8.oak_wood'
plan.made.unparse().should.equal '7.charcoal:8.iron_ingot'
(s.toString() for s in plan.steps).should.eql [
'1x 8 test__oak_wood,test__coal>.0. ... .1.>>8 test__charcoal'
'1x 8 test__iron_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__iron_ingot'
]
plan = plans[1]
plan.computeRequired()
plan.need.unparse().should.equal 'coal:8.iron_ore'
plan.made.unparse().should.equal '8.iron_ingot'
(s.toString() for s in plan.steps).should.eql [
'1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
]
it 'can compute multiple items with multiple plans', ->
plans = fixtures.makePlans [1, 'test__copper_block'], [1, 'test__iron_sword']
plans.length.should.equal 4
for plan in plans
plan.computeRequired()
plan.need.unparse().should.match /16.copper_ore.*8.iron_ore/
plan.made.unparse().should.match /copper_block.*:iron_sword/
plan = plans[0]
plan.need.unparse().should.match /3.coal.*9.oak_wood/
plan.made.unparse().should.match /7.charcoal/
"#{plan.steps[3]}".should.equal \
'1x 8 test__iron_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__iron_ingot'
"#{plan.steps[4]}".should.equal \
'2x 8 test__copper_ore,test__coal>.0. ... .1.>test__furnace>8 test__copper_ingot'
plan.steps.length.should.equal 7
plan = plans[1]
plan.need.unparse().should.match /3.coal.*:oak_wood/
plan.made.unparse().should.not.match /charcoal/
"#{plan.steps[2]}".should.equal '1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
"#{plan.steps[3]}".should.equal \
'2x 8 test__copper_ore,test__coal>.0. ... .1.>test__furnace>8 test__copper_ingot'
plan.steps.length.should.equal 6
plan = plans[2]
plan.need.unparse().should.match /^coal.*9.oak_wood/
plan.made.unparse().should.match /5.charcoal/
"#{plan.steps[3]}".should.equal \
'1x 8 test__iron_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__iron_ingot'
"#{plan.steps[4]}".should.equal \
'2x 8 test__copper_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__copper_ingot'
plan.steps.length.should.equal 7
plan = plans[3]
plan.need.unparse().should.match /2.coal.*9.oak_wood/
plan.made.unparse().should.match /6.charcoal/
"#{plan.steps[3]}".should.equal '1x 8 test__iron_ore,test__coal>.0. ... .1.>test__furnace>8 test__iron_ingot'
"#{plan.steps[4]}".should.equal \
'2x 8 test__copper_ore,test__charcoal>.0. ... .1.>test__furnace>8 test__copper_ingot'
plan.steps.length.should.equal 7
@@ -1,11 +1,11 @@
###
Crafting Guide - crafting_step.coffee
#
# Crafting Guide - crafting_step.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
SimpleInventory = require '../simple_inventory'
SimpleInventory = require './simple_inventory'
########################################################################################################################
@@ -1,18 +1,15 @@
###
Crafting Guide - craftsman.coffee
#
# Crafting Guide - craftsman.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
BaseModel = require '../base_model'
{Event} = require '../../constants'
GraphBuilder = require './graph_builder'
Inventory = require '../inventory'
Inventory = require '../game/inventory'
PlanBuilder = require './plan_builder'
PlanEvaluator = require './plan_evaluator'
w = require 'when'
########################################################################################################################
@@ -46,13 +43,13 @@ module.exports = class Craftsman extends BaseModel
reevaluatePlans = _.debounce (=> @reevaluatePlans()), 100
@_have = new Inventory modPack:@_modPack
@_have.on Event.change, reevaluatePlans
@_have.on c.event.change, reevaluatePlans
@_want = new Inventory modPack:@_modPack
@_want.on Event.change, reset
@_want.on c.event.change, reset
@on Event.change + ':paused', reset
@on Event.change + ':stage', => logger.info "Craftsman has started #{@stage}..."
@on c.event.change + ':paused', reset
@on c.event.change + ':stage', => logger.info "Craftsman has started #{@stage}..."
@on 'scheduleNextWork', => @_scheduleNextWork()
@reset()
@@ -124,7 +121,7 @@ module.exports = class Craftsman extends BaseModel
logger.info => "Craftsman has finished with plans: #{(p.toString() for p in @_plans).join('\n\n')}"
@trigger Event.change, this
@trigger c.event.change, this
@trigger 'scheduleNextWork'
return @complete
@@ -0,0 +1,166 @@
#
# Crafting Guide - fixtures.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
GraphBuilder = require './graph_builder'
Inventory = require '../game/inventory'
ItemSlug = require '../game/item_slug'
Mod = require '../game/mod'
ModPack = require '../game/mod_pack'
ModVersion = require '../game/mod_version'
PlanBuilder = require './plan_builder'
########################################################################################################################
MOD_VERSION_FILE =
"""
schema: 1
item: Bed
recipe:
input: Oak Planks, Wool
pattern: ... 000 111
item: Charcoal
recipe:
input: 8 Oak Wood, Coal
pattern: .0. ... .1.
quantity: 8
item: Crafting Table
recipe:
input: Oak Planks
pattern: .00 .00 ...
item: Coal
gatherable: yes
item: Cobblestone
gatherable: yes
item: Copper Block
recipe:
input: Copper Ingot
pattern: 000 000 000
tools: Crafting Table
item: Copper Ingot
recipe:
input: 8 Copper Ore, Coal
pattern: .0. ... .1.
quantity: 8
tools: Furnace
recipe:
input: 8 Copper Ore, Charcoal
pattern: .0. ... .1.
quantity: 8
tools: Furnace
recipe:
input: Copper Block
pattern: ... .0. ...
quantity: 9
item: Copper Ore
gatherable: yes
item: Furnace
recipe:
input: Cobblestone
pattern: 000 0.0 000
tools: Crafting Table
item: Iron Ore
gatherable: yes
item: Iron Ingot
recipe:
input: 8 Iron Ore, Charcoal
pattern: .0. ... .1.
quantity: 8
tools: Furnace
recipe:
input: 8 Iron Ore, Coal
pattern: .0. ... .1.
quantity: 8
tools: Furnace
item: Iron Sword
recipe:
input: Iron Ingot, Stick
pattern: .0. .0. .1.
tools: Crafting Table
item: Lever
recipe:
input: Stick, Cobblestone
pattern: .0. .1. ...
item: Oak Planks
recipe:
input: Oak Wood
pattern: ... .0. ...
quantity: 4
item: Oak Wood
gatherable: yes
item: Stick
recipe:
input: Oak Planks
pattern: .0. .0. ...
quantity: 4
item: String
gatherable: yes
item: Wool
gatherable: yes
recipe:
input: String
pattern: 00. 00. ...
"""
########################################################################################################################
module.exports = fixtures =
makeGraphBuilder: ->
return new GraphBuilder modPack:fixtures.makeModPack(), want:new Inventory
makeModPack: ->
modPack = new ModPack
mod = new Mod name:'Test', slug:'test'
modPack.addMod mod
modVersion = new ModVersion modSlug:'test', version:'0.0'
modVersion.parse MOD_VERSION_FILE
mod.addModVersion modVersion
return modPack
makePlans: (stacks...)->
modPack = fixtures.makeModPack()
have = new Inventory
want = new Inventory
for stack in stacks
want.add ItemSlug.slugify(stack[1]), stack[0]
graphBuilder = new GraphBuilder modPack:modPack, want:want
graphBuilder.expandGraph()
planBuilder = new PlanBuilder graphBuilder.rootNode, modPack, want:want, have:have
return planBuilder.producePlans()
makeTree: (itemSlug, quantity=1)->
want = new Inventory
want.add ItemSlug.slugify itemSlug
builder = new GraphBuilder modPack:fixtures.makeModPack(), want:want
builder.expandGraph()
return builder.rootNode
@@ -1,13 +1,12 @@
###
Crafting Guide - graph_builder.coffee
#
# Crafting Guide - graph_builder.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
Inventory = require '../inventory'
Inventory = require '../game/inventory'
InventoryNode = require './inventory_node'
{Limits} = require '../../constants'
########################################################################################################################
@@ -17,7 +16,7 @@ module.exports = class GraphBuilder
if not options.modPack? then throw new Error 'options.modPack is required'
if not options.want? then throw new Error 'options.want is required'
@_maximumGraphSize = Limits.maximumGraphSize
@_maximumGraphSize = c.limits.maximumGraphSize
@_modPack = options.modPack
@_stepCount = 0
@_want = options.want
@@ -0,0 +1,77 @@
#
# Crafting Guide - graph_builder.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
fixtures = require './fixtures.test'
ItemSlug = require '../game/item_slug'
########################################################################################################################
builder = null
########################################################################################################################
describe 'GraphBuilder.coffee', ->
beforeEach ->
builder = fixtures.makeGraphBuilder()
describe 'expand', ->
it 'can work a few steps at a time', ->
builder.want.add ItemSlug.slugify 'test__iron_sword'
builder.expandGraph 9
builder.rootNode.depth.should.equal 6
builder.rootNode.size.should.equal 13
builder.complete.should.be.false
builder.expandGraph 9
builder.rootNode.depth.should.equal 8
builder.rootNode.size.should.equal 18
builder.complete.should.be.true
it 'works properly with an empty inventory', ->
builder.expandGraph 100
builder.rootNode.depth.should.equal 1
builder.rootNode.size.should.equal 1
builder.complete.should.be.true
describe 'can build a tree for', ->
runSingleItemTreeBuildingTest = (slug, depth, size)->
builder.want.add ItemSlug.slugify slug
builder.expandGraph 100
builder.rootNode.depth.should.equal depth
builder.rootNode.size.should.equal size
builder.complete.should.be.true
it 'a gatherable item', ->
runSingleItemTreeBuildingTest 'test__oak_wood', 2, 2
it 'a single-step item', ->
runSingleItemTreeBuildingTest 'test__crafting_table', 6, 6
it 'an item with multiple inputs', ->
runSingleItemTreeBuildingTest 'test__lever', 8, 9
it 'an item with multiple recipes', ->
runSingleItemTreeBuildingTest 'test__iron_ingot', 6, 11
it 'an item with multiple inputs and multiple recipes', ->
runSingleItemTreeBuildingTest 'test__iron_sword', 8, 18
it 'an item with one recursive recipe', ->
runSingleItemTreeBuildingTest 'test__copper_ingot', 6, 15
it 'an item which gatherable and craftable', ->
runSingleItemTreeBuildingTest 'test__wool', 4, 4
it 'an item which requires a gatherable and craftable item', ->
runSingleItemTreeBuildingTest 'test__bed', 6, 7
@@ -1,9 +1,9 @@
###
Crafting Guide - crafting_plan_node.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - inventory_node.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CraftingNode = require './crafting_node'
ItemNode = require './item_node'
@@ -1,9 +1,9 @@
###
Crafting Guide - item_node.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - item_node.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CraftingNode = require './crafting_node'
RecipeNode = require './recipe_node'
@@ -1,15 +1,14 @@
###
Crafting Plan - plan_builder.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - plan_builder.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CraftingNode = require './crafting_node'
CraftingPlan = require './crafting_plan'
CraftingStep = require './crafting_step'
Inventory = require '../inventory'
{Limits} = require '../../constants'
Inventory = require '../game/inventory'
########################################################################################################################
@@ -24,7 +23,7 @@ module.exports = class PlanBuilder
@_choiceNodes = []
@_complete = false
@_maxPlanCount = Limits.maximumPlanCount
@_maxPlanCount = c.limits.maximumPlanCount
@_modPack = modPack
@_plans = []
@_rootNode = rootNode
@@ -0,0 +1,43 @@
#
# Crafting Guide - plan_builder.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
fixtures = require './fixtures.test'
PlanBuilder = require './plan_builder'
########################################################################################################################
describe 'plan_builder.coffee', ->
printPlan = (plan)->
return '' unless plan?
return ((s.recipe.slug.replace(/^.*>.*>/, '') for s in plan.steps;;)).join ' > '
it 'generates an empty plan for a gatherable item', ->
plans = fixtures.makePlans [1, 'test__oak_wood']
plans.length.should.equal 1
plans[0].length.should.equal 0
it 'can find a multi-step plan', ->
plans = fixtures.makePlans [1, 'test__lever']
printPlan(plans[0]).should.equal '4 test__oak_planks > 4 test__stick > test__lever'
plans.length.should.equal 1
it 'can find multiple plans', ->
plans = fixtures.makePlans [1, 'test__iron_ingot']
printPlan(plans[0]).should.equal '8 test__charcoal > 8 test__iron_ingot'
printPlan(plans[1]).should.equal '8 test__iron_ingot'
plans.length.should.equal 2
it 'ignores invalid plans', ->
plans = fixtures.makePlans [1, 'test__copper_block']
printPlan(plans[0]).should.equal '8 test__copper_ingot > test__copper_block'
printPlan(plans[1]).should.equal '8 test__charcoal > 8 test__copper_ingot > test__copper_block'
plans.length.should.equal 2
@@ -1,9 +1,9 @@
###
Crafting Guide - plan_evaluator.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - plan_evaluator.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
@@ -0,0 +1,80 @@
#
# Crafting Guide - plan_evaluator.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CraftingPlan = require './crafting_plan'
fixtures = require './fixtures.test'
Inventory = require '../game/inventory'
PlanEvaluator = require './plan_evaluator'
########################################################################################################################
criteria = evaluator = planA = planB = planC = plans = wanted = null
########################################################################################################################
describe 'plan_evaluator.coffee', ->
beforeEach ->
criteria = PlanEvaluator::CRITERIA.FEWEST_STEPS
describe 'findBestPlan', ->
it 'can find the shortest of two options', ->
evaluator = new PlanEvaluator fixtures.makePlans [1, 'test__copper_block']
evaluator.scorePlans()
bestPlan = evaluator.findBestPlan criteria
bestPlan.getScore(criteria).should.equal 1.0
it 'can find the shortest of many options', ->
evaluator = new PlanEvaluator fixtures.makePlans [1, 'test__copper_block'], [1, 'test__iron_sword']
evaluator.scorePlans()
bestPlan = evaluator.findBestPlan criteria
bestPlan.getScore(criteria).should.equal 1.0
it 'can break ties using the non-primary criteria', ->
evaluator = new PlanEvaluator fixtures.makePlans [2, 'test__iron_ingot'], [1, 'test__charcoal']
evaluator.scorePlans()
bestPlan = evaluator.findBestPlan criteria
bestPlan.getScore(criteria).should.equal 1.0
(p.getRawScore('fewest steps') for p in evaluator._plans).should.eql [2, 2]
(p.getRawScore('least materials') for p in evaluator._plans).should.eql [17, 18]
describe '_normalizeScores', ->
beforeEach ->
modPack = fixtures.makeModPack()
wanted = new Inventory modPack:modPack
have = new Inventory modPack:modPack
planA = new CraftingPlan modPack, wanted, have, []
planB = new CraftingPlan modPack, wanted, have, []
planC = new CraftingPlan modPack, wanted, have, []
plans = [planA, planB, planC]
evaluator = new PlanEvaluator plans
criteria = PlanEvaluator::CRITERIA.FEWEST_STEPS
it 'does not set scores when none have been evaluated', ->
evaluator._normalizeScores()
(plan.hasRawScore(criteria) for plan in plans).should.eql [false, false, false]
it 'assigns a 1.0 when all plans equal', ->
for plan in plans
plan.setRawScore criteria, 5
evaluator._normalizeScores()
(plan.getScore(criteria) for plan in plans).should.eql [1.0, 1.0, 1.0]
it 'computes correct scores from raw scores', ->
planA.setRawScore criteria, 7
planB.setRawScore criteria, 8
planC.setRawScore criteria, 9
evaluator._normalizeScores()
(plan.getScore(criteria) for plan in plans).should.eql [1.0, 0.5, 0.0]
@@ -1,14 +1,14 @@
###
Crafting Guide - recipe_node.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - recipe_node.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CraftingNode = require './crafting_node'
Item = require '../item'
Item = require '../game/item'
# ItemNode = require './item_node' # don't include here, causes a cycle
ItemSlug = require '../item_slug'
ItemSlug = require '../game/item_slug'
########################################################################################################################
@@ -1,13 +1,12 @@
###
Crafting Guide - inventory.coffee
#
# Crafting Guide - simple_inventory.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
ItemSlug = require './item_slug'
SimpleStack = require './simple_stack'
_ = require 'underscore'
ItemSlug = require '../game/item_slug'
SimpleStack = require './simple_stack'
########################################################################################################################
@@ -0,0 +1,33 @@
#
# Crafting Guide - simple_stack.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ItemSlug = require '../game/item_slug'
########################################################################################################################
module.exports = class Stack
constructor: (attributes={}, options={})->
if not attributes.itemSlug? then throw new Error 'attributes.itemSlug is required'
attributes.quantity ?= 1
@itemSlug = attributes.itemSlug
@quantity = attributes.quantity
# Class Methods ################################################################################
@compare: (a, b)->
if a? and not b? then return -1
if not a? and b? then return +1
if a.quantity isnt b.quantity
return if a.quantity > b.quantity then -1 else +1
return ItemSlug.compare a.itemSlug, b.itemSlug
# Object Overrides #############################################################################
toString: ->
return "#{@quantity} #{@itemSlug}"
+36
View File
@@ -0,0 +1,36 @@
###
# Crafting Guide - event_recorder.coffee
#
# Copyright (c) 2014-2015 by Redwood Labs
# All rights reserved.
###
util = require 'util'
########################################################################################################################
module.exports = class EventRecorder
constructor: (model)->
if not model? then throw new Error 'model is required'
@model = model
@events = []
@model.on 'all', (event, model, args...)=>
logger.verbose -> "#{model?.constructor?.name}(#{model?.cid}) emitted #{event}
with args: #{util.inspect(args)}"
@events.push id:model?.cid, event:event, args:args
# Public Methods ###############################################################################
reset: ->
@events = []
# Property Methods #############################################################################
getNames: ->
return (e.event for e in @events)
Object.defineProperties @prototype,
names: {get:@prototype.getNames}
@@ -1,16 +1,13 @@
###
Crafting Guide - inventory.coffee
#
# Crafting Guide - inventory.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
ItemSlug = require './item_slug'
Stack = require './stack'
_ = require 'underscore'
{Event} = require '../constants'
{RequiredMods} = require '../constants'
BaseModel = require '../base_model'
ItemSlug = require './item_slug'
Stack = require './stack'
########################################################################################################################
@@ -36,21 +33,21 @@ module.exports = class Inventory extends BaseModel
return this unless quantity > 0
@_add itemSlug, quantity
@trigger Event.add, this, itemSlug, quantity
@trigger Event.change, this
@trigger c.event.add, this, itemSlug, quantity
@trigger c.event.change, this
return this
addInventory: (inventory)->
inventory.each (stack)=> @_add stack.itemSlug, stack.quantity
@trigger Event.change, this
@trigger c.event.change, this
return this
clear: (options={})->
@_stacks = {}
@_itemSlugs = []
@trigger Event.change, this
@trigger c.event.change, this
clone: ->
inventory = new Inventory
@@ -103,10 +100,10 @@ module.exports = class Inventory extends BaseModel
@_stacks = {}
for stack in newStacks
@_stacks[stack.itemSlug] = stack
@listenTo stack, Event.change, => @trigger Event.change, this
@listenTo stack, c.event.change, => @trigger c.event.change, this
@_sort()
@trigger Event.change, this
@trigger c.event.change, this
pop: ->
itemSlug = @_itemSlugs.pop()
@@ -115,8 +112,8 @@ module.exports = class Inventory extends BaseModel
stack = @_stacks[itemSlug]
delete @_stacks[itemSlug]
@trigger Event.remove, this, stack.itemSlug, stack.quantity
@trigger Event.change, this
@trigger c.event.remove, this, stack.itemSlug, stack.quantity
@trigger c.event.change, this
return stack
quantityOf: (itemSlug)->
@@ -140,8 +137,8 @@ module.exports = class Inventory extends BaseModel
delete @_stacks[itemSlug]
@_itemSlugs = (s for s in @_itemSlugs when not ItemSlug.equal(s, itemSlug))
@trigger Event.remove, this, itemSlug, quantity
@trigger Event.change, this
@trigger c.event.remove, this, itemSlug, quantity
@trigger c.event.change, this
return this
# Parsing Methods ##############################################################################
@@ -184,18 +181,16 @@ module.exports = class Inventory extends BaseModel
# Property Methods #############################################################################
getIsEmpty: ->
return @_itemSlugs.length is 0
getTotalQuantity: ->
total = 0
@each (stack)->
total += stack.quantity
return total
Object.defineProperties @prototype,
isEmpty: { get:@prototype.getIsEmpty }
totalQuantity: { get:@prototype.getTotalQuantity }
isEmpty:
get: -> @_itemSlugs.length is 0
totalQuantity:
get: ->
total = 0
@each (stack)->
total += stack.quantity
return total
# Object Overrides #############################################################################
@@ -221,7 +216,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, this
@listenTo stack, c.event.change, => @trigger c.event.change, this
@_stacks[itemSlug] = stack
@_itemSlugs.push itemSlug
@_sort()
@@ -0,0 +1,213 @@
#
# Crafting Guide - inventory.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
EventRecorder = require '../event_recorder'
Inventory = require './inventory'
Item = require './item'
ItemSlug = require './item_slug'
########################################################################################################################
inventory = modPack = null
########################################################################################################################
describe 'inventory.coffee', ->
beforeEach ->
inventory = new Inventory {}, silent:false
inventory.add ItemSlug.slugify('wool'), 4
inventory.add ItemSlug.slugify('string'), 20
inventory.add ItemSlug.slugify('boat')
describe 'add', ->
it 'can add to an empty inventory', ->
inventory.add ItemSlug.slugify('iron_ingot'), 4
stack = inventory._stacks['iron_ingot']
stack.constructor.name.should.equal 'Stack'
stack.itemSlug.qualified.should.equal 'iron_ingot'
stack.quantity.should.equal 4
it 'can augment quantity of existing items', ->
inventory.add ItemSlug.slugify('wool'), 2
inventory.unparse().should.equal 'boat:20.string:6.wool'
it 'can add zero quantity', ->
inventory.add ItemSlug.slugify('wool'), 0
inventory.unparse().should.equal 'boat:20.string:4.wool'
it 'emits the proper events', ->
events = new EventRecorder inventory
inventory.add ItemSlug.slugify('iron_ingot'), 10
events.names.should.eql [c.event.add, c.event.change]
describe 'addInventory', ->
it 'can add to an empty inventory', ->
newInventory = new Inventory
newInventory.addInventory inventory
newInventory.unparse().should.equal 'boat:20.string:4.wool'
it 'can add a mix of new and existing items', ->
newInventory = new Inventory
newInventory.add ItemSlug.slugify('string'), 2
newInventory.addInventory inventory
newInventory.unparse().should.equal 'boat:22.string:4.wool'
describe 'clone', ->
it 'creates an empty inventory from an empty inventory', ->
a = new Inventory
b = a.clone()
b._itemSlugs.should.eql []
it 'faithfully copies an existing inventory', ->
copy = inventory.clone()
copy.unparse().should.equal 'boat:20.string:4.wool'
describe 'each', ->
it 'works with an empty inventory', ->
inventory = new Inventory
result = []
inventory.each (item)-> result.push item.name
result.should.eql []
it 'works when items have only been added', ->
result = []
inventory.each (stack)-> result.push stack.itemSlug.qualified
result.should.eql ['boat', 'string', 'wool']
it 'works when items have been augmented', ->
inventory.add ItemSlug.slugify 'iron_ingot'
inventory.add ItemSlug.slugify 'boat'
inventory.add ItemSlug.slugify('wool'), 2
result = []
inventory.each (stack)-> result.push stack.itemSlug.qualified
result.should.eql ['boat', 'iron_ingot', 'string', 'wool']
describe 'hasAtLeast', ->
it 'works when the item is completely absent', ->
answer = inventory.hasAtLeast 'chicken', 1
answer.should.be.false
it 'always returns true for zero quantity', ->
inventory.hasAtLeast('chicken', 0).should.be.true
inventory.hasAtLeast('wool', 0).should.be.true
it 'works for a quantity above 1', ->
inventory.hasAtLeast('wool', 3).should.be.true
inventory.hasAtLeast('wool', 4).should.be.true
inventory.hasAtLeast('wool', 5).should.be.false
describe 'localize', ->
before ->
modPack =
modSlug:
wool: 'minecraft'
string: 'minecraft'
boat: 'minecraft'
stone_gear: 'buildcraft'
findItem: (slug)->
return slug:new ItemSlug @modSlug[slug.item], slug.item
it 'replaces item slugs with qualified slugs', ->
inventory.add ItemSlug.slugify 'stone_gear'
inventory.modPack = modPack
inventory.localize()
slugs = []
inventory.each (stack)-> slugs.push stack.itemSlug.qualified
slugs.should.eql [
'minecraft__boat'
'buildcraft__stone_gear'
'minecraft__string'
'minecraft__wool'
]
it 'ignores qualified slugs', ->
inventory.add ItemSlug.slugify 'buildcraft__stone_gear'
inventory.modPack = modPack
inventory.localize()
slugs = []
inventory.each (stack)-> slugs.push stack.itemSlug.qualified
slugs.should.eql [
'minecraft__boat'
'buildcraft__stone_gear'
'minecraft__string'
'minecraft__wool'
]
describe 'parse', ->
beforeEach ->
inventory = new Inventory {}, silent:false
it 'ignores an empty string', ->
result = inventory.parse ''
result.unparse().should.eql ''
it 'can parse a single item without quantity', ->
result = inventory.parse 'wool'
result.unparse().should.equal 'wool'
it 'can parse a single item with quantity', ->
result = inventory.parse '4.wool'
result.unparse().should.equal '4.wool'
it 'can parse multiple mixed-type items', ->
result = inventory.parse '4.wool:10.string:boat'
result.unparse().should.equal 'boat:10.string:4.wool'
describe 'pop', ->
it 'returns null for an empty inventory', ->
inventory = new Inventory
result = inventory.pop()
expect(result).to.be.null
it 'completely removes the last item', ->
stack = inventory.pop()
stack.itemSlug.qualified.should.equal 'wool'
stack.quantity.should.equal 4
inventory.unparse().should.equal 'boat:20.string'
it 'triggers the right events', ->
events = new EventRecorder inventory
result = inventory.pop()
events.names.should.eql [c.event.remove, c.event.change]
describe 'remove', ->
it 'does nothing when the item is absent', ->
before = inventory.unparse()
inventory.remove 'foo'
after = inventory.unparse()
before.should.equal after
it 'throws when the item has insufficient quantity', ->
expect(-> inventory.remove('wool', 10)).to.throw Error,
'cannot remove 10: only 4 wool in this inventory'
it 'removes all items by default', ->
inventory.remove 'wool'
expect(inventory._stacks.wool).to.be.empty
it 'removes a quantity above 1', ->
inventory.remove 'wool', 3
inventory._stacks.wool.quantity.should.equal 1
it 'emits the proper events', ->
events = new EventRecorder inventory
inventory.remove 'wool'
events.names.should.eql [c.event.change, c.event.remove, c.event.change]
@@ -1,16 +1,14 @@
###
Crafting Guide - item.coffee
#
# Crafting Guide - item.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
BaseModel = require '../base_model'
ItemSlug = require './item_slug'
Recipe = require './recipe'
{Event} = require '../constants'
{StringBuilder} = require 'crafting-guide-common'
{Url} = require '../constants'
########################################################################################################################
@@ -33,7 +31,7 @@ module.exports = class Item extends BaseModel
options.logEvents ?= false
super attributes, options
@on Event.change + ':modVersion', =>
@on c.event.change + ':modVersion', =>
@_isCraftable = null
@slug.mod = @modVersion?.modSlug
@@ -47,7 +45,7 @@ module.exports = class Item extends BaseModel
return 0
unparse: ->
ItemParser = require './item_parser' # to avoid require cycles
ItemParser = require '../parsing/item_parser' # to avoid require cycles
@_parser ?= new ItemParser model:this
return @_parser.unparse()
@@ -67,14 +65,14 @@ module.exports = class Item extends BaseModel
# Backbone.Model Overrides #####################################################################
parse: (text)->
ItemParser = require './item_parser' # to avoid require cycles
ItemParser = require '../parsing/item_parser' # to avoid require cycles
@_parser ?= new ItemParser model:this
@_parser.parse text
return null # prevent calling `set`
url: ->
return Url.itemData modSlug:@slug.mod, itemSlug:@slug.item
return c.url.itemData modSlug:@slug.mod, itemSlug:@slug.item
# Object Overrides #############################################################################
@@ -1,12 +1,9 @@
###
Crafting Guide - item_slug.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
{RequiredMods} = require '../constants'
#
# Crafting Guide - item_slug.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
@@ -68,33 +65,27 @@ module.exports = class ItemSlug
# Property Methods #############################################################################
getIsQualified: ->
return @_mod?
getItem: ->
return @_item
setItem: (item)->
if not item? then throw new Error 'item is required'
@_item = item
@mod = @mod # reset @_qualified
getMod: ->
return @_mod
setMod: (mod)->
@_mod = mod
@_qualified = if @_mod? then _.composeSlugs(@_mod, @_item) else @_item
getQualified: ->
return @_qualified
Object.defineProperties @prototype,
isQualified: { get:@prototype.getIsQualified }
mod: { get:@prototype.getMod, set:@prototype.setMod }
item: { get:@prototype.getItem, set:@prototype.setItem }
qualified: { get:@prototype.getQualified }
isQualified:
get: -> @_mod?
item:
get: -> @_item
set: (newItem)->
if not newItem? then throw new Error 'item is required'
@_item = newItem
@mod = @mod # reset @_qualified
mod:
get: -> @_mod
set: (newMod)->
@_mod = newMod
@_qualified = if @_mod? then _.composeSlugs(@_mod, @_item) else @_item
qualified:
get: -> @_qualified
# Object Overrides #############################################################################
@@ -0,0 +1,122 @@
#
# Crafting Guide - item_slug.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ItemSlug = require './item_slug'
########################################################################################################################
describe 'item_slug.coffee', ->
describe 'constructor', ->
it 'can handle one argument', ->
slug = new ItemSlug 'alpha'
slug.item.should.equal 'alpha'
expect(slug.mod).to.be.null
slug.qualified.should.equal 'alpha'
it 'can handle two arguments', ->
slug = new ItemSlug 'alpha', 'bravo'
slug.mod.should.equal 'alpha'
slug.item.should.equal 'bravo'
slug.qualified.should.equal 'alpha__bravo'
it 'throws with zero arguments', ->
f = -> new ItemSlug
expect(f).to.throw Error, 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
it 'throws with more arguments', ->
f = -> new ItemSlug 'alpha', 'bravo', 'charlie'
expect(f).to.throw Error, 'expected arguments to be "modSlug, itemSlug" or just "itemSlug"'
describe 'ItemSlug.compare', ->
it 'sorts by item when both are qualified in the same mod', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'charlie', 'bravo'
ItemSlug.compare(a, b).should.equal -1
ItemSlug.compare(b, a).should.equal +1
it 'sorts by item when not qualified', ->
a = new ItemSlug 'alpha'
b = new ItemSlug 'bravo'
ItemSlug.compare(a, b).should.equal -1
ItemSlug.compare(b, a).should.equal +1
describe 'ItemSlug.equal', ->
it 'requires both to have the same mod', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'alpha', 'charlie'
c = new ItemSlug 'alpha', 'bravo'
ItemSlug.equal(a, b).should.be.false
ItemSlug.equal(a, c).should.be.true
it 'requires both to have the same item', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'alpha', 'charlie'
c = new ItemSlug 'alpha', 'bravo'
ItemSlug.equal(a, b).should.be.false
ItemSlug.equal(a, c).should.be.true
describe 'ItemSlug.slugify', ->
it 'can slugify a pure name', ->
slug = ItemSlug.slugify 'Alpha Bravo (Charlie)'
slug.item.should.equal 'alpha_bravo_charlie'
expect(slug.mod).to.be.null
it 'can slugify a simple item slug', ->
slug = ItemSlug.slugify 'alpha_bravo_charlie'
slug.item.should.equal 'alpha_bravo_charlie'
expect(slug.mod).to.be.null
it 'can slugify a fully-qualified slug', ->
slug = ItemSlug.slugify 'alpha_bravo__charlie_delta'
slug.mod.should.equal 'alpha_bravo'
slug.item.should.equal 'charlie_delta'
describe 'matches', ->
it 'ignores mod when either is unqualified', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'bravo'
c = new ItemSlug 'charlie'
a.matches(b).should.be.true
a.matches(c).should.be.false
it 'observes differences in mod when all are qualified', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'charlie', 'bravo'
c = new ItemSlug 'delta', 'echo'
d = new ItemSlug 'alpha', 'bravo'
a.matches(b).should.be.false
a.matches(c).should.be.false
a.matches(d).should.be.true
describe 'isQualified', ->
it 'returns true only when the mod slug is set', ->
a = new ItemSlug 'alpha', 'bravo'
b = new ItemSlug 'charlie'
a.isQualified.should.be.true
b.isQualified.should.be.false
describe '[]', ->
it 'allows slugs as a key', ->
slug = new ItemSlug 'alpha', 'bravo'
data = {}
data[slug] = 'foo'
data['alpha__bravo'].should.equal 'foo'
data[slug].should.equal 'foo'
@@ -1,15 +1,11 @@
###
Crafting Guide - mod.coffee
#
# Crafting Guide - mod.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
BaseModel = require './base_model'
{Event} = require '../constants'
{RequiredMods} = require '../constants'
{Url} = require '../constants'
BaseModel = require '../base_model'
########################################################################################################################
@@ -33,7 +29,7 @@ module.exports = class Mod extends BaseModel
@_modVersions = []
@_tutorials = []
@once Event.sync, => @_verifyActiveModVersion()
@once c.event.sync, => @_verifyActiveModVersion()
# Class Methods ##################################################################################
@@ -44,8 +40,8 @@ module.exports = class Mod extends BaseModel
# Public Methods #################################################################################
compareTo: (that)->
thisRequired = this.slug in RequiredMods
thatRequired = that.slug in RequiredMods
thisRequired = this.slug in c.requiredMods
thatRequired = that.slug in c.requiredMods
if thisRequired isnt thatRequired
return -1 if thisRequired
@@ -76,8 +72,8 @@ module.exports = class Mod extends BaseModel
@_activeVersion = version
@_activateModVersion null
@trigger Event.change + ':activeVersion', this, @_activeVersion
@trigger Event.change, this
@trigger c.event.change + ':activeVersion', this, @_activeVersion
@trigger c.event.change, this
else
for modVersion in @_modVersions
if version is modVersion.version
@@ -85,12 +81,15 @@ module.exports = class Mod extends BaseModel
break
@_activeVersion = version
@trigger Event.change + ':activeVersion', this, @_activeVersion
@trigger Event.change, this
@trigger c.event.change + ':activeVersion', this, @_activeVersion
@trigger c.event.change, this
enabled:
get: -> @_activeModVersion?
modVersions:
get: -> @_modVersions[..]
tutorials:
get: -> @getAllTutorials()
@@ -129,12 +128,12 @@ module.exports = class Mod extends BaseModel
return if @_modVersions.indexOf(modVersion) isnt -1
@_modVersions.push modVersion
@listenTo modVersion, Event.change, => @trigger Event.change, this
@listenTo modVersion, c.event.change, => @trigger c.event.change, this
modVersion.mod = this
@trigger Event.add + ':modVersion', modVersion, this
@trigger Event.change + ':version', modVersion, this
@trigger Event.change, this
@trigger c.event.add + ':modVersion', modVersion, this
@trigger c.event.change + ':version', modVersion, this
@trigger c.event.change, this
if not @activeVersion? then @activeVersion = modVersion.version
if modVersion.version is @_activeVersion then @_activateModVersion modVersion
@@ -198,21 +197,21 @@ module.exports = class Mod extends BaseModel
# Backbone.Model Overrides #####################################################################
parse: (text)->
ModParser = require './mod_parser' # to avoid require cycles
ModParser = require '../parsing/mod_parser' # to avoid require cycles
@_parser ?= new ModParser model:this
@_parser.parse text
return null # prevent calling `set`
url: ->
return Url.modData modSlug:@slug
return c.url.modData modSlug:@slug
# Private Methods ##############################################################################
_activateModVersion: (modVersion)->
if @_activeModVersion? then @stopListening @_activeModVersion
@_activeModVersion = modVersion
@trigger Event.change + ':activeModVersion', this, @_activeModVersion
@trigger c.event.change + ':activeModVersion', this, @_activeModVersion
logger.verbose => "#{@slug} switched to version #{@_activeVersion}"
+30
View File
@@ -0,0 +1,30 @@
#
# Crafting Guide - mod.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Mod = require './mod'
########################################################################################################################
mod = null
########################################################################################################################
describe 'mod.coffee', ->
beforeEach -> mod = new Mod name:'Test', slug:'test'
describe 'compareTo', ->
it 'lists required mods first', ->
minecraft = new Mod name:'Minecraft', slug:'minecraft'
mod.compareTo(minecraft).should.equal +1
minecraft.compareTo(mod).should.equal -1
it 'sorts by name second', ->
buildcraft = new Mod name:'Buildcraft', slug:'buildcraft'
mod.compareTo(buildcraft).should.equal +1
buildcraft.compareTo(mod).should.equal -1
@@ -1,17 +1,14 @@
###
Crafting Guide - mod_pack.coffee
#
# Crafting Guide - mod_pack.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
{DefaultModVersions} = require '../constants'
{Event} = require '../constants'
Inventory = require './inventory'
ModVersionParser = require './mod_version_parser'
Recipe = require './recipe'
{Url} = require '../constants'
BaseModel = require '../base_model'
Inventory = require './inventory'
ModVersionParser = require '../parsing/mod_version_parser'
Recipe = require './recipe'
########################################################################################################################
@@ -23,7 +20,7 @@ module.exports = class ModPack extends BaseModel
@_mods = []
@_cache = {}
@on Event.change, => @_cache = {}
@on c.event.change, => @_cache = {}
# Item Methods #################################################################################
@@ -84,10 +81,10 @@ module.exports = class ModPack extends BaseModel
else
craftingUrlInventory.add itemSlug
result.craftingUrl = Url.crafting inventoryText:craftingUrlInventory.unparse()
result.iconUrl = Url.itemIcon result
result.itemUrl = Url.item result
result.modName = @getMod(result.modSlug).name
result.craftingUrl = c.url.crafting inventoryText:craftingUrlInventory.unparse()
result.iconUrl = c.url.itemIcon result
result.itemUrl = c.url.item result
result.modName = @getMod(result.modSlug).name
return result
qualifySlug: (itemSlug)->
@@ -105,12 +102,12 @@ module.exports = class ModPack extends BaseModel
mod.modPack = this
@_mods.push mod
@listenTo mod, Event.change, (modVersion)=> @_onModVersionLoaded modVersion
@trigger Event.add + ':mod', mod, this
@listenTo mod, c.event.change, (modVersion)=> @_onModVersionLoaded modVersion
@trigger c.event.add + ':mod', mod, this
@_mods.sort (a, b)-> a.compareTo b
@trigger Event.sort + ':mod', this
@trigger Event.change, this
@trigger c.event.sort + ':mod', this
@trigger c.event.change, this
return this
@@ -175,5 +172,5 @@ module.exports = class ModPack extends BaseModel
return if working
@trigger Event.change, this
@trigger Event.sync, this
@trigger c.event.change, this
@trigger c.event.sync, this
+103
View File
@@ -0,0 +1,103 @@
#
# Crafting Guide - mod_pack.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Item = require './item'
ItemSlug = require './item_slug'
Mod = require './mod'
ModPack = require './mod_pack'
ModVersion = require './mod_version'
########################################################################################################################
buildcraft = industrialCraft = minecraft = modPack = null
########################################################################################################################
describe 'mod_pack.coffee', ->
beforeEach ->
minecraft = new Mod slug:'minecraft', name:'Minecraft'
minecraft.addModVersion new ModVersion modSlug:minecraft.slug, version:'1.7.10'
minecraft.activeModVersion.addItem new Item name:'Wool'
minecraft.activeModVersion.addItem new Item name:'Bed', recipes:['']
minecraft.activeModVersion.registerName ItemSlug.slugify('iron_chestplate'), 'Iron Chestplate'
buildcraft = new Mod slug:'buildcraft', name:'Buildcraft'
buildcraft.addModVersion new ModVersion modSlug:buildcraft.slug, version:'6.2.6'
buildcraft.activeModVersion.addItem new Item name:'Stone Gear', recipes:['']
buildcraft.activeModVersion.addItem new Item name:'Wrench', recipes:['']
buildcraft.activeVersion = Mod.Version.None
industrialCraft = new Mod slug:'industrial_craft', name:'Industrial Craft'
industrialCraft.addModVersion new ModVersion modSlug:industrialCraft.slug, version:'2.0'
industrialCraft.activeModVersion.addItem new Item name:'Resin'
industrialCraft.activeModVersion.addItem new Item name:'Rubber'
industrialCraft.activeModVersion.addItem new Item name:'Wrench', recipes:['']
industrialCraft.activeVersion = Mod.Version.None
modPack = new ModPack
modPack.addMod minecraft
modPack.addMod buildcraft
modPack.addMod industrialCraft
describe 'findItem', ->
it 'can find an item by partial slug', ->
item = modPack.findItem ItemSlug.slugify 'wool'
item.slug.qualified.should.equal 'minecraft__wool'
it 'can find an item by full slug', ->
item = modPack.findItem ItemSlug.slugify 'minecraft__wool'
item.name.should.equal 'Wool'
it 'can find an ambiguous item by full slug', ->
buildcraft.activeVersion = Mod.Version.Latest
industrialCraft.activeVersion = Mod.Version.Latest
item = modPack.findItem ItemSlug.slugify 'industrial_craft__wrench'
item.name.should.equal 'Wrench'
item.modVersion.mod.name.should.equal 'Industrial Craft'
it 'can find an ambiguous item by partial slug', ->
buildcraft.activeVersion = Mod.Version.Latest
industrialCraft.activeVersion = Mod.Version.Latest
item = modPack.findItem ItemSlug.slugify 'wrench'
item.name.should.equal 'Wrench'
item.modVersion.mod.name.should.equal 'Buildcraft'
describe 'findItemByName', ->
it 'finds the requested item', ->
item = modPack.findItemByName 'Bed'
item.name.should.equal 'Bed'
it 'ignores disabled mod versions', ->
item = modPack.findItemByName 'Stone Gear'
expect(item).to.be.null
describe 'findItemDisplay', ->
it 'returns all data for a regular Minecraft item', ->
display = modPack.findItemDisplay ItemSlug.slugify 'bed'
display.iconUrl.should.equal '/data/minecraft/items/bed/icon.png'
display.itemUrl.should.equal '/browse/minecraft/bed/'
display.itemName.should.equal 'Bed'
display.modSlug.should.equal 'minecraft'
it 'returns all data for an item in an enabled mod', ->
buildcraft.activeVersion = '6.2.6'
display = modPack.findItemDisplay ItemSlug.slugify 'stone_gear'
display.iconUrl.should.equal '/data/buildcraft/items/stone_gear/icon.png'
display.itemUrl.should.equal '/browse/buildcraft/stone_gear/'
display.itemName.should.equal 'Stone Gear'
display.modSlug.should.equal 'buildcraft'
it 'assumes an unfound item is from Minecraft', ->
display = modPack.findItemDisplay ItemSlug.slugify 'iron_chestplate'
display.iconUrl.should.equal '/data/minecraft/items/iron_chestplate/icon.png'
display.itemUrl.should.equal '/browse/minecraft/iron_chestplate/'
display.itemName.should.equal 'Iron Chestplate'
display.modSlug.should.equal 'minecraft'
@@ -1,18 +1,14 @@
###
Crafting Guide - mod_version.coffee
#
# Crafting Guide - mod_version.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
Item = require './item'
ItemSlug = require './item_slug'
Recipe = require './recipe'
_ = require 'underscore'
{Event} = require '../constants'
{RequiredMods} = require '../constants'
{Url} = require '../constants'
BaseModel = require '../base_model'
Item = require './item'
ItemSlug = require './item_slug'
Recipe = require './recipe'
########################################################################################################################
@@ -183,14 +179,14 @@ module.exports = class ModVersion extends BaseModel
# Backbone.Model Overrides #####################################################################
parse: (text)->
ModVersionParser = require './mod_version_parser' # to avoid require cycles
ModVersionParser = require '../parsing/mod_version_parser' # to avoid require cycles
@_parser ?= new ModVersionParser model:this
@_parser.parse text
return null # prevent calling `set`
url: ->
return Url.modVersionData modSlug:@modSlug, modVersion:@version
return c.url.modVersionData modSlug:@modSlug, modVersion:@version
# Object Overrides #############################################################################
@@ -0,0 +1,101 @@
#
# Crafting Guide - mod_version.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Item = require './item'
ItemSlug = require './item_slug'
ModVersion = require './mod_version'
########################################################################################################################
modVersion = null
########################################################################################################################
describe 'mod_version.coffee', ->
beforeEach ->
modVersion = new ModVersion modSlug:'test', version:'0.0'
modVersion.addItem new Item name:'underscore', group:'punctuation'
modVersion.addItem new Item name:'bravo', group:'letter'
modVersion.addItem new Item name:'alpha', group:'letter'
modVersion.addItem new Item name:'one', group:'number'
modVersion.addItem new Item name:'two', group:'number'
describe 'constructor', ->
it 'requires a mod slug', ->
expect(-> new ModVersion version:'0.0').to.throw Error, 'attributes.modSlug is required'
it 'requires a mod version', ->
expect(-> new ModVersion modSlug:'test').to.throw Error, 'attributes.version is required'
describe 'addItem', ->
it 'refuses to add duplicates', ->
modVersion.addItem new Item name:'Wool'
expect(-> modVersion.addItem new Item name:'Wool').to.throw Error, 'duplicate item for Wool'
it 'adds an item indexed by its slug', ->
modVersion.addItem new Item name:'Wool'
modVersion._items.wool.name.should.equal 'Wool'
it 'sets the modVersion', ->
modVersion.addItem new Item name:'Wool'
modVersion._items.wool.modVersion.should.equal modVersion
describe 'eachGroup', ->
it 'returns all the groups in order', ->
groupNames = []
modVersion.eachGroup (groupName)-> groupNames.push groupName
groupNames.should.eql ['letter', 'number', 'punctuation']
describe 'eachItemInGroup', ->
it 'returns immediately for unknown group', ->
slugs = []
modVersion.eachItemInGroup 'foobar', (item)-> slugs.push item.slug.qualified
slugs.should.eql []
it 'calls callback for exactly the items in a group in order', ->
slugs = []
modVersion.eachItemInGroup 'letter', (item)-> slugs.push item.slug.qualified
slugs.should.eql ['test__alpha', 'test__bravo']
slugs = []
modVersion.eachItemInGroup 'number', (item)-> slugs.push item.slug.qualified
slugs.should.eql ['test__one', 'test__two']
describe 'findItemByName', ->
it 'locates items by slugified name', ->
modVersion.addItem new Item name:'Crafting Table'
modVersion.findItemByName('Crafting Table').slug.qualified.should.equal 'test__crafting_table'
describe 'findRecipes', ->
beforeEach ->
modVersion = new ModVersion modSlug:'test', version:'1.0'
modVersion.parse """
schema:1
item: Cake
recipe:; input: Milk, Sugar, Egg, Wheat; pattern: 000 121 333; extras: 3 Bucket
recipe:; input: Milk, Cocoa Beans, Egg, Wheat; pattern: 000 121 333; extras: 3 Bucket
recipe:; input: Cake Slice; pattern: 000 000 000; onlyIf: item Cake Slice
item: Bucket
recipe:; input: Iron Ingot; pattern: ... 0.0 .0.
"""
it 'finds all recipes which list item as output', ->
recipes = modVersion.findRecipes ItemSlug.slugify('test__bucket')
(r.output[0].itemSlug.item for r in recipes).sort().should.eql ['bucket', 'cake', 'cake']
it 'skip recipes whose conditions are not met', ->
recipes = modVersion.findRecipes ItemSlug.slugify('test__cake')
recipes.length.should.equal 2
@@ -1,12 +1,11 @@
###
Crafting Guide - multiblock.coffee
#
# Crafting Guide - multiblock.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
_ = require '../underscore_mixins'
BaseModel = require './base_model'
BaseModel = require '../base_model'
Inventory = require './inventory'
########################################################################################################################
@@ -0,0 +1,85 @@
#
# Crafting Guide - multiblock.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ItemSlug = require './item_slug'
Mod = require './mod'
Multiblock = require './multiblock'
Stack = require './stack'
########################################################################################################################
describe 'multiblock.coffee', ->
describe 'with a single block type', ->
beforeEach ->
@input = [ new Stack itemSlug:ItemSlug.slugify('cobblestone'), quantity:1 ]
it 'correctly parses a 1x1x1 cube', ->
model = new Multiblock input:@input, layers:['0']
model.depth.should.equal 1
model.height.should.equal 1
model.width.should.equal 1
model.getStackAt(0, 0, 0).toString().should.equal '1 cobblestone'
it 'correctly parses a solid 3x3x3 cube', ->
model = new Multiblock input:@input, layers:['000 000 000', '000 000 000', '000 000 000']
model.depth.should.equal 3
model.height.should.equal 3
model.width.should.equal 3
for x in [0..2]
for y in [0..2]
for z in [0..2]
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
it 'correctly parses a hollow 3x3x3 cube', ->
model = new Multiblock input:@input, layers:['000 000 000', '000 0.0 000', '000 000 000']
model.depth.should.equal 3
model.height.should.equal 3
model.width.should.equal 3
for x in [0..2]
for y in [0..2]
for z in [0..2]
if x isnt 1 or y isnt 1 or z isnt 1
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
else
expect(model.getStackAt(x, y, z)).to.be.null
it 'correctly parses a 3x2x3 pyramid', ->
model = new Multiblock input:@input, layers:['000 000 000', '.. .0 ..']
model.depth.should.equal 3
model.height.should.equal 2
model.width.should.equal 3
for y in [0..1]
for z in [0..2]
for x in [0..2]
if y is 1 and (x isnt 1 or z isnt 1)
expect(model.getStackAt(x, y, z)).to.be.null
else
model.getStackAt(x, y, z).toString().should.equal '1 cobblestone'
describe 'with multiple block types', ->
beforeEach ->
@input = [
new Stack itemSlug:ItemSlug.slugify('cobblestone'), quantity:1
new Stack itemSlug:ItemSlug.slugify('stone'), quantity:1
new Stack itemSlug:ItemSlug.slugify('oak wood'), quantity:1
]
it 'correctly parses a 1x3x1 column of different types', ->
model = new Multiblock input:@input, layers:['0', '1', '2']
model.depth.should.equal 1
model.height.should.equal 3
model.width.should.equal 1
model.getStackAt(0, 0, 0).toString().should.equal '1 cobblestone'
model.getStackAt(0, 1, 0).toString().should.equal '1 stone'
model.getStackAt(0, 2, 0).toString().should.equal '1 oak_wood'
@@ -1,14 +1,13 @@
###
Crafting Guide - recipe.coffee
#
# Crafting Guide - recipe.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
BaseModel = require '../base_model'
ItemSlug = require './item_slug'
Stack = require './stack'
{Event} = require '../constants'
{StringBuilder} = require 'crafting-guide-common'
########################################################################################################################
@@ -38,8 +37,8 @@ module.exports = class Recipe extends BaseModel
@_computeQuantities attributes.pattern
@on Event.change + ':modVersion', => @_slug = null
@on Event.change + ':pattern', => @_patternCache = null
@on c.event.change + ':modVersion', => @_slug = null
@on c.event.change + ':pattern', => @_patternCache = null
# Class Methods ################################################################################
@@ -104,12 +103,9 @@ module.exports = class Recipe extends BaseModel
if modPack?.findItemByName(@condition.noun)?
result = true
else if @condition.verb is 'mod'
if not modPack?
result = true
else
modPack.eachMod (mod)=>
if mod.name is @condition.noun
result = true
modPack.eachMod (mod)->
if mod.name is @condition.noun
result = true
if @condition.inverted then result = not result
return result
+90
View File
@@ -0,0 +1,90 @@
#
# Crafting Guide - recipe.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Item = require './item'
ItemSlug = require './item_slug'
Recipe = require './recipe'
Stack = require './stack'
########################################################################################################################
input = output = pattern = recipe = null
########################################################################################################################
describe 'recipe.coffee', ->
describe 'constructor', ->
beforeEach ->
input = [
new Stack(itemSlug:new ItemSlug('iron_gear')),
new Stack(itemSlug:new ItemSlug('gold_ingot'), quantity:4)
]
pattern = '.1. 101 .1.'
it 'requires input', ->
expect(-> new Recipe slug:'gold_gear', pattern:pattern).to.throw Error, 'attributes.input is required'
it 'requires a pattern', ->
expect(-> new Recipe slug:'gold_gear', input:input).to.throw Error, 'attributes.pattern is required'
it 'requires either outputs or a slug', ->
f = -> new Recipe input:input, pattern:pattern
expect(f).to.throw 'attributes.itemSlug or attributes.output is required'
it 'creates default output', ->
recipe = new Recipe itemSlug:ItemSlug.slugify('gold_gear'), input:input, pattern:pattern
recipe.output.length.should.equal 1
recipe.output[0].itemSlug.qualified.should.equal 'gold_gear'
recipe.output[0].quantity.should.equal 1
it 'assigns a default slug', ->
recipe = new Recipe input:input, pattern:pattern, output:[new Stack itemSlug:ItemSlug.slugify('gold_gear')]
recipe.itemSlug.qualified.should.equal 'gold_gear'
describe 'getStackAtSlot', ->
beforeEach ->
input = [
new Stack itemSlug:ItemSlug.slugify('iron_gear')
new Stack itemSlug:ItemSlug.slugify('gold_ingot'), quantity:4
]
recipe = new Recipe itemSlug:'gold_gear', input:input, pattern:'.1. 101 .1.'
it 'returns the proper item for an early slot', ->
stack = recipe.getStackAtSlot(1)
stack.itemSlug.qualified.should.equal 'gold_ingot'
stack.quantity.should.equal 4
it 'returns the proper item for a late slot', ->
stack = recipe.getStackAtSlot(4)
stack.itemSlug.qualified.should.equal 'iron_gear'
stack.quantity.should.equal 1
it 'returns null for an invalid slot', ->
expect(recipe.getStackAtSlot(12)).to.be.null
describe '_parsePattern', ->
beforeEach ->
recipe = new Recipe
itemSlug: 'oak_wood_planks',
input: [new Stack itemSlug:new ItemSlug('oak_wood')],
pattern:'... .0. ...'
it 'normalizes invalid characters', ->
recipe._parsePattern('$$0 #() 010').should.equal '..0 ... 010'
it 'removes extra characters', ->
recipe._parsePattern('000 000 000 000').should.equal '000 000 000'
it 'fills in missing characters', ->
recipe._parsePattern('000000').should.equal '000 000 ...'
it 'fills in spaces', ->
recipe._parsePattern('000000000').should.equal '000 000 000'
@@ -1,9 +1,9 @@
###
Crafting Guide - simple_stack.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - simple_stack.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
ItemSlug = require './item_slug'
@@ -1,11 +1,11 @@
###
Crafting Guide - stack.coffee
#
# Crafting Guide - stack.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
BaseModel = require '../base_model'
########################################################################################################################
@@ -1,11 +1,10 @@
###
Crafting Guide - command_parser_version_base.coffee
#
# Crafting Guide - command_parser_version_base.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
{StringBuilder} = require 'crafting-guide-common'
########################################################################################################################
@@ -0,0 +1,48 @@
#
# Crafting Guide - command_parser_version_base.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CommandParserVersionBase = require './command_parser_version_base'
########################################################################################################################
parser = null
########################################################################################################################
describe 'command_parser_version_base.coffee', ->
beforeEach -> parser = new CommandParserVersionBase model:{}
describe '_parseHereDoc', ->
it 'returns null for non-heredoc lines', ->
result = parser._parseHereDoc 'foobar: baz'
expect(result[1]).to.be.null
it 'identifies the right text for a real heredoc', ->
parser._lines = ['command: <<-END', 'alpha', 'bravo', 'charlie', 'END', 'command1: arg2']
parser._lineNumber = 1
result = parser._parseHereDoc parser._lines[0]
result[0].should.equal 'command: '
result[1].should.equal 'alpha\nbravo\ncharlie'
it 'identifies an empty heredoc', ->
parser._lines = ['command: <<-END', 'END']
parser._lineNumber = 1
result = parser._parseHereDoc parser._lines[0]
result[0].should.equal 'command: '
expect(result[1]).to.be.null
it 'trims smallest leading whitespace', ->
parser._lines = ['command: <<-END', ' alpha', ' bravo', '', ' charlie', 'END', 'command1: arg2']
parser._lineNumber = 1
result = parser._parseHereDoc parser._lines[0]
result[0].should.equal 'command: '
result[1].should.equal 'alpha\n bravo\n\ncharlie'
@@ -1,12 +1,12 @@
###
Crafting Guide - item_parser.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - item_parser.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
VersionedParserBase = require './versioned_parser_base'
ItemParserV1 = require './parser_versions/item_parser_v1'
ItemParserV1 = require './item_parser_v1'
########################################################################################################################
@@ -1,9 +1,9 @@
###
Crafting Guide - item_parser_v1.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - item_parser_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CommandParserVersionBase = require './command_parser_version_base'
@@ -0,0 +1,99 @@
#
# Crafting Guide - item_parser_v1.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Item = require '../game/item'
ItemParserV1 = require './item_parser_v1'
########################################################################################################################
baseText = item = parser = null
########################################################################################################################
describe 'item_parser_v1.coffee', ->
beforeEach ->
item = new Item name:'alpha'
parser = new ItemParserV1 model:item
describe 'officialUrl', ->
it 'may be omitted', ->
parser.parse 'schema: 1\nvideo: youtubeid, Video Alpha\ndescription: Bravo, Charlie'
expect(item.officialUrl).to.be.null
it 'is assigned properly when given', ->
parser.parse 'schema: 1\nofficialUrl: http://testurl.com'
item.officialUrl.should.equal 'http://testurl.com'
it 'does not allow duplicate declarations', ->
func = -> parser.parse 'schema: 1\nofficialUrl: http://testurl.com\nofficialUrl: http://testurl2.com'
expect(func).to.throw Error, 'duplicate'
it 'does not allow an empty value if given', ->
func = -> parser.parse 'schema: 1\nofficialUrl:'
expect(func).to.throw Error, 'empty'
describe 'description', ->
it 'may be omitted', ->
parser.parse 'schema: 1\nvideo: youTubeId, Video Alpha\nofficialUrl: http://testurl.com'
expect(item.description).to.be.null
it 'is assigned properly when given', ->
parser.parse 'schema: 1\ndescription: Alpha Bravo Charlie'
item.description.should.equal 'Alpha Bravo Charlie'
it 'can be a heredoc', ->
parser.parse 'schema: 1\ndescription: <<-END\nAlpha\nBravo\nCharlie\nEND'
item.description.should.equal 'Alpha\nBravo\nCharlie'
it 'concatenates multiple declarations', ->
parser.parse 'schema: 1\ndescription: Alpha\ndescription: Bravo'
item.description.should.equal 'Alpha\nBravo'
describe 'video', ->
it 'may be omitted', ->
parser.parse 'schema: 1\nofficialUrl: http://testurl.com\ndescription: Alpha Bravo Charlie'
item.videos.should.eql []
it 'is assigned properly when given', ->
parser.parse 'schema: 1\nvideo: youtubeid, Alpha Bravo'
item.videos[0].should.eql youTubeId:'youtubeid', name:'Alpha Bravo'
item.videos.length.should.equal 1
it 'may be included multiple times', ->
parser.parse 'schema: 1\nvideo: youtubeid1, Alpha\nvideo: youtubeid2, Bravo'
item.videos[0].should.eql youTubeId:'youtubeid1', name:'Alpha'
item.videos[1].should.eql youTubeId:'youtubeid2', name:'Bravo'
item.videos.length.should.equal 2
it 'requires a YouTubeId and name', ->
func = -> parser.parse 'schema: 1\nvideo: alpha'
expect(func).to.throw Error, 'requires a name'
describe 'unparsing', ->
it 'can round-trip a fully described item', ->
text = """
schema: 1
officialUrl: http://testurl.com
description: <<-END
Alpha
Bravo
END
video: youtubeid1, Alpha Bravo
video: youtubeid2, Charlie Delta
"""
parser.parse text
parser.unparse().should.equal text
@@ -1,12 +1,12 @@
###
Crafting Guide - mod_parser.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - mod_parser.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
VersionedParserBase = require './versioned_parser_base'
ModParserV1 = require './parser_versions/mod_parser_v1'
ModParserV1 = require './mod_parser_v1'
########################################################################################################################
@@ -1,14 +1,14 @@
###
Crafting Guide - mod_parser_v2.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - mod_parser_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CommandParserVersionBase = require './command_parser_version_base'
Mod = require '../mod'
ModVersion = require '../mod_version'
Tutorial = require '../tutorial'
Mod = require '../game/mod'
ModVersion = require '../game/mod_version'
Tutorial = require '../site/tutorial'
########################################################################################################################
@@ -1,12 +1,12 @@
###
Crafting Guide - mod_version_parser.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - mod_version_parser.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
VersionedParserBase = require './versioned_parser_base'
ModVersionParserV1 = require './parser_versions/mod_version_parser_v1'
ModVersionParserV1 = require './mod_version_parser_v1'
########################################################################################################################
@@ -1,18 +1,17 @@
###
Crafting Guide - mod_version_parser_v2.coffee
#
# Crafting Guide - mod_version_parser_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
CommandParserVersionBase = require './command_parser_version_base'
Item = require '../item'
ItemSlug = require '../item_slug'
ModVersion = require '../mod_version'
Multiblock = require '../multiblock'
Recipe = require '../recipe'
Stack = require '../simple_stack'
Item = require '../game/item'
ItemSlug = require '../game/item_slug'
ModVersion = require '../game/mod_version'
Multiblock = require '../game/multiblock'
Recipe = require '../game/recipe'
Stack = require '../game/simple_stack'
{StringBuilder} = require 'crafting-guide-common'
########################################################################################################################
@@ -0,0 +1,344 @@
#
# Crafting Guide - mod_version_parser_v1.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CommandParserVersionBase = require './command_parser_version_base'
ItemSlug = require '../game/item_slug'
ModVersion = require '../game/mod_version'
ModVersionParserV1 = require './mod_version_parser_v1'
########################################################################################################################
baseText = modVersion = parser = null
########################################################################################################################
describe 'mod_version_parser_v1.coffee', ->
beforeEach ->
modVersion = new ModVersion modSlug:'test', version:'0.0'
parser = new ModVersionParserV1 model:modVersion
describe 'Item', ->
it 'allows multiple recipes', ->
recipes = "item: Charlie;
recipe:; input:Alpha; pattern:... .0. ...;
recipe:; input:Bravo; pattern:... 0.0 ...;"
modVersion = parser.parse recipes
recipes = modVersion.findRecipes ItemSlug.slugify 'test__charlie'
recipes[0].input[0].itemSlug.qualified.should.equal 'alpha'
recipes[1].input[0].itemSlug.qualified.should.equal 'bravo'
describe 'name', ->
it 'adds the name when present', ->
modVersion = parser.parse 'item: Charlie'
modVersion._items.charlie.name.should.equal 'Charlie'
it 'requires a non-empty name', ->
func = -> parser.parse 'item: \n'
expect(func).to.throw Error, 'cannot be empty'
describe 'gatherable', ->
it 'adds "gatherable" when present', ->
modVersion = parser.parse 'item: Alpha Bravo; gatherable: yes'
modVersion._items.alpha_bravo.isGatherable.should.be.true
it 'does not allow a duplicate "gatherable" declaration', ->
func = -> parser.parse 'item: Alpha Bravo; gatherable: yes; gatherable: yes'
expect(func).to.throw Error, 'duplicate'
it 'requires "gatherable" to be "yes" or "no"', ->
func = -> parser.parse 'item: Alpha Bravo; gatherable: true'
expect(func).to.throw Error, 'gatherable must be'
it 'does not allow "gatherable" before "item"', ->
func = -> parser.parse 'gatherable: yes; item: Alpha Bravo; gatherable: yes'
expect(func).to.throw Error, '"gatherable" before "item"'
describe 'Multiblock', ->
it 'adds a "multiblock" when present', ->
modVersion = parser.parse 'item: Alpha; multiblock:; input:Bravo; layer: 0'
item = modVersion.findItemByName 'Alpha'
item.multiblock.height.should.equal 1
it 'requires "item" be declared before "multiblock"', ->
func = -> parser.parse "multiblock:"
expect(func).to.throw Error, '"multiblock" before "item"'
it 'prohibits multiple "multiblock" commands per item"', ->
func = -> parser.parse "item: Alpha; multiblock:; multiblock:"
expect(func).to.throw Error, 'duplicate'
it 'prohibits multiblocks with no inputs', ->
func = -> parser.parse "item: Alpha; multiblock:; layer: 000"
expect(func).to.throw Error, 'at least one "input"'
describe 'layer', ->
it 'allows multiple "layer" commands', ->
modVersion = parser.parse 'item: Alpha; multiblock:; input:Bravo, Charlie; layer: 01 10; layer: 10 01'
item = modVersion.findItemByName 'Alpha'
item.multiblock.depth.should.equal 2
item.multiblock.height.should.equal 2
item.multiblock.width.should.equal 2
it 'prohibits empty layers', ->
func = -> parser.parse 'item: Alpha; multiblock:; input: Bravo; layer:; layer: 00 00'
expect(func).to.throw Error, 'empty layer'
it 'prohibits multiblocks with no layers', ->
func = -> parser.parse "item: Alpha; multiblock:; input: Bravo"
expect(func).to.throw Error, 'at least one "layer"'
describe 'Recipe', ->
beforeEach -> baseText = 'item: Charlie; '
describe 'input', ->
it 'adds "input" when present', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern: ... 010 ...'
charlieSlug = ItemSlug.slugify('test__charlie')
slugs = (s.itemSlug.item for s in modVersion.findRecipes(charlieSlug)[0].input)
slugs.should.eql ['alpha', 'bravo']
it 'requires an "input" declaration', ->
func = -> parser.parse baseText + 'recipe:; pattern: ... .0. ...'
expect(func).to.throw Error, 'the "input" declaration is required'
it 'does not allow a duplicate "input" declaration', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:....0....; input:Bravo'
expect(func).to.throw Error, 'duplicate declaration of "input"'
it 'does not allow "input" before "recipe"', ->
func = -> parser.parse baseText + 'input:Alpha, Bravo; recipe:; pattern:....0....'
expect(func).to.throw Error, 'cannot declare "input" before "recipe"'
it 'registers slugs for each input name', ->
modVersion = parser.parse baseText + 'recipe:; input:Delta, Echo, Foxtrot; pattern:...012...'
(s.item for s in modVersion._slugs).should.eql ['charlie', 'delta', 'echo', 'foxtrot']
it 'correctly handles recipes which use the same input multiple times', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha; pattern:.0..0....'
recipe = _.values(modVersion._recipes)[0]
recipe.getQuantityRequired(ItemSlug.slugify('alpha')).should.equal 2
it 'allows a quantity for each input', ->
modVersion = parser.parse baseText + 'recipe:; input: 12 Delta, 3 Echo; pattern:... 0.1 ...'
recipe = _.values(modVersion._recipes)[0]
recipe.input[0].quantity.should.equal 12
recipe.input[0].itemSlug.qualified.should.equal 'delta'
recipe.input[1].quantity.should.equal 3
recipe.input[1].itemSlug.qualified.should.equal 'echo'
describe 'pattern', ->
it 'adds "pattern" when present', ->
modVersion = parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:... .0. .1.'
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].pattern.should.equal '... .0. .1.'
it 'requires a "pattern" declaration', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo'
expect(func).to.throw Error, 'the "pattern" declaration is required'
it 'does not allow a duplicate "pattern" declaration', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern:....0..1.; pattern:01.......'
expect(func).to.throw Error, 'duplicate declaration of "pattern"'
it 'requires pattern to be the right length', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:000'
expect(func).to.throw Error, 'a pattern must have'
it 'requires pattern to only use proper characters', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:abc def ghi'
expect(func).to.throw Error, 'a pattern must have'
it 'requires pattern to only refer to existing items', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha; pattern:... 010 ...'
expect(func).to.throw Error, 'there is no item 1 in this recipe'
it 'requires all items to appear in the pattern', ->
func = -> parser.parse baseText + 'recipe:; input:Alpha, Bravo; pattern: 000 0.0 000'
expect(func).to.throw Error, 'Bravo is listed'
it 'does not allow "pattern" before "recipe"', ->
func = -> parser.parse baseText + 'pattern:... .0. ...; recipe:; inputs:Alpha'
expect(func).to.throw Error, 'cannot declare "pattern" before "recipe"'
describe 'quantity', ->
beforeEach ->
baseText = 'item: Charlie; recipe:; input:Alpha; pattern:...0.0...; '
it 'adds "quantity" when present', ->
modVersion = parser.parse baseText + 'quantity: 2'
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].output[0].quantity.should.equal 2
it 'does not allow a duplicate "quantity" declaration', ->
func = -> parser.parse baseText + 'quantity:1; quantity:2'
expect(func).to.throw Error, 'duplicate declaration of "quantity"'
it 'requires quantity to be an integer', ->
func = -> parser.parse baseText + 'quantity:ten'
expect(func).to.throw Error, 'quantity must be an integer'
it 'assumes a quantity of 1 by default', ->
modVersion = parser.parse baseText
modVersion.findRecipes(ItemSlug.slugify('test__charlie'))[0].output[0].quantity.should.equal 1
it 'does not allow "quantity" before recipe', ->
func = -> parser.parse 'item:Bravo; quantity:12; recipe:;'
expect(func).to.throw Error, 'cannot declare "quantity" before "recipe"'
describe 'onlyIf', ->
beforeEach ->
baseText = 'item: Charlie; recipe:; input:Alpha; pattern:...0.0...; '
it 'understands the "item" verb', ->
modVersion = parser.parse baseText + 'onlyIf: item Iron Ingot'
recipes = []
modVersion.eachRecipe (recipe)-> recipes.push recipe
recipe = recipes[0]
recipe.condition.should.eql verb:'item', noun:'Iron Ingot', inverted:false
it 'understands the "mod" verb', ->
modVersion = parser.parse baseText + 'onlyIf: mod BuildCraft'
recipes = []
modVersion.eachRecipe (recipe)-> recipes.push recipe
recipe = recipes[0]
recipe.condition.should.eql verb:'mod', noun:'BuildCraft', inverted:false
it 'understands inverting verbs', ->
modVersion = parser.parse baseText + 'onlyIf: not item Iron Ingot'
recipes = []
modVersion.eachRecipe (recipe)-> recipes.push recipe
recipe = recipes[0]
recipe.condition.should.eql verb:'item', noun:'Iron Ingot', inverted:true
it 'requires at least two words', ->
func = -> parser.parse baseText + 'onlyIf: item'
expect(func).to.throw Error, 'verb followed by a noun'
it 'only allows known verbs', ->
func = -> parser.parse baseText + 'onlyIf: foo Iron Ingot'
expect(func).to.throw Error, 'unknown verb'
describe 'output', ->
beforeEach ->
baseText = 'item: Delta; item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
it 'adds a single item as the default output', ->
modVersion = parser.parse baseText
stack = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].output[0]
stack.itemSlug.qualified.should.equal 'test__bravo'
stack.quantity.should.equal 1
it 'can add multiple extras with quantities', ->
modVersion = parser.parse baseText + 'extras:2 Delta, 4 Echo'
output = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].output
output[0].itemSlug.qualified.should.equal 'test__bravo'
output[0].quantity.should.equal 1
output[1].itemSlug.qualified.should.equal 'test__delta'
output[1].quantity.should.equal 2
output[2].itemSlug.qualified.should.equal 'echo'
output[2].quantity.should.equal 4
it 'does not allow "extras" before "recipe"', ->
func = -> parser.parse 'item:Bravo; extras:Charlie'
expect(func).to.throw Error, 'cannot declare "extras" before "recipe"'
it 'registers slugs for each output name', ->
modVersion = parser.parse baseText + 'extras:Delta, Echo'
(s.qualified for s in modVersion._slugs).should.eql [
'test__bravo', 'charlie', 'test__delta', 'echo'
]
it 'does not allow a duplicate "extras" declaration', ->
func = -> parser.parse baseText + 'extras:Echo; extras:Delta'
expect(func).to.throw Error, 'duplicate declaration of "extras"'
describe 'tools', ->
beforeEach ->
baseText = 'item:Bravo; recipe:; input:Charlie; pattern:... .0. ...; '
it 'can add a single tool', ->
modVersion = parser.parse baseText + 'tools: Furnace'
modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].tools[0].itemSlug.item.should.equal 'furnace'
it 'can add multiple tools', ->
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
tools = modVersion.findRecipes(ItemSlug.slugify('test__bravo'))[0].tools
tools[0].itemSlug.item.should.equal 'crafting_table'
tools[1].itemSlug.item.should.equal 'furnace'
it 'registers slugs for each tool name', ->
modVersion = parser.parse baseText + 'tools: Crafting Table, Furnace'
(s.item for s in modVersion._slugs).should.eql ['bravo', 'charlie', 'crafting_table', 'furnace']
it 'does not allow a duplicate "tools" declaration', ->
func = -> parser.parse baseText + 'tools:Crafting Table; tools:Furnace'
expect(func).to.throw Error, 'duplicate declaration of "tools"'
describe "unparsing", ->
beforeEach ->
baseText = """
schema: 1
group: Agriculture
item: Apple
item: Baked Potato
recipe:
input: furnace fuel, Potato
pattern: .1. ... .0.
tools: Furnace
item: (filled) Canned Food
recipe:
input: 4 (Empty) Tin Can, Apple
pattern: .1. .0. ...
item: Pyramid
multiblock:
input: Cobblestone
layer: 000 000 000
layer: ... .0. ...
group: Functional Blocks
item: Furnace
recipe:
input: Cobblestone
pattern: 000 0.0 000
tools: Crafting Table
update: Iron Ingot
recipe:
input: furnace fuel, Iron Dust
pattern: .1. ... .0.
tools: Furnace
recipe:
onlyIf: item Redstone Furnace
input: Iron Ore
pattern: ... .0. ...
tools: Redstone Furnace
"""
it 'can round-trip a data file', ->
text = parser.unparse parser.parse baseText
actual = CommandParserVersionBase.simplify text
expected = CommandParserVersionBase.simplify baseText
actual.should.equal expected
@@ -1,12 +1,12 @@
###
Crafting Guide - tutorial_parser.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - tutorial_parser.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
VersionedParserBase = require './versioned_parser_base'
TutorialParserV1 = require './parser_versions/tutorial_parser_v1'
TutorialParserV1 = require './tutorial_parser_v1'
########################################################################################################################
@@ -1,12 +1,12 @@
###
Crafting Guide - tutorial_parser_v1.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
#
# Crafting Guide - tutorial_parser_v1.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
CommandParserVersionBase = require './command_parser_version_base'
Tutorial = require '../tutorial'
Tutorial = require '../site/tutorial'
########################################################################################################################
@@ -1,11 +1,9 @@
###
Crafting Guide - versioned_parser_base.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
#
# Crafting Guide - versioned_parser_base.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
@@ -1,15 +1,14 @@
###
Crafting Guide - craft_page.coffee
#
# Crafting Guide - craft_page.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
Craftsman = require './crafting/craftsman'
{Event} = require '../constants'
Inventory = require './inventory'
ModPack = require './mod_pack'
BaseModel = require '../base_model'
Craftsman = require '../crafting/craftsman'
Inventory = require '../game/inventory'
ModPack = require '../game/mod_pack'
########################################################################################################################
@@ -21,10 +20,10 @@ module.exports = class CraftPage extends BaseModel
attributes.craftsman ?= new Craftsman attributes.modPack
super attributes, options
@modPack.on Event.change, => @_consumeParams()
@on Event.change + ':params', => @_consumeParams()
@craftsman.on Event.change + ':stage', => @trigger Event.change, this
@craftsman.on Event.change + ':complete', => @trigger Event.change, this
@modPack.on c.event.change, => @_consumeParams()
@on c.event.change + ':params', => @_consumeParams()
@craftsman.on c.event.change + ':stage', => @trigger c.event.change, this
@craftsman.on c.event.change + ':complete', => @trigger c.event.change, this
# Private Methods ##############################################################################
@@ -1,19 +1,17 @@
###
Crafting Guide - editable_file.coffee
#
# Crafting Guide - editable_file.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
{Event} = require '../constants'
w = require 'when'
BaseModel = require '../base_model'
########################################################################################################################
module.exports = class EditableFile extends BaseModel
@MimeTypes = MimeTypes = [
@::MimeTypes = [
{ pattern:/\.cg$/i, text:'text/plain' }
{ pattern:/\.png$/i, text:'image/png' }
{ pattern:/\.gif$/i, text:'image/gif' }
@@ -21,7 +19,7 @@ module.exports = class EditableFile extends BaseModel
{ pattern:/\.jpeg$/i, text:'image/jpeg' }
]
@Status = Status = {
@::Status = {
'unknown': 'unknown'
'checking': 'checking'
'empty': 'empty'
@@ -30,16 +28,17 @@ module.exports = class EditableFile extends BaseModel
}
constructor: (attributes={}, options={})->
if not options.client? then throw new Error 'options.client is required'
attributes.fileName ?= ''
attributes.path ?= ''
attributes.sha ?= null
attributes.status ?= Status.unknown
super
super attributes, options
@client = options.client
@_client = options.client
@_status = @Status.unknown
@_encodedData = null
@on Event.change + ':fileName', => @_mimeType = null
@on c.event.change + ':fileName', => @_mimeType = null
# Public Methods ###############################################################################
@@ -66,66 +65,79 @@ module.exports = class EditableFile extends BaseModel
@_encodedData = newEncodedData
@_updateStatusForNewData()
@trigger Event.change + ':encodedData', this, oldEncodedData, newEncodedData
@trigger Event.change, this
@trigger c.event.change + ':encodedData', this, oldEncodedData, newEncodedData
@trigger c.event.change, this
getFullPath: ->
return "#{@path}/#{@fileName}"
getMimeType: ->
if not @_mimeType?
for mimeType in MimeTypes
for mimeType in @MimeTypes
if mimeType.pattern.test @fileName
@_mimeType = mimeType.text
break
return @_mimeType
getStatus: ->
return @_status
isValid: ->
return @status in [ Status.clean, Status.dirty ]
return @_status in [ @Status.clean, @Status.dirty ]
Object.defineProperties @prototype,
encodedData: { get:@prototype.getEncodedData, set:@prototype.setEncodedData }
fullPath: { get:@prototype.getFullPath }
imageUrl: { get:@prototype.getImageUrl }
mimeType: { get:@prototype.getMimeType }
status: { get:@prototype.getStatus }
valid: { get:@prototype.isValid }
# BaseModel Overrides ##########################################################################
fetch: ->
if not @client? then throw new Error 'EditableFile must be given a client to fetch with'
if not @_client? then throw new Error 'EditableFile must be given a client to fetch with'
@status = Status.checking
@client.fetchFile path:@fullPath
@_updateStatus @Status.checking
@_client.fetchFile path:@fullPath
.then (response)=>
data = response.json.data
if data.sha?
@encodedData = data.content
@sha = data.sha
@_updateStatusForNewData()
else
@status = Status.empty
@_updateStatus @Status.empty
return this
save: (commitMessage)->
return w(true) if @status is Status.clean
return w(true) if @_status is @Status.clean
return w(true) unless @encodedData?
if not @valid then return w.reject new Error "cannot save with an invalid status: #{@status}"
if not @valid then return w.reject new Error "cannot save with an invalid status: #{@_status}"
commitMessage ?= "User-submitted update to #{@fileName} from #{global.hostName}"
@client.updateFile content:@encodedData, message:commitMessage, path:@fullPath, sha:@sha
@_client.updateFile content:@encodedData, message:commitMessage, path:@fullPath, sha:@sha
# Private Methods ##############################################################################
_updateStatus: (newStatus)->
oldStatus = @_status
return if oldStatus is newStatus
@_status = newStatus
@trigger c.event.change, this, oldStatus, newStatus
@trigger c.event.change, this
_updateStatusForNewData: ->
switch @status
when Status.unknown
@status = Status.dirty
when Status.checking
@status = Status.clean
when Status.empty
@status = Status.dirty
when Status.clean
@status = Status.dirty
switch @_status
when @Status.unknown
@_updateStatus @Status.dirty
when @Status.checking
@_updateStatus @Status.clean
when @Status.empty
@_updateStatus @Status.dirty
when @Status.clean
@_updateStatus @Status.dirty
@@ -0,0 +1,32 @@
#
# Crafting Guide - email_client.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
emailjs = require 'emailjs'
########################################################################################################################
emailjs.init 'user_sLxEH6RwPvzowjaIkfY6H'
########################################################################################################################
module.exports = class EmailClient
constructor: ->
@serviceId = 'default_service'
# Public Methods ###############################################################################
send: (template, data={})->
logger.info -> "sending email with template #{template} and data: #{JSON.stringify(data)}"
emailjs.send @serviceId, template, data
.then (response)->
logger.info -> "Email sent! status: #{response.status}, message: #{response.text}"
return status:response.status, message:response.text
.catch (error)->
logger.error -> "Email failed! error: #{error}"
throw error
@@ -1,19 +1,18 @@
###
Crafting Guide - user.coffee
#
# Crafting Guide - github_user.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
{Url} = require '../constants'
BaseModel = require '../base_model'
########################################################################################################################
module.exports = class GitHubUser extends BaseModel
constructor: (attributes={}, options={})->
attributes.avatarUrl ?= null
attributes.avatarUrl ?= attributes.avatar_url ?= null
attributes.email ?= null
attributes.login ?= null
attributes.name ?= null
@@ -1,14 +1,11 @@
###
Crafting Guide - item_page.coffee
#
# Crafting Guide - item_page.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
BaseModel = require './base_model'
{Event} = require '../constants'
{Url} = require '../constants'
BaseModel = require '../base_model'
########################################################################################################################
@@ -42,13 +39,13 @@ module.exports = class ItemPage extends BaseModel
# Private Methods ##############################################################################
_findRecipesMatching: (callback)->
_findRecipesMatching: (isAcceptable)->
return null unless @item?
result = {}
@modPack.eachMod (mod)=>
mod.eachRecipe (recipe)=>
if callback(recipe)
if isAcceptable recipe
for outputStack in recipe.output
outputItem = @modPack.findItem outputStack.itemSlug, includeDisabled:true
result[outputItem.slug] = outputItem
@@ -1,13 +1,12 @@
###
Crafting Guide - item_selector.coffee
#
# Crafting Guide - item_selector.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
ItemSlug = require './item_slug'
_ = require 'underscore'
BaseModel = require '../base_model'
ItemSlug = require '../game/item_slug'
########################################################################################################################
@@ -26,25 +25,25 @@ module.exports = class ItemSelector extends BaseModel
# Property Methods #############################################################################
getHint: ->
return @_hint
setHint: (newHint)->
oldHint = @_hint
return if newHint is oldHint
@_hint = newHint.toLowerCase()
@trigger Event.change + ':hint', this, oldHint, newHint
@_refreshResults()
@trigger Event.change, this
getResults: ->
return @_results
Object.defineProperties @prototype,
hint: {get:@prototype.getHint, set:@prototype.setHint}
results: {get:@prototype.getResults}
hint:
get: ->
return @_hint
set: (newHint)->
oldHint = @_hint
return if newHint is oldHint
@_hint = newHint.toLowerCase()
@trigger c.event.change + ':hint', this, oldHint, newHint
@_refreshResults()
@trigger c.event.change, this
results:
get: ->
return @_results
# Private Methods ##############################################################################
@@ -87,4 +86,4 @@ module.exports = class ItemSelector extends BaseModel
newResults.sort ItemSlug.compare
@_results = newResults
@trigger Event.change + ':results', this, oldResults, newResults
@trigger c.event.change + ':results', this, oldResults, newResults
@@ -1,11 +1,10 @@
###
Crafting Guide - markdown_image.coffee
#
# Crafting Guide - markdown_image.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
EditableFile = require './editable_file'
########################################################################################################################
@@ -1,12 +1,11 @@
###
Crafting Guide - markdown_image_list.coffee
#
# Crafting Guide - markdown_image_list.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
{Event} = require '../constants'
BaseModel = require '../base_model'
MarkdownImage = require './markdown_image'
########################################################################################################################
@@ -21,11 +20,11 @@ module.exports = class MarkdownImageList extends BaseModel
@client = options.client
@_images = {}
@on Event.change + ':imageBase', =>
@on c.event.change + ':imageBase', =>
for fileName, image of @_images
image.path = @imageBase
@on Event.change + ':markdownText', => @_analyzeMarkdownText()
@on c.event.change + ':markdownText', => @_analyzeMarkdownText()
@_analyzeMarkdownText()
# Public Methods ###############################################################################
@@ -35,7 +34,7 @@ module.exports = class MarkdownImageList extends BaseModel
fetchImages: ->
for fileName, image of @_images
if image.status is MarkdownImage.Status.unknown
if image.status is MarkdownImage::Status.unknown
image.fetch()
reset: ->
@@ -80,17 +79,17 @@ module.exports = class MarkdownImageList extends BaseModel
image = @_images[fileName]
if not image?
image = new MarkdownImage {fileName:fileName, path:@imageBase}, {client:@client}
@listenTo image, Event.change, => @trigger Event.change, this
@trigger Event.add, this, image
@listenTo image, c.event.change, => @trigger c.event.change, this
@trigger c.event.add, this, image
changed = true
newImages[fileName] = image
for fileName, image of @_images
if not newImages[fileName]?
@trigger Event.remove, this, image
@trigger c.event.remove, this, image
changed = true
if changed
@_images = newImages
@trigger Event.change, this
@trigger c.event.change, this
@@ -0,0 +1,65 @@
#
# Crafting Guide - markdown_image_list.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
MarkdownImageList = require './markdown_image_list'
########################################################################################################################
list = null
########################################################################################################################
describe 'markdown_image_list.coffee', ->
beforeEach ->
list = new MarkdownImageList {}, client:{fetchFile:sinon.stub().returns(w.promise(->))}
describe '_analyzeMarkdownText', ->
it 'finds nothing in a null string', ->
list.all.should.eql []
it 'finds nothing in an empty string', ->
list.markdownText = ''
list.all.should.eql []
it 'finds nothing when there are no images in the markdown', ->
list.markdownText = 'alpha bravo charlie delta'
list.all.should.eql []
it 'finds a single image alone', ->
list.markdownText = '![](alpha.png)'
(i.fileName for i in list.all).sort().should.eql ['alpha.png']
it 'finds a single image inside a markdown document', ->
list.markdownText = '# alpha\n bravo charlie\n\n| ![](alpha.png) |\n|:----------:|\ndelta'
(i.fileName for i in list.all).sort().should.eql ['alpha.png']
it 'finds multiple images inside a markdown document', ->
list.markdownText = '| Images |\n|:------:|\n| ![](alpha.png) |\n| ![](bravo.png) |\n| ![](charlie.png) |\n'
(i.fileName for i in list.all).sort().should.eql ['alpha.png', 'bravo.png', 'charlie.png']
it 'adds new images when markdown changes', ->
list.markdownText = '![](alpha.png)'
(i.fileName for i in list.all).sort().should.eql ['alpha.png']
list.markdownText = '![](alpha.png), ![](bravo.png)'
(i.fileName for i in list.all).sort().should.eql ['alpha.png', 'bravo.png']
it 'removes missing images when markdown changes', ->
list.markdownText = '![](alpha.png), ![](bravo.png)'
(i.fileName for i in list.all).sort().should.eql ['alpha.png', 'bravo.png']
list.markdownText = '![](alpha.png)'
(i.fileName for i in list.all).sort().should.eql ['alpha.png']
it 'does not change remaining images when still present in changed markdown', ->
list.markdownText = '![](alpha.png)'
list.all[0].testField = 'foo'
list.markdownText = '![](alpha.png) bravo'
list.all[0].testField.should.equal 'foo'
@@ -1,14 +1,11 @@
###
Crafting Guide - tutorial.coffee
#
# Crafting Guide - tutorial.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseModel = require './base_model'
TutorialParser = require './tutorial_parser'
_ = require 'underscore'
{Url} = require '../constants'
BaseModel = require '../base_model'
########################################################################################################################
@@ -26,11 +23,11 @@ module.exports = class Tutorial extends BaseModel
# Backbone.Model Overrides #####################################################################
parse: (text)->
TutorialParser = require './tutorial_parser' # to avoid require cycles
TutorialParser = require '../parsing/tutorial_parser' # to avoid require cycles
@_parser ?= new TutorialParser model:this
@_parser.parse text
return null # prevent calling `set`
url: ->
return Url.tutorialData modSlug:@modSlug, tutorialSlug:@slug
return c.url.tutorialData modSlug:@modSlug, tutorialSlug:@slug
+162
View File
@@ -0,0 +1,162 @@
#
# Crafting Guide - base_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
templates = require './templates'
########################################################################################################################
module.exports = class BaseController extends Backbone.View
constructor: (options={})->
@_children = []
@_model = null
@_parent = options.parent
@_rendered = false
@router = options.router
@tryRefresh = _.debounce (=> @_tryRefresh()), 100
@_loadTemplate options.templateName
super options
# Public Methods ###############################################################################
addChild: (Controller, atSelector, options={})->
if not _.isFunction Controller then throw new Error "Controller must be a constructor"
options.el = @$(atSelector)[0]
options.parent = this
child = new Controller options
child.render options
@_children.push child
return child
hide: ($el)->
$el ?= @$el
$el.css 'display', 'none'
refresh: ->
logger.verbose => "#{this} refreshing"
remove: ->
@$el.remove()
@off()
routeLinkClick: (event)->
event.preventDefault()
href = $(event.target).attr 'href'
href ?= $(event.currentTarget).attr 'href'
logger.info "Re-routing link to internal navigation: #{href}"
if href? and href.match /^http/
window.location.href = href
else
@router.navigate href, trigger:true
show: ($el)->
$el ?= @$el
$el.css 'display', ''
unrender: ->
@undelegateEvents()
@$el.empty()
@_rendered = false
# Event Methods ################################################################################
onDidModelChange: ->
@tryRefresh()
onDidModelSync: ->
@tryRefresh()
onWillRender: -> # do nothing
onDidRender: -> # do nothing
onWillShow: -> # do nothing
onDidShow: -> # do nothing
onWillChangeModel: (oldModel, newModel)->
if oldModel?.on?
@stopListening oldModel
if newModel?.on?
@listenTo newModel, 'sync', (e)=> @onDidModelSync e
@listenTo newModel, 'change', (e)=> @onDidModelChange e
return newModel
# Property Methods #############################################################################
Object.defineProperties @prototype,
model:
get: -> @_model
set: (newModel)->
return if @model is newModel
newModel = @onWillChangeModel @_model, newModel
@_model = newModel
@tryRefresh()
rendered:
get: -> @_rendered
user:
get: -> @_user
set: (newUser)->
@_user = newUser
@tryRefresh()
# Backbone.View Overrides ######################################################################
events: ->
return {}
render: (options={})->
options.force ?= false
options.show ?= true
return this unless not @_rendered or options.force
if not @_template?
logger.error => "Default render called for #{@constructor.name} without a template"
return this
data = (@model?.toHash? and @model.toHash()) or @model or {}
logger.verbose => "#{this} rendering with data: #{data}"
$renderedEl = $($(@_template(data))[0])
@onWillRender()
@unrender()
@$el.append $renderedEl.children()
@$el.addClass $renderedEl.attr 'class'
@$el.data $renderedEl.data()
@delegateEvents()
@_rendered = true
@onDidRender()
@refresh()
return this
# Object Overrides #############################################################################
toString: ->
return "#{@constructor.name}.#{@cid}"
# Private Methods ##############################################################################
_loadTemplate: (templateName)->
if templateName?
@_template = templates[templateName]
_tryRefresh: ->
return unless @_rendered
@refresh()
@@ -0,0 +1,13 @@
//-
//- Crafting Guide - browse_page.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.content.view__browse_page
.left
.view__adsense
.right
.tile_container
@@ -0,0 +1,23 @@
//
// Crafting Guide - browse_page.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__browse_page {
@include layout-body-with-sidebar;
.right {
.tile_container {
display : flex;
flex-wrap : wrap;
justify-content : space-between;
.view__mod_tile {
max-width : calc(50% - 12px); // $size-margin-large / 2
margin-bottom : $size-margin-large;
}
}
}
}
@@ -0,0 +1,64 @@
#
# Crafting Guide - browse_page_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
AdsenseController = require '../common/adsense/adsense_controller'
ModTileController = require './mod_tile/mod_tile_controller'
PageController = require '../page_controller'
########################################################################################################################
module.exports = class BrowsePageController extends PageController
constructor: (options={})->
if not options.modPack then throw new Error 'options.modPack is required'
options.templateName = 'browse_page'
super options
@_modPack = options.modPack
@_tileControllers = []
# PageController Overrides #####################################################################
getTitle: ->
return "Browse"
# BaseController Overrides #####################################################################
onDidRender: ->
@_adsenseController = @addChild AdsenseController, '.view__adsense', model:'skyscraper'
@$tileContainer = @$('.tile_container')
refresh: ->
@_refreshModTiles()
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click a': 'routeLinkClick'
# Private Methods ##############################################################################
_refreshModTiles: ->
index = 0
mods = @_modPack.getAllMods()
for mod in mods
controller = @_tileControllers[index]
if not controller
controller = new ModTileController model:mod, router:@router
controller.render()
@$tileContainer.append controller.$el
@_tileControllers.push controller
else
controller.model = mod
index++
while @_tileControllers.length > mods.length
controller = @_tileControllers.pop()
controller.remove()
@@ -0,0 +1,14 @@
//-
//- Crafting Guide - mod_tile.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__mod_tile
a
.left
img
.right
p.title
p.description
@@ -0,0 +1,88 @@
//
// Crafting Guide - mod_tile.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__mod_tile {
min-width : $size-minecraft-block * 9;
display : flex;
justify-content : stretch;
align-items : stretch;
a {
max-width: 100%;
@include floating-panel;
border : $size-border-medium solid $color-background-panel;
color : $color-text;
text-decoration : none;
display : flex;
padding : $size-margin-medium;
& > .left {
flex: 0 0 $size-minecraft-block * 4;
margin-right: $size-margin-medium;
img {
width: $size-minecraft-block * 4;
max-height: $size-minecraft-block * 4;
}
}
& > .right {
flex : 1 1 100%;
overflow : hidden;
.title {
margin-bottom : $size-margin-medium;
font-family : $font-family-header;
font-size : $font-size-xlarge;
}
.description {
font-family : $font-family-text;
font-size : $font-size-medium;
line-height : $font-size-medium * 1.5;
}
}
&:hover {
background: $color-blue-faint;
border-color: $color-link-normal;
& > .right {
.title {
color: $color-link-normal;
text-decoration: underline;
}
}
}
}
&.disabled {
a {
& > .left {
img {
@include grayscale;
opacity: 0.25;
}
}
& > .right {
.title {
color: $color-gray-light;
}
.description {
color: $color-gray-light;
}
}
}
}
}
@@ -0,0 +1,44 @@
#
# Crafting Guide - mod_tile_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
########################################################################################################################
module.exports = class ModTileController extends BaseController
constructor: (options={})->
options.templateName = 'browse_page/mod_tile'
super options
# BaseController Overrides #####################################################################
onDidRender: ->
@$link = @$('a')
@$logoImage = @$('img')
@$title = @$('p.title')
@$description = @$('p.description')
super
refresh: ->
@$link.attr 'href', c.url.mod modSlug:@model.slug
@$logoImage.attr 'src', c.url.modIcon modSlug:@model.slug
@$title.text @model.name
@$description.text @model.description
if @model.enabled
@$el.removeClass 'disabled'
else
@$el.addClass 'disabled'
super
# Backbone.View Overrides ######################################################################
events: ->
_.extend super,
'click a': 'routeLinkClick'
@@ -0,0 +1,8 @@
//-
//- Crafting Guide - adsense.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__adsense
@@ -0,0 +1,25 @@
//
// Crafting Guide - adsense.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__adsense {
.skyscraper {
height : $size-skyscraper-height;
width : $size-skyscraper-width;
position : relative;
border : 0.1em solid $color-gray-faint;
margin : $size-margin-large 0;
&:first-child {
margin-top : 0;
}
ins {
position: absolute; top: 0; right: 0; bottom: 0; left: 0;
}
}
}
@@ -1,46 +1,39 @@
###
Crafting Guide - adsense_controller.coffee
#
# Crafting Guide - adsense_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
$ = require 'jquery'
BaseController = require './base_controller'
_ = require 'underscore'
{Adsense} = require '../constants'
{Duration} = require '../constants'
{ProductionEnvs} = require '../constants'
BaseController = require '../../base_controller'
########################################################################################################################
module.exports = class AdsenseController extends BaseController
constructor: (options={})->
options.templateName = "adsense_sidebar_skyscraper"
options.templateName = 'common/adsense'
super options
@_adsEnabled = global.env in ProductionEnvs
@_adHeight = 630
@_adCount = 0
@_waiting = false
@_adsEnabled = global.env in c.productionEnvs
@_adCount = 0
@_waiting = false
# Public Methods ###############################################################################
fillAdPositions: ->
if @_adCount > 0
return unless @_adCount < Adsense.slotIds.length
return unless @$sidebar? and @$mainBody?
return unless @$sidebar.height() + @_adHeight < @$mainBody.height()
return unless @$sidebar and @$mainBody
return unless @_adCount < @_computeMaxAds()
return unless @_adCount < c.adsense.slotIds.length
$adContainer = $('<div class="sidebar_skyscraper"></div>')
$adContainer = $('<div class="skyscraper"></div>')
@$el.append $adContainer
if @_adsEnabled
$ad = $('<ins class="adsbygoogle"></ins>')
$ad.attr 'data-ad-client', Adsense.clientId
$ad.attr 'data-ad-slot', Adsense.slotIds[@_adCount]
$ad.css 'height':'600px', 'width':'160px'
$ad.attr 'data-ad-client', c.adsense.clientId
$ad.attr 'data-ad-slot', c.adsense.slotIds[@_adCount]
$adContainer.append $ad
@_loadAds()
@@ -50,8 +43,8 @@ module.exports = class AdsenseController extends BaseController
# BaseController Overrides #####################################################################
onDidRender: ->
@$mainBody = $('.mainBody')
@$sidebar = $('.sidebar')
@$mainBody = $('.page > .content > .right')
@$sidebar = $('.page > .content > .left')
super
refresh: ->
@@ -60,6 +53,11 @@ module.exports = class AdsenseController extends BaseController
# Private Methods #############################################################################
_computeMaxAds: ->
return null unless @$sidebar
result = Math.floor @$sidebar.height() / (c.adsense.skyscraper.height + c.adsense.skyscraper.margin)
return result
_loadAds: ->
return unless @_adsEnabled
@@ -73,8 +71,10 @@ module.exports = class AdsenseController extends BaseController
_waitForPageReadiness: ->
return if @_waiting
if @$mainBody.height() < @_adHeight / 2
if @$sidebar.height() < c.adsense[@model].height / 2
logger.verbose "Adsense is waiting for room to insert ads"
@_waiting = true
_.delay (=> @_waiting = false; @_waitForPageReadiness()), Duration.normal
_.delay (=> @_waiting = false; @_waitForPageReadiness()), c.duration.normal
else
logger.verbose "Adsense is ready to fill ads"
@fillAdPositions()
@@ -0,0 +1,20 @@
//-
//- Crafting Guide - crafting_grid.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- All rights reserved.
//-
.view__crafting_grid
.row
.view__slot
.view__slot
.view__slot
.row
.view__slot
.view__slot
.view__slot
.row
.view__slot
.view__slot
.view__slot
@@ -0,0 +1,27 @@
//
// Crafting Guide - crafting_grid.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__crafting_grid {
background : $color-gray-light;
border : $size-border-medium solid $color-gray-medium;
display : flex;
flex-direction : column;
.row {
flex: 1 1 auto;
display: flex;
.view__slot {
flex: 1 1 auto;
background : $color-white;
margin : $size-border-small;
}
}
}
@@ -0,0 +1,43 @@
#
# Crafting Guide - crafting_grid_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
SlotController = require '../slot/slot_controller'
########################################################################################################################
module.exports = class CraftingGridController extends BaseController
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'
# options.model should be a recipe
options.templateName = 'common/crafting_grid'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_router = options.router
@_modPack.on c.event.change, => @tryRefresh()
# BaseController Methods #######################################################################
onDidRender: ->
@_slotControllers = []
for el in @$('.view__slot')
controller = new SlotController el:el, imageLoader:@_imageLoader, modPack:@_modPack, router:@_router
controller.render()
@_slotControllers.push controller
super
refresh: ->
for controller, index in @_slotControllers
controller.model = @model?.getStackAtSlot(index)
super
@@ -0,0 +1,13 @@
//-
//- Crafting Guide - inventory.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__inventory
.item_container
.empty_placeholder nothing
.buttons
.button.item-selector: .bezel: p add an item...
.button.clear: .bezel: p clear
@@ -0,0 +1,93 @@
//
// Crafting Guide - inventory.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__inventory {
display : flex;
flex-direction : column;
.empty_placeholder {
color : $color-gray-light;
font-family : $font-family-text;
font-size : $font-size-large;
margin : $size-margin-large 0;
text-align : center;
}
.item_container {
flex: 0 1 100%;
display: table;
}
.buttons {
display: none;
}
}
// Editable Variant ////////////////////////////////////////////////////////////////////////////////////////////////////
.view__inventory.editable {
.buttons {
display : flex;
flex-direction : row;
margin-top: $size-margin-large;
.item-selector {
flex : 85%;
margin-right : $size-margin-medium;
}
.clear {
flex : 15%;
}
}
}
// Large variant ///////////////////////////////////////////////////////////////////////////////////////////////////////
.view__inventory.large {
.view__stack {
& > * {
&:not(:last-child) {
padding-bottom : $size-margin-small;
}
}
.quantity {
padding-right : $size-margin-medium;
input {
width: 3em;
font-size : $font-size-xlarge;
}
}
.icon {
padding-right : $size-margin-medium;
img {
height : $size-minecraft-block * 1.5;
width : $size-minecraft-block * 1.5;
}
}
.name {
a {
font-family : $font-family-text;
font-size : $font-size-xlarge;
}
}
.action {
padding-left : $size-margin-small;
.button {
width : $size-button-height;
}
}
}
}
@@ -0,0 +1,151 @@
#
# Crafting Guide - inventory_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
ItemSelectorController = require '../item_selector/item_selector_controller'
StackController = require '../stack/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
@ONLY_DIGITS = /^[0-9]*$/
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'
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_router = options.router
@_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
options.templateName = 'common/inventory'
super options
@_stackControllers = []
@listenTo @_modPack, c.event.change, => @tryRefresh()
# Event Methods ################################################################################
onClearButtonClicked: ->
return unless @model?
return if @$clearButton.hasClass 'disabled'
@model.clear()
@trigger c.event.clear, this
@trigger c.event.change, this
onFirstButtonClicked: (stackController)->
@trigger c.event.button.first, this, stackController?.model?.itemSlug
onItemSelectorButtonClicked: ->
return unless @model?
@_selector.launch()
.then (itemSlug)=>
return unless itemSlug?
@model.add itemSlug, 1
@trigger c.event.add, this, itemSlug
@trigger c.event.change, this
onSecondButtonClicked: (stackController)->
@trigger c.event.button.second, this, stackController?.model?.itemSlug
# BaseController Overrides #####################################################################
onDidRender: ->
@_selector = @addChild ItemSelectorController, null, isAcceptable: @_isAcceptable, modPack: @_modPack
@$clearButton = @$('.button.clear')
@$emptyPlaceholder = @$('.empty_placeholder')
@$icon = @$('.icon')
@$itemContainer = @$('.item_container')
@$title = @$('h2 p')
super
refresh: ->
if @_editable
@_selector.show()
@show @$clearButton
else
@_selector.hide()
@hide @$clearButton
@$icon.attr 'src', @_icon
@_refreshStacks()
if not @model or @model.isEmpty
@show @$emptyPlaceholder
@$clearButton.addClass 'disabled'
else
@hide @$emptyPlaceholder
@$clearButton.removeClass 'disabled'
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click .button.item-selector': 'onItemSelectorButtonClicked'
'click .button.clear': 'onClearButtonClicked'
'click button.first': 'onFirstButtonClicked'
'click button.second': 'onSecondButtonClicked'
# Private Methods ##############################################################################
_refreshStacks: ->
@_stackControllers ?= []
index = 0
if @model?
@model.each (stack)=>
controller = @_stackControllers[index]
if not controller?
controller = new StackController
editable: @_editable
firstButtonType: @_firstButtonType
imageLoader: @_imageLoader
model: stack
modPack: @_modPack
router: @_router
secondButtonType: @_secondButtonType
shouldEnableButton: @_shouldEnableButton
controller.on c.event.change, => @trigger c.event.change, this
controller.on c.event.button.first, (c)=> @onFirstButtonClicked(c)
controller.on c.event.button.second, (c)=> @onSecondButtonClicked(c)
controller.render()
@_stackControllers.push controller
@$itemContainer.append controller.$el
else
controller.model = stack
index += 1
while @_stackControllers.length > index
@_stackControllers.pop().remove()
@@ -5,6 +5,6 @@
//- All rights reserved.
//-
.view__item_group.section.hideable.hidden
section.view__item_group
h2
.panel
@@ -0,0 +1,19 @@
//
// Crafting Guide - item_group.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__item_group {
.panel {
display : flex;
flex-wrap : wrap;
justify-content : space-between;
.view__item_tile {
margin-bottom: $size-margin-medium;
width: calc(33% - 8px); // $size-margin-xsmall * 2
}
}
}
@@ -1,15 +1,12 @@
###
Crafting Guide - item_group_controller.coffee
#
# Crafting Guide - item_group_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
Item = require '../models/item'
ItemController = require './item_controller'
{Duration} = require '../constants'
{Event} = require '../constants'
BaseController = require '../../base_controller'
ItemController = require './item_tile/item_tile_controller'
########################################################################################################################
@@ -18,26 +15,28 @@ module.exports = class ItemGroupController extends BaseController
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'
options.model ?= []
options.templateName = 'common/item_group'
options.title ?= ''
options.templateName = 'item_group'
options.tagName = 'section'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_title = options.title
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_router = options.router
@_title = options.title
# Property Methods #############################################################################
getTitle: ->
return @_title
setTitle: (title)->
@_title = title
@tryRefresh()
Object.defineProperties @prototype,
title: {get:@prototype.getTitle, set:@prototype.setTitle}
title:
get: -> @_title
set: (newTitle)->
@_title = newTitle
@tryRefresh()
# BaseController Overrides #####################################################################
@@ -69,7 +68,11 @@ module.exports = class ItemGroupController extends BaseController
for item in @model
controller = @_itemControllers[controllerIndex]
if not controller?
controller = new ItemController imageLoader:@_imageLoader, model:item, modPack:@_modPack
controller = new ItemController
imageLoader: @_imageLoader
model: item
modPack: @_modPack
router: @_router
@_itemControllers.push controller
@$items.append controller.$el
controller.render()
@@ -0,0 +1,11 @@
//-
//- Crafting Guide - item_tile.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- All rights reserved.
//-
.view__item_tile
a
img
p.itemName
@@ -0,0 +1,30 @@
//
// Crafting Guide - item_tile.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__item_tile {
a {
overflow: visible;
display: flex;
align-items: center;
img {
height: $size-minecraft-block;
width: $size-minecraft-block;
margin-right: $size-margin-small;
}
p {
font-family: $font-family-text;
font-size: $font-size-medium;
line-height: $size-minecraft-block;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
@@ -1,26 +1,25 @@
###
Crafting Guide - item_controller.coffee
#
# Crafting Guide - item_group_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
_ = require 'underscore'
BaseController = require '../../../base_controller'
########################################################################################################################
module.exports = class ItemController extends BaseController
module.exports = class ItemTileController extends BaseController
constructor: (options={})->
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
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.templateName = 'item'
options.templateName = 'common/item_group/item_tile'
super options
@imageLoader = options.imageLoader
@modPack = options.modPack
@_imageLoader = options.imageLoader
@_modPack = options.modPack
# BaseController Overrides #####################################################################
@@ -31,9 +30,9 @@ module.exports = class ItemController extends BaseController
super
refresh: ->
display = @modPack.findItemDisplay @model.slug
display = @_modPack.findItemDisplay @model.slug
@imageLoader.load display.iconUrl, @$icon
@_imageLoader.load display.iconUrl, @$icon
@$name.html display.itemName
@$nameLink.attr 'href', display.itemUrl
@@ -0,0 +1,11 @@
//-
//- Crafting Guide - element.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__element
img
.name
.modName
@@ -0,0 +1,62 @@
//
// Crafting Guide - element.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__element {
display : flex;
align-items: center;
background: $color-white;
padding: $size-margin-xsmall $size-margin-medium;
& > * {
margin-right: $size-margin-medium;
}
& > :last-child {
margin-right: 0;
}
img {
flex : 0 0 auto;
height : $size-minecraft-block;
width : $size-minecraft-block;
}
.name {
flex : 1 1 100%;
color : $color-text;
font-family : $font-family-text;
font-size : $font-size-large;
}
.modName {
flex : 0 0 auto;
color : $color-text-secondary;
font-family : $font-family-text;
font-size : $font-size-small;
}
&:nth-child(even) {
background : $color-gray-faint;
}
&.selected {
flex : 0 0 auto;
background : $color-active-hover;
.name {
color : $color-text-light;
}
.modName {
color : $color-text-light;
}
}
}
@@ -1,23 +1,21 @@
###
Crafting Guide - item_selector_element_controller.coffee
#
# Crafting Guide - element.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
_ = require 'underscore'
{Event} = require '../constants'
BaseController = require '../../../base_controller'
########################################################################################################################
module.exports = class ItemSelectorElementController extends BaseController
module.exports = class ElementController extends BaseController
constructor: (options={})->
if not options.model? then throw new Error 'options.model is required'
options.onClicked ?= (controller)-> # do nothing
options.onSelected ?= (controller)-> # do nothing
options.templateName = 'item_selector_element'
options.templateName = 'common/item_selector/element'
options.useAnimations = false
super options
@@ -36,8 +34,8 @@ module.exports = class ItemSelectorElementController extends BaseController
@_selected = newSelected
@tryRefresh()
@trigger Event.change + ':selected', this, oldSelected, newSelected
@trigger Event.change, this
@trigger c.event.change + ':selected', this, oldSelected, newSelected
@trigger c.event.change, this
Object.defineProperties @prototype,
selected: {get:@prototype.isSelected, set:@prototype.setSelected}
@@ -46,8 +44,8 @@ module.exports = class ItemSelectorElementController extends BaseController
onDidRender: ->
@$icon = @$('img')
@$name = @$('.name p')
@$modName = @$('.modName p')
@$name = @$('.name')
@$modName = @$('.modName')
super
refresh: ->
@@ -77,4 +75,4 @@ module.exports = class ItemSelectorElementController extends BaseController
_onMouseEnter: (event)->
event.preventDefault()
@onSelected this
@onSelected this
@@ -0,0 +1,14 @@
//-
//- Crafting Guide - item_selector.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__item_selector
.view__item_selector_popup
.search
img(src='/images/search.png')
input(placeholder='enter the name of an item')
img.close(src='/images/close.png')
.results
@@ -0,0 +1,57 @@
//
// Crafting Guide - item_selector.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__item_selector {
}
.view__item_selector_popup {
height : 66%;
background : $color-background-panel;
border : 1px solid $color-gray-medium;
display: flex;
flex-direction: column;
.search {
flex: 0 0 auto;
border-bottom: 1px solid $color-gray-medium;
display: flex;
padding: $size-margin-small;
& > :not(:first-child) {
margin-left: $size-margin-medium;
}
img {
flex: 0 0 auto;
}
input {
flex: 1 1 100%;
background: none;
border: 0;
font-family: $font-family-input;
font-size: $font-size-large;
outline: none;
}
img.close {
cursor: pointer;
}
}
.results {
flex: 1 1 100%;
overflow-x: hidden;
overflow-y: scroll;
}
}
@@ -1,18 +1,13 @@
###
Crafting Guide - item_selector_controller.coffee
#
# Crafting Guide - item_selector_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
$ = require 'jquery'
_ = require 'underscore'
BaseController = require './base_controller'
{Duration} = require '../constants'
{Event} = require '../constants'
ItemSelector = require '../models/item_selector'
ItemSelectorElementController = require './item_selector_element_controller'
{Key} = require '../constants'
BaseController = require '../../base_controller'
ItemSelector = require '../../../models/site/item_selector'
ElementController = require './element/element_controller'
########################################################################################################################
@@ -23,28 +18,63 @@ module.exports = class ItemSelectorController extends BaseController
options.isAcceptable ?= null
options.model ?= new ItemSelector {}, modPack:options.modPack, isAcceptable:options.isAcceptable
options.onChoseItem ?= (item)-> # do nothing
options.templateName = 'item_selector'
options.templateName = 'common/item_selector'
super options
@modPack = options.modPack
@onChoseItem = options.onChoseItem
@_modPack = options.modPack
@_session = null
@_elementControllers = []
# Public Methods ###############################################################################
launch: (hint='')->
if @_session
@_session.resolve null
@_session = null
@model.hint = hint
if @rendered then @refresh()
@_disableWindowScrolling()
@$page.addClass 'blur'
@$screen.append @$popup
@$screen.css 'display', ''
@$popup.off c.event.click
@$popup.on c.event.click, (event)=> @onPopupClicked(event)
@$hintField.off 'keyup input'
@$hintField.on 'keyup', (event)=> @onHintKeyPress(event)
@$hintField.on 'input', (event)=> @onHintChanged(event)
@$hintField.focus()
@$closeButton.one 'click', (event)=> @_close()
@$screen.one c.event.click, (event)=> @onScreenClicked(event)
@_session = w.defer()
return @_session.promise
# Event Methods ################################################################################
onClose: (event)->
@_close()
return false
onHintKeyPress: (event)->
switch event.which
when Key.Escape
when c.key.escape
@_close()
return false
when Key.Enter
when c.key.enter
@_chooseSelected()
return false
when Key.DownArrow
when c.key.down
@_selectNext()
return false
when Key.UpArrow
when c.key.up
@_selectPrevious()
return false
@@ -53,39 +83,13 @@ module.exports = class ItemSelectorController extends BaseController
@tryRefresh()
@onResultSelected @_elementControllers[0]
onLaunchButtonClicked: (event)->
event.preventDefault()
@model.hint = ''
@refresh()
$('body').addClass 'stop-scrolling'
@$screen.append @$popup
@$screen.css 'display', 'block'
@$popup.offset @$el.offset()
@$popup.css 'width', @$el.css 'width'
@$popup.off Event.click
@$popup.on Event.click, (event)=> @onPopupClicked(event)
@$popup.removeClass 'hiding'
@$hintField.off 'keyup input'
@$hintField.on 'keyup', (event)=> @onHintKeyPress(event)
@$hintField.on 'input', (event)=> @onHintChanged(event)
@$popup.off Event.transitionEnd
@$popup.one Event.transitionEnd, => @onPopupDisplayed()
@$screen.one Event.click, (event)=> @onScreenClicked(event)
onPopupClicked: (event)->
return false
onPopupDisplayed: ->
@$hintField.focus()
onResultClicked: (controller)->
@_session.resolve controller.model.slug
@_session = null
@_close()
@onChoseItem controller.model.slug
onResultSelected: (controller)->
for c in @_elementControllers
@@ -101,7 +105,10 @@ module.exports = class ItemSelectorController extends BaseController
# BaseController Overrides #####################################################################
onDidRender: ->
@$page = $('.page') # find the shared, global page
@$screen = $('.view__screen') # find the shared, global screen
@$closeButton = @$('img.close')
@$popup = @$('.view__item_selector_popup')
@$hintField = @$('.view__item_selector_popup input')
@$searchInput = @$('.search input')
@@ -115,17 +122,6 @@ module.exports = class ItemSelectorController extends BaseController
@_refreshResults()
super
render: ->
$buttonContent = @$el.html()
super
@$el.html $buttonContent
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click': 'onLaunchButtonClicked'
# Private Methods ##############################################################################
_chooseSelected: ->
@@ -135,24 +131,28 @@ module.exports = class ItemSelectorController extends BaseController
return
_close: ->
$('body').removeClass 'stop-scrolling'
@_enableWindowScrolling()
@$page.removeClass 'blur'
@$screen.css 'display', 'none'
@$popup.detach()
@model.hint = ''
@$popup.addClass 'hiding'
_disableWindowScrolling: ->
@_windowScrollPosition = $(window).scrollTop()
$('body').addClass('scrollDisabled').css('margin-top', -@_windowScrollPosition)
@$popup.off Event.transitionEnd
@$popup.one Event.transitionEnd, =>
@$screen.css 'display', 'none'
@$popup.detach()
@model.hint = ''
_enableWindowScrolling: ->
$('body').removeClass('scrollDisabled').css('margin-top', 0)
$(window).scrollTop @_windowScrollPosition
_refreshResults: ->
index = 0
for itemSlug in @model.results
displayModel = @modPack.findItemDisplay itemSlug
displayModel = @_modPack.findItemDisplay itemSlug
controller = @_elementControllers[index]
if not controller?
controller = new ItemSelectorElementController
controller = new ElementController
model: displayModel
onClicked: (c)=> @onResultClicked(c)
onSelected: (c)=> @onResultSelected(c)
@@ -192,4 +192,4 @@ module.exports = class ItemSelectorController extends BaseController
_showElement: ($el)->
top = $el.position().top + @$resultsContainer.scrollTop()
@$resultsContainer.animate {scrollTop:top}, duration:Duration.snap
@$resultsContainer.css 'scrollTop', top
@@ -1,7 +1,7 @@
//-
//- Crafting Guide - markdown_image.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
@@ -9,8 +9,9 @@
.loaded
.thumbnail: img
.dynamic
.button: .bezel: p
.fileName: p
.error: p
button
form: input(type="file", accept="image/gif,image/jpeg,image/png" multiple="false")
.loading: p checking for image...
.loading
p checking for image...
@@ -0,0 +1,71 @@
//
// Crafting Guide - markdown_image.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__markdown_image {
display: flex;
flex-direction: column;
.loaded {
display: flex;
.thumbnail {
flex: 0 0 auto;
height: $size-minecraft-block * 4;
width: $size-minecraft-block * 4;
margin-right: $size-margin-medium;
background: $color-gray-faint;
border: 1px solid $color-gray-light;
display: flex;
justify-content: center;
align-items: center;
img {
width: $size-minecraft-block * 3;
max-height: $size-minecraft-block * 3;
}
}
.dynamic {
flex: 1 1 100%;
& > :not(:last-child) {
margin-bottom: $size-margin-medium;
}
.fileName {
font-family: $font-family-control;
font-size: $font-size-medium;
}
.error {
color: $color-error;
font-family: $font-family-control;
font-size: $font-size-small;
}
}
form {
display: none;
}
}
.loading {
height: $size-minecraft-block * 4;
display: flex;
justify-content: center;
align-items: center;
p {
color: $color-gray-light;
font-family: $font-family-control;
font-size: $font-size-medium;
}
}
}
@@ -5,23 +5,20 @@ Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
$ = require 'jquery'
_ = require 'underscore'
BaseController = require './base_controller'
{Duration} = require '../constants'
MarkdownImage = require '../models/markdown_image'
BaseController = require '../../../../base_controller'
MarkdownImage = require '../../../../../models/site/markdown_image'
########################################################################################################################
module.exports = class MarkdownImageController extends BaseController
MAX_FILE_SIZE = 750 * 1024
MAX_HEIGHT = 600
MAX_WIDTH = 740
@::MAX_FILE_SIZE = 750 * 1024
@::MAX_HEIGHT = 600
@::MAX_WIDTH = 740
constructor: (options={})->
if not options.model? then throw new Error 'options.model is required'
options.templateName = 'markdown_image'
options.templateName = 'common/markdown_section/markdown_image_list/markdown_image'
super
@_errorMessage = null
@@ -37,9 +34,9 @@ module.exports = class MarkdownImageController extends BaseController
file = @$input.prop('files')[0]
return unless file?
if file.size > MAX_FILE_SIZE
if file.size > @MAX_FILE_SIZE
logger.warning "The choosen file, #{file.name}, is too large: #{file.size}"
@errorMessage = "Choose a file less than #{MAX_FILE_SIZE / 1024}kB"
@errorMessage = "Choose a file less than #{@MAX_FILE_SIZE / 1024}kB"
@_clearFile()
return
@@ -55,15 +52,15 @@ module.exports = class MarkdownImageController extends BaseController
$image = $("<img src=\"#{reader.result}\">")
$image.load =>
if $image[0].naturalHeight > MAX_HEIGHT
if $image[0].naturalHeight > @MAX_HEIGHT
logger.warning "The choosen file, #{file.name}, is too tall: #{$image[0].naturalHeight}"
@errorMessage = "Choose an image less than #{MAX_HEIGHT}px tall"
@errorMessage = "Choose an image less than #{@MAX_HEIGHT}px tall"
@_clearFile()
return
if $image[0].naturalWidth > MAX_WIDTH
if $image[0].naturalWidth > @MAX_WIDTH
logger.warning "The choosen file, #{file.name}, is too wide: #{$image[0].naturalWidth}"
@errorMessage = "Choose an image less than #{MAX_WIDTH}px wide"
@errorMessage = "Choose an image less than #{@MAX_WIDTH}px wide"
@_clearFile()
return
@@ -106,7 +103,7 @@ module.exports = class MarkdownImageController extends BaseController
onDidRender: ->
@$image = @$('img')
@$fileName = @$('.fileName p')
@$button = @$('button')
@$buttonLabel = @$('.button p')
@$form = @$('form')
@$input = @$('input')
@$errorContainer = @$('.error')
@@ -116,6 +113,10 @@ module.exports = class MarkdownImageController extends BaseController
super
onWillChangeModel: (oldModel, newModel)->
@errorMessage = null
super oldModel, newModel
refresh: ->
if @model.mimeType? and @model.encodedData?
@$image.attr 'src', "data:#{@model.mimeType};base64,#{@model.encodedData}"
@@ -125,25 +126,25 @@ module.exports = class MarkdownImageController extends BaseController
@$fileName.html @model.fileName
if @model.encodedData?
@$button.html 'Update'
@$buttonLabel.html 'Update'
else
@$button.html 'Choose'
@$buttonLabel.html 'Choose'
if @model.status is MarkdownImage.Status.checking
@$loaded.fadeOut duration:Duration.normal
@$loading.fadeIn duration:Duration.normal, queue:true
if @model.status is MarkdownImage::Status.checking
@hide @$loaded
@show @$loading
else
@$loading.fadeOut duration:Duration.normal
@$loaded.fadeIn duration:Duration.normal, queue:true
@hide @$loading
@show @$loaded
if @errorMessage?
@$errorMessage.html @errorMessage
@$errorContainer.fadeIn duration:Duration.normal
else if @model.status is MarkdownImage.Status.empty
@show @$errorContainer
else if @model.status is MarkdownImage::Status.empty
@$errorMessage.html 'please choose an image'
@$errorContainer.fadeIn duration:Duration.normal
@show @$errorContainer
else
@$errorContainer.fadeOut duration:Duration.normal
@hide @$errorContainer
super
@@ -151,7 +152,7 @@ module.exports = class MarkdownImageController extends BaseController
events: ->
return _.extend super,
'click button': 'onButtonClicked'
'click .button': 'onButtonClicked'
'change input': 'onFileChanged'
# Private Methods ##############################################################################
@@ -1,10 +1,10 @@
//-
//- Crafting Guide - markdown_image_list.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__markdown_image_list
h3 Images Referenced
.image_container
.image_container
@@ -0,0 +1,29 @@
//
// Crafting Guide - markdown_image_list.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__markdown_image_list {
position: relative; min-width: 33em; width: 100%;
border-bottom: 0.1em solid $color-gray-medium;
margin: 2em 0;
padding: 0 0 2em 0;
h3 {
margin-bottom: 1em;
}
.image_container {
display: flex;
flex-wrap: wrap;
margin-right: -$size-margin-large;
& > * {
width: calc(50% - 24px); // $size-margin-large
margin-right: $size-margin-large;
}
}
}
@@ -1,14 +1,13 @@
###
Crafting Guide - markdown_image_list_controller.coffee
#
# Crafting Guide - markdown_image_list_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
BaseController = require './base_controller'
{Event} = require '../constants'
MarkdownImageController = require './markdown_image_controller'
MarkdownImageList = require '../models/markdown_image_list'
BaseController = require '../../../base_controller'
MarkdownImageController = require './markdown_image/markdown_image_controller'
MarkdownImageList = require '../../../../models/site/markdown_image_list'
########################################################################################################################
@@ -16,7 +15,7 @@ module.exports = class MarkdownImageListController extends BaseController
constructor: (options={})->
options.model ?= new MarkdownImageList {}, {client:options.client}
options.templateName = 'markdown_image_list'
options.templateName = 'common/markdown_section/markdown_image_list'
super options
@imageBase = ''
@@ -43,8 +42,8 @@ module.exports = class MarkdownImageListController extends BaseController
return unless oldImageBase isnt newImageBase
@_imageBase = newImageBase
@trigger Event.change + ':imageBase', this, oldImageBase, newImageBase
@trigger Event.change, this
@trigger c.event.change + ':imageBase', this, oldImageBase, newImageBase
@trigger c.event.change, this
getMarkdownText: ->
return @model.markdownText
@@ -96,5 +95,5 @@ module.exports = class MarkdownImageListController extends BaseController
return if newValid is oldValid
@_valid = newValid
@trigger Event.change + ':valid', this, oldValid, newValid
@trigger Event.change
@trigger c.event.change + ':valid', this, oldValid, newValid
@trigger c.event.change
@@ -0,0 +1,38 @@
//-
//- Crafting Guide - markdown_section.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__markdown_section
h2
.panel
.waiting: img(src='/images/wait.gif')
.markdown
.creating
p Please help by adding a description!
.editor
.instructions.
Edit using <a href="https://help.github.com/articles/github-flavored-markdown/" target="new">GitHub
Flavored Markdown</a>. Use <tt>[[Item Name]]</tt> to link to another item and <tt>![](image.png)</tt>
to add an image.
.text
textarea
.sizer
.view__markdown_image_list
.footer
.question: a(href="javascript:void;") Confused? Click here to ask a question!
.error: p
.buttons
.button.edit: .bezel: p edit
.button.save: .bezel: p save
.button.preview: .bezel: p preview
.button.cancel: .bezel: p cancel
.button.return: .bezel: p return
.confirming
p Thanks for your help!
p Your changes will appear on the site in a few minutes.
.appologizing
p Sorry, but something went wrong.
p Please try again after a few moments.
@@ -0,0 +1,130 @@
//
// Crafting Guide - markdown_section.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__markdown_section {
display : flex;
flex-direction : column;
.waiting {
display : flex;
justify-content : center;
padding : $size-margin-xlarge;
}
.appologizing, .confirming, .creating {
display : flex;
flex-direction : column;
justify-content : center;
padding : $size-margin-xlarge;
p {
color : $color-gray-medium;
font-family : $font-family-text;
font-size : $font-size-large;
text-align : center;
&:not(:first-child) {
margin-top: $size-margin-medium;
}
}
}
.creating {
padding-bottom: $size-margin-large;
p {
color: $color-gray-light;
font-size: $font-size-xlarge;
}
}
.editor {
display : flex;
flex-direction : column;
.instructions {
margin-bottom : $size-margin-medium;
color : $color-gray-medium;
font-family : $font-family-text;
font-size : $font-size-small;
}
.text {
display : flex;
flex-direction : column;
position : relative;
textarea, .sizer {
font-family : $font-family-input;
font-size : $font-size-medium;
line-height : $font-size-medium;
overflow : hidden;
padding : $size-margin-small;
resize : none;
white-space : pre-wrap;
}
textarea {
position : absolute; width: 100%; height: 100%;
background: none;
z-index: $layer-normal;
}
.sizer {
min-height: $size-minecraft-block * 4;
opacity: 0;
z-index: $layer-normal - 1;
}
}
}
.footer {
display : flex;
align-items : flex-end;
margin-top : $size-margin-large;
.question {
flex : 1 1 100%;
a {
color : $color-gray-light;
font-family : $font-family-text;
font-size : $font-size-medium;
text-decoration : underline;
}
}
.error {
flex: 1 1 100%;
align-self: center;
p {
color : $color-error;
font-family : $font-family-text;
font-size : $font-size-medium;
font-style : italic;
text-align : center;
}
}
.buttons {
flex : 0 0 auto;
display : flex;
flex-direction : row-reverse;
align-items : center;
.button {
margin-left : $size-margin-medium;
width : $size-minecraft-block * 5;
}
}
}
}
@@ -1,24 +1,19 @@
###
Crafting Guide - markdown_section_controller.coffee
#
# Crafting Guide - markdown_section_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
_ = require 'underscore'
BaseController = require './base_controller'
BaseController = require '../../base_controller'
convertMarkdown = require 'marked'
{Duration} = require '../constants'
{Event} = require '../constants'
MarkdownImageListController = require './markdown_image_list_controller'
{Url} = require '../constants'
w = require 'when'
MarkdownImageListController = require './markdown_image_list/markdown_image_list_controller'
########################################################################################################################
module.exports = class MarkdownSectionController extends BaseController
@State = State =
@::State =
appologizing: 'applogizing'
confirming: 'confirming'
creating: 'creating'
@@ -30,19 +25,17 @@ module.exports = class MarkdownSectionController extends BaseController
constructor: (options={})->
if not options.modPack? then throw new Error 'options.modPack is required'
options.model ?= null
options.templateName = 'markdown_section'
@_state = State.waiting
options.templateName = 'common/markdown_section'
@_state = @State.waiting
super options
@on Event.animate.hide.finish, => @_updateStateVisibility()
@on Event.animate.show.finish, => @_updateStateVisibility()
@client = options.client
@confirmactionMessage = options.confirmationMessage
@confirmDuration = options.confirmationDuration ?= 5000
@imageBase = options.imageBase ?= ''
@modPack = options.modPack
@title = options.title ?= 'Description'
@_client = options.client
@_confirmationMessage = options.confirmationMessage
@_confirmDuration = options.confirmationDuration or 5000
@_enterFeedback = options.enterFeedback or -> # do nothing
@_imageBase = options.imageBase or ''
@_modPack = options.modPack
@_title = options.title or 'Description'
# @_beginEditing must be a function returning a promise which resolves when all actions necessary prior to an
# editing session have been completed (e.g., loading the latest content from a remote server). The promise must
@@ -57,12 +50,12 @@ module.exports = class MarkdownSectionController extends BaseController
resetToDefaultState: ->
if @model?
@state = State.viewing
@state = @State.viewing
else
if @editable?
@state = State.creating
@state = @State.creating
else
@state = State.waiting
@state = @State.waiting
# Event Methods ################################################################################
@@ -79,42 +72,42 @@ module.exports = class MarkdownSectionController extends BaseController
return unless @editable
@_originalModel = @model
@state = State.waiting
@state = @State.waiting
@_beginEditing()
.then =>
@state = State.editing
@state = @State.editing
.catch (e)=>
logger.warning "cannot begin editing: #{e.stack}"
@state = State.appologizing
w(true).delay(@confirmDuration).then => @resetToDefaultState()
@state = @State.appologizing
w(true).delay(@_confirmDuration).then => @resetToDefaultState()
onQuestionClicked: (event)->
event.preventDefault()
global.feedbackController.enterFeedback ''
@_enterFeedback ''
onPreviewClicked: (event)->
event.preventDefault()
@_updatePreview()
@state = State.previewing
@state = @State.previewing
onReturnClicked: (event)->
event.preventDefault()
@state = State.editing
@state = @State.editing
onSaveClicked: (event)->
event.preventDefault()
@state = State.waiting
@state = @State.waiting
@_endEditing()
.then =>
@state = State.confirming
@state = @State.confirming
.catch (e)=>
message = if e.stack? then e.stack else e
logger.error "failed to end editing: #{message}"
@state = State.appologizing
.delay @confirmDuration
@state = @State.appologizing
.delay @_confirmDuration
.then =>
@state = State.viewing
@state = @State.viewing
onTextChanged: (event)->
event.preventDefault()
@@ -133,8 +126,8 @@ module.exports = class MarkdownSectionController extends BaseController
return unless newImageBase isnt oldImageBase
@_imageBase = newImageBase
@trigger Event.change + ':imageBase', this, oldImageBase, newImageBase
@trigger Event.change, this
@trigger c.event.change + ':imageBase', this, oldImageBase, newImageBase
@trigger c.event.change, this
getImageFiles: ->
return @_imageListController.model.all
@@ -148,7 +141,7 @@ module.exports = class MarkdownSectionController extends BaseController
@_state = newState
logger.verbose => "MarkdownSectionController.#{@cid} changed state from #{oldState} to #{newState}"
@trigger Event.change + ':state', this, oldState, newState
@trigger c.event.change + ':state', this, oldState, newState
@tryRefresh()
Object.defineProperties @prototype,
@@ -160,19 +153,18 @@ module.exports = class MarkdownSectionController extends BaseController
# BaseController Overrides #####################################################################
onDidRender: ->
@_imageListController = @addChild MarkdownImageListController, '.image_list',
client: @client
imageBase: @imageBase
@on Event.change + ':imageBase', => @_imageListController.model.imageBase = @imageBase
@listenTo @_imageListController, Event.change + ':valid', => @tryRefresh()
if not global.feedbackController?
@$('.question').hide duration:0
@_imageListController = @addChild MarkdownImageListController, '.view__markdown_image_list',
client: @_client
imageBase: @_imageBase
@on c.event.change + ':imageBase', => @_imageListController.model.imageBase = @_imageBase
@listenTo @_imageListController, c.event.change + ':valid', => @tryRefresh()
@$errorPanel = @$('.error')
@$errorText = @$('.error p')
@$markdownPanel = @$('.markdown')
@$previewButton = @$('button.preview')
@$saveButton = @$('button.save')
@$previewButton = @$('.button.preview')
@$questionPanel = @$('.question')
@$saveButton = @$('.button.save')
@$sizer = @$('.sizer')
@$textarea = @$('textarea')
@$title = @$('h2')
@@ -182,9 +174,9 @@ module.exports = class MarkdownSectionController extends BaseController
super
refresh: ->
@$title.html @title
@$title.html @_title
@_imageListController.markdownText = @model
@_imageListController.fetchImages() if @state is State.editing
@_imageListController.fetchImages() if @state is @State.editing
@_updateButtonStates()
@_updateSizer()
@@ -201,14 +193,14 @@ module.exports = class MarkdownSectionController extends BaseController
events: ->
return _.extend super,
'click .markdown a': 'routeLinkClick'
'click button.cancel': 'onCancelClicked'
'click button.edit': 'onEditClicked'
'click button.preview': 'onPreviewClicked'
'click button.return': 'onReturnClicked'
'click button.save': 'onSaveClicked'
'click .question a': 'onQuestionClicked'
'input textarea': 'onTextChanged'
'click .markdown a': 'routeLinkClick'
'click .button.cancel': 'onCancelClicked'
'click .button.edit': 'onEditClicked'
'click .button.preview': 'onPreviewClicked'
'click .button.return': 'onReturnClicked'
'click .button.save': 'onSaveClicked'
'click .question a': 'onQuestionClicked'
'input textarea': 'onTextChanged'
# Private Methods ##############################################################################
@@ -219,9 +211,9 @@ module.exports = class MarkdownSectionController extends BaseController
_convertWikiLinks: (text)->
text.replace /\[\[([^\]]*)\]\]/g, (match, name)=>
result = match
item = @modPack.findItemByName name
item = @_modPack.findItemByName name
if item?
display = @modPack.findItemDisplay item.slug
display = @_modPack.findItemDisplay item.slug
result = "[#{name}](#{display.itemUrl})"
return result
@@ -257,53 +249,50 @@ module.exports = class MarkdownSectionController extends BaseController
elements =
appologizingPanel: @$('.appologizing')
buttonPanel: @$('.buttons')
cancelButton: @$('button.cancel')
cancelButton: @$('.button.cancel')
confirmingPanel: @$('.confirming')
creatingPanel: @$('.creating')
editButton: @$('button.edit')
editButton: @$('.button.edit')
editorPanel: @$('.editor')
errorPanel: @$('.error')
imageList: @$('.image_list')
footerPanel: @$('.footer')
imageList: @$('.view__markdown_image_list')
markdownPanel: @$markdownPanel
previewButton: @$previewButton
returnButton: @$('button.return')
returnButton: @$('.button.return')
saveButton: @$saveButton
waitingPanel: @$('.waiting')
visible = {}
errorText = ''
if @state is State.appologizing
if @state is @State.appologizing
visible = appologizingPanel:true
else if @state is State.confirming
else if @state is @State.confirming
visible = confirmingPanel:true
else if @state is State.creating
visible = buttonPanel:true, creatingPanel:true, editButton:true
else if @state is State.editing
visible = buttonPanel:true, cancelButton:true, editorPanel:true, previewButton:true, imageList:true
else if @state is State.previewing
visible = buttonPanel:true, errorPanel:true, markdownPanel:true, returnButton:true, saveButton:true
else if @state is @State.creating
visible = footerPanel:true, creatingPanel:true, editButton:true
else if @state is @State.editing
visible = footerPanel:true, editorPanel:true, cancelButton:true, previewButton:true, imageList:true
else if @state is @State.previewing
visible = footerPanel:true, markdownPanel:true, returnButton:true, saveButton:true
errorText = "remember: your changes aren't saved yet!"
else if @state is State.viewing
else if @state is @State.viewing
visible = markdownPanel:true
if @editable then _.extend visible, {buttonPanel:true, editButton:true}
if @editable then _.extend visible, {footerPanel:true, editButton:true}
else # assume any unknown state is the same as "waiting"
visible = waitingPanel:true
toHide = ($el for name, $el of elements when not visible[name])
toShow = ($el for name, $el of elements when visible[name])
for name, $el of elements when not visible[name]
@hide $el
for name, $el of elements when visible[name]
@show $el
$el.clearQueue() for name, $el of elements
if toHide.length > 0
$el.fadeOut(duration:Duration.normal) for $el in toHide
$el.delay(Duration.normal) for $el in toShow
$el.fadeIn(duration:Duration.normal) for $el in toShow
if @state is State.editing
@once Event.animate.show.finish, =>
@$textarea.focus()
if @state is @State.editing then @$textarea.focus()
@$errorText.html errorText
if errorText.length > 0
@show @$errorPanel
@hide @$questionPanel
else
@hide @$errorPanel
@show @$questionPanel

Some files were not shown because too many files have changed in this diff Show More