diff --git a/frontend/src/metabase/collections/containers/CollectionCreate.jsx b/frontend/src/metabase/collections/containers/CollectionCreate.jsx deleted file mode 100644 index 923db528236668f8b96b9d4301bbba26788d6421..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/collections/containers/CollectionCreate.jsx +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable react/prop-types */ -import React, { Component } from "react"; -import { connect } from "react-redux"; -import { getValues } from "redux-form"; -import { withRouter } from "react-router"; -import { goBack } from "react-router-redux"; -import _ from "underscore"; - -import Collection from "metabase/entities/collections"; -import { PLUGIN_COLLECTIONS } from "metabase/plugins"; - -const { REGULAR_COLLECTION } = PLUGIN_COLLECTIONS; - -const FORM_NAME = "create-collection"; - -const mapStateToProps = (state, props) => { - const formValues = getValues(state.form[FORM_NAME]); - return { - form: Collection.selectors.getForm(state, { ...props, formValues }), - initialCollectionId: Collection.selectors.getInitialCollectionId( - state, - props, - ), - }; -}; - -const mapDispatchToProps = { - goBack, -}; - -class CollectionCreate extends Component { - handleClose = () => { - const { goBack, onClose } = this.props; - return onClose ? onClose() : goBack(); - }; - - handleSaved = collection => { - const { goBack, onSaved } = this.props; - return onSaved ? onSaved(collection) : goBack(); - }; - - render() { - const { form, initialCollectionId } = this.props; - return ( - <Collection.ModalForm - overwriteOnInitialValuesChange - formName={FORM_NAME} - form={form} - collection={{ - parent_id: initialCollectionId, - authority_level: REGULAR_COLLECTION.type, - }} - onSaved={this.handleSaved} - onClose={this.handleClose} - /> - ); - } -} - -export default _.compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps), -)(CollectionCreate); diff --git a/frontend/src/metabase/collections/containers/CollectionCreate/CollectionCreate.tsx b/frontend/src/metabase/collections/containers/CollectionCreate/CollectionCreate.tsx new file mode 100644 index 0000000000000000000000000000000000000000..74ffefec49f6b3a32b029f254c667ef6b24a6aab --- /dev/null +++ b/frontend/src/metabase/collections/containers/CollectionCreate/CollectionCreate.tsx @@ -0,0 +1,104 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { withRouter } from "react-router"; +import { goBack } from "react-router-redux"; +import _ from "underscore"; + +import { Collection as BaseCollection, CollectionId } from "metabase-types/api"; +import { State } from "metabase-types/store"; + +import Collections from "metabase/entities/collections"; + +import CollectionCreateForm from "./CollectionCreateForm"; + +type Collection = BaseCollection & { + parent_id: CollectionId; +}; + +interface CollectionCreateOwnProps { + goBack?: () => void; + onClose?: () => void; + onSaved?: (collection: Collection) => void; +} + +interface CollectionCreateStateProps { + initialCollectionId: CollectionId; +} + +interface CollectionCreateProps + extends CollectionCreateOwnProps, + CollectionCreateStateProps {} + +function mapStateToProps(state: State, props: CollectionCreateOwnProps) { + return { + initialCollectionId: Collections.selectors.getInitialCollectionId( + state, + props, + ), + }; +} + +const mapDispatchToProps = { + goBack, +}; + +function CollectionCreate({ + initialCollectionId, + goBack, + onClose, + onSaved, +}: CollectionCreateProps) { + const [parentCollectionId, setParentCollectionId] = useState<CollectionId>( + initialCollectionId, + ); + const [hasSetParentCollection, setHasSetParentCollection] = useState(false); + + useEffect(() => { + if (!hasSetParentCollection) { + setParentCollectionId(initialCollectionId); + } + }, [initialCollectionId, hasSetParentCollection]); + + const onChangeValues = useCallback( + (collection: Collection) => { + if (collection.parent_id !== parentCollectionId) { + setParentCollectionId(collection.parent_id); + setHasSetParentCollection(true); + } + }, + [parentCollectionId], + ); + + const handleClose = useCallback(() => { + if (onClose) { + onClose(); + } else { + goBack?.(); + } + }, [goBack, onClose]); + + const handleSave = useCallback( + (collection: Collection) => { + if (onSaved) { + onSaved(collection); + } else { + goBack?.(); + } + }, + [goBack, onSaved], + ); + + return ( + <CollectionCreateForm + parentCollectionId={parentCollectionId} + onChange={onChangeValues} + onSaved={handleSave} + onClose={handleClose} + /> + ); +} + +export default _.compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps), +)(CollectionCreate); diff --git a/frontend/src/metabase/collections/containers/CollectionCreate/CollectionCreateForm.tsx b/frontend/src/metabase/collections/containers/CollectionCreate/CollectionCreateForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ca7e29e0452195cb23db0de0d1ccaf949ddd9874 --- /dev/null +++ b/frontend/src/metabase/collections/containers/CollectionCreate/CollectionCreateForm.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import { connect } from "react-redux"; +import { withRouter } from "react-router"; +import _ from "underscore"; + +import { Collection as BaseCollection, CollectionId } from "metabase-types/api"; +import { State } from "metabase-types/store"; + +import Collections from "metabase/entities/collections"; +import { PLUGIN_COLLECTIONS } from "metabase/plugins"; + +const { REGULAR_COLLECTION } = PLUGIN_COLLECTIONS; + +type Collection = BaseCollection & { + parent_id: CollectionId; +}; + +interface CollectionCreateFormOwnProps { + parentCollectionId: CollectionId; + onChange: (collection: Collection) => void; + onSaved?: (collection: Collection) => void; + onClose?: () => void; +} + +interface CollectionCreateFormStateProps { + form: unknown; +} + +interface CollectionCreateFormProps + extends CollectionCreateFormOwnProps, + CollectionCreateFormStateProps {} + +function mapStateToProps(state: State, props: CollectionCreateFormOwnProps) { + return { + form: Collections.selectors.getForm(state, props), + }; +} + +function CollectionCreateForm({ + form, + parentCollectionId, + onChange, + onSaved, + onClose, +}: CollectionCreateFormProps) { + return ( + <Collections.ModalForm + form={form} + collection={{ + parent_id: parentCollectionId, + authority_level: REGULAR_COLLECTION.type, + }} + overwriteOnInitialValuesChange + onChange={onChange} + onSaved={onSaved} + onClose={onClose} + /> + ); +} + +export default _.compose( + withRouter, + connect(mapStateToProps), +)(CollectionCreateForm); diff --git a/frontend/src/metabase/collections/containers/CollectionCreate/index.ts b/frontend/src/metabase/collections/containers/CollectionCreate/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..eba57ce7ecb86e71f38bc48d875a3745393b6968 --- /dev/null +++ b/frontend/src/metabase/collections/containers/CollectionCreate/index.ts @@ -0,0 +1 @@ +export { default } from "./CollectionCreate"; diff --git a/frontend/src/metabase/collections/containers/CollectionEdit.jsx b/frontend/src/metabase/collections/containers/CollectionEdit.jsx deleted file mode 100644 index a70970b92dfe84248fe782a5013f99a51af2e6f5..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/collections/containers/CollectionEdit.jsx +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint-disable react/prop-types */ -import React, { Component } from "react"; - -import { connect } from "react-redux"; -import { goBack, push } from "react-router-redux"; - -import * as Urls from "metabase/lib/urls"; -import Collection from "metabase/entities/collections"; - -function mapStateToProps(state, props) { - return { - form: Collection.selectors.getForm(state, props), - }; -} - -function CollectionForm({ form, collection, onSave, onClose }) { - return ( - <Collection.ModalForm - form={form} - collection={collection} - onSaved={onSave} - onClose={onClose} - /> - ); -} - -const UpdateCollectionForm = connect(mapStateToProps)(CollectionForm); - -const mapDispatchToProps = { - push, - goBack, -}; - -class CollectionEdit extends Component { - onSave = updatedCollection => { - const url = Urls.collection(updatedCollection); - this.props.push(url); - }; - - render() { - const collectionId = Urls.extractCollectionId(this.props.params.slug); - return ( - <Collection.Loader id={collectionId}> - {({ collection, update }) => ( - <UpdateCollectionForm - collection={collection} - onSave={this.onSave} - onClose={this.props.goBack} - /> - )} - </Collection.Loader> - ); - } -} - -export default connect(null, mapDispatchToProps)(CollectionEdit); diff --git a/frontend/src/metabase/collections/containers/CollectionEdit/CollectionEdit.tsx b/frontend/src/metabase/collections/containers/CollectionEdit/CollectionEdit.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f2cf96dc4eeabe32b4f48e4ec5d1547d43454270 --- /dev/null +++ b/frontend/src/metabase/collections/containers/CollectionEdit/CollectionEdit.tsx @@ -0,0 +1,92 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { goBack, push, LocationAction } from "react-router-redux"; +import _ from "underscore"; + +import * as Urls from "metabase/lib/urls"; +import Collections from "metabase/entities/collections"; + +import { Collection as BaseCollection, CollectionId } from "metabase-types/api"; +import { State } from "metabase-types/store"; + +import CollectionEditForm from "./CollectionEditForm"; + +type Collection = BaseCollection & { + parent_id: CollectionId; +}; + +interface CollectionEditOwnProps { + params: { + slug: string; + }; +} + +interface CollectionEditLoaderProps { + collection: Collection; +} + +interface CollectionEditDispatchProps { + push: LocationAction; + goBack: () => void; +} + +interface CollectionEditProps + extends CollectionEditOwnProps, + CollectionEditLoaderProps, + CollectionEditDispatchProps {} + +const mapDispatchToProps = { + push, + goBack, +}; + +function CollectionEdit({ collection, push, goBack }: CollectionEditProps) { + const [parentCollectionId, setParentCollectionId] = useState<CollectionId>( + collection?.parent_id, + ); + const [hasSetParentCollection, setHasSetParentCollection] = useState(false); + + useEffect(() => { + if (collection && !hasSetParentCollection) { + setParentCollectionId(collection.parent_id); + } + }, [collection, hasSetParentCollection]); + + const onChangeValues = useCallback( + (collection: Collection) => { + if (collection.parent_id !== parentCollectionId) { + setParentCollectionId(collection.parent_id); + setHasSetParentCollection(true); + } + }, + [parentCollectionId], + ); + + const onSave = useCallback( + collection => { + push(Urls.collection(collection)); + }, + [push], + ); + + return ( + <CollectionEditForm + collection={collection} + parentCollectionId={parentCollectionId} + onChange={onChangeValues} + onSave={onSave} + onClose={goBack} + /> + ); +} + +export default _.compose( + connect<unknown, CollectionEditDispatchProps, CollectionEditOwnProps, State>( + null, + mapDispatchToProps, + ), + Collections.load({ + id: (state: State, props: CollectionEditOwnProps) => + Urls.extractCollectionId(props.params.slug), + }), +)(CollectionEdit); diff --git a/frontend/src/metabase/collections/containers/CollectionEdit/CollectionEditForm.tsx b/frontend/src/metabase/collections/containers/CollectionEdit/CollectionEditForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..21a60833c7004ca4e3e7700488c3a6e50fa796bb --- /dev/null +++ b/frontend/src/metabase/collections/containers/CollectionEdit/CollectionEditForm.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { connect } from "react-redux"; + +import Collections from "metabase/entities/collections"; + +import { Collection as BaseCollection, CollectionId } from "metabase-types/api"; +import { State } from "metabase-types/store"; + +type Collection = BaseCollection & { + parent_id: CollectionId; +}; + +interface CollectionEditFormOwnProps { + collection: Collection; + parentCollectionId: CollectionId; + onChange: (collection: Collection) => void; + onSave: (collection: Collection) => void; + onClose: () => void; +} + +interface CollectionEditFormStateProps { + form: unknown; +} + +interface CollectionEditProps + extends CollectionEditFormOwnProps, + CollectionEditFormStateProps {} + +function mapStateToProps(state: State, props: CollectionEditFormOwnProps) { + return { + form: Collections.selectors.getForm(state, props), + }; +} + +function CollectionEditForm({ + form, + collection, + onChange, + onSave, + onClose, +}: CollectionEditProps) { + return ( + <Collections.ModalForm + form={form} + collection={collection} + overwriteOnInitialValuesChange + onChange={onChange} + onSaved={onSave} + onClose={onClose} + /> + ); +} + +export default connect(mapStateToProps)(CollectionEditForm); diff --git a/frontend/src/metabase/collections/containers/CollectionEdit/index.ts b/frontend/src/metabase/collections/containers/CollectionEdit/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b7bebb0cfa42438adde4aa186740902222be8903 --- /dev/null +++ b/frontend/src/metabase/collections/containers/CollectionEdit/index.ts @@ -0,0 +1 @@ +export { default } from "./CollectionEdit"; diff --git a/frontend/src/metabase/containers/Form.jsx b/frontend/src/metabase/containers/Form.jsx index ad9960a08bbc5525500b3917c4e8539d5fb7f1d6..63d7e69bd15f56c776d77bc0d2e19905e1a9593f 100644 --- a/frontend/src/metabase/containers/Form.jsx +++ b/frontend/src/metabase/containers/Form.jsx @@ -169,6 +169,7 @@ class Form extends React.Component { initialize(this.props.formName, this._getInitialValues(), newFields), ); } + this.props.onChange?.(this.props.values); } _registerFormField = field => { diff --git a/frontend/src/metabase/entities/collections/forms.js b/frontend/src/metabase/entities/collections/forms.js index 9a440e8dba9ebcb4682883a1eb76441381531d12..f8d68ccbc47bc5f4b66255983d8791069cba5709 100644 --- a/frontend/src/metabase/entities/collections/forms.js +++ b/frontend/src/metabase/entities/collections/forms.js @@ -63,19 +63,15 @@ function isPersonalOrPersonalChild(collection, collectionList) { export const getFormSelector = createSelector( [ (state, props) => props.collection || {}, - (state, props) => props.formValues || {}, + (state, props) => props.parentCollectionId, state => state.entities.collections || {}, getUser, ], - (collection, formValues, allCollections, user) => { + (collection, parentCollectionId, allCollections, user) => { const collectionList = Object.values(allCollections); const extraFields = []; - const creatingNewCollection = !collection.id; - const parentId = creatingNewCollection - ? formValues.parent_id - : collection.parent_id; - + const parentId = parentCollectionId || collection?.parent_id; const parentCollection = allCollections[parentId]; const canManageAuthorityLevel = user.is_superuser &&