Refactor website with new design & structure
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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})"
|
||||
@@ -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 = ''
|
||||
(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|  |\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|  |\n|  |\n|  |\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 = ''
|
||||
(i.fileName for i in list.all).sort().should.eql ['alpha.png']
|
||||
|
||||
list.markdownText = ', '
|
||||
(i.fileName for i in list.all).sort().should.eql ['alpha.png', 'bravo.png']
|
||||
|
||||
it 'removes missing images when markdown changes', ->
|
||||
list.markdownText = ', '
|
||||
(i.fileName for i in list.all).sort().should.eql ['alpha.png', 'bravo.png']
|
||||
|
||||
list.markdownText = ''
|
||||
(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 = ''
|
||||
list.all[0].testField = 'foo'
|
||||
|
||||
list.markdownText = ' bravo'
|
||||
list.all[0].testField.should.equal 'foo'
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user