diff --git a/ui/app/src/CHANGELOG.md b/ui/app/src/CHANGELOG.md index a9d890ca89..49a3b46a53 100644 --- a/ui/app/src/CHANGELOG.md +++ b/ui/app/src/CHANGELOG.md @@ -1,6 +1,17 @@ # Changelog ## 5.0.0 +* [utils/resource] Removed `createFakeResource`, `createResource` and `createResourceBundle` utils. +* [components] + * Removed `pages/QuickCreateMenu` component. + * Removed `SuspenseWithEmptyState` and `WithEmptyState` components. + * Removed `resource` prop from StoreProvider +* [hooks] + * Removed `useLogicResource` hook. + * Removed `useSelectorResource` hook. + * Removed `useQuickCreateListResource` hook. + * Removed `useSystemVersionResource` hook. + * Removed `useResolveWhenNoNullResource` hook. * Upgrade to the latest version to date of the following libraries: * @mui/icons-material * @mui/lab diff --git a/ui/app/src/components/ChangeContentTypeDialog/ChangeContentTypeDialogContainer.tsx b/ui/app/src/components/ChangeContentTypeDialog/ChangeContentTypeDialogContainer.tsx index ddd6ee09b2..6381ba8522 100644 --- a/ui/app/src/components/ChangeContentTypeDialog/ChangeContentTypeDialogContainer.tsx +++ b/ui/app/src/components/ChangeContentTypeDialog/ChangeContentTypeDialogContainer.tsx @@ -21,7 +21,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { LegacyContentType } from '../../models/ContentType'; import { fetchLegacyContentTypes } from '../../services/contentTypes'; import { showErrorDialog } from '../../state/reducers/dialogs/error'; -import { useLogicResource } from '../../hooks/useLogicResource'; import { useSubject } from '../../hooks/useSubject'; import { debounceTime } from 'rxjs/operators'; import DialogBody from '../DialogBody/DialogBody'; @@ -29,10 +28,10 @@ import { Box, Checkbox, FormControlLabel } from '@mui/material'; import SingleItemSelector from '../SingleItemSelector'; import { FormattedMessage } from 'react-intl'; import SearchBar from '../SearchBar/SearchBar'; -import { SuspenseWithEmptyState } from '../Suspencified/Suspencified'; import { ContentTypesGrid, ContentTypesLoader } from '../NewContentDialog'; import DialogFooter from '../DialogFooter/DialogFooter'; import { makeStyles } from 'tss-react/mui'; +import EmptyState from '../EmptyState'; const useStyles = makeStyles()(() => ({ compact: { @@ -61,8 +60,13 @@ export function ChangeContentTypeDialogContainer(props: ChangeContentTypeDialogC const [openSelector, setOpenSelector] = useState(false); const [selectedItem, setSelectedItem] = useState(item); const [contentTypes, setContentTypes] = useState(); + const [isFetching, setIsFetching] = useState(false); const [keyword, setKeyword] = useState(''); const [debounceKeyword, setDebounceKeyword] = useState(''); + const filteredContentTypes = useMemo(() => { + const lowercaseKeyword = debounceKeyword.toLowerCase(); + return contentTypes?.filter((contentType) => contentType.label.toLowerCase().includes(lowercaseKeyword)); + }, [contentTypes, debounceKeyword]); const onSelectedContentType = (contentType: LegacyContentType) => { onContentTypeSelected?.({ @@ -72,8 +76,10 @@ export function ChangeContentTypeDialogContainer(props: ChangeContentTypeDialogC useEffect(() => { if (selectedItem.path) { - fetchLegacyContentTypes(site, selectedItem.path).subscribe( - (response) => { + setIsFetching(true); + const sub = fetchLegacyContentTypes(site, selectedItem.path).subscribe({ + next: (response) => { + setIsFetching(false); setContentTypes( response.filter( (contentType) => @@ -81,28 +87,17 @@ export function ChangeContentTypeDialogContainer(props: ChangeContentTypeDialogC ) ); }, - (response) => { + error: (response) => { + setIsFetching(false); dispatch(showErrorDialog({ error: response })); } - ); + }); + return () => { + sub.unsubscribe(); + }; } }, [dispatch, selectedItem, site]); - const resource = useLogicResource( - useMemo(() => ({ contentTypes, debounceKeyword }), [contentTypes, debounceKeyword]), - { - shouldResolve: ({ contentTypes }) => Boolean(contentTypes), - shouldReject: () => null, - shouldRenew: (source, resource) => resource.complete, - resultSelector: ({ contentTypes, debounceKeyword }) => { - return contentTypes.filter((contentType) => - contentType.label.toLowerCase().includes(debounceKeyword.toLowerCase()) - ); - }, - errorSelector: () => null - } - ); - const onSearch$ = useSubject(); useEffect(() => { @@ -138,32 +133,32 @@ export function ChangeContentTypeDialogContainer(props: ChangeContentTypeDialogC - - }} - withEmptyStateProps={{ - emptyStateProps: { - classes: { - image: classes.emptyStateImg - }, - title: ( + {isFetching ? ( + + ) : filteredContentTypes ? ( + filteredContentTypes.length > 0 ? ( + + ) : ( + - ) - } - }} - > - - + } + classes={{ + image: classes.emptyStateImg + }} + /> + ) + ) : ( + <> + )} ; -} - interface CompareVersionsProps { - resource: Resource; + versions: ContentInstance[]; } const getLegacyDialogStyles = makeStyles()(() => ({ @@ -169,7 +160,7 @@ const getLegacyDialogStyles = makeStyles()(() => ({ })); export function CompareVersions(props: CompareVersionsProps) { - const { a, b } = props.resource.read(); + const [a, b] = props.versions; const { classes } = getLegacyDialogStyles(); const authoringUrl = useSelection((state) => state.env.authoringBase); return ( diff --git a/ui/app/src/components/CompareVersionsDialog/CompareVersionsDialogContainer.tsx b/ui/app/src/components/CompareVersionsDialog/CompareVersionsDialogContainer.tsx index ffdb9dd5bc..8fd9ee740c 100644 --- a/ui/app/src/components/CompareVersionsDialog/CompareVersionsDialogContainer.tsx +++ b/ui/app/src/components/CompareVersionsDialog/CompareVersionsDialogContainer.tsx @@ -15,15 +15,14 @@ */ import { CompareVersionsDialogContainerProps } from './utils'; -import React, { useMemo } from 'react'; -import { useLogicResource } from '../../hooks/useLogicResource'; -import { CompareVersionsBranch } from '../../models/Version'; -import { CompareVersions, CompareVersionsResource } from './CompareVersions'; -import { EntityState } from '../../models/EntityState'; -import ContentType from '../../models/ContentType'; +import React from 'react'; +import { CompareVersions } from './CompareVersions'; import DialogBody from '../DialogBody/DialogBody'; -import { SuspenseWithEmptyState } from '../Suspencified/Suspencified'; import { makeStyles } from 'tss-react/mui'; +import { ApiResponseErrorState } from '../ApiResponseErrorState'; +import { LoadingState } from '../LoadingState'; +import { EmptyState } from '../EmptyState'; +import { FormattedMessage } from 'react-intl'; const useStyles = makeStyles()(() => ({ dialogBody: { @@ -42,47 +41,23 @@ const useStyles = makeStyles()(() => ({ })); export function CompareVersionsDialogContainer(props: CompareVersionsDialogContainerProps) { - const { versionsBranch, contentTypesBranch } = props; + const { versionsBranch } = props; const { compareVersionsBranch } = versionsBranch; const { classes, cx } = useStyles(); - const compareVersionsData = useMemo( - () => ({ - compareVersionsBranch, - contentTypesBranch - }), - [compareVersionsBranch, contentTypesBranch] - ); - - const compareVersionsResource = useLogicResource< - CompareVersionsResource, - { compareVersionsBranch: CompareVersionsBranch; contentTypesBranch: EntityState } - >(compareVersionsData, { - shouldResolve: ({ compareVersionsBranch, contentTypesBranch }) => - compareVersionsBranch.compareVersions && - contentTypesBranch.byId && - !compareVersionsBranch.isFetching && - !contentTypesBranch.isFetching, - shouldReject: ({ compareVersionsBranch, contentTypesBranch }) => - Boolean(compareVersionsBranch.error || contentTypesBranch.error), - shouldRenew: ({ compareVersionsBranch, contentTypesBranch }, resource) => resource.complete, - resultSelector: ({ compareVersionsBranch, contentTypesBranch }) => ({ - a: compareVersionsBranch.compareVersions?.[0], - b: compareVersionsBranch.compareVersions?.[1], - contentTypes: contentTypesBranch.byId - }), - errorSelector: ({ compareVersionsBranch, contentTypesBranch }) => - compareVersionsBranch.error || contentTypesBranch.error - }); - return ( - <> - - - - - - + + {compareVersionsBranch && + (compareVersionsBranch.error ? ( + + ) : compareVersionsBranch.isFetching ? ( + + ) : compareVersionsBranch.compareVersions?.length > 0 ? ( + + ) : ( + } /> + ))} + ); } diff --git a/ui/app/src/components/ConflictedPathDiffDialog/ConflictedPathDiffDialog.tsx b/ui/app/src/components/ConflictedPathDiffDialog/ConflictedPathDiffDialog.tsx index 2b6c735cf1..25b7b9ea27 100644 --- a/ui/app/src/components/ConflictedPathDiffDialog/ConflictedPathDiffDialog.tsx +++ b/ui/app/src/components/ConflictedPathDiffDialog/ConflictedPathDiffDialog.tsx @@ -14,7 +14,7 @@ * along with this program. If not, see . */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import Dialog from '@mui/material/Dialog'; import DialogHeader from '../DialogHeader'; import DialogBody from '../DialogBody/DialogBody'; @@ -23,7 +23,6 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { diffConflictedFile } from '../../services/repositories'; import ApiResponse from '../../models/ApiResponse'; import { FileDiff } from '../../models/Repository'; -import { SuspenseWithEmptyState } from '../Suspencified'; import ConflictedPathDiffDialogUI from './ConflictedPathDiffDialogUI'; import SecondaryButton from '../SecondaryButton'; import ConfirmDropdown from '../ConfirmDropdown'; @@ -33,7 +32,8 @@ import { makeStyles } from 'tss-react/mui'; import Tab from '@mui/material/Tab'; import Tabs from '@mui/material/Tabs'; import { useActiveSiteId } from '../../hooks/useActiveSiteId'; -import { useLogicResource } from '../../hooks/useLogicResource'; +import { ApiResponseErrorState } from '../ApiResponseErrorState'; +import { LoadingState } from '../LoadingState'; export interface RemoteRepositoriesDiffDialogProps { open: boolean; @@ -101,17 +101,6 @@ export function ConflictedPathDiffDialog(props: RemoteRepositoriesDiffDialogProp setTab(newValue); }; - const resource = useLogicResource( - useMemo(() => ({ fileDiff, error, fetching }), [fileDiff, error, fetching]), - { - shouldResolve: (source) => Boolean(source.fileDiff) && !fetching, - shouldReject: ({ error }) => Boolean(error), - shouldRenew: (source, resource) => fetching && resource.complete, - resultSelector: (source) => source.fileDiff, - errorSelector: () => error - } - ); - return ( - - - + {error ? ( + + ) : fetching ? ( + + ) : fileDiff ? ( + + ) : null} diff --git a/ui/app/src/components/ConflictedPathDiffDialog/ConflictedPathDiffDialogUI.tsx b/ui/app/src/components/ConflictedPathDiffDialog/ConflictedPathDiffDialogUI.tsx index c45eaf87df..33f2e66162 100644 --- a/ui/app/src/components/ConflictedPathDiffDialog/ConflictedPathDiffDialogUI.tsx +++ b/ui/app/src/components/ConflictedPathDiffDialog/ConflictedPathDiffDialogUI.tsx @@ -14,7 +14,6 @@ * along with this program. If not, see . */ -import { Resource } from '../../models/Resource'; import { FileDiff } from '../../models/Repository'; import React from 'react'; import { makeStyles } from 'tss-react/mui'; @@ -54,13 +53,12 @@ const useStyles = makeStyles()((theme) => ({ })); export interface RemoteRepositoriesDiffDialogUIProps { - resource: Resource; + fileDiff: FileDiff; tab: number; } export function ConflictedPathDiffDialogUI(props: RemoteRepositoriesDiffDialogUIProps) { - const { resource, tab } = props; - const fileDiff = resource.read(); + const { fileDiff, tab } = props; const { classes } = useStyles(); return ( diff --git a/ui/app/src/components/DeleteContentTypeDialog/DeleteContentTypeDialogBody.tsx b/ui/app/src/components/DeleteContentTypeDialog/DeleteContentTypeDialogBody.tsx index 0a9682fce5..0b65fc8d28 100644 --- a/ui/app/src/components/DeleteContentTypeDialog/DeleteContentTypeDialogBody.tsx +++ b/ui/app/src/components/DeleteContentTypeDialog/DeleteContentTypeDialogBody.tsx @@ -81,8 +81,7 @@ const useStyles = makeStyles()((theme) => ({ export function DeleteContentTypeDialogBody(props: DeleteContentTypeDialogBodyProps) { const { classes } = useStyles(); - const { onCloseButtonClick, resource, contentType, onSubmit: onSubmitProp, password = 'delete', submitting } = props; - const data = resource.read(); + const { onCloseButtonClick, data, contentType, onSubmit: onSubmitProp, password = 'delete', submitting } = props; const { formatMessage } = useIntl(); const dataEntries = Object.entries(data) as Array<[keyof FetchContentTypeUsageResponse, SandboxItem[]]>; const entriesWithItems = dataEntries.filter(([, items]) => items.length > 0); diff --git a/ui/app/src/components/DeleteContentTypeDialog/DeleteContentTypeDialogContainer.tsx b/ui/app/src/components/DeleteContentTypeDialog/DeleteContentTypeDialogContainer.tsx index 27a42ffe67..59bca89c4e 100644 --- a/ui/app/src/components/DeleteContentTypeDialog/DeleteContentTypeDialogContainer.tsx +++ b/ui/app/src/components/DeleteContentTypeDialog/DeleteContentTypeDialogContainer.tsx @@ -19,13 +19,14 @@ import { useActiveSiteId } from '../../hooks/useActiveSiteId'; import { defineMessages, useIntl } from 'react-intl'; import { useDispatch } from 'react-redux'; import * as React from 'react'; -import { useMemo } from 'react'; -import { createResource } from '../../utils/resource'; +import { useEffect } from 'react'; import { deleteContentType, fetchContentTypeUsage } from '../../services/contentTypes'; import { showSystemNotification } from '../../state/actions/system'; -import Suspencified from '../Suspencified/Suspencified'; import DeleteContentTypeDialogBody from './DeleteContentTypeDialogBody'; import useUpdateRefs from '../../hooks/useUpdateRefs'; +import useSpreadState from '../../hooks/useSpreadState'; +import ApiResponseErrorState from '../ApiResponseErrorState'; +import LoadingState from '../LoadingState'; const messages = defineMessages({ deleteComplete: { @@ -46,11 +47,34 @@ export function DeleteContentTypeDialogContainer(props: DeleteContentTypeDialogC const functionRefs = useUpdateRefs({ onSubmittingAndOrPendingChange }); + const [{ data, isFetching, error }, setState] = useSpreadState({ + data: null, + isFetching: false, + error: null + }); + + useEffect(() => { + setState({ isFetching: true }); + const sub = fetchContentTypeUsage(site, contentType.id).subscribe({ + next(response) { + setState({ + data: response, + isFetching: false, + error: null + }); + }, + error(e) { + setState({ + error: e, + isFetching: false + }); + } + }); + return () => { + sub.unsubscribe(); + }; + }, [site, contentType.id, setState]); - const resource = useMemo( - () => createResource(() => fetchContentTypeUsage(site, contentType.id).toPromise()), - [site, contentType.id] - ); const onSubmit = () => { functionRefs.current.onSubmittingAndOrPendingChange({ isSubmitting: true @@ -80,15 +104,17 @@ export function DeleteContentTypeDialogContainer(props: DeleteContentTypeDialogC const onCloseButtonClick = (e: React.MouseEvent) => onClose(e, null); - return ( - - - - ); + return error ? ( + + ) : isFetching ? ( + + ) : data ? ( + + ) : null; } diff --git a/ui/app/src/components/DeleteContentTypeDialog/utils.ts b/ui/app/src/components/DeleteContentTypeDialog/utils.ts index cca2cc41a1..c7b027cb3e 100644 --- a/ui/app/src/components/DeleteContentTypeDialog/utils.ts +++ b/ui/app/src/components/DeleteContentTypeDialog/utils.ts @@ -14,7 +14,6 @@ * along with this program. If not, see . */ import ContentType from '../../models/ContentType'; -import { Resource } from '../../models/Resource'; import { FetchContentTypeUsageResponse } from '../../services/contentTypes'; import { EnhancedDialogProps } from '../EnhancedDialog'; import React from 'react'; @@ -36,7 +35,7 @@ export interface DeleteContentTypeDialogContainerProps export interface DeleteContentTypeDialogBodyProps { submitting: boolean; contentType: ContentType; - resource: Resource; + data: FetchContentTypeUsageResponse; password?: string; onCloseButtonClick?(e: React.MouseEvent): void; onSubmit(): void; diff --git a/ui/app/src/components/DeletePluginDialog/UninstallPluginDialogBody.tsx b/ui/app/src/components/DeletePluginDialog/UninstallPluginDialogBody.tsx index 778070ff3f..f7f2dc1dc6 100644 --- a/ui/app/src/components/DeletePluginDialog/UninstallPluginDialogBody.tsx +++ b/ui/app/src/components/DeletePluginDialog/UninstallPluginDialogBody.tsx @@ -60,15 +60,7 @@ function getStyles(sx: UninstallPluginDialogBodyPartialSx): UninstallPluginDialo } export function UninstallPluginDialogBody(props: UninstallPluginDialogBodyProps) { - const { - onCloseButtonClick, - resource, - pluginId, - onSubmit: onSubmitProp, - password = 'uninstall', - isSubmitting - } = props; - const data = resource.read(); + const { onCloseButtonClick, data, pluginId, onSubmit: onSubmitProp, password = 'uninstall', isSubmitting } = props; const hasUsages = data.length > 0; const [confirmPasswordPassed, setConfirmPasswordPassed] = useState(false); const [passwordFieldValue, setPasswordFieldValue] = useState(''); diff --git a/ui/app/src/components/DeletePluginDialog/UninstallPluginDialogContainer.tsx b/ui/app/src/components/DeletePluginDialog/UninstallPluginDialogContainer.tsx index 561ee5d476..285c6a9a31 100644 --- a/ui/app/src/components/DeletePluginDialog/UninstallPluginDialogContainer.tsx +++ b/ui/app/src/components/DeletePluginDialog/UninstallPluginDialogContainer.tsx @@ -15,26 +15,49 @@ */ import * as React from 'react'; +import { Suspense, useEffect } from 'react'; import { UninstallPluginDialogContainerProps } from './utils'; import { useActiveSiteId } from '../../hooks/useActiveSiteId'; -import { useMemo } from 'react'; -import { createResource } from '../../utils/resource'; -import { uninstallMarketplacePlugin, fetchMarketplacePluginUsage } from '../../services/marketplace'; -import Suspencified from '../Suspencified/Suspencified'; +import { fetchMarketplacePluginUsage, uninstallMarketplacePlugin } from '../../services/marketplace'; import { UninstallPluginDialogBody } from './UninstallPluginDialogBody'; import { useDispatch } from 'react-redux'; import { showErrorDialog } from '../../state/reducers/dialogs/error'; import useUpdateRefs from '../../hooks/useUpdateRefs'; +import useSpreadState from '../../hooks/useSpreadState'; +import { ApiResponseErrorState } from '../ApiResponseErrorState'; +import { LoadingState } from '../LoadingState'; export function UninstallPluginDialogContainer(props: UninstallPluginDialogContainerProps) { const { onClose, pluginId, onComplete, isSubmitting, onSubmittingAndOrPendingChange } = props; const site = useActiveSiteId(); const dispatch = useDispatch(); const callbacksRef = useUpdateRefs({ onSubmittingAndOrPendingChange }); + const [{ data, isFetching, error }, setState] = useSpreadState({ + data: null, + isFetching: false, + error: null + }); - const resource = useMemo(() => { - return createResource(() => fetchMarketplacePluginUsage(site, pluginId).toPromise()); - }, [site, pluginId]); + useEffect(() => { + setState({ isFetching: true }); + const sub = fetchMarketplacePluginUsage(site, pluginId).subscribe({ + next(response) { + setState({ + data: response, + isFetching: false + }); + }, + error: ({ response: { response } }) => { + setState({ + error: response, + isFetching: false + }); + } + }); + return () => { + sub.unsubscribe(); + }; + }, [site, pluginId, setState]); const onSubmit = (id: string) => { onSubmittingAndOrPendingChange({ @@ -59,15 +82,19 @@ export function UninstallPluginDialogContainer(props: UninstallPluginDialogConta const onCloseButtonClick = (e: React.MouseEvent) => onClose(e, null); - return ( - + return error ? ( + + ) : isFetching ? ( + + ) : data ? ( + onSubmit(pluginId)} /> - - ); + + ) : null; } diff --git a/ui/app/src/components/DeletePluginDialog/utils.ts b/ui/app/src/components/DeletePluginDialog/utils.ts index 2f66a6a4ac..00be193c5c 100644 --- a/ui/app/src/components/DeletePluginDialog/utils.ts +++ b/ui/app/src/components/DeletePluginDialog/utils.ts @@ -16,7 +16,6 @@ import { EnhancedDialogProps } from '../EnhancedDialog'; import { onSubmittingAndOrPendingChangeProps } from '../../hooks/useEnhancedDialogState'; -import { Resource } from '../../models/Resource'; import { SandboxItem } from '../../models/Item'; import { FullSxRecord, PartialSxRecord } from '../../models/CustomRecord'; @@ -36,7 +35,7 @@ export interface UninstallPluginDialogContainerProps export interface UninstallPluginDialogBodyProps { isSubmitting: boolean; pluginId: string; - resource: Resource; + data: SandboxItem[]; password?: string; sx?: UninstallPluginDialogBodyPartialSx; onCloseButtonClick?(e: React.MouseEvent): void; diff --git a/ui/app/src/components/NewContentDialog/NewContentDialog.tsx b/ui/app/src/components/NewContentDialog/NewContentDialog.tsx index c4cbf8f465..256a5c3ba8 100644 --- a/ui/app/src/components/NewContentDialog/NewContentDialog.tsx +++ b/ui/app/src/components/NewContentDialog/NewContentDialog.tsx @@ -63,12 +63,11 @@ export function ContentTypesLoader(props: { numOfItems?: number; isCompact: bool } export function ContentTypesGrid(props: ContentTypesGridProps) { - const { resource, isCompact, onTypeOpen, selectedContentType } = props; + const { contentTypes, isCompact, onTypeOpen, selectedContentType } = props; const { classes } = useStyles(); - const filterContentTypes = resource.read(); return ( - {filterContentTypes.map((content) => ( + {contentTypes.map((content) => ( (); + const [isFetching, setIsFetching] = useState(false); const [keyword, setKeyword] = useState(''); const [debounceKeyword, setDebounceKeyword] = useState(''); const [selectedFilter, setSelectedFilter] = useState('all'); + const filteredContentTypes = useMemo(() => { + const lowercaseKeyword = debounceKeyword.toLowerCase(); + return contentTypes?.filter( + (contentType) => + contentType.label.toLowerCase().includes(lowercaseKeyword) && + (selectedFilter === 'all' || contentType.type === selectedFilter) + ); + }, [contentTypes, debounceKeyword, selectedFilter]); const filters = [ { @@ -93,8 +101,10 @@ export function NewContentDialogContainer(props: NewContentDialogContainerProps) ? `${selectedItem.path}/` : selectedItem.path; - fetchLegacyContentTypes(site, path).subscribe({ + setIsFetching(true); + const sub = fetchLegacyContentTypes(site, path).subscribe({ next(response) { + setIsFetching(false); if (response.length === 1) { dispatch(closeNewContentDialog()); onSelectedContentType(response[0]); @@ -103,29 +113,16 @@ export function NewContentDialogContainer(props: NewContentDialogContainerProps) } }, error(response) { + setIsFetching(false); dispatch(showErrorDialog({ error: response })); } }); + return () => { + sub.unsubscribe(); + }; } }, [dispatch, selectedItem, site, onSelectedContentType]); - const resource = useLogicResource( - useMemo(() => ({ contentTypes, selectedFilter, debounceKeyword }), [contentTypes, selectedFilter, debounceKeyword]), - { - shouldResolve: ({ contentTypes }) => Boolean(contentTypes), - shouldReject: () => null, - shouldRenew: (source, resource) => resource.complete, - resultSelector: ({ contentTypes, debounceKeyword, selectedFilter }) => { - return contentTypes.filter( - (contentType) => - contentType.label.toLowerCase().includes(debounceKeyword.toLowerCase()) && - (selectedFilter === 'all' || contentType.type === selectedFilter) - ); - }, - errorSelector: () => null - } - ); - const onSearch$ = useSubject(); useEffect(() => { @@ -162,24 +159,28 @@ export function NewContentDialogContainer(props: NewContentDialogContainerProps) - - }} - withEmptyStateProps={{ - emptyStateProps: { - classes: { - image: classes.emptyStateImg - }, - title: ( + {isFetching ? ( + + ) : filteredContentTypes ? ( + filteredContentTypes.length > 0 ? ( + + ) : ( + - ) - } - }} - > - - + } + classes={{ + image: classes.emptyStateImg + }} + /> + ) + ) : ( + <> + )} . */ -import { Resource } from '../../models/Resource'; import { LegacyContentType } from '../../models/ContentType'; import { DetailedItem } from '../../models/Item'; import StandardAction from '../../models/StandardAction'; @@ -22,7 +21,7 @@ import { EnhancedDialogProps } from '../EnhancedDialog'; import { EnhancedDialogState } from '../../hooks/useEnhancedDialogState'; export interface ContentTypesGridProps { - resource: Resource; + contentTypes: LegacyContentType[]; isCompact: boolean; selectedContentType?: string; onTypeOpen(data: LegacyContentType): void; diff --git a/ui/app/src/components/PreviewBrowseComponentsPanel/PreviewBrowseComponentsPanelUI.tsx b/ui/app/src/components/PreviewBrowseComponentsPanel/PreviewBrowseComponentsPanelUI.tsx index 312e12d4c2..45c016b31b 100644 --- a/ui/app/src/components/PreviewBrowseComponentsPanel/PreviewBrowseComponentsPanelUI.tsx +++ b/ui/app/src/components/PreviewBrowseComponentsPanel/PreviewBrowseComponentsPanelUI.tsx @@ -27,14 +27,6 @@ import Pagination from '../Pagination'; import HourglassEmptyRounded from '@mui/icons-material/HourglassEmptyRounded'; import Alert from '@mui/material/Alert'; -export interface ComponentResource { - count: number; - limit: number; - pageNumber: number; - contentTypeFilter: string; - items: Array; -} - export interface PreviewBrowseComponentsPanelUIProps { awaitingGuestCheckIn: boolean; items: Array; diff --git a/ui/app/src/components/PreviewDropTargetsPanel/PreviewDropTargetsPanel.tsx b/ui/app/src/components/PreviewDropTargetsPanel/PreviewDropTargetsPanel.tsx index 210cd22d87..9ab123934a 100644 --- a/ui/app/src/components/PreviewDropTargetsPanel/PreviewDropTargetsPanel.tsx +++ b/ui/app/src/components/PreviewDropTargetsPanel/PreviewDropTargetsPanel.tsx @@ -32,11 +32,7 @@ import { scrollToDropTarget, setPreviewEditMode } from '../../state/actions/preview'; -import { Resource } from '../../models/Resource'; -import { SuspenseWithEmptyState } from '../Suspencified/Suspencified'; -import { LookupTable } from '../../models/LookupTable'; import { useSelection } from '../../hooks/useSelection'; -import { useLogicResource } from '../../hooks/useLogicResource'; import { useMount } from '../../hooks/useMount'; import { getAvatarWithIconColors } from '../../utils/contentType'; import { darken, useTheme } from '@mui/material/styles'; @@ -55,6 +51,7 @@ import HourglassEmptyRounded from '@mui/icons-material/HourglassEmptyRounded'; import Alert from '@mui/material/Alert'; import { EmptyState } from '../EmptyState'; import FormHelperText from '@mui/material/FormHelperText'; +import { LoadingState } from '../LoadingState'; const translations = defineMessages({ dropTargetsPanel: { @@ -108,6 +105,15 @@ export function PreviewDropTargetsPanel() { }); return allowedTypes; }, [allowedTypesData, contentTypes]); + const filteredDropTargets = useMemo(() => { + return dropTargetsBranch + ? dropTargetsBranch.byId + ? Object.values(dropTargetsBranch.byId).filter( + (dropTarget) => dropTarget.contentTypeId === dropTargetsBranch.selectedContentType + ) + : [] + : null; + }, [dropTargetsBranch]); useMount(() => { return () => { @@ -138,22 +144,6 @@ export function PreviewDropTargetsPanel() { hostToGuest$.next(clearHighlightedDropTargets()); }; - const dropTargetsResource = useLogicResource< - ContentTypeDropTarget[], - { selectedContentType: string; byId: LookupTable } - >(dropTargetsBranch, { - shouldResolve: (source) => source.selectedContentType === null || Boolean(source.byId), - shouldReject: (source) => false, - shouldRenew: (source, resource) => resource.complete, - resultSelector: (source) => - source.byId - ? Object.values(source.byId).filter( - (dropTarget) => dropTarget.contentTypeId === dropTargetsBranch.selectedContentType - ) - : [], - errorSelector: (source) => null - }); - return awaitingGuestCheckIn ? ( } sx={{ border: 0 }}> @@ -225,18 +215,23 @@ export function PreviewDropTargetsPanel() { )} - - - + {dropTargetsBranch?.selectedContentType !== null && !Boolean(dropTargetsBranch?.byId) ? ( + + ) : filteredDropTargets ? ( + filteredDropTargets.length > 0 ? ( + + ) : ( + + ) + ) : ( + <> + )} ) @@ -281,12 +276,12 @@ function ContentTypeItem(props: ContentTypeItemContentProps) { } interface DropTargetsListProps { - resource: Resource; + dropTargets: ContentTypeDropTarget[]; onSelectedDropZone(dropTarget: ContentTypeDropTarget): void; } function DropTargetsList(props: DropTargetsListProps) { - const dropTargets = props.resource.read(); + const { dropTargets } = props; return dropTargets?.map((dropTarget: ContentTypeDropTarget) => ( props.onSelectedDropZone(dropTarget)}> diff --git a/ui/app/src/components/PreviewInPageInstancesPanel/PreviewInPageInstancesPanel.tsx b/ui/app/src/components/PreviewInPageInstancesPanel/PreviewInPageInstancesPanel.tsx index b4e0735512..5a4a3b435a 100644 --- a/ui/app/src/components/PreviewInPageInstancesPanel/PreviewInPageInstancesPanel.tsx +++ b/ui/app/src/components/PreviewInPageInstancesPanel/PreviewInPageInstancesPanel.tsx @@ -32,11 +32,10 @@ import ListItemText from '@mui/material/ListItemText'; import CircularProgress from '@mui/material/CircularProgress'; import { getHostToGuestBus } from '../../utils/subjects'; import EmptyState from '../EmptyState/EmptyState'; -import { Resource } from '../../models/Resource'; import { useSelection } from '../../hooks/useSelection'; import { usePreviewGuest } from '../../hooks/usePreviewGuest'; import { useContentTypes } from '../../hooks/useContentTypes'; -import { useLogicResource } from '../../hooks/useLogicResource'; +import { LoadingState } from '../LoadingState'; import ListItemButton from '@mui/material/ListItemButton'; const translations = defineMessages({ @@ -95,6 +94,9 @@ export function PreviewInPageInstancesPanel() { const models = useMemo(() => { return guest?.models; }, [guest]); + const filteredContentTypes = Object.values(models ?? {}).filter( + (model) => model.craftercms.contentTypeId === contentTypeFilter + ); const selectedModels = useMemo(() => { return Object.values(models ?? []).filter((model) => { @@ -127,25 +129,6 @@ export function PreviewInPageInstancesPanel() { }; }, [models]); - const resource = useLogicResource< - ContentInstance[], - { models: LookupTable; contentTypeFilter: string } - >( - { - models, - contentTypeFilter - }, - { - shouldRenew: (source, resource) => Boolean(contentTypeFilter) && !keyword && resource.complete, - shouldResolve: (source) => Boolean(source.models), - shouldReject: (source) => false, - errorSelector: (source) => null, - resultSelector: (source) => { - return Object.values(source.models)?.filter((model) => model.craftercms.contentTypeId === contentTypeFilter); - } - } - ); - const handleSearchKeyword = (keyword) => { setKeyword(keyword); }; @@ -205,27 +188,28 @@ export function PreviewInPageInstancesPanel() { - + {filteredContentTypes ? ( + + ) : ( + + )} ); } interface InPageInstancesUIProps { - resource: Resource; selectedModels: ContentInstance[]; contentTypeFilter: string; onItemClick(instance: ContentInstance): void; } function InPageInstancesUI(props: InPageInstancesUIProps) { - const { resource, selectedModels, onItemClick, contentTypeFilter } = props; - resource.read(); + const { selectedModels, onItemClick, contentTypeFilter } = props; const { classes } = useStyles(); const { formatMessage } = useIntl(); diff --git a/ui/app/src/components/PreviewPageExplorerPanel/PreviewPageExplorerPanel.tsx b/ui/app/src/components/PreviewPageExplorerPanel/PreviewPageExplorerPanel.tsx index 83d8d8865a..1db1e317c3 100644 --- a/ui/app/src/components/PreviewPageExplorerPanel/PreviewPageExplorerPanel.tsx +++ b/ui/app/src/components/PreviewPageExplorerPanel/PreviewPageExplorerPanel.tsx @@ -42,8 +42,6 @@ import { sortItemOperationComplete } from '../../state/actions/preview'; import { getHostToGuestBus, getHostToHostBus } from '../../utils/subjects'; -import Suspencified from '../Suspencified/Suspencified'; -import { Resource } from '../../models/Resource'; import palette from '../../styles/palette'; import { useDispatch } from 'react-redux'; import Typography from '@mui/material/Typography'; @@ -56,10 +54,10 @@ import { showItemMegaMenu } from '../../state/actions/dialogs'; import { useSelection } from '../../hooks/useSelection'; import { useActiveSiteId } from '../../hooks/useActiveSiteId'; import { usePreviewGuest } from '../../hooks/usePreviewGuest'; -import { useLogicResource } from '../../hooks/useLogicResource'; import { useUnmount } from '../../hooks/useUnmount'; import { useSpreadState } from '../../hooks/useSpreadState'; import { SimpleTreeView } from '@mui/x-tree-view'; +import { LoadingState } from '../LoadingState'; const rootPrefix = '{root}_'; @@ -716,17 +714,6 @@ export function PreviewPageExplorerPanel() { setKeyword(keyword); }; - const resource = useLogicResource( - { models, byId: ContentTypesById }, - { - shouldResolve: (source) => Boolean(source.models && source.byId), - shouldReject: () => false, - shouldRenew: () => !Object.keys(processedModels.current).length && resource.complete, - resultSelector: () => true, - errorSelector: null - } - ); - useUnmount(onBack); return ( @@ -753,7 +740,7 @@ export function PreviewPageExplorerPanel() { - + {models && ContentTypesById ? ( - + ) : ( + + )} ); } interface PageExplorerUIProps { - resource: Resource; optionsMenu: { modelId: string; anchorEl: Element; @@ -797,7 +784,6 @@ interface PageExplorerUIProps { function PageExplorerUI(props: PageExplorerUIProps) { const { - resource, handleScroll, handleClick, handleOptions, @@ -814,8 +800,6 @@ function PageExplorerUI(props: PageExplorerUIProps) { const { classes } = useStyles(); const { formatMessage } = useIntl(); - resource.read(); - let node: any = null; if (selected === 'root') { diff --git a/ui/app/src/components/QuickCreate/QuickCreate.tsx b/ui/app/src/components/QuickCreate/QuickCreate.tsx index b274cf5f58..1e715a62c4 100644 --- a/ui/app/src/components/QuickCreate/QuickCreate.tsx +++ b/ui/app/src/components/QuickCreate/QuickCreate.tsx @@ -14,7 +14,7 @@ * along with this program. If not, see . */ -import React, { forwardRef, useState } from 'react'; +import React, { forwardRef, useEffect, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import IconButton from '@mui/material/IconButton'; import AddCircleIcon from '@mui/icons-material/AddRounded'; @@ -30,19 +30,20 @@ import CardContent from '@mui/material/CardContent'; import Button from '@mui/material/Button'; import ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined'; import QuickCreateItem from '../../models/content/QuickCreateItem'; -import { Resource } from '../../models/Resource'; -import Suspencified from '../Suspencified/Suspencified'; -import { getSimplifiedVersion } from '../../utils/string'; import palette from '../../styles/palette'; import Tooltip from '@mui/material/Tooltip'; import { DetailedItem } from '../../models/Item'; import { useSelection } from '../../hooks/useSelection'; import { usePreviewState } from '../../hooks/usePreviewState'; -import { useQuickCreateListResource } from '../../hooks/useQuickCreateListResource'; -import { useSystemVersionResource } from '../../hooks/useSystemVersionResource'; import { useItemsByPath } from '../../hooks/useItemsByPath'; import { lookupItemByPath } from '../../utils/content'; import { processPathMacros } from '../../utils/path'; +import { fetchQuickCreateList } from '../../state/actions/content'; +import useQuickCreateState from '../../hooks/useQuickCreateState'; +import useActiveSiteId from '../../hooks/useActiveSiteId'; +import useSystemVersion from '../../hooks/useSystemVersion'; +import { ApiResponseErrorState } from '../ApiResponseErrorState'; +import { LoadingState } from '../LoadingState'; const translations = defineMessages({ quickCreateBtnLabel: { @@ -104,10 +105,6 @@ interface QuickCreateMenuProps { open: boolean; item?: DetailedItem; anchorEl: HTMLElement; - resource: { - version: Resource; - quickCreate: Resource; - }; onNewContentSelected?(): void; onQuickCreateItemSelected?(props: { authoringBase: string; @@ -126,17 +123,17 @@ interface QuickCreateMenuButtonProps { interface QuickCreateSectionProps { classes: { [className: string]: string }; onItemSelected: (item: QuickCreateItem) => any; - resource: { - version: Resource; - quickCreate: Resource; - }; + version: string; + quickCreateItems: QuickCreateItem[]; } export function QuickCreateMenu(props: QuickCreateMenuProps) { - const { open, onClose, anchorEl, resource, onNewContentSelected, onQuickCreateItemSelected, item } = props; + const { open, onClose, anchorEl, onNewContentSelected, onQuickCreateItemSelected, item } = props; const { classes } = useStyles(); const authoringBase = useSelection((state) => state.env.authoringBase); const itemNewContentButton = item?.availableActionsMap.createContent; + const { error, isFetching, items: quickCreateItems } = useQuickCreateState(); + const systemVersion = useSystemVersion(); const onFormDisplay = (item: QuickCreateItem) => { const { contentTypeId, path } = item; @@ -171,19 +168,25 @@ export function QuickCreateMenu(props: QuickCreateMenuProps) { - - - + {error ? ( + + ) : isFetching ? ( + + ) : quickCreateItems && systemVersion ? ( + + ) : null} ); } function QuickCreateSection(props: QuickCreateSectionProps) { - const { resource, classes, onItemSelected } = props; - const quickCreateItems = resource.quickCreate.read(); - - let version = getSimplifiedVersion(resource.version.read()); + const { version, quickCreateItems, classes, onItemSelected } = props; return ( <> @@ -249,6 +252,11 @@ const QuickCreate = forwardRef((prop const { guest } = usePreviewState(); const dispatch = useDispatch(); const items = useItemsByPath(); + const site = useActiveSiteId(); + + useEffect(() => { + site && dispatch(fetchQuickCreateList()); + }, [site, dispatch]); const onMenuBtnClick = (e) => { setAnchorEl(e.currentTarget); @@ -285,10 +293,6 @@ const QuickCreate = forwardRef((prop ); }; - const quickCreateResource = useQuickCreateListResource(); - - const versionResource = useSystemVersionResource(); - return ( <> @@ -297,7 +301,6 @@ const QuickCreate = forwardRef((prop open={Boolean(anchorEl)} anchorEl={anchorEl} onClose={onMenuClose} - resource={{ quickCreate: quickCreateResource, version: versionResource }} onNewContentSelected={onNewContentSelected} onQuickCreateItemSelected={onQuickCreateItemSelected} /> diff --git a/ui/app/src/components/RejectDialog/utils.ts b/ui/app/src/components/RejectDialog/utils.ts index a65e4b2e91..c3a4b774c1 100644 --- a/ui/app/src/components/RejectDialog/utils.ts +++ b/ui/app/src/components/RejectDialog/utils.ts @@ -14,7 +14,6 @@ * along with this program. If not, see . */ -import { Resource } from '../../models/Resource'; import StandardAction from '../../models/StandardAction'; import { SandboxItem } from '../../models/Item'; import { ApiResponse } from '../../models/ApiResponse'; diff --git a/ui/app/src/components/SiteManagement/SiteManagement.tsx b/ui/app/src/components/SiteManagement/SiteManagement.tsx index 6f27d9ac71..3a9d26477f 100644 --- a/ui/app/src/components/SiteManagement/SiteManagement.tsx +++ b/ui/app/src/components/SiteManagement/SiteManagement.tsx @@ -14,7 +14,7 @@ * along with this program. If not, see . */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import AddIcon from '@mui/icons-material/Add'; import SkeletonSitesGrid from '../SitesGrid/SitesGridSkeleton'; @@ -35,7 +35,6 @@ import { fetchSites, popSite } from '../../state/actions/sites'; import { showErrorDialog } from '../../state/reducers/dialogs/error'; import { showEditSiteDialog } from '../../state/actions/dialogs'; import { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary'; -import { SuspenseWithEmptyState } from '../Suspencified/Suspencified'; import SitesGrid from '../SitesGrid/SitesGrid'; import PublishingStatusDialog from '../PublishingStatusDialog'; import GlobalAppToolbar from '../GlobalAppToolbar'; @@ -45,7 +44,6 @@ import { hasGlobalPermissions } from '../../services/users'; import { foo } from '../../utils/object'; import { useEnv } from '../../hooks/useEnv'; import { useActiveUser } from '../../hooks/useActiveUser'; -import { useLogicResource } from '../../hooks/useLogicResource'; import { useSpreadState } from '../../hooks/useSpreadState'; import { useSitesBranch } from '../../hooks/useSitesBranch'; import Paper from '@mui/material/Paper'; @@ -61,6 +59,7 @@ import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; import { ConfirmDialog } from '../ConfirmDialog'; import { previewSwitch } from '../../services/security'; +import { EmptyState } from '../EmptyState'; const translations = defineMessages({ siteDeleted: { @@ -85,6 +84,7 @@ export function SiteManagement() { getStoredGlobalMenuSiteViewPreference(user.username) ?? 'grid' ); const { byId: sitesById, isFetching, active } = useSitesBranch(); + const sitesList = sitesById ? Object.values(sitesById) : null; const [selectedSiteStatus, setSelectedSiteStatus] = useState(null); const [permissionsLookup, setPermissionsLookup] = useState>(foo); const duplicateSiteDialogState = useEnhancedDialogState(); @@ -100,17 +100,6 @@ export function SiteManagement() { return () => subscription.unsubscribe(); }, []); - const resource = useLogicResource; isFetching: boolean }>( - useMemo(() => ({ sitesById, isFetching, permissionsLookup }), [sitesById, isFetching, permissionsLookup]), - { - shouldResolve: (source) => Boolean(source.sitesById) && permissionsLookup !== foo && !isFetching, - shouldReject: () => false, - shouldRenew: (source, resource) => resource.complete, - resultSelector: () => Object.values(sitesById), - errorSelector: () => null - } - ); - const onSiteClick = (site: Site) => { setSiteCookie(site.id, useBaseDomain); previewSwitch().subscribe(() => { @@ -224,16 +213,25 @@ export function SiteManagement() { } /> - - }} - withEmptyStateProps={{ - emptyStateProps: { - title: , - styles: { root: { margin: undefined } }, - sxs: { + {isFetching ? ( + + ) : sitesList ? ( + sitesList.length > 0 ? ( + + ) : ( + } + styles={{ root: { margin: undefined } }} + sxs={{ root: { p: 5, bgcolor: 'background.default', @@ -243,40 +241,30 @@ export function SiteManagement() { marginLeft: 'auto', marginRight: 'auto' } - }, - children: ( - - {hasCreateSitePermission ? ( - {cardHeaderBlock} - ) : ( - cardHeaderBlock - )} - - ) - } - }} - > - - + }} + > + + {hasCreateSitePermission ? ( + {cardHeaderBlock} + ) : ( + cardHeaderBlock + )} + + + ) + ) : ( + <> + )} ; + sites: Site[]; onSiteClick(site: Site): void; onDeleteSiteClick(site: Site): void; onEditSiteClick(site: Site): void; @@ -40,7 +39,7 @@ interface SitesGridProps { export function SitesGrid(props: SitesGridProps) { const { - resource, + sites, onSiteClick, onDeleteSiteClick, onEditSiteClick, @@ -49,7 +48,6 @@ export function SitesGrid(props: SitesGridProps) { onPublishButtonClick, disabledSitesLookup } = props; - const sites = resource.read(); const { classes } = useSitesGridStyles(); return (
diff --git a/ui/app/src/components/StoreProvider/StoreProvider.tsx b/ui/app/src/components/StoreProvider/StoreProvider.tsx index b7a09f58f8..2b50937308 100644 --- a/ui/app/src/components/StoreProvider/StoreProvider.tsx +++ b/ui/app/src/components/StoreProvider/StoreProvider.tsx @@ -15,20 +15,13 @@ */ import React, { PropsWithChildren } from 'react'; -import { Resource } from '../../models/Resource'; import { CrafterCMSStore } from '../../state/store'; import { Provider } from 'react-redux'; -type StoreProviderPropsA = PropsWithChildren<{ resource: Resource }>; -type StoreProviderPropsB = PropsWithChildren<{ store: CrafterCMSStore }>; -export type StoreProviderProps = StoreProviderPropsA | StoreProviderPropsB; +export type StoreProviderProps = PropsWithChildren<{ store: CrafterCMSStore }>; -export function StoreProvider(props: StoreProviderPropsA): JSX.Element; -export function StoreProvider(props: StoreProviderPropsB): JSX.Element; -export function StoreProvider( - props: PropsWithChildren<{ store?: CrafterCMSStore; resource?: Resource }> -) { - const store = props.store ?? props.resource.read(); +export function StoreProvider(props: StoreProviderProps): JSX.Element { + const { store } = props; return ; } diff --git a/ui/app/src/components/Suspencified/Suspencified.tsx b/ui/app/src/components/Suspencified/Suspencified.tsx index 1502ec66aa..14cd21afba 100644 --- a/ui/app/src/components/Suspencified/Suspencified.tsx +++ b/ui/app/src/components/Suspencified/Suspencified.tsx @@ -14,25 +14,9 @@ * along with this program. If not, see . */ -import React, { Fragment, PropsWithChildren, Suspense, SuspenseProps } from 'react'; +import React, { PropsWithChildren, Suspense, SuspenseProps } from 'react'; import { ErrorBoundary, ErrorBoundaryProps } from '../ErrorBoundary/ErrorBoundary'; import LoadingState, { LoadingStateProps } from '../LoadingState/LoadingState'; -import { Resource } from '../../models/Resource'; -import EmptyState, { EmptyStateProps } from '../EmptyState/EmptyState'; -import { FormattedMessage } from 'react-intl'; - -export type PropsWithResource = PropsWithChildren< - { - resource: Resource; - } & Props ->; - -type SuspenseWithEmptyStateProps = PropsWithChildren< - PropsWithResource & { - isEmpty?(value: ResourceType): boolean; - emptyStateProps?: Partial; - } ->; type SuspencifiedProps = PropsWithChildren<{ suspenseProps?: SuspenseProps; @@ -40,16 +24,6 @@ type SuspencifiedProps = PropsWithChildren<{ errorBoundaryProps?: ErrorBoundaryProps; }>; -export function WithEmptyState(props: SuspenseWithEmptyStateProps) { - const { children, isEmpty = (value: ResourceType) => (value as any).length === 0, resource, emptyStateProps } = props; - const value = resource.read(); - const finalEmptyStateProps: EmptyStateProps = { - title: , - ...emptyStateProps - }; - return {isEmpty(value) ? : children}; -} - export function Suspencified(props: SuspencifiedProps) { const { children, loadingStateProps, errorBoundaryProps, suspenseProps } = props; return ( @@ -59,20 +33,4 @@ export function Suspencified(props: SuspencifiedProps) { ); } -export function SuspenseWithEmptyState( - props: SuspencifiedProps & { - resource: Resource; - withEmptyStateProps?: Partial>; - } -) { - const { children, withEmptyStateProps, resource } = props; - return ( - - - {children} - - - ); -} - export default Suspencified; diff --git a/ui/app/src/components/ViewVersionDialog/LegacyVersionDialog.tsx b/ui/app/src/components/ViewVersionDialog/LegacyVersionDialog.tsx index be2f827c3b..387b9be493 100644 --- a/ui/app/src/components/ViewVersionDialog/LegacyVersionDialog.tsx +++ b/ui/app/src/components/ViewVersionDialog/LegacyVersionDialog.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { getLegacyDialogStyles } from './ViewVersionDialog'; export function LegacyVersionDialog(props: VersionViewProps) { - const { version } = props.resource.read(); + const { version } = props; const { classes } = getLegacyDialogStyles(); const authoringUrl = useSelection((state) => state.env.authoringBase); return ( diff --git a/ui/app/src/components/ViewVersionDialog/ViewVersionDialogContainer.tsx b/ui/app/src/components/ViewVersionDialog/ViewVersionDialogContainer.tsx index ec5b4304bd..5aca568b85 100644 --- a/ui/app/src/components/ViewVersionDialog/ViewVersionDialogContainer.tsx +++ b/ui/app/src/components/ViewVersionDialog/ViewVersionDialogContainer.tsx @@ -14,31 +14,25 @@ * along with this program. If not, see . */ -import { VersionResource, ViewVersionDialogContainerProps } from './utils'; -import { useLogicResource } from '../../hooks/useLogicResource'; +import { ViewVersionDialogContainerProps } from './utils'; import DialogBody from '../DialogBody/DialogBody'; -import { SuspenseWithEmptyState } from '../Suspencified/Suspencified'; import React from 'react'; import LegacyVersionDialog from './LegacyVersionDialog'; +import ApiResponseErrorState from '../ApiResponseErrorState'; +import { LoadingState } from '../LoadingState'; export function ViewVersionDialogContainer(props: ViewVersionDialogContainerProps) { - const resource = useLogicResource(props, { - shouldResolve: (source) => - source.version && source.contentTypesBranch.byId && !source.isFetching && !source.contentTypesBranch.isFetching, - shouldReject: (source) => Boolean(source.error) || Boolean(source.contentTypesBranch.error), - shouldRenew: (source, resource) => (source.isFetching || source.contentTypesBranch.isFetching) && resource.complete, - resultSelector: (source) => ({ - version: source.version, - contentTypes: source.contentTypesBranch.byId - }), - errorSelector: (source) => source.error || source.contentTypesBranch.error - }); + const { error, isFetching, version } = props; return ( - - - + {error ? ( + + ) : isFetching ? ( + + ) : version ? ( + + ) : null} ); } diff --git a/ui/app/src/components/ViewVersionDialog/utils.ts b/ui/app/src/components/ViewVersionDialog/utils.ts index b53709feee..09883d56c0 100644 --- a/ui/app/src/components/ViewVersionDialog/utils.ts +++ b/ui/app/src/components/ViewVersionDialog/utils.ts @@ -20,18 +20,16 @@ import { DialogHeaderStateAction } from '../DialogHeader'; import { DialogHeaderActionProps } from '../DialogHeaderAction'; import { ApiResponse } from '../../models/ApiResponse'; import StandardAction from '../../models/StandardAction'; -import { LookupTable } from '../../models/LookupTable'; -import { Resource } from '../../models/Resource'; import { EnhancedDialogProps } from '../EnhancedDialog'; import { EnhancedDialogState } from '../../hooks/useEnhancedDialogState'; export interface VersionViewProps { - resource: Resource; -} - -export interface VersionResource { - version: any; - contentTypes: LookupTable; + version: { + content: string; + path: string; + site: string; + versionNumber: string; + }; } export interface ViewVersionDialogBaseProps { diff --git a/ui/app/src/env/codebase-bridge.tsx b/ui/app/src/env/codebase-bridge.tsx index 0b3f799445..ea874bb0dd 100644 --- a/ui/app/src/env/codebase-bridge.tsx +++ b/ui/app/src/env/codebase-bridge.tsx @@ -122,7 +122,6 @@ export function createCodebaseBridge() { SiteTools: lazy(() => import('../pages/SiteTools')), Login: lazy(() => import('../pages/Login')), PagesWidget: lazy(() => import('../components/PathNavigator/PathNavigator')), - QuickCreateMenu: lazy(() => import('../pages/QuickCreateMenu')), DeleteContentTypeButton: lazy(() => import('../pages/DeleteContentTypeButton')), PreviewCompatDialog: lazy(() => import('../components/PreviewCompatibilityDialog/PreviewCompatibilityDialog')) }, diff --git a/ui/app/src/hooks/useLogicResource.ts b/ui/app/src/hooks/useLogicResource.ts deleted file mode 100644 index d19c0cc022..0000000000 --- a/ui/app/src/hooks/useLogicResource.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import { CustomResourceSelectors } from './useSelectorResource'; -import { Resource } from '../models/Resource'; -import { useEffect, useRef, useState } from 'react'; -import { createResourceBundle } from '../utils/resource'; - -export function useLogicResource( - source: SourceType, - checkers: CustomResourceSelectors -): Resource { - const checkersRef = useRef>(); - const [[resource, resolve, reject], setBundle] = useState(() => createResourceBundle()); - - checkersRef.current = checkers; - - useEffect(() => { - const { shouldRenew, shouldReject, shouldResolve, errorSelector, resultSelector } = checkersRef.current; - if (shouldRenew(source, resource)) { - setBundle(createResourceBundle); - } else if (shouldReject(source, resource)) { - reject(errorSelector(source, resource)); - } else if (shouldResolve(source, resource)) { - resolve(resultSelector(source, resource)); - } - }, [source, resource, reject, resolve]); - - return resource; -} - -export default useLogicResource; diff --git a/ui/app/src/hooks/useQuickCreateListResource.ts b/ui/app/src/hooks/useQuickCreateListResource.ts deleted file mode 100644 index 0dc301cf38..0000000000 --- a/ui/app/src/hooks/useQuickCreateListResource.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import { useDispatch } from 'react-redux'; -import { useActiveSiteId } from './useActiveSiteId'; -import { useQuickCreateState } from './useQuickCreateState'; -import { useEffect } from 'react'; -import { fetchQuickCreateList } from '../state/actions/content'; -import { useLogicResource } from './useLogicResource'; - -export function useQuickCreateListResource() { - const dispatch = useDispatch(); - const site = useActiveSiteId(); - const quickCreate = useQuickCreateState(); - useEffect(() => { - site && dispatch(fetchQuickCreateList()); - }, [site, dispatch]); - return useLogicResource(quickCreate, { - errorSelector: (source) => source.error, - resultSelector: (source) => source.items, - shouldReject: (source) => Boolean(source.error), - shouldResolve: (source) => Boolean(source.items) && !source.isFetching, - shouldRenew: (source) => Boolean(source.items) && source.isFetching - }); -} - -export default useQuickCreateListResource; diff --git a/ui/app/src/hooks/useResolveWhenNotNullResource.ts b/ui/app/src/hooks/useResolveWhenNotNullResource.ts deleted file mode 100644 index b282e47484..0000000000 --- a/ui/app/src/hooks/useResolveWhenNotNullResource.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import { Resource } from '../models/Resource'; -import { useEffect, useState } from 'react'; -import { nnou } from '../utils/object'; -import { createResourceBundle } from '../utils/resource'; - -export function useResolveWhenNotNullResource(source: ResultType): Resource { - const [[resource, resolve], setBundle] = useState(() => createResourceBundle()); - useEffect(() => { - if (resource.complete) { - setBundle(createResourceBundle); - } else if (nnou(source)) { - resolve(source); - } - }, [source, resource, resolve]); - return resource; -} - -export default useResolveWhenNotNullResource; diff --git a/ui/app/src/hooks/useSelectorResource.ts b/ui/app/src/hooks/useSelectorResource.ts deleted file mode 100644 index a59fef9b60..0000000000 --- a/ui/app/src/hooks/useSelectorResource.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import { Resource } from '../models/Resource'; -import GlobalState from '../models/GlobalState'; -import { useSelection } from './useSelection'; -import { useLogicResource } from './useLogicResource'; - -export interface CustomResourceSelectors { - shouldResolve: (source: SourceType, resource: Resource) => boolean; - shouldReject: (source: SourceType, resource: Resource) => boolean; - shouldRenew: (source: SourceType, resource: Resource) => boolean; - resultSelector: (source: SourceType, resource: Resource) => ReturnType; - errorSelector: (source: SourceType, resource: Resource) => ErrorType; -} - -export function useSelectorResource( - sourceSelector: (state: GlobalState) => SourceType, - checkers: CustomResourceSelectors -): Resource { - const state = useSelection(sourceSelector); - return useLogicResource(state, checkers); -} - -export default useSelectorResource; diff --git a/ui/app/src/hooks/useSystemVersionResource.ts b/ui/app/src/hooks/useSystemVersionResource.ts deleted file mode 100644 index d60c526568..0000000000 --- a/ui/app/src/hooks/useSystemVersionResource.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import { useSystemVersion } from './useSystemVersion'; -import { useResolveWhenNotNullResource } from './useResolveWhenNotNullResource'; - -export function useSystemVersionResource() { - return useResolveWhenNotNullResource(useSystemVersion()); -} - -export default useSystemVersionResource; diff --git a/ui/app/src/models/Resource.ts b/ui/app/src/models/Resource.ts index 49e494201b..9d8c8cd796 100644 --- a/ui/app/src/models/Resource.ts +++ b/ui/app/src/models/Resource.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. + * Copyright (C) 2007-2024 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published by diff --git a/ui/app/src/pages/QuickCreateMenu.tsx b/ui/app/src/pages/QuickCreateMenu.tsx deleted file mode 100644 index 7aa3c4610c..0000000000 --- a/ui/app/src/pages/QuickCreateMenu.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 3 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import React, { useState } from 'react'; -import { QuickCreateMenu } from '../components/QuickCreate/QuickCreate'; -import { useQuickCreateListResource } from '../hooks/useQuickCreateListResource'; -import { useSystemVersionResource } from '../hooks/useSystemVersionResource'; - -interface QuickCreateMenuProps { - anchorEl: HTMLElement; - onNewContentSelected?(): void; - onQuickCreateItemSelected?(props: { - authoringBase: string; - path: string; - contentTypeId: string; - isNewContent: boolean; - }): void; - onClose?(): void; -} - -export default function QuickCreateMenuApp(props: QuickCreateMenuProps) { - const { anchorEl, onClose, onQuickCreateItemSelected, onNewContentSelected } = props; - const [open, setOpen] = useState(true); - - // Wait a few millis for the animation to finish before - // notifying legacy that the menu is closed to avoid - // the animation getting cut of by the unmounting - const onCloseDiffered = () => { - setTimeout(onClose, 500); - }; - - const closeMenu = () => { - setOpen(false); - onCloseDiffered(); - }; - - const quickCreateResource = useQuickCreateListResource(); - - const versionResource = useSystemVersionResource(); - - return ( - { - closeMenu(); - onNewContentSelected(); - }} - onQuickCreateItemSelected={(props) => { - closeMenu(); - onQuickCreateItemSelected(props); - }} - /> - ); -} diff --git a/ui/app/src/utils/resource.ts b/ui/app/src/utils/resource.ts index 38eaace020..fd0657ede0 100644 --- a/ui/app/src/utils/resource.ts +++ b/ui/app/src/utils/resource.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. + * Copyright (C) 2007-2024 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published by