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
+46
View File
@@ -0,0 +1,46 @@
#
# Crafting Guide - craft_page.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
Craftsman = require '../crafting/craftsman'
Inventory = require '../game/inventory'
ModPack = require '../game/mod_pack'
########################################################################################################################
module.exports = class CraftPage extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.modPack then throw new Error 'attributes.modPack is required'
attributes.params ?= null
attributes.craftsman ?= new Craftsman attributes.modPack
super attributes, options
@modPack.on c.event.change, => @_consumeParams()
@on c.event.change + ':params', => @_consumeParams()
@craftsman.on c.event.change + ':stage', => @trigger c.event.change, this
@craftsman.on c.event.change + ':complete', => @trigger c.event.change, this
# Private Methods ##############################################################################
_consumeParams: ->
return unless @params?
@craftsman.want.clear()
if not @params.inventoryText?
@params = null
else
inventory = new Inventory
inventory.parse @params.inventoryText
inventory.each (stack)=>
item = @modPack.findItem stack.itemSlug, enableAsNeeded:true
return unless item? and item.isCraftable
@craftsman.want.add stack.itemSlug, stack.quantity
inventory.remove stack.itemSlug
if inventory.isEmpty then @params = null
+143
View File
@@ -0,0 +1,143 @@
#
# Crafting Guide - editable_file.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
########################################################################################################################
module.exports = class EditableFile extends BaseModel
@::MimeTypes = [
{ pattern:/\.cg$/i, text:'text/plain' }
{ pattern:/\.png$/i, text:'image/png' }
{ pattern:/\.gif$/i, text:'image/gif' }
{ pattern:/\.jpg$/i, text:'image/jpeg' }
{ pattern:/\.jpeg$/i, text:'image/jpeg' }
]
@::Status = {
'unknown': 'unknown'
'checking': 'checking'
'empty': 'empty'
'clean': 'clean'
'dirty': 'dirty'
}
constructor: (attributes={}, options={})->
if not options.client? then throw new Error 'options.client is required'
attributes.fileName ?= ''
attributes.path ?= ''
attributes.sha ?= null
super attributes, options
@_client = options.client
@_status = @Status.unknown
@_encodedData = null
@on c.event.change + ':fileName', => @_mimeType = null
# Public Methods ###############################################################################
getDecodedData: (targetEncoding='utf8')->
return new Buffer(@encodedData, 'base64').toString(targetEncoding)
setDecodedData: (text, sourceEncoding='utf8')->
@encodedData = new Buffer(text, sourceEncoding).toString('base64')
# Property Methods #############################################################################
getDataAsText: ->
if not @_dataAsText
@_dataAsText = new Buffer(@encodedData, 'base64').toString('utf8')
return @_dataAsText
getEncodedData: ->
return @_encodedData
setEncodedData: (newEncodedData)->
oldEncodedData = @_encodedData
return if newEncodedData is oldEncodedData
@_encodedData = newEncodedData
@_updateStatusForNewData()
@trigger c.event.change + ':encodedData', this, oldEncodedData, newEncodedData
@trigger c.event.change, this
getFullPath: ->
return "#{@path}/#{@fileName}"
getMimeType: ->
if not @_mimeType?
for mimeType in @MimeTypes
if mimeType.pattern.test @fileName
@_mimeType = mimeType.text
break
return @_mimeType
getStatus: ->
return @_status
isValid: ->
return @_status in [ @Status.clean, @Status.dirty ]
Object.defineProperties @prototype,
encodedData: { get:@prototype.getEncodedData, set:@prototype.setEncodedData }
fullPath: { get:@prototype.getFullPath }
imageUrl: { get:@prototype.getImageUrl }
mimeType: { get:@prototype.getMimeType }
status: { get:@prototype.getStatus }
valid: { get:@prototype.isValid }
# BaseModel Overrides ##########################################################################
fetch: ->
if not @_client? then throw new Error 'EditableFile must be given a client to fetch with'
@_updateStatus @Status.checking
@_client.fetchFile path:@fullPath
.then (response)=>
data = response.json.data
if data.sha?
@encodedData = data.content
@sha = data.sha
@_updateStatusForNewData()
else
@_updateStatus @Status.empty
return this
save: (commitMessage)->
return w(true) if @_status is @Status.clean
return w(true) unless @encodedData?
if not @valid then return w.reject new Error "cannot save with an invalid status: #{@_status}"
commitMessage ?= "User-submitted update to #{@fileName} from #{global.hostName}"
@_client.updateFile content:@encodedData, message:commitMessage, path:@fullPath, sha:@sha
# Private Methods ##############################################################################
_updateStatus: (newStatus)->
oldStatus = @_status
return if oldStatus is newStatus
@_status = newStatus
@trigger c.event.change, this, oldStatus, newStatus
@trigger c.event.change, this
_updateStatusForNewData: ->
switch @_status
when @Status.unknown
@_updateStatus @Status.dirty
when @Status.checking
@_updateStatus @Status.clean
when @Status.empty
@_updateStatus @Status.dirty
when @Status.clean
@_updateStatus @Status.dirty
@@ -0,0 +1,32 @@
#
# Crafting Guide - email_client.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
emailjs = require 'emailjs'
########################################################################################################################
emailjs.init 'user_sLxEH6RwPvzowjaIkfY6H'
########################################################################################################################
module.exports = class EmailClient
constructor: ->
@serviceId = 'default_service'
# Public Methods ###############################################################################
send: (template, data={})->
logger.info -> "sending email with template #{template} and data: #{JSON.stringify(data)}"
emailjs.send @serviceId, template, data
.then (response)->
logger.info -> "Email sent! status: #{response.status}, message: #{response.text}"
return status:response.status, message:response.text
.catch (error)->
logger.error -> "Email failed! error: #{error}"
throw error
+32
View File
@@ -0,0 +1,32 @@
#
# Crafting Guide - github_user.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
########################################################################################################################
module.exports = class GitHubUser extends BaseModel
constructor: (attributes={}, options={})->
attributes.avatarUrl ?= attributes.avatar_url ?= null
attributes.email ?= null
attributes.login ?= null
attributes.name ?= null
super attributes, options
# Property Methods #############################################################################
isAuthenticated: ->
return @login?
Object.defineProperties @prototype,
authenticated: {get:@prototype.isAuthenticated}
# Object Overrides #############################################################################
toString: ->
return "#{@name} (#{@login})"
+56
View File
@@ -0,0 +1,56 @@
#
# Crafting Guide - item_page.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
########################################################################################################################
module.exports = class ItemPage extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.modPack? then throw new Error 'attributes.modPack is required'
attributes.item ?= null
super attributes, options
# Property Methods #############################################################################
findComponentInItems: ->
return @_findRecipesMatching (recipe)=> recipe.requires @item.slug
findSimilarItems: ->
return null unless @item?.modVersion?
result = []
@item.modVersion.eachItemInGroup @item.group, (item)=>
result.push item
return null unless result.length > 0
return result
findRecipes: ->
return @modPack.findRecipes @item?.slug, [], alwaysFromOwningMod:true
findToolForRecipes: ->
return @_findRecipesMatching (recipe)=> recipe.requiresTool @item.slug
# Private Methods ##############################################################################
_findRecipesMatching: (isAcceptable)->
return null unless @item?
result = {}
@modPack.eachMod (mod)=>
mod.eachRecipe (recipe)=>
if isAcceptable recipe
for outputStack in recipe.output
outputItem = @modPack.findItem outputStack.itemSlug, includeDisabled:true
result[outputItem.slug] = outputItem
result = _.values result
return null unless result.length > 0
return result.sort (a, b)-> a.compareTo b
@@ -0,0 +1,89 @@
#
# Crafting Guide - item_selector.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
ItemSlug = require '../game/item_slug'
########################################################################################################################
module.exports = class ItemSelector extends BaseModel
constructor: (attributes={}, options={})->
if not options.modPack? then throw new Error 'options.modPack is required'
options.isAcceptable ?= (item)-> return true # accept everything by default
super attributes, options
@_isAcceptable = options.isAcceptable
@_maxResults = 100
@_minHintLength = 3
@_modPack = options.modPack
@_results = []
# Property Methods #############################################################################
Object.defineProperties @prototype,
hint:
get: ->
return @_hint
set: (newHint)->
oldHint = @_hint
return if newHint is oldHint
@_hint = newHint.toLowerCase()
@trigger c.event.change + ':hint', this, oldHint, newHint
@_refreshResults()
@trigger c.event.change, this
results:
get: ->
return @_results
# Private Methods ##############################################################################
_isMatch: (name, itemSlug)->
hintIndex = 0
hintLetter = @_hint[hintIndex]
name = name.toLowerCase()
for nameIndex in [0...name.length] by 1
if name.charAt(nameIndex) is hintLetter
hintIndex += 1
if hintIndex is @_hint.length
item = @_modPack.findItem itemSlug
return false unless item?
return @_isAcceptable item
hintLetter = @_hint[hintIndex]
return false
_refreshResults: ->
oldResults = @_results
newResults = {}
count = 0
logger.verbose => "Looking for items which match: #{@_hint}"
if @_hint.length >= @_minHintLength
@_modPack.eachMod (mod)=>
return if count >= @_maxResults
return unless mod.enabled
mod.eachName (name, itemSlug)=>
return if count >= @_maxResults
if @_isMatch name, itemSlug
if itemSlug.isQualified
newResults[itemSlug] = itemSlug
count += 1
newResults = _.values(newResults)
newResults.sort ItemSlug.compare
@_results = newResults
@trigger c.event.change + ':results', this, oldResults, newResults
@@ -0,0 +1,23 @@
#
# Crafting Guide - markdown_image.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
EditableFile = require './editable_file'
########################################################################################################################
module.exports = class MarkdownImage extends EditableFile
# Property Methods #############################################################################
getImageUrl: ->
if @mimeType? and @encodedData?
return "data:#{@mimeType};base64,#{@encodedData}"
else
return @fullPath
Object.defineProperties @prototype,
imageUrl: {get:@prototype.getImageUrl}
@@ -0,0 +1,95 @@
#
# Crafting Guide - markdown_image_list.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
MarkdownImage = require './markdown_image'
########################################################################################################################
module.exports = class MarkdownImageList extends BaseModel
constructor: (attributes={}, options={})->
attributes.imageBase ?= ''
attributes.markdownText ?= null
super attributes, options
@client = options.client
@_images = {}
@on c.event.change + ':imageBase', =>
for fileName, image of @_images
image.path = @imageBase
@on c.event.change + ':markdownText', => @_analyzeMarkdownText()
@_analyzeMarkdownText()
# Public Methods ###############################################################################
getFile: (fileName)->
return @_images[fileName]
fetchImages: ->
for fileName, image of @_images
if image.status is MarkdownImage::Status.unknown
image.fetch()
reset: ->
for fileName, image of @_images
@stopListening image
@_images = {}
@_analyzeMarkdownText()
# Property Methods #############################################################################
getAll: ->
fileNames = (fileName for fileName, image of @_images).sort()
result = []
for fileName in fileNames
result.push @_images[fileName]
return result
isValid: ->
for fileName, image of @_images
return false unless image.valid
return true
Object.defineProperties @prototype,
all: {get:@prototype.getAll}
valid: {get:@prototype.isValid}
# Private Methods ##############################################################################
_analyzeMarkdownText: ->
regex = /\!\[([^\]\n]*)\]\(([^\)\n]*)\)/g
newImages = {}
changed = false
if @markdownText?
while true
match = regex.exec @markdownText
break unless match?
fileName = match[2]
image = @_images[fileName]
if not image?
image = new MarkdownImage {fileName:fileName, path:@imageBase}, {client:@client}
@listenTo image, c.event.change, => @trigger c.event.change, this
@trigger c.event.add, this, image
changed = true
newImages[fileName] = image
for fileName, image of @_images
if not newImages[fileName]?
@trigger c.event.remove, this, image
changed = true
if changed
@_images = newImages
@trigger c.event.change, this
@@ -0,0 +1,65 @@
#
# Crafting Guide - markdown_image_list.test.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
MarkdownImageList = require './markdown_image_list'
########################################################################################################################
list = null
########################################################################################################################
describe 'markdown_image_list.coffee', ->
beforeEach ->
list = new MarkdownImageList {}, client:{fetchFile:sinon.stub().returns(w.promise(->))}
describe '_analyzeMarkdownText', ->
it 'finds nothing in a null string', ->
list.all.should.eql []
it 'finds nothing in an empty string', ->
list.markdownText = ''
list.all.should.eql []
it 'finds nothing when there are no images in the markdown', ->
list.markdownText = 'alpha bravo charlie delta'
list.all.should.eql []
it 'finds a single image alone', ->
list.markdownText = '![](alpha.png)'
(i.fileName for i in list.all).sort().should.eql ['alpha.png']
it 'finds a single image inside a markdown document', ->
list.markdownText = '# alpha\n bravo charlie\n\n| ![](alpha.png) |\n|:----------:|\ndelta'
(i.fileName for i in list.all).sort().should.eql ['alpha.png']
it 'finds multiple images inside a markdown document', ->
list.markdownText = '| Images |\n|:------:|\n| ![](alpha.png) |\n| ![](bravo.png) |\n| ![](charlie.png) |\n'
(i.fileName for i in list.all).sort().should.eql ['alpha.png', 'bravo.png', 'charlie.png']
it 'adds new images when markdown changes', ->
list.markdownText = '![](alpha.png)'
(i.fileName for i in list.all).sort().should.eql ['alpha.png']
list.markdownText = '![](alpha.png), ![](bravo.png)'
(i.fileName for i in list.all).sort().should.eql ['alpha.png', 'bravo.png']
it 'removes missing images when markdown changes', ->
list.markdownText = '![](alpha.png), ![](bravo.png)'
(i.fileName for i in list.all).sort().should.eql ['alpha.png', 'bravo.png']
list.markdownText = '![](alpha.png)'
(i.fileName for i in list.all).sort().should.eql ['alpha.png']
it 'does not change remaining images when still present in changed markdown', ->
list.markdownText = '![](alpha.png)'
list.all[0].testField = 'foo'
list.markdownText = '![](alpha.png) bravo'
list.all[0].testField.should.equal 'foo'
+33
View File
@@ -0,0 +1,33 @@
#
# Crafting Guide - tutorial.coffee
#
# Copyright © 2014-2016 by Redwood Labs
# All rights reserved.
#
BaseModel = require '../base_model'
########################################################################################################################
module.exports = class Tutorial extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.name?.length > 0 then throw new Error "attributes.name cannot be empty"
attributes.modSlug ?= null
attributes.officialUrl ?= null
attributes.sections ?= []
attributes.slug ?= _.slugify attributes.name
attributes.videos ?= []
super attributes, options
# Backbone.Model Overrides #####################################################################
parse: (text)->
TutorialParser = require '../parsing/tutorial_parser' # to avoid require cycles
@_parser ?= new TutorialParser model:this
@_parser.parse text
return null # prevent calling `set`
url: ->
return c.url.tutorialData modSlug:@modSlug, tutorialSlug:@slug