import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import {
ActionRow, Badge, Button, Icon, Stack, useToggle,
} from '@openedx/paragon';
import { Add, Description } from '@openedx/paragon/icons';
import classNames from 'classnames';
import {
useCallback, useContext, useEffect, useState,
} from 'react';
import { blockTypes } from '../../editors/data/constants/app';
import DraggableList, { SortableItem } from '../../generic/DraggableList';
import ErrorAlert from '../../generic/alert-error';
import { getItemIcon } from '../../generic/block-type-utils';
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
import { IframeProvider } from '../../generic/hooks/context/iFrameContext';
import { InplaceTextEditor } from '../../generic/inplace-text-editor';
import Loading from '../../generic/Loading';
import TagCount from '../../generic/tag-count';
import { useLibraryContext } from '../common/context/LibraryContext';
import { PickLibraryContentModal } from '../add-content';
import ComponentMenu from '../components';
import { LibraryBlockMetadata } from '../data/api';
import {
useContainerChildren,
useUpdateContainerChildren,
useUpdateXBlockFields,
} from '../data/apiHooks';
import { LibraryBlock } from '../LibraryBlock';
import { useLibraryRoutes, ContentType } from '../routes';
import messages from './messages';
import { SidebarActions, SidebarBodyComponentId, useSidebarContext } from '../common/context/SidebarContext';
import { ToastContext } from '../../generic/toast-context';
import { canEditComponent } from '../components/ComponentEditorModal';
import { useRunOnNextRender } from '../../utils';
/** Components that need large min height in preview */
const LARGE_COMPONENTS = [
COMPONENT_TYPES.advanced,
COMPONENT_TYPES.dragAndDrop,
COMPONENT_TYPES.discussion,
'lti',
'lti_consumer',
];
interface LibraryBlockMetadataWithUniqueId extends LibraryBlockMetadata {
originalId: string;
}
interface ComponentBlockProps {
block: LibraryBlockMetadataWithUniqueId;
preview?: boolean;
isDragging?: boolean;
}
/** Component header */
const BlockHeader = ({ block }: ComponentBlockProps) => {
const intl = useIntl();
const { showOnlyPublished } = useLibraryContext();
const { showToast } = useContext(ToastContext);
const { navigateTo } = useLibraryRoutes();
const { openComponentInfoSidebar, setSidebarAction } = useSidebarContext();
const updateMutation = useUpdateXBlockFields(block.originalId);
const handleSaveDisplayName = async (newDisplayName: string) => {
try {
await updateMutation.mutateAsync({
metadata: {
display_name: newDisplayName,
},
});
showToast(intl.formatMessage(messages.updateComponentSuccessMsg));
} catch (err) {
showToast(intl.formatMessage(messages.updateComponentErrorMsg));
throw err;
}
};
/* istanbul ignore next */
const scheduleJumpToTags = useRunOnNextRender(() => {
// TODO: Ugly hack to make sure sidebar shows manage tags section
// This needs to run after all changes to url takes place to avoid conflicts.
setTimeout(() => setSidebarAction(SidebarActions.JumpToManageTags), 250);
});
/* istanbul ignore next */
const jumpToManageTags = () => {
navigateTo({ componentId: block.originalId });
openComponentInfoSidebar(block.originalId);
scheduleJumpToTags();
};
return (
<>
e.stopPropagation()}
>
e.stopPropagation()}
>
{!showOnlyPublished && block.hasUnpublishedChanges && (
)}
>
);
};
/** ComponentBlock to render preview of given component under Unit */
const ComponentBlock = ({ block, preview, isDragging }: ComponentBlockProps) => {
const { showOnlyPublished } = useLibraryContext();
const { navigateTo } = useLibraryRoutes();
const {
unitId, collectionId, componentId, openComponentEditor,
} = useLibraryContext();
const { openInfoSidebar, sidebarComponentInfo } = useSidebarContext();
const handleComponentSelection = useCallback((numberOfClicks: number) => {
navigateTo({ componentId: block.originalId });
const canEdit = canEditComponent(block.originalId);
if (numberOfClicks > 1 && canEdit) {
// Open editor on double click.
openComponentEditor(block.originalId);
} else {
// open current component sidebar
openInfoSidebar(block.originalId, collectionId, unitId);
}
}, [block, collectionId, unitId, navigateTo, canEditComponent, openComponentEditor, openInfoSidebar]);
useEffect(() => {
if (block.isNew) {
handleComponentSelection(1);
}
}, [block]);
/* istanbul ignore next */
const calculateMinHeight = () => {
if (LARGE_COMPONENTS.includes(block.blockType)) {
return '700px';
}
return '200px';
};
const getComponentStyle = useCallback(() => {
if (isDragging) {
return {
outline: '2px dashed gray',
maxHeight: '200px',
overflowY: 'hidden',
};
}
return {};
}, [isDragging, componentId, block]);
const selected = sidebarComponentInfo?.type === SidebarBodyComponentId.ComponentInfo
&& sidebarComponentInfo?.id === block.originalId;
return (
}
actionStyle={{
borderRadius: '8px 8px 0px 0px',
padding: '0.5rem 1rem',
background: '#FBFAF9',
borderBottom: 'solid 1px #E1DDDB',
}}
isClickable
onClick={(e: { detail: number; }) => handleComponentSelection(e.detail)}
disabled={preview}
cardClassName={selected ? 'selected' : undefined}
>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
e.stopPropagation()}
>
);
};
interface LibraryUnitBlocksProps {
/** set to true if it is rendered as preview
* This disables drag and drop
*/
preview?: boolean;
}
export const LibraryUnitBlocks = ({ preview }: LibraryUnitBlocksProps) => {
const intl = useIntl();
const [orderedBlocks, setOrderedBlocks] = useState([]);
const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle();
const [hidePreviewFor, setHidePreviewFor] = useState(null);
const { showToast } = useContext(ToastContext);
const { unitId, readOnly, showOnlyPublished } = useLibraryContext();
const { openAddContentSidebar } = useSidebarContext();
const orderMutator = useUpdateContainerChildren(unitId);
const {
data: blocks,
isLoading,
isError,
error,
} = useContainerChildren(unitId, showOnlyPublished);
const handleReorder = useCallback(() => async (newOrder?: LibraryBlockMetadataWithUniqueId[]) => {
if (!newOrder) {
return;
}
const usageKeys = newOrder.map((o) => o.originalId);
try {
await orderMutator.mutateAsync(usageKeys);
showToast(intl.formatMessage(messages.orderUpdatedMsg));
} catch (e) {
showToast(intl.formatMessage(messages.failedOrderUpdatedMsg));
}
}, [orderMutator]);
useEffect(() => {
// Create new ids which are unique using index.
// This is required to support multiple components with same id under a unit.
const newBlocks = blocks?.map((block, idx) => {
const newBlock: LibraryBlockMetadataWithUniqueId = {
...block,
id: `${block.id}----${idx}`,
originalId: block.id,
};
return newBlock;
});
return setOrderedBlocks(newBlocks || []);
}, [blocks, setOrderedBlocks]);
if (isLoading) {
return ;
}
if (isError) {
// istanbul ignore next
return ;
}
return (
{orderedBlocks?.map((block, idx) => (
// A container can have multiple instances of the same block
// eslint-disable-next-line react/no-array-index-key
))}
{!preview && (