mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 09:35:39 -05:00
Refactored schemas to be in /app
This commit is contained in:
parent
059b5e9b66
commit
6fb5b59a01
29 changed files with 1429 additions and 35 deletions
|
@ -5,9 +5,11 @@ auth = require 'lib/auth'
|
|||
class CocoSchema extends Backbone.Model
|
||||
constructor: (path, args...) ->
|
||||
super(args...)
|
||||
@urlRoot = path + '/schema'
|
||||
# @urlRoot = path + '/schema'
|
||||
@schemaName = path[4..].replace '.', '_'
|
||||
@schema = require 'schemas/' + @schemaName + '_schema'
|
||||
|
||||
window.CocoSchema = CocoSchema
|
||||
# window.CocoSchema = CocoSchema.schema
|
||||
|
||||
class CocoModel extends Backbone.Model
|
||||
idAttribute: "_id"
|
||||
|
@ -18,7 +20,7 @@ class CocoModel extends Backbone.Model
|
|||
|
||||
initialize: ->
|
||||
super()
|
||||
@constructor.schema ?= new CocoSchema(@urlRoot)
|
||||
@constructor.schema ?= @urlRoot[4..].replace '.', '_'
|
||||
if not @constructor.className
|
||||
console.error("#{@} needs a className set.")
|
||||
@markToRevert()
|
||||
|
@ -65,8 +67,9 @@ class CocoModel extends Backbone.Model
|
|||
|
||||
loadSchema: ->
|
||||
return if @constructor.schema.loading
|
||||
@constructor.schema.fetch()
|
||||
@listenToOnce(@constructor.schema, 'sync', @onConstructorSync)
|
||||
@constructor.schema = require 'schemas/' + @constructor.schema + '_schema' unless @constructor.schema.loaded
|
||||
@onConstructorSync()
|
||||
# @listenToOnce(@constructor.schema, 'sync', @onConstructorSync)
|
||||
|
||||
onConstructorSync: ->
|
||||
@constructor.schema.loaded = true
|
||||
|
@ -77,7 +80,7 @@ class CocoModel extends Backbone.Model
|
|||
schema: -> return @constructor.schema
|
||||
|
||||
validate: ->
|
||||
result = tv4.validateMultiple(@attributes, @constructor.schema?.attributes or {})
|
||||
result = tv4.validateMultiple(@attributes, @constructor.schema? or {})
|
||||
if result.errors?.length
|
||||
console.log @, "got validate result with errors:", result
|
||||
return result.errors unless result.valid
|
||||
|
@ -138,11 +141,11 @@ class CocoModel extends Backbone.Model
|
|||
addSchemaDefaults: ->
|
||||
return if @addedSchemaDefaults or not @constructor.hasSchema()
|
||||
@addedSchemaDefaults = true
|
||||
for prop, defaultValue of @constructor.schema.attributes.default or {}
|
||||
for prop, defaultValue of @constructor.schema.default or {}
|
||||
continue if @get(prop)?
|
||||
#console.log "setting", prop, "to", defaultValue, "from attributes.default"
|
||||
@set prop, defaultValue
|
||||
for prop, sch of @constructor.schema.attributes.properties or {}
|
||||
for prop, sch of @constructor.schema.properties or {}
|
||||
continue if @get(prop)?
|
||||
#console.log "setting", prop, "to", sch.default, "from sch.default" if sch.default?
|
||||
@set prop, sch.default if sch.default?
|
||||
|
@ -154,7 +157,7 @@ class CocoModel extends Backbone.Model
|
|||
# returns unfetched model shells for every referenced doc in this model
|
||||
# OPTIMIZE so that when loading models, it doesn't cause the site to stutter
|
||||
data ?= @attributes
|
||||
schema ?= @schema().attributes
|
||||
schema ?= @schema()
|
||||
models = []
|
||||
|
||||
if $.isArray(data) and schema.items?
|
||||
|
|
|
@ -29,9 +29,9 @@ class SuperModel
|
|||
model.loadSchema()
|
||||
schema = model.schema()
|
||||
unless schema.loaded
|
||||
@schemas[schema.urlRoot] = schema
|
||||
@schemas[model.urlRoot] = schema
|
||||
return schema.once('sync', => @modelLoaded(model))
|
||||
refs = model.getReferencedModels(model.attributes, schema.attributes, '/', @shouldLoadProjection)
|
||||
refs = model.getReferencedModels(model.attributes, schema, '/', @shouldLoadProjection)
|
||||
refs = [] unless @mustPopulate is model or @shouldPopulate(model)
|
||||
# console.log 'Loaded', model.get('name')
|
||||
for ref, i in refs when @shouldLoadReference ref
|
||||
|
|
13
app/schemas/article_schema.coffee
Normal file
13
app/schemas/article_schema.coffee
Normal file
|
@ -0,0 +1,13 @@
|
|||
c = require './schemas'
|
||||
|
||||
ArticleSchema = c.object()
|
||||
c.extendNamedProperties ArticleSchema # name first
|
||||
|
||||
ArticleSchema.properties.body = { type: 'string', title: 'Content', format: 'markdown' }
|
||||
ArticleSchema.properties.i18n = { type: 'object', title: 'i18n', format: 'i18n', props: ['name', 'body'] }
|
||||
|
||||
c.extendBasicProperties(ArticleSchema, 'article')
|
||||
c.extendSearchableProperties(ArticleSchema)
|
||||
c.extendVersionedProperties(ArticleSchema, 'article')
|
||||
|
||||
module.exports = ArticleSchema
|
48
app/schemas/i18n_schema.coffee
Normal file
48
app/schemas/i18n_schema.coffee
Normal file
|
@ -0,0 +1,48 @@
|
|||
#this file will hold the experimental JSON schema for i18n
|
||||
c = require './schemas'
|
||||
|
||||
languageCodeArrayRegex = c.generateLanguageCodeArrayRegex()
|
||||
|
||||
|
||||
ExampleSchema = {
|
||||
title: "Example Schema",
|
||||
description:"An example schema",
|
||||
type: "object",
|
||||
properties: {
|
||||
text: {
|
||||
title: "Text",
|
||||
description: "A short message to display in the dialogue area. Markdown okay.",
|
||||
type: "string",
|
||||
maxLength: 400
|
||||
},
|
||||
i18n: {"$ref": "#/definitions/i18n"}
|
||||
},
|
||||
|
||||
definitions: {
|
||||
i18n: {
|
||||
title: "i18n",
|
||||
description: "The internationalization object",
|
||||
type: "object",
|
||||
patternProperties: {
|
||||
languageCodeArrayRegex: {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
#put the translatable properties here
|
||||
#if it is possible to not include i18n with a reference
|
||||
# to #/properties, you could just do
|
||||
properties: {"$ref":"#/properties"}
|
||||
# text: {"$ref": "#/properties/text"}
|
||||
}
|
||||
default: {
|
||||
title: "LanguageCode",
|
||||
description: "LanguageDescription"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
#define a i18n object type for each schema, then have the i18n have it's oneOf check against
|
||||
#translatable schemas of that object
|
49
app/schemas/languages.coffee
Normal file
49
app/schemas/languages.coffee
Normal file
|
@ -0,0 +1,49 @@
|
|||
# errors = require '../commons/errors'
|
||||
# log = require 'winston'
|
||||
locale = require '../locale/locale' # requiring from app; will break if we stop serving from where app lives
|
||||
|
||||
# module.exports.setup = (app) ->
|
||||
# app.all '/languages/add/:lang/:namespace', (req, res) ->
|
||||
# # Should probably store these somewhere
|
||||
# log.info "#{req.params.lang}.#{req.params.namespace} missing an i18n key:", req.body
|
||||
# res.send('')
|
||||
# res.end()
|
||||
|
||||
# app.all '/languages', (req, res) ->
|
||||
# # Now that these are in the client, not sure when we would use this, but hey
|
||||
# return errors.badMethod(res) if req.route.method isnt 'get'
|
||||
# res.send(languages)
|
||||
# return res.end()
|
||||
|
||||
languages = []
|
||||
for code, localeInfo of locale
|
||||
languages.push code: code, nativeDescription: localeInfo.nativeDescription, englishDescription: localeInfo.englishDescription
|
||||
|
||||
module.exports.languages = languages
|
||||
module.exports.languageCodes = languageCodes = (language.code for language in languages)
|
||||
module.exports.languageCodesLower = languageCodesLower = (code.toLowerCase() for code in languageCodes)
|
||||
|
||||
# Keep keys lower-case for matching and values with second subtag uppercase like i18next expects
|
||||
languageAliases =
|
||||
'en': 'en-US'
|
||||
|
||||
'zh-cn': 'zh-HANS'
|
||||
'zh-hans-cn': 'zh-HANS'
|
||||
'zh-sg': 'zh-HANS'
|
||||
'zh-hans-sg': 'zh-HANS'
|
||||
|
||||
'zh-tw': 'zh-HANT'
|
||||
'zh-hant-tw': 'zh-HANT'
|
||||
'zh-hk': 'zh-HANT'
|
||||
'zh-hant-hk': 'zh-HANT'
|
||||
'zh-mo': 'zh-HANT'
|
||||
'zh-hant-mo': 'zh-HANT'
|
||||
|
||||
module.exports.languageCodeFromAcceptedLanguages = languageCodeFromAcceptedLanguages = (acceptedLanguages) ->
|
||||
for lang in acceptedLanguages ? []
|
||||
code = languageAliases[lang.toLowerCase()]
|
||||
return code if code
|
||||
codeIndex = _.indexOf languageCodesLower, lang
|
||||
if codeIndex isnt -1
|
||||
return languageCodes[codeIndex]
|
||||
return 'en-US'
|
119
app/schemas/level_component_schema.coffee
Normal file
119
app/schemas/level_component_schema.coffee
Normal file
|
@ -0,0 +1,119 @@
|
|||
c = require './schemas'
|
||||
metaschema = require './metaschema'
|
||||
|
||||
attackSelfCode = """
|
||||
class AttacksSelf extends Component
|
||||
@className: "AttacksSelf"
|
||||
chooseAction: ->
|
||||
@attack @
|
||||
"""
|
||||
systems = [
|
||||
'action', 'ai', 'alliance', 'collision', 'combat', 'display', 'event', 'existence', 'hearing'
|
||||
'inventory', 'movement', 'programming', 'targeting', 'ui', 'vision', 'misc', 'physics', 'effect',
|
||||
'magic'
|
||||
]
|
||||
|
||||
PropertyDocumentationSchema = c.object {
|
||||
title: "Property Documentation"
|
||||
description: "Documentation entry for a property this Component will add to its Thang which other Components might
|
||||
want to also use."
|
||||
"default":
|
||||
name: "foo"
|
||||
type: "object"
|
||||
description: 'The `foo` property can satisfy all the #{spriteName}\'s foobar needs. Use it wisely.'
|
||||
required: ['name', 'type', 'description']
|
||||
},
|
||||
name: {type: 'string', title: "Name", description: "Name of the property."}
|
||||
# not actual JS types, just whatever they describe...
|
||||
type: c.shortString(title: "Type", description: "Intended type of the property.")
|
||||
description: {title: "Description", type: 'string', description: "Description of the property.", format: 'markdown', maxLength: 1000}
|
||||
args: c.array {title: "Arguments", description: "If this property has type 'function', then provide documentation for any function arguments."}, c.FunctionArgumentSchema
|
||||
owner: {title: "Owner", type: 'string', description: 'Owner of the property, like "this" or "Math".'}
|
||||
example: {title: "Example", type: 'string', description: 'An optional example code block.', format: 'javascript'}
|
||||
returns: c.object {
|
||||
title: "Return Value"
|
||||
description: 'Optional documentation of any return value.'
|
||||
required: ['type']
|
||||
default: {type: 'null'}
|
||||
},
|
||||
type: c.shortString(title: "Type", description: "Type of the return value")
|
||||
example: c.shortString(title: "Example", description: "Example return value")
|
||||
description: {title: "Description", type: 'string', description: "Description of the return value.", maxLength: 1000}
|
||||
|
||||
DependencySchema = c.object {
|
||||
title: "Component Dependency"
|
||||
description: "A Component upon which this Component depends."
|
||||
"default":
|
||||
#original: ?
|
||||
majorVersion: 0
|
||||
required: ["original", "majorVersion"]
|
||||
format: 'latest-version-reference'
|
||||
links: [{rel: "db", href: "/db/level.component/{(original)}/version/{(majorVersion)}"}]
|
||||
},
|
||||
original: c.objectId(title: "Original", description: "A reference to another Component upon which this Component depends.")
|
||||
majorVersion:
|
||||
title: "Major Version"
|
||||
description: "Which major version of the Component this Component needs."
|
||||
type: 'integer'
|
||||
minimum: 0
|
||||
|
||||
LevelComponentSchema = c.object {
|
||||
title: "Component"
|
||||
description: "A Component which can affect Thang behavior."
|
||||
required: ["system", "name", "description", "code", "dependencies", "propertyDocumentation", "language"]
|
||||
"default":
|
||||
system: "ai"
|
||||
name: "AttacksSelf"
|
||||
description: "This Component makes the Thang attack itself."
|
||||
code: attackSelfCode
|
||||
language: "coffeescript"
|
||||
dependencies: [] # TODO: should depend on something by default
|
||||
propertyDocumentation: []
|
||||
}
|
||||
c.extendNamedProperties LevelComponentSchema # let's have the name be the first property
|
||||
LevelComponentSchema.properties.name.pattern = c.classNamePattern
|
||||
_.extend LevelComponentSchema.properties,
|
||||
system:
|
||||
title: "System"
|
||||
description: "The short name of the System this Component belongs to, like \"ai\"."
|
||||
type: "string"
|
||||
"enum": systems
|
||||
"default": "ai"
|
||||
description:
|
||||
title: "Description"
|
||||
description: "A short explanation of what this Component does."
|
||||
type: "string"
|
||||
maxLength: 2000
|
||||
"default": "This Component makes the Thang attack itself."
|
||||
language:
|
||||
type: "string"
|
||||
title: "Language"
|
||||
description: "Which programming language this Component is written in."
|
||||
"enum": ["coffeescript"]
|
||||
code:
|
||||
title: "Code"
|
||||
description: "The code for this Component, as a CoffeeScript class. TODO: add link to documentation for
|
||||
how to write these."
|
||||
"default": attackSelfCode
|
||||
type: "string"
|
||||
format: "coffee"
|
||||
js:
|
||||
title: "JavaScript"
|
||||
description: "The transpiled JavaScript code for this Component"
|
||||
type: "string"
|
||||
format: "hidden"
|
||||
dependencies: c.array {title: "Dependencies", description: "An array of Components upon which this Component depends.", "default": [], uniqueItems: true}, DependencySchema
|
||||
propertyDocumentation: c.array {title: "Property Documentation", description: "An array of documentation entries for each notable property this Component will add to its Thang which other Components might want to also use.", "default": []}, PropertyDocumentationSchema
|
||||
configSchema: _.extend metaschema, {title: "Configuration Schema", description: "A schema for validating the arguments that can be passed to this Component as configuration.", default: {type: 'object', additionalProperties: false}}
|
||||
official:
|
||||
type: "boolean"
|
||||
title: "Official"
|
||||
description: "Whether this is an official CodeCombat Component."
|
||||
"default": false
|
||||
|
||||
c.extendBasicProperties LevelComponentSchema, 'level.component'
|
||||
c.extendSearchableProperties LevelComponentSchema
|
||||
c.extendVersionedProperties LevelComponentSchema, 'level.component'
|
||||
c.extendPermissionsProperties LevelComponentSchema, 'level.component'
|
||||
|
||||
module.exports = LevelComponentSchema
|
27
app/schemas/level_feedback_schema.coffee
Normal file
27
app/schemas/level_feedback_schema.coffee
Normal file
|
@ -0,0 +1,27 @@
|
|||
c = require './schemas'
|
||||
|
||||
LevelFeedbackLevelSchema = c.object {required: ['original', 'majorVersion']}, {
|
||||
original: c.objectId({})
|
||||
majorVersion: {type: 'integer', minimum: 0, default: 0}}
|
||||
|
||||
LevelFeedbackSchema = c.object {
|
||||
title: "Feedback"
|
||||
description: "Feedback on a level."
|
||||
}
|
||||
|
||||
_.extend LevelFeedbackSchema.properties,
|
||||
# denormalization
|
||||
creatorName: { type: 'string' }
|
||||
levelName: { type: 'string' }
|
||||
levelID: { type: 'string' }
|
||||
|
||||
creator: c.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}])
|
||||
created: c.date( { title: 'Created', readOnly: true })
|
||||
|
||||
level: LevelFeedbackLevelSchema
|
||||
rating: { type: 'number', minimum: 1, maximum: 5 }
|
||||
review: { type: 'string' }
|
||||
|
||||
c.extendBasicProperties LevelFeedbackSchema, 'level.feedback'
|
||||
|
||||
module.exports = LevelFeedbackSchema
|
254
app/schemas/level_schema.coffee
Normal file
254
app/schemas/level_schema.coffee
Normal file
|
@ -0,0 +1,254 @@
|
|||
c = require './schemas'
|
||||
ThangComponentSchema = require './thang_component_schema'
|
||||
|
||||
SpecificArticleSchema = c.object()
|
||||
c.extendNamedProperties SpecificArticleSchema # name first
|
||||
SpecificArticleSchema.properties.body = { type: 'string', title: 'Content', description: "The body content of the article, in Markdown.", format: 'markdown' }
|
||||
SpecificArticleSchema.displayProperty = 'name'
|
||||
|
||||
side = {title: "Side", description: "A side.", type: 'string', 'enum': ['left', 'right', 'top', 'bottom']}
|
||||
thang = {title: "Thang", description: "The name of a Thang.", type: 'string', maxLength: 30, format:'thang'}
|
||||
|
||||
eventPrereqValueTypes = ["boolean", "integer", "number", "null", "string"] # not "object" or "array"
|
||||
EventPrereqSchema = c.object {title: "Event Prerequisite", format: 'event-prereq', description: "Script requires that the value of some property on the event triggering it to meet some prerequisite.", "default": {eventProps: []}, required: ["eventProps"]},
|
||||
eventProps: c.array {'default': ["thang"], format:'event-value-chain', maxItems: 10, title: "Event Property", description: 'A chain of keys in the event, like "thang.pos.x" to access event.thang.pos.x.'}, c.shortString(title: "Property", description: "A key in the event property key chain.")
|
||||
equalTo: c.object {type: eventPrereqValueTypes, title: "==", description: "Script requires the event's property chain value to be equal to this value."}
|
||||
notEqualTo: c.object {type: eventPrereqValueTypes, title: "!=", description: "Script requires the event's property chain value to *not* be equal to this value."}
|
||||
greaterThan: {type: 'number', title: ">", description: "Script requires the event's property chain value to be greater than this value."}
|
||||
greaterThanOrEqualTo: {type: 'number', title: ">=", description: "Script requires the event's property chain value to be greater or equal to this value."}
|
||||
lessThan: {type: 'number', title: "<", description: "Script requires the event's property chain value to be less than this value."}
|
||||
lessThanOrEqualTo: {type: 'number', title: "<=", description: "Script requires the event's property chain value to be less than or equal to this value."}
|
||||
containingString: c.shortString(title: "Contains", description: "Script requires the event's property chain value to be a string containing this string.")
|
||||
notContainingString: c.shortString(title: "Does not contain", description: "Script requires the event's property chain value to *not* be a string containing this string.")
|
||||
containingRegexp: c.shortString(title: "Contains Regexp", description: "Script requires the event's property chain value to be a string containing this regular expression.")
|
||||
notContainingRegexp: c.shortString(title: "Does not contain regexp", description: "Script requires the event's property chain value to *not* be a string containing this regular expression.")
|
||||
|
||||
GoalSchema = c.object {title: "Goal", description: "A goal that the player can accomplish.", required: ["name", "id"]},
|
||||
name: c.shortString(title: "Name", description: "Name of the goal that the player will see, like \"Defeat eighteen dragons\".")
|
||||
i18n: {type: "object", format: 'i18n', props: ['name'], description: "Help translate this goal"}
|
||||
id: c.shortString(title: "ID", description: "Unique identifier for this goal, like \"defeat-dragons\".") # unique somehow?
|
||||
worldEndsAfter: {title: 'World Ends After', description: "When included, ends the world this many seconds after this goal succeeds or fails.", type: 'number', minimum: 0, exclusiveMinimum: true, maximum: 300, default: 3}
|
||||
howMany: {title: "How Many", description: "When included, require only this many of the listed goal targets instead of all of them.", type: 'integer', minimum: 1}
|
||||
hiddenGoal: {title: "Hidden", description: "Hidden goals don't show up in the goals area for the player until they're failed. (Usually they're obvious, like 'don't die'.)", 'type': 'boolean', default: false}
|
||||
team: c.shortString(title: 'Team', description: 'Name of the team this goal is for, if it is not for all of the playable teams.')
|
||||
killThangs: c.array {title: "Kill Thangs", description: "A list of Thang IDs the player should kill, or team names.", uniqueItems: true, minItems: 1, "default": ["ogres"]}, thang
|
||||
saveThangs: c.array {title: "Save Thangs", description: "A list of Thang IDs the player should save, or team names", uniqueItems: true, minItems: 1, "default": ["humans"]}, thang
|
||||
getToLocations: c.object {title: "Get To Locations", description: "Will be set off when any of the \"who\" touch any of the \"targets\" ", required: ["who", "targets"]},
|
||||
who: c.array {title: "Who", description: "The Thangs who must get to the target locations.", minItems: 1}, thang
|
||||
targets: c.array {title: "Targets", description: "The target locations to which the Thangs must get.", minItems: 1}, thang
|
||||
getAllToLocations: c.array {title: "Get all to locations", description: "Similar to getToLocations but now a specific \"who\" can have a specific \"target\", also must be used with the HowMany property for desired effect",required: ["getToLocation"]},
|
||||
c.object {title: "", description: ""},
|
||||
getToLocation: c.object {title: "Get To Locations", description: "TODO: explain", required: ["who", "targets"]},
|
||||
who: c.array {title: "Who", description: "The Thangs who must get to the target locations.", minItems: 1}, thang
|
||||
targets: c.array {title: "Targets", description: "The target locations to which the Thangs must get.", minItems: 1}, thang
|
||||
keepFromLocations: c.object {title: "Keep From Locations", description: "TODO: explain", required: ["who", "targets"]},
|
||||
who: c.array {title: "Who", description: "The Thangs who must not get to the target locations.", minItems: 1}, thang
|
||||
targets: c.array {title: "Targets", description: "The target locations to which the Thangs must not get.", minItems: 1}, thang
|
||||
keepAllFromLocations: c.array {title: "Keep ALL From Locations", description: "Similar to keepFromLocations but now a specific \"who\" can have a specific \"target\", also must be used with the HowMany property for desired effect", required: ["keepFromLocation"]},
|
||||
c.object {title: "", description: ""},
|
||||
keepFromLocation: c.object {title: "Keep From Locations", description: "TODO: explain", required: ["who", "targets"]},
|
||||
who: c.array {title: "Who", description: "The Thangs who must not get to the target locations.", minItems: 1}, thang
|
||||
targets: c.array {title: "Targets", description: "The target locations to which the Thangs must not get.", minItems: 1}, thang
|
||||
leaveOffSides: c.object {title: "Leave Off Sides", description: "Sides of the level to get some Thangs to leave across.", required: ["who", "sides"]},
|
||||
who: c.array {title: "Who", description: "The Thangs which must leave off the sides of the level.", minItems: 1}, thang
|
||||
sides: c.array {title: "Sides", description: "The sides off which the Thangs must leave.", minItems: 1}, side
|
||||
keepFromLeavingOffSides: c.object {title: "Keep From Leaving Off Sides", description: "Sides of the level to keep some Thangs from leaving across.", required: ["who", "sides"]},
|
||||
who: c.array {title: "Who", description: "The Thangs which must not leave off the sides of the level.", minItems: 1}, thang
|
||||
sides: side, {title: "Sides", description: "The sides off which the Thangs must not leave.", minItems: 1}, side
|
||||
collectThangs: c.object {title: "Collect", description: "Thangs that other Thangs must collect.", required: ["who", "targets"]},
|
||||
who: c.array {title: "Who", description: "The Thangs which must collect the target items.", minItems: 1}, thang
|
||||
targets: c.array {title: "Targets", description: "The target items which the Thangs must collect.", minItems: 1}, thang
|
||||
keepFromCollectingThangs: c.object {title: "Keep From Collecting", description: "Thangs that the player must prevent other Thangs from collecting.", required: ["who", "targets"]},
|
||||
who: c.array {title: "Who", description: "The Thangs which must not collect the target items.", minItems: 1}, thang
|
||||
targets: c.array {title: "Targets", description: "The target items which the Thangs must not collect.", minItems: 1}, thang
|
||||
|
||||
ResponseSchema = c.object {title: "Dialogue Button", description: "A button to be shown to the user with the dialogue.", required: ["text"]},
|
||||
text: {title: "Title", description: "The text that will be on the button", "default": "Okay", type: 'string', maxLength: 30}
|
||||
channel: c.shortString(title: "Channel", format: 'event-channel', description: 'Channel that this event will be broadcast over, like "level-set-playing".')
|
||||
event: {type: 'object', title: "Event", description: "Event that will be broadcast when this button is pressed, like {playing: true}."}
|
||||
buttonClass: c.shortString(title: "Button Class", description: 'CSS class that will be added to the button, like "btn-primary".')
|
||||
i18n: {type: "object", format: 'i18n', props: ['text'], description: "Help translate this button"}
|
||||
|
||||
PointSchema = c.object {title: "Point", description: "An {x, y} coordinate point.", format: "point2d", required: ["x", "y"]},
|
||||
x: {title: "x", description: "The x coordinate.", type: "number", "default": 15}
|
||||
y: {title: "y", description: "The y coordinate.", type: "number", "default": 20}
|
||||
|
||||
SpriteCommandSchema = c.object {title: "Thang Command", description: "Make a target Thang move or say something, or select/deselect it.", required: ["id"], default: {id: "Captain Anya"}},
|
||||
id: thang
|
||||
select: {title: "Select", description: "Select or deselect this Thang.", type: 'boolean'}
|
||||
say: c.object {title: "Say", description: "Make this Thang say a message.", required: ["text"]},
|
||||
blurb: c.shortString(title: "Blurb", description: "A very short message to display above this Thang's head. Plain text.", maxLength: 50)
|
||||
mood: c.shortString(title: "Mood", description: "The mood with which the Thang speaks.", "enum": ["explain", "debrief", "congrats", "attack", "joke", "tip", "alarm"], "default": "explain")
|
||||
text: {title: "Text", description: "A short message to display in the dialogue area. Markdown okay.", type: "string", maxLength: 400}
|
||||
sound: c.object {title: "Sound", description: "A dialogue sound file to accompany the message.", required: ["mp3", "ogg"]},
|
||||
mp3: c.shortString(title: "MP3", format: 'sound-file')
|
||||
ogg: c.shortString(title: "OGG", format: 'sound-file')
|
||||
preload: {title: "Preload", description: "Whether to load this sound file before the level can begin (typically for the first dialogue of a level).", type: 'boolean', "default": false}
|
||||
responses: c.array {title: "Buttons", description: "An array of buttons to include with the dialogue, with which the user can respond."}, ResponseSchema
|
||||
i18n: {type: "object", format: 'i18n', props: ['blurb', 'text'], description: "Help translate this message"}
|
||||
move: c.object {title: "Move", description: "Tell the Thang to move.", required: ['target'], default: {target: {x: 20, y: 20}, duration: 500}},
|
||||
target: _.extend _.cloneDeep(PointSchema), {title: 'Target', description: 'Target point to which the Thang will move.'}
|
||||
duration: {title: "Duration", description: "Number of milliseconds over which to move, or 0 for an instant move.", type: 'integer', minimum: 0, default: 500, format: 'milliseconds'}
|
||||
|
||||
NoteGroupSchema = c.object {title: "Note Group", description: "A group of notes that should be sent out as a result of this script triggering.", displayProperty: "name"},
|
||||
name: {title: "Name", description: "Short name describing the script, like \"Anya greets the player\", for your convenience.", type: "string"}
|
||||
dom: c.object {title: "DOM", description: "Manipulate things in the play area DOM, outside of the level area canvas."},
|
||||
focus: c.shortString(title: "Focus", description: "Set the window focus to this DOM selector string.")
|
||||
showVictory: {
|
||||
title: "Show Victory",
|
||||
description: "Show the done button and maybe also the victory modal.",
|
||||
enum: [true, 'Done Button', 'Done Button And Modal'] # deprecate true, same as 'done_button_and_modal'
|
||||
}
|
||||
highlight: c.object {title: "Highlight", description: "Highlight the target DOM selector string with a big arrow."},
|
||||
target: c.shortString(title: "Target", description: "Target highlight element DOM selector string.")
|
||||
delay: {type: 'integer', minimum: 0, title: "Delay", description: "Show the highlight after this many milliseconds. Doesn't affect the dim shade cutout highlight method."}
|
||||
offset: _.extend _.cloneDeep(PointSchema), {title: 'Offset', description: 'Pointing arrow tip offset in pixels from the default target.', format: null}
|
||||
rotation: {type: 'number', minimum: 0, title: "Rotation", description: "Rotation of the pointing arrow, in radians. PI / 2 points left, PI points up, etc."}
|
||||
sides: c.array {title: "Sides", description: "Which sides of the target element to point at."}, {type: 'string', 'enum': ['left', 'right', 'top', 'bottom'], title: "Side", description: "A side of the target element to point at."}
|
||||
lock: {title: "Lock", description: "Whether the interface should be locked so that the player's focus is on the script, or specific areas to lock.", type: ['boolean', 'array'], items: {type: 'string', enum: ['surface', 'editor', 'palette', 'hud', 'playback', 'playback-hover', 'level', ]}}
|
||||
letterbox: {type: 'boolean', title: 'Letterbox', description:'Turn letterbox mode on or off. Disables surface and playback controls.'}
|
||||
|
||||
goals: c.object {title: "Goals", description: "Add or remove goals for the player to complete in the level."},
|
||||
add: c.array {title: "Add", description: "Add these goals."}, GoalSchema
|
||||
remove: c.array {title: "Remove", description: "Remove these goals."}, GoalSchema
|
||||
|
||||
playback: c.object {title: "Playback", description: "Control the playback of the level."},
|
||||
playing: {type: 'boolean', title: "Set Playing", description: "Set whether playback is playing or paused."}
|
||||
scrub: c.object {title: "Scrub", description: "Scrub the level playback time to a certain point.", default: {offset: 2, duration: 1000, toRatio: 0.5}},
|
||||
offset: {type: 'integer', title: "Offset", description: "Number of frames by which to adjust the scrub target time.", default: 2}
|
||||
duration: {type: 'integer', title: "Duration", description: "Number of milliseconds over which to scrub time.", minimum: 0, format: 'milliseconds'}
|
||||
toRatio: {type: 'number', title: "To Progress Ratio", description: "Set playback time to a target playback progress ratio.", minimum: 0, maximum: 1}
|
||||
toTime: {type: 'number', title: "To Time", description: "Set playback time to a target playback point, in seconds.", minimum: 0}
|
||||
toGoal: c.shortString(title: "To Goal", description: "Set playback time to when this goal was achieved. (TODO: not implemented.)")
|
||||
|
||||
script: c.object {title: "Script", description: "Extra configuration for this action group."},
|
||||
duration: {type: 'integer', minimum: 0, title: "Duration", description: "How long this script should last in milliseconds. 0 for indefinite.", format: 'milliseconds'}
|
||||
skippable: {type: 'boolean', title: "Skippable", description: "Whether this script shouldn't bother firing when the player skips past all current scripts."}
|
||||
beforeLoad: {type: 'boolean', title: "Before Load", description: "Whether this script should fire before the level is finished loading."}
|
||||
|
||||
sprites: c.array {title: "Sprites", description: "Commands to issue to Sprites on the Surface."}, SpriteCommandSchema
|
||||
|
||||
surface: c.object {title: "Surface", description: "Commands to issue to the Surface itself."},
|
||||
focus: c.object {title: "Camera", description: "Focus the camera on a specific point on the Surface.", format:'viewport'},
|
||||
target: {anyOf: [PointSchema, thang, {type: 'null'}], title: "Target", description: "Where to center the camera view."}
|
||||
zoom: {type: 'number', minimum: 0, exclusiveMinimum: true, maximum: 64, title: "Zoom", description: "What zoom level to use."}
|
||||
duration: {type:'number', minimum: 0, title: "Duration", description: "in ms"}
|
||||
bounds: c.array {title:'Boundary', maxItems: 2, minItems: 2, default:[{x:0,y:0}, {x:46, y:39}], format: 'bounds'}, PointSchema
|
||||
isNewDefault: {type:'boolean', format: 'hidden', title: "New Default", description: 'Set this as new default zoom once scripts end.'} # deprecated
|
||||
highlight: c.object {title: "Highlight", description: "Highlight specific Sprites on the Surface."},
|
||||
targets: c.array {title: "Targets", description: "Thang IDs of target Sprites to highlight."}, thang
|
||||
delay: {type: 'integer', minimum: 0, title: "Delay", description: "Delay in milliseconds before the highlight appears."}
|
||||
lockSelect: {type: 'boolean', title: "Lock Select", description: "Whether to lock Sprite selection so that the player can't select/deselect anything."}
|
||||
|
||||
sound: c.object {title: "Sound", description: "Commands to control sound playback."},
|
||||
suppressSelectionSounds: {type: "boolean", title: "Suppress Selection Sounds", description: "Whether to suppress selection sounds made from clicking on Thangs."}
|
||||
music: c.object { title: "Music", description: "Control music playing"},
|
||||
play: { title: "Play", type: "boolean" }
|
||||
file: c.shortString(title: "File", enum:['/music/music_level_1','/music/music_level_2','/music/music_level_3','/music/music_level_4','/music/music_level_5'])
|
||||
|
||||
ScriptSchema = c.object {
|
||||
title: "Script"
|
||||
description: 'A script fires off a chain of notes to interact with the game when a certain event triggers it.'
|
||||
required: ["channel"]
|
||||
'default': {channel: "world:won", noteChain: []}
|
||||
},
|
||||
id: c.shortString(title: "ID", description: "A unique ID that other scripts can rely on in their Happens After prereqs, for sequencing.") # uniqueness?
|
||||
channel: c.shortString(title: "Event", format: 'event-channel', description: 'Event channel this script might trigger for, like "world:won".')
|
||||
eventPrereqs: c.array {title: "Event Checks", description: "Logical checks on the event for this script to trigger.", format:'event-prereqs'}, EventPrereqSchema
|
||||
repeats: {title: "Repeats", description: "Whether this script can trigger more than once during a level.", enum: [true, false, 'session'], "default": false}
|
||||
scriptPrereqs: c.array {title: "Happens After", description: "Scripts that need to fire first."},
|
||||
c.shortString(title: "ID", description: "A unique ID of a script.")
|
||||
notAfter: c.array {title: "Not After", description: "Do not run this script if any of these scripts have run."},
|
||||
c.shortString(title: "ID", description: "A unique ID of a script.")
|
||||
noteChain: c.array {title: "Actions", description: "A list of things that happen when this script triggers."}, NoteGroupSchema
|
||||
|
||||
LevelThangSchema = c.object {
|
||||
title: "Thang",
|
||||
description: "Thangs are any units, doodads, or abstract things that you use to build the level. (\"Thing\" was too confusing to say.)",
|
||||
format: "thang"
|
||||
required: ["id", "thangType", "components"]
|
||||
'default':
|
||||
id: "Boris"
|
||||
thangType: "Soldier"
|
||||
components: []
|
||||
},
|
||||
id: thang # TODO: figure out if we can make this unique and how to set dynamic defaults
|
||||
# TODO: split thangType into "original" and "majorVersion" like the rest for consistency
|
||||
thangType: c.objectId(links: [{rel: "db", href: "/db/thang.type/{($)}/version"}], title: "Thang Type", description: "A reference to the original Thang template being configured.", format: 'thang-type')
|
||||
components: c.array {title: "Components", description: "Thangs are configured by changing the Components attached to them.", uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema # TODO: uniqueness should be based on "original", not whole thing
|
||||
|
||||
LevelSystemSchema = c.object {
|
||||
title: "System"
|
||||
description: "Configuration for a System that this Level uses."
|
||||
format: 'level-system'
|
||||
required: ['original', 'majorVersion']
|
||||
'default':
|
||||
majorVersion: 0
|
||||
config: {}
|
||||
links: [{rel: "db", href: "/db/level.system/{(original)}/version/{(majorVersion)}"}]
|
||||
},
|
||||
original: c.objectId(title: "Original", description: "A reference to the original System being configured.", format: "hidden")
|
||||
config: c.object {title: "Configuration", description: "System-specific configuration properties.", additionalProperties: true, format: 'level-system-configuration'}
|
||||
majorVersion: {title: "Major Version", description: "Which major version of the System is being used.", type: 'integer', minimum: 0, default: 0, format: "hidden"}
|
||||
|
||||
GeneralArticleSchema = c.object {
|
||||
title: "Article"
|
||||
description: "Reference to a general documentation article."
|
||||
required: ['original']
|
||||
format: 'latest-version-reference'
|
||||
'default':
|
||||
original: null
|
||||
majorVersion: 0
|
||||
links: [{rel: "db", href: "/db/article/{(original)}/version/{(majorVersion)}"}]
|
||||
},
|
||||
original: c.objectId(title: "Original", description: "A reference to the original Article.")#, format: "hidden") # hidden?
|
||||
majorVersion: {title: "Major Version", description: "Which major version of the Article is being used.", type: 'integer', minimum: 0}#, format: "hidden"} # hidden?
|
||||
|
||||
LevelSchema = c.object {
|
||||
title: "Level"
|
||||
description: "A spectacular level which will delight and educate its stalwart players with the sorcery of coding."
|
||||
required: ["name", "description", "scripts", "thangs", "documentation"]
|
||||
'default':
|
||||
name: "Ineffable Wizardry"
|
||||
description: "This level is indescribably flarmy."
|
||||
documentation: {specificArticles: [], generalArticles: []}
|
||||
scripts: []
|
||||
thangs: []
|
||||
}
|
||||
c.extendNamedProperties LevelSchema # let's have the name be the first property
|
||||
_.extend LevelSchema.properties,
|
||||
description: {title: "Description", description: "A short explanation of what this level is about.", type: "string", maxLength: 65536, "default": "This level is indescribably flarmy!", format: 'markdown'}
|
||||
documentation: c.object {title: "Documentation", description: "Documentation articles relating to this level.", required: ["specificArticles", "generalArticles"], 'default': {specificArticles: [], generalArticles: []}},
|
||||
specificArticles: c.array {title: "Specific Articles", description: "Specific documentation articles that live only in this level.", uniqueItems: true, "default": []}, SpecificArticleSchema
|
||||
generalArticles: c.array {title: "General Articles", description: "General documentation articles that can be linked from multiple levels.", uniqueItems: true, "default": []}, GeneralArticleSchema
|
||||
background: c.objectId({format: 'hidden'})
|
||||
nextLevel: {
|
||||
type:'object',
|
||||
links: [{rel: "extra", href: "/db/level/{($)}"}, {rel:'db', href: "/db/level/{(original)}/version/{(majorVersion)}"}],
|
||||
format: 'latest-version-reference',
|
||||
title: "Next Level",
|
||||
description: "Reference to the next level players will player after beating this one."
|
||||
}
|
||||
scripts: c.array {title: "Scripts", description: "An array of scripts that trigger based on what the player does and affect things outside of the core level simulation.", "default": []}, ScriptSchema
|
||||
thangs: c.array {title: "Thangs", description: "An array of Thangs that make up the level.", "default": []}, LevelThangSchema
|
||||
systems: c.array {title: "Systems", description: "Levels are configured by changing the Systems attached to them.", uniqueItems: true, default: []}, LevelSystemSchema # TODO: uniqueness should be based on "original", not whole thing
|
||||
victory: c.object {title: "Victory Screen", default: {}, properties: {'body': {type: 'string', format: 'markdown', title: 'Body Text', description: 'Inserted into the Victory Modal once this level is complete. Tell the player they did a good job and what they accomplished!'}, i18n: {type: "object", format: 'i18n', props: ['body'], description: "Help translate this victory message"}}}
|
||||
i18n: {type: "object", format: 'i18n', props: ['name', 'description'], description: "Help translate this level"}
|
||||
icon: { type: 'string', format: 'image-file', title: 'Icon' }
|
||||
goals: c.array {title: 'Goals', description: 'An array of goals which are visible to the player and can trigger scripts.'}, GoalSchema
|
||||
type: c.shortString(title: "Type", description: "What kind of level this is.", "enum": ['campaign', 'ladder', 'ladder-tutorial'])
|
||||
showsGuide: c.shortString(title: "Shows Guide", description: "If the guide is shown at the beginning of the level.", "enum": ['first-time', 'always'])
|
||||
|
||||
c.extendBasicProperties LevelSchema, 'level'
|
||||
c.extendSearchableProperties LevelSchema
|
||||
c.extendVersionedProperties LevelSchema, 'level'
|
||||
c.extendPermissionsProperties LevelSchema, 'level'
|
||||
|
||||
module.exports = LevelSchema
|
||||
|
||||
# To test:
|
||||
# 1: Copy the schema from http://localhost:3000/db/level/schema
|
||||
# 2. Open up the Treema demo page http://localhost:9090/demo.html
|
||||
# 3. tv4.addSchema(metaschema.id, metaschema)
|
||||
# 4. S = <paste big schema here>
|
||||
# 5. tv4.validateMultiple(S, metaschema) and look for errors
|
213
app/schemas/level_session_schema.coffee
Normal file
213
app/schemas/level_session_schema.coffee
Normal file
|
@ -0,0 +1,213 @@
|
|||
c = require './schemas'
|
||||
|
||||
LevelSessionPlayerSchema = c.object
|
||||
id: c.objectId
|
||||
links: [
|
||||
{
|
||||
rel: 'extra'
|
||||
href: "/db/user/{($)}"
|
||||
}
|
||||
]
|
||||
time:
|
||||
type: 'Number'
|
||||
changes:
|
||||
type: 'Number'
|
||||
|
||||
|
||||
LevelSessionLevelSchema = c.object {required: ['original', 'majorVersion']},
|
||||
original: c.objectId({})
|
||||
majorVersion:
|
||||
type: 'integer'
|
||||
minimum: 0
|
||||
default: 0
|
||||
|
||||
|
||||
LevelSessionSchema = c.object
|
||||
title: "Session"
|
||||
description: "A single session for a given level."
|
||||
|
||||
|
||||
_.extend LevelSessionSchema.properties,
|
||||
# denormalization
|
||||
creatorName:
|
||||
type: 'string'
|
||||
levelName:
|
||||
type: 'string'
|
||||
levelID:
|
||||
type: 'string'
|
||||
multiplayer:
|
||||
type: 'boolean'
|
||||
creator: c.objectId
|
||||
links:
|
||||
[
|
||||
{
|
||||
rel: 'extra'
|
||||
href: "/db/user/{($)}"
|
||||
}
|
||||
]
|
||||
created: c.date
|
||||
title: 'Created'
|
||||
readOnly: true
|
||||
|
||||
changed: c.date
|
||||
title: 'Changed'
|
||||
readOnly: true
|
||||
|
||||
team: c.shortString()
|
||||
level: LevelSessionLevelSchema
|
||||
|
||||
screenshot:
|
||||
type: 'string'
|
||||
|
||||
state: c.object {},
|
||||
complete:
|
||||
type: 'boolean'
|
||||
scripts: c.object {},
|
||||
ended:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
type: 'number'
|
||||
currentScript:
|
||||
type: [
|
||||
'null'
|
||||
'string'
|
||||
]
|
||||
currentScriptOffset:
|
||||
type: 'number'
|
||||
|
||||
selected:
|
||||
type: [
|
||||
'null'
|
||||
'string'
|
||||
]
|
||||
playing:
|
||||
type: 'boolean'
|
||||
frame:
|
||||
type: 'number'
|
||||
thangs:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
title: 'Thang'
|
||||
type: 'object'
|
||||
properties:
|
||||
methods:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
title: 'Thang Method'
|
||||
type: 'object'
|
||||
properties:
|
||||
metrics:
|
||||
type: 'object'
|
||||
source:
|
||||
type: 'string'
|
||||
|
||||
# TODO: specify this more
|
||||
code:
|
||||
type: 'object'
|
||||
|
||||
teamSpells:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
type: 'array'
|
||||
|
||||
players:
|
||||
type: 'object'
|
||||
|
||||
chat:
|
||||
type: 'array'
|
||||
|
||||
meanStrength:
|
||||
type: 'number'
|
||||
|
||||
standardDeviation:
|
||||
type:'number'
|
||||
minimum: 0
|
||||
|
||||
totalScore:
|
||||
type: 'number'
|
||||
|
||||
submitted:
|
||||
type: 'boolean'
|
||||
|
||||
submitDate: c.date
|
||||
title: 'Submitted'
|
||||
|
||||
submittedCode:
|
||||
type: 'object'
|
||||
|
||||
isRanking:
|
||||
type: 'boolean'
|
||||
description: 'Whether this session is still in the first ranking chain after being submitted.'
|
||||
|
||||
unsubscribed:
|
||||
type: 'boolean'
|
||||
description: 'Whether the player has opted out of receiving email updates about ladder rankings for this session.'
|
||||
|
||||
numberOfWinsAndTies:
|
||||
type: 'number'
|
||||
|
||||
numberOfLosses:
|
||||
type: 'number'
|
||||
|
||||
scoreHistory:
|
||||
type: 'array'
|
||||
title: 'Score History'
|
||||
description: 'A list of objects representing the score history of a session'
|
||||
items:
|
||||
title: 'Score History Point'
|
||||
description: 'An array with the format [unix timestamp, totalScore]'
|
||||
type: 'array'
|
||||
items:
|
||||
type: 'number'
|
||||
|
||||
matches:
|
||||
type: 'array'
|
||||
title: 'Matches'
|
||||
description: 'All of the matches a submitted session has played in its current state.'
|
||||
items:
|
||||
type: 'object'
|
||||
properties:
|
||||
date: c.date
|
||||
title: 'Date computed'
|
||||
description: 'The date a match was computed.'
|
||||
metrics:
|
||||
type: 'object'
|
||||
title: 'Metrics'
|
||||
description: 'Various information about the outcome of a match.'
|
||||
properties:
|
||||
rank:
|
||||
title: 'Rank'
|
||||
description: 'A 0-indexed ranking representing the player\'s standing in the outcome of a match'
|
||||
type: 'number'
|
||||
opponents:
|
||||
type: 'array'
|
||||
title: 'Opponents'
|
||||
description: 'An array containing information about the opponents\' sessions in a given match.'
|
||||
items:
|
||||
type: 'object'
|
||||
properties:
|
||||
sessionID:
|
||||
title: 'Opponent Session ID'
|
||||
description: 'The session ID of an opponent.'
|
||||
type: ['object', 'string']
|
||||
userID:
|
||||
title: 'Opponent User ID'
|
||||
description: 'The user ID of an opponent'
|
||||
type: ['object','string']
|
||||
metrics:
|
||||
type: 'object'
|
||||
properties:
|
||||
rank:
|
||||
title: 'Opponent Rank'
|
||||
description: 'The opponent\'s ranking in a given match'
|
||||
type: 'number'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
c.extendBasicProperties LevelSessionSchema, 'level.session'
|
||||
c.extendPermissionsProperties LevelSessionSchema, 'level.session'
|
||||
|
||||
module.exports = LevelSessionSchema
|
106
app/schemas/level_system_schema.coffee
Normal file
106
app/schemas/level_system_schema.coffee
Normal file
|
@ -0,0 +1,106 @@
|
|||
c = require './schemas'
|
||||
metaschema = require './metaschema'
|
||||
|
||||
jitterSystemCode = """
|
||||
class Jitter extends System
|
||||
constructor: (world, config) ->
|
||||
super world, config
|
||||
@idlers = @addRegistry (thang) -> thang.exists and thang.acts and thang.moves and thang.action is 'idle'
|
||||
|
||||
update: ->
|
||||
# We return a simple numeric hash that will combine to a frame hash
|
||||
# help us determine whether this frame has changed in resimulations.
|
||||
hash = 0
|
||||
for thang in @idlers
|
||||
hash += thang.pos.x += 0.5 - Math.random()
|
||||
hash += thang.pos.y += 0.5 - Math.random()
|
||||
thang.hasMoved = true
|
||||
return hash
|
||||
"""
|
||||
|
||||
PropertyDocumentationSchema = c.object {
|
||||
title: "Property Documentation"
|
||||
description: "Documentation entry for a property this System will add to its Thang which other Systems
|
||||
might want to also use."
|
||||
"default":
|
||||
name: "foo"
|
||||
type: "object"
|
||||
description: "This System provides a 'foo' property to satisfy all one's foobar needs. Use it wisely."
|
||||
required: ['name', 'type', 'description']
|
||||
},
|
||||
name: {type: 'string', pattern: c.identifierPattern, title: "Name", description: "Name of the property."}
|
||||
# not actual JS types, just whatever they describe...
|
||||
type: c.shortString(title: "Type", description: "Intended type of the property.")
|
||||
description: {type: 'string', description: "Description of the property.", maxLength: 1000}
|
||||
args: c.array {title: "Arguments", description: "If this property has type 'function', then provide documentation for any function arguments."}, c.FunctionArgumentSchema
|
||||
|
||||
DependencySchema = c.object {
|
||||
title: "System Dependency"
|
||||
description: "A System upon which this System depends."
|
||||
"default":
|
||||
#original: ?
|
||||
majorVersion: 0
|
||||
required: ["original", "majorVersion"]
|
||||
format: 'latest-version-reference'
|
||||
links: [{rel: "db", href: "/db/level.system/{(original)}/version/{(majorVersion)}"}]
|
||||
},
|
||||
original: c.objectId(title: "Original", description: "A reference to another System upon which this System depends.")
|
||||
majorVersion:
|
||||
title: "Major Version"
|
||||
description: "Which major version of the System this System needs."
|
||||
type: 'integer'
|
||||
minimum: 0
|
||||
|
||||
LevelSystemSchema = c.object {
|
||||
title: "System"
|
||||
description: "A System which can affect Level behavior."
|
||||
required: ["name", "description", "code", "dependencies", "propertyDocumentation", "language"]
|
||||
"default":
|
||||
name: "JitterSystem"
|
||||
description: "This System makes all idle, movable Thangs jitter around."
|
||||
code: jitterSystemCode
|
||||
language: "coffeescript"
|
||||
dependencies: [] # TODO: should depend on something by default
|
||||
propertyDocumentation: []
|
||||
}
|
||||
c.extendNamedProperties LevelSystemSchema # let's have the name be the first property
|
||||
LevelSystemSchema.properties.name.pattern = c.classNamePattern
|
||||
_.extend LevelSystemSchema.properties,
|
||||
description:
|
||||
title: "Description"
|
||||
description: "A short explanation of what this System does."
|
||||
type: "string"
|
||||
maxLength: 2000
|
||||
"default": "This System doesn't do anything yet."
|
||||
language:
|
||||
type: "string"
|
||||
title: "Language"
|
||||
description: "Which programming language this System is written in."
|
||||
"enum": ["coffeescript"]
|
||||
code:
|
||||
title: "Code"
|
||||
description: "The code for this System, as a CoffeeScript class. TODO: add link to documentation
|
||||
for how to write these."
|
||||
"default": jitterSystemCode
|
||||
type: "string"
|
||||
format: "coffee"
|
||||
js:
|
||||
title: "JavaScript"
|
||||
description: "The transpiled JavaScript code for this System"
|
||||
type: "string"
|
||||
format: "hidden"
|
||||
dependencies: c.array {title: "Dependencies", description: "An array of Systems upon which this System depends.", "default": [], uniqueItems: true}, DependencySchema
|
||||
propertyDocumentation: c.array {title: "Property Documentation", description: "An array of documentation entries for each notable property this System will add to its Level which other Systems might want to also use.", "default": []}, PropertyDocumentationSchema
|
||||
configSchema: _.extend metaschema, {title: "Configuration Schema", description: "A schema for validating the arguments that can be passed to this System as configuration.", default: {type: 'object', additionalProperties: false}}
|
||||
official:
|
||||
type: "boolean"
|
||||
title: "Official"
|
||||
description: "Whether this is an official CodeCombat System."
|
||||
"default": false
|
||||
|
||||
c.extendBasicProperties LevelSystemSchema, 'level.system'
|
||||
c.extendSearchableProperties LevelSystemSchema
|
||||
c.extendVersionedProperties LevelSystemSchema, 'level.system'
|
||||
c.extendPermissionsProperties LevelSystemSchema, 'level.system'
|
||||
|
||||
module.exports = LevelSystemSchema
|
132
app/schemas/metaschema.coffee
Normal file
132
app/schemas/metaschema.coffee
Normal file
|
@ -0,0 +1,132 @@
|
|||
# The JSON Schema Core/Validation Meta-Schema, but with titles and descriptions added to make it easier to edit in Treema, and in CoffeeScript
|
||||
|
||||
module.exports =
|
||||
id: "metaschema"
|
||||
displayProperty: "title"
|
||||
$schema: "http://json-schema.org/draft-04/schema#"
|
||||
title: "Schema"
|
||||
description: "Core schema meta-schema"
|
||||
definitions:
|
||||
schemaArray:
|
||||
type: "array"
|
||||
minItems: 1
|
||||
items: { $ref: "#" }
|
||||
title: "Array of Schemas"
|
||||
"default": [{}]
|
||||
positiveInteger:
|
||||
type: "integer"
|
||||
minimum: 0
|
||||
title: "Positive Integer"
|
||||
positiveIntegerDefault0:
|
||||
allOf: [ { $ref: "#/definitions/positiveInteger" }, { "default": 0 } ]
|
||||
simpleTypes:
|
||||
title: "Single Type"
|
||||
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
|
||||
stringArray:
|
||||
type: "array"
|
||||
items: { type: "string" }
|
||||
minItems: 1
|
||||
uniqueItems: true
|
||||
title: "String Array"
|
||||
"default": ['']
|
||||
type: "object"
|
||||
properties:
|
||||
id:
|
||||
type: "string"
|
||||
format: "uri"
|
||||
$schema:
|
||||
type: "string"
|
||||
format: "uri"
|
||||
"default": "http://json-schema.org/draft-04/schema#"
|
||||
title:
|
||||
type: "string"
|
||||
description:
|
||||
type: "string"
|
||||
"default": {}
|
||||
multipleOf:
|
||||
type: "number"
|
||||
minimum: 0
|
||||
exclusiveMinimum: true
|
||||
maximum:
|
||||
type: "number"
|
||||
exclusiveMaximum:
|
||||
type: "boolean"
|
||||
"default": false
|
||||
minimum:
|
||||
type: "number"
|
||||
exclusiveMinimum:
|
||||
type: "boolean"
|
||||
"default": false
|
||||
maxLength: { $ref: "#/definitions/positiveInteger" }
|
||||
minLength: { $ref: "#/definitions/positiveIntegerDefault0" }
|
||||
pattern:
|
||||
type: "string"
|
||||
format: "regex"
|
||||
additionalItems:
|
||||
anyOf: [
|
||||
{ type: "boolean", "default": false }
|
||||
{ $ref: "#" }
|
||||
]
|
||||
items:
|
||||
anyOf: [
|
||||
{ $ref: "#" }
|
||||
{ $ref: "#/definitions/schemaArray" }
|
||||
]
|
||||
"default": {}
|
||||
maxItems: { $ref: "#/definitions/positiveInteger" }
|
||||
minItems: { $ref: "#/definitions/positiveIntegerDefault0" }
|
||||
uniqueItems:
|
||||
type: "boolean"
|
||||
"default": false
|
||||
maxProperties: { $ref: "#/definitions/positiveInteger" }
|
||||
minProperties: { $ref: "#/definitions/positiveIntegerDefault0" }
|
||||
required: { $ref: "#/definitions/stringArray" }
|
||||
additionalProperties:
|
||||
anyOf: [
|
||||
{ type: "boolean", "default": true }
|
||||
{ $ref: "#" }
|
||||
]
|
||||
"default": {}
|
||||
definitions:
|
||||
type: "object"
|
||||
additionalProperties: { $ref: "#" }
|
||||
"default": {}
|
||||
properties:
|
||||
type: "object"
|
||||
additionalProperties: { $ref: "#" }
|
||||
"default": {}
|
||||
patternProperties:
|
||||
type: "object"
|
||||
additionalProperties: { $ref: "#" }
|
||||
"default": {}
|
||||
dependencies:
|
||||
type: "object"
|
||||
additionalProperties:
|
||||
anyOf: [
|
||||
{ $ref: "#" }
|
||||
{ $ref: "#/definitions/stringArray" }
|
||||
]
|
||||
"enum":
|
||||
type: "array"
|
||||
minItems: 1
|
||||
uniqueItems: true
|
||||
"default": ['']
|
||||
type:
|
||||
anyOf: [
|
||||
{ $ref: "#/definitions/simpleTypes" }
|
||||
{
|
||||
type: "array"
|
||||
items: { $ref: "#/definitions/simpleTypes" }
|
||||
minItems: 1
|
||||
uniqueItems: true
|
||||
title: "Array of Types"
|
||||
"default": ['string']
|
||||
}]
|
||||
allOf: { $ref: "#/definitions/schemaArray" }
|
||||
anyOf: { $ref: "#/definitions/schemaArray" }
|
||||
oneOf: { $ref: "#/definitions/schemaArray" }
|
||||
not: { $ref: "#" }
|
||||
dependencies:
|
||||
exclusiveMaximum: [ "maximum" ]
|
||||
exclusiveMinimum: [ "minimum" ]
|
||||
"default": {}
|
158
app/schemas/schemas.coffee
Normal file
158
app/schemas/schemas.coffee
Normal file
|
@ -0,0 +1,158 @@
|
|||
#language imports
|
||||
Language = require './languages'
|
||||
# schema helper methods
|
||||
|
||||
me = module.exports
|
||||
|
||||
combine = (base, ext) ->
|
||||
return base unless ext?
|
||||
return _.extend(base, ext)
|
||||
|
||||
urlPattern = '^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_=]*)?$'
|
||||
|
||||
# Common schema properties
|
||||
me.object = (ext, props) -> combine {type: 'object', additionalProperties: false, properties: props or {}}, ext
|
||||
me.array = (ext, items) -> combine {type: 'array', items: items or {}}, ext
|
||||
me.shortString = (ext) -> combine({type: 'string', maxLength: 100}, ext)
|
||||
me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext)
|
||||
me.date = (ext) -> combine({type: 'string', format: 'date-time'}, ext)
|
||||
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
|
||||
me.objectId = (ext) -> schema = combine({type: ['object', 'string'] }, ext)
|
||||
me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext)
|
||||
|
||||
PointSchema = me.object {title: "Point", description: "An {x, y} coordinate point.", format: "point2d", required: ["x", "y"]},
|
||||
x: {title: "x", description: "The x coordinate.", type: "number", "default": 15}
|
||||
y: {title: "y", description: "The y coordinate.", type: "number", "default": 20}
|
||||
|
||||
me.point2d = (ext) -> combine(_.cloneDeep(PointSchema), ext)
|
||||
|
||||
SoundSchema = me.object { format: 'sound' },
|
||||
mp3: { type: 'string', format: 'sound-file' }
|
||||
ogg: { type: 'string', format: 'sound-file' }
|
||||
|
||||
me.sound = (props) ->
|
||||
obj = _.cloneDeep(SoundSchema)
|
||||
obj.properties[prop] = props[prop] for prop of props
|
||||
obj
|
||||
|
||||
ColorConfigSchema = me.object { format: 'color-sound' },
|
||||
hue: { format: 'range', type: 'number', minimum: 0, maximum: 1 }
|
||||
saturation: { format: 'range', type: 'number', minimum: 0, maximum: 1 }
|
||||
lightness: { format: 'range', type: 'number', minimum: 0, maximum: 1 }
|
||||
|
||||
me.colorConfig = (props) ->
|
||||
obj = _.cloneDeep(ColorConfigSchema)
|
||||
obj.properties[prop] = props[prop] for prop of props
|
||||
obj
|
||||
|
||||
# BASICS
|
||||
|
||||
basicProps = (linkFragment) ->
|
||||
_id: me.objectId(links: [{rel: 'self', href: "/db/#{linkFragment}/{($)}"}], format: 'hidden')
|
||||
__v: { title: 'Mongoose Version', format: 'hidden' }
|
||||
|
||||
me.extendBasicProperties = (schema, linkFragment) ->
|
||||
schema.properties = {} unless schema.properties?
|
||||
_.extend(schema.properties, basicProps(linkFragment))
|
||||
|
||||
|
||||
# NAMED
|
||||
|
||||
namedProps = ->
|
||||
name: me.shortString({title: 'Name'})
|
||||
slug: me.shortString({title: 'Slug', format: 'hidden'})
|
||||
|
||||
me.extendNamedProperties = (schema) ->
|
||||
schema.properties = {} unless schema.properties?
|
||||
_.extend(schema.properties, namedProps())
|
||||
|
||||
|
||||
# VERSIONED
|
||||
|
||||
versionedProps = (linkFragment) ->
|
||||
version:
|
||||
'default': { minor: 0, major: 0, isLatestMajor: true, isLatestMinor: true }
|
||||
format: 'version'
|
||||
title: 'Version'
|
||||
type: 'object'
|
||||
readOnly: true
|
||||
additionalProperties: false
|
||||
properties:
|
||||
major: { type: 'number', minimum: 0 }
|
||||
minor: { type: 'number', minimum: 0 }
|
||||
isLatestMajor: { type: 'boolean' }
|
||||
isLatestMinor: { type: 'boolean' }
|
||||
# TODO: figure out useful 'rel' values here
|
||||
original: me.objectId(links: [{rel: 'extra', href: "/db/#{linkFragment}/{($)}"}], format: 'hidden')
|
||||
parent: me.objectId(links: [{rel: 'extra', href: "/db/#{linkFragment}/{($)}"}], format: 'hidden')
|
||||
creator: me.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}], format: 'hidden')
|
||||
created: me.date( { title: 'Created', readOnly: true })
|
||||
commitMessage: { type: 'string', maxLength: 500, title: 'Commit Message', readOnly: true }
|
||||
|
||||
me.extendVersionedProperties = (schema, linkFragment) ->
|
||||
schema.properties = {} unless schema.properties?
|
||||
_.extend(schema.properties, versionedProps(linkFragment))
|
||||
|
||||
|
||||
# SEARCHABLE
|
||||
|
||||
searchableProps = ->
|
||||
index: { format: 'hidden' }
|
||||
|
||||
me.extendSearchableProperties = (schema) ->
|
||||
schema.properties = {} unless schema.properties?
|
||||
_.extend(schema.properties, searchableProps())
|
||||
|
||||
|
||||
# PERMISSIONED
|
||||
|
||||
permissionsProps = ->
|
||||
permissions:
|
||||
type: 'array'
|
||||
items:
|
||||
type: 'object'
|
||||
additionalProperties: false
|
||||
properties:
|
||||
target: {}
|
||||
access: {type: 'string', 'enum': ['read', 'write', 'owner']}
|
||||
format: "hidden"
|
||||
|
||||
me.extendPermissionsProperties = (schema) ->
|
||||
schema.properties = {} unless schema.properties?
|
||||
_.extend(schema.properties, permissionsProps())
|
||||
|
||||
# TRANSLATABLE
|
||||
|
||||
me.generateLanguageCodeArrayRegex = -> "^(" + Language.languageCodes.join("|") + ")$"
|
||||
|
||||
me.getLanguageCodeArray = ->
|
||||
return Language.languageCodes
|
||||
|
||||
me.getLanguagesObject = -> return Language
|
||||
|
||||
# OTHER
|
||||
|
||||
me.classNamePattern = "^[A-Z][A-Za-z0-9]*$" # starts with capital letter; just letters and numbers
|
||||
me.identifierPattern = "^[a-z][A-Za-z0-9]*$" # starts with lowercase letter; just letters and numbers
|
||||
me.constantPattern = "^[A-Z0-9_]+$" # just uppercase letters, underscores, and numbers
|
||||
me.identifierOrConstantPattern = "^([a-z][A-Za-z0-9]*|[A-Z0-9_]+)$"
|
||||
|
||||
me.FunctionArgumentSchema = me.object {
|
||||
title: "Function Argument",
|
||||
description: "Documentation entry for a function argument."
|
||||
"default":
|
||||
name: "target"
|
||||
type: "object"
|
||||
example: "this.getNearestEnemy()"
|
||||
description: "The target of this function."
|
||||
required: ['name', 'type', 'example', 'description']
|
||||
},
|
||||
name: {type: 'string', pattern: me.identifierPattern, title: "Name", description: "Name of the function argument."}
|
||||
# not actual JS types, just whatever they describe...
|
||||
type: me.shortString(title: "Type", description: "Intended type of the argument.")
|
||||
example: me.shortString(title: "Example", description: "Example value for the argument.")
|
||||
description: {title: "Description", type: 'string', description: "Description of the argument.", maxLength: 1000}
|
||||
"default":
|
||||
title: "Default"
|
||||
description: "Default value of the argument. (Your code should set this.)"
|
||||
"default": null
|
21
app/schemas/thang_component_schema.coffee
Normal file
21
app/schemas/thang_component_schema.coffee
Normal file
|
@ -0,0 +1,21 @@
|
|||
c = require './schemas'
|
||||
|
||||
module.exports = ThangComponentSchema = c.object {
|
||||
title: "Component"
|
||||
description: "Configuration for a Component that this Thang uses."
|
||||
format: 'thang-component'
|
||||
required: ['original', 'majorVersion']
|
||||
'default':
|
||||
majorVersion: 0
|
||||
config: {}
|
||||
links: [{rel: "db", href: "/db/level.component/{(original)}/version/{(majorVersion)}"}]
|
||||
},
|
||||
original: c.objectId(title: "Original", description: "A reference to the original Component being configured.", format: "hidden")
|
||||
config: c.object {title: "Configuration", description: "Component-specific configuration properties.", additionalProperties: true, format: 'thang-component-configuration'}
|
||||
majorVersion:
|
||||
title: "Major Version"
|
||||
description: "Which major version of the Component is being used."
|
||||
type: 'integer'
|
||||
minimum: 0
|
||||
default: 0
|
||||
format: "hidden"
|
153
app/schemas/thang_type_schema.coffee
Normal file
153
app/schemas/thang_type_schema.coffee
Normal file
|
@ -0,0 +1,153 @@
|
|||
c = require './schemas'
|
||||
ThangComponentSchema = require './thang_component_schema'
|
||||
|
||||
ThangTypeSchema = c.object()
|
||||
c.extendNamedProperties ThangTypeSchema # name first
|
||||
|
||||
ShapeObjectSchema = c.object { title: 'Shape' },
|
||||
fc: { type: 'string', title: 'Fill Color' }
|
||||
lf: { type: 'array', title: 'Linear Gradient Fill' }
|
||||
ls: { type: 'array', title: 'Linear Gradient Stroke' }
|
||||
p: { type: 'string', title: 'Path' }
|
||||
de: { type: 'array', title: 'Draw Ellipse' }
|
||||
sc: { type: 'string', title: 'Stroke Color' }
|
||||
ss: { type: 'array', title: 'Stroke Style' }
|
||||
t: c.array {}, { type: 'number', title: 'Transform' }
|
||||
m: { type: 'string', title: 'Mask' }
|
||||
|
||||
ContainerObjectSchema = c.object { format: 'container' },
|
||||
b: c.array { title: 'Bounds' }, { type: 'number' }
|
||||
c: c.array { title: 'Children' }, { anyOf: [
|
||||
{ type: 'string', title: 'Shape Child' },
|
||||
c.object { title: 'Container Child' }
|
||||
gn: { type: 'string', title: 'Global Name' }
|
||||
t: c.array {}, { type: 'number' }
|
||||
]}
|
||||
|
||||
RawAnimationObjectSchema = c.object {},
|
||||
bounds: c.array { title: 'Bounds' }, { type: 'number' }
|
||||
frameBounds: c.array { title: 'Frame Bounds' }, c.array { title: 'Bounds' }, { type: 'number' }
|
||||
shapes: c.array {},
|
||||
bn: { type: 'string', title: 'Block Name' }
|
||||
gn: { type: 'string', title: 'Global Name' }
|
||||
im : { type: 'boolean', title: 'Is Mask' }
|
||||
m: { type: 'string', title: 'Uses Mask' }
|
||||
containers: c.array {},
|
||||
bn: { type: 'string', title: 'Block Name' }
|
||||
gn: { type: 'string', title: 'Global Name' }
|
||||
t: c.array {}, { type: 'number' }
|
||||
o: { type: 'boolean', title: 'Starts Hidden (_off)'}
|
||||
al: { type: 'number', title: 'Alpha'}
|
||||
animations: c.array {},
|
||||
bn: { type: 'string', title: 'Block Name' }
|
||||
gn: { type: 'string', title: 'Global Name' }
|
||||
t: c.array {}, { type: 'number', title: 'Transform' }
|
||||
a: c.array { title: 'Arguments' }
|
||||
tweens: c.array {},
|
||||
c.array { title: 'Function Chain', },
|
||||
c.object { title: 'Function Call' },
|
||||
n: { type: 'string', title: 'Name' }
|
||||
a: c.array { title: 'Arguments' }
|
||||
graphics: c.array {},
|
||||
bn: { type: 'string', title: 'Block Name' }
|
||||
p: { type: 'string', title: 'Path' }
|
||||
|
||||
PositionsSchema = c.object { title: 'Positions', description: 'Customize position offsets.' },
|
||||
registration: c.point2d { title: 'Registration Point', description: "Action-specific registration point override." }
|
||||
torso: c.point2d { title: 'Torso Offset', description: "Action-specific torso offset override." }
|
||||
mouth: c.point2d { title: 'Mouth Offset', description: "Action-specific mouth offset override." }
|
||||
aboveHead: c.point2d { title: 'Above Head Offset', description: "Action-specific above-head offset override." }
|
||||
|
||||
ActionSchema = c.object {},
|
||||
animation: { type: 'string', description: 'Raw animation being sourced', format: 'raw-animation' }
|
||||
container: { type: 'string', description: 'Name of the container to show' }
|
||||
relatedActions: c.object { },
|
||||
begin: { $ref: '#/definitions/action' }
|
||||
end: { $ref: '#/definitions/action' }
|
||||
main: { $ref: '#/definitions/action' }
|
||||
fore: { $ref: '#/definitions/action' }
|
||||
back: { $ref: '#/definitions/action' }
|
||||
side: { $ref: '#/definitions/action' }
|
||||
|
||||
"?0?011?11?11": { $ref: '#/definitions/action', title: "NW corner" }
|
||||
"?0?11011?11?": { $ref: '#/definitions/action', title: "NE corner, flipped" }
|
||||
"?0?111111111": { $ref: '#/definitions/action', title: "N face" }
|
||||
"?11011011?0?": { $ref: '#/definitions/action', title: "SW corner, top" }
|
||||
"11?11?110?0?": { $ref: '#/definitions/action', title: "SE corner, top, flipped" }
|
||||
"?11011?0????": { $ref: '#/definitions/action', title: "SW corner, bottom" }
|
||||
"11?110?0????": { $ref: '#/definitions/action', title: "SE corner, bottom, flipped" }
|
||||
"?11011?11?11": { $ref: '#/definitions/action', title: "W face" }
|
||||
"11?11011?11?": { $ref: '#/definitions/action', title: "E face, flipped" }
|
||||
"011111111111": { $ref: '#/definitions/action', title: "NW elbow" }
|
||||
"110111111111": { $ref: '#/definitions/action', title: "NE elbow, flipped" }
|
||||
"111111111?0?": { $ref: '#/definitions/action', title: "S face, top" }
|
||||
"111111?0????": { $ref: '#/definitions/action', title: "S face, bottom" }
|
||||
"111111111011": { $ref: '#/definitions/action', title: "SW elbow, top" }
|
||||
"111111111110": { $ref: '#/definitions/action', title: "SE elbow, top, flipped" }
|
||||
"111111011?11": { $ref: '#/definitions/action', title: "SW elbow, bottom" }
|
||||
"11111111011?": { $ref: '#/definitions/action', title: "SE elbow, bottom, flipped" }
|
||||
"111111111111": { $ref: '#/definitions/action', title: "Middle" }
|
||||
|
||||
loops: { type: 'boolean' }
|
||||
speed: { type: 'number' }
|
||||
goesTo: { type: 'string', description: 'Action (animation?) to which we switch after this animation.' }
|
||||
frames: { type: 'string', pattern:'^[0-9,]+$', description: 'Manually way to specify frames.' }
|
||||
framerate: { type: 'number', description: 'Get this from the HTML output.' }
|
||||
positions: PositionsSchema
|
||||
scale: { title: 'Scale', type: 'number' }
|
||||
flipX: { title: "Flip X", type: 'boolean', description: "Flip this animation horizontally?" }
|
||||
flipY: { title: "Flip Y", type: 'boolean', description: "Flip this animation vertically?" }
|
||||
|
||||
SoundSchema = c.sound({delay: { type: 'number' }})
|
||||
|
||||
_.extend ThangTypeSchema.properties,
|
||||
raw: c.object {title: 'Raw Vector Data'},
|
||||
shapes: c.object {title: 'Shapes', additionalProperties: ShapeObjectSchema}
|
||||
containers: c.object {title: 'Containers', additionalProperties: ContainerObjectSchema}
|
||||
animations: c.object {title: 'Animations', additionalProperties: RawAnimationObjectSchema}
|
||||
kind: c.shortString { enum: ['Unit', 'Floor', 'Wall', 'Doodad', 'Misc', 'Mark'], default: 'Misc', title: 'Kind' }
|
||||
|
||||
actions: c.object { title: 'Actions', additionalProperties: { $ref: '#/definitions/action' } }
|
||||
soundTriggers: c.object { title: "Sound Triggers", additionalProperties: c.array({}, { $ref: '#/definitions/sound' }) },
|
||||
say: c.object { format: 'slug-props', additionalProperties: { $ref: '#/definitions/sound' } },
|
||||
defaultSimlish: c.array({}, { $ref: '#/definitions/sound' })
|
||||
swearingSimlish: c.array({}, { $ref: '#/definitions/sound' })
|
||||
rotationType: { title: 'Rotation', type: 'string', enum: ['isometric', 'fixed']}
|
||||
matchWorldDimensions: { title: 'Match World Dimensions', type: 'boolean' }
|
||||
shadow: { title: 'Shadow Diameter', type: 'number', format: 'meters', description: "Shadow diameter in meters" }
|
||||
layerPriority:
|
||||
title: 'Layer Priority'
|
||||
type: 'integer'
|
||||
description: "Within its layer, sprites are sorted by layer priority, then y, then z."
|
||||
scale:
|
||||
title: 'Scale'
|
||||
type: 'number'
|
||||
positions: PositionsSchema
|
||||
colorGroups: c.object
|
||||
title: 'Color Groups'
|
||||
additionalProperties:
|
||||
type:'array'
|
||||
format: 'thang-color-group'
|
||||
items: {type:'string'}
|
||||
snap: c.object { title: "Snap", description: "In the level editor, snap positioning to these intervals.", required: ['x', 'y'] },
|
||||
x:
|
||||
title: "Snap X"
|
||||
type: 'number'
|
||||
description: "Snap to this many meters in the x-direction."
|
||||
default: 4
|
||||
y:
|
||||
title: "Snap Y"
|
||||
type: 'number'
|
||||
description: "Snap to this many meters in the y-direction."
|
||||
default: 4
|
||||
components: c.array {title: "Components", description: "Thangs are configured by changing the Components attached to them.", uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema # TODO: uniqueness should be based on "original", not whole thing
|
||||
|
||||
ThangTypeSchema.definitions =
|
||||
action: ActionSchema
|
||||
sound: SoundSchema
|
||||
|
||||
c.extendBasicProperties(ThangTypeSchema, 'thang.type')
|
||||
c.extendSearchableProperties(ThangTypeSchema)
|
||||
c.extendVersionedProperties(ThangTypeSchema, 'thang.type')
|
||||
|
||||
module.exports = ThangTypeSchema
|
98
app/schemas/user_schema.coffee
Normal file
98
app/schemas/user_schema.coffee
Normal file
|
@ -0,0 +1,98 @@
|
|||
c = require './schemas'
|
||||
emailSubscriptions = ['announcement', 'tester', 'level_creator', 'developer', 'article_editor', 'translator', 'support', 'notification']
|
||||
|
||||
UserSchema = c.object {},
|
||||
name: c.shortString({title: 'Display Name', default:''})
|
||||
email: c.shortString({title: 'Email', format: 'email'})
|
||||
firstName: c.shortString({title: 'First Name'})
|
||||
lastName: c.shortString({title: 'Last Name'})
|
||||
gender: {type: 'string', 'enum': ['male', 'female']}
|
||||
password: {type: 'string', maxLength: 256, minLength: 2, title:'Password'}
|
||||
passwordReset: {type: 'string'}
|
||||
photoURL: {type: 'string', format: 'image-file', title: 'Profile Picture', description: 'Upload a 256x256px or larger image to serve as your profile picture.'}
|
||||
|
||||
facebookID: c.shortString({title: 'Facebook ID'})
|
||||
gplusID: c.shortString({title: 'G+ ID'})
|
||||
|
||||
wizardColor1: c.pct({title: 'Wizard Clothes Color'})
|
||||
volume: c.pct({title: 'Volume'})
|
||||
music: {type: 'boolean', default: true}
|
||||
autocastDelay: {type: 'integer', 'default': 5000 }
|
||||
lastLevel: { type: 'string' }
|
||||
|
||||
emailSubscriptions: c.array {uniqueItems: true, 'default': ['announcement', 'notification']}, {'enum': emailSubscriptions}
|
||||
|
||||
# server controlled
|
||||
permissions: c.array {'default': []}, c.shortString()
|
||||
dateCreated: c.date({title: 'Date Joined'})
|
||||
anonymous: {type: 'boolean', 'default': true}
|
||||
testGroupNumber: {type: 'integer', minimum: 0, maximum: 256, exclusiveMaximum: true}
|
||||
mailChimp: {type: 'object'}
|
||||
hourOfCode: {type: 'boolean'}
|
||||
hourOfCodeComplete: {type: 'boolean'}
|
||||
|
||||
emailLower: c.shortString()
|
||||
nameLower: c.shortString()
|
||||
passwordHash: {type: 'string', maxLength: 256}
|
||||
|
||||
# client side
|
||||
emailHash: {type: 'string'}
|
||||
|
||||
#Internationalization stuff
|
||||
preferredLanguage: {type: 'string', default: 'en', 'enum': c.getLanguageCodeArray()}
|
||||
|
||||
signedCLA: c.date({title: 'Date Signed the CLA'})
|
||||
wizard: c.object {},
|
||||
colorConfig: c.object {additionalProperties: c.colorConfig()}
|
||||
|
||||
aceConfig: c.object {},
|
||||
language: {type: 'string', 'default': 'javascript', 'enum': ['javascript', 'coffeescript']}
|
||||
keyBindings: {type: 'string', 'default': 'default', 'enum': ['default', 'vim', 'emacs']}
|
||||
invisibles: {type: 'boolean', 'default': false}
|
||||
indentGuides: {type: 'boolean', 'default': false}
|
||||
behaviors: {type: 'boolean', 'default': false}
|
||||
|
||||
simulatedBy: {type: 'integer', minimum: 0, default: 0}
|
||||
simulatedFor: {type: 'integer', minimum: 0, default: 0}
|
||||
|
||||
jobProfile: c.object {title: 'Job Profile', required: ['lookingFor', 'jobTitle', 'active', 'name', 'city', 'country', 'skills', 'experience', 'shortDescription', 'longDescription', 'visa', 'work', 'education', 'projects', 'links']},
|
||||
lookingFor: {title: 'Looking For', type: 'string', enum: ['Full-time', 'Part-time', 'Remote', 'Contracting', 'Internship'], default: 'Full-time', description: 'What kind of developer position do you want?'}
|
||||
jobTitle: {type: 'string', maxLength: 50, title: 'Desired Job Title', description: 'What role are you looking for? Ex.: "Full Stack Engineer", "Front-End Developer", "iOS Developer"', default: 'Software Developer'}
|
||||
active: {title: 'Active', type: 'boolean', description: 'Want interview offers right now?'}
|
||||
updated: c.date {title: 'Last Updated', description: 'How fresh your profile appears to employers. The fresher, the better. Profiles go inactive after 30 days.'}
|
||||
name: c.shortString {title: 'Name', description: 'Name you want employers to see, like "Nick Winter".'}
|
||||
city: c.shortString {title: 'City', description: 'City you want to work in (or live in now), like "San Francisco" or "Lubbock, TX".', default: 'Defaultsville, CA', format: 'city'}
|
||||
country: c.shortString {title: 'Country', description: 'Country you want to work in (or live in now), like "USA" or "France".', default: 'USA', format: 'country'}
|
||||
skills: c.array {title: 'Skills', description: 'Tag relevant developer skills in order of proficiency. Employers will see the first five at a glance.', default: ['javascript'], minItems: 1, maxItems: 30, uniqueItems: true},
|
||||
{type: 'string', minLength: 1, maxLength: 20, description: 'Ex.: "objective-c", "mongodb", "rails", "android", "javascript"', format: 'skill'}
|
||||
experience: {type: 'integer', title: 'Years of Experience', minimum: 0, description: 'How many years of professional experience (getting paid) developing software do you have?'}
|
||||
shortDescription: {type: 'string', maxLength: 140, title: 'Short Description', description: 'Who are you, and what are you looking for? 140 characters max.', default: 'Programmer seeking to build great software.'}
|
||||
longDescription: {type: 'string', maxLength: 600, title: 'Description', description: 'Describe yourself to potential employers. Keep it short and to the point. We recommend outlining the position that would most interest you. Tasteful markdown okay; 600 characters max.', format: 'markdown', default: '* I write great code.\n* You need great code?\n* Great!'}
|
||||
visa: c.shortString {title: 'US Work Status', description: 'Are you authorized to work in the US, or do you need visa sponsorship?', enum: ['Authorized to work in the US', 'Need visa sponsorship'], default: 'Authorized to work in the US'}
|
||||
work: c.array {title: 'Work Experience', description: 'List your relevant work experience, most recent first.'},
|
||||
c.object {title: 'Job', description: 'Some work experience you had.', required: ['employer', 'role', 'duration']},
|
||||
employer: c.shortString {title: 'Employer', description: 'Name of your employer.'}
|
||||
role: c.shortString {title: 'Job Title', description: 'What was your job title or role?'}
|
||||
duration: c.shortString {title: 'Duration', description: 'When did you hold this gig? Ex.: "Feb 2013 - present".'}
|
||||
education: c.array {title: 'Education', description: 'List your academic ordeals.'},
|
||||
c.object {title: 'Ordeal', description: 'Some education that befell you.', required: ['school', 'degree', 'duration']},
|
||||
school: c.shortString {title: 'School', description: 'Name of your school.'}
|
||||
degree: c.shortString {title: 'Degree', description: 'What was your degree and field of study? Ex. Ph.D. Human-Computer Interaction (incomplete)'}
|
||||
duration: c.shortString {title: 'Dates', description: 'When? Ex.: "Aug 2004 - May 2008".'}
|
||||
projects: c.array {title: 'Projects', description: 'Highlight your projects to amaze employers.'},
|
||||
c.object {title: 'Project', description: 'A project you created.', required: ['name', 'description', 'picture'], default: {name: 'My Project', description: 'A project I worked on.', link: 'http://example.com', picture: ''}},
|
||||
name: c.shortString {title: 'Project Name', description: 'What was the project called?', default: 'My Project'}
|
||||
description: {type: 'string', title: 'Description', description: 'Briefly describe the project.', maxLength: 400, default: 'A project I worked on.', format: 'markdown'}
|
||||
picture: {type: 'string', title: 'Picture', format: 'image-file', description: 'Upload a 230x115px or larger image showing off the project.'}
|
||||
link: c.url {title: 'Link', description: 'Link to the project.', default: 'http://example.com'}
|
||||
links: c.array {title: 'Personal and Social Links', description: 'Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog.'},
|
||||
c.object {title: 'Link', description: 'A link to another site you want to highlight, like your GitHub, your LinkedIn, or your blog.', required: ['name', 'link']},
|
||||
name: {type: 'string', maxLength: 30, title: 'Link Name', description: 'What are you linking to? Ex: "Personal Website", "Twitter"', format: 'link-name'}
|
||||
link: c.url {title: 'Link', description: 'The URL.', default: 'http://example.com'}
|
||||
photoURL: {type: 'string', format: 'image-file', title: 'Profile Picture', description: 'Upload a 256x256px or larger image if you want to show a different profile picture to employers than your normal avatar.'}
|
||||
|
||||
jobProfileApproved: {title: 'Job Profile Approved', type: 'boolean', description: 'Whether your profile has been approved by CodeCombat.'}
|
||||
jobProfileNotes: {type: 'string', maxLength: 1000, title: 'Our Notes', description: "CodeCombat's notes on the candidate.", format: 'markdown', default: ''}
|
||||
c.extendBasicProperties UserSchema, 'user'
|
||||
|
||||
module.exports = UserSchema
|
|
@ -82,8 +82,8 @@ module.exports = class SettingsView extends View
|
|||
buildPictureTreema: ->
|
||||
data = photoURL: me.get('photoURL')
|
||||
data.photoURL = null if data.photoURL?.search('gravatar') isnt -1 # Old style
|
||||
schema = _.cloneDeep me.schema().attributes
|
||||
schema.properties = _.pick me.schema().get('properties'), 'photoURL'
|
||||
schema = _.cloneDeep me.schema()
|
||||
schema.properties = _.pick me.schema().properties, 'photoURL'
|
||||
schema.required = ['photoURL']
|
||||
treemaOptions =
|
||||
filePath: "db/user/#{me.id}"
|
||||
|
|
|
@ -54,7 +54,7 @@ module.exports = class ArticleEditView extends View
|
|||
options =
|
||||
data: data
|
||||
filePath: "db/thang.type/#{@article.get('original')}"
|
||||
schema: Article.schema.attributes
|
||||
schema: Article.schema
|
||||
readOnly: true unless me.isAdmin() or @article.hasWriteAccess(me)
|
||||
callbacks:
|
||||
change: @pushChangesToPreview
|
||||
|
|
|
@ -45,7 +45,7 @@ module.exports = class ThangComponentEditView extends CocoView
|
|||
buildExtantComponentTreema: ->
|
||||
treemaOptions =
|
||||
supermodel: @supermodel
|
||||
schema: Level.schema.get('properties').thangs.items.properties.components
|
||||
schema: Level.schema.properties.thangs.items.properties.components
|
||||
data: _.cloneDeep @components
|
||||
callbacks: {select: @onSelectExtantComponent, change:@onChangeExtantComponents}
|
||||
noSortable: true
|
||||
|
@ -69,7 +69,7 @@ module.exports = class ThangComponentEditView extends CocoView
|
|||
|
||||
treemaOptions =
|
||||
supermodel: @supermodel
|
||||
schema: { type: 'array', items: LevelComponent.schema.attributes }
|
||||
schema: { type: 'array', items: LevelComponent.schema }
|
||||
data: ($.extend(true, {}, c) for c in components)
|
||||
callbacks: {select: @onSelectAddableComponent, enter: @onAddComponentEnterPressed }
|
||||
readOnly: true
|
||||
|
|
|
@ -31,7 +31,7 @@ module.exports = class LevelComponentEditView extends View
|
|||
|
||||
buildSettingsTreema: ->
|
||||
data = _.pick @levelComponent.attributes, (value, key) => key in @editableSettings
|
||||
schema = _.cloneDeep LevelComponent.schema.attributes
|
||||
schema = _.cloneDeep LevelComponent.schema
|
||||
schema.properties = _.pick schema.properties, (value, key) => key in @editableSettings
|
||||
schema.required = _.intersection schema.required, @editableSettings
|
||||
|
||||
|
@ -55,7 +55,7 @@ module.exports = class LevelComponentEditView extends View
|
|||
buildConfigSchemaTreema: ->
|
||||
treemaOptions =
|
||||
supermodel: @supermodel
|
||||
schema: LevelComponent.schema.get('properties').configSchema
|
||||
schema: LevelComponent.schema.properties.configSchema
|
||||
data: @levelComponent.get 'configSchema'
|
||||
callbacks: {change: @onConfigSchemaEdited}
|
||||
treemaOptions.readOnly = true unless me.isAdmin()
|
||||
|
@ -63,7 +63,7 @@ module.exports = class LevelComponentEditView extends View
|
|||
@configSchemaTreema.build()
|
||||
@configSchemaTreema.open()
|
||||
# TODO: schema is not loaded for the first one here?
|
||||
@configSchemaTreema.tv4.addSchema('metaschema', LevelComponent.schema.get('properties').configSchema)
|
||||
@configSchemaTreema.tv4.addSchema('metaschema', LevelComponent.schema.properties.configSchema)
|
||||
|
||||
onConfigSchemaEdited: =>
|
||||
@levelComponent.set 'configSchema', @configSchemaTreema.data
|
||||
|
|
|
@ -22,7 +22,7 @@ module.exports = class ScriptsTabView extends View
|
|||
@dimensions = @level.dimensions()
|
||||
scripts = $.extend(true, [], @level.get('scripts') ? [])
|
||||
treemaOptions =
|
||||
schema: Level.schema.get('properties').scripts
|
||||
schema: Level.schema.properties.scripts
|
||||
data: scripts
|
||||
callbacks:
|
||||
change: @onScriptsChanged
|
||||
|
@ -52,7 +52,7 @@ module.exports = class ScriptsTabView extends View
|
|||
filePath: "db/level/#{@level.get('original')}"
|
||||
files: @files
|
||||
view: @
|
||||
schema: Level.schema.get('properties').scripts.items
|
||||
schema: Level.schema.properties.scripts.items
|
||||
data: selected.data
|
||||
thangIDs: thangIDs
|
||||
dimensions: @dimensions
|
||||
|
|
|
@ -25,7 +25,7 @@ module.exports = class SettingsTabView extends View
|
|||
onLevelLoaded: (e) ->
|
||||
@level = e.level
|
||||
data = _.pick @level.attributes, (value, key) => key in @editableSettings
|
||||
schema = _.cloneDeep Level.schema.attributes
|
||||
schema = _.cloneDeep Level.schema
|
||||
schema.properties = _.pick schema.properties, (value, key) => key in @editableSettings
|
||||
schema.required = _.intersection schema.required, @editableSettings
|
||||
thangIDs = @getThangIDs()
|
||||
|
|
|
@ -29,7 +29,7 @@ module.exports = class LevelSystemEditView extends View
|
|||
|
||||
buildSettingsTreema: ->
|
||||
data = _.pick @levelSystem.attributes, (value, key) => key in @editableSettings
|
||||
schema = _.cloneDeep LevelSystem.schema.attributes
|
||||
schema = _.cloneDeep LevelSystem.schema
|
||||
schema.properties = _.pick schema.properties, (value, key) => key in @editableSettings
|
||||
schema.required = _.intersection schema.required, @editableSettings
|
||||
|
||||
|
@ -53,7 +53,7 @@ module.exports = class LevelSystemEditView extends View
|
|||
buildConfigSchemaTreema: ->
|
||||
treemaOptions =
|
||||
supermodel: @supermodel
|
||||
schema: LevelSystem.schema.get('properties').configSchema
|
||||
schema: LevelSystem.schema.properties.configSchema
|
||||
data: @levelSystem.get 'configSchema'
|
||||
callbacks: {change: @onConfigSchemaEdited}
|
||||
treemaOptions.readOnly = true unless me.isAdmin()
|
||||
|
@ -61,7 +61,7 @@ module.exports = class LevelSystemEditView extends View
|
|||
@configSchemaTreema.build()
|
||||
@configSchemaTreema.open()
|
||||
# TODO: schema is not loaded for the first one here?
|
||||
@configSchemaTreema.tv4.addSchema('metaschema', LevelSystem.schema.get('properties').configSchema)
|
||||
@configSchemaTreema.tv4.addSchema('metaschema', LevelSystem.schema.properties.configSchema)
|
||||
|
||||
onConfigSchemaEdited: =>
|
||||
@levelSystem.set 'configSchema', @configSchemaTreema.data
|
||||
|
|
|
@ -67,7 +67,7 @@ module.exports = class SystemsTabView extends View
|
|||
treemaOptions =
|
||||
# TODO: somehow get rid of the + button, or repurpose it to open the LevelSystemAddView instead
|
||||
supermodel: @supermodel
|
||||
schema: Level.schema.get('properties').systems
|
||||
schema: Level.schema.properties.systems
|
||||
data: systems
|
||||
readOnly: true unless me.isAdmin() or @level.hasWriteAccess(me)
|
||||
callbacks:
|
||||
|
|
|
@ -140,7 +140,7 @@ module.exports = class ThangsTabView extends View
|
|||
return if @startsLoading
|
||||
data = $.extend(true, {}, @level.attributes)
|
||||
treemaOptions =
|
||||
schema: Level.schema.get('properties').thangs
|
||||
schema: Level.schema.properties.thangs
|
||||
data: data.thangs
|
||||
supermodel: @supermodel
|
||||
callbacks:
|
||||
|
|
|
@ -12,7 +12,7 @@ module.exports = class ColorsTabView extends CocoView
|
|||
|
||||
constructor: (@thangType, options) ->
|
||||
@listenToOnce(@thangType, 'sync', @tryToBuild)
|
||||
@listenToOnce(@thangType.schema(), 'sync', @tryToBuild)
|
||||
# @listenToOnce(@thangType.schema(), 'sync', @tryToBuild)
|
||||
@colorConfig = { hue: 0, saturation: 0.5, lightness: 0.5 }
|
||||
@spriteBuilder = new SpriteBuilder(@thangType)
|
||||
f = =>
|
||||
|
@ -115,7 +115,7 @@ module.exports = class ColorsTabView extends CocoView
|
|||
return unless @thangType.loaded and @thangType.schema().loaded
|
||||
data = @thangType.get('colorGroups')
|
||||
data ?= {}
|
||||
schema = @thangType.schema().attributes.properties?.colorGroups
|
||||
schema = @thangType.schema().properties?.colorGroups
|
||||
treemaOptions =
|
||||
data: data
|
||||
schema: schema
|
||||
|
|
|
@ -62,7 +62,7 @@ module.exports = class ThangTypeEditView extends View
|
|||
|
||||
@thangType.fetch()
|
||||
@thangType.loadSchema()
|
||||
@listenToOnce(@thangType.schema(), 'sync', @onThangTypeSync)
|
||||
# @listenToOnce(@thangType.schema(), 'sync', @onThangTypeSync)
|
||||
@listenToOnce(@thangType, 'sync', @onThangTypeSync)
|
||||
@refreshAnimation = _.debounce @refreshAnimation, 500
|
||||
|
||||
|
@ -344,7 +344,7 @@ module.exports = class ThangTypeEditView extends View
|
|||
|
||||
buildTreema: ->
|
||||
data = @getThangData()
|
||||
schema = _.cloneDeep ThangType.schema.attributes
|
||||
schema = _.cloneDeep ThangType.schema
|
||||
schema.properties = _.pick schema.properties, (value, key) => not (key in ['components'])
|
||||
options =
|
||||
data: data
|
||||
|
|
|
@ -36,7 +36,7 @@ module.exports = class LoginModalView extends View
|
|||
loginAccount: (e) =>
|
||||
forms.clearFormAlerts(@$el)
|
||||
userObject = forms.formToObject @$el
|
||||
res = tv4.validateMultiple userObject, User.schema.attributes
|
||||
res = tv4.validateMultiple userObject, User.schema
|
||||
return forms.applyErrorsToForm(@$el, res.errors) unless res.valid
|
||||
@enableModalInProgress(@$el) # TODO: part of forms
|
||||
loginUser(userObject)
|
||||
|
|
|
@ -57,7 +57,7 @@ module.exports = class SignupModalView extends View
|
|||
userObject.emailSubscriptions.push 'notification' unless 'notification' in userObject.emailSubscriptions
|
||||
else
|
||||
userObject.emailSubscriptions = _.without (userObject.emailSubscriptions ? []), 'announcement', 'notification'
|
||||
res = tv4.validateMultiple userObject, User.schema.attributes
|
||||
res = tv4.validateMultiple userObject, User.schema
|
||||
return forms.applyErrorsToForm(@$el, res.errors) unless res.valid
|
||||
window.tracker?.trackEvent 'Finished Signup'
|
||||
@enableModalInProgress(@$el)
|
||||
|
|
|
@ -47,8 +47,8 @@ module.exports.setup = (app) ->
|
|||
|
||||
getSchema = (req, res, moduleName) ->
|
||||
try
|
||||
name = schemas[moduleName.replace '.', '_']
|
||||
schema = require('../' + name)
|
||||
name = moduleName.replace '.', '_'
|
||||
schema = require('../../app/schemas/' + name + '_schema')
|
||||
|
||||
res.send(JSON.stringify(schema, null, '\t'))
|
||||
res.end()
|
||||
|
|
Loading…
Reference in a new issue