Skip to content
This repository was archived by the owner on Nov 16, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 80 additions & 14 deletions src/routes/Apps/App/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@ import FormattedMessageId from '../../../components/FormattedMessageId'
import { returntypeof } from 'react-redux-typescript'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { RouteComponentProps } from 'react-router'
import { ActionCreatorEditor } from '../../../components/modals'
import { State } from '../../../types'
import { FM } from '../../../react-intl-messages'
import { injectIntl, InjectedIntlProps } from 'react-intl'
import * as queryString from 'query-string'

const queryParams = {
selectedActionId: 'selectedActionId'
}

interface ComponentState {
actionSelected: CLM.ActionBase | null
Expand Down Expand Up @@ -46,15 +53,71 @@ class Actions extends React.Component<Props, ComponentState> {

componentDidMount() {
this.focusNewActionButton()
this.handleQueryParameters(this.props.location.search)
}

componentWillReceiveProps(nextProps: Props) {
this.handleQueryParameters(nextProps.location.search, this.props.location.search)
}


@OF.autobind
async handleQueryParameters(newSearch: string, oldSearch?: string): Promise<void> {
if (this.props.actions.length === 0) {
return
}

const searchParams = new URLSearchParams(newSearch)
const selectedActionId = searchParams.get(queryParams.selectedActionId)

if (oldSearch) {
const searchParamsPrev = new URLSearchParams(oldSearch)
const selectedActionIdPrev = searchParamsPrev.get(queryParams.selectedActionId)
// If query parameter hasn't changed, no action to take
if (selectedActionId === selectedActionIdPrev) {
return
}
}

/**
* If there a id in URL, and action is not already open or is different than open item, open the item from url.
*/
if (selectedActionId
&& (!this.state.actionSelected
|| (selectedActionId !== this.state.actionSelected.actionId))
) {
const action = this.props.actions.find(a => a.actionId === selectedActionId)
if (!action) {
// Invalid action id, go back to list
this.props.history.replace(this.props.match.path, { app: this.props.app })
return
}

this.openAction(action)
}
}

onSelectAction(action: CLM.ActionBase) {
if (this.props.editingPackageId === this.props.app.devPackageId) {
this.setState({
actionSelected: action,
isActionEditorOpen: true
})
const isEditingDevPackage = this.props.editingPackageId === this.props.app.devPackageId
if (!isEditingDevPackage) {
return
}

this.openAction(action)
}

private async openAction(action: CLM.ActionBase) {
const queryObject = {
[queryParams.selectedActionId]: action.actionId
}
const query = queryString.stringify(queryObject)
const url = `${this.props.match.path}?${query}`
this.props.history.push(url, { app: this.props.app })

this.setState({
actionSelected: action,
isActionEditorOpen: true
})
}

onClickOpenActionEditor() {
Expand All @@ -66,6 +129,13 @@ class Actions extends React.Component<Props, ComponentState> {

@OF.autobind
onClickCancelActionEditor() {
// Remove selection from query parameter
const searchParams = new URLSearchParams(this.props.location.search)
const selectedActionId = searchParams.get(queryParams.selectedActionId)
if (selectedActionId) {
this.props.history.replace(this.props.match.path, { app: this.props.app })
}

this.setState({
isActionEditorOpen: false,
actionSelected: null
Expand All @@ -75,14 +145,9 @@ class Actions extends React.Component<Props, ComponentState> {
}

@OF.autobind
async onClickDeleteActionEditor(action: CLM.ActionBase, removeFromDialogs: boolean) {
await Utils.setStateAsync(this, {
isActionEditorOpen: false,
actionSelected: null
})

onClickDeleteActionEditor(action: CLM.ActionBase, removeFromDialogs: boolean) {
this.onClickCancelActionEditor()
this.props.deleteActionThunkAsync(this.props.app.appId, action.actionId, removeFromDialogs)
setTimeout(() => this.focusNewActionButton(), 1000)
}

@OF.autobind
Expand Down Expand Up @@ -236,6 +301,7 @@ export interface ReceivedProps {
// Props types inferred from mapStateToProps & dispatchToProps
const stateProps = returntypeof(mapStateToProps);
const dispatchProps = returntypeof(mapDispatchToProps);
type Props = typeof stateProps & typeof dispatchProps & ReceivedProps & InjectedIntlProps;
type Props = typeof stateProps & typeof dispatchProps & ReceivedProps & InjectedIntlProps & RouteComponentProps<any>

export default connect<typeof stateProps, typeof dispatchProps, ReceivedProps>(mapStateToProps, mapDispatchToProps)(injectIntl(Actions) as any)
// TODO: Why use 'as any' hack? This component is almost same as Entities component.
export default connect<typeof stateProps, typeof dispatchProps, ReceivedProps>(mapStateToProps, mapDispatchToProps)(withRouter(injectIntl(Actions) as any) as any)
92 changes: 78 additions & 14 deletions src/routes/Apps/App/Entities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import * as React from 'react'
import { returntypeof } from 'react-redux-typescript'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { RouteComponentProps } from 'react-router'
import * as OF from 'office-ui-fabric-react'
import { EntityCreatorEditor } from '../../../components/modals'
import actions from '../../../actions'
Expand All @@ -17,6 +19,7 @@ import FormattedMessageId from '../../../components/FormattedMessageId'
import { injectIntl, InjectedIntl, InjectedIntlProps } from 'react-intl'
import * as Util from '../../../Utils/util'
import * as moment from 'moment'
import * as queryString from 'query-string'

interface IRenderableColumn extends OF.IColumn {
render: (entity: EntityBase, component: Entities) => JSX.Element | JSX.Element[]
Expand Down Expand Up @@ -136,6 +139,10 @@ function getColumns(intl: InjectedIntl): IRenderableColumn[] {
]
}

const queryParams = {
selectedEntityId: 'selectedEntityId'
}

interface ComponentState {
searchValue: string
createEditModalOpen: boolean
Expand All @@ -145,8 +152,7 @@ interface ComponentState {
}

class Entities extends React.Component<Props, ComponentState> {
newEntityButtonRef = React.createRef<OF.IButton>()
state: ComponentState
private newEntityButtonRef = React.createRef<OF.IButton>()

constructor(props: Props) {
super(props)
Expand Down Expand Up @@ -177,16 +183,53 @@ class Entities extends React.Component<Props, ComponentState> {

componentDidMount() {
this.focusNewEntityButton()
this.handleQueryParameters(this.props.location.search)
}

componentWillReceiveProps(nextProps: Props) {
this.handleQueryParameters(nextProps.location.search, this.props.location.search)
}

@OF.autobind
async handleQueryParameters(newSearch: string, oldSearch?: string): Promise<void> {
if (this.props.entities.length === 0) {
return
}

const searchParams = new URLSearchParams(newSearch)
const selectedEntityId = searchParams.get(queryParams.selectedEntityId)

if (oldSearch) {
const searchParamsPrev = new URLSearchParams(oldSearch)
const selectedEntityIdPrev = searchParamsPrev.get(queryParams.selectedEntityId)
// If query parameter hasn't changed, no action to take
if (selectedEntityId === selectedEntityIdPrev) {
return
}
}

/**
* If there a id in URL, and entity is not already open or is different than open item, open the item from url.
*/
if (selectedEntityId
&& (!this.state.entitySelected
|| (selectedEntityId !== this.state.entitySelected.entityId))
) {
const entity = this.props.entities.find(e => e.entityId === selectedEntityId)
if (!entity) {
// Invalid entity id, go back to list
this.props.history.replace(this.props.match.path, { app: this.props.app })
return
}

this.openEntity(entity)
}
}

@OF.autobind
handleDelete(entity: EntityBase) {
this.setState({
createEditModalOpen: false,
entitySelected: null
})
this.handleCloseCreateModal()
this.props.deleteEntityThunkAsync(this.props.app.appId, entity)
setTimeout(() => this.focusNewEntityButton(), 1000)
}

@OF.autobind
Expand All @@ -199,6 +242,13 @@ class Entities extends React.Component<Props, ComponentState> {

@OF.autobind
handleCloseCreateModal() {
// Remove selection from query parameter
const searchParams = new URLSearchParams(this.props.location.search)
const selectedEntityId = searchParams.get(queryParams.selectedEntityId)
if (selectedEntityId) {
this.props.history.replace(this.props.match.path, { app: this.props.app })
}

this.setState({
createEditModalOpen: false,
entitySelected: null
Expand All @@ -209,12 +259,12 @@ class Entities extends React.Component<Props, ComponentState> {
}

onSelectEntity(entity: EntityBase) {
if (this.props.editingPackageId === this.props.app.devPackageId) {
this.setState({
entitySelected: entity,
createEditModalOpen: true
})
const isEditingDevPackage = this.props.editingPackageId === this.props.app.devPackageId
if (!isEditingDevPackage) {
return
}

this.openEntity(entity)
}

@OF.autobind
Expand Down Expand Up @@ -368,6 +418,20 @@ class Entities extends React.Component<Props, ComponentState> {
);
}

private async openEntity(entity: EntityBase) {
const queryObject = {
[queryParams.selectedEntityId]: entity.entityId
}
const query = queryString.stringify(queryObject)
const url = `${this.props.match.path}?${query}`
this.props.history.push(url, { app: this.props.app })

this.setState({
entitySelected: entity,
createEditModalOpen: true
})
}

private focusNewEntityButton() {
if (this.newEntityButtonRef.current) {
this.newEntityButtonRef.current.focus()
Expand All @@ -394,6 +458,6 @@ export interface ReceivedProps {
// Props types inferred from mapStateToProps & dispatchToProps
const stateProps = returntypeof(mapStateToProps);
const dispatchProps = returntypeof(mapDispatchToProps);
type Props = typeof stateProps & typeof dispatchProps & ReceivedProps & InjectedIntlProps
type Props = typeof stateProps & typeof dispatchProps & ReceivedProps & InjectedIntlProps & RouteComponentProps<any>

export default connect<typeof stateProps, typeof dispatchProps, ReceivedProps>(mapStateToProps, mapDispatchToProps)(injectIntl(Entities))
export default connect<typeof stateProps, typeof dispatchProps, ReceivedProps>(mapStateToProps, mapDispatchToProps)(withRouter(injectIntl(Entities)))