mirror of
https://github.com/status-im/consul.git
synced 2025-01-28 22:45:58 +00:00
5a2fc5bffb
This includes an update of our CSS npm module. The majority of this is just moving files from one folder to another. 1. %breadrumbs: As well as moving we've added 'milestone' breadcrumbs 2. %checkbox-group: Moved 3. %radio-group: Moved 4. %sliding-toggle: Moved (used to be called just %toggle) 5. %form-elements: Moved and added a new %inline-alert for form field messaging (see Structure design system) 6. %action-group is now a composition of %menu-panel, %toggle-button, plus edits to existing style to bring the %action-groups inline with the dropdowns from Structure). %action-group also includes a composed %confirmation-alert that is yet to be include. This will be compiled out until we integrate it. We've also removed some of our old icon placeholders as the above work seased to use some of them. Now we done this I'd say all in all over half of our CSS components now use the CSS npm module. The CSS specific to Consul UI also uses much of this CSS module by way of helper placeholders such as our %frames.
289 lines
9.4 KiB
JavaScript
289 lines
9.4 KiB
JavaScript
import CollectionComponent 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 'block-slots';
|
|
import WithResizing from 'consul-ui/mixins/with-resizing';
|
|
import style from 'ember-computed-style';
|
|
|
|
import { inject as service } from '@ember/service';
|
|
import { computed, get, set } from '@ember/object';
|
|
/**
|
|
* Heavily extended `ember-collection` component
|
|
* This adds support for z-index calculations to enable
|
|
* Popup menus to pop over either rows above or below
|
|
* the popup.
|
|
* Additionally adds calculations for figuring out what the height
|
|
* of the tabular component should be depending on the other elements
|
|
* in the page.
|
|
* Currently everything is here together for clarity, but to be split up
|
|
* in the future
|
|
*/
|
|
|
|
// need to copy Cell in wholesale as there is no way to import it
|
|
// there is no change made to `Cell` here, its only here as its
|
|
// private in `ember-collection`
|
|
// 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;
|
|
}
|
|
}
|
|
// this is an amount of rows in the table NOT items
|
|
// unlikely to have 10000 DOM rows ever :)
|
|
const maxZIndex = 10000;
|
|
// Adds z-index styling to the default Grid
|
|
class ZIndexedGrid extends Grid {
|
|
formatItemStyle(index, w, h, checked) {
|
|
let style = super.formatItemStyle(index, w, h);
|
|
// count backwards from maxZIndex
|
|
let zIndex = maxZIndex - index;
|
|
// apart from the row that contains an opened dropdown menu
|
|
// this one should be highest z-index, so use max plus 1
|
|
if (checked == index) {
|
|
zIndex = maxZIndex + 1;
|
|
}
|
|
style += 'z-index: ' + zIndex;
|
|
return style;
|
|
}
|
|
}
|
|
/**
|
|
* The tabular-collection can contain 'actions' the UI for which
|
|
* uses dropdown 'action groups', so a group of different actions.
|
|
* State makes use of native HTML state using radiogroups
|
|
* to ensure that only a single dropdown can be open at one time.
|
|
* Therefore we listen to change events to do anything extra when
|
|
* a dropdown is opened (the change function is bound to the instance of
|
|
* the `tabular-component` on init, hoisted here for visibility)
|
|
*
|
|
* The extra functionality we have here is to detect whether the opened
|
|
* dropdown menu would be cut off or not if it 'dropped down'.
|
|
* If it would be cut off we use CSS to 'drop it up' instead.
|
|
* We also set this row to have the max z-index here, and mark this
|
|
* row as the 'checked row' for when a scroll/grid re-calculation is
|
|
* performed
|
|
*/
|
|
const change = function(e) {
|
|
if (e instanceof MouseEvent) {
|
|
return;
|
|
}
|
|
// TODO: Why am I getting a jQuery event here?!
|
|
if (e instanceof Event) {
|
|
const value = e.currentTarget.value;
|
|
if (value != get(this, 'checked')) {
|
|
set(this, 'checked', value);
|
|
// 'actions_close' would mean that all menus have been closed
|
|
// therefore we don't need to calculate
|
|
if (e.currentTarget.getAttribute('id') !== 'actions_close') {
|
|
const dom = get(this, 'dom');
|
|
|
|
const $tr = dom.closest('tr', e.currentTarget);
|
|
const $group = dom.sibling(e.currentTarget, 'div');
|
|
const groupRect = $group.getBoundingClientRect();
|
|
const groupBottom = groupRect.top + $group.clientHeight;
|
|
|
|
const $footer = dom.element('footer[role="contentinfo"]');
|
|
const footerRect = $footer.getBoundingClientRect();
|
|
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);
|
|
}
|
|
} else if (e.detail && e.detail.index) {
|
|
if (e.detail.confirming) {
|
|
this.confirming.push(e.detail.index);
|
|
} else {
|
|
const pos = this.confirming.indexOf(e.detail.index);
|
|
if (pos !== -1) {
|
|
this.confirming.splice(pos, 1);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
export default CollectionComponent.extend(SlotsMixin, WithResizing, {
|
|
tagName: 'table',
|
|
classNames: ['dom-recycling'],
|
|
classNameBindings: ['hasActions'],
|
|
attributeBindings: ['style'],
|
|
width: 1150,
|
|
rowHeight: 50,
|
|
maxHeight: 500,
|
|
style: style('getStyle'),
|
|
checked: null,
|
|
hasCaption: false,
|
|
dom: service('dom'),
|
|
init: function() {
|
|
this._super(...arguments);
|
|
this.change = change.bind(this);
|
|
this.confirming = [];
|
|
// TODO: The row height should auto calculate properly from the CSS
|
|
this['cell-layout'] = new ZIndexedGrid(get(this, 'width'), get(this, 'rowHeight'));
|
|
},
|
|
getStyle: computed('rowHeight', '_items', 'maxRows', 'maxHeight', function() {
|
|
const maxRows = get(this, 'rows');
|
|
let height = get(this, 'maxHeight');
|
|
if (maxRows) {
|
|
let rows = Math.max(3, get(this._items || [], 'length'));
|
|
rows = Math.min(maxRows, rows);
|
|
height = get(this, 'rowHeight') * rows + 29;
|
|
}
|
|
return {
|
|
height: height,
|
|
};
|
|
}),
|
|
resize: function(e) {
|
|
const $tbody = this.element;
|
|
const dom = get(this, 'dom');
|
|
const $appContent = dom.element('main > div');
|
|
if ($appContent) {
|
|
const border = 1;
|
|
const rect = $tbody.getBoundingClientRect();
|
|
const $footer = dom.element('footer[role="contentinfo"]');
|
|
const space = rect.top + $footer.clientHeight + border;
|
|
const height = e.detail.height - space;
|
|
this.set('maxHeight', Math.max(0, height));
|
|
// TODO: The row height should auto calculate properly from the CSS
|
|
this['cell-layout'] = new ZIndexedGrid($appContent.clientWidth, get(this, 'rowHeight'));
|
|
this.updateItems();
|
|
this.updateScrollPosition();
|
|
}
|
|
},
|
|
willRender: function() {
|
|
this._super(...arguments);
|
|
set(this, 'hasCaption', this._isRegistered('caption'));
|
|
set(this, 'hasActions', this._isRegistered('actions'));
|
|
},
|
|
// `ember-collection` bug workaround
|
|
// https://github.com/emberjs/ember-collection/issues/138
|
|
_needsRevalidate: function() {
|
|
if (this.isDestroyed || this.isDestroying) {
|
|
return;
|
|
}
|
|
if (this._isGlimmer2()) {
|
|
this.rerender();
|
|
} else {
|
|
needsRevalidate(this);
|
|
}
|
|
},
|
|
// need to overwrite this completely so I can pass through the checked index
|
|
// unfortunately the nicest way I could think to do this is to copy this in wholesale
|
|
// to add an extra argument for `formatItemStyle` in 3 places
|
|
// tradeoff between changing as little code as possible in the original code
|
|
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) {
|
|
return get(this, 'dom').clickFirstAnchor(e);
|
|
},
|
|
},
|
|
});
|