@openedx/frontend-base 1.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +112 -0
- package/config/babel/babel.config.js +28 -0
- package/config/config-helpers/createConfig.js +13 -0
- package/config/config-helpers/createLintConfig.js +16 -0
- package/config/config-helpers/getBaseConfig.js +12 -0
- package/config/defaultConfigPaths.js +35 -0
- package/config/eslint/base.eslint.config.js +113 -0
- package/config/index.js +12 -0
- package/config/jest/jest.config.js +30 -0
- package/config/tsconfig.json +32 -0
- package/config/types.js +25 -0
- package/config/webpack/common-config/all/getCodeRules.js +52 -0
- package/config/webpack/common-config/all/getFileLoaderRules.js +26 -0
- package/config/webpack/common-config/all/getIgnoreWarnings.js +14 -0
- package/config/webpack/common-config/all/getImageMinimizer.js +25 -0
- package/config/webpack/common-config/all/getStylesheetRule.js +112 -0
- package/config/webpack/common-config/dev/getDevServer.js +38 -0
- package/config/webpack/common-config/index.js +18 -0
- package/config/webpack/common-config/site/getHtmlWebpackPlugin.js +16 -0
- package/config/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.js +91 -0
- package/config/webpack/plugins/html-webpack-new-relic-plugin/index.js +7 -0
- package/config/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +3 -0
- package/config/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.js +108 -0
- package/config/webpack/plugins/paragon-webpack-plugin/index.js +7 -0
- package/config/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.js +64 -0
- package/config/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.js +53 -0
- package/config/webpack/plugins/paragon-webpack-plugin/utils/index.js +9 -0
- package/config/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.js +114 -0
- package/config/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.js +146 -0
- package/config/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.js +126 -0
- package/config/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.js +57 -0
- package/config/webpack/types.js +2 -0
- package/config/webpack/utils/getLocalAliases.js +65 -0
- package/config/webpack/utils/getPublicPath.js +6 -0
- package/config/webpack/utils/getResolvedSiteConfigPath.js +32 -0
- package/config/webpack/utils/paragonUtils.js +138 -0
- package/config/webpack/webpack.config.build.js +80 -0
- package/config/webpack/webpack.config.dev.js +76 -0
- package/config/webpack/webpack.config.dev.shell.js +110 -0
- package/eslint.config.js +18 -0
- package/frontend-base.d.ts +8 -0
- package/index.ts +7 -0
- package/jest.config.js +7 -0
- package/openedx-frontend-base.tgz +0 -0
- package/package.json +149 -0
- package/runtime/analytics/MockAnalyticsService.js +71 -0
- package/runtime/analytics/SegmentAnalyticsService.js +243 -0
- package/runtime/analytics/index.ts +12 -0
- package/runtime/analytics/interface.js +145 -0
- package/runtime/auth/AxiosCsrfTokenService.js +60 -0
- package/runtime/auth/AxiosJwtAuthService.js +363 -0
- package/runtime/auth/AxiosJwtTokenService.js +134 -0
- package/runtime/auth/LocalForageCache.js +76 -0
- package/runtime/auth/MockAuthService.js +278 -0
- package/runtime/auth/index.ts +19 -0
- package/runtime/auth/interceptors/createCsrfTokenProviderInterceptor.js +36 -0
- package/runtime/auth/interceptors/createJwtTokenProviderInterceptor.js +37 -0
- package/runtime/auth/interceptors/createProcessAxiosRequestErrorInterceptor.js +20 -0
- package/runtime/auth/interceptors/createRetryInterceptor.js +74 -0
- package/runtime/auth/interface.js +309 -0
- package/runtime/auth/utils.js +105 -0
- package/runtime/babel.config.js +3 -0
- package/runtime/config/index.ts +295 -0
- package/runtime/constants.ts +68 -0
- package/runtime/i18n/index.js +118 -0
- package/runtime/i18n/injectIntlWithShim.jsx +48 -0
- package/runtime/i18n/lib.ts +272 -0
- package/runtime/index.ts +134 -0
- package/runtime/initialize.js +352 -0
- package/runtime/jest.config.js +32 -0
- package/runtime/logging/MockLoggingService.js +31 -0
- package/runtime/logging/NewRelicLoggingService.js +184 -0
- package/runtime/logging/index.ts +9 -0
- package/runtime/logging/interface.js +109 -0
- package/runtime/logging/types.ts +4 -0
- package/runtime/react/AuthenticatedPageRoute.jsx +43 -0
- package/runtime/react/CombinedAppProvider.tsx +46 -0
- package/runtime/react/CurrentAppContext.tsx +25 -0
- package/runtime/react/CurrentAppProvider.tsx +46 -0
- package/runtime/react/Divider.tsx +5 -0
- package/runtime/react/ErrorBoundary.jsx +47 -0
- package/runtime/react/ErrorPage.jsx +72 -0
- package/runtime/react/LoginRedirect.jsx +16 -0
- package/runtime/react/PageWrap.jsx +24 -0
- package/runtime/react/SiteContext.tsx +32 -0
- package/runtime/react/SiteProvider.tsx +78 -0
- package/runtime/react/hooks.ts +106 -0
- package/runtime/react/index.ts +19 -0
- package/runtime/routing/index.ts +1 -0
- package/runtime/routing/utils.ts +34 -0
- package/runtime/scripts/GoogleAnalyticsLoader.ts +59 -0
- package/runtime/scripts/index.ts +1 -0
- package/runtime/setupTest.js +49 -0
- package/runtime/slots/Slot.tsx +32 -0
- package/runtime/slots/SlotContext.tsx +8 -0
- package/runtime/slots/hooks.ts +35 -0
- package/runtime/slots/index.ts +7 -0
- package/runtime/slots/layout/DefaultSlotLayout.tsx +9 -0
- package/runtime/slots/layout/hooks.ts +65 -0
- package/runtime/slots/layout/index.ts +5 -0
- package/runtime/slots/layout/types.ts +45 -0
- package/runtime/slots/layout/utils.ts +14 -0
- package/runtime/slots/types.ts +23 -0
- package/runtime/slots/utils.ts +59 -0
- package/runtime/slots/widget/WidgetContext.tsx +9 -0
- package/runtime/slots/widget/WidgetProvider.tsx +30 -0
- package/runtime/slots/widget/hooks.ts +105 -0
- package/runtime/slots/widget/iframe/IFrameContentWrapper.messages.tsx +16 -0
- package/runtime/slots/widget/iframe/IFrameContentWrapper.tsx +84 -0
- package/runtime/slots/widget/iframe/IFrameWidget.tsx +59 -0
- package/runtime/slots/widget/iframe/constants.ts +19 -0
- package/runtime/slots/widget/iframe/hooks.ts +179 -0
- package/runtime/slots/widget/iframe/index.ts +6 -0
- package/runtime/slots/widget/iframe/types.ts +7 -0
- package/runtime/slots/widget/index.ts +6 -0
- package/runtime/slots/widget/types.ts +134 -0
- package/runtime/slots/widget/utils.tsx +201 -0
- package/runtime/subscriptions.ts +60 -0
- package/runtime/testing/index.ts +9 -0
- package/runtime/testing/initializeMockApp.ts +81 -0
- package/runtime/testing/mockMessages.ts +23 -0
- package/runtime/utils.js +178 -0
- package/shell/DefaultLayout.tsx +18 -0
- package/shell/DefaultMain.tsx +7 -0
- package/shell/Logo.tsx +28 -0
- package/shell/Shell.messages.ts +61 -0
- package/shell/Shell.tsx +18 -0
- package/shell/app.scss +149 -0
- package/shell/app.ts +24 -0
- package/shell/babel.config.js +3 -0
- package/shell/dev/devFooter/app.tsx +43 -0
- package/shell/dev/devFooter/index.ts +1 -0
- package/shell/dev/devHeader/BarContext.tsx +13 -0
- package/shell/dev/devHeader/BarLink.tsx +16 -0
- package/shell/dev/devHeader/BarProvider.tsx +25 -0
- package/shell/dev/devHeader/CoursesLink.tsx +16 -0
- package/shell/dev/devHeader/FooContext.tsx +13 -0
- package/shell/dev/devHeader/FooLink.tsx +16 -0
- package/shell/dev/devHeader/FooProvider.tsx +25 -0
- package/shell/dev/devHeader/app.tsx +53 -0
- package/shell/dev/devHeader/index.ts +1 -0
- package/shell/dev/devHeader/providers.tsx +11 -0
- package/shell/dev/devHome/HomePage.tsx +28 -0
- package/shell/dev/devHome/app.ts +18 -0
- package/shell/dev/devHome/i18n/index.ts +27 -0
- package/shell/dev/devHome/index.ts +1 -0
- package/shell/dev/devHome/messages.ts +11 -0
- package/shell/dev/devUser/app.tsx +24 -0
- package/shell/dev/devUser/index.ts +1 -0
- package/shell/dev/index.ts +5 -0
- package/shell/dev/slotShowcase/HorizontalSlotLayout.tsx +11 -0
- package/shell/dev/slotShowcase/LayoutWithOptions.tsx +17 -0
- package/shell/dev/slotShowcase/SlotShowcasePage.tsx +66 -0
- package/shell/dev/slotShowcase/WidgetWithOptions.tsx +11 -0
- package/shell/dev/slotShowcase/app.tsx +373 -0
- package/shell/dev/slotShowcase/index.ts +1 -0
- package/shell/footer/CenterLinks.tsx +11 -0
- package/shell/footer/CopyrightNotice.tsx +36 -0
- package/shell/footer/Footer.tsx +34 -0
- package/shell/footer/LabeledLinkColumn.tsx +19 -0
- package/shell/footer/LanguageMenu.tsx +35 -0
- package/shell/footer/LanguageMenuItem.tsx +23 -0
- package/shell/footer/LeftLinks.tsx +11 -0
- package/shell/footer/LegalNotices.tsx +17 -0
- package/shell/footer/PoweredBy.tsx +17 -0
- package/shell/footer/RevealLinks.tsx +43 -0
- package/shell/footer/RightLinks.tsx +11 -0
- package/shell/footer/app.tsx +73 -0
- package/shell/footer/data/api.ts +48 -0
- package/shell/footer/index.ts +2 -0
- package/shell/header/AuthenticatedMenu.tsx +32 -0
- package/shell/header/Header.tsx +17 -0
- package/shell/header/anonymous-menu/AnonymousMenu.tsx +14 -0
- package/shell/header/anonymous-menu/LoginButton.tsx +14 -0
- package/shell/header/anonymous-menu/RegisterButton.tsx +15 -0
- package/shell/header/app.tsx +142 -0
- package/shell/header/desktop/DesktopLayout.tsx +22 -0
- package/shell/header/desktop/PrimaryNavLinks.tsx +10 -0
- package/shell/header/desktop/SecondaryNavLinks.tsx +10 -0
- package/shell/header/index.ts +2 -0
- package/shell/header/mobile/MobileLayout.tsx +47 -0
- package/shell/header/mobile/MobileNavLinks.tsx +10 -0
- package/shell/i18n/index.ts +25 -0
- package/shell/index.ts +7 -0
- package/shell/jest.config.js +30 -0
- package/shell/menus/LinkMenuItem.tsx +64 -0
- package/shell/menus/NavDropdownMenuSlot.tsx +29 -0
- package/shell/menus/ProfileLinkMenuItem.tsx +33 -0
- package/shell/menus/data/utils.ts +19 -0
- package/shell/public/index.html +10 -0
- package/shell/router/createRouter.ts +17 -0
- package/shell/router/getAppRoutes.ts +21 -0
- package/shell/setupTest.js +48 -0
- package/shell/site.config.dev.tsx +49 -0
- package/shell/site.tsx +41 -0
- package/test-site/app.d.ts +15 -0
- package/test-site/dist/176.436443549ebb858db483.js +2 -0
- package/test-site/dist/176.436443549ebb858db483.js.map +1 -0
- package/test-site/dist/362.536eff787d2380fe246c.js +2 -0
- package/test-site/dist/362.536eff787d2380fe246c.js.map +1 -0
- package/test-site/dist/653.486966b108d224551296.js +2 -0
- package/test-site/dist/653.486966b108d224551296.js.map +1 -0
- package/test-site/dist/74e025d3fe9a7b7f8503054e2563b353.jpg +0 -0
- package/test-site/dist/806.323cf6496ad0a7fe73a7.js +3 -0
- package/test-site/dist/806.323cf6496ad0a7fe73a7.js.LICENSE.txt +106 -0
- package/test-site/dist/806.323cf6496ad0a7fe73a7.js.map +1 -0
- package/test-site/dist/95ec738c0b7faac5b5c9126794446bbd.svg +4 -0
- package/test-site/dist/app.612058b36c74787759ac.css +61 -0
- package/test-site/dist/app.612058b36c74787759ac.css.map +1 -0
- package/test-site/dist/app.612058b36c74787759ac.js +2 -0
- package/test-site/dist/app.612058b36c74787759ac.js.map +1 -0
- package/test-site/dist/cb28cdb1468c915e27e5cec9af64f22f.svg +1 -0
- package/test-site/dist/index.html +1 -0
- package/test-site/dist/report.html +39 -0
- package/test-site/dist/runtime.c7aeaf7b967496cb076f.js +2 -0
- package/test-site/dist/runtime.c7aeaf7b967496cb076f.js.map +1 -0
- package/test-site/eslint.config.js +12 -0
- package/test-site/package-lock.json +19226 -0
- package/test-site/package.json +29 -0
- package/test-site/public/index.html +10 -0
- package/test-site/site.config.build.tsx +27 -0
- package/test-site/site.config.dev.tsx +27 -0
- package/test-site/src/authenticated-page/AuthenticatedPage.tsx +18 -0
- package/test-site/src/authenticated-page/i18n/index.ts +27 -0
- package/test-site/src/authenticated-page/index.tsx +28 -0
- package/test-site/src/example-page/ExamplePage.tsx +79 -0
- package/test-site/src/example-page/Image.tsx +11 -0
- package/test-site/src/example-page/ParagonPreview.jsx +66 -0
- package/test-site/src/example-page/apple.jpg +0 -0
- package/test-site/src/example-page/apple.svg +1 -0
- package/test-site/src/example-page/index.ts +16 -0
- package/test-site/src/i18n/README.md +3 -0
- package/test-site/src/i18n/messages/frontend-app-sample/ar.json +4 -0
- package/test-site/src/i18n/messages/frontend-app-sample/eo.json +1 -0
- package/test-site/src/i18n/messages/frontend-app-sample/es_419.json +4 -0
- package/test-site/src/i18n/messages/frontend-component-emptylangs/ar.json +1 -0
- package/test-site/src/i18n/messages/frontend-component-singlelang/ar.json +3 -0
- package/test-site/src/iframe-widget/IframeWidget.tsx +14 -0
- package/test-site/src/iframe-widget/index.ts +16 -0
- package/test-site/src/index.tsx +3 -0
- package/test-site/src/messages.js +11 -0
- package/test-site/src/site.scss +11 -0
- package/test-site/tsconfig.json +14 -0
- package/tools/babel/babel.config.js +27 -0
- package/tools/babel.config.js +3 -0
- package/tools/cli/README.md +29 -0
- package/tools/cli/commands/pack.ts +9 -0
- package/tools/cli/commands/release.ts +27 -0
- package/tools/cli/commands/serve.ts +43 -0
- package/tools/cli/intl-imports.ts +274 -0
- package/tools/cli/openedx.ts +101 -0
- package/tools/cli/transifex-utils.ts +75 -0
- package/tools/cli/utils/ensureConfigFilenameOption.ts +40 -0
- package/tools/cli/utils/formatter.ts +10 -0
- package/tools/cli/utils/getResolvedConfigPath.ts +23 -0
- package/tools/cli/utils/prettyPrintTitle.ts +15 -0
- package/tools/cli/utils/printUsage.ts +53 -0
- package/tools/config-helpers/createConfig.ts +8 -0
- package/tools/config-helpers/createLintConfig.ts +14 -0
- package/tools/config-helpers/getBaseConfig.ts +11 -0
- package/tools/defaultConfigPaths.ts +30 -0
- package/tools/dist/babel/babel.config.js +28 -0
- package/tools/dist/cli/commands/pack.js +14 -0
- package/tools/dist/cli/commands/release.js +28 -0
- package/tools/dist/cli/commands/serve.js +44 -0
- package/tools/dist/cli/intl-imports.js +233 -0
- package/tools/dist/cli/openedx.js +100 -0
- package/tools/dist/cli/transifex-utils.js +68 -0
- package/tools/dist/cli/utils/ensureConfigFilenameOption.js +42 -0
- package/tools/dist/cli/utils/formatter.js +10 -0
- package/tools/dist/cli/utils/getResolvedConfigPath.js +28 -0
- package/tools/dist/cli/utils/prettyPrintTitle.js +17 -0
- package/tools/dist/cli/utils/printUsage.js +48 -0
- package/tools/dist/config-helpers/createConfig.js +13 -0
- package/tools/dist/config-helpers/createLintConfig.js +16 -0
- package/tools/dist/config-helpers/getBaseConfig.js +12 -0
- package/tools/dist/defaultConfigPaths.js +35 -0
- package/tools/dist/eslint/base.eslint.config.js +113 -0
- package/tools/dist/eslint.config.js +11 -0
- package/tools/dist/index.js +12 -0
- package/tools/dist/jest/jest.config.js +30 -0
- package/tools/dist/jest.config.js +20 -0
- package/tools/dist/types.js +25 -0
- package/tools/dist/typescript/tsconfig.json +32 -0
- package/tools/dist/webpack/common-config/all/getCodeRules.js +52 -0
- package/tools/dist/webpack/common-config/all/getFileLoaderRules.js +26 -0
- package/tools/dist/webpack/common-config/all/getIgnoreWarnings.js +14 -0
- package/tools/dist/webpack/common-config/all/getImageMinimizer.js +25 -0
- package/tools/dist/webpack/common-config/all/getStylesheetRule.js +112 -0
- package/tools/dist/webpack/common-config/dev/getDevServer.js +38 -0
- package/tools/dist/webpack/common-config/index.js +18 -0
- package/tools/dist/webpack/common-config/site/getHtmlWebpackPlugin.js +16 -0
- package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.js +91 -0
- package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/index.js +7 -0
- package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +3 -0
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.js +108 -0
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/index.js +7 -0
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.js +64 -0
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.js +53 -0
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/index.js +9 -0
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.js +114 -0
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.js +146 -0
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.js +126 -0
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.js +57 -0
- package/tools/dist/webpack/types.js +2 -0
- package/tools/dist/webpack/utils/getLocalAliases.js +65 -0
- package/tools/dist/webpack/utils/getPublicPath.js +6 -0
- package/tools/dist/webpack/utils/getResolvedSiteConfigPath.js +32 -0
- package/tools/dist/webpack/utils/paragonUtils.js +138 -0
- package/tools/dist/webpack/webpack.config.build.js +80 -0
- package/tools/dist/webpack/webpack.config.dev.js +76 -0
- package/tools/dist/webpack/webpack.config.dev.shell.js +110 -0
- package/tools/eslint/base.eslint.config.js +124 -0
- package/tools/eslint/modules.d.ts +5 -0
- package/tools/eslint.config.js +15 -0
- package/tools/index.ts +3 -0
- package/tools/jest/jest.config.js +30 -0
- package/tools/jest.config.js +19 -0
- package/tools/tsconfig.json +24 -0
- package/tools/types.ts +21 -0
- package/tools/typescript/tsconfig.json +32 -0
- package/tools/webpack/common-config/README.md +15 -0
- package/tools/webpack/common-config/all/getCodeRules.ts +51 -0
- package/tools/webpack/common-config/all/getFileLoaderRules.ts +23 -0
- package/tools/webpack/common-config/all/getIgnoreWarnings.ts +13 -0
- package/tools/webpack/common-config/all/getImageMinimizer.ts +26 -0
- package/tools/webpack/common-config/all/getStylesheetRule.ts +111 -0
- package/tools/webpack/common-config/dev/getDevServer.ts +35 -0
- package/tools/webpack/common-config/index.ts +6 -0
- package/tools/webpack/common-config/site/getHtmlWebpackPlugin.ts +11 -0
- package/tools/webpack/modules.d.ts +6 -0
- package/tools/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.ts +102 -0
- package/tools/webpack/plugins/html-webpack-new-relic-plugin/LICENSE +21 -0
- package/tools/webpack/plugins/html-webpack-new-relic-plugin/README.md +7 -0
- package/tools/webpack/plugins/html-webpack-new-relic-plugin/index.js +3 -0
- package/tools/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +1 -0
- package/tools/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.ts +134 -0
- package/tools/webpack/plugins/paragon-webpack-plugin/index.ts +3 -0
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.ts +71 -0
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.ts +72 -0
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/index.ts +6 -0
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.ts +131 -0
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.ts +144 -0
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.ts +106 -0
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.ts +54 -0
- package/tools/webpack/types.ts +69 -0
- package/tools/webpack/utils/getLocalAliases.ts +65 -0
- package/tools/webpack/utils/getPublicPath.ts +3 -0
- package/tools/webpack/utils/getResolvedSiteConfigPath.ts +28 -0
- package/tools/webpack/utils/paragonUtils.ts +152 -0
- package/tools/webpack/webpack.config.build.ts +93 -0
- package/tools/webpack/webpack.config.dev.shell.ts +122 -0
- package/tools/webpack/webpack.config.dev.ts +90 -0
- package/tsconfig.json +23 -0
- package/types.ts +99 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { SlotOperation } from '../types';
|
|
3
|
+
import { isSlotOperationConditionSatisfied } from '../utils';
|
|
4
|
+
import { IFrameWidget } from './iframe';
|
|
5
|
+
import { IdentifiedWidget, WidgetAbsoluteOperation, WidgetAppendOperation, WidgetComponentProps, WidgetElementProps, WidgetIdentityProps, WidgetIFrameProps, WidgetInsertAfterOperation, WidgetInsertBeforeOperation, WidgetOperation, WidgetOperationTypes, WidgetOptionsOperation, WidgetPrependOperation, WidgetRemoveOperation, WidgetRendererOperation, WidgetRendererProps, WidgetReplaceOperation } from './types';
|
|
6
|
+
import WidgetProvider from './WidgetProvider';
|
|
7
|
+
|
|
8
|
+
export function isWidgetOperation(operation: SlotOperation): operation is WidgetOperation {
|
|
9
|
+
return Object.values(WidgetOperationTypes).includes(operation.op as WidgetOperationTypes);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function isWidgetAbsoluteOperation(operation: WidgetOperation): operation is WidgetAbsoluteOperation {
|
|
13
|
+
return operation.op === WidgetOperationTypes.APPEND || operation.op === WidgetOperationTypes.PREPEND;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isWidgetRelativeOperation(operation: WidgetOperation): operation is (WidgetInsertAfterOperation | WidgetInsertBeforeOperation) {
|
|
17
|
+
return operation.op === WidgetOperationTypes.INSERT_AFTER || operation.op === WidgetOperationTypes.INSERT_BEFORE;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isWidgetAppendOperation(operation: SlotOperation): operation is WidgetAppendOperation {
|
|
21
|
+
return isWidgetOperation(operation) && operation.op === WidgetOperationTypes.APPEND;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isWidgetInsertAfterOperation(operation: SlotOperation): operation is WidgetInsertAfterOperation {
|
|
25
|
+
return isWidgetOperation(operation) && operation.op === WidgetOperationTypes.INSERT_AFTER;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isWidgetInsertBeforeOperation(operation: SlotOperation): operation is WidgetInsertBeforeOperation {
|
|
29
|
+
return isWidgetOperation(operation) && operation.op === WidgetOperationTypes.INSERT_BEFORE;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isWidgetPrependOperation(operation: SlotOperation): operation is WidgetPrependOperation {
|
|
33
|
+
return isWidgetOperation(operation) && operation.op === WidgetOperationTypes.PREPEND;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isWidgetRemoveOperation(operation: SlotOperation): operation is WidgetRemoveOperation {
|
|
37
|
+
return isWidgetOperation(operation) && operation.op === WidgetOperationTypes.REMOVE;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isWidgetReplaceOperation(operation: SlotOperation): operation is WidgetReplaceOperation {
|
|
41
|
+
return isWidgetOperation(operation) && operation.op === WidgetOperationTypes.REPLACE;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isWidgetOptionsOperation(operation: SlotOperation): operation is WidgetOptionsOperation {
|
|
45
|
+
return isWidgetOperation(operation) && operation.op === WidgetOperationTypes.OPTIONS;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isWidgetRendererOperation(operation: SlotOperation): operation is WidgetRendererOperation {
|
|
49
|
+
return isWidgetOperation(operation) && hasWidgetRendererProps(operation);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isWidgetIdentityOperation(operation: SlotOperation): operation is (WidgetOperation & WidgetIdentityProps) {
|
|
53
|
+
return isWidgetOperation(operation) && hasWidgetIdentityProps(operation);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isWidgetIdentityRoleOperation(operation: SlotOperation): operation is (WidgetOperation & WidgetIdentityProps & { role: string }) {
|
|
57
|
+
return isWidgetIdentityOperation(operation) && hasWidgetIdentityRoleProps(operation);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createWidgetAppendOperation(id: string, slotId: string, element: ReactNode) {
|
|
61
|
+
let operation: WidgetOperation | undefined = undefined;
|
|
62
|
+
if (element) {
|
|
63
|
+
operation = {
|
|
64
|
+
slotId,
|
|
65
|
+
id,
|
|
66
|
+
op: WidgetOperationTypes.APPEND,
|
|
67
|
+
element
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return operation;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Widget Operation props helpers
|
|
75
|
+
|
|
76
|
+
function hasWidgetComponentProps(operation: WidgetOperation): operation is (WidgetOperation & WidgetComponentProps) {
|
|
77
|
+
return isWidgetOperation(operation) && 'component' in operation;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasWidgetElementProps(operation: WidgetOperation): operation is (WidgetOperation & WidgetElementProps) {
|
|
81
|
+
return isWidgetOperation(operation) && 'element' in operation;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hasWidgetIFrameProps(operation: WidgetOperation): operation is (WidgetOperation & WidgetIFrameProps) {
|
|
85
|
+
return isWidgetOperation(operation) && 'url' in operation && 'title' in operation;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hasWidgetRendererProps(operation: WidgetOperation): operation is (WidgetOperation & WidgetRendererProps) {
|
|
89
|
+
return hasWidgetComponentProps(operation) || hasWidgetElementProps(operation) || hasWidgetIFrameProps(operation);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function hasWidgetIdentityProps(operation: WidgetOperation): operation is (WidgetOperation & WidgetIdentityProps) {
|
|
93
|
+
return isWidgetOperation(operation) && 'id' in operation;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function hasWidgetIdentityRoleProps(operation: WidgetOperation): operation is (WidgetOperation & WidgetIdentityProps & { role: string }) {
|
|
97
|
+
return hasWidgetIdentityProps(operation) && 'role' in operation;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* An 'identified' widget just means a data structure with an ID and a ReactNode.
|
|
102
|
+
*/
|
|
103
|
+
function createIdentifiedWidget(operation: WidgetRendererOperation, componentProps?: Record<string, unknown>) {
|
|
104
|
+
let widget: ReactNode = null;
|
|
105
|
+
const { id } = operation;
|
|
106
|
+
if (hasWidgetComponentProps(operation)) {
|
|
107
|
+
widget = (
|
|
108
|
+
<operation.component {...componentProps} />
|
|
109
|
+
);
|
|
110
|
+
} else if (hasWidgetElementProps(operation)) {
|
|
111
|
+
widget = operation.element;
|
|
112
|
+
} else if (hasWidgetIFrameProps(operation)) {
|
|
113
|
+
widget = (
|
|
114
|
+
<IFrameWidget url={operation.url} title={operation.title} />
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
id,
|
|
120
|
+
node: (
|
|
121
|
+
<WidgetProvider key={id} slotId={operation.slotId} widgetId={operation.id} role={operation.role}>
|
|
122
|
+
{widget}
|
|
123
|
+
</WidgetProvider>
|
|
124
|
+
),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function findRelatedWidgetIndex(id: string, widgets: IdentifiedWidget[]) {
|
|
129
|
+
for (const candidateRelatedWidget of widgets) {
|
|
130
|
+
if (candidateRelatedWidget.id === id) {
|
|
131
|
+
return widgets.indexOf(candidateRelatedWidget);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function appendWidget(operation: WidgetAppendOperation, widgets: IdentifiedWidget[], componentProps?: Record<string, unknown>) {
|
|
138
|
+
const widget = createIdentifiedWidget(operation, componentProps);
|
|
139
|
+
widgets.push(widget);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function prependWidget(operation: WidgetPrependOperation, widgets: IdentifiedWidget[], componentProps?: Record<string, unknown>) {
|
|
143
|
+
const widget = createIdentifiedWidget(operation, componentProps);
|
|
144
|
+
widgets.unshift(widget);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function insertAfterWidget(operation: WidgetInsertAfterOperation, widgets: IdentifiedWidget[], componentProps?: Record<string, unknown>) {
|
|
148
|
+
const widget = createIdentifiedWidget(operation, componentProps);
|
|
149
|
+
const relatedIndex = findRelatedWidgetIndex(operation.relatedId, widgets);
|
|
150
|
+
if (relatedIndex !== null) {
|
|
151
|
+
widgets.splice(relatedIndex + 1, 0, widget);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function insertBeforeWidget(operation: WidgetInsertBeforeOperation, widgets: IdentifiedWidget[], componentProps?: Record<string, unknown>) {
|
|
156
|
+
const widget = createIdentifiedWidget(operation, componentProps);
|
|
157
|
+
const relatedIndex = findRelatedWidgetIndex(operation.relatedId, widgets);
|
|
158
|
+
if (relatedIndex !== null) {
|
|
159
|
+
widgets.splice(relatedIndex, 0, widget);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function replaceWidget(operation: WidgetReplaceOperation, widgets: IdentifiedWidget[], componentProps?: Record<string, unknown>) {
|
|
164
|
+
const widget = createIdentifiedWidget(operation, componentProps);
|
|
165
|
+
const relatedIndex = findRelatedWidgetIndex(operation.relatedId, widgets);
|
|
166
|
+
if (relatedIndex !== null) {
|
|
167
|
+
widgets.splice(relatedIndex, 1, widget);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function removeWidget(operation: WidgetRemoveOperation, widgets: IdentifiedWidget[]) {
|
|
172
|
+
const relatedIndex = findRelatedWidgetIndex(operation.relatedId, widgets);
|
|
173
|
+
if (relatedIndex !== null) {
|
|
174
|
+
widgets.splice(relatedIndex, 1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function createWidgets(operations: WidgetOperation[], componentProps?: Record<string, unknown>) {
|
|
179
|
+
const identifiedWidgets: IdentifiedWidget[] = [];
|
|
180
|
+
|
|
181
|
+
for (const operation of operations) {
|
|
182
|
+
if (isSlotOperationConditionSatisfied(operation)) {
|
|
183
|
+
if (isWidgetAppendOperation(operation)) {
|
|
184
|
+
appendWidget(operation, identifiedWidgets, componentProps);
|
|
185
|
+
} else if (isWidgetPrependOperation(operation)) {
|
|
186
|
+
prependWidget(operation, identifiedWidgets, componentProps);
|
|
187
|
+
} else if (isWidgetInsertAfterOperation(operation)) {
|
|
188
|
+
insertAfterWidget(operation, identifiedWidgets, componentProps);
|
|
189
|
+
} else if (isWidgetInsertBeforeOperation(operation)) {
|
|
190
|
+
insertBeforeWidget(operation, identifiedWidgets, componentProps);
|
|
191
|
+
} else if (isWidgetReplaceOperation(operation)) {
|
|
192
|
+
replaceWidget(operation, identifiedWidgets, componentProps);
|
|
193
|
+
} else if (isWidgetRemoveOperation(operation)) {
|
|
194
|
+
removeWidget(operation, identifiedWidgets);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Remove the 'id' metadata and return just the nodes.
|
|
200
|
+
return identifiedWidgets.map(widget => widget.node);
|
|
201
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #### Import members from **@openedx/frontend-base**
|
|
3
|
+
*
|
|
4
|
+
* The PubSub module is a thin wrapper around the base functionality of
|
|
5
|
+
* [PubSubJS](https://github.com/mroderick/PubSubJS). For the sake of simplicity and not relying
|
|
6
|
+
* too heavily on implementation-specific features, it maintains a fairly simple API (subscribe,
|
|
7
|
+
* unsubscribe, and publish).
|
|
8
|
+
*
|
|
9
|
+
* Publish/Subscribe events should be used mindfully, especially in relation to application UI
|
|
10
|
+
* frameworks like React. Given React's unidirectional data flow and prop/state management
|
|
11
|
+
* capabilities, using a pub/sub mechanism is at odds with that framework's best practices.
|
|
12
|
+
*
|
|
13
|
+
* That said, we use pub/sub in our application initialization sequence to allow applications to
|
|
14
|
+
* hook into the initialization lifecycle, and we also use them to publish when the application
|
|
15
|
+
* state has changed, i.e., when the config document or user's authentication state have changed.
|
|
16
|
+
*
|
|
17
|
+
* @module PubSub
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// export const {
|
|
21
|
+
// subscribe,
|
|
22
|
+
// unsubscribe,
|
|
23
|
+
// publish,
|
|
24
|
+
// } = PubSub;
|
|
25
|
+
|
|
26
|
+
type CallbackFunction = (topic: string, data?: any) => void;
|
|
27
|
+
|
|
28
|
+
let subscriptions: Record<string, CallbackFunction[]> = {};
|
|
29
|
+
|
|
30
|
+
export function subscribe(topic: string, callback) {
|
|
31
|
+
if (subscriptions[topic] === undefined) {
|
|
32
|
+
subscriptions[topic] = [];
|
|
33
|
+
}
|
|
34
|
+
subscriptions[topic].push(callback);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function publish(topic: string, data?: any) {
|
|
38
|
+
if (subscriptions[topic] === undefined) {
|
|
39
|
+
subscriptions[topic] = [];
|
|
40
|
+
}
|
|
41
|
+
subscriptions[topic].forEach(callback => {
|
|
42
|
+
if (data) {
|
|
43
|
+
callback(topic, data);
|
|
44
|
+
} else {
|
|
45
|
+
callback(topic);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function unsubscribe(topic: string, callback: CallbackFunction) {
|
|
51
|
+
if (subscriptions[topic] === undefined) {
|
|
52
|
+
subscriptions[topic] = [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
subscriptions[topic] = subscriptions[topic].filter((value) => value !== callback);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function clearAllSubscriptions() {
|
|
59
|
+
subscriptions = {};
|
|
60
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #### Import members from **@openedx/frontend-base** Testing
|
|
3
|
+
* The testing module provides helpers for writing tests in Jest.
|
|
4
|
+
*
|
|
5
|
+
* @module Testing
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { default as initializeMockApp } from './initializeMockApp';
|
|
9
|
+
export { default as mockMessages } from './mockMessages';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import siteConfig from 'site.config';
|
|
2
|
+
|
|
3
|
+
import { configureAnalytics, MockAnalyticsService } from '../analytics';
|
|
4
|
+
import { configureAuth, MockAuthService, setAuthenticatedUser } from '../auth';
|
|
5
|
+
import { getSiteConfig, mergeSiteConfig } from '../config';
|
|
6
|
+
import { configureI18n } from '../i18n';
|
|
7
|
+
import { configureLogging, MockLoggingService } from '../logging';
|
|
8
|
+
import mockMessages from './mockMessages';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Initializes a mock application for component testing. The mock application includes
|
|
12
|
+
* mock analytics, auth, and logging services, and the real i18n service.
|
|
13
|
+
*
|
|
14
|
+
* See MockAnalyticsService, MockAuthService, and MockLoggingService for mock implementation
|
|
15
|
+
* details. For the most part, the analytics and logging services just implement their functions
|
|
16
|
+
* with jest.fn() and do nothing else, whereas the MockAuthService actually has some behavior
|
|
17
|
+
* implemented, it just doesn't make any HTTP calls.
|
|
18
|
+
*
|
|
19
|
+
* Note that this mock application is not sufficient for testing the full application lifecycle or
|
|
20
|
+
* initialization callbacks/custom handlers as described in the 'initialize' function's
|
|
21
|
+
* documentation. It exists merely to set up the mock services that components themselves tend to
|
|
22
|
+
* interact with most often. It could be extended to allow for setting up custom handlers fairly
|
|
23
|
+
* easily, as this functionality would be more-or-less identical to what the real initialize
|
|
24
|
+
* function does.
|
|
25
|
+
*
|
|
26
|
+
* Example:
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* import { initializeMockApp, logInfo } from '@openedx/frontend-base';
|
|
30
|
+
*
|
|
31
|
+
* describe('initializeMockApp', () => {
|
|
32
|
+
* it('mocks things correctly', () => {
|
|
33
|
+
* const { loggingService } = initializeMockApp();
|
|
34
|
+
* logInfo('test', {});
|
|
35
|
+
* expect(loggingService.logInfo).toHaveBeenCalledWith('test', {});
|
|
36
|
+
* });
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} [options]
|
|
41
|
+
* @param {*} [options.messages] A i18n-compatible messages object, or an array of such objects. If
|
|
42
|
+
* an array is provided, duplicate keys are resolved with the last-one-in winning.
|
|
43
|
+
* @param {UserData|null} [options.authenticatedUser] A UserData object representing the
|
|
44
|
+
* authenticated user. This is passed directly to MockAuthService.
|
|
45
|
+
* @memberof module:Testing
|
|
46
|
+
*/
|
|
47
|
+
export default function initializeMockApp({
|
|
48
|
+
messages = mockMessages,
|
|
49
|
+
authenticatedUser = null,
|
|
50
|
+
} = {}) {
|
|
51
|
+
const config = siteConfig;
|
|
52
|
+
mergeSiteConfig(config);
|
|
53
|
+
|
|
54
|
+
const loggingService = configureLogging(MockLoggingService, {
|
|
55
|
+
config: getSiteConfig(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const authService = configureAuth(MockAuthService, {
|
|
59
|
+
config: getSiteConfig(),
|
|
60
|
+
loggingService,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
setAuthenticatedUser(authenticatedUser);
|
|
64
|
+
|
|
65
|
+
const analyticsService = configureAnalytics(MockAnalyticsService, {
|
|
66
|
+
config: getSiteConfig(),
|
|
67
|
+
httpClient: authService.getAuthenticatedHttpClient(),
|
|
68
|
+
loggingService,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// The i18n service configureI18n function has no return value, since there isn't a service class.
|
|
72
|
+
configureI18n({
|
|
73
|
+
messages,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
analyticsService,
|
|
78
|
+
authService,
|
|
79
|
+
loggingService,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An empty messages object suitable for fulfilling the i18n service's contract.
|
|
3
|
+
* @memberof module:Testing
|
|
4
|
+
*/
|
|
5
|
+
const messages = {
|
|
6
|
+
ar: {},
|
|
7
|
+
'es-419': {},
|
|
8
|
+
fa: {},
|
|
9
|
+
'fa-ir': {},
|
|
10
|
+
fr: {},
|
|
11
|
+
'zh-cn': {},
|
|
12
|
+
ca: {},
|
|
13
|
+
he: {},
|
|
14
|
+
id: {},
|
|
15
|
+
'ko-kr': {},
|
|
16
|
+
pl: {},
|
|
17
|
+
'pt-br': {},
|
|
18
|
+
ru: {},
|
|
19
|
+
th: {},
|
|
20
|
+
uk: {},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default messages;
|
package/runtime/utils.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #### Import members from **@openedx/frontend-base**
|
|
3
|
+
*
|
|
4
|
+
* @module Utilities
|
|
5
|
+
*/
|
|
6
|
+
import camelCase from 'lodash.camelcase';
|
|
7
|
+
import snakeCase from 'lodash.snakecase';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This is the underlying function used by camelCaseObject, snakeCaseObject, and convertKeyNames
|
|
11
|
+
* above.
|
|
12
|
+
*
|
|
13
|
+
* Given an object (or array) and a modification function, will perform the function on each key it
|
|
14
|
+
* encounters on the object and its tree of children.
|
|
15
|
+
*
|
|
16
|
+
* The modification function must take a string as an argument and returns a string.
|
|
17
|
+
*
|
|
18
|
+
* Example:
|
|
19
|
+
*
|
|
20
|
+
* ```
|
|
21
|
+
* (key) => {
|
|
22
|
+
* if (key === 'edX') {
|
|
23
|
+
* return 'Open edX';
|
|
24
|
+
* }
|
|
25
|
+
* return key;
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* This function will turn any key that matches 'edX' into 'Open edX'. All other keys will be
|
|
30
|
+
* passed through unmodified.
|
|
31
|
+
*
|
|
32
|
+
* Can accept arrays as well as objects, and will perform its conversion on any objects it finds in
|
|
33
|
+
* the array.
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} object
|
|
36
|
+
* @param {function} modify
|
|
37
|
+
* @returns {Object}
|
|
38
|
+
*/
|
|
39
|
+
export function modifyObjectKeys(object, modify) {
|
|
40
|
+
// If the passed in object is not an Object, return it.
|
|
41
|
+
if (
|
|
42
|
+
object === undefined
|
|
43
|
+
|| object === null
|
|
44
|
+
|| (typeof object !== 'object' && !Array.isArray(object))
|
|
45
|
+
) {
|
|
46
|
+
return object;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (Array.isArray(object)) {
|
|
50
|
+
return object.map(value => modifyObjectKeys(value, modify));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Otherwise, process all its keys.
|
|
54
|
+
const result = {};
|
|
55
|
+
Object.entries(object).forEach(([key, value]) => {
|
|
56
|
+
result[modify(key)] = modifyObjectKeys(value, modify);
|
|
57
|
+
});
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Performs a deep conversion to camelCase on all keys in the provided object and its tree of
|
|
63
|
+
* children. Uses [lodash.camelcase](https://lodash.com/docs/4.17.15#camelCase) on each key. This
|
|
64
|
+
* is commonly used to convert snake_case keys in models from a backend server into camelCase keys
|
|
65
|
+
* for use in the JavaScript client.
|
|
66
|
+
*
|
|
67
|
+
* Can accept arrays as well as objects, and will perform its conversion on any objects it finds in
|
|
68
|
+
* the array.
|
|
69
|
+
*
|
|
70
|
+
* @param {Array|Object} object
|
|
71
|
+
* @returns {Array|Object}
|
|
72
|
+
*/
|
|
73
|
+
export function camelCaseObject(object) {
|
|
74
|
+
return modifyObjectKeys(object, camelCase);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Performs a deep conversion to snake_case on all keys in the provided object and its tree of
|
|
79
|
+
* children. Uses [lodash.snakecase](https://lodash.com/docs/4.17.15#snakeCase) on each key. This
|
|
80
|
+
* is commonly used to convert camelCase keys from the JavaScript app into snake_case keys expected
|
|
81
|
+
* by backend servers.
|
|
82
|
+
*
|
|
83
|
+
* Can accept arrays as well as objects, and will perform its conversion on any objects it finds in
|
|
84
|
+
* the array.
|
|
85
|
+
*
|
|
86
|
+
* @param {Array|Object} object
|
|
87
|
+
* @returns {Array|Object}
|
|
88
|
+
*/
|
|
89
|
+
export function snakeCaseObject(object) {
|
|
90
|
+
return modifyObjectKeys(object, snakeCase);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Given a map of key-value pairs, performs a deep conversion key names in the specified object
|
|
95
|
+
* _from_ the key _to_ the value. This is useful for updating names in an API request to the names
|
|
96
|
+
* used throughout a client application if they happen to differ. It can also be used in the
|
|
97
|
+
* reverse - formatting names from the client application to names expected by an API.
|
|
98
|
+
*
|
|
99
|
+
* ```
|
|
100
|
+
* import { convertKeyNames } from '@openedx/frontend-base';
|
|
101
|
+
*
|
|
102
|
+
* // This object can be of any shape or depth with subobjects/arrays.
|
|
103
|
+
* const myObject = {
|
|
104
|
+
* myKey: 'my value',
|
|
105
|
+
* }
|
|
106
|
+
*
|
|
107
|
+
* const result = convertKeyNames(myObject, { myKey: 'their_key' });
|
|
108
|
+
*
|
|
109
|
+
* console.log(result) // { their_key: 'my value' }
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* Can accept arrays as well as objects, and will perform its conversion on any objects it finds in
|
|
113
|
+
* the array.
|
|
114
|
+
*
|
|
115
|
+
* @param {Array|Object} object
|
|
116
|
+
* @param {Object} nameMap
|
|
117
|
+
* @returns {Array|Object}
|
|
118
|
+
*/
|
|
119
|
+
export function convertKeyNames(object, nameMap) {
|
|
120
|
+
const transformer = key => (nameMap[key] === undefined ? key : nameMap[key]);
|
|
121
|
+
|
|
122
|
+
return modifyObjectKeys(object, transformer);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* *Deprecated*: A method which converts the supplied query string into an object of
|
|
127
|
+
* key-value pairs and returns it. Defaults to the current query string - should perform like
|
|
128
|
+
* [window.searchParams](https://developer.mozilla.org/en-US/docs/Web/API/URL/searchParams)
|
|
129
|
+
*
|
|
130
|
+
* @deprecated
|
|
131
|
+
* @param {string} [search=global.location.search]
|
|
132
|
+
* @returns {Object}
|
|
133
|
+
*/
|
|
134
|
+
export function getQueryParameters(search = global.location.search) {
|
|
135
|
+
const keyValueFragments = search
|
|
136
|
+
.slice(search.indexOf('?') + 1)
|
|
137
|
+
.split('&')
|
|
138
|
+
.filter(hash => hash !== '');
|
|
139
|
+
|
|
140
|
+
return keyValueFragments.reduce((params, keyValueFragment) => {
|
|
141
|
+
const split = keyValueFragment.indexOf('=');
|
|
142
|
+
const key = keyValueFragment.slice(0, split);
|
|
143
|
+
const value = keyValueFragment.slice(split + 1);
|
|
144
|
+
return Object.assign(params, { [key]: decodeURIComponent(value) });
|
|
145
|
+
}, {});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function isValidVariableName(name) {
|
|
149
|
+
// Check if the name is a string and not empty
|
|
150
|
+
if (typeof name !== 'string' || name === '') {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check if the first character is a letter, underscore, or dollar sign
|
|
155
|
+
if (!/^[a-zA-Z_$]/.test(name)) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check if the name contains only letters, numbers, underscores, or dollar signs
|
|
160
|
+
if (!/^[a-zA-Z0-9_$]+$/.test(name)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check if the name is a reserved keyword
|
|
165
|
+
const reservedKeywords = [
|
|
166
|
+
'break', 'case', 'catch', 'class', 'const', 'continue',
|
|
167
|
+
'debugger', 'default', 'delete', 'do', 'else', 'enum',
|
|
168
|
+
'export', 'extends', 'false', 'finally', 'for', 'function',
|
|
169
|
+
'if', 'import', 'in', 'instanceof', 'new', 'null', 'return',
|
|
170
|
+
'super', 'switch', 'this', 'throw', 'true', 'try',
|
|
171
|
+
'typeof', 'var', 'void', 'while', 'with', 'yield'
|
|
172
|
+
];
|
|
173
|
+
if (reservedKeywords.includes(name)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Slot } from '../runtime';
|
|
2
|
+
import DefaultMain from './DefaultMain';
|
|
3
|
+
|
|
4
|
+
export default function DefaultLayout() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="d-flex flex-column min-vh-100">
|
|
7
|
+
<div className="flex-grow-0 flex-shrink-0">
|
|
8
|
+
<Slot id="org.openedx.frontend.slot.header.main.v1" />
|
|
9
|
+
</div>
|
|
10
|
+
<div id="main-content" className="flex-grow-1">
|
|
11
|
+
<Slot id="org.openedx.frontend.slot.content.main.v1" layout={DefaultMain} />
|
|
12
|
+
</div>
|
|
13
|
+
<div className="flex-grow-0 flex-shrink-0">
|
|
14
|
+
<Slot id="org.openedx.frontend.slot.footer.main.v1" />
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
package/shell/Logo.tsx
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IntlProvider } from 'react-intl';
|
|
2
|
+
import { Hyperlink, Image } from '@openedx/paragon';
|
|
3
|
+
|
|
4
|
+
interface LogoProps {
|
|
5
|
+
imageUrl?: string,
|
|
6
|
+
destinationUrl?: string,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function Logo({
|
|
10
|
+
imageUrl = 'https://edx-cdn.org/v3/default/logo.svg',
|
|
11
|
+
destinationUrl
|
|
12
|
+
}: LogoProps) {
|
|
13
|
+
const image = (
|
|
14
|
+
<Image src={imageUrl} style={{ maxHeight: '2rem' }} />
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
if (destinationUrl === undefined) {
|
|
18
|
+
return image;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<IntlProvider locale="en">
|
|
23
|
+
<Hyperlink destination={destinationUrl} className="p-0">
|
|
24
|
+
{image}
|
|
25
|
+
</Hyperlink>
|
|
26
|
+
</IntlProvider>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { defineMessages } from '../runtime';
|
|
2
|
+
|
|
3
|
+
const messages = defineMessages({
|
|
4
|
+
'header.user.menu.dashboard': {
|
|
5
|
+
id: 'header.user.menu.dashboard',
|
|
6
|
+
defaultMessage: 'Dashboard',
|
|
7
|
+
description: 'Link to the user dashboard',
|
|
8
|
+
},
|
|
9
|
+
'header.user.menu.logout': {
|
|
10
|
+
id: 'header.user.menu.logout',
|
|
11
|
+
defaultMessage: 'Logout',
|
|
12
|
+
description: 'Logout link',
|
|
13
|
+
},
|
|
14
|
+
'header.user.menu.profile': {
|
|
15
|
+
id: 'header.user.menu.profile',
|
|
16
|
+
defaultMessage: 'Profile',
|
|
17
|
+
description: 'Link to the user profile',
|
|
18
|
+
},
|
|
19
|
+
'header.user.menu.account': {
|
|
20
|
+
id: 'header.user.menu.account',
|
|
21
|
+
defaultMessage: 'Account',
|
|
22
|
+
description: 'Link to account settings',
|
|
23
|
+
},
|
|
24
|
+
'header.user.menu.order.history': {
|
|
25
|
+
id: 'header.user.menu.order.history',
|
|
26
|
+
defaultMessage: 'Order History',
|
|
27
|
+
description: 'Link to order history',
|
|
28
|
+
},
|
|
29
|
+
'header.user.menu.login': {
|
|
30
|
+
id: 'header.user.menu.login',
|
|
31
|
+
defaultMessage: 'Login',
|
|
32
|
+
description: 'Login link',
|
|
33
|
+
},
|
|
34
|
+
'header.user.menu.register': {
|
|
35
|
+
id: 'header.user.menu.register',
|
|
36
|
+
defaultMessage: 'Sign Up',
|
|
37
|
+
description: 'Link to registration',
|
|
38
|
+
},
|
|
39
|
+
skipNavLink: {
|
|
40
|
+
id: 'header.navigation.skipNavLink',
|
|
41
|
+
defaultMessage: 'Skip to main content.',
|
|
42
|
+
description: 'A link used by screen readers to allow users to skip to the main content of the page.',
|
|
43
|
+
},
|
|
44
|
+
footerPoweredBy: {
|
|
45
|
+
id: 'footer.powered.by',
|
|
46
|
+
defaultMessage: 'Powered by Open edX',
|
|
47
|
+
description: 'Alt text for the \'Powered by Open edX\' logo displayed in the footer.',
|
|
48
|
+
},
|
|
49
|
+
footerTrademarkNotice: {
|
|
50
|
+
id: 'footer.trademark.notice',
|
|
51
|
+
defaultMessage: 'edX and Open edX are registered trademarks of edX LLC.',
|
|
52
|
+
description: 'A legal notice that "edX LLC" owns the trademarks on "edX" and "Open edX". Please do not translate these three proper names.'
|
|
53
|
+
},
|
|
54
|
+
'footer.revealLinks.more': {
|
|
55
|
+
id: 'footer.revealLinks.more',
|
|
56
|
+
defaultMessage: 'More',
|
|
57
|
+
description: 'Text for a button that reveals more links and content in the footer.',
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export default messages;
|