Skip to content

Commit c97f045

Browse files
authored
Merge pull request #6985 from skateman/icon-picker-react
Replace angular-based fonticon picker with a react component
2 parents b7c04d1 + e37e69c commit c97f045

File tree

7 files changed

+188
-35
lines changed

7 files changed

+188
-35
lines changed

app/assets/stylesheets/application.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
@import "font-fabulous-sprockets";
44
@import "font-fabulous";
5+
@import "fonticon_picker";
56
@import "patternfly-sprockets";
67
@import "patternfly";
78
@import "patternfly_overrides";
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.fonticon-picker-modal {
2+
height: calc(100vh - 260px);
3+
overflow-y: auto !important;
4+
overflow-x: hidden;
5+
6+
div.fonticon {
7+
font-size: 24px;
8+
text-align: center;
9+
10+
span.active {
11+
i {
12+
padding: 3px 5px 3px 5px;
13+
background-color: #0088ce;
14+
color: #fff;
15+
}
16+
}
17+
}
18+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React, { useMemo } from 'react';
2+
import { Grid, Row, Col } from 'patternfly-react';
3+
import PropTypes from 'prop-types';
4+
import classNames from 'classnames';
5+
6+
const isFontIcon = (value, family) => value.selectorText && value.selectorText.includes(`.${family}-`) && value.cssText.includes('content:');
7+
8+
const clearRule = (rule, family) => {
9+
const re = new RegExp(`.*(${family}\-[a-z0-9\-\_]+).*`);
10+
return rule.replace(re, '$1');
11+
};
12+
13+
const findIcons = family => _.chain(document.styleSheets)
14+
.map(oneSheet => oneSheet.cssRules)
15+
.map(rule => _.filter(rule, value => isFontIcon(value, family)))
16+
.filter(rules => rules.length !== 0)
17+
.map(rules => _.map(rules, value => clearRule(value.selectorText, family)))
18+
.flatten()
19+
.value()
20+
.map(icon => `${family} ${icon}`);
21+
22+
const IconList = ({ type, activeIcon, activeTab, setState }) => {
23+
const icons = useMemo(() => findIcons(type), [type]);
24+
25+
// Short path for not rendering the icons if the tab isn't active
26+
if (activeTab !== type) {
27+
return <div />;
28+
}
29+
30+
return (
31+
<Grid fluid>
32+
<Row>
33+
{ icons.map(icon => (
34+
<Col xs={1} key={icon} className="fonticon" onClick={() => setState(state => ({ ...state, activeIcon: icon }))}>
35+
<span className={classNames({ active: icon === activeIcon })}>
36+
<i className={icon} title={icon.replace(' ', '.')} />
37+
</span>
38+
</Col>
39+
)) }
40+
</Row>
41+
</Grid>
42+
);
43+
};
44+
45+
IconList.propTypes = {
46+
type: PropTypes.string.isRequired,
47+
activeIcon: PropTypes.string,
48+
activeTab: PropTypes.string,
49+
setState: PropTypes.func.isRequired,
50+
};
51+
52+
IconList.defaultProps = {
53+
activeIcon: undefined,
54+
activeTab: undefined,
55+
};
56+
57+
export default IconList;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React, { useState } from 'react';
2+
import {
3+
Button,
4+
Modal,
5+
ButtonGroup,
6+
Icon,
7+
Tabs,
8+
Tab,
9+
} from 'patternfly-react';
10+
import PropTypes from 'prop-types';
11+
import classNames from 'classnames';
12+
13+
import IconList from './icon-list';
14+
15+
const FontIconPicker = ({ iconTypes, selected, onChangeURL }) => {
16+
const [{
17+
showModal,
18+
selectedIcon,
19+
activeTab,
20+
activeIcon,
21+
}, setState] = useState({
22+
showModal: false,
23+
selectedIcon: selected,
24+
activeIcon: selected,
25+
activeTab: selected ? selected.split(' ')[0] : Object.keys(iconTypes)[0],
26+
});
27+
28+
const onModalApply = () => {
29+
// This is required to connect the old session-backed form with the component
30+
window.miqObserveRequest(onChangeURL, { data: { button_icon: activeIcon } });
31+
setState(state => ({ ...state, showModal: false, selectedIcon: activeIcon }));
32+
};
33+
34+
const hideModal = () => setState(state => ({ ...state, showModal: false }));
35+
36+
return (
37+
<div className="fonticon-picker">
38+
<ButtonGroup>
39+
<Button className="icon-picker-btn">
40+
{ selectedIcon ? (<i id="selected-icon" className={classNames('fa-lg', selectedIcon)} />) : __('No icon') }
41+
</Button>
42+
<Button onClick={() => setState(state => ({ ...state, showModal: true }))}><Icon type="fa" name="angle-down" /></Button>
43+
</ButtonGroup>
44+
<Modal show={showModal} onHide={hideModal} bsSize="large">
45+
<Modal.Header>
46+
<button
47+
id="close-icon-picker-modal"
48+
type="button"
49+
className="close"
50+
onClick={hideModal}
51+
aria-label={__('Close')}
52+
>
53+
<Icon type="pf" name="close" />
54+
</button>
55+
<Modal.Title>{ __('Select an icon') }</Modal.Title>
56+
</Modal.Header>
57+
<Modal.Body>
58+
<div className="fonticon-picker-modal">
59+
<Tabs id="font-icon-tabs" activeKey={activeTab} animation={false} onSelect={activeTab => setState(state => ({ ...state, activeTab }))}>
60+
{ Object.keys(iconTypes).map(type => (
61+
<Tab eventKey={type} key={type} title={iconTypes[type]}>
62+
<IconList {...{
63+
type,
64+
activeIcon,
65+
activeTab,
66+
setState,
67+
}} />
68+
</Tab>
69+
)) }
70+
</Tabs>
71+
</div>
72+
</Modal.Body>
73+
<Modal.Footer>
74+
<Button
75+
id="apply-icon-picker-icon"
76+
bsStyle="primary"
77+
onClick={onModalApply}
78+
disabled={selectedIcon === activeIcon || activeIcon === undefined}
79+
>
80+
{ __('Apply') }
81+
</Button>
82+
<Button id="cancel-icon-picker-modal" bsStyle="default" className="btn-cancel" onClick={hideModal}>
83+
{ __('Cancel') }
84+
</Button>
85+
</Modal.Footer>
86+
</Modal>
87+
</div>
88+
);
89+
};
90+
91+
FontIconPicker.propTypes = {
92+
iconTypes: PropTypes.any,
93+
selected: PropTypes.string,
94+
onChangeURL: PropTypes.string.isRequired,
95+
};
96+
97+
FontIconPicker.defaultProps = {
98+
selected: undefined,
99+
iconTypes: {
100+
ff: 'Font Fabulous',
101+
pficon: 'PatternFly',
102+
fa: 'Font Awesome',
103+
},
104+
};
105+
106+
export default FontIconPicker;

app/javascript/packs/component-definitions-common.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import FirmwareRegistryForm from '../components/firmware-registry/firmware-regis
1515
import FormButtonsRedux from '../forms/form-buttons-redux';
1616
import GenericGroupWrapper from '../react/generic_group_wrapper';
1717
import { HierarchicalTreeView } from '../components/tree-view';
18+
import FonticonPicker from '../components/fonticon-picker';
1819
import ImportDatastoreViaGit from '../components/automate-import-export-form/import-datastore-via-git';
1920
import MainMenu from '../components/main-menu/main-menu';
2021
import MiqAboutModal from '../components/miq-about-modal';
@@ -64,6 +65,7 @@ ManageIQ.component.addReact('GenericGroup', GenericGroup);
6465
ManageIQ.component.addReact('GenericGroupWrapper', GenericGroupWrapper);
6566
ManageIQ.component.addReact('navbar.Help', navbar.Help);
6667
ManageIQ.component.addReact('HierarchicalTreeView', HierarchicalTreeView);
68+
ManageIQ.component.addReact('FonticonPicker', FonticonPicker);
6769
ManageIQ.component.addReact('ImportDatastoreViaGit', ImportDatastoreViaGit);
6870
ManageIQ.component.addReact('MainMenu', MainMenu);
6971
ManageIQ.component.addReact('MiqAboutModal', MiqAboutModal);

app/views/shared/buttons/_ab_options_form.html.haml

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,8 @@
4545
.form-group
4646
%label.control-label.col-md-2
4747
= _('Icon')
48-
.col-md-8#button-icon-picker{'ng-controller' => 'fonticonPickerController as vm'}
49-
%miq-fonticon-picker{'input-name' => 'button_icon', :selected => @edit[:new][:button_icon], 'icon-changed' => 'vm.select(selected);'}
50-
%miq-fonticon-family{:selector => 'ff', :title => 'Font Fabulous'}
51-
%miq-fonticon-family{:selector => 'pficon', :title => 'PatternFly'}
52-
%miq-fonticon-family{:selector => 'fa', :title => 'Font Awesome'}
48+
.col-md-8#button-icon-picker
49+
= react 'FonticonPicker', :selected => @edit[:new][:button_icon], :onChangeURL => url
5350
.form-group
5451
%label.control-label.col-md-2
5552
= _('Icon Color')
@@ -102,15 +99,3 @@
10299
miqSelectPickerEvent('dialog_id', '#{url}');
103100
miqSelectPickerEvent('display_for', '#{url}');
104101
miqSelectPickerEvent('submit_how', '#{url}');
105-
miq_bootstrap('#button-icon-picker');
106-
107-
// This is an ugly hack to be able to use this component in a non-angular context with miq-observe
108-
// FIXME: Remove this when the form is converted to angular
109-
$(function() {
110-
$('#button-icon-picker input[type="hidden"]').on('change', _.debounce(function() {
111-
miqObserveRequest('#{url}', {
112-
no_encoding: true,
113-
data: 'button_icon' + '=' + $(this).val(),
114-
});
115-
}, 700, {leading: true, trailing: true}));
116-
});

app/views/shared/buttons/_group_form.html.haml

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,8 @@
3131
.form-group
3232
%label.control-label.col-md-2
3333
= _('Icon')
34-
.col-md-8#button-icon-picker{'ng-controller' => 'fonticonPickerController as vm'}
35-
%miq-fonticon-picker{'input-name' => 'button_icon', :selected => @edit[:new][:button_icon], 'icon-changed' => 'vm.select(selected);'}
36-
%miq-fonticon-family{:selector => 'ff', :title => 'Font Fabulous'}
37-
%miq-fonticon-family{:selector => 'pficon', :title => 'PatternFly'}
38-
%miq-fonticon-family{:selector => 'fa', :title => 'Font Awesome'}
34+
.col-md-8#button-icon-picker
35+
= react 'FonticonPicker', :selected => @edit[:new][:button_icon], :onChangeURL => url
3936
.form-group
4037
%label.control-label.col-md-2
4138
= _('Icon Color')
@@ -51,16 +48,3 @@
5148
%h3
5249
= _('Assign Buttons')
5350
= render :partial => "shared/buttons/column_lists"
54-
55-
:javascript
56-
miq_bootstrap('#button-icon-picker');
57-
// This is an ugly hack to be able to use this component in a non-angular context with miq-observe
58-
// FIXME: Remove this when the form is converted to angular
59-
$(function() {
60-
$('#button-icon-picker input[type="hidden"]').on('change', _.debounce(function() {
61-
miqObserveRequest('#{url}', {
62-
no_encoding: true,
63-
data: 'button_icon' + '=' + $(this).val(),
64-
});
65-
}, 700, {leading: true, trailing: true}));
66-
});

0 commit comments

Comments
 (0)