Skip to content

Commit

Permalink
Merge pull request #3542 from serlo/exercise-group-intermediate-tasks
Browse files Browse the repository at this point in the history
feat(plugin-exercise-group): add intermediate tasks as experiment
  • Loading branch information
elbotho authored Mar 25, 2024
2 parents 3c5c7e8 + 7e63b8c commit ee44cd8
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 14 deletions.
17 changes: 17 additions & 0 deletions apps/web/src/components/user/profile-experimental.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import { cn } from '@/helper/cn'
import { isProduction } from '@/helper/is-production'

export const features = {
editorIntermediateTasks: {
cookieName: 'useEditorIntermediateTasks',
isActive: false,
activeInDev: true,
hideInProduction: false,
},
edtrPasteHack: {
cookieName: 'useEdtrPasteHack',
isActive: false,
Expand Down Expand Up @@ -88,6 +94,17 @@ export function ProfileExperimental() {
<h2 className="serlo-h2" id="experiments">
🧪 Experimente
</h2>
{shouldBeVisible('editorIntermediateTasks') ? (
<div>
<h3 className="serlo-h3 mb-3">
{renderFeatureButton('editorIntermediateTasks')} Zwischentexte für
Aufgaben mit Teilaufgaben 🛠
</h3>
<p className="serlo-p">
Experimentelles Feature: nur für Teammitglieder im Prüfungsbereich.
</p>
</div>
) : null}
{shouldBeVisible('edtrPasteHack') ? (
<div>
<h3 className="serlo-h3 mb-3">
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/data/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,9 @@ export const loggedInData = {
kindOfExerciseGroup: 'Kind of exercise group',
notCohesive: 'not cohesive',
cohesive: 'cohesive',
addIntermediateTask: 'Add Intermediate Task',
removeIntermediateTask: 'Remove intermediate Task',
intermediateTask: 'Intermediate Task',
},
},
edtrIo: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ function createExerciseGroupDocument(
},
exercises,
cohesive: false,
intermediateTasks: undefined,
},
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { EditorTooltip } from '@editor/editor-ui/editor-tooltip'
import { SerloAddButton } from '@editor/plugin/helpers/serlo-editor-button'
import { EditorPluginType } from '@editor/types/editor-plugin-type'
import { faArrowCircleUp, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { useEditorStrings } from '@serlo/frontend/src/contexts/logged-in-data-context'

import type { ExerciseGroupProps } from '.'
import { ExerciseGroupRenderer } from './renderer'
import { IntermediateTask } from './intermediate-task'
import { type ExerciseGroupProps } from '..'
import { ExerciseGroupRenderer } from '../renderer'
import { FaIcon } from '@/components/fa-icon'
import { shouldUseFeature } from '@/components/user/profile-experimental'

export function ExeriseGroupEditor({ state }: ExerciseGroupProps) {
const { content, exercises } = state
const { content, exercises, intermediateTasks } = state

const templateStrings = useEditorStrings().templatePlugins
const exGroupStrings = templateStrings.textExerciseGroup

const lastExerciseIndex = exercises.length - 1

return (
<>
<ExerciseGroupRenderer
Expand Down Expand Up @@ -45,23 +50,55 @@ export function ExeriseGroupEditor({ state }: ExerciseGroupProps) {
</button>
</nav>
{exercise.render()}
<IntermediateTask
intermediateTasks={intermediateTasks}
exerciseIndex={index}
lastExerciseIndex={lastExerciseIndex}
/>
</>
),
}
})}
/>
{renderButton(exGroupStrings.addExercise)}
{renderButtons()}
</>
)

function renderButton(text: string, noIcon?: boolean) {
function renderButtons() {
const showIntermediateTaskButton =
shouldUseFeature('editorIntermediateTasks') &&
lastExerciseIndex >= 0 &&
(intermediateTasks.defined
? !intermediateTasks.find(
(task) => task.afterIndex.value === lastExerciseIndex
)
: true)

return (
<SerloAddButton
text={text}
noIcon={noIcon}
onClick={() => exercises.insert()}
className="mb-8 mt-4"
/>
<>
<SerloAddButton
text={exGroupStrings.addExercise}
onClick={() => exercises.insert()}
className="mb-8 mt-4"
/>
{showIntermediateTaskButton ? (
<SerloAddButton
text={exGroupStrings.addIntermediateTask}
onClick={() => {
const newTask = {
afterIndex: lastExerciseIndex,
content: { plugin: EditorPluginType.Rows },
}
if (intermediateTasks.defined) {
intermediateTasks.insert(undefined, newTask)
} else {
intermediateTasks.create([newTask])
}
}}
className="mb-8 mt-4"
/>
) : null}
</>
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { EditorTooltip } from '@editor/editor-ui/editor-tooltip'
import {
faArrowCircleDown,
faArrowCircleUp,
faTrashAlt,
} from '@fortawesome/free-solid-svg-icons'

import { type ExerciseGroupProps } from '..'
import { FaIcon } from '@/components/fa-icon'
import { useEditorStrings } from '@/contexts/logged-in-data-context'

interface IntermediateTaskProps {
intermediateTasks: ExerciseGroupProps['state']['intermediateTasks']
exerciseIndex: number
lastExerciseIndex: number
}

export function IntermediateTask({
intermediateTasks,
exerciseIndex,
lastExerciseIndex,
}: IntermediateTaskProps) {
const templateStrings = useEditorStrings().templatePlugins
const exGroupStrings = templateStrings.textExerciseGroup

if (!intermediateTasks.defined) return
const taskIndex = intermediateTasks.findIndex(
(task) => task.afterIndex.value === exerciseIndex
)
const task = intermediateTasks[taskIndex]
if (!task) return null

function canMoveTask(newIndex: number) {
if (!intermediateTasks.defined) return false
const indexOccupied = !!intermediateTasks.find(
({ afterIndex }) => afterIndex.value === newIndex
)
return newIndex >= 0 && newIndex < lastExerciseIndex && !indexOccupied
}

return (
<>
<nav className="flex justify-end">
<small className="mx-2 bg-editor-primary-50 p-1 font-bold text-gray-600">
{exGroupStrings.intermediateTask}
</small>
<div>
{canMoveTask(exerciseIndex - 1) ? (
<button
className="serlo-button-editor-secondary serlo-tooltip-trigger mr-2"
onClick={() => {
task.afterIndex.set(exerciseIndex - 1)
}}
>
<EditorTooltip text={templateStrings.article.moveUpLabel} />
<FaIcon icon={faArrowCircleUp} />
</button>
) : null}
{canMoveTask(exerciseIndex + 1) ? (
<button
className="serlo-button-editor-secondary serlo-tooltip-trigger mr-2"
onClick={() => {
task.afterIndex.set(exerciseIndex + 1)
}}
>
<FaIcon icon={faArrowCircleDown} />
</button>
) : null}
<button
className="serlo-button-editor-secondary serlo-tooltip-trigger mr-2"
onClick={() => {
intermediateTasks.set((currentTasks) => {
return currentTasks.filter((_, index) => index !== taskIndex)
})
}}
>
<EditorTooltip text={exGroupStrings.removeIntermediateTask} />
<FaIcon icon={faTrashAlt} />
</button>
</div>
</nav>
<div className="rounded-lg bg-gray-50 p-0.25">
{task.content.render()}
</div>
</>
)
}
25 changes: 24 additions & 1 deletion packages/editor/src/plugins/exercise-group/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,37 @@ import {
list,
object,
boolean,
optional,
number,
} from '@editor/plugin'
import { EditorPluginType } from '@editor/types/editor-plugin-type'

import { ExeriseGroupEditor } from './editor'
import { ExeriseGroupEditor } from './editor/editor'

const allowedPluginsIntermediateTasks = [
EditorPluginType.Text,
EditorPluginType.Image,
EditorPluginType.Multimedia,
EditorPluginType.Box,
EditorPluginType.Spoiler,
]

const exerciseGroupState = object({
content: child({ plugin: EditorPluginType.Rows }),
exercises: list(child({ plugin: EditorPluginType.Exercise })),
intermediateTasks: optional(
list(
object({
afterIndex: number(), // insert after this exercise index
content: child({
plugin: EditorPluginType.Rows,
config: {
allowedPlugins: allowedPluginsIntermediateTasks,
},
}),
})
)
),
/* cohesive field would indicate whether the children of a grouped exercise are cohesive
this field might be used in the future, but currently it has no effect and can not be changed
*/
Expand Down
23 changes: 21 additions & 2 deletions packages/editor/src/plugins/exercise-group/static.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ export function ExerciseGroupStaticRenderer(
useEffect(() => setLoaded(true), [])
const auth = useAuthentication()

const { content, exercises } = state
const { content, exercises, intermediateTasks } = state
if (!exercises) return null

const rendered = exercises.map((exercise, index) => {
const id = `${exercise.id ?? index}`
return {
id,
element: <StaticRenderer document={exercise} />,
element: (
<>
<StaticRenderer document={exercise} /> {renderIntermediateTask(index)}
</>
),
}
})

Expand Down Expand Up @@ -64,4 +68,19 @@ export function ExerciseGroupStaticRenderer(
/>
</div>
)

function renderIntermediateTask(exerciseIndex: number) {
if (!intermediateTasks || !intermediateTasks.length) return null

const task = intermediateTasks.find(
({ afterIndex }) => afterIndex === exerciseIndex
)
if (!task) return null

return (
<div className="rounded-lg bg-gray-50 p-0.25">
<StaticRenderer document={task.content as EditorRowsDocument} />
</div>
)
}
}

0 comments on commit ee44cd8

Please sign in to comment.