diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index fbee64d..0d90d61 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -21,13 +21,7 @@ export const App = () => ( - + ) => void; +}) { + const { initialData, onSubmit } = props; + const [stacData, setStacData] = useState(initialData || {}); + + const { plugins, formData, toOutData, isLoading } = + useCollectionPlugins(stacData); + + const [view, setView] = useState('fields'); + + const editorData = useMemo( + () => (view === 'json' ? { jsonData: stacData } : formData), + [view, formData] + ); + + return ( + + {isLoading ? ( + Loading plugins... + ) : ( + + { + const exitData = + view === 'json' ? values.jsonData : toOutData(values); + return onSubmit(exitData, actions); + }} + > + {({ handleSubmit, values, isSubmitting }) => ( + + + + + {initialData && 'Edit '}Collection + + + {initialData + ? initialData.title || 'Untitled' + : 'New Collection'} + + + + + { + setView(['fields', 'json'][i] as FormView); + setStacData( + view === 'json' ? values.jsonData : toOutData(values) + ); + }} + > + + FORM + JSON + + + + {view === 'fields' && + plugins.map((pl) => ( + + {({ field }) => ( + + {pl.name} + + + )} + + ))} + + + {view === 'json' && ( + + )} + + + + + )} + + + )} + + ); +} diff --git a/packages/client/src/pages/CollectionForm/index.tsx b/packages/client/src/pages/CollectionForm/index.tsx index 1443320..5e250c6 100644 --- a/packages/client/src/pages/CollectionForm/index.tsx +++ b/packages/client/src/pages/CollectionForm/index.tsx @@ -1,144 +1,148 @@ -import React, { useMemo, useState } from 'react'; -import { - Plugin, - PluginBox, - useCollectionPlugins, - WidgetRenderer -} from '@stac-manager/data-core'; -import { Box, Button, Flex, Heading, Textarea } from '@chakra-ui/react'; -import useUpdateCollection from './useUpdateCollection'; -import { Field, FieldProps, Formik } from 'formik'; -import { WidgetJSON } from '@stac-manager/data-widgets'; - -interface CollectionFormProps {} - -type FormView = 'fields' | 'json'; -type FormAction = 'submit' | 'switch-view'; - -export function CollectionForm(props: CollectionFormProps) { - const [stacData, setStacData] = useState({}); - - const { plugins, formData, toOutData, isLoading } = - useCollectionPlugins(stacData); - - const [view, setView] = useState('fields'); - - const onAction: EditFormProps['onAction'] = (action, { view, data }) => { - console.log(action, view, data); - if (action === 'submit') { - const exitData = - view === 'json' ? JSON.parse(data.jsonData) : toOutData(data); - console.log('🚀 ~ onSubmit ~ exitData:', exitData); - } else if (action === 'switch-view') { - if (view === 'json') { - console.log('data.jsonData', data.jsonData); - setStacData(data.jsonData); - setView('fields'); - } else { - const d = toOutData(data); - console.log('d', d); - setStacData(d); - setView('json'); - } - } - }; +import React, { useEffect, useState } from 'react'; +import { Box, useToast } from '@chakra-ui/react'; +import { FormikHelpers } from 'formik'; +import { useParams } from 'react-router-dom'; +import { useCollection } from '@developmentseed/stac-react'; +import { StacCollection } from 'stac-ts'; - const editorData = useMemo( - () => - view === 'json' - ? // ? { jsonData: JSON.stringify(stacData, null, 2) } - { jsonData: stacData } - : formData, - [view, formData] - ); +import usePageTitle from '$hooks/usePageTitle'; +import Api from 'src/api'; +import { EditForm } from './EditForm'; + +export function CollectionForm() { + const { collectionId } = useParams(); - console.log('🚀 ~ CollectionForm ~ editorData:', editorData); - return ( - - {isLoading ? ( - Loading plugins... - ) : ( - - )} - + return collectionId ? ( + + ) : ( + ); } -interface EditFormProps { - data: Record; - plugins: Plugin[]; - onAction: ( - action: FormAction, - { view, data }: { view: FormView; data: any } - ) => void; - view: FormView; +export function CollectionFormNew() { + usePageTitle('New collection'); + + const toast = useToast(); + + const onSubmit = async (data: any, formikHelpers: FormikHelpers) => { + try { + toast({ + id: 'collection-submit', + title: 'Creating collection...', + status: 'loading', + duration: null, + position: 'bottom-right' + }); + + await collectionTransaction().create(data); + + toast.update('collection-submit', { + title: 'Collection created', + status: 'success', + duration: 5000, + isClosable: true + }); + } catch (error: any) { + toast.update('collection-submit', { + title: 'Collection creation failed', + description: error.detail?.code, + status: 'error', + duration: 8000, + isClosable: true + }); + } + formikHelpers.setSubmitting(false); + }; + + return ; } -export function EditForm({ plugins, data, onAction, view }: EditFormProps) { - return ( - - { - onAction('submit', { - view, - data: values - }); - }} - > - {(props) => ( - - - - {view === 'json' ? ( - - ) : ( - plugins.map((pl) => ( - - {({ field }) => ( - - {pl.name} - - - )} - - )) - )} - - )} - - - ); -} \ No newline at end of file +export function CollectionFormEdit(props: { id: string }) { + const { id } = props; + const { collection, state, error } = useCollection(id); + const [triedLoading, setTriedLoading] = useState(!!collection); + + usePageTitle(collection ? `Edit collection ${id}` : 'Edit collection'); + + const toast = useToast(); + + useEffect(() => { + if (state === 'LOADING') { + setTriedLoading(true); + } + }, [state]); + + if (state === 'LOADING' || !triedLoading) { + return Loading collection...; + } + + if (error) { + return Error loading collection: {error.detail}; + } + + const onSubmit = async (data: any, formikHelpers: FormikHelpers) => { + try { + toast({ + id: 'collection-submit', + title: 'Updating collection...', + status: 'loading', + duration: null, + position: 'bottom-right' + }); + + await collectionTransaction().update(id, data); + + toast.update('collection-submit', { + title: 'Collection updated', + status: 'success', + duration: 5000, + isClosable: true + }); + } catch (error: any) { + toast.update('collection-submit', { + title: 'Collection update failed', + description: error.detail?.code, + status: 'error', + duration: 8000, + isClosable: true + }); + } + formikHelpers.setSubmitting(false); + }; + + return ; +} + +type collectionTransactionType = { + update: (id: string, data: StacCollection) => Promise; + create: (data: StacCollection) => Promise; +}; + +function collectionTransaction(): collectionTransactionType { + const createRequest = async ( + url: string, + method: string, + data: StacCollection + ) => { + return Api.fetch(url, { + method, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + }; + + return { + update: (id: string, data: StacCollection) => + createRequest( + `${process.env.REACT_APP_STAC_API}/collections/${id}`, + 'PUT', + data + ), + create: (data: StacCollection) => + createRequest( + `${process.env.REACT_APP_STAC_API}/collections/`, + 'POST', + data + ) + }; +} diff --git a/packages/client/src/pages/CollectionForm/useCollectionTransaction.ts b/packages/client/src/pages/CollectionForm/useCollectionTransaction.ts new file mode 100644 index 0000000..39f2a51 --- /dev/null +++ b/packages/client/src/pages/CollectionForm/useCollectionTransaction.ts @@ -0,0 +1,52 @@ +import { useCallback, useState } from 'react'; +import { StacCollection } from 'stac-ts'; +import Api from '../../api'; +import { LoadingState, ApiError } from '../../types'; + +type UseCollectionTransactionType = { + update: (id: string, data: StacCollection) => Promise; + create: (data: StacCollection) => Promise; + error?: ApiError; + state: LoadingState; +}; + +export function useCollectionTransaction(): UseCollectionTransactionType { + const [error, setError] = useState(); + const [state, setState] = useState('IDLE'); + + const createRequest = useCallback( + async (url: string, method: string, data: StacCollection) => { + setState('LOADING'); + + try { + return Api.fetch(url, { + method: method, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + } catch (error) { + setError(error as ApiError); + } finally { + setState('IDLE'); + } + }, + [] + ); + + return { + update: (id: string, data: StacCollection) => + createRequest( + `${process.env.REACT_APP_STAC_API}/collections/${id}`, + 'PUT', + data + ), + create: (data: StacCollection) => + createRequest( + `${process.env.REACT_APP_STAC_API}/collections/`, + 'POST', + data + ), + error, + state + }; +} diff --git a/packages/client/src/pages/CollectionForm/useUpdateCollection.ts b/packages/client/src/pages/CollectionForm/useUpdateCollection.ts deleted file mode 100644 index b6d961a..0000000 --- a/packages/client/src/pages/CollectionForm/useUpdateCollection.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useCallback, useState } from "react"; -import { StacCollection } from "stac-ts"; -import Api from "../../api"; -import { LoadingState, ApiError } from "../../types"; - -type UseUpdateCollectionType = { - update: (data: StacCollection) => Promise; - error?: ApiError; - state: LoadingState; -} - -function useUpdateCollection(): UseUpdateCollectionType { - const [ error, setError ] = useState(); - const [ state, setState ] = useState("IDLE"); - - const update = useCallback((data: StacCollection) => { - setState("LOADING"); - - return Api.fetch( - `${process.env.REACT_APP_STAC_API}/collections/`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(data) - }, - ) - .catch((e) => setError(e)) - .finally(() => setState("IDLE")); - }, []); - - return { - update, - error, - state - }; -} - -export default useUpdateCollection; diff --git a/packages/client/src/pages/CollectionList.tsx b/packages/client/src/pages/CollectionList.tsx index 358a7ce..2e56ce7 100644 --- a/packages/client/src/pages/CollectionList.tsx +++ b/packages/client/src/pages/CollectionList.tsx @@ -1,14 +1,16 @@ import React from 'react'; -import { Link, useNavigate } from 'react-router-dom'; +import { Link, NavLink, useNavigate } from 'react-router-dom'; import { TableContainer, Table, - Text, + Button, Thead, Tr, Th, Td, - Tbody + Tbody, + Heading, + Flex } from '@chakra-ui/react'; import { useCollections } from '@developmentseed/stac-react'; import type { StacCollection } from 'stac-ts'; @@ -21,8 +23,18 @@ function CollectionList() { const { collections, state } = useCollections(); return ( - <> - Collections + + + Collections + + @@ -69,7 +81,7 @@ function CollectionList() {
- +
); } diff --git a/packages/client/src/pages/ItemList/index.tsx b/packages/client/src/pages/ItemList/index.tsx index 1fdadd0..b566c21 100644 --- a/packages/client/src/pages/ItemList/index.tsx +++ b/packages/client/src/pages/ItemList/index.tsx @@ -1,13 +1,13 @@ -import { useEffect } from "react"; -import { Heading, Box } from "@chakra-ui/react"; -import { useStacSearch } from "@developmentseed/stac-react"; +import React, { useEffect } from 'react'; +import { Heading, Flex } from '@chakra-ui/react'; +import { useStacSearch } from '@developmentseed/stac-react'; -import { usePageTitle } from "../../hooks"; -import ItemListFilter from "./ItemListFilter"; -import ItemResults from "../../components/ItemResults"; +import { usePageTitle } from '../../hooks'; +import ItemListFilter from './ItemListFilter'; +import ItemResults from '../../components/ItemResults'; function ItemList() { - usePageTitle("Items"); + usePageTitle('Items'); const { results, state, @@ -29,10 +29,8 @@ function ItemList() { }, [submit]); // eslint-disable-line react-hooks/exhaustive-deps return ( - <> - - Items - + + Items - + ); } diff --git a/packages/client/src/plugin-system/config.ts b/packages/client/src/plugin-system/config.ts index 558ba43..fd7faf7 100644 --- a/packages/client/src/plugin-system/config.ts +++ b/packages/client/src/plugin-system/config.ts @@ -1,10 +1,19 @@ import { extendPluginConfig } from '@stac-manager/data-core'; import { defaultPluginWidgetConfig } from '@stac-manager/data-widgets'; -import { PluginCore, PluginTest } from '@stac-manager/data-plugins'; +import { + PluginCore, + PluginItemAssets, + // PluginKitchenSink, + PluginRender +} from '@stac-manager/data-plugins'; export const config = extendPluginConfig(defaultPluginWidgetConfig, { - collectionPlugins: [new PluginCore()], - // collectionPlugins: [new PluginTest()], + collectionPlugins: [ + new PluginCore(), + new PluginItemAssets(), + new PluginRender() + ], + // collectionPlugins: [new PluginKitchenSink()], itemPlugins: [], 'ui:widget': {}