diff --git a/.gitignore b/.gitignore index ae6c06af5..048b61d64 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ e2e/screenshots e2e/tests/__pycache__ temp/ + +docs-pages-build/ diff --git a/backend/experiment/actions/explainer.py b/backend/experiment/actions/explainer.py index d605dc4c0..e2f9e7333 100644 --- a/backend/experiment/actions/explainer.py +++ b/backend/experiment/actions/explainer.py @@ -1,48 +1,79 @@ +from typing import List, TypedDict, Optional from .base_action import BaseAction +class StepResponse(TypedDict): + number: Optional[int] + description: str + + +class ExplainerResponse(TypedDict): + view: str + instruction: str + button_label: str + steps: List[StepResponse] + timer: Optional[int] + + +class Step(object): + """ + A step in an explainer + + Args: + description (str): Description of the step + number (Optional[int]): Optional number of the step + """ + + def __init__(self, description: str, number: Optional[int] = None): + self.description = description + self.number = number + + def action(self, number=None) -> StepResponse: + """Create an explainer step, with description and optional number""" + return {"number": self.number if self.number else number, "description": self.description} + + class Explainer(BaseAction): """ Provide data for a explainer that explains the experiment steps - Relates to client component: Explainer.js + Relates to client component: Explainer.tsx Explainer view automatically proceeds to the following view after timer (in ms) expires. If timer=None, explainer view will proceed to the next view only after a click of a button. Intro explainers should always have timer=None (i.e. interaction with a browser is required), otherwise the browser will not autoplay the first segment. + + Args: + instruction (str): Instruction for the explainer + steps (List[Step]): List of steps to explain + button_label (Optional[str]): Label for the button that proceeds to the next view + timer (Optional[int]): Timer in ms + step_numbers (Optional[bool]): Show step numbers """ ID = "EXPLAINER" - def __init__(self, instruction, steps, button_label="Let's go!", timer=None, step_numbers=False): + def __init__( + self, + instruction: str, + steps: List[Step], + button_label="Let's go!", + timer: Optional[int] = None, + step_numbers: Optional[bool] = False, + ): self.instruction = instruction self.steps = steps self.button_label = button_label self.timer = timer self.step_numbers = step_numbers - def action(self): - """Get data for explainer action""" + def action(self) -> ExplainerResponse: if self.step_numbers: - serialized_steps = [step.action(index+1) for index, step in enumerate(self.steps)] + serialized_steps = [step.action(index + 1) for index, step in enumerate(self.steps)] else: serialized_steps = [step.action() for step in self.steps] return { - 'view': self.ID, - 'instruction': self.instruction, - 'button_label': self.button_label, - 'steps': serialized_steps, - 'timer': self.timer, - } - - -class Step(object): - - def __init__(self, description, number=None): - self.description = description - self.number = number - - def action(self, number=None): - """Create an explainer step, with description and optional number""" - return { - 'number': self.number if self.number else number, - 'description': self.description + "view": self.ID, + "instruction": self.instruction, + "button_label": self.button_label, + "steps": serialized_steps, + "timer": self.timer, } diff --git a/backend/experiment/actions/html.py b/backend/experiment/actions/html.py index bc5052311..bf69524ec 100644 --- a/backend/experiment/actions/html.py +++ b/backend/experiment/actions/html.py @@ -2,16 +2,19 @@ from .base_action import BaseAction -class HTML(BaseAction): # pylint: disable=too-few-public-methods - """ - A custom view that handles a custom HTML question - Relates to client component: HTML.js +class HTML(BaseAction): + """An action that renders HTML content. See also the `HTML.tsx` component in the frontend project. + + Args: + body (str): The HTML body content + + Examples: + To render a simple HTML snippet with a title and a paragraph: + + >>> html_action = HTML('

Hello, world!

This is a simple HTML snippet.

') """ - ID = 'HTML' + ID = "HTML" - def __init__(self, body): - """ - - body: HTML body - """ + def __init__(self, body: str): self.body = body diff --git a/backend/experiment/actions/playback.py b/backend/experiment/actions/playback.py index 4370c0ace..0791eb964 100644 --- a/backend/experiment/actions/playback.py +++ b/backend/experiment/actions/playback.py @@ -5,47 +5,47 @@ from section.validators import audio_extensions # player types -TYPE_AUTOPLAY = 'AUTOPLAY' -TYPE_BUTTON = 'BUTTON' -TYPE_IMAGE = 'IMAGE' -TYPE_MULTIPLAYER = 'MULTIPLAYER' -TYPE_MATCHINGPAIRS = 'MATCHINGPAIRS' +TYPE_AUTOPLAY = "AUTOPLAY" +TYPE_BUTTON = "BUTTON" +TYPE_IMAGE = "IMAGE" +TYPE_MULTIPLAYER = "MULTIPLAYER" +TYPE_MATCHINGPAIRS = "MATCHINGPAIRS" # playback methods -PLAY_EXTERNAL = 'EXTERNAL' -PLAY_HTML = 'HTML' -PLAY_BUFFER = 'BUFFER' -PLAY_NOAUDIO = 'NOAUDIO' +PLAY_EXTERNAL = "EXTERNAL" +PLAY_HTML = "HTML" +PLAY_BUFFER = "BUFFER" +PLAY_NOAUDIO = "NOAUDIO" class Playback(BaseAction): - ''' A playback base class for different kinds of players - - sections: a list of sections (in many cases, will only contain *one* section) - - preload_message: text to display during preload - - instruction: text to display during presentation of the sound - - play_from: where in the audio file to start playing/ - - show_animation: whether to show animations with this player - - mute: whether to mute the audio - - timeout_after_playback: once playback has finished, add optional timeout (in seconds) before proceeding - - stop_audio_after: stop playback after so many seconds - - resume_play: if the playback should resume from where a previous view left off - ''' + """A playback base class for different kinds of players + - sections: a list of sections (in many cases, will only contain *one* section) + - preload_message: text to display during preload + - instruction: text to display during presentation of the sound + - play_from: where in the audio file to start playing/ + - show_animation: whether to show animations with this player + - mute: whether to mute the audio + - timeout_after_playback: once playback has finished, add optional timeout (in seconds) before proceeding + - stop_audio_after: stop playback after so many seconds + - resume_play: if the playback should resume from where a previous view left off + """ def __init__( self, sections, - preload_message='', - instruction='', + preload_message="", + instruction="", play_from=0, show_animation=False, mute=False, timeout_after_playback=None, stop_audio_after=None, resume_play=False, - style=FrontendStyle() + style=FrontendStyle(), + tutorial: Dict | None = None, ): - self.sections = [{'id': s.id, 'url': s.absolute_url(), 'group': s.group} - for s in sections] + self.sections = [{"id": s.id, "url": s.absolute_url(), "group": s.group} for s in sections] self.play_method = determine_play_method(sections[0]) self.show_animation = show_animation self.preload_message = preload_message @@ -56,13 +56,14 @@ def __init__( self.stop_audio_after = stop_audio_after self.resume_play = resume_play self.style = style + self.tutorial = tutorial class Autoplay(Playback): - ''' + """ This player starts playing automatically - show_animation: if True, show a countdown and moving histogram - ''' + """ def __init__(self, sections, **kwargs): super().__init__(sections, **kwargs) @@ -70,10 +71,10 @@ def __init__(self, sections, **kwargs): class PlayButton(Playback): - ''' + """ This player shows a button, which triggers playback - play_once: if True, button will be disabled after one play - ''' + """ def __init__(self, sections, play_once=False, **kwargs): super().__init__(sections, **kwargs) @@ -82,11 +83,11 @@ def __init__(self, sections, play_once=False, **kwargs): class Multiplayer(PlayButton): - ''' + """ This is a player with multiple play buttons - stop_audio_after: after how many seconds to stop audio - labels: pass list of strings if players should have custom labels - ''' + """ def __init__( self, @@ -102,50 +103,50 @@ def __init__( self.style = style if labels: if len(labels) != len(self.sections): - raise UserWarning( - 'Number of labels and sections for the play buttons do not match') + raise UserWarning("Number of labels and sections for the play buttons do not match") self.labels = labels class ImagePlayer(Multiplayer): - ''' + """ This is a special case of the Multiplayer: it shows an image next to each play button - ''' + """ def __init__(self, sections, images, image_labels=[], **kwargs): super().__init__(sections, **kwargs) self.ID = TYPE_IMAGE if len(images) != len(self.sections): - raise UserWarning( - 'Number of images and sections for the ImagePlayer do not match') + raise UserWarning("Number of images and sections for the ImagePlayer do not match") self.images = images if image_labels: if len(image_labels) != len(self.sections): - raise UserWarning( - 'Number of image labels and sections do not match') + raise UserWarning("Number of image labels and sections do not match") self.image_labels = image_labels class MatchingPairs(Multiplayer): - ''' + """ This is a special case of multiplayer: play buttons are represented as cards - sections: a list of sections (in many cases, will only contain *one* section) - score_feedback_display: how to display the score feedback (large-top, small-bottom-right, hidden) - ''' + """ - def __init__(self, sections: List[Dict], score_feedback_display: str = 'large-top', **kwargs): + def __init__( + self, sections: List[Dict], score_feedback_display: str = "large-top", tutorial: Dict | None = None, **kwargs + ): super().__init__(sections, **kwargs) self.ID = TYPE_MATCHINGPAIRS self.score_feedback_display = score_feedback_display + self.tutorial = tutorial def determine_play_method(section): filename = str(section.filename) if not is_audio_file(filename): return PLAY_NOAUDIO - elif filename.startswith('http'): + elif filename.startswith("http"): return PLAY_EXTERNAL elif section.duration > 45: return PLAY_HTML diff --git a/backend/experiment/actions/redirect.py b/backend/experiment/actions/redirect.py index ec0d050a6..15e05565c 100644 --- a/backend/experiment/actions/redirect.py +++ b/backend/experiment/actions/redirect.py @@ -2,7 +2,19 @@ class Redirect(BaseAction): - ID = 'REDIRECT' + """ + Redirect Action + + This action is used to redirect the user to a specified URL. + + Args: + url (str): The URL to redirect to. + + Example: + redirect_action = Redirect('https://example.com') + """ + + ID = "REDIRECT" def __init__(self, url): self.url = url diff --git a/backend/experiment/rules/__init__.py b/backend/experiment/rules/__init__.py index 7925417d2..a0f68e155 100644 --- a/backend/experiment/rules/__init__.py +++ b/backend/experiment/rules/__init__.py @@ -15,6 +15,7 @@ from .matching_pairs_fixed import MatchingPairsFixed from .matching_pairs_lite import MatchingPairsLite from .matching_pairs_icmpc import MatchingPairsICMPC +from .matching_pairs_2025 import MatchingPairs2025 from .musical_preferences import MusicalPreferences from .rhythm_battery_final import RhythmBatteryFinal from .rhythm_battery_intro import RhythmBatteryIntro @@ -59,6 +60,7 @@ MatchingPairsGame.ID: MatchingPairsGame, MatchingPairsLite.ID: MatchingPairsLite, MatchingPairsICMPC.ID: MatchingPairsICMPC, + MatchingPairs2025.ID: MatchingPairs2025, MusicalPreferences.ID: MusicalPreferences, RhythmBatteryFinal.ID: RhythmBatteryFinal, RhythmBatteryIntro.ID: RhythmBatteryIntro, diff --git a/backend/experiment/rules/matching_pairs.py b/backend/experiment/rules/matching_pairs.py index 208fe1086..e4cde2739 100644 --- a/backend/experiment/rules/matching_pairs.py +++ b/backend/experiment/rules/matching_pairs.py @@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _ from .base import Base -from experiment.actions import Consent, Explainer, Final, Playlist, Step, Trial +from experiment.actions import Explainer, Final, Playlist, Step, Trial from experiment.actions.playback import MatchingPairs from result.utils import prepare_result @@ -17,6 +17,7 @@ class MatchingPairsGame(Base): num_pairs = 8 show_animation = True score_feedback_display = "large-top" + tutorial = None contact_email = "aml.tunetwins@gmail.com" random_seed = None @@ -121,6 +122,7 @@ def get_matching_pairs_trial(self, session): stop_audio_after=5, show_animation=self.show_animation, score_feedback_display=self.score_feedback_display, + tutorial=self.tutorial, ) trial = Trial(title="Tune twins", playback=playback, feedback_form=None, config={"show_continue_button": False}) return trial @@ -139,14 +141,14 @@ def calculate_intermediate_score(self, session, result): second_section = Section.objects.get(pk=second_card["id"]) second_card["filename"] = str(second_section.filename) if first_section.group == second_section.group: - if "seen" in second_card: + if "seen" in second_card and second_card["seen"]: score = 20 given_response = "match" else: score = 10 given_response = "lucky match" else: - if "seen" in second_card: + if "seen" in second_card and second_card["seen"]: score = -10 given_response = "misremembered" else: diff --git a/backend/experiment/rules/matching_pairs_2025.py b/backend/experiment/rules/matching_pairs_2025.py new file mode 100644 index 000000000..71f8327ea --- /dev/null +++ b/backend/experiment/rules/matching_pairs_2025.py @@ -0,0 +1,28 @@ +from django.utils.translation import gettext_lazy as _ + +from .matching_pairs import MatchingPairsGame + + +class MatchingPairs2025(MatchingPairsGame): + """This is the working version of the Matching Pairs game for the 2025 Tunetwins experiment. The difference between this version and the original Matching Pairs game is that this version has some additional tutorial messages. These messages are intended to help the user understand the game. The tutorial messages are as follows: + - no_match: This was not a match, so you get 0 points. Please try again to see if you can find a matching pair. + - lucky_match: You got a matching pair, but you didn't hear both cards before. This is considered a lucky match. You get 10 points. + - memory_match: You got a matching pair. You get 20 points. + - misremembered: You thought you found a matching pair, but you didn't. This is considered a misremembered pair. You lose 10 points. + + The tutorial messages are displayed to the user in an overlay on the game screen. + """ + + ID = "MATCHING_PAIRS_2025" + tutorial = { + "no_match": _( + "This was not a match, so you get 0 points. Please try again to see if you can find a matching pair." + ), + "lucky_match": _( + "You got a matching pair, but you didn't hear both cards before. This is considered a lucky match. You get 10 points." + ), + "memory_match": _("You got a matching pair. You get 20 points."), + "misremembered": _( + "You thought you found a matching pair, but you didn't. This is considered a misremembered pair. You lose 10 points." + ), + } diff --git a/backend/experiment/rules/matching_pairs_icmpc.py b/backend/experiment/rules/matching_pairs_icmpc.py index d61dd4a7a..5ecf54f77 100644 --- a/backend/experiment/rules/matching_pairs_icmpc.py +++ b/backend/experiment/rules/matching_pairs_icmpc.py @@ -1,12 +1,11 @@ from django.utils.translation import gettext_lazy as _ from .matching_pairs import MatchingPairsGame -from experiment.actions.form import TextQuestion class MatchingPairsICMPC(MatchingPairsGame): - ID = 'MATCHING_PAIRS_ICMPC' + ID = "MATCHING_PAIRS_ICMPC" def __init__(self): super().__init__() - self.question_series[0]['keys'].append('fame_name') + self.question_series[0]["keys"].append("fame_name") diff --git a/backend/experiment/rules/matching_pairs_lite.py b/backend/experiment/rules/matching_pairs_lite.py index aba5d5ab7..4dde14fcf 100644 --- a/backend/experiment/rules/matching_pairs_lite.py +++ b/backend/experiment/rules/matching_pairs_lite.py @@ -8,33 +8,28 @@ class MatchingPairsLite(MatchingPairsGame): - ID = 'MATCHING_PAIRS_LITE' + ID = "MATCHING_PAIRS_LITE" num_pairs = 8 show_animation = False - score_feedback_display = 'small-bottom-right' - contact_email = 'aml.tunetwins@gmail.com' + score_feedback_display = "small-bottom-right" + contact_email = "aml.tunetwins@gmail.com" def next_round(self, session): playlist = Playlist(session.block.playlists.all()) - info = Info('', - heading='Press start to enter the game', - button_label='Start') + info = Info("", heading="Press start to enter the game", button_label="Start") if session.get_rounds_passed() < 1: trial = self.get_matching_pairs_trial(session) return [playlist, info, trial] else: - return final_action_with_optional_button(session, final_text='End of the game', title='Score', button_text='Back to dashboard') + return final_action_with_optional_button( + session, final_text="End of the game", title="Score", button_text="Back to dashboard" + ) def select_sections(self, session): - pairs = list(session.playlist.section_set.order_by().distinct( - 'group').values_list('group', flat=True)) - selected_pairs = pairs[:self.num_pairs] - originals = session.playlist.section_set.filter( - group__in=selected_pairs, tag='Original' - ) - degradations = session.playlist.section_set.exclude(tag='Original').filter( - group__in=selected_pairs - ) + pairs = list(session.playlist.section_set.order_by().distinct("group").values_list("group", flat=True)) + selected_pairs = pairs[: self.num_pairs] + originals = session.playlist.section_set.filter(group__in=selected_pairs, tag="Original") + degradations = session.playlist.section_set.exclude(tag="Original").filter(group__in=selected_pairs) if degradations: sections = list(originals) + list(degradations) return self.shuffle_sections(sections) diff --git a/backend/experiment/rules/tests/test_matching_pairs.py b/backend/experiment/rules/tests/test_matching_pairs.py index 84729a9cc..d4aa025b5 100644 --- a/backend/experiment/rules/tests/test_matching_pairs.py +++ b/backend/experiment/rules/tests/test_matching_pairs.py @@ -11,7 +11,6 @@ class MatchingPairsTest(TestCase): - @classmethod def setUpTestData(cls): create_default_questions() @@ -32,16 +31,12 @@ def setUpTestData(cls): "default,TwinPeaks_0_E1,0.0,10.0,MatchingPairs/2ndDegradation/TwinPeaks_0_E1.mp3,2ndDegradation,86\n" "default,TwinPeaks_1_E1,0.0,10.0,MatchingPairs/Original/TwinPeaks_1_E1.mp3,Original,86\n" ) - cls.playlist = Playlist.objects.create(name='TestMatchingPairs') + cls.playlist = Playlist.objects.create(name="TestMatchingPairs") cls.playlist.csv = section_csv cls.playlist._update_sections() cls.participant = Participant.objects.create() - cls.block = Block.objects.create(rules='MATCHING_PAIRS', slug='mpairs', rounds=42) - cls.session = Session.objects.create( - block=cls.block, - participant=cls.participant, - playlist=cls.playlist - ) + cls.block = Block.objects.create(rules="MATCHING_PAIRS", slug="mpairs", rounds=42) + cls.session = Session.objects.create(block=cls.block, participant=cls.participant, playlist=cls.playlist) cls.rules = cls.session.block_rules() def test_next_round(self): @@ -59,8 +54,8 @@ def test_matching_pairs_trial(self): trial = self.rules.get_matching_pairs_trial(self.session) assert trial data = self.session.json_data - pairs = data.get('pairs') - degradations = data.get('degradations') + pairs = data.get("pairs") + degradations = data.get("degradations") # degradations cycle through list of two, list of one, empty list if i % 2 == 0: # there are 5 pairs available in total from the playlist; @@ -76,38 +71,40 @@ def test_matching_pairs_trial(self): assert len(degradations) == 0 def intermediate_score_request(self, data): - request_data = {'json_data': json.dumps( - data), **self.csrf_token, **self.session_data} - self.client.post( - '/result/intermediate_score/', request_data) - result = Result.objects.filter( - question_key='move').last() + request_data = {"json_data": json.dumps(data), **self.csrf_token, **self.session_data} + self.client.post("/result/intermediate_score/", request_data) + result = Result.objects.filter(question_key="move").last() return result def test_intermediate_score(self): - participant_info = json.loads(self.client.get('/participant/').content) - self.csrf_token = { - 'csrfmiddlewaretoken': participant_info.get('csrf_token')} - self.session.participant = Participant.objects.get( - pk=int(participant_info.get('id'))) + participant_info = json.loads(self.client.get("/participant/").content) + self.csrf_token = {"csrfmiddlewaretoken": participant_info.get("csrf_token")} + self.session.participant = Participant.objects.get(pk=int(participant_info.get("id"))) self.session.save() - self.session_data = {'session_id': self.session.id} + self.session_data = {"session_id": self.session.id} sections = self.playlist.section_set.all() - data = {'first_card': {'id': sections[0].id}, - 'second_card': {'id': sections[1].id}} + data = {"first_card": {"id": sections[0].id}, "second_card": {"id": sections[1].id}, "overlay_was_shown": False} result = self.intermediate_score_request(data) assert result.score == 10 - assert result.given_response == 'lucky match' - data['second_card'].update({'seen': True}) + assert result.given_response == "lucky match" + data["second_card"].update({"seen": True}) result = self.intermediate_score_request(data) assert result.score == 20 - assert result.given_response == 'match' - data['second_card'] = {'id': sections[3].id, 'seen': True} + assert result.given_response == "match" + data["second_card"] = {"id": sections[3].id, "seen": True} result = self.intermediate_score_request(data) assert result.score == -10 - assert result.given_response == 'misremembered' - data['first_card'].update({'seen': True}) - data['second_card'].pop('seen') + assert result.given_response == "misremembered" + data["first_card"].update({"seen": True}) + data["second_card"].pop("seen") result = self.intermediate_score_request(data) assert result.score == 0 - assert result.given_response == 'no match' + assert result.given_response == "no match" + + data["overlay_was_shown"] = True + result = self.intermediate_score_request(data) + assert result.json_data["overlay_was_shown"] is True + + data["overlay_was_shown"] = False + result = self.intermediate_score_request(data) + assert result.json_data["overlay_was_shown"] is False diff --git a/backend/experiment/static/collapsible_blocks.js b/backend/experiment/static/collapsible_blocks.js index 01918c92c..78a50c02d 100644 --- a/backend/experiment/static/collapsible_blocks.js +++ b/backend/experiment/static/collapsible_blocks.js @@ -48,6 +48,9 @@ function initializeBlockForm(blockForm) { }); blockForm.classList.add('collapsed'); + + // Initialize playlist input + django.jQuery('.django-select2').djangoSelect2(); } function toggleBlockVisibility(blockForm) { diff --git a/backend/mkdocs.yml b/backend/mkdocs.yml index 53d097591..c8ecd2f92 100644 --- a/backend/mkdocs.yml +++ b/backend/mkdocs.yml @@ -45,7 +45,7 @@ extra_css: - assets/css/style.css nav: - - MUSCLE: + - Getting started: - 1. Overview of the application: 'index.md' - 2. Start the application: '02_Start_the_application.md' - 3. The admin interface: '03_The_admin_interface.md' @@ -57,51 +57,55 @@ nav: - 9. Playback and player widgets: '09_Playback_and_player_widgets.md' - 10. Create a custom ruleset: '10_Create_custom_ruleset.md' - 11. Exporting result data: '11_Exporting_result_data.md' - - Frontend tools: + - Preview frontend components: - 'Storybook': 'https://amsterdam-music-lab.github.io/MUSCLE/storybook' - - Python code documentation: - - Experiment app: - - Experiment actions: 'experiment_actions.md' - - Experiment admin: 'experiment_admin.md' - - Experiment models: 'experiment_models.md' + - Ruleset documentation: + - Data models: + - Experiment: 'experiment_models.md' + - Image: 'image_models.md' + - Participant: 'participant_models.md' + - Question: 'question_models.md' + - Result: 'result_models.md' + - Section: 'section_models.md' + - Session: 'session_models.md' + - Theme: 'theme_models.md' + - Rules, actions & utils: - Experiment rules: 'experiment_rules.md' + - Participant utils: 'participant_utils.md' + - Question utils: 'question_utils.md' + - Experiment actions: 'experiment_actions.md' + - Technical documentation: + - Experiment app: + - Experiment admin: 'experiment_admin.md' - Experiment serializers: 'experiment_serializers.md' - Experiment utils: 'experiment_utils.md' - Experiment views: 'experiment_views.md' - Image app: - - Image admin: 'image_admin.md' - - Image models: 'image_models.md' + - Image admin: 'image_admin.md' - Image serializers: 'image_serializers.md' - Image validators: 'image_validators.md' - Participant app: - Participant admin: 'participant_admin.md' - - Participant models: 'participant_models.md' - - Participant utils: 'participant_utils.md' - Participant views: 'participant_views.md' - Question app: - - Question admin: 'question_admin.md' - - Question models: 'question_models.md' + - Question admin: 'question_admin.md' - Question utils: 'question_utils.md' - Question views: 'question_views.md' - Result app: - - Result admin: 'result_admin.md' - - Result models: 'result_models.md' + - Result admin: 'result_admin.md' - Result score: 'result_score.md' - Result utils: 'result_utils.md' - Result views: 'result_views.md' - Section app: - - Section admin: 'section_admin.md' - - Section models: 'section_models.md' + - Section admin: 'section_admin.md' - Section views: 'section_views.md' - Section utils: 'section_utils.md' - Section validators: 'section_validators.md' - Section views: 'section_views.md' - Session app: - - Session admin: 'session_admin.md' - - Session models: 'session_models.md' + - Session admin: 'session_admin.md' - Session views: 'session_views.md' - Theme app: - - Theme admin: 'theme_admin.md' - - Theme models: 'theme_models.md' + - Theme admin: 'theme_admin.md' - Theme views: 'theme_views.md' - About this document: 'this_doc.md' diff --git a/frontend/.pnp.cjs b/frontend/.pnp.cjs index 01a1706e8..75aebecd3 100755 --- a/frontend/.pnp.cjs +++ b/frontend/.pnp.cjs @@ -58,6 +58,8 @@ const RAW_RUNTIME_STATE = ["file-saver", "npm:2.0.5"],\ ["happy-dom", "npm:15.10.2"],\ ["history", "npm:5.3.0"],\ + ["msw", "virtual:74792effab46f58ba1849ed2d34bd613b5659d762979dea959819ade1937f6e3f71378429c07ee0ed12a2f529924b755998205bbacdbe5f616b371b307f52d67#npm:2.6.6"],\ + ["msw-storybook-addon", "virtual:74792effab46f58ba1849ed2d34bd613b5659d762979dea959819ade1937f6e3f71378429c07ee0ed12a2f529924b755998205bbacdbe5f616b371b307f52d67#npm:2.0.4"],\ ["prop-types", "npm:15.8.1"],\ ["qs", "npm:6.11.2"],\ ["react", "npm:18.3.1"],\ @@ -3469,6 +3471,37 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@bundled-es-modules/cookie", [\ + ["npm:2.0.1", {\ + "packageLocation": "../../../.yarn/berry/cache/@bundled-es-modules-cookie-npm-2.0.1-fb001bd2b9-10c0.zip/node_modules/@bundled-es-modules/cookie/",\ + "packageDependencies": [\ + ["@bundled-es-modules/cookie", "npm:2.0.1"],\ + ["cookie", "npm:0.7.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@bundled-es-modules/statuses", [\ + ["npm:1.0.1", {\ + "packageLocation": "../../../.yarn/berry/cache/@bundled-es-modules-statuses-npm-1.0.1-c6f8822c93-10c0.zip/node_modules/@bundled-es-modules/statuses/",\ + "packageDependencies": [\ + ["@bundled-es-modules/statuses", "npm:1.0.1"],\ + ["statuses", "npm:2.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@bundled-es-modules/tough-cookie", [\ + ["npm:0.1.6", {\ + "packageLocation": "../../../.yarn/berry/cache/@bundled-es-modules-tough-cookie-npm-0.1.6-aeb12ee45b-10c0.zip/node_modules/@bundled-es-modules/tough-cookie/",\ + "packageDependencies": [\ + ["@bundled-es-modules/tough-cookie", "npm:0.1.6"],\ + ["@types/tough-cookie", "npm:4.0.5"],\ + ["tough-cookie", "npm:4.1.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@chromatic-com/storybook", [\ ["npm:3.2.2", {\ "packageLocation": "../../../.yarn/berry/cache/@chromatic-com-storybook-npm-3.2.2-5a6acb70cc-10c0.zip/node_modules/@chromatic-com/storybook/",\ @@ -4167,6 +4200,75 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@inquirer/confirm", [\ + ["npm:5.0.2", {\ + "packageLocation": "../../../.yarn/berry/cache/@inquirer-confirm-npm-5.0.2-cb24775360-10c0.zip/node_modules/@inquirer/confirm/",\ + "packageDependencies": [\ + ["@inquirer/confirm", "npm:5.0.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:bc2343b0237acbbbfd07301eb563f1408bd9f9f07adc3ec2fd36933d63738b66cf90845fb98684e131aae35f492ab4dc29d3a5694caa272df5cd3ac024de8199#npm:5.0.2", {\ + "packageLocation": "./.yarn/__virtual__/@inquirer-confirm-virtual-72a55ee6b3/4/.yarn/berry/cache/@inquirer-confirm-npm-5.0.2-cb24775360-10c0.zip/node_modules/@inquirer/confirm/",\ + "packageDependencies": [\ + ["@inquirer/confirm", "virtual:bc2343b0237acbbbfd07301eb563f1408bd9f9f07adc3ec2fd36933d63738b66cf90845fb98684e131aae35f492ab4dc29d3a5694caa272df5cd3ac024de8199#npm:5.0.2"],\ + ["@inquirer/core", "npm:10.1.0"],\ + ["@inquirer/type", "virtual:572381c9a8388495067fc42be43ff6362e6c6b52126523c9bb77573f32b927ee671084b4aa78e82c9127bb29e716c6a5afcea14d7634218f50290dcc55b1ee63#npm:3.0.1"],\ + ["@types/node", null]\ + ],\ + "packagePeers": [\ + "@types/node"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@inquirer/core", [\ + ["npm:10.1.0", {\ + "packageLocation": "../../../.yarn/berry/cache/@inquirer-core-npm-10.1.0-572381c9a8-10c0.zip/node_modules/@inquirer/core/",\ + "packageDependencies": [\ + ["@inquirer/core", "npm:10.1.0"],\ + ["@inquirer/figures", "npm:1.0.8"],\ + ["@inquirer/type", "virtual:572381c9a8388495067fc42be43ff6362e6c6b52126523c9bb77573f32b927ee671084b4aa78e82c9127bb29e716c6a5afcea14d7634218f50290dcc55b1ee63#npm:3.0.1"],\ + ["ansi-escapes", "npm:4.3.2"],\ + ["cli-width", "npm:4.1.0"],\ + ["mute-stream", "npm:2.0.0"],\ + ["signal-exit", "npm:4.1.0"],\ + ["strip-ansi", "npm:6.0.1"],\ + ["wrap-ansi", "npm:6.2.0"],\ + ["yoctocolors-cjs", "npm:2.1.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@inquirer/figures", [\ + ["npm:1.0.8", {\ + "packageLocation": "../../../.yarn/berry/cache/@inquirer-figures-npm-1.0.8-b84d6f580a-10c0.zip/node_modules/@inquirer/figures/",\ + "packageDependencies": [\ + ["@inquirer/figures", "npm:1.0.8"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@inquirer/type", [\ + ["npm:3.0.1", {\ + "packageLocation": "../../../.yarn/berry/cache/@inquirer-type-npm-3.0.1-5402bd13af-10c0.zip/node_modules/@inquirer/type/",\ + "packageDependencies": [\ + ["@inquirer/type", "npm:3.0.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:572381c9a8388495067fc42be43ff6362e6c6b52126523c9bb77573f32b927ee671084b4aa78e82c9127bb29e716c6a5afcea14d7634218f50290dcc55b1ee63#npm:3.0.1", {\ + "packageLocation": "./.yarn/__virtual__/@inquirer-type-virtual-27f6485a34/4/.yarn/berry/cache/@inquirer-type-npm-3.0.1-5402bd13af-10c0.zip/node_modules/@inquirer/type/",\ + "packageDependencies": [\ + ["@inquirer/type", "virtual:572381c9a8388495067fc42be43ff6362e6c6b52126523c9bb77573f32b927ee671084b4aa78e82c9127bb29e716c6a5afcea14d7634218f50290dcc55b1ee63#npm:3.0.1"],\ + ["@types/node", null]\ + ],\ + "packagePeers": [\ + "@types/node"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@isaacs/cliui", [\ ["npm:8.0.2", {\ "packageLocation": "../../../.yarn/berry/cache/@isaacs-cliui-npm-8.0.2-f4364666d5-10c0.zip/node_modules/@isaacs/cliui/",\ @@ -4334,6 +4436,21 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@mswjs/interceptors", [\ + ["npm:0.37.3", {\ + "packageLocation": "../../../.yarn/berry/cache/@mswjs-interceptors-npm-0.37.3-bb1c9ca629-10c0.zip/node_modules/@mswjs/interceptors/",\ + "packageDependencies": [\ + ["@mswjs/interceptors", "npm:0.37.3"],\ + ["@open-draft/deferred-promise", "npm:2.2.0"],\ + ["@open-draft/logger", "npm:0.3.0"],\ + ["@open-draft/until", "npm:2.1.0"],\ + ["is-node-process", "npm:1.2.0"],\ + ["outvariant", "npm:1.4.3"],\ + ["strict-event-emitter", "npm:0.5.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@nicolo-ribaudo/eslint-scope-5-internals", [\ ["npm:5.1.1-v1", {\ "packageLocation": "../../../.yarn/berry/cache/@nicolo-ribaudo-eslint-scope-5-internals-npm-5.1.1-v1-87df86be4b-10c0.zip/node_modules/@nicolo-ribaudo/eslint-scope-5-internals/",\ @@ -4399,6 +4516,35 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@open-draft/deferred-promise", [\ + ["npm:2.2.0", {\ + "packageLocation": "../../../.yarn/berry/cache/@open-draft-deferred-promise-npm-2.2.0-adf396dc9f-10c0.zip/node_modules/@open-draft/deferred-promise/",\ + "packageDependencies": [\ + ["@open-draft/deferred-promise", "npm:2.2.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@open-draft/logger", [\ + ["npm:0.3.0", {\ + "packageLocation": "../../../.yarn/berry/cache/@open-draft-logger-npm-0.3.0-12b03e55aa-10c0.zip/node_modules/@open-draft/logger/",\ + "packageDependencies": [\ + ["@open-draft/logger", "npm:0.3.0"],\ + ["is-node-process", "npm:1.2.0"],\ + ["outvariant", "npm:1.4.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@open-draft/until", [\ + ["npm:2.1.0", {\ + "packageLocation": "../../../.yarn/berry/cache/@open-draft-until-npm-2.1.0-e27da33c52-10c0.zip/node_modules/@open-draft/until/",\ + "packageDependencies": [\ + ["@open-draft/until", "npm:2.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@pkgjs/parseargs", [\ ["npm:0.11.0", {\ "packageLocation": "../../../.yarn/berry/cache/@pkgjs-parseargs-npm-0.11.0-cd2a3fe948-10c0.zip/node_modules/@pkgjs/parseargs/",\ @@ -5736,6 +5882,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@types/cookie", [\ + ["npm:0.6.0", {\ + "packageLocation": "../../../.yarn/berry/cache/@types-cookie-npm-0.6.0-1f4c3f48f0-10c0.zip/node_modules/@types/cookie/",\ + "packageDependencies": [\ + ["@types/cookie", "npm:0.6.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/doctrine", [\ ["npm:0.0.9", {\ "packageLocation": "../../../.yarn/berry/cache/@types-doctrine-npm-0.0.9-ffe93045db-10c0.zip/node_modules/@types/doctrine/",\ @@ -5955,6 +6110,24 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@types/statuses", [\ + ["npm:2.0.5", {\ + "packageLocation": "../../../.yarn/berry/cache/@types-statuses-npm-2.0.5-f46121f53f-10c0.zip/node_modules/@types/statuses/",\ + "packageDependencies": [\ + ["@types/statuses", "npm:2.0.5"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@types/tough-cookie", [\ + ["npm:4.0.5", {\ + "packageLocation": "../../../.yarn/berry/cache/@types-tough-cookie-npm-4.0.5-8c5e2162e1-10c0.zip/node_modules/@types/tough-cookie/",\ + "packageDependencies": [\ + ["@types/tough-cookie", "npm:4.0.5"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/uuid", [\ ["npm:9.0.7", {\ "packageLocation": "../../../.yarn/berry/cache/@types-uuid-npm-9.0.7-c380bb8654-10c0.zip/node_modules/@types/uuid/",\ @@ -6633,6 +6806,8 @@ const RAW_RUNTIME_STATE = ["file-saver", "npm:2.0.5"],\ ["happy-dom", "npm:15.10.2"],\ ["history", "npm:5.3.0"],\ + ["msw", "virtual:74792effab46f58ba1849ed2d34bd613b5659d762979dea959819ade1937f6e3f71378429c07ee0ed12a2f529924b755998205bbacdbe5f616b371b307f52d67#npm:2.6.6"],\ + ["msw-storybook-addon", "virtual:74792effab46f58ba1849ed2d34bd613b5659d762979dea959819ade1937f6e3f71378429c07ee0ed12a2f529924b755998205bbacdbe5f616b371b307f52d67#npm:2.0.4"],\ ["prop-types", "npm:15.8.1"],\ ["qs", "npm:6.11.2"],\ ["react", "npm:18.3.1"],\ @@ -6655,6 +6830,16 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ + ["ansi-escapes", [\ + ["npm:4.3.2", {\ + "packageLocation": "../../../.yarn/berry/cache/ansi-escapes-npm-4.3.2-3ad173702f-10c0.zip/node_modules/ansi-escapes/",\ + "packageDependencies": [\ + ["ansi-escapes", "npm:4.3.2"],\ + ["type-fest", "npm:0.21.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["ansi-regex", [\ ["npm:5.0.1", {\ "packageLocation": "../../../.yarn/berry/cache/ansi-regex-npm-5.0.1-c963a48615-10c0.zip/node_modules/ansi-regex/",\ @@ -7371,6 +7556,27 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["cli-width", [\ + ["npm:4.1.0", {\ + "packageLocation": "../../../.yarn/berry/cache/cli-width-npm-4.1.0-c08b53be83-10c0.zip/node_modules/cli-width/",\ + "packageDependencies": [\ + ["cli-width", "npm:4.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["cliui", [\ + ["npm:8.0.1", {\ + "packageLocation": "../../../.yarn/berry/cache/cliui-npm-8.0.1-3b029092cf-10c0.zip/node_modules/cliui/",\ + "packageDependencies": [\ + ["cliui", "npm:8.0.1"],\ + ["string-width", "npm:4.2.3"],\ + ["strip-ansi", "npm:6.0.1"],\ + ["wrap-ansi", "npm:7.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["color-convert", [\ ["npm:1.9.3", {\ "packageLocation": "../../../.yarn/berry/cache/color-convert-npm-1.9.3-1fe690075e-10c0.zip/node_modules/color-convert/",\ @@ -7449,6 +7655,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["cookie", [\ + ["npm:0.7.2", {\ + "packageLocation": "../../../.yarn/berry/cache/cookie-npm-0.7.2-6ea9ee4231-10c0.zip/node_modules/cookie/",\ + "packageDependencies": [\ + ["cookie", "npm:0.7.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["core-js-compat", [\ ["npm:3.32.1", {\ "packageLocation": "../../../.yarn/berry/cache/core-js-compat-npm-3.32.1-d74aca93d6-10c0.zip/node_modules/core-js-compat/",\ @@ -8892,6 +9107,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["get-caller-file", [\ + ["npm:2.0.5", {\ + "packageLocation": "../../../.yarn/berry/cache/get-caller-file-npm-2.0.5-80e8a86305-10c0.zip/node_modules/get-caller-file/",\ + "packageDependencies": [\ + ["get-caller-file", "npm:2.0.5"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["get-intrinsic", [\ ["npm:1.2.1", {\ "packageLocation": "../../../.yarn/berry/cache/get-intrinsic-npm-1.2.1-ae857fd610-10c0.zip/node_modules/get-intrinsic/",\ @@ -9053,6 +9277,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["graphql", [\ + ["npm:16.9.0", {\ + "packageLocation": "../../../.yarn/berry/cache/graphql-npm-16.9.0-a36f71845f-10c0.zip/node_modules/graphql/",\ + "packageDependencies": [\ + ["graphql", "npm:16.9.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["happy-dom", [\ ["npm:15.10.2", {\ "packageLocation": "../../../.yarn/berry/cache/happy-dom-npm-15.10.2-3eadf189bc-10c0.zip/node_modules/happy-dom/",\ @@ -9138,6 +9371,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["headers-polyfill", [\ + ["npm:4.0.3", {\ + "packageLocation": "../../../.yarn/berry/cache/headers-polyfill-npm-4.0.3-65ca63b329-10c0.zip/node_modules/headers-polyfill/",\ + "packageDependencies": [\ + ["headers-polyfill", "npm:4.0.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["history", [\ ["npm:5.3.0", {\ "packageLocation": "../../../.yarn/berry/cache/history-npm-5.3.0-00136b6a63-10c0.zip/node_modules/history/",\ @@ -9491,6 +9733,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["is-node-process", [\ + ["npm:1.2.0", {\ + "packageLocation": "../../../.yarn/berry/cache/is-node-process-npm-1.2.0-34f2abe8e1-10c0.zip/node_modules/is-node-process/",\ + "packageDependencies": [\ + ["is-node-process", "npm:1.2.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["is-number", [\ ["npm:7.0.0", {\ "packageLocation": "../../../.yarn/berry/cache/is-number-npm-7.0.0-060086935c-10c0.zip/node_modules/is-number/",\ @@ -10338,6 +10589,78 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["msw", [\ + ["npm:2.6.6", {\ + "packageLocation": "./.yarn/unplugged/msw-virtual-bc2343b023/node_modules/msw/",\ + "packageDependencies": [\ + ["msw", "npm:2.6.6"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:74792effab46f58ba1849ed2d34bd613b5659d762979dea959819ade1937f6e3f71378429c07ee0ed12a2f529924b755998205bbacdbe5f616b371b307f52d67#npm:2.6.6", {\ + "packageLocation": "./.yarn/unplugged/msw-virtual-bc2343b023/node_modules/msw/",\ + "packageDependencies": [\ + ["msw", "virtual:74792effab46f58ba1849ed2d34bd613b5659d762979dea959819ade1937f6e3f71378429c07ee0ed12a2f529924b755998205bbacdbe5f616b371b307f52d67#npm:2.6.6"],\ + ["@bundled-es-modules/cookie", "npm:2.0.1"],\ + ["@bundled-es-modules/statuses", "npm:1.0.1"],\ + ["@bundled-es-modules/tough-cookie", "npm:0.1.6"],\ + ["@inquirer/confirm", "virtual:bc2343b0237acbbbfd07301eb563f1408bd9f9f07adc3ec2fd36933d63738b66cf90845fb98684e131aae35f492ab4dc29d3a5694caa272df5cd3ac024de8199#npm:5.0.2"],\ + ["@mswjs/interceptors", "npm:0.37.3"],\ + ["@open-draft/deferred-promise", "npm:2.2.0"],\ + ["@open-draft/until", "npm:2.1.0"],\ + ["@types/cookie", "npm:0.6.0"],\ + ["@types/statuses", "npm:2.0.5"],\ + ["@types/typescript", null],\ + ["chalk", "npm:4.1.2"],\ + ["graphql", "npm:16.9.0"],\ + ["headers-polyfill", "npm:4.0.3"],\ + ["is-node-process", "npm:1.2.0"],\ + ["outvariant", "npm:1.4.3"],\ + ["path-to-regexp", "npm:6.3.0"],\ + ["strict-event-emitter", "npm:0.5.1"],\ + ["type-fest", "npm:4.30.0"],\ + ["typescript", "patch:typescript@npm%3A5.3.3#optional!builtin::version=5.3.3&hash=e012d7"],\ + ["yargs", "npm:17.7.2"]\ + ],\ + "packagePeers": [\ + "@types/typescript",\ + "typescript"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["msw-storybook-addon", [\ + ["npm:2.0.4", {\ + "packageLocation": "../../../.yarn/berry/cache/msw-storybook-addon-npm-2.0.4-4c4d69bc12-10c0.zip/node_modules/msw-storybook-addon/",\ + "packageDependencies": [\ + ["msw-storybook-addon", "npm:2.0.4"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:74792effab46f58ba1849ed2d34bd613b5659d762979dea959819ade1937f6e3f71378429c07ee0ed12a2f529924b755998205bbacdbe5f616b371b307f52d67#npm:2.0.4", {\ + "packageLocation": "./.yarn/__virtual__/msw-storybook-addon-virtual-95b6ac0886/4/.yarn/berry/cache/msw-storybook-addon-npm-2.0.4-4c4d69bc12-10c0.zip/node_modules/msw-storybook-addon/",\ + "packageDependencies": [\ + ["msw-storybook-addon", "virtual:74792effab46f58ba1849ed2d34bd613b5659d762979dea959819ade1937f6e3f71378429c07ee0ed12a2f529924b755998205bbacdbe5f616b371b307f52d67#npm:2.0.4"],\ + ["@types/msw", null],\ + ["is-node-process", "npm:1.2.0"],\ + ["msw", "virtual:74792effab46f58ba1849ed2d34bd613b5659d762979dea959819ade1937f6e3f71378429c07ee0ed12a2f529924b755998205bbacdbe5f616b371b307f52d67#npm:2.6.6"]\ + ],\ + "packagePeers": [\ + "@types/msw",\ + "msw"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["mute-stream", [\ + ["npm:2.0.0", {\ + "packageLocation": "../../../.yarn/berry/cache/mute-stream-npm-2.0.0-45d3c1ef83-10c0.zip/node_modules/mute-stream/",\ + "packageDependencies": [\ + ["mute-stream", "npm:2.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["nanoid", [\ ["npm:3.3.8", {\ "packageLocation": "../../../.yarn/berry/cache/nanoid-npm-3.3.8-d22226208b-10c0.zip/node_modules/nanoid/",\ @@ -10565,6 +10888,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["outvariant", [\ + ["npm:1.4.3", {\ + "packageLocation": "../../../.yarn/berry/cache/outvariant-npm-1.4.3-192f951f81-10c0.zip/node_modules/outvariant/",\ + "packageDependencies": [\ + ["outvariant", "npm:1.4.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["p-limit", [\ ["npm:3.1.0", {\ "packageLocation": "../../../.yarn/berry/cache/p-limit-npm-3.1.0-05d2ede37f-10c0.zip/node_modules/p-limit/",\ @@ -10683,6 +11015,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["path-to-regexp", [\ + ["npm:6.3.0", {\ + "packageLocation": "../../../.yarn/berry/cache/path-to-regexp-npm-6.3.0-ee2cdde576-10c0.zip/node_modules/path-to-regexp/",\ + "packageDependencies": [\ + ["path-to-regexp", "npm:6.3.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["path-type", [\ ["npm:4.0.0", {\ "packageLocation": "../../../.yarn/berry/cache/path-type-npm-4.0.0-10d47fc86a-10c0.zip/node_modules/path-type/",\ @@ -10828,6 +11169,16 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["psl", [\ + ["npm:1.15.0", {\ + "packageLocation": "../../../.yarn/berry/cache/psl-npm-1.15.0-410584ca6b-10c0.zip/node_modules/psl/",\ + "packageDependencies": [\ + ["psl", "npm:1.15.0"],\ + ["punycode", "npm:2.3.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["punycode", [\ ["npm:2.3.0", {\ "packageLocation": "../../../.yarn/berry/cache/punycode-npm-2.3.0-df4bdce06b-10c0.zip/node_modules/punycode/",\ @@ -10835,6 +11186,13 @@ const RAW_RUNTIME_STATE = ["punycode", "npm:2.3.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:2.3.1", {\ + "packageLocation": "../../../.yarn/berry/cache/punycode-npm-2.3.1-97543c420d-10c0.zip/node_modules/punycode/",\ + "packageDependencies": [\ + ["punycode", "npm:2.3.1"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["qs", [\ @@ -10847,6 +11205,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["querystringify", [\ + ["npm:2.2.0", {\ + "packageLocation": "../../../.yarn/berry/cache/querystringify-npm-2.2.0-4e77c9f606-10c0.zip/node_modules/querystringify/",\ + "packageDependencies": [\ + ["querystringify", "npm:2.2.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["queue-microtask", [\ ["npm:1.2.3", {\ "packageLocation": "../../../.yarn/berry/cache/queue-microtask-npm-1.2.3-fcc98e4e2d-10c0.zip/node_modules/queue-microtask/",\ @@ -11355,6 +11722,24 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["require-directory", [\ + ["npm:2.1.1", {\ + "packageLocation": "../../../.yarn/berry/cache/require-directory-npm-2.1.1-8608aee50b-10c0.zip/node_modules/require-directory/",\ + "packageDependencies": [\ + ["require-directory", "npm:2.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["requires-port", [\ + ["npm:1.0.0", {\ + "packageLocation": "../../../.yarn/berry/cache/requires-port-npm-1.0.0-fd036b488a-10c0.zip/node_modules/requires-port/",\ + "packageDependencies": [\ + ["requires-port", "npm:1.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["resize-observer-polyfill", [\ ["npm:1.5.1", {\ "packageLocation": "../../../.yarn/berry/cache/resize-observer-polyfill-npm-1.5.1-603120e8a0-10c0.zip/node_modules/resize-observer-polyfill/",\ @@ -11720,6 +12105,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["statuses", [\ + ["npm:2.0.1", {\ + "packageLocation": "../../../.yarn/berry/cache/statuses-npm-2.0.1-81d2b97fee-10c0.zip/node_modules/statuses/",\ + "packageDependencies": [\ + ["statuses", "npm:2.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["std-env", [\ ["npm:3.7.0", {\ "packageLocation": "../../../.yarn/berry/cache/std-env-npm-3.7.0-5261c3c3c3-10c0.zip/node_modules/std-env/",\ @@ -11752,6 +12146,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["strict-event-emitter", [\ + ["npm:0.5.1", {\ + "packageLocation": "../../../.yarn/berry/cache/strict-event-emitter-npm-0.5.1-8414bf36b3-10c0.zip/node_modules/strict-event-emitter/",\ + "packageDependencies": [\ + ["strict-event-emitter", "npm:0.5.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["string-natural-compare", [\ ["npm:3.0.1", {\ "packageLocation": "../../../.yarn/berry/cache/string-natural-compare-npm-3.0.1-f6d0be6457-10c0.zip/node_modules/string-natural-compare/",\ @@ -12042,6 +12445,19 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["tough-cookie", [\ + ["npm:4.1.4", {\ + "packageLocation": "../../../.yarn/berry/cache/tough-cookie-npm-4.1.4-8293cc8bd5-10c0.zip/node_modules/tough-cookie/",\ + "packageDependencies": [\ + ["tough-cookie", "npm:4.1.4"],\ + ["psl", "npm:1.15.0"],\ + ["punycode", "npm:2.3.1"],\ + ["universalify", "npm:0.2.0"],\ + ["url-parse", "npm:1.5.10"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["ts-api-utils", [\ ["npm:1.4.0", {\ "packageLocation": "../../../.yarn/berry/cache/ts-api-utils-npm-1.4.0-b091964d6e-10c0.zip/node_modules/ts-api-utils/",\ @@ -12198,12 +12614,26 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["npm:0.21.3", {\ + "packageLocation": "../../../.yarn/berry/cache/type-fest-npm-0.21.3-5ff2a9c6fd-10c0.zip/node_modules/type-fest/",\ + "packageDependencies": [\ + ["type-fest", "npm:0.21.3"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:2.19.0", {\ "packageLocation": "../../../.yarn/berry/cache/type-fest-npm-2.19.0-918b953248-10c0.zip/node_modules/type-fest/",\ "packageDependencies": [\ ["type-fest", "npm:2.19.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:4.30.0", {\ + "packageLocation": "../../../.yarn/berry/cache/type-fest-npm-4.30.0-b4b33e2412-10c0.zip/node_modules/type-fest/",\ + "packageDependencies": [\ + ["type-fest", "npm:4.30.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["typed-array-buffer", [\ @@ -12338,6 +12768,13 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["universalify", [\ + ["npm:0.2.0", {\ + "packageLocation": "../../../.yarn/berry/cache/universalify-npm-0.2.0-9984e61c10-10c0.zip/node_modules/universalify/",\ + "packageDependencies": [\ + ["universalify", "npm:0.2.0"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:2.0.0", {\ "packageLocation": "../../../.yarn/berry/cache/universalify-npm-2.0.0-03b8b418a8-10c0.zip/node_modules/universalify/",\ "packageDependencies": [\ @@ -12415,6 +12852,17 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["url-parse", [\ + ["npm:1.5.10", {\ + "packageLocation": "../../../.yarn/berry/cache/url-parse-npm-1.5.10-64fa2bcd6d-10c0.zip/node_modules/url-parse/",\ + "packageDependencies": [\ + ["url-parse", "npm:1.5.10"],\ + ["querystringify", "npm:2.2.0"],\ + ["requires-port", "npm:1.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["use-isomorphic-layout-effect", [\ ["npm:1.1.2", {\ "packageLocation": "../../../.yarn/berry/cache/use-isomorphic-layout-effect-npm-1.1.2-65facd0a4b-10c0.zip/node_modules/use-isomorphic-layout-effect/",\ @@ -12815,6 +13263,16 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["wrap-ansi", [\ + ["npm:6.2.0", {\ + "packageLocation": "../../../.yarn/berry/cache/wrap-ansi-npm-6.2.0-439a7246d8-10c0.zip/node_modules/wrap-ansi/",\ + "packageDependencies": [\ + ["wrap-ansi", "npm:6.2.0"],\ + ["ansi-styles", "npm:4.3.0"],\ + ["string-width", "npm:4.2.3"],\ + ["strip-ansi", "npm:6.0.1"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:7.0.0", {\ "packageLocation": "../../../.yarn/berry/cache/wrap-ansi-npm-7.0.0-ad6e1a0554-10c0.zip/node_modules/wrap-ansi/",\ "packageDependencies": [\ @@ -12871,6 +13329,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["y18n", [\ + ["npm:5.0.8", {\ + "packageLocation": "../../../.yarn/berry/cache/y18n-npm-5.0.8-5f3a0a7e62-10c0.zip/node_modules/y18n/",\ + "packageDependencies": [\ + ["y18n", "npm:5.0.8"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["yallist", [\ ["npm:3.1.1", {\ "packageLocation": "../../../.yarn/berry/cache/yallist-npm-3.1.1-a568a556b4-10c0.zip/node_modules/yallist/",\ @@ -12896,6 +13363,31 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["yargs", [\ + ["npm:17.7.2", {\ + "packageLocation": "../../../.yarn/berry/cache/yargs-npm-17.7.2-80b62638e1-10c0.zip/node_modules/yargs/",\ + "packageDependencies": [\ + ["yargs", "npm:17.7.2"],\ + ["cliui", "npm:8.0.1"],\ + ["escalade", "npm:3.1.1"],\ + ["get-caller-file", "npm:2.0.5"],\ + ["require-directory", "npm:2.1.1"],\ + ["string-width", "npm:4.2.3"],\ + ["y18n", "npm:5.0.8"],\ + ["yargs-parser", "npm:21.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["yargs-parser", [\ + ["npm:21.1.1", {\ + "packageLocation": "../../../.yarn/berry/cache/yargs-parser-npm-21.1.1-8fdc003314-10c0.zip/node_modules/yargs-parser/",\ + "packageDependencies": [\ + ["yargs-parser", "npm:21.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["yocto-queue", [\ ["npm:0.1.0", {\ "packageLocation": "../../../.yarn/berry/cache/yocto-queue-npm-0.1.0-c6c9a7db29-10c0.zip/node_modules/yocto-queue/",\ @@ -12905,6 +13397,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["yoctocolors-cjs", [\ + ["npm:2.1.2", {\ + "packageLocation": "../../../.yarn/berry/cache/yoctocolors-cjs-npm-2.1.2-52d47e1a9b-10c0.zip/node_modules/yoctocolors-cjs/",\ + "packageDependencies": [\ + ["yoctocolors-cjs", "npm:2.1.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["zustand", [\ ["npm:4.4.7", {\ "packageLocation": "../../../.yarn/berry/cache/zustand-npm-4.4.7-974264f8cd-10c0.zip/node_modules/zustand/",\ diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js index b2a75c67f..c40ed9dc1 100644 --- a/frontend/.storybook/preview.js +++ b/frontend/.storybook/preview.js @@ -1,3 +1,4 @@ +import { initialize, mswLoader } from 'msw-storybook-addon' import "../public/vendor/bootstrap/bootstrap.min.css"; import "../src/index.scss"; import { initAudioListener } from "../src/util/audio"; @@ -7,6 +8,9 @@ import { initWebAudioListener } from "../src/util/webAudio"; initAudioListener(); initWebAudioListener(); +// Initialize MSW +initialize() + /** @type { import('@storybook/react').Preview } */ const preview = { parameters: { @@ -17,6 +21,8 @@ const preview = { }, }, }, + // Provide the MSW addon loader globally + loaders: [mswLoader], }; diff --git a/frontend/package.json b/frontend/package.json index ead449e27..403348ada 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -96,8 +96,15 @@ "eslint-plugin-storybook": "^0.11.1", "happy-dom": "^15.10.2", "history": "^5.3.0", + "msw": "^2.6.6", + "msw-storybook-addon": "^2.0.4", "prop-types": "15.8.1", "storybook": "^8.4.6", "vitest": "^2.1.4" + }, + "msw": { + "workerDirectory": [ + "public" + ] } } diff --git a/frontend/public/mockServiceWorker.js b/frontend/public/mockServiceWorker.js new file mode 100644 index 000000000..fead0b3ff --- /dev/null +++ b/frontend/public/mockServiceWorker.js @@ -0,0 +1,295 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.6.6' +const INTEGRITY_CHECKSUM = 'ca7800994cc8bfb5eb961e037c877074' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + headers.delete('accept', 'msw/passthrough') + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/frontend/src/components/Button/Button.tsx b/frontend/src/components/Button/Button.tsx index 9d5b5725d..c64e7dc82 100644 --- a/frontend/src/components/Button/Button.tsx +++ b/frontend/src/components/Button/Button.tsx @@ -10,9 +10,10 @@ interface ButtonProps { style?: React.CSSProperties; disabled?: boolean; value?: string; + clickOnce?: boolean; } -// Button is a button that can only be clicked one time +// Button is a button that can only be clicked one time by default const Button = ({ title, onClick, @@ -21,11 +22,17 @@ const Button = ({ style = {}, disabled = false, value = "", + clickOnce = true, }: ButtonProps) => { const clicked = useRef(false); // Only handle the first click - const clickOnce = () => { + const clickOnceGuard = () => { + + if (!clickOnce) { + return onClick(value); + } + if (disabled || clicked.current) { return; } @@ -38,9 +45,9 @@ const Button = ({ // Without the browser having registered any user interaction (e.g. click) const touchEvent = audioInitialized ? { - onTouchStart: (e) => { + onTouchStart: (e: React.TouchEvent) => { e.stopPropagation(); - clickOnce(); + clickOnceGuard(); return false; }, } @@ -50,13 +57,13 @@ const Button = ({ + +
+

{title}

+
+ +
+ {content} +
+ +
+
+ + + ); +}; + +export default Overlay; diff --git a/frontend/src/components/Playback/Playback.tsx b/frontend/src/components/Playback/Playback.tsx index 1a7bcd75a..d35276b82 100644 --- a/frontend/src/components/Playback/Playback.tsx +++ b/frontend/src/components/Playback/Playback.tsx @@ -238,11 +238,10 @@ const Playback = ({ return ( ); default: diff --git a/frontend/src/stories/MatchingPairs.stories.jsx b/frontend/src/stories/MatchingPairs.stories.tsx similarity index 87% rename from frontend/src/stories/MatchingPairs.stories.jsx rename to frontend/src/stories/MatchingPairs.stories.tsx index 37734fd59..4779bfbad 100644 --- a/frontend/src/stories/MatchingPairs.stories.jsx +++ b/frontend/src/stories/MatchingPairs.stories.tsx @@ -1,7 +1,11 @@ +import { http, HttpResponse } from 'msw' import useBoundStore from "@/util/stores"; import MatchingPairs, { SCORE_FEEDBACK_DISPLAY } from "../components/MatchingPairs/MatchingPairs"; + import audio from "./assets/audio.wav"; +import { API_BASE_URL } from '@/config'; +import { URLS } from '@/API'; const StoreDecorator = (Story) => { const setSession = useBoundStore((state) => state.setSession); @@ -34,7 +38,7 @@ export default { }; const getDefaultArgs = (overrides = {}) => ({ - playSection: () => {}, + playSection: () => { }, sections: [ { id: 1, @@ -94,9 +98,20 @@ const getDefaultArgs = (overrides = {}) => ({ }, ], playerIndex: 0, - stopAudio: () => {}, - submitResult: () => {}, - finishedPlaying: () => {}, + stopAudio: () => { }, + submitResult: () => { }, + finishedPlaying: () => { }, + ...overrides, +}); + +const getDefaultParams = (overrides = {}) => ({ + msw: { + handlers: [ + http.post(API_BASE_URL + URLS.result.intermediateScore, () => { + return HttpResponse.json({ score: 10 }); + }) + ], + }, ...overrides, }); @@ -105,13 +120,13 @@ export const Default = { ...getDefaultArgs(), }, decorators: [StoreDecorator], - parameters: { + parameters: getDefaultParams({ docs: { description: { component: "This story shows the component with the default props.", }, }, - }, + }), }; export const WithThreeColumns = { @@ -162,14 +177,14 @@ export const WithThreeColumns = { ], }), decorators: [StoreDecorator], - parameters: { + parameters: getDefaultParams({ docs: { description: { component: "This story shows the component with three columns. The component automatically adjusts the number of columns based on the number of sections. Six or less sections will result in three columns, more than six sections will result in four columns.", }, }, - }, + }), }; export const WithSmallBottomRightScoreFeedback = { @@ -178,13 +193,13 @@ export const WithSmallBottomRightScoreFeedback = { scoreFeedbackDisplay: SCORE_FEEDBACK_DISPLAY.SMALL_BOTTOM_RIGHT, }, decorators: [StoreDecorator], - parameters: { + parameters: getDefaultParams({ docs: { description: { component: "This story shows the component with the default props.", }, }, - }, + }), }; export const WithShowAnimation = { @@ -193,11 +208,11 @@ export const WithShowAnimation = { showAnimation: true, }, decorators: [StoreDecorator], - parameters: { + parameters: getDefaultParams({ docs: { description: { component: "This story shows the component with the default props.", }, }, - }, + }), }; diff --git a/frontend/src/stories/Overlay.stories.tsx b/frontend/src/stories/Overlay.stories.tsx new file mode 100644 index 000000000..66dcba716 --- /dev/null +++ b/frontend/src/stories/Overlay.stories.tsx @@ -0,0 +1,80 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Overlay from '@/components/Overlay/Overlay'; +import { useState } from 'react'; +import Button from '@/components/Button/Button'; + +const meta: Meta = { + title: 'Components/Overlay', + component: Overlay, + parameters: { + }, + decorators: [ + (Story, context) => { + const [isOpen, setIsOpen] = useState(false); + + return ( +
+
+ ) + } + ], +}; + +export default meta; +type Story = StoryObj; + +const defaultOverlayProps = { + isOpen: false, + title: 'Tutorial', + content: 'This is a tutorial.', + onClose: () => { }, +}; + +export const Default: Story = { + args: { + ...defaultOverlayProps, + }, +}; + +export const WithLongContent: Story = { + args: { + ...defaultOverlayProps, + content: ( +
+

Welcome to our app!

+

Here's how to get started:

+
    +
  1. First, create your profile
  2. +
  3. Then, explore our features
  4. +
  5. Finally, start creating!
  6. +
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +

+
+ ), + }, +}; + +export const CustomTitle: Story = { + args: { + ...defaultOverlayProps, + title: '🎉 Getting Started', + content: 'Welcome to our awesome app!', + }, +}; diff --git a/frontend/src/types/Playback.ts b/frontend/src/types/Playback.ts index 20b2cb049..be60974d5 100644 --- a/frontend/src/types/Playback.ts +++ b/frontend/src/types/Playback.ts @@ -35,4 +35,5 @@ export interface PlaybackArgs { stop_audio_after?: number; timeout_after_playback?: number; score_feedback_display?: string; + tutorial?: { [key: string]: string }; } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 083e0bf49..26c6bf0b2 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2130,6 +2130,34 @@ __metadata: languageName: node linkType: hard +"@bundled-es-modules/cookie@npm:^2.0.1": + version: 2.0.1 + resolution: "@bundled-es-modules/cookie@npm:2.0.1" + dependencies: + cookie: "npm:^0.7.2" + checksum: 10c0/dfac5e36127e827c5557b8577f17a8aa94c057baff6d38555917927b99da0ecf0b1357e7fedadc8853ecdbd4a8a7fa1f5e64111b2a656612f4a36376f5bdbe8d + languageName: node + linkType: hard + +"@bundled-es-modules/statuses@npm:^1.0.1": + version: 1.0.1 + resolution: "@bundled-es-modules/statuses@npm:1.0.1" + dependencies: + statuses: "npm:^2.0.1" + checksum: 10c0/c1a8ede3efa8da61ccda4b98e773582a9733edfbeeee569d4630785f8e018766202edb190a754a3ec7a7f6bd738e857829affc2fdb676b6dab4db1bb44e62785 + languageName: node + linkType: hard + +"@bundled-es-modules/tough-cookie@npm:^0.1.6": + version: 0.1.6 + resolution: "@bundled-es-modules/tough-cookie@npm:0.1.6" + dependencies: + "@types/tough-cookie": "npm:^4.0.5" + tough-cookie: "npm:^4.1.4" + checksum: 10c0/28bcac878bff6b34719ba3aa8341e9924772ee55de5487680ebe784981ec9fccb70ed5d46f563e2404855a04de606f9e56aa4202842d4f5835bc04a4fe820571 + languageName: node + linkType: hard + "@chromatic-com/storybook@npm:^3.2.2": version: 3.2.2 resolution: "@chromatic-com/storybook@npm:3.2.2" @@ -2695,6 +2723,51 @@ __metadata: languageName: node linkType: hard +"@inquirer/confirm@npm:^5.0.0": + version: 5.0.2 + resolution: "@inquirer/confirm@npm:5.0.2" + dependencies: + "@inquirer/core": "npm:^10.1.0" + "@inquirer/type": "npm:^3.0.1" + peerDependencies: + "@types/node": ">=18" + checksum: 10c0/c121cfb0557b42dd6570b54dce707a048d85f328481d5230d21fede195902012ede06887aa478875cc83afa064c2e30953eb2cab0744f832195867b418865115 + languageName: node + linkType: hard + +"@inquirer/core@npm:^10.1.0": + version: 10.1.0 + resolution: "@inquirer/core@npm:10.1.0" + dependencies: + "@inquirer/figures": "npm:^1.0.8" + "@inquirer/type": "npm:^3.0.1" + ansi-escapes: "npm:^4.3.2" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^2.0.0" + signal-exit: "npm:^4.1.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10c0/ffd187edb210426c3e25ed564f7aa8844468c28dd2ba3c53dbe28d3359b519cdfae987b31bf927c1dd2e9f70a914fdefe319abe4c5f384e5e08410d11e0a7ce2 + languageName: node + linkType: hard + +"@inquirer/figures@npm:^1.0.8": + version: 1.0.8 + resolution: "@inquirer/figures@npm:1.0.8" + checksum: 10c0/34d287ff1fd16476c58bbd5b169db315f8319b5ffb09f81a1bb9aabd4165114e7406b1f418d021fd9cd48923008446e3eec274bb818f378ea132a0450bbc91d4 + languageName: node + linkType: hard + +"@inquirer/type@npm:^3.0.1": + version: 3.0.1 + resolution: "@inquirer/type@npm:3.0.1" + peerDependencies: + "@types/node": ">=18" + checksum: 10c0/c8612362d382114a318dbb523de7b1f54dc6bc6d3016c6eaf299b6a32486b92b0dfb1b4cfc6fe9d99496d15fbb721873a1bd66819f796c8bb09853a3b808812d + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -2821,6 +2894,20 @@ __metadata: languageName: node linkType: hard +"@mswjs/interceptors@npm:^0.37.0": + version: 0.37.3 + resolution: "@mswjs/interceptors@npm:0.37.3" + dependencies: + "@open-draft/deferred-promise": "npm:^2.2.0" + "@open-draft/logger": "npm:^0.3.0" + "@open-draft/until": "npm:^2.0.0" + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.3" + strict-event-emitter: "npm:^0.5.1" + checksum: 10c0/5a8d9ab7c491d14dff996f23bda0fa7b7059f68a1d4981b804b6114f1c0c0490bc35860df135ed36da1720984323878b1a9d13dbc80936bbfa67b3d3c3476f6c + languageName: node + linkType: hard + "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": version: 5.1.1-v1 resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" @@ -2879,6 +2966,30 @@ __metadata: languageName: node linkType: hard +"@open-draft/deferred-promise@npm:^2.2.0": + version: 2.2.0 + resolution: "@open-draft/deferred-promise@npm:2.2.0" + checksum: 10c0/eafc1b1d0fc8edb5e1c753c5e0f3293410b40dde2f92688211a54806d4136887051f39b98c1950370be258483deac9dfd17cf8b96557553765198ef2547e4549 + languageName: node + linkType: hard + +"@open-draft/logger@npm:^0.3.0": + version: 0.3.0 + resolution: "@open-draft/logger@npm:0.3.0" + dependencies: + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.0" + checksum: 10c0/90010647b22e9693c16258f4f9adb034824d1771d3baa313057b9a37797f571181005bc50415a934eaf7c891d90ff71dcd7a9d5048b0b6bb438f31bef2c7c5c1 + languageName: node + linkType: hard + +"@open-draft/until@npm:^2.0.0, @open-draft/until@npm:^2.1.0": + version: 2.1.0 + resolution: "@open-draft/until@npm:2.1.0" + checksum: 10c0/61d3f99718dd86bb393fee2d7a785f961dcaf12f2055f0c693b27f4d0cd5f7a03d498a6d9289773b117590d794a43cd129366fd8e99222e4832f67b1653d54cf + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -3651,6 +3762,13 @@ __metadata: languageName: node linkType: hard +"@types/cookie@npm:^0.6.0": + version: 0.6.0 + resolution: "@types/cookie@npm:0.6.0" + checksum: 10c0/5b326bd0188120fb32c0be086b141b1481fec9941b76ad537f9110e10d61ee2636beac145463319c71e4be67a17e85b81ca9e13ceb6e3bb63b93d16824d6c149 + languageName: node + linkType: hard + "@types/doctrine@npm:^0.0.9": version: 0.0.9 resolution: "@types/doctrine@npm:0.0.9" @@ -3837,6 +3955,20 @@ __metadata: languageName: node linkType: hard +"@types/statuses@npm:^2.0.4": + version: 2.0.5 + resolution: "@types/statuses@npm:2.0.5" + checksum: 10c0/4dacec0b29483a44be902a022a11a22b339de7a6e7b2059daa4f7add10cb6dbcc28d02d2a416fe9687e48d335906bf983065391836d4e7c847e55ddef4de8fad + languageName: node + linkType: hard + +"@types/tough-cookie@npm:^4.0.5": + version: 4.0.5 + resolution: "@types/tough-cookie@npm:4.0.5" + checksum: 10c0/68c6921721a3dcb40451543db2174a145ef915bc8bcbe7ad4e59194a0238e776e782b896c7a59f4b93ac6acefca9161fccb31d1ce3b3445cb6faa467297fb473 + languageName: node + linkType: hard + "@types/uuid@npm:^9.0.1": version: 9.0.7 resolution: "@types/uuid@npm:9.0.7" @@ -4328,6 +4460,8 @@ __metadata: file-saver: "npm:^2.0.5" happy-dom: "npm:^15.10.2" history: "npm:^5.3.0" + msw: "npm:^2.6.6" + msw-storybook-addon: "npm:^2.0.4" prop-types: "npm:15.8.1" qs: "npm:^6.10.3" react: "npm:18.3.1" @@ -4349,6 +4483,15 @@ __metadata: languageName: unknown linkType: soft +"ansi-escapes@npm:^4.3.2": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + "ansi-regex@npm:^5.0.1": version: 5.0.1 resolution: "ansi-regex@npm:5.0.1" @@ -4874,7 +5017,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.1.0": +"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -4957,6 +5100,24 @@ __metadata: languageName: node linkType: hard +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -5026,6 +5187,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.7.2": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 + languageName: node + linkType: hard + "core-js-compat@npm:^3.31.0": version: 3.32.1 resolution: "core-js-compat@npm:3.32.1" @@ -6252,6 +6420,13 @@ __metadata: languageName: node linkType: hard +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1": version: 1.2.1 resolution: "get-intrinsic@npm:1.2.1" @@ -6406,6 +6581,13 @@ __metadata: languageName: node linkType: hard +"graphql@npm:^16.8.1": + version: 16.9.0 + resolution: "graphql@npm:16.9.0" + checksum: 10c0/a8850f077ff767377237d1f8b1da2ec70aeb7623cdf1dfc9e1c7ae93accc0c8149c85abe68923be9871a2934b1bce5a2496f846d4d56e1cfb03eaaa7ddba9b6a + languageName: node + linkType: hard + "happy-dom@npm:^15.10.2": version: 15.10.2 resolution: "happy-dom@npm:15.10.2" @@ -6479,6 +6661,13 @@ __metadata: languageName: node linkType: hard +"headers-polyfill@npm:^4.0.2": + version: 4.0.3 + resolution: "headers-polyfill@npm:4.0.3" + checksum: 10c0/53e85b2c6385f8d411945fb890c5369f1469ce8aa32a6e8d28196df38568148de640c81cf88cbc7c67767103dd9acba48f4f891982da63178fc6e34560022afe + languageName: node + linkType: hard + "history@npm:^5.3.0": version: 5.3.0 resolution: "history@npm:5.3.0" @@ -6781,6 +6970,13 @@ __metadata: languageName: node linkType: hard +"is-node-process@npm:^1.0.1, is-node-process@npm:^1.2.0": + version: 1.2.0 + resolution: "is-node-process@npm:1.2.0" + checksum: 10c0/5b24fda6776d00e42431d7bcd86bce81cb0b6cabeb944142fe7b077a54ada2e155066ad06dbe790abdb397884bdc3151e04a9707b8cd185099efbc79780573ed + languageName: node + linkType: hard + "is-number-object@npm:^1.0.4": version: 1.0.7 resolution: "is-number-object@npm:1.0.7" @@ -7562,6 +7758,57 @@ __metadata: languageName: node linkType: hard +"msw-storybook-addon@npm:^2.0.4": + version: 2.0.4 + resolution: "msw-storybook-addon@npm:2.0.4" + dependencies: + is-node-process: "npm:^1.0.1" + peerDependencies: + msw: ^2.0.0 + checksum: 10c0/91cd85b0d6e3bdf8cc4f519b29da644d4df324a3fc6753a98b472488986aee888dfe4fc6a216a41057b01d10899e1e3a16ad253f63d7ae06f3dc3560fe5e561d + languageName: node + linkType: hard + +"msw@npm:^2.6.6": + version: 2.6.6 + resolution: "msw@npm:2.6.6" + dependencies: + "@bundled-es-modules/cookie": "npm:^2.0.1" + "@bundled-es-modules/statuses": "npm:^1.0.1" + "@bundled-es-modules/tough-cookie": "npm:^0.1.6" + "@inquirer/confirm": "npm:^5.0.0" + "@mswjs/interceptors": "npm:^0.37.0" + "@open-draft/deferred-promise": "npm:^2.2.0" + "@open-draft/until": "npm:^2.1.0" + "@types/cookie": "npm:^0.6.0" + "@types/statuses": "npm:^2.0.4" + chalk: "npm:^4.1.2" + graphql: "npm:^16.8.1" + headers-polyfill: "npm:^4.0.2" + is-node-process: "npm:^1.2.0" + outvariant: "npm:^1.4.3" + path-to-regexp: "npm:^6.3.0" + strict-event-emitter: "npm:^0.5.1" + type-fest: "npm:^4.26.1" + yargs: "npm:^17.7.2" + peerDependencies: + typescript: ">= 4.8.x" + peerDependenciesMeta: + typescript: + optional: true + bin: + msw: cli/index.js + checksum: 10c0/d7f0e0ea6512cfddda5b7695a0c6c6a0ed43b36b62f25925beee8e12daec12a5ef369d00e0976f9dd76d3af779741fda11a3eae812da11a044b03992e697010c + languageName: node + linkType: hard + +"mute-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "mute-stream@npm:2.0.0" + checksum: 10c0/2cf48a2087175c60c8dcdbc619908b49c07f7adcfc37d29236b0c5c612d6204f789104c98cc44d38acab7b3c96f4a3ec2cfdc4934d0738d876dbefa2a12c69f4 + languageName: node + linkType: hard + "nanoid@npm:^3.3.7": version: 3.3.8 resolution: "nanoid@npm:3.3.8" @@ -7766,6 +8013,13 @@ __metadata: languageName: node linkType: hard +"outvariant@npm:^1.4.0, outvariant@npm:^1.4.3": + version: 1.4.3 + resolution: "outvariant@npm:1.4.3" + checksum: 10c0/5976ca7740349cb8c71bd3382e2a762b1aeca6f33dc984d9d896acdf3c61f78c3afcf1bfe9cc633a7b3c4b295ec94d292048f83ea2b2594fae4496656eba992c + languageName: node + linkType: hard + "p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" @@ -7869,6 +8123,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^6.3.0": + version: 6.3.0 + resolution: "path-to-regexp@npm:6.3.0" + checksum: 10c0/73b67f4638b41cde56254e6354e46ae3a2ebc08279583f6af3d96fe4664fc75788f74ed0d18ca44fa4a98491b69434f9eee73b97bb5314bd1b5adb700f5c18d6 + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -7991,6 +8252,15 @@ __metadata: languageName: node linkType: hard +"psl@npm:^1.1.33": + version: 1.15.0 + resolution: "psl@npm:1.15.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 10c0/d8d45a99e4ca62ca12ac3c373e63d80d2368d38892daa40cfddaa1eb908be98cd549ac059783ef3a56cfd96d57ae8e2fd9ae53d1378d90d42bc661ff924e102a + languageName: node + linkType: hard + "punycode@npm:^2.1.0": version: 2.3.0 resolution: "punycode@npm:2.3.0" @@ -7998,6 +8268,13 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^2.1.1, punycode@npm:^2.3.1": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + "qs@npm:^6.10.3": version: 6.11.2 resolution: "qs@npm:6.11.2" @@ -8007,6 +8284,13 @@ __metadata: languageName: node linkType: hard +"querystringify@npm:^2.1.1": + version: 2.2.0 + resolution: "querystringify@npm:2.2.0" + checksum: 10c0/3258bc3dbdf322ff2663619afe5947c7926a6ef5fb78ad7d384602974c467fadfc8272af44f5eb8cddd0d011aae8fabf3a929a8eee4b86edcc0a21e6bd10f9aa + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -8342,6 +8626,20 @@ __metadata: languageName: node linkType: hard +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"requires-port@npm:^1.0.0": + version: 1.0.0 + resolution: "requires-port@npm:1.0.0" + checksum: 10c0/b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267 + languageName: node + linkType: hard + "resize-observer-polyfill@npm:^1.4.2": version: 1.5.1 resolution: "resize-observer-polyfill@npm:1.5.1" @@ -8678,7 +8976,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1": +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 @@ -8764,6 +9062,13 @@ __metadata: languageName: node linkType: hard +"statuses@npm:^2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + "std-env@npm:^3.7.0": version: 3.7.0 resolution: "std-env@npm:3.7.0" @@ -8789,6 +9094,13 @@ __metadata: languageName: node linkType: hard +"strict-event-emitter@npm:^0.5.1": + version: 0.5.1 + resolution: "strict-event-emitter@npm:0.5.1" + checksum: 10c0/f5228a6e6b6393c57f52f62e673cfe3be3294b35d6f7842fc24b172ae0a6e6c209fa83241d0e433fc267c503bc2f4ffdbe41a9990ff8ffd5ac425ec0489417f7 + languageName: node + linkType: hard + "string-natural-compare@npm:^3.0.1": version: 3.0.1 resolution: "string-natural-compare@npm:3.0.1" @@ -8796,7 +9108,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -9046,6 +9358,18 @@ __metadata: languageName: node linkType: hard +"tough-cookie@npm:^4.1.4": + version: 4.1.4 + resolution: "tough-cookie@npm:4.1.4" + dependencies: + psl: "npm:^1.1.33" + punycode: "npm:^2.1.1" + universalify: "npm:^0.2.0" + url-parse: "npm:^1.5.3" + checksum: 10c0/aca7ff96054f367d53d1e813e62ceb7dd2eda25d7752058a74d64b7266fd07be75908f3753a32ccf866a2f997604b414cfb1916d6e7f69bc64d9d9939b0d6c45 + languageName: node + linkType: hard + "ts-api-utils@npm:^1.3.0": version: 1.4.0 resolution: "ts-api-utils@npm:1.4.0" @@ -9147,6 +9471,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + "type-fest@npm:^2.19.0": version: 2.19.0 resolution: "type-fest@npm:2.19.0" @@ -9154,6 +9485,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.26.1": + version: 4.30.0 + resolution: "type-fest@npm:4.30.0" + checksum: 10c0/9441fbbc971f92a53d7dfdb0db3f9c71a5a33ac3e021ca605cba8ad0b5c0a1e191cc778b4980c534b098ccb4e3322809100baf763be125510c993c9b8361f60e + languageName: node + linkType: hard + "typed-array-buffer@npm:^1.0.0": version: 1.0.0 resolution: "typed-array-buffer@npm:1.0.0" @@ -9282,6 +9620,13 @@ __metadata: languageName: node linkType: hard +"universalify@npm:^0.2.0": + version: 0.2.0 + resolution: "universalify@npm:0.2.0" + checksum: 10c0/cedbe4d4ca3967edf24c0800cfc161c5a15e240dac28e3ce575c689abc11f2c81ccc6532c8752af3b40f9120fb5e454abecd359e164f4f6aa44c29cd37e194fe + languageName: node + linkType: hard + "universalify@npm:^2.0.0": version: 2.0.0 resolution: "universalify@npm:2.0.0" @@ -9338,6 +9683,16 @@ __metadata: languageName: node linkType: hard +"url-parse@npm:^1.5.3": + version: 1.5.10 + resolution: "url-parse@npm:1.5.10" + dependencies: + querystringify: "npm:^2.1.1" + requires-port: "npm:^1.0.0" + checksum: 10c0/bd5aa9389f896974beb851c112f63b466505a04b4807cea2e5a3b7092f6fbb75316f0491ea84e44f66fed55f1b440df5195d7e3a8203f64fcefa19d182f5be87 + languageName: node + linkType: hard + "use-isomorphic-layout-effect@npm:^1.1.2": version: 1.1.2 resolution: "use-isomorphic-layout-effect@npm:1.1.2" @@ -9637,7 +9992,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -9648,6 +10003,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c + languageName: node + linkType: hard + "wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0" @@ -9681,6 +10047,13 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + "yallist@npm:^3.0.2": version: 3.1.1 resolution: "yallist@npm:3.1.1" @@ -9702,6 +10075,28 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0" @@ -9709,6 +10104,13 @@ __metadata: languageName: node linkType: hard +"yoctocolors-cjs@npm:^2.1.2": + version: 2.1.2 + resolution: "yoctocolors-cjs@npm:2.1.2" + checksum: 10c0/a0e36eb88fea2c7981eab22d1ba45e15d8d268626e6c4143305e2c1628fa17ebfaa40cd306161a8ce04c0a60ee0262058eab12567493d5eb1409780853454c6f + languageName: node + linkType: hard + "zustand@npm:^4.4.7": version: 4.4.7 resolution: "zustand@npm:4.4.7"