ru.coon 2.6.22 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,178 @@
1
+ Ext.define('Coon.report.plugin.grid.AddFilterConditionPlugin', {
2
+ extend: 'Ext.AbstractPlugin',
3
+ alias: 'plugin.AddFilterConditionPlugin',
4
+ uses: [],
5
+ requires: [
6
+ 'Ext.grid.Panel'
7
+ ],
8
+ configurePanelWizard: 'AddFilterConditionPluginConfigPanel',
9
+
10
+ filters: new Map(),
11
+
12
+ init: function(grid) {
13
+ this.grid = grid;
14
+ grid.on('added', function(grid) {
15
+ const contextMenu = grid.contextMenu;
16
+ contextMenu.add(new Ext.menu.Item({
17
+ iconCls: 'svg-icon svg-icon-filter-alt',
18
+ text: 'фильтр по значению',
19
+ itemId: 'filterByCellValue',
20
+ handler: this.addFilter.bind(this),
21
+ }));
22
+ contextMenu.add(new Ext.menu.Item({
23
+ text: 'Отменить фильтр для столбца',
24
+ hidden: true,
25
+ itemId: 'cancelCurrentColumnFilter',
26
+ handler: this.clearCurrentColumnFilter.bind(this),
27
+ }));
28
+ contextMenu.add(new Ext.menu.Item({
29
+ text: 'Отменить все',
30
+ itemId: 'cancelAllFilters',
31
+ handler: this.clearAllFilters.bind(this),
32
+ }));
33
+ }, this);
34
+ grid.on('load', function() {
35
+ this.clearAllFilters();
36
+ }, this);
37
+ grid.contextMenu.on('requestMenuItems', this.onShowMenu, this);
38
+ },
39
+
40
+ onShowMenu({record, cellIndex}) {
41
+ const cancelAllItem = this.findMenuItem('cancelAllFilters');
42
+ cancelAllItem && cancelAllItem.setDisabled(this.filters.size === 0);
43
+ if (this.filters.size) {
44
+ this.findMenuItem('cancelCurrentColumnFilter').hide();
45
+ }
46
+ this.findMenuItem('filterByCellValue').hide();
47
+ record && this.setMenuText(record, cellIndex);
48
+ },
49
+
50
+ addFilter: function() {
51
+ if (!this.grid) {
52
+ return;
53
+ }
54
+ const key = this.getColumnKey();
55
+ this.filters.set(key, this.columnValue);
56
+ this.doFilter();
57
+ this.changeColumnStyle(this.column);
58
+ },
59
+
60
+ changeColumnStyle(column) {
61
+ if (this.filters.has(column.dataIndex)) {
62
+ column.addCls('filtered-column');
63
+ } else {
64
+ column.removeCls('filtered-column');
65
+ }
66
+ },
67
+
68
+ doFilter() {
69
+ if (this.filters.size) {
70
+ this.grid.getStore().suspendEvents();
71
+ this.grid.getStore().clearFilter();
72
+ this.grid.getStore().resumeEvents();
73
+ this.grid.getStore().filter(
74
+ [...this.filters.entries()].map(function([property, value]) {
75
+ if (Ext.isString(value) || Ext.isNumber(value)) {
76
+ return {property, operator: '=', value};
77
+ } else if (Ext.isDate(value)) {
78
+ return new Ext.util.Filter({
79
+ filterFn: function(rec) {
80
+ return Ext.Date.format(rec.get(property), Coon.format.dateTime) === Ext.Date.format(value, Coon.format.dateTime);
81
+ },
82
+ }, this);
83
+ } else {
84
+ return new Ext.util.Filter({
85
+ filterFn: function(rec) {
86
+ return rec.get(property) === value;
87
+ },
88
+ }, this);
89
+ }
90
+ }, this)
91
+ );
92
+ } else {
93
+ this.grid.getStore().clearFilter();
94
+ }
95
+ },
96
+
97
+ isColumnFiltered() {
98
+ return this.filters.has(this.getColumnKey());
99
+ },
100
+
101
+ showCancelMenuItems: function() {
102
+ const cancelCurrentItem = this.findMenuItem('cancelCurrentColumnFilter');
103
+ cancelCurrentItem && cancelCurrentItem.setHidden(!this.isColumnFiltered());
104
+ },
105
+
106
+ findMenuItem: function(findBy) {
107
+ const contextMenu = this.grid.contextMenu;
108
+ return contextMenu.down(`#${findBy}`);
109
+ },
110
+
111
+ getColumnKey(column) {
112
+ return (column || this.column).initialConfig.dataIndex;
113
+ },
114
+
115
+ clearAllFilters: function() {
116
+ this.filters.clear();
117
+ this.doFilter();
118
+ this.grid.getColumns().forEach((column) => {
119
+ if (column.hasCls('filtered-column')) {
120
+ column.removeCls('filtered-column');
121
+ }
122
+ });
123
+ },
124
+
125
+ clearCurrentColumnFilter: function() {
126
+ this.filters.delete(this.getColumnKey());
127
+ this.doFilter();
128
+ this.changeColumnStyle(this.column);
129
+ },
130
+
131
+ isFilterable: function() {
132
+ if (!this.filterOptions[this.column.dataIndex]) {
133
+ return false;
134
+ }
135
+
136
+ if (Array.isArray(this.columnValue) || Ext.isObject(this.columnValue)) {
137
+ Coon.log.debug('Фильтрация по значению из ячейки невозможна. Сложный тип данных');
138
+ return false;
139
+ }
140
+ if (typeof this.columnValue === 'number' && isNaN(this.columnValue)) {
141
+ Coon.log.debug('Фильтрация по значению из ячейки невозможна. Значение NaN');
142
+ return false;
143
+ }
144
+ return true;
145
+ },
146
+
147
+ setMenuText(record, cellIndex) {
148
+ const contextMenu = this.grid.contextMenu;
149
+ const columns = this.grid.getColumns();
150
+ const column = columns.length && columns[cellIndex];
151
+ if (!column) {
152
+ return;
153
+ }
154
+ this.column = column;
155
+ this.columnValue = record.get(column.dataIndex);
156
+
157
+ this.showCancelMenuItems();
158
+ const foundMenuItem = contextMenu.down('#filterByCellValue');
159
+ foundMenuItem.show();
160
+ if (this.filters.size && this.filters.get(this.getColumnKey()) === this.columnValue) {
161
+ foundMenuItem.hide();
162
+ }
163
+
164
+ if (this.isFilterable()) {
165
+ foundMenuItem.setDisabled(false);
166
+ if (Ext.isDate(this.columnValue)) {
167
+ foundMenuItem.setText(`фильтр по "${Ext.Date.format(this.columnValue, column.format || Coon.format.dateTime)}"`);
168
+ } else if (Ext.isEmpty(this.columnValue)) {
169
+ foundMenuItem.setText(`фильтр по пустому значению`);
170
+ } else {
171
+ foundMenuItem.setText(`фильтр по "${this.columnValue}"`);
172
+ }
173
+ } else {
174
+ foundMenuItem.setText(`Нет возможности фильтрации`);
175
+ foundMenuItem.setDisabled(true);
176
+ }
177
+ },
178
+ });
@@ -16,16 +16,52 @@ Ext.define('Coon.report.plugin.grid.GridContextMenu', {
16
16
  }
17
17
  this.grid = component;
18
18
  this.grid.getView().on({
19
- cellcontextmenu: {fn: this.cellContextMenuHandler, scope: this},
20
- containercontextmenu: {fn: this.containerContextMenuHandler, scope: this}, /* ,
21
- destroy: {fn: this.destroyHandler, scope: this}*/
19
+ cellcontextmenu: {
20
+ fn: this.cellContextMenuHandler,
21
+ scope: this,
22
+ },
23
+ containercontextmenu: {
24
+ fn: function(view, event) {
25
+ this.showContextMenu({event, view});
26
+ event.preventDefault();
27
+ },
28
+ scope: this,
29
+ },
22
30
  });
23
31
 
24
32
  this.grid.contextMenu = Ext.create('Coon.common.tree.BaseContextMenu', {items: this.items});
33
+ this.grid.contextMenu.on('show', this.displacementContextMenu, this);
34
+ this.grid.contextMenu.on('hide', this.restoreCellQtip, this);
25
35
  this.grid.getSelectionModel().on('selectionchange', this.updateContext, this);
26
36
  this.grid.getStore().on('load', this.updateContext, this);
27
37
  },
28
38
 
39
+ /**
40
+ * Размещение контекстного меню под текстом в ячейке.
41
+ * Удаление qtip (восстанавливается при скрытии контекстного меню)
42
+ */
43
+ displacementContextMenu: function() {
44
+ const contextMenu = this.grid.contextMenu;
45
+ const cellIndex = contextMenu.cellIndex;
46
+ const columns = this.grid.getColumns();
47
+ const column = columns.length && columns[cellIndex];
48
+ if (!column || !contextMenu.record) {
49
+ return;
50
+ }
51
+ const cell = this.grid.getView().getCell(contextMenu.record, column);
52
+ this.qtipSpan = cell.querySelector('[data-qtip]');
53
+ this.qtipSpan && this.qtipSpan.removeAttribute('data-qtip');
54
+
55
+ contextMenu.alignTo(cell, 'tl-bl');
56
+
57
+ const y = contextMenu.getY();
58
+ contextMenu.setY(y - 7);
59
+ },
60
+
61
+ restoreCellQtip: function() {
62
+ this.qtipSpan && this.qtipSpan.setAttribute('data-qtip', this.columnValue);
63
+ },
64
+
29
65
  destroyHandler: function() {
30
66
  if (this.grid.contextMenu) {
31
67
  this.grid.contextMenu.destroy();
@@ -33,10 +69,6 @@ Ext.define('Coon.report.plugin.grid.GridContextMenu', {
33
69
  }
34
70
  },
35
71
 
36
- containerContextMenuHandler: function(view, e, eOpts) {
37
- this.showContextMenu(undefined, e);
38
- },
39
-
40
72
  updateContext: function() {
41
73
  const grid = this.grid;
42
74
  const selected = grid.getSelectionModel().getLastSelected();
@@ -51,17 +83,20 @@ Ext.define('Coon.report.plugin.grid.GridContextMenu', {
51
83
  grid.contextMenu.params = params && params.parameterList && Coon.Function.convertProperties(Ext.decode(params.parameterList));
52
84
  },
53
85
 
54
- cellContextMenuHandler: function(view, td, cellIndex, record, tr, rowIndex, e, eOpts) {
86
+ cellContextMenuHandler: function(view, td, cellIndex, record, tr, rowIndex, event) {
55
87
  this.grid.getSelectionModel().select(rowIndex);
56
88
  if (record) {
57
- this.showContextMenu(record, e);
89
+ this.showContextMenu({view, td, cellIndex, record, tr, rowIndex, event});
58
90
  }
59
91
  return false;
60
92
  },
61
93
 
62
- showContextMenu: function(selectedRecord, event) {
94
+ showContextMenu: function({view, td, cellIndex, record, tr, rowIndex, event}) {
63
95
  const contextMenu = this.grid.contextMenu;
64
- contextMenu.record = selectedRecord;
96
+ if (record) {
97
+ contextMenu.record = record;
98
+ }
99
+ cellIndex && (contextMenu.cellIndex = cellIndex);
65
100
  const store = this.grid.getStore();
66
101
  let params;
67
102
  if (this.grid instanceof Ext.tree.Panel) {
@@ -70,7 +105,10 @@ Ext.define('Coon.report.plugin.grid.GridContextMenu', {
70
105
  params = store.lastOptions && store.lastOptions.params;
71
106
  }
72
107
  contextMenu.params = params && params.parameterList && Coon.Function.convertProperties(Ext.decode(params.parameterList));
73
- if (contextMenu.items.length) {
108
+ contextMenu.fireEvent('requestMenuItems', {view, td, cellIndex, record, tr, rowIndex, event});
109
+ const showMenu = contextMenu.items &&
110
+ contextMenu.items.getRange().find((item) => !item.hidden);
111
+ if (showMenu) {
74
112
  contextMenu.show([event.getX(), event.getY()]);
75
113
  return this.removeBrowserContextMenu(event);
76
114
  }
@@ -5,6 +5,10 @@ Ext.define('Coon.uielement.component.UiCustomController', {
5
5
 
6
6
  init: function(view) {
7
7
  const config = view.propertyData || view.initialConfig;
8
+ if (!view || typeof view.on !== 'function') {
9
+ Coon.log.debug(` [Error]UiCustomController.init - view not found or view.on is not a function`);
10
+ return;
11
+ }
8
12
  view.on('afterrender', function() {
9
13
  const {accessDecision, securePoints} = config;
10
14
  Coon.log.debug('UiCustomController.accessProps', {accessDecision, securePoints});
@@ -3,6 +3,8 @@ Ext.define('Coon.uielement.component.formchips.Chip', {
3
3
  xtype: 'Chip',
4
4
  alias: 'widget.Chip',
5
5
  defaultListenerScope: true,
6
+ searchRequired: false,
7
+
6
8
  config: {
7
9
  // Служебный
8
10
  service: false,
@@ -10,6 +12,12 @@ Ext.define('Coon.uielement.component.formchips.Chip', {
10
12
  unusable: undefined,
11
13
  // С кнопкой закрытия
12
14
  closable: undefined,
15
+ // Чип критерия для удаленного поиска (если false то для локальной фильтрации в ReportPanel)
16
+ forRemoteSearch: true,
17
+ // Всплывающая подсказка для кнопки закрытия чипа
18
+ deleteChipTooltipMessage: 'Очистить поле формы',
19
+ // Функция колбэка, срабатывающая при закрытии чипа
20
+ removeFilterCallback: undefined,
13
21
  },
14
22
  tpl: new Ext.XTemplate(
15
23
  '<span class="chip-value">{value}</span>',
@@ -22,15 +30,30 @@ Ext.define('Coon.uielement.component.formchips.Chip', {
22
30
  },
23
31
 
24
32
  initComponent: function() {
33
+ if (this.forRemoteSearch && this.data) {
34
+ this.originalValue = this.data.value;
35
+ }
36
+
37
+ this.configureCls();
38
+ this.callParent();
39
+ },
40
+
41
+ configureCls() {
42
+ const cls = [];
25
43
  if (this.getService()) {
26
- this.cls = 'service-chip';
44
+ cls.push('service-chip');
27
45
  } else {
28
- this.cls = this.getUnusable() ?
46
+ cls.push(this.getUnusable() ?
29
47
  'unusable-chip':
30
- this.getClosable() ? 'closable-chip' : 'chip';
48
+ this.getClosable() ? 'closable-chip' : 'chip');
31
49
  }
32
50
 
33
- this.callParent();
51
+ if (this.getForRemoteSearch()) {
52
+ cls.push('remote-search-chip');
53
+ } else {
54
+ cls.push('local-search-chip');
55
+ }
56
+ this.cls = cls.join(' ');
34
57
  },
35
58
 
36
59
  closeChip: function() {
@@ -42,6 +65,9 @@ Ext.define('Coon.uielement.component.formchips.Chip', {
42
65
  chip.fireEvent('delete', field);
43
66
  }
44
67
  chip.fireEvent('needReloadChips');
68
+ } else if (Ext.isFunction(chip.removeFilterCallback)) {
69
+ chip.removeFilterCallback();
70
+ chip.fireEvent('needReloadChips');
45
71
  }
46
72
  },
47
73
 
@@ -62,6 +88,9 @@ Ext.define('Coon.uielement.component.formchips.Chip', {
62
88
  if (chip.data.unusable) {
63
89
  toolTipText+=`<br>Не использовано при последнем поиске!`;
64
90
  }
91
+ if (chip.searchRequired) {
92
+ toolTipText+=`<br>Критерий поиска "${chip.data.label}" изменился. Необходимо выполнить поиск!`;
93
+ }
65
94
  tip.update(toolTipText);
66
95
  }
67
96
  },
@@ -71,7 +100,7 @@ Ext.define('Coon.uielement.component.formchips.Chip', {
71
100
  // Тултип для крестика закрытия чипа
72
101
  chip.tipClose = Ext.create('Ext.tip.ToolTip', {
73
102
  target: chip.el.query('[class=closebtn]')[0],
74
- html: 'Очистить поле формы',
103
+ html: this.getDeleteChipTooltipMessage(),
75
104
  });
76
105
  }
77
106
  },
@@ -84,4 +113,5 @@ Ext.define('Coon.uielement.component.formchips.Chip', {
84
113
  cmp.tip && cmp.tip.destroy();
85
114
  cmp.tipClose && cmp.tipClose.destroy();
86
115
  },
116
+
87
117
  });
@@ -1,18 +1,25 @@
1
1
  /**
2
2
  * Компонет формы поиска для отображения чипов (типа https://materializecss.com/chips.html) с критериями поиска.
3
- * Копмонент - это тулбар.
3
+ * Копмонент - тулбар.
4
+ *
5
+ * Поведение.
6
+ * - отображается всегда
7
+ * - включает тул (глаз) для сворачивания формы поиска
8
+ * - при выполнении поиска отображает (перечитывает) чипы с критериями, используемые при поиске.
9
+ * - каждый чип отображает введенное значение критерия (наприпер дату), а при наведении показывает тултип с названием.
10
+ * - чипы не закрываемые
11
+ * - при сворачивании формы поиска в тулбар добавляются кнопки "Поиск" и "Очистить", которые дублируют поведение формы поиска.
12
+ * - каждый чип связан с полем формы и меняет свой вид и содержание тултипа в случае изменения значения в поле поиска.
13
+ *
14
+ * Использование в репорте:
15
+ * Включается свойством репорта - enableChipToolbar.
16
+ *
17
+ * Использование в кастомной панели:
4
18
  * Д.быть добавлен в основной контейней кастомной панели, где есть title.
5
- * В заголовок будет добавлен tool, управляющий формой критериев поиска (Свернуть/Развернуть) через searchFormItemId.
6
- * Тулбар виден, если свернута форма поиска, и свернут, когда форма поиска видна.
7
- * При сворачивании формы поиска тулбар показывает критерии поиска в виде чипов двух типов:
8
- * 1. Критерии, использованные для последнего поиска. Отображение более яркое.
9
- * 2. Критерии, заполненные на форме поиска, но не использованные при последнем поиске. Отображения бледное.
10
- * Всегда есть иконка крестика для закрытия чипа.
11
- * При нажатии "Закрыть" чип будет удален с тулбара, а соответствующий критерий поиска формы будет очищен.
12
- * Конфигурационный параметр компонента closableFilterCondition в значении true дает чипам типа 1 возможность быть тоже закрываемыми.
13
- * При закрытии таких чипов компонент формирует событие chipdelete.
14
- * По этому событию кастомная панель сможет к примеру выполнить новый поиск.
15
- * Каждый чип отображает введенное значение критерия (наприпер дату), а при наведении показывает тултип с названием
19
+ * В заголовок будет добавлен tool, управляющий сворачиванием формы критериев поиска (Свернуть/Развернуть)
20
+ * (Ссылка на форму поиска по searchFormItemId).
21
+ * Конфигурационный параметр компонента closableFilterCondition пока не используется.
22
+ *
16
23
  * критерия поиска (например "Период").
17
24
  * Пример использования:
18
25
  * @example
@@ -24,10 +31,12 @@
24
31
  * {
25
32
  * xtype: 'FilterConditionToolbar',
26
33
  * reference: 'filterConditionToolbarRef',
27
- * closableFilterCondition: true,
28
- listeners: {
29
- chipdelete: 'onChipDeleteHandler', // Реакция на закрытие чипа. Например, выполнение нового поиска.
30
- },
34
+ * dock: 'bottom',
35
+ * listeners: {
36
+ * clear: 'onClearFromChips', // Событие от кнопки "Очистить" из тулбара. Привязать хендлер очистки формы
37
+ * needreload: 'doInitReport', // Событие от кнопки "Поиск" из тулбара. Привязать хендлер выполнения поиска
38
+ * // chipdelete: 'onChipDeleteHandler', // Реакция на закрытие чипа. Например, выполнение нового поиска. НЕ ИСПОЛЬЗУЕТСЯ
39
+ * },
31
40
  * },
32
41
  * ],
33
42
  * ...
@@ -40,7 +49,7 @@
40
49
  *}]
41
50
  *
42
51
  * Обязательно в хендлере выполнения поиска формы нужно добавить формирование события rememberconditions,
43
- * чтобы тулбар ЗАПОМНИЛ критерии поиска, использованные для поиска в текущий момент:
52
+ * чтобы тулбар ИСПОЛЬЗОВАЛ критерии поиска, использованные для поиска в текущий момент:
44
53
  *
45
54
  * @example
46
55
  * this.lookup('filterConditionToolbarRef').fireEvent('rememberconditions');
@@ -49,7 +58,7 @@
49
58
  * 1. Панель, содержащая поля формы поиска, должна иметь itemId searchFormItemId
50
59
  */
51
60
  Ext.define('Coon.uielement.component.formchips.FilterConditionToolbar', {
52
- extend: 'Ext.toolbar.Toolbar',
61
+ extend: 'Ext.container.Container',
53
62
  xtype: 'FilterConditionToolbar',
54
63
  alias: 'widget.FilterConditionToolbar',
55
64
  cls: 'FilterConditionToolbar',
@@ -60,10 +69,11 @@ Ext.define('Coon.uielement.component.formchips.FilterConditionToolbar', {
60
69
  controller: 'filterconditiontoolbarcontroller',
61
70
  bodyPadding: 10,
62
71
  hidden: true,
63
- overflowHandler: 'scroller',
64
-
72
+ layout: {
73
+ type: 'hbox', align: 'stretch',
74
+ },
65
75
  listeners: {
66
- beforerender: 'setPanelTools',
76
+ beforerender: 'initToolbar',
67
77
  boxready: 'fillChipsHandler',
68
78
  show: 'fadeInHandler',
69
79
  },
@@ -79,6 +89,34 @@ Ext.define('Coon.uielement.component.formchips.FilterConditionToolbar', {
79
89
  * Предназначено для вызова из кастомной панели из хендлера
80
90
  */
81
91
 
92
+ items: [
93
+ {
94
+ xtype: 'toolbar',
95
+ flex: 1,
96
+ overflowHandler: 'menu',
97
+ reference: 'chipsContainer',
98
+ },
99
+ {
100
+ xtype: 'toolbar',
101
+ reference: 'buttonsContainer',
102
+ hidden: true,
103
+ width: 200,
104
+ items: [
105
+ {
106
+ reference: 'searchButton',
107
+ ui: 'orange-button',
108
+ text: 'Поиск',
109
+ handler: 'onSearchButton',
110
+ },
111
+ {
112
+ reference: 'clearButton',
113
+ ui: 'green-button',
114
+ text: 'Очистить',
115
+ handler: 'onClearButton',
116
+ }
117
+ ],
118
+ }
119
+ ],
82
120
  config: {
83
121
  /**
84
122
  * @param {boolean} Если true, то ЧИП, по которому был произведен поиск, можно закрывать. Что приведет к очистке поля, связанного с чипом.
@@ -1,16 +1,14 @@
1
- .FilterConditionToolbar {
1
+ .x-menu-body .x-box-target, .FilterConditionToolbar {
2
2
 
3
3
  .closable-chip, .unusable-chip, .chip, .service-chip {
4
4
  display: inline-block;
5
5
  cursor: default;
6
6
  padding: 0 15px;
7
- height: 30px;
8
- line-height: 30px;
9
- border-radius: 15px;
10
- background-color: #f1f1f1;
7
+ height: 26px;
8
+ line-height: 26px;
9
+ border-radius: 13px;
11
10
  .chip-value {
12
11
  font-size: 13px;
13
- font-weight: bold;
14
12
  }
15
13
  .closebtn {
16
14
  padding-left: 10px;
@@ -24,23 +22,46 @@
24
22
  color: #000;
25
23
  }
26
24
  }
25
+ .remote-search-chip {
26
+ background-color: #f1f1f1;
27
+ }
28
+
29
+ .local-search-chip {
30
+ background-color: #dffad3;
31
+ }
32
+
27
33
  /* Неиспользованное для поиска поле и сервисное поле */
28
- .unusable-chip, .service-chip {
34
+ .unusable-chip {
35
+ border: 1px solid #ff9800;
29
36
  .chip-value {
30
37
  font-size: 13px;
38
+ font-style: italic;
31
39
  color: gray;
32
40
  font-weight: normal;
33
41
  }
34
42
  }
35
- .chip, .service-chip {
43
+
44
+ .service-chip {
45
+ .chip-value {
46
+ font-size: 13px;
47
+ color: gray;
48
+ font-weight: normal;
49
+ }
50
+ }
51
+
52
+ .chip, .service-chip, .unusable-chip {
36
53
  .closebtn {
37
54
  display: none;
38
55
  }
39
56
  }
57
+
40
58
  .closable-chip {
41
59
  .closebtn {
42
60
  display: block;
43
61
  }
44
62
  }
45
63
 
64
+ .tool-eye-horus {
65
+ color: #002453;
66
+ }
46
67
  }