diff --git a/frontend/src/components/Block/Block.tsx b/frontend/src/components/Block/Block.tsx index 0bbcc18a0..0de00f482 100644 --- a/frontend/src/components/Block/Block.tsx +++ b/frontend/src/components/Block/Block.tsx @@ -5,87 +5,40 @@ import classNames from "classnames"; import useBoundStore from "@/util/stores"; import { getNextRound, useBlock } from "@/API"; -import Consent from "@/components/Consent/Consent"; +import Consent, { ConsentProps } from "@/components/Consent/Consent"; import DefaultPage from "@/components/Page/DefaultPage"; -import Explainer from "@/components/Explainer/Explainer"; -import Final from "@/components/Final/Final"; -import Loading from "@/components/Loading/Loading"; -import Playlist from "@/components/Playlist/Playlist"; -import Score from "@/components/Score/Score"; -import Trial, { IFeedbackForm } from "@/components/Trial/Trial"; -import Info from "@/components/Info/Info"; +import Explainer, { ExplainerProps } from "@/components/Explainer/Explainer"; +import Final, { FinalProps } from "@/components/Final/Final"; +import Loading, { LoadingProps } from "@/components/Loading/Loading"; +import Playlist, { PlaylistProps } from "@/components/Playlist/Playlist"; +import Score, { ScoreProps } from "@/components/Score/Score"; +import Trial, { TrialProps } from "@/components/Trial/Trial"; +import Info, { InfoProps } from "@/components/Info/Info"; import FloatingActionButton from "@/components/FloatingActionButton/FloatingActionButton"; import UserFeedback from "@/components/UserFeedback/UserFeedback"; import FontLoader from "@/components/FontLoader/FontLoader"; import useResultHandler from "@/hooks/useResultHandler"; import Session from "@/types/Session"; -import { PlaybackArgs, PlaybackView } from "@/types/Playback"; -import { FeedbackInfo, Step } from "@/types/Block"; -import { TrialConfig } from "@/types/Trial"; -import Social from "@/types/Social"; +import { RedirectProps } from "../Redirect/Redirect"; -type BlockView = PlaybackView | "TRIAL_VIEW" | "EXPLAINER" | "SCORE" | "FINAL" | "PLAYLIST" | "LOADING" | "CONSENT" | "INFO" | "REDIRECT"; - -interface ActionProps { - - view: BlockView; +interface SharedActionProps { title?: string; - url?: string; - next_round?: any[]; - - // Some views require additional data - button_label?: string; - instruction?: string; - timer?: number; - steps: Step[]; - body?: string; - html?: string; - feedback_form?: IFeedbackForm; - playback?: PlaybackArgs; - config?: TrialConfig; - - // TODO: Think about how to properly handle the typing of different views - - // Score-related - score?: number; - score_message?: string; - texts?: { - score: string; - next: string; - listen_explainer: string; - }; - feedback?: string; - icon?: string; - - // Final related - feedback_info?: FeedbackInfo; - rank?: string; - button?: { - text: string; - link: string; - }; - final_text?: string | TrustedHTML; - show_participant_link?: boolean; - participant_id_only?: boolean; - show_profile_link?: boolean; - action_texts?: { - all_experiments: string; - profile: string; - play_again: string; - } - points?: string; - social?: Social; - logo?: { - image: string; - link: string; - }; - - // Consent related - text?: string; - confirm?: string; - deny?: string; + config?: object; + style?: object; } +type ActionProps = SharedActionProps & + ( + | { view: "CONSENT" } & ConsentProps + | { view: "EXPLAINER" } & ExplainerProps + | { view: "INFO" } & InfoProps + | { view: "TRIAL_VIEW" } & TrialProps + | { view: 'SCORE' } & ScoreProps + | { view: 'FINAL' } & FinalProps + | { view: 'PLAYLIST' } & PlaylistProps + | { view: 'REDIRECT' } & RedirectProps + | { view: "LOADING" } & LoadingProps + ) // Block handles the main (experiment) block flow: // - Loads the block and participant @@ -111,7 +64,7 @@ const Block = () => { // Current block state const [actions, setActions] = useState([]); const [state, setState] = useState(startState); - const [key, setKey] = useState(null); + const [key, setKey] = useState(Math.random()); const playlist = useRef(null); // API hooks @@ -239,46 +192,57 @@ const Block = () => { }); // Render block state - const render = (view: BlockView) => { + const render = () => { + + if (!state) { + return ( +
+ No valid state +
+ ); + } + // Default attributes for every view const attrs = { - block, - participant, + block: block!, + participant: participant!, loadingText, onResult, onNext, playlist, + key, ...state, }; // Show view, based on the unique view ID: - switch (view) { + switch (attrs.view) { // Block views // ------------------------- case "TRIAL_VIEW": - return ; + return ; // Information & Scoring // ------------------------- case "EXPLAINER": - return ; + return ; case "SCORE": - return ; + return ; case "FINAL": - return ; + return ; // Generic / helpers // ------------------------- case "PLAYLIST": - return ; + return ; case "LOADING": - return ; + return ; case "CONSENT": - return ; + return ; case "INFO": - return ; + return ; case "REDIRECT": - return window.location.replace(state.url); + window.location.replace(state.url); + return null; default: return ( @@ -326,7 +290,7 @@ const Block = () => { } className={className} > - {render(view)} + {render()} {block?.feedback_info?.show_float_button && ( diff --git a/frontend/src/components/Explainer/Explainer.tsx b/frontend/src/components/Explainer/Explainer.tsx index 8137f6f79..d4a0ff7c1 100644 --- a/frontend/src/components/Explainer/Explainer.tsx +++ b/frontend/src/components/Explainer/Explainer.tsx @@ -6,7 +6,7 @@ interface ExplainerStep { description: string; } -interface ExplainerProps { +export interface ExplainerProps { instruction: string; button_label: string; steps?: Array; diff --git a/frontend/src/components/Final/Final.tsx b/frontend/src/components/Final/Final.tsx index fb3f7679d..c6104a1c2 100644 --- a/frontend/src/components/Final/Final.tsx +++ b/frontend/src/components/Final/Final.tsx @@ -14,7 +14,7 @@ import ISocial from "@/types/Social"; import Block, { FeedbackInfo } from "@/types/Block"; import Participant from "@/types/Participant"; -interface FinalProps { +export interface FinalProps { block: Block; participant: Participant; score: number; diff --git a/frontend/src/components/Info/Info.tsx b/frontend/src/components/Info/Info.tsx index b8de15166..b25affc2b 100644 --- a/frontend/src/components/Info/Info.tsx +++ b/frontend/src/components/Info/Info.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import Button from "../Button/Button"; -interface InfoProps { +export interface InfoProps { heading?: string; body: string | TrustedHTML; button_label?: string; diff --git a/frontend/src/components/Loading/Loading.tsx b/frontend/src/components/Loading/Loading.tsx index df448691f..cdfbabbd6 100644 --- a/frontend/src/components/Loading/Loading.tsx +++ b/frontend/src/components/Loading/Loading.tsx @@ -1,6 +1,6 @@ import Circle from "../Circle/Circle"; -interface LoadingProps { +export interface LoadingProps { duration?: number; loadingText?: string; } diff --git a/frontend/src/components/Playlist/Playlist.tsx b/frontend/src/components/Playlist/Playlist.tsx index 5dab5120d..d62e3befe 100644 --- a/frontend/src/components/Playlist/Playlist.tsx +++ b/frontend/src/components/Playlist/Playlist.tsx @@ -1,7 +1,7 @@ import { ExtendedBlock } from "@/types/Block"; import { MutableRefObject, useEffect } from "react"; -interface PlaylistProps { +export interface PlaylistProps { block: ExtendedBlock; instruction: string; onNext: () => void; diff --git a/frontend/src/components/Redirect/Redirect.tsx b/frontend/src/components/Redirect/Redirect.tsx index f3e929d23..2300dd696 100644 --- a/frontend/src/components/Redirect/Redirect.tsx +++ b/frontend/src/components/Redirect/Redirect.tsx @@ -1,7 +1,7 @@ import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; -interface RedirectProps { +export interface RedirectProps { to: string; } diff --git a/frontend/src/components/Score/Score.tsx b/frontend/src/components/Score/Score.tsx index d78c5caa5..49467b0ad 100644 --- a/frontend/src/components/Score/Score.tsx +++ b/frontend/src/components/Score/Score.tsx @@ -3,7 +3,7 @@ import classNames from "classnames"; import Circle from "../Circle/Circle"; import Button from "../Button/Button"; -interface ScoreProps { +export interface ScoreProps { last_song?: string; score: number; score_message: string; diff --git a/frontend/src/components/Trial/Trial.tsx b/frontend/src/components/Trial/Trial.tsx index a4d717d7a..317af8074 100644 --- a/frontend/src/components/Trial/Trial.tsx +++ b/frontend/src/components/Trial/Trial.tsx @@ -1,7 +1,7 @@ import { useState, useRef, useCallback } from "react"; import classNames from "classnames"; -import { getCurrentTime, getTimeSince } from "../../util/time"; +import { getCurrentTime, getTimeSince } from "@/util/time"; import FeedbackForm from "../FeedbackForm/FeedbackForm"; import HTML from "../HTML/HTML"; import Playback from "../Playback/Playback"; @@ -9,6 +9,7 @@ import Button from "../Button/Button"; import Question from "@/types/Question"; import { OnResultType } from "@/hooks/useResultHandler"; import { TrialConfig } from "@/types/Trial"; +import { PlaybackArgs } from "@/types/Playback"; export interface IFeedbackForm { form: Question[]; @@ -17,8 +18,8 @@ export interface IFeedbackForm { is_skippable: boolean; } -interface TrialProps { - playback: any; +export interface TrialProps { + playback: PlaybackArgs; html: { body: string | TrustedHTML }; feedback_form: IFeedbackForm; config: TrialConfig; diff --git a/frontend/src/util/section.js b/frontend/src/util/section.js deleted file mode 100644 index 1172e6137..000000000 --- a/frontend/src/util/section.js +++ /dev/null @@ -1,16 +0,0 @@ - // Get a section url from given (nested) action - export const getSectionUrl = (action) => { - if (!action) { - return ""; - } - - if (action.section && action.section.url) { - return action.section.url; - } - - if (action.next_round) { - return getSectionUrl(action.next_round); - } - - return ""; -}; diff --git a/frontend/src/util/section.ts b/frontend/src/util/section.ts new file mode 100644 index 000000000..c7b2c9f8c --- /dev/null +++ b/frontend/src/util/section.ts @@ -0,0 +1,18 @@ +/** Get a section url from given (nested) action + * TODO: This function is not used anywhere in the codebase, should it be removed? + */ +export const getSectionUrl = (action) => { + if (!action) { + return ""; + } + + if (action.section && action.section.url) { + return action.section.url; + } + + if (action.next_round) { + return getSectionUrl(action.next_round); + } + + return ""; +}; diff --git a/frontend/src/util/time.js b/frontend/src/util/time.js deleted file mode 100644 index e9d919ad2..000000000 --- a/frontend/src/util/time.js +++ /dev/null @@ -1,3 +0,0 @@ -export const getCurrentTime = () => Date.now() / 1000; - -export const getTimeSince = (time) => getCurrentTime() - time; diff --git a/frontend/src/util/time.ts b/frontend/src/util/time.ts new file mode 100644 index 000000000..785f35eb8 --- /dev/null +++ b/frontend/src/util/time.ts @@ -0,0 +1,3 @@ +export const getCurrentTime = () => Date.now() / 1000; + +export const getTimeSince = (time: number) => getCurrentTime() - time;