chore: add slot to allow additional course app plugin (#941)
* chore: add @openedx/frontend-plugin-framework chore: move plugin page setting button to a props chore: split out app setting modal for reusability chore: add implementation of WTC plugin chore: update app setting form chore: implement the plugin form with mock chore: follow the UI design chore: remove translation plugin and move it into frontend-plugin instead * chore: add eslint ignore for env.config.jsx * chore: update package-lock.json
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
coverage/*
|
||||
dist/
|
||||
node_modules/
|
||||
jest.config.js
|
||||
jest.config.js
|
||||
env.config.jsx
|
||||
example.env.config.jsx
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,3 +26,6 @@ temp/babel-plugin-react-intl
|
||||
|
||||
# Messages .json files fetched by atlas
|
||||
src/i18n/messages/
|
||||
|
||||
# environment js config
|
||||
env.config.jsx
|
||||
|
||||
24
example.env.config.jsx
Normal file
24
example.env.config.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import WholeCourseTranslation from '@edx/course-app-translation-plugin';
|
||||
import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework';
|
||||
|
||||
// Load environment variables from .env file
|
||||
const config = {
|
||||
...process.env,
|
||||
pluginSlots: {
|
||||
additional_course_plugin: {
|
||||
plugins: [
|
||||
{
|
||||
op: PLUGIN_OPERATIONS.Insert,
|
||||
widget: {
|
||||
id: 'whole-course-translation-plugin',
|
||||
type: DIRECT_PLUGIN,
|
||||
priority: 1,
|
||||
RenderWidget: WholeCourseTranslation,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
267
package-lock.json
generated
267
package-lock.json
generated
@@ -37,6 +37,7 @@
|
||||
"@openedx-plugins/course-app-teams": "file:plugins/course-apps/teams",
|
||||
"@openedx-plugins/course-app-wiki": "file:plugins/course-apps/wiki",
|
||||
"@openedx-plugins/course-app-xpert_unit_summary": "file:plugins/course-apps/xpert_unit_summary",
|
||||
"@openedx/frontend-plugin-framework": "^1.1.0",
|
||||
"@openedx/paragon": "^22.2.1",
|
||||
"@reduxjs/toolkit": "1.9.7",
|
||||
"@tanstack/react-query": "4.36.1",
|
||||
@@ -50,10 +51,11 @@
|
||||
"lodash": "4.17.21",
|
||||
"meilisearch": "^0.38.0",
|
||||
"moment": "2.29.4",
|
||||
"prop-types": "15.7.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "17.0.2",
|
||||
"react-datepicker": "^4.13.0",
|
||||
"react-dom": "17.0.2",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-responsive": "9.0.2",
|
||||
@@ -2377,16 +2379,6 @@
|
||||
"react-dom": "^16.9.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-component-ai-translations/node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/frontend-component-ai-translations/node_modules/react-responsive": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-8.2.0.tgz",
|
||||
@@ -3495,16 +3487,6 @@
|
||||
"react": ">=16.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/react-fontawesome/node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@fullhuman/postcss-purgecss": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-5.0.0.tgz",
|
||||
@@ -4705,6 +4687,174 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@openedx/frontend-plugin-framework/-/frontend-plugin-framework-1.1.0.tgz",
|
||||
"integrity": "sha512-WT2Up9UR1ctQLeN43iCwEF6BOmU3zxL+s+clDeS4zp/aPue3v5ObTbzRsuJGlFg9gA6lY1C6Yh2+QQ6w2sK2aw==",
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||
"@edx/frontend-component-footer": "13.0.3",
|
||||
"@edx/frontend-component-header": "5.0.2",
|
||||
"classnames": "^2.3.2",
|
||||
"core-js": "3.36.0",
|
||||
"react-redux": "7.2.9",
|
||||
"redux": "4.2.1",
|
||||
"regenerator-runtime": "0.14.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-platform": "^7.0.0",
|
||||
"@openedx/paragon": "^21.0.0 || ^22.0.0",
|
||||
"prop-types": "^15.8.0",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-error-boundary": "^4.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/@edx/frontend-component-footer": {
|
||||
"version": "13.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-13.0.3.tgz",
|
||||
"integrity": "sha512-09vX6qC7AcDwG02qhBzKr4x58hpe9FXZrA9ui2cJnsG53pKaNL+wvOSRtDUBNexCf+y/iPg+8RgR+4alkzhZhw==",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.1",
|
||||
"@fortawesome/free-brands-svg-icons": "6.5.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.5.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.1",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-platform": "^7.0.0",
|
||||
"@openedx/paragon": ">= 21.11.3 < 23.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^16.9.0 || ^17.0.0",
|
||||
"react-dom": "^16.9.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/@edx/frontend-component-header": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-5.0.2.tgz",
|
||||
"integrity": "sha512-73fNNc1X/tevb3/hw7+s22T+nPGlW1yXA7zsT9eRzdH7rBxONfp0Jz7yEdeBvTax9a96PaOht45DA6GX9eG4KA==",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "6.5.1",
|
||||
"@fortawesome/free-brands-svg-icons": "6.5.1",
|
||||
"@fortawesome/free-regular-svg-icons": "6.5.1",
|
||||
"@fortawesome/free-solid-svg-icons": "6.5.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"axios-mock-adapter": "1.22.0",
|
||||
"babel-polyfill": "6.26.0",
|
||||
"react-responsive": "8.2.0",
|
||||
"react-transition-group": "4.4.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edx/frontend-platform": "^7.0.0",
|
||||
"@openedx/paragon": ">= 21.5.7 < 23.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^16.9.0 || ^17.0.0",
|
||||
"react-dom": "^16.9.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz",
|
||||
"integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz",
|
||||
"integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/@fortawesome/free-brands-svg-icons": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.5.1.tgz",
|
||||
"integrity": "sha512-093l7DAkx0aEtBq66Sf19MgoZewv1zeY9/4C7vSKPO4qMwEsW/2VYTUTpBtLwfb9T2R73tXaRDPmE4UqLCYHfg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/@fortawesome/free-regular-svg-icons": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.1.tgz",
|
||||
"integrity": "sha512-m6ShXn+wvqEU69wSP84coxLbNl7sGVZb+Ca+XZq6k30SzuP3X4TfPqtycgUh9ASwlNh5OfQCd8pDIWxl+O+LlQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz",
|
||||
"integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/core-js": {
|
||||
"version": "3.36.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz",
|
||||
"integrity": "sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==",
|
||||
"hasInstallScript": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/react-responsive": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-8.2.0.tgz",
|
||||
"integrity": "sha512-iagCqVrw4QSjhxKp3I/YK6+ODkWY6G+YPElvdYKiUUbywwh9Ds0M7r26Fj2/7dWFFbOpcGnJE6uE7aMck8j5Qg==",
|
||||
"dependencies": {
|
||||
"hyphenate-style-name": "^1.0.0",
|
||||
"matchmediaquery": "^0.3.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"shallow-equal": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/redux": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/frontend-plugin-framework/node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/@openedx/paragon": {
|
||||
"version": "22.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-22.2.1.tgz",
|
||||
@@ -4824,16 +4974,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/paragon/node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@openedx/paragon/node_modules/react-responsive": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-8.2.0.tgz",
|
||||
@@ -5386,6 +5526,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/react-hooks/node_modules/react-error-boundary": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz",
|
||||
"integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/react/node_modules/@testing-library/dom": {
|
||||
"version": "8.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz",
|
||||
@@ -10062,16 +10218,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/resolve": {
|
||||
"version": "2.0.0-next.5",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
|
||||
@@ -17422,13 +17568,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.8.1"
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types-extra": {
|
||||
@@ -17878,28 +18024,13 @@
|
||||
"react": ">= 16.8 || 18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dropzone/node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-error-boundary": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz",
|
||||
"integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==",
|
||||
"dev": true,
|
||||
"version": "4.0.13",
|
||||
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.13.tgz",
|
||||
"integrity": "sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.13.1"
|
||||
}
|
||||
@@ -17996,16 +18127,6 @@
|
||||
"react": ">=0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-imask/node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-intl": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.6.4.tgz",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"scripts": {
|
||||
"build": "fedx-scripts webpack",
|
||||
"i18n_extract": "fedx-scripts formatjs extract",
|
||||
"stylelint": "stylelint \"src/**/*.scss\" \"scss/**/*.scss\" --config .stylelintrc.json",
|
||||
"stylelint": "stylelint \"plugins/**/*.scss\" \"src/**/*.scss\" \"scss/**/*.scss\" --config .stylelintrc.json",
|
||||
"lint": "npm run stylelint && fedx-scripts eslint --ext .js --ext .jsx .",
|
||||
"lint:fix": "npm run stylelint && fedx-scripts eslint --ext .js --ext .jsx . --fix",
|
||||
"snapshot": "TZ=UTC fedx-scripts jest --updateSnapshot",
|
||||
@@ -64,6 +64,7 @@
|
||||
"@openedx-plugins/course-app-teams": "file:plugins/course-apps/teams",
|
||||
"@openedx-plugins/course-app-wiki": "file:plugins/course-apps/wiki",
|
||||
"@openedx-plugins/course-app-xpert_unit_summary": "file:plugins/course-apps/xpert_unit_summary",
|
||||
"@openedx/frontend-plugin-framework": "^1.1.0",
|
||||
"@openedx/paragon": "^22.2.1",
|
||||
"@reduxjs/toolkit": "1.9.7",
|
||||
"@tanstack/react-query": "4.36.1",
|
||||
@@ -77,10 +78,11 @@
|
||||
"lodash": "4.17.21",
|
||||
"meilisearch": "^0.38.0",
|
||||
"moment": "2.29.4",
|
||||
"prop-types": "15.7.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "17.0.2",
|
||||
"react-datepicker": "^4.13.0",
|
||||
"react-dom": "17.0.2",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-responsive": "9.0.2",
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
ActionRow,
|
||||
Alert,
|
||||
Badge,
|
||||
Form,
|
||||
Hyperlink,
|
||||
ModalDialog,
|
||||
StatefulButton,
|
||||
TransitionReplace,
|
||||
} from '@openedx/paragon';
|
||||
@@ -31,81 +29,10 @@ import { updateSavingStatus } from '../data/slice';
|
||||
import { updateAppStatus } from '../data/thunks';
|
||||
import AppConfigFormDivider from '../discussions/app-config-form/apps/shared/AppConfigFormDivider';
|
||||
import { PagesAndResourcesContext } from '../PagesAndResourcesProvider';
|
||||
import AppSettingsModalBase from './AppSettingsModalBase';
|
||||
import messages from './messages';
|
||||
|
||||
const AppSettingsForm = ({
|
||||
formikProps, children, showForm,
|
||||
}) => children && (
|
||||
<TransitionReplace>
|
||||
{showForm ? (
|
||||
<React.Fragment key="app-enabled">
|
||||
{children(formikProps)}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment key="app-disabled" />
|
||||
)}
|
||||
</TransitionReplace>
|
||||
);
|
||||
|
||||
AppSettingsForm.propTypes = {
|
||||
// Ignore the warning here since we're just passing along the props as-is and the child component should validate
|
||||
// eslint-disable-next-line react/forbid-prop-types
|
||||
formikProps: PropTypes.object.isRequired,
|
||||
showForm: PropTypes.bool.isRequired,
|
||||
children: PropTypes.func,
|
||||
};
|
||||
|
||||
AppSettingsForm.defaultProps = {
|
||||
children: null,
|
||||
};
|
||||
|
||||
const AppSettingsModalBase = ({
|
||||
intl, title, onClose, variant, isMobile, children, footer,
|
||||
}) => (
|
||||
<ModalDialog
|
||||
title={title}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
size="lg"
|
||||
variant={variant}
|
||||
hasCloseButton={isMobile}
|
||||
isFullscreenOnMobile
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
<ModalDialog.Title data-testid="modal-title">
|
||||
{title}
|
||||
</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body>
|
||||
{children}
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer className="p-4">
|
||||
<ActionRow>
|
||||
<ModalDialog.CloseButton variant="tertiary">
|
||||
{intl.formatMessage(messages.cancel)}
|
||||
</ModalDialog.CloseButton>
|
||||
{footer}
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
);
|
||||
|
||||
AppSettingsModalBase.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
variant: PropTypes.oneOf(['default', 'dark']).isRequired,
|
||||
isMobile: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
footer: PropTypes.node,
|
||||
};
|
||||
|
||||
AppSettingsModalBase.defaultProps = {
|
||||
footer: null,
|
||||
};
|
||||
|
||||
const AppSettingsModal = ({
|
||||
intl,
|
||||
appId,
|
||||
title,
|
||||
children,
|
||||
@@ -121,6 +48,7 @@ const AppSettingsModal = ({
|
||||
enableReinitialize,
|
||||
hideAppToggle,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { courseId } = useContext(PagesAndResourcesContext);
|
||||
const loadingStatus = useSelector(getLoadingStatus);
|
||||
const updateSettingsRequestStatus = useSelector(getSavingStatus);
|
||||
@@ -199,14 +127,12 @@ const AppSettingsModal = ({
|
||||
onClose={onClose}
|
||||
variant={modalVariant}
|
||||
isMobile={isMobile}
|
||||
isFullscreenOnMobile
|
||||
intl={intl}
|
||||
footer={(
|
||||
<StatefulButton
|
||||
labels={{
|
||||
default: intl.formatMessage(messages.save),
|
||||
pending: intl.formatMessage(messages.saving),
|
||||
complete: intl.formatMessage(messages.saved),
|
||||
default: formatMessage(messages.save),
|
||||
pending: formatMessage(messages.saving),
|
||||
complete: formatMessage(messages.saved),
|
||||
}}
|
||||
state={submitButtonState}
|
||||
onClick={handleFormikSubmit(formikProps)}
|
||||
@@ -216,9 +142,9 @@ const AppSettingsModal = ({
|
||||
{saveError && (
|
||||
<Alert variant="danger" icon={Info} ref={alertRef}>
|
||||
<Alert.Heading>
|
||||
{formikProps.errors.enabled?.title || intl.formatMessage(messages.errorSavingTitle)}
|
||||
{formikProps.errors.enabled?.title || formatMessage(messages.errorSavingTitle)}
|
||||
</Alert.Heading>
|
||||
{formikProps.errors.enabled?.message || intl.formatMessage(messages.errorSavingMessage)}
|
||||
{formikProps.errors.enabled?.message || formatMessage(messages.errorSavingMessage)}
|
||||
</Alert>
|
||||
)}
|
||||
{!hideAppToggle && (
|
||||
@@ -233,7 +159,7 @@ const AppSettingsModal = ({
|
||||
{enableAppLabel}
|
||||
{formikProps.values.enabled && (
|
||||
<Badge className="ml-2" variant="success" data-testid="enable-badge">
|
||||
{intl.formatMessage(messages.enabled)}
|
||||
{formatMessage(messages.enabled)}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -249,9 +175,19 @@ const AppSettingsModal = ({
|
||||
{bodyChildren}
|
||||
{(formikProps.values.enabled || configureBeforeEnable) && children
|
||||
&& <AppConfigFormDivider marginAdj={{ default: 0, sm: 0 }} />}
|
||||
<AppSettingsForm formikProps={formikProps} showForm={formikProps.values.enabled || configureBeforeEnable}>
|
||||
{children}
|
||||
</AppSettingsForm>
|
||||
{
|
||||
children && (
|
||||
<TransitionReplace>
|
||||
{formikProps.values.enabled || configureBeforeEnable ? (
|
||||
<React.Fragment key="app-enabled">
|
||||
{children(formikProps)}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment key="app-disabled" />
|
||||
)}
|
||||
</TransitionReplace>
|
||||
)
|
||||
}
|
||||
</AppSettingsModalBase>
|
||||
</Form>
|
||||
)}
|
||||
@@ -260,7 +196,6 @@ const AppSettingsModal = ({
|
||||
}
|
||||
return (
|
||||
<AppSettingsModalBase
|
||||
intl={intl}
|
||||
title={title}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
@@ -277,7 +212,6 @@ const AppSettingsModal = ({
|
||||
};
|
||||
|
||||
AppSettingsModal.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
appId: PropTypes.string.isRequired,
|
||||
children: PropTypes.func,
|
||||
@@ -306,4 +240,4 @@ AppSettingsModal.defaultProps = {
|
||||
hideAppToggle: false,
|
||||
};
|
||||
|
||||
export default injectIntl(AppSettingsModal);
|
||||
export default AppSettingsModal;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { ActionRow, ModalDialog } from '@openedx/paragon';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const AppSettingsModalBase = ({
|
||||
title,
|
||||
onClose,
|
||||
variant,
|
||||
isMobile,
|
||||
children,
|
||||
footer,
|
||||
isOpen,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
return (
|
||||
<ModalDialog
|
||||
title={title}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
size="lg"
|
||||
variant={variant}
|
||||
hasCloseButton={isMobile}
|
||||
isFullscreenOnMobile
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
<ModalDialog.Title data-testid="modal-title">{title}</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body>{children}</ModalDialog.Body>
|
||||
<ModalDialog.Footer className="p-4">
|
||||
<ActionRow>
|
||||
<ModalDialog.CloseButton variant="tertiary">
|
||||
{formatMessage(messages.cancel)}
|
||||
</ModalDialog.CloseButton>
|
||||
{footer}
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
);
|
||||
};
|
||||
|
||||
AppSettingsModalBase.defaultProps = {
|
||||
isOpen: true,
|
||||
};
|
||||
|
||||
AppSettingsModalBase.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
variant: PropTypes.oneOf(['default', 'dark']).isRequired,
|
||||
isMobile: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
footer: PropTypes.node,
|
||||
isOpen: PropTypes.bool,
|
||||
};
|
||||
|
||||
AppSettingsModalBase.defaultProps = {
|
||||
footer: null,
|
||||
};
|
||||
|
||||
export default AppSettingsModalBase;
|
||||
@@ -1,15 +1,11 @@
|
||||
import classNames from 'classnames';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Badge, Card, Icon, IconButton, Hyperlink,
|
||||
} from '@openedx/paragon';
|
||||
import { ArrowForward, Settings } from '@openedx/paragon/icons';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Badge, Card } from '@openedx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useContext } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import messages from '../messages';
|
||||
import { PagesAndResourcesContext } from '../PagesAndResourcesProvider';
|
||||
import { useIsDesktop } from '../../utils';
|
||||
import PageSettingButton from './PageSettingButton';
|
||||
import './PageCard.scss';
|
||||
|
||||
const CoursePageShape = PropTypes.shape({
|
||||
@@ -19,45 +15,21 @@ const CoursePageShape = PropTypes.shape({
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
legacyLink: PropTypes.string,
|
||||
allowedOperations: PropTypes.shape({
|
||||
enable: PropTypes.bool.isRequired,
|
||||
configure: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
enable: PropTypes.bool,
|
||||
configure: PropTypes.bool,
|
||||
}),
|
||||
});
|
||||
|
||||
export { CoursePageShape };
|
||||
|
||||
const PageCard = ({
|
||||
intl,
|
||||
page,
|
||||
settingButton,
|
||||
}) => {
|
||||
const { path: pagesAndResourcesPath } = useContext(PagesAndResourcesContext);
|
||||
const { formatMessage } = useIntl();
|
||||
const isDesktop = useIsDesktop();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
const SettingsButton = () => {
|
||||
if (page.legacyLink) {
|
||||
return (
|
||||
<Hyperlink destination={page.legacyLink}>
|
||||
<IconButton
|
||||
src={ArrowForward}
|
||||
iconAs={Icon}
|
||||
size="inline"
|
||||
alt={intl.formatMessage(messages.settings)}
|
||||
/>
|
||||
</Hyperlink>
|
||||
);
|
||||
}
|
||||
return (page.allowedOperations.configure || page.allowedOperations.enable) && (
|
||||
<IconButton
|
||||
src={Settings}
|
||||
iconAs={Icon}
|
||||
size="inline"
|
||||
alt={intl.formatMessage(messages.settings)}
|
||||
onClick={() => navigate(`${pagesAndResourcesPath}/${page.id}/settings`)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const SettingButton = settingButton || <PageSettingButton {...page} />;
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -70,10 +42,10 @@ const PageCard = ({
|
||||
title={page.name}
|
||||
subtitle={page.enabled && (
|
||||
<Badge variant="success" className="mt-1">
|
||||
{intl.formatMessage(messages.enabled)}
|
||||
{formatMessage(messages.enabled)}
|
||||
</Badge>
|
||||
)}
|
||||
actions={<div className="mt-1"><SettingsButton /></div>}
|
||||
actions={<div className="mt-1">{SettingButton}</div>}
|
||||
size="sm"
|
||||
/>
|
||||
<Card.Body>
|
||||
@@ -85,9 +57,13 @@ const PageCard = ({
|
||||
);
|
||||
};
|
||||
|
||||
PageCard.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
page: CoursePageShape.isRequired,
|
||||
PageCard.defaultProps = {
|
||||
settingButton: null,
|
||||
};
|
||||
|
||||
export default injectIntl(PageCard);
|
||||
PageCard.propTypes = {
|
||||
page: CoursePageShape.isRequired,
|
||||
settingButton: PropTypes.node,
|
||||
};
|
||||
|
||||
export default PageCard;
|
||||
|
||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { CardGrid } from '@openedx/paragon';
|
||||
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||
import PageCard, { CoursePageShape } from './PageCard';
|
||||
|
||||
const PageGrid = ({ pages }) => (
|
||||
@@ -15,6 +16,7 @@ const PageGrid = ({ pages }) => (
|
||||
{pages.map((page) => (
|
||||
<PageCard page={page} key={page.id} />
|
||||
))}
|
||||
<PluginSlot id="additional_course_plugin" />
|
||||
</CardGrid>
|
||||
);
|
||||
|
||||
|
||||
59
src/pages-and-resources/pages/PageSettingButton.jsx
Normal file
59
src/pages-and-resources/pages/PageSettingButton.jsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Icon, IconButton, Hyperlink } from '@openedx/paragon';
|
||||
import { ArrowForward, Settings } from '@openedx/paragon/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import messages from '../messages';
|
||||
import { PagesAndResourcesContext } from '../PagesAndResourcesProvider';
|
||||
|
||||
const PageSettingButton = ({
|
||||
id,
|
||||
legacyLink,
|
||||
allowedOperations,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { path: pagesAndResourcesPath } = useContext(PagesAndResourcesContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (legacyLink) {
|
||||
return (
|
||||
<Hyperlink destination={legacyLink}>
|
||||
<IconButton
|
||||
src={ArrowForward}
|
||||
iconAs={Icon}
|
||||
size="inline"
|
||||
alt={formatMessage(messages.settings)}
|
||||
/>
|
||||
</Hyperlink>
|
||||
);
|
||||
} if (!(allowedOperations?.configure || allowedOperations?.enable)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<IconButton
|
||||
src={Settings}
|
||||
iconAs={Icon}
|
||||
size="inline"
|
||||
alt={formatMessage(messages.settings)}
|
||||
onClick={() => navigate(`${pagesAndResourcesPath}/${id}/settings`)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
PageSettingButton.defaultProps = {
|
||||
legacyLink: null,
|
||||
allowedOperations: null,
|
||||
};
|
||||
|
||||
PageSettingButton.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
legacyLink: PropTypes.string,
|
||||
allowedOperations: PropTypes.shape({
|
||||
configure: PropTypes.bool,
|
||||
enable: PropTypes.bool,
|
||||
}),
|
||||
};
|
||||
|
||||
export default PageSettingButton;
|
||||
Reference in New Issue
Block a user