diff --git a/app/assets/javascripts/mock-ajax.js b/app/assets/javascripts/mock-ajax.js index d622aad13..966033263 100644 --- a/app/assets/javascripts/mock-ajax.js +++ b/app/assets/javascripts/mock-ajax.js @@ -235,6 +235,10 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. this.at = function(index) { return requests[index]; }; + + this.all = function() { + return requests.slice(0); + }; this.filter = function(url_to_match) { if (requests.length == 0) return []; diff --git a/app/templates/account/profile.jade b/app/templates/account/profile.jade index 639d14d84..de9dcca11 100644 --- a/app/templates/account/profile.jade +++ b/app/templates/account/profile.jade @@ -43,7 +43,9 @@ block content i.icon-eye-close span(data-i18n='account_profile.not_featured') Not Featured if me.isAdmin() && !myProfile - button.btn.edit-settings-button#enter-espionage-mode 007 + button.btn#enter-espionage-mode 007 + if me.isAdmin() + button.btn#open-model-modal Raw if profile && allowedToViewJobProfile div(class="job-profile-container" + (editing ? " editable-profile" : "")) diff --git a/app/templates/modal/model.jade b/app/templates/modal/model.jade index 4fae4c294..404dfb571 100644 --- a/app/templates/modal/model.jade +++ b/app/templates/modal/model.jade @@ -4,6 +4,8 @@ block modal-header block modal-body-content for model in models - h3= model.type() + ': ' + model.id - .model-treema(data-model-id=model.id) + .model-container(data-model-id=model.id) + h3= model.type() + ': ' + model.id + .model-treema(data-model-id=model.id) + btn.btn.btn-success.save-model(data-i18n="common.save") Save hr diff --git a/app/views/account/profile_view.coffee b/app/views/account/profile_view.coffee index a4100808c..6f91cb5a6 100644 --- a/app/views/account/profile_view.coffee +++ b/app/views/account/profile_view.coffee @@ -8,6 +8,7 @@ JobProfileContactView = require 'views/modal/job_profile_contact_modal' JobProfileView = require 'views/account/job_profile_view' UserRemark = require 'models/UserRemark' forms = require 'lib/forms' +ModelModal = require 'views/modal/model_modal' class LevelSessionsCollection extends CocoCollection url: -> "/db/user/#{@userID}/level.sessions/employer" @@ -37,6 +38,7 @@ module.exports = class ProfileView extends View 'click #save-notes-button': 'onJobProfileNotesChanged' 'click #contact-candidate': 'onContactCandidate' 'click #enter-espionage-mode': 'enterEspionageMode' + 'click #open-model-modal': 'openModelModal' 'click .editable-profile .profile-photo': 'onEditProfilePhoto' 'click .editable-profile .project-image': 'onEditProjectImage' 'click .editable-profile .editable-display': 'onEditSection' @@ -252,7 +254,7 @@ module.exports = class ProfileView extends View link.icon = @iconForLink link for link in links context.profileLinks = _.sortBy links, (link) -> not link.icon # icons first if @sessions - context.sessions = (s.attributes for s in @sessions.models when (s.get('submitted') or s.get('level-id') is 'gridmancer')) + context.sessions = (s.attributes for s in @sessions.models when (s.get('submitted') or (s.get('levelID') is 'gridmancer') and s.get('code')?.thoktar?.plan?.length isnt 942)) # no default code context.sessions.sort (a, b) -> (b.playtime ? 0) - (a.playtime ? 0) else context.sessions = [] @@ -340,6 +342,9 @@ module.exports = class ProfileView extends View espionageSuccess: (model) -> window.location.reload() + openModelModal: (e) -> + @openModalView new ModelModal models: [@user] + onJobProfileNotesChanged: (e) => notes = @$el.find("#job-profile-notes").val() @user.set 'jobProfileNotes', notes diff --git a/app/views/editor/level/component/edit.coffee b/app/views/editor/level/component/edit.coffee index 3bc526813..856f79a4d 100644 --- a/app/views/editor/level/component/edit.coffee +++ b/app/views/editor/level/component/edit.coffee @@ -25,6 +25,7 @@ module.exports = class LevelComponentEditView extends View super options @levelComponent = @supermodel.getModelByOriginalAndMajorVersion LevelComponent, options.original, options.majorVersion or 0 console.log "Couldn't get levelComponent for", options, "from", @supermodel.models unless @levelComponent + @onEditorChange = _.debounce @onEditorChange, 1500 getRenderData: (context={}) -> context = super(context) @@ -95,6 +96,7 @@ module.exports = class LevelComponentEditView extends View @editor.on('change', @onEditorChange) onEditorChange: => + return if @destroyed @levelComponent.set 'code', @editor.getValue() Backbone.Mediator.publish 'level-component-edited', levelComponent: @levelComponent null diff --git a/app/views/editor/level/components_tab_view.coffee b/app/views/editor/level/components_tab_view.coffee index a0fe32f94..0ae4a3873 100644 --- a/app/views/editor/level/components_tab_view.coffee +++ b/app/views/editor/level/components_tab_view.coffee @@ -35,13 +35,13 @@ module.exports = class ComponentsTabView extends View componentModels = @supermodel.getModels LevelComponent componentModelMap = {} - componentModelMap[comp.get('original')] = comp for comp in componentModels + componentModelMap[comp.get('original')] = comp for comp in componentModels components = ({original: key.split('.')[0], majorVersion: parseInt(key.split('.')[1], 10), thangs: value, count: value.length} for key, value of @presentComponents) treemaData = _.sortBy components, (comp) -> comp = componentModelMap[comp.original] res = [comp.get('system'), comp.get('name')] return res - + treemaOptions = supermodel: @supermodel schema: {type: 'array', items: {type: 'object', format: 'level-component'}} diff --git a/app/views/kinds/CocoView.coffee b/app/views/kinds/CocoView.coffee index 616fd7847..22e00f40e 100644 --- a/app/views/kinds/CocoView.coffee +++ b/app/views/kinds/CocoView.coffee @@ -150,12 +150,6 @@ module.exports = class CocoView extends Backbone.View @lastToggleModalCall = 0 toggleModal: (e) -> - if new Date().getTime() - CocoView.lastToggleModalCall < 5 - # Defensive move. This function has had a history of messing things up. - console.error 'toggleModal is getting called too often!' - return - CocoView.lastToggleModalCall = new Date().getTime() - if $(e.currentTarget).prop('target') is '_blank' return true # special handler for opening modals that are dynamically loaded, rather than static in the page. It works (or should work) like Bootstrap's modals, except use coco-modal for the data-toggle value. @@ -163,6 +157,7 @@ module.exports = class CocoView extends Backbone.View return unless elem.data('toggle') is 'coco-modal' target = elem.data('target') view = application.router.getView(target, '_modal') # could set up a system for loading cached modals, if told to + e.stopPropagation() @openModalView(view) openModalView: (modalView, softly=false) -> diff --git a/app/views/modal/model_modal.coffee b/app/views/modal/model_modal.coffee index e1f983247..52c18b78d 100644 --- a/app/views/modal/model_modal.coffee +++ b/app/views/modal/model_modal.coffee @@ -6,6 +6,8 @@ module.exports = class ModelModal extends View template: template plain: true + events: 'click .save-model': 'onSaveModel' + constructor: (options) -> super options @models = options.models @@ -20,6 +22,7 @@ module.exports = class ModelModal extends View afterRender: -> return unless @supermodel.finished() + @modelTreemas = {} for model in @models data = $.extend true, {}, model.attributes schema = $.extend true, {}, model.schema() @@ -31,6 +34,7 @@ module.exports = class ModelModal extends View modelTreema?.build() modelTreema?.open() @openTastyTreemas modelTreema, model + @modelTreemas[model.id] = modelTreema openTastyTreemas: (modelTreema, model) -> # To save on quick inspection, let's auto-open the properties we're most likely to want to see. @@ -45,3 +49,23 @@ module.exports = class ModelModal extends View }[team] for dessert in desserts child.childrenTreemas[dessert]?.open() + + onSaveModel: (e) -> + container = $(e.target).closest('.model-container') + model = _.find @models, id: container.data('model-id') + treema = @modelTreemas[model.id] + for key, val of treema.data when not _.isEqual val, model.get key + console.log "Updating", key, "from", model.get(key), "to", val + model.set key, val + for key, val of model.attributes when treema.get(key) is undefined and not _.string.startsWith key, '_' + console.log "Deleting", key, "which was", val, "but man, that ain't going to work, now is it?" + model.unset key + if errors = model.validate() + return console.warn model, "failed validation with errors:", errors + return unless res = model.patch() + res.error => + return if @destroyed + console.error model, "failed to save with error:", res.responseText + res.success (model, response, options) => + return if @destroyed + @hide() diff --git a/app/views/play/level/tome/spell.coffee b/app/views/play/level/tome/spell.coffee index 7c796482d..5e3501014 100644 --- a/app/views/play/level/tome/spell.coffee +++ b/app/views/play/level/tome/spell.coffee @@ -18,23 +18,19 @@ module.exports = class Spell @supermodel = options.supermodel @skipProtectAPI = options.skipProtectAPI @worker = options.worker - p = options.programmableMethod + p = options.programmableMethod + @languages = p.languages ? {} + @languages.javascript ?= p.source @name = p.name @permissions = read: p.permissions?.read ? [], readwrite: p.permissions?.readwrite ? [] # teams - teamSpells = @session.get('teamSpells') - team = @session.get('team') ? 'humans' - @useTranspiledCode = @permissions.readwrite.length and ((teamSpells and not _.contains(teamSpells[team], @spellKey)) or (@session.get('creator') isnt me.id and not (me.isAdmin() or 'employer' in me.get('permissions'))) or @spectateView) - if @useTranspiledCode - console.log "#{@spellKey} is using transpiled code because permissions.readwrite is #{@permissions.readwrite} - #{if @spectateView then ', we are spectating' else ''} - #{if teamSpells and not _.contains(teamSpells[team], @spellKey) then ', teamSpells[' + team + '] does not have ' + @spellKey + ' (just ' + teamSpells[team] + ')' else ''} - #{if @session.get('creator') isnt me.id then ', and the session was created by ' + @session.get('creator') + ' but I am ' + me.id else ''}" - @source = @originalSource = p.source + @setLanguage if @canWrite() then options.language else 'javascript' + @useTranspiledCode = @shouldUseTranspiledCode() + + @source = @originalSource @parameters = p.parameters if @permissions.readwrite.length and sessionSource = @session.getSourceFor(@spellKey) @source = sessionSource - @language = if @canWrite() then options.language else 'javascript' @thangs = {} @view = new SpellView {spell: @, session: @session, worker: @worker} @view.render() # Get it ready and code loaded in advance @@ -49,6 +45,9 @@ module.exports = class Spell @thangs = null @worker = null + setLanguage: (@language) -> + @originalSource = @languages[language] ? @languages.javascript + addThang: (thang) -> if @thangs[thang.id] @thangs[thang.id].thang = thang @@ -155,3 +154,14 @@ module.exports = class Spell toString: -> "" + + shouldUseTranspiledCode: -> + # Determine whether this code has already been transpiled, or whether it's raw source needing transpilation. + return false unless @permissions.readwrite.length # Only player-writable code will be stored transpiled. + return true if @spectateView # Use transpiled code for both teams if we're just spectating. + teamSpells = @session.get('teamSpells') + team = @session.get('team') ? 'humans' + return true if teamSpells and not _.contains(teamSpells[team], @spellKey) # Use transpiled for enemy spells. + # Players without permissions can't view the raw code. + return true if @session.get('creator') isnt me.id and not (me.isAdmin() or 'employer' in me.get('permissions')) + false diff --git a/app/views/play/level/tome/spell_view.coffee b/app/views/play/level/tome/spell_view.coffee index 9933033d0..3c69d79c2 100644 --- a/app/views/play/level/tome/spell_view.coffee +++ b/app/views/play/level/tome/spell_view.coffee @@ -619,8 +619,11 @@ module.exports = class SpellView extends View @ace.setKeyboardHandler @keyBindings[aceConfig.keyBindings ? 'default'] onChangeLanguage: (e) -> - if @spell.canWrite() - @aceSession.setMode @editModes[e.language] + return unless @spell.canWrite() + @aceSession.setMode @editModes[e.language] + wasDefault = @getSource() is @spell.originalSource + @spell.setLanguage e.language + @reloadCode true if wasDefault dismiss: -> @spell.hasChangedSignificantly @getSource(), null, (hasChanged) => diff --git a/scripts/devSetup/bootstrap.sh b/scripts/devSetup/bootstrap.sh index 9d1f34b8e..46099635c 100644 --- a/scripts/devSetup/bootstrap.sh +++ b/scripts/devSetup/bootstrap.sh @@ -58,7 +58,13 @@ checkDependencies deps[@] basicDependenciesErrorHandling if command -v node >/dev/null 2>&1; then checkNodeVersion fi -#install git repository -git clone $repositoryUrl coco -#python ./coco/scripts/devSetup/setup.py -echo "Now copy and paste 'sudo python ./coco/scripts/devSetup/setup.py' into the terminal!" + +#check if a git repository already exists here +if [ -d .git ]; then + echo "A git repository already exists here!" +else + #install git repository + git clone $repositoryUrl coco + #python ./coco/scripts/devSetup/setup.py + echo "Now copy and paste 'sudo python ./coco/scripts/devSetup/setup.py' into the terminal!" +fi diff --git a/scripts/devSetup/systemConfiguration.py b/scripts/devSetup/systemConfiguration.py index c8b2b99de..78df92fdf 100644 --- a/scripts/devSetup/systemConfiguration.py +++ b/scripts/devSetup/systemConfiguration.py @@ -33,6 +33,8 @@ class SystemConfiguration(object): return 64 else: if self.operating_system == u"mac": + if os.uname()[4] == u"x86_64": + return 64 raise NotSupportedError(u"Your processor is determined to have a maxSize of" + str(sys.maxsize) + u",\n which doesn't correspond with a 64-bit architecture.") return 32 diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index a9e0dc106..6d4609d35 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -24,7 +24,7 @@ candidateProperties = [ UserHandler = class UserHandler extends Handler modelClass: User - + jsonSchema: schema editableProperties: [ 'name', 'photoURL', 'password', 'anonymous', 'wizardColor1', 'volume', 'firstName', 'lastName', 'gender', 'facebookID', 'gplusID', 'emails', @@ -32,15 +32,11 @@ UserHandler = class UserHandler extends Handler 'wizard', 'aceConfig', 'autocastDelay', 'lastLevel', 'jobProfile' ] - jsonSchema: schema - - constructor: -> - super(arguments...) - @editableProperties.push('permissions') unless config.isProduction - getEditableProperties: (req, document) -> props = super req, document - props.push 'jobProfileApproved', 'jobProfileNotes' if req.user.isAdmin() + props.push 'permissions' unless config.isProduction + props.push 'jobProfileApproved', 'jobProfileNotes' if req.user.isAdmin() # Admins naturally edit these + props.push privateProperties... if req.user.isAdmin() # Admins are mad with power props formatEntity: (req, document) -> @@ -233,7 +229,7 @@ UserHandler = class UserHandler extends Handler getLevelSessionsForEmployer: (req, res, userID) -> return @sendUnauthorizedError(res) unless req.user._id+'' is userID or req.user.isAdmin() or ('employer' in req.user.get('permissions')) query = creator: userID, levelID: {$in: ['gridmancer', 'greed', 'dungeon-arena', 'brawlwood', 'gold-rush']} - projection = 'levelName levelID team playtime codeLanguage submitted' # code totalScore + projection = 'levelName levelID team playtime codeLanguage submitted code totalScore' LevelSession.find(query).select(projection).exec (err, documents) => return @sendDatabaseError(res, err) if err documents = (LevelSessionHandler.formatEntity(req, doc) for doc in documents) @@ -344,7 +340,7 @@ UserHandler = class UserHandler extends Handler getEmployers: (req, res) -> return @sendUnauthorizedError(res) unless req.user.isAdmin() - query = {employerAt: {$exists: true}} + query = {employerAt: {$exists: true, $ne: ''}} selection = 'name firstName lastName email activity signedEmployerAgreement photoURL employerAt' User.find(query).select(selection).lean().exec (err, documents) => return @sendDatabaseError res, err if err diff --git a/test/app/views/editor/level/EditorLevelView.spec.coffee b/test/app/views/editor/level/EditorLevelView.spec.coffee new file mode 100644 index 000000000..860fa826c --- /dev/null +++ b/test/app/views/editor/level/EditorLevelView.spec.coffee @@ -0,0 +1,14 @@ +EditorLevelView = require 'views/editor/level/edit' + +emptyLevel = {"_id":"53a0a1e2d9048dbc3a793c81","name":"Emptiness","description":"Tis nothing..","documentation":{"generalArticles":[],"specificArticles":[]},"scripts":[],"thangs":[],"systems":[],"victory":{},"version":{"minor":0,"major":0,"isLatestMajor":true,"isLatestMinor":true},"index":"5388f9ac9a904d0000d94f87","slug":"emptiness","creator":"5388f9ac9a904d0000d94f87","original":"53a0a1e2d9048dbc3a793c81","watchers":["5388f9ac9a904d0000d94f87"],"__v":0,"created":"2014-06-17T20:15:30.207Z","permissions":[{"access":"owner","target":"5388f9ac9a904d0000d94f87"}]} + +describe 'EditorLevelView', -> + describe 'revert button', -> + it 'opens just one modal when you click it', -> + view = new EditorLevelView({}, 'something') + request = jasmine.Ajax.requests.first() + request.response {status:200, responseText: JSON.stringify(emptyLevel)} + view.render() + spyOn(view, 'openModalView') + view.$el.find('#revert-button').click() + expect(view.openModalView.calls.count()).toBe(1) \ No newline at end of file