Merge branch 'master' into production
|
@ -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'
|
|
||||||
});
|
|
|
@ -18,9 +18,10 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
|
|
||||||
'about': go('AboutView')
|
'about': go('AboutView')
|
||||||
|
|
||||||
'account/profile(/:userID)': go('account/JobProfileView')
|
'account': go('account/MainAccountView')
|
||||||
'account/settings': go('account/AccountSettingsView')
|
'account/settings': go('account/AccountSettingsView')
|
||||||
'account/unsubscribe': go('account/UnsubscribeView')
|
'account/unsubscribe': go('account/UnsubscribeView')
|
||||||
|
#'account/payment'
|
||||||
|
|
||||||
'admin': go('admin/MainAdminView')
|
'admin': go('admin/MainAdminView')
|
||||||
'admin/candidates': go('admin/CandidatesView')
|
'admin/candidates': go('admin/CandidatesView')
|
||||||
|
@ -50,7 +51,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'editor': go('editor/MainEditorView')
|
'editor': go('editor/MainEditorView')
|
||||||
|
|
||||||
'editor/achievement': go('editor/achievement/AchievementSearchView')
|
'editor/achievement': go('editor/achievement/AchievementSearchView')
|
||||||
'editor/achievement': go('editor/achievement/AchievementEditView')
|
'editor/achievement/:articleID': go('editor/achievement/AchievementEditView')
|
||||||
'editor/article': go('editor/article/ArticleSearchView')
|
'editor/article': go('editor/article/ArticleSearchView')
|
||||||
'editor/article/preview': go('editor/article/ArticlePreviewView')
|
'editor/article/preview': go('editor/article/ArticlePreviewView')
|
||||||
'editor/article/:articleID': go('editor/article/ArticleEditView')
|
'editor/article/:articleID': go('editor/article/ArticleEditView')
|
||||||
|
@ -79,6 +80,11 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
|
|
||||||
'test(/*subpath)': go('TestView')
|
'test(/*subpath)': go('TestView')
|
||||||
|
|
||||||
|
'user/:slugOrID': go('user/MainUserView')
|
||||||
|
'user/:slugOrID/stats': go('user/AchievementsView')
|
||||||
|
'user/:slugOrID/profile': go('user/JobProfileView')
|
||||||
|
#'user/:slugOrID/code': go('user/CodeView')
|
||||||
|
|
||||||
'*name': 'showNotFoundView'
|
'*name': 'showNotFoundView'
|
||||||
|
|
||||||
routeToServer: (e) ->
|
routeToServer: (e) ->
|
||||||
|
|
|
@ -5,7 +5,6 @@ locale = require 'locale/locale'
|
||||||
{me} = require 'lib/auth'
|
{me} = require 'lib/auth'
|
||||||
Tracker = require 'lib/Tracker'
|
Tracker = require 'lib/Tracker'
|
||||||
CocoView = require 'views/kinds/CocoView'
|
CocoView = require 'views/kinds/CocoView'
|
||||||
AchievementNotify = require '../../templates/achievement_notify'
|
|
||||||
|
|
||||||
marked.setOptions {gfm: true, sanitize: true, smartLists: true, breaks: false}
|
marked.setOptions {gfm: true, sanitize: true, smartLists: true, breaks: false}
|
||||||
|
|
||||||
|
@ -40,7 +39,6 @@ Application = initialize: ->
|
||||||
@facebookHandler = new FacebookHandler()
|
@facebookHandler = new FacebookHandler()
|
||||||
@gplusHandler = new GPlusHandler()
|
@gplusHandler = new GPlusHandler()
|
||||||
$(document).bind 'keydown', preventBackspace
|
$(document).bind 'keydown', preventBackspace
|
||||||
$.notify.addStyle 'achievement', html: $(AchievementNotify())
|
|
||||||
@linkedinHandler = new LinkedInHandler()
|
@linkedinHandler = new LinkedInHandler()
|
||||||
preload(COMMON_FILES)
|
preload(COMMON_FILES)
|
||||||
$.i18n.init {
|
$.i18n.init {
|
||||||
|
|
6
app/collections/AchievementCollection.coffee
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
|
Achievement = require 'models/Achievement'
|
||||||
|
|
||||||
|
module.exports = class AchievementCollection extends CocoCollection
|
||||||
|
url: '/db/achievement'
|
||||||
|
model: Achievement
|
9
app/collections/EarnedAchievementCollection.coffee
Normal 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()
|
|
@ -1,6 +1,9 @@
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
|
Achievement = require 'models/Achievement'
|
||||||
|
|
||||||
class NewAchievementCollection extends CocoCollection
|
class NewAchievementCollection extends CocoCollection
|
||||||
|
model: Achievement
|
||||||
|
|
||||||
initialize: (me = require('lib/auth').me) ->
|
initialize: (me = require('lib/auth').me) ->
|
||||||
@url = "/db/user/#{me.id}/achievements?notified=false"
|
@url = "/db/user/#{me.id}/achievements?notified=false"
|
||||||
|
|
||||||
|
|
9
app/collections/RecentlyPlayedCollection.coffee
Normal 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
|
10
app/collections/RelatedAchievementsCollection.coffee
Normal 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
|
|
@ -1,6 +1,7 @@
|
||||||
Bus = require './Bus'
|
Bus = require './Bus'
|
||||||
{me} = require 'lib/auth'
|
{me} = require 'lib/auth'
|
||||||
LevelSession = require 'models/LevelSession'
|
LevelSession = require 'models/LevelSession'
|
||||||
|
utils = require 'lib/utils'
|
||||||
|
|
||||||
module.exports = class LevelBus extends Bus
|
module.exports = class LevelBus extends Bus
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ module.exports = class LevelBus extends Bus
|
||||||
'tome:spell-changed': 'onSpellChanged'
|
'tome:spell-changed': 'onSpellChanged'
|
||||||
'tome:spell-created': 'onSpellCreated'
|
'tome:spell-created': 'onSpellCreated'
|
||||||
'application:idle-changed': 'onIdleChanged'
|
'application:idle-changed': 'onIdleChanged'
|
||||||
|
'goal-manager:new-goal-states': 'onNewGoalStates'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super(arguments...)
|
super(arguments...)
|
||||||
|
@ -192,6 +194,14 @@ module.exports = class LevelBus extends Bus
|
||||||
@changedSessionProperties.state = true
|
@changedSessionProperties.state = true
|
||||||
@saveSession()
|
@saveSession()
|
||||||
|
|
||||||
|
onNewGoalStates: ({goalStates})->
|
||||||
|
state = @session.get 'state'
|
||||||
|
unless utils.kindaEqual state.goalStates, goalStates # Only save when goals really change
|
||||||
|
state.goalStates = goalStates
|
||||||
|
@session.set 'state', state
|
||||||
|
@changedSessionProperties.state = true
|
||||||
|
@saveSession()
|
||||||
|
|
||||||
onPlayerJoined: (snapshot) =>
|
onPlayerJoined: (snapshot) =>
|
||||||
super(arguments...)
|
super(arguments...)
|
||||||
return unless @onPoint()
|
return unless @onPoint()
|
||||||
|
|
|
@ -24,6 +24,7 @@ doQuerySelector = (value, operatorObj) ->
|
||||||
|
|
||||||
matchesQuery = (target, queryObj) ->
|
matchesQuery = (target, queryObj) ->
|
||||||
return true unless queryObj
|
return true unless queryObj
|
||||||
|
throw new Error 'Expected an object to match a query against, instead got null' unless target
|
||||||
for prop, query of queryObj
|
for prop, query of queryObj
|
||||||
if prop[0] == '$'
|
if prop[0] == '$'
|
||||||
switch prop
|
switch prop
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
CocoClass = require 'lib/CocoClass'
|
CocoClass = require './CocoClass'
|
||||||
|
|
||||||
namesCache = {}
|
namesCache = {}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
SystemNameLoader = require 'lib/SystemNameLoader'
|
SystemNameLoader = require './SystemNameLoader'
|
||||||
###
|
###
|
||||||
Good-to-knows:
|
Good-to-knows:
|
||||||
dataPath: an array of keys that walks you up a JSON object that's being patched
|
dataPath: an array of keys that walks you up a JSON object that's being patched
|
||||||
|
@ -12,7 +12,7 @@ module.exports.expandDelta = (delta, left, schema) ->
|
||||||
(expandFlattenedDelta(fd, left, schema) for fd in flattenedDeltas)
|
(expandFlattenedDelta(fd, left, schema) for fd in flattenedDeltas)
|
||||||
|
|
||||||
|
|
||||||
flattenDelta = (delta, dataPath=null, deltaPath=null) ->
|
module.exports.flattenDelta = flattenDelta = (delta, dataPath=null, deltaPath=null) ->
|
||||||
# takes a single jsondiffpatch delta and returns an array of objects with
|
# takes a single jsondiffpatch delta and returns an array of objects with
|
||||||
return [] unless delta
|
return [] unless delta
|
||||||
dataPath ?= []
|
dataPath ?= []
|
||||||
|
@ -175,3 +175,5 @@ prunePath = (delta, path) ->
|
||||||
prunePath delta[path[0]], path.slice(1) unless delta[path[0]] is undefined
|
prunePath delta[path[0]], path.slice(1) unless delta[path[0]] is undefined
|
||||||
keys = (k for k in _.keys(delta[path[0]]) when k isnt '_t')
|
keys = (k for k in _.keys(delta[path[0]]) when k isnt '_t')
|
||||||
delete delta[path[0]] if keys.length is 0
|
delete delta[path[0]] if keys.length is 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ module.exports.i18n = (say, target, language=me.lang(), fallback='en') ->
|
||||||
null
|
null
|
||||||
|
|
||||||
module.exports.getByPath = (target, path) ->
|
module.exports.getByPath = (target, path) ->
|
||||||
|
throw new Error 'Expected an object to match a query against, instead got null' unless target
|
||||||
pieces = path.split('.')
|
pieces = path.split('.')
|
||||||
obj = target
|
obj = target
|
||||||
for piece in pieces
|
for piece in pieces
|
||||||
|
@ -82,7 +83,7 @@ module.exports.isID = (id) -> _.isString(id) and id.length is 24 and id.match(/[
|
||||||
module.exports.round = _.curry (digits, n) ->
|
module.exports.round = _.curry (digits, n) ->
|
||||||
n = +n.toFixed(digits)
|
n = +n.toFixed(digits)
|
||||||
|
|
||||||
positify = (func) -> (x) -> if x > 0 then func(x) else 0
|
positify = (func) -> (params) -> (x) -> if x > 0 then func(params)(x) else 0
|
||||||
|
|
||||||
# f(x) = ax + b
|
# f(x) = ax + b
|
||||||
createLinearFunc = (params) ->
|
createLinearFunc = (params) ->
|
||||||
|
@ -100,3 +101,32 @@ module.exports.functionCreators =
|
||||||
linear: positify(createLinearFunc)
|
linear: positify(createLinearFunc)
|
||||||
quadratic: positify(createQuadraticFunc)
|
quadratic: positify(createQuadraticFunc)
|
||||||
logarithmic: positify(createLogFunc)
|
logarithmic: positify(createLogFunc)
|
||||||
|
|
||||||
|
# Call done with true to satisfy the 'until' goal and stop repeating func
|
||||||
|
module.exports.keepDoingUntil = (func, wait=100, totalWait=5000) ->
|
||||||
|
waitSoFar = 0
|
||||||
|
(done = (success) ->
|
||||||
|
if (waitSoFar += wait) <= totalWait and not success
|
||||||
|
_.delay (-> func done), wait) false
|
||||||
|
|
||||||
|
module.exports.grayscale = (imageData) ->
|
||||||
|
d = imageData.data
|
||||||
|
for i in [0..d.length] by 4
|
||||||
|
r = d[i]
|
||||||
|
g = d[i+1]
|
||||||
|
b = d[i+2]
|
||||||
|
v = 0.2126*r + 0.7152*g + 0.0722*b
|
||||||
|
d[i] = d[i+1] = d[i+2] = v
|
||||||
|
imageData
|
||||||
|
|
||||||
|
# Deep compares l with r, with the exception that undefined values are considered equal to missing values
|
||||||
|
# Very practical for comparing Mongoose documents where undefined is not allowed, instead fields get deleted
|
||||||
|
module.exports.kindaEqual = compare = (l, r) ->
|
||||||
|
if _.isObject(l) and _.isObject(r)
|
||||||
|
for key in _.union Object.keys(l), Object.keys(r)
|
||||||
|
return false unless compare l[key], r[key]
|
||||||
|
return true
|
||||||
|
else if l is r
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
|
|
@ -49,6 +49,9 @@
|
||||||
blog: "Blog"
|
blog: "Blog"
|
||||||
forum: "Forum"
|
forum: "Forum"
|
||||||
account: "Account"
|
account: "Account"
|
||||||
|
profile: "Profile"
|
||||||
|
stats: "Stats"
|
||||||
|
code: "Code"
|
||||||
admin: "Admin"
|
admin: "Admin"
|
||||||
home: "Home"
|
home: "Home"
|
||||||
contribute: "Contribute"
|
contribute: "Contribute"
|
||||||
|
@ -176,12 +179,14 @@
|
||||||
new_password: "New Password"
|
new_password: "New Password"
|
||||||
new_password_verify: "Verify"
|
new_password_verify: "Verify"
|
||||||
email_subscriptions: "Email Subscriptions"
|
email_subscriptions: "Email Subscriptions"
|
||||||
|
email_subscriptions_none: "No Email Subscriptions."
|
||||||
email_announcements: "Announcements"
|
email_announcements: "Announcements"
|
||||||
email_announcements_description: "Get emails on the latest news and developments at CodeCombat."
|
email_announcements_description: "Get emails on the latest news and developments at CodeCombat."
|
||||||
email_notifications: "Notifications"
|
email_notifications: "Notifications"
|
||||||
email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
|
email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
|
||||||
email_any_notes: "Any Notifications"
|
email_any_notes: "Any Notifications"
|
||||||
email_any_notes_description: "Disable to stop all activity notification emails."
|
email_any_notes_description: "Disable to stop all activity notification emails."
|
||||||
|
email_news: "News"
|
||||||
email_recruit_notes: "Job Opportunities"
|
email_recruit_notes: "Job Opportunities"
|
||||||
email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
|
email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
|
||||||
contributor_emails: "Contributor Class Emails"
|
contributor_emails: "Contributor Class Emails"
|
||||||
|
@ -591,6 +596,10 @@
|
||||||
level_search_title: "Search Levels Here"
|
level_search_title: "Search Levels Here"
|
||||||
achievement_search_title: "Search Achievements"
|
achievement_search_title: "Search Achievements"
|
||||||
read_only_warning2: "Note: you can't save any edits here, because you're not logged in."
|
read_only_warning2: "Note: you can't save any edits here, because you're not logged in."
|
||||||
|
no_achievements: "No achievements have been added for this level yet."
|
||||||
|
achievement_query_misc: "Key achievement off of miscellanea"
|
||||||
|
achievement_query_goals: "Key achievement off of level goals"
|
||||||
|
level_completion: "Level Completion"
|
||||||
|
|
||||||
article:
|
article:
|
||||||
edit_btn_preview: "Preview"
|
edit_btn_preview: "Preview"
|
||||||
|
@ -599,6 +608,7 @@
|
||||||
general:
|
general:
|
||||||
and: "and"
|
and: "and"
|
||||||
name: "Name"
|
name: "Name"
|
||||||
|
date: "Date"
|
||||||
body: "Body"
|
body: "Body"
|
||||||
version: "Version"
|
version: "Version"
|
||||||
commit_msg: "Commit Message"
|
commit_msg: "Commit Message"
|
||||||
|
@ -938,3 +948,38 @@
|
||||||
text_diff: "Text Diff"
|
text_diff: "Text Diff"
|
||||||
merge_conflict_with: "MERGE CONFLICT WITH"
|
merge_conflict_with: "MERGE CONFLICT WITH"
|
||||||
no_changes: "No Changes"
|
no_changes: "No Changes"
|
||||||
|
|
||||||
|
user:
|
||||||
|
stats: "Stats"
|
||||||
|
singleplayer_title: "Singleplayer Levels"
|
||||||
|
multiplayer_title: "Multiplayer Levels"
|
||||||
|
achievements_title: "Achievements"
|
||||||
|
last_played: "Last Played"
|
||||||
|
status: "Status"
|
||||||
|
status_completed: "Completed"
|
||||||
|
status_unfinished: "Unfinished"
|
||||||
|
no_singleplayer: "No Singleplayer games played yet."
|
||||||
|
no_multiplayer: "No Multiplayer games played yet."
|
||||||
|
no_achievements: "No Achievements earned yet."
|
||||||
|
favorite_prefix: "Favorite language is "
|
||||||
|
favorite_postfix: "."
|
||||||
|
|
||||||
|
achievements:
|
||||||
|
last_earned: "Last Earned"
|
||||||
|
amount_achieved: "Amount"
|
||||||
|
achievement: "Achievement"
|
||||||
|
category_contributor: "Contributor"
|
||||||
|
category_miscellaneous: "Miscellaneous"
|
||||||
|
category_levels: "Levels"
|
||||||
|
category_undefined: "Uncategorized"
|
||||||
|
current_xp_prefix: ""
|
||||||
|
current_xp_postfix: " in total"
|
||||||
|
new_xp_prefix: ""
|
||||||
|
new_xp_postfix: " earned"
|
||||||
|
left_xp_prefix: ""
|
||||||
|
left_xp_infix: " until level "
|
||||||
|
left_xp_postfix: ""
|
||||||
|
|
||||||
|
account:
|
||||||
|
recently_played: "Recently Played"
|
||||||
|
no_recent_games: "No games played during the past two weeks."
|
||||||
|
|
|
@ -26,7 +26,7 @@ module.exports =
|
||||||
ar: require './ar' # العربية, Arabic
|
ar: require './ar' # العربية, Arabic
|
||||||
pt: require './pt' # português, Portuguese
|
pt: require './pt' # português, Portuguese
|
||||||
'pt-BR': require './pt-BR' # português do Brasil, Portuguese (Brazil)
|
'pt-BR': require './pt-BR' # português do Brasil, Portuguese (Brazil)
|
||||||
'pt-PT': require './pt-PT' # Português europeu, Portuguese (Portugal)
|
'pt-PT': require './pt-PT' # Português (Portugal), Portuguese (Portugal)
|
||||||
pl: require './pl' # język polski, Polish
|
pl: require './pl' # język polski, Polish
|
||||||
it: require './it' # italiano, Italian
|
it: require './it' # italiano, Italian
|
||||||
tr: require './tr' # Türkçe, Turkish
|
tr: require './tr' # Türkçe, Turkish
|
||||||
|
@ -58,3 +58,4 @@ module.exports =
|
||||||
hi: require './hi' # मानक हिन्दी, Hindi
|
hi: require './hi' # मानक हिन्दी, Hindi
|
||||||
ur: require './ur' # اُردُو, Urdu
|
ur: require './ur' # اُردُو, Urdu
|
||||||
ms: require './ms' # Bahasa Melayu, Bahasa Malaysia
|
ms: require './ms' # Bahasa Melayu, Bahasa Malaysia
|
||||||
|
ca: require './ca' # Català, Catalan
|
||||||
|
|
|
@ -213,10 +213,10 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
# active: "Looking for interview offers now"
|
# active: "Looking for interview offers now"
|
||||||
# inactive: "Not looking for offers right now"
|
# inactive: "Not looking for offers right now"
|
||||||
# complete: "complete"
|
# complete: "complete"
|
||||||
# next: "Next"
|
next: "Seguinte"
|
||||||
# next_city: "city?"
|
next_city: "cidade?"
|
||||||
# next_country: "pick your country."
|
# next_country: "pick your country."
|
||||||
# next_name: "name?"
|
next_name: "nome?"
|
||||||
# next_short_description: "write a short description."
|
# next_short_description: "write a short description."
|
||||||
# next_long_description: "describe your desired position."
|
# next_long_description: "describe your desired position."
|
||||||
# next_skills: "list at least five skills."
|
# next_skills: "list at least five skills."
|
||||||
|
@ -226,39 +226,39 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
# next_links: "add any personal or social links."
|
# next_links: "add any personal or social links."
|
||||||
# next_photo: "add an optional professional photo."
|
# next_photo: "add an optional professional photo."
|
||||||
# next_active: "mark yourself open to offers to show up in searches."
|
# next_active: "mark yourself open to offers to show up in searches."
|
||||||
# example_blog: "Blog"
|
example_blog: "Blog"
|
||||||
# example_personal_site: "Personal Site"
|
example_personal_site: "Sítio Pessoal"
|
||||||
# links_header: "Personal Links"
|
links_header: "Ligações Pessoais"
|
||||||
# links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog."
|
# links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog."
|
||||||
# links_name: "Link Name"
|
links_name: "Nome da Ligação"
|
||||||
# links_name_help: "What are you linking to?"
|
links_name_help: "A que é que está a ligar?"
|
||||||
# links_link_blurb: "Link URL"
|
links_link_blurb: "URL da Ligação"
|
||||||
# basics_header: "Update basic info"
|
# basics_header: "Update basic info"
|
||||||
# basics_active: "Open to Offers"
|
# basics_active: "Open to Offers"
|
||||||
# basics_active_help: "Want interview offers right now?"
|
# basics_active_help: "Want interview offers right now?"
|
||||||
# basics_job_title: "Desired Job Title"
|
# basics_job_title: "Desired Job Title"
|
||||||
# basics_job_title_help: "What role are you looking for?"
|
# basics_job_title_help: "What role are you looking for?"
|
||||||
# basics_city: "City"
|
basics_city: "Cidade"
|
||||||
# basics_city_help: "City you want to work in (or live in now)."
|
# basics_city_help: "City you want to work in (or live in now)."
|
||||||
# basics_country: "Country"
|
basics_country: "País"
|
||||||
# basics_country_help: "Country you want to work in (or live in now)."
|
# basics_country_help: "Country you want to work in (or live in now)."
|
||||||
# basics_visa: "US Work Status"
|
# basics_visa: "US Work Status"
|
||||||
# basics_visa_help: "Are you authorized to work in the US, or do you need visa sponsorship? (If you live in Canada or Australia, mark authorized.)"
|
# basics_visa_help: "Are you authorized to work in the US, or do you need visa sponsorship? (If you live in Canada or Australia, mark authorized.)"
|
||||||
# basics_looking_for: "Looking For"
|
basics_looking_for: "À Procura De"
|
||||||
# basics_looking_for_full_time: "Full-time"
|
basics_looking_for_full_time: "Tempo Inteiro"
|
||||||
# basics_looking_for_part_time: "Part-time"
|
basics_looking_for_part_time: "Part-time"
|
||||||
# basics_looking_for_remote: "Remote"
|
basics_looking_for_remote: "Remoto"
|
||||||
# basics_looking_for_contracting: "Contracting"
|
# basics_looking_for_contracting: "Contracting"
|
||||||
# basics_looking_for_internship: "Internship"
|
# basics_looking_for_internship: "Internship"
|
||||||
# basics_looking_for_help: "What kind of developer position do you want?"
|
# basics_looking_for_help: "What kind of developer position do you want?"
|
||||||
# name_header: "Fill in your name"
|
# name_header: "Fill in your name"
|
||||||
# name_anonymous: "Anonymous Developer"
|
name_anonymous: "Desenvolvedor Anónimo"
|
||||||
# name_help: "Name you want employers to see, like 'Nick Winter'."
|
# name_help: "Name you want employers to see, like 'Nick Winter'."
|
||||||
# short_description_header: "Write a short description of yourself"
|
# short_description_header: "Write a short description of yourself"
|
||||||
# short_description_blurb: "Add a tagline to help an employer quickly learn more about you."
|
# short_description_blurb: "Add a tagline to help an employer quickly learn more about you."
|
||||||
# short_description: "Tagline"
|
# short_description: "Tagline"
|
||||||
# short_description_help: "Who are you, and what are you looking for? 140 characters max."
|
# short_description_help: "Who are you, and what are you looking for? 140 characters max."
|
||||||
# skills_header: "Skills"
|
skills_header: "Habilidades"
|
||||||
# skills_help: "Tag relevant developer skills in order of proficiency."
|
# skills_help: "Tag relevant developer skills in order of proficiency."
|
||||||
# long_description_header: "Describe your desired position"
|
# long_description_header: "Describe your desired position"
|
||||||
# long_description_blurb: "Tell employers how awesome you are and what role you want."
|
# long_description_blurb: "Tell employers how awesome you are and what role you want."
|
||||||
|
@ -266,22 +266,22 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
# long_description_help: "Describe yourself to potential employers. Keep it short and to the point. We recommend outlining the position that would most interest you. Tasteful markdown okay; 600 characters max."
|
# long_description_help: "Describe yourself to potential employers. Keep it short and to the point. We recommend outlining the position that would most interest you. Tasteful markdown okay; 600 characters max."
|
||||||
# work_experience: "Work Experience"
|
# work_experience: "Work Experience"
|
||||||
# work_header: "Chronicle your work history"
|
# work_header: "Chronicle your work history"
|
||||||
# work_years: "Years of Experience"
|
work_years: "Anos de Experiência"
|
||||||
# work_years_help: "How many years of professional experience (getting paid) developing software do you have?"
|
# work_years_help: "How many years of professional experience (getting paid) developing software do you have?"
|
||||||
# work_blurb: "List your relevant work experience, most recent first."
|
# work_blurb: "List your relevant work experience, most recent first."
|
||||||
# work_employer: "Employer"
|
work_employer: "Empregador"
|
||||||
# work_employer_help: "Name of your employer."
|
work_employer_help: "Nome do seu empregador."
|
||||||
# work_role: "Job Title"
|
work_role: "Título do Emprego"
|
||||||
# work_role_help: "What was your job title or role?"
|
# work_role_help: "What was your job title or role?"
|
||||||
# work_duration: "Duration"
|
work_duration: "Duração"
|
||||||
# work_duration_help: "When did you hold this gig?"
|
# work_duration_help: "When did you hold this gig?"
|
||||||
# work_description: "Description"
|
work_description: "Descrição"
|
||||||
# work_description_help: "What did you do there? (140 chars; optional)"
|
# work_description_help: "What did you do there? (140 chars; optional)"
|
||||||
# education: "Education"
|
education: "Educação"
|
||||||
# education_header: "Recount your academic ordeals"
|
# education_header: "Recount your academic ordeals"
|
||||||
# education_blurb: "List your academic ordeals."
|
# education_blurb: "List your academic ordeals."
|
||||||
# education_school: "School"
|
education_school: "Escola"
|
||||||
# education_school_help: "Name of your school."
|
education_school_help: "Nome da sua escola."
|
||||||
# education_degree: "Degree"
|
# education_degree: "Degree"
|
||||||
# education_degree_help: "What was your degree and field of study?"
|
# education_degree_help: "What was your degree and field of study?"
|
||||||
# education_duration: "Dates"
|
# education_duration: "Dates"
|
||||||
|
@ -312,18 +312,18 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
filter_visa: "Visa"
|
filter_visa: "Visa"
|
||||||
filter_visa_yes: "Autorizado Para Trabalhar Nos EUA"
|
filter_visa_yes: "Autorizado Para Trabalhar Nos EUA"
|
||||||
filter_visa_no: "Não Autorizado"
|
filter_visa_no: "Não Autorizado"
|
||||||
# filter_education_top: "Top School"
|
filter_education_top: "Universidade"
|
||||||
# filter_education_other: "Other"
|
filter_education_other: "Outro"
|
||||||
# filter_role_web_developer: "Web Developer"
|
filter_role_web_developer: "Desenvolvedor da Web"
|
||||||
# filter_role_software_developer: "Software Developer"
|
filter_role_software_developer: "Desenvolvedor de Software"
|
||||||
# filter_role_mobile_developer: "Mobile Developer"
|
filter_role_mobile_developer: "Desenvolvedor Mobile"
|
||||||
# filter_experience: "Experience"
|
filter_experience: "Experiência"
|
||||||
# filter_experience_senior: "Senior"
|
filter_experience_senior: "Sénior"
|
||||||
# filter_experience_junior: "Junior"
|
filter_experience_junior: "Júnior"
|
||||||
# filter_experience_recent_grad: "Recent Grad"
|
# filter_experience_recent_grad: "Recent Grad"
|
||||||
# filter_experience_student: "College Student"
|
filter_experience_student: "Estudante Universitário"
|
||||||
# filter_results: "results"
|
filter_results: "resultados"
|
||||||
# start_hiring: "Start hiring."
|
start_hiring: "Começar a contratar."
|
||||||
# reasons: "Three reasons you should hire through us:"
|
# reasons: "Three reasons you should hire through us:"
|
||||||
# everyone_looking: "Everyone here is looking for their next opportunity."
|
# everyone_looking: "Everyone here is looking for their next opportunity."
|
||||||
# everyone_looking_blurb: "Forget about 20% LinkedIn InMail response rates. Everyone that we list on this site wants to find their next position and will respond to your request for an introduction."
|
# everyone_looking_blurb: "Forget about 20% LinkedIn InMail response rates. Everyone that we list on this site wants to find their next position and will respond to your request for an introduction."
|
||||||
|
@ -408,11 +408,11 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
tip_morale_improves: "O carregamento irá continuar até que a moral melhore."
|
tip_morale_improves: "O carregamento irá continuar até que a moral melhore."
|
||||||
tip_all_species: "Acreditamos em oportunidades iguais para todas as espécies, em relação a aprenderem a programar."
|
tip_all_species: "Acreditamos em oportunidades iguais para todas as espécies, em relação a aprenderem a programar."
|
||||||
tip_reticulating: "A reticular espinhas."
|
tip_reticulating: "A reticular espinhas."
|
||||||
tip_harry: "Você é um Feitiçeiro, "
|
tip_harry: "Você é um Feiticeiro, "
|
||||||
# tip_great_responsibility: "With great coding skill comes great debug responsibility."
|
tip_great_responsibility: "Com uma grande habilidade de programação vem uma grande responsabilidade de depuração."
|
||||||
# tip_munchkin: "If you don't eat your vegetables, a munchkin will come after you while you're asleep."
|
tip_munchkin: "Se não comer os seus vegetais, virá um ogre atrás de si enquanto estiver a dormir."
|
||||||
tip_binary: "Há apenas 10 tipos de pessoas no mundo: aquelas que percebem binário e aquelas que não."
|
tip_binary: "Há apenas 10 tipos de pessoas no mundo: aquelas que percebem binário e aquelas que não."
|
||||||
# tip_commitment_yoda: "A programmer must have the deepest commitment, the most serious mind. ~ Yoda"
|
tip_commitment_yoda: "Um programador deve ter o compromisso mais profundo, a mente mais séria. ~ Yoda"
|
||||||
tip_no_try: "Fazer. Ou não fazer. Não há nenhum tentar. - Yoda"
|
tip_no_try: "Fazer. Ou não fazer. Não há nenhum tentar. - Yoda"
|
||||||
tip_patience: "Paciência tu deves ter, jovem Padawan. - Yoda"
|
tip_patience: "Paciência tu deves ter, jovem Padawan. - Yoda"
|
||||||
tip_documented_bug: "Um erro documentado não é um erro; é uma funcionalidade."
|
tip_documented_bug: "Um erro documentado não é um erro; é uma funcionalidade."
|
||||||
|
@ -448,7 +448,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
# temp: "Temp"
|
# temp: "Temp"
|
||||||
|
|
||||||
save_load:
|
save_load:
|
||||||
# granularity_saved_games: "Saved"
|
granularity_saved_games: "Guardados"
|
||||||
granularity_change_history: "Histórico"
|
granularity_change_history: "Histórico"
|
||||||
|
|
||||||
options:
|
options:
|
||||||
|
@ -504,7 +504,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
# toggle_grid: "Toggle grid overlay."
|
# toggle_grid: "Toggle grid overlay."
|
||||||
# toggle_pathfinding: "Toggle pathfinding overlay."
|
# toggle_pathfinding: "Toggle pathfinding overlay."
|
||||||
# beautify: "Beautify your code by standardizing its formatting."
|
# beautify: "Beautify your code by standardizing its formatting."
|
||||||
move_wizard: "Mover o seu Feitiçeiro pelo nível."
|
move_wizard: "Mover o seu Feiticeiro pelo nível."
|
||||||
|
|
||||||
admin:
|
admin:
|
||||||
av_title: "Vistas de Administrador"
|
av_title: "Vistas de Administrador"
|
||||||
|
@ -547,7 +547,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
grassy: "Com Relva"
|
grassy: "Com Relva"
|
||||||
fork_title: "Bifurcar Nova Versão"
|
fork_title: "Bifurcar Nova Versão"
|
||||||
fork_creating: "A Criar Bifurcação..."
|
fork_creating: "A Criar Bifurcação..."
|
||||||
# randomize: "Randomize"
|
randomize: "Randomizar"
|
||||||
more: "Mais"
|
more: "Mais"
|
||||||
wiki: "Wiki"
|
wiki: "Wiki"
|
||||||
live_chat: "Chat Ao Vivo"
|
live_chat: "Chat Ao Vivo"
|
||||||
|
@ -561,8 +561,8 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
level_tab_thangs_all: "Todos"
|
level_tab_thangs_all: "Todos"
|
||||||
level_tab_thangs_conditions: "Condições Iniciais"
|
level_tab_thangs_conditions: "Condições Iniciais"
|
||||||
level_tab_thangs_add: "Adicionar Thangs"
|
level_tab_thangs_add: "Adicionar Thangs"
|
||||||
# delete: "Delete"
|
delete: "Eliminar"
|
||||||
# duplicate: "Duplicate"
|
duplicate: "Duplicar"
|
||||||
level_settings_title: "Configurações"
|
level_settings_title: "Configurações"
|
||||||
level_component_tab_title: "Componentes Atuais"
|
level_component_tab_title: "Componentes Atuais"
|
||||||
level_component_btn_new: "Criar Novo Componente"
|
level_component_btn_new: "Criar Novo Componente"
|
||||||
|
@ -572,7 +572,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
level_components_title: "Voltar para Todos os Thangs"
|
level_components_title: "Voltar para Todos os Thangs"
|
||||||
level_components_type: "Tipo"
|
level_components_type: "Tipo"
|
||||||
level_component_edit_title: "Editar Componente"
|
level_component_edit_title: "Editar Componente"
|
||||||
# level_component_config_schema: "Config Schema"
|
level_component_config_schema: "Configurar Esquema"
|
||||||
level_component_settings: "Configurações"
|
level_component_settings: "Configurações"
|
||||||
level_system_edit_title: "Editar Sistema"
|
level_system_edit_title: "Editar Sistema"
|
||||||
create_system_title: "Criar Novo Sistema"
|
create_system_title: "Criar Novo Sistema"
|
||||||
|
@ -632,22 +632,22 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
who_description_prefix: "começaram juntos o CodeCombat em 2013. Também criaram o "
|
who_description_prefix: "começaram juntos o CodeCombat em 2013. Também criaram o "
|
||||||
who_description_suffix: "em 2008, tornando-o a aplicação nº1 da web e iOS para aprender a escrever caracteteres Chineses e Japoneses."
|
who_description_suffix: "em 2008, tornando-o a aplicação nº1 da web e iOS para aprender a escrever caracteteres Chineses e Japoneses."
|
||||||
who_description_ending: "Agora, está na altura de ensinar as pessoas a escrever código."
|
who_description_ending: "Agora, está na altura de ensinar as pessoas a escrever código."
|
||||||
# why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that."
|
why_paragraph_1: "Aquando da conceção do Skritter, o George não sabia programar e estava constantemente frustrado devido à sua inabilidade para implementar as ideias dele. Mais tarde, tentou aprender, mas as aulas eram muito lentas. O seu colega de quarto, numa tentativa de melhorar as suas habilidades e parar de ensinar, tentou o Codecademy, mas \"aborreceu-se.\" A cada semana, um outro amigo começava no Codecademy, mas desistia sempre. Apercebemo-nos de que era o mesmo problema que resolveríamos com o Skritter: pessoas a aprender uma habilidade através de aulas lentas e intensivas, quando o que precisam é de praticar rápida e extensivamente. Nós sabemos como resolver isso."
|
||||||
# why_paragraph_2: "Need to learn to code? You don't need lessons. You need to write a lot of code and have a great time doing it."
|
why_paragraph_2: "Precisa de aprender a programar? Não precisa de aulas. Precisa sim de escrever muito código e passar um bom bocado enquanto o faz."
|
||||||
# why_paragraph_3_prefix: "That's what programming is about. It's gotta be fun. Not fun like"
|
why_paragraph_3_prefix: "Afinal, é sobre isso que é a programação. Tem de ser divertida. Não divertida do género"
|
||||||
# why_paragraph_3_italic: "yay a badge"
|
why_paragraph_3_italic: "yay uma medalha"
|
||||||
# why_paragraph_3_center: "but fun like"
|
why_paragraph_3_center: "mas sim divertida do género"
|
||||||
# why_paragraph_3_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!"
|
why_paragraph_3_italic_caps: "NÃO MÃE, TENHO DE ACABAR O NÍVEL!"
|
||||||
# why_paragraph_3_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
|
why_paragraph_3_suffix: "É por isso que o CodeCombat é um jogo multijogador, e não um jogo que não passa de um curso com lições. Nós não vamos parar enquanto não puderes parar--mas desta vez, isso é uma coisa boa."
|
||||||
# why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
|
why_paragraph_4: "Se vais ficar viciado em algum jogo, vicia-te neste e torna-te num dos feiticeiros da idade da tecnologia."
|
||||||
# why_ending: "And hey, it's free. "
|
why_ending: "E vejam só, é gratuito. "
|
||||||
# why_ending_url: "Start wizarding now!"
|
why_ending_url: "Comece a enfeitiçar agora!"
|
||||||
# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
|
george_description: "CEO, homem de negócios, designer da web, designer de jogos e campeão dos programadores iniciantes de todo o lado."
|
||||||
# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
|
scott_description: "Programador extraordinário, arquiteto de software, feiticeiro da cozinha e mestre das finanças. O Scott é sensato."
|
||||||
# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat."
|
nick_description: "Feiticeiro da programção, mago da motivação excêntrico e experimentador de pernas para o ar. O Nick pode fazer qualquer coisa e escolhe construir o CodeCombat."
|
||||||
# jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy."
|
jeremy_description: "Mago do suporte ao cliente, testador do uso e organizador da comunidade; provavelmente já falou com o Jeremy."
|
||||||
# michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online."
|
michael_description: "Programador, administrador do sistema e técnico de graduação prodígio, o Michael é a pessoa que mantém os nossos servidores online."
|
||||||
# glen_description: "Programmer and passionate game developer, with the motivation to make this world a better place, by developing things that matter. The word impossible can't be found in his dictionary. Learning new skills is his joy!"
|
glen_description: "Programador e desenvolvedor de jogos apaixonado, com a motivação necessária para tornar este mundo um lugar melhor, ao desenvolver coisas que importam. A palavra impossível não pode ser encontrada no dicionário dele. Aprender novas habilidades é a alegria dele!"
|
||||||
|
|
||||||
legal:
|
legal:
|
||||||
page_title: "Legal"
|
page_title: "Legal"
|
||||||
|
@ -834,11 +834,11 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
rank_failed: "Falhou a Classificar"
|
rank_failed: "Falhou a Classificar"
|
||||||
rank_being_ranked: "Jogo a ser Classificado"
|
rank_being_ranked: "Jogo a ser Classificado"
|
||||||
# rank_last_submitted: "submitted "
|
# rank_last_submitted: "submitted "
|
||||||
# help_simulate: "Help simulate games?"
|
help_simulate: "Ajudar a simular jogos?"
|
||||||
code_being_simulated: "O teu código está a ser simulado por outros jogadores, para ser classificado. Isto será actualizado quando surgirem novas partidas."
|
code_being_simulated: "O seu novo código está a ser simulado por outros jogadores, para ser classificado. Isto será atualizado quando surgirem novas partidas."
|
||||||
no_ranked_matches_pre: "Sem jogos classificados pela equipa "
|
no_ranked_matches_pre: "Sem jogos classificados pela equipa "
|
||||||
no_ranked_matches_post: "! Joga contra alguns adversários e volta aqui para veres o teu jogo classificado."
|
no_ranked_matches_post: "! Joga contra alguns adversários e volta aqui para veres o teu jogo classificado."
|
||||||
choose_opponent: "Escolhe um Adversário"
|
choose_opponent: "Escolha um Adversário"
|
||||||
select_your_language: "Selecione a sua linguagem!"
|
select_your_language: "Selecione a sua linguagem!"
|
||||||
tutorial_play: "Jogar Tutorial"
|
tutorial_play: "Jogar Tutorial"
|
||||||
tutorial_recommended: "Recomendado se nunca jogou antes"
|
tutorial_recommended: "Recomendado se nunca jogou antes"
|
||||||
|
@ -848,40 +848,40 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
simple_ai: "Inteligência Artificial Simples"
|
simple_ai: "Inteligência Artificial Simples"
|
||||||
warmup: "Aquecimento"
|
warmup: "Aquecimento"
|
||||||
vs: "VS"
|
vs: "VS"
|
||||||
# friends_playing: "Friends Playing"
|
friends_playing: "Amigos a Jogar"
|
||||||
# log_in_for_friends: "Log in to play with your friends!"
|
log_in_for_friends: "Inicie sessão para jogar com os seus amigos!"
|
||||||
# social_connect_blurb: "Connect and play against your friends!"
|
social_connect_blurb: "Conecte-se e jogue contra os seus amigos!"
|
||||||
# invite_friends_to_battle: "Invite your friends to join you in battle!"
|
invite_friends_to_battle: "Convide os seus amigos para se juntarem a si em batalha!"
|
||||||
# fight: "Fight!"
|
fight: "Lutar!"
|
||||||
# watch_victory: "Watch your victory"
|
watch_victory: "Veja a sua vitória"
|
||||||
# defeat_the: "Defeat the"
|
defeat_the: "Derrote o"
|
||||||
# tournament_ends: "Tournament ends"
|
tournament_ends: "O Torneio acaba"
|
||||||
# tournament_ended: "Tournament ended"
|
tournament_ended: "O Torneio acabou"
|
||||||
# tournament_rules: "Tournament Rules"
|
tournament_rules: "Regras do Torneio"
|
||||||
# tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details"
|
tournament_blurb: "Escreva código, recolha ouro, construa exércitos, esmague inimigos, ganhe prémios e melhore a sua carreira no nosso torneio $40,000 Greed! Confira os detalhes"
|
||||||
# tournament_blurb_blog: "on our blog"
|
tournament_blurb_blog: "no nosso blog"
|
||||||
rules: "Regras"
|
rules: "Regras"
|
||||||
winners: "Vencedores"
|
winners: "Vencedores"
|
||||||
|
|
||||||
# ladder_prizes:
|
ladder_prizes:
|
||||||
# title: "Tournament Prizes"
|
title: "Prémios do Torneio"
|
||||||
# blurb_1: "These prizes will be awarded according to"
|
blurb_1: "Estes prémios serão entregues de acordo com"
|
||||||
# blurb_2: "the tournament rules"
|
blurb_2: "as regras do torneio"
|
||||||
# blurb_3: "to the top human and ogre players."
|
blurb_3: "aos melhores jogadores humanos e ogres."
|
||||||
# blurb_4: "Two teams means double the prizes!"
|
blurb_4: "Duas equipas significam o dobro dos prémios!"
|
||||||
# blurb_5: "(There will be two first place winners, two second-place winners, etc.)"
|
blurb_5: "(Haverá dois vencedores em primeiro lugar, dois em segundo, etc.)"
|
||||||
# rank: "Rank"
|
rank: "Classificação"
|
||||||
# prizes: "Prizes"
|
prizes: "Prémios"
|
||||||
# total_value: "Total Value"
|
total_value: "Valor Total"
|
||||||
# in_cash: "in cash"
|
in_cash: "em dinheiro"
|
||||||
# custom_wizard: "Custom CodeCombat Wizard"
|
custom_wizard: "Um Feiticeiro do CodeCombat Personalizado"
|
||||||
# custom_avatar: "Custom CodeCombat avatar"
|
custom_avatar: "Um Avatar do CodeCombat Personalizado"
|
||||||
# heap: "for six months of \"Startup\" access"
|
heap: "para seis meses de acesso \"Startup\""
|
||||||
# credits: "credits"
|
credits: "créditos"
|
||||||
# one_month_coupon: "coupon: choose either Rails or HTML"
|
one_month_coupon: "cupão: escolha Rails ou HTML"
|
||||||
# one_month_discount: "discount, 30% off: choose either Rails or HTML"
|
one_month_discount: "desconto de 30%: escolha Rails ou HTML"
|
||||||
# license: "license"
|
license: "licença"
|
||||||
# oreilly: "ebook of your choice"
|
oreilly: "ebook à sua escolha"
|
||||||
|
|
||||||
loading_error:
|
loading_error:
|
||||||
could_not_load: "Erro ao carregar do servidor"
|
could_not_load: "Erro ao carregar do servidor"
|
||||||
|
@ -909,8 +909,8 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
# user_schema: "User Schema"
|
# user_schema: "User Schema"
|
||||||
# user_profile: "User Profile"
|
# user_profile: "User Profile"
|
||||||
# patches: "Patches"
|
# patches: "Patches"
|
||||||
# patched_model: "Source Document"
|
patched_model: "Documento Fonte"
|
||||||
# model: "Model"
|
model: "Modelo"
|
||||||
system: "Sistema"
|
system: "Sistema"
|
||||||
component: "Componente"
|
component: "Componente"
|
||||||
components: "Componentes"
|
components: "Componentes"
|
||||||
|
@ -921,20 +921,20 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
|
||||||
article: "Artigo"
|
article: "Artigo"
|
||||||
# user_names: "User Names"
|
# user_names: "User Names"
|
||||||
# thang_names: "Thang Names"
|
# thang_names: "Thang Names"
|
||||||
# files: "Files"
|
files: "Ficheiros"
|
||||||
# top_simulators: "Top Simulators"
|
# top_simulators: "Top Simulators"
|
||||||
# source_document: "Source Document"
|
source_document: "Documento Fonte"
|
||||||
document: "Documento"
|
document: "Documento"
|
||||||
# sprite_sheet: "Sprite Sheet"
|
# sprite_sheet: "Sprite Sheet"
|
||||||
# candidate_sessions: "Candidate Sessions"
|
# candidate_sessions: "Candidate Sessions"
|
||||||
# user_remark: "User Remark"
|
# user_remark: "User Remark"
|
||||||
versions: "Versões"
|
versions: "Versões"
|
||||||
|
|
||||||
# delta:
|
delta:
|
||||||
# added: "Added"
|
added: "Adicionados/as"
|
||||||
# modified: "Modified"
|
modified: "Modificados/as"
|
||||||
# deleted: "Deleted"
|
deleted: "Eliminados/as"
|
||||||
# moved_index: "Moved Index"
|
moved_index: "Índice Movido"
|
||||||
# text_diff: "Text Diff"
|
text_diff: "Diferença de Texto"
|
||||||
# merge_conflict_with: "MERGE CONFLICT WITH"
|
merge_conflict_with: "FUNDIR CONFLITO COM"
|
||||||
# no_changes: "No Changes"
|
no_changes: "Sem Alterações"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
CocoModel = require './CocoModel'
|
CocoModel = require './CocoModel'
|
||||||
util = require '../lib/utils'
|
utils = require '../lib/utils'
|
||||||
|
|
||||||
module.exports = class Achievement extends CocoModel
|
module.exports = class Achievement extends CocoModel
|
||||||
@className: 'Achievement'
|
@className: 'Achievement'
|
||||||
|
@ -11,6 +11,47 @@ module.exports = class Achievement extends CocoModel
|
||||||
|
|
||||||
# TODO logic is duplicated in Mongoose Achievement schema
|
# TODO logic is duplicated in Mongoose Achievement schema
|
||||||
getExpFunction: ->
|
getExpFunction: ->
|
||||||
kind = @get('function')?.kind or @schema.function.default.kind
|
kind = @get('function')?.kind or jsonschema.properties.function.default.kind
|
||||||
parameters = @get('function')?.parameters or @schema.function.default.parameters
|
parameters = @get('function')?.parameters or jsonschema.properties.function.default.parameters
|
||||||
return utils.functionCreators[kind](parameters) if kind of utils.functionCreators
|
return utils.functionCreators[kind](parameters) if kind of utils.functionCreators
|
||||||
|
|
||||||
|
@styleMapping:
|
||||||
|
1: 'achievement-wood'
|
||||||
|
2: 'achievement-stone'
|
||||||
|
3: 'achievement-silver'
|
||||||
|
4: 'achievement-gold'
|
||||||
|
5: 'achievement-diamond'
|
||||||
|
|
||||||
|
getStyle: -> Achievement.styleMapping[@get 'difficulty']
|
||||||
|
|
||||||
|
@defaultImageURL: '/images/achievements/default.png'
|
||||||
|
|
||||||
|
getImageURL: ->
|
||||||
|
if @get 'icon' then '/file/' + @get('icon') else Achievement.defaultImageURL
|
||||||
|
|
||||||
|
hasImage: -> @get('icon')?
|
||||||
|
|
||||||
|
# TODO Could cache the default icon separately
|
||||||
|
cacheLockedImage: ->
|
||||||
|
return @lockedImageURL if @lockedImageURL
|
||||||
|
canvas = document.createElement 'canvas'
|
||||||
|
image = new Image
|
||||||
|
image.src = @getImageURL()
|
||||||
|
defer = $.Deferred()
|
||||||
|
image.onload = =>
|
||||||
|
canvas.width = image.width
|
||||||
|
canvas.height = image.height
|
||||||
|
context = canvas.getContext '2d'
|
||||||
|
context.drawImage image, 0, 0
|
||||||
|
imgData = context.getImageData 0, 0, canvas.width, canvas.height
|
||||||
|
imgData = utils.grayscale imgData
|
||||||
|
context.putImageData imgData, 0, 0
|
||||||
|
@lockedImageURL = canvas.toDataURL()
|
||||||
|
defer.resolve @lockedImageURL
|
||||||
|
defer
|
||||||
|
|
||||||
|
getLockedImageURL: -> @lockedImageURL
|
||||||
|
|
||||||
|
i18nName: -> utils.i18n @attributes, 'name'
|
||||||
|
|
||||||
|
i18nDescription: -> utils.i18n @attributes, 'description'
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
storage = require 'lib/storage'
|
storage = require 'lib/storage'
|
||||||
deltasLib = require 'lib/deltas'
|
deltasLib = require 'lib/deltas'
|
||||||
|
|
||||||
NewAchievementCollection = require '../collections/NewAchievementCollection'
|
|
||||||
|
|
||||||
class CocoModel extends Backbone.Model
|
class CocoModel extends Backbone.Model
|
||||||
idAttribute: '_id'
|
idAttribute: '_id'
|
||||||
loaded: false
|
loaded: false
|
||||||
|
@ -301,11 +299,13 @@ class CocoModel extends Backbone.Model
|
||||||
return if _.isString @url then @url else @url()
|
return if _.isString @url then @url else @url()
|
||||||
|
|
||||||
@pollAchievements: ->
|
@pollAchievements: ->
|
||||||
|
NewAchievementCollection = require '../collections/NewAchievementCollection' # Nasty mutual inclusion if put on top
|
||||||
achievements = new NewAchievementCollection
|
achievements = new NewAchievementCollection
|
||||||
achievements.fetch(
|
achievements.fetch
|
||||||
success: (collection) ->
|
success: (collection) ->
|
||||||
me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models)
|
me.fetch (success: -> Backbone.Mediator.publish('achievements:new', collection)) unless _.isEmpty(collection.models)
|
||||||
)
|
error: ->
|
||||||
|
console.error 'Miserably failed to fetch unnotified achievements', arguments
|
||||||
|
|
||||||
CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500
|
CocoModel.pollAchievements = _.debounce CocoModel.pollAchievements, 500
|
||||||
|
|
||||||
|
|
7
app/models/EarnedAchievement.coffee
Normal 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'
|
|
@ -80,7 +80,7 @@ module.exports = class Level extends CocoModel
|
||||||
visit = (c) ->
|
visit = (c) ->
|
||||||
return if c in sorted
|
return if c in sorted
|
||||||
lc = _.find levelComponents, {original: c.original}
|
lc = _.find levelComponents, {original: c.original}
|
||||||
console.error thang.id, 'couldn\'t find lc for', c, 'of', levelComponents unless lc
|
console.error thang.id or thang.name, 'couldn\'t find lc for', c, 'of', levelComponents unless lc
|
||||||
return unless lc
|
return unless lc
|
||||||
if lc.name is 'Programmable'
|
if lc.name is 'Programmable'
|
||||||
# Programmable always comes last
|
# Programmable always comes last
|
||||||
|
@ -88,7 +88,7 @@ module.exports = class Level extends CocoModel
|
||||||
else
|
else
|
||||||
for d in lc.dependencies or []
|
for d in lc.dependencies or []
|
||||||
c2 = _.find thang.components, {original: d.original}
|
c2 = _.find thang.components, {original: d.original}
|
||||||
console.error thang.id, 'couldn\'t find dependent Component', d.original, 'from', lc.name unless c2
|
console.error thang.id or thang.name, 'couldn\'t find dependent Component', d.original, 'from', lc.name unless c2
|
||||||
visit c2 if c2
|
visit c2 if c2
|
||||||
if lc.name is 'Collides'
|
if lc.name is 'Collides'
|
||||||
allied = _.find levelComponents, {name: 'Allied'}
|
allied = _.find levelComponents, {name: 'Allied'}
|
||||||
|
|
|
@ -36,3 +36,9 @@ module.exports = class LevelSession extends CocoModel
|
||||||
spell = item[1]
|
spell = item[1]
|
||||||
return true if c1[thang][spell] isnt c2[thang]?[spell]
|
return true if c1[thang][spell] isnt c2[thang]?[spell]
|
||||||
false
|
false
|
||||||
|
|
||||||
|
isMultiplayer: ->
|
||||||
|
@get('team')? # Only multiplayer level sessions have teams defined
|
||||||
|
|
||||||
|
completed: ->
|
||||||
|
@get('state')?.complete || false
|
||||||
|
|
|
@ -58,9 +58,15 @@ module.exports = class SuperModel extends Backbone.Model
|
||||||
return res
|
return res
|
||||||
else
|
else
|
||||||
@addCollection collection
|
@addCollection collection
|
||||||
@listenToOnce collection, 'sync', (c) ->
|
onCollectionSynced = (c) ->
|
||||||
console.debug 'Registering collection', url
|
if collection.url is c.url
|
||||||
|
console.debug 'Registering collection', url, c
|
||||||
@registerCollection c
|
@registerCollection c
|
||||||
|
else
|
||||||
|
console.warn 'Sync triggered for collection', c
|
||||||
|
console.warn 'Yet got other object', c
|
||||||
|
@listenToOnce collection, 'sync', onCollectionSynced
|
||||||
|
@listenToOnce collection, 'sync', onCollectionSynced
|
||||||
res = @addModelResource(collection, name, fetchOptions, value)
|
res = @addModelResource(collection, name, fetchOptions, value)
|
||||||
res.load() if not (res.isLoading or res.isLoaded)
|
res.load() if not (res.isLoading or res.isLoaded)
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -9,14 +9,24 @@ module.exports = class User extends CocoModel
|
||||||
urlRoot: '/db/user'
|
urlRoot: '/db/user'
|
||||||
notyErrors: false
|
notyErrors: false
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
points: 0
|
||||||
|
|
||||||
initialize: ->
|
initialize: ->
|
||||||
super()
|
super()
|
||||||
@migrateEmails()
|
@migrateEmails()
|
||||||
|
|
||||||
|
onLoaded: ->
|
||||||
|
CocoModel.pollAchievements() # Check for achievements on login
|
||||||
|
super arguments...
|
||||||
|
|
||||||
isAdmin: ->
|
isAdmin: ->
|
||||||
permissions = @attributes['permissions'] or []
|
permissions = @attributes['permissions'] or []
|
||||||
return 'admin' in permissions
|
return 'admin' in permissions
|
||||||
|
|
||||||
|
isAnonymous: ->
|
||||||
|
@get 'anonymous'
|
||||||
|
|
||||||
displayName: ->
|
displayName: ->
|
||||||
@get('name') or 'Anoner'
|
@get('name') or 'Anoner'
|
||||||
|
|
||||||
|
@ -32,47 +42,13 @@ module.exports = class User extends CocoModel
|
||||||
return "/file/#{photoURL}#{prefix}s=#{size}"
|
return "/file/#{photoURL}#{prefix}s=#{size}"
|
||||||
return "/db/user/#{@id}/avatar?s=#{size}&employerPageAvatar=#{useEmployerPageAvatar}"
|
return "/db/user/#{@id}/avatar?s=#{size}&employerPageAvatar=#{useEmployerPageAvatar}"
|
||||||
|
|
||||||
@getByID = (id, properties, force) ->
|
getSlugOrID: -> @get('slug') or @get('_id')
|
||||||
{me} = require 'lib/auth'
|
|
||||||
return me if me.id is id
|
|
||||||
user = cache[id] or new module.exports({_id: id})
|
|
||||||
if force or not cache[id]
|
|
||||||
user.loading = true
|
|
||||||
user.fetch(
|
|
||||||
success: ->
|
|
||||||
user.loading = false
|
|
||||||
Backbone.Mediator.publish('user:fetched')
|
|
||||||
#user.trigger 'sync' # needed?
|
|
||||||
)
|
|
||||||
cache[id] = user
|
|
||||||
user
|
|
||||||
set: ->
|
set: ->
|
||||||
if arguments[0] is 'jobProfileApproved' and @get("jobProfileApproved") is false and not @get("jobProfileApprovedDate")
|
if arguments[0] is 'jobProfileApproved' and @get("jobProfileApproved") is false and not @get("jobProfileApprovedDate")
|
||||||
@set "jobProfileApprovedDate", (new Date()).toISOString()
|
@set "jobProfileApprovedDate", (new Date()).toISOString()
|
||||||
super arguments...
|
super arguments...
|
||||||
|
|
||||||
# callbacks can be either success or error
|
|
||||||
@getByIDOrSlug: (idOrSlug, force, callbacks={}) ->
|
|
||||||
{me} = require 'lib/auth'
|
|
||||||
isID = util.isID idOrSlug
|
|
||||||
if me.id is idOrSlug or me.slug is idOrSlug
|
|
||||||
callbacks.success me if callbacks.success?
|
|
||||||
return me
|
|
||||||
cached = cache[idOrSlug]
|
|
||||||
user = cached or new @ _id: idOrSlug
|
|
||||||
if force or not cached
|
|
||||||
user.loading = true
|
|
||||||
user.fetch
|
|
||||||
success: ->
|
|
||||||
user.loading = false
|
|
||||||
Backbone.Mediator.publish 'user:fetched'
|
|
||||||
callbacks.success user if callbacks.success?
|
|
||||||
error: ->
|
|
||||||
user.loading = false
|
|
||||||
callbacks.error user if callbacks.error?
|
|
||||||
cache[idOrSlug] = user
|
|
||||||
user
|
|
||||||
|
|
||||||
@getUnconflictedName: (name, done) ->
|
@getUnconflictedName: (name, done) ->
|
||||||
$.ajax "/auth/name/#{name}",
|
$.ajax "/auth/name/#{name}",
|
||||||
success: (data) -> done data.name
|
success: (data) -> done data.name
|
||||||
|
@ -111,19 +87,16 @@ module.exports = class User extends CocoModel
|
||||||
isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled
|
isEmailSubscriptionEnabled: (name) -> (@get('emails') or {})[name]?.enabled
|
||||||
|
|
||||||
a = 5
|
a = 5
|
||||||
b = 40
|
b = 100
|
||||||
|
c = b
|
||||||
|
|
||||||
# y = a * ln(1/b * (x + b)) + 1
|
# y = a * ln(1/b * (x + c)) + 1
|
||||||
@levelFromExp: (xp) ->
|
@levelFromExp: (xp) ->
|
||||||
if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + b))) + 1 else 1
|
if xp > 0 then Math.floor(a * Math.log((1/b) * (xp + c))) + 1 else 1
|
||||||
|
|
||||||
# x = (e^((y-1)/a) - 1) * b
|
# x = b * e^((y-1)/a) - c
|
||||||
@expForLevel: (level) ->
|
@expForLevel: (level) ->
|
||||||
Math.ceil((Math.exp((level - 1)/ a) - 1) * b)
|
if level > 1 then Math.ceil Math.exp((level - 1)/ a) * b - c else 0
|
||||||
|
|
||||||
level: ->
|
level: ->
|
||||||
User.levelFromExp(@get('points'))
|
User.levelFromExp(@get('points'))
|
||||||
|
|
||||||
levelFromExp: (xp) -> User.levelFromExp(xp)
|
|
||||||
|
|
||||||
expForLevel: (level) -> User.expForLevel(level)
|
|
||||||
|
|
|
@ -24,8 +24,9 @@ MongoFindQuerySchema =
|
||||||
'^[-a-zA-Z0-9\.]*$':
|
'^[-a-zA-Z0-9\.]*$':
|
||||||
oneOf: [
|
oneOf: [
|
||||||
#{$ref: '#/definitions/' + MongoQueryOperatorSchema.id},
|
#{$ref: '#/definitions/' + MongoQueryOperatorSchema.id},
|
||||||
{type: 'string'},
|
{type: 'string'}
|
||||||
{type: 'object'}
|
{type: 'object'}
|
||||||
|
{type: 'boolean'}
|
||||||
]
|
]
|
||||||
additionalProperties: true # TODO make Treema accept new pattern matched keys
|
additionalProperties: true # TODO make Treema accept new pattern matched keys
|
||||||
definitions: {}
|
definitions: {}
|
||||||
|
@ -34,36 +35,58 @@ MongoFindQuerySchema.definitions[MongoQueryOperatorSchema.id] = MongoQueryOperat
|
||||||
|
|
||||||
AchievementSchema = c.object()
|
AchievementSchema = c.object()
|
||||||
c.extendNamedProperties AchievementSchema
|
c.extendNamedProperties AchievementSchema
|
||||||
c.extendBasicProperties AchievementSchema, 'article'
|
c.extendBasicProperties AchievementSchema, 'achievement'
|
||||||
c.extendSearchableProperties AchievementSchema
|
c.extendSearchableProperties AchievementSchema
|
||||||
|
|
||||||
_.extend(AchievementSchema.properties,
|
_.extend AchievementSchema.properties,
|
||||||
query:
|
query:
|
||||||
#type:'object'
|
#type:'object'
|
||||||
$ref: '#/definitions/' + MongoFindQuerySchema.id
|
$ref: '#/definitions/' + MongoFindQuerySchema.id
|
||||||
worth: {type: 'number'}
|
worth: c.float
|
||||||
|
default: 10
|
||||||
collection: {type: 'string'}
|
collection: {type: 'string'}
|
||||||
description: {type: 'string'}
|
description: c.shortString
|
||||||
userField: {type: 'string'}
|
default: 'Probably the coolest you\'ll ever get.'
|
||||||
|
userField: c.shortString()
|
||||||
related: c.objectId(description: 'Related entity')
|
related: c.objectId(description: 'Related entity')
|
||||||
icon: {type: 'string', format: 'image-file', title: 'Icon'}
|
icon: {type: 'string', format: 'image-file', title: 'Icon'}
|
||||||
|
category:
|
||||||
|
enum: ['level', 'ladder', 'contributor']
|
||||||
|
description: 'For categorizing and display purposes'
|
||||||
|
difficulty: c.int
|
||||||
|
description: 'The higher the more difficult'
|
||||||
|
default: 1
|
||||||
proportionalTo:
|
proportionalTo:
|
||||||
type: 'string'
|
type: 'string'
|
||||||
description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations'
|
description: 'For repeatables only. Denotes the field a repeatable achievement needs for its calculations'
|
||||||
|
recalculable:
|
||||||
|
type: 'boolean'
|
||||||
|
description: 'Needs to be set to true before it is elligible for recalculation.'
|
||||||
|
default: true
|
||||||
function:
|
function:
|
||||||
type: 'object'
|
type: 'object'
|
||||||
|
description: 'Function that gives total experience for X amount achieved'
|
||||||
properties:
|
properties:
|
||||||
kind: {enum: ['linear', 'logarithmic'], default: 'linear'}
|
kind: {enum: ['linear', 'logarithmic', 'quadratic'], default: 'linear'}
|
||||||
parameters:
|
parameters:
|
||||||
type: 'object'
|
type: 'object'
|
||||||
properties:
|
properties:
|
||||||
a: {type: 'number', default: 1}
|
a: {type: 'number', default: 1}
|
||||||
b: {type: 'number', default: 1}
|
b: {type: 'number', default: 1}
|
||||||
c: {type: 'number', default: 1}
|
c: {type: 'number', default: 1}
|
||||||
|
additionalProperties: true
|
||||||
default: {kind: 'linear', parameters: a: 1}
|
default: {kind: 'linear', parameters: a: 1}
|
||||||
required: ['kind', 'parameters']
|
required: ['kind', 'parameters']
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
)
|
i18n: c.object
|
||||||
|
format: 'i18n'
|
||||||
|
props: ['name', 'description']
|
||||||
|
description: 'Help translate this achievement'
|
||||||
|
|
||||||
|
_.extend AchievementSchema, # Let's have these on the bottom
|
||||||
|
# TODO We really need some required properties in my opinion but this makes creating new achievements impossible as it is now
|
||||||
|
#required: ['name', 'description', 'query', 'worth', 'collection', 'userField', 'category', 'difficulty']
|
||||||
|
additionalProperties: false
|
||||||
|
|
||||||
AchievementSchema.definitions = {}
|
AchievementSchema.definitions = {}
|
||||||
AchievementSchema.definitions[MongoFindQuerySchema.id] = MongoFindQuerySchema
|
AchievementSchema.definitions[MongoFindQuerySchema.id] = MongoFindQuerySchema
|
||||||
|
|
|
@ -101,6 +101,14 @@ _.extend LevelSessionSchema.properties,
|
||||||
type: 'object'
|
type: 'object'
|
||||||
source:
|
source:
|
||||||
type: 'string'
|
type: 'string'
|
||||||
|
goalStates:
|
||||||
|
type: 'object'
|
||||||
|
description: 'Maps Goal ID on a goal state object'
|
||||||
|
additionalProperties:
|
||||||
|
title: 'Goal State'
|
||||||
|
type: 'object'
|
||||||
|
properties:
|
||||||
|
status: enum: ['failure', 'incomplete', 'success']
|
||||||
|
|
||||||
code:
|
code:
|
||||||
type: 'object'
|
type: 'object'
|
||||||
|
|
|
@ -20,6 +20,9 @@ PatchSchema = c.object({title: 'Patch', required: ['target', 'delta', 'commitMes
|
||||||
major: {type: 'number', minimum: 0}
|
major: {type: 'number', minimum: 0}
|
||||||
minor: {type: 'number', minimum: 0}
|
minor: {type: 'number', minimum: 0}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
wasPending: type: 'boolean'
|
||||||
|
newlyAccepted: type: 'boolean'
|
||||||
})
|
})
|
||||||
|
|
||||||
c.extendBasicProperties(PatchSchema, 'patch')
|
c.extendBasicProperties(PatchSchema, 'patch')
|
||||||
|
|
|
@ -222,6 +222,33 @@ _.extend UserSchema.properties,
|
||||||
|
|
||||||
points: {type: 'number'}
|
points: {type: 'number'}
|
||||||
activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity}
|
activity: {type: 'object', description: 'Summary statistics about user activity', additionalProperties: c.activity}
|
||||||
|
stats: c.object {additionalProperties: false},
|
||||||
|
gamesCompleted: c.int()
|
||||||
|
articleEdits: c.int()
|
||||||
|
levelEdits: c.int()
|
||||||
|
levelSystemEdits: c.int()
|
||||||
|
levelComponentEdits: c.int()
|
||||||
|
thangTypeEdits: c.int()
|
||||||
|
patchesSubmitted: c.int
|
||||||
|
description: 'Amount of patches submitted, not necessarily accepted'
|
||||||
|
patchesContributed: c.int
|
||||||
|
description: 'Amount of patches submitted and accepted'
|
||||||
|
patchesAccepted: c.int
|
||||||
|
description: 'Amount of patches accepted by the user as owner'
|
||||||
|
# The below patches only apply to those that actually got accepted
|
||||||
|
totalTranslationPatches: c.int()
|
||||||
|
totalMiscPatches: c.int()
|
||||||
|
articleTranslationPatches: c.int()
|
||||||
|
articleMiscPatches: c.int()
|
||||||
|
levelTranslationPatches: c.int()
|
||||||
|
levelMiscPatches: c.int()
|
||||||
|
levelComponentTranslationPatches: c.int()
|
||||||
|
levelComponentMiscPatches: c.int()
|
||||||
|
levelSystemTranslationPatches: c.int()
|
||||||
|
levelSystemMiscPatches: c.int()
|
||||||
|
thangTypeTranslationPatches: c.int()
|
||||||
|
thangTypeMiscPatches: c.int()
|
||||||
|
|
||||||
|
|
||||||
c.extendBasicProperties UserSchema, 'user'
|
c.extendBasicProperties UserSchema, 'user'
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ex
|
||||||
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
|
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
|
||||||
me.objectId = (ext) -> schema = combine({type: ['object', 'string']}, ext)
|
me.objectId = (ext) -> schema = combine({type: ['object', 'string']}, ext)
|
||||||
me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext)
|
me.url = (ext) -> combine({type: 'string', format: 'url', pattern: urlPattern}, ext)
|
||||||
|
me.int = (ext) -> combine {type: 'integer'}, ext
|
||||||
|
me.float = (ext) -> combine {type: 'number'}, ext
|
||||||
|
|
||||||
PointSchema = me.object {title: 'Point', description: 'An {x, y} coordinate point.', format: 'point2d', required: ['x', 'y']},
|
PointSchema = me.object {title: 'Point', description: 'An {x, y} coordinate point.', format: 'point2d', required: ['x', 'y']},
|
||||||
x: {title: 'x', description: 'The x coordinate.', type: 'number', 'default': 15}
|
x: {title: 'x', description: 'The x coordinate.', type: 'number', 'default': 15}
|
||||||
|
|
32
app/styles/account/account_home.sass
Normal 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
|
||||||
|
|
243
app/styles/achievements.sass
Normal 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
|
|
@ -21,7 +21,8 @@ h1 h2 h3 h4
|
||||||
margin: 56px auto 0
|
margin: 56px auto 0
|
||||||
min-height: 600px
|
min-height: 600px
|
||||||
padding: 14px 12px 5px 12px
|
padding: 14px 12px 5px 12px
|
||||||
@include box-sizing(border-box)
|
+box-sizing(border-box)
|
||||||
|
+clearfix()
|
||||||
|
|
||||||
#outer-content-wrapper
|
#outer-content-wrapper
|
||||||
background: #B4B4B4
|
background: #B4B4B4
|
||||||
|
@ -291,3 +292,9 @@ body[lang='ja']
|
||||||
|
|
||||||
a[data-toggle="coco-modal"]
|
a[data-toggle="coco-modal"]
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
|
|
||||||
|
.achievement-corner
|
||||||
|
position: fixed
|
||||||
|
bottom: 0px
|
||||||
|
right: 0px
|
||||||
|
z-index: 1001
|
||||||
|
|
|
@ -1,4 +1,44 @@
|
||||||
@import "../bootstrap/variables"
|
@import "../bootstrap/variables"
|
||||||
|
@import "../bootstrap/mixins"
|
||||||
|
|
||||||
|
// This is still very blocky. Browser reflows? Investigate why.
|
||||||
|
.open > .dropdown-menu
|
||||||
|
animation-name: fadeAnimation
|
||||||
|
animation-duration: .7s
|
||||||
|
animation-iteration-count: 1
|
||||||
|
animation-timing-function: ease
|
||||||
|
animation-fill-mode: forwards
|
||||||
|
-webkit-animation-name: fadeAnimation
|
||||||
|
-webkit-animation-duration: .7s
|
||||||
|
-webkit-animation-iteration-count: 1
|
||||||
|
-webkit-animation-timing-function: ease
|
||||||
|
-webkit-animation-fill-mode: backwards
|
||||||
|
-moz-animation-name: fadeAnimation
|
||||||
|
-moz-animation-duration: .7s
|
||||||
|
-moz-animation-iteration-count: 1
|
||||||
|
-moz-animation-timing-function: ease
|
||||||
|
-moz-animation-fill-mode: forwards
|
||||||
|
|
||||||
|
@keyframes fadeAnimation
|
||||||
|
from
|
||||||
|
opacity: 0
|
||||||
|
top: 120%
|
||||||
|
to
|
||||||
|
opacity: 1
|
||||||
|
top: 100%
|
||||||
|
|
||||||
|
@-webkit-keyframes fadeAnimation
|
||||||
|
from
|
||||||
|
opacity: 0
|
||||||
|
top: 120%
|
||||||
|
to
|
||||||
|
opacity: 1
|
||||||
|
top: 100%
|
||||||
|
|
||||||
|
a.disabled
|
||||||
|
color: #5b5855
|
||||||
|
text-decoration: none
|
||||||
|
cursor: default
|
||||||
|
|
||||||
#top-nav
|
#top-nav
|
||||||
a.navbar-brand
|
a.navbar-brand
|
||||||
|
@ -19,11 +59,65 @@
|
||||||
.account-settings-image
|
.account-settings-image
|
||||||
width: 18px
|
width: 18px
|
||||||
height: 18px
|
height: 18px
|
||||||
|
margin-right: 5px
|
||||||
|
|
||||||
.glyphicon-user
|
.glyphicon-user
|
||||||
font-size: 16px
|
font-size: 16px
|
||||||
|
margin-right: 5px
|
||||||
|
|
||||||
.nav.navbar-link-text, .nav.navbar-link-text > li > a
|
.dropdown
|
||||||
|
.dropdown-menu
|
||||||
|
left: auto
|
||||||
|
width: 280px
|
||||||
|
padding: 0px
|
||||||
|
border-radius: 0px
|
||||||
|
font-family: Bangers
|
||||||
|
|
||||||
|
> .user-dropdown-header
|
||||||
|
position: relative
|
||||||
|
background: #E4CF8C
|
||||||
|
height: 160px
|
||||||
|
padding: 10px
|
||||||
|
text-align: center
|
||||||
|
color: black
|
||||||
|
border-bottom: #32281e 1px solid
|
||||||
|
> a:hover
|
||||||
|
background-color: transparent
|
||||||
|
img
|
||||||
|
border: #e3be7a 8px solid
|
||||||
|
height: 98px // Includes the border
|
||||||
|
&:hover
|
||||||
|
box-shadow: 0 0 20px #e3be7a
|
||||||
|
> h3
|
||||||
|
margin-top: 10px
|
||||||
|
text-shadow: 2px 2px 3px white
|
||||||
|
color: #31281E
|
||||||
|
.user-level
|
||||||
|
position: absolute
|
||||||
|
top: 73px
|
||||||
|
right: 86px
|
||||||
|
color: gold
|
||||||
|
text-shadow: 1px 1px black, -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black
|
||||||
|
|
||||||
|
.user-dropdown-body
|
||||||
|
color: black
|
||||||
|
padding: 15px
|
||||||
|
letter-spacing: 1px
|
||||||
|
font: 15px 'Helvetica Neue', Helvetica, Arial, sans-serif
|
||||||
|
+clearfix()
|
||||||
|
|
||||||
|
.user-dropdown-footer
|
||||||
|
padding: 10px
|
||||||
|
margin-left: 0px
|
||||||
|
font-size: 14px
|
||||||
|
+clearfix()
|
||||||
|
|
||||||
|
.btn-flat
|
||||||
|
border: #ddd 1px solid
|
||||||
|
border-radius: 0px
|
||||||
|
margin: 0px
|
||||||
|
|
||||||
|
.nav.navbar-link-text > li > a
|
||||||
font-weight: normal
|
font-weight: normal
|
||||||
font-size: 25px
|
font-size: 25px
|
||||||
letter-spacing: 2px
|
letter-spacing: 2px
|
||||||
|
@ -31,7 +125,7 @@
|
||||||
&:hover
|
&:hover
|
||||||
color: #f8e413
|
color: #f8e413
|
||||||
|
|
||||||
.navbar-link-text a:hover
|
.navbar-link-text > li > a:hover
|
||||||
background: darken($body-bg, 3%)
|
background: darken($body-bg, 3%)
|
||||||
|
|
||||||
.btn, .btn-group, .fancy-select
|
.btn, .btn-group, .fancy-select
|
||||||
|
@ -67,9 +161,6 @@
|
||||||
top: 13px
|
top: 13px
|
||||||
max-width: 140px
|
max-width: 140px
|
||||||
|
|
||||||
.nav
|
|
||||||
margin-bottom: 0
|
|
||||||
|
|
||||||
div.fancy-select
|
div.fancy-select
|
||||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25)
|
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25)
|
||||||
div.trigger
|
div.trigger
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#editor-achievement-edit-view
|
#editor-achievement-edit-view
|
||||||
|
height: 100%
|
||||||
|
|
||||||
.treema-root
|
.treema-root
|
||||||
margin: 28px 0px 20px
|
margin: 28px 0px 20px
|
||||||
|
|
||||||
|
@ -10,3 +12,9 @@
|
||||||
textarea
|
textarea
|
||||||
width: 92%
|
width: 92%
|
||||||
height: 300px
|
height: 300px
|
||||||
|
|
||||||
|
#achievement-view
|
||||||
|
min-height: 200px
|
||||||
|
position: relative
|
||||||
|
padding-left: 200px
|
||||||
|
|
||||||
|
|
6
app/styles/editor/related-achievements.sass
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#related-achievements-view
|
||||||
|
#new-achievement-button
|
||||||
|
margin-bottom: 10px
|
||||||
|
|
||||||
|
.icon-column
|
||||||
|
width: 25px
|
|
@ -77,6 +77,8 @@
|
||||||
background-color: white
|
background-color: white
|
||||||
border-radius: 4px
|
border-radius: 4px
|
||||||
|
|
||||||
|
.play-with-level-input
|
||||||
|
margin: 5px
|
||||||
|
|
||||||
|
|
||||||
#spritesheets
|
#spritesheets
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
@import "app/styles/bootstrap/variables"
|
@import "app/styles/bootstrap/variables"
|
||||||
|
|
||||||
#options-view
|
#options-view
|
||||||
.select-group
|
.select-group, .slider-group
|
||||||
display: block
|
display: block
|
||||||
min-height: 20px
|
min-height: 20px
|
||||||
margin-top: 10px
|
margin-top: 10px
|
||||||
|
@ -14,6 +14,9 @@
|
||||||
margin-right: 20px
|
margin-right: 20px
|
||||||
margin-bottom: 0
|
margin-bottom: 0
|
||||||
|
|
||||||
|
.slider
|
||||||
|
width: 200px
|
||||||
|
|
||||||
.form-group.radio-inline
|
.form-group.radio-inline
|
||||||
input
|
input
|
||||||
margin-left: 0px
|
margin-left: 0px
|
||||||
|
@ -22,6 +25,7 @@
|
||||||
.radio-inline-parent-label
|
.radio-inline-parent-label
|
||||||
padding-left: 0
|
padding-left: 0
|
||||||
|
|
||||||
|
|
||||||
#player-avatar-container
|
#player-avatar-container
|
||||||
position: relative
|
position: relative
|
||||||
margin: 0px 0px 15px 15px
|
margin: 0px 0px 15px 15px
|
||||||
|
|
|
@ -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
|
|
73
app/styles/user/user_home.sass
Normal 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
|
141
app/templates/account/account_home.jade
Normal 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.
|
|
@ -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")
|
|
21
app/templates/achievements/achievement-popup.jade
Normal 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
|
|
@ -27,26 +27,44 @@ body
|
||||||
|
|
||||||
select.language-dropdown
|
select.language-dropdown
|
||||||
|
|
||||||
if me.get('anonymous') === false
|
|
||||||
button.btn.btn-primary.navbuttontext.header-font#logout-button(data-i18n="login.log_out") Log Out
|
|
||||||
a.btn.btn-primary.navbuttontext.header-font(href=me.get('jobProfile') ? "/account/profile/#{me.id}" : "/account/settings")
|
|
||||||
div.navbuttontext-account(data-i18n="nav.account") Account
|
|
||||||
if me.get('photoURL')
|
|
||||||
img.account-settings-image(src=me.getPhotoURL(18), alt="")
|
|
||||||
else
|
|
||||||
span.glyphicon.glyphicon-user
|
|
||||||
|
|
||||||
else
|
|
||||||
button.btn.btn-primary.navbuttontext.header-font.auth-button
|
|
||||||
span(data-i18n="login.log_in") Log In
|
|
||||||
span.spr.spl /
|
|
||||||
span(data-i18n="login.sign_up") Create Account
|
|
||||||
|
|
||||||
ul(class='navbar-link-text').nav.navbar-nav.pull-right
|
ul(class='navbar-link-text').nav.navbar-nav.pull-right
|
||||||
li.play
|
li.play
|
||||||
a.header-font(href='/play', data-i18n="nav.play") Levels
|
a.header-font(href='/play', data-i18n="nav.play") Levels
|
||||||
li
|
li
|
||||||
a.header-font(href='/community', data-i18n="nav.community") Community
|
a.header-font(href='/community', data-i18n="nav.community") Community
|
||||||
|
if me.get('anonymous') === false
|
||||||
|
li.dropdown
|
||||||
|
button.btn.btn-primary.navbuttontext.header-font.dropdown-toggle(href="#", data-toggle="dropdown")
|
||||||
|
if me.get('photoURL')
|
||||||
|
img.account-settings-image(src=me.getPhotoURL(18), alt="")
|
||||||
|
else
|
||||||
|
i.glyphicon.glyphicon-user
|
||||||
|
.navbuttontext-account(data-i18n="nav.account" href="/account") Account
|
||||||
|
span.caret
|
||||||
|
ul.dropdown-menu(role="menu")
|
||||||
|
li.user-dropdown-header
|
||||||
|
span.user-level= me.level()
|
||||||
|
a(href="/user/#{me.getSlugOrID()}")
|
||||||
|
img.img-circle(src="#{me.getPhotoURL()}" alt="")
|
||||||
|
h3=me.displayName()
|
||||||
|
li.user-dropdown-body
|
||||||
|
.col-xs-4.text-center
|
||||||
|
a(href="/user/#{me.getSlugOrID()}" data-i18n="nav.profile") Profile
|
||||||
|
.col-xs-4.text-center
|
||||||
|
a(href="/user/#{me.getSlugOrID()}/stats" data-i18n="nav.stats") Stats
|
||||||
|
.col-xs-4.text-center
|
||||||
|
a.disabled(data-i18n="nav.code") Code
|
||||||
|
li.user-dropdown-footer
|
||||||
|
.pull-left
|
||||||
|
a.btn.btn-default.btn-flat(href="/account" data-i18n="nav.account") Account
|
||||||
|
.pull-right
|
||||||
|
button#logout-button.btn.btn-default.btn-flat(data-i18n="login.log_out") Log Out
|
||||||
|
else
|
||||||
|
li
|
||||||
|
button.btn.btn-primary.navbuttontext.header-font.auth-button
|
||||||
|
span(data-i18n="login.log_in") Log In
|
||||||
|
span.spr.spl /
|
||||||
|
span(data-i18n="login.sign_up") Create Account
|
||||||
|
|
||||||
block outer_content
|
block outer_content
|
||||||
#outer-content-wrapper(class=showBackground ? 'show-background' : '')
|
#outer-content-wrapper(class=showBackground ? 'show-background' : '')
|
||||||
|
@ -55,6 +73,7 @@ body
|
||||||
.main-content-area
|
.main-content-area
|
||||||
block content
|
block content
|
||||||
p If this is showing, you dun goofed
|
p If this is showing, you dun goofed
|
||||||
|
.achievement-corner
|
||||||
|
|
||||||
block footer
|
block footer
|
||||||
.footer.clearfix
|
.footer.clearfix
|
||||||
|
|
|
@ -2,7 +2,6 @@ extends /templates/base
|
||||||
|
|
||||||
block content
|
block content
|
||||||
if me.isAdmin()
|
if me.isAdmin()
|
||||||
div
|
|
||||||
ol.breadcrumb
|
ol.breadcrumb
|
||||||
li
|
li
|
||||||
a(href="/editor", data-i18n="editor.main_title") CodeCombat Editors
|
a(href="/editor", data-i18n="editor.main_title") CodeCombat Editors
|
||||||
|
@ -12,6 +11,7 @@ block content
|
||||||
| #{achievement.attributes.name}
|
| #{achievement.attributes.name}
|
||||||
|
|
||||||
button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-button Recalculate
|
button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-button Recalculate
|
||||||
|
button(data-i18n="common.delete", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#delete-button Delete
|
||||||
button(data-i18n="common.save", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#save-button Save
|
button(data-i18n="common.save", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#save-button Save
|
||||||
|
|
||||||
h3(data-i18n="achievement.edit_achievement_title") Edit Achievement
|
h3(data-i18n="achievement.edit_achievement_title") Edit Achievement
|
||||||
|
@ -20,12 +20,10 @@ block content
|
||||||
|
|
||||||
#achievement-treema
|
#achievement-treema
|
||||||
|
|
||||||
#achievement-view
|
#achievement-view.clearfix
|
||||||
|
|
||||||
hr
|
hr
|
||||||
|
|
||||||
div#error-view
|
|
||||||
|
|
||||||
else
|
else
|
||||||
.alert.alert-danger
|
.alert.alert-danger
|
||||||
span Admin only. Turn around.
|
span Admin only. Turn around.
|
||||||
|
|
|
@ -4,14 +4,14 @@ block modal-header-content
|
||||||
h3(data-i18n="editor.fork_title") Fork New Version
|
h3(data-i18n="editor.fork_title") Fork New Version
|
||||||
|
|
||||||
block modal-body-content
|
block modal-body-content
|
||||||
form#save-level-form.form
|
form.form
|
||||||
.form-group
|
.form-group
|
||||||
label(for="level-name", data-i18n="general.name") Name
|
label(for="model-name", data-i18n="general.name") Name
|
||||||
input#level-name(name="name", type="text").form-control
|
input#fork-model-name(name="name", type="text").form-control
|
||||||
|
|
||||||
block modal-footer-content
|
block modal-footer-content
|
||||||
button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel
|
button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel
|
||||||
button.btn.btn-primary#fork-level-confirm-button(data-i18n="common.save") Save
|
button.btn.btn-primary#fork-model-confirm-button(data-i18n="common.save") Save
|
||||||
|
|
||||||
block modal-body-wait-content
|
block modal-body-wait-content
|
||||||
h3(data-i18n="editor.fork_creating") Creating Fork...
|
h3(data-i18n="editor.fork_creating") Creating Fork...
|
|
@ -33,6 +33,8 @@ block header
|
||||||
- var patches = level.get('patches')
|
- var patches = level.get('patches')
|
||||||
if patches && patches.length
|
if patches && patches.length
|
||||||
span.badge= patches.length
|
span.badge= patches.length
|
||||||
|
li
|
||||||
|
a(href="#related-achievements-view", data-toggle="tab") Achievements
|
||||||
li
|
li
|
||||||
a(href="#docs-components-view", data-toggle="tab", data-i18n="editor.level_tab_docs") Documentation
|
a(href="#docs-components-view", data-toggle="tab", data-i18n="editor.level_tab_docs") Documentation
|
||||||
.navbar-header
|
.navbar-header
|
||||||
|
@ -83,7 +85,7 @@ block header
|
||||||
span.spl(data-i18n="common.unwatch") Unwatch
|
span.spl(data-i18n="common.unwatch") Unwatch
|
||||||
|
|
||||||
li(class=anonymous ? "disabled": "")
|
li(class=anonymous ? "disabled": "")
|
||||||
a(data-i18n="common.fork")#fork-level-start-button Fork
|
a(data-i18n="common.fork")#fork-start-button Fork
|
||||||
li(class=anonymous ? "disabled": "")
|
li(class=anonymous ? "disabled": "")
|
||||||
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
|
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
|
||||||
li(class=anonymous ? "disabled": "")
|
li(class=anonymous ? "disabled": "")
|
||||||
|
@ -121,6 +123,8 @@ block outer_content
|
||||||
div.tab-pane#editor-level-patches
|
div.tab-pane#editor-level-patches
|
||||||
.patches-view
|
.patches-view
|
||||||
|
|
||||||
|
div.tab-pane#related-achievements-view
|
||||||
|
|
||||||
div.tab-pane#docs-components-view
|
div.tab-pane#docs-components-view
|
||||||
|
|
||||||
div#error-view
|
div#error-view
|
||||||
|
|
23
app/templates/editor/level/modal/new-achievement.jade
Normal 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
|
26
app/templates/editor/level/related-achievements.jade
Normal 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')
|
|
@ -33,6 +33,16 @@ block header
|
||||||
span.navbar-brand #{thangType.attributes.name}
|
span.navbar-brand #{thangType.attributes.name}
|
||||||
|
|
||||||
ul.nav.navbar-nav.navbar-right
|
ul.nav.navbar-nav.navbar-right
|
||||||
|
li.dropdown
|
||||||
|
a(data-toggle='dropdown').play-with-level-parent
|
||||||
|
span.glyphicon-play.glyphicon
|
||||||
|
ul.dropdown-menu
|
||||||
|
li.dropdown-header Play Which Level?
|
||||||
|
li
|
||||||
|
for level in recentlyPlayedLevels
|
||||||
|
a.play-with-level-button(data-level=level)= level
|
||||||
|
input.play-with-level-input(placeholder="Type in a level name")
|
||||||
|
|
||||||
if authorized
|
if authorized
|
||||||
li#save-button
|
li#save-button
|
||||||
a
|
a
|
||||||
|
@ -42,6 +52,8 @@ block header
|
||||||
span.glyphicon-chevron-down.glyphicon
|
span.glyphicon-chevron-down.glyphicon
|
||||||
ul.dropdown-menu
|
ul.dropdown-menu
|
||||||
li.dropdown-header Actions
|
li.dropdown-header Actions
|
||||||
|
li(class=anonymous ? "disabled": "")
|
||||||
|
a(data-i18n="common.fork")#fork-start-button Fork
|
||||||
li(class=anonymous ? "disabled": "")
|
li(class=anonymous ? "disabled": "")
|
||||||
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
|
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert
|
||||||
li.divider
|
li.divider
|
||||||
|
|
|
@ -2,10 +2,10 @@ h3(data-i18n="play_level.reload_title") Reload All Code?
|
||||||
|
|
||||||
p(data-i18n="play_level.reload_really") Are you sure you want to reload this level back to the beginning?
|
p(data-i18n="play_level.reload_really") Are you sure you want to reload this level back to the beginning?
|
||||||
|
|
||||||
if showDevBits
|
|
||||||
p
|
p
|
||||||
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="play_level.reload_confirm").btn.btn-primary#restart-level-confirm-button Reload All
|
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="play_level.reload_confirm").btn.btn-primary#restart-level-confirm-button Reload All
|
||||||
|
|
||||||
|
if showDevBits
|
||||||
img(src="/images/pages/game-menu/choose-hero-stub.png")
|
img(src="/images/pages/game-menu/choose-hero-stub.png")
|
||||||
|
|
||||||
div(data-i18n="choose_hero.temp") Temp
|
div(data-i18n="choose_hero.temp") Temp
|
||||||
|
|
|
@ -8,6 +8,13 @@
|
||||||
.form
|
.form
|
||||||
h3(data-i18n="options.general_options") General Options
|
h3(data-i18n="options.general_options") General Options
|
||||||
|
|
||||||
|
.form-group.slider-group
|
||||||
|
label(for="option-volume")
|
||||||
|
span(data-i18n="options.volume") Volume
|
||||||
|
span.spr :
|
||||||
|
span#option-volume-value= (me.get('volume') * 100).toFixed(0) + '%'
|
||||||
|
#option-volume.slider
|
||||||
|
|
||||||
.form-group.checkbox
|
.form-group.checkbox
|
||||||
label(for="option-music")
|
label(for="option-music")
|
||||||
input#option-music(name="option-music", type="checkbox", checked=music)
|
input#option-music(name="option-music", type="checkbox", checked=music)
|
||||||
|
|
|
@ -12,32 +12,12 @@ block content
|
||||||
if me.get('anonymous')
|
if me.get('anonymous')
|
||||||
a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="modal/AuthModal", role="button", data-i18n="#{currentNewSignup}") Log in to Create a New Content
|
a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="modal/AuthModal", role="button", data-i18n="#{currentNewSignup}") Log in to Create a New Content
|
||||||
else
|
else
|
||||||
a.btn.btn-primary.open-modal-button(href='#new-model-modal', role="button", data-toggle="modal", data-i18n="#{currentNew}") Create a New Something
|
a.btn.btn-primary.open-modal-button#new-model-button(data-i18n="#{currentNew}") Create a New Something
|
||||||
input#search(data-i18n="[placeholder]#{currentSearch}")
|
input#search(data-i18n="[placeholder]#{currentSearch}")
|
||||||
hr
|
hr
|
||||||
div.results
|
div.results
|
||||||
table
|
table
|
||||||
|
|
||||||
// TODO: make this into a ModalView subview
|
|
||||||
div.modal.fade#new-model-modal
|
|
||||||
.modal-dialog
|
|
||||||
.background-wrapper
|
|
||||||
.modal-content
|
|
||||||
.modal-header
|
|
||||||
h3(data-i18n="#{currentNew}") Create New #{modelLabel}
|
|
||||||
.modal-body
|
|
||||||
form.form
|
|
||||||
.form-group
|
|
||||||
label.control-label(for="name", data-i18n="general.name") Name
|
|
||||||
input#name.form-control(name="name", type="text")
|
|
||||||
.modal-footer
|
|
||||||
button.btn(data-dismiss="modal", data-i18n="common.cancel") Cancel
|
|
||||||
button.btn.btn-primary.new-model-submit(data-i18n="common.create") Create
|
|
||||||
.modal-body.wait.secret
|
|
||||||
h3(data-i18n="play_level.tip_reticulating") Reticulating Splines...
|
|
||||||
.progress.progress-striped.active
|
|
||||||
.progress-bar
|
|
||||||
|
|
||||||
else
|
else
|
||||||
.alert.alert-danger
|
.alert.alert-danger
|
||||||
span Admin only. Turn around.
|
span Admin only. Turn around.
|
||||||
|
|
13
app/templates/kinds/user.jade
Normal 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
|
19
app/templates/modal/new_model.jade
Normal 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
|
52
app/templates/user/achievements.jade
Normal 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?
|
133
app/templates/user/user_home.jade
Normal 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
|
|
@ -1,4 +1,4 @@
|
||||||
CocoView = require 'views/kinds/CocoView'
|
RootView = require 'views/kinds/RootView'
|
||||||
ModalView = require 'views/kinds/ModalView'
|
ModalView = require 'views/kinds/ModalView'
|
||||||
template = require 'templates/demo'
|
template = require 'templates/demo'
|
||||||
requireUtils = require 'lib/requireUtils'
|
requireUtils = require 'lib/requireUtils'
|
||||||
|
@ -24,7 +24,7 @@ DEMO_URL_PREFIX = '/demo/'
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
module.exports = DemoView = class DemoView extends CocoView
|
module.exports = DemoView = class DemoView extends RootView
|
||||||
id: 'demo-view'
|
id: 'demo-view'
|
||||||
template: template
|
template: template
|
||||||
|
|
||||||
|
|
38
app/views/account/MainAccountView.coffee
Normal 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()
|
91
app/views/achievements/AchievementPopup.coffee
Normal 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()
|
43
app/views/editor/ForkModal.coffee
Normal 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})
|
|
@ -1,7 +1,10 @@
|
||||||
RootView = require 'views/kinds/RootView'
|
RootView = require 'views/kinds/RootView'
|
||||||
template = require 'templates/editor/achievement/edit'
|
template = require 'templates/editor/achievement/edit'
|
||||||
Achievement = require 'models/Achievement'
|
Achievement = require 'models/Achievement'
|
||||||
|
AchievementPopup = require 'views/achievements/AchievementPopup'
|
||||||
ConfirmModal = require 'views/modal/ConfirmModal'
|
ConfirmModal = require 'views/modal/ConfirmModal'
|
||||||
|
errors = require 'lib/errors'
|
||||||
|
app = require 'application'
|
||||||
|
|
||||||
module.exports = class AchievementEditView extends RootView
|
module.exports = class AchievementEditView extends RootView
|
||||||
id: 'editor-achievement-edit-view'
|
id: 'editor-achievement-edit-view'
|
||||||
|
@ -11,6 +14,7 @@ module.exports = class AchievementEditView extends RootView
|
||||||
events:
|
events:
|
||||||
'click #save-button': 'saveAchievement'
|
'click #save-button': 'saveAchievement'
|
||||||
'click #recalculate-button': 'confirmRecalculation'
|
'click #recalculate-button': 'confirmRecalculation'
|
||||||
|
'click #delete-button': 'confirmDeletion'
|
||||||
|
|
||||||
subscriptions:
|
subscriptions:
|
||||||
'save-new': 'saveAchievement'
|
'save-new': 'saveAchievement'
|
||||||
|
@ -20,13 +24,10 @@ module.exports = class AchievementEditView extends RootView
|
||||||
@achievement = new Achievement(_id: @achievementID)
|
@achievement = new Achievement(_id: @achievementID)
|
||||||
@achievement.saveBackups = true
|
@achievement.saveBackups = true
|
||||||
|
|
||||||
@listenToOnce(@achievement, 'error',
|
@achievement.once 'error', (achievement, jqxhr) =>
|
||||||
() =>
|
|
||||||
@hideLoading()
|
@hideLoading()
|
||||||
$(@$el).find('.main-content-area').children('*').not('#error-view').remove()
|
$(@$el).find('.main-content-area').children('*').not('.breadcrumb').remove()
|
||||||
|
errors.backboneFailure arguments...
|
||||||
@insertSubView(new ErrorView())
|
|
||||||
)
|
|
||||||
|
|
||||||
@achievement.fetch()
|
@achievement.fetch()
|
||||||
@listenToOnce(@achievement, 'sync', @buildTreema)
|
@listenToOnce(@achievement, 'sync', @buildTreema)
|
||||||
|
@ -49,17 +50,31 @@ module.exports = class AchievementEditView extends RootView
|
||||||
|
|
||||||
@treema.build()
|
@treema.build()
|
||||||
|
|
||||||
pushChangesToPreview: =>
|
|
||||||
'TODO' # TODO might want some intrinsic preview thing
|
|
||||||
|
|
||||||
getRenderData: (context={}) ->
|
getRenderData: (context={}) ->
|
||||||
context = super(context)
|
context = super(context)
|
||||||
context.achievement = @achievement
|
context.achievement = @achievement
|
||||||
context.authorized = me.isAdmin()
|
context.authorized = me.isAdmin()
|
||||||
context
|
context
|
||||||
|
|
||||||
|
afterRender: ->
|
||||||
|
super(arguments...)
|
||||||
|
@pushChangesToPreview()
|
||||||
|
|
||||||
|
pushChangesToPreview: =>
|
||||||
|
$('#achievement-view').empty()
|
||||||
|
|
||||||
|
if @treema?
|
||||||
|
for key, value of @treema.data
|
||||||
|
@achievement.set key, value
|
||||||
|
|
||||||
|
earned =
|
||||||
|
earnedPoints: @achievement.get 'worth'
|
||||||
|
|
||||||
|
popup = new AchievementPopup achievement: @achievement, earnedAchievement:earned, popup: false, container: $('#achievement-view')
|
||||||
|
|
||||||
|
|
||||||
openSaveModal: ->
|
openSaveModal: ->
|
||||||
'Maybe later' # TODO
|
'Maybe later' # TODO patch patch patch
|
||||||
|
|
||||||
saveAchievement: (e) ->
|
saveAchievement: (e) ->
|
||||||
@treema.endExistingEdits()
|
@treema.endExistingEdits()
|
||||||
|
@ -75,20 +90,31 @@ module.exports = class AchievementEditView extends RootView
|
||||||
url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}"
|
url = "/editor/achievement/#{@achievement.get('slug') or @achievement.id}"
|
||||||
document.location.href = url
|
document.location.href = url
|
||||||
|
|
||||||
confirmRecalculation: (e) ->
|
confirmRecalculation: ->
|
||||||
renderData =
|
renderData =
|
||||||
'confirmTitle': 'Are you really sure?'
|
'confirmTitle': 'Are you really sure?'
|
||||||
'confirmBody': 'This will trigger recalculation of the achievement for all users. Are you really sure you want to go down this path?'
|
'confirmBody': 'This will trigger recalculation of the achievement for all users. Are you really sure you want to go down this path?'
|
||||||
'confirmDecline': 'Not really'
|
'confirmDecline': 'Not really'
|
||||||
'confirmConfirm': 'Definitely'
|
'confirmConfirm': 'Definitely'
|
||||||
|
|
||||||
confirmModal = new ConfirmModal(renderData)
|
confirmModal = new ConfirmModal renderData
|
||||||
confirmModal.onConfirm @recalculateAchievement
|
confirmModal.on 'confirm', @recalculateAchievement
|
||||||
|
@openModalView confirmModal
|
||||||
|
|
||||||
|
confirmDeletion: ->
|
||||||
|
renderData =
|
||||||
|
'confirmTitle': 'Are you really sure?'
|
||||||
|
'confirmBody': 'This will completely delete the achievement, potentially breaking a lot of stuff you don\'t want breaking. Are you entirely sure?'
|
||||||
|
'confirmDecline': 'Not really'
|
||||||
|
'confirmConfirm': 'Definitely'
|
||||||
|
|
||||||
|
confirmModal = new ConfirmModal renderData
|
||||||
|
confirmModal.on 'confirm', @deleteAchievement
|
||||||
@openModalView confirmModal
|
@openModalView confirmModal
|
||||||
|
|
||||||
recalculateAchievement: =>
|
recalculateAchievement: =>
|
||||||
$.ajax
|
$.ajax
|
||||||
data: JSON.stringify(achievements: [@achievement.get('slug') or @achievement.get('_id')])
|
data: JSON.stringify(earnedAchievements: [@achievement.get('slug') or @achievement.get('_id')])
|
||||||
success: (data, status, jqXHR) ->
|
success: (data, status, jqXHR) ->
|
||||||
noty
|
noty
|
||||||
timeout: 5000
|
timeout: 5000
|
||||||
|
@ -105,3 +131,24 @@ module.exports = class AchievementEditView extends RootView
|
||||||
url: '/admin/earned.achievement/recalculate'
|
url: '/admin/earned.achievement/recalculate'
|
||||||
type: 'POST'
|
type: 'POST'
|
||||||
contentType: 'application/json'
|
contentType: 'application/json'
|
||||||
|
|
||||||
|
deleteAchievement: =>
|
||||||
|
console.debug 'deleting'
|
||||||
|
$.ajax
|
||||||
|
type: 'DELETE'
|
||||||
|
success: ->
|
||||||
|
noty
|
||||||
|
timeout: 5000
|
||||||
|
text: 'Aaaand it\'s gone.'
|
||||||
|
type: 'success'
|
||||||
|
layout: 'topCenter'
|
||||||
|
_.delay ->
|
||||||
|
app.router.navigate '/editor/achievement', trigger: true
|
||||||
|
, 500
|
||||||
|
error: (jqXHR, status, error) ->
|
||||||
|
console.error jqXHR
|
||||||
|
timeout: 5000
|
||||||
|
text: "Deleting achievement failed with error code #{jqXHR.status}"
|
||||||
|
type: 'error'
|
||||||
|
layout: 'topCenter'
|
||||||
|
url: "/db/achievement/#{@achievement.id}"
|
||||||
|
|
|
@ -12,9 +12,10 @@ ScriptsTabView = require './scripts/ScriptsTabView'
|
||||||
ComponentsTabView = require './components/ComponentsTabView'
|
ComponentsTabView = require './components/ComponentsTabView'
|
||||||
SystemsTabView = require './systems/SystemsTabView'
|
SystemsTabView = require './systems/SystemsTabView'
|
||||||
SaveLevelModal = require './modals/SaveLevelModal'
|
SaveLevelModal = require './modals/SaveLevelModal'
|
||||||
LevelForkView = require './modals/ForkLevelModal'
|
ForkModal = require 'views/editor/ForkModal'
|
||||||
SaveVersionModal = require 'views/modal/SaveVersionModal'
|
SaveVersionModal = require 'views/modal/SaveVersionModal'
|
||||||
PatchesView = require 'views/editor/PatchesView'
|
PatchesView = require 'views/editor/PatchesView'
|
||||||
|
RelatedAchievementsView = require 'views/editor/level/RelatedAchievementsView'
|
||||||
VersionHistoryView = require './modals/LevelVersionsModal'
|
VersionHistoryView = require './modals/LevelVersionsModal'
|
||||||
ComponentDocsView = require 'views/docs/ComponentDocumentationView'
|
ComponentDocsView = require 'views/docs/ComponentDocumentationView'
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ module.exports = class LevelEditView extends RootView
|
||||||
'click .play-with-team-button': 'onPlayLevel'
|
'click .play-with-team-button': 'onPlayLevel'
|
||||||
'click .play-with-team-parent': 'onPlayLevelTeamSelect'
|
'click .play-with-team-parent': 'onPlayLevelTeamSelect'
|
||||||
'click #commit-level-start-button': 'startCommittingLevel'
|
'click #commit-level-start-button': 'startCommittingLevel'
|
||||||
'click #fork-level-start-button': 'startForkingLevel'
|
'click #fork-start-button': 'startForking'
|
||||||
'click #level-history-button': 'showVersionHistory'
|
'click #level-history-button': 'showVersionHistory'
|
||||||
'click #undo-button': 'onUndo'
|
'click #undo-button': 'onUndo'
|
||||||
'click #redo-button': 'onRedo'
|
'click #redo-button': 'onRedo'
|
||||||
|
@ -75,7 +76,8 @@ module.exports = class LevelEditView extends RootView
|
||||||
@insertSubView new ScriptsTabView world: @world, supermodel: @supermodel, files: @files
|
@insertSubView new ScriptsTabView world: @world, supermodel: @supermodel, files: @files
|
||||||
@insertSubView new ComponentsTabView supermodel: @supermodel
|
@insertSubView new ComponentsTabView supermodel: @supermodel
|
||||||
@insertSubView new SystemsTabView supermodel: @supermodel
|
@insertSubView new SystemsTabView supermodel: @supermodel
|
||||||
@insertSubView new ComponentDocsView()
|
@insertSubView new RelatedAchievementsView supermodel: @supermodel, level: @level
|
||||||
|
@insertSubView new ComponentDocsView supermodel: @supermodel
|
||||||
|
|
||||||
Backbone.Mediator.publish 'level-loaded', level: @level
|
Backbone.Mediator.publish 'level-loaded', level: @level
|
||||||
@showReadOnly() if me.get('anonymous')
|
@showReadOnly() if me.get('anonymous')
|
||||||
|
@ -128,9 +130,8 @@ module.exports = class LevelEditView extends RootView
|
||||||
@openModalView new SaveLevelModal level: @level, supermodel: @supermodel
|
@openModalView new SaveLevelModal level: @level, supermodel: @supermodel
|
||||||
Backbone.Mediator.publish 'level:view-switched', e
|
Backbone.Mediator.publish 'level:view-switched', e
|
||||||
|
|
||||||
startForkingLevel: (e) ->
|
startForking: (e) ->
|
||||||
levelForkView = new LevelForkView level: @level
|
@openModalView new ForkModal model: @level, editorPath: 'level'
|
||||||
@openModalView levelForkView
|
|
||||||
Backbone.Mediator.publish 'level:view-switched', e
|
Backbone.Mediator.publish 'level:view-switched', e
|
||||||
|
|
||||||
showVersionHistory: (e) ->
|
showVersionHistory: (e) ->
|
||||||
|
|
40
app/views/editor/level/RelatedAchievementsView.coffee
Normal 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
|
|
@ -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})
|
|
54
app/views/editor/level/modals/NewAchievementModal.coffee
Normal 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
|
|
@ -10,8 +10,10 @@ ThangComponentsEditView = require 'views/editor/component/ThangComponentsEditVie
|
||||||
ThangTypeVersionsModal = require './ThangTypeVersionsModal'
|
ThangTypeVersionsModal = require './ThangTypeVersionsModal'
|
||||||
ThangTypeColorsTabView = require './ThangTypeColorsTabView'
|
ThangTypeColorsTabView = require './ThangTypeColorsTabView'
|
||||||
PatchesView = require 'views/editor/PatchesView'
|
PatchesView = require 'views/editor/PatchesView'
|
||||||
|
ForkModal = require 'views/editor/ForkModal'
|
||||||
SaveVersionModal = require 'views/modal/SaveVersionModal'
|
SaveVersionModal = require 'views/modal/SaveVersionModal'
|
||||||
template = require 'templates/editor/thang/thang-type-edit-view'
|
template = require 'templates/editor/thang/thang-type-edit-view'
|
||||||
|
storage = require 'lib/storage'
|
||||||
|
|
||||||
CENTER = {x: 200, y: 300}
|
CENTER = {x: 200, y: 300}
|
||||||
|
|
||||||
|
@ -35,8 +37,12 @@ module.exports = class ThangTypeEditView extends RootView
|
||||||
'click #marker-button': 'toggleDots'
|
'click #marker-button': 'toggleDots'
|
||||||
'click #end-button': 'endAnimation'
|
'click #end-button': 'endAnimation'
|
||||||
'click #history-button': 'showVersionHistory'
|
'click #history-button': 'showVersionHistory'
|
||||||
|
'click #fork-start-button': 'startForking'
|
||||||
'click #save-button': 'openSaveModal'
|
'click #save-button': 'openSaveModal'
|
||||||
'click #patches-tab': -> @patchesView.load()
|
'click #patches-tab': -> @patchesView.load()
|
||||||
|
'click .play-with-level-button': 'onPlayLevel'
|
||||||
|
'click .play-with-level-parent': 'onPlayLevelSelect'
|
||||||
|
'keyup .play-with-level-input': 'onPlayLevelKeyUp'
|
||||||
|
|
||||||
subscriptions:
|
subscriptions:
|
||||||
'save-new-version': 'saveNewThangType'
|
'save-new-version': 'saveNewThangType'
|
||||||
|
@ -58,6 +64,7 @@ module.exports = class ThangTypeEditView extends RootView
|
||||||
context.thangType = @thangType
|
context.thangType = @thangType
|
||||||
context.animations = @getAnimationNames()
|
context.animations = @getAnimationNames()
|
||||||
context.authorized = not me.get('anonymous')
|
context.authorized = not me.get('anonymous')
|
||||||
|
context.recentlyPlayedLevels = storage.load('recently-played-levels') ? ['items']
|
||||||
context
|
context
|
||||||
|
|
||||||
getAnimationNames: ->
|
getAnimationNames: ->
|
||||||
|
@ -401,12 +408,46 @@ module.exports = class ThangTypeEditView extends RootView
|
||||||
@showingSelectedNode = false
|
@showingSelectedNode = false
|
||||||
|
|
||||||
showVersionHistory: (e) ->
|
showVersionHistory: (e) ->
|
||||||
versionHistoryModal = new ThangTypeVersionsModal thangType: @thangType, @thangTypeID
|
@openModalView new ThangTypeVersionsModal thangType: @thangType, @thangTypeID
|
||||||
@openModalView versionHistoryModal
|
|
||||||
Backbone.Mediator.publish 'level:view-switched', e
|
|
||||||
|
|
||||||
openSaveModal: ->
|
openSaveModal: ->
|
||||||
@openModalView(new SaveVersionModal({model: @thangType}))
|
@openModalView new SaveVersionModal model: @thangType
|
||||||
|
|
||||||
|
startForking: (e) ->
|
||||||
|
@openModalView new ForkModal model: @thangType, editorPath: 'thang'
|
||||||
|
|
||||||
|
onPlayLevelSelect: (e) ->
|
||||||
|
if @childWindow and not @childWindow.closed
|
||||||
|
# We already have a child window open, so we don't need to ask for a level; we'll use its existing level.
|
||||||
|
e.stopImmediatePropagation()
|
||||||
|
@onPlayLevel e
|
||||||
|
_.defer -> $('.play-with-level-input').focus()
|
||||||
|
|
||||||
|
onPlayLevelKeyUp: (e) ->
|
||||||
|
return unless e.keyCode is 13 # return
|
||||||
|
input = @$el.find('.play-with-level-input')
|
||||||
|
input.parents('.dropdown').find('.play-with-level-parent').dropdown('toggle')
|
||||||
|
level = _.string.slugify input.val()
|
||||||
|
return unless level
|
||||||
|
@onPlayLevel null, level
|
||||||
|
recentlyPlayedLevels = storage.load('recently-played-levels') ? []
|
||||||
|
recentlyPlayedLevels.push level
|
||||||
|
storage.save 'recently-played-levels', recentlyPlayedLevels
|
||||||
|
|
||||||
|
onPlayLevel: (e, level=null) ->
|
||||||
|
level ?= $(e.target).data('level')
|
||||||
|
level = _.string.slugify level
|
||||||
|
if @childWindow and not @childWindow.closed
|
||||||
|
# Reset the LevelView's world, but leave the rest of the state alone
|
||||||
|
@childWindow.Backbone.Mediator.publish 'level-reload-thang-type', thangType: @thangType
|
||||||
|
else
|
||||||
|
# Create a new Window with a blank LevelView
|
||||||
|
scratchLevelID = level + '?dev=true'
|
||||||
|
if me.get('name') is 'Nick'
|
||||||
|
@childWindow = window.open("/play/level/#{scratchLevelID}", 'child_window', 'width=2560,height=1080,left=0,top=-1600,location=1,menubar=1,scrollbars=1,status=0,titlebar=1,toolbar=1', true)
|
||||||
|
else
|
||||||
|
@childWindow = window.open("/play/level/#{scratchLevelID}", 'child_window', 'width=1024,height=560,left=10,top=10,location=0,menubar=0,scrollbars=0,status=0,titlebar=0,toolbar=0', true)
|
||||||
|
@childWindow.focus()
|
||||||
|
|
||||||
destroy: ->
|
destroy: ->
|
||||||
@camera?.destroy()
|
@camera?.destroy()
|
||||||
|
|
|
@ -48,6 +48,20 @@ module.exports = class OptionsView extends CocoView
|
||||||
|
|
||||||
afterRender: ->
|
afterRender: ->
|
||||||
super()
|
super()
|
||||||
|
@volumeSlider = @$el.find('#option-volume').slider(animate: 'fast', min: 0, max: 1, step: 0.05)
|
||||||
|
@volumeSlider.slider('value', me.get('volume'))
|
||||||
|
@volumeSlider.on('slide', @onVolumeSliderChange)
|
||||||
|
@volumeSlider.on('slidechange', @onVolumeSliderChange)
|
||||||
|
|
||||||
|
destroy: ->
|
||||||
|
@volumeSlider?.slider?('destroy')
|
||||||
|
super()
|
||||||
|
|
||||||
|
onVolumeSliderChange: (e) =>
|
||||||
|
volume = @volumeSlider.slider('value')
|
||||||
|
me.set 'volume', volume
|
||||||
|
@$el.find('#option-volume-value').text (volume * 100).toFixed(0) + '%'
|
||||||
|
Backbone.Mediator.publish 'level-set-volume', volume: volume
|
||||||
|
|
||||||
onHidden: ->
|
onHidden: ->
|
||||||
if @playerName and @playerName isnt me.get('name')
|
if @playerName and @playerName isnt me.get('name')
|
||||||
|
|
|
@ -6,8 +6,9 @@ CocoView = require './CocoView'
|
||||||
{logoutUser, me} = require('lib/auth')
|
{logoutUser, me} = require('lib/auth')
|
||||||
locale = require 'locale/locale'
|
locale = require 'locale/locale'
|
||||||
|
|
||||||
Achievement = require '../../models/Achievement'
|
AchievementPopup = require 'views/achievements/AchievementPopup'
|
||||||
User = require '../../models/User'
|
utils = require 'lib/utils'
|
||||||
|
|
||||||
# TODO remove
|
# TODO remove
|
||||||
|
|
||||||
filterKeyboardEvents = (allowedEvents, func) ->
|
filterKeyboardEvents = (allowedEvents, func) ->
|
||||||
|
@ -32,61 +33,13 @@ module.exports = class RootView extends CocoView
|
||||||
'achievements:new': 'handleNewAchievements'
|
'achievements:new': 'handleNewAchievements'
|
||||||
|
|
||||||
showNewAchievement: (achievement, earnedAchievement) ->
|
showNewAchievement: (achievement, earnedAchievement) ->
|
||||||
currentLevel = me.level()
|
popup = new AchievementPopup achievement: achievement, earnedAchievement: earnedAchievement
|
||||||
nextLevel = currentLevel + 1
|
|
||||||
currentLevelExp = User.expForLevel(currentLevel)
|
|
||||||
nextLevelExp = User.expForLevel(nextLevel)
|
|
||||||
totalExpNeeded = nextLevelExp - currentLevelExp
|
|
||||||
expFunction = achievement.getExpFunction()
|
|
||||||
currentExp = me.get('points')
|
|
||||||
previousExp = currentExp - achievement.get('worth')
|
|
||||||
previousExp = expFunction(earnedAchievement.get('previouslyAchievedAmount')) * achievement.get('worth') if achievement.isRepeatable()
|
|
||||||
achievedExp = currentExp - previousExp
|
|
||||||
leveledUp = currentExp - achievedExp < currentLevelExp
|
|
||||||
alreadyAchievedPercentage = 100 * (previousExp - currentLevelExp) / totalExpNeeded
|
|
||||||
newlyAchievedPercentage = if leveledUp then 100 * (currentExp - currentLevelExp) / totalExpNeeded else 100 * achievedExp / totalExpNeeded
|
|
||||||
|
|
||||||
console.debug "Current level is #{currentLevel} (#{currentLevelExp} xp), next level is #{nextLevel} (#{nextLevelExp} xp)."
|
|
||||||
console.debug "Need a total of #{nextLevelExp - currentLevelExp}, already had #{previousExp} and just now earned #{achievedExp} totalling on #{currentExp}"
|
|
||||||
|
|
||||||
alreadyAchievedBar = $("<div class='progress-bar progress-bar-warning' style='width:#{alreadyAchievedPercentage}%'></div>")
|
|
||||||
newlyAchievedBar = $("<div data-toggle='tooltip' class='progress-bar progress-bar-success' style='width:#{newlyAchievedPercentage}%'></div>")
|
|
||||||
emptyBar = $("<div data-toggle='tooltip' class='progress-bar progress-bar-white' style='width:#{100 - newlyAchievedPercentage - alreadyAchievedPercentage}%'></div>")
|
|
||||||
progressBar = $('<div class="progress" data-toggle="tooltip"></div>').append(alreadyAchievedBar).append(newlyAchievedBar).append(emptyBar)
|
|
||||||
message = if (currentLevel isnt 1) and leveledUp then "Reached level #{currentLevel}!" else null
|
|
||||||
|
|
||||||
alreadyAchievedBar.tooltip(title: "#{currentExp} XP in total")
|
|
||||||
newlyAchievedBar.tooltip(title: "#{achievedExp} XP earned")
|
|
||||||
emptyBar.tooltip(title: "#{nextLevelExp - currentExp} XP until level #{nextLevel}")
|
|
||||||
|
|
||||||
# TODO a default should be linked here
|
|
||||||
imageURL = '/file/' + achievement.get('icon')
|
|
||||||
data =
|
|
||||||
title: achievement.get('name')
|
|
||||||
image: $("<img src='#{imageURL}' />")
|
|
||||||
description: achievement.get('description')
|
|
||||||
progressBar: progressBar
|
|
||||||
earnedExp: "+ #{achievedExp} XP"
|
|
||||||
message: message
|
|
||||||
|
|
||||||
options =
|
|
||||||
autoHideDelay: 10000
|
|
||||||
globalPosition: 'bottom right'
|
|
||||||
showDuration: 400
|
|
||||||
style: 'achievement'
|
|
||||||
autoHide: true
|
|
||||||
clickToHide: true
|
|
||||||
|
|
||||||
$.notify( data, options )
|
|
||||||
|
|
||||||
handleNewAchievements: (earnedAchievements) ->
|
handleNewAchievements: (earnedAchievements) ->
|
||||||
_.each(earnedAchievements.models, (earnedAchievement) =>
|
_.each earnedAchievements.models, (earnedAchievement) =>
|
||||||
achievement = new Achievement(_id: earnedAchievement.get('achievement'))
|
achievement = new Achievement(_id: earnedAchievement.get('achievement'))
|
||||||
console.log achievement
|
achievement.fetch
|
||||||
achievement.fetch(
|
|
||||||
success: (achievement) => @showNewAchievement(achievement, earnedAchievement)
|
success: (achievement) => @showNewAchievement(achievement, earnedAchievement)
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
logoutAccount: ->
|
logoutAccount: ->
|
||||||
logoutUser($('#login-email').val())
|
logoutUser($('#login-email').val())
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
RootView = require 'views/kinds/RootView'
|
RootView = require 'views/kinds/RootView'
|
||||||
|
NewModelModal = require 'views/modal/NewModelModal'
|
||||||
template = require 'templates/kinds/search'
|
template = require 'templates/kinds/search'
|
||||||
forms = require 'lib/forms'
|
|
||||||
app = require 'application'
|
app = require 'application'
|
||||||
|
|
||||||
class SearchCollection extends Backbone.Collection
|
class SearchCollection extends Backbone.Collection
|
||||||
|
@ -26,9 +26,7 @@ module.exports = class SearchView extends RootView
|
||||||
events:
|
events:
|
||||||
'change input#search': 'runSearch'
|
'change input#search': 'runSearch'
|
||||||
'keydown input#search': 'runSearch'
|
'keydown input#search': 'runSearch'
|
||||||
'click button.new-model-submit': 'makeNewModel'
|
'click #new-model-button': 'newModel'
|
||||||
'submit form': 'makeNewModel'
|
|
||||||
'shown.bs.modal #new-model-modal': 'focusOnName'
|
|
||||||
'hidden.bs.modal #new-model-modal': 'onModalHidden'
|
'hidden.bs.modal #new-model-modal': 'onModalHidden'
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
|
@ -79,31 +77,11 @@ module.exports = class SearchView extends RootView
|
||||||
@collection.off()
|
@collection.off()
|
||||||
@collection = null
|
@collection = null
|
||||||
|
|
||||||
makeNewModel: (e) ->
|
onNewModelSaved: (@model) ->
|
||||||
e.preventDefault()
|
|
||||||
name = @$el.find('#name').val()
|
|
||||||
model = new @model()
|
|
||||||
model.set('name', name)
|
|
||||||
if @model.schema.properties.permissions
|
|
||||||
model.set 'permissions', [{access: 'owner', target: me.id}]
|
|
||||||
res = model.save()
|
|
||||||
return unless res
|
|
||||||
|
|
||||||
modal = @$el.find('#new-model-modal')
|
|
||||||
forms.clearFormAlerts(modal)
|
|
||||||
@showLoading(modal.find('.modal-body'))
|
|
||||||
res.error =>
|
|
||||||
@hideLoading()
|
|
||||||
forms.applyErrorsToForm(modal, JSON.parse(res.responseText))
|
|
||||||
that = @
|
|
||||||
res.success ->
|
|
||||||
that.model = model
|
|
||||||
modal.modal('hide')
|
|
||||||
|
|
||||||
onModalHidden: ->
|
|
||||||
# Can only redirect after the modal hidden event has triggered
|
|
||||||
base = document.location.pathname[1..] + '/'
|
base = document.location.pathname[1..] + '/'
|
||||||
app.router.navigate(base + (@model.get('slug') or @model.id), {trigger: true})
|
app.router.navigate(base + (@model.get('slug') or @model.id), {trigger: true})
|
||||||
|
|
||||||
focusOnName: ->
|
newModel: (e) ->
|
||||||
@$el.find('#name').focus()
|
modal = new NewModelModal model: @model, modelLabel: @modelLabel
|
||||||
|
modal.once 'success', @onNewModelSaved
|
||||||
|
@openModalView modal
|
||||||
|
|
35
app/views/kinds/UserView.coffee
Normal 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()
|
|
@ -8,8 +8,8 @@ module.exports = class ConfirmModal extends ModalView
|
||||||
closeOnConfirm: true
|
closeOnConfirm: true
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click #decline-button': 'doDecline'
|
'click #decline-button': 'onDecline'
|
||||||
'click #confirm-button': 'doConfirm'
|
'click #confirm-button': 'onConfirm'
|
||||||
|
|
||||||
constructor: (@renderData={}, options={}) ->
|
constructor: (@renderData={}, options={}) ->
|
||||||
super(options)
|
super(options)
|
||||||
|
@ -21,10 +21,6 @@ module.exports = class ConfirmModal extends ModalView
|
||||||
|
|
||||||
setRenderData: (@renderData) ->
|
setRenderData: (@renderData) ->
|
||||||
|
|
||||||
onDecline: (@decline) ->
|
onDecline: -> @trigger 'decline'
|
||||||
|
|
||||||
onConfirm: (@confirm) ->
|
onConfirm: -> @trigger 'confirm'
|
||||||
|
|
||||||
doConfirm: -> @confirm() if @confirm
|
|
||||||
|
|
||||||
doDecline: -> @decline() if @decline
|
|
||||||
|
|
54
app/views/modal/NewModelModal.coffee
Normal 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
|
|
@ -193,35 +193,7 @@ module.exports = class MainPlayView extends RootView
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
playerCreated = [
|
classicAlgorithms = [
|
||||||
{
|
|
||||||
name: 'Extra Extrapolation'
|
|
||||||
difficulty: 2
|
|
||||||
id: 'extra-extrapolation'
|
|
||||||
image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png'
|
|
||||||
description: 'Predict your target\'s position for deadly aim. - by Sootn'
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name: 'The Right Route'
|
|
||||||
difficulty: 1
|
|
||||||
id: 'the-right-route'
|
|
||||||
image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png'
|
|
||||||
description: 'Strike at the weak point in an array of enemies. - by Aftermath'
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name: 'Sword Loop'
|
|
||||||
difficulty: 2
|
|
||||||
id: 'sword-loop'
|
|
||||||
image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png'
|
|
||||||
description: 'Kill the ogres and save the peasants with for-loops. - by Prabh Simran Singh Baweja'
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name: 'Coin Mania'
|
|
||||||
difficulty: 2
|
|
||||||
id: 'coin-mania'
|
|
||||||
image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png'
|
|
||||||
description: 'Learn while-loops to grab coins and potions. - by Prabh Simran Singh Baweja'
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
name: 'Bubble Sort Bootcamp Battle'
|
name: 'Bubble Sort Bootcamp Battle'
|
||||||
difficulty: 3
|
difficulty: 3
|
||||||
|
@ -257,6 +229,37 @@ module.exports = class MainPlayView extends RootView
|
||||||
image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
|
image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
|
||||||
description: 'Learn Quicksort while sorting a spiral of ogres! - by Alexandru Caciulescu'
|
description: 'Learn Quicksort while sorting a spiral of ogres! - by Alexandru Caciulescu'
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
playerCreated = [
|
||||||
|
{
|
||||||
|
name: 'Extra Extrapolation'
|
||||||
|
difficulty: 2
|
||||||
|
id: 'extra-extrapolation'
|
||||||
|
image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png'
|
||||||
|
description: 'Predict your target\'s position for deadly aim. - by Sootn'
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name: 'The Right Route'
|
||||||
|
difficulty: 1
|
||||||
|
id: 'the-right-route'
|
||||||
|
image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png'
|
||||||
|
description: 'Strike at the weak point in an array of enemies. - by Aftermath'
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name: 'Sword Loop'
|
||||||
|
difficulty: 2
|
||||||
|
id: 'sword-loop'
|
||||||
|
image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png'
|
||||||
|
description: 'Kill the ogres and save the peasants with for-loops. - by Prabh Simran Singh Baweja'
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name: 'Coin Mania'
|
||||||
|
difficulty: 2
|
||||||
|
id: 'coin-mania'
|
||||||
|
image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png'
|
||||||
|
description: 'Learn while-loops to grab coins and potions. - by Prabh Simran Singh Baweja'
|
||||||
|
}
|
||||||
{
|
{
|
||||||
name: 'Find the Spy'
|
name: 'Find the Spy'
|
||||||
difficulty: 2
|
difficulty: 2
|
||||||
|
@ -291,6 +294,7 @@ module.exports = class MainPlayView extends RootView
|
||||||
{id: 'beginner', name: 'Beginner Campaign', description: '... in which you learn the wizardry of programming.', levels: tutorials}
|
{id: 'beginner', name: 'Beginner Campaign', description: '... in which you learn the wizardry of programming.', levels: tutorials}
|
||||||
{id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas}
|
{id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas}
|
||||||
{id: 'dev', name: 'Random Harder Levels', description: '... in which you learn the interface while doing something a little harder.', levels: experienced}
|
{id: 'dev', name: 'Random Harder Levels', description: '... in which you learn the interface while doing something a little harder.', levels: experienced}
|
||||||
|
{id: 'classic' ,name: 'Classic Algorithms', description: '... in which you learn the most popular algorithms in Computer Science.', levels: classicAlgorithms}
|
||||||
{id: 'player_created', name: 'Player-Created', description: '... in which you battle against the creativity of your fellow <a href=\"/contribute#artisan\">Artisan Wizards</a>.', levels: playerCreated}
|
{id: 'player_created', name: 'Player-Created', description: '... in which you battle against the creativity of your fellow <a href=\"/contribute#artisan\">Artisan Wizards</a>.', levels: playerCreated}
|
||||||
]
|
]
|
||||||
context.levelStatusMap = @levelStatusMap
|
context.levelStatusMap = @levelStatusMap
|
||||||
|
|
|
@ -55,6 +55,7 @@ module.exports = class PlayLevelView extends RootView
|
||||||
'god:new-world-created': 'onNewWorld'
|
'god:new-world-created': 'onNewWorld'
|
||||||
'god:infinite-loop': 'onInfiniteLoop'
|
'god:infinite-loop': 'onInfiniteLoop'
|
||||||
'level-reload-from-data': 'onLevelReloadFromData'
|
'level-reload-from-data': 'onLevelReloadFromData'
|
||||||
|
'level-reload-thang-type': 'onLevelReloadThangType'
|
||||||
'play-next-level': 'onPlayNextLevel'
|
'play-next-level': 'onPlayNextLevel'
|
||||||
'edit-wizard-settings': 'showWizardSettingsModal'
|
'edit-wizard-settings': 'showWizardSettingsModal'
|
||||||
'surface:world-set-up': 'onSurfaceSetUpNewWorld'
|
'surface:world-set-up': 'onSurfaceSetUpNewWorld'
|
||||||
|
@ -326,6 +327,15 @@ module.exports = class PlayLevelView extends RootView
|
||||||
@scriptManager.setScripts(e.level.get('scripts'))
|
@scriptManager.setScripts(e.level.get('scripts'))
|
||||||
Backbone.Mediator.publish 'tome:cast-spell' # a bit hacky
|
Backbone.Mediator.publish 'tome:cast-spell' # a bit hacky
|
||||||
|
|
||||||
|
onLevelReloadThangType: (e) ->
|
||||||
|
tt = e.thangType
|
||||||
|
for url, model of @supermodel.models
|
||||||
|
if model.id is tt.id
|
||||||
|
for key, val of tt.attributes
|
||||||
|
model.attributes[key] = val
|
||||||
|
break
|
||||||
|
Backbone.Mediator.publish 'tome:cast-spell'
|
||||||
|
|
||||||
onWindowResize: (s...) ->
|
onWindowResize: (s...) ->
|
||||||
$('#pointer').css('opacity', 0.0)
|
$('#pointer').css('opacity', 0.0)
|
||||||
|
|
||||||
|
|
56
app/views/user/AchievementsView.coffee
Normal 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
|
|
@ -1,4 +1,4 @@
|
||||||
RootView = require 'views/kinds/RootView'
|
UserView = require 'views/kinds/UserView'
|
||||||
template = require 'templates/account/profile'
|
template = require 'templates/account/profile'
|
||||||
User = require 'models/User'
|
User = require 'models/User'
|
||||||
LevelSession = require 'models/LevelSession'
|
LevelSession = require 'models/LevelSession'
|
||||||
|
@ -26,7 +26,7 @@ adminContacts = [
|
||||||
{id: '52a57252a89409700d0000d9', name: 'Ignore'}
|
{id: '52a57252a89409700d0000d9', name: 'Ignore'}
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports = class JobProfileView extends RootView
|
module.exports = class JobProfileView extends UserView
|
||||||
id: 'profile-view'
|
id: 'profile-view'
|
||||||
template: template
|
template: template
|
||||||
showBackground: false
|
showBackground: false
|
||||||
|
@ -54,8 +54,7 @@ module.exports = class JobProfileView extends RootView
|
||||||
'change #admin-contact': 'onAdminContactChanged'
|
'change #admin-contact': 'onAdminContactChanged'
|
||||||
'click .session-link': 'onSessionLinkPressed'
|
'click .session-link': 'onSessionLinkPressed'
|
||||||
|
|
||||||
constructor: (options, @userID) ->
|
constructor: (userID, options) ->
|
||||||
@userID ?= me.id
|
|
||||||
@onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000
|
@onJobProfileNotesChanged = _.debounce @onJobProfileNotesChanged, 1000
|
||||||
@onRemarkChanged = _.debounce @onRemarkChanged, 1000
|
@onRemarkChanged = _.debounce @onRemarkChanged, 1000
|
||||||
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
|
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
|
||||||
|
@ -64,32 +63,19 @@ module.exports = class JobProfileView extends RootView
|
||||||
window.contractCallback = =>
|
window.contractCallback = =>
|
||||||
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
|
@authorizedWithLinkedIn = IN?.User?.isAuthorized()
|
||||||
@render()
|
@render()
|
||||||
super options
|
super options, userID
|
||||||
if me.get('anonymous') is true
|
|
||||||
@render()
|
onUserLoaded: ->
|
||||||
return
|
|
||||||
if User.isObjectID @userID
|
|
||||||
@finishInit()
|
|
||||||
else
|
|
||||||
$.ajax "/db/user/#{@userID}/nameToID", success: (@userID) =>
|
|
||||||
@finishInit() unless @destroyed
|
@finishInit() unless @destroyed
|
||||||
@render()
|
super()
|
||||||
|
|
||||||
finishInit: ->
|
finishInit: ->
|
||||||
return unless @userID
|
return unless @userID
|
||||||
@uploadFilePath = "db/user/#{@userID}"
|
@uploadFilePath = "db/user/#{@userID}"
|
||||||
@highlightedContainers = []
|
@highlightedContainers = []
|
||||||
if @userID is me.id
|
if me.isAdmin() or 'employer' in me.get('permissions')
|
||||||
@user = me
|
|
||||||
else if me.isAdmin() or 'employer' in me.get('permissions')
|
|
||||||
@user = User.getByID(@userID)
|
|
||||||
@user.fetch()
|
|
||||||
@listenTo @user, 'sync', =>
|
|
||||||
@render()
|
|
||||||
$.post "/db/user/#{me.id}/track/view_candidate"
|
$.post "/db/user/#{me.id}/track/view_candidate"
|
||||||
$.post "/db/user/#{@userID}/track/viewed_by_employer" unless me.isAdmin()
|
$.post "/db/user/#{@userID}/track/viewed_by_employer" unless me.isAdmin()
|
||||||
else
|
|
||||||
@user = User.getByID(@userID)
|
|
||||||
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(@userID), 'candidate_sessions').model
|
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(@userID), 'candidate_sessions').model
|
||||||
if me.isAdmin()
|
if me.isAdmin()
|
||||||
# Mimicking how the VictoryModal fetches LevelFeedback
|
# Mimicking how the VictoryModal fetches LevelFeedback
|
||||||
|
@ -248,7 +234,7 @@ module.exports = class JobProfileView extends RootView
|
||||||
jobProfile.name ?= (@user.get('firstName') + ' ' + @user.get('lastName')).trim() if @user?.get('firstName')
|
jobProfile.name ?= (@user.get('firstName') + ' ' + @user.get('lastName')).trim() if @user?.get('firstName')
|
||||||
context.profile = jobProfile
|
context.profile = jobProfile
|
||||||
context.user = @user
|
context.user = @user
|
||||||
context.myProfile = @user?.id is context.me.id
|
context.myProfile = @isMe()
|
||||||
context.allowedToViewJobProfile = @user and (me.isAdmin() or 'employer' in me.get('permissions') or (context.myProfile && !me.get('anonymous')))
|
context.allowedToViewJobProfile = @user and (me.isAdmin() or 'employer' in me.get('permissions') or (context.myProfile && !me.get('anonymous')))
|
||||||
context.allowedToEditJobProfile = @user and (me.isAdmin() or (context.myProfile && !me.get('anonymous')))
|
context.allowedToEditJobProfile = @user and (me.isAdmin() or (context.myProfile && !me.get('anonymous')))
|
||||||
context.profileApproved = @user?.get 'jobProfileApproved'
|
context.profileApproved = @user?.get 'jobProfileApproved'
|
||||||
|
@ -289,7 +275,7 @@ module.exports = class JobProfileView extends RootView
|
||||||
_.delay ->
|
_.delay ->
|
||||||
justSavedSection.removeClass 'just-saved', duration: 1500, easing: 'easeOutQuad'
|
justSavedSection.removeClass 'just-saved', duration: 1500, easing: 'easeOutQuad'
|
||||||
, 500
|
, 500
|
||||||
if me.isAdmin()
|
if me.isAdmin() and @user
|
||||||
visibleSettings = ['history', 'tasks']
|
visibleSettings = ['history', 'tasks']
|
||||||
data = _.pick (@remark.attributes), (value, key) -> key in visibleSettings
|
data = _.pick (@remark.attributes), (value, key) -> key in visibleSettings
|
||||||
data.history ?= []
|
data.history ?= []
|
54
app/views/user/MainUserView.coffee
Normal 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()
|
|
@ -32,7 +32,7 @@
|
||||||
"firepad": "~0.1.2",
|
"firepad": "~0.1.2",
|
||||||
"marked": "~0.3.0",
|
"marked": "~0.3.0",
|
||||||
"moment": "~2.5.0",
|
"moment": "~2.5.0",
|
||||||
"aether": "~0.2.22",
|
"aether": "~0.2.28",
|
||||||
"underscore.string": "~2.3.3",
|
"underscore.string": "~2.3.3",
|
||||||
"firebase": "~1.0.2",
|
"firebase": "~1.0.2",
|
||||||
"catiline": "~2.9.3",
|
"catiline": "~2.9.3",
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
"jsondiffpatch": "~0.1.5",
|
"jsondiffpatch": "~0.1.5",
|
||||||
"nanoscroller": "~0.8.0",
|
"nanoscroller": "~0.8.0",
|
||||||
"jquery.tablesorter": "~2.15.13",
|
"jquery.tablesorter": "~2.15.13",
|
||||||
"treema": "~0.0.12",
|
"treema": "~0.0.14",
|
||||||
"bootstrap": "~3.1.1",
|
"bootstrap": "~3.1.1",
|
||||||
"validated-backbone-mediator": "~0.1.3",
|
"validated-backbone-mediator": "~0.1.3",
|
||||||
"jquery.browser": "~0.0.6",
|
"jquery.browser": "~0.0.6",
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
"winston": "0.6.x",
|
"winston": "0.6.x",
|
||||||
"passport": "0.1.x",
|
"passport": "0.1.x",
|
||||||
"passport-local": "0.1.x",
|
"passport-local": "0.1.x",
|
||||||
|
"moment": "~2.5.0",
|
||||||
"mongodb": "1.2.x",
|
"mongodb": "1.2.x",
|
||||||
"mongoose": "3.8.x",
|
"mongoose": "3.8.x",
|
||||||
"mongoose-text-search": "~0.0.2",
|
"mongoose-text-search": "~0.0.2",
|
||||||
|
@ -65,7 +66,7 @@
|
||||||
"redis": "",
|
"redis": "",
|
||||||
"webworker-threads": "~0.4.11",
|
"webworker-threads": "~0.4.11",
|
||||||
"node-gyp": "~0.13.0",
|
"node-gyp": "~0.13.0",
|
||||||
"aether": "~0.2.22",
|
"aether": "~0.2.28",
|
||||||
"JASON": "~0.1.3",
|
"JASON": "~0.1.3",
|
||||||
"JQDeferred": "~2.1.0"
|
"JQDeferred": "~2.1.0"
|
||||||
},
|
},
|
||||||
|
|
BIN
public/images/achievements/achievement_background.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
public/images/achievements/achievement_background_light.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
public/images/achievements/achievement_background_locked.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
public/images/achievements/bar_border.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/images/achievements/border_diamond.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
public/images/achievements/border_diamond_locked.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/images/achievements/border_gold.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/images/achievements/border_gold_locked.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/achievements/border_silver.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/achievements/border_silver_locked.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/achievements/border_stone.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/images/achievements/border_stone_locked.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
public/images/achievements/border_wood.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/images/achievements/border_wood_locked.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
public/images/achievements/cross-01.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
public/images/achievements/cup-01.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
public/images/achievements/cup-02.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
public/images/achievements/default.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
public/images/achievements/level-bg.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
public/images/achievements/message-01.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
public/images/achievements/patch-01.png
Normal file
After Width: | Height: | Size: 6.9 KiB |