├── .dockerignore
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
└── workflows
│ ├── ci.yaml
│ └── docker.yaml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── Dockerfile
├── LICENSE
├── README.md
├── a11y.txt
├── changeBackend.js
├── config.template
├── config
├── env.js
├── getHttpsConfig.js
├── jest
│ ├── babelTransform.js
│ ├── cssTransform.js
│ └── fileTransform.js
├── modules.js
├── paths.js
├── pnpTs.js
├── webpack.config.js
└── webpackDevServer.config.js
├── getDefaultConfig.js
├── jsconfig.json
├── nginx.conf
├── package.json
├── public
├── favicon.ico
├── index.html
├── manifest.json
└── page-wrapper.css
├── scripts
├── build.js
├── start.js
└── test.js
├── src
├── App.css
├── App.js
├── assets
│ ├── cc-fonts
│ │ ├── OpenSans-Medium.ttf
│ │ ├── calibri.ttf
│ │ ├── helvetica.ttf
│ │ ├── tahoma.ttf
│ │ └── times-new-roman.ttf
│ └── images
│ │ ├── box-logo.svg
│ │ ├── brand-text-dark.png
│ │ ├── brand-text.png
│ │ ├── epub-example-bottom.png
│ │ ├── epub-example-top.png
│ │ ├── index.js
│ │ ├── kaltura-logo.png
│ │ ├── logo-outline.png
│ │ ├── logo-outline.svg
│ │ ├── logo.png
│ │ ├── offering-poster.jpg
│ │ └── video-poster.jpg
├── azure-app-insights
│ ├── AppInsightsProvider.js
│ ├── index.js
│ └── service.js
├── components
│ ├── CTCookieAgreement
│ │ ├── CookieOption.js
│ │ ├── index.js
│ │ ├── index.scss
│ │ └── policies
│ │ │ ├── AcceptableUsePolicy.js
│ │ │ ├── CookiePolicy.js
│ │ │ └── index.js
│ ├── CTEPubListScreen
│ │ ├── components
│ │ │ ├── EPubCTList
│ │ │ │ ├── EPubCTList.js
│ │ │ │ ├── EPubCTListItem.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── EPubList.js
│ │ │ ├── EPubPoster.js
│ │ │ ├── NewEPubButton.js
│ │ │ ├── NewEPubModal.js
│ │ │ ├── NewEPubModal.test.js
│ │ │ ├── Wrappers.js
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── controllers
│ │ │ ├── EPubListController.js
│ │ │ ├── helpers.js
│ │ │ └── index.js
│ │ └── index.js
│ ├── CTImageMagnifer
│ │ └── index.js
│ ├── CTImagePickerModal
│ │ ├── ImagePickerModalActions.js
│ │ ├── ImagePreview.js
│ │ ├── ImagesTab.js
│ │ ├── UploadTab.js
│ │ ├── VideoTab.js
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTMarkdown
│ │ ├── MarkdownEditor
│ │ │ ├── ActionButtonGroup.js
│ │ │ ├── MDTextArea
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── MDToolBar
│ │ │ │ ├── BoldTextTrigger.js
│ │ │ │ ├── BulletedListTrigger.js
│ │ │ │ ├── CodeTrigger.js
│ │ │ │ ├── HeaderTextTrigger.js
│ │ │ │ ├── InsertLinkTrigger.js
│ │ │ │ ├── InsertMedia
│ │ │ │ │ ├── InsertMediaTrigger.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── ItalicTextTrigger.js
│ │ │ │ ├── MDToolButton.js
│ │ │ │ ├── MathBlockTrigger.js
│ │ │ │ ├── NumberedListTrigger.js
│ │ │ │ ├── QuoteTrigger.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── ace
│ │ │ │ ├── ace-controller.js
│ │ │ │ └── ace-shortcut.js
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── MarkdownPreviewer
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ └── index.js
│ ├── CTPlayer
│ │ ├── Player
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── Range
│ │ │ ├── RangeControlBar
│ │ │ │ ├── RangeInput.js
│ │ │ │ ├── RangePlayButton.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── RangeSlider.js
│ │ │ ├── RangeTimeLabel.js
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── Wrapper
│ │ │ ├── InteractiveLayer
│ │ │ │ ├── ActionBar
│ │ │ │ │ ├── LiveTranscriptDownload
│ │ │ │ │ │ └── index.js
│ │ │ │ │ ├── Screenshot
│ │ │ │ │ │ ├── ScreenshotPopup.js
│ │ │ │ │ │ └── index.js
│ │ │ │ │ ├── Share
│ │ │ │ │ │ ├── SharePopup.js
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── index.scss
│ │ │ │ │ ├── ShortcutButton.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── ActionButton
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── ControlBar
│ │ │ │ │ ├── Progress
│ │ │ │ │ │ ├── SeekTimeLabel.js
│ │ │ │ │ │ ├── SliderTimeLabel.js
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── index.scss
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── SettingsMenu
│ │ │ │ │ ├── CCBackgroundColorsMenu.js
│ │ │ │ │ ├── CCFontColorsMenu.js
│ │ │ │ │ ├── CCFontSizesMenu.js
│ │ │ │ │ ├── CCOpacityMenu.js
│ │ │ │ │ ├── CCOptionsMenu.js
│ │ │ │ │ ├── ClosedCaptionMenu.js
│ │ │ │ │ ├── LiveCaptionMenu.js
│ │ │ │ │ ├── LiveCaptionTrackSelection.js
│ │ │ │ │ ├── MenuItem.js
│ │ │ │ │ ├── PlaybackRateMenu.js
│ │ │ │ │ ├── RootMenu.js
│ │ │ │ │ ├── ScreenModesMenu.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── NonInteractiveLayer
│ │ │ │ ├── ErrorWrapper.js
│ │ │ │ ├── EventVisualPopup.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── controllers
│ │ │ ├── constants
│ │ │ │ ├── LanguageConstants.js
│ │ │ │ ├── PlayerConstants.js
│ │ │ │ └── PlayerIDs.js
│ │ │ ├── helpers.js
│ │ │ └── index.js
│ │ └── index.js
│ ├── CTPlaylistIcon
│ │ ├── index.js
│ │ └── index.scss
│ ├── Cards
│ │ ├── CourseCard
│ │ │ ├── Card.js
│ │ │ ├── CardList.js
│ │ │ ├── Placeholders.js
│ │ │ ├── index.js
│ │ │ ├── index.scss
│ │ │ └── parse-course.js
│ │ ├── MediaCard
│ │ │ ├── Card.js
│ │ │ ├── Poster
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── index.js
│ │ │ ├── index.scss
│ │ │ └── parse-media.js
│ │ └── index.js
│ ├── CircularProgress
│ │ └── index.js
│ ├── CopyButton
│ │ └── index.js
│ ├── Image
│ │ └── index.js
│ ├── InfoAndListLayout
│ │ ├── Info.js
│ │ ├── List.js
│ │ ├── index.js
│ │ └── index.scss
│ ├── Modals
│ │ ├── index.js
│ │ └── index.scss
│ ├── SelectCtrlButton
│ │ └── index.js
│ ├── Shortcut
│ │ ├── ShortcutModal.js
│ │ ├── ShortcutTable.js
│ │ ├── index.js
│ │ └── index.scss
│ ├── SignInPrompt
│ │ ├── index.js
│ │ └── index.scss
│ ├── index.css
│ ├── index.js
│ └── stylesheets
│ │ ├── csstrans.playlist-video.css
│ │ ├── ct-prompt.css
│ │ ├── general.animation.css
│ │ ├── general.class.css
│ │ ├── general.transition.css
│ │ ├── general.vars.css
│ │ ├── semantic-ui-css
│ │ ├── semantic-ui.min.css
│ │ └── themes
│ │ │ └── default
│ │ │ └── assets
│ │ │ ├── fonts
│ │ │ ├── brand-icons.eot
│ │ │ ├── brand-icons.svg
│ │ │ ├── brand-icons.ttf
│ │ │ ├── brand-icons.woff
│ │ │ ├── brand-icons.woff2
│ │ │ ├── icons.eot
│ │ │ ├── icons.otf
│ │ │ ├── icons.svg
│ │ │ ├── icons.ttf
│ │ │ ├── icons.woff
│ │ │ ├── icons.woff2
│ │ │ ├── outline-icons.eot
│ │ │ ├── outline-icons.svg
│ │ │ ├── outline-icons.ttf
│ │ │ ├── outline-icons.woff
│ │ │ └── outline-icons.woff2
│ │ │ └── images
│ │ │ └── flags.png
│ │ ├── sidebar.css
│ │ └── sk-loader.css
├── docs
│ ├── api-types.js
│ ├── component-usage
│ │ ├── ct-form
│ │ │ └── usage.mdx
│ │ └── index.js
│ ├── index.js
│ └── layouts
│ │ ├── MDXDocsContainer.js
│ │ └── index.js
├── entities
│ ├── EPubs
│ │ ├── html-converters.js
│ │ ├── index.js
│ │ ├── structs
│ │ │ ├── EPubChapterData.js
│ │ │ ├── EPubData.js
│ │ │ ├── EPubImageData.js
│ │ │ ├── EPubSubChapterData.js
│ │ │ └── index.js
│ │ └── utils.js
│ ├── Entity.js
│ ├── ErrorTypes.js
│ ├── Playlists
│ │ ├── Playlist.js
│ │ ├── PlaylistTypes.js
│ │ └── index.js
│ ├── SourceTypes.js
│ ├── UserEvent
│ │ ├── UserEvent.js
│ │ ├── UserEventType.js
│ │ └── index.js
│ └── index.js
├── hooks
│ ├── index.js
│ ├── useArray.js
│ ├── useCTDocTitle.js
│ ├── useCustomizedButton.js
│ ├── useInput.js
│ ├── useLoaded.js
│ └── useWindowSize.js
├── index.js
├── layout
│ ├── CTButtons
│ │ ├── CTFileButton
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── index.js
│ │ └── styles.js
│ ├── CTDnd
│ │ ├── DNDContext
│ │ │ └── index.js
│ │ ├── DNDItem
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── DNDList.js
│ │ └── index.js
│ ├── CTDropdown
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTErrorWrapper
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTFilter
│ │ ├── DefaultFilter.js
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTFooter
│ │ └── index.js
│ ├── CTForm
│ │ ├── AutoComplete
│ │ │ └── index.js
│ │ ├── Checkbox
│ │ │ └── index.js
│ │ ├── ExampleUsage.js
│ │ ├── Form
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── FormHeading
│ │ │ └── index.js
│ │ ├── FormHelp
│ │ │ └── index.js
│ │ ├── FormRow
│ │ │ └── index.js
│ │ ├── Input
│ │ │ └── index.js
│ │ ├── Radio
│ │ │ └── index.js
│ │ ├── Select
│ │ │ └── index.js
│ │ ├── Upload
│ │ │ ├── UploadBase.js
│ │ │ ├── UploadButton.js
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ └── index.js
│ ├── CTFragment
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTHeading
│ │ ├── create-props.js
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTHorizontalScroll
│ │ ├── ArrowButton.js
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTLayout
│ │ ├── NavSidebarTrigger.js
│ │ ├── create-props.js
│ │ ├── default-sidebar-items
│ │ │ ├── admin-items.js
│ │ │ ├── component-api-items.js
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── index.scss
│ │ └── responsive.js
│ ├── CTList
│ │ ├── CTList.js
│ │ ├── CTListItem.js
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTLoadable
│ │ └── index.js
│ ├── CTLoader
│ │ ├── index.css
│ │ └── index.js
│ ├── CTMetaTags
│ │ └── index.js
│ ├── CTModal
│ │ ├── Confirmation.js
│ │ ├── Modal.js
│ │ └── index.js
│ ├── CTNavHeader
│ │ ├── CTBrand.js
│ │ ├── NavHeaderMenu
│ │ │ ├── MenuTrigger.js
│ │ │ ├── ProfileInfo.js
│ │ │ ├── ProfileMenu.js
│ │ │ ├── SignInButton.js
│ │ │ ├── SignInMenu.js
│ │ │ ├── index.js
│ │ │ └── styles.js
│ │ ├── NavHeaderSearch
│ │ │ ├── NavHeaderSearchBar.js
│ │ │ ├── NavHeaderSearchResult.js
│ │ │ ├── SearchCard.js
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── NavHeaderTabPanel
│ │ │ ├── NavHeaderTab.js
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── create-props.js
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTNavSidebar
│ │ ├── SidebarItem
│ │ │ ├── SidebarSubItem.js
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── SidebarNavItems.js
│ │ ├── create-props.js
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTPopoverLabel
│ │ └── index.js
│ ├── CTScrollArea
│ │ ├── index.js
│ │ └── index.scss
│ ├── CTText
│ │ ├── Paragraph.js
│ │ ├── Text.js
│ │ ├── index.js
│ │ └── index.scss
│ ├── index.js
│ └── tools
│ │ ├── elem-selectors.js
│ │ └── index.js
├── model
│ └── global.js
├── redux
│ ├── example
│ │ ├── example.action.types.js
│ │ ├── example.actions.js
│ │ ├── example.reducers.js
│ │ ├── example.state.js
│ │ └── index.js
│ ├── media-settings
│ │ └── transcriptions
│ │ │ ├── index.js
│ │ │ ├── trans.action.types.js
│ │ │ ├── trans.actions.js
│ │ │ ├── trans.reducers.js
│ │ │ └── trans.state.js
│ ├── redux-creators.js
│ └── redux-provider.js
├── screens
│ ├── Admin
│ │ ├── Alerts
│ │ │ ├── AlertMesgs.js
│ │ │ ├── index.css
│ │ │ └── index.js
│ │ ├── Components.jsx
│ │ ├── Courses
│ │ │ ├── CourseEditing.jsx
│ │ │ └── index.js
│ │ ├── Departments
│ │ │ ├── DepartmentEditing.jsx
│ │ │ └── index.js
│ │ ├── EventLogs
│ │ │ ├── DownloadLogs.jsx
│ │ │ └── index.js
│ │ ├── Instructors
│ │ │ ├── InstructorEditing.jsx
│ │ │ ├── InstructorList.jsx
│ │ │ ├── InstructorList.test.js
│ │ │ └── index.js
│ │ ├── LoginAsUser
│ │ │ └── index.js
│ │ ├── Terms
│ │ │ ├── TermEditing.jsx
│ │ │ └── index.js
│ │ ├── Universities
│ │ │ ├── UniversityEditing.jsx
│ │ │ └── index.js
│ │ ├── helpers.js
│ │ ├── index.css
│ │ ├── index.js
│ │ └── tabs.js
│ ├── Analytics
│ │ └── index.js
│ ├── Asl
│ │ ├── components
│ │ │ └── AslTable
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ ├── index.js
│ │ └── model.js
│ ├── Authentication
│ │ ├── AuthCallback.js
│ │ ├── SignIn
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ └── index.js
│ ├── ComponentAPI
│ │ ├── docs-selector.js
│ │ └── index.js
│ ├── Course
│ │ ├── components
│ │ │ ├── CourseInfo
│ │ │ │ ├── ActionButtons
│ │ │ │ │ ├── CourseAnalyticsButton.js
│ │ │ │ │ ├── CourseSettingsButton.js
│ │ │ │ │ ├── InstModeCheckBox.js
│ │ │ │ │ ├── StarButton.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── Playlists
│ │ │ │ ├── NewPlaylistButton.js
│ │ │ │ ├── PlaylistItem.js
│ │ │ │ ├── PlaylistsErrorWrapper.js
│ │ │ │ ├── PlaylistsView.js
│ │ │ │ ├── VideosView.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── model.js
│ │ └── util.js
│ ├── EPub
│ │ ├── components
│ │ │ ├── ChapterEditButton
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── ChapterImage
│ │ │ │ ├── ImageDescription.js
│ │ │ │ ├── ImageWrapper.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── ChapterText
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── ChapterTitle
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── DescriptionText
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── EPubCopyModal.js
│ │ │ ├── EPubFileInfoModal.js
│ │ │ ├── EPubHeader
│ │ │ │ ├── EPubRightActions.js
│ │ │ │ ├── EPubTitle.js
│ │ │ │ ├── EPubToolbar.js
│ │ │ │ ├── SaveStatusLabel.js
│ │ │ │ ├── ToolButton.js
│ │ │ │ ├── ViewDropdown.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── EPubInstruction
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── EPubNavigation
│ │ │ │ ├── NavigationMenu.js
│ │ │ │ ├── NavigationProvider.js
│ │ │ │ ├── NavigationTrigger.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── ImagePickerModal.js
│ │ │ ├── MDEditorModal.js
│ │ │ ├── Markdown.js
│ │ │ ├── PlayerModal
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── ShortcutModal.js
│ │ │ ├── Tags
│ │ │ │ ├── TagGroup.js
│ │ │ │ └── TagGroup.scss
│ │ │ └── index.js
│ │ ├── controllers
│ │ │ ├── EPubDownloadController.js
│ │ │ ├── EPubHistoryManager.js
│ │ │ ├── PreferenceController.js
│ │ │ ├── constants
│ │ │ │ ├── EPubConstants.js
│ │ │ │ ├── EPubIDs.js
│ │ │ │ └── shortcuts.js
│ │ │ ├── file-builders
│ │ │ │ ├── EPubFileBuilder.js
│ │ │ │ ├── EPubParser.js
│ │ │ │ ├── GlossaryCreator.js
│ │ │ │ ├── HTMLFileBuilder.js
│ │ │ │ ├── LatexFileBuilder.js
│ │ │ │ ├── PDFFileBuilder.js
│ │ │ │ ├── file-templates
│ │ │ │ │ ├── epub
│ │ │ │ │ │ ├── container.xml.js
│ │ │ │ │ │ ├── content.opf.js
│ │ │ │ │ │ ├── content.xhtml.js
│ │ │ │ │ │ ├── epub.css
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ ├── mimetype.js
│ │ │ │ │ │ ├── toc.ncx.js
│ │ │ │ │ │ └── toc.xhtml.js
│ │ │ │ │ ├── html
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ ├── index.live.html.js
│ │ │ │ │ │ ├── index.local.html.js
│ │ │ │ │ │ ├── prism.js.txt
│ │ │ │ │ │ └── style.css
│ │ │ │ │ ├── latex
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── template.js
│ │ │ │ │ ├── pdf
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ ├── pdfstyle.js
│ │ │ │ │ │ └── textbox.js
│ │ │ │ │ └── styles
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ ├── katex.min.css
│ │ │ │ │ │ ├── prism.css
│ │ │ │ │ │ └── root.css
│ │ │ │ ├── index.js
│ │ │ │ └── utils.js
│ │ │ ├── index.js
│ │ │ ├── onboard-guide.js
│ │ │ └── utils.js
│ │ ├── index.js
│ │ ├── index.scss
│ │ ├── model.js
│ │ ├── models
│ │ │ ├── data_reducer.js
│ │ │ └── navigator_effects.js
│ │ ├── service.js
│ │ └── views
│ │ │ ├── EditEPubChapter
│ │ │ ├── ChapterEditor
│ │ │ │ ├── ChapterContent.js
│ │ │ │ ├── ChapterInfo.js
│ │ │ │ ├── ChapterNewContent.js
│ │ │ │ ├── SubChapterItem.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── Instruction.js
│ │ │ ├── Toolbuttons.js
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ │ ├── EditEPubStructure
│ │ │ ├── ChapterList
│ │ │ │ ├── ChapterTitleButton.js
│ │ │ │ ├── EPubChapterItem.js
│ │ │ │ ├── EPubListItem.js
│ │ │ │ ├── EPubSubChapterItem.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── EPubItemView
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── Instruction.js
│ │ │ ├── QuickActions.js
│ │ │ └── index.js
│ │ │ ├── EditINote
│ │ │ ├── INoteEditor
│ │ │ │ ├── INoteChapter.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── QuickActionsEditNote.js
│ │ │ └── index.js
│ │ │ ├── ViewAndDownload
│ │ │ ├── DownloadOptions.js
│ │ │ ├── EPubPreview.js
│ │ │ ├── EditOptions.js
│ │ │ └── index.js
│ │ │ └── index.js
│ ├── Embed
│ │ └── index.js
│ ├── Example
│ │ ├── components
│ │ │ ├── OfferingList
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── controllers
│ │ │ ├── index.js
│ │ │ └── setup.js
│ │ └── index.js
│ ├── Glossary
│ │ ├── components
│ │ │ ├── GlossaryAddForm
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── GlossaryBar
│ │ │ │ └── index.js
│ │ │ ├── GlossaryEditForm
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ └── GlossaryTable
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ └── index.js
│ ├── History
│ │ ├── components
│ │ │ └── WatchHistories
│ │ │ │ └── index.js
│ │ ├── index.js
│ │ └── model.js
│ ├── Home
│ │ ├── components
│ │ │ ├── CourseFilter
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── MaintenanceMesg
│ │ │ │ └── index.js
│ │ │ ├── Placeholder
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── SectionList
│ │ │ │ ├── SectionItem.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ └── index.js
│ │ ├── controllers
│ │ │ ├── FeedSectionBuilder.js
│ │ │ └── HomeConstants.js
│ │ ├── index.js
│ │ └── model.js
│ ├── Instructor
│ │ ├── CourseAnalytics
│ │ │ ├── components
│ │ │ │ ├── TempVideoTimeTable
│ │ │ │ │ ├── index.css
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── vtime.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── CourseSettings
│ │ │ ├── components
│ │ │ │ ├── CourseInfo.js
│ │ │ │ ├── EmailList
│ │ │ │ │ ├── EmailFilter.js
│ │ │ │ │ ├── EmailItem.js
│ │ │ │ │ ├── UploadFile.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── RemoveCourse.js
│ │ │ │ ├── Staffs
│ │ │ │ │ └── index.js
│ │ │ │ ├── Students
│ │ │ │ │ └── index.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── InstPlaylist
│ │ │ ├── components
│ │ │ │ ├── Confirmation
│ │ │ │ │ └── index.js
│ │ │ │ ├── MediaList
│ │ │ │ │ ├── ActionBar
│ │ │ │ │ │ ├── UploadButton.js
│ │ │ │ │ │ └── index.js
│ │ │ │ │ ├── MediaDNDList.js
│ │ │ │ │ ├── MediaItem
│ │ │ │ │ │ ├── MediaItemActions.js
│ │ │ │ │ │ ├── MediaName.js
│ │ │ │ │ │ ├── UploadASLButton.js
│ │ │ │ │ │ └── index.js
│ │ │ │ │ ├── NoVideoHolder.js
│ │ │ │ │ ├── UploadFile
│ │ │ │ │ │ ├── VideoUploadActions.js
│ │ │ │ │ │ ├── VideoUploadTable.js
│ │ │ │ │ │ ├── VideoUploadTableStatusCell.js
│ │ │ │ │ │ ├── VideoUploadTableVideoCell.js
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── index.scss
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── PlaylistInfo
│ │ │ │ │ ├── Actions.js
│ │ │ │ │ ├── BreadCrumb.js
│ │ │ │ │ ├── PlaylistName.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── UploadFiles
│ │ │ │ │ ├── UploadActions.js
│ │ │ │ │ ├── UploadTable.js
│ │ │ │ │ ├── UploadTableStatusCell.js
│ │ │ │ │ ├── UploadTableVideoCell.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ └── model.js
│ │ ├── MyCourses
│ │ │ ├── components
│ │ │ │ ├── CourseList
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── NoCourseHolder.js
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ └── model.js
│ │ ├── NewCourse
│ │ │ ├── components
│ │ │ │ ├── CourseForm
│ │ │ │ │ ├── BasicInfo.js
│ │ │ │ │ ├── CourseSelection.js
│ │ │ │ │ ├── UniversitySelection.js
│ │ │ │ │ └── index.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── NewPlaylist
│ │ │ ├── components
│ │ │ │ ├── NewPlaylistForm
│ │ │ │ │ ├── PlaylistName.js
│ │ │ │ │ ├── PlaylistSelection.js
│ │ │ │ │ ├── PlaylistUrl.js
│ │ │ │ │ ├── SourceTypeInstruction.js
│ │ │ │ │ └── index.js
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ └── index.js
│ ├── Maintenance
│ │ ├── index.css
│ │ └── index.js
│ ├── MediaSettings
│ │ ├── Components
│ │ │ ├── MSPHeaderTabTitle
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ └── index.js
│ │ ├── Tabs
│ │ │ ├── EPub
│ │ │ │ └── index.js
│ │ │ ├── Transcriptions
│ │ │ │ ├── Player
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── TransTable
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── controllers
│ │ │ ├── constants.js
│ │ │ └── setup.js
│ │ ├── index.js
│ │ ├── index.scss
│ │ └── model.js
│ ├── NotFound404
│ │ └── index.js
│ ├── Search
│ │ ├── components
│ │ │ ├── SearchInput
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ └── SearchResult
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ ├── index.js
│ │ └── model.js
│ ├── Watch
│ │ ├── Components
│ │ │ ├── CTPlayer
│ │ │ │ ├── PlayerWrapper.jsx
│ │ │ │ ├── index.js
│ │ │ │ ├── index.scss
│ │ │ │ ├── player.jsx
│ │ │ │ ├── playerModes.css
│ │ │ │ └── player_hls.jsx
│ │ │ ├── CTPopup
│ │ │ │ ├── CTPopup.jsx
│ │ │ │ ├── CTPopup.scss
│ │ │ │ └── GlossaryPanel.jsx
│ │ │ ├── ControlBar
│ │ │ │ ├── CtrlButtons
│ │ │ │ │ ├── AudioDescriptionButton.jsx
│ │ │ │ │ ├── ClosedCaptionButton.jsx
│ │ │ │ │ ├── ForwardButton.jsx
│ │ │ │ │ ├── FullscreenButton.jsx
│ │ │ │ │ ├── GlossaryButton.jsx
│ │ │ │ │ ├── LanguagePickerButton.jsx
│ │ │ │ │ ├── NextVideoButton.jsx
│ │ │ │ │ ├── PlayButton.jsx
│ │ │ │ │ ├── PlaybackRateButton.jsx
│ │ │ │ │ ├── RewindButton.jsx
│ │ │ │ │ ├── ScreenModeSettingButton.jsx
│ │ │ │ │ ├── SettingButton.jsx
│ │ │ │ │ ├── ShowASLButton.jsx
│ │ │ │ │ ├── SwitchScreenButton.jsx
│ │ │ │ │ ├── TranscriptionPickerButton.jsx
│ │ │ │ │ └── index.js
│ │ │ │ ├── ProgressBar
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── TimeDisplay
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── VolumeControl
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── slider.scss
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── ErrorWrapper
│ │ │ │ └── index.js
│ │ │ ├── Menus
│ │ │ │ ├── DownloadMenu
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── LanguageMenu
│ │ │ │ │ └── index.js
│ │ │ │ ├── PlaybackrateMenu
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── index.scss
│ │ │ │ │ └── slider.scss
│ │ │ │ ├── PlaylistsMenu
│ │ │ │ │ ├── PlaylistView.jsx
│ │ │ │ │ ├── Videos.jsx
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── ScreenModeMenu
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── SettingMenu
│ │ │ │ │ ├── ADSetting
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ └── slider.scss
│ │ │ │ │ ├── CCSetting
│ │ │ │ │ │ ├── CCStylePicker.jsx
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ ├── index.scss
│ │ │ │ │ │ └── slider.scss
│ │ │ │ │ ├── DisplaySetting
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ ├── index.scss
│ │ │ │ │ │ └── slider.scss
│ │ │ │ │ ├── GeneralSetting
│ │ │ │ │ │ └── index.js
│ │ │ │ │ ├── MenuRadio.jsx
│ │ │ │ │ ├── TranscriptionSetting
│ │ │ │ │ │ └── index.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── ShortcutsTable
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── TranscriptionMenu
│ │ │ │ │ └── index.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── Modals
│ │ │ │ ├── EmbedModal
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── ShareModal
│ │ │ │ │ ├── index.css
│ │ │ │ │ └── index.js
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── Overlays
│ │ │ │ ├── AudioDescription
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── BigPlayButton
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── ClosedCaption
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── FlashWarningButton
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── SecondaryPlayerWrapper
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── TabEventHelperButtons
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── TransCtrlButtons
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── UpNext
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ └── index.js
│ │ │ ├── Search
│ │ │ │ ├── Accordion
│ │ │ │ │ └── index.js
│ │ │ │ ├── InputBar
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── Placeholder.jsx
│ │ │ │ ├── Results
│ │ │ │ │ ├── OpenMenuButton.jsx
│ │ │ │ │ ├── PageControlButtons.jsx
│ │ │ │ │ ├── ResultList.jsx
│ │ │ │ │ ├── ResultListItems.jsx
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── Transcriptions
│ │ │ │ ├── CaptionLine
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── PlaceHolder.jsx
│ │ │ │ ├── TranscriptText
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── index.js
│ │ │ │ ├── index.scss
│ │ │ │ └── index_liveplayer.scss
│ │ │ ├── WatchCtrlButton
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ ├── WatchHeader
│ │ │ │ ├── Buttons
│ │ │ │ │ ├── DownloadMenuTrigger.jsx
│ │ │ │ │ ├── GuideTrigger.jsx
│ │ │ │ │ ├── PlaylistMenuTrigger.jsx
│ │ │ │ │ ├── ShareTrigger.jsx
│ │ │ │ │ ├── ShortcutsTableTrigger.jsx
│ │ │ │ │ └── index.scss
│ │ │ │ ├── MediaInfo.jsx
│ │ │ │ ├── Search
│ │ │ │ │ ├── index.js
│ │ │ │ │ └── index.scss
│ │ │ │ ├── index.js
│ │ │ │ └── index.scss
│ │ │ └── index.js
│ │ ├── Utils
│ │ │ ├── UserEventController.js
│ │ │ ├── constants.util.js
│ │ │ ├── data
│ │ │ │ ├── ad.example.json
│ │ │ │ ├── index.js
│ │ │ │ └── shortcuts.js
│ │ │ ├── download.control.js
│ │ │ ├── helpers.js
│ │ │ ├── index.js
│ │ │ ├── keydown.control.js
│ │ │ ├── keydown.control.test.js
│ │ │ ├── progress-controllers
│ │ │ │ ├── index.js
│ │ │ │ ├── prog.chrome-safari.js
│ │ │ │ ├── prog.firefox.js
│ │ │ │ ├── prog.general.js
│ │ │ │ └── prog.mobile.js
│ │ │ ├── prompt.control.js
│ │ │ ├── trans.control.js
│ │ │ └── user-guide.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── model.js
│ │ ├── model
│ │ │ ├── menu_effects.js
│ │ │ ├── player_effects.js
│ │ │ ├── search_effects.js
│ │ │ ├── setup.js
│ │ │ └── trans_effects.js
│ │ ├── player.js
│ │ ├── playermodel.js
│ │ ├── service.js
│ │ └── zIndex.css
│ └── index.js
├── setupTests.js
└── utils
│ ├── 2html
│ ├── index.js
│ ├── markdown.js
│ ├── plaintext.js
│ └── prism-highlight.js
│ ├── constants.js
│ ├── cthttp
│ ├── entities
│ │ ├── Account.js
│ │ ├── Captions.js
│ │ ├── Courses.js
│ │ ├── Departments.js
│ │ ├── EPubs.js
│ │ ├── Images.js
│ │ ├── Logs.js
│ │ ├── Media.js
│ │ ├── Offerings.js
│ │ ├── Playlists.js
│ │ ├── Roles.js
│ │ ├── Terms.js
│ │ ├── Universities.js
│ │ └── WatchHistories.js
│ ├── general-requests.js
│ ├── index.js
│ ├── request.js
│ ├── responses
│ │ ├── errors.js
│ │ └── parsers.js
│ ├── statics.js
│ └── userMetadata.js
│ ├── env.js
│ ├── index.js
│ ├── json
│ ├── initialData.json
│ ├── monthNames.json
│ ├── offeringAccessTypes.json
│ └── playlistTypes.json
│ ├── links.js
│ ├── logger.js
│ ├── prompt
│ ├── CTPrompt.js
│ ├── index.js
│ └── prompt-creators.js
│ ├── search
│ ├── CTSearch.js
│ └── index.js
│ ├── state-controller.js
│ ├── use-elem.js
│ ├── use-email.js
│ ├── use-error.js
│ ├── use-time.js
│ ├── use-url.js
│ ├── user-guide.js
│ ├── user-preference
│ ├── CTPreference.js
│ ├── CTPreferenceV2.js
│ └── index.js
│ └── user
│ ├── Auth0.js
│ ├── CILogon.js
│ ├── User.js
│ ├── constants.js
│ ├── index.js
│ ├── redirect.js
│ └── storage.js
└── yarn.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .git
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | max_line_length = 100
10 | trim_trailing_whitespace = true
11 | [*.jsx]
12 | indent_size = 2
13 | indent_style = space
14 | [*.md]
15 | max_line_length = 0
16 | trim_trailing_whitespace = false
17 | [{Makefile,**.mk}]
18 | # Use tabs for indentation (Makefiles require tabs)
19 | indent_style = tab
20 | [*.scss]
21 | indent_size = 2
22 | indent_style = space
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*
2 | nginx/*
3 | node_modules/*
4 |
5 | write_env.js
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v18.20.4
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # editor configs
2 | .idea/
3 | .vscode/
4 |
5 | build/*
6 | nginx/*
7 | node_modules/*
8 |
9 | package.json
10 | write_env.js
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "trailingComma": "all",
4 | "tabWidth": 2,
5 | "semi": true,
6 | "singleQuote": true
7 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ClassTranscribe Front-End
2 |
3 | The React.js Frontend for ClassTranscribe
4 |
5 | ClassTranscribe provides searchable transcribed video lectures. Copyright 2019-2024 University of Illinois, USA
6 |
7 | ## Setup Front-End
8 |
9 | Please follow the intructions on [wiki](https://github.com/classtranscribe/FrontEnd/wiki) to get started on React JS and setup the front-end on your machine.
10 |
11 | To contribute the Front-End, please follow this [Contributing Guidelines](https://github.com/classtranscribe/FrontEnd/wiki/Contributing-Guidelines).
12 |
--------------------------------------------------------------------------------
/a11y.txt:
--------------------------------------------------------------------------------
1 | WAVE checker warns of duplicate links.
2 | There is a Home sidebar link and logo link to the home page.
3 | This is just a warning and standard design practice. Further reading-
4 | https://stackoverflow.com/questions/73029201/resolving-a-redundant-link-accessibility-alert-with-a-logo-and-home-nav-link-t
5 |
6 |
7 | Admin panes.
8 | We believe the "Missing form label" and "Orphaned form label" are false positives with WAVE
9 | because the Combobox dropdown is implemented in Javascript.
10 |
11 | Home page
12 | Wave A11Y checker does not like the interactive combo drop box and generates Orphaned from label, Missing Form label and Broken Aria Reference.
--------------------------------------------------------------------------------
/config.template:
--------------------------------------------------------------------------------
1 | window.env={}
2 | window.env.TEST_SIGN_IN="$TEST_SIGN_IN"
3 | // window.env.REACT_APP_FRONTEND_COMMIT_ENDPOINT="$REACT_APP_FRONTEND_COMMIT_ENDPOINT"
4 | window.env.AUTH0_CLIENT_ID="$AUTH0_CLIENT_ID"
5 | window.env.AUTH0_DOMAIN="$AUTH0_DOMAIN"
6 | window.env.CILOGON_CLIENT_ID="$CILOGON_CLIENT_ID"
7 | window.env.APPLICATION_INSIGHTS_KEY="$APPLICATION_INSIGHTS_KEY"
8 | window.env.BRANCH="$BRANCH"
9 | window.env.BUILDNUMBER="$BUILDNUMBER"
10 | window.env.GITSHA1="$GITSHA1"
11 |
--------------------------------------------------------------------------------
/config/jest/babelTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const babelJest = require('babel-jest');
4 |
5 | const hasJsxRuntime = (() => {
6 | if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
7 | return false;
8 | }
9 |
10 | try {
11 | require.resolve('react/jsx-runtime');
12 | return true;
13 | } catch (e) {
14 | return false;
15 | }
16 | })();
17 |
18 | module.exports = babelJest.createTransformer({
19 | presets: [
20 | [
21 | require.resolve('babel-preset-react-app'),
22 | {
23 | runtime: hasJsxRuntime ? 'automatic' : 'classic',
24 | },
25 | ],
26 | ],
27 | babelrc: false,
28 | configFile: false,
29 | });
30 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return {
9 | code: 'module.exports = {};'
10 | };
11 | },
12 | getCacheKey() {
13 | // The output is always the same.
14 | return 'cssTransform';
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/config/pnpTs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { resolveModuleName } = require('ts-pnp');
4 |
5 | exports.resolveModuleName = (
6 | typescript,
7 | moduleName,
8 | containingFile,
9 | compilerOptions,
10 | resolutionHost
11 | ) => {
12 | return resolveModuleName(
13 | moduleName,
14 | containingFile,
15 | compilerOptions,
16 | resolutionHost,
17 | typescript.resolveModuleName
18 | );
19 | };
20 |
21 | exports.resolveTypeReferenceDirective = (
22 | typescript,
23 | moduleName,
24 | containingFile,
25 | compilerOptions,
26 | resolutionHost
27 | ) => {
28 | return resolveModuleName(
29 | moduleName,
30 | containingFile,
31 | compilerOptions,
32 | resolutionHost,
33 | typescript.resolveTypeReferenceDirective
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/getDefaultConfig.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | // const chalk = require('chalk');
3 | const { createLogger, format, transports } = require('winston');
4 | const {setBackend} = require("./changeBackend");
5 |
6 | const logger = createLogger({
7 | format: format.combine(
8 | format.splat(),
9 | format.simple()
10 | ),
11 | transports: [new transports.Console()]
12 | });
13 |
14 | let url = "https://ct-dev.ncsa.illinois.edu"
15 |
16 | try {
17 | if (!fs.existsSync("./public/config.js")) {
18 | setBackend(url)
19 | return;
20 | }
21 | }
22 | catch(err) {
23 | logger.log('error', err)
24 | }
25 |
26 |
27 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./src"
4 | }
5 | }
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 |
4 | root /build;
5 | index index.html index.htm;
6 |
7 | location / {
8 | try_files $uri /index.html;
9 | }
10 |
11 | location ^~ /api/ {
12 | return 404;
13 | }
14 |
15 | location /pgadmin/ {
16 | return 404;
17 | }
18 | location /rabbitmq/ {
19 | return 404;
20 | }
21 | location /swag/ {
22 | return 404;
23 | }
24 | location /traefik/ {
25 | return 404;
26 | }
27 | location /data/ {
28 | return 404;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/public/favicon.ico
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "ClassTranscribe",
3 | "name": "ClassTranscribe",
4 | "description": "ClassTranscribe provides accessible searchable transcribed video lectures.",
5 | "icons": [
6 | {
7 | "src": "favicon.ico",
8 | "sizes": "64x64 32x32 24x24 16x16",
9 | "type": "image/x-icon"
10 | }
11 | ],
12 | "start_url": ".",
13 | "display": "standalone",
14 | "theme_color": "#255b8b",
15 | "background_color": "#ffffff"
16 | }
17 |
--------------------------------------------------------------------------------
/src/assets/cc-fonts/OpenSans-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/cc-fonts/OpenSans-Medium.ttf
--------------------------------------------------------------------------------
/src/assets/cc-fonts/calibri.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/cc-fonts/calibri.ttf
--------------------------------------------------------------------------------
/src/assets/cc-fonts/helvetica.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/cc-fonts/helvetica.ttf
--------------------------------------------------------------------------------
/src/assets/cc-fonts/tahoma.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/cc-fonts/tahoma.ttf
--------------------------------------------------------------------------------
/src/assets/cc-fonts/times-new-roman.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/cc-fonts/times-new-roman.ttf
--------------------------------------------------------------------------------
/src/assets/images/box-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/brand-text-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/images/brand-text-dark.png
--------------------------------------------------------------------------------
/src/assets/images/brand-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/images/brand-text.png
--------------------------------------------------------------------------------
/src/assets/images/epub-example-bottom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/images/epub-example-bottom.png
--------------------------------------------------------------------------------
/src/assets/images/epub-example-top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/images/epub-example-top.png
--------------------------------------------------------------------------------
/src/assets/images/index.js:
--------------------------------------------------------------------------------
1 | export const theTextBrand = require('./brand-text.png').default;
2 | export const theDarkTextBrand = require('./brand-text-dark.png').default;
3 | export const theLogo = require('./logo.png').default;
4 | export const theLogoOutline = require('./logo-outline.png').default;
5 | export const theLogoOutlineSvg = require('./logo-outline.svg').default;
6 |
7 | export const theBoxLogo = require('./box-logo.svg').default;
8 | export const theKalturaLogo = require('./kaltura-logo.png').default;
9 |
10 | export const theVideoPosterImg = require('./video-poster.jpg').default;
11 | // export const theOfferingPosterImg = require('./offering-poster.jpg').default;
12 |
13 | export const theEpubExampleTop = require('./epub-example-top.png').default;
14 | export const theEpubExampleBottom = require('./epub-example-bottom.png').default;
15 |
--------------------------------------------------------------------------------
/src/assets/images/kaltura-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/images/kaltura-logo.png
--------------------------------------------------------------------------------
/src/assets/images/logo-outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/images/logo-outline.png
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/images/offering-poster.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/images/offering-poster.jpg
--------------------------------------------------------------------------------
/src/assets/images/video-poster.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/assets/images/video-poster.jpg
--------------------------------------------------------------------------------
/src/azure-app-insights/index.js:
--------------------------------------------------------------------------------
1 | import AppInsightsProvider from './AppInsightsProvider';
2 |
3 | export default AppInsightsProvider;
4 |
5 | export { getAppInsights } from './service';
6 |
7 | export { SeverityLevel } from '@microsoft/applicationinsights-web';
8 |
--------------------------------------------------------------------------------
/src/components/CTCookieAgreement/CookieOption.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { isMobile } from 'react-device-detect';
3 | import { ButtonBase } from '@material-ui/core';
4 | import { CTFragment, CTText } from 'layout';
5 |
6 | function CookieOption({
7 | name,
8 | desp,
9 | muted,
10 | icon,
11 | ...otherProps
12 | }) {
13 | const nameSize = isMobile ? 'medium' : 'big';
14 | const despSize = isMobile ? 'normal' : 'medium';
15 |
16 | return (
17 |
18 | {icon}
19 |
20 |
27 | {name}
28 |
29 | {desp}
30 |
31 |
32 | );
33 | }
34 |
35 | export default CookieOption;
36 |
--------------------------------------------------------------------------------
/src/components/CTCookieAgreement/index.scss:
--------------------------------------------------------------------------------
1 | #cookie-agreement-inner-wrapper {
2 | .cookie-acp-title {
3 | display: flex;
4 | align-items: flex-end;
5 | justify-content: center;
6 | }
7 | .policy-container {
8 | background: rgb(245, 245, 245);
9 | }
10 | .policy-btn {
11 | color: #328383;
12 | margin: 0 4px;
13 | padding-bottom: 2px;
14 | }
15 | .cookie-acp-btns {
16 | padding: 0 0 20px 0 !important;
17 | }
18 | }
19 |
20 | @media screen and (max-width: 520px) {
21 | #cookie-agreement-inner-wrapper {
22 | .cookie-acp-title .welcome-txt {
23 | display: none;
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/components/CTCookieAgreement/policies/index.js:
--------------------------------------------------------------------------------
1 | // import CookiePoilcy from './CookiePolicy';
2 | // import AcceptableUsePolicy from './AcceptableUsePolicy';
3 |
4 | const CookiePolicyLinks = [ "https://www.vpaa.uillinois.edu/resources/cookies" ];
5 | const PrivacyPolicyLinks = [ "https://www.vpaa.uillinois.edu/resources/web_privacy/",
6 | "https://www.vpaa.uillinois.edu/resources/supplemental_privacy_notice_pipl/"]
7 | const TermsOfUseLinks = [ "https://www.vpaa.uillinois.edu/resources/terms_of_use/" ];
8 |
9 | export {CookiePolicyLinks, PrivacyPolicyLinks,TermsOfUseLinks};
10 | // export { CookiePoilcy };
11 | // export { AcceptableUsePolicy };
12 |
--------------------------------------------------------------------------------
/src/components/CTEPubListScreen/components/EPubCTList/EPubCTList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { CTFragment } from 'layout';
4 | import EPubCTListItem from './EPubCTListItem';
5 |
6 | function EPubCTList(props) {
7 | const {
8 | id,
9 | role = 'list',
10 | className,
11 | items = [],
12 | onDelete,
13 | ...listProps
14 | } = props;
15 |
16 | return (
17 |
18 | {items.map(item => )}
19 |
20 | );
21 | }
22 |
23 | EPubCTList.propTypes = {
24 | /** An unique id */
25 | id: PropTypes.string,
26 |
27 | /** role of the list, default as `list` */
28 | role: PropTypes.string,
29 |
30 | /** Addtional classes */
31 | className: PropTypes.string,
32 |
33 | /** List items using `CTListItem` */
34 | items: PropTypes.arrayOf(PropTypes.shape(EPubCTListItem.propTypes)),
35 | };
36 |
37 | export default EPubCTList;
38 |
39 |
--------------------------------------------------------------------------------
/src/components/CTEPubListScreen/components/EPubCTList/index.js:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 |
3 | export { default as EPubCTListItem } from './EPubCTListItem';
4 | export { default as EPubCTList } from './EPubCTList';
--------------------------------------------------------------------------------
/src/components/CTEPubListScreen/components/EPubCTList/index.scss:
--------------------------------------------------------------------------------
1 | .ct-listitem-con {
2 | background-color: #ffffff;
3 | transition: background 200ms ease-in-out;
4 |
5 | .ct-listitem-approved {
6 | color: orange;
7 | }
8 |
9 | &:not(:last-child) {
10 | .ct-listitem-text {
11 | border-bottom: 1px solid rgb(233, 233, 233);
12 | }
13 | }
14 |
15 | .ct-listitem {
16 | padding: 0 25px;
17 | text-align: left;
18 |
19 | span.material-icons {
20 | color: rgb(61, 61, 61);
21 | padding-right: 15px;
22 | font-size: 30px;
23 | pointer-events: none;
24 | }
25 |
26 | .ct-listitem-text {
27 | padding: 10px 0;
28 | }
29 | }
30 |
31 | &:hover, &:focus {
32 | span.material-icons,
33 | .ct-listitem-text .ct-listitem-title {
34 | color: teal !important;
35 | }
36 | }
37 |
38 | &:focus {
39 | background-color: rgb(239, 245, 245);
40 | outline: none;
41 | }
42 |
43 | &:hover {
44 | background-color: rgb(243, 243, 243);
45 | }
46 | }
--------------------------------------------------------------------------------
/src/components/CTEPubListScreen/components/EPubPoster.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { theEpubExampleTop, theEpubExampleBottom } from 'assets/images';
3 | import { CTFragment } from 'layout';
4 |
5 | function EPubPoster() {
6 | return (
7 |
8 |
9 |
15 |
16 |
17 | Convert Your Lectures to I-Note Books
18 |
19 |
20 |
26 |
27 |
28 | );
29 | }
30 |
31 | export default EPubPoster;
32 |
--------------------------------------------------------------------------------
/src/components/CTEPubListScreen/components/NewEPubButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import AddIcon from '@material-ui/icons/Add';
4 | import { useButtonStyles } from 'layout';
5 |
6 | function NewEPubButton({ onCreate }) {
7 | const btn = useButtonStyles();
8 |
9 | return (
10 | }
14 | onClick={onCreate}
15 | aria-haspopup="dialog"
16 | >
17 | Create new I-Note
18 |
19 | );
20 | }
21 |
22 | export default NewEPubButton;
23 |
--------------------------------------------------------------------------------
/src/components/CTEPubListScreen/components/index.js:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 | import './EPubCTList/index.scss';
3 |
4 | export { default as EPubList } from './EPubList';
5 | export { default as EPubPoster } from './EPubPoster';
6 | export { default as NewEPubModal } from './NewEPubModal';
--------------------------------------------------------------------------------
/src/components/CTEPubListScreen/components/index.scss:
--------------------------------------------------------------------------------
1 | #ct-epub-list-screen-con {
2 | h1 {
3 | font-size: 40px;
4 | margin: 50px 0;
5 | min-height: auto !important;
6 | padding: 0;
7 | background: white;
8 | position: sticky;
9 | bottom: 20vh;
10 | width: 100%;
11 | text-align: center;
12 | }
13 |
14 | #ct-epb-list {
15 | width: 45%;
16 | box-shadow: 0 0 8px 2px rgba(38, 41, 41, 0.068);
17 | }
18 |
19 | #ct-epb-poster {
20 | width: 55%;
21 | img {
22 | filter: blur(3px);
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/components/CTEPubListScreen/controllers/index.js:
--------------------------------------------------------------------------------
1 | export { EPubListCtrl } from './EPubListController';
2 | export * from './helpers';
--------------------------------------------------------------------------------
/src/components/CTImageMagnifer/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import PropTypes from 'prop-types';
4 | import { SideBySideMagnifier } from 'react-image-magnifiers';
5 | import { makeStyles } from '@material-ui/core/styles';
6 |
7 | const useStyle = makeStyles({
8 | img: {
9 | width: '100%',
10 | overflow: 'hidden'
11 | }
12 | });
13 |
14 | function CTImageMagnifer(props) {
15 | const {
16 | id,
17 | className,
18 | src,
19 | alt
20 | } = props;
21 |
22 | const classes = useStyle();
23 |
24 | return (
25 |
34 | );
35 | }
36 |
37 | CTImageMagnifer.propTypes = {
38 | id: PropTypes.string,
39 | className: PropTypes.string,
40 | src: PropTypes.string,
41 | alt: PropTypes.string
42 | };
43 |
44 | export default CTImageMagnifer;
45 |
46 |
--------------------------------------------------------------------------------
/src/components/CTImagePickerModal/ImagePickerModalActions.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import PropTypes from 'prop-types';
4 | import Button from '@material-ui/core/Button';
5 | import { useButtonStyles, CTFragment } from 'layout';
6 |
7 | function ImagePickerModalActions(props) {
8 | const { onClose, onSave, canSave } = props;
9 | const btn = useButtonStyles();
10 |
11 | return (
12 |
13 |
21 |
27 |
28 | );
29 | }
30 |
31 | ImagePickerModalActions.propTypes = {
32 | onClose: PropTypes.func,
33 | onSave: PropTypes.func,
34 | canSave: PropTypes.bool
35 | };
36 |
37 | export default ImagePickerModalActions;
38 |
--------------------------------------------------------------------------------
/src/components/CTImagePickerModal/ImagePreview.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { uurl } from 'utils/use-url';
3 | import CTImageMagnifer from '../CTImageMagnifer';
4 |
5 | function ImagePreview({ imgUrl }) {
6 | return (
7 |
8 | {
9 | imgUrl
10 | ?
11 |
12 | :
13 |
No image picked.
14 | }
15 |
16 | );
17 | }
18 |
19 | export default ImagePreview;
20 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/ActionButtonGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Button } from 'pico-ui';
4 | import { CTText, CTFragment } from 'layout';
5 |
6 | function ActionButtonGroup(props) {
7 | const { onSave, onClose } = props;
8 |
9 | return (
10 |
11 | ctrl + s to save the changes
12 |
13 |
14 |
20 |
21 |
27 |
28 |
29 | );
30 | }
31 |
32 | ActionButtonGroup.propTypes = {
33 | onSave: PropTypes.func,
34 | onClose: PropTypes.func
35 | };
36 |
37 | export default ActionButtonGroup;
38 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDTextArea/index.scss:
--------------------------------------------------------------------------------
1 | .ct-md-textarea-con {
2 | overflow: hidden;
3 | padding: 0 0 0 10px;
4 | overflow: hidden;
5 |
6 | // &:not(:focus-within) {
7 | // // box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.205);
8 | // }
9 |
10 | &.preview {
11 | display: none;
12 | }
13 |
14 | .ct-md-textarea{
15 | *::-webkit-scrollbar {
16 | display: block;
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/BoldTextTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MDToolButton from './MDToolButton';
4 | import { addBoldText } from '../ace/ace-controller';
5 | import { getShortcut } from '../ace/ace-shortcut';
6 |
7 | function BoldTextTrigger(props) {
8 | const { ace } = props;
9 | const handleAddBoldText = () => addBoldText(ace);
10 |
11 | return (
12 |
17 | );
18 | }
19 |
20 | BoldTextTrigger.propTypes = {
21 | ace: PropTypes.any
22 | };
23 |
24 | export default BoldTextTrigger;
25 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/BulletedListTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MDToolButton from './MDToolButton';
4 | import { addBulletedList } from '../ace/ace-controller';
5 |
6 | function BulletedListTrigger(props) {
7 | const { ace } = props;
8 | return (
9 | addBulletedList(ace)}
13 | />
14 | );
15 | }
16 |
17 | BulletedListTrigger.propTypes = {
18 | ace: PropTypes.any
19 | };
20 |
21 | export default BulletedListTrigger;
22 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/CodeTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MDToolButton from './MDToolButton';
4 | import { insertCode } from '../ace/ace-controller'
5 |
6 | function CodeTrigger(props) {
7 | const { ace } = props;
8 | const handleInsertCode = () => insertCode(ace);
9 |
10 | return (
11 |
16 | );
17 | }
18 |
19 | CodeTrigger.propTypes = {
20 | ace: PropTypes.any
21 | };
22 |
23 | export default CodeTrigger;
24 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/HeaderTextTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MDToolButton from './MDToolButton';
4 | import { addHeaderText } from '../ace/ace-controller';
5 | import { getShortcut } from '../ace/ace-shortcut';
6 |
7 | function HeaderTextTrigger(props) {
8 | const { ace } = props;
9 | const handleAddHeaderText = () => addHeaderText(ace);
10 |
11 | return (
12 |
17 | );
18 | }
19 |
20 | HeaderTextTrigger.propTypes = {
21 | ace: PropTypes.any
22 | };
23 |
24 | export default HeaderTextTrigger;
25 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/InsertLinkTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MDToolButton from './MDToolButton';
4 | import { insertLink } from '../ace/ace-controller';
5 | import { getShortcut } from '../ace/ace-shortcut';
6 |
7 | function InsertLinkTrigger(props) {
8 | const { ace } = props;
9 | return (
10 | insertLink(ace)}
14 | />
15 | );
16 | }
17 |
18 | InsertLinkTrigger.propTypes = {
19 | ace: PropTypes.any
20 | };
21 |
22 | export default InsertLinkTrigger;
23 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/InsertMedia/InsertMediaTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MDToolButton from '../MDToolButton';
4 |
5 | function InsertMediaTrigger(props) {
6 | const { onClick } = props;
7 |
8 | return (
9 |
14 | );
15 | }
16 |
17 | InsertMediaTrigger.propTypes = {
18 | onClick: PropTypes.func
19 | };
20 |
21 | export default InsertMediaTrigger;
22 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/ItalicTextTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MDToolButton from './MDToolButton';
4 | import { addItalicText } from '../ace/ace-controller';
5 | import { getShortcut } from '../ace/ace-shortcut';
6 |
7 | function ItalicTextTrigger(props) {
8 | const { ace } = props;
9 | const handleAddItalicText = () => addItalicText(ace);
10 |
11 | return (
12 |
17 | );
18 | }
19 |
20 | ItalicTextTrigger.propTypes = {
21 | ace: PropTypes.any
22 | };
23 |
24 | export default ItalicTextTrigger;
25 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/MathBlockTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MDToolButton from './MDToolButton';
4 | import { insertMathCodeBlock } from '../ace/ace-controller';
5 |
6 | function MathBlockTrigger(props) {
7 | const { ace } = props;
8 | const handleClick = () => insertMathCodeBlock(ace);
9 |
10 | return (
11 |
16 | );
17 | }
18 |
19 | MathBlockTrigger.propTypes = {
20 | ace: PropTypes.any
21 | };
22 |
23 | export default MathBlockTrigger;
24 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/NumberedListTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MDToolButton from './MDToolButton';
4 | import { addNumberedList } from '../ace/ace-controller';
5 |
6 | function NumberedListTrigger(props) {
7 | const { ace } = props;
8 | return (
9 | addNumberedList(ace)}
13 | />
14 | );
15 | }
16 |
17 | NumberedListTrigger.propTypes = {
18 | ace: PropTypes.any
19 | };
20 |
21 | export default NumberedListTrigger;
22 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/QuoteTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import MDToolButton from './MDToolButton';
4 | import { insertQuote } from '../ace/ace-controller';
5 |
6 | function QuoteTrigger(props) {
7 | const { ace } = props;
8 | return (
9 | insertQuote(ace)}
13 | />
14 | );
15 | }
16 |
17 | QuoteTrigger.propTypes = {
18 | ace: PropTypes.any
19 | };
20 |
21 | export default QuoteTrigger;
22 |
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/MDToolBar/index.scss:
--------------------------------------------------------------------------------
1 | .ct-md-toolbar {
2 | // background-color: rgb(224, 224, 224);
3 | min-height: 50px;
4 | display: flex;
5 | flex-direction: row;
6 | align-items: center;
7 | justify-content: space-between;
8 | border-bottom: 1px solid rgb(224, 224, 224);
9 | padding: 0 10px;
10 |
11 | .ct-md-tool-r {
12 | display: flex;
13 | align-items: center;
14 | }
15 | }
--------------------------------------------------------------------------------
/src/components/CTMarkdown/MarkdownEditor/index.scss:
--------------------------------------------------------------------------------
1 | .ct-md-editor-con {
2 | border-radius: 5px;
3 | overflow: hidden;
4 | border: 1px solid rgb(224, 224, 224);
5 | transition: border linear 150ms;
6 |
7 | &:focus-within {
8 | border-color: teal;
9 | }
10 |
11 | &.top {
12 | border-top-left-radius: 0;
13 | border-top-right-radius: 0;
14 | }
15 |
16 | &.bottom {
17 | border-bottom-left-radius: 0;
18 | border-bottom-right-radius: 0;
19 | }
20 |
21 | .ct-md-act-btns-con {
22 | border-top: 1px solid rgb(224, 224, 224);
23 | display: flex;
24 | flex-direction: row;
25 | align-items: center;
26 | justify-content: space-between;
27 | }
28 |
29 | .ct-md-editor-preview {
30 | overflow-x: hidden;
31 | overflow-y: auto;
32 | }
33 | }
--------------------------------------------------------------------------------
/src/components/CTMarkdown/index.js:
--------------------------------------------------------------------------------
1 | export { default as CTMarkdownPreviewer } from './MarkdownPreviewer';
2 | export { default as CTMarkdownEditor } from './MarkdownEditor';
--------------------------------------------------------------------------------
/src/components/CTPlayer/Player/index.scss:
--------------------------------------------------------------------------------
1 | .ctp.ct-player-con {
2 | display: flex;
3 | flex-direction: column;
4 | width: max-content;
5 | height: max-content;
6 |
7 | .ctp.ct-player {
8 | position: relative;
9 | overflow: hidden;
10 |
11 | background-color: black;
12 | color: white;
13 |
14 | font-size: 14px;
15 | line-height: 19px;
16 |
17 | &.md {
18 | font-size: 17px;
19 | line-height: 21px;
20 | }
21 |
22 | &.lg {
23 | font-size: 20px;
24 | line-height: 24px;
25 | }
26 |
27 | &:focus {
28 | outline: none;
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/components/CTPlayer/Range/RangeControlBar/RangePlayButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ActionButton from '../../Wrapper/InteractiveLayer/ActionButton';
4 |
5 | function RangePlayButton(props) {
6 | const {
7 | onClick,
8 | } = props;
9 |
10 | return (
11 |
17 | );
18 | }
19 |
20 | RangePlayButton.propTypes = {
21 | onClick: PropTypes.func
22 | };
23 |
24 | export default RangePlayButton;
25 |
26 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Range/RangeControlBar/index.scss:
--------------------------------------------------------------------------------
1 | .ctp.range-ctrl {
2 | margin-top: 15px;
3 |
4 | .ctp.range-input-con {
5 | margin-left: 10px;
6 |
7 | input {
8 | border-radius: 3px;
9 | border-width: 1px;
10 | border-color: rgba(190, 197, 197, 0);
11 | border-style: solid;
12 | padding: 3px 5px;
13 | &:focus {
14 | border-color: rgb(1, 184, 184);
15 | outline: none;
16 | }
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/components/CTPlayer/Range/RangeTimeLabel.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import SliderTimeLabel from '../Wrapper/InteractiveLayer/ControlBar/Progress/SliderTimeLabel';
4 |
5 | function RangeTimeLabel(props) {
6 | const { index } = props;
7 | const placement = index === 0 ? 'left' : 'right';
8 | return (
9 |
10 | );
11 | }
12 |
13 | RangeTimeLabel.propTypes = {
14 | index: PropTypes.number
15 | };
16 |
17 | export default RangeTimeLabel;
18 |
19 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/InteractiveLayer/ActionBar/ShortcutButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ActionButton from '../ActionButton';
4 |
5 | function ShortcutButton(props) {
6 | let { onClick } = props;
7 | return (
8 |
14 | );
15 | }
16 |
17 | ShortcutButton.propTypes = {
18 | onClick: PropTypes.func
19 | };
20 |
21 | export default ShortcutButton;
22 |
23 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/InteractiveLayer/ControlBar/Progress/SeekTimeLabel.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import timestr from 'utils/use-time';
4 |
5 | function SeekTimeLabel(props) {
6 | const { width, left, duration, reverse = false } = props;
7 | // left += 10;
8 | const sec = Math.round(((reverse ? (width - left) : left) / width) * duration);
9 | const shouldDisplay = sec <= duration && width > 100 && left >= 0;
10 |
11 | let styleLeft = left;
12 | if (left < 30) {
13 | styleLeft = 30;
14 | } else if (left > width - 30) {
15 | styleLeft = width - 30;
16 | }
17 |
18 | return shouldDisplay ? (
19 |
23 | {(reverse ? '-' : '') + timestr.toTimeString(sec)}
24 |
25 | ) : null;
26 | }
27 |
28 | SeekTimeLabel.propTypes = {
29 | width: PropTypes.number,
30 | left: PropTypes.number,
31 | duration: PropTypes.number
32 | };
33 |
34 | export default SeekTimeLabel;
35 |
36 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/InteractiveLayer/SettingsMenu/CCFontColorsMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { CTPlayerConstants as PConstants } from '../../../controllers';
4 | import MenuItem from './MenuItem';
5 |
6 | function CCFontColorsMenu(props) {
7 | let {
8 | fontColor,
9 | setCCFontColor,
10 | onGoBack
11 | } = props;
12 |
13 | return (
14 |
15 |
16 |
17 | {PConstants.CCColors.map(color => (
18 |
26 | );
27 | }
28 |
29 | CCFontColorsMenu.propTypes = {
30 | fontColor: PropTypes.string.isRequired,
31 | setCCFontColor: PropTypes.func.isRequired,
32 | onGoBack: PropTypes.func.isRequired
33 | };
34 |
35 | export default CCFontColorsMenu;
36 |
37 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/InteractiveLayer/SettingsMenu/CCFontSizesMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { CTPlayerConstants as PConstants } from '../../../controllers';
4 | import MenuItem from './MenuItem';
5 |
6 | function CCFontSizesMenu(props) {
7 | let {
8 | fontSize,
9 | setCCFontSize,
10 | onGoBack
11 | } = props;
12 |
13 | return (
14 |
15 |
16 |
17 | {PConstants.CCFontSizes.map(size => (
18 |
26 | );
27 | }
28 |
29 | CCFontSizesMenu.propTypes = {
30 | fontSize: PropTypes.string.isRequired,
31 | setCCFontSize: PropTypes.func.isRequired,
32 | onGoBack: PropTypes.func.isRequired
33 | };
34 |
35 | export default CCFontSizesMenu;
36 |
37 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/InteractiveLayer/SettingsMenu/CCOpacityMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { CTPlayerConstants as PConstants } from '../../../controllers';
4 | import MenuItem from './MenuItem';
5 |
6 | function CCOpacityMenu(props) {
7 | let {
8 | opacity,
9 | setCCOpacity,
10 | onGoBack
11 | } = props;
12 |
13 | return (
14 |
15 |
16 |
17 | {PConstants.CCOpacities.map(_opac => (
18 |
26 | );
27 | }
28 |
29 | CCOpacityMenu.propTypes = {
30 | opacity: PropTypes.string.isRequired,
31 | setCCOpacity: PropTypes.func.isRequired,
32 | onGoBack: PropTypes.func.isRequired
33 | };
34 |
35 | export default CCOpacityMenu;
36 |
37 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/InteractiveLayer/SettingsMenu/LiveCaptionMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from './MenuItem';
3 |
4 | function LiveCaptionMenu(props) {
5 | let {
6 | fontSize,
7 | setFontSize,
8 | onGoBack,
9 | } = props;
10 |
11 | const fontSizes = ["small", "normal", "large"];
12 | // console.log(fontSize);
13 |
14 | return (
15 |
16 |
17 |
18 | {fontSizes.map(size => (
19 |
28 | );
29 | }
30 |
31 |
32 | export default LiveCaptionMenu;
33 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/InteractiveLayer/SettingsMenu/LiveCaptionTrackSelection.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from './MenuItem';
3 |
4 | function LiveCaptionTrackSelection(props) {
5 | let {
6 | englishTrack,
7 | textTracks,
8 | onGoBack,
9 | setTextTrack,
10 | } = props;
11 |
12 | return (
13 |
14 |
15 |
16 | {textTracks.map((pbr, index) => (
17 |
25 | );
26 | }
27 |
28 |
29 | export default LiveCaptionTrackSelection;
30 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/InteractiveLayer/SettingsMenu/PlaybackRateMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // delete? import PropTypes from 'prop-types';
3 | import MenuItem from './MenuItem';
4 |
5 | function PlaybackRateMenu(props) {
6 | let {
7 | playbackRate,
8 | playbackRates,
9 | onGoBack,
10 | setPlaybackRate
11 | } = props;
12 |
13 | return (
14 |
15 |
16 |
17 | {playbackRates.map(pbr => (
18 |
26 | );
27 | }
28 |
29 |
30 | export default PlaybackRateMenu;
31 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/NonInteractiveLayer/ErrorWrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTFragment, CTText } from 'layout';
3 | import { SignInPrompt } from 'components';
4 | import { user } from 'utils';
5 |
6 | function ErrorWrapper({ error }) {
7 | let errorPrompt = Media Unavailable: {error} Error;
8 | if (error === 401) {
9 | if (user.isLoggedIn) {
10 | errorPrompt = (
11 |
12 | Unauthorized Access:
13 | Sorry, you are not authorized for your requested page or resource.
14 |
15 | );
16 | } else {
17 | errorPrompt = (
18 |
23 | );
24 | }
25 | }
26 |
27 | return (
28 |
29 | {errorPrompt}
30 |
31 | );
32 | }
33 |
34 | export default ErrorWrapper;
35 |
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/NonInteractiveLayer/index.scss:
--------------------------------------------------------------------------------
1 | .ctp.wrapper.non-interact {
2 | z-index: 1;
3 |
4 | .ctp.e-v-popup-con {
5 | .ctp.e-v-volume-con {
6 | position: absolute;
7 | top: 10%;
8 | width: 100%;
9 |
10 | .ctp.e-v-volume {
11 | background-color: rgba(36, 36, 36, 0.61);
12 | width: 100px;
13 | border-radius: 10px;
14 | padding: 5px 10px;
15 | font-size: 15px;
16 | }
17 | }
18 | .ctp.e-v-popup-icon {
19 | background-color: rgba(0, 0, 0, 0.274);
20 | border-radius: 100%;
21 | padding: 1.5em;
22 | pointer-events: none;
23 | opacity: 0;
24 | animation: 600ms linear 0ms 1 normal forwards running show-and-fade;
25 |
26 | i { font-size: 50px; }
27 | }
28 | }
29 | }
30 |
31 | @keyframes show-and-fade {
32 | 0% { opacity: .7; transform: scale(0.5); }
33 | 100% { opacity: 0; transform: scale(1.5);}
34 | }
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import './index.scss';
3 |
4 | import InteractiveLayer from './InteractiveLayer';
5 | import NonInteractiveLayer from './NonInteractiveLayer';
6 |
7 | function Wrapper(props) {
8 | const { screenshotActionElement } = props;
9 | const [userActive, setUserActive] = useState(true);
10 |
11 | return (
12 | setUserActive(true)}
15 | onMouseLeave={() => setUserActive(false)}
16 | >
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | export default Wrapper;
--------------------------------------------------------------------------------
/src/components/CTPlayer/Wrapper/index.scss:
--------------------------------------------------------------------------------
1 | .ctp.wrapper {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | }
8 |
9 | .ctp.menu {
10 | z-index: 20;
11 | position: absolute;
12 | }
13 |
14 | // .ctp.main-wrapper {
15 |
16 | // }
--------------------------------------------------------------------------------
/src/components/CTPlayer/controllers/constants/PlayerIDs.js:
--------------------------------------------------------------------------------
1 | import { _buildID } from 'utils';
2 |
3 | class PlayerIDs {
4 | static playerOuterContainerID(id) {
5 | return _buildID('ctp-outer-con-', id);
6 | }
7 |
8 | static playerInnerContainerID(id) {
9 | return _buildID('ctp-inner-con-', id);
10 | }
11 |
12 | static video1ID(id) {
13 | return _buildID('ctp-v1-', id);
14 | }
15 |
16 | static video2ID(id) {
17 | return _buildID('ctp-v2-', id);
18 | }
19 |
20 | static extraPanelID(id) {
21 | return _buildID('ctp-extra', id);
22 | }
23 |
24 | static rangeContainerID(id) {
25 | return _buildID('ctp-range', id);
26 | }
27 | }
28 |
29 | export default PlayerIDs;
--------------------------------------------------------------------------------
/src/components/CTPlayer/controllers/index.js:
--------------------------------------------------------------------------------
1 | export { default as CTPlayerIDs } from './constants/PlayerIDs';
2 | export { default as CTPlayerConstants } from './constants/PlayerConstants';
3 | export { default as LanguageConstants } from './constants/LanguageConstants';
4 |
5 |
6 | export * from './helpers';
--------------------------------------------------------------------------------
/src/components/CTPlaylistIcon/index.scss:
--------------------------------------------------------------------------------
1 | img.ct-pl-icon {
2 | margin-right: 10px;
3 | pointer-events: none;
4 | width: 19px;
5 |
6 | &.small {
7 | width: 15px;
8 | }
9 |
10 | &.normal {
11 | width: 21px;
12 | }
13 |
14 | &.large {
15 | width: 28px;
16 | }
17 |
18 | &.big {
19 | margin-right: 14px;
20 | width: 36px;
21 | }
22 | }
23 |
24 | i.ct-pl-icon {
25 | margin-right: 8px !important;
26 |
27 | &.small {
28 | font-size: 15px !important;
29 | }
30 |
31 | &.normal {
32 | font-size: 20px !important;
33 | }
34 |
35 | &.large {
36 | font-size: 25px !important;
37 | }
38 |
39 | &.big {
40 | margin-right: 12px !important;
41 | font-size: 32px !important;
42 | }
43 | }
--------------------------------------------------------------------------------
/src/components/Cards/CourseCard/Placeholders.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Skeleton from '@material-ui/lab/Skeleton';
3 | import { CTFragment } from 'layout';
4 |
5 | function CardPlaceholder() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default CardPlaceholder;
21 |
--------------------------------------------------------------------------------
/src/components/Cards/CourseCard/index.js:
--------------------------------------------------------------------------------
1 | export { default as CourseCardHolder } from './Placeholders';
2 | export { default as CourseCard } from './Card';
3 | export { default as CourseCardList } from './CardList';
4 |
--------------------------------------------------------------------------------
/src/components/Cards/CourseCard/parse-course.js:
--------------------------------------------------------------------------------
1 | import { links } from 'utils/links';
2 |
3 | /**
4 | * Parse raw offering object to valid CourseCard props
5 | * @param {Object} offering
6 | */
7 | export function parseCourse(offering) {
8 | let {
9 | id,
10 | fullNumber,
11 | courseName,
12 | termName,
13 | term,
14 | sectionName,
15 | description,
16 | } = offering;
17 |
18 | return {
19 | id,
20 | number: fullNumber,
21 | name: courseName,
22 | term: termName || (term ? term.name : 'Unknown Term'),
23 | section: sectionName,
24 | description,
25 | href: links.course(id)
26 | };
27 | }
--------------------------------------------------------------------------------
/src/components/Cards/MediaCard/Poster/index.scss:
--------------------------------------------------------------------------------
1 | .ct-media-poster {
2 | position: relative;
3 | height: max-content;
4 | overflow: hidden;
5 |
6 | &.round {
7 | border-radius: 10px;
8 | }
9 |
10 | img.poster-img {
11 | position: relative;
12 | pointer-events: none;
13 | object-fit: cover;
14 | width: 100%;
15 | }
16 |
17 | .progress-bar {
18 | position: absolute;
19 | left: 0;
20 | bottom: 0;
21 | height: 4px;
22 | width: 100%;
23 | background-color: rgba(114, 114, 114, 0.26);
24 |
25 | .progress {
26 | height: 100%;
27 | border-radius: 0;
28 | background-color: rgb(45, 170, 170);
29 | }
30 | }
31 |
32 | .duration-box {
33 | position: absolute;
34 | right: 4px;
35 | bottom: 4px;
36 | height: 18px;
37 | padding: 1px 4px;
38 | font-size: 12px;
39 | background: rgba(0, 0, 0, 0.534);
40 | color: white;
41 | font-weight: bold;
42 | border-radius: 2px;
43 |
44 | &.watched {
45 | bottom: 7px;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/Cards/MediaCard/index.js:
--------------------------------------------------------------------------------
1 | export { default as MediaCard } from './Card';
2 | export { default as MediaPoster } from './Poster';
--------------------------------------------------------------------------------
/src/components/Cards/MediaCard/parse-media.js:
--------------------------------------------------------------------------------
1 | import { api, links } from "utils";
2 |
3 | export function parseMedia(mediaLike) {
4 | let media = mediaLike;
5 | if (!media.mediaName) {
6 | media = api.parseMedia(media);
7 | }
8 |
9 | return {
10 | key: media.id,
11 | id: media.id,
12 | name: media.mediaName,
13 | ratio: media.watchHistory.ratio,
14 | href: links.watch(media.id),
15 | isUnavailable: media.isUnavailable,
16 | duration: media.duration
17 | };
18 | }
--------------------------------------------------------------------------------
/src/components/Cards/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | CourseCard,
3 | CourseCardList,
4 | CourseCardHolder
5 | } from './CourseCard';
6 |
7 | export { MediaCard, MediaPoster } from './MediaCard';
--------------------------------------------------------------------------------
/src/components/Image/index.js:
--------------------------------------------------------------------------------
1 | // unused import { string } from 'prop-types';
2 | import React, { useEffect, useState } from 'react';
3 | import EPubParser from 'screens/EPub/controllers/file-builders/EPubParser';
4 |
5 | function Image({ src, ...props }) {
6 | const [dataUrl, setDataUrl] = useState(null);
7 | useEffect(() => {
8 | if (typeof src !== 'string') {
9 | src = src.default;
10 | }
11 | async function load() {
12 | const img = await EPubParser.loadImageBuffer(String(src))
13 | const v = new Blob([img]);
14 | setDataUrl(URL.createObjectURL(v))
15 | }
16 |
17 | if (!src.startsWith('data:')) {
18 | load()
19 | } else {
20 | setDataUrl(src);
21 | }
22 | }, [src])
23 | // eslint-disable-next-line jsx-a11y/alt-text
24 | return
25 | }
26 |
27 | export default Image;
--------------------------------------------------------------------------------
/src/components/InfoAndListLayout/Info.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import { CTFragment } from 'layout';
4 | import './index.scss';
5 |
6 | function Info(props) {
7 | const {
8 | id,
9 | className,
10 | children,
11 | ...fragmentProps
12 | } = props;
13 |
14 | const infoNLiClasses = cx('ct-info-n-li', 'info', className);
15 |
16 | return (
17 |
18 | {children}
19 |
20 | );
21 | }
22 |
23 | Info.propTypes = CTFragment.propTypes;
24 | export default Info;
25 |
26 |
--------------------------------------------------------------------------------
/src/components/InfoAndListLayout/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import { CTFragment } from 'layout';
4 | import './index.scss';
5 |
6 | function Info(props) {
7 | const {
8 | id,
9 | className,
10 | children,
11 | ...fragmentProps
12 | } = props;
13 |
14 | const infoNLiClasses = cx('ct-info-n-li', 'list', className);
15 |
16 | return (
17 |
18 |
19 | {children}
20 |
21 |
22 | );
23 | }
24 |
25 | Info.propTypes = CTFragment.propTypes;
26 | export default Info;
27 |
28 |
--------------------------------------------------------------------------------
/src/components/InfoAndListLayout/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import { CTFragment } from 'layout';
4 | import './index.scss';
5 |
6 | import Info from './Info';
7 | import List from './List';
8 |
9 | function InfoAndListLayout(props) {
10 | const {
11 | id,
12 | className,
13 | children,
14 | ...fragmentProps
15 | } = props;
16 |
17 | const infoNLiClasses = cx('ct-info-n-li', 'inl-container', className);
18 |
19 | return (
20 |
21 | {children}
22 |
23 | );
24 | }
25 |
26 | InfoAndListLayout.propTypes = CTFragment.propTypes;
27 | InfoAndListLayout.Info = Info;
28 | InfoAndListLayout.List = List;
29 |
30 | export default InfoAndListLayout;
31 |
32 |
--------------------------------------------------------------------------------
/src/components/Shortcut/ShortcutModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTModal } from 'layout/CTModal';
3 | import ShortcutTable from './ShortcutTable';
4 |
5 | function ShortcutModal(props) {
6 | const {
7 | open,
8 | title = 'Shortcuts',
9 | shortcuts = [],
10 | onClose,
11 | fullWidth,
12 | ...modalProps
13 | } = props;
14 |
15 | return (
16 |
24 |
25 |
26 | );
27 | }
28 |
29 | ShortcutModal.propTypes = {
30 | ...CTModal.propTypes,
31 | ...ShortcutTable.propTypes
32 | };
33 |
34 | export default ShortcutModal;
35 |
36 |
--------------------------------------------------------------------------------
/src/components/Shortcut/index.js:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 |
3 | export { default as CTShortcutTable } from './ShortcutTable';
4 | export { default as CTShortcutModal } from './ShortcutModal';
--------------------------------------------------------------------------------
/src/components/SignInPrompt/index.scss:
--------------------------------------------------------------------------------
1 | .ct-signin-prompt {
2 | margin: 10px 0;
3 | width: 100%;
4 | padding: 0 5px;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 |
9 | .ct-signin-card {
10 | width: 100%;
11 | max-width: 300px;
12 | border-radius: 5px;
13 | // background-color: rgb(241, 246, 246);
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 | padding: 10px;
18 |
19 | .ct-signin-descrip {
20 | text-align: center;
21 | margin: 10px 0;
22 | font-size: 14px;
23 | color: rgb(61, 61, 61);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/src/components/stylesheets/csstrans.playlist-video.css:
--------------------------------------------------------------------------------
1 | .playlist-view {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | height: 100%;
6 | }
7 | .playlist-view-enter {
8 | opacity: 0;
9 | transform: translateX(-25em);
10 | }
11 | .playlist-view-enter-active {
12 | opacity: 1;
13 | transform: translateX(0em);
14 | transition: opacity 0.1s linear, transform 0.1s linear;
15 | }
16 | .playlist-view-exit {
17 | opacity: 0;
18 | }
19 | .playlist-view-exit-active {
20 | opacity: 0 !important;
21 | transition: opacity 0.1s linear, transform 0.1s linear !important;
22 | }
23 |
24 | .video-view-enter {
25 | opacity: 0 !important;
26 | transform: translateX(20em) !important;
27 | }
28 | .video-view-enter-active {
29 | opacity: 1 !important;
30 | transform: translateX(0em) !important;
31 | transition: opacity 0.1s linear, transform 0.1s linear !important;
32 | }
33 | .video-view-exit {
34 | opacity: 0 !important;
35 | }
36 | .video-view-exit-active {
37 | opacity: 0 !important;
38 | }
--------------------------------------------------------------------------------
/src/components/stylesheets/general.transition.css:
--------------------------------------------------------------------------------
1 | /* Transition classNames */
2 | .ripple-btn {
3 | background-position: center;
4 | transition: background 0.8s;
5 | }
6 | .ripple-btn:hover {
7 | background: #c2c2c2 radial-gradient(circle, transparent 1%, #b1b1b1 1%) center/15000%;
8 | }
9 | .ripple-btn:active {
10 | background-color: #aaaaaa;
11 | background-size: 100%;
12 | transition: background 0s;
13 | }
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/brand-icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/brand-icons.eot
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/brand-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/brand-icons.ttf
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/brand-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/brand-icons.woff
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/brand-icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/brand-icons.woff2
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/icons.eot
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/icons.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/icons.otf
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/icons.ttf
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/icons.woff
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/icons.woff2
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/outline-icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/outline-icons.eot
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/outline-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/outline-icons.ttf
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/outline-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/outline-icons.woff
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/outline-icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/fonts/outline-icons.woff2
--------------------------------------------------------------------------------
/src/components/stylesheets/semantic-ui-css/themes/default/assets/images/flags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/components/stylesheets/semantic-ui-css/themes/default/assets/images/flags.png
--------------------------------------------------------------------------------
/src/docs/api-types.js:
--------------------------------------------------------------------------------
1 | export class ComponentAPIs {
2 | static CT_FORM = 'ct-form';
3 | }
--------------------------------------------------------------------------------
/src/docs/component-usage/index.js:
--------------------------------------------------------------------------------
1 | import { importMDX } from 'mdx.macro';
2 |
3 | export const CTFormUsage = importMDX.sync('./ct-form/usage.mdx');
--------------------------------------------------------------------------------
/src/docs/index.js:
--------------------------------------------------------------------------------
1 | // import './styles/md.scss';
2 |
3 | export * from './component-usage';
4 | export { ComponentAPIs } from './api-types';
--------------------------------------------------------------------------------
/src/docs/layouts/MDXDocsContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { CTFragment } from 'layout';
4 | import { CTMarkdownPreviewer } from 'components';
5 |
6 | function MDXDocsContainer(props) {
7 | const { children } = props;
8 |
9 | return (
10 |
11 |
12 | {children}
13 |
14 |
15 | );
16 | }
17 |
18 | MDXDocsContainer.propTypes = {
19 | /** The MDX content */
20 | children: PropTypes.node
21 | };
22 |
23 | export default MDXDocsContainer;
24 |
25 |
--------------------------------------------------------------------------------
/src/docs/layouts/index.js:
--------------------------------------------------------------------------------
1 | export { default as MDXDocsContainer } from './MDXDocsContainer';
--------------------------------------------------------------------------------
/src/entities/EPubs/index.js:
--------------------------------------------------------------------------------
1 | export { default as EPubSourceTypes } from '../SourceTypes';
2 | export * from './structs';
--------------------------------------------------------------------------------
/src/entities/EPubs/structs/EPubSubChapterData.js:
--------------------------------------------------------------------------------
1 | import EPubChapterData from "./EPubChapterData";
2 |
3 | let untitledSubChapterNum = 0;
4 | function _createSubChapterTitle() {
5 | untitledSubChapterNum += 1;
6 | let chapterNum = untitledSubChapterNum > 0 ? ` (${untitledSubChapterNum})` : '';
7 | return `Untitled Sub-Chapter${chapterNum}`;
8 | }
9 |
10 | class EPubSubChapterData extends EPubChapterData {
11 | constructor(subChapterLike, resetText) {
12 | super(subChapterLike, resetText, _createSubChapterTitle);
13 | }
14 |
15 | toObject() {
16 | return {
17 | ...this.__data__,
18 | items: this.itemsToObject(),
19 | contents: this.contentsToObject()
20 | };
21 | }
22 | }
23 |
24 | export default EPubSubChapterData;
25 |
26 |
--------------------------------------------------------------------------------
/src/entities/EPubs/structs/index.js:
--------------------------------------------------------------------------------
1 | export { default as EPubImageData } from './EPubImageData';
2 | export { default as EPubChapterData } from './EPubChapterData';
3 | export { default as EPubSubChapterData } from './EPubSubChapterData';
4 | export { default as EPubData, EPubDataValidationError } from './EPubData';
--------------------------------------------------------------------------------
/src/entities/Entity.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | class Entity {
4 | constructor(data) {
5 | if (data && data.id) this.__id = data.id;
6 | this.__data = _.cloneDeep(data);
7 | }
8 |
9 | get id() {
10 | return this.__id;
11 | }
12 |
13 | toObject() {
14 | return _.cloneDeep(this.__data);
15 | }
16 | }
17 |
18 | export default Entity;
--------------------------------------------------------------------------------
/src/entities/Playlists/index.js:
--------------------------------------------------------------------------------
1 | export { default as PlaylistTypes } from './PlaylistTypes';
2 | export { default as Playlist } from './Playlist';
--------------------------------------------------------------------------------
/src/entities/SourceTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Source types for some general entities
3 | */
4 | export class SourceTypes {
5 | static Offering = 0;
6 | static Course = 1;
7 | static Media = 2;
8 | static Playlist = 3;
9 | static EPub = 4;
10 | }
11 |
12 | export default SourceTypes;
--------------------------------------------------------------------------------
/src/entities/UserEvent/UserEvent.js:
--------------------------------------------------------------------------------
1 | import {
2 | deviceType,
3 | osVersion,
4 | osName,
5 | fullBrowserVersion,
6 | browserName,
7 | } from 'react-device-detect';
8 | import { api, user } from 'utils';
9 |
10 | class UserEvent {
11 | constructor(eventType, data) {
12 | this.type = eventType;
13 |
14 | const { mediaId, offeringId, json } = data;
15 | this.event = {
16 | eventType,
17 | userId: user.userId,
18 | json: {
19 | ...json,
20 | device: {
21 | deviceType,
22 | osVersion,
23 | osName,
24 | fullBrowserVersion,
25 | browserName
26 | }
27 | }
28 | };
29 |
30 | if (offeringId) {
31 | this.event.offeringId = offeringId;
32 | }
33 |
34 | if (mediaId) {
35 | this.event.mediaId = mediaId;
36 | }
37 |
38 | this.send = this.send.bind(this);
39 | }
40 |
41 | send() {
42 | // console.log(this.event);
43 | return api.sendUserAction(this.event);
44 | }
45 | }
46 |
47 | export default UserEvent;
--------------------------------------------------------------------------------
/src/entities/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | default as UserEventManager,
3 | UserEventType,
4 | UserEvent
5 | } from './UserEvent';
--------------------------------------------------------------------------------
/src/hooks/index.js:
--------------------------------------------------------------------------------
1 | export { useArray } from './useArray';
2 | export { useInput, useCheckbox } from './useInput';
3 | export { useLoaded } from './useLoaded';
4 | export { useCTDocTitle } from './useCTDocTitle';
5 | export { useWindowSize } from './useWindowSize';
6 | export { useCustomizedButton } from './useCustomizedButton';
--------------------------------------------------------------------------------
/src/hooks/useCTDocTitle.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | /**
4 | * Update the document's title
5 | * @param {String} title the document.title
6 | */
7 | export function useCTDocTitle(title, push) {
8 | useEffect(() => {
9 | if (push) {
10 | document.title = `${title} | ${document.title}`;
11 | } else {
12 | document.title = `${title} | ClassTranscribe`;
13 | }
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/src/hooks/useCustomizedButton.js:
--------------------------------------------------------------------------------
1 | import * as KeyCode from 'keycode-js';
2 |
3 | export function useCustomizedButton(handleClick) {
4 | const onClick = (e) => {
5 | if (typeof handleClick === 'function') {
6 | handleClick(e);
7 | }
8 | }
9 |
10 | const onKeyDown = (e) => {
11 | if (e.keyCode === KeyCode.KEY_RETURN || e.keyCode === KeyCode.KEY_SPACE) {
12 | onClick(e);
13 | }
14 | }
15 |
16 | return {
17 | role: 'button',
18 | tabIndex: '0',
19 | onClick,
20 | onKeyDown
21 | };
22 | }
--------------------------------------------------------------------------------
/src/hooks/useLoaded.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { api } from 'utils';
3 |
4 | /**
5 | * The hooks that cancels the CT loading wrapper as soon as the components are rendered
6 | * @param {Function} callback function called when the component is rendered
7 | */
8 | export function useLoaded(callback) {
9 | useEffect(() => {
10 | if (typeof callback === 'function') {
11 | callback();
12 | }
13 |
14 | api.contentLoaded();
15 | }, []);
16 | }
--------------------------------------------------------------------------------
/src/hooks/useWindowSize.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | /**
4 | * Hooks that returns the dynamically updated window's size
5 | * @returns {Number[]} the latest [window.innerWidth, window.innerHeight]
6 | */
7 | export function useWindowSize() {
8 | const [size, setSize] = useState(window.innerWidth);
9 | useEffect(() => {
10 | function handleResize() {
11 | setSize([window.innerWidth, window.innerHeight]);
12 | };
13 |
14 | window.addEventListener('resize', handleResize);
15 |
16 | return () => {
17 | window.removeEventListener('resize', handleResize);
18 | };
19 | }, []);
20 |
21 | return size;
22 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Router } from 'dva/router'
3 | import { createBrowserHistory as createHistory } from 'history';
4 | import dva from 'dva'
5 | import App from './App'
6 |
7 | const app = dva({ history: createHistory() });
8 |
9 | // Todo: Remove this hack
10 | // window.temp_app is used twice in screens/EPub/controllers/EPubDownloadController.js
11 | // to access the store (to build and download epub/pdf)
12 | window.temp_app = app
13 |
14 | app.model(require('./model/global').default);
15 | app.model(require('./screens/Home/model').default);
16 | app.model(require('./screens/Search/model').default);
17 | app.model(require('./screens/History/model').default);
18 | app.model(require('./screens/Course/model').default);
19 | app.model(require('./screens/Watch/model').default);
20 | app.model(require('./screens/Watch/playermodel').default);
21 |
22 | app.router(({ history }) => ); // basename="/"
23 | app.start('#root')
24 |
--------------------------------------------------------------------------------
/src/layout/CTButtons/index.js:
--------------------------------------------------------------------------------
1 | export { useButtonStyles } from './styles';
2 | export { default as CTFileButton } from './CTFileButton';
--------------------------------------------------------------------------------
/src/layout/CTButtons/styles.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from '@material-ui/core/styles';
2 |
3 | export const useButtonStyles = makeStyles({
4 | bold: {
5 | fontWeight: 'bold'
6 | },
7 | teal: {
8 | fontWeight: 'bold',
9 | marginLeft: 5,
10 | minWidth: 'max-content',
11 | '&:not(.MuiButton-outlined)': {
12 | background: 'teal',
13 | color: 'white',
14 | '&:hover': {
15 | background: 'var(--ct-green-normal)',
16 | },
17 | '&:disabled': {
18 | opacity: 0.8
19 | }
20 | }
21 | },
22 | tealLink: {
23 | color: 'var(--ct-text-primary) !important',
24 | fontWeight: 'bold',
25 | '&:hover': {
26 | color: 'teal !important'
27 | },
28 | '&.MuiButton-root.Mui-disabled': {
29 | color: 'rgba(0, 0, 0, 0.26) !important'
30 | }
31 | },
32 | danger: {
33 | color: 'red !important',
34 | fontWeight: 'bold',
35 | }
36 | });
--------------------------------------------------------------------------------
/src/layout/CTDnd/DNDItem/index.scss:
--------------------------------------------------------------------------------
1 | .ct-dnd.dnd-li {
2 | &.dragging {
3 | .ct-dnd.dnd-item-con {
4 | transition: all 200ms ease-in-out;
5 | }
6 | }
7 |
8 | .ct-dnd.dnd-item-con {
9 | display: flex;
10 | align-items: center;
11 | position: relative;
12 | outline: 0;
13 | padding-left: 5px;
14 | background: inherit;
15 |
16 | &.disabled {
17 | .ct-dnd.dnd-icon {
18 | display: none;
19 | }
20 |
21 | .ct-dnd.dnd-item {
22 | width: 100%;
23 | padding-left: 10px;
24 | }
25 | }
26 |
27 | &.dragging {
28 | box-shadow: 0 0 3px 3px rgba(0, 0, 0, 0.171);
29 | }
30 |
31 | .ct-dnd.dnd-icon {
32 | width: 30px;
33 | display: flex;
34 | align-items: center;
35 | i.material-icons {
36 | color: rgb(133, 133, 133);
37 | }
38 | }
39 |
40 | .ct-dnd.dnd-item {
41 | cursor: auto;
42 | width: calc(100% - 30px);
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/src/layout/CTDnd/index.js:
--------------------------------------------------------------------------------
1 | export { default as CTDNDContext } from './DNDContext';
2 | export { default as CTDNDItem } from './DNDItem';
3 | export { default as CTDNDList } from './DNDList';
--------------------------------------------------------------------------------
/src/layout/CTDropdown/index.scss:
--------------------------------------------------------------------------------
1 | .ct-dropd-menu-con {
2 | box-shadow: 0 1px 8px rgba(47, 47, 47, 0.219) !important;
3 | .ct-dropd-menu-item {
4 | .MuiListItemIcon-root {
5 | color: rgb(75, 75, 75) !important;
6 | min-width: auto !important;
7 | margin-right: 20px !important;
8 | }
9 |
10 | &.MuiListItem-root.Mui-selected,
11 | &.MuiListItem-root.Mui-selected:hover {
12 | background-color: teal !important;
13 | color: white !important;
14 | .MuiListItemIcon-root {
15 | color: white !important;
16 | }
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/layout/CTFilter/index.scss:
--------------------------------------------------------------------------------
1 | .ct-filter.d-input-con {
2 | width: 100%;
3 | display: flex;
4 | align-items: center;
5 | padding: 0 0 0 20px;
6 |
7 | background: rgb(246, 246, 246);
8 | border-radius: 20px;
9 | overflow: hidden;
10 |
11 | &.grey {
12 | background: rgb(236, 236, 236);
13 |
14 | &:focus-within {
15 | background: rgb(225, 233, 235);
16 | }
17 | }
18 |
19 | &:focus-within {
20 | background: rgb(235, 237, 238);
21 | }
22 |
23 | input {
24 | background: rgba(0,0,0,0);
25 | width: 100%;
26 | border: none;
27 | font-size: 15px;
28 | padding: 10px 0;
29 |
30 | &:focus {
31 | outline: none;
32 | }
33 | }
34 |
35 | .ct-filter.reverse-btn{
36 | transition: transform 100ms linear;
37 | transform: rotateZ(0);
38 |
39 | &.reversed {
40 | transform: rotateZ(180deg);
41 | }
42 | }
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/src/layout/CTFooter/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CTFragment from '../CTFragment';
3 |
4 | /**
5 | * A general footer for ClassTranscribe
6 | */
7 | function CTFooter() {
8 | const year = Math.max( new Date().getFullYear(),2024);
9 | return (
10 |
24 | );
25 | }
26 |
27 | export default CTFooter;
28 |
29 |
--------------------------------------------------------------------------------
/src/layout/CTForm/Form/index.scss:
--------------------------------------------------------------------------------
1 | .ct-form {
2 | box-shadow: none !important;
3 | border: 1px solid rgba(218, 218, 218, 0);
4 |
5 | // &:first-child {
6 | // border-radius: 10px 10px 0 0;
7 | // }
8 |
9 | // &:not(:first-child) {
10 | // margin-top: -1px;
11 | // }
12 |
13 | // &:last-child {
14 | // border-radius: 0 0 10px 10px;
15 | // }
16 |
17 | &.danger {
18 | h3.ct-heading {
19 | color: rgb(195, 0, 0) !important;
20 | }
21 | }
22 |
23 | &.warning {
24 | border-color: rgb(224, 146, 0);
25 | h3.ct-heading {
26 | color: rgb(224, 146, 0) !important;
27 | }
28 | }
29 |
30 | &.Mui-expanded {
31 | border-radius: 10px;
32 | border: 1px solid rgb(218, 218, 218);
33 | &.danger {
34 | border-color: rgb(195, 0, 0);
35 | }
36 |
37 | &.warning {
38 | border-color: rgb(224, 146, 0);
39 | }
40 | }
41 |
42 | .MuiAccordionSummary-root {
43 | outline: none !important;
44 | &:not(.collapsible) {
45 | cursor: default !important;
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/layout/CTForm/FormHeading/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import CTHeading from '../../CTHeading';
4 |
5 | /**
6 | * A section heading component used in `CTForm`
7 | */
8 | function FormHeading(props) {
9 | let {
10 | padding = [20, 0, 0, 0],
11 | children,
12 | ...otherProps
13 | } = props;
14 | const {as = "h3" } = otherProps
15 | return (
16 |
17 | {children}
18 |
19 | );
20 | }
21 |
22 | FormHeading.propTypes = {
23 | ...CTHeading.propTypes,
24 |
25 | /** The primary content */
26 | children: PropTypes.node
27 | };
28 |
29 | export default FormHeading;
30 |
--------------------------------------------------------------------------------
/src/layout/CTForm/Upload/index.js:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 |
3 | export { default as CTUploadBase } from './UploadBase';
4 | export { default as CTUploadButton } from './UploadButton';
--------------------------------------------------------------------------------
/src/layout/CTForm/Upload/index.scss:
--------------------------------------------------------------------------------
1 | .ct-upload.base {
2 | width: max-content;
3 | &.fluid { width: 100%; }
4 | }
--------------------------------------------------------------------------------
/src/layout/CTForm/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The customized form components using Material-UI
3 | */
4 |
5 | export { default as CTForm } from './Form';
6 | export { default as CTFormHeading } from './FormHeading';
7 | export { default as CTFormRow } from './FormRow';
8 | export { default as CTFormHelp } from './FormHelp';
9 | export { default as CTInput } from './Input';
10 | export { default as CTSelect } from './Select';
11 | export { default as CTCheckbox } from './Checkbox';
12 | export { default as CTRadio } from './Radio';
13 |
14 | export { default as CTAutoComplete } from './AutoComplete';
15 | export { CTUploadBase, CTUploadButton } from './Upload';
--------------------------------------------------------------------------------
/src/layout/CTHeading/index.scss:
--------------------------------------------------------------------------------
1 | .ct-heading {
2 | color: rgb(51, 51, 51);
3 |
4 | &.uppercase {
5 | text-transform: uppercase;
6 | }
7 |
8 | &.gradient {
9 | background: linear-gradient(to bottom,white 90%,rgba(255, 255, 255, 0));
10 | }
11 |
12 | i {
13 | margin-right: 5px;
14 | font-weight: bold;
15 | font-size: 1em;
16 | font-weight: normal;
17 | pointer-events: none;
18 | }
19 |
20 | &.highlight {
21 | color: var(--ct-green-normal);
22 | }
23 |
24 | &.highlightIcon i {
25 | color: var(--ct-green-normal);
26 | }
27 |
28 | .content {
29 | span {
30 | color: var(--ct-green-normal);
31 | }
32 | }
33 | }
34 |
35 | h1.ct-heading {
36 | margin-top: 0 !important;
37 | font-size: 30px;
38 | padding: 30px 30px 20px 30px;
39 | }
40 |
41 | .dark .ct-heading,
42 | .ct-heading.dark {
43 | color: white;
44 | }
--------------------------------------------------------------------------------
/src/layout/CTHorizontalScroll/ArrowButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import cx from 'classnames';
4 | import ButtonBase from '@material-ui/core/ButtonBase';
5 | import CTFragment from 'layout/CTFragment';
6 |
7 | function ArrowButton(props) {
8 | const { isLeft } = props;
9 | const label = isLeft ? 'Left' : 'Right';
10 | const icon = isLeft ? 'chevron_left' : 'chevron_right';
11 |
12 | const btnClasses = cx('ct-h-scroll', 'arrow-btn', { left: isLeft, right: !isLeft });
13 |
14 | return (
15 |
16 |
17 | {icon}
18 |
19 |
20 | );
21 | }
22 |
23 | ArrowButton.propTypes = {
24 | isLeft: PropTypes.bool
25 | };
26 |
27 | export default ArrowButton
28 |
29 |
--------------------------------------------------------------------------------
/src/layout/CTHorizontalScroll/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import PropTypes from 'prop-types';
4 | import { isMobile } from 'react-device-detect';
5 | import OverflowWrapper from 'react-overflow-wrapper';
6 | import ArrowButton from './ArrowButton';
7 | import './index.scss';
8 |
9 | function CTHorizontalScroll(props) {
10 | const { children } = props;
11 |
12 | const arrowLeftElement = ;
13 | const arrowRightElement = ;
14 | const scrollClasses = cx('ct-h-scroll', 'scroll-con', { mobile: isMobile });
15 |
16 | return (
17 |
22 | {children}
23 |
24 | );
25 | }
26 |
27 | CTHorizontalScroll.propTypes = {
28 | /** A list of react elements */
29 | children: PropTypes.node
30 | };
31 |
32 | export default CTHorizontalScroll;
33 |
34 |
--------------------------------------------------------------------------------
/src/layout/CTHorizontalScroll/index.scss:
--------------------------------------------------------------------------------
1 | .ct-h-scroll.arrow-btn {
2 | border-radius: 100%;
3 | background-color: rgb(240, 240, 240);
4 | margin: 0 2px;
5 | height: 40px;
6 | width: 40px;
7 | box-shadow: 0px 2px 1px -1px rgba(0,0,0,0.2), 0px 1px 1px 0px rgba(0,0,0,0.14), 0px 1px 3px 0px rgba(0,0,0,0.12);
8 | }
9 |
10 | .ct-h-scroll.scroll-con {
11 | padding: 5px 0 0 0;
12 |
13 | .react-overflow-wrapper__content {
14 | align-items: unset !important;
15 | }
16 |
17 | .react-overflow-wrapper__icon-right,
18 | .react-overflow-wrapper__icon-left {
19 | background: rgba(255, 255, 255, 0.664) !important;
20 | }
21 |
22 | &.mobile {
23 | overflow: auto !important;
24 |
25 | .react-overflow-wrapper__icon-right,
26 | .react-overflow-wrapper__icon-left {
27 | display: none !important;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/layout/CTLayout/default-sidebar-items/component-api-items.js:
--------------------------------------------------------------------------------
1 | import { links } from 'utils/links';
2 | import { createCTNavSidebarItemProps } from '../../CTNavSidebar/create-props';
3 |
4 | export const getComponentAPINavItem = () => {
5 | return createCTNavSidebarItemProps({
6 | text: 'Components (Dev)',
7 | icon: 'description',
8 | href: links.componentAPI('ct-form'),
9 | active: window.location.pathname.startsWith(links.componentAPI('')),
10 | reloadOnPathnameChange: true,
11 | items: [
12 | {
13 | value: 'capi-ct-form',
14 | text: 'CTForm',
15 | href: links.componentAPI('ct-form')
16 | }
17 | ]
18 | });
19 | };
--------------------------------------------------------------------------------
/src/layout/CTLayout/index.scss:
--------------------------------------------------------------------------------
1 | .ct-nav-header-sb-trigger-con {
2 | display: flex;
3 | flex-direction: row;
4 | align-items: center;
5 | white-space: nowrap;
6 | height: 50px;
7 | padding-left: 5px;
8 | }
9 |
10 | #ct-layout-container {
11 | display: flex;
12 | width: 100%;
13 | height: 100%;
14 | overflow: hidden;
15 |
16 | .ct-layout-main {
17 | width: 100%;
18 |
19 | &.transition {
20 | transition: padding 200ms linear;
21 | }
22 |
23 | &.padded-240 {
24 | padding-left: 240px;
25 | }
26 |
27 | &.padded-50 {
28 | padding-left: 50px;
29 | }
30 |
31 | .ct-layout-fill {
32 | height: calc(100% - 50px);
33 | }
34 |
35 | &.tab-header {
36 | .ct-layout-fill {
37 | height: calc(100% - 87px);
38 | }
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/src/layout/CTList/CTList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import CTFragment from '../CTFragment';
4 | import CTListItem from './CTListItem';
5 |
6 | function CTList(props) {
7 | const {
8 | id,
9 | role = 'list',
10 | className,
11 | items = [],
12 | ...listProps
13 | } = props;
14 |
15 | return (
16 |
17 | {items.map(item => )}
18 |
19 | );
20 | }
21 |
22 | CTList.propTypes = {
23 | /** An unique id */
24 | id: PropTypes.string,
25 |
26 | /** role of the list, default as `list` */
27 | role: PropTypes.string,
28 |
29 | /** Addtional classes */
30 | className: PropTypes.string,
31 |
32 | /** List items using `CTListItem` */
33 | items: PropTypes.arrayOf(PropTypes.shape(CTListItem.propTypes)),
34 | };
35 |
36 | export default CTList;
37 |
38 |
--------------------------------------------------------------------------------
/src/layout/CTList/index.js:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 |
3 | export { default as CTListItem } from './CTListItem';
4 | export { default as CTList } from './CTList';
--------------------------------------------------------------------------------
/src/layout/CTList/index.scss:
--------------------------------------------------------------------------------
1 | .ct-listitem-con {
2 | background-color: #ffffff;
3 | transition: background 200ms ease-in-out;
4 |
5 | &:not(:last-child) {
6 | .ct-listitem-text {
7 | border-bottom: 1px solid rgb(233, 233, 233);
8 | }
9 | }
10 |
11 | .ct-listitem {
12 | padding: 0 25px;
13 | text-align: left;
14 |
15 | span.material-icons {
16 | color: rgb(61, 61, 61);
17 | padding-right: 15px;
18 | font-size: 30px;
19 | pointer-events: none;
20 | }
21 |
22 | .ct-listitem-text {
23 | padding: 10px 0;
24 | }
25 | }
26 |
27 | &:hover, &:focus {
28 | span.material-icons,
29 | .ct-listitem-text .ct-listitem-title {
30 | color: teal !important;
31 | }
32 | }
33 |
34 | &:focus {
35 | background-color: rgb(239, 245, 245);
36 | outline: none;
37 | }
38 |
39 | &:hover {
40 | background-color: rgb(243, 243, 243);
41 | }
42 | }
--------------------------------------------------------------------------------
/src/layout/CTModal/index.js:
--------------------------------------------------------------------------------
1 | export { default as CTModal } from './Modal';
2 | export {
3 | default as CTConfirmation,
4 | useConfirmation as useCTConfirmation
5 | } from './Confirmation';
--------------------------------------------------------------------------------
/src/layout/CTNavHeader/NavHeaderMenu/styles.js:
--------------------------------------------------------------------------------
1 | export const styles = {
2 | menu: {
3 | backgroundColor: '#306868',
4 | color: 'rgb(236, 236, 236)',
5 | minWidth: '280px'
6 | },
7 |
8 | icon: {
9 | color: 'rgb(236, 236, 236)',
10 | fontSize: '1.3rem'
11 | },
12 |
13 | title: {
14 | color: '#d5dedf'
15 | },
16 |
17 | font: {
18 | color: '#d5dedf',
19 | fontSize: '1.15rem'
20 | },
21 | }
--------------------------------------------------------------------------------
/src/layout/CTNavHeader/NavHeaderTabPanel/NavHeaderTab.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 | import { Link } from 'dva/router';
5 |
6 | export function NavHeaderTab(props) {
7 | let {
8 | text,
9 | active = false,
10 | href = false,
11 | } = props;
12 |
13 | const tabClasses = classNames('plain-btn ct-nh-tab', { active })
14 |
15 | return (
16 |
20 |
21 | {text}
22 |
23 |
24 | );
25 | }
26 |
27 | export const NavHeaderTabPropTypes = {
28 | /** The text content of the tab */
29 | text: PropTypes.string,
30 |
31 | /** True if it's the current tab */
32 | active: PropTypes.bool,
33 |
34 | /** The pathname of the tab */
35 | href: PropTypes.string
36 | };
37 |
38 | NavHeaderTab.propTypes = NavHeaderTabPropTypes;
39 |
--------------------------------------------------------------------------------
/src/layout/CTNavHeader/NavHeaderTabPanel/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './index.scss';
4 |
5 | import { NavHeaderTab, NavHeaderTabPropTypes } from './NavHeaderTab';
6 |
7 | export function NavHeaderTabPanel(props) {
8 | let {
9 | tabs = [],
10 | tabTitleElem,
11 | } = props;
12 |
13 | return (
14 |
15 |
16 | {tabTitleElem}
17 |
18 |
19 |
20 | {tabs.map(tab => )}
21 |
22 |
23 | );
24 | }
25 |
26 | export const NavHeaderTabPanelPropsTypes = {
27 | /** Nav tab objects */
28 | tabs: PropTypes.arrayOf(PropTypes.shape(NavHeaderTabPropTypes)),
29 |
30 | /** The Nav Header can have a title element for tabs */
31 | tabTitleElem: PropTypes.node,
32 | };
33 |
34 | NavHeaderTabPanel.propTypes = NavHeaderTabPanelPropsTypes;
35 |
--------------------------------------------------------------------------------
/src/layout/CTNavSidebar/SidebarNavItems.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { v4 as uuid} from 'uuid'
4 | import { SidebarItem, SidebarItemPropTypes } from './SidebarItem';
5 |
6 | export function SidebarNavItems(props) {
7 | let {
8 | darkMode = false,
9 | mini,
10 | items
11 | } = props;
12 |
13 | return (
14 |
15 | {items.map( item => (
16 |
23 | ))}
24 |
25 | );
26 | }
27 |
28 | SidebarNavItems.propTypes = {
29 | /** Sidebar supports dark mode */
30 | darkMode: PropTypes.bool,
31 |
32 | /** The sidebar supports a mini view */
33 | mini: PropTypes.bool,
34 |
35 | /** Nav items */
36 | items: PropTypes.arrayOf(PropTypes.oneOfType([
37 | PropTypes.string,
38 | PropTypes.shape(SidebarItemPropTypes)
39 | ]))
40 | };
41 |
--------------------------------------------------------------------------------
/src/layout/CTPopoverLabel/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { makeStyles } from '@material-ui/core/styles';
4 | import Tooltip from '@material-ui/core/Tooltip';
5 |
6 | export const useStyles = makeStyles({
7 | tooltip: {
8 | backgroundColor: '#363636',
9 | fontSize: '13px'
10 | },
11 | arrow: {
12 | color: '#363636'
13 | }
14 | });
15 |
16 | function CTPopoverLabel(props) {
17 | const {
18 | children,
19 | label,
20 | placement,
21 | disabled
22 | } = props;
23 |
24 | const labelClasses = useStyles();
25 |
26 | return disabled ? children : (
27 |
28 | {children}
29 |
30 | );
31 | }
32 |
33 | CTPopoverLabel.propTypes = {
34 | children: PropTypes.node,
35 | label: PropTypes.node,
36 | placement: PropTypes.string,
37 | disabled: PropTypes.bool
38 | };
39 |
40 | export default CTPopoverLabel;
41 |
42 |
--------------------------------------------------------------------------------
/src/layout/CTText/Paragraph.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Text from './Text';
3 |
4 | function Paragraph(props) {
5 | return (
6 |
7 | );
8 | }
9 |
10 | Paragraph.propTypes = Text.propTypes;
11 |
12 | export default Paragraph;
13 |
14 |
--------------------------------------------------------------------------------
/src/layout/CTText/index.js:
--------------------------------------------------------------------------------
1 | export { default as CTText } from './Text';
2 | export { default as CTParagraph } from './Paragraph';
--------------------------------------------------------------------------------
/src/layout/tools/elem-selectors.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const isRenderable = (Renderable) => {
4 | if (!Renderable) {
5 | return true
6 | } if (Array.isArray(Renderable)) {
7 | return Renderable.reduce((isValid, curr) => (isValid && isRenderable(curr)));
8 | }
9 | return React.isValidElement(Renderable)
10 | };
11 |
12 | export const makeEl = (Renderable, props = {}) => {
13 | if (isRenderable(Renderable)) {
14 | return Renderable;
15 | } if (Renderable) {
16 | return React.createElement(Renderable, props);
17 | }
18 | };
19 |
20 | export const altEl = (
21 | Component,
22 | use,
23 | props,
24 | AltComponent,
25 | altProps = {}
26 | ) => {
27 | let element = null;
28 | if (use) {
29 | element = makeEl(Component, props);
30 | } else if (AltComponent) {
31 | element = makeEl(AltComponent, altProps);
32 | }
33 |
34 | return element;
35 | };
--------------------------------------------------------------------------------
/src/layout/tools/index.js:
--------------------------------------------------------------------------------
1 | export * from './elem-selectors';
--------------------------------------------------------------------------------
/src/model/global.js:
--------------------------------------------------------------------------------
1 | export default {
2 | namespace: 'global',
3 | state: {
4 |
5 | },
6 | reducers: {
7 | delete(state, { payload: id }) {
8 | return state.filter(item => item.id !== id);
9 | },
10 | },
11 | subscriptions: {
12 | setup() {
13 |
14 | }
15 | }
16 | };
--------------------------------------------------------------------------------
/src/redux/example/example.action.types.js:
--------------------------------------------------------------------------------
1 | export const SET_OFFERINGS = 'eg-set-offerings';
--------------------------------------------------------------------------------
/src/redux/example/example.actions.js:
--------------------------------------------------------------------------------
1 | import { createAction } from 'redux/redux-creators';
2 | import {
3 | SET_OFFERINGS
4 | } from './example.action.types';
5 |
6 | export const setOfferings = createAction(SET_OFFERINGS);
--------------------------------------------------------------------------------
/src/redux/example/example.reducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | SET_OFFERINGS
3 | } from './example.action.types';
4 | import { initialState } from './example.state.js';
5 |
6 | const exampleReducer = (state = initialState, action) => {
7 | const { type, value } = action;
8 |
9 | switch (type) {
10 | case SET_OFFERINGS:
11 | return { ...state, offerings: value };
12 |
13 | default:
14 | return state;
15 | }
16 | };
17 |
18 | export default exampleReducer;
19 |
--------------------------------------------------------------------------------
/src/redux/example/example.state.js:
--------------------------------------------------------------------------------
1 | export const initialState = {
2 | offerings: []
3 | };
--------------------------------------------------------------------------------
/src/redux/example/index.js:
--------------------------------------------------------------------------------
1 | import { createReduxStore, createSelector } from '../redux-creators';
2 | import exampleReducer from './example.reducers';
3 | import * as exampleActions from './example.actions';
4 |
5 | export const connectWithRedux = createSelector(exampleActions);
6 |
7 | export const exampleStore = createReduxStore(exampleReducer);
8 |
--------------------------------------------------------------------------------
/src/redux/media-settings/transcriptions/index.js:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/redux/media-settings/transcriptions/trans.action.types.js:
--------------------------------------------------------------------------------
1 | export const SET_TRANSCRIPTIONS = 'trans-set-trans';
--------------------------------------------------------------------------------
/src/redux/media-settings/transcriptions/trans.actions.js:
--------------------------------------------------------------------------------
1 | import { createAction } from '../../redux-creators';
2 | import {
3 | SET_TRANSCRIPTIONS
4 | } from './trans.action.types';
5 |
6 | export const setTranscriptions = createAction(SET_TRANSCRIPTIONS);
--------------------------------------------------------------------------------
/src/redux/media-settings/transcriptions/trans.reducers.js:
--------------------------------------------------------------------------------
1 | import {
2 | SET_TRANSCRIPTIONS
3 | } from './trans.action.types';
4 | import { initialState } from './trans.state';
5 |
6 | const transReducer = (state = initialState, action) => {
7 | const { type, value } = action;
8 |
9 | switch (type) {
10 | case SET_TRANSCRIPTIONS:
11 | return { ...state, transcriptions: value };
12 |
13 | default:
14 | return state;
15 | }
16 | };
17 |
18 | export default transReducer;
--------------------------------------------------------------------------------
/src/redux/media-settings/transcriptions/trans.state.js:
--------------------------------------------------------------------------------
1 | import { ARRAY_INIT } from 'utils';
2 |
3 | export const initialState = {
4 | transcriptions: ARRAY_INIT
5 | };
--------------------------------------------------------------------------------
/src/redux/redux-provider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 |
4 | /**
5 | * Connect the component to the provided redux store
6 | * @param {*} Component - the component to connect
7 | * @param {*} reduxStore - the redux store to this component
8 | * @param {Function} connectWithRedux - the redux selector to this store
9 | * @param {String[]} requestedStates - the requested states' names
10 | * @param {String[]} requestedDispatches - the requested dispatch functions' names
11 | */
12 | export function withReduxProvider(
13 | Component,
14 | reduxStore,
15 | connectWithRedux,
16 | requestedStates = [],
17 | requestedDispatches = [],
18 | ) {
19 | function ConnectedComponent(props) {
20 | const ConnectToRedux = connectWithRedux(
21 | Component,
22 | requestedStates,
23 | requestedDispatches
24 | );
25 |
26 | return (
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | return ConnectedComponent;
34 | }
--------------------------------------------------------------------------------
/src/screens/Admin/Alerts/index.css:
--------------------------------------------------------------------------------
1 | .general-alert {
2 | margin-top: 0.5rem;
3 | margin-bottom: -0.5rem;
4 | }
--------------------------------------------------------------------------------
/src/screens/Admin/Alerts/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert } from 'react-bootstrap';
3 | import './index.css';
4 |
5 | import alertMesgs from './AlertMesgs';
6 |
7 | export function GeneralAlert({ width, fixed, open, type, onClose }) {
8 | const mesg = alertMesgs[type];
9 | return (
10 |
18 | {mesg.header ? {mesg.header} : <>>}
19 | {mesg.detail ? {mesg.detail}
: <>>}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/screens/Admin/EventLogs/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DownloadLogs from './DownloadLogs';
3 |
4 | export default function More() {
5 | return (
6 |
7 |
Download Event Logs
8 |
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/screens/Admin/helpers.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | export function updateJson(old, changed) {
4 | old = _.clone(old);
5 | const res = {};
6 | Object.keys(old).forEach((key) => {
7 | res[key] = old[key] || changed[key];
8 | });
9 |
10 | return res;
11 | }
12 |
13 | export function momentToISOString(moment) {
14 | if (typeof moment === 'string') return moment;
15 | const date = moment.toDate();
16 | date.setUTCHours(0, 0, 0);
17 | return date.toISOString();
18 | }
19 |
--------------------------------------------------------------------------------
/src/screens/Analytics/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { CTLayout } from 'layout';
3 | import { api, links } from 'utils';
4 |
5 | export class Analytics extends Component {
6 | componentDidMount() {
7 | api.contentLoaded();
8 | links.title('Analytics');
9 | }
10 |
11 | render() {
12 | const layoutProps = CTLayout.createProps({
13 | transition: true,
14 | responsive: true,
15 | footer: true,
16 | headingProps: {
17 | heading: 'Personal Analytics',
18 | icon: 'bar_chart',
19 | sticky: true,
20 | gradient: true,
21 | offsetTop: 30
22 | }
23 | });
24 |
25 | return (
26 |
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/screens/Asl/model.js:
--------------------------------------------------------------------------------
1 |
2 | const AslModel = {
3 | namespace: 'aslpage',
4 | state: {
5 |
6 | },
7 | reducers: {
8 |
9 | setWatchHistories() {
10 | return null;
11 | }
12 | },
13 | }
14 | export default AslModel
--------------------------------------------------------------------------------
/src/screens/Authentication/AuthCallback.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { user, links } from 'utils';
3 |
4 | function AuthCallback() {
5 | useEffect(() => {
6 | switch (window.location.pathname) {
7 | case links.auth0Callback():
8 | user.auth0Setup();
9 | break;
10 | case links.ciLogonCallback():
11 | user.ciLogonSetup();
12 | break;
13 | default:
14 | user.execCloseAfterSignIn();
15 | window.location = links.home();
16 | break;
17 | }
18 | }, []);
19 |
20 | return ;
21 | }
22 |
23 | export default AuthCallback;
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/screens/Authentication/SignIn/index.scss:
--------------------------------------------------------------------------------
1 | #ct-signin-main {
2 | background: teal;
3 | }
4 |
5 | .ct-signin-card {
6 | width: 500px;
7 | max-width: 100%;
8 | border-radius: 10px;
9 | border: solid 1px rgb(233, 233, 233);
10 | padding: 30px 0;
11 | background: white;
12 |
13 | &.shadow {
14 | border: none;
15 | box-shadow: 0 0 8px 8px rgb(44, 44, 44);
16 | }
17 | }
18 |
19 | @media screen and (max-width: 520px) {
20 | .ct-signin-card {
21 | width: 100%;
22 | height: 100%;
23 | max-width: 100%;
24 | border-radius: 0;
25 | border: none;
26 | padding: 30px 0;
27 | background: white;
28 | display: flex;
29 | flex-direction: column;
30 | justify-content: center;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/screens/Authentication/index.js:
--------------------------------------------------------------------------------
1 | export { default as SignIn } from './SignIn';
2 | export { default as AuthCallback } from './AuthCallback';
--------------------------------------------------------------------------------
/src/screens/ComponentAPI/docs-selector.js:
--------------------------------------------------------------------------------
1 | import { Fragment } from 'react';
2 | import { CTFormUsage } from 'docs';
3 |
4 | const createDocsData = (title, Component) => ({
5 | title,
6 | Component: Component || Fragment
7 | });
8 |
9 | /**
10 | * Get the docs data for a component
11 | * @param {String} type - the type of the component api
12 | * @returns {{title:string, Component:ReactNode}} the docs data
13 | */
14 | export default (type) => {
15 | const docsSelector = {
16 | 'ct-form': createDocsData('CTForm', CTFormUsage)
17 | };
18 |
19 | return docsSelector[type] || createDocsData('Unknown');
20 | }
--------------------------------------------------------------------------------
/src/screens/ComponentAPI/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useParams } from 'dva/router';
3 | import { useCTDocTitle, useLoaded } from 'hooks';
4 | import { CTLayout } from 'layout';
5 | import { MDXDocsContainer } from 'docs/layouts';
6 | import docsSelector from './docs-selector';
7 |
8 | export function ComponentAPI() {
9 | const { type } = useParams();
10 | const Docs = docsSelector(type || 'ct-form');
11 |
12 | useLoaded();
13 | useCTDocTitle(`${Docs.title} | Component API`);
14 |
15 | const layoutProps = CTLayout.createProps({
16 | transition: true,
17 | responsive: true,
18 | footer: true,
19 | headingProps: {
20 | heading: Docs.title,
21 | icon: 'description',
22 | sticky: false,
23 | gradient: true,
24 | offsetTop: 30
25 | }
26 | });
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
34 | );
35 | }
--------------------------------------------------------------------------------
/src/screens/Course/components/CourseInfo/ActionButtons/CourseAnalyticsButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import PropTypes from 'prop-types';
4 | import { Link } from 'dva/router';
5 | import IconButton from '@material-ui/core/IconButton';
6 | import BarChartIcon from '@material-ui/icons/BarChart';
7 | import { links } from 'utils/links';
8 | import { useButtonStyles, CTPopoverLabel } from 'layout';
9 |
10 | function CourseAnalyticsButton(props) {
11 | const { offeringId } = props;
12 |
13 | const btn = useButtonStyles();
14 |
15 | return (
16 |
17 |
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | CourseAnalyticsButton.propTypes = {
29 | offeringId: PropTypes.string
30 | };
31 |
32 | export default CourseAnalyticsButton;
33 |
34 |
--------------------------------------------------------------------------------
/src/screens/Course/components/CourseInfo/ActionButtons/CourseSettingsButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import PropTypes from 'prop-types';
4 | import { Link } from 'dva/router';
5 | import IconButton from '@material-ui/core/IconButton';
6 | import SettingsIcon from '@material-ui/icons/Settings';
7 | import { links } from 'utils/links';
8 | import { useButtonStyles, CTPopoverLabel } from 'layout';
9 |
10 | function CourseSettingsButton(props) {
11 | const { offeringId } = props;
12 |
13 | const btn = useButtonStyles();
14 |
15 | return (
16 |
17 |
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | CourseSettingsButton.propTypes = {
29 | offeringId: PropTypes.string
30 | };
31 |
32 | export default CourseSettingsButton;
33 |
34 |
--------------------------------------------------------------------------------
/src/screens/Course/components/CourseInfo/ActionButtons/InstModeCheckBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTCheckbox } from 'layout';
3 |
4 | function InstModeCheckBox({
5 | isInstMode, onChange
6 | }) {
7 | return (
8 |
13 | );
14 | }
15 |
16 | export default InstModeCheckBox;
17 |
18 |
--------------------------------------------------------------------------------
/src/screens/Course/components/CourseInfo/ActionButtons/StarButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import PropTypes from 'prop-types';
4 | import Button from '@material-ui/core/Button';
5 | import StarIcon from '@material-ui/icons/Star';
6 | import StarBorderIcon from '@material-ui/icons/StarBorder';
7 | import { useButtonStyles } from 'layout';
8 |
9 | function StarButton(props) {
10 | const { isStarred, onStarAction } = props;
11 | // const setup = {};
12 | const btn = useButtonStyles();
13 | return (
14 | : }
18 | size="large"
19 | onClick={() => onStarAction(!isStarred)}
20 | >
21 | {isStarred ? 'Unstar' : 'Star'}
22 |
23 | );
24 | }
25 |
26 | StarButton.propTypes = {
27 | isStarred: PropTypes.bool
28 | };
29 |
30 | export default StarButton;
31 |
32 |
--------------------------------------------------------------------------------
/src/screens/Course/components/Playlists/NewPlaylistButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import { Link } from 'dva/router';
4 | import Button from '@material-ui/core/Button';
5 | import AddIcon from '@material-ui/icons/Add';
6 | import { links } from 'utils/links';
7 | import { useButtonStyles } from 'layout';
8 |
9 | function NewPlaylistButton({
10 | offeringId
11 | }) {
12 | const btn = useButtonStyles();
13 |
14 | return (
15 | }
21 | to={links.instNewPlaylist(offeringId)}
22 | >
23 | new playlist
24 |
25 | );
26 | }
27 |
28 | export default NewPlaylistButton;
29 |
30 |
--------------------------------------------------------------------------------
/src/screens/Course/components/Playlists/PlaylistItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'dva/router';
3 | import { CTFragment } from 'layout';
4 | import { links } from 'utils/links';
5 |
6 | function PlaylistItem({
7 | isInstMode,
8 | playlist,
9 | offering
10 | }) {
11 | const { id, name } = playlist;
12 |
13 | const linkToInstPl = {
14 | pathname: links.playlist(id),
15 | state: { playlist, offering }
16 | };
17 |
18 | return (
19 |
23 |
24 | video_library
25 | {name}
26 |
27 | chevron_right
28 |
29 | );
30 | }
31 |
32 | export default PlaylistItem;
33 |
--------------------------------------------------------------------------------
/src/screens/Course/components/index.js:
--------------------------------------------------------------------------------
1 | export { CourseInfo } from './CourseInfo';
2 | export { Playlists } from './Playlists';
--------------------------------------------------------------------------------
/src/screens/Course/util.js:
--------------------------------------------------------------------------------
1 | import {
2 | elem,
3 | INSTRUCTOR
4 | } from 'utils';
5 |
6 | export default {
7 | isInstructor(role) {
8 | return role === INSTRUCTOR;
9 | },
10 | scrollToPlaylist(playlistId) {
11 | if (playlistId) {
12 | elem.scrollIntoCenter(playlistId, { focus: true });
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/screens/EPub/components/ChapterEditButton/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import { useCustomizedButton } from 'hooks';
4 | import './index.scss';
5 |
6 | function ChapterEditButton({
7 | muted,
8 | children,
9 | attached,
10 | onClick,
11 | className,
12 | ...props
13 | }) {
14 | const btnClasses = cx(
15 | 'ct-epb', 'clickable', 'bordered', 'ch-edit-btn',
16 | {
17 | 'text-muted': muted
18 | },
19 | attached,
20 | className
21 | );
22 |
23 | const clickProps = useCustomizedButton(onClick);
24 |
25 | return (
26 |
27 | {children}
28 |
29 | )
30 | }
31 |
32 | export default ChapterEditButton;
33 |
--------------------------------------------------------------------------------
/src/screens/EPub/components/ChapterEditButton/index.scss:
--------------------------------------------------------------------------------
1 | .ct-epb.ch-edit-btn {
2 | padding: 9px;
3 | width: 100%;
4 | font-size: 14px;
5 | cursor: pointer;
6 |
7 | &.top {
8 | border-top: none !important;
9 | padding-top: 13px;
10 | }
11 |
12 | &.bottom {
13 | border-bottom: none !important;
14 | padding-bottom: 13px;
15 | }
16 | }
--------------------------------------------------------------------------------
/src/screens/EPub/components/ChapterText/index.scss:
--------------------------------------------------------------------------------
1 | .ct-epb.ch-text-con {
2 | width: 100%;
3 | margin: 10px 0;
4 |
5 | &.attached {
6 | margin: 0;
7 |
8 | .ct-epb.clickable.bordered[data-empty=true] {
9 | padding-top: 0;
10 | color: rgb(85, 85, 85);
11 | // text-align: center;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/src/screens/EPub/components/ChapterTitle/index.scss:
--------------------------------------------------------------------------------
1 | .ct-epb.ch-edit-title {
2 | width: 100%;
3 | border: none;
4 | outline: none;
5 | cursor: pointer;
6 | display: flex;
7 | flex-direction: row;
8 | align-items: center;
9 | margin-top: 10px;
10 |
11 | &.focused,
12 | &:focus-within {
13 | cursor: text;
14 | }
15 |
16 | .ch-edit-title-txt {
17 | width: 100%;
18 | margin: 0;
19 | padding: 5px 9px 6px 9px;
20 |
21 | // &:focus {
22 | // outline: none;
23 | // }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/screens/EPub/components/DescriptionText/index.scss:
--------------------------------------------------------------------------------
1 | .ct-epb.ch-text-con {
2 | width: 100%;
3 | margin: 10px 0;
4 |
5 | &.attached {
6 | margin: 0;
7 |
8 | .ct-epb.clickable.bordered[data-empty=true] {
9 | padding-top: 0;
10 | color: rgb(85, 85, 85);
11 | // text-align: center;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/src/screens/EPub/components/EPubHeader/EPubRightActions.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTFragment } from 'layout';
3 | import ViewDropdown from './ViewDropdown';
4 |
5 | function EPubRightActions() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default EPubRightActions;
14 |
--------------------------------------------------------------------------------
/src/screens/EPub/components/EPubHeader/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTNavHeader, CTBrand, makeEl } from 'layout';
3 | import EPubTitle from './EPubTitle';
4 | import EPubRightActions from './EPubRightActions';
5 | import EPubToolbar from './EPubToolbar';
6 | import './index.scss';
7 |
8 | function EPubHeader() {
9 | const brandElement = makeEl(CTBrand, { logo: true, medium: true });
10 | const titleElement = makeEl(EPubTitle);
11 | const rightActionElement = makeEl(EPubRightActions);
12 | const toolbarElement = makeEl(EPubToolbar);
13 |
14 | return (
15 |
25 | );
26 | }
27 |
28 | export default EPubHeader;
29 |
--------------------------------------------------------------------------------
/src/screens/EPub/components/EPubInstruction/index.scss:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////
2 | // ePub Instruction
3 | ////////////////////////////////////////////////////////////
4 | .ct-epb.instruction {
5 | margin: 0 10px !important;
6 | border-radius: 0 !important;
7 | box-shadow: none;
8 | color: rgb(36, 36, 36);
9 | font-size: 15px;
10 | line-height: 20px;
11 | border-right: 4px rgb(3, 163, 163) solid;
12 | background-color: #e2f1f179;
13 | white-space: pre-line;
14 |
15 | .MuiAccordionSummary-root.Mui-expanded {
16 | min-height: 0 !important;
17 | }
18 |
19 | .MuiAccordionSummary-root {
20 | padding: 0 10px !important;
21 | .MuiAccordionSummary-content {
22 | margin: 6px 0 !important;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/screens/EPub/components/EPubNavigation/NavigationTrigger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IconButton from '@material-ui/core/IconButton';
3 | import { CTPopoverLabel } from 'layout';
4 | import { epub } from '../../controllers';
5 |
6 | function NavigationTrigger({ show, onToggle }) {
7 | const label = `${show ? 'Close' : 'Open' } Chapter Navigation (⌘B)`;
8 |
9 | return (
10 |
11 |
12 |
18 |
19 | {show ? 'chevron_left' : 'list'}
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | export default NavigationTrigger;
28 |
--------------------------------------------------------------------------------
/src/screens/EPub/components/EPubNavigation/index.js:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 |
3 | export { default as EPubNavigationProvider } from './NavigationProvider';
--------------------------------------------------------------------------------
/src/screens/EPub/components/MDEditorModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { CTMarkdownEditor } from 'components';
3 | import { CTModal, CTFragment } from 'layout';
4 |
5 | function MDEditorModal({
6 | show,
7 | value,
8 | onSave,
9 | onClose,
10 | title = 'Edit Markdown Text'
11 | }) {
12 | return (
13 |
21 |
22 |
29 |
30 |
31 | );
32 | }
33 |
34 | export default MDEditorModal;
35 |
--------------------------------------------------------------------------------
/src/screens/EPub/components/Markdown.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTMarkdownEditor, CTMarkdownPreviewer } from 'components';
3 |
4 | export function MDEditor({
5 | id = '',
6 | defaultValue = '',
7 | placeholder = '\n\n#### Transcript\n\n',
8 | height = '400px',
9 | onSave,
10 | onClose,
11 | attached
12 | }) {
13 | const editorProps = {
14 | id,
15 | defaultValue,
16 | placeholder,
17 | height,
18 | onSave,
19 | onClose,
20 | attached
21 | };
22 |
23 | return ;
24 | }
25 |
26 | export function MDPreviewer({ value, className, id, ...otherProps }) {
27 | return (
28 |
34 | );
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/src/screens/EPub/components/PlayerModal/index.scss:
--------------------------------------------------------------------------------
1 | .ct-epb.player-modal {
2 | background: black !important;
3 | h3 {
4 | width: max-content !important;
5 | }
6 | .ct-epb.player-modal-player-con {
7 | width: 100%;
8 | height: 600px;
9 | max-height: 80vh;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/screens/EPub/components/ShortcutModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTShortcutModal } from 'components/Shortcut';
3 | import { shortcuts } from '../controllers/constants/shortcuts';
4 | import { connectWithRedux } from '../controllers';
5 |
6 | function ShortcutModal({ showShortcuts, dispatch }) {
7 | const onClose = () => dispatch({ type: 'epub/setShowShortcuts', payload: false });
8 |
9 | return (
10 |
16 | );
17 | }
18 |
19 | export default connectWithRedux(
20 | ShortcutModal,
21 | ['showShortcuts']
22 | );
23 |
--------------------------------------------------------------------------------
/src/screens/EPub/components/Tags/TagGroup.scss:
--------------------------------------------------------------------------------
1 | .ct-epub-tag-dialog {
2 | min-width: 500px;
3 | }
4 |
5 | .tagGroup {
6 | display: flex;
7 | flex-direction: row;
8 | align-items: center;
9 | margin-left: 10px;
10 | .ct-epb-dropdown-button {
11 | margin-left: 20px;
12 | }
13 | #ct-epub-tag-dropdown {
14 | width: max-content;
15 | min-width: 180px;
16 | white-space: nowrap;
17 | font-size: 14px;
18 | color: rgb(47, 47, 47);
19 | }
20 | }
--------------------------------------------------------------------------------
/src/screens/EPub/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as EPubHeader } from './EPubHeader';
2 | export { EPubNavigationProvider } from './EPubNavigation';
3 | export { default as EPubInstruction } from './EPubInstruction';
4 | export * from './Markdown';
5 |
6 | export { default as ChapterTitle } from './ChapterTitle';
7 | export { default as ChapterText } from './ChapterText';
8 | export { default as ChapterImage } from './ChapterImage';
9 | export { default as ChapterEditButton } from './ChapterEditButton';
10 |
11 | export { default as ImagePickerModal } from './ImagePickerModal';
12 | export { default as MDEditorModal } from './MDEditorModal';
13 | export { default as PlayerModal } from './PlayerModal';
14 | export { default as ShortcutModal } from './ShortcutModal';
15 | export { default as EPubFileInfoModal } from './EPubFileInfoModal';
16 | export { default as EPubCopyModal } from './EPubCopyModal';
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/PreferenceController.js:
--------------------------------------------------------------------------------
1 | import { CTPreferenceV2 } from 'utils/user-preference';
2 |
3 | class PreferenceController extends CTPreferenceV2 {
4 | UserOnboardKey = 'epb-onboard';
5 |
6 | get isUserOnboard() {
7 | return this.isTrue(this.UserOnboardKey);
8 | }
9 |
10 | userOnboard = () => {
11 | this.setTrue(this.UserOnboardKey)
12 | }
13 | }
14 |
15 | export default PreferenceController;
16 | export const epubPref = new PreferenceController();
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/constants/EPubConstants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * The contants used for generating ePub files
3 | */
4 | export default class EPubConstants {
5 | // errors
6 | static EPubDataNotRequestedError = 'epub-not-requested';
7 | static EPubDataRequestedError = 'epub-requested';
8 |
9 | // view options
10 | static EpbReadOnly = 'v-read-only';
11 | static EditINote = 'v-edit-inote'
12 |
13 | static EpbDefaultView = EPubConstants.EditINote;
14 | static EPubViews = [
15 | EPubConstants.EpbReadOnly,
16 | EPubConstants.EditINote
17 | ];
18 |
19 | // saving status
20 | static EpbUnsaved = 0;
21 | static EpbSaving = 1;
22 | static EpbSaved = 2;
23 | static EpbSaveFailed = 3;
24 |
25 | // nav
26 | static EPubNavShowing = 'show';
27 | static EPubNavHiding = 'hide';
28 | static EPubNavClosed = null;
29 |
30 | // in window.location.hash
31 | // from =
32 | static HFromNew = 'new'
33 | }
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/constants/shortcuts.js:
--------------------------------------------------------------------------------
1 | export const shortcuts = [
2 | {
3 | category: '',
4 | actions: [
5 | {
6 | name: 'Save Changes',
7 | keys: [['Shift⇧', 's']]
8 | }, {
9 | name: 'Open Keyboard Shortcuts',
10 | keys: [['Shift⇧', '/']]
11 | }, {
12 | name: 'Toggle Chapter Navigation Menu',
13 | keys: [['Shift⇧', 'b']]
14 | }, {
15 | name: 'View or Download I-Note',
16 | keys: [['Shift⇧', '1']]
17 | }, {
18 | name: 'Edit I-Note',
19 | keys: [['Shift⇧', '2']]
20 | }
21 | ]
22 | }
23 | ]
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/epub/container.xml.js:
--------------------------------------------------------------------------------
1 | export default '';
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/epub/content.xhtml.js:
--------------------------------------------------------------------------------
1 | import { dedent } from 'dentist';
2 |
3 | export default ({
4 | title = '',
5 | content = '',
6 | language = ''
7 | }) => dedent(`
8 |
9 |
10 |
11 |
12 |
13 | ${title}
14 |
15 |
16 |
17 |
18 |
19 |
20 | ${content}
21 |
22 |
23 |
24 | `);
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/epub/epub.css:
--------------------------------------------------------------------------------
1 | .epub-author {
2 | color: #555;
3 | }
4 |
5 | .epub-link {
6 | margin-bottom: 30px;
7 | }
8 |
9 | .epub-link a {
10 | color: #666;
11 | font-size: 90%;
12 | }
13 |
14 | .toc-author {
15 | font-size: 90%;
16 | color: #555;
17 | }
18 |
19 | .toc-link {
20 | color: #999;
21 | font-size: 85%;
22 | display: block;
23 | }
24 |
25 | .table-of-content {
26 | page-break-before: avoid;
27 | page-break-inside: avoid;
28 | }
29 |
30 | #root h2,
31 | #root h3 {
32 | page-break-after: avoid;
33 | page-break-before: always;
34 | }
35 |
36 | #root p {
37 | widows: 2;
38 | orphans: 2;
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/epub/index.js:
--------------------------------------------------------------------------------
1 | import raw from 'raw.macro';
2 | import { ROOT_CSS } from '../styles';
3 |
4 | export const OEBPS_STYLE_CSS = ROOT_CSS + raw('./epub.css');
5 |
6 | export { default as MIMETYPE } from './mimetype.js';
7 | export { default as META_INF_CONTAINER_XML } from './container.xml.js';
8 | export { default as OEBPS_TOC_NCX } from './toc.ncx.js';
9 | export { default as OEBPS_TOC_XHTML } from './toc.xhtml.js';
10 | export { default as OEBPS_CONTENT_OPF } from './content.opf.js'
11 | export { default as OEBPS_CONTENT_XHTML } from './content.xhtml.js';
12 |
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/epub/mimetype.js:
--------------------------------------------------------------------------------
1 | export default 'application/epub+zip';
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/epub/toc.xhtml.js:
--------------------------------------------------------------------------------
1 | import { dedent } from 'dentist';
2 |
3 | export default ({
4 | title = '',
5 | language = '',
6 | navContents = ''
7 | }) =>
8 | dedent(`
9 |
10 |
11 |
12 |
13 | ${title}
14 |
15 |
16 |
17 |
18 | ${title}
19 | Table Of Contents
20 |
28 |
29 |
30 |
31 | `);
32 |
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/html/index.js:
--------------------------------------------------------------------------------
1 | import raw from 'raw.macro';
2 | import { ROOT_CSS } from '../styles';
3 |
4 | export const PRISM_JS = raw('./prism.js.txt');
5 | export const STYLE_CSS = raw('./style.css') + ROOT_CSS;
6 | export { default as INDEX_HTML_LIVE } from './index.live.html.js';
7 | export { default as INDEX_HTML_LOCAL } from './index.local.html.js';
8 |
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/latex/index.js:
--------------------------------------------------------------------------------
1 | export {default as INDEX_LATEX} from './template.js';
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/latex/template.js:
--------------------------------------------------------------------------------
1 | export default({
2 | title = '',
3 | content = '',
4 | author = ''
5 | }) => `
6 | \\documentclass[11pt]{article}
7 | \\usepackage{amsmath, graphicx}
8 | \\title{${title}}
9 | \\author{${author}}
10 | \\begin{document}
11 | \\maketitle
12 | \\newpage
13 | ${content}
14 | \\end{document}
15 | `;
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/pdf/index.js:
--------------------------------------------------------------------------------
1 | export { newPDF, STYLE_SHEET, placeholderImg } from './pdfstyle.js';
2 | export { TextBox } from './textbox.js'
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/file-templates/styles/index.js:
--------------------------------------------------------------------------------
1 | import raw from 'raw.macro';
2 |
3 | export const KATEX_MIN_CSS = raw('./katex.min.css');
4 | export const ROOT_CSS = raw('./root.css');
5 | export const PRISM_CSS = raw('./prism.css');
6 |
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/index.js:
--------------------------------------------------------------------------------
1 | export { LoadImageError } from './EPubParser';
2 |
3 | export { default as HTMLFileBuilder } from './HTMLFileBuilder';
4 | export { default as EPubFileBuilder } from './EPubFileBuilder';
5 | export { default as LatexFileBuilder } from './LatexFileBuilder';
6 | export { default as PDFFileBuilder } from './PDFFileBuilder';
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/file-builders/utils.js:
--------------------------------------------------------------------------------
1 | export function epubIsText(content) {
2 | return typeof content === "string" || (typeof content === "object" && "text" in content);
3 | }
4 |
5 | export function epubIsImage(content) {
6 | return typeof content === "object" && "src" in content;
7 | }
--------------------------------------------------------------------------------
/src/screens/EPub/controllers/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'dva'
2 | import EPubConstants from './constants/EPubConstants';
3 | import EPubIDs from './constants/EPubIDs';
4 | import EPubDownloadController from './EPubDownloadController';
5 | import { epubPref } from './PreferenceController';
6 |
7 | export const epub = {
8 | const: EPubConstants,
9 | id: EPubIDs,
10 | download: EPubDownloadController,
11 | history: {}, // epubData.history, NOT IMPLEMENTED
12 | pref: epubPref
13 | };
14 | // Transition Function
15 | export const connectWithRedux = (Component, property) => {
16 | return connect(({ epub : _epub}) => {
17 | if (!property) {
18 | return {};
19 | }
20 | const props = {};
21 | property.map((key) => {
22 | props[key] = _epub[key];
23 | return false;
24 | })
25 | return props;
26 | })(Component);
27 | }
28 | export * from './utils';
29 | export * from './onboard-guide';
--------------------------------------------------------------------------------
/src/screens/EPub/index.scss:
--------------------------------------------------------------------------------
1 | #ct-epb-main {
2 | min-width: 1200px;
3 | height: 100%;
4 |
5 | #ct-epb-view-con {
6 | height: 100%;
7 | padding-top: 75px;
8 | }
9 | }
10 |
11 | .ct-epb.clickable {
12 | border: dashed 1px rgba(0, 0, 0, 0);
13 | transition: all linear 200ms;
14 |
15 | &.bordered {
16 | border-color: rgb(197, 197, 197);
17 | }
18 |
19 | &:hover {
20 | border-color: rgb(41, 41, 41);
21 | background-color: rgb(251, 251, 251);
22 | }
23 |
24 | &:focus {
25 | border-color: rgba(0, 0, 0, 0);
26 | background-color: rgb(246, 248, 248);
27 | }
28 | }
--------------------------------------------------------------------------------
/src/screens/EPub/service.js:
--------------------------------------------------------------------------------
1 | import { api } from 'utils';
2 | import ErrorTypes from 'entities/ErrorTypes';
3 |
4 | export async function getEPubById(ePubId) {
5 | try {
6 | const { data } = await api.getEPubById(ePubId);
7 | return data;
8 | } catch (error) {
9 | return ErrorTypes.getError(error);
10 | }
11 | }
12 | export async function getMediaById(mediaId) {
13 | try {
14 | const { data } = await api.getMediaById(mediaId);
15 | return api.parseMedia(data);
16 | } catch (error) {
17 | return ErrorTypes.getError(error);
18 | }
19 | }
--------------------------------------------------------------------------------
/src/screens/EPub/views/EditEPubChapter/ChapterEditor/index.scss:
--------------------------------------------------------------------------------
1 | .ct-epb.ech.ch-con {
2 | padding: 20px 20px 30px 20px;
3 | margin: 40px 20px 50% 20px;
4 |
5 | .ct-epb.ech.ch-info {
6 | width: 100%;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/screens/EPub/views/EditEPubChapter/index.scss:
--------------------------------------------------------------------------------
1 | .ct-epb.ech-tool-bar {
2 | width: calc(33% - 40px);
3 | margin-left: 40px;
4 | padding: 30px 10px 30px 0px;
5 | }
--------------------------------------------------------------------------------
/src/screens/EPub/views/EditEPubStructure/ChapterList/ChapterTitleButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button } from 'pico-ui';
3 | import { CTPopoverLabel } from 'layout';
4 |
5 | function ChapterTitleButton({
6 | show=false,
7 | content="",
8 | icon="",
9 | color="transparent",
10 | outlined=true,
11 | className="",
12 | onClick=null,
13 | }) {
14 | return show ? (
15 |
16 |
17 |
25 |
26 |
27 | ) : null;
28 | }
29 |
30 | export default ChapterTitleButton;
31 |
--------------------------------------------------------------------------------
/src/screens/EPub/views/EditEPubStructure/EPubItemView/index.scss:
--------------------------------------------------------------------------------
1 | .epb-item-view {
2 | margin: 10px 0;
3 | padding: 10px 15px 20px 15px;
4 | border-top: 1px solid rgb(231, 231, 231);
5 | border-bottom: 1px solid rgb(231, 231, 231);
6 |
7 | img {
8 | width: 100%;
9 | margin-bottom: 15px;
10 | }
11 | }
--------------------------------------------------------------------------------
/src/screens/EPub/views/index.js:
--------------------------------------------------------------------------------
1 | export { default as EditEPubStructure } from './EditEPubStructure';
2 | export { default as EditEPubChapter } from './EditEPubChapter';
3 | export { default as ViewAndDownload } from './ViewAndDownload';
4 | export {default as EditINote } from './EditINote';
--------------------------------------------------------------------------------
/src/screens/Example/components/OfferingList/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTFragment } from 'layout';
3 | import { connectWithRedux } from '../../controllers';
4 |
5 | export function OfferingListWithRedux({
6 | offerings
7 | }) {
8 | const loading = offerings.length === 0;
9 |
10 | return (
11 |
12 | {
13 | offerings.map(offering => (
14 | {offering.offering.courseName}
15 | ))
16 | }
17 |
18 | )
19 | }
20 |
21 | export const OfferingList = connectWithRedux(
22 | OfferingListWithRedux,
23 | ['offerings'],
24 | []
25 | )
--------------------------------------------------------------------------------
/src/screens/Example/components/index.js:
--------------------------------------------------------------------------------
1 | export { OfferingList } from './OfferingList';
--------------------------------------------------------------------------------
/src/screens/Example/controllers/index.js:
--------------------------------------------------------------------------------
1 | export { exampleStore, connectWithRedux } from 'redux/example';
2 | export { setup } from './setup';
--------------------------------------------------------------------------------
/src/screens/Example/controllers/setup.js:
--------------------------------------------------------------------------------
1 | import { StateController } from "utils/state-controller";
2 | import { api } from 'utils';
3 |
4 | class SetupExamplePage extends StateController {
5 | init(props) {
6 | const { setOfferings } = props;
7 |
8 | this.register({ setOfferings });
9 | }
10 |
11 | offerings = []
12 | setOfferings(offerings) {
13 | this.setState('setOfferings', 'offerings', offerings);
14 | }
15 |
16 | async setupExamplePage() {
17 | let { data } = await api.getOfferingsByStudent();
18 | this.setOfferings(data);
19 | api.contentLoaded();
20 | }
21 | }
22 |
23 | export const setup = new SetupExamplePage();
--------------------------------------------------------------------------------
/src/screens/Glossary/components/GlossaryAddForm/index.scss:
--------------------------------------------------------------------------------
1 | .edit-textbox {
2 | margin: 30px;
3 | width: 100px;
4 | }
5 |
6 | .editform {
7 | // position: relative;
8 | display: flex;
9 | flex-direction: row;
10 | flex-wrap: wrap;
11 | padding: 10px;
12 | }
13 | .warning {
14 | color: red;
15 | font-size: 16px;
16 | margin: 0;
17 | padding: 0;
18 | margin-left: 30px;
19 | margin-top: 0px;
20 | margin-bottom: 10px;
21 | }
--------------------------------------------------------------------------------
/src/screens/Glossary/components/GlossaryEditForm/index.scss:
--------------------------------------------------------------------------------
1 | // #descriptioninput {
2 | // width: 200px;
3 | // height: 60px;
4 | // overflow-wrap: normal;
5 | // overflow: scroll;
6 | // }
7 |
8 | // #GlossaryEditForm {
9 | // width: 400px;
10 | // height: auto;
11 | // background-color: antiquewhite;
12 | // z-index: 11;
13 | // position: absolute;
14 | // top: 40px;
15 | // left: 200px;
16 | // }
17 |
18 | .edit-textbox {
19 | margin: 30px;
20 | width: 100px;
21 | }
22 |
23 | .editform {
24 | // position: relative;
25 | display: flex;
26 | flex-wrap:wrap;
27 | flex-direction: row;
28 | padding: 10px;
29 | }
--------------------------------------------------------------------------------
/src/screens/History/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTLayout } from 'layout';
3 | import { connect } from 'dva';
4 | import WatchHistories from './components/WatchHistories';
5 |
6 | const HistoryWithRedux = () => {
7 | const layoutProps = CTLayout.createProps({
8 | transition: true,
9 | responsive: true,
10 | footer: true,
11 | headingProps: {
12 | heading: 'Watch History',
13 | icon: 'history',
14 | sticky: true,
15 | gradient: true,
16 | offsetTop: 30
17 | },
18 | metaTagsProps: {
19 | title: 'History',
20 | description: 'Track your activities in ClassTranscribe.'
21 | }
22 | });
23 |
24 | return (
25 |
26 |
27 |
28 | )
29 | }
30 |
31 | export const History = connect(({ historypage }) => ({
32 | historypage
33 | }))(HistoryWithRedux);
--------------------------------------------------------------------------------
/src/screens/Home/components/CourseFilter/index.scss:
--------------------------------------------------------------------------------
1 | .ct-homep.course-filter {
2 | margin-top: 30px;
3 | margin-bottom: -10px;
4 | background: linear-gradient(to bottom,white 90%,rgba(255, 255, 255, 0));
5 |
6 | .course-filter-item {
7 | margin-bottom: 10px;
8 | // padding-bottom: 20px;
9 |
10 | #home-uni-filter {
11 | .MuiListItemText-root span {
12 | overflow: hidden !important;
13 | white-space: nowrap !important;
14 | text-overflow: ellipsis !important;
15 | }
16 | }
17 |
18 | .selected {
19 | #home-uni-filter {
20 | padding: 10px 15px !important;
21 | }
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/screens/Home/components/MaintenanceMesg/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTFragment, CTText } from 'layout';
3 |
4 | function MaintenanceMesg({ message }) {
5 | return message ? (
6 |
7 |
8 | {message}
9 |
10 |
11 | ) : null;
12 | }
13 |
14 | export default MaintenanceMesg;
15 |
--------------------------------------------------------------------------------
/src/screens/Home/components/Placeholder/index.scss:
--------------------------------------------------------------------------------
1 | #ct-homep-holder {
2 | height: calc(100% - 50px);
3 | overflow-y: hidden;
4 | pointer-events: none;
5 | }
--------------------------------------------------------------------------------
/src/screens/Home/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Placeholder } from './Placeholder';
2 | export { default as SectionList } from './SectionList';
3 | export { default as CourseFilter } from './CourseFilter';
4 | export { default as MaintenanceMesg } from './MaintenanceMesg';
--------------------------------------------------------------------------------
/src/screens/Home/controllers/HomeConstants.js:
--------------------------------------------------------------------------------
1 | class HomeConstants {
2 | static UnknownUniversityID = '0000'
3 | // errors
4 | static CTHomepageLoadError = 'h-load-err'
5 | // feed sections
6 | static FSectionCourses = 'sec-courses'
7 | static FSectionVideos = 'sec-videos'
8 | }
9 |
10 | export default HomeConstants;
--------------------------------------------------------------------------------
/src/screens/Instructor/CourseAnalytics/components/TempVideoTimeTable/index.css:
--------------------------------------------------------------------------------
1 | .VictoryContainer{
2 | height: 30% !important;
3 | width: 30% !important;
4 | user-select: none; pointer-events: none; touch-action: none; position: relative;
5 | }
6 |
7 | .outer{
8 | min-width: 800px;
9 | padding: 10px;
10 | }
11 |
12 | .ip-bg .table_loader{
13 | height: 20em;
14 | border: none !important;
15 | background: transparent !important;
16 | box-shadow: none !important;
17 | }
18 | .ip-bg .ui.inverted.dimmer {
19 | background-color: var(--ct-grey-1);
20 | }
21 |
22 | .charts {
23 | padding: 1px 1px;
24 | display: flex;
25 | flex-direction: row;
26 | }
27 |
28 | .analytics_tab {
29 | position: relative;
30 | height: 40px;
31 | display: flex;
32 | justify-content: left;
33 | text-align: center;
34 | padding: 10px;
35 | }
36 |
37 | /* .analytic_table {
38 | } */
--------------------------------------------------------------------------------
/src/screens/Instructor/CourseAnalytics/components/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/screens/Instructor/CourseAnalytics/components/index.js
--------------------------------------------------------------------------------
/src/screens/Instructor/CourseSettings/components/index.js:
--------------------------------------------------------------------------------
1 | export { Students } from './Students';
2 | export { Staffs } from './Staffs';
3 | export { CourseInfo } from './CourseInfo';
4 | export { RemoveCourse } from './RemoveCourse';
--------------------------------------------------------------------------------
/src/screens/Instructor/InstPlaylist/components/Confirmation/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTConfirmation } from 'layout';
3 |
4 | function ConfirmationWithRedux(props) {
5 | const {
6 | confirmation,
7 | onClose
8 | } = props;
9 | const { title, text, onConfirm } = confirmation;
10 |
11 | const confirmProps = {
12 | open: true,
13 | title,
14 | text,
15 | onConfirm,
16 | onClose
17 | };
18 |
19 | return ;
20 | }
21 |
22 | export const Confirmation = ConfirmationWithRedux;
--------------------------------------------------------------------------------
/src/screens/Instructor/InstPlaylist/components/MediaList/ActionBar/UploadButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import { Link } from 'dva/router';
4 | import Button from '@material-ui/core/Button';
5 | import UploadIcon from '@material-ui/icons/CloudUpload';
6 | import { links } from 'utils/links';
7 | import { useButtonStyles } from 'layout';
8 |
9 | function UploadButton({
10 | playlistId
11 | }) {
12 | const btn = useButtonStyles();
13 |
14 | return (
15 | }
19 | variant="contained"
20 | to={links.playlistUploadFiles(playlistId)}
21 | >
22 | upload
23 |
24 | );
25 | }
26 |
27 | export default UploadButton;
28 |
29 |
--------------------------------------------------------------------------------
/src/screens/Instructor/InstPlaylist/components/MediaList/MediaItem/UploadASLButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cx from 'classnames';
3 | import { Link } from 'dva/router';
4 | import Button from '@material-ui/core/Button';
5 | import { links } from 'utils/links';
6 | import { useButtonStyles } from 'layout';
7 |
8 | function UploadASLButton({
9 | playlistId, mediaId
10 | }) {
11 | const btn = useButtonStyles();
12 | const btnClassName = cx(btn.tealLink, 'media-item-button');
13 |
14 | return (
15 |
24 | );
25 | }
26 |
27 | export default UploadASLButton;
--------------------------------------------------------------------------------
/src/screens/Instructor/InstPlaylist/components/MediaList/UploadFile/VideoUploadActions.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button } from 'pico-ui';
3 | import { CTFragment } from 'layout';
4 |
5 | function VideoUploadActions({
6 | noFileUploaded,
7 | uploading,
8 | handleClose,
9 | handleUpload
10 | }) {
11 | return (
12 |
13 |
14 |
22 |
23 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default VideoUploadActions;
36 |
--------------------------------------------------------------------------------
/src/screens/Instructor/InstPlaylist/components/PlaylistInfo/index.scss:
--------------------------------------------------------------------------------
1 | #ipl-pl-info {
2 | .pl-name {
3 | padding-left: 0;
4 | padding-bottom: 0;
5 | font-size: 21px;
6 | }
7 |
8 | .details {
9 | line-height: 20px;
10 | padding: 8px 15px;
11 | background-color: #f4f4f4;
12 | border-radius: 10px;
13 |
14 | }
15 | .optionHeading {
16 | padding: 4px 4px;
17 | background-color: #f4f4f4;
18 | font-size: 14px;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/screens/Instructor/InstPlaylist/components/UploadFiles/UploadActions.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button } from 'pico-ui';
3 | import { CTFragment } from 'layout';
4 |
5 | function UploadActions({
6 | noFileUploaded,
7 | uploading,
8 | handleClose,
9 | handleUpload
10 | }) {
11 | return (
12 |
13 |
14 |
22 |
23 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default UploadActions;
36 |
--------------------------------------------------------------------------------
/src/screens/Instructor/InstPlaylist/components/index.js:
--------------------------------------------------------------------------------
1 | export { PlaylistInfo } from './PlaylistInfo';
2 | export { MediaList } from './MediaList';
3 | export { Confirmation } from './Confirmation';
4 | export { UploadFiles } from './UploadFiles';
--------------------------------------------------------------------------------
/src/screens/Instructor/MyCourses/components/CourseList/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTFragment , CTHeading } from 'layout';
3 | import { CourseCardList } from 'components';
4 |
5 | import './index.scss';
6 |
7 | export function CourseList({
8 | title,
9 | offerings = []
10 | }) {
11 | return offerings.length > 0 ? (
12 |
13 |
14 |
15 |
16 |
17 | ) : null;
18 | }
19 |
--------------------------------------------------------------------------------
/src/screens/Instructor/MyCourses/components/CourseList/index.scss:
--------------------------------------------------------------------------------
1 | .mc-c-ul-con {
2 | .mc-c-ul {
3 | display: -webkit-box;
4 | display: -webkit-flex;
5 | display: flex;
6 | -webkit-flex-direction: row;
7 | flex-direction: row;
8 | -webkit-flex-wrap: wrap;
9 | flex-wrap: wrap;
10 | -webkit-justify-content: flex-start;
11 | justify-content: flex-start;
12 | width: 100%;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/screens/Instructor/MyCourses/components/NoCourseHolder.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'dva/router';
3 | import Button from '@material-ui/core/Button';
4 | import AddIcon from '@material-ui/icons/Add';
5 | import { CTFragment, CTText, useButtonStyles } from 'layout';
6 | import { links } from 'utils';
7 |
8 | export function NoCourseHolder() {
9 | const btn = useButtonStyles();
10 |
11 | return (
12 |
13 | Welcome to ClassTranscribe
14 |
15 | }
21 | to={links.newCourse()}
22 | >
23 | create your first course
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/screens/Instructor/MyCourses/components/index.js:
--------------------------------------------------------------------------------
1 | export { CourseList } from './CourseList';
2 | export { NoCourseHolder } from './NoCourseHolder';
--------------------------------------------------------------------------------
/src/screens/Instructor/NewCourse/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as CourseForm } from './CourseForm';
--------------------------------------------------------------------------------
/src/screens/Instructor/NewPlaylist/components/NewPlaylistForm/PlaylistName.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { CTFragment, CTFormHeading, CTInput } from 'layout';
4 |
5 | function PlaylistName(props) {
6 | let { error, enable, name, setName } = props;
7 | const handleOnchanged = ({ target: { value } }) => setName(value);
8 | const emptyPlaylistName = error.includes('playlistName') && enable;
9 |
10 | return (
11 |
12 | PLAYLIST NAME
13 |
24 |
25 | );
26 | }
27 | PlaylistName.propTypes = {
28 | name: PropTypes.string,
29 | };
30 | export default PlaylistName;
31 |
--------------------------------------------------------------------------------
/src/screens/Instructor/NewPlaylist/components/index.js:
--------------------------------------------------------------------------------
1 | export { NewPlaylistForm } from './NewPlaylistForm';
2 |
--------------------------------------------------------------------------------
/src/screens/Instructor/index.js:
--------------------------------------------------------------------------------
1 | export { MyCourses } from './MyCourses';
2 | export { NewCourse } from './NewCourse';
3 | export { CourseSettings } from './CourseSettings';
4 | export { CourseAnalytics } from './CourseAnalytics';
5 | export { InstPlaylist } from './InstPlaylist';
6 | export { NewPlaylist } from './NewPlaylist';
7 |
--------------------------------------------------------------------------------
/src/screens/Maintenance/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { api,env } from 'utils';
3 | import './index.css';
4 |
5 | export function Maintenance() {
6 | useEffect(() => {
7 | api.contentLoaded();
8 | });
9 | const message = env.classTranscribeDownMessage;
10 | return (
11 |
12 |
13 |
14 |
ClassTranscribe
15 | {message}
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/screens/MediaSettings/Components/MSPHeaderTabTitle/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'dva/router';
3 | import { CTPopoverLabel } from 'layout';
4 | import { links } from 'utils/links';
5 | import './index.scss';
6 |
7 | function MSPHeaderTabTitleWithRedux(props) {
8 | let { media, playlist } = props.mediasetting;
9 |
10 | const { mediaName } = media;
11 | // const { offeringId } = playlist;
12 |
13 | return (
14 | {mediaName}}>
15 |
19 |
20 | chevron_left
21 |
22 | {mediaName}
23 |
24 |
25 | )
26 | }
27 |
28 | export const MSPHeaderTabTitle = MSPHeaderTabTitleWithRedux;
29 |
--------------------------------------------------------------------------------
/src/screens/MediaSettings/Components/MSPHeaderTabTitle/index.scss:
--------------------------------------------------------------------------------
1 | .msp-me-name-con {
2 | color: rgb(58, 58, 58);
3 | display: flex;
4 | align-items: center;
5 | font-size: 18px;
6 | line-height: 20px;
7 | font-family: var(--ct-font-narrow);
8 |
9 | .name {
10 | max-width: 260px;
11 | overflow: hidden;
12 | white-space: nowrap;
13 | text-overflow: ellipsis;
14 | }
15 |
16 | &:hover {
17 | color: var(--ct-green-normal);
18 | }
19 | }
--------------------------------------------------------------------------------
/src/screens/MediaSettings/Components/index.js:
--------------------------------------------------------------------------------
1 | export { MSPHeaderTabTitle } from './MSPHeaderTabTitle';
2 |
--------------------------------------------------------------------------------
/src/screens/MediaSettings/Tabs/EPub/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CTEPubListScreen from 'components/CTEPubListScreen';
3 | import SourceTypes from 'entities/SourceTypes';
4 | import { useCTDocTitle } from 'hooks';
5 | import { connect } from 'dva';
6 |
7 | export function EpubWithRedux(props) {
8 | const { mediasetting } = props;
9 | const { media } = mediasetting;
10 | const { id, mediaName = '' } = media || {};
11 |
12 | useCTDocTitle(`I-Notes | ${mediaName}`);
13 |
14 | const ePubLiProps = {
15 | sourceType: SourceTypes.Media,
16 | sourceId: id,
17 | source: media,
18 | defaultTitle: mediaName
19 | };
20 |
21 | return ;
22 | }
23 |
24 | export const EPub = connect(({ mediasetting }) => ({
25 | mediasetting
26 | }))(EpubWithRedux);
27 |
--------------------------------------------------------------------------------
/src/screens/MediaSettings/Tabs/Transcriptions/Player/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTFragment } from 'layout';
3 | import CTPlayer from 'components/CTPlayer';
4 | import './index.scss';
5 |
6 | function Player({
7 | media
8 | }) {
9 | return (
10 |
11 |
24 |
25 | );
26 | }
27 |
28 | export default Player;
29 |
--------------------------------------------------------------------------------
/src/screens/MediaSettings/Tabs/Transcriptions/Player/index.scss:
--------------------------------------------------------------------------------
1 | #msp-t-player-con {
2 | width: 540px + 40px;
3 | height: 100%;
4 | padding: 30px 20px;
5 | overflow-y: auto;
6 | overflow-x: hidden;
7 | }
--------------------------------------------------------------------------------
/src/screens/MediaSettings/Tabs/Transcriptions/TransTable/index.scss:
--------------------------------------------------------------------------------
1 | #msp-t-table-con {
2 | width: calc(100% - 580px);
3 | height: 100%;
4 | padding: 30px 20px;
5 | }
--------------------------------------------------------------------------------
/src/screens/MediaSettings/Tabs/Transcriptions/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTFragment, CTText } from 'layout';
3 | // import Player from './Player';
4 | // import TransTable from './TransTable';
5 |
6 | export function TranscriptionsWithRedux({
7 | // eslint-disable-next-line no-unused-vars
8 | media
9 | }) {
10 | return (
11 |
12 | {/*
13 | */}
14 |
15 |
16 | In progress
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | export const Transcriptions = TranscriptionsWithRedux;
--------------------------------------------------------------------------------
/src/screens/MediaSettings/Tabs/index.js:
--------------------------------------------------------------------------------
1 | export { EPub } from './EPub';
2 | export { Transcriptions } from './Transcriptions';
--------------------------------------------------------------------------------
/src/screens/MediaSettings/controllers/constants.js:
--------------------------------------------------------------------------------
1 | /** Tabs */
2 | export const TAB_EDIT_TRANS = 'trans';
3 | export const TAB_EPUB = 'epub';
4 | export const TAB_DEFAULT = TAB_EDIT_TRANS;
5 | export const mspTabs = [
6 | { id: `msp-tab-${TAB_EDIT_TRANS}`, name: 'Transcriptions', hash: TAB_EDIT_TRANS },
7 | { id: `msp-tab-${TAB_EPUB}`, name: 'ePub', hash: TAB_EPUB },
8 | ];
9 |
--------------------------------------------------------------------------------
/src/screens/MediaSettings/index.scss:
--------------------------------------------------------------------------------
1 | .msp-bg {
2 | width: 100%;
3 | min-width: 1000px;
4 | height: 100%;
5 |
6 | .msp-content {
7 | top: 0;
8 | left: 0;
9 | width: 100%;
10 | height: 100%;
11 | background: white;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/screens/NotFound404/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { CTErrorWrapper } from 'layout';
3 | import { api, links } from 'utils';
4 |
5 | export function NotFound404() {
6 | useEffect(() => {
7 | api.contentLoaded(100);
8 | links.title('404');
9 | }, []);
10 |
11 | return (
12 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/screens/Search/components/SearchInput/index.scss:
--------------------------------------------------------------------------------
1 | .sp-input-con {
2 | background: rgb(246, 246, 246);
3 | border-radius: 20px;
4 | overflow: hidden;
5 |
6 | &:focus-within {
7 | background: rgb(236, 242, 243);
8 | }
9 |
10 | #sp-input {
11 | background: rgba(0,0,0,0);
12 | width: 100%;
13 | border: none;
14 | font-size: 15px;
15 |
16 | &:focus {
17 | outline: none;
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/screens/Search/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'dva';
3 | import { CTLayout } from 'layout';
4 | import SearchInput from './components/SearchInput';
5 | import SearchResult from './components/SearchResult';
6 |
7 | const SearchWithRedux = (props) => {
8 | const layoutProps = CTLayout.createProps({
9 | transition: true,
10 | responsive: true,
11 | footer: true,
12 | headingProps: {
13 | heading: 'Search',
14 | icon: 'search',
15 | sticky: true,
16 | gradient: true,
17 | offsetTop: 30,
18 | },
19 | metaTagsProps: {
20 | title: 'Search',
21 | description: 'Find your courses in ClassTranscribe.'
22 | }
23 | });
24 |
25 | return (
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | export const Search = connect(({ search }) => ({
34 | search
35 | }))(SearchWithRedux);
--------------------------------------------------------------------------------
/src/screens/Watch/Components/CTPlayer/index.scss:
--------------------------------------------------------------------------------
1 | .ct-video-row {
2 | top: 4.5em;
3 | position: fixed;
4 | }
5 | .ct-video-con {
6 | height: 100%;
7 | }
8 | .ct-video-container {
9 | width: 100%;
10 | max-height: 100%;
11 | position: relative;
12 | }
13 | .ct-video {
14 | position: relative;
15 | width: 100%;
16 | height: 100%;
17 | object-fit: contain;
18 | }
19 | .watch-player-wrapper {
20 | position: absolute;
21 | width: 100%;
22 | height: 100%;
23 | display: flex;
24 | flex-direction: column;
25 | align-items: center;
26 | justify-content: center;
27 | }
28 | .watch-player-wrapper[data-blur=true] {
29 | background: rgba(0, 0, 0, 0.719);
30 | }
31 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/ControlBar/CtrlButtons/ForwardButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import WatchCtrlButton from '../../WatchCtrlButton';
3 | import { connectWithRedux } from '../../../Utils';
4 | // Not used
5 | export function ForwardButtonWithRedux({ dispatch }) {
6 | const handleForward = () => {
7 | dispatch({ type: 'watch/media_forward', payload: 10 })
8 | };
9 |
10 | return (
11 |
19 |
20 | forward_10
21 |
22 |
23 | );
24 | }
25 |
26 | export const ForwardButton = connectWithRedux(ForwardButtonWithRedux);
27 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/ControlBar/CtrlButtons/RewindButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import WatchCtrlButton from '../../WatchCtrlButton';
3 | import { connectWithRedux } from '../../../Utils';
4 |
5 | export function RewindButtonWithRedux({ dispatch }) {
6 | const handleRewind = () => {
7 | dispatch({ type: 'watch/media_backward', payload: 10 })
8 | };
9 |
10 | return (
11 |
19 |
20 | replay_10
21 |
22 |
23 | );
24 | }
25 |
26 | export const RewindButton = connectWithRedux(RewindButtonWithRedux);
27 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/ControlBar/CtrlButtons/SwitchScreenButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import WatchCtrlButton from '../../WatchCtrlButton';
3 | import { connectWithRedux } from '../../../Utils';
4 |
5 | export function SwitchScreenButtonWithRedux({dispatch}) {
6 | const handleSwitch = () => {
7 | dispatch({ type: 'watch/switchVideo' });
8 | };
9 |
10 | return (
11 |
20 |
21 | compare_arrows
22 |
23 |
24 | );
25 | }
26 |
27 | export const SwitchScreenButton = connectWithRedux(SwitchScreenButtonWithRedux);
28 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/ControlBar/CtrlButtons/index.js:
--------------------------------------------------------------------------------
1 | export { PlayButton } from './PlayButton';
2 | export { ShowASLButton } from './ShowASLButton';
3 | export { RewindButton } from './RewindButton';
4 | export { ForwardButton } from './ForwardButton';
5 | export { SettingButton } from './SettingButton';
6 | export { NextVideoButton } from './NextVideoButton';
7 | export { FullscreenButton } from './FullscreenButton';
8 | export { SwitchScreenButton } from './SwitchScreenButton';
9 | export { PlaybackRateButton } from './PlaybackRateButton';
10 | export { ClosedCaptionButton } from './ClosedCaptionButton';
11 | export { LanguagePickerButton } from './LanguagePickerButton';
12 | export { AudioDescriptionButton } from './AudioDescriptionButton';
13 | export { ScreenModeSettingButton } from './ScreenModeSettingButton';
14 | export { TranscriptionPickerButton } from './TranscriptionPickerButton';
15 | export { GlossaryButton } from './GlossaryButton'; // May 20 Jiaxi
--------------------------------------------------------------------------------
/src/screens/Watch/Components/ControlBar/TimeDisplay/index.scss:
--------------------------------------------------------------------------------
1 | .watch-time-display {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: center;
5 | align-items: center;
6 | font-family: var(--ct-font-google);
7 | color: var(--ct-text-white);
8 | font-size: 16px;
9 | height: 30px;
10 | margin-left: 1em;
11 | }
12 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/ControlBar/VolumeControl/index.scss:
--------------------------------------------------------------------------------
1 | .watch-volume-ctrl {
2 | display: flex;
3 | flex-direction: row;
4 | width: 38px;
5 | min-width: 38px;
6 | overflow: hidden;
7 | transition: var(--ct-transition-width);
8 | -o-transition: var(--ct-transition-width);
9 | -moz-transition: var(--ct-transition-width);
10 | -webkit-transition: var(--ct-transition-width);
11 | &:hover {
12 | width: 115px;
13 | }
14 | &:focus-within {
15 | width: 115px;
16 | }
17 | }
18 | @media screen and (max-width: 550px) {
19 | .watch-volume-ctrl {
20 | display: none;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/ErrorWrapper/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { CTErrorWrapper } from 'layout';
3 | import { user } from 'utils';
4 |
5 | export function ErrorWrapper({ error = {} }) {
6 | return (
7 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Menus/DownloadMenu/index.scss:
--------------------------------------------------------------------------------
1 | .watch-download-menu-title {
2 | height: max-content;
3 | width: 18em;
4 | display: flex;
5 | flex-direction: row;
6 | align-items: center;
7 | justify-content: flex-start;
8 | font-size: 1.4em;
9 | animation: var(--ct-animation-expand-padding-h);
10 | -o-animation: var(--ct-animation-expand-padding-h);
11 | -moz-animation: var(--ct-animation-expand-padding-h);
12 | -webkit-animation: var(--ct-animation-expand-padding-h);
13 | .file-type {
14 | color: var(--ct-text-white-hover);
15 | font-size: 0.8em;
16 | margin-left: .5em;
17 | }
18 | }
19 | .watch-downloading {
20 | width: 35px;
21 | margin-right: 16px;
22 | }
23 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Menus/ScreenModeMenu/index.scss:
--------------------------------------------------------------------------------
1 | .watch-screenmode-menu {
2 | width: 100%;
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: flex-end;
7 | align-items: flex-end;
8 | padding: 4em 2vw;
9 | overflow-x: auto;
10 | overflow-y: hidden;
11 | animation: var(--ct-animation-popup);
12 | -o-animation: var(--ct-animation-popup);
13 | -moz-animation: var(--ct-animation-popup);
14 | -webkit-animation: var(--ct-animation-popup);
15 | }
16 | .watch-icon-list[data-2screen=false] {
17 | .watch-icon-listitem[mode=ps-mode] {
18 | display: none !important;
19 | }
20 | .watch-icon-listitem[mode=nested-mode] {
21 | display: none !important;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Menus/SettingMenu/DisplaySetting/index.scss:
--------------------------------------------------------------------------------
1 | .watch-brightness-ctrl {
2 | display: flex;
3 | flex-direction: row;
4 | width: 135px;
5 | min-width: 135px;
6 | overflow: hidden;
7 | transition: var(--ct-transition-width);
8 | -o-transition: var(--ct-transition-width);
9 | -moz-transition: var(--ct-transition-width);
10 | -webkit-transition: var(--ct-transition-width);
11 | &:hover {
12 | width: 115px;
13 | }
14 | &:focus-within {
15 | width: 115px;
16 | }
17 | }
18 | @media screen and (max-width: 550px) {
19 | .watch-brightness-ctrl {
20 | display: none;
21 | }
22 | }
23 | .watch-icon-listitem-checkmark {
24 | position: relative;
25 | right: 5%;
26 | }
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Modals/EmbedModal/index.scss:
--------------------------------------------------------------------------------
1 | #wp-embed-iframe {
2 | iframe {
3 | margin: 10px 0 10px 0;
4 | border: none;
5 | box-shadow: 0 0 8px 10px rgba(34, 34, 34, 0.171);
6 | }
7 | }
8 | .wp-embed-code {
9 | textarea::selection {
10 | background: rgb(0, 136, 146);
11 | color: white !important;
12 | }
13 | }
14 |
15 | .wp-embed-btime {
16 | padding-top: 5px !important;
17 | width: 8em !important;
18 | }
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Overlays/AudioDescription/index.scss:
--------------------------------------------------------------------------------
1 | .watch-ad-container {
2 | position: absolute;
3 | width: 100%;
4 | height: auto;
5 | display: flex;
6 | flex-direction: row;
7 | align-items: center;
8 | justify-content: center;
9 | }
10 | .watch-ad-text {
11 | position: relative;
12 | width: max-content;
13 | font-weight: bold;
14 | text-align: center;
15 | line-height: normal;
16 | white-space: normal;
17 | padding: .8em 1.2em;
18 | border-radius: 5px;
19 | display: flex;
20 | flex-direction: row;
21 | align-items: center;
22 | justify-content: center;
23 | i {
24 | margin-right: .5em;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Overlays/FlashWarningButton/index.scss:
--------------------------------------------------------------------------------
1 | .ack-overlay {
2 | position: absolute;
3 | background-color: rgba(90, 90, 90, 0.7);
4 |
5 | display:block;
6 | min-width: 100%;
7 | min-height: 100%;
8 | }
9 | .ack-button-container {
10 | position: relative;
11 | align-items: center;
12 | justify-content: center;
13 |
14 | color: rgba(240, 240, 240, 0.95);
15 | background-color: rgba(90, 90, 90, 0.95);
16 | padding: 3em 3em 5em 5em;
17 | }
18 | .ack-button {
19 | position: relative;
20 | display: flex;
21 | flex-direction: row;
22 | font-size: 3em;
23 | }
24 | .ack-text {
25 | color: rgba(220, 220, 255, 0.953);
26 | font-size: 4em;
27 | }
28 | .ack-button-content {
29 | color: rgba(20, 20, 20, 0.953);
30 | i {
31 | font-size: 5em;
32 | size: 5em;
33 | }
34 | width: 10px;
35 | padding: 15px 15px 15px 15px;
36 | }
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Overlays/TabEventHelperButtons/index.scss:
--------------------------------------------------------------------------------
1 | .watch-tab-helper {
2 | position: fixed;
3 | top: -10em;
4 | left: 0;
5 | opacity: 0;
6 | &:focus {
7 | opacity: 1;
8 | top: 0;
9 | }
10 | &:focus-within {
11 | opacity: 1;
12 | top: 0;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Overlays/UpNext/index.scss:
--------------------------------------------------------------------------------
1 | .watch-upn {
2 | max-width: 30em;
3 | .prompt-upnext {
4 | padding: .5em 0 0 0;
5 | overflow: hidden;
6 | }
7 | }
8 | .watch-un-header {
9 | margin: 0;
10 | height: 2em;
11 | padding: 0 .5em;
12 | h3 {
13 | margin: 0;
14 | padding: 0;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Overlays/index.js:
--------------------------------------------------------------------------------
1 | export { TabEventHelperButtons } from './TabEventHelperButtons';
2 | export { SecondaryPlayerWrapper } from './SecondaryPlayerWrapper';
3 | export { BigPlayButton } from './BigPlayButton';
4 | export { ClosedCaption } from './ClosedCaption';
5 | export { AudioDescription } from './AudioDescription';
6 | export { UpNext } from './UpNext';
7 | export { TransCtrlButtons } from './TransCtrlButtons';
8 | export { FlashWarningButton } from './FlashWarningButton';
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Search/Results/OpenMenuButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'dva';
3 |
4 | const OpenMenuButton = (props)=> {
5 | const { show, menu, name, dispatch } = props;
6 | const openMenu = () => dispatch({ type: 'watch/menu_open', payload: { type: menu } });
7 |
8 | return show ? (
9 |
10 |
20 |
21 | ) : null;
22 | }
23 |
24 | export default connect(() => ({
25 |
26 | }))(OpenMenuButton);
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Search/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connectWithRedux, SEARCH_INIT, SEARCH_HIDE } from '../../Utils';
3 | import './index.scss';
4 |
5 | import InputBar from './InputBar';
6 | import Results from './Results';
7 |
8 | function SearchWithRedux({ search = SEARCH_INIT, dispatch }) {
9 | return search.status !== SEARCH_HIDE ? (
10 |
11 |
12 |
13 |
14 | ) : null;
15 | }
16 |
17 | export const Search = connectWithRedux(SearchWithRedux, ['search']);
18 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Search/index.scss:
--------------------------------------------------------------------------------
1 | .watch-search {
2 | position: absolute;
3 | left: 62vw;
4 | top: 1em !important;
5 | width: calc(38vw - 1em);
6 | height: max-content;
7 | padding-bottom: 2em;
8 | background: rgb(42, 42, 42);
9 | overflow-x: hidden;
10 | overflow-y: auto;
11 | border-radius: 10px;
12 | box-shadow: 0 0 10px 5px rgba(0, 0, 0, 0.349);
13 | }
14 |
15 | @media screen and (max-width: 900px) {
16 | .watch-search {
17 | left: 1vw;
18 | width: 98vw;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Transcriptions/PlaceHolder.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function PlaceHolder() {
4 | return (
5 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/Transcriptions/index_liveplayer.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/screens/Watch/Components/Transcriptions/index_liveplayer.scss
--------------------------------------------------------------------------------
/src/screens/Watch/Components/WatchHeader/Buttons/GuideTrigger.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import WatchCtrlButton from '../../WatchCtrlButton';
3 | import { generateWatchUserGuide } from '../../../Utils';
4 |
5 | function GuideTrigger({dispatch}) {
6 | const handleGuideTrigger = () => {
7 | dispatch({type: 'watch/media_pause', payload: true});
8 | let watchUserGuide = generateWatchUserGuide();
9 | watchUserGuide.start();
10 | };
11 |
12 | return (
13 |
22 |
23 | help_outline
24 |
25 |
26 | );
27 | }
28 |
29 | export default GuideTrigger;
30 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/WatchHeader/Search/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connectWithRedux } from '../../../Utils';
3 | import './index.scss';
4 |
5 | function Search({dispatch}) {
6 | const handleOpenSearch = () => {
7 | dispatch({type: 'watch/search_open'});
8 | };
9 |
10 | return (
11 |
26 | );
27 | }
28 |
29 | export default connectWithRedux(Search);
30 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/WatchHeader/Search/index.scss:
--------------------------------------------------------------------------------
1 | /* Search Button */
2 | .watch-header-search-btn {
3 | color: var(--ct-text-white-hover);
4 | width: auto;
5 | height: auto;
6 | margin: 0 1em;
7 | span {
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | padding: .2em 5em .2em .5em;
12 | border: transparent .1em solid;
13 | border-radius: 5px;
14 | font-size: 1.2em;
15 | background: rgba(135, 136, 136, 0.459);
16 | i {
17 | margin-right: .3em;
18 | }
19 | }
20 | &:hover {
21 | >span {
22 | background: rgb(42, 42, 42);
23 | }
24 | }
25 | &:focus {
26 | >span {
27 | background: rgb(42, 42, 42);
28 | }
29 | }
30 | &:active {
31 | >span {
32 | background: rgba(24, 24, 24, 0.562);
33 | }
34 | }
35 | }
36 | @media screen and (max-width: 800px) {
37 | .watch-header-search-btn {
38 | display: none;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/screens/Watch/Components/index.js:
--------------------------------------------------------------------------------
1 | export { ErrorWrapper } from './ErrorWrapper';
2 | export { WatchHeaderLeftElem, WatchHeaderRightElem } from './WatchHeader';
3 | export { Menus } from './Menus';
4 | export { Modals } from './Modals';
5 | export { ClassTranscribePlayer } from './CTPlayer';
6 | export { ControlBar } from './ControlBar';
7 | export { Transcriptions } from './Transcriptions';
8 | export { Search } from './Search';
9 | export * from './Overlays';
10 |
--------------------------------------------------------------------------------
/src/screens/Watch/Utils/UserEventController.js:
--------------------------------------------------------------------------------
1 | import UserEventManager from 'entities/UserEvent';
2 |
3 | class UserEventController extends UserEventManager {
4 | registerIds(mediaId, offeringId) {
5 | if (mediaId) this.mediaId = mediaId;
6 | if (offeringId) this.offeringId = offeringId;
7 | }
8 |
9 | registerLanguage(lang) {
10 | if (lang) this.lang = lang;
11 | }
12 |
13 | buildEventData = (data) => {
14 | if (this.mediaId) data.mediaId = this.mediaId;
15 | if (this.offeringId) data.offeringId = this.offeringId;
16 | if (this.lang) data.json.lang = this.lang;
17 | return { ...data };
18 | }
19 | }
20 |
21 | export const uEvent = new UserEventController();
--------------------------------------------------------------------------------
/src/screens/Watch/Utils/data/ad.example.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "index": 1,
4 | "id": "sample-ad-1",
5 | "text": "TEST1 TEST1 TEST1 TEST1 TEST1 TEST1 TEST1 TEST1 TEST1",
6 | "begin": "00:00:6.4500000",
7 | "end": "00:00:10.4500000"
8 | },
9 | {
10 | "index": 2,
11 | "id": "sample-ad-2",
12 | "text": "TEST2 TEST2 TEST2 TEST2 TEST2 TEST2 TEST2 TEST2 TEST2",
13 | "begin": "00:00:12.4500000",
14 | "end": "00:00:20.4500000"
15 | },
16 | {
17 | "index": 3,
18 | "id": "sample-ad-3",
19 | "text": "TEST3 TEST3 TEST3 TEST3 TEST3 TEST3 TEST3 TEST3 TEST3",
20 | "begin": "00:01:30.4500000",
21 | "end": "00:01:40.4500000"
22 | }
23 | ]
24 |
--------------------------------------------------------------------------------
/src/screens/Watch/Utils/data/index.js:
--------------------------------------------------------------------------------
1 | export const adSample = require('./ad.example.json');
2 |
3 | export { shortcuts } from './shortcuts';
4 |
--------------------------------------------------------------------------------
/src/screens/Watch/Utils/download.control.js:
--------------------------------------------------------------------------------
1 | import { api } from 'utils';
2 |
3 | const fileDownload = require('js-file-download');
4 |
5 | export const downloadControl = {
6 | async webVTT(transcriptionId = '', format, filename = '', onSuccess, onError) {
7 | try {
8 | // let { data } = await api.getFile(path); // vtt
9 | let { data } = await api.getTranscriptionFile(transcriptionId,format);
10 |
11 | fileDownload(data, `${filename}.${format}`);
12 | if (onSuccess) onSuccess();
13 | } catch (error) {
14 | if (onError) onError();
15 | }
16 | },
17 |
18 | epub() {},
19 | };
20 |
--------------------------------------------------------------------------------
/src/screens/Watch/Utils/progress-controllers/index.js:
--------------------------------------------------------------------------------
1 | import { isFirefox, isMobile } from 'react-device-detect';
2 |
3 | import { ChromeProgressController } from './prog.chrome-safari';
4 | import { FireFoxProgressController } from './prog.firefox';
5 | import { MobileProgressController } from './prog.mobile';
6 |
7 | // determine which controller to use
8 | const ProgressController = isMobile
9 | ? MobileProgressController
10 | : isFirefox
11 | ? FireFoxProgressController
12 | : ChromeProgressController;
13 |
14 | export const prog = new ProgressController();
15 |
--------------------------------------------------------------------------------
/src/screens/Watch/index.css:
--------------------------------------------------------------------------------
1 | .watch-bg {
2 | width: 100%;
3 | height: 100%;
4 | overflow-x: hidden;
5 | overflow-y: hidden;
6 | background: var(--watch-page-bg-color);
7 | color: white;
8 | }
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/screens/Watch/player.js:
--------------------------------------------------------------------------------
1 | export default {
2 | video1: null,
3 | video2: null,
4 | aslVideo: null,
5 | param: {
6 |
7 | }
8 | };
--------------------------------------------------------------------------------
/src/screens/Watch/service.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classtranscribe/FrontEnd/b4576a543fa474202a610d44e497b3f1b44ea28a/src/screens/Watch/service.js
--------------------------------------------------------------------------------
/src/screens/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | SignIn,
3 | AuthCallback,
4 | } from './Authentication';
5 |
6 | export { Admin } from './Admin';
7 | export { Asl } from './Asl'
8 | export {
9 | MyCourses,
10 | NewCourse,
11 | CourseSettings,
12 | CourseAnalytics,
13 | InstPlaylist,
14 | NewPlaylist,
15 | } from './Instructor';
16 | export { MediaSettings } from './MediaSettings';
17 |
18 | export { Home } from './Home';
19 | export { Course } from './Course';
20 | export { Search } from './Search';
21 | export { History } from './History';
22 | export { Analytics } from './Analytics';
23 | export { Glossary } from './Glossary';
24 | export { Watch } from './Watch';
25 |
26 | export { NotFound404 } from './NotFound404';
27 | export { Maintenance } from './Maintenance';
28 | // export { ComponentAPI } from './ComponentAPI';
29 | export { Example } from './Example';
30 | export { Embed } from './Embed';
31 | export { EPub } from './EPub';
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // react-testing-library renders your components to document.body,
2 | // this adds jest-dom's custom assertions
3 | // https://create-react-app.dev/docs/running-tests/
4 | import '@testing-library/jest-dom';
5 |
6 | // Similar to config.template but we put in placeholders for required fields
7 | window.env={}
8 | window.env.TEST_SIGN_IN='true'
9 | // window.env.REACT_APP_FRONTEND_COMMIT_ENDPOINT="$REACT_APP_FRONTEND_COMMIT_ENDPOINT"
10 | window.env.AUTH0_CLIENT_ID=""
11 | window.env.AUTH0_DOMAIN=""
12 | window.env.CILOGON_CLIENT_ID=""
13 | // window.env.APPLICATION_INSIGHTS_KEY=""
14 | // window.env.BRANCH=""
15 | // window.env.BUILDNUMBER=""
16 | // window.env.GITSHA1=""
17 |
--------------------------------------------------------------------------------
/src/utils/2html/index.js:
--------------------------------------------------------------------------------
1 | import { markdown2Html } from './markdown';
2 | import { strList2Html, plaintext2Html } from './plaintext';
3 | import registerHighlightLanguages from './prism-highlight';
4 |
5 | /**
6 | * Parse raw texts to HTML
7 | */
8 | export const html = {
9 | markdown: markdown2Html,
10 | plainText: plaintext2Html,
11 | strList: strList2Html,
12 | registerHighlightLanguages
13 | };
14 |
--------------------------------------------------------------------------------
/src/utils/2html/markdown.js:
--------------------------------------------------------------------------------
1 | import showdown from 'showdown';
2 | import showdownKatex from 'showdown-katex';
3 |
4 | function getMDConverter() {
5 | return new showdown.Converter({
6 | tables: true,
7 | simpleLineBreaks: true,
8 | strikethrough: true,
9 | parseImgDimensions: true,
10 | simplifiedAutoLink: true,
11 | excludeTrailingPunctuationFromURLs: true,
12 | tasklists: true,
13 | underline: true,
14 | extensions: [showdownKatex({})],
15 | });
16 | }
17 |
18 | /**
19 | * Parse markdown input to html
20 | * @param {String} markdownData raw markdown text
21 | * @returns {String} parsed raw html
22 | */
23 | export function markdown2Html(markdownData) {
24 | const md = getMDConverter();
25 | return md.makeHtml(markdownData);
26 | }
27 |
--------------------------------------------------------------------------------
/src/utils/2html/prism-highlight.js:
--------------------------------------------------------------------------------
1 | const registerHighlightLanguages = () => {
2 | require('prismjs/components/prism-jsx.min');
3 | require('prismjs/components/prism-python.min');
4 | };
5 |
6 | export default registerHighlightLanguages;
--------------------------------------------------------------------------------
/src/utils/cthttp/entities/Account.js:
--------------------------------------------------------------------------------
1 | import { cthttp } from '../request';
2 |
3 | // ------------------------------------------------------------
4 | // SignIn
5 | // ------------------------------------------------------------
6 |
7 | export function accountSignIn(token, authMethod, callbackURL) {
8 | return cthttp.post('Account/SignIn', {
9 | token,
10 | authMethod,
11 | callbackURL,
12 | });
13 | }
14 |
15 | export function testSignIn() {
16 | return cthttp.get('Account/TestSignIn');
17 | }
18 |
19 | export function loginAsAccountSignIn(emailId) {
20 | return cthttp.post('Account/LoginAs', { emailId });
21 | }
22 |
23 | // ------------------------------------------------------------
24 | // User Metadata
25 | // ------------------------------------------------------------
26 |
27 | export function getUserMetaData() {
28 | return cthttp.get('Account/GetUserMetadata/GetUserMetadata');
29 | }
30 |
31 | export function postUserMetaData(metadata) {
32 | return cthttp.post('Account/PostUserMetadata/PostUserMetadata', metadata);
33 | }
34 |
--------------------------------------------------------------------------------
/src/utils/cthttp/entities/Courses.js:
--------------------------------------------------------------------------------
1 | import { cthttp } from '../request';
2 |
3 | // ------------------------------------------------------------
4 | // Courses
5 | // ------------------------------------------------------------
6 |
7 | // GET
8 |
9 | export function getCourseById(courseId) {
10 | return cthttp.get(`Courses/${courseId}`);
11 | }
12 |
13 | export function getCoursesByDepartId(departId) {
14 | return cthttp.get(`Courses/ByDepartment/${departId}`);
15 | }
16 |
17 | export function getCoursesByInstId(instructorId) {
18 | return cthttp.get(`Courses/ByInstructor/${instructorId}`);
19 | }
20 |
21 | // POST
22 |
23 | export function createCourse(data) {
24 | return cthttp.post('Courses', data);
25 | }
26 |
27 | // PUT
28 |
29 | export function updateCourse(data) {
30 | return cthttp.put(`Courses/${data.id}`, data);
31 | }
32 |
33 | // DELETE
34 |
35 | export function deleteCourse(courseId) {
36 | return cthttp.delete(`Courses/${courseId}`);
37 | }
38 |
--------------------------------------------------------------------------------
/src/utils/cthttp/entities/Departments.js:
--------------------------------------------------------------------------------
1 | import { cthttp } from '../request';
2 |
3 | // ------------------------------------------------------------
4 | // Departments
5 | // ------------------------------------------------------------
6 |
7 | // GET
8 |
9 | export function getDepartments() {
10 | return cthttp.get('Departments');
11 | }
12 |
13 | export function getDepartById(departId) {
14 | return cthttp.get(`Departments/${departId}`);
15 | }
16 |
17 | export function getDepartsByUniId(universityId) {
18 | return cthttp.get(`Departments/ByUniversity/${universityId}`);
19 | }
20 |
21 | // POST
22 |
23 | export function createDepartment(data) {
24 | return cthttp.post('Departments', data);
25 | }
26 |
27 | // PUT
28 |
29 | export function updateDepartment(data) {
30 | return cthttp.put(`Departments/${data.id}`, data);
31 | }
32 |
33 | // DELETE
34 |
35 | export function deleteDepartment(departId) {
36 | return cthttp.delete(`Departments/${departId}`);
37 | }
38 |
--------------------------------------------------------------------------------
/src/utils/cthttp/entities/Images.js:
--------------------------------------------------------------------------------
1 | import { cthttp } from '../request';
2 |
3 | // ------------------------------------------------------------
4 | // Departments
5 | // ------------------------------------------------------------
6 |
7 | // GET
8 |
9 | export function getImageById(imageId) {
10 | return cthttp.get(`Images/${imageId}`);
11 | }
12 |
13 | export function getImagesBySource(sourceType, sourceId) {
14 | return cthttp.get(`Images/BySource/${sourceType}/${sourceId}`);
15 | }
16 |
17 | // POST
18 |
19 | export function createImage(imageFile, sourceType, sourceId) {
20 | const formData = new FormData();
21 | formData.append('imageFile', imageFile);
22 | formData.append('sourceType', sourceType);
23 | formData.append('sourceId', sourceId);
24 | return cthttp.post('Images', formData);
25 | }
26 |
27 | // DELETE
28 |
29 | export function deleteImage(imageId) {
30 | return cthttp.delete(`Images/${imageId}`);
31 | }
--------------------------------------------------------------------------------
/src/utils/cthttp/entities/Roles.js:
--------------------------------------------------------------------------------
1 | import { cthttp } from '../request';
2 |
3 | // ------------------------------------------------------------
4 | // Roles
5 | // ------------------------------------------------------------
6 |
7 | // GET
8 |
9 | export function getRolesByUniId(universityId) {
10 | return cthttp.get('Roles', { params: { universityId } });
11 | }
12 |
13 | // POST
14 |
15 | /**
16 | * Create a role
17 | * @param {String} mailId email
18 | * @param {String} role `Instructor`, `Student`, `Admin`
19 | */
20 | export function createRole(mailId, role) {
21 | return cthttp.post('Roles', null, { params: { mailId, role } });
22 | }
23 |
24 | export function createInstructor(mailId) {
25 | return cthttp.post('Roles', null, { params: { mailId, role: 'Instructor' } });
26 | }
27 |
28 | // DELETE
29 |
30 | export function deleteRole(mailId) {
31 | return cthttp.delete('Roles', { params: { mailId } });
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/cthttp/entities/Terms.js:
--------------------------------------------------------------------------------
1 | import { cthttp } from '../request';
2 |
3 | // ------------------------------------------------------------
4 | // Terms
5 | // ------------------------------------------------------------
6 |
7 | // GET
8 |
9 | export function getTermById(termId) {
10 | return cthttp.get(`Terms/${termId}`);
11 | }
12 |
13 | export function getTermsByUniId(universityId) {
14 | return cthttp.get(`Terms/ByUniversity/${universityId}`);
15 | }
16 |
17 | // POST
18 |
19 | export function createTerm(data) {
20 | return cthttp.post('Terms', data);
21 | }
22 |
23 | // PUT
24 |
25 | export function updateTerm(data) {
26 | return cthttp.put(`Terms/${data.id}`, data);
27 | }
28 |
29 | // DELETE
30 |
31 | export function deleteTerm(termId) {
32 | return cthttp.delete(`Terms/${termId}`);
33 | }
34 |
--------------------------------------------------------------------------------
/src/utils/cthttp/entities/Universities.js:
--------------------------------------------------------------------------------
1 | import { cthttp } from '../request';
2 |
3 | // ------------------------------------------------------------
4 | // Universities
5 | // ------------------------------------------------------------
6 |
7 | // GET
8 |
9 | export function getUniversities() {
10 | return cthttp.get('Universities');
11 | }
12 |
13 | export function getUniversityById(universityId) {
14 | return cthttp.get(`Universities/${universityId}`);
15 | }
16 |
17 | // POST
18 |
19 | export function createUniversity(data) {
20 | return cthttp.post('Universities', data);
21 | }
22 |
23 | // PUT
24 |
25 | export function updateUniversity(data) {
26 | return cthttp.put(`Universities/${data.id}`, data);
27 | }
28 |
29 | // DELETE
30 |
31 | export function deleteUniversity(universityId) {
32 | return cthttp.delete(`Universities/${universityId}`);
33 | }
34 |
--------------------------------------------------------------------------------
/src/utils/cthttp/entities/WatchHistories.js:
--------------------------------------------------------------------------------
1 | import { cthttp } from '../request';
2 |
3 | // ------------------------------------------------------------
4 | // Watch Histories
5 | // ------------------------------------------------------------
6 |
7 | // GET
8 |
9 | export function getMediaWatchHistories(mediaId) {
10 | return cthttp.get(`WatchHistories/${mediaId}`);
11 | }
12 |
13 | export function getUserWatchHistories() {
14 | return cthttp.get('WatchHistories/GetAllWatchedMediaForUser');
15 | }
16 |
17 | // POST
18 |
19 | export function sendMediaWatchHistories(mediaId, timestamp, ratio) {
20 | return cthttp.post(`WatchHistories/${mediaId}`, { timestamp, ratio });
21 | }
22 |
23 | // DELETE
24 |
25 | export function deleteWatchHistory(watchHistoryId) {
26 | return cthttp.delete('WatchHistories', { params: { id: watchHistoryId }});
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/cthttp/general-requests.js:
--------------------------------------------------------------------------------
1 | import { cthttp } from './request';
2 | // import { env } from '../env';
3 |
4 | // get latest git commit of FrontEnd repo
5 | // function getLatestGitCommitData() {
6 | // return cthttp.request(false).get(env.frontendCommitEndpoint);
7 | // }
8 |
9 | // export async function getLatestGitCommitSHA() {
10 | // const { data } = await getLatestGitCommitData();
11 | // return data.sha;
12 | // }
13 |
14 | export function getFile(path) {
15 | return cthttp.request(false).get(path);
16 | }
17 |
18 | /**
19 | * load src buffer
20 | * @param {String} path path to the src
21 | * @returns {Promise} the loaded src buffer
22 | */
23 | export async function getBuffer(path) {
24 | // Adding authorization token
25 | const { data } = await cthttp.request().get(path, { responseType: 'arraybuffer' });
26 | return Buffer.from(data);
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/cthttp/responses/errors.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Parse request response error
3 | * @param {Error} error error object
4 | * @returns {{status:number}}
5 | */
6 | export function parseError(error) {
7 | // console.log(JSON.stringify(error))
8 | const { response } = error;
9 | if (!response) {
10 | // Server Error
11 | return { status: 500 };
12 | }
13 | return { status: response.status };
14 | }
15 |
16 | export function errorType(error) {
17 | return parseError(error).status;
18 | }
19 |
20 | export function isError(errorLike) {
21 | return typeof errorLike === 'number';
22 | }
23 |
24 | /**
25 | * Determine if the error type is an authorization error
26 | * @param {Error} error error object
27 | * @returns {Boolean} true if the error type is an authorization error; otherwise return false
28 | */
29 | export function isAuthError(error) {
30 | const { status } = parseError(error);
31 | return status === 401 || status === 403;
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/json/initialData.json:
--------------------------------------------------------------------------------
1 | {
2 | "initialTerm": {
3 | "name": "",
4 | "startDate": "",
5 | "endDate": "",
6 | "universityId": ""
7 | },
8 | "initialUni": {
9 | "name": "",
10 | "domain": ""
11 | },
12 | "initialDepart": {
13 | "name": "",
14 | "acronym": "",
15 | "universityId": ""
16 | },
17 | "initialCourse": {
18 | "courseName": "",
19 | "courseNumber": "",
20 | "description": "",
21 | "departmentId": ""
22 | },
23 | "initialOffering": {
24 | "offering": {
25 | "sectionName": "",
26 | "termId": "",
27 | "accessType": 0,
28 | "logEventsFlag": true
29 | },
30 | "courseId": "",
31 | "instructorId": ""
32 | },
33 | "initialPlaylist": {
34 | "name": "",
35 | "offeringId": "",
36 | "sourceType": 2,
37 | "playlistIdentifier": ""
38 | },
39 | "initialVideo": {
40 | "description": "",
41 | "playlistId": "",
42 | "path": ""
43 | }
44 | }
--------------------------------------------------------------------------------
/src/utils/json/monthNames.json:
--------------------------------------------------------------------------------
1 | [
2 | "January",
3 | "February",
4 | "March",
5 | "April",
6 | "May",
7 | "June",
8 | "July",
9 | "August",
10 | "September",
11 | "October",
12 | "November",
13 | "December"
14 | ]
--------------------------------------------------------------------------------
/src/utils/json/offeringAccessTypes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Public",
4 | "description": "Course contents are visible to all users",
5 | "id": 0
6 | },
7 | {
8 | "name": "Authenticated Only",
9 | "description": "Course contents are visible to logged-in users",
10 | "id": 1
11 | },
12 | {
13 | "name": "Students Only",
14 | "description": "Course contents are visible to students taking the course",
15 | "id": 2
16 | },
17 | {
18 | "name": "University Only",
19 | "description": "Course contents are visible to everyone from your university",
20 | "id": 3
21 | }
22 | ]
--------------------------------------------------------------------------------
/src/utils/json/playlistTypes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Upload",
4 | "id": 2,
5 | "description": "Manually upload videos"
6 | },
7 | {
8 | "name": "Echo360",
9 | "id": 0,
10 | "description": "Host videos from Echo360"
11 | },
12 | {
13 | "name": "YouTube",
14 | "id": 1,
15 | "description": "Host videos from YouTube playlist"
16 | },
17 | {
18 | "name": "Kaltura/MediaSpace",
19 | "id": 3,
20 | "description": "Host videos from Kaltura/MediaSpace"
21 | },
22 | {
23 | "name": "Box",
24 | "id": 4,
25 | "description": "Host videos from a Box folder"
26 | }
27 | ]
--------------------------------------------------------------------------------
/src/utils/prompt/index.js:
--------------------------------------------------------------------------------
1 | import { CTPrompt } from './CTPrompt'
2 |
3 | export { CTPrompt } from './CTPrompt'
4 |
5 | export const prompt = new CTPrompt()
--------------------------------------------------------------------------------
/src/utils/search/index.js:
--------------------------------------------------------------------------------
1 | import { CTSearch } from './CTSearch';
2 |
3 | export { CTSearch } from './CTSearch';
4 |
5 | export const search = CTSearch;
6 |
--------------------------------------------------------------------------------
/src/utils/state-controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A template class for handling states
3 | */
4 | export class StateController {
5 | dispatches = {};
6 |
7 | /**
8 | * Set state
9 | * @param {String} funcName - The name of the set-state function
10 | * @param {String} stateName - The name of the state
11 | * @param {*} value - the value to set
12 | */
13 | setState(funcName, stateName, value) {
14 | const setState = this.dispatches[funcName];
15 | if (setState) {
16 | setState(value);
17 | this[stateName] = value;
18 | }
19 | }
20 |
21 | /**
22 | * Register the set state functions
23 | * @param {Object} dispatches - The set-state functions to register
24 | * @param {Boolean} replace - True if replace the registered functions
25 | */
26 | register(dispatches, replace) {
27 | if (replace) {
28 | this.dispatches = { ...dispatches };
29 | } else {
30 | this.dispatches = { ...this.dispatches, ...dispatches };
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/utils/use-error.js:
--------------------------------------------------------------------------------
1 | function CTError(name, message) {
2 | this.name = name;
3 | this.message = message;
4 | }
5 |
6 | CTError.prototype = Error.prototype;
7 |
8 | export default CTError;
9 |
10 | export const InvalidDataError = new CTError('InvalidDataError', 'The data is not valid.');
--------------------------------------------------------------------------------
/src/utils/user-preference/index.js:
--------------------------------------------------------------------------------
1 | export { CTPreference } from './CTPreference';
2 | export { default as CTPreferenceV2 } from './CTPreferenceV2';
--------------------------------------------------------------------------------
/src/utils/user/CILogon.js:
--------------------------------------------------------------------------------
1 | import { env } from '../env';
2 | import { links } from '../links';
3 | import { uurl } from '../use-url';
4 | import redirect from './redirect';
5 |
6 | export class CILogon {
7 | constructor() {
8 | this.callback = window.location.origin + links.ciLogonCallback();
9 | }
10 |
11 | authorize(redirectURL) {
12 | redirect.saveRedirectURI(redirectURL);
13 |
14 | const query = uurl.createSearch({
15 | response_type: 'code',
16 | client_id: env.ciLogonClientID,
17 | selected_idp: env.ciLogonSelectedIDP,
18 | initialidp: env.ciLogonDefaultIDP,
19 | redirect_uri: this.callback,
20 | scope: 'openid profile email',
21 | });
22 |
23 | window.location = `https://cilogon.org/authorize${query}`;
24 | }
25 |
26 | parseCallback() {
27 | const redirectUri = redirect.getRedirectURI();
28 | redirect.clear();
29 |
30 | const { code } = uurl.useSearch();
31 |
32 | return {
33 | token: code,
34 | redirect_uri: redirectUri,
35 | };
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/utils/user/constants.js:
--------------------------------------------------------------------------------
1 | export const TOKEN_INFO_ROLES = 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role';
2 | export const TOKEN_INFO_EMAIL = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress';
3 | export const TOKEN_INFO_GIVEN_NAME = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname';
4 | export const TOKEN_INFO_FAMILY_NAME = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname';
5 |
6 | // Possible roles of a user
7 | export const ROLE_ADMIN = 'Admin';
8 | export const ROLE_INST = 'Instructor';
9 |
10 | // auth method
11 | export const AUTH_AUTH0 = 'Auth0';
12 | export const AUTH_CILOGON = 'CILogon';
13 | export const AUTH_TEST = 'Test';
--------------------------------------------------------------------------------
/src/utils/user/index.js:
--------------------------------------------------------------------------------
1 | import { User } from './User';
2 |
3 | export const user = new User();
--------------------------------------------------------------------------------
/src/utils/user/redirect.js:
--------------------------------------------------------------------------------
1 | export default {
2 | REDIRECT_URI_KEY: 'redirect_uri',
3 |
4 | saveRedirectURI(redirect_uri = window.location.href) {
5 | localStorage.setItem(this.REDIRECT_URI_KEY, redirect_uri);
6 | },
7 |
8 | getRedirectURI() {
9 | return localStorage.getItem(this.REDIRECT_URI_KEY) || window.location.origin;
10 | },
11 |
12 | clear() {
13 | localStorage.removeItem(this.REDIRECT_URI_KEY);
14 | },
15 | };
16 |
--------------------------------------------------------------------------------