{"version":3,"file":"grammar-DgQ16h4M.js","sources":["../../../client/app/bundles/Grammar/components/Header.tsx","../../../client/app/bundles/Grammar/components/PageLayout.tsx","../../../client/app/bundles/Grammar/actions/concepts.ts","../../../client/app/bundles/Grammar/libs/concept_feedback_api.ts","../../../client/app/bundles/Grammar/actions/conceptsFeedback.ts","../../../client/app/bundles/Grammar/libs/grammar_activities_api.ts","../../../client/app/bundles/Grammar/actions/grammarActivities.ts","../../../client/app/bundles/Grammar/libs/shared_cache_api.ts","../../../client/app/bundles/Grammar/actions/questionAndConceptMap.ts","../../../client/app/bundles/Grammar/components/concepts/concept.tsx","../../../client/app/bundles/Grammar/components/shared/linkListItem.tsx","../../../client/app/bundles/Grammar/components/concepts/concepts.tsx","../../../client/app/bundles/Grammar/components/conceptsFeedback/conceptFeedback.tsx","../../../client/app/bundles/Grammar/components/conceptsFeedback/conceptsFeedback.tsx","../../../client/app/bundles/Grammar/constants.ts","../../../client/app/bundles/Grammar/components/shared/loading_spinner.tsx","../../../client/app/bundles/Grammar/components/dashboards/dashboardFilters.tsx","../../../client/app/bundles/Grammar/components/dashboards/conceptDashboard.tsx","../../../client/app/bundles/Grammar/components/dashboards/questionDashboard.tsx","../../../client/app/bundles/Grammar/components/lessons/lessonForm.tsx","../../../client/app/bundles/Grammar/components/lessons/lesson.tsx","../../../client/app/bundles/Grammar/components/lessons/lessons.tsx","../../../client/app/bundles/Grammar/components/grammarActivities/cues.tsx","../../../client/app/bundles/Grammar/components/questions/boilerplateFeedback.ts","../../../client/app/bundles/Grammar/components/questions/chooseModelContainer.tsx","../../../client/app/bundles/Grammar/actions/filters.ts","../../../client/app/bundles/Grammar/actions/massEdit.ts","../../../client/app/bundles/Grammar/libs/grading/rematching.ts","../../../client/app/bundles/Grammar/libs/partsOfSpeechTagging.ts","../../../client/app/bundles/Grammar/components/questions/POSForResponse.tsx","../../../client/app/bundles/Grammar/components/questions/POSIndex.ts","../../../client/app/bundles/Grammar/components/questions/POSForResponsesList.tsx","../../../client/app/bundles/Grammar/components/questions/conceptResults.tsx","../../../client/app/bundles/Grammar/components/questions/response.tsx","../../../client/app/bundles/Grammar/components/questions/responseList.tsx","../../../client/app/bundles/Grammar/components/questions/responseComponent.tsx","../../../client/app/bundles/Grammar/components/questions/editFocusPointsContainer.tsx","../../../client/app/bundles/Grammar/components/questions/editIncorrectSequenceContainer.tsx","../../../client/app/bundles/Grammar/components/questions/focusPointsContainer.tsx","../../../client/app/bundles/Grammar/components/questions/incorrectSequenceContainer.tsx","../../../client/app/bundles/Grammar/components/questions/conceptResultList.tsx","../../../client/app/bundles/Grammar/actions/display.ts","../../../client/app/bundles/Grammar/components/questions/massEditContainer.tsx","../../../client/app/bundles/Grammar/components/questions/newFocusPointsContainer.tsx","../../../client/app/bundles/Grammar/components/questions/newIncorrectSequenceContainer.tsx","../../../client/app/bundles/Grammar/components/questions/questionForm.tsx","../../../client/app/bundles/Grammar/components/questions/responseRouteWrapper.tsx","../../../client/app/bundles/Grammar/components/grammarActivities/question.tsx","../../../client/app/bundles/Grammar/components/questions/testQuestion.tsx","../../../client/app/bundles/Grammar/components/questions/question.tsx","../../../client/app/bundles/Grammar/libs/checkAnswer.ts","../../../client/app/bundles/Grammar/components/questions/questionListByConcept.tsx","../../../client/app/bundles/Grammar/components/questions/questions.tsx","../../../client/app/bundles/Grammar/components/admin/tabLink.tsx","../../../client/app/bundles/Grammar/components/admin/admin.tsx","../../../client/app/bundles/Grammar/components/grammarActivities/intro.tsx","../../../client/app/bundles/Grammar/components/grammarActivities/turkCodePage.tsx","../../../client/app/bundles/Grammar/helpers/conceptResultsGenerator.ts","../../../client/app/bundles/Grammar/components/grammarActivities/container.tsx","../../../client/app/bundles/Grammar/routes.tsx","../../../client/app/bundles/Grammar/actions/actions.ts","../../../client/app/bundles/Grammar/reducers/conceptsFeedbackReducer.ts","../../../client/app/bundles/Grammar/reducers/conceptsReducer.ts","../../../client/app/bundles/Grammar/reducers/displayReducer.ts","../../../client/app/bundles/Grammar/reducers/filtersReducer.ts","../../../client/app/bundles/Grammar/reducers/generatedIncorrectSequencesReducer.ts","../../../client/app/bundles/Grammar/reducers/grammarActivitiesReducer.ts","../../../client/app/bundles/Grammar/reducers/massEditReducer.ts","../../../client/app/bundles/Grammar/reducers/questionAndConceptMapReducer.ts","../../../client/app/bundles/Grammar/reducers/questionsReducer.ts","../../../client/app/bundles/Grammar/reducers/responsesReducer.ts","../../../client/app/bundles/Grammar/reducers/sessionReducer.ts","../../../client/app/bundles/Grammar/reducers/rootReducer.ts","../../../client/app/bundles/Grammar/store/configStore.ts","../../../client/app/bundles/Grammar/App.tsx","../../../client/app/bundles/Grammar/clientRegistration.js"],"sourcesContent":["import * as React from \"react\";\n\nimport { LanguagePicker, TeacherPreviewMenuButton, renderSaveAndExitButton, showTranslations } from '../../Shared/index';\nimport { DropdownObjectInterface } from \"../../Staff/interfaces/evidenceInterfaces\";\nconst quillLogoSrc = `${window.postbuildEnv.CDN_URL}/images/logos/quill-logo-white-2022.svg`;\n\ninterface HeaderProps {\n isOnMobile?: boolean;\n isTeacher?: boolean;\n previewShowing?: boolean;\n onTogglePreview?: () => void;\n language?: string;\n languageOptions?: DropdownObjectInterface[]\n translate: (language: string) => string;\n updateLanguage?: (language: string) => void;\n}\n\nexport const Header: React.SFC = ({\n isOnMobile,\n isTeacher,\n previewShowing,\n onTogglePreview,\n language,\n languageOptions,\n translate,\n updateLanguage\n}) => {\n\n function handleTogglePreview() {\n onTogglePreview();\n }\n\n return (\n
\n
\n {isTeacher && !previewShowing && !isOnMobile && }\n \"Quill\n
\n {showTranslations(language, languageOptions) && }\n {renderSaveAndExitButton({ language, languageOptions, translate })}\n
\n
\n
\n );\n};\n","import * as React from \"react\";\nimport { useQuery } from 'react-query';\nimport { renderRoutes } from \"react-router-config\";\nimport { connect } from 'react-redux';\nimport { withTranslation } from 'react-i18next';\n\nimport { Header } from \"./Header\";\n\nimport { addKeyDownListener } from '../../Shared/hooks/addKeyDownListener';\nimport i18n from '../../Shared/libs/translations/i18n';\nimport { ScreenreaderInstructions, TeacherPreviewMenu, getlanguageOptions, } from '../../Shared/index';\nimport { fetchUserRole } from '../../Shared/utils/userAPIs';\nimport getParameterByName from '../helpers/getParameterByName';\nimport { routes } from \"../routes\";\nimport { setLanguage } from \"../actions/session\";\n\nexport const PageLayout = ({ dispatch, grammarActivities, session, t }) => {\n\n const studentSession = getParameterByName('student', window.location.href);\n const proofreaderSession = getParameterByName('proofreaderSessionId', window.location.href);\n const turkSession = window.location.href.includes('turk');\n const studentOrTurkOrProofreader = studentSession || turkSession || proofreaderSession;\n const isPlaying = window.location.href.includes('play');\n const { data } = useQuery(\"user-role\", fetchUserRole);\n const isTeacherOrAdmin = data && data.role && data.role !== 'student';\n const language = session?.language\n const languageInitializedRef = React.useRef(false);\n\n const [showFocusState, setShowFocusState] = React.useState(false);\n const [previewShowing, setPreviewShowing] = React.useState(!studentOrTurkOrProofreader);\n const [questionToPreview, setQuestionToPreview] = React.useState(null);\n const [switchedBackToPreview, setSwitchedBackToPreview] = React.useState(false);\n const [skippedToQuestionFromIntro, setSkippedToQuestionFromIntro] = React.useState(false);\n const [languageOptions, setLanguageOptions] = React.useState(null);\n\n React.useEffect(() => {\n if (!grammarActivities?.hasreceiveddata) { return }\n handleSetLanguageOptions()\n }, [grammarActivities]);\n\n React.useEffect(() => {\n if (languageInitializedRef.current) { return }\n if (language && i18n.language !== language) {\n handleUpdateLanguage(language)\n languageInitializedRef.current = true\n }\n }, [session]);\n\n function handleKeyDown (e: any) {\n if (e.key !== 'Tab') { return }\n if (showFocusState) { return }\n\n setShowFocusState(true);\n }\n\n addKeyDownListener(handleKeyDown);\n\n function handleSkipToMainContentClick () {\n const element = document.getElementById(\"main-content\")\n if (!element) { return }\n element.focus()\n element.scrollIntoView()\n }\n\n function handleTogglePreviewMenu () {\n if(previewShowing) {\n setSwitchedBackToPreview(false);\n } else {\n setSwitchedBackToPreview(true);\n }\n setPreviewShowing(!previewShowing);\n }\n\n function handleToggleQuestion (question: object) {\n setQuestionToPreview(question);\n }\n\n function handleSkipToQuestionFromIntro () {\n setSkippedToQuestionFromIntro(true);\n }\n\n function handleUpdateLanguage(language: string) {\n i18n.changeLanguage(language);\n const action = setLanguage(language)\n dispatch(action)\n }\n\n function handleSetLanguageOptions() {\n const translations = grammarActivities.currentActivity?.translations || {};\n const formattedLanguageOptions = getlanguageOptions(translations)\n setLanguageOptions(formattedLanguageOptions);\n }\n\n function renderContent (header: JSX.Element, showPreview: boolean, isOnMobile: boolean) {\n return(\n
\n \n \n {header}\n
{renderRoutes(routes, {\n isOnMobile: isOnMobile,\n handleTogglePreviewMenu: handleTogglePreviewMenu,\n switchedBackToPreview: switchedBackToPreview,\n handleToggleQuestion: handleToggleQuestion,\n previewMode: showPreview,\n questionToPreview: questionToPreview,\n skippedToQuestionFromIntro: skippedToQuestionFromIntro,\n availableLanguages: languageOptions?.map(option => option.value),\n updateLanguage: handleUpdateLanguage,\n language: language,\n translate: t\n })}
\n
\n );\n }\n\n const showPreview = previewShowing && isTeacherOrAdmin && isPlaying;\n const isOnMobile = window.innerWidth < 1100;\n let className = \"ant-layout \";\n className += showFocusState ? '' : 'hide-focus-outline';\n let header;\n\n if(isPlaying && isTeacherOrAdmin) {\n header = (\n \n );\n } else if(isPlaying) {\n header = (\n \n );\n }\n\n return(\n
\n
\n {showPreview && \n \n }\n {renderContent(header, showPreview, isOnMobile)}\n
\n
\n );\n}\n\nconst select = (state: any, props: any) => {\n return {\n grammarActivities: state.grammarActivities,\n session: state.session\n };\n}\n\nexport default withTranslation()(connect(select)(PageLayout));\n","import _ from 'underscore';\n\nimport { requestGet, } from '../../../modules/request/index';\nimport { Concept } from '../interfaces/concepts';\nimport { ActionTypes } from './actionTypes';\n\nconst conceptsEndpoint = `${window.postbuildEnv.DEFAULT_URL}/api/v1/concepts.json`;\n\nfunction splitInLevels(concepts: Concept[]) {\n return _.groupBy(concepts, 'level');\n}\n\nfunction getParentName(concept: Concept, concepts: Concept[][]): string|void {\n const parent: Concept|undefined = concepts[1].find(c => c.id === concept.parent_id)\n if (parent) {\n const grandParent: Concept|undefined = concepts[2].find(c => c.id === parent.parent_id)\n if (grandParent) {\n return `${grandParent.name} | ${parent.name}`;\n }\n }\n}\n\nexport const startListeningToConcepts = () => {\n return (dispatch: Function) => {\n requestGet(conceptsEndpoint, (body) => {\n const concepts = splitInLevels(body.concepts);\n concepts['0'] = concepts['0'].map((concept: Concept) => {\n concept.displayName = `${getParentName(concept, concepts)} | ${concept.name}`;\n return concept;\n });\n dispatch({ type: ActionTypes.RECEIVE_CONCEPTS_DATA, data: concepts, });\n });\n };\n}\n","import { requestDelete, requestGet, requestPost, requestPut } from '../../../modules/request/index';\nimport { languageToLocale } from '../../Shared';\nimport { ConceptFeedback, ConceptFeedbackCollection } from '../interfaces/conceptsFeedback';\n\nconst GRAMMAR_TYPE = 'grammar'\n\nconst conceptFeedbackApiBaseUrl = `${window.postbuildEnv.DEFAULT_URL}/api/v1/activity_type/${GRAMMAR_TYPE}/concept_feedback`;\n\nclass ConceptFeedbackApi {\n static getAll(language?: string): Promise {\n const language_string = language ? `/translations/${languageToLocale[language]}` : ''\n return requestGet(`${conceptFeedbackApiBaseUrl}${language_string}.json`, null, (error) => { throw (error) });\n }\n\n static get(uid: string): Promise {\n return requestGet(`${conceptFeedbackApiBaseUrl}/${uid}.json`, null, (error) => {throw(error)});\n }\n\n static create(data: ConceptFeedback): Promise {\n return requestPost(`${conceptFeedbackApiBaseUrl}.json`, {concept_feedback: data}, null, (error) => {throw(error)});\n }\n\n static update(uid: string, data: ConceptFeedback): Promise {\n return requestPut(`${conceptFeedbackApiBaseUrl}/${uid}.json`, {concept_feedback: data}, null, (error) => {throw(error)});\n }\n\n static remove(uid: string): Promise {\n return requestDelete(`${conceptFeedbackApiBaseUrl}/${uid}.json`, null, null, (error) => {throw(error)});\n }\n}\n\nexport {\n ConceptFeedbackApi, GRAMMAR_TYPE, conceptFeedbackApiBaseUrl\n};\n\n","import { ConceptFeedback } from '../interfaces/conceptsFeedback';\nimport { ConceptFeedbackApi } from '../libs/concept_feedback_api';\nimport { ActionTypes } from './actionTypes';\n\nexport const startListeningToConceptsFeedback = () => {\n return (dispatch: Function) => {\n dispatch(loadConceptsFeedback())\n };\n}\n\nexport const loadConceptsFeedback = () => {\n return (dispatch: Function) => {\n ConceptFeedbackApi.getAll().then((data) => {\n dispatch({ type: ActionTypes.RECEIVE_CONCEPTS_FEEDBACK_DATA, data: data, });\n });\n };\n}\n\nexport const loadTranslatedConceptsFeedback = (language) => {\n return function (dispatch, getState) {\n ConceptFeedbackApi.getAll(language).then((data) => {\n dispatch({ type: ActionTypes.RECEIVE_TRANSLATED_CONCEPTS_FEEDBACK_DATA, data: data, });\n });\n };\n}\n\nexport const startConceptsFeedbackEdit = (cid: string) => {\n return { type: ActionTypes.START_CONCEPTS_FEEDBACK_EDIT, cid, };\n}\n\nexport const cancelConceptsFeedbackEdit = (cid: string) => {\n return { type: ActionTypes.FINISH_CONCEPTS_FEEDBACK_EDIT, cid, };\n}\n\nexport const deleteConceptsFeedback = (cid: string) => {\n return (dispatch: Function) => {\n dispatch({ type: ActionTypes.SUBMIT_CONCEPTS_FEEDBACK_EDIT, cid, });\n ConceptFeedbackApi.remove(cid).then(() => {\n dispatch({ type: ActionTypes.FINISH_CONCEPTS_FEEDBACK_EDIT, cid, });\n dispatch({ type: ActionTypes.DISPLAY_MESSAGE, message: 'ConceptsFeedback successfully deleted!', });\n dispatch(loadConceptsFeedback())\n }).catch((error) => {\n dispatch({ type: ActionTypes.FINISH_CONCEPTS_FEEDBACK_EDIT, cid, });\n dispatch({ type: ActionTypes.DISPLAY_ERROR, error: `Deletion failed! ${error}`, });\n })\n };\n}\n\nexport const submitConceptsFeedbackEdit = (cid: string, content: ConceptFeedback) => {\n return (dispatch: Function) => {\n dispatch({ type: ActionTypes.SUBMIT_CONCEPTS_FEEDBACK_EDIT, cid, });\n ConceptFeedbackApi.update(cid, content).then(() => {\n dispatch({ type: ActionTypes.FINISH_CONCEPTS_FEEDBACK_EDIT, cid, });\n alert(\"Update successfully saved!\");\n dispatch(loadConceptsFeedback())\n }).catch((error) => {\n dispatch({ type: ActionTypes.FINISH_CONCEPTS_FEEDBACK_EDIT, cid, });\n dispatch({ type: ActionTypes.DISPLAY_ERROR, error: `Update failed! ${error}`, });\n })\n };\n}\n\nexport const toggleNewConceptsFeedbackModal = () => {\n return { type: ActionTypes.TOGGLE_NEW_CONCEPTS_FEEDBACK_MODAL, };\n}\n\nexport const submitNewConceptsFeedback = (content: ConceptFeedback) => {\n return (dispatch: Function) => {\n dispatch({ type: ActionTypes.AWAIT_NEW_CONCEPTS_FEEDBACK_RESPONSE, });\n ConceptFeedbackApi.create(content).then((result) => {\n const UID = Object.keys(question)[0]\n dispatch({ type: ActionTypes.RECEIVE_NEW_CONCEPTS_FEEDBACK_RESPONSE, });\n dispatch({ type: ActionTypes.DISPLAY_MESSAGE, message: 'Submission successfully saved!', });\n dispatch(loadConceptsFeedback())\n }).catch((error) => {\n dispatch({ type: ActionTypes.RECEIVE_NEW_CONCEPTS_FEEDBACK_RESPONSE, });\n dispatch({ type: ActionTypes.DISPLAY_ERROR, error: `Submission failed! ${error}`, });\n })\n };\n}\n","import { requestDelete, requestGet, requestPost, requestPut } from '../../../modules/request/index';\nimport { GrammarActivities, GrammarActivity } from '../interfaces/grammarActivities';\n\nconst grammarActivityApiBaseUrl = `${window.postbuildEnv.DEFAULT_URL}/api/v1/lessons`;\n\nconst TYPE_GRAMMAR_ACTIVITY = 'grammar_activity'\n\nclass GrammarActivityApi {\n static getAll(): Promise {\n return requestGet(`${grammarActivityApiBaseUrl}.json?lesson_type=${TYPE_GRAMMAR_ACTIVITY}`, null, (error) => {throw(error)});\n }\n\n static get(uid: string): Promise {\n return requestGet(`${grammarActivityApiBaseUrl}/${uid}.json`, null, (error) => {throw(error)});\n }\n\n static create(data: GrammarActivity): Promise {\n return requestPost(`${grammarActivityApiBaseUrl}.json?lesson_type=${TYPE_GRAMMAR_ACTIVITY}`, {lesson: data}, null, (error) => {throw(error)});\n }\n\n static update(uid: string, data: GrammarActivity): Promise {\n return requestPut(`${grammarActivityApiBaseUrl}/${uid}.json`, {lesson: data}, null, (error) => {throw(error)});\n }\n\n static remove(uid: string): Promise {\n return requestDelete(`${grammarActivityApiBaseUrl}/${uid}.json`, null, null, (error) => {throw(error)});\n }\n}\n\nexport {\n GrammarActivityApi, TYPE_GRAMMAR_ACTIVITY, grammarActivityApiBaseUrl\n};\n\n","import { pickBy } from 'lodash';\nimport { push } from 'react-router-redux';\nimport { GrammarActivity } from '../interfaces/grammarActivities';\nimport { GrammarActivityApi, } from '../libs/grammar_activities_api';\nimport { ActionTypes } from './actionTypes';\nimport { reloadQuestionsIfNecessary } from '../../Shared/actions/questions';\nimport { GRAMMAR } from '../../Shared';\n\nexport const startListeningToActivities = () => {\n return (dispatch: Function) => {\n GrammarActivityApi.getAll().then((activities) => {\n if (activities) {\n dispatch({ type: ActionTypes.RECEIVE_GRAMMAR_ACTIVITIES_DATA, data: activities, });\n } else {\n dispatch({ type: ActionTypes.NO_GRAMMAR_ACTIVITIES_FOUND })\n }\n });\n }\n}\n\nexport const getActivity = (activityUID: string) => {\n return (dispatch: Function) => {\n GrammarActivityApi.get(activityUID).then((activity) => {\n if (activity) {\n dispatch({ type: ActionTypes.RECEIVE_GRAMMAR_ACTIVITY_DATA, data: activity, });\n } else {\n dispatch({ type: ActionTypes.NO_GRAMMAR_ACTIVITY_FOUND })\n }\n });\n }\n}\n\nexport const toggleNewLessonModal = () => {\n return { type: ActionTypes.TOGGLE_NEW_LESSON_MODAL, };\n}\n\nexport const submitNewLesson = (content: GrammarActivity) => {\n const cleanedContent = pickBy(content)\n return (dispatch: Function) => {\n dispatch({ type: ActionTypes.AWAIT_NEW_LESSON_RESPONSE, });\n GrammarActivityApi.create(content).then((activity) => {\n const lessonUid = Object.keys(lesson)[0];\n dispatch({ type: ActionTypes.RECEIVE_NEW_LESSON_RESPONSE, });\n dispatch({ type: ActionTypes.DISPLAY_MESSAGE, message: 'Submission successfully saved!', });\n const action = push(`/admin/lessons/${lessonUid}`);\n dispatch(action);\n dispatch(startListeningToActivities());\n }).catch((error) => {\n dispatch({ type: ActionTypes.RECEIVE_NEW_LESSON_RESPONSE, });\n dispatch({ type: ActionTypes.DISPLAY_ERROR, error: `Submission failed! ${error}`, });\n });\n };\n}\n\nexport const startLessonEdit = (cid: string) => {\n return { type: ActionTypes.START_LESSON_EDIT, cid, };\n}\n\nexport const cancelLessonEdit = (cid: string) => {\n return { type: ActionTypes.FINISH_LESSON_EDIT, cid, };\n}\n\nexport const submitLessonEdit = (cid: string, content: GrammarActivity) => {\n return (dispatch: Function) => {\n dispatch({ type: ActionTypes.SUBMIT_LESSON_EDIT, cid, });\n\n const cleanedContent = pickBy(content)\n GrammarActivityApi.update(cid, content).then(() => {\n dispatch({ type: ActionTypes.FINISH_LESSON_EDIT, cid, });\n dispatch({ type: ActionTypes.DISPLAY_MESSAGE, message: 'Update successfully saved!', });\n dispatch(startListeningToActivities());\n dispatch(reloadQuestionsIfNecessary(content.flag, GRAMMAR));\n }).catch((error) => {\n dispatch({ type: ActionTypes.FINISH_LESSON_EDIT, cid, });\n dispatch({ type: ActionTypes.DISPLAY_ERROR, error: `Update failed! ${error}`, });\n });\n };\n}\n\nexport const deleteLesson = (cid: string) => {\n return (dispatch: Function) => {\n dispatch({ type: ActionTypes.SUBMIT_LESSON_EDIT, cid, });\n GrammarActivityApi.remove(cid).then(() => {\n dispatch({ type: ActionTypes.FINISH_LESSON_EDIT, cid, });\n dispatch({ type: ActionTypes.DISPLAY_MESSAGE, message: 'Lesson successfully deleted!', });\n const action = push(`/admin/lessons`)\n dispatch(action)\n dispatch(startListeningToActivities());\n }).catch((error) => {\n dispatch({ type: ActionTypes.FINISH_LESSON_EDIT, cid, });\n dispatch({ type: ActionTypes.DISPLAY_ERROR, error: `Deletion failed! ${error}`, });\n });\n };\n}\n","import { requestDelete, requestGet, requestPut } from '../../../modules/request/index';\n\nconst sharedCacheApiBaseUrl = `${window.postbuildEnv.DEFAULT_URL}/api/v1/shared_cache`;\n\nclass SharedCacheApi {\n static get(uid: string): Promise {\n return requestGet(`${sharedCacheApiBaseUrl}/${uid}.json`, null, (error) => {throw(error)});\n }\n\n static update(uid: string, data: object): Promise {\n return requestPut(`${sharedCacheApiBaseUrl}/${uid}.json`, {data: data}, null, (error) => {throw(error)});\n }\n\n static remove(uid: string): Promise {\n return requestDelete(`${sharedCacheApiBaseUrl}/${uid}.json`, null, null, (error) => {throw(error)});\n }\n}\n\nexport {\n SharedCacheApi,\n sharedCacheApiBaseUrl\n};\n\n","import * as _ from 'lodash'\n\nimport { hashToCollection } from '../../Shared/index'\nimport { Concept } from '../interfaces/concepts'\nimport { DashboardActivity, DashboardConceptRow, DashboardQuestionRow } from '../interfaces/dashboards'\nimport { GrammarActivity } from '../interfaces/grammarActivities'\nimport { SharedCacheApi } from '../libs/shared_cache_api'\nimport { ActionTypes } from './actionTypes'\n\nexport const SHARED_CACHE_KEY = 'GRAMMAR_QUESTIONS_AND_CONCEPTS_MAP'\n\n\nexport function startListeningToQuestionAndConceptMapData() {\n return function (dispatch: Function) {\n SharedCacheApi.get(SHARED_CACHE_KEY).then((data) => {\n dispatch({ type: ActionTypes.RECEIVE_GRAMMAR_QUESTION_AND_CONCEPT_MAP, data: data, });\n })\n };\n}\n\nexport function updateData() {\n return function (dispatch: Function, getState: Function) {\n const { concepts, questions, grammarActivities, } = getState()\n const conceptsArray = concepts.data[0]\n const questionsArray = hashToCollection(questions.data)\n const activitiesArray = hashToCollection(grammarActivities.data)\n const questionRows: Array = questionsArray.map((q) => {\n const questionRow:DashboardQuestionRow = {}\n questionRow.concept_uid = q.concept_uid\n questionRow.prompt = q.prompt\n questionRow.flag = q.flag\n questionRow.link = `/#/admin/questions/${q.key}/responses`\n const concept = conceptsArray.find((c: Concept) => c.uid === q.concept_uid)\n questionRow.concept = concept ? { name: concept.displayName, link: `/#/admin/concepts/${concept.uid}`} : {}\n const explicitlyAssignedActivities:Array<{title: string, flag: string, link: string}> = []\n const implicitlyAssignedActivities:Array<{title: string, flag: string, link: string}> = []\n activitiesArray.forEach((act: GrammarActivity) => {\n const actObj = _.pickBy({\n title: act.title || 'No Name',\n flag: act.flag,\n link: `/#/admin/lessons/${act.key}/`\n })\n if (act.questions && act.questions.find(aq => aq.key === q.key)) {\n explicitlyAssignedActivities.push(actObj)\n } else if (act.concepts && Object.keys(act.concepts).includes(q.concept_uid)) {\n implicitlyAssignedActivities.push(actObj)\n }\n })\n questionRow.noActivities = !(explicitlyAssignedActivities.length || implicitlyAssignedActivities.length)\n questionRow.explicitlyAssignedActivities = explicitlyAssignedActivities\n questionRow.implicitlyAssignedActivities = implicitlyAssignedActivities\n return removeNullAndUndefinedValues(questionRow)\n })\n\n const groupedConcepts = _.groupBy(questionRows, 'concept_uid')\n const conceptRows = Object.keys(groupedConcepts).map(uid => {\n const associatedQuestions = groupedConcepts[uid]\n const conceptRow:DashboardConceptRow = {}\n conceptRow.link = associatedQuestions[0].concept.link || ''\n conceptRow.name = associatedQuestions[0].concept.name || 'Missing Concept'\n let explicitlyAssignedActivities:Array = []\n let implicitlyAssignedActivities:Array = []\n associatedQuestions.forEach(q => {\n // explicit and implicitly assigned activities are inverted for questions and concepts\n // because if a concept is explicitly associated, its questions are implicitly associated, and vice versa\n explicitlyAssignedActivities = explicitlyAssignedActivities.concat(q.implicitlyAssignedActivities)\n implicitlyAssignedActivities = implicitlyAssignedActivities.concat(q.explicitlyAssignedActivities)\n })\n const uniqueExplicitlyAssignedActivityLinks = Array.from(new Set(explicitlyAssignedActivities.map(a => a.link)))\n const uniqueExplicitlyAssignedActivities = uniqueExplicitlyAssignedActivityLinks.map(link => explicitlyAssignedActivities.find(act => act.link === link))\n const uniqueImplicitlyAssignedActivityLinks = Array.from(new Set(implicitlyAssignedActivities.map(a => a.link)))\n const uniqueImplicitlyAssignedActivities = uniqueImplicitlyAssignedActivityLinks.map(link => implicitlyAssignedActivities.find(act => act.link === link))\n conceptRow.explicitlyAssignedActivities = uniqueExplicitlyAssignedActivities\n conceptRow.implicitlyAssignedActivities = uniqueImplicitlyAssignedActivities\n return removeNullAndUndefinedValues(conceptRow)\n })\n SharedCacheApi.update(SHARED_CACHE_KEY, {\n questionRows: questionRows,\n conceptRows: conceptRows\n }).then(() => {\n dispatch(startListeningToQuestionAndConceptMapData())\n })\n }\n}\n\nexport function checkTimeout() {\n return function (dispatch: Function) {\n SharedCacheApi.get(SHARED_CACHE_KEY).then((result) => {\n dispatch(startListeningToQuestionAndConceptMapData())\n }).catch((response) => {\n if (response.status == 404) {\n dispatch(updateData())\n }\n })\n };\n}\n\nfunction removeNullAndUndefinedValues(obj: any) {\n Object.keys(obj).forEach((key: any) => {\n if ([undefined, null].includes(obj[key])) {\n delete obj[key]\n }\n })\n return obj\n}\n","import * as React from 'react';\nimport { connect } from 'react-redux';\nimport { Link } from 'react-router-dom';\n\nimport { FlagDropdown, TextEditor, hashToCollection, } from '../../../Shared/index';\nimport * as questionActions from '../../actions/questions';\nimport { Match } from '../../interfaces/match';\nimport { Question } from '../../interfaces/questions';\nimport { ConceptReducerState } from '../../reducers/conceptsReducer';\nimport { QuestionsReducerState } from '../../reducers/questionsReducer';\n\ninterface ConceptState {\n prompt: string;\n concept_uid: string|undefined;\n instructions: string;\n flag: string;\n rule_description: string;\n answers: Answer[];\n}\n\ninterface ConceptProps {\n concepts: ConceptReducerState;\n match: Match;\n questions: QuestionsReducerState;\n dispatch: Function;\n}\n\ninterface Answer {\n text: string;\n}\n\nclass Concept extends React.Component {\n constructor(props: ConceptProps) {\n super(props)\n\n this.state = {\n prompt: '',\n concept_uid: props.match.params.conceptID,\n instructions: '',\n flag: 'alpha',\n rule_description: '',\n answers: []\n }\n\n this.submit = this.submit.bind(this)\n this.handlePromptChange = this.handlePromptChange.bind(this)\n this.handleInstructionsChange = this.handleInstructionsChange.bind(this)\n this.handleRuleDescriptionChange = this.handleRuleDescriptionChange.bind(this)\n this.handleSelectorChange = this.handleSelectorChange.bind(this)\n this.handleConceptChange = this.handleConceptChange.bind(this)\n this.handleFlagChange = this.handleFlagChange.bind(this)\n this.handleAnswersChange = this.handleAnswersChange.bind(this)\n }\n\n getConcept() {\n const {data} = this.props.concepts, {conceptID} = this.props.match.params;\n return data[0].find(c => c.uid === conceptID)\n }\n\n questionsForConcept() {\n const questionsCollection = hashToCollection(this.props.questions.data)\n return questionsCollection.filter(q => q.concept_uid === this.props.match.params.conceptID && q.flag !== 'archived')\n }\n\n renderQuestionsForConcept() {\n const questionsForConcept = this.questionsForConcept()\n const listItems = questionsForConcept.map((question: Question) => {\n const archivedTag = question.flag === 'archived' ? ARCHIVED - : ''\n return (\n
  • \n \n \n \n
  • \n );\n })\n return (\n
      {listItems}
    \n )\n }\n\n submit(): void {\n if (this.state.prompt !== '') {\n this.props.dispatch(questionActions.submitNewQuestion({\n prompt: this.state.prompt,\n concept_uid: this.state.concept_uid,\n instructions: this.state.instructions,\n flag: this.state.flag,\n rule_description: this.state.rule_description,\n answers: this.state.answers\n }))\n }\n }\n\n handlePromptChange(e: string) {\n this.setState({prompt: e})\n }\n\n handleInstructionsChange(e: React.ChangeEvent) {\n this.setState({instructions: e.target.value})\n }\n\n handleRuleDescriptionChange(e: string) {\n this.setState({rule_description: e})\n }\n\n handleSelectorChange(e: {value: string}) {\n this.setState({concept_uid: e.value})\n }\n\n handleConceptChange() {\n this.setState({concept_uid: this.refs.concept.value})\n }\n\n handleFlagChange(e: React.ChangeEvent) {\n this.setState({ flag: e.target.value, });\n }\n\n handleAnswersChange(e: React.ChangeEvent) {\n this.setState({ answers: [{ text: e.target.value }], });\n }\n\n render() {\n const {data} = this.props.concepts, {conceptID} = this.props.match.params;\n if (this.props.concepts.hasreceiveddata && this.getConcept()) {\n return (\n
    \n Return to All Concepts\n

    {this.getConcept().displayName}

    \n
    {this.questionsForConcept().length} Questions
    \n
    \n
    Create a new question
    \n \n \n \n

    \n