diff --git a/.gitignore b/.gitignore index b23c1cdea..aad805e24 100644 --- a/.gitignore +++ b/.gitignore @@ -4,49 +4,51 @@ # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile ~/.gitignore_global +# Ignore .swp files +.*.swp + # Ignore bundler config /.bundle +# Ignore mac stuff +.DS_Store + +# Ignore Rubymine config +/.idea + +# Ignore .project files +/.project + +# Ignore .rbenv-vars +/.rbenv-vars + # Ignore database config /config/database.yml +# Ignore coverage stuff +/coverage + # Ignore the default SQLite database. /db/*.sqlite3 # Ignore local changes to schema.rb (e. g. through extentions) /db/schema.rb +# Ignore custom gem file +/Gemfile.local + +# Ignore node modules +/node_modules + # Ignore all logfiles and tempfiles. /log -/tmp/* -/tmp/pids/* /public/assets/*.* /public/assets/app /public/assets/custom +/tmp/* +/tmp/pids/* # except /tmp/pids/ which is needed for certain Zammad processes !/tmp !/tmp/pids !/tmp/pids/.keep - -# Ignore custom gem file -/Gemfile.local - -# Ignore .project files -/.project - -# Ignore local database settings -/config/database.yml - -# Ignore mac stuff -.DS_Store - -# Ignore .swp files -.*.swp - -# Ignore coverage stuff -/coverage - -# Ignore Rubymine config -/.idea -node_modules diff --git a/Gemfile b/Gemfile index 52624c5c0..11b2c490d 100644 --- a/Gemfile +++ b/Gemfile @@ -12,6 +12,7 @@ gem 'activerecord-session_store' gem 'json' # Supported DBs +gem 'activerecord-nulldb-adapter', group: :nulldb gem 'mysql2', group: :mysql gem 'pg', group: :postgres @@ -113,7 +114,8 @@ group :development, :test do gem 'github_changelog_generator' end -gem 'puma' +gem 'puma', group: :puma +gem 'unicorn', group: :unicorn # load onw gem's local_gemfile = File.join(File.dirname(__FILE__), 'Gemfile.local') diff --git a/Gemfile.lock b/Gemfile.lock index d2ffa73ec..faac04b1b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -30,6 +30,8 @@ GEM activemodel (= 4.2.7.1) activesupport (= 4.2.7.1) arel (~> 6.0) + activerecord-nulldb-adapter (0.3.6) + activerecord (>= 2.0.0) activerecord-session_store (1.0.0) actionpack (>= 4.0, < 5.1) activerecord (>= 4.0, < 5.1) @@ -152,6 +154,7 @@ GEM inflection (1.0.0) json (1.8.3) jwt (1.5.4) + kgio (2.11.0) koala (2.4.0) addressable faraday @@ -271,6 +274,7 @@ GEM rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.1.0) + raindrops (0.17.0) rake (11.2.2) rb-fsevent (0.9.7) rb-inotify (0.9.7) @@ -371,6 +375,9 @@ GEM unf_ext unf_ext (0.0.7.2) unicode-display_width (1.1.1) + unicorn (5.2.0) + kgio (~> 2.6) + raindrops (~> 0.7) websocket (1.2.3) writeexcel (1.0.5) zendesk_api (1.14.0) @@ -385,6 +392,7 @@ PLATFORMS ruby DEPENDENCIES + activerecord-nulldb-adapter activerecord-session_store autoprefixer-rails biz @@ -447,6 +455,7 @@ DEPENDENCIES therubyracer twitter uglifier + unicorn writeexcel zendesk_api diff --git a/app/assets/javascripts/app/controllers/ticket_overview.coffee b/app/assets/javascripts/app/controllers/ticket_overview.coffee index c7f3ae44d..670ab5934 100644 --- a/app/assets/javascripts/app/controllers/ticket_overview.coffee +++ b/app/assets/javascripts/app/controllers/ticket_overview.coffee @@ -1,13 +1,372 @@ class App.TicketOverview extends App.Controller className: 'overviews' activeFocus: 'nav' + mouse: + x: null + y: null + + elements: + '.js-batch-overlay': 'batchOverlay' + '.js-batch-overlay-backdrop': 'batchOverlayBackdrop' + '.js-batch-cancel': 'batchCancel' + '.js-batch-macro-circle': 'batchMacroCircle' + '.js-batch-assign-circle': 'batchAssignCircle' + '.js-batch-assign': 'batchAssign' + '.js-batch-macro': 'batchMacro' + + events: + 'mousedown .item': 'startDragItem' + 'mouseenter .js-batch-overlay-entry': 'highlightBatchEntry' + 'mouseleave .js-batch-overlay-entry': 'unhighlightBatchEntry' constructor: -> super @render() + startDragItem: (event) -> + @grabbedItem = $(event.currentTarget) + offset = @grabbedItem.offset() + @batchDragger = $(App.view('ticket_overview/batch_dragger')()) + @grabbedItemClone = @grabbedItem.clone() + @grabbedItemClone.data('offset', @grabbedItem.offset()) + @grabbedItemClone.addClass('batch-dragger-item js-main-item') + @batchDragger.append @grabbedItemClone + + @batchDragger.data + startX: event.pageX + startY: event.pageY + dx: Math.min(event.pageX - offset.left, 180) + dy: event.pageY - offset.top + moved: false + + $(document).on 'mousemove.item', @dragItem + $(document).one 'mouseup.item', @endDragItem + # TODO: fire @cancelDrag on ESC + + dragItem: (event) => + event.preventDefault() + pos = @batchDragger.data() + threshold = 3 + x = event.pageX - pos.dx + y = event.pageY - pos.dy + dir = if event.pageY > pos.startY then 1 else -1 + + if !pos.moved + if Math.abs(event.pageX - pos.startX) > threshold or Math.abs(event.pageY - pos.startY) > threshold + @batchDragger.data 'moved', true + # check grabbed items batch checkbox to make sure its checked + # (could be grabbed without checking the checkbox it) + @grabbedItemWasntChecked = !@grabbedItem.find('[name="bulk"]').prop('checked') + @grabbedItem.find('[name="bulk"]').prop('checked', true) + @grabbedItemClone.find('[name="bulk"]').prop('checked', true) + + additionalItems = @el.find('[name="bulk"]:checked').parents('.item').not(@grabbedItem) + additionalItemsClones = additionalItems.clone() + @draggedItems = @grabbedItemClone.add(additionalItemsClones) + # store offsets for later use + additionalItemsClones.each (i, item) -> $(@).data('offset', additionalItems.eq(i).offset()) + @batchDragger.prepend additionalItemsClones.addClass('batch-dragger-item').get().reverse() + if(additionalItemsClones.length) + @batchDragger.find('.js-batch-dragger-count').text(@draggedItems.length) + + $('#app').append @batchDragger + + @draggedItems.each (i, item) => + dx = $(item).data('offset').left - $(item).offset().left - x + dy = $(item).data('offset').top - $(item).offset().top - y + $.Velocity.hook item, 'translateX', "#{dx}px" + $.Velocity.hook item, 'translateY', "#{dy}px" + + @moveDraggedItems(-dir) + + @mouseY = event.pageY + @showBatchOverlay() + else + return + + $.Velocity.hook @batchDragger, 'translateX', "#{x}px" + $.Velocity.hook @batchDragger, 'translateY', "#{y}px" + + endDragItem: (event) => + $(document).off 'mousemove.item' + $(document).off 'mouseup.item' + pos = @batchDragger.data() + + if !@hoveredBatchEntry + @cleanUpDrag() + return + + $.Velocity.hook @batchDragger, 'transformOriginX', "#{pos.dx}px" + $.Velocity.hook @batchDragger, 'transformOriginY', "#{pos.dy}px" + @hoveredBatchEntry.velocity + properties: + scale: 1.1 + options: + duration: 200 + complete: => + @hoveredBatchEntry.velocity "reverse", + duration: 200 + complete: => + # clean scale + @hoveredBatchEntry.removeAttr('style') + @cleanUpDrag(true) + @performBatchAction @hoveredBatchEntry.attr('data-action') + @hoveredBatchEntry = null + @batchDragger.velocity + properties: + scale: 0 + options: + duration: 200 + + cancelDrag: -> + $(document).off 'mousemove.item' + $(document).off 'mouseup.item' + @cleanUpDrag() + + cleanUpDrag: (success) -> + @hideBatchOverlay() + $('.batch-dragger').remove() + + if @grabbedItemWasntChecked + @grabbedItem.find('[name="bulk"]').prop('checked', false) + + if success + # uncheck all checked items + @el.find('[name="bulk"]:checked').prop('checked', false) + @el.find('[name="bulk_all"]').prop('checked', false) + + moveDraggedItems: (dir) -> + @draggedItems.velocity + properties: + translateX: 0 + translateY: (i) => dir * i * @batchDragger.height()/2 + options: + easing: 'ease-in-out' + duration: 300 + + @batchDragger.find('.js-batch-dragger-count').velocity + properties: + translateY: if dir < 0 then 0 else -@batchDragger.height()+8 + options: + easing: 'ease-in-out' + duration: 300 + + performBatchAction: (action) -> + console.log "perform action #{action} on checked items" + + showBatchOverlay: -> + @batchOverlay.show() + @batchOverlayBackdrop.velocity { opacity: [1, 0] }, { duration: 500 } + @batchOverlayShown = true + $(document).on 'mousemove.batchoverlay', @controlBatchOverlay + + hideBatchOverlay: -> + $(document).off 'mousemove.batchoverlay' + @batchOverlayShown = false + @batchOverlayBackdrop.velocity { opacity: [0, 1] }, { duration: 300, queue: false } + @hideBatchCircles => + @batchOverlay.hide() + + if @batchAssignShown + @hideBatchAssign() + + if @batchMacroShown + @hideBatchMacro() + + controlBatchOverlay: (event) => + # store to detect if the mouse is hovering a drag-action entry + # after an animation ended -> @highlightBatchEntryAtMousePosition + @mouse.x = event.pageX + @mouse.y = event.pageY + + if event.pageY <= window.innerHeight/5*2 + mouseInArea = 'top' + else if event.pageY > window.innerHeight/5*2 && event.pageY <= window.innerHeight/5*3 + mouseInArea = 'middle' + else + mouseInArea = 'bottom' + + switch mouseInArea + when 'top' + if !@batchMacroShown + @hideBatchCircles() + @showBatchMacro() + @moveDraggedItems(1) + + when 'middle' + if @batchAssignShown + @hideBatchAssign() + + if @batchMacroShown + @hideBatchMacro() + + if !@batchCirclesShown + @showBatchCircles() + + when 'bottom' + if !@batchAssignShown + @hideBatchCircles() + @showBatchAssign() + @moveDraggedItems(-1) + + showBatchCircles: -> + @batchCirclesShown = true + + @batchMacroCircle.velocity + properties: + translateY: [0, '-150%'] + opacity: [1, 0] + options: + easing: [1,-.55,.2,1.37] + duration: 500 + visibility: 'visible' + delay: 200 + + @batchAssignCircle.velocity + properties: + translateY: [0, '150%'] + opacity: [1, 0] + options: + easing: [1,-.55,.2,1.37] + duration: 500 + visibility: 'visible' + delay: 200 + + hideBatchCircles: (callback) -> + @batchMacroCircle.velocity + properties: + translateY: ['-150%', 0] + opacity: [0, 1] + options: + duration: 300 + visibility: 'hidden' + queue: false + + @batchAssignCircle.velocity + properties: + translateY: ['150%', 0] + opacity: [0, 1] + options: + duration: 300 + complete: callback + visibility: 'hidden' + queue: false + + @batchCirclesShown = false + + showBatchAssign: -> + return if !@batchOverlayShown # user might have dropped the item already + @batchAssignShown = true + + @batchAssign.velocity + properties: + translateY: [0, '100%'] + opacity: [1, 0] + options: + easing: [1,-.55,.2,1.37] + duration: 500 + visibility: 'visible' + complete: @highlightBatchEntryAtMousePosition + delay: if @batchCirclesShown then 0 else 200 + + @batchCancel.css + top: 0 + bottom: 'auto' + + @batchCancel.velocity + properties: + translateY: [0, '100%'] + opacity: [1, 0] + options: + easing: [1,-.55,.2,1.37] + duration: 500 + visibility: 'visible' + delay: if @batchCirclesShown then 0 else 200 + + hideBatchAssign: -> + @batchAssign.velocity + properties: + translateY: ['100%', 0] + opacity: [0, 1] + options: + duration: 300 + visibility: 'hidden' + queue: false + + @batchCancel.velocity + properties: + translateY: ['100%', 0] + opacity: [0, 1] + options: + duration: 300 + visibility: 'hidden' + queue: false + + @batchAssignShown = false + + showBatchMacro: -> + return if !@batchOverlayShown # user might have dropped the item already + @batchMacroShown = true + + @batchMacro.velocity + properties: + translateY: [0, '-100%'] + opacity: [1, 0] + options: + easing: [1,-.55,.2,1.37] + duration: 500 + visibility: 'visible' + complete: @highlightBatchEntryAtMousePosition + delay: if @batchCirclesShown then 0 else 200 + + @batchCancel.css + top: 'auto' + bottom: 0 + @batchCancel.velocity + properties: + translateY: [0, '-100%'] + opacity: [1, 0] + options: + easing: [1,-.55,.2,1.37] + duration: 500 + visibility: 'visible' + delay: if @batchCirclesShown then 0 else 200 + + hideBatchMacro: -> + @batchMacro.velocity + properties: + translateY: ['-100%', 0] + opacity: [0, 1] + options: + duration: 300 + visibility: 'hidden' + queue: false + + @batchCancel.velocity + properties: + translateY: ['-100%', 0] + opacity: [0, 1] + options: + duration: 300 + visibility: 'hidden' + queue: false + + @batchMacroShown = false + + highlightBatchEntryAtMousePosition: => + entryAtPoint = $(document.elementFromPoint(@mouse.x, @mouse.y)).closest('.js-batch-overlay-entry') + if(entryAtPoint.length) + @hoveredBatchEntry = entryAtPoint.addClass('is-hovered') + + highlightBatchEntry: (event) -> + @hoveredBatchEntry = $(event.currentTarget).addClass('is-hovered') + + unhighlightBatchEntry: (event) -> + @hoveredBatchEntry = null + $(event.currentTarget).removeClass('is-hovered') + render: -> - elLocal = $(App.view('ticket_overview')()) + elLocal = $(App.view('ticket_overview/index')()) @navBarControllerVertical = new Navbar el: elLocal.find('.overview-header') @@ -397,10 +756,10 @@ class Table extends App.Controller ) table = $(table) table.delegate('[name="bulk_all"]', 'click', (e) -> - if $(e.target).attr('checked') - $(e.target).closest('table').find('[name="bulk"]').attr('checked', true) + if $(e.currentTarget).prop('checked') + $(e.currentTarget).closest('table').find('[name="bulk"]').prop('checked', true) else - $(e.target).closest('table').find('[name="bulk"]').attr('checked', false) + $(e.currentTarget).closest('table').find('[name="bulk"]').prop('checked', false) ) @$('.table-overview').append(table) else @@ -440,6 +799,25 @@ class Table extends App.Controller @bulkForm.hide() else @bulkForm.show() + + if @lastChecked && e.shiftKey + # check items in a row + currentItem = $(e.currentTarget).parents('.item') + lastCheckedItem = @lastChecked.parents('.item') + items = currentItem.parent().children() + + if currentItem.index() > lastCheckedItem.index() + # current item is below last checked item + startId = lastCheckedItem.index() + endId = currentItem.index() + else + # current item is above last checked item + startId = currentItem.index() + endId = lastCheckedItem.index() + + items.slice(startId+1, endId).find('[name="bulk"]').prop('checked', (-> !@checked)) + + @lastChecked = $(e.currentTarget) callbackIconHeader = (headers) -> attribute = name: 'icon' @@ -523,8 +901,8 @@ class Table extends App.Controller # deselect bulk_all if one item is uncheck observ @$('.table-overview').delegate('[name="bulk"]', 'click', (e) -> - if !$(e.target).attr('checked') - $(e.target).parents().find('[name="bulk_all"]').attr('checked', false) + if !$(e.target).prop('checked') + $(e.target).parents().find('[name="bulk_all"]').prop('checked', false) ) getSelected: -> @@ -540,7 +918,7 @@ class Table extends App.Controller ticketId = $(element).val() for ticketIdSelected in ticketIDs if ticketIdSelected is ticketId - $(element).attr('checked', true) + $(element).prop('checked', true) ) viewmode: (e) => diff --git a/app/assets/javascripts/app/views/ticket_overview.jst.eco b/app/assets/javascripts/app/views/ticket_overview.jst.eco deleted file mode 100644 index f4e27af10..000000000 --- a/app/assets/javascripts/app/views/ticket_overview.jst.eco +++ /dev/null @@ -1,5 +0,0 @@ -
-