From c5d465df97e1639c2fa3b4f807a0bb2bc07ae4d1 Mon Sep 17 00:00:00 2001 From: Arthur Jamet <60505370+Arthi-chaud@users.noreply.github.com> Date: Sat, 17 Jun 2023 07:01:23 +0100 Subject: [PATCH] Front: Pretty and Lint (#225) --- .github/workflows/CI.yml | 4 + front/.eslintrc.json | 24 + front/.prettierignore | 5 + front/.prettierrc.json | 13 + front/API.ts | 246 +++--- front/App.tsx | 15 +- front/Navigation.tsx | 139 +-- front/Theme.tsx | 149 ++-- front/app.config.ts | 78 +- front/app.json | 72 +- front/babel.config.js | 9 +- front/components/ArtistCard.tsx | 15 +- front/components/BigActionButton.tsx | 58 +- front/components/Card.tsx | 46 +- front/components/CardGridCustom.tsx | 5 +- front/components/CompetenciesTable.tsx | 38 +- front/components/GenreCard.tsx | 19 +- front/components/GtkUI/Element.tsx | 15 +- front/components/GtkUI/ElementList.tsx | 30 +- front/components/GtkUI/ElementTypes.tsx | 31 +- front/components/GtkUI/RawElement.tsx | 69 +- front/components/HistoryCard.tsx | 14 +- front/components/IconButton.tsx | 14 +- front/components/Loading.stories.tsx | 6 +- front/components/Loading.tsx | 20 +- front/components/PartitionView.tsx | 91 +- .../PartitionVisualizer.tsx | 13 +- .../PartitionVisualizer/SlideView.tsx | 54 +- front/components/ProgressBar.tsx | 40 +- front/components/SearchBar.tsx | 77 +- front/components/SearchResult.tsx | 186 ++-- front/components/SongCard.tsx | 13 +- front/components/SongCardGrid.tsx | 34 +- front/components/TextButton.tsx | 36 +- front/components/Translate.tsx | 16 +- front/components/VirtualPiano/Octave.tsx | 62 +- .../components/VirtualPiano/PianoKeyComp.tsx | 35 +- .../components/VirtualPiano/VirtualPiano.tsx | 42 +- front/components/forms/changeEmailForm.tsx | 157 ++-- front/components/forms/changePasswordForm.tsx | 221 +++-- front/components/forms/signinform.tsx | 183 ++-- front/components/forms/signupform.tsx | 82 +- .../components/navigators/TabRowNavigator.tsx | 108 ++- front/components/utils/api.tsx | 24 +- front/eas.json | 2 +- front/hooks/colorScheme.ts | 8 +- front/hooks/userSettings.ts | 17 +- front/i18n/LanguageGate.ts | 12 +- front/i18n/Translations.ts | 819 +++++++++--------- front/i18n/i18n.ts | 48 +- front/models/Album.ts | 4 +- front/models/Artist.ts | 4 +- front/models/AuthToken.ts | 2 +- front/models/Chapter.ts | 8 +- front/models/Genre.ts | 4 +- front/models/Lesson.ts | 8 +- front/models/LessonHistory.ts | 4 +- front/models/LocalSettings.ts | 18 +- front/models/Model.ts | 2 +- front/models/Piano.ts | 138 +-- front/models/SearchHistory.ts | 12 +- front/models/Skill.ts | 8 +- front/models/Song.ts | 8 +- front/models/SongDetails.ts | 28 +- front/models/SongHistory.ts | 2 +- front/models/User.ts | 8 +- front/models/UserData.ts | 10 +- front/models/UserSettings.ts | 20 +- front/package.json | 210 ++--- front/state/LanguageSlice.ts | 9 +- front/state/SettingsSlice.ts | 14 +- front/state/Store.ts | 51 +- front/state/UserSlice.ts | 26 +- front/tsconfig.json | 204 ++--- front/views/ArtistDetailsView.tsx | 64 +- front/views/AuthenticationView.test.tsx | 14 +- front/views/AuthenticationView.tsx | 80 +- front/views/HomeView.test.tsx | 12 +- front/views/HomeView.tsx | 306 ++++--- front/views/PlayView.tsx | 312 ++++--- front/views/ProfileView.tsx | 193 +++-- front/views/ScoreView.tsx | 112 +-- front/views/SearchView.tsx | 44 +- front/views/SettingsView.test.tsx | 14 +- front/views/SongLobbyView.tsx | 88 +- front/views/StartPageView.tsx | 128 ++- front/views/settings/GuestToUserView.tsx | 28 +- front/views/settings/NotificationView.tsx | 52 +- front/views/settings/PreferencesView.tsx | 85 +- front/views/settings/PrivacyView.tsx | 40 +- front/views/settings/SettingsProfileView.tsx | 129 ++- front/views/settings/SettingsView.tsx | 228 ++--- front/webpack.config.js | 24 +- front/yarn.lock | 447 +++++++++- 94 files changed, 3627 insertions(+), 3089 deletions(-) create mode 100644 front/.eslintrc.json create mode 100644 front/.prettierignore create mode 100644 front/.prettierrc.json diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index bb0285d..48dc008 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -45,6 +45,10 @@ jobs: - name: Type Check run: yarn tsc + - name: Check Prettier + run: yarn pretty:check . + - name: Run Linter + run: yarn lint - name: 🏗 Setup Expo uses: expo/expo-github-action@v7 diff --git a/front/.eslintrc.json b/front/.eslintrc.json new file mode 100644 index 0000000..c49a8ef --- /dev/null +++ b/front/.eslintrc.json @@ -0,0 +1,24 @@ +{ + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { "project": ["./tsconfig.json"] }, + "plugins": ["react", "@typescript-eslint"], + "ignorePatterns": [ + "node_modules/", + "webpack.config.js", + "babel.config.js", + "*.test.*", + "app.config.ts" + ], + "rules": { + "react/react-in-jsx-scope": "off", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-empty-function": "off" + } +} diff --git a/front/.prettierignore b/front/.prettierignore new file mode 100644 index 0000000..f379c08 --- /dev/null +++ b/front/.prettierignore @@ -0,0 +1,5 @@ +.expo +.expo-shared/ +dist/ +.vscode/ +.storybook/ \ No newline at end of file diff --git a/front/.prettierrc.json b/front/.prettierrc.json new file mode 100644 index 0000000..fa5cfde --- /dev/null +++ b/front/.prettierrc.json @@ -0,0 +1,13 @@ +{ + "printWidth": 100, + "tabWidth": 4, + "useTabs": true, + "semi": true, + "singleQuote": true, + "quoteProps": "consistent", + "jsxSingleQuote": false, + "trailingComma": "es5", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "always" +} diff --git a/front/API.ts b/front/API.ts index 26c2575..4d2d3b7 100644 --- a/front/API.ts +++ b/front/API.ts @@ -1,21 +1,21 @@ -import Artist from "./models/Artist"; -import Album from "./models/Album"; -import AuthToken from "./models/AuthToken"; -import Chapter from "./models/Chapter"; -import Lesson from "./models/Lesson"; -import Genre from "./models/Genre"; -import LessonHistory from "./models/LessonHistory"; -import Song from "./models/Song"; -import SongHistory from "./models/SongHistory"; -import User from "./models/User"; -import Constants from "expo-constants"; -import store from "./state/Store"; -import { Platform } from "react-native"; -import { en } from "./i18n/Translations"; -import { useQuery, QueryClient } from "react-query"; -import UserSettings from "./models/UserSettings"; -import { PartialDeep } from "type-fest"; -import SearchHistory from "./models/SearchHistory"; +import Artist from './models/Artist'; +import Album from './models/Album'; +import AuthToken from './models/AuthToken'; +import Chapter from './models/Chapter'; +import Lesson from './models/Lesson'; +import Genre from './models/Genre'; +import LessonHistory from './models/LessonHistory'; +import Song from './models/Song'; +import SongHistory from './models/SongHistory'; +import User from './models/User'; +import Constants from 'expo-constants'; +import store from './state/Store'; +import { Platform } from 'react-native'; +import { en } from './i18n/Translations'; +import { QueryClient } from 'react-query'; +import UserSettings from './models/UserSettings'; +import { PartialDeep } from 'type-fest'; +import SearchHistory from './models/SearchHistory'; type AuthenticationInput = { username: string; password: string }; type RegistrationInput = AuthenticationInput & { email: string }; @@ -24,8 +24,8 @@ export type AccessToken = string; type FetchParams = { route: string; - body?: Object; - method?: "GET" | "POST" | "DELETE" | "PATCH" | "PUT"; + body?: object; + method?: 'GET' | 'POST' | 'DELETE' | 'PATCH' | 'PUT'; // If true, No JSON parsing is done, the raw response's content is returned raw?: true; }; @@ -40,7 +40,7 @@ export class APIError extends Error { public status: number, // Set the message to the correct error this is a placeholder // when the error is only used internally (middleman) - public userMessage: keyof typeof en = "unknownError" + public userMessage: keyof typeof en = 'unknownError' ) { super(message); } @@ -48,24 +48,22 @@ export class APIError extends Error { // we will need the same thing for the scorometer API url const baseAPIUrl = - process.env.NODE_ENV != "development" && Platform.OS === "web" - ? "/api" + process.env.NODE_ENV != 'development' && Platform.OS === 'web' + ? '/api' : Constants.manifest?.extra?.apiUrl; export default class API { public static async fetch(params: FetchParams) { const jwtToken = store.getState().user.accessToken; const header = { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }; const response = await fetch(`${baseAPIUrl}${params.route}`, { - headers: - (jwtToken && { ...header, Authorization: `Bearer ${jwtToken}` }) || - header, + headers: (jwtToken && { ...header, Authorization: `Bearer ${jwtToken}` }) || header, body: JSON.stringify(params.body), - method: params.method ?? "GET", + method: params.method ?? 'GET', }).catch(() => { - throw new Error("Error while fetching API: " + baseAPIUrl); + throw new Error('Error while fetching API: ' + baseAPIUrl); }); if (params.raw) { return response.arrayBuffer(); @@ -74,15 +72,11 @@ export default class API { try { const jsonResponse = body.length != 0 ? JSON.parse(body) : {}; if (!response.ok) { - throw new APIError( - jsonResponse ?? response.statusText, - response.status - ); + throw new APIError(jsonResponse ?? response.statusText, response.status); } return jsonResponse; } catch (e) { - if (e instanceof SyntaxError) - throw new Error("Error while parsing Server's response"); + if (e instanceof SyntaxError) throw new Error("Error while parsing Server's response"); throw e; } } @@ -91,16 +85,16 @@ export default class API { authenticationInput: AuthenticationInput ): Promise { return API.fetch({ - route: "/auth/login", + route: '/auth/login', body: authenticationInput, - method: "POST", + method: 'POST', }) .then((responseBody) => responseBody.access_token) .catch((e) => { if (!(e instanceof APIError)) throw e; if (e.status == 401) - throw new APIError("invalidCredentials", 401, "invalidCredentials"); + throw new APIError('invalidCredentials', 401, 'invalidCredentials'); throw e; }); } @@ -109,13 +103,11 @@ export default class API { * @param registrationInput the credentials to create a new profile * @returns A Promise. On success, will be resolved into an instance of the API wrapper */ - public static async createAccount( - registrationInput: RegistrationInput - ): Promise { + public static async createAccount(registrationInput: RegistrationInput): Promise { await API.fetch({ - route: "/auth/register", + route: '/auth/register', body: registrationInput, - method: "POST", + method: 'POST', }); // In the Future we should move autheticate out of this function // and maybe create a new function to create and login in one go @@ -126,22 +118,19 @@ export default class API { } public static async createAndGetGuestAccount(): Promise { - let response = await API.fetch({ - route: "/auth/guest", - method: "POST", + const response = await API.fetch({ + route: '/auth/guest', + method: 'POST', }); - if (!response.access_token) - throw new APIError("No access token", response.status); + if (!response.access_token) throw new APIError('No access token', response.status); return response.access_token; } - public static async transformGuestToUser( - registrationInput: RegistrationInput - ): Promise { + public static async transformGuestToUser(registrationInput: RegistrationInput): Promise { await API.fetch({ - route: "/auth/me", + route: '/auth/me', body: registrationInput, - method: "PUT", + method: 'PUT', }); } @@ -149,8 +138,8 @@ export default class API { * Retrieve information of the currently authentified user */ public static async getUserInfo(): Promise { - let user = await API.fetch({ - route: "/auth/me", + const user = await API.fetch({ + route: '/auth/me', }); // this a dummy settings object, we will need to fetch the real one from the API @@ -163,16 +152,15 @@ export default class API { data: { gamesPlayed: user.partyPlayed as number, xp: 0, - createdAt: new Date("2023-04-09T00:00:00.000Z"), - avatar: - "https://imgs.search.brave.com/RnQpFhmAFvuQsN_xTw7V-CN61VeHDBg2tkEXnKRYHAE/rs:fit:768:512:1/g:ce/aHR0cHM6Ly96b29h/c3Ryby5jb20vd3At/Y29udGVudC91cGxv/YWRzLzIwMjEvMDIv/Q2FzdG9yLTc2OHg1/MTIuanBn", + createdAt: new Date('2023-04-09T00:00:00.000Z'), + avatar: 'https://imgs.search.brave.com/RnQpFhmAFvuQsN_xTw7V-CN61VeHDBg2tkEXnKRYHAE/rs:fit:768:512:1/g:ce/aHR0cHM6Ly96b29h/c3Ryby5jb20vd3At/Y29udGVudC91cGxv/YWRzLzIwMjEvMDIv/Q2FzdG9yLTc2OHg1/MTIuanBn', }, } as User; } public static async getUserSettings(): Promise { const settings = await API.fetch({ - route: "/auth/me/settings", + route: '/auth/me/settings', }); return { @@ -189,9 +177,7 @@ export default class API { }; } - public static async updateUserSettings( - settings: PartialDeep - ): Promise { + public static async updateUserSettings(settings: PartialDeep): Promise { const dto = { pushNotification: settings.notifications?.pushNotif, emailNotification: settings.notifications?.emailNotif, @@ -203,8 +189,8 @@ export default class API { showActivity: settings.showActivity, }; return API.fetch({ - method: "PATCH", - route: "/auth/me/settings", + method: 'PATCH', + route: '/auth/me/settings', body: dto, }); } @@ -225,27 +211,29 @@ export default class API { */ public static async authWithGoogle(): Promise { //TODO - return "11111"; + return '11111'; } public static async getAllSongs(): Promise { - let songs = await API.fetch({ - route: "/song", + const songs = await API.fetch({ + route: '/song', }); // this is a dummy illustration, we will need to fetch the real one from the API return songs.data.map( + // To be fixed with #168 + // eslint-disable-next-line @typescript-eslint/no-explicit-any (song: any) => - ({ - id: song.id as number, - name: song.name as string, - artistId: song.artistId as number, - albumId: song.albumId as number, - genreId: song.genreId as number, - details: song.difficulties, - cover: `${baseAPIUrl}/song/${song.id}/illustration`, - metrics: {}, - } as Song) + ({ + id: song.id as number, + name: song.name as string, + artistId: song.artistId as number, + albumId: song.albumId as number, + genreId: song.genreId as number, + details: song.difficulties, + cover: `${baseAPIUrl}/song/${song.id}/illustration`, + metrics: {}, + } as Song) ); } @@ -254,7 +242,7 @@ export default class API { * @param songId the id to find the song */ public static async getSong(songId: number): Promise { - let song = await API.fetch({ + const song = await API.fetch({ route: `/song/${songId}`, }); @@ -326,8 +314,8 @@ export default class API { end: 100 * value, songId: songId, name: `Chapter ${value}`, - type: "chorus", - key_aspect: "rhythm", + type: 'chorus', + key_aspect: 'rhythm', difficulty: value, id: value * 10, })); @@ -369,23 +357,26 @@ export default class API { * Search Album by name * @param query the string used to find the album */ - public static async searchAlbum(query?: string): Promise { + public static async searchAlbum( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + query?: string + ): Promise { return [ { id: 1, - name: "Super Trooper", + name: 'Super Trooper', }, { id: 2, - name: "Kingdom Heart 365/2 OST", + name: 'Kingdom Heart 365/2 OST', }, { id: 3, - name: "The Legend Of Zelda Ocarina Of Time OST", + name: 'The Legend Of Zelda Ocarina Of Time OST', }, { id: 4, - name: "Random Access Memories", + name: 'Random Access Memories', }, ] as Album[]; } @@ -405,10 +396,10 @@ export default class API { */ public static async getLesson(lessonId: number): Promise { return { - title: "Song", - description: "A song", + title: 'Song', + description: 'A song', requiredLevel: 1, - mainSkill: "lead-head-change", + mainSkill: 'lead-head-change', id: lessonId, }; } @@ -419,24 +410,26 @@ export default class API { * @param take how much do we take to return * @returns Returns an array of history entries (temporary type any) */ - public static async getSearchHistory( - skip?: number, - take?: number - ): Promise { + public static async getSearchHistory(skip?: number, take?: number): Promise { return ( - await API.fetch({ - route: `/history/search?skip=${skip ?? 0}&take=${take ?? 5}`, - method: "GET", - }) - ).map((e: any) => { - return { - id: e.id, - query: e.query, - type: e.type, - userId: e.userId, - timestamp: new Date(e.searchDate), - } as SearchHistory; - }); + ( + await API.fetch({ + route: `/history/search?skip=${skip ?? 0}&take=${take ?? 5}`, + method: 'GET', + }) + ) + // To be fixed with #168 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .map((e: any) => { + return { + id: e.id, + query: e.query, + type: e.type, + userId: e.userId, + timestamp: new Date(e.searchDate), + } as SearchHistory; + }) + ); } /** @@ -446,14 +439,10 @@ export default class API { * @param timestamp the date it's been issued * @returns nothing */ - public static async createSearchHistoryEntry( - query: string, - type: string, - timestamp: number - ): Promise { + public static async createSearchHistoryEntry(query: string, type: string): Promise { return await API.fetch({ route: `/history/search`, - method: "POST", + method: 'POST', body: { query: query, type: type, @@ -467,7 +456,7 @@ export default class API { */ public static async getSongSuggestions(): Promise { const queryClient = new QueryClient(); - return await queryClient.fetchQuery(["API", "allsongs"], API.getAllSongs); + return await queryClient.fetchQuery(['API', 'allsongs'], API.getAllSongs); } /** @@ -476,7 +465,7 @@ export default class API { */ public static async getUserPlayHistory(): Promise { return this.fetch({ - route: "/history", + route: '/history', }); } @@ -484,9 +473,7 @@ export default class API { * Retrieve a lesson's history * @param lessonId the id to find the lesson */ - public static async getLessonHistory( - lessonId: number - ): Promise { + public static async getLessonHistory(lessonId: number): Promise { return [ { lessonId, @@ -497,50 +484,51 @@ export default class API { /** * Retrieve a partition images - * @param songId the id of the song + * @param _songId the id of the song * This API may be merged with the fetch song in the future */ public static async getPartitionRessources( + // eslint-disable-next-line @typescript-eslint/no-unused-vars songId: number ): Promise<[string, number, number][]> { return [ [ - "https://media.discordapp.net/attachments/717080637038788731/1067469560426545222/vivaldi_split_1.png", + 'https://media.discordapp.net/attachments/717080637038788731/1067469560426545222/vivaldi_split_1.png', 1868, 400, ], [ - "https://media.discordapp.net/attachments/717080637038788731/1067469560900505660/vivaldi_split_2.png", + 'https://media.discordapp.net/attachments/717080637038788731/1067469560900505660/vivaldi_split_2.png', 1868, 400, ], [ - "https://media.discordapp.net/attachments/717080637038788731/1067469561261203506/vivaldi_split_3.png", + 'https://media.discordapp.net/attachments/717080637038788731/1067469561261203506/vivaldi_split_3.png', 1868, 400, ], [ - "https://media.discordapp.net/attachments/717080637038788731/1067469561546424381/vivaldi_split_4.png", + 'https://media.discordapp.net/attachments/717080637038788731/1067469561546424381/vivaldi_split_4.png', 1868, 400, ], [ - "https://media.discordapp.net/attachments/717080637038788731/1067469562058133564/vivaldi_split_5.png", + 'https://media.discordapp.net/attachments/717080637038788731/1067469562058133564/vivaldi_split_5.png', 1868, 400, ], [ - "https://media.discordapp.net/attachments/717080637038788731/1067469562347528202/vivaldi_split_6.png", + 'https://media.discordapp.net/attachments/717080637038788731/1067469562347528202/vivaldi_split_6.png', 1868, 400, ], [ - "https://media.discordapp.net/attachments/717080637038788731/1067469562792136815/vivaldi_split_7.png", + 'https://media.discordapp.net/attachments/717080637038788731/1067469562792136815/vivaldi_split_7.png', 1868, 400, ], [ - "https://media.discordapp.net/attachments/717080637038788731/1067469563073142804/vivaldi_split_8.png", + 'https://media.discordapp.net/attachments/717080637038788731/1067469563073142804/vivaldi_split_8.png', 1868, 400, ], @@ -549,8 +537,8 @@ export default class API { public static async updateUserEmail(newEmail: string): Promise { const rep = await API.fetch({ - route: "/auth/me", - method: "PUT", + route: '/auth/me', + method: 'PUT', body: { email: newEmail, }, @@ -567,8 +555,8 @@ export default class API { newPassword: string ): Promise { const rep = await API.fetch({ - route: "/auth/me", - method: "PUT", + route: '/auth/me', + method: 'PUT', body: { oldPassword: oldPassword, password: newPassword, diff --git a/front/App.tsx b/front/App.tsx index e3b7ea4..e66dade 100644 --- a/front/App.tsx +++ b/front/App.tsx @@ -5,21 +5,20 @@ import store, { persistor } from './state/Store'; import { Router } from './Navigation'; import './i18n/i18n'; import * as SplashScreen from 'expo-splash-screen'; -import { PersistGate } from "redux-persist/integration/react"; -import LanguageGate from "./i18n/LanguageGate"; +import { PersistGate } from 'redux-persist/integration/react'; +import LanguageGate from './i18n/LanguageGate'; import ThemeProvider, { ColorSchemeProvider } from './Theme'; import 'react-native-url-polyfill/auto'; const queryClient = new QueryClient({ defaultOptions: { - queries: { - refetchOnWindowFocus: false, - }, + queries: { + refetchOnWindowFocus: false, + }, }, - }); +}); export default function App() { - SplashScreen.preventAutoHideAsync(); setTimeout(SplashScreen.hideAsync, 500); @@ -30,7 +29,7 @@ export default function App() { - + diff --git a/front/Navigation.tsx b/front/Navigation.tsx index ce5622e..35ba673 100644 --- a/front/Navigation.tsx +++ b/front/Navigation.tsx @@ -1,5 +1,10 @@ +/* eslint-disable @typescript-eslint/ban-types */ import { NativeStackScreenProps, createNativeStackNavigator } from '@react-navigation/native-stack'; -import { NavigationProp, ParamListBase, useNavigation as navigationHook } from "@react-navigation/native"; +import { + NavigationProp, + ParamListBase, + useNavigation as navigationHook, +} from '@react-navigation/native'; import React, { useEffect } from 'react'; import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/native'; import { RootState, useSelector } from './state/Store'; @@ -23,34 +28,39 @@ import { Button, Center, VStack } from 'native-base'; import { unsetAccessToken } from './state/UserSlice'; import TextButton from './components/TextButton'; +const protectedRoutes = () => + ({ + Home: { component: HomeView, options: { title: translate('welcome'), headerLeft: null } }, + Play: { component: PlayView, options: { title: translate('play') } }, + Settings: { component: SetttingsNavigator, options: { title: 'Settings' } }, + Song: { component: SongLobbyView, options: { title: translate('play') } }, + Artist: { component: ArtistDetailsView, options: { title: translate('artistFilter') } }, + Score: { component: ScoreView, options: { title: translate('score'), headerLeft: null } }, + Search: { component: SearchView, options: { title: translate('search') } }, + User: { component: ProfileView, options: { title: translate('user') } }, + } as const); -const protectedRoutes = () => ({ - Home: { component: HomeView, options: { title: translate('welcome'), headerLeft: null } }, - Play: { component: PlayView, options: { title: translate('play') } }, - Settings: { component: SetttingsNavigator, options: { title: 'Settings' } }, - Song: { component: SongLobbyView, options: { title: translate('play') } }, - Artist: { component: ArtistDetailsView, options: { title: translate('artistFilter') } }, - Score: { component: ScoreView, options: { title: translate('score'), headerLeft: null } }, - Search: { component: SearchView, options: { title: translate('search') } }, - User: { component: ProfileView, options: { title: translate('user') } }, -}) as const; - -const publicRoutes = () => ({ - Start: { component: StartPageView, options: { title: "Chromacase", headerShown: false } }, - Login: { component: AuthenticationView, options: { title: translate('signInBtn') } }, - Oops: { component: ProfileErrorView, options: { title: 'Oops', headerShown: false } }, -}) as const; +const publicRoutes = () => + ({ + Start: { component: StartPageView, options: { title: 'Chromacase', headerShown: false } }, + Login: { component: AuthenticationView, options: { title: translate('signInBtn') } }, + Oops: { component: ProfileErrorView, options: { title: 'Oops', headerShown: false } }, + } as const); +// eslint-disable-next-line @typescript-eslint/no-explicit-any type Route = { - component: (arg: RouteProps) => JSX.Element | (() => JSX.Element), - options: any -} + component: (arg: RouteProps) => JSX.Element | (() => JSX.Element); + options: object; +}; -type OmitOrUndefined = T extends undefined ? T : Omit +type OmitOrUndefined = T extends undefined ? T : Omit; type RouteParams> = { - [RouteName in keyof Routes]: OmitOrUndefined[0], keyof NativeStackScreenProps<{}>>; -} + [RouteName in keyof Routes]: OmitOrUndefined< + Parameters[0], + keyof NativeStackScreenProps<{}> + >; +}; type PrivateRoutesParams = RouteParams>; type PublicRoutesParams = RouteParams>; @@ -58,34 +68,47 @@ type AppRouteParams = PrivateRoutesParams & PublicRoutesParams; const Stack = createNativeStackNavigator(); -const RouteToScreen = (component: Route['component']) => (props: NativeStackScreenProps) => - <> - {component({ ...props.route.params, route: props.route } as Parameters['component']>[0])} - +const RouteToScreen = + (component: Route['component']) => + // eslint-disable-next-line react/display-name + (props: NativeStackScreenProps) => + ( + <> + {component({ ...props.route.params, route: props.route } as Parameters< + Route['component'] + >[0])} + + ); -const routesToScreens = (routes: Partial>) => Object.entries(routes) - .map(([name, route], routeIndex) => ( +const routesToScreens = (routes: Partial>) => + Object.entries(routes).map(([name, route], routeIndex) => ( - )) + )); -const ProfileErrorView = (props: { onTryAgain: () => any }) => { +const ProfileErrorView = (props: { onTryAgain: () => void }) => { const dispatch = useDispatch(); - return
- - - - dispatch(unsetAccessToken())} - colorScheme="error" variant='outline' - translate={{ translationKey: 'signOutBtn' }} - /> - -
-} + return ( +
+ + + + dispatch(unsetAccessToken())} + colorScheme="error" + variant="outline" + translate={{ translationKey: 'signOutBtn' }} + /> + +
+ ); +}; export const Router = () => { const dispatch = useDispatch(); @@ -108,25 +131,27 @@ export const Router = () => { }, [accessToken]); return ( - + - { userProfile.isError && accessToken && !userProfile.isLoading - ? userProfile.refetch()}/>)}/> - : userProfile.isLoading && !userProfile.data ? - - : routesToScreens(userProfile.isSuccess && accessToken - ? protectedRoutes() - : publicRoutes()) - } + {userProfile.isError && accessToken && !userProfile.isLoading ? ( + ( + userProfile.refetch()} /> + ))} + /> + ) : userProfile.isLoading && !userProfile.data ? ( + + ) : ( + routesToScreens( + userProfile.isSuccess && accessToken ? protectedRoutes() : publicRoutes() + ) + )} ); -} +}; export type RouteProps = T & Pick, 'route'>; - -export const useNavigation = () => navigationHook>(); \ No newline at end of file +export const useNavigation = () => navigationHook>(); diff --git a/front/Theme.tsx b/front/Theme.tsx index 0e528b9..6d7fc57 100644 --- a/front/Theme.tsx +++ b/front/Theme.tsx @@ -1,81 +1,84 @@ -import { NativeBaseProvider, extendTheme, useColorMode, useTheme } from 'native-base'; +import { NativeBaseProvider, extendTheme, useColorMode } from 'native-base'; import useColorScheme from './hooks/colorScheme'; import { useEffect } from 'react'; const ThemeProvider = ({ children }: { children: JSX.Element }) => { const colorScheme = useColorScheme(); - return ({ - rounded: 'full', - }) - } - } - } - })}> - { children } - ; -} - -const ColorSchemeProvider = (props: { children: any }) => { + return ( + ({ + rounded: 'full', + }), + }, + }, + }, + })} + > + {children} + + ); +}; +const ColorSchemeProvider = (props: { children: JSX.Element }) => { const colorScheme = useColorScheme(); const colorMode = useColorMode(); @@ -83,7 +86,7 @@ const ColorSchemeProvider = (props: { children: any }) => { colorMode.setColorMode(colorScheme); }, [colorScheme]); return props.children; -} +}; export default ThemeProvider; -export { ColorSchemeProvider }; \ No newline at end of file +export { ColorSchemeProvider }; diff --git a/front/app.config.ts b/front/app.config.ts index 134c0bd..e9cd5e5 100644 --- a/front/app.config.ts +++ b/front/app.config.ts @@ -1,41 +1,39 @@ module.exports = { - "name": "Chromacase", - "slug": "Chromacase", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icon.png", - "userInterfaceStyle": "light", - "splash": { - "image": "./assets/splashLogo.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "updates": { - "fallbackToCacheTimeout": 0 - }, - "assetBundlePatterns": [ - "**/*" - ], - "ios": { - "supportsTablet": true - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#FFFFFF", - "package": "com.chromacase.chromacase", - "versionCode": 1 - }, - "package": "build.apk" - }, - "web": { - "favicon": "./assets/favicon.png" - }, - "extra": { - apiUrl: process.env.API_URL, - scoroUrl: process.env.SCORO_URL, - "eas": { - "projectId": "dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2" - } - } -} + name: 'Chromacase', + slug: 'Chromacase', + version: '1.0.0', + orientation: 'portrait', + icon: './assets/icon.png', + userInterfaceStyle: 'light', + splash: { + image: './assets/splashLogo.png', + resizeMode: 'contain', + backgroundColor: '#ffffff', + }, + updates: { + fallbackToCacheTimeout: 0, + }, + assetBundlePatterns: ['**/*'], + ios: { + supportsTablet: true, + }, + android: { + adaptiveIcon: { + foregroundImage: './assets/adaptive-icon.png', + backgroundColor: '#FFFFFF', + package: 'com.chromacase.chromacase', + versionCode: 1, + }, + package: 'build.apk', + }, + web: { + favicon: './assets/favicon.png', + }, + extra: { + apiUrl: process.env.API_URL, + scoroUrl: process.env.SCORO_URL, + eas: { + projectId: 'dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2', + }, + }, +}; diff --git a/front/app.json b/front/app.json index a766f17..a1939e1 100644 --- a/front/app.json +++ b/front/app.json @@ -1,39 +1,37 @@ { - "expo": { - "name": "Chromacase", - "slug": "Chromacase", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icon.png", - "userInterfaceStyle": "light", - "splash": { - "image": "./assets/splashLogo.png", - "resizeMode": "cover", - "backgroundColor": "#ffffff" - }, - "updates": { - "fallbackToCacheTimeout": 0 - }, - "assetBundlePatterns": [ - "**/*" - ], - "ios": { - "supportsTablet": true - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#FFFFFF" - }, - "package": "build.apk" - }, - "web": { - "favicon": "./assets/favicon.png" - }, - "extra": { - "eas": { - "projectId": "dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2" - } - } - } + "expo": { + "name": "Chromacase", + "slug": "Chromacase", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/icon.png", + "userInterfaceStyle": "light", + "splash": { + "image": "./assets/splashLogo.png", + "resizeMode": "cover", + "backgroundColor": "#ffffff" + }, + "updates": { + "fallbackToCacheTimeout": 0 + }, + "assetBundlePatterns": ["**/*"], + "ios": { + "supportsTablet": true + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#FFFFFF" + }, + "package": "build.apk" + }, + "web": { + "favicon": "./assets/favicon.png" + }, + "extra": { + "eas": { + "projectId": "dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2" + } + } + } } diff --git a/front/babel.config.js b/front/babel.config.js index 4da58ea..2825b93 100644 --- a/front/babel.config.js +++ b/front/babel.config.js @@ -1,14 +1,11 @@ module.exports = function (api) { api.cache(true); return { - presets: ["babel-preset-expo"], - plugins: [ - "@babel/plugin-proposal-export-namespace-from", - "react-native-reanimated/plugin", - ], + presets: ['babel-preset-expo'], + plugins: ['@babel/plugin-proposal-export-namespace-from', 'react-native-reanimated/plugin'], env: { production: { - plugins: ["react-native-paper/babel"], + plugins: ['react-native-paper/babel'], }, }, }; diff --git a/front/components/ArtistCard.tsx b/front/components/ArtistCard.tsx index 46e188d..83f021b 100644 --- a/front/components/ArtistCard.tsx +++ b/front/components/ArtistCard.tsx @@ -1,7 +1,6 @@ -import React from "react"; -import Card, { CardBorderRadius } from "./Card"; -import { VStack, Text, Image } from "native-base"; -import API from "../API"; +import React from 'react'; +import Card, { CardBorderRadius } from './Card'; +import { VStack, Text, Image } from 'native-base'; type ArtistCardProps = { image: string; @@ -11,7 +10,7 @@ type ArtistCardProps = { }; const ArtistCard = (props: ArtistCardProps) => { - const { image, name, id } = props; + const { image, name } = props; return ( @@ -32,10 +31,10 @@ const ArtistCard = (props: ArtistCardProps) => { }; ArtistCard.defaultProps = { - image: "https://picsum.photos/200", - name: "Artist", + image: 'https://picsum.photos/200', + name: 'Artist', id: 0, - onPress: () => { }, + onPress: () => {}, }; export default ArtistCard; diff --git a/front/components/BigActionButton.tsx b/front/components/BigActionButton.tsx index aa19eb2..847c59d 100644 --- a/front/components/BigActionButton.tsx +++ b/front/components/BigActionButton.tsx @@ -1,9 +1,7 @@ -import React from "react"; +import React from 'react'; import { Box, - Center, Heading, - View, Image, Text, Pressable, @@ -11,9 +9,9 @@ import { Icon, Row, PresenceTransition, -} from "native-base"; -import { StyleProp, ViewStyle } from "react-native"; -import useColorScheme from "../hooks/colorScheme"; +} from 'native-base'; +import { StyleProp, ViewStyle } from 'react-native'; +import useColorScheme from '../hooks/colorScheme'; type BigActionButtonProps = { title: string; @@ -21,6 +19,8 @@ type BigActionButtonProps = { image: string; style?: StyleProp; iconName?: string; + // It is not possible to recover the type, the `Icon` parameter is `any` as well. + // eslint-disable-next-line @typescript-eslint/no-explicit-any iconProvider?: any; onPress: () => void; }; @@ -34,27 +34,27 @@ const BigActionButton = ({ iconProvider, onPress, }: BigActionButtonProps) => { - const screenSize = useBreakpointValue({ base: "small", md: "big" }); + const screenSize = useBreakpointValue({ base: 'small', md: 'big' }); const colorScheme = useColorScheme(); - const isDark = colorScheme === "dark"; + const isDark = colorScheme === 'dark'; return ( - {({ isHovered, isPressed }) => { + {({ isHovered }) => { return ( {title} @@ -126,7 +126,7 @@ const BigActionButton = ({ }} > diff --git a/front/components/Card.tsx b/front/components/Card.tsx index 4f4dfa3..e2f5a47 100644 --- a/front/components/Card.tsx +++ b/front/components/Card.tsx @@ -9,31 +9,39 @@ export const CardBorderRadius = 10; const cardBorder = (theme: ReturnType) => ({ borderColor: theme.colors.text[100], borderRadius: CardBorderRadius, - borderWidth: 1 -}) + borderWidth: 1, +}); type CardProps = Parameters[0] & { - onPress: () => void -} + onPress: () => void; +}; const Card = (props: CardProps) => { const theme = useTheme(); const colorScheme = useSelector((state: RootState) => state.settings.local.colorScheme); const systemColorMode = useColorScheme(); - return - {({ isHovered, isPressed }) => ( - - { props.children } - - )} - - -} + return ( + + {({ isHovered, isPressed }) => ( + + {props.children} + + )} + + ); +}; -export default Card; \ No newline at end of file +export default Card; diff --git a/front/components/CardGridCustom.tsx b/front/components/CardGridCustom.tsx index 41fc00d..bdb2aa4 100644 --- a/front/components/CardGridCustom.tsx +++ b/front/components/CardGridCustom.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { FlatGrid } from 'react-native-super-grid'; import { Heading, VStack } from 'native-base'; - type CardGridCustomProps = { content: T[]; heading?: JSX.Element; @@ -11,7 +10,7 @@ type CardGridCustomProps = { cardComponent: React.ComponentType; }; -const CardGridCustom = >(props: CardGridCustomProps) => { +const CardGridCustom = >(props: CardGridCustomProps) => { const { content, heading, maxItemsPerRow, style, cardComponent: CardComponent } = props; return ( @@ -28,4 +27,4 @@ const CardGridCustom = >(props: CardGridCustomProp ); }; -export default CardGridCustom; \ No newline at end of file +export default CardGridCustom; diff --git a/front/components/CompetenciesTable.tsx b/front/components/CompetenciesTable.tsx index acea685..e51f478 100644 --- a/front/components/CompetenciesTable.tsx +++ b/front/components/CompetenciesTable.tsx @@ -1,6 +1,6 @@ -import { useNavigation } from "../Navigation"; -import { HStack, VStack, Text, Progress } from "native-base"; -import { translate } from "../i18n/i18n"; +import { useNavigation } from '../Navigation'; +import { HStack, VStack, Text, Progress } from 'native-base'; +import { translate } from '../i18n/i18n'; import Card from './Card'; type CompetenciesTableProps = { @@ -10,26 +10,32 @@ type CompetenciesTableProps = { accuracyCompetency: number; arpegeCompetency: number; chordsCompetency: number; -} +}; const CompetenciesTable = (props: CompetenciesTableProps) => { const navigation = useNavigation(); return ( navigation.navigate('User')} shadow={3}> - - - { Object.keys(props).map((competencyName, i) => ( - {translate(competencyName as keyof CompetenciesTableProps)} - ))} + + + {Object.keys(props).map((competencyName, i) => ( + + {translate(competencyName as keyof CompetenciesTableProps)} + + ))} - { Object.keys(props).map((competencyName, i) => ( - - ))} + {Object.keys(props).map((competencyName, i) => ( + + ))} - + - ) -} + ); +}; -export default CompetenciesTable \ No newline at end of file +export default CompetenciesTable; diff --git a/front/components/GenreCard.tsx b/front/components/GenreCard.tsx index 348ecf7..5023159 100644 --- a/front/components/GenreCard.tsx +++ b/front/components/GenreCard.tsx @@ -1,9 +1,8 @@ -import React from "react"; -import Card from "./Card"; -import { VStack, Text, Box, Icon, Image } from "native-base"; -import { useTheme } from "native-base"; -import { Ionicons } from "@expo/vector-icons"; -import API from "../API"; +import React from 'react'; +import Card from './Card'; +import { VStack, Text, Box, Image } from 'native-base'; +import { useTheme } from 'native-base'; + type GenreCardProps = { image: string; name: string; @@ -12,7 +11,7 @@ type GenreCardProps = { }; const GenreCard = (props: GenreCardProps) => { - const { image, name, id } = props; + const { image, name } = props; const theme = useTheme(); return ( @@ -45,9 +44,9 @@ const GenreCard = (props: GenreCardProps) => { }; GenreCard.defaultProps = { - icon: "https://picsum.photos/200", - name: "Genre", - onPress: () => { }, + icon: 'https://picsum.photos/200', + name: 'Genre', + onPress: () => {}, }; export default GenreCard; diff --git a/front/components/GtkUI/Element.tsx b/front/components/GtkUI/Element.tsx index 268f484..931c2e6 100644 --- a/front/components/GtkUI/Element.tsx +++ b/front/components/GtkUI/Element.tsx @@ -1,17 +1,16 @@ -import React from "react"; -import { ElementProps } from "./ElementTypes"; -import { RawElement } from "./RawElement"; -import { Pressable, IPressableProps } from "native-base"; -import { ElementTextProps, ElementToggleProps } from './ElementTypes'; +import React from 'react'; +import { ElementProps } from './ElementTypes'; +import { RawElement } from './RawElement'; +import { Pressable, IPressableProps } from 'native-base'; -export const Element = (props: T) => { +export const Element = (props: T) => { let actionFunction: IPressableProps['onPress'] = null; switch (props.type) { - case "text": + case 'text': actionFunction = props.data?.onPress; break; - case "toggle": + case 'toggle': actionFunction = props.data?.onToggle; break; default: diff --git a/front/components/GtkUI/ElementList.tsx b/front/components/GtkUI/ElementList.tsx index ff175c9..42a5657 100644 --- a/front/components/GtkUI/ElementList.tsx +++ b/front/components/GtkUI/ElementList.tsx @@ -1,14 +1,10 @@ -import React from "react"; -import { StyleProp, ViewStyle } from "react-native"; -import { Element } from "./Element"; -import useColorScheme from "../../hooks/colorScheme"; -import { ElementProps } from "./ElementTypes"; +import React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import { Element } from './Element'; +import useColorScheme from '../../hooks/colorScheme'; +import { ElementProps } from './ElementTypes'; -import { - Box, - Column, - Divider, -} from "native-base"; +import { Box, Column, Divider } from 'native-base'; type ElementListProps = { elements: ElementProps[]; @@ -17,23 +13,21 @@ type ElementListProps = { const ElementList = ({ elements, style }: ElementListProps) => { const colorScheme = useColorScheme(); - const isDark = colorScheme === "dark"; + const isDark = colorScheme === 'dark'; const elementStyle = { borderRadius: 10, boxShadow: isDark - ? "0px 0px 3px 0px rgba(255,255,255,0.6)" - : "0px 0px 3px 0px rgba(0,0,0,0.4)", - overflow: "hidden", + ? '0px 0px 3px 0px rgba(255,255,255,0.6)' + : '0px 0px 3px 0px rgba(0,0,0,0.4)', + overflow: 'hidden', } as const; return ( - {elements.map((element, index, __) => ( + {elements.map((element, index) => ( - { index < elements.length - 1 && - - } + {index < elements.length - 1 && } ))} diff --git a/front/components/GtkUI/ElementTypes.tsx b/front/components/GtkUI/ElementTypes.tsx index c19edf3..278c7be 100644 --- a/front/components/GtkUI/ElementTypes.tsx +++ b/front/components/GtkUI/ElementTypes.tsx @@ -1,5 +1,5 @@ -import { Select, Switch, Text, Icon, Row, Slider } from "native-base"; -import { MaterialIcons } from "@expo/vector-icons"; +import { Select, Switch, Text, Icon, Row, Slider } from 'native-base'; +import { MaterialIcons } from '@expo/vector-icons'; export type ElementProps = { title: string; @@ -8,11 +8,11 @@ export type ElementProps = { description?: string; disabled?: boolean; } & ( - { type: 'text', data : ElementTextProps } | - { type: 'toggle', data : ElementToggleProps } | - { type: 'dropdown', data : ElementDropdownProps } | - { type: 'range', data : ElementRangeProps } | - { type: 'custom', data : React.ReactNode } + | { type: 'text'; data: ElementTextProps } + | { type: 'toggle'; data: ElementToggleProps } + | { type: 'dropdown'; data: ElementDropdownProps } + | { type: 'range'; data: ElementRangeProps } + | { type: 'custom'; data: React.ReactNode } ); export type DropdownOption = { @@ -47,14 +47,11 @@ export type ElementRangeProps = { step?: number; }; -export const getElementTextNode = ( - { text, onPress }: ElementTextProps, - disabled: boolean -) => { +export const getElementTextNode = ({ text, onPress }: ElementTextProps, disabled: boolean) => { return ( { return ( @@ -105,18 +102,14 @@ export const getElementDropdownNode = ( isDisabled={disabled} > {options.map((option) => ( - + ))} ); }; export const getElementRangeNode = ( - { onChange, value, defaultValue, min, max, step }: ElementRangeProps, + { onChange, value, min, max, step }: ElementRangeProps, disabled: boolean, title: string ) => { diff --git a/front/components/GtkUI/RawElement.tsx b/front/components/GtkUI/RawElement.tsx index 12a02f1..a4608c6 100644 --- a/front/components/GtkUI/RawElement.tsx +++ b/front/components/GtkUI/RawElement.tsx @@ -1,28 +1,14 @@ -import React from "react"; -import { - Box, - Button, - Column, - Divider, - Icon, - Popover, - Row, - Text, - useBreakpointValue, -} from "native-base"; -import useColorScheme from "../../hooks/colorScheme"; -import { Ionicons } from "@expo/vector-icons"; -import { ElementProps } from "./ElementTypes"; +import React from 'react'; +import { Box, Button, Column, Icon, Popover, Row, Text, useBreakpointValue } from 'native-base'; +import useColorScheme from '../../hooks/colorScheme'; +import { Ionicons } from '@expo/vector-icons'; +import { ElementProps } from './ElementTypes'; import { getElementDropdownNode, getElementTextNode, getElementToggleNode, getElementRangeNode, - ElementDropdownProps, - ElementTextProps, - ElementToggleProps, - ElementRangeProps, -} from "./ElementTypes"; +} from './ElementTypes'; type RawElementProps = { element: ElementProps; @@ -30,25 +16,24 @@ type RawElementProps = { }; export const RawElement = ({ element, isHovered }: RawElementProps) => { - const { title, icon, type, helperText, description, disabled, data } = - element; + const { title, icon, type, helperText, description, disabled, data } = element; const colorScheme = useColorScheme(); - const isDark = colorScheme === "dark"; - const screenSize = useBreakpointValue({ base: "small", md: "big" }); - const isSmallScreen = screenSize === "small"; + const isDark = colorScheme === 'dark'; + const screenSize = useBreakpointValue({ base: 'small', md: 'big' }); + const isSmallScreen = screenSize === 'small'; return ( @@ -60,14 +45,14 @@ export const RawElement = ({ element, isHovered }: RawElementProps) => { }} > {icon} - - + + {title} {description && ( { > @@ -99,7 +84,7 @@ export const RawElement = ({ element, isHovered }: RawElementProps) => { leftIcon={ } @@ -110,7 +95,7 @@ export const RawElement = ({ element, isHovered }: RawElementProps) => { @@ -120,15 +105,15 @@ export const RawElement = ({ element, isHovered }: RawElementProps) => { )} {(() => { switch (type) { - case "text": + case 'text': return getElementTextNode(data, disabled ?? false); - case "toggle": + case 'toggle': return getElementToggleNode(data, disabled ?? false); - case "dropdown": + case 'dropdown': return getElementDropdownNode(data, disabled ?? false); - case "range": + case 'range': return getElementRangeNode(data, disabled ?? false, title); - case "custom": + case 'custom': return data; default: return Unknown type; diff --git a/front/components/HistoryCard.tsx b/front/components/HistoryCard.tsx index 21c6045..b694a25 100644 --- a/front/components/HistoryCard.tsx +++ b/front/components/HistoryCard.tsx @@ -8,7 +8,9 @@ type SearchHistoryCardProps = { timestamp?: string; }; -const SearchHistoryCard = (props: SearchHistoryCardProps & { onPress: (query: string) => void }) => { +const SearchHistoryCard = ( + props: SearchHistoryCardProps & { onPress: (query: string) => void } +) => { const { query, type, timestamp, onPress } = props; const handlePress = () => { @@ -18,18 +20,18 @@ const SearchHistoryCard = (props: SearchHistoryCardProps & { onPress: (query: st }; return ( - + - {query ?? "query"} + {query ?? 'query'} - {type ?? "type"} + {type ?? 'type'} - {timestamp ?? "timestamp"} + {timestamp ?? 'timestamp'} ); }; -export default SearchHistoryCard; \ No newline at end of file +export default SearchHistoryCard; diff --git a/front/components/IconButton.tsx b/front/components/IconButton.tsx index 3a1e2f1..5b54bde 100644 --- a/front/components/IconButton.tsx +++ b/front/components/IconButton.tsx @@ -1,12 +1,16 @@ -import { Box, Button } from "native-base"; +import { Box, Button } from 'native-base'; type IconButtonProps = { - icon: Parameters[0]['leftIcon'] + icon: Parameters[0]['leftIcon']; } & Omit[0], 'leftIcon' | 'rightIcon'>; // Wrapper around Button for IconButton as Native's one sucks <3 const IconButton = (props: IconButtonProps) => { - return ))} ); -} +}; -export default SearchBar; \ No newline at end of file +export default SearchBar; diff --git a/front/components/SearchResult.tsx b/front/components/SearchResult.tsx index 06acd85..59e8035 100644 --- a/front/components/SearchResult.tsx +++ b/front/components/SearchResult.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from "react"; +import React from 'react'; import { HStack, VStack, @@ -12,34 +12,32 @@ import { useBreakpointValue, Column, ScrollView, -} from "native-base"; -import { SafeAreaView, useColorScheme } from "react-native"; -import { RootState, useSelector } from "../state/Store"; -import { SearchContext } from "../views/SearchView"; -import { useQuery } from "react-query"; -import { translate } from "../i18n/i18n"; -import API from "../API"; -import LoadingComponent from "./Loading"; -import ArtistCard from "./ArtistCard"; -import GenreCard from "./GenreCard"; -import SongCard from "./SongCard"; -import CardGridCustom from "./CardGridCustom"; -import TextButton from "./TextButton"; -import SearchHistoryCard from "./HistoryCard"; -import Song, { SongWithArtist } from "../models/Song"; -import { getSongWArtistSuggestions } from "./utils/api"; -import { useNavigation } from "../Navigation"; +} from 'native-base'; +import { SafeAreaView, useColorScheme } from 'react-native'; +import { RootState, useSelector } from '../state/Store'; +import { SearchContext } from '../views/SearchView'; +import { useQuery } from 'react-query'; +import { translate } from '../i18n/i18n'; +import API from '../API'; +import LoadingComponent from './Loading'; +import ArtistCard from './ArtistCard'; +import GenreCard from './GenreCard'; +import SongCard from './SongCard'; +import CardGridCustom from './CardGridCustom'; +import TextButton from './TextButton'; +import SearchHistoryCard from './HistoryCard'; +import Song, { SongWithArtist } from '../models/Song'; +import { getSongWArtistSuggestions } from './utils/api'; +import { useNavigation } from '../Navigation'; const swaToSongCardProps = (song: SongWithArtist) => ({ songId: song.id, name: song.name, artistName: song.artist.name, - cover: song.cover ?? "https://picsum.photos/200", + cover: song.cover ?? 'https://picsum.photos/200', }); -const RowCustom = ( - props: Parameters[0] & { onPress?: () => void } -) => { +const RowCustom = (props: Parameters[0] & { onPress?: () => void }) => { const settings = useSelector((state: RootState) => state.settings.local); const systemColorMode = useColorScheme(); const colorScheme = settings.colorScheme; @@ -52,13 +50,13 @@ const RowCustom = ( py={3} my={1} bg={ - (colorScheme == "system" ? systemColorMode : colorScheme) == "dark" + (colorScheme == 'system' ? systemColorMode : colorScheme) == 'dark' ? isHovered || isPressed - ? "gray.800" + ? 'gray.800' : undefined : isHovered || isPressed - ? "coolGray.200" - : undefined + ? 'coolGray.200' + : undefined } > {props.children} @@ -75,8 +73,8 @@ type SongRowProps = { const SongRow = ({ song, onPress }: SongRowProps) => { return ( - - + + { /> @@ -101,7 +99,7 @@ const SongRow = ({ song, onPress }: SongRowProps) => { }} isTruncated pl={10} - maxW={"100%"} + maxW={'100%'} bold fontSize="md" > @@ -111,17 +109,17 @@ const SongRow = ({ song, onPress }: SongRowProps) => { style={{ flexShrink: 0, }} - fontSize={"sm"} + fontSize={'sm'} > - {song.artistId ?? "artist"} + {song.artistId ?? 'artist'} @@ -131,26 +129,29 @@ const SongRow = ({ song, onPress }: SongRowProps) => { }; SongRow.defaultProps = { - onPress: () => { }, + onPress: () => {}, }; const HomeSearchComponent = () => { - const { stringQuery, updateStringQuery } = React.useContext(SearchContext); + const { updateStringQuery } = React.useContext(SearchContext); const { isLoading: isLoadingHistory, data: historyData = [] } = useQuery( - "history", + 'history', () => API.getSearchHistory(0, 12), { enabled: true } ); - const { isLoading: isLoadingSuggestions, data: suggestionsData = [] } = - useQuery("suggestions", () => getSongWArtistSuggestions(), { + const { isLoading: isLoadingSuggestions, data: suggestionsData = [] } = useQuery( + 'suggestions', + () => getSongWArtistSuggestions(), + { enabled: true, - }); + } + ); return ( - + - {translate("lastSearched")} + {translate('lastSearched')} {isLoadingHistory ? ( ) : ( @@ -169,7 +170,7 @@ const HomeSearchComponent = () => { )} - {translate("songsToGetBetter")} + {translate('songsToGetBetter')} {isLoadingSuggestions ? ( ) : ( @@ -183,14 +184,18 @@ const HomeSearchComponent = () => { ); }; -const SongsSearchComponent = (props: any) => { +type SongsSearchComponentProps = { + maxRows?: number; +}; + +const SongsSearchComponent = (props: SongsSearchComponentProps) => { const { songData } = React.useContext(SearchContext); const navigation = useNavigation(); return ( - {translate("songsFilter")} + {translate('songsFilter')} {songData?.length ? ( @@ -199,94 +204,94 @@ const SongsSearchComponent = (props: any) => { key={index} song={comp} onPress={() => { - API.createSearchHistoryEntry(comp.name, "song", Date.now()); - navigation.navigate("Song", { songId: comp.id }); + API.createSearchHistoryEntry(comp.name, 'song'); + navigation.navigate('Song', { songId: comp.id }); }} /> )) ) : ( - {translate("errNoResults")} + {translate('errNoResults')} )} ); }; -const ArtistSearchComponent = (props: any) => { +type ItemSearchComponentProps = { + maxItems?: number; +}; + +const ArtistSearchComponent = (props: ItemSearchComponentProps) => { const { artistData } = React.useContext(SearchContext); const navigation = useNavigation(); return ( - {translate("artistFilter")} + {translate('artistFilter')} {artistData?.length ? ( ({ - image: API.getArtistIllustration(a.id), - name: a.name, - id: a.id, - onPress: () => { - API.createSearchHistoryEntry(a.name, "artist", Date.now()); - navigation.navigate("Artist", { artistId: a.id }); - }, - }))} + content={artistData.slice(0, props.maxItems ?? artistData.length).map((a) => ({ + image: API.getArtistIllustration(a.id), + name: a.name, + id: a.id, + onPress: () => { + API.createSearchHistoryEntry(a.name, 'artist'); + navigation.navigate('Artist', { artistId: a.id }); + }, + }))} cardComponent={ArtistCard} /> ) : ( - {translate("errNoResults")} + {translate('errNoResults')} )} ); }; -const GenreSearchComponent = (props: any) => { +const GenreSearchComponent = (props: ItemSearchComponentProps) => { const { genreData } = React.useContext(SearchContext); const navigation = useNavigation(); return ( - {translate("genreFilter")} + {translate('genreFilter')} {genreData?.length ? ( ({ - image: API.getGenreIllustration(g.id), - name: g.name, - id: g.id, - onPress: () => { - API.createSearchHistoryEntry(g.name, "genre", Date.now()); - navigation.navigate("Home"); - }, - }))} + content={genreData.slice(0, props.maxItems ?? genreData.length).map((g) => ({ + image: API.getGenreIllustration(g.id), + name: g.name, + id: g.id, + onPress: () => { + API.createSearchHistoryEntry(g.name, 'genre'); + navigation.navigate('Home'); + }, + }))} cardComponent={GenreCard} /> ) : ( - {translate("errNoResults")} + {translate('errNoResults')} )} ); }; const AllComponent = () => { - const screenSize = useBreakpointValue({ base: "small", md: "big" }); - const isMobileView = screenSize == "small"; + const screenSize = useBreakpointValue({ base: 'small', md: 'big' }); + const isMobileView = screenSize == 'small'; return ( - + @@ -294,7 +299,7 @@ const AllComponent = () => { - + @@ -311,22 +316,21 @@ const FilterSwitch = () => { }, [filter]); switch (currentFilter) { - case "all": + case 'all': return ; - case "song": + case 'song': return ; - case "artist": + case 'artist': return ; - case "genre": + case 'genre': return ; default: return Something very bad happened: {currentFilter}; } }; -export const SearchResultComponent = (props: any) => { - const [searchString, setSearchString] = useState(""); - const { stringQuery, updateStringQuery } = React.useContext(SearchContext); +export const SearchResultComponent = () => { + const { stringQuery } = React.useContext(SearchContext); const shouldOutput = !!stringQuery.trim(); return shouldOutput ? ( diff --git a/front/components/SongCard.tsx b/front/components/SongCard.tsx index 64ec328..439bdeb 100644 --- a/front/components/SongCard.tsx +++ b/front/components/SongCard.tsx @@ -1,8 +1,7 @@ -import React from "react"; -import Card, { CardBorderRadius } from "./Card"; -import { VStack, Text, Image } from "native-base"; -import { useNavigation } from "../Navigation"; -import API from "../API"; +import React from 'react'; +import Card, { CardBorderRadius } from './Card'; +import { VStack, Text, Image } from 'native-base'; +import { useNavigation } from '../Navigation'; type SongCardProps = { cover: string; name: string; @@ -14,12 +13,12 @@ const SongCard = (props: SongCardProps) => { const { cover, name, artistName, songId } = props; const navigation = useNavigation(); return ( - navigation.navigate("Song", { songId })}> + navigation.navigate('Song', { songId })}> {[props.name, diff --git a/front/components/SongCardGrid.tsx b/front/components/SongCardGrid.tsx index c003c34..c529d43 100644 --- a/front/components/SongCardGrid.tsx +++ b/front/components/SongCardGrid.tsx @@ -5,22 +5,24 @@ import { Heading, VStack } from 'native-base'; type SongCardGrid = { songs: Parameters[0][]; - heading?: JSX.Element, - maxItemsPerRow?: number, - style?: Parameters[0]['additionalRowStyle'] -} + heading?: JSX.Element; + maxItemsPerRow?: number; + style?: Parameters[0]['additionalRowStyle']; +}; const SongCardGrid = (props: SongCardGrid) => { - return - {props.heading} - } - spacing={10} - /> - -} + return ( + + {props.heading} + } + spacing={10} + /> + + ); +}; -export default SongCardGrid; \ No newline at end of file +export default SongCardGrid; diff --git a/front/components/TextButton.tsx b/front/components/TextButton.tsx index 7a5fcc2..49d282f 100644 --- a/front/components/TextButton.tsx +++ b/front/components/TextButton.tsx @@ -1,24 +1,26 @@ -import { Button, Text } from "native-base" -import Translate from "./Translate"; +import { Button, Text } from 'native-base'; +import Translate from './Translate'; import { RequireExactlyOne } from 'type-fest'; -type TextButtonProps = Parameters[0] & RequireExactlyOne<{ - label: string; - translate: Parameters[0]; -}> +type TextButtonProps = Parameters[0] & + RequireExactlyOne<{ + label: string; + translate: Parameters[0]; + }>; const TextButton = (props: TextButtonProps) => { // accepts undefined variant, as it is the default variant - const textColor = !props.variant || props.variant == 'solid' - ? 'light.50' - : undefined; + const textColor = !props.variant || props.variant == 'solid' ? 'light.50' : undefined; - return -} + return ( + + ); +}; -export default TextButton \ No newline at end of file +export default TextButton; diff --git a/front/components/Translate.tsx b/front/components/Translate.tsx index 6dbef8b..cd5a0ed 100644 --- a/front/components/Translate.tsx +++ b/front/components/Translate.tsx @@ -1,7 +1,7 @@ -import { Text } from "native-base"; -import { translate } from "../i18n/i18n"; -import { en } from "../i18n/Translations"; -import { RootState, useSelector } from "../state/Store"; +import { Text } from 'native-base'; +import { translate } from '../i18n/i18n'; +import { en } from '../i18n/Translations'; +import { RootState, useSelector } from '../state/Store'; type TranslateProps = { translationKey: keyof typeof en; @@ -9,14 +9,14 @@ type TranslateProps = { } & Parameters[0]; /** * Translation component - * @param param0 - * @returns + * @param param0 + * @returns */ const Translate = ({ translationKey, format, ...props }: TranslateProps) => { const selectedLanguage = useSelector((state: RootState) => state.language.value); const translated = translate(translationKey, selectedLanguage); return {format ? format(translated) : translated}; -} +}; -export default Translate; \ No newline at end of file +export default Translate; diff --git a/front/components/VirtualPiano/Octave.tsx b/front/components/VirtualPiano/Octave.tsx index d8f3052..c459816 100644 --- a/front/components/VirtualPiano/Octave.tsx +++ b/front/components/VirtualPiano/Octave.tsx @@ -5,9 +5,9 @@ import { octaveKeys, isAccidental, HighlightedKey, -} from "../../models/Piano"; -import { Box, Row, Text } from "native-base"; -import PianoKeyComp from "./PianoKeyComp"; +} from '../../models/Piano'; +import { Box, Row, Text } from 'native-base'; +import PianoKeyComp from './PianoKeyComp'; type OctaveProps = Parameters[0] & { number: number; @@ -57,22 +57,20 @@ const Octave = (props: OctaveProps) => { const whiteKeys = keys.filter((k) => !isAccidental(k)); const blackKeys = keys.filter(isAccidental); - const whiteKeyWidthExpr = "calc(100% / 7)"; - const whiteKeyHeightExpr = "100%"; - const blackKeyWidthExpr = "calc(100% / 13)"; - const blackKeyHeightExpr = "calc(100% / 1.5)"; + const whiteKeyWidthExpr = 'calc(100% / 7)'; + const whiteKeyHeightExpr = '100%'; + const blackKeyWidthExpr = 'calc(100% / 13)'; + const blackKeyHeightExpr = 'calc(100% / 1.5)'; return ( - - {whiteKeys.map((key, i) => { - const highlightedKey = highlightedNotes.find( - (h) => h.key.note === key.note - ); + + {whiteKeys.map((key) => { + const highlightedKey = highlightedNotes.find((h) => h.key.note === key.note); const isHighlighted = highlightedKey !== undefined; - const highlightColor = - highlightedKey?.bgColor ?? defaultHighlightColor; + const highlightColor = highlightedKey?.bgColor ?? defaultHighlightColor; return ( + // eslint-disable-next-line react/jsx-key { ); })} {blackKeys.map((key, i) => { - const highlightedKey = highlightedNotes.find( - (h) => h.key.note === key.note - ); + const highlightedKey = highlightedNotes.find((h) => h.key.note === key.note); const isHighlighted = highlightedKey !== undefined; - const highlightColor = - highlightedKey?.bgColor ?? defaultHighlightColor; + const highlightColor = highlightedKey?.bgColor ?? defaultHighlightColor; return ( + // eslint-disable-next-line react/jsx-key { style={{ width: blackKeyWidthExpr, height: blackKeyHeightExpr, - position: "absolute", + position: 'absolute', left: `calc(calc(${whiteKeyWidthExpr} * ${ i + ((i > 1) as unknown as number) + 1 }) - calc(${blackKeyWidthExpr} / 2))`, - top: "0px", + top: '0px', }} text={{ - color: "white", - fontSize: "xs", + color: 'white', + fontSize: 'xs', }} /> ); @@ -139,18 +135,18 @@ const Octave = (props: OctaveProps) => { }; Octave.defaultProps = { - startNote: "C", - endNote: "B", - showNoteNames: "onpress", + startNote: 'C', + endNote: 'B', + showNoteNames: 'onpress', showOctaveNumber: false, - whiteKeyBg: "white", - whiteKeyBgPressed: "gray.200", - whiteKeyBgHovered: "gray.100", - blackKeyBg: "black", - blackKeyBgPressed: "gray.600", - blackKeyBgHovered: "gray.700", + whiteKeyBg: 'white', + whiteKeyBgPressed: 'gray.200', + whiteKeyBgHovered: 'gray.100', + blackKeyBg: 'black', + blackKeyBgPressed: 'gray.600', + blackKeyBgHovered: 'gray.700', highlightedNotes: [], - defaultHighlightColor: "#FF0000", + defaultHighlightColor: '#FF0000', onNoteDown: () => {}, onNoteUp: () => {}, }; diff --git a/front/components/VirtualPiano/PianoKeyComp.tsx b/front/components/VirtualPiano/PianoKeyComp.tsx index 5a86f49..d0f3a50 100644 --- a/front/components/VirtualPiano/PianoKeyComp.tsx +++ b/front/components/VirtualPiano/PianoKeyComp.tsx @@ -1,11 +1,6 @@ -import { Box, Pressable, Text } from "native-base"; -import { StyleProp, ViewStyle } from "react-native"; -import { - PianoKey, - NoteNameBehavior, - octaveKeys, - keyToStr, -} from "../../models/Piano"; +import { Box, Pressable, Text } from 'native-base'; +import { StyleProp, ViewStyle } from 'react-native'; +import { PianoKey, NoteNameBehavior, octaveKeys, keyToStr } from '../../models/Piano'; type PianoKeyProps = { pianoKey: PianoKey; @@ -48,22 +43,18 @@ const PianoKeyComp = ({ }: PianoKeyProps) => { const textDefaultProps = { style: { - userSelect: "none", - WebkitUserSelect: "none", - MozUserSelect: "none", - msUserSelect: "none", + userSelect: 'none', + WebkitUserSelect: 'none', + MozUserSelect: 'none', + msUserSelect: 'none', }, - fontSize: "xl", - color: "black", + fontSize: 'xl', + color: 'black', } as Parameters[0]; const textProps = { ...textDefaultProps, ...text }; return ( - + {({ isHovered, isPressed }) => ( { @@ -90,9 +81,9 @@ const PianoKeyComp = ({ PianoKeyComp.defaultProps = { key: octaveKeys[0], showNoteNames: NoteNameBehavior.onhover, - keyBg: "white", - keyBgPressed: "gray.200", - keyBgHovered: "gray.100", + keyBg: 'white', + keyBgPressed: 'gray.200', + keyBgHovered: 'gray.100', onKeyDown: () => {}, onKeyUp: () => {}, text: {}, diff --git a/front/components/VirtualPiano/VirtualPiano.tsx b/front/components/VirtualPiano/VirtualPiano.tsx index 085c550..3552f4f 100644 --- a/front/components/VirtualPiano/VirtualPiano.tsx +++ b/front/components/VirtualPiano/VirtualPiano.tsx @@ -1,16 +1,8 @@ -import { Row, Box } from "native-base"; -import React, { useState, useEffect } from "react"; -import Octave from "./Octave"; -import { StyleProp, ViewStyle } from "react-native"; -import { - Note, - PianoKey, - NoteNameBehavior, - KeyPressStyle, - keyToStr, - strToKey, - HighlightedKey, -} from "../../models/Piano"; +import { Row } from 'native-base'; +import React from 'react'; +import Octave from './Octave'; +import { StyleProp, ViewStyle } from 'react-native'; +import { Note, PianoKey, NoteNameBehavior, HighlightedKey } from '../../models/Piano'; type VirtualPianoProps = Parameters[0] & { onNoteDown: (note: PianoKey) => void; @@ -37,29 +29,21 @@ const VirtualPiano = ({ showOctaveNumbers, style, }: VirtualPianoProps) => { - const notesList: Array = [ - Note.C, - Note.D, - Note.E, - Note.F, - Note.G, - Note.A, - Note.B, - ]; + const notesList: Array = [Note.C, Note.D, Note.E, Note.F, Note.G, Note.A, Note.B]; const octaveList = []; for (let octaveNum = startOctave; octaveNum <= endOctave; octaveNum++) { octaveList.push(octaveNum); } - const octaveWidthExpr = `calc(100% / ${octaveList.length})`; + const octaveWidthExpr = `calc(100% / ${octaveList.length})`; return ( {octaveList.map((octaveNum) => { return ( @@ -81,8 +63,8 @@ const VirtualPiano = ({ }; VirtualPiano.defaultProps = { - onNoteDown: (_n: PianoKey) => {}, - onNoteUp: (_n: PianoKey) => {}, + onNoteDown: () => {}, + onNoteUp: () => {}, startOctave: 2, startNote: Note.C, endOctave: 6, @@ -90,7 +72,7 @@ VirtualPiano.defaultProps = { showNoteNames: NoteNameBehavior.onpress, highlightedNotes: [], showOctaveNumbers: true, - style: {}, + style: {}, }; export default VirtualPiano; diff --git a/front/components/forms/changeEmailForm.tsx b/front/components/forms/changeEmailForm.tsx index d49c7c0..a997ae0 100644 --- a/front/components/forms/changeEmailForm.tsx +++ b/front/components/forms/changeEmailForm.tsx @@ -1,37 +1,26 @@ -import React from "react"; -import { translate } from "../../i18n/i18n"; -import { string } from "yup"; -import { - FormControl, - Input, - Stack, - WarningOutlineIcon, - Box, - Button, - useToast, -} from "native-base"; +import React from 'react'; +import { translate } from '../../i18n/i18n'; +import { string } from 'yup'; +import { FormControl, Input, Stack, WarningOutlineIcon, Box, Button, useToast } from 'native-base'; interface ChangeEmailFormProps { - onSubmit: ( - oldEmail: string, - newEmail: string - ) => Promise; + onSubmit: (oldEmail: string, newEmail: string) => Promise; } const validationSchemas = { - email: string().email("Invalid email").required("Email is required"), + email: string().email('Invalid email').required('Email is required'), }; const ChangeEmailForm = ({ onSubmit }: ChangeEmailFormProps) => { const [formData, setFormData] = React.useState({ oldEmail: { - value: "", + value: '', error: null as string | null, }, newEmail: { - value: "", + value: '', error: null as string | null, - } + }, }); const [submittingForm, setSubmittingForm] = React.useState(false); @@ -42,77 +31,73 @@ const ChangeEmailForm = ({ onSubmit }: ChangeEmailFormProps) => { - {translate("oldEmail")} - { - let error: null | string = null; - validationSchemas.email - .validate(t) - .catch((e) => (error = e.message)) - .finally(() => { - setFormData({ ...formData, oldEmail: { value: t, error } }); - }); - }} - /> - } > - {formData.oldEmail.error} - + {translate('oldEmail')} + { + let error: null | string = null; + validationSchemas.email + .validate(t) + .catch((e) => (error = e.message)) + .finally(() => { + setFormData({ ...formData, oldEmail: { value: t, error } }); + }); + }} + /> + }> + {formData.oldEmail.error} + - {translate("newEmail")} - { - let error: null | string = null; - validationSchemas.email - .validate(t) - .catch((e) => (error = e.message)) - .finally(() => { - setFormData({ ...formData, newEmail: { value: t, error } }); - }); - }} - /> - } > - {formData.oldEmail.error} - - - + {translate('newEmail')} + { + let error: null | string = null; + validationSchemas.email + .validate(t) + .catch((e) => (error = e.message)) + .finally(() => { + setFormData({ ...formData, newEmail: { value: t, error } }); + }); + }} + /> + }> + {formData.oldEmail.error} + + + ); -} +}; export default ChangeEmailForm; diff --git a/front/components/forms/changePasswordForm.tsx b/front/components/forms/changePasswordForm.tsx index 647f9aa..e7b4c9d 100644 --- a/front/components/forms/changePasswordForm.tsx +++ b/front/components/forms/changePasswordForm.tsx @@ -1,36 +1,24 @@ -import React from "react"; -import { translate } from "../../i18n/i18n"; -import { string } from "yup"; -import { - FormControl, - Input, - Stack, - WarningOutlineIcon, - Box, - Button, - useToast, -} from "native-base"; - +import React from 'react'; +import { translate } from '../../i18n/i18n'; +import { string } from 'yup'; +import { FormControl, Input, Stack, WarningOutlineIcon, Box, Button, useToast } from 'native-base'; interface ChangePasswordFormProps { - onSubmit: ( - oldPassword: string, - newPassword: string - ) => Promise; + onSubmit: (oldPassword: string, newPassword: string) => Promise; } const ChangePasswordForm = ({ onSubmit }: ChangePasswordFormProps) => { const [formData, setFormData] = React.useState({ oldPassword: { - value: "", + value: '', error: null as string | null, }, newPassword: { - value: "", + value: '', error: null as string | null, }, confirmNewPassword: { - value: "", + value: '', error: null as string | null, }, }); @@ -38,120 +26,119 @@ const ChangePasswordForm = ({ onSubmit }: ChangePasswordFormProps) => { const validationSchemas = { password: string() - .min(4, translate("passwordTooShort")) - .max(100, translate("passwordTooLong")) - .required("Password is required"), + .min(4, translate('passwordTooShort')) + .max(100, translate('passwordTooLong')) + .required('Password is required'), }; const toast = useToast(); - return ( - - + return ( + + + {translate('oldPassword')} + { + let error: null | string = null; + validationSchemas.password + .validate(t) + .catch((e) => (error = e.message)) + .finally(() => { + setFormData({ ...formData, oldPassword: { value: t, error } }); + }); + }} + /> + }> + {formData.oldPassword.error} + - {translate("oldPassword")} - { - let error: null | string = null; - validationSchemas.password - .validate(t) - .catch((e) => (error = e.message)) - .finally(() => { - setFormData({ ...formData, oldPassword: { value: t, error } }); - }); - }} - /> - } > - {formData.oldPassword.error} - + {translate('newPassword')} + { + let error: null | string = null; + validationSchemas.password + .validate(t) + .catch((e) => (error = e.message)) + .finally(() => { + setFormData({ ...formData, newPassword: { value: t, error } }); + }); + }} + /> + }> + {formData.newPassword.error} + - {translate("newPassword")} - { - let error: null | string = null; - validationSchemas.password - .validate(t) - .catch((e) => (error = e.message)) - .finally(() => { - setFormData({ ...formData, newPassword: { value: t, error } }); - }); - }} - /> - } > - {formData.newPassword.error} - - - {translate("confirmNewPassword")} - { - let error: null | string = null; - validationSchemas.password - .validate(t) - .catch((e) => (error = e.message)) + {translate('confirmNewPassword')} + { + let error: null | string = null; + validationSchemas.password + .validate(t) + .catch((e) => (error = e.message)); if (!error && t !== formData.newPassword.value) { - error = translate("passwordsDontMatch"); + error = translate('passwordsDontMatch'); } setFormData({ ...formData, confirmNewPassword: { value: t, error }, }); - }} - /> - } > - {formData.confirmNewPassword.error} - + }} + /> + }> + {formData.confirmNewPassword.error} + - - + onPress={async () => { + setSubmittingForm(true); + try { + const resp = await onSubmit( + formData.oldPassword.value, + formData.newPassword.value + ); + toast.show({ description: resp }); + } catch (e) { + toast.show({ description: e as string }); + } finally { + setSubmittingForm(false); + } + }} + > + {translate('submitBtn')} + - - - ); -} + + + ); +}; export default ChangePasswordForm; diff --git a/front/components/forms/signinform.tsx b/front/components/forms/signinform.tsx index 35dfcd4..c6ebb84 100644 --- a/front/components/forms/signinform.tsx +++ b/front/components/forms/signinform.tsx @@ -1,17 +1,10 @@ // a form for sign in -import React from "react"; -import { Translate, translate } from "../../i18n/i18n"; -import { string } from "yup"; -import { - FormControl, - Input, - Stack, - WarningOutlineIcon, - Box, - useToast, -} from "native-base"; -import TextButton from "../TextButton"; +import React from 'react'; +import { Translate, translate } from '../../i18n/i18n'; +import { string } from 'yup'; +import { FormControl, Input, Stack, WarningOutlineIcon, Box, useToast } from 'native-base'; +import TextButton from '../TextButton'; interface SigninFormProps { onSubmit: (username: string, password: string) => Promise; @@ -20,11 +13,11 @@ interface SigninFormProps { const LoginForm = ({ onSubmit }: SigninFormProps) => { const [formData, setFormData] = React.useState({ username: { - value: "", + value: '', error: null as string | null, }, password: { - value: "", + value: '', error: null as string | null, }, }); @@ -32,98 +25,96 @@ const LoginForm = ({ onSubmit }: SigninFormProps) => { const validationSchemas = { username: string() - .min(3, translate("usernameTooShort")) - .max(20, translate("usernameTooLong")) - .required("Username is required"), + .min(3, translate('usernameTooShort')) + .max(20, translate('usernameTooLong')) + .required('Username is required'), password: string() - .min(4, translate("passwordTooShort")) - .max(100, translate("passwordTooLong")) - .required("Password is required"), + .min(4, translate('passwordTooShort')) + .max(100, translate('passwordTooLong')) + .required('Password is required'), }; const toast = useToast(); return ( - + + + + { + let error: null | string = null; + validationSchemas.username + .validate(t) + .catch((e) => (error = e.message)) + .finally(() => { + setFormData({ ...formData, username: { value: t, error } }); + }); + }} + /> + }> + {formData.username.error} + + + + + { + let error: null | string = null; + validationSchemas.password + .validate(t) + .catch((e) => (error = e.message)) + .finally(() => { + setFormData({ ...formData, password: { value: t, error } }); + }); + }} + /> + }> + {formData.password.error} + + - - - - { - let error: null | string = null; - validationSchemas.username - .validate(t) - .catch((e) => (error = e.message)) - .finally(() => { - setFormData({ ...formData, username: { value: t, error } }); - }); - }} - /> - } - > - {formData.username.error} - - - - - { - let error: null | string = null; - validationSchemas.password - .validate(t) - .catch((e) => (error = e.message)) - .finally(() => { - setFormData({ ...formData, password: { value: t, error } }); - }); - }} - /> - } - > - {formData.password.error} - - { + setSubmittingForm(true); + try { + const resp = await onSubmit( + formData.username.value, + formData.password.value + ); + toast.show({ description: resp, colorScheme: 'secondary' }); + setSubmittingForm(false); + } catch (e) { + toast.show({ + description: e as string, + colorScheme: 'red', + avoidKeyboard: true, + }); + setSubmittingForm(false); } - onPress={async () => { - setSubmittingForm(true); - try { - const resp = await onSubmit( - formData.username.value, - formData.password.value - ); - toast.show({ description: resp, colorScheme: 'secondary' }); - setSubmittingForm(false); - } catch (e) { - toast.show({ description: e as string, colorScheme: 'red', avoidKeyboard: true }) - setSubmittingForm(false); - } - }} - /> - - + }} + /> + + ); }; diff --git a/front/components/forms/signupform.tsx b/front/components/forms/signupform.tsx index 85a12a1..973d0e5 100644 --- a/front/components/forms/signupform.tsx +++ b/front/components/forms/signupform.tsx @@ -1,42 +1,31 @@ // a form for sign up -import React from "react"; -import { Translate, translate } from "../../i18n/i18n"; -import { string } from "yup"; -import { - FormControl, - Input, - Stack, - WarningOutlineIcon, - Box, - useToast, -} from "native-base"; -import TextButton from "../TextButton"; +import React from 'react'; +import { Translate, translate } from '../../i18n/i18n'; +import { string } from 'yup'; +import { FormControl, Input, Stack, WarningOutlineIcon, Box, useToast } from 'native-base'; +import TextButton from '../TextButton'; interface SignupFormProps { - onSubmit: ( - username: string, - password: string, - email: string - ) => Promise; + onSubmit: (username: string, password: string, email: string) => Promise; } const SignUpForm = ({ onSubmit }: SignupFormProps) => { const [formData, setFormData] = React.useState({ username: { - value: "", + value: '', error: null as string | null, }, password: { - value: "", + value: '', error: null as string | null, }, repeatPassword: { - value: "", + value: '', error: null as string | null, }, email: { - value: "", + value: '', error: null as string | null, }, }); @@ -44,20 +33,20 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => { const validationSchemas = { username: string() - .min(3, translate("usernameTooShort")) - .max(20, translate("usernameTooLong")) - .required("Username is required"), - email: string().email("Invalid email").required("Email is required"), + .min(3, translate('usernameTooShort')) + .max(20, translate('usernameTooLong')) + .required('Username is required'), + email: string().email('Invalid email').required('Email is required'), password: string() - .min(4, translate("passwordTooShort")) - .max(100, translate("passwordTooLong")) + .min(4, translate('passwordTooShort')) + .max(100, translate('passwordTooLong')) // .matches( // /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$-_%\^&\*])(?=.{8,})/, // translate( // "Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and One Special Case Character" // ) // ) - .required("Password is required"), + .required('Password is required'), }; const toast = useToast(); @@ -75,7 +64,7 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => { } > - + { }); }} /> - } - > + }> {formData.username.error} - + { }); }} /> - } - > + }> {formData.email.error} - + { }); }} /> - } - > + }> {formData.password.error} - + { .catch((e) => (error = e.message)) .finally(() => { if (!error && t !== formData.password.value) { - error = translate("passwordsDontMatch"); + error = translate('passwordsDontMatch'); } setFormData({ ...formData, @@ -169,12 +152,11 @@ const SignUpForm = ({ onSubmit }: SignupFormProps) => { }); }} /> - } - > + }> {formData.repeatPassword.error} - { formData.username.error !== null || formData.repeatPassword.error !== null || formData.email.error !== null || - formData.username.value === "" || - formData.password.value === "" || - formData.repeatPassword.value === "" || - formData.repeatPassword.value === "" + formData.username.value === '' || + formData.password.value === '' || + formData.repeatPassword.value === '' || + formData.repeatPassword.value === '' } onPress={async () => { setSubmittingForm(true); diff --git a/front/components/navigators/TabRowNavigator.tsx b/front/components/navigators/TabRowNavigator.tsx index 5a10ba2..920a92a 100644 --- a/front/components/navigators/TabRowNavigator.tsx +++ b/front/components/navigators/TabRowNavigator.tsx @@ -1,16 +1,7 @@ -import * as React from "react"; -import { StyleProp, ViewStyle, StyleSheet } from "react-native"; -import { Ionicons } from "@expo/vector-icons"; -import { - View, - Text, - Pressable, - Box, - Row, - Icon, - Button, - useBreakpointValue, -} from "native-base"; +import * as React from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { View, Text, Row, Icon, Button, useBreakpointValue } from 'native-base'; import { createNavigatorFactory, DefaultNavigatorOptions, @@ -21,12 +12,12 @@ import { TabRouter, TabRouterOptions, useNavigationBuilder, -} from "@react-navigation/native"; -import { useNavigation } from "../../Navigation"; +} from '@react-navigation/native'; +import { useNavigation } from '../../Navigation'; -const TabRowNavigatorInitialComponentName = "TabIndex"; +const TabRowNavigatorInitialComponentName = 'TabIndex'; -export {TabRowNavigatorInitialComponentName}; +export { TabRowNavigatorInitialComponentName }; // Props accepted by the view type TabNavigationConfig = { @@ -37,6 +28,7 @@ type TabNavigationConfig = { // Supported screen options type TabNavigationOptions = { title?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any iconProvider?: any; iconName?: string; }; @@ -70,34 +62,31 @@ function TabNavigator({ contentStyle, }: Props) { const navigator = useNavigation(); - const { state, navigation, descriptors, NavigationContent } = - useNavigationBuilder< - TabNavigationState, - TabRouterOptions, - TabActionHelpers, - TabNavigationOptions, - TabNavigationEventMap - >(TabRouter, { - children, - screenOptions, - initialRouteName, - }); + const { state, navigation, descriptors, NavigationContent } = useNavigationBuilder< + TabNavigationState, + TabRouterOptions, + TabActionHelpers, + TabNavigationOptions, + TabNavigationEventMap + >(TabRouter, { + children, + screenOptions, + initialRouteName, + }); - const screenSize = useBreakpointValue({ base: "small", md: "big" }); + const screenSize = useBreakpointValue({ base: 'small', md: 'big' }); const [isPanelView, setIsPanelView] = React.useState(false); - const isMobileView = screenSize == "small"; + const isMobileView = screenSize == 'small'; React.useEffect(() => { if (state.index === 0) { if (isMobileView) { setIsPanelView(true); } else { - navigation.reset( - { - ...state, - index: 1, - } - ); + navigation.reset({ + ...state, + index: 1, + }); } } }, [state.index]); @@ -110,18 +99,18 @@ function TabNavigator({ return ( - + {(!isMobileView || isPanelView) && ( { const event = navigation.emit({ - type: "tabPress", + type: 'tabPress', target: route.key, canPreventDefault: true, data: { @@ -157,12 +146,16 @@ function TabNavigator({ setIsPanelView(false); } }} - bgColor={isSelected && (!isMobileView || !isPanelView) ? "primary.300" : undefined} + bgColor={ + isSelected && (!isMobileView || !isPanelView) + ? 'primary.300' + : undefined + } style={{ - justifyContent: "flex-start", - padding: "10px", - height: "50px", - width: "100%", + justifyContent: 'flex-start', + padding: '10px', + height: '50px', + width: '100%', }} leftIcon={ options?.iconProvider && options?.iconName ? ( @@ -185,17 +178,14 @@ function TabNavigator({ )} {(!isMobileView || !isPanelView) && ( {isMobileView && ( } + + + + {mode === 'signin' ? ( + + hanldeSignin(username, password, (accessToken) => + dispatch(setAccessToken(accessToken)) + ) + } + /> + ) : ( + + handleSignup(username, password, email, (accessToken) => + dispatch(setAccessToken(accessToken)) + ) + } + /> + )} + {mode === 'signin' && ( + + )} setMode(mode === "signin" ? "signup" : "signin")} + translate={{ translationKey: mode === 'signin' ? 'signUpBtn' : 'signInBtn' }} + variant="outline" + marginTop={5} + colorScheme="primary" + onPress={() => setMode(mode === 'signin' ? 'signup' : 'signin')} /> ); diff --git a/front/views/HomeView.test.tsx b/front/views/HomeView.test.tsx index d5558e4..59831a1 100644 --- a/front/views/HomeView.test.tsx +++ b/front/views/HomeView.test.tsx @@ -6,8 +6,12 @@ import store from '../state/Store'; import HomeView from '../views/HomeView'; describe('', () => { - it('has 2 children', () => { - const tree = TestRenderer.create().toJSON(); - expect(tree.children.length).toBe(2); - }); + it('has 2 children', () => { + const tree = TestRenderer.create( + + + + ).toJSON(); + expect(tree.children.length).toBe(2); + }); }); diff --git a/front/views/HomeView.tsx b/front/views/HomeView.tsx index ae85d81..22c6c44 100644 --- a/front/views/HomeView.tsx +++ b/front/views/HomeView.tsx @@ -1,40 +1,19 @@ -import React from "react"; -import { useQueries, useQuery } from "react-query"; -import API from "../API"; -import LoadingComponent from "../components/Loading"; -import CardGridCustom from "../components/CardGridCustom"; -import { LoadingView } from "../components/Loading"; -import { - Center, - Box, - ScrollView, - Flex, - useBreakpointValue, - Stack, - Heading, - Container, - VStack, - HStack, - Column, - Button, - Text, - useTheme -} from "native-base"; - -import { useNavigation } from "../Navigation"; -import SongCardGrid from "../components/SongCardGrid"; -import CompetenciesTable from "../components/CompetenciesTable"; -import ProgressBar from "../components/ProgressBar"; -import Translate from "../components/Translate"; -import TextButton from "../components/TextButton"; -import SearchHistoryCard from "../components/HistoryCard"; -import Song from "../models/Song"; -import { FontAwesome5 } from "@expo/vector-icons"; +import React from 'react'; +import { useQueries, useQuery } from 'react-query'; +import API from '../API'; +import { LoadingView } from '../components/Loading'; +import { Box, ScrollView, Flex, Stack, Heading, VStack, HStack } from 'native-base'; +import { useNavigation } from '../Navigation'; +import SongCardGrid from '../components/SongCardGrid'; +import CompetenciesTable from '../components/CompetenciesTable'; +import ProgressBar from '../components/ProgressBar'; +import Translate from '../components/Translate'; +import TextButton from '../components/TextButton'; +import Song from '../models/Song'; +import { FontAwesome5 } from '@expo/vector-icons'; const HomeView = () => { - const theme = useTheme(); const navigation = useNavigation(); - const screenSize = useBreakpointValue({ base: 'small', md: "big"}); const userQuery = useQuery(['user'], () => API.getUserInfo()); const playHistoryQuery = useQuery(['history', 'play'], () => API.getUserPlayHistory()); const searchHistoryQuery = useQuery(['history', 'search'], () => API.getSearchHistory(0, 10)); @@ -43,125 +22,168 @@ const HomeView = () => { const songHistory = useQueries( playHistoryQuery.data?.map(({ songID }) => ({ queryKey: ['song', songID], - queryFn: () => API.getSong(songID) + queryFn: () => API.getSong(songID), })) ?? [] ); - const artistsQueries = useQueries((songHistory - .map((entry) => entry.data) - .concat(nextStepQuery.data ?? []) - .filter((s): s is Song => s !== undefined)) - .map((song) => ( - { queryKey: ['artist', song.id], queryFn: () => API.getArtist(song.artistId) } - )) + const artistsQueries = useQueries( + songHistory + .map((entry) => entry.data) + .concat(nextStepQuery.data ?? []) + .filter((s): s is Song => s !== undefined) + .map((song) => ({ + queryKey: ['artist', song.id], + queryFn: () => API.getArtist(song.artistId), + })) ); - if (!userQuery.data || !skillsQuery.data || !searchHistoryQuery.data || !playHistoryQuery.data) { - return + if ( + !userQuery.data || + !skillsQuery.data || + !searchHistoryQuery.data || + !playHistoryQuery.data + ) { + return ; } - return - - - `${welcome} ${userQuery.data.name}!`} - /> - - - - - - - - } - songs={nextStepQuery.data?.filter((song) => artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId)) - .map((song) => ({ - cover: song.cover, - name: song.name, - songId: song.id, - artistName: artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId)!.data!.name - })) ?? [] - } - /> - - - - - - + return ( + + + + `${welcome} ${userQuery.data.name}!`} + /> + + - - } - songs={songHistory - .map(({ data }) => data) - .filter((data): data is Song => data !== undefined) - .filter((song, i, array) => array.map((s) => s.id).findIndex((id) => id == song.id) == i) - .filter((song) => artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId)) + + + + + } + songs={ + nextStepQuery.data + ?.filter((song) => + artistsQueries.find( + (artistQuery) => artistQuery.data?.id === song.artistId + ) + ) .map((song) => ({ cover: song.cover, name: song.name, songId: song.id, - artistName: artistsQueries.find((artistQuery) => artistQuery.data?.id === song.artistId)!.data!.name + artistName: artistsQueries.find( + (artistQuery) => artistQuery.data?.id === song.artistId + )!.data!.name, })) ?? [] - } + } + /> + + + + + + + + + + + } + songs={ + songHistory + .map(({ data }) => data) + .filter((data): data is Song => data !== undefined) + .filter( + (song, i, array) => + array + .map((s) => s.id) + .findIndex((id) => id == song.id) == i + ) + .filter((song) => + artistsQueries.find( + (artistQuery) => + artistQuery.data?.id === song.artistId + ) + ) + .map((song) => ({ + cover: song.cover, + name: song.name, + songId: song.id, + artistName: artistsQueries.find( + (artistQuery) => + artistQuery.data?.id === song.artistId + )!.data!.name, + })) ?? [] + } + /> + + + + + + navigation.navigate('Search', {})} /> - - - - - - navigation.navigate('Search', {})} - /> - navigation.navigate('Settings')} - /> - - - - - { - searchHistoryQuery.data?.length === 0 && - } - { - [...(new Set(searchHistoryQuery.data.map((x) => x.query)))].slice(0, 5).map((query) => ( - - } - style={{ - margin: 2, - }} - key={ query } - variant="solid" - size="xs" - colorScheme="primary" - label={query} - onPress={() => navigation.navigate('Search', { query: query })} - /> - )) - } - - - - - - - -} + navigation.navigate('Settings')} + /> + + + + + + + {searchHistoryQuery.data?.length === 0 && ( + + )} + {[...new Set(searchHistoryQuery.data.map((x) => x.query))] + .slice(0, 5) + .map((query) => ( + } + style={{ + margin: 2, + }} + key={query} + variant="solid" + size="xs" + colorScheme="primary" + label={query} + onPress={() => + navigation.navigate('Search', { query: query }) + } + /> + ))} + + + + + + ); +}; export default HomeView; diff --git a/front/views/PlayView.tsx b/front/views/PlayView.tsx index 2a0ac41..1c8a6f8 100644 --- a/front/views/PlayView.tsx +++ b/front/views/PlayView.tsx @@ -1,10 +1,22 @@ +/* eslint-disable no-mixed-spaces-and-tabs */ import React, { useEffect, useRef, useState } from 'react'; import { SafeAreaView, Platform, Animated } from 'react-native'; import * as ScreenOrientation from 'expo-screen-orientation'; -import { Box, Center, Column, Progress, Text, Row, View, useToast, Icon, HStack } from 'native-base'; +import { + Box, + Center, + Column, + Progress, + Text, + Row, + View, + useToast, + Icon, + HStack, +} from 'native-base'; import IconButton from '../components/IconButton'; -import { Ionicons, MaterialCommunityIcons } from "@expo/vector-icons"; -import { RouteProps, useNavigation } from "../Navigation"; +import { Ionicons, MaterialCommunityIcons } from '@expo/vector-icons'; +import { RouteProps, useNavigation } from '../Navigation'; import { useQuery } from 'react-query'; import API from '../API'; import LoadingComponent, { LoadingView } from '../components/Loading'; @@ -13,39 +25,36 @@ import VirtualPiano from '../components/VirtualPiano/VirtualPiano'; import { strToKey, keyToStr, Note } from '../models/Piano'; import { useSelector } from 'react-redux'; import { RootState } from '../state/Store'; -import { Translate, translate } from '../i18n/i18n'; +import { translate } from '../i18n/i18n'; import { ColorSchemeType } from 'native-base/lib/typescript/components/types'; -import { useStopwatch } from "react-use-precision-timer"; +import { useStopwatch } from 'react-use-precision-timer'; import PartitionView from '../components/PartitionView'; import TextButton from '../components/TextButton'; -import { MIDIAccess, MIDIMessageEvent, requestMIDIAccess } from "@motiz88/react-native-midi"; +import { MIDIAccess, MIDIMessageEvent, requestMIDIAccess } from '@motiz88/react-native-midi'; import * as Linking from 'expo-linking'; import { URL } from 'url'; -import { url } from 'inspector'; type PlayViewProps = { - songId: number, - type: 'practice' | 'normal' -} + songId: number; + type: 'practice' | 'normal'; +}; type ScoreMessage = { - content: string, - color?: ColorSchemeType -} - + content: string; + color?: ColorSchemeType; +}; // this a hot fix this should be reverted soon let scoroBaseApiUrl = Constants.manifest?.extra?.scoroUrl; if (process.env.NODE_ENV != 'development' && Platform.OS === 'web') { - Linking.getInitialURL().then((url) => { if (url !== null) { const location = new URL(url); if (location.protocol === 'https:') { - scoroBaseApiUrl = "wss://" + location.host + "/ws"; + scoroBaseApiUrl = 'wss://' + location.host + '/ws'; } else { - scoroBaseApiUrl = "ws://" + location.host + "/ws"; + scoroBaseApiUrl = 'ws://' + location.host + '/ws'; } } }); @@ -74,8 +83,9 @@ const PlayView = ({ songId, type, route }: RouteProps) => { const [partitionRendered, setPartitionRendered] = useState(false); // Used to know when partitionview can render const [score, setScore] = useState(0); // Between 0 and 100 const fadeAnim = useRef(new Animated.Value(0)).current; - const musixml = useQuery(["musixml", songId], () => - API.getSongMusicXML(songId).then((data) => new TextDecoder().decode(data)), + const musixml = useQuery( + ['musixml', songId], + () => API.getSongMusicXML(songId).then((data) => new TextDecoder().decode(data)), { staleTime: Infinity } ); const getElapsedTime = () => stopwatch.getElapsedRunningTime() - 3000; @@ -84,12 +94,14 @@ const PlayView = ({ songId, type, route }: RouteProps) => { const onPause = () => { stopwatch.pause(); setPause(true); - webSocket.current?.send(JSON.stringify({ - type: "pause", - paused: true, - time: getElapsedTime() - })); - } + webSocket.current?.send( + JSON.stringify({ + type: 'pause', + paused: true, + time: getElapsedTime(), + }) + ); + }; const onResume = () => { if (stopwatch.isStarted()) { stopwatch.resume(); @@ -97,17 +109,21 @@ const PlayView = ({ songId, type, route }: RouteProps) => { stopwatch.start(); } setPause(false); - webSocket.current?.send(JSON.stringify({ - type: "pause", - paused: false, - time: getElapsedTime() - })); - } + webSocket.current?.send( + JSON.stringify({ + type: 'pause', + paused: false, + time: getElapsedTime(), + }) + ); + }; const onEnd = () => { - webSocket.current?.send(JSON.stringify({ - type: "end" - })); - } + webSocket.current?.send( + JSON.stringify({ + type: 'end', + }) + ); + }; const onMIDISuccess = (access: MIDIAccess) => { const inputs = access.inputs; @@ -120,12 +136,14 @@ const PlayView = ({ songId, type, route }: RouteProps) => { let inputIndex = 0; webSocket.current = new WebSocket(scoroBaseApiUrl); webSocket.current.onopen = () => { - webSocket.current!.send(JSON.stringify({ - type: "start", - id: song.data!.id, - mode: type, - bearer: accessToken - })); + webSocket.current!.send( + JSON.stringify({ + type: 'start', + id: song.data!.id, + mode: type, + bearer: accessToken, + }) + ); }; webSocket.current.onmessage = (message) => { try { @@ -137,7 +155,7 @@ const PlayView = ({ songId, type, route }: RouteProps) => { const points = data.info.score; const maxPoints = data.info.max_score || 1; - setScore(Math.floor(Math.max(points, 0) * 100 / maxPoints)); + setScore(Math.floor((Math.max(points, 0) * 100) / maxPoints)); let formattedMessage = ''; let messageColor: ColorSchemeType | undefined; @@ -172,18 +190,18 @@ const PlayView = ({ songId, type, route }: RouteProps) => { } catch (e) { console.error(e); } - } + }; inputs.forEach((input) => { if (inputIndex != 0) { return; } input.onmidimessage = (message) => { - const { command, channel, note, velocity } = parseMidiMessage(message); - const keyIsPressed = command == 9;; + const { command } = parseMidiMessage(message); + const keyIsPressed = command == 9; const keyCode = message.data[1]; webSocket.current?.send( JSON.stringify({ - type: keyIsPressed ? "note_on" : "note_off", + type: keyIsPressed ? 'note_on' : 'note_off', note: keyCode, id: song.data!.id, time: getElapsedTime(), @@ -192,22 +210,22 @@ const PlayView = ({ songId, type, route }: RouteProps) => { }; inputIndex++; }); - } + }; const onMIDIFailure = () => { toast.show({ description: `Failed to get MIDI access` }); - } + }; useEffect(() => { ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE).catch(() => {}); - let interval = setInterval(() => { - setTime(() => getElapsedTime()) // Countdown + const interval = setInterval(() => { + setTime(() => getElapsedTime()); // Countdown }, 1); return () => { ScreenOrientation.unlockAsync().catch(() => {}); onEnd(); clearInterval(interval); - } + }; }, []); useEffect(() => { if (lastScoreMessage) { @@ -231,109 +249,157 @@ const PlayView = ({ songId, type, route }: RouteProps) => { }, [song.data, partitionRendered]); if (!song.data || !musixml.data) { - return ; + return ; } return ( - + - setPartitionRendered(true)} timestamp={Math.max(0, time)} onEndReached={() => { onEnd(); }} /> - { !partitionRendered && } + {!partitionRendered && } - {isVirtualPianoVisible && + { + console.log('On note down', keyToStr(note)); + }} + onNoteUp={(note) => { + console.log('On note up', keyToStr(note)); + }} + showOctaveNumbers={true} + startNote={Note.C} + endNote={Note.B} + startOctave={2} + endOctave={5} + style={{ + width: '80%', + height: '100%', + }} + highlightedNotes={[ + { key: strToKey('D3') }, + { key: strToKey('A#'), bgColor: '#00FF00' }, + ]} + /> + + )} + - { - console.log("On note down", keyToStr(note)); - }} - onNoteUp={(note: any) => { - console.log("On note up", keyToStr(note)); - }} - showOctaveNumbers={true} - startNote={Note.C} - endNote={Note.B} - startOctave={2} - endOctave={5} - style={{ - width: '80%', - height: '100%', - }} - highlightedNotes={ - [ - { key: strToKey("D3") }, - { key: strToKey("A#"), bgColor: "#00FF00" }, - ] - } - - /> - } - - - + + Score: {score}% - +
{song.data.name}
- - {midiKeyboardFound && <> - - } onPress={() => { - if (paused) { - onResume(); - } else { - onPause(); - } - }}/> - - } onPress={() => { - setVirtualPianoVisible(!isVirtualPianoVisible); - }}/> - - { time < 0 - ? paused - ? '0:00' - : Math.floor((time % 60000) / 1000).toFixed(0).toString() - : `${Math.floor(time / 60000)}:${Math.floor((time % 60000) / 1000).toFixed(0).toString().padStart(2, '0')}` - } - - - } onPress={() => { - onEnd(); - }}/> - } + + {midiKeyboardFound && ( + <> + } + onPress={() => { + if (paused) { + onResume(); + } else { + onPause(); + } + }} + /> + + } + onPress={() => { + setVirtualPianoVisible(!isVirtualPianoVisible); + }} + /> + + {time < 0 + ? paused + ? '0:00' + : Math.floor((time % 60000) / 1000) + .toFixed(0) + .toString() + : `${Math.floor(time / 60000)}:${Math.floor( + (time % 60000) / 1000 + ) + .toFixed(0) + .toString() + .padStart(2, '0')}`} + + } + onPress={() => { + onEnd(); + }} + /> + + )}
); -} +}; -export default PlayView +export default PlayView; diff --git a/front/views/ProfileView.tsx b/front/views/ProfileView.tsx index 0a18629..3efcd41 100644 --- a/front/views/ProfileView.tsx +++ b/front/views/ProfileView.tsx @@ -1,100 +1,131 @@ import React from 'react'; import { Dimensions, View } from 'react-native'; -import { Box, Image, Heading, HStack, Card, Button, Spacer, Text } from 'native-base'; +import { Box, Image, Heading, HStack, Card, Text } from 'native-base'; import Translate from '../components/Translate'; -import { useNavigation } from "../Navigation"; +import { useNavigation } from '../Navigation'; import TextButton from '../components/TextButton'; const UserMedals = () => { - return ( - - - - - - Profile picture - Profile picture - Profile picture - Profile picture - - - ); -} + return ( + + + + + + Profile picture + Profile picture + Profile picture + Profile picture + + + ); +}; const PlayerStats = () => { - const answer = "Answer from back"; + const answer = 'Answer from back'; - return( - - - {answer} - {answer} - {answer} - {answer} - - - ); -} + return ( + + + {' '} + {' '} + + + {' '} + {answer}{' '} + + + {' '} + {answer}{' '} + + + {' '} + {answer}{' '} + + + {' '} + {answer}{' '} + + + ); +}; const ProfilePictureBannerAndLevel = () => { - const profilePic = "https://wallpaperaccess.com/full/317501.jpg" - const username = "Username" - const level = "1" + const profilePic = 'https://wallpaperaccess.com/full/317501.jpg'; + const username = 'Username'; + const level = '1'; - // banner size - const dimensions = Dimensions.get('window'); - const imageHeight = dimensions.height / 5; - const imageWidth = dimensions.width; + // banner size + const dimensions = Dimensions.get('window'); + const imageHeight = dimensions.height / 5; + const imageWidth = dimensions.width; - // need to change the padding for the username and level + // need to change the padding for the username and level - return ( - - - - Profile picture + + + Profile picture - - {username} - Level : {level} - - - - ); -} + + {username} + Level : {level} + +
+ + ); +}; const ProfileView = () => { - const navigation = useNavigation(); + const navigation = useNavigation(); - return ( - - - - - - navigation.navigate('Settings', { screen: 'Profile' })} - translate={{ translationKey: 'settingsBtn' }} - /> - - - ); -} + return ( + + + + + + navigation.navigate('Settings', { screen: 'Profile' })} + translate={{ translationKey: 'settingsBtn' }} + /> + + + ); +}; -export default ProfileView; \ No newline at end of file +export default ProfileView; diff --git a/front/views/ScoreView.tsx b/front/views/ScoreView.tsx index 288ff90..1f6c0c0 100644 --- a/front/views/ScoreView.tsx +++ b/front/views/ScoreView.tsx @@ -1,23 +1,13 @@ -import { - Card, - Column, - Image, - Row, - Text, - ScrollView, - VStack, -} from "native-base"; -import Translate from "../components/Translate"; -import SongCardGrid from "../components/SongCardGrid"; -import { RouteProps, useNavigation } from "../Navigation"; -import { CardBorderRadius } from "../components/Card"; -import TextButton from "../components/TextButton"; -import API from "../API"; -import LoadingComponent from "../components/Loading"; -import CardGridCustom from "../components/CardGridCustom"; -import SongCard from "../components/SongCard"; -import { useQueries, useQuery } from "react-query"; -import { LoadingView } from "../components/Loading"; +import { Card, Column, Image, Row, Text, ScrollView, VStack } from 'native-base'; +import Translate from '../components/Translate'; +import { RouteProps, useNavigation } from '../Navigation'; +import { CardBorderRadius } from '../components/Card'; +import TextButton from '../components/TextButton'; +import API from '../API'; +import CardGridCustom from '../components/CardGridCustom'; +import SongCard from '../components/SongCard'; +import { useQueries, useQuery } from 'react-query'; +import { LoadingView } from '../components/Loading'; type ScoreViewProps = { songId: number; @@ -35,28 +25,23 @@ type ScoreViewProps = { }; }; -const ScoreView = ({ - songId, - overallScore, - precision, - score, -}: RouteProps) => { +const ScoreView = ({ songId, overallScore, precision, score }: RouteProps) => { const navigation = useNavigation(); - const songQuery = useQuery(["song", songId], () => API.getSong(songId)); + const songQuery = useQuery(['song', songId], () => API.getSong(songId)); const artistQuery = useQuery( - ["song", songId, "artist"], + ['song', songId, 'artist'], () => API.getArtist(songQuery.data!.artistId!), - { enabled: songQuery.data != undefined } + { + enabled: songQuery.data != undefined, + } ); // const perfoamnceRecommandationsQuery = useQuery(['song', props.songId, 'score', 'latest', 'recommendations'], () => API.getLastSongPerformanceScore(props.songId)); - const recommendations = useQuery(["song", "recommendations"], () => - API.getSongSuggestions() - ); + const recommendations = useQuery(['song', 'recommendations'], () => API.getSongSuggestions()); const artistRecommendations = useQueries( recommendations.data ?.filter(({ artistId }) => artistId !== null) .map((song) => ({ - queryKey: ["artist", song.artistId], + queryKey: ['artist', song.artistId], queryFn: () => API.getArtist(song.artistId!), })) ?? [] ); @@ -71,13 +56,13 @@ const ScoreView = ({ } return ( - - + + {songQuery.data.name} {artistQuery.data?.name} - + - + {/* @@ -103,67 +88,66 @@ const ScoreView = ({ ' ' + t}/> */} - - t + " : "} /> + + t + ' : '} /> - {overallScore + "pts"} + {overallScore + 'pts'} - - t + " : "} /> + + t + ' : '} /> {score.perfect} - - t + " : "} /> + + t + ' : '} /> {score.great} - - t + " : "} /> + + t + ' : '} /> {score.good} - - t + " : "} /> + + t + ' : '} /> {score.wrong} - - t + " : "} /> + + t + ' : '} /> {score.missed} - - t + " : "} /> + + t + ' : '} /> {score.max_streak} - - t + " : "} /> + + t + ' : '} /> - {precision + "%"} + {precision + '%'} - ({ cover: i.cover, name: i.name, artistName: - artistRecommendations.find(({ data }) => data?.id == i.artistId) - ?.data?.name ?? "", + artistRecommendations.find(({ data }) => data?.id == i.artistId)?.data + ?.name ?? '', songId: i.id, }))} cardComponent={SongCard} @@ -173,15 +157,15 @@ const ScoreView = ({ } /> - + navigation.navigate("Home")} + translate={{ translationKey: 'backBtn' }} + onPress={() => navigation.navigate('Home')} /> navigation.navigate("Song", { songId })} - translate={{ translationKey: "playAgain" }} + onPress={() => navigation.navigate('Song', { songId })} + translate={{ translationKey: 'playAgain' }} /> diff --git a/front/views/SearchView.tsx b/front/views/SearchView.tsx index 3982d9d..c9de230 100644 --- a/front/views/SearchView.tsx +++ b/front/views/SearchView.tsx @@ -1,19 +1,19 @@ -import React, { useState } from "react"; -import SearchBar from "../components/SearchBar"; -import Artist from "../models/Artist"; -import Song from "../models/Song"; -import Genre from "../models/Genre"; -import API from "../API"; -import { useQuery } from "react-query"; -import { SearchResultComponent } from "../components/SearchResult"; -import { SafeAreaView } from "react-native"; -import { Filter } from "../components/SearchBar"; -import { ScrollView } from "native-base"; -import { RouteProps } from "../Navigation"; +import React, { useState } from 'react'; +import SearchBar from '../components/SearchBar'; +import Artist from '../models/Artist'; +import Song from '../models/Song'; +import Genre from '../models/Genre'; +import API from '../API'; +import { useQuery } from 'react-query'; +import { SearchResultComponent } from '../components/SearchResult'; +import { SafeAreaView } from 'react-native'; +import { Filter } from '../components/SearchBar'; +import { ScrollView } from 'native-base'; +import { RouteProps } from '../Navigation'; interface SearchContextType { - filter: "artist" | "song" | "genre" | "all"; - updateFilter: (newData: "artist" | "song" | "genre" | "all") => void; + filter: 'artist' | 'song' | 'genre' | 'all'; + updateFilter: (newData: 'artist' | 'song' | 'genre' | 'all') => void; stringQuery: string; updateStringQuery: (newData: string) => void; songData: Song[]; @@ -25,9 +25,9 @@ interface SearchContextType { } export const SearchContext = React.createContext({ - filter: "all", + filter: 'all', updateFilter: () => {}, - stringQuery: "", + stringQuery: '', updateStringQuery: () => {}, songData: [], artistData: [], @@ -42,24 +42,23 @@ type SearchViewProps = { }; const SearchView = (props: RouteProps) => { - let isRequestSucceeded = false; - const [filter, setFilter] = useState("all"); - const [stringQuery, setStringQuery] = useState(props?.query ?? ""); + const [filter, setFilter] = useState('all'); + const [stringQuery, setStringQuery] = useState(props?.query ?? ''); const { isLoading: isLoadingSong, data: songData = [] } = useQuery( - ["song", stringQuery], + ['song', stringQuery], () => API.searchSongs(stringQuery), { enabled: !!stringQuery } ); const { isLoading: isLoadingArtist, data: artistData = [] } = useQuery( - ["artist", stringQuery], + ['artist', stringQuery], () => API.searchArtists(stringQuery), { enabled: !!stringQuery } ); const { isLoading: isLoadingGenre, data: genreData = [] } = useQuery( - ["genre", stringQuery], + ['genre', stringQuery], () => API.searchGenres(stringQuery), { enabled: !!stringQuery } ); @@ -72,7 +71,6 @@ const SearchView = (props: RouteProps) => { const updateStringQuery = (newData: string) => { // called when the stringQuery is updated setStringQuery(newData); - isRequestSucceeded = false; }; return ( diff --git a/front/views/SettingsView.test.tsx b/front/views/SettingsView.test.tsx index 56ea9ff..6814e23 100644 --- a/front/views/SettingsView.test.tsx +++ b/front/views/SettingsView.test.tsx @@ -6,8 +6,12 @@ import store from '../state/Store'; import AuthenticationView from '../views/AuthenticationView'; describe('', () => { - it('has 3 children', () => { - const tree = TestRenderer.create().toJSON(); - expect(tree.children.length).toBe(3); - }); -}); \ No newline at end of file + it('has 3 children', () => { + const tree = TestRenderer.create( + + + + ).toJSON(); + expect(tree.children.length).toBe(3); + }); +}); diff --git a/front/views/SongLobbyView.tsx b/front/views/SongLobbyView.tsx index 6a24c9c..63324ce 100644 --- a/front/views/SongLobbyView.tsx +++ b/front/views/SongLobbyView.tsx @@ -1,23 +1,13 @@ -import { - Divider, - Box, - Center, - Image, - Text, - VStack, - PresenceTransition, - Icon, - Stack, -} from "native-base"; -import { useQuery } from "react-query"; -import LoadingComponent, { LoadingView } from "../components/Loading"; -import React, { useEffect, useState } from "react"; -import { Translate, translate } from "../i18n/i18n"; -import formatDuration from "format-duration"; -import { Ionicons } from "@expo/vector-icons"; -import API from "../API"; -import TextButton from "../components/TextButton"; -import { RouteProps, useNavigation } from "../Navigation"; +import { Divider, Box, Image, Text, VStack, PresenceTransition, Icon, Stack } from 'native-base'; +import { useQuery } from 'react-query'; +import LoadingComponent, { LoadingView } from '../components/Loading'; +import React, { useEffect, useState } from 'react'; +import { Translate, translate } from '../i18n/i18n'; +import formatDuration from 'format-duration'; +import { Ionicons } from '@expo/vector-icons'; +import API from '../API'; +import TextButton from '../components/TextButton'; +import { RouteProps, useNavigation } from '../Navigation'; interface SongLobbyProps { // The unique identifier to find a song @@ -26,32 +16,30 @@ interface SongLobbyProps { const SongLobbyView = (props: RouteProps) => { const navigation = useNavigation(); - const songQuery = useQuery(["song", props.songId], () => - API.getSong(props.songId) - ); - const chaptersQuery = useQuery(["song", props.songId, "chapters"], () => + const songQuery = useQuery(['song', props.songId], () => API.getSong(props.songId)); + const chaptersQuery = useQuery(['song', props.songId, 'chapters'], () => API.getSongChapters(props.songId) ); - const scoresQuery = useQuery(["song", props.songId, "scores"], () => + const scoresQuery = useQuery(['song', props.songId, 'scores'], () => API.getSongHistory(props.songId) ); const [chaptersOpen, setChaptersOpen] = useState(false); useEffect(() => { if (chaptersOpen && !chaptersQuery.data) chaptersQuery.refetch(); }, [chaptersOpen]); - useEffect(() => { }, [songQuery.isLoading]); + useEffect(() => {}, [songQuery.isLoading]); if (songQuery.isLoading || scoresQuery.isLoading) return ; return ( - - + + {songQuery.data?.name} @@ -61,8 +49,8 @@ const SongLobbyView = (props: RouteProps) => { style={{ flex: 3, padding: 10, - flexDirection: "column", - justifyContent: "space-between", + flexDirection: 'column', + justifyContent: 'space-between', }} > @@ -73,30 +61,31 @@ const SongLobbyView = (props: RouteProps) => { - `${level}: ${chaptersQuery.data!.reduce((a, b) => a + b.difficulty, 0) / - chaptersQuery.data!.length + `${level}: ${ + chaptersQuery.data!.reduce((a, b) => a + b.difficulty, 0) / + chaptersQuery.data!.length }` } /> - navigation.navigate("Play", { + navigation.navigate('Play', { songId: songQuery.data!.id, - type: "normal", + type: 'normal', }) } rightIcon={} /> - navigation.navigate("Play", { + navigation.navigate('Play', { songId: songQuery.data!.id, - type: "practice", + type: 'practice', }) } rightIcon={} @@ -107,18 +96,18 @@ const SongLobbyView = (props: RouteProps) => { - + {scoresQuery.data?.best ?? 0} - + @@ -128,15 +117,13 @@ const SongLobbyView = (props: RouteProps) => { {/* {songQuery.data!.description} */} setChaptersOpen(!chaptersOpen)} endIcon={ } /> @@ -154,8 +141,9 @@ const SongLobbyView = (props: RouteProps) => { > {chapter.name} - {`${translate("level")} ${chapter.difficulty - } - ${formatDuration((chapter.end - chapter.start) * 1000)}`} + {`${translate('level')} ${ + chapter.difficulty + } - ${formatDuration((chapter.end - chapter.start) * 1000)}`} ))} diff --git a/front/views/StartPageView.tsx b/front/views/StartPageView.tsx index cd9c1af..ead1697 100644 --- a/front/views/StartPageView.tsx +++ b/front/views/StartPageView.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import { useNavigation } from "../Navigation"; +import React from 'react'; +import { useNavigation } from '../Navigation'; import { View, Text, @@ -15,54 +15,52 @@ import { Row, Heading, Icon, -} from "native-base"; -import { FontAwesome5 } from "@expo/vector-icons"; -import BigActionButton from "../components/BigActionButton"; -import API, { APIError } from "../API"; -import { setAccessToken } from "../state/UserSlice"; -import { useDispatch } from "../state/Store"; -import { translate } from "../i18n/i18n"; +} from 'native-base'; +import { FontAwesome5 } from '@expo/vector-icons'; +import BigActionButton from '../components/BigActionButton'; +import API, { APIError } from '../API'; +import { setAccessToken } from '../state/UserSlice'; +import { useDispatch } from '../state/Store'; +import { translate } from '../i18n/i18n'; -const handleGuestLogin = async ( - apiSetter: (accessToken: string) => void -): Promise => { +const handleGuestLogin = async (apiSetter: (accessToken: string) => void): Promise => { const apiAccess = await API.createAndGetGuestAccount(); apiSetter(apiAccess); - return translate("loggedIn"); + return translate('loggedIn'); }; const imgLogin = - "https://media.discordapp.net/attachments/717080637038788731/1095980610981478470/Octopus_a_moder_style_image_of_a_musician_showing_a_member_card_c0b9072c-d834-40d5-bc83-796501e1382c.png?width=657&height=657"; + 'https://media.discordapp.net/attachments/717080637038788731/1095980610981478470/Octopus_a_moder_style_image_of_a_musician_showing_a_member_card_c0b9072c-d834-40d5-bc83-796501e1382c.png?width=657&height=657'; const imgGuest = - "https://media.discordapp.net/attachments/717080637038788731/1095996800835539014/Chromacase_guest_2.png?width=865&height=657"; + 'https://media.discordapp.net/attachments/717080637038788731/1095996800835539014/Chromacase_guest_2.png?width=865&height=657'; const imgRegister = - "https://media.discordapp.net/attachments/717080637038788731/1095991220267929641/chromacase_register.png?width=1440&height=511"; + 'https://media.discordapp.net/attachments/717080637038788731/1095991220267929641/chromacase_register.png?width=1440&height=511'; const imgBanner = - "https://chromacase.studio/wp-content/uploads/2023/03/music-sheet-music-color-2462438.jpg"; + 'https://chromacase.studio/wp-content/uploads/2023/03/music-sheet-music-color-2462438.jpg'; const imgLogo = - "https://chromacase.studio/wp-content/uploads/2023/03/cropped-cropped-splashLogo-280x300.png"; + 'https://chromacase.studio/wp-content/uploads/2023/03/cropped-cropped-splashLogo-280x300.png'; const StartPageView = () => { const navigation = useNavigation(); - const screenSize = useBreakpointValue({ base: "small", md: "big" }); - const isSmallScreen = screenSize === "small"; + const screenSize = useBreakpointValue({ base: 'small', md: 'big' }); + const isSmallScreen = screenSize === 'small'; const dispatch = useDispatch(); const toast = useToast(); return (
@@ -75,17 +73,17 @@ const StartPageView = () => { }} /> } - size={isSmallScreen ? "5xl" : "6xl"} + size={isSmallScreen ? '5xl' : '6xl'} /> - Chromacase + Chromacase
{ image={imgLogin} iconName="user" iconProvider={FontAwesome5} - onPress={() => navigation.navigate("Login", { isSignup: false })} + onPress={() => navigation.navigate('Login', { isSignup: false })} style={{ - width: isSmallScreen ? "90%" : "clamp(100px, 33.3%, 600px)", - height: "300px", - margin: "clamp(10px, 2%, 50px)", + width: isSmallScreen ? '90%' : 'clamp(100px, 33.3%, 600px)', + height: '300px', + margin: 'clamp(10px, 2%, 50px)', }} /> { } }} style={{ - width: isSmallScreen ? "90%" : "clamp(100px, 33.3%, 600px)", - height: "300px", - margin: "clamp(10px, 2%, 50px)", + width: isSmallScreen ? '90%' : 'clamp(100px, 33.3%, 600px)', + height: '300px', + margin: 'clamp(10px, 2%, 50px)', }} /> @@ -134,60 +132,60 @@ const StartPageView = () => { subtitle="Create an account to save your progress" iconProvider={FontAwesome5} iconName="user-plus" - onPress={() => navigation.navigate("Login", { isSignup: true })} + onPress={() => navigation.navigate('Login', { isSignup: true })} style={{ - height: "150px", - width: isSmallScreen ? "90%" : "clamp(150px, 50%, 600px)", + height: '150px', + width: isSmallScreen ? '90%' : 'clamp(150px, 50%, 600px)', }} /> - + What is Chromacase? - - Chromacase is a free and open source project that aims to provide a - complete learning experience for anyone willing to learn piano. + + Chromacase is a free and open source project that aims to provide a complete + learning experience for anyone willing to learn piano. - + Chromacase Banner { Click here for more infos diff --git a/front/views/settings/GuestToUserView.tsx b/front/views/settings/GuestToUserView.tsx index d9bcfb2..4f09da2 100644 --- a/front/views/settings/GuestToUserView.tsx +++ b/front/views/settings/GuestToUserView.tsx @@ -1,31 +1,27 @@ -import React from "react"; -import SignUpForm from "../../components/forms/signupform"; -import { Center, Heading, Text } from "native-base"; -import API, { APIError } from "../../API"; -import { translate } from "../../i18n/i18n"; +import React from 'react'; +import SignUpForm from '../../components/forms/signupform'; +import { Center, Heading, Text } from 'native-base'; +import API, { APIError } from '../../API'; +import { translate } from '../../i18n/i18n'; -const handleSubmit = async ( - username: string, - password: string, - email: string -) => { +const handleSubmit = async (username: string, password: string, email: string) => { try { await API.transformGuestToUser({ username, password, email }); } catch (error) { if (error instanceof APIError) return translate(error.userMessage); if (error instanceof Error) return error.message; - return translate("unknownError"); + return translate('unknownError'); } - return translate("loggedIn"); + return translate('loggedIn'); }; const GuestToUserView = () => { return ( -
-
- {translate("signUp")} +
+
+ {translate('signUp')} - {translate("transformGuestToUserExplanations")} + {translate('transformGuestToUserExplanations')} diff --git a/front/views/settings/NotificationView.tsx b/front/views/settings/NotificationView.tsx index 7b52483..929f3a2 100644 --- a/front/views/settings/NotificationView.tsx +++ b/front/views/settings/NotificationView.tsx @@ -1,72 +1,80 @@ -import React from "react"; -import { Center, Heading } from "native-base"; -import { translate, Translate } from "../../i18n/i18n"; -import ElementList from "../../components/GtkUI/ElementList"; -import useUserSettings from "../../hooks/userSettings"; -import { LoadingView } from "../../components/Loading"; +import React from 'react'; +import { Center, Heading } from 'native-base'; +import { translate, Translate } from '../../i18n/i18n'; +import ElementList from '../../components/GtkUI/ElementList'; +import useUserSettings from '../../hooks/userSettings'; +import { LoadingView } from '../../components/Loading'; const NotificationsView = () => { const { settings, updateSettings } = useUserSettings(); if (!settings.data) { - return + return ; } return ( -
- +
+ { updateSettings({ - notifications: { pushNotif: !settings.data.notifications.pushNotif }, + notifications: { + pushNotif: !settings.data.notifications.pushNotif, + }, }); }, }, }, { - type: "toggle", - title: translate("SettingsNotificationsEmailNotifications"), + type: 'toggle', + title: translate('SettingsNotificationsEmailNotifications'), data: { value: settings.data.notifications.emailNotif, onToggle: () => { updateSettings({ - notifications: { emailNotif: !settings.data.notifications.emailNotif }, + notifications: { + emailNotif: !settings.data.notifications.emailNotif, + }, }); }, }, }, { - type: "toggle", - title: translate("SettingsNotificationsTrainingReminder"), + type: 'toggle', + title: translate('SettingsNotificationsTrainingReminder'), data: { value: settings.data.notifications.trainNotif, onToggle: () => { updateSettings({ - notifications: { trainNotif: !settings.data.notifications.trainNotif }, + notifications: { + trainNotif: !settings.data.notifications.trainNotif, + }, }); }, }, }, { - type: "toggle", - title: translate("SettingsNotificationsReleaseAlert"), + type: 'toggle', + title: translate('SettingsNotificationsReleaseAlert'), data: { value: settings.data.notifications.newSongNotif, onToggle: () => { updateSettings({ - notifications: { newSongNotif: !settings.data.notifications.newSongNotif }, + notifications: { + newSongNotif: !settings.data.notifications.newSongNotif, + }, }); }, }, diff --git a/front/views/settings/PreferencesView.tsx b/front/views/settings/PreferencesView.tsx index f53a7cb..1ed2eb0 100644 --- a/front/views/settings/PreferencesView.tsx +++ b/front/views/settings/PreferencesView.tsx @@ -1,19 +1,12 @@ -import React from "react"; -import { useDispatch } from "react-redux"; -import { - Center, - Heading, -} from "native-base"; -import { useLanguage } from "../../state/LanguageSlice"; -import { - AvailableLanguages, - DefaultLanguage, - translate, - Translate, -} from "../../i18n/i18n"; -import { useSelector } from "../../state/Store"; -import { updateSettings } from "../../state/SettingsSlice"; -import ElementList from "../../components/GtkUI/ElementList"; +import React from 'react'; +import { useDispatch } from 'react-redux'; +import { Center, Heading } from 'native-base'; +import { useLanguage } from '../../state/LanguageSlice'; +import { AvailableLanguages, DefaultLanguage, translate, Translate } from '../../i18n/i18n'; +import { useSelector } from '../../state/Store'; +import { updateSettings } from '../../state/SettingsSlice'; +import ElementList from '../../components/GtkUI/ElementList'; +import LocalSettings from '../../models/LocalSettings'; const PreferencesView = () => { const dispatch = useDispatch(); @@ -21,37 +14,39 @@ const PreferencesView = () => { const settings = useSelector((state) => state.settings.local); return (
- + { dispatch( - updateSettings({ colorScheme: newColorScheme as any }) + updateSettings({ + colorScheme: newColorScheme as LocalSettings['colorScheme'], + }) ); }, options: [ - { label: translate("dark"), value: "dark" }, - { label: translate("light"), value: "light" }, - { label: translate("system"), value: "system" }, + { label: translate('dark'), value: 'dark' }, + { label: translate('light'), value: 'light' }, + { label: translate('system'), value: 'system' }, ], }, }, { - type: "dropdown", - title: translate("SettingsPreferencesLanguage"), + type: 'dropdown', + title: translate('SettingsPreferencesLanguage'), data: { value: language, defaultValue: DefaultLanguage, @@ -59,25 +54,29 @@ const PreferencesView = () => { dispatch(useLanguage(itemValue as AvailableLanguages)); }, options: [ - { label: "Français", value: "fr" }, - { label: "English", value: "en" }, - { label: "Espanol", value: "sp" }, + { label: 'Français', value: 'fr' }, + { label: 'English', value: 'en' }, + { label: 'Espanol', value: 'sp' }, ], }, }, { - type: "dropdown", - title: translate("SettingsPreferencesDifficulty"), + type: 'dropdown', + title: translate('SettingsPreferencesDifficulty'), data: { value: settings.difficulty, - defaultValue: "medium", + defaultValue: 'medium', onSelect: (itemValue) => { - dispatch(updateSettings({ difficulty: itemValue as any })); + dispatch( + updateSettings({ + difficulty: itemValue as LocalSettings['difficulty'], + }) + ); }, options: [ - { label: translate("easy"), value: "beg" }, - { label: translate("medium"), value: "inter" }, - { label: translate("hard"), value: "pro" }, + { label: translate('easy'), value: 'beg' }, + { label: translate('medium'), value: 'inter' }, + { label: translate('hard'), value: 'pro' }, ], }, }, @@ -86,13 +85,13 @@ const PreferencesView = () => { { @@ -105,13 +104,13 @@ const PreferencesView = () => { { const dispatch = useDispatch(); @@ -14,22 +14,22 @@ const PrivacyView = () => { const { settings: userSettings, updateSettings: updateUserSettings } = useUserSettings(); if (!userSettings.data) { - return ; + return ; } return (
- {translate("privBtn")} + {translate('privBtn')} @@ -39,8 +39,8 @@ const PrivacyView = () => { }, }, { - type: "toggle", - title: translate("customAds"), + type: 'toggle', + title: translate('customAds'), data: { value: settings.customAds, onToggle: () => @@ -48,12 +48,14 @@ const PrivacyView = () => { }, }, { - type: "toggle", - title: translate("recommendations"), + type: 'toggle', + title: translate('recommendations'), data: { value: userSettings.data.recommendations, onToggle: () => - updateUserSettings({ recommendations: !userSettings.data.recommendations }) + updateUserSettings({ + recommendations: !userSettings.data.recommendations, + }), }, }, ]} diff --git a/front/views/settings/SettingsProfileView.tsx b/front/views/settings/SettingsProfileView.tsx index cc838cc..cbb289f 100644 --- a/front/views/settings/SettingsProfileView.tsx +++ b/front/views/settings/SettingsProfileView.tsx @@ -1,48 +1,43 @@ -import API from "../../API"; -import { useDispatch } from "react-redux"; -import { unsetAccessToken } from "../../state/UserSlice"; -import React from "react"; -import { - Column, - Text, - Button, - Box, - Flex, - Center, - Heading, - Avatar, - Popover, -} from "native-base"; -import TextButton from "../../components/TextButton"; -import { LoadingView } from "../../components/Loading"; -import ElementList from "../../components/GtkUI/ElementList"; -import { translate } from "../../i18n/i18n"; -import { useQuery } from "react-query"; +import API from '../../API'; +import { useDispatch } from 'react-redux'; +import { unsetAccessToken } from '../../state/UserSlice'; +import React from 'react'; +import { Column, Text, Button, Box, Flex, Center, Heading, Avatar, Popover } from 'native-base'; +import TextButton from '../../components/TextButton'; +import { LoadingView } from '../../components/Loading'; +import ElementList from '../../components/GtkUI/ElementList'; +import { translate } from '../../i18n/i18n'; +import { useQuery } from 'react-query'; const getInitials = (name: string) => { - return name.split(" ").map((n) => n[0]).join(""); + return name + .split(' ') + .map((n) => n[0]) + .join(''); }; +// Too painful to infer the settings-only, typed navigator. Gave up +// eslint-disable-next-line @typescript-eslint/no-explicit-any const ProfileSettings = ({ navigation }: { navigation: any }) => { const userQuery = useQuery(['user'], () => API.getUserInfo()); const dispatch = useDispatch(); if (!userQuery.data || userQuery.isLoading) { - return + return ; } const user = userQuery.data; return (
@@ -53,17 +48,17 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => { { - navigation.navigate("ChangeEmail"); + navigation.navigate('ChangeEmail'); }, }, }, @@ -73,54 +68,54 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => { { { }, }, { - type: "dropdown", - title: "Thème de piano", + type: 'dropdown', + title: 'Thème de piano', disabled: true, data: { - value: "default", + value: 'default', onSelect: () => {}, options: [ { - label: "Default", - value: "default", + label: 'Default', + value: 'default', }, { - label: "Catpuccino", - value: "catpuccino", + label: 'Catpuccino', + value: 'catpuccino', }, ], }, @@ -176,41 +171,39 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => { dispatch(unsetAccessToken())} translate={{ - translationKey: "signOutBtn", + translationKey: 'signOutBtn', }} /> )} {user.isGuest && ( ( - + )} > - {translate("Attention")} + {translate('Attention')} - {translate( - "YouAreCurrentlyConnectedWithAGuestAccountWarning" - )} + {translate('YouAreCurrentlyConnectedWithAGuestAccountWarning')} diff --git a/front/views/settings/SettingsView.tsx b/front/views/settings/SettingsView.tsx index 9283bc6..ab25aef 100644 --- a/front/views/settings/SettingsView.tsx +++ b/front/views/settings/SettingsView.tsx @@ -1,9 +1,6 @@ import React, { useMemo } from 'react'; -import { Center, Button, Text, Heading, Box } from "native-base"; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { unsetAccessToken } from '../../state/UserSlice'; -import { useDispatch } from "react-redux"; -import { translate, Translate } from "../../i18n/i18n"; +import { Center, Text, Heading, Box } from 'native-base'; +import { translate } from '../../i18n/i18n'; import createTabRowNavigator from '../../components/navigators/TabRowNavigator'; import { MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons'; import ChangePasswordForm from '../../components/forms/changePasswordForm'; @@ -17,69 +14,66 @@ import { useQuery } from 'react-query'; import API from '../../API'; const handleChangeEmail = async (newEmail: string): Promise => { - try { - let response = await API.updateUserEmail(newEmail); - return translate('emailUpdated'); - } catch (e) { - throw e; - } -} + await API.updateUserEmail(newEmail); + return translate('emailUpdated'); +}; const handleChangePassword = async (oldPassword: string, newPassword: string): Promise => { - try { - let response = await API.updateUserPassword(oldPassword, newPassword); - return translate('passwordUpdated'); - } catch (e) { - throw e; - } -} + await API.updateUserPassword(oldPassword, newPassword); + return translate('passwordUpdated'); +}; export const ChangePasswordView = () => { return ( -
+
{translate('changePassword')} - handleChangePassword(oldPassword, newPassword)}/> + + handleChangePassword(oldPassword, newPassword) + } + />
- ) -} + ); +}; export const ChangeEmailView = () => { return ( -
- {translate('changeEmail')} - handleChangeEmail(newEmail)}/> -
- ) -} +
+ {translate('changeEmail')} + handleChangeEmail(newEmail)} /> +
+ ); +}; export const GoogleAccountView = () => { return ( -
+
GoogleAccount
- ) -} + ); +}; export const PianoSettingsView = () => { return ( -
+
Global settings for the virtual piano
- ) -} + ); +}; const TabRow = createTabRowNavigator(); type SetttingsNavigatorProps = { - screen?: 'Profile' | - 'Preferences' | - 'Notifications' | - 'Privacy' | - 'ChangePassword' | - 'ChangeEmail' | - 'GoogleAccount' | - 'PianoSettings' -} + screen?: + | 'Profile' + | 'Preferences' + | 'Notifications' + | 'Privacy' + | 'ChangePassword' + | 'ChangeEmail' + | 'GoogleAccount' + | 'PianoSettings'; +}; const SetttingsNavigator = (props?: SetttingsNavigatorProps) => { const userQuery = useQuery(['user'], () => API.getUserInfo()); @@ -87,66 +81,106 @@ const SetttingsNavigator = (props?: SetttingsNavigatorProps) => { if (userQuery.isLoading) { return ( -
+
Loading...
- ) + ); } return ( - + {/* I'm doing this to be able to land on the summary of settings when clicking on settings and directly to the wanted settings page if needed so I need to do special work with the 0 index */} - - {user && user.isGuest && - + {user && user.isGuest && ( + + )} + - } - - - - - - - - + iconName: 'user', + }} + /> + + + + + + + - ) -} + ); +}; -export default SetttingsNavigator; \ No newline at end of file +export default SetttingsNavigator; diff --git a/front/webpack.config.js b/front/webpack.config.js index f8027a2..20c4ca8 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -1,17 +1,15 @@ -const createExpoWebpackConfigAsync = require('@expo/webpack-config') +const createExpoWebpackConfigAsync = require('@expo/webpack-config'); module.exports = async function (env, argv) { - const config = await createExpoWebpackConfigAsync( - { - ...env, - babel: { dangerouslyAddModulePathsToTranspile: ['moti'] }, - }, - argv - ) + const config = await createExpoWebpackConfigAsync( + { + ...env, + babel: { dangerouslyAddModulePathsToTranspile: ['moti'] }, + }, + argv + ); - config.resolve.alias['framer-motion'] = 'framer-motion/dist/framer-motion' + config.resolve.alias['framer-motion'] = 'framer-motion/dist/framer-motion'; - - - return config -} \ No newline at end of file + return config; +}; diff --git a/front/yarn.lock b/front/yarn.lock index 97d7753..cbc5877 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -1415,6 +1415,38 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" + integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== + +"@eslint/eslintrc@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" + integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.5.2" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.42.0": + version "8.42.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" + integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== + "@expo/bunyan@4.0.0", "@expo/bunyan@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@expo/bunyan/-/bunyan-4.0.0.tgz#be0c1de943c7987a9fbd309ea0b1acd605890c7b" @@ -1899,6 +1931,25 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@humanwhocodes/config-array@^0.11.10": + version "0.11.10" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" + integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + "@internationalized/date@^3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.0.2.tgz#1566a0bcbd82dce4dd54a5b26456bb701068cb89" @@ -2384,7 +2435,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -4680,6 +4731,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-schema@^7.0.9": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + "@types/keyv@^3.1.4": version "3.1.4" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" @@ -4833,6 +4889,11 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== +"@types/semver@^7.3.12": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== + "@types/source-list-map@*": version "0.1.2" resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" @@ -4927,6 +4988,90 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@^5.43.0": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz#8d466aa21abea4c3f37129997b198d141f09e76f" + integrity sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg== + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.59.11" + "@typescript-eslint/type-utils" "5.59.11" + "@typescript-eslint/utils" "5.59.11" + debug "^4.3.4" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.0.0": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.11.tgz#af7d4b7110e3068ce0b97550736de455e4250103" + integrity sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA== + dependencies: + "@typescript-eslint/scope-manager" "5.59.11" + "@typescript-eslint/types" "5.59.11" + "@typescript-eslint/typescript-estree" "5.59.11" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz#5d131a67a19189c42598af9fb2ea1165252001ce" + integrity sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q== + dependencies: + "@typescript-eslint/types" "5.59.11" + "@typescript-eslint/visitor-keys" "5.59.11" + +"@typescript-eslint/type-utils@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz#5eb67121808a84cb57d65a15f48f5bdda25f2346" + integrity sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g== + dependencies: + "@typescript-eslint/typescript-estree" "5.59.11" + "@typescript-eslint/utils" "5.59.11" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.11.tgz#1a9018fe3c565ba6969561f2a49f330cf1fe8db1" + integrity sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA== + +"@typescript-eslint/typescript-estree@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz#b2caaa31725e17c33970c1197bcd54e3c5f42b9f" + integrity sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA== + dependencies: + "@typescript-eslint/types" "5.59.11" + "@typescript-eslint/visitor-keys" "5.59.11" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.11.tgz#9dbff49dc80bfdd9289f9f33548f2e8db3c59ba1" + integrity sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.59.11" + "@typescript-eslint/types" "5.59.11" + "@typescript-eslint/typescript-estree" "5.59.11" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.59.11": + version "5.59.11" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz#dca561ddad169dc27d62396d64f45b2d2c3ecc56" + integrity sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA== + dependencies: + "@typescript-eslint/types" "5.59.11" + eslint-visitor-keys "^3.3.0" + "@urql/core@2.3.6": version "2.3.6" resolved "https://registry.yarnpkg.com/@urql/core/-/core-2.3.6.tgz#ee0a6f8fde02251e9560c5f17dce5cd90f948552" @@ -5371,7 +5516,7 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -5391,7 +5536,7 @@ acorn@^7.1.1, acorn@^7.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.1: +acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: version "8.8.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== @@ -5473,7 +5618,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -5701,7 +5846,7 @@ array-flatten@^2.1.0: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== -array-includes@^3.0.3: +array-includes@^3.0.3, array-includes@^3.1.5, array-includes@^3.1.6: version "3.1.6" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== @@ -5744,7 +5889,7 @@ array.prototype.flat@^1.2.1: es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.2.1: +array.prototype.flatmap@^1.2.1, array.prototype.flatmap@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== @@ -5776,6 +5921,17 @@ array.prototype.reduce@^1.0.5: es-array-method-boxes-properly "^1.0.0" is-string "^1.0.7" +array.prototype.tosorted@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz#ccf44738aa2b5ac56578ffda97c03fd3e23dd532" + integrity sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.1.3" + arrify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" @@ -7435,7 +7591,7 @@ cross-fetch@^3.1.5: dependencies: node-fetch "2.6.7" -cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.3: +cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -7810,7 +7966,7 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@~0.1.3: +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== @@ -8065,6 +8221,13 @@ dns-txt@^2.0.2: dependencies: buffer-indexof "^1.0.0" +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -8572,7 +8735,40 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-scope@5.1.1: +eslint-config-prettier@^8.3.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348" + integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== + +eslint-plugin-prettier@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-react@^7.31.11: + version "7.32.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz#e71f21c7c265ebce01bcbc9d0955170c55571f10" + integrity sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg== + dependencies: + array-includes "^3.1.6" + array.prototype.flatmap "^1.3.1" + array.prototype.tosorted "^1.1.1" + doctrine "^2.1.0" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.6" + object.fromentries "^2.0.6" + object.hasown "^1.1.2" + object.values "^1.1.6" + prop-types "^15.8.1" + resolve "^2.0.0-next.4" + semver "^6.3.0" + string.prototype.matchall "^4.0.8" + +eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -8588,11 +8784,85 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" + integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" + integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== + +eslint@^8.42.0: + version "8.42.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.42.0.tgz#7bebdc3a55f9ed7167251fe7259f75219cade291" + integrity sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.4.0" + "@eslint/eslintrc" "^2.0.3" + "@eslint/js" "8.42.0" + "@humanwhocodes/config-array" "^0.11.10" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.0" + eslint-visitor-keys "^3.4.1" + espree "^9.5.2" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.1" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + +espree@^9.5.2: + version "9.5.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" + integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + esrecurse@^4.1.0, esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -8605,7 +8875,7 @@ estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.2.0: +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== @@ -9037,6 +9307,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + fast-glob@^2.2.6: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -9070,7 +9345,7 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== @@ -9148,6 +9423,13 @@ figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + file-loader@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" @@ -9728,6 +10010,13 @@ glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob-promise@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-3.4.0.tgz#b6b8f084504216f702dc2ce8c9bc9ac8866fdb20" @@ -9820,6 +10109,13 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^13.19.0: + version "13.20.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== + dependencies: + type-fest "^0.20.2" + globalthis@^1.0.0, globalthis@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" @@ -9839,7 +10135,7 @@ globby@11.0.1: merge2 "^1.3.0" slash "^3.0.0" -globby@^11.0.1, globby@^11.0.2: +globby@^11.0.1, globby@^11.0.2, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -9922,6 +10218,11 @@ grapheme-splitter@^1.0.4: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + graphql-tag@^2.10.1: version "2.12.6" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" @@ -10542,7 +10843,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.1.0, import-fresh@^3.2.1: +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -10973,7 +11274,7 @@ is-glob@^3.0.0, is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -11060,7 +11361,7 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" -is-path-inside@^3.0.2: +is-path-inside@^3.0.2, is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -12042,6 +12343,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json3@^3.3.2: version "3.3.3" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" @@ -12087,6 +12393,14 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.3.3" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea" + integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw== + dependencies: + array-includes "^3.1.5" + object.assign "^4.1.3" + jszip@3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" @@ -12179,6 +12493,14 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -13161,7 +13483,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1: +"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -13455,6 +13777,11 @@ native-base@^3.4.17: tinycolor2 "^1.4.2" use-subscription "^1.8.0" +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -13814,7 +14141,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0, object.assign@^4.1.4: +object.assign@^4.1.0, object.assign@^4.1.3, object.assign@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== @@ -13824,7 +14151,7 @@ object.assign@^4.1.0, object.assign@^4.1.4: has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.0: +object.entries@^1.1.0, object.entries@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.6.tgz#9737d0e5b8291edd340a3e3264bb8a3b00d5fa23" integrity sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w== @@ -13833,7 +14160,7 @@ object.entries@^1.1.0: define-properties "^1.1.4" es-abstract "^1.20.4" -"object.fromentries@^2.0.0 || ^1.0.0": +"object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73" integrity sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg== @@ -13863,6 +14190,14 @@ object.getownpropertydescriptors@^2.1.0: es-abstract "^1.21.2" safe-array-concat "^1.0.0" +object.hasown@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.2.tgz#f919e21fad4eb38a57bc6345b3afd496515c3f92" + integrity sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw== + dependencies: + define-properties "^1.1.4" + es-abstract "^1.20.4" + object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" @@ -13870,7 +14205,7 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.1.0: +object.values@^1.1.0, object.values@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== @@ -14005,6 +14340,18 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + ora@3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" @@ -14890,16 +15237,33 @@ prebuild-install@^7.1.1: tar-fs "^2.0.0" tunnel-agent "^0.6.0" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + "prettier@>=2.2.1 <=2.3.0": version "2.3.0" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18" integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== +prettier@^2.8.8: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + pretty-bytes@5.6.0, pretty-bytes@^5.1.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" @@ -15050,7 +15414,7 @@ prompts@^2.0.1, prompts@^2.2.1, prompts@^2.3.2, prompts@^2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.7.2: +prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -16034,6 +16398,15 @@ resolve@^1.10.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.18.1, resolve@^1.1 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^2.0.0-next.4: + version "2.0.0-next.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" + integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@~1.7.1: version "1.7.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" @@ -16321,6 +16694,13 @@ semver@^7.0.0, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semve dependencies: lru-cache "^6.0.0" +semver@^7.3.7: + version "7.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" + integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== + dependencies: + lru-cache "^6.0.0" + send@0.18.0, send@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -16986,7 +17366,7 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -"string.prototype.matchall@^4.0.0 || ^3.0.1": +"string.prototype.matchall@^4.0.0 || ^3.0.1", string.prototype.matchall@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3" integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg== @@ -17128,6 +17508,11 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -17682,7 +18067,7 @@ ts-pnp@^1.1.6: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== -tslib@^1.13.0, tslib@^1.9.3: +tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -17697,6 +18082,13 @@ tslib@^2.5.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -17714,6 +18106,13 @@ tunnel@^0.0.6: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -18718,7 +19117,7 @@ wonka@^6.1.2: resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.1.2.tgz#2c66fa5b26a12f002a03619b988258313d0b5352" integrity sha512-zNrXPMccg/7OEp9tSfFkMgTvhhowqasiSHdJ3eCZolXxVTV/aT6HUTofoZk9gwRbGoFey/Nss3JaZKUMKMbofg== -word-wrap@~1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==