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
+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;
}
}
}
@@ -0,0 +1,80 @@
#
# Crafting Guide - adsense_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
########################################################################################################################
module.exports = class AdsenseController extends BaseController
constructor: (options={})->
options.templateName = 'common/adsense'
super options
@_adsEnabled = global.env in c.productionEnvs
@_adCount = 0
@_waiting = false
# Public Methods ###############################################################################
fillAdPositions: ->
if @_adCount > 0
return unless @$sidebar and @$mainBody
return unless @_adCount < @_computeMaxAds()
return unless @_adCount < c.adsense.slotIds.length
$adContainer = $('<div class="skyscraper"></div>')
@$el.append $adContainer
if @_adsEnabled
$ad = $('<ins class="adsbygoogle"></ins>')
$ad.attr 'data-ad-client', c.adsense.clientId
$ad.attr 'data-ad-slot', c.adsense.slotIds[@_adCount]
$adContainer.append $ad
@_loadAds()
@_adCount += 1
_.defer => @fillAdPositions()
# BaseController Overrides #####################################################################
onDidRender: ->
@$mainBody = $('.page > .content > .right')
@$sidebar = $('.page > .content > .left')
super
refresh: ->
@_waitForPageReadiness()
super
# 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
logger.info "Loading #{@_adCount} Google AdSense ads"
try
global.adsbygoogle ||= []
global.adsbygoogle.push {}
catch e
logger.warning "Could not load Adsense ad: #{e}"
_waitForPageReadiness: ->
return if @_waiting
if @$sidebar.height() < c.adsense[@model].height / 2
logger.verbose "Adsense is waiting for room to insert ads"
@_waiting = true
_.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()
@@ -0,0 +1,10 @@
//-
//- Crafting Guide - item_group.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- All rights reserved.
//-
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
}
}
}
@@ -0,0 +1,84 @@
#
# Crafting Guide - item_group_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
ItemController = require './item_tile/item_tile_controller'
########################################################################################################################
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.tagName = 'section'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_router = options.router
@_title = options.title
# Property Methods #############################################################################
Object.defineProperties @prototype,
title:
get: -> @_title
set: (newTitle)->
@_title = newTitle
@tryRefresh()
# BaseController Overrides #####################################################################
onDidRender: ->
@$title = @$('h2')
@$items = @$('.panel')
super
onWillChangeModel: (oldModel, newModel)->
newModel ?= []
return super oldModel, newModel
refresh: ->
if @model.length > 0
@$title.html @_title
@_refreshItems()
@show()
else
@hide()
super
# Private Methods ##############################################################################
_refreshItems: ->
@_itemControllers ?= []
controllerIndex = 0
for item in @model
controller = @_itemControllers[controllerIndex]
if not controller?
controller = new ItemController
imageLoader: @_imageLoader
model: item
modPack: @_modPack
router: @_router
@_itemControllers.push controller
@$items.append controller.$el
controller.render()
else
controller.model = item
controllerIndex += 1
while @_itemControllers.length > controllerIndex
@_itemControllers.pop().remove()
@@ -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;
}
}
}
@@ -0,0 +1,43 @@
#
# Crafting Guide - item_group_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../../base_controller'
########################################################################################################################
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 = 'common/item_group/item_tile'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
# BaseController Overrides #####################################################################
onDidRender: ->
@$icon = @$('img')
@$name = @$('.itemName')
@$nameLink = @$('a')
super
refresh: ->
display = @_modPack.findItemDisplay @model.slug
@_imageLoader.load display.iconUrl, @$icon
@$name.html display.itemName
@$nameLink.attr 'href', display.itemUrl
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click a': 'routeLinkClick'
@@ -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;
}
}
}
@@ -0,0 +1,78 @@
#
# Crafting Guide - element.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../../base_controller'
########################################################################################################################
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 = 'common/item_selector/element'
options.useAnimations = false
super options
@onClicked = options.onClicked
@onSelected = options.onSelected
# Property Methods #############################################################################
isSelected: ->
return @_selected
setSelected: (newSelected)->
oldSelected = @_selected
return if newSelected is oldSelected
@_selected = newSelected
@tryRefresh()
@trigger c.event.change + ':selected', this, oldSelected, newSelected
@trigger c.event.change, this
Object.defineProperties @prototype,
selected: {get:@prototype.isSelected, set:@prototype.setSelected}
# BaseController Overrides #####################################################################
onDidRender: ->
@$icon = @$('img')
@$name = @$('.name')
@$modName = @$('.modName')
super
refresh: ->
@$icon.attr 'src', @model.iconUrl
@$name.html @model.itemName
@$modName.html @model.modName
if @_selected
@$el.addClass 'selected'
else
@$el.removeClass 'selected'
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click': '_onClick'
'mouseenter': '_onMouseEnter'
# Private Methods ##############################################################################
_onClick: (event)->
event.preventDefault()
@onClicked this
_onMouseEnter: (event)->
event.preventDefault()
@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;
}
}
@@ -0,0 +1,195 @@
#
# Crafting Guide - item_selector_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
ItemSelector = require '../../../models/site/item_selector'
ElementController = require './element/element_controller'
########################################################################################################################
module.exports = class ItemSelectorController extends BaseController
constructor: (options)->
if not options.modPack? then throw new Error 'options.modPack is required'
options.isAcceptable ?= null
options.model ?= new ItemSelector {}, modPack:options.modPack, isAcceptable:options.isAcceptable
options.onChoseItem ?= (item)-> # do nothing
options.templateName = 'common/item_selector'
super options
@_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 c.key.escape
@_close()
return false
when c.key.enter
@_chooseSelected()
return false
when c.key.down
@_selectNext()
return false
when c.key.up
@_selectPrevious()
return false
onHintChanged: (event)->
@model.hint = @$hintField.val()
@tryRefresh()
@onResultSelected @_elementControllers[0]
onPopupClicked: (event)->
return false
onResultClicked: (controller)->
@_session.resolve controller.model.slug
@_session = null
@_close()
onResultSelected: (controller)->
for c in @_elementControllers
c.selected = false
if controller?
controller.selected = true
onScreenClicked: (event)->
@_close()
return false
# 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')
@$resultsContainer = @$('.results')
@$popup.detach()
super
refresh: ->
@$hintField.val @model.hint
@_refreshResults()
super
# Private Methods ##############################################################################
_chooseSelected: ->
for controller in @_elementControllers
if controller.selected
@onResultClicked controller
return
_close: ->
@_enableWindowScrolling()
@$page.removeClass 'blur'
@$screen.css 'display', 'none'
@$popup.detach()
@model.hint = ''
_disableWindowScrolling: ->
@_windowScrollPosition = $(window).scrollTop()
$('body').addClass('scrollDisabled').css('margin-top', -@_windowScrollPosition)
_enableWindowScrolling: ->
$('body').removeClass('scrollDisabled').css('margin-top', 0)
$(window).scrollTop @_windowScrollPosition
_refreshResults: ->
index = 0
for itemSlug in @model.results
displayModel = @_modPack.findItemDisplay itemSlug
controller = @_elementControllers[index]
if not controller?
controller = new ElementController
model: displayModel
onClicked: (c)=> @onResultClicked(c)
onSelected: (c)=> @onResultSelected(c)
controller.render show:false
@_elementControllers[index] = controller
@$resultsContainer.append controller.$el
else
controller.model = displayModel
controller.show()
index += 1
while @_elementControllers.length > index
@_elementControllers.pop().remove()
_selectNext: ->
for i in [0...(@_elementControllers.length - 1)] by 1
controller = @_elementControllers[i]
nextController = @_elementControllers[i + 1]
if controller.selected
controller.selected = false
nextController.selected = true
@_showElement nextController.$el
return
_selectPrevious: ->
for i in [1...@_elementControllers.length] by 1
previousController = @_elementControllers[i - 1]
controller = @_elementControllers[i]
if controller.selected
previousController.selected = true
controller.selected = false
@_showElement previousController.$el
return
_showElement: ($el)->
top = $el.position().top + @$resultsContainer.scrollTop()
@$resultsContainer.css 'scrollTop', top
@@ -0,0 +1,17 @@
//-
//- Crafting Guide - markdown_image.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__markdown_image
.loaded
.thumbnail: img
.dynamic
.button: .bezel: p
.fileName: p
.error: p
form: input(type="file", accept="image/gif,image/jpeg,image/png" multiple="false")
.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;
}
}
}
@@ -0,0 +1,161 @@
###
Crafting Guide - markdown_image_controller.coffee
Copyright (c) 2015 by Redwood Labs
All rights reserved.
###
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
constructor: (options={})->
if not options.model? then throw new Error 'options.model is required'
options.templateName = 'common/markdown_section/markdown_image_list/markdown_image'
super
@_errorMessage = null
# Event Methods ################################################################################
onButtonClicked: ->
@$input.click()
onFileChanged: ->
if @_reader? then @_reader.abort = true
file = @$input.prop('files')[0]
return unless file?
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"
@_clearFile()
return
reader = @_reader = new FileReader
reader.abort = false
reader.onload = =>
if reader.abort
logger.info "aborted reading #{file.name}"
return
@_reader = null
$image = $("<img src=\"#{reader.result}\">")
$image.load =>
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"
@_clearFile()
return
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"
@_clearFile()
return
index = reader.result.indexOf ','
meta = reader.result.substring 0, index
encodedData = reader.result.substring index + 1
match = meta.match /data:(.*);(.*)/
mimeType = match[1]
encoding = match[2]
@model.encodedData = encodedData
logger.info "loaded #{encodedData.length} bytes from #{file.name} as #{mimeType}"
@errorMessage = null
@_clearFile()
logger.info "starting to read local file: #{file.name}"
reader.readAsDataURL file
# Property Methods #############################################################################
getErrorMessage: ->
return @_errorMessage
setErrorMessage: (newErrorMessage)->
oldErrorMessage = @_errorMessage
return if newErrorMessage is oldErrorMessage
@_errorMessage = newErrorMessage
@trigger Event.change + ':errorMessage', this, oldErrorMessage, newErrorMessage
@trigger Event.change, this
@tryRefresh()
Object.defineProperties @prototype,
errorMessage: { get:@prototype.getErrorMessage, set:@prototype.setErrorMessage }
# BaseController Overrides #####################################################################
onDidRender: ->
@$image = @$('img')
@$fileName = @$('.fileName p')
@$buttonLabel = @$('.button p')
@$form = @$('form')
@$input = @$('input')
@$errorContainer = @$('.error')
@$errorMessage = @$('.error p')
@$loaded = @$('.loaded')
@$loading = @$('.loading')
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}"
else
@$image.attr 'src', '/images/unknown.png'
@$fileName.html @model.fileName
if @model.encodedData?
@$buttonLabel.html 'Update'
else
@$buttonLabel.html 'Choose'
if @model.status is MarkdownImage::Status.checking
@hide @$loaded
@show @$loading
else
@hide @$loading
@show @$loaded
if @errorMessage?
@$errorMessage.html @errorMessage
@show @$errorContainer
else if @model.status is MarkdownImage::Status.empty
@$errorMessage.html 'please choose an image'
@show @$errorContainer
else
@hide @$errorContainer
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click .button': 'onButtonClicked'
'change input': 'onFileChanged'
# Private Methods ##############################################################################
_clearFile: ->
@$form[0].reset()
@@ -0,0 +1,10 @@
//-
//- Crafting Guide - markdown_image_list.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__markdown_image_list
h3 Images Referenced
.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;
}
}
}
@@ -0,0 +1,99 @@
#
# Crafting Guide - markdown_image_list_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../../base_controller'
MarkdownImageController = require './markdown_image/markdown_image_controller'
MarkdownImageList = require '../../../../models/site/markdown_image_list'
########################################################################################################################
module.exports = class MarkdownImageListController extends BaseController
constructor: (options={})->
options.model ?= new MarkdownImageList {}, {client:options.client}
options.templateName = 'common/markdown_section/markdown_image_list'
super options
@imageBase = ''
@_valid = null
# Public Methods ###############################################################################
fetchImages: ->
@model.fetchImages()
getImageUrlForFile: (fileName)->
@model.getFile(fileName).imageUrl
reset: ->
@model.reset()
# Property Methods #############################################################################
getImageBase: ->
return @_imageBase
setImageBase: (newImageBase)->
oldImageBase = @_imageBase
return unless oldImageBase isnt newImageBase
@_imageBase = newImageBase
@trigger c.event.change + ':imageBase', this, oldImageBase, newImageBase
@trigger c.event.change, this
getMarkdownText: ->
return @model.markdownText
setMarkdownText: (markdownText)->
@model.markdownText = markdownText
getValid: ->
return @_valid
Object.defineProperties @prototype,
imageBase: {get:@prototype.getImageBase, set:@prototype.setImageBase}
markdownText: {get:@prototype.getMarkdownText, set:@prototype.setMarkdownText}
valid: {get:@prototype.getValid}
# BaseController Overrides #####################################################################
onDidModelChange: ->
super
@_setValid @model.valid
onDidRender: ->
@$imageContainer = @$('.image_container')
super
refresh: ->
@_controllers ?= []
index = 0
for image in @model.all
controller = @_controllers[index]
if not controller?
controller = new MarkdownImageController model:image
@_controllers.push controller
@$imageContainer.append controller.$el
controller.render()
else
controller.model = image
index += 1
while @_controllers.length > index
@_controllers.pop().remove()
# Private Methods ##############################################################################
_setValid: (newValid)->
oldValid = @_valid
return if newValid is oldValid
@_valid = newValid
@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;
}
}
}
}
@@ -0,0 +1,298 @@
#
# Crafting Guide - markdown_section_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
convertMarkdown = require 'marked'
MarkdownImageListController = require './markdown_image_list/markdown_image_list_controller'
########################################################################################################################
module.exports = class MarkdownSectionController extends BaseController
@::State =
appologizing: 'applogizing'
confirming: 'confirming'
creating: 'creating'
editing: 'editing'
previewing: 'previewing'
viewing: 'viewing'
waiting: 'waiting'
constructor: (options={})->
if not options.modPack? then throw new Error 'options.modPack is required'
options.model ?= null
options.templateName = 'common/markdown_section'
@_state = @State.waiting
super options
@_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
# reject if an error occured which should prevent editing.
@_beginEditing = options.beginEditing
# @_endEditing must be a function returning a promise which resolves when the model of this controller has been
# saved however is appropriate. The promise must reject if saving failed for some reason.
@_endEditing = options.endEditing
# Public Methods ###############################################################################
resetToDefaultState: ->
if @model?
@state = @State.viewing
else
if @editable?
@state = @State.creating
else
@state = @State.waiting
# Event Methods ################################################################################
onCancelClicked: (event)->
event.preventDefault()
@model = @_originalModel
@resetToDefaultState()
@_imageListController.reset()
@_updatePreview()
onEditClicked: (event)->
event.preventDefault()
return unless @editable
@_originalModel = @model
@state = @State.waiting
@_beginEditing()
.then =>
@state = @State.editing
.catch (e)=>
logger.warning "cannot begin editing: #{e.stack}"
@state = @State.appologizing
w(true).delay(@_confirmDuration).then => @resetToDefaultState()
onQuestionClicked: (event)->
event.preventDefault()
@_enterFeedback ''
onPreviewClicked: (event)->
event.preventDefault()
@_updatePreview()
@state = @State.previewing
onReturnClicked: (event)->
event.preventDefault()
@state = @State.editing
onSaveClicked: (event)->
event.preventDefault()
@state = @State.waiting
@_endEditing()
.then =>
@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
.then =>
@state = @State.viewing
onTextChanged: (event)->
event.preventDefault()
@model = @$textarea.val()
# Property Methods #############################################################################
isEditable: ->
return _.isFunction(@_beginEditing) and _.isFunction(@_endEditing)
getImageBase: ->
return @_imageBase
setImageBase: (newImageBase)->
oldImageBase = @_imageBase
return unless newImageBase isnt oldImageBase
@_imageBase = newImageBase
@trigger c.event.change + ':imageBase', this, oldImageBase, newImageBase
@trigger c.event.change, this
getImageFiles: ->
return @_imageListController.model.all
getState: ->
return @_state
setState: (newState)->
oldState = @_state
return if oldState is newState
@_state = newState
logger.verbose => "MarkdownSectionController.#{@cid} changed state from #{oldState} to #{newState}"
@trigger c.event.change + ':state', this, oldState, newState
@tryRefresh()
Object.defineProperties @prototype,
editable: { get:@prototype.isEditable }
imageBase: { get:@prototype.getImageBase, set:@prototype.setImageBase }
imageFiles: { get:@prototype.getImageFiles }
state: { get:@prototype.getState, set:@prototype.setState }
# BaseController Overrides #####################################################################
onDidRender: ->
@_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')
@$questionPanel = @$('.question')
@$saveButton = @$('.button.save')
@$sizer = @$('.sizer')
@$textarea = @$('textarea')
@$title = @$('h2')
@resetToDefaultState()
super
refresh: ->
@$title.html @_title
@_imageListController.markdownText = @model
@_imageListController.fetchImages() if @state is @State.editing
@_updateButtonStates()
@_updateSizer()
@_updatePreview()
@_updateStateVisibility()
super
onWillChangeModel: (oldModel, newModel)->
if @rendered then @$textarea.val newModel
super oldModel, newModel
# Backbone.View Overrides ######################################################################
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'
# Private Methods ##############################################################################
_convertImageLinks: (text)->
text.replace /\<img src="([^"]*)"/g, (match, fileName)=>
return "<img src=\"#{@_imageListController.getImageUrlForFile(fileName)}\""
_convertWikiLinks: (text)->
text.replace /\[\[([^\]]*)\]\]/g, (match, name)=>
result = match
item = @_modPack.findItemByName name
if item?
display = @_modPack.findItemDisplay item.slug
result = "[#{name}](#{display.itemUrl})"
return result
_updateButtonStates: ->
for $button in [ @$previewButton, @$saveButton ]
if @_imageListController.valid
$button.prop 'disabled', false
else
$button.prop 'disabled', true
_updateSizer: ->
text = @model
if @model?
text = text.replace /\n/g, '<br>'
@$sizer.html text
_updatePreview: ->
text = @model
if @model?
text = @_convertWikiLinks text
text = convertMarkdown text
text = @_convertImageLinks text
@$markdownPanel.html text
_updateStateVisibility: ->
return if @_lastUpdatedState is @state
@_lastUpdatedState = @state
logger.verbose => "Updating markdown section visibility for state: #{@state}"
elements =
appologizingPanel: @$('.appologizing')
cancelButton: @$('.button.cancel')
confirmingPanel: @$('.confirming')
creatingPanel: @$('.creating')
editButton: @$('.button.edit')
editorPanel: @$('.editor')
footerPanel: @$('.footer')
imageList: @$('.view__markdown_image_list')
markdownPanel: @$markdownPanel
previewButton: @$previewButton
returnButton: @$('.button.return')
saveButton: @$saveButton
waitingPanel: @$('.waiting')
visible = {}
errorText = ''
if @state is @State.appologizing
visible = appologizingPanel:true
else if @state is @State.confirming
visible = confirmingPanel: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
visible = markdownPanel:true
if @editable then _.extend visible, {footerPanel:true, editButton:true}
else # assume any unknown state is the same as "waiting"
visible = waitingPanel:true
for name, $el of elements when not visible[name]
@hide $el
for name, $el of elements when visible[name]
@show $el
if @state is @State.editing then @$textarea.focus()
@$errorText.html errorText
if errorText.length > 0
@show @$errorPanel
@hide @$questionPanel
else
@hide @$errorPanel
@show @$questionPanel
+14
View File
@@ -0,0 +1,14 @@
//-
//- Crafting Guide - recipe.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- All rights reserved.
//-
.view__recipe
.input
.view__crafting_grid
.tool
.output
.view__slot
.multiplier
+45
View File
@@ -0,0 +1,45 @@
//
// Crafting Guide - recipe.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__recipe {
display: flex;
.input {
flex: 1 1 50%;
align-items : center;
display : flex;
flex-direction : column;
.tool {
margin-top : $size-margin-small;
font-family : $font-family-text;
font-size : $font-size-medium;
}
}
.output {
flex: 1 1 50%;
margin-left: $size-margin-medium;
display : flex;
flex-direction : column;
justify-content : center;
align-items : center;
.view__slot {
border: $size-border-medium solid $color-gray-medium;
}
.multiplier {
font-family : $font-family-header;
font-size : $font-size-large;
margin-top : $size-margin-medium;
}
}
}
@@ -0,0 +1,97 @@
#
# Crafting Guide - recipe_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
CraftingGridController = require '../crafting_grid/crafting_grid_controller'
SlotController = require '../slot/slot_controller'
{StringBuilder} = require 'crafting-guide-common'
########################################################################################################################
module.exports = class RecipeController 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.templateName = 'common/recipe'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_multiplier = options.multiplier
@_router = options.router
# BaseController Overrides #####################################################################
onDidRender: ->
@_gridController = @addChild CraftingGridController, '.view__crafting_grid',
modPack: @_modPack
imageLoader: @_imageLoader
router: @_router
@_outputSlotController = @addChild SlotController, '.output .view__slot',
imageLoader: @_imageLoader
modPack: @_modPack
router: @_router
@$multiplier = @$('.multiplier')
@$outputImg = @$('.output img')
@$outputLink = @$('.output a')
@$outputQuantity = @$('.quantity')
@$toolContainer = @$('.tool')
super
refresh: ->
@_gridController.model = @model
@_outputSlotController.model = @model?.output?[0]
@_refreshMultiplier()
@_refreshTools()
super
# Property Methods #############################################################################
Object.defineProperties @prototype,
multiplier:
get: ->
@_multiplier ?= 1
return @_multiplier
set: (newMultiplier)->
oldMultiplier = @_multiplier
return if newMultiplier is oldMultiplier
@_multiplier = newMultiplier
@_refreshMultiplier()
@trigger Event.change + ':multiplier', this, oldMultiplier, newMultiplier
@trigger Event.change, this
# Backbone.View Methods ########################################################################
events: ->
return _.extend super,
'click a': 'routeLinkClick'
# Private Methods ##############################################################################
_refreshMultiplier: ->
if @multiplier > 1
@$multiplier.html "x#{@multiplier}"
else
@$multiplier.html ''
_refreshTools: ->
@$toolContainer.empty()
return unless @model?
builder = new StringBuilder
builder.loop @model.tools, delimiter:', ', onEach:(b, stack)=>
display = @_modPack.findItemDisplay stack.itemSlug
b.push "<a href=\"#{display.itemUrl}\">#{display.itemName}</a>"
@$toolContainer.html builder.toString()
+11
View File
@@ -0,0 +1,11 @@
//-
//- Crafting Guide - slot.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- All rights reserved.
//-
.view__slot
a
img(src='/images/empty.png')
.quantity
+30
View File
@@ -0,0 +1,30 @@
//
// Crafting Guide - slot.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__slot {
position : relative;
height : ($size-minecraft-block * 1.5) + ($size-margin-xsmall * 2);
width : ($size-minecraft-block * 1.5) + ($size-margin-xsmall * 2);
display : flex;
align-items : center;
justify-content : center;
padding : $size-margin-xsmall;
img {
height: $size-minecraft-block * 1.5;
width: $size-minecraft-block * 1.5;
}
.quantity {
position : absolute; right: 2px; bottom: 0;
color : white;
font-family : $font-family-header;
font-size : $font-size-large;
text-shadow : -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000, 2px 2px 0 #000;
}
}
@@ -0,0 +1,58 @@
#
# Crafting Guide - slot_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
########################################################################################################################
module.exports = class SlotController 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 Stack
options.templateName = 'common/slot'
options.useAnimations = false
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_router = options.router
# BaseController Overrides #####################################################################
onDidRender: ->
@$link = @$('a')
@$image = @$('img')
@$quantity = @$('.quantity')
super
refresh: ->
if @model?
display = @_modPack.findItemDisplay @model.itemSlug
@$link.attr 'href', display.itemUrl
@_imageLoader.load display.iconUrl, @$image
if @model.quantity > 1
@$quantity.html @model.quantity
else
@$quantity.html ''
else
@$link.removeAttr 'href'
@$quantity.html ''
@$image.attr 'src', '/images/empty.png'
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click a': 'routeLinkClick'
+15
View File
@@ -0,0 +1,15 @@
//-
//- Crafting Guide - stack.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__stack
.quantity: input
.icon: img(src='')
.name: a
.action.first(style="display: none")
.button: .bezel: p &nbsp;
.action.second(style="display: none")
.button: .bezel: p &nbsp;
+86
View File
@@ -0,0 +1,86 @@
//
// Crafting Guide - stack.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__stack {
// using table-row display type to allow the quantity to always line up, even when the length is different
display : table-row;
max-width: 100%;
& > * {
display : table-cell;
vertical-align : middle;
&:not(:last-child) {
padding-bottom : $size-margin-xsmall;
}
}
.quantity {
padding-right : $size-margin-medium;
text-align : right;
input {
width: 4em;
background : none;
border : none;
font-family : $font-family-text;
font-size : $font-size-large;
text-align : right;
}
}
.icon {
padding-right : $size-margin-small;
img {
height : $size-minecraft-block;
width : $size-minecraft-block;
}
}
.name {
padding-right : $size-margin-large;
position: relative;
width: 100%;
a {
// use absolute positioning to pull the name out of the layout so that a long name won't cause the row to
// be too long. Instead, this element will truncate itself with an ellipsis.
position: absolute; top: 50%; width: 100%;
transform: translateY(-50%);
font-family : $font-family-text;
font-size : $font-size-medium;
line-height : $size-minecraft-block;
overflow : hidden;
text-overflow : ellipsis;
white-space : nowrap;
}
}
.action {
padding-left : $size-margin-small;
.button {
width : $size-button-height;
}
}
}
// Editable ////////////////////////////////////////////////////////////////////////////////////////////////////////////
.view__stack.editable {
.quantity {
input {
width : 3em;
border-bottom : 1px dashed $color-gray-dark;
font-family : $font-family-input;
}
}
}
@@ -0,0 +1,180 @@
#
# Crafting Guide - stack_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
########################################################################################################################
###
Events:
change(this)
change:quantity(this, oldQuantity, newQuantity)
button:first(this, type)
button:second(this, type)
###
module.exports = class StackController extends BaseController
@::MAX_QUANTITY = 9999
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'
if not options.router? then throw new Error 'options.router is required'
options.templateName = 'common/stack'
super options
@_editable = options.editable ?= false
@_firstButtonType = options.firstButtonType ?= null
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_secondButtonType = options.secondButtonType ?= null
@_shouldEnableButton = options.shouldEnableButton ?= (model, button)-> true
@_modPack.on c.event.change, => @tryRefresh()
# Event Methods ################################################################################
onFirstButtonClicked: (event)->
@trigger c.event.button.first, this, @_firstButtonType
return false
onQuantityFieldBlur: ->
return unless @_editable
oldQuantity = @_priorValue
quantityText = @$quantityField.val().trim()
if quantityText.length is 0
newQuantity = oldQuantity
else if not quantityText.match /^[0-9]*$/
newQuantity = 1
else
newQuantity = parseInt quantityText, 10
if _.isNaN newQuantity then newQuantity = 1
newQuantity = Math.min newQuantity, @MAX_QUANTITY
newQuantity = Math.max 1, newQuantity
@$quantityField.val "#{newQuantity}"
if newQuantity? and newQuantity isnt oldQuantity
@$quantityField.removeClass 'error'
@$quantityField.removeClass 'error-new'
@model.quantity = newQuantity
@trigger c.event.change + ':quantity', this, oldQuantity, newQuantity
@trigger c.event.change, this
onQuantityFieldChanged: ->
return unless @_editable
quantityText = @$quantityField.val().trim()
if not quantityText.match /^[0-9]*$/
@$quantityField.addClass 'error', 0
@$quantityField.addClass 'error-new', 0
@$quantityField.removeClass 'error-new'
else
@$quantityField.removeClass 'error'
@$quantityField.removeClass 'error-new'
onQuantityFieldFocused: ->
if @_editable
@$quantityField.addClass 'editing'
@_priorValue = @model.quantity
@$quantityField.val ''
else
@$quantityField.blur()
onQuantityKeyUp: (event)->
return unless @_editable
if event.which is c.key.return
@$quantityField.blur()
onSecondButtonClicked: (event)->
event.preventDefault()
@trigger c.event.button.second, this, @_secondButtonType
# BaseController Overrides #####################################################################
onDidRender: ->
@$firstAction = @$('.action.first')
@$firstButton = @$('.action.first .button')
@$image = @$('.icon img')
@$nameLink = @$('.name a')
@$quantityField = @$('.quantity input')
@$secondAction = @$('.action.second')
@$secondButton = @$('.action.second .button')
super
refresh: ->
if not @model? then throw new Error "must have a model to render"
display = @_modPack.findItemDisplay @model.itemSlug
@_imageLoader.load display.iconUrl, @$image
@$nameLink.html display.itemName
@$nameLink.attr 'href', display.itemUrl
quantityText = if @model.quantity > 10000 then "#{@model.quantity / 1000}k" else "#{@model.quantity}"
@$quantityField.val quantityText
@_refreshButtons()
if @_editable
@$el.addClass 'editable'
@$quantityField.css 'width', ''
@$quantityField.removeAttr 'readonly'
else
@$el.removeClass 'editable'
@$quantityField.css 'width', "#{quantityText}".length * 0.66 + "em"
@$quantityField.attr 'readonly', 'readonly'
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'blur .quantity input': 'onQuantityFieldBlur'
'click .name a': 'routeLinkClick'
'click .action.first .button': 'onFirstButtonClicked'
'click .action.second .button': 'onSecondButtonClicked'
'focus .quantity input': 'onQuantityFieldFocused'
'input .quantity input': 'onQuantityFieldChanged'
'keyup .quantity input': 'onQuantityKeyUp'
# Private Methods ##############################################################################
_refreshButtons: ->
@_updateButtonType @$firstButton, @_firstButtonType
@_updateButtonType @$secondButton, @_secondButtonType
actions = [@$firstAction, @$secondAction]
buttons = [@$firstButton, @$secondButton]
for buttonType, index in [@_firstButtonType, @_secondButtonType]
display = if @_shouldEnableButton(@model, index) then '' else 'none'
display = if not buttonType? then 'none' else display
actions[index].css 'display', display
$label = buttons[index].find 'p'
switch buttonType
when 'check' then $label.text ''
when 'down' then $label.text '⬇︎'
when 'remove' then $label.text '-'
when 'up' then $label.text '⬆︎'
_updateButtonType: ($button, type)->
for currentType in ['check', 'down', 'remove', 'up']
$button.removeClass currentType
if type?
$button.css 'display', null
$button.addClass type
else
$button.css 'display', 'none'
+10
View File
@@ -0,0 +1,10 @@
//-
//- Crafting Guide - video.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__video
iframe(width="356", height="267", src="", frameborder="0", allowfullscreen="true")
.caption: p
+20
View File
@@ -0,0 +1,20 @@
//
// Crafting Guide - video.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__video {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.caption {
margin-top: $size-margin-small;
font-family: $font-family-control;
font-size: $font-size-medium;
}
}
@@ -0,0 +1,34 @@
#
# Crafting Guide - video_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
########################################################################################################################
module.exports = class VideoController extends BaseController
constructor: (options={})->
if not options.model? then throw new Error 'options.model is required'
options.templateName = 'common/video'
super options
# BaseController Overrides #####################################################################
onDidRender: ->
@$iframe = @$('iframe')
@$caption = @$('.caption p')
super
refresh: ->
@$caption.html @model.name
@$iframe.attr 'src', @_createYouTubeUrl()
super
# Private ######################################################################################
_createYouTubeUrl: ->
return "http://www.youtube.com/embed/#{@model.youTubeId}?modestbranding=1&autohide=1&showinfo=0"
@@ -0,0 +1,63 @@
//-
//- Crafting Guide - craft_page.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
- var quarryHref = '/craft/quarry'
- var quarry
- var quantumSuitHref = '/craft/quantumsuit_bodyarmor:quantumsuit_boots:quantumsuit_helmet:quantumsuit_leggings'
- var ic2Href = '/browse/industrial_craft_2'
- var solarPanelHref = '/craft/solar_panel_vi'
- var solarFluxHref = '/browse/solar_flux'
.content.view__craft_page
.left
.view__adsense
.right
section.instructions
h2 Instructions
.panel
p.
Crafting Guide provides step-by-step instructions for thousands of items from Minecraft and many
popular mods. Just click "Add an item..." down below.
p.
Not sure what you'd like to make? Try one of these:
.items
a(href="/craft/potion_of_strength_ii_1_30")
img(src="/data/minecraft/items/potion_of_strength_ii_1_30/icon.png")
a(href="/craft/quarry")
img(src="/data/buildcraft/items/quarry/icon.png")
a(href="/craft/quantumsuit_bodyarmor:quantumsuit_boots:quantumsuit_helmet:quantumsuit_leggings")
img(src="/data/ic2_classic/items/quantumsuit_helmet/icon.png")
a(href="/craft/solar_panel_vi")
img(src="/data/solar_flux/items/solar_panel_vi/icon.png")
a(href="/craft/crystal_chest")
img(src="/data/iron_chests/items/crystal_chest/icon.png")
a(href="/craft/digital_miner")
img(src="/data/mekanism/items/digital_miner/icon.png")
a(href="/craft/2.tesseract")
img(src="/data/thermal_expansion/items/tesseract/icon.png")
section.want
h2 Items to Make
.panel
.view__inventory.large.editable
section.have
h2 Already in Inventory
.panel
.view__inventory.large.editable
section.view__craftsman_working
section.need
h2 Need to Gather
.panel
.view__inventory.large
section.steps
h2 Steps
.panel
@@ -0,0 +1,52 @@
//
// Crafting Guide - craft_page.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__craft_page {
@include layout-body-with-sidebar;
.instructions {
.panel {
padding : $size-margin-large $size-margin-xlarge;
p {
margin-bottom : $size-margin-large;
font-family : $font-family-text;
font-size : $font-size-large;
line-height : $font-size-large * 1.5;
}
.items {
display : flex;
flex-wrap : wrap;
justify-content : space-around;
margin-top : $size-margin-medium;
margin-right : -$size-margin-medium;
a {
flex : 0 0 auto;
margin-top : $size-margin-medium;
margin-right : $size-margin-medium;
background : $color-gray-faint;
border : 1px solid $color-gray-light;
padding : $size-margin-medium;
img {
height : $size-minecraft-block * 1.5;
width : $size-minecraft-block * 1.5;
}
&:hover {
background: $color-gray-light;
border-color: $color-gray-medium;
}
}
}
}
}
}
@@ -0,0 +1,211 @@
#
# Crafting Guide - craft_page_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
AdsenseController = require '../common/adsense/adsense_controller'
BaseController = require '../base_controller'
CraftPage = require '../../models/site/craft_page'
Craftsman = require '../../models/crafting/craftsman'
CraftsmanWorkingController = require './craftsman_working/craftsman_working_controller'
InventoryController = require '../common/inventory/inventory_controller'
PageController = require '../page_controller'
StepController = require './step/step_controller'
########################################################################################################################
module.exports = class CraftPageController extends PageController
constructor: (options={})->
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
if not options.modPack? then throw new Error 'options.modPack is required'
if not options.router? then throw new Error 'options.router is required'
if not options.storage? then throw new Error 'options.storage is required'
options.model ?= new CraftPage modPack:options.modPack
options.templateName = 'craft_page'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_router = options.router
@_storage = options.storage
# c.event Methods ################################################################################
onHaveInventoryChanged: ->
logger.warning => "storing have: #{@model.craftsman.have.unparse()}"
@_storage.store 'crafting-plan:have', @model.craftsman.have.unparse()
onMoveNeedToHave: (itemSlug)->
quantity = @_needInventoryController.model.quantityOf itemSlug
@model.craftsman.have.add itemSlug, quantity
onRemoveFromHaveInventory: (itemSlug)->
@model.craftsman.have.remove itemSlug
onRemoveFromWant: (itemSlug)->
@model.craftsman.want.remove itemSlug
@onWantInventoryChanged()
onWantInventoryChanged: ->
text = @model.craftsman.want.unparse()
url = c.url.crafting inventoryText:text
@router.navigate url
if @model.craftsman.want.isEmpty
@model.craftsman.have.clear()
# PageController Overrides #####################################################################
getMetaDescription: ->
return c.text.craftDescription()
getTitle: ->
return 'Craft'
# BaseController Overrides #####################################################################
onDidRender: ->
@_adsenseController = @addChild AdsenseController, '.view__adsense', model:'skyscraper'
@_wantInventoryController = @addChild InventoryController, '.want .view__inventory',
firstButtonType: 'remove'
imageLoader: @_imageLoader
isAcceptable: (item)=> item.isCraftable
modPack: @_modPack
model: @model.craftsman.want
router: @_router
@_wantInventoryController.on c.event.button.first, (c, s)=> @onRemoveFromWant(s)
@_wantInventoryController.on c.event.change, (c)=> @onWantInventoryChanged()
@_haveInventoryController = @addChild InventoryController, '.have .view__inventory',
firstButtonType: 'down'
imageLoader: @_imageLoader
modPack: @_modPack
model: @model.craftsman.have
router: @_router
@_haveInventoryController.on c.event.button.first, (controller, itemSlug)=>
@onRemoveFromHaveInventory itemSlug
@_haveInventoryController.on c.event.change, (c)=> @onHaveInventoryChanged()
@_needInventoryController = @addChild InventoryController, '.need .view__inventory',
editable: false
firstButtonType: 'up'
imageLoader: @_imageLoader
modPack: @_modPack
model: null
router: @_router
@_needInventoryController.on c.event.button.first, (c, s)=> @onMoveNeedToHave(s)
@_workingSectionController = @addChild CraftsmanWorkingController, '.view__craftsman_working',
model: @model.craftsman
@$haveSection = @$('section.have')
@$instructionsSection = @$('section.instructions')
@$makeSection = @$('section.make')
@$needSection = @$('section.need')
@$stepsContainer = @$('section.steps .panel')
@$stepsSection = @$('section.steps')
@$toolsSection = @$('section.tools')
@$workingSection = @$('.view__craftsman_working')
super
onWillRender: ->
@model.craftsman.have.clear()
@model.craftsman.have.parse @_storage.load('crafting-plan:have')
super
refresh: ->
@_needInventoryController.model = @model.craftsman.plan?.need
@_refreshSectionVisibility()
@_refreshSteps()
@_adsenseController.fillAdPositions()
super
# Backbone.View Overrides ########################################################################
events: ->
return _.extend super,
'click .instructions a': 'routeLinkClick'
# Private Methods ################################################################################
_addTools: (controller)->
controller.model.addToolsTo @model.craftsman.want
_completeStep: (controller)->
controller.model.completeInto @model.craftsman.have
_isAddingToolsPossible: (controller)->
tools = controller.model.recipe.tools
return false unless tools.length > 0
have = @model.craftsman.have
want = @model.craftsman.want
for stack in tools
return false if have.hasAtLeast stack.itemSlug
return false if want.hasAtLeast stack.itemSlug
return true
_isStepCompletable: (controller)->
recipe = controller.model.recipe
for stack in recipe.output
return false if @model.craftsman.want.hasAtLeast stack.itemSlug
return true
_refreshSectionVisibility: ->
return unless @_rendered
allSections = [
@$instructionsSection, @$haveSection, @$needSection, @$stepsSection, @$toolsSection, @$workingSection
]
visibleSections = []
if @model.craftsman.want.isEmpty
visibleSections.push el for el in [@$instructionsSection, @$wantSection]
else if @model.craftsman.stage is Craftsman::STAGE.INVALID
visibleSections.push el for el in [@$wantSection, @$workingSection]
else if not @model.craftsman.complete
visibleSections.push el for el in [@$wantSection, @$workingSection]
else
visibleSections.push el for el in [@$haveSection, @$needSection, @$stepsSection, @$wantSection]
@hide $el for $el in allSections
@show $el for $el in visibleSections
_refreshSteps: ->
steps = @model.craftsman.plan?.steps or []
@_stepControllers ?= []
index = 0
for step in steps
controller = @_stepControllers[index]
if not controller?
controller = new StepController
canAddTools: (controller)=> @_isAddingToolsPossible controller
canComplete: (controller)=> @_isStepCompletable controller
onComplete: (controller)=> @_completeStep controller
onAddTools: (controller)=> @_addTools controller
imageLoader: @_imageLoader
model: step
modPack: @_modPack
router: @_router
controller.render()
@$stepsContainer.append controller.$el
@_stepControllers.push controller
else
controller.model = step
index += 1
while @_stepControllers.length > index
@_stepControllers.pop().remove()
@@ -0,0 +1,15 @@
//-
//- Crafting Guide - craftsman_working.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__item_group.section
.content
.waiting
img(src='/images/wait.gif')
.message
p
.count
p
@@ -0,0 +1,38 @@
/*
Crafting Table - crafting_table.scss
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
*/
.view__craftsman_working {
.content {
background: $color-white;
box-shadow: none;
align-items : center;
display : flex;
flex-direction : column;
padding : $size-margin-xlarge;
.waiting {
margin-bottom: $size-margin-xlarge;
}
.message {
margin-bottom: $size-margin-medium;
p {
font-family: $font-family-header;
font-size: $font-size-large;
}
}
.count {
p {
font-family: $font-family-header;
font-size: $font-size-medium;
}
}
}
}
@@ -0,0 +1,59 @@
#
# Crafting Guide - craftsman_working_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
Craftsman = require '../../../models/crafting/craftsman'
########################################################################################################################
module.exports = class CraftsmanWorkingController extends BaseController
constructor: (options={})->
if not options.model then throw new Error 'options.model is required'
options.templateName = 'craft_page/craftsman_working'
super options
@model.on c.event.change, => @_refreshStatusText()
# BaseController Methods #######################################################################
onDidRender: ->
@$message = @$('.message p')
@$count = @$('.count p')
@$waiting = @$('.waiting')
super
refresh: ->
@_refreshStatusText()
super
# Private Methods ##############################################################################
_refreshStatusText: ->
return unless @$message? and @$count?
@show @$waiting
switch @model.stage
when Craftsman::STAGE.WAITING
@$message.html 'Preparing crafting calculation...'
@$count.html ''
when Craftsman::STAGE.GRAPHING
@$message.html 'Researching recipes...'
@$count.html "Found #{@model.stageCount} recipes so far..."
when Craftsman::STAGE.PLANNING
@$message.html 'Figuring out possible crafting plans...'
@$count.html "Found #{@model.stageCount} possibilities so far..."
when Craftsman::STAGE.ANALYZING
@$message.html 'Looking for the best plan...'
@$count.html "Finished checking #{@model.stageCount} so far..."
when Craftsman::STAGE.COMPLETE
@$message.html 'All done!'
@$count.html ''
when Craftsman::STAGE.INVALID
@$message.html 'Couldn\'t make a crafting plan'
@$count.html ''
@hide @waiting
+15
View File
@@ -0,0 +1,15 @@
//-
//- Crafting Guide - step.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__step
h3
.main_content
.view__inventory
.view__recipe
.buttons
.button.complete: .bezel: p complete step
.button.tool: .bezel: p make this tool
+56
View File
@@ -0,0 +1,56 @@
//
// Crafting Guide - step.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__step {
display: flex;
flex-direction: column;
&:not(:first-child) {
border-top: 1px solid $color-gray-light;
margin-top: $size-margin-large;
padding-top: $size-margin-small;
}
&:hover {
& > .buttons {
opacity: 1.0;
}
}
h3 {
margin-bottom: 1em;
}
.main_content {
display: flex;
.view__inventory {
flex: 50%;
margin-left: $size-margin-large;
}
.view__recipe {
flex: 50%;
}
}
.buttons {
margin-top: $size-margin-large;
opacity: 0;
display: flex;
.button {
flex: 1 1 50%;
&:not(:first-child) {
margin-left: $size-margin-medium;
}
}
}
}
@@ -0,0 +1,100 @@
#
# Crafting Guide - step_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
InventoryController = require '../../common/inventory/inventory_controller'
RecipeController = require '../../common/recipe/recipe_controller'
########################################################################################################################
module.exports = class StepController extends BaseController
constructor: (options={})->
if not options.model? then throw new Error 'options.model is required'
if not options.modPack? then throw new Error 'options.modPack is required'
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
options.templateName = 'craft_page/step'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_router = options.router
@canAddTools = options.canAddTools or (controller)-> true
@canComplete = options.canComplete or (controller)-> true
@onComplete = options.onComplete or (controller)-> # do nothing
@onAddTools = options.onAddTools or (controller)-> # do nothing
# Event Methods ################################################################################
onCompleteButtonClicked: (event)->
return if @$completeButton.hasClass 'disabled'
@onComplete this
onToolButtonClicked: (event)->
return if @$toolButton.hasClass 'disabled'
@onAddTools this
# BaseController Overrides #####################################################################
onDidRender: ->
@inventoryController = @addChild InventoryController, '.view__inventory',
editable: false
imageLoader: @_imageLoader
model: @model.inventory
modPack: @_modPack
router: @_router
@recipeController = @addChild RecipeController, '.view__recipe',
imageLoader: @_imageLoader
model: @model.recipe
modPack: @_modPack
router: @_router
@$completeButton = @$('.button.complete')
@$header = @$('h3')
@$toolButton = @$('.button.tool')
@$toolButtonLabel = @$('.button.tool p')
super
onWillChangeModel: (oldModel, newModel)->
if not newModel? then throw new Error 'model cannot be null'
return super
refresh: ->
itemDisplay = @_modPack.findItemDisplay @model.recipe.output[0].itemSlug
@$header.html "#{@model.number}. #{itemDisplay.itemName}"
@inventoryController.model = @model.inventory
@recipeController.model = @model.recipe
@recipeController.multiplier = @model.multiplier
@_refreshCompleteButton()
@_refreshToolButton()
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click .button.complete': 'onCompleteButtonClicked'
'click .button.tool': 'onToolButtonClicked'
# Private Methods ##############################################################################
_refreshToolButton: ->
if @canAddTools this
@$toolButton.removeClass 'disabled'
else
@$toolButton.addClass 'disabled'
_refreshCompleteButton: ->
if @canComplete this
@$completeButton.removeClass 'disabled'
else
@$completeButton.addClass 'disabled'
+19
View File
@@ -0,0 +1,19 @@
//-
//- Crafting Guide - feedback.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__feedback
.form-content
input(name="name", placeholder="name (optional)")
input(name="email", placeholder="email (optional)")
label(name="comment") Comment:
textarea(name="comment")
.error: p Sending failed. Please try again later.
.button.send: .bezel: p send
.tab
p Feedback
+88
View File
@@ -0,0 +1,88 @@
//
// Crafting Guide - feedback.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__feedback {
min-height: $size-minecraft-block * 10;
z-index: $layer-dialog;
background: white;
box-shadow: $size-margin-xxsmall $size-margin-xxsmall $size-margin-xsmall $color-shadow;
opacity: 0;
display: flex;
.form-content {
flex: 1 1 100%;
border: $size-border-medium solid $color-black;
border-left: 0;
border-right: 0;
display: flex;
flex-direction: column;
padding: $size-margin-medium;
& > :not(:last-child) {
flex: 0 0 auto;
margin-bottom: $size-margin-medium;
}
input {
display: block;
font-family: $font-family-input;
font-size: $font-size-medium;
}
label {
margin-top: $size-margin-small;
margin-bottom: $size-margin-small !important;
font-family: $font-family-control;
font-size: $font-size-medium;
}
textarea {
height: $font-size-medium * 10;
flex: 1 1 100%;
border: $size-border-small solid $color-gray-light;
font-family: $font-family-input;
font-size: $font-size-medium;
resize: none;
}
.error {
color: $color-error;
font-family: $font-family-control;
font-size: $font-size-small;
text-align: center;
}
}
.tab {
$label-size: 25px;
flex: 0 0 auto;
width: $label-size + 2 * $size-margin-medium;
background: $color-gray-light;
border: $size-border-medium solid $color-black;
border-left: 0;
display: flex;
align-items: center;
justify-content: center;
p {
transform: rotate(-90deg);
font-family: $font-family-header;
font-size: $label-size;
}
}
}
@@ -0,0 +1,112 @@
#
# Crafting Guide - feedback_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../base_controller'
EmailClient = require '../../models/site/email_client'
########################################################################################################################
module.exports = class FeedbackController extends BaseController
constructor: (options={})->
options.model ?= new EmailClient
options.templateName = 'feedback'
super options
Object.defineProperty this, 'isOpen', get:-> @$el.offset().left is 0
# Public Methods ###############################################################################
enterFeedback: (message)->
promise = w(true)
if not @isOpen then promise = @onToggle()
promise.then => @$commentField.val message
# Event Methods ################################################################################
onSendClicked: ->
return unless @$commentField.val().length > 0
@$sendButton.addClass 'disabled'
message =
url: window.location.href
name: @$nameField.val()
email: @$emailField.val()
comment: @$commentField.val()
@model.send('feedback', message)
.then =>
@onToggle()
@$error.css display: 'none'
.catch (error)=>
@$error.css display: ''
.then =>
@$sendButton.removeClass 'disabled'
onTextChanged: (event)->
if @$commentField.val().length > 0
@$sendButton.removeClass 'disabled'
else
@$sendButton.addClass 'disabled'
onToggle: ->
w.promise (resolve)=>
@$screen.off 'click'
if @isOpen
@$screen.css display:'none'
@$page.removeClass 'blur'
@$el.animate {left:-@_closeWidth}, duration:c.duration.fast, complete:=>
@$commentField.val ''
@$('input, textarea').blur()
resolve()
else
@$page.addClass 'blur'
@$screen.on 'click', => @onToggle()
@$screen.css display:'block'
@$el.animate {left:0}, duration:c.duration.fast, complete:=>
if @$nameField.val().length is 0
@$nameField.focus()
else if @$emailField.val().length is 0
@$emailField.focus()
else
@$commentField.focus()
resolve()
# BaseController Overrides #####################################################################
onDidRender: ->
@$page = $('.page')
@$screen = $('.view__screen')
@$commentField = @$('textarea[name="comment"]')
@$emailField = @$('input[name="email"]')
@$error = @$('.error')
@$nameField = @$('input[name="name"]')
@$sendButton = @$('.button.send')
@$el.css left:-@$el.outerWidth() * 1.25, opacity: 1.0;
@_closeWidth = @$el.outerWidth() - @$('.tab').outerWidth()
@$el.delay(c.duration.slow).animate {left:-@_closeWidth}, c.duration.normal
@onTextChanged()
@$error.css display:'none'
@$sendButton.addClass 'disabled'
super
# Backbone.View Methods ########################################################################
events: ->
return _.extend super,
'click .tab': 'onToggle'
'click .button': 'onSendClicked'
'input textarea': 'onTextChanged'
'keyup textarea': 'onTextChanged'
+42
View File
@@ -0,0 +1,42 @@
//-
//- Crafting Guide - footer.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__footer
.divider.top
.content
.left
h2 About
p.
Crafting Guide gives step-by-step instructions for making anything in Minecraft or its many mods. Just
say what you'd like to make, what you already have, it will do the rest, giving you a list of raw
materials and instructions of which items to make in the proper order. You can even ask it to include
the materials and instructions for all the tools you'll need along the way!
.center
h2 Donate
p.
Crafting Guide is free for all, but if you find it helpful, donations in any amount are gratefully
accepted.
.action
form(action="https://www.paypal.com/cgi-bin/webscr", class="centered", method="post", target="_top")
input(type="hidden", name="cmd", value="_s-xclick")
input(type="hidden", name="hosted_button_id", value="GCB2TYZJYLAE6")
input(type="image", src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif", border="0",
name="submit" alt="PayPal - The safer, easier way to pay online!")
img(src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif", width="1", height="1")
.right
h2 Get Involved
p.
Crafting Guide is completely open-source, and you can help! Whether you want to write a recipe book
(all simple JSON), or implement new features, just head over to GitHub to get started.
.action
iframe(src="http://ghbtns.com/github-btn.html?user=andrewminer&repo=crafting-guide&type=fork&size=large",
allowtransparency="true", frameborder="0", scrolling="0", width="100", height="32")
.divider.bottom
+57
View File
@@ -0,0 +1,57 @@
//
// Crafting Guide - footer.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__footer {
display: flex;
flex-direction: column;
background: $color-background-footer;
.divider.top {
background: $color-dirt url('/images/grass_side.png');
height: $size-minecraft-block;
}
> .content {
display: flex;
color: $color-text-light;
.left, .center, .right {
flex: 1 1 33%;
margin: $size-margin-large $size-margin-large $size-margin-large 0;
display: flex;
flex-direction: column;
p {
font-family : $font-family-text;
font-size : $font-size-medium;
line-height : $font-size-medium * 1.25;
text-align : justify;
}
.action {
flex: 1 1 auto;
align-self: center;
margin-bottom: $size-margin-large;
display: flex;
align-items: flex-end;
}
&:first-child {
margin-left: $size-margin-large;
}
}
}
.divider.bottom {
background: $color-bedrock url('/images/bedrock.png');
height: $size-minecraft-block;
}
}
@@ -0,0 +1,16 @@
#
# Crafting Guide - footer_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../base_controller'
########################################################################################################################
module.exports = class FooterController extends BaseController
constructor: (options={})->
options.templateName = 'footer'
super options
+24
View File
@@ -0,0 +1,24 @@
//-
//- Crafting Guide - header.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__header
.top-row
a.logo(href="/")
img(src="/images/site-icon.png")
h1 Minecraft Crafting Guide
.addthis_sharing_toolbox
.divider
.button.craft: .bezel: p Craft
.button.browse: .bezel: p Browse
.button.news: .bezel: p News
.button.search: .bezel: p Search
.button.login: .bezel: p Login
.breadcrumbs
+97
View File
@@ -0,0 +1,97 @@
//
// Crafting Guide - header.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__header {
background: $color-gray-light;
display : flex;
flex-direction : column;
.top-row {
display : flex;
align-items : center;
a {
flex : 0 0 auto;
height : $size-minecraft-block * 1.5;
width : $size-minecraft-block * 1.5;
margin-right : $size-margin-medium;
img {
position : relative;
top : -$size-minecraft-block * 0.5;
left : -$size-minecraft-block * 0.5;
height : $size-minecraft-block * 2.0;
width : $size-minecraft-block * 2.0;
}
}
h1 {
flex: 1 1 auto;
}
.addthis_sharing_toolbox {
flex : 0 0 auto;
min-height : 37px;
min-width : 144px;
margin-right : $size-margin-small;
&.placeholder {
border : 1px solid orange;
text-align : center;
&:before {
color : orange;
content : "ADD THIS";
font-family : $font-family-header;
font-size : $font-size-large;
line-height : 37px;
}
}
}
}
.divider {
background : $color-planks url('/images/planks.png');
align-items : center;
display : flex;
flex-wrap : wrap;
justify-content : space-around;
padding : $size-margin-medium $size-margin-medium;
padding-top : 0;
.button {
flex : 1 1 20%;
min-width : 4 * $size-minecraft-block;
max-width : 5 * $size-minecraft-block;
margin-top: $size-margin-medium;
}
}
.breadcrumbs {
background: $color-screen;
font-family: $font-family-control;
font-size: $font-size-medium;
padding: $size-margin-small $size-margin-medium;
&:before {
content: "";
margin-right: $size-margin-medium;
}
a {
text-decoration: underline;
}
span {
margin: 0 $size-margin-medium;
}
}
}
@@ -0,0 +1,114 @@
#
# Crafting Guide - header_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../base_controller'
ItemSelectorController = require '../common/item_selector/item_selector_controller'
########################################################################################################################
module.exports = class HeaderController extends BaseController
constructor: (options={})->
if not options.modPack? then throw new Error 'options.modPack is required'
options.model ?= {}
options.templateName = 'header'
super options
@_modPack = options.modPack
global.site.on c.event.change + ':user', => @tryRefresh()
# Event Methods ################################################################################
onCraft: ->
@router.navigate '/craft', trigger:true
return false
onBrowse: ->
@router.navigate '/browse', trigger:true
return false
onLogin: ->
if global.site.user?
global.site.logout()
else
@router.navigate '/login', trigger:true
return false
onNews: ->
@router.navigate '/news', trigger:true
return false
onSearch: ->
@_selector.launch()
.then (itemSlug)=>
itemDisplay = @_modPack.findItemDisplay itemSlug
@router.navigate itemDisplay.itemUrl, trigger:true
return false
# BaseController Overrides #####################################################################
onDidRender: ->
@_selector = @addChild ItemSelectorController, null, modPack: @_modPack
if global.env in c.productionEnvs
addThisUrl = 'http://s7.addthis.com/js/300/addthis_widget.js?pubid=ra-54f3c9717b2e530d'
$('body').append "<script async=\"async\" src=\"#{addThisUrl}\"></script>"
else
@$('.addthis_sharing_toolbox').addClass 'placeholder'
@$breadcrumbs = @$('.breadcrumbs')
@$loginButtonLabel = @$('.button.login p')
@$title = $('title')
super
onWillChangeModel: (oldModel, newModel)->
if oldModel? then @stopListening oldModel.controller
@listenTo newModel.controller, c.event.change, => @tryRefresh()
return newModel
refresh: ->
@$loginButtonLabel.text if global.site.user? then "Logout" else "Login"
@_refreshBreadcrumbs()
@_refreshTitle()
super
# Backbone.View Overrides ######################################################################
events: ->
_.extend super,
'click .craft': 'onCraft'
'click .browse': 'onBrowse'
'click .login': 'onLogin'
'click .news': 'onNews'
'click .search': 'onSearch'
'click .breadcrumbs a': 'routeLinkClick'
# Private Methods ##############################################################################
_refreshBreadcrumbs: ->
breadcrumbs = @model?.controller?.getBreadcrumbs()
breadcrumbs ?= []
if breadcrumbs.length > 0
@$breadcrumbs.empty()
for crumb, index in breadcrumbs
if index isnt 0 then @$breadcrumbs.append $("<span>⟩</span>")
@$breadcrumbs.append crumb
@show @$breadcrumbs
else
@hide @$breadcrumbs
_refreshTitle: ->
title = @model?.controller?.getTitle()
if title
@$title.text c.text.titleWithText text: title
else
@$title.text c.text.title()
+103
View File
@@ -0,0 +1,103 @@
#
# Crafting Guide - image_loader.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
module.exports = class ImageLoader
constructor: (options={})->
options.defaultUrl ?= null
if not options.defaultUrl?
options.onLoading ?= -> @hide()
options.onLoad ?= -> @show()
@defaultUrl = options.defaultUrl
@onLoading = options.onLoading
@onLoad = options.onLoad
@_images = {}
if @defaultUrl
@_defaultImage = new Image
@_defaultImage.src = @defaultUrl
# Class Methods ################################################################################
@load: (imageUrl, $el, options={}) ->
loader = new ImageLoader options
loader.load $el, options
return loader
# Public Methods ###############################################################################
isLoaded: (imageUrl)->
data = @_images[imageUrl]
return false unless data?
return data.isLoaded
load: (imageUrl, $el)->
return if not $el
data = @preload imageUrl
if $el.attr('src')?
return if $el.attr('src').indexOf(imageUrl) isnt -1
if @onLoading? then @onLoading.call $el
$el.data 'isLoading', true
$el.data 'isLoaded', false
if data.isLoaded
@_loadImageIntoElement data.imageUrl, $el
else
if @defaultUrl?
$el.attr 'src', @defaultUrl
else
$el.removeAttr 'src'
if data.elements.indexOf($el) is -1 then data.elements.push $el
return this
preload: (imageUrl)->
data = @_images[imageUrl]
if not data?
data = imageUrl:imageUrl, elements:[], image:new Image, isLoaded:false
@_images[imageUrl] = data
data.image = new Image
data.image.onload = => @_onImageLoaded data
data.image.onerror = => @_onImageError data
data.image.src = imageUrl
return data
# Private Methods ##############################################################################
_onImageLoaded: (data)->
for $el in data.elements
@_loadImageIntoElement data.imageUrl, $el
data.elements = []
data.isLoaded = true
_onImageError: (data)->
if @defaultUrl?
data.imageUrl = @defaultUrl
for $el in data.elements
@_loadImageIntoElement @defaultUrl, $el
data.elements = []
data.isLoaded = true
_loadImageIntoElement: (imageUrl, $el)->
$el.data 'isLoading', false
$el.data 'isLoaded', true
$el.attr 'src', imageUrl
if @onLoad? then @onLoad.call $el
+35
View File
@@ -0,0 +1,35 @@
//-
//- Crafting Guide - index.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
doctype html
html
head
title The Ultimate Minecraft Crafting Guide
link(href="/images/favicon.png" rel="icon" type="image/png")
link(href="/main.css" rel="stylesheet" type="text/css" )
meta(charset="UTF-8")
meta(name="viewport" content="width=480")
body
.view__feedback
.view__screen(style="display: none")
.page
.view__header
.content(style="display: none")
.content-loading
img(src="/images/wait.gif")
.view__footer
script(src="/external.js")
script(src="/internal.js")
include ../includes/_google_adsense.jade
include ../includes/_google_analytics.jade
+69
View File
@@ -0,0 +1,69 @@
//
// Crafting Guide - index.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
html {
font-size : $size-rem;
body {
background : $color-background-window url('/images/ice.png');
width : 100%;
z-index : $layer-normal;
display : flex;
justify-content : center;
.view__feedback {
position: fixed; top: 50%; left: 0; width: $size-minecraft-block * 10;
transform: translateY(-50%);
}
.view__screen {
position : fixed; top: 0; right: 0; bottom: 0; left: 0;
z-index : $layer-screen;
background: $color-screen;
display: flex;
align-items: center;
justify-content: center;
& > :not(:first-child) {
display: none;
}
& > *:first-child {
flex: 0 0 50%;
box-shadow : $size-margin-small $size-margin-small $size-margin-large $color-shadow;
}
}
.page {
margin : $size-margin-large 0;
max-width : $size-page-width;
width : 100%;
background : $color-background-page;
box-shadow : $size-margin-small $size-margin-small $size-margin-large $color-shadow;
display : flex;
flex-direction : column;
> .content {
padding : $size-margin-medium;
}
> .content-loading {
margin : $size-margin-xlarge 0;
display : flex;
align-items : center;
justify-content : center;
}
}
}
}
+44
View File
@@ -0,0 +1,44 @@
//-
//- Crafting Guide - item_page.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.content.view__item_page
.left
.view__adsense
.right
.about
.left
img
.center
.title
a.sourceMod
.button-row
.button.craftingPlan: .bezel: p View Crafting Plan
.right
p Official Link:
a.officialLink project page
section.description.view__markdown_section
section.multiblock
h2 Multiblock Construction
.panel
.view__multiblock_viewer
section.recipes
h2 Recipes
.panel
section.videos
h2 Videos
.panel
section.view__item_group.usedToMake
section.view__item_group.usedAsToolToMake
section.view__item_group.similar
+60
View File
@@ -0,0 +1,60 @@
//
// Crafting Guide - item_page.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__item_page {
@include layout-body-with-sidebar;
.about {
margin-bottom: $size-margin-large;
display: flex;
& > .left {
margin-right: $size-margin-large;
flex: 0 0 auto;
img {
height: $size-minecraft-block * 5;
width: $size-minecraft-block * 5;
}
}
& > .center {
flex: 1 1 100%;
display: flex;
flex-direction: column;
.title {
font-family: $font-family-header;
font-size: $font-size-xxlarge;
}
a.sourceMod {
font-family: $font-family-text;
font-size: $font-size-medium;
}
.button-row {
flex: 1 1 100%;
display: flex;
justify-content: center;
align-items: flex-end;
.button {
flex: 1 1 100%;
max-width: 66%;
}
}
}
& > .right {
flex: 0 0 auto;
}
}
}
@@ -0,0 +1,325 @@
#
# Crafting Guide - item_page_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
AdsenseController = require '../common/adsense/adsense_controller'
EditableFile = require '../../models/site/editable_file'
Item = require '../../models/game/item'
ItemGroupController = require '../common/item_group/item_group_controller'
ItemPage = require '../../models/site/item_page'
ItemSlug = require '../../models/game/item_slug'
MarkdownSectionController = require '../common/markdown_section/markdown_section_controller'
MultiblockViewerController = require './multiblock_viewer/multiblock_viewer_controller'
PageController = require '../page_controller'
RecipeDetailController = require './recipe_detail/recipe_detail_controller'
VideoController = require '../common/video/video_controller'
########################################################################################################################
module.exports = class ItemPageController extends PageController
@::FILE_UPLOAD_DELAY = 250
constructor: (options={})->
if not options.client? then throw new Error 'options.client is required'
if not options.itemSlug? then throw new Error 'options.itemSlug is required'
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 ?= new ItemPage modPack:options.modPack
options.templateName ?= 'item_page'
super options
@_client = options.client
@_descriptionFile = null
@_enterFeedback = options.enterFeedback
@_imageLoader = options.imageLoader
@_itemSlug = options.itemSlug
@_modPack = options.modPack
@_router = options.router
@_modPack.on c.event.change, =>
@tryRefresh()
@trigger c.event.change
# Event Methods ################################################################################
craftingPlanButtonClicked: ->
display = @_modPack.findItemDisplay @model.item.slug
@_router.navigate display.craftingUrl, trigger:true
return false
# PageController Overrides #####################################################################
getBreadcrumbs: ->
return [] unless @_itemSlug?
display = @_modPack.findItemDisplay @_itemSlug
return [] unless display.itemName? and display.modName
return [
$("<a href='/browse'>Browse</a>")
$("<a href='#{c.url.mod modSlug:display.modSlug}'>#{display.modName}</a>")
$("<b>#{display.itemName}</b>")
]
getMetaDescription: ->
return null unless @_itemSlug?
display = @_modPack.findItemDisplay @_itemSlug
return c.text.itemDescription display
getTitle: ->
return null unless @_itemSlug?
display = @_modPack.findItemDisplay @_itemSlug
return null unless display.itemName? and display.modName?
return "#{display.itemName} from #{display.modName}"
# BaseController Overrides #####################################################################
onDidRender: ->
@_adsenseController = @addChild AdsenseController, '.view__adsense', model:'skyscraper', router: @_router
options = imageLoader:@_imageLoader, modPack:@_modPack, router:@_router, show:false
@_multiblockController = @addChild MultiblockViewerController, '.view__multiblock_viewer', options
@_similarItemsController = @addChild ItemGroupController, '.view__item_group.similar', options
@_usedAsToolToMakeController = @addChild ItemGroupController, '.view__item_group.usedAsToolToMake ', options
@_usedToMakeController = @addChild ItemGroupController, '.view__item_group.usedToMake', options
@_descriptionController = @addChild MarkdownSectionController, '.view__markdown_section',
client: @_client
editable: true
modPack: @_modPack
router: @_router
beginEditing: => @_beginEditingDescription()
endEditing: => @_endEditingDescription()
enterFeedback: @_enterFeedback
@$aboutImage = @$('.about img')
@$name = @$('.about .title')
@$officialLink = @$('.about a.officialLink')
@$sourceModLink = @$('.about a.sourceMod')
@$aboutLinks = @$('.about .right')
@$multiblockSection = @$('section.multiblock')
@$recipeContainer = @$('section.recipes .panel')
@$recipesSection = @$('section.recipes')
@$recipesSectionTitle = @$('section.recipes h2')
@$similarSection = @$('section.similar')
@$usedAsToolToMakeSection = @$('section.usedAsToolToMake')
@$usedToMakeSection = @$('section.usedToMake')
@$videosContainer = @$('section.videos .panel')
@$videosSection = @$('section.videos')
@$videosSectionTitle = @$('section.videos h2')
super
refresh: ->
@_resolveItemSlug()
if @model.item?
display = @_modPack.findItemDisplay @model.item.slug
@_imageLoader.load display.iconUrl, @$aboutImage
@$name.text display.itemName
@_descriptionController.imageBase = c.url.itemImageDir display
if @model.item.officialUrl?
@$officialLink.attr 'href', @model.item.officialUrl
@show @$aboutLinks
else
@hide @$aboutLinks
@show()
else
@hide()
@_refreshSourceMod()
@_refreshDescription()
@_refreshMultiblock()
@_refreshRecipes()
@_refreshSimilarItems()
@_refreshUsedAsToolToMake()
@_refreshUsedToMake()
@_refreshVideos()
super
setUser: (user)->
super user
@_descriptionController.user = user if @_descriptionController?
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click a.craftingPlan': 'routeLinkClick'
'click a.sourceMod': 'routeLinkClick'
'click .markdown a': 'routeLinkClick'
'click .button.craftingPlan': 'craftingPlanButtonClicked'
# Private Methods ##############################################################################
_beginEditingDescription: ->
if not @user?
global.site.login()
return w.reject new Error 'must be logged in to edit'
if not @model.item?
return w.reject new Error 'must have an item'
pathArgs = modSlug:@_itemSlug.mod, itemSlug:@_itemSlug.item
attributes =
fileName: c.gitHub.file.itemDescription.fileName pathArgs
path: c.gitHub.file.itemDescription.path pathArgs
@_descriptionFile = new EditableFile attributes, client:@_client
@_descriptionFile.fetch()
.then =>
if @_descriptionFile.encodedData?.length > 0
@model.item.parse @_descriptionFile.getDecodedData 'utf8'
else
@model.item.description = ''
@_descriptionController.model = @model.item.description
_endEditingDescription: ->
oldDescription = @model.item.description
promises = []
saveList = []
for imageFile in @_descriptionController.imageFiles
saveList.push
file: imageFile
message: "User-submitted image for #{@model.item.name} from #{global.hostName}"
@model.item.description = @_descriptionController.model
@_descriptionFile.setDecodedData @model.item.unparse()
saveList.push
file: @_descriptionFile
message: "User-submitted text for #{@model.item.name} from #{global.hostName}"
saveNextFile = (fileList)->
return w(true) if fileList.length is 0
{file, message} = fileList.shift()
file.save message
.delay @FILE_UPLOAD_DELAY
.then ->
saveNextFile fileList
saveNextFile saveList
.catch (e)=>
@model.item.description = oldDescription
throw e
_refreshDescription: ->
if @model.item?.description?.length > 0
@_descriptionController.model = @model.item.description
@_descriptionController.resetToDefaultState()
_refreshMultiblock: ->
if @model.item?.multiblock?
@_multiblockController.model = @model.item.multiblock
@show @$multiblockSection
else
@hide @$multiblockSection
_refreshRecipes: ->
@_recipeControllers ?= []
index = 0
recipes = @model.findRecipes()
if recipes?.length > 0
@$recipesSectionTitle.html if recipes.length is 1 then 'Recipe' else 'Recipes'
for recipe in @model.findRecipes()
controller = @_recipeControllers[index]
if not controller?
controller = new RecipeDetailController
imageLoader: @_imageLoader
modPack: @_modPack
model: recipe
router: @_router
@_recipeControllers.push controller
@$recipeContainer.append controller.$el
controller.render()
else
controller.model = recipe
index++
@show @$recipesSection
else
@hide @$recipesSection
while @_recipeControllers.length > index
@_recipeControllers.pop().remove()
_refreshSimilarItems: ->
group = @model.item?.group
if group? and group isnt Item.Group.Other
@_similarItemsController.title = "Other #{group}"
@_similarItemsController.model = @model.findSimilarItems()
else
@_similarItemsController.model = null
_refreshSourceMod: ->
mod = @model.item?.modVersion?.mod
if mod?.name?.length > 0
@$sourceModLink.attr 'href', c.url.mod modSlug:mod.slug
@$sourceModLink.text mod.name
@show @$sourceModLink
else
@hide @$sourceModLink
_refreshUsedAsToolToMake: ->
@_usedAsToolToMakeController.title = 'Used as Tool to Make'
@_usedAsToolToMakeController.model = @model.findToolForRecipes()
_refreshUsedToMake: ->
@_usedToMakeController.title = 'Used to Make'
@_usedToMakeController.model = @model.findComponentInItems()
_refreshVideos: ->
@_videoControllers ?= []
index = 0
videos = @model?.item?.videos or []
if videos? and videos.length > 0
@$videosSectionTitle.html if videos.length is 1 then 'Video' else 'Videos'
for video in videos
controller = @_videoControllers[index]
if not controller?
controller = new VideoController model:video
@_videoControllers.push controller
controller.render()
@$videosContainer.append controller.$el
else
controller.model = video
index++
@show @$videosSection
else
@hide @$videosSection
while @_videoControllers.length > index
@_videoControllers.pop().remove()
_resolveItemSlug: ->
return if @model.item?
item = @_modPack.findItem @_itemSlug, includeDisabled:true
if item?
if not ItemSlug.equal item.slug, @_itemSlug
router.navigate c.url.item(modSlug:item.slug.mod, itemSlug:item.slug.item), trigger:true
return
@model.item = item
item.fetch()
item.on c.event.sync, => @refresh()
@@ -0,0 +1,9 @@
//-
//- Crafting Guide - multiblock.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- All rights reserved.
//-
.view__multiblock
img.outline(src="/images/outline.png")
@@ -0,0 +1,24 @@
//
// Crafting Guide - multiblock.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
$animate-slow: 400ms;
$animate-fast: 100ms;
.view__multiblock {
position: relative;
.block {
position: absolute;
transition: all $animate-slow cubic-bezier(0.615, -0.090, 0.010, 1.090);
}
.outline {
position: absolute;
opacity: 0.0;
transition: opacity $animate-fast;
}
}
@@ -0,0 +1,186 @@
###
Crafting Guide - multiblock_controller.coffee
Copyright (c) 2014-2015 by Redwood Labs
All rights reserved.
###
BaseController = require '../../../base_controller'
########################################################################################################################
module.exports = class MultiblockController extends BaseController
@::HOVER_TIMEOUT = 5000 # ms
@::IMAGE_SIZE = 48 # px
@::X_LAYER_H_SHIFT = -24 # px
@::Z_LAYER_H_SHIFT = +24 # px
@::X_LAYER_V_SHIFT = -12 # px
@::Y_LAYER_V_SHIFT = -30 # px
@::Z_LAYER_V_SHIFT = -12 # px
@::MAX_DROP_DELAY = 250 # ms
constructor: (options={})->
if not options.imageLoader? then throw new Error 'options.imageLoader is required'
if not options.modPack? then throw new Error 'options.modPack is required'
options.templateName = 'item_page/multiblock_viewer/multiblock'
super options
@_currentLayer = 0
@_blockCache = null
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_onHovering = options.onHovering ?= (itemDisplay)-> # do nothing
# Public Methods ###############################################################################
goBackLayer: ->
@_currentLayer = Math.max 0, @_currentLayer - 1
@_refreshAllBlocks()
goNextLayer: ->
@_currentLayer = Math.min @model.height - 1, @_currentLayer + 1
@_refreshAllBlocks()
hasBackLayer: ->
return false unless @model?
return @_currentLayer > 0
hasNextLayer: ->
return false unless @model?
return @_currentLayer < @model.height - 1
# Property Methods #############################################################################
Object.defineProperties @prototype,
currentLayer:
get: -> return @_currentLayer
# BaseController Overrides #####################################################################
onDidRender: ->
@$outline = @$('.outline')
super
refresh: ->
@$el.css 'height':@_computeMinHeight(), 'width':@_computeMinWidth()
@_refreshBlockCache()
@_refreshAllBlocks()
super
# Private Methods ##############################################################################
_computePosition: (x, y, z, options={})->
if not @model? then return left:0, top:-@IMAGE_SIZE, 'z-index':0, opacity:0.0
options.hideUpperLayers ?= false
pxWidth = @$el.width()
pxHeight = @$el.height()
left = ((pxWidth - @IMAGE_SIZE) / 2) + (x * @X_LAYER_H_SHIFT) + (z * @Z_LAYER_H_SHIFT)
if options.hideUpperLayers and y > @_currentLayer
top = 0
opacity = 0.0
else
top = (pxHeight - @IMAGE_SIZE) + (x * @X_LAYER_V_SHIFT) + (z * @Z_LAYER_V_SHIFT) + (y * @Y_LAYER_V_SHIFT)
opacity = 1.0
zindex = ((@model.width - x) + (@model.depth - z) + ((@model.width * @model.depth) * y)) * 2
return left:left, top:top, 'z-index':zindex, opacity:opacity
_computeMinHeight: ->
return 0 unless @model?
minHeight = Math.max(Math.abs(@model.width * @X_LAYER_V_SHIFT), Math.abs(@model.depth * @Z_LAYER_V_SHIFT))
minHeight += Math.abs(@model.height * @Y_LAYER_V_SHIFT)
minHeight += 2 * @IMAGE_SIZE
return minHeight
_computeMinWidth: ->
return 0 unless @model?
leftPosition = @_computePosition @model.width, 0, 0
rightPosition = @_computePosition 0, 0, @model.depth
return rightPosition.left - leftPosition.left
_endHovering: ->
@$outline.css opacity:0.0
@_hoverTimer = null
@_onHovering null
_randomDropDelay: ->
return Math.random() * @MAX_DROP_DELAY
_refreshBlockCache: ->
@_blockCache = null
return unless @model?
@_blockCache = width:@model.width, height:@model.height, depth:@model.depth
for x in [0...@model.width]
@_blockCache[x] ?= {}
for y in [0...@model.height]
@_blockCache[x][y] ?= {}
for z in [0...@model.depth]
@_blockCache[x][y][z] ?= null
for el in @$('.block')
$el = $(el)
@_blockCache[$el.data('x')][$el.data('y')][$el.data('z')] = $el
_refreshBlock: (x, y, z)->
return unless @model?
stack = @model.getStackAt x, y, z
$block = @_blockCache[x][y][z]
position = @_computePosition x, y, z, hideUpperLayers:true
move = false
if stack?
itemDisplay = @_modPack.findItemDisplay stack.itemSlug
if not $block?
$block = $("<img alt='#{itemDisplay.itemName}' class='block' />")
$block.css position, top:-@IMAGE_SIZE, opacity:0.0
@_registerHoverHandlers $block, y, itemDisplay
@_imageLoader.load itemDisplay.iconUrl, $block
@_blockCache[x][y][z] = $block
@$el.append $block
move = true
else
$block.attr 'src', itemDisplay.iconUrl
move = true
else if $block?
$block.attr 'src', ''
move = true
if move then _.delay (-> $block.css position), @_randomDropDelay()
_refreshAllBlocks: ->
return unless @model?
if @_endHovering? then @_endHovering()
for x in [0...@model.width]
for y in [0...@model.height]
for z in [0...@model.depth]
@_refreshBlock(x, y, z)
_registerHoverHandlers: ($el, y, itemDisplay)->
startHovering = =>
return unless y is @_currentLayer
zIndex = parseInt($el.css('z-index')) + 1
zIndex = if _.isNaN(zIndex) then 0 else zIndex
@$outline.css opacity:1.0, top:$el.css('top'), left:$el.css('left'), 'z-index':zIndex
@_onHovering itemDisplay
if @_hoverTimer? then clearTimeout @_hoverTimer
@_hoverTimer = setTimeout (=> @_endHovering()), @HOVER_TIMEOUT
$el.hover startHovering, (->)
@@ -0,0 +1,22 @@
//-
//- Crafting Guide - multiblock_viewer.jade
//-
//- Copyright (c) 2015 by Redwood Labs
//- All rights reserved.
//-
.view__multiblock_viewer
.left
h3 Complete Materials
.view__inventory.complete
h3 Current Layer
.view__inventory.layer
.right
.buttons
.button.back: .bezel: p back
.button.next: .bezel: p next
.view__multiblock
.caption
img
p &nbsp;
@@ -0,0 +1,77 @@
//
// Crafting Guide - multiblock_viewer.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__multiblock_viewer {
display: flex;
align-items: stretch;
h3 {
margin-bottom: $size-margin-small;
margin-left: 0;
&:not(:first-child) {
margin-top: $size-margin-large;
}
}
.left {
border-right: 1px solid $color-gray-light;
flex: 1 0 40%;
.view__inventory {
margin-left: $size-margin-medium;
}
}
.right {
flex: 0 1 60%;
display: flex;
flex-direction: column;
.buttons {
flex: 0 0 auto;
margin-bottom: $size-margin-medium;
display: flex;
justify-content: space-between;
.button {
flex: 1 1 50%;
margin-left: $size-margin-medium;
}
}
.view__multiblock {
flex: 0 0 auto;
align-self: center;
}
.caption {
flex: 0 0 auto;
margin-top: $size-margin-medium;
display: flex;
justify-content: center;
align-items: center;
img {
margin-right : $size-margin-medium;
height : $size-minecraft-block;
width : $size-minecraft-block;
border : none;
}
p {
font-family: $font-family-text;
font-size: $font-size-medium;
}
}
}
}
@@ -0,0 +1,97 @@
#
# Crafting Guide - multiblock_viewer_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
InventoryController = require '../../common/inventory/inventory_controller'
MultiblockController = require './multiblock/multiblock_controller'
########################################################################################################################
module.exports = class MultiblockViewerController 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.templateName = 'item_page/multiblock_viewer'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_hoverTimer = null
@_router = options.router
# Event Methods ################################################################################
onBackClicked: ->
return if @$backButton.hasClass 'disabled'
@multiblockController.goBackLayer()
@refresh()
onNextClicked: ->
return if @$nextButton.hasClass 'disabled'
@multiblockController.goNextLayer()
@refresh()
onBlockHovered: (itemDisplay)->
if itemDisplay?
@$captionText.html itemDisplay.itemName
@_imageLoader.load itemDisplay.iconUrl, @$captionIcon
else
@$captionText.html '&nbsp;'
@$captionIcon.attr 'src', '/images/empty.png'
# BaseController Overrides #####################################################################
onDidRender: ->
@completeInventoryController = @addChild InventoryController, '.view__inventory.complete',
editable: false
imageLoader: @_imageLoader
modPack: @_modPack
router: @_router
@layerInventoryController = @addChild InventoryController, '.view__inventory.layer',
editable: false
imageLoader: @_imageLoader
modPack: @_modPack
router: @_router
@multiblockController = @addChild MultiblockController, '.view__multiblock',
imageLoader: @_imageLoader
modPack: @_modPack
onHovering: (itemDisplay)=> @onBlockHovered(itemDisplay)
@$backButton = @$('.button.back')
@$nextButton = @$('.button.next')
@$captionText = @$('.caption p')
@$captionIcon = @$('.caption img')
super
refresh: ->
if @model?
@completeInventoryController.model = @model.inventory
@layerInventoryController.model = @model.getLayerInventory @multiblockController.currentLayer
@multiblockController.model = @model
if @multiblockController.hasBackLayer()
@$backButton.removeClass 'disabled'
else
@$backButton.addClass 'disabled'
if @multiblockController.hasNextLayer()
@$nextButton.removeClass 'disabled'
else
@$nextButton.addClass 'disabled'
super
# Backbone.View Overrides ######################################################################
events: ->
_.extend super,
'click .button.back': 'onBackClicked'
'click .button.next': 'onNextClicked'
@@ -0,0 +1,19 @@
//-
//- Crafting Guide - recipe_detail.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__recipe_detail
.input
h3 Ingredients
.view__inventory
.pattern
.view__crafting_grid
a.tool
.output
h3 Produces
.view__inventory
@@ -0,0 +1,44 @@
//
// Crafting Guide - recipe_detail.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__recipe_detail {
display : flex;
.input, .output{
flex : 0 1 33%;
h3 {
margin-bottom : 1em;
}
.view__inventory {
margin-left: $size-margin-large;
}
}
.pattern {
flex : 0 1 33%;
display : flex;
flex-direction : column;
align-items : center;
.tool {
margin-top : $size-margin-small;
font-family : $font-family-text;
font-size : $font-size-medium;
text-align : center;
}
}
&:not(:first-child) {
border-top : 1px solid $color-gray-light;
margin-top : $size-margin-medium;
padding-top : $size-margin-large;
}
}
@@ -0,0 +1,99 @@
#
# Crafting Guide - recipe_detail_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
CraftingGridController = require '../../common/crafting_grid/crafting_grid_controller'
Inventory = require '../../../models/game/inventory'
InventoryController = require '../../common/inventory/inventory_controller'
{StringBuilder} = require 'crafting-guide-common'
########################################################################################################################
module.exports = class RecipeDetailController 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.templateName = 'item_page/recipe_detail'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_router = options.router
# BaseController Overrides #####################################################################
onDidRender: ->
@gridController = @addChild CraftingGridController, '.view__crafting_grid',
imageLoader: @_imageLoader
modPack: @_modPack
router: @_router
@inputController = @addChild InventoryController, '.input .view__inventory',
editable: false
imageLoader: @_imageLoader
model: new Inventory
modPack: @_modPack
router: @_router
@outputController = @addChild InventoryController, '.output .view__inventory',
editable: false
imageLoader: @_imageLoader
model: new Inventory
modPack: @_modPack
router: @_router
@$toolContainer = @$('.tool')
super
refresh: ->
if @model?
@gridController.model = @model
@_refreshInputs()
@_refreshOutputs()
@_refreshTools()
@show()
else
@hide()
super
# Backbone.View Methods ########################################################################
events: ->
return _.extend super,
'click a': 'routeLinkClick'
# Private Methods ##############################################################################
_refreshInputs: ->
inputs = @inputController.model
inputs.clear()
if @model?
for stack in @model.input
inputs.add stack.itemSlug, @model.getQuantityRequired stack.itemSlug
_refreshOutputs: ->
outputs = @outputController.model
outputs.clear()
if @model?
for stack in @model.output
outputs.add stack.itemSlug, @model.getQuantityProduced stack.itemSlug
_refreshTools: ->
@$toolContainer.empty()
return unless @model?
builder = new StringBuilder
builder.loop @model.tools, delimiter:', ', onEach:(b, stack)=>
display = @_modPack.findItemDisplay stack.itemSlug
b.push "<a href=\"#{display.itemUrl}\">#{display.itemName}</a>"
@$toolContainer.html builder.toString()
@@ -0,0 +1,80 @@
//-
//- Crafting Guide - login_page.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.content.view__login_page
.ready-to-login
.left
h1 Login with GitHub
p.
Use your GitHub credentials to login and make changes on Crafting Guide. No GitHub account? Don't
worry, you can get one for free. <a class="read-more" href="#">read more...</a>
.expandable-container.read-more
.content
p.
All changes you make on the site get submitted directly to our GitHub repo, and then published
through the same automated deployment process as changes made by anyone else. That way, anyone
can contribute easily: whether developer or end user.
p.
To log in, we redirect you to GitHub's site, and once you're signed in, they'll send you back
here. We never see your credentials, and only get the minimum set of permissions necessary to
submit changes to the Crafting Guide repo on your behalf.
p.
GitHub offers free accounts, so even if you don't already have one, you've got nothing to lose!
.actions
.button.login: .bezel: p Login with GitHub
.right
img(src="/images/github-login-large.png")
.fetching-token
.left
h1 Completing log in...
p Hang on just a second while we finish logging you in...
.actions
img(src="/images/wait.gif")
.right
img(src="/images/github-login-large.png")
.invalid-callback
.left
h1 Oops!
p.
Sorry about that, but it looks like something went wrong with your GitHub login. Click the button below
to try again, or use the Feedback button on the left to let us know, and we'll try to help.
.actions
.button.login: .bezel: p Login with GitHub
.right
img(src="/images/github-login-large.png")
.logged-in
.left
h1 All set!
p.
You are logged in to CraftingGuide using the following GitHub account:
.user
.left
img
.right
.name
.email
p.withRedirect.
You will be redirected shortly.
.actions
.button.logout: .bezel: p Logout
.right
img.float-right(src="/images/github-login-large.png")
.server-down
.left
h1 Sorry!
p.
Our interactive editing service is down right now, but you can always submit a pull request over at the
<a href="https://github.com/andrewminer/crafting-guide">Crafting Guide GitHub Repository</a>. We'll
have the editing service back up soon!
@@ -0,0 +1,89 @@
//
// Crafting Guide - login_page.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__login_page {
display: flex;
align-items: center;
justify-content: center;
& > div {
max-width: 75%;
display: flex;
align-items: center;
& > .left {
flex: 1 1 66%;
margin-right: $size-margin-large;
display: flex;
flex-direction: column;
p {
font-family: $font-family-text;
font-size: $font-size-medium;
line-height: $font-size-medium * 1.25;
&:not(:first-child) {
margin-top: $size-margin-medium;
}
}
.actions {
margin-top: $size-margin-large;
padding: 0 $size-margin-xlarge;
p {
font-family: $font-family-header;
font-size: 20px;
}
}
.expandable-container {
margin-top: $size-margin-medium;
max-height: 0;
overflow: hidden;
transition: max-height 500ms;
}
.user {
margin: $size-margin-large;
display: flex;
@include floating-panel;
padding: $size-margin-medium;
.left {
margin-right: $size-margin-small;
img {
height: $size-minecraft-block * 2;
width: $size-minecraft-block * 2;
}
}
.right {
.name {
font-family: $font-family-text;
font-size: $font-size-xlarge;
}
.email {
font-family: $font-family-text;
font-size: $font-size-small;
margin-top: $size-margin-xsmall;
}
}
}
}
& > .right {
flex: 0 0 33%;
}
}
}
@@ -0,0 +1,144 @@
#
# Crafting Guide - login_page_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
PageController = require '../page_controller'
GitHubUser = require '../../models/site/github_user'
{CraftingGuideClient} = require 'crafting-guide-common'
########################################################################################################################
module.exports = class LoginPageController extends PageController
@::State =
ReadyToLogin: 'ready-to-login'
ServerDown: 'server-down'
InvalidCallback: 'invalid-callback'
FetchingToken: 'fetching-token'
LoggedIn: 'logged-in'
constructor: (options={})->
options.templateName = 'login_page'
@_client = options.client
@_params = options.params
@_storage = options.storage
@_accessTokenLookupFailed = false
@_client.on c.event.change + ':status', => @tryRefresh()
super options
# Event Methods ################################################################################
onExpandReadMore: (event)->
@$readMoreLink.css display: 'none'
@$readMoreContainer.css 'max-height':@$readMoreContent.height()
return false
onLoginButtonClicked: (event)->
@_redirectToGitHub()
return false
onLogoutButtonClicked: (event)->
global.site.logout()
return false
# Property Methods #############################################################################
getLoginSecurityToken: ->
@_storage.load 'loginSecurityToken'
setLoginSecurityToken: (value)->
@_storage.store 'loginSecurityToken', value
Object.defineProperties @prototype,
loginSecurityToken: {get:@prototype.getLoginSecurityToken, set:@prototype.setLoginSecurityToken}
# PageController Overrides #####################################################################
getTitle: ->
return "Login"
# BaseController Methods #######################################################################
onDidRender: ->
@$readMoreContainer = @$('.expandable-container.read-more')
@$readMoreContent = @$('.read-more .content')
@$readMoreLink = @$('a.read-more')
@$redirectMessage = @$('.withRedirect')
@$userAvatar = @$('.user img')
@$userEmail = @$('.user .email')
@$userName = @$('.user .name')
if (not @user?) and (@_params?.state?) and (@_params?.state is @loginSecurityToken)
@_client.completeGitHubLogin code:@_params.code
.then (response)=>
attributes = response.json.data.user
if attributes?
global.site.user = new GitHubUser attributes
global.site.resumeAfterLogin()
.catch (error)=>
logger.error "Failed to get access token: #{error}"
@_accessTokenLookupFailed = true
@refresh()
.done()
super
refresh: ->
state = @_computeState()
@$el.find('> *').css display: 'none'
@$(".#{state}").css display: ''
if state is @State.LoggedIn
@$userAvatar.attr 'src', @user.avatarUrl
@$userEmail.text @user.email
@$userName.text @user.name
postLoginUrl = @_storage.load 'post-login-url'
@$redirectMessage.css display:(if postLoginUrl? then 'none' else '')
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click a.read-more': 'onExpandReadMore'
'click .button.login': 'onLoginButtonClicked'
'click .button.logout': 'onLogoutButtonClicked'
# Private Methods ##############################################################################
_computeState: ->
if @user? then return @State.LoggedIn
if @_client.status is CraftingGuideClient.Status.Down then return @State.ServerDown
if @_accessTokenLookupFailed then return @State.InvalidCallback
if @_params?.state?
if @_params.state is @loginSecurityToken
return @State.FetchingToken
else
return @State.InvalidCallback
return @State.ReadyToLogin
_compeleteLogin: (code)->
@_client.completeGitHubLogin code:code
.then (response)=>
global.site.user = new GitHubUser response.json
.catch (error)=>
@_accessTokenLookupFailed = true
@refresh()
.done()
_redirectToGitHub: ->
@loginSecurityToken = _.uuid()
clientId = c.login.clientIds[global.env]
window.location.href = c.login.authorizeUrl clientId:clientId, state:@loginSecurityToken
+36
View File
@@ -0,0 +1,36 @@
//-
//- Crafting Guide - item_page.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.content.view__mod_page
.left
.view__adsense
.right
.about
.left
img
.center
.title
.author
.description
.version
span in my mod pack
select
.warning.
<b>PLEASE NOTE:</b> By selecting "no", crafting plans, search, and other features will ignore items
and recipes from this mod.
.right
p Official Links:
a.homePage project page
a.documentation documentation
a.download download
section.tutorials
h2 Tutorials
.panel
.itemGroups
+94
View File
@@ -0,0 +1,94 @@
//
// Crafting Guide - item_page.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__mod_page {
@include layout-body-with-sidebar;
.about {
margin-bottom: $size-margin-xlarge;
display: flex;
.left {
flex: 0 0 auto;
margin-right: $size-margin-large;
img {
height: $size-minecraft-block * 5;
width: $size-minecraft-block * 5;
}
}
.center {
flex: 1 1 100%;
margin-right: $size-margin-large;
.title {
font-family: $font-family-header;
font-size: $font-size-xxlarge;
}
.author {
font-family: $font-family-text;
font-size: $font-size-medium;
&:before {
content: "by: "
}
}
.description {
margin-top: $size-margin-large;
font-family: $font-family-text;
font-size: $font-size-medium;
line-height: $font-size-medium * 1.25;
}
.version {
margin-top: $size-margin-large;
font-family: $font-family-control;
font-size: $font-size-medium;
display: flex;
align-items: baseline;
span {
flex: 0 0 auto;
margin-right: $size-margin-medium;
}
select {
flex: 1 1 100%;
font-family: $font-family-control;
font-size: $font-size-medium;
}
}
.warning {
margin-top: $size-margin-medium;
color: $color-error;
font-family: $font-family-text;
font-size: $font-size-small;
line-height: $font-size-small * 1.25;
b {
font-weight: bold;
}
}
}
.right {
flex: 0 0 auto;
@include layout-link-list;
}
}
}
@@ -0,0 +1,184 @@
#
# Crafting Guide - mod_page_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
AdsenseController = require '../common/adsense/adsense_controller'
BaseController = require '../base_controller'
Item = require '../../models/game/item'
ItemGroupController = require '../common/item_group/item_group_controller'
Mod = require '../../models/game/mod'
TutorialController = require './tutorial/tutorial_controller'
########################################################################################################################
module.exports = class ModPageController 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.templateName = 'mod_page'
super options
@_imageLoader = options.imageLoader
@_modPack = options.modPack
@_router = options.router
# Event Methods ################################################################################
onVersionChanged: ->
@model.activeVersion = @$versionSelector.val()
return false
# Property Methods #############################################################################
Object.defineProperties @prototype,
effectiveModVersion:
get: ->
modVersion = @model.activeModVersion
modVersion ?= @model.getModVersion Mod.Version.Latest
modVersion.fetch() if modVersion?
return modVersion
# PageController Overrides #####################################################################
getBreadcrumbs: ->
return [
$('<a href="/browse">Browse</a>')
$("<b>#{@model.name}</b>")
]
getTitle: ->
return @model.name
# BaseController Overrides #####################################################################
onDidRender: ->
@_adsenseController = @addChild AdsenseController, '.view__adsense', model: 'skyscraper'
@$author = @$('.about .author')
@$description = @$('.about .description')
@$documentationLink = @$('.about .documentation')
@$downloadLink = @$('.about .download')
@$homePageLink = @$('.about .homePage')
@$itemGroups = @$(".itemGroups")
@$logo = @$('.about img')
@$title = @$('.about .title')
@$tutorialsContainer = @$('section.tutorials .panel')
@$tutorialsSection = @$('section.tutorials')
@$versionSelector = @$('.version select')
@$warning = @$('.warning')
super
refresh: ->
@_refreshAboutBlock()
@_refreshItemGroups()
@_refreshTutorials()
@_refreshVersionSelector()
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'change .version select': 'onVersionChanged'
# Private Methods ##############################################################################
_refreshAboutBlock: ->
if @model?
@$author.text @model.author
@$description.text @model.description
@$documentationLink.attr 'href', @model.documentationUrl
@$downloadLink.attr 'href', @model.downloadUrl
@$homePageLink.attr 'href', @model.homePageUrl
@$logo.attr 'src', c.url.modIcon modSlug:@model.slug
@$title.text @model.name
else
@$author.text ''
@$description.text ''
@$documentationLink.attr 'href', ''
@$downloadLink.attr 'href', ''
@$homePageLink.attr 'href', ''
@$logo.attr 'src', ''
@$title.text ''
_refreshItemGroups: ->
@_groupControllers ?= []
groupIndex = 0
modVersion = @effectiveModVersion
if modVersion?
modVersion.eachGroup (group)=>
controller = @_groupControllers[groupIndex]
items = modVersion.allItemsInGroup group
if not controller?
controller = new ItemGroupController
imageLoader: @_imageLoader
model: items
modPack: @_modPack
router: @_router
title: if group is Item.Group.Other then 'Items' else group
@_groupControllers.push controller
@$itemGroups.append controller.$el
controller.render()
else
controller.modVersion = modVersion
controller.model = items
controller.refresh()
groupIndex++
while @_groupControllers.length > groupIndex + 1
@_groupControllers.pop().remove()
_refreshTutorials: ->
@_tutorialControllers ?= []
index = 0
tutorials = @model.tutorials
if tutorials.length > 0
for tutorial in tutorials
controller = @_tutorialControllers[index]
if not controller?
controller = new TutorialController model: tutorial, router: @_router
@_tutorialControllers.push controller
@$tutorialsContainer.append controller.$el
controller.render()
else
controller.model = tutorial
index += 1
while @_tutorialControllers.length > index
@_tutorialControllers.pop().remove()
@show @$tutorialsSection
else
@hide @$tutorialsSection
_refreshVersionSelector: ->
modVersions = @model.modVersions
selectedModVersion = @effectiveModVersion
@$versionSelector.empty()
$option = $("<option value=\"none\">no</option>")
if not @model.enabled
@$warning.css display:''
selectedModVersion = null
$option.attr 'selected', true
else
@$warning.css display:'none'
@$versionSelector.append $option
for modVersion in @model.modVersions
$option = $("<option value=\"#{modVersion.version}\">#{modVersion.version}</option>")
if modVersion is selectedModVersion
$option.attr 'selected', 'true'
@$versionSelector.append $option
@$versionSelector.css display:''
@@ -0,0 +1,11 @@
//-
//- Crafting Guide - tutorial.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__tutorial
a
img
p
@@ -0,0 +1,30 @@
//
// Crafting Guide - tutorial.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__tutorial {
display: flex;
flex-wrap: wrap;
a {
flex: 0 0 auto;
display: flex;
flex-direction: column;
align-items: center;
img {
height: $size-minecraft-block * 4;
margin-bottom: $size-margin-xsmall;
width: $size-minecraft-block * 4;
}
p {
font-family: $font-family-text;
font-size: $font-size-medium;
}
}
}
@@ -0,0 +1,38 @@
#
# Crafting Guide - tutorial_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require '../../base_controller'
########################################################################################################################
module.exports = class TutorialController extends BaseController
constructor: (options={})->
if not options.model? then throw new Error 'options.model is required'
options.templateName = 'mod_page/tutorial'
super options
# BaseController Overrides #####################################################################
onDidRender: ->
@$icon = @$('img')
@$link = @$('a')
@$title = @$('p')
super
refresh: ->
templateData = modSlug:@model.modSlug, tutorialSlug:@model.slug
@$icon.attr 'src', c.url.tutorialIcon templateData
@$link.attr 'href', c.url.tutorial templateData
@$title.html @model.name
super
# Backbone.View Overrides ######################################################################
events: ->
return _.extend super,
'click a': 'routeLinkClick'
+162
View File
@@ -0,0 +1,162 @@
//-
//- Crafting Guide - news_page.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.content.view__news_page
.left
.view__adsense
.right
section
h3 2015-02-11
.panel
p.
I've been using <a href="/browse/modular_powersuits">Modular PowerSuits</a> myself lately, and I
noticed that the newest version (finally!) comes with default recipes. So, I've updated
Crafting Guide to support both the newest vanilla recipes as well as those from Thermal
Expansion. You can switch back and forth on the <a href="/configure">Configure</a> page.
section
h3 2015-02-09
.panel
p.
Back before the winter holidays, the extremely patient <a href="https://github.com/tdallmann">
tdallmann</a> completed support for <a href="/browse/hydraulicraft/">Hydraulicraft</a>, and I'm
afraid I'm only now getting it up on the site! It looks like a neat mod based all around the
science of hydraulics with a nicely stepped set of devices to be made at each level. Many thanks
to tddallmann for adding it to Crafting Guide!
p.
I also received a submission for adding <a href="/browse/opencomputers">OpenComputers</a> from
<a href="https://github.com/yut23">yut23</a>. Should be a big help to those aspiring Minecraft
programmers who want a bit more challege than is provided by <a href="/browse/computercraft">
ComputerCraft</a>! Thanks, yut23!
section
h3 2015-11-25
.panel
p.
With many thanks to <a href="https://github.com/pkmnfrk">Mike Caron</a>, I'm pleased to announce
support for <a href="/browse/storage_drawers">Storage Drawers</a>!
section
h3 2015-12-13
.panel
p.
I'm super excited to announce that I've added full multi-block support for Crafting Guide! For
example, check out a <a href="/browse/big_reactors/5_core_passive_reactor">5-Core Reactor</a>
from Big Reactors, or the <a href="/browse/railcraft/steel_tank_5x5x5">5x5x5 Steel Tank</a> from
Railcraft. I'm really pleased with how it turned out!
section
h3 2015-11-17
.panel
p.
And today, it's <a href="/browse/extra_cells">Extra Cells</a>!
section
h3 2015-11-16
.panel
p.
It's a small one, but also highly requested... Here's <a
href="/browse/computercraft">ComputerCraft</a>!
section
h3 2015-11-14
.panel
p.
One more small one over the weekend... <a href="/browse/advanced_solar_panels">Advanced Solar
Panels</a> has been added to crafting guide!
section
h3 2015-11-14
.panel
p.
For a long while now, <a href="/browse/galacticraft">Galacticraft</a> has been the most
requested mod, and it's finally here! Enjoy!
section
h3 2015-11-10
.panel
p.
Remember me? They guy who runs the site. Yeah, I'm back, and with big news! The site now has
a completely new crafting algorithm. Instead of just taking the first crafting plan it comes up
with, it tries to compute every possible plan, rates them, and then picks the best one. This
also clears up a lot of bugs related to the old algorithm. The one drawback is that some items
can take a bit longer to compute than with the old way, but I think you'll find it's worth it!
p.
On a personal note, I think it's clear that I don't have nearly as much time to dedicate to the
site as I used to. That's mostly because I started a new job which keeps me away from working
on the site so much. I hope to be able to get into a more regular pace now things my personal
life has settled down again.
section
h3 2015-07-25
.panel
p.
You may have noticed things have been very quiet with the site for the past month... well I'm
afraid that's because I haven't been working on it. Instead, I've been hunting for a new job.
Now that I've got that all squared away (I'll be starting at Looker in Santa Cruz on Monday),
I'll have nights and weekends to continue working on the site. Thanks for being patient!
section
h3 2015-06-12
.panel
p.
Today I launched the second major part of the item description editor: image uploads! As you
enter your description, use the markdown notation for an image <tt> ![](image.png) </tt> and a
empty image preview will show up underneath the editor. Click the button to select an image from
your computer. Just be sure to keep the images to a reasonable size (< 750kB, and < 740px by
600px).
section
h3 2015-05-13
.panel
p.
As of today, you can now add and edit item descriptions right on Crafting Guide. This feature is
starting small with just item descriptions, but eventually all the content on the site will be
editable. All you need is a (free) GitHub account, and you're ready to go!
p.
For those more comfortable with a regular text editor, all the content is still in GitHub under
the <a href="https://github.com/andrewminer/crafting-guide-data">crafting-guide-data</a> repo.
Just follow the instructions, and you can submit content via pull request instead of using the
website's editor.
section
h3 2015-04-30
.panel
p.
I've been working on a big new feature for a while now, but I didn't want to leave everyone
without anything new for so long... So, I've just finished adding <a
href="/browse/minefactory_reloaded">MineFactory Reloaded</a>! Enjoy!
section
h3 2015-04-20
.panel
p.
Okay... big changes on the crafting page! Instead of having four little boxes for the various
parts of the crafting plan, the new page shows all the steps as one long list. In addition, you
get a lot more control over the plan. Now, you can even mark individual steps as complete to get
a new crafting plan which doesn't include those items.
p.
As an example, let's say you're thinking about upgrading your <a
href="/browse/simply_jetpacks/reinforced_jetpack">Reinforced Jetpack</a> to a shiny new <a
href="/browse/simply_jetpacks/resonant_jetpack">Resonant Jetpack</a>, you can just mark off the
step for the Reinforced Jetpack, and get only the materials and steps for doing the upgrade.
I'm really pleased with the new crafting page, and I think you're going to love it!
section
h3 2015-04-11
.panel
p.
I've just finished adding <a href="/browse/simply_jetpacks">Simply Jetpacks</a> by Tonius. It
seems like a simple mod on the surface, but what with all the upgrades and technology tiers, it
takes an amazing <a href="/craft/flux_infused_jetplate">87 steps</a> to make the highest-tier
jetpack!
p.
I've also added <a href="/browse/solar_flux">Solar Flux</a> by Nauktis. Again, another
deceptively simple mod, but the <a href="/browse/solar_flux/solar_panel_vi/">most powerful solar
panel</a> kicks out an impressive 4096 RF/t! I just hope your mine has been productive... among
other things, you'll need 45 Diamonds, 110 Gold Ore, 1568 Copper Ore, and 6023 Iron Ore!
+30
View File
@@ -0,0 +1,30 @@
//
// Crafting Guide - news_page.scss
//
// Copyright © 2014-2016 by Redwood Labs
// All rights reserved.
//
.view__news_page {
@include layout-body-with-sidebar;
section {
&:not(:first-child) {
margin-top : $size-margin-large;
}
h3 {
margin-bottom : $size-margin-small;
}
p {
font-family : $font-family-text;
font-size : $font-size-medium;
line-height : $font-size-medium * 1.5;
&:not(:last-child) {
margin-bottom : $size-margin-medium;
}
}
}
}
@@ -0,0 +1,33 @@
#
# Crafting Guide - news_page_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
AdsenseController = require '../common/adsense/adsense_controller'
PageController = require '../page_controller'
########################################################################################################################
module.exports = class NewsPageController extends PageController
constructor: (options={})->
options.templateName = 'news_page'
super options
# PageController Overrides #####################################################################
getTitle: ->
return "News"
# BaseController Overrides #####################################################################
onDidRender: ->
@_adsenseController = @addChild AdsenseController, '.view__adsense', model:'skyscraper'
# Backbone.View Overrides ######################################################################
events: ->
_.extend super,
'click a': 'routeLinkClick'
+54
View File
@@ -0,0 +1,54 @@
#
# Crafting Guide - page_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require './base_controller'
########################################################################################################################
module.exports = class PageController extends BaseController
constructor: (options={})->
super options
# Public Methods ###############################################################################
getBreadcrumbs: ->
return []
getMetaDescription: ->
# subclasses should override this to return the page-specific text for the meta description tag
return null
getTitle: ->
# subclasses should override this to return the page-specific portion of the title
return null
# BaseController Overrides #####################################################################
refresh: ->
@_refreshMetaDescription()
@_refreshTitle()
super
# Private Methods ##############################################################################
_refreshMetaDescription: ->
description = @getMetaDescription()
description ?= ''
if description.length > 0
$('meta[name="description"]').remove()
$('head').append "<meta name=\"description\" content=\"#{description}\">"
_refreshTitle: ->
title = @getTitle()
title = if title? then title.trim() else ''
if title.length > 0
$('title').html c.text.titleWithText text:title
else
$('title').html c.text.title()
+169
View File
@@ -0,0 +1,169 @@
#
# Crafting Guide - router.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BrowsePageController = require './browse_page/browse_page_controller'
CraftPageController = require './craft_page/craft_page_controller'
ItemPageController = require './item_page/item_page_controller'
ItemSlug = require '../models/game/item_slug'
LoginPageController = require './login_page/login_page_controller'
ModPageController = require './mod_page/mod_page_controller'
NewsPageController = require './news_page/news_page_controller'
TutorialPageController = require './tutorial_page/tutorial_page_controller'
UrlParams = require './url_params'
########################################################################################################################
module.exports = class Router extends Backbone.Router
constructor: (siteController, options={})->
if not siteController? then throw new Error "siteController is required"
@_siteController = siteController
@_lastReported = null
super options
# Backbone.Router Overrides ####################################################################
navigate: ->
super
@_recordPageView()
routes:
'': 'route__home'
'/': 'route__home'
'/index.html': 'route__home'
'browse': 'route__browse'
'browse/': 'route__browse'
'browse/index.html': 'route__browse'
'browse/:modSlug': 'route__browseMod'
'browse/:modSlug/': 'route__browseMod'
'browse/:modSlug/index.html': 'route__browseMod'
'browse/:modSlug/:itemSlug': 'route__browseModItem'
'browse/:modSlug/:itemSlug/': 'route__browseModItem'
'browse/:modSlug/:itemSlug/index.html': 'route__browseModItem'
'browse/:modSlug/tutorials/:tutorialSlug': 'route__browseTutorial'
'browse/:modSlug/tutorials/:tutorialSlug/': 'route__browseTutorial'
'browse/:modSlug/tutorials/:tutorialSlug/index.html': 'route__browseTutorial'
'configure': 'route__configure'
'configure/': 'route__configure'
'configure/index.html': 'route__configure'
'craft': 'route__craft'
'craft/': 'route__craft'
'craft/index.html': 'route__craft'
'craft/:text': 'route__craft'
'craft/:text/': 'route__craft'
'craft/:text/index.html': 'route__craft'
'login': 'route__login'
'login/': 'route__login'
'login/index.html': 'route__login'
'news': 'route__news'
'news/': 'route__news'
'news/index.html': 'route__news'
'item/:itemSlug': 'deprecated__item'
'crafting/(:text)': 'deprecated__crafting'
'mod/:modSlug': 'deprecated__mod'
'mod/:modSlug/:itemSlug': 'deprecated__modItem'
# Route Methods ################################################################################
route__home: ->
params = new UrlParams recipeName:{type:'string'}, count:{type:'integer'}
if params.recipeName?
@deprecated__v1_root params
return
@route__craft()
route__browse: ->
@_siteController.setPage 'browse', new BrowsePageController @_makeOptions {}
route__browseMod: (modSlug)->
controller = new ModPageController @_makeOptions()
controller.model = @_siteController.modPack.getMod modSlug
@_siteController.setPage 'browseMod', controller
route__browseModItem: (modSlug, itemSlug)->
slug = new ItemSlug modSlug, itemSlug
controller = new ItemPageController @_makeOptions {itemSlug:slug}
@_siteController.setPage 'browseModItem', controller
route__browseTutorial: (modSlug, tutorialSlug)->
controller = new TutorialPageController @_makeOptions {modSlug:modSlug, tutorialSlug:tutorialSlug}
@_siteController.setPage 'browseTutorial', controller
route__configure: ->
@route__craft()
route__craft: (text)->
controller = new CraftPageController @_makeOptions {}
controller.model.params = inventoryText:text
@_siteController.setPage 'craft', controller
route__login: ->
params = new UrlParams code:{type:'string'}, state:{type:'string'}
controller = new LoginPageController @_makeOptions {params:params}
@_siteController.setPage 'login', controller
route__news: ->
controller = new NewsPageController @_makeOptions {}
@_siteController.setPage 'news', controller
# Deprecated Route Methods #####################################################################
deprecated__crafting: (text)->
controller = new CraftPageController @_makeOptions {}
controller.model.params = inventoryText:text
@_siteController.setPage 'crafting', controller
deprecated__item: (itemSlug)->
controller = new ItemPageController @_makeOptions {itemSlug:ItemSlug.slugify(itemSlug)}
@_siteController.setPage 'item', controller
deprecated__modItem: (modSlug, itemSlug)->
slug = new ItemSlug modSlug, itemSlug
controller = new ItemPageController @_makeOptions {itemSlug:slug}
@_siteController.setPage 'item', controller
deprecated__mod: (modSlug)->
controller = new ModPageController @_makeOptions {}
controller.model = @_siteController.modPack.getMod modSlug
@_siteController.setPage 'mod', controller
deprecated__v1_root: (params)->
text = ''
if params.recipeName?
if params.count? and params.count > 1
text = "#{params.count}.#{_.slugify(params.recipeName)}"
else
text = _.slugify params.recipeName
@navigate c.url.crafting(inventoryText:text), trigger:true
# Private Methods ##############################################################################
_makeOptions: (options={})->
return _.extend options, @_siteController.controllerOptions
_recordPageView: ->
pathname = window.location.pathname
if global.env is 'production' and ga?
logger.info -> "Recording GA page view: #{pathname}"
ga 'send', 'pageview', pathname
else
logger.info -> "Suppressing GA page view: #{pathname}"
+163
View File
@@ -0,0 +1,163 @@
#
# Crafting Guide - site_controller.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseController = require './base_controller'
FeedbackController = require './feedback/feedback_controller'
FooterController = require './footer/footer_controller'
GitHubUser = require '../models/site/github_user'
HeaderController = require './header/header_controller'
ImageLoader = require './image_loader'
Mod = require '../models/game/mod'
ModPack = require '../models/game/mod_pack'
Router = require './router'
Storage = require './storage'
########################################################################################################################
module.exports = class SiteController extends BaseController
constructor: (options={})->
options.el = 'html'
super options
@client = options.client
@imageLoader = new ImageLoader defaultUrl:'/images/unknown.png'
@modPack = new ModPack
@router = new Router this
@storage = new Storage storage:global.localStorage
@_currentPage = null
@_currentPageController = null
@_user = null
# Public Methods ###############################################################################
loadDefaultModPack: ->
makeResponder = (m)-> return ->
m.activeModVersion.fetch() if m.activeModVersion?
for modSlug, modData of c.defaultMods
mod = new Mod slug:modSlug
mod.on c.event.change + ':activeModVersion', makeResponder mod
@storage.register "mod:#{mod.slug}", mod, 'activeVersion', modData.defaultVersion
mod.fetch()
@modPack.addMod mod
if global.env isnt 'prerender'
@modPack.once c.event.sync, =>
@$pageContent.css 'display', ''
@$pageContentLoading.css 'display', 'none'
loadCurrentUser: ->
@client.fetchCurrentUser()
.then (response)=>
userData = response.json?.data?.user
@user = new GitHubUser userData if userData?
.catch (error)=>
if error?.response?.statusCode isnt 401
logger.error "Failed to get current user: #{error}"
else
logger.info "User is not logged in"
.done()
login: ->
@storage.store 'post-login-url', window.location.pathname
@router.navigate c.url.login(), trigger:true
logout: ->
@client.logout()
.then =>
@storage.store 'loginSecurityToken', null
@user = null
.catch (error)->
logger.error -> "Failed to log out: #{error}"
.done()
resumeAfterLogin: ->
url = @storage.load 'post-login-url'
url = if (not url? or url is 'null') then null else url
if url? then @router.navigate url, trigger:true
@storage.store 'post-login-url', null
# Property Methods #############################################################################
getControllerOptions: ->
client: @client
imageLoader: @imageLoader
modPack: @modPack
router: @router
storage: @storage
enterFeedback: (message)=> @_feedbackController.enterFeedback(message)
user: @user
getUser: ->
return @_user
setUser: (newUser)->
oldUser = @_user
return if newUser is oldUser
@_user = newUser
logger.info -> "User changed to: #{newUser?.name}"
@trigger c.event.change + ':user', this, oldUser, newUser
@trigger c.event.change, this
if @_currentPageController? then @_currentPageController.user = newUser
Object.defineProperties @prototype,
controllerOptions: { get:@::getControllerOptions }
user: { get:@::getUser, set:@::setUser }
# BaseController Overrides #####################################################################
render: ->
@_footerController = @addChild FooterController, '.view__footer', @controllerOptions
@_headerController = @addChild HeaderController, '.view__header', @controllerOptions
@$pageContent = @$('.page > .content')
@$pageContentLoading = @$('.page > .content-loading')
@_feedbackController = @addChild FeedbackController, '.view__feedback'
# Private Methods ##############################################################################
setPage: (page, controller)->
return if @_currentPageController is controller
logger.info -> "changing to page controller: #{controller.constructor.name}"
if @_currentPageController
@_currentPageController.unrender()
@_resetGlobals()
@_currentPage = page
@_currentPageController = controller
$pageContent = $('.page > .content')
$pageContent.removeClass()
$pageContent.addClass 'content'
window.scrollTo 0, 0
@_headerController.model = controller: controller
@_currentPageController.user = @user
@_currentPageController.onWillShow()
@_currentPageController.$el = $pageContent
@_currentPageController.render()
_resetGlobals: ->
if global.env in c.productionEnvs
for key, value of global
if key.indexOf('google') isnt -1
delete global[key]
delete global.adsByGoogle
$('script[src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"]').remove()
$('body').append('<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>')
+62
View File
@@ -0,0 +1,62 @@
#
# Crafting Guide - storage.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
########################################################################################################################
module.exports = class Storage
constructor: (options={})->
options.storage ?= window.sessionStorage
@storage = options.storage
@_models = {}
# Public Methods ###############################################################################
load: (key)->
value = @storage.getItem key
logger.verbose -> "loaded #{value} from #{key}"
return value
store: (key, value)->
@storage.setItem key, value
logger.verbose -> "stored #{value} into #{key}"
register: (key, model, property, defaultValue=null)->
modelData = @_models[key]
if not modelData? then modelData = @_models[key] = model:model, properties:{}
makeStoreMethod = (k, m, p)=> return => @_storeProperty(k, m, p)
return if modelData.properties[property]?
modelData.properties[property] = property
@_loadProperty key, model, property, defaultValue
model.on "change:#{property}", makeStoreMethod(key, model, property)
unregister: (key, property)->
modelData = @_models[key]
return unless modelData?
delete modelData.properties[key]
# Private Methods ##############################################################################
_loadProperty: (key, model, property, defaultValue=null)->
loadedValue = @load "#{key}:#{property}"
if loadedValue?
value = JSON.parse loadedValue
else
value = defaultValue
return unless value?
model[property] = value
if value? and not loadedValue?
@_storeProperty key, model, property
_storeProperty: (key, model, property)->
value = JSON.stringify model[property]
@store "#{key}:#{property}", value
File diff suppressed because one or more lines are too long
@@ -0,0 +1,26 @@
//-
//- Crafting Guide - tutorial_page.jade
//-
//- Copyright © 2014-2016 by Redwood Labs
//- All rights reserved.
//-
.view__tutorial_page
.left
.view__adsense
.right
.about
.left
img
.right
.title
.sourceMod a tutorial for <a></a>
.officialLink from <a></a>
.tutorial-sections
section.videos
h2
.panel
.video-container

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