diff --git a/app/schemas/models/thang_component.coffee b/app/schemas/models/thang_component.coffee index a8b6b5b74..97b5e18d8 100644 --- a/app/schemas/models/thang_component.coffee +++ b/app/schemas/models/thang_component.coffee @@ -3,7 +3,7 @@ c = require './../schemas' module.exports = ThangComponentSchema = c.object { title: 'Component' description: 'Configuration for a Component that this Thang uses.' - format: 'thang-component' + format: 'component-reference' required: ['original', 'majorVersion'] 'default': majorVersion: 0 diff --git a/app/styles/editor/components/config.sass b/app/styles/editor/components/config.sass deleted file mode 100644 index f5bc2e642..000000000 --- a/app/styles/editor/components/config.sass +++ /dev/null @@ -1,16 +0,0 @@ -#component-config-column-view - h3 - float: left - #add-remove-button - float: right - margin: 5px - #description - clear: both - - .treema - position: absolute - left: 5px - right: 5px - top: 90px - bottom: 0 - overflow: scroll \ No newline at end of file diff --git a/app/styles/editor/components/thang-component-config-view.sass b/app/styles/editor/components/thang-component-config-view.sass new file mode 100644 index 000000000..2a7426e5e --- /dev/null +++ b/app/styles/editor/components/thang-component-config-view.sass @@ -0,0 +1 @@ +//.thang-component-config-view diff --git a/app/styles/editor/components/main.sass b/app/styles/editor/components/thang-components-edit-view.sass similarity index 65% rename from app/styles/editor/components/main.sass rename to app/styles/editor/components/thang-components-edit-view.sass index a71b608b3..7179528bc 100644 --- a/app/styles/editor/components/main.sass +++ b/app/styles/editor/components/thang-components-edit-view.sass @@ -19,7 +19,7 @@ #extant-components-column left: 0 - width: 20% + width: 300px .treema position: absolute @@ -28,27 +28,24 @@ right: 10px bottom: 0 overflow: scroll + + .dependent + background-color: rgba(128, 64, 255, 0.10) - #add-component-column - right: 0 - width: 20% - - .treema - position: absolute - top: 75px - left: 10px - right: 0px - bottom: 0 - overflow: scroll - - .treema-row - line-height: 24px - .add-button - margin-left: 10px - - #component-config-column-view + #thang-components-config-column border-left: 1px solid black - border-right: 1px solid black - right: 20% - left: 20% + right: 0 + left: 300px + min-width: 600px + + h3 + margin-left: 20px + + #thang-component-configs + position: absolute + top: 80px + bottom: 0 + right: 0 + left: 20px + overflow: scroll diff --git a/app/styles/editor/level/edit.sass b/app/styles/editor/editor.sass similarity index 92% rename from app/styles/editor/level/edit.sass rename to app/styles/editor/editor.sass index a234ddd58..0893f75a8 100644 --- a/app/styles/editor/level/edit.sass +++ b/app/styles/editor/editor.sass @@ -1,9 +1,6 @@ -#editor-level-view - &, #level-editor-top-nav - // min-width: 1024px - - a - font-family: helvetica, arial, sans serif +.editor + h1, h2, h3, h4, h5, h6, a + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important position: absolute top: 0px @@ -48,7 +45,7 @@ border-radius: 0 .navbar-right // not sure why bootstrap puts a big negative margin in, but this overrides it - margin-right: 10px + margin-right: 10px !important float: right .dropdown-menu @@ -130,9 +127,7 @@ margin-top: -10px padding-top: 10px - // keeps the editor tabs a certain height - - #level-editor-tabs + #level-editor-tabs, #thang-type-edit-view .tab-content position: absolute left: 15px right: 15px diff --git a/app/styles/editor/thang/edit.sass b/app/styles/editor/thang/edit.sass deleted file mode 100644 index 565b7e88a..000000000 --- a/app/styles/editor/thang/edit.sass +++ /dev/null @@ -1,85 +0,0 @@ -#editor-thang-type-edit-view - #save-button, #revert-button - float: right - margin-right: 20px - - #portrait - float: right - - .main-area - padding: 5px - & > div.slider-cell - margin-right: 5px - float: right - .file-controls - float: left - position: relative - top: 5px - left: 5px - button - margin-left: 5px - select - position: relative - top: 5px - input - display: none - - .slider-cell - margin: 5px 0 - float: left - width: 120px - - .selector - display: inline-block - width: 100px - position: relative - top: 3px - - #marker-button, #end-button - float: right - margin-right: 10px - position: relative - top: 15px - - #canvas - float: right - width: 400px - border: 1px solid blue - background-color: lightgray - - #settings-col - float: left - width: 550px - - .treema-row img - max-width: 100% - - #thang-type-treema - height: 400px - overflow: scroll - box-sizing: border-box - - - #thang-components-edit-view - position: absolute - top: 200px - bottom: 0 - - .treema-root - background-color: white - border-radius: 4px - - - -#spritesheets - border: 1px solid green - max-width: 100% - max-height: 600px - overflow: scroll - clear: both - box-sizing: border-box - margin-bottom: 20px - canvas - background: darkseagreen - margin: 2px - diff --git a/app/styles/editor/thang/thang-type-edit-view.sass b/app/styles/editor/thang/thang-type-edit-view.sass new file mode 100644 index 000000000..6cded5b03 --- /dev/null +++ b/app/styles/editor/thang/thang-type-edit-view.sass @@ -0,0 +1,92 @@ +#thang-type-edit-view + #portrait + float: left + margin: 5px + width: 100px + height: 100px + + #settings-col + width: 560px + position: absolute + left: 0 + top: 0 + bottom: 0 + + .file-controls + float: left + position: relative + top: 5px + left: 5px + button + margin-left: 5px + input + display: none + + .treema-row img + max-width: 100% + + .treema-root + overflow: scroll + box-sizing: border-box + position: absolute + top: 160px + bottom: 20px + right: 20px + left: 20px + + #display-col + position: absolute + width: 440px + left: 580px + top: 0 + bottom: 0 + overflow: hidden + + & > div.slider-cell + margin-right: 5px + + canvas + width: 400px + border: 1px solid blue + background-color: lightgray + + select + margin-top: 10px + + #marker-button, #end-button + margin: 10px 10px 10px 0 + + .slider-cell + margin: 5px 0 + width: 195px + float: left + + .selector + display: inline-block + width: 180px + position: relative + top: 3px + + + #thang-components-edit-view + position: absolute + top: 0px + bottom: 0 + + .treema-root + background-color: white + border-radius: 4px + + + +#spritesheets + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + border: 1px solid green + overflow: scroll + canvas + background: darkseagreen + diff --git a/app/styles/treema-ext.sass b/app/styles/treema-ext.sass index be549724c..6fea9decf 100644 --- a/app/styles/treema-ext.sass +++ b/app/styles/treema-ext.sass @@ -10,9 +10,6 @@ &, & > div.ace_editor width: 100% - & > div.ace_editor - height: 400px - .treema-markdown.treema-display, .treema-coffee.treema-display, .treema-javascript.treema-display width: 100% border: 3px inset rgba(0, 100, 100, 0.2) diff --git a/app/templates/editor/components/config.jade b/app/templates/editor/components/config.jade deleted file mode 100644 index 72de0193a..000000000 --- a/app/templates/editor/components/config.jade +++ /dev/null @@ -1,8 +0,0 @@ -h3= component.name -//button.btn#add-remove-button Add Component -p#description= component.description -.treema -//p#other-props -// strong Other Config Properties: -// for prop in configProperties -// a(title=prop.description)= prop.name \ No newline at end of file diff --git a/app/templates/editor/components/main.jade b/app/templates/editor/components/main.jade deleted file mode 100644 index 6f03e8ac5..000000000 --- a/app/templates/editor/components/main.jade +++ /dev/null @@ -1,11 +0,0 @@ -#extant-components-column.column - h3 Components - p Click then press delete to remove. - .treema - -#component-config-column-view.column - -#add-component-column.column - h3 Add Components - p Click then press enter to add. - .treema diff --git a/app/templates/editor/components/thang-component-config-view.jade b/app/templates/editor/components/thang-component-config-view.jade new file mode 100644 index 000000000..ff9a0e199 --- /dev/null +++ b/app/templates/editor/components/thang-component-config-view.jade @@ -0,0 +1,7 @@ +.panel.panel-default + .panel-heading + strong.panel-title= component.name + em.spl.spr (#{component.system}) + span#description= component.description + .panel-body + .treema diff --git a/app/templates/editor/components/thang-components-edit-view.jade b/app/templates/editor/components/thang-components-edit-view.jade new file mode 100644 index 000000000..dbcbf2f10 --- /dev/null +++ b/app/templates/editor/components/thang-components-edit-view.jade @@ -0,0 +1,9 @@ +#extant-components-column.column + h3 Components + p Click then press delete to remove. + .treema + +.column#thang-components-config-column + h3 Component Configurations + #thang-component-configs + diff --git a/app/templates/editor/thang/edit.jade b/app/templates/editor/thang/edit.jade deleted file mode 100644 index df7972622..000000000 --- a/app/templates/editor/thang/edit.jade +++ /dev/null @@ -1,95 +0,0 @@ -extends /templates/base - -block content - div - ol.breadcrumb - li - a(href="/editor", data-i18n="editor.main_title") CodeCombat Editors - li - a(href="/editor/thang", data-i18n="editor.thang_title") Thang Editor - li.active - | #{thangType.attributes.name} - - img#portrait.img-thumbnail - - button.btn.btn-secondary#history-button(data-i18n="general.version_history") Version History - button.btn.btn-primary#save-button(data-i18n="common.save", disabled=authorized === true ? undefined : "true") Save - button.btn.btn-primary#revert-button(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert", disabled=authorized === true ? undefined : "true") Revert - - h3 Edit Thang Type: "#{thangType.attributes.name}" - - ul.nav.nav-tabs - li.active - a(href="#editor-thang-main-tab-view", data-toggle="tab") Main - li - a(href="#editor-thang-components-tab-view", data-toggle="tab") Components - li - a(href="#editor-thang-spritesheets-view", data-toggle="tab") Spritesheets - li - a(href="#editor-thang-colors-tab-view", data-toggle="tab")#color-tab Colors - li - a(href="#editor-thang-patches-view", data-toggle="tab")#patches-tab Patches - - div.tab-content - div.tab-pane#editor-thang-colors-tab-view - - div.tab-pane#editor-thang-main-tab-view.active - - div.main-area.well - div.file-controls - select#animations-select - for animation in animations - option #{animation} - button(disabled=authorized === true ? undefined : "true").btn.btn-small.btn-primary#upload-button - i.icon-upload - button(disabled=authorized === true ? undefined : "true").btn.btn-small.btn-primary#clear-button - i.icon-remove - input#real-upload-button(type="file") - - button.btn.btn-small.btn-primary#marker-button - i.icon-map-marker - - button.btn.btn-small.btn-primary#end-button - i.icon-stop - - div.slider-cell - | Rotation: - span.rotation-label - .selector#rotation-slider - div.slider-cell - | Scale: - span.scale-label - .selector#scale-slider - div.slider-cell - | Resolution: - span.resolution-label 4.0x - .selector#resolution-slider - div.slider-cell - | Health: - span.health-label 10hp - .selector#health-slider - - .clearfix - - div#settings-col - div#thang-type-treema - - canvas#canvas(width="400", height="400") - - .clearfix - - div.tab-pane#editor-thang-components-tab-view - - #thang-components-edit-view - - div.tab-pane#editor-thang-spritesheets-view - - div#spritesheets - - div.tab-pane#editor-thang-patches-view - - div.patches-view - - div#error-view - - .clearfix diff --git a/app/templates/editor/thang/thang-type-edit-view.jade b/app/templates/editor/thang/thang-type-edit-view.jade new file mode 100644 index 000000000..f3560d212 --- /dev/null +++ b/app/templates/editor/thang/thang-type-edit-view.jade @@ -0,0 +1,124 @@ +extends /templates/base +block header + if thangType.loading + nav.navbar.navbar-default(role='navigation')#thang-editor-top-nav + .container-fluid + ul.nav.navbar-nav + li + a(href="/editor/thang") + span.glyphicon-home.glyphicon + else + nav.navbar.navbar-default(role='navigation')#thang-editor-top-nav + ul.nav.navbar-nav + li + a(href="/editor/thang") + span.glyphicon-home.glyphicon + ul.nav.navbar-nav.nav-tabs + li.active + a(href="#editor-thang-main-tab-view", data-toggle="tab") Main + li + a(href="#editor-thang-components-tab-view", data-toggle="tab") Components + li + a(href="#editor-thang-spritesheets-view", data-toggle="tab") Spritesheets + li + a(href="#editor-thang-colors-tab-view", data-toggle="tab")#color-tab Colors + li + a(href="#editor-thang-patches-view", data-toggle="tab")#patches-tab + span(data-i18n="resources.patches").spr Patches + - var patches = thangType.get('patches') + if patches && patches.length + span.badge= patches.length + + .navbar-header + span.navbar-brand #{thangType.attributes.name} + + ul.nav.navbar-nav.navbar-right + if authorized + li#save-button + a + span.glyphicon-floppy-disk.glyphicon + li.dropdown + a(data-toggle='dropdown') + span.glyphicon-chevron-down.glyphicon + ul.dropdown-menu + li.dropdown-header Actions + li(class=anonymous ? "disabled": "") + a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert")#revert-button Revert + li.divider + li.dropdown-header Info + li#history-button + a(href='#', data-i18n="general.version_history") Version History + li.divider + li.dropdown-header Help + li + a(href='https://github.com/codecombat/codecombat/wiki/Artisan-Home', data-i18n="editor.wiki", target="_blank") Wiki + li + a(href='http://www.hipchat.com/g3plnOKqa', data-i18n="editor.live_chat", target="_blank") Live Chat + li + a(href='http://discourse.codecombat.com/category/artisan', data-i18n="nav.forum", target="_blank") Forum + li + a(data-toggle="coco-modal", data-target="modal/ContactModal", data-i18n="nav.contact") Email + +block outer_content + .outer-content + div.tab-content + div.tab-pane#editor-thang-colors-tab-view + div.tab-pane#editor-thang-main-tab-view.active + div#settings-col.well + img#portrait.img-thumbnail + div.file-controls + button(disabled=authorized === true ? undefined : "true").btn.btn-small.btn-info#upload-button + span.glyphicon.glyphicon-upload + span.spl Upload Animation + button(disabled=authorized === true ? undefined : "true").btn.btn-small.btn-danger#clear-button + span.glyphicon.glyphicon-remove + span.spl Clear Data + input#real-upload-button(type="file") + div#thang-type-treema + .clearfix + div#display-col.well + canvas#canvas(width="400", height="400") + select#animations-select.form-control + for animation in animations + option #{animation} + div + button.btn.btn-small.btn-primary#marker-button + i.icon-map-marker + span.spl Markers + button.btn.btn-small.btn-primary#end-button + i.icon-stop + span.spl Stop + div.slider-cell + | Rotation: + span.rotation-label + .selector#rotation-slider + div.slider-cell + | Scale: + span.scale-label + .selector#scale-slider + div.slider-cell + | Resolution: + span.resolution-label 4.0x + .selector#resolution-slider + div.slider-cell + | Health: + span.health-label 10hp + .selector#health-slider + + div.tab-pane#editor-thang-components-tab-view + #thang-components-edit-view + div.tab-pane#editor-thang-spritesheets-view + div#spritesheets + div.tab-pane#editor-thang-patches-view + div.patches-view + div#error-view + .clearfix +block footer + + + + + + + + diff --git a/app/treema-ext.coffee b/app/treema-ext.coffee index d5fda7677..6fab2cc2c 100644 --- a/app/treema-ext.coffee +++ b/app/treema-ext.coffee @@ -225,14 +225,8 @@ class CodeTreema extends TreemaNode.nodeMap.ace super(valEl) if not @schema.aceMode and mode = codeLanguages[@keyForParent] @editor.getSession().setMode mode - @editor.on('change', @onEditorChange) valEl - onEditorChange: => - @saveChanges() - @flushChanges() - @getRoot().broadcastChanges() - class CoffeeTreema extends CodeTreema constructor: -> super(arguments...) @@ -307,27 +301,20 @@ class LatestVersionReferenceNode extends TreemaNode input = valEl.find('input') input.focus().keyup @search input.attr('placeholder', @formatDocument(@data)) if @data + + buildSearchURL: (term) -> "#{@url}?term=#{term}&project=true" search: => term = @getValEl().find('input').val() return if term is @lastTerm - # HACK while search is broken - if @collection - @lastTerm = term - @searchCallback() - return - @getSearchResultsEl().empty() if @lastTerm and not term return unless term @lastTerm = term @getSearchResultsEl().empty().append('Searching') @collection = new LatestVersionCollection([], model: @model) - # HACK while search is broken -# @collection.url = "#{@url}?term=#{term}&project=true" - @collection.url = "#{@url}?term=#{''}&project=true" - + @collection.url = @buildSearchURL(term) @collection.fetch() @collection.once 'sync', @searchCallback, @ @@ -338,10 +325,6 @@ class LatestVersionReferenceNode extends TreemaNode row = $('
').addClass('treema-search-result-row') text = @formatDocument(model) continue unless text? - - # HACK while search is broken - continue unless text.toLowerCase().indexOf(@lastTerm.toLowerCase()) >= 0 - row.addClass('treema-search-selected') if first first = false row.text(text) @@ -353,10 +336,11 @@ class LatestVersionReferenceNode extends TreemaNode getSearchResultsEl: -> @getValEl().find('.treema-search-results') getSelectedResultEl: -> @getValEl().find('.treema-search-selected') + + modelToString: (model) -> model.get('name') formatDocument: (docOrModel) -> - doc = docOrModel.attributes or docOrModel - return doc.name if doc.name? + return @modelToString(docOrModel) if docOrModel instanceof CocoModel return 'Unknown' unless @settings.supermodel? m = CocoModel.getReferencedModel(@data, @schema) urlGoingFor = m.url() @@ -366,7 +350,7 @@ class LatestVersionReferenceNode extends TreemaNode m.url = -> urlGoingFor @settings.supermodel.registerModel(m) return 'Unknown' unless m - return m.get('name') + return @modelToString(m) saveChanges: -> selected = @getSelectedResultEl() @@ -379,10 +363,12 @@ class LatestVersionReferenceNode extends TreemaNode @instance = fullValue onDownArrowPressed: (e) -> + return super(arguments...) unless @isEditing() @navigateSearch(1) e.preventDefault() onUpArrowPressed: (e) -> + return super(arguments...) unless @isEditing() e.preventDefault() @navigateSearch(-1) @@ -410,7 +396,14 @@ class LatestVersionReferenceNode extends TreemaNode return if @data? selected = @getSelectedResultEl() return not selected.length - + +class LevelComponentReferenceNode extends LatestVersionReferenceNode + # HACK: this list of properties is needed by the thang components edit view and config views. + # need a better way to specify this, or keep the search models from bleeding into those + # supermodels. + buildSearchURL: (term) -> "#{@url}?term=#{term}&project=name,system,original,version,dependencies,configSchema,description" + modelToString: (model) -> model.get('system') + '.' + model.get('name') + canEdit: -> not @data.original # only allow editing if the row's data hasn't been set yet LatestVersionReferenceNode.prototype.search = _.debounce(LatestVersionReferenceNode.prototype.search, 200) @@ -431,6 +424,7 @@ module.exports.setup = -> TreemaNode.setNodeSubclass('javascript', JavaScriptTreema) TreemaNode.setNodeSubclass('image-file', ImageFileTreema) TreemaNode.setNodeSubclass('latest-version-reference', LatestVersionReferenceNode) + TreemaNode.setNodeSubclass('component-reference', LevelComponentReferenceNode) TreemaNode.setNodeSubclass('i18n', InternationalizationNode) TreemaNode.setNodeSubclass('sound-file', SoundFileTreema) TreemaNode.setNodeSubclass 'slug-props', SlugPropsObject diff --git a/app/views/editor/component/ThangComponentConfigView.coffee b/app/views/editor/component/ThangComponentConfigView.coffee index 8b544465e..68ba90601 100644 --- a/app/views/editor/component/ThangComponentConfigView.coffee +++ b/app/views/editor/component/ThangComponentConfigView.coffee @@ -1,15 +1,17 @@ CocoView = require 'views/kinds/CocoView' -template = require 'templates/editor/components/config' +template = require 'templates/editor/components/thang-component-config-view' Level = require 'models/Level' LevelComponent = require 'models/LevelComponent' nodes = require '../level/treema_nodes' module.exports = class ThangComponentConfigView extends CocoView - id: 'component-config-column-view' + className: 'thang-component-config-view' template: template - className: 'column' changed: false + + events: + 'click .treema-shortened': -> console.log 'clicked treema root' constructor: (options) -> super options @@ -17,12 +19,11 @@ module.exports = class ThangComponentConfigView extends CocoView @config = options.config or {} @world = options.world @level = options.level - @editing = options.editing @callback = options.callback - + getRenderData: (context={}) -> context = super(context) - context.component = @component + context.component = @component.attributes context.configProperties = [] context @@ -38,7 +39,7 @@ module.exports = class ThangComponentConfigView extends CocoView superteams = _.union(teams, superteams) treemaOptions = supermodel: @supermodel - schema: @component.configSchema + schema: @component.attributes.configSchema data: _.cloneDeep @config callbacks: {change: @onConfigEdited} world: @world @@ -58,16 +59,16 @@ module.exports = class ThangComponentConfigView extends CocoView 'seconds': nodes.SecondsNode 'speed': nodes.SpeedNode 'acceleration': nodes.AccelerationNode - treemaOptions.readOnly = not @editing @editThangTreema = @$el.find('.treema').treema treemaOptions @editThangTreema.build() @editThangTreema.open(2) - @hideLoading() + if _.isEqual(@config, {}) and not @editThangTreema.canAddChild() + @$el.find('.panel-body').hide() onConfigEdited: => @changed = true - @callback?(@data()) + @trigger 'changed', { component: @component, config: @data() } undo: -> @editThangTreema.undo() diff --git a/app/views/editor/component/ThangComponentEditView.coffee b/app/views/editor/component/ThangComponentEditView.coffee deleted file mode 100644 index 91530cb3f..000000000 --- a/app/views/editor/component/ThangComponentEditView.coffee +++ /dev/null @@ -1,284 +0,0 @@ -CocoView = require 'views/kinds/CocoView' -template = require 'templates/editor/components/main' - -Level = require 'models/Level' -LevelComponent = require 'models/LevelComponent' -LevelSystem = require 'models/LevelSystem' -ComponentsCollection = require 'collections/ComponentsCollection' -ComponentConfigView = require './ThangComponentConfigView' - -module.exports = class ThangComponentEditView extends CocoView - id: 'thang-components-edit-view' - template: template - - constructor: (options) -> - super options - @components = options.components or [] - @world = options.world - @level = options.level - @callback = options.callback - @componentCollection = @supermodel.loadCollection(new ComponentsCollection(), 'components').model - - afterRender: -> - super() - return unless @supermodel.finished() - @buildExtantComponentTreema() - @buildAddComponentTreema() - - buildExtantComponentTreema: -> - level = new Level() - treemaOptions = - supermodel: @supermodel - schema: level.schema().properties.thangs.items.properties.components - data: _.cloneDeep @components - callbacks: {select: @onSelectExtantComponent, change: @onChangeExtantComponents} - noSortable: true - nodeClasses: - 'thang-components-array': ThangComponentsArrayNode - 'thang-component': ThangComponentNode - - @extantComponentsTreema = @$el.find('#extant-components-column .treema').treema treemaOptions - @extantComponentsTreema.build() - @$el.find('#extant-components-column .treema').droppable({ - drop: => - @onAddComponentEnterPressed @selectedRow - }) - - buildAddComponentTreema: -> - return unless @componentCollection and @extantComponentsTreema - extantComponents = @extantComponentsTreema.data - componentsUsedCount = extantComponents.length - return if @lastComponentsUsedCount is componentsUsedCount - @lastComponentsUsedCount = componentsUsedCount - components = (m.attributes for m in @componentCollection.models) - _.remove components, (comp) => - _.find extantComponents, {original: comp.original} # already have this one added - components = _.sortBy components, (comp) -> comp.system + '.' + comp.name - - treemaOptions = - supermodel: @supermodel - schema: {type: 'array', items: LevelComponent.schema} - data: ($.extend(true, {}, c) for c in components) - callbacks: { - select: @onSelectAddableComponent - enter: @onAddComponentEnterPressed - dblclick: @onAddComponentDoubleClicked - mouseenter: @onAddComponentMouseEnter - mouseleave: @onAddComponentMouseLeave - } - readOnly: true - noSortable: true - nodeClasses: - 'array': ComponentArrayNode - 'object': ComponentNode - # I have no idea why it's not building in the Thang Editor unless I defer - _.defer (=> - @addComponentsTreema = @$el.find('#add-component-column .treema').treema treemaOptions - @addComponentsTreema.build() - @$el.find('#add-component-column .treema-node').draggable({ - revert: 'invalid' - helper: 'clone' - appendTo: 'body' - start: (e) -> - # Hack to ensure dragged treema node is selected - $(@).trigger('click') unless $(@).hasClass 'treema-selected' - }) - @hideLoading() - ), 500 - - onSelectAddableComponent: (e, selected) => - @extantComponentsTreema.deselectAll() - @onComponentSelected(selected, false) - - onSelectExtantComponent: (e, selected) => - return if @updatingFromConfig - @addComponentsTreema.deselectAll() - @onComponentSelected(selected, true) - - onComponentSelected: (selected, extant=true) -> - return if @alreadySaving # handle infinite loops - @alreadySaving = true - @closeExistingView() - @alreadySaving = false - - return unless selected.length - - # select dependencies. - node = selected[0] - original = node.data.original - - toRemoveTreema = [] - dependent_class = 'treema-dependent' - try - for index, child of @extantComponentsTreema.childrenTreemas - $(child.$el).removeClass(dependent_class) - - for index, child of @extantComponentsTreema.childrenTreemas - if child.data.original == original # Here we assume that the treemas are sorted by their dependency. - break - - dep_originals = (d.original for d in child.component.attributes.dependencies) - for dep_original in dep_originals - if original == dep_original - toRemoveTreema.push child - - for dep_treema in toRemoveTreema - dep_treema.toggleSelect() - $(dep_treema.$el).addClass(dependent_class) - - catch error - console.error error - - return unless selected.length - row = selected[0] - @selectedRow = row - component = row.component?.attributes or row.data - config = if extant then row.data?.config else {} - @configView = new ComponentConfigView({ - supermodel: @supermodel - level: @level - world: @world - config: config - component: component - editing: extant - callback: @onComponentConfigChanged - }) - @insertSubView @configView - - closeExistingView: -> - return unless @configView - data = @configView.data() - @selectedRow.set '/config', data if data and @configView.changed and @configView.editing - @removeSubView @configView - @configView = null - - onComponentConfigChanged: (data) => - @updatingFromConfig = true - @selectedRow.set '/config', data if data and @configView.changed and @configView.editing - @updatingFromConfig = false - - onChangeExtantComponents: => - @buildAddComponentTreema() - @reportChanges() - - onAddComponentEnterPressed: (node) => - if extantSystems - extantSystems = - (@supermodel.getModelByOriginalAndMajorVersion LevelSystem, sn.original, sn.majorVersion).attributes.name.toLowerCase() for idx, sn of @level.get('systems') - requireSystem = node.data.system.toLowerCase() - - if requireSystem not in extantSystems - warn_element = 'Component ' + node.data.name + ' requires system ' + requireSystem + ' which is currently not specified in this level.' - noty({ - text: warn_element, - layout: 'bottomLeft', - type: 'warning' - }) - - id = node.data._id - comp = _.find @componentCollection.models, id: id - unless comp - return console.error 'Couldn\'t find component for id', id, 'out of', @components.models - # Add all dependencies, recursively, unless we already have them - toAdd = comp.getDependencies(@componentCollection.models) - _.remove toAdd, (c1) => - _.find @extantComponentsTreema.data, (c2) -> - c2.original is c1.get('original') - for c in toAdd.concat [comp] - @extantComponentsTreema.insert '/', { - original: c.get('original') ? id - majorVersion: c.get('version').major ? 0 - } - - # reselect newly added child treema in the extantComponentsTreema - for index, treema of @extantComponentsTreema.childrenTreemas - if treema.component.id is id - _.defer => - treema.select() - @onSelectExtantComponent({}, [treema]) - return - - onAddComponentDoubleClicked: (e, treema) => - @onAddComponentEnterPressed treema - - onAddComponentMouseEnter: (e, treema) -> - treema.$el.find('.add-button').show() - - onAddComponentMouseLeave: (e, treema) -> - treema.$el.find('.add-button').hide() - return - - reportChanges: -> - @callback?($.extend(true, [], @extantComponentsTreema.data)) - - undo: -> - if @configView is null or @configView?.editing is false then @extantComponentsTreema.undo() else @configView.undo() - - redo: -> - if @configView is null or @configView?.editing is false then @extantComponentsTreema.redo() else @configView.redo() - -class ThangComponentsArrayNode extends TreemaArrayNode - valueClass: 'treema-thang-components-array' - editable: false - sort: true - canAddChild: -> false - - sortFunction: (a, b) => - a = @settings.supermodel.getModelByOriginalAndMajorVersion(LevelComponent, a.original, a.majorVersion) - b = @settings.supermodel.getModelByOriginalAndMajorVersion(LevelComponent, b.original, b.majorVersion) - return 1 if a.attributes.system > b.attributes.system - return -1 if a.attributes.system < b.attributes.system - return 1 if a.name > b.name - return -1 if a.name < b.name - return 0 - -class ThangComponentNode extends TreemaObjectNode - valueClass: 'treema-thang-component' - collection: false - - constructor: -> - super(arguments...) - @grabDBComponent() - - grabDBComponent: -> - @component = @settings.supermodel.getModelByOriginalAndMajorVersion LevelComponent, @data.original, @data.majorVersion - console.error 'Couldn\'t find comp for', @data.original, @data.majorVersion, 'from models', @settings.supermodel.models unless @component - - buildValueForDisplay: (valEl) -> - return super valEl unless @data.original and @component - s = @component.get('system') + '.' + @component.get('name') - @buildValueForDisplaySimply valEl, s - -class ComponentArrayNode extends TreemaArrayNode - editable: false - sort: true - canAddChild: -> false - - sortFunction: (a, b) => - return 1 if a.system > b.system - return -1 if a.system < b.system - return 1 if a.name > b.name - return -1 if a.name < b.name - return 0 - -class ComponentNode extends TreemaObjectNode - valueClass: 'treema-component' - addButtonTemplate: '' - collection: false - - build: -> - super() - @$el.find('> .treema-row').append @addButtonTemplate - addButton = @$el.find('.add-button') - addButton.hide() - addButton.click => - @callbacks.enter?(@) - @$el - - buildValueForDisplay: (valEl) -> - s = @data.system + '.' + @data.name - @buildValueForDisplaySimply valEl, s - - onEnterPressed: (args...) -> - super(args...) - @callbacks.enter?(@) \ No newline at end of file diff --git a/app/views/editor/component/ThangComponentsEditView.coffee b/app/views/editor/component/ThangComponentsEditView.coffee new file mode 100644 index 000000000..9ed2ae5a8 --- /dev/null +++ b/app/views/editor/component/ThangComponentsEditView.coffee @@ -0,0 +1,282 @@ +CocoView = require 'views/kinds/CocoView' +template = require 'templates/editor/components/thang-components-edit-view' + +Level = require 'models/Level' +LevelComponent = require 'models/LevelComponent' +LevelSystem = require 'models/LevelSystem' +ComponentsCollection = require 'collections/ComponentsCollection' +ThangComponentConfigView = require './ThangComponentConfigView' + +module.exports = class ThangComponentsEditView extends CocoView + id: 'thang-components-edit-view' + template: template + + constructor: (options) -> + super options + @components = options.components or [] + @components = $.extend true, [], @components # just to be sure + @lastComponentLength = @components.length + @world = options.world + @level = options.level + @callback = options.callback # TODO: Switch to 'trigger' + @loadComponents(@components) + + loadComponents: (components) -> + for componentRef in components + levelComponent = new LevelComponent() + url = "/db/level.component/#{componentRef.original}/version/#{componentRef.majorVersion}" + levelComponent.setURL(url) + resource = @supermodel.loadModel levelComponent, 'component' + @listenToOnce resource, 'loaded', -> + return if @handlingChange + if @supermodel.finished() + @handlingChange = true + @onComponentsAdded() + @handlingChange = false + + afterRender: -> + super() + return unless @supermodel.finished() + @buildExtantComponentsTreema() + @addThangComponentConfigViews() + + buildExtantComponentsTreema: -> + treemaOptions = + supermodel: @supermodel + schema: Level.schema.properties.thangs.items.properties.components + data: $.extend true, [], @components + callbacks: {select: @onSelectComponent, change: @onComponentsTreemaChanged} + noSortable: true + nodeClasses: + 'thang-components-array': ThangComponentsArrayNode + + @extantComponentsTreema = @$el.find('#extant-components-column .treema').treema treemaOptions + @extantComponentsTreema.build() + + onComponentsTreemaChanged: => + return if @handlingChange + @handlingChange = true + componentMap = {} + for component in @components + componentMap[component.original] = component + + newComponentsList = [] + for component in @extantComponentsTreema.data + newComponentsList.push(componentMap[component.original] or component) + @components = newComponentsList + + # update the components list here + @onComponentsChanged() + @handlingChange = false + + onComponentsChanged: => + # happens whenever the list of components changed, one way or another + # * if the treema gets changed + # * if components are added externally, like by a modal + # * if a dependency loads and is added to the list + + # TODO: Disallow editing components in the list, otherwise this system breaks. + + if @components.length < @lastComponentLength + @onComponentsRemoved() + else + @onComponentsAdded() + + @lastComponentLength = @components.length + + onComponentsRemoved: -> + componentMap = {} + for component in @components + componentMap[component.original] = component + + # Deleting components missing dependencies. + while true + removedSomething = false + for componentRef in _.values(componentMap) + componentModel = @supermodel.getModelByOriginalAndMajorVersion( + LevelComponent, componentRef.original, componentRef.majorVersion) + for dependency in componentModel.get('dependencies') or [] + if not componentMap[dependency.original] + delete componentMap[componentRef.original] + component = @supermodel.getModelByOriginal( + LevelComponent, componentRef.original) + noty { + text: "Removed dependent component: #{component.get('name')}" + layout: 'topCenter' + timeout: 5000 + type: 'information' + } + removedSomething = true + break if removedSomething + break unless removedSomething + + @components = _.values(componentMap) + + # Delete individual component config views that are no longer included. + for subview in _.values(@subviews) + continue unless subview instanceof ThangComponentConfigView + if not componentMap[subview.component.get('original')] + @removeSubView(subview) + + @updateComponentsList() + @reportChanges() + + updateComponentsList: -> + @extantComponentsTreema?.set('/', $.extend(true, [], @components)) + + onComponentsAdded: -> + componentMap = {} + for component in @components + componentMap[component.original] = component + + # Go through the map, adding missing dependencies. + while true + addedSomething = false + for componentRef in _.values(componentMap) + componentModel = @supermodel.getModelByOriginalAndMajorVersion( + LevelComponent, componentRef.original, componentRef.majorVersion) + for dependency in componentModel.get('dependencies') or [] + if not componentMap[dependency.original] + component = @supermodel.getModelByOriginalAndMajorVersion( + LevelComponent, dependency.original, dependency.majorVersion) + if not component + @loadComponents([dependency]) + # will run onComponentsAdded once more when the model loads + else + addedSomething = true + noty { + text: "Added dependency: #{component.get('name')}" + layout: 'topCenter' + timeout: 5000 + type: 'information' + } + componentMap[dependency.original] = dependency + @components.push dependency + break unless addedSomething + + + # Sort the component list, reorder the component config views + @updateComponentsList() + @addThangComponentConfigViews() + @reportChanges() + + addThangComponentConfigViews: -> + # Detach all component config views temporarily. + componentConfigViews = {} + for subview in _.values(@subviews) + continue unless subview instanceof ThangComponentConfigView + componentConfigViews[subview.component.get('original')] = subview + subview.$el.detach() + + # Put back config views into the DOM based on the component list ordering, + # adding and registering new ones as needed. + configsEl = @$el.find('#thang-component-configs') + for componentRef in @extantComponentsTreema.data + subview = componentConfigViews[componentRef.original] + if not subview + subview = @makeThangComponentConfigView(componentRef) + continue unless subview + @registerSubView(subview) + configsEl.append(subview.$el) + + makeThangComponentConfigView: (thangComponent) -> + component = @supermodel.getModelByOriginal(LevelComponent, thangComponent.original) + return unless component + config = thangComponent.config ? {} + configView = new ThangComponentConfigView({ + supermodel: @supermodel + level: @level + world: @world + config: config + component: component + }) + configView.render() + @listenTo configView, 'changed', @onConfigChanged + configView + + onConfigChanged: (e) -> + for thangComponent in @components + if thangComponent.original is e.component.get('original') + thangComponent.config = e.config + @reportChanges() + + onSelectComponent: (e, nodes) => + @extantComponentsTreema.$el.find('.dependent').removeClass('dependent') + return unless nodes.length is 1 + + # find dependent components + dependents = {} + dependents[nodes[0].data.original] = true + componentsToCheck = [nodes[0].data.original] + while componentsToCheck.length + componentOriginal = componentsToCheck.pop() + for otherComponentRef in @components + continue if otherComponentRef.original is componentOriginal + continue if dependents[otherComponentRef.original] + otherComponent = @supermodel.getModelByOriginal(LevelComponent, otherComponentRef.original) + for dependency in otherComponent.get('dependencies') + if dependents[dependency.original] + dependents[otherComponentRef.original] = true + componentsToCheck.push otherComponentRef.original + + # highlight them + for child in _.values(@extantComponentsTreema.childrenTreemas) + if dependents[child.data.original] + child.$el.addClass('dependent') + + # scroll to the config + for subview in _.values(@subviews) + continue unless subview instanceof ThangComponentConfigView + if subview.component.get('original') is nodes[0].data.original + subview.$el[0].scrollIntoView() + break + + onComponentConfigChanged: (data) => + @updatingFromConfig = true + @selectedRow.set '/config', data if data and @configView.changed and @configView.editing + @updatingFromConfig = false + + onChangeExtantComponents: => + @buildAddComponentTreema() + @reportChanges() + + onAddComponentEnterPressed: (node) => + # TODO: Incorporate this logic when adding components + if extantSystems + extantSystems = + (@supermodel.getModelByOriginalAndMajorVersion LevelSystem, sn.original, sn.majorVersion).attributes.name.toLowerCase() for idx, sn of @level.get('systems') + requireSystem = node.data.system.toLowerCase() + + if requireSystem not in extantSystems + warn_element = 'Component ' + node.data.name + ' requires system ' + requireSystem + ' which is currently not specified in this level.' + noty({ + text: warn_element, + layout: 'bottomLeft', + type: 'warning' + }) + + reportChanges: -> + @callback?($.extend(true, [], @components)) + + # TODO: Fix these. + undo: -> + if @configView is null or @configView?.editing is false then @extantComponentsTreema.undo() else @configView.undo() + + redo: -> + if @configView is null or @configView?.editing is false then @extantComponentsTreema.redo() else @configView.redo() + +class ThangComponentsArrayNode extends TreemaArrayNode + valueClass: 'treema-thang-components-array' + sort: true + + sortFunction: (a, b) => + a = @settings.supermodel.getModelByOriginalAndMajorVersion(LevelComponent, a.original, a.majorVersion) + b = @settings.supermodel.getModelByOriginalAndMajorVersion(LevelComponent, b.original, b.majorVersion) + return 0 if not (a or b) + return 1 if not b + return -1 if not a + return 1 if a.attributes.system > b.attributes.system + return -1 if a.attributes.system < b.attributes.system + return 1 if a.name > b.name + return -1 if a.name < b.name + return 0 \ No newline at end of file diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee index 5fd5388fb..e3d34a0a8 100644 --- a/app/views/editor/level/LevelEditView.coffee +++ b/app/views/editor/level/LevelEditView.coffee @@ -20,6 +20,7 @@ ComponentDocsView = require 'views/docs/ComponentDocumentationView' module.exports = class LevelEditView extends RootView id: 'editor-level-view' + className: 'editor' template: template cache: false diff --git a/app/views/editor/level/thangs/LevelThangEditView.coffee b/app/views/editor/level/thangs/LevelThangEditView.coffee index 96e2c7e1a..bb551b41b 100644 --- a/app/views/editor/level/thangs/LevelThangEditView.coffee +++ b/app/views/editor/level/thangs/LevelThangEditView.coffee @@ -1,12 +1,12 @@ CocoView = require 'views/kinds/CocoView' template = require 'templates/editor/level/thang/edit' -ThangComponentEditView = require 'views/editor/component/ThangComponentEditView' +ThangComponentsEditView = require 'views/editor/component/ThangComponentsEditView' ThangType = require 'models/ThangType' module.exports = class LevelThangEditView extends CocoView ### In the level editor, is the bar at the top when editing a single thang. - Everything below is part of the ThangComponentEditView, which is shared with the + Everything below is part of the ThangComponentsEditView, which is shared with the ThangType editor view. ### @@ -43,7 +43,7 @@ module.exports = class LevelThangEditView extends CocoView world: @world callback: @onComponentsChanged - @thangComponentEditView = new ThangComponentEditView options + @thangComponentEditView = new ThangComponentsEditView options @insertSubView @thangComponentEditView thangTypeNames = (m.get('name') for m in @supermodel.getModels ThangType) input = @$el.find('#thang-type-link input').autocomplete(source: thangTypeNames, minLength: 0, delay: 0, autoFocus: true) diff --git a/app/views/editor/thang/ThangTypeEditView.coffee b/app/views/editor/thang/ThangTypeEditView.coffee index c9fd478bb..4d6edefbe 100644 --- a/app/views/editor/thang/ThangTypeEditView.coffee +++ b/app/views/editor/thang/ThangTypeEditView.coffee @@ -6,17 +6,18 @@ Camera = require 'lib/surface/Camera' DocumentFiles = require 'collections/DocumentFiles' RootView = require 'views/kinds/RootView' -ThangComponentEditView = require 'views/editor/component/ThangComponentEditView' +ThangComponentsEditView = require 'views/editor/component/ThangComponentsEditView' ThangTypeVersionsModal = require './ThangTypeVersionsModal' ThangTypeColorsTabView = require './ThangTypeColorsTabView' PatchesView = require 'views/editor/PatchesView' SaveVersionModal = require 'views/modal/SaveVersionModal' -template = require 'templates/editor/thang/edit' +template = require 'templates/editor/thang/thang-type-edit-view' CENTER = {x: 200, y: 300} module.exports = class ThangTypeEditView extends RootView - id: 'editor-thang-type-edit-view' + id: 'thang-type-edit-view' + className: 'editor' template: template startsLoading: true resolution: 4 @@ -49,7 +50,6 @@ module.exports = class ThangTypeEditView extends RootView @thangType = @supermodel.loadModel(@thangType, 'thang').model @thangType.saveBackups = true @listenToOnce @thangType, 'sync', -> - console.log 'files for?', @thangType.id, @thangType.get 'name' @files = @supermodel.loadCollection(new DocumentFiles(@thangType), 'files').model @refreshAnimation = _.debounce @refreshAnimation, 500 @@ -82,7 +82,7 @@ module.exports = class ThangTypeEditView extends RootView components: @thangType.get('components') ? [] supermodel: @supermodel callback: @onComponentsChanged - @thangComponentEditView = new ThangComponentEditView options + @thangComponentEditView = new ThangComponentsEditView options @insertSubView @thangComponentEditView onComponentsChanged: (components) => diff --git a/app/views/kinds/CocoView.coffee b/app/views/kinds/CocoView.coffee index 3efaae08d..a562621dd 100644 --- a/app/views/kinds/CocoView.coffee +++ b/app/views/kinds/CocoView.coffee @@ -86,6 +86,8 @@ module.exports = class CocoView extends Backbone.View render: -> return @ unless me + view.destroy() for id, view of @subviews + @subviews = {} super() return @template if _.isString(@template) @$el.html @template(@getRenderData()) @@ -248,17 +250,29 @@ module.exports = class CocoView extends Backbone.View # Subviews insertSubView: (view, elToReplace=null) -> - key = view.id or (view.constructor.name+classCount++) - key = _.string.underscored(key) # handy for autocomplete in dev console + # used to insert views with ids + key = @makeSubViewKey(view) @subviews[key].destroy() if key of @subviews elToReplace ?= @$el.find('#'+view.id) elToReplace.after(view.el).remove() - view.parent = @ + @registerSubView(view, key) view.render() view.afterInsert() + view + + registerSubView: (view, key) -> + # used to register views which are custom inserted into the view, + # like views where you add multiple instances of them + key = @makeSubViewKey(view) + view.parent = @ view.parentKey = key @subviews[key] = view view + + makeSubViewKey: (view) -> + key = view.id or (view.constructor.name+classCount++) + key = _.string.underscored(key) # handy for autocomplete in dev console + key removeSubView: (view) -> view.$el.empty() diff --git a/bower.json b/bower.json index 32c815408..a909afb13 100644 --- a/bower.json +++ b/bower.json @@ -40,7 +40,7 @@ "jsondiffpatch": "~0.1.5", "nanoscroller": "~0.8.0", "jquery.tablesorter": "~2.15.13", - "treema": "~0.0.9", + "treema": "~0.0.12", "bootstrap": "~3.1.1", "validated-backbone-mediator": "~0.1.3", "jquery.browser": "~0.0.6", diff --git a/test/app/require.spec.coffee b/test/app/require.spec.coffee index 7af4f6b9e..33ebcf846 100644 --- a/test/app/require.spec.coffee +++ b/test/app/require.spec.coffee @@ -1,6 +1,6 @@ describe 'require', -> it 'has no modules that error when you import them', -> - modules = window.require.list(); + modules = window.require.list() for module in modules try require(module) diff --git a/test/app/utils.coffee b/test/app/utils.coffee deleted file mode 100644 index c8d2cebc0..000000000 --- a/test/app/utils.coffee +++ /dev/null @@ -1,2 +0,0 @@ -module.exports.sendTestResponses = (responseMap) -> - \ No newline at end of file