Skip to content

Commit

Permalink
Conditional block (#1228)
Browse files Browse the repository at this point in the history
* initial implimentation to render a block based on condition

* refactor  BlockPlaceholderWrapper

* conditional block implemmentation and blockwrapper refactoring

* conditional block implementation fixed, lacking translations only

* use translation in conditional block

* fix translations
  • Loading branch information
anadis504 authored Feb 1, 2024
1 parent 7d44254 commit 874dfe1
Show file tree
Hide file tree
Showing 23 changed files with 344 additions and 43 deletions.
10 changes: 9 additions & 1 deletion services/cms/src/blocks/BlockPlaceholderWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@

interface BlockPlaceholderWrapperProps {
id: string
title: string
explanation: string
}

const BlockPlaceholderWrapper: React.FC<React.PropsWithChildren<BlockPlaceholderWrapperProps>> = ({
id,
children,
title,
explanation,
}) => {
return (
<div className={"wp-block wp-block-embed"} id={"placeholder-block-" + id}>
<div className={"components-placeholder"}>{children}</div>
<div className={"components-placeholder"}>
<h3>{title}</h3>
<p>{explanation}</p>
{children}
</div>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ const ChapterProgressEditor: React.FC<
> = ({ clientId }) => {
const { t } = useTranslation()
return (
<BlockPlaceholderWrapper id={clientId}>
<h3>{t("chapter-progress-placeholder")}</h3>
<p>{t("chapter-progress-placeholder-explanation")}</p>
<BlockPlaceholderWrapper
id={clientId}
title={t("chapter-progress-placeholder")}
explanation={t("chapter-progress-placeholder-explanation")}
>
<InnerBlocks allowedBlocks={ALLOWED_NESTED_BLOCKS} />
</BlockPlaceholderWrapper>
)
Expand Down
113 changes: 113 additions & 0 deletions services/cms/src/blocks/ConditionalBlock/ConditionalBlockEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import styled from "@emotion/styled"
import { useQuery } from "@tanstack/react-query"
import { InnerBlocks, InspectorControls } from "@wordpress/block-editor"
import { BlockEditProps } from "@wordpress/blocks"
import React, { useContext, useState } from "react"
import { useTranslation } from "react-i18next"

import PageContext from "../../contexts/PageContext"
import { fetchCourseInstances } from "../../services/backend/course-instances"
import { fetchCourseModulesByCourseId } from "../../services/backend/courses"
import CheckBox from "../../shared-module/components/InputFields/CheckBox"
import { assertNotNullOrUndefined } from "../../shared-module/utils/nullability"
import BlockPlaceholderWrapper from "../BlockPlaceholderWrapper"

import { ConditionAttributes } from "."

const ALLOWED_NESTED_BLOCKS = ["core/heading", "core/buttons", "core/button", "core/paragraph"]

const Wrapper = styled.div`
margin-left: 1rem;
margin-right: 1rem;
height: auto;
`
const Text = styled.p`
padding-bottom: 1rem;
`

const ConditionalBlockEditor: React.FC<
React.PropsWithChildren<BlockEditProps<ConditionAttributes>>
> = ({ attributes, clientId, setAttributes }) => {
const { t } = useTranslation()
const courseId = useContext(PageContext)?.page.course_id
const courseModules = useQuery({
queryKey: [`/courses/${courseId}/modules`],
queryFn: () => fetchCourseModulesByCourseId(assertNotNullOrUndefined(courseId)),
enabled: !!courseId,
})

const courseInstances = useQuery({
queryKey: [`/courses/${courseId}/course-instances`],
queryFn: () => fetchCourseInstances(assertNotNullOrUndefined(courseId)),
enabled: !!courseId,
})
const [requiredModules, setRequiredModules] = useState<string[]>(attributes.module_completion)
const [requiredInstanceEnrollment, setRequiredInstanceEnrollment] = useState<string[]>(
attributes.instance_enrollment,
)
return (
<BlockPlaceholderWrapper
id={clientId}
title={t("conditional-block")}
explanation={t("conditional-block-explanation")}
>
<InspectorControls>
{courseModules.data && (
<Wrapper>
<Text>{t("module-completion-condition")}</Text>
{courseModules.data.map((mod) => {
return (
<CheckBox
key={mod.id}
label={mod.name ?? "Default"}
value={mod.id}
onChange={() => {
const previuoslyChecked = requiredModules.some((modId) => modId == mod.id)
const newRequiredModules = requiredModules.filter((i) => i != mod.id)
if (!previuoslyChecked) {
newRequiredModules.push(mod.id)
}
setAttributes({ module_completion: newRequiredModules })
setRequiredModules(newRequiredModules)
}}
checked={requiredModules.some((modId) => modId == mod.id)}
></CheckBox>
)
})}
</Wrapper>
)}
{courseInstances.data && (
<Wrapper>
<Text>{t("course-instance-enrollment-condition")}</Text>
{courseInstances.data.map((inst) => {
return (
<CheckBox
key={inst.id}
label={inst.name ?? "Default"}
value={inst.id}
onChange={() => {
const previuoslyChecked = requiredInstanceEnrollment.some(
(instId) => instId == inst.id,
)
const newRequiredInstEnrl = requiredInstanceEnrollment.filter(
(i) => i != inst.id,
)
if (!previuoslyChecked) {
newRequiredInstEnrl.push(inst.id)
}
setAttributes({ instance_enrollment: newRequiredInstEnrl })
setRequiredInstanceEnrollment(newRequiredInstEnrl)
}}
checked={requiredInstanceEnrollment.some((instId) => instId == inst.id)}
></CheckBox>
)
})}
</Wrapper>
)}
</InspectorControls>
<InnerBlocks allowedBlocks={ALLOWED_NESTED_BLOCKS} />
</BlockPlaceholderWrapper>
)
}

export default ConditionalBlockEditor
11 changes: 11 additions & 0 deletions services/cms/src/blocks/ConditionalBlock/ConditionalBlockSave.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { InnerBlocks } from "@wordpress/block-editor"

const ConditionalBlockSave: React.FC = () => {
return (
<div>
<InnerBlocks.Content />
</div>
)
}

export default ConditionalBlockSave
31 changes: 31 additions & 0 deletions services/cms/src/blocks/ConditionalBlock/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { BlockConfiguration } from "@wordpress/blocks"

import { MOOCFI_CATEGORY_SLUG } from "../../utils/Gutenberg/modifyGutenbergCategories"

import ConditionalBlockEditor from "./ConditionalBlockEditor"
import ConditionalBlockSave from "./ConditionalBlockSave"

export interface ConditionAttributes {
module_completion: string[]
instance_enrollment: string[]
}

const ConditionalBlockConfiguration: BlockConfiguration<ConditionAttributes> = {
title: "ConditionalBlock",
description: "Conditionally shown block",
category: MOOCFI_CATEGORY_SLUG,
attributes: {
module_completion: {
type: "array",
default: [],
},
instance_enrollment: {
type: "array",
default: [],
},
},
edit: ConditionalBlockEditor,
save: ConditionalBlockSave,
}

export default ConditionalBlockConfiguration
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ const CongratulationsEditor: React.FC<
> = ({ clientId }) => {
const { t } = useTranslation()
return (
<BlockPlaceholderWrapper id={clientId}>
<h3>{t("congratulations-placeholder")}</h3>
<p>{t("congratulations-explanation")}</p>
<BlockPlaceholderWrapper
id={clientId}
title={t("congratulations-placeholder")}
explanation={t("congratulations-explanation")}
>
<InnerBlocks allowedBlocks={ALLOWED_NESTED_BLOCKS} />
</BlockPlaceholderWrapper>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ const CourseGridEditor: React.FC<
> = ({ clientId }) => {
const { t } = useTranslation()
return (
<BlockPlaceholderWrapper id={clientId}>
<h3>{t("chapters-grid-placeholder")}</h3>
<p>{t("chapters-grid-placeholder-explanation")}</p>
<BlockPlaceholderWrapper
id={clientId}
title={t("chapters-grid-placeholder")}
explanation={t("chapters-grid-placeholder-explanation")}
>
<InnerBlocks allowedBlocks={ALLOWED_NESTED_BLOCKS} />
</BlockPlaceholderWrapper>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ const CourseProgressEditor: React.FC<
> = ({ clientId }) => {
const { t } = useTranslation()
return (
<BlockPlaceholderWrapper id={clientId}>
<h3>{t("course-progress-placeholder")}</h3>
<p>{t("course-progress-placeholder-explanation")}</p>
<BlockPlaceholderWrapper
id={clientId}
title={t("course-progress-placeholder")}
explanation={t("course-progress-placeholder-explanation")}
>
<InnerBlocks allowedBlocks={ALLOWED_NESTED_BLOCKS} />
</BlockPlaceholderWrapper>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ const ExercisesInChapterEditor: React.FC<
> = ({ clientId }) => {
const { t } = useTranslation()
return (
<BlockPlaceholderWrapper id={clientId}>
<h3>{t("exercises-in-chapter-placeholder")}</h3>
<p>{t("exercises-in-chapter-placeholder-explanation")}</p>
<BlockPlaceholderWrapper
id={clientId}
title={t("exercises-in-chapter-placeholder")}
explanation={t("exercises-in-chapter-placeholder-explanation")}
>
<InnerBlocks allowedBlocks={ALLOWED_NESTED_BLOCKS} />
</BlockPlaceholderWrapper>
)
Expand Down
8 changes: 5 additions & 3 deletions services/cms/src/blocks/Glossary/GlossaryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ const CourseGridEditor: React.FC<
> = ({ clientId }) => {
const { t } = useTranslation()
return (
<BlockPlaceholderWrapper id={clientId}>
<h3>{t("glossary-placeholder")}</h3>
<p>{t("glossary-placeholder-explanation")}</p>
<BlockPlaceholderWrapper
id={clientId}
title={t("glossary-placeholder")}
explanation={t("glossary-placeholder-explanation")}
>
<InnerBlocks allowedBlocks={ALLOWED_NESTED_BLOCKS} />
</BlockPlaceholderWrapper>
)
Expand Down
8 changes: 5 additions & 3 deletions services/cms/src/blocks/Map/MapEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ const MapEditor: React.FC<React.PropsWithChildren<BlockEditProps<Record<string,
}) => {
const { t } = useTranslation()
return (
<BlockPlaceholderWrapper id={clientId}>
<h3>{t("map-block-placeholder")}</h3>
<p>{t("map-block-placeholder-explanation")}</p>
<BlockPlaceholderWrapper
id={clientId}
title={t("map-block-placeholder")}
explanation={t("map-block-placeholder-explanation")}
>
<InnerBlocks allowedBlocks={ALLOWED_NESTED_BLOCKS} />
</BlockPlaceholderWrapper>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ const PagesInChapterEditor: React.FC<
> = ({ clientId }) => {
const { t } = useTranslation()
return (
<BlockPlaceholderWrapper id={clientId}>
<h3>{t("pages-in-chapter-placeholder")}</h3>
<p>{t("pages-in-chapter-placeholder-explanation")}</p>
<BlockPlaceholderWrapper
id={clientId}
title={t("pages-in-chapter-placeholder")}
explanation={t("pages-in-chapter-placeholder-explanation")}
>
<InnerBlocks allowedBlocks={ALLOWED_NESTED_BLOCKS} />
</BlockPlaceholderWrapper>
)
Expand Down
8 changes: 5 additions & 3 deletions services/cms/src/blocks/TopLevelPage/TopLevelPageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ const TopLevelPageEditor: React.FC<
> = ({ clientId }) => {
const { t } = useTranslation()
return (
<BlockPlaceholderWrapper id={clientId}>
<h3>{t("top-level-block-placeholder")}</h3>
<p>{t("top-level-block-placeholder-explanation")}</p>
<BlockPlaceholderWrapper
id={clientId}
title={t("top-level-block-placeholder")}
explanation={t("top-level-block-placeholder-explanation")}
>
<InnerBlocks allowedBlocks={ALLOWED_NESTED_BLOCKS} />
</BlockPlaceholderWrapper>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ const UnsupportedEditor: React.FC<
> = ({ clientId }) => {
const { t } = useTranslation()
return (
<BlockPlaceholderWrapper id={clientId}>
<h3>{t("unsupported-block-placeholder")}</h3>
<p>{t("unsupported-block-placeholder-explanation")}</p>
</BlockPlaceholderWrapper>
<BlockPlaceholderWrapper
id={clientId}
title={t("unsupported-block-placeholder")}
explanation={t("unsupported-block-placeholder-explanation")}
></BlockPlaceholderWrapper>
)
}

Expand Down
2 changes: 2 additions & 0 deletions services/cms/src/blocks/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Aside from "./Aside"
import Author from "./Author"
import AuthorInnerBlock from "./AuthorInnerBlock"
import ChapterProgress from "./ChapterProgress"
import ConditionalBlock from "./ConditionalBlock"
import Congratulations from "./Congratulations"
import CourseChapterGrid from "./CourseChapterGrid"
import CourseObjectiveSection from "./CourseObjectiveSection"
Expand Down Expand Up @@ -58,6 +59,7 @@ export const blockTypeMapForPages = [
["moocfi/map", Map],
["moocfi/author", Author],
["moocfi/author-inner-block", AuthorInnerBlock],
["moocfi/conditional-block", ConditionalBlock],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
] as Array<[string, BlockConfiguration<Record<string, any>>]>

Expand Down
9 changes: 8 additions & 1 deletion services/cms/src/services/backend/course-instances.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CourseInstance } from "../../shared-module/bindings"
import { isCourseInstance } from "../../shared-module/bindings.guard"
import { validateResponse } from "../../shared-module/utils/fetching"
import { isArray, validateResponse } from "../../shared-module/utils/fetching"

import { cmsClient } from "./cmsClient"

Expand All @@ -10,3 +10,10 @@ export const fetchCourseInstance = async (courseInstanceId: string): Promise<Cou
})
return validateResponse(response, isCourseInstance)
}

export const fetchCourseInstances = async (courseId: string): Promise<Array<CourseInstance>> => {
const response = await cmsClient.get(`/courses/${courseId}/course-instances`, {
responseType: "json",
})
return validateResponse(response, isArray(isCourseInstance))
}
11 changes: 11 additions & 0 deletions services/cms/src/services/backend/courses.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
CmsPeerReviewConfiguration,
CourseModule,
NewResearchForm,
NewResearchFormQuestion,
Page,
Expand All @@ -8,6 +9,7 @@ import {
} from "../../shared-module/bindings"
import {
isCmsPeerReviewConfiguration,
isCourseModule,
isPage,
isResearchForm,
isResearchFormQuestion,
Expand Down Expand Up @@ -68,3 +70,12 @@ export const upsertResearchFormQuestion = async (
)
return validateResponse(response, isResearchFormQuestion)
}

export const fetchCourseModulesByCourseId = async (
courseId: string,
): Promise<Array<CourseModule>> => {
const response = await cmsClient.get(`/courses/${courseId}/modules`, {
responseType: "json",
})
return validateResponse(response, isArray(isCourseModule))
}
Loading

0 comments on commit 874dfe1

Please sign in to comment.