diff --git a/ui-v2/app/components/tabular-collection.js b/ui-v2/app/components/tabular-collection.js index 11830f0866..c6716eac5b 100644 --- a/ui-v2/app/components/tabular-collection.js +++ b/ui-v2/app/components/tabular-collection.js @@ -1,21 +1,40 @@ import Component from 'ember-collection/components/ember-collection'; import needsRevalidate from 'ember-collection/utils/needs-revalidate'; +import identity from 'ember-collection/utils/identity'; import Grid from 'ember-collection/layouts/grid'; import SlotsMixin from 'ember-block-slots'; import style from 'ember-computed-style'; import { computed, get, set } from '@ember/object'; -const $$ = document.querySelectorAll.bind(document); +const $$ = function(sel, context = document) { + return context.querySelectorAll(sel); +}; const createSizeEvent = function(detail) { return { detail: { width: window.innerWidth, height: window.innerHeight }, }; }; +// need to copy this in wholesale as there is no way to import it +// TODO: separate both Cell and ZIndexedGrid out +class Cell { + constructor(key, item, index, style) { + this.key = key; + this.hidden = false; + this.item = item; + this.index = index; + this.style = style; + } +} +const maxZIndex = 10000; class ZIndexedGrid extends Grid { - formatItemStyle(index, w, h) { - let style = super.formatItemStyle(...arguments); - style += 'z-index: ' + (10000 - index); + formatItemStyle(index, w, h, checked) { + let style = super.formatItemStyle(index, w, h); + let zIndex = maxZIndex - index; + if (checked == index) { + zIndex = maxZIndex + 1; + } + style += 'z-index: ' + zIndex; return style; } } @@ -37,6 +56,21 @@ const change = function(e) { const value = e.currentTarget.value; if (value != get(this, 'checked')) { set(this, 'checked', value); + if (e.currentTarget.getAttribute('id') !== 'actions_close') { + const $tr = closest('tr', e.currentTarget); + const $group = [...$('~ ul', e.currentTarget)][0]; + const $footer = [...$$('footer[role="contentinfo"]')][0]; + const groupRect = $group.getBoundingClientRect(); + const footerRect = $footer.getBoundingClientRect(); + const groupBottom = groupRect.top + $group.clientHeight; + const footerTop = footerRect.top; + if (groupBottom > footerTop) { + $group.classList.add('above'); + } else { + $group.classList.remove('above'); + } + $tr.style.zIndex = maxZIndex + 1; + } } else { set(this, 'checked', null); } @@ -109,6 +143,108 @@ export default Component.extend(SlotsMixin, { needsRevalidate(this); } }, + // need to overwrite this completely so I can pass through the checked index + // for `formatItemStyle` in 3 places + updateCells: function() { + if (!this._items) { + return; + } + const numItems = get(this._items, 'length'); + if (this._cellLayout.length !== numItems) { + this._cellLayout.length = numItems; + } + + var priorMap = this._cellMap; + var cellMap = Object.create(null); + + var index = this._cellLayout.indexAt( + this._scrollLeft, + this._scrollTop, + this._clientWidth, + this._clientHeight + ); + var count = this._cellLayout.count( + this._scrollLeft, + this._scrollTop, + this._clientWidth, + this._clientHeight + ); + var items = this._items; + var bufferBefore = Math.min(index, this._buffer); + index -= bufferBefore; + count += bufferBefore; + count = Math.min(count + this._buffer, get(items, 'length') - index); + var i, style, itemIndex, itemKey, cell; + + var newItems = []; + + for (i = 0; i < count; i++) { + itemIndex = index + i; + itemKey = identity(items.objectAt(itemIndex)); + if (priorMap) { + cell = priorMap[itemKey]; + } + if (cell) { + // additional `checked` argument + style = this._cellLayout.formatItemStyle( + itemIndex, + this._clientWidth, + this._clientHeight, + this.checked + ); + set(cell, 'style', style); + set(cell, 'hidden', false); + set(cell, 'key', itemKey); + cellMap[itemKey] = cell; + } else { + newItems.push(itemIndex); + } + } + + for (i = 0; i < this._cells.length; i++) { + cell = this._cells[i]; + if (!cellMap[cell.key]) { + if (newItems.length) { + itemIndex = newItems.pop(); + let item = items.objectAt(itemIndex); + itemKey = identity(item); + // additional `checked` argument + style = this._cellLayout.formatItemStyle( + itemIndex, + this._clientWidth, + this._clientHeight, + this.checked + ); + set(cell, 'style', style); + set(cell, 'key', itemKey); + set(cell, 'index', itemIndex); + set(cell, 'item', item); + set(cell, 'hidden', false); + cellMap[itemKey] = cell; + } else { + set(cell, 'hidden', true); + set(cell, 'style', 'height: 0; display: none;'); + } + } + } + + for (i = 0; i < newItems.length; i++) { + itemIndex = newItems[i]; + let item = items.objectAt(itemIndex); + itemKey = identity(item); + // additional `checked` argument + style = this._cellLayout.formatItemStyle( + itemIndex, + this._clientWidth, + this._clientHeight, + this.checked + ); + cell = new Cell(itemKey, item, itemIndex, style); + cellMap[itemKey] = cell; + this._cells.pushObject(cell); + } + this._cellMap = cellMap; + }, actions: { click: function(e) { const name = e.target.nodeName.toLowerCase(); diff --git a/ui-v2/app/styles/components/action-group.scss b/ui-v2/app/styles/components/action-group.scss index 51685c970a..b4f324c114 100644 --- a/ui-v2/app/styles/components/action-group.scss +++ b/ui-v2/app/styles/components/action-group.scss @@ -70,19 +70,30 @@ %action-group ul { position: absolute; right: -10px; - top: 35px; padding: 1px; } %action-group ul::before { position: absolute; right: 18px; - top: -6px; content: ''; display: block; width: 10px; height: 10px; +} +%action-group ul:not(.above) { + top: 35px; +} +%action-group ul:not(.above)::before { + top: -6px; transform: rotate(45deg); } +%action-group ul.above { + bottom: 35px; +} +%action-group ul.above::before { + bottom: -6px; + transform: rotate(225deg); +} %action-group li { position: relative; z-index: 1;