diff --git a/frontend/package.json b/frontend/package.json index 6dff1e633..4739d3443 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,7 +36,7 @@ "storybook": "storybook dev -p 6006 --no-open", "storybook:build": "storybook build", "lint": "eslint src/**/*.{js,jsx,ts,tsx}", - "lint:fix": "eslint --fix src/**/*.js", + "lint:fix": "eslint --fix src/**/*.{js,jsx,ts,tsx}", "build-storybook": "storybook build" }, "eslintConfig": { diff --git a/frontend/src/API.ts b/frontend/src/API.ts index 05eb160e3..e6a88ee28 100644 --- a/frontend/src/API.ts +++ b/frontend/src/API.ts @@ -7,6 +7,7 @@ import IExperiment from "@/types/Experiment"; import Participant, { ParticipantLink } from "./types/Participant"; import Session from "./types/Session"; import Experiment from "@/types/Experiment"; +import { RoundResponse } from "./types/Round"; // API handles the calls to the Hooked-server api @@ -173,7 +174,7 @@ interface GetNextRoundParams { // Get next_round from server -export const getNextRound = async ({ session }: GetNextRoundParams) => { +export const getNextRound = async ({ session }: GetNextRoundParams): Promise => { const sessionId = session.id.toString(); diff --git a/frontend/src/components/AppBar/AppBar.test.tsx b/frontend/src/components/AppBar/AppBar.test.tsx index 4b4e71ce8..7b96355d1 100644 --- a/frontend/src/components/AppBar/AppBar.test.tsx +++ b/frontend/src/components/AppBar/AppBar.test.tsx @@ -1,4 +1,4 @@ -import { render, fireEvent } from '@testing-library/react'; +import { render } from '@testing-library/react'; import AppBar from './AppBar'; import { BrowserRouter as Router } from 'react-router-dom'; import { vi, describe, beforeEach, it, expect } from 'vitest'; diff --git a/frontend/src/components/Block/Block.test.tsx b/frontend/src/components/Block/Block.test.tsx index 7f5a2081a..b36d2e501 100644 --- a/frontend/src/components/Block/Block.test.tsx +++ b/frontend/src/components/Block/Block.test.tsx @@ -42,7 +42,7 @@ vi.mock('../../API', () => ({ vi.mock('../../util/stores', () => ({ __esModule: true, - default: (fn) => { + default: (fn: any) => { const state = { session: mockSessionStore, participant: mockParticipantStore, @@ -51,6 +51,7 @@ vi.mock('../../util/stores', () => ({ setHeadData: vi.fn(), resetHeadData: vi.fn(), setBlock: vi.fn(), + setCurrentAction: vi.fn(), }; return fn(state); diff --git a/frontend/src/components/Block/Block.tsx b/frontend/src/components/Block/Block.tsx index 5707ffb38..43dea159b 100644 --- a/frontend/src/components/Block/Block.tsx +++ b/frontend/src/components/Block/Block.tsx @@ -6,37 +6,20 @@ import classNames from "classnames"; import useBoundStore from "@/util/stores"; import { getNextRound, useBlock } from "@/API"; import DefaultPage from "@/components/Page/DefaultPage"; -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 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 from "@/components/Trial/Trial"; +import Info 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 { RedirectProps } from "../Redirect/Redirect"; - -interface SharedActionProps { - title?: string; - config?: object; - style?: object; -} - -type ActionProps = SharedActionProps & - ( - | { 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 - ) +import { Action } from "@/types/Action"; +import { Round } from "@/types/Round"; // Block handles the main (experiment) block flow: // - Loads the block and participant @@ -46,7 +29,7 @@ type ActionProps = SharedActionProps & // Empty URL parameter "participant_id" is the same as no URL parameter at all const Block = () => { const { slug } = useParams(); - const startState = { view: "LOADING" } as ActionProps; + const startState = { view: "LOADING" } as Action; // Stores const setError = useBoundStore(state => state.setError); const participant = useBoundStore((state) => state.participant); @@ -56,36 +39,39 @@ const Block = () => { const setTheme = useBoundStore((state) => state.setTheme); const resetTheme = useBoundStore((state) => state.resetTheme); const setBlock = useBoundStore((state) => state.setBlock); + const setCurrentAction = useBoundStore((state) => state.setCurrentAction); const setHeadData = useBoundStore((state) => state.setHeadData); const resetHeadData = useBoundStore((state) => state.resetHeadData); // Current block state - const [actions, setActions] = useState([]); - const [state, setState] = useState(startState); + const [actions, setActions] = useState([]); + const [state, setState] = useState(startState); const [key, setKey] = useState(Math.random()); const playlist = useRef(null); // API hooks - const [block, loadingBlock] = useBlock(slug); + const [block, loadingBlock] = useBlock(slug!); const loadingText = block ? block.loading_text : ""; const className = block ? block.class_name : ""; /** Set new state as spread of current state to force re-render */ - const updateState = useCallback((state: ActionProps) => { + const updateState = useCallback((state: Action) => { if (!state) return; setState({ ...state }); setKey(Math.random()); }, []); - const updateActions = useCallback((currentActions: []) => { + const updateActions = useCallback((currentActions: Round) => { const newActions = currentActions; setActions(newActions); const newState = newActions.shift(); + const currentAction = newState ? newState : null; + setCurrentAction({ ...currentAction }); updateState(newState); - }, [updateState]); + }, [updateState, setCurrentAction]); const continueToNextRound = async (activeSession: Session) => { // Try to get next_round data from server @@ -98,6 +84,7 @@ const Block = () => { setError( "An error occured while loading the data, please try to reload the page. (Error: next_round data unavailable)" ); + setCurrentAction(null); setState(null); } }; diff --git a/frontend/src/components/Experiment/Header/Header.tsx b/frontend/src/components/Experiment/Header/Header.tsx index 2e6be4dae..afbd7d51c 100644 --- a/frontend/src/components/Experiment/Header/Header.tsx +++ b/frontend/src/components/Experiment/Header/Header.tsx @@ -30,9 +30,6 @@ export const Header: React.FC = ({ socialMediaConfig }) => { - // Get current URL minus the query string - const currentUrl = window.location.href.split('?')[0]; - return (
diff --git a/frontend/src/components/Explainer/Explainer.tsx b/frontend/src/components/Explainer/Explainer.tsx index d5fc42245..b5ca016c4 100644 --- a/frontend/src/components/Explainer/Explainer.tsx +++ b/frontend/src/components/Explainer/Explainer.tsx @@ -1,16 +1,8 @@ import { useEffect } from "react"; import Button from "../Button/Button"; +import { Explainer as ExplainerAction } from "@/types/Action"; -interface ExplainerStep { - number: number; - description: string; -} - -export interface ExplainerProps { - instruction: string; - button_label: string; - steps?: Array; - timer: number | null; +export interface ExplainerProps extends ExplainerAction { onNext: () => void; } diff --git a/frontend/src/components/Final/Final.tsx b/frontend/src/components/Final/Final.tsx index 25d94d738..324a6fb23 100644 --- a/frontend/src/components/Final/Final.tsx +++ b/frontend/src/components/Final/Final.tsx @@ -10,39 +10,10 @@ import useBoundStore from "../../util/stores"; import ParticipantLink from "../ParticipantLink/ParticipantLink"; import UserFeedback from "../UserFeedback/UserFeedback"; import FinalButton from "./FinalButton"; -import ISocial from "@/types/Social"; -import Block, { FeedbackInfo } from "@/types/Block"; -import Participant from "@/types/Participant"; +import { Final as FinalAction } from "@/types/Action"; -export interface FinalProps { - block: Block; - participant: Participant; - score: number; - final_text: string | TrustedHTML; - action_texts: { - all_experiments: string; - profile: string; - play_again: string; - } - button: { - text: string; - link: string; - }; +export interface FinalProps extends FinalAction { onNext: () => void; - show_participant_link: boolean; - participant_id_only: boolean; - show_profile_link: boolean; - social: ISocial; - feedback_info?: FeedbackInfo; - points: string; - rank: { - class: string; - text: string; - } - logo: { - image: string; - link: string; - }; } /** diff --git a/frontend/src/components/Histogram/Histogram.test.tsx b/frontend/src/components/Histogram/Histogram.test.tsx index 7ccd40456..9912326db 100644 --- a/frontend/src/components/Histogram/Histogram.test.tsx +++ b/frontend/src/components/Histogram/Histogram.test.tsx @@ -1,5 +1,5 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { render, act, waitFor } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach, afterEach, Mock, } from 'vitest'; +import { render, act } from '@testing-library/react'; import Histogram from './Histogram'; // Mock requestAnimationFrame and cancelAnimationFrame @@ -14,9 +14,21 @@ vi.stubGlobal('cancelAnimationFrame', (handle: number): void => { // Mock setInterval and clearInterval vi.useFakeTimers(); +vi.mock('../../util/stores', () => ({ + __esModule: true, + default: (fn: any) => { + const state = { + currentAction: { playback: { play_method: 'BUFFER' } }, + }; + + return fn(state); + }, + useBoundStore: vi.fn() +})); + describe('Histogram', () => { let mockAnalyser: { - getByteFrequencyData: vi.Mock; + getByteFrequencyData: Mock }; beforeEach(() => { @@ -154,11 +166,8 @@ describe('Histogram', () => { it('updates bar heights based on random data when random is true and running is true', async () => { const bars = 5; - // Ensure the analyser does not provide data - mockAnalyser.getByteFrequencyData.mockImplementation(() => { }); - const { container, rerender } = render( - + ); const getHeights = () => @@ -168,9 +177,9 @@ describe('Histogram', () => { const initialHeights = getHeights(); - // Advance timers and trigger animation frame + // Advance timers by at least one interval await act(async () => { - vi.advanceTimersByTime(100); + vi.advanceTimersByTime(200); }); rerender(); @@ -178,7 +187,6 @@ describe('Histogram', () => { const updatedHeights = getHeights(); expect(initialHeights).not.to.deep.equal(updatedHeights); - expect(mockAnalyser.getByteFrequencyData).not.toHaveBeenCalled(); }); it('does not call getByteFrequencyData when random is true', async () => { @@ -225,6 +233,7 @@ describe('Histogram', () => { it('updates bar heights based on frequency data using requestAnimationFrame', async () => { const bars = 5; + mockAnalyser.getByteFrequencyData.mockImplementation((array) => { for (let i = 0; i < array.length; i++) { array[i] = Math.floor(Math.random() * 256); diff --git a/frontend/src/components/Histogram/Histogram.tsx b/frontend/src/components/Histogram/Histogram.tsx index e3055199a..519180887 100644 --- a/frontend/src/components/Histogram/Histogram.tsx +++ b/frontend/src/components/Histogram/Histogram.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; +import useBoundStore from '@/util/stores'; interface HistogramProps { bars?: number; @@ -27,10 +28,15 @@ const Histogram: React.FC = ({ backgroundColor = undefined, borderRadius = '0.15rem', random = false, - interval = 100, + interval = 200, }) => { const [frequencyData, setFrequencyData] = useState(new Uint8Array(bars)); + const currentAction = useBoundStore((state) => state.currentAction); + const isBuffer = currentAction?.playback?.play_method === 'BUFFER'; + + const shouldRandomize = random || !isBuffer; + const animationFrameRef = useRef(); const intervalRef = useRef(); @@ -50,7 +56,7 @@ const Histogram: React.FC = ({ const updateFrequencyData = () => { let dataWithoutExtremes: Uint8Array; - if (random) { + if (shouldRandomize) { // Generate random frequency data dataWithoutExtremes = new Uint8Array(bars); for (let i = 0; i < bars; i++) { @@ -71,14 +77,14 @@ const Histogram: React.FC = ({ } }; - if (random) { - // Use setInterval when random is true + if (shouldRandomize) { + // Use setInterval when shouldRandomize is true if (intervalRef.current) { clearInterval(intervalRef.current); } intervalRef.current = window.setInterval(updateFrequencyData, interval); } else { - // Use requestAnimationFrame when random is false + // Use requestAnimationFrame when shouldRandomize is false if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } @@ -93,7 +99,7 @@ const Histogram: React.FC = ({ clearInterval(intervalRef.current); } }; - }, [running, bars, random, interval]); + }, [running, bars, shouldRandomize, interval]); const barWidth = `calc((100% - ${(bars - 1) * spacing}px) / ${bars})`; @@ -120,7 +126,7 @@ const Histogram: React.FC = ({ height: `${(frequencyData[index] / 255) * 100}%`, backgroundColor: 'currentColor', marginRight: index < bars - 1 ? spacing : 0, - transition: random + transition: shouldRandomize ? `height ${interval / 1000}s ease` : 'height 0.05s ease', }} diff --git a/frontend/src/components/Info/Info.tsx b/frontend/src/components/Info/Info.tsx index f9f682f54..b187b96dc 100644 --- a/frontend/src/components/Info/Info.tsx +++ b/frontend/src/components/Info/Info.tsx @@ -1,12 +1,9 @@ import { useEffect, useState } from "react"; import Button from "../Button/Button"; +import { Info as InfoAction } from "@/types/Action"; -export interface InfoProps { - heading?: string; - body: string | TrustedHTML; - button_label?: string; - button_link?: string; +export interface InfoProps extends InfoAction { onNext?: () => void; } diff --git a/frontend/src/components/ListenCircle/ListenCircle.tsx b/frontend/src/components/ListenCircle/ListenCircle.tsx index b3af4cc3e..e2d098030 100644 --- a/frontend/src/components/ListenCircle/ListenCircle.tsx +++ b/frontend/src/components/ListenCircle/ListenCircle.tsx @@ -16,15 +16,7 @@ const ListenCircle = ({ <>
- +
); diff --git a/frontend/src/components/MatchingPairs/MatchingPairs.test.tsx b/frontend/src/components/MatchingPairs/MatchingPairs.test.tsx index 181d4d035..115a96d7c 100644 --- a/frontend/src/components/MatchingPairs/MatchingPairs.test.tsx +++ b/frontend/src/components/MatchingPairs/MatchingPairs.test.tsx @@ -10,17 +10,18 @@ import MatchingPairs, { SCORE_FEEDBACK_DISPLAY } from './MatchingPairs'; let mock: MockAdapter; vi.mock("@/components/PlayButton/PlayCard", () => ({ - default: props =>
+ default: (props: any) =>
})); vi.mock("../../util/stores", () => ({ __esModule: true, - default: (fn) => { + default: (fn: any) => { const state = { participant: 1, session: 1, setError: vi.fn(), - block: { bonus_points: 42 } + block: { bonus_points: 42 }, + currentAction: () => ({ view: 'TRIAL_VIEW' }), }; return fn(state); }, diff --git a/frontend/src/components/Playlist/Playlist.tsx b/frontend/src/components/Playlist/Playlist.tsx index f6161bf2d..089bef978 100644 --- a/frontend/src/components/Playlist/Playlist.tsx +++ b/frontend/src/components/Playlist/Playlist.tsx @@ -1,11 +1,10 @@ +import { Playlist as PlaylistAction } from "@/types/Action"; import Block from "@/types/Block"; -import { MutableRefObject, useEffect } from "react"; +import { useEffect } from "react"; -export interface PlaylistProps { +export interface PlaylistProps extends PlaylistAction { block: Block; - instruction: string; onNext: () => void; - playlist: MutableRefObject; } /** diff --git a/frontend/src/components/Score/Score.tsx b/frontend/src/components/Score/Score.tsx index 49467b0ad..191e1fe0a 100644 --- a/frontend/src/components/Score/Score.tsx +++ b/frontend/src/components/Score/Score.tsx @@ -2,20 +2,9 @@ import { useState, useEffect, useRef } from "react"; import classNames from "classnames"; import Circle from "../Circle/Circle"; import Button from "../Button/Button"; +import { Score as ScoreAction } from "@/types/Action"; -export interface ScoreProps { - last_song?: string; - score: number; - score_message: string; - total_score?: number; - texts: { - score: string; - next: string; - listen_explainer: string; - }; - icon?: string; - feedback?: string; - timer?: number; +export interface ScoreProps extends ScoreAction { onNext: () => void; } diff --git a/frontend/src/components/Trial/Trial.test.tsx b/frontend/src/components/Trial/Trial.test.tsx index fe3efa9c3..29c8c8110 100644 --- a/frontend/src/components/Trial/Trial.test.tsx +++ b/frontend/src/components/Trial/Trial.test.tsx @@ -43,7 +43,6 @@ const defaultConfig = { describe('Trial', () => { const mockOnNext = vi.fn(); const mockOnResult = vi.fn(); - const mockMakeResult = vi.fn(); beforeEach(() => { vi.clearAllMocks(); diff --git a/frontend/src/components/Trial/Trial.tsx b/frontend/src/components/Trial/Trial.tsx index 76e19a7d5..48db9b826 100644 --- a/frontend/src/components/Trial/Trial.tsx +++ b/frontend/src/components/Trial/Trial.tsx @@ -6,23 +6,11 @@ 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"; +import { Trial as TrialAction } from "@/types/Action"; -export interface IFeedbackForm { - form: Question[]; - submit_label: string; - skip_label: string; - is_skippable: boolean; -} - -export interface TrialProps { - playback: PlaybackArgs; - html: { body: string | TrustedHTML }; - feedback_form: IFeedbackForm; - config: TrialConfig; +export interface TrialProps extends TrialAction { onNext: (breakRound?: boolean) => void; onResult: OnResultType; } diff --git a/frontend/src/stories/Histogram.stories.tsx b/frontend/src/stories/Histogram.stories.tsx index cc9e2b767..906be212e 100644 --- a/frontend/src/stories/Histogram.stories.tsx +++ b/frontend/src/stories/Histogram.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react'; +import type { Meta, StoryFn } from '@storybook/react'; import Histogram from "../components/Histogram/Histogram"; const meta: Meta = { @@ -22,7 +22,7 @@ export const Default = { borderRadius: "0.15rem", }, decorators: [ - (Story) => ( + (Story: any) => (
( + (Story: StoryFn) => (
(Story: StoryFn) => { + const [initialized, setInitialized] = useState(false); + + const setCurrentAction = useBoundStore((state) => state.setCurrentAction); + setCurrentAction({ + view: "TRIAL_VIEW", + playback: { view: AUTOPLAY, - play_method: "BUFFER", + play_method, show_animation: true, preload_message: "Loading audio...", instruction: "Click the button to play the audio.", - sections: [ - { - id: 0, - url: audio, - } - ], + sections: [{ id: 0, url: audio }], play_from: 0.0, resume_play: false, - }, - onPreloadReady: () => { }, - autoAdvance: false, - responseTime: 10, - submitResult: () => { }, - finishedPlaying: () => { }, - } as PlaybackProps, - decorators: [ - (Story) => { + } + }); - const [initialized, setInitialized] = useState(false); + if (!initialized) { + return ( + <> + + + ); + } + return ( +
+ +
+ ); +}; - if (!initialized) { - return ( - <> - - - ) +// Create playback arguments dynamically +const createPlaybackArgs = (play_method: "BUFFER" | "EXTERNAL"): PlaybackProps => ({ + playbackArgs: { + view: AUTOPLAY, + play_method, + show_animation: true, + preload_message: "Loading audio...", + instruction: "Click the button to play the audio.", + sections: [ + { + id: 0, + url: audio, } + ], + play_from: 0.0, + resume_play: false, + }, + onPreloadReady: () => { }, + autoAdvance: false, + responseTime: 10, + submitResult: () => { }, + finishedPlaying: () => { }, +}); - return ( -
- -
- ) - } - ], +export const PlaybackAutoplayBuffer = { + args: createPlaybackArgs("BUFFER"), + decorators: [createCommonDecorator("BUFFER")], +}; + +export const PlaybackAutoplayExternal = { + args: createPlaybackArgs("EXTERNAL"), + decorators: [createCommonDecorator("EXTERNAL")], }; diff --git a/frontend/src/types/Action.ts b/frontend/src/types/Action.ts new file mode 100644 index 000000000..1d937353f --- /dev/null +++ b/frontend/src/types/Action.ts @@ -0,0 +1,119 @@ +import Social from "@/types/Social"; +import Block, { FeedbackInfo } from "@/types/Block"; +import Participant from "@/types/Participant"; +import { PlaybackArgs } from "./Playback"; +import Question from "./Question"; +import { TrialConfig } from "./Trial"; +import { MutableRefObject } from "react"; + +interface SharedActionProps { + title?: string; + config?: object; + style?: object; +} + +interface ExplainerStep { + number: number; + description: string; +} + +export interface Explainer { + instruction: string; + button_label: string; + steps?: Array; + timer: number | null; +} + +export interface Info { + heading?: string; + body: string | TrustedHTML; + button_label?: string; + button_link?: string; +} + +export interface IFeedbackForm { + form: Question[]; + submit_label: string; + skip_label: string; + is_skippable: boolean; +} + +export interface Trial { + playback: PlaybackArgs; + html: { body: string | TrustedHTML }; + feedback_form: IFeedbackForm; + config: TrialConfig; +} + +export interface Score { + last_song?: string; + score: number; + score_message: string; + total_score?: number; + texts: { + score: string; + next: string; + listen_explainer: string; + }; + icon?: string; + feedback?: string; + timer?: number; +} + +export interface Final { + block: Block; + participant: Participant; + score: number; + final_text: string | TrustedHTML; + action_texts: { + all_experiments: string; + profile: string; + play_again: string; + } + button: { + text: string; + link: string; + }; + show_participant_link: boolean; + participant_id_only: boolean; + show_profile_link: boolean; + social: Social; + feedback_info?: FeedbackInfo; + points: string; + rank: { + class: string; + text: string; + } + logo: { + image: string; + link: string; + }; +} + +export interface Playlist { + instruction: string; + playlist: MutableRefObject; +} + +export interface Redirect { + url: string; +} + +export interface Loading { + duration?: number; + loadingText?: string; +} + +export type Action = SharedActionProps & + ( + | { view: "EXPLAINER" } & Explainer + | { view: "INFO" } & Info + | { view: "TRIAL_VIEW" } & Trial + | { view: 'SCORE' } & Score + | { view: 'FINAL' } & Final + | { view: 'PLAYLIST' } & Playlist + | { view: 'REDIRECT' } & Redirect + | { view: "LOADING" } & Loading + ) + +export default Action; diff --git a/frontend/src/types/Round.ts b/frontend/src/types/Round.ts new file mode 100644 index 000000000..fc5e3676f --- /dev/null +++ b/frontend/src/types/Round.ts @@ -0,0 +1,7 @@ +import { Action } from "./Action"; + +export type Round = Action[]; + +export interface RoundResponse { + next_round: Round; +} diff --git a/frontend/src/util/stores.ts b/frontend/src/util/stores.ts index aa4258b6b..4289ef2e6 100644 --- a/frontend/src/util/stores.ts +++ b/frontend/src/util/stores.ts @@ -5,6 +5,7 @@ import IParticipant from "@/types/Participant"; import ISession from "@/types/Session"; import ITheme from "@/types/Theme"; import IBlock from "@/types/Block"; +import { Action } from '@/types/Action'; interface BlockSlice { block?: IBlock; @@ -96,6 +97,16 @@ const createParticipantSlice: StateCreator = (set) => ({ setParticipantLoading: (participantLoading: boolean) => set(() => ({ participantLoading })) }); +interface ActionSlice { + currentAction: Action | null; + setCurrentAction: (action: Action) => void; +} + +const createActionSlice: StateCreator = (set) => ({ + setCurrentAction: (action: Action) => set(() => ({ currentAction: action })), + currentAction: null, +}); + interface SessionSlice { session: ISession | null; setSession: (session: ISession) => void; @@ -118,11 +129,12 @@ const createThemeSlice: StateCreator = (set) => ({ resetTheme: () => set(() => ({ theme: null })), }); -export const useBoundStore = create((...args) => ({ +export const useBoundStore = create((...args) => ({ ...createBlockSlice(...args), ...createDocumentHeadSlice(...args), ...createErrorSlice(...args), ...createParticipantSlice(...args), + ...createActionSlice(...args), ...createSessionSlice(...args), ...createThemeSlice(...args), })); diff --git a/frontend/src/util/webAudio.ts b/frontend/src/util/webAudio.ts index 02d519ae7..59cd68478 100644 --- a/frontend/src/util/webAudio.ts +++ b/frontend/src/util/webAudio.ts @@ -4,7 +4,6 @@ let track: MediaElementAudioSourceNode; let source: AudioBufferSourceNode; let buffers: { [key: string]: AudioBuffer } = {}; let audioContext: AudioContext; -let previousSource: string; let analyzer: AnalyserNode; export let audioInitialized = false; @@ -97,7 +96,6 @@ export const loadBuffer = async (id: number, src: string, canPlay: () => void) = // store buffer in buffers object .then(decodedData => { buffers[id] = decodedData; - previousSource = src; canPlay(); }); }; diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index e218d6f7a..27de7b68d 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -7,6 +7,7 @@ export default defineConfig({ include: ['**/*.test.js', '**/*.test.jsx', '**/*.test.ts', '**/*.test.tsx'], globals: true, environment: 'happy-dom', + allowOnly: true, coverage: { reportsDirectory: 'public/coverage', provider: 'v8',