Skip to content

Commit

Permalink
Refactor: Properly type Block child components (#1243)
Browse files Browse the repository at this point in the history
* refactor(Block): Properly type the child components of `Block.tsx`

* refactor: Improve typing of key prop

* chore: Remove unused types & imports

* type: Rename `time.js` to `time.ts` and add param type

* type: Rename `section.js` to `section.ts` without typing as it is hard to figure it its param type at a first glance

Also: It seems we are not using this function anywhere in the codebase

* type: Type the `Trial` component's `playback` prop as `PlaybackArgs`
  • Loading branch information
drikusroor authored Sep 2, 2024
1 parent 6734719 commit 2a6c347
Show file tree
Hide file tree
Showing 13 changed files with 82 additions and 115 deletions.
136 changes: 50 additions & 86 deletions frontend/src/components/Block/Block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -111,7 +64,7 @@ const Block = () => {
// Current block state
const [actions, setActions] = useState([]);
const [state, setState] = useState<ActionProps | null>(startState);
const [key, setKey] = useState(null);
const [key, setKey] = useState<number>(Math.random());
const playlist = useRef(null);

// API hooks
Expand Down Expand Up @@ -239,46 +192,57 @@ const Block = () => {
});

// Render block state
const render = (view: BlockView) => {
const render = () => {

if (!state) {
return (
<div className="text-white bg-danger">
No valid state
</div>
);
}

// 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 <Trial key={key} {...attrs} />;
return <Trial {...attrs} />;

// Information & Scoring
// -------------------------
case "EXPLAINER":
return <Explainer key={key} {...attrs}/>;
return <Explainer {...attrs} />;
case "SCORE":
return <Score key={key} {...attrs} />;
return <Score {...attrs} />;
case "FINAL":
return <Final key={key} {...attrs} />;
return <Final {...attrs} />;

// Generic / helpers
// -------------------------
case "PLAYLIST":
return <Playlist key={key} {...attrs} />;
return <Playlist {...attrs} />;
case "LOADING":
return <Loading key={key} {...attrs} />;
return <Loading {...attrs} />;
case "CONSENT":
return <Consent key={key} {...attrs} />;
return <Consent {...attrs} />;
case "INFO":
return <Info key={key} {...attrs} />;
return <Info {...attrs} />;
case "REDIRECT":
return window.location.replace(state.url);
window.location.replace(state.url);
return null;

default:
return (
Expand Down Expand Up @@ -326,7 +290,7 @@ const Block = () => {
}
className={className}
>
{render(view)}
{render()}

{block?.feedback_info?.show_float_button && (
<FloatingActionButton>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Explainer/Explainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface ExplainerStep {
description: string;
}

interface ExplainerProps {
export interface ExplainerProps {
instruction: string;
button_label: string;
steps?: Array<ExplainerStep>;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Final/Final.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Info/Info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Loading/Loading.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Circle from "../Circle/Circle";

interface LoadingProps {
export interface LoadingProps {
duration?: number;
loadingText?: string;
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Playlist/Playlist.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Redirect/Redirect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";

interface RedirectProps {
export interface RedirectProps {
to: string;
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Score/Score.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/components/Trial/Trial.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
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";
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[];
Expand All @@ -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;
Expand Down
16 changes: 0 additions & 16 deletions frontend/src/util/section.js

This file was deleted.

18 changes: 18 additions & 0 deletions frontend/src/util/section.ts
Original file line number Diff line number Diff line change
@@ -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 "";
};
3 changes: 0 additions & 3 deletions frontend/src/util/time.js

This file was deleted.

3 changes: 3 additions & 0 deletions frontend/src/util/time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const getCurrentTime = () => Date.now() / 1000;

export const getTimeSince = (time: number) => getCurrentTime() - time;

0 comments on commit 2a6c347

Please sign in to comment.