Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-08-13 16:39:44 -07:00
commit 7728958d40
147 changed files with 3527 additions and 739 deletions

View file

@ -1,12 +0,0 @@
// Fixtures
db.achievements.insert({
query: '{"level.original": "52d97ecd32362bc86e004e87"}',
index: true,
slug: 'dungeon-arena-started',
name: 'Dungeon Arena started',
worth: 1,
collection: 'level.session',
description: 'Started playing Dungeon Arena.',
userField: 'creator'
});

View file

@ -18,9 +18,10 @@ module.exports = class CocoRouter extends Backbone.Router
'about': go('AboutView') 'about': go('AboutView')
'account/profile(/:userID)': go('account/JobProfileView') 'account': go('account/MainAccountView')
'account/settings': go('account/AccountSettingsView') 'account/settings': go('account/AccountSettingsView')
'account/unsubscribe': go('account/UnsubscribeView') 'account/unsubscribe': go('account/UnsubscribeView')
#'account/payment'
'admin': go('admin/MainAdminView') 'admin': go('admin/MainAdminView')
'admin/candidates': go('admin/CandidatesView') 'admin/candidates': go('admin/CandidatesView')
@ -50,7 +51,7 @@ module.exports = class CocoRouter extends Backbone.Router
'editor': go('editor/MainEditorView') 'editor': go('editor/MainEditorView')
'editor/achievement': go('editor/achievement/AchievementSearchView') 'editor/achievement': go('editor/achievement/AchievementSearchView')
'editor/achievement': go('editor/achievement/AchievementEditView') 'editor/achievement/:articleID': go('editor/achievement/AchievementEditView')
'editor/article': go('editor/article/ArticleSearchView') 'editor/article': go('editor/article/ArticleSearchView')
'editor/article/preview': go('editor/article/ArticlePreviewView') 'editor/article/preview': go('editor/article/ArticlePreviewView')
'editor/article/:articleID': go('editor/article/ArticleEditView') 'editor/article/:articleID': go('editor/article/ArticleEditView')
@ -79,6 +80,11 @@ module.exports = class CocoRouter extends Backbone.Router
'test(/*subpath)': go('TestView') 'test(/*subpath)': go('TestView')
'user/:slugOrID': go('user/MainUserView')
'user/:slugOrID/stats': go('user/AchievementsView')
'user/:slugOrID/profile': go('user/JobProfileView')
#'user/:slugOrID/code': go('user/CodeView')
'*name': 'showNotFoundView' '*name': 'showNotFoundView'
routeToServer: (e) -> routeToServer: (e) ->

View file

@ -5,7 +5,6 @@ locale = require 'locale/locale'
{me} = require 'lib/auth' {me} = require 'lib/auth'
Tracker = require 'lib/Tracker' Tracker = require 'lib/Tracker'
CocoView = require 'views/kinds/CocoView' CocoView = require 'views/kinds/CocoView'
AchievementNotify = require '../../templates/achievement_notify'
marked.setOptions {gfm: true, sanitize: true, smartLists: true, breaks: false} marked.setOptions {gfm: true, sanitize: true, smartLists: true, breaks: false}
@ -40,7 +39,6 @@ Application = initialize: ->
@facebookHandler = new FacebookHandler() @facebookHandler = new FacebookHandler()
@gplusHandler = new GPlusHandler() @gplusHandler = new GPlusHandler()
$(document).bind 'keydown', preventBackspace $(document).bind 'keydown', preventBackspace
$.notify.addStyle 'achievement', html: $(AchievementNotify())
@linkedinHandler = new LinkedInHandler() @linkedinHandler = new LinkedInHandler()
preload(COMMON_FILES) preload(COMMON_FILES)
$.i18n.init { $.i18n.init {

View file

@ -0,0 +1,6 @@
CocoCollection = require 'collections/CocoCollection'
Achievement = require 'models/Achievement'
module.exports = class AchievementCollection extends CocoCollection
url: '/db/achievement'
model: Achievement

View file

@ -0,0 +1,9 @@
CocoCollection = require 'collections/CocoCollection'
EarnedAchievement = require 'models/EarnedAchievement'
module.exports = class EarnedAchievementCollection extends CocoCollection
model: EarnedAchievement
initialize: (userID) ->
@url = "/db/user/#{userID}/achievements"
super()

View file

@ -1,6 +1,9 @@
CocoCollection = require 'collections/CocoCollection' CocoCollection = require 'collections/CocoCollection'
Achievement = require 'models/Achievement'
class NewAchievementCollection extends CocoCollection class NewAchievementCollection extends CocoCollection
model: Achievement
initialize: (me = require('lib/auth').me) -> initialize: (me = require('lib/auth').me) ->
@url = "/db/user/#{me.id}/achievements?notified=false" @url = "/db/user/#{me.id}/achievements?notified=false"

View file

@ -0,0 +1,9 @@
CocoCollection = require './CocoCollection'
LevelSession = require 'models/LevelSession'
module.exports = class RecentlyPlayedCollection extends CocoCollection
model: LevelSession
constructor: (userID, options) ->
@url = "/db/user/#{userID}/recently_played"
super options

View file

@ -0,0 +1,10 @@
CocoCollection = require 'collections/CocoCollection'
Achievement = require 'models/Achievement'
class RelatedAchievementCollection extends CocoCollection
model: Achievement
initialize: (relatedID) ->
@url = "/db/achievement?related=#{relatedID}"
module.exports = RelatedAchievementCollection

View file

@ -1,6 +1,7 @@
Bus = require './Bus' Bus = require './Bus'
{me} = require 'lib/auth' {me} = require 'lib/auth'
LevelSession = require 'models/LevelSession' LevelSession = require 'models/LevelSession'
utils = require 'lib/utils'
module.exports = class LevelBus extends Bus module.exports = class LevelBus extends Bus
@ -22,6 +23,7 @@ module.exports = class LevelBus extends Bus
'tome:spell-changed': 'onSpellChanged' 'tome:spell-changed': 'onSpellChanged'
'tome:spell-created': 'onSpellCreated' 'tome:spell-created': 'onSpellCreated'
'application:idle-changed': 'onIdleChanged' 'application:idle-changed': 'onIdleChanged'
'goal-manager:new-goal-states': 'onNewGoalStates'
constructor: -> constructor: ->
super(arguments...) super(arguments...)
@ -192,6 +194,14 @@ module.exports = class LevelBus extends Bus
@changedSessionProperties.state = true @changedSessionProperties.state = true
@saveSession() @saveSession()
onNewGoalStates: ({goalStates})->
state = @session.get 'state'
unless utils.kindaEqual state.goalStates, goalStates # Only save when goals really change
state.goalStates = goalStates
@session.set 'state', state
@changedSessionProperties.state = true
@saveSession()
onPlayerJoined: (snapshot) => onPlayerJoined: (snapshot) =>
super(arguments...) super(arguments...)
return unless @onPoint() return unless @onPoint()

View file

@ -24,6 +24,7 @@ doQuerySelector = (value, operatorObj) ->
matchesQuery = (target, queryObj) -> matchesQuery = (target, queryObj) ->
return true unless queryObj return true unless queryObj
throw new Error 'Expected an object to match a query against, instead got null' unless target
for prop, query of queryObj for prop, query of queryObj
if prop[0] == '$' if prop[0] == '$'
switch prop switch prop

View file

@ -1,4 +1,4 @@
CocoClass = require 'lib/CocoClass' CocoClass = require './CocoClass'
namesCache = {} namesCache = {}

View file

@ -1,4 +1,4 @@
SystemNameLoader = require 'lib/SystemNameLoader' SystemNameLoader = require './SystemNameLoader'
### ###
Good-to-knows: Good-to-knows:
dataPath: an array of keys that walks you up a JSON object that's being patched dataPath: an array of keys that walks you up a JSON object that's being patched
@ -12,7 +12,7 @@ module.exports.expandDelta = (delta, left, schema) ->
(expandFlattenedDelta(fd, left, schema) for fd in flattenedDeltas) (expandFlattenedDelta(fd, left, schema) for fd in flattenedDeltas)
flattenDelta = (delta, dataPath=null, deltaPath=null) -> module.exports.flattenDelta = flattenDelta = (delta, dataPath=null, deltaPath=null) ->
# takes a single jsondiffpatch delta and returns an array of objects with # takes a single jsondiffpatch delta and returns an array of objects with
return [] unless delta return [] unless delta
dataPath ?= [] dataPath ?= []
@ -175,3 +175,5 @@ prunePath = (delta, path) ->
prunePath delta[path[0]], path.slice(1) unless delta[path[0]] is undefined prunePath delta[path[0]], path.slice(1) unless delta[path[0]] is undefined
keys = (k for k in _.keys(delta[path[0]]) when k isnt '_t') keys = (k for k in _.keys(delta[path[0]]) when k isnt '_t')
delete delta[path[0]] if keys.length is 0 delete delta[path[0]] if keys.length is 0

View file

@ -70,6 +70,7 @@ module.exports.i18n = (say, target, language=me.lang(), fallback='en') ->
null null
module.exports.getByPath = (target, path) -> module.exports.getByPath = (target, path) ->
throw new Error 'Expected an object to match a query against, instead got null' unless target
pieces = path.split('.') pieces = path.split('.')
obj = target obj = target
for piece in pieces for piece in pieces
@ -82,7 +83,7 @@ module.exports.isID = (id) -> _.isString(id) and id.length is 24 and id.match(/[
module.exports.round = _.curry (digits, n) -> module.exports.round = _.curry (digits, n) ->
n = +n.toFixed(digits) n = +n.toFixed(digits)
positify = (func) -> (x) -> if x > 0 then func(x) else 0 positify = (func) -> (params) -> (x) -> if x > 0 then func(params)(x) else 0
# f(x) = ax + b # f(x) = ax + b
createLinearFunc = (params) -> createLinearFunc = (params) ->
@ -100,3 +101,32 @@ module.exports.functionCreators =
linear: positify(createLinearFunc) linear: positify(createLinearFunc)
quadratic: positify(createQuadraticFunc) quadratic: positify(createQuadraticFunc)
logarithmic: positify(createLogFunc) logarithmic: positify(createLogFunc)
# Call done with true to satisfy the 'until' goal and stop repeating func
module.exports.keepDoingUntil = (func, wait=100, totalWait=5000) ->
waitSoFar = 0
(done = (success) ->
if (waitSoFar += wait) <= totalWait and not success
_.delay (-> func done), wait) false
module.exports.grayscale = (imageData) ->
d = imageData.data
for i in [0..d.length] by 4
r = d[i]
g = d[i+1]
b = d[i+2]
v = 0.2126*r + 0.7152*g + 0.0722*b
d[i] = d[i+1] = d[i+2] = v
imageData
# Deep compares l with r, with the exception that undefined values are considered equal to missing values
# Very practical for comparing Mongoose documents where undefined is not allowed, instead fields get deleted
module.exports.kindaEqual = compare = (l, r) ->
if _.isObject(l) and _.isObject(r)
for key in _.union Object.keys(l), Object.keys(r)
return false unless compare l[key], r[key]
return true
else if l is r
return true
else
return false

View file

@ -49,6 +49,9 @@
blog: "Blog" blog: "Blog"
forum: "Forum" forum: "Forum"
account: "Account" account: "Account"
profile: "Profile"
stats: "Stats"
code: "Code"
admin: "Admin" admin: "Admin"
home: "Home" home: "Home"
contribute: "Contribute" contribute: "Contribute"
@ -176,12 +179,14 @@
new_password: "New Password" new_password: "New Password"
new_password_verify: "Verify" new_password_verify: "Verify"
email_subscriptions: "Email Subscriptions" email_subscriptions: "Email Subscriptions"
email_subscriptions_none: "No Email Subscriptions."
email_announcements: "Announcements" email_announcements: "Announcements"
email_announcements_description: "Get emails on the latest news and developments at CodeCombat." email_announcements_description: "Get emails on the latest news and developments at CodeCombat."
email_notifications: "Notifications" email_notifications: "Notifications"
email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity." email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
email_any_notes: "Any Notifications" email_any_notes: "Any Notifications"
email_any_notes_description: "Disable to stop all activity notification emails." email_any_notes_description: "Disable to stop all activity notification emails."
email_news: "News"
email_recruit_notes: "Job Opportunities" email_recruit_notes: "Job Opportunities"
email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job." email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
contributor_emails: "Contributor Class Emails" contributor_emails: "Contributor Class Emails"
@ -591,6 +596,10 @@
level_search_title: "Search Levels Here" level_search_title: "Search Levels Here"
achievement_search_title: "Search Achievements" achievement_search_title: "Search Achievements"
read_only_warning2: "Note: you can't save any edits here, because you're not logged in." read_only_warning2: "Note: you can't save any edits here, because you're not logged in."
no_achievements: "No achievements have been added for this level yet."
achievement_query_misc: "Key achievement off of miscellanea"
achievement_query_goals: "Key achievement off of level goals"
level_completion: "Level Completion"
article: article:
edit_btn_preview: "Preview" edit_btn_preview: "Preview"
@ -599,6 +608,7 @@
general: general:
and: "and" and: "and"
name: "Name" name: "Name"
date: "Date"
body: "Body" body: "Body"
version: "Version" version: "Version"
commit_msg: "Commit Message" commit_msg: "Commit Message"
@ -938,3 +948,38 @@
text_diff: "Text Diff" text_diff: "Text Diff"
merge_conflict_with: "MERGE CONFLICT WITH" merge_conflict_with: "MERGE CONFLICT WITH"
no_changes: "No Changes" no_changes: "No Changes"
user:
stats: "Stats"
singleplayer_title: "Singleplayer Levels"
multiplayer_title: "Multiplayer Levels"
achievements_title: "Achievements"
last_played: "Last Played"
status: "Status"
status_completed: "Completed"
status_unfinished: "Unfinished"
no_singleplayer: "No Singleplayer games played yet."
no_multiplayer: "No Multiplayer games played yet."
no_achievements: "No Achievements earned yet."
favorite_prefix: "Favorite language is "
favorite_postfix: "."
achievements:
last_earned: "Last Earned"
amount_achieved: "Amount"
achievement: "Achievement"
category_contributor: "Contributor"
category_miscellaneous: "Miscellaneous"
category_levels: "Levels"
category_undefined: "Uncategorized"
current_xp_prefix: ""
current_xp_postfix: " in total"
new_xp_prefix: ""
new_xp_postfix: " earned"
left_xp_prefix: ""
left_xp_infix: " until level "
left_xp_postfix: ""
account:
recently_played: "Recently Played"
no_recent_games: "No games played during the past two weeks."

View file

@ -26,7 +26,7 @@ module.exports =
ar: require './ar' # العربية, Arabic ar: require './ar' # العربية, Arabic
pt: require './pt' # português, Portuguese pt: require './pt' # português, Portuguese
'pt-BR': require './pt-BR' # português do Brasil, Portuguese (Brazil) 'pt-BR': require './pt-BR' # português do Brasil, Portuguese (Brazil)
'pt-PT': require './pt-PT' # Português europeu, Portuguese (Portugal) 'pt-PT': require './pt-PT' # Português (Portugal), Portuguese (Portugal)
pl: require './pl' # język polski, Polish pl: require './pl' # język polski, Polish
it: require './it' # italiano, Italian it: require './it' # italiano, Italian
tr: require './tr' # Türkçe, Turkish tr: require './tr' # Türkçe, Turkish
@ -58,3 +58,4 @@ module.exports =
hi: require './hi' # ि, Hindi hi: require './hi' # ि, Hindi
ur: require './ur' # اُردُو, Urdu ur: require './ur' # اُردُو, Urdu
ms: require './ms' # Bahasa Melayu, Bahasa Malaysia ms: require './ms' # Bahasa Melayu, Bahasa Malaysia
ca: require './ca' # Català, Catalan

View file

@ -213,10 +213,10 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# active: "Looking for interview offers now" # active: "Looking for interview offers now"
# inactive: "Not looking for offers right now" # inactive: "Not looking for offers right now"
# complete: "complete" # complete: "complete"
# next: "Next" next: "Seguinte"
# next_city: "city?" next_city: "cidade?"
# next_country: "pick your country." # next_country: "pick your country."
# next_name: "name?" next_name: "nome?"
# next_short_description: "write a short description." # next_short_description: "write a short description."
# next_long_description: "describe your desired position." # next_long_description: "describe your desired position."
# next_skills: "list at least five skills." # next_skills: "list at least five skills."
@ -226,39 +226,39 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# next_links: "add any personal or social links." # next_links: "add any personal or social links."
# next_photo: "add an optional professional photo." # next_photo: "add an optional professional photo."
# next_active: "mark yourself open to offers to show up in searches." # next_active: "mark yourself open to offers to show up in searches."
# example_blog: "Blog" example_blog: "Blog"
# example_personal_site: "Personal Site" example_personal_site: "Sítio Pessoal"
# links_header: "Personal Links" links_header: "Ligações Pessoais"
# links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog." # links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog."
# links_name: "Link Name" links_name: "Nome da Ligação"
# links_name_help: "What are you linking to?" links_name_help: "A que é que está a ligar?"
# links_link_blurb: "Link URL" links_link_blurb: "URL da Ligação"
# basics_header: "Update basic info" # basics_header: "Update basic info"
# basics_active: "Open to Offers" # basics_active: "Open to Offers"
# basics_active_help: "Want interview offers right now?" # basics_active_help: "Want interview offers right now?"
# basics_job_title: "Desired Job Title" # basics_job_title: "Desired Job Title"
# basics_job_title_help: "What role are you looking for?" # basics_job_title_help: "What role are you looking for?"
# basics_city: "City" basics_city: "Cidade"
# basics_city_help: "City you want to work in (or live in now)." # basics_city_help: "City you want to work in (or live in now)."
# basics_country: "Country" basics_country: "País"
# basics_country_help: "Country you want to work in (or live in now)." # basics_country_help: "Country you want to work in (or live in now)."
# basics_visa: "US Work Status" # basics_visa: "US Work Status"
# basics_visa_help: "Are you authorized to work in the US, or do you need visa sponsorship? (If you live in Canada or Australia, mark authorized.)" # basics_visa_help: "Are you authorized to work in the US, or do you need visa sponsorship? (If you live in Canada or Australia, mark authorized.)"
# basics_looking_for: "Looking For" basics_looking_for: "À Procura De"
# basics_looking_for_full_time: "Full-time" basics_looking_for_full_time: "Tempo Inteiro"
# basics_looking_for_part_time: "Part-time" basics_looking_for_part_time: "Part-time"
# basics_looking_for_remote: "Remote" basics_looking_for_remote: "Remoto"
# basics_looking_for_contracting: "Contracting" # basics_looking_for_contracting: "Contracting"
# basics_looking_for_internship: "Internship" # basics_looking_for_internship: "Internship"
# basics_looking_for_help: "What kind of developer position do you want?" # basics_looking_for_help: "What kind of developer position do you want?"
# name_header: "Fill in your name" # name_header: "Fill in your name"
# name_anonymous: "Anonymous Developer" name_anonymous: "Desenvolvedor Anónimo"
# name_help: "Name you want employers to see, like 'Nick Winter'." # name_help: "Name you want employers to see, like 'Nick Winter'."
# short_description_header: "Write a short description of yourself" # short_description_header: "Write a short description of yourself"
# short_description_blurb: "Add a tagline to help an employer quickly learn more about you." # short_description_blurb: "Add a tagline to help an employer quickly learn more about you."
# short_description: "Tagline" # short_description: "Tagline"
# short_description_help: "Who are you, and what are you looking for? 140 characters max." # short_description_help: "Who are you, and what are you looking for? 140 characters max."
# skills_header: "Skills" skills_header: "Habilidades"
# skills_help: "Tag relevant developer skills in order of proficiency." # skills_help: "Tag relevant developer skills in order of proficiency."
# long_description_header: "Describe your desired position" # long_description_header: "Describe your desired position"
# long_description_blurb: "Tell employers how awesome you are and what role you want." # long_description_blurb: "Tell employers how awesome you are and what role you want."
@ -266,22 +266,22 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# long_description_help: "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." # long_description_help: "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."
# work_experience: "Work Experience" # work_experience: "Work Experience"
# work_header: "Chronicle your work history" # work_header: "Chronicle your work history"
# work_years: "Years of Experience" work_years: "Anos de Experiência"
# work_years_help: "How many years of professional experience (getting paid) developing software do you have?" # work_years_help: "How many years of professional experience (getting paid) developing software do you have?"
# work_blurb: "List your relevant work experience, most recent first." # work_blurb: "List your relevant work experience, most recent first."
# work_employer: "Employer" work_employer: "Empregador"
# work_employer_help: "Name of your employer." work_employer_help: "Nome do seu empregador."
# work_role: "Job Title" work_role: "Título do Emprego"
# work_role_help: "What was your job title or role?" # work_role_help: "What was your job title or role?"
# work_duration: "Duration" work_duration: "Duração"
# work_duration_help: "When did you hold this gig?" # work_duration_help: "When did you hold this gig?"
# work_description: "Description" work_description: "Descrição"
# work_description_help: "What did you do there? (140 chars; optional)" # work_description_help: "What did you do there? (140 chars; optional)"
# education: "Education" education: "Educação"
# education_header: "Recount your academic ordeals" # education_header: "Recount your academic ordeals"
# education_blurb: "List your academic ordeals." # education_blurb: "List your academic ordeals."
# education_school: "School" education_school: "Escola"
# education_school_help: "Name of your school." education_school_help: "Nome da sua escola."
# education_degree: "Degree" # education_degree: "Degree"
# education_degree_help: "What was your degree and field of study?" # education_degree_help: "What was your degree and field of study?"
# education_duration: "Dates" # education_duration: "Dates"
@ -312,18 +312,18 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
filter_visa: "Visa" filter_visa: "Visa"
filter_visa_yes: "Autorizado Para Trabalhar Nos EUA" filter_visa_yes: "Autorizado Para Trabalhar Nos EUA"
filter_visa_no: "Não Autorizado" filter_visa_no: "Não Autorizado"
# filter_education_top: "Top School" filter_education_top: "Universidade"
# filter_education_other: "Other" filter_education_other: "Outro"
# filter_role_web_developer: "Web Developer" filter_role_web_developer: "Desenvolvedor da Web"
# filter_role_software_developer: "Software Developer" filter_role_software_developer: "Desenvolvedor de Software"
# filter_role_mobile_developer: "Mobile Developer" filter_role_mobile_developer: "Desenvolvedor Mobile"
# filter_experience: "Experience" filter_experience: "Experiência"
# filter_experience_senior: "Senior" filter_experience_senior: "Sénior"
# filter_experience_junior: "Junior" filter_experience_junior: "Júnior"
# filter_experience_recent_grad: "Recent Grad" # filter_experience_recent_grad: "Recent Grad"
# filter_experience_student: "College Student" filter_experience_student: "Estudante Universitário"
# filter_results: "results" filter_results: "resultados"
# start_hiring: "Start hiring." start_hiring: "Começar a contratar."
# reasons: "Three reasons you should hire through us:" # reasons: "Three reasons you should hire through us:"
# everyone_looking: "Everyone here is looking for their next opportunity." # everyone_looking: "Everyone here is looking for their next opportunity."
# everyone_looking_blurb: "Forget about 20% LinkedIn InMail response rates. Everyone that we list on this site wants to find their next position and will respond to your request for an introduction." # everyone_looking_blurb: "Forget about 20% LinkedIn InMail response rates. Everyone that we list on this site wants to find their next position and will respond to your request for an introduction."
@ -408,11 +408,11 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
tip_morale_improves: "O carregamento irá continuar até que a moral melhore." tip_morale_improves: "O carregamento irá continuar até que a moral melhore."
tip_all_species: "Acreditamos em oportunidades iguais para todas as espécies, em relação a aprenderem a programar." tip_all_species: "Acreditamos em oportunidades iguais para todas as espécies, em relação a aprenderem a programar."
tip_reticulating: "A reticular espinhas." tip_reticulating: "A reticular espinhas."
tip_harry: "Você é um Feitiçeiro, " tip_harry: "Você é um Feiticeiro, "
# tip_great_responsibility: "With great coding skill comes great debug responsibility." tip_great_responsibility: "Com uma grande habilidade de programação vem uma grande responsabilidade de depuração."
# tip_munchkin: "If you don't eat your vegetables, a munchkin will come after you while you're asleep." tip_munchkin: "Se não comer os seus vegetais, virá um ogre atrás de si enquanto estiver a dormir."
tip_binary: "Há apenas 10 tipos de pessoas no mundo: aquelas que percebem binário e aquelas que não." tip_binary: "Há apenas 10 tipos de pessoas no mundo: aquelas que percebem binário e aquelas que não."
# tip_commitment_yoda: "A programmer must have the deepest commitment, the most serious mind. ~ Yoda" tip_commitment_yoda: "Um programador deve ter o compromisso mais profundo, a mente mais séria. ~ Yoda"
tip_no_try: "Fazer. Ou não fazer. Não há nenhum tentar. - Yoda" tip_no_try: "Fazer. Ou não fazer. Não há nenhum tentar. - Yoda"
tip_patience: "Paciência tu deves ter, jovem Padawan. - Yoda" tip_patience: "Paciência tu deves ter, jovem Padawan. - Yoda"
tip_documented_bug: "Um erro documentado não é um erro; é uma funcionalidade." tip_documented_bug: "Um erro documentado não é um erro; é uma funcionalidade."
@ -448,7 +448,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# temp: "Temp" # temp: "Temp"
save_load: save_load:
# granularity_saved_games: "Saved" granularity_saved_games: "Guardados"
granularity_change_history: "Histórico" granularity_change_history: "Histórico"
options: options:
@ -504,7 +504,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# toggle_grid: "Toggle grid overlay." # toggle_grid: "Toggle grid overlay."
# toggle_pathfinding: "Toggle pathfinding overlay." # toggle_pathfinding: "Toggle pathfinding overlay."
# beautify: "Beautify your code by standardizing its formatting." # beautify: "Beautify your code by standardizing its formatting."
move_wizard: "Mover o seu Feitiçeiro pelo nível." move_wizard: "Mover o seu Feiticeiro pelo nível."
admin: admin:
av_title: "Vistas de Administrador" av_title: "Vistas de Administrador"
@ -547,7 +547,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
grassy: "Com Relva" grassy: "Com Relva"
fork_title: "Bifurcar Nova Versão" fork_title: "Bifurcar Nova Versão"
fork_creating: "A Criar Bifurcação..." fork_creating: "A Criar Bifurcação..."
# randomize: "Randomize" randomize: "Randomizar"
more: "Mais" more: "Mais"
wiki: "Wiki" wiki: "Wiki"
live_chat: "Chat Ao Vivo" live_chat: "Chat Ao Vivo"
@ -561,8 +561,8 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
level_tab_thangs_all: "Todos" level_tab_thangs_all: "Todos"
level_tab_thangs_conditions: "Condições Iniciais" level_tab_thangs_conditions: "Condições Iniciais"
level_tab_thangs_add: "Adicionar Thangs" level_tab_thangs_add: "Adicionar Thangs"
# delete: "Delete" delete: "Eliminar"
# duplicate: "Duplicate" duplicate: "Duplicar"
level_settings_title: "Configurações" level_settings_title: "Configurações"
level_component_tab_title: "Componentes Atuais" level_component_tab_title: "Componentes Atuais"
level_component_btn_new: "Criar Novo Componente" level_component_btn_new: "Criar Novo Componente"
@ -572,7 +572,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
level_components_title: "Voltar para Todos os Thangs" level_components_title: "Voltar para Todos os Thangs"
level_components_type: "Tipo" level_components_type: "Tipo"
level_component_edit_title: "Editar Componente" level_component_edit_title: "Editar Componente"
# level_component_config_schema: "Config Schema" level_component_config_schema: "Configurar Esquema"
level_component_settings: "Configurações" level_component_settings: "Configurações"
level_system_edit_title: "Editar Sistema" level_system_edit_title: "Editar Sistema"
create_system_title: "Criar Novo Sistema" create_system_title: "Criar Novo Sistema"
@ -632,22 +632,22 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
who_description_prefix: "começaram juntos o CodeCombat em 2013. Também criaram o " who_description_prefix: "começaram juntos o CodeCombat em 2013. Também criaram o "
who_description_suffix: "em 2008, tornando-o a aplicação nº1 da web e iOS para aprender a escrever caracteteres Chineses e Japoneses." who_description_suffix: "em 2008, tornando-o a aplicação nº1 da web e iOS para aprender a escrever caracteteres Chineses e Japoneses."
who_description_ending: "Agora, está na altura de ensinar as pessoas a escrever código." who_description_ending: "Agora, está na altura de ensinar as pessoas a escrever 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_1: "Aquando da conceção do Skritter, o George não sabia programar e estava constantemente frustrado devido à sua inabilidade para implementar as ideias dele. Mais tarde, tentou aprender, mas as aulas eram muito lentas. O seu colega de quarto, numa tentativa de melhorar as suas habilidades e parar de ensinar, tentou o Codecademy, mas \"aborreceu-se.\" A cada semana, um outro amigo começava no Codecademy, mas desistia sempre. Apercebemo-nos de que era o mesmo problema que resolveríamos com o Skritter: pessoas a aprender uma habilidade através de aulas lentas e intensivas, quando o que precisam é de praticar rápida e extensivamente. Nós sabemos como resolver isso."
# 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_2: "Precisa de aprender a programar? Não precisa de aulas. Precisa sim de escrever muito código e passar um bom bocado enquanto o faz."
# why_paragraph_3_prefix: "That's what programming is about. It's gotta be fun. Not fun like" why_paragraph_3_prefix: "Afinal, é sobre isso que é a programação. Tem de ser divertida. Não divertida do género"
# why_paragraph_3_italic: "yay a badge" why_paragraph_3_italic: "yay uma medalha"
# why_paragraph_3_center: "but fun like" why_paragraph_3_center: "mas sim divertida do género"
# why_paragraph_3_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!" why_paragraph_3_italic_caps: "NÃO MÃE, TENHO DE ACABAR O NÍVEL!"
# 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_3_suffix: "É por isso que o CodeCombat é um jogo multijogador, e não um jogo que não passa de um curso com lições. Nós não vamos parar enquanto não puderes parar--mas desta vez, isso é uma coisa boa."
# 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_paragraph_4: "Se vais ficar viciado em algum jogo, vicia-te neste e torna-te num dos feiticeiros da idade da tecnologia."
# why_ending: "And hey, it's free. " why_ending: "E vejam só, é gratuito. "
# why_ending_url: "Start wizarding now!" why_ending_url: "Comece a enfeitiçar agora!"
# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere." george_description: "CEO, homem de negócios, designer da web, designer de jogos e campeão dos programadores iniciantes de todo o lado."
# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one." scott_description: "Programador extraordinário, arquiteto de software, feiticeiro da cozinha e mestre das finanças. O Scott é sensato."
# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat." nick_description: "Feiticeiro da programção, mago da motivação excêntrico e experimentador de pernas para o ar. O Nick pode fazer qualquer coisa e escolhe construir o CodeCombat."
# jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy." jeremy_description: "Mago do suporte ao cliente, testador do uso e organizador da comunidade; provavelmente já falou com o Jeremy."
# michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online." michael_description: "Programador, administrador do sistema e técnico de graduação prodígio, o Michael é a pessoa que mantém os nossos servidores online."
# glen_description: "Programmer and passionate game developer, with the motivation to make this world a better place, by developing things that matter. The word impossible can't be found in his dictionary. Learning new skills is his joy!" glen_description: "Programador e desenvolvedor de jogos apaixonado, com a motivação necessária para tornar este mundo um lugar melhor, ao desenvolver coisas que importam. A palavra impossível não pode ser encontrada no dicionário dele. Aprender novas habilidades é a alegria dele!"
legal: legal:
page_title: "Legal" page_title: "Legal"
@ -834,11 +834,11 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
rank_failed: "Falhou a Classificar" rank_failed: "Falhou a Classificar"
rank_being_ranked: "Jogo a ser Classificado" rank_being_ranked: "Jogo a ser Classificado"
# rank_last_submitted: "submitted " # rank_last_submitted: "submitted "
# help_simulate: "Help simulate games?" help_simulate: "Ajudar a simular jogos?"
code_being_simulated: "O teu código está a ser simulado por outros jogadores, para ser classificado. Isto será actualizado quando surgirem novas partidas." code_being_simulated: "O seu novo código está a ser simulado por outros jogadores, para ser classificado. Isto será atualizado quando surgirem novas partidas."
no_ranked_matches_pre: "Sem jogos classificados pela equipa " no_ranked_matches_pre: "Sem jogos classificados pela equipa "
no_ranked_matches_post: "! Joga contra alguns adversários e volta aqui para veres o teu jogo classificado." no_ranked_matches_post: "! Joga contra alguns adversários e volta aqui para veres o teu jogo classificado."
choose_opponent: "Escolhe um Adversário" choose_opponent: "Escolha um Adversário"
select_your_language: "Selecione a sua linguagem!" select_your_language: "Selecione a sua linguagem!"
tutorial_play: "Jogar Tutorial" tutorial_play: "Jogar Tutorial"
tutorial_recommended: "Recomendado se nunca jogou antes" tutorial_recommended: "Recomendado se nunca jogou antes"
@ -848,40 +848,40 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
simple_ai: "Inteligência Artificial Simples" simple_ai: "Inteligência Artificial Simples"
warmup: "Aquecimento" warmup: "Aquecimento"
vs: "VS" vs: "VS"
# friends_playing: "Friends Playing" friends_playing: "Amigos a Jogar"
# log_in_for_friends: "Log in to play with your friends!" log_in_for_friends: "Inicie sessão para jogar com os seus amigos!"
# social_connect_blurb: "Connect and play against your friends!" social_connect_blurb: "Conecte-se e jogue contra os seus amigos!"
# invite_friends_to_battle: "Invite your friends to join you in battle!" invite_friends_to_battle: "Convide os seus amigos para se juntarem a si em batalha!"
# fight: "Fight!" fight: "Lutar!"
# watch_victory: "Watch your victory" watch_victory: "Veja a sua vitória"
# defeat_the: "Defeat the" defeat_the: "Derrote o"
# tournament_ends: "Tournament ends" tournament_ends: "O Torneio acaba"
# tournament_ended: "Tournament ended" tournament_ended: "O Torneio acabou"
# tournament_rules: "Tournament Rules" tournament_rules: "Regras do Torneio"
# tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details" tournament_blurb: "Escreva código, recolha ouro, construa exércitos, esmague inimigos, ganhe prémios e melhore a sua carreira no nosso torneio $40,000 Greed! Confira os detalhes"
# tournament_blurb_blog: "on our blog" tournament_blurb_blog: "no nosso blog"
rules: "Regras" rules: "Regras"
winners: "Vencedores" winners: "Vencedores"
# ladder_prizes: ladder_prizes:
# title: "Tournament Prizes" title: "Prémios do Torneio"
# blurb_1: "These prizes will be awarded according to" blurb_1: "Estes prémios serão entregues de acordo com"
# blurb_2: "the tournament rules" blurb_2: "as regras do torneio"
# blurb_3: "to the top human and ogre players." blurb_3: "aos melhores jogadores humanos e ogres."
# blurb_4: "Two teams means double the prizes!" blurb_4: "Duas equipas significam o dobro dos prémios!"
# blurb_5: "(There will be two first place winners, two second-place winners, etc.)" blurb_5: "(Haverá dois vencedores em primeiro lugar, dois em segundo, etc.)"
# rank: "Rank" rank: "Classificação"
# prizes: "Prizes" prizes: "Prémios"
# total_value: "Total Value" total_value: "Valor Total"
# in_cash: "in cash" in_cash: "em dinheiro"
# custom_wizard: "Custom CodeCombat Wizard" custom_wizard: "Um Feiticeiro do CodeCombat Personalizado"
# custom_avatar: "Custom CodeCombat avatar" custom_avatar: "Um Avatar do CodeCombat Personalizado"
# heap: "for six months of \"Startup\" access" heap: "para seis meses de acesso \"Startup\""
# credits: "credits" credits: "créditos"
# one_month_coupon: "coupon: choose either Rails or HTML" one_month_coupon: "cupão: escolha Rails ou HTML"
# one_month_discount: "discount, 30% off: choose either Rails or HTML" one_month_discount: "desconto de 30%: escolha Rails ou HTML"
# license: "license" license: "licença"
# oreilly: "ebook of your choice" oreilly: "ebook à sua escolha"
loading_error: loading_error:
could_not_load: "Erro ao carregar do servidor" could_not_load: "Erro ao carregar do servidor"
@ -909,8 +909,8 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# user_schema: "User Schema" # user_schema: "User Schema"
# user_profile: "User Profile" # user_profile: "User Profile"
# patches: "Patches" # patches: "Patches"
# patched_model: "Source Document" patched_model: "Documento Fonte"
# model: "Model" model: "Modelo"
system: "Sistema" system: "Sistema"
component: "Componente" component: "Componente"
components: "Componentes" components: "Componentes"
@ -921,20 +921,20 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
article: "Artigo" article: "Artigo"
# user_names: "User Names" # user_names: "User Names"
# thang_names: "Thang Names" # thang_names: "Thang Names"
# files: "Files" files: "Ficheiros"
# top_simulators: "Top Simulators" # top_simulators: "Top Simulators"
# source_document: "Source Document" source_document: "Documento Fonte"
document: "Documento" document: "Documento"
# sprite_sheet: "Sprite Sheet" # sprite_sheet: "Sprite Sheet"
# candidate_sessions: "Candidate Sessions" # candidate_sessions: "Candidate Sessions"
# user_remark: "User Remark" # user_remark: "User Remark"
versions: "Versões" versions: "Versões"
# delta: delta:
# added: "Added" added: "Adicionados/as"
# modified: "Modified" modified: "Modificados/as"
# deleted: "Deleted" deleted: "Eliminados/as"
# moved_index: "Moved Index" moved_index: "Índice Movido"
# text_diff: "Text Diff" text_diff: "Diferença de Texto"
# merge_conflict_with: "MERGE CONFLICT WITH" merge_conflict_with: "FUNDIR CONFLITO COM"
# no_changes: "No Changes" no_changes: "Sem Alterações"

View file

@ -1,5 +1,5 @@
CocoModel = require './CocoModel' CocoModel = require './CocoModel'
util = require '../lib/utils' utils = require '../lib/utils'
module.exports = class Achievement extends CocoModel module.exports = class Achievement extends CocoModel
@className: 'Achievement' @className: 'Achievement'
@ -11,6 +11,47 @@ module.exports = class Achievement extends CocoModel
# TODO logic is duplicated in Mongoose Achievement schema # TODO logic is duplicated in Mongoose Achievement schema
getExpFunction: -> getExpFunction: ->
kind = @get('function')?.kind or @schema.function.default.kind kind = @get('function')?.kind or jsonschema.properties.function.default.kind
parameters = @get('function')?.parameters or @schema.function.default.parameters parameters = @get('function')?.parameters or jsonschema.properties.function.default.parameters
return utils.functionCreators[kind](parameters) if kind of utils.functionCreators return utils.functionCreators[kind](parameters) if kind of utils.functionCreators
@styleMapping:
1: 'achievement-wood'
2: 'achievement-stone'
3: 'achievement-silver'
4: 'achievement-gold'
5: 'achievement-diamond'
getStyle: -> Achievement.styleMapping[@get 'difficulty']
@defaultImageURL: '/images/achievements/default.png'
getImageURL: ->
if @get 'icon' then '/file/' + @get('icon') else Achievement.defaultImageURL
hasImage: -> @get('icon')?
# TODO Could cache the default icon separately
cacheLockedImage: ->
return @lockedImageURL if @lockedImageURL
canvas = document.createElement 'canvas'
image = new Image
image.src = @getImageURL()
defer = $.Deferred()
image.onload = =>
canvas.width = image.width
canvas.height = image.height
context = canvas.getContext '2d'
context.drawImage image, 0, 0
imgData = context.getImageData 0, 0, canvas.width, canvas.height
imgData = utils.grayscale imgData
context.putImageData imgData, 0, 0
@lockedImageURL = canvas.toDataURL()
defer.resolve @lockedImageURL
defer
getLockedImageURL: -> @lockedImageURL
i18nName: -> utils.i18n @attributes, 'name'
i18nDescription: -> utils.i18n @attributes, 'description'

View file

@ -1,8 +1,6 @@
storage = require 'lib/storage' storage = require 'lib/storage'
deltasLib = require 'lib/deltas' deltasLib = require 'lib/deltas'
NewAchievementCollection = require '../collections/NewAchievementCollection'
class CocoModel extends Backbone.Model class CocoModel extends Backbone.Model
idAttribute: '_id' idAttribute: '_id'
loaded: false loaded: false
@ -301,11 +299,13 @@ class CocoModel extends Backbone.Model
return if _.isString @url then @url else @url() return if _.isString @url then @url else @url()
@pollAchievements: -> @pollAchievements: ->
NewAchievementCollection = require '../collections/NewAchievementCollection' # Nasty mutual inclusion if put on top
achievements = new NewAchievementCollection achievements = new NewAchievementCollection
achievements.fetch( achievements.fetch
success: (collection) -> success: (collection) ->
me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models) me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models)
) error: ->
console.error 'Miserably failed to fetch unnotified achievements', arguments
CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500 CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500

View file

@ -0,0 +1,7 @@
CocoModel = require './CocoModel'
utils = require '../lib/utils'
module.exports = class EarnedAchievement extends CocoModel
@className: 'EarnedAchievement'
@schema: require 'schemas/models/earned_achievement'
urlRoot: '/db/earnedachievement'

View file

@ -80,7 +80,7 @@ module.exports = class Level extends CocoModel
visit = (c) -> visit = (c) ->
return if c in sorted return if c in sorted
lc = _.find levelComponents, {original: c.original} lc = _.find levelComponents, {original: c.original}
console.error thang.id, 'couldn\'t find lc for', c, 'of', levelComponents unless lc console.error thang.id or thang.name, 'couldn\'t find lc for', c, 'of', levelComponents unless lc
return unless lc return unless lc
if lc.name is 'Programmable' if lc.name is 'Programmable'
# Programmable always comes last # Programmable always comes last
@ -88,7 +88,7 @@ module.exports = class Level extends CocoModel
else else
for d in lc.dependencies or [] for d in lc.dependencies or []
c2 = _.find thang.components, {original: d.original} c2 = _.find thang.components, {original: d.original}
console.error thang.id, 'couldn\'t find dependent Component', d.original, 'from', lc.name unless c2 console.error thang.id or thang.name, 'couldn\'t find dependent Component', d.original, 'from', lc.name unless c2
visit c2 if c2 visit c2 if c2
if lc.name is 'Collides' if lc.name is 'Collides'
allied = _.find levelComponents, {name: 'Allied'} allied = _.find levelComponents, {name: 'Allied'}

View file

@ -36,3 +36,9 @@ module.exports = class LevelSession extends CocoModel
spell = item[1] spell = item[1]
return true if c1[thang][spell] isnt c2[thang]?[spell] return true if c1[thang][spell] isnt c2[thang]?[spell]
false false
isMultiplayer: ->
@get('team')? # Only multiplayer level sessions have teams defined
completed: ->
@get('state')?.complete || false

View file

@ -58,9 +58,15 @@ module.exports = class SuperModel extends Backbone.Model
return res return res
else else
@addCollection collection @addCollection collection
@listenToOnce collection, 'sync', (c) -> onCollectionSynced = (c) ->
console.debug 'Registering collection', url if collection.url is c.url
console.debug 'Registering collection', url, c
@registerCollection c @registerCollection c
else
console.warn 'Sync triggered for collection', c
console.warn 'Yet got other object', c
@listenToOnce collection, 'sync', onCollectionSynced
@listenToOnce collection, 'sync', onCollectionSynced
res = @addModelResource(collection, name, fetchOptions, value) res = @addModelResource(collection, name, fetchOptions, value)
res.load() if not (res.isLoading or res.isLoaded) res.load() if not (res.isLoading or res.isLoaded)
return res return res

View file

@ -9,14 +9,24 @@ module.exports = class User extends CocoModel
urlRoot: '/db/user' urlRoot: '/db/user'
notyErrors: false notyErrors: false
defaults:
points: 0
initialize: -> initialize: ->
super() super()
@migrateEmails() @migrateEmails()
onLoaded: ->
CocoModel.pollAchievements() # Check for achievements on login
super arguments...
isAdmin: -> isAdmin: ->
permissions = @attributes['permissions'] or [] permissions = @attributes['permissions'] or []
return 'admin' in permissions return 'admin' in permissions
isAnonymous: ->
@get 'anonymous'
displayName: -> displayName: ->
@get('name') or 'Anoner' @get('name') or 'Anoner'
@ -32,47 +42,13 @@ module.exports = class User extends CocoModel
return "/file/#{photoURL}#{prefix}s=#{size}" return "/file/#{photoURL}#{prefix}s=#{size}"
return "/db/user/#{@id}/avatar?s=#{size}&employerPageAvatar=#{useEmployerPageAvatar}" return "/db/user/#{@id}/avatar?s=#{size}&employerPageAvatar=#{useEmployerPageAvatar}"
@getByID = (id, properties, force) -> getSlugOrID: -> @get('slug') or @get('_id')
{me} = require 'lib/auth'
return me if me.id is id
user = cache[id] or new module.exports({_id: id})
if force or not cache[id]
user.loading = true
user.fetch(
success: ->
user.loading = false
Backbone.Mediator.publish('user:fetched')
#user.trigger 'sync' # needed?
)
cache[id] = user
user
set: -> set: ->
if arguments[0] is 'jobProfileApproved' and @get("jobProfileApproved") is false and not @get("jobProfileApprovedDate") if arguments[0] is 'jobProfileApproved' and @get("jobProfileApproved") is false and not @get("jobProfileApprovedDate")
@set "jobProfileApprovedDate", (new Date()).toISOString() @set "jobProfileApprovedDate", (new Date()).toISOString()
super arguments... super arguments...
# callbacks can be either success or error
@getByIDOrSlug: (idOrSlug, force, callbacks={}) ->
{me} = require 'lib/auth'
isID = util.isID idOrSlug
if me.id is idOrSlug or me.slug is idOrSlug
callbacks.success me if callbacks.success?
return me
cached = cache[idOrSlug]
user = cached or new @ _id: idOrSlug
if force or not cached
user.loading = true
user.fetch
success: ->
user.loading = false
Backbone.Mediator.publish 'user:fetched'
callbacks.success user if callbacks.success?
error: ->
user.loading = false
callbacks.error user if callbacks.error?
cache[idOrSlug] = user
user
@getUnconflictedName: (name, done) -> @getUnconflictedName: (name, done) ->
$.ajax "/auth/name/#{name}", $.ajax "/auth/name/#{name}",
success: (data) -> done data.name success: (data) -> done data.name
@ -111,19 +87,16 @@ module.exports = class User extends CocoModel
isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled
a = 5 a = 5
b = 40 b = 100
c = b
# y = a * ln(1/b * (x + b)) + 1 # y = a * ln(1/b * (x + c)) + 1
@levelFromExp: (xp) -> @levelFromExp: (xp) ->
if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + b))) + 1 else 1 if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + c))) + 1 else 1
# x = (e^((y-1)/a) - 1) * b # x = b * e^((y-1)/a) - c
@expForLevel: (level) -> @expForLevel: (level) ->
Math.ceil((Math.exp((level - 1)/ a) - 1) * b) if level > 1 then Math.ceil Math.exp((level - 1)/ a) * b - c else 0
level: -> level: ->
User.levelFromExp(@get('points')) User.levelFromExp(@get('points'))
levelFromExp: (xp) -> User.levelFromExp(xp)
expForLevel: (level) -> User.expForLevel(level)

View file

@ -24,8 +24,9 @@ MongoFindQuerySchema =
'^[-a-zA-Z0-9\.]*$': '^[-a-zA-Z0-9\.]*$':
oneOf: [ oneOf: [
#{$ref: '#/definitions/' + MongoQueryOperatorSchema.id}, #{$ref: '#/definitions/' + MongoQueryOperatorSchema.id},
{type: 'string'}, {type: 'string'}
{type: 'object'} {type: 'object'}
{type: 'boolean'}
] ]
additionalProperties: true # TODO make Treema accept new pattern matched keys additionalProperties: true # TODO make Treema accept new pattern matched keys
definitions: {} definitions: {}
@ -34,36 +35,58 @@ MongoFindQuerySchema.definitions[MongoQueryOperatorSchema.id] = MongoQueryOperat
AchievementSchema = c.object() AchievementSchema = c.object()
c.extendNamedProperties AchievementSchema c.extendNamedProperties AchievementSchema
c.extendBasicProperties AchievementSchema, 'article' c.extendBasicProperties AchievementSchema, 'achievement'
c.extendSearchableProperties AchievementSchema c.extendSearchableProperties AchievementSchema
_.extend(AchievementSchema.properties, _.extend AchievementSchema.properties,
query: query:
#type:'object' #type:'object'
$ref: '#/definitions/' + MongoFindQuerySchema.id $ref: '#/definitions/' + MongoFindQuerySchema.id
worth: {type: 'number'} worth: c.float
default: 10
collection: {type: 'string'} collection: {type: 'string'}
description: {type: 'string'} description: c.shortString
userField: {type: 'string'} default: 'Probably the coolest you\'ll ever get.'
userField: c.shortString()
related: c.objectId(description: 'Related entity') related: c.objectId(description: 'Related entity')
icon: {type: 'string', format: 'image-file', title: 'Icon'} icon: {type: 'string', format: 'image-file', title: 'Icon'}
category:
enum: ['level', 'ladder', 'contributor']
description: 'For categorizing and display purposes'
difficulty: c.int
description: 'The higher the more difficult'
default: 1
proportionalTo: proportionalTo:
type: 'string' type: 'string'
description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations' description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations'
recalculable:
type: 'boolean'
description: 'Needs to be set to true before it is elligible for recalculation.'
default: true
function: function:
type: 'object' type: 'object'
description: 'Function that gives total experience for X amount achieved'
properties: properties:
kind: {enum: ['linear', 'logarithmic'], default: 'linear'} kind: {enum: ['linear', 'logarithmic', 'quadratic'], default: 'linear'}
parameters: parameters:
type: 'object' type: 'object'
properties: properties:
a: {type: 'number', default: 1} a: {type: 'number', default: 1}
b: {type: 'number', default: 1} b: {type: 'number', default: 1}
c: {type: 'number', default: 1} c: {type: 'number', default: 1}
additionalProperties: true
default: {kind: 'linear', parameters: a: 1} default: {kind: 'linear', parameters: a: 1}
required: ['kind', 'parameters'] required: ['kind', 'parameters']
additionalProperties: false additionalProperties: false
) i18n: c.object
format: 'i18n'
props: ['name', 'description']
description: 'Help translate this achievement'
_.extend AchievementSchema, # Let's have these on the bottom
# TODO We really need some required properties in my opinion but this makes creating new achievements impossible as it is now
#required: ['name', 'description', 'query', 'worth', 'collection', 'userField', 'category', 'difficulty']
additionalProperties: false
AchievementSchema.definitions = {} AchievementSchema.definitions = {}
AchievementSchema.definitions[MongoFindQuerySchema.id] = MongoFindQuerySchema AchievementSchema.definitions[MongoFindQuerySchema.id] = MongoFindQuerySchema

View file

@ -101,6 +101,14 @@ _.extend LevelSessionSchema.properties,
type: 'object' type: 'object'
source: source:
type: 'string' type: 'string'
goalStates:
type: 'object'
description: 'Maps Goal ID on a goal state object'
additionalProperties:
title: 'Goal State'
type: 'object'
properties:
status: enum: ['failure', 'incomplete', 'success']
code: code:
type: 'object' type: 'object'

View file

@ -20,6 +20,9 @@ PatchSchema = c.object({title: 'Patch', required: ['target', 'delta', 'commitMes
major: {type: 'number', minimum: 0} major: {type: 'number', minimum: 0}
minor: {type: 'number', minimum: 0} minor: {type: 'number', minimum: 0}
}) })
wasPending: type: 'boolean'
newlyAccepted: type: 'boolean'
}) })
c.extendBasicProperties(PatchSchema, 'patch') c.extendBasicProperties(PatchSchema, 'patch')

View file

@ -222,6 +222,33 @@ _.extend UserSchema.properties,
points: {type: 'number'} points: {type: 'number'}
activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity} activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity}
stats: c.object {additionalProperties: false},
gamesCompleted: c.int()
articleEdits: c.int()
levelEdits: c.int()
levelSystemEdits: c.int()
levelComponentEdits: c.int()
thangTypeEdits: c.int()
patchesSubmitted: c.int
description: 'Amount of patches submitted, not necessarily accepted'
patchesContributed: c.int
description: 'Amount of patches submitted and accepted'
patchesAccepted: c.int
description: 'Amount of patches accepted by the user as owner'
# The below patches only apply to those that actually got accepted
totalTranslationPatches: c.int()
totalMiscPatches: c.int()
articleTranslationPatches: c.int()
articleMiscPatches: c.int()
levelTranslationPatches: c.int()
levelMiscPatches: c.int()
levelComponentTranslationPatches: c.int()
levelComponentMiscPatches: c.int()
levelSystemTranslationPatches: c.int()
levelSystemMiscPatches: c.int()
thangTypeTranslationPatches: c.int()
thangTypeMiscPatches: c.int()
c.extendBasicProperties UserSchema, 'user' c.extendBasicProperties UserSchema, 'user'

View file

@ -19,6 +19,8 @@ me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ex
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient # 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.objectId = (ext) -> schema = combine({type: ['object', 'string']}, ext)
me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext) me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext)
me.int = (ext) -> combine {type: 'integer'}, ext
me.float = (ext) -> combine {type: 'number'}, ext
PointSchema = me.object {title: 'Point', description: 'An {x, y} coordinate point.', format: 'point2d', required: ['x', 'y']}, 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} x: {title: 'x', description: 'The x coordinate.', type: 'number', 'default': 15}

View file

@ -0,0 +1,32 @@
@import "../bootstrap/variables"
@import "../bootstrap/mixins"
#account-home
dl
margin-bottom: 0px
img#picture
max-width: 100%
.panel
margin-bottom: 10px
h2
margin-bottom: 0px
a
font-size: 28px
margin-left: 5px
.panel-title > a
margin-left: 5px
color: rgb(11, 99, 188)
.panel-me
td
padding-left: 15px
.panel-emails
h4
font-family: $font-family-base

View file

@ -0,0 +1,243 @@
@import 'bootstrap/variables'
.achievement-body
position: relative
.achievement-icon
position: absolute
.achievement-image
width: 100%
height: 100%
img
position: absolute
margin: auto
top: 0
left: 0
right: 0
bottom: 0
&.locked
.achievement-content
background-image: url("/images/achievements/achievement_background_locked.png")
&:not(.locked)
.achievement-content
background-image: url("/images/achievements/achievement_background_light.png")
.achievement-content
background-size: 100% 100%
text-align: center
overflow: hidden
> .achievement-title
font-family: $font-family-base
font-weight: bold
white-space: nowrap
max-height: 2em
overflow: hidden
text-overflow: ellipsis
> .achievement-description
white-space: initial
font-size: 12px
line-height: 1.3em
max-height: 2.6em
margin-top: auto
margin-bottom: 0px !important
padding-left: 5px
overflow: hidden
text-overflow: ellipsis
// Specific to the user stats page
#user-achievements-view
.achievement-body
width: 335px
height: 120px
margin: 10px 0px
.achievement-icon
width: 120px
height: 120px
top: -10px
.achievement-image
img
-moz-transform: scale(0.6)
-webkit-transform: scale(0.6)
transform: scale(0.6)
.achievement-content
margin-left: 60px
margin-right: 5px
width: 260px
height: 100px
padding: 15px 10px 20px 60px
.achievement-title
font-size: 20px
.achievement-description
font-size: 12px
line-height: 1.3em
max-height: 2.6em
.achievement-popup
padding: 20px 0px
position: relative
.achievement-body
.achievement-icon
z-index: 1000
width: 200px
height: 200px
left: -140px
top: -20px
.achievement-image
img
position: absolute
margin: auto
top: 0
left: 0
right: 0
bottom: 0
.achievement-content
background-image: url("/images/achievements/achievement_background.png")
position: relative
width: 450px
height: 160px
padding: 24px 30px 20px 60px
.achievement-title
font-family: Bangers
font-size: 28px
padding-left: -50px
.achievement-description
font-size: 15px
line-height: 1.3em
max-height: 2.6em
margin-top: auto
margin-bottom: 0px !important
.progress-wrapper
margin-left: 20px
position: absolute
bottom: 48px
.user-level
font-size: 20px
color: white
position: absolute
left: -15px
margin-top: -8px
vertical-align: middle
z-index: 1000
> .progress-bar-wrapper
position: absolute
margin-left: 17px
width: 314px
height: 20px
z-index: 2
> .progress
margin-top: 5px
border-radius: 50px
height: 14px
> .progress-bar-border
position: absolute
width: 340px
height: 30px
margin-top: -2px
background: url("/images/achievements/bar_border.png") no-repeat
background-size: 100% 100%
z-index: 1
.achievement-icon
background-size: 100% 100% !important
.achievement-wood
&.locked
.achievement-icon
background: url("/images/achievements/border_wood_locked.png") no-repeat
&:not(.locked)
.achievement-icon
background: url("/images/achievements/border_wood.png") no-repeat
.achievement-stone
&.locked
.achievement-icon
background: url("/images/achievements/border_stone_locked.png") no-repeat
&:not(.locked)
.achievement-icon
background: url("/images/achievements/border_stone.png") no-repeat
.achievement-silver
&.locked
.achievement-icon
background: url("/images/achievements/border_silver_locked.png") no-repeat
&:not(.locked)
.achievement-icon
background: url("/images/achievements/border_silver.png") no-repeat
.achievement-gold
&.locked
.achievement-icon
background: url("/images/achievements/border_gold_locked.png") no-repeat
&:not(.locked)
.achievement-icon
background: url("/images/achievements/border_gold.png") no-repeat
.achievement-diamond
&.locked
.achievement-icon
background: url("/images/achievements/border_diamond_locked.png") no-repeat
&:not(.locked)
.achievement-icon
background: url("/images/achievements/border_diamond.png") no-repeat
.xp-bar-old
background-color: #680080
.xp-bar-new
background-color: #0096ff
.xp-bar-left
background-color: #fffbfd
// Achievements page
.achievement-category-title
margin-left: 20px
font-family: $font-family-base
font-weight: bold
color: #5a5a5a
text-transform: uppercase
.table-layout
#no-achievements
margin-top: 40px
.achievement-icon-small
height: 18px
// Achievement Popup
.achievement-popup-container
position: fixed
right: 0px
bottom: 0px
.popup
cursor: default
left: 600px
.user-level
background-image: url("/images/achievements/level-bg.png")
width: 38px
height: 38px
line-height: 38px
font-size: 20px
font-family: $font-family-base

View file

@ -21,7 +21,8 @@ h1 h2 h3 h4
margin: 56px auto 0 margin: 56px auto 0
min-height: 600px min-height: 600px
padding: 14px 12px 5px 12px padding: 14px 12px 5px 12px
@include box-sizing(border-box) +box-sizing(border-box)
+clearfix()
#outer-content-wrapper #outer-content-wrapper
background: #B4B4B4 background: #B4B4B4
@ -291,3 +292,9 @@ body[lang='ja']
a[data-toggle="coco-modal"] a[data-toggle="coco-modal"]
cursor: pointer cursor: pointer
.achievement-corner
position: fixed
bottom: 0px
right: 0px
z-index: 1001

View file

@ -1,4 +1,44 @@
@import "../bootstrap/variables" @import "../bootstrap/variables"
@import "../bootstrap/mixins"
// This is still very blocky. Browser reflows? Investigate why.
.open > .dropdown-menu
animation-name: fadeAnimation
animation-duration: .7s
animation-iteration-count: 1
animation-timing-function: ease
animation-fill-mode: forwards
-webkit-animation-name: fadeAnimation
-webkit-animation-duration: .7s
-webkit-animation-iteration-count: 1
-webkit-animation-timing-function: ease
-webkit-animation-fill-mode: backwards
-moz-animation-name: fadeAnimation
-moz-animation-duration: .7s
-moz-animation-iteration-count: 1
-moz-animation-timing-function: ease
-moz-animation-fill-mode: forwards
@keyframes fadeAnimation
from
opacity: 0
top: 120%
to
opacity: 1
top: 100%
@-webkit-keyframes fadeAnimation
from
opacity: 0
top: 120%
to
opacity: 1
top: 100%
a.disabled
color: #5b5855
text-decoration: none
cursor: default
#top-nav #top-nav
a.navbar-brand a.navbar-brand
@ -19,11 +59,65 @@
.account-settings-image .account-settings-image
width: 18px width: 18px
height: 18px height: 18px
margin-right: 5px
.glyphicon-user .glyphicon-user
font-size: 16px font-size: 16px
margin-right: 5px
.nav.navbar-link-text, .nav.navbar-link-text > li > a .dropdown
.dropdown-menu
left: auto
width: 280px
padding: 0px
border-radius: 0px
font-family: Bangers
> .user-dropdown-header
position: relative
background: #E4CF8C
height: 160px
padding: 10px
text-align: center
color: black
border-bottom: #32281e 1px solid
> a:hover
background-color: transparent
img
border: #e3be7a 8px solid
height: 98px // Includes the border
&:hover
box-shadow: 0 0 20px #e3be7a
> h3
margin-top: 10px
text-shadow: 2px 2px 3px white
color: #31281E
.user-level
position: absolute
top: 73px
right: 86px
color: gold
text-shadow: 1px 1px black, -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black
.user-dropdown-body
color: black
padding: 15px
letter-spacing: 1px
font: 15px 'Helvetica Neue', Helvetica, Arial, sans-serif
+clearfix()
.user-dropdown-footer
padding: 10px
margin-left: 0px
font-size: 14px
+clearfix()
.btn-flat
border: #ddd 1px solid
border-radius: 0px
margin: 0px
.nav.navbar-link-text > li > a
font-weight: normal font-weight: normal
font-size: 25px font-size: 25px
letter-spacing: 2px letter-spacing: 2px
@ -31,7 +125,7 @@
&:hover &:hover
color: #f8e413 color: #f8e413
.navbar-link-text a:hover .navbar-link-text > li > a:hover
background: darken($body-bg, 3%) background: darken($body-bg, 3%)
.btn, .btn-group, .fancy-select .btn, .btn-group, .fancy-select
@ -67,9 +161,6 @@
top: 13px top: 13px
max-width: 140px max-width: 140px
.nav
margin-bottom: 0
div.fancy-select div.fancy-select
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25) text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25)
div.trigger div.trigger

View file

@ -1,4 +1,6 @@
#editor-achievement-edit-view #editor-achievement-edit-view
height: 100%
.treema-root .treema-root
margin: 28px 0px 20px margin: 28px 0px 20px
@ -10,3 +12,9 @@
textarea textarea
width: 92% width: 92%
height: 300px height: 300px
#achievement-view
min-height: 200px
position: relative
padding-left: 200px

View file

@ -0,0 +1,6 @@
#related-achievements-view
#new-achievement-button
margin-bottom: 10px
.icon-column
width: 25px

View file

@ -77,6 +77,8 @@
background-color: white background-color: white
border-radius: 4px border-radius: 4px
.play-with-level-input
margin: 5px
#spritesheets #spritesheets

View file

@ -1,7 +1,7 @@
@import "app/styles/bootstrap/variables" @import "app/styles/bootstrap/variables"
#options-view #options-view
.select-group .select-group, .slider-group
display: block display: block
min-height: 20px min-height: 20px
margin-top: 10px margin-top: 10px
@ -14,6 +14,9 @@
margin-right: 20px margin-right: 20px
margin-bottom: 0 margin-bottom: 0
.slider
width: 200px
.form-group.radio-inline .form-group.radio-inline
input input
margin-left: 0px margin-left: 0px
@ -22,6 +25,7 @@
.radio-inline-parent-label .radio-inline-parent-label
padding-left: 0 padding-left: 0
#player-avatar-container #player-avatar-container
position: relative position: relative
margin: 0px 0px 15px 15px margin: 0px 0px 15px 15px

View file

@ -1,50 +0,0 @@
.notifyjs-achievement-base
//background: url("/images/pages/base/notify_mockup.png")
background-image: url("/images/pages/base/modal_background.png")
background-size: 100% 100%
width: 500px
height: 200px
padding: 35px 35px 15px 15px
text-align: center
cursor: auto
.achievement-body
.achievement-image
img
float: left
width: 100px
height: 100px
border-radius: 50%
margin: 20px 30px 20px 30px
-webkit-box-shadow: 0px 0px 36px 0px white
-moz-box-shadow: 0px 0px 36px 0px white
box-shadow: 0px 0px 36px 0px white
.achievement-title
font-family: Bangers
font-size: 28px
.achievement-description
margin-top: 10px
font-size: 16px
.achievement-progress
padding: 15px 0px 0px 0px
.achievement-message
font-family: Bangers
font-size: 18px
&:empty
display: none
.progress-wrapper
.progress-bar-wrapper
width: 100%
.earned-exp
padding-left: 5px
font-family: Bangers
font-size: 16px
float: right
.progress-bar-white
background-color: white

View file

@ -0,0 +1,73 @@
@import "../bootstrap/variables"
@import "../bootstrap/mixins"
#user-home
margin-top: 20px
.left-column
+make-sm-column(4)
.right-column
+make-sm-column(8)
.profile-wrapper
text-align: center
outline: 1px solid darkgrey
max-width: 100%
+center-block()
> .picture
width: 100%
background-color: #ffe4bc
border: 4px solid white
> .profile-info
background: white
.extra-info
padding-bottom: 3px
&:empty
display: none
.name
margin: 0px auto
padding: 10px inherit
color: white
text-shadow: 2px 0 0 #000, -2px 0 0 #000, 0 2px 0 #000, 0 -2px 0 #000, 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000
.profile-menu
padding-left: 0px
width: 100%
> a
border-radius: 0
border-width: 1px 0px 0px 0px
border-color: darkgrey
&:hover
border-color: #888
> span
color: #555555
font-size: 15px
margin-left: 5px
.contributor-categories
list-style: none
padding: 0px
margin-top: 15px
> .contributor-category
outline: 1px solid black
margin-bottom: 15px
> .contributor-image
border: none
width: 100%
border-bottom: 1px solid black
> .contributor-title
text-align: center
padding: 5px 0px
margin: 0px
background: white
.vertical-buffer
padding: 10px 0px

View file

@ -0,0 +1,141 @@
extends /templates/base
block content
if !me.isAnonymous()
.clearfix
.col-sm-6.clearfix
h2
span(data-i18n="account_settings.title") Account Settings
a.spl(href="/account/settings")
i.glyphicon.glyphicon-cog
hr
.row
.col-xs-6
.panel.panel-default
.panel-heading
h3.panel-title
i.glyphicon.glyphicon-picture
a(href="account/settings#picture" data-i18n="account_settings.picture_tab") Picture
.panel-body.text-center
img#picture(src="#{me.getPhotoURL(150)}" alt="Picture")
.col-xs-6
.panel.panel-default
.panel-heading
h3.panel-title
i.glyphicon.glyphicon-user
a(href="account/settings#wizard" data-i18n="account_settings.wizard_tab") Wizard
if (wizardSource)
.panel-body.text-center
img(src="#{wizardSource}")
.panel.panel-default.panel-me
.panel-heading
h3.panel-title
i.glyphicon.glyphicon-user
a(href="account/settings#me" data-i18n="account_settings.me_tab") Me
.panel-body
table
tr
th(data-i18n="general.name") Name
td=me.displayName()
tr
th(data-i18n="general.email") Email
td=me.get('email')
.panel.panel-default.panel-emails
.panel-heading
h3.panel-title
i.glyphicon.glyphicon-envelope
a(href="account/settings#emails" data-i18n="account_settings.emails_tab") Emails
.panel-body
if !hasEmailNotes && !hasEmailNews && !hasGeneralNews
p(data-i18n="account_settings.email_subscriptions_none") No email subscriptions.
if hasGeneralNews
h4(data-i18n="account_settings.email_news") News
ul
li(data-i18n="account_settings.email_announcements") Announcements
if hasEmailNotes
h4(data-i18n="account_settings.email_notifications") Notifications
ul
if subs.anyNotes
li(data-i18n="account_settings.email_any_notes") Any Notifications
if subs.recruitNotes
li(data-i18n="account_settings.email_recruit_notes") Job Opportunities
if hasEmailNews
h4(data-i18n="account_settings.contributor_emails") Contributor Emails
ul
if (subs.archmageNews)
li
span(data-i18n="classes.archmage_title")
| Archmage
span(data-i18n="classes.archmage_title_description")
| (Coder)
if (subs.artisanNews)
li
span.spr(data-i18n="classes.artisan_title")
| Artisan
span(data-i18n="classes.artisan_title_description")
| (Level Builder)
if (subs.adventurerNews)
li
span.spr(data-i18n="classes.adventurer_title")
| Adventurer
span(data-i18n="classes.adventurer_title_description")
| (Level Playtester)
if (subs.scribeNews)
li
span.spr(data-i18n="classes.scribe_title")
| Scribe
span(data-i18n="classes.scribe_title_description")
| (Article Editor)
if (subs.diplomatNews)
li
span.spr(data-i18n="classes.diplomat_title")
| Diplomat
span(data-i18n="classes.diplomat_title_description")
| (Translator)
if (subs.ambassadorNews)
li
span.spr(data-i18n="classes.ambassador_title")
| Ambassador
span(data-i18n="classes.ambassador_title_description")
| (Support)
.panel.panel-default
.panel-heading
h3.panel-title
i.glyphicon.glyphicon-wrench
a(href="account/settings#password" data-i18n="general.password") Password
.panel.panel-default
.panel-heading
h3.panel-title
i.glyphicon.glyphicon-briefcase
a(href="account/settings#job-profile" data-i18n="account_settings.job_profile") Job Profile
.col-sm-6
h2(data-i18n="user.recently_played") Recently Played
hr
if !recentlyPlayed
div(data-i18n="common.loading") Loading...
else if recentlyPlayed.length
table.table
tr
th(data-i18n="resources.level") Level
th(data-i18n="user.last_played") Last Played
th(data-i18n="user.status") Status
each session in recentlyPlayed
if session.get('levelName')
tr
td
- var posturl = ''
- if (session.get('team')) posturl = '?team=' + session.get('team')
a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '')
td= moment(session.get('changed')).fromNow()
if session.get('state').complete === true
td(data-i18n="user.status_completed") Completed
else if ! session.isMultiplayer()
td(data-i18n="user.status_unfinished") Unfinished
else
td
else
.panel.panel-default
.panel-body
div(data-i18n="account.no_recent_games") No games played during the past two weeks.

View file

@ -1,12 +0,0 @@
div
.clearfix.achievement-body
.achievement-image(data-notify-html="image")
.achievement-content
.achievement-title(data-notify-html="title")
.achievement-description(data-notify-html="description")
.achievement-progress
.achievement-message(data-notify-html="message")
.progress-wrapper
.earned-exp(data-notify-html="earnedExp")
.progress-bar-wrapper(data-notify-html="progressBar")

View file

@ -0,0 +1,21 @@
- var addedClass = style + (locked === true ? ' locked' : '')
.clearfix.achievement-body(class=addedClass)
.achievement-icon
.achievement-image
img(src=imgURL)
.achievement-content
.achievement-title= title
p.achievement-description= description
if popup
.progress-wrapper
span.user-level= level
.progress-bar-wrapper
.progress
- var currentTitle = $.i18n.t('achievements.current_xp_prefix') + currentXP + ' XP' + $.i18n.t('achievements.current_xp_postfix');
- var newTitle = $.i18n.t('achievements.new_xp_prefix') + newXP + ' XP' + $.i18n.t('achievements.new_xp_postfix');
- var leftTitle = $.i18n.t('achievements.left_xp_prefix') + newXP + ' XP' + $.i18n.t('achievements.left_xp_infix') + (level+1) + $.i18n.t('achievements.left_xp_postfix');
.progress-bar.xp-bar-old(style="width:#{oldXPWidth}%" data-toggle="tooltip" data-placement="top" title="#{currentTitle}")
.progress-bar.xp-bar-new(style="width:#{newXPWidth}%" data-toggle="tooltip" title="#{newTitle}")
.progress-bar.xp-bar-left(style="width:#{leftXPWidth}%" data-toggle="tooltip" title="#{leftTitle}")
.progress-bar-border

View file

@ -27,26 +27,44 @@ body
select.language-dropdown select.language-dropdown
if me.get('anonymous') === false
button.btn.btn-primary.navbuttontext.header-font#logout-button(data-i18n="login.log_out") Log Out
a.btn.btn-primary.navbuttontext.header-font(href=me.get('jobProfile') ? "/account/profile/#{me.id}" : "/account/settings")
div.navbuttontext-account(data-i18n="nav.account") Account
if me.get('photoURL')
img.account-settings-image(src=me.getPhotoURL(18), alt="")
else
span.glyphicon.glyphicon-user
else
button.btn.btn-primary.navbuttontext.header-font.auth-button
span(data-i18n="login.log_in") Log In
span.spr.spl /
span(data-i18n="login.sign_up") Create Account
ul(class='navbar-link-text').nav.navbar-nav.pull-right ul(class='navbar-link-text').nav.navbar-nav.pull-right
li.play li.play
a.header-font(href='/play', data-i18n="nav.play") Levels a.header-font(href='/play', data-i18n="nav.play") Levels
li li
a.header-font(href='/community', data-i18n="nav.community") Community a.header-font(href='/community', data-i18n="nav.community") Community
if me.get('anonymous') === false
li.dropdown
button.btn.btn-primary.navbuttontext.header-font.dropdown-toggle(href="#", data-toggle="dropdown")
if me.get('photoURL')
img.account-settings-image(src=me.getPhotoURL(18), alt="")
else
i.glyphicon.glyphicon-user
.navbuttontext-account(data-i18n="nav.account" href="/account") Account
span.caret
ul.dropdown-menu(role="menu")
li.user-dropdown-header
span.user-level= me.level()
a(href="/user/#{me.getSlugOrID()}")
img.img-circle(src="#{me.getPhotoURL()}" alt="")
h3=me.displayName()
li.user-dropdown-body
.col-xs-4.text-center
a(href="/user/#{me.getSlugOrID()}" data-i18n="nav.profile") Profile
.col-xs-4.text-center
a(href="/user/#{me.getSlugOrID()}/stats" data-i18n="nav.stats") Stats
.col-xs-4.text-center
a.disabled(data-i18n="nav.code") Code
li.user-dropdown-footer
.pull-left
a.btn.btn-default.btn-flat(href="/account" data-i18n="nav.account") Account
.pull-right
button#logout-button.btn.btn-default.btn-flat(data-i18n="login.log_out") Log Out
else
li
button.btn.btn-primary.navbuttontext.header-font.auth-button
span(data-i18n="login.log_in") Log In
span.spr.spl /
span(data-i18n="login.sign_up") Create Account
block outer_content block outer_content
#outer-content-wrapper(class=showBackground ? 'show-background' : '') #outer-content-wrapper(class=showBackground ? 'show-background' : '')
@ -55,6 +73,7 @@ body
.main-content-area .main-content-area
block content block content
p If this is showing, you dun goofed p If this is showing, you dun goofed
.achievement-corner
block footer block footer
.footer.clearfix .footer.clearfix

View file

@ -2,7 +2,6 @@ extends /templates/base
block content block content
if me.isAdmin() if me.isAdmin()
div
ol.breadcrumb ol.breadcrumb
li li
a(href="/editor", data-i18n="editor.main_title") CodeCombat Editors a(href="/editor", data-i18n="editor.main_title") CodeCombat Editors
@ -12,6 +11,7 @@ block content
| #{achievement.attributes.name} | #{achievement.attributes.name}
button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-button Recalculate button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-button Recalculate
button(data-i18n="common.delete", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#delete-button Delete
button(data-i18n="common.save", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#save-button Save button(data-i18n="common.save", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#save-button Save
h3(data-i18n="achievement.edit_achievement_title") Edit Achievement h3(data-i18n="achievement.edit_achievement_title") Edit Achievement
@ -20,12 +20,10 @@ block content
#achievement-treema #achievement-treema
#achievement-view #achievement-view.clearfix
hr hr
div#error-view
else else
.alert.alert-danger .alert.alert-danger
span Admin only. Turn around. span Admin only. Turn around.

View file

@ -4,14 +4,14 @@ block modal-header-content
h3(data-i18n="editor.fork_title") Fork New Version h3(data-i18n="editor.fork_title") Fork New Version
block modal-body-content block modal-body-content
form#save-level-form.form form.form
.form-group .form-group
label(for="level-name", data-i18n="general.name") Name label(for="model-name", data-i18n="general.name") Name
input#level-name(name="name", type="text").form-control input#fork-model-name(name="name", type="text").form-control
block modal-footer-content block modal-footer-content
button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel
button.btn.btn-primary#fork-level-confirm-button(data-i18n="common.save") Save button.btn.btn-primary#fork-model-confirm-button(data-i18n="common.save") Save
block modal-body-wait-content block modal-body-wait-content
h3(data-i18n="editor.fork_creating") Creating Fork... h3(data-i18n="editor.fork_creating") Creating Fork...

View file

@ -33,6 +33,8 @@ block header
- var patches = level.get('patches') - var patches = level.get('patches')
if patches && patches.length if patches && patches.length
span.badge= patches.length span.badge= patches.length
li
a(href="#related-achievements-view", data-toggle="tab") Achievements
li li
a(href="#docs-components-view", data-toggle="tab", data-i18n="editor.level_tab_docs") Documentation a(href="#docs-components-view", data-toggle="tab", data-i18n="editor.level_tab_docs") Documentation
.navbar-header .navbar-header
@ -83,7 +85,7 @@ block header
span.spl(data-i18n="common.unwatch") Unwatch span.spl(data-i18n="common.unwatch") Unwatch
li(class=anonymous ? "disabled": "") li(class=anonymous ? "disabled": "")
a(data-i18n="common.fork")#fork-level-start-button Fork a(data-i18n="common.fork")#fork-start-button Fork
li(class=anonymous ? "disabled": "") li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
li(class=anonymous ? "disabled": "") li(class=anonymous ? "disabled": "")
@ -121,6 +123,8 @@ block outer_content
div.tab-pane#editor-level-patches div.tab-pane#editor-level-patches
.patches-view .patches-view
div.tab-pane#related-achievements-view
div.tab-pane#docs-components-view div.tab-pane#docs-components-view
div#error-view div#error-view

View file

@ -0,0 +1,23 @@
extends /templates/modal/new_model
block modal-body-content
form.form
.form-group
label.control-label(for="name", data-i18n="general.name") Name
input#name.form-control(name="name", type="text")
.form-group
label.control-label(for="description" data-i18n="general.description") Description
input#description.form-control(name="description", type="text")
h4(data-i18n="editor.achievement_query_misc") Key achievement off of miscellanea
.radio
label
input(type="checkbox", name="queryOptions" id="misc-level-completion" value="misc-level-completion")
span.spl(data-i18n="editor.level_completion") Level Completion
- var goals = level.get('goals');
if goals && goals.length
h4(data-i18n="editor.achievement_query_goals") Key achievement off of level goals
each goal in goals
.radio
label
input(type="checkbox", name="queryOptions" id="#{goal.id}" value="#{goal.id}")
span.spl= goal.name

View file

@ -0,0 +1,26 @@
button.btn.btn-primary#new-achievement-button(disabled=me.isAdmin() === true ? undefined : "true" data-i18n="editor.new_achievement_title") Create a New Achievement
if achievements.loading
h2(data-i18n="common.loading") Loading...
else if ! achievements.models.length
.panel
.panel-body
p(data-i18n="editor.no_achievements") No achievements added for this level yet.
else
table.table.table-hover
thead
tr
th
th(data-i18n="general.name") Name
th(data-i18n="general.description") Description
th XP
tbody
each achievement in achievements.models
tr
td(style="width: 20px")
img.achievement-icon-small(src=achievement.getImageURL() alt="#{achievement.get('name') icon")
td
a(href="/editor/achievement/#{achievement.get('slug')}")= achievement.get('name')
td= achievement.get('description')
td= achievement.get('worth')

View file

@ -33,6 +33,16 @@ block header
span.navbar-brand #{thangType.attributes.name} span.navbar-brand #{thangType.attributes.name}
ul.nav.navbar-nav.navbar-right ul.nav.navbar-nav.navbar-right
li.dropdown
a(data-toggle='dropdown').play-with-level-parent
span.glyphicon-play.glyphicon
ul.dropdown-menu
li.dropdown-header Play Which Level?
li
for level in recentlyPlayedLevels
a.play-with-level-button(data-level=level)= level
input.play-with-level-input(placeholder="Type in a level name")
if authorized if authorized
li#save-button li#save-button
a a
@ -42,6 +52,8 @@ block header
span.glyphicon-chevron-down.glyphicon span.glyphicon-chevron-down.glyphicon
ul.dropdown-menu ul.dropdown-menu
li.dropdown-header Actions li.dropdown-header Actions
li(class=anonymous ? "disabled": "")
a(data-i18n="common.fork")#fork-start-button Fork
li(class=anonymous ? "disabled": "") li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
li.divider li.divider

View file

@ -2,10 +2,10 @@ h3(data-i18n="play_level.reload_title") Reload All Code?
p(data-i18n="play_level.reload_really") Are you sure you want to reload this level back to the beginning? p(data-i18n="play_level.reload_really") Are you sure you want to reload this level back to the beginning?
if showDevBits
p p
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="play_level.reload_confirm").btn.btn-primary#restart-level-confirm-button Reload All a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="play_level.reload_confirm").btn.btn-primary#restart-level-confirm-button Reload All
if showDevBits
img(src="/images/pages/game-menu/choose-hero-stub.png") img(src="/images/pages/game-menu/choose-hero-stub.png")
div(data-i18n="choose_hero.temp") Temp div(data-i18n="choose_hero.temp") Temp

View file

@ -8,6 +8,13 @@
.form .form
h3(data-i18n="options.general_options") General Options h3(data-i18n="options.general_options") General Options
.form-group.slider-group
label(for="option-volume")
span(data-i18n="options.volume") Volume
span.spr :
span#option-volume-value= (me.get('volume') * 100).toFixed(0) + '%'
#option-volume.slider
.form-group.checkbox .form-group.checkbox
label(for="option-music") label(for="option-music")
input#option-music(name="option-music", type="checkbox", checked=music) input#option-music(name="option-music", type="checkbox", checked=music)

View file

@ -12,32 +12,12 @@ block content
if me.get('anonymous') if me.get('anonymous')
a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="modal/AuthModal", role="button", data-i18n="#{currentNewSignup}") Log in to Create a New Content a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="modal/AuthModal", role="button", data-i18n="#{currentNewSignup}") Log in to Create a New Content
else else
a.btn.btn-primary.open-modal-button(href='#new-model-modal', role="button", data-toggle="modal", data-i18n="#{currentNew}") Create a New Something a.btn.btn-primary.open-modal-button#new-model-button(data-i18n="#{currentNew}") Create a New Something
input#search(data-i18n="[placeholder]#{currentSearch}") input#search(data-i18n="[placeholder]#{currentSearch}")
hr hr
div.results div.results
table table
// TODO: make this into a ModalView subview
div.modal.fade#new-model-modal
.modal-dialog
.background-wrapper
.modal-content
.modal-header
h3(data-i18n="#{currentNew}") Create New #{modelLabel}
.modal-body
form.form
.form-group
label.control-label(for="name", data-i18n="general.name") Name
input#name.form-control(name="name", type="text")
.modal-footer
button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel
button.btn.btn-primary.new-model-submit(data-i18n="common.create") Create
.modal-body.wait.secret
h3(data-i18n="play_level.tip_reticulating") Reticulating Splines...
.progress.progress-striped.active
.progress-bar
else else
.alert.alert-danger .alert.alert-danger
span Admin only. Turn around. span Admin only. Turn around.

View file

@ -0,0 +1,13 @@
extends /templates/base
// User pages might have some user page specific header, if not remove this
block content
.clearfix
if user && viewName
ol.breadcrumb
li
a(href="/user/#{user.getSlugOrID()}") #{user.displayName()}
li.active
| #{viewName}
if !user || user.loading
| LOADING

View file

@ -0,0 +1,19 @@
extends /templates/modal/modal_base
block modal-header-content
h3(data-i18n="#{currentNew}") Create New #{modelLabel}
block modal-body-content
form.form
.form-group
label.control-label(for="name", data-i18n="general.name") Name
input#name.form-control(name="name", type="text")
block modal-footer
.modal-footer
button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel
button.btn.btn-primary.new-model-submit(data-i18n="common.create") Create
.modal-body.wait.secret
h3(data-i18n="play_level.tip_reticulating") Reticulating Splines...
.progress.progress-striped.active
.progress-bar

View file

@ -0,0 +1,52 @@
extends /templates/kinds/user
block append content
.btn-group.pull-right
button#grid-layout-button.btn.btn-default(data-layout='grid', class=activeLayout==='grid' ? 'active' : '')
i.glyphicon.glyphicon-th
button#table-layout-button.btn.btn-default(data-layout='table', class=activeLayout==='table' ? 'active' : '')
i.glyphicon.glyphicon-th-list
if achievementsByCategory
if activeLayout === 'grid'
.grid-layout
each achievements, category in achievementsByCategory
.row
h2.achievement-category-title(data-i18n="category_#{category}")=category
each achievement, index in achievements
- var title = achievement.i18nName();
- var description = achievement.i18nDescription();
- var locked = ! achievement.get('unlocked');
- var style = achievement.getStyle()
- var imgURL = achievement.getImageURL();
if locked
- var imgURL = achievement.getLockedImageURL();
else
- var imgURL = achievement.getImageURL();
.col-lg-4.col-xs-12
include ../achievements/achievement-popup
else if activeLayout === 'table'
.table-layout
if earnedAchievements.length
table.table
tr
th(data-i18n="general.name") Name
th(data-i18n="general.description") Description
th(data-i18n="general.date") Date
th(data-i18n="achievements.amount_achieved") Amount
th XP
each earnedAchievement in earnedAchievements.models
- var achievement = earnedAchievement.get('achievement');
tr
td= achievement.i18nName()
td= achievement.i18nDescription()
td= moment().format("MMMM Do YYYY", earnedAchievement.get('changed'))
if achievement.isRepeatable()
td= earnedAchievement.get('achievedAmount')
else
td
td= earnedAchievement.get('earnedPoints')
else
.panel#no-achievements
.panel-body(data-i18n="user.no_achievements") No achievements earned yet.
else
div How did you even do that?

View file

@ -0,0 +1,133 @@
extends /templates/kinds/user
block append content
if user
.vertical-buffer
.row
.left-column
.profile-wrapper
img.picture(src="#{user.getPhotoURL(150)}" alt="")
div.profile-info
h3.name= user.get('name')
if favoriteLanguage
div.extra-info
span(data-i18n="user.favorite_prefix") Favorite language is
strong.favorite-language= favoriteLanguage
span(data-i18n="user.favorite_postfix") .
.btn-group-vertical.profile-menu
a.btn.btn-default(href="/user/#{user.getSlugOrID()}/profile")
i.glyphicon.glyphicon-briefcase
span(data-i18n="account_settings.job_profile") Job Profile
a.btn.btn-default(href="/user/#{user.getSlugOrID()}/stats")
i.glyphicon.glyphicon-certificate
span(data-i18n="user.stats") Stats
a.btn.btn-default.disabled(href="#")
i.glyphicon.glyphicon-pencil
span(data-i18n="general.code") Code
- var emails = user.get('emails')
if emails
ul.contributor-categories
//li.contributor-category
img.contributor-image(src="/images/pages/user/general.png")
h4.contributor-title CodeCombateer
if emails.adventurerNews
li.contributor-category
img.contributor-image(src="/images/pages/user/adventurer.png")
h4.contributor-title
a(href="/contribute#adventurer" data-i18n="classes.adventurer_title") Adventurer
if emails.ambassadorNews
li.contributor-category
img.contributor-image(src="/images/pages/user/ambassador.png")
h4.contributor-title
a(href="/contribute#ambassador" data-i18n="classes.ambassador_title") Ambassador
if emails.archmageNews
li.contributor-category
img.contributor-image(src="/images/pages/user/archmage.png")
h4.contributor-title
a(href="/contribute#archmage" data-i18n="classes.archmage_title") Archmage
if emails.artisanNews
li.contributor-category
img.contributor-image(src="/images/pages/user/artisan.png")
h4.contributor-title
a(href="/contribute#artisan" data-i18n="classes.artisan_title") Artisan
if emails.scribeNews
li.contributor-category
img.contributor-image(src="/images/pages/user/scribe.png")
h4.contributor-title
a(href="/contribute#scribe" data-i18n="classes.scribe_title") Scribe
.right-column
.panel.panel-default
.panel-heading
h3.panel-title(data-i18n="user.singleplayer_title") Singleplayer Levels
if (!singlePlayerSessions)
.panel-body
p(data-i18n="common.loading") Loading...
else if (singlePlayerSessions.length)
table.table
tr
th.col-xs-4(data-i18n="resources.level") Level
th.col-xs-4(data-i18n="user.last_played") Last Played
th.col-xs-4(data-i18n="user.status") Status
each session in singlePlayerSessions
if session.get('levelName')
tr
td
a(href="/play/level/#{session.get('levelID')}")= session.get('levelName')
td= moment(session.get('changed')).fromNow()
if session.get('state').complete === true
td(data-i18n="user.status_completed") Completed
else
td(data-i18n="user.status_unfinished") Unfinished
else
.panel-body
p(data-i18n="no_singleplayer") No Singleplayer games played yet.
.panel.panel-default
.panel-heading
h3.panel-title(data-i18n="no_multiplayer") Multiplayer Levels
if (!multiPlayerSessions)
.panel-body
p(data-i18n="common.loading") Loading...
else if (multiPlayerSessions.length)
table.table
tr
th.col-xs-4(data-i18n="resources.level") Level
th.col-xs-4(data-i18n="user.last_played") Last Played
th.col-xs-4(data-i18n="general.score") Score
each session in multiPlayerSessions
tr
td
- var posturl = ''
- if (session.get('team')) posturl = '?team=' + session.get('team')
a(href="/play/level/#{session.get('levelID') + posturl}")= session.get('levelName') + (session.get('team') ? ' (' + session.get('team') + ')' : '')
td= moment(session.get('changed')).fromNow()
if session.get('totalScore')
td= session.get('totalScore') * 100
else
td(data-i18n="user.status_unfinished") Unfinished
else
.panel-body
p(data-i18n="user.no_multiplayer") No Multiplayer games played yet.
.panel.panel-default
.panel-heading
h3.panel-title(data-i18n="user.achievements") Achievements
if ! earnedAchievements
.panel-body
p(data-i18n="common.loading") Loading...
else if ! earnedAchievements.length
.panel-body
p(data-i18n="user.no_achievements") No achievements earned so far.
else
table.table
tr
th.col-xs-4(data-i18n="achievements.achievement") Achievement
th.col-xs-4(data-i18n="achievements.last_earned") Last Earned
th.col-xs-4(data-i18n="achievements.amount_achieved") Amount
each achievement in earnedAchievements.models
tr
td= achievement.get('achievementName')
td= moment().format("MMMM Do YYYY", achievement.get('changed'))
if achievement.get('achievedAmount')
td= achievement.get('achievedAmount')
else
td

View file

@ -1,4 +1,4 @@
CocoView = require 'views/kinds/CocoView' RootView = require 'views/kinds/RootView'
ModalView = require 'views/kinds/ModalView' ModalView = require 'views/kinds/ModalView'
template = require 'templates/demo' template = require 'templates/demo'
requireUtils = require 'lib/requireUtils' requireUtils = require 'lib/requireUtils'
@ -24,7 +24,7 @@ DEMO_URL_PREFIX = '/demo/'
### ###
module.exports = DemoView = class DemoView extends CocoView module.exports = DemoView = class DemoView extends RootView
id: 'demo-view' id: 'demo-view'
template: template template: template

View file

@ -0,0 +1,38 @@
View = require 'views/kinds/RootView'
template = require 'templates/account/account_home'
{me} = require 'lib/auth'
User = require 'models/User'
AuthModalView = require 'views/modal/AuthModal'
RecentlyPlayedCollection = require 'collections/RecentlyPlayedCollection'
ThangType = require 'models/ThangType'
module.exports = class MainAccountView extends View
id: 'account-home'
template: template
constructor: (options) ->
super options
return unless me
@wizardType = ThangType.loadUniversalWizard()
@recentlyPlayed = new RecentlyPlayedCollection me.get('_id')
@supermodel.loadModel @wizardType, 'thang'
@supermodel.loadCollection @recentlyPlayed, 'recentlyPlayed'
onLoaded: ->
super()
getRenderData: ->
c = super()
c.subs = {}
enabledEmails = c.me.getEnabledEmails()
c.subs[sub] = 1 for sub in enabledEmails
c.hasEmailNotes = _.any enabledEmails, (sub) -> sub.contains 'Notes'
c.hasEmailNews = _.any enabledEmails, (sub) -> sub.contains('News') and sub isnt 'generalNews'
c.hasGeneralNews = 'generalNews' in enabledEmails
c.wizardSource = @wizardType.getPortraitSource colorConfig: me.get('wizard')?.colorConfig if @wizardType.loaded
c.recentlyPlayed = @recentlyPlayed.models
c
afterRender: ->
super()
@openModalView new AuthModalView if me.isAnonymous()

View file

@ -0,0 +1,91 @@
CocoView = require 'views/kinds/CocoView'
template = require 'templates/achievements/achievement-popup'
User = require '../../models/User'
Achievement = require '../../models/Achievement'
module.exports = class AchievementPopup extends CocoView
className: 'achievement-popup'
template: template
constructor: (options) ->
@achievement = options.achievement
@earnedAchievement = options.earnedAchievement
@container = options.container or @getContainer()
@popup = options.container
@popup ?= true
@className += ' popup' if @popup
super options
console.debug 'Created an AchievementPopup', @$el
@render()
calculateData: ->
currentLevel = me.level()
nextLevel = currentLevel + 1
currentLevelExp = User.expForLevel(currentLevel)
nextLevelXP = User.expForLevel(nextLevel)
totalExpNeeded = nextLevelXP - currentLevelExp
expFunction = @achievement.getExpFunction()
currentXP = me.get 'points'
if @achievement.isRepeatable()
achievedXP = expFunction(@earnedAchievement.get('previouslyAchievedAmount')) * @achievement.get('worth') if @achievement.isRepeatable()
else
achievedXP = @achievement.get 'worth'
previousXP = currentXP - achievedXP
leveledUp = currentXP - achievedXP < currentLevelExp
#console.debug 'Leveled up' if leveledUp
alreadyAchievedPercentage = 100 * (previousXP - currentLevelExp) / totalExpNeeded
alreadyAchievedPercentage = 0 if alreadyAchievedPercentage < 0 # In case of level up
newlyAchievedPercentage = if leveledUp then 100 * (currentXP - currentLevelExp) / totalExpNeeded else 100 * achievedXP / totalExpNeeded
#console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelXP} xp)."
#console.debug "Need a total of #{nextLevelXP - currentLevelExp}, already had #{previousXP} and just now earned #{achievedXP} totalling on #{currentXP}"
data =
title: @achievement.i18nName()
imgURL: @achievement.getImageURL()
description: @achievement.i18nDescription()
level: currentLevel
currentXP: currentXP
newXP: achievedXP
leftXP: nextLevelXP - currentXP
oldXPWidth: alreadyAchievedPercentage
newXPWidth: newlyAchievedPercentage
leftXPWidth: 100 - newlyAchievedPercentage - alreadyAchievedPercentage
getRenderData: ->
c = super()
_.extend c, @calculateData()
c.style = @achievement.getStyle()
c.popup = true
c.$ = $ # Allows the jade template to do i18n
c
render: ->
console.debug 'render achievement popup'
super()
@container.prepend @$el
if @popup
@$el.animate
left: 0
@$el.on 'click', (e) =>
@$el.animate
left: 600
, =>
@$el.remove()
@destroy()
getContainer: ->
unless @container
@container = $('.achievement-popup-container')
unless @container.length
$('body').append('<div class="achievement-popup-container"></div>')
@container = $('.achievement-popup-container')
@container
afterRender: ->
super()
_.delay @initializeTooltips, 1000 # TODO this could be smoother
initializeTooltips: ->
$('.progress-bar').tooltip()

View file

@ -0,0 +1,43 @@
ModalView = require 'views/kinds/ModalView'
template = require 'templates/editor/fork-modal'
forms = require 'lib/forms'
module.exports = class ForkModal extends ModalView
id: 'fork-modal'
template: template
instant: false
modalWidthPercent: 60
events:
'click #fork-model-confirm-button': 'forkModel'
'submit form': 'forkModel'
constructor: (options) ->
super options
@editorPath = options.editorPath # like 'level' or 'thang'
@model = options.model
@modelClass = @model.constructor
forkModel: ->
@showLoading()
forms.clearFormAlerts(@$el)
newModel = new @modelClass($.extend(true, {}, @model.attributes))
newModel.unset '_id'
newModel.unset 'version'
newModel.unset 'creator'
newModel.unset 'created'
newModel.unset 'original'
newModel.unset 'parent'
newModel.set 'commitMessage', "Forked from #{@model.get('name')}"
newModel.set 'name', @$el.find('#fork-model-name').val()
if @model.get 'permissions'
newModel.set 'permissions', [access: 'owner', target: me.id]
newPathPrefix = "editor/#{@editorPath}/"
res = newModel.save()
return unless res
res.error =>
@hideLoading()
forms.applyErrorsToForm(@$el.find('form'), JSON.parse(res.responseText))
res.success =>
@hide()
application.router.navigate(newPathPrefix + newModel.get('slug'), {trigger: true})

View file

@ -1,7 +1,10 @@
RootView = require 'views/kinds/RootView' RootView = require 'views/kinds/RootView'
template = require 'templates/editor/achievement/edit' template = require 'templates/editor/achievement/edit'
Achievement = require 'models/Achievement' Achievement = require 'models/Achievement'
AchievementPopup = require 'views/achievements/AchievementPopup'
ConfirmModal = require 'views/modal/ConfirmModal' ConfirmModal = require 'views/modal/ConfirmModal'
errors = require 'lib/errors'
app = require 'application'
module.exports = class AchievementEditView extends RootView module.exports = class AchievementEditView extends RootView
id: 'editor-achievement-edit-view' id: 'editor-achievement-edit-view'
@ -11,6 +14,7 @@ module.exports = class AchievementEditView extends RootView
events: events:
'click #save-button': 'saveAchievement' 'click #save-button': 'saveAchievement'
'click #recalculate-button': 'confirmRecalculation' 'click #recalculate-button': 'confirmRecalculation'
'click #delete-button': 'confirmDeletion'
subscriptions: subscriptions:
'save-new': 'saveAchievement' 'save-new': 'saveAchievement'
@ -20,13 +24,10 @@ module.exports = class AchievementEditView extends RootView
@achievement = new Achievement(_id: @achievementID) @achievement = new Achievement(_id: @achievementID)
@achievement.saveBackups = true @achievement.saveBackups = true
@listenToOnce(@achievement, 'error', @achievement.once 'error', (achievement, jqxhr) =>
() =>
@hideLoading() @hideLoading()
$(@$el).find('.main-content-area').children('*').not('#error-view').remove() $(@$el).find('.main-content-area').children('*').not('.breadcrumb').remove()
errors.backboneFailure arguments...
@insertSubView(new ErrorView())
)
@achievement.fetch() @achievement.fetch()
@listenToOnce(@achievement, 'sync', @buildTreema) @listenToOnce(@achievement, 'sync', @buildTreema)
@ -49,17 +50,31 @@ module.exports = class AchievementEditView extends RootView
@treema.build() @treema.build()
pushChangesToPreview: =>
'TODO' # TODO might want some intrinsic preview thing
getRenderData: (context={}) -> getRenderData: (context={}) ->
context = super(context) context = super(context)
context.achievement = @achievement context.achievement = @achievement
context.authorized = me.isAdmin() context.authorized = me.isAdmin()
context context
afterRender: ->
super(arguments...)
@pushChangesToPreview()
pushChangesToPreview: =>
$('#achievement-view').empty()
if @treema?
for key, value of @treema.data
@achievement.set key, value
earned =
earnedPoints: @achievement.get 'worth'
popup = new AchievementPopup achievement: @achievement, earnedAchievement:earned, popup: false, container: $('#achievement-view')
openSaveModal: -> openSaveModal: ->
'Maybe later' # TODO 'Maybe later' # TODO patch patch patch
saveAchievement: (e) -> saveAchievement: (e) ->
@treema.endExistingEdits() @treema.endExistingEdits()
@ -75,20 +90,31 @@ module.exports = class AchievementEditView extends RootView
url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}" url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}"
document.location.href = url document.location.href = url
confirmRecalculation: (e) -> confirmRecalculation: ->
renderData = renderData =
'confirmTitle': 'Are you really sure?' 'confirmTitle': 'Are you really sure?'
'confirmBody': 'This will trigger recalculation of the achievement for all users. Are you really sure you want to go down this path?' 'confirmBody': 'This will trigger recalculation of the achievement for all users. Are you really sure you want to go down this path?'
'confirmDecline': 'Not really' 'confirmDecline': 'Not really'
'confirmConfirm': 'Definitely' 'confirmConfirm': 'Definitely'
confirmModal = new ConfirmModal(renderData) confirmModal = new ConfirmModal renderData
confirmModal.onConfirm @recalculateAchievement confirmModal.on 'confirm', @recalculateAchievement
@openModalView confirmModal
confirmDeletion: ->
renderData =
'confirmTitle': 'Are you really sure?'
'confirmBody': 'This will completely delete the achievement, potentially breaking a lot of stuff you don\'t want breaking. Are you entirely sure?'
'confirmDecline': 'Not really'
'confirmConfirm': 'Definitely'
confirmModal = new ConfirmModal renderData
confirmModal.on 'confirm', @deleteAchievement
@openModalView confirmModal @openModalView confirmModal
recalculateAchievement: => recalculateAchievement: =>
$.ajax $.ajax
data: JSON.stringify(achievements: [@achievement.get('slug') or @achievement.get('_id')]) data: JSON.stringify(earnedAchievements: [@achievement.get('slug') or @achievement.get('_id')])
success: (data, status, jqXHR) -> success: (data, status, jqXHR) ->
noty noty
timeout: 5000 timeout: 5000
@ -105,3 +131,24 @@ module.exports = class AchievementEditView extends RootView
url: '/admin/earned.achievement/recalculate' url: '/admin/earned.achievement/recalculate'
type: 'POST' type: 'POST'
contentType: 'application/json' contentType: 'application/json'
deleteAchievement: =>
console.debug 'deleting'
$.ajax
type: 'DELETE'
success: ->
noty
timeout: 5000
text: 'Aaaand it\'s gone.'
type: 'success'
layout: 'topCenter'
_.delay ->
app.router.navigate '/editor/achievement', trigger: true
, 500
error: (jqXHR, status, error) ->
console.error jqXHR
timeout: 5000
text: "Deleting achievement failed with error code #{jqXHR.status}"
type: 'error'
layout: 'topCenter'
url: "/db/achievement/#{@achievement.id}"

View file

@ -12,9 +12,10 @@ ScriptsTabView = require './scripts/ScriptsTabView'
ComponentsTabView = require './components/ComponentsTabView' ComponentsTabView = require './components/ComponentsTabView'
SystemsTabView = require './systems/SystemsTabView' SystemsTabView = require './systems/SystemsTabView'
SaveLevelModal = require './modals/SaveLevelModal' SaveLevelModal = require './modals/SaveLevelModal'
LevelForkView = require './modals/ForkLevelModal' ForkModal = require 'views/editor/ForkModal'
SaveVersionModal = require 'views/modal/SaveVersionModal' SaveVersionModal = require 'views/modal/SaveVersionModal'
PatchesView = require 'views/editor/PatchesView' PatchesView = require 'views/editor/PatchesView'
RelatedAchievementsView = require 'views/editor/level/RelatedAchievementsView'
VersionHistoryView = require './modals/LevelVersionsModal' VersionHistoryView = require './modals/LevelVersionsModal'
ComponentDocsView = require 'views/docs/ComponentDocumentationView' ComponentDocsView = require 'views/docs/ComponentDocumentationView'
@ -29,7 +30,7 @@ module.exports = class LevelEditView extends RootView
'click .play-with-team-button': 'onPlayLevel' 'click .play-with-team-button': 'onPlayLevel'
'click .play-with-team-parent': 'onPlayLevelTeamSelect' 'click .play-with-team-parent': 'onPlayLevelTeamSelect'
'click #commit-level-start-button': 'startCommittingLevel' 'click #commit-level-start-button': 'startCommittingLevel'
'click #fork-level-start-button': 'startForkingLevel' 'click #fork-start-button': 'startForking'
'click #level-history-button': 'showVersionHistory' 'click #level-history-button': 'showVersionHistory'
'click #undo-button': 'onUndo' 'click #undo-button': 'onUndo'
'click #redo-button': 'onRedo' 'click #redo-button': 'onRedo'
@ -75,7 +76,8 @@ module.exports = class LevelEditView extends RootView
@insertSubView new ScriptsTabView world: @world, supermodel: @supermodel, files: @files @insertSubView new ScriptsTabView world: @world, supermodel: @supermodel, files: @files
@insertSubView new ComponentsTabView supermodel: @supermodel @insertSubView new ComponentsTabView supermodel: @supermodel
@insertSubView new SystemsTabView supermodel: @supermodel @insertSubView new SystemsTabView supermodel: @supermodel
@insertSubView new ComponentDocsView() @insertSubView new RelatedAchievementsView supermodel: @supermodel, level: @level
@insertSubView new ComponentDocsView supermodel: @supermodel
Backbone.Mediator.publish 'level-loaded', level: @level Backbone.Mediator.publish 'level-loaded', level: @level
@showReadOnly() if me.get('anonymous') @showReadOnly() if me.get('anonymous')
@ -128,9 +130,8 @@ module.exports = class LevelEditView extends RootView
@openModalView new SaveLevelModal level: @level, supermodel: @supermodel @openModalView new SaveLevelModal level: @level, supermodel: @supermodel
Backbone.Mediator.publish 'level:view-switched', e Backbone.Mediator.publish 'level:view-switched', e
startForkingLevel: (e) -> startForking: (e) ->
levelForkView = new LevelForkView level: @level @openModalView new ForkModal model: @level, editorPath: 'level'
@openModalView levelForkView
Backbone.Mediator.publish 'level:view-switched', e Backbone.Mediator.publish 'level:view-switched', e
showVersionHistory: (e) -> showVersionHistory: (e) ->

View file

@ -0,0 +1,40 @@
CocoView = require 'views/kinds/CocoView'
template = require 'templates/editor/level/related-achievements'
RelatedAchievementsCollection = require 'collections/RelatedAchievementsCollection'
Achievement = require 'models/Achievement'
NewAchievementModal = require './modals/NewAchievementModal'
app = require 'application'
module.exports = class RelatedAchievementsView extends CocoView
id: 'related-achievements-view'
template: template
className: 'tab-pane'
events:
'click #new-achievement-button': 'makeNewAchievement'
constructor: (options) ->
super options
@level = options.level
@relatedID = @level.id
@achievements = new RelatedAchievementsCollection @relatedID
@supermodel.loadCollection @achievements, 'achievements'
onLoaded: ->
console.debug 'related achievements loaded'
@achievements.loading = false
super()
getRenderData: ->
c = super()
c.achievements = @achievements
c.relatedID = @relatedID
c
onNewAchievementSaved: (achievement) ->
app.router.navigate('/editor/achievement/' + (achievement.get('slug') or achievement.id), {trigger: true})
makeNewAchievement: ->
modal = new NewAchievementModal model: Achievement, modelLabel: 'Achievement', level: @level
modal.once 'model-created', @onNewAchievementSaved
@openModalView modal

View file

@ -1,45 +0,0 @@
ModalView = require 'views/kinds/ModalView'
template = require 'templates/editor/level/fork'
forms = require 'lib/forms'
Level = require 'models/Level'
module.exports = class ForkLevelModal extends ModalView
id: 'editor-level-fork-modal'
template: template
instant: false
modalWidthPercent: 60
events:
'click #fork-level-confirm-button': 'forkLevel'
'submit form': 'forkLevel'
constructor: (options) ->
super options
@level = options.level
getRenderData: (context={}) ->
context = super(context)
context.level = @level
context
forkLevel: ->
@showLoading()
forms.clearFormAlerts(@$el)
newLevel = new Level($.extend(true, {}, @level.attributes))
newLevel.unset '_id'
newLevel.unset 'version'
newLevel.unset 'creator'
newLevel.unset 'created'
newLevel.unset 'original'
newLevel.unset 'parent'
newLevel.set 'commitMessage', "Forked from #{@level.get('name')}"
newLevel.set 'name', @$el.find('#level-name').val()
newLevel.set 'permissions', [access: 'owner', target: me.id]
res = newLevel.save()
return unless res
res.error =>
@hideLoading()
forms.applyErrorsToForm(@$el.find('form'), JSON.parse(res.responseText))
res.success =>
@hide()
application.router.navigate('editor/level/' + newLevel.get('slug'), {trigger: true})

View file

@ -0,0 +1,54 @@
NewModelModal = require 'views/modal/NewModelModal'
template = require 'templates/editor/level/modal/new-achievement'
forms = require 'lib/forms'
Achievement = require 'models/Achievement'
module.exports = class NewAchievementModal extends NewModelModal
id: 'new-achievement-modal'
template: template
plain: false
constructor: (options) ->
super options
@level = options.level
getRenderData: ->
c = super()
c.level = @level
console.debug 'level', c.level
c
createQuery: ->
checked = @$el.find('[name=queryOptions]:checked')
checkedValues = ($(check).val() for check in checked)
subQueries = []
for id in checkedValues
switch id
when 'misc-level-completion'
subQueries.push state: complete: true
else # It's a goal
q = state: goalStates: {}
q.state.goalStates[id] = {}
q.state.goalStates[id].status = 'success'
subQueries.push q
unless subQueries.length
query = {}
else if subQueries.length is 1
query = subQueries[0]
else
query = $or: subQueries
query
makeNewModel: ->
achievement = new Achievement
name = @$el.find('#name').val()
description = @$el.find('#description').val()
query = @createQuery()
achievement.set 'name', name
achievement.set 'description', description
achievement.set 'query', query
achievement.set 'collection', 'level.sessions'
achievement.set 'userField', 'creator'
achievement

View file

@ -10,8 +10,10 @@ ThangComponentsEditView = require 'views/editor/component/ThangComponentsEditVie
ThangTypeVersionsModal = require './ThangTypeVersionsModal' ThangTypeVersionsModal = require './ThangTypeVersionsModal'
ThangTypeColorsTabView = require './ThangTypeColorsTabView' ThangTypeColorsTabView = require './ThangTypeColorsTabView'
PatchesView = require 'views/editor/PatchesView' PatchesView = require 'views/editor/PatchesView'
ForkModal = require 'views/editor/ForkModal'
SaveVersionModal = require 'views/modal/SaveVersionModal' SaveVersionModal = require 'views/modal/SaveVersionModal'
template = require 'templates/editor/thang/thang-type-edit-view' template = require 'templates/editor/thang/thang-type-edit-view'
storage = require 'lib/storage'
CENTER = {x: 200, y: 300} CENTER = {x: 200, y: 300}
@ -35,8 +37,12 @@ module.exports = class ThangTypeEditView extends RootView
'click #marker-button': 'toggleDots' 'click #marker-button': 'toggleDots'
'click #end-button': 'endAnimation' 'click #end-button': 'endAnimation'
'click #history-button': 'showVersionHistory' 'click #history-button': 'showVersionHistory'
'click #fork-start-button': 'startForking'
'click #save-button': 'openSaveModal' 'click #save-button': 'openSaveModal'
'click #patches-tab': -> @patchesView.load() 'click #patches-tab': -> @patchesView.load()
'click .play-with-level-button': 'onPlayLevel'
'click .play-with-level-parent': 'onPlayLevelSelect'
'keyup .play-with-level-input': 'onPlayLevelKeyUp'
subscriptions: subscriptions:
'save-new-version': 'saveNewThangType' 'save-new-version': 'saveNewThangType'
@ -58,6 +64,7 @@ module.exports = class ThangTypeEditView extends RootView
context.thangType = @thangType context.thangType = @thangType
context.animations = @getAnimationNames() context.animations = @getAnimationNames()
context.authorized = not me.get('anonymous') context.authorized = not me.get('anonymous')
context.recentlyPlayedLevels = storage.load('recently-played-levels') ? ['items']
context context
getAnimationNames: -> getAnimationNames: ->
@ -401,12 +408,46 @@ module.exports = class ThangTypeEditView extends RootView
@showingSelectedNode = false @showingSelectedNode = false
showVersionHistory: (e) -> showVersionHistory: (e) ->
versionHistoryModal = new ThangTypeVersionsModal thangType: @thangType, @thangTypeID @openModalView new ThangTypeVersionsModal thangType: @thangType, @thangTypeID
@openModalView versionHistoryModal
Backbone.Mediator.publish 'level:view-switched', e
openSaveModal: -> openSaveModal: ->
@openModalView(new SaveVersionModal({model: @thangType})) @openModalView new SaveVersionModal model: @thangType
startForking: (e) ->
@openModalView new ForkModal model: @thangType, editorPath: 'thang'
onPlayLevelSelect: (e) ->
if @childWindow and not @childWindow.closed
# We already have a child window open, so we don't need to ask for a level; we'll use its existing level.
e.stopImmediatePropagation()
@onPlayLevel e
_.defer -> $('.play-with-level-input').focus()
onPlayLevelKeyUp: (e) ->
return unless e.keyCode is 13 # return
input = @$el.find('.play-with-level-input')
input.parents('.dropdown').find('.play-with-level-parent').dropdown('toggle')
level = _.string.slugify input.val()
return unless level
@onPlayLevel null, level
recentlyPlayedLevels = storage.load('recently-played-levels') ? []
recentlyPlayedLevels.push level
storage.save 'recently-played-levels', recentlyPlayedLevels
onPlayLevel: (e, level=null) ->
level ?= $(e.target).data('level')
level = _.string.slugify level
if @childWindow and not @childWindow.closed
# Reset the LevelView's world, but leave the rest of the state alone
@childWindow.Backbone.Mediator.publish 'level-reload-thang-type', thangType: @thangType
else
# Create a new Window with a blank LevelView
scratchLevelID = level + '?dev=true'
if me.get('name') is 'Nick'
@childWindow = window.open("/play/level/#{scratchLevelID}", 'child_window', 'width=2560,height=1080,left=0,top=-1600,location=1,menubar=1,scrollbars=1,status=0,titlebar=1,toolbar=1', true)
else
@childWindow = window.open("/play/level/#{scratchLevelID}", 'child_window', 'width=1024,height=560,left=10,top=10,location=0,menubar=0,scrollbars=0,status=0,titlebar=0,toolbar=0', true)
@childWindow.focus()
destroy: -> destroy: ->
@camera?.destroy() @camera?.destroy()

View file

@ -48,6 +48,20 @@ module.exports = class OptionsView extends CocoView
afterRender: -> afterRender: ->
super() super()
@volumeSlider = @$el.find('#option-volume').slider(animate: 'fast', min: 0, max: 1, step: 0.05)
@volumeSlider.slider('value', me.get('volume'))
@volumeSlider.on('slide', @onVolumeSliderChange)
@volumeSlider.on('slidechange', @onVolumeSliderChange)
destroy: ->
@volumeSlider?.slider?('destroy')
super()
onVolumeSliderChange: (e) =>
volume = @volumeSlider.slider('value')
me.set 'volume', volume
@$el.find('#option-volume-value').text (volume * 100).toFixed(0) + '%'
Backbone.Mediator.publish 'level-set-volume', volume: volume
onHidden: -> onHidden: ->
if @playerName and @playerName isnt me.get('name') if @playerName and @playerName isnt me.get('name')

View file

@ -6,8 +6,9 @@ CocoView = require './CocoView'
{logoutUser, me} = require('lib/auth') {logoutUser, me} = require('lib/auth')
locale = require 'locale/locale' locale = require 'locale/locale'
Achievement = require '../../models/Achievement' AchievementPopup = require 'views/achievements/AchievementPopup'
User = require '../../models/User' utils = require 'lib/utils'
# TODO remove # TODO remove
filterKeyboardEvents = (allowedEvents, func) -> filterKeyboardEvents = (allowedEvents, func) ->
@ -32,61 +33,13 @@ module.exports = class RootView extends CocoView
'achievements:new': 'handleNewAchievements' 'achievements:new': 'handleNewAchievements'
showNewAchievement: (achievement, earnedAchievement) -> showNewAchievement: (achievement, earnedAchievement) ->
currentLevel = me.level() popup = new AchievementPopup achievement: achievement, earnedAchievement: earnedAchievement
nextLevel = currentLevel + 1
currentLevelExp = User.expForLevel(currentLevel)
nextLevelExp = User.expForLevel(nextLevel)
totalExpNeeded = nextLevelExp - currentLevelExp
expFunction = achievement.getExpFunction()
currentExp = me.get('points')
previousExp = currentExp - achievement.get('worth')
previousExp = expFunction(earnedAchievement.get('previouslyAchievedAmount')) * achievement.get('worth') if achievement.isRepeatable()
achievedExp = currentExp - previousExp
leveledUp = currentExp - achievedExp < currentLevelExp
alreadyAchievedPercentage = 100 * (previousExp - currentLevelExp) / totalExpNeeded
newlyAchievedPercentage = if leveledUp then 100 * (currentExp - currentLevelExp) / totalExpNeeded else 100 * achievedExp / totalExpNeeded
console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)."
console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{previousExp} and just now earned #{achievedExp} totalling on #{currentExp}"
alreadyAchievedBar = $("<div class='progress-bar progress-bar-warning' style='width:#{alreadyAchievedPercentage}%'></div>")
newlyAchievedBar = $("<div data-toggle='tooltip' class='progress-bar progress-bar-success' style='width:#{newlyAchievedPercentage}%'></div>")
emptyBar = $("<div data-toggle='tooltip' class='progress-bar progress-bar-white' style='width:#{100 - newlyAchievedPercentage - alreadyAchievedPercentage}%'></div>")
progressBar = $('<div class="progress" data-toggle="tooltip"></div>').append(alreadyAchievedBar).append(newlyAchievedBar).append(emptyBar)
message = if (currentLevel isnt 1) and leveledUp then "Reached level #{currentLevel}!" else null
alreadyAchievedBar.tooltip(title: "#{currentExp} XP in total")
newlyAchievedBar.tooltip(title: "#{achievedExp} XP earned")
emptyBar.tooltip(title: "#{nextLevelExp - currentExp} XP until level #{nextLevel}")
# TODO a default should be linked here
imageURL = '/file/' + achievement.get('icon')
data =
title: achievement.get('name')
image: $("<img src='#{imageURL}' />")
description: achievement.get('description')
progressBar: progressBar
earnedExp: "+ #{achievedExp} XP"
message: message
options =
autoHideDelay: 10000
globalPosition: 'bottom right'
showDuration: 400
style: 'achievement'
autoHide: true
clickToHide: true
$.notify( data, options )
handleNewAchievements: (earnedAchievements) -> handleNewAchievements: (earnedAchievements) ->
_.each(earnedAchievements.models, (earnedAchievement) => _.each earnedAchievements.models, (earnedAchievement) =>
achievement = new Achievement(_id: earnedAchievement.get('achievement')) achievement = new Achievement(_id: earnedAchievement.get('achievement'))
console.log achievement achievement.fetch
achievement.fetch(
success: (achievement) => @showNewAchievement(achievement, earnedAchievement) success: (achievement) => @showNewAchievement(achievement, earnedAchievement)
)
)
logoutAccount: -> logoutAccount: ->
logoutUser($('#login-email').val()) logoutUser($('#login-email').val())

View file

@ -1,6 +1,6 @@
RootView = require 'views/kinds/RootView' RootView = require 'views/kinds/RootView'
NewModelModal = require 'views/modal/NewModelModal'
template = require 'templates/kinds/search' template = require 'templates/kinds/search'
forms = require 'lib/forms'
app = require 'application' app = require 'application'
class SearchCollection extends Backbone.Collection class SearchCollection extends Backbone.Collection
@ -26,9 +26,7 @@ module.exports = class SearchView extends RootView
events: events:
'change input#search': 'runSearch' 'change input#search': 'runSearch'
'keydown input#search': 'runSearch' 'keydown input#search': 'runSearch'
'click button.new-model-submit': 'makeNewModel' 'click #new-model-button': 'newModel'
'submit form': 'makeNewModel'
'shown.bs.modal #new-model-modal': 'focusOnName'
'hidden.bs.modal #new-model-modal': 'onModalHidden' 'hidden.bs.modal #new-model-modal': 'onModalHidden'
constructor: (options) -> constructor: (options) ->
@ -79,31 +77,11 @@ module.exports = class SearchView extends RootView
@collection.off() @collection.off()
@collection = null @collection = null
makeNewModel: (e) -> onNewModelSaved: (@model) ->
e.preventDefault()
name = @$el.find('#name').val()
model = new @model()
model.set('name', name)
if @model.schema.properties.permissions
model.set 'permissions', [{access: 'owner', target: me.id}]
res = model.save()
return unless res
modal = @$el.find('#new-model-modal')
forms.clearFormAlerts(modal)
@showLoading(modal.find('.modal-body'))
res.error =>
@hideLoading()
forms.applyErrorsToForm(modal, JSON.parse(res.responseText))
that = @
res.success ->
that.model = model
modal.modal('hide')
onModalHidden: ->
# Can only redirect after the modal hidden event has triggered
base = document.location.pathname[1..] + '/' base = document.location.pathname[1..] + '/'
app.router.navigate(base + (@model.get('slug') or @model.id), {trigger: true}) app.router.navigate(base + (@model.get('slug') or @model.id), {trigger: true})
focusOnName: -> newModel: (e) ->
@$el.find('#name').focus() modal = new NewModelModal model: @model, modelLabel: @modelLabel
modal.once 'success', @onNewModelSaved
@openModalView modal

View file

@ -0,0 +1,35 @@
RootView = require 'views/kinds/RootView'
template = require 'templates/kinds/user'
User = require 'models/User'
module.exports = class UserView extends RootView
template: template
className: 'user-view'
viewName: null # Used for the breadcrumbs
constructor: (@userID, options) ->
super options
@listenTo @, 'userNotFound', @ifUserNotFound
@fetchUser @userID
fetchUser: (id) ->
if @isMe()
@user = me
@onLoaded()
@user = new User _id: id
@supermodel.loadModel @user, 'user'
getRenderData: ->
context = super()
context.viewName = @viewName
context.user = @user unless @user?.isAnonymous()
context
isMe: -> @userID is me.id
onLoaded: ->
super()
ifUserNotFound: ->
console.warn 'user not found'
@render()

View file

@ -8,8 +8,8 @@ module.exports = class ConfirmModal extends ModalView
closeOnConfirm: true closeOnConfirm: true
events: events:
'click #decline-button': 'doDecline' 'click #decline-button': 'onDecline'
'click #confirm-button': 'doConfirm' 'click #confirm-button': 'onConfirm'
constructor: (@renderData={}, options={}) -> constructor: (@renderData={}, options={}) ->
super(options) super(options)
@ -21,10 +21,6 @@ module.exports = class ConfirmModal extends ModalView
setRenderData: (@renderData) -> setRenderData: (@renderData) ->
onDecline: (@decline) -> onDecline: -> @trigger 'decline'
onConfirm: (@confirm) -> onConfirm: -> @trigger 'confirm'
doConfirm: -> @confirm() if @confirm
doDecline: -> @decline() if @decline

View file

@ -0,0 +1,54 @@
ModalView = require 'views/kinds/ModalView'
template = require 'templates/modal/new_model'
forms = require 'lib/forms'
module.exports = class NewModelModal extends ModalView
id: 'new-model-modal'
template: template
plain: false
events:
'click button.new-model-submit': 'onModelSubmitted'
'submit form': 'onModelSubmitted'
constructor: (options) ->
super options
@model = options.model
@modelLabel = options.modelLabel
@properties = options.properties
$('#name').ready @focusOnName
getRenderData: ->
c = super()
c.modelLabel = @modelLabel
#c.newModelTitle = @newModelTitle
c
makeNewModel: ->
model = new @model
name = @$el.find('#name').val()
model.set('name', name)
if @model.schema.properties.permissions
model.set 'permissions', [{access: 'owner', target: me.id}]
model.set(key, prop) for key, prop of @properties if @properties?
model
onModelSubmitted: (e) ->
e.preventDefault()
model = @makeNewModel()
res = model.save()
return unless res
forms.clearFormAlerts @$el
@showLoading(@$el.find('.modal-body'))
res.error =>
@hideLoading()
forms.applyErrorsToForm(@$el, JSON.parse(res.responseText))
#Backbone.Mediator.publish 'model-save-fail', model
res.success =>
@$el.modal('hide')
@trigger 'model-created', model
#Backbone.Mediator.publish 'model-save-success', model
focusOnName: (e) ->
$('#name').focus() # TODO Why isn't this working anymore.. It does get called

View file

@ -193,35 +193,7 @@ module.exports = class MainPlayView extends RootView
} }
] ]
playerCreated = [ classicAlgorithms = [
{
name: 'Extra Extrapolation'
difficulty: 2
id: 'extra-extrapolation'
image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png'
description: 'Predict your target\'s position for deadly aim. - by Sootn'
}
{
name: 'The Right Route'
difficulty: 1
id: 'the-right-route'
image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png'
description: 'Strike at the weak point in an array of enemies. - by Aftermath'
}
{
name: 'Sword Loop'
difficulty: 2
id: 'sword-loop'
image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png'
description: 'Kill the ogres and save the peasants with for-loops. - by Prabh Simran Singh Baweja'
}
{
name: 'Coin Mania'
difficulty: 2
id: 'coin-mania'
image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png'
description: 'Learn while-loops to grab coins and potions. - by Prabh Simran Singh Baweja'
}
{ {
name: 'Bubble Sort Bootcamp Battle' name: 'Bubble Sort Bootcamp Battle'
difficulty: 3 difficulty: 3
@ -257,6 +229,37 @@ module.exports = class MainPlayView extends RootView
image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
description: 'Learn Quicksort while sorting a spiral of ogres! - by Alexandru Caciulescu' description: 'Learn Quicksort while sorting a spiral of ogres! - by Alexandru Caciulescu'
} }
]
playerCreated = [
{
name: 'Extra Extrapolation'
difficulty: 2
id: 'extra-extrapolation'
image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png'
description: 'Predict your target\'s position for deadly aim. - by Sootn'
}
{
name: 'The Right Route'
difficulty: 1
id: 'the-right-route'
image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png'
description: 'Strike at the weak point in an array of enemies. - by Aftermath'
}
{
name: 'Sword Loop'
difficulty: 2
id: 'sword-loop'
image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png'
description: 'Kill the ogres and save the peasants with for-loops. - by Prabh Simran Singh Baweja'
}
{
name: 'Coin Mania'
difficulty: 2
id: 'coin-mania'
image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png'
description: 'Learn while-loops to grab coins and potions. - by Prabh Simran Singh Baweja'
}
{ {
name: 'Find the Spy' name: 'Find the Spy'
difficulty: 2 difficulty: 2
@ -291,6 +294,7 @@ module.exports = class MainPlayView extends RootView
{id: 'beginner', name: 'Beginner Campaign', description: '... in which you learn the wizardry of programming.', levels: tutorials} {id: 'beginner', name: 'Beginner Campaign', description: '... in which you learn the wizardry of programming.', levels: tutorials}
{id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas} {id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas}
{id: 'dev', name: 'Random Harder Levels', description: '... in which you learn the interface while doing something a little harder.', levels: experienced} {id: 'dev', name: 'Random Harder Levels', description: '... in which you learn the interface while doing something a little harder.', levels: experienced}
{id: 'classic' ,name: 'Classic Algorithms', description: '... in which you learn the most popular algorithms in Computer Science.', levels: classicAlgorithms}
{id: 'player_created', name: 'Player-Created', description: '... in which you battle against the creativity of your fellow <a href=\"/contribute#artisan\">Artisan Wizards</a>.', levels: playerCreated} {id: 'player_created', name: 'Player-Created', description: '... in which you battle against the creativity of your fellow <a href=\"/contribute#artisan\">Artisan Wizards</a>.', levels: playerCreated}
] ]
context.levelStatusMap = @levelStatusMap context.levelStatusMap = @levelStatusMap

View file

@ -55,6 +55,7 @@ module.exports = class PlayLevelView extends RootView
'god:new-world-created': 'onNewWorld' 'god:new-world-created': 'onNewWorld'
'god:infinite-loop': 'onInfiniteLoop' 'god:infinite-loop': 'onInfiniteLoop'
'level-reload-from-data': 'onLevelReloadFromData' 'level-reload-from-data': 'onLevelReloadFromData'
'level-reload-thang-type': 'onLevelReloadThangType'
'play-next-level': 'onPlayNextLevel' 'play-next-level': 'onPlayNextLevel'
'edit-wizard-settings': 'showWizardSettingsModal' 'edit-wizard-settings': 'showWizardSettingsModal'
'surface:world-set-up': 'onSurfaceSetUpNewWorld' 'surface:world-set-up': 'onSurfaceSetUpNewWorld'
@ -326,6 +327,15 @@ module.exports = class PlayLevelView extends RootView
@scriptManager.setScripts(e.level.get('scripts')) @scriptManager.setScripts(e.level.get('scripts'))
Backbone.Mediator.publish 'tome:cast-spell' # a bit hacky Backbone.Mediator.publish 'tome:cast-spell' # a bit hacky
onLevelReloadThangType: (e) ->
tt = e.thangType
for url, model of @supermodel.models
if model.id is tt.id
for key, val of tt.attributes
model.attributes[key] = val
break
Backbone.Mediator.publish 'tome:cast-spell'
onWindowResize: (s...) -> onWindowResize: (s...) ->
$('#pointer').css('opacity', 0.0) $('#pointer').css('opacity', 0.0)

View file

@ -0,0 +1,56 @@
UserView = require 'views/kinds/UserView'
template = require 'templates/user/achievements'
{me} = require 'lib/auth'
Achievement = require 'models/Achievement'
EarnedAchievement = require 'models/EarnedAchievement'
AchievementCollection = require 'collections/AchievementCollection'
EarnedAchievementCollection = require 'collections/EarnedAchievementCollection'
module.exports = class AchievementsView extends UserView
id: 'user-achievements-view'
template: template
viewName: 'Stats'
activeLayout: 'grid'
events:
'click #grid-layout-button': 'layoutChanged'
'click #table-layout-button': 'layoutChanged'
constructor: (userID, options) ->
super options, userID
onLoaded: ->
unless @achievements or @earnedAchievements
@supermodel.resetProgress()
@achievements = new AchievementCollection
@earnedAchievements = new EarnedAchievementCollection @user.getSlugOrID()
@supermodel.loadCollection @achievements, 'achievements'
@supermodel.loadCollection @earnedAchievements, 'earnedAchievements'
else
for earned in @earnedAchievements.models
return unless relatedAchievement = _.find @achievements.models, (achievement) ->
achievement.get('_id') is earned.get 'achievement'
relatedAchievement.set 'unlocked', true
earned.set 'achievement', relatedAchievement
deferredImages = (achievement.cacheLockedImage() for achievement in @achievements.models when not achievement.get 'unlocked')
whenever = $.when deferredImages...
whenever.done => @render()
super()
layoutChanged: (e) ->
@activeLayout = $(e.currentTarget).data 'layout'
@render()
getRenderData: ->
context = super()
context.activeLayout = @activeLayout
# After user is loaded
if @user and not @user.isAnonymous()
context.earnedAchievements = @earnedAchievements
context.achievements = @achievements
context.achievementsByCategory = {}
for achievement in @achievements.models
context.achievementsByCategory[achievement.get('category')] ?= []
context.achievementsByCategory[achievement.get('category')].push achievement
context

View file

@ -1,4 +1,4 @@
RootView = require 'views/kinds/RootView' UserView = require 'views/kinds/UserView'
template = require 'templates/account/profile' template = require 'templates/account/profile'
User = require 'models/User' User = require 'models/User'
LevelSession = require 'models/LevelSession' LevelSession = require 'models/LevelSession'
@ -26,7 +26,7 @@ adminContacts = [
{id: '52a57252a89409700d0000d9', name: 'Ignore'} {id: '52a57252a89409700d0000d9', name: 'Ignore'}
] ]
module.exports = class JobProfileView extends RootView module.exports = class JobProfileView extends UserView
id: 'profile-view' id: 'profile-view'
template: template template: template
showBackground: false showBackground: false
@ -54,8 +54,7 @@ module.exports = class JobProfileView extends RootView
'change #admin-contact': 'onAdminContactChanged' 'change #admin-contact': 'onAdminContactChanged'
'click .session-link': 'onSessionLinkPressed' 'click .session-link': 'onSessionLinkPressed'
constructor: (options, @userID) -> constructor: (userID, options) ->
@userID ?= me.id
@onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000 @onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000
@onRemarkChanged = _.debounce @onRemarkChanged, 1000 @onRemarkChanged = _.debounce @onRemarkChanged, 1000
@authorizedWithLinkedIn = IN?.User?.isAuthorized() @authorizedWithLinkedIn = IN?.User?.isAuthorized()
@ -64,32 +63,19 @@ module.exports = class JobProfileView extends RootView
window.contractCallback = => window.contractCallback = =>
@authorizedWithLinkedIn = IN?.User?.isAuthorized() @authorizedWithLinkedIn = IN?.User?.isAuthorized()
@render() @render()
super options super options, userID
if me.get('anonymous') is true
@render() onUserLoaded: ->
return
if User.isObjectID @userID
@finishInit()
else
$.ajax "/db/user/#{@userID}/nameToID", success: (@userID) =>
@finishInit() unless @destroyed @finishInit() unless @destroyed
@render() super()
finishInit: -> finishInit: ->
return unless @userID return unless @userID
@uploadFilePath = "db/user/#{@userID}" @uploadFilePath = "db/user/#{@userID}"
@highlightedContainers = [] @highlightedContainers = []
if @userID is me.id if me.isAdmin() or 'employer' in me.get('permissions')
@user = me
else if me.isAdmin() or 'employer' in me.get('permissions')
@user = User.getByID(@userID)
@user.fetch()
@listenTo @user, 'sync', =>
@render()
$.post "/db/user/#{me.id}/track/view_candidate" $.post "/db/user/#{me.id}/track/view_candidate"
$.post "/db/user/#{@userID}/track/viewed_by_employer" unless me.isAdmin() $.post "/db/user/#{@userID}/track/viewed_by_employer" unless me.isAdmin()
else
@user = User.getByID(@userID)
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(@userID), 'candidate_sessions').model @sessions = @supermodel.loadCollection(new LevelSessionsCollection(@userID), 'candidate_sessions').model
if me.isAdmin() if me.isAdmin()
# Mimicking how the VictoryModal fetches LevelFeedback # Mimicking how the VictoryModal fetches LevelFeedback
@ -248,7 +234,7 @@ module.exports = class JobProfileView extends RootView
jobProfile.name ?= (@user.get('firstName') + ' ' + @user.get('lastName')).trim() if @user?.get('firstName') jobProfile.name ?= (@user.get('firstName') + ' ' + @user.get('lastName')).trim() if @user?.get('firstName')
context.profile = jobProfile context.profile = jobProfile
context.user = @user context.user = @user
context.myProfile = @user?.id is context.me.id context.myProfile = @isMe()
context.allowedToViewJobProfile = @user and (me.isAdmin() or 'employer' in me.get('permissions') or (context.myProfile && !me.get('anonymous'))) context.allowedToViewJobProfile = @user and (me.isAdmin() or 'employer' in me.get('permissions') or (context.myProfile && !me.get('anonymous')))
context.allowedToEditJobProfile = @user and (me.isAdmin() or (context.myProfile && !me.get('anonymous'))) context.allowedToEditJobProfile = @user and (me.isAdmin() or (context.myProfile && !me.get('anonymous')))
context.profileApproved = @user?.get 'jobProfileApproved' context.profileApproved = @user?.get 'jobProfileApproved'
@ -289,7 +275,7 @@ module.exports = class JobProfileView extends RootView
_.delay -> _.delay ->
justSavedSection.removeClass 'just-saved', duration: 1500, easing: 'easeOutQuad' justSavedSection.removeClass 'just-saved', duration: 1500, easing: 'easeOutQuad'
, 500 , 500
if me.isAdmin() if me.isAdmin() and @user
visibleSettings = ['history', 'tasks'] visibleSettings = ['history', 'tasks']
data = _.pick (@remark.attributes), (value, key) -> key in visibleSettings data = _.pick (@remark.attributes), (value, key) -> key in visibleSettings
data.history ?= [] data.history ?= []

View file

@ -0,0 +1,54 @@
UserView = require 'views/kinds/UserView'
CocoCollection = require 'collections/CocoCollection'
LevelSession = require 'models/LevelSession'
template = require 'templates/user/user_home'
{me} = require 'lib/auth'
EarnedAchievementCollection = require 'collections/EarnedAchievementCollection'
class LevelSessionsCollection extends CocoCollection
model: LevelSession
constructor: (userID) ->
@url = "/db/user/#{userID}/level.sessions?project=state.complete,levelID,levelName,changed,team,submittedCodeLanguage,totalScore&order=-1"
super()
module.exports = class MainUserView extends UserView
id: 'user-home'
template: template
constructor: (userID, options) ->
super options
getRenderData: ->
context = super()
if @levelSessions and @levelSessions.loaded
singlePlayerSessions = []
multiPlayerSessions = []
languageCounts = {}
for levelSession in @levelSessions.models
if levelSession.isMultiplayer()
multiPlayerSessions.push levelSession
else
singlePlayerSessions.push levelSession
languageCounts[levelSession.get 'submittedCodeLanguage'] = (languageCounts[levelSession.get 'submittedCodeLanguage'] or 0) + 1
mostUsedCount = 0
favoriteLanguage = null
for language, count of languageCounts
if count > mostUsedCount
mostUsedCount = count
favoriteLanguage = language
context.singlePlayerSessions = singlePlayerSessions
context.multiPlayerSessions = multiPlayerSessions
context.favoriteLanguage = favoriteLanguage
if @earnedAchievements and @earnedAchievements.loaded
context.earnedAchievements = @earnedAchievements
context
onLoaded: ->
if @user.loaded and not (@earnedAchievements or @levelSessions)
@supermodel.resetProgress()
@levelSessions = new LevelSessionsCollection @user.getSlugOrID()
@earnedAchievements = new EarnedAchievementCollection @user.getSlugOrID()
@supermodel.loadCollection @levelSessions, 'levelSessions'
@supermodel.loadCollection @earnedAchievements, 'earnedAchievements'
super()

View file

@ -32,7 +32,7 @@
"firepad": "~0.1.2", "firepad": "~0.1.2",
"marked": "~0.3.0", "marked": "~0.3.0",
"moment": "~2.5.0", "moment": "~2.5.0",
"aether": "~0.2.22", "aether": "~0.2.28",
"underscore.string": "~2.3.3", "underscore.string": "~2.3.3",
"firebase": "~1.0.2", "firebase": "~1.0.2",
"catiline": "~2.9.3", "catiline": "~2.9.3",
@ -40,7 +40,7 @@
"jsondiffpatch": "~0.1.5", "jsondiffpatch": "~0.1.5",
"nanoscroller": "~0.8.0", "nanoscroller": "~0.8.0",
"jquery.tablesorter": "~2.15.13", "jquery.tablesorter": "~2.15.13",
"treema": "~0.0.12", "treema": "~0.0.14",
"bootstrap": "~3.1.1", "bootstrap": "~3.1.1",
"validated-backbone-mediator": "~0.1.3", "validated-backbone-mediator": "~0.1.3",
"jquery.browser": "~0.0.6", "jquery.browser": "~0.0.6",

View file

@ -42,6 +42,7 @@
"winston": "0.6.x", "winston": "0.6.x",
"passport": "0.1.x", "passport": "0.1.x",
"passport-local": "0.1.x", "passport-local": "0.1.x",
"moment": "~2.5.0",
"mongodb": "1.2.x", "mongodb": "1.2.x",
"mongoose": "3.8.x", "mongoose": "3.8.x",
"mongoose-text-search": "~0.0.2", "mongoose-text-search": "~0.0.2",
@ -65,7 +66,7 @@
"redis": "", "redis": "",
"webworker-threads": "~0.4.11", "webworker-threads": "~0.4.11",
"node-gyp": "~0.13.0", "node-gyp": "~0.13.0",
"aether": "~0.2.22", "aether": "~0.2.28",
"JASON": "~0.1.3", "JASON": "~0.1.3",
"JQDeferred": "~2.1.0" "JQDeferred": "~2.1.0"
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Some files were not shown because too many files have changed in this diff Show more