Skip to content

Commit

Permalink
Merge pull request #3541 from serlo/exercise-add-hidden-interactive
Browse files Browse the repository at this point in the history
feat(plugin-exercise): add option to load interactive elements hidden on load
  • Loading branch information
elbotho authored Mar 25, 2024
2 parents f2fc049 + a0da3a1 commit 3c5c7e8
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 5 deletions.
6 changes: 6 additions & 0 deletions apps/web/src/data/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export const instanceData = {
printModeChooseOption: 'Check one of the options.',
strategy: 'Strategy',
solution: 'Proposed Solution',
showHiddenInteractive: 'Check your solution here',
},
boxTypes: {
blank: 'Blank',
Expand Down Expand Up @@ -901,6 +902,11 @@ export const loggedInData = {
title: 'Exercise',
description: 'Interactive or text based exercise',
placeholder: 'Type the assignment here (Optional)',
hideInteractiveInitially: {
info: 'Interactive element collapsed on load',
deactivate: 'Load Interactive Element visible',
activate: 'Load Interactive Element collapsed',
},
},
inputExercise: {
title: 'Input Exercise',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export function convertAiGeneratedScExerciseToEditorDocument(
],
},
interactive,
hideInteractiveInitially: undefined,
solution,
licenseId: undefined,
},
Expand Down
17 changes: 15 additions & 2 deletions packages/editor/src/plugins/exercise/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ export type InteractiveExerciseType =

export function ExerciseEditor(props: ExerciseProps) {
const { state, focused } = props
const { content, interactive, solution, licenseId } = state
const {
content,
interactive,
solution,
licenseId,
hideInteractiveInitially,
} = state
const isSerlo = useContext(IsSerloContext) // only on serlo

const interactiveExerciseTypes = allInteractiveExerciseTypes.filter((type) =>
Expand Down Expand Up @@ -76,7 +82,14 @@ export function ExerciseEditor(props: ExerciseProps) {
})}
<div className="mx-side">
{interactive.defined ? (
interactive.render()
<>
{interactive.render()}
{hideInteractiveInitially.defined ? (
<small className="bg-editor-primary-200 p-1">
[{exPluginStrings.hideInteractiveInitially.info}]
</small>
) : null}
</>
) : (
<>
<p className="mb-2 text-gray-400">
Expand Down
2 changes: 2 additions & 0 deletions packages/editor/src/plugins/exercise/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
object,
optional,
number,
boolean,
} from '@editor/plugin'
import { EditorPluginType } from '@editor/types/editor-plugin-type'

Expand All @@ -25,6 +26,7 @@ const exerciseState = object({
),
solution: optional(child({ plugin: EditorPluginType.Solution })),
licenseId: optional(number()),
hideInteractiveInitially: optional(boolean()),
})

export type ExercisePluginState = typeof exerciseState
Expand Down
24 changes: 22 additions & 2 deletions packages/editor/src/plugins/exercise/static.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { StaticRenderer } from '@editor/static-renderer/static-renderer'
import { EditorExerciseDocument } from '@editor/types/editor-plugins'
import { isRowsDocument } from '@editor/types/plugin-type-guards'
import { faCircleCheck } from '@fortawesome/free-regular-svg-icons'
import { useState } from 'react'

import { isEmptyTextDocument } from '../text/utils/static-is-empty'
import { FaIcon } from '@/components/fa-icon'
import { useInstanceData } from '@/contexts/instance-context'

export function ExerciseStaticRenderer({ state }: EditorExerciseDocument) {
const { content, interactive, solution } = state
const { content, interactive, solution, hideInteractiveInitially } = state
const [interactiveHidden, setInteractiveHidden] = useState(
hideInteractiveInitially
)
const { strings } = useInstanceData()
if (!content) return null

const isEmptyContent =
Expand All @@ -20,7 +28,19 @@ export function ExerciseStaticRenderer({ state }: EditorExerciseDocument) {
) : (
<StaticRenderer document={content} />
)}
<StaticRenderer document={interactive} />

{interactiveHidden ? (
<button
className="serlo-button-blue-transparent ml-side text-base hover:bg-brand-100 hover:text-brand-700"
onClick={() => setInteractiveHidden(false)}
>
<FaIcon icon={faCircleCheck} />{' '}
{strings.content.exercises.showHiddenInteractive}
</button>
) : (
<StaticRenderer document={interactive} />
)}

<StaticRenderer document={solution} />
</>
)
Expand Down
32 changes: 31 additions & 1 deletion packages/editor/src/plugins/exercise/toolbar/toolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { PluginToolbar } from '@editor/editor-ui/plugin-toolbar'
import { ToolbarSelect } from '@editor/editor-ui/plugin-toolbar/components/toolbar-select'
import { DropdownButton } from '@editor/editor-ui/plugin-toolbar/plugin-tool-menu/dropdown-button'
import { PluginDefaultTools } from '@editor/editor-ui/plugin-toolbar/plugin-tool-menu/plugin-default-tools'
import { selectDocument, store } from '@editor/store'
import { EditorPluginType } from '@editor/types/editor-plugin-type'
import { faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'
import { useEditorStrings } from '@serlo/frontend/src/contexts/logged-in-data-context'

import type { ExerciseProps } from '..'
Expand All @@ -17,6 +19,7 @@ export const ExerciseToolbar = ({
}) => {
const { interactive } = state
const exTemplateStrings = useEditorStrings().templatePlugins.exercise
const exPluginStrings = useEditorStrings().plugins.exercise

const currentlySelected = interactive.defined
? selectDocument(store.getState(), interactive.id)?.plugin
Expand All @@ -40,7 +43,34 @@ export const ExerciseToolbar = ({
return (
<PluginToolbar
pluginType={EditorPluginType.Exercise}
pluginControls={<PluginDefaultTools pluginId={id} />}
pluginControls={
<>
<PluginDefaultTools pluginId={id} />
{state.interactive.defined ? (
<>
<div className="m-2 h-0.25 bg-gray-500"></div>
<DropdownButton
onClick={() => {
if (state.hideInteractiveInitially.defined) {
state.hideInteractiveInitially.remove()
} else state.hideInteractiveInitially.create(true)
}}
label={
exPluginStrings.hideInteractiveInitially[
state.hideInteractiveInitially.defined
? 'deactivate'
: 'activate'
]
}
icon={
state.hideInteractiveInitially.defined ? faEye : faEyeSlash
}
dataQa="toggle-interactive-default-visibility"
/>
</>
) : null}
</>
}
pluginSettings={pluginSettings}
className="mt-2.5"
/>
Expand Down

0 comments on commit 3c5c7e8

Please sign in to comment.