diff --git a/src/collection/CollectionTable.tsx b/src/collection/CollectionTable.tsx index 308027f..449ee2c 100644 --- a/src/collection/CollectionTable.tsx +++ b/src/collection/CollectionTable.tsx @@ -1,22 +1,8 @@ -import React, { - MouseEvent, - useCallback, - useEffect, - useMemo, - useRef -} from "react"; +import React, { useCallback, useEffect, useMemo, useRef } from "react"; import BaseTable, { Column } from "react-base-table"; import Measure, { ContentRect } from "react-measure"; import "react-base-table/styles.css"; -import { - Box, - Button, - Paper, - Typography, - useMediaQuery, - useTheme -} from "@material-ui/core"; -import { Add, Delete } from "@material-ui/icons"; +import { Box, Paper, Typography } from "@material-ui/core"; import { AdditionalColumnDelegate, @@ -34,7 +20,6 @@ import { getIconForProperty } from "../util/property_icons"; import { CollectionTableToolbar } from "./CollectionTableToolbar"; import SkeletonComponent from "../preview/components/SkeletonComponent"; import ErrorBoundary from "../components/ErrorBoundary"; -import DeleteEntityDialog from "./DeleteEntityDialog"; import TableCell from "./TableCell"; import PopupFormField from "./popup_field/PopupFormField"; import { OutsideAlerter } from "../util/OutsideAlerter"; @@ -46,7 +31,6 @@ import { TableCellProps } from "./SelectedCellContext"; import { useHistory } from "react-router-dom"; import { CircularProgressCenter } from "../components"; import { useTableStyles } from "./styles"; -import { CollectionRowActions } from "./CollectionRowActions"; import { getPreviewSizeFrom } from "../preview/util"; const PAGE_SIZE = 50; @@ -69,30 +53,24 @@ type Order = "asc" | "desc" | undefined; export function CollectionTable, Key extends string = string, P extends Properties = Properties>({ - includeToolbar, initialFilter, initialSort, collectionPath, schema, paginationEnabled, properties, - deleteEnabled = true, editEnabled = true, excludedProperties, textSearchDelegate, additionalColumns, filterableProperties, inlineEditing, - onNewClick, - extraActions, + toolbarWidgetBuilder, title, - onSelection, + tableRowWidgetBuilder, onEntityClick, - onEntityDelete, - onMultipleEntitiesDelete, defaultSize = "m", - createFormField, - selectionEnabled = true + createFormField }: CollectionTableProps) { const [data, setData] = React.useState[]>([]); @@ -101,9 +79,6 @@ export function CollectionTable, const [dataLoadingError, setDataLoadingError] = React.useState(); const [size, setSize] = React.useState(defaultSize); - const [selectedItems, setSelectedItems] = React.useState[]>([]); - - const [deleteEntityClicked, setDeleteEntityClicked] = React.useState | Entity[] | undefined>(undefined); const [textSearchInProgress, setTextSearchInProgress] = React.useState(false); const [textSearchLoading, setTextSearchLoading] = React.useState(false); @@ -134,50 +109,7 @@ export function CollectionTable, const clickableRows = (!editEnabled || !inlineEditing) && onEntityClick; - const theme = useTheme(); - const largeLayout = useMediaQuery(theme.breakpoints.up("md")); - - function buildActions() { - const addButton = editEnabled && onNewClick && (largeLayout ? - - : ); - - const multipleDeleteButton = selectionEnabled && deleteEnabled && - ; - - return ( - <> - {multipleDeleteButton} - {addButton} - - ); - } - - const actions = buildActions(); + const actions = toolbarWidgetBuilder && toolbarWidgetBuilder({ size }); const history = useHistory(); history.listen(() => { @@ -197,19 +129,6 @@ export function CollectionTable, setPreventOutsideClick(false); }, []); - const toggleEntitySelection = (entity: Entity) => { - let newValue; - if (selectedItems.indexOf(entity) > -1) { - newValue = selectedItems.filter((item: Entity) => item !== entity); - } else { - newValue = [...selectedItems, entity]; - } - setSelectedItems(newValue); - if (onSelection) - onSelection(collectionPath, newValue); - }; - - const additionalColumnsMap: Record> = useMemo(() => { return additionalColumns ? additionalColumns @@ -388,9 +307,9 @@ export function CollectionTable, const entity: Entity = rowData; - if (columnIndex === 0) { - return buildTableRowButtons({ - rowIndex, + if (columnIndex === 0 && tableRowWidgetBuilder) { + return tableRowWidgetBuilder({ + size, entity }); } @@ -495,25 +414,6 @@ export function CollectionTable, } }; - const buildTableRowButtons = ({ entity }: any) => { - - const isSelected = selectedItems.indexOf(entity) > -1; - - return ( - - ); - - }; const headerRenderer = ({ columnIndex }: any) => { @@ -651,45 +551,22 @@ export function CollectionTable, ); } - - const internalOnEntityDelete = (collectionPath: string, entity: Entity) => { - if (onEntityDelete) - onEntityDelete(collectionPath, entity); - setSelectedItems(selectedItems.filter((e) => e.id !== entity.id)); - }; - - const internalOnMultipleEntitiesDelete = (collectionPath: string, entities: Entity[]) => { - if (onMultipleEntitiesDelete) - onMultipleEntitiesDelete(collectionPath, entities); - setSelectedItems([]); - }; - return ( <> - setDeleteEntityClicked(undefined)}/> - - {includeToolbar && } + onFilterUpdate={onFilterUpdate}/> , P extends Properties = Properties> { + /** * Absolute collection path */ @@ -22,11 +24,6 @@ export interface CollectionTableProps void; - - /** - * Additional components such as buttons in the - * collection toolbar - */ - extraActions?: React.ReactNode; - /** * Should the table add an edit button. If set to false `inlineEditing` * has no effect. @@ -108,16 +94,6 @@ export interface CollectionTableProps[]): void; - /** * Callback when anywhere on the table is clicked */ @@ -138,4 +114,20 @@ export interface CollectionTableProps React.ReactNode; + + /** + * Builder for creating the buttons in each row + * @param entity + * @param size + */ + tableRowWidgetBuilder?: ({ + entity, + size + }: { entity: Entity, size: CollectionSize }) => React.ReactNode; + } diff --git a/src/collection/CollectionTableToolbar.tsx b/src/collection/CollectionTableToolbar.tsx index f14ad7a..d632862 100644 --- a/src/collection/CollectionTableToolbar.tsx +++ b/src/collection/CollectionTableToolbar.tsx @@ -81,7 +81,6 @@ interface CollectionTableToolbarProps { onTextSearch?: (searchString?: string) => void; filterableProperties?: (keyof S["properties"])[]; actions?: React.ReactNode; - extraActions?: React.ReactNode; loading: boolean; title?: React.ReactNode, @@ -178,8 +177,6 @@ export function CollectionTableToolbar(props: Collection } - {props.extraActions} - {props.actions} diff --git a/src/collection/EntityCollectionTable.tsx b/src/collection/EntityCollectionTable.tsx new file mode 100644 index 0000000..2d6427d --- /dev/null +++ b/src/collection/EntityCollectionTable.tsx @@ -0,0 +1,183 @@ +import { + CollectionSize, + Entity, + EntityCollectionView, + EntitySchema +} from "../models"; +import { CollectionTable } from "./CollectionTable"; +import { createFormField } from "../form/form_factory"; +import { Button, Typography, useMediaQuery, useTheme } from "@material-ui/core"; +import { useSelectedEntityContext } from "../side_dialog/SelectedEntityContext"; +import React, { useState } from "react"; +import { Add, Delete } from "@material-ui/icons"; +import { CollectionRowActions } from "./CollectionRowActions"; +import DeleteEntityDialog from "./DeleteEntityDialog"; + +type EntitySubCollectionProps = { + collectionPath: string; + view: EntityCollectionView; +} + +export function EntityCollectionTable({ + collectionPath, + view + }: EntitySubCollectionProps +) { + const selectedEntityContext = useSelectedEntityContext(); + + const theme = useTheme(); + const largeLayout = useMediaQuery(theme.breakpoints.up("md")); + + const [deleteEntityClicked, setDeleteEntityClicked] = React.useState | Entity[] | undefined>(undefined); + const [selectedEntities, setSelectedEntities] = useState[]>([]); + + const deleteEnabled = view.deleteEnabled === undefined || view.deleteEnabled; + const editEnabled = view.editEnabled === undefined || view.editEnabled; + const inlineEditing = editEnabled && (view.inlineEditing === undefined || view.inlineEditing); + const selectionEnabled = view.selectionEnabled === undefined || view.selectionEnabled; + const paginationEnabled = view.pagination === undefined || view.pagination; + + const onEntityClick = (collectionPath: string, entity: Entity) => { + selectedEntityContext.open({ + entityId: entity.id, + collectionPath + }); + }; + + const onNewClick = (e: React.MouseEvent) => { + e.stopPropagation(); + return collectionPath && selectedEntityContext.open({ collectionPath }); + }; + + const internalOnEntityDelete = (collectionPath: string, entity: Entity) => { + setSelectedEntities(selectedEntities.filter((e) => e.id !== entity.id)); + }; + + const internalOnMultipleEntitiesDelete = (collectionPath: string, entities: Entity[]) => { + setSelectedEntities([]); + }; + + const title = ( + <> + + {`${view.schema.name} list`} + + + {`/${collectionPath}`} + + + ); + + const toggleEntitySelection = (entity: Entity) => { + let newValue; + if (selectedEntities.indexOf(entity) > -1) { + newValue = selectedEntities.filter((item: Entity) => item !== entity); + } else { + newValue = [...selectedEntities, entity]; + } + setSelectedEntities(newValue); + }; + + const tableRowButtonsBuilder = ({ + entity, + size + }: { entity: Entity, size: CollectionSize }) => { + + const isSelected = selectedEntities.indexOf(entity) > -1; + + return ( + + ); + + }; + + function toolbarActionsBuilder({ size }: { size: CollectionSize }) { + + const addButton = editEnabled && onNewClick && (largeLayout ? + + : ); + + const multipleDeleteButton = selectionEnabled && deleteEnabled && + ; + + const extraActions = view.extraActions ? view.extraActions({ + view: view, + selectedEntities + }) : undefined; + + return ( + <> + {extraActions} + {multipleDeleteButton} + {addButton} + + ); + } + + return (<> + + + + setDeleteEntityClicked(undefined)}/> + + ); +} diff --git a/src/collection/EntitySubcollection.tsx b/src/collection/EntitySubcollection.tsx deleted file mode 100644 index 517e919..0000000 --- a/src/collection/EntitySubcollection.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Entity, EntityCollectionView, EntitySchema } from "../models"; -import { removeInitialSlash } from "../routes/navigation"; -import { CollectionTable } from "./CollectionTable"; -import { createFormField } from "../form/form_factory"; -import { Box, Typography } from "@material-ui/core"; -import { useSelectedEntityContext } from "../side_dialog/SelectedEntityContext"; -import React, { useState } from "react"; - -type EntitySubCollectionProps = { - entity: Entity; - view: EntityCollectionView; - onSubcollectionEntityClick: (collectionPath: string, entity: Entity) => void; - tabsPosition: number; - colIndex: number; - context: "main" | "side" -} - -export function EntityFormSubCollection({ - entity, - view, - onSubcollectionEntityClick, - tabsPosition, - colIndex, - context - }: EntitySubCollectionProps -) { - const selectedEntityContext = useSelectedEntityContext(); - const collectionPath = entity ? `${entity?.reference.path}/${removeInitialSlash(view.relativePath)}` : undefined; - const onNewClick = (e: React.MouseEvent) => { - e.stopPropagation(); - return collectionPath && selectedEntityContext.open({ collectionPath }); - }; - - const deleteEnabled = view.deleteEnabled === undefined || view.deleteEnabled; - const editEnabled = view.editEnabled === undefined || view.editEnabled; - const inlineEditing = editEnabled && (view.inlineEditing === undefined || view.inlineEditing); - const [selectedEntities, setSelectedEntities] = useState[] | undefined>(); - - const onEntityPreDelete = (collectionPath: string, entity: Entity) => - view.schema.onDelete && view.schema.onPreDelete({ - schema: view.schema, - collectionPath, - id: entity.id, - entity - }); - - const onEntityDelete = (collectionPath: string, entity: Entity) => - view.schema.onDelete && view.schema.onDelete({ - schema: view.schema, - collectionPath, - id: entity.id, - entity - }); - - const onMultipleEntitiesDelete = (collectionPath: string, entities: Entity[]) => - view.schema.onDelete && - entities.forEach((entity) => view.schema.onDelete({ - schema: view.schema, - collectionPath, - id: entity.id, - entity - })); - - const onEntityClick = (collectionPath: string, clickedEntity: Entity) => - onSubcollectionEntityClick(collectionPath, clickedEntity); - - const title = ( - - {`/${collectionPath}`} - - ); - - const extraActions = view.extraActions ? view.extraActions({ - view: view, - selectedEntities - }) : undefined; - - function onSelection(collectionPath: string, entities?: Entity[]) { - setSelectedEntities(entities); - } - - return ; -} diff --git a/src/routes/CollectionRoute.tsx b/src/routes/CollectionRoute.tsx index 79adffb..4f72b8d 100644 --- a/src/routes/CollectionRoute.tsx +++ b/src/routes/CollectionRoute.tsx @@ -7,6 +7,7 @@ import { useBreadcrumbsContext } from "../contexts"; import { CollectionTable } from "../collection/CollectionTable"; import { useSelectedEntityContext } from "../side_dialog/SelectedEntityContext"; import { createFormField } from "../form/form_factory"; +import { EntityCollectionTable } from "../collection/EntityCollectionTable"; export const useStyles = makeStyles(() => createStyles({ @@ -32,9 +33,6 @@ export function CollectionRoute({ : CollectionRouteProps) { const { url } = useRouteMatch(); - - const [selectedEntities, setSelectedEntities] = useState [] | undefined>(); - const breadcrumbsContext = useBreadcrumbsContext(); React.useEffect(() => { breadcrumbsContext.set({ @@ -42,90 +40,13 @@ export function CollectionRoute({ }); }, [url]); - const selectedEntityContext = useSelectedEntityContext(); - - const onEntityClick = (collectionPath: string, entity: Entity) => { - selectedEntityContext.open({ - entityId: entity.id, - collectionPath - }); - }; - - const deleteEnabled = view.deleteEnabled === undefined || view.deleteEnabled; - const editEnabled = view.editEnabled === undefined || view.editEnabled; - const inlineEditing = editEnabled && (view.inlineEditing === undefined || view.inlineEditing); - const selectionEnabled = view.selectionEnabled === undefined || view.selectionEnabled; - const classes = useStyles(); - const onNewClick = (e: React.MouseEvent) => { - e.stopPropagation(); - return selectedEntityContext.open({ collectionPath }); - }; - - const title = ( - <> - - {`${view.schema.name} list`} - - - {`/${collectionPath}`} - - - ); - - const extraActions = view.extraActions ? view.extraActions({ - view: view, - selectedEntities - }) : undefined; - - function onSelection(collectionPath: string, entities?: Entity[]) { - setSelectedEntities(entities); - } - - const onEntityDelete = (collectionPath: string, entity: Entity) => - view.schema.onDelete && view.schema.onDelete({ - schema: view.schema, - collectionPath, - id: entity.id, - entity: entity - }); - - const onMultipleEntitiesDelete = (collectionPath: string, entities: Entity[]) => - entities.forEach((entity) => view.schema.onDelete && view.schema.onDelete({ - schema: view.schema, - collectionPath, - id: entity.id, - entity - })); - return (
- +
); diff --git a/src/routes/EntityFormRoute.tsx b/src/routes/EntityFormRoute.tsx index d2794ee..9288a02 100644 --- a/src/routes/EntityFormRoute.tsx +++ b/src/routes/EntityFormRoute.tsx @@ -20,7 +20,11 @@ import { Theme, Typography } from "@material-ui/core"; -import { BreadcrumbEntry, getEntityPath } from "./navigation"; +import { + BreadcrumbEntry, + getEntityPath, + removeInitialSlash +} from "./navigation"; import { CircularProgressCenter } from "../components"; import { useSelectedEntityContext } from "../side_dialog/SelectedEntityContext"; import { useBreadcrumbsContext, useSnackbarController } from "../contexts"; @@ -35,7 +39,7 @@ import { import CloseIcon from "@material-ui/icons/Close"; import OpenInBrowserIcon from "@material-ui/icons/OpenInBrowser"; import { EntityPreview } from "../preview"; -import { EntityFormSubCollection } from "../collection/EntitySubcollection"; +import { EntityCollectionTable } from "../collection/EntityCollectionTable"; const useStylesSide = makeStyles((theme: Theme) => @@ -226,14 +230,6 @@ function EntityFormRoute({ }, [location["subcollection"]]); - function onSubcollectionEntityClick(collectionPath: string, - entity: Entity) { - selectedEntityContext.open({ - entityId: entity.id, - collectionPath - }); - } - async function onEntitySave(schema: S, collectionPath: string, id: string | undefined, values: EntityValues): Promise { if (!status) @@ -342,14 +338,34 @@ function EntityFormRoute({ const subCollectionsView = view.subcollections && view.subcollections.map( (subcollectionView, colIndex) => { - return EntityFormSubCollection({ - entity: entity as Entity, - view: subcollectionView, - onSubcollectionEntityClick, - tabsPosition, - colIndex, - context - }); + const collectionPath = entity ? `${entity?.reference.path}/${removeInitialSlash(subcollectionView.relativePath)}` : undefined; + + return ( + + ); } ); diff --git a/tsconfig.json b/tsconfig.json index 2711ace..966f539 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -36,6 +36,7 @@ "node_modules", "dist", "example", + "example_backend", "test" ] }