mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 23:58:02 -05:00
Merge branch 'master' into feature/bootstrap3
This commit is contained in:
commit
88c8c3896b
49 changed files with 1515 additions and 339 deletions
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,7 @@
|
|||
CocoClass = require 'lib/CocoClass'
|
||||
{me, CURRENT_USER_KEY} = require 'lib/auth'
|
||||
{backboneFailure} = require 'lib/errors'
|
||||
{saveObjectToStorage} = require 'lib/storage'
|
||||
storage = require 'lib/storage'
|
||||
|
||||
# facebook user object props to
|
||||
userPropsToSave =
|
||||
|
@ -59,7 +59,7 @@ module.exports = FacebookHandler = class FacebookHandler extends CocoClass
|
|||
error: backboneFailure,
|
||||
url: "/db/user?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}"
|
||||
success: (model) ->
|
||||
saveObjectToStorage(CURRENT_USER_KEY, model.attributes)
|
||||
storage.save(CURRENT_USER_KEY, model.attributes)
|
||||
window.location.reload() if model.get('email') isnt oldEmail
|
||||
})
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
CocoClass = require 'lib/CocoClass'
|
||||
{me, CURRENT_USER_KEY} = require 'lib/auth'
|
||||
{backboneFailure} = require 'lib/errors'
|
||||
{saveObjectToStorage} = require 'lib/storage'
|
||||
storage = require 'lib/storage'
|
||||
|
||||
# gplus user object props to
|
||||
userPropsToSave =
|
||||
|
@ -73,7 +73,7 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
|
|||
error: backboneFailure,
|
||||
url: "/db/user?gplusID=#{gplusID}&gplusAccessToken=#{@accessToken}"
|
||||
success: (model) ->
|
||||
saveObjectToStorage(CURRENT_USER_KEY, model.attributes)
|
||||
storage.save(CURRENT_USER_KEY, model.attributes)
|
||||
window.location.reload()
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{backboneFailure, genericFailure} = require 'lib/errors'
|
||||
User = require 'models/User'
|
||||
{saveObjectToStorage, loadObjectFromStorage} = require 'lib/storage'
|
||||
storage = require 'lib/storage'
|
||||
|
||||
module.exports.CURRENT_USER_KEY = CURRENT_USER_KEY = 'whoami'
|
||||
BEEN_HERE_BEFORE_KEY = 'beenHereBefore'
|
||||
|
@ -10,7 +10,7 @@ module.exports.createUser = (userObject, failure=backboneFailure) ->
|
|||
user.save({}, {
|
||||
error: failure,
|
||||
success: (model) ->
|
||||
saveObjectToStorage(CURRENT_USER_KEY, model)
|
||||
storage.save(CURRENT_USER_KEY, model)
|
||||
window.location.reload()
|
||||
})
|
||||
|
||||
|
@ -21,7 +21,7 @@ module.exports.loginUser = (userObject, failure=genericFailure) ->
|
|||
password:userObject.password
|
||||
},
|
||||
(model) ->
|
||||
saveObjectToStorage(CURRENT_USER_KEY, model)
|
||||
storage.save(CURRENT_USER_KEY, model)
|
||||
window.location.reload()
|
||||
)
|
||||
jqxhr.fail(failure)
|
||||
|
@ -29,7 +29,7 @@ module.exports.loginUser = (userObject, failure=genericFailure) ->
|
|||
module.exports.logoutUser = ->
|
||||
FB?.logout?()
|
||||
res = $.post('/auth/logout', {}, ->
|
||||
saveObjectToStorage(CURRENT_USER_KEY, null)
|
||||
storage.save(CURRENT_USER_KEY, null)
|
||||
window.location.reload()
|
||||
)
|
||||
res.fail(genericFailure)
|
||||
|
@ -38,7 +38,7 @@ init = ->
|
|||
# Load the user from local storage, and refresh it from the server.
|
||||
# Also refresh and cache the gravatar info.
|
||||
|
||||
storedUser = loadObjectFromStorage(CURRENT_USER_KEY)
|
||||
storedUser = storage.load(CURRENT_USER_KEY)
|
||||
firstTime = not storedUser
|
||||
module.exports.me = window.me = new User(storedUser)
|
||||
me.url = -> '/auth/whoami'
|
||||
|
@ -50,14 +50,14 @@ init = ->
|
|||
# Assign testGroupNumber to returning visitors; new ones in server/handlers/user
|
||||
me.set 'testGroupNumber', Math.floor(Math.random() * 256)
|
||||
me.save()
|
||||
saveObjectToStorage(CURRENT_USER_KEY, me.attributes)
|
||||
storage.save(CURRENT_USER_KEY, me.attributes)
|
||||
|
||||
me.loadGravatarProfile() if me.get('email')
|
||||
me.on('sync', userSynced)
|
||||
|
||||
userSynced = (user) ->
|
||||
Backbone.Mediator.publish('me:synced', {me:user})
|
||||
saveObjectToStorage(CURRENT_USER_KEY, user)
|
||||
storage.save(CURRENT_USER_KEY, user)
|
||||
|
||||
init()
|
||||
|
||||
|
@ -71,7 +71,7 @@ Backbone.Mediator.subscribe('level-set-volume', onSetVolume, module.exports)
|
|||
trackFirstArrival = ->
|
||||
# will have to filter out users who log in with existing accounts separately
|
||||
# but can at least not track logouts as first arrivals using local storage
|
||||
beenHereBefore = loadObjectFromStorage(BEEN_HERE_BEFORE_KEY)
|
||||
beenHereBefore = storage.load(BEEN_HERE_BEFORE_KEY)
|
||||
return if beenHereBefore
|
||||
window.tracker?.trackEvent 'First Arrived'
|
||||
saveObjectToStorage(BEEN_HERE_BEFORE_KEY, true)
|
||||
storage.save(BEEN_HERE_BEFORE_KEY, true)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
module.exports.loadObjectFromStorage = (key) ->
|
||||
module.exports.load = (key) ->
|
||||
s = localStorage.getItem(key)
|
||||
return null unless s
|
||||
try
|
||||
|
@ -8,6 +8,8 @@ module.exports.loadObjectFromStorage = (key) ->
|
|||
console.warning('error loading from storage', key)
|
||||
return null
|
||||
|
||||
module.exports.saveObjectToStorage = (key, value) ->
|
||||
module.exports.save = (key, value) ->
|
||||
s = JSON.stringify(value)
|
||||
localStorage.setItem(key, s)
|
||||
localStorage.setItem(key, s)
|
||||
|
||||
module.exports.remove = (key) -> localStorage.removeItem key
|
|
@ -186,6 +186,7 @@ module.exports = class Camera extends CocoClass
|
|||
# Target is either just a {x, y} pos or a display object with {x, y} that might change; surface coordinates.
|
||||
time = 0 if @instant
|
||||
newTarget ?= {x:0, y:0}
|
||||
newTarget = (@newTarget or @target) if @locked
|
||||
newZoom = Math.min((Math.max @minZoom, newZoom), MAX_ZOOM)
|
||||
return if @zoom is newZoom and newTarget is newTarget.x and newTarget.y is newTarget.y
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module.exports.thangNames = thangNames =
|
||||
"Soldier": [
|
||||
"Soldier M": [
|
||||
"William"
|
||||
"Lucas"
|
||||
"Marcus"
|
||||
|
@ -45,16 +45,18 @@ module.exports.thangNames = thangNames =
|
|||
"Sterling"
|
||||
"Alistair"
|
||||
"Remy"
|
||||
"Lana"
|
||||
"Stormy"
|
||||
"Halle"
|
||||
"Sage"
|
||||
]
|
||||
"Soldier F": [
|
||||
"Sarah"
|
||||
"Alexandra"
|
||||
"Holly"
|
||||
"Trinity"
|
||||
"Nikita"
|
||||
"Alana"
|
||||
"Sage"
|
||||
"Lana"
|
||||
]
|
||||
"Peasant": [
|
||||
"Yorik"
|
||||
|
@ -77,7 +79,7 @@ module.exports.thangNames = thangNames =
|
|||
"Bernadette"
|
||||
"Hershell"
|
||||
]
|
||||
"Archer": [
|
||||
"Archer F": [
|
||||
"Phoebe"
|
||||
"Mira"
|
||||
"Agapi"
|
||||
|
@ -98,13 +100,15 @@ module.exports.thangNames = thangNames =
|
|||
"Clare"
|
||||
"Rowan"
|
||||
"Omar"
|
||||
"Brian"
|
||||
"Cole"
|
||||
"Alden"
|
||||
"Cairn"
|
||||
"Jensen"
|
||||
]
|
||||
"Ogre Munchkin": [
|
||||
"Archer M": [
|
||||
"Brian"
|
||||
"Cole"
|
||||
]
|
||||
"Ogre Munchkin M": [
|
||||
"Brack"
|
||||
"Gort"
|
||||
"Weeb"
|
||||
|
@ -128,7 +132,10 @@ module.exports.thangNames = thangNames =
|
|||
"Snortt"
|
||||
"Kog"
|
||||
]
|
||||
"Ogre": [
|
||||
"Ogre Munchkin F": [
|
||||
|
||||
]
|
||||
"Ogre M": [
|
||||
"Krogg"
|
||||
"Dronck"
|
||||
"Trogdor"
|
||||
|
@ -140,6 +147,9 @@ module.exports.thangNames = thangNames =
|
|||
"Nareng"
|
||||
"Morthrug"
|
||||
"Glonc"
|
||||
]
|
||||
"Ogre F": [
|
||||
|
||||
]
|
||||
"Ogre Brawler": [
|
||||
"Grul'thock"
|
||||
|
|
|
@ -114,4 +114,7 @@ class Rectangle
|
|||
@deserialize: (o, world, classMap) ->
|
||||
new Rectangle o.x, o.y, o.w, o.h, o.r
|
||||
|
||||
serializeForAether: -> @serialize()
|
||||
@deserializeFromAether: (o) -> @deserialize o
|
||||
|
||||
module.exports = Rectangle
|
||||
|
|
|
@ -25,7 +25,7 @@ module.exports = class Thang
|
|||
Thang.lastIDNums ?= {}
|
||||
names = thangNames[spriteName]
|
||||
order = @ordering spriteName
|
||||
if names
|
||||
if names and names.length
|
||||
lastIDNum = Thang.lastIDNums[spriteName]
|
||||
idNum = (if lastIDNum? then lastIDNum + 1 else 0)
|
||||
Thang.lastIDNums[spriteName] = idNum
|
||||
|
@ -158,6 +158,9 @@ module.exports = class Thang
|
|||
t[prop] = val
|
||||
t
|
||||
|
||||
serializeForAether: ->
|
||||
{CN: @constructor.className, id: @id}
|
||||
|
||||
getSpriteOptions: ->
|
||||
colorConfigs = @world?.getTeamColors() or {}
|
||||
options = {}
|
||||
|
|
|
@ -119,4 +119,7 @@ class Vector
|
|||
@deserialize: (o, world, classMap) ->
|
||||
new Vector o.x, o.y, o.z
|
||||
|
||||
serializeForAether: -> @serialize()
|
||||
@deserializeFromAether: (o) -> @deserialize o
|
||||
|
||||
module.exports = Vector
|
||||
|
|
|
@ -4,7 +4,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
saving: "Guardando..."
|
||||
sending: "Enviando..."
|
||||
cancel: "Cancelar"
|
||||
# save: "Save"
|
||||
save: "Guardar"
|
||||
delay_1_sec: "1 segundo"
|
||||
delay_3_sec: "3 segundos"
|
||||
delay_5_sec: "5 segundos"
|
||||
|
@ -31,15 +31,15 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
about: "Sobre nosotros"
|
||||
contact: "Contacta"
|
||||
twitter_follow: "Síguenos"
|
||||
# employers: "Employers"
|
||||
employers: "Empresas"
|
||||
|
||||
# versions:
|
||||
# save_version_title: "Save New Version"
|
||||
# new_major_version: "New Major Version"
|
||||
# cla_prefix: "To save changes, first you must agree to our"
|
||||
# cla_url: "CLA"
|
||||
# cla_suffix: "."
|
||||
# cla_agree: "I AGREE"
|
||||
versions:
|
||||
save_version_title: "Guardar nueva versión"
|
||||
new_major_version: "Nueva versión principal"
|
||||
cla_prefix: "Para guardar los cambios, primero debes aceptar nuestro"
|
||||
cla_url: "CLA"
|
||||
cla_suffix: "."
|
||||
cla_agree: "De acuerdo"
|
||||
|
||||
login:
|
||||
sign_up: "Crear una cuenta"
|
||||
|
@ -49,10 +49,10 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
|
||||
recover:
|
||||
recover_account_title: "recuperar cuenta"
|
||||
# send_password: "Send Recovery Password"
|
||||
send_password: "Enviar recuperación de contraseña"
|
||||
|
||||
signup:
|
||||
# create_account_title: "Create Account to Save Progress"
|
||||
create_account_title: "Crea una cuenta para guardar tu progreso"
|
||||
description: "Es gratis. Solo necesitamos un par de cosas y listo para comenzar!"
|
||||
email_announcements: "Recibir noticias por correo electrónico"
|
||||
coppa: "Soy mayor de 13 o de fuera de los Estados Unidos"
|
||||
|
@ -70,7 +70,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
play:
|
||||
choose_your_level: "Elige tu nivel"
|
||||
adventurer_prefix: "Puedes elegir cualquier pantalla o charlar en "
|
||||
adventurer_forum: "el foro del aventurero"
|
||||
adventurer_forum: "el foro del aventurero "
|
||||
adventurer_suffix: "sobre ello."
|
||||
campaign_beginner: "Campaña de Principiante"
|
||||
campaign_beginner_description: "... en la que aprenderás la magia de la programación."
|
||||
|
@ -96,14 +96,14 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
diplomat_suggestion:
|
||||
title: "¡Ayuda a traducir CodeCombat!"
|
||||
sub_heading: "Necesitamos tus habilidades lingüisticas."
|
||||
pitch_body: "Nosotros desarrollamos CodeCombat en inglés, pero ya tenemos jugadores de todo el mundo. Muchos de ellos quieren jugar en Español porque no hablan inglés, así quesi hablas ambos idiomas, inscríbete como Diplomático y ayuda a traducir la web y todos los niveles de CodeCombat al Español."
|
||||
missing_translations: "Mientras terminamos la traducción al Español, verás en inglés las partes que no estén todavía disponibles."
|
||||
pitch_body: "Nosotros desarrollamos CodeCombat en inglés, pero ya tenemos jugadores de todo el mundo. Muchos de ellos quieren jugar en español porque no hablan inglés, así que si hablas ambos idiomas, inscríbete como Diplomático y ayuda a traducir la web y todos los niveles de CodeCombat al español."
|
||||
missing_translations: "Mientras terminamos la traducción al español, verás en inglés las partes que no estén todavía disponibles."
|
||||
learn_more: "Aprende más sobre ser un Diplomático"
|
||||
subscribe_as_diplomat: "Suscríbete como Diplomático"
|
||||
|
||||
# wizard_settings:
|
||||
# title: "Wizard Settings"
|
||||
# customize_avatar: "Customize Your Avatar"
|
||||
wizard_settings:
|
||||
title: "Ajustes del mago"
|
||||
customize_avatar: "Personaliza tu Avatar"
|
||||
|
||||
account_settings:
|
||||
title: "Ajustes de la cuenta"
|
||||
|
@ -122,7 +122,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
new_password_verify: "Verificar"
|
||||
email_subscriptions: "Suscripciones de correo electrónico"
|
||||
email_announcements: "Noticias"
|
||||
# email_notifications_description: "Get periodic notifications for your account."
|
||||
email_notifications_description: "Recibe notificaciones periódicas para tu cuenta."
|
||||
email_announcements_description: "Recibe correos electrónicos con las últimas noticias y desarrollos de CodeCombat."
|
||||
contributor_emails: "Correos para colaboradores"
|
||||
contribute_prefix: "¡Buscamos gente que se una a nuestro comunidad! Comprueba la "
|
||||
|
@ -139,15 +139,15 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
# profile_for_suffix: ""
|
||||
profile: "Perfil"
|
||||
user_not_found: "No se encontró al usuario. ¿Comprueba la URL?"
|
||||
gravatar_not_found_mine: "No podemos encontrar el prefil asociado con:"
|
||||
gravatar_not_found_mine: "No podemos encontrar el perfil asociado con:"
|
||||
# gravatar_not_found_email_suffix: "."
|
||||
gravatar_signup_prefix: "Suscribete "
|
||||
gravatar_signup_prefix: "¡Suscribete en "
|
||||
gravatar_signup_suffix: " para ponerte en marcha!"
|
||||
gravatar_not_found_other: "Vaya, no hay un perfil asociado a la dirección de correo electrónico de esta persona."
|
||||
gravatar_contact: "Contacto"
|
||||
gravatar_websites: "Paginas web"
|
||||
gravatar_accounts: "Como se vé en"
|
||||
gravatar_profile_link: "Prefil de Gravatar completo"
|
||||
gravatar_profile_link: "Perfil de Gravatar completo"
|
||||
|
||||
play_level:
|
||||
level_load_error: "No se pudo cargar el nivel."
|
||||
|
@ -191,7 +191,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
tome_select_a_thang: "Selecciona a alguien para "
|
||||
tome_available_spells: "Hechizos disponibles"
|
||||
hud_continue: "Continuar (pulsa Shift+Space)"
|
||||
# spell_saved: "Spell Saved"
|
||||
spell_saved: "Hechizo guardado"
|
||||
|
||||
# admin:
|
||||
# av_title: "Admin Views"
|
||||
|
@ -203,29 +203,29 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
# u_title: "User List"
|
||||
# lg_title: "Latest Games"
|
||||
|
||||
# editor:
|
||||
editor:
|
||||
# main_title: "CodeCombat Editors"
|
||||
# main_description: "Build your own levels, campaigns, units and educational content. We provide all the tools you need!"
|
||||
# article_title: "Article Editor"
|
||||
main_description: "Construye tus propios niveles, campañas, unidades y contenido educativo. ¡Nosotros te ofrecemos todas las herramientas que necesitas!"
|
||||
article_title: "Editor de artículos"
|
||||
# article_description: "Write articles that give players overviews of programming concepts which can be used across a variety of levels and campaigns."
|
||||
# thang_title: "Thang Editor"
|
||||
# thang_description: "Build units, defining their default logic, graphics and audio. Currently only supports importing Flash exported vector graphics."
|
||||
# level_title: "Level Editor"
|
||||
level_title: "Editor de Niveles"
|
||||
# level_description: "Includes the tools for scripting, uploading audio, and constructing custom logic to create all sorts of levels. Everything we use ourselves!"
|
||||
# security_notice: "Many major features in these editors are not currently enabled by default. As we improve the security of these systems, they will be made generally available. If you'd like to use these features sooner, "
|
||||
# contact_us: "contact us!"
|
||||
# hipchat_prefix: "You can also find us in our"
|
||||
# hipchat_url: "HipChat room."
|
||||
contact_us: "¡Contacta con nosotros!"
|
||||
hipchat_prefix: "También puedes encontrarnos en nuestra"
|
||||
# hipchat_url: "sala de HipChat."
|
||||
# level_some_options: "Some Options?"
|
||||
# level_tab_thangs: "Thangs"
|
||||
# level_tab_scripts: "Scripts"
|
||||
# level_tab_settings: "Settings"
|
||||
level_tab_settings: "Ajustes"
|
||||
# level_tab_components: "Components"
|
||||
# level_tab_systems: "Systems"
|
||||
# level_tab_thangs_title: "Current Thangs"
|
||||
# level_tab_thangs_conditions: "Starting Conditions"
|
||||
# level_tab_thangs_add: "Add Thangs"
|
||||
# level_settings_title: "Settings"
|
||||
level_settings_title: "Ajustes"
|
||||
# level_component_tab_title: "Current Components"
|
||||
# level_component_btn_new: "Create New Component"
|
||||
# level_systems_tab_title: "Current Systems"
|
||||
|
@ -239,86 +239,86 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
# new_component_title: "Create New Component"
|
||||
# new_component_field_system: "System"
|
||||
|
||||
# article:
|
||||
# edit_btn_preview: "Preview"
|
||||
# edit_article_title: "Edit Article"
|
||||
article:
|
||||
edit_btn_preview: "Vista preliminar"
|
||||
edit_article_title: "Editar artículo"
|
||||
|
||||
# general:
|
||||
# and: "and"
|
||||
general:
|
||||
and: "y"
|
||||
or: "o"
|
||||
# name: "Name"
|
||||
name: "Nombre"
|
||||
# body: "Body"
|
||||
# version: "Version"
|
||||
version: "Versión"
|
||||
# commit_msg: "Commit Message"
|
||||
# version_history_for: "Version History for: "
|
||||
# results: "Results"
|
||||
# description: "Description"
|
||||
results: "Resultados"
|
||||
description: "Descripción"
|
||||
email: "Correo electrónico"
|
||||
message: "Mensaje"
|
||||
|
||||
# about:
|
||||
# who_is_codecombat: "Who is CodeCombat?"
|
||||
# why_codecombat: "Why CodeCombat?"
|
||||
about:
|
||||
who_is_codecombat: "¿Qué es CodeCombat?"
|
||||
why_codecombat: "¿Por qué CodeCombat?"
|
||||
# who_description_prefix: "together started CodeCombat in 2013. We also created "
|
||||
# who_description_suffix: "in 2008, growing it to the #1 web and iOS application for learning to write Chinese and Japanese characters."
|
||||
# who_description_ending: "Now it's time to teach people to write code."
|
||||
# who_description_ending: "Es hora de empezar a enseñar a la gente a escribir código."
|
||||
# why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that."
|
||||
# why_paragraph_2: "Need to learn to code? You don't need lessons. You need to write a lot of code and have a great time doing it."
|
||||
# why_paragraph_3_prefix: "That's what programming is about. It's gotta be fun. Not fun like"
|
||||
# why_paragraph_3_italic: "yay a badge"
|
||||
# why_paragraph_3_center: "but fun like"
|
||||
# why_paragraph_3_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!"
|
||||
# why_paragraph_3_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
|
||||
# why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
|
||||
# why_ending: "And hey, it's free. "
|
||||
# why_ending_url: "Start wizarding now!"
|
||||
why_paragraph_3_prefix: "De eso va la programación. Tiene que ser divertido. No divertido como:"
|
||||
why_paragraph_3_italic: "¡bien una insignia!,"
|
||||
why_paragraph_3_center: "sino más bien como:"
|
||||
why_paragraph_3_italic_caps: "¡NO MAMA, TENGO QUE TERMINAR EL NIVEL!"
|
||||
why_paragraph_3_suffix: "Por eso Codecombat es multijugador, no un curso con lecciones \"gamificadas\" . No pararemos hasta que tú no puedas parar... pero esta vez, eso será buena señal."
|
||||
why_paragraph_4: "Si vas a engancharte a algún juego, engánchate a este y conviértete en uno de los magos de la era tecnológica."
|
||||
why_ending: "Y, oye, es gratis. "
|
||||
why_ending_url: "Comienza a hacer magia ¡ya!"
|
||||
# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
|
||||
# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
|
||||
# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat."
|
||||
# jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy."
|
||||
# michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online."
|
||||
|
||||
# legal:
|
||||
# page_title: "Legal"
|
||||
# opensource_intro: "CodeCombat is free to play and completely open source."
|
||||
# opensource_description_prefix: "Check out "
|
||||
# github_url: "our GitHub"
|
||||
# opensource_description_center: "and help out if you like! CodeCombat is built on dozens of open source projects, and we love them. See "
|
||||
# archmage_wiki_url: "our Archmage wiki"
|
||||
# opensource_description_suffix: "for a list of the software that makes this game possible."
|
||||
# practices_title: "Respectful Best Practices"
|
||||
# practices_description: "These are our promises to you, the player, in slightly less legalese."
|
||||
# privacy_title: "Privacy"
|
||||
# privacy_description: "We will not sell any of your personal information. We intend to make money through recruitment eventually, but rest assured we will not distribute your personal information to interested companies without your explicit consent."
|
||||
# security_title: "Security"
|
||||
# security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
|
||||
# email_title: "Email"
|
||||
# email_description_prefix: "We will not inundate you with spam. Through"
|
||||
# email_settings_url: "your email settings"
|
||||
# email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time."
|
||||
# cost_title: "Cost"
|
||||
# cost_description: "Currently, CodeCombat is 100% free! One of our main goals is to keep it that way, so that as many people can play as possible, regardless of place in life. If the sky darkens, we might have to charge subscriptions or for some content, but we'd rather not. With any luck, we'll be able to sustain the company with:"
|
||||
# recruitment_title: "Recruitment"
|
||||
# recruitment_description_prefix: "Here on CodeCombat, you're going to become a powerful wizard–not just in the game, but also in real life."
|
||||
# url_hire_programmers: "No one can hire programmers fast enough"
|
||||
# recruitment_description_suffix: "so once you've sharpened your skills and if you agree, we will demo your best coding accomplishments to the thousands of employers who are drooling for the chance to hire you. They pay us a little, they pay you"
|
||||
# recruitment_description_italic: "a lot"
|
||||
# recruitment_description_ending: "the site remains free and everybody's happy. That's the plan."
|
||||
# copyrights_title: "Copyrights and Licenses"
|
||||
# contributor_title: "Contributor License Agreement"
|
||||
# contributor_description_prefix: "All contributions, both on the site and on our GitHub repository, are subject to our"
|
||||
# cla_url: "CLA"
|
||||
# contributor_description_suffix: "to which you should agree before contributing."
|
||||
legal:
|
||||
page_title: "Legal"
|
||||
opensource_intro: "CodeCombat es gratis y totalmente open source."
|
||||
opensource_description_prefix: "Echa un vistazo a "
|
||||
github_url: "nuestro GitHub"
|
||||
opensource_description_center: "y ayúdanos si quieres. CodeCombat está desarrollado sobre docenas de proyectos open source, y nos encantana. Mira "
|
||||
archmage_wiki_url: "nuestra wiki del Archimago"
|
||||
opensource_description_suffix: "para encontrar una lista del software que hace este juego posible."
|
||||
practices_title: "Prácticas respetuosas"
|
||||
practices_description: "Esto es lo que te prometemos a ti, el jugador, sin usar mucha jerga legal."
|
||||
privacy_title: "Privacidad"
|
||||
privacy_description: "No venderemos tu información personal. Tenemos la intención de hacer dinero a través de la contratación con el tiempo, pero puedes estar seguro que no vamos a distribuir tu información personal a las empresas interesadas sin tu consentimiento expreso."
|
||||
security_title: "Seguridad"
|
||||
security_description: "Nos esforzamos por mantener segura tu información personal. Como proyecto de código abierto, nuestro sitio está abierto a cualquiera que quiera revisarlo y mejorar nuestros sistemas de seguridad."
|
||||
email_title: "Correo electrónico"
|
||||
email_description_prefix: "No te inundaremos con spam. Mediante"
|
||||
email_settings_url: "tus ajustes de correo electrónico"
|
||||
email_description_suffix: "o a través de los enlaces en los correos que te enviemos, puedes cambiar tus preferencias y darte de baja fácilmente en cualquier momento."
|
||||
cost_title: "Precio"
|
||||
cost_description: "Actualmente, ¡CodeCombat es 100% gratis! Uno de nuestros principales objetivos es mantenerlo así, de forma que el mayor número posible de gente pueda jugar, independientemente de sus posibilidades económicas. Si las cosas se tuercen, quizás tengamos que cobrar suscripciones o por algún contenido, pero preferimos no hacerlo. Con un poco de suerte, podremos mantener la empresa con: "
|
||||
recruitment_title: "Contratación"
|
||||
recruitment_description_prefix: "En CodeCombat, te vas a convertir en un poderoso mago no solo en el juego, también en el mundo real."
|
||||
url_hire_programmers: "Nadie puede contratar programadores con la suficiente rapidez"
|
||||
recruitment_description_suffix: "así que una vez que hayas afilado tus habilidades y si estás de acuerdo, mostraremos tus mejores logros en programación a los miles de empresas que están deseando tener la oportunidad de contratarte. Ellos nos pagan un poco y ellos te pagan a ti"
|
||||
recruitment_description_italic: "un montón."
|
||||
recruitment_description_ending: "La web permanece gratuita y todo el mundo es feliz. Ese es el plan."
|
||||
copyrights_title: "Copyrights y Licencias"
|
||||
contributor_title: "Acuerdo de Licencia del Colaborador"
|
||||
contributor_description_prefix: "Todas las colaboraciones, tanto en la web como en nuestro repositorio de GitHub, están sujetas a nuestro"
|
||||
cla_url: "CLA"
|
||||
contributor_description_suffix: "con el que deberás estar de acuerdo antes de colaborar."
|
||||
# code_title: "Code - MIT"
|
||||
# code_description_prefix: "All code owned by CodeCombat or hosted on codecombat.com, both in the GitHub repository or in the codecombat.com database, is licensed under the"
|
||||
# mit_license_url: "MIT license"
|
||||
mit_license_url: "Licencia MIT"
|
||||
# code_description_suffix: "This includes all code in Systems and Components that are made available by CodeCombat for the purpose of creating levels."
|
||||
# art_title: "Art/Music - Creative Commons "
|
||||
# art_description_prefix: "All common content is available under the"
|
||||
art_title: "Arte/Música - Creative Commons "
|
||||
art_description_prefix: "Todo el contenido común está disponible bajo la"
|
||||
# cc_license_url: "Creative Commons Attribution 4.0 International License"
|
||||
# art_description_suffix: "Common content is anything made generally available by CodeCombat for the purpose of creating Levels. This includes:"
|
||||
# art_music: "Music"
|
||||
# art_sound: "Sound"
|
||||
art_music: "Música"
|
||||
art_sound: "Sonido"
|
||||
# art_artwork: "Artwork"
|
||||
# art_sprites: "Sprites"
|
||||
# art_other: "Any and all other non-code creative works that are made available when creating Levels."
|
||||
|
@ -339,8 +339,8 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
# nutshell_description: "Any resources we provide in the Level Editor are free to use as you like for creating Levels. But we reserve the right to restrict distribution of the Levels themselves (that are created on codecombat.com) so that they may be charged for in the future, if that's what ends up happening."
|
||||
# canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence."
|
||||
|
||||
# contribute:
|
||||
# page_title: "Contributing"
|
||||
contribute:
|
||||
page_title: "Colaborar"
|
||||
# character_classes_title: "Character Classes"
|
||||
# introduction_desc_intro: "We have high hopes for CodeCombat."
|
||||
# introduction_desc_pref: "We want to be where programmers of all stripes come to learn and play together, introduce others to the wonderful world of coding, and reflect the best parts of the community. We can't and don't want to do that alone; what makes projects like GitHub, Stack Overflow and Linux great are the people who use them and build on them. To that end, "
|
||||
|
@ -424,8 +424,8 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
# translating_diplomats: "Our Translating Diplomats:"
|
||||
# helpful_ambassadors: "Our Helpful Ambassadors:"
|
||||
|
||||
# classes:
|
||||
# archmage_title: "Archmage"
|
||||
classes:
|
||||
archmage_title: "Archimago"
|
||||
# archmage_title_description: "(Coder)"
|
||||
# artisan_title: "Artisan"
|
||||
# artisan_title_description: "(Level Builder)"
|
||||
|
|
|
@ -205,46 +205,46 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
|
|||
|
||||
editor:
|
||||
main_title: "CodeCombat szerkesztők"
|
||||
main_description: "KLészíts saját pályákat, hadjáratokat, egységeket és oktatési célú tartalmakat. Mi megadunk hozzá minden eszközt amire csak szükséged lehet!"
|
||||
# article_title: "Article Editor"
|
||||
# article_description: "Write articles that give players overviews of programming concepts which can be used across a variety of levels and campaigns."
|
||||
# thang_title: "Thang Editor"
|
||||
# thang_description: "Build units, defining their default logic, graphics and audio. Currently only supports importing Flash exported vector graphics."
|
||||
# level_title: "Level Editor"
|
||||
# level_description: "Includes the tools for scripting, uploading audio, and constructing custom logic to create all sorts of levels. Everything we use ourselves!"
|
||||
# security_notice: "Many major features in these editors are not currently enabled by default. As we improve the security of these systems, they will be made generally available. If you'd like to use these features sooner, "
|
||||
# contact_us: "contact us!"
|
||||
# hipchat_prefix: "You can also find us in our"
|
||||
# hipchat_url: "HipChat room."
|
||||
# level_some_options: "Some Options?"
|
||||
# level_tab_thangs: "Thangs"
|
||||
# level_tab_scripts: "Scripts"
|
||||
# level_tab_settings: "Settings"
|
||||
# level_tab_components: "Components"
|
||||
# level_tab_systems: "Systems"
|
||||
# level_tab_thangs_title: "Current Thangs"
|
||||
# level_tab_thangs_conditions: "Starting Conditions"
|
||||
# level_tab_thangs_add: "Add Thangs"
|
||||
# level_settings_title: "Settings"
|
||||
# level_component_tab_title: "Current Components"
|
||||
# level_component_btn_new: "Create New Component"
|
||||
# level_systems_tab_title: "Current Systems"
|
||||
# level_systems_btn_new: "Create New System"
|
||||
# level_systems_btn_add: "Add System"
|
||||
# level_components_title: "Back to All Thangs"
|
||||
# level_components_type: "Type"
|
||||
# level_component_edit_title: "Edit Component"
|
||||
# level_system_edit_title: "Edit System"
|
||||
# create_system_title: "Create New System"
|
||||
# new_component_title: "Create New Component"
|
||||
# new_component_field_system: "System"
|
||||
main_description: "Készíts saját pályákat, hadjáratokat, egységeket és oktatési célú tartalmakat. Mi megadunk hozzá minden eszközt amire csak szükséged lehet!"
|
||||
article_title: "Cikk szerkesztő"
|
||||
article_description: "Írhatsz cikkeket, hogy átfogó képet adhass olyan programozási szemléletekről, melyeket a különböző pályákon és küldetések során felhasználhatnak."
|
||||
thang_title: "Eszköz szerkesztő"
|
||||
thang_description: "Építs egységeket, határozd meg az működésüket, kinézetüket és hangjukat. Jelenleg csak a Flash-ből exportált vektorgrafika támogatott."
|
||||
level_title: "Pálya szerkesztő"
|
||||
level_description: "Mindent magába foglal, ami kódolás, hangok feltöltése, és a pályák teljesen egyedi felépítése. Minden, amit mi használunk!"
|
||||
security_notice: "Számos főbb funkció ezekben a szerkesztőkben még nincs engedélyezve alapesetben. Amint a rendszer biztonságát növelni tudjuk, elérhetővé teszzük ezeket. Ha a későbbiekben használni szeretnéf ezeket a funkciókat, "
|
||||
contact_us: "lépj kapcsolatba velünk!"
|
||||
hipchat_prefix: "Megtalálhatsz bennünket a "
|
||||
hipchat_url: "HipChat szobában."
|
||||
level_some_options: "Néhány beállítás?"
|
||||
level_tab_thangs: "Eszközök"
|
||||
level_tab_scripts: "Kódok"
|
||||
level_tab_settings: "Beállítások"
|
||||
level_tab_components: "Komponensek"
|
||||
level_tab_systems: "Rendszerek"
|
||||
level_tab_thangs_title: "Jelenlegi eszközök"
|
||||
level_tab_thangs_conditions: "Kezdő feltételek"
|
||||
level_tab_thangs_add: "Eszköz hozzáadása"
|
||||
level_settings_title: "Beállítások"
|
||||
level_component_tab_title: "Jelenlegi komponensek"
|
||||
level_component_btn_new: "Új komponens készítése"
|
||||
level_systems_tab_title: "Jelenlegi rendszerek"
|
||||
level_systems_btn_new: "Új rendszer készítése"
|
||||
level_systems_btn_add: "Rendszer hozzáadása"
|
||||
level_components_title: "Vissza az összes eszközhöz"
|
||||
level_components_type: "Típus"
|
||||
level_component_edit_title: "Komponens szerkesztése"
|
||||
level_system_edit_title: "Rendszer szerkesztése"
|
||||
create_system_title: "Új rendszer készítése"
|
||||
new_component_title: "Új komponens készítése"
|
||||
new_component_field_system: "Rendszer"
|
||||
|
||||
# article:
|
||||
# edit_btn_preview: "Preview"
|
||||
# edit_article_title: "Edit Article"
|
||||
article:
|
||||
edit_btn_preview: "Előnézet"
|
||||
edit_article_title: "Cikk szerkesztése"
|
||||
|
||||
general:
|
||||
# and: "and"
|
||||
and: "és"
|
||||
or: "vagy "
|
||||
name: "Név"
|
||||
# body: "Body"
|
||||
|
|
|
@ -3,3 +3,4 @@ CocoModel = require('./CocoModel')
|
|||
module.exports = class Article extends CocoModel
|
||||
@className: "Article"
|
||||
urlRoot: "/db/article"
|
||||
saveBackups: true
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
storage = require 'lib/storage'
|
||||
|
||||
class CocoSchema extends Backbone.Model
|
||||
constructor: (path, args...) ->
|
||||
super(args...)
|
||||
|
@ -9,6 +11,7 @@ class CocoModel extends Backbone.Model
|
|||
idAttribute: "_id"
|
||||
loaded: false
|
||||
loading: false
|
||||
saveBackups: false
|
||||
@schema: null
|
||||
|
||||
initialize: ->
|
||||
|
@ -20,15 +23,32 @@ class CocoModel extends Backbone.Model
|
|||
@addSchemaDefaults()
|
||||
else
|
||||
@loadSchema()
|
||||
@once 'sync', @onLoaded
|
||||
@once 'sync', @onLoaded, @
|
||||
@saveBackup = _.debounce(@saveBackup, 500)
|
||||
|
||||
type: ->
|
||||
@constructor.className
|
||||
|
||||
onLoaded: =>
|
||||
onLoaded: ->
|
||||
@loaded = true
|
||||
@loading = false
|
||||
@markToRevert()
|
||||
if @saveBackups
|
||||
existing = storage.load @id
|
||||
if existing
|
||||
@set(existing, {silent:true})
|
||||
CocoModel.backedUp[@id] = @
|
||||
|
||||
set: ->
|
||||
res = super(arguments...)
|
||||
@saveBackup() if @saveBackups and @loaded
|
||||
res
|
||||
|
||||
saveBackup: ->
|
||||
storage.save(@id, @attributes)
|
||||
CocoModel.backedUp[@id] = @
|
||||
|
||||
@backedUp = {}
|
||||
|
||||
loadSchema: ->
|
||||
unless @constructor.schema
|
||||
|
@ -38,7 +58,6 @@ class CocoModel extends Backbone.Model
|
|||
@constructor.schema.on 'sync', =>
|
||||
@constructor.schema.loaded = true
|
||||
@addSchemaDefaults()
|
||||
@markToRevert()
|
||||
@trigger 'schema-loaded'
|
||||
|
||||
@hasSchema: -> return @schema?.loaded
|
||||
|
@ -57,6 +76,7 @@ class CocoModel extends Backbone.Model
|
|||
@trigger "save:success", @
|
||||
success(@, resp) if success
|
||||
@markToRevert()
|
||||
@clearBackup()
|
||||
@trigger "save", @
|
||||
return super attrs, options
|
||||
|
||||
|
@ -69,6 +89,10 @@ class CocoModel extends Backbone.Model
|
|||
|
||||
revert: ->
|
||||
@set(@_revertAttributes, {silent: true}) if @_revertAttributes
|
||||
@clearBackup()
|
||||
|
||||
clearBackup: ->
|
||||
storage.remove @id
|
||||
|
||||
hasLocalChanges: ->
|
||||
not _.isEqual @attributes, @_revertAttributes
|
||||
|
|
|
@ -6,6 +6,7 @@ class SuperModel
|
|||
|
||||
populateModel: (model) ->
|
||||
@mustPopulate = model
|
||||
model.saveBackups = @shouldSaveBackups(model)
|
||||
model.fetch() unless model.loaded or model.loading
|
||||
model.on('sync', @modelLoaded) unless model.loaded
|
||||
model.once('error', @modelErrored) unless model.loaded
|
||||
|
@ -13,7 +14,9 @@ class SuperModel
|
|||
@models[url] = model unless @models[url]?
|
||||
@modelLoaded(model) if model.loaded
|
||||
|
||||
shouldPopulate: (url) -> return true # replace or overwrite
|
||||
# replace or overwrite
|
||||
shouldPopulate: (url) -> return true
|
||||
shouldSaveBackups: (model) -> return false
|
||||
|
||||
modelErrored: (model) =>
|
||||
@trigger 'error'
|
||||
|
@ -25,6 +28,7 @@ class SuperModel
|
|||
refs = [] unless @mustPopulate is model or @shouldPopulate(model)
|
||||
# console.log 'Loaded', model.get('name')
|
||||
for ref, i in refs
|
||||
ref.saveBackups = @shouldSaveBackups(ref)
|
||||
refURL = ref.url()
|
||||
continue if @models[refURL]
|
||||
@models[refURL] = ref
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#editor-thang-type-edit-view
|
||||
#save-button
|
||||
#save-button, #revert-button
|
||||
float: right
|
||||
margin-right: 20px
|
||||
|
||||
|
|
3
app/styles/modal/revert.sass
Normal file
3
app/styles/modal/revert.sass
Normal file
|
@ -0,0 +1,3 @@
|
|||
#revert-modal
|
||||
table
|
||||
width: 100%
|
|
@ -31,13 +31,13 @@
|
|||
|
||||
#cast-button-view
|
||||
display: none
|
||||
position: absolute
|
||||
width: 35%
|
||||
|
||||
.cast-button-group
|
||||
position: absolute
|
||||
top: 55px
|
||||
left: 20px
|
||||
z-index: 2
|
||||
@include opacity(77)
|
||||
width: 100%
|
||||
|
||||
.button-progress-overlay
|
||||
position: absolute
|
||||
|
@ -75,7 +75,8 @@
|
|||
padding: 3px 10px
|
||||
|
||||
.cast-button
|
||||
width: 90px
|
||||
width: 100%
|
||||
height: 29px
|
||||
|
||||
.autocast-delays
|
||||
min-width: 0
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
.problem-alert
|
||||
z-index: 10
|
||||
position: absolute
|
||||
bottom: -70px
|
||||
bottom: -110px
|
||||
left: 10px
|
||||
right: 10px
|
||||
background: transparent url(/images/level/code_editor_error_background.png) no-repeat
|
||||
|
|
|
@ -21,19 +21,26 @@
|
|||
|
||||
.save-status
|
||||
display: none
|
||||
position: relative
|
||||
padding-top: 2px
|
||||
padding-left: 10px
|
||||
position: absolute
|
||||
bottom: 2%
|
||||
left: 1%
|
||||
|
||||
.firepad
|
||||
width: 100%
|
||||
height: 100%
|
||||
@include box-sizing(border-box)
|
||||
// When Firepad is active, it wraps .ace_editor in .firepad.
|
||||
width: 98%
|
||||
height: 83%
|
||||
height: -webkit-calc(100% - 60px - 40px)
|
||||
height: calc(100% - 60px - 40px)
|
||||
|
||||
.ace_editor
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
.ace_editor
|
||||
@include box-sizing(border-box)
|
||||
margin-top: 40px
|
||||
width: 100%
|
||||
// When Firepad isn't active, .ace_editor needs the width/height set itself.
|
||||
width: 98%
|
||||
height: 83%
|
||||
height: -webkit-calc(100% - 60px - 40px)
|
||||
height: calc(100% - 60px - 40px)
|
||||
|
@ -61,9 +68,9 @@
|
|||
.executing, .executed, .problem-marker-info, .problem-marker-warning, .problem-marker-error
|
||||
position: absolute
|
||||
.executing
|
||||
background-color: rgba(216, 255, 255, 0.55)
|
||||
background-color: rgba(216, 255, 255, 0.85)
|
||||
.executed
|
||||
background-color: rgba(216, 255, 255, 0.25)
|
||||
background-color: rgba(245, 255, 6, 0.18)
|
||||
.problem-marker-info
|
||||
background-color: rgba(96, 63, 84, 0.25)
|
||||
.problem-marker-warning
|
||||
|
@ -79,7 +86,7 @@
|
|||
.ace_marker-layer
|
||||
.ace_bracket
|
||||
// Override faint gray
|
||||
border-color: #8FF
|
||||
border-color: #BFF
|
||||
|
||||
.ace_identifier
|
||||
background-color: rgba(255, 128, 128, 0.15)
|
||||
border-bottom: 1px dotted rgba(255, 128, 128, 0.45)
|
||||
|
|
|
@ -1,54 +1,82 @@
|
|||
@import "../../../bootstrap/mixins"
|
||||
|
||||
.spell-toolbar-view
|
||||
position: absolute
|
||||
z-index: 2
|
||||
top: 2px
|
||||
left: 5px
|
||||
position: relative
|
||||
box-sizing: border-box
|
||||
padding-left: 150px
|
||||
height: 36px
|
||||
width: 95%
|
||||
width: -webkit-calc(95% - 5px)
|
||||
width: calc(95% - 5px)
|
||||
background-color: rgba(100, 45, 210, 0.05)
|
||||
margin: 4px 1%
|
||||
height: 45px
|
||||
width: 97%
|
||||
//background-color: rgba(100, 45, 210, 0.15)
|
||||
|
||||
.spell-progress
|
||||
position: relative
|
||||
height: 100%
|
||||
width: 50%
|
||||
display: inline-block
|
||||
.flow
|
||||
&:hover .spell-progress
|
||||
opacity: 1
|
||||
|
||||
.progress
|
||||
.spell-progress
|
||||
position: absolute
|
||||
left: 0px
|
||||
top: 8px
|
||||
bottom: 0px
|
||||
width: 100%
|
||||
height: 100%
|
||||
width: 40%
|
||||
left: 45%
|
||||
display: inline-block
|
||||
cursor: pointer
|
||||
overflow: visible
|
||||
|
||||
.bar
|
||||
@include transition(width .0s linear)
|
||||
position: relative
|
||||
box-sizing: border-box
|
||||
opacity: 0.25
|
||||
|
||||
.progress
|
||||
position: absolute
|
||||
left: 0px
|
||||
top: 12.5px
|
||||
bottom: 0px
|
||||
width: 100%
|
||||
height: 4px
|
||||
overflow: visible
|
||||
pointer-events: none
|
||||
background-color: #67A4C8
|
||||
width: 50%
|
||||
|
||||
.scrubber-handle
|
||||
position: absolute
|
||||
|
||||
.bar
|
||||
@include transition(width .0s linear)
|
||||
position: relative
|
||||
pointer-events: none
|
||||
right: -16px
|
||||
top: -7px
|
||||
background: transparent url(/images/level/playback_thumb.png)
|
||||
width: 32px
|
||||
height: 32px
|
||||
background: linear-gradient(#2c3e5f, #2c3e5f 16%, #3a537f 16%, #3a537f 83%, #2c3e5f 84%, #2c3e5f)
|
||||
width: 50%
|
||||
pointer-events: none
|
||||
|
||||
.scrubber-handle
|
||||
position: absolute
|
||||
pointer-events: none
|
||||
right: -5px
|
||||
top: -12.5px
|
||||
background: linear-gradient(#2c3e5f, #2c3e5f 16%, #3a537f 16%, #3a537f 83%, #2c3e5f 84%, #2c3e5f)
|
||||
width: 14px
|
||||
height: 29px
|
||||
border-radius: 3px
|
||||
box-sizing: border-box
|
||||
border: 1px solid black
|
||||
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5)
|
||||
|
||||
&:hover .steppers
|
||||
opacity: 1
|
||||
|
||||
.btn-group
|
||||
// I don't know, I can figure this out for real later
|
||||
margin: -26px 0 0 18px
|
||||
|
||||
.metrics
|
||||
display: inline-block
|
||||
margin: -30px 0 0 10px
|
||||
vertical-align: middle
|
||||
.steppers
|
||||
position: absolute
|
||||
z-index: 2
|
||||
width: 10%
|
||||
right: 2%
|
||||
box-sizing: border-box
|
||||
opacity: 0.25
|
||||
|
||||
button
|
||||
height: 29px
|
||||
|
||||
.metrics
|
||||
display: none
|
||||
top: 30px
|
||||
position: absolute
|
||||
z-index: 10
|
||||
pointer-events: none
|
||||
padding: 10px
|
||||
background: transparent url(/images/level/popover_background.png)
|
||||
background-size: 100% 100%
|
||||
font-variant: small-caps
|
||||
text-overflow: ellipsis
|
||||
font-size: 13px
|
||||
white-space: nowrap
|
||||
|
|
|
@ -8,7 +8,7 @@ block content
|
|||
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
|
||||
|
||||
else
|
||||
button.btn#save-button.disabled.hide(data-i18n="account_settings.autosave") Changes Save Automatically
|
||||
button.btn#save-button.disabled.hide(data-i18n="account_settings.saveBackups") Changes Save Automatically
|
||||
|
||||
ul.nav.nav-tabs#settings-tabs
|
||||
li
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
extends /templates/base
|
||||
|
||||
block content
|
||||
button(data-toggle="coco-modal", data-target="modal/revert", data-i18n="revert.revert").btn.btn-primary#revert-button Revert
|
||||
button(data-i18n="article.edit_btn_preview").btn.btn-primary#preview-button Preview
|
||||
button(data-toggle="coco-modal", data-target="modal/save_version", data-i18n="common.save").btn.btn-primary#save-button Save
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ block outer_content
|
|||
span.level-title #{level.attributes.name}
|
||||
|
||||
.level-control-buttons
|
||||
button(data-toggle="coco-modal", data-target="modal/revert", data-i18n="revert.revert").btn.btn-primary#revert-button Revert
|
||||
button(data-i18n="common.save").btn.btn-primary#commit-level-start-button Save
|
||||
button(data-i18n="common.fork").btn.btn-primary#fork-level-start-button Fork
|
||||
.btn-group.play-button-group
|
||||
|
|
|
@ -6,6 +6,7 @@ block content
|
|||
|
||||
button.btn.btn-primary#save-button(data-toggle="coco-modal", data-target="modal/save_version")
|
||||
| Save
|
||||
button.btn.btn-primary#revert-button(data-toggle="coco-modal", data-target="modal/revert", data-i18n="revert.revert") Revert
|
||||
|
||||
h3 Edit Thang Type: "#{thangType.attributes.name}"
|
||||
|
||||
|
|
13
app/templates/modal/revert.jade
Normal file
13
app/templates/modal/revert.jade
Normal file
|
@ -0,0 +1,13 @@
|
|||
extends /templates/modal/modal_base
|
||||
|
||||
block modal-header-content
|
||||
h3(data-i18n="revert.revert_models") Revert Models
|
||||
|
||||
block modal-body-content
|
||||
table.table.table-striped#changed-models
|
||||
for model in models
|
||||
tr
|
||||
td
|
||||
| #{model.type()}: #{model.get('name')}
|
||||
td
|
||||
button(value=model.id) Revert
|
|
@ -1,7 +1,7 @@
|
|||
div.btn-group.cast-button-group
|
||||
.button-progress-overlay
|
||||
button.btn.btn-inverse.banner.cast-button(title=castShortcutVerbose + ": Cast current spell", data-i18n="play_level.tome_cast_button_cast") Spell Cast
|
||||
button.btn.btn-inverse.banner.cast-options-button.dropdown-toggle(data-toggle="dropdown")
|
||||
button.btn.btn-inverse.btn-large.banner.cast-button(title=castShortcutVerbose + ": Cast current spell", data-i18n="play_level.tome_cast_button_cast") Spell Cast
|
||||
button.btn.btn-inverse.btn-large.banner.cast-options-button.dropdown-toggle(data-toggle="dropdown")
|
||||
i.icon-cog.icon-white
|
||||
|
||||
ul.dropdown-menu.autocast-delays
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
.spell-progress
|
||||
.progress
|
||||
.bar
|
||||
.scrubber-handle
|
||||
.flow
|
||||
|
||||
.btn-group
|
||||
button.btn.btn-mini.btn-inverse.banner.step-backward(title="Ctrl/Cmd + Alt + [: Step Backward")
|
||||
i.icon-arrow-left.icon-white
|
||||
button.btn.btn-mini.btn-inverse.banner.step-forward(title="Ctrl/Cmd + Alt + ]: Step Forward")
|
||||
i.icon-arrow-right.icon-white
|
||||
|
||||
.metrics
|
||||
code.statements-metric
|
||||
span.metric.statement-index
|
||||
| /
|
||||
span.metric.statements-executed
|
||||
span.metric.statements-executed-total
|
||||
|
|
||||
code.calls-metric
|
||||
span.metric.call-index
|
||||
| /
|
||||
span.metric.calls-executed
|
||||
.spell-progress
|
||||
.progress
|
||||
.bar
|
||||
.scrubber-handle
|
||||
|
||||
.btn-group.steppers
|
||||
button.btn.btn-mini.btn-inverse.banner.step-backward(title="Ctrl/Cmd + Alt + [: Step Backward")
|
||||
i.icon-arrow-left.icon-white
|
||||
button.btn.btn-mini.btn-inverse.banner.step-forward(title="Ctrl/Cmd + Alt + ]: Step Forward")
|
||||
i.icon-arrow-right.icon-white
|
||||
|
||||
.metrics
|
||||
.statements-metric
|
||||
| Statement
|
||||
span.metric.statement-index
|
||||
| /
|
||||
span.metric.statements-executed
|
||||
span.metric.statements-executed-total
|
||||
.calls-metric
|
||||
| Call
|
||||
span.metric.call-index
|
||||
| /
|
||||
span.metric.calls-executed
|
||||
|
|
|
@ -16,6 +16,7 @@ module.exports = class ArticleEditView extends View
|
|||
constructor: (options, @articleID) ->
|
||||
super options
|
||||
@article = new Article(_id: @articleID)
|
||||
@article.saveBackups = true
|
||||
@article.fetch()
|
||||
@article.once('sync', @onArticleSync)
|
||||
@article.on('schema-loaded', @buildTreema)
|
||||
|
@ -43,6 +44,8 @@ module.exports = class ArticleEditView extends View
|
|||
@treema.build()
|
||||
|
||||
pushChangesToPreview: =>
|
||||
for key, value of @treema.data
|
||||
@article.set(key, value)
|
||||
return unless @treema and @preview
|
||||
m = marked(@treema.data.body)
|
||||
b = $(@preview.document.body)
|
||||
|
|
|
@ -31,8 +31,16 @@ module.exports = class ComponentsTabView extends View
|
|||
haveThisComponent.push thang.id if haveThisComponent.length < 100 # for performance when adding many Thangs
|
||||
return if _.isEqual presentComponents, @presentComponents
|
||||
@presentComponents = presentComponents
|
||||
treemaData = _.sortBy ({original: key.split('.')[0], majorVersion: parseInt(key.split('.')[1], 10), thangs: value, count: value.length} for key, value of @presentComponents), "count"
|
||||
treemaData.reverse()
|
||||
|
||||
componentModels = @supermodel.getModels LevelComponent
|
||||
componentModelMap = {}
|
||||
componentModelMap[comp.get('original')] = comp for comp in componentModels
|
||||
components = ({original: key.split('.')[0], majorVersion: parseInt(key.split('.')[1], 10), thangs: value, count: value.length} for key, value of @presentComponents)
|
||||
treemaData = _.sortBy components, (comp) ->
|
||||
comp = componentModelMap[comp.original]
|
||||
res = [comp.get('system'), comp.get('name')]
|
||||
return res
|
||||
|
||||
treemaOptions =
|
||||
supermodel: @supermodel
|
||||
schema: {type: 'array', items: {type: 'object', format: 'level-component'}}
|
||||
|
|
|
@ -36,6 +36,9 @@ module.exports = class EditorLevelView extends View
|
|||
return false if @levelsLoaded > 1
|
||||
return true
|
||||
|
||||
@supermodel.shouldSaveBackups = (model) ->
|
||||
model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem']
|
||||
|
||||
@level = new Level _id: @levelID
|
||||
@level.once 'sync', @onLevelLoaded
|
||||
@supermodel.populateModel @level
|
||||
|
|
|
@ -30,6 +30,7 @@ module.exports = class SystemsTabView extends View
|
|||
for system in @buildDefaultSystems()
|
||||
url = "/db/level.system/#{system.original}/version/#{system.majorVersion}"
|
||||
ls = new LevelSystem()
|
||||
ls.saveBackups = true
|
||||
do (url) -> ls.url = -> url
|
||||
continue if @supermodel.getModelByURL ls.url
|
||||
ls.fetch()
|
||||
|
@ -57,7 +58,12 @@ module.exports = class SystemsTabView extends View
|
|||
unless systems.length
|
||||
systems = @buildDefaultSystems()
|
||||
insertedDefaults = true
|
||||
systems = _.sortBy systems, "name"
|
||||
|
||||
systemModels = @supermodel.getModels LevelSystem
|
||||
systemModelMap = {}
|
||||
systemModelMap[sys.get('original')] = sys.get('name') for sys in systemModels
|
||||
systems = _.sortBy systems, (sys) -> systemModelMap[sys.original]
|
||||
|
||||
treemaOptions =
|
||||
# TODO: somehow get rid of the + button, or repurpose it to open the LevelSystemAddView instead
|
||||
supermodel: @supermodel
|
||||
|
|
|
@ -39,6 +39,7 @@ module.exports = class ThangTypeEditView extends View
|
|||
super options
|
||||
@mockThang = _.cloneDeep(@mockThang)
|
||||
@thangType = new ThangType(_id: @thangTypeID)
|
||||
@thangType.saveBackups = true
|
||||
@thangType.fetch()
|
||||
@thangType.once('sync', @onThangTypeSync)
|
||||
@refreshAnimation = _.debounce @refreshAnimation, 500
|
||||
|
|
23
app/views/modal/revert_modal.coffee
Normal file
23
app/views/modal/revert_modal.coffee
Normal file
|
@ -0,0 +1,23 @@
|
|||
ModalView = require 'views/kinds/ModalView'
|
||||
template = require 'templates/modal/revert'
|
||||
CocoModel = require 'models/CocoModel'
|
||||
|
||||
module.exports = class RevertModal extends ModalView
|
||||
id: 'revert-modal'
|
||||
template: template
|
||||
|
||||
events:
|
||||
'click #changed-models button': 'onRevertModel'
|
||||
|
||||
onRevertModel: (e) ->
|
||||
id = $(e.target).val()
|
||||
CocoModel.backedUp[id].revert()
|
||||
$(e.target).closest('tr').remove()
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
models = _.values CocoModel.backedUp
|
||||
models = (m for m in models when m.hasLocalChanges())
|
||||
c.models = models
|
||||
c
|
||||
|
|
@ -32,6 +32,9 @@ module.exports = class CastButtonView extends View
|
|||
delay ?= 5000
|
||||
@setAutocastDelay delay
|
||||
|
||||
attachTo: (spellView) ->
|
||||
@$el.detach().prependTo(spellView.toolbarView.$el).show()
|
||||
|
||||
hookUpButtons: ->
|
||||
# hook up cast button callbacks
|
||||
@castButton = $('.cast-button', @$el)
|
||||
|
|
|
@ -56,7 +56,6 @@ module.exports = class Spell
|
|||
|
||||
createAether: (thang) ->
|
||||
aetherOptions =
|
||||
thisValue: thang.createUserContext()
|
||||
problems:
|
||||
jshint_W040: {level: "ignore"}
|
||||
aether_MissingThis: {level: (if thang.requiresThis then 'error' else 'warning')}
|
||||
|
@ -68,7 +67,7 @@ module.exports = class Spell
|
|||
#callIndex: 0
|
||||
#timelessVariables: ['i']
|
||||
#statementIndex: 9001
|
||||
if not (me.team in @permissions.readwrite)# or @name is 'chooseAction' or thang.id is 'Thoktar' # Gridmancer can't handle it
|
||||
if not (me.team in @permissions.readwrite) or window.currentView?.sessionID is "52bfb88099264e565d001349" # temp fix for debugger explosion bug
|
||||
#console.log "Turning off includeFlow for", @spellKey
|
||||
aetherOptions.includeFlow = false
|
||||
aether = new Aether aetherOptions
|
||||
|
|
|
@ -1,51 +1,76 @@
|
|||
View = require 'views/kinds/CocoView'
|
||||
template = require 'templates/play/level/tome/spell_debug'
|
||||
Range = ace.require("ace/range").Range
|
||||
TokenIterator = ace.require("ace/token_iterator").TokenIterator
|
||||
serializedClasses =
|
||||
Thang: require "lib/world/thang"
|
||||
Vector: require "lib/world/vector"
|
||||
Rectangle: require "lib/world/rectangle"
|
||||
|
||||
module.exports = class DebugView extends View
|
||||
className: 'spell-debug-view'
|
||||
template: template
|
||||
subscriptions: {}
|
||||
|
||||
subscriptions:
|
||||
'god:new-world-created': 'onNewWorld'
|
||||
|
||||
events: {}
|
||||
|
||||
constructor: (options) ->
|
||||
super options
|
||||
@ace = options.ace
|
||||
@thang = options.thang
|
||||
@variableStates = {}
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@ace.on "mousemove", @onMouseMove
|
||||
#@ace.on "click", onClick # same ACE API as mousemove
|
||||
|
||||
setVariableStates: (@variableStates) ->
|
||||
@update()
|
||||
|
||||
onMouseMove: (e) =>
|
||||
pos = e.getDocumentPosition()
|
||||
column = pos.column
|
||||
until column < 0
|
||||
if token = e.editor.session.getTokenAt pos.row, column
|
||||
break if token.type is 'identifier'
|
||||
column = token.start - 1
|
||||
else
|
||||
--column
|
||||
if token?.type is 'identifier' and token.value of @variableStates
|
||||
@variable = token.value
|
||||
endOfDoc = pos.row is @ace.getSession().getDocument().getLength() - 1
|
||||
it = new TokenIterator e.editor.session, pos.row, pos.column
|
||||
isIdentifier = (t) -> t and (t.type is 'identifier' or t.value is 'this')
|
||||
while it.getCurrentTokenRow() is pos.row and not isIdentifier(token = it.getCurrentToken())
|
||||
it.stepBackward()
|
||||
break unless token
|
||||
break if endOfDoc # Don't iterate backward on last line, since we might be way below.
|
||||
if isIdentifier token
|
||||
# This could be a property access, like "enemy.target.pos" or "this.spawnedRectangles".
|
||||
# We have to realize this and dig into the nesting of the objects.
|
||||
start = it.getCurrentTokenColumn()
|
||||
[chain, start, end] = [[token.value], start, start + token.value.length]
|
||||
while it.getCurrentTokenRow() is pos.row
|
||||
it.stepBackward()
|
||||
break unless it.getCurrentToken()?.value is "."
|
||||
it.stepBackward()
|
||||
token = null # If we're doing a complex access like this.getEnemies().length, then length isn't a valid var.
|
||||
break unless isIdentifier(prev = it.getCurrentToken())
|
||||
token = prev
|
||||
start = it.getCurrentTokenColumn()
|
||||
chain.unshift token.value
|
||||
if token and (token.value of @variableStates or token.value is "this")
|
||||
@variableChain = chain
|
||||
@pos = {left: e.domEvent.offsetX + 50, top: e.domEvent.offsetY + 50}
|
||||
@markerRange = new Range pos.row, token.start, pos.row, token.start + token.value.length
|
||||
@markerRange = new Range pos.row, start, pos.row, end
|
||||
else
|
||||
@variable = @markerRange = null
|
||||
@variableChain = @markerRange = null
|
||||
@update()
|
||||
|
||||
onMouseOut: (e) =>
|
||||
@variable = @markerRange = null
|
||||
@variableChain = @markerRange = null
|
||||
@update()
|
||||
|
||||
onNewWorld: (e) ->
|
||||
@thang = @options.thang = e.world.thangMap[@thang.id] if @thang
|
||||
|
||||
update: ->
|
||||
if @variable
|
||||
value = @variableStates[@variable]
|
||||
@$el.find("code").text "#{@variable}: #{value}"
|
||||
if @variableChain
|
||||
{key, value} = @deserializeVariableChain @variableChain
|
||||
@$el.find("code").text "#{key}: #{value}"
|
||||
@$el.show().css(@pos)
|
||||
else
|
||||
@$el.hide()
|
||||
|
@ -58,6 +83,28 @@ module.exports = class DebugView extends View
|
|||
if @markerRange
|
||||
@marker = @ace.getSession().addMarker @markerRange, "ace_bracket", "text"
|
||||
|
||||
deserializeVariableChain: (chain) ->
|
||||
keys = []
|
||||
for prop, i in chain
|
||||
if prop is "this"
|
||||
value = @thang
|
||||
else
|
||||
value = (if i is 0 then @variableStates else value)[prop]
|
||||
keys.push prop
|
||||
break unless value
|
||||
if theClass = serializedClasses[value.CN]
|
||||
if value.CN is "Thang"
|
||||
thang = @thang.world.thangMap[value.id]
|
||||
value = thang or "<Thang #{value.id} (non-existent)>"
|
||||
else
|
||||
value = theClass.deserializeFromAether(value)
|
||||
if value and not _.isString value
|
||||
if value.constructor?.className is "Thang"
|
||||
value = "<#{value.spriteName} - #{value.id}, #{if value.pos then value.pos.toString() else 'non-physical'}>"
|
||||
else
|
||||
value = value.toString()
|
||||
key: keys.join("."), value: value
|
||||
|
||||
destroy: ->
|
||||
super()
|
||||
@ace?.removeEventListener "mousemove", @onMouseMove
|
||||
|
|
|
@ -4,14 +4,15 @@ template = require 'templates/play/level/tome/spell_toolbar'
|
|||
module.exports = class SpellToolbarView extends View
|
||||
className: 'spell-toolbar-view'
|
||||
template: template
|
||||
progressHoverDelay: 500
|
||||
|
||||
subscriptions:
|
||||
'spell-step-backward': 'onStepBackward'
|
||||
'spell-step-forward': 'onStepForward'
|
||||
|
||||
events:
|
||||
'mousemove .progress': 'onProgressHover'
|
||||
'mouseout .progress': 'onProgressMouseOut'
|
||||
'mousemove .spell-progress': 'onProgressHover'
|
||||
'mouseout .spell-progress': 'onProgressMouseOut'
|
||||
'click .step-backward': 'onStepBackward'
|
||||
'click .step-forward': 'onStepForward'
|
||||
|
||||
|
@ -22,20 +23,25 @@ module.exports = class SpellToolbarView extends View
|
|||
afterRender: ->
|
||||
super()
|
||||
|
||||
toggleFlow: (to) ->
|
||||
@$el.find(".flow").toggle to
|
||||
|
||||
setStatementIndex: (statementIndex) ->
|
||||
return unless total = @callState?.statementsExecuted
|
||||
@statementIndex = Math.min(total - 1, Math.max(0, statementIndex))
|
||||
@statementRatio = @statementIndex / (total - 1)
|
||||
@statementTime = @callState.statements[@statementIndex]?.userInfo.time ? 0
|
||||
@$el.find('.bar').css('width', 100 * @statementRatio + '%')
|
||||
Backbone.Mediator.publish 'tome:spell-statement-index-updated', statementIndex: @statementIndex, ace: @ace
|
||||
@$el.find('.step-backward').prop('disabled', @statementIndex is 0)
|
||||
@$el.find('.step-forward').prop('disabled', @statementIndex is total - 1)
|
||||
@updateMetrics()
|
||||
_.defer =>
|
||||
Backbone.Mediator.publish 'tome:spell-statement-index-updated', statementIndex: @statementIndex, ace: @ace
|
||||
|
||||
updateMetrics: ->
|
||||
statementsExecuted = @callState.statementsExecuted
|
||||
$metrics = @$el.find('.metrics')
|
||||
return $metrics.hide() if @suppressMetricsUpdates or not (statementsExecuted or @metrics.statementsExecuted)
|
||||
if @metrics.callsExecuted > 1
|
||||
$metrics.find('.call-index').text @callIndex + 1
|
||||
$metrics.find('.calls-executed').text @metrics.callsExecuted
|
||||
|
@ -54,25 +60,40 @@ module.exports = class SpellToolbarView extends View
|
|||
$metrics.find('.statements-metric').show().attr('title', "Statement #{@statementIndex + 1} of #{statementsExecuted} this call#{titleSuffix}")
|
||||
else
|
||||
$metrics.find('.statements-metric').hide()
|
||||
left = @$el.find('.scrubber-handle').position().left + @$el.find('.spell-progress').position().left
|
||||
$metrics.finish().show().css({left: left - $metrics.width() / 2}).delay(2000).fadeOut('fast')
|
||||
|
||||
setStatementRatio: (ratio) ->
|
||||
return unless total = @callState?.statementsExecuted
|
||||
@setStatementIndex Math.floor ratio * total
|
||||
statementIndex = Math.floor ratio * total
|
||||
@setStatementIndex statementIndex unless statementIndex is @statementIndex
|
||||
|
||||
onProgressHover: (e) ->
|
||||
return @onProgressHoverLong(e) if @maintainIndexHover
|
||||
@lastHoverEvent = e
|
||||
@hoverTimeout = _.delay @onProgressHoverLong, @progressHoverDelay unless @hoverTimeout
|
||||
|
||||
onProgressHoverLong: (e) =>
|
||||
e ?= @lastHoverEvent
|
||||
@hoverTimeout = null
|
||||
@setStatementRatio e.offsetX / @$el.find('.progress').width()
|
||||
@updateTime()
|
||||
@maintainIndexHover = true
|
||||
@updateScroll()
|
||||
|
||||
onProgressMouseOut: (e) ->
|
||||
@maintainIndexHover = false
|
||||
if @hoverTimeout
|
||||
clearTimeout @hoverTimeout
|
||||
@hoverTimeout = null
|
||||
|
||||
onStepBackward: (e) -> @step -1
|
||||
onStepForward: (e) -> @step 1
|
||||
step: (delta) ->
|
||||
lastTime = @statementTime
|
||||
@setStatementIndex @statementIndex + delta
|
||||
@updateTime() if @statementIndex isnt lastTime
|
||||
@updateTime() if @statementTime isnt lastTime
|
||||
@updateScroll()
|
||||
|
||||
updateTime: ->
|
||||
@maintainIndexScrub = true
|
||||
|
@ -80,15 +101,18 @@ module.exports = class SpellToolbarView extends View
|
|||
@maintainIndexScrubTimeout = _.delay (=> @maintainIndexScrub = false), 500
|
||||
Backbone.Mediator.publish 'level-set-time', time: @statementTime, scrubDuration: 500
|
||||
|
||||
updateScroll: ->
|
||||
return unless statementStart = @callState?.statements?[@statementIndex]?.range[0]
|
||||
text = @ace.getValue()
|
||||
currentLine = text.substr(0, statementStart).split('\n').length - 1
|
||||
@ace.scrollToLine currentLine, true, true
|
||||
|
||||
setCallState: (callState, statementIndex, @callIndex, @metrics) ->
|
||||
return if callState is @callState and statementIndex is @statementIndex
|
||||
return unless @callState = callState
|
||||
@suppressMetricsUpdates = true
|
||||
if not @maintainIndexHover and not @maintainIndexScrub and statementIndex? and callState.statements[statementIndex]?.userInfo.time isnt @statementTime
|
||||
@setStatementIndex statementIndex
|
||||
else
|
||||
@setStatementRatio @statementRatio
|
||||
# Not sure yet whether it's better to maintain @statementIndex or @statementRatio
|
||||
#else if @statementRatio is 1 or not @statementIndex?
|
||||
# @setStatementRatio 1
|
||||
#else
|
||||
# @setStatementIndex @statementIndex
|
||||
@suppressMetricsUpdates = false
|
||||
|
|
|
@ -51,7 +51,6 @@ module.exports = class SpellView extends View
|
|||
else
|
||||
# needs to happen after the code generating this view is complete
|
||||
setTimeout @onLoaded, 1
|
||||
@createDebugView()
|
||||
|
||||
createACE: ->
|
||||
# Test themes and settings here: http://ace.ajax.org/build/kitchen-sink.html
|
||||
|
@ -69,6 +68,7 @@ module.exports = class SpellView extends View
|
|||
@ace.setShowPrintMargin false
|
||||
@ace.setShowInvisibles false
|
||||
@ace.setBehavioursEnabled false
|
||||
@ace.setAnimatedScroll true
|
||||
@toggleControls null, @writable
|
||||
@aceSession.selection.on 'changeCursor', @onCursorActivity
|
||||
$(@ace.container).find('.ace_gutter').on 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
|
||||
|
@ -154,14 +154,15 @@ module.exports = class SpellView extends View
|
|||
@spell.loaded = true
|
||||
Backbone.Mediator.publish 'tome:spell-loaded', spell: @spell
|
||||
@eventsSuppressed = false # Now that the initial change is in, we can start running any changed code
|
||||
@createToolbarView()
|
||||
|
||||
createDebugView: ->
|
||||
@debugView = new SpellDebugView ace: @ace
|
||||
@debugView = new SpellDebugView ace: @ace, thang: @thang
|
||||
@$el.append @debugView.render().$el.hide()
|
||||
|
||||
createToolbarView: ->
|
||||
@toolbarView = new SpellToolbarView ace: @ace
|
||||
@$el.prepend @toolbarView.render().$el
|
||||
@$el.append @toolbarView.render().$el
|
||||
|
||||
onMouseOut: (e) ->
|
||||
@debugView.onMouseOut e
|
||||
|
@ -174,6 +175,8 @@ module.exports = class SpellView extends View
|
|||
return if thang.id is @thang?.id
|
||||
@thang = thang
|
||||
@spellThang = @spell.thangs[@thang.id]
|
||||
@createDebugView() unless @debugView
|
||||
@debugView.thang = @thang
|
||||
@updateAether false, true
|
||||
@highlightCurrentLine()
|
||||
|
||||
|
@ -343,6 +346,7 @@ module.exports = class SpellView extends View
|
|||
@spellHasChanged = true
|
||||
|
||||
onSessionWillSave: (e) ->
|
||||
return unless @spellHasChanged
|
||||
setTimeout(=>
|
||||
unless @spellHasChanged
|
||||
@$el.find('.save-status').finish().show().fadeOut(2000)
|
||||
|
@ -432,35 +436,32 @@ module.exports = class SpellView extends View
|
|||
@debugView.setVariableStates {}
|
||||
@aceSession.removeGutterDecoration row, 'executing' for row in [0 ... @aceSession.getLength()]
|
||||
$(@ace.container).find('.ace_gutter-cell.executing').removeClass('executing')
|
||||
unless executed.length
|
||||
@toolbarView?.$el.hide()
|
||||
if not executed.length or (@spell.name is "plan" and @spellThang.castAether.metrics.statementsExecuted < 20)
|
||||
@toolbarView?.toggleFlow false
|
||||
return
|
||||
unless @toolbarView or (@spell.name is "plan" and @spellThang.castAether.metrics.statementsExecuted < 20)
|
||||
@createToolbarView()
|
||||
lastExecuted = _.last executed
|
||||
@toolbarView?.$el.show()
|
||||
@toolbarView?.toggleFlow true
|
||||
statementIndex = Math.max 0, lastExecuted.length - 1
|
||||
@toolbarView?.setCallState states[currentCallIndex], statementIndex, currentCallIndex, @spellThang.castAether.metrics
|
||||
marked = {}
|
||||
lastExecuted = lastExecuted[0 .. @toolbarView.statementIndex] if @toolbarView?.statementIndex?
|
||||
for state, i in lastExecuted
|
||||
#clazz = if state.executing then 'executing' else 'executed' # doesn't work
|
||||
[start, end] = [offsetToPos(state.range[0]), offsetToPos(state.range[1])]
|
||||
clazz = if i is lastExecuted.length - 1 then 'executing' else 'executed'
|
||||
if clazz is 'executed'
|
||||
key = state.range[0] + '_' + state.range[1]
|
||||
continue if marked[key] > 2 # don't allow more than three of the same marker
|
||||
marked[key] ?= 0
|
||||
++marked[key]
|
||||
continue if marked[start.row]
|
||||
marked[start.row] = true
|
||||
markerType = "fullLine"
|
||||
else
|
||||
@debugView.setVariableStates state.variables
|
||||
#console.log "at", state.userInfo.time, "vars are now:", state.variables
|
||||
[start, end] = [offsetToPos(state.range[0]), offsetToPos(state.range[1])]
|
||||
markerType = "text"
|
||||
markerRange = new Range(start.row, start.column, end.row, end.column)
|
||||
markerRange.start = @aceDoc.createAnchor markerRange.start
|
||||
markerRange.end = @aceDoc.createAnchor markerRange.end
|
||||
markerRange.id = @aceSession.addMarker markerRange, clazz, "text"
|
||||
markerRange.id = @aceSession.addMarker markerRange, clazz, markerType
|
||||
@markerRanges.push markerRange
|
||||
@aceSession.addGutterDecoration start.row, clazz if clazz is 'executing'
|
||||
null
|
||||
|
||||
onAnnotationClick: ->
|
||||
alertBox = $("<div class='alert alert-info fade in'>#{msg}</div>")
|
||||
|
|
|
@ -138,7 +138,7 @@ module.exports = class TomeView extends View
|
|||
@$el.find('#' + @spellTabView.id).after(@spellTabView.el).remove()
|
||||
@spellView.setThang thang
|
||||
@spellTabView.setThang thang
|
||||
@castButton.$el.show()
|
||||
@castButton.attachTo @spellView
|
||||
@thangList.$el.hide()
|
||||
@spellPaletteView = @insertSubView new SpellPaletteView thang: thang
|
||||
@spellPaletteView.toggleControls {}, @spellView.controlsEnabled # TODO: know when palette should have been disabled but didn't exist
|
||||
|
|
|
@ -70,8 +70,8 @@ module.exports = class PlayLevelView extends View
|
|||
$('body').append($("<img src='http://code.org/api/hour/begin_codecombat.png' style='visibility: hidden;'>"))
|
||||
window.tracker?.trackEvent 'Hour of Code Begin', {}
|
||||
|
||||
@isEditorPreview = @getQueryVariable "dev"
|
||||
@sessionID = @getQueryVariable "session"
|
||||
@isEditorPreview = @getQueryVariable 'dev'
|
||||
@sessionID = @getQueryVariable 'session'
|
||||
|
||||
$(window).on('resize', @onWindowResize)
|
||||
@supermodel.once 'error', =>
|
||||
|
@ -79,7 +79,14 @@ module.exports = class PlayLevelView extends View
|
|||
@$el.html('<div class="alert">' + msg + '</div>')
|
||||
@saveScreenshot = _.throttle @saveScreenshot, 30000
|
||||
|
||||
@load() unless @isEditorPreview
|
||||
if @isEditorPreview
|
||||
f = =>
|
||||
@supermodel.shouldSaveBackups = (model) ->
|
||||
model.constructor.className in ['Level', 'LevelComponent', 'LevelSystem']
|
||||
@load() unless @levelLoader
|
||||
setTimeout f, 100
|
||||
else
|
||||
@load()
|
||||
|
||||
# Save latest level played in local storage
|
||||
if localStorage?
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
"firepad": "~0.1.2",
|
||||
"marked": "~0.3.0",
|
||||
"moment": "~2.5.0",
|
||||
"aether": "~0.0.5",
|
||||
"aether": "~0.0.7",
|
||||
"underscore.string": "~2.3.3",
|
||||
"firebase": "~1.0.2"
|
||||
},
|
||||
|
|
|
@ -18,6 +18,7 @@ logging = require './server/commons/logging'
|
|||
sprites = require './server/routes/sprites'
|
||||
contact = require './server/routes/contact'
|
||||
languages = require './server/routes/languages'
|
||||
mail = require './server/routes/mail'
|
||||
|
||||
https = require 'https'
|
||||
http = require 'http'
|
||||
|
@ -82,6 +83,7 @@ contact.setupRoutes(app)
|
|||
file.setupRoutes(app)
|
||||
folder.setupRoutes(app)
|
||||
languages.setupRoutes(app)
|
||||
mail.setupRoutes(app)
|
||||
|
||||
# Some sort of cross-domain communication hack facebook requires
|
||||
app.get('/channel.html', (req, res) ->
|
||||
|
|
19
server/commons/mail.coffee
Normal file
19
server/commons/mail.coffee
Normal file
|
@ -0,0 +1,19 @@
|
|||
config = require '../../server_config'
|
||||
|
||||
module.exports.MAILCHIMP_LIST_ID = 'e9851239eb'
|
||||
module.exports.MAILCHIMP_GROUP_ID = '4529'
|
||||
module.exports.MAILCHIMP_GROUP_MAP =
|
||||
announcement: 'Announcements'
|
||||
tester: 'Adventurers'
|
||||
level_creator: 'Artisans'
|
||||
developer: 'Archmages'
|
||||
article_editor: 'Scribes'
|
||||
translator: 'Diplomats'
|
||||
support: 'Ambassadors'
|
||||
|
||||
nodemailer = require 'nodemailer'
|
||||
module.exports.transport = nodemailer.createTransport "SMTP",
|
||||
service: config.mail.service
|
||||
user: config.mail.username
|
||||
pass: config.mail.password
|
||||
authMethod: "LOGIN"
|
|
@ -4,8 +4,8 @@ LocalStrategy = require('passport-local').Strategy
|
|||
User = require('../users/User')
|
||||
UserHandler = require('../users/user_handler')
|
||||
config = require '../../server_config'
|
||||
nodemailer = require 'nodemailer'
|
||||
errors = require '../commons/errors'
|
||||
mail = require '../commons/mail'
|
||||
|
||||
module.exports.setupRoutes = (app) ->
|
||||
passport.serializeUser((user, done) -> done(null, user._id))
|
||||
|
@ -66,9 +66,8 @@ module.exports.setupRoutes = (app) ->
|
|||
user.save (err) =>
|
||||
return errors.serverError(res) if err
|
||||
if config.isProduction
|
||||
transport = createSMTPTransport()
|
||||
options = createMailOptions req.body.email, user.get('passwordReset')
|
||||
transport.sendMail options, (error, response) ->
|
||||
mail.transport.sendMail options, (error, response) ->
|
||||
if error
|
||||
console.error "Error sending mail: #{error.message or error}"
|
||||
return errors.serverError(res) if err
|
||||
|
@ -104,13 +103,4 @@ createMailOptions = (receiver, password) ->
|
|||
replyTo: config.mail.username
|
||||
subject: "[CodeCombat] Password Reset"
|
||||
text: "You can log into your account with: #{password}"
|
||||
#html: message.replace '\n', '<br>\n'
|
||||
|
||||
createSMTPTransport = ->
|
||||
return smtpTransport if smtpTransport
|
||||
smtpTransport = nodemailer.createTransport "SMTP",
|
||||
service: config.mail.service
|
||||
user: config.mail.username
|
||||
pass: config.mail.password
|
||||
authMethod: "LOGIN"
|
||||
smtpTransport
|
||||
#
|
|
@ -1,14 +1,13 @@
|
|||
config = require '../../server_config'
|
||||
winston = require 'winston'
|
||||
nodemailer = require 'nodemailer'
|
||||
mail = require '../commons/mail'
|
||||
|
||||
module.exports.setupRoutes = (app) ->
|
||||
app.post '/contact', (req, res) ->
|
||||
winston.info "Sending mail from #{req.body.email} saying #{req.body.message}"
|
||||
if config.isProduction
|
||||
transport = createSMTPTransport()
|
||||
options = createMailOptions req.body.email, req.body.message, req.user
|
||||
transport.sendMail options, (error, response) ->
|
||||
mail.transport.sendMail options, (error, response) ->
|
||||
if error
|
||||
winston.error "Error sending mail: #{error.message or error}"
|
||||
else
|
||||
|
@ -17,21 +16,10 @@ module.exports.setupRoutes = (app) ->
|
|||
|
||||
createMailOptions = (sender, message, user) ->
|
||||
# TODO: use email templates here
|
||||
console.log 'text is now', "#{message}\n\n#{user.get('name')}\nID: #{user._id}"
|
||||
options =
|
||||
from: config.mail.username
|
||||
to: config.mail.username
|
||||
replyTo: sender
|
||||
subject: "[CodeCombat] Feedback - #{sender}"
|
||||
text: "#{message}\n\nUsername: #{user.get('name') or 'Anonymous'}\nID: #{user._id}"
|
||||
#html: message.replace '\n', '<br>\n'
|
||||
|
||||
smtpTransport = null
|
||||
createSMTPTransport = ->
|
||||
return smtpTransport if smtpTransport
|
||||
smtpTransport = nodemailer.createTransport "SMTP",
|
||||
service: config.mail.service
|
||||
user: config.mail.username
|
||||
pass: config.mail.password
|
||||
authMethod: "LOGIN"
|
||||
smtpTransport
|
||||
#html: message.replace '\n', '<br>\n'
|
61
server/routes/mail.coffee
Normal file
61
server/routes/mail.coffee
Normal file
|
@ -0,0 +1,61 @@
|
|||
mail = require '../commons/mail'
|
||||
map = _.invert mail.MAILCHIMP_GROUP_MAP
|
||||
User = require '../users/User.coffee'
|
||||
errors = require '../commons/errors'
|
||||
#request = require 'request'
|
||||
config = require '../../server_config'
|
||||
|
||||
#badLog = (text) ->
|
||||
# console.log text
|
||||
# request.post 'http://requestb.in/1brdpaz1', { form: {log: text} }
|
||||
|
||||
module.exports.setupRoutes = (app) ->
|
||||
app.all config.mail.mailchimpWebhook, (req, res) ->
|
||||
post = req.body
|
||||
# badLog("Got post data: #{JSON.stringify(post, null, '\t')}")
|
||||
|
||||
unless post.type in ['unsubscribe', 'profile']
|
||||
res.send 'Bad post type'
|
||||
return res.end()
|
||||
|
||||
unless post.data.email
|
||||
res.send 'No email provided'
|
||||
return res.end()
|
||||
|
||||
query = {'mailChimp.leid':post.data.web_id}
|
||||
User.findOne query, (err, user) ->
|
||||
return errors.serverError(res) if err
|
||||
if not user
|
||||
return errors.notFound(res)
|
||||
|
||||
handleProfileUpdate(user, post) if post.type is 'profile'
|
||||
handleUnsubscribe(user) if post.type is 'unsubscribe'
|
||||
|
||||
user.updatedMailChimp = true # so as not to echo back to mailchimp
|
||||
user.save (err) ->
|
||||
return errors.serverError(res) if err
|
||||
res.end('Success')
|
||||
|
||||
|
||||
handleProfileUpdate = (user, post) ->
|
||||
groups = post.data.merges.INTERESTS.split(', ')
|
||||
groups = (map[g] for g in groups when map[g])
|
||||
otherSubscriptions = (g for g in user.get('emailSubscriptions') when not mail.MAILCHIMP_GROUP_MAP[g])
|
||||
groups = groups.concat otherSubscriptions
|
||||
user.set 'emailSubscriptions', groups
|
||||
|
||||
fname = post.data.merges.FNAME
|
||||
user.set('firstName', fname) if fname
|
||||
|
||||
lname = post.data.merges.LNAME
|
||||
user.set('lastName', lname) if lname
|
||||
|
||||
user.set 'mailChimp.email', post.data.email
|
||||
user.set 'mailChimp.euid', post.data.id
|
||||
|
||||
# badLog("Updating user object to: #{JSON.stringify(user.toObject(), null, '\t')}")
|
||||
|
||||
handleUnsubscribe = (user) ->
|
||||
user.set 'emailSubscriptions', []
|
||||
|
||||
# badLog("Unsubscribing user object to: #{JSON.stringify(user.toObject(), null, '\t')}")
|
|
@ -2,6 +2,7 @@ mongoose = require('mongoose')
|
|||
jsonschema = require('./user_schema')
|
||||
crypto = require('crypto')
|
||||
{salt, isProduction} = require('../../server_config')
|
||||
mail = require '../commons/mail'
|
||||
|
||||
sendwithus = require '../sendwithus'
|
||||
|
||||
|
@ -34,16 +35,17 @@ UserSchema.statics.updateMailChimp = (doc, callback) ->
|
|||
existingProps = doc.get('mailChimp')
|
||||
emailChanged = (not existingProps) or existingProps?.email isnt doc.get('email')
|
||||
emailSubs = doc.get('emailSubscriptions')
|
||||
newGroups = (groupingMap[name] for name in emailSubs when groupingMap[name]?)
|
||||
gm = mail.MAILCHIMP_GROUP_MAP
|
||||
newGroups = (gm[name] for name in emailSubs when gm[name]?)
|
||||
if (not existingProps) and newGroups.length is 0
|
||||
return callback?() # don't add totally unsubscribed people to the list
|
||||
subsChanged = doc.currentSubscriptions isnt JSON.stringify(emailSubs)
|
||||
return callback?() unless emailChanged or subsChanged
|
||||
|
||||
params = {}
|
||||
params.id = MAILCHIMP_LIST_ID
|
||||
params.id = mail.MAILCHIMP_LIST_ID
|
||||
params.email = if existingProps then {leid:existingProps.leid} else {email:doc.get('email')}
|
||||
params.merge_vars = { groupings: [ {id: MAILCHIMP_GROUP_ID, groups: newGroups} ] }
|
||||
params.merge_vars = { groupings: [ {id: mail.MAILCHIMP_GROUP_ID, groups: newGroups} ] }
|
||||
params.update_existing = true
|
||||
params.double_optin = false
|
||||
|
||||
|
@ -79,18 +81,6 @@ UserSchema.pre('save', (next) ->
|
|||
next()
|
||||
)
|
||||
|
||||
MAILCHIMP_LIST_ID = 'e9851239eb'
|
||||
MAILCHIMP_GROUP_ID = '4529'
|
||||
|
||||
groupingMap =
|
||||
announcement: 'Announcements'
|
||||
tester: 'Adventurers'
|
||||
level_creator: 'Artisans'
|
||||
developer: 'Archmages'
|
||||
article_editor: 'Scribes'
|
||||
translator: 'Diplomats'
|
||||
support: 'Ambassadors'
|
||||
|
||||
UserSchema.post 'save', (doc) ->
|
||||
UserSchema.statics.updateMailChimp(doc)
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ config.mail.service = process.env.COCO_MAIL_SERVICE_NAME || "Zoho";
|
|||
config.mail.username = process.env.COCO_MAIL_SERVICE_USERNAME || "";
|
||||
config.mail.password = process.env.COCO_MAIL_SERVICE_PASSWORD || "";
|
||||
config.mail.mailchimpAPIKey = process.env.COCO_MAILCHIMP_API_KEY || '';
|
||||
config.mail.mailchimpWebhook = process.env.COCO_MAILCHIMP_WEBHOOK || '/mail/webhook';
|
||||
config.mail.sendwithusAPIKey = process.env.COCO_SENDWITHUS_API_KEY || '';
|
||||
|
||||
config.salt = process.env.COCO_SALT || 'pepper';
|
||||
|
|
Loading…
Reference in a new issue