From 8259b589a31b28644e7fd0d9187f7baf8f930463 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Mon, 29 Jul 2024 14:25:02 +0300 Subject: [PATCH] feat: Add `Paste from Clipboard` to lib sidebar This includes a fix for fetching the initial clipboard data and populating the redux state on first render. --- .../clipboard/hooks/useCopyToClipboard.js | 22 ++++++++++++++++++- .../add-content/AddContentContainer.tsx | 18 +++++++++++++++ src/library-authoring/add-content/messages.ts | 5 +++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/generic/clipboard/hooks/useCopyToClipboard.js b/src/generic/clipboard/hooks/useCopyToClipboard.js index 86303fab95..ce911c6b36 100644 --- a/src/generic/clipboard/hooks/useCopyToClipboard.js +++ b/src/generic/clipboard/hooks/useCopyToClipboard.js @@ -1,6 +1,9 @@ +// @ts-check import { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; +import { getClipboard } from '../../data/api'; +import { updateClipboardData } from '../../data/slice'; import { CLIPBOARD_STATUS, STRUCTURAL_XBLOCK_TYPES, STUDIO_CLIPBOARD_CHANNEL } from '../../../constants'; import { getClipboardData } from '../../data/selectors'; @@ -14,6 +17,7 @@ import { getClipboardData } from '../../data/selectors'; * @property {Object} sharedClipboardData - The shared clipboard data object. */ const useCopyToClipboard = (canEdit = true) => { + const dispatch = useDispatch(); const [clipboardBroadcastChannel] = useState(() => new BroadcastChannel(STUDIO_CLIPBOARD_CHANNEL)); const [showPasteUnit, setShowPasteUnit] = useState(false); const [showPasteXBlock, setShowPasteXBlock] = useState(false); @@ -30,6 +34,22 @@ const useCopyToClipboard = (canEdit = true) => { setShowPasteUnit(!!isPasteableUnit); }; + // Called on initial render to fetch and populate the initial clipboard data in redux state. + // Without this, the initial clipboard data redux state is always null. + useEffect(() => { + const fetchInitialClipboardData = async () => { + try { + const userClipboard = await getClipboard(); + dispatch(updateClipboardData(userClipboard)); + } catch (error) { + // eslint-disable-next-line no-console + console.error(`Failed to fetch initial clipboard data: ${error}`); + } + }; + + fetchInitialClipboardData(); + }, [dispatch]); + useEffect(() => { // Handle updates to clipboard data if (canEdit) { diff --git a/src/library-authoring/add-content/AddContentContainer.tsx b/src/library-authoring/add-content/AddContentContainer.tsx index 9af31593cb..dab3bca92e 100644 --- a/src/library-authoring/add-content/AddContentContainer.tsx +++ b/src/library-authoring/add-content/AddContentContainer.tsx @@ -1,4 +1,5 @@ import React, { useContext } from 'react'; +import { useSelector } from 'react-redux'; import { Stack, Button, @@ -12,10 +13,13 @@ import { ThumbUpOutline, Question, VideoCamera, + ContentPaste, } from '@openedx/paragon/icons'; import { v4 as uuid4 } from 'uuid'; import { useParams } from 'react-router-dom'; import { ToastContext } from '../../generic/toast-context'; +import { useCopyToClipboard } from '../../generic/clipboard'; +import { getCanEdit } from '../../course-unit/data/selectors'; import { useCreateLibraryBlock } from '../data/apiHooks'; import messages from './messages'; @@ -24,6 +28,8 @@ const AddContentContainer = () => { const { libraryId } = useParams(); const createBlockMutation = useCreateLibraryBlock(); const { showToast } = useContext(ToastContext); + const canEdit = useSelector(getCanEdit); + const { showPasteXBlock } = useCopyToClipboard(canEdit); const contentTypes = [ { @@ -64,6 +70,18 @@ const AddContentContainer = () => { }, ]; + // Include the 'Paste from Clipboard' button if there is an Xblock in the clipboard + // that can be pasted + if (showPasteXBlock) { + const pasteButton = { + name: intl.formatMessage(messages.pasteButton), + disabled: false, + icon: ContentPaste, + blockType: 'paste', + }; + contentTypes.push(pasteButton); + } + const onCreateContent = (blockType: string) => { if (libraryId) { createBlockMutation.mutateAsync({ diff --git a/src/library-authoring/add-content/messages.ts b/src/library-authoring/add-content/messages.ts index 6024e144c1..72314a67a8 100644 --- a/src/library-authoring/add-content/messages.ts +++ b/src/library-authoring/add-content/messages.ts @@ -40,6 +40,11 @@ const messages = defineMessages({ defaultMessage: 'Advanced / Other', description: 'Content of button to create a Advanced / Other component.', }, + pasteButton: { + id: 'course-authoring.library-authoring.add-content.buttons.paste', + defaultMessage: 'Paste From Clipboard', + description: 'Content of button to paste from clipboard.', + }, successCreateMessage: { id: 'course-authoring.library-authoring.add-content.success.text', defaultMessage: 'Content created successfully.',