From e045a426eb51e8cf759e6a7aea16a7c8e15f0343 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Thu, 27 Jun 2024 17:45:40 -0700 Subject: [PATCH 1/3] feat: plugin slot wrapping unit contents Co-authored-by: Adolfo R. Brandes --- src/courseware/course/sequence/Unit/index.jsx | 23 +++++++++++-------- src/plugin-slots/UnitContentsSlot/index.jsx | 19 +++++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 src/plugin-slots/UnitContentsSlot/index.jsx diff --git a/src/courseware/course/sequence/Unit/index.jsx b/src/courseware/course/sequence/Unit/index.jsx index 37eb396d88..a496946133 100644 --- a/src/courseware/course/sequence/Unit/index.jsx +++ b/src/courseware/course/sequence/Unit/index.jsx @@ -15,6 +15,7 @@ import { modelKeys, views } from './constants'; import { useExamAccess, useShouldDisplayHonorCode } from './hooks'; import { getIFrameUrl } from './urls'; import UnitTitleSlot from '../../../../plugin-slots/UnitTitleSlot'; +import UnitContentsSlot from '../../../../plugin-slots/UnitContentsSlot'; const Unit = ({ courseId, @@ -50,16 +51,18 @@ const Unit = ({
- + + +
); }; diff --git a/src/plugin-slots/UnitContentsSlot/index.jsx b/src/plugin-slots/UnitContentsSlot/index.jsx new file mode 100644 index 0000000000..15fc9f6e38 --- /dev/null +++ b/src/plugin-slots/UnitContentsSlot/index.jsx @@ -0,0 +1,19 @@ +import PropTypes from 'prop-types'; +import { PluginSlot } from '@openedx/frontend-plugin-framework'; + +const UnitContentsSlot = ({ courseId, unitId, children }) => ( + + {children} + +); + +UnitContentsSlot.propTypes = { + courseId: PropTypes.string.isRequired, + unitId: PropTypes.string.isRequired, + children: PropTypes.element.isRequired, +}; + +export default UnitContentsSlot; From b3e8197061b555ccac8b7ce6d66538c409a8f533 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Fri, 28 Jun 2024 09:32:37 -0700 Subject: [PATCH 2/3] feat: add react-query Co-authored-by: Adolfo R. Brandes --- package-lock.json | 27 +++++++++++++++++++++++++++ package.json | 1 + src/index.jsx | 23 +++++++++++++---------- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89ca93b040..7ba404eaa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@openedx/paragon": "^22.16.0", "@popperjs/core": "2.11.8", "@reduxjs/toolkit": "1.9.7", + "@tanstack/react-query": "^5.77.2", "buffer": "^6.0.3", "classnames": "2.5.1", "copy-webpack-plugin": "^12.0.0", @@ -5169,6 +5170,32 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tanstack/query-core": { + "version": "5.77.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.77.2.tgz", + "integrity": "sha512-1lqJwPsR6GX6nZFw06erRt518O19tWU6Q+x0fJUygl4lxHCYF2nhzBPwLKk2NPjYOrpR0K567hxPc5K++xDe9Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.77.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.77.2.tgz", + "integrity": "sha512-BRHxWdy1mHmgAcYA/qy2IPLylT81oebLgkm9K85viN2Qol/Vq48t1dzDFeDIVQjTWDV96AmqsLNPlH5HjyKCxA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.77.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", diff --git a/package.json b/package.json index b4351459a1..0c192f2dda 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@openedx/paragon": "^22.16.0", "@popperjs/core": "2.11.8", "@reduxjs/toolkit": "1.9.7", + "@tanstack/react-query": "^5.77.2", "buffer": "^6.0.3", "classnames": "2.5.1", "copy-webpack-plugin": "^12.0.0", diff --git a/src/index.jsx b/src/index.jsx index 72c4992ac7..9df243298e 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -7,6 +7,7 @@ import { AppProvider, ErrorPage, PageWrap } from '@edx/frontend-platform/react'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { Routes, Route } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Helmet } from 'react-helmet'; import { fetchDiscussionTab, fetchLiveTab } from './course-home/data/thunks'; @@ -37,6 +38,8 @@ import { DECODE_ROUTES, ROUTES } from './constants'; import PreferencesUnsubscribe from './preferences-unsubscribe'; import PageNotFound from './generic/PageNotFound'; +const queryClient = new QueryClient(); + subscribe(APP_READY, () => { const root = createRoot(document.getElementById('root')); @@ -49,7 +52,7 @@ subscribe(APP_READY, () => { - + } /> } /> } /> @@ -57,7 +60,7 @@ subscribe(APP_READY, () => { path={ROUTES.PREFERENCES_UNSUBSCRIBE} element={ - } + } /> { - )} + )} /> { - )} + )} /> { - )} + )} /> { - )} + )} /> {DECODE_ROUTES.PROGRESS.map((route) => ( { - )} + )} /> ))} { - )} + )} /> {DECODE_ROUTES.COURSEWARE.map((route) => ( { - )} + )} /> ))} - + From 7e24003200f0be8bbdb06f28b75175db7aa64754 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Fri, 28 Jun 2024 09:36:02 -0700 Subject: [PATCH 3/3] feat: example plugins Co-authored-by: Adolfo R. Brandes --- .gitignore | 4 +-- env.config.jsx | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 env.config.jsx diff --git a/.gitignore b/.gitignore index 3fc643913a..83554b03e8 100755 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ node_modules npm-debug.log coverage -env.config.* +# env.config.* dist/ src/i18n/transifex_input.json @@ -29,4 +29,4 @@ module.config.js src/i18n/messages/ -env.config.jsx +# env.config.jsx diff --git a/env.config.jsx b/env.config.jsx new file mode 100644 index 0000000000..eeadbc37bd --- /dev/null +++ b/env.config.jsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { AppContext } from '@edx/frontend-platform/react'; +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; +import { Button } from '@openedx/paragon'; +import { useQuery } from '@tanstack/react-query' + +const config = { + pluginSlots: { + 'org.openedx.frontend.learning.unit_contents.v1': { + keepDefault: true, + plugins: [ + { + // Display the unit ID *above* the content + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'before_unit_content', + priority: 10, // 10 will come before the unit content, which has priority 50 + type: DIRECT_PLUGIN, + RenderWidget: (props) => ( +
+ This unit is {props.unitId} +
+ ), + }, + }, + { + // Display the course ID *after* the content + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'after_unit_content', + priority: 80, // will come after the unit content, which has priority 50 + type: DIRECT_PLUGIN, + RenderWidget: (props) => ( +
+ This course is {props.courseId} +
+ ), + }, + }, + { + // Blur the content + op: PLUGIN_OPERATIONS.Wrap, + widgetId: 'default_contents', // Wrap the contents + wrapper: ({ component }) => { + const [isBlurred, setBlur] = React.useState(true); + const { authenticatedUser } = React.useContext(AppContext); + if (isBlurred) { + return ( +
+
+ {component} +
+
+

{authenticatedUser?.username || 'Learner'}, are you sure you want to learn this now?

+ +
+
+ ); + } else { + return <>{component}; + } + }, + }, + { + // Display a random dog picture after the each unit + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'after_unit_dog', + priority: 90, + type: DIRECT_PLUGIN, + RenderWidget: (props) => { + const { data, isLoading, error } = useQuery({ + queryKey: ['unit_dog', props.unitId], + queryFn: async () => { + const response = await fetch('https://dog.ceo/api/breeds/image/random'); + return (await response.json()).message; + }, + refetchOnWindowFocus: false, + refetchOnMount: false, + }); + if (isLoading) return
Loading doggo...
; + if (!data) return
Error: {error}
; + return

Bonus doggo for this unit:

Doggo

; + }, + }, + }, + ] + } + }, +} + +export default config;