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
@@ -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"