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')
'account/profile(/:userID)': go('account/JobProfileView')
'account': go('account/MainAccountView')
'account/settings': go('account/AccountSettingsView')
'account/unsubscribe': go('account/UnsubscribeView')
#'account/payment'
'admin': go('admin/MainAdminView')
'admin/candidates': go('admin/CandidatesView')
@ -50,7 +51,7 @@ module.exports = class CocoRouter extends Backbone.Router
'editor': go('editor/MainEditorView')
'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/preview': go('editor/article/ArticlePreviewView')
'editor/article/:articleID': go('editor/article/ArticleEditView')
@ -79,6 +80,11 @@ module.exports = class CocoRouter extends Backbone.Router
'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'
routeToServer: (e) ->

View file

@ -5,7 +5,6 @@ locale = require 'locale/locale'
{me} = require 'lib/auth'
Tracker = require 'lib/Tracker'
CocoView = require 'views/kinds/CocoView'
AchievementNotify = require '../../templates/achievement_notify'
marked.setOptions {gfm: true, sanitize: true, smartLists: true, breaks: false}
@ -40,7 +39,6 @@ Application = initialize: ->
@facebookHandler = new FacebookHandler()
@gplusHandler = new GPlusHandler()
$(document).bind 'keydown', preventBackspace
$.notify.addStyle 'achievement', html: $(AchievementNotify())
@linkedinHandler = new LinkedInHandler()
preload(COMMON_FILES)
$.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'
Achievement = require 'models/Achievement'
class NewAchievementCollection extends CocoCollection
model: Achievement
initialize: (me = require('lib/auth').me) ->
@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'
{me} = require 'lib/auth'
LevelSession = require 'models/LevelSession'
utils = require 'lib/utils'
module.exports = class LevelBus extends Bus
@ -22,6 +23,7 @@ module.exports = class LevelBus extends Bus
'tome:spell-changed': 'onSpellChanged'
'tome:spell-created': 'onSpellCreated'
'application:idle-changed': 'onIdleChanged'
'goal-manager:new-goal-states': 'onNewGoalStates'
constructor: ->
super(arguments...)
@ -192,6 +194,14 @@ module.exports = class LevelBus extends Bus
@changedSessionProperties.state = true
@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) =>
super(arguments...)
return unless @onPoint()

View file

@ -24,6 +24,7 @@ doQuerySelector = (value, operatorObj) ->
matchesQuery = (target, 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
if prop[0] == '$'
switch prop

View file

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

View file

@ -1,4 +1,4 @@
SystemNameLoader = require 'lib/SystemNameLoader'
SystemNameLoader = require './SystemNameLoader'
###
Good-to-knows:
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)
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
return [] unless delta
dataPath ?= []
@ -175,3 +175,5 @@ prunePath = (delta, path) ->
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')
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
module.exports.getByPath = (target, path) ->
throw new Error 'Expected an object to match a query against, instead got null' unless target
pieces = path.split('.')
obj = target
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) ->
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
createLinearFunc = (params) ->
@ -100,3 +101,32 @@ module.exports.functionCreators =
linear: positify(createLinearFunc)
quadratic: positify(createQuadraticFunc)
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"
forum: "Forum"
account: "Account"
profile: "Profile"
stats: "Stats"
code: "Code"
admin: "Admin"
home: "Home"
contribute: "Contribute"
@ -176,12 +179,14 @@
new_password: "New Password"
new_password_verify: "Verify"
email_subscriptions: "Email Subscriptions"
email_subscriptions_none: "No Email Subscriptions."
email_announcements: "Announcements"
email_announcements_description: "Get emails on the latest news and developments at CodeCombat."
email_notifications: "Notifications"
email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
email_any_notes: "Any Notifications"
email_any_notes_description: "Disable to stop all activity notification emails."
email_news: "News"
email_recruit_notes: "Job Opportunities"
email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
contributor_emails: "Contributor Class Emails"
@ -591,6 +596,10 @@
level_search_title: "Search Levels Here"
achievement_search_title: "Search Achievements"
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:
edit_btn_preview: "Preview"
@ -599,6 +608,7 @@
general:
and: "and"
name: "Name"
date: "Date"
body: "Body"
version: "Version"
commit_msg: "Commit Message"
@ -938,3 +948,38 @@
text_diff: "Text Diff"
merge_conflict_with: "MERGE CONFLICT WITH"
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
pt: require './pt' # português, Portuguese
'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
it: require './it' # italiano, Italian
tr: require './tr' # Türkçe, Turkish
@ -58,3 +58,4 @@ module.exports =
hi: require './hi' # ि, Hindi
ur: require './ur' # اُردُو, Urdu
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"
# inactive: "Not looking for offers right now"
# complete: "complete"
# next: "Next"
# next_city: "city?"
next: "Seguinte"
next_city: "cidade?"
# next_country: "pick your country."
# next_name: "name?"
next_name: "nome?"
# next_short_description: "write a short description."
# next_long_description: "describe your desired position."
# 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_photo: "add an optional professional photo."
# next_active: "mark yourself open to offers to show up in searches."
# example_blog: "Blog"
# example_personal_site: "Personal Site"
# links_header: "Personal Links"
example_blog: "Blog"
example_personal_site: "Sítio Pessoal"
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_name: "Link Name"
# links_name_help: "What are you linking to?"
# links_link_blurb: "Link URL"
links_name: "Nome da Ligação"
links_name_help: "A que é que está a ligar?"
links_link_blurb: "URL da Ligação"
# basics_header: "Update basic info"
# basics_active: "Open to Offers"
# basics_active_help: "Want interview offers right now?"
# basics_job_title: "Desired Job Title"
# 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_country: "Country"
basics_country: "País"
# basics_country_help: "Country you want to work in (or live in now)."
# 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_looking_for: "Looking For"
# basics_looking_for_full_time: "Full-time"
# basics_looking_for_part_time: "Part-time"
# basics_looking_for_remote: "Remote"
basics_looking_for: "À Procura De"
basics_looking_for_full_time: "Tempo Inteiro"
basics_looking_for_part_time: "Part-time"
basics_looking_for_remote: "Remoto"
# basics_looking_for_contracting: "Contracting"
# basics_looking_for_internship: "Internship"
# basics_looking_for_help: "What kind of developer position do you want?"
# 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'."
# 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: "Tagline"
# 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."
# long_description_header: "Describe your desired position"
# 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."
# work_experience: "Work Experience"
# 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_blurb: "List your relevant work experience, most recent first."
# work_employer: "Employer"
# work_employer_help: "Name of your employer."
# work_role: "Job Title"
work_employer: "Empregador"
work_employer_help: "Nome do seu empregador."
work_role: "Título do Emprego"
# 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_description: "Description"
work_description: "Descrição"
# work_description_help: "What did you do there? (140 chars; optional)"
# education: "Education"
education: "Educação"
# education_header: "Recount your academic ordeals"
# education_blurb: "List your academic ordeals."
# education_school: "School"
# education_school_help: "Name of your school."
education_school: "Escola"
education_school_help: "Nome da sua escola."
# education_degree: "Degree"
# education_degree_help: "What was your degree and field of study?"
# education_duration: "Dates"
@ -312,18 +312,18 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
filter_visa: "Visa"
filter_visa_yes: "Autorizado Para Trabalhar Nos EUA"
filter_visa_no: "Não Autorizado"
# filter_education_top: "Top School"
# filter_education_other: "Other"
# filter_role_web_developer: "Web Developer"
# filter_role_software_developer: "Software Developer"
# filter_role_mobile_developer: "Mobile Developer"
# filter_experience: "Experience"
# filter_experience_senior: "Senior"
# filter_experience_junior: "Junior"
filter_education_top: "Universidade"
filter_education_other: "Outro"
filter_role_web_developer: "Desenvolvedor da Web"
filter_role_software_developer: "Desenvolvedor de Software"
filter_role_mobile_developer: "Desenvolvedor Mobile"
filter_experience: "Experiência"
filter_experience_senior: "Sénior"
filter_experience_junior: "Júnior"
# filter_experience_recent_grad: "Recent Grad"
# filter_experience_student: "College Student"
# filter_results: "results"
# start_hiring: "Start hiring."
filter_experience_student: "Estudante Universitário"
filter_results: "resultados"
start_hiring: "Começar a contratar."
# reasons: "Three reasons you should hire through us:"
# 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."
@ -408,11 +408,11 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
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_reticulating: "A reticular espinhas."
tip_harry: "Você é um Feitiçeiro, "
# tip_great_responsibility: "With great coding skill comes great debug responsibility."
# tip_munchkin: "If you don't eat your vegetables, a munchkin will come after you while you're asleep."
tip_harry: "Você é um Feiticeiro, "
tip_great_responsibility: "Com uma grande habilidade de programação vem uma grande responsabilidade de depuração."
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_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_patience: "Paciência tu deves ter, jovem Padawan. - Yoda"
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"
save_load:
# granularity_saved_games: "Saved"
granularity_saved_games: "Guardados"
granularity_change_history: "Histórico"
options:
@ -504,7 +504,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# toggle_grid: "Toggle grid overlay."
# toggle_pathfinding: "Toggle pathfinding overlay."
# 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:
av_title: "Vistas de Administrador"
@ -547,7 +547,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
grassy: "Com Relva"
fork_title: "Bifurcar Nova Versão"
fork_creating: "A Criar Bifurcação..."
# randomize: "Randomize"
randomize: "Randomizar"
more: "Mais"
wiki: "Wiki"
live_chat: "Chat Ao Vivo"
@ -561,8 +561,8 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
level_tab_thangs_all: "Todos"
level_tab_thangs_conditions: "Condições Iniciais"
level_tab_thangs_add: "Adicionar Thangs"
# delete: "Delete"
# duplicate: "Duplicate"
delete: "Eliminar"
duplicate: "Duplicar"
level_settings_title: "Configurações"
level_component_tab_title: "Componentes Atuais"
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_type: "Tipo"
level_component_edit_title: "Editar Componente"
# level_component_config_schema: "Config Schema"
level_component_config_schema: "Configurar Esquema"
level_component_settings: "Configurações"
level_system_edit_title: "Editar 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_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."
# why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that."
# why_paragraph_2: "Need to learn to code? You don't need lessons. You need to write a lot of code and have a great time doing it."
# why_paragraph_3_prefix: "That's what programming is about. It's gotta be fun. Not fun like"
# why_paragraph_3_italic: "yay a badge"
# why_paragraph_3_center: "but fun like"
# why_paragraph_3_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!"
# why_paragraph_3_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
# why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
# why_ending: "And hey, it's free. "
# why_ending_url: "Start wizarding now!"
# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat."
# jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy."
# michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online."
# 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!"
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: "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: "Afinal, é sobre isso que é a programação. Tem de ser divertida. Não divertida do género"
why_paragraph_3_italic: "yay uma medalha"
why_paragraph_3_center: "mas sim divertida do género"
why_paragraph_3_italic_caps: "NÃO MÃE, TENHO DE ACABAR O NÍVEL!"
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: "Se vais ficar viciado em algum jogo, vicia-te neste e torna-te num dos feiticeiros da idade da tecnologia."
why_ending: "E vejam só, é gratuito. "
why_ending_url: "Comece a enfeitiçar agora!"
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: "Programador extraordinário, arquiteto de software, feiticeiro da cozinha e mestre das finanças. O Scott é sensato."
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: "Mago do suporte ao cliente, testador do uso e organizador da comunidade; provavelmente já falou com o Jeremy."
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: "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:
page_title: "Legal"
@ -834,11 +834,11 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
rank_failed: "Falhou a Classificar"
rank_being_ranked: "Jogo a ser Classificado"
# rank_last_submitted: "submitted "
# help_simulate: "Help simulate games?"
code_being_simulated: "O teu código está a ser simulado por outros jogadores, para ser classificado. Isto será actualizado quando surgirem novas partidas."
help_simulate: "Ajudar a simular jogos?"
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_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!"
tutorial_play: "Jogar Tutorial"
tutorial_recommended: "Recomendado se nunca jogou antes"
@ -848,40 +848,40 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
simple_ai: "Inteligência Artificial Simples"
warmup: "Aquecimento"
vs: "VS"
# friends_playing: "Friends Playing"
# log_in_for_friends: "Log in to play with your friends!"
# social_connect_blurb: "Connect and play against your friends!"
# invite_friends_to_battle: "Invite your friends to join you in battle!"
# fight: "Fight!"
# watch_victory: "Watch your victory"
# defeat_the: "Defeat the"
# tournament_ends: "Tournament ends"
# tournament_ended: "Tournament ended"
# tournament_rules: "Tournament Rules"
# 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_blog: "on our blog"
friends_playing: "Amigos a Jogar"
log_in_for_friends: "Inicie sessão para jogar com os seus amigos!"
social_connect_blurb: "Conecte-se e jogue contra os seus amigos!"
invite_friends_to_battle: "Convide os seus amigos para se juntarem a si em batalha!"
fight: "Lutar!"
watch_victory: "Veja a sua vitória"
defeat_the: "Derrote o"
tournament_ends: "O Torneio acaba"
tournament_ended: "O Torneio acabou"
tournament_rules: "Regras do Torneio"
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: "no nosso blog"
rules: "Regras"
winners: "Vencedores"
# ladder_prizes:
# title: "Tournament Prizes"
# blurb_1: "These prizes will be awarded according to"
# blurb_2: "the tournament rules"
# blurb_3: "to the top human and ogre players."
# blurb_4: "Two teams means double the prizes!"
# blurb_5: "(There will be two first place winners, two second-place winners, etc.)"
# rank: "Rank"
# prizes: "Prizes"
# total_value: "Total Value"
# in_cash: "in cash"
# custom_wizard: "Custom CodeCombat Wizard"
# custom_avatar: "Custom CodeCombat avatar"
# heap: "for six months of \"Startup\" access"
# credits: "credits"
# one_month_coupon: "coupon: choose either Rails or HTML"
# one_month_discount: "discount, 30% off: choose either Rails or HTML"
# license: "license"
# oreilly: "ebook of your choice"
ladder_prizes:
title: "Prémios do Torneio"
blurb_1: "Estes prémios serão entregues de acordo com"
blurb_2: "as regras do torneio"
blurb_3: "aos melhores jogadores humanos e ogres."
blurb_4: "Duas equipas significam o dobro dos prémios!"
blurb_5: "(Haverá dois vencedores em primeiro lugar, dois em segundo, etc.)"
rank: "Classificação"
prizes: "Prémios"
total_value: "Valor Total"
in_cash: "em dinheiro"
custom_wizard: "Um Feiticeiro do CodeCombat Personalizado"
custom_avatar: "Um Avatar do CodeCombat Personalizado"
heap: "para seis meses de acesso \"Startup\""
credits: "créditos"
one_month_coupon: "cupão: escolha Rails ou HTML"
one_month_discount: "desconto de 30%: escolha Rails ou HTML"
license: "licença"
oreilly: "ebook à sua escolha"
loading_error:
could_not_load: "Erro ao carregar do servidor"
@ -909,8 +909,8 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
# user_schema: "User Schema"
# user_profile: "User Profile"
# patches: "Patches"
# patched_model: "Source Document"
# model: "Model"
patched_model: "Documento Fonte"
model: "Modelo"
system: "Sistema"
component: "Componente"
components: "Componentes"
@ -921,20 +921,20 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
article: "Artigo"
# user_names: "User Names"
# thang_names: "Thang Names"
# files: "Files"
files: "Ficheiros"
# top_simulators: "Top Simulators"
# source_document: "Source Document"
source_document: "Documento Fonte"
document: "Documento"
# sprite_sheet: "Sprite Sheet"
# candidate_sessions: "Candidate Sessions"
# user_remark: "User Remark"
versions: "Versões"
# delta:
# added: "Added"
# modified: "Modified"
# deleted: "Deleted"
# moved_index: "Moved Index"
# text_diff: "Text Diff"
# merge_conflict_with: "MERGE CONFLICT WITH"
# no_changes: "No Changes"
delta:
added: "Adicionados/as"
modified: "Modificados/as"
deleted: "Eliminados/as"
moved_index: "Índice Movido"
text_diff: "Diferença de Texto"
merge_conflict_with: "FUNDIR CONFLITO COM"
no_changes: "Sem Alterações"

View file

@ -1,5 +1,5 @@
CocoModel = require './CocoModel'
util = require '../lib/utils'
utils = require '../lib/utils'
module.exports = class Achievement extends CocoModel
@className: 'Achievement'
@ -11,6 +11,47 @@ module.exports = class Achievement extends CocoModel
# TODO logic is duplicated in Mongoose Achievement schema
getExpFunction: ->
kind = @get('function')?.kind or @schema.function.default.kind
parameters = @get('function')?.parameters or @schema.function.default.parameters
kind = @get('function')?.kind or jsonschema.properties.function.default.kind
parameters = @get('function')?.parameters or jsonschema.properties.function.default.parameters
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'
deltasLib = require 'lib/deltas'
NewAchievementCollection = require '../collections/NewAchievementCollection'
class CocoModel extends Backbone.Model
idAttribute: '_id'
loaded: false
@ -301,11 +299,13 @@ class CocoModel extends Backbone.Model
return if _.isString @url then @url else @url()
@pollAchievements: ->
NewAchievementCollection = require '../collections/NewAchievementCollection' # Nasty mutual inclusion if put on top
achievements = new NewAchievementCollection
achievements.fetch(
achievements.fetch
success: (collection) ->
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

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) ->
return if c in sorted
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
if lc.name is 'Programmable'
# Programmable always comes last
@ -88,7 +88,7 @@ module.exports = class Level extends CocoModel
else
for d in lc.dependencies or []
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
if lc.name is 'Collides'
allied = _.find levelComponents, {name: 'Allied'}

View file

@ -36,3 +36,9 @@ module.exports = class LevelSession extends CocoModel
spell = item[1]
return true if c1[thang][spell] isnt c2[thang]?[spell]
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
else
@addCollection collection
@listenToOnce collection, 'sync', (c) ->
console.debug 'Registering collection', url
onCollectionSynced = (c) ->
if collection.url is c.url
console.debug 'Registering collection', url, 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.load() if not (res.isLoading or res.isLoaded)
return res

View file

@ -9,14 +9,24 @@ module.exports = class User extends CocoModel
urlRoot: '/db/user'
notyErrors: false
defaults:
points: 0
initialize: ->
super()
@migrateEmails()
onLoaded: ->
CocoModel.pollAchievements() # Check for achievements on login
super arguments...
isAdmin: ->
permissions = @attributes['permissions'] or []
return 'admin' in permissions
isAnonymous: ->
@get 'anonymous'
displayName: ->
@get('name') or 'Anoner'
@ -32,47 +42,13 @@ module.exports = class User extends CocoModel
return "/file/#{photoURL}#{prefix}s=#{size}"
return "/db/user/#{@id}/avatar?s=#{size}&employerPageAvatar=#{useEmployerPageAvatar}"
@getByID = (id, properties, force) ->
{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
getSlugOrID: -> @get('slug') or @get('_id')
set: ->
if arguments[0] is 'jobProfileApproved' and @get("jobProfileApproved") is false and not @get("jobProfileApprovedDate")
@set "jobProfileApprovedDate", (new Date()).toISOString()
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) ->
$.ajax "/auth/name/#{name}",
success: (data) -> done data.name
@ -111,19 +87,16 @@ module.exports = class User extends CocoModel
isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled
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) ->
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) ->
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: ->
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\.]*$':
oneOf: [
#{$ref: '#/definitions/' + MongoQueryOperatorSchema.id},
{type: 'string'},
{type: 'string'}
{type: 'object'}
{type: 'boolean'}
]
additionalProperties: true # TODO make Treema accept new pattern matched keys
definitions: {}
@ -34,36 +35,58 @@ MongoFindQuerySchema.definitions[MongoQueryOperatorSchema.id] = MongoQueryOperat
AchievementSchema = c.object()
c.extendNamedProperties AchievementSchema
c.extendBasicProperties AchievementSchema, 'article'
c.extendBasicProperties AchievementSchema, 'achievement'
c.extendSearchableProperties AchievementSchema
_.extend(AchievementSchema.properties,
_.extend AchievementSchema.properties,
query:
#type:'object'
$ref: '#/definitions/' + MongoFindQuerySchema.id
worth: {type: 'number'}
worth: c.float
default: 10
collection: {type: 'string'}
description: {type: 'string'}
userField: {type: 'string'}
description: c.shortString
default: 'Probably the coolest you\'ll ever get.'
userField: c.shortString()
related: c.objectId(description: 'Related entity')
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:
type: 'string'
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:
type: 'object'
description: 'Function that gives total experience for X amount achieved'
properties:
kind: {enum: ['linear', 'logarithmic'], default: 'linear'}
kind: {enum: ['linear', 'logarithmic', 'quadratic'], default: 'linear'}
parameters:
type: 'object'
properties:
a: {type: 'number', default: 1}
b: {type: 'number', default: 1}
c: {type: 'number', default: 1}
additionalProperties: true
default: {kind: 'linear', parameters: a: 1}
required: ['kind', 'parameters']
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[MongoFindQuerySchema.id] = MongoFindQuerySchema

View file

@ -101,6 +101,14 @@ _.extend LevelSessionSchema.properties,
type: 'object'
source:
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:
type: 'object'

View file

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

View file

@ -222,6 +222,33 @@ _.extend UserSchema.properties,
points: {type: 'number'}
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'

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
me.objectId = (ext) -> schema = combine({type: ['object', 'string']}, 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']},
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
min-height: 600px
padding: 14px 12px 5px 12px
@include box-sizing(border-box)
+box-sizing(border-box)
+clearfix()
#outer-content-wrapper
background: #B4B4B4
@ -291,3 +292,9 @@ body[lang='ja']
a[data-toggle="coco-modal"]
cursor: pointer
.achievement-corner
position: fixed
bottom: 0px
right: 0px
z-index: 1001

View file

@ -1,4 +1,44 @@
@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
a.navbar-brand
@ -19,11 +59,65 @@
.account-settings-image
width: 18px
height: 18px
margin-right: 5px
.glyphicon-user
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-size: 25px
letter-spacing: 2px
@ -31,7 +125,7 @@
&:hover
color: #f8e413
.navbar-link-text a:hover
.navbar-link-text > li > a:hover
background: darken($body-bg, 3%)
.btn, .btn-group, .fancy-select
@ -67,9 +161,6 @@
top: 13px
max-width: 140px
.nav
margin-bottom: 0
div.fancy-select
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25)
div.trigger

View file

@ -1,4 +1,6 @@
#editor-achievement-edit-view
height: 100%
.treema-root
margin: 28px 0px 20px
@ -10,3 +12,9 @@
textarea
width: 92%
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
border-radius: 4px
.play-with-level-input
margin: 5px
#spritesheets

View file

@ -1,7 +1,7 @@
@import "app/styles/bootstrap/variables"
#options-view
.select-group
.select-group, .slider-group
display: block
min-height: 20px
margin-top: 10px
@ -14,6 +14,9 @@
margin-right: 20px
margin-bottom: 0
.slider
width: 200px
.form-group.radio-inline
input
margin-left: 0px
@ -22,6 +25,7 @@
.radio-inline-parent-label
padding-left: 0
#player-avatar-container
position: relative
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
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
li.play
a.header-font(href='/play', data-i18n="nav.play") Levels
li
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
#outer-content-wrapper(class=showBackground ? 'show-background' : '')
@ -55,6 +73,7 @@ body
.main-content-area
block content
p If this is showing, you dun goofed
.achievement-corner
block footer
.footer.clearfix

View file

@ -2,7 +2,6 @@ extends /templates/base
block content
if me.isAdmin()
div
ol.breadcrumb
li
a(href="/editor", data-i18n="editor.main_title") CodeCombat Editors
@ -12,6 +11,7 @@ block content
| #{achievement.attributes.name}
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
h3(data-i18n="achievement.edit_achievement_title") Edit Achievement
@ -20,12 +20,10 @@ block content
#achievement-treema
#achievement-view
#achievement-view.clearfix
hr
div#error-view
else
.alert.alert-danger
span Admin only. Turn around.

View file

@ -4,14 +4,14 @@ block modal-header-content
h3(data-i18n="editor.fork_title") Fork New Version
block modal-body-content
form#save-level-form.form
form.form
.form-group
label(for="level-name", data-i18n="general.name") Name
input#level-name(name="name", type="text").form-control
label(for="model-name", data-i18n="general.name") Name
input#fork-model-name(name="name", type="text").form-control
block modal-footer-content
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
h3(data-i18n="editor.fork_creating") Creating Fork...

View file

@ -33,6 +33,8 @@ block header
- var patches = level.get('patches')
if patches && patches.length
span.badge= patches.length
li
a(href="#related-achievements-view", data-toggle="tab") Achievements
li
a(href="#docs-components-view", data-toggle="tab", data-i18n="editor.level_tab_docs") Documentation
.navbar-header
@ -83,7 +85,7 @@ block header
span.spl(data-i18n="common.unwatch") Unwatch
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": "")
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
li(class=anonymous ? "disabled": "")
@ -121,6 +123,8 @@ block outer_content
div.tab-pane#editor-level-patches
.patches-view
div.tab-pane#related-achievements-view
div.tab-pane#docs-components-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}
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
li#save-button
a
@ -42,6 +52,8 @@ block header
span.glyphicon-chevron-down.glyphicon
ul.dropdown-menu
li.dropdown-header Actions
li(class=anonymous ? "disabled": "")
a(data-i18n="common.fork")#fork-start-button Fork
li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
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?
if showDevBits
p
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")
div(data-i18n="choose_hero.temp") Temp

View file

@ -8,6 +8,13 @@
.form
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
label(for="option-music")
input#option-music(name="option-music", type="checkbox", checked=music)

View file

@ -12,32 +12,12 @@ block content
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
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}")
hr
div.results
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
.alert.alert-danger
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'
template = require 'templates/demo'
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'
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'
template = require 'templates/editor/achievement/edit'
Achievement = require 'models/Achievement'
AchievementPopup = require 'views/achievements/AchievementPopup'
ConfirmModal = require 'views/modal/ConfirmModal'
errors = require 'lib/errors'
app = require 'application'
module.exports = class AchievementEditView extends RootView
id: 'editor-achievement-edit-view'
@ -11,6 +14,7 @@ module.exports = class AchievementEditView extends RootView
events:
'click #save-button': 'saveAchievement'
'click #recalculate-button': 'confirmRecalculation'
'click #delete-button': 'confirmDeletion'
subscriptions:
'save-new': 'saveAchievement'
@ -20,13 +24,10 @@ module.exports = class AchievementEditView extends RootView
@achievement = new Achievement(_id: @achievementID)
@achievement.saveBackups = true
@listenToOnce(@achievement, 'error',
() =>
@achievement.once 'error', (achievement, jqxhr) =>
@hideLoading()
$(@$el).find('.main-content-area').children('*').not('#error-view').remove()
@insertSubView(new ErrorView())
)
$(@$el).find('.main-content-area').children('*').not('.breadcrumb').remove()
errors.backboneFailure arguments...
@achievement.fetch()
@listenToOnce(@achievement, 'sync', @buildTreema)
@ -49,17 +50,31 @@ module.exports = class AchievementEditView extends RootView
@treema.build()
pushChangesToPreview: =>
'TODO' # TODO might want some intrinsic preview thing
getRenderData: (context={}) ->
context = super(context)
context.achievement = @achievement
context.authorized = me.isAdmin()
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: ->
'Maybe later' # TODO
'Maybe later' # TODO patch patch patch
saveAchievement: (e) ->
@treema.endExistingEdits()
@ -75,20 +90,31 @@ module.exports = class AchievementEditView extends RootView
url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}"
document.location.href = url
confirmRecalculation: (e) ->
confirmRecalculation: ->
renderData =
'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?'
'confirmDecline': 'Not really'
'confirmConfirm': 'Definitely'
confirmModal = new ConfirmModal(renderData)
confirmModal.onConfirm @recalculateAchievement
confirmModal = new ConfirmModal renderData
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
recalculateAchievement: =>
$.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) ->
noty
timeout: 5000
@ -105,3 +131,24 @@ module.exports = class AchievementEditView extends RootView
url: '/admin/earned.achievement/recalculate'
type: 'POST'
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'
SystemsTabView = require './systems/SystemsTabView'
SaveLevelModal = require './modals/SaveLevelModal'
LevelForkView = require './modals/ForkLevelModal'
ForkModal = require 'views/editor/ForkModal'
SaveVersionModal = require 'views/modal/SaveVersionModal'
PatchesView = require 'views/editor/PatchesView'
RelatedAchievementsView = require 'views/editor/level/RelatedAchievementsView'
VersionHistoryView = require './modals/LevelVersionsModal'
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-parent': 'onPlayLevelTeamSelect'
'click #commit-level-start-button': 'startCommittingLevel'
'click #fork-level-start-button': 'startForkingLevel'
'click #fork-start-button': 'startForking'
'click #level-history-button': 'showVersionHistory'
'click #undo-button': 'onUndo'
'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 ComponentsTabView 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
@showReadOnly() if me.get('anonymous')
@ -128,9 +130,8 @@ module.exports = class LevelEditView extends RootView
@openModalView new SaveLevelModal level: @level, supermodel: @supermodel
Backbone.Mediator.publish 'level:view-switched', e
startForkingLevel: (e) ->
levelForkView = new LevelForkView level: @level
@openModalView levelForkView
startForking: (e) ->
@openModalView new ForkModal model: @level, editorPath: 'level'
Backbone.Mediator.publish 'level:view-switched', 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'
ThangTypeColorsTabView = require './ThangTypeColorsTabView'
PatchesView = require 'views/editor/PatchesView'
ForkModal = require 'views/editor/ForkModal'
SaveVersionModal = require 'views/modal/SaveVersionModal'
template = require 'templates/editor/thang/thang-type-edit-view'
storage = require 'lib/storage'
CENTER = {x: 200, y: 300}
@ -35,8 +37,12 @@ module.exports = class ThangTypeEditView extends RootView
'click #marker-button': 'toggleDots'
'click #end-button': 'endAnimation'
'click #history-button': 'showVersionHistory'
'click #fork-start-button': 'startForking'
'click #save-button': 'openSaveModal'
'click #patches-tab': -> @patchesView.load()
'click .play-with-level-button': 'onPlayLevel'
'click .play-with-level-parent': 'onPlayLevelSelect'
'keyup .play-with-level-input': 'onPlayLevelKeyUp'
subscriptions:
'save-new-version': 'saveNewThangType'
@ -58,6 +64,7 @@ module.exports = class ThangTypeEditView extends RootView
context.thangType = @thangType
context.animations = @getAnimationNames()
context.authorized = not me.get('anonymous')
context.recentlyPlayedLevels = storage.load('recently-played-levels') ? ['items']
context
getAnimationNames: ->
@ -401,12 +408,46 @@ module.exports = class ThangTypeEditView extends RootView
@showingSelectedNode = false
showVersionHistory: (e) ->
versionHistoryModal = new ThangTypeVersionsModal thangType: @thangType, @thangTypeID
@openModalView versionHistoryModal
Backbone.Mediator.publish 'level:view-switched', e
@openModalView new ThangTypeVersionsModal thangType: @thangType, @thangTypeID
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: ->
@camera?.destroy()

View file

@ -48,6 +48,20 @@ module.exports = class OptionsView extends CocoView
afterRender: ->
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: ->
if @playerName and @playerName isnt me.get('name')

View file

@ -6,8 +6,9 @@ CocoView = require './CocoView'
{logoutUser, me} = require('lib/auth')
locale = require 'locale/locale'
Achievement = require '../../models/Achievement'
User = require '../../models/User'
AchievementPopup = require 'views/achievements/AchievementPopup'
utils = require 'lib/utils'
# TODO remove
filterKeyboardEvents = (allowedEvents, func) ->
@ -32,61 +33,13 @@ module.exports = class RootView extends CocoView
'achievements:new': 'handleNewAchievements'
showNewAchievement: (achievement, earnedAchievement) ->
currentLevel = me.level()
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 )
popup = new AchievementPopup achievement: achievement, earnedAchievement: earnedAchievement
handleNewAchievements: (earnedAchievements) ->
_.each(earnedAchievements.models, (earnedAchievement) =>
_.each earnedAchievements.models, (earnedAchievement) =>
achievement = new Achievement(_id: earnedAchievement.get('achievement'))
console.log achievement
achievement.fetch(
achievement.fetch
success: (achievement) => @showNewAchievement(achievement, earnedAchievement)
)
)
logoutAccount: ->
logoutUser($('#login-email').val())

View file

@ -1,6 +1,6 @@
RootView = require 'views/kinds/RootView'
NewModelModal = require 'views/modal/NewModelModal'
template = require 'templates/kinds/search'
forms = require 'lib/forms'
app = require 'application'
class SearchCollection extends Backbone.Collection
@ -26,9 +26,7 @@ module.exports = class SearchView extends RootView
events:
'change input#search': 'runSearch'
'keydown input#search': 'runSearch'
'click button.new-model-submit': 'makeNewModel'
'submit form': 'makeNewModel'
'shown.bs.modal #new-model-modal': 'focusOnName'
'click #new-model-button': 'newModel'
'hidden.bs.modal #new-model-modal': 'onModalHidden'
constructor: (options) ->
@ -79,31 +77,11 @@ module.exports = class SearchView extends RootView
@collection.off()
@collection = null
makeNewModel: (e) ->
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
onNewModelSaved: (@model) ->
base = document.location.pathname[1..] + '/'
app.router.navigate(base + (@model.get('slug') or @model.id), {trigger: true})
focusOnName: ->
@$el.find('#name').focus()
newModel: (e) ->
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
events:
'click #decline-button': 'doDecline'
'click #confirm-button': 'doConfirm'
'click #decline-button': 'onDecline'
'click #confirm-button': 'onConfirm'
constructor: (@renderData={}, options={}) ->
super(options)
@ -21,10 +21,6 @@ module.exports = class ConfirmModal extends ModalView
setRenderData: (@renderData) ->
onDecline: (@decline) ->
onDecline: -> @trigger 'decline'
onConfirm: (@confirm) ->
doConfirm: -> @confirm() if @confirm
doDecline: -> @decline() if @decline
onConfirm: -> @trigger 'confirm'

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 = [
{
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'
}
classicAlgorithms = [
{
name: 'Bubble Sort Bootcamp Battle'
difficulty: 3
@ -257,6 +229,37 @@ module.exports = class MainPlayView extends RootView
image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
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'
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: '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: '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}
]
context.levelStatusMap = @levelStatusMap

View file

@ -55,6 +55,7 @@ module.exports = class PlayLevelView extends RootView
'god:new-world-created': 'onNewWorld'
'god:infinite-loop': 'onInfiniteLoop'
'level-reload-from-data': 'onLevelReloadFromData'
'level-reload-thang-type': 'onLevelReloadThangType'
'play-next-level': 'onPlayNextLevel'
'edit-wizard-settings': 'showWizardSettingsModal'
'surface:world-set-up': 'onSurfaceSetUpNewWorld'
@ -326,6 +327,15 @@ module.exports = class PlayLevelView extends RootView
@scriptManager.setScripts(e.level.get('scripts'))
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...) ->
$('#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'
User = require 'models/User'
LevelSession = require 'models/LevelSession'
@ -26,7 +26,7 @@ adminContacts = [
{id: '52a57252a89409700d0000d9', name: 'Ignore'}
]
module.exports = class JobProfileView extends RootView
module.exports = class JobProfileView extends UserView
id: 'profile-view'
template: template
showBackground: false
@ -54,8 +54,7 @@ module.exports = class JobProfileView extends RootView
'change #admin-contact': 'onAdminContactChanged'
'click .session-link': 'onSessionLinkPressed'
constructor: (options, @userID) ->
@userID ?= me.id
constructor: (userID, options) ->
@onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000
@onRemarkChanged = _.debounce @onRemarkChanged, 1000
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
@ -64,32 +63,19 @@ module.exports = class JobProfileView extends RootView
window.contractCallback = =>
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
@render()
super options
if me.get('anonymous') is true
@render()
return
if User.isObjectID @userID
@finishInit()
else
$.ajax "/db/user/#{@userID}/nameToID", success: (@userID) =>
super options, userID
onUserLoaded: ->
@finishInit() unless @destroyed
@render()
super()
finishInit: ->
return unless @userID
@uploadFilePath = "db/user/#{@userID}"
@highlightedContainers = []
if @userID is me.id
@user = me
else if me.isAdmin() or 'employer' in me.get('permissions')
@user = User.getByID(@userID)
@user.fetch()
@listenTo @user, 'sync', =>
@render()
if me.isAdmin() or 'employer' in me.get('permissions')
$.post "/db/user/#{me.id}/track/view_candidate"
$.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
if me.isAdmin()
# 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')
context.profile = jobProfile
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.allowedToEditJobProfile = @user and (me.isAdmin() or (context.myProfile && !me.get('anonymous')))
context.profileApproved = @user?.get 'jobProfileApproved'
@ -289,7 +275,7 @@ module.exports = class JobProfileView extends RootView
_.delay ->
justSavedSection.removeClass 'just-saved', duration: 1500, easing: 'easeOutQuad'
, 500
if me.isAdmin()
if me.isAdmin() and @user
visibleSettings = ['history', 'tasks']
data = _.pick (@remark.attributes), (value, key) -> key in visibleSettings
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",
"marked": "~0.3.0",
"moment": "~2.5.0",
"aether": "~0.2.22",
"aether": "~0.2.28",
"underscore.string": "~2.3.3",
"firebase": "~1.0.2",
"catiline": "~2.9.3",
@ -40,7 +40,7 @@
"jsondiffpatch": "~0.1.5",
"nanoscroller": "~0.8.0",
"jquery.tablesorter": "~2.15.13",
"treema": "~0.0.12",
"treema": "~0.0.14",
"bootstrap": "~3.1.1",
"validated-backbone-mediator": "~0.1.3",
"jquery.browser": "~0.0.6",

View file

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