@performant-software/semantic-components 0.5.1
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.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/build/index.js +2 -0
- package/build/index.js.map +1 -0
- package/build/main.css +786 -0
- package/index.js +1 -0
- package/package.json +37 -0
- package/src/components/AccordionDataList.css +8 -0
- package/src/components/AccordionDataList.js +224 -0
- package/src/components/AccordionList.css +27 -0
- package/src/components/AccordionList.js +596 -0
- package/src/components/AccordionSelector.css +3 -0
- package/src/components/AccordionSelector.js +359 -0
- package/src/components/ArrowButtons.css +4 -0
- package/src/components/ArrowButtons.js +38 -0
- package/src/components/AssociatedDropdown.css +44 -0
- package/src/components/AssociatedDropdown.js +338 -0
- package/src/components/BooleanIcon.css +0 -0
- package/src/components/BooleanIcon.js +33 -0
- package/src/components/CancelButton.css +0 -0
- package/src/components/CancelButton.js +25 -0
- package/src/components/ColorButton.css +4 -0
- package/src/components/ColorButton.js +34 -0
- package/src/components/ColorPickerModal.css +3 -0
- package/src/components/ColorPickerModal.js +77 -0
- package/src/components/ColumnResize.css +9 -0
- package/src/components/ColumnResize.js +20 -0
- package/src/components/DataList.css +0 -0
- package/src/components/DataList.js +531 -0
- package/src/components/DataTable.css +43 -0
- package/src/components/DataTable.js +596 -0
- package/src/components/DataTableColumnSelector.css +10 -0
- package/src/components/DataTableColumnSelector.js +146 -0
- package/src/components/DateInput.css +6 -0
- package/src/components/DateInput.js +58 -0
- package/src/components/DatePicker.css +72 -0
- package/src/components/DatePicker.js +81 -0
- package/src/components/DescriptorField.css +0 -0
- package/src/components/DescriptorField.js +42 -0
- package/src/components/DownloadButton.css +0 -0
- package/src/components/DownloadButton.js +23 -0
- package/src/components/Draggable.css +0 -0
- package/src/components/Draggable.js +94 -0
- package/src/components/DropdownButton.css +3 -0
- package/src/components/DropdownButton.js +65 -0
- package/src/components/DropdownMenu.css +0 -0
- package/src/components/DropdownMenu.js +68 -0
- package/src/components/EditModal.css +8 -0
- package/src/components/EditModal.js +99 -0
- package/src/components/EditPage.css +7 -0
- package/src/components/EditPage.js +249 -0
- package/src/components/EmbeddedList.css +7 -0
- package/src/components/EmbeddedList.js +278 -0
- package/src/components/FileInputButton.css +0 -0
- package/src/components/FileInputButton.js +54 -0
- package/src/components/FileUpload.css +31 -0
- package/src/components/FileUpload.js +188 -0
- package/src/components/FileUploadModal.css +0 -0
- package/src/components/FileUploadModal.js +408 -0
- package/src/components/FuzzyDate.css +8 -0
- package/src/components/FuzzyDate.js +575 -0
- package/src/components/GoogleMap.css +0 -0
- package/src/components/GoogleMap.js +105 -0
- package/src/components/GooglePlacesSearch.css +0 -0
- package/src/components/GooglePlacesSearch.js +43 -0
- package/src/components/HorizontalCards.css +50 -0
- package/src/components/HorizontalCards.js +226 -0
- package/src/components/ItemCollection.css +3 -0
- package/src/components/ItemCollection.js +159 -0
- package/src/components/ItemList.css +0 -0
- package/src/components/ItemList.js +126 -0
- package/src/components/Items.css +19 -0
- package/src/components/Items.js +365 -0
- package/src/components/ItemsToggle.css +0 -0
- package/src/components/ItemsToggle.js +168 -0
- package/src/components/KeyboardField.css +4 -0
- package/src/components/KeyboardField.js +147 -0
- package/src/components/LazyDocument.css +21 -0
- package/src/components/LazyDocument.js +113 -0
- package/src/components/LazyImage.css +21 -0
- package/src/components/LazyImage.js +119 -0
- package/src/components/LazyVideo.css +21 -0
- package/src/components/LazyVideo.js +131 -0
- package/src/components/LinkButton.css +8 -0
- package/src/components/LinkButton.js +23 -0
- package/src/components/LinkLabel.css +8 -0
- package/src/components/LinkLabel.js +29 -0
- package/src/components/List.css +8 -0
- package/src/components/List.js +761 -0
- package/src/components/ListFilters.css +0 -0
- package/src/components/ListFilters.js +408 -0
- package/src/components/ListLoader.css +8 -0
- package/src/components/ListLoader.js +32 -0
- package/src/components/ListTable.css +3 -0
- package/src/components/ListTable.js +86 -0
- package/src/components/LoginModal.css +7 -0
- package/src/components/LoginModal.js +102 -0
- package/src/components/MasonryGrid.css +48 -0
- package/src/components/MasonryGrid.js +202 -0
- package/src/components/MediaGallery.css +37 -0
- package/src/components/MediaGallery.js +148 -0
- package/src/components/MediaGrid.css +72 -0
- package/src/components/MediaGrid.js +74 -0
- package/src/components/MediaList.css +3 -0
- package/src/components/MediaList.js +98 -0
- package/src/components/ModalDropdown.css +11 -0
- package/src/components/ModalDropdown.js +84 -0
- package/src/components/NestedAccordion.css +41 -0
- package/src/components/NestedAccordion.js +276 -0
- package/src/components/PhotoViewer.css +3 -0
- package/src/components/PhotoViewer.js +36 -0
- package/src/components/PlayButton.css +3 -0
- package/src/components/PlayButton.js +37 -0
- package/src/components/RemoteDropdown.css +13 -0
- package/src/components/RemoteDropdown.js +368 -0
- package/src/components/SaveButton.css +0 -0
- package/src/components/SaveButton.js +31 -0
- package/src/components/Section.css +0 -0
- package/src/components/Section.js +41 -0
- package/src/components/Selectize.css +11 -0
- package/src/components/Selectize.js +297 -0
- package/src/components/SelectizeHeader.css +3 -0
- package/src/components/SelectizeHeader.js +40 -0
- package/src/components/TabbedModal.css +14 -0
- package/src/components/TabbedModal.js +165 -0
- package/src/components/TabsMenu.css +0 -0
- package/src/components/TabsMenu.js +35 -0
- package/src/components/TagsList.css +0 -0
- package/src/components/TagsList.js +43 -0
- package/src/components/Thumbnail.css +0 -0
- package/src/components/Thumbnail.js +47 -0
- package/src/components/Toaster.css +9 -0
- package/src/components/Toaster.js +73 -0
- package/src/components/VideoFrameSelector.css +3 -0
- package/src/components/VideoFrameSelector.js +148 -0
- package/src/components/VideoPlayer.css +3 -0
- package/src/components/VideoPlayer.js +55 -0
- package/src/components/VideoPlayerButton.css +17 -0
- package/src/components/VideoPlayerButton.js +17 -0
- package/src/components/ViewXML.css +0 -0
- package/src/components/ViewXML.js +72 -0
- package/src/i18n/en.json +204 -0
- package/src/i18n/i18n.js +24 -0
- package/src/index.js +76 -0
- package/types/components/AccordionDataList.js.flow +224 -0
- package/types/components/AccordionList.js.flow +596 -0
- package/types/components/AccordionSelector.js.flow +359 -0
- package/types/components/ArrowButtons.js.flow +38 -0
- package/types/components/AssociatedDropdown.js.flow +338 -0
- package/types/components/BooleanIcon.js.flow +33 -0
- package/types/components/CancelButton.js.flow +25 -0
- package/types/components/ColorButton.js.flow +34 -0
- package/types/components/ColorPickerModal.js.flow +77 -0
- package/types/components/ColumnResize.js.flow +20 -0
- package/types/components/DataList.js.flow +531 -0
- package/types/components/DataTable.js.flow +596 -0
- package/types/components/DataTableColumnSelector.js.flow +146 -0
- package/types/components/DataView.js.flow +125 -0
- package/types/components/DateInput.js.flow +58 -0
- package/types/components/DatePicker.js.flow +81 -0
- package/types/components/DescriptorField.js.flow +42 -0
- package/types/components/DownloadButton.js.flow +23 -0
- package/types/components/Draggable.js.flow +94 -0
- package/types/components/DropdownButton.js.flow +65 -0
- package/types/components/DropdownMenu.js.flow +68 -0
- package/types/components/EditModal.js.flow +99 -0
- package/types/components/EditPage.js.flow +249 -0
- package/types/components/EmbeddedList.js.flow +278 -0
- package/types/components/FileInputButton.js.flow +54 -0
- package/types/components/FileUpload.js.flow +188 -0
- package/types/components/FileUploadModal.js.flow +408 -0
- package/types/components/FuzzyDate.js.flow +575 -0
- package/types/components/GoogleMap.js.flow +105 -0
- package/types/components/GooglePlacesSearch.js.flow +43 -0
- package/types/components/HorizontalCards.js.flow +226 -0
- package/types/components/ItemCollection.js.flow +159 -0
- package/types/components/ItemList.js.flow +126 -0
- package/types/components/Items.js.flow +365 -0
- package/types/components/ItemsToggle.js.flow +168 -0
- package/types/components/KeyboardField.js.flow +147 -0
- package/types/components/LazyDocument.js.flow +113 -0
- package/types/components/LazyImage.js.flow +119 -0
- package/types/components/LazyVideo.js.flow +131 -0
- package/types/components/LinkButton.js.flow +23 -0
- package/types/components/LinkLabel.js.flow +29 -0
- package/types/components/List.js.flow +761 -0
- package/types/components/ListFilters.js.flow +408 -0
- package/types/components/ListLoader.js.flow +32 -0
- package/types/components/ListTable.js.flow +86 -0
- package/types/components/LoginModal.js.flow +102 -0
- package/types/components/MasonryGrid.js.flow +202 -0
- package/types/components/MediaGallery.js.flow +148 -0
- package/types/components/MediaGrid.js.flow +74 -0
- package/types/components/MediaList.js.flow +98 -0
- package/types/components/MenuBar.js.flow +77 -0
- package/types/components/MenuSidebar.js.flow +72 -0
- package/types/components/ModalDropdown.js.flow +84 -0
- package/types/components/NestedAccordion.js.flow +276 -0
- package/types/components/PhotoViewer.js.flow +36 -0
- package/types/components/PlayButton.js.flow +37 -0
- package/types/components/RemoteDropdown.js.flow +368 -0
- package/types/components/SaveButton.js.flow +31 -0
- package/types/components/Section.js.flow +41 -0
- package/types/components/Selectize.js.flow +297 -0
- package/types/components/SelectizeHeader.js.flow +40 -0
- package/types/components/TabbedModal.js.flow +165 -0
- package/types/components/TabsMenu.js.flow +35 -0
- package/types/components/TagsList.js.flow +43 -0
- package/types/components/Thumbnail.js.flow +47 -0
- package/types/components/Toaster.js.flow +73 -0
- package/types/components/VideoFrameSelector.js.flow +148 -0
- package/types/components/VideoPlayer.js.flow +55 -0
- package/types/components/VideoPlayerButton.js.flow +17 -0
- package/types/components/ViewXML.js.flow +72 -0
- package/types/hooks/Imageable.js.flow +54 -0
- package/types/i18n/i18n.js.flow +24 -0
- package/types/index.js.flow +78 -0
- package/webpack.config.js +3 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import { useEditContainer, type EditContainerProps } from '@performant-software/shared-components';
|
|
4
|
+
import React, { type ComponentType, useState } from 'react';
|
|
5
|
+
import {
|
|
6
|
+
Button,
|
|
7
|
+
Dimmer,
|
|
8
|
+
Loader,
|
|
9
|
+
Message,
|
|
10
|
+
Modal
|
|
11
|
+
} from 'semantic-ui-react';
|
|
12
|
+
import Toaster from './Toaster';
|
|
13
|
+
import i18n from '../i18n/i18n';
|
|
14
|
+
import './EditModal.css';
|
|
15
|
+
|
|
16
|
+
type Props = EditContainerProps & {
|
|
17
|
+
component: ComponentType<any>,
|
|
18
|
+
onClose: () => void,
|
|
19
|
+
onSave: () => Promise<any>,
|
|
20
|
+
showLoading: boolean
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const EditModal = (props: Props) => {
|
|
24
|
+
const OuterComponent = props.component;
|
|
25
|
+
|
|
26
|
+
const [showToaster, setShowToaster] = useState(false);
|
|
27
|
+
const hasErrors = !!(props.errors && props.errors.length);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<OuterComponent
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{ props.showLoading && props.loading && (
|
|
34
|
+
<Dimmer
|
|
35
|
+
active={props.loading}
|
|
36
|
+
inverted
|
|
37
|
+
>
|
|
38
|
+
<Loader
|
|
39
|
+
content={i18n.t('Common.messages.loading')}
|
|
40
|
+
/>
|
|
41
|
+
</Dimmer>
|
|
42
|
+
)}
|
|
43
|
+
{ showToaster && hasErrors && (
|
|
44
|
+
<Toaster
|
|
45
|
+
onDismiss={() => setShowToaster(false)}
|
|
46
|
+
timeout={0}
|
|
47
|
+
type={Toaster.MessageTypes.negative}
|
|
48
|
+
>
|
|
49
|
+
<Message.Header
|
|
50
|
+
content={i18n.t('Common.messages.error.header')}
|
|
51
|
+
/>
|
|
52
|
+
<Message.List
|
|
53
|
+
items={props.errors}
|
|
54
|
+
/>
|
|
55
|
+
</Toaster>
|
|
56
|
+
)}
|
|
57
|
+
<Modal.Actions
|
|
58
|
+
className='edit-modal-actions'
|
|
59
|
+
>
|
|
60
|
+
<Button
|
|
61
|
+
disabled={props.saving}
|
|
62
|
+
onClick={() => {
|
|
63
|
+
setShowToaster(true);
|
|
64
|
+
return props.onSave();
|
|
65
|
+
}}
|
|
66
|
+
primary
|
|
67
|
+
size='medium'
|
|
68
|
+
type='submit'
|
|
69
|
+
>
|
|
70
|
+
{ i18n.t('Common.buttons.save') }
|
|
71
|
+
{ props.saving && (
|
|
72
|
+
<Loader
|
|
73
|
+
active
|
|
74
|
+
className='saving'
|
|
75
|
+
inline
|
|
76
|
+
size='tiny'
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
79
|
+
</Button>
|
|
80
|
+
<Button
|
|
81
|
+
disabled={props.saving}
|
|
82
|
+
inverted
|
|
83
|
+
onClick={props.onClose.bind(this)}
|
|
84
|
+
primary
|
|
85
|
+
size='medium'
|
|
86
|
+
type='button'
|
|
87
|
+
>
|
|
88
|
+
{ i18n.t('Common.buttons.cancel') }
|
|
89
|
+
</Button>
|
|
90
|
+
</Modal.Actions>
|
|
91
|
+
</OuterComponent>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
EditModal.defaultProps = {
|
|
96
|
+
showLoading: true
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default useEditContainer(EditModal);
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import { useEditContainer, type EditContainerProps } from '@performant-software/shared-components';
|
|
4
|
+
import React, { Component, type ComponentType } from 'react';
|
|
5
|
+
import {
|
|
6
|
+
Dimmer,
|
|
7
|
+
Form,
|
|
8
|
+
Loader,
|
|
9
|
+
Menu,
|
|
10
|
+
Message,
|
|
11
|
+
type MenuProps
|
|
12
|
+
} from 'semantic-ui-react';
|
|
13
|
+
import _ from 'underscore';
|
|
14
|
+
import CancelButton from './CancelButton';
|
|
15
|
+
import SaveButton from './SaveButton';
|
|
16
|
+
import Toaster from './Toaster';
|
|
17
|
+
import i18n from '../i18n/i18n';
|
|
18
|
+
import './EditPage.css';
|
|
19
|
+
|
|
20
|
+
type Props = EditContainerProps & {
|
|
21
|
+
className?: string,
|
|
22
|
+
component: ComponentType<any>,
|
|
23
|
+
menu?: MenuProps,
|
|
24
|
+
onClose: () => void,
|
|
25
|
+
onSave: () => Promise<any>,
|
|
26
|
+
showLoading: boolean
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type State = {
|
|
30
|
+
currentTab: string,
|
|
31
|
+
showToaster: boolean
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
class EditPage extends Component<Props, State> {
|
|
35
|
+
static defaultProps: any;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Constructs a new EditPage component.
|
|
39
|
+
*
|
|
40
|
+
* @param props
|
|
41
|
+
*/
|
|
42
|
+
constructor(props: Props) {
|
|
43
|
+
super(props);
|
|
44
|
+
|
|
45
|
+
this.state = {
|
|
46
|
+
currentTab: '',
|
|
47
|
+
showToaster: false
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sets the current tab to the first tab in the array.
|
|
53
|
+
*/
|
|
54
|
+
componentDidMount() {
|
|
55
|
+
if (this.props.menu) {
|
|
56
|
+
const tab = _.first(this.props.menu.items);
|
|
57
|
+
this.setState({ currentTab: tab && tab.key });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resets the <code>showToaster</code> property when the set of errors changes.
|
|
63
|
+
*
|
|
64
|
+
* @param prevProps
|
|
65
|
+
*/
|
|
66
|
+
componentDidUpdate(prevProps: Props) {
|
|
67
|
+
if (!_.isEmpty(this.props.errors) && !_.isEmpty(prevProps.errors) && prevProps.errors !== this.props.errors) {
|
|
68
|
+
this.setState({ showToaster: true });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Shows the toaster and calls the onSave prop.
|
|
74
|
+
*
|
|
75
|
+
* @returns {*}
|
|
76
|
+
*/
|
|
77
|
+
onSave() {
|
|
78
|
+
this.setState({ showToaster: true });
|
|
79
|
+
return this.props.onSave();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Renders the EditPage component.
|
|
84
|
+
*
|
|
85
|
+
* @returns {*}
|
|
86
|
+
*/
|
|
87
|
+
render() {
|
|
88
|
+
return (
|
|
89
|
+
<Dimmer.Dimmable
|
|
90
|
+
as='div'
|
|
91
|
+
className={`edit-page ${this.props.className || ''}`}
|
|
92
|
+
>
|
|
93
|
+
{ this.renderLoading() }
|
|
94
|
+
<Form
|
|
95
|
+
noValidate
|
|
96
|
+
>
|
|
97
|
+
{ this.renderMenu() }
|
|
98
|
+
{ this.renderButtons() }
|
|
99
|
+
{ this.renderComponent() }
|
|
100
|
+
{ this.renderToaster() }
|
|
101
|
+
</Form>
|
|
102
|
+
</Dimmer.Dimmable>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Renders the buttons and container if no menu is present.
|
|
108
|
+
*
|
|
109
|
+
* @returns {null|*}
|
|
110
|
+
*/
|
|
111
|
+
renderButtons() {
|
|
112
|
+
if (this.props.menu) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div
|
|
118
|
+
className='button-container'
|
|
119
|
+
>
|
|
120
|
+
<SaveButton
|
|
121
|
+
onClick={this.onSave.bind(this)}
|
|
122
|
+
saving={this.props.saving}
|
|
123
|
+
/>
|
|
124
|
+
<CancelButton
|
|
125
|
+
disabled={this.props.saving}
|
|
126
|
+
onClick={this.props.onClose.bind(this)}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Renders the wrapped component.
|
|
134
|
+
*
|
|
135
|
+
* @returns {*}
|
|
136
|
+
*/
|
|
137
|
+
renderComponent() {
|
|
138
|
+
const WrappedComponent = this.props.component;
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<WrappedComponent
|
|
142
|
+
{...this.props}
|
|
143
|
+
currentTab={this.state.currentTab}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Renders the loading indicator.
|
|
150
|
+
*
|
|
151
|
+
* @returns {null|*}
|
|
152
|
+
*/
|
|
153
|
+
renderLoading() {
|
|
154
|
+
if (!(this.props.showLoading && this.props.loading)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<Dimmer
|
|
160
|
+
active={this.props.loading}
|
|
161
|
+
inverted
|
|
162
|
+
>
|
|
163
|
+
<Loader
|
|
164
|
+
content={i18n.t('Common.messages.loading')}
|
|
165
|
+
/>
|
|
166
|
+
</Dimmer>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Renders the menu (if present).
|
|
172
|
+
*
|
|
173
|
+
* @returns {null|*}
|
|
174
|
+
*/
|
|
175
|
+
renderMenu() {
|
|
176
|
+
if (!this.props.menu) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<Menu
|
|
182
|
+
{..._.omit(this.props.menu, 'items')}
|
|
183
|
+
>
|
|
184
|
+
{ this.props.menu && _.map(this.props.menu.items, (item) => (
|
|
185
|
+
<Menu.Item
|
|
186
|
+
active={item.key === this.state.currentTab}
|
|
187
|
+
key={item.key}
|
|
188
|
+
name={item.name}
|
|
189
|
+
onClick={() => this.setState({ currentTab: item.key })}
|
|
190
|
+
/>
|
|
191
|
+
))}
|
|
192
|
+
<Menu.Menu
|
|
193
|
+
position='right'
|
|
194
|
+
>
|
|
195
|
+
<Menu.Item>
|
|
196
|
+
<SaveButton
|
|
197
|
+
onClick={this.onSave.bind(this)}
|
|
198
|
+
saving={this.props.saving}
|
|
199
|
+
/>
|
|
200
|
+
<CancelButton
|
|
201
|
+
disabled={this.props.saving}
|
|
202
|
+
onClick={this.props.onClose.bind(this)}
|
|
203
|
+
/>
|
|
204
|
+
</Menu.Item>
|
|
205
|
+
</Menu.Menu>
|
|
206
|
+
</Menu>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Renders the toaster component.
|
|
212
|
+
*
|
|
213
|
+
* @returns {null|*}
|
|
214
|
+
*/
|
|
215
|
+
renderToaster() {
|
|
216
|
+
if (!this.state.showToaster) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!(this.props.errors && this.props.errors.length)) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<Toaster
|
|
226
|
+
onDismiss={() => this.setState({ showToaster: false })}
|
|
227
|
+
timeout={0}
|
|
228
|
+
type={Toaster.MessageTypes.negative}
|
|
229
|
+
>
|
|
230
|
+
<Message.Header
|
|
231
|
+
content={i18n.t('Common.messages.error.header')}
|
|
232
|
+
/>
|
|
233
|
+
<Message.List
|
|
234
|
+
items={this.props.errors}
|
|
235
|
+
/>
|
|
236
|
+
</Toaster>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
EditPage.defaultProps = {
|
|
242
|
+
showLoading: true
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export default useEditContainer(EditPage);
|
|
246
|
+
|
|
247
|
+
export type EditPageProps = EditContainerProps & {
|
|
248
|
+
currentTab: string
|
|
249
|
+
};
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React, { Component, type ComponentType } from 'react';
|
|
4
|
+
import { Table } from 'semantic-ui-react';
|
|
5
|
+
import uuid from 'react-uuid';
|
|
6
|
+
import _ from 'underscore';
|
|
7
|
+
import DataTable from './DataTable';
|
|
8
|
+
import Draggable from './Draggable';
|
|
9
|
+
import './EmbeddedList.css';
|
|
10
|
+
|
|
11
|
+
import type { Action } from './List';
|
|
12
|
+
import type { Column } from './DataTable';
|
|
13
|
+
|
|
14
|
+
type ListButton = {
|
|
15
|
+
render: () => ComponentType<any>
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
actions: Array<Action>,
|
|
20
|
+
addButton?: {
|
|
21
|
+
location: string,
|
|
22
|
+
color: string
|
|
23
|
+
},
|
|
24
|
+
buttons?: Array<ListButton>,
|
|
25
|
+
className?: string,
|
|
26
|
+
columns: Array<Column>,
|
|
27
|
+
configurable: boolean,
|
|
28
|
+
items: Array<any>,
|
|
29
|
+
modal?: {
|
|
30
|
+
component: ComponentType<any>,
|
|
31
|
+
props: any,
|
|
32
|
+
state: any
|
|
33
|
+
},
|
|
34
|
+
onCopy?: (item: any) => any,
|
|
35
|
+
onDelete: (item: any) => void,
|
|
36
|
+
onDrag?: (dragIndex: number, hoverIndex: number) => void,
|
|
37
|
+
onSave?: (item: any) => void,
|
|
38
|
+
renderDeleteModal?: ({ selectedItem: any, onCancel: () => void, onConfirm: () => void }) => void,
|
|
39
|
+
renderEmptyRow?: () => void,
|
|
40
|
+
selectable: boolean,
|
|
41
|
+
onRowSelect: (Array<{id: number}>),
|
|
42
|
+
selectedRows: Array<{id: number}>,
|
|
43
|
+
showRecordCount: boolean,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type State = {
|
|
47
|
+
sortColumn: ?string,
|
|
48
|
+
sortDirection: ?string
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const PATH_DELIMITER = '.';
|
|
52
|
+
const SORT_ASCENDING = 'ascending';
|
|
53
|
+
const SORT_DESCENDING = 'descending';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The EmbeddedList component can be used to display a collection of records that live within a parent object. This is
|
|
57
|
+
* especially useful when the collection is to be saved at the same time as the parent.
|
|
58
|
+
*/
|
|
59
|
+
class EmbeddedList extends Component<Props, State> {
|
|
60
|
+
static defaultProps: any;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Constructs a new EmbeddedList component.
|
|
64
|
+
*
|
|
65
|
+
* @param props
|
|
66
|
+
*/
|
|
67
|
+
constructor(props: Props) {
|
|
68
|
+
super(props);
|
|
69
|
+
|
|
70
|
+
this.state = {
|
|
71
|
+
sortColumn: null,
|
|
72
|
+
sortDirection: null
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Sorts the table by the first column.
|
|
78
|
+
*/
|
|
79
|
+
componentDidMount() {
|
|
80
|
+
const column = _.find(this.props.columns, (c) => c.sortable !== false);
|
|
81
|
+
|
|
82
|
+
if (column) {
|
|
83
|
+
this.onColumnClick(column);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Returns the sorted, filtered list of items.
|
|
89
|
+
*
|
|
90
|
+
* @returns {*}
|
|
91
|
+
*/
|
|
92
|
+
getItems() {
|
|
93
|
+
const { items } = this.props;
|
|
94
|
+
const { sortColumn, sortDirection } = this.state;
|
|
95
|
+
|
|
96
|
+
return _.orderBy(_.filter(items, (item) => !item._destroy), sortColumn, sortDirection);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Sorts the table by the passed column.
|
|
101
|
+
*
|
|
102
|
+
* @param column
|
|
103
|
+
*/
|
|
104
|
+
onColumnClick(column: Column) {
|
|
105
|
+
/*
|
|
106
|
+
* We'll disable the column sorting if the table rows are draggable. Making the table rows draggable implies
|
|
107
|
+
* that the sorting will be done manually. Allowing column click sorting could make things confusing.
|
|
108
|
+
*/
|
|
109
|
+
if (this.props.onDrag) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* If the column is not sortable, we'll do nothing. Check explicity for "false" here because the default behavior
|
|
115
|
+
* should allow columns to be sortable.
|
|
116
|
+
*/
|
|
117
|
+
if (column.sortable === false) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const sortColumn = column.name;
|
|
122
|
+
let sortDirection = SORT_ASCENDING;
|
|
123
|
+
|
|
124
|
+
if (column.name === this.state.sortColumn) {
|
|
125
|
+
sortDirection = this.state.sortDirection === SORT_ASCENDING ? SORT_DESCENDING : SORT_ASCENDING;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.setState({ sortColumn, sortDirection });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Deletes the passed item. This function returns a promise so that calls can be chained together.
|
|
133
|
+
*
|
|
134
|
+
* @param item
|
|
135
|
+
*
|
|
136
|
+
* @returns {Promise}
|
|
137
|
+
*/
|
|
138
|
+
onDelete(item: any) {
|
|
139
|
+
this.props.onDelete(item);
|
|
140
|
+
return Promise.resolve();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Saves the passed item, resets the state, and reloads the data. This function returns a promise so that calls can
|
|
145
|
+
* be chained together.
|
|
146
|
+
*
|
|
147
|
+
* @param item
|
|
148
|
+
*
|
|
149
|
+
* @returns {Promise}
|
|
150
|
+
*/
|
|
151
|
+
onSave(item: any) {
|
|
152
|
+
const uid = item.uid ? item.uid : uuid();
|
|
153
|
+
|
|
154
|
+
if (this.props.onSave) {
|
|
155
|
+
this.props.onSave({ ...item, uid });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return Promise.resolve();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Renders the EmbeddedList component.
|
|
163
|
+
*
|
|
164
|
+
* @returns {*}
|
|
165
|
+
*/
|
|
166
|
+
render() {
|
|
167
|
+
return (
|
|
168
|
+
<DataTable
|
|
169
|
+
{...this.props}
|
|
170
|
+
actions={this.props.actions}
|
|
171
|
+
addButton={this.props.addButton}
|
|
172
|
+
buttons={this.props.buttons}
|
|
173
|
+
className={`embedded-list ${this.props.className ? this.props.className : ''}`}
|
|
174
|
+
configurable={this.props.configurable}
|
|
175
|
+
columns={this.props.columns}
|
|
176
|
+
count={this.props.items.length}
|
|
177
|
+
items={this.getItems()}
|
|
178
|
+
modal={this.props.modal}
|
|
179
|
+
onColumnClick={this.onColumnClick.bind(this)}
|
|
180
|
+
onCopy={this.props.onCopy}
|
|
181
|
+
onDrag={this.props.onDrag}
|
|
182
|
+
onDelete={this.onDelete.bind(this)}
|
|
183
|
+
onSave={this.onSave.bind(this)}
|
|
184
|
+
renderDeleteModal={this.props.renderDeleteModal}
|
|
185
|
+
renderEmptyRow={this.props.renderEmptyRow}
|
|
186
|
+
renderItem={this.renderItem.bind(this)}
|
|
187
|
+
sortColumn={this.state.sortColumn}
|
|
188
|
+
sortDirection={this.state.sortDirection}
|
|
189
|
+
tableProps={{
|
|
190
|
+
celled: true,
|
|
191
|
+
sortable: !this.props.onDrag
|
|
192
|
+
}}
|
|
193
|
+
selectable={this.props.selectable}
|
|
194
|
+
onRowSelect={this.props.onRowSelect}
|
|
195
|
+
selectedRows={this.props.selectedRows}
|
|
196
|
+
showRecordCount={this.props.showRecordCount}
|
|
197
|
+
/>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Renders the passed item. This function should be used if the table row is draggable.
|
|
203
|
+
*
|
|
204
|
+
* @param item
|
|
205
|
+
* @param index
|
|
206
|
+
* @param children
|
|
207
|
+
*
|
|
208
|
+
* @returns {*}
|
|
209
|
+
*/
|
|
210
|
+
renderItem(item: any, index: number, children: ComponentType<any>) {
|
|
211
|
+
if (this.props.onDrag) {
|
|
212
|
+
// Since the item may not be saved yet, we'll look for the ID or UID columns as the key. This is necessary to
|
|
213
|
+
// maintain the correct element when dragging.
|
|
214
|
+
const key = item.id || item.uid;
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<Draggable
|
|
218
|
+
id={key}
|
|
219
|
+
index={index}
|
|
220
|
+
item={item}
|
|
221
|
+
key={key}
|
|
222
|
+
onDrag={this.props.onDrag.bind(this)}
|
|
223
|
+
>
|
|
224
|
+
<Table.Row>
|
|
225
|
+
{ children }
|
|
226
|
+
</Table.Row>
|
|
227
|
+
</Draggable>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<Table.Row
|
|
233
|
+
key={index}
|
|
234
|
+
>
|
|
235
|
+
{ children }
|
|
236
|
+
</Table.Row>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* UnderscoreJS mixin to allow sorting by a property string (i.e. "person.name") and a sort
|
|
243
|
+
* direction ("ascending" or "descending").
|
|
244
|
+
*/
|
|
245
|
+
_.mixin({
|
|
246
|
+
orderBy: (items, property, direction) => {
|
|
247
|
+
if (!property) {
|
|
248
|
+
return items;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const sortProperty = _.property(property.split(PATH_DELIMITER));
|
|
252
|
+
let ordered = _.sortBy(items, (item) => sortProperty(item));
|
|
253
|
+
|
|
254
|
+
if (direction === SORT_DESCENDING) {
|
|
255
|
+
ordered = ordered.reverse();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return ordered;
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
EmbeddedList.defaultProps = {
|
|
263
|
+
items: [],
|
|
264
|
+
addButton: {
|
|
265
|
+
location: 'top'
|
|
266
|
+
},
|
|
267
|
+
buttons: [],
|
|
268
|
+
className: '',
|
|
269
|
+
configurable: true,
|
|
270
|
+
modal: undefined,
|
|
271
|
+
onCopy: undefined,
|
|
272
|
+
onDrag: undefined,
|
|
273
|
+
onSave: () => {},
|
|
274
|
+
renderDeleteModal: undefined,
|
|
275
|
+
renderEmptyRow: undefined
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export default EmbeddedList;
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React, { useRef } from 'react';
|
|
4
|
+
import { Button } from 'semantic-ui-react';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
multiple?: boolean,
|
|
8
|
+
onSelection: (files: Array<File>) => void
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const FileInputButton = ({ onSelection, multiple, ...buttonProps }: Props) => {
|
|
12
|
+
const fileInputRef = useRef();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Converts the passed file list to an array.
|
|
16
|
+
*
|
|
17
|
+
* @param fileList
|
|
18
|
+
*
|
|
19
|
+
* @returns {[]}
|
|
20
|
+
*/
|
|
21
|
+
const toArray = (fileList) => {
|
|
22
|
+
const array = [];
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < fileList.length; i += 1) {
|
|
25
|
+
array.push(fileList.item(i));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return array;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<Button
|
|
34
|
+
{...buttonProps}
|
|
35
|
+
onClick={() => fileInputRef.current && fileInputRef.current.click()}
|
|
36
|
+
/>
|
|
37
|
+
<input
|
|
38
|
+
ref={fileInputRef}
|
|
39
|
+
type='file'
|
|
40
|
+
multiple={multiple}
|
|
41
|
+
onChange={(e) => onSelection(toArray(e.target.files))}
|
|
42
|
+
style={{
|
|
43
|
+
display: 'none'
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
FileInputButton.defaultProps = {
|
|
51
|
+
multiple: false
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default FileInputButton;
|