Source

externalCode/QuestionApiHooks.ts

import * as React from "react";
import {NavigationService, TQuizViewModel, TTranslationFunction} from "./types";

export const API_NAME = "questionApiHooks";

/**
 * OverrideQuestionImageSizeCallback
 */
type OverrideQuestionImageSizeCallback = {
	/**
	 * Returns `true` if component is intended to be displayed in a modal
	 */
	isPopup: boolean,
	/**
	 * Current image max width
	 */
	value: number
};
/**
 * HTMLQuestionTitleProps
 */
type HTMLQuestionTitleProps = {
	/**
	 * Default tags styles
	 */
	tagsStyles: Record<any, any>,
	/**
	 * Default font style
	 */
	baseFontStyle: Record<any, any>,
	/**
	 * Returns the question title in html format
	 */
	html: string,
	/**
	 * Default image max width when generated by the HTML component
	 */
	imageMaxWidth: number,
	/**
	 * Handles link press for the HTML component
	 */
	onLinkPress: Function,
	/**
	 * Default ignored tags
	 */
	ignoredTags: string[],
	/**
	 * Default classes styles
	 */
	classesStyles: Record<any, any>,
	/**
	 * App colors
	 */
	colors: Record<any, any>,

	navigation: NavigationService
};

/**
 * QuestionNumberProps
 */
type QuestionNumberProps = {
	/**
	 * Question details
	 */
	item: Record<any, any>,
	/**
	 * Selects the question when executed
	 */
	onPress: Function,

	/**
	 * Title of the component
	 */
	title: number,
	/**
	 * Size of the status indicated placed on the component
	 */
	markItemSize: number,
	/**
	 * Returns `true` if the component should be marked
	 */
	isMarked: boolean,
	/**
	 * Title color
	 */
	titleColor: string,
	/**
	 * Question number component size
	 */
	itemSize: number,
	/**
	 * Background color for the question number component
	 */
	backgroundColor: string,
	/**
	 * Color of the status indicator placed on the component
	 */
	markColor: string
};

/**
 * QuestionNumberIndicatorsProps
 */
type QuestionNumberIndicatorsProps = {
	styles: Record<any, any>,
	/**
	 * App colors
	 */
	colors: Record<any, any>,
	t: TTranslationFunction
};

/**
 * QuestionHintComponentProps
 */
type QuestionHintComponentProps = {
	/**
	 * Returns `true` if the hint component should be visible.
	 */
	showFooter: boolean,
	/**
	 * App global style
	 */
	global: Record<any, any>,
	/**
	 * Question data
	 */
	question: Record<any, any>,
	/**
	 * Default html css styles. This can be added to an html string if the hint is to be generated in an HTML component.
	 */
	htmlStylesCss: string,
	/**
	 * Default html css styles. This can be added to an html string if the hint is to be generated in an HTML component.
	 */
	htmlAdjustedCss: string,
	/**
	 * Font data
	 */
	typography: Record<any, any>,
	t: TTranslationFunction,
	/**
	 * Default styles for the Hint component
	 */
	styles: Record<any, any>
};

/**
 * QuestionSubmitButtonProps
 */
type QuestionSubmitButtonProps = {
	styles: Record<any, any>,
	/**
	 * App global style
	 */
	global: Record<any, any>,
	quiz: TQuizViewModel,
	/**
	 * Returns `true` if question has an answer
	 */
	hasAnswer: boolean,
	/**
	 * Returns `true` if back button should be visible
	 */
	hasBackButton: boolean,
	/**
	 * Shows an alert modal if a question requires an answer but no answer was inputted
	 */
	showNoAnswerAlert: Function,
	/**
	 * Returns `true` if the question in the screen is the last question in the quiz
	 */
	isLastQuestion: false,
	/**
	 * Returns `true` when a request is made to submit the quiz
	 */
	completing: boolean,
	/**
	 * Shows the quiz summary in a modal
	 */
	onQuestionOverviewSummaryPress: Function,
	/**
	 * Submits the quiz
	 */
	submitQuiz: Function,
	/**
	 * Submits the answer to a question
	 */
	onQuestionSubmitClick: Function,
	/**
	 * Returns the default button label
	 */
	label: string,
	/**
	 * Returns `true` when the quiz is being submitted
	 */
	submitting: boolean
};

/**
 * @class
 * Question Screen Hooks.
 * Instance name: questionApiHooks
  
   If you have courses in your app, you can use this hook to customize the way the questions are displayed. For example, you can change the image width in LearnDash questions and more.
 * @example
 * externalCodeSetup.questionApiHooks.METHOD_NAME
 */
export class QuestionApiHooks {
	// Determine maximum width given to image
	imageSize = (isPopup: boolean, value: number) => value;

	/**
	 * It is used to set the maximum width of images that are displayed in the LearnDash LMS questions.
	 * @method
	 * @param {OverrideQuestionImageSizeCallback} imageSizeFunction
	 * @example
	 * externalCodeSetup.questionApiHooks.setImageSizeForMaxWidth( (isPopup, defaultMaxWidth) => {
	 *  return defaultMaxWidth + 100;
	 * })
	 */
	setImageSizeForMaxWidth = (
		imageSize: (isPopup: boolean, value: number) => number
	) => {
		this.imageSize = imageSize;
	};

	HTMLQuestionTitle: React.ComponentType<HTMLQuestionTitleProps> | null = null;

	/**
	 * You can use this hook to customize the question title components (if they are rendered as HTML tags instead of Blocks) which are shown when a quiz has started.
	 * @method
	 * @param {HTMLQuestionTitleProps} HTMLQuestionTitle
	 * @example
	 *
	 * ...
	 *
	 * import {sourceRenderer} from "@src/utils/htmlRender";
	 * import HTML from "react-native-render-html";
	 * export const applyCustomCode = (externalCodeSetup) => {
	 *     externalCodeSetup.questionApiHooks.setHTMLQuestionTitle(
	 *         ({
	 *             tagsStyles,
	 *             baseFontStyle,
	 *             html,
	 *             imageMaxWidth,
	 *             classesStyles,
	 *             onLinkPress,
	 *             ignoredTags,
	 *             colors,
	 *             navigation
	 *         }) => (
	 *             <HTML
	 *                 tagsStyles={tagsStyles}
	 *                 baseFontStyle={baseFontStyle}
	 *                 html={html}
	 *                 imagesMaxWidth={imageMaxWidth}
	 *                 onLinkPress={onLinkPress}
	 *                 ignoredTags={ignoredTags}
	 *                 classesStyles={classesStyles}
	 *                 alterChildren={node => {
	 *                     // removing <a/> if the parent node is audio
	 *                     if (node.name === "a" && node.parent?.name === "audio") {
	 *                         return [];
	 *                     }
	 *                 }}
	 *                 renderers={{
	 *                     source: (htmlAttribs, children, convertedCSSStyles, passProps) =>
	 *                         sourceRenderer(
	 *                             htmlAttribs,
	 *                             children,
	 *                             convertedCSSStyles,
	 *                             passProps,
	 *                             colors,
	 *                             navigation
	 *                         )
	 *                 }}
	 *             />
	 *         )
	 *     );
	 * }
	 */
	setHTMLQuestionTitle = (
		HTMLQuestionTitle: React.ComponentType<HTMLQuestionTitleProps> | null
	) => {
		this.HTMLQuestionTitle = HTMLQuestionTitle;
	};

	QuestionNumberComponent: React.ComponentType<
		QuestionNumberProps
	> | null = null;

	/**
	 * You can use this hook to customize the QuestionNumber components.
	 * These components are displayed above the question title component when a quiz has started.
	 * @method
	 * @param {QuestionNumberProps} QuestionNumberComponent
	 * @example
	 * ...
	 *
	 * import QuestionNumber from "@src/components/QuestionOverview/QuestionNumber";
	 * export const applyCustomCode = (externalCodeSetup) => {
	 *     externalCodeSetup.questionApiHooks.setQuestionNumberComponent(
	 *         ({
	 *             onPress,
	 *             title,
	 *             markItemSize,
	 *             isMarked,
	 *             titleColor,
	 *             itemSize,
	 *             backgroundColor,
	 *             markColor
	 *         }) => (
	 *             <QuestionNumber
	 *                 onPress={onPress}
	 *                 title={title}
	 *                 markItemSize={markItemSize}
	 *                 isMarked={isMarked}
	 *                 titleColor={titleColor}
	 *                 itemSize={itemSize}
	 *                 backgroundColor={backgroundColor}
	 *                 markColor={markColor}
	 *             />
	 *         )
	 *     );
	 * }
	 */
	setQuestionNumberComponent = (
		QuestionNumberComponent: React.ComponentType<QuestionNumberProps> | null
	) => {
		this.QuestionNumberComponent = QuestionNumberComponent;
	};

	QuestionNumberIndicators: React.ComponentType<
		QuestionNumberIndicatorsProps
	> | null = null;

	/**
	 * You can use this hook to customize the QuestionNumberIndicators component.
	 * These components represent the status of a question whether it's "Current", "Review" or "Answered".
	 * @method
	 * @param {QuestionNumberIndicatorsProps} QuestionNumberIndicators
	 * @example
	 *
	 * externalCodeSetup.questionApiHooks.setQuestionNumberIndicators(
	 *     ({styles, colors, t}) => (
	 *         <View style={styles.indicatorContainer}>
	 *             <View style={styles.row}>
	 *                 <View style={styles.indicator}>
	 *                     <View
	 *                         style={[styles.dot, {backgroundColor: colors.primaryColor}]}
	 *                     />
	 *                     <Text>{t("quiz:currentLabel")}</Text>
	 *                 </View>
	 *                 <View style={styles.indicator}>
	 *                     <View
	 *                         style={[styles.dot, {backgroundColor: colors.warningColor}]}
	 *                     />
	 *                     <Text>{t("quiz:reviewLabel")}</Text>
	 *                 </View>
	 *                 <View style={styles.indicator}>
	 *                     <View
	 *                         style={[styles.dot, {backgroundColor: colors.successColor}]}
	 *                     />
	 *                     <Text>{t("quiz:answeredLabel")}</Text>
	 *                 </View>
	 *             </View>
	 *         </View>
	 *     )
	 * );
	 */
	setQuestionNumberIndicators = (
		QuestionNumberIndicators: React.ComponentType<
			QuestionNumberIndicatorsProps
		> | null
	) => {
		this.QuestionNumberIndicators = QuestionNumberIndicators;
	};

	QuestionHintComponent: React.ComponentType<
		QuestionHintComponentProps
	> | null = null;

	/**
	 * You can use this hook to customize the QuestionHint component.
	 * The QuestionHint component will be visible when a hint is added to a question.
	 * @method
	 * @param {QuestionHintComponentProps} QuestionHintComponent
	 * @example
	 *
	 * ...
	 *
	 * import QuestionHint from "@src/components/Questions/QuestionHint";
	 *
	 * export const applyCustomCode = (externalCodeSetup) => {
	 *     externalCodeSetup.questionApiHooks.setQuestionHintComponent(
	 *         ({
	 *             showFooter,
	 *             global,
	 *             question,
	 *             htmlStylesCss,
	 *             htmlAdjustedCss,
	 *             typography,
	 *             t,
	 *             styles
	 *         }) => (
	 *             <View style={styles.container}>
	 *                 {showFooter && (
	 *                     <QuestionHint
	 *                         global={global}
	 *                         hint={question.hint}
	 *                         htmlStylesCss={htmlStylesCss}
	 *                         htmlAdjustedCss={htmlAdjustedCss}
	 *                         typography={typography}
	 *                         t={t}
	 *                         style={styles.hintComponent}
	 *                     />
	 *                 )}
	 *             </View>
	 *         )
	 *     );
	 * }
	 *
	 */
	setQuestionHintComponent = (
		QuestionHintComponent: React.ComponentType<
			QuestionHintComponentProps
		> | null
	) => {
		this.QuestionHintComponent = QuestionHintComponent;
	};

	QuestionSubmitButton: React.ComponentType<
		QuestionSubmitButtonProps
	> | null = null;

	/**
	 *
	 * You can use this hook to customize the "Submit" buttons for each question in a quiz.
	 * This hook can also be used to customize the button to submit a quiz or show its summary.
	 * @method
	 * @param {QuestionSubmitButtonProps} QuestionSubmitButton
	 * @example
	 *
	 * ...
	 *
	 * import AppButton from "@src/components/AppButton";
	 *
	 * export const applyCustomCode = (externalCodeSetup) => {
	 *     externalCodeSetup.questionApiHooks.setQuestionSubmitButton(
	 *         ({
	 *             styles,
	 *             global,
	 *             quiz,
	 *             hasAnswer,
	 *             hasBackButton,
	 *             showNoAnswerAlert,
	 *             isLastQuestion,
	 *             completing,
	 *             onQuestionOverviewSummaryPress,
	 *             submitQuiz,
	 *             onQuestionSubmitClick,
	 *             label,
	 *             submitting
	 *         }) => (
	 *             <AppButton
	 *                 style={[
	 *                     styles.button,
	 *                     hasBackButton ? styles.buttonRight : styles.defaultButtonStyle,
	 *                     global.quizSubmitButton
	 *                 ]}
	 *                 onPress={() => {
	 *                     if (quiz.forcingQuestionSolve && !hasAnswer) {
	 *                         showNoAnswerAlert();
	 *                     } else if (isLastQuestion) {
	 *                         if (!completing) {
	 *                             if (quiz.showReviewQuestion && !quiz.quizSummaryHide) {
	 *                                 onQuestionOverviewSummaryPress();
	 *                             } else {
	 *                                 submitQuiz();
	 *                             }
	 *                         }
	 *                     } else {
	 *                         onQuestionSubmitClick();
	 *                     }
	 *                 }}
	 *                 label={label}
	 *                 labelStyle={global.quizSubmitButtonLabel}
	 *                 global={global}
	 *                 loading={submitting}
	 *                 loadingStyle={global.quizSubmitButtonLoading}
	 *             />
	 *         )
	 *     );
	 * }
	 *
	 */
	setQuestionSubmitButton = (
		QuestionSubmitButton: React.ComponentType<QuestionSubmitButtonProps> | null
	) => {
		this.QuestionSubmitButton = QuestionSubmitButton;
	};
}